From a5b78007849e566dab8b3223e09c3b7c6cafecab Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 13 Feb 2023 01:48:24 +0100 Subject: [PATCH] frontend: refactoring --- frontend/index.html | 24 --- frontend/src/main.ts | 341 +++++++++++++++++++++++++++++-------------- 2 files changed, 231 insertions(+), 134 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index e504d7a..1047e98 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -15,29 +15,5 @@ -
-

Postnummer App

-
- - -
-
- - -
-
-
-

Postnummer ikke fundet

-

-

-
-
-
diff --git a/frontend/src/main.ts b/frontend/src/main.ts index f0e0ac5..5adcd1e 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -120,138 +120,259 @@ function displayZipCode( boundaryElem.style.height = `${bottomleft.y - topright.y}px`; } -function setupMap( - mousePositionElement: HTMLParagraphElement, - coordsElement: HTMLParagraphElement, - zipCodeElement: HTMLParagraphElement, -) { - const mapImg = domSelect("#map")!; - const fetcher = new Throttler(200); +type Component = { + children?: Component[], + render(): string, + hydrate?(update: (action?: () => Promise) => Promise): Promise, +}; - mapImg.addEventListener('mousemove', async (event: MouseEvent) => { - const mousePosition: Position = { x: event.offsetX, y: event.offsetY }; - displayMousePosition(mousePositionElement, mousePosition); - - const mapSize: Size = { - width: mapImg.clientWidth, - height: mapImg.clientHeight, - }; - - const coords = convertPixelsToCoordinate(mousePosition, mapSize); - displayCoords(coordsElement, coords); - - fetcher.call(async () => await fetchAndDisplayZipCode(coords)); - }); - - mapImg.addEventListener("mouseup", async (event: MouseEvent) => { - const mousePosition: Position = { x: event.offsetX, y: event.offsetY }; - displayMousePosition(mousePositionElement, mousePosition); - - const mapSize: Size = { - width: mapImg.clientWidth, - height: mapImg.clientHeight, - }; - - const coords = convertPixelsToCoordinate(mousePosition, mapSize); - displayCoords(coordsElement, coords); - - fetcher.call(async () => await fetchAndDisplayZipCode(coords)); - }); - - mapImg.addEventListener("mouseleave", (_event: MouseEvent) => { - displayZipCode(zipCodeElement, null, null, null, null); - }); +function makeReviewsPage(): Component { + return { + render: () => /*html*/` +

Anmeldelser

+
${loadReviews()}
+ `, + } } -function setupSearchBar(zipCodeElement: HTMLParagraphElement) { - const searchBar = - domSelect("#search-bar")!; - const searchInput = - domSelect("#search-input")!; +function makeSearchBar(updateZipCodeInfo: (zipCode: string) => Promise): Component { + return { + render: () => /*html*/` + + `, + async hydrate(update) { + const searchBar = + domSelect("#search-bar")!; + const searchInput = + domSelect("#search-input")!; - searchInput.addEventListener("keydown", (event) => { - if (event.key.length === 1 && !"0123456789".includes(event.key)) - event.preventDefault(); - }); + searchInput.addEventListener("keydown", (event) => { + if (event.key.length === 1 && !"0123456789".includes(event.key)) + event.preventDefault(); + }); - searchBar.addEventListener("submit", async (event: Event) => { - event.preventDefault(); + searchBar.addEventListener("submit", async (event: Event) => { + event.preventDefault(); + update(() => updateZipCodeInfo(searchInput.value)); + }); + }, + }; +} - const inputValue = searchInput.value; +type ZipCodeInfo = { + zipCode: number | null, + name: string | null, + center: Coordinate | null, + boundary: Square | null, +} + +function makeInfoBox(info: ZipCodeInfo): Component { + const { zipCode, name, center, boundary } = info; + return { + render: () => /*html*/` +
+

${info.zipCode === null ? "Postnummer ikke fundet" : `Postnummer: ${zipCode}, ${name}`}

+

+

+
+ `, + async hydrate() { + tooltip.setText(zipCode ? `${zipCode} ${name}` : ""); + + const dot = document.getElementById("dot")!; + const boundaryElem = document.getElementById("boundary")!; + + if (!center || !boundary) { + dot.style.display = "none"; + boundaryElem.style.display = "none"; + return; + } + + const mapImg = document.getElementById("map")!; + const mapSize: Size = { + width: mapImg.clientWidth, + height: mapImg.clientHeight, + }; + + // Draw dot + const position = convertCoordinateToPixels(center, mapSize); + const rect = document.getElementById("map")!.getBoundingClientRect(); + dot.style.display = "block"; + dot.style.left = `${position.x + rect.left}px`; + dot.style.top = `${position.y + rect.top + document.documentElement.scrollTop}px`; + + // Draw boundary + const bottomleft = convertCoordinateToPixels({ longitude: boundary.x1, latitude: boundary.y1 }, mapSize); + const topright = convertCoordinateToPixels({ longitude: boundary.x2, latitude: boundary.y2 }, mapSize); + + boundaryElem.style.display = "block"; + boundaryElem.style.left = `${bottomleft.x + rect.left}px`; + boundaryElem.style.top = `${topright.y + rect.top + document.documentElement.scrollTop}px`; + boundaryElem.style.width = `${topright.x - bottomleft.x}px`; + boundaryElem.style.height = `${bottomleft.y - topright.y}px`; + } + } +} + + +function makeMainPage(): Component { + const zipCodeInfo: ZipCodeInfo = { + zipCode: null, + name: null, + center: null, + boundary: null, + } + const searchBar = makeSearchBar(async (searchInputValue) => { + const inputValue = searchInputValue; if (!/^\d+$/.test(inputValue)) return; const data = await fetch( `https://api.dataforsyningen.dk/postnumre?nr=${inputValue}`, ).then((response) => response.json()) - displayZipCode( - zipCodeElement, - data.length ? parseInt(data[0]["nr"]) : null, - data.length ? data[0]["navn"] : null, - data.length ? { longitude: data[0]["visueltcenter"][0], latitude: data[0]["visueltcenter"][1] } : null, - data.length ? { x1: data[0]["bbox"][0], y1: data[0]["bbox"][1], x2: data[0]["bbox"][2], y2: data[0]["bbox"][3] } : null, - ); - + zipCodeInfo.zipCode = data.length ? parseInt(data[0]["nr"]) : null; + zipCodeInfo.name = data.length ? data[0]["navn"] : null; + zipCodeInfo.center = data.length ? { longitude: data[0]["visueltcenter"][0], latitude: data[0]["visueltcenter"][1] } : null; + zipCodeInfo.boundary = data.length ? { x1: data[0]["bbox"][0], y1: data[0]["bbox"][1], x2: data[0]["bbox"][2], y2: data[0]["bbox"][3] } : null; }); -} - -function pageRedirects() { - const reviewRedirect = document.getElementById("reviews-redirect")! - const mapRedirect = document.getElementById("map-redirect")! - const mainElement = document.getElementById("main")! - - - reviewRedirect.addEventListener("click", () => { - mainElement.innerHTML = /*html*/` -

Anmeldelser

-
${loadReviews()}
- `; - const dropdown = document.getElementById("dropdown")!; - dropdown.classList.remove("enabled"); - - }); - - mapRedirect.addEventListener("click", () => { - mainElement.innerHTML = /*html*/` - + const infoBox = makeInfoBox(zipCodeInfo); + return { + children: [searchBar, infoBox], + render: () => /*html*/` + ${searchBar.render()}
-
-

Postnummer ikke fundet

-

-

-
- `; - const [mousePositionElement, coordsElement, zipCodeElement] = [ - "#mouse-position", - "#coords", - "#zip-code", - ].map((id) => domSelect(id)!); - setupSearchBar(zipCodeElement); - setupMap(mousePositionElement, coordsElement, zipCodeElement); - const dropdown = document.getElementById("dropdown")!; - dropdown.classList.remove("enabled"); + `, + async hydrate() { + const mousePositionElement = domSelect("#mouse-position")!; + const coordsElement = domSelect("#coords")!; + const zipCodeElement = domSelect("#zip-code")!; + const mapImg = domSelect("#map")!; + const fetcher = new Throttler(200); - }) + mapImg.addEventListener('mousemove', async (event: MouseEvent) => { + const mousePosition: Position = { x: event.offsetX, y: event.offsetY }; + displayMousePosition(mousePositionElement, mousePosition); + + const mapSize: Size = { + width: mapImg.clientWidth, + height: mapImg.clientHeight, + }; + + const coords = convertPixelsToCoordinate(mousePosition, mapSize); + displayCoords(coordsElement, coords); + + fetcher.call(async () => await fetchAndDisplayZipCode(coords)); + }); + + mapImg.addEventListener("mouseup", async (event: MouseEvent) => { + const mousePosition: Position = { x: event.offsetX, y: event.offsetY }; + displayMousePosition(mousePositionElement, mousePosition); + + const mapSize: Size = { + width: mapImg.clientWidth, + height: mapImg.clientHeight, + }; + + const coords = convertPixelsToCoordinate(mousePosition, mapSize); + displayCoords(coordsElement, coords); + + fetcher.call(async () => await fetchAndDisplayZipCode(coords)); + }); + + mapImg.addEventListener("mouseleave", (_event: MouseEvent) => { + displayZipCode(zipCodeElement, null, null, null, null); + }); + }, + }; +} + +function makeRouter(route: "main" | "reviews"): Component { + switch (route) { + case "main": + { + const mainPage = makeMainPage(); + return { + children: [mainPage], + render: () => mainPage.render(), + }; + } + case "reviews": { + const reviewsPage = makeReviewsPage(); + return { + children: [reviewsPage], + render: () => reviewsPage.render(), + } + } + } +} + +function makeTooltip(): Component { + return { + render: () => /*html*/` +
+ `, + } +} + +function makeLayout(): Component { + let route: "main" | "reviews" = "main"; + const router = makeRouter(route); + const tooltip = makeTooltip(); + return { + children: [router, tooltip], + render: () => /**/` +
+

Postnummer App

+
+ + +
+
+ ${router.render()} +
+ ${tooltip.render()} + `, + async hydrate(update) { + setTopbarOffset(); + addToggleDropdownListener(); + document.getElementById("reviews-redirect")!.addEventListener('click', () => { + update(async () => route = "reviews"); + }) + document.getElementById("map-redirect")!.addEventListener('click', () => { + update(async () => route = "main"); + }) + }, + } +} + +function hydrateComponentAndChildren(component: Component, update: (action?: () => Promise) => Promise) { + component.hydrate && component.hydrate(update); + component.children?.forEach((child) => hydrateComponentAndChildren(child, update)); +} + +function renderComponent(element: HTMLElement, component: Component) { + element.innerHTML = component.render(); + window.requestAnimationFrame(() => { + hydrateComponentAndChildren(component, async (action) => { + action && await action(); + element.innerHTML = component.render(); + }); + }); } function main() { if (navigator.userAgent.match("Chrome")) { location.href = "https://mozilla.org/firefox"; } - - const mousePositionElement = domSelect("#mouse-position")!; - const coordsElement = domSelect("#coords")!; - const zipCodeElement = domSelect("#zip-code")!; - - setupSearchBar(zipCodeElement); - setupMap(mousePositionElement, coordsElement, zipCodeElement); - setTopbarOffset(); - addToggleDropdownListener(); - pageRedirects(); + const bodyElement = document.querySelector("body")!; + renderComponent(bodyElement, makeLayout()); } main();