From e6851255dc2ba7982dae5828530caf8a542e13e0 Mon Sep 17 00:00:00 2001 From: Lett Osprey Date: Sun, 29 Mar 2026 13:53:16 +0200 Subject: [PATCH] First bits of pure lustre-javascript for user login session --- .gitignore | 20 +- Dockerfile | 14 +- README.md | 46 +- api-test/api-test.sh | 5 + api-test/docker-down.sh | 2 + api-test/docker-up.sh | 4 + api-test/run.sh | 3 + api-test/test.json | 8 + client/dist/client.js | 6344 +++++++++++++++++ client/dist/index.html | 17 + client/gleam.toml | 15 + client/manifest.toml | 57 + client/src/client.gleam | 83 + client/src/shared.gleam | 19 + game1.png | Bin 0 -> 50030 bytes game2.png | Bin 0 -> 102071 bytes layout.html | 74 - manifest.toml | 33 - priv/layout.css | 69 - screenshot.png | Bin 41829 -> 0 bytes gleam.toml => server/gleam.toml | 5 +- server/manifest.toml | 39 + server/priv/static/layout.css | 276 + server/priv/static/root.html | 31 + server/src/backend/playerhandler.gleam | 262 + server/src/backend/roomhandler.gleam | 68 + .../src}/backend/sockethandler.gleam | 60 +- server/src/backend/statehandler.gleam | 75 + server/src/quizterm.gleam | 103 + server/src/shared/message.gleam | 50 + server/src/web/components/answerlist.gleam | 182 + server/src/web/components/card.gleam | 275 + .../src/web}/components/control.gleam | 84 +- server/src/web/components/shared.gleam | 104 + server/src/web/handlers/serve.gleam | 123 + server/src/web/router.gleam | 124 + server/test/live.gleam | 1 + src/backend/statehandler.gleam | 86 - src/components/chat.gleam | 239 - src/quizterm.gleam | 128 - src/shared/message.gleam | 18 - 41 files changed, 8413 insertions(+), 733 deletions(-) create mode 100644 api-test/api-test.sh create mode 100644 api-test/docker-down.sh create mode 100644 api-test/docker-up.sh create mode 100644 api-test/run.sh create mode 100644 api-test/test.json create mode 100644 client/dist/client.js create mode 100644 client/dist/index.html create mode 100644 client/gleam.toml create mode 100644 client/manifest.toml create mode 100644 client/src/client.gleam create mode 100644 client/src/shared.gleam create mode 100644 game1.png create mode 100644 game2.png delete mode 100644 layout.html delete mode 100644 manifest.toml delete mode 100644 priv/layout.css delete mode 100644 screenshot.png rename gleam.toml => server/gleam.toml (80%) create mode 100644 server/manifest.toml create mode 100644 server/priv/static/layout.css create mode 100644 server/priv/static/root.html create mode 100644 server/src/backend/playerhandler.gleam create mode 100644 server/src/backend/roomhandler.gleam rename {src => server/src}/backend/sockethandler.gleam (56%) create mode 100644 server/src/backend/statehandler.gleam create mode 100644 server/src/quizterm.gleam create mode 100644 server/src/shared/message.gleam create mode 100644 server/src/web/components/answerlist.gleam create mode 100644 server/src/web/components/card.gleam rename {src => server/src/web}/components/control.gleam (53%) create mode 100644 server/src/web/components/shared.gleam create mode 100644 server/src/web/handlers/serve.gleam create mode 100644 server/src/web/router.gleam create mode 100644 server/test/live.gleam delete mode 100644 src/backend/statehandler.gleam delete mode 100644 src/components/chat.gleam delete mode 100644 src/quizterm.gleam delete mode 100644 src/shared/message.gleam diff --git a/.gitignore b/.gitignore index 89d431a..095eb04 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1 @@ -# ---> Erlang -.eunit -*.o -*.beam -*.plt -erl_crash.dump -.concrete/DEV_MODE - -# rebar 2.x -.rebar -rel/example_project -ebin/*.beam -deps - -# rebar 3 -.rebar3 -_build/ -_checkouts/ - +localbuild.sh diff --git a/Dockerfile b/Dockerfile index 73c380f..a72311e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,23 @@ -ARG GLEAM_VERSION=v1.12.0 +ARG GLEAM_VERSION=v1.15.0 # Build stage - compile the application FROM ghcr.io/gleam-lang/gleam:${GLEAM_VERSION}-erlang-alpine AS builder # Add project code -COPY ./priv /quizterm/priv -COPY ./src /quizterm/src -COPY ./gleam.toml /quizterm/ +COPY ./server/priv /quizterm/server/priv +COPY ./server/src /quizterm/server/src +COPY ./server/gleam.toml /quizterm/server/ -RUN cd /quizterm && gleam deps download +RUN cd /quizterm/server && gleam deps download # Compile the server code -RUN cd /quizterm \ +RUN cd /quizterm/server \ && gleam export erlang-shipment # Runtime stage - slim image with only what's needed to run FROM ghcr.io/gleam-lang/gleam:${GLEAM_VERSION}-erlang-alpine # Copy the compiled server code from the builder stage -COPY --from=builder /quizterm/build/erlang-shipment /app +COPY --from=builder /quizterm/server/build/erlang-shipment /app # Set up the entrypoint WORKDIR /app diff --git a/README.md b/README.md index 74b3c3c..4b8fe41 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,39 @@ was, will show up on everyones screen. Not quite finished yet, it is at a point where it is "usable" enough. -There are two endpoints to use: -| / |endpoint for "regular" players. -| /control | endpoint for person controlling the quiz. Same interface as for regular players, but with possiblity to control when to reveal answers and when to move on to next question. This gives the possiblity for the person asking the question to also provide answers, but the controls will work even if there is no player joined from this page. +Endpoints explained -![Screenshot of the game](screenshot.png) +| Endpoint | Usage | +|--------------------------|--------------------------------------------------------------| +| /room/ | Create room with given room_id (max 200 rooms) | +| /board/ | Join a game with the given room_id | +| /board//control | Join a game with the given room_id with more control options | -Next steps are: -- Display questions. Currently, Quizterm only asks user to provide answer, the actual question needs to be asked -elsewhere. This is often not a problem, since questions are asked on site, or in streamed meetings. -- Make it a little harder to join a quiz. So far, a quiz is open, and anyone that knows the URL can easily join. -A good idea to deploy to a disposable URL. -- Bad handling of players with the same name. If a player register with a name that is already in used, two players -will "compete" about being this player. You need to make sure to register with a different name than those already in -use. As all "in use" names are displayed on your screen, this is somewhat doable. + +| Ingame example | Idle player | +|--------------------------|--------------------------| +| ![Screenshot](game1.png) | ![Screenshot](game2.png) | + +### Building and running + +Docker, or a compatible container manager, like podman, is required to build and run +quizterm. The alternative is to install Gleam and Erlang/BEAM and run it dockerless. +Unless you plan to do Gleam development, using Docker will save a lot of hassle. + +To compile project and build docker image, write: +``` +docker build . -t quizterm:1 +``` +quizterm can be whatever name you want to give the container, 1 can be +changed to whatever you want the version of the container to be. + +Start server on port 4321: +``` +docker run -p 4321:1234 quizterm:1 +``` + +Port 1234 is the port used internally in the docker container, while 4321 +is the port exposed outside the container. The latter can be set to whatever +port you want to use. + +Open web browser and access http://localhost:4321 diff --git a/api-test/api-test.sh b/api-test/api-test.sh new file mode 100644 index 0000000..3f1cb68 --- /dev/null +++ b/api-test/api-test.sh @@ -0,0 +1,5 @@ +cat test.json | curl --json @- http://localhost:1234/api/info + +echo "" +echo "" + diff --git a/api-test/docker-down.sh b/api-test/docker-down.sh new file mode 100644 index 0000000..2fb0cd5 --- /dev/null +++ b/api-test/docker-down.sh @@ -0,0 +1,2 @@ +docker stop do_integration_test +docker rm do_integration_test diff --git a/api-test/docker-up.sh b/api-test/docker-up.sh new file mode 100644 index 0000000..3728472 --- /dev/null +++ b/api-test/docker-up.sh @@ -0,0 +1,4 @@ +docker build . -t quizterm:1 +docker run --name do_integration_test -d -p 4321:1234 quizterm:1 + +sleep 2 diff --git a/api-test/run.sh b/api-test/run.sh new file mode 100644 index 0000000..a22777e --- /dev/null +++ b/api-test/run.sh @@ -0,0 +1,3 @@ +sh docker-up +sh api-test +sh docker-down diff --git a/api-test/test.json b/api-test/test.json new file mode 100644 index 0000000..8e13b56 --- /dev/null +++ b/api-test/test.json @@ -0,0 +1,8 @@ +{ + "answers": + [ + {"question" : 14, "answer": "what is the meaning of the question that is number what is the meaning of the question that is numberwhat is the meaning of the question that is numberwhat is the meaning of the question that is number"} + ] +} + + diff --git a/client/dist/client.js b/client/dist/client.js new file mode 100644 index 0000000..18c3006 --- /dev/null +++ b/client/dist/client.js @@ -0,0 +1,6344 @@ +// build/dev/javascript/prelude.mjs +class CustomType { + withFields(fields) { + let properties = Object.keys(this).map((label) => (label in fields) ? fields[label] : this[label]); + return new this.constructor(...properties); + } +} + +class List { + static fromArray(array, tail) { + let t = tail || new Empty; + for (let i = array.length - 1;i >= 0; --i) { + t = new NonEmpty(array[i], t); + } + return t; + } + [Symbol.iterator]() { + return new ListIterator(this); + } + toArray() { + return [...this]; + } + atLeastLength(desired) { + let current = this; + while (desired-- > 0 && current) + current = current.tail; + return current !== undefined; + } + hasLength(desired) { + let current = this; + while (desired-- > 0 && current) + current = current.tail; + return desired === -1 && current instanceof Empty; + } + countLength() { + let current = this; + let length = 0; + while (current) { + current = current.tail; + length++; + } + return length - 1; + } +} +function prepend(element, tail) { + return new NonEmpty(element, tail); +} +function toList(elements, tail) { + return List.fromArray(elements, tail); +} + +class ListIterator { + #current; + constructor(current) { + this.#current = current; + } + next() { + if (this.#current instanceof Empty) { + return { done: true }; + } else { + let { head, tail } = this.#current; + this.#current = tail; + return { value: head, done: false }; + } + } +} + +class Empty extends List { +} +var List$Empty = () => new Empty; +var List$isEmpty = (value) => value instanceof Empty; + +class NonEmpty extends List { + constructor(head, tail) { + super(); + this.head = head; + this.tail = tail; + } +} +var List$NonEmpty = (head, tail) => new NonEmpty(head, tail); +var List$isNonEmpty = (value) => value instanceof NonEmpty; +var List$NonEmpty$first = (value) => value.head; +var List$NonEmpty$rest = (value) => value.tail; + +class BitArray { + bitSize; + byteSize; + bitOffset; + rawBuffer; + constructor(buffer, bitSize, bitOffset) { + if (!(buffer instanceof Uint8Array)) { + throw globalThis.Error("BitArray can only be constructed from a Uint8Array"); + } + this.bitSize = bitSize ?? buffer.length * 8; + this.byteSize = Math.trunc((this.bitSize + 7) / 8); + this.bitOffset = bitOffset ?? 0; + if (this.bitSize < 0) { + throw globalThis.Error(`BitArray bit size is invalid: ${this.bitSize}`); + } + if (this.bitOffset < 0 || this.bitOffset > 7) { + throw globalThis.Error(`BitArray bit offset is invalid: ${this.bitOffset}`); + } + if (buffer.length !== Math.trunc((this.bitOffset + this.bitSize + 7) / 8)) { + throw globalThis.Error("BitArray buffer length is invalid"); + } + this.rawBuffer = buffer; + } + byteAt(index) { + if (index < 0 || index >= this.byteSize) { + return; + } + return bitArrayByteAt(this.rawBuffer, this.bitOffset, index); + } + equals(other) { + if (this.bitSize !== other.bitSize) { + return false; + } + const wholeByteCount = Math.trunc(this.bitSize / 8); + if (this.bitOffset === 0 && other.bitOffset === 0) { + for (let i = 0;i < wholeByteCount; i++) { + if (this.rawBuffer[i] !== other.rawBuffer[i]) { + return false; + } + } + const trailingBitsCount = this.bitSize % 8; + if (trailingBitsCount) { + const unusedLowBitCount = 8 - trailingBitsCount; + if (this.rawBuffer[wholeByteCount] >> unusedLowBitCount !== other.rawBuffer[wholeByteCount] >> unusedLowBitCount) { + return false; + } + } + } else { + for (let i = 0;i < wholeByteCount; i++) { + const a = bitArrayByteAt(this.rawBuffer, this.bitOffset, i); + const b = bitArrayByteAt(other.rawBuffer, other.bitOffset, i); + if (a !== b) { + return false; + } + } + const trailingBitsCount = this.bitSize % 8; + if (trailingBitsCount) { + const a = bitArrayByteAt(this.rawBuffer, this.bitOffset, wholeByteCount); + const b = bitArrayByteAt(other.rawBuffer, other.bitOffset, wholeByteCount); + const unusedLowBitCount = 8 - trailingBitsCount; + if (a >> unusedLowBitCount !== b >> unusedLowBitCount) { + return false; + } + } + } + return true; + } + get buffer() { + if (this.bitOffset !== 0 || this.bitSize % 8 !== 0) { + throw new globalThis.Error("BitArray.buffer does not support unaligned bit arrays"); + } + return this.rawBuffer; + } + get length() { + if (this.bitOffset !== 0 || this.bitSize % 8 !== 0) { + throw new globalThis.Error("BitArray.length does not support unaligned bit arrays"); + } + return this.rawBuffer.length; + } +} +function bitArrayByteAt(buffer, bitOffset, index) { + if (bitOffset === 0) { + return buffer[index] ?? 0; + } else { + const a = buffer[index] << bitOffset & 255; + const b = buffer[index + 1] >> 8 - bitOffset; + return a | b; + } +} + +class UtfCodepoint { + constructor(value) { + this.value = value; + } +} +class Result extends CustomType { + static isResult(data2) { + return data2 instanceof Result; + } +} + +class Ok extends Result { + constructor(value) { + super(); + this[0] = value; + } + isOk() { + return true; + } +} +var Result$Ok = (value) => new Ok(value); +var Result$isOk = (value) => value instanceof Ok; +var Result$Ok$0 = (value) => value[0]; + +class Error extends Result { + constructor(detail) { + super(); + this[0] = detail; + } + isOk() { + return false; + } +} +var Result$Error = (detail) => new Error(detail); +var Result$isError = (value) => value instanceof Error; +function isEqual(x, y) { + let values = [x, y]; + while (values.length) { + let a = values.pop(); + let b = values.pop(); + if (a === b) + continue; + if (!isObject(a) || !isObject(b)) + return false; + let unequal = !structurallyCompatibleObjects(a, b) || unequalDates(a, b) || unequalBuffers(a, b) || unequalArrays(a, b) || unequalMaps(a, b) || unequalSets(a, b) || unequalRegExps(a, b); + if (unequal) + return false; + const proto = Object.getPrototypeOf(a); + if (proto !== null && typeof proto.equals === "function") { + try { + if (a.equals(b)) + continue; + else + return false; + } catch {} + } + let [keys, get] = getters(a); + const ka = keys(a); + const kb = keys(b); + if (ka.length !== kb.length) + return false; + for (let k of ka) { + values.push(get(a, k), get(b, k)); + } + } + return true; +} +function getters(object) { + if (object instanceof Map) { + return [(x) => x.keys(), (x, y) => x.get(y)]; + } else { + let extra = object instanceof globalThis.Error ? ["message"] : []; + return [(x) => [...extra, ...Object.keys(x)], (x, y) => x[y]]; + } +} +function unequalDates(a, b) { + return a instanceof Date && (a > b || a < b); +} +function unequalBuffers(a, b) { + return !(a instanceof BitArray) && a.buffer instanceof ArrayBuffer && a.BYTES_PER_ELEMENT && !(a.byteLength === b.byteLength && a.every((n, i) => n === b[i])); +} +function unequalArrays(a, b) { + return Array.isArray(a) && a.length !== b.length; +} +function unequalMaps(a, b) { + return a instanceof Map && a.size !== b.size; +} +function unequalSets(a, b) { + return a instanceof Set && (a.size != b.size || [...a].some((e) => !b.has(e))); +} +function unequalRegExps(a, b) { + return a instanceof RegExp && (a.source !== b.source || a.flags !== b.flags); +} +function isObject(a) { + return typeof a === "object" && a !== null; +} +function structurallyCompatibleObjects(a, b) { + if (typeof a !== "object" && typeof b !== "object" && (!a || !b)) + return false; + let nonstructural = [Promise, WeakSet, WeakMap, Function]; + if (nonstructural.some((c) => a instanceof c)) + return false; + return a.constructor === b.constructor; +} +function makeError(variant, file, module, line, fn, message, extra) { + let error = new globalThis.Error(message); + error.gleam_error = variant; + error.file = file; + error.module = module; + error.line = line; + error.function = fn; + error.fn = fn; + for (let k in extra) + error[k] = extra[k]; + return error; +} +// build/dev/javascript/gleam_stdlib/dict.mjs +var referenceMap = /* @__PURE__ */ new WeakMap; +var tempDataView = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(8)); +var referenceUID = 0; +function hashByReference(o) { + const known = referenceMap.get(o); + if (known !== undefined) { + return known; + } + const hash = referenceUID++; + if (referenceUID === 2147483647) { + referenceUID = 0; + } + referenceMap.set(o, hash); + return hash; +} +function hashMerge(a, b) { + return a ^ b + 2654435769 + (a << 6) + (a >> 2) | 0; +} +function hashString(s) { + let hash = 0; + const len = s.length; + for (let i = 0;i < len; i++) { + hash = Math.imul(31, hash) + s.charCodeAt(i) | 0; + } + return hash; +} +function hashNumber(n) { + tempDataView.setFloat64(0, n); + const i = tempDataView.getInt32(0); + const j = tempDataView.getInt32(4); + return Math.imul(73244475, i >> 16 ^ i) ^ j; +} +function hashBigInt(n) { + return hashString(n.toString()); +} +function hashObject(o) { + const proto = Object.getPrototypeOf(o); + if (proto !== null && typeof proto.hashCode === "function") { + try { + const code = o.hashCode(o); + if (typeof code === "number") { + return code; + } + } catch {} + } + if (o instanceof Promise || o instanceof WeakSet || o instanceof WeakMap) { + return hashByReference(o); + } + if (o instanceof Date) { + return hashNumber(o.getTime()); + } + let h = 0; + if (o instanceof ArrayBuffer) { + o = new Uint8Array(o); + } + if (Array.isArray(o) || o instanceof Uint8Array) { + for (let i = 0;i < o.length; i++) { + h = Math.imul(31, h) + getHash(o[i]) | 0; + } + } else if (o instanceof Set) { + o.forEach((v) => { + h = h + getHash(v) | 0; + }); + } else if (o instanceof Map) { + o.forEach((v, k) => { + h = h + hashMerge(getHash(v), getHash(k)) | 0; + }); + } else { + const keys = Object.keys(o); + for (let i = 0;i < keys.length; i++) { + const k = keys[i]; + const v = o[k]; + h = h + hashMerge(getHash(v), hashString(k)) | 0; + } + } + return h; +} +function getHash(u) { + if (u === null) + return 1108378658; + if (u === undefined) + return 1108378659; + if (u === true) + return 1108378657; + if (u === false) + return 1108378656; + switch (typeof u) { + case "number": + return hashNumber(u); + case "string": + return hashString(u); + case "bigint": + return hashBigInt(u); + case "object": + return hashObject(u); + case "symbol": + return hashByReference(u); + case "function": + return hashByReference(u); + default: + return 0; + } +} + +class Dict { + constructor(size, root) { + this.size = size; + this.root = root; + } +} +var bits = 5; +var mask = (1 << bits) - 1; +var noElementMarker = Symbol(); +var generationKey = Symbol(); +var emptyNode = /* @__PURE__ */ newNode(0); +var emptyDict = /* @__PURE__ */ new Dict(0, emptyNode); +var errorNil = /* @__PURE__ */ Result$Error(undefined); +function makeNode(generation, datamap, nodemap, data2) { + return { + datamap, + nodemap, + data: data2, + [generationKey]: generation + }; +} +function newNode(generation) { + return makeNode(generation, 0, 0, []); +} +function copyNode(node, generation) { + if (node[generationKey] === generation) { + return node; + } + const newData = node.data.slice(0); + return makeNode(generation, node.datamap, node.nodemap, newData); +} +function copyAndSet(node, generation, idx, val) { + if (node.data[idx] === val) { + return node; + } + node = copyNode(node, generation); + node.data[idx] = val; + return node; +} +function copyAndInsertPair(node, generation, bit, idx, key, val) { + const data2 = node.data; + const length = data2.length; + const newData = new Array(length + 2); + let readIndex = 0; + let writeIndex = 0; + while (readIndex < idx) + newData[writeIndex++] = data2[readIndex++]; + newData[writeIndex++] = key; + newData[writeIndex++] = val; + while (readIndex < length) + newData[writeIndex++] = data2[readIndex++]; + return makeNode(generation, node.datamap | bit, node.nodemap, newData); +} +function make() { + return emptyDict; +} +function get(dict, key) { + const result = lookup(dict.root, key, getHash(key)); + return result !== noElementMarker ? Result$Ok(result) : errorNil; +} +function lookup(node, key, hash) { + for (let shift = 0;shift < 32; shift += bits) { + const data2 = node.data; + const bit = hashbit(hash, shift); + if (node.nodemap & bit) { + node = data2[data2.length - 1 - index(node.nodemap, bit)]; + } else if (node.datamap & bit) { + const dataidx = Math.imul(index(node.datamap, bit), 2); + return isEqual(key, data2[dataidx]) ? data2[dataidx + 1] : noElementMarker; + } else { + return noElementMarker; + } + } + const overflow = node.data; + for (let i = 0;i < overflow.length; i += 2) { + if (isEqual(key, overflow[i])) { + return overflow[i + 1]; + } + } + return noElementMarker; +} +function toTransient(dict) { + return { + generation: nextGeneration(dict), + root: dict.root, + size: dict.size, + dict + }; +} +function nextGeneration(dict) { + const root = dict.root; + if (root[generationKey] < Number.MAX_SAFE_INTEGER) { + return root[generationKey] + 1; + } + const queue = [root]; + while (queue.length) { + const node = queue.pop(); + node[generationKey] = 0; + const nodeStart = data.length - popcount(node.nodemap); + for (let i = nodeStart;i < node.data.length; ++i) { + queue.push(node.data[i]); + } + } + return 1; +} +var globalTransient = /* @__PURE__ */ toTransient(emptyDict); +function insert(dict, key, value) { + globalTransient.generation = nextGeneration(dict); + globalTransient.size = dict.size; + const hash = getHash(key); + const root = insertIntoNode(globalTransient, dict.root, key, value, hash, 0); + if (root === dict.root) { + return dict; + } + return new Dict(globalTransient.size, root); +} +function insertIntoNode(transient, node, key, value, hash, shift) { + const data2 = node.data; + const generation = transient.generation; + if (shift > 32) { + for (let i = 0;i < data2.length; i += 2) { + if (isEqual(key, data2[i])) { + return copyAndSet(node, generation, i + 1, value); + } + } + transient.size += 1; + return copyAndInsertPair(node, generation, 0, data2.length, key, value); + } + const bit = hashbit(hash, shift); + if (node.nodemap & bit) { + const nodeidx2 = data2.length - 1 - index(node.nodemap, bit); + let child2 = data2[nodeidx2]; + child2 = insertIntoNode(transient, child2, key, value, hash, shift + bits); + return copyAndSet(node, generation, nodeidx2, child2); + } + const dataidx = Math.imul(index(node.datamap, bit), 2); + if ((node.datamap & bit) === 0) { + transient.size += 1; + return copyAndInsertPair(node, generation, bit, dataidx, key, value); + } + if (isEqual(key, data2[dataidx])) { + return copyAndSet(node, generation, dataidx + 1, value); + } + const childShift = shift + bits; + let child = emptyNode; + child = insertIntoNode(transient, child, key, value, hash, childShift); + const key2 = data2[dataidx]; + const value2 = data2[dataidx + 1]; + const hash2 = getHash(key2); + child = insertIntoNode(transient, child, key2, value2, hash2, childShift); + transient.size -= 1; + const length = data2.length; + const nodeidx = length - 1 - index(node.nodemap, bit); + const newData = new Array(length - 1); + let readIndex = 0; + let writeIndex = 0; + while (readIndex < dataidx) + newData[writeIndex++] = data2[readIndex++]; + readIndex += 2; + while (readIndex <= nodeidx) + newData[writeIndex++] = data2[readIndex++]; + newData[writeIndex++] = child; + while (readIndex < length) + newData[writeIndex++] = data2[readIndex++]; + return makeNode(generation, node.datamap ^ bit, node.nodemap | bit, newData); +} +function fold(dict, state, fun) { + const queue = [dict.root]; + while (queue.length) { + const node = queue.pop(); + const data2 = node.data; + const edgesStart = data2.length - popcount(node.nodemap); + for (let i = 0;i < edgesStart; i += 2) { + state = fun(state, data2[i], data2[i + 1]); + } + for (let i = edgesStart;i < data2.length; ++i) { + queue.push(data2[i]); + } + } + return state; +} +function popcount(n) { + n -= n >>> 1 & 1431655765; + n = (n & 858993459) + (n >>> 2 & 858993459); + return Math.imul(n + (n >>> 4) & 252645135, 16843009) >>> 24; +} +function index(bitmap, bit) { + return popcount(bitmap & bit - 1); +} +function hashbit(hash, shift) { + return 1 << (hash >>> shift & mask); +} + +// build/dev/javascript/gleam_stdlib/gleam/option.mjs +class Some extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +} +var Option$isSome = (value) => value instanceof Some; +var Option$Some$0 = (value) => value[0]; + +class None extends CustomType { +} +function to_result(option, e) { + if (option instanceof Some) { + let a = option[0]; + return new Ok(a); + } else { + return new Error(e); + } +} +function unwrap(option, default$) { + if (option instanceof Some) { + let x = option[0]; + return x; + } else { + return default$; + } +} + +// build/dev/javascript/gleam_stdlib/gleam/dict.mjs +function keys(dict) { + return fold(dict, toList([]), (acc, key, _) => { + return prepend(key, acc); + }); +} + +// build/dev/javascript/gleam_stdlib/gleam/order.mjs +class Lt extends CustomType { +} +var Order$Lt = () => new Lt; +class Eq extends CustomType { +} +var Order$Eq = () => new Eq; +class Gt extends CustomType { +} +var Order$Gt = () => new Gt; + +// build/dev/javascript/gleam_stdlib/gleam/string.mjs +function concat_loop(loop$strings, loop$accumulator) { + while (true) { + let strings = loop$strings; + let accumulator = loop$accumulator; + if (strings instanceof Empty) { + return accumulator; + } else { + let string = strings.head; + let strings$1 = strings.tail; + loop$strings = strings$1; + loop$accumulator = accumulator + string; + } + } +} +function concat2(strings) { + return concat_loop(strings, ""); +} +function split2(x, substring) { + if (substring === "") { + return graphemes(x); + } else { + let _pipe = x; + let _pipe$1 = identity(_pipe); + let _pipe$2 = split(_pipe$1, substring); + return map2(_pipe$2, identity); + } +} + +// build/dev/javascript/gleam_stdlib/gleam/dynamic/decode.mjs +class DecodeError extends CustomType { + constructor(expected, found, path) { + super(); + this.expected = expected; + this.found = found; + this.path = path; + } +} +class Decoder extends CustomType { + constructor(function$) { + super(); + this.function = function$; + } +} +var int2 = /* @__PURE__ */ new Decoder(decode_int); +var float2 = /* @__PURE__ */ new Decoder(decode_float); +var string2 = /* @__PURE__ */ new Decoder(decode_string); +function run(data2, decoder) { + let $ = decoder.function(data2); + let maybe_invalid_data; + let errors; + maybe_invalid_data = $[0]; + errors = $[1]; + if (errors instanceof Empty) { + return new Ok(maybe_invalid_data); + } else { + return new Error(errors); + } +} +function success(data2) { + return new Decoder((_) => { + return [data2, toList([])]; + }); +} +function map3(decoder, transformer) { + return new Decoder((d) => { + let $ = decoder.function(d); + let data2; + let errors; + data2 = $[0]; + errors = $[1]; + return [transformer(data2), errors]; + }); +} +function run_decoders(loop$data, loop$failure, loop$decoders) { + while (true) { + let data2 = loop$data; + let failure = loop$failure; + let decoders = loop$decoders; + if (decoders instanceof Empty) { + return failure; + } else { + let decoder = decoders.head; + let decoders$1 = decoders.tail; + let $ = decoder.function(data2); + let layer; + let errors; + layer = $; + errors = $[1]; + if (errors instanceof Empty) { + return layer; + } else { + loop$data = data2; + loop$failure = failure; + loop$decoders = decoders$1; + } + } + } +} +function one_of(first, alternatives) { + return new Decoder((dynamic_data) => { + let $ = first.function(dynamic_data); + let layer; + let errors; + layer = $; + errors = $[1]; + if (errors instanceof Empty) { + return layer; + } else { + return run_decoders(dynamic_data, layer, alternatives); + } + }); +} +function run_dynamic_function(data2, name, f) { + let $ = f(data2); + if ($ instanceof Ok) { + let data$1 = $[0]; + return [data$1, toList([])]; + } else { + let placeholder = $[0]; + return [ + placeholder, + toList([new DecodeError(name, classify_dynamic(data2), toList([]))]) + ]; + } +} +function decode_int(data2) { + return run_dynamic_function(data2, "Int", int); +} +function decode_float(data2) { + return run_dynamic_function(data2, "Float", float); +} +function decode_string(data2) { + return run_dynamic_function(data2, "String", string); +} +function path_segment_to_string(key) { + let decoder = one_of(string2, toList([ + (() => { + let _pipe = int2; + return map3(_pipe, to_string); + })(), + (() => { + let _pipe = float2; + return map3(_pipe, float_to_string); + })() + ])); + let $ = run(key, decoder); + if ($ instanceof Ok) { + let key$1 = $[0]; + return key$1; + } else { + return "<" + classify_dynamic(key) + ">"; + } +} +function push_path(layer, path) { + let path$1 = map2(path, (key) => { + let _pipe = key; + let _pipe$1 = identity(_pipe); + return path_segment_to_string(_pipe$1); + }); + let errors = map2(layer[1], (error) => { + return new DecodeError(error.expected, error.found, append2(path$1, error.path)); + }); + return [layer[0], errors]; +} +function index3(loop$path, loop$position, loop$inner, loop$data, loop$handle_miss) { + while (true) { + let path = loop$path; + let position = loop$position; + let inner = loop$inner; + let data2 = loop$data; + let handle_miss = loop$handle_miss; + if (path instanceof Empty) { + let _pipe = data2; + let _pipe$1 = inner(_pipe); + return push_path(_pipe$1, reverse(position)); + } else { + let key = path.head; + let path$1 = path.tail; + let $ = index2(data2, key); + if ($ instanceof Ok) { + let $1 = $[0]; + if ($1 instanceof Some) { + let data$1 = $1[0]; + loop$path = path$1; + loop$position = prepend(key, position); + loop$inner = inner; + loop$data = data$1; + loop$handle_miss = handle_miss; + } else { + return handle_miss(data2, prepend(key, position)); + } + } else { + let kind = $[0]; + let $1 = inner(data2); + let default$; + default$ = $1[0]; + let _pipe = [ + default$, + toList([new DecodeError(kind, classify_dynamic(data2), toList([]))]) + ]; + return push_path(_pipe, reverse(position)); + } + } + } +} +function subfield(field_path, field_decoder, next) { + return new Decoder((data2) => { + let $ = index3(field_path, toList([]), field_decoder.function, data2, (data3, position) => { + let $12 = field_decoder.function(data3); + let default$; + default$ = $12[0]; + let _pipe = [ + default$, + toList([new DecodeError("Field", "Nothing", toList([]))]) + ]; + return push_path(_pipe, reverse(position)); + }); + let out; + let errors1; + out = $[0]; + errors1 = $[1]; + let $1 = next(out).function(data2); + let out$1; + let errors2; + out$1 = $1[0]; + errors2 = $1[1]; + return [out$1, append2(errors1, errors2)]; + }); +} + +// build/dev/javascript/gleam_stdlib/gleam_stdlib.mjs +var Nil = undefined; +function identity(x) { + return x; +} +function parse_int(value) { + if (/^[-+]?(\d+)$/.test(value)) { + return Result$Ok(parseInt(value)); + } else { + return Result$Error(Nil); + } +} +function to_string(term) { + return term.toString(); +} +function graphemes(string3) { + const iterator = graphemes_iterator(string3); + if (iterator) { + return arrayToList(Array.from(iterator).map((item) => item.segment)); + } else { + return arrayToList(string3.match(/./gsu)); + } +} +var segmenter = undefined; +function graphemes_iterator(string3) { + if (globalThis.Intl && Intl.Segmenter) { + segmenter ||= new Intl.Segmenter; + return segmenter.segment(string3)[Symbol.iterator](); + } +} +function pop_codeunit(str) { + return [str.charCodeAt(0) | 0, str.slice(1)]; +} +function lowercase(string3) { + return string3.toLowerCase(); +} +function split(xs, pattern) { + return arrayToList(xs.split(pattern)); +} +function string_codeunit_slice(str, from2, length2) { + return str.slice(from2, from2 + length2); +} +function starts_with(haystack, needle) { + return haystack.startsWith(needle); +} +var unicode_whitespaces = [ + " ", + "\t", + ` +`, + "\v", + "\f", + "\r", + "…", + "\u2028", + "\u2029" +].join(""); +var trim_start_regex = /* @__PURE__ */ new RegExp(`^[${unicode_whitespaces}]*`); +var trim_end_regex = /* @__PURE__ */ new RegExp(`[${unicode_whitespaces}]*$`); +function classify_dynamic(data2) { + if (typeof data2 === "string") { + return "String"; + } else if (typeof data2 === "boolean") { + return "Bool"; + } else if (isResult(data2)) { + return "Result"; + } else if (isList(data2)) { + return "List"; + } else if (data2 instanceof BitArray) { + return "BitArray"; + } else if (data2 instanceof Dict) { + return "Dict"; + } else if (Number.isInteger(data2)) { + return "Int"; + } else if (Array.isArray(data2)) { + return `Array`; + } else if (typeof data2 === "number") { + return "Float"; + } else if (data2 === null) { + return "Nil"; + } else if (data2 === undefined) { + return "Nil"; + } else { + const type = typeof data2; + return type.charAt(0).toUpperCase() + type.slice(1); + } +} +var MIN_I32 = -(2 ** 31); +var MAX_I32 = 2 ** 31 - 1; +var U32 = 2 ** 32; +var MAX_SAFE = Number.MAX_SAFE_INTEGER; +var MIN_SAFE = Number.MIN_SAFE_INTEGER; +function float_to_string(float3) { + const string3 = float3.toString().replace("+", ""); + if (string3.indexOf(".") >= 0) { + return string3; + } else { + const index4 = string3.indexOf("e"); + if (index4 >= 0) { + return string3.slice(0, index4) + ".0" + string3.slice(index4); + } else { + return string3 + ".0"; + } + } +} + +class Inspector { + #references = new Set; + inspect(v) { + const t = typeof v; + if (v === true) + return "True"; + if (v === false) + return "False"; + if (v === null) + return "//js(null)"; + if (v === undefined) + return "Nil"; + if (t === "string") + return this.#string(v); + if (t === "bigint" || Number.isInteger(v)) + return v.toString(); + if (t === "number") + return float_to_string(v); + if (v instanceof UtfCodepoint) + return this.#utfCodepoint(v); + if (v instanceof BitArray) + return this.#bit_array(v); + if (v instanceof RegExp) + return `//js(${v})`; + if (v instanceof Date) + return `//js(Date("${v.toISOString()}"))`; + if (v instanceof globalThis.Error) + return `//js(${v.toString()})`; + if (v instanceof Function) { + const args = []; + for (const i of Array(v.length).keys()) + args.push(String.fromCharCode(i + 97)); + return `//fn(${args.join(", ")}) { ... }`; + } + if (this.#references.size === this.#references.add(v).size) { + return "//js(circular reference)"; + } + let printed; + if (Array.isArray(v)) { + printed = `#(${v.map((v2) => this.inspect(v2)).join(", ")})`; + } else if (isList(v)) { + printed = this.#list(v); + } else if (v instanceof CustomType) { + printed = this.#customType(v); + } else if (v instanceof Dict) { + printed = this.#dict(v); + } else if (v instanceof Set) { + return `//js(Set(${[...v].map((v2) => this.inspect(v2)).join(", ")}))`; + } else { + printed = this.#object(v); + } + this.#references.delete(v); + return printed; + } + #object(v) { + const name = Object.getPrototypeOf(v)?.constructor?.name || "Object"; + const props = []; + for (const k of Object.keys(v)) { + props.push(`${this.inspect(k)}: ${this.inspect(v[k])}`); + } + const body = props.length ? " " + props.join(", ") + " " : ""; + const head = name === "Object" ? "" : name + " "; + return `//js(${head}{${body}})`; + } + #dict(map4) { + let body = "dict.from_list(["; + let first = true; + body = fold(map4, body, (body2, key, value) => { + if (!first) + body2 = body2 + ", "; + first = false; + return body2 + "#(" + this.inspect(key) + ", " + this.inspect(value) + ")"; + }); + return body + "])"; + } + #customType(record) { + const props = Object.keys(record).map((label) => { + const value = this.inspect(record[label]); + return isNaN(parseInt(label)) ? `${label}: ${value}` : value; + }).join(", "); + return props ? `${record.constructor.name}(${props})` : record.constructor.name; + } + #list(list2) { + if (List$isEmpty(list2)) { + return "[]"; + } + let char_out = 'charlist.from_string("'; + let list_out = "["; + let current = list2; + while (List$isNonEmpty(current)) { + let element = current.head; + current = current.tail; + if (list_out !== "[") { + list_out += ", "; + } + list_out += this.inspect(element); + if (char_out) { + if (Number.isInteger(element) && element >= 32 && element <= 126) { + char_out += String.fromCharCode(element); + } else { + char_out = null; + } + } + } + if (char_out) { + return char_out + '")'; + } else { + return list_out + "]"; + } + } + #string(str) { + let new_str = '"'; + for (let i = 0;i < str.length; i++) { + const char = str[i]; + switch (char) { + case ` +`: + new_str += "\\n"; + break; + case "\r": + new_str += "\\r"; + break; + case "\t": + new_str += "\\t"; + break; + case "\f": + new_str += "\\f"; + break; + case "\\": + new_str += "\\\\"; + break; + case '"': + new_str += "\\\""; + break; + default: + if (char < " " || char > "~" && char < " ") { + new_str += "\\u{" + char.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0") + "}"; + } else { + new_str += char; + } + } + } + new_str += '"'; + return new_str; + } + #utfCodepoint(codepoint2) { + return `//utfcodepoint(${String.fromCodePoint(codepoint2.value)})`; + } + #bit_array(bits2) { + if (bits2.bitSize === 0) { + return "<<>>"; + } + let acc = "<<"; + for (let i = 0;i < bits2.byteSize - 1; i++) { + acc += bits2.byteAt(i).toString(); + acc += ", "; + } + if (bits2.byteSize * 8 === bits2.bitSize) { + acc += bits2.byteAt(bits2.byteSize - 1).toString(); + } else { + const trailingBitsCount = bits2.bitSize % 8; + acc += bits2.byteAt(bits2.byteSize - 1) >> 8 - trailingBitsCount; + acc += `:size(${trailingBitsCount})`; + } + acc += ">>"; + return acc; + } +} +function index2(data2, key) { + if (data2 instanceof Dict) { + const result = get(data2, key); + return Result$Ok(result.isOk() ? new Some(result[0]) : new None); + } + if (data2 instanceof WeakMap || data2 instanceof Map) { + const token = {}; + const entry = data2.get(key, token); + if (entry === token) + return Result$Ok(new None); + return Result$Ok(new Some(entry)); + } + const key_is_int = Number.isInteger(key); + if (key_is_int && key >= 0 && key < 8 && isList(data2)) { + let i = 0; + for (const value of data2) { + if (i === key) + return Result$Ok(new Some(value)); + i++; + } + return Result$Error("Indexable"); + } + if (key_is_int && Array.isArray(data2) || data2 && typeof data2 === "object" || data2 && Object.getPrototypeOf(data2) === Object.prototype) { + if (key in data2) + return Result$Ok(new Some(data2[key])); + return Result$Ok(new None); + } + return Result$Error(key_is_int ? "Indexable" : "Dict"); +} +function float(data2) { + if (typeof data2 === "number") + return Result$Ok(data2); + return Result$Error(0); +} +function int(data2) { + if (Number.isInteger(data2)) + return Result$Ok(data2); + return Result$Error(0); +} +function string(data2) { + if (typeof data2 === "string") + return Result$Ok(data2); + return Result$Error(""); +} +function arrayToList(array) { + let list2 = List$Empty(); + let i = array.length; + while (i--) { + list2 = List$NonEmpty(array[i], list2); + } + return list2; +} +function isList(data2) { + return List$isEmpty(data2) || List$isNonEmpty(data2); +} +function isResult(data2) { + return Result$isOk(data2) || Result$isError(data2); +} + +// build/dev/javascript/gleam_stdlib/gleam/list.mjs +class Ascending extends CustomType { +} + +class Descending extends CustomType { +} +function reverse_and_prepend(loop$prefix, loop$suffix) { + while (true) { + let prefix = loop$prefix; + let suffix = loop$suffix; + if (prefix instanceof Empty) { + return suffix; + } else { + let first$1 = prefix.head; + let rest$1 = prefix.tail; + loop$prefix = rest$1; + loop$suffix = prepend(first$1, suffix); + } + } +} +function reverse(list2) { + return reverse_and_prepend(list2, toList([])); +} +function map_loop(loop$list, loop$fun, loop$acc) { + while (true) { + let list2 = loop$list; + let fun = loop$fun; + let acc = loop$acc; + if (list2 instanceof Empty) { + return reverse(acc); + } else { + let first$1 = list2.head; + let rest$1 = list2.tail; + loop$list = rest$1; + loop$fun = fun; + loop$acc = prepend(fun(first$1), acc); + } + } +} +function map2(list2, fun) { + return map_loop(list2, fun, toList([])); +} +function index_map_loop(loop$list, loop$fun, loop$index, loop$acc) { + while (true) { + let list2 = loop$list; + let fun = loop$fun; + let index4 = loop$index; + let acc = loop$acc; + if (list2 instanceof Empty) { + return reverse(acc); + } else { + let first$1 = list2.head; + let rest$1 = list2.tail; + let acc$1 = prepend(fun(first$1, index4), acc); + loop$list = rest$1; + loop$fun = fun; + loop$index = index4 + 1; + loop$acc = acc$1; + } + } +} +function index_map(list2, fun) { + return index_map_loop(list2, fun, 0, toList([])); +} +function append_loop(loop$first, loop$second) { + while (true) { + let first = loop$first; + let second = loop$second; + if (first instanceof Empty) { + return second; + } else { + let first$1 = first.head; + let rest$1 = first.tail; + loop$first = rest$1; + loop$second = prepend(first$1, second); + } + } +} +function append2(first, second) { + return append_loop(reverse(first), second); +} +function prepend2(list2, item) { + return prepend(item, list2); +} +function fold2(loop$list, loop$initial, loop$fun) { + while (true) { + let list2 = loop$list; + let initial = loop$initial; + let fun = loop$fun; + if (list2 instanceof Empty) { + return initial; + } else { + let first$1 = list2.head; + let rest$1 = list2.tail; + loop$list = rest$1; + loop$initial = fun(initial, first$1); + loop$fun = fun; + } + } +} +function sequences(loop$list, loop$compare, loop$growing, loop$direction, loop$prev, loop$acc) { + while (true) { + let list2 = loop$list; + let compare3 = loop$compare; + let growing = loop$growing; + let direction = loop$direction; + let prev = loop$prev; + let acc = loop$acc; + let growing$1 = prepend(prev, growing); + if (list2 instanceof Empty) { + if (direction instanceof Ascending) { + return prepend(reverse(growing$1), acc); + } else { + return prepend(growing$1, acc); + } + } else { + let new$1 = list2.head; + let rest$1 = list2.tail; + let $ = compare3(prev, new$1); + if (direction instanceof Ascending) { + if ($ instanceof Lt) { + loop$list = rest$1; + loop$compare = compare3; + loop$growing = growing$1; + loop$direction = direction; + loop$prev = new$1; + loop$acc = acc; + } else if ($ instanceof Eq) { + loop$list = rest$1; + loop$compare = compare3; + loop$growing = growing$1; + loop$direction = direction; + loop$prev = new$1; + loop$acc = acc; + } else { + let _block; + if (direction instanceof Ascending) { + _block = prepend(reverse(growing$1), acc); + } else { + _block = prepend(growing$1, acc); + } + let acc$1 = _block; + if (rest$1 instanceof Empty) { + return prepend(toList([new$1]), acc$1); + } else { + let next = rest$1.head; + let rest$2 = rest$1.tail; + let _block$1; + let $1 = compare3(new$1, next); + if ($1 instanceof Lt) { + _block$1 = new Ascending; + } else if ($1 instanceof Eq) { + _block$1 = new Ascending; + } else { + _block$1 = new Descending; + } + let direction$1 = _block$1; + loop$list = rest$2; + loop$compare = compare3; + loop$growing = toList([new$1]); + loop$direction = direction$1; + loop$prev = next; + loop$acc = acc$1; + } + } + } else if ($ instanceof Lt) { + let _block; + if (direction instanceof Ascending) { + _block = prepend(reverse(growing$1), acc); + } else { + _block = prepend(growing$1, acc); + } + let acc$1 = _block; + if (rest$1 instanceof Empty) { + return prepend(toList([new$1]), acc$1); + } else { + let next = rest$1.head; + let rest$2 = rest$1.tail; + let _block$1; + let $1 = compare3(new$1, next); + if ($1 instanceof Lt) { + _block$1 = new Ascending; + } else if ($1 instanceof Eq) { + _block$1 = new Ascending; + } else { + _block$1 = new Descending; + } + let direction$1 = _block$1; + loop$list = rest$2; + loop$compare = compare3; + loop$growing = toList([new$1]); + loop$direction = direction$1; + loop$prev = next; + loop$acc = acc$1; + } + } else if ($ instanceof Eq) { + let _block; + if (direction instanceof Ascending) { + _block = prepend(reverse(growing$1), acc); + } else { + _block = prepend(growing$1, acc); + } + let acc$1 = _block; + if (rest$1 instanceof Empty) { + return prepend(toList([new$1]), acc$1); + } else { + let next = rest$1.head; + let rest$2 = rest$1.tail; + let _block$1; + let $1 = compare3(new$1, next); + if ($1 instanceof Lt) { + _block$1 = new Ascending; + } else if ($1 instanceof Eq) { + _block$1 = new Ascending; + } else { + _block$1 = new Descending; + } + let direction$1 = _block$1; + loop$list = rest$2; + loop$compare = compare3; + loop$growing = toList([new$1]); + loop$direction = direction$1; + loop$prev = next; + loop$acc = acc$1; + } + } else { + loop$list = rest$1; + loop$compare = compare3; + loop$growing = growing$1; + loop$direction = direction; + loop$prev = new$1; + loop$acc = acc; + } + } + } +} +function merge_ascendings(loop$list1, loop$list2, loop$compare, loop$acc) { + while (true) { + let list1 = loop$list1; + let list2 = loop$list2; + let compare3 = loop$compare; + let acc = loop$acc; + if (list1 instanceof Empty) { + let list3 = list2; + return reverse_and_prepend(list3, acc); + } else if (list2 instanceof Empty) { + let list3 = list1; + return reverse_and_prepend(list3, acc); + } else { + let first1 = list1.head; + let rest1 = list1.tail; + let first2 = list2.head; + let rest2 = list2.tail; + let $ = compare3(first1, first2); + if ($ instanceof Lt) { + loop$list1 = rest1; + loop$list2 = list2; + loop$compare = compare3; + loop$acc = prepend(first1, acc); + } else if ($ instanceof Eq) { + loop$list1 = list1; + loop$list2 = rest2; + loop$compare = compare3; + loop$acc = prepend(first2, acc); + } else { + loop$list1 = list1; + loop$list2 = rest2; + loop$compare = compare3; + loop$acc = prepend(first2, acc); + } + } + } +} +function merge_ascending_pairs(loop$sequences, loop$compare, loop$acc) { + while (true) { + let sequences2 = loop$sequences; + let compare3 = loop$compare; + let acc = loop$acc; + if (sequences2 instanceof Empty) { + return reverse(acc); + } else { + let $ = sequences2.tail; + if ($ instanceof Empty) { + let sequence = sequences2.head; + return reverse(prepend(reverse(sequence), acc)); + } else { + let ascending1 = sequences2.head; + let ascending2 = $.head; + let rest$1 = $.tail; + let descending = merge_ascendings(ascending1, ascending2, compare3, toList([])); + loop$sequences = rest$1; + loop$compare = compare3; + loop$acc = prepend(descending, acc); + } + } + } +} +function merge_descendings(loop$list1, loop$list2, loop$compare, loop$acc) { + while (true) { + let list1 = loop$list1; + let list2 = loop$list2; + let compare3 = loop$compare; + let acc = loop$acc; + if (list1 instanceof Empty) { + let list3 = list2; + return reverse_and_prepend(list3, acc); + } else if (list2 instanceof Empty) { + let list3 = list1; + return reverse_and_prepend(list3, acc); + } else { + let first1 = list1.head; + let rest1 = list1.tail; + let first2 = list2.head; + let rest2 = list2.tail; + let $ = compare3(first1, first2); + if ($ instanceof Lt) { + loop$list1 = list1; + loop$list2 = rest2; + loop$compare = compare3; + loop$acc = prepend(first2, acc); + } else if ($ instanceof Eq) { + loop$list1 = rest1; + loop$list2 = list2; + loop$compare = compare3; + loop$acc = prepend(first1, acc); + } else { + loop$list1 = rest1; + loop$list2 = list2; + loop$compare = compare3; + loop$acc = prepend(first1, acc); + } + } + } +} +function merge_descending_pairs(loop$sequences, loop$compare, loop$acc) { + while (true) { + let sequences2 = loop$sequences; + let compare3 = loop$compare; + let acc = loop$acc; + if (sequences2 instanceof Empty) { + return reverse(acc); + } else { + let $ = sequences2.tail; + if ($ instanceof Empty) { + let sequence = sequences2.head; + return reverse(prepend(reverse(sequence), acc)); + } else { + let descending1 = sequences2.head; + let descending2 = $.head; + let rest$1 = $.tail; + let ascending = merge_descendings(descending1, descending2, compare3, toList([])); + loop$sequences = rest$1; + loop$compare = compare3; + loop$acc = prepend(ascending, acc); + } + } + } +} +function merge_all(loop$sequences, loop$direction, loop$compare) { + while (true) { + let sequences2 = loop$sequences; + let direction = loop$direction; + let compare3 = loop$compare; + if (sequences2 instanceof Empty) { + return sequences2; + } else if (direction instanceof Ascending) { + let $ = sequences2.tail; + if ($ instanceof Empty) { + let sequence = sequences2.head; + return sequence; + } else { + let sequences$1 = merge_ascending_pairs(sequences2, compare3, toList([])); + loop$sequences = sequences$1; + loop$direction = new Descending; + loop$compare = compare3; + } + } else { + let $ = sequences2.tail; + if ($ instanceof Empty) { + let sequence = sequences2.head; + return reverse(sequence); + } else { + let sequences$1 = merge_descending_pairs(sequences2, compare3, toList([])); + loop$sequences = sequences$1; + loop$direction = new Ascending; + loop$compare = compare3; + } + } + } +} +function sort(list2, compare3) { + if (list2 instanceof Empty) { + return list2; + } else { + let $ = list2.tail; + if ($ instanceof Empty) { + return list2; + } else { + let x = list2.head; + let y = $.head; + let rest$1 = $.tail; + let _block; + let $1 = compare3(x, y); + if ($1 instanceof Lt) { + _block = new Ascending; + } else if ($1 instanceof Eq) { + _block = new Ascending; + } else { + _block = new Descending; + } + let direction = _block; + let sequences$1 = sequences(rest$1, compare3, toList([x]), direction, y, toList([])); + return merge_all(sequences$1, new Ascending, compare3); + } + } +} +function key_set_loop(loop$list, loop$key, loop$value, loop$inspected) { + while (true) { + let list2 = loop$list; + let key = loop$key; + let value = loop$value; + let inspected = loop$inspected; + if (list2 instanceof Empty) { + return reverse(prepend([key, value], inspected)); + } else { + let k = list2.head[0]; + if (isEqual(k, key)) { + let rest$1 = list2.tail; + return reverse_and_prepend(inspected, prepend([k, value], rest$1)); + } else { + let first$1 = list2.head; + let rest$1 = list2.tail; + loop$list = rest$1; + loop$key = key; + loop$value = value; + loop$inspected = prepend(first$1, inspected); + } + } + } +} +function key_set(list2, key, value) { + return key_set_loop(list2, key, value, toList([])); +} +function each(loop$list, loop$f) { + while (true) { + let list2 = loop$list; + let f = loop$f; + if (list2 instanceof Empty) { + return; + } else { + let first$1 = list2.head; + let rest$1 = list2.tail; + f(first$1); + loop$list = rest$1; + loop$f = f; + } + } +} + +// build/dev/javascript/gleam_stdlib/gleam/result.mjs +function map_error(result, fun) { + if (result instanceof Ok) { + return result; + } else { + let error = result[0]; + return new Error(fun(error)); + } +} +function try$(result, fun) { + if (result instanceof Ok) { + let x = result[0]; + return fun(x); + } else { + return result; + } +} +function unwrap2(result, default$) { + if (result instanceof Ok) { + let v = result[0]; + return v; + } else { + return default$; + } +} +function replace_error(result, error) { + if (result instanceof Ok) { + return result; + } else { + return new Error(error); + } +} +// build/dev/javascript/gleam_stdlib/gleam/bool.mjs +function guard(requirement, consequence, alternative) { + if (requirement) { + return consequence; + } else { + return alternative(); + } +} + +// build/dev/javascript/gleam_http/gleam/http.mjs +class Get extends CustomType { +} +class Post extends CustomType { +} +class Head extends CustomType { +} +class Put extends CustomType { +} +class Delete extends CustomType { +} +class Trace extends CustomType { +} +class Connect extends CustomType { +} +class Options extends CustomType { +} +class Patch extends CustomType { +} +class Http extends CustomType { +} +class Https extends CustomType { +} +function method_to_string(method) { + if (method instanceof Get) { + return "GET"; + } else if (method instanceof Post) { + return "POST"; + } else if (method instanceof Head) { + return "HEAD"; + } else if (method instanceof Put) { + return "PUT"; + } else if (method instanceof Delete) { + return "DELETE"; + } else if (method instanceof Trace) { + return "TRACE"; + } else if (method instanceof Connect) { + return "CONNECT"; + } else if (method instanceof Options) { + return "OPTIONS"; + } else if (method instanceof Patch) { + return "PATCH"; + } else { + let method$1 = method[0]; + return method$1; + } +} +function scheme_to_string(scheme) { + if (scheme instanceof Http) { + return "http"; + } else { + return "https"; + } +} +function scheme_from_string(scheme) { + let $ = lowercase(scheme); + if ($ === "http") { + return new Ok(new Http); + } else if ($ === "https") { + return new Ok(new Https); + } else { + return new Error(undefined); + } +} + +// build/dev/javascript/gleam_http/gleam/http/response.mjs +class Response extends CustomType { + constructor(status, headers, body) { + super(); + this.status = status; + this.headers = headers; + this.body = body; + } +} +// build/dev/javascript/gleam_stdlib/gleam/function.mjs +function identity2(x) { + return x; +} +// build/dev/javascript/gleam_json/gleam_json_ffi.mjs +function json_to_string(json) { + return JSON.stringify(json); +} +function object(entries) { + return Object.fromEntries(entries); +} +function identity3(x) { + return x; +} +function array(list2) { + const array2 = []; + while (List$isNonEmpty(list2)) { + array2.push(List$NonEmpty$first(list2)); + list2 = List$NonEmpty$rest(list2); + } + return array2; +} + +// build/dev/javascript/gleam_json/gleam/json.mjs +function to_string2(json) { + return json_to_string(json); +} +function string3(input) { + return identity3(input); +} +function bool(input) { + return identity3(input); +} +function int3(input) { + return identity3(input); +} +function object2(entries) { + return object(entries); +} +function preprocessed_array(from2) { + return array(from2); +} +function array2(entries, inner_type) { + let _pipe = entries; + let _pipe$1 = map2(_pipe, inner_type); + return preprocessed_array(_pipe$1); +} + +// build/dev/javascript/houdini/houdini.ffi.mjs +function do_escape(string4) { + return string4.replaceAll(/[><&"']/g, (replaced) => { + switch (replaced) { + case ">": + return ">"; + case "<": + return "<"; + case "'": + return "'"; + case "&": + return "&"; + case '"': + return """; + default: + return replaced; + } + }); +} + +// build/dev/javascript/houdini/houdini/internal/escape_js.mjs +function escape(text) { + return do_escape(text); +} + +// build/dev/javascript/houdini/houdini.mjs +function escape2(string4) { + return escape(string4); +} + +// build/dev/javascript/lustre/lustre/internals/constants.mjs +var empty_list = /* @__PURE__ */ toList([]); +var error_nil = /* @__PURE__ */ new Error(undefined); + +// build/dev/javascript/lustre/lustre/vdom/vattr.ffi.mjs +var GT = /* @__PURE__ */ Order$Gt(); +var LT = /* @__PURE__ */ Order$Lt(); +var EQ = /* @__PURE__ */ Order$Eq(); +function compare3(a, b) { + if (a.name === b.name) { + return EQ; + } else if (a.name < b.name) { + return LT; + } else { + return GT; + } +} + +// build/dev/javascript/lustre/lustre/vdom/vattr.mjs +class Attribute extends CustomType { + constructor(kind, name, value) { + super(); + this.kind = kind; + this.name = name; + this.value = value; + } +} +class Property extends CustomType { + constructor(kind, name, value) { + super(); + this.kind = kind; + this.name = name; + this.value = value; + } +} +class Event2 extends CustomType { + constructor(kind, name, handler, include, prevent_default, stop_propagation, debounce, throttle) { + super(); + this.kind = kind; + this.name = name; + this.handler = handler; + this.include = include; + this.prevent_default = prevent_default; + this.stop_propagation = stop_propagation; + this.debounce = debounce; + this.throttle = throttle; + } +} +class Handler extends CustomType { + constructor(prevent_default, stop_propagation, message) { + super(); + this.prevent_default = prevent_default; + this.stop_propagation = stop_propagation; + this.message = message; + } +} +class Never extends CustomType { + constructor(kind) { + super(); + this.kind = kind; + } +} +var attribute_kind = 0; +var property_kind = 1; +var event_kind = 2; +var never_kind = 0; +var never = /* @__PURE__ */ new Never(never_kind); +var always_kind = 2; +function merge(loop$attributes, loop$merged) { + while (true) { + let attributes = loop$attributes; + let merged = loop$merged; + if (attributes instanceof Empty) { + return merged; + } else { + let $ = attributes.head; + if ($ instanceof Attribute) { + let $1 = $.name; + if ($1 === "") { + let rest = attributes.tail; + loop$attributes = rest; + loop$merged = merged; + } else if ($1 === "class") { + let $2 = $.value; + if ($2 === "") { + let rest = attributes.tail; + loop$attributes = rest; + loop$merged = merged; + } else { + let $3 = attributes.tail; + if ($3 instanceof Empty) { + let attribute$1 = $; + let rest = $3; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } else { + let $4 = $3.head; + if ($4 instanceof Attribute) { + let $5 = $4.name; + if ($5 === "class") { + let kind = $.kind; + let class1 = $2; + let rest = $3.tail; + let class2 = $4.value; + let value = class1 + " " + class2; + let attribute$1 = new Attribute(kind, "class", value); + loop$attributes = prepend(attribute$1, rest); + loop$merged = merged; + } else { + let attribute$1 = $; + let rest = $3; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } + } else { + let attribute$1 = $; + let rest = $3; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } + } + } + } else if ($1 === "style") { + let $2 = $.value; + if ($2 === "") { + let rest = attributes.tail; + loop$attributes = rest; + loop$merged = merged; + } else { + let $3 = attributes.tail; + if ($3 instanceof Empty) { + let attribute$1 = $; + let rest = $3; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } else { + let $4 = $3.head; + if ($4 instanceof Attribute) { + let $5 = $4.name; + if ($5 === "style") { + let kind = $.kind; + let style1 = $2; + let rest = $3.tail; + let style2 = $4.value; + let value = style1 + ";" + style2; + let attribute$1 = new Attribute(kind, "style", value); + loop$attributes = prepend(attribute$1, rest); + loop$merged = merged; + } else { + let attribute$1 = $; + let rest = $3; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } + } else { + let attribute$1 = $; + let rest = $3; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } + } + } + } else { + let attribute$1 = $; + let rest = attributes.tail; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } + } else { + let attribute$1 = $; + let rest = attributes.tail; + loop$attributes = rest; + loop$merged = prepend(attribute$1, merged); + } + } + } +} +function prepare(attributes) { + if (attributes instanceof Empty) { + return attributes; + } else { + let $ = attributes.tail; + if ($ instanceof Empty) { + return attributes; + } else { + let _pipe = attributes; + let _pipe$1 = sort(_pipe, (a, b) => { + return compare3(b, a); + }); + return merge(_pipe$1, empty_list); + } + } +} +function attribute(name, value) { + return new Attribute(attribute_kind, name, value); +} +function property(name, value) { + return new Property(property_kind, name, value); +} +function event(name, handler, include, prevent_default, stop_propagation, debounce, throttle) { + return new Event2(event_kind, name, handler, include, prevent_default, stop_propagation, debounce, throttle); +} + +// build/dev/javascript/lustre/lustre/attribute.mjs +function attribute2(name, value) { + return attribute(name, value); +} +function property2(name, value) { + return property(name, value); +} +function boolean_attribute(name, value) { + if (value) { + return attribute2(name, ""); + } else { + return property2(name, bool(false)); + } +} +function class$(name) { + return attribute2("class", name); +} +function style(property3, value) { + if (property3 === "") { + return class$(""); + } else if (value === "") { + return class$(""); + } else { + return attribute2("style", property3 + ":" + value + ";"); + } +} +function do_styles(loop$properties, loop$styles) { + while (true) { + let properties = loop$properties; + let styles = loop$styles; + if (properties instanceof Empty) { + return styles; + } else { + let $ = properties.head[0]; + if ($ === "") { + let rest = properties.tail; + loop$properties = rest; + loop$styles = styles; + } else { + let $1 = properties.head[1]; + if ($1 === "") { + let rest = properties.tail; + loop$properties = rest; + loop$styles = styles; + } else { + let rest = properties.tail; + let name$1 = $; + let value$1 = $1; + loop$properties = rest; + loop$styles = styles + name$1 + ":" + value$1 + ";"; + } + } + } + } +} +function styles(properties) { + return attribute2("style", do_styles(properties, "")); +} +function disabled(is_disabled) { + return boolean_attribute("disabled", is_disabled); +} +function min2(value) { + return attribute2("min", value); +} +function placeholder(text) { + return attribute2("placeholder", text); +} +function type_(control_type) { + return attribute2("type", control_type); +} +function value(control_value) { + return attribute2("value", control_value); +} + +// build/dev/javascript/lustre/lustre/effect.mjs +class Effect extends CustomType { + constructor(synchronous, before_paint, after_paint) { + super(); + this.synchronous = synchronous; + this.before_paint = before_paint; + this.after_paint = after_paint; + } +} + +class Actions extends CustomType { + constructor(dispatch, emit, select, root, provide) { + super(); + this.dispatch = dispatch; + this.emit = emit; + this.select = select; + this.root = root; + this.provide = provide; + } +} +var empty = /* @__PURE__ */ new Effect(/* @__PURE__ */ toList([]), /* @__PURE__ */ toList([]), /* @__PURE__ */ toList([])); +function perform(effect, dispatch, emit, select, root, provide) { + let actions = new Actions(dispatch, emit, select, root, provide); + return each(effect.synchronous, (run2) => { + return run2(actions); + }); +} +function none() { + return empty; +} +function from2(effect) { + let task = (actions) => { + let dispatch = actions.dispatch; + return effect(dispatch); + }; + return new Effect(toList([task]), empty.before_paint, empty.after_paint); +} +function batch(effects) { + return fold2(effects, empty, (acc, eff) => { + return new Effect(fold2(eff.synchronous, acc.synchronous, prepend2), fold2(eff.before_paint, acc.before_paint, prepend2), fold2(eff.after_paint, acc.after_paint, prepend2)); + }); +} + +// build/dev/javascript/lustre/lustre/internals/mutable_map.ffi.mjs +function empty2() { + return null; +} +function get2(map6, key) { + return map6?.get(key); +} +function get_or_compute(map6, key, compute) { + return map6?.get(key) ?? compute(); +} +function has_key(map6, key) { + return map6 && map6.has(key); +} +function insert2(map6, key, value2) { + map6 ??= new Map; + map6.set(key, value2); + return map6; +} +function remove(map6, key) { + map6?.delete(key); + return map6; +} + +// build/dev/javascript/lustre/lustre/internals/ref.ffi.mjs +function sameValueZero(x, y) { + if (typeof x === "number" && typeof y === "number") { + return x === y || x !== x && y !== y; + } + return x === y; +} + +// build/dev/javascript/lustre/lustre/internals/ref.mjs +function equal_lists(loop$xs, loop$ys) { + while (true) { + let xs = loop$xs; + let ys = loop$ys; + if (xs instanceof Empty) { + if (ys instanceof Empty) { + return true; + } else { + return false; + } + } else if (ys instanceof Empty) { + return false; + } else { + let x = xs.head; + let xs$1 = xs.tail; + let y = ys.head; + let ys$1 = ys.tail; + let $ = sameValueZero(x, y); + if ($) { + loop$xs = xs$1; + loop$ys = ys$1; + } else { + return $; + } + } + } +} + +// build/dev/javascript/lustre/lustre/vdom/vnode.mjs +class Fragment extends CustomType { + constructor(kind, key, children, keyed_children) { + super(); + this.kind = kind; + this.key = key; + this.children = children; + this.keyed_children = keyed_children; + } +} +class Element extends CustomType { + constructor(kind, key, namespace, tag, attributes, children, keyed_children, self_closing, void$) { + super(); + this.kind = kind; + this.key = key; + this.namespace = namespace; + this.tag = tag; + this.attributes = attributes; + this.children = children; + this.keyed_children = keyed_children; + this.self_closing = self_closing; + this.void = void$; + } +} +class Text extends CustomType { + constructor(kind, key, content) { + super(); + this.kind = kind; + this.key = key; + this.content = content; + } +} +class UnsafeInnerHtml extends CustomType { + constructor(kind, key, namespace, tag, attributes, inner_html) { + super(); + this.kind = kind; + this.key = key; + this.namespace = namespace; + this.tag = tag; + this.attributes = attributes; + this.inner_html = inner_html; + } +} +class Map2 extends CustomType { + constructor(kind, key, mapper, child) { + super(); + this.kind = kind; + this.key = key; + this.mapper = mapper; + this.child = child; + } +} +class Memo extends CustomType { + constructor(kind, key, dependencies, view) { + super(); + this.kind = kind; + this.key = key; + this.dependencies = dependencies; + this.view = view; + } +} +var fragment_kind = 0; +var element_kind = 1; +var text_kind = 2; +var unsafe_inner_html_kind = 3; +var map_kind = 4; +var memo_kind = 5; +function is_void_html_element(tag, namespace) { + if (namespace === "") { + if (tag === "area") { + return true; + } else if (tag === "base") { + return true; + } else if (tag === "br") { + return true; + } else if (tag === "col") { + return true; + } else if (tag === "embed") { + return true; + } else if (tag === "hr") { + return true; + } else if (tag === "img") { + return true; + } else if (tag === "input") { + return true; + } else if (tag === "link") { + return true; + } else if (tag === "meta") { + return true; + } else if (tag === "param") { + return true; + } else if (tag === "source") { + return true; + } else if (tag === "track") { + return true; + } else if (tag === "wbr") { + return true; + } else { + return false; + } + } else { + return false; + } +} +function to_keyed(key, node) { + if (node instanceof Fragment) { + return new Fragment(node.kind, key, node.children, node.keyed_children); + } else if (node instanceof Element) { + return new Element(node.kind, key, node.namespace, node.tag, node.attributes, node.children, node.keyed_children, node.self_closing, node.void); + } else if (node instanceof Text) { + return new Text(node.kind, key, node.content); + } else if (node instanceof UnsafeInnerHtml) { + return new UnsafeInnerHtml(node.kind, key, node.namespace, node.tag, node.attributes, node.inner_html); + } else if (node instanceof Map2) { + let child = node.child; + return new Map2(node.kind, key, node.mapper, to_keyed(key, child)); + } else { + let view = node.view; + return new Memo(node.kind, key, node.dependencies, () => { + return to_keyed(key, view()); + }); + } +} +function fragment(key, children, keyed_children) { + return new Fragment(fragment_kind, key, children, keyed_children); +} +function element(key, namespace, tag, attributes, children, keyed_children, self_closing, void$) { + return new Element(element_kind, key, namespace, tag, prepare(attributes), children, keyed_children, self_closing, void$); +} +function text(key, content) { + return new Text(text_kind, key, content); +} +function map6(element2, mapper) { + if (element2 instanceof Map2) { + let child_mapper = element2.mapper; + return new Map2(map_kind, element2.key, (handler) => { + return identity2(mapper)(child_mapper(handler)); + }, identity2(element2.child)); + } else { + return new Map2(map_kind, element2.key, identity2(mapper), identity2(element2)); + } +} +function memo(key, dependencies, view) { + return new Memo(memo_kind, key, dependencies, view); +} + +// build/dev/javascript/lustre/lustre/element.mjs +function element2(tag, attributes, children) { + return element("", "", tag, attributes, children, empty2(), false, is_void_html_element(tag, "")); +} +function text2(content) { + return text("", content); +} +function none2() { + return text("", ""); +} +function memo2(dependencies, view) { + return memo("", dependencies, view); +} +function ref(value2) { + return identity2(value2); +} +function map7(element3, f) { + return map6(element3, f); +} + +// build/dev/javascript/lustre/lustre/element/html.mjs +function text3(content) { + return text2(content); +} +function h1(attrs, children) { + return element2("h1", attrs, children); +} +function div(attrs, children) { + return element2("div", attrs, children); +} +function li(attrs, children) { + return element2("li", attrs, children); +} +function p(attrs, children) { + return element2("p", attrs, children); +} +function ul(attrs, children) { + return element2("ul", attrs, children); +} +function span(attrs, children) { + return element2("span", attrs, children); +} +function button(attrs, children) { + return element2("button", attrs, children); +} +function input(attrs) { + return element2("input", attrs, empty_list); +} + +// build/dev/javascript/lustre/lustre/vdom/patch.mjs +class Patch2 extends CustomType { + constructor(index4, removed, changes, children) { + super(); + this.index = index4; + this.removed = removed; + this.changes = changes; + this.children = children; + } +} +class ReplaceText extends CustomType { + constructor(kind, content) { + super(); + this.kind = kind; + this.content = content; + } +} +class ReplaceInnerHtml extends CustomType { + constructor(kind, inner_html) { + super(); + this.kind = kind; + this.inner_html = inner_html; + } +} +class Update extends CustomType { + constructor(kind, added, removed) { + super(); + this.kind = kind; + this.added = added; + this.removed = removed; + } +} +class Move extends CustomType { + constructor(kind, key, before) { + super(); + this.kind = kind; + this.key = key; + this.before = before; + } +} +class Replace extends CustomType { + constructor(kind, index4, with$) { + super(); + this.kind = kind; + this.index = index4; + this.with = with$; + } +} +class Remove extends CustomType { + constructor(kind, index4) { + super(); + this.kind = kind; + this.index = index4; + } +} +class Insert extends CustomType { + constructor(kind, children, before) { + super(); + this.kind = kind; + this.children = children; + this.before = before; + } +} +var replace_text_kind = 0; +var replace_inner_html_kind = 1; +var update_kind = 2; +var move_kind = 3; +var remove_kind = 4; +var replace_kind = 5; +var insert_kind = 6; +function new$3(index4, removed, changes, children) { + return new Patch2(index4, removed, changes, children); +} +function replace_text(content) { + return new ReplaceText(replace_text_kind, content); +} +function replace_inner_html(inner_html) { + return new ReplaceInnerHtml(replace_inner_html_kind, inner_html); +} +function update(added, removed) { + return new Update(update_kind, added, removed); +} +function move(key, before) { + return new Move(move_kind, key, before); +} +function remove2(index4) { + return new Remove(remove_kind, index4); +} +function replace2(index4, with$) { + return new Replace(replace_kind, index4, with$); +} +function insert3(children, before) { + return new Insert(insert_kind, children, before); +} + +// build/dev/javascript/lustre/lustre/runtime/transport.mjs +class Mount extends CustomType { + constructor(kind, open_shadow_root, will_adopt_styles, observed_attributes, observed_properties, requested_contexts, provided_contexts, vdom, memos) { + super(); + this.kind = kind; + this.open_shadow_root = open_shadow_root; + this.will_adopt_styles = will_adopt_styles; + this.observed_attributes = observed_attributes; + this.observed_properties = observed_properties; + this.requested_contexts = requested_contexts; + this.provided_contexts = provided_contexts; + this.vdom = vdom; + this.memos = memos; + } +} +class Reconcile extends CustomType { + constructor(kind, patch, memos) { + super(); + this.kind = kind; + this.patch = patch; + this.memos = memos; + } +} +class Emit extends CustomType { + constructor(kind, name, data2) { + super(); + this.kind = kind; + this.name = name; + this.data = data2; + } +} +class Provide extends CustomType { + constructor(kind, key, value2) { + super(); + this.kind = kind; + this.key = key; + this.value = value2; + } +} +class Batch extends CustomType { + constructor(kind, messages) { + super(); + this.kind = kind; + this.messages = messages; + } +} +var ServerMessage$isBatch = (value2) => value2 instanceof Batch; +class AttributeChanged extends CustomType { + constructor(kind, name, value2) { + super(); + this.kind = kind; + this.name = name; + this.value = value2; + } +} +var ServerMessage$isAttributeChanged = (value2) => value2 instanceof AttributeChanged; +class PropertyChanged extends CustomType { + constructor(kind, name, value2) { + super(); + this.kind = kind; + this.name = name; + this.value = value2; + } +} +var ServerMessage$isPropertyChanged = (value2) => value2 instanceof PropertyChanged; +class EventFired extends CustomType { + constructor(kind, path, name, event2) { + super(); + this.kind = kind; + this.path = path; + this.name = name; + this.event = event2; + } +} +var ServerMessage$isEventFired = (value2) => value2 instanceof EventFired; +class ContextProvided extends CustomType { + constructor(kind, key, value2) { + super(); + this.kind = kind; + this.key = key; + this.value = value2; + } +} +var ServerMessage$isContextProvided = (value2) => value2 instanceof ContextProvided; +var mount_kind = 0; +var reconcile_kind = 1; +var emit_kind = 2; +var provide_kind = 3; +function mount(open_shadow_root, will_adopt_styles, observed_attributes, observed_properties, requested_contexts, provided_contexts, vdom, memos) { + return new Mount(mount_kind, open_shadow_root, will_adopt_styles, observed_attributes, observed_properties, requested_contexts, provided_contexts, vdom, memos); +} +function reconcile(patch, memos) { + return new Reconcile(reconcile_kind, patch, memos); +} +function emit(name, data2) { + return new Emit(emit_kind, name, data2); +} +function provide(key, value2) { + return new Provide(provide_kind, key, value2); +} + +// build/dev/javascript/lustre/lustre/vdom/path.mjs +class Root extends CustomType { +} + +class Key extends CustomType { + constructor(key, parent) { + super(); + this.key = key; + this.parent = parent; + } +} + +class Index extends CustomType { + constructor(index4, parent) { + super(); + this.index = index4; + this.parent = parent; + } +} + +class Subtree extends CustomType { + constructor(parent) { + super(); + this.parent = parent; + } +} +var root = /* @__PURE__ */ new Root; +var separator_element = "\t"; +var separator_subtree = "\r"; +var separator_event = ` +`; +function do_matches(loop$path, loop$candidates) { + while (true) { + let path = loop$path; + let candidates = loop$candidates; + if (candidates instanceof Empty) { + return false; + } else { + let candidate = candidates.head; + let rest = candidates.tail; + let $ = starts_with(path, candidate); + if ($) { + return $; + } else { + loop$path = path; + loop$candidates = rest; + } + } + } +} +function add2(parent, index4, key) { + if (key === "") { + return new Index(index4, parent); + } else { + return new Key(key, parent); + } +} +function subtree(path) { + return new Subtree(path); +} +function finish_to_string(acc) { + if (acc instanceof Empty) { + return ""; + } else { + let segments = acc.tail; + return concat2(segments); + } +} +function split_subtree_path(path) { + return split2(path, separator_subtree); +} +function do_to_string(loop$full, loop$path, loop$acc) { + while (true) { + let full = loop$full; + let path = loop$path; + let acc = loop$acc; + if (path instanceof Root) { + return finish_to_string(acc); + } else if (path instanceof Key) { + let key = path.key; + let parent = path.parent; + loop$full = full; + loop$path = parent; + loop$acc = prepend(separator_element, prepend(key, acc)); + } else if (path instanceof Index) { + let index4 = path.index; + let parent = path.parent; + let acc$1 = prepend(separator_element, prepend(to_string(index4), acc)); + loop$full = full; + loop$path = parent; + loop$acc = acc$1; + } else if (!full) { + return finish_to_string(acc); + } else { + let parent = path.parent; + if (acc instanceof Empty) { + loop$full = full; + loop$path = parent; + loop$acc = acc; + } else { + let acc$1 = acc.tail; + loop$full = full; + loop$path = parent; + loop$acc = prepend(separator_subtree, acc$1); + } + } + } +} +function child(path) { + return do_to_string(false, path, empty_list); +} +function to_string4(path) { + return do_to_string(true, path, empty_list); +} +function matches(path, candidates) { + if (candidates instanceof Empty) { + return false; + } else { + return do_matches(to_string4(path), candidates); + } +} +function event2(path, event3) { + return do_to_string(false, path, prepend(separator_event, prepend(event3, empty_list))); +} + +// build/dev/javascript/lustre/lustre/vdom/cache.mjs +class Cache extends CustomType { + constructor(events, vdoms, old_vdoms, dispatched_paths, next_dispatched_paths) { + super(); + this.events = events; + this.vdoms = vdoms; + this.old_vdoms = old_vdoms; + this.dispatched_paths = dispatched_paths; + this.next_dispatched_paths = next_dispatched_paths; + } +} + +class Events extends CustomType { + constructor(handlers, children) { + super(); + this.handlers = handlers; + this.children = children; + } +} + +class Child extends CustomType { + constructor(mapper, events) { + super(); + this.mapper = mapper; + this.events = events; + } +} + +class AddedChildren extends CustomType { + constructor(handlers, children, vdoms) { + super(); + this.handlers = handlers; + this.children = children; + this.vdoms = vdoms; + } +} + +class DecodedEvent extends CustomType { + constructor(path, handler) { + super(); + this.path = path; + this.handler = handler; + } +} + +class DispatchedEvent extends CustomType { + constructor(path) { + super(); + this.path = path; + } +} +function compose_mapper(mapper, child_mapper) { + return (msg) => { + return mapper(child_mapper(msg)); + }; +} +function new_events() { + return new Events(empty2(), empty2()); +} +function new$4() { + return new Cache(new_events(), empty2(), empty2(), empty_list, empty_list); +} +function tick(cache) { + return new Cache(cache.events, empty2(), cache.vdoms, cache.next_dispatched_paths, empty_list); +} +function events(cache) { + return cache.events; +} +function update_events(cache, events2) { + return new Cache(events2, cache.vdoms, cache.old_vdoms, cache.dispatched_paths, cache.next_dispatched_paths); +} +function memos(cache) { + return cache.vdoms; +} +function get_old_memo(cache, old, new$5) { + return get_or_compute(cache.old_vdoms, old, new$5); +} +function keep_memo(cache, old, new$5) { + let node = get_or_compute(cache.old_vdoms, old, new$5); + let vdoms = insert2(cache.vdoms, new$5, node); + return new Cache(cache.events, vdoms, cache.old_vdoms, cache.dispatched_paths, cache.next_dispatched_paths); +} +function add_memo(cache, new$5, node) { + let vdoms = insert2(cache.vdoms, new$5, node); + return new Cache(cache.events, vdoms, cache.old_vdoms, cache.dispatched_paths, cache.next_dispatched_paths); +} +function get_subtree(events2, path, old_mapper) { + let child2 = get_or_compute(events2.children, path, () => { + return new Child(old_mapper, new_events()); + }); + return child2.events; +} +function update_subtree(parent, path, mapper, events2) { + let new_child = new Child(mapper, events2); + let children = insert2(parent.children, path, new_child); + return new Events(parent.handlers, children); +} +function do_add_event(handlers, path, name, handler) { + return insert2(handlers, event2(path, name), handler); +} +function add_event(events2, path, name, handler) { + let handlers = do_add_event(events2.handlers, path, name, handler); + return new Events(handlers, events2.children); +} +function do_remove_event(handlers, path, name) { + return remove(handlers, event2(path, name)); +} +function remove_event(events2, path, name) { + let handlers = do_remove_event(events2.handlers, path, name); + return new Events(handlers, events2.children); +} +function add_attributes(handlers, path, attributes) { + return fold2(attributes, handlers, (events2, attribute3) => { + if (attribute3 instanceof Event2) { + let name = attribute3.name; + let handler = attribute3.handler; + return do_add_event(events2, path, name, handler); + } else { + return events2; + } + }); +} +function do_add_children(loop$handlers, loop$children, loop$vdoms, loop$parent, loop$child_index, loop$nodes) { + while (true) { + let handlers = loop$handlers; + let children = loop$children; + let vdoms = loop$vdoms; + let parent = loop$parent; + let child_index = loop$child_index; + let nodes = loop$nodes; + let next = child_index + 1; + if (nodes instanceof Empty) { + return new AddedChildren(handlers, children, vdoms); + } else { + let $ = nodes.head; + if ($ instanceof Fragment) { + let rest = nodes.tail; + let key = $.key; + let nodes$1 = $.children; + let path = add2(parent, child_index, key); + let $1 = do_add_children(handlers, children, vdoms, path, 0, nodes$1); + let handlers$1; + let children$1; + let vdoms$1; + handlers$1 = $1.handlers; + children$1 = $1.children; + vdoms$1 = $1.vdoms; + loop$handlers = handlers$1; + loop$children = children$1; + loop$vdoms = vdoms$1; + loop$parent = parent; + loop$child_index = next; + loop$nodes = rest; + } else if ($ instanceof Element) { + let rest = nodes.tail; + let key = $.key; + let attributes = $.attributes; + let nodes$1 = $.children; + let path = add2(parent, child_index, key); + let handlers$1 = add_attributes(handlers, path, attributes); + let $1 = do_add_children(handlers$1, children, vdoms, path, 0, nodes$1); + let handlers$2; + let children$1; + let vdoms$1; + handlers$2 = $1.handlers; + children$1 = $1.children; + vdoms$1 = $1.vdoms; + loop$handlers = handlers$2; + loop$children = children$1; + loop$vdoms = vdoms$1; + loop$parent = parent; + loop$child_index = next; + loop$nodes = rest; + } else if ($ instanceof Text) { + let rest = nodes.tail; + loop$handlers = handlers; + loop$children = children; + loop$vdoms = vdoms; + loop$parent = parent; + loop$child_index = next; + loop$nodes = rest; + } else if ($ instanceof UnsafeInnerHtml) { + let rest = nodes.tail; + let key = $.key; + let attributes = $.attributes; + let path = add2(parent, child_index, key); + let handlers$1 = add_attributes(handlers, path, attributes); + loop$handlers = handlers$1; + loop$children = children; + loop$vdoms = vdoms; + loop$parent = parent; + loop$child_index = next; + loop$nodes = rest; + } else if ($ instanceof Map2) { + let rest = nodes.tail; + let key = $.key; + let mapper = $.mapper; + let child2 = $.child; + let path = add2(parent, child_index, key); + let added = do_add_children(empty2(), empty2(), vdoms, subtree(path), 0, prepend(child2, empty_list)); + let vdoms$1 = added.vdoms; + let child_events = new Events(added.handlers, added.children); + let child$1 = new Child(mapper, child_events); + let children$1 = insert2(children, child(path), child$1); + loop$handlers = handlers; + loop$children = children$1; + loop$vdoms = vdoms$1; + loop$parent = parent; + loop$child_index = next; + loop$nodes = rest; + } else { + let rest = nodes.tail; + let view = $.view; + let child_node = view(); + let vdoms$1 = insert2(vdoms, view, child_node); + let next$1 = child_index; + let rest$1 = prepend(child_node, rest); + loop$handlers = handlers; + loop$children = children; + loop$vdoms = vdoms$1; + loop$parent = parent; + loop$child_index = next$1; + loop$nodes = rest$1; + } + } + } +} +function add_children(cache, events2, path, child_index, nodes) { + let vdoms = cache.vdoms; + let handlers; + let children; + handlers = events2.handlers; + children = events2.children; + let $ = do_add_children(handlers, children, vdoms, path, child_index, nodes); + let handlers$1; + let children$1; + let vdoms$1; + handlers$1 = $.handlers; + children$1 = $.children; + vdoms$1 = $.vdoms; + return [ + new Cache(cache.events, vdoms$1, cache.old_vdoms, cache.dispatched_paths, cache.next_dispatched_paths), + new Events(handlers$1, children$1) + ]; +} +function add_child(cache, events2, parent, index4, child2) { + let children = prepend(child2, empty_list); + return add_children(cache, events2, parent, index4, children); +} +function from_node(root2) { + let cache = new$4(); + let $ = add_child(cache, cache.events, root, 0, root2); + let cache$1; + let events$1; + cache$1 = $[0]; + events$1 = $[1]; + return new Cache(events$1, cache$1.vdoms, cache$1.old_vdoms, cache$1.dispatched_paths, cache$1.next_dispatched_paths); +} +function remove_attributes(handlers, path, attributes) { + return fold2(attributes, handlers, (events2, attribute3) => { + if (attribute3 instanceof Event2) { + let name = attribute3.name; + return do_remove_event(events2, path, name); + } else { + return events2; + } + }); +} +function do_remove_children(loop$handlers, loop$children, loop$vdoms, loop$parent, loop$index, loop$nodes) { + while (true) { + let handlers = loop$handlers; + let children = loop$children; + let vdoms = loop$vdoms; + let parent = loop$parent; + let index4 = loop$index; + let nodes = loop$nodes; + let next = index4 + 1; + if (nodes instanceof Empty) { + return new Events(handlers, children); + } else { + let $ = nodes.head; + if ($ instanceof Fragment) { + let rest = nodes.tail; + let key = $.key; + let nodes$1 = $.children; + let path = add2(parent, index4, key); + let $1 = do_remove_children(handlers, children, vdoms, path, 0, nodes$1); + let handlers$1; + let children$1; + handlers$1 = $1.handlers; + children$1 = $1.children; + loop$handlers = handlers$1; + loop$children = children$1; + loop$vdoms = vdoms; + loop$parent = parent; + loop$index = next; + loop$nodes = rest; + } else if ($ instanceof Element) { + let rest = nodes.tail; + let key = $.key; + let attributes = $.attributes; + let nodes$1 = $.children; + let path = add2(parent, index4, key); + let handlers$1 = remove_attributes(handlers, path, attributes); + let $1 = do_remove_children(handlers$1, children, vdoms, path, 0, nodes$1); + let handlers$2; + let children$1; + handlers$2 = $1.handlers; + children$1 = $1.children; + loop$handlers = handlers$2; + loop$children = children$1; + loop$vdoms = vdoms; + loop$parent = parent; + loop$index = next; + loop$nodes = rest; + } else if ($ instanceof Text) { + let rest = nodes.tail; + loop$handlers = handlers; + loop$children = children; + loop$vdoms = vdoms; + loop$parent = parent; + loop$index = next; + loop$nodes = rest; + } else if ($ instanceof UnsafeInnerHtml) { + let rest = nodes.tail; + let key = $.key; + let attributes = $.attributes; + let path = add2(parent, index4, key); + let handlers$1 = remove_attributes(handlers, path, attributes); + loop$handlers = handlers$1; + loop$children = children; + loop$vdoms = vdoms; + loop$parent = parent; + loop$index = next; + loop$nodes = rest; + } else if ($ instanceof Map2) { + let rest = nodes.tail; + let key = $.key; + let path = add2(parent, index4, key); + let children$1 = remove(children, child(path)); + loop$handlers = handlers; + loop$children = children$1; + loop$vdoms = vdoms; + loop$parent = parent; + loop$index = next; + loop$nodes = rest; + } else { + let rest = nodes.tail; + let view = $.view; + let $1 = has_key(vdoms, view); + if ($1) { + let child2 = get2(vdoms, view); + let nodes$1 = prepend(child2, rest); + loop$handlers = handlers; + loop$children = children; + loop$vdoms = vdoms; + loop$parent = parent; + loop$index = index4; + loop$nodes = nodes$1; + } else { + loop$handlers = handlers; + loop$children = children; + loop$vdoms = vdoms; + loop$parent = parent; + loop$index = next; + loop$nodes = rest; + } + } + } + } +} +function remove_child(cache, events2, parent, child_index, child2) { + return do_remove_children(events2.handlers, events2.children, cache.old_vdoms, parent, child_index, prepend(child2, empty_list)); +} +function replace_child(cache, events2, parent, child_index, prev, next) { + let events$1 = remove_child(cache, events2, parent, child_index, prev); + return add_child(cache, events$1, parent, child_index, next); +} +function dispatch(cache, event3) { + let next_dispatched_paths = prepend(event3.path, cache.next_dispatched_paths); + let cache$1 = new Cache(cache.events, cache.vdoms, cache.old_vdoms, cache.dispatched_paths, next_dispatched_paths); + if (event3 instanceof DecodedEvent) { + let handler = event3.handler; + return [cache$1, new Ok(handler)]; + } else { + return [cache$1, error_nil]; + } +} +function has_dispatched_events(cache, path) { + return matches(path, cache.dispatched_paths); +} +function get_handler(loop$events, loop$path, loop$mapper) { + while (true) { + let events2 = loop$events; + let path = loop$path; + let mapper = loop$mapper; + if (path instanceof Empty) { + return error_nil; + } else { + let $ = path.tail; + if ($ instanceof Empty) { + let key = path.head; + let $1 = has_key(events2.handlers, key); + if ($1) { + let handler = get2(events2.handlers, key); + return new Ok(map3(handler, (handler2) => { + return new Handler(handler2.prevent_default, handler2.stop_propagation, identity2(mapper)(handler2.message)); + })); + } else { + return error_nil; + } + } else { + let key = path.head; + let path$1 = $; + let $1 = has_key(events2.children, key); + if ($1) { + let child2 = get2(events2.children, key); + let mapper$1 = compose_mapper(mapper, child2.mapper); + loop$events = child2.events; + loop$path = path$1; + loop$mapper = mapper$1; + } else { + return error_nil; + } + } + } + } +} +function decode2(cache, path, name, event3) { + let parts = split_subtree_path(path + separator_event + name); + let $ = get_handler(cache.events, parts, identity2); + if ($ instanceof Ok) { + let handler = $[0]; + let $1 = run(event3, handler); + if ($1 instanceof Ok) { + let handler$1 = $1[0]; + return new DecodedEvent(path, handler$1); + } else { + return new DispatchedEvent(path); + } + } else { + return new DispatchedEvent(path); + } +} +function handle(cache, path, name, event3) { + let _pipe = decode2(cache, path, name, event3); + return ((_capture) => { + return dispatch(cache, _capture); + })(_pipe); +} + +// build/dev/javascript/lustre/lustre/runtime/server/runtime.mjs +class ClientDispatchedMessage extends CustomType { + constructor(message) { + super(); + this.message = message; + } +} +var Message$isClientDispatchedMessage = (value2) => value2 instanceof ClientDispatchedMessage; +class ClientRegisteredCallback extends CustomType { + constructor(callback) { + super(); + this.callback = callback; + } +} +var Message$isClientRegisteredCallback = (value2) => value2 instanceof ClientRegisteredCallback; +class ClientDeregisteredCallback extends CustomType { + constructor(callback) { + super(); + this.callback = callback; + } +} +var Message$isClientDeregisteredCallback = (value2) => value2 instanceof ClientDeregisteredCallback; +class EffectDispatchedMessage extends CustomType { + constructor(message) { + super(); + this.message = message; + } +} +var Message$EffectDispatchedMessage = (message) => new EffectDispatchedMessage(message); +var Message$isEffectDispatchedMessage = (value2) => value2 instanceof EffectDispatchedMessage; +class EffectEmitEvent extends CustomType { + constructor(name, data2) { + super(); + this.name = name; + this.data = data2; + } +} +var Message$EffectEmitEvent = (name, data2) => new EffectEmitEvent(name, data2); +var Message$isEffectEmitEvent = (value2) => value2 instanceof EffectEmitEvent; +class EffectProvidedValue extends CustomType { + constructor(key, value2) { + super(); + this.key = key; + this.value = value2; + } +} +var Message$EffectProvidedValue = (key, value2) => new EffectProvidedValue(key, value2); +var Message$isEffectProvidedValue = (value2) => value2 instanceof EffectProvidedValue; +class SystemRequestedShutdown extends CustomType { +} +var Message$isSystemRequestedShutdown = (value2) => value2 instanceof SystemRequestedShutdown; + +// build/dev/javascript/lustre/lustre/runtime/app.mjs +class App extends CustomType { + constructor(name, init, update2, view, config2) { + super(); + this.name = name; + this.init = init; + this.update = update2; + this.view = view; + this.config = config2; + } +} +class Config2 extends CustomType { + constructor(open_shadow_root, adopt_styles, delegates_focus, attributes, properties, contexts, is_form_associated, on_form_autofill, on_form_reset, on_form_restore, on_connect, on_adopt, on_disconnect) { + super(); + this.open_shadow_root = open_shadow_root; + this.adopt_styles = adopt_styles; + this.delegates_focus = delegates_focus; + this.attributes = attributes; + this.properties = properties; + this.contexts = contexts; + this.is_form_associated = is_form_associated; + this.on_form_autofill = on_form_autofill; + this.on_form_reset = on_form_reset; + this.on_form_restore = on_form_restore; + this.on_connect = on_connect; + this.on_adopt = on_adopt; + this.on_disconnect = on_disconnect; + } +} +var default_config = /* @__PURE__ */ new Config2(true, true, false, empty_list, empty_list, empty_list, false, /* @__PURE__ */ new None, /* @__PURE__ */ new None, /* @__PURE__ */ new None, /* @__PURE__ */ new None, /* @__PURE__ */ new None, /* @__PURE__ */ new None); + +// build/dev/javascript/lustre/lustre/internals/equals.ffi.mjs +var isEqual2 = (a, b) => { + if (a === b) { + return true; + } + if (a == null || b == null) { + return false; + } + const type = typeof a; + if (type !== typeof b) { + return false; + } + if (type !== "object") { + return false; + } + const ctor = a.constructor; + if (ctor !== b.constructor) { + return false; + } + if (Array.isArray(a)) { + return areArraysEqual(a, b); + } + return areObjectsEqual(a, b); +}; +var areArraysEqual = (a, b) => { + let index4 = a.length; + if (index4 !== b.length) { + return false; + } + while (index4--) { + if (!isEqual2(a[index4], b[index4])) { + return false; + } + } + return true; +}; +var areObjectsEqual = (a, b) => { + const properties = Object.keys(a); + let index4 = properties.length; + if (Object.keys(b).length !== index4) { + return false; + } + while (index4--) { + const property3 = properties[index4]; + if (!Object.hasOwn(b, property3)) { + return false; + } + if (!isEqual2(a[property3], b[property3])) { + return false; + } + } + return true; +}; + +// build/dev/javascript/lustre/lustre/vdom/diff.mjs +class Diff extends CustomType { + constructor(patch, cache) { + super(); + this.patch = patch; + this.cache = cache; + } +} +class PartialDiff extends CustomType { + constructor(patch, cache, events2) { + super(); + this.patch = patch; + this.cache = cache; + this.events = events2; + } +} + +class AttributeChange extends CustomType { + constructor(added, removed, events2) { + super(); + this.added = added; + this.removed = removed; + this.events = events2; + } +} +function is_controlled(cache, namespace, tag, path) { + if (tag === "input" && namespace === "") { + return has_dispatched_events(cache, path); + } else if (tag === "select" && namespace === "") { + return has_dispatched_events(cache, path); + } else if (tag === "textarea" && namespace === "") { + return has_dispatched_events(cache, path); + } else { + return false; + } +} +function diff_attributes(loop$controlled, loop$path, loop$events, loop$old, loop$new, loop$added, loop$removed) { + while (true) { + let controlled = loop$controlled; + let path = loop$path; + let events2 = loop$events; + let old = loop$old; + let new$5 = loop$new; + let added = loop$added; + let removed = loop$removed; + if (old instanceof Empty) { + if (new$5 instanceof Empty) { + return new AttributeChange(added, removed, events2); + } else { + let $ = new$5.head; + if ($ instanceof Event2) { + let next = $; + let new$1 = new$5.tail; + let name = $.name; + let handler = $.handler; + let events$1 = add_event(events2, path, name, handler); + let added$1 = prepend(next, added); + loop$controlled = controlled; + loop$path = path; + loop$events = events$1; + loop$old = old; + loop$new = new$1; + loop$added = added$1; + loop$removed = removed; + } else { + let next = $; + let new$1 = new$5.tail; + let added$1 = prepend(next, added); + loop$controlled = controlled; + loop$path = path; + loop$events = events2; + loop$old = old; + loop$new = new$1; + loop$added = added$1; + loop$removed = removed; + } + } + } else if (new$5 instanceof Empty) { + let $ = old.head; + if ($ instanceof Event2) { + let prev = $; + let old$1 = old.tail; + let name = $.name; + let events$1 = remove_event(events2, path, name); + let removed$1 = prepend(prev, removed); + loop$controlled = controlled; + loop$path = path; + loop$events = events$1; + loop$old = old$1; + loop$new = new$5; + loop$added = added; + loop$removed = removed$1; + } else { + let prev = $; + let old$1 = old.tail; + let removed$1 = prepend(prev, removed); + loop$controlled = controlled; + loop$path = path; + loop$events = events2; + loop$old = old$1; + loop$new = new$5; + loop$added = added; + loop$removed = removed$1; + } + } else { + let prev = old.head; + let remaining_old = old.tail; + let next = new$5.head; + let remaining_new = new$5.tail; + let $ = compare3(prev, next); + if ($ instanceof Lt) { + if (prev instanceof Event2) { + let name = prev.name; + loop$controlled = controlled; + loop$path = path; + loop$events = remove_event(events2, path, name); + loop$old = remaining_old; + loop$new = new$5; + loop$added = added; + loop$removed = prepend(prev, removed); + } else { + loop$controlled = controlled; + loop$path = path; + loop$events = events2; + loop$old = remaining_old; + loop$new = new$5; + loop$added = added; + loop$removed = prepend(prev, removed); + } + } else if ($ instanceof Eq) { + if (prev instanceof Attribute) { + if (next instanceof Attribute) { + let _block; + let $1 = next.name; + if ($1 === "value") { + _block = controlled || prev.value !== next.value; + } else if ($1 === "checked") { + _block = controlled || prev.value !== next.value; + } else if ($1 === "selected") { + _block = controlled || prev.value !== next.value; + } else { + _block = prev.value !== next.value; + } + let has_changes = _block; + let _block$1; + if (has_changes) { + _block$1 = prepend(next, added); + } else { + _block$1 = added; + } + let added$1 = _block$1; + loop$controlled = controlled; + loop$path = path; + loop$events = events2; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed; + } else if (next instanceof Event2) { + let name = next.name; + let handler = next.handler; + loop$controlled = controlled; + loop$path = path; + loop$events = add_event(events2, path, name, handler); + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = prepend(next, added); + loop$removed = prepend(prev, removed); + } else { + loop$controlled = controlled; + loop$path = path; + loop$events = events2; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = prepend(next, added); + loop$removed = prepend(prev, removed); + } + } else if (prev instanceof Property) { + if (next instanceof Property) { + let _block; + let $1 = next.name; + if ($1 === "scrollLeft") { + _block = true; + } else if ($1 === "scrollRight") { + _block = true; + } else if ($1 === "value") { + _block = controlled || !isEqual2(prev.value, next.value); + } else if ($1 === "checked") { + _block = controlled || !isEqual2(prev.value, next.value); + } else if ($1 === "selected") { + _block = controlled || !isEqual2(prev.value, next.value); + } else { + _block = !isEqual2(prev.value, next.value); + } + let has_changes = _block; + let _block$1; + if (has_changes) { + _block$1 = prepend(next, added); + } else { + _block$1 = added; + } + let added$1 = _block$1; + loop$controlled = controlled; + loop$path = path; + loop$events = events2; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed; + } else if (next instanceof Event2) { + let name = next.name; + let handler = next.handler; + loop$controlled = controlled; + loop$path = path; + loop$events = add_event(events2, path, name, handler); + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = prepend(next, added); + loop$removed = prepend(prev, removed); + } else { + loop$controlled = controlled; + loop$path = path; + loop$events = events2; + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = prepend(next, added); + loop$removed = prepend(prev, removed); + } + } else if (next instanceof Event2) { + let name = next.name; + let handler = next.handler; + let has_changes = prev.prevent_default.kind !== next.prevent_default.kind || prev.stop_propagation.kind !== next.stop_propagation.kind || prev.debounce !== next.debounce || prev.throttle !== next.throttle; + let _block; + if (has_changes) { + _block = prepend(next, added); + } else { + _block = added; + } + let added$1 = _block; + loop$controlled = controlled; + loop$path = path; + loop$events = add_event(events2, path, name, handler); + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = added$1; + loop$removed = removed; + } else { + let name = prev.name; + loop$controlled = controlled; + loop$path = path; + loop$events = remove_event(events2, path, name); + loop$old = remaining_old; + loop$new = remaining_new; + loop$added = prepend(next, added); + loop$removed = prepend(prev, removed); + } + } else if (next instanceof Event2) { + let name = next.name; + let handler = next.handler; + loop$controlled = controlled; + loop$path = path; + loop$events = add_event(events2, path, name, handler); + loop$old = old; + loop$new = remaining_new; + loop$added = prepend(next, added); + loop$removed = removed; + } else { + loop$controlled = controlled; + loop$path = path; + loop$events = events2; + loop$old = old; + loop$new = remaining_new; + loop$added = prepend(next, added); + loop$removed = removed; + } + } + } +} +function do_diff(loop$old, loop$old_keyed, loop$new, loop$new_keyed, loop$moved, loop$moved_offset, loop$removed, loop$node_index, loop$patch_index, loop$changes, loop$children, loop$path, loop$cache, loop$events) { + while (true) { + let old = loop$old; + let old_keyed = loop$old_keyed; + let new$5 = loop$new; + let new_keyed = loop$new_keyed; + let moved = loop$moved; + let moved_offset = loop$moved_offset; + let removed = loop$removed; + let node_index = loop$node_index; + let patch_index = loop$patch_index; + let changes = loop$changes; + let children = loop$children; + let path = loop$path; + let cache = loop$cache; + let events2 = loop$events; + if (old instanceof Empty) { + if (new$5 instanceof Empty) { + let patch = new Patch2(patch_index, removed, changes, children); + return new PartialDiff(patch, cache, events2); + } else { + let $ = add_children(cache, events2, path, node_index, new$5); + let cache$1; + let events$1; + cache$1 = $[0]; + events$1 = $[1]; + let insert4 = insert3(new$5, node_index - moved_offset); + let changes$1 = prepend(insert4, changes); + let patch = new Patch2(patch_index, removed, changes$1, children); + return new PartialDiff(patch, cache$1, events$1); + } + } else if (new$5 instanceof Empty) { + let prev = old.head; + let old$1 = old.tail; + let _block; + let $ = prev.key === "" || !has_key(moved, prev.key); + if ($) { + _block = removed + 1; + } else { + _block = removed; + } + let removed$1 = _block; + let events$1 = remove_child(cache, events2, path, node_index, prev); + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$5; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed$1; + loop$node_index = node_index; + loop$patch_index = patch_index; + loop$changes = changes; + loop$children = children; + loop$path = path; + loop$cache = cache; + loop$events = events$1; + } else { + let prev = old.head; + let next = new$5.head; + if (prev.key !== next.key) { + let old_remaining = old.tail; + let new_remaining = new$5.tail; + let next_did_exist = has_key(old_keyed, next.key); + let prev_does_exist = has_key(new_keyed, prev.key); + if (prev_does_exist) { + if (next_did_exist) { + let $ = has_key(moved, prev.key); + if ($) { + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new$5; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset - 1; + loop$removed = removed; + loop$node_index = node_index; + loop$patch_index = patch_index; + loop$changes = changes; + loop$children = children; + loop$path = path; + loop$cache = cache; + loop$events = events2; + } else { + let match = get2(old_keyed, next.key); + let before = node_index - moved_offset; + let changes$1 = prepend(move(next.key, before), changes); + let moved$1 = insert2(moved, next.key, undefined); + loop$old = prepend(match, old); + loop$old_keyed = old_keyed; + loop$new = new$5; + loop$new_keyed = new_keyed; + loop$moved = moved$1; + loop$moved_offset = moved_offset + 1; + loop$removed = removed; + loop$node_index = node_index; + loop$patch_index = patch_index; + loop$changes = changes$1; + loop$children = children; + loop$path = path; + loop$cache = cache; + loop$events = events2; + } + } else { + let before = node_index - moved_offset; + let $ = add_child(cache, events2, path, node_index, next); + let cache$1; + let events$1; + cache$1 = $[0]; + events$1 = $[1]; + let insert4 = insert3(toList([next]), before); + let changes$1 = prepend(insert4, changes); + loop$old = old; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset + 1; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$changes = changes$1; + loop$children = children; + loop$path = path; + loop$cache = cache$1; + loop$events = events$1; + } + } else if (next_did_exist) { + let index4 = node_index - moved_offset; + let changes$1 = prepend(remove2(index4), changes); + let events$1 = remove_child(cache, events2, path, node_index, prev); + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new$5; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset - 1; + loop$removed = removed; + loop$node_index = node_index; + loop$patch_index = patch_index; + loop$changes = changes$1; + loop$children = children; + loop$path = path; + loop$cache = cache; + loop$events = events$1; + } else { + let change = replace2(node_index - moved_offset, next); + let $ = replace_child(cache, events2, path, node_index, prev, next); + let cache$1; + let events$1; + cache$1 = $[0]; + events$1 = $[1]; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$changes = prepend(change, changes); + loop$children = children; + loop$path = path; + loop$cache = cache$1; + loop$events = events$1; + } + } else { + let $ = old.head; + if ($ instanceof Fragment) { + let $1 = new$5.head; + if ($1 instanceof Fragment) { + let prev2 = $; + let old$1 = old.tail; + let next2 = $1; + let new$1 = new$5.tail; + let $2 = do_diff(prev2.children, prev2.keyed_children, next2.children, next2.keyed_children, empty2(), 0, 0, 0, node_index, empty_list, empty_list, add2(path, node_index, next2.key), cache, events2); + let patch; + let cache$1; + let events$1; + patch = $2.patch; + cache$1 = $2.cache; + events$1 = $2.events; + let _block; + let $3 = patch.changes; + if ($3 instanceof Empty) { + let $4 = patch.children; + if ($4 instanceof Empty) { + let $5 = patch.removed; + if ($5 === 0) { + _block = children; + } else { + _block = prepend(patch, children); + } + } else { + _block = prepend(patch, children); + } + } else { + _block = prepend(patch, children); + } + let children$1 = _block; + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$1; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$changes = changes; + loop$children = children$1; + loop$path = path; + loop$cache = cache$1; + loop$events = events$1; + } else { + let prev2 = $; + let old_remaining = old.tail; + let next2 = $1; + let new_remaining = new$5.tail; + let change = replace2(node_index - moved_offset, next2); + let $2 = replace_child(cache, events2, path, node_index, prev2, next2); + let cache$1; + let events$1; + cache$1 = $2[0]; + events$1 = $2[1]; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$changes = prepend(change, changes); + loop$children = children; + loop$path = path; + loop$cache = cache$1; + loop$events = events$1; + } + } else if ($ instanceof Element) { + let $1 = new$5.head; + if ($1 instanceof Element) { + let prev2 = $; + let next2 = $1; + if (prev2.namespace === next2.namespace && prev2.tag === next2.tag) { + let old$1 = old.tail; + let new$1 = new$5.tail; + let child_path = add2(path, node_index, next2.key); + let controlled = is_controlled(cache, next2.namespace, next2.tag, child_path); + let $2 = diff_attributes(controlled, child_path, events2, prev2.attributes, next2.attributes, empty_list, empty_list); + let added_attrs; + let removed_attrs; + let events$1; + added_attrs = $2.added; + removed_attrs = $2.removed; + events$1 = $2.events; + let _block; + if (added_attrs instanceof Empty && removed_attrs instanceof Empty) { + _block = empty_list; + } else { + _block = toList([update(added_attrs, removed_attrs)]); + } + let initial_child_changes = _block; + let $3 = do_diff(prev2.children, prev2.keyed_children, next2.children, next2.keyed_children, empty2(), 0, 0, 0, node_index, initial_child_changes, empty_list, child_path, cache, events$1); + let patch; + let cache$1; + let events$2; + patch = $3.patch; + cache$1 = $3.cache; + events$2 = $3.events; + let _block$1; + let $4 = patch.changes; + if ($4 instanceof Empty) { + let $5 = patch.children; + if ($5 instanceof Empty) { + let $6 = patch.removed; + if ($6 === 0) { + _block$1 = children; + } else { + _block$1 = prepend(patch, children); + } + } else { + _block$1 = prepend(patch, children); + } + } else { + _block$1 = prepend(patch, children); + } + let children$1 = _block$1; + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$1; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$changes = changes; + loop$children = children$1; + loop$path = path; + loop$cache = cache$1; + loop$events = events$2; + } else { + let prev3 = $; + let old_remaining = old.tail; + let next3 = $1; + let new_remaining = new$5.tail; + let change = replace2(node_index - moved_offset, next3); + let $2 = replace_child(cache, events2, path, node_index, prev3, next3); + let cache$1; + let events$1; + cache$1 = $2[0]; + events$1 = $2[1]; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$changes = prepend(change, changes); + loop$children = children; + loop$path = path; + loop$cache = cache$1; + loop$events = events$1; + } + } else { + let prev2 = $; + let old_remaining = old.tail; + let next2 = $1; + let new_remaining = new$5.tail; + let change = replace2(node_index - moved_offset, next2); + let $2 = replace_child(cache, events2, path, node_index, prev2, next2); + let cache$1; + let events$1; + cache$1 = $2[0]; + events$1 = $2[1]; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$changes = prepend(change, changes); + loop$children = children; + loop$path = path; + loop$cache = cache$1; + loop$events = events$1; + } + } else if ($ instanceof Text) { + let $1 = new$5.head; + if ($1 instanceof Text) { + let prev2 = $; + let next2 = $1; + if (prev2.content === next2.content) { + let old$1 = old.tail; + let new$1 = new$5.tail; + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$1; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$changes = changes; + loop$children = children; + loop$path = path; + loop$cache = cache; + loop$events = events2; + } else { + let old$1 = old.tail; + let next3 = $1; + let new$1 = new$5.tail; + let child2 = new$3(node_index, 0, toList([replace_text(next3.content)]), empty_list); + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$1; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$changes = changes; + loop$children = prepend(child2, children); + loop$path = path; + loop$cache = cache; + loop$events = events2; + } + } else { + let prev2 = $; + let old_remaining = old.tail; + let next2 = $1; + let new_remaining = new$5.tail; + let change = replace2(node_index - moved_offset, next2); + let $2 = replace_child(cache, events2, path, node_index, prev2, next2); + let cache$1; + let events$1; + cache$1 = $2[0]; + events$1 = $2[1]; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$changes = prepend(change, changes); + loop$children = children; + loop$path = path; + loop$cache = cache$1; + loop$events = events$1; + } + } else if ($ instanceof UnsafeInnerHtml) { + let $1 = new$5.head; + if ($1 instanceof UnsafeInnerHtml) { + let prev2 = $; + let old$1 = old.tail; + let next2 = $1; + let new$1 = new$5.tail; + let child_path = add2(path, node_index, next2.key); + let $2 = diff_attributes(false, child_path, events2, prev2.attributes, next2.attributes, empty_list, empty_list); + let added_attrs; + let removed_attrs; + let events$1; + added_attrs = $2.added; + removed_attrs = $2.removed; + events$1 = $2.events; + let _block; + if (added_attrs instanceof Empty && removed_attrs instanceof Empty) { + _block = empty_list; + } else { + _block = toList([update(added_attrs, removed_attrs)]); + } + let child_changes = _block; + let _block$1; + let $3 = prev2.inner_html === next2.inner_html; + if ($3) { + _block$1 = child_changes; + } else { + _block$1 = prepend(replace_inner_html(next2.inner_html), child_changes); + } + let child_changes$1 = _block$1; + let _block$2; + if (child_changes$1 instanceof Empty) { + _block$2 = children; + } else { + _block$2 = prepend(new$3(node_index, 0, child_changes$1, toList([])), children); + } + let children$1 = _block$2; + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$1; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$changes = changes; + loop$children = children$1; + loop$path = path; + loop$cache = cache; + loop$events = events$1; + } else { + let prev2 = $; + let old_remaining = old.tail; + let next2 = $1; + let new_remaining = new$5.tail; + let change = replace2(node_index - moved_offset, next2); + let $2 = replace_child(cache, events2, path, node_index, prev2, next2); + let cache$1; + let events$1; + cache$1 = $2[0]; + events$1 = $2[1]; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$changes = prepend(change, changes); + loop$children = children; + loop$path = path; + loop$cache = cache$1; + loop$events = events$1; + } + } else if ($ instanceof Map2) { + let $1 = new$5.head; + if ($1 instanceof Map2) { + let prev2 = $; + let old$1 = old.tail; + let next2 = $1; + let new$1 = new$5.tail; + let child_path = add2(path, node_index, next2.key); + let child_key = child(child_path); + let $2 = do_diff(prepend(prev2.child, empty_list), empty2(), prepend(next2.child, empty_list), empty2(), empty2(), 0, 0, 0, node_index, empty_list, empty_list, subtree(child_path), cache, get_subtree(events2, child_key, prev2.mapper)); + let patch; + let cache$1; + let child_events; + patch = $2.patch; + cache$1 = $2.cache; + child_events = $2.events; + let events$1 = update_subtree(events2, child_key, next2.mapper, child_events); + let _block; + let $3 = patch.changes; + if ($3 instanceof Empty) { + let $4 = patch.children; + if ($4 instanceof Empty) { + let $5 = patch.removed; + if ($5 === 0) { + _block = children; + } else { + _block = prepend(patch, children); + } + } else { + _block = prepend(patch, children); + } + } else { + _block = prepend(patch, children); + } + let children$1 = _block; + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$1; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$changes = changes; + loop$children = children$1; + loop$path = path; + loop$cache = cache$1; + loop$events = events$1; + } else { + let prev2 = $; + let old_remaining = old.tail; + let next2 = $1; + let new_remaining = new$5.tail; + let change = replace2(node_index - moved_offset, next2); + let $2 = replace_child(cache, events2, path, node_index, prev2, next2); + let cache$1; + let events$1; + cache$1 = $2[0]; + events$1 = $2[1]; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$changes = prepend(change, changes); + loop$children = children; + loop$path = path; + loop$cache = cache$1; + loop$events = events$1; + } + } else { + let $1 = new$5.head; + if ($1 instanceof Memo) { + let prev2 = $; + let old$1 = old.tail; + let next2 = $1; + let new$1 = new$5.tail; + let $2 = equal_lists(prev2.dependencies, next2.dependencies); + if ($2) { + let cache$1 = keep_memo(cache, prev2.view, next2.view); + loop$old = old$1; + loop$old_keyed = old_keyed; + loop$new = new$1; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$changes = changes; + loop$children = children; + loop$path = path; + loop$cache = cache$1; + loop$events = events2; + } else { + let prev_node = get_old_memo(cache, prev2.view, prev2.view); + let next_node = next2.view(); + let cache$1 = add_memo(cache, next2.view, next_node); + loop$old = prepend(prev_node, old$1); + loop$old_keyed = old_keyed; + loop$new = prepend(next_node, new$1); + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index; + loop$patch_index = patch_index; + loop$changes = changes; + loop$children = children; + loop$path = path; + loop$cache = cache$1; + loop$events = events2; + } + } else { + let prev2 = $; + let old_remaining = old.tail; + let next2 = $1; + let new_remaining = new$5.tail; + let change = replace2(node_index - moved_offset, next2); + let $2 = replace_child(cache, events2, path, node_index, prev2, next2); + let cache$1; + let events$1; + cache$1 = $2[0]; + events$1 = $2[1]; + loop$old = old_remaining; + loop$old_keyed = old_keyed; + loop$new = new_remaining; + loop$new_keyed = new_keyed; + loop$moved = moved; + loop$moved_offset = moved_offset; + loop$removed = removed; + loop$node_index = node_index + 1; + loop$patch_index = patch_index; + loop$changes = prepend(change, changes); + loop$children = children; + loop$path = path; + loop$cache = cache$1; + loop$events = events$1; + } + } + } + } + } +} +function diff(cache, old, new$5) { + let cache$1 = tick(cache); + let $ = do_diff(prepend(old, empty_list), empty2(), prepend(new$5, empty_list), empty2(), empty2(), 0, 0, 0, 0, empty_list, empty_list, root, cache$1, events(cache$1)); + let patch; + let cache$2; + let events2; + patch = $.patch; + cache$2 = $.cache; + events2 = $.events; + return new Diff(patch, update_events(cache$2, events2)); +} + +// build/dev/javascript/lustre/lustre/internals/list.ffi.mjs +var toList2 = (arr) => arr.reduceRight((xs, x) => List$NonEmpty(x, xs), empty_list); +var iterate = (list4, callback) => { + if (Array.isArray(list4)) { + for (let i = 0;i < list4.length; i++) { + callback(list4[i]); + } + } else if (list4) { + for (list4;List$NonEmpty$rest(list4); list4 = List$NonEmpty$rest(list4)) { + callback(List$NonEmpty$first(list4)); + } + } +}; +var append4 = (a, b) => { + if (!List$NonEmpty$rest(a)) { + return b; + } else if (!List$NonEmpty$rest(b)) { + return a; + } else { + return append2(a, b); + } +}; + +// build/dev/javascript/lustre/lustre/internals/constants.ffi.mjs +var NAMESPACE_HTML = "http://www.w3.org/1999/xhtml"; +var ELEMENT_NODE = 1; +var TEXT_NODE = 3; +var COMMENT_NODE = 8; +var SUPPORTS_MOVE_BEFORE = !!globalThis.HTMLElement?.prototype?.moveBefore; + +// build/dev/javascript/lustre/lustre/vdom/reconciler.ffi.mjs +var setTimeout = globalThis.setTimeout; +var clearTimeout = globalThis.clearTimeout; +var createElementNS = (ns, name) => globalThis.document.createElementNS(ns, name); +var createTextNode = (data2) => globalThis.document.createTextNode(data2); +var createComment = (data2) => globalThis.document.createComment(data2); +var createDocumentFragment = () => globalThis.document.createDocumentFragment(); +var insertBefore = (parent, node, reference) => parent.insertBefore(node, reference); +var moveBefore = SUPPORTS_MOVE_BEFORE ? (parent, node, reference) => parent.moveBefore(node, reference) : insertBefore; +var removeChild = (parent, child2) => parent.removeChild(child2); +var getAttribute = (node, name) => node.getAttribute(name); +var setAttribute = (node, name, value2) => node.setAttribute(name, value2); +var removeAttribute = (node, name) => node.removeAttribute(name); +var addEventListener = (node, name, handler, options) => node.addEventListener(name, handler, options); +var removeEventListener = (node, name, handler) => node.removeEventListener(name, handler); +var setInnerHtml = (node, innerHtml) => node.innerHTML = innerHtml; +var setData = (node, data2) => node.data = data2; +var meta = Symbol("lustre"); + +class MetadataNode { + constructor(kind, parent, node, key) { + this.kind = kind; + this.key = key; + this.parent = parent; + this.children = []; + this.node = node; + this.endNode = null; + this.handlers = new Map; + this.throttles = new Map; + this.debouncers = new Map; + } + get isVirtual() { + return this.kind === fragment_kind || this.kind === map_kind; + } + get parentNode() { + return this.isVirtual ? this.node.parentNode : this.node; + } +} +var insertMetadataChild = (kind, parent, node, index4, key) => { + const child2 = new MetadataNode(kind, parent, node, key); + node[meta] = child2; + parent?.children.splice(index4, 0, child2); + return child2; +}; +var getPath = (node) => { + let path = ""; + for (let current = node[meta];current.parent; current = current.parent) { + const separator = current.parent && current.parent.kind === map_kind ? separator_subtree : separator_element; + if (current.key) { + path = `${separator}${current.key}${path}`; + } else { + const index4 = current.parent.children.indexOf(current); + path = `${separator}${index4}${path}`; + } + } + return path.slice(1); +}; + +class Reconciler { + #root = null; + #decodeEvent; + #dispatch; + #debug = false; + constructor(root2, decodeEvent, dispatch2, { debug = false } = {}) { + this.#root = root2; + this.#decodeEvent = decodeEvent; + this.#dispatch = dispatch2; + this.#debug = debug; + } + mount(vdom) { + insertMetadataChild(element_kind, null, this.#root, 0, null); + this.#insertChild(this.#root, null, this.#root[meta], 0, vdom); + } + push(patch, memos2 = null) { + this.#memos = memos2; + this.#stack.push({ node: this.#root[meta], patch }); + this.#reconcile(); + } + #memos; + #stack = []; + #reconcile() { + const stack = this.#stack; + while (stack.length) { + const { node, patch } = stack.pop(); + const { children: childNodes } = node; + const { changes, removed, children: childPatches } = patch; + iterate(changes, (change) => this.#patch(node, change)); + if (removed) { + this.#removeChildren(node, childNodes.length - removed, removed); + } + iterate(childPatches, (childPatch) => { + const child2 = childNodes[childPatch.index | 0]; + this.#stack.push({ node: child2, patch: childPatch }); + }); + } + } + #patch(node, change) { + switch (change.kind) { + case replace_text_kind: + this.#replaceText(node, change); + break; + case replace_inner_html_kind: + this.#replaceInnerHtml(node, change); + break; + case update_kind: + this.#update(node, change); + break; + case move_kind: + this.#move(node, change); + break; + case remove_kind: + this.#remove(node, change); + break; + case replace_kind: + this.#replace(node, change); + break; + case insert_kind: + this.#insert(node, change); + break; + } + } + #insert(parent, { children, before }) { + const fragment2 = createDocumentFragment(); + const beforeEl = this.#getReference(parent, before); + this.#insertChildren(fragment2, null, parent, before | 0, children); + insertBefore(parent.parentNode, fragment2, beforeEl); + } + #replace(parent, { index: index4, with: child2 }) { + this.#removeChildren(parent, index4 | 0, 1); + const beforeEl = this.#getReference(parent, index4); + this.#insertChild(parent.parentNode, beforeEl, parent, index4 | 0, child2); + } + #getReference(node, index4) { + index4 = index4 | 0; + const { children } = node; + const childCount = children.length; + if (index4 < childCount) + return children[index4].node; + if (node.endNode) + return node.endNode; + if (!node.isVirtual) + return null; + while (node.isVirtual && node.children.length) { + if (node.endNode) + return node.endNode.nextSibling; + node = node.children[node.children.length - 1]; + } + return node.node.nextSibling; + } + #move(parent, { key, before }) { + before = before | 0; + const { children, parentNode } = parent; + const beforeEl = children[before].node; + let prev = children[before]; + for (let i = before + 1;i < children.length; ++i) { + const next = children[i]; + children[i] = prev; + prev = next; + if (next.key === key) { + children[before] = next; + break; + } + } + this.#moveChild(parentNode, prev, beforeEl); + } + #moveChildren(domParent, children, beforeEl) { + for (let i = 0;i < children.length; ++i) { + this.#moveChild(domParent, children[i], beforeEl); + } + } + #moveChild(domParent, child2, beforeEl) { + moveBefore(domParent, child2.node, beforeEl); + if (child2.isVirtual) { + this.#moveChildren(domParent, child2.children, beforeEl); + } + if (child2.endNode) { + moveBefore(domParent, child2.endNode, beforeEl); + } + } + #remove(parent, { index: index4 }) { + this.#removeChildren(parent, index4, 1); + } + #removeChildren(parent, index4, count) { + const { children, parentNode } = parent; + const deleted = children.splice(index4, count); + for (let i = 0;i < deleted.length; ++i) { + const child2 = deleted[i]; + const { node, endNode, isVirtual, children: nestedChildren } = child2; + removeChild(parentNode, node); + if (endNode) { + removeChild(parentNode, endNode); + } + this.#removeDebouncers(child2); + if (isVirtual) { + deleted.push(...nestedChildren); + } + } + } + #removeDebouncers(node) { + const { debouncers, children } = node; + for (const { timeout } of debouncers.values()) { + if (timeout) { + clearTimeout(timeout); + } + } + debouncers.clear(); + iterate(children, (child2) => this.#removeDebouncers(child2)); + } + #update({ node, handlers, throttles, debouncers }, { added, removed }) { + iterate(removed, ({ name }) => { + if (handlers.delete(name)) { + removeEventListener(node, name, handleEvent); + this.#updateDebounceThrottle(throttles, name, 0); + this.#updateDebounceThrottle(debouncers, name, 0); + } else { + removeAttribute(node, name); + SYNCED_ATTRIBUTES[name]?.removed?.(node, name); + } + }); + iterate(added, (attribute3) => this.#createAttribute(node, attribute3)); + } + #replaceText({ node }, { content }) { + setData(node, content ?? ""); + } + #replaceInnerHtml({ node }, { inner_html }) { + setInnerHtml(node, inner_html ?? ""); + } + #insertChildren(domParent, beforeEl, metaParent, index4, children) { + iterate(children, (child2) => this.#insertChild(domParent, beforeEl, metaParent, index4++, child2)); + } + #insertChild(domParent, beforeEl, metaParent, index4, vnode) { + switch (vnode.kind) { + case element_kind: { + const node = this.#createElement(metaParent, index4, vnode); + this.#insertChildren(node, null, node[meta], 0, vnode.children); + insertBefore(domParent, node, beforeEl); + break; + } + case text_kind: { + const node = this.#createTextNode(metaParent, index4, vnode); + insertBefore(domParent, node, beforeEl); + break; + } + case fragment_kind: { + const marker = "lustre:fragment"; + const head = this.#createHead(marker, metaParent, index4, vnode); + insertBefore(domParent, head, beforeEl); + this.#insertChildren(domParent, beforeEl, head[meta], 0, vnode.children); + if (this.#debug) { + head[meta].endNode = createComment(` /${marker} `); + insertBefore(domParent, head[meta].endNode, beforeEl); + } + break; + } + case unsafe_inner_html_kind: { + const node = this.#createElement(metaParent, index4, vnode); + this.#replaceInnerHtml({ node }, vnode); + insertBefore(domParent, node, beforeEl); + break; + } + case map_kind: { + const head = this.#createHead("lustre:map", metaParent, index4, vnode); + insertBefore(domParent, head, beforeEl); + this.#insertChild(domParent, beforeEl, head[meta], 0, vnode.child); + break; + } + case memo_kind: { + const child2 = this.#memos?.get(vnode.view) ?? vnode.view(); + this.#insertChild(domParent, beforeEl, metaParent, index4, child2); + break; + } + } + } + #createElement(parent, index4, { kind, key, tag, namespace, attributes }) { + const node = createElementNS(namespace || NAMESPACE_HTML, tag); + insertMetadataChild(kind, parent, node, index4, key); + if (this.#debug && key) { + setAttribute(node, "data-lustre-key", key); + } + iterate(attributes, (attribute3) => this.#createAttribute(node, attribute3)); + return node; + } + #createTextNode(parent, index4, { kind, key, content }) { + const node = createTextNode(content ?? ""); + insertMetadataChild(kind, parent, node, index4, key); + return node; + } + #createHead(marker, parent, index4, { kind, key }) { + const node = this.#debug ? createComment(markerComment(marker, key)) : createTextNode(""); + insertMetadataChild(kind, parent, node, index4, key); + return node; + } + #createAttribute(node, attribute3) { + const { debouncers, handlers, throttles } = node[meta]; + const { + kind, + name, + value: value2, + prevent_default: prevent, + debounce: debounceDelay, + throttle: throttleDelay + } = attribute3; + switch (kind) { + case attribute_kind: { + const valueOrDefault = value2 ?? ""; + if (name === "virtual:defaultValue") { + node.defaultValue = valueOrDefault; + return; + } else if (name === "virtual:defaultChecked") { + node.defaultChecked = true; + return; + } else if (name === "virtual:defaultSelected") { + node.defaultSelected = true; + return; + } + if (valueOrDefault !== getAttribute(node, name)) { + setAttribute(node, name, valueOrDefault); + } + SYNCED_ATTRIBUTES[name]?.added?.(node, valueOrDefault); + break; + } + case property_kind: + node[name] = value2; + break; + case event_kind: { + if (handlers.has(name)) { + removeEventListener(node, name, handleEvent); + } + const passive = prevent.kind === never_kind; + addEventListener(node, name, handleEvent, { passive }); + this.#updateDebounceThrottle(throttles, name, throttleDelay); + this.#updateDebounceThrottle(debouncers, name, debounceDelay); + handlers.set(name, (event3) => this.#handleEvent(attribute3, event3)); + break; + } + } + } + #updateDebounceThrottle(map8, name, delay) { + const debounceOrThrottle = map8.get(name); + if (delay > 0) { + if (debounceOrThrottle) { + debounceOrThrottle.delay = delay; + } else { + map8.set(name, { delay }); + } + } else if (debounceOrThrottle) { + const { timeout } = debounceOrThrottle; + if (timeout) { + clearTimeout(timeout); + } + map8.delete(name); + } + } + #handleEvent(attribute3, event3) { + const { currentTarget, type } = event3; + const { debouncers, throttles } = currentTarget[meta]; + const path = getPath(currentTarget); + const { + prevent_default: prevent, + stop_propagation: stop, + include + } = attribute3; + if (prevent.kind === always_kind) + event3.preventDefault(); + if (stop.kind === always_kind) + event3.stopPropagation(); + if (type === "submit") { + event3.detail ??= {}; + event3.detail.formData = [ + ...new FormData(event3.target, event3.submitter).entries() + ]; + } + const data2 = this.#decodeEvent(event3, path, type, include); + const throttle = throttles.get(type); + if (throttle) { + const now = Date.now(); + const last = throttle.last || 0; + if (now > last + throttle.delay) { + throttle.last = now; + throttle.lastEvent = event3; + this.#dispatch(event3, data2); + } + } + const debounce = debouncers.get(type); + if (debounce) { + clearTimeout(debounce.timeout); + debounce.timeout = setTimeout(() => { + if (event3 === throttles.get(type)?.lastEvent) + return; + this.#dispatch(event3, data2); + }, debounce.delay); + } + if (!throttle && !debounce) { + this.#dispatch(event3, data2); + } + } +} +var markerComment = (marker, key) => { + if (key) { + return ` ${marker} key="${escape2(key)}" `; + } else { + return ` ${marker} `; + } +}; +var handleEvent = (event3) => { + const { currentTarget, type } = event3; + const handler = currentTarget[meta].handlers.get(type); + handler(event3); +}; +var syncedBooleanAttribute = (name) => { + return { + added(node) { + node[name] = true; + }, + removed(node) { + node[name] = false; + } + }; +}; +var syncedAttribute = (name) => { + return { + added(node, value2) { + node[name] = value2; + } + }; +}; +var SYNCED_ATTRIBUTES = { + checked: syncedBooleanAttribute("checked"), + selected: syncedBooleanAttribute("selected"), + value: syncedAttribute("value"), + autofocus: { + added(node) { + queueMicrotask(() => { + node.focus?.(); + }); + } + }, + autoplay: { + added(node) { + try { + node.play?.(); + } catch (e) { + console.error(e); + } + } + } +}; + +// build/dev/javascript/lustre/lustre/element/keyed.mjs +function do_extract_keyed_children(loop$key_children_pairs, loop$keyed_children, loop$children) { + while (true) { + let key_children_pairs = loop$key_children_pairs; + let keyed_children = loop$keyed_children; + let children = loop$children; + if (key_children_pairs instanceof Empty) { + return [keyed_children, reverse(children)]; + } else { + let rest = key_children_pairs.tail; + let key = key_children_pairs.head[0]; + let element$1 = key_children_pairs.head[1]; + let keyed_element = to_keyed(key, element$1); + let _block; + if (key === "") { + _block = keyed_children; + } else { + _block = insert2(keyed_children, key, keyed_element); + } + let keyed_children$1 = _block; + let children$1 = prepend(keyed_element, children); + loop$key_children_pairs = rest; + loop$keyed_children = keyed_children$1; + loop$children = children$1; + } + } +} +function extract_keyed_children(children) { + return do_extract_keyed_children(children, empty2(), empty_list); +} +function element3(tag, attributes, children) { + let $ = extract_keyed_children(children); + let keyed_children; + let children$1; + keyed_children = $[0]; + children$1 = $[1]; + return element("", "", tag, attributes, children$1, keyed_children, false, is_void_html_element(tag, "")); +} +function namespaced2(namespace, tag, attributes, children) { + let $ = extract_keyed_children(children); + let keyed_children; + let children$1; + keyed_children = $[0]; + children$1 = $[1]; + return element("", namespace, tag, attributes, children$1, keyed_children, false, is_void_html_element(tag, namespace)); +} +function fragment2(children) { + let $ = extract_keyed_children(children); + let keyed_children; + let children$1; + keyed_children = $[0]; + children$1 = $[1]; + return fragment("", children$1, keyed_children); +} + +// build/dev/javascript/lustre/lustre/vdom/virtualise.ffi.mjs +var virtualise = (root2) => { + const rootMeta = insertMetadataChild(element_kind, null, root2, 0, null); + for (let child2 = root2.firstChild;child2; child2 = child2.nextSibling) { + const result = virtualiseChild(rootMeta, root2, child2, 0); + if (result) + return result.vnode; + } + const placeholder2 = globalThis.document.createTextNode(""); + insertMetadataChild(text_kind, rootMeta, placeholder2, 0, null); + root2.insertBefore(placeholder2, root2.firstChild); + return none2(); +}; +var virtualiseChild = (meta2, domParent, child2, index4) => { + if (child2.nodeType === COMMENT_NODE) { + const data2 = child2.data.trim(); + if (data2.startsWith("lustre:fragment")) { + return virtualiseFragment(meta2, domParent, child2, index4); + } + if (data2.startsWith("lustre:map")) { + return virtualiseMap(meta2, domParent, child2, index4); + } + if (data2.startsWith("lustre:memo")) { + return virtualiseMemo(meta2, domParent, child2, index4); + } + return null; + } + if (child2.nodeType === ELEMENT_NODE) { + return virtualiseElement(meta2, child2, index4); + } + if (child2.nodeType === TEXT_NODE) { + return virtualiseText(meta2, child2, index4); + } + return null; +}; +var virtualiseElement = (metaParent, node, index4) => { + const key = node.getAttribute("data-lustre-key") ?? ""; + if (key) { + node.removeAttribute("data-lustre-key"); + } + const meta2 = insertMetadataChild(element_kind, metaParent, node, index4, key); + const tag = node.localName; + const namespace = node.namespaceURI; + const isHtmlElement = !namespace || namespace === NAMESPACE_HTML; + if (isHtmlElement && INPUT_ELEMENTS.includes(tag)) { + virtualiseInputEvents(tag, node); + } + const attributes = virtualiseAttributes(node); + const children = []; + for (let childNode = node.firstChild;childNode; ) { + const child2 = virtualiseChild(meta2, node, childNode, children.length); + if (child2) { + children.push([child2.key, child2.vnode]); + childNode = child2.next; + } else { + childNode = childNode.nextSibling; + } + } + const vnode = isHtmlElement ? element3(tag, attributes, toList3(children)) : namespaced2(namespace, tag, attributes, toList3(children)); + return childResult(key, vnode, node.nextSibling); +}; +var virtualiseText = (meta2, node, index4) => { + insertMetadataChild(text_kind, meta2, node, index4, null); + return childResult("", text2(node.data), node.nextSibling); +}; +var virtualiseFragment = (metaParent, domParent, node, index4) => { + const key = parseKey(node.data); + const meta2 = insertMetadataChild(fragment_kind, metaParent, node, index4, key); + const children = []; + node = node.nextSibling; + while (node && (node.nodeType !== COMMENT_NODE || node.data.trim() !== "/lustre:fragment")) { + const child2 = virtualiseChild(meta2, domParent, node, children.length); + if (child2) { + children.push([child2.key, child2.vnode]); + node = child2.next; + } else { + node = node.nextSibling; + } + } + meta2.endNode = node; + const vnode = fragment2(toList3(children)); + return childResult(key, vnode, node?.nextSibling); +}; +var virtualiseMap = (metaParent, domParent, node, index4) => { + const key = parseKey(node.data); + const meta2 = insertMetadataChild(map_kind, metaParent, node, index4, key); + const child2 = virtualiseNextChild(meta2, domParent, node, 0); + if (!child2) + return null; + const vnode = map7(child2.vnode, (x) => x); + return childResult(key, vnode, child2.next); +}; +var virtualiseMemo = (meta2, domParent, node, index4) => { + const key = parseKey(node.data); + const child2 = virtualiseNextChild(meta2, domParent, node, index4); + if (!child2) + return null; + domParent.removeChild(node); + const vnode = memo2(toList3([ref({})]), () => child2.vnode); + return childResult(key, vnode, child2.next); +}; +var virtualiseNextChild = (meta2, domParent, node, index4) => { + while (true) { + node = node.nextSibling; + if (!node) + return null; + const child2 = virtualiseChild(meta2, domParent, node, index4); + if (child2) + return child2; + } +}; +var childResult = (key, vnode, next) => { + return { key, vnode, next }; +}; +var virtualiseAttributes = (node) => { + const attributes = []; + for (let i = 0;i < node.attributes.length; i++) { + const attr = node.attributes[i]; + if (attr.name !== "xmlns") { + attributes.push(attribute2(attr.localName, attr.value)); + } + } + return toList3(attributes); +}; +var INPUT_ELEMENTS = ["input", "select", "textarea"]; +var virtualiseInputEvents = (tag, node) => { + const value2 = node.value; + const checked = node.checked; + if (tag === "input" && node.type === "checkbox" && !checked) + return; + if (tag === "input" && node.type === "radio" && !checked) + return; + if (node.type !== "checkbox" && node.type !== "radio" && !value2) + return; + queueMicrotask(() => { + node.value = value2; + node.checked = checked; + node.dispatchEvent(new Event("input", { bubbles: true })); + node.dispatchEvent(new Event("change", { bubbles: true })); + if (globalThis.document.activeElement !== node) { + node.dispatchEvent(new Event("blur", { bubbles: true })); + } + }); +}; +var parseKey = (data2) => { + const keyMatch = data2.match(/key="([^"]*)"/); + if (!keyMatch) + return ""; + return unescapeKey(keyMatch[1]); +}; +var unescapeKey = (key) => { + return key.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/&/g, "&").replace(/'/g, "'"); +}; +var toList3 = (arr) => arr.reduceRight((xs, x) => List$NonEmpty(x, xs), empty_list); + +// build/dev/javascript/lustre/lustre/runtime/client/runtime.ffi.mjs +var is_browser = () => !!globalThis.document; +class Runtime { + constructor(root2, [model, effects], view, update2, options) { + this.root = root2; + this.#model = model; + this.#view = view; + this.#update = update2; + this.root.addEventListener("context-request", (event3) => { + if (!(event3.context && event3.callback)) + return; + if (!this.#contexts.has(event3.context)) + return; + event3.stopImmediatePropagation(); + const context = this.#contexts.get(event3.context); + if (event3.subscribe) { + const unsubscribe = () => { + context.subscribers = context.subscribers.filter((subscriber) => subscriber !== event3.callback); + }; + context.subscribers.push([event3.callback, unsubscribe]); + event3.callback(context.value, unsubscribe); + } else { + event3.callback(context.value); + } + }); + const decodeEvent = (event3, path, name) => decode2(this.#cache, path, name, event3); + const dispatch2 = (event3, data2) => { + const [cache, result] = dispatch(this.#cache, data2); + this.#cache = cache; + if (Result$isOk(result)) { + const handler = Result$Ok$0(result); + if (handler.stop_propagation) + event3.stopPropagation(); + if (handler.prevent_default) + event3.preventDefault(); + this.dispatch(handler.message, false); + } + }; + this.#reconciler = new Reconciler(this.root, decodeEvent, dispatch2, options); + this.#vdom = virtualise(this.root); + this.#cache = new$4(); + this.#handleEffects(effects); + this.#render(); + } + root = null; + dispatch(msg, shouldFlush = false) { + if (this.#shouldQueue) { + this.#queue.push(msg); + } else { + const [model, effects] = this.#update(this.#model, msg); + this.#model = model; + this.#tick(effects, shouldFlush); + } + } + emit(event3, data2) { + const target = this.root.host ?? this.root; + target.dispatchEvent(new CustomEvent(event3, { + detail: data2, + bubbles: true, + composed: true + })); + } + provide(key, value2) { + if (!this.#contexts.has(key)) { + this.#contexts.set(key, { value: value2, subscribers: [] }); + } else { + const context = this.#contexts.get(key); + if (isEqual2(context.value, value2)) { + return; + } + context.value = value2; + for (let i = context.subscribers.length - 1;i >= 0; i--) { + const [subscriber, unsubscribe] = context.subscribers[i]; + if (!subscriber) { + context.subscribers.splice(i, 1); + continue; + } + subscriber(value2, unsubscribe); + } + } + } + #model; + #view; + #update; + #vdom; + #cache; + #reconciler; + #contexts = new Map; + #shouldQueue = false; + #queue = []; + #beforePaint = empty_list; + #afterPaint = empty_list; + #renderTimer = null; + #actions = { + dispatch: (msg) => this.dispatch(msg), + emit: (event3, data2) => this.emit(event3, data2), + select: () => {}, + root: () => this.root, + provide: (key, value2) => this.provide(key, value2) + }; + #tick(effects, shouldFlush = false) { + this.#handleEffects(effects); + if (!this.#renderTimer) { + if (shouldFlush) { + this.#renderTimer = "sync"; + queueMicrotask(() => this.#render()); + } else { + this.#renderTimer = window.requestAnimationFrame(() => this.#render()); + } + } + } + #handleEffects(effects) { + this.#shouldQueue = true; + while (true) { + iterate(effects.synchronous, (effect) => effect(this.#actions)); + this.#beforePaint = append4(this.#beforePaint, effects.before_paint); + this.#afterPaint = append4(this.#afterPaint, effects.after_paint); + if (!this.#queue.length) + break; + const msg = this.#queue.shift(); + [this.#model, effects] = this.#update(this.#model, msg); + } + this.#shouldQueue = false; + } + #render() { + this.#renderTimer = null; + const next = this.#view(this.#model); + const { patch, cache } = diff(this.#cache, this.#vdom, next); + this.#cache = cache; + this.#vdom = next; + this.#reconciler.push(patch, memos(cache)); + if (List$isNonEmpty(this.#beforePaint)) { + const effects = makeEffect(this.#beforePaint); + this.#beforePaint = empty_list; + queueMicrotask(() => { + this.#tick(effects, true); + }); + } + if (List$isNonEmpty(this.#afterPaint)) { + const effects = makeEffect(this.#afterPaint); + this.#afterPaint = empty_list; + window.requestAnimationFrame(() => this.#tick(effects, true)); + } + } +} +function makeEffect(synchronous) { + return { + synchronous, + after_paint: empty_list, + before_paint: empty_list + }; +} +var copiedStyleSheets = new WeakMap; + +// build/dev/javascript/lustre/lustre/runtime/client/spa.ffi.mjs +class Spa { + #runtime; + constructor(root2, [init, effects], update2, view) { + this.#runtime = new Runtime(root2, [init, effects], view, update2); + } + send(message) { + if (Message$isEffectDispatchedMessage(message)) { + this.dispatch(message.message, false); + } else if (Message$isEffectEmitEvent(message)) { + this.emit(message.name, message.data); + } else if (Message$isSystemRequestedShutdown(message)) {} + } + dispatch(msg) { + this.#runtime.dispatch(msg); + } + emit(event3, data2) { + this.#runtime.emit(event3, data2); + } +} +var start = ({ init, update: update2, view }, selector, flags) => { + if (!is_browser()) + return Result$Error(Error$NotABrowser()); + const root2 = selector instanceof HTMLElement ? selector : globalThis.document.querySelector(selector); + if (!root2) + return Result$Error(Error$ElementNotFound(selector)); + return Result$Ok(new Spa(root2, init(flags), update2, view)); +}; + +// build/dev/javascript/lustre/lustre/runtime/server/runtime.ffi.mjs +class Runtime2 { + #model; + #update; + #view; + #config; + #vdom; + #cache; + #providers = make(); + #callbacks = /* @__PURE__ */ new Set; + constructor(_, init, update2, view, config2, start_arguments) { + const [model, effects] = init(start_arguments); + this.#model = model; + this.#update = update2; + this.#view = view; + this.#config = config2; + this.#vdom = this.#view(this.#model); + this.#cache = from_node(this.#vdom); + this.#handle_effect(effects); + } + send(msg) { + if (Message$isClientDispatchedMessage(msg)) { + const { message } = msg; + const next = this.#handle_client_message(message); + const diff2 = diff(this.#cache, this.#vdom, next); + this.#vdom = next; + this.#cache = diff2.cache; + this.broadcast(reconcile(diff2.patch, memos(diff2.cache))); + } else if (Message$isClientRegisteredCallback(msg)) { + const { callback } = msg; + this.#callbacks.add(callback); + callback(mount(this.#config.open_shadow_root, this.#config.adopt_styles, keys(this.#config.attributes), keys(this.#config.properties), keys(this.#config.contexts), this.#providers, this.#vdom, memos(this.#cache))); + if (Option$isSome(config.on_connect)) { + this.#dispatch(Option$Some$0(config.on_connect)); + } + } else if (Message$isClientDeregisteredCallback(msg)) { + const { callback } = msg; + this.#callbacks.delete(callback); + if (Option$isSome(config.on_disconnect)) { + this.#dispatch(Option$Some$0(config.on_disconnect)); + } + } else if (Message$isEffectDispatchedMessage(msg)) { + const { message } = msg; + const [model, effect] = this.#update(this.#model, message); + const next = this.#view(model); + const diff2 = diff(this.#cache, this.#vdom, next); + this.#handle_effect(effect); + this.#model = model; + this.#vdom = next; + this.#cache = diff2.cache; + this.broadcast(reconcile(diff2.patch, memos(diff2.cache))); + } else if (Message$isEffectEmitEvent(msg)) { + const { name, data: data2 } = msg; + this.broadcast(emit(name, data2)); + } else if (Message$isEffectProvidedValue(msg)) { + const { key, value: value2 } = msg; + const existing = get(this.#providers, key); + if (Result$isOk(existing) && isEqual2(Result$Ok$0(existing), value2)) { + return; + } + this.#providers = insert(this.#providers, key, value2); + this.broadcast(provide(key, value2)); + } else if (Message$isSystemRequestedShutdown(msg)) { + this.#model = null; + this.#update = null; + this.#view = null; + this.#config = null; + this.#vdom = null; + this.#cache = null; + this.#providers = null; + this.#callbacks.clear(); + } + } + broadcast(msg) { + for (const callback of this.#callbacks) { + callback(msg); + } + } + #handle_client_message(msg) { + if (ServerMessage$isBatch(msg)) { + const { messages } = msg; + let model = this.#model; + let effect = none(); + for (let list4 = messages;List$NonEmpty$rest(list4); list4 = List$NonEmpty$rest(list4)) { + const result = this.#handle_client_message(List$NonEmpty$first(list4)); + if (Result$isOk(result)) { + model = Result$Ok$0(result)[0]; + effect = batch(toList2([effect, Result$Ok$0(result)[1]])); + break; + } + } + this.#handle_effect(effect); + this.#model = model; + return this.#view(model); + } else if (ServerMessage$isAttributeChanged(msg)) { + const { name, value: value2 } = msg; + const result = this.#handle_attribute_change(name, value2); + if (!Result$isOk(result)) { + return this.#vdom; + } + return this.#dispatch(Result$Ok$0(result)); + } else if (ServerMessage$isPropertyChanged(msg)) { + const { name, value: value2 } = msg; + const result = this.#handle_properties_change(name, value2); + if (!Result$isOk(result)) { + return this.#vdom; + } + return this.#dispatch(Result$Ok$0(result)); + } else if (ServerMessage$isEventFired(msg)) { + const { path, name, event: event3 } = msg; + const [cache, result] = handle(this.#cache, path, name, event3); + this.#cache = cache; + if (!Result$isOk(result)) { + return this.#vdom; + } + const { message } = Result$Ok$0(result); + return this.#dispatch(message); + } else if (ServerMessage$isContextProvided(msg)) { + const { key, value: value2 } = msg; + let result = get(this.#config.contexts, key); + if (!Result$isOk(result)) { + return this.#vdom; + } + result = run(value2, Result$Ok$0(result)); + if (!Result$isOk(result)) { + return this.#vdom; + } + return this.#dispatch(Result$Ok$0(result)); + } + } + #dispatch(msg) { + const [model, effects] = this.#update(this.#model, msg); + this.#handle_effect(effects); + this.#model = model; + return this.#view(this.#model); + } + #handle_attribute_change(name, value2) { + const result = get(this.#config.attributes, name); + if (!Result$isOk(result)) { + return result; + } + return Result$Ok$0(result)(value2); + } + #handle_properties_change(name, value2) { + const result = get(this.#config.properties, name); + if (!Result$isOk(result)) { + return result; + } + return Result$Ok$0(result)(value2); + } + #handle_effect(effect) { + const dispatch2 = (message) => this.send(Message$EffectDispatchedMessage(message)); + const emit2 = (name, data2) => this.send(Message$EffectEmitEvent(name, data2)); + const select = () => { + return; + }; + const internals = () => { + return; + }; + const provide2 = (key, value2) => this.send(Message$EffectProvidedValue(key, value2)); + globalThis.queueMicrotask(() => { + perform(effect, dispatch2, emit2, select, internals, provide2); + }); + } +} + +// build/dev/javascript/lustre/lustre.mjs +class ElementNotFound extends CustomType { + constructor(selector) { + super(); + this.selector = selector; + } +} +var Error$ElementNotFound = (selector) => new ElementNotFound(selector); +class NotABrowser extends CustomType { +} +var Error$NotABrowser = () => new NotABrowser; +function application(init, update2, view) { + return new App(new None, init, update2, view, default_config); +} +function start4(app, selector, arguments$) { + return guard(!is_browser(), new Error(new NotABrowser), () => { + return start(app, selector, arguments$); + }); +} + +// build/dev/javascript/lustre/lustre/event.mjs +function on(name, handler) { + return event(name, map3(handler, (msg) => { + return new Handler(false, false, msg); + }), empty_list, never, never, 0, 0); +} +function on_click(msg) { + return on("click", success(msg)); +} +function on_input(msg) { + return on("input", subfield(toList(["target", "value"]), string2, (value2) => { + return success(msg(value2)); + })); +} + +// build/dev/javascript/gleam_stdlib/gleam/uri.mjs +class Uri extends CustomType { + constructor(scheme, userinfo, host, port, path, query, fragment3) { + super(); + this.scheme = scheme; + this.userinfo = userinfo; + this.host = host; + this.port = port; + this.path = path; + this.query = query; + this.fragment = fragment3; + } +} +var empty3 = /* @__PURE__ */ new Uri(/* @__PURE__ */ new None, /* @__PURE__ */ new None, /* @__PURE__ */ new None, /* @__PURE__ */ new None, "", /* @__PURE__ */ new None, /* @__PURE__ */ new None); +function is_valid_host_within_brackets_char(char) { + return 48 >= char && char <= 57 || 65 >= char && char <= 90 || 97 >= char && char <= 122 || char === 58 || char === 46; +} +function parse_fragment(rest, pieces) { + return new Ok(new Uri(pieces.scheme, pieces.userinfo, pieces.host, pieces.port, pieces.path, pieces.query, new Some(rest))); +} +function parse_query_with_question_mark_loop(loop$original, loop$uri_string, loop$pieces, loop$size) { + while (true) { + let original = loop$original; + let uri_string = loop$uri_string; + let pieces = loop$pieces; + let size3 = loop$size; + if (uri_string.startsWith("#")) { + if (size3 === 0) { + let rest = uri_string.slice(1); + return parse_fragment(rest, pieces); + } else { + let rest = uri_string.slice(1); + let query = string_codeunit_slice(original, 0, size3); + let pieces$1 = new Uri(pieces.scheme, pieces.userinfo, pieces.host, pieces.port, pieces.path, new Some(query), pieces.fragment); + return parse_fragment(rest, pieces$1); + } + } else if (uri_string === "") { + return new Ok(new Uri(pieces.scheme, pieces.userinfo, pieces.host, pieces.port, pieces.path, new Some(original), pieces.fragment)); + } else { + let $ = pop_codeunit(uri_string); + let rest; + rest = $[1]; + loop$original = original; + loop$uri_string = rest; + loop$pieces = pieces; + loop$size = size3 + 1; + } + } +} +function parse_query_with_question_mark(uri_string, pieces) { + return parse_query_with_question_mark_loop(uri_string, uri_string, pieces, 0); +} +function parse_path_loop(loop$original, loop$uri_string, loop$pieces, loop$size) { + while (true) { + let original = loop$original; + let uri_string = loop$uri_string; + let pieces = loop$pieces; + let size3 = loop$size; + if (uri_string.startsWith("?")) { + let rest = uri_string.slice(1); + let path = string_codeunit_slice(original, 0, size3); + let pieces$1 = new Uri(pieces.scheme, pieces.userinfo, pieces.host, pieces.port, path, pieces.query, pieces.fragment); + return parse_query_with_question_mark(rest, pieces$1); + } else if (uri_string.startsWith("#")) { + let rest = uri_string.slice(1); + let path = string_codeunit_slice(original, 0, size3); + let pieces$1 = new Uri(pieces.scheme, pieces.userinfo, pieces.host, pieces.port, path, pieces.query, pieces.fragment); + return parse_fragment(rest, pieces$1); + } else if (uri_string === "") { + return new Ok(new Uri(pieces.scheme, pieces.userinfo, pieces.host, pieces.port, original, pieces.query, pieces.fragment)); + } else { + let $ = pop_codeunit(uri_string); + let rest; + rest = $[1]; + loop$original = original; + loop$uri_string = rest; + loop$pieces = pieces; + loop$size = size3 + 1; + } + } +} +function parse_path(uri_string, pieces) { + return parse_path_loop(uri_string, uri_string, pieces, 0); +} +function parse_port_loop(loop$uri_string, loop$pieces, loop$port) { + while (true) { + let uri_string = loop$uri_string; + let pieces = loop$pieces; + let port = loop$port; + if (uri_string.startsWith("0")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10; + } else if (uri_string.startsWith("1")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10 + 1; + } else if (uri_string.startsWith("2")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10 + 2; + } else if (uri_string.startsWith("3")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10 + 3; + } else if (uri_string.startsWith("4")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10 + 4; + } else if (uri_string.startsWith("5")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10 + 5; + } else if (uri_string.startsWith("6")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10 + 6; + } else if (uri_string.startsWith("7")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10 + 7; + } else if (uri_string.startsWith("8")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10 + 8; + } else if (uri_string.startsWith("9")) { + let rest = uri_string.slice(1); + loop$uri_string = rest; + loop$pieces = pieces; + loop$port = port * 10 + 9; + } else if (uri_string.startsWith("?")) { + let rest = uri_string.slice(1); + let pieces$1 = new Uri(pieces.scheme, pieces.userinfo, pieces.host, new Some(port), pieces.path, pieces.query, pieces.fragment); + return parse_query_with_question_mark(rest, pieces$1); + } else if (uri_string.startsWith("#")) { + let rest = uri_string.slice(1); + let pieces$1 = new Uri(pieces.scheme, pieces.userinfo, pieces.host, new Some(port), pieces.path, pieces.query, pieces.fragment); + return parse_fragment(rest, pieces$1); + } else if (uri_string.startsWith("/")) { + let pieces$1 = new Uri(pieces.scheme, pieces.userinfo, pieces.host, new Some(port), pieces.path, pieces.query, pieces.fragment); + return parse_path(uri_string, pieces$1); + } else if (uri_string === "") { + return new Ok(new Uri(pieces.scheme, pieces.userinfo, pieces.host, new Some(port), pieces.path, pieces.query, pieces.fragment)); + } else { + return new Error(undefined); + } + } +} +function parse_port(uri_string, pieces) { + if (uri_string.startsWith(":0")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 0); + } else if (uri_string.startsWith(":1")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 1); + } else if (uri_string.startsWith(":2")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 2); + } else if (uri_string.startsWith(":3")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 3); + } else if (uri_string.startsWith(":4")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 4); + } else if (uri_string.startsWith(":5")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 5); + } else if (uri_string.startsWith(":6")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 6); + } else if (uri_string.startsWith(":7")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 7); + } else if (uri_string.startsWith(":8")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 8); + } else if (uri_string.startsWith(":9")) { + let rest = uri_string.slice(2); + return parse_port_loop(rest, pieces, 9); + } else if (uri_string === ":") { + return new Ok(pieces); + } else if (uri_string === "") { + return new Ok(pieces); + } else if (uri_string.startsWith("?")) { + let rest = uri_string.slice(1); + return parse_query_with_question_mark(rest, pieces); + } else if (uri_string.startsWith(":?")) { + let rest = uri_string.slice(2); + return parse_query_with_question_mark(rest, pieces); + } else if (uri_string.startsWith("#")) { + let rest = uri_string.slice(1); + return parse_fragment(rest, pieces); + } else if (uri_string.startsWith(":#")) { + let rest = uri_string.slice(2); + return parse_fragment(rest, pieces); + } else if (uri_string.startsWith("/")) { + return parse_path(uri_string, pieces); + } else if (uri_string.startsWith(":")) { + let rest = uri_string.slice(1); + if (rest.startsWith("/")) { + return parse_path(rest, pieces); + } else { + return new Error(undefined); + } + } else { + return new Error(undefined); + } +} +function parse_host_outside_of_brackets_loop(loop$original, loop$uri_string, loop$pieces, loop$size) { + while (true) { + let original = loop$original; + let uri_string = loop$uri_string; + let pieces = loop$pieces; + let size3 = loop$size; + if (uri_string === "") { + return new Ok(new Uri(pieces.scheme, pieces.userinfo, new Some(original), pieces.port, pieces.path, pieces.query, pieces.fragment)); + } else if (uri_string.startsWith(":")) { + let host = string_codeunit_slice(original, 0, size3); + let pieces$1 = new Uri(pieces.scheme, pieces.userinfo, new Some(host), pieces.port, pieces.path, pieces.query, pieces.fragment); + return parse_port(uri_string, pieces$1); + } else if (uri_string.startsWith("/")) { + let host = string_codeunit_slice(original, 0, size3); + let pieces$1 = new Uri(pieces.scheme, pieces.userinfo, new Some(host), pieces.port, pieces.path, pieces.query, pieces.fragment); + return parse_path(uri_string, pieces$1); + } else if (uri_string.startsWith("?")) { + let rest = uri_string.slice(1); + let host = string_codeunit_slice(original, 0, size3); + let pieces$1 = new Uri(pieces.scheme, pieces.userinfo, new Some(host), pieces.port, pieces.path, pieces.query, pieces.fragment); + return parse_query_with_question_mark(rest, pieces$1); + } else if (uri_string.startsWith("#")) { + let rest = uri_string.slice(1); + let host = string_codeunit_slice(original, 0, size3); + let pieces$1 = new Uri(pieces.scheme, pieces.userinfo, new Some(host), pieces.port, pieces.path, pieces.query, pieces.fragment); + return parse_fragment(rest, pieces$1); + } else { + let $ = pop_codeunit(uri_string); + let rest; + rest = $[1]; + loop$original = original; + loop$uri_string = rest; + loop$pieces = pieces; + loop$size = size3 + 1; + } + } +} +function parse_host_within_brackets_loop(loop$original, loop$uri_string, loop$pieces, loop$size) { + while (true) { + let original = loop$original; + let uri_string = loop$uri_string; + let pieces = loop$pieces; + let size3 = loop$size; + if (uri_string === "") { + return new Ok(new Uri(pieces.scheme, pieces.userinfo, new Some(uri_string), pieces.port, pieces.path, pieces.query, pieces.fragment)); + } else if (uri_string.startsWith("]")) { + if (size3 === 0) { + let rest = uri_string.slice(1); + return parse_port(rest, pieces); + } else { + let rest = uri_string.slice(1); + let host = string_codeunit_slice(original, 0, size3 + 1); + let pieces$1 = new Uri(pieces.scheme, pieces.userinfo, new Some(host), pieces.port, pieces.path, pieces.query, pieces.fragment); + return parse_port(rest, pieces$1); + } + } else if (uri_string.startsWith("/")) { + if (size3 === 0) { + return parse_path(uri_string, pieces); + } else { + let host = string_codeunit_slice(original, 0, size3); + let pieces$1 = new Uri(pieces.scheme, pieces.userinfo, new Some(host), pieces.port, pieces.path, pieces.query, pieces.fragment); + return parse_path(uri_string, pieces$1); + } + } else if (uri_string.startsWith("?")) { + if (size3 === 0) { + let rest = uri_string.slice(1); + return parse_query_with_question_mark(rest, pieces); + } else { + let rest = uri_string.slice(1); + let host = string_codeunit_slice(original, 0, size3); + let pieces$1 = new Uri(pieces.scheme, pieces.userinfo, new Some(host), pieces.port, pieces.path, pieces.query, pieces.fragment); + return parse_query_with_question_mark(rest, pieces$1); + } + } else if (uri_string.startsWith("#")) { + if (size3 === 0) { + let rest = uri_string.slice(1); + return parse_fragment(rest, pieces); + } else { + let rest = uri_string.slice(1); + let host = string_codeunit_slice(original, 0, size3); + let pieces$1 = new Uri(pieces.scheme, pieces.userinfo, new Some(host), pieces.port, pieces.path, pieces.query, pieces.fragment); + return parse_fragment(rest, pieces$1); + } + } else { + let $ = pop_codeunit(uri_string); + let char; + let rest; + char = $[0]; + rest = $[1]; + let $1 = is_valid_host_within_brackets_char(char); + if ($1) { + loop$original = original; + loop$uri_string = rest; + loop$pieces = pieces; + loop$size = size3 + 1; + } else { + return parse_host_outside_of_brackets_loop(original, original, pieces, 0); + } + } + } +} +function parse_host_within_brackets(uri_string, pieces) { + return parse_host_within_brackets_loop(uri_string, uri_string, pieces, 0); +} +function parse_host_outside_of_brackets(uri_string, pieces) { + return parse_host_outside_of_brackets_loop(uri_string, uri_string, pieces, 0); +} +function parse_host(uri_string, pieces) { + if (uri_string.startsWith("[")) { + return parse_host_within_brackets(uri_string, pieces); + } else if (uri_string.startsWith(":")) { + let pieces$1 = new Uri(pieces.scheme, pieces.userinfo, new Some(""), pieces.port, pieces.path, pieces.query, pieces.fragment); + return parse_port(uri_string, pieces$1); + } else if (uri_string === "") { + return new Ok(new Uri(pieces.scheme, pieces.userinfo, new Some(""), pieces.port, pieces.path, pieces.query, pieces.fragment)); + } else { + return parse_host_outside_of_brackets(uri_string, pieces); + } +} +function parse_userinfo_loop(loop$original, loop$uri_string, loop$pieces, loop$size) { + while (true) { + let original = loop$original; + let uri_string = loop$uri_string; + let pieces = loop$pieces; + let size3 = loop$size; + if (uri_string.startsWith("@")) { + if (size3 === 0) { + let rest = uri_string.slice(1); + return parse_host(rest, pieces); + } else { + let rest = uri_string.slice(1); + let userinfo = string_codeunit_slice(original, 0, size3); + let pieces$1 = new Uri(pieces.scheme, new Some(userinfo), pieces.host, pieces.port, pieces.path, pieces.query, pieces.fragment); + return parse_host(rest, pieces$1); + } + } else if (uri_string === "") { + return parse_host(original, pieces); + } else if (uri_string.startsWith("/")) { + return parse_host(original, pieces); + } else if (uri_string.startsWith("?")) { + return parse_host(original, pieces); + } else if (uri_string.startsWith("#")) { + return parse_host(original, pieces); + } else { + let $ = pop_codeunit(uri_string); + let rest; + rest = $[1]; + loop$original = original; + loop$uri_string = rest; + loop$pieces = pieces; + loop$size = size3 + 1; + } + } +} +function parse_authority_pieces(string5, pieces) { + return parse_userinfo_loop(string5, string5, pieces, 0); +} +function parse_authority_with_slashes(uri_string, pieces) { + if (uri_string === "//") { + return new Ok(new Uri(pieces.scheme, pieces.userinfo, new Some(""), pieces.port, pieces.path, pieces.query, pieces.fragment)); + } else if (uri_string.startsWith("//")) { + let rest = uri_string.slice(2); + return parse_authority_pieces(rest, pieces); + } else { + return parse_path(uri_string, pieces); + } +} +function parse_scheme_loop(loop$original, loop$uri_string, loop$pieces, loop$size) { + while (true) { + let original = loop$original; + let uri_string = loop$uri_string; + let pieces = loop$pieces; + let size3 = loop$size; + if (uri_string.startsWith("/")) { + if (size3 === 0) { + return parse_authority_with_slashes(uri_string, pieces); + } else { + let scheme = string_codeunit_slice(original, 0, size3); + let pieces$1 = new Uri(new Some(lowercase(scheme)), pieces.userinfo, pieces.host, pieces.port, pieces.path, pieces.query, pieces.fragment); + return parse_authority_with_slashes(uri_string, pieces$1); + } + } else if (uri_string.startsWith("?")) { + if (size3 === 0) { + let rest = uri_string.slice(1); + return parse_query_with_question_mark(rest, pieces); + } else { + let rest = uri_string.slice(1); + let scheme = string_codeunit_slice(original, 0, size3); + let pieces$1 = new Uri(new Some(lowercase(scheme)), pieces.userinfo, pieces.host, pieces.port, pieces.path, pieces.query, pieces.fragment); + return parse_query_with_question_mark(rest, pieces$1); + } + } else if (uri_string.startsWith("#")) { + if (size3 === 0) { + let rest = uri_string.slice(1); + return parse_fragment(rest, pieces); + } else { + let rest = uri_string.slice(1); + let scheme = string_codeunit_slice(original, 0, size3); + let pieces$1 = new Uri(new Some(lowercase(scheme)), pieces.userinfo, pieces.host, pieces.port, pieces.path, pieces.query, pieces.fragment); + return parse_fragment(rest, pieces$1); + } + } else if (uri_string.startsWith(":")) { + if (size3 === 0) { + return new Error(undefined); + } else { + let rest = uri_string.slice(1); + let scheme = string_codeunit_slice(original, 0, size3); + let pieces$1 = new Uri(new Some(lowercase(scheme)), pieces.userinfo, pieces.host, pieces.port, pieces.path, pieces.query, pieces.fragment); + return parse_authority_with_slashes(rest, pieces$1); + } + } else if (uri_string === "") { + return new Ok(new Uri(pieces.scheme, pieces.userinfo, pieces.host, pieces.port, original, pieces.query, pieces.fragment)); + } else { + let $ = pop_codeunit(uri_string); + let rest; + rest = $[1]; + loop$original = original; + loop$uri_string = rest; + loop$pieces = pieces; + loop$size = size3 + 1; + } + } +} +function to_string5(uri) { + let _block; + let $ = uri.fragment; + if ($ instanceof Some) { + let fragment3 = $[0]; + _block = toList(["#", fragment3]); + } else { + _block = toList([]); + } + let parts = _block; + let _block$1; + let $1 = uri.query; + if ($1 instanceof Some) { + let query = $1[0]; + _block$1 = prepend("?", prepend(query, parts)); + } else { + _block$1 = parts; + } + let parts$1 = _block$1; + let parts$2 = prepend(uri.path, parts$1); + let _block$2; + let $2 = uri.host; + let $3 = starts_with(uri.path, "/"); + if ($2 instanceof Some && !$3) { + let host = $2[0]; + if (host !== "") { + _block$2 = prepend("/", parts$2); + } else { + _block$2 = parts$2; + } + } else { + _block$2 = parts$2; + } + let parts$3 = _block$2; + let _block$3; + let $4 = uri.host; + let $5 = uri.port; + if ($4 instanceof Some && $5 instanceof Some) { + let port = $5[0]; + _block$3 = prepend(":", prepend(to_string(port), parts$3)); + } else { + _block$3 = parts$3; + } + let parts$4 = _block$3; + let _block$4; + let $6 = uri.scheme; + let $7 = uri.userinfo; + let $8 = uri.host; + if ($6 instanceof Some) { + if ($7 instanceof Some) { + if ($8 instanceof Some) { + let s = $6[0]; + let u = $7[0]; + let h = $8[0]; + _block$4 = prepend(s, prepend("://", prepend(u, prepend("@", prepend(h, parts$4))))); + } else { + let s = $6[0]; + _block$4 = prepend(s, prepend(":", parts$4)); + } + } else if ($8 instanceof Some) { + let s = $6[0]; + let h = $8[0]; + _block$4 = prepend(s, prepend("://", prepend(h, parts$4))); + } else { + let s = $6[0]; + _block$4 = prepend(s, prepend(":", parts$4)); + } + } else if ($7 instanceof None && $8 instanceof Some) { + let h = $8[0]; + _block$4 = prepend("//", prepend(h, parts$4)); + } else { + _block$4 = parts$4; + } + let parts$5 = _block$4; + return concat2(parts$5); +} +function parse2(uri_string) { + return parse_scheme_loop(uri_string, uri_string, empty3, 0); +} + +// build/dev/javascript/gleam_http/gleam/http/request.mjs +class Request extends CustomType { + constructor(method, headers, body, scheme, host, port, path, query) { + super(); + this.method = method; + this.headers = headers; + this.body = body; + this.scheme = scheme; + this.host = host; + this.port = port; + this.path = path; + this.query = query; + } +} +function to_uri(request) { + return new Uri(new Some(scheme_to_string(request.scheme)), new None, new Some(request.host), request.port, request.path, request.query, new None); +} +function from_uri(uri) { + return try$((() => { + let _pipe = uri.scheme; + let _pipe$1 = unwrap(_pipe, ""); + return scheme_from_string(_pipe$1); + })(), (scheme) => { + return try$((() => { + let _pipe = uri.host; + return to_result(_pipe, undefined); + })(), (host) => { + let req = new Request(new Get, toList([]), "", scheme, host, uri.port, uri.path, uri.query); + return new Ok(req); + }); + }); +} +function set_header2(request, key, value2) { + let headers = key_set(request.headers, lowercase(key), value2); + return new Request(request.method, headers, request.body, request.scheme, request.host, request.port, request.path, request.query); +} +function set_body(req, body) { + return new Request(req.method, req.headers, body, req.scheme, req.host, req.port, req.path, req.query); +} +function set_method(req, method) { + return new Request(method, req.headers, req.body, req.scheme, req.host, req.port, req.path, req.query); +} +// build/dev/javascript/gleam_javascript/gleam_javascript_ffi.mjs +class PromiseLayer { + constructor(promise) { + this.promise = promise; + } + static wrap(value2) { + return value2 instanceof Promise ? new PromiseLayer(value2) : value2; + } + static unwrap(value2) { + return value2 instanceof PromiseLayer ? value2.promise : value2; + } +} +function resolve(value2) { + return Promise.resolve(PromiseLayer.wrap(value2)); +} +function then_await(promise, fn) { + return promise.then((value2) => fn(PromiseLayer.unwrap(value2))); +} +function map_promise(promise, fn) { + return promise.then((value2) => PromiseLayer.wrap(fn(PromiseLayer.unwrap(value2)))); +} + +// build/dev/javascript/gleam_javascript/gleam/javascript/promise.mjs +function tap(promise, callback) { + let _pipe = promise; + return map_promise(_pipe, (a) => { + callback(a); + return a; + }); +} +function try_await(promise, callback) { + let _pipe = promise; + return then_await(_pipe, (result) => { + if (result instanceof Ok) { + let a = result[0]; + return callback(a); + } else { + let e = result[0]; + return resolve(new Error(e)); + } + }); +} +// build/dev/javascript/gleam_fetch/gleam_fetch_ffi.mjs +async function raw_send(request) { + try { + return new Ok(await fetch(request)); + } catch (error) { + return new Error(new NetworkError(error.toString())); + } +} +function from_fetch_response(response) { + return new Response(response.status, List.fromArray([...response.headers]), response); +} +function request_common(request) { + let url = to_string5(to_uri(request)); + let method = method_to_string(request.method).toUpperCase(); + let options = { + headers: make_headers(request.headers), + method + }; + return [url, options]; +} +function to_fetch_request(request) { + let [url, options] = request_common(request); + if (options.method !== "GET" && options.method !== "HEAD") + options.body = request.body; + return new globalThis.Request(url, options); +} +function make_headers(headersList) { + let headers = new globalThis.Headers; + for (let [k, v] of headersList) + headers.append(k.toLowerCase(), v); + return headers; +} +async function read_text_body(response) { + let body; + try { + body = await response.body.text(); + } catch (error) { + return new Error(new UnableToReadBody); + } + return new Ok(response.withFields({ body })); +} + +// build/dev/javascript/gleam_fetch/gleam/fetch.mjs +class NetworkError extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +} +class UnableToReadBody extends CustomType { +} +function send2(request) { + let _pipe = request; + let _pipe$1 = to_fetch_request(_pipe); + let _pipe$2 = raw_send(_pipe$1); + return try_await(_pipe$2, (resp) => { + return resolve(new Ok(from_fetch_response(resp))); + }); +} +// build/dev/javascript/rsvp/rsvp.ffi.mjs +var from_relative_url = (url_string) => { + if (!globalThis.location) + return new Error(undefined); + const url = new URL(url_string, globalThis.location.href); + const uri = uri_from_url(url); + return new Ok(uri); +}; +var uri_from_url = (url) => { + const optional = (value2) => value2 ? new Some(value2) : new None; + return new Uri(optional(url.protocol?.slice(0, -1)), new None, optional(url.hostname), optional(url.port && Number(url.port)), url.pathname, optional(url.search?.slice(1)), optional(url.hash?.slice(1))); +}; + +// build/dev/javascript/rsvp/rsvp.mjs +class BadBody extends CustomType { +} +class BadUrl extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +} +class HttpError extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +} +class NetworkError2 extends CustomType { +} +class UnhandledResponse extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +} +class Handler2 extends CustomType { + constructor(run2) { + super(); + this.run = run2; + } +} +function expect_ok_response(handler) { + return new Handler2((result) => { + return handler(try$(result, (response) => { + let $ = response.status; + let code = $; + if (code >= 200 && code < 300) { + return new Ok(response); + } else { + let code2 = $; + if (code2 >= 400 && code2 < 600) { + return new Error(new HttpError(response)); + } else { + return new Error(new UnhandledResponse(response)); + } + } + })); + }); +} +function do_send(request, handler) { + return from2((dispatch2) => { + let _pipe = send2(request); + let _pipe$1 = try_await(_pipe, read_text_body); + let _pipe$2 = map_promise(_pipe$1, (_capture) => { + return map_error(_capture, (error) => { + if (error instanceof NetworkError) { + return new NetworkError2; + } else if (error instanceof UnableToReadBody) { + return new BadBody; + } else { + return new BadBody; + } + }); + }); + let _pipe$3 = map_promise(_pipe$2, handler.run); + tap(_pipe$3, dispatch2); + return; + }); +} +function send3(request, handler) { + return do_send(request, handler); +} +function reject(err, handler) { + return from2((dispatch2) => { + let _pipe = new Error(err); + let _pipe$1 = handler.run(_pipe); + return dispatch2(_pipe$1); + }); +} +function to_uri2(uri_string) { + let _block; + if (uri_string.startsWith("./")) { + _block = from_relative_url(uri_string); + } else if (uri_string.startsWith("/")) { + _block = from_relative_url(uri_string); + } else { + _block = parse2(uri_string); + } + let _pipe = _block; + return replace_error(_pipe, new BadUrl(uri_string)); +} +function post(url, body, handler) { + let $ = to_uri2(url); + if ($ instanceof Ok) { + let uri = $[0]; + let $1 = from_uri(uri); + if ($1 instanceof Ok) { + let request = $1[0]; + let _pipe = request; + let _pipe$1 = set_method(_pipe, new Post); + let _pipe$2 = set_header2(_pipe$1, "content-type", "application/json"); + let _pipe$3 = set_body(_pipe$2, to_string2(body)); + return send3(_pipe$3, handler); + } else { + return reject(new BadUrl(url), handler); + } + } else { + let err = $[0]; + return reject(err, handler); + } +} +// build/dev/javascript/client/shared.mjs +class GroceryItem extends CustomType { + constructor(name, quantity) { + super(); + this.name = name; + this.quantity = quantity; + } +} +function grocery_item_to_json(grocery_item) { + let name; + let quantity; + name = grocery_item.name; + quantity = grocery_item.quantity; + return object2(toList([["name", string3(name)], ["quantity", int3(quantity)]])); +} +function grocery_list_to_json(items) { + return array2(items, grocery_item_to_json); +} + +// build/dev/javascript/client/client.mjs +var FILEPATH = "src/client.gleam"; + +class Model extends CustomType { + constructor(items, new_item, saving, error) { + super(); + this.items = items; + this.new_item = new_item; + this.saving = saving; + this.error = error; + } +} + +class ServerSavedList extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +} + +class UserAddedItem extends CustomType { +} + +class UserTypedNewItem extends CustomType { + constructor($0) { + super(); + this[0] = $0; + } +} + +class UserSavedList extends CustomType { +} + +class UserUpdatedQuantity extends CustomType { + constructor(index5, quantity) { + super(); + this.index = index5; + this.quantity = quantity; + } +} +function init(items) { + let model = new Model(items, "", false, new None); + return [model, none()]; +} +function save_list(items) { + let body = grocery_list_to_json(items); + let url = "/api/groceries"; + return post(url, body, expect_ok_response((var0) => { + return new ServerSavedList(var0); + })); +} +function update2(model, msg) { + if (msg instanceof ServerSavedList) { + let $ = msg[0]; + if ($ instanceof Ok) { + return [ + new Model(model.items, model.new_item, false, new None), + none() + ]; + } else { + return [ + new Model(model.items, model.new_item, false, new Some("Failed to save list")), + none() + ]; + } + } else if (msg instanceof UserAddedItem) { + let $ = model.new_item; + if ($ === "") { + return [model, none()]; + } else { + let name = $; + let item = new GroceryItem(name, 1); + let updated_items = append2(model.items, toList([item])); + return [ + new Model(updated_items, "", model.saving, model.error), + none() + ]; + } + } else if (msg instanceof UserTypedNewItem) { + let text4 = msg[0]; + return [ + new Model(model.items, text4, model.saving, model.error), + none() + ]; + } else if (msg instanceof UserSavedList) { + return [ + new Model(model.items, model.new_item, true, model.error), + save_list(model.items) + ]; + } else { + let index5 = msg.index; + let quantity = msg.quantity; + let updated_items = index_map(model.items, (item, item_index) => { + let $ = item_index === index5; + if ($) { + return new GroceryItem(item.name, quantity); + } else { + return item; + } + }); + return [ + new Model(updated_items, model.new_item, model.saving, model.error), + none() + ]; + } +} +function view_new_item(new_item) { + return div(toList([]), toList([ + input(toList([ + placeholder("Enter item name"), + value(new_item), + on_input((var0) => { + return new UserTypedNewItem(var0); + }) + ])), + button(toList([on_click(new UserAddedItem)]), toList([text3("Add")])) + ])); +} +function view_grocery_item(item, index5) { + return div(toList([styles(toList([["display", "flex"], ["gap", "1em"]]))]), toList([ + span(toList([style("flex", "1")]), toList([text3(item.name)])), + input(toList([ + style("width", "4em"), + type_("number"), + value(to_string(item.quantity)), + min2("0"), + on_input((value2) => { + let _pipe = unwrap2(parse_int(value2), 0); + return ((_capture) => { + return new UserUpdatedQuantity(index5, _capture); + })(_pipe); + }) + ])) + ])); +} +function view_grocery_list(items) { + if (items instanceof Empty) { + return p(toList([]), toList([text3("No items in your list yet.")])); + } else { + return ul(toList([]), index_map(items, (item, index5) => { + return li(toList([]), toList([view_grocery_item(item, index5)])); + })); + } +} +function view(model) { + let styles2 = toList([ + ["max-width", "30ch"], + ["margin", "0 auto"], + ["display", "flex"], + ["flex-direction", "column"], + ["gap", "1em"] + ]); + return div(toList([styles(styles2)]), toList([ + h1(toList([]), toList([text3("Grocery List")])), + view_grocery_list(model.items), + view_new_item(model.new_item), + div(toList([]), toList([ + button(toList([ + on_click(new UserSavedList), + disabled(model.saving) + ]), toList([ + text3((() => { + let $ = model.saving; + if ($) { + return "Saving..."; + } else { + return "Save List"; + } + })()) + ])) + ])), + (() => { + let $ = model.error; + if ($ instanceof Some) { + let error = $[0]; + return div(toList([style("color", "red")]), toList([text3(error)])); + } else { + return none2(); + } + })() + ])); +} +function main() { + let app = application(init, update2, view); + let $ = start4(app, "#app", toList([])); + if (!($ instanceof Ok)) { + throw makeError("let_assert", FILEPATH, "client", 17, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 417, end: 465, pattern_start: 428, pattern_end: 433 }); + } + return; +} + +// .lustre/build/client.mjs +main(); diff --git a/client/dist/index.html b/client/dist/index.html new file mode 100644 index 0000000..ae79510 --- /dev/null +++ b/client/dist/index.html @@ -0,0 +1,17 @@ + + + + + + + + client + + + + + + + +
+ \ No newline at end of file diff --git a/client/gleam.toml b/client/gleam.toml new file mode 100644 index 0000000..05fd0f0 --- /dev/null +++ b/client/gleam.toml @@ -0,0 +1,15 @@ +name = "client" +version = "1.0.0" +target = "javascript" + +[dependencies] +gleam_stdlib = ">= 0.44.0 and < 2.0.0" +lustre = ">= 5.6.0 and < 6.0.0" +rsvp = ">= 1.2.0 and < 2.0.0" +gleam_json = ">= 3.1.0 and < 4.0.0" +gleam_http = ">= 4.3.0 and < 5.0.0" +plinth = ">= 0.10.2 and < 1.0.0" + +[dev_dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" +lustre_dev_tools = ">= 2.3.5 and < 3.0.0" diff --git a/client/manifest.toml b/client/manifest.toml new file mode 100644 index 0000000..9cf9beb --- /dev/null +++ b/client/manifest.toml @@ -0,0 +1,57 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, + { name = "booklet", version = "1.1.0", build_tools = ["gleam"], requirements = [], otp_app = "booklet", source = "hex", outer_checksum = "08E0FDB78DC4D8A5D3C80295B021505C7D2A2E7B6C6D5EAB7286C36F4A53C851" }, + { name = "directories", version = "1.2.0", build_tools = ["gleam"], requirements = ["envoy", "gleam_stdlib", "platform", "simplifile"], otp_app = "directories", source = "hex", outer_checksum = "D13090CFCDF6759B87217E8DDD73A75903A700148A82C1D33799F333E249BF9E" }, + { name = "envoy", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "850DA9D29D2E5987735872A2B5C81035146D7FE19EFC486129E44440D03FD832" }, + { name = "exception", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "329D269D5C2A314F7364BD2711372B6F2C58FA6F39981572E5CA68624D291F8C" }, + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, + { name = "gleam_community_ansi", version = "1.4.4", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "1B3AEA6074AB34D5F0674744F36DDC7290303A03295507E2DEC61EDD6F5777FE" }, + { name = "gleam_community_colour", version = "2.0.4", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "6DB4665555D7D2B27F0EA32EF47E8BEBC4303821765F9C73D483F38EE24894F0" }, + { name = "gleam_crypto", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "50774BAFFF1144E7872814C566C5D653D83A3EBF23ACC3156B757A1B6819086E" }, + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, + { name = "gleam_fetch", version = "1.3.1", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_javascript", "gleam_stdlib"], otp_app = "gleam_fetch", source = "hex", outer_checksum = "A8FEB5FC4F9C4C72A71BA0D7AC249CF3AE4E98A4123607A5077D8C0B8ECC5A40" }, + { name = "gleam_http", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "82EA6A717C842456188C190AFB372665EA56CE13D8559BF3B1DD9E40F619EE0C" }, + { name = "gleam_httpc", version = "5.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "C545172618D07811494E97AAA4A0FB34DA6F6D0061FDC8041C2F8E3BE2B2E48F" }, + { name = "gleam_javascript", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "EF6C77A506F026C6FB37941889477CD5E4234FCD4337FF0E9384E297CB8F97EB" }, + { name = "gleam_json", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "44FDAA8847BE8FC48CA7A1C089706BD54BADCC4C45B237A992EDDF9F2CDB2836" }, + { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, + { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, + { name = "gleam_stdlib", version = "0.70.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86949BF5D1F0E4AC0AB5B06F235D8A5CC11A2DFC33BF22F752156ED61CA7D0FF" }, + { name = "gleam_time", version = "1.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "533D8723774D61AD4998324F5DD1DABDCDBFABAFB9E87CB5D03C6955448FC97D" }, + { name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" }, + { name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" }, + { name = "glint", version = "1.2.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "snag"], otp_app = "glint", source = "hex", outer_checksum = "2214C7CEFDE457CEE62140C3D4899B964E05236DA74E4243DFADF4AF29C382BB" }, + { name = "glisten", version = "8.0.3", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging", "telemetry"], otp_app = "glisten", source = "hex", outer_checksum = "86B838196592D9EBDE7A1D2369AE3A51E568F7DD2D168706C463C42D17B95312" }, + { name = "gramps", version = "6.0.0", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "8B7195978FBFD30B43DF791A8A272041B81E45D245314D7A41FC57237AA882A0" }, + { name = "group_registry", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "group_registry", source = "hex", outer_checksum = "BC798A53D6F2406DB94E27CB45C57052CB56B32ACF7CC16EA20F6BAEC7E36B90" }, + { name = "houdini", version = "1.2.0", build_tools = ["gleam"], requirements = [], otp_app = "houdini", source = "hex", outer_checksum = "5DB1053F1AF828049C2B206D4403C18970ABEF5C18671CA3C2D2ED0DD64F6385" }, + { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, + { name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" }, + { name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" }, + { name = "lustre", version = "5.6.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib", "houdini"], otp_app = "lustre", source = "hex", outer_checksum = "EE558CD4DB9F09FCC16417ADF0183A3C2DAC3E4B21ED3AC0CAE859792AB810CA" }, + { name = "lustre_dev_tools", version = "2.3.5", build_tools = ["gleam"], requirements = ["argv", "booklet", "filepath", "gleam_community_ansi", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_httpc", "gleam_json", "gleam_otp", "gleam_regexp", "gleam_stdlib", "glint", "group_registry", "justin", "lustre", "mist", "polly", "simplifile", "tom", "wisp"], otp_app = "lustre_dev_tools", source = "hex", outer_checksum = "0B376F4057AB6E0FB5D48DFED9943D48571968D132EBD67EAED0A21EA29027FE" }, + { name = "marceau", version = "1.3.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "2D1C27504BEF45005F5DFB18591F8610FB4BFA91744878210BDC464412EC44E9" }, + { name = "mist", version = "5.0.4", build_tools = ["gleam"], requirements = ["exception", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "gleam_yielder", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "7CED4B2D81FD547ADB093D97B9928B9419A7F58B8562A30A6CC17A252B31AD05" }, + { name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" }, + { name = "plinth", version = "0.10.2", build_tools = ["gleam"], requirements = ["gleam_javascript", "gleam_json", "gleam_stdlib"], otp_app = "plinth", source = "hex", outer_checksum = "3FE77CED3F19D70918EE32CE8BFB12BE1C28CA004D997F874C2D8DAD2DB73D87" }, + { name = "polly", version = "3.1.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_erlang", "gleam_otp", "gleam_stdlib", "simplifile"], otp_app = "polly", source = "hex", outer_checksum = "51FB565D81FF6212FDF3306D44419601F2A7C4EDD1F00FC9DA5C376A00AED4FE" }, + { name = "rsvp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_fetch", "gleam_http", "gleam_httpc", "gleam_javascript", "gleam_json", "gleam_stdlib", "lustre"], otp_app = "rsvp", source = "hex", outer_checksum = "40F9E0E662FF258E10C7041A9591261FE802D56625FB444B91510969644F7722" }, + { name = "simplifile", version = "2.4.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "7C18AFA4FED0B4CE1FA5B0B4BAC1FA1744427054EA993565F6F3F82E5453170D" }, + { name = "snag", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "274F41D6C3ECF99F7686FDCE54183333E41D2C1CA5A3A673F9A8B2C7A4401077" }, + { name = "telemetry", version = "1.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "telemetry", source = "hex", outer_checksum = "2172E05A27531D3D31DD9782841065C50DD5C3C7699D95266B2EDD54C2DAFA1C" }, + { name = "tom", version = "2.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_time"], otp_app = "tom", source = "hex", outer_checksum = "234A842F3D087D35737483F5DFB6DE9839E3366EF0CAF8726D2D094210227670" }, + { name = "wisp", version = "2.2.1", build_tools = ["gleam"], requirements = ["directories", "exception", "filepath", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "houdini", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "6E37308F4F8C45B5D3F2A2DC71BDE4DA6AD2F7978D26A7C8D6790CE275C8614E" }, +] + +[requirements] +gleam_http = { version = ">= 4.3.0 and < 5.0.0" } +gleam_json = { version = ">= 3.1.0 and < 4.0.0" } +gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } +gleeunit = { version = ">= 1.0.0 and < 2.0.0" } +lustre = { version = ">= 5.6.0 and < 6.0.0" } +lustre_dev_tools = { version = ">= 2.3.5 and < 3.0.0" } +plinth = { version = ">= 0.10.2 and < 1.0.0" } +rsvp = { version = ">= 1.2.0 and < 2.0.0" } diff --git a/client/src/client.gleam b/client/src/client.gleam new file mode 100644 index 0000000..17d8591 --- /dev/null +++ b/client/src/client.gleam @@ -0,0 +1,83 @@ +import gleam/json +import gleam/list +import gleam/result +import lustre +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import plinth/browser/document +import plinth/browser/element as plinth_element +import shared + +pub fn main() { + let initial_items = + document.query_selector("#model") + |> result.map(plinth_element.inner_text) + |> result.try(fn(json) { + json.parse(json, shared.grocery_list_decoder()) + |> result.replace_error(Nil) + }) + |> result.unwrap([]) + + let app = lustre.application(init, update, view) + let assert Ok(_) = lustre.start(app, "#app", initial_items) + + Nil +} + +type Model { + Model(rooms: List(String), name: String) +} + +fn init(items: List(String)) -> #(Model, Effect(Msg)) { + let model = Model(rooms: items, name: "") + + #(model, effect.none()) +} + +type Msg { + UserTypedNewItem(String) + UserAddedItem +} + +fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { + case msg { + UserAddedItem -> { + case model.name { + "" -> #(model, effect.none()) + name -> { + let updated_items = [name, ..model.rooms] + + #(Model(rooms: updated_items, name: ""), effect.none()) + } + } + } + + UserTypedNewItem(text) -> #(Model(..model, name: text), effect.none()) + } +} + +fn view(model: Model) -> Element(Msg) { + let styles = [ + #("max-width", "30ch"), + #("margin", "0 auto"), + #("display", "flex"), + #("flex-direction", "column"), + #("gap", "1em"), + ] + + html.div([attribute.styles(styles)], [ + html.h1([], [html.text("Select your QuizRoom")]), + view_room_list(model.rooms), + ]) +} + +fn view_room_list(items: List(String)) -> Element(Msg) { + case items { + [] -> html.p([], [html.text("No items in your list yet.")]) + _ -> { + html.ul([], list.map(items, fn(item) { html.li([], [html.text(item)]) })) + } + } +} diff --git a/client/src/shared.gleam b/client/src/shared.gleam new file mode 100644 index 0000000..f0bae3c --- /dev/null +++ b/client/src/shared.gleam @@ -0,0 +1,19 @@ +import gleam/dynamic/decode +import gleam/json + +pub type GroceryItem { + GroceryItem(name: String, quantity: Int) +} + +pub fn grocery_list_decoder() -> decode.Decoder(List(String)) { + decode.list(decode.string) +} + +fn grocery_item_to_json(grocery_item: GroceryItem) -> json.Json { + let GroceryItem(name:, quantity:) = grocery_item + json.object([#("name", json.string(name)), #("quantity", json.int(quantity))]) +} + +pub fn grocery_list_to_json(items: List(GroceryItem)) -> json.Json { + json.array(items, grocery_item_to_json) +} \ No newline at end of file diff --git a/game1.png b/game1.png new file mode 100644 index 0000000000000000000000000000000000000000..2b6d36522fd2191be771a6396e5fb798e6aa9305 GIT binary patch literal 50030 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

-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 literal 0 HcmV?d00001 diff --git a/game2.png b/game2.png new file mode 100644 index 0000000000000000000000000000000000000000..58a343f9555e3fbd1e547e8ebbc95826a6c0096d GIT binary patch literal 102071 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

8c?Ut@niLQSbtQrLy}=o{ky%_ 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%0AhP<(iCCXUIYB%pGxsdymII(= zW1N@YvFHb0i7+_lW)9kF!XU6_>31XK7m)*V3tT&ZG?UI7jXAUTH}L`rzJY#mrs zG*zRw@Gczo!#2ghzw!{io6J+MnHJPTFyHTQAPR*!ko#z1fc+$Bclz^-gh<59f>k%w5RY|&1_ zFHv0%%ApyDU2E52>MWAzUK>^jTx0Mi?Re8^R_5@~$!LEbaW~vHVtgYSU20=o1&AAZ zTQw1B%bc-;;^p)=2O|$pZA6F_dxrsO&estCfA)1AkJw0^&4-o1s zBtRdI1~>OwqxvPx>5cZK&0;M2+IB=#(!nI|Uy8*h10#?|I{_gJ@z}EXAQQD)Q8h{9 z@++VlYS<%zGNgfA0NDN3FGB;jt%8W4&aQwJ-y)NdhhnM70ZPMe>v7YG1s&5B^LEPwg^~%dCg}1G5f2h=|iv>tDHS1VU6-uIfO_;9iqOJ#O`$9e*k2@ChfPT zr`DOc+en0h)SY$PRshWH?A(UTxQ`|t1(rGb$PGqm-4aA$Fu*Gssf~oHepeUthBYe3 z>eS|g@wg~V1$Av0zm2ml2bSirw8t&hY2ISE+Z{?wt8d{haK!myeDKLI?Zso`KC*+) zw1&XqMI&7BhJ{CF!4aX}x^8Zz5yuzT@joyxB`>@2H&ra`vRromkXnk+Ure9(a^u}P zyLisjJ>V!ytF=__REkQGq>16pbfn8@-o-B3hpS*=3WcIliDBm3xGo$>I=sWPcw;{s{u$&7xMB2KC>_AXR)T( zSA;kOH}x*HP>$~vpw2U$k;82PV;JENXNpSkv4rIKD$)6pg}t}@{z^2EMPCNlG^axq zSdf%M1D<$;vxT$|aY3iTRL6U!YIo=%G-Ht!fqC-2faa?jqJ(Y6FRGXRmPjRf;me;XdsXFKmwR8iYxT?($)oEVym zTZ5%9gnaAK7%i@?eDVD`$-uZwBT{23jo}dTBYh>Myj^TOHRX^~nEs;5oFInr=Y67nB-C8BFe7x!&jaZVk4!`@a{egX4j~jMbDpwS z^i#^azmR6+2H*4{R?p-A)*9x(Ou5!AOs$tscU69`PQu(|H*8UYo5juMu8fD6Lf4d1 zl)3I>YqmA7d3xG>Yya(=6&My_s+F5@!)Q6=$|3B<~`Crmm_qbD!`B$Dwqr6hO~?|Q>mr7l1o!+2}&xWfJ$XS=7LFzf=Gr+ihznh zh`{%4&-e5C{tw^R=hyf9F6W+m&b?>7r_;XXtTEz!b@gvM{ePrfj5?cHCD6r62OlNG zODWGWxcRM8_2jhwh8M5={&fB6;HGVx{<^1kt!D4h>rO={ez_K9bgBKb+ zAw3G&o9=}(-N; z<;?A2N~*V=?Z32R#BD0yVamLc$B17WRI?OQMzO{xMje*^niIw)<}H!A{wa32^U7nx z*H}NswkQ~;bx4Y{?Znz5@x!vL4*59`ajx^%US;fvd@Y=%gfKbKl=Z-!kEbHn3&dW& zXa#~OMq)KCSJAc6nD479W{cMgcKhN{s49i2YZaV+>mF3A(Pc$$C#GQTh$E%IevXPXuAM$P9)^R zY6MSU!lI+rKETYI>cGb$0;uEtR!BIW#2!drl`I6e{Fv1`s=mph7q1P&Owor6t1GH- zPV9~n!dBk`vDM%*F> zwM*{trfettk}m^phhHx}+p({`9`LD^;eyUCo?&^xGqtX;T_IqiMnxQ&)3E3{c+;h@ z3b3=_;wqq|$#6@99!_k00jz%!x(@=r zkNyn?g^}6*SbT#bZTRS@x(>8ZdIpCu)7wt|r%t4?9kZgwz_+*y(V zEfm(~`Ko+aPMPuTh~O5TWE>0+K?$^`EE(Sm-cyOUK8!QX$nz=3AD!52OGx8N*~9db zwNJz=OL;zCj&sN8n?4eLi5Ac7KG|j4JH3 zY@{xJU$~YH8Ilv{koG=?w$-j0Er-}_400Y2y@r8NBUL7KyuXB+*3E{r3z=fooMT|1 zs6{hs2`vQ|Pmd69(HiJ@(~TqP#h{95dw+P7HM1a)3hQ?-THZ1OyqE#J0`{g%i*zkS9v@FrAg1St#vqa7%XX zEsd>x$pi%~AN0|Rh#J~?&S>y(gN-y0*O`3^5}hCxwfl+A7C$xqp*m1hrE!3bV4(-v zs2uep7`Ux>esC(Wye=atC@Q-965 zFwVBW@_Of8%o)VaLe*xVEU}(m{`AFTKxqc->@SrfjSvUk}YULBc}kL0eQhDR6ovErfS+vC8`R$A~4TdT{qU+R)R+d4wO z8H?Eu76qf;bl(SugzN$ywJi%q{z6D{Ao#)6xt7&HTo8G#IUe?KhRWJ7&Ul=A-~PmM zILoO)(m&GeMZ~8;PZ%0XElSAPY41I%cvhN5wLr+vPDh*-E!x7SrtAI0Cm?%OYs8u* zEwQ1mf{kWnzONjNH+wf9qfr*F%@LzMOu@bq1nyBS;qwc!>X7MyoE*&5A#@9c{4Vfu z?i1z-Zo9O2`*8QKy+#3WzaV3pNgeEWR5Y@4G+e5l?_Ua-(lpyWUneh@La|$mBw=Z7 z$K*m9Uo`86s?Q5fPlVJ?w+9kbEqKXLIPad2;;Q7OsheiA;yEEx;b=m$xmr~Kb(9|D zi7Xbn8>lJ%XZpm989d@Gsy4S&SjT8Wu||lREy=5<6iYSrUcpz3ZBK1W0S=lLI4vZ8 zlj>!=DB>P!_hb+tv~F=QRmJv*!xRY0BBT)*ay$y$IJ$HS?%|xkO~St+?=m=nah5KH z;KgZ;*lrHAZ*Jpcjwdq}uC5^7dM`b`>)xjCZKLO z`CD^Q^XB{3XB+0J_E?Loj6z)@ct2o>xN!?`W%&v@_ZFm8lLkKMC&m2+H=~AvuAcYz zHn|~ArLnhMUY%>Yh`H>PuM9nv3|h?-h05j<^Ha#!Zlm5Nf96O$&R>v*uX$#4S{*{= z=NMD=h^`B|sj1!`&ubTqGEgm)BkyMLlFaicJQMfyCQ;W*fM2%c=9Wb1kr5TCEcru!{UC5#IwnYPWREz;ro0M+2$}*T zlPqk0Q8#p7^y-+0vJGlRs>pUCYPnH!gRQYsKDdB=Q;OVJF@HKN@;vBh6VA*`$TdKA zf2Hx7G!JGs+Bn)oZrAO&swsbio?NJpC4*r^Y<@*@QP1qb?L zzr8G^TvXDd1f2c;d`XUlW4Jz032_<5J7mJjpQ5y8=Eb@NF$UXU-$z;jus{E|B*nud zLQ#;1Iwg^Aj|jhF{}_3u+XgpAv|e0o-pOd{-Z|^}b&%-JE(ndD@>Pg8(<^~Wnv$ar zj|5u>J$EztBtM72Z#EOUwtGd*2twqg3r0UP-oEXAnfmQjHh5o?VYpf<$Q+ew;?;*U zn}&%bD6MkTHqkD1qApx??PF5)`yI7NJx|_kyWq<8FN2&5H0&NKRq9+l1VbJ_)67bj)6toXjD!86N1KQV=SA%QVAsBJ?JRW9(;M1XhksK~## z%)mdmxOxQMMW-!LDeRDXu<_+m<+9y92v815vfPvEc0g!~&rAI|cvdcH@Te^ZbW%JJ#~K zDQ9*7;ez?Bgv^BSOXc{6{c6dSfOxZcZbzsxjCbc8t=!If*rf!M^Mgd}GU)qq8Rxo)DYzxIy1Gozqh8CLc1npg1CqWJ{lL zqquByQsMG`QaOqSpFR5dBF0(Sjnz`nKbNhaUt^plFeIO;WcBkN#6D;;+RI3?O8nzP z%0c6jN7ya4?6#>IhdpfD6jn$LH~;(0AAG;DZ4Vy|3tmF9?i9a>%AGlN11MS#92Kt- z{+G)8`FCR;#F$wv)g*l%_)@(2ZvKzoS)7d~!!od>IUj`dQP9BT=9{ix3+dZD7yQ%d z(HnrSm%m5e;r^>skjg##SxzGR4El78>>5r=P2xLsdORsyt^tb3PPp0bBKnOQA)ob9 zBp){T0^P10qpAkk2KxRf&R54*D1uL0bHW5+UO$-0?M4JS{2mP}qqO2wt2H+MAB~B63l^3{JWAi$!wlZzuE1;oStALvI z`II@zWbyhes$T#1Ke}Y?i^=ZX1irhtY#r{7fqd!Y#LcIqPUAlJhI7V;ZM%M63E1LT z0E8&<~e$$ENLpFSV!s}T8+!4J7U zxqg4Zb;H(&LGy^{zxF=+a<|;)qK)Qy+~@Qz@pv_f!ZrN_hUgIf7oh9&V`II4mbCvI zlD&688B>CE-#~JAeNP<`rCB9@LC*Kd2Y5Y3+-tib5as2P-*$YFo-ZIUz&m;&g+-G; z{}IaPw&fvo@0vYG>;Inr?Uq6{z03_G^R}$Rc4dnKf!c_uj{dWRog%W?WIy?FW*seh z=R$v1E^?>lSI~2R%TH4Vs|O#g!}?(~>&bMG)uo-qCG9;)!4SLMVPUc1YSNZp3gR_? zRC2Gg_JJ{U@HD zg)|U7PS?&1ZfD)+Py{R32-?6}X|l=E^&I}JyuofAaY0`iCEeU)!g!V}X-jdCQKHS# z#=FS-mogFfD`wnQfBo8R-vKSpdlr|}yBvj-hJb$1Lws*tLbjzo{g}L#!5vCMP>@kk zh|M0t4Ggc&Fz1LF7mZV2SK4!_L6-&4^+Q1IFaQSzRVEpWx853`PC`E8THXauKsXIG-|anrVf`ekXPhtl0C|nPqk@8jGEMX>};IkW=V>1u>sgGzqmq>?c`4z zob$K1@tH_j)AOY`1U_g&JX?(4@TMYg;L%Hl%{a)Ts=RwJ#`tgqwvPH>`n0mb z;n@Pac{DM6_sourG!-mcI6LSv1hZqJ|t`Mf^$c_6D2iCUA;(pU;Q zSt9CRNzcs-Zt?CgqdpDh4kBd@CuI=GHyIfffnD9u*>U`a!9E|01&*7^&k@zCA3>mY zAN_x*<4~AuP}q+>>)Y(o2v}Z*hmx&&b2nCjFp`aZ2YZ4&BLs(oQ1jhcNPUc8Aa?cH zazY`sXyPE+yVUg9t3KY^Y=bANUNuoX-3k zU62lj!$FDfS*UZMY7y=?Wu4RGbEU}e*MW()`+=!1L*Q>41O8_1gzUNLi&xLTou{Ln zdR2330POb7I|z?+(HfU~DX4x49y5wb;~>(lVkdMEMjjQB!nWrPxsZ#d43#u#4&O(=@*N3rFZ3!KGl@SQ>Z4m7gi z?iU0M00{UvlpKD+b8;-E^DE{!alceDC+hW?EGO;8iDVmLUqzz9(NW;@5u#ak!M+R> zq$54CFgBns16AllHsmLG$_%c;!UjgV~{Eu7j;m(jWd} zCf5>LHLWSkh86}t*1u}xC-W`>e+rEhblh2;@99(eEdL41k@_ts`FQQ^#nr=_0{gTA zpQ?)O0TF)7%fwr~w_y*X8--{eub+B_mKlD9Y%A5MCGDOmH3iyl{V#FiP;2S*J(zI~ zsB(Lp7EuWDTV}zg1ZN7fH7l~%{ql*bm!k zYg81XGoFUG6ydbar-As;n`i%GYqG)CVX>dmlELSTUQl ztJlgZBPD1`7`$8c%92(!{hLotU}0dO;Z|J$n{EhuI2&B2U!$~C{bh-9=;iwvZUsc} zoy^>);f|yj^IgD?8hThi=pJMl}u*hf27 zMS+bK#U)uhwiG+7!m*UMeZ2PL>^}M6B%Ni`1dxx z@80Tw<*}z4N776&ObdpEPQ`(5dwDkAR++Ns?9PhqB{@jQVb$7PGst&Y1si$j?LMFu z)T^||6~PY_QRml@I`o9-@%rIRSjZo<>a!_wzY)9cBB|x zji+<*&DOZ#<)%3ty=Cpm9GPEL!YznZJ+=&dz)^g(gx(JtP)!rDYiruMHgB%271?2% zq%kYt%{uNT=Du*s^0U9?6l}joM`Q%3d^)jS|F*qkmc_ImhrUxqhJs_YPv@GuU^_wg zr?;>~-M{pjJkHGpIib-ph1DmPi(ru#@)}ijb3)2(R9m}bHXNmGqUK3sz*o!hk^?yo zjUr{W`Dkl{t4*1v+_L&^ki#lxu8me0;j-168KA zReP|Y0c;Ap?*}gicyz0 zkg=;#-GP__K+rQd1IxH|tPlR3i2U~Lk2cGvA?@NHQ(D4&mSwQ05WY@kqZ3@m=i(Zd(t^hFlxE`J%FBH zbK0;{?l7eb>SGuYUl~y{eXKr%_9R~RM}RCtc1ROZ$bOtBQoSNN9O9QxTh$3c{SAP4 z&Pm3eI5AQJ!1uh;uXbi1z^2#`CHNXTH!^QyhOI4r%?K6=*fq93o1aJTc1|V61&g1WMJFOtC4= z)kSw6#NFycO#|5mNI@^?nt)zD`u)}ftJ>y8{}$I9Qh94`YBK^Eb4JTIl`hs zss^;~+3fhZwZ4O)x#W?yt`at(PiU$7NPIg$@GI{qDSq+8M&E<)APX!sj;6V)mxfIz;5j{2O>1bJ8gP!F-*5s4)Y2;PQ9&i zAEv)t>m@ovkjJXyeFuaP1(RAgp0@Tjsj91TW7Ya{LpDbNxAl)ils_NjAe#u=9}LYR zH*aXE?l#(}Zg@*>^l+~Bs#!=3VijB+Z-)_^;^h>^>Kx<5TgYIYR8Hwy0A65a2Fovb zlVbB=<2T7sR6=yu_53j``itb6)-W+Y$p!p9^D8$>G-G;EpPg@{MV(|Dlt#TNO^}py z*?lWM_KqA~5IIAc??F1Zm7AI*BPcnpkCMit96yyWfQ(wLPsKlAZnlXx8xWNfj(X0W zTu!xE0$_iBEZ4y{5T;z5z8tmj?ARE8EXG-Fvk!G+E(=TXfP!*pPMtB|+8$i%hLKbH zxG|i`uyj{;3B_<#vY3tBBu!jvZJJ-{SW(CETK-kKKH%w{tmPjV&RA<(VZ$b2gKg_9 z*}*c*q^8l-xrO%CAO$v?V^Ny)zKaUeiY%&eZep8#!Uh|m1W}cMkVQ%@yPslJPK0&5 zc8shG+hMquc(P;+5K2J%Eq>^16eaUSI46da*~K%ZOOMVTu=wFGg?BKOVLgoFF}Zib z$uRTTDyT`nLXe7>PtM7V$^wQu4=5s4AK%Q@oamdJ;1L<7@9Eg&>zRp8Iw=oa616*8r`{2iA1Rzz9&N+reDI`5i%-V!Bkr>HGzDbnI+%T%TFg zmy5!;ei%TLW7GxD8<(OxsVGm9yKJJasZWrXHk;ty1c*{ggC7OU?Ok&LItskrf3}?$ zHX~@8Qu+6NqE0eBgZnDEF(SzcFH+>-T(&a+m!9uGfQA!YC@;=>)UaVedN>?Gq{G@b^V zu97aM^C*kSYv}OTa}%Q9jej1@y(Z#xBebH1AVwU!IZNASS;~?GMaVoZnVkZLzp^{D z^jzi7_0Gfzr)G=9mNUg@D(sweVZ||MZ6LQKWl9jB2>xS_?X!u>Zs<-RD*9w7+_gwE z@@H^sgV#hI_H*6lzU$UM2y^U>7gz-ZO=ryKFtEHoZMX6Ik%2@!)d`Uaiey7T=OqJRKRDn0+yRB0mn{;kA)wOWOluG6&-$QoPVeusz?u(0Fq(^`(_@x4My`<#|`E1tp94v z$S-a{Ukc^URY|h);zMOsqPVT=;GF$0IQIc?XsdT6rwjT1%SU`BOTd>%rQ7#J_!~M_ zS4?*mdfc5J@yQ`$rKJHZZ@XSAzXILiLg+%>+(?W1R!=MJh0wk}w^Uj>-Nur!3$xQ8 zdsjcr#as2-`@x?M0lG+z?meJ*=GgYis7_waz2|O|TmidJf&TTT;Foh5G!PTv;z|13@ZFS!lb?(7Ac*f)VwD}TG zUeCI@qV_Z_qemPQP;lE766cxknBmxXVX@niR=#~cIunoBAbVvg^;y0JTSZxBs8-U+ z54O*aY)QB1*Ea80zkS~DlNqB1klwaQA%*OEuNH?K#XOtX+2yf zK~w^2j&B{DQZyZ0NAk#tV|-sXqgf^9E&tU z?sew4_+RFDPo&VU`h)-DRhGtS^aX+<52xOP!;%-Hiv9_OYbk8RcP4 zdJ%GC8ISlu`XbURwpAzLg3%pM4O2Xo_la-cn8Y|%2_6m8Gqb0N_!L-HCkI+w;?9eT zWl^GOdm3*o|H5k6!*^?BbcG^qX;!wsb~%d&&N}~;Nx)ME0Wz-24i4h+Q1vrspM^xu zPOiv`VHSzy7PP7ne{|vYfNtfpaEr=OYN5xzMjMoOL3@2}qHvfJLJMr~-28cU^UH>@&Jti- zO6$OfyCYXKXNYTm1c-6b;Sajm$s%QnkM?fd^)+;=c5Gy8SW#^gamS#InV3T82~+m++KM`=D4R~KQtnob;OU#Y3Y|)X zfER32W-S--2)S$hM1TI>4i;eC3~I1$rtJ&Rzu;JM0h!nRVAOH-X(%!;=O=MKuloM9 zfOV~-w2pVD{^*;>$R2IKCDhelc8z$8aZ&fVL^E*3`r9sdxX0VLcyyOnufb#eGNi$> zFV6$WtQIu-p7y)A`ioR z4zku7OtWb@_I^~?Ay8F`vc%H+{`OljactlOUH+bRw4ePr_d#h3%_f6_G!9x!xL;#d=VfTSHqXTMcb3uU6eI1gJTVCyMwPzts2c7Lcj!2Z7r{coi+Q81WN?v+? zU8-S5T?)P$2C@sfj{YaUhLCs;6tV}mm=u=N$dDr}=5j2g&nu@?+NJLK*6LW@ z=ZRol82NxLl@trAQ1pE|HVz=X9C_xU4LL~#O!(imCtb=I>d=d73y1CwwK&ebW=zVv z%AXJkul22>zul#d>-Qq!B=3#m6vg*ab_sT92f*LuU!A7B3qakFvSYgoiYw6{xCH4~ zG?fJ9^=7wYoqAG7=;Ve0j$H~|L|Zoy|N3qph3{57?4ZZ^6PYxN2T-|V2<?S#nx|H&8ee0O$F4 zUtB0POIx{uJS5#zvBEcsKjZk(DUzFh{lTLUlXMHk?@n0CdC+?j`2^Gdh1g|aOdf{1 z4D|i*@IUJo`i(ZWMe^`QqlV{U!e?J`j)Xrol6`i{=d^jaW~&Ph(BO5>H`b5p#gpqE zhUh}m#N!7c?7WO`>2%XqW-&Piq0V)JBowN9{KNn`ZKC1iWrXX^#%=mQmk!>#hSRyL zd6M+NSNf3Z}-t^ zC!}f+1+@Aidx0T?91eox1)>PtWrUOnT4u9$i;)^v<99bKNwh_xzu4Y}XrH`R(73bx8DX|NLbVW4^IG-S^eFnG zFdf@2RzTk>Qo6^Pe@p_Nz-Q-DeyMVogVJ2YvyE{x$@Ycs- zVRkFOvJ(N^MAxV{*QGkW+sVrhYXd({4U$@Ch1}*f!GjXklx4vt2KR*%{LyZNw-Bod z=yBVicbWTOA}@XG>K~(JC~oL{*(qN><`K8eyVNOW@^pUvBS}F~oX*D_sH{a49M5RJxA@Fg?+U#0AUb;I$* z(fTMqUmiK1IZV}A2;)JUE#jPSxa3EBzgIk!Fxp3t!LSehb1qSt_Y9$2E_GqL|1I9A z_uMvht++&_eaH)yt<~|3SS?7_WRqly5ng$4P@0B}qK+f3SdG|rR6*Z9``062F$P^2 zQn)sf*PubaSH3>QMU54}5ouZcg^aklt1pH*s4$Gls6S?cq4a^%;{V9zk;I-&;*7gU z-5+KnByq<`&;1AQ`MtGf&<5aQ92+i=!m>`;(YJ2?c5BKJB0}YB&Pv9rlyE3y$L05m z2WNw4;RQ7Ah;jNR#+jA}%AO-21U{vZt8`&6J&PP1rtAQXh!Kndg!G(oKoeH`*y~p+ z7$}cq++CgeR|d|;Em5id)G-NSI0Dja{piwJh{Xz1Ik>2v5MXD?PjmlhB>q_M9rsuA zP`2}U%p?Q+;@SI?(@%DdP&7GXw{=A!1b99h4)SgiEI-+**1Aa)h)kZ`jIZ!$y4BZ% zAY8Ab@zlB)-u$hOM}xxE?W}}&tOGW2M3v6t7jTiCyoA@P#?{5A56(ufh;wun!IwJx zjs7|Z&Nb9~Za()Jkjzi%_DZ+XnEnTqV-xz?Q`DkF!4rYLMXX69&rA%`36iM{f%~Pb zdO;d^@aAQW}f}4W@kfFPn*0jXp`35{p`ZY8fwlQQk{Q5s%B84E^-CY9w~#iN=xfk z1*_UF^ZZ8?MThlIM(3K*W3stOoAkh+`$6s83Ac&-b)3wt8{O4ps z?YuafZ_$S8iL47nMR?_BY}C8lVCG5m_ypoNR{#Mo5y})@+d#JPS{1yfuP4<9YZ&UB zYy3y3wr)QW+J4>oP5$r*g7uqXLS%IAZ_+@E6TE=D|I(SE_NMtXR<5{iZYw8P#Bupi z3G+{Zf6jT2CJlIvuS9SZfUCq|5Kh3t5n%lu6ju9*~j?LKzXac6Vc%`Iv6HP(w?!fu#;HUnKZufZdjrol|*kNy|M%fVVS0k4vWeO<^Jnn%C7gM7qbDgY}zad+yFkR);5!+g66Ed@QtlV##>-P#KePst%xdv$|!ZU zk!e6)!GO5+(OP4f+RMvH88Hz9A{gG89gNhtMtUS7Qo_vk{3l%L92qv~Bz^tp&l0pL z-6?`O3qKb}y#sgk`$U;rJ79<6@hh%ttKIf~a$n%j`pb4=KuUg{xb>h1O`4x^@nV1=bCk*E5i#FWwN_9Jk=8G&qx(tG1 zMLd?8bVfG0-olZ9a~%tFcM=cec&~^*ah)Sh_J?mXpM4T)@<49FcR$FF4{wSw_G`pF z5h>4k>&P`v6WKd{&sURXq3Uk$5}c+ zgESeknv=9@<`J>`-itiNT8rJ@iPSp-Z7JE_7W5-iQT#eG?VbU=FO^^5W;Qsn=G=wA zM#b=S9$ZosmZnaT)-UM{*O7Y8jcmGf$K=N>*6`p~zOqFJnjvEv)I;@lujtR7M7V2N zp23^i=dL{>{7X!ulN3`IKsE2Po%J7~ubjIY(p5K_m{`V4-}T-eSAsrS=3&<=MNsYj zGycOo-q7Az-)}O1@BykP?&2G`9hnk%4Z_D_0 z1G`Z=@tbDHhG5fG*b5;Il8GiVqC*rDowND3mzu9%>sSg2 zRYrbHmyGY}c%arx4ZE2%Das!w>m?`XCbCzfV#2c+%I9^mDN5A&1kn*)V1gSnwny5V z4gN%#VWRg#gzb=et%jAzteCA);MKGZK!p3;MX7~XaAby4FWC~X5Tdu;T`*#6^ zF}0i>r|+1w$|;nex(;Y2b!R+L4@3F5E@6xJMKMlDSFB74>JY_k{(~1W-;5eWYH~9% z@6HTP)1M{tS{Ohbl@WRk0)IhlPf_1~RS|33s_T|~L`gOfjPk8_G)0d%b-izceXCm4 zc(&)!ZB>l~QMv!gg(T2rIb5JLfa8sqqn#G5LAd8F;JLS+9i4!v>jAf!DNM&-RsJ zvzYnn^J}IDs(R)V&tN2Y`5g0+AniexQGP+A;bwLlGJLUXPCcO}c_&J@@BuDnfCm!8 zx3Sw%9#;7yS2e1{HFBc#bNe3h(vBV8Vc4w1d=bV|RB)ZF{MeDNp~wk*qPM!5abCq) zq~w&#CyEgjUauaw+vn52T;|A@htVyo^@@cRWbl^=PmRZoG~?gji?o?QfFl&SD=lNS z!SKUU^`iD;qft%MRvD;g(CfYX!8U2OQj?&7u7Qig(ee>!)RlLM1>MQgghKnk<>11# zTp%%S0LO8Ucp}&JD~}*(YfIOtw7=$+Jt3Py`YH{Tt3yxtX>HvC1&HhRb-y4R7f$uj z`T}P7<EiL=e!0mhjZ>MSjrzm^+= z!p`#*O-FHEmVrLY8+oavh1o~jd35+G(Y+~QB|iUkX7!55E#ghS^m_AKocA688=GHu zZ(-jcXg^E1T>Dnw5jd~=pL+g58pAY6!@)4!#X_jxGLn_nNO04N)H{y$cQHg|IHQw; zsf3;0hn^Un)1E10*LFeGvUg+VD7(KJRSMxhZUuS8gQ7tnjf1;|RN&R=>Mi`>P47v6Z#xBj!_=7gE zS`#;cc;qmRi|QVP3$zu6a{3k89gHvFZGLc)u~a^hE?Fpbta4W?CqHZZ7SZ2Mt;*&m z9QLy%FnGTO($d0B5Iqb5>@A2P$2AZ zIBs!$K#TUsdE!zf6lyK-xQpgY2LWG`)3w^5Sn_ltwbi*7nP&Oi;l{BHoE$MPb^}6S zTFb+k^nVkv)1f}v7uyM%x0Z=M>YK_7kT)5-ahN~tQ8eX<1N!w@jVz1>t(fKMHhM?Y z$RjyGWbdf`@#UR97DdyZK2_Dz+@eloh2(UQ$#cPP+9!60tiRFlEWJyoc`y@+;Pu=N z$XRZDmqq`qVIzc=Lly$rjFy=Fy;fVfwML><#5;Lco!H(*?W6=3If`AnA)q(%5quU9 zkjt$S*2&$AG`y#3&0HRZ``vJ))Cmw>pybG7DiF_=orXLlpEy1)% z4+Y~Lws7q=%nOL;{H4|Hci_NsUgGr|*lK=D)Q>@f!I&jai$qPg1%FDABeI_qUQ=vl zsN-Ie57$Fuz>gJ-gae=-?wBYAue0HTOz%nOHbFL`bFBNmMtYqrh@S8gvL*#U632k< zkHXRzoq>U7Sz29^qI}=&<`lom`5k#@BsmVfdby2rjuB{Xs3BkaR*#{4yW^XJtDnPM zybz@h--5jFe@e6{ULgED(VM3}6xu~#TB$&V9*(^_r$*66mS{E7cy)f-xvtBEUo@h+ zY^81&g}f$9!Va0}$w7)cN0<>#H-CLZe)e=U)*fdxOP47-x>grqcGJ>h;?3_EQ$bFZ z?hM5!Q!`{GSUM}7@9EZ-g(;`bR*FQ;sk8#)=f)S6H04(l9cWP*An-_;HwFCX+Me6w zyVMe1A~(KAL@<(dJM~A|^-GvO8r5a9?_xcagSmIx7UBXcp>bF|E~ZMY_Xx_&!779w zlAxtt9|)v7AeY^0B}S(kZA0X6@34X7k0Kyxg|V2Jjn_J8<(9gi#n%Mc5WSTZOCaQW zOKA9|FdxIO?fjqxZ?p|`ua3@9+o**-C|le2S^LEoR9d`tALtm->yvBs_MnL4)G_TZ zJ`wk)`BoWWkR9^(mY99E3HS!wW=p!>rOu&oe`<|q>Gmtyk1MOxIw39|%CGAF*i@2& zQ5Zl|z=j&q)3+@-+{uH)Ry*rB&T7N+$0g1|&3l|9YS?ZCFM|11UO#Yrm!Hjvjs}?^ zODUQ&CK2c-nN3+}%vWF$A3BkzUIij>cdJ&PqJb>%tY=(A9>cucf{>y-LW4_BxQ_yX zCsH4GriTY^^WZ!OD$O-ytrRSfd@FCxt9LZXg^%0<&t}D|<;5c-qYnL4oVr19o`B6U zbBeO-+<{Zkl}9n1@7L(^A8)ojQ7ETkD*2KV%j$Z?Bz9zpA8aVSs7sPvn-e8-nrWpY z`}H(Qqc?v|5lt94)DBw7ogeD-YDQJhUPSNYx1)=5a!Um_zY>rW@D(DRg#wV$tkp)} z%ui$lDro%GTO{r0g`UL<=nWYCaGN(teL3DP3@3Ind7=b$d%6q=lbs1(9wI&gC8R4~ zeEupF)6;qpb2m)96IUf#(jFr%ZG65I=JNGw8=fqi;)RNF+A?t|3uMs7T%C){(C~+H zx`qc5CkCTFcd-W^89h|j(7d;*bxErcKWWG{+N!YL`D1n3!~j}ep+;>rE-t?p>$yW<9q^SH;1`9BRYt;TU0$|z_LbIR8HS!Q6|0aZeP)eh}Qk9?=kkh z2Me-y}w-|1xamRme!XU9Uks_!n(zA!gWLU113=+#Zp`Kg4GqUv6Wu zZCY0i)(#e~5q!G$^!C*=EIi>iC!P1Go;H~ymt+qt14CNj)kV|N0Kmn1LupbU6~ap7 z-VP1#hWt?o!-oyY_*RN{a ziNSiLzGfP>6G-IUPaKqNA-p;niCH7b1B6R5x3A7?YZQDcAD1EwJ z)wnmaN4sy2V|I-~zg3vrY|AS$wdbrNKiM?Vf12|vG&;V_^U>x&Oytaf%=;;%2Pf>+FrZ6Z(S*iZ9#GHq;8`QyQzlh783@!Tw6_+@69vN25v<^?AfE)>Z z9_887MCG_8zhb%ddMt?aXs%&_Y|)6S_b+Vc4GGSV*0hp7eK6eb(%K4 z`l;ByqC|-$I}o3c@6lH-+ghU5C-5$ncJpZ~!t1nsqziMrnvH#Y)H zTO~FOdtJ%=z0kWPO#*}`NY)* zHARR|b6*W*W$OJnQT7V;xKiCsJr`hp9a;?pDrmoE8yp9DXtWPm9+P)RJv9a`y0E3h z;rt$2l}1>GafaU}^sa7x!J(t0*T^unrk}V_|9A^-d5uW>%4Ap}F1RRHe$)UN?Uo?m zku~SqE%L?cR*tT^dYW0c#dq=6!$v9$BZQVJP|v(0=zB>;~h79KWd|{pWF}_D@06@!r zg)~oeS9%IlrP-ax;CU@B&p_`+1VPtGCn(T`K1;0xg5^Ax_lA7B>S z9;1P>JiIM?tKVwhc5OCiTB@>OMEql>nB+LMa}0QwteKVB^^^EQ8|Ao8L%ci(T!Ai_-yp}AAHg0j zOZ}IF>ycenxSEDhve61Xu7o;F8HXOH-qjQ@c<&MYtUboEo0n8osYhYv%-URc$I&?c zNIO4Y;J|Hf%n%8HWj~jUEvC4qtnV@fr}|hV7hmVVslt?yOW@ zDkq=s>95-bQ;T})=Lowq*ejoZNfwmdDX;6gn|2lBl<0 zLqFTXUVH@k8oEbdHO`?8Cx{Iz*$EJ(PFI)~sqKeVPL`)l1Yc6N!Cp)P4vBYsfNgU0U|< zg_!%(m_S3Dc^A!FqU>WU>m{25~A9jI?wH zcrqpyBJ4gP34?^F9AUfGoC8H#GOQ;YcnNNQm?#s)ah>~RSR2&oD&AO3^MLC>19`kl zYV;{%ye5l?A=G8zy_*`oTigi~cCX$QHxY+aAV2O5s*Vjj8>3D-b57?E=~&Hx?c#9j z<&9Ay=3{e$cj5N;UQTLerC{3zv^8ShjZvUFOI8$W9t$c{pyp7+ z+C8*b2%C1GZHC&iSw5eh6{4s~*n6~{@^7O=cJG7y|*g^!Q66sFbL;--#tyTSLCVeV^=%rmSq_(YmXY>HrDnQpX6 zwB}i5RPB!L3WVI%>|#0LcKsZO%}(O*-zHX&dUW^GCbi4N$dq}P&uZ2h&0d_IA%%5= z?kXbQ5GO$o(zJ6-=?IideXMsBkP zH?moi@f!X>Dqn3hU#P*v9`rC+S4n`N1$jYi_-FZ%$UN9+!6}pjuK8oba0?=A@%bS_ z^$F^}o;D(;4tN2i;Q_V^$hf(x?DNr9BUJXjhVQz6vOQTQI)Gg;!meVTfvOeEHJjES zuQ!o6Hp6z44O<+wG5Y43+{<7t`2RHZ?eR?gfBd6FDW(#+&6LPph~}1iMWk}SeGnq| z`*oAtLR4~#P404;V&;CyZ89e2ew*9O{m!uA=hNr=`#pYtocCX6JMZ&;U7oM|8EzHE z2WG4wHZW*y@|e*p7wg?IN-0=28L|EYa*9t&*&+9J&Pdq*tp%X_O}O8)ljq0= zu}U&1G&p)vP!m(ofO0~_I$}E9@+~Hy563j7I4dr3SS1>e>r?Dm@0oWy1^@8~2r)X) zHd!l!g2-$&teT1+4P@M5*6Hg$D?+rC<4=xCM*XzEzn+XbzCF>(weS{jfla2*XXZ74 zeEVrnR~%<>Q3Y#Fgh$0EwGK{%!~&Py?C-}3ndnKgy8F9FvAaq-N>V)}dVEVP2W)#C z)x30uuHWJl5*{%!LA}3j45G;6c>KP#5BFrx$&>Pgv;}gHBPsHpE8&C$%^ZRTE}@WY zcpO{VBYBWw>R@#Z>+^|}8*)@Q%69c=_bW`HvOp*ak$S^B*vgdEo?Gp#|AnL^FZ$2T zbE}Qp$oSaCu2trO2x(Z=IJ67_E&K6f!C=MRHPsFNW`!$Atk{cG z-YdYI%%$7eyZ8>{kL%no%Fu;f_hWO`m<0in`X$Dl}`U2e;1} z4ObK$NuLVre5SsW*FaAgU!T6&-`IExOB{kTue4(7@bSX}1@PFF$}=dpdbJ0``{i>> zfzk{^&XidbChz!|TH`u?;yV`5a8l*JhKf6C-aRhZueWiUo9mDNP1eCvKcPZQNMXf} zq%gNfjNw*&m3!Ry6-1J+2+YiIV~gTxE911*I#%U%S^)q&B+b`Gk*#%LEF&GZ}4KzH@buXOQ!*d;*W(z)fl_I zULWPi;WQLuO%R^8!S5A}_-L~5mdOW@NtP|FV5x|wh(w90AG;6|#FJ=vxI0!-q%$00 zxinPVSY-2E?CVj?uc9w?Q^&!6W2xSc>lR_J3@J;4_?67BLoP@$KC6!gNzNAkobBji zt;AZ2K&}Dd*w`>@L)Vn}8s=~aov5uT8&i@IBOan+`%?1h)T~l{KkYH&X}cL6*~kVK zrj&Xyk&Sh<23>C@$SL5gMThe7n~=-$!Fn;`Xs!&D{f+q}WvgeIJ-}c;A&87y+QK;O zSsolXel@Z;;I2>cojKHSoEdY=^U)7}KDHIaUuwFwj(WIma5l=n@+swP_J@m=%et^= zZ-GVqP*9dwKV@#(uN+p=pqH^fimfQ}r2Z^%t;L_UD}zge_J_|1^m+l~Uvl4)n}@UM z%%4HQ#P=vgVZ&zywzn!zn03~IsnQ8>6mpEMf2zPosrFvO{_HNbqT&+MN^9I!Ya#^w z=2#gb>W=gxO-VOZ<*#AtDlS2eme(O{@Fkum3DJ$?0<^NYsC4nS*(YO^BS*Z#5vT6p zNuY_L?bVac-G=xY)3QdJ@?994QwU`l6)1k@nb!NSzlwxJj5akqcrgG{k zj%@W*V(+cD7#6C>${6-0=#$mUG&z(NSRtwh_d0-fABe%Y`LB~J#e*ErOXgEz<-6`WgqbBxjVLrUlIqJ`qI>+03& zy<$yGT>3mPR&erEQ#dT}J!8av#WSlRE-e&Y!WVawCjFKm?&XTBR{dLU(4qd*Lxn89 z{7b_f?6z@|3%T}3*>+Z#cxK8f*YnhG*VY7!@SmB>yfY`>`WMm{jraiv^~*#3r_@y3bnDpTw3!o5xm^x8D*j*!X?$)5XQbwN}!pz8laEy23TF|Y3raucDf<{1-#{Y zOMv1s-*3kfUF>RqR~Yn;mAFNHRTi?VHXNClxT@i8?nP~Uwr_`+2wPmteCPg%TZKWN zS*6?B*YFT2R*`WM5_T`@U1owHy*Hq|eybt|cpXW*NvjS&0=DdtnY>rpOb?c?H&mqdvlU=B(%t~<6sNpD9R3h`^;x_6#oRjI_IzQ93n;IwHl2;%389mHWqi4 z4`hUgW~#9Y+{9TAB-P3&xC;0QdxWSSz8dl6xl4et2`mf;0kczTsmAn+wf%Gh2b^>5 z)Nc>7b+tq8df6YYc`XhR+RM*q^Q~R_fNNPHSSRyb;bC6993lbq)IrqhW``uBNf3~4 z`W8#}Cv+5sc%Twf93xAwuTeSSi|q1skvGFDK0I*x%BTp8-G2-HpOOg;{S9k9|YJ;kFL1~ zyP&_|rXJ_*-;|q&-f>7iRgU)G@?un-KQ+FXXt!e4CD7Xbv-;5_M|SX(DGr9B<};=d z@6meid~s|$6@AHZ#;qOf)`8i1jW>HwNmVOjELp9Oxt|NsdWb1cMwTB-dz0$MqS#w} z&p8#O3Wt=><-+fOsPkQ|&?a)d^gA~n?2rwiAH3L+gnkbZU3!w0{%L3M`?VJlZ18v6 z?a>cz!Z`Y@`*#1Vn7$4kj+f@X8aOq0JsBw&#F1UG9kpHOtJ!$laX&bmaix@jL}#Q9 zyI8CHLQ&QtFOEFfwdWMPXL@vECFD^9QswoRQ+?e4oOxCUDw=KxAA6jgE?Q@O*W3NI z&|GV|COt1n4!`{?0q6L8XFh;!XR{{5idyW|bPtq#TdbtV`&wT^x7^xeN`GDePYe7> z6qEca2^`sL@Aq2RRe>O;esX+Cb(b2{Cb4YLMs-Qa!7;zT+GGaYkygr`$E&flT4lx? zi!A6~q)3O4uLr6ZP@c4zyDdK%ltXuN+a}YX)l3nF`1_F2P**c8S@5Kd&Wf_oRsp-# z+g%*OpLh?BH^LUy{^)%OPfzkWlzv$tT$bWkD5lx5XQdxyNJ`PZ?#KqrR*ouKpdQ^F zctB?Ndn(mof`$1C-czdAT}Xk%n?&v@I6I`#H@_Ft`;b^$G}!mu=6-}h>A1;DeaZMk zb+&yy@PbUpEt^kew!{f#6CCVP?O`R`q-XlvSP@2O|Hs+<*JzLVN>Y&Yt8p_HFFvSL z_LXW>#$1#BMxS%+K;Q2*=Z!@qj`(c+whx;8Ky?qs?f7fcpKDz6DJVHJmc>gG2JGYE z7MW3Go;*|zK>G16c-Mnk@($I*v6~g24Qm<}7&SXyU{#70xp>D~9kP|!HXnp7VK(26 zDfQ{&6q?ZH!^5|h=OUsN>X;-!3w+ChAIsFMNy;yj2*&ZsY+LHUC5l%Q+#1VhkGdD*2En@Ee4xF7cM6%|3FRMV#XhO zH(ZLpIr`05p_jOy*y!D0T2-AuR+~k3`K#C2E?h-GlrLPpA%C%(*@vbG@5zfn@3z5oVfvwVc=RW9SlM|)uqy}Wj`fb3fGR-gV z&49=TN)q*`uuX-ofo3@)`L-X@)XNl|5j>#>PZp(z^JuXPT(Z}3h*aBxZz+97i+@>x z0%~=+eRr9`5P}x)Fc1Ld3g&M~I$~*aZ#}jnBy?83iqda%UFbWD`+-(3u}VW0dWh#@dcGXrto9r$j3Id%rWLWt&!^ElR)m*#f1?yuf|sc%7Ic*U8+_-Ko3^mk=A zB$M}#i@ci-X-*sDb1JF3p&{pyW~CenjJAA-9H&auW?$HG&~F*2&DYg-iM;`g7lRy@ z3zoBi{}i74bm;REE&3?KA7uQw=_^`9+&R%K-tn_lm&ep#RhHNj^!aJaLc2#hnRd~R z?JLXohGW`i0BybF!?@ z921T6^<9}|EZF*<&e7;PGn+7t@bpiXl3!VJVt=uCZ)BNf8fjM#C%cQQDTuFXp|&zr ztKg@4;T79v`dXmi>|DC>uyfHQLCaz^-SUxz`n))Htq;Y#@#%;!+D9~&6zEp<+VNkF zYkMg+$1m+_sV;-OZw5WGQkg(qaj$yoOj6q`U}5e~P+RM)$nm1HDNOXeEv*#dVWHLy z!US+Wz|2{yV|Lyawql2#MctWVv>*Ya1n~OVP2cgKahj06#gSk+8@f@Lnw;f;N55yv z_u@*T+?=nXcyIg4i>m%B(YnL7e^`z7Z*kpq$X*z{Sy(K#Q=gEuBhjOrF!tSuOv=%> zQ0Ew4b?tl8r*O<=Gu2zB#l5|6t&Z7KipKe3xjVPj$=7xYiGb`Ceo{egJiA_`Hd_h~ zRJ8uI!Eg=bsI;9C&p3fIdzpR=6w7l z_NO;V@`NqtjA9R~6QDq_^qUjUt?zY@+0PB2r{!8$_pRF`hMWneN_ZlWU#GD8?7 zb#`9tc!xp+;JOw+VgzL&%Cq!cjR|UGjbB$)C+c|N=^j`yDb|1?Ak%lPZ!5q$lo$1W}dTz3RS79UZxL38*;qu0>Mcla(lBLS! zpC|d-G^)UXqpuNzN>Ph^V{2fbV>{Zcb>@BunBJQ-!Od%_(*U$!aSs)&t54Z#pJNHf zJ|90kSLn4dWx8KSr*A2)O3wR+rsF6x7znCEb5gDFZx3M?Lz`6M8awyo309`P;{P*c zZRT{b`pjRBB#^4Jmu6>D#V*!=k2L#2O0wlwaqF7>8xm3L8d18%K z%t<}31nw)}egQNQqnt9@JF}S;N=inFs*q6Q22yiGB+*^e=mD(OUzrIvF05{R~ny$yO*yh&$rC= zz_j1J)O=1OI_aw)7?MlAusPrf#krv47J-WFgTJ-}2WoJ*vpu;9dr*wRQ>hKnGma$i z0yJk_ZL%r=s;x@TMOU5P<)a9YG@4Ce;l^RHjplp7H^D>jx3vfK+~#awBsN4a+ZiGb zRw5M3ILPpf0E-QHCseh4jmUurUj*`~eMwC3BU~H`%(nYPgnKbA1iJrluDbw+Sb~>A zlx#XlO<*q1hhG{Jc)JGZpVXFt4YNan-&1-PYaLKiC0UTlfzmxo^@0OFb(2y0>EA>y zvIYZq7`)v7>FOD3=m@yet3L7%u0hIX`R+eJo$@YcL^OpN51#NxXU)c?C)>54=$tI? zg(w@hgg};U?mMK&or*i9x1)vRn`D@80Ew#{;hQ@a(J5DgfND?4w-zFDTc6MYJ_u{* zLZtn`ktru4p&%8xgY`&voypKbtL}oq&mDTI_0H8ieK_+-;0aR}@~A$Pz2k(hZI{XI zE-C2cA(f+OWraN(s=y$LA*B52DU;2H(6IsSRxhY?QsKmj0>1EEf{LgXTXrL=#_i&Y z646|k!EZ(Ydw5#dwiES)Bf}86<9@gXsKuH47;FcQHi}^P`P&$?)uIDEg>{3oKK7*u zm0+E1n~agiV1~D=5`5w)MTJUgZxHwRs}I+zxF3qTxjmexutp!DLEwju$Wt*v zbu1 zv_ec?(*e#OZo(9c zcz3B%KAs^M(@d8QNu)KN{g01cDKF$|(>vfLC^uLE{tj3t2fIU;E)Sqy2z9u%@LVB! zk=n@d(>?x@f|2a3M+EisM);pVkh&I-Kd##)8W#>x@PA63lI>25&mXb@`z5c~7R(U= zh*;xKly#yhfo57yKZ*vKyaMwWJ(Xz5+rcor-B|)ad78btPGF|9o9yZZ+jqhrZr>Zo zidqF!nnPD5w!g-MBX_)XG2|}<=38x_qq2N;hk@Cc))%P>;vnAeIqi8_rlyxH{)chF4xg%GrK@TraQZ>}IC@Fu|1f^VW%ZuJ5>G7(sJAfBB z72*ViPgAy|c6yhjX}P}EbIQ2g0E+9oYSnL6U+?3oX9mkbxhhxftqt(TbRXxUv*08? z^-Q)~=66sGPnBff>oV7hE*12?$D(y)Jy4G;_} z3i{|_cXNT-RHq*mCe9b0r^wK^baeo|lUL|cpvx)||Ly~UJ{o;oM?-xVuJ#r#4ujs% zcPJAYchwxwXk4|aJ>k#hvV1Ej3(^><1gXQtP3R2r^YXu=BQgXIud6E(W5(YQ?lv#ab1<;Oi}G_wrm^w;Y(k7u<@pvJ7*7A_@-g=6t5X?HhAb zi&bmnZ66bJS!`?0Vt6i~2(Hv2yrD*K?)wpq)W#!PuRLgr_t0lsb%G$&0w3r+pLA9L z%@wMB_JW`yTb)jd)QE!1T%lt$!%WxMNFy(lg0O_4Fp<&0PC_b4$I3S@4SikX!R6=J z(qzdGIZUUd;qIbiM*0ZVf2zVOcqyAldk{9f!^O#!Vce@JfjWf;CC{*_IO2r| z1-d>zIKj7nIFsA0Wgla7JhV?4xzH%f(x zudhr7%t#|gliIvq2i4H~{Jb$_o&2E~$W$)e*Ha*Zbo_dye&pRY0AI%}&>`gWl5Y(p|RaWb0CtqwpvUzC{W z_0IwXiw@ngtLve#Y*da-p~uV{%A5`Y#^@~_-f$#}l8QGzDtzlXRl%> zz15@@Xnm&@&7`~%#9&WN$f@N>X2DfAsa+0Qh$(`I7)H=W=5R}|eUU(Fdm<*@+d&*j z8HGPmKws6^YH#<|?9e98n_vTxx#MCK0H+GMx02sM^I1U8|RjUoM4Q^ACaO@zue{4r- z@%^&&4TL_ePqrxZnq8K$-!i3p*dbE|kBLzeRe5toQs>|eP^>=LwnW2~REfK~@YpJS zr07~rypU;=7sui=H^{8%(x=wJZC^;sTQXDI4|~?J(Z(%m-Mq0&#nC#}gds=m#2;cf zt|qEIrnJd361bE_YlGVu3Q@-n03_Rg-(u0V4Y`>uo+f?yiuPQUNR!9wpkl69Gv#Ha zhA*3OKKl5wB&C>SmCsg}cMy3muDqHt*l>Gr@=#Qs1C16xi~w*DKK$#ibw9=&MMXIj z{Qhx_s-X3@2o82P@VhO4mr>5cr+=tMX0;ua+4yU?COH&s_Dyeo+5`4BHDP-++Pc3g z8)uTYPJ1P1PXg!vhF&wGPGw9FZz3ODBlW4bGC6)&dI)?}N{|7SS0|NyD13(ZM8E2L zUMA^hZ`4mPJel{-m;0#kL#aIWE+hZw%gHj^S2n0fJf4EA6o}joZjCZJ^rFmIMiq& z@!NG=q*EmOQo2y&fu>su2w zaMz29UmIJOWn25VGr5z$LWSb*5%zB_ygcCvSKA>2?P7Evh1|{Iv(k;Fs%8&r+DSr& zO^!wK35)wL$^We6sVKo1eM&Y|qm!6bFWWUSH7jS;9xpKru$hDq!ByIURvd;*+a8z8 zSWgWV=T8o+4SC!%4byR(dmI%z<_QiHUP?>QZm&{dQ%e!u{~|~iIIW@!HqJi0B$WaB zaksPZQODdNC)w6tpCiw z-{z=eCw|TZ81_qsaNo_IO@!Rph6*VGj^Rzog;Gw+c^hNpUP7@f=N=>n=+KuWSLwgb z@G6k>E0pft4LoXd;x5NjSE=iYijVdba8HVa?>%^Z6uyvQ6w*WtOk82=Z#0Fb`YoT_ z^c6d9(0kprrX$Aq6q@ z|9QgGm5IMcvem913Xy@9D)g)s4W(Gm9&*$YQT%O{+vNs`^3Z#i5V0+@D?mO2sAGT3 zJ4;Ygabg_ir=sc7`>GDBGPNplWkHBIm*;rePLt0Rjg3Bvjdwwgda*6|I|LrlxM^Fv zM^w{BbyJAG&sM^wf>3ahkA7#j8e+%lTL{!enN8n_2;~XY;=NH7#J_xDNo69Zjkg|GXCI6KqZx@ssmW0-Lz^w^q zULq@bXr3}%+dJCtA-TR^J*1W%FkNFcvtIi7z~wDcFr&Sx(Op`1^teL-OX~6H7B4a- zEos9(TeO9!0_VPa#cxk_RKIY}`v)Wa+oOBUfpGZ6P|-pS#+hT6Gk9nJyaJFHXczH;+=1_9?f|>%ib}`3b6Hzo{T`HoOF<};Mos6cE?2FXn}lSm zT2VcXAIg?`9-;P!U24qH@i&vLYM-{tXvk0JoS(E_%9KYO!K^|!Zs^_!Wgpg;K+Jt9 zH5?=QFC1^zgf542@cYNb;N^%Sev4*A(;f5i=82!f36Loiyq`_*(aikL)0l-fnSUf4 zC*NG`xLhV);*nueik7H*XSU{pb)2jrrl%ke6#b$~@ttTE@rX*O)r*XR% zZ*y98*=_k6n``zNA)?x4HiiTHBt8-%;VJWMenKy$68w)L-AeM8yp+)Dt$XOGxj*EL zP*jMq;!V=jpQH2yPU51=;9R3Z)wBNv{1Q9$CUdsQtF@g>H<*59%Rk~)Re)A*^~Lu) z9a%+$622Y?#f|c0Fi-znWb-*fCwLC;ev(Jzv~?K% zig=*&id`$?&Bdsk@MW$O+S+(Uc!GWuiUh7I-K3yDTqQ>3VFXpmHn?)oRV_ou0(Uc8 z`(-y|buyN3$s@cbSG@#~QPx|yaZ{A$!`08WD3QG$QW|z&ZtrO;Xul=bC&TqkWl-rZ z_deVjwaoGd9;Sk~ZP;2%hh23Xzn#5+I{)WJh>H^;HfG49wPkJG?emYn;@TZ=AE=9+ zki?2p+SY6hCwNTA95wW+ zBs%u&J?>aDPY_o6aCQ2Nl7L723k1Z*ykPToTp}W5%XYjVel~f|P@{^=enla%oG0p5 znmUS$|CO##VWKrtWwM-9*TsA$)delAMSjy>M5TTQ%f|uMBFtiNvyeHu?T__L0;k?F zEMxn@lb3FJZuv=|;KtVxWw*pMpSFIksvK+e7>jkhG`eE<$MXw!vFD?6CQ@%^&JC$e{`_~?MP``Us_XymQNdIsgQZxkA1E-JpaM@;Lmdy*7Q416HMf1@AFxHxLO_s_si*S z=^MP-+R;;H5({4WUG{j{mT)+Pt;Xmr12Q7UJwLl+0#4X{(yA_q#0yhZg`O-icSTw<6zLePEwG&hlJ+L8HLj z45RhG5_+l@os8w**J5F=_BH(qkA{ev?baZUBH!s@Jw`EwHB4;%1btGbCP_v5~ozM#pEqXmdx{IdbJdNXR z8n#T`YPcNMhpnBHRy=R*jfds^Yski>@y)$Y=426(^41W`!m((`j(UXSw;73)QUHQ( zx{qON+lXf3{PZ4RDiTsK4)}L_zvYMlBknzq(|n>l8sfFC#mxzhKN%9f=Q_ldu!E}~ zPSAJhvB$U-q+|vZZO5*fyGhePi*rhfK6op^8BVb0Q9i>_dc&Www2$o_6f-c zFm+{`7NAG7`5YilyJ-r5otu{Wf$v|{=@9M3^B`p3++Tjkk23Q8OP&()$m5BwbQNC!4y$?}Z&Tbh-2-to#c=)=TW*YhHc=B|{bmuT zQA0Zo>T)A6P0ujMjxqvi7tFAJV$H2{eqi%tkm}`IP1l?4`0I^+>1tO<^(D2MIm<@% z*<KY(&Gt`0-e_8!Q15u@ zPSxbByqj~9wzuKo!uB7#seW5UqKj0SwPEZ~x3*HZwnF}I-TFI!)AdqPcR)N!jslTt z^-CG%8kf&0N4jf4xn}NAqyTj+@z@QO%k=ci68(w*F_mhmjR^L8C#rzqGRlLxUi0Q!D^XfNF7@!UG2yDo-d(g&`fIKbeGiW|1BW zceHo-L_%exJiz&Oai!;sEYIZc)W+=Q>IgTEKaKmEa#X92c8VVj=-DvoHL0B(ab5fL zm$^spcWa`Yd!#hTtpYUl*P2-whKU6%v1d|NKY9&wgI6=0Dm^s)Oi~v43=Q` zk_LZ9@oT?{0X&9{JVPpQ4Jta&V78RY6~cVjpr4-x+fl;UtW1n6p4#`=R|#+uUzPRV zWK8}~OGQ4@&7c-iL%_#Pq3lNu(PtND^LK#mIrlrESejr)8)#Pp!>8??=)qR21b0gw zErvv4hF_irC(7~bG4o9pxKn9>gHEtUN4J1qQj=+ZRV`k=sD0#dHvf-3uXz_AsM{Au z62bSX=-ahSScQGXc;W}o@?lc9;js8$R;b8HYs5c~`q^(FA$W@C4`666rZGP^Jq0+q zG*hn-1{p~AGI*XbPcpvf#yfuDuWXqT1B53bbKcJiZZ-uh0^Rnw0>($-GO<+ov<2gd zC1>BDa(&(Xql~nuBfGzd?kkOF5#9Ucqk{Gr+>{AFD5QA#lQ}{O^qX`8hj;H9B0IH6 z9rJ$QE@dOmWLN#DYp34t21yZ`SUxyhF-XdkMVkdWR~_IubgntR+5pe(SQJ*c{k2{?sql(8pd7JdT z73jcE)>XTMzN_XVr5lAXFRHu4_*)e(O$Ucdeb@nTR80Q#8u{;8XD1~9t}s#LT4aK8 ze7lr(uI-N-M3@b$718-N_+{?-TAMt?+!=tZp~PGM-!D4YD@Ju3_BMw$YQG-X9KwEX zDMl4!zdF&Kxp#^zvEF43P`dk<1ph4y$)mB!M-~y?d0>pgjE74nS|76?;r90c|69_u z?FCHtk2Bk`hQ^|A%lBkYm7T|LH~m#q|9dJrZnVd7R8<2LfqL5HpzrbJ(SLg7zdsn2 zEa`RpeG79KG6IcN93Pfnt=kwm3vB=2<2k#_`R)pvs7&}{XfL6{eUyV%2}jm9{$J|g zKZZ5?;nDXwOQ(E8j=~(9M5q1#y@Ibu$T`$svHjn!?X^?m@t?sxo-mt~|FGk)j7iFW bJ*6+wnZ6dD7svPS_@Boby6Q;Pf5QF`b;R6_ literal 0 HcmV?d00001 diff --git a/layout.html b/layout.html deleted file mode 100644 index f462667..0000000 --- a/layout.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - -

-

QUIZTerminal

-

What is your name?

-

-
-
-
- TESTsfdfdsk jhfkj dshfkjs skjfdshjs TESTsfdfdsk jhfkj - dshfkjs skjfdshjsTESTsfdfdsk jhfkj dshfkjs skjfdshjs -
-
TEST
-
TEST
-
- TESTsfdfdsk jhfkj dshfkjs skjfdshjs TESTsfdfdsk jhfkj - dshfkjs skjfdshjsTESTsfdfdsk jhfkj dshfkjs skjfdshjs -
-
TEST
-
- TESTsfdfdsk jhfkj dshfkjs skjfdshjs TESTsfdfdsk jhfkj - dshfkjs skjfdshjsTESTsfdfdsk jhfkj dshfkjs skjfdshjs -
-
TEST
-
TEST
-
- - - diff --git a/manifest.toml b/manifest.toml deleted file mode 100644 index fda893a..0000000 --- a/manifest.toml +++ /dev/null @@ -1,33 +0,0 @@ -# This file was generated by Gleam -# You typically do not need to edit this file - -packages = [ - { name = "exception", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "329D269D5C2A314F7364BD2711372B6F2C58FA6F39981572E5CA68624D291F8C" }, - { name = "gleam_crypto", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "50774BAFFF1144E7872814C566C5D653D83A3EBF23ACC3156B757A1B6819086E" }, - { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, - { name = "gleam_http", version = "4.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "DD0271B32C356FB684EC7E9F48B1E835D0480168848581F68983C0CC371405D4" }, - { name = "gleam_json", version = "3.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "874FA3C3BB6E22DD2BB111966BD40B3759E9094E05257899A7C08F5DE77EC049" }, - { name = "gleam_otp", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "7987CBEBC8060B88F14575DEF546253F3116EBE2A5DA6FD82F38243FCE97C54B" }, - { name = "gleam_stdlib", version = "0.63.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "E1D5EC07638F606E48F0EA1556044DD805F2ACE9092A6F6AFBE4A0CC4DA21C2F" }, - { name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" }, - { name = "glisten", version = "8.0.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging", "telemetry"], otp_app = "glisten", source = "hex", outer_checksum = "534BB27C71FB9E506345A767C0D76B17A9E9199934340C975DC003C710E3692D" }, - { name = "gramps", version = "6.0.0", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "8B7195978FBFD30B43DF791A8A272041B81E45D245314D7A41FC57237AA882A0" }, - { name = "group_registry", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "group_registry", source = "hex", outer_checksum = "BC798A53D6F2406DB94E27CB45C57052CB56B32ACF7CC16EA20F6BAEC7E36B90" }, - { name = "houdini", version = "1.2.0", build_tools = ["gleam"], requirements = [], otp_app = "houdini", source = "hex", outer_checksum = "5DB1053F1AF828049C2B206D4403C18970ABEF5C18671CA3C2D2ED0DD64F6385" }, - { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, - { name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" }, - { name = "lustre", version = "5.3.5", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib", "houdini"], otp_app = "lustre", source = "hex", outer_checksum = "5CBB5DD2849D8316A2101792FC35AEB58CE4B151451044A9C2A2A70A2F7FCEB8" }, - { name = "mist", version = "5.0.3", build_tools = ["gleam"], requirements = ["exception", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "gleam_yielder", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "7C4BE717A81305323C47C8A591E6B9BA4AC7F56354BF70B4D3DF08CC01192668" }, - { name = "telemetry", version = "1.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "telemetry", source = "hex", outer_checksum = "7015FC8919DBE63764F4B4B87A95B7C0996BD539E0D499BE6EC9D7F3875B79E6" }, -] - -[requirements] -gleam_crypto = { version = ">= 1.5.1 and < 2.0.0" } -gleam_erlang = { version = ">= 1.0.0 and < 2.0.0" } -gleam_http = { version = ">= 3.7.2 and < 5.0.0" } -gleam_json = { version = ">= 2.3.0 and < 4.0.0" } -gleam_otp = { version = ">= 1.1.0 and < 2.0.0" } -gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } -group_registry = { version = ">= 1.0.0 and < 2.0.0" } -lustre = { version = ">= 5.3.5 and < 6.0.0" } -mist = { version = ">= 5.0.0 and < 6.0.0" } diff --git a/priv/layout.css b/priv/layout.css deleted file mode 100644 index bfbd3d8..0000000 --- a/priv/layout.css +++ /dev/null @@ -1,69 +0,0 @@ -body { - background-color: #000000; - color: #73ad21; -} -.center { - margin: auto; - width: 50%; - border: 3px solid #73ad21; - padding: 10px; -} -.under { - margin: auto; - cursor: default; - margin-bottom: 8px; - display: grid; - width: 50%; - grid-template-columns: 33% 33% 33%; -} - -.under_cell { - text-align: center; - border: 3px solid #73ad21; - margin: 5px; - width: 95%; - min-height: 75px; -} - -.under_cell_nb { - text-align: center; - margin: 5px; - width: 95%; - min-height: 75px; -} - -.control { - margin: auto; - text-align: center; - cursor: default; - margin-bottom: 8px; - display: grid; - width: 50%; - grid-template-columns: 100% - border: 3px solid #73ad21; - padding: 10px; -} - -input { - background-color: #000000; - color: #00ff00; - width: 20cap; -} - -.controlbutton { - background-color: #000000; - color: #00FF00; - border: 3px solid #73ad21; - width: 100%; -} - -.controlbutton:focus { - border: 3px solid #534d01; -} - -input:focus { - background-color: #000000; - width: 20cap; - border-color: #00ff00; - outline: none; -} diff --git a/screenshot.png b/screenshot.png deleted file mode 100644 index 1ad1b8153afd54e876fd95d6964d04942fe7cdf0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41829 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 nQ+12wi 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 X=_5A~EiMGkNUoaMUM5`n^|${6fp$P5 diff --git a/gleam.toml b/server/gleam.toml similarity index 80% rename from gleam.toml rename to server/gleam.toml index e3286ee..e1ed142 100644 --- a/gleam.toml +++ b/server/gleam.toml @@ -1,5 +1,5 @@ name = "quizterm" -version = "0.5.0" +version = "1.0.0" [dependencies] gleam_erlang = ">= 1.0.0 and < 2.0.0" @@ -7,7 +7,8 @@ gleam_http = ">= 3.7.2 and < 5.0.0" gleam_json = ">= 2.3.0 and < 4.0.0" gleam_otp = ">= 1.1.0 and < 2.0.0" gleam_stdlib = ">= 0.44.0 and < 2.0.0" -mist = ">= 5.0.0 and < 6.0.0" +mist = ">= 6.0.0 and < 7.0.0" lustre = ">= 5.3.5 and < 6.0.0" gleam_crypto = ">= 1.5.1 and < 2.0.0" group_registry = ">= 1.0.0 and < 2.0.0" +wisp = ">= 2.2.1 and < 3.0.0" diff --git a/server/manifest.toml b/server/manifest.toml new file mode 100644 index 0000000..81fd2aa --- /dev/null +++ b/server/manifest.toml @@ -0,0 +1,39 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "directories", version = "1.2.0", build_tools = ["gleam"], requirements = ["envoy", "gleam_stdlib", "platform", "simplifile"], otp_app = "directories", source = "hex", outer_checksum = "D13090CFCDF6759B87217E8DDD73A75903A700148A82C1D33799F333E249BF9E" }, + { name = "envoy", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "850DA9D29D2E5987735872A2B5C81035146D7FE19EFC486129E44440D03FD832" }, + { name = "exception", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "329D269D5C2A314F7364BD2711372B6F2C58FA6F39981572E5CA68624D291F8C" }, + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, + { name = "gleam_crypto", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "50774BAFFF1144E7872814C566C5D653D83A3EBF23ACC3156B757A1B6819086E" }, + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, + { name = "gleam_http", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "82EA6A717C842456188C190AFB372665EA56CE13D8559BF3B1DD9E40F619EE0C" }, + { name = "gleam_json", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "44FDAA8847BE8FC48CA7A1C089706BD54BADCC4C45B237A992EDDF9F2CDB2836" }, + { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, + { name = "gleam_stdlib", version = "0.70.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86949BF5D1F0E4AC0AB5B06F235D8A5CC11A2DFC33BF22F752156ED61CA7D0FF" }, + { name = "glisten", version = "9.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging"], otp_app = "glisten", source = "hex", outer_checksum = "D92808C66F7D3F22F2289CD04CBA8151757AAE9CB3D86992F0C6DE32A41205E1" }, + { name = "gramps", version = "6.0.0", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "8B7195978FBFD30B43DF791A8A272041B81E45D245314D7A41FC57237AA882A0" }, + { name = "group_registry", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "group_registry", source = "hex", outer_checksum = "BC798A53D6F2406DB94E27CB45C57052CB56B32ACF7CC16EA20F6BAEC7E36B90" }, + { name = "houdini", version = "1.2.0", build_tools = ["gleam"], requirements = [], otp_app = "houdini", source = "hex", outer_checksum = "5DB1053F1AF828049C2B206D4403C18970ABEF5C18671CA3C2D2ED0DD64F6385" }, + { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, + { name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" }, + { name = "lustre", version = "5.6.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib", "houdini"], otp_app = "lustre", source = "hex", outer_checksum = "EE558CD4DB9F09FCC16417ADF0183A3C2DAC3E4B21ED3AC0CAE859792AB810CA" }, + { name = "marceau", version = "1.3.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "2D1C27504BEF45005F5DFB18591F8610FB4BFA91744878210BDC464412EC44E9" }, + { name = "mist", version = "6.0.2", build_tools = ["gleam"], requirements = ["exception", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "6B03DEEA38A02F276333CB27B53B16D3D45BD741B89599085A601BAF635F2006" }, + { name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" }, + { name = "simplifile", version = "2.4.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "7C18AFA4FED0B4CE1FA5B0B4BAC1FA1744427054EA993565F6F3F82E5453170D" }, + { name = "wisp", version = "2.2.2", build_tools = ["gleam"], requirements = ["directories", "exception", "filepath", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "houdini", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "5FF5F1E288C3437252ABB93D8F9CF42FF652CE7AD54480CFE736038DC09C4F22" }, +] + +[requirements] +gleam_crypto = { version = ">= 1.5.1 and < 2.0.0" } +gleam_erlang = { version = ">= 1.0.0 and < 2.0.0" } +gleam_http = { version = ">= 3.7.2 and < 5.0.0" } +gleam_json = { version = ">= 2.3.0 and < 4.0.0" } +gleam_otp = { version = ">= 1.1.0 and < 2.0.0" } +gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } +group_registry = { version = ">= 1.0.0 and < 2.0.0" } +lustre = { version = ">= 5.3.5 and < 6.0.0" } +mist = { version = ">= 6.0.0 and < 7.0.0" } +wisp = { version = ">= 2.2.1 and < 3.0.0" } diff --git a/server/priv/static/layout.css b/server/priv/static/layout.css new file mode 100644 index 0000000..94434d9 --- /dev/null +++ b/server/priv/static/layout.css @@ -0,0 +1,276 @@ +/* Reset and Base Styles */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + background: #000; + color: #00ff00; + font-family: "Courier New", "Courier", monospace; + overflow-x: hidden; + height: 100vh; +} + +/* Terminal Screen Container */ +.terminal-screen { + min-height: 100vh; + background: #000; + padding: 2rem; + padding-bottom: 5rem; /* Space for prompt */ + position: relative; +} + +/* CRT Glow Effect */ +.terminal-glow { + position: relative; + text-shadow: + 0 0 5px rgba(0, 255, 0, 0.8), + 0 0 10px rgba(0, 255, 0, 0.5), + 0 0 20px rgba(0, 255, 0, 0.3); + animation: flicker 0.15s infinite alternate; +} + +/* Scanlines Effect */ +.scanlines { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: repeating-linear-gradient( + 0deg, + rgba(0, 0, 0, 0.15), + rgba(0, 0, 0, 0.15) 1px, + transparent 1px, + transparent 2px + ); + pointer-events: none; + z-index: 1000; + opacity: 0.3; +} + +/* Terminal Header */ +.terminal-header { + text-align: center; + margin-bottom: 3rem; + position: relative; + z-index: 1; +} + +.terminal-title { + font-size: 1.25rem; + line-height: 1.6; + letter-spacing: 0.05em; + color: #00ff00; + margin-bottom: 1rem; + white-space: pre-wrap; + display: inline-block; + text-align: left; +} + +.terminal-status { + font-size: 0.875rem; + margin-top: 1rem; + color: #00aa00; +} + +.status-blink { + animation: blink 1s infinite; + color: #00ff00; +} + +.mb-4 { + margin-bottom: 1rem; +} + +/* Terminal Sections */ +.terminal-section { + margin-bottom: 2rem; + position: relative; + z-index: 1; +} + +.terminal-box { + border: 2px solid #00ff00; + padding: 1rem; + text-align: center; + max-width: 500px; + margin: 0 auto; +} + +.terminal-label { + color: #00ff00; + font-weight: bold; + display: inline-block; +} + +/* Participants Grid */ +.participants-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1rem; + max-width: 1200px; + margin: 0 auto; +} + +.participant-box { + border: 2px solid #00ff00; + padding: 1rem; + transition: all 0.2s; +} + +.participant-hidden { + border: 0px dashed #005500; + padding: 1rem; + color: #000000; + transition: all 0.2s; +} + +.participant-disconnect { + border: 1px dashed #005500; + padding: 1rem; + transition: all 0.2s; +} + +.participant-box:hover { + background: rgba(0, 255, 0, 0.05); + box-shadow: 0 0 20px rgba(0, 255, 0, 0.3); +} + +.participant-name { + color: #00ff00; + font-weight: bold; + margin-bottom: 0.5rem; + font-size: 1rem; +} + +.participant-answer { + color: #00aa00; + font-size: 0.875rem; +} + +.dim { + color: #006600; +} + +/* Terminal Prompt */ +.terminal-prompt { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: #000; + border-top: 2px solid #00ff00; + padding: 1rem 2rem; + z-index: 100; +} + +.prompt-line { + font-size: 1.125rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.prompt-symbol { + color: #00ff00; + font-weight: bold; +} + +.prompt-text { + color: #00ff00; +} + +/* Blinking Cursor */ +.cursor-blink { + animation: blink 1s infinite; + color: #00ff00; + font-weight: bold; +} + +/* Animations */ +@keyframes blink { + 0%, + 49% { + opacity: 1; + } + 50%, + 100% { + opacity: 0; + } +} + +@keyframes flicker { + 0% { + opacity: 0.98; + } + 100% { + opacity: 1; + } +} + +/* Responsive */ +@media (max-width: 768px) { + .terminal-screen { + padding: 1rem; + padding-bottom: 4rem; + } + + .terminal-title { + font-size: 0.875rem; + } + + .participants-grid { + grid-template-columns: 1fr; + } + + .terminal-prompt { + padding: 0.75rem 1rem; + } + + .prompt-line { + font-size: 0.875rem; + } + + .ml-8 { + margin-left: 0; + display: block; + margin-top: 0.5rem; + } +} + +input { + background-color: #000000; + color: #00ff00; + width: 20cap; +} + +input:focus { + background-color: #000000; + width: 20cap; + border-color: #00ff00; + outline: none; +} + +button { + background-color: #000000; + color: #00ff00; + border: 3px solid #73ad21; +} + +button:focus { + border: 3px solid #534d01; +} + +.controlbutton { + background-color: #000000; + color: #00ff00; + border: 3px solid #73ad21; + width: 100%; +} + +.controlbutton:focus { + border: 3px solid #534d01; +} diff --git a/server/priv/static/root.html b/server/priv/static/root.html new file mode 100644 index 0000000..dd37914 --- /dev/null +++ b/server/priv/static/root.html @@ -0,0 +1,31 @@ + + + + + QUIZTERMINAL v1.0 + + + + + + +
+
+
+
+╔═══════════════════════════════════════╗
+║       Q U I Z T E R M I N A L         ║
+╚═══════════════════════════════════════╝
+
+
+
+
+
+ + diff --git a/server/src/backend/playerhandler.gleam b/server/src/backend/playerhandler.gleam new file mode 100644 index 0000000..1e02e30 --- /dev/null +++ b/server/src/backend/playerhandler.gleam @@ -0,0 +1,262 @@ +import gleam/erlang/process.{type Subject} +import gleam/int +import gleam/list +import gleam/option.{None, Some} +import gleam/otp/actor +import group_registry.{type GroupRegistry} +import shared/message.{ + type AnswerStatus, type NotifyClient, type StateControl, Answer, AnswerQuiz, + Await, GiveAnswer, GiveName, GiveSingleAnswer, GivenAnswer, HasAnswered, + IDontKnow, Lobby, NotAnswered, PingTime, Pong, PurgePlayers, RevealAnswer, + User, +} + +type State { + State( + question_number: Int, + // int in #pair: answer number + slow_answers: List(#(String, List(#(Int, String)))), + // int in #pair: ping counted since response back. + name_answers: List(#(String, #(Int, AnswerStatus))), + hide_answers: Bool, + question: option.Option(String), + state_handler: actor.Started(Subject(StateControl)), + ) +} + +pub fn initialize( + state_handler: actor.Started(Subject(StateControl)), + registry: GroupRegistry(NotifyClient), +) { + actor.new(State(1, [], [], True, None, state_handler)) + |> actor.on_message(fn(state: State, message) { + let question = case state.question { + None -> { + case + actor.call(state_handler.data, 1000, message.FetchQuestion( + state.question_number, + _, + )) + { + Some(question) -> question + None -> "(no question text found)" + } + } + Some(question) -> question + } + + let state = State(..state, question: Some(question)) + + case message { + // Ask all the clients to let us know they are still there by sending a Pong with their name. Schedule + // a new ping as well. Count unacced pings per client + PingTime(sender) -> ping(state, registry, sender) + + // A client has responded to the ping with a pong. Reset the unacced ping count + Pong(name) -> pong(state, name) + + // (Controller) client asks to remove all players from the board + PurgePlayers -> purge_players(state, registry) + + // A new player has signed up, put their name in the registry + GiveName(name) -> give_name(state, registry, name) + + // A player has answered a question, put it in their state. If every player has answered, signal + // to reveal answers (live game) + GiveAnswer(name, answer) -> give_answer(state, registry, name, answer) + + // A player has answered a question in "single" game. Register the answer. + GiveSingleAnswer(name, question, answer) -> { + State( + ..state, + slow_answers: case list.key_find(state.slow_answers, name) { + Ok(l) -> { + list.key_set( + state.slow_answers, + name, + list.key_set(l, question, answer), + ) + } + Error(_) -> { + list.key_set(state.slow_answers, name, [#(question, answer)]) + } + }, + ) + } + // Reveal all answers given by players, setting the game in a "wait for next question" mode + RevealAnswer -> revel_answers(state, registry) + + // Switch from "Wait for next question" to "Answer next question" mode + AnswerQuiz -> answer_quiz(state, registry) + } + |> actor.continue() + }) + |> actor.start +} + +// Reschedule a new ping request, and ask clients to ping us back +fn ping(state, registry, sender) { + broadcast(registry, message.Ping) + process.send_after(sender, 500, message.PingTime(sender)) + State( + ..state, + // Increase ping count with one, + // filter away users with more than 4 missed pings first. + name_answers: list.map( + list.filter(state.name_answers, fn(user) { + let #(_, #(count, _)) = user + count < 8 + }), + fn(user) { + let #(name, #(count, stat)) = user + #(name, #(count + 1, stat)) + }, + ), + ) + |> broadcast_lobby(registry) +} + +fn give_answer(state, registry, name, answer) { + let state = + State( + ..state, + name_answers: list.key_set( + state.name_answers, + name, + #(0, case answer { + Some("?") -> IDontKnow + Some(answer) -> GivenAnswer(answer) + None -> IDontKnow + }), + ), + ) + // Check if everyone has answered, if so, reveal answer. + case + list.filter(state.name_answers, fn(x) { + case x { + #(_, #(_, message.NotAnswered)) -> True + _ -> False + } + }) + |> list.length + { + 0 -> { + broadcast(registry, Await) + State(..state, hide_answers: False) + } + _ -> state + } + |> broadcast_lobby(registry) +} + +fn give_name(state: State, registry, name) { + // Let the new client (and everyone else) know the current question state + case state.hide_answers { + True -> broadcast(registry, Answer) + False -> broadcast(registry, Await) + } + // Add the new user to lobby, and broadcast lobby + State( + ..state, + name_answers: list.key_set(state.name_answers, name, #(0, NotAnswered)), + ) + |> broadcast_lobby(registry) +} + +fn answer_quiz(state, registry) { + // Tell the clients to switch to "answer quiz" mode + broadcast(registry, Answer) + State( + ..state, + name_answers: list.map(state.name_answers, fn(user) { + let #(name, #(count, _)) = user + #(name, #(count, NotAnswered)) + }), + question: None, + question_number: state.question_number + 1, + hide_answers: True, + ) + |> broadcast_lobby(registry) +} + +fn purge_players(state: State, registry) { + broadcast(registry, message.Exit) + State(1, [], [], True, None, state.state_handler) + |> broadcast_lobby(registry) +} + +fn revel_answers(state, registry) { + // Tell the clients to switch to "view answers" mode + broadcast(registry, Await) + State(..state, hide_answers: False) + |> broadcast_lobby(registry) +} + +fn pong(state: State, name) { + // Reset ping count + case list.key_find(state.name_answers, name) { + Ok(#(_, answer)) -> + State( + ..state, + name_answers: list.key_set(state.name_answers, name, #(0, answer)), + ) + Error(_) -> state + } +} + +// Combine the active player answers with the answers given by the "single" player. +fn combine_lists(state: State) { + list.append( + list.map(state.name_answers, fn(name_answer) { + let #(name, #(ping_time, answer)) = name_answer + User(name, ping_time, case answer, state.hide_answers { + GivenAnswer(_), True -> HasAnswered + GivenAnswer(answer), False -> GivenAnswer(answer) + other, _ -> other + }) + }), + // Second list require a bit more work Iterate over each payers answers, + // creating user objects where question number match current question number. + list.flat_map(state.slow_answers, fn(name_answers) { + let #(name, answers) = name_answers + list.filter_map(answers, fn(number_answer) { + let #(answer_number, answer) = number_answer + case state.question_number == answer_number { + True -> { + Ok( + User(name, 0, case state.hide_answers { + True -> HasAnswered + False -> GivenAnswer(answer) + }), + ) + } + False -> Error("ignore") + } + }) + }), + ) +} + +fn broadcast_lobby(state: State, registry: GroupRegistry(NotifyClient)) { + broadcast( + registry, + Lobby( + "Question " + <> int.to_string(state.question_number) + <> ": " + <> case state.question { + Some(question) -> question + None -> "(question not found)" + }, + combine_lists(state), + ), + ) + + state +} + +fn broadcast(registry: GroupRegistry(msg), msg) -> Nil { + use member <- list.each(group_registry.members(registry, "quiz")) + + process.send(member, msg) +} diff --git a/server/src/backend/roomhandler.gleam b/server/src/backend/roomhandler.gleam new file mode 100644 index 0000000..04fd45b --- /dev/null +++ b/server/src/backend/roomhandler.gleam @@ -0,0 +1,68 @@ +import backend/playerhandler as player_handler +import gleam/erlang/process.{type Subject} +import gleam/list +import gleam/option.{Some} +import gleam/otp/actor.{type Started} +import group_registry +import shared/message.{type ClientsServer, type RoomControl, type StateControl} + +// Room handler, actor to hold the rooms for the different teams playing. +// +// Reacts to: +// CreateRoom(id) - create room with given ID. +// +// Responds to: +// FetchRoom(id, ) - Fetch room with the given id. + +type Room { + Room(questions: List(#(Int, String)), rooms: List(#(String, ClientsServer))) +} + +pub fn initialize(state_handler: Started(Subject(StateControl))) { + actor.new(Room([], [])) + |> actor.on_message(fn(state: Room, message: RoomControl(ClientsServer)) { + case message { + message.CreateRoom(id:) -> { + case + // Does room already exist? + state.rooms |> list.key_find(id) + { + Error(_) -> { + // Prevent overflowing server with rooms, set max 200 + case list.length(state.rooms) < 200 { + True -> { + // Room not found (not really an error case), create it. + let name = process.new_name("quiz-registry" <> id) + let assert Ok(actor.Started(data: registry, ..)) = + group_registry.start(name) + let assert Ok(actor) = + player_handler.initialize(state_handler, registry) + process.send_after( + actor.data, + 1000, + message.PingTime(actor.data), + ) + Room(..state, rooms: [#(id, #(registry, actor)), ..state.rooms]) + } + False -> state + } + } + // Room exists, do nothing. + Ok(_) -> state + } + } + message.FetchRoom(id:, subject:) -> { + case + // Find the room, if it exists + state.rooms |> list.key_find(id) + { + Ok(room) -> actor.send(subject, Some(room)) + Error(_) -> actor.send(subject, option.None) + } + state + } + } + |> actor.continue() + }) + |> actor.start +} diff --git a/src/backend/sockethandler.gleam b/server/src/backend/sockethandler.gleam similarity index 56% rename from src/backend/sockethandler.gleam rename to server/src/backend/sockethandler.gleam index 5135a01..335d3f2 100644 --- a/src/backend/sockethandler.gleam +++ b/server/src/backend/sockethandler.gleam @@ -1,23 +1,63 @@ +import gleam/bytes_tree import gleam/erlang/process.{type Selector, type Subject} import gleam/http/request.{type Request} import gleam/http/response.{type Response} import gleam/json -import gleam/option.{type Option, Some} +import gleam/option.{type Option, None, Some} +import gleam/otp/actor import lustre import lustre/server_component import mist.{type Connection, type ResponseData} +import shared/message pub fn serve( request: Request(Connection), component: lustre.App(start_args, model, msg), - start_args: start_args, + id: String, + actor: actor.Started(Subject(message.RoomControl(start_args))), ) -> Response(ResponseData) { - mist.websocket( - request:, - on_init: init_socket(_, component, start_args), - handler: loop_socket, - on_close: close_socket, - ) + let start_args = actor.call(actor.data, 1000, message.FetchRoom(id, _)) + case start_args { + Some(start_args) -> + mist.websocket( + request:, + on_init: init_socket(_, component, start_args), + handler: loop_socket, + on_close: close_socket, + ) + None -> + response.new(404) + |> response.set_body( + bytes_tree.from_string("Requested resource not found") |> mist.Bytes, + ) + } +} + +pub fn serve_slow( + request: Request(Connection), + component: lustre.App(#(List(#(Int, String)), start_args), model, msg), + id: String, + roomhandler: actor.Started(Subject(message.RoomControl(start_args))), + statehandler: actor.Started(Subject(message.StateControl)), +) -> Response(ResponseData) { + let start_args_opt = + actor.call(roomhandler.data, 1000, message.FetchRoom(id, _)) + let answer_list = actor.call(statehandler.data, 1000, message.FetchQuestions) + + case start_args_opt { + Some(start_args) -> + mist.websocket( + request:, + on_init: init_socket(_, component, #(answer_list, start_args)), + handler: loop_socket, + on_close: close_socket, + ) + None -> + response.new(404) + |> response.set_body( + bytes_tree.from_string("Requested resource not found") |> mist.Bytes, + ) + } } type Socket(msg) { @@ -30,7 +70,7 @@ type Socket(msg) { type SocketMessage(msg) = server_component.ClientMessage(msg) -type SocketInit(msg) = +pub type SocketInit(msg) = #(Socket(msg), Option(Selector(SocketMessage(msg)))) fn init_socket( @@ -86,6 +126,6 @@ fn loop_socket( } fn close_socket(state: Socket(msg)) -> Nil { - server_component.deregister_subject(state.self) + lustre.shutdown() |> lustre.send(to: state.component) } diff --git a/server/src/backend/statehandler.gleam b/server/src/backend/statehandler.gleam new file mode 100644 index 0000000..12b3cb7 --- /dev/null +++ b/server/src/backend/statehandler.gleam @@ -0,0 +1,75 @@ +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/otp/actor +import shared/message.{type StateControl, SetQuestion} + +type State { + State(uri: Option(String), questions: List(#(Int, #(String, String)))) +} + +pub fn initialize() { + actor.new(State(None, [])) + |> actor.on_message(fn(state: State, message: StateControl) { + case message { + SetQuestion(id:, question:) if id >= 0 && id <= 14 -> { + case list.key_find(state.questions, id) { + Ok(#(_, answer)) -> + State( + ..state, + questions: list.key_set(state.questions, id, #(question, answer)), + ) + Error(_) -> + State( + ..state, + questions: list.key_set(state.questions, id, #( + question, + "not provided", + )), + ) + } + } + message.SetAnswer(id:, answer:) if id >= 0 && id <= 14 -> + case list.key_find(state.questions, id) { + Ok(#(question, _)) -> + State( + ..state, + questions: list.key_set(state.questions, id, #(question, answer)), + ) + Error(_) -> + State( + ..state, + questions: list.key_set(state.questions, id, #( + "not provided", + answer, + )), + ) + } + + // Ignore requests for questions/answers not between 1 and 14. + message.SetQuestion(_, _) | message.SetAnswer(_, _) -> state + message.FetchQuestion(id:, subject:) -> { + case + // Find the room, if it exists + list.key_find(state.questions, id) + { + Ok(#(question, _)) -> actor.send(subject, Some(question)) + Error(_) -> actor.send(subject, option.None) + } + state + } + message.SetInfo(uri) -> State(..state, uri: Some(uri)) + message.FetchQuestions(subject) -> { + actor.send( + subject, + list.map(state.questions, fn(x) { + let #(i, #(q, _)) = x + #(i, q) + }), + ) + state + } + } + |> actor.continue() + }) + |> actor.start +} diff --git a/server/src/quizterm.gleam b/server/src/quizterm.gleam new file mode 100644 index 0000000..7030a77 --- /dev/null +++ b/server/src/quizterm.gleam @@ -0,0 +1,103 @@ +import backend/roomhandler +import backend/sockethandler +import backend/statehandler +import gleam/bytes_tree +import gleam/erlang/application +import gleam/erlang/process +import gleam/http/request +import gleam/http/response.{type Response} +import gleam/list +import gleam/option.{None} +import gleam/result +import gleam/string +import mist.{type ResponseData, File} +import web/components/answerlist +import web/components/card +import web/components/control +import web/router +import wisp +import wisp/wisp_mist + +pub fn main() { + wisp.configure_logger() + + let assert Ok(state_handler) = statehandler.initialize() + let assert Ok(room_handler) = roomhandler.initialize(state_handler) + + let assert Ok(_) = + fn(req) { + case request.path_segments(req) { + ["lustre", "runtime.mjs"] -> serve_runtime() + [] | ["index.html"]-> serve_static("root.html") + ["client.js"] -> serve_static("client.js") + ["static", file] -> serve_static(file) + ["socket", "card", id] -> + sockethandler.serve(req, card.component(), id, room_handler) + ["socket", "control", id] -> + sockethandler.serve(req, control.component(), id, room_handler) + ["socket", "slow", id] -> + sockethandler.serve_slow( + req, + answerlist.component(), + id, + room_handler, + state_handler, + ) + _ -> + wisp_mist.handler( + router.handle_request(room_handler, state_handler, _), + "very_secret", + )(req) + } + } + |> mist.new + |> mist.bind("0.0.0.0") + |> mist.port(1234) + |> mist.start + + process.sleep_forever() +} + +fn serve_static(filename: String) { + let assert Ok(priv) = application.priv_directory("quizterm") + let surname = string.split(filename, ".") |> list.last + let path = priv <> "/static/" <> filename + let data = + mist.send_file(path, offset: 0, limit: None) + |> result.map(fn(file) { + echo "SUCCESS " <> filename + response.new(200) + |> response.set_header("Content-Type", case surname { + Ok("css") -> "text/css" + Ok("js") -> "application/javascript" + Ok(_) | Error(_) -> "text/html" + }) + |> response.set_body(file) + }) + |> result.lazy_unwrap(fn() { + echo "FAIL " <> filename + response.new(404) + |> response.set_body( + bytes_tree.from_string("Requested resource not found") |> mist.Bytes, + ) + }) + echo "Attempting to serve file " <> filename <> " was " + + data +} + +fn serve_runtime() -> Response(ResponseData) { + let assert Ok(lustre_priv) = application.priv_directory("lustre") + let file_path = lustre_priv <> "/static/lustre-server-component.mjs" + + case mist.send_file(file_path, offset: 0, limit: None) { + Ok(file) -> + response.new(200) + |> response.prepend_header("content-type", "application/javascript") + |> response.set_body(file) + + Error(_) -> + response.new(404) + |> response.set_body(mist.Bytes(bytes_tree.new())) + } +} diff --git a/server/src/shared/message.gleam b/server/src/shared/message.gleam new file mode 100644 index 0000000..3a6bb11 --- /dev/null +++ b/server/src/shared/message.gleam @@ -0,0 +1,50 @@ +import gleam/erlang/process.{type Subject} +import gleam/option.{type Option} +import gleam/otp/actor.{type Started} +import group_registry.{type GroupRegistry} + +pub type ClientsServer = + #(GroupRegistry(NotifyClient), Started(Subject(NotifyServer))) + +pub type NotifyServer { + PingTime(Subject(NotifyServer)) + Pong(name: String) + AnswerQuiz + RevealAnswer + PurgePlayers + GiveName(name: String) + GiveAnswer(name: String, answer: Option(String)) + GiveSingleAnswer(name: String, question: Int, answer: String) +} + +pub type StateControl { + SetQuestion(id: Int, question: String) + SetAnswer(id: Int, answer: String) + SetInfo(url: String) + FetchQuestion(id: Int, subject: Subject(Option(String))) + FetchQuestions(subject: Subject(List(#(Int, String)))) +} + +pub type RoomControl(msg) { + CreateRoom(id: String) + FetchRoom(id: String, subject: Subject(Option(msg))) +} + +pub type AnswerStatus { + NotAnswered + HasAnswered + IDontKnow + GivenAnswer(answer: String) +} + +pub type NotifyClient { + Ping + Lobby(question: String, names: List(User)) + Answer + Await + Exit +} + +pub type User { + User(name: String, ping_time: Int, answer: AnswerStatus) +} diff --git a/server/src/web/components/answerlist.gleam b/server/src/web/components/answerlist.gleam new file mode 100644 index 0000000..78adcfd --- /dev/null +++ b/server/src/web/components/answerlist.gleam @@ -0,0 +1,182 @@ +import gleam/erlang/process.{type Subject} +import gleam/int +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/otp/actor.{type Started} +import lustre +import lustre/attribute.{class} +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import shared/message.{type NotifyClient, type NotifyServer} +import web/components/shared.{ + step_prompt, view_input, view_named_input, view_named_keyed_input, view_yes_no, +} + +pub fn component() -> lustre.App( + #(List(#(Int, String)), message.ClientsServer), + Model, + Msg, +) { + lustre.application(init, update, view) +} + +pub opaque type Model { + Model( + state: Msg, + answers: List(#(Int, #(String, String))), + handler: Started(Subject(NotifyServer)), + ) +} + +fn init( + start_args: #(List(#(Int, String)), message.ClientsServer), +) -> #(Model, Effect(Msg)) { + let #(answers, handlers) = start_args + let #(_registry, handler) = handlers + + // Convert a "question number -> question text" array to + // "question number" -> #("question text", "users answer" array + // with blank user answers. + let initial_array = + list.filter(answers, fn(x) { + let #(i, _) = x + i <= 14 && i >= 0 + }) + |> list.map(fn(x) { + let #(a, b) = x + #(a, #(b, "")) + }) + + #(Model(Initial, initial_array, handler), effect.none()) +} + +pub opaque type Msg { + Initial + SharedMessage(message: NotifyClient) + ReceiveName(message: String) + AcceptName(accept: Option(String)) + GiveQuestion(name: String, question: String) + GiveAnswer(name: String, question: Int, answer: String) +} + +fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { + case msg { + Initial | SharedMessage(_) -> #(model, effect.none()) + AcceptName(None) -> #(Model(Initial, [], model.handler), effect.none()) + AcceptName(Some(name)) -> { + #(Model(..model, state: GiveQuestion(name, "")), effect.none()) + } + GiveQuestion(name, question) -> + case int.parse(question) { + Ok(question) if question >= 1 && question <= 14 -> #( + Model(..model, state: GiveAnswer(name:, question:, answer: "")), + effect.none(), + ) + _ -> #( + Model(..model, state: GiveQuestion(name:, question: "")), + effect.none(), + ) + } + GiveAnswer(name, question, answer) -> { + actor.send( + model.handler.data, + message.GiveSingleAnswer(name:, question:, answer:), + ) + let new_value = case list.key_find(model.answers, question) { + Ok(pair) -> { + let #(a, _) = pair + #(a, answer) + } + Error(_) -> #("", answer) + } + #( + Model( + ..model, + state: GiveQuestion(name, ""), + answers: list.key_set(model.answers, question, new_value), + ), + effect.none(), + ) + } + ReceiveName(_) -> #(Model(..model, state: msg), effect.none()) + } +} + +fn view(model: Model) -> Element(Msg) { + element.fragment([ + html.div([attribute.class("terminal-prompt")], [ + case model.state { + Initial -> + step_prompt( + "Hello stranger. To join the quiz, I need to know your name", + fn() { view_input(ReceiveName) }, + ) + ReceiveName(name) -> + step_prompt( + "Your name is " <> name <> "? Are you absolutely sure???", + fn() { view_yes_no(name, AcceptName) }, + ) + GiveQuestion(name, _) -> + step_prompt( + "Enter the number of the question you want to answer", + fn() { view_named_input(name, GiveQuestion) }, + ) + GiveAnswer(name, question, _) -> + step_prompt( + "Enter the answer to question number " <> int.to_string(question), + fn() { view_named_keyed_input(question, name, GiveAnswer) }, + ) + _ -> html.h3([], [html.text("Waiting for next question")]) + }, + ]), + html.div([class("terminal-header")], [ + html.div([class("terminal-status")], [ + html.span([class("status-blink")], [html.text("●")]), + html.text(" SYSTEM READY"), + html.span([class("ml-8")], [ + case model.state { + Initial -> html.text("STATUS: Please input your name") + ReceiveName(_) -> html.text("STATUS: Please validate your name") + GiveQuestion(_, _) -> html.text("STATUS: Pick question to answer") + GiveAnswer(_, _, _) -> html.text("STATUS: Give your answer") + _ -> html.text("STATUS: Waiting for next question") + }, + ]), + ]), + ]), + terminal_section(model.answers, "[ACTIVE TRANSMISSIONS]", fn(answer) { + content_cell(answer) + }), + ]) +} + +fn terminal_section( + answers: List(#(Int, #(String, String))), + header: String, + extract: fn(#(Int, #(String, String))) -> Element(Msg), +) { + html.div([attribute.class("terminal-section")], [ + html.div([attribute.class("terminal-label mb-4")], [ + html.text(header), + ]), + html.div([attribute.class("participants-grid")], list.map(answers, extract)), + ]) +} + +fn content_cell(answer: #(Int, #(String, String))) -> Element(Msg) { + let #(question, #(question_text, answer)) = answer + html.div( + [ + class("participant-box"), + ], + [ + html.div([class("participant-name")], [ + html.text("► " <> int.to_string(question) <> " " <> question_text), + ]), + html.div([class("participant-answer")], [ + html.text(answer), + ]), + ], + ) +} diff --git a/server/src/web/components/card.gleam b/server/src/web/components/card.gleam new file mode 100644 index 0000000..db71267 --- /dev/null +++ b/server/src/web/components/card.gleam @@ -0,0 +1,275 @@ +import gleam/erlang/process.{type Subject} +import gleam/int +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/otp/actor.{type Started} +import group_registry.{type GroupRegistry} +import lustre +import lustre/attribute.{class} +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import lustre/server_component +import shared/message.{type NotifyClient, type NotifyServer, type User, User} +import web/components/shared.{ + step_prompt, view_input, view_named_input, view_yes_no, +} + +pub fn component() -> lustre.App(message.ClientsServer, Model, Msg) { + lustre.application(init, update, view) +} + +type State { + AskName + NameOk(String) + WaitForQuiz(String) + Answer(String) +} + +pub opaque type Model { + Model( + state: State, + lobby: #(String, List(User)), + registry: GroupRegistry(NotifyClient), + handler: Started(Subject(NotifyServer)), + ) +} + +fn init(handlers: message.ClientsServer) -> #(Model, Effect(Msg)) { + let #(registry, handler) = handlers + + let model = Model(AskName, #("", []), registry, handler) + #(model, subscribe(registry, SharedMessage)) +} + +fn subscribe( + registry: GroupRegistry(topic), + on_msg handle_msg: fn(topic) -> msg, +) -> Effect(msg) { + use _, _ <- server_component.select + let subject = group_registry.join(registry, "quiz", process.self()) + + let selector = + process.new_selector() + |> process.select_map(subject, handle_msg) + + selector +} + +pub opaque type Msg { + SharedMessage(message: NotifyClient) + ReceiveName(message: String) + AcceptName(accept: Option(String)) + GiveAnswer(name: String, answer: String) +} + +fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { + let handler = model.handler + + case msg { + ReceiveName(name) -> #(Model(..model, state: NameOk(name)), effect.none()) + AcceptName(Some(name)) -> { + actor.send(handler.data, message.GiveName(name:)) + #(Model(..model, state: WaitForQuiz(name)), effect.none()) + } + AcceptName(None) -> #(Model(..model, state: AskName), effect.none()) + GiveAnswer(name, answer) -> { + actor.send(handler.data, message.GiveAnswer(name, Some(answer))) + #(Model(..model, state: WaitForQuiz(name)), effect.none()) + } + SharedMessage(shared_msg) -> #( + handle_server_message(model, shared_msg), + effect.none(), + ) + } +} + +fn handle_server_message(model: Model, notify_client) { + case notify_client { + message.Lobby(question, lobby) -> Model(..model, lobby: #(question, lobby)) + message.Exit -> Model(AskName, #("", []), model.registry, model.handler) + message.Answer -> + case model.state { + // We are currently waiting for next quiz question, ok to switch to answer mode + WaitForQuiz(name) -> Model(..model, state: Answer(name)) + // We are not in a state to react, ignore switch to answer mode. + _ -> model + } + message.Await -> + case model.state { + Answer(name) -> Model(..model, state: WaitForQuiz(name)) + _ -> model + } + message.Ping -> { + let has_name = case model.state { + Answer(name) -> Some(name) + WaitForQuiz(name) -> Some(name) + _ -> None + } + case has_name { + Some(name) -> actor.send(model.handler.data, message.Pong(name)) + _ -> Nil + } + model + } + } +} + +fn view(model: Model) -> Element(Msg) { + let #(question, lobby) = model.lobby + + element.fragment([ + html.div([attribute.class("terminal-prompt")], [ + case model.state { + AskName -> + step_prompt( + "Hello stranger. To join the quiz, I need to know your name", + fn() { view_input(ReceiveName) }, + ) + NameOk(name) -> + step_prompt( + "Your name is " <> name <> "? Are you absolutely sure???", + fn() { view_yes_no(name, AcceptName) }, + ) + Answer(name) -> + step_prompt( + "The Quiz Lead will now ask the question, and you may answer.", + fn() { view_named_input(name, GiveAnswer) }, + ) + _ -> html.h3([], [html.text("Waiting for next question")]) + }, + ]), + html.div([class("terminal-header")], [ + html.div([class("terminal-status")], [ + html.span([class("status-blink")], [html.text("●")]), + html.text(" SYSTEM READY"), + html.span([class("ml-8")], [ + case model.state { + AskName -> html.text("STATUS: Please input your name") + NameOk(_) -> html.text("STATUS: Please validate your name") + Answer(_) -> + html.div([], [ + html.div([], [html.text("STATUS: Answer the following:")]), + html.div([], [html.text(question)]), + ]) + _ -> html.text("STATUS: Waiting for next question") + }, + ]), + ]), + ]), + html.div([class("terminal-section")], case lobby { + [] -> [] + lobby -> { + let answered = + list.filter(lobby, fn(x) { + case x.answer { + message.IDontKnow | message.HasAnswered | message.GivenAnswer(_) -> + True + _ -> False + } + }) + |> list.length + |> int.to_string + let size = lobby |> list.length |> int.to_string + [ + html.div([attribute.class("terminal-box")], [ + html.span([attribute.class("terminal-label")], [ + html.text("[PROGRESS] "), + ]), + html.text("Answered: "), + case answered == size { + True -> html.text("Everyone!") + False -> html.text(answered <> "/" <> size) + }, + ]), + ] + } + }), + terminal_section( + lobby, + "[ACTIVE TRANSMISSIONS]", + fn(x) { + case x.answer { + message.GivenAnswer(_) | message.HasAnswered -> True + _ -> False + } + }, + fn(user) { + let User(name, ping_time, answer) = user + case answer { + message.GivenAnswer(answer) -> answer + message.HasAnswered -> "Answer Given" + _ -> "Odd State..." + } + |> content_cell(name, ping_time, _) + }, + ), + terminal_section( + lobby, + "[P A S S]", + fn(x) { + case x.answer { + message.IDontKnow -> True + _ -> False + } + }, + fn(user) { + let User(name, ping_time, _) = user + content_cell(name, ping_time, "P.A.S.S :(") + }, + ), + terminal_section( + lobby, + "[AWAITING RESPONSE]", + fn(x) { + case x.answer { + message.NotAnswered -> True + _ -> False + } + }, + fn(user) { + case user { + User(name, ping_time, _) -> + content_cell(name, ping_time, "Not Answered") + } + }, + ), + ]) +} + +fn terminal_section( + lobby: List(User), + header: String, + filter: fn(User) -> Bool, + extract: fn(User) -> Element(Msg), +) { + html.div([attribute.class("terminal-section")], [ + html.div([attribute.class("terminal-label mb-4")], [ + html.text(header), + ]), + html.div( + [attribute.class("participants-grid")], + list.filter(lobby, filter) + |> list.map(extract), + ), + ]) +} + +fn content_cell(header: String, ping_time: Int, content: String) -> Element(Msg) { + html.div( + [ + class(case ping_time > 1 { + True -> "participant-disconnect" + False -> "participant-box" + }), + ], + [ + html.div([class("participant-name")], [ + html.text("► " <> header), + ]), + html.div([class("participant-answer")], [ + html.text(content), + ]), + ], + ) +} diff --git a/src/components/control.gleam b/server/src/web/components/control.gleam similarity index 53% rename from src/components/control.gleam rename to server/src/web/components/control.gleam index 11ac002..8a12746 100644 --- a/src/components/control.gleam +++ b/server/src/web/components/control.gleam @@ -16,11 +16,7 @@ import shared/message.{ type NotifyClient, type NotifyServer, AnswerQuiz, RevealAnswer, } -pub fn component() -> lustre.App( - #(GroupRegistry(NotifyClient), Started(Subject(NotifyServer))), - Model, - Msg, -) { +pub fn component() -> lustre.App(message.ClientsServer, Model, Msg) { lustre.application(init, update, view) } @@ -40,13 +36,11 @@ pub opaque type Model { pub opaque type Msg { AnnounceQuiz AnnounceAnswer - End + PurgePlayers SharedMessage(message: message.NotifyClient) } -fn init( - handlers: #(GroupRegistry(NotifyClient), Started(Subject(NotifyServer))), -) -> #(Model, Effect(Msg)) { +fn init(handlers: message.ClientsServer) -> #(Model, Effect(Msg)) { let #(registry, handler) = handlers let model = Model(state: Quiz, registry:, handler:) @@ -68,42 +62,60 @@ fn subscribe( } fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { - let registry = model.registry let handler = model.handler - case msg { - AnnounceQuiz -> { - actor.send(handler.data, AnswerQuiz) - #(Model(Quiz, registry:, handler:), effect.none()) - } - AnnounceAnswer -> { - actor.send(handler.data, RevealAnswer) - #(Model(Reveal, registry:, handler:), effect.none()) - } - End -> #(model, effect.none()) - SharedMessage(_) -> #(model, effect.none()) - } + #( + case msg { + PurgePlayers -> { + // Temp removed button to issue this action. + actor.send(handler.data, message.PurgePlayers) + model + } + AnnounceQuiz -> { + actor.send(handler.data, AnswerQuiz) + Model(..model, state: Quiz) + } + AnnounceAnswer -> { + actor.send(handler.data, RevealAnswer) + Model(..model, state: Reveal) + } + SharedMessage(message.Await) -> Model(..model, state: Reveal) + SharedMessage(_) -> model + }, + effect.none(), + ) } fn view(model: Model) -> Element(Msg) { - case model.state { - Quiz -> { + html.div([attribute.class("terminal-section")], [ + html.div([attribute.class("participants-grid")], [ element.fragment([ - keyed.div([attribute.class("control")], [ - #("reveal", view_input("Reveal answers", AnnounceAnswer)), + keyed.div([attribute.class("participand-hidden")], [ + #("reveal", html.text("")), ]), - ]) - } - Reveal -> { - element.fragment([ - keyed.div([attribute.class("control")], [ - #("next", view_input("Ask for next answer", AnnounceQuiz)), + keyed.div([attribute.class("participand-hidden")], [ + #("reveal", html.text("")), ]), - ]) - } - } + keyed.div([attribute.class("control")], [ + #("reveal", html.text("")), + ]), + case model.state { + Quiz -> { + keyed.div([attribute.class("control")], [ + #("reveal", view_button("Reveal answers", AnnounceAnswer)), + ]) + } + Reveal -> { + keyed.div([attribute.class("control")], [ + #("next", view_button("Ask next question", AnnounceQuiz)), + ]) + } + }, + ]), + ]), + ]) } -fn view_input(text: String, on_submit handle_keydown: msg) -> Element(msg) { +fn view_button(text: String, on_submit handle_keydown: msg) -> Element(msg) { let on_keydown = event.on("click", { decode.success(handle_keydown) }) html.button([attribute.class("controlbutton"), on_keydown], [ diff --git a/server/src/web/components/shared.gleam b/server/src/web/components/shared.gleam new file mode 100644 index 0000000..4177701 --- /dev/null +++ b/server/src/web/components/shared.gleam @@ -0,0 +1,104 @@ +import gleam/dynamic/decode +import gleam/option.{type Option, None, Some} +import lustre/attribute +import lustre/element.{type Element} +import lustre/element/html +import lustre/element/keyed +import lustre/event +import lustre/server_component + +// Components use "keyed.div" rather than "html.div" for input fields +// The value of the fields are uncontrolled, so this is needed to +// get a new state between each input, or else the value transfers between +// input fields. +// +// see: https://hexdocs.pm/lustre/lustre/element/keyed.html + +pub fn view_named_input( + name: String, + on_submit handle_keydown: fn(String, String) -> msg, +) -> Element(msg) { + prompt_input( + "nameinput", + key_down(fn(a: String) { decode.success(handle_keydown(name, a)) }, fn() { + decode.failure(handle_keydown(name, ""), "") + }), + ) +} + +pub fn view_named_keyed_input( + question: Int, + name: String, + on_submit handle_keydown: fn(String, Int, String) -> msg, +) -> Element(msg) { + prompt_input( + "keyput", + key_down( + fn(a: String) { decode.success(handle_keydown(name, question, a)) }, + fn() { decode.failure(handle_keydown(name, question, ""), "") }, + ), + ) +} + +pub fn view_input(on_submit handle_keydown: fn(String) -> msg) -> Element(msg) { + prompt_input( + "input", + key_down(fn(a: String) { decode.success(handle_keydown(a)) }, fn() { + decode.failure(handle_keydown(""), "") + }), + ) +} + +pub fn view_yes_no( + accepted: String, + on_submit handle_button: fn(Option(String)) -> msg, +) -> Element(msg) { + html.div([], [ + html.button([event.on_click(handle_button(Some(accepted)))], [ + html.text(" "), + ]), + html.text(" - "), + html.button([event.on_click(handle_button(None))], [html.text(" ")]), + ]) +} + +fn key_down( + success: fn(String) -> decode.Decoder(msg), + fail: fn() -> decode.Decoder(msg), +) { + event.on("keydown", { + use key <- decode.field("key", decode.string) + use value <- decode.subfield(["target", "value"], decode.string) + + case key { + "Enter" if value != "" -> success(value) + _ -> fail() + } + }) + |> server_component.include(["key", "target.value"]) +} + +fn prompt_input(key, on_keydown) { + keyed.div([], [ + #(key <> "header", html.text("$>")), + #( + key, + html.input([ + attribute.type_("text"), + on_keydown, + attribute.autofocus(True), + ]), + ), + ]) +} + +pub fn step_prompt(text: String, fetch: fn() -> Element(a)) { + html.div([attribute.class("prompt-line")], [ + html.div([attribute.class("prompt-text")], [ + html.div([], [ + html.text(text), + ]), + fetch(), + ]), + ]) +} diff --git a/server/src/web/handlers/serve.gleam b/server/src/web/handlers/serve.gleam new file mode 100644 index 0000000..1a5cc57 --- /dev/null +++ b/server/src/web/handlers/serve.gleam @@ -0,0 +1,123 @@ +import gleam/erlang/process.{type Subject} +import gleam/json +import gleam/int +import gleam/option.{None, Some} +import gleam/otp/actor.{type Started} +import lustre/attribute.{class} +import lustre/element +import lustre/element/html.{body, div, head, html, link, meta, script, title} +import lustre/server_component +import shared/message.{ + type ClientsServer, type RoomControl, CreateRoom, FetchRoom, +} +import wisp.{type Response} + +pub fn main_html(content: fn() -> element.Element(a)) -> Response { + html([], [ + head([], [ + meta([attribute.charset("utf-8")]), + meta([ + attribute.name("viewport"), + attribute.content("width=device-width, initial-scale=1.0"), + ]), + title([], "QUIZTERMINAL v1.0"), + script( + [attribute.type_("module"), attribute.src("/lustre/runtime.mjs")], + "", + ), + link([ + attribute.rel("stylesheet"), + attribute.type_("text/css"), + attribute.href("/static/layout.css"), + ]), + ]), + body([], [ + div([class("terminal-screen")], [ + div([class("terminal-glow")], [ + div([class("scanlines")], []), + + // title + div([class("terminal-header")], [ + html.pre([class("terminal-title")], [ + html.text( + " +╔═══════════════════════════════════════╗ +║ Q U I Z T E R M I N A L ║ +╚═══════════════════════════════════════╝ +", + ), + ]), + ]), + // Insert content + content(), + ]), + ]), + ]), + ]) + |> element.to_document_string + |> wisp.html_response(200) +} + +pub fn room(actor: Started(Subject(RoomControl(ClientsServer))), id: String) { + process.send(actor.data, CreateRoom(id)) + status_head("Created room with id " <> id) +} + +pub fn slow( + actor: Started(Subject(RoomControl(ClientsServer))), + id: String, +) -> fn() -> element.Element(a) { + let start_args = actor.call(actor.data, 1000, FetchRoom(id, _)) + case start_args { + Some(_) -> fn() { + div([], [ + server_component.element( + [server_component.route("/socket/slow/" <> id)], + [], + ), + ]) + } + None -> status_head("Could not find that room...") + } +} + +pub fn board( + actor: Started(Subject(RoomControl(ClientsServer))), + id: String, +) -> fn() -> element.Element(a) { + let start_args = actor.call(actor.data, 1000, FetchRoom(id, _)) + case start_args { + Some(_) -> fn() { + div([], [ + server_component.element( + [server_component.route("/socket/card/" <> id)], + [], + ), + server_component.element( + [server_component.route("/socket/control/" <> id)], + [], + ), + ]) + } + None -> status_head("Could not find that room...") + } +} + +pub fn create_json_response(response: #(Int, String, String)) { + let #(code, message, output) = response + wisp.log_info("[api][" <>int.to_string(code)<>"][" <> message<> "]") + json.object([#("response", json.string(output))]) + |> json.to_string + |> wisp.json_response(200) +} + +pub fn status_head(output: String) { + fn() -> element.Element(a) { + html.div([class("terminal-header")], [ + html.div([class("terminal-status")], [ + html.span([class("status-blink")], [html.text("●")]), + html.h2([class("ml-8")], [html.text(output)]), + ]), + ]) + } +} diff --git a/server/src/web/router.gleam b/server/src/web/router.gleam new file mode 100644 index 0000000..330092b --- /dev/null +++ b/server/src/web/router.gleam @@ -0,0 +1,124 @@ +import gleam/bit_array +import gleam/crypto +import gleam/dynamic/decode +import gleam/erlang/process.{type Subject} +import gleam/http +import gleam/int +import gleam/list +import gleam/otp/actor.{type Started} +import shared/message.{type ClientsServer, type RoomControl, type StateControl} +import web/handlers/serve.{board, main_html, room, slow, status_head} +import wisp.{type Request, type Response} + +pub fn handle_request( + room_handler: Started(Subject(RoomControl(ClientsServer))), + state_handler: Started(Subject(StateControl)), + req: Request, +) -> Response { + use req <- middleware(req) + case wisp.path_segments(req) { + ["api", ..path] -> handle_api(state_handler, req, path) + _ -> handle_html(room_handler, req) + } +} + +fn handle_html( + actor: Started(Subject(RoomControl(ClientsServer))), + req: Request, +) -> Response { + case wisp.path_segments(req) { + ["slow", id] -> slow(actor, id) + ["board", id] -> board(actor, id) + ["room", id] -> room(actor, id) + _ -> { + wisp.log_info("No match for request") + status_head("Nothing to see here") + } + } + |> main_html +} + +fn handle_api( + actor: Started(Subject(StateControl)), + req: Request, + path: List(String), +) { + use json <- wisp.require_json(req) + + case list.key_find(req.headers, "x-api-key") { + Ok(key) -> { + case + bit_array.base64_encode(crypto.hash(crypto.Sha256, <>), True) + == "1nIr1fQzs0K9UZAeUcG/67n12iRiviIS6gO5WXyI2+0=" + { + True -> + case req.method, path { + http.Post, ["info"] -> decode_info(actor, json) + http.Post, ["questions"] -> + decode_index_to_text(actor, json, message.SetQuestion) + http.Post, ["answers"] -> + decode_index_to_text(actor, json, message.SetAnswer) + _, _ -> #(404, "bad api apth","Resource not found") + } + False -> { + #(401, "invalid api key","unauthorized") + } + } + } + Error(_) -> { + #(401, "missing api key","unauthorized") + } + } + |> serve.create_json_response +} + +fn decode_info( + actor: Started(Subject(StateControl)), + json_string: decode.Dynamic, +) { + let decode_uri = { + use uri <- decode.field("teaserImage", decode.string) + decode.success(message.SetInfo(uri)) + } + case decode.run(json_string, decode_uri) { + Ok(info) -> { + actor.send(actor.data, info) + #(200, "Updated info", "Updated info") + } + Error(_) -> #(400, "Unable to update info","bad request") + } +} + +fn decode_index_to_text( + actor: Started(Subject(StateControl)), + json_string: decode.Dynamic, + message: fn(Int, String) -> StateControl, +) { + let decode_answer = { + use index <- decode.field("index", decode.int) + use text <- decode.field("text", decode.string) + decode.success(message(index, text)) + } + + case decode.run(json_string, decode.list(decode_answer)) { + Ok(answers) -> { + list.each(answers, fn(answer_question) { + actor.send(actor.data, answer_question) + }) + #(200, "imported " <> int.to_string(list.length(answers)) <> " items.","imported " <> int.to_string(list.length(answers)) <> " items.") + } + Error(_) -> #(400, "Failed to import","bad request") + } +} + +pub fn middleware( + req: wisp.Request, + handle_request: fn(wisp.Request) -> wisp.Response, +) -> wisp.Response { + let req = wisp.method_override(req) + use <- wisp.log_request(req) + use <- wisp.rescue_crashes + use req <- wisp.handle_head(req) + use req <- wisp.csrf_known_header_protection(req) + handle_request(req) +} diff --git a/server/test/live.gleam b/server/test/live.gleam new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/server/test/live.gleam @@ -0,0 +1 @@ + diff --git a/src/backend/statehandler.gleam b/src/backend/statehandler.gleam deleted file mode 100644 index 6edcd13..0000000 --- a/src/backend/statehandler.gleam +++ /dev/null @@ -1,86 +0,0 @@ -import gleam/erlang/process -import gleam/list -import gleam/option.{type Option, None, Some} -import gleam/otp/actor -import group_registry.{type GroupRegistry} -import shared/message.{ - type NotifyClient, Answer, AnswerQuiz, Await, GiveAnswer, GiveName, Lobby, - RevealAnswer, User, -} - -type State { - State(name_answers: List(#(String, Option(String))), hide_answers: Bool) -} - -pub fn initialize(registry: GroupRegistry(NotifyClient)) { - actor.new(State([], True)) - |> actor.on_message(fn(state: State, message) { - case message { - GiveName(name) -> { - // Let the new client know the current question state - case state.hide_answers { - True -> broadcast(registry, Answer) - False -> broadcast(registry, Await) - } - State(list.key_set(state.name_answers, name, None), state.hide_answers) - |> broadcast_lobby(registry) - } - GiveAnswer(name, answer) -> { - State( - list.key_set(state.name_answers, name, Some(answer)), - state.hide_answers, - ) - |> broadcast_lobby(registry) - } - AnswerQuiz -> { - broadcast(registry, Answer) - State( - list.map(state.name_answers, fn(user) { - let #(name, _) = user - #(name, None) - }), - True, - ) - |> broadcast_lobby(registry) - } - RevealAnswer -> { - broadcast(registry, Await) - State(state.name_answers, hide_answers: False) - |> broadcast_lobby(registry) - } - } - |> actor.continue() - }) - |> actor.start -} - -fn broadcast_lobby(state: State, registry: GroupRegistry(NotifyClient)) { - broadcast( - registry, - Lobby( - list.map(state.name_answers, fn(name_answer) { - let #(name, answer) = name_answer - User(name, case answer { - Some(answer) -> - Some(case state.hide_answers { - True -> "Answer" - False -> answer - }) - None -> - case state.hide_answers { - True -> None - False -> Some("No answer") - } - }) - }), - ), - ) - - state -} - -fn broadcast(registry: GroupRegistry(msg), msg) -> Nil { - use member <- list.each(group_registry.members(registry, "quiz")) - - process.send(member, msg) -} diff --git a/src/components/chat.gleam b/src/components/chat.gleam deleted file mode 100644 index 0a1ef34..0000000 --- a/src/components/chat.gleam +++ /dev/null @@ -1,239 +0,0 @@ -import gleam/dynamic/decode -import gleam/erlang/process.{type Subject} -import gleam/list -import gleam/option.{type Option, None, Some} -import gleam/otp/actor.{type Started} -import gleam/string -import group_registry.{type GroupRegistry} -import lustre -import lustre/attribute -import lustre/effect.{type Effect} -import lustre/element.{type Element} -import lustre/element/html -import lustre/element/keyed -import lustre/event -import lustre/server_component -import shared/message.{type NotifyClient, type NotifyServer, type User, User} - -pub fn component() -> lustre.App( - #(GroupRegistry(NotifyClient), Started(Subject(NotifyServer))), - Model, - Msg, -) { - lustre.application(init, update, view) -} - -type State { - AskName - NameOk(String) - WaitForQuiz(String) - Answer(String) -} - -pub opaque type Model { - Model( - state: State, - lobby: List(User), - registry: GroupRegistry(NotifyClient), - handler: Started(Subject(NotifyServer)), - ) -} - -fn init( - handlers: #(GroupRegistry(NotifyClient), Started(Subject(NotifyServer))), -) -> #(Model, Effect(Msg)) { - let #(registry, handler) = handlers - - let model = Model(AskName, [], registry, handler) - #(model, subscribe(registry, SharedMessage)) -} - -fn subscribe( - registry: GroupRegistry(topic), - on_msg handle_msg: fn(topic) -> msg, -) -> Effect(msg) { - use _, _ <- server_component.select - let subject = group_registry.join(registry, "quiz", process.self()) - - let selector = - process.new_selector() - |> process.select_map(subject, handle_msg) - - selector -} - -pub opaque type Msg { - SharedMessage(message: NotifyClient) - ReceiveName(message: String) - AcceptName(accept: Option(String)) - GiveAnswer(name: String, answer: String) -} - -fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { - let handler = model.handler - - case msg { - ReceiveName(name) -> { - #(Model(..model, state: NameOk(name)), effect.none()) - } - AcceptName(name) -> { - case name { - Some(name) -> { - actor.send(handler.data, message.GiveName(name:)) - #(Model(..model, state: WaitForQuiz(name)), effect.none()) - } - _ -> #(Model(..model, state: AskName), effect.none()) - } - } - GiveAnswer(name, answer) -> { - actor.send(handler.data, message.GiveAnswer(name, answer)) - #(Model(..model, state: WaitForQuiz(name)), effect.none()) - } - - SharedMessage(message:) -> { - case message { - message.Lobby(lobby) -> #(Model(..model, lobby: lobby), effect.none()) - message.Answer -> - case model.state { - WaitForQuiz(name) -> #( - Model(..model, state: Answer(name)), - effect.none(), - ) - _ -> #(model, effect.none()) - } - message.Await -> - case model.state { - Answer(name) -> #( - Model(..model, state: WaitForQuiz(name)), - effect.none(), - ) - _ -> #(model, effect.none()) - } - } - } - } -} - -fn view(model: Model) -> Element(Msg) { - element.fragment([ - keyed.div([attribute.class("center")], [ - #("header", html.h1([], [html.text("QUIZTerminal")])), - case model.state { - AskName -> #( - "name", - view_input("Enter your name to join the quiz: ", ReceiveName), - ) - NameOk(name) -> { - #( - "accept", - view_accept( - "Are you ok with the name " <> name <> "? (y/n)", - name, - AcceptName, - ), - ) - } - Answer(name) -> { - #( - "answer", - view_named_input("Answer the question: ", name, GiveAnswer), - ) - } - _ -> { - #("history", view_ask_question("Waiting for next question")) - } - }, - ]), - html.div( - [attribute.class("under")], - list.map(model.lobby, fn(user) { - let User(name, answer) = user - let answer = case answer { - None -> "waiting..." - Some(answer) -> answer - } - html.div([attribute.class("under_cell")], [ - html.h3([], [ - html.text(name), - ]), - html.text(answer), - ]) - }), - ), - ]) -} - -fn view_ask_question(question: String) -> Element(msg) { - html.text(question) -} - -fn view_accept( - prompt: String, - accepted: String, - on_submit handle_keydown: fn(Option(String)) -> msg, -) -> Element(msg) { - let on_keydown = - event.on("keydown", { - use value <- decode.field("key", decode.string) - let result = case string.lowercase(value) { - "y" -> Some(accepted) - _ -> None - } - decode.success(handle_keydown(result)) - }) - |> server_component.include(["key"]) - - html.div([], [ - html.text(prompt), - html.input([ - attribute.class("input"), - on_keydown, - attribute.autofocus(True), - ]), - ]) -} - -fn view_input( - text: String, - on_submit handle_keydown: fn(String) -> msg, -) -> Element(msg) { - let on_keydown = - event.on("keydown", { - use key <- decode.field("key", decode.string) - use value <- decode.subfield(["target", "value"], decode.string) - - case key { - "Enter" if value != "" -> decode.success(handle_keydown(value)) - _ -> decode.failure(handle_keydown(""), "") - } - }) - |> server_component.include(["key", "target.value"]) - - html.div([], [ - html.text(text), - html.input([attribute.class("input"), on_keydown, attribute.autofocus(True)]), - ]) -} - -fn view_named_input( - text: String, - name: String, - on_submit handle_keydown: fn(String, String) -> msg, -) -> Element(msg) { - let on_keydown = - event.on("keydown", { - use key <- decode.field("key", decode.string) - use value <- decode.subfield(["target", "value"], decode.string) - - case key { - "Enter" if value != "" -> decode.success(handle_keydown(name, value)) - _ -> decode.failure(handle_keydown("", ""), "") - } - }) - |> server_component.include(["key", "target.value"]) - - html.div([], [ - html.text(text), - html.input([attribute.class("input"), on_keydown, attribute.autofocus(True)]), - ]) -} diff --git a/src/quizterm.gleam b/src/quizterm.gleam deleted file mode 100644 index 918f7fc..0000000 --- a/src/quizterm.gleam +++ /dev/null @@ -1,128 +0,0 @@ -import backend/sockethandler -import backend/statehandler -import components/chat -import components/control -import gleam/bytes_tree -import gleam/erlang/application -import gleam/erlang/process -import gleam/http/request.{type Request} -import gleam/http/response.{type Response} -import gleam/option.{None} -import gleam/otp/actor -import gleam/result -import group_registry -import lustre/attribute -import lustre/element -import lustre/element/html.{ - body, div, head, html, img, link, meta, script, title, -} -import lustre/server_component -import mist.{type Connection, type ResponseData} - -pub fn main() { - let name = process.new_name("quiz-registry") - let assert Ok(actor.Started(data: registry, ..)) = group_registry.start(name) - let assert Ok(actor) = statehandler.initialize(registry) - let assert Ok(_) = - fn(request: Request(Connection)) -> Response(ResponseData) { - case request.path_segments(request) { - [] -> serve_html(False) - ["control"] -> serve_html(True) - ["lustre", "runtime.mjs"] -> serve_runtime() - ["static", file] -> serve_static(file) - ["ws"] -> - sockethandler.serve(request, chat.component(), #(registry, actor)) - ["cws"] -> - sockethandler.serve(request, control.component(), #(registry, actor)) - _ -> response.set_body(response.new(404), mist.Bytes(bytes_tree.new())) - } - } - |> mist.new - |> mist.bind("localhost") - |> mist.port(1234) - |> mist.start - - process.sleep_forever() -} - -fn serve_html(control: Bool) -> Response(ResponseData) { - let html = - html([attribute.lang("en")], [ - head([], [ - link([ - attribute.rel("stylesheet"), - attribute.type_("text/css"), - attribute.href("/static/layout.css"), - ]), - meta([attribute.charset("utf-8")]), - meta([ - attribute.name("viewport"), - attribute.content("width=device-width, initial-scale=1"), - ]), - title([], "Quizterm"), - script( - [attribute.type_("module"), attribute.src("/lustre/runtime.mjs")], - "", - ), - ]), - body([], [ - case control { - False -> server_component.element([server_component.route("/ws")], []) - True -> - div([], [ - server_component.element([server_component.route("/ws")], []), - server_component.element([server_component.route("/cws")], []), - ]) - }, - div([attribute.class("under")], [ - div([attribute.class("under_cell_nb")], []), - div([attribute.class("under_cell_nb")], []), - div([attribute.class("under_cell_bn")], [ - img([ - attribute.src("https://gleam.run/images/lucy/lucydebugfail.svg"), - attribute.width(150), - ]), - ]), - ]), - ]), - ]) - |> element.to_document_string_tree - |> bytes_tree.from_string_tree - - response.new(200) - |> response.set_body(mist.Bytes(html)) - |> response.set_header("content-type", "text/html") -} - -fn serve_static(filename: String) { - let assert Ok(priv) = application.priv_directory("quizterm") - let path = priv <> "/" <> filename - mist.send_file(path, offset: 0, limit: None) - |> result.map(fn(file) { - response.new(200) - |> response.set_header("Content-Type", "text/css") - |> response.set_body(file) - }) - |> result.lazy_unwrap(fn() { - response.new(404) - |> response.set_body( - bytes_tree.from_string("Requested resource not found") |> mist.Bytes, - ) - }) -} - -fn serve_runtime() -> Response(ResponseData) { - let assert Ok(lustre_priv) = application.priv_directory("lustre") - let file_path = lustre_priv <> "/static/lustre-server-component.mjs" - - case mist.send_file(file_path, offset: 0, limit: None) { - Ok(file) -> - response.new(200) - |> response.prepend_header("content-type", "application/javascript") - |> response.set_body(file) - - Error(_) -> - response.new(404) - |> response.set_body(mist.Bytes(bytes_tree.new())) - } -} diff --git a/src/shared/message.gleam b/src/shared/message.gleam deleted file mode 100644 index 2968521..0000000 --- a/src/shared/message.gleam +++ /dev/null @@ -1,18 +0,0 @@ -import gleam/option.{type Option} - -pub type NotifyServer { - AnswerQuiz - RevealAnswer - GiveName(name: String) - GiveAnswer(name: String, answer: String) -} - -pub type NotifyClient { - Lobby(names: List(User)) - Answer - Await -} - -pub type User { - User(name: String, answer: Option(String)) -}