import { createElement } from "../lib/skeleton/index.js"; import rxjs, { effect, stateMutation, applyMutation, preventDefault } from "../lib/rx.js"; import { qs } from "../lib/dom.js"; import { navigate, toHref } from "../lib/skeleton/router.js"; import { transition, zoomIn } from "../lib/animate.js"; import ajax from "../lib/ajax.js"; import { AjaxError } from "../lib/error.js"; import { basename } from "../lib/path.js"; import { loadCSS } from "../helpers/loader.js"; import { isDir } from "../pages/filespage/helper.js"; import assert from "../lib/assert.js"; import t from "../locales/index.js"; import notification from "../components/notification.js"; import ctrlError from "./ctrl_error.js"; export default function(render) { const shareID = location.pathname.replace(toHref("/s/"), ""); const state$ = new rxjs.BehaviorSubject({ step: null }); const setState = (newState) => state$.next(newState); effect(state$.asObservable().pipe(rxjs.mergeMap(({ step, ...state }) => { if (step === null) return verify(render, { shareID, setState, body: null }); else if (step === "password") return ctrlPassword(render, { shareID, setState }); else if (step === "email") return ctrlEmail(render, { shareID, setState }); else if (step === "code") return ctrlEmailCodeVerification(render, { shareID, setState }); else if (step === "done") { if (isDir(state["path"])) navigate(toHref(`/files/?share=${shareID}`)); else navigate(toHref(`/view/${encodeURIComponent(basename(state["path"]))}?share=${shareID}&nav=false`)); return rxjs.EMPTY; } else assert.fail(`unknown step: "${step}"`); }), rxjs.catchError(ctrlError()))); } function ctrlPassword(render, { shareID, setState }) { const $page = createElement(`
`); return ctrlAbstract(render, { shareID, setState, $page }); } function ctrlEmail(render, { shareID, setState }) { const $page = createElement(`
`); return ctrlAbstract(render, { shareID, setState, $page }); } function ctrlEmailCodeVerification(render, { shareID, setState }) { const $page = createElement(`
`); return ctrlAbstract(render, { shareID, setState, $page }); } function verify(_, { shareID, setState, body }) { return ajax({ method: "POST", url: `api/share/${shareID}/proof`, responseType: "json", body, }).pipe(rxjs.mergeMap(({ responseJSON }) => { const { key = "", path } = responseJSON.result; if (key === "") setState({ step: "done", path }); else setState({ step: key }); return rxjs.of(!("error" in responseJSON.result)); })); } export async function init() { return loadCSS(import.meta.url, "./ctrl_sharepage.css"); } function ctrlAbstract(render, { shareID, setState, $page }) { // feature: nice transition render(transition($page, { timeEnter: 250, enter: zoomIn(1.2), timeLeave: 0, })); effect(rxjs.fromEvent(qs($page, "form"), "submit").pipe( preventDefault(), // STEP1: loading spinner rxjs.mapTo(["name", "loading"]), applyMutation(qs($page, "component-icon"), "setAttribute"), // STEP2: attempt to login rxjs.map(() => ({ type: qs($page, "input").name, value: qs($page, "input").value })), rxjs.switchMap((creds) => verify(render, { shareID, body: creds, setState }).pipe(rxjs.catchError((err) => { if (err instanceof AjaxError) { switch (err.code()) { case "INTERNAL_SERVER_ERROR": return rxjs.throwError(err); case "FORBIDDEN": return rxjs.of(false); } } notification.error(err && err.message); return rxjs.of(false); }))), // 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($page, "component-icon"), "setAttribute"), rxjs.mapTo(""), stateMutation(qs($page, "input"), "value"), rxjs.mapTo(["error"]), applyMutation(qs($page, ".input_group"), "classList", "add"), rxjs.delay(300), applyMutation(qs($page, ".input_group"), "classList", "remove"), rxjs.catchError(ctrlError(render)), )); // feature: autofocus effect(rxjs.of(null).pipe( applyMutation(qs($page, "input"), "focus") )); // feature: vertically center the form effect(rxjs.fromEvent(window, "resize").pipe( rxjs.startWith(null), rxjs.map(() => ["margin-top", `${Math.floor(window.innerHeight / 3)}px`]), applyMutation($page, "style", "setProperty") )); return rxjs.EMPTY; }