import { toHref } from "../lib/skeleton/router.js"; import { animate, slideYOut, slideYIn, opacityOut } from "../lib/animate.js"; import { forwardURLParams } from "../lib/path.js"; import { safe } from "../lib/dom.js"; import assert from "../lib/assert.js"; import { settingsSave } from "../lib/store.js"; import { get as getConfig } from "../model/config.js"; import { loadCSS } from "../helpers/loader.js"; import { extractPath, isDir, isNativeFileUpload } from "../pages/filespage/helper.js"; import { mv as mv$ } from "../pages/filespage/model_files.js"; import { mv as mvVL, withVirtualLayer } from "../pages/filespage/model_virtual_layer.js"; const mv = (from, to) => withVirtualLayer( mv$(from, to), mvVL(from, to), ); class ComponentBreadcrumb extends HTMLElement { constructor() { super(); if (new URL(location.href).searchParams.get("nav") === "false") { this.disabled = true; return; } this.__init(); } async __init() { this.innerHTML = ` ${this.__htmlLogout()} `; assert.type(this.querySelector("img[alt=\"sidebar-open\"]"), HTMLElement).onclick = () => { settingsSave({ visible: true }, "sidebar"); window.dispatchEvent(new Event("resize")); }; } attributeChangedCallback(name, oldValue, newValue) { if (this.disabled === true) return; else if (oldValue === newValue) return; switch (name) { case "path": if (newValue === "") return; return this.renderPath({ path: newValue, previous: oldValue || null }); case "indicator": return this.renderIndicator(); } throw new Error("component::breadcrumb.js unknow attribute name: "+ name); } static get observedAttributes() { return ["path", "indicator"]; } async renderPath({ path = "", previous }) { path = this.__normalised(path); previous = this.__normalised(previous); const pathChunks = path.split("/"); // STEP1: leaving animation on elements that will be removed if (previous !== null && previous.indexOf(path) >= 0) { const previousChunks = previous.split("/"); const nToAnimate = previousChunks.length - pathChunks.length; const tasks = []; for (let i=0; i { const label = idx === 0 ? getConfig("name", "Filestash") : chunk; const link = pathChunks.slice(0, idx + 1).join("/") + "/"; const limitSize = (word, highlight = false) => { if (highlight === true && word.length > 30) { return word.substring(0, 12).trim() + "..." + word.substring(word.length - 10, word.length).trim(); } else if (word.length > 27) return word.substring(0, 20).trim() + "..."; return word; }; const isLast = idx === pathChunks.length - 1; if (isLast) return ` ${safe(limitSize(label))} `; const minify = (() => { if (idx === 0) return false; else if (pathChunks.length <= (document.body.clientWidth > 800 ? 5 : 4)) return false; else if (idx > pathChunks.length - (document.body.clientWidth > 1000 ? 4 : 3)) return false; return true; })(); const tmpl = (() => { if (minify) return ` ... ${safe(limitSize(label, true))} `; return `${safe(limitSize(label))}`; })(); return ` ${tmpl} `; }).join(""); this.setupDragDropTarget(); // STEP3: entering animation for elements that got added in if (previous !== null && path.indexOf(previous) >= 0) { const previousChunks = previous.split("/"); const nToAnimate = pathChunks.length - previousChunks.length; for (let i=0; i*`; await animate($indicator, { time: 500, keyframes: [ { transform: "scale(0)", offset: 0 }, { transform: "scale(1.5)", offset: 0.3 }, { transform: "scale(1)", offset: 1 }, ], fill: "none" }); } else { $indicator.style.opacity = 0; await animate($indicator, { time: 200, keyframes: opacityOut(), fill: "none" }); } } setupDragDropTarget() { this.querySelectorAll("a.label").forEach(($elmnt) => { const $folder = assert.type($elmnt, HTMLElement); const $path = assert.truthy($folder.closest(".component_path-element")); $folder.ondrop = async(e) => { $path.classList.remove("highlight"); const from = e.dataTransfer.getData("path"); let to = $path.getAttribute("data-path"); const [, fromName] = extractPath(from); to += fromName; if (isDir(from)) to += "/"; await mv(from, to).toPromise(); }; $folder.ondragover = (e) => { if (isNativeFileUpload(e)) return; e.preventDefault(); $path.classList.add("highlight"); }; $folder.ondragleave = () => { $path.classList.remove("highlight"); }; }); } __htmlLogout() { if (window.self !== window.top) return ""; // no logout button from an iframe return ` `; } __normalised(path) { if (path === null) return null; else if (path.endsWith("/") === false) return path; return path.replace(new RegExp("/$"), ""); } } export function init() { return loadCSS(import.meta.url, "./breadcrumb.css"); } customElements.define("component-breadcrumb", ComponentBreadcrumb);