backend: backend fixies
This commit is contained in:
parent
28a91d9f6b
commit
c82b4c1023
@ -1,9 +1,11 @@
|
||||
#include "tcp.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
@ -36,10 +38,12 @@ TcpServer* tcp_server_create(const char* ip, uint16_t port)
|
||||
server_address.sin_port = htons(port);
|
||||
if (bind(server_socket, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) {
|
||||
printf("error: tcp: could not bind socket\n");
|
||||
close(server_socket);
|
||||
return NULL;
|
||||
}
|
||||
if (listen(server_socket, SOMAXCONN) < 0) {
|
||||
if (listen(server_socket, SOMAXCONN - 1) < 0) {
|
||||
printf("error: tcp: could not listen on server\n");
|
||||
close(server_socket);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -59,12 +63,13 @@ void tcp_server_destroy(TcpServer* server)
|
||||
|
||||
TcpConnection* tcp_server_accept(TcpServer* server)
|
||||
{
|
||||
struct sockaddr_in client_address;
|
||||
socklen_t client_size;
|
||||
struct sockaddr_in client_address = { 0 };
|
||||
socklen_t client_size = 0;
|
||||
int client_socket
|
||||
= accept(server->server_socket, (struct sockaddr*)&client_address, &client_size);
|
||||
if (client_socket < 0) {
|
||||
printf("error: tcp: could not accept connection\n");
|
||||
printf("errno: %d %s\n", errno, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
248
backend/main.c
248
backend/main.c
@ -7,8 +7,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
const uint16_t port = 8000;
|
||||
|
||||
TcpServer* server = NULL;
|
||||
|
||||
void interrupt_handler(int a)
|
||||
@ -20,12 +18,132 @@ void interrupt_handler(int a)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void handle_client_connection(TcpConnection* connection)
|
||||
{
|
||||
|
||||
uint8_t buffer[8192] = { 0 };
|
||||
ssize_t recieved = tcp_recieve(connection, buffer, 8192);
|
||||
if (recieved < 0) {
|
||||
printf("error: could not recieve\n");
|
||||
return;
|
||||
} else if (recieved == 0) {
|
||||
printf("client disconnected\n");
|
||||
return;
|
||||
}
|
||||
HttpRequestHeader header = parse_http_request_header((char*)buffer, strlen((char*)buffer));
|
||||
char* path = calloc(header.path_length + 1, sizeof(char));
|
||||
strncpy(path, (char*)&buffer[header.path_index], header.path_length);
|
||||
if (strncmp(path, "/api", 4) == 0) {
|
||||
// something something api
|
||||
} else {
|
||||
if (strstr(path, "..") != NULL) {
|
||||
uint8_t send_buffer[]
|
||||
= "HTTP/1.1 400 BAD\r\n"
|
||||
"Content-Type: text/html\r\n"
|
||||
"\r\n"
|
||||
"<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Bad "
|
||||
"request</title></head><body><h1>Fuck you!</h1></body></html>\r\n";
|
||||
ssize_t written = tcp_send(connection, send_buffer, sizeof(send_buffer));
|
||||
if (written < 0) {
|
||||
printf("error: could not write\n");
|
||||
return;
|
||||
}
|
||||
} else if (header.path_length == 0 || strncmp(path, "/", header.path_length) == 0) {
|
||||
FILE* file = fopen("../frontend/index.html", "r");
|
||||
if (file == NULL) {
|
||||
printf("error: could not open file\n");
|
||||
return;
|
||||
}
|
||||
uint8_t send_buffer[] = "HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: text/html\r\n"
|
||||
"\r\n";
|
||||
ssize_t written = tcp_send(connection, send_buffer, sizeof(send_buffer));
|
||||
if (written < 0) {
|
||||
printf("error: could not write\n");
|
||||
return;
|
||||
}
|
||||
char char_read;
|
||||
while ((char_read = (char)fgetc(file)) != EOF) {
|
||||
tcp_send(connection, (uint8_t*)&char_read, sizeof(char));
|
||||
}
|
||||
} else {
|
||||
char rootpath[] = "../frontend";
|
||||
size_t filepath_size = sizeof(rootpath) + header.path_length + 1;
|
||||
char* filepath = calloc(filepath_size, sizeof(char));
|
||||
snprintf(filepath, filepath_size, "%s%s", rootpath, path);
|
||||
|
||||
char* dot = strrchr(path, '.');
|
||||
char mime_type[20] = { 0 };
|
||||
char file_flag[3] = { 'r', 0 };
|
||||
if (dot != NULL && strncmp(dot, ".html", 5) == 0) {
|
||||
snprintf(mime_type, 20, "text/html");
|
||||
} else if (dot != NULL && strncmp(dot, ".css", 4) == 0) {
|
||||
snprintf(mime_type, 20, "text/css");
|
||||
} else if (dot != NULL && strncmp(dot, ".js", 3) == 0) {
|
||||
snprintf(mime_type, 20, "text/javascript");
|
||||
} else if (dot != NULL && strncmp(dot, ".map", 4) == 0) {
|
||||
snprintf(mime_type, 20, "application/json");
|
||||
} else if (dot != NULL && strncmp(dot, ".ico", 4) == 0) {
|
||||
snprintf(mime_type, 20, "image/x-icon");
|
||||
} else if (dot != NULL && strncmp(dot, ".jpg", 4) == 0) {
|
||||
snprintf(mime_type, 20, "image/jpeg");
|
||||
file_flag[1] = 'b';
|
||||
} else if (dot != NULL && strncmp(dot, ".png", 4) == 0) {
|
||||
snprintf(mime_type, 20, "image/png");
|
||||
file_flag[1] = 'b';
|
||||
} else if (dot != NULL && strncmp(dot, ".woff2", 6) == 0) {
|
||||
snprintf(mime_type, 20, "font/woff2");
|
||||
file_flag[1] = 'b';
|
||||
} else {
|
||||
printf("error: unknown file type\n");
|
||||
return;
|
||||
}
|
||||
|
||||
FILE* file = fopen(filepath, file_flag);
|
||||
if (file == NULL) {
|
||||
printf("error: could not open file\n");
|
||||
return;
|
||||
}
|
||||
|
||||
char send_buffer_1[] = "HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: ";
|
||||
char send_buffer_2[] = "\r\n"
|
||||
"\r\n";
|
||||
ssize_t written = tcp_send(connection, (uint8_t*)send_buffer_1, strlen(send_buffer_1));
|
||||
if (written < 0) {
|
||||
printf("error: could not write\n");
|
||||
return;
|
||||
}
|
||||
written = tcp_send(connection, (uint8_t*)mime_type, strlen(mime_type));
|
||||
if (written < 0) {
|
||||
printf("error: could not write\n");
|
||||
return;
|
||||
}
|
||||
written = tcp_send(connection, (uint8_t*)send_buffer_2, strlen(send_buffer_2));
|
||||
if (written < 0) {
|
||||
printf("error: could not write\n");
|
||||
return;
|
||||
}
|
||||
int char_read;
|
||||
while ((char_read = fgetc(file)) != EOF) {
|
||||
tcp_send(connection, (uint8_t*)&char_read, sizeof(char));
|
||||
}
|
||||
|
||||
free(filepath);
|
||||
fclose(file);
|
||||
}
|
||||
}
|
||||
free(path);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
tcp_global_initialize_sockets();
|
||||
|
||||
signal(SIGINT, &interrupt_handler);
|
||||
|
||||
const uint16_t port = 8000;
|
||||
|
||||
printf("starting server...\n");
|
||||
server = tcp_server_create("127.0.0.1", port);
|
||||
if (server == NULL)
|
||||
@ -34,133 +152,15 @@ int main(void)
|
||||
|
||||
while (true) {
|
||||
printf("waiting for client...\n");
|
||||
TcpConnection* client_connection = tcp_server_accept(server);
|
||||
if (client_connection == NULL) {
|
||||
TcpConnection* connection = tcp_server_accept(server);
|
||||
if (connection == NULL) {
|
||||
printf("error: could not accept client\n");
|
||||
continue;
|
||||
}
|
||||
printf("client connected\n");
|
||||
while (true) {
|
||||
uint8_t buffer[8192] = { 0 };
|
||||
ssize_t recieved = tcp_recieve(client_connection, buffer, 8192);
|
||||
if (recieved < 0) {
|
||||
printf("error: could not recieve\n");
|
||||
break;
|
||||
} else if (recieved == 0) {
|
||||
printf("client disconnected\n");
|
||||
break;
|
||||
}
|
||||
HttpRequestHeader header
|
||||
= parse_http_request_header((char*)buffer, strlen((char*)buffer));
|
||||
char* path = calloc(header.path_length + 1, sizeof(char));
|
||||
strncpy(path, (char*)&buffer[header.path_index], header.path_length);
|
||||
if (strncmp(path, "/api", 4) == 0) {
|
||||
// something something api
|
||||
} else {
|
||||
if (strstr(path, "..") != NULL) {
|
||||
uint8_t send_buffer[]
|
||||
= "HTTP/1.1 400 BAD\r\n"
|
||||
"Content-Type: text/html\r\n"
|
||||
"\r\n"
|
||||
"<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Bad "
|
||||
"request</title></head><body><h1>Fuck you!</h1></body></html>\r\n";
|
||||
ssize_t written = tcp_send(client_connection, send_buffer, sizeof(send_buffer));
|
||||
if (written < 0) {
|
||||
printf("error: could not write\n");
|
||||
break;
|
||||
}
|
||||
} else if (header.path_length == 0 || strncmp(path, "/", header.path_length) == 0) {
|
||||
FILE* file = fopen("../frontend/index.html", "r");
|
||||
if (file == NULL) {
|
||||
printf("error: could not open file\n");
|
||||
break;
|
||||
}
|
||||
uint8_t send_buffer[] = "HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: text/html\r\n"
|
||||
"\r\n";
|
||||
ssize_t written = tcp_send(client_connection, send_buffer, sizeof(send_buffer));
|
||||
if (written < 0) {
|
||||
printf("error: could not write\n");
|
||||
break;
|
||||
}
|
||||
char char_read;
|
||||
while ((char_read = (char)fgetc(file)) != EOF) {
|
||||
tcp_send(client_connection, (uint8_t*)&char_read, sizeof(char));
|
||||
}
|
||||
} else {
|
||||
char rootpath[] = "../frontend";
|
||||
size_t filepath_size = sizeof(rootpath) + header.path_length + 1;
|
||||
char* filepath = calloc(filepath_size, sizeof(char));
|
||||
snprintf(filepath, filepath_size, "%s%s", rootpath, path);
|
||||
|
||||
char* dot = strrchr(path, '.');
|
||||
char mime_type[20] = { 0 };
|
||||
char file_flag[3] = { 'r', 0 };
|
||||
if (dot != NULL && strncmp(dot, ".html", 5) == 0) {
|
||||
snprintf(mime_type, 20, "text/html");
|
||||
} else if (dot != NULL && strncmp(dot, ".css", 4) == 0) {
|
||||
snprintf(mime_type, 20, "text/css");
|
||||
} else if (dot != NULL && strncmp(dot, ".js", 3) == 0) {
|
||||
snprintf(mime_type, 20, "text/javascript");
|
||||
} else if (dot != NULL && strncmp(dot, ".map", 4) == 0) {
|
||||
snprintf(mime_type, 20, "application/json");
|
||||
} else if (dot != NULL && strncmp(dot, ".ico", 4) == 0) {
|
||||
snprintf(mime_type, 20, "image/x-icon");
|
||||
} else if (dot != NULL && strncmp(dot, ".jpg", 4) == 0) {
|
||||
snprintf(mime_type, 20, "image/jpeg");
|
||||
file_flag[1] = 'b';
|
||||
} else if (dot != NULL && strncmp(dot, ".png", 4) == 0) {
|
||||
snprintf(mime_type, 20, "image/png");
|
||||
file_flag[1] = 'b';
|
||||
} else if (dot != NULL && strncmp(dot, ".woff2", 6) == 0) {
|
||||
snprintf(mime_type, 20, "font/woff2");
|
||||
file_flag[1] = 'b';
|
||||
} else {
|
||||
printf("error: unknown file type\n");
|
||||
break;
|
||||
}
|
||||
|
||||
FILE* file = fopen(filepath, file_flag);
|
||||
if (file == NULL) {
|
||||
printf("error: could not open file\n");
|
||||
break;
|
||||
}
|
||||
|
||||
char send_buffer_1[] = "HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: ";
|
||||
char send_buffer_2[] = "\r\n"
|
||||
"\r\n";
|
||||
ssize_t written = tcp_send(
|
||||
client_connection, (uint8_t*)send_buffer_1, strlen(send_buffer_1));
|
||||
if (written < 0) {
|
||||
printf("error: could not write\n");
|
||||
break;
|
||||
}
|
||||
written = tcp_send(client_connection, (uint8_t*)mime_type, strlen(mime_type));
|
||||
if (written < 0) {
|
||||
printf("error: could not write\n");
|
||||
break;
|
||||
}
|
||||
written = tcp_send(
|
||||
client_connection, (uint8_t*)send_buffer_2, strlen(send_buffer_2));
|
||||
if (written < 0) {
|
||||
printf("error: could not write\n");
|
||||
break;
|
||||
}
|
||||
int char_read;
|
||||
while ((char_read = fgetc(file)) != EOF) {
|
||||
tcp_send(client_connection, (uint8_t*)&char_read, sizeof(char));
|
||||
}
|
||||
|
||||
free(filepath);
|
||||
fclose(file);
|
||||
}
|
||||
}
|
||||
free(path);
|
||||
break;
|
||||
}
|
||||
handle_client_connection(connection);
|
||||
printf("disconnecting client\n");
|
||||
tcp_connection_destroy(client_connection);
|
||||
tcp_connection_destroy(connection);
|
||||
}
|
||||
tcp_server_destroy(server);
|
||||
return 0;
|
||||
|
@ -15,29 +15,5 @@
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="topbar">
|
||||
<h1>Postnummer App</h1>
|
||||
<div class="spacer"></div>
|
||||
<button id="dropdown-button">≡</button>
|
||||
<div id="dropdown">
|
||||
<button id="map-redirect">Kort</button>
|
||||
<button id="reviews-redirect">Anmeldelser</button>
|
||||
</div>
|
||||
</div>
|
||||
<main id="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">
|
||||
<div id="dot"></div>
|
||||
<div id="boundary"></div>
|
||||
<div id="info">
|
||||
<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>
|
||||
|
@ -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<HTMLImageElement>("#map")!;
|
||||
const fetcher = new Throttler(200);
|
||||
type Component = {
|
||||
children?: Component[],
|
||||
render(): string,
|
||||
hydrate?(update: (action?: () => Promise<any>) => Promise<void>): Promise<void>,
|
||||
};
|
||||
|
||||
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*/`
|
||||
<h2 id="reviews-title">Anmeldelser</h2>
|
||||
<div id="reviews-container">${loadReviews()}</div>
|
||||
`,
|
||||
}
|
||||
}
|
||||
|
||||
function setupSearchBar(zipCodeElement: HTMLParagraphElement) {
|
||||
const searchBar =
|
||||
domSelect<HTMLFormElement>("#search-bar")!;
|
||||
const searchInput =
|
||||
domSelect<HTMLInputElement>("#search-input")!;
|
||||
function makeSearchBar(updateZipCodeInfo: (zipCode: string) => Promise<void>): Component {
|
||||
return {
|
||||
render: () => /*html*/`
|
||||
<form id="search-bar">
|
||||
<input id="search-input" type="text" placeholder="Postnummer" maxlength="4">
|
||||
<button id="search-button" type="submit">Search</button>
|
||||
</form>
|
||||
`,
|
||||
async hydrate(update) {
|
||||
const searchBar =
|
||||
domSelect<HTMLFormElement>("#search-bar")!;
|
||||
const searchInput =
|
||||
domSelect<HTMLInputElement>("#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*/`
|
||||
<div id="info">
|
||||
<p id="zip-code">${info.zipCode === null ? "Postnummer ikke fundet" : `Postnummer: <code>${zipCode}</code>, ${name}`}</p>
|
||||
<p id="mouse-position"></p>
|
||||
<p id="coords"></p>
|
||||
</div>
|
||||
`,
|
||||
async hydrate() {
|
||||
tooltip.setText(zipCode ? `<code>${zipCode}</code> ${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*/`
|
||||
<h2 id="reviews-title">Anmeldelser</h2>
|
||||
<div id="reviews-container">${loadReviews()}</div>
|
||||
`;
|
||||
const dropdown = document.getElementById("dropdown")!;
|
||||
dropdown.classList.remove("enabled");
|
||||
|
||||
});
|
||||
|
||||
mapRedirect.addEventListener("click", () => {
|
||||
mainElement.innerHTML = /*html*/`
|
||||
<form id="search-bar">
|
||||
<input id="search-input" type="text" placeholder="Postnummer" maxlength="4">
|
||||
<button id="search-button" type="submit">Search</button>
|
||||
</form>
|
||||
const infoBox = makeInfoBox(zipCodeInfo);
|
||||
return {
|
||||
children: [searchBar, infoBox],
|
||||
render: () => /*html*/`
|
||||
${searchBar.render()}
|
||||
<img src="assets/map.jpg" id="map">
|
||||
<div id="dot"></div>
|
||||
<div id="boundary"></div>
|
||||
<div id="info">
|
||||
<p id="zip-code">Postnummer ikke fundet</p>
|
||||
<p id="mouse-position"></p>
|
||||
<p id="coords"></p>
|
||||
</div>
|
||||
`;
|
||||
const [mousePositionElement, coordsElement, zipCodeElement] = [
|
||||
"#mouse-position",
|
||||
"#coords",
|
||||
"#zip-code",
|
||||
].map((id) => domSelect<HTMLParagraphElement>(id)!);
|
||||
setupSearchBar(zipCodeElement);
|
||||
setupMap(mousePositionElement, coordsElement, zipCodeElement);
|
||||
const dropdown = document.getElementById("dropdown")!;
|
||||
dropdown.classList.remove("enabled");
|
||||
`,
|
||||
async hydrate() {
|
||||
const mousePositionElement = domSelect<HTMLParagraphElement>("#mouse-position")!;
|
||||
const coordsElement = domSelect<HTMLParagraphElement>("#coords")!;
|
||||
const zipCodeElement = domSelect<HTMLParagraphElement>("#zip-code")!;
|
||||
const mapImg = domSelect<HTMLImageElement>("#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*/`
|
||||
<div id="tooltip"></div>
|
||||
`,
|
||||
}
|
||||
}
|
||||
|
||||
function makeLayout(): Component {
|
||||
let route: "main" | "reviews" = "main";
|
||||
const router = makeRouter(route);
|
||||
const tooltip = makeTooltip();
|
||||
return {
|
||||
children: [router, tooltip],
|
||||
render: () => /**/`
|
||||
<div id="topbar">
|
||||
<h1>Postnummer App</h1>
|
||||
<div class="spacer"></div>
|
||||
<button id="dropdown-button">≡</button>
|
||||
<div id="dropdown">
|
||||
<button id="map-redirect">Kort</button>
|
||||
<button id="reviews-redirect">Anmeldelser</button>
|
||||
</div>
|
||||
</div>
|
||||
<main id="main">
|
||||
${router.render()}
|
||||
</main>
|
||||
${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<any>) => Promise<void>) {
|
||||
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<HTMLParagraphElement>("#mouse-position")!;
|
||||
const coordsElement = domSelect<HTMLParagraphElement>("#coords")!;
|
||||
const zipCodeElement = domSelect<HTMLParagraphElement>("#zip-code")!;
|
||||
|
||||
setupSearchBar(zipCodeElement);
|
||||
setupMap(mousePositionElement, coordsElement, zipCodeElement);
|
||||
setTopbarOffset();
|
||||
addToggleDropdownListener();
|
||||
pageRedirects();
|
||||
const bodyElement = document.querySelector<HTMLElement>("body")!;
|
||||
renderComponent(bodyElement, makeLayout());
|
||||
}
|
||||
|
||||
main();
|
||||
|
Loading…
Reference in New Issue
Block a user