chore (migration): migrate frontend code

This commit is contained in:
Mickael Kerjean 2023-08-15 00:38:08 +10:00
parent 2e7b49660b
commit 40f4caa213
27 changed files with 144 additions and 115 deletions

View file

@ -12,10 +12,8 @@ class ComponentBreadcrumb extends HTMLDivElement {
: `
<a href="/logout" data-link>
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0ODkuODg4IDQ4OS44ODgiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDQ4OS44ODggNDg5Ljg4ODsiPgogIDxwYXRoIGZpbGw9IiM2ZjZmNmYiIGQ9Ik0yNS4zODMsMjkwLjVjLTcuMi03Ny41LDI1LjktMTQ3LjcsODAuOC0xOTIuM2MyMS40LTE3LjQsNTMuNC0yLjUsNTMuNCwyNWwwLDBjMCwxMC4xLTQuOCwxOS40LTEyLjYsMjUuNyAgICBjLTM4LjksMzEuNy02Mi4zLDgxLjctNTYuNiwxMzYuOWM3LjQsNzEuOSw2NSwxMzAuMSwxMzYuOCwxMzguMWM5My43LDEwLjUsMTczLjMtNjIuOSwxNzMuMy0xNTQuNWMwLTQ4LjYtMjIuNS05Mi4xLTU3LjYtMTIwLjYgICAgYy03LjgtNi4zLTEyLjUtMTUuNi0xMi41LTI1LjZsMCwwYzAtMjcuMiwzMS41LTQyLjYsNTIuNy0yNS42YzUwLjIsNDAuNSw4Mi40LDEwMi40LDgyLjQsMTcxLjhjMCwxMjYuOS0xMDcuOCwyMjkuMi0yMzYuNywyMTkuOSAgICBDMTIyLjE4Myw0ODEuOCwzNS4yODMsMzk2LjksMjUuMzgzLDI5MC41eiBNMjQ0Ljg4MywwYy0xOCwwLTMyLjUsMTQuNi0zMi41LDMyLjV2MTQ5LjdjMCwxOCwxNC42LDMyLjUsMzIuNSwzMi41ICAgIHMzMi41LTE0LjYsMzIuNS0zMi41VjMyLjVDMjc3LjM4MywxNC42LDI2Mi44ODMsMCwyNDQuODgzLDB6IiAvPgo8L3N2Zz4K" alt="power">
</a>
`;
</a>`;
const paths = (this.getAttribute("path") || "").split("/");
console.log(this.getAttribute("path"), paths);
const htmlPathChunks = paths.slice(0, -1).map((chunk, idx) => {
const label = idx === 0 ? "Filestash" : chunk;
const link = paths.slice(0, idx).join("/") + "/";
@ -54,7 +52,11 @@ class ComponentBreadcrumb extends HTMLDivElement {
</div>
</div>`;
}).join("");
this.render({ htmlLogout, htmlPathChunks });
}
async render({ htmlLogout, htmlPathChunks }) {
const css = await CSS(import.meta.url, "breadcrumb.css");
this.innerHTML = `
<div class="component_breadcrumb" role="navigation">
<style>${css}</style>
@ -66,11 +68,8 @@ class ComponentBreadcrumb extends HTMLDivElement {
<span>${htmlPathChunks}</span>
</div>
</div>
</div>
`;
</div>`;
}
}
const css = await CSS(import.meta, "breadcrumb.css");
customElements.define("component-breadcrumb", ComponentBreadcrumb, { extends: "div" });

View file

@ -14,7 +14,7 @@ class Icon extends window.HTMLElement {
class="component_icon"
draggable="false"
src="${img}"
alt="${name}" />`;
alt="${alt}" />`;
}
_mapOfIcon(name) {
@ -32,4 +32,4 @@ class Icon extends window.HTMLElement {
}
}
window.customElements.define("component-icon", Icon);
customElements.define("component-icon", Icon);

View file

@ -16,7 +16,7 @@ const free = () => {
export default class Modal extends HTMLElement {
async trigger($node, opts = {}) {
const { onQuit, leftButton, rightButton } = opts;
const { onQuit } = opts;
const $modal = createElement(`
<div class="component_modal" id="modal-box">
<style>${await CSS(import.meta.url, "modal.css")}</style>

View file

@ -19,3 +19,6 @@ global.console = {
warn: jest.fn(),
error: jest.fn()
};
global.customElements = {
define: jest.fn(),
};

View file

@ -6,7 +6,6 @@ export default function(opts) {
else if (typeof opts !== "object") throw new Error("unsupported call");
if (!opts.headers) opts.headers = {};
opts.headers["X-Requested-With"] = "XmlHttpRequest";
const isJson = opts.responseType;
return ajax({ ...opts, responseType: "text" }).pipe(
rxjs.catchError((err) => rxjs.throwError(processError(err.xhr, err))),
rxjs.map((res) => {
@ -52,7 +51,6 @@ function processError(xhr, err) {
message || "Oups something went wrong with our servers",
err, "INTERNAL_SERVER_ERROR"
);
break;
case 401:
return new AjaxError(
message || "Authentication error",
@ -60,10 +58,9 @@ function processError(xhr, err) {
);
case 403:
return new AjaxError(
message || "You can\'t do that",
message || "You can't do that",
err, "FORBIDDEN"
);
break;
case 413:
return new AjaxError(
message || "Payload too large",
@ -77,7 +74,7 @@ function processError(xhr, err) {
case 409:
if (response.error_summary) { // dropbox way to say doesn't exist
return new AjaxError(
"Doesn\'t exist",
"Doesn't exist",
err, "UNKNOWN_PATH"
);
}
@ -92,7 +89,6 @@ function processError(xhr, err) {
"Service unavailable, if the problem persist, contact your administrator",
err, "INTERNAL_SERVER_ERROR"
);
break;
default:
return new AjaxError(xhr.responseText, err, "INTERNAL_SERVER_ERROR");
}

View file

@ -1,5 +1,4 @@
import { onDestroy, createElement } from "./skeleton/index.js";
import rxjs from "./rx.js";
import { onDestroy } from "./skeleton/index.js";
export function transition($node, opts = {}) {
const {
@ -18,7 +17,7 @@ export function animate($node, opts = {}) {
else if (typeof $node.animate !== "function") return Promise.resolve();
return new Promise((done) => {
const run = $node.animate(keyframes, {
$node.animate(keyframes, {
duration: time,
fill: "forwards"
}).onfinish = done;

View file

@ -13,9 +13,9 @@ export default function t(str = "", replacementString, requestedKey) {
// ).replace("{{VALUE}}", replacementString);
}
function reformat(translated, initial) {
if (initial[0] && initial[0].toLowerCase() === initial[0]) {
return translated || "";
}
return (translated[0] && translated[0].toUpperCase() + translated.substring(1)) || "";
}
// function reformat(translated, initial) {
// if (initial[0] && initial[0].toLowerCase() === initial[0]) {
// return translated || "";
// }
// return (translated[0] && translated[0].toUpperCase() + translated.substring(1)) || "";
// }

View file

@ -5,7 +5,7 @@
"description": "Frontend for Filestash",
"main": "index.js",
"scripts": {
"check": "tsc",
"check": "tsc && eslint .",
"test": "NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 npx jest"
},
"author": "Mickael Kerjean",
@ -44,5 +44,33 @@
]
}
}
},
"eslintConfig": {
"env": {
"browser": true,
"es2021": true,
"jest": true
},
"ignorePatterns": ["lib/vendor/*.js", "*.test.js"],
"extends": "standard",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"rules": {
"quotes": ["error", "double"],
"indent": ["error", 4],
"semi": ["error", "always"],
"space-before-function-paren": ["error", "never"],
"camelcase": ["off"],
"dot-notation": ["off"],
"no-case-declarations": ["off"],
"no-fallthrough": ["off"],
"prefer-regex-literals": ["off"],
"promise/param-names": ["off"],
"no-return-assign": ["off"],
"brace-style": ["off"],
"no-useless-escape": ["off"]
}
}
}

View file

@ -23,4 +23,4 @@ export default AdminOnly(WithShell(async function(render) {
));
}));
const css = await CSS(import.meta, "ctrl_about.css");
const css = await CSS(import.meta.url, "ctrl_about.css");

View file

@ -51,4 +51,4 @@ function componentStorageBackend(render) {
));
}
const css = await CSS(import.meta, "ctrl_backend.css");
const css = await CSS(import.meta.url, "ctrl_backend.css");

View file

@ -1,6 +0,0 @@
import { navigate } from "../../lib/skeleton/index.js";
import AdminOnly from "./decorator_admin_only.js";
export default AdminOnly(function() {
navigate("/admin/backend");
});

View file

@ -1,7 +1,7 @@
import { createElement, createRender } from "../../lib/skeleton/index.js";
import rxjs, { effect, stateMutation, applyMutation } from "../../lib/rx.js";
import { qs } from "../../lib/dom.js";
import { createForm, mutateForm } from "../../lib/form.js";
import { createForm } from "../../lib/form.js";
import { formTmpl } from "../../components/form.js";
import transition from "./animate.js";

View file

@ -11,7 +11,7 @@ import "../../components/icon.js";
export default function(render) {
const $form = createElement(`
<div class="component_container component_page_adminlogin">
<style>${css}</style>
<style id="adminpage::ctrl_login">.component_page_adminlogin{ visibility: hidden; } </style>
<form>
<div class="input_group">
<input type="password" name="password" placeholder="Password" class="component_input" autocomplete>
@ -41,6 +41,11 @@ export default function(render) {
rxjs.delay(300), applyMutation(qs($form, ".input_group"), "classList", "remove")
));
// feature: load CSS
effect(rxjs.from(CSS(import.meta.url, "ctrl_login.css")).pipe(
stateMutation(qs($form, `style[id="adminpage::ctrl_login"]`), "textContent"),
));
// feature: nice transition
render(transition($form, {
timeoutEnter: 250,
@ -60,5 +65,3 @@ export default function(render) {
applyMutation($form, "style", "setProperty")
));
}
const css = await CSS(import.meta, "ctrl_login.css");

View file

@ -2,7 +2,7 @@ import { createElement } from "../../lib/skeleton/index.js";
import rxjs, { effect, applyMutation } from "../../lib/rx.js";
import { qs, qsa } from "../../lib/dom.js";
import { createForm, mutateForm } from "../../lib/form.js";
import { formTmpl, format } from "../../components/form.js";
import { formTmpl } from "../../components/form.js";
import transition from "./animate.js";
import { renderLeaf } from "./helper_form.js";

View file

@ -130,17 +130,17 @@ function componentStep2(render) {
});
}
const animateOut = ($el) => {
return rxjs.pipe(
rxjs.tap(() => animate($el, {
time: 300,
keyframes: [
{ transform: "translateX(0px)", opacity: "1" },
{ transform: "translateX(-30px)", opacity: "0" }
]
})),
rxjs.delay(200)
);
};
// const animateOut = ($el) => {
// return rxjs.pipe(
// rxjs.tap(() => animate($el, {
// time: 300,
// keyframes: [
// { transform: "translateX(0px)", opacity: "1" },
// { transform: "translateX(-30px)", opacity: "0" }
// ]
// })),
// rxjs.delay(200)
// );
// };
const css = await CSS(import.meta, "ctrl_setup.css");
const css = await CSS(import.meta.url, "ctrl_setup.css");

View file

@ -12,9 +12,9 @@ export default function AdminOnly(ctrlWrapped) {
effect(isAdmin$().pipe(
rxjs.map((isAdmin) => isAdmin ? ctrlWrapped : ctrlLogin),
rxjs.tap((ctrl) => ctrl(render)),
rxjs.catchError((err) => ctrlError(err)(render)),
rxjs.tap(() => loader$.unsubscribe())
rxjs.catchError((err) => rxjs.of(ctrlError(err))),
rxjs.tap(() => loader$.unsubscribe()),
rxjs.tap((ctrl) => ctrl(render))
));
};
}

View file

@ -4,7 +4,7 @@ import { qs } from "../../lib/dom.js";
import { CSS } from "../../helpers/loader.js";
import Release from "./model_release.js";
import { get as getRelease } from "./model_release.js";
import Config from "./model_config.js";
import "../../components/icon.js";
@ -45,7 +45,7 @@ export default function(ctrl) {
</ul>
</div>
<div class="page_container scroll-y" data-bind="admin"></div>
<style>${css}</style>
<style id="adminpage::decorator_sidemenu">.component_menu_sidebar { visibility: hidden; } </style>
</div>
`);
render($page);
@ -53,8 +53,15 @@ export default function(ctrl) {
// feature: setup the childrens
ctrl(($node) => qs($page, "[data-bind=\"admin\"]").appendChild($node));
// feature: css loading
effect(rxjs.from(CSS(import.meta.url, "decorator_sidemenu.css", "index.css")).pipe(
rxjs.mergeMap((cssPromise) => cssPromise),
stateMutation(qs($page, `style[id="adminpage::decorator_sidemenu"]`), "textContent"),
));
// feature: display the release version
effect(Release.get().pipe(
effect(getRelease().pipe(
rxjs.map(({ version }) => version),
stateMutation(qs($page, "[data-bind=\"version\"]"), "textContent")
));
@ -79,5 +86,3 @@ export default function(ctrl) {
));
};
}
const css = await CSS(import.meta, "decorator_sidemenu.css", "index.css");

View file

@ -4,7 +4,7 @@ import ajax from "../../lib/ajax.js";
class AuditManager {
get(searchParams = {}) {
const p = new URLSearchParams();
Object.keys(searchParams).map((key) => {
Object.keys(searchParams).forEach((key) => {
p.set(key, searchParams[key]);
});
return ajax({

View file

@ -6,19 +6,15 @@ const release$ = ajax({
responseType: "text"
}).pipe(rxjs.shareReplay(1));
class ReleaseImpl {
get() {
return release$.pipe(
rxjs.map(({ response, responseHeaders }) => {
const a = document.createElement("html");
a.innerHTML = response;
return {
html: a.querySelector("table").outerHTML,
version: responseHeaders["x-powered-by"].trim().replace(/^Filestash\/([v\.0-9]*).*$/, "$1")
};
})
);
}
export function get() {
return release$.pipe(
rxjs.map(({ response, responseHeaders }) => {
const a = document.createElement("html");
a.innerHTML = response;
return {
html: a.querySelector("table").outerHTML,
version: responseHeaders["x-powered-by"].trim().replace(/^Filestash\/([v\.0-9]*).*$/, "$1")
};
})
);
}
export default new ReleaseImpl();

View file

@ -2,7 +2,7 @@ import { createElement } from "../../lib/skeleton/index.js";
import rxjs, { effect, applyMutation, preventDefault } from "../../lib/rx.js";
import { qs, qsa, safe } from "../../lib/dom.js";
import { animate, slideYIn } from "../../lib/animate.js";
import { createForm, mutateForm } from "../../lib/form.js";
import { createForm } from "../../lib/form.js";
import { formTmpl } from "../../components/form.js";
import { CSS } from "../../helpers/loader.js";

View file

@ -0,0 +1,6 @@
import { navigate } from "../lib/skeleton/index.js";
import AdminOnly from "./adminpage/decorator_admin_only.js";
export default AdminOnly(function() {
navigate("/admin/backend");
});

View file

@ -103,22 +103,22 @@ async function setup_device() {
});
}
async function setup_sw() {
if (!("serviceWorker" in window.navigator)) return;
// async function setup_sw() {
// if (!("serviceWorker" in window.navigator)) return;
if (window.navigator.userAgent.indexOf("Mozilla/") !== -1 &&
window.navigator.userAgent.indexOf("Firefox/") !== -1 &&
window.navigator.userAgent.indexOf("Gecko/") !== -1) {
// Firefox was acting weird with service worker so we disabled it
// see: https://github.com/mickael-kerjean/filestash/issues/255
return;
}
try {
await window.navigator.serviceWorker.register("/sw_cache.js");
} catch (err) {
report("ServiceWorker registration failed", err);
}
}
// if (window.navigator.userAgent.indexOf("Mozilla/") !== -1 &&
// window.navigator.userAgent.indexOf("Firefox/") !== -1 &&
// window.navigator.userAgent.indexOf("Gecko/") !== -1) {
// // Firefox was acting weird with service worker so we disabled it
// // see: https://github.com/mickael-kerjean/filestash/issues/255
// return;
// }
// try {
// await window.navigator.serviceWorker.register("/sw_cache.js");
// } catch (err) {
// report("ServiceWorker registration failed", err);
// }
// }
async function setup_blue_death_screen() {
window.onerror = function(msg, url, lineNo, colNo, error) {
@ -127,13 +127,13 @@ async function setup_blue_death_screen() {
};
}
async function setup_chromecast() {
if (!window.CONFIG["enable_chromecast"]) {
return Promise.resolve();
} else if (!("chrome" in window)) {
return Promise.resolve();
} else if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
return Promise.resolve();
}
return window.Chromecast.init();
}
// async function setup_chromecast() {
// if (!window.CONFIG["enable_chromecast"]) {
// return Promise.resolve();
// } else if (!("chrome" in window)) {
// return Promise.resolve();
// } else if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
// return Promise.resolve();
// }
// return window.Chromecast.init();
// }

View file

@ -4,7 +4,6 @@ import { qs } from "../lib/dom.js";
import t from "../lib/locales.js";
import { AjaxError, ApplicationError } from "../lib/error.js";
import { CSS } from "../helpers/loader.js";
import "../components/icon.js";

View file

@ -1,6 +1,5 @@
import { createElement, createRender } from "../lib/skeleton/index.js";
import rxjs, { effect, applyMutation } from "../lib/rx.js";
import { qs } from "../lib/dom.js";
import rxjs, { effect } from "../lib/rx.js";
import { CSS } from "../helpers/loader.js";
import ctrlError from "./ctrl_error.js";
@ -9,7 +8,7 @@ import componentFilesystem from "./filespage/filesystem.js";
import "../components/breadcrumb.js";
export default async function(render) {
export default function(render) {
const currentPath = location.pathname.replace(new RegExp("/files"), "");
const $page = createElement(`
<div class="component_page_filespage">
@ -21,7 +20,7 @@ export default async function(render) {
<div is="component-filesystem"></div>
</div>
</div>
<style>${await CSS(import.meta.url, "ctrl_filespage.css")}</style>
<style>${css}</style>
</div>
`);
render($page);
@ -37,3 +36,5 @@ export default async function(render) {
// feature2: render the filesystem
componentFilesystem(createRender($page.querySelector("[is=\"component-filesystem\"]")));
}
const css = await CSS(import.meta.url, "ctrl_filespage.css");

View file

@ -5,7 +5,7 @@ import { deleteSession } from "../model/session.js";
import ctrlError from "./ctrl_error.js";
import $loader from "../components/loader.js";
export default function(render) {
export default async function(render) {
render($loader);
effect(deleteSession().pipe(

View file

@ -1,11 +1,11 @@
import { createElement } from "../../lib/skeleton/index.js";
import rxjs, { applyMutation, effect } from "../../lib/rx.js";
import rxjs, { effect } from "../../lib/rx.js";
import { qs } from "../../lib/dom.js";
import { toggle as toggleLoader } from "../../components/loader.js";
import { createThing, css as cssThing } from "./thing.js";
import { getState$, handleError } from "./state.js";
import { handleError } from "./state.js";
import { ls } from "./model_files.js";
export default async function(render) {

View file

@ -11,8 +11,8 @@ export function ls() {
);
}
function repeat(element, times) {
const result = Array(times);
for (let i = 0; i < times; i++) result[i] = element;
return result;
}
// function repeat(element, times) {
// const result = Array(times);
// for (let i = 0; i < times; i++) result[i] = element;
// return result;
// }