diff options
| author | Petri Hienonen <petri.hienonen@gmail.com> | 2025-11-09 07:09:23 +0200 |
|---|---|---|
| committer | Petri Hienonen <petri.hienonen@gmail.com> | 2025-11-09 07:09:23 +0200 |
| commit | 843faa37b2bf376f6fbb58637c75692b29c14858 (patch) | |
| tree | 804ea735b6e9c3d3f3fa88e0c0bcf8d75bc88c18 | |
| parent | 644652342e4d57f2c3c9f70969596937823aa207 (diff) | |
| download | housing-843faa37b2bf376f6fbb58637c75692b29c14858.tar.zst | |
Update
| -rw-r--r-- | app/dom.js | 288 | ||||
| -rw-r--r-- | app/main.js | 32 |
2 files changed, 161 insertions, 159 deletions
@@ -220,21 +220,20 @@ export class Widgets { * Show toast notification * @param {string} message * @param {ToastType} [type=ToastType.error] - * @param {number} [duration=5000] */ - static showToast(message, type = ToastType.error, duration = 5000) { - document.getElementById("app-toast")?.remove(); - - const bg = - type === ToastType.error ? "#f44336" : type === ToastType.warning ? "#ff9800" : "#4caf50"; - - const toast = Dom.div( + static toast(message, type = ToastType.error) { + return Dom.div( new DomOptions({ children: [Dom.p(message)], classes: ["toast", `toast-${type}`], id: "app-toast", styles: { - background: bg, + background: + type === ToastType.error + ? "#f44336" + : type === ToastType.warning + ? "#ff9800" + : "#4caf50", borderRadius: "4px", boxShadow: "0 4px 12px rgba(0,0,0,0.15)", color: "white", @@ -250,15 +249,6 @@ export class Widgets { }, }), ); - - document.body.appendChild(toast); - - setTimeout(() => { - if (toast.parentNode) { - toast.style.opacity = "0"; - setTimeout(() => toast.remove(), 300); - } - }, duration); } /** @@ -279,13 +269,6 @@ export class Widgets { * @returns {HTMLElement} */ static slider(id, labelText, weightKey, initialValue, onChange) { - const group = Dom.div( - new DomOptions({ - styles: { display: "flex", flexDirection: "column", marginBottom: "1rem" }, - }), - ); - - const label = Dom.label(id, labelText); const output = Dom.span( initialValue.toFixed(1), new DomOptions({ @@ -294,37 +277,48 @@ export class Widgets { }), ); - const labelTextSpan = Dom.span( - labelText, - new DomOptions({ - styles: { fontSize: "0.85rem" }, - }), - ); - - label.append(labelTextSpan, " ", output); - - const slider = Dom.input( - "range", - (e) => { - const target = /** @type {HTMLInputElement} */ (e.target); - const val = Number(target.value); - output.textContent = val.toFixed(1); - onChange(weightKey, val); - }, - "", - "", + return Dom.div( new DomOptions({ - attributes: { max: "1", min: "0", step: "0.1", value: initialValue.toString() }, - id, - styles: { - margin: "0.5rem 0", - width: "100%", - }, + 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" }, }), ); - - group.append(label, slider); - return group; } /** @@ -334,7 +328,7 @@ export class Widgets { * @param {(value: number | null) => void} onChange * @returns {HTMLElement} */ - static addNumberFilter(id, labelText, onChange) { + static numberFilter(id, labelText, onChange) { return Dom.div( new DomOptions({ children: [ @@ -394,41 +388,41 @@ export class Modal { */ static buildModalContent(house) { const frag = document.createDocumentFragment(); - - /* Header */ - const header = Dom.div( - new DomOptions({ - styles: { - alignItems: "center", - display: "flex", - justifyContent: "space-between", - marginBottom: "20px", - }, - }), - ); - - const title = Dom.heading( - 2, - house.address, - new DomOptions({ - styles: { color: "#333", fontSize: "20px", margin: "0" }, - }), - ); - const score = Dom.span( - `Score: ${house.scores.current.toFixed(1)}`, - new DomOptions({ - styles: { - background: "#e8f5e9", - borderRadius: "4px", - color: "#2e7d32", - fontSize: "16px", - fontWeight: "bold", - padding: "4px 8px", - }, - }), + 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", + }, + }), + ), ); - header.append(title, score); - frag.appendChild(header); const grid = Dom.div( new DomOptions({ @@ -471,49 +465,57 @@ export class Modal { grid.appendChild(item); } frag.appendChild(grid); - - /* Description */ - const descSect = Dom.div(new DomOptions({ styles: { marginBottom: "20px" } })); - const descTitle = Dom.span( - "Description", - new DomOptions({ - styles: { fontSize: "14px", fontWeight: "bold", marginBottom: "5px" }, - }), - ); - const descText = Dom.p( - house.description ?? "No description available.", - new DomOptions({ - styles: { color: "#333", fontSize: "14px", lineHeight: "1.4", marginTop: "5px" }, - }), + 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" }, + }), + ), ); - descSect.append(descTitle, descText); - frag.appendChild(descSect); - /* Images */ if (house.images?.length) { - const imgSect = Dom.div(new DomOptions({ styles: { marginBottom: "20px" } })); - const imgTitle = Dom.div( + const imgSect = Dom.div( new DomOptions({ - styles: { fontSize: "14px", fontWeight: "bold", marginBottom: "10px" }, - }), - ); - const imgCont = Dom.div( - new DomOptions({ - styles: { display: "flex", gap: "10px", overflowX: "auto", paddingBottom: "5px" }, + 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" }, }), ); - for (const src of house.images.slice(0, 3)) { - imgCont.appendChild( - Dom.img( - src, - new DomOptions({ - attributes: { loading: "lazy" }, - styles: { borderRadius: "4px", flexShrink: "0", height: "100px" }, - }), - ), - ); - } - imgSect.append(imgTitle, imgCont); frag.appendChild(imgSect); } @@ -555,25 +557,27 @@ export class Modal { positionStyles, ); - const closeBtn = Dom.button( - "x", - () => this.hide(), - new DomOptions({ - styles: { - background: "none", - border: "none", - color: "#666", - cursor: "pointer", - fontSize: "24px", - position: "absolute", - right: "10px", - top: "10px", - }, - }), + 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), ); - this.#dialog.append(closeBtn, Modal.buildModalContent(house)); - // Add event listeners with AbortController this.#dialog.addEventListener("close", () => this.hide(), { signal: this.#abortController.signal, diff --git a/app/main.js b/app/main.js index c8a10a3..30770ed 100644 --- a/app/main.js +++ b/app/main.js @@ -27,8 +27,8 @@ export class App { #weights = new Weights(); /** @type {District[]} */ #districts = []; - /** @type {MapEl|null} */ - #map = null; + /** @type {MapEl} */ + #map; /** @type {HTMLElement} */ #stats; /** @type {HTMLElement} */ @@ -259,12 +259,12 @@ export class App { Dom.div( new DomOptions({ children: [ - Widgets.addNumberFilter("min-price", "Min price (€)", (v) => { + Widgets.numberFilter("min-price", "Min price (€)", (v) => { filters.minPrice = v ?? 0; onFilterChange(); }), - Widgets.addNumberFilter("max-price", "Max price (€)", (v) => { + Widgets.numberFilter("max-price", "Max price (€)", (v) => { filters.maxPrice = v ?? Number.POSITIVE_INFINITY; onFilterChange(); }), @@ -276,12 +276,12 @@ export class App { }, }), ), - Widgets.addNumberFilter("min-year", "Min year", (v) => { + Widgets.numberFilter("min-year", "Min year", (v) => { filters.minYear = v ?? 0; onFilterChange(); }), - Widgets.addNumberFilter("min-area", "Min area (m²)", (v) => { + Widgets.numberFilter("min-area", "Min area (m²)", (v) => { filters.minArea = v ?? 0; onFilterChange(); }), @@ -464,17 +464,15 @@ export class App { this.#filtered = houses.slice(); - if (this.#map) { - this.#map.initialize( - districts, - coastLine, - mainRoads, - trainTracks, - trainStations, - houses, - this.#colorParameter, - ); - } + this.#map.initialize( + districts, + coastLine, + mainRoads, + trainTracks, + trainStations, + houses, + this.#colorParameter, + ); // Populate district multi-select const districtOptions = App.#renderDistrictOptions(this.#districts, this.#houses); |
