mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-09 18:03:15 +01:00
feature (admin): modal component
This commit is contained in:
parent
a301c28d96
commit
c83f57f8b2
6 changed files with 223 additions and 17 deletions
14
public/Makefile
Normal file
14
public/Makefile
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
all:
|
||||
find . -type f -name '*.html' | xargs brotli -f -k
|
||||
find . -type f -name '*.html' | xargs gzip -f -k
|
||||
find . -type f -name '*.js' | xargs brotli -f -k
|
||||
find . -type f -name '*.js' | xargs gzip -f -k --best
|
||||
find . -type f -name '*.css' | xargs brotli -f -k
|
||||
find . -type f -name '*.css' | xargs gzip -f -k
|
||||
|
||||
clean:
|
||||
find . -name '*.gz' -exec rm {} \;
|
||||
find . -name '*.br' -exec rm {} \;
|
||||
|
||||
serve:
|
||||
go run server.go
|
||||
56
public/components/modal.css
Normal file
56
public/components/modal.css
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
.component_modal{
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #f2f3f5f0;
|
||||
z-index: 1000;
|
||||
}
|
||||
.component_modal > div{
|
||||
box-shadow: 1px 2px 20px rgba(0, 0, 0, 0.1);
|
||||
background: white;
|
||||
width: 80%;
|
||||
max-width: 310px;
|
||||
padding: 20px 20px 0 20px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.dark-mode .component_modal{
|
||||
background: var(--bg-color);
|
||||
}
|
||||
|
||||
.component_popup .popup--content {
|
||||
font-size: 1.1em;
|
||||
margin: 0;
|
||||
}
|
||||
.component_popup .popup--content p {
|
||||
margin: 0;
|
||||
}
|
||||
.component_popup .popup--content .modal-error-message {
|
||||
font-size: 15px;
|
||||
}
|
||||
.component_popup .buttons {
|
||||
margin: 15px -20px 0 -20px;
|
||||
display: flex;
|
||||
}
|
||||
.component_popup .buttons > div {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
.component_popup .buttons [type="submit"] {
|
||||
border-radius: 10px 0 0;
|
||||
}
|
||||
.component_popup .buttons > button {
|
||||
width: 50%;
|
||||
margin-left: auto;
|
||||
}
|
||||
.component_popup .buttons button {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.component_popup .modal-error-message {
|
||||
color: var(--error);
|
||||
}
|
||||
.component_popup .center {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -1,13 +1,114 @@
|
|||
import { createElement } from "../common/skeleton/index.js";
|
||||
import { createElement } from "../lib/skeleton/index.js";
|
||||
import rxjs, { applyMutation } from "../lib/rxjs/index.js";
|
||||
import { animate } from "../lib/animate/index.js";
|
||||
import { qs } from "../lib/dom/index.js";
|
||||
|
||||
export function prompt(label, okFn, errFn) {
|
||||
const $node = createElement(`
|
||||
<dialog open>
|
||||
<p>Greetings, one and all!</p>
|
||||
<form method="dialog">
|
||||
<button>OK</button>
|
||||
</form>
|
||||
</dialog>`);
|
||||
document.body.appendChild($node);
|
||||
okFn("OK")
|
||||
import CSSLoader from "../../helpers/css.js";
|
||||
// http://127.0.0.1:8000/admin/setup
|
||||
|
||||
const _observables = [];
|
||||
const effect = (obs) => _observables.push(obs.subscribe());
|
||||
const free = () => {
|
||||
for (let i=0; i<_observables.length; i++) {
|
||||
_observables[i].unsubscribe();
|
||||
}
|
||||
_observables = [];
|
||||
}
|
||||
|
||||
class Modal extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
trigger($node, opts = {}) {
|
||||
const { onQuit, leftButton, rightButton } = opts;
|
||||
const $modal = createElement(`
|
||||
<div class="component_modal" id="modal-box">
|
||||
<style>${css}</style>
|
||||
<div>
|
||||
<div class="component_popup">
|
||||
<div class="popup--content">
|
||||
<div class="modal-message" data-bind="body"><!-- MODAL BODY --></div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<button type="submit" class="emphasis">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`);
|
||||
this.replaceChildren($modal);
|
||||
|
||||
// feature: setup the modal body
|
||||
effect(rxjs.of([$node]).pipe(
|
||||
applyxMutation(qs($modal, `[data-bind="body"]`), "appendChild"),
|
||||
));
|
||||
|
||||
// feature: closing the modal
|
||||
effect(rxjs.merge(
|
||||
rxjs.fromEvent($modal, "click").pipe(
|
||||
rxjs.filter((e) => e.target.getAttribute("id") === "modal-box")
|
||||
),
|
||||
rxjs.fromEvent(window, "keydown").pipe(
|
||||
rxjs.filter((e) => e.keyCode === 27),
|
||||
),
|
||||
).pipe(
|
||||
rxjs.tap(() => typeof onQuit === "function" && onQuit()),
|
||||
rxjs.tap(() => animate(qs($modal, "div > div"), {
|
||||
time: 200,
|
||||
keyframes: [
|
||||
{ opacity: 1, transform: "translateY(0)" },
|
||||
{ opacity: 0, transform: "translateY(20px)" },
|
||||
]
|
||||
})),
|
||||
rxjs.delay(100),
|
||||
rxjs.tap(() => animate($modal, {
|
||||
time: 200,
|
||||
keyframes: [ { opacity: 1 }, { opacity: 0 } ]
|
||||
})),
|
||||
rxjs.mapTo([]), applyMutation($modal, "remove"),
|
||||
rxjs.tap(free),
|
||||
));
|
||||
|
||||
// feature: animate opening
|
||||
effect(rxjs.of(["opacity", "0"]).pipe(
|
||||
applyMutation(qs($modal, "div > div"), "style", "setProperty"),
|
||||
rxjs.tap(() => animate($modal, {
|
||||
time: 250,
|
||||
keyframes: [
|
||||
{ opacity: 0 },
|
||||
{ opacity: 1 },
|
||||
],
|
||||
})),
|
||||
rxjs.delay(50),
|
||||
rxjs.tap(() => animate(qs($modal, "div > div"), {
|
||||
time: 200,
|
||||
keyframes: [
|
||||
{ opacity: 0, transform: "translateY(10px)" },
|
||||
{ opacity: 1, transform: "translateY(0)" },
|
||||
],
|
||||
})),
|
||||
));
|
||||
|
||||
// feature: center horizontally
|
||||
effect(rxjs.fromEvent(window, "resize").pipe(
|
||||
rxjs.startWith(null),
|
||||
rxjs.distinct(() => document.body.offsetHeight),
|
||||
rxjs.map(() => {
|
||||
let size = 300;
|
||||
const $box = document.querySelector("#modal-box > div");
|
||||
if ($box) size = $box.offsetHeight;
|
||||
|
||||
size = Math.round((document.body.offsetHeight - size) / 2);
|
||||
if (size < 0) return 0;
|
||||
if (size > 250) return 250;
|
||||
return size;
|
||||
}),
|
||||
rxjs.map((size) => ["margin", `${size}px auto 0 auto`]),
|
||||
applyMutation(qs(this, ".component_modal > div"), "style", "setProperty"),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("component-modal", Modal);
|
||||
|
||||
const css = await CSSLoader(import.meta, "modal.css");
|
||||
|
|
|
|||
13
public/helpers/modal.js
Normal file
13
public/helpers/modal.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// prompt, alert, confirm, modal, popup?
|
||||
class ModalManager {
|
||||
constructor() {
|
||||
this.$dom = document.body.querySelector("component-modal");
|
||||
if (!this.$dom) throw new Error("dom not set");
|
||||
}
|
||||
|
||||
alert($node, opts) {
|
||||
this.$dom.trigger($node, opts);
|
||||
}
|
||||
}
|
||||
|
||||
export default new ModalManager();
|
||||
|
|
@ -32,6 +32,10 @@
|
|||
loader: `<data-loader></data-loader>`
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="module" src="/components/modal.js"></script>
|
||||
<component-modal></component-modal>
|
||||
|
||||
<noscript>
|
||||
<div>
|
||||
<h2>Error: Javascript is off</h2>
|
||||
|
|
|
|||
|
|
@ -5,13 +5,15 @@ import { ApplicationError } from "../../lib/error/index.js";
|
|||
import { transition, animate } from "../../lib/animate/index.js";
|
||||
|
||||
import CSSLoader from "../../helpers/css.js";
|
||||
import modal from "../../helpers/modal.js";
|
||||
|
||||
import ctrlError from "../ctrl_error.js";
|
||||
import WithShell from "./decorator_sidemenu.js";
|
||||
import { zoomIn, slideXOut, slideXIn, cssHideMenu } from "./animate.js";
|
||||
|
||||
import "../../components/icon.js";
|
||||
|
||||
const stepper$ = new rxjs.BehaviorSubject(1);
|
||||
const stepper$ = new rxjs.BehaviorSubject(2);
|
||||
|
||||
export default function(render) {
|
||||
const $page = createElement(`
|
||||
|
|
@ -23,7 +25,6 @@ export default function(render) {
|
|||
render($page);
|
||||
|
||||
effect(stepper$.pipe(
|
||||
dbg("CHANGE"),
|
||||
rxjs.map((step) => {
|
||||
switch(step) {
|
||||
case 1: return WithShell(componentStep1);
|
||||
|
|
@ -94,7 +95,6 @@ function componentStep2(render) {
|
|||
|
||||
// feature: navigate previous step
|
||||
effect(rxjs.fromEvent(qs($page, `[data-bind="previous"]`), "click").pipe(
|
||||
dbg("click"),
|
||||
rxjs.tap(() => stepper$.next(1)),
|
||||
));
|
||||
|
||||
|
|
@ -104,11 +104,29 @@ function componentStep2(render) {
|
|||
rxjs.tap(() => animate(qs($page, "h4"), { time: 200, keyframes: slideXIn(30) })),
|
||||
rxjs.delay(200),
|
||||
rxjs.mapTo([]), applyMutation(qs($page, "style"), "remove"),
|
||||
dbg("")
|
||||
));
|
||||
|
||||
// feature: opt in for telemetry
|
||||
onDestroy(() => console.log("opt in for telemetry"));
|
||||
// feature: telemetry popup
|
||||
onDestroy(() => {
|
||||
const $node = createElement(`
|
||||
<div>
|
||||
<p style="text-align: justify;">Help making this software better by sending crash reports and anonymous usage statistics</p>
|
||||
<form style="font-size: 0.9em; margin-top: 10px;">
|
||||
<label>
|
||||
<div class="component_checkbox">
|
||||
<input type="checkbox">
|
||||
<span class="indicator"></span>
|
||||
</div>I accept but the data is not to be share with any third party
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
`);
|
||||
return new Promise((done) => {
|
||||
modal.alert($node, {
|
||||
onQuit: done,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const animateOut = ($el) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue