Refactoring Document storage #5
|
@ -17,7 +17,7 @@ import type { ComputedRef } from 'vue'
|
||||||
import type HeaderMain from '@/components/HeaderMain.vue'
|
import type HeaderMain from '@/components/HeaderMain.vue'
|
||||||
import { onMounted, onUnmounted, ref, watchEffect } from 'vue'
|
import { onMounted, onUnmounted, ref, watchEffect } from 'vue'
|
||||||
import { loadSession, watchConnect, watchDisconnect } from '@/repositories/WS'
|
import { loadSession, watchConnect, watchDisconnect } from '@/repositories/WS'
|
||||||
import { useDocumentStore } from '@/stores/documents'
|
import { useMainStore } from '@/stores/main'
|
||||||
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import Router from '@/router/index'
|
import Router from '@/router/index'
|
||||||
|
@ -27,7 +27,7 @@ interface Path {
|
||||||
pathList: string[]
|
pathList: string[]
|
||||||
query: string
|
query: string
|
||||||
}
|
}
|
||||||
const documentStore = useDocumentStore()
|
const store = useMainStore()
|
||||||
const path: ComputedRef<Path> = computed(() => {
|
const path: ComputedRef<Path> = computed(() => {
|
||||||
const p = decodeURIComponent(Router.currentRoute.value.path).split('//')
|
const p = decodeURIComponent(Router.currentRoute.value.path).split('//')
|
||||||
const pathList = p[0].split('/').filter(value => value !== '')
|
const pathList = p[0].split('/').filter(value => value !== '')
|
||||||
|
@ -39,7 +39,7 @@ const path: ComputedRef<Path> = computed(() => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
document.title = path.value.path.replace(/\/$/, '').split('/').pop() || documentStore.server.name || 'Cista Storage'
|
document.title = path.value.path.replace(/\/$/, '').split('/').pop() || store.server.name || 'Cista Storage'
|
||||||
})
|
})
|
||||||
onMounted(loadSession)
|
onMounted(loadSession)
|
||||||
onMounted(watchConnect)
|
onMounted(watchConnect)
|
||||||
|
@ -48,7 +48,7 @@ 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 fileExplorer = documentStore.fileExplorer as any
|
const fileExplorer = store.fileExplorer as any
|
||||||
if (!fileExplorer) return
|
if (!fileExplorer) return
|
||||||
const c = fileExplorer.isCursor()
|
const c = fileExplorer.isCursor()
|
||||||
const keyup = event.type === 'keyup'
|
const keyup = event.type === 'keyup'
|
||||||
|
@ -124,3 +124,4 @@ onUnmounted(() => {
|
||||||
})
|
})
|
||||||
export type { Path }
|
export type { Path }
|
||||||
</script>
|
</script>
|
||||||
|
@/stores/main
|
||||||
|
|
|
@ -36,11 +36,11 @@
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
:checked="documentStore.selected.has(doc.key)"
|
:checked="store.selected.has(doc.key)"
|
||||||
@change="
|
@change="
|
||||||
($event.target as HTMLInputElement).checked
|
($event.target as HTMLInputElement).checked
|
||||||
? documentStore.selected.add(doc.key)
|
? store.selected.add(doc.key)
|
||||||
: documentStore.selected.delete(doc.key)
|
: store.selected.delete(doc.key)
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
@ -80,7 +80,7 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watchEffect, shallowRef, onMounted, onUnmounted } from 'vue'
|
import { ref, computed, watchEffect, shallowRef, onMounted, onUnmounted } from 'vue'
|
||||||
import { useDocumentStore } from '@/stores/documents'
|
import { useMainStore } from '@/stores/main'
|
||||||
import { Doc } from '@/repositories/Document'
|
import { Doc } from '@/repositories/Document'
|
||||||
import FileRenameInput from './FileRenameInput.vue'
|
import FileRenameInput from './FileRenameInput.vue'
|
||||||
import { connect, controlUrl } from '@/repositories/WS'
|
import { connect, controlUrl } from '@/repositories/WS'
|
||||||
|
@ -91,7 +91,7 @@ const props = defineProps<{
|
||||||
path: Array<string>
|
path: Array<string>
|
||||||
documents: Doc[]
|
documents: Doc[]
|
||||||
}>()
|
}>()
|
||||||
const documentStore = useDocumentStore()
|
const store = useMainStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const url_for = (doc: Doc) => {
|
const url_for = (doc: Doc) => {
|
||||||
const p = doc.loc ? `${doc.loc}/${doc.name}` : doc.name
|
const p = doc.loc ? `${doc.loc}/${doc.name}` : doc.name
|
||||||
|
@ -159,10 +159,10 @@ defineExpose({
|
||||||
cursorSelect() {
|
cursorSelect() {
|
||||||
const doc = cursor.value
|
const doc = cursor.value
|
||||||
if (!doc) return
|
if (!doc) return
|
||||||
if (documentStore.selected.has(doc.key)) {
|
if (store.selected.has(doc.key)) {
|
||||||
documentStore.selected.delete(doc.key)
|
store.selected.delete(doc.key)
|
||||||
} else {
|
} else {
|
||||||
documentStore.selected.add(doc.key)
|
store.selected.add(doc.key)
|
||||||
}
|
}
|
||||||
this.cursorMove(1)
|
this.cursorMove(1)
|
||||||
},
|
},
|
||||||
|
@ -187,8 +187,8 @@ defineExpose({
|
||||||
for (let p = begin; p !== end; p = increment(p, 1)) {
|
for (let p = begin; p !== end; p = increment(p, 1)) {
|
||||||
if (p === N) continue
|
if (p === N) continue
|
||||||
const key = documents[p].key
|
const key = documents[p].key
|
||||||
if (documentStore.selected.has(key)) documentStore.selected.delete(key)
|
if (store.selected.has(key)) store.selected.delete(key)
|
||||||
else documentStore.selected.add(key)
|
else store.selected.add(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -278,7 +278,7 @@ const selectionIndeterminate = computed({
|
||||||
get: () => {
|
get: () => {
|
||||||
return (
|
return (
|
||||||
props.documents.length > 0 &&
|
props.documents.length > 0 &&
|
||||||
props.documents.some((doc: Doc) => documentStore.selected.has(doc.key)) &&
|
props.documents.some((doc: Doc) => store.selected.has(doc.key)) &&
|
||||||
!allSelected.value
|
!allSelected.value
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -289,16 +289,16 @@ const allSelected = computed({
|
||||||
get: () => {
|
get: () => {
|
||||||
return (
|
return (
|
||||||
props.documents.length > 0 &&
|
props.documents.length > 0 &&
|
||||||
props.documents.every((doc: Doc) => documentStore.selected.has(doc.key))
|
props.documents.every((doc: Doc) => store.selected.has(doc.key))
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
set: (value: boolean) => {
|
set: (value: boolean) => {
|
||||||
console.log('Setting allSelected', value)
|
console.log('Setting allSelected', value)
|
||||||
for (const doc of props.documents) {
|
for (const doc of props.documents) {
|
||||||
if (value) {
|
if (value) {
|
||||||
documentStore.selected.add(doc.key)
|
store.selected.add(doc.key)
|
||||||
} else {
|
} else {
|
||||||
documentStore.selected.delete(doc.key)
|
store.selected.delete(doc.key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -456,3 +456,4 @@ tbody .selection input {
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@/stores/main
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<nav class="headermain">
|
<nav class="headermain">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<template v-if="documentStore.error">
|
<template v-if="store.error">
|
||||||
<div class="error-message" @click="documentStore.error = ''">{{ documentStore.error }}</div>
|
<div class="error-message" @click="store.error = ''">{{ store.error }}</div>
|
||||||
<div class="smallgap"></div>
|
<div class="smallgap"></div>
|
||||||
</template>
|
</template>
|
||||||
<UploadButton :path="props.path" />
|
<UploadButton :path="props.path" />
|
||||||
<SvgButton
|
<SvgButton
|
||||||
name="create-folder"
|
name="create-folder"
|
||||||
data-tooltip="New folder"
|
data-tooltip="New folder"
|
||||||
@click="() => documentStore.fileExplorer!.newFolder()"
|
@click="() => store.fileExplorer!.newFolder()"
|
||||||
/>
|
/>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<div class="spacer smallgap"></div>
|
<div class="spacer smallgap"></div>
|
||||||
|
@ -32,12 +32,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useDocumentStore } from '@/stores/documents'
|
import { useMainStore } from '@/stores/main'
|
||||||
import { ref, nextTick, watchEffect } from 'vue'
|
import { ref, nextTick, watchEffect } from 'vue'
|
||||||
import ContextMenu from '@imengyu/vue3-context-menu'
|
import ContextMenu from '@imengyu/vue3-context-menu'
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
|
|
||||||
const documentStore = useDocumentStore()
|
const store = useMainStore()
|
||||||
const showSearchInput = ref<boolean>(false)
|
const showSearchInput = ref<boolean>(false)
|
||||||
const search = ref<HTMLInputElement | null>()
|
const search = ref<HTMLInputElement | null>()
|
||||||
const searchButton = ref<HTMLButtonElement | null>()
|
const searchButton = ref<HTMLButtonElement | null>()
|
||||||
|
@ -76,10 +76,10 @@ watchEffect(() => {
|
||||||
const settingsMenu = (e: Event) => {
|
const settingsMenu = (e: Event) => {
|
||||||
// show the context menu
|
// show the context menu
|
||||||
const items = []
|
const items = []
|
||||||
if (documentStore.user.isLoggedIn) {
|
if (store.user.isLoggedIn) {
|
||||||
items.push({ label: `Logout ${documentStore.user.username ?? ''}`, onClick: () => documentStore.logout() })
|
items.push({ label: `Logout ${store.user.username ?? ''}`, onClick: () => store.logout() })
|
||||||
} else {
|
} else {
|
||||||
items.push({ label: 'Login', onClick: () => documentStore.loginDialog() })
|
items.push({ label: 'Login', onClick: () => store.loginDialog() })
|
||||||
}
|
}
|
||||||
ContextMenu.showContextMenu({
|
ContextMenu.showContextMenu({
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -115,3 +115,4 @@ input[type='search'] {
|
||||||
max-width: 30vw;
|
max-width: 30vw;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@/stores/main
|
||||||
|
|
|
@ -1,29 +1,29 @@
|
||||||
<template>
|
<template>
|
||||||
<template v-if="documentStore.selected.size">
|
<template v-if="store.selected.size">
|
||||||
<div class="smallgap"></div>
|
<div class="smallgap"></div>
|
||||||
<p class="select-text">{{ documentStore.selected.size }} selected ➤</p>
|
<p class="select-text">{{ store.selected.size }} selected ➤</p>
|
||||||
<SvgButton name="download" data-tooltip="Download" @click="download" />
|
<SvgButton name="download" data-tooltip="Download" @click="download" />
|
||||||
<SvgButton name="copy" data-tooltip="Copy here" @click="op('cp', dst)" />
|
<SvgButton name="copy" data-tooltip="Copy here" @click="op('cp', dst)" />
|
||||||
<SvgButton name="paste" data-tooltip="Move here" @click="op('mv', dst)" />
|
<SvgButton name="paste" data-tooltip="Move here" @click="op('mv', dst)" />
|
||||||
<SvgButton name="trash" data-tooltip="Delete ⚠️" @click="op('rm')" />
|
<SvgButton name="trash" data-tooltip="Delete ⚠️" @click="op('rm')" />
|
||||||
<button class="action-button unselect" data-tooltip="Unselect all" @click="documentStore.selected.clear()">❌</button>
|
<button class="action-button unselect" data-tooltip="Unselect all" @click="store.selected.clear()">❌</button>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {connect, controlUrl} from '@/repositories/WS'
|
import {connect, controlUrl} from '@/repositories/WS'
|
||||||
import { useDocumentStore } from '@/stores/documents'
|
import { useMainStore } from '@/stores/main'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import type { SelectedItems } from '@/repositories/Document'
|
import type { SelectedItems } from '@/repositories/Document'
|
||||||
|
|
||||||
const documentStore = useDocumentStore()
|
const store = useMainStore()
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
path: Array<string>
|
path: Array<string>
|
||||||
})
|
})
|
||||||
|
|
||||||
const dst = computed(() => props.path!.join('/'))
|
const dst = computed(() => props.path!.join('/'))
|
||||||
const op = (op: string, dst?: string) => {
|
const op = (op: string, dst?: string) => {
|
||||||
const sel = documentStore.selectedFiles
|
const sel = store.selectedFiles
|
||||||
const msg = {
|
const msg = {
|
||||||
op,
|
op,
|
||||||
sel: sel.keys.map(key => {
|
sel: sel.keys.map(key => {
|
||||||
|
@ -38,12 +38,12 @@ const op = (op: string, dst?: string) => {
|
||||||
const res = JSON.parse(ev.data)
|
const res = JSON.parse(ev.data)
|
||||||
if ('error' in res) {
|
if ('error' in res) {
|
||||||
console.error('Control socket error', msg, res.error)
|
console.error('Control socket error', msg, res.error)
|
||||||
documentStore.error = res.error.message
|
store.error = res.error.message
|
||||||
return
|
return
|
||||||
} else if (res.status === 'ack') {
|
} else if (res.status === 'ack') {
|
||||||
console.log('Control ack OK', res)
|
console.log('Control ack OK', res)
|
||||||
control.close()
|
control.close()
|
||||||
documentStore.selected.clear()
|
store.selected.clear()
|
||||||
return
|
return
|
||||||
} else console.log('Unknown control response', msg, res)
|
} else console.log('Unknown control response', msg, res)
|
||||||
}
|
}
|
||||||
|
@ -108,17 +108,17 @@ const filesystemdl = async (sel: SelectedItems, handle: FileSystemDirectoryHandl
|
||||||
}
|
}
|
||||||
|
|
||||||
const download = async () => {
|
const download = async () => {
|
||||||
const sel = documentStore.selectedFiles
|
const sel = store.selectedFiles
|
||||||
console.log('Download', sel)
|
console.log('Download', sel)
|
||||||
if (sel.keys.length === 0) {
|
if (sel.keys.length === 0) {
|
||||||
console.warn('Attempted download but no files found. Missing selected keys:', sel.missing)
|
console.warn('Attempted download but no files found. Missing selected keys:', sel.missing)
|
||||||
documentStore.selected.clear()
|
store.selected.clear()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Plain old a href download if only one file (ignoring any folders)
|
// Plain old a href download if only one file (ignoring any folders)
|
||||||
const files = sel.recursive.filter(([rel, full, doc]) => !doc.dir)
|
const files = sel.recursive.filter(([rel, full, doc]) => !doc.dir)
|
||||||
if (files.length === 1) {
|
if (files.length === 1) {
|
||||||
documentStore.selected.clear()
|
store.selected.clear()
|
||||||
return linkdl(`/files/${files[0][1]}`)
|
return linkdl(`/files/${files[0][1]}`)
|
||||||
}
|
}
|
||||||
// Use FileSystem API if multiple files and the browser supports it
|
// Use FileSystem API if multiple files and the browser supports it
|
||||||
|
@ -130,7 +130,7 @@ const download = async () => {
|
||||||
mode: 'readwrite'
|
mode: 'readwrite'
|
||||||
})
|
})
|
||||||
filesystemdl(sel, handle).then(() => {
|
filesystemdl(sel, handle).then(() => {
|
||||||
documentStore.selected.clear()
|
store.selected.clear()
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -140,7 +140,7 @@ const download = async () => {
|
||||||
// Otherwise, zip and download
|
// Otherwise, zip and download
|
||||||
const name = sel.keys.length === 1 ? sel.docs[sel.keys[0]].name : 'download'
|
const name = sel.keys.length === 1 ? sel.docs[sel.keys[0]].name : 'download'
|
||||||
linkdl(`/zip/${Array.from(sel.keys).join('+')}/${name}.zip`)
|
linkdl(`/zip/${Array.from(sel.keys).join('+')}/${name}.zip`)
|
||||||
documentStore.selected.clear()
|
store.selected.clear()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -152,3 +152,4 @@ const download = async () => {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@/stores/main
|
||||||
|
|
|
@ -39,10 +39,10 @@
|
||||||
import { reactive, ref } from 'vue'
|
import { reactive, ref } from 'vue'
|
||||||
import { loginUser } from '@/repositories/User'
|
import { loginUser } from '@/repositories/User'
|
||||||
import type { ISimpleError } from '@/repositories/Client'
|
import type { ISimpleError } from '@/repositories/Client'
|
||||||
import { useDocumentStore } from '@/stores/documents'
|
import { useMainStore } from '@/stores/main'
|
||||||
|
|
||||||
const confirmLoading = ref<boolean>(false)
|
const confirmLoading = ref<boolean>(false)
|
||||||
const store = useDocumentStore()
|
const store = useMainStore()
|
||||||
|
|
||||||
const loginForm = reactive({
|
const loginForm = reactive({
|
||||||
username: '',
|
username: '',
|
||||||
|
@ -99,3 +99,4 @@ const login = async () => {
|
||||||
height: 1em;
|
height: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@/stores/main
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { connect, uploadUrl } from '@/repositories/WS';
|
import { connect, uploadUrl } from '@/repositories/WS';
|
||||||
import { useDocumentStore } from '@/stores/documents'
|
import { useMainStore } from '@/stores/main'
|
||||||
import { collator } from '@/utils';
|
import { collator } from '@/utils';
|
||||||
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'
|
||||||
|
|
||||||
const fileInput = ref()
|
const fileInput = ref()
|
||||||
const folderInput = ref()
|
const folderInput = ref()
|
||||||
const documentStore = useDocumentStore()
|
const store = useMainStore()
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
path: Array<string>
|
path: Array<string>
|
||||||
})
|
})
|
||||||
|
@ -75,7 +75,7 @@ const uploadFiles = (infiles: File[]) => {
|
||||||
const uploadCloudFiles = (files: CloudFile[]) => {
|
const uploadCloudFiles = (files: CloudFile[]) => {
|
||||||
const dotfiles = files.filter(f => f.cloudName.includes('/.'))
|
const dotfiles = files.filter(f => f.cloudName.includes('/.'))
|
||||||
if (dotfiles.length) {
|
if (dotfiles.length) {
|
||||||
documentStore.error = "Won't upload dotfiles"
|
store.error = "Won't upload dotfiles"
|
||||||
console.log("Dotfiles omitted", dotfiles)
|
console.log("Dotfiles omitted", dotfiles)
|
||||||
files = files.filter(f => !f.cloudName.includes('/.'))
|
files = files.filter(f => !f.cloudName.includes('/.'))
|
||||||
}
|
}
|
||||||
|
@ -171,13 +171,13 @@ const WSCreate = async () => await new Promise<WebSocket>(resolve => {
|
||||||
open(ev: Event) { resolve(ws) },
|
open(ev: Event) { resolve(ws) },
|
||||||
error(ev: Event) {
|
error(ev: Event) {
|
||||||
console.error('Upload socket error', ev)
|
console.error('Upload socket error', ev)
|
||||||
documentStore.error = 'Upload socket error'
|
store.error = 'Upload socket error'
|
||||||
},
|
},
|
||||||
message(ev: MessageEvent) {
|
message(ev: MessageEvent) {
|
||||||
const res = JSON.parse(ev!.data)
|
const res = JSON.parse(ev!.data)
|
||||||
if ('error' in res) {
|
if ('error' in res) {
|
||||||
console.error('Upload socket error', res.error)
|
console.error('Upload socket error', res.error)
|
||||||
documentStore.error = res.error.message
|
store.error = res.error.message
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (res.status === 'ack') {
|
if (res.status === 'ack') {
|
||||||
|
@ -302,3 +302,4 @@ span {
|
||||||
.position { min-width: 4em }
|
.position { min-width: 4em }
|
||||||
.speed { min-width: 4em }
|
.speed { min-width: 4em }
|
||||||
</style>
|
</style>
|
||||||
|
@/stores/main
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useDocumentStore } from "@/stores/documents"
|
import { useMainStore } from "@/stores/main"
|
||||||
import type { FileEntry, UpdateEntry, errorEvent } from "./Document"
|
import type { FileEntry, UpdateEntry, errorEvent } from "./Document"
|
||||||
|
|
||||||
export const controlUrl = '/api/control'
|
export const controlUrl = '/api/control'
|
||||||
|
@ -12,7 +12,7 @@ let wsWatch = null as WebSocket | null
|
||||||
export const loadSession = () => {
|
export const loadSession = () => {
|
||||||
const s = localStorage['cista-files']
|
const s = localStorage['cista-files']
|
||||||
if (!s) return false
|
if (!s) return false
|
||||||
const store = useDocumentStore()
|
const store = useMainStore()
|
||||||
try {
|
try {
|
||||||
tree = JSON.parse(s)
|
tree = JSON.parse(s)
|
||||||
store.updateRoot(tree)
|
store.updateRoot(tree)
|
||||||
|
@ -39,7 +39,7 @@ export const watchConnect = () => {
|
||||||
clearTimeout(watchTimeout)
|
clearTimeout(watchTimeout)
|
||||||
watchTimeout = null
|
watchTimeout = null
|
||||||
}
|
}
|
||||||
const store = useDocumentStore()
|
const store = useMainStore()
|
||||||
if (store.error !== 'Reconnecting...') store.error = 'Connecting...'
|
if (store.error !== 'Reconnecting...') store.error = 'Connecting...'
|
||||||
console.log(store.error)
|
console.log(store.error)
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ export const watchDisconnect = () => {
|
||||||
let watchTimeout: any = null
|
let watchTimeout: any = null
|
||||||
|
|
||||||
const watchReconnect = (event: MessageEvent) => {
|
const watchReconnect = (event: MessageEvent) => {
|
||||||
const store = useDocumentStore()
|
const store = useMainStore()
|
||||||
if (store.connected) {
|
if (store.connected) {
|
||||||
console.warn("Disconnected from server", event)
|
console.warn("Disconnected from server", event)
|
||||||
store.connected = false
|
store.connected = false
|
||||||
|
@ -114,7 +114,7 @@ const handleWatchMessage = (event: MessageEvent) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRootMessage({ root }: { root: FileEntry[] }) {
|
function handleRootMessage({ root }: { root: FileEntry[] }) {
|
||||||
const store = useDocumentStore()
|
const store = useMainStore()
|
||||||
console.log('Watch root', root)
|
console.log('Watch root', root)
|
||||||
store.updateRoot(root)
|
store.updateRoot(root)
|
||||||
tree = root
|
tree = root
|
||||||
|
@ -122,7 +122,7 @@ function handleRootMessage({ root }: { root: FileEntry[] }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUpdateMessage(updateData: { update: UpdateEntry[] }) {
|
function handleUpdateMessage(updateData: { update: UpdateEntry[] }) {
|
||||||
const store = useDocumentStore()
|
const store = useMainStore()
|
||||||
const update = updateData.update
|
const update = updateData.update
|
||||||
console.log('Watch update', update)
|
console.log('Watch update', update)
|
||||||
if (!tree) return console.error('Watch update before root')
|
if (!tree) return console.error('Watch update before root')
|
||||||
|
@ -146,7 +146,7 @@ function handleUpdateMessage(updateData: { update: UpdateEntry[] }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleError(msg: errorEvent) {
|
function handleError(msg: errorEvent) {
|
||||||
const store = useDocumentStore()
|
const store = useMainStore()
|
||||||
if (msg.error.code === 401) {
|
if (msg.error.code === 401) {
|
||||||
store.user.isOpenLoginModal = true
|
store.user.isOpenLoginModal = true
|
||||||
store.user.isLoggedIn = false
|
store.user.isLoggedIn = false
|
||||||
|
|
|
@ -1,126 +0,0 @@
|
||||||
import type { FileEntry, FUID, SelectedItems } from '@/repositories/Document'
|
|
||||||
import { Doc } from '@/repositories/Document'
|
|
||||||
import { defineStore } from 'pinia'
|
|
||||||
import { collator } from '@/utils'
|
|
||||||
import { logoutUser } from '@/repositories/User'
|
|
||||||
import { watchConnect } from '@/repositories/WS'
|
|
||||||
import { shallowRef } from 'vue'
|
|
||||||
|
|
||||||
type User = {
|
|
||||||
username: string
|
|
||||||
privileged: boolean
|
|
||||||
isOpenLoginModal: boolean
|
|
||||||
isLoggedIn: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useDocumentStore = defineStore({
|
|
||||||
id: 'documents',
|
|
||||||
state: () => ({
|
|
||||||
document: shallowRef<Doc[]>([]),
|
|
||||||
selected: new Set<FUID>(),
|
|
||||||
fileExplorer: null as any,
|
|
||||||
error: '' as string,
|
|
||||||
connected: false,
|
|
||||||
server: {} as Record<string, any>,
|
|
||||||
user: {
|
|
||||||
username: '',
|
|
||||||
privileged: false,
|
|
||||||
isLoggedIn: false,
|
|
||||||
isOpenLoginModal: false
|
|
||||||
} as User
|
|
||||||
}),
|
|
||||||
actions: {
|
|
||||||
updateRoot(root: FileEntry[]) {
|
|
||||||
const docs = []
|
|
||||||
let loc = [] as string[]
|
|
||||||
for (const [level, name, key, mtime, size, isfile] of root) {
|
|
||||||
loc = loc.slice(0, level - 1)
|
|
||||||
docs.push(new Doc({
|
|
||||||
name,
|
|
||||||
loc: level ? loc.join('/') : '/',
|
|
||||||
key,
|
|
||||||
size,
|
|
||||||
mtime,
|
|
||||||
dir: !isfile,
|
|
||||||
}))
|
|
||||||
loc.push(name)
|
|
||||||
}
|
|
||||||
this.document = docs
|
|
||||||
},
|
|
||||||
login(username: string, privileged: boolean) {
|
|
||||||
this.user.username = username
|
|
||||||
this.user.privileged = privileged
|
|
||||||
this.user.isLoggedIn = true
|
|
||||||
this.user.isOpenLoginModal = false
|
|
||||||
if (!this.connected) watchConnect()
|
|
||||||
},
|
|
||||||
loginDialog() {
|
|
||||||
this.user.isOpenLoginModal = true
|
|
||||||
},
|
|
||||||
async logout() {
|
|
||||||
console.log("Logout")
|
|
||||||
await logoutUser()
|
|
||||||
this.$reset()
|
|
||||||
localStorage.clear()
|
|
||||||
history.go() // Reload page
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getters: {
|
|
||||||
isUserLogged(): boolean {
|
|
||||||
return this.user.isLoggedIn
|
|
||||||
},
|
|
||||||
recentDocuments(): Doc[] {
|
|
||||||
const ret = [...this.document]
|
|
||||||
ret.sort((a, b) => b.mtime - a.mtime)
|
|
||||||
return ret
|
|
||||||
},
|
|
||||||
largeDocuments(): Doc[] {
|
|
||||||
const ret = [...this.document]
|
|
||||||
ret.sort((a, b) => b.size - a.size)
|
|
||||||
return ret
|
|
||||||
},
|
|
||||||
selectedFiles(): SelectedItems {
|
|
||||||
const selected = this.selected
|
|
||||||
const found = new Set<FUID>()
|
|
||||||
const ret: SelectedItems = {
|
|
||||||
missing: new Set(),
|
|
||||||
docs: {},
|
|
||||||
keys: [],
|
|
||||||
recursive: [],
|
|
||||||
}
|
|
||||||
for (const doc of this.document) {
|
|
||||||
if (selected.has(doc.key)) {
|
|
||||||
found.add(doc.key)
|
|
||||||
ret.keys.push(doc.key)
|
|
||||||
ret.docs[doc.key] = doc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// What did we not select?
|
|
||||||
for (const key of selected) if (!found.has(key)) ret.missing.add(key)
|
|
||||||
// Build a flat list including contents recursively
|
|
||||||
const relnames = new Set<string>()
|
|
||||||
function add(rel: string, full: string, doc: Doc) {
|
|
||||||
if (!doc.dir && relnames.has(rel)) throw Error(`Multiple selections conflict for: ${rel}`)
|
|
||||||
relnames.add(rel)
|
|
||||||
ret.recursive.push([rel, full, doc])
|
|
||||||
}
|
|
||||||
for (const key of ret.keys) {
|
|
||||||
const base = ret.docs[key]
|
|
||||||
const basepath = base.loc ? `${base.loc}/${base.name}` : base.name
|
|
||||||
const nremove = base.loc.length
|
|
||||||
add(base.name, basepath, base)
|
|
||||||
for (const doc of this.document) {
|
|
||||||
if (doc.loc === basepath || doc.loc.startsWith(basepath) && doc.loc[basepath.length] === '/') {
|
|
||||||
const full = doc.loc ? `${doc.loc}/${doc.name}` : doc.name
|
|
||||||
const rel = full.slice(nremove)
|
|
||||||
add(rel, full, doc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Sort by rel (name stored as on download)
|
|
||||||
ret.recursive.sort((a, b) => collator.compare(a[0], b[0]))
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -10,11 +10,11 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { watchEffect, ref, computed } from 'vue'
|
import { watchEffect, ref, computed } from 'vue'
|
||||||
import { useDocumentStore } from '@/stores/documents'
|
import { useMainStore } from '@/stores/main'
|
||||||
import Router from '@/router/index'
|
import Router from '@/router/index'
|
||||||
import { needleFormat, localeIncludes, collator } from '@/utils';
|
import { needleFormat, localeIncludes, collator } from '@/utils';
|
||||||
|
|
||||||
const documentStore = useDocumentStore()
|
const store = useMainStore()
|
||||||
const fileExplorer = ref()
|
const fileExplorer = ref()
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
path: Array<string>
|
path: Array<string>
|
||||||
|
@ -24,12 +24,12 @@ const documents = computed(() => {
|
||||||
const loc = props.path.join('/')
|
const loc = props.path.join('/')
|
||||||
const query = props.query
|
const query = props.query
|
||||||
// List the current location
|
// List the current location
|
||||||
if (!query) return documentStore.document.filter(doc => doc.loc === loc)
|
if (!query) return store.document.filter(doc => doc.loc === loc)
|
||||||
// Find up to 100 newest documents that match the search
|
// Find up to 100 newest documents that match the search
|
||||||
const needle = needleFormat(query)
|
const needle = needleFormat(query)
|
||||||
let limit = 100
|
let limit = 100
|
||||||
let docs = []
|
let docs = []
|
||||||
for (const doc of documentStore.recentDocuments) {
|
for (const doc of store.recentDocuments) {
|
||||||
if (localeIncludes(doc.haystack, needle)) {
|
if (localeIncludes(doc.haystack, needle)) {
|
||||||
docs.push(doc)
|
docs.push(doc)
|
||||||
if (--limit === 0) break
|
if (--limit === 0) break
|
||||||
|
@ -53,6 +53,7 @@ const documents = computed(() => {
|
||||||
})
|
})
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
documentStore.fileExplorer = fileExplorer.value
|
store.fileExplorer = fileExplorer.value
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@/stores/main
|
||||||
|
|
Loading…
Reference in New Issue
Block a user