diff --git a/public/Makefile b/public/Makefile new file mode 100644 index 00000000..a2d90da1 --- /dev/null +++ b/public/Makefile @@ -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 diff --git a/public/components/modal.css b/public/components/modal.css new file mode 100644 index 00000000..29fceebc --- /dev/null +++ b/public/components/modal.css @@ -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; +} diff --git a/public/components/modal.js b/public/components/modal.js index 05e2fbd5..408d1b14 100644 --- a/public/components/modal.js +++ b/public/components/modal.js @@ -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(` - -

Greetings, one and all!

-
- -
-
`); - 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(` +`); + 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"); diff --git a/public/helpers/modal.js b/public/helpers/modal.js new file mode 100644 index 00000000..29d40a7b --- /dev/null +++ b/public/helpers/modal.js @@ -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(); diff --git a/public/index.html b/public/index.html index 9d7af112..e4778155 100644 --- a/public/index.html +++ b/public/index.html @@ -32,6 +32,10 @@ loader: `` }); + + + +