Compare commits
	
		
			3 Commits
		
	
	
		
			v1.2.1
			...
			19a5c4ad8a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 19a5c4ad8a | ||
|   | 3d3b078e60 | ||
|   | d4e91ea9a6 | 
| @@ -17,7 +17,7 @@ | |||||||
|         <td class="name"> |         <td class="name"> | ||||||
|           <FileRenameInput :doc="editing" :rename="mkdir" :exit="() => {editing = null}" /> |           <FileRenameInput :doc="editing" :rename="mkdir" :exit="() => {editing = null}" /> | ||||||
|         </td> |         </td> | ||||||
|         <FileModified :doc=editing /> |         <FileModified :doc=editing :key=nowkey /> | ||||||
|         <FileSize :doc=editing /> |         <FileSize :doc=editing /> | ||||||
|         <td class="menu"></td> |         <td class="menu"></td> | ||||||
|       </tr> |       </tr> | ||||||
| @@ -61,7 +61,7 @@ | |||||||
|               <button v-if="cursor == doc" class="rename-button" @click="() => (editing = doc)">🖊️</button> |               <button v-if="cursor == doc" class="rename-button" @click="() => (editing = doc)">🖊️</button> | ||||||
|             </template> |             </template> | ||||||
|           </td> |           </td> | ||||||
|           <FileModified :doc=doc /> |           <FileModified :doc=doc :key=nowkey /> | ||||||
|           <FileSize :doc=doc /> |           <FileSize :doc=doc /> | ||||||
|           <td class="menu"> |           <td class="menu"> | ||||||
|             <button tabindex="-1" @click.stop="contextMenu($event, doc)">⋮</button> |             <button tabindex="-1" @click.stop="contextMenu($event, doc)">⋮</button> | ||||||
| @@ -79,28 +79,28 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { ref, computed, watchEffect, onMounted, onUnmounted } from 'vue' | import { ref, computed, watchEffect, shallowRef, onMounted, onUnmounted } from 'vue' | ||||||
| import { useDocumentStore } from '@/stores/documents' | import { useDocumentStore } from '@/stores/documents' | ||||||
| import type { Document } 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' | ||||||
| import { collator, formatSize, formatUnixDate } from '@/utils' | import { collator, formatSize } from '@/utils' | ||||||
| import { useRouter } from 'vue-router' | import { useRouter } from 'vue-router' | ||||||
|  |  | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
|   path: Array<string> |   path: Array<string> | ||||||
|   documents: Document[] |   documents: Doc[] | ||||||
| }>() | }>() | ||||||
| const documentStore = useDocumentStore() | const documentStore = useDocumentStore() | ||||||
| const router = useRouter() | const router = useRouter() | ||||||
| const url_for = (doc: Document) => { | 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 | ||||||
|   return doc.dir ? `#/${p}/` : `/files/${p}` |   return doc.dir ? `#/${p}/` : `/files/${p}` | ||||||
| } | } | ||||||
| const cursor = ref<Document | null>(null) | const cursor = shallowRef<Doc | null>(null) | ||||||
| // File rename | // File rename | ||||||
| const editing = ref<Document | null>(null) | const editing = shallowRef<Doc | null>(null) | ||||||
| const rename = (doc: Document, newName: string) => { | const rename = (doc: Doc, newName: string) => { | ||||||
|   const oldName = doc.name |   const oldName = doc.name | ||||||
|   const control = connect(controlUrl, { |   const control = connect(controlUrl, { | ||||||
|     message(ev: MessageEvent) { |     message(ev: MessageEvent) { | ||||||
| @@ -124,7 +124,7 @@ const rename = (doc: Document, newName: string) => { | |||||||
|   } |   } | ||||||
|   doc.name = newName // We should get an update from watch but this is quicker |   doc.name = newName // We should get an update from watch but this is quicker | ||||||
| } | } | ||||||
| const sortedDocuments = computed(() => sorted(props.documents as Document[])) | const sortedDocuments = computed(() => sorted(props.documents)) | ||||||
| const showFolderBreadcrumb = (i: number) => { | const showFolderBreadcrumb = (i: number) => { | ||||||
|   const docs = sortedDocuments.value |   const docs = sortedDocuments.value | ||||||
|   const docloc = docs[i].loc |   const docloc = docs[i].loc | ||||||
| @@ -132,19 +132,15 @@ const showFolderBreadcrumb = (i: number) => { | |||||||
| } | } | ||||||
| defineExpose({ | defineExpose({ | ||||||
|   newFolder() { |   newFolder() { | ||||||
|     const now = Date.now() / 1000 |     const now = Math.floor(Date.now() / 1000) | ||||||
|     editing.value = { |     editing.value = new Doc({ | ||||||
|       loc: loc.value, |       loc: loc.value, | ||||||
|       key: 'new', |       key: 'new', | ||||||
|       name: 'New Folder', |       name: 'New Folder', | ||||||
|       dir: true, |       dir: true, | ||||||
|       mtime: now, |       mtime: now, | ||||||
|       size: 0, |       size: 0, | ||||||
|       sizedisp: formatSize(0), |     }) | ||||||
|       modified: formatUnixDate(now), |  | ||||||
|       haystack: '', |  | ||||||
|     } |  | ||||||
|     console.log("New") |  | ||||||
|   }, |   }, | ||||||
|   toggleSelectAll() { |   toggleSelectAll() { | ||||||
|     console.log('Select') |     console.log('Select') | ||||||
| @@ -229,14 +225,14 @@ watchEffect(() => { | |||||||
|     focusBreadcrumb() |     focusBreadcrumb() | ||||||
|   } |   } | ||||||
| }) | }) | ||||||
| // Update human-readable x seconds ago messages from mtimes | let nowkey = ref(0) | ||||||
| let modifiedTimer: any = null | let modifiedTimer: any = null | ||||||
| const updateModified = () => { | const updateModified = () => { | ||||||
|   for (const doc of props.documents) doc.modified = formatUnixDate(doc.mtime) |   nowkey.value = Math.floor(Date.now() / 1000) | ||||||
| } | } | ||||||
| onMounted(() => { updateModified(); modifiedTimer = setInterval(updateModified, 1000) }) | onMounted(() => { updateModified(); modifiedTimer = setInterval(updateModified, 1000) }) | ||||||
| onUnmounted(() => { clearInterval(modifiedTimer) }) | onUnmounted(() => { clearInterval(modifiedTimer) }) | ||||||
| const mkdir = (doc: Document, name: string) => { | const mkdir = (doc: Doc, name: string) => { | ||||||
|   const control = connect(controlUrl, { |   const control = connect(controlUrl, { | ||||||
|     open() { |     open() { | ||||||
|       control.send( |       control.send( | ||||||
| @@ -257,7 +253,9 @@ const mkdir = (doc: Document, name: string) => { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   }) |   }) | ||||||
|   doc.name = name // We should get an update from watch but this is quicker |   // We should get an update from watch but this is quicker | ||||||
|  |   doc.name = name | ||||||
|  |   doc.key = crypto.randomUUID() | ||||||
| } | } | ||||||
|  |  | ||||||
| // Column sort | // Column sort | ||||||
| @@ -266,11 +264,11 @@ const toggleSort = (name: string) => { | |||||||
| } | } | ||||||
| const sort = ref<string>('') | const sort = ref<string>('') | ||||||
| const sortCompare = { | const sortCompare = { | ||||||
|   name: (a: Document, b: Document) => collator.compare(a.name, b.name), |   name: (a: Doc, b: Doc) => collator.compare(a.name, b.name), | ||||||
|   modified: (a: Document, b: Document) => b.mtime - a.mtime, |   modified: (a: Doc, b: Doc) => b.mtime - a.mtime, | ||||||
|   size: (a: Document, b: Document) => b.size - a.size |   size: (a: Doc, b: Doc) => b.size - a.size | ||||||
| } | } | ||||||
| const sorted = (documents: Document[]) => { | const sorted = (documents: Doc[]) => { | ||||||
|   const cmp = sortCompare[sort.value as keyof typeof sortCompare] |   const cmp = sortCompare[sort.value as keyof typeof sortCompare] | ||||||
|   const sorted = [...documents] |   const sorted = [...documents] | ||||||
|   if (cmp) sorted.sort(cmp) |   if (cmp) sorted.sort(cmp) | ||||||
| @@ -280,7 +278,7 @@ const selectionIndeterminate = computed({ | |||||||
|   get: () => { |   get: () => { | ||||||
|     return ( |     return ( | ||||||
|       props.documents.length > 0 && |       props.documents.length > 0 && | ||||||
|       props.documents.some((doc: Document) => documentStore.selected.has(doc.key)) && |       props.documents.some((doc: Doc) => documentStore.selected.has(doc.key)) && | ||||||
|       !allSelected.value |       !allSelected.value | ||||||
|     ) |     ) | ||||||
|   }, |   }, | ||||||
| @@ -291,7 +289,7 @@ const allSelected = computed({ | |||||||
|   get: () => { |   get: () => { | ||||||
|     return ( |     return ( | ||||||
|       props.documents.length > 0 && |       props.documents.length > 0 && | ||||||
|       props.documents.every((doc: Document) => documentStore.selected.has(doc.key)) |       props.documents.every((doc: Doc) => documentStore.selected.has(doc.key)) | ||||||
|     ) |     ) | ||||||
|   }, |   }, | ||||||
|   set: (value: boolean) => { |   set: (value: boolean) => { | ||||||
| @@ -308,7 +306,7 @@ const allSelected = computed({ | |||||||
|  |  | ||||||
| const loc = computed(() => props.path.join('/')) | const loc = computed(() => props.path.join('/')) | ||||||
|  |  | ||||||
| const contextMenu = (ev: Event, doc: Document) => { | const contextMenu = (ev: Event, doc: Doc) => { | ||||||
|   cursor.value = doc |   cursor.value = doc | ||||||
|   console.log('Context menu', ev, doc) |   console.log('Context menu', ev, doc) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import type { Document } from '@/repositories/Document' | import { Doc } from '@/repositories/Document' | ||||||
| import { computed } from 'vue' | import { computed } from 'vue' | ||||||
|  |  | ||||||
| const datetime = computed(() => | const datetime = computed(() => | ||||||
| @@ -17,6 +17,6 @@ const tooltip = computed(() => | |||||||
| ) | ) | ||||||
|  |  | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
|     doc: Document |     doc: Doc | ||||||
| }>() | }>() | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import type { Document } from '@/repositories/Document' | import { Doc } from '@/repositories/Document' | ||||||
| import { ref, onMounted, nextTick } from 'vue' | import { ref, onMounted, nextTick } from 'vue' | ||||||
|  |  | ||||||
| const input = ref<HTMLInputElement | null>(null) | const input = ref<HTMLInputElement | null>(null) | ||||||
| @@ -28,8 +28,8 @@ onMounted(() => { | |||||||
| }) | }) | ||||||
|  |  | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
|   doc: Document |   doc: Doc | ||||||
|   rename: (doc: Document, newName: string) => void |   rename: (doc: Doc, newName: string) => void | ||||||
|   exit: () => void |   exit: () => void | ||||||
| }>() | }>() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import type { Document } from '@/repositories/Document' | import { Doc } from '@/repositories/Document' | ||||||
| import { computed } from 'vue' | import { computed } from 'vue' | ||||||
|  |  | ||||||
| const sizeClass = computed(() => { | const sizeClass = computed(() => { | ||||||
| @@ -12,7 +12,7 @@ const sizeClass = computed(() => { | |||||||
| }) | }) | ||||||
|  |  | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
|     doc: Document |     doc: Doc | ||||||
| }>() | }>() | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,17 +1,34 @@ | |||||||
|  | import { formatSize, formatUnixDate, haystackFormat } from "@/utils" | ||||||
|  |  | ||||||
| export type FUID = string | export type FUID = string | ||||||
|  |  | ||||||
| export type Document = { | export type DocProps = { | ||||||
|   loc: string |   loc: string | ||||||
|   name: string |   name: string | ||||||
|   key: FUID |   key: FUID | ||||||
|   size: number |   size: number | ||||||
|   sizedisp: string |  | ||||||
|   mtime: number |   mtime: number | ||||||
|   modified: string |  | ||||||
|   haystack: string |  | ||||||
|   dir: boolean |   dir: boolean | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export class Doc { | ||||||
|  |   private _name: string = "" | ||||||
|  |   public loc: string = "" | ||||||
|  |   public key: FUID = "" | ||||||
|  |   public size: number = 0 | ||||||
|  |   public mtime: number = 0 | ||||||
|  |   public haystack: string = "" | ||||||
|  |   public dir: boolean = false | ||||||
|  |  | ||||||
|  |   constructor(props: Partial<DocProps> = {}) { Object.assign(this, props) } | ||||||
|  |   get name() { return this._name } | ||||||
|  |   set name(name: string) { | ||||||
|  |     this._name = name | ||||||
|  |     this.haystack = haystackFormat(name) | ||||||
|  |   } | ||||||
|  |   get sizedisp(): string { return formatSize(this.size) } | ||||||
|  |   get modified(): string { return formatUnixDate(this.mtime) } | ||||||
|  | } | ||||||
| export type errorEvent = { | export type errorEvent = { | ||||||
|   error: { |   error: { | ||||||
|     code: number |     code: number | ||||||
| @@ -36,7 +53,7 @@ export type UpdateEntry = ['k', number] | ['d', number] | ['i', Array<FileEntry> | |||||||
| // Helper structure for selections | // Helper structure for selections | ||||||
| export interface SelectedItems { | export interface SelectedItems { | ||||||
|   keys: FUID[] |   keys: FUID[] | ||||||
|   docs: Record<FUID, Document> |   docs: Record<FUID, Doc> | ||||||
|   recursive: Array<[string, string, Document]> |   recursive: Array<[string, string, Doc]> | ||||||
|   missing: Set<FUID> |   missing: Set<FUID> | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,14 +1,11 @@ | |||||||
| import type { Document, FileEntry, FUID, SelectedItems } from '@/repositories/Document' | import type { FileEntry, FUID, SelectedItems } from '@/repositories/Document' | ||||||
| import { formatSize, formatUnixDate, haystackFormat } from '@/utils' | import { Doc } from '@/repositories/Document' | ||||||
| import { defineStore } from 'pinia' | import { defineStore } from 'pinia' | ||||||
| import { collator } from '@/utils' | import { collator } from '@/utils' | ||||||
| import { logoutUser } from '@/repositories/User' | import { logoutUser } from '@/repositories/User' | ||||||
| import { watchConnect } from '@/repositories/WS' | import { watchConnect } from '@/repositories/WS' | ||||||
|  | import { shallowRef } from 'vue' | ||||||
|  |  | ||||||
| type FileData = { id: string; mtime: number; size: number; dir: DirectoryData } |  | ||||||
| type DirectoryData = { |  | ||||||
|   [filename: string]: FileData |  | ||||||
| } |  | ||||||
| type User = { | type User = { | ||||||
|   username: string |   username: string | ||||||
|   privileged: boolean |   privileged: boolean | ||||||
| @@ -19,7 +16,7 @@ type User = { | |||||||
| export const useDocumentStore = defineStore({ | export const useDocumentStore = defineStore({ | ||||||
|   id: 'documents', |   id: 'documents', | ||||||
|   state: () => ({ |   state: () => ({ | ||||||
|     document: [] as Document[], |     document: shallowRef<Doc[]>([]), | ||||||
|     selected: new Set<FUID>(), |     selected: new Set<FUID>(), | ||||||
|     fileExplorer: null as any, |     fileExplorer: null as any, | ||||||
|     error: '' as string, |     error: '' as string, | ||||||
| @@ -38,20 +35,17 @@ export const useDocumentStore = defineStore({ | |||||||
|       let loc = [] as string[] |       let loc = [] as string[] | ||||||
|       for (const [level, name, key, mtime, size, isfile] of root) { |       for (const [level, name, key, mtime, size, isfile] of root) { | ||||||
|         loc = loc.slice(0, level - 1) |         loc = loc.slice(0, level - 1) | ||||||
|         docs.push({ |         docs.push(new Doc({ | ||||||
|           name, |           name, | ||||||
|           loc: level ? loc.join('/') : '/', |           loc: level ? loc.join('/') : '/', | ||||||
|           key, |           key, | ||||||
|           size, |           size, | ||||||
|           sizedisp: formatSize(size), |  | ||||||
|           mtime, |           mtime, | ||||||
|           modified: formatUnixDate(mtime), |  | ||||||
|           haystack: haystackFormat(name), |  | ||||||
|           dir: !isfile, |           dir: !isfile, | ||||||
|         }) |         })) | ||||||
|         loc.push(name) |         loc.push(name) | ||||||
|       } |       } | ||||||
|       this.document = docs as Document[] |       this.document = docs | ||||||
|     }, |     }, | ||||||
|     login(username: string, privileged: boolean) { |     login(username: string, privileged: boolean) { | ||||||
|       this.user.username = username |       this.user.username = username | ||||||
| @@ -75,12 +69,12 @@ export const useDocumentStore = defineStore({ | |||||||
|     isUserLogged(): boolean { |     isUserLogged(): boolean { | ||||||
|       return this.user.isLoggedIn |       return this.user.isLoggedIn | ||||||
|     }, |     }, | ||||||
|     recentDocuments(): Document[] { |     recentDocuments(): Doc[] { | ||||||
|       const ret = [...this.document] |       const ret = [...this.document] | ||||||
|       ret.sort((a, b) => b.mtime - a.mtime) |       ret.sort((a, b) => b.mtime - a.mtime) | ||||||
|       return ret |       return ret | ||||||
|     }, |     }, | ||||||
|     largeDocuments(): Document[] { |     largeDocuments(): Doc[] { | ||||||
|       const ret = [...this.document] |       const ret = [...this.document] | ||||||
|       ret.sort((a, b) => b.size - a.size) |       ret.sort((a, b) => b.size - a.size) | ||||||
|       return ret |       return ret | ||||||
| @@ -105,7 +99,7 @@ export const useDocumentStore = defineStore({ | |||||||
|       for (const key of selected) if (!found.has(key)) ret.missing.add(key) |       for (const key of selected) if (!found.has(key)) ret.missing.add(key) | ||||||
|       // Build a flat list including contents recursively |       // Build a flat list including contents recursively | ||||||
|       const relnames = new Set<string>() |       const relnames = new Set<string>() | ||||||
|       function add(rel: string, full: string, doc: Document) { |       function add(rel: string, full: string, doc: Doc) { | ||||||
|         if (!doc.dir && relnames.has(rel)) throw Error(`Multiple selections conflict for: ${rel}`) |         if (!doc.dir && relnames.has(rel)) throw Error(`Multiple selections conflict for: ${rel}`) | ||||||
|         relnames.add(rel) |         relnames.add(rel) | ||||||
|         ret.recursive.push([rel, full, doc]) |         ret.recursive.push([rel, full, doc]) | ||||||
|   | |||||||
| @@ -86,7 +86,7 @@ export function haystackFormat(str: string) { | |||||||
| // Preformat search string for faster search | // Preformat search string for faster search | ||||||
| export function needleFormat(query: string) { | export function needleFormat(query: string) { | ||||||
|   const based = query.normalize('NFKD').replace(/[\u0300-\u036f]/g, '').toLowerCase() |   const based = query.normalize('NFKD').replace(/[\u0300-\u036f]/g, '').toLowerCase() | ||||||
|   return {based, words: based.split(/\W+/)} |   return {based, words: based.split(/\s+/)} | ||||||
| } | } | ||||||
|  |  | ||||||
| // Test if haystack includes needle | // Test if haystack includes needle | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user