chore (lint): linter

This commit is contained in:
Mickael Kerjean
2023-08-14 01:28:49 +10:00
parent abdc359023
commit 2e7b49660b
44 changed files with 308 additions and 296 deletions
+13 -9
View File
@@ -7,7 +7,9 @@ class ComponentBreadcrumb extends HTMLDivElement {
super();
if (new window.URL(location.href).searchParams.get("nav") === "false") return null;
const htmlLogout = isRunningFromAnIframe ? "":`
const htmlLogout = isRunningFromAnIframe
? ""
: `
<a href="/logout" data-link>
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0ODkuODg4IDQ4OS44ODgiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDQ4OS44ODggNDg5Ljg4ODsiPgogIDxwYXRoIGZpbGw9IiM2ZjZmNmYiIGQ9Ik0yNS4zODMsMjkwLjVjLTcuMi03Ny41LDI1LjktMTQ3LjcsODAuOC0xOTIuM2MyMS40LTE3LjQsNTMuNC0yLjUsNTMuNCwyNWwwLDBjMCwxMC4xLTQuOCwxOS40LTEyLjYsMjUuNyAgICBjLTM4LjksMzEuNy02Mi4zLDgxLjctNTYuNiwxMzYuOWM3LjQsNzEuOSw2NSwxMzAuMSwxMzYuOCwxMzguMWM5My43LDEwLjUsMTczLjMtNjIuOSwxNzMuMy0xNTQuNWMwLTQ4LjYtMjIuNS05Mi4xLTU3LjYtMTIwLjYgICAgYy03LjgtNi4zLTEyLjUtMTUuNi0xMi41LTI1LjZsMCwwYzAtMjcuMiwzMS41LTQyLjYsNTIuNy0yNS42YzUwLjIsNDAuNSw4Mi40LDEwMi40LDgyLjQsMTcxLjhjMCwxMjYuOS0xMDcuOCwyMjkuMi0yMzYuNywyMTkuOSAgICBDMTIyLjE4Myw0ODEuOCwzNS4yODMsMzk2LjksMjUuMzgzLDI5MC41eiBNMjQ0Ljg4MywwYy0xOCwwLTMyLjUsMTQuNi0zMi41LDMyLjV2MTQ5LjdjMCwxOCwxNC42LDMyLjUsMzIuNSwzMi41ICAgIHMzMi41LTE0LjYsMzIuNS0zMi41VjMyLjVDMjc3LjM4MywxNC42LDI2Mi44ODMsMCwyNDQuODgzLDB6IiAvPgo8L3N2Zz4K" alt="power">
</a>
@@ -17,17 +19,18 @@ class ComponentBreadcrumb extends HTMLDivElement {
const htmlPathChunks = paths.slice(0, -1).map((chunk, idx) => {
const label = idx === 0 ? "Filestash" : chunk;
const link = paths.slice(0, idx).join("/") + "/";
const minify = () => {
if (idx === 0) return false;
else if (paths.length <= (document.body.clientWidth > 800 ? 5 : 4)) return false;
else if (idx > paths.length - (document.body.clientWidth > 1000? 4 : 3)) return false;
return true;
};
// const minify = () => {
// if (idx === 0) return false;
// else if (paths.length <= (document.body.clientWidth > 800 ? 5 : 4)) return false;
// else if (idx > paths.length - (document.body.clientWidth > 1000 ? 4 : 3)) return false;
// return true;
// };
const limitSize = (word) => { // TODO
return word;
};
const isLast = idx === paths.length - 1;
if (isLast) return `
if (isLast) {
return `
<div class="component_path-element n${idx}">
<div class="li component_path-element-wrapper">
<div class="label">
@@ -36,6 +39,7 @@ class ComponentBreadcrumb extends HTMLDivElement {
</div>
</div>
</div>`;
}
return `
<div class="component_path-element n${idx}">
<div class="li component_path-element-wrapper">
@@ -48,7 +52,7 @@ class ComponentBreadcrumb extends HTMLDivElement {
</div>
</div>
</div>
</div>`
</div>`;
}).join("");
this.innerHTML = `
+50 -48
View File
@@ -8,7 +8,7 @@ export function formTmpl(options = {}) {
const {
autocomplete = true,
renderNode = null,
renderLeaf = null,
renderLeaf = null
} = options;
return {
renderNode: (opts) => {
@@ -36,7 +36,7 @@ export function formTmpl(options = {}) {
`);
},
renderInput: $renderInput({ autocomplete }),
formatLabel: format,
formatLabel: format
};
};
@@ -53,34 +53,34 @@ function $renderInput(options = {}) {
readonly = false,
path = [],
datalist = null,
options = null,
options = null
} = props;
let attr = `name="${path.join(".")}" `;
if (id) attr += `id="${id}" `;
if (placeholder) attr += `placeholder="${safe(placeholder, "\"")}" `;
if (!autocomplete) attr += `autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="off" `;
if (!autocomplete) attr += "autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"off\" spellcheck=\"off\" ";
if (required) attr += "required ";
if (readonly) attr += "readonly ";
switch (type) {
case "text": // TODO
const dataListId = gid("list_");
const $input = createElement(`
case "text": // TODO
const dataListId = gid("list_");
const $input = createElement(`
<input ${safe(attr)}
type="text"
value="${safe(value, "\"") || ""}"
class="component_input"
/>
`);
if (!datalist) return $input;
const $wrapper = window.document.createElement("span");
const $datalist = window.document.createElement("datalist")
$wrapper.appendChild($input);
$datalist.setAttribute("id", dataListId);
return $wrapper;
case "enable":
return createElement(`
if (!datalist) return $input;
const $wrapper = window.document.createElement("span");
const $datalist = window.document.createElement("datalist");
$wrapper.appendChild($input);
$datalist.setAttribute("id", dataListId);
return $wrapper;
case "enable":
return createElement(`
<div class="component_checkbox">
<input
type="checkbox"
@@ -89,8 +89,8 @@ function $renderInput(options = {}) {
<span className="indicator"></span>
</div>
`);
case "number":
return createElement(`
case "number":
return createElement(`
<input
${safe(attr)}
type="number"
@@ -98,9 +98,9 @@ function $renderInput(options = {}) {
class="component_input"
/>
`);
case "password":
// TODO: click eye
const $node = createElement(`
case "password":
// TODO: click eye
const $node = createElement(`
<div class="formbuilder_password">
<input
${safe(attr)}
@@ -111,23 +111,25 @@ function $renderInput(options = {}) {
<component-icon name="eye"></component-icon>
</div>
`);
const $icon = $node.querySelector("component-icon");
if ($icon instanceof window.HTMLElement) $icon.onclick = function(e) {
const $icon = $node.querySelector("component-icon");
if ($icon instanceof window.HTMLElement) {
$icon.onclick = function(e) {
if (!(e.target instanceof window.HTMLElement)) return;
const $input = e.target.parentElement.previousElementSibling;
if ($input.getAttribute("type") === "password") $input.setAttribute("type", "text");
else $input.setAttribute("type", "password");
};
return $node;
case "long_password":
// TODO
case "long_text":
return createElement(`
}
return $node;
case "long_password":
// TODO
case "long_text":
return createElement(`
<textarea ${safe(attr)} class="component_textarea" rows="8">
</textarea>
`);
case "bcrypt":
return createElement(`
case "bcrypt":
return createElement(`
<input
type="password"
${safe(attr)}
@@ -136,17 +138,17 @@ function $renderInput(options = {}) {
class="component_input"
/>
`);
// TODO
case "hidden":
return createElement(`
// TODO
case "hidden":
return createElement(`
<input
type="hidden"
value=${safe(value)}
name="${safe(path.join("."))}"
/>
`);
case "boolean":
return createElement(`
case "boolean":
return createElement(`
<div class="component_checkbox">
<input
${safe(attr)}
@@ -156,35 +158,35 @@ function $renderInput(options = {}) {
<span class="indicator"></span>
</div>
`);
case "select":
const renderOption = (name) => `<option name="${safe(name)}">${safe(name)}</option>`;
return createElement(`
case "select":
const renderOption = (name) => `<option name="${safe(name)}">${safe(name)}</option>`;
return createElement(`
<select class="component_select" ${safe(attr)}>
${(options || []).map(renderOption)}
</select>
`);
case "date":
return createElement(`
case "date":
return createElement(`
<input
${safe(attr)}
type="date"
class="component_input"
/>
`);
case "datetime":
return createElement(`
case "datetime":
return createElement(`
<input
${safe(attr)}
type="datetime-local"
class="component_input"
/>
`);
case "image":
return createElement(`<img id="${safe(id)}" src="${safe(value)}" />`);
case "file":
// return createElement() // TODO
default:
return createElement(`
case "image":
return createElement(`<img id="${safe(id)}" src="${safe(value)}" />`);
case "file":
// return createElement() // TODO
default:
return createElement(`
<input
value="unknown element type ${type}"
type="text"
@@ -194,7 +196,7 @@ function $renderInput(options = {}) {
/>
`);
}
}
};
}
export function format(name) {
+4 -8
View File
@@ -1,8 +1,4 @@
class Icon extends window.HTMLElement {
constructor() {
super();
}
static get observedAttributes() {
return ["name"];
}
@@ -19,16 +15,16 @@ class Icon extends window.HTMLElement {
draggable="false"
src="${img}"
alt="${name}" />`;
}
_mapOfIcon(name) {
switch(name) {
switch (name) {
case "arrow_right":
return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPHBhdGggc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MC41MzMzMzMzNiIgZD0iTTguNTkgMTYuMzRsNC41OC00LjU5LTQuNTgtNC41OUwxMCA1Ljc1bDYgNi02IDZ6IiAvPgogIDxwYXRoIGZpbGw9Im5vbmUiIGQ9Ik0wLS4yNWgyNHYyNEgweiIgLz4KPC9zdmc+Cg==";
case "arrow_left":
return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPHBhdGggc3R5bGU9ImZpbGw6IzZmNmY2ZjtmaWxsLW9wYWNpdHk6MTtzdHJva2Utd2lkdGg6MS41MTE4MTEwMjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZSIgZD0ibSAxNiw3LjE2IC00LjU4LDQuNTkgNC41OCw0LjU5IC0xLjQxLDEuNDEgLTYsLTYgNiwtNiB6Ii8+CiAgPHBhdGggZmlsbD0ibm9uZSIgZD0iTTAtLjI1aDI0djI0SDB6Ii8+Cjwvc3ZnPgo="
return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPHBhdGggc3R5bGU9ImZpbGw6IzZmNmY2ZjtmaWxsLW9wYWNpdHk6MTtzdHJva2Utd2lkdGg6MS41MTE4MTEwMjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZSIgZD0ibSAxNiw3LjE2IC00LjU4LDQuNTkgNC41OCw0LjU5IC0xLjQxLDEuNDEgLTYsLTYgNiwtNiB6Ii8+CiAgPHBhdGggZmlsbD0ibm9uZSIgZD0iTTAtLjI1aDI0djI0SDB6Ii8+Cjwvc3ZnPgo=";
case "eye":
return "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNDggNDgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8c3R5bGU+LmNscy0xe2ZpbGw6bm9uZTtzdHJva2U6IzkxOTE5MjtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLXdpZHRoOjRweDt9PC9zdHlsZT4KICAgIDxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTEsMjRhMjYuODUsMjYuODUsMCwwLDEsNDYsMCIvPgogICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMSwyNGEyNi44NSwyNi44NSwwLDAsMCw0NiwwIi8+CiAgICA8ZWxsaXBzZSBjbGFzcz0iY2xzLTEiIGN4PSIyNCIgY3k9IjI0IiByeD0iNyIgcnk9IjciLz4KPC9zdmc+Cg=="
return "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNDggNDgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8c3R5bGU+LmNscy0xe2ZpbGw6bm9uZTtzdHJva2U6IzkxOTE5MjtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLXdpZHRoOjRweDt9PC9zdHlsZT4KICAgIDxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTEsMjRhMjYuODUsMjYuODUsMCwwLDEsNDYsMCIvPgogICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMSwyNGEyNi44NSwyNi44NSwwLDAsMCw0NiwwIi8+CiAgICA8ZWxsaXBzZSBjbGFzcz0iY2xzLTEiIGN4PSIyNCIgY3k9IjI0IiByeD0iNyIgcnk9IjciLz4KPC9zdmc+Cg==";
case "loading":
return "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0nMTIwcHgnIGhlaWdodD0nMTIwcHgnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIiBjbGFzcz0idWlsLXJpbmctYWx0Ij4KICA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0ibm9uZSIgY2xhc3M9ImJrIj48L3JlY3Q+CiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iNDAiIHN0cm9rZT0ibm9uZSIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj48L2NpcmNsZT4KICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjNmY2ZjZmIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+CiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJzdHJva2UtZGFzaG9mZnNldCIgZHVyPSIycyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIGZyb209IjAiIHRvPSI1MDIiPjwvYW5pbWF0ZT4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9InN0cm9rZS1kYXNoYXJyYXkiIGR1cj0iMnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiB2YWx1ZXM9IjE1MC42IDEwMC40OzEgMjUwOzE1MC42IDEwMC40Ij48L2FuaW1hdGU+CiAgPC9jaXJjbGU+Cjwvc3ZnPgo=";
}
+2 -2
View File
@@ -40,8 +40,8 @@ class Loader extends window.HTMLElement {
window.customElements.define("component-loader", Loader);
export default createElement(`<component-loader></component-loader>`);
export default createElement("<component-loader></component-loader>");
export function toggle($node, show = false) {
if (show === true) return rxjs.tap(() => $node.appendChild(createElement(`<component-loader></component-loader>`)));
if (show === true) return rxjs.tap(() => $node.appendChild(createElement("<component-loader></component-loader>")));
else return rxjs.tap(() => $node.querySelector("component-loader")?.remove());
}
+14 -18
View File
@@ -8,17 +8,13 @@ import { CSS } from "../helpers/loader.js";
let _observables = [];
const effect = (obs) => _observables.push(obs.subscribe());
const free = () => {
for (let i=0; i<_observables.length; i++) {
for (let i = 0; i < _observables.length; i++) {
_observables[i].unsubscribe();
}
_observables = [];
}
};
export default class Modal extends HTMLElement {
constructor() {
super();
}
async trigger($node, opts = {}) {
const { onQuit, leftButton, rightButton } = opts;
const $modal = createElement(`
@@ -39,7 +35,7 @@ export default class Modal extends HTMLElement {
// feature: setup the modal body
effect(rxjs.of([$node]).pipe(
applyMutation(qs($modal, `[data-bind="body"]`), "appendChild"),
applyMutation(qs($modal, "[data-bind=\"body\"]"), "appendChild")
));
// feature: closing the modal
@@ -48,24 +44,24 @@ export default class Modal extends HTMLElement {
rxjs.filter((e) => e.target.getAttribute("id") === "modal-box")
),
rxjs.fromEvent(window, "keydown").pipe(
rxjs.filter((e) => e.keyCode === 27),
),
rxjs.filter((e) => e.keyCode === 27)
)
).pipe(
rxjs.tap(() => typeof onQuit === "function" && onQuit()),
rxjs.tap(() => animate(qs($modal, "div > div"), {
time: 200,
keyframes: [
{ opacity: 1, transform: "translateY(0)" },
{ opacity: 0, transform: "translateY(20px)" },
{ opacity: 0, transform: "translateY(20px)" }
]
})),
rxjs.delay(100),
rxjs.tap(() => animate($modal, {
time: 200,
keyframes: [ { opacity: 1 }, { opacity: 0 } ]
keyframes: [{ opacity: 1 }, { opacity: 0 }]
})),
rxjs.mapTo([]), applyMutation($modal, "remove"),
rxjs.tap(free),
rxjs.tap(free)
));
// feature: animate opening
@@ -75,17 +71,17 @@ export default class Modal extends HTMLElement {
time: 250,
keyframes: [
{ opacity: 0 },
{ opacity: 1 },
],
{ opacity: 1 }
]
})),
rxjs.delay(50),
rxjs.tap(() => animate(qs($modal, "div > div"), {
time: 200,
keyframes: [
{ opacity: 0, transform: "translateY(10px)" },
{ opacity: 1, transform: "translateY(0)" },
],
})),
{ opacity: 1, transform: "translateY(0)" }
]
}))
));
// feature: center horizontally
@@ -103,7 +99,7 @@ export default class Modal extends HTMLElement {
return size;
}),
rxjs.map((size) => ["margin", `${size}px auto 0 auto`]),
applyMutation(qs(this, ".component_modal > div"), "style", "setProperty"),
applyMutation(qs(this, ".component_modal > div"), "style", "setProperty")
));
}
}
+2 -2
View File
@@ -15,9 +15,9 @@ export async function CSS(baseURL, ...arrayOfFilenames) {
async function loadSingleCSS(baseURL, filename) {
const res = await fetch(baseURL.replace(/(.*)\/[^\/]+$/, "$1/") + filename, {
cache: "default",
cache: "default"
});
if (res.status !== 200) return `/* ERROR: ${res.status} */`;
else if (!res.headers.get("Content-Type").startsWith("text/css")) return `/* ERROR: wrong type, got "${res.headers.get("Content-Type")}"*/`
else if (!res.headers.get("Content-Type").startsWith("text/css")) return `/* ERROR: wrong type, got "${res.headers.get("Content-Type")}"*/`;
return await res.text();
}
+6 -6
View File
@@ -1,12 +1,12 @@
export function report(msg, error, link, lineNo, columnNo) {
if (window.navigator.onLine === false) return Promise.resolve();
let url = "/report?";
url += "url="+encodeURIComponent(location.href)+"&";
url += "msg="+encodeURIComponent(msg)+"&";
url += "from="+encodeURIComponent(link)+"&";
url += "from.lineNo="+lineNo+"&";
url += "from.columnNo="+columnNo;
if (error) url += "error="+encodeURIComponent(error.message)+"&";
url += "url=" + encodeURIComponent(location.href) + "&";
url += "msg=" + encodeURIComponent(msg) + "&";
url += "from=" + encodeURIComponent(link) + "&";
url += "from.lineNo=" + lineNo + "&";
url += "from.columnNo=" + columnNo;
if (error) url += "error=" + encodeURIComponent(error.message) + "&";
return fetch(url, { method: "post" }).catch(() => {});
}
+4 -4
View File
@@ -6,16 +6,16 @@ global.window = new JSDOM("<html></html>", { url: "http://example.com" }).window
global.document = global.window.document;
global.nextTick = () => new Promise((done) => process.nextTick(done));
global.location = global.window.location;
global.createRender = function () {
global.createRender = function() {
const fn = jest.fn();
fn.get = (i = 0) => fn.mock.calls[i][0]
fn.get = (i = 0) => fn.mock.calls[i][0];
fn.size = () => fn.mock.calls.length;
return fn;
}
};
global.console = {
...console,
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
error: jest.fn()
};
+17 -17
View File
@@ -7,19 +7,19 @@ export default function(opts) {
if (!opts.headers) opts.headers = {};
opts.headers["X-Requested-With"] = "XmlHttpRequest";
const isJson = opts.responseType;
return ajax({ ...opts, responseType: "text"}).pipe(
return ajax({ ...opts, responseType: "text" }).pipe(
rxjs.catchError((err) => rxjs.throwError(processError(err.xhr, err))),
rxjs.map((res) => {
let result = res.xhr.responseText;
const result = res.xhr.responseText;
if (opts.responseType === "json") {
const json = JSON.parse(result);
if (json["status"] !== "ok") {
if (json.status !== "ok") {
throw new AjaxError("Oups something went wrong", result);
}
res["responseJSON"] = json;
res.responseJSON = json;
}
return res;
}),
})
);
}
@@ -35,7 +35,7 @@ function processError(xhr, err) {
.replace(/\n{2,}/, "\n")
.trim()
.split("\n")
)).join(" "),
)).join(" ")
};
}
return message || { message: "empty response" };
@@ -46,51 +46,51 @@ function processError(xhr, err) {
if (navigator.onLine === false) {
return new AjaxError("Connection Lost", err, "NO_INTERNET");
}
switch(xhr.status) {
switch (xhr.status) {
case 500:
return new AjaxError(
message || "Oups something went wrong with our servers",
err, "INTERNAL_SERVER_ERROR",
err, "INTERNAL_SERVER_ERROR"
);
break;
case 401:
return new AjaxError(
message || "Authentication error",
err, "Unauthorized",
err, "Unauthorized"
);
case 403:
return new AjaxError(
message || "You can\'t do that",
err, "FORBIDDEN",
err, "FORBIDDEN"
);
break;
case 413:
return new AjaxError(
message || "Payload too large",
err, "PAYLOAD_TOO_LARGE",
err, "PAYLOAD_TOO_LARGE"
);
case 502:
return new AjaxError(
message || "The destination is acting weird",
err, "BAD_GATEWAY",
err, "BAD_GATEWAY"
);
case 409:
if (response["error_summary"]) { // dropbox way to say doesn't exist
if (response.error_summary) { // dropbox way to say doesn't exist
return new AjaxError(
"Doesn\'t exist",
err, "UNKNOWN_PATH",
err, "UNKNOWN_PATH"
);
}
return new AjaxError(
message || "Oups you just ran into a conflict",
err, "CONFLICT",
err, "CONFLICT"
);
case 0:
switch(xhr.responseText) {
switch (xhr.responseText) {
case "":
return new AjaxError(
"Service unavailable, if the problem persist, contact your administrator",
err, "INTERNAL_SERVER_ERROR",
err, "INTERNAL_SERVER_ERROR"
);
break;
default:
+14 -14
View File
@@ -4,15 +4,15 @@ import rxjs from "./rx.js";
export function transition($node, opts = {}) {
const {
timeEnter = 250, enter = slideXIn(5),
timeLeave = 100, leave = opacityOut(),
timeLeave = 100, leave = opacityOut()
} = opts;
animate($node, { time: timeEnter, keyframes: enter });
onDestroy(async () => await animate($node, { time: timeLeave, keyframes: leave }));
onDestroy(async() => await animate($node, { time: timeLeave, keyframes: leave }));
return $node;
}
export function animate($node, opts = {}) {
let { time = 250, keyframes = opacityIn() } = opts;
const { time = 250, keyframes = opacityIn() } = opts;
if (!$node) return Promise.resolve();
else if (typeof $node.animate !== "function") return Promise.resolve();
@@ -20,42 +20,42 @@ export function animate($node, opts = {}) {
return new Promise((done) => {
const run = $node.animate(keyframes, {
duration: time,
fill: "forwards",
fill: "forwards"
}).onfinish = done;
});
}
export const slideXIn = (dist) => ([
{ transform: `translateX(${dist}px)`, opacity: 0 },
{ transform: `translateX(0)`, opacity: 1 },
{ transform: "translateX(0)", opacity: 1 }
]);
export const slideXOut = (size) => ([
{ opacity: 1, transform: `translateX(0)`},
{ opacity: 0, transform: `translateX(${size}px)` },
{ opacity: 1, transform: "translateX(0)" },
{ opacity: 0, transform: `translateX(${size}px)` }
]);
export const opacityIn = () => ([
{ opacity: 0 },
{ opacity: 1 },
{ opacity: 1 }
]);
export const opacityOut = () => ([
{ opacity: 1 },
{ opacity: 0 },
{ opacity: 0 }
]);
export const slideYIn = (size) => ([
{ opacity: 0, transform: `translateY(${size}px)` },
{ opacity: 1, transform: `translateY(0)`},
{ opacity: 1, transform: "translateY(0)" }
]);
export const slideYOut = (size) => ([
{ opacity: 0, transform: `translateY(0px)` },
{ opacity: 1, transform: `translateY(${size}px)`},
{ opacity: 0, transform: "translateY(0px)" },
{ opacity: 1, transform: `translateY(${size}px)` }
]);
export const zoomIn = (size) => ([
{ opacity: 0, transform: `scale(${size})`},
{ opacity: 1, transform: `scale(1)`},
{ opacity: 0, transform: `scale(${size})` },
{ opacity: 1, transform: "scale(1)" }
]);
+3 -3
View File
@@ -15,8 +15,8 @@ export function safe(str, ...escapeChars) {
const $div = document.createElement("div");
escapeChars.forEach((c) => {
str = str.replaceAll(c, "\\"+c);
str = str.replaceAll(c, "\\" + c);
});
$div.textContent = str;
return $div.innerHTML;
$div.textContent = str;
return $div.innerHTML;
}
+21 -17
View File
@@ -7,8 +7,8 @@ export function mutateForm(formSpec, formState) {
const keys = inputName.split(".");
let ptr = formSpec;
while (keys.length>1) ptr = ptr[keys.shift()]
ptr[keys.shift()]["value"] = (value === "" ? null : value);
while (keys.length > 1) ptr = ptr[keys.shift()];
ptr[keys.shift()].value = (value === "" ? null : value);
});
return formSpec;
}
@@ -24,13 +24,17 @@ async function createFormNodes(node, { renderNode, renderLeaf, renderInput, path
$list.push(createElement(`<div>ERR: node[${typeof node[key]}] path[${path.join(".")}] level[${level}]</div>`));
}
// CASE 1: non leaf node
else if (typeof node[key]["type"] !== "string") {
else if (typeof node[key].type !== "string") {
const $chunk = renderNode({ level, label: key });
const $children = $chunk.querySelector(`[data-bind="children"]`) || $chunk;
const $children = $chunk.querySelector("[data-bind=\"children\"]") || $chunk;
$children.removeAttribute("data-bind");
const $nested = await createForm(node[key], {
path: path.concat(key), level: level + 1, label: key,
renderNode, renderLeaf, renderInput,
path: path.concat(key),
level: level + 1,
label: key,
renderNode,
renderLeaf,
renderInput
});
$children.appendChild($nested);
$list.push($chunk);
@@ -40,14 +44,14 @@ async function createFormNodes(node, { renderNode, renderLeaf, renderInput, path
const currentPath = path.concat(key);
const $leaf = renderLeaf({ ...node[key], path: currentPath, label: key });
const $input = await renderInput({ ...node[key], path: currentPath });
const $target = $leaf.querySelector(`[data-bind="children"]`) || $leaf;
const $target = $leaf.querySelector("[data-bind=\"children\"]") || $leaf;
// leaf node is either "classic" or can be the target of something that can be toggled
// That's how we can hide input elements conditionally for use cases like the log level
// settings that will not be visible unless log is first enabled or the advanced section
// of the login screen
const isAToggleElementItself = typeof node[key]["id"] === "string";
const canToggleOtherElements = node[key]["type"] === "enable" && node[key]["target"] && node[key]["target"].length > 0;
const isAToggleElementItself = typeof node[key].id === "string";
const canToggleOtherElements = node[key].type === "enable" && node[key].target && node[key].target.length > 0;
if (!isAToggleElementItself) {
$target.removeAttribute("data-bind");
$target.appendChild($input);
@@ -60,12 +64,12 @@ async function createFormNodes(node, { renderNode, renderLeaf, renderInput, path
$container.style.setProperty("overflow", "hidden");
for (const k of Object.keys(node)) {
if (typeof node[k] !== "object") continue;
else if (!node[k]["id"]) continue;
else if (node[key]["target"].indexOf(node[k]["id"]) === -1) continue;
else if (!node[k].id) continue;
else if (node[key].target.indexOf(node[k].id) === -1) continue;
const $kleaf = renderLeaf({ ...node[k], path: currentPath, label: k });
const $kinput = await renderInput({ ...node[k], path: currentPath });
const $ktarget = $kleaf.querySelector(`[data-bind="children"]`) || $kleaf;
const $ktarget = $kleaf.querySelector("[data-bind=\"children\"]") || $kleaf;
$ktarget.removeAttribute("data-bind");
$ktarget.appendChild($kinput);
$container.appendChild($kleaf);
@@ -73,26 +77,26 @@ async function createFormNodes(node, { renderNode, renderLeaf, renderInput, path
$list.push($container);
// initial state of the toggle
const isToggled = typeof node[key]["value"] === "boolean" ? node[key]["value"] : node[key]["default"];
const isToggled = typeof node[key].value === "boolean" ? node[key].value : node[key].default;
if (!isToggled) $container.style.setProperty("display", "none");
let clientHeight = null; // this will only be known when the dom is mounted
// setup events
$input.onchange = async (e) => {
$input.onchange = async(e) => {
$container.style.setProperty("display", "inherit");
if (clientHeight === null) clientHeight = $container.offsetHeight;
if (e.target.checked) {
animate($container, {
time: Math.max(50, Math.min(clientHeight, 150)),
keyframes: [{ height:0 }, {height:`${clientHeight}px`}],
keyframes: [{ height: 0 }, { height: `${clientHeight}px` }]
});
} else {
animate($container, {
time: Math.max(25, Math.min(clientHeight, 75)),
keyframes: [ {height: `${clientHeight}px`}, {height: 0}],
keyframes: [{ height: `${clientHeight}px` }, { height: 0 }]
});
}
}
};
}
}
}
+5 -5
View File
@@ -7,16 +7,16 @@ export { onDestroy } from "./lifecycle.js";
let pageLoader;
export default async function($root, routes, opts = {}) {
window.addEventListener("pagechange", async () => {
window.addEventListener("pagechange", async() => {
try {
const route = currentRoute(routes, "");
const [ctrl] = await Promise.all([
load(route, { ...opts, $root }),
$root.cleanup(),
$root.cleanup()
]);
if (typeof ctrl !== "function") throw new Error(`Unknown route for ${route}`);
pageLoader = ctrl(createRender($root));
} catch(err) {
} catch (err) {
window.onerror && window.onerror(err.message);
}
});
@@ -50,12 +50,12 @@ async function load(route, opts) {
/**
* @param {string} str
* @returns {HTMLElement}
* @return {HTMLElement}
*/
export function createElement(str) {
const $n = window.document.createElement("div");
$n.innerHTML = str;
if (!($n.firstElementChild instanceof window.HTMLElement)) throw new Error(`createElement - unexpected type`);
if (!($n.firstElementChild instanceof window.HTMLElement)) throw new Error("createElement - unexpected type");
return $n.firstElementChild;
}
+1 -1
View File
@@ -2,7 +2,7 @@ let _cleanup = [];
export async function init($root) {
$root.cleanup = () => {
const fns = _cleanup.map((fn) => fn($root))
const fns = _cleanup.map((fn) => fn($root));
_cleanup = [];
return Promise.all(fns);
};
+1 -1
View File
@@ -29,7 +29,7 @@ export function currentRoute(r, notFoundRoute) {
return r[notFoundRoute];
}
function _getHref ($node, $root) {
function _getHref($node, $root) {
if ($node.matches("[data-link]")) return $node.getAttribute("href");
if (!$node.parentElement || $node.isSameNode($root)) return null;
return _getHref($node.parentElement, $root);
+3 -3
View File
@@ -13,15 +13,15 @@ export function getSession() {
return ajax({
url: "/api/session",
method: "GET",
responseType: "json",
responseType: "json"
}).pipe(
rxjs.map(({ responseJSON }) => responseJSON.result),
rxjs.map(({ responseJSON }) => responseJSON.result)
);
}
export function deleteSession() {
return ajax({
url: "/api/session",
method: "DELETE",
method: "DELETE"
});
}
+2 -2
View File
@@ -3,8 +3,8 @@ import { transition, slideYIn } from "../../lib/animate.js";
export default function($node) {
return transition($node, {
timeEnter: 100,
enter: slideYIn(3),
enter: slideYIn(3)
});
}
export const cssHideMenu = `.component_menu_sidebar{transform: translateX(-300px)}`;
export const cssHideMenu = ".component_menu_sidebar{transform: translateX(-300px)}";
+2 -2
View File
@@ -1,5 +1,5 @@
import { createElement } from "../../lib/skeleton/index.js";
import rxjs, { effect, stateMutation } from "../../lib/rx.js"
import rxjs, { effect, stateMutation } from "../../lib/rx.js";
import { qs } from "../../lib/dom.js";
import { CSS } from "../../helpers/loader.js";
import transition from "./animate.js";
@@ -19,7 +19,7 @@ export default AdminOnly(WithShell(async function(render) {
effect(Release.get().pipe(
rxjs.map(({ html }) => html),
stateMutation(qs($page, `[data-bind="about"]`), "innerHTML"),
stateMutation(qs($page, "[data-bind=\"about\"]"), "innerHTML")
));
}));
+2 -3
View File
@@ -22,7 +22,7 @@ export default AdminOnly(WithShell(function(render) {
`);
render(transition($page));
componentStorageBackend(createRender(qs($page, `[data-bind="backend"]`)));
componentStorageBackend(createRender(qs($page, "[data-bind=\"backend\"]")));
}));
function componentStorageBackend(render) {
@@ -47,9 +47,8 @@ function componentStorageBackend(render) {
</div>
</div>
`)]),
applyMutation(qs($page, `[data-bind="backend-available"]`), "appendChild"),
applyMutation(qs($page, "[data-bind=\"backend-available\"]"), "appendChild")
));
}
const css = await CSS(import.meta, "ctrl_backend.css");
+6 -6
View File
@@ -33,7 +33,7 @@ function Page(render) {
export default AdminOnly(WithShell(Page));
function componentLogForm(render) {
const $form = createElement(`<form></form>`);
const $form = createElement("<form></form>");
render($form);
@@ -43,14 +43,14 @@ function componentLogForm(render) {
rxjs.map((formSpec) => createForm(formSpec, formTmpl({ renderLeaf }))),
rxjs.mergeMap((promise) => rxjs.from(promise)),
rxjs.map(($form) => [$form]),
applyMutation($form, "appendChild"),
applyMutation($form, "appendChild")
));
// TODO feature2: response to form change
}
function componentLogViewer(render) {
const $page = createElement(`<pre>t</pre>`);
const $page = createElement("<pre>t</pre>");
render($page);
effect(Log.get().pipe(
@@ -69,16 +69,16 @@ function componentAuditor(render) {
// setup the form
effect(Audit.get().pipe(
rxjs.map(({ form }) => form),
rxjs.map(({ form }) => form),
rxjs.map((formSpec) => createForm(formSpec, formTmpl())),
rxjs.mergeMap((promise) => rxjs.from(promise)),
rxjs.map(($form) => [$form]),
applyMutation(qs($page, "form"), "appendChild"),
applyMutation(qs($page, "form"), "appendChild")
));
// setup the result
effect(Audit.get().pipe(
rxjs.map(({ render }) => render),
stateMutation(qs($page, `[data-bind="auditor"]`), "innerHTML"),
stateMutation(qs($page, "[data-bind=\"auditor\"]"), "innerHTML")
));
}
+7 -6
View File
@@ -30,21 +30,22 @@ export default function(render) {
rxjs.mapTo(["name", "loading"]),
applyMutation(qs($form, "component-icon"), "setAttribute"),
// STEP2: attempt to login
rxjs.map(() => ({ password: qs($form, `[name="password"]`).value })),
rxjs.map(() => ({ password: qs($form, "[name=\"password\"]").value })),
authenticate$(),
// STEP3: update the UI when authentication fails, happy path is handle at the middleware
// level one layer above as the login ctrl has no idea what to show after login
rxjs.filter((ok) => !ok),
rxjs.mapTo(["name", "arrow_right"]), applyMutation(qs($form, "component-icon"), "setAttribute"),
rxjs.mapTo(""), stateMutation(qs($form, `[name="password"]`), "value"),
rxjs.mapTo(""), stateMutation(qs($form, "[name=\"password\"]"), "value"),
rxjs.mapTo(["error"]), applyMutation(qs($form, ".input_group"), "classList", "add"),
rxjs.delay(300), applyMutation(qs($form, ".input_group"), "classList", "remove"),
rxjs.delay(300), applyMutation(qs($form, ".input_group"), "classList", "remove")
));
// feature: nice transition
render(transition($form, {
timeoutEnter: 250, enter: zoomIn(1.2),
timeoutLeave: 0,
timeoutEnter: 250,
enter: zoomIn(1.2),
timeoutLeave: 0
}));
// feature: autofocus
@@ -56,7 +57,7 @@ export default function(render) {
effect(rxjs.fromEvent(window, "resize").pipe(
rxjs.startWith(null),
rxjs.map(() => ["margin-top", `${Math.floor(window.innerHeight / 3)}px`]),
applyMutation($form, "style", "setProperty"),
applyMutation($form, "style", "setProperty")
));
}
+9 -8
View File
@@ -23,7 +23,7 @@ export default AdminOnly(WithShell(function(render) {
delete res.constant;
delete res.middleware;
return res;
}),
})
);
const tmpl = formTmpl({
@@ -35,28 +35,29 @@ export default AdminOnly(WithShell(function(render) {
<div data-bind="children"></div>
</div>
`);
}, renderLeaf,
})
},
renderLeaf
});
effect(config$.pipe(
rxjs.mergeMap((formSpec) => createForm(formSpec, tmpl)),
rxjs.map(($form) => [$form]),
applyMutation(qs($container, `[data-bind="form"]`), "appendChild"),
applyMutation(qs($container, "[data-bind=\"form\"]"), "appendChild")
));
effect(config$.pipe(
rxjs.mergeMap(() => qsa($container, `[data-bind="form"] [name]`)),
rxjs.mergeMap(() => qsa($container, "[data-bind=\"form\"] [name]")),
rxjs.mergeMap(($el) => rxjs.fromEvent($el, "input")),
rxjs.map((e) => ({
name: e.target.getAttribute("name"),
value: e.target.value,
value: e.target.value
})),
rxjs.scan((store, keyValue) => {
store[keyValue.name] = keyValue.value;
return store;
}, {}),
}, {})
).pipe(
rxjs.withLatestFrom(config$),
rxjs.map(([formState, formSpec]) => mutateForm(formSpec, formState)),
Config.save(),
Config.save()
));
}));
+22 -18
View File
@@ -26,14 +26,14 @@ export default function(render) {
effect(stepper$.pipe(
rxjs.map((step) => {
switch(step) {
switch (step) {
case 1: return WithShell(componentStep1);
case 2: return WithShell(componentStep2);
default: throw new ApplicationError("INTERNAL_ERROR", "Assumption failed");
}
}),
rxjs.tap((ctrl) => ctrl(createRender(qs($page, `[data-bind="multistep-form"]`)))),
rxjs.catchError((err) => ctrlError(err)(render)),
rxjs.tap((ctrl) => ctrl(createRender(qs($page, "[data-bind=\"multistep-form\"]")))),
rxjs.catchError((err) => ctrlError(err)(render))
));
};
@@ -54,8 +54,9 @@ function componentStep1(render) {
</div>
`);
render(transition($page, {
timeEnter: 250, enter: zoomIn(1.2),
timeLeave: 0,
timeEnter: 250,
enter: zoomIn(1.2),
timeLeave: 0
}));
// feature: form handling
@@ -66,17 +67,17 @@ function componentStep1(render) {
rxjs.delay(1000),
rxjs.tap(() => animate($page, { time: 200, keyframes: slideXOut(-30) })),
rxjs.delay(200),
rxjs.tap(() => stepper$.next(2)),
rxjs.tap(() => stepper$.next(2))
));
// feature: hide side menu to remove distractions
effect(rxjs.of(cssHideMenu).pipe(
stateMutation(qs($page, "style"), "textContent"),
stateMutation(qs($page, "style"), "textContent")
));
// feature: autofocus
effect(rxjs.of([]).pipe(
applyMutation(qs($page, "input"), "focus"),
applyMutation(qs($page, "input"), "focus")
));
}
@@ -94,8 +95,8 @@ function componentStep2(render) {
render($page);
// feature: navigate previous step
effect(rxjs.fromEvent(qs($page, `[data-bind="previous"]`), "click").pipe(
rxjs.tap(() => stepper$.next(1)),
effect(rxjs.fromEvent(qs($page, "[data-bind=\"previous\"]"), "click").pipe(
rxjs.tap(() => stepper$.next(1))
));
// feature: reveal animation
@@ -103,7 +104,7 @@ function componentStep2(render) {
stateMutation(qs($page, "style"), "textContent"),
rxjs.tap(() => animate(qs($page, "h4"), { time: 200, keyframes: slideXIn(30) })),
rxjs.delay(200),
rxjs.mapTo([]), applyMutation(qs($page, "style"), "remove"),
rxjs.mapTo([]), applyMutation(qs($page, "style"), "remove")
));
// feature: telemetry popup
@@ -123,7 +124,7 @@ function componentStep2(render) {
`);
return new Promise((done) => {
modal.alert($node, {
onQuit: done,
onQuit: done
});
});
});
@@ -131,12 +132,15 @@ 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),
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");
@@ -7,14 +7,14 @@ import { isAdmin$ } from "./model_admin_session.js";
export default function AdminOnly(ctrlWrapped) {
return (render) => {
const loader$ = rxjs.timer(1000).subscribe(() => render(createElement(`<div>loading</div>`)));
const loader$ = rxjs.timer(1000).subscribe(() => render(createElement("<div>loading</div>")));
onDestroy(() => loader$.unsubscribe());
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.tap(() => loader$.unsubscribe())
));
}
};
}
+7 -7
View File
@@ -51,23 +51,23 @@ export default function(ctrl) {
render($page);
// feature: setup the childrens
ctrl(($node) => qs($page, `[data-bind="admin"]`).appendChild($node));
ctrl(($node) => qs($page, "[data-bind=\"admin\"]").appendChild($node));
// feature: display the release version
effect(Release.get().pipe(
rxjs.map(({ version }) => version),
stateMutation(qs($page, `[data-bind="version"]`), "textContent"),
stateMutation(qs($page, "[data-bind=\"version\"]"), "textContent")
));
// feature: logo serving as loading indicator
effect(Config.isSaving().pipe(
rxjs.startWith(false),
rxjs.map((isLoading) => isLoading ?
`<component-icon name="loading"></component-icon>` :
`<svg class="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
rxjs.map((isLoading) => isLoading
? "<component-icon name=\"loading\"></component-icon>"
: `<svg class="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M330 202a81 79 0 00-162 0 81 79 0 000 158 81 79 0 000-158m81 79a81 79 0 1181 79H168" fill="none" stroke="currentColor" stroke-width="35px"/>
</svg>`),
stateMutation(qs($page, `[data-bind="logo"]`), "innerHTML"),
stateMutation(qs($page, "[data-bind=\"logo\"]"), "innerHTML")
));
// feature: currently active menu link
@@ -75,7 +75,7 @@ export default function(ctrl) {
rxjs.mergeMap(($els) => $els),
rxjs.filter(($el) => location.pathname.endsWith($el.getAttribute("href"))),
rxjs.tap(($el) => $el.classList.add("active")),
rxjs.tap(($el) => $el.removeAttribute("href")),
rxjs.tap(($el) => $el.removeAttribute("href"))
));
};
}
+1 -1
View File
@@ -16,5 +16,5 @@ export function renderLeaf({ format, type, label, description }) {
</div>
</div>
</label>
`);
`);
}
@@ -10,8 +10,8 @@ const adminSession$ = rxjs.merge(
rxjs.mergeMap(() => ajax({ url: "/admin/api/session", responseType: "json" })),
rxjs.map(({ responseJSON }) => responseJSON.result),
rxjs.distinctUntilChanged(),
rxjs.shareReplay(1),
),
rxjs.shareReplay(1)
)
);
export function isAdmin$() {
@@ -22,11 +22,13 @@ export function authenticate$() {
return rxjs.pipe(
rxjs.mergeMap((body) => ajax({
url: "/admin/api/session",
method: "POST", body, responseType: "json",
method: "POST",
body,
responseType: "json"
}).pipe(
rxjs.mapTo(true),
rxjs.catchError(() => rxjs.of(false)),
rxjs.tap((ok) => ok && sessionSubject$.next(ok))
)),
))
);
}
+2 -2
View File
@@ -9,9 +9,9 @@ class AuditManager {
});
return ajax({
url: "/admin/api/audit?" + p.toString(),
responseType: "json",
responseType: "json"
}).pipe(
rxjs.map((res) => res.responseJSON.result),
rxjs.map((res) => res.responseJSON.result)
);
}
}
+4 -3
View File
@@ -14,9 +14,10 @@ class ConfigManager {
return ajax({
url: "/admin/api/config",
withCredentials: true,
method: "GET", responseType: "json",
method: "GET",
responseType: "json"
}).pipe(
rxjs.map((res) => res.responseJSON.result),
rxjs.map((res) => res.responseJSON.result)
);
}
@@ -25,7 +26,7 @@ class ConfigManager {
rxjs.tap(() => this.isSaving$.next(true)),
rxjs.debounceTime(1000),
rxjs.delay(1000),
rxjs.tap(() => this.isSaving$.next(false)),
rxjs.tap(() => this.isSaving$.next(false))
);
}
}
+2 -2
View File
@@ -5,9 +5,9 @@ class LogManager {
get(maxSize = 1000) {
return ajax({
url: `/admin/api/logs?maxSize=${maxSize}`,
responseType: "text",
responseType: "text"
}).pipe(
rxjs.map(({ response }) => response),
rxjs.map(({ response }) => response)
// rxjs.repeat({ delay: 10000 }),
);
}
+4 -4
View File
@@ -3,20 +3,20 @@ import ajax from "../../lib/ajax.js";
const release$ = ajax({
url: "/about",
responseType: "text",
responseType: "text"
}).pipe(rxjs.shareReplay(1));
class ReleaseImpl {
get() {
return release$.pipe(
rxjs.map(({ response, responseHeaders }) => {
const a = document.createElement("html")
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"),
version: responseHeaders["x-powered-by"].trim().replace(/^Filestash\/([v\.0-9]*).*$/, "$1")
};
}),
})
);
}
}
+17 -15
View File
@@ -27,47 +27,49 @@ export default async function(render) {
effect(config$.pipe(
// dom creation
rxjs.map(({ connections }) => connections),
rxjs.mergeMap((conns) => conns.map((conn, i) => ({...conn, n: i }))),
rxjs.mergeMap((conns) => conns.map((conn, i) => ({ ...conn, n: i }))),
rxjs.map(({ label, n }) => createElement(`<button class="" data-current="${n}">${safe(label)}</button>`)),
rxjs.map(($button) => [$button]), applyMutation(qs($page, `[role="navigation"]`), "appendChild"),
rxjs.map(($button) => [$button]), applyMutation(qs($page, "[role=\"navigation\"]"), "appendChild"),
// initialise selection
rxjs.toArray(),
rxjs.map((conns) => Math.max(0, conns.length / 2 - 1)),
rxjs.tap((current) => setCurrentBackend(current)),
rxjs.tap((current) => setCurrentBackend(current))
));
// feature2: interaction with the buttons
effect(getCurrentBackend().pipe(
rxjs.first(),
rxjs.map(() => qsa($page, `[role="navigation"] button`)),
rxjs.map(() => qsa($page, "[role=\"navigation\"] button")),
rxjs.mergeMap((els) => els),
rxjs.mergeMap(($button) => rxjs.fromEvent($button, "click")),
rxjs.map((e) => parseInt(e.target.getAttribute("data-current"))),
rxjs.tap((current) => setCurrentBackend(current)),
rxjs.tap((current) => setCurrentBackend(current))
));
// feature3: highlight the selected button
effect(getCurrentBackend().pipe(
rxjs.map((n) => ({ $buttons: qsa($page, `[role="navigation"] button`), n })),
rxjs.map((n) => ({ $buttons: qsa($page, "[role=\"navigation\"] button"), n })),
rxjs.tap(({ $buttons }) => $buttons.forEach(($node) => $node.classList.remove("active", "primary"))),
rxjs.map(({ $buttons, n }) => $buttons[n]),
rxjs.filter(($button) => !!$button),
rxjs.tap(($button) => $button.classList.add("active", "primary")),
rxjs.tap(($button) => $button.classList.add("active", "primary"))
));
// feature4: insert all the connection form
const tmpl = formTmpl({
renderNode: () => createElement(`<div></div>`),
renderNode: () => createElement("<div></div>"),
renderLeaf: ({ label, type, format }) => {
if (type === "enable") return createElement(`
if (type === "enable") {
return createElement(`
<label class="advanced">
<span data-bind="children"></span>
${label}
</label>
`);
return createElement(`<label></label>`);
}
return createElement("<label></label>");
}
})
});
effect(rxjs.combineLatest(
config$.pipe(
rxjs.first(),
@@ -75,14 +77,14 @@ export default async function(render) {
rxjs.mergeMap(({ type }) => backend$.pipe(rxjs.map((spec) => spec[type]))),
rxjs.mergeMap((formSpec) => createForm(formSpec, tmpl)),
rxjs.toArray(),
rxjs.share(),
rxjs.share()
),
getCurrentBackend(),
getCurrentBackend()
).pipe(
rxjs.map(([$forms, n]) => [$forms[n]]),
applyMutation(qs($page, "form"), "replaceChildren"),
rxjs.tap(() => animate($page.querySelector("form > div"), { time: 200, keyframes: slideYIn(-2) })),
rxjs.tap(() => qs($page, "form").appendChild(createElement(`<button class="emphasis full-width">CONNECT</button>`))),
rxjs.tap(() => qs($page, "form").appendChild(createElement("<button class=\"emphasis full-width\">CONNECT</button>")))
));
// feature5: form submission
@@ -96,7 +98,7 @@ export default async function(render) {
}
return json;
}),
rxjs.mergeMap((creds) => createSession(creds)),
rxjs.mergeMap((creds) => createSession(creds))
));
render($page);
+1 -1
View File
@@ -3,5 +3,5 @@ import ajax from "../../lib/ajax.js";
export default ajax({
url: "/api/backend",
responseType: "json",
responseType: "json"
}).pipe(rxjs.map(({ responseJSON }) => responseJSON.result));
+2 -2
View File
@@ -3,8 +3,8 @@ import ajax from "../../lib/ajax.js";
export default ajax({
url: "/api/config",
responseType: "json",
responseType: "json"
}).pipe(
rxjs.map(({ responseJSON }) => responseJSON.result),
rxjs.share(),
rxjs.share()
);
+16 -16
View File
@@ -11,7 +11,7 @@ export default async function main() {
// setup_cache(), // TODO: dependency on session
setup_device(),
// setup_sw(), // TODO
setup_blue_death_screen(),
setup_blue_death_screen()
]);
// await Config.refresh()
@@ -20,14 +20,14 @@ export default async function main() {
]);
window.dispatchEvent(new window.Event("pagechange"));
} catch(err) {
} catch (err) {
console.error(err);
const msg = window.navigator.onLine === false ? "OFFLINE" : (err.message || "CAN'T LOAD");
report(msg + " - " + (err && err.message), location.href);
$error(msg);
}
}
main()
main();
function $error(msg) {
const $code = document.createElement("code");
@@ -37,7 +37,7 @@ function $error(msg) {
$code.style.padding = "0 10% 0 10%";
$code.textContent = msg;
let $img = document.createElement("img");
const $img = document.createElement("img");
$img.setAttribute("src", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABQAQMAAADcLOLWAAAABlBMVEUAAABTU1OoaSf/AAAAAXRSTlMAQObYZgAAAFlJREFUeF69zrERgCAQBdElMqQEOtHSuNIohRIMjfjO6DDmB7jZy5YgySQVYDIakIHD1kBPC9Bra5G2Ans0N7iAcOLF+EHvXySpjSBWCDI/3nIdBDihr8m4AcKdbn96jpAHAAAAAElFTkSuQmCC");
$img.style.display = "block";
$img.style.padding = "20vh 10% 0 10%";
@@ -47,11 +47,11 @@ function $error(msg) {
document.body.appendChild($code);
}
////////////////////////////////////////////
/// /////////////////////////////////////////
// boot steps helpers
function setup_translation() {
let selectedLanguage = "en";
switch(window.navigator.language) {
switch (window.navigator.language) {
case "zh-TW":
selectedLanguage = "zh_tw";
break;
@@ -61,28 +61,28 @@ function setup_translation() {
"az", "be", "bg", "ca", "cs", "da", "de", "el", "es", "et",
"eu", "fi", "fr", "gl", "hr", "hu", "id", "is", "it", "ja",
"ka", "ko", "lt", "lv", "mn", "nb", "nl", "pl", "pt", "ro",
"ru", "sk", "sl", "sr", "sv", "th", "tr", "uk", "vi", "zh",
"ru", "sk", "sl", "sr", "sv", "th", "tr", "uk", "vi", "zh"
].indexOf(window.navigator.language.split("-")[0]);
if(idx !== -1) {
if (idx !== -1) {
selectedLanguage = userLanguage;
}
}
if (selectedLanguage === "en") {
return
return;
}
return ajax({
url: "/assets/locales/"+selectedLanguage+".json",
responseType: "json",
url: "/assets/locales/" + selectedLanguage + ".json",
responseType: "json"
}).pipe(
rxjs.tap(({ responseHeaders, response }) => {
const contentType = responseHeaders["content-type"].trim();
if (contentType !== "application/json") {
report(`ctrl_boot.js - wrong content type '${contentType}'`);
return
return;
}
window.LNG = response;
}),
})
).toPromise();
}
@@ -115,8 +115,8 @@ async function setup_sw() {
}
try {
await window.navigator.serviceWorker.register("/sw_cache.js");
} catch(err) {
report("ServiceWorker registration failed", err)
} catch (err) {
report("ServiceWorker registration failed", err);
}
}
@@ -132,7 +132,7 @@ async function setup_chromecast() {
return Promise.resolve();
} else if (!("chrome" in window)) {
return Promise.resolve();
} else if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
} else if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
return Promise.resolve();
}
return window.Chromecast.init();
+4 -4
View File
@@ -23,20 +23,20 @@ export default function(render) {
render($page);
// feature1: connection form
ctrlForm(createRender(qs($page, `[data-bind="component_form"]`)));
ctrlForm(createRender(qs($page, "[data-bind=\"component_form\"]")));
// feature2: forkme button
effect(config$.pipe(
rxjs.filter(({ fork_button }) => fork_button !== false),
rxjs.mapTo([$fork]),
applyMutation(qs($page, `[data-bind="component_forkme"]`), "appendChild"),
applyMutation(qs($page, "[data-bind=\"component_forkme\"]"), "appendChild")
));
// feature3: poweredby button
effect(config$.pipe(
rxjs.filter(({ fork_button }) => fork_button !== false),
rxjs.mapTo([$poweredby]),
applyMutation(qs($page, `[data-bind="component_poweredby"]`), "appendChild"),
applyMutation(qs($page, "[data-bind=\"component_poweredby\"]"), "appendChild")
));
// feature4: center the form
@@ -53,7 +53,7 @@ export default function(render) {
return size;
}),
rxjs.map((size) => ["padding-top", `${size}px`]),
applyMutation(qs($page, `[data-bind="centerthis"]`), "style", "setProperty"),
applyMutation(qs($page, "[data-bind=\"centerthis\"]"), "style", "setProperty")
));
}
+5 -6
View File
@@ -39,18 +39,18 @@ export default function(err) {
// ));
// feature: show error details
effect(rxjs.fromEvent(qs($page, `button[data-bind="details"]`), "click").pipe(
effect(rxjs.fromEvent(qs($page, "button[data-bind=\"details\"]"), "click").pipe(
rxjs.mapTo(["hidden"]),
applyMutation(qs($page, "pre"), "classList", "toggle"),
applyMutation(qs($page, "pre"), "classList", "toggle")
));
// feature: refresh button
effect(rxjs.fromEvent(qs($page, `button[data-bind="refresh"]`), "click").pipe(
rxjs.tap(() => location.reload()),
effect(rxjs.fromEvent(qs($page, "button[data-bind=\"refresh\"]"), "click").pipe(
rxjs.tap(() => location.reload())
));
return rxjs.of(err);
}
};
}
function processError(err) {
@@ -68,7 +68,6 @@ trace: ${err.stack}`;
type: ${err.type()}
debug: ${err.debug()}
trace: ${err.stack}`;
} else {
msg = t("Internal Error");
trace = `
+2 -2
View File
@@ -31,9 +31,9 @@ export default async function(render) {
rxjs.map(({ error }) => error),
rxjs.filter((error) => !!error),
rxjs.map(ctrlError),
rxjs.tap((fn) => fn(render)),
rxjs.tap((fn) => fn(render))
));
// feature2: render the filesystem
componentFilesystem(createRender($page.querySelector(`[is="component-filesystem"]`)));
componentFilesystem(createRender($page.querySelector("[is=\"component-filesystem\"]")));
}
+3 -3
View File
@@ -12,18 +12,18 @@ export default function(render) {
if (GET.get("error")) {
ctrlError(new ApplicationError(
GET.get("error"),
GET.get("trace") || "server error from URL",
GET.get("trace") || "server error from URL"
))(render);
return;
}
render(createElement(`<component-loader></component-loader>`));
render(createElement("<component-loader></component-loader>"));
effect(getSession().pipe(
rxjs.tap(({ is_authenticated, home = "/" }) => {
if (is_authenticated !== true) return navigate("/login");
return navigate(`/files${home}`);
}),
rxjs.catchError(() => navigate("/login")),
rxjs.catchError(() => navigate("/login"))
));
};
+1 -1
View File
@@ -10,6 +10,6 @@ export default function(render) {
effect(deleteSession().pipe(
rxjs.tap(() => navigate("/")),
rxjs.catchError(ctrlError(render)),
rxjs.catchError(ctrlError(render))
));
}
+4 -4
View File
@@ -18,22 +18,22 @@ export default async function(render) {
render($page);
// feature1: files on the current path
const path = location.pathname.replace(new RegExp("^/files"), "")
const path = location.pathname.replace(new RegExp("^/files"), "");
effect(rxjs.of(path).pipe(
toggleLoader($page, true),
ls(), // TODO: ls_from_cache then ls_from_server
toggleLoader($page, false),
rxjs.tap(({ files }) => {
const $fs = document.createDocumentFragment();
for(let i=0; i<files.length && i < 100; i++) {
for (let i = 0; i < files.length && i < 100; i++) {
// $node.querySelector(".component_filename .file-details > span").textContent = files[i]["name"];
// if (files[i]["type"] === "file") $node.querySelector("a").setAttribute("href", "/view" + path + files[i]["name"]);
// else $node.querySelector("a").setAttribute("href", "/files" + path + files[i]["name"] + "/");
$fs.appendChild(createThing({ label: files[i]["name"], link: "/test/" }));
$fs.appendChild(createThing({ label: files[i].name, link: "/test/" }));
}
qs($page, ".list").appendChild($fs);
}),
handleError(),
handleError()
));
// feature2: fs in "search" mode
+4 -5
View File
@@ -5,15 +5,14 @@ export function ls() {
return rxjs.pipe(
rxjs.mergeMap((path) => ajax({
url: `/api/files/ls?path=${path}`,
responseType: "json",
responseType: "json"
})),
rxjs.map(({ responseJSON }) => ({ files: responseJSON.results })),
rxjs.map(({ responseJSON }) => ({ files: responseJSON.results }))
);
}
function repeat(element, times) {
var result = Array(times);
for(var i=0;i<times;i++) result[i] = element;
const result = Array(times);
for (let i = 0; i < times; i++) result[i] = element;
return result;
}
+8 -6
View File
@@ -7,7 +7,7 @@ const state$ = new rxjs.BehaviorSubject({
acl: {},
path: "/",
mutation: {},
error: null,
error: null
});
export const getState$ = () => state$.asObservable();
@@ -18,10 +18,12 @@ export const onNewFile = () => {
export const handleError = () => {
return rxjs.catchError((err) => {
if (err) state$.next({
...state$.value,
error: err,
});
if (err) {
state$.next({
...state$.value,
error: err
});
}
return rxjs.empty();
});
};
@@ -31,5 +33,5 @@ export const onNewDirectory = () => {
};
export const onSearch = () => {
console.log("SEARCH")
console.log("SEARCH");
};
+2 -2
View File
@@ -39,8 +39,8 @@ export function createThing({
}) {
const $thing = $tmpl.cloneNode(true);
if ($thing instanceof HTMLElement) {
const $label = $thing.querySelector(".component_filename .file-details > span")
if($label instanceof HTMLElement) $label.textContent = label;
const $label = $thing.querySelector(".component_filename .file-details > span");
if ($label instanceof HTMLElement) $label.textContent = label;
// if (files[i]["type"] === "file") $node.querySelector("a").setAttribute("href", "/view" + path + files[i]["name"]);
// else $node.querySelector("a").setAttribute("href", "/files" + path + files[i]["name"] + "/");
}