Compare commits
10 Commits
2d5f11f5fc
...
dd79fc826d
Author | SHA1 | Date | |
---|---|---|---|
dd79fc826d | |||
fa10db953a | |||
404cea44e3 | |||
f8a99113bf | |||
aeccce0f89 | |||
9e6b9e7c27 | |||
427bf68941 | |||
15f4a9d3ab | |||
15d0cdccb5 | |||
19e75f9647 |
BIN
frontend/assets/logo.png
Normal file
BIN
frontend/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
@ -15,22 +15,26 @@
|
||||
<body>
|
||||
<div id="topbar">
|
||||
<h1>Postnummer App</h1>
|
||||
<div class="spacer"></div>
|
||||
<button id="dropdown-button">≡</button>
|
||||
<div id="dropdown">
|
||||
<a href="https://tpho.dk">based site</a>
|
||||
<a href="https://tpho.dk">based site</a>
|
||||
</div>
|
||||
</div>
|
||||
<main>
|
||||
<form id="search-bar">
|
||||
<input id="search-input" type="text" placeholder="Postnummer" maxlength="4">
|
||||
<button id="search-button" type="submit">Search</button>
|
||||
</form>
|
||||
<img src="assets/map.jpg" id="map"><br>
|
||||
<img src="assets/map.jpg" id="map">
|
||||
<div id="dot"></div>
|
||||
<div id="info">
|
||||
<span id="mouse-position"></span>
|
||||
<br>
|
||||
<span id="coords"></span>
|
||||
<br>
|
||||
<span id="zip-code"></span>
|
||||
<br>
|
||||
<p id="zip-code">Postnummer ikke fundet</p>
|
||||
<p id="mouse-position"></p>
|
||||
<p id="coords"></p>
|
||||
</div>
|
||||
</main>
|
||||
<div id="tooltip"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
BIN
frontend/logo.ico
Normal file
BIN
frontend/logo.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
24
frontend/src/Tooltip.ts
Normal file
24
frontend/src/Tooltip.ts
Normal file
@ -0,0 +1,24 @@
|
||||
const OFFSET = 12;
|
||||
|
||||
export class Tooltip {
|
||||
private timeout: number | null = null;
|
||||
|
||||
constructor(private element: HTMLElement) {
|
||||
document.body.addEventListener("mousemove", (event: MouseEvent) => {
|
||||
this.element.style.opacity = "1";
|
||||
this.element.style.left = event.x + OFFSET + "px";
|
||||
this.element.style.top = event.y + OFFSET + "px";
|
||||
|
||||
if (this.timeout) clearTimeout(this.timeout);
|
||||
this.timeout = setTimeout(() => {
|
||||
this.element.style.opacity = "0.8";
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
setText(text: string) {
|
||||
this.element.style.display = text ? "block" : "none";
|
||||
this.element.innerHTML = text;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
import { Throttler } from "./Throttler";
|
||||
import { setTopbarOffset, addToggleDropdownListener } from "./topbar";
|
||||
import { Coordinate, Position, convertPixelsToCoordinate, convertCoordinateToPixels } from "./coordinates";
|
||||
import { Size } from "./size";
|
||||
import { Tooltip } from "./Tooltip";
|
||||
|
||||
const tooltip = new Tooltip(document.getElementById("tooltip")!);
|
||||
|
||||
type ZipCodeReverseResponse = {
|
||||
nr: number | null;
|
||||
@ -13,7 +17,7 @@ async function fetchZipCode({
|
||||
latitude,
|
||||
}: Coordinate): Promise<ZipCodeReverseResponse> {
|
||||
return fetch(
|
||||
`https://api.dataforsyningen.dk/postnumre/reverse?x=${longitude}&y=${latitude}`,
|
||||
`https://api.dataforsyningen.dk/postnumre/reverse?x=${longitude}&y=${latitude}&landpostnumre`,
|
||||
)
|
||||
.then((request) => request.json())
|
||||
.then((data) => {
|
||||
@ -31,9 +35,9 @@ function displayMousePosition(element: HTMLParagraphElement, mouse: Position) {
|
||||
}
|
||||
|
||||
function displayCoords(element: HTMLParagraphElement, coords: Coordinate) {
|
||||
element.innerHTML = `Coords: <code>${coords.longitude.toFixed(
|
||||
3,
|
||||
)}, ${coords.latitude.toFixed(3)}</code>`;
|
||||
const longitude = coords.longitude.toFixed(3);
|
||||
const latitude = coords.latitude.toFixed(3);
|
||||
element.innerHTML = `Coords: <code>${longitude}, ${latitude}</code>`;
|
||||
}
|
||||
|
||||
function displayZipCode(
|
||||
@ -47,11 +51,11 @@ function displayZipCode(
|
||||
? `Postnummer ikke fundet`
|
||||
: `Postnummer: <code>${zipCode}</code>, ${name}`;
|
||||
|
||||
if (center == null) return;
|
||||
tooltip.setText(zipCode ? `<code>${zipCode}</code> ${name}` : "");
|
||||
|
||||
const dot = document.getElementById("dot")!;
|
||||
|
||||
if (!zipCode) {
|
||||
if (!center) {
|
||||
dot.style.display = "none";
|
||||
return;
|
||||
}
|
||||
@ -65,8 +69,8 @@ function displayZipCode(
|
||||
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) + "px";
|
||||
dot.style.left = position.x + rect.left + "px";
|
||||
dot.style.top = position.y + rect.top + document.documentElement.scrollTop + "px";
|
||||
}
|
||||
|
||||
function setupMap(
|
||||
@ -75,7 +79,7 @@ function setupMap(
|
||||
zipCodeElement: HTMLParagraphElement,
|
||||
) {
|
||||
const mapImg = document.querySelector<HTMLImageElement>("#map")!;
|
||||
const fetcher = new Throttler(500);
|
||||
const fetcher = new Throttler(200);
|
||||
|
||||
mapImg.onmousemove = async (event: MouseEvent) => {
|
||||
const mousePosition: Position = { x: event.offsetX, y: event.offsetY };
|
||||
@ -120,9 +124,10 @@ function setupMap(
|
||||
|
||||
mapImg.onmouseleave = (_event: MouseEvent) => {
|
||||
document.getElementById("dot")!.style.display = "none";
|
||||
[mousePositionElement, coordsElement, zipCodeElement].forEach(
|
||||
[mousePositionElement, coordsElement].forEach(
|
||||
(e) => (e.innerHTML = ""),
|
||||
);
|
||||
zipCodeElement.innerHTML = "Postnummer ikke fundet";
|
||||
};
|
||||
}
|
||||
|
||||
@ -167,6 +172,8 @@ function main() {
|
||||
|
||||
setupSearchBar(zipCodeElement);
|
||||
setupMap(mousePositionElement, coordsElement, zipCodeElement);
|
||||
setTopbarOffset();
|
||||
addToggleDropdownListener();
|
||||
}
|
||||
|
||||
main();
|
||||
|
15
frontend/src/topbar.ts
Normal file
15
frontend/src/topbar.ts
Normal file
@ -0,0 +1,15 @@
|
||||
// handles automatically sizing the topbar
|
||||
|
||||
export function setTopbarOffset() {
|
||||
const height = document.getElementById("topbar")!.getBoundingClientRect().height;
|
||||
const root: HTMLElement = document.querySelector(":root")!;
|
||||
root.style.setProperty("--topbar-offset", `${height}px`);
|
||||
}
|
||||
|
||||
export function addToggleDropdownListener() {
|
||||
const element = document.getElementById("dropdown-button")!;
|
||||
const dropdown = document.getElementById("dropdown")!;
|
||||
element.addEventListener("click", () => {
|
||||
dropdown.classList.toggle("enabled");
|
||||
});
|
||||
}
|
@ -8,40 +8,114 @@
|
||||
src: url("assets/JetBrainsMono-Bold.woff2");
|
||||
}
|
||||
|
||||
* {
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--brand-200: #D55D91;
|
||||
--brand-300: #D14D86;
|
||||
--brand: #C33271;
|
||||
--brand-700: #0D3B45;
|
||||
--brand-800: #0A2E36;
|
||||
--brand-900: #061D22;
|
||||
--light: #FFF;
|
||||
--topbar-offset: 4rem;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
font-family: "Open Sans", sans-serif;
|
||||
background-color: #E1F5FE;
|
||||
background-color: var(--brand-900);
|
||||
color: var(--light);
|
||||
}
|
||||
|
||||
#topbar {
|
||||
background-color: #03A9F4;
|
||||
color: white;
|
||||
display: flex;
|
||||
background-color: var(--brand);
|
||||
color: var(--light);
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
padding: 0.5rem;
|
||||
box-shadow: 0px 3px 3px #01579B;
|
||||
padding: 1rem 2rem;
|
||||
box-shadow: 0px 5px 1px RGBA(127, 127, 127, 0.2);
|
||||
}
|
||||
|
||||
#topbar > h1 {
|
||||
#topbar h1 {
|
||||
align-self: center;
|
||||
margin: 0;
|
||||
font-size: 2rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#dropdown-button {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
font-size: 2rem;
|
||||
transform: scale(1.5);
|
||||
color: inherit;
|
||||
margin-left: auto;
|
||||
padding: 0 0.5rem;
|
||||
transition: transform 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#dropdown-button:hover, #dropdown-button:focus {
|
||||
transform: scale(1.7);
|
||||
}
|
||||
|
||||
#dropdown-button:focus-visible {
|
||||
outline: none;
|
||||
background-color: var(--brand-300);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#dropdown-button:active {
|
||||
transform: scale(2);
|
||||
}
|
||||
|
||||
#dropdown {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
top: var(--topbar-offset);
|
||||
right: 0;
|
||||
box-shadow: 0px 5px 1px RGBA(127, 127, 127, 0.2);
|
||||
transform: scaleY(0);
|
||||
transition: transform 0.2s;
|
||||
transform-origin: 0% 0%;
|
||||
border-radius: 0 0 0 5px;
|
||||
}
|
||||
|
||||
#dropdown.enabled, #dropdown:focus-within {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
|
||||
#dropdown a {
|
||||
background-color: var(--brand);
|
||||
color: var(--light);
|
||||
padding: 1rem 1rem;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#dropdown a:hover, #dropdown a:focus {
|
||||
background-color: var(--brand-300);
|
||||
}
|
||||
|
||||
#dropdown a:last-child {
|
||||
border-radius: 0 0 0 5px;
|
||||
}
|
||||
|
||||
|
||||
main {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
margin: auto;
|
||||
width: 50%;
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
main > * {
|
||||
margin: 2px;
|
||||
margin-block: 2px;
|
||||
}
|
||||
|
||||
code {
|
||||
@ -53,63 +127,57 @@ code {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 15px;
|
||||
filter: drop-shadow(0 2px 2px #9E9E9E);
|
||||
}
|
||||
|
||||
#search-input {
|
||||
background-color: white;
|
||||
padding: 5px;
|
||||
transition: background-color 0.2s;
|
||||
background-color: var(--brand-800);
|
||||
flex: 1;
|
||||
border: 1px solid #666;
|
||||
color: var(--light);
|
||||
border-radius: 5px 0 0 5px;
|
||||
border: none;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
#search-input:focus {
|
||||
background-color: #FAFAFA;
|
||||
background-color: var(--brand-700);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#search-button {
|
||||
cursor: pointer;
|
||||
width: 5rem;
|
||||
background-color: #03A9F4;
|
||||
color: white;
|
||||
background-color: var(--brand);
|
||||
color: var(--light);
|
||||
border: none;
|
||||
border-radius: 0 5px 5px 0;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
#search-button:hover, #search-button:focus {
|
||||
background-color: #039BE5;
|
||||
background-color: var(--brand-300);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#search-button:active {
|
||||
background-color: #0288D1;
|
||||
background-color: var(--brand-200);
|
||||
}
|
||||
|
||||
#map {
|
||||
width: 100%;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 4px #9E9E9E;
|
||||
border: 1px solid #7E7E7E;
|
||||
}
|
||||
|
||||
#info {
|
||||
background-color: white;
|
||||
padding: 5px;
|
||||
background-color: var(--brand-800);
|
||||
padding: 1rem;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #7E7E7E;
|
||||
box-shadow: 0 2px 2px #9E9E9E;
|
||||
}
|
||||
|
||||
#info {
|
||||
font-size: 1.1em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#mouse-position, #coords {
|
||||
color: #7E7E7E;
|
||||
color: white;
|
||||
opacity: 0.5;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
@ -118,12 +186,23 @@ code {
|
||||
height: 15px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid black;
|
||||
background-color: var(--brand);
|
||||
filter: drop-shadow(1px 1px 2px black);
|
||||
background-color: red;
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#tooltip {
|
||||
display: none;
|
||||
position: fixed;
|
||||
background-color: white;
|
||||
color: black;
|
||||
padding: 5px;
|
||||
border: 1px solid black;
|
||||
box-shadow: 2px 2px 2px black;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
main {
|
||||
width: 100%;
|
||||
|
Loading…
Reference in New Issue
Block a user