- 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
156 lines
4.2 KiB
TypeScript
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
|
|
}
|
|
}
|