filestash/public/pages/adminpage/ctrl_setup.js
2023-10-07 22:47:37 +11:00

173 lines
6.3 KiB
JavaScript

import { createElement, createRender, onDestroy } from "../../lib/skeleton/index.js";
import rxjs, { effect, stateMutation, applyMutation, preventDefault } from "../../lib/rx.js";
import { qs } from "../../lib/dom.js";
import { ApplicationError } from "../../lib/error.js";
import { transition, animate, zoomIn, slideXOut, slideXIn } from "../../lib/animate.js";
import bcrypt from "../../lib/vendor/bcrypt.js";
import { CSS } from "../../helpers/loader.js";
import modal from "../../components/modal.js";
import { get as getConfig } from "../../model/config.js";
import { get as getAdminConfig, save as saveConfig } from "./model_config.js";
import ctrlError from "../ctrl_error.js";
import WithShell from "./decorator_sidemenu.js";
import { cssHideMenu } from "./animate.js";
import { formObjToJSON$ } from "./helper_form.js";
import { getDeps } from "./model_setup.js";
import "../../components/icon.js";
const stepper$ = new rxjs.BehaviorSubject(1);
export default async function(render) {
const $page = createElement(`
<div class="component_setup">
<div data-bind="multistep-form"></div>
<style>${await CSS(import.meta.url, "ctrl_setup.css")}</style>
</div>
`);
render($page);
effect(stepper$.pipe(
rxjs.map((step) => {
if (step === 1) return WithShell(componentStep1);
else if (step === 2) return WithShell(componentStep2);
throw new ApplicationError("INTERNAL_ERROR", "Assumption failed");
}),
rxjs.tap((ctrl) => ctrl(createRender(qs($page, `[data-bind="multistep-form"]`)))),
rxjs.catchError((err) => ctrlError(err)(render)),
));
};
function componentStep1(render) {
const $page = createElement(`
<div id="step1">
<h4>Admin Password</h4>
<div>
<p>Create your instance admin password: </p>
<form>
<div class="input_group">
<input type="password" name="password" placeholder="Password" class="component_input" autocomplete autofocus>
<button class="transparent">
<component-icon name="arrow_right"></component-icon>
</button>
</div>
</form>
</div>
<style>${cssHideMenu}</style>
</div>
`);
render(transition($page, {
timeEnter: 250,
enter: zoomIn(1.2),
timeLeave: 0
}));
// feature: form handling
effect(rxjs.fromEvent(qs($page, "form"), "submit").pipe(
preventDefault(),
rxjs.mapTo(["name", "loading"]), applyMutation(qs($page, "component-icon"), "setAttribute"),
rxjs.map(() => qs($page, "input").value),
rxjs.combineLatestWith(getAdminConfig().pipe(rxjs.first())),
rxjs.map(([pwd, config]) => {
config["auth"]["admin"]["value"] = bcrypt.hashSync(pwd);
return config;
}),
reshapeConfigBeforeSave,
saveConfig(),
rxjs.tap(() => animate($page, { time: 200, keyframes: slideXOut(-30) })),
rxjs.delay(200),
rxjs.tap(() => stepper$.next(2))
));
}
const reshapeConfigBeforeSave = rxjs.pipe(
formObjToJSON$(),
rxjs.combineLatestWith(getConfig().pipe(rxjs.first())),
rxjs.map(([config, publicConfig]) => {
config["connections"] = publicConfig["connections"];
return config;
}),
)
function componentStep2(render) {
const deps = [];
const $page = createElement(`
<div id="step2">
<h4>
<component-icon name="arrow_left" data-bind="previous"></component-icon>
Summary
</h4>
<div data-bind="dependencies"></div>
<style>${cssHideMenu}</style>
</div>
`);
render($page);
// feature: show state of dependencies
effect(getDeps().pipe(
rxjs.mergeMap((deps) => deps),
rxjs.map(({ name_success, name_failure, pass, severe, message }) => ({
className: (severe ? "severe" : "") + " " +(pass ? "yes" : "no"),
label: pass ? name_success : name_failure,
extraLabel: pass ? "" : ": " + message,
})),
rxjs.map(({ label, className, extraLabel }) => createElement(`
<div class="component_dependency_installed ${className}">
<span>${label}</span>${extraLabel}
</div>
`)),
applyMutation(qs($page, `[data-bind="dependencies"]`), "appendChild"),
))
// feature: navigate previous step
effect(rxjs.fromEvent(qs($page, `[data-bind="previous"]`), "click").pipe(
rxjs.tap(() => stepper$.next(1))
));
// feature: reveal animation
effect(rxjs.of(null).pipe(
rxjs.tap(() => animate(qs($page, "h4"), { time: 200, keyframes: slideXIn(30) })),
rxjs.delay(200),
rxjs.mapTo([]), applyMutation(qs($page, "style"), "remove")
));
// feature: telemetry popup
const $modal = createElement(`
<div>
<p style="text-align: justify;">
Help making this software better by sending crash reports and anonymous usage statistics
</p>
<form style="font-size: 0.9em; margin-top: 10px;">
<label>
<div class="component_checkbox">
<input type="checkbox">
<span class="indicator"></span>
</div>
I accept but the data is not to be share with any third party
</label>
</form>
</div>
`);
effect(getAdminConfig().pipe(
reshapeConfigBeforeSave,
rxjs.delay(300),
rxjs.filter((config) => config["log"]["telemetry"] !== true),
rxjs.mergeMap((config) => new Promise((next) => {
modal.open($modal, {
withButtonsRight: "OK",
onQuit: () => next(config),
});
qs($modal, `[type="checkbox"]`).oninput = (e) => {
if (!e.target.checked) return;
qs(document, "component-modal > div").click();
}
})),
rxjs.filter(() => qs($modal, `[type="checkbox"]`).checked),
rxjs.map((config) => {
config["log"]["telemetry"] = true;
return config;
}),
saveConfig(),
));
}