feature (connectpage): poc for connect page

This commit is contained in:
Mickael Kerjean 2023-08-03 02:05:31 +10:00
parent b65365b99b
commit 141b6094d1
11 changed files with 331 additions and 8 deletions

View file

@ -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",

View file

@ -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"),
));

View file

@ -0,0 +1,25 @@
import { createElement } from "../../lib/skeleton/index.js";
export default createElement(`
<a href="https://github.com/mickael-kerjean/skeleton" class="component_forkme" aria-label="View source on GitHub">
<svg width="80" height="80" viewBox="0 0 250 250" style="fill:var(--color); color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path>
</svg>
<style>
.component_forkme:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}
.dark-mode .component_forkme .octo-arm,
.dark-mode .component_forkme .octo-body { fill: var(--bg-color); }
@media (max-width:500px){
.component_forkme:hover .octo-arm{animation:none}
.component_forkme .octo-arm{animation:octocat-wave 560ms ease-in-out}
}
@keyframes octocat-wave{
0%,100%{transform:rotate(0)}
20%,60%{transform:rotate(-25deg)}
40%,80%{transform:rotate(10deg)}
}
</style>
</a>
`);

View file

@ -0,0 +1,29 @@
import { createElement } from "../../lib/skeleton/index.js";
import t from "../../lib/locales.js";
export default createElement(`
<div class="component_poweredbyfilestash">
${t("Powered by")} <strong><a href="https://www.filestash.app">Filestash</a></strong>
<style>
.component_poweredbyfilestash{
display: inline-block;
color: rgba(0, 0, 0, 0.4);
font-size: 0.9em;
line-height: 20px;
position: fixed;
bottom: 10px;
right: 20px;
}
.component_poweredbyfilestash strong{
font-weight: normal;
}
.component_poweredbyfilestash strong a{
text-decoration: underline;
}
.dark-mode .component_poweredbyfilestash {
color: var(--light);
}
</style>
</div>
`);

View file

@ -1,5 +0,0 @@
import { createElement } from "../../lib/skeleton/index.js";
export default function(render) {
render(createElement(`<div> LOGIN </div>`));
}

View file

@ -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(`
<div class="component_page_connect">
<div data-bind="component_forkme"></div>
<div data-bind="centerthis" class="component_container" style="max-width:565px;">
<div data-bind="component_form"></div>
</div>
<div data-bind="component_poweredby"></div>
</div>
`);
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"),
));
}

View file

@ -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);
}

View file

@ -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(`
<div class="no-select component_page_connection_form">
<div role="navigation" class="buttons scroll-x box"></div>
<style>${css}</style>
<form class="box"></form>
</div>
`);
// 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(`<button class="" data-current="${n}">${safe(label)}</button>`)),
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(`<button class="emphasis">CONNECT</button>`))),
));
// 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");

View file

@ -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));

View file

@ -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(),
);

View file

@ -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();
}