mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-15 21:04:46 +01:00
chore (rewrite): breadcrumb
This commit is contained in:
parent
58cf21d8e1
commit
3be2ea460c
5 changed files with 115 additions and 81 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" });
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) });
|
||||
}),
|
||||
));
|
||||
|
|
|
|||
Loading…
Reference in a new issue