From f18f3fee8844df98a30c2d248dfca04643dd4365 Mon Sep 17 00:00:00 2001 From: ivar Date: Mon, 6 Oct 2025 12:06:46 +0200 Subject: Initial --- VegaData/wwwroot/.DS_Store | Bin 0 -> 6148 bytes .../Baskerville No.2 Regular.otf | Bin 0 -> 33308 bytes .../wwwroot/Baskerville No.2 Regular/readme.html | 188 +++++++++++++++++++++ .../Broadsheet Italic/Broadsheet Italic.ttf | Bin 0 -> 132156 bytes VegaData/wwwroot/Broadsheet Italic/readme.html | 188 +++++++++++++++++++++ VegaData/wwwroot/framework.js | 150 ++++++++++++++++ VegaData/wwwroot/index.html | 113 +++++++++++++ VegaData/wwwroot/index.js | 72 ++++++++ 8 files changed, 711 insertions(+) create mode 100644 VegaData/wwwroot/.DS_Store create mode 100644 VegaData/wwwroot/Baskerville No.2 Regular/Baskerville No.2 Regular.otf create mode 100644 VegaData/wwwroot/Baskerville No.2 Regular/readme.html create mode 100644 VegaData/wwwroot/Broadsheet Italic/Broadsheet Italic.ttf create mode 100644 VegaData/wwwroot/Broadsheet Italic/readme.html create mode 100644 VegaData/wwwroot/framework.js create mode 100644 VegaData/wwwroot/index.html create mode 100644 VegaData/wwwroot/index.js (limited to 'VegaData/wwwroot') diff --git a/VegaData/wwwroot/.DS_Store b/VegaData/wwwroot/.DS_Store new file mode 100644 index 0000000..0fb4e25 Binary files /dev/null and b/VegaData/wwwroot/.DS_Store differ diff --git a/VegaData/wwwroot/Baskerville No.2 Regular/Baskerville No.2 Regular.otf b/VegaData/wwwroot/Baskerville No.2 Regular/Baskerville No.2 Regular.otf new file mode 100644 index 0000000..35da359 Binary files /dev/null and b/VegaData/wwwroot/Baskerville No.2 Regular/Baskerville No.2 Regular.otf differ diff --git a/VegaData/wwwroot/Baskerville No.2 Regular/readme.html b/VegaData/wwwroot/Baskerville No.2 Regular/readme.html new file mode 100644 index 0000000..ce69521 --- /dev/null +++ b/VegaData/wwwroot/Baskerville No.2 Regular/readme.html @@ -0,0 +1,188 @@ + + + + + + + Baskerville No.2 RegularFontsgeek + + + + + + + + + + + + +
+ + +
+ + + + +
+

Baskerville No.2 Regular

+

This font was downloaded from fontsgeek.com . You can visit fontsgeek.com for thousands of free fonts.

+

View Charmap and other information Browse other free fonts

+

You will be shortly redirected to fontsgeek.

+
+
+ + +
+ + + + diff --git a/VegaData/wwwroot/Broadsheet Italic/Broadsheet Italic.ttf b/VegaData/wwwroot/Broadsheet Italic/Broadsheet Italic.ttf new file mode 100644 index 0000000..443d2ab Binary files /dev/null and b/VegaData/wwwroot/Broadsheet Italic/Broadsheet Italic.ttf differ diff --git a/VegaData/wwwroot/Broadsheet Italic/readme.html b/VegaData/wwwroot/Broadsheet Italic/readme.html new file mode 100644 index 0000000..1fddb99 --- /dev/null +++ b/VegaData/wwwroot/Broadsheet Italic/readme.html @@ -0,0 +1,188 @@ + + + + + + + Broadsheet ItalicFontsgeek + + + + + + + + + + + + +
+ + +
+ + + + +
+

Broadsheet Italic

+

This font was downloaded from fontsgeek.com . You can visit fontsgeek.com for thousands of free fonts.

+

View Charmap and other information Browse other free fonts

+

You will be shortly redirected to fontsgeek.

+
+
+ + +
+ + + + diff --git a/VegaData/wwwroot/framework.js b/VegaData/wwwroot/framework.js new file mode 100644 index 0000000..e1652d0 --- /dev/null +++ b/VegaData/wwwroot/framework.js @@ -0,0 +1,150 @@ +const targetMap = new WeakMap(); +let activeEffect = null; + +const root = document.getElementById("root"); + +function createElement(tagName, props = {}, children = []) { + const el = document.createElement(tagName); + + for (const [key, value] of Object.entries(props || {})) { + if (key.startsWith("on") && typeof value === "function") { + el.addEventListener(key.substring(2).toLowerCase(), value); + } else if (key === "style" && typeof value === "object") { + Object.assign(el.style, value); + } else { + el.setAttribute(key, value); + } + } + + const appendChild = (child) => { + if (Array.isArray(child)) { + child.forEach(appendChild); + } else if (typeof child === "function") { + const placeholder = document.createTextNode(""); + el.appendChild(placeholder); + e(() => { + placeholder.textContent = child() ?? ""; + }); + } else if (child instanceof Node) { + el.appendChild(child); + } else if (child != null) { + el.appendChild(document.createTextNode(String(child))); + } + }; + + children.forEach(appendChild); + return el; +} + +function track(target, key) { + if (!activeEffect) { + return; + } + let depsMap = targetMap.get(target); + if (!depsMap) { + depsMap = new Map(); + targetMap.set(target, depsMap); + } + + let dep = depsMap.get(key); + if (!dep) { + dep = new Set(); + depsMap.set(key, dep); + } + + dep.add(activeEffect); +} + +function trigger(target, key) { + const depsMap = targetMap.get(target); + if (!depsMap) { + return; + } + + const dep = depsMap.get(key); + if (dep) { + dep.forEach((effect) => effect()); + } +} + +//** +// Create a reactive value, the value is at .value. +// To use this in element props you need to supply the .value read as a function. +// */ +export function r(target) { + target = {value: target}; + return new Proxy(target, { + get(obj, key, receiver) { + track(obj, key); + return Reflect.get(obj, key, receiver); + }, + set(obj, key, value, receiver) { + const result = Reflect.set(obj, key, value, receiver); + trigger(obj, key); + return result; + }, + }); +} + +//** +// Run code when value changes +// */ +export function e(fn) { + let active = true; + + const runner = () => { + if (active) { + activeEffect = runner; + fn(); + activeEffect = null; + } + }; + + runner(); + + runner.stop = () => { + active = false; + }; + + return runner; +} + +//** +// Combine elements +// */ +export function c(a, b) { + const normalize = (x) => (x == null ? [] : Array.isArray(x) ? x : [x]); + + return [...normalize(a), ...normalize(b)]; +} + +//** +// Mount element to a target (target is #root by default) +// */ +export async function m(component, target = root) { + if (typeof component === "function") { + component = await component(); + } + if (Array.isArray(component)) { + target.append(...component); + } else { + target.appendChild(component); + } +} + +export function css(styleObject) { + return Object.entries(styleObject).map(([key, value]) => { + const kebabKey = key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); + return `${kebabKey}: ${value}`; + }).join("; "); +} + +//** +// Create element +// */ +export function t(name, props, ...children) { + if (typeof name === "function") { + return name({...props, children}); + } + return createElement(name, props, children); +} diff --git a/VegaData/wwwroot/index.html b/VegaData/wwwroot/index.html new file mode 100644 index 0000000..5ea2694 --- /dev/null +++ b/VegaData/wwwroot/index.html @@ -0,0 +1,113 @@ + + + + + + + + Vega eller + + + +

Bli med på vega

+
+ + + +
+ + + + + \ No newline at end of file diff --git a/VegaData/wwwroot/index.js b/VegaData/wwwroot/index.js new file mode 100644 index 0000000..deb9552 --- /dev/null +++ b/VegaData/wwwroot/index.js @@ -0,0 +1,72 @@ +import { Temporal } from "temporal-polyfill" +import { t } from "./framework.js"; + +let _ +async function getShows() { + if (_) return _ + const response = await fetch("/shows"); + _ = await response.json(); + return _ +} + +const ulShows = document.getElementById("ulShows"); +const search = document.getElementById("search"); +const renderShowsBtn = document.getElementById("renderShowsBtn"); +const { timeZoneId: tzId } = Temporal.Now.zonedDateTimeISO(); +renderShowsBtn.addEventListener("click", () => { + renderShows() + search.value = "" +}); + +function dateString(date, small = false) { + if (small) return Temporal.Instant.from(date).toZonedDateTimeISO(tzId).toPlainDateTime().toLocaleString('nb-NO', { weekday: "long", calendar: 'gregory', hour: "2-digit", minute: "2-digit", month: 'long', day: 'numeric' }) + return Temporal.Instant.from(date).toZonedDateTimeISO(tzId).toPlainDateTime().toLocaleString('nb-NO', { weekday: "long", calendar: 'gregory', hour: "2-digit", minute: "2-digit", era: 'long', year: 'numeric', month: 'long', day: 'numeric' }) +} + +search.addEventListener("input", e => renderShows(e.currentTarget.value)) + +async function renderShows(query) { + query = query?.trim() + const searchParams = new URLSearchParams(location.search); + if (!query && searchParams.has("q")) query = searchParams.get("q") + searchParams.set("q", query) + let lis = []; + + const shows = (await getShows()).reduce((acc, curr) => { + const key = curr.title; + if (!acc[key]) { + acc[key] = []; + } + acc[key].push(curr); + return acc; + }, {}); + + async function share(show) { + const shareData = { + title: `${show.title} ${dateString(show.startDateTime, true)} på vega`, + text: "", + url: `#${show.title}-${show.startDateTime}`, + }; + await navigator.share(shareData); + } + + for (const showKey of Object.keys(shows).sort((a, b) => a.localeCompare(b))) { + const times = shows[showKey].sort((a, b) => Temporal.PlainDate.compare(Temporal.PlainDate.from(a.startDateTime), Temporal.PlainDate.from(b.startDateTime))) + if (query && !showKey.toLowerCase().match(query.toLowerCase())) continue + if (times.every(e => e.ticketUrl === "")) continue + lis.push(t("li", { class: "show", id: showKey }, [t("span", { class: "title italic" }, showKey), t("ul", undefined, [t("li", undefined, + times.filter(e => e.ticketUrl !== "").map(e => t("li", { class: `time time-${e.id}`, id: `${showKey}-${e.startDateTime}` }, [t("a", undefined, [ + t("div", undefined, [ + t("span", undefined, dateString(e.startDateTime)), + t("span", undefined, `${e.scene} - ${e.tags.join(", ")}`), + t("div", { class: "actions" }, [ + t("a", { href: e.ticketUrl }, "Billetter"), + t("button", { onclick: () => share(e) }, "Del tid") + ]) + ]) + ])])))])])) + } + ulShows.replaceChildren(...lis); +} +renderShows() + -- cgit v1.3