Compare commits
	
		
			3 Commits
		
	
	
		
			v0.4.0
			...
			19a5c4ad8a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 19a5c4ad8a | ||
|   | 3d3b078e60 | ||
|   | d4e91ea9a6 | 
| @@ -17,7 +17,7 @@ | ||||
|         <td class="name"> | ||||
|           <FileRenameInput :doc="editing" :rename="mkdir" :exit="() => {editing = null}" /> | ||||
|         </td> | ||||
|         <FileModified :doc=editing /> | ||||
|         <FileModified :doc=editing :key=nowkey /> | ||||
|         <FileSize :doc=editing /> | ||||
|         <td class="menu"></td> | ||||
|       </tr> | ||||
| @@ -61,7 +61,7 @@ | ||||
|               <button v-if="cursor == doc" class="rename-button" @click="() => (editing = doc)">🖊️</button> | ||||
|             </template> | ||||
|           </td> | ||||
|           <FileModified :doc=doc /> | ||||
|           <FileModified :doc=doc :key=nowkey /> | ||||
|           <FileSize :doc=doc /> | ||||
|           <td class="menu"> | ||||
|             <button tabindex="-1" @click.stop="contextMenu($event, doc)">⋮</button> | ||||
| @@ -79,28 +79,28 @@ | ||||
| </template> | ||||
|  | ||||
| <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 type { Document } from '@/repositories/Document' | ||||
| import { Doc } from '@/repositories/Document' | ||||
| import FileRenameInput from './FileRenameInput.vue' | ||||
| import { connect, controlUrl } from '@/repositories/WS' | ||||
| import { collator, formatSize, formatUnixDate } from '@/utils' | ||||
| import { collator, formatSize } from '@/utils' | ||||
| import { useRouter } from 'vue-router' | ||||
|  | ||||
| const props = defineProps<{ | ||||
|   path: Array<string> | ||||
|   documents: Document[] | ||||
|   documents: Doc[] | ||||
| }>() | ||||
| const documentStore = useDocumentStore() | ||||
| const router = useRouter() | ||||
| const url_for = (doc: Document) => { | ||||
| const url_for = (doc: Doc) => { | ||||
|   const p = doc.loc ? `${doc.loc}/${doc.name}` : doc.name | ||||
|   return doc.dir ? `#/${p}/` : `/files/${p}` | ||||
| } | ||||
| const cursor = ref<Document | null>(null) | ||||
| const cursor = shallowRef<Doc | null>(null) | ||||
| // File rename | ||||
| const editing = ref<Document | null>(null) | ||||
| const rename = (doc: Document, newName: string) => { | ||||
| const editing = shallowRef<Doc | null>(null) | ||||
| const rename = (doc: Doc, newName: string) => { | ||||
|   const oldName = doc.name | ||||
|   const control = connect(controlUrl, { | ||||
|     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 | ||||
| } | ||||
| const sortedDocuments = computed(() => sorted(props.documents as Document[])) | ||||
| const sortedDocuments = computed(() => sorted(props.documents)) | ||||
| const showFolderBreadcrumb = (i: number) => { | ||||
|   const docs = sortedDocuments.value | ||||
|   const docloc = docs[i].loc | ||||
| @@ -132,19 +132,15 @@ const showFolderBreadcrumb = (i: number) => { | ||||
| } | ||||
| defineExpose({ | ||||
|   newFolder() { | ||||
|     const now = Date.now() / 1000 | ||||
|     editing.value = { | ||||
|     const now = Math.floor(Date.now() / 1000) | ||||
|     editing.value = new Doc({ | ||||
|       loc: loc.value, | ||||
|       key: 'new', | ||||
|       name: 'New Folder', | ||||
|       dir: true, | ||||
|       mtime: now, | ||||
|       size: 0, | ||||
|       sizedisp: formatSize(0), | ||||
|       modified: formatUnixDate(now), | ||||
|       haystack: '', | ||||
|     } | ||||
|     console.log("New") | ||||
|     }) | ||||
|   }, | ||||
|   toggleSelectAll() { | ||||
|     console.log('Select') | ||||
| @@ -229,14 +225,14 @@ watchEffect(() => { | ||||
|     focusBreadcrumb() | ||||
|   } | ||||
| }) | ||||
| // Update human-readable x seconds ago messages from mtimes | ||||
| let nowkey = ref(0) | ||||
| let modifiedTimer: any = null | ||||
| 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) }) | ||||
| onUnmounted(() => { clearInterval(modifiedTimer) }) | ||||
| const mkdir = (doc: Document, name: string) => { | ||||
| const mkdir = (doc: Doc, name: string) => { | ||||
|   const control = connect(controlUrl, { | ||||
|     open() { | ||||
|       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 | ||||
| @@ -266,11 +264,11 @@ const toggleSort = (name: string) => { | ||||
| } | ||||
| const sort = ref<string>('') | ||||
| const sortCompare = { | ||||
|   name: (a: Document, b: Document) => collator.compare(a.name, b.name), | ||||
|   modified: (a: Document, b: Document) => b.mtime - a.mtime, | ||||
|   size: (a: Document, b: Document) => b.size - a.size | ||||
|   name: (a: Doc, b: Doc) => collator.compare(a.name, b.name), | ||||
|   modified: (a: Doc, b: Doc) => b.mtime - a.mtime, | ||||
|   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 sorted = [...documents] | ||||
|   if (cmp) sorted.sort(cmp) | ||||
| @@ -280,7 +278,7 @@ const selectionIndeterminate = computed({ | ||||
|   get: () => { | ||||
|     return ( | ||||
|       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 | ||||
|     ) | ||||
|   }, | ||||
| @@ -291,7 +289,7 @@ const allSelected = computed({ | ||||
|   get: () => { | ||||
|     return ( | ||||
|       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) => { | ||||
| @@ -308,7 +306,7 @@ const allSelected = computed({ | ||||
|  | ||||
| const loc = computed(() => props.path.join('/')) | ||||
|  | ||||
| const contextMenu = (ev: Event, doc: Document) => { | ||||
| const contextMenu = (ev: Event, doc: Doc) => { | ||||
|   cursor.value = doc | ||||
|   console.log('Context menu', ev, doc) | ||||
| } | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import type { Document } from '@/repositories/Document' | ||||
| import { Doc } from '@/repositories/Document' | ||||
| import { computed } from 'vue' | ||||
|  | ||||
| const datetime = computed(() => | ||||
| @@ -17,6 +17,6 @@ const tooltip = computed(() => | ||||
| ) | ||||
|  | ||||
| const props = defineProps<{ | ||||
|     doc: Document | ||||
|     doc: Doc | ||||
| }>() | ||||
| </script> | ||||
|   | ||||
| @@ -12,7 +12,7 @@ | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import type { Document } from '@/repositories/Document' | ||||
| import { Doc } from '@/repositories/Document' | ||||
| import { ref, onMounted, nextTick } from 'vue' | ||||
|  | ||||
| const input = ref<HTMLInputElement | null>(null) | ||||
| @@ -28,8 +28,8 @@ onMounted(() => { | ||||
| }) | ||||
|  | ||||
| const props = defineProps<{ | ||||
|   doc: Document | ||||
|   rename: (doc: Document, newName: string) => void | ||||
|   doc: Doc | ||||
|   rename: (doc: Doc, newName: string) => void | ||||
|   exit: () => void | ||||
| }>() | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import type { Document } from '@/repositories/Document' | ||||
| import { Doc } from '@/repositories/Document' | ||||
| import { computed } from 'vue' | ||||
|  | ||||
| const sizeClass = computed(() => { | ||||
| @@ -12,7 +12,7 @@ const sizeClass = computed(() => { | ||||
| }) | ||||
|  | ||||
| const props = defineProps<{ | ||||
|     doc: Document | ||||
|     doc: Doc | ||||
| }>() | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -1,17 +1,34 @@ | ||||
| import { formatSize, formatUnixDate, haystackFormat } from "@/utils" | ||||
|  | ||||
| export type FUID = string | ||||
|  | ||||
| export type Document = { | ||||
| export type DocProps = { | ||||
|   loc: string | ||||
|   name: string | ||||
|   key: FUID | ||||
|   size: number | ||||
|   sizedisp: string | ||||
|   mtime: number | ||||
|   modified: string | ||||
|   haystack: string | ||||
|   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 = { | ||||
|   error: { | ||||
|     code: number | ||||
| @@ -36,7 +53,7 @@ export type UpdateEntry = ['k', number] | ['d', number] | ['i', Array<FileEntry> | ||||
| // Helper structure for selections | ||||
| export interface SelectedItems { | ||||
|   keys: FUID[] | ||||
|   docs: Record<FUID, Document> | ||||
|   recursive: Array<[string, string, Document]> | ||||
|   docs: Record<FUID, Doc> | ||||
|   recursive: Array<[string, string, Doc]> | ||||
|   missing: Set<FUID> | ||||
| } | ||||
|   | ||||
| @@ -1,14 +1,11 @@ | ||||
| import type { Document, FileEntry, FUID, SelectedItems } from '@/repositories/Document' | ||||
| import { formatSize, formatUnixDate, haystackFormat } from '@/utils' | ||||
| 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 FileData = { id: string; mtime: number; size: number; dir: DirectoryData } | ||||
| type DirectoryData = { | ||||
|   [filename: string]: FileData | ||||
| } | ||||
| type User = { | ||||
|   username: string | ||||
|   privileged: boolean | ||||
| @@ -19,7 +16,7 @@ type User = { | ||||
| export const useDocumentStore = defineStore({ | ||||
|   id: 'documents', | ||||
|   state: () => ({ | ||||
|     document: [] as Document[], | ||||
|     document: shallowRef<Doc[]>([]), | ||||
|     selected: new Set<FUID>(), | ||||
|     fileExplorer: null as any, | ||||
|     error: '' as string, | ||||
| @@ -38,20 +35,17 @@ export const useDocumentStore = defineStore({ | ||||
|       let loc = [] as string[] | ||||
|       for (const [level, name, key, mtime, size, isfile] of root) { | ||||
|         loc = loc.slice(0, level - 1) | ||||
|         docs.push({ | ||||
|         docs.push(new Doc({ | ||||
|           name, | ||||
|           loc: level ? loc.join('/') : '/', | ||||
|           key, | ||||
|           size, | ||||
|           sizedisp: formatSize(size), | ||||
|           mtime, | ||||
|           modified: formatUnixDate(mtime), | ||||
|           haystack: haystackFormat(name), | ||||
|           dir: !isfile, | ||||
|         }) | ||||
|         })) | ||||
|         loc.push(name) | ||||
|       } | ||||
|       this.document = docs as Document[] | ||||
|       this.document = docs | ||||
|     }, | ||||
|     login(username: string, privileged: boolean) { | ||||
|       this.user.username = username | ||||
| @@ -75,12 +69,12 @@ export const useDocumentStore = defineStore({ | ||||
|     isUserLogged(): boolean { | ||||
|       return this.user.isLoggedIn | ||||
|     }, | ||||
|     recentDocuments(): Document[] { | ||||
|     recentDocuments(): Doc[] { | ||||
|       const ret = [...this.document] | ||||
|       ret.sort((a, b) => b.mtime - a.mtime) | ||||
|       return ret | ||||
|     }, | ||||
|     largeDocuments(): Document[] { | ||||
|     largeDocuments(): Doc[] { | ||||
|       const ret = [...this.document] | ||||
|       ret.sort((a, b) => b.size - a.size) | ||||
|       return ret | ||||
| @@ -105,7 +99,7 @@ export const useDocumentStore = defineStore({ | ||||
|       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: Document) { | ||||
|       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]) | ||||
|   | ||||
| @@ -86,7 +86,7 @@ export function haystackFormat(str: string) { | ||||
| // Preformat search string for faster search | ||||
| export function needleFormat(query: string) { | ||||
|   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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user