Refactoring cursor to be stored in store as key only. A few issues remain.

This commit is contained in:
Leo Vasanko 2023-11-17 19:44:18 -08:00
parent 8da141744e
commit e20b04189f
5 changed files with 92 additions and 81 deletions

View File

@ -57,6 +57,8 @@ const globalShortcutHandler = (event: KeyboardEvent) => {
if ( if (
event.key === 'ArrowUp' || event.key === 'ArrowUp' ||
event.key === 'ArrowDown' || event.key === 'ArrowDown' ||
event.key === 'ArrowLeft' ||
event.key === 'ArrowRight' ||
(c && event.code === 'Space') (c && event.code === 'Space')
) { ) {
event.preventDefault() event.preventDefault()
@ -65,8 +67,17 @@ const globalShortcutHandler = (event: KeyboardEvent) => {
} }
//console.log("key pressed", event) //console.log("key pressed", event)
// For up/down implement custom fast repeat // For up/down implement custom fast repeat
if (event.key === 'ArrowUp') vert = keyup ? 0 : event.altKey ? -10 : -1 let stride = 1
else if (event.key === 'ArrowDown') vert = keyup ? 0 : event.altKey ? 10 : 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 // Find: process on keydown so that we can bypass the built-in search hotkey
else if (!keyup && event.key === 'f' && (event.ctrlKey || event.metaKey)) { else if (!keyup && event.key === 'f' && (event.ctrlKey || event.metaKey)) {
headerMain.value!.toggleSearchInput() headerMain.value!.toggleSearchInput()
@ -83,6 +94,10 @@ const globalShortcutHandler = (event: KeyboardEvent) => {
else if (!keyup && event.key === 'a' && (event.ctrlKey || event.metaKey)) { else if (!keyup && event.key === 'a' && (event.ctrlKey || event.metaKey)) {
fileExplorer.toggleSelectAll() fileExplorer.toggleSelectAll()
} }
// G toggles Gallery
else if (keyup && event.key === 'g') {
store.gallery = !store.gallery
}
// Keys 1-3 to sort columns // Keys 1-3 to sort columns
else if ( else if (
!input && !input &&

View File

@ -28,11 +28,11 @@
<tr <tr
:id="`file-${doc.key}`" :id="`file-${doc.key}`"
:class="{ file: !doc.dir, folder: doc.dir, cursor: store.cursor === doc }" :class="{ file: !doc.dir, folder: doc.dir, cursor: store.cursor === doc.key }"
@click="store.cursor = store.cursor === doc ? null : doc" @click="store.cursor = store.cursor === doc.key ? '' : doc.key"
@contextmenu.prevent="contextMenu($event, doc)" @contextmenu.prevent="contextMenu($event, doc)"
> >
<td class="selection" @click.up.stop="store.cursor = store.cursor === doc ? doc : null"> <td class="selection" @click.up.stop="store.cursor = store.cursor === doc.key ? doc.key : ''">
<input <input
type="checkbox" type="checkbox"
tabindex="-1" tabindex="-1"
@ -53,12 +53,12 @@
:href="doc.url" :href="doc.url"
tabindex="-1" tabindex="-1"
@contextmenu.prevent @contextmenu.prevent
@focus.stop="store.cursor = doc" @focus.stop="store.cursor = doc.key"
@keyup.left="router.back()" @keyup.left="router.back()"
@keyup.right.stop="ev => { if (doc.dir) (ev.target as HTMLElement).click() }" @keyup.right.stop="ev => { if (doc.dir) (ev.target as HTMLElement).click() }"
>{{ doc.name }}</a >{{ doc.name }}</a
> >
<button tabindex=-1 v-if="cursor == doc" class="rename-button" @click="() => (editing = doc)">🖊</button> <button tabindex=-1 v-if="store.cursor == doc.key" class="rename-button" @click="() => (editing = doc)">🖊</button>
</template> </template>
</td> </td>
<FileModified :doc=doc :key=nowkey /> <FileModified :doc=doc :key=nowkey />
@ -142,18 +142,18 @@ defineExpose({
if (order) store.toggleSort(order as SortOrder) if (order) store.toggleSort(order as SortOrder)
}, },
isCursor() { isCursor() {
return store.cursor !== null && editing.value === null return store.cursor && editing.value === null
}, },
cursorRename() { cursorRename() {
editing.value = store.cursor editing.value = store.cursor
}, },
cursorSelect() { cursorSelect() {
const doc = store.cursor const key = store.cursor
if (!doc) return if (!key) return
if (store.selected.has(doc.key)) { if (store.selected.has(key)) {
store.selected.delete(doc.key) store.selected.delete(key)
} else { } else {
store.selected.add(doc.key) store.selected.add(key)
} }
this.cursorMove(1) this.cursorMove(1)
}, },
@ -161,17 +161,17 @@ defineExpose({
// Move cursor up or down (keyboard navigation) // Move cursor up or down (keyboard navigation)
const docs = props.documents const docs = props.documents
if (docs.length === 0) { if (docs.length === 0) {
store.cursor = null store.cursor = ''
return return
} }
const N = docs.length const N = docs.length
const mod = (a: number, b: number) => ((a % b) + b) % b const mod = (a: number, b: number) => ((a % b) + b) % b
const increment = (i: number, d: number) => mod(i + d, N + 1) const increment = (i: number, d: number) => mod(i + d, N + 1)
const index = 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) const moveto = increment(index, d)
store.cursor = docs[moveto] ?? null store.cursor = docs[moveto]?.key ?? ''
const tr = store.cursor ? document.getElementById(`file-${store.cursor.key}`) : null const tr = store.cursor ? document.getElementById(`file-${store.cursor}`) : ''
if (select) { if (select) {
// Go forwards, possibly wrapping over the end; the last entry is not toggled // Go forwards, possibly wrapping over the end; the last entry is not toggled
let [begin, end] = d > 0 ? [index, moveto] : [moveto, index] let [begin, end] = d > 0 ? [index, moveto] : [moveto, index]
@ -201,18 +201,18 @@ const focusBreadcrumb = () => {
let scrolltimer: any = null let scrolltimer: any = null
let scrolltr: any = null let scrolltr: any = null
watchEffect(() => { watchEffect(() => {
if (store.cursor && store.cursor !== editing.value) editing.value = null if (store.cursor && store.cursor !== editing.value?.key) editing.value = null
if (editing.value) store.cursor = editing.value if (editing.value) store.cursor = editing.value?.key
if (store.cursor) { if (store.cursor) {
const a = document.querySelector( const a = document.querySelector(
`#file-${store.cursor.key} .name a` `#file-${store.cursor} .name a`
) as HTMLAnchorElement | null ) as HTMLAnchorElement | null
if (a) a.focus() if (a) a.focus()
} }
}) })
watchEffect(() => { watchEffect(() => {
if (!props.documents.length && store.cursor) { if (!props.documents.length && store.cursor) {
store.cursor = null store.cursor = ''
focusBreadcrumb() focusBreadcrumb()
} }
}) })
@ -286,7 +286,7 @@ const allSelected = computed({
const loc = computed(() => props.path.join('/')) const loc = computed(() => props.path.join('/'))
const contextMenu = (ev: MouseEvent, doc: Doc) => { const contextMenu = (ev: MouseEvent, doc: Doc) => {
store.cursor = doc store.cursor = doc.key
ContextMenu.showContextMenu({ ContextMenu.showContextMenu({
x: ev.x, y: ev.y, items: [ x: ev.x, y: ev.y, items: [
{ label: 'Rename', onClick: () => { editing.value = doc } }, { label: 'Rename', onClick: () => { editing.value = doc } },

View File

@ -51,43 +51,6 @@ const rename = (doc: Doc, newName: string) => {
} }
doc.name = newName // We should get an update from watch but this is quicker 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({ defineExpose({
newFolder() { newFolder() {
const now = Math.floor(Date.now() / 1000) const now = Math.floor(Date.now() / 1000)
@ -109,22 +72,57 @@ defineExpose({
if (order) store.toggleSort(order as SortOrder) if (order) store.toggleSort(order as SortOrder)
}, },
isCursor() { isCursor() {
return store.cursor !== null && editing.value === null return store.cursor && editing.value === null
}, },
cursorRename() { cursorRename() {
editing.value = store.cursor editing.value = props.documents.find(doc => doc.key === store.cursor) ?? null
}, },
cursorSelect() { cursorSelect() {
const doc = store.cursor const key = store.cursor
if (!doc) return if (!key) return
if (store.selected.has(doc.key)) { if (store.selected.has(key)) {
store.selected.delete(doc.key) store.selected.delete(key)
} else { } else {
store.selected.add(doc.key) store.selected.add(key)
} }
this.cursorMove(1) 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 focusBreadcrumb = () => {
const el = document.querySelector('.breadcrumb') as HTMLElement | null const el = document.querySelector('.breadcrumb') as HTMLElement | null
@ -133,18 +131,18 @@ const focusBreadcrumb = () => {
let scrolltimer: any = null let scrolltimer: any = null
let scrolltr: any = null let scrolltr: any = null
watchEffect(() => { watchEffect(() => {
if (store.cursor && store.cursor !== editing.value) editing.value = null if (store.cursor && store.cursor !== editing.value?.key) editing.value = null
if (editing.value) store.cursor = editing.value if (editing.value) store.cursor = editing.value.key
if (store.cursor) { if (store.cursor) {
const a = document.querySelector( const a = document.querySelector(
`#file-${store.cursor.key} .name a` `#file-${store.cursor} a`
) as HTMLAnchorElement | null ) as HTMLAnchorElement | null
if (a) a.focus() if (a) a.focus()
} }
}) })
watchEffect(() => { watchEffect(() => {
if (!props.documents.length && store.cursor) { if (!props.documents.length && store.cursor) {
store.cursor = null store.cursor = ''
focusBreadcrumb() focusBreadcrumb()
} }
}) })
@ -218,7 +216,7 @@ const allSelected = computed({
const loc = computed(() => props.path.join('/')) const loc = computed(() => props.path.join('/'))
const contextMenu = (ev: MouseEvent, doc: Doc) => { const contextMenu = (ev: MouseEvent, doc: Doc) => {
store.cursor = doc store.cursor = doc.key
ContextMenu.showContextMenu({ ContextMenu.showContextMenu({
x: ev.x, y: ev.y, items: [ x: ev.x, y: ev.y, items: [
{ label: 'Rename', onClick: () => { editing.value = doc } }, { label: 'Rename', onClick: () => { editing.value = doc } },

View File

@ -1,19 +1,17 @@
<template> <template>
<a <a
:id="`file-${doc.key}`"
:href="doc.url" :href="doc.url"
tabindex=0 tabindex=0
:class="{ file: !doc.dir, folder: doc.dir, cursor: store.cursor === doc.key }"
@contextmenu.prevent @contextmenu.prevent
@focus.stop="store.cursor = doc" @focus.stop="store.cursor = doc.key"
@click="ev => { @click="ev => {
store.cursor = store.cursor === doc.key ? '' : doc.key
if (media) { media.play(); ev.preventDefault() } if (media) { media.play(); ev.preventDefault() }
}" }"
> >
<figure <figure>
:id="`file-${doc.key}`"
:class="{ file: !doc.dir, folder: doc.dir, cursor: store.cursor === doc }"
@click="store.cursor = store.cursor === doc ? null : doc"
@click.up.stop="store.cursor = store.cursor === doc ? doc : null"
>
<slot></slot> <slot></slot>
<MediaPreview ref=media :doc="doc" /> <MediaPreview ref=media :doc="doc" />
<caption> <caption>
@ -58,7 +56,7 @@ figure caption {
color: var(--text-color); color: var(--text-color);
text-shadow: 0 0 .2rem #000, 0 0 1rem #000; text-shadow: 0 0 .2rem #000, 0 0 1rem #000;
} }
figure.cursor caption { .cursor caption {
background: var(--accent-color); background: var(--accent-color);
} }
caption { caption {

View File

@ -24,7 +24,7 @@ export const useMainStore = defineStore({
error: '' as string, error: '' as string,
connected: false, connected: false,
gallery: false, gallery: false,
cursor: shallowRef<Doc | null>(null), cursor: '' as string,
server: {} as Record<string, any>, server: {} as Record<string, any>,
prefs: { prefs: {
sortListing: '' as SortOrder, sortListing: '' as SortOrder,