diff --git a/public/assets/components/breadcrumb.css b/public/assets/components/breadcrumb.css index 26c9fbe2..41c35f10 100644 --- a/public/assets/components/breadcrumb.css +++ b/public/assets/components/breadcrumb.css @@ -5,36 +5,27 @@ .component_breadcrumb .breadcrumb { margin: 0 0 0px 0; z-index: 1000; - padding: 3px 0 3px 10px; } .component_breadcrumb .breadcrumb .ul { + display: flex; list-style-type: none; margin: 0; width: 100%; - padding: 0 10px 0 10px; box-sizing: border-box; } .component_breadcrumb .breadcrumb .ul > span { display: block; + flex-grow: 1; padding: 7px 0; } .component_breadcrumb .breadcrumb .ul div, .component_breadcrumb .breadcrumb .ul .li { display: inline-block; } .component_breadcrumb .component_logout { - float: right; - display: inline-block; - margin: 0 0px 0 5px; - line-height: 25px; - padding: 7px 0; -} -.component_breadcrumb .component_logout a { - display: block; - vertical-align: middle; + align-self: center; } .component_breadcrumb .component_logout .component_icon { height: 20px; - vertical-align: middle; } .component_breadcrumb .component_saving { padding-left: 1px; @@ -46,6 +37,7 @@ color: rgba(0, 0, 0, 0.75); padding: 4px 5px; } +.component_breadcrumb .component_path-element .label span { display: inline-block; } .component_breadcrumb .component_path-element a.label { position: relative; color: rgba(0, 0, 0, 0.5); @@ -79,6 +71,28 @@ .component_breadcrumb .component_separator img { vertical-align: middle; } + +/* Phone like devices */ +body.touch-yes [is="component-breadcrumb"] .ul span { + overflow-x: auto; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + box-sizing: border-box; + -moz-box-sizing: border-box; + white-space: nowrap; +} +body.touch-yes [is="component-breadcrumb"] .ul span::-webkit-scrollbar { + height: 0px; +} +body.touch-yes [is="component-breadcrumb"] .ul span::-webkit-scrollbar-track { + background: var(--super-light); +} +body.touch-yes [is="component-breadcrumb"] .ul span::-webkit-scrollbar-thumb { + background: #d2d2d2; + border-radius: 1px; +} + +/* Dark Mode */ .dark-mode .component_breadcrumb .component_separator img { filter: brightness(0.5) invert(1); } @@ -91,41 +105,18 @@ body.touch-no .component_path-element-wrapper a.label:hover span.title { transform: translateY(0px); transition: all 0.15s ease-out; } - body.dark-mode.touch-no .component_path-element-wrapper a.label:hover { background: rgba(255, 255, 255, 0.05); } - -body.touch-yes ul span { - overflow-x: auto; - overflow-y: hidden; - -webkit-overflow-scrolling: touch; - box-sizing: border-box; - -moz-box-sizing: border-box; - white-space: nowrap; +.dark-mode .component_breadcrumb .component_path-element .label { + color: #f1f1f1; + opacity: 0.7; } -body.touch-yes ul span::-webkit-scrollbar { - height: 0px; -} -body.touch-yes ul span::-webkit-scrollbar-track { - background: var(--super-light); -} -body.touch-yes ul span::-webkit-scrollbar-thumb { - background: #d2d2d2; - border-radius: 1px; +.dark-mode .component_breadcrumb .component_path-element a.label { + opacity: 1; } /* ANIMATION */ -.component_breadcrumb .breadcrumb-leave { - display: inline-block; - opacity: 1; - transform: translateY(0px); -} -.component_breadcrumb .breadcrumb-leave.breadcrumb-leave-active { - opacity: 0; - transform: translateY(-10px); - transition: all 0.15s ease-out; -} .component_breadcrumb .breadcrumb-enter { transform: translateX(10px); opacity: 0; @@ -159,11 +150,3 @@ body.touch-yes ul span::-webkit-scrollbar-thumb { transform: scale(1); } } - -.dark-mode .component_breadcrumb .component_path-element .label { - color: #f1f1f1; - opacity: 0.7; -} -.dark-mode .component_breadcrumb .component_path-element a.label { - opacity: 1; -} diff --git a/public/assets/components/breadcrumb.js b/public/assets/components/breadcrumb.js index 168d3380..5543c950 100644 --- a/public/assets/components/breadcrumb.js +++ b/public/assets/components/breadcrumb.js @@ -1,7 +1,5 @@ -import { animate, slideYOut, slideYIn } from "../lib/animate.js"; -import { CSS } from "../helpers/loader.js"; - -const css = await CSS(import.meta.url, "breadcrumb.css"); +import { animate, slideYOut, slideYIn, slideXIn, opacityOut } from "../lib/animate.js"; +import { loadCSS } from "../helpers/loader.js"; class ComponentBreadcrumb extends HTMLDivElement { @@ -11,35 +9,42 @@ class ComponentBreadcrumb extends HTMLDivElement { this.disabled = true; return; } + this.__init(); + } + async __init() { this.innerHTML = ` `; } - attributeChangedCallback(name, previousPath, path) { + attributeChangedCallback(name, oldValue, newValue) { if (this.disabled === true) return; - if (name !== "path") throw new Error("component::breadcrumb.js unknow attribute name: "+ name); - if (path == "") return; - this.render({ path, previous: previousPath || null }) + 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"]; + return ["path", "indicator"]; } - async render({ path = "", previous }) { - path = this._normalised(path); - previous = this._normalised(previous); + async renderPath({ path = "", previous }) { + path = this.__normalised(path); + previous = this.__normalised(previous); let pathChunks = path.split("/"); // STEP1: leaving animation on elements that will be removed @@ -60,15 +65,11 @@ class ComponentBreadcrumb extends HTMLDivElement { this.querySelector(`[data-bind="path"]`).innerHTML = pathChunks.map((chunk, idx) => { const label = idx === 0 ? "Filestash" : chunk; const link = pathChunks.slice(0, idx + 1).join("/") + "/"; - // const minify = (function() { - // if (idx === 0) return false; - // else if (paths.length <= (document.body.clientWidth > 800 ? 5 : 4)) return false; - // else if (idx > paths.length - (document.body.clientWidth > 1000 ? 4 : 3)) return false; - // return true; - // }()); 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(); } + 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; }; @@ -77,17 +78,34 @@ class ComponentBreadcrumb extends HTMLDivElement {
-
${limitSize(label)}
- +
${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 ` + ... + + ${limitSize(label, true)} + + `; + return `
${limitSize(label)}
` + })(); + return `
-
${limitSize(label)}
+ ${tmpl}
path_separator @@ -110,7 +128,29 @@ class ComponentBreadcrumb extends HTMLDivElement { } } - _htmlLogout() { + async renderIndicator() { + let state = this.hasAttribute("indicator"); + if (state && this.getAttribute("indicator") !== "false") state = true; + + const $indicator = this.querySelector(`[data-bind="path"]`) + .lastChild + .querySelector("span"); + + if (state) { + $indicator.style.opacity = 1; + $indicator.innerHTML = `
*
`; + 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" }); + } + } + + __htmlLogout() { if (window.self !== window.top) return ""; // no logout button from an iframe return ` @@ -119,11 +159,15 @@ class ComponentBreadcrumb extends HTMLDivElement { `; } - _normalised(path) { + __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, { extends: "div" }); diff --git a/public/assets/components/decorator_shell_filemanager.css b/public/assets/components/decorator_shell_filemanager.css index f1801c91..7c5b1e75 100644 --- a/public/assets/components/decorator_shell_filemanager.css +++ b/public/assets/components/decorator_shell_filemanager.css @@ -52,7 +52,7 @@ .component_filemanager_shell [data-bind="sidebar"].hidden ~ div > [data-bind="filemanager-children"] .container { width: 95%; margin: 0 auto; - max-width: 820px; + max-width: 815px; } .component_filemanager_shell [data-bind="sidebar"].hidden ~ div > [data-bind="filemanager-children"] { background: var(--bg-color); diff --git a/public/assets/components/decorator_shell_filemanager.js b/public/assets/components/decorator_shell_filemanager.js index e3e772e6..e264923a 100644 --- a/public/assets/components/decorator_shell_filemanager.js +++ b/public/assets/components/decorator_shell_filemanager.js @@ -3,6 +3,7 @@ import { onDestroy } from "../lib/skeleton/lifecycle.js"; import { animate, slideYOut } from "../lib/animate.js"; import { qs } from "../lib/dom.js"; import { loadCSS } from "../helpers/loader.js"; +import { init as initBreadcrumb } from "../components/breadcrumb.js"; export default function(ctrl) { const urlToPath = (pathname = "") => decodeURIComponent(pathname.split("/").filter((chunk, i) => i !== 1).join("/")); @@ -63,5 +64,8 @@ async function ctrlSidebar(render) { } export function init() { - return loadCSS(import.meta.url, "../components/decorator_shell_filemanager.css"); + return Promise.all([ + loadCSS(import.meta.url, "../components/decorator_shell_filemanager.css"), + initBreadcrumb(), + ]); } diff --git a/public/assets/pages/viewerpage/application_editor.js b/public/assets/pages/viewerpage/application_editor.js index fd248e0a..ad2b3014 100644 --- a/public/assets/pages/viewerpage/application_editor.js +++ b/public/assets/pages/viewerpage/application_editor.js @@ -111,16 +111,19 @@ export default async function(render) { rxjs.mergeMap((editor) => content$.pipe(rxjs.map((oldContent) => [editor, editor.getValue(), oldContent]))), rxjs.tap(async ([editor, newContent = "", oldContent = ""]) => { if ($fab.disabled) return; - else if (newContent === oldContent) { + const $breadcrumb = qs(document.body, `[is="component-breadcrumb"]`); + if (newContent === oldContent) { await animate($fab, { time: 100, keyframes: opacityOut() }); $fab.classList.add("hidden"); + $breadcrumb.removeAttribute("indicator"); return } + $breadcrumb.setAttribute("indicator", "true"); const shouldAnimate = $fab.classList.contains("hidden"); $fab.classList.remove("hidden"); $fab.render($ICON.SAVING); $fab.onclick = () => CodeMirror.commands.save(editor); - // TODO: breadcrumb saving hint * + if (shouldAnimate) await animate($fab, { time: 100, keyframes: slideXIn(40) }); }), ));