mirror of
https://github.com/mickael-kerjean/filestash
synced 2026-01-03 22:33:08 +01:00
feature (connectpage): poc for connect page
This commit is contained in:
parent
b65365b99b
commit
141b6094d1
11 changed files with 331 additions and 8 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
));
|
||||
|
|
|
|||
25
public/pages/connectpage/component_forkme.js
Normal file
25
public/pages/connectpage/component_forkme.js
Normal 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>
|
||||
`);
|
||||
29
public/pages/connectpage/component_poweredby.js
Normal file
29
public/pages/connectpage/component_poweredby.js
Normal 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>
|
||||
`);
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import { createElement } from "../../lib/skeleton/index.js";
|
||||
|
||||
export default function(render) {
|
||||
render(createElement(`<div> LOGIN </div>`));
|
||||
}
|
||||
56
public/pages/connectpage/ctrl_connectpage.js
Normal file
56
public/pages/connectpage/ctrl_connectpage.js
Normal 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"),
|
||||
));
|
||||
}
|
||||
103
public/pages/connectpage/ctrl_form.css
Normal file
103
public/pages/connectpage/ctrl_form.css
Normal 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);
|
||||
}
|
||||
88
public/pages/connectpage/ctrl_form.js
Normal file
88
public/pages/connectpage/ctrl_form.js
Normal 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");
|
||||
7
public/pages/connectpage/model_backend.js
Normal file
7
public/pages/connectpage/model_backend.js
Normal 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));
|
||||
10
public/pages/connectpage/model_config.js
Normal file
10
public/pages/connectpage/model_config.js
Normal 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(),
|
||||
);
|
||||
11
public/pages/connectpage/state.js
Normal file
11
public/pages/connectpage/state.js
Normal 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();
|
||||
}
|
||||
Loading…
Reference in a new issue