Leo Vasanko 41e8c78ecd Refactoring Document storage (#5)
- Major refactoring that makes Doc a class with properties
- Data made only shallow reactive, for a good speedup of initial load
- Minor bugfixes and UX improvements along the way
- Fixed handling of hash and question marks in URLs (was confusing Vue Router)
- Search made stricter to find good results (not ignore all punctuation)

Reviewed-on: #5
2023-11-13 17:52:57 +00:00

156 lines
4.2 KiB
TypeScript

import { useMainStore } from "@/stores/main"
import type { FileEntry, UpdateEntry, errorEvent } from "./Document"
export const controlUrl = '/api/control'
export const uploadUrl = '/api/upload'
export const watchUrl = '/api/watch'
let tree = [] as FileEntry[]
let reconnDelay = 500
let wsWatch = null as WebSocket | null
export const loadSession = () => {
const s = localStorage['cista-files']
if (!s) return false
const store = useMainStore()
try {
tree = JSON.parse(s)
store.updateRoot(tree)
console.log(`Loaded session with ${tree.length} items cached`)
return true
} catch (error) {
console.log("Loading session failed", error)
return false
}
}
const saveSession = () => {
localStorage["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)
return webSocket
}
export const watchConnect = () => {
if (watchTimeout !== null) {
clearTimeout(watchTimeout)
watchTimeout = null
}
const store = useMainStore()
if (store.error !== 'Reconnecting...') store.error = 'Connecting...'
console.log(store.error)
wsWatch = connect(watchUrl, {
message: handleWatchMessage,
close: watchReconnect,
})
wsWatch.addEventListener("message", event => {
if (store.connected) return
const msg = JSON.parse(event.data)
if ('error' in msg) {
if (msg.error.code === 401) {
store.user.isLoggedIn = false
store.user.isOpenLoginModal = true
} else {
store.error = msg.error.message
}
return
}
if ("server" in msg) {
console.log('Connected to backend', msg)
store.server = msg.server
store.connected = true
reconnDelay = 500
store.error = ''
if (msg.user) store.login(msg.user.username, msg.user.privileged)
else if (store.isUserLogged) store.logout()
if (!msg.server.public && !msg.user) store.user.isOpenLoginModal = true
}
})
}
export const watchDisconnect = () => {
if (!wsWatch) return
wsWatch.close()
wsWatch = null
}
let watchTimeout: any = null
const watchReconnect = (event: MessageEvent) => {
const store = useMainStore()
if (store.connected) {
console.warn("Disconnected from server", event)
store.connected = false
store.error = 'Reconnecting...'
}
reconnDelay = Math.min(5000, reconnDelay + 500)
// The server closes the websocket after errors, so we need to reopen it
if (watchTimeout !== null) clearTimeout(watchTimeout)
watchTimeout = setTimeout(watchConnect, reconnDelay)
}
const handleWatchMessage = (event: MessageEvent) => {
const msg = JSON.parse(event.data)
switch (true) {
case !!msg.root:
handleRootMessage(msg)
break
case !!msg.update:
handleUpdateMessage(msg)
break
case !!msg.space:
console.log('Watch space', msg.space)
break
case !!msg.error:
handleError(msg)
break
default:
}
}
function handleRootMessage({ root }: { root: FileEntry[] }) {
const store = useMainStore()
console.log('Watch root', root)
store.updateRoot(root)
tree = root
saveSession()
}
function handleUpdateMessage(updateData: { update: UpdateEntry[] }) {
const store = useMainStore()
const update = updateData.update
console.log('Watch update', update)
if (!tree) return console.error('Watch update before root')
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)
}
if (oidx != tree.length)
throw Error(`Tree update out of sync, number of entries mismatch: got ${oidx}, expected ${tree.length}, new tree ${newtree.length}`)
store.updateRoot(newtree)
tree = newtree
saveSession()
}
function handleError(msg: errorEvent) {
const store = useMainStore()
if (msg.error.code === 401) {
store.user.isOpenLoginModal = true
store.user.isLoggedIn = false
return
}
}