New filelist format on frontend
This commit is contained in:
parent
ef5e37187d
commit
00a4297c0b
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<LoginModal />
|
||||
<header>
|
||||
<HeaderMain ref="headerMain" :path="path.pathList">
|
||||
<HeaderMain ref="headerMain" :path="path.pathList" :query="path.query">
|
||||
<HeaderSelected :path="path.pathList" />
|
||||
</HeaderMain>
|
||||
<BreadCrumb :path="path.pathList" tabindex="-1"/>
|
||||
</header>
|
||||
<main>
|
||||
<RouterView :path="path.pathList" />
|
||||
<RouterView :path="path.pathList" :query="path.query" />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
|
@ -16,7 +16,7 @@ import { RouterView } from 'vue-router'
|
|||
import type { ComputedRef } from 'vue'
|
||||
import type HeaderMain from '@/components/HeaderMain.vue'
|
||||
import { onMounted, onUnmounted, ref, watchEffect } from 'vue'
|
||||
import { watchConnect, watchDisconnect } from '@/repositories/WS'
|
||||
import { loadSession, watchConnect, watchDisconnect } from '@/repositories/WS'
|
||||
import { useDocumentStore } from '@/stores/documents'
|
||||
|
||||
import { computed } from 'vue'
|
||||
|
@ -25,19 +25,23 @@ import Router from '@/router/index'
|
|||
interface Path {
|
||||
path: string
|
||||
pathList: string[]
|
||||
query: string
|
||||
}
|
||||
const documentStore = useDocumentStore()
|
||||
const path: ComputedRef<Path> = computed(() => {
|
||||
const p = decodeURIComponent(Router.currentRoute.value.path)
|
||||
const pathList = p.split('/').filter(value => value !== '')
|
||||
const p = decodeURIComponent(Router.currentRoute.value.path).split('//')
|
||||
const pathList = p[0].split('/').filter(value => value !== '')
|
||||
const query = p.slice(1).join('//')
|
||||
return {
|
||||
path: p,
|
||||
pathList
|
||||
path: p[0],
|
||||
pathList,
|
||||
query
|
||||
}
|
||||
})
|
||||
watchEffect(() => {
|
||||
document.title = path.value.path.replace(/\/$/, '').split('/').pop() || documentStore.server.name || 'Cista Storage'
|
||||
})
|
||||
onMounted(loadSession)
|
||||
onMounted(watchConnect)
|
||||
onUnmounted(watchDisconnect)
|
||||
// Update human-readable x seconds ago messages from mtimes
|
||||
|
|
|
@ -22,29 +22,16 @@ export type errorEvent = {
|
|||
|
||||
// Raw types the backend /api/watch sends us
|
||||
|
||||
export type FileEntry = {
|
||||
key: FUID
|
||||
size: number
|
||||
mtime: number
|
||||
}
|
||||
export type FileEntry = [
|
||||
number, // level
|
||||
string, // name
|
||||
FUID,
|
||||
number, //mtime
|
||||
number, // size
|
||||
number, // isfile
|
||||
]
|
||||
|
||||
export type DirEntry = {
|
||||
key: FUID
|
||||
size: number
|
||||
mtime: number
|
||||
dir: DirList
|
||||
}
|
||||
|
||||
export type DirList = Record<string, FileEntry | DirEntry>
|
||||
|
||||
export type UpdateEntry = {
|
||||
name: string
|
||||
deleted?: boolean
|
||||
key?: FUID
|
||||
size?: number
|
||||
mtime?: number
|
||||
dir?: DirList
|
||||
}
|
||||
export type UpdateEntry = ['k', number] | ['d', number] | ['i', Array<FileEntry>]
|
||||
|
||||
// Helper structure for selections
|
||||
export interface SelectedItems {
|
||||
|
|
|
@ -1,14 +1,29 @@
|
|||
import { useDocumentStore } from "@/stores/documents"
|
||||
import type { DirEntry, UpdateEntry, errorEvent } from "./Document"
|
||||
import type { FileEntry, UpdateEntry, errorEvent } from "./Document"
|
||||
|
||||
export const controlUrl = '/api/control'
|
||||
export const uploadUrl = '/api/upload'
|
||||
export const watchUrl = '/api/watch'
|
||||
|
||||
let tree = null as DirEntry | null
|
||||
let tree = [] as FileEntry[]
|
||||
let reconnectDuration = 500
|
||||
let wsWatch = null as WebSocket | null
|
||||
|
||||
export const loadSession = () => {
|
||||
const store = useDocumentStore()
|
||||
try {
|
||||
tree = JSON.parse(sessionStorage["cista-files"])
|
||||
store.updateRoot(tree)
|
||||
return true
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const saveSession = () => {
|
||||
sessionStorage["cista-files"] = JSON.stringify(tree)
|
||||
}
|
||||
|
||||
export const connect = (path: string, handlers: Partial<Record<keyof WebSocketEventMap, any>>) => {
|
||||
const webSocket = new WebSocket(new URL(path, location.origin.replace(/^http/, 'ws')))
|
||||
for (const [event, handler] of Object.entries(handlers)) webSocket.addEventListener(event, handler)
|
||||
|
@ -99,29 +114,31 @@ function handleRootMessage({ root }: { root: DirEntry }) {
|
|||
console.log('Watch root', root)
|
||||
store.updateRoot(root)
|
||||
tree = root
|
||||
saveSession()
|
||||
}
|
||||
|
||||
function handleUpdateMessage(updateData: { update: UpdateEntry[] }) {
|
||||
const store = useDocumentStore()
|
||||
console.log('Watch update', updateData.update)
|
||||
const update = updateData.update
|
||||
console.log('Watch update', update)
|
||||
if (!tree) return console.error('Watch update before root')
|
||||
let node: DirEntry = tree
|
||||
for (const elem of updateData.update) {
|
||||
if (elem.deleted) {
|
||||
delete node.dir[elem.name]
|
||||
break // Deleted elements can't have further children
|
||||
let newtree = []
|
||||
let oidx = 0
|
||||
|
||||
for (const [action, arg] of update) {
|
||||
if (action === 'k') {
|
||||
newtree.push(...tree.slice(oidx, oidx + arg))
|
||||
oidx += arg
|
||||
}
|
||||
if (elem.name) {
|
||||
// @ts-ignore
|
||||
console.log(node, elem.name)
|
||||
node = node.dir[elem.name] ||= {}
|
||||
else if (action === 'd') oidx += arg
|
||||
else if (action === 'i') newtree.push(...arg)
|
||||
else console.log("Unknown update action", action, arg)
|
||||
}
|
||||
if (elem.key !== undefined) node.key = elem.key
|
||||
if (elem.size !== undefined) node.size = elem.size
|
||||
if (elem.mtime !== undefined) node.mtime = elem.mtime
|
||||
if (elem.dir !== undefined) node.dir = elem.dir
|
||||
}
|
||||
store.updateRoot(tree)
|
||||
if (oidx != tree.length)
|
||||
throw Error(`Tree update out of sync, number of entries mismatch: got ${oidx}, expected ${tree.length}`)
|
||||
store.updateRoot(newtree)
|
||||
tree = newtree
|
||||
saveSession()
|
||||
}
|
||||
|
||||
function handleError(msg: errorEvent) {
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
import type {
|
||||
Document,
|
||||
DirEntry,
|
||||
FileEntry,
|
||||
FUID,
|
||||
SelectedItems
|
||||
} from '@/repositories/Document'
|
||||
import type { Document, FileEntry, FUID, SelectedItems } from '@/repositories/Document'
|
||||
import { formatSize, formatUnixDate, haystackFormat } from '@/utils'
|
||||
import { defineStore } from 'pinia'
|
||||
import { collator } from '@/utils'
|
||||
|
@ -26,7 +20,6 @@ export const useDocumentStore = defineStore({
|
|||
id: 'documents',
|
||||
state: () => ({
|
||||
document: [] as Document[],
|
||||
search: "" as string,
|
||||
selected: new Set<FUID>(),
|
||||
uploadingDocuments: [],
|
||||
uploadCount: 0 as number,
|
||||
|
@ -41,46 +34,27 @@ export const useDocumentStore = defineStore({
|
|||
isOpenLoginModal: false
|
||||
} as User
|
||||
}),
|
||||
persist: {
|
||||
storage: sessionStorage,
|
||||
paths: ['document'],
|
||||
},
|
||||
actions: {
|
||||
updateRoot(root: DirEntry | null = null) {
|
||||
if (!root) {
|
||||
this.document = []
|
||||
return
|
||||
}
|
||||
// Transform tree data to flat documents array
|
||||
let loc = ""
|
||||
const mapper = ([name, attr]: [string, FileEntry | DirEntry]) => ({
|
||||
...attr,
|
||||
loc,
|
||||
name,
|
||||
sizedisp: formatSize(attr.size),
|
||||
modified: formatUnixDate(attr.mtime),
|
||||
haystack: haystackFormat(name),
|
||||
})
|
||||
const queue = [...Object.entries(root.dir ?? {}).map(mapper)]
|
||||
updateRoot(root: FileEntry[]) {
|
||||
const docs = []
|
||||
for (let doc; (doc = queue.shift()) !== undefined;) {
|
||||
docs.push(doc)
|
||||
if ("dir" in doc) {
|
||||
// Recurse but replace recursive structure with boolean
|
||||
loc = doc.loc ? `${doc.loc}/${doc.name}` : doc.name
|
||||
queue.push(...Object.entries(doc.dir).map(mapper))
|
||||
// @ts-ignore
|
||||
doc.dir = true
|
||||
let loc = [] as string[]
|
||||
for (const [level, name, key, mtime, size, isfile] of root) {
|
||||
if (level === 0) continue
|
||||
loc = loc.slice(0, level - 1)
|
||||
docs.push({
|
||||
name,
|
||||
loc: loc.join('/'),
|
||||
key,
|
||||
size,
|
||||
sizedisp: formatSize(size),
|
||||
mtime,
|
||||
modified: formatUnixDate(mtime),
|
||||
haystack: haystackFormat(name),
|
||||
dir: !isfile,
|
||||
})
|
||||
loc.push(name)
|
||||
}
|
||||
// @ts-ignore
|
||||
else doc.dir = false
|
||||
}
|
||||
// Pre sort directory entries folders first then files, names in natural ordering
|
||||
docs.sort((a, b) =>
|
||||
// @ts-ignore
|
||||
b.dir - a.dir ||
|
||||
collator.compare(a.name, b.name)
|
||||
)
|
||||
console.log("Documents", docs)
|
||||
this.document = docs as Document[]
|
||||
},
|
||||
login(username: string, privileged: boolean) {
|
||||
|
|
|
@ -16,17 +16,17 @@ import { needleFormat, localeIncludes, collator } from '@/utils';
|
|||
|
||||
const documentStore = useDocumentStore()
|
||||
const fileExplorer = ref()
|
||||
const props = defineProps({
|
||||
const props = defineProps<{
|
||||
path: Array<string>
|
||||
})
|
||||
query: string
|
||||
}>()
|
||||
const documents = computed(() => {
|
||||
if (!props.path) return []
|
||||
const loc = props.path.join('/')
|
||||
const query = props.query
|
||||
// List the current location
|
||||
if (!documentStore.search) return documentStore.document.filter(doc => doc.loc === loc)
|
||||
if (!query) return documentStore.document.filter(doc => doc.loc === loc)
|
||||
// Find up to 100 newest documents that match the search
|
||||
const search = documentStore.search
|
||||
const needle = needleFormat(search)
|
||||
const needle = needleFormat(query)
|
||||
let limit = 100
|
||||
let docs = []
|
||||
for (const doc of documentStore.recentDocuments) {
|
||||
|
@ -46,7 +46,7 @@ const documents = computed(() => {
|
|||
// @ts-ignore
|
||||
(a.type === 'file') - (b.type === 'file') ||
|
||||
// @ts-ignore
|
||||
b.name.includes(search) - a.name.includes(search) ||
|
||||
b.name.includes(query) - a.name.includes(query) ||
|
||||
collator.compare(a.name, b.name)
|
||||
))
|
||||
return docs
|
||||
|
|
Loading…
Reference in New Issue
Block a user