attempt to use separate folder for docs and meetings

This commit is contained in:
Mariano Gabriel
2026-07-06 01:18:17 -03:00
parent bb84558526
commit 31ad8e5928
10 changed files with 261 additions and 62 deletions

View File

@@ -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)
}