diff --git a/public/assets/components/loader.js b/public/assets/components/loader.js
index 1f142520..97267964 100644
--- a/public/assets/components/loader.js
+++ b/public/assets/components/loader.js
@@ -1,5 +1,6 @@
import { createElement } from "../lib/skeleton/index.js";
-import rxjs from "../lib/rx.js";
+import rxjs, { effect } from "../lib/rx.js";
+import { animate, opacityIn } from "../lib/animate.js";
class Loader extends window.HTMLElement {
constructor() {
@@ -42,25 +43,27 @@ class Loader extends window.HTMLElement {
}
}
-
-// function tap(sideEffectFn) {
-// return (source) => {
-// // TODO: use source.lift
-// console.log("SOURCE", source)
-// source.lift({
-// call: (subscriber, source) => {
-// console.log(source, subscriber);
-// return subscriber;
-// },
-// })
-// }
-// }
-const tap = rxjs.tap;
-
customElements.define("component-loader", Loader);
+export function createLoader($parent, opts = {}) {
+ const { wait = 500 } = opts
+ const cancel = effect(new rxjs.Observable((observer) => {
+ const $icon = createElement(``);
+ const id = window.setTimeout(() => {
+ $parent.appendChild($icon);
+ animate($icon, { time: 1000, keyframes: opacityIn() });
+ }, wait);
+ return () => {
+ clearTimeout(id);
+ $icon.remove();
+ };
+ }));
+ return rxjs.tap(() => cancel());
+}
+
+// > after this should be deprecated
export default createElement("");
export function toggle($node, show = false) {
- if (show === true) return tap(() => $node.appendChild(createElement("")));
- else return tap(() => $node.querySelector("component-loader")?.remove());
+ if (show === true) return rxjs.tap(() => $node.appendChild(createElement("")));
+ else return rxjs.tap(() => $node.querySelector("component-loader")?.remove());
}
diff --git a/public/assets/lib/rx.js b/public/assets/lib/rx.js
index bef94e6e..5cf23e5b 100644
--- a/public/assets/lib/rx.js
+++ b/public/assets/lib/rx.js
@@ -8,8 +8,9 @@ export default rxjs;
export const ajax = ajaxModule.ajax;
export function effect(obs) {
- const tmp = obs.subscribe(() => {}, (err) => { throw err; });
- onDestroy(() => tmp.unsubscribe());
+ const sub = obs.subscribe(() => {}, (err) => { throw err; });
+ onDestroy(() => sub.unsubscribe());
+ return sub.unsubscribe.bind(sub);
}
const getFn = (obj, arg0, ...args) => {
@@ -27,9 +28,7 @@ export function applyMutation($node, ...keys) {
export function applyMutations($node, ...keys) {
if (!$node) throw new Error("undefined node");
const execute = getFn($node, ...keys);
- return rxjs.tap((vals) => vals.forEach((val) => {
- execute(val);
- }));
+ return rxjs.tap((vals) => vals.forEach((val) => execute(val)));
}
export function stateMutation($node, attr) {
@@ -42,8 +41,17 @@ export function preventDefault() {
}
export function onClick($node) {
- if (!$node) return rxjs.EMPTY;
- return rxjs.fromEvent($node, "click").pipe(
- rxjs.map(() => $node),
- );
+ if (!$node) throw new Error("undefined node");
+ return rxjs.fromEvent($node, "click").pipe(rxjs.map(() => $node));
+}
+
+export function onLoad($node) {
+ if (!$node) throw new Error("undefined node");
+ return new rxjs.Observable((observer) => {
+ $node.onload = () => {
+ observer.next($node);
+ observer.complete();
+ };
+ $node.onerror = (err) => observer.error(err);
+ });
}
diff --git a/public/assets/pages/ctrl_viewerpage.js b/public/assets/pages/ctrl_viewerpage.js
index 64c4d24f..737ae59e 100644
--- a/public/assets/pages/ctrl_viewerpage.js
+++ b/public/assets/pages/ctrl_viewerpage.js
@@ -1,12 +1,20 @@
import { createElement, createRender } from "../lib/skeleton/index.js";
-import WithShell from "../components/decorator_shell_filemanager.js";
+import rxjs, { effect } from "../lib/rx.js";
+import { ApplicationError } from "../lib/error.js";
+import { basename } from "../lib/path.js";
import { loadCSS } from "../helpers/loader.js";
+import WithShell from "../components/decorator_shell_filemanager.js";
+import { get as getConfig } from "../model/config.js";
+
+import { opener } from "./viewerpage/mimetype.js";
+import { getCurrentPath } from "./viewerpage/common.js";
import "../components/breadcrumb.js";
-function opener() {
- return "_";
-};
+const mime$ = getConfig().pipe(
+ rxjs.map((config) => config.mime),
+ rxjs.shareReplay(),
+);
function loadModule(appName) {
switch(appName) {
@@ -14,23 +22,32 @@ function loadModule(appName) {
return import("./viewerpage/application_codemirror.js");
case "pdf":
return import("./viewerpage/application_pdf.js");
+ case "download":
+ return import("./viewerpage/application_downloader.js");
+ default:
+ throw new ApplicationError("Internal Error", `Unknown opener app "${appName}" at "${getCurrentPath()}"`);
}
- return import("./viewerpage/application_downloader.js");
};
export default WithShell(async function(render) {
const $page = createElement(`
`);
render($page);
- const module = await loadModule(opener());
- module.default(createRender($page));
+ effect(mime$.pipe(
+ rxjs.map((mimes) => opener(basename(getCurrentPath()), mimes)),
+ rxjs.mergeMap(([opener]) => loadModule(opener)),
+ rxjs.map((module) => module.default(createRender($page))),
+ ));
})
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(),
+ mime$.pipe(
+ rxjs.map((mimes) => opener(basename(getCurrentPath()), mimes)),
+ rxjs.mergeMap(([opener]) => loadModule(opener)),
+ rxjs.mergeMap((module) => typeof module.init === "function"? module.init() : rxjs.EMPTY),
+ ).toPromise(),
]);
}
diff --git a/public/assets/pages/viewerpage/application_pdf.css b/public/assets/pages/viewerpage/application_pdf.css
index 0e98c025..254a1106 100644
--- a/public/assets/pages/viewerpage/application_pdf.css
+++ b/public/assets/pages/viewerpage/application_pdf.css
@@ -1,11 +1,17 @@
.component_page_viewerpage .component_pdfviewer {
display: flex;
flex-direction: column;
+ background: #525659;
+ text-align:center;
}
.component_page_viewerpage .component_pdfviewer [data-bind="pdf"] {
overflow-y: scroll;
flex: 1 1 auto;
}
+.component_page_viewerpage .component_pdfviewer [data-bind="pdf"] embed {
+ width:100%;
+ height:100%;
+}
.component_page_viewerpage .component_pdfviewer [data-bind="pdf"] component-icon[name="loading"] {
padding-top: 75px;
display: block;
diff --git a/public/assets/pages/viewerpage/application_pdf.js b/public/assets/pages/viewerpage/application_pdf.js
index 2fd1b887..dd78d411 100644
--- a/public/assets/pages/viewerpage/application_pdf.js
+++ b/public/assets/pages/viewerpage/application_pdf.js
@@ -1,89 +1,98 @@
import { createElement } from "../../lib/skeleton/index.js";
+import rxjs, { effect, onLoad } from "../../lib/rx.js";
import { animate, opacityIn } from "../../lib/animate.js";
import { qs } from "../../lib/dom.js";
+import { createLoader } from "../../components/loader.js";
import { loadCSS, loadJS } from "../../helpers/loader.js";
import { join } from "../../lib/path.js";
+import ctrlError from "../ctrl_error.js";
import { getFilename, getDownloadUrl } from "./common.js";
import "../../components/menubar.js";
import "../../components/icon.js";
-const hasNativePDF = "application/pdf" in navigator.mimeTypes;
+// const hasNativePDF = "application/pdf" in navigator.mimeTypes;
+const hasNativePDF = true
export default async function(render) {
- hasNativePDF ? pdfNative(render) : pdfJs(render);
+ const ctrl = hasNativePDF ? ctrlPDFNative : ctrlPDFJs;
+ ctrl(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) {
+function ctrlPDFNative(render) {
const $page = createElement(`
-
+
`);
render($page);
- const $embed = $page.querySelector("embed");
- $embed.onload = () => {
- $embed.style.opacity = 1;
- animate($embed, { time: 300, keyframes: opacityIn() });
- };
+ const removeLoader = createLoader(qs($page, `[data-bind="pdf"]`));
+ effect(onLoad(qs($page, "embed")).pipe(
+ removeLoader,
+ rxjs.tap(($embed) => $embed.classList.remove("hidden")),
+ rxjs.tap(($embed) => animate($embed, { time: 200, keyframes: opacityIn() })),
+ rxjs.catchError(ctrlError(render)),
+ ));
}
-async function pdfJs(render) {
+async function ctrlPDFJs(render) {
const $page = createElement(`
-
+
`);
render($page);
-
- const createBr = () => $container.appendChild(document.createElement("br"));
const $container = qs($page, `[data-bind="pdf"]`);
- const timeoutID = window.setTimeout(() => {
- const $icon = createElement(`
`);
- $container.appendChild($icon);
- }, 300);
- const pdf = await pdfjsLib.getDocument(getDownloadUrl()).promise;
- clearTimeout(timeoutID);
- $container.innerHTML = "";
- createBr();
- for (let i=0; i
requestAnimationFrame(done));
+ const createBr = () => $container.appendChild(document.createElement("br"));
+ const removeLoader = createLoader($container);
+ effect(rxjs.from(pdfjsLib.getDocument(getDownloadUrl()).promise).pipe(
+ removeLoader,
+ rxjs.mergeMap(async (pdf) => {
+ createBr();
+ for (let i=0; i requestAnimationFrame(done));
+ }
+ createBr();
+ }),
+ rxjs.catchError(ctrlError(render)),
+ ));
+}
+
+export function init() {
+ const deps = [
+ loadCSS(import.meta.url, "./application_pdf.css"),
+ ];
+ if (!hasNativePDF) {
+ deps.push(loadJS(import.meta.url, "../../lib/vendor/pdfjs/pdf.js", { type: "module" }));
+ deps.push(loadJS(import.meta.url, "../../lib/vendor/pdfjs/pdf.worker.js", { type: "module" }).then(() => {
+ pdfjsLib.GlobalWorkerOptions.workerSrc = join(import.meta.url, "../../lib/vendor/pdfjs/pdf.worker.js");
+ }));
}
- for (let i=0; i<4; i++) createBr();
+ return Promise.all(deps);
}
diff --git a/public/assets/pages/viewerpage/common.js b/public/assets/pages/viewerpage/common.js
index 906d4c94..fcd8b64a 100644
--- a/public/assets/pages/viewerpage/common.js
+++ b/public/assets/pages/viewerpage/common.js
@@ -8,13 +8,13 @@ export function getDownloadUrl() {
return "/api/files/cat?path=" + getCurrentPath().replace(/%23/g, "#");
}
-function getCurrentPath() {
- return location.pathname.replace("/view", "") + (location.hash || "");
+export function getCurrentPath() {
+ return decodeURIComponent(location.pathname.replace("/view", "") + (location.hash || ""));
}
-function prepare(path) {
- return encodeURIComponent(decodeURIComponent(path.replace(/%/g, "%25")));
-}
+// function prepare(path) {
+// return encodeURIComponent(decodeURIComponent(path.replace(/%/g, "%25")));
+// }
function appendShareToUrl() {
// TODO
diff --git a/public/assets/pages/viewerpage/mimetype.js b/public/assets/pages/viewerpage/mimetype.js
new file mode 100644
index 00000000..9fe270c7
--- /dev/null
+++ b/public/assets/pages/viewerpage/mimetype.js
@@ -0,0 +1,38 @@
+export function opener(file = "", mimes) {
+ const mime = getMimeType(file, mimes);
+ const type = mime.split("/")[0];
+
+ if (window.overrides && typeof window.overrides["xdg-open"] === "function") {
+ const openerFromPlugin = window.overrides["xdg-open"](mime);
+ if (openerFromPlugin !== null) {
+ return openerFromPlugin;
+ }
+ }
+
+ if (type === "text") {
+ return ["editor", null];
+ } else if (mime === "application/pdf") {
+ return ["pdf", null];
+ } else if (type === "image") {
+ return ["image", null];
+ } else if (["application/javascript", "application/xml", "application/json",
+ "application/x-perl"].indexOf(mime) !== -1) {
+ return ["editor", null];
+ } else if (["audio/wave", "audio/mp3", "audio/flac", "audio/ogg"].indexOf(mime) !== -1) {
+ return ["audio", null];
+ } else if (mime === "application/x-form") {
+ return ["form", null];
+ } else if (type === "video" || mime === "application/ogg") {
+ return ["video", null];
+ } else if(["application/epub+zip"].indexOf(mime) !== -1) {
+ return ["ebook", null];
+ } else if (type === "application") {
+ return ["download", null];
+ }
+
+ return ["editor", null];
+}
+
+function getMimeType(file, mimes = {}) {
+ return mimes[file.split(".")[1]] || "text/plain";
+}