mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2024-04-21 12:32:08 +00:00
feature (viewerpage): form app
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import rxjs, { ajax } from "../lib/rx.js";
|
||||
import { loadJS } from "../helpers/loader.js";
|
||||
// import { setup_cache } from "../helpers/cache.js";
|
||||
import { init as setup_loader } from "../helpers/loader.js";
|
||||
import { report } from "../helpers/log.js";
|
||||
|
||||
|
||||
export default async function main() {
|
||||
try {
|
||||
await Promise.all([ // procedure with no outside dependencies
|
||||
@@ -12,6 +14,7 @@ export default async function main() {
|
||||
setup_device(),
|
||||
// setup_sw(), // TODO
|
||||
setup_blue_death_screen(),
|
||||
setup_loader(),
|
||||
setup_history(),
|
||||
]);
|
||||
// await Config.refresh()
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
.component_fab {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
z-index: 2;
|
||||
}
|
||||
.component_fab .content {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
padding: 13px;
|
||||
border-radius: 50%;
|
||||
background: var(--dark);
|
||||
box-shadow: rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px, rgba(0, 0, 0, 0.2) 0px 2px 4px -1px;
|
||||
z-index: 1000;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { loadCSS } from "../helpers/loader.js";
|
||||
|
||||
export default class ComponentFab extends window.HTMLButtonElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.innerHTML = `<div class="content"></div>`;
|
||||
this.classList.add("component_fab");
|
||||
}
|
||||
|
||||
async render($icon) {
|
||||
await loadCSS(import.meta.url, "./fab.css");
|
||||
this.querySelector(".content").replaceChildren($icon);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("component-fab", ComponentFab, { extends: "button" });
|
||||
@@ -4,7 +4,7 @@ let version = null;
|
||||
|
||||
export async function loadJS(baseURL, path, opts = {}) {
|
||||
const $script = document.createElement("script");
|
||||
const link = new URL(path, baseURL);
|
||||
const link = new URL(path, baseURL) + "?version=" + version;
|
||||
$script.setAttribute("src", link.toString());
|
||||
for (let key in opts) {
|
||||
$script.setAttribute(key, opts[key]);
|
||||
@@ -20,7 +20,7 @@ export async function loadJS(baseURL, path, opts = {}) {
|
||||
|
||||
export async function loadCSS(baseURL, path) {
|
||||
const $style = document.createElement("link");
|
||||
const link = new URL(path, baseURL);
|
||||
const link = new URL(path, baseURL) + "?version=" + version;
|
||||
$style.setAttribute("href", link.toString());
|
||||
$style.setAttribute("rel", "stylesheet");
|
||||
if (document.head.querySelector(`[href="${link.toString()}"]`)) return Promise.resolve();
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { createElement } from "../../lib/skeleton/index.js";
|
||||
import rxjs, { effect } from "../lib/rx.js";
|
||||
import { onDestroy } from "../../lib/skeleton/lifecycle.js";
|
||||
import rxjs, { effect } from "../../lib/rx.js";
|
||||
import WaveSurfer from "../../lib/vendor/wavesurfer.js";
|
||||
|
||||
export default function(render) {
|
||||
const $page = createElement(`
|
||||
<div class="component_formviewer">
|
||||
<component-menubar></component-menubar>
|
||||
<div id="waveform">
|
||||
AUDIO
|
||||
</div>
|
||||
<div id="waveform">AUDIO</div>
|
||||
</div>
|
||||
`);
|
||||
render($page);
|
||||
@@ -24,5 +23,5 @@ export default function(render) {
|
||||
height: 200,
|
||||
barWidth: 1,
|
||||
});
|
||||
// onDestroy(() => wavesurfer.destroy());
|
||||
onDestroy(() => wavesurfer.destroy());
|
||||
}
|
||||
|
||||
@@ -1,64 +1,125 @@
|
||||
import { createElement } from "../../lib/skeleton/index.js";
|
||||
import rxjs, { effect, applyMutation } from "../../lib/rx.js";
|
||||
import rxjs, { effect, applyMutation, onClick } from "../../lib/rx.js";
|
||||
import { animate, slideXIn, opacityOut } from "../../lib/animate.js";
|
||||
import { qs, qsa } from "../../lib/dom.js";
|
||||
import { loadCSS } from "../../helpers/loader.js";
|
||||
import { createForm, mutateForm } from "../../lib/form.js";
|
||||
import { formTmpl } from "../../components/form.js";
|
||||
import ajax from "../../lib/ajax.js";
|
||||
import ctrlError from "../ctrl_error.js";
|
||||
|
||||
import { transition, getDownloadUrl } from "./common.js";
|
||||
import { transition, getFile$, saveFile$ } from "./common.js";
|
||||
import { $ICON } from "./common_fab.js";
|
||||
|
||||
import "../../components/icon.js";
|
||||
import "../../components/menubar.js";
|
||||
import "../../components/fab.js";
|
||||
|
||||
export default function(render) {
|
||||
const $page = createElement(`
|
||||
<div class="component_formviewer">
|
||||
<component-menubar></component-menubar>
|
||||
<div class="formviewer_container">
|
||||
<form class="sticky box">
|
||||
</form>
|
||||
<form class="sticky box"></form>
|
||||
</div>
|
||||
<button is="component-fab"></button>
|
||||
</div>
|
||||
`);
|
||||
render($page);
|
||||
transition(qs($page, ".formviewer_container"));
|
||||
|
||||
const spec$ = ajax(getDownloadUrl()).pipe(
|
||||
rxjs.map(({ response }) => JSON.parse(response)),
|
||||
rxjs.share(),
|
||||
);
|
||||
const setup$ = spec$.pipe(
|
||||
rxjs.mergeMap((formSpec) => createForm(formSpec, formTmpl({
|
||||
renderLeaf: ({ label = "", format = (s) => s, description = "", required = false }) => {
|
||||
const mandatory = required ? `<span class="mandatory">*</span>` : "";
|
||||
return createElement(`
|
||||
<label class="no-select">
|
||||
<div>
|
||||
<span> ${format(label)} ${mandatory} </span>
|
||||
<div data-bind="children"></div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="nothing"></span>
|
||||
<div class="description">${description}</div>
|
||||
</div>
|
||||
</label>
|
||||
`);
|
||||
},
|
||||
}))),
|
||||
applyMutation(qs($page, "form"), "replaceChildren"),
|
||||
rxjs.catchError(ctrlError()),
|
||||
rxjs.share(),
|
||||
);
|
||||
const file$ = new rxjs.ReplaySubject(1);
|
||||
const $fab = qs($page, `[is="component-fab"]`);
|
||||
const formState = () => [...new FormData(qs($page, "form"))].reduce((acc, el) => {
|
||||
acc[el[0]] = el[1];
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
effect(setup$);
|
||||
effect(setup$.pipe(
|
||||
rxjs.mergeMap(() => qsa($page, "form [name]")),
|
||||
rxjs.mergeMap(($el) => rxjs.fromEvent($el, "input")),
|
||||
rxjs.tap(() => console.log("SETUP SAVE BUTTON")),
|
||||
// feature1: setup the dom
|
||||
effect(getFile$().pipe(
|
||||
rxjs.map((content) => JSON.parse(content)),
|
||||
rxjs.mergeMap((formSpec) => rxjs.from(createForm(formSpec, formTmpl({
|
||||
renderLeaf: ({ label, format, description, required }) => createElement(`
|
||||
<label class="no-select">
|
||||
<div>
|
||||
<span>
|
||||
${format(label)}
|
||||
` + (required === true ? `<span class="mandatory">*</span>`: "") +`
|
||||
</span>
|
||||
<div data-bind="children"></div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="nothing"></span>
|
||||
<div class="description">${description || ""}</div>
|
||||
</div>
|
||||
</label>
|
||||
`),
|
||||
}))).pipe(
|
||||
applyMutation(qs($page, "form"), "replaceChildren"),
|
||||
rxjs.mapTo(formSpec),
|
||||
)),
|
||||
rxjs.tap((formSpec) => file$.next(formObjToJSON(formSpec))),
|
||||
rxjs.catchError(ctrlError()),
|
||||
));
|
||||
|
||||
// feature2: display/hide save button
|
||||
effect(rxjs.merge(
|
||||
file$.asObservable(),
|
||||
file$.pipe(
|
||||
rxjs.first(),
|
||||
rxjs.mergeMap((formSpec) => rxjs.from(qsa($page, "form [name]")).pipe(
|
||||
rxjs.mergeMap(($el) => rxjs.fromEvent($el, "input")),
|
||||
rxjs.mapTo(formSpec),
|
||||
)),
|
||||
),
|
||||
).pipe(
|
||||
rxjs.map((originalState) => {
|
||||
const smod = (key, value) => value ? value : undefined;
|
||||
return JSON.stringify(originalState, smod) !== JSON.stringify(formState(), smod);
|
||||
}),
|
||||
rxjs.mergeMap(async (isSaveButtonVisible) => {
|
||||
if (isSaveButtonVisible && $fab.classList.contains("hidden")) {
|
||||
$fab.render($ICON.SAVING);
|
||||
$fab.classList.remove("hidden");
|
||||
await animate($fab, { time: 100, keyframes: slideXIn(40) });
|
||||
} else if (!isSaveButtonVisible && !$fab.classList.contains("hidden")) {
|
||||
await animate($fab, { time: 100, keyframes: opacityOut() });
|
||||
$fab.classList.add("hidden");
|
||||
}
|
||||
}),
|
||||
rxjs.catchError(ctrlError()),
|
||||
));
|
||||
|
||||
// feature3: submit the form
|
||||
effect(onClick($fab).pipe(
|
||||
rxjs.tap(() => {
|
||||
$fab.render($ICON.LOADING)
|
||||
$fab.disabled = true;
|
||||
}),
|
||||
rxjs.mergeMap(($fab) => rxjs.of(JSON.stringify(formState())).pipe(
|
||||
saveFile$(),
|
||||
rxjs.mergeMap(() => getFile$().pipe(rxjs.tap((formSpec) => file$.next(JSON.parse(formSpec))))),
|
||||
)),
|
||||
rxjs.tap(() => {
|
||||
$fab.render($ICON.SAVING);
|
||||
$fab.removeAttribute("disabled");
|
||||
}),
|
||||
rxjs.catchError(ctrlError()),
|
||||
));
|
||||
}
|
||||
|
||||
export function init() {
|
||||
return loadCSS(import.meta.url, "./application_form.css");
|
||||
}
|
||||
|
||||
const formObjToJSON = (o, level = 0) => {
|
||||
const obj = Object.assign({}, o);
|
||||
Object.keys(obj).forEach((key) => {
|
||||
const t = obj[key];
|
||||
if ("label" in t && "type" in t && "default" in t && "value" in t) {
|
||||
obj[key] = obj[key].value;
|
||||
} else {
|
||||
obj[key] = formObjToJSON(obj[key], level + 1);
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { createElement } from "../../lib/skeleton/index.js";
|
||||
import rxjs, { effect, onLoad } from "../../lib/rx.js";
|
||||
import { animate, opacityIn } from "../../lib/animate.js";
|
||||
import { qs } from "../../lib/dom.js";
|
||||
import { createLoader } from "../../components/loader.js";
|
||||
import { loadCSS, loadJS } from "../../helpers/loader.js";
|
||||
@@ -82,7 +81,6 @@ async function ctrlPDFJs(render) {
|
||||
}
|
||||
createBr();
|
||||
}),
|
||||
rxjs.tap(() => transition($container)),
|
||||
rxjs.catchError(ctrlError()),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,10 +1,25 @@
|
||||
import { transition as transitionLib, slideYIn } from "../../lib/animate.js";
|
||||
import { basename } from "../../lib/path.js";
|
||||
import rxjs from "../../lib/rx.js";
|
||||
import ajax from "../../lib/ajax.js";
|
||||
|
||||
export function transition($node) {
|
||||
return transitionLib($node, { timeEnter: 150, enter: slideYIn(2) });
|
||||
}
|
||||
|
||||
export function getFile$() {
|
||||
return ajax(getDownloadUrl()).pipe(
|
||||
rxjs.map(({ response }) => response),
|
||||
);
|
||||
}
|
||||
|
||||
export function saveFile$() {
|
||||
return rxjs.pipe(
|
||||
rxjs.delay(2000),
|
||||
rxjs.tap((content) => console.log("SAVED", content)),
|
||||
);
|
||||
}
|
||||
|
||||
export function getFilename() {
|
||||
return basename(getCurrentPath()) || "untitled.dat";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { createElement } from "../../lib/skeleton/index.js";
|
||||
|
||||
export const $ICON = {
|
||||
SAVING: createElement(`<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MzguNTMzIDQzOC41MzMiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDQzOC41MzMgNDM4LjUzMzsiPgogIDxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik00MzIuODIzLDEyMS4wNDljLTMuODA2LTkuMTMyLTguMzc3LTE2LjM2Ny0xMy43MDktMjEuNjk1bC03OS45NDEtNzkuOTQyYy01LjMyNS01LjMyNS0xMi41Ni05Ljg5NS0yMS42OTYtMTMuNzA0ICAgQzMwOC4zNDYsMS45MDMsMjk5Ljk2OSwwLDI5Mi4zNTcsMEgyNy40MDlDMTkuNzk4LDAsMTMuMzI1LDIuNjYzLDcuOTk1LDcuOTkzYy01LjMzLDUuMzI3LTcuOTkyLDExLjc5OS03Ljk5MiwxOS40MTR2MzgzLjcxOSAgIGMwLDcuNjE3LDIuNjYyLDE0LjA4OSw3Ljk5MiwxOS40MTdjNS4zMyw1LjMyNSwxMS44MDMsNy45OTEsMTkuNDE0LDcuOTkxaDM4My43MThjNy42MTgsMCwxNC4wODktMi42NjYsMTkuNDE3LTcuOTkxICAgYzUuMzI1LTUuMzI4LDcuOTg3LTExLjgsNy45ODctMTkuNDE3VjE0Ni4xNzhDNDM4LjUzMSwxMzguNTYyLDQzNi42MjksMTMwLjE4OCw0MzIuODIzLDEyMS4wNDl6IE0xODIuNzI1LDQ1LjY3NyAgIGMwLTIuNDc0LDAuOTA1LTQuNjExLDIuNzE0LTYuNDIzYzEuODA3LTEuODA0LDMuOTQ5LTIuNzA4LDYuNDIzLTIuNzA4aDU0LjgxOWMyLjQ2OCwwLDQuNjA5LDAuOTAyLDYuNDE3LDIuNzA4ICAgYzEuODEzLDEuODEyLDIuNzE3LDMuOTQ5LDIuNzE3LDYuNDIzdjkxLjM2MmMwLDIuNDc4LTAuOTEsNC42MTgtMi43MTcsNi40MjdjLTEuODA4LDEuODAzLTMuOTQ5LDIuNzA4LTYuNDE3LDIuNzA4aC01NC44MTkgICBjLTIuNDc0LDAtNC42MTctMC45MDItNi40MjMtMi43MDhjLTEuODA5LTEuODEyLTIuNzE0LTMuOTQ5LTIuNzE0LTYuNDI3VjQ1LjY3N3ogTTMyOC45MDYsNDAxLjk5MUgxMDkuNjMzVjI5Mi4zNTVoMjE5LjI3MyAgIFY0MDEuOTkxeiBNNDAyLDQwMS45OTFoLTM2LjU1MmgtMC4wMDdWMjgzLjIxOGMwLTcuNjE3LTIuNjYzLTE0LjA4NS03Ljk5MS0xOS40MTdjLTUuMzI4LTUuMzI4LTExLjgtNy45OTQtMTkuNDEtNy45OTRIMTAwLjQ5OCAgIGMtNy42MTQsMC0xNC4wODcsMi42NjYtMTkuNDE3LDcuOTk0Yy01LjMyNyw1LjMyOC03Ljk5MiwxMS44LTcuOTkyLDE5LjQxN3YxMTguNzczSDM2LjU0NFYzNi41NDJoMzYuNTQ0djExOC43NzEgICBjMCw3LjYxNSwyLjY2MiwxNC4wODQsNy45OTIsMTkuNDE0YzUuMzMsNS4zMjcsMTEuODAzLDcuOTkzLDE5LjQxNyw3Ljk5M2gxNjQuNDU2YzcuNjEsMCwxNC4wODktMi42NjYsMTkuNDEtNy45OTMgICBjNS4zMjUtNS4zMjcsNy45OTQtMTEuNzk5LDcuOTk0LTE5LjQxNFYzNi41NDJjMi44NTQsMCw2LjU2MywwLjk1LDExLjEzNiwyLjg1M2M0LjU3MiwxLjkwMiw3LjgwNiwzLjgwNSw5LjcwOSw1LjcwOGw4MC4yMzIsODAuMjMgICBjMS45MDIsMS45MDMsMy44MDYsNS4xOSw1LjcwOCw5Ljg1MWMxLjkwOSw0LjY2NSwyLjg1Nyw4LjMzLDIuODU3LDEwLjk5NFY0MDEuOTkxeiIvPgo8L3N2Zz4K" alt="save" style="height: 100%; width: 100%;">`),
|
||||
LOADING: createElement(`<component-icon name="loading"></component-icon>`),
|
||||
};
|
||||
Reference in New Issue
Block a user