From 0a44c11582b1356b950495d2c816e4f88736393f Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 7 Aug 2024 19:47:38 +0200 Subject: [PATCH] Use `npm:` instead of `esm.sh`; Introduce Database use --- components/home/Intro.tsx | 4 +- core/i18n/mod.ts | 4 +- core/{colors.ts => mod.ts} | 7 ++ core/types.ts | 31 +++++++++ deno.json | 6 +- routes/_middleware.ts | 4 +- routes/img/world.svg.ts | 129 +++++++++++++++---------------------- 7 files changed, 101 insertions(+), 84 deletions(-) rename core/{colors.ts => mod.ts} (56%) diff --git a/components/home/Intro.tsx b/components/home/Intro.tsx index fa0779d..ed65479 100644 --- a/components/home/Intro.tsx +++ b/components/home/Intro.tsx @@ -14,11 +14,11 @@ export default function Intro({ lang }: { lang: AllowedLanguage }) {
{translations[lang].globe_alt} diff --git a/core/i18n/mod.ts b/core/i18n/mod.ts index 77593e6..997be41 100644 --- a/core/i18n/mod.ts +++ b/core/i18n/mod.ts @@ -2,7 +2,9 @@ import type { AllowedLanguage } from "@/core/types.ts"; const ALLOWED_LANGUAGES: Array = ["en", "de", "zh"]; -export function isAllowedLanguage(lang: string): lang is AllowedLanguage { +export function isAllowedLanguage( + lang: string | null, +): lang is AllowedLanguage { return ALLOWED_LANGUAGES.includes(lang as AllowedLanguage); } diff --git a/core/colors.ts b/core/mod.ts similarity index 56% rename from core/colors.ts rename to core/mod.ts index 79b602c..cfc27d9 100644 --- a/core/colors.ts +++ b/core/mod.ts @@ -1,3 +1,10 @@ +import PocketBase from "pocketbase"; +import type { TypedPocketBase } from "./types.ts"; + +// THE PocketBase instance +export const pb = new PocketBase("https://pb.schindlerfelix.de") + .autoCancellation(false) as TypedPocketBase; + /** @var Record> */ export const tw = { red: { diff --git a/core/types.ts b/core/types.ts index 892a8d5..77cecc3 100644 --- a/core/types.ts +++ b/core/types.ts @@ -1,5 +1,36 @@ +import type PocketBase from "pocketbase"; +import type { RecordService } from "pocketbase"; + export type AllowedLanguage = "en" | "de" | "zh"; +type RecordID = string; +type DateString = string; + export type State = { language: AllowedLanguage; }; + +type BaseFields = { + id: RecordID; + updated: DateString; + created: DateString; +}; + +export type Location = BaseFields & { + name_en: string; + name_de: string; + name_zh: string; + lat: number; + lon: number; +}; + +type Region = BaseFields & { + name_en: string; + name_de: string; + name_zh: string; +}; + +export interface TypedPocketBase extends PocketBase { + collection(idOrName: "regions"): RecordService; + collection(idOrName: "locations"): RecordService; +} diff --git a/deno.json b/deno.json index c98df8f..56b954b 100644 --- a/deno.json +++ b/deno.json @@ -18,13 +18,17 @@ "@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.5.1", "@std/http/cookie": "jsr:@std/http@^0.224.5/cookie", "@std/path": "jsr:@std/path@^0.225.2", + "d3-geo": "npm:d3-geo@^3.1.1", + "d3-geo-projection": "npm:d3-geo-projection@^4.0.0", "icons/": "https://deno.land/x/tabler_icons_tsx@0.0.7/tsx/", + "pocketbase": "npm:pocketbase@^0.21.3", "preact": "https://esm.sh/preact@10.19.6", "preact/": "https://esm.sh/preact@10.19.6/", "prism/": "https://esm.sh/prismjs@1.29.0/components/", "tailwindcss": "npm:tailwindcss@3.4.3", "tailwindcss/": "npm:/tailwindcss@3.4.3/", - "tailwindcss/plugin": "npm:/tailwindcss@3.4.3/plugin.js" + "tailwindcss/plugin": "npm:/tailwindcss@3.4.3/plugin.js", + "topojson-client": "npm:topojson-client@^3.1.0" }, "compilerOptions": { "jsx": "react-jsx", diff --git a/routes/_middleware.ts b/routes/_middleware.ts index b7c76dd..deb5ec5 100644 --- a/routes/_middleware.ts +++ b/routes/_middleware.ts @@ -15,9 +15,9 @@ export async function handler( if (hasFileExt) { const res = await ctx.next(); - if (res.ok) { + if (res.ok && !res.headers.has("cache-control")) { // Add cache header for 15 days - res.headers.set("Cache-Control", "public, max-age=1296000, immutable"); + res.headers.set("cache-control", "public, max-age=1296000, immutable"); } return res; diff --git a/routes/img/world.svg.ts b/routes/img/world.svg.ts index ef76c9c..4971cf9 100644 --- a/routes/img/world.svg.ts +++ b/routes/img/world.svg.ts @@ -1,13 +1,14 @@ -import { Handlers } from "$fresh/server.ts"; +import type { Handlers } from "$fresh/server.ts"; -import { feature } from "https://esm.sh/topojson-client@3.1.0"; -import { geoSatellite } from "https://esm.sh/d3-geo-projection@4.0.0"; -import { geoPath } from "https://esm.sh/d3-geo@3.1.0"; +import { feature } from "topojson-client"; +import { geoSatellite } from "d3-geo-projection"; +import { geoPath } from "d3-geo"; -import { tw } from "@/core/colors.ts"; +import { pb, tw } from "@/core/mod.ts"; import topology from "@/core/land-110m.json" with { type: "json" }; import { isAllowedLanguage } from "@/core/i18n/mod.ts"; -import type { AllowedLanguage } from "@/core/types.ts"; +import type { State } from "@/core/types.ts"; +import { AllowedLanguage } from "@/core/types.ts"; const land = feature(topology, topology.objects.land); @@ -16,6 +17,11 @@ const distance = 8; const w = 1000; const h = 1000; const rad_to_deg = 180 / Math.PI; +const multiply: Record = { + "de": 0.6, + "en": 0.6, + "zh": 1, +} as const; const projection = geoSatellite() .distance(distance) @@ -26,71 +32,12 @@ const projection = geoSatellite() const path = geoPath(projection); -type Region = { - name: string; - coords: [number, number]; - multiply: number; // 0.6 for latin; 0.65 for mixed; 1 for chinese characters -}; - -const regions: Record< - AllowedLanguage, - Record<"stuttgart" | "xian" | "shanghai", Region> -> = { - en: { - stuttgart: { - name: "Stuttgart", - coords: [9.1770, 48.7823], - multiply: 0.6, - }, - shanghai: { - name: "上海(Shanghai)", - coords: [121.4737, 31.2304], - multiply: 0.7, - }, - xian: { - name: "西安(Xi'an) ", - coords: [108.9402, 34.3416], - multiply: 0.65, - }, - }, - de: { - stuttgart: { - name: "Stuttgart", - coords: [9.1770, 48.7823], - multiply: 0.6, - }, - shanghai: { - name: "上海(Schanghai)", - coords: [121.4737, 31.2304], - multiply: 0.7, - }, - xian: { - name: "西安(Xi'an) ", - coords: [108.9402, 34.3416], - multiply: 0.65, - }, - }, - zh: { - stuttgart: { - name: "斯图加特", - coords: [9.1770, 48.7823], - multiply: 1, - }, - shanghai: { - name: "上海", - coords: [121.4737, 31.2304], - multiply: 1, - }, - xian: { - name: "西安", - coords: [108.9402, 34.3416], - multiply: 1, - }, - }, -} as const; - -function render(region: Region, isDark: boolean) { - const coords = region.coords; +function render( + name: string, + coords: [number, number], + multiply: number, + isDark: boolean, +) { projection.rotate([-coords[0] - 30, -coords[1] * (30 / 90), 0]); const dot = projection(coords); @@ -150,7 +97,7 @@ function render(region: Region, isDark: boolean) { ${region.name} + >${name} ` : "" } @@ -168,18 +115,44 @@ function render(region: Region, isDark: boolean) { `; } -export const handler: Handlers = { - GET(_req, ctx) { +export const handler: Handlers = { + async GET(_req, ctx) { const searchParams = ctx.url.searchParams; // Get values from params + const loc = searchParams.get("loc"); + + if (loc === null) { + return new Response(undefined, { status: 400 }); + } + const isDark = searchParams.has("dark"); - let lang = searchParams.get("lang") ?? "en"; + let lang = searchParams.get("lang"); if (!isAllowedLanguage(lang)) lang = "en"; - const region = regions[lang as AllowedLanguage].shanghai; + lang as AllowedLanguage; // TODO: This shouldn't be needed + + const location = await pb.collection("locations").getOne(loc); + + let name = ""; + switch (lang) { + case "en": + name = location.name_en; + break; + case "de": + name = location.name_de; + break; + case "zh": + name = location.name_zh; + break; + } // Generate SVG - const svg = render(region, isDark).replace( + const svg = render( + name, + [location.lat, location.lon], + multiply[lang], + isDark, + ).replace( /\d\.\d+/g, (match) => match.slice(0, 4), );