chore (rewrite): rebuild filepage / viewerpage

This commit is contained in:
MickaelK
2023-12-12 00:16:36 +11:00
parent 1440febe42
commit 4e49d9054a
22 changed files with 75171 additions and 90 deletions
+79 -27
View File
@@ -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");
}
+76
View File
@@ -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;
}
+42
View File
@@ -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);
+5 -1
View File
@@ -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) => {
+4
View File
@@ -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;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -7,6 +7,7 @@
align-self: flex-start;
background: var(--bg-color);
padding-top: 20px;
box-sizing: border-box;
}
/* .component_page_filespage .error { */
+4 -7
View File
@@ -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(),
]);
}
+19 -16
View File
@@ -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");
}
+14 -7
View File
@@ -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;
};
+11 -6
View File
@@ -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();
}
+1 -1
View File
@@ -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() {