aboutsummaryrefslogtreecommitdiffstats
path: root/app/dom.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/dom.js')
-rw-r--r--app/dom.js410
1 files changed, 0 insertions, 410 deletions
diff --git a/app/dom.js b/app/dom.js
index ab43570..508636a 100644
--- a/app/dom.js
+++ b/app/dom.js
@@ -1,6 +1,3 @@
-// dom.js
-import { House } from "models";
-
export class DomOptions {
attributes;
children;
@@ -214,410 +211,3 @@ export class Dom {
return p;
}
}
-
-export class Widgets {
- /**
- * Show toast notification
- * @param {string} message
- * @param {ToastType} [type=ToastType.error]
- */
- static toast(message, type = ToastType.error) {
- return Dom.div(
- new DomOptions({
- children: [Dom.p(message)],
- classes: ["toast", `toast-${type}`],
- id: "app-toast",
- styles: {
- background:
- type === ToastType.error
- ? "#f44336"
- : type === ToastType.warning
- ? "#ff9800"
- : "#4caf50",
- borderRadius: "4px",
- boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
- color: "white",
- fontSize: "14px",
- fontWeight: "500",
- maxWidth: "300px",
- padding: "12px 20px",
- position: "fixed",
- right: "20px",
- top: "20px",
- transition: "all .3s ease",
- zIndex: "10000",
- },
- }),
- );
- }
-
- /**
- * Remove all children
- * @param {HTMLElement} el
- */
- static clear(el) {
- while (el.firstChild) el.removeChild(el.firstChild);
- }
-
- /**
- * Create a weight slider
- * @param {string} id
- * @param {string} labelText
- * @param {string} weightKey
- * @param {number} initialValue
- * @param {(key: string, value: number) => void} onChange
- * @returns {HTMLElement}
- */
- static slider(id, labelText, weightKey, initialValue, onChange) {
- const output = Dom.span(
- initialValue.toFixed(1),
- new DomOptions({
- id: `${id}-value`,
- styles: { color: "#0066cc", fontSize: "0.85rem", fontWeight: "bold", textAlign: "center" },
- }),
- );
-
- return Dom.div(
- new DomOptions({
- children: [
- Dom.label(
- id,
- labelText,
- new DomOptions({
- children: [
- Dom.span(
- labelText,
- new DomOptions({
- styles: { fontSize: "0.85rem" },
- }),
- ),
- Dom.span(" "),
- output,
- ],
- }),
- ),
- Dom.input(
- "range",
- (e) => {
- const target = /** @type {HTMLInputElement} */ (e.target);
- const val = Number(target.value);
- output.textContent = val.toFixed(1);
- onChange(weightKey, val);
- },
- "",
- "",
- new DomOptions({
- attributes: { max: "1", min: "0", step: "0.1", value: initialValue.toString() },
- id,
- styles: {
- margin: "0.5rem 0",
- width: "100%",
- },
- }),
- ),
- ],
- styles: { display: "flex", flexDirection: "column", marginBottom: "1rem" },
- }),
- );
- }
-
- /**
- * Create a number filter input
- * @param {string} id
- * @param {string} labelText
- * @param {(value: number | null) => void} onChange
- * @returns {HTMLElement}
- */
- static numberFilter(id, labelText, onChange) {
- return Dom.div(
- new DomOptions({
- children: [
- Dom.label(
- id,
- labelText,
- new DomOptions({
- styles: { fontSize: "0.85rem", fontWeight: "bold", marginBottom: "0.25rem" },
- }),
- ),
- Dom.input(
- "number",
- (e) => {
- const target = /** @type {HTMLInputElement} */ (e.target);
- const raw = target.value.trim();
- onChange(raw === "" ? null : Number(raw));
- },
- "any",
- "",
- new DomOptions({
- id,
- styles: {
- border: "1px solid #ddd",
- borderRadius: "4px",
- fontSize: "0.9rem",
- padding: "0.5rem",
- },
- }),
- ),
- ],
- styles: { display: "flex", flexDirection: "column", marginBottom: "1.75rem" },
- }),
- );
- }
-}
-
-export class Modal {
- /** @type {HTMLDialogElement} */
- #dialog;
- /** @type {AbortController} */
- #abortController;
- /** @type {number | undefined} */
- #timer;
- /** @type {boolean} */
- #persistent;
- /** @type {House} */
- #house;
- /** @type {() => void} */
- #onHide;
- /** @type {() => void} */
- #onClearMapTimer;
-
- /**
- * Build modal content for a house
- * @param {House} house
- * @returns {DocumentFragment}
- */
- static buildModalContent(house) {
- const frag = document.createDocumentFragment();
- frag.appendChild(
- Dom.div(
- new DomOptions({
- children: [
- Dom.heading(
- 2,
- house.address,
- new DomOptions({
- styles: { color: "#333", fontSize: "20px", margin: "0" },
- }),
- ),
- Dom.span(
- `Score: ${house.scores.current.toFixed(1)}`,
- new DomOptions({
- styles: {
- background: "#e8f5e9",
- borderRadius: "4px",
- color: "#2e7d32",
- fontSize: "16px",
- fontWeight: "bold",
- padding: "4px 8px",
- },
- }),
- ),
- ],
- id: "modal-header",
- styles: {
- alignItems: "center",
- display: "flex",
- justifyContent: "space-between",
- marginBottom: "20px",
- },
- }),
- ),
- );
-
- const grid = Dom.div(
- new DomOptions({
- styles: {
- display: "grid",
- gap: "15px",
- gridTemplateColumns: "repeat(2,1fr)",
- marginBottom: "20px",
- },
- }),
- );
- const details = [
- { label: "Price", value: `${house.price} €` },
- { label: "Building Type", value: house.buildingType },
- { label: "Construction Year", value: house.constructionYear?.toString() ?? "N/A" },
- { label: "Living Area", value: `${house.livingArea} m²` },
- { label: "District", value: house.district },
- { label: "Rooms", value: house.rooms?.toString() ?? "N/A" },
- { label: "Price", value: `${house.price} €` },
- { label: "Building Type", value: house.buildingType },
- { label: "Construction Year", value: house.constructionYear?.toString() ?? "N/A" },
- { label: "Living Area", value: `${house.livingArea} m²` },
- { label: "District", value: house.district },
- { label: "Rooms", value: house.rooms?.toString() ?? "N/A" },
- ];
- for (const { label, value } of details) {
- const item = Dom.div(
- new DomOptions({
- children: [
- Dom.span(
- label,
- new DomOptions({
- styles: { fontSize: "14px", fontWeight: "bold", marginBottom: "4px" },
- }),
- ),
- Dom.span(value, new DomOptions({ styles: { color: "#333", fontSize: "14px" } })),
- ],
- }),
- );
- grid.appendChild(item);
- }
- frag.appendChild(grid);
- frag.appendChild(
- Dom.div(
- new DomOptions({
- children: [
- Dom.span(
- "Description",
- new DomOptions({
- styles: { fontSize: "14px", fontWeight: "bold", marginBottom: "5px" },
- }),
- ),
- Dom.p(
- house.description ?? "No description available.",
- new DomOptions({
- styles: { color: "#333", fontSize: "14px", lineHeight: "1.4", marginTop: "5px" },
- }),
- ),
- ],
- styles: { marginBottom: "20px" },
- }),
- ),
- );
-
- if (house.images?.length) {
- const imgSect = Dom.div(
- new DomOptions({
- children: [
- Dom.div(
- new DomOptions({
- id: "img_title",
- styles: { fontSize: "14px", fontWeight: "bold", marginBottom: "10px" },
- }),
- ),
- Dom.div(
- new DomOptions({
- children: house.images.slice(0, 3).map((src) => {
- return Dom.img(
- src,
- new DomOptions({
- attributes: { loading: "lazy" },
- styles: { borderRadius: "4px", flexShrink: "0", height: "100px" },
- }),
- );
- }),
-
- styles: { display: "flex", gap: "10px", overflowX: "auto", paddingBottom: "5px" },
- }),
- ),
- ],
- styles: { marginBottom: "20px" },
- }),
- );
- frag.appendChild(imgSect);
- }
-
- return frag;
- }
-
- /**
- * @param {House} house
- * @param {boolean} persistent
- * @param {object} positionStyles
- * @param {() => void} onHide
- * @param {() => void} onClearMapTimer
- */
- constructor(house, persistent, positionStyles, onHide, onClearMapTimer) {
- this.#house = house;
- this.#persistent = persistent;
- this.#onHide = onHide;
- this.#onClearMapTimer = onClearMapTimer;
- this.#abortController = new AbortController();
- this.#dialog = document.createElement("dialog");
-
- Object.assign(
- this.#dialog.style,
- {
- background: "white",
- border: "none",
- borderRadius: "8px",
- boxShadow: "0 4px 20px rgba(0,0,0,0.2)",
- maxHeight: "80vh",
- maxWidth: "600px",
- overflowY: "auto",
- padding: "20px",
- position: "fixed",
- top: "50%",
- transform: "translateY(-50%)",
- width: "90%",
- zIndex: "1000",
- },
- positionStyles,
- );
-
- this.#dialog.append(
- Dom.button(
- "x",
- () => this.hide(),
- new DomOptions({
- id: "close-modal-btn",
- styles: {
- background: "none",
- border: "none",
- color: "#666",
- cursor: "pointer",
- fontSize: "24px",
- position: "absolute",
- right: "10px",
- top: "10px",
- },
- }),
- ),
- Modal.buildModalContent(house),
- );
-
- // Add event listeners with AbortController
- this.#dialog.addEventListener("close", () => this.hide(), {
- signal: this.#abortController.signal,
- });
- this.#dialog.addEventListener(
- "mouseenter",
- () => {
- clearTimeout(this.#timer);
- this.#onClearMapTimer();
- },
- { signal: this.#abortController.signal },
- );
- this.#dialog.addEventListener(
- "mouseleave",
- () => {
- if (!this.#persistent) {
- this.#timer = setTimeout(() => this.hide(), 200);
- }
- },
- { signal: this.#abortController.signal },
- );
- }
-
- render() {
- return this.#dialog;
- }
-
- show() {
- if (this.#persistent) {
- this.#dialog.showModal();
- } else {
- this.#dialog.show();
- }
- }
-
- hide() {
- clearTimeout(this.#timer);
- this.#dialog.close();
- this.#dialog.remove();
- this.#abortController.abort();
- this.#onHide();
- }
-}