mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2024-04-21 12:32:08 +00:00
chore (rewrite): rebuild filepage / viewerpage
This commit is contained in:
@@ -1,24 +1,65 @@
|
||||
import { animate, slideYOut, slideYIn } from "../lib/animate.js";
|
||||
import { CSS } from "../helpers/loader.js";
|
||||
|
||||
const isRunningFromAnIframe = window.self !== window.top;
|
||||
const css = await CSS(import.meta.url, "breadcrumb.css");
|
||||
|
||||
class ComponentBreadcrumb extends HTMLDivElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
if (new window.URL(location.href).searchParams.get("nav") === "false") return;
|
||||
if (new window.URL(location.href).searchParams.get("nav") === "false") {
|
||||
this.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const htmlLogout = isRunningFromAnIframe
|
||||
? ""
|
||||
: `
|
||||
<a href="/logout" data-link>
|
||||
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0ODkuODg4IDQ4OS44ODgiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDQ4OS44ODggNDg5Ljg4ODsiPgogIDxwYXRoIGZpbGw9IiM2ZjZmNmYiIGQ9Ik0yNS4zODMsMjkwLjVjLTcuMi03Ny41LDI1LjktMTQ3LjcsODAuOC0xOTIuM2MyMS40LTE3LjQsNTMuNC0yLjUsNTMuNCwyNWwwLDBjMCwxMC4xLTQuOCwxOS40LTEyLjYsMjUuNyAgICBjLTM4LjksMzEuNy02Mi4zLDgxLjctNTYuNiwxMzYuOWM3LjQsNzEuOSw2NSwxMzAuMSwxMzYuOCwxMzguMWM5My43LDEwLjUsMTczLjMtNjIuOSwxNzMuMy0xNTQuNWMwLTQ4LjYtMjIuNS05Mi4xLTU3LjYtMTIwLjYgICAgYy03LjgtNi4zLTEyLjUtMTUuNi0xMi41LTI1LjZsMCwwYzAtMjcuMiwzMS41LTQyLjYsNTIuNy0yNS42YzUwLjIsNDAuNSw4Mi40LDEwMi40LDgyLjQsMTcxLjhjMCwxMjYuOS0xMDcuOCwyMjkuMi0yMzYuNywyMTkuOSAgICBDMTIyLjE4Myw0ODEuOCwzNS4yODMsMzk2LjksMjUuMzgzLDI5MC41eiBNMjQ0Ljg4MywwYy0xOCwwLTMyLjUsMTQuNi0zMi41LDMyLjV2MTQ5LjdjMCwxOCwxNC42LDMyLjUsMzIuNSwzMi41ICAgIHMzMi41LTE0LjYsMzIuNS0zMi41VjMyLjVDMjc3LjM4MywxNC42LDI2Mi44ODMsMCwyNDQuODgzLDB6IiAvPgo8L3N2Zz4K" alt="power">
|
||||
</a>
|
||||
`;
|
||||
const paths = (this.getAttribute("path") || "").replace(new RegExp("/$"), "").split("/");
|
||||
const htmlPathChunks = paths.map((chunk, idx) => {
|
||||
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>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, previousPath, path) {
|
||||
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 })
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return ["path"];
|
||||
}
|
||||
|
||||
async render({ path = "", previous }) {
|
||||
path = this._normalised(path);
|
||||
previous = this._normalised(previous);
|
||||
let 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<nToAnimate; i++) {
|
||||
const n = previousChunks.length - i - 1;
|
||||
const $chunk = this.querySelector(`.component_path-element.n${n}`);
|
||||
if (!$chunk) throw new Error("component::breadcrumb.js - assertion failed - empty element");
|
||||
tasks.push(animate($chunk, { time: 100, keyframes: slideYOut(-10) }));
|
||||
}
|
||||
await Promise.all(tasks);
|
||||
}
|
||||
|
||||
// STEP2: setup the actual content
|
||||
this.querySelector(`[data-bind="path"]`).innerHTML = pathChunks.map((chunk, idx) => {
|
||||
const label = idx === 0 ? "Filestash" : chunk;
|
||||
const link = paths.slice(0, idx + 1).join("/") + "/";
|
||||
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;
|
||||
@@ -31,7 +72,7 @@ class ComponentBreadcrumb extends HTMLDivElement {
|
||||
else if (word.length > 27) return word.substring(0, 20).trim() + "...";
|
||||
return word;
|
||||
};
|
||||
const isLast = idx === paths.length - 1;
|
||||
const isLast = idx === pathChunks.length - 1;
|
||||
if (isLast) return `
|
||||
<div class="component_path-element n${idx}">
|
||||
<div class="li component_path-element-wrapper">
|
||||
@@ -55,22 +96,33 @@ class ComponentBreadcrumb extends HTMLDivElement {
|
||||
</div>
|
||||
</div>`;
|
||||
}).join("");
|
||||
this.render({ htmlLogout, htmlPathChunks });
|
||||
|
||||
// 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<nToAnimate; i++) {
|
||||
const n = pathChunks.length - i - 1;
|
||||
const $chunk = this.querySelector(`.component_path-element.n${n}`);
|
||||
if (!$chunk) throw new Error("component::breadcrumb.js - assertion failed - empty element");
|
||||
await animate($chunk, { time: 100, keyframes: slideYIn(-5) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async render({ htmlLogout, htmlPathChunks }) {
|
||||
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">
|
||||
${htmlLogout}
|
||||
</div>
|
||||
<span>${htmlPathChunks}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
_htmlLogout() {
|
||||
if (window.self !== window.top) return ""; // no logout button from an iframe
|
||||
return `
|
||||
<a href="/logout" data-link>
|
||||
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0ODkuODg4IDQ4OS44ODgiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDQ4OS44ODggNDg5Ljg4ODsiPgogIDxwYXRoIGZpbGw9IiM2ZjZmNmYiIGQ9Ik0yNS4zODMsMjkwLjVjLTcuMi03Ny41LDI1LjktMTQ3LjcsODAuOC0xOTIuM2MyMS40LTE3LjQsNTMuNC0yLjUsNTMuNCwyNWwwLDBjMCwxMC4xLTQuOCwxOS40LTEyLjYsMjUuNyAgICBjLTM4LjksMzEuNy02Mi4zLDgxLjctNTYuNiwxMzYuOWM3LjQsNzEuOSw2NSwxMzAuMSwxMzYuOCwxMzguMWM5My43LDEwLjUsMTczLjMtNjIuOSwxNzMuMy0xNTQuNWMwLTQ4LjYtMjIuNS05Mi4xLTU3LjYtMTIwLjYgICAgYy03LjgtNi4zLTEyLjUtMTUuNi0xMi41LTI1LjZsMCwwYzAtMjcuMiwzMS41LTQyLjYsNTIuNy0yNS42YzUwLjIsNDAuNSw4Mi40LDEwMi40LDgyLjQsMTcxLjhjMCwxMjYuOS0xMDcuOCwyMjkuMi0yMzYuNywyMTkuOSAgICBDMTIyLjE4Myw0ODEuOCwzNS4yODMsMzk2LjksMjUuMzgzLDI5MC41eiBNMjQ0Ljg4MywwYy0xOCwwLTMyLjUsMTQuNi0zMi41LDMyLjV2MTQ5LjdjMCwxOCwxNC42LDMyLjUsMzIuNSwzMi41ICAgIHMzMi41LTE0LjYsMzIuNS0zMi41VjMyLjVDMjc3LjM4MywxNC42LDI2Mi44ODMsMCwyNDQuODgzLDB6IiAvPgo8L3N2Zz4K" alt="power">
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
_normalised(path) {
|
||||
if (path === null) return null;
|
||||
else if (path.endsWith("/") === false) return path;
|
||||
return path.replace(new RegExp("/$"), "");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@ import { qs } from "../lib/dom.js";
|
||||
import { loadCSS } from "../helpers/loader.js";
|
||||
|
||||
export default function(ctrl) {
|
||||
const urlToPath = (pathname = "") => decodeURIComponent(pathname.split("/").filter((chunk, i) => i !== 1).join("/"));
|
||||
const $page = createElement(`
|
||||
<div class="component_filemanager_shell" style="flex-direction:row">
|
||||
<div data-bind="sidebar" class="hidden"></div>
|
||||
<div style="width:100%;display: flex; flex-direction: column;">
|
||||
<div is="component-breadcrumb" path="/home/mickael/Documents/projects/"></div>
|
||||
<div is="component-breadcrumb" path="${urlToPath(history.state.previous)}"></div>
|
||||
<div class="scroll-y" data-bind="filemanager-children"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -16,14 +17,12 @@ export default function(ctrl) {
|
||||
return async function(render) {
|
||||
render($page);
|
||||
|
||||
// feature1: setup the childrens
|
||||
// feature1: setup the breadcrumb path
|
||||
qs($page, `[is="component-breadcrumb"]`).setAttribute("path", urlToPath(location.pathname));
|
||||
|
||||
// feature2: setup the childrens
|
||||
ctrl(createRender(qs($page, `[data-bind="filemanager-children"]`)));
|
||||
ctrlSidebar(createRender(qs($page, `[data-bind="sidebar"]`)));
|
||||
|
||||
qs($page, `[is="component-breadcrumb"]`).setAttribute(
|
||||
"path",
|
||||
decodeURIComponent(location.pathname).replace(new RegExp("/files"), ""),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,3 +50,7 @@ async function ctrlSidebar(render) {
|
||||
`);
|
||||
render($comp);
|
||||
}
|
||||
|
||||
export function init() {
|
||||
return loadCSS(import.meta.url, "../components/decorator_shell_filemanager.css");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/* chromecast */
|
||||
:root {
|
||||
--disconnected-color: #F2F2F2;
|
||||
--connected-color: var(--primary);
|
||||
}
|
||||
|
||||
google-cast-launcher {
|
||||
display: inline-block;
|
||||
height: 23px;
|
||||
width: 23px;
|
||||
cursor: pointer;
|
||||
padding: 0 5px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.component_menubar {
|
||||
background: var(--dark);
|
||||
color: #f1f1f1;
|
||||
position: relative;
|
||||
display: block;
|
||||
box-shadow: 0 0px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
|
||||
z-index: 4;
|
||||
text-align: left;
|
||||
}
|
||||
.component_menubar .component_container {
|
||||
padding: 0;
|
||||
color: var(--bg-color);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.component_menubar .component_container > span {
|
||||
display: flex;
|
||||
}
|
||||
.component_menubar .titlebar {
|
||||
flex: 1;
|
||||
padding: 8px 0;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.component_menubar .action-item .component_icon {
|
||||
height: 19px;
|
||||
width: 19px;
|
||||
cursor: pointer;
|
||||
padding: 7px 7px 5px 7px;
|
||||
}
|
||||
.component_menubar .action-item .download-button .component_icon {
|
||||
padding-right: 1px;
|
||||
}
|
||||
|
||||
.dark-mode .component_menubar {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
.dark-mode .component_menubar .component_container {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.touch-yes .component_menubar .action-item .component_icon {
|
||||
height: 21px;
|
||||
width: 21px;
|
||||
}
|
||||
|
||||
.menubar-appear {
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
transform: translateY(2px);
|
||||
}
|
||||
|
||||
.menubar-appear.menubar-appear-active {
|
||||
opacity: 1;
|
||||
transform: translateY(0px);
|
||||
transition: all 0.25s ease-out;
|
||||
transition-delay: 0.30s;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { createElement } from "../lib/skeleton/index.js";
|
||||
import { loadCSS } from "../helpers/loader.js";
|
||||
|
||||
export default class ComponentMenubar extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.classList.add("component_menubar")
|
||||
this.innerHTML = `
|
||||
<div class="component_container">
|
||||
<span>
|
||||
<div class="titlebar" style="letter-spacing: 0.3px;">getting_started.pdf</div>
|
||||
<div class="action-item no-select"></div>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
this.render();
|
||||
}
|
||||
|
||||
async render(html) {
|
||||
await loadCSS(import.meta.url, "./menubar.css");
|
||||
html = `<span class="specific">
|
||||
<span id="chromecast-target"></span>
|
||||
</span>
|
||||
<span class="download-button">
|
||||
<span>
|
||||
<a href="/api/files/cat?path=%2FDocuments%2Fgetting_started.pdf" download="getting_started.pdf">
|
||||
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzODQgNTEyIj4KICA8cGF0aCBmaWxsPSIjZjJmMmYyIiBkPSJNIDM2MCw0NjAgSCAyNCBDIDEwLjcsNDYwIDAsNDUzLjMgMCw0NDAgdiAtMTIgYyAwLC0xMy4zIDEwLjcsLTIwIDI0LC0yMCBoIDMzNiBjIDEzLjMsMCAyNCw2LjcgMjQsMjAgdiAxMiBjIDAsMTMuMyAtMTAuNywyMCAtMjQsMjAgeiIgLz4KICA8cGF0aCBmaWxsPSIjZjJmMmYyIiBkPSJNIDIyNi41NTM5LDIzNC44ODQyOCBWIDUyLjk0MzI4MyBjIDAsLTYuNjI3IC01LjM3MywtMTIgLTEyLC0xMiBoIC00NCBjIC02LjYyNywwIC0xMiw1LjM3MyAtMTIsMTIgViAyMzQuODg0MjggaCAtNTIuMDU5IGMgLTIxLjM4MiwwIC0zMi4wOSwyNS44NTEgLTE2Ljk3MSw0MC45NzEgbCA4Ni4wNTksODYuMDU5IGMgOS4zNzMsOS4zNzMgMjQuNTY5LDkuMzczIDMzLjk0MSwwIGwgODYuMDU5LC04Ni4wNTkgYyAxNS4xMTksLTE1LjExOSA0LjQxMSwtNDAuOTcxIC0xNi45NzEsLTQwLjk3MSB6IiAvPgo8L3N2Zz4K" alt="download_white">
|
||||
</a>
|
||||
</span>
|
||||
</span>`;
|
||||
this.querySelector(".action-item").appendChild(createElement(html));
|
||||
}
|
||||
}
|
||||
|
||||
export function render(html = "") {
|
||||
const $el = document.body.querySelector("component-menubar");
|
||||
if (!$el) throw new Error("component::menubar.js missing element");
|
||||
$el.render(html);
|
||||
}
|
||||
|
||||
customElements.define("component-menubar", ComponentMenubar);
|
||||
@@ -2,10 +2,14 @@ import { get as getRelease } from "../pages/adminpage/model_release.js";
|
||||
|
||||
let version = null;
|
||||
|
||||
export async function loadJS(baseURL, path) {
|
||||
export async function loadJS(baseURL, path, opts = {}) {
|
||||
const $script = document.createElement("script");
|
||||
const link = new URL(path, baseURL);
|
||||
$script.setAttribute("src", link.toString());
|
||||
for (let key in opts) {
|
||||
$script.setAttribute(key, opts[key]);
|
||||
}
|
||||
if (typeof type === "string") ;
|
||||
if (document.head.querySelector(`[src="${link.toString()}"]`)) return Promise.resolve();
|
||||
document.head.appendChild($script);
|
||||
return new Promise((done) => {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
export function basename(str, sep = "/") {
|
||||
return str.substr(str.lastIndexOf(sep) + 1);
|
||||
}
|
||||
|
||||
export function join(baseURL, segment) {
|
||||
return new URL(segment, baseURL).pathname;
|
||||
}
|
||||
|
||||
+17398
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
+241
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+57124
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -7,6 +7,7 @@
|
||||
align-self: flex-start;
|
||||
background: var(--bg-color);
|
||||
padding-top: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* .component_page_filespage .error { */
|
||||
|
||||
@@ -2,11 +2,11 @@ import { createElement, createRender } from "../lib/skeleton/index.js";
|
||||
import rxjs, { effect } from "../lib/rx.js";
|
||||
import { qs } from "../lib/dom.js";
|
||||
import { loadCSS } from "../helpers/loader.js";
|
||||
import WithShell from "../components/decorator_shell_filemanager.js"
|
||||
import WithShell, { init as initShell } from "../components/decorator_shell_filemanager.js"
|
||||
|
||||
import { getState$ } from "./filespage/ctrl_filesystem_state.js";
|
||||
import componentFilesystem from "./filespage/ctrl_filesystem.js";
|
||||
import componentSubmenu from "./filespage/ctrl_submenu.js";
|
||||
import componentFilesystem, { init as initFilesystem } from "./filespage/ctrl_filesystem.js";
|
||||
import componentSubmenu, { init as initSubmenu } from "./filespage/ctrl_submenu.js";
|
||||
|
||||
import "../components/breadcrumb.js";
|
||||
|
||||
@@ -36,9 +36,6 @@ export default WithShell(function(render) {
|
||||
export function init() {
|
||||
return Promise.all([
|
||||
loadCSS(import.meta.url, "./ctrl_filespage.css"),
|
||||
loadCSS(import.meta.url, "../components/decorator_shell_filemanager.css"),
|
||||
loadCSS(import.meta.url, "./filespage/ctrl_filesystem.css"),
|
||||
loadCSS(import.meta.url, "./filespage/thing.css"),
|
||||
loadCSS(import.meta.url, "./filespage/ctrl_submenu.css"),
|
||||
initShell(), initFilesystem(), initSubmenu(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -4,30 +4,33 @@ import { loadCSS } from "../helpers/loader.js";
|
||||
|
||||
import "../components/breadcrumb.js";
|
||||
|
||||
function opener() {
|
||||
return "_";
|
||||
};
|
||||
|
||||
function loadModule(appName) {
|
||||
switch(appName) {
|
||||
case "editor":
|
||||
return import("./viewerpage/application_codemirror.js");
|
||||
case "pdf":
|
||||
return import("./viewerpage/application_pdf.js");
|
||||
}
|
||||
return import("./viewerpage/application_downloader.js");
|
||||
};
|
||||
|
||||
export default WithShell(async function(render) {
|
||||
const $page = createElement(`
|
||||
<div class="component_page_viewerpage"></div>
|
||||
`);
|
||||
const $page = createElement(`<div class="component_page_viewerpage"></div>`);
|
||||
render($page);
|
||||
|
||||
const opener = "editor";
|
||||
|
||||
let module;
|
||||
switch(opener) {
|
||||
case "editor":
|
||||
module = await import("./viewerpage/application_codemirror.js");
|
||||
break;
|
||||
default:
|
||||
module = await import("./viewerpage/application_downloader.js");
|
||||
break;
|
||||
}
|
||||
if (typeof module.init === "function") await module.init();
|
||||
const module = await loadModule(opener());
|
||||
module.default(createRender($page));
|
||||
})
|
||||
|
||||
export function init() {
|
||||
export async function init() {
|
||||
const module = await loadModule(opener());
|
||||
return Promise.all([
|
||||
loadCSS(import.meta.url, "./ctrl_viewerpage.css"),
|
||||
loadCSS(import.meta.url, "../components/decorator_shell_filemanager.css"),
|
||||
typeof module.init === "function" ? module.init() : Promise.resolve(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createElement } from "../../lib/skeleton/index.js";
|
||||
import { animate, slideYIn } from "../../lib/animate.js";
|
||||
import rxjs, { effect } from "../../lib/rx.js";
|
||||
import { CSS } from "../../helpers/loader.js";
|
||||
import { loadCSS } from "../../helpers/loader.js";
|
||||
import { qs } from "../../lib/dom.js";
|
||||
import { ApplicationError } from "../../lib/error.js";
|
||||
import { toggle as toggleLoader } from "../../components/loader.js";
|
||||
@@ -26,27 +26,26 @@ export default async function(render) {
|
||||
const path = location.pathname.replace(new RegExp("^/files"), "");
|
||||
effect(rxjs.of(path).pipe(
|
||||
toggleLoader($page, true),
|
||||
rxjs.mergeMap(() => new Promise((done) => setTimeout(() => done({
|
||||
files: new Array(100).fill(1),
|
||||
}), 1000))),
|
||||
ls(),
|
||||
toggleLoader($page, false),
|
||||
rxjs.mergeMap(({ files }) => { // STEP1: setup the list of files
|
||||
rxjs.mergeMap(({ files, path }) => { // STEP1: setup the list of files
|
||||
const FILE_HEIGHT = 160;
|
||||
// const BLOCK_SIZE = Math.ceil(document.body.clientHeight / FILE_HEIGHT) + 1;
|
||||
const BLOCK_SIZE = 10;
|
||||
const BLOCK_SIZE = Math.ceil(document.body.clientHeight / FILE_HEIGHT) + 1;
|
||||
// const BLOCK_SIZE = 6;
|
||||
const COLUMN_PER_ROW = 4;
|
||||
const VIRTUAL_SCROLL_MINIMUM_TRIGGER = 20;
|
||||
const VIRTUAL_SCROLL_MINIMUM_TRIGGER = 50;
|
||||
let size = files.length;
|
||||
if (size > VIRTUAL_SCROLL_MINIMUM_TRIGGER) {
|
||||
size = BLOCK_SIZE * COLUMN_PER_ROW;
|
||||
size = Math.min(files.length, BLOCK_SIZE * COLUMN_PER_ROW);
|
||||
}
|
||||
const $list = qs($page, ".list");
|
||||
const $fs = document.createDocumentFragment();
|
||||
for (let i = 0; i < size; i++) {
|
||||
const file = files[i];
|
||||
$fs.appendChild(createThing({
|
||||
name: `file ${i}`,
|
||||
type: "file",
|
||||
link: "/view/test.txt",
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
link: createLink(file, path),
|
||||
}));
|
||||
}
|
||||
animate($list, { time: 200, keyframes: slideYIn(5) });
|
||||
@@ -77,7 +76,7 @@ export default async function(render) {
|
||||
setHeight(0);
|
||||
const top = ($node) => $node.getBoundingClientRect().top;
|
||||
return rxjs.of({
|
||||
files,
|
||||
files, path,
|
||||
currentState: 0,
|
||||
$list,
|
||||
setHeight,
|
||||
@@ -86,7 +85,7 @@ export default async function(render) {
|
||||
});
|
||||
}),
|
||||
rxjs.mergeMap(({
|
||||
files,
|
||||
files, path,
|
||||
BLOCK_SIZE, COLUMN_PER_ROW, FILE_HEIGHT,
|
||||
MARGIN,
|
||||
currentState,
|
||||
@@ -142,12 +141,13 @@ export default async function(render) {
|
||||
for (let i = fileStart; i < fileEnd; i++) {
|
||||
const file = files[i];
|
||||
if (file === undefined) $fs.appendChild(createThing({
|
||||
name: "dummy",
|
||||
type: "hidden",
|
||||
}))
|
||||
else $fs.appendChild(createThing({
|
||||
name: `file - ${i}`,
|
||||
type: "file",
|
||||
link: "/view/test.txt",
|
||||
name: file.name,
|
||||
// name: `file ${i}`,
|
||||
type: file.type,
|
||||
link: createLink(file, path),
|
||||
}));
|
||||
n += 1;
|
||||
}
|
||||
@@ -167,3 +167,17 @@ export default async function(render) {
|
||||
rxjs.catchError(ctrlError()),
|
||||
));
|
||||
}
|
||||
|
||||
export function init() {
|
||||
return Promise.all([
|
||||
loadCSS(import.meta.url, "./ctrl_filesystem.css"),
|
||||
loadCSS(import.meta.url, "./thing.css"),
|
||||
]);
|
||||
}
|
||||
|
||||
function createLink(file, path) {
|
||||
if (file.type === "file") {
|
||||
return "/view" + path + file.name;
|
||||
}
|
||||
return "/files" + path + file.name + "/";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { onDestroy, createElement, createRender, createFragment } from "../../lib/skeleton/index.js";
|
||||
import rxjs, { effect, applyMutation, onClick } from "../../lib/rx.js";
|
||||
import { animate } from "../../lib/animate.js";
|
||||
import { loadCSS } from "../../helpers/loader.js";
|
||||
import { qs } from "../../lib/dom.js";
|
||||
import { getSelection$, clearSelection } from "./model_files.js";
|
||||
|
||||
@@ -12,6 +13,7 @@ export default async function(render) {
|
||||
effect(rxjs.fromEvent($scroll, "scroll", { passive: true }).pipe(
|
||||
rxjs.map((e) => e.target.scrollTop > 30),
|
||||
rxjs.distinctUntilChanged(),
|
||||
rxjs.startWith(false),
|
||||
rxjs.tap((scrolling) => scrolling ?
|
||||
$scroll.classList.add("scrolling") :
|
||||
$scroll.classList.remove("scrolling")),
|
||||
@@ -75,3 +77,7 @@ export default async function(render) {
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
||||
export function init() {
|
||||
return loadCSS(import.meta.url, "./ctrl_submenu.css");
|
||||
}
|
||||
|
||||
@@ -23,13 +23,20 @@ export function ls() {
|
||||
rxjs.mergeMap((path) => ajax({
|
||||
url: `/api/files/ls?path=${path}`,
|
||||
responseType: "json"
|
||||
})),
|
||||
rxjs.map(({ responseJSON }) => ({ files: responseJSON.results }))
|
||||
}).pipe(rxjs.map(({ responseJSON }) => ({
|
||||
files: responseJSON.results.sort(sortByDefault),
|
||||
path,
|
||||
})))),
|
||||
);
|
||||
}
|
||||
|
||||
// function repeat(element, times) {
|
||||
// const result = Array(times);
|
||||
// for (let i = 0; i < times; i++) result[i] = element;
|
||||
// return result;
|
||||
// }
|
||||
const sortByDefault = (fileA, fileB) => {
|
||||
if (fileA.type !== fileB.type) {
|
||||
if (fileA.type === "file") return +1;
|
||||
return -1;
|
||||
}
|
||||
// if (fileA.name < fileB.name) {
|
||||
// return -1
|
||||
// }
|
||||
return 0;
|
||||
};
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import { createElement } from "../../lib/skeleton/index.js";
|
||||
import { CSS } from "../../helpers/loader.js";
|
||||
import { addSelection } from "./model_files.js";
|
||||
|
||||
const IMAGE = {
|
||||
"FILE": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiPgogIDxwYXRoIHN0eWxlPSJjb2xvcjojMDAwMDAwO3RleHQtaW5kZW50OjA7dGV4dC10cmFuc2Zvcm06bm9uZTtmaWxsOiM4YzhjOGM7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlLXdpZHRoOjAuOTg0ODEwNDEiIGQ9Im0gMiwxMy4wODI0MTIgMC4wMTk0NjIsMS40OTIzNDcgYyA1ZS02LDAuMjIyMTQ1IDAuMjA1NTkwMiwwLjQyNDI2MiAwLjQzMTE1MDIsMC40MjQyNzIgTCAxMy41ODk2MTIsMTUgQyAxMy44MTUxNzMsMTQuOTk5OTk1IDEzLjk5OTk5LDE0Ljc5Nzg3NCAxNCwxNC41NzU3MjkgdiAtMS40OTMzMTcgYyAtNC4xNzE4NjkyLDAuNjYyMDIzIC03LjY1MTY5MjgsMC4zOTg2OTYgLTEyLDAgeiIgLz4KICA8cGF0aCBzdHlsZT0iY29sb3I6IzAwMDAwMDt0ZXh0LWluZGVudDowO3RleHQtdHJhbnNmb3JtOm5vbmU7ZGlzcGxheTppbmxpbmU7ZmlsbDojYWFhYWFhO3N0cm9rZS13aWR0aDowLjk4NDA4MTI3IiBkPSJNIDIuMzUwMSwxLjAwMTMzMTIgQyAyLjE1MjU5LDEuMDM4MzI0NyAxLjk5NjU5LDEuMjI3MjcyMyAyLjAwMDA5LDEuNDI0OTM1NiBWIDE0LjEzMzQ1NyBjIDVlLTYsMC4yMjE4MTYgMC4yMDUyMywwLjQyMzYzNCAwLjQzMDc5LDAuNDIzNjQ0IGwgMTEuMTM5LC0xLjAxZS00IGMgMC4yMjU1NiwtNmUtNiAwLjQzMDExLC0wLjIwMDc1OCAwLjQzMDEyLC0wLjQyMjU3NCBsIDYuN2UtNCwtOS44MjI2NDI2IGMgLTIuNDg0MDQ2LC0xLjM1NTAwNiAtMi40MzUyMzQsLTIuMDMxMjI1NCAtMy41MDAxLC0zLjMwOTcwNyAtMC4wNDMsLTAuMDE1ODgyIDAuMDQ2LDAuMDAxNzQgMCwwIEwgMi40MzA2NywxLjAwMTEwOCBDIDIuNDAzODMsMC45OTg1OSAyLjM3Njc0LDAuOTk4NTkgMi4zNDk5LDEuMDAxMTA4IFoiIC8+CiAgPHBhdGggc3R5bGU9ImRpc3BsYXk6aW5saW5lO2ZpbGw6IzhjOGM4YztmaWxsLW9wYWNpdHk6MTtzdHJva2U6IzllNzU3NTtzdHJva2Utd2lkdGg6MDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIiBkPSJtIDEwLjUwMDU3LDEuMDAyMDc2NCBjIDAsMy4yNzY4MDI4IC0wLjAwNTIsMy4xNzM5MTYxIDAuMzYyOTIxLDMuMjY5ODIwMiAwLjI4MDEwOSwwLjA3Mjk4NCAzLjEzNzE4LDAuMDM5ODg3IDMuMTM3MTgsMC4wMzk4ODcgLTEuMTIwMDY3LC0xLjA1NTY2OTIgLTIuMzMzNCwtMi4yMDY0NzEzIC0zLjUwMDEsLTMuMzA5NzA3NCB6IiAvPgo8L3N2Zz4K",
|
||||
"FOLDER": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiPgogIDxnIHRyYW5zZm9ybT0ibWF0cml4KDAuODY2NjY0MzEsMCwwLDAuODY2NjcsLTE3Mi4wNDU3OCwtODY0LjMyNzU5KSIgc3R5bGU9ImZpbGw6Izc1YmJkOTtmaWxsLW9wYWNpdHk6MC45NDExNzY0NztmaWxsLXJ1bGU6ZXZlbm9kZCI+CiAgICA8cGF0aCBzdHlsZT0iZmlsbDojNzViYmQ5O2ZpbGwtb3BhY2l0eTowLjk0MTE3NjQ3O2ZpbGwtcnVsZTpldmVub2RkIiBkPSJtIDIwMC4yLDk5OS43MiBjIC0wLjI4OTEzLDAgLTAuNTMxMjUsMC4yNDIxIC0wLjUzMTI1LDAuNTMxMiB2IDEyLjc4NCBjIDAsMC4yOTg1IDAuMjMyNjQsMC41MzEyIDAuNTMxMjUsMC41MzEyIGggMTUuMDkxIGMgMC4yOTg2LDAgMC41MzEyNCwtMC4yMzI3IDAuNTMxMjQsLTAuNTMxMiBsIDRlLTQsLTEwLjQ3NCBjIDAsLTAuMjg4OSAtMC4yNDIxMSwtMC41MzM4IC0wLjUzMTI0LC0wLjUzMzggbCAtNy41NDU3LDVlLTQgLTIuMzA3NiwtMi4zMDc4MyB6IiAvPgogIDwvZz4KICA8ZyB0cmFuc2Zvcm09Im1hdHJpeCgwLjg2NjY3LDAsMCwwLjg2NjY3LC0xNzIuMDQ2OTIsLTg2NC43ODM0KSIgc3R5bGU9ImZpbGw6IzlhZDFlZDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6ZXZlbm9kZCI+CiAgICA8cGF0aCBzdHlsZT0iZmlsbDojOWFkMWVkO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpldmVub2RkIiBkPSJtIDIwMC4yLDk5OS43MiBjIC0wLjI4OTEzLDAgLTAuNTMxMjUsMC4yNDIxIC0wLjUzMTI1LDAuNTMxMiB2IDEyLjc4NCBjIDAsMC4yOTg1IDAuMjMyNjQsMC41MzEyIDAuNTMxMjUsMC41MzEyIGggMTUuMDkxIGMgMC4yOTg2LDAgMC41MzEyNCwtMC4yMzI3IDAuNTMxMjQsLTAuNTMxMiBsIDRlLTQsLTEwLjQ3NCBjIDAsLTAuMjg4OSAtMC4yNDIxMSwtMC41MzM4IC0wLjUzMTI0LC0wLjUzMzggbCAtNy41NDU3LDVlLTQgLTIuMzA3NiwtMi4zMDc4MyB6IiAvPgogIDwvZz4KPC9zdmc+Cg=="
|
||||
}
|
||||
|
||||
const $tmpl = createElement(`
|
||||
<div class="component_thing view-grid not-selected" draggable="true">
|
||||
<a href="/view/README.org" data-link>
|
||||
<a href="__TEMPLATE__" data-link>
|
||||
<div class="box">
|
||||
<div class="component_checkbox"><input type="checkbox"><span class="indicator"></span></div>
|
||||
<span>
|
||||
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiPgogIDxnIHRyYW5zZm9ybT0ibWF0cml4KDAuODY2NjY0MzEsMCwwLDAuODY2NjcsLTE3Mi4wNDU3OCwtODY0LjMyNzU5KSIgc3R5bGU9ImZpbGw6Izc1YmJkOTtmaWxsLW9wYWNpdHk6MC45NDExNzY0NztmaWxsLXJ1bGU6ZXZlbm9kZCI+CiAgICA8cGF0aCBzdHlsZT0iZmlsbDojNzViYmQ5O2ZpbGwtb3BhY2l0eTowLjk0MTE3NjQ3O2ZpbGwtcnVsZTpldmVub2RkIiBkPSJtIDIwMC4yLDk5OS43MiBjIC0wLjI4OTEzLDAgLTAuNTMxMjUsMC4yNDIxIC0wLjUzMTI1LDAuNTMxMiB2IDEyLjc4NCBjIDAsMC4yOTg1IDAuMjMyNjQsMC41MzEyIDAuNTMxMjUsMC41MzEyIGggMTUuMDkxIGMgMC4yOTg2LDAgMC41MzEyNCwtMC4yMzI3IDAuNTMxMjQsLTAuNTMxMiBsIDRlLTQsLTEwLjQ3NCBjIDAsLTAuMjg4OSAtMC4yNDIxMSwtMC41MzM4IC0wLjUzMTI0LC0wLjUzMzggbCAtNy41NDU3LDVlLTQgLTIuMzA3NiwtMi4zMDc4MyB6IiAvPgogIDwvZz4KICA8ZyB0cmFuc2Zvcm09Im1hdHJpeCgwLjg2NjY3LDAsMCwwLjg2NjY3LC0xNzIuMDQ2OTIsLTg2NC43ODM0KSIgc3R5bGU9ImZpbGw6IzlhZDFlZDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6ZXZlbm9kZCI+CiAgICA8cGF0aCBzdHlsZT0iZmlsbDojOWFkMWVkO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpldmVub2RkIiBkPSJtIDIwMC4yLDk5OS43MiBjIC0wLjI4OTEzLDAgLTAuNTMxMjUsMC4yNDIxIC0wLjUzMTI1LDAuNTMxMiB2IDEyLjc4NCBjIDAsMC4yOTg1IDAuMjMyNjQsMC41MzEyIDAuNTMxMjUsMC41MzEyIGggMTUuMDkxIGMgMC4yOTg2LDAgMC41MzEyNCwtMC4yMzI3IDAuNTMxMjQsLTAuNTMxMiBsIDRlLTQsLTEwLjQ3NCBjIDAsLTAuMjg4OSAtMC4yNDIxMSwtMC41MzM4IC0wLjUzMTI0LC0wLjUzMzggbCAtNy41NDU3LDVlLTQgLTIuMzA3NiwtMi4zMDc4MyB6IiAvPgogIDwvZz4KPC9zdmc+Cg==" alt="directory">
|
||||
<img class="component_icon" draggable="false" src="__TEMPLATE__" alt="directory">
|
||||
</span>
|
||||
<span class="component_filename">
|
||||
<span class="file-details">
|
||||
<span>Videos<span class="extension"></span></span>
|
||||
<span>__TEMPLATE__<span class="extension"></span></span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="component_datetime"><span>06/06/2020</span></span>
|
||||
@@ -23,8 +27,6 @@ const $tmpl = createElement(`
|
||||
</div>
|
||||
`);
|
||||
|
||||
export const css = CSS(import.meta.url, "thing.css");
|
||||
|
||||
// a filesystem "thing" is typically either a file or folder which have a lot of behavior builtin.
|
||||
// Probably one day we can rename that to something more clear but the gist is a thing can be
|
||||
// displayed in list mode / grid mode, have some substate to enable loading state for upload,
|
||||
@@ -45,6 +47,9 @@ export function createThing({
|
||||
|
||||
$label.textContent = name;
|
||||
$thing.querySelector("a").setAttribute("href", link);
|
||||
$thing.querySelector("img").setAttribute("src", (type === "file" ? IMAGE.FILE : IMAGE.FOLDER));
|
||||
if (type === "hidden") $thing.classList.add("hidden");
|
||||
|
||||
$thing.querySelector(".component_checkbox").onclick = function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
.component_page_viewerpage .component_pdfviewer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.component_page_viewerpage .component_pdfviewer [data-bind="pdf"] {
|
||||
overflow-y: scroll;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.component_page_viewerpage .component_pdfviewer [data-bind="pdf"] component-icon[name="loading"] {
|
||||
padding-top: 75px;
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { createElement } from "../../lib/skeleton/index.js";
|
||||
import { animate, opacityIn } from "../../lib/animate.js";
|
||||
import { qs } from "../../lib/dom.js";
|
||||
import { loadCSS, loadJS } from "../../helpers/loader.js";
|
||||
import { join } from "../../lib/path.js";
|
||||
|
||||
import { getFilename, getDownloadUrl } from "./common.js";
|
||||
|
||||
import "../../components/menubar.js";
|
||||
import "../../components/icon.js";
|
||||
|
||||
const hasNativePDF = "application/pdf" in navigator.mimeTypes;
|
||||
|
||||
export default async function(render) {
|
||||
hasNativePDF ? pdfNative(render) : pdfJs(render);
|
||||
}
|
||||
|
||||
export function init() {
|
||||
if (hasNativePDF) return Promise.resolve();
|
||||
|
||||
return Promise.all([
|
||||
loadJS(import.meta.url, "../../lib/vendor/pdfjs/pdf.js", { type: "module" }),
|
||||
loadJS(import.meta.url, "../../lib/vendor/pdfjs/pdf.worker.js", { type: "module" }),
|
||||
loadCSS(import.meta.url, "./application_pdf.css"),
|
||||
]).then(() => {
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = join(import.meta.url, "../../lib/vendor/pdfjs/pdf.worker.js");
|
||||
});
|
||||
}
|
||||
|
||||
function pdfNative(render) {
|
||||
const $page = createElement(`
|
||||
<div class="component_pdfviewer" style="background: #525659">
|
||||
<component-menubar></component-menubar>
|
||||
<embed
|
||||
style="width:100%;height:100%;opacity:0"
|
||||
src="${getDownloadUrl()}#toolbar=0"
|
||||
type="application/pdf"
|
||||
/>
|
||||
</div>
|
||||
`);
|
||||
render($page);
|
||||
|
||||
const $embed = $page.querySelector("embed");
|
||||
$embed.onload = () => {
|
||||
$embed.style.opacity = 1;
|
||||
animate($embed, { time: 300, keyframes: opacityIn() });
|
||||
};
|
||||
}
|
||||
|
||||
async function pdfJs(render) {
|
||||
const $page = createElement(`
|
||||
<div class="component_pdfviewer" style="background: #525659;text-align:center;">
|
||||
<component-menubar></component-menubar>
|
||||
<div data-bind="pdf"></div>
|
||||
</div>
|
||||
`);
|
||||
render($page);
|
||||
|
||||
|
||||
const createBr = () => $container.appendChild(document.createElement("br"));
|
||||
const $container = qs($page, `[data-bind="pdf"]`);
|
||||
const timeoutID = window.setTimeout(() => {
|
||||
const $icon = createElement(`<component-icon name="loading"></component-icon>`);
|
||||
$container.appendChild($icon);
|
||||
}, 300);
|
||||
const pdf = await pdfjsLib.getDocument(getDownloadUrl()).promise;
|
||||
clearTimeout(timeoutID);
|
||||
$container.innerHTML = "";
|
||||
createBr();
|
||||
for (let i=0; i<pdf.numPages; i++) {
|
||||
const page = await pdf.getPage(i + 1);
|
||||
const viewport = page.getViewport({
|
||||
scale: Math.min(
|
||||
Math.max(document.body.clientWidth - 200, 0),
|
||||
800,
|
||||
) / page.getViewport({ scale: 1 }).width,
|
||||
});
|
||||
const $canvas = document.createElement("canvas");
|
||||
$canvas.height = viewport.height;
|
||||
$canvas.width = viewport.width;
|
||||
$container.appendChild($canvas);
|
||||
await page.render({
|
||||
canvasContext: $canvas.getContext("2d"),
|
||||
viewport: viewport,
|
||||
});
|
||||
if (i % 5 === 0) await new Promise((done) => requestAnimationFrame(done));
|
||||
}
|
||||
for (let i=0; i<4; i++) createBr();
|
||||
}
|
||||
@@ -5,7 +5,7 @@ export function getFilename() {
|
||||
}
|
||||
|
||||
export function getDownloadUrl() {
|
||||
return "/api/files/cat?path=" + prepare(getCurrentPath().replace(/%23/g, "#"));
|
||||
return "/api/files/cat?path=" + getCurrentPath().replace(/%23/g, "#");
|
||||
}
|
||||
|
||||
function getCurrentPath() {
|
||||
|
||||
Reference in New Issue
Block a user