diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index d7a91d5..a862b9a 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -1,13 +1,13 @@
-
+
@@ -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 = 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
diff --git a/frontend/src/repositories/Document.ts b/frontend/src/repositories/Document.ts
index 57355b4..022b260 100644
--- a/frontend/src/repositories/Document.ts
+++ b/frontend/src/repositories/Document.ts
@@ -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
-
-export type UpdateEntry = {
- name: string
- deleted?: boolean
- key?: FUID
- size?: number
- mtime?: number
- dir?: DirList
-}
+export type UpdateEntry = ['k', number] | ['d', number] | ['i', Array]
// Helper structure for selections
export interface SelectedItems {
diff --git a/frontend/src/repositories/WS.ts b/frontend/src/repositories/WS.ts
index d5ebf8a..0570921 100644
--- a/frontend/src/repositories/WS.ts
+++ b/frontend/src/repositories/WS.ts
@@ -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>) => {
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
- }
- if (elem.name) {
- // @ts-ignore
- console.log(node, elem.name)
- node = node.dir[elem.name] ||= {}
- }
- 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
+ let newtree = []
+ let oidx = 0
+
+ for (const [action, arg] of update) {
+ if (action === 'k') {
+ newtree.push(...tree.slice(oidx, oidx + arg))
+ oidx += arg
+ }
+ else if (action === 'd') oidx += arg
+ else if (action === 'i') newtree.push(...arg)
+ else console.log("Unknown update action", action, arg)
}
- 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) {
diff --git a/frontend/src/stores/documents.ts b/frontend/src/stores/documents.ts
index 97c6ca6..985d5fd 100644
--- a/frontend/src/stores/documents.ts
+++ b/frontend/src/stores/documents.ts
@@ -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(),
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
- }
- // @ts-ignore
- else doc.dir = false
+ 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)
}
- // 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) {
diff --git a/frontend/src/views/ExplorerView.vue b/frontend/src/views/ExplorerView.vue
index b163caa..773d2ee 100644
--- a/frontend/src/views/ExplorerView.vue
+++ b/frontend/src/views/ExplorerView.vue
@@ -16,17 +16,17 @@ import { needleFormat, localeIncludes, collator } from '@/utils';
const documentStore = useDocumentStore()
const fileExplorer = ref()
-const props = defineProps({
+const props = defineProps<{
path: Array
-})
+ 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