import { t } from "../../../app/i18n.js"; import { createPanelStateBridge } from "../../../stores/panelStateBridge.js"; export function createBrowserNavigationController({ state, gridContainer, folderBreadcrumb, folderBreadcrumbController = null, customSelect, reloadGrid, clearSelection = null, onContextChanged = null, lifecycleSignal = null, }: Record = {}): Record { const { read, write } = createPanelStateBridge(state, [ "scope", "customRootId", "currentFolderRelativePath", ]); const folderBackStack: any[] = []; const normPath = (value: any) => String(value || "") .trim() .replaceAll("\\", "/"); const isWindowsDrive = (part: any) => /^[a-zA-Z]:$/.test(String(part || "").trim()); const currentFolderPath = () => normPath(read("currentFolderRelativePath", "") || ""); const pushUniqueHistory = (stack: any, value: any) => { const v = normPath(value); if (!Array.isArray(stack)) return; if (!v && v !== "") return; const top = stack.length ? normPath(stack[stack.length - 1]) : null; if (top === v) return; stack.push(v); }; const popDistinctHistory = (stack: any, current: any) => { const cur = normPath(current); if (!Array.isArray(stack)) return null; while (stack.length) { const candidate = normPath(stack.pop()); if (candidate !== cur) return candidate; } return null; }; const setFolderPath = (next: any) => { const v = normPath(next); write("currentFolderRelativePath", v); try { if (gridContainer?.dataset) gridContainer.dataset.mjrSubfolder = v; } catch (e) { console.debug?.(e); } }; const resolveFolderTargetPath = (asset: any) => { const rawSubfolder = normPath(asset?.subfolder || ""); if (rawSubfolder) return rawSubfolder; const rawFilepath = normPath(asset?.filepath || asset?.path || ""); if (rawFilepath) { const kind = String(asset?.kind || "").toLowerCase(); if (kind === "folder") return rawFilepath; // Defensive fallback: if a file path is emitted, browse its parent folder. const parent = parentFolderPath(rawFilepath); return parent || rawFilepath; } const name = normPath(asset?.filename || ""); if (!name) return currentFolderPath(); const base = currentFolderPath(); if (!base) return name; return `${base}/${name}`.replace(/\/{2,}/g, "/"); }; const parentFolderPath = (pathValue: any) => { const cur = normPath(pathValue); if (!cur) return ""; const parts = cur.split("/").filter(Boolean); if (!parts.length) return ""; if (parts.length === 1) return isWindowsDrive(parts[0]) ? `${parts[0]}/` : ""; parts.pop(); if (parts.length === 1 && isWindowsDrive(parts[0])) return `${parts[0]}/`; return parts.join("/"); }; const notifyContext = () => { try { onContextChanged?.(); } catch (e) { console.debug?.(e); } try { renderBreadcrumb(); } catch (e) { console.debug?.(e); } }; const resetGridScrollToTop = () => { try { if (gridContainer && typeof gridContainer.scrollTop === "number") { gridContainer.scrollTop = 0; } } catch (e) { console.debug?.(e); } try { const parent = gridContainer?.parentElement || null; if (parent && typeof parent.scrollTop === "number") { parent.scrollTop = 0; } } catch (e) { console.debug?.(e); } }; const navigateFolder = async (target: any, { pushHistory = true } = {}) => { const prev = currentFolderPath(); const next = normPath(target); if (prev === next) return; if (pushHistory) pushUniqueHistory(folderBackStack, prev); setFolderPath(next); resetGridScrollToTop(); notifyContext(); try { clearSelection?.(); } catch (e) { console.debug?.(e); } await reloadGrid?.(); }; const resetToBrowserRoot = async () => { write("customRootId", ""); setFolderPath(""); resetGridScrollToTop(); notifyContext(); try { clearSelection?.(); } catch (e) { console.debug?.(e); } await reloadGrid?.(); }; const setVueBreadcrumb = (payload: any) => { if (typeof folderBreadcrumbController?.setFolderBreadcrumb !== "function") return false; try { folderBreadcrumbController.setFolderBreadcrumb(payload); return true; } catch (e) { console.debug?.(e); return false; } }; const renderBreadcrumb = () => { const isCustom = String(read("scope", "") || "") === "custom"; const rel = String(read("currentFolderRelativePath", "") || "") .trim() .replaceAll("\\", "/"); const folderBrowsingActive = isCustom || !!rel; const selectedRootId = String(read("customRootId", "") || "").trim(); const selectedRootLabel = (() => { if (!selectedRootId) return ""; try { const opt = customSelect?.selectedOptions?.[0] || null; return String(opt?.textContent || "").trim(); } catch { return ""; } })(); if (!folderBrowsingActive) { folderBackStack.length = 0; if (setVueBreadcrumb({ visible: false })) return; folderBreadcrumb.style.display = "none"; folderBreadcrumb.replaceChildren(); return; } const hasSelectedRoot = !!String(read("customRootId", "") || "").trim(); const canBackToBrowserRoot = hasSelectedRoot && !rel; const canBackByPath = !!rel; const backDisabled = folderBackStack.length === 0 && !canBackToBrowserRoot && !canBackByPath; const upDisabled = !rel || parentFolderPath(currentFolderPath()) === currentFolderPath(); const handleBack = async () => { const prev = popDistinctHistory(folderBackStack, currentFolderPath()); if (prev != null) { await navigateFolder(prev, { pushHistory: false }); return; } const cur = currentFolderPath(); if (cur) { const parent = parentFolderPath(cur); if (parent === cur) { if (hasSelectedRoot) await resetToBrowserRoot(); return; } await navigateFolder(parent, { pushHistory: false }); return; } if (canBackToBrowserRoot) await resetToBrowserRoot(); }; const handleUp = async () => { const parent = parentFolderPath(currentFolderPath()); if (parent === currentFolderPath()) { if (hasSelectedRoot) await resetToBrowserRoot(); return; } await navigateFolder(parent); }; const buildItem = (label: any, target: any, isCurrent = false) => ({ label: String(label || ""), target: String(target || ""), current: !!isCurrent, onClick: async () => { if (label === "Computer" && String(read("customRootId", "") || "").trim()) { await resetToBrowserRoot(); return; } await navigateFolder(target); }, }); const breadcrumbItems = [buildItem(t("label.computer", "Computer"), "", !rel)]; if (selectedRootId) { breadcrumbItems.push(buildItem(selectedRootLabel || selectedRootId, "", !rel)); } if (rel) { const parts = rel.split("/").filter(Boolean); let acc = ""; for (let i = 0; i < parts.length; i++) { if (!acc) acc = isWindowsDrive(parts[i]) ? `${parts[i]}/` : parts[i]; else acc = `${acc}/${parts[i]}`.replace(/\/{2,}/g, "/"); breadcrumbItems.push(buildItem(parts[i], acc, i === parts.length - 1)); } } if ( setVueBreadcrumb({ visible: true, back: { label: t("btn.back", "Back"), disabled: backDisabled, onClick: handleBack, }, up: { label: t("btn.up", "Up"), disabled: upDisabled, onClick: handleUp, }, items: breadcrumbItems, }) ) { return; } folderBreadcrumb.style.display = "flex"; folderBreadcrumb.replaceChildren(); const backBtn = document.createElement("button"); backBtn.type = "button"; backBtn.textContent = t("btn.back", "Back"); backBtn.className = "mjr-btn-link mjr-folder-breadcrumb-action"; backBtn.disabled = backDisabled; backBtn.addEventListener("click", handleBack, { signal: lifecycleSignal || undefined }); folderBreadcrumb.appendChild(backBtn); const upBtn = document.createElement("button"); upBtn.type = "button"; upBtn.textContent = t("btn.up", "Up"); upBtn.className = "mjr-btn-link mjr-folder-breadcrumb-action"; upBtn.disabled = upDisabled; upBtn.addEventListener("click", handleUp, { signal: lifecycleSignal || undefined }); folderBreadcrumb.appendChild(upBtn); const sep0 = document.createElement("span"); sep0.textContent = "|"; sep0.className = "mjr-folder-breadcrumb-separator"; folderBreadcrumb.appendChild(sep0); const mk = (label: any, target: any, isCurrent = false) => { const el = document.createElement("button"); el.type = "button"; el.textContent = label; el.className = isCurrent ? "mjr-btn-link mjr-folder-breadcrumb-segment is-current" : "mjr-btn-link mjr-folder-breadcrumb-segment"; if (!isCurrent) { el.addEventListener( "click", async () => { if (label === "Computer" && String(read("customRootId", "") || "").trim()) { await resetToBrowserRoot(); return; } await navigateFolder(target); }, { signal: lifecycleSignal || undefined }, ); } else { el.disabled = true; } return el; }; for (let i = 0; i < breadcrumbItems.length; i++) { if (i > 0) { const sep = document.createElement("span"); sep.textContent = "/"; sep.className = "mjr-folder-breadcrumb-separator"; folderBreadcrumb.appendChild(sep); } const item = breadcrumbItems[i]; folderBreadcrumb.appendChild(mk(item.label, item.target, item.current)); } }; const bindGridFolderNavigation = () => { const onOpenFolderAsset = async (e: any) => { const asset = e?.detail?.asset || null; const target = resolveFolderTargetPath(asset || {}); const prev = currentFolderPath(); if (target !== prev) { pushUniqueHistory(folderBackStack, prev); setFolderPath(target); resetGridScrollToTop(); notifyContext(); try { clearSelection?.(); } catch (e) { console.debug?.(e); } try { await reloadGrid?.(); } catch (e) { console.debug?.(e); } } }; const onCustomSubfolderChanged = async (e: any) => { const next = String(e?.detail?.subfolder || "") .trim() .replaceAll("\\", "/"); const prev = currentFolderPath(); if (next !== prev) { pushUniqueHistory(folderBackStack, prev); setFolderPath(next); resetGridScrollToTop(); notifyContext(); try { clearSelection?.(); } catch (e) { console.debug?.(e); } try { await reloadGrid?.(); } catch (e) { console.debug?.(e); } } }; gridContainer?.addEventListener?.( "mjr:custom-subfolder-changed", onCustomSubfolderChanged, { signal: lifecycleSignal || undefined, }, ); gridContainer?.addEventListener?.("mjr:open-folder-asset", onOpenFolderAsset, { signal: lifecycleSignal || undefined, }); try { gridContainer._mjrHasCustomSubfolderHandler = true; } catch (e) { console.debug?.(e); } return () => { try { gridContainer?.removeEventListener?.( "mjr:custom-subfolder-changed", onCustomSubfolderChanged, ); } catch (e) { console.debug?.(e); } try { gridContainer?.removeEventListener?.("mjr:open-folder-asset", onOpenFolderAsset); } catch (e) { console.debug?.(e); } try { gridContainer._mjrHasCustomSubfolderHandler = false; } catch (e) { console.debug?.(e); } }; }; return { renderBreadcrumb, resetHistory: () => { folderBackStack.length = 0; }, bindGridFolderNavigation, notifyContext, }; }