mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2024-04-21 12:32:08 +00:00
chore (lint): linter
This commit is contained in:
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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=";
|
||||
}
|
||||
|
||||
@@ -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
@@ -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")
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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(() => {});
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 }]
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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)}";
|
||||
|
||||
@@ -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")
|
||||
));
|
||||
}));
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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")
|
||||
));
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
));
|
||||
}));
|
||||
|
||||
@@ -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())
|
||||
));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
)),
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
};
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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")
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = `
|
||||
|
||||
@@ -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\"]")));
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
));
|
||||
};
|
||||
|
||||
@@ -10,6 +10,6 @@ export default function(render) {
|
||||
|
||||
effect(deleteSession().pipe(
|
||||
rxjs.tap(() => navigate("/")),
|
||||
rxjs.catchError(ctrlError(render)),
|
||||
rxjs.catchError(ctrlError(render))
|
||||
));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
};
|
||||
|
||||
@@ -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"] + "/");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user