chore (rewrite): breadcrumb

This commit is contained in:
MickaelK 2023-12-31 15:15:21 +11:00
parent 58cf21d8e1
commit 3be2ea460c
5 changed files with 115 additions and 81 deletions

View file

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

View file

@ -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 = `
<div class="component_breadcrumb container" role="navigation">
<style>${css}</style>
<div class="breadcrumb no-select">
<div class="ul">
<div class="li component_logout">
${this._htmlLogout()}
</div>
<span data-bind="path"></span>
<div class="li component_logout">${this.__htmlLogout()}</div>
</div>
</div>
</div>`;
}
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 {
<div class="component_path-element n${idx}">
<div class="li component_path-element-wrapper">
<div class="label">
<div>${limitSize(label)}</div>
<span></span>
<div>${limitSize(label)}</div><span></span>
</div>
</div>
</div>`;
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 `
...
<span class="title">
${limitSize(label, true)}
</span>
`;
return `<div>${limitSize(label)}</div>`
})();
return `
<div class="component_path-element n${idx}">
<div class="li component_path-element-wrapper">
<div>
<a class="label" href="/files${link}" data-link>
<div>${limitSize(label)}</div>
${tmpl}
</a>
<div class="component_separator">
<img alt="path_separator" width="16" height="16" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAYAAAA7bUf6AAAA30lEQVQ4T63T7Q2CMBAG4OuVPdQNcAPdBCYwDdclCAQ3ACfRDXQDZQMHgNRcAoYApfWjv0jIPX3b3gn4wxJjI03TUAhRBkGwV0o9ffaYIEVRrJumuQHA3ReaILxzl+bCkNZ660ozi/QQIl4BoCKieAmyIlyU53lkjCld0CIyhIwxSmt9nEvkRLgoyzIuPggh4iRJqjHkhXTQAwBWUsqNUoq/38sL+TlJf7lf38ngdU5EFNme2adPFgGGrR2LiGcAqIko/LhjeXbatuVOraWUO58hnJ1iRKx8AetxXPHH/1+y62USursaSgAAAABJRU5ErkJggg==">
@ -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 = `<div class="component_saving">*</div>`;
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 `
<a href="/logout" data-link>
@ -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" });

View file

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

View file

@ -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(),
]);
}

View file

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