feat: use wrdu-keyboard

This commit is contained in:
PandaDEV 2024-12-15 18:53:18 +10:00
parent 5c6853b02b
commit c078b631f6
No known key found for this signature in database
GPG key ID: 13EFF9BAF70EE75C
5 changed files with 173 additions and 86 deletions

View file

@ -12,7 +12,7 @@ import { onMounted } from 'vue'
onMounted(async () => { onMounted(async () => {
await listen('change_keybind', async () => { await listen('change_keybind', async () => {
console.log("change_keybind"); console.log("change_keybind");
await navigateTo('/keybind') await navigateTo('/settings')
await app.show(); await app.show();
await window.getCurrentWindow().show(); await window.getCurrentWindow().show();
}) })

View file

@ -99,7 +99,7 @@ $mutedtext: #78756f;
top: 53px; top: 53px;
left: 284px; left: 284px;
padding: 8px; padding: 8px;
height: calc(100vh - 96px); height: calc(100vh - 256px);
font-family: CommitMono Nerd Font !important; font-family: CommitMono Nerd Font !important;
font-size: 14px; font-size: 14px;
letter-spacing: 1; 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 { .clothoid-corner {
clip-path: polygon( clip-path: polygon(
13.890123px 0px, 13.890123px 0px,

View file

@ -1,11 +1,5 @@
<template> <template>
<div <div class="bg" tabindex="0">
class="bg"
@keydown.down.prevent="selectNext"
@keydown.up.prevent="selectPrevious"
@keydown.enter.prevent="pasteSelectedItem"
@keydown.esc="hideApp"
tabindex="0">
<input <input
ref="searchInput" ref="searchInput"
v-model="searchQuery" v-model="searchQuery"
@ -55,7 +49,7 @@
]" ]"
@click="selectItem(groupIndex, index)" @click="selectItem(groupIndex, index)"
:ref=" :ref="
(el) => { (el: any) => {
if (isSelected(groupIndex, index)) if (isSelected(groupIndex, index))
selectedElement = el as HTMLElement; selectedElement = el as HTMLElement;
} }
@ -67,10 +61,7 @@
alt="Image" alt="Image"
class="image" class="image"
@error="onImageError" /> @error="onImageError" />
<img <img v-else src="../public/icons/Image.svg" class="icon" />
v-else
src="../public/icons/Image.svg"
class="icon" />
</template> </template>
<img <img
v-else-if="hasFavicon(item.favicon ?? '')" v-else-if="hasFavicon(item.favicon ?? '')"
@ -90,7 +81,7 @@
class="icon" class="icon"
v-else-if="item.content_type === ContentType.Code" /> v-else-if="item.content_type === ContentType.Code" />
<span v-if="item.content_type === ContentType.Image"> <span v-if="item.content_type === ContentType.Image">
Image ({{ imageDimensions[item.id] || 'Loading...' }}) Image ({{ imageDimensions[item.id] || "Loading..." }})
</span> </span>
<span v-else>{{ truncateContent(item.content) }}</span> <span v-else>{{ truncateContent(item.content) }}</span>
</div> </div>
@ -107,6 +98,9 @@
class="full-image" /> class="full-image" />
<span v-else>{{ selectedItem?.content || "" }}</span> <span v-else>{{ selectedItem?.content || "" }}</span>
</OverlayScrollbarsComponent> </OverlayScrollbarsComponent>
<div class="information">
<div class="title">Information</div>
</div>
<Noise /> <Noise />
</div> </div>
</template> </template>
@ -136,7 +130,9 @@ const history = shallowRef<HistoryItem[]>([]);
let offset = 0; let offset = 0;
let isLoading = false; let isLoading = false;
const resultsContainer = shallowRef<InstanceType<typeof OverlayScrollbarsComponent> | null>(null); const resultsContainer = shallowRef<InstanceType<
typeof OverlayScrollbarsComponent
> | null>(null);
const searchQuery = ref(""); const searchQuery = ref("");
const selectedGroupIndex = ref(0); const selectedGroupIndex = ref(0);
const selectedItemIndex = ref(0); const selectedItemIndex = ref(0);
@ -149,15 +145,24 @@ const lastUpdateTime = ref<number>(Date.now());
const imageLoadError = ref<boolean>(false); const imageLoadError = ref<boolean>(false);
const imageLoading = ref<boolean>(false); const imageLoading = ref<boolean>(false);
const keyboard = useKeyboard();
const isSameDay = (date1: Date, date2: Date): boolean => { const isSameDay = (date1: Date, date2: Date): boolean => {
return date1.getFullYear() === date2.getFullYear() return (
&& date1.getMonth() === date2.getMonth() date1.getFullYear() === date2.getFullYear() &&
&& date1.getDate() === date2.getDate(); date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate()
);
}; };
const getWeekNumber = (date: Date): number => { const getWeekNumber = (date: Date): number => {
const firstDayOfYear = new Date(date.getFullYear(), 0, 1); 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<GroupedHistory[]>(() => { const groupedHistory = computed<GroupedHistory[]>(() => {
@ -169,30 +174,33 @@ const groupedHistory = computed<GroupedHistory[]>(() => {
const groups: Record<string, HistoryItem[]> = { const groups: Record<string, HistoryItem[]> = {
Today: [], Today: [],
Yesterday: [], Yesterday: [],
'This Week': [], "This Week": [],
'Last Week': [], "Last Week": [],
'This Year': [], "This Year": [],
'Last Year': [] "Last Year": [],
}; };
const filteredItems = searchQuery.value const filteredItems = searchQuery.value
? history.value.filter(item => ? history.value.filter((item) =>
item.content.toLowerCase().includes(searchQuery.value.toLowerCase())) item.content.toLowerCase().includes(searchQuery.value.toLowerCase())
)
: history.value; : history.value;
const yesterday = new Date(today.getTime() - 86400000); const yesterday = new Date(today.getTime() - 86400000);
filteredItems.forEach(item => { filteredItems.forEach((item) => {
const itemDate = new Date(item.timestamp); const itemDate = new Date(item.timestamp);
const itemWeek = getWeekNumber(itemDate); const itemWeek = getWeekNumber(itemDate);
const itemYear = itemDate.getFullYear(); const itemYear = itemDate.getFullYear();
if (isSameDay(itemDate, today)) groups.Today.push(item); if (isSameDay(itemDate, today)) groups.Today.push(item);
else if (isSameDay(itemDate, yesterday)) groups.Yesterday.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)
else if (itemYear === thisYear && itemWeek === thisWeek - 1) groups['Last Week'].push(item); groups["This Week"].push(item);
else if (itemYear === thisYear) groups['This Year'].push(item); else if (itemYear === thisYear && itemWeek === thisWeek - 1)
else groups['Last Year'].push(item); groups["Last Week"].push(item);
else if (itemYear === thisYear) groups["This Year"].push(item);
else groups["Last Year"].push(item);
}); });
return Object.entries(groups) return Object.entries(groups)
@ -217,21 +225,22 @@ const loadHistoryChunk = async (): Promise<void> => {
} }
const processedItems = await Promise.all( const processedItems = await Promise.all(
results.map(async item => { results.map(async (item) => {
const historyItem = new HistoryItem( const historyItem = new HistoryItem(
item.content_type as ContentType, item.source,
item.content_type,
item.content, item.content,
item.favicon item.favicon
); );
Object.assign(historyItem, { Object.assign(historyItem, {
id: item.id, id: item.id,
timestamp: new Date(item.timestamp) timestamp: new Date(item.timestamp),
}); });
if (historyItem.content_type === ContentType.Image) { if (historyItem.content_type === ContentType.Image) {
await Promise.all([ await Promise.all([
getItemDimensions(historyItem), getItemDimensions(historyItem),
loadImageUrl(historyItem) loadImageUrl(historyItem),
]); ]);
} }
return historyItem; return historyItem;
@ -290,10 +299,17 @@ const isSelected = (groupIndex: number, itemIndex: number): boolean => {
const searchHistory = async (): Promise<void> => { const searchHistory = async (): Promise<void> => {
const results = await $history.searchHistory(searchQuery.value); const results = await $history.searchHistory(searchQuery.value);
history.value = results.map(item => Object.assign( history.value = results.map((item) =>
new HistoryItem(item.content_type as ContentType, item.content, item.favicon), Object.assign(
new HistoryItem(
item.source,
item.content_type,
item.content,
item.favicon
),
{ id: item.id, timestamp: new Date(item.timestamp) } { id: item.id, timestamp: new Date(item.timestamp) }
)); )
);
}; };
const selectNext = (): void => { const selectNext = (): void => {
@ -378,7 +394,9 @@ const getFaviconFromDb = (favicon: string): string => {
return `data:image/png;base64,${favicon}`; 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 { try {
const base64 = await $history.readImage({ filename: item.content }); const base64 = await $history.readImage({ filename: item.content });
const dataUrl = `data:image/png;base64,${base64}`; const dataUrl = `data:image/png;base64,${base64}`;
@ -392,7 +410,7 @@ const getImageData = async (item: HistoryItem): Promise<{ url: string; dimension
return { return {
url: dataUrl, url: dataUrl,
dimensions: `${img.width}x${img.height}` dimensions: `${img.width}x${img.height}`,
}; };
} catch (error) { } catch (error) {
console.error("Error processing image:", 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<HistoryItem> => { const processHistoryItem = async (item: any): Promise<HistoryItem> => {
const historyItem = new HistoryItem( 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.content,
item.favicon item.favicon
); );
Object.assign(historyItem, { Object.assign(historyItem, {
id: item.id, id: item.id,
timestamp: new Date(item.timestamp) timestamp: new Date(item.timestamp),
}); });
if (historyItem.content_type === ContentType.Image) { if (historyItem.content_type === ContentType.Image) {
@ -426,21 +445,31 @@ const updateHistory = async (resetScroll: boolean = false): Promise<void> => {
offset = 0; offset = 0;
await loadHistoryChunk(); await loadHistoryChunk();
if (resetScroll && resultsContainer.value?.osInstance()?.elements().viewport) { if (
resetScroll &&
resultsContainer.value?.osInstance()?.elements().viewport
) {
resultsContainer.value.osInstance()?.elements().viewport?.scrollTo({ resultsContainer.value.osInstance()?.elements().viewport?.scrollTo({
top: 0, 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; selectedGroupIndex.value = groupIndex;
selectedItemIndex.value = itemIndex; selectedItemIndex.value = itemIndex;
if (shouldScroll) scrollToSelectedItem(); if (shouldScroll) scrollToSelectedItem();
}; };
const handleMediaContent = async (content: string, type: string): Promise<string> => { const handleMediaContent = async (
content: string,
type: string
): Promise<string> => {
if (type === "image") { if (type === "image") {
return await $history.getImagePath(content); return await $history.getImagePath(content);
} }
@ -468,7 +497,9 @@ const setupEventListeners = async (): Promise<void> => {
const previousState = { const previousState = {
groupIndex: selectedGroupIndex.value, groupIndex: selectedGroupIndex.value,
itemIndex: selectedItemIndex.value, itemIndex: selectedItemIndex.value,
scroll: resultsContainer.value?.osInstance()?.elements().viewport?.scrollTop || 0 scroll:
resultsContainer.value?.osInstance()?.elements().viewport
?.scrollTop || 0,
}; };
await updateHistory(); await updateHistory();
@ -476,11 +507,13 @@ const setupEventListeners = async (): Promise<void> => {
handleSelection(previousState.groupIndex, previousState.itemIndex, false); handleSelection(previousState.groupIndex, previousState.itemIndex, false);
nextTick(() => { nextTick(() => {
const viewport = resultsContainer.value?.osInstance()?.elements().viewport; const viewport = resultsContainer.value
?.osInstance()
?.elements().viewport;
if (viewport) { if (viewport) {
viewport.scrollTo({ viewport.scrollTo({
top: previousState.scroll, top: previousState.scroll,
behavior: "instant" behavior: "instant",
}); });
} }
}); });
@ -491,6 +524,43 @@ const setupEventListeners = async (): Promise<void> => {
await listen("tauri://blur", () => { await listen("tauri://blur", () => {
searchInput.value?.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<void> => { const hideApp = async (): Promise<void> => {
@ -557,7 +627,7 @@ const getItemDimensions = async (item: HistoryItem) => {
imageDimensions.value[item.id] = "Error"; imageDimensions.value[item.id] = "Error";
} }
} }
return imageDimensions.value[item.id] || 'Loading...'; return imageDimensions.value[item.id] || "Loading...";
}; };
const loadImageUrl = async (item: HistoryItem) => { const loadImageUrl = async (item: HistoryItem) => {
@ -572,8 +642,8 @@ const loadImageUrl = async (item: HistoryItem) => {
}; };
const getComputedImageUrl = (item: HistoryItem | null): string => { const getComputedImageUrl = (item: HistoryItem | null): string => {
if (!item) return ''; if (!item) return "";
return imageUrls.value[item.id] || ''; return imageUrls.value[item.id] || "";
}; };
</script> </script>

View file

@ -60,6 +60,8 @@ const lastBlurTime = ref(0);
const os = ref(''); const os = ref('');
const router = useRouter(); const router = useRouter();
const keyboard = useKeyboard();
const keyToDisplayMap: Record<string, string> = { const keyToDisplayMap: Record<string, string> = {
' ': 'Space', ' ': 'Space',
Alt: 'Alt', Alt: 'Alt',
@ -141,36 +143,34 @@ const saveKeybind = async () => {
await invoke('save_keybind', { keybind: keybind.value }); await invoke('save_keybind', { keybind: keybind.value });
}; };
const handleGlobalKeyDown = (event: KeyboardEvent) => { onMounted(() => {
const now = Date.now(); os.value = platform();
if (
(os.value === 'macos' keyboard.down('all', (event) => {
? (event.code === 'MetaLeft' || event.code === 'MetaRight') && event.key === 'Enter' const isMacSaveCombo = os.value === 'macos' &&
: (event.code === 'ControlLeft' || event.code === 'ControlRight') && event.key === 'Enter') && (event.code === 'MetaLeft' || event.code === 'MetaRight') &&
!isKeybindInputFocused.value event.key === 'Enter';
) {
const isOtherOsSaveCombo = os.value !== 'macos' &&
(event.code === 'ControlLeft' || event.code === 'ControlRight') &&
event.key === 'Enter';
if ((isMacSaveCombo || isOtherOsSaveCombo) && !isKeybindInputFocused.value) {
event.preventDefault(); event.preventDefault();
saveKeybind(); saveKeybind();
} else if ( }
event.key === 'Escape' && });
!isKeybindInputFocused.value &&
now - lastBlurTime.value > 100 keyboard.down('Escape', (event) => {
) { const now = Date.now();
if (!isKeybindInputFocused.value && now - lastBlurTime.value > 100) {
event.preventDefault(); event.preventDefault();
router.push('/'); router.push('/');
} }
}; });
onMounted(() => {
os.value = platform();
window.addEventListener('keydown', handleGlobalKeyDown);
});
onUnmounted(() => {
window.removeEventListener('keydown', handleGlobalKeyDown);
}); });
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@use '~/assets/css/keybind.scss'; @use '~/assets/css/settings.scss';
</style> </style>