Frontend created and rewritten a few times, with some backend fixes #1
|
@ -62,7 +62,9 @@ const headerMain = ref<typeof HeaderMain | null>(null)
|
||||||
let vert = 0
|
let vert = 0
|
||||||
let timer: any = null
|
let timer: any = null
|
||||||
const globalShortcutHandler = (event: KeyboardEvent) => {
|
const globalShortcutHandler = (event: KeyboardEvent) => {
|
||||||
const c = documentStore.fileExplorer.isCursor()
|
const fileExplorer = documentStore.fileExplorer as any
|
||||||
|
if (!fileExplorer) return
|
||||||
|
const c = fileExplorer.isCursor()
|
||||||
const keyup = event.type === 'keyup'
|
const keyup = event.type === 'keyup'
|
||||||
if (event.repeat) {
|
if (event.repeat) {
|
||||||
if (
|
if (
|
||||||
|
@ -84,7 +86,7 @@ const globalShortcutHandler = (event: KeyboardEvent) => {
|
||||||
}
|
}
|
||||||
// Select all (toggle); keydown to prevent builtin
|
// Select all (toggle); keydown to prevent builtin
|
||||||
else if (!keyup && event.key === 'a' && (event.ctrlKey || event.metaKey)) {
|
else if (!keyup && event.key === 'a' && (event.ctrlKey || event.metaKey)) {
|
||||||
documentStore.fileExplorer.toggleSelectAll()
|
fileExplorer.toggleSelectAll()
|
||||||
}
|
}
|
||||||
// Keys 1-3 to sort columns
|
// Keys 1-3 to sort columns
|
||||||
else if (
|
else if (
|
||||||
|
@ -92,16 +94,16 @@ const globalShortcutHandler = (event: KeyboardEvent) => {
|
||||||
keyup &&
|
keyup &&
|
||||||
(event.key === '1' || event.key === '2' || event.key === '3')
|
(event.key === '1' || event.key === '2' || event.key === '3')
|
||||||
) {
|
) {
|
||||||
documentStore.fileExplorer.toggleSortColumn(+event.key)
|
fileExplorer.toggleSortColumn(+event.key)
|
||||||
}
|
}
|
||||||
// Rename
|
// Rename
|
||||||
else if (c && keyup && !event.ctrlKey && (event.key === 'F2' || event.key === 'r')) {
|
else if (c && keyup && !event.ctrlKey && (event.key === 'F2' || event.key === 'r')) {
|
||||||
documentStore.fileExplorer.cursorRename()
|
fileExplorer.cursorRename()
|
||||||
}
|
}
|
||||||
// Toggle selections on file explorer; ignore all spaces to prevent scrolling built-in hotkey
|
// Toggle selections on file explorer; ignore all spaces to prevent scrolling built-in hotkey
|
||||||
else if (c && event.code === 'Space') {
|
else if (c && event.code === 'Space') {
|
||||||
if (keyup && !event.altKey && !event.ctrlKey)
|
if (keyup && !event.altKey && !event.ctrlKey)
|
||||||
documentStore.fileExplorer.cursorSelect()
|
fileExplorer.cursorSelect()
|
||||||
} else return
|
} else return
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (!vert) {
|
if (!vert) {
|
||||||
|
@ -114,13 +116,13 @@ const globalShortcutHandler = (event: KeyboardEvent) => {
|
||||||
if (!timer) {
|
if (!timer) {
|
||||||
// Initial move, then t0 delay until repeats at tr intervals
|
// Initial move, then t0 delay until repeats at tr intervals
|
||||||
const select = event.shiftKey
|
const select = event.shiftKey
|
||||||
documentStore.fileExplorer.cursorMove(vert, select)
|
fileExplorer.cursorMove(vert, select)
|
||||||
const t0 = 200,
|
const t0 = 200,
|
||||||
tr = 30
|
tr = 30
|
||||||
timer = setTimeout(
|
timer = setTimeout(
|
||||||
() =>
|
() =>
|
||||||
(timer = setInterval(() => {
|
(timer = setInterval(() => {
|
||||||
documentStore.fileExplorer.cursorMove(vert, select)
|
fileExplorer.cursorMove(vert, select)
|
||||||
}, tr)),
|
}, tr)),
|
||||||
t0 - tr
|
t0 - tr
|
||||||
)
|
)
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: end;
|
align-items: end;
|
||||||
}
|
}
|
||||||
.breadcrumb {
|
header .breadcrumb {
|
||||||
font-size: 1.7em;
|
font-size: 1.7em;
|
||||||
flex-shrink: 10;
|
flex-shrink: 10;
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
<td class="menu"></td>
|
<td class="menu"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<template
|
<template
|
||||||
v-for="doc of sorted(props.documents as FolderDocument[])"
|
v-for="doc of sorted(props.documents as Document[])"
|
||||||
:key="doc.key">
|
:key="doc.key">
|
||||||
<tr v-if="doc.loc !== prevloc && ((prevloc = doc.loc) || true)">
|
<tr v-if="doc.loc !== prevloc && ((prevloc = doc.loc) || true)">
|
||||||
<th colspan="5"><BreadCrumb :path="doc.loc ? doc.loc.split('/') : []" /></th>
|
<th colspan="5"><BreadCrumb :path="doc.loc ? doc.loc.split('/') : []" /></th>
|
||||||
|
@ -141,7 +141,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watchEffect, onBeforeUpdate } from 'vue'
|
import { ref, computed, watchEffect, onBeforeUpdate } from 'vue'
|
||||||
import { useDocumentStore } from '@/stores/documents'
|
import { useDocumentStore } from '@/stores/documents'
|
||||||
import type { Document, FolderDocument } from '@/repositories/Document'
|
import type { Document } from '@/repositories/Document'
|
||||||
import FileRenameInput from './FileRenameInput.vue'
|
import FileRenameInput from './FileRenameInput.vue'
|
||||||
import createWebSocket from '@/repositories/WS'
|
import createWebSocket from '@/repositories/WS'
|
||||||
import { collator, formatSize, formatUnixDate } from '@/utils'
|
import { collator, formatSize, formatUnixDate } from '@/utils'
|
||||||
|
@ -156,14 +156,14 @@ const props = withDefaults(
|
||||||
)
|
)
|
||||||
const documentStore = useDocumentStore()
|
const documentStore = useDocumentStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const url_for = (doc: FolderDocument) => {
|
const url_for = (doc: Document) => {
|
||||||
const p = doc.loc ? `${doc.loc}/${doc.name}` : doc.name
|
const p = doc.loc ? `${doc.loc}/${doc.name}` : doc.name
|
||||||
return doc.type === 'folder' ? `#/${p}/` : `/files/${p}`
|
return doc.type === 'folder' ? `#/${p}/` : `/files/${p}`
|
||||||
}
|
}
|
||||||
const cursor = ref<FolderDocument | null>(null)
|
const cursor = ref<Document | null>(null)
|
||||||
// File rename
|
// File rename
|
||||||
const editing = ref<FolderDocument | null>(null)
|
const editing = ref<Document | null>(null)
|
||||||
const rename = (doc: FolderDocument, newName: string) => {
|
const rename = (doc: Document, newName: string) => {
|
||||||
const oldName = doc.name
|
const oldName = doc.name
|
||||||
const control = createWebSocket('/api/control', (ev: MessageEvent) => {
|
const control = createWebSocket('/api/control', (ev: MessageEvent) => {
|
||||||
const msg = JSON.parse(ev.data)
|
const msg = JSON.parse(ev.data)
|
||||||
|
@ -189,14 +189,15 @@ defineExpose({
|
||||||
newFolder() {
|
newFolder() {
|
||||||
const now = Date.now() / 1000
|
const now = Date.now() / 1000
|
||||||
editing.value = {
|
editing.value = {
|
||||||
loc,
|
loc: loc.value,
|
||||||
key: 'new',
|
key: 'new',
|
||||||
name: 'New Folder',
|
name: 'New Folder',
|
||||||
type: 'folder',
|
type: 'folder',
|
||||||
mtime: now,
|
mtime: now,
|
||||||
size: 0,
|
size: 0,
|
||||||
sizedisp: formatSize(0),
|
sizedisp: formatSize(0),
|
||||||
modified: formatUnixDate(now)
|
modified: formatUnixDate(now),
|
||||||
|
haystack: '',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggleSelectAll() {
|
toggleSelectAll() {
|
||||||
|
@ -225,7 +226,7 @@ defineExpose({
|
||||||
},
|
},
|
||||||
cursorMove(d: number, select = false) {
|
cursorMove(d: number, select = false) {
|
||||||
// Move cursor up or down (keyboard navigation)
|
// Move cursor up or down (keyboard navigation)
|
||||||
const documents = sorted(props.documents as FolderDocument[])
|
const documents = sorted(props.documents as Document[])
|
||||||
if (documents.length === 0) {
|
if (documents.length === 0) {
|
||||||
cursor.value = null
|
cursor.value = null
|
||||||
return
|
return
|
||||||
|
@ -311,10 +312,10 @@ const toggleSort = (name: string) => {
|
||||||
const sort = ref<string>('')
|
const sort = ref<string>('')
|
||||||
const sortCompare = {
|
const sortCompare = {
|
||||||
name: (a: Document, b: Document) => collator.compare(a.name, b.name),
|
name: (a: Document, b: Document) => collator.compare(a.name, b.name),
|
||||||
modified: (a: FolderDocument, b: FolderDocument) => b.mtime - a.mtime,
|
modified: (a: Document, b: Document) => b.mtime - a.mtime,
|
||||||
size: (a: FolderDocument, b: FolderDocument) => b.size - a.size
|
size: (a: Document, b: Document) => b.size - a.size
|
||||||
}
|
}
|
||||||
const sorted = (documents: FolderDocument[]) => {
|
const sorted = (documents: Document[]) => {
|
||||||
const cmp = sortCompare[sort.value as keyof typeof sortCompare]
|
const cmp = sortCompare[sort.value as keyof typeof sortCompare]
|
||||||
const sorted = [...documents]
|
const sorted = [...documents]
|
||||||
if (cmp) sorted.sort(cmp)
|
if (cmp) sorted.sort(cmp)
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { FolderDocument } from '@/repositories/Document'
|
import type { Document } from '@/repositories/Document'
|
||||||
import { ref, onMounted, nextTick } from 'vue'
|
import { ref, onMounted, nextTick } from 'vue'
|
||||||
|
|
||||||
const input = ref<HTMLInputElement | null>(null)
|
const input = ref<HTMLInputElement | null>(null)
|
||||||
|
@ -28,8 +28,8 @@ onMounted(() => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
doc: FolderDocument
|
doc: Document
|
||||||
rename: (doc: FolderDocument, newName: string) => void
|
rename: (doc: Document, newName: string) => void
|
||||||
exit: () => void
|
exit: () => void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ defineExpose({
|
||||||
ref="search"
|
ref="search"
|
||||||
type="search"
|
type="search"
|
||||||
v-model="documentStore.search"
|
v-model="documentStore.search"
|
||||||
|
placeholder="Search words"
|
||||||
class="margin-input"
|
class="margin-input"
|
||||||
@blur="() => { if (documentStore.search === '') toggleSearchInput() }"
|
@blur="() => { if (documentStore.search === '') toggleSearchInput() }"
|
||||||
@keyup.esc="toggleSearchInput"
|
@keyup.esc="toggleSearchInput"
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import type { DocumentStore } from '@/stores/documents'
|
|
||||||
import { useDocumentStore } from '@/stores/documents'
|
import { useDocumentStore } from '@/stores/documents'
|
||||||
import createWebSocket from './WS'
|
import createWebSocket from './WS'
|
||||||
|
|
||||||
|
@ -68,7 +67,7 @@ export const url_document_upload_ws = '/api/upload'
|
||||||
export const url_document_get = '/files'
|
export const url_document_get = '/files'
|
||||||
|
|
||||||
export class DocumentHandler {
|
export class DocumentHandler {
|
||||||
constructor(private store: DocumentStore = useDocumentStore()) {
|
constructor(private store = useDocumentStore()) {
|
||||||
this.handleWebSocketMessage = this.handleWebSocketMessage.bind(this)
|
this.handleWebSocketMessage = this.handleWebSocketMessage.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,7 +141,7 @@ export class DocumentHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DocumentUploadHandler {
|
export class DocumentUploadHandler {
|
||||||
constructor(private store: DocumentStore = useDocumentStore()) {
|
constructor(private store = useDocumentStore()) {
|
||||||
this.handleWebSocketMessage = this.handleWebSocketMessage.bind(this)
|
this.handleWebSocketMessage = this.handleWebSocketMessage.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import type {
|
||||||
DirList,
|
DirList,
|
||||||
SelectedItems
|
SelectedItems
|
||||||
} from '@/repositories/Document'
|
} from '@/repositories/Document'
|
||||||
import { needleFormat, formatSize, formatUnixDate, haystackFormat, localeIncludes } from '@/utils'
|
import { formatSize, formatUnixDate, haystackFormat } from '@/utils'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { collator } from '@/utils'
|
import { collator } from '@/utils'
|
||||||
|
|
||||||
|
@ -21,23 +21,9 @@ type User = {
|
||||||
isLoggedIn: boolean
|
isLoggedIn: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DocumentStore = {
|
|
||||||
root: DirEntry
|
|
||||||
document: Document[]
|
|
||||||
search: string
|
|
||||||
selected: Set<FUID>
|
|
||||||
uploadingDocuments: Array<{ key: number; name: string; progress: number }>
|
|
||||||
uploadCount: number
|
|
||||||
wsWatch: WebSocket | undefined
|
|
||||||
wsUpload: WebSocket | undefined
|
|
||||||
fileExplorer: any
|
|
||||||
user: User
|
|
||||||
error: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useDocumentStore = defineStore({
|
export const useDocumentStore = defineStore({
|
||||||
id: 'documents',
|
id: 'documents',
|
||||||
state: (): DocumentStore => ({
|
state: () => ({
|
||||||
root: {} as DirEntry,
|
root: {} as DirEntry,
|
||||||
document: [] as Document[],
|
document: [] as Document[],
|
||||||
search: "" as string,
|
search: "" as string,
|
||||||
|
@ -70,7 +56,7 @@ export const useDocumentStore = defineStore({
|
||||||
modified: formatUnixDate(attr.mtime),
|
modified: formatUnixDate(attr.mtime),
|
||||||
haystack: haystackFormat(name),
|
haystack: haystackFormat(name),
|
||||||
})
|
})
|
||||||
const queue = [...Object.entries(root.dir).map(mapper)]
|
const queue = [...Object.entries(root.dir ?? {}).map(mapper)]
|
||||||
const docs = []
|
const docs = []
|
||||||
for (let doc; (doc = queue.shift()) !== undefined;) {
|
for (let doc; (doc = queue.shift()) !== undefined;) {
|
||||||
docs.push(doc)
|
docs.push(doc)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user