diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 4abd6d9..47d57cb 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -57,6 +57,8 @@ const globalShortcutHandler = (event: KeyboardEvent) => { if ( event.key === 'ArrowUp' || event.key === 'ArrowDown' || + event.key === 'ArrowLeft' || + event.key === 'ArrowRight' || (c && event.code === 'Space') ) { event.preventDefault() @@ -65,8 +67,17 @@ const globalShortcutHandler = (event: KeyboardEvent) => { } //console.log("key pressed", event) // For up/down implement custom fast repeat - if (event.key === 'ArrowUp') vert = keyup ? 0 : event.altKey ? -10 : -1 - else if (event.key === 'ArrowDown') vert = keyup ? 0 : event.altKey ? 10 : 1 + let stride = 1 + if (store.gallery) { + const grid = document.querySelector('.gallery') as HTMLElement + stride = getComputedStyle(grid).gridTemplateColumns.split(' ').length + } + else if (event.altKey) stride *= 10 + // Long if-else machina for all keys we handle here + if (event.key === 'ArrowUp') vert = stride * (keyup ? 0 : -1) + else if (event.key === 'ArrowDown') vert = stride * (keyup ? 0 : 1) + else if (event.key === 'ArrowLeft') vert = keyup ? 0 : -1 + else if (event.key === 'ArrowRight') vert = keyup ? 0 : 1 // Find: process on keydown so that we can bypass the built-in search hotkey else if (!keyup && event.key === 'f' && (event.ctrlKey || event.metaKey)) { headerMain.value!.toggleSearchInput() @@ -83,6 +94,10 @@ const globalShortcutHandler = (event: KeyboardEvent) => { else if (!keyup && event.key === 'a' && (event.ctrlKey || event.metaKey)) { fileExplorer.toggleSelectAll() } + // G toggles Gallery + else if (keyup && event.key === 'g') { + store.gallery = !store.gallery + } // Keys 1-3 to sort columns else if ( !input && diff --git a/frontend/src/components/FileExplorer.vue b/frontend/src/components/FileExplorer.vue index cdd4b10..16d2132 100644 --- a/frontend/src/components/FileExplorer.vue +++ b/frontend/src/components/FileExplorer.vue @@ -28,11 +28,11 @@ - + {{ doc.name }} - + @@ -142,18 +142,18 @@ defineExpose({ if (order) store.toggleSort(order as SortOrder) }, isCursor() { - return store.cursor !== null && editing.value === null + return store.cursor && editing.value === null }, cursorRename() { editing.value = store.cursor }, cursorSelect() { - const doc = store.cursor - if (!doc) return - if (store.selected.has(doc.key)) { - store.selected.delete(doc.key) + const key = store.cursor + if (!key) return + if (store.selected.has(key)) { + store.selected.delete(key) } else { - store.selected.add(doc.key) + store.selected.add(key) } this.cursorMove(1) }, @@ -161,17 +161,17 @@ defineExpose({ // Move cursor up or down (keyboard navigation) const docs = props.documents if (docs.length === 0) { - store.cursor = null + store.cursor = '' return } const N = docs.length const mod = (a: number, b: number) => ((a % b) + b) % b const increment = (i: number, d: number) => mod(i + d, N + 1) const index = - store.cursor !== null ? docs.indexOf(store.cursor) : docs.length + store.cursor ? docs.find(doc => doc.key === store.cursor) : docs.length const moveto = increment(index, d) - store.cursor = docs[moveto] ?? null - const tr = store.cursor ? document.getElementById(`file-${store.cursor.key}`) : null + store.cursor = docs[moveto]?.key ?? '' + const tr = store.cursor ? document.getElementById(`file-${store.cursor}`) : '' if (select) { // Go forwards, possibly wrapping over the end; the last entry is not toggled let [begin, end] = d > 0 ? [index, moveto] : [moveto, index] @@ -201,18 +201,18 @@ const focusBreadcrumb = () => { let scrolltimer: any = null let scrolltr: any = null watchEffect(() => { - if (store.cursor && store.cursor !== editing.value) editing.value = null - if (editing.value) store.cursor = editing.value + if (store.cursor && store.cursor !== editing.value?.key) editing.value = null + if (editing.value) store.cursor = editing.value?.key if (store.cursor) { const a = document.querySelector( - `#file-${store.cursor.key} .name a` + `#file-${store.cursor} .name a` ) as HTMLAnchorElement | null if (a) a.focus() } }) watchEffect(() => { if (!props.documents.length && store.cursor) { - store.cursor = null + store.cursor = '' focusBreadcrumb() } }) @@ -286,7 +286,7 @@ const allSelected = computed({ const loc = computed(() => props.path.join('/')) const contextMenu = (ev: MouseEvent, doc: Doc) => { - store.cursor = doc + store.cursor = doc.key ContextMenu.showContextMenu({ x: ev.x, y: ev.y, items: [ { label: 'Rename', onClick: () => { editing.value = doc } }, diff --git a/frontend/src/components/Gallery.vue b/frontend/src/components/Gallery.vue index e2ed9a2..04527e2 100644 --- a/frontend/src/components/Gallery.vue +++ b/frontend/src/components/Gallery.vue @@ -51,43 +51,6 @@ const rename = (doc: Doc, newName: string) => { } doc.name = newName // We should get an update from watch but this is quicker } -const cursorMove = (d: number, select = false) => { - // Move cursor up or down (keyboard navigation) - const docs = props.documents - if (docs.length === 0) { - store.cursor = null - return - } - const N = docs.length - const mod = (a: number, b: number) => ((a % b) + b) % b - const increment = (i: number, d: number) => mod(i + d, N + 1) - const index = - store.cursor !== null ? docs.indexOf(store.cursor) : docs.length - const moveto = increment(index, d) - store.cursor = docs[moveto] ?? null - const tr = store.cursor ? document.getElementById(`file-${store.cursor.key}`) : null - if (select) { - // Go forwards, possibly wrapping over the end; the last entry is not toggled - let [begin, end] = d > 0 ? [index, moveto] : [moveto, index] - for (let p = begin; p !== end; p = increment(p, 1)) { - if (p === N) continue - const key = docs[p].key - if (store.selected.has(key)) store.selected.delete(key) - else store.selected.add(key) - } - } - // @ts-ignore - scrolltr = tr - if (!scrolltimer) { - scrolltimer = setTimeout(() => { - if (scrolltr) - scrolltr.scrollIntoView({ block: 'center', behavior: 'smooth' }) - scrolltimer = null - }, 300) - } - if (moveto === N) focusBreadcrumb() -} - defineExpose({ newFolder() { const now = Math.floor(Date.now() / 1000) @@ -109,22 +72,57 @@ defineExpose({ if (order) store.toggleSort(order as SortOrder) }, isCursor() { - return store.cursor !== null && editing.value === null + return store.cursor && editing.value === null }, cursorRename() { - editing.value = store.cursor + editing.value = props.documents.find(doc => doc.key === store.cursor) ?? null }, cursorSelect() { - const doc = store.cursor - if (!doc) return - if (store.selected.has(doc.key)) { - store.selected.delete(doc.key) + const key = store.cursor + if (!key) return + if (store.selected.has(key)) { + store.selected.delete(key) } else { - store.selected.add(doc.key) + store.selected.add(key) } this.cursorMove(1) }, - cursorMove, + cursorMove(d: number, select = false) { + // Move cursor up or down (keyboard navigation) + const docs = props.documents + if (docs.length === 0) { + store.cursor = '' + return + } + const N = docs.length + const mod = (a: number, b: number) => ((a % b) + b) % b + const increment = (i: number, d: number) => mod(i + d, N + 1) + const index = + store.cursor ? docs.findIndex(doc => doc.key === store.cursor) : docs.length + const moveto = increment(index, d) + store.cursor = docs[moveto]?.key ?? '' + const tr = store.cursor ? document.getElementById(`file-${store.cursor}`) : '' + if (select) { + // Go forwards, possibly wrapping over the end; the last entry is not toggled + let [begin, end] = d > 0 ? [index, moveto] : [moveto, index] + for (let p = begin; p !== end; p = increment(p, 1)) { + if (p === N) continue + const key = docs[p].key + if (store.selected.has(key)) store.selected.delete(key) + else store.selected.add(key) + } + } + // @ts-ignore + scrolltr = tr + if (!scrolltimer) { + scrolltimer = setTimeout(() => { + if (scrolltr) + scrolltr.scrollIntoView({ block: 'center', behavior: 'smooth' }) + scrolltimer = null + }, 300) + } + if (moveto === N) focusBreadcrumb() + } }) const focusBreadcrumb = () => { const el = document.querySelector('.breadcrumb') as HTMLElement | null @@ -133,18 +131,18 @@ const focusBreadcrumb = () => { let scrolltimer: any = null let scrolltr: any = null watchEffect(() => { - if (store.cursor && store.cursor !== editing.value) editing.value = null - if (editing.value) store.cursor = editing.value + if (store.cursor && store.cursor !== editing.value?.key) editing.value = null + if (editing.value) store.cursor = editing.value.key if (store.cursor) { const a = document.querySelector( - `#file-${store.cursor.key} .name a` + `#file-${store.cursor} a` ) as HTMLAnchorElement | null if (a) a.focus() } }) watchEffect(() => { if (!props.documents.length && store.cursor) { - store.cursor = null + store.cursor = '' focusBreadcrumb() } }) @@ -218,7 +216,7 @@ const allSelected = computed({ const loc = computed(() => props.path.join('/')) const contextMenu = (ev: MouseEvent, doc: Doc) => { - store.cursor = doc + store.cursor = doc.key ContextMenu.showContextMenu({ x: ev.x, y: ev.y, items: [ { label: 'Rename', onClick: () => { editing.value = doc } }, diff --git a/frontend/src/components/GalleryFigure.vue b/frontend/src/components/GalleryFigure.vue index d27116b..b0ee900 100644 --- a/frontend/src/components/GalleryFigure.vue +++ b/frontend/src/components/GalleryFigure.vue @@ -1,19 +1,17 @@