refactor: serach function now async which is way faster

This commit is contained in:
pandadev 2025-03-15 17:44:14 +01:00
parent ae878b7203
commit f435a7b20a
No known key found for this signature in database
GPG key ID: C39629DACB8E762F
14 changed files with 203 additions and 106 deletions

View file

@ -38,25 +38,25 @@ onMounted(async () => {
@font-face {
font-family: SFRoundedRegular;
font-display: swap;
src: url("~/assets/fonts/SFRoundedRegular.otf") format("opentype");
src: url("/fonts/SFRoundedRegular.otf") format("opentype");
}
@font-face {
font-family: SFRoundedMedium;
font-display: swap;
src: url("~/assets/fonts/SFRoundedMedium.otf") format("opentype");
src: url("/fonts/SFRoundedMedium.otf") format("opentype");
}
@font-face {
font-family: SFRoundedSemiBold;
font-display: swap;
src: url("~/assets/fonts/SFRoundedSemiBold.otf") format("opentype");
src: url("/fonts/SFRoundedSemiBold.otf") format("opentype");
}
@font-face {
font-family: CommitMono;
font-display: swap;
src: url("~/assets/fonts/CommitMono.woff2") format("woff2");
src: url("/fonts/CommitMono.woff2") format("woff2");
}
:root {
@ -85,6 +85,7 @@ onMounted(async () => {
font-family: SFRoundedRegular;
scroll-behavior: smooth;
scrollbar-width: thin;
user-select: none;
--os-handle-bg: #ada9a1;
--os-handle-bg-hover: #78756f;

View file

@ -3,7 +3,7 @@
<input
ref="searchInput"
v-model="searchQuery"
@input="onSearch"
@input="onInputChange"
class="search"
autocorrect="off"
autocapitalize="off"
@ -21,10 +21,12 @@ const searchInput = ref<HTMLInputElement | null>(null);
const emit = defineEmits<{
(e: "search", query: string): void;
(e: "searchStarted"): void;
(e: "focus"): void;
}>();
const onSearch = () => {
const onInputChange = () => {
emit("searchStarted");
emit("search", searchQuery.value);
};

View file

@ -1,64 +1,40 @@
<template>
<main>
<TopBar ref="topBar" @search="searchHistory" />
<TopBar ref="topBar" @search="searchHistory" @searchStarted="searchStarted" />
<div class="container">
<OverlayScrollbarsComponent
class="results"
ref="resultsContainer"
<OverlayScrollbarsComponent class="results" ref="resultsContainer"
:options="{ scrollbars: { autoHide: 'scroll' } }">
<div
v-for="(group, groupIndex) in groupedHistory"
:key="groupIndex"
class="group">
<div v-for="(group, groupIndex) in groupedHistory" :key="groupIndex" class="group">
<div class="time-separator">{{ group.label }}</div>
<div class="results-group">
<Result
v-for="(item, index) in group.items"
:key="item.id"
:item="item"
:selected="isSelected(groupIndex, index)"
:image-url="imageUrls[item.id]"
:dimensions="imageDimensions[item.id]"
@select="selectItem(groupIndex, index)"
@image-error="onImageError"
<Result v-for="(item, index) in group.items" :key="item.id" :item="item"
:selected="isSelected(groupIndex, index)" :image-url="imageUrls[item.id]"
:dimensions="imageDimensions[item.id]" @select="selectItem(groupIndex, index)" @image-error="onImageError"
@setRef="(el) => (selectedElement = el)" />
</div>
</div>
</OverlayScrollbarsComponent>
<div class="right">
<div
class="content"
v-if="selectedItem?.content_type === ContentType.Image">
<div class="content" v-if="selectedItem?.content_type === ContentType.Image">
<img :src="imageUrls[selectedItem.id]" alt="Image" class="image" />
</div>
<div
v-else-if="selectedItem && isYoutubeWatchUrl(selectedItem.content)"
class="content">
<img
class="image"
:src="getYoutubeThumbnail(selectedItem.content)"
alt="YouTube Thumbnail" />
<div v-else-if="selectedItem && isYoutubeWatchUrl(selectedItem.content)" class="content">
<img class="image" :src="getYoutubeThumbnail(selectedItem.content)" alt="YouTube Thumbnail" />
</div>
<div
class="content"
v-else-if="
selectedItem?.content_type === ContentType.Link && pageOgImage
">
<div class="content" v-else-if="
selectedItem?.content_type === ContentType.Link && pageOgImage
">
<img :src="pageOgImage" alt="Image" class="image" />
</div>
<OverlayScrollbarsComponent v-else class="content">
<span class="content-text">{{ selectedItem?.content || "" }}</span>
</OverlayScrollbarsComponent>
<OverlayScrollbarsComponent
class="information"
:options="{ scrollbars: { autoHide: 'scroll' } }">
<OverlayScrollbarsComponent class="information" :options="{ scrollbars: { autoHide: 'scroll' } }">
<div class="title">Information</div>
<div class="info-content" v-if="selectedItem && getInfo">
<div class="info-row" v-for="(row, index) in infoRows" :key="index">
<p class="label">{{ row.label }}</p>
<span
:class="{ 'url-truncate': row.isUrl }"
:data-text="row.value">
<span :class="{ 'url-truncate': row.isUrl }" :data-text="row.value">
<img v-if="row.icon" :src="row.icon" :alt="String(row.value)">
{{ row.value }}
</span>
@ -67,17 +43,15 @@
</OverlayScrollbarsComponent>
</div>
</div>
<BottomBar
:primary-action="{
text: 'Paste',
icon: IconsEnter,
onClick: pasteSelectedItem,
}"
:secondary-action="{
text: 'Actions',
icon: IconsK,
showModifier: true,
}" />
<BottomBar :primary-action="{
text: 'Paste',
icon: IconsEnter,
onClick: pasteSelectedItem,
}" :secondary-action="{
text: 'Actions',
icon: IconsK,
showModifier: true,
}" />
</main>
</template>
@ -156,7 +130,7 @@ const getWeekNumber = (date: Date): number => {
((date.getTime() - firstDayOfYear.getTime()) / 86400000 +
firstDayOfYear.getDay() +
1) /
7
7
);
};
@ -177,8 +151,8 @@ const groupedHistory = computed<GroupedHistory[]>(() => {
const filteredItems = searchQuery.value
? history.value.filter((item) =>
item.content.toLowerCase().includes(searchQuery.value.toLowerCase())
)
item.content.toLowerCase().includes(searchQuery.value.toLowerCase())
)
: history.value;
const yesterday = new Date(today.getTime() - 86400000);
@ -321,32 +295,78 @@ const scrollToSelectedItem = (): void => {
});
};
let searchController: AbortController | null = null;
let searchQueue: Array<string> = [];
let isProcessingSearch = false;
const searchStarted = () => {
if (searchController) {
searchController.abort();
}
};
const processSearchQueue = async () => {
if (isProcessingSearch || searchQueue.length === 0) return;
isProcessingSearch = true;
const query = searchQueue.pop();
searchQueue = [];
try {
if (!query || !query.trim()) {
history.value = [];
offset = 0;
await loadHistoryChunk();
isProcessingSearch = false;
return;
}
const results = await $history.searchHistory(query);
if (searchController?.signal.aborted) {
isProcessingSearch = false;
return;
}
history.value = results.map((item) =>
Object.assign(
new HistoryItem(
item.source,
item.content_type,
item.content,
item.favicon,
item.source_icon,
item.language
),
{ id: item.id, timestamp: new Date(item.timestamp) }
)
);
if (groupedHistory.value.length > 0) {
handleSelection(0, 0, false);
}
} catch (error) {
console.error("Search error:", error);
} finally {
isProcessingSearch = false;
if (searchQueue.length > 0) {
requestAnimationFrame(() => processSearchQueue());
}
}
};
const searchHistory = async (query: string): Promise<void> => {
searchQuery.value = query;
if (!query.trim()) {
history.value = [];
offset = 0;
await loadHistoryChunk();
return;
if (searchController) {
searchController.abort();
}
const results = await $history.searchHistory(query);
history.value = results.map((item) =>
Object.assign(
new HistoryItem(
item.source,
item.content_type,
item.content,
item.favicon,
item.source_icon,
item.language
),
{ id: item.id, timestamp: new Date(item.timestamp) }
)
);
searchController = new AbortController();
if (groupedHistory.value.length > 0) {
handleSelection(0, 0, false);
searchQueue.push(query);
if (!isProcessingSearch) {
processSearchQueue();
}
};
@ -517,14 +537,14 @@ const setupEventListeners = async (): Promise<void> => {
switch (os.value) {
case "macos":
keyboard.prevent.down([Key.LeftMeta, Key.K], () => {});
keyboard.prevent.down([Key.RightMeta, Key.K], () => {});
keyboard.prevent.down([Key.LeftMeta, Key.K], () => { });
keyboard.prevent.down([Key.RightMeta, Key.K], () => { });
break;
case "linux":
case "windows":
keyboard.prevent.down([Key.LeftControl, Key.K], () => {});
keyboard.prevent.down([Key.RightControl, Key.K], () => {});
keyboard.prevent.down([Key.LeftControl, Key.K], () => { });
keyboard.prevent.down([Key.RightControl, Key.K], () => { });
break;
}
});
@ -552,14 +572,14 @@ const setupEventListeners = async (): Promise<void> => {
switch (os.value) {
case "macos":
keyboard.prevent.down([Key.LeftMeta, Key.K], () => {});
keyboard.prevent.down([Key.RightMeta, Key.K], () => {});
keyboard.prevent.down([Key.LeftMeta, Key.K], () => { });
keyboard.prevent.down([Key.RightMeta, Key.K], () => { });
break;
case "linux":
case "windows":
keyboard.prevent.down([Key.LeftControl, Key.K], () => {});
keyboard.prevent.down([Key.RightControl, Key.K], () => {});
keyboard.prevent.down([Key.LeftControl, Key.K], () => { });
keyboard.prevent.down([Key.RightControl, Key.K], () => { });
break;
}
};
@ -785,7 +805,7 @@ const infoRows = computed(() => {
],
[ContentType.Link]: [
...((getInfo.value as InfoLink).title &&
(getInfo.value as InfoLink).title !== "Loading..."
(getInfo.value as InfoLink).title !== "Loading..."
? [{ label: "Title", value: (getInfo.value as InfoLink).title || "" }]
: []),
{ label: "URL", value: (getInfo.value as InfoLink).url, isUrl: true },
@ -815,5 +835,5 @@ const infoRows = computed(() => {
</script>
<style scoped lang="scss">
@use "~/assets/css/index.scss";
@use "/styles/index.scss";
</style>

View file

@ -228,5 +228,5 @@ onUnmounted(() => {
</script>
<style scoped lang="scss">
@use "~/assets/css/settings.scss";
@use "/styles/settings.scss";
</style>

View file

@ -14,17 +14,27 @@ export default defineNuxtPlugin(() => {
},
async searchHistory(query: string): Promise<HistoryItem[]> {
return await invoke<HistoryItem[]>("search_history", { query });
try {
return await invoke<HistoryItem[]>("search_history", { query });
} catch (error) {
console.error("Error searching history:", error);
return [];
}
},
async loadHistoryChunk(
offset: number,
limit: number
): Promise<HistoryItem[]> {
return await invoke<HistoryItem[]>("load_history_chunk", {
offset,
limit,
});
try {
return await invoke<HistoryItem[]>("load_history_chunk", {
offset,
limit,
});
} catch (error) {
console.error("Error loading history chunk:", error);
return [];
}
},
async deleteHistoryItem(id: string): Promise<void> {

49
src-tauri/Cargo.lock generated
View file

@ -77,15 +77,17 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "applications"
version = "0.3.0"
source = "git+https://github.com/HuakunShen/applications-rs?branch=load_icon#bcfbebb93a57918aca4ba51257b2788be90892da"
source = "git+https://github.com/HuakunShen/applications-rs?branch=fix/win-app-detection#ddcec7cdc3cc4f6678f1b1b525b3e509987b21ae"
dependencies = [
"anyhow",
"cocoa 0.25.0",
"core-foundation 0.9.4",
"env_logger",
"glob",
"image",
"ini",
"lnk",
"log",
"objc",
"parselnk",
"plist",
@ -1359,6 +1361,19 @@ dependencies = [
"syn 2.0.87",
]
[[package]]
name = "env_logger"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
name = "equivalent"
version = "1.0.1"
@ -2121,6 +2136,12 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
[[package]]
name = "hermit-abi"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
[[package]]
name = "hex"
version = "0.4.3"
@ -2208,6 +2229,12 @@ version = "1.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9"
[[package]]
name = "humantime"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
[[package]]
name = "hyper"
version = "1.4.1"
@ -2584,6 +2611,17 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "is-terminal"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi 0.5.0",
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "itertools"
version = "0.12.1"
@ -5957,6 +5995,15 @@ dependencies = [
"utf-8",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "texting_robots"
version = "0.2.2"

View file

@ -49,7 +49,7 @@ log = { version = "0.4.26", features = ["std"] }
uuid = "1.16.0"
include_dir = "0.7.4"
# hyperpolyglot = { git = "https://github.com/0pandadev/hyperpolyglot" }
applications = { git = "https://github.com/HuakunShen/applications-rs", branch = "load_icon" }
applications = { git = "https://github.com/HuakunShen/applications-rs", branch = "fix/win-app-detection" }
glob = "0.3.2"
meta_fetcher = "0.1.1"
parking_lot = "0.12.3"

View file

@ -111,18 +111,27 @@ pub async fn search_history(
pool: tauri::State<'_, SqlitePool>,
query: String
) -> Result<Vec<HistoryItem>, String> {
if query.trim().is_empty() {
return Ok(Vec::new());
}
let query = format!("%{}%", query);
let rows = sqlx
::query(
"SELECT id, source, source_icon, content_type, content, favicon, timestamp, language FROM history WHERE content LIKE ? ORDER BY timestamp DESC"
"SELECT id, source, source_icon, content_type, content, favicon, timestamp, language
FROM history
WHERE content LIKE ?
ORDER BY timestamp DESC
LIMIT 100"
)
.bind(query)
.fetch_all(&*pool).await
.map_err(|e| e.to_string())?;
let items = rows
.iter()
.map(|row| HistoryItem {
let mut items = Vec::with_capacity(rows.len());
for row in rows.iter() {
items.push(HistoryItem {
id: row.get("id"),
source: row.get("source"),
source_icon: row.get("source_icon"),
@ -131,8 +140,8 @@ pub async fn search_history(
favicon: row.get("favicon"),
timestamp: row.get("timestamp"),
language: row.get("language"),
})
.collect();
});
}
Ok(items)
}

View file

@ -176,3 +176,11 @@ main {
}
}
}
.search-loading {
display: flex;
justify-content: center;
padding: 20px;
font-size: 16px;
color: var(--text-secondary);
}