attempt to use separate folder for docs and meetings
This commit is contained in:
@@ -8,8 +8,10 @@
|
||||
import { reactive, computed } from 'vue'
|
||||
|
||||
export interface FileNode {
|
||||
path: string // composite: "<collectionId>/<relpath>"
|
||||
path: string // composite: "<collectionId>/<relpath>" (API resolution)
|
||||
collection?: string
|
||||
root?: string // shared source path (groups docs + meetings of a source)
|
||||
rel?: string // path within the source (for the merged tree)
|
||||
name: string
|
||||
ext: string
|
||||
family: string
|
||||
@@ -28,6 +30,19 @@ export interface CollectionInfo {
|
||||
name: string
|
||||
available: boolean
|
||||
count: number
|
||||
root: string | null
|
||||
only: string
|
||||
meetings: number
|
||||
}
|
||||
|
||||
/** A source = one downloaded drive, possibly split into a docs collection and a
|
||||
* meetings collection that share the same `root`. */
|
||||
export interface SourceInfo {
|
||||
root: string
|
||||
label: string
|
||||
collectionIds: string[]
|
||||
docs: number
|
||||
meetings: number
|
||||
}
|
||||
|
||||
export interface Detail {
|
||||
@@ -152,11 +167,31 @@ export function toggleFolder(path: string): void {
|
||||
else store.collapsed.add(path)
|
||||
}
|
||||
|
||||
/** Build a nested folder tree from flat posix paths, folders before files. */
|
||||
/** Unique display label per source root (top-level tree group). */
|
||||
function sourceLabels(files: FileNode[]): Map<string, string> {
|
||||
const roots = [...new Set(files.map((f) => f.root).filter(Boolean))] as string[]
|
||||
const labels = new Map<string, string>()
|
||||
const seen = new Set<string>()
|
||||
for (const r of roots.sort()) {
|
||||
const base = r.split('/').filter(Boolean).pop() || r
|
||||
let label = base, n = 1
|
||||
while (seen.has(label)) { n++; label = `${base}-${n}` }
|
||||
seen.add(label)
|
||||
labels.set(r, label)
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
/** Build the tree grouped by source root — so a source's docs + meetings
|
||||
* interleave under one folder — while each file keeps its composite `node.path`
|
||||
* for API calls. Folder items are keyed by their display path. */
|
||||
function buildTree(files: FileNode[]): TreeItem[] {
|
||||
const labels = sourceLabels(files)
|
||||
const root: TreeItem = { name: '', path: '', dir: true, children: [] }
|
||||
for (const f of files) {
|
||||
const parts = f.path.split('/')
|
||||
const src = (f.root && labels.get(f.root)) || f.collection || 'source'
|
||||
const display = `${src}/${f.rel ?? f.path}`
|
||||
const parts = display.split('/')
|
||||
let cur = root
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
const seg = parts[i]
|
||||
@@ -168,7 +203,7 @@ function buildTree(files: FileNode[]): TreeItem[] {
|
||||
}
|
||||
cur = next
|
||||
}
|
||||
cur.children!.push({ name: f.name, path: f.path, dir: false, node: f })
|
||||
cur.children!.push({ name: f.name, path: display, dir: false, node: f })
|
||||
}
|
||||
const sort = (items: TreeItem[]): TreeItem[] => {
|
||||
items.sort((a, b) =>
|
||||
@@ -196,6 +231,21 @@ export async function loadCollections(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/** Re-discover sources (picks up newly-scanned collections) without a page
|
||||
* reload; preserves current toggles and turns on any brand-new source. */
|
||||
export async function refreshSources(): Promise<void> {
|
||||
const prevActive = new Set(store.activeCollections)
|
||||
const known = new Set(store.collections.map((c) => c.id))
|
||||
await loadCollections() // sets active = all available
|
||||
store.activeCollections = new Set(
|
||||
store.collections
|
||||
.filter((c) => c.available && (prevActive.has(c.id) || !known.has(c.id)))
|
||||
.map((c) => c.id),
|
||||
)
|
||||
await loadTree()
|
||||
if (store.query) runSearch(store.query)
|
||||
}
|
||||
|
||||
export async function loadTree(): Promise<void> {
|
||||
store.loading = true
|
||||
store.error = ''
|
||||
@@ -214,10 +264,42 @@ export async function loadTree(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/** Toggle a collection on/off and re-merge the tree (and re-run any search). */
|
||||
export async function toggleCollection(id: string): Promise<void> {
|
||||
if (store.activeCollections.has(id)) store.activeCollections.delete(id)
|
||||
else store.activeCollections.add(id)
|
||||
/** Collections grouped by shared root — the selector unit. */
|
||||
export const sources = computed<SourceInfo[]>(() => {
|
||||
const byRoot = new Map<string, SourceInfo>()
|
||||
const seen = new Set<string>()
|
||||
for (const c of store.collections) {
|
||||
if (!c.root) continue
|
||||
let s = byRoot.get(c.root)
|
||||
if (!s) {
|
||||
const base = c.root.split('/').filter(Boolean).pop() || c.root
|
||||
let label = base, n = 1
|
||||
while (seen.has(label)) { n++; label = `${base}-${n}` }
|
||||
seen.add(label)
|
||||
s = { root: c.root, label, collectionIds: [], docs: 0, meetings: 0 }
|
||||
byRoot.set(c.root, s)
|
||||
}
|
||||
s.collectionIds.push(c.id)
|
||||
s.docs += c.count - c.meetings
|
||||
s.meetings += c.meetings
|
||||
}
|
||||
return [...byRoot.values()]
|
||||
})
|
||||
|
||||
export function sourceActive(root: string): boolean {
|
||||
const s = sources.value.find((x) => x.root === root)
|
||||
return !!s && s.collectionIds.some((id) => store.activeCollections.has(id))
|
||||
}
|
||||
|
||||
/** Toggle a whole source (its docs + meetings collections) and re-merge. */
|
||||
export async function toggleSource(root: string): Promise<void> {
|
||||
const s = sources.value.find((x) => x.root === root)
|
||||
if (!s) return
|
||||
const allOn = s.collectionIds.every((id) => store.activeCollections.has(id))
|
||||
for (const id of s.collectionIds) {
|
||||
if (allOn) store.activeCollections.delete(id)
|
||||
else store.activeCollections.add(id)
|
||||
}
|
||||
await loadTree()
|
||||
if (store.query) runSearch(store.query)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user