diff --git a/public/index.html b/public/index.html index 29a124dc..358423a7 100644 --- a/public/index.html +++ b/public/index.html @@ -25,7 +25,7 @@ "/logout": "/pages/ctrl_logout.js", - "/login": "/pages/connectpage/connectpage.js", + "/login": "/pages/connectpage/ctrl_connectpage.js", //"/": "/pages/home/index.js", "": "/pages/ctrl_notfound.js", diff --git a/public/pages/adminpage/ctrl_settings.js b/public/pages/adminpage/ctrl_settings.js index 67f5e187..4096b4bd 100644 --- a/public/pages/adminpage/ctrl_settings.js +++ b/public/pages/adminpage/ctrl_settings.js @@ -39,8 +39,7 @@ export default AdminOnly(WithShell(function(render) { ); effect(config$.pipe( - rxjs.map((formSpec) => createForm(formSpec, formTmpl({ autocomplete: false }))), - rxjs.mergeMap((promise) => rxjs.from(promise)), + rxjs.mergeMap((formSpec) => createForm(formSpec, formTmpl(false))), rxjs.map(($form) => [$form]), applyMutation(qs($container, `[data-bind="form"]`), "appendChild"), )); diff --git a/public/pages/connectpage/component_forkme.js b/public/pages/connectpage/component_forkme.js new file mode 100644 index 00000000..512c8352 --- /dev/null +++ b/public/pages/connectpage/component_forkme.js @@ -0,0 +1,25 @@ +import { createElement } from "../../lib/skeleton/index.js"; + +export default createElement(` + + + + +`); diff --git a/public/pages/connectpage/component_poweredby.js b/public/pages/connectpage/component_poweredby.js new file mode 100644 index 00000000..0aa4fd30 --- /dev/null +++ b/public/pages/connectpage/component_poweredby.js @@ -0,0 +1,29 @@ +import { createElement } from "../../lib/skeleton/index.js"; +import t from "../../lib/locales.js"; + +export default createElement(` +
+ ${t("Powered by")} Filestash + +
+`); diff --git a/public/pages/connectpage/connectpage.js b/public/pages/connectpage/connectpage.js deleted file mode 100644 index 88239e3e..00000000 --- a/public/pages/connectpage/connectpage.js +++ /dev/null @@ -1,5 +0,0 @@ -import { createElement } from "../../lib/skeleton/index.js"; - -export default function(render) { - render(createElement(`
LOGIN
`)); -} diff --git a/public/pages/connectpage/ctrl_connectpage.js b/public/pages/connectpage/ctrl_connectpage.js new file mode 100644 index 00000000..52480abd --- /dev/null +++ b/public/pages/connectpage/ctrl_connectpage.js @@ -0,0 +1,56 @@ +import { createElement, createRender } from "../../lib/skeleton/index.js"; +import rxjs, { effect, applyMutation } from "../../lib/rx.js"; +import { qs } from "../../lib/dom.js"; + +import ctrlForm from "./ctrl_form.js"; +import config$ from "./model_config.js"; + +import $fork from "./component_forkme.js"; +import $poweredby from "./component_poweredby.js"; + +export default function(render) { + const $page = createElement(` +
+
+
+
+
+
+
+ `); + render($page); + + // feature1: connection 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"), + )); + + // feature3: poweredby button + effect(config$.pipe( + rxjs.filter(({ fork_button }) => fork_button !== false), + rxjs.mapTo([$poweredby]), + applyMutation(qs($page, `[data-bind="component_poweredby"]`), "appendChild"), + )); + + // feature4: center the form + effect(rxjs.fromEvent(window, "resize").pipe( + rxjs.startWith(null), + rxjs.map(() => { + let size = 300; + const $screen = document.querySelector(".login-form"); + if ($screen) size = $screen.offsetHeight; + + size = Math.round((document.body.offsetHeight - size) / 2); + if (size < 0) return 0; + if (size > 150) return 150; + return size; + }), + rxjs.map((size) => ["padding-top", `${size}px`]), + applyMutation(qs($page, `[data-bind="centerthis"]`), "style", "setProperty"), + )); +} diff --git a/public/pages/connectpage/ctrl_form.css b/public/pages/connectpage/ctrl_form.css new file mode 100644 index 00000000..73c7ea90 --- /dev/null +++ b/public/pages/connectpage/ctrl_form.css @@ -0,0 +1,103 @@ +.component_page_connection_form .box { + margin-bottom: 7px; + border-radius: 3px; + box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2); + background: white; +} +.component_page_connection_form div.buttons { + display: flex; + margin: 0px 0px 15px 0px; + padding: 5px; + overflow-x: auto; +} +.component_page_connection_form div.buttons button { + min-width: 110px; + padding: 8px 5px; + box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2); +} +.component_page_connection_form div.buttons button:not(.active):hover { + transition: background 0.2s ease; + background: rgba(0, 0, 0, 0.03); +} +.component_page_connection_form div.buttons button.active { + transition: box-shadow 0.2s; + box-shadow: 0px 2px 20px rgba(0, 0, 0, 0.2); +} +.component_page_connection_form .formBody form .formbuilder { + animation-name: PageConnectionFormBody; + opacity: 0; + animation-duration: 0.25s; + animation-fill-mode: forwards; +} +.component_page_connection_form form { + padding-top: 5px; +} +.component_page_connection_form form label { + font-style: italic; + font-size: 0.9em; + display: block; + text-transform: capitalize; +} +.component_page_connection_form form .advanced_form { + max-height: 156px; + overflow-y: auto; + margin-top: 5px; + padding-right: 10px; +} +.component_page_connection_form form button.emphasis { + margin-top: 10px; + color: white; + text-transform: uppercase; +} +.component_page_connection_form form .third-party { + text-align: center; +} +.component_page_connection_form form .third-party img { + height: 115px; + margin: 0; +} +.component_page_connection_form form .component_checkbox .indicator { + top: 2px; +} +.component_page_connection_form form input.component_input[name="oauth2"] { + display: none; +} +.component_page_connection_form .component_loader { + margin: 45px 0; +} + +.component_page_connection_form.form-appear { + opacity: 0; + transform: translateX(5px); +} + +.component_page_connection_form.form-appear.form-appear-active { + opacity: 1; + transform: translateX(0); + transition: transform 0.25s ease-out, opacity 0.5s ease-out; +} + +.scroll-x { + overflow-x: auto !important; + overflow-y: hidden !important; + -webkit-overflow-scrolling: touch; +} +.scroll-x::-webkit-scrollbar { + height: 4px; +} +.scroll-x::-webkit-scrollbar-track { + background: white; +} +.scroll-x::-webkit-scrollbar-thumb { + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + -ms-border-radius: 2px; + -o-border-radius: 2px; + border-radius: 2px; + background: rgba(0, 0, 0, 0.1); +} + +.dark-mode div.buttons button.active { + background: var(--bg-color); + color: var(--super-light); +} diff --git a/public/pages/connectpage/ctrl_form.js b/public/pages/connectpage/ctrl_form.js new file mode 100644 index 00000000..0d7dbbfb --- /dev/null +++ b/public/pages/connectpage/ctrl_form.js @@ -0,0 +1,88 @@ +import { createElement } from "../../lib/skeleton/index.js"; +import rxjs, { effect, applyMutation, preventDefault } from "../../lib/rx.js"; +import { qs, qsa, safe } from "../../lib/dom.js"; +import { createForm, mutateForm } from "../../lib/form.js"; +import { formTmpl } from "../../components/form.js"; + +import CSSLoader from "../../helpers/css.js"; + +import config$ from "./model_config.js"; +import backend$ from "./model_backend.js"; +import { setCurrentBackend, getCurrentBackend } from "./state.js"; + +export default function(render) { + const $page = createElement(` +
+ + +
+
+ `); + + // feature1: backend selector + effect(config$.pipe( + // dom creation + rxjs.map(({ connections }) => connections), + rxjs.mergeMap((conns) => conns.map((conn, i) => ({...conn, n: i }))), + rxjs.map(({ label, n }) => createElement(``)), + 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)), + )); + + // feature2: interaction with the buttons + effect(getCurrentBackend().pipe( + rxjs.first(), + 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)), + )); + + // feature3: highlight the selected button + effect(getCurrentBackend().pipe( + rxjs.map((n) => ({ $buttons: qsa($page, `[role="navigation"] button`), n })), + rxjs.tap(({ $buttons }) => $buttons.forEach(($node) => $node.classList.remove("active"))), + rxjs.map(({ $buttons, n }) => $buttons[n]), + rxjs.filter(($button) => !!$button), + rxjs.tap(($button) => $button.classList.add("active")), + )); + + // feature4: insert all the connection form + effect(rxjs.combineLatest( + config$.pipe( + rxjs.first(), + rxjs.mergeMap(({ connections }) => connections), + rxjs.mergeMap(({ type }) => backend$.pipe(rxjs.map((spec) => spec[type]))), + rxjs.mergeMap((formSpec) => createForm(formSpec, formTmpl(true))), + rxjs.toArray(), + rxjs.share(), + ), + getCurrentBackend(), + ).pipe( + rxjs.map(([$forms, n]) => [$forms[n]]), + applyMutation(qs($page, "form"), "replaceChildren"), + rxjs.tap(() => qs($page, "form").appendChild(createElement(``))), + )); + + // feature5: form submission + effect(rxjs.fromEvent(qs($page, "form"), "submit").pipe( + preventDefault(), + rxjs.map((e) => new FormData(e.target)), + rxjs.map((formData) => { + const json = {}; + for (const pair of formData.entries()) { + json[pair[0]] = pair[1] === "" ? null : pair[1]; + } + return json; + }), + dbg("SUBMIT"), + )); + + render($page); +} + +const css = await CSSLoader(import.meta, "ctrl_form.css"); diff --git a/public/pages/connectpage/model_backend.js b/public/pages/connectpage/model_backend.js new file mode 100644 index 00000000..a4c19c33 --- /dev/null +++ b/public/pages/connectpage/model_backend.js @@ -0,0 +1,7 @@ +import rxjs from "../../lib/rx.js"; +import ajax from "../../lib/ajax.js"; + +export default ajax({ + url: "/api/backend", + responseType: "json", +}).pipe(rxjs.map(({ responseJSON }) => responseJSON.result)); diff --git a/public/pages/connectpage/model_config.js b/public/pages/connectpage/model_config.js new file mode 100644 index 00000000..3c090217 --- /dev/null +++ b/public/pages/connectpage/model_config.js @@ -0,0 +1,10 @@ +import rxjs from "../../lib/rx.js"; +import ajax from "../../lib/ajax.js"; + +export default ajax({ + url: "/api/config", + responseType: "json", +}).pipe( + rxjs.map(({ responseJSON }) => responseJSON.result), + rxjs.share(), +); diff --git a/public/pages/connectpage/state.js b/public/pages/connectpage/state.js new file mode 100644 index 00000000..025c753f --- /dev/null +++ b/public/pages/connectpage/state.js @@ -0,0 +1,11 @@ +import rxjs from "../../lib/rx.js"; + +const currentBackend$ = new rxjs.Subject(1); + +export function setCurrentBackend(n) { + currentBackend$.next(n); +} + +export function getCurrentBackend() { + return currentBackend$.asObservable(); +}