mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2025-10-27 19:53:41 +08:00
This contains a bunch of things packaged in 1: 1) UI improvements for the 3D viewer to support all sort of file types and create a nice rendering in a clean way with all sort of options 2) enable people to use Filestash as an SDK so we can embed the 3d viewer elsewhere
165 lines
4.4 KiB
JavaScript
165 lines
4.4 KiB
JavaScript
import { createElement, createRender } from "../lib/skeleton/index.js";
|
|
import { toHref, fromHref } from "../lib/skeleton/router.js";
|
|
import { forwardURLParams } from "../lib/path.js";
|
|
import rxjs, { effect, applyMutation } from "../lib/rx.js";
|
|
import { qs } from "../lib/dom.js";
|
|
import t from "../locales/index.js";
|
|
|
|
import { AjaxError, ApplicationError } from "../lib/error.js";
|
|
|
|
import "../components/icon.js";
|
|
|
|
const strToHTML = (str) => str
|
|
.replaceAll("<", "<")
|
|
.replaceAll(">", ">")
|
|
.replaceAll(" ", " ");
|
|
|
|
export default function(render) {
|
|
let hasBack = true;
|
|
if (!render) {
|
|
render = createRender(document.body);
|
|
try { render = createRender(qs(document.body, "[role=\"main\"]")); }
|
|
catch (err) { hasBack = false; }
|
|
}
|
|
|
|
return function(err) {
|
|
const [msg, trace] = processError(err);
|
|
|
|
const link = forwardURLParams(calculateBacklink(fromHref(window.location.pathname)), ["share"]);
|
|
const $page = createElement(`
|
|
<div>
|
|
<style>${css}</style>
|
|
<a href="${link}" class="backnav ${!hasBack && "hidden"}">
|
|
<component-icon name="arrow_left"></component-icon>
|
|
${t("home")}
|
|
</a>
|
|
<div class="component_container">
|
|
<div class="error-page">
|
|
<h1>${t("Oops!")}</h1>
|
|
<h2>${t(msg)}</h2>
|
|
<p>
|
|
<button class="light" data-bind="details">${t("More details")}</button>
|
|
<button class="primary" data-bind="refresh">${t("Reload")}</button>
|
|
<pre class="hidden"><code>${strToHTML(trace)}</code></pre>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`);
|
|
render($page);
|
|
|
|
// feature: show error details
|
|
effect(rxjs.fromEvent(qs($page, "button[data-bind=\"details\"]"), "click").pipe(
|
|
rxjs.mapTo(["hidden"]),
|
|
applyMutation(qs($page, "pre"), "classList", "toggle")
|
|
));
|
|
|
|
// feature: refresh button
|
|
const shouldHideRefreshButton = location.pathname === "/";
|
|
const $refresh = qs($page, "button[data-bind=\"refresh\"]");
|
|
if (shouldHideRefreshButton) $refresh.remove();
|
|
else effect(rxjs.fromEvent($refresh, "click").pipe(
|
|
rxjs.tap(() => location.reload())
|
|
));
|
|
|
|
return rxjs.EMPTY;
|
|
};
|
|
}
|
|
|
|
function processError(err) {
|
|
let msg, trace;
|
|
if (err instanceof AjaxError) {
|
|
msg = t(err.message);
|
|
trace = `
|
|
type: ${err.type()}
|
|
code: ${err.code()}
|
|
message: ${err.message}
|
|
trace: ${err.stack}`;
|
|
} else if (err instanceof ApplicationError) {
|
|
msg = t(err.message);
|
|
trace = `
|
|
type: ${err.type()}
|
|
debug: ${err.debug()}
|
|
trace: ${err.stack}`;
|
|
} else {
|
|
msg = t("Internal Error");
|
|
trace = `
|
|
type: Error
|
|
message: ${err.message}
|
|
trace: ${err.stack || "N/A"}`;
|
|
}
|
|
return [msg, trace.trim()];
|
|
}
|
|
|
|
const css = `
|
|
.error-page {
|
|
width: 80%;
|
|
max-width: 600px;
|
|
margin: 50px auto 0 auto;
|
|
flex-direction: column;
|
|
}
|
|
.error-page h1 {
|
|
margin: 5px 0;
|
|
font-size: 3.1em;
|
|
}
|
|
.error-page h2 {
|
|
margin: 10px 0;
|
|
font-weight: normal;
|
|
font-weight: 100;
|
|
}
|
|
.error-page button {
|
|
padding-left: 10px;
|
|
padding-right: 10px;
|
|
line-height: 1.2rem;
|
|
text-transform: capitalize;
|
|
}
|
|
.error-page code {
|
|
margin-top: 5px;
|
|
display: block;
|
|
padding: 10px;
|
|
overflow-x: auto;
|
|
overflow-y: auto;
|
|
background: #e2e2e2;
|
|
color: var(--dark);
|
|
border-radius: 3px;
|
|
max-height: 350px;
|
|
}
|
|
.error-page pre {
|
|
margin: 0;
|
|
}
|
|
.error-page p {
|
|
font-style: italic;
|
|
margin-bottom: 5px;
|
|
}
|
|
.error-page a {
|
|
border-bottom: 1px dashed;
|
|
}
|
|
|
|
.backnav {
|
|
display: inline-block;
|
|
padding: 10px 5px;
|
|
}
|
|
.backnav .component_icon {
|
|
height: 23px;
|
|
margin-right: -3px;
|
|
vertical-align: middle;
|
|
}
|
|
`;
|
|
|
|
function calculateBacklink(pathname = "") {
|
|
let url = "/";
|
|
const listPath = pathname.replace(new RegExp("/$"), "").split("/");
|
|
switch (listPath[1]) {
|
|
case "view": // in view mode, navigate to current folder
|
|
listPath[1] = "files";
|
|
listPath.pop();
|
|
url = listPath.join("/") + "/";
|
|
break;
|
|
case "files": // in file browser mode, navigate to parent folder
|
|
listPath.pop();
|
|
url = listPath.join("/") + "/";
|
|
break;
|
|
}
|
|
return toHref(url === "/files/" ? "/" : url);
|
|
}
|