From c078b631f6f0f6223b0ab7d204d484b3d7da9361 Mon Sep 17 00:00:00 2001 From: PandaDEV <70103896+0PandaDEV@users.noreply.github.com> Date: Sun, 15 Dec 2024 18:53:18 +1000 Subject: [PATCH] feat: use wrdu-keyboard --- app.vue | 2 +- assets/css/index.scss | 19 ++- assets/css/{keybind.scss => settings.scss} | 0 pages/index.vue | 188 ++++++++++++++------- pages/{keybind.vue => settings.vue} | 50 +++--- 5 files changed, 173 insertions(+), 86 deletions(-) rename assets/css/{keybind.scss => settings.scss} (100%) rename pages/{keybind.vue => settings.vue} (83%) diff --git a/app.vue b/app.vue index a54b0c0..ef6e0aa 100644 --- a/app.vue +++ b/app.vue @@ -12,7 +12,7 @@ import { onMounted } from 'vue' onMounted(async () => { await listen('change_keybind', async () => { console.log("change_keybind"); - await navigateTo('/keybind') + await navigateTo('/settings') await app.show(); await window.getCurrentWindow().show(); }) diff --git a/assets/css/index.scss b/assets/css/index.scss index aab6b24..003253d 100644 --- a/assets/css/index.scss +++ b/assets/css/index.scss @@ -99,7 +99,7 @@ $mutedtext: #78756f; top: 53px; left: 284px; padding: 8px; - height: calc(100vh - 96px); + height: calc(100vh - 256px); font-family: CommitMono Nerd Font !important; font-size: 14px; letter-spacing: 1; @@ -210,6 +210,23 @@ $mutedtext: #78756f; } } +.information { + position: absolute; + bottom: 40px; + left: 284px; + height: 160px; + width: calc(100vw - 286px); + border-top: 1px solid $divider; + background-color: $primary; + padding: 14px; + + .title { + font-family: SFRoundedSemiBold; + font-size: 12px; + letter-spacing: 0.6px; + } +} + .clothoid-corner { clip-path: polygon( 13.890123px 0px, diff --git a/assets/css/keybind.scss b/assets/css/settings.scss similarity index 100% rename from assets/css/keybind.scss rename to assets/css/settings.scss diff --git a/pages/index.vue b/pages/index.vue index 57864aa..22a727d 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -1,11 +1,5 @@ - Image ({{ imageDimensions[item.id] || 'Loading...' }}) + Image ({{ imageDimensions[item.id] || "Loading..." }}) {{ truncateContent(item.content) }} @@ -107,6 +98,9 @@ class="full-image" /> {{ selectedItem?.content || "" }} +
+
Information
+
@@ -136,7 +130,9 @@ const history = shallowRef([]); let offset = 0; let isLoading = false; -const resultsContainer = shallowRef | null>(null); +const resultsContainer = shallowRef | null>(null); const searchQuery = ref(""); const selectedGroupIndex = ref(0); const selectedItemIndex = ref(0); @@ -149,15 +145,24 @@ const lastUpdateTime = ref(Date.now()); const imageLoadError = ref(false); const imageLoading = ref(false); +const keyboard = useKeyboard(); + const isSameDay = (date1: Date, date2: Date): boolean => { - return date1.getFullYear() === date2.getFullYear() - && date1.getMonth() === date2.getMonth() - && date1.getDate() === date2.getDate(); + return ( + date1.getFullYear() === date2.getFullYear() && + date1.getMonth() === date2.getMonth() && + date1.getDate() === date2.getDate() + ); }; const getWeekNumber = (date: Date): number => { const firstDayOfYear = new Date(date.getFullYear(), 0, 1); - return Math.ceil(((date.getTime() - firstDayOfYear.getTime()) / 86400000 + firstDayOfYear.getDay() + 1) / 7); + return Math.ceil( + ((date.getTime() - firstDayOfYear.getTime()) / 86400000 + + firstDayOfYear.getDay() + + 1) / + 7 + ); }; const groupedHistory = computed(() => { @@ -169,30 +174,33 @@ const groupedHistory = computed(() => { const groups: Record = { Today: [], Yesterday: [], - 'This Week': [], - 'Last Week': [], - 'This Year': [], - 'Last Year': [] + "This Week": [], + "Last Week": [], + "This Year": [], + "Last Year": [], }; const filteredItems = searchQuery.value - ? history.value.filter(item => - item.content.toLowerCase().includes(searchQuery.value.toLowerCase())) + ? history.value.filter((item) => + item.content.toLowerCase().includes(searchQuery.value.toLowerCase()) + ) : history.value; const yesterday = new Date(today.getTime() - 86400000); - filteredItems.forEach(item => { + filteredItems.forEach((item) => { const itemDate = new Date(item.timestamp); const itemWeek = getWeekNumber(itemDate); const itemYear = itemDate.getFullYear(); if (isSameDay(itemDate, today)) groups.Today.push(item); else if (isSameDay(itemDate, yesterday)) groups.Yesterday.push(item); - else if (itemYear === thisYear && itemWeek === thisWeek) groups['This Week'].push(item); - else if (itemYear === thisYear && itemWeek === thisWeek - 1) groups['Last Week'].push(item); - else if (itemYear === thisYear) groups['This Year'].push(item); - else groups['Last Year'].push(item); + else if (itemYear === thisYear && itemWeek === thisWeek) + groups["This Week"].push(item); + else if (itemYear === thisYear && itemWeek === thisWeek - 1) + groups["Last Week"].push(item); + else if (itemYear === thisYear) groups["This Year"].push(item); + else groups["Last Year"].push(item); }); return Object.entries(groups) @@ -217,21 +225,22 @@ const loadHistoryChunk = async (): Promise => { } const processedItems = await Promise.all( - results.map(async item => { + results.map(async (item) => { const historyItem = new HistoryItem( - item.content_type as ContentType, + item.source, + item.content_type, item.content, item.favicon ); Object.assign(historyItem, { id: item.id, - timestamp: new Date(item.timestamp) + timestamp: new Date(item.timestamp), }); if (historyItem.content_type === ContentType.Image) { await Promise.all([ getItemDimensions(historyItem), - loadImageUrl(historyItem) + loadImageUrl(historyItem), ]); } return historyItem; @@ -266,7 +275,7 @@ const scrollToSelectedItem = (forceScrollTop: boolean = false): void => { if (!forceScrollTop) { const viewportRect = viewport.getBoundingClientRect(); const elementRect = selectedElement.value.getBoundingClientRect(); - + const isAbove = elementRect.top < viewportRect.top; const isBelow = elementRect.bottom > viewportRect.bottom - 8; @@ -290,10 +299,17 @@ const isSelected = (groupIndex: number, itemIndex: number): boolean => { const searchHistory = async (): Promise => { const results = await $history.searchHistory(searchQuery.value); - history.value = results.map(item => Object.assign( - new HistoryItem(item.content_type as ContentType, item.content, item.favicon), - { id: item.id, timestamp: new Date(item.timestamp) } - )); + history.value = results.map((item) => + Object.assign( + new HistoryItem( + item.source, + item.content_type, + item.content, + item.favicon + ), + { id: item.id, timestamp: new Date(item.timestamp) } + ) + ); }; const selectNext = (): void => { @@ -378,13 +394,15 @@ const getFaviconFromDb = (favicon: string): string => { return `data:image/png;base64,${favicon}`; }; -const getImageData = async (item: HistoryItem): Promise<{ url: string; dimensions: string }> => { +const getImageData = async ( + item: HistoryItem +): Promise<{ url: string; dimensions: string }> => { try { const base64 = await $history.readImage({ filename: item.content }); const dataUrl = `data:image/png;base64,${base64}`; const img = new Image(); img.src = dataUrl; - + await new Promise((resolve, reject) => { img.onload = () => resolve(); img.onerror = reject; @@ -392,7 +410,7 @@ const getImageData = async (item: HistoryItem): Promise<{ url: string; dimension return { url: dataUrl, - dimensions: `${img.width}x${img.height}` + dimensions: `${img.width}x${img.height}`, }; } catch (error) { console.error("Error processing image:", error); @@ -402,14 +420,15 @@ const getImageData = async (item: HistoryItem): Promise<{ url: string; dimension const processHistoryItem = async (item: any): Promise => { const historyItem = new HistoryItem( - item.content_type as ContentType, + item.content_type as string, + ContentType[item.content_type as keyof typeof ContentType], item.content, item.favicon ); - + Object.assign(historyItem, { id: item.id, - timestamp: new Date(item.timestamp) + timestamp: new Date(item.timestamp), }); if (historyItem.content_type === ContentType.Image) { @@ -425,33 +444,43 @@ const updateHistory = async (resetScroll: boolean = false): Promise => { history.value = []; offset = 0; await loadHistoryChunk(); - - if (resetScroll && resultsContainer.value?.osInstance()?.elements().viewport) { + + if ( + resetScroll && + resultsContainer.value?.osInstance()?.elements().viewport + ) { resultsContainer.value.osInstance()?.elements().viewport?.scrollTo({ top: 0, - behavior: "smooth" + behavior: "smooth", }); } }; -const handleSelection = (groupIndex: number, itemIndex: number, shouldScroll: boolean = true): void => { +const handleSelection = ( + groupIndex: number, + itemIndex: number, + shouldScroll: boolean = true +): void => { selectedGroupIndex.value = groupIndex; selectedItemIndex.value = itemIndex; if (shouldScroll) scrollToSelectedItem(); }; -const handleMediaContent = async (content: string, type: string): Promise => { +const handleMediaContent = async ( + content: string, + type: string +): Promise => { if (type === "image") { return await $history.getImagePath(content); } - + if (isYoutubeWatchUrl(content)) { const videoId = content.includes("youtu.be") ? content.split("youtu.be/")[1] : content.match(/[?&]v=([^&]+)/)?.[1]; return videoId ? `https://img.youtube.com/vi/${videoId}/0.jpg` : ""; } - + return content; }; @@ -468,19 +497,23 @@ const setupEventListeners = async (): Promise => { const previousState = { groupIndex: selectedGroupIndex.value, itemIndex: selectedItemIndex.value, - scroll: resultsContainer.value?.osInstance()?.elements().viewport?.scrollTop || 0 + scroll: + resultsContainer.value?.osInstance()?.elements().viewport + ?.scrollTop || 0, }; - + await updateHistory(); lastUpdateTime.value = currentTime; handleSelection(previousState.groupIndex, previousState.itemIndex, false); - + nextTick(() => { - const viewport = resultsContainer.value?.osInstance()?.elements().viewport; + const viewport = resultsContainer.value + ?.osInstance() + ?.elements().viewport; if (viewport) { viewport.scrollTo({ top: previousState.scroll, - behavior: "instant" + behavior: "instant", }); } }); @@ -491,6 +524,43 @@ const setupEventListeners = async (): Promise => { await listen("tauri://blur", () => { searchInput.value?.blur(); }); + + keyboard.down("ArrowDown", (event) => { + event.preventDefault(); + selectNext(); + }); + + keyboard.down("ArrowUp", (event) => { + event.preventDefault(); + selectPrevious(); + }); + + keyboard.down("Enter", (event) => { + event.preventDefault(); + pasteSelectedItem(); + }); + + keyboard.down("Escape", (event) => { + event.preventDefault(); + hideApp(); + }); + + keyboard.down("all", (event) => { + const isMacActionCombo = + os.value === "macos" && + (event.code === "MetaLeft" || event.code === "MetaRight") && + event.key === "k"; + + const isOtherOsActionCombo = + os.value !== "macos" && + (event.code === "ControlLeft" || event.code === "ControlRight") && + event.key === "k"; + + if (isMacActionCombo || isOtherOsActionCombo) { + event.preventDefault(); + console.log("Actions shortcut triggered"); + } + }); }; const hideApp = async (): Promise => { @@ -557,7 +627,7 @@ const getItemDimensions = async (item: HistoryItem) => { imageDimensions.value[item.id] = "Error"; } } - return imageDimensions.value[item.id] || 'Loading...'; + return imageDimensions.value[item.id] || "Loading..."; }; const loadImageUrl = async (item: HistoryItem) => { @@ -572,8 +642,8 @@ const loadImageUrl = async (item: HistoryItem) => { }; const getComputedImageUrl = (item: HistoryItem | null): string => { - if (!item) return ''; - return imageUrls.value[item.id] || ''; + if (!item) return ""; + return imageUrls.value[item.id] || ""; }; diff --git a/pages/keybind.vue b/pages/settings.vue similarity index 83% rename from pages/keybind.vue rename to pages/settings.vue index c097a2e..2a837cc 100644 --- a/pages/keybind.vue +++ b/pages/settings.vue @@ -60,6 +60,8 @@ const lastBlurTime = ref(0); const os = ref(''); const router = useRouter(); +const keyboard = useKeyboard(); + const keyToDisplayMap: Record = { ' ': 'Space', Alt: 'Alt', @@ -141,36 +143,34 @@ const saveKeybind = async () => { await invoke('save_keybind', { keybind: keybind.value }); }; -const handleGlobalKeyDown = (event: KeyboardEvent) => { - const now = Date.now(); - if ( - (os.value === 'macos' - ? (event.code === 'MetaLeft' || event.code === 'MetaRight') && event.key === 'Enter' - : (event.code === 'ControlLeft' || event.code === 'ControlRight') && event.key === 'Enter') && - !isKeybindInputFocused.value - ) { - event.preventDefault(); - saveKeybind(); - } else if ( - event.key === 'Escape' && - !isKeybindInputFocused.value && - now - lastBlurTime.value > 100 - ) { - event.preventDefault(); - router.push('/'); - } -}; - onMounted(() => { os.value = platform(); - window.addEventListener('keydown', handleGlobalKeyDown); -}); -onUnmounted(() => { - window.removeEventListener('keydown', handleGlobalKeyDown); + keyboard.down('all', (event) => { + const isMacSaveCombo = os.value === 'macos' && + (event.code === 'MetaLeft' || event.code === 'MetaRight') && + event.key === 'Enter'; + + const isOtherOsSaveCombo = os.value !== 'macos' && + (event.code === 'ControlLeft' || event.code === 'ControlRight') && + event.key === 'Enter'; + + if ((isMacSaveCombo || isOtherOsSaveCombo) && !isKeybindInputFocused.value) { + event.preventDefault(); + saveKeybind(); + } + }); + + keyboard.down('Escape', (event) => { + const now = Date.now(); + if (!isKeybindInputFocused.value && now - lastBlurTime.value > 100) { + event.preventDefault(); + router.push('/'); + } + }); }); \ No newline at end of file