Compare commits
4 Commits
b1dc4835b3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9312c14d4e | |||
| dd7e7e59e6 | |||
| 952373bbee | |||
| c506108ca8 |
39
index.ts
39
index.ts
@@ -1,16 +1,10 @@
|
|||||||
import type { ServerWebSocket } from "bun";
|
import type { ServerWebSocket } from "bun";
|
||||||
import { callTicket, completeTicket, getCurrentTicket, getDisplayTickets, getLogEntries, getTicket, noShowTicket } from "./redis";
|
import { callTicket, completeTicket, getCurrentTicket, getDisplayTickets, getLogEntries, getTicket, noShowTicket } from "./redis";
|
||||||
|
|
||||||
let IP_TO_ROOMS: Record<string, string> = {};
|
const roomstxt = await Bun.file("./rooms.txt").text();
|
||||||
|
const ROOMS = roomstxt.split("\n");
|
||||||
|
|
||||||
const iprooms = await Bun.file("./iprooms.csv").text();
|
const MOTD = process.env.MOTD || "50;";
|
||||||
const lines = iprooms.split("\n");
|
|
||||||
for (const line of lines.slice(1)) {
|
|
||||||
const [ip, room] = line.split(",");
|
|
||||||
if (ip && room) {
|
|
||||||
IP_TO_ROOMS[ip.trim()] = room.trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export let displaySockets: ServerWebSocket<unknown>[] = [];
|
export let displaySockets: ServerWebSocket<unknown>[] = [];
|
||||||
|
|
||||||
@@ -34,22 +28,7 @@ Bun.serve({
|
|||||||
async message(ws, message) {
|
async message(ws, message) {
|
||||||
console.log("Received message:", message);
|
console.log("Received message:", message);
|
||||||
const data = JSON.parse(message.toString());
|
const data = JSON.parse(message.toString());
|
||||||
if (data.type === "hello") {}
|
if (data.type === "hello") {
|
||||||
else if (data.type === "my-room") {
|
|
||||||
console.log("Client requested room for IP:", ws.remoteAddress);
|
|
||||||
const room = IP_TO_ROOMS[ws.remoteAddress];
|
|
||||||
if (!room) {
|
|
||||||
return void ws.send(JSON.stringify({ type: "error", code: -1, message: "No room" }));
|
|
||||||
}
|
|
||||||
ws.send(JSON.stringify({ type: "my-room", room }) );
|
|
||||||
// } else if (data.type === "create-ticket") {
|
|
||||||
// const ticket = await getTicket(data.ticket);
|
|
||||||
// if(!ticket || ticket.status == "completed" || ticket.status == "no-show") {
|
|
||||||
// await createTicket(data.ticket);
|
|
||||||
// ws.send(JSON.stringify({ type: "create-ticket", status: "created", ticket, num: data.ticket }) );
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// ws.send(JSON.stringify({ type: "error", code: -2, message: "Ticket already exists and is not completed or no-show", ticket, num: data.ticket }));
|
|
||||||
} else if (data.type === "call-ticket") {
|
} else if (data.type === "call-ticket") {
|
||||||
// const ticket = await getTicket(data.ticket);
|
// const ticket = await getTicket(data.ticket);
|
||||||
// if(!ticket || ticket.status == "completed" || ticket.status == "no-show") {
|
// if(!ticket || ticket.status == "completed" || ticket.status == "no-show") {
|
||||||
@@ -111,8 +90,16 @@ Bun.serve({
|
|||||||
if(!displaySockets.includes(ws)) {
|
if(!displaySockets.includes(ws)) {
|
||||||
displaySockets.push(ws);
|
displaySockets.push(ws);
|
||||||
console.log("Added display socket. Total:", displaySockets.length);
|
console.log("Added display socket. Total:", displaySockets.length);
|
||||||
ws.send(JSON.stringify({ type: "display", tickets: await getDisplayTickets() }));
|
ws.send(JSON.stringify({ type: "display", tickets: await getDisplayTickets(), motd: MOTD }));
|
||||||
}
|
}
|
||||||
|
} else if (data.type == "rooms") {
|
||||||
|
ws.send(JSON.stringify({ type: "rooms", rooms: ROOMS }));
|
||||||
|
} else if (data.type == "call-audio") {
|
||||||
|
// Send the call.wav file as data URI
|
||||||
|
const file = await Bun.file("./call.wav").arrayBuffer();
|
||||||
|
const base64 = Buffer.from(file).toString("base64");
|
||||||
|
const dataUri = `data:audio/wav;base64,${base64}`;
|
||||||
|
ws.send(JSON.stringify({ type: "call-audio", dataUri }));
|
||||||
}
|
}
|
||||||
}, // a message is received
|
}, // a message is received
|
||||||
open(ws) {
|
open(ws) {
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
import SearchForm from "./search-form.svelte";
|
import SearchForm from "./search-form.svelte";
|
||||||
import VersionSwitcher from "./switcher.svelte";
|
import VersionSwitcher from "./switcher.svelte";
|
||||||
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
|
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
|
||||||
import type { ComponentProps } from "svelte";
|
import { onMount, type ComponentProps } from "svelte";
|
||||||
import { page } from "$app/state";
|
import { page } from "$app/state";
|
||||||
|
import { eventTarget } from "./ws.svelte";
|
||||||
let { ref = $bindable(null), ...restProps }: ComponentProps<typeof Sidebar.Root> = $props();
|
let { ref = $bindable(null), ...restProps }: ComponentProps<typeof Sidebar.Root> = $props();
|
||||||
|
|
||||||
type NavData = {
|
type NavData = {
|
||||||
@@ -36,6 +37,10 @@
|
|||||||
url: "#",
|
url: "#",
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
|
title: "Laden...",
|
||||||
|
url: "#"
|
||||||
|
},
|
||||||
|
/*{
|
||||||
title: "1",
|
title: "1",
|
||||||
url: "/room/1",
|
url: "/room/1",
|
||||||
},
|
},
|
||||||
@@ -74,7 +79,7 @@
|
|||||||
{
|
{
|
||||||
title: "Empfang 2",
|
title: "Empfang 2",
|
||||||
url: "/room/Empfang%202",
|
url: "/room/Empfang%202",
|
||||||
},
|
},*/
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
@@ -103,10 +108,10 @@
|
|||||||
title: "Logs",
|
title: "Logs",
|
||||||
url: "/log",
|
url: "/log",
|
||||||
},
|
},
|
||||||
{
|
/*{
|
||||||
title: "Admin",
|
title: "Admin",
|
||||||
url: "/admin",
|
url: "/admin",
|
||||||
},
|
},*/
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -136,17 +141,37 @@
|
|||||||
title: "Logs",
|
title: "Logs",
|
||||||
url: "/log",
|
url: "/log",
|
||||||
},
|
},
|
||||||
{
|
/*{
|
||||||
title: "Admin",
|
title: "Admin",
|
||||||
url: "/",
|
url: "/",
|
||||||
isActive: true
|
isActive: true
|
||||||
},
|
},*/
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
const data = $derived(isAdmin ? adminData : regularData);
|
const data = $derived(isAdmin ? adminData : regularData);
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
eventTarget.addEventListener("rooms", (e) => {
|
||||||
|
if(e instanceof CustomEvent) {
|
||||||
|
const rooms = e.detail.rooms;
|
||||||
|
data.navMain[0].items = rooms.map((room: string) => ({
|
||||||
|
title: room,
|
||||||
|
url: `/room/${encodeURIComponent(room)}`
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
eventTarget.addEventListener("hello", (e) => {
|
||||||
|
eventTarget.dispatchEvent(new CustomEvent("send", {
|
||||||
|
detail: {
|
||||||
|
type: "rooms"
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
<Sidebar.Root {...restProps} bind:ref variant="floating">
|
<Sidebar.Root {...restProps} bind:ref variant="floating">
|
||||||
<Sidebar.Header>
|
<Sidebar.Header>
|
||||||
|
|||||||
@@ -1,43 +1,14 @@
|
|||||||
<script lang="ts">
|
<script>
|
||||||
import { goto } from "$app/navigation";
|
import { sidebarState } from "$lib/sidebar.svelte";
|
||||||
import { eventTarget } from "$lib/ws.svelte";
|
import { onDestroy, onMount } from "svelte";
|
||||||
import { onMount } from "svelte";
|
|
||||||
|
|
||||||
let noRoom = $state(false);
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// ws.addEventListener("message", (e) => {
|
sidebarState.open = true;
|
||||||
// const msg = JSON.parse(e.data);
|
})
|
||||||
// if(msg.type === "my-room") {
|
|
||||||
// goto(`/room/${msg.room}`);
|
onDestroy(() => {
|
||||||
// }
|
sidebarState.open = false;
|
||||||
// });
|
|
||||||
// ws.addEventListener("open", () => {
|
|
||||||
// ws.send(JSON.stringify({ type: "my-room" }));
|
|
||||||
// });
|
|
||||||
eventTarget.addEventListener("my-room", (e) => {
|
|
||||||
console.log("Received my-room event", e);
|
|
||||||
const ev = e as CustomEvent;
|
|
||||||
// if(ev.detail.room == "entry") {
|
|
||||||
// goto(`/entry`);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
goto(`/room/${ev.detail.room}`);
|
|
||||||
});
|
|
||||||
eventTarget.addEventListener("error", (e) => {
|
|
||||||
const ev = e as CustomEvent;
|
|
||||||
if(ev.detail.code === -1) {
|
|
||||||
noRoom = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
eventTarget.addEventListener("hello", (e) => {
|
|
||||||
eventTarget.dispatchEvent(new CustomEvent("send", { detail: { type: "my-room" } }));
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1>Willkommen zu NextUp!</h1>
|
<h1>Willkommen zu NextUp!</h1>
|
||||||
<span>Sie werden zu Ihrem Raum weitergeleitet.</span>
|
|
||||||
{#if noRoom}
|
|
||||||
<span class="text-red-500">Kein Raum verfügbar</span>
|
|
||||||
{/if}
|
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let withSound = false;
|
let withSound = false;
|
||||||
|
let callAudioURI = "";
|
||||||
let persisted: PersistedState<CallEntry[]> = new PersistedState("calls", []);
|
let persisted: PersistedState<CallEntry[]> = new PersistedState("calls", []);
|
||||||
let calls = $state<CallEntry[]>($state.snapshot(persisted.current));
|
let calls = $state<CallEntry[]>($state.snapshot(persisted.current));
|
||||||
let newCalls = $state<CallEntry[]>([]);
|
let newCalls = $state<CallEntry[]>([]);
|
||||||
@@ -24,6 +25,13 @@
|
|||||||
withSound = location.search.includes("sound=true");
|
withSound = location.search.includes("sound=true");
|
||||||
eventTarget.addEventListener("display", (e) => {
|
eventTarget.addEventListener("display", (e) => {
|
||||||
const detail = (e as CustomEvent).detail;
|
const detail = (e as CustomEvent).detail;
|
||||||
|
if(detail.motd) {
|
||||||
|
const [speed, ...text] = detail.motd.split(";");
|
||||||
|
marqueeText = text.join(" ");
|
||||||
|
marqueeSpeed = parseInt(speed);
|
||||||
|
console.log(detail.motd, marqueeSpeed, marqueeText);
|
||||||
|
updateMarqueeSpeed();
|
||||||
|
}
|
||||||
const _newCalls = detail.tickets.filter(
|
const _newCalls = detail.tickets.filter(
|
||||||
(call: CallEntry) => !calls.find((c) => c.num === call.num)
|
(call: CallEntry) => !calls.find((c) => c.num === call.num)
|
||||||
);
|
);
|
||||||
@@ -31,8 +39,8 @@
|
|||||||
persisted.current = calls;
|
persisted.current = calls;
|
||||||
connected = true;
|
connected = true;
|
||||||
if (!firstLoad) {
|
if (!firstLoad) {
|
||||||
if (_newCalls.length > 0 && withSound) {
|
if (_newCalls.length > 0 && withSound && callAudioURI) {
|
||||||
const audio = new Audio("/call.wav");
|
const audio = new Audio(callAudioURI);
|
||||||
audio.play();
|
audio.play();
|
||||||
}
|
}
|
||||||
newCalls.push(..._newCalls);
|
newCalls.push(..._newCalls);
|
||||||
@@ -46,11 +54,33 @@
|
|||||||
eventTarget.dispatchEvent(
|
eventTarget.dispatchEvent(
|
||||||
new CustomEvent("send", { detail: { type: "display" } })
|
new CustomEvent("send", { detail: { type: "display" } })
|
||||||
);
|
);
|
||||||
|
if(withSound) {
|
||||||
|
eventTarget.addEventListener("call-audio", (e) => {
|
||||||
|
const detail = (e as CustomEvent).detail;
|
||||||
|
callAudioURI = detail.dataUri;
|
||||||
});
|
});
|
||||||
|
eventTarget.dispatchEvent(
|
||||||
|
new CustomEvent("send", { detail: { type: "call-audio" } })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateMarqueeSpeed() {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const marqueeContent = document.querySelectorAll(".marquee-content") as NodeListOf<HTMLElement>;
|
||||||
|
const speed = marqueeSpeed; // px per second
|
||||||
|
const width = marqueeContent[0].offsetWidth;
|
||||||
|
marqueeContent[0].style.animationDuration = `${width / speed}s`;
|
||||||
|
marqueeContent[1].style.animationDuration = `${width / speed}s`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const ENTRIES: Record<string, number> = { A: 1, B: 2, C: 1, D: 2, E: 1 };
|
const ENTRIES: Record<string, number> = { A: 1, B: 2, C: 1, D: 2, E: 1 };
|
||||||
|
|
||||||
const ENTRIES_PER_TABLE = 10;
|
const ENTRIES_PER_TABLE = 10;
|
||||||
|
|
||||||
|
let marqueeSpeed = 50;
|
||||||
|
let marqueeText = $state("");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if newCalls.length > 0}
|
{#if newCalls.length > 0}
|
||||||
@@ -59,15 +89,15 @@
|
|||||||
transition:fade
|
transition:fade
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-9xl font-bold bg-background text-foreground p-4 rounded-md flex flex-col gap-10"
|
class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-[12rem] font-bold bg-background text-foreground p-4 rounded-md flex flex-col gap-10"
|
||||||
>
|
>
|
||||||
{#each newCalls as call (call.num)}
|
{#each newCalls as call (call.num)}
|
||||||
<span class="mx-4 flex items-center gap-10">
|
<span class="mx-4 flex items-center gap-10">
|
||||||
{call.num}
|
{call.num}
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="80"
|
width="120"
|
||||||
height="80"
|
height="120"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -84,7 +114,11 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="p-4 flex justify-around gap-4 text-5xl">
|
<div class="marquee bg-yellow-300 text-black">
|
||||||
|
<div class="marquee-content">{marqueeText}</div>
|
||||||
|
<div class="marquee-content">{marqueeText}</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 flex justify-around gap-4 text-7xl">
|
||||||
<!-- <h1 class="text-6xl font-bold">Aufruf</h1> -->
|
<!-- <h1 class="text-6xl font-bold">Aufruf</h1> -->
|
||||||
<table class="self-start">
|
<table class="self-start">
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -136,7 +170,7 @@
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="fixed h-full w-1 top-0 left-1/2 -translate-x-1/2 {wsState.closed
|
class="fixed h-full w-1 top-30 left-1/2 -translate-x-1/2 {wsState.closed
|
||||||
? 'bg-red-800'
|
? 'bg-red-800'
|
||||||
: !wsState.connected
|
: !wsState.connected
|
||||||
? 'bg-amber-800'
|
? 'bg-amber-800'
|
||||||
@@ -239,4 +273,28 @@
|
|||||||
background-color: var(--accent);
|
background-color: var(--accent);
|
||||||
border: 2px solid var(--background);
|
border: 2px solid var(--background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.marquee {
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100vw;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 4.5rem;
|
||||||
|
gap: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marquee-content {
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-name: marquee;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes marquee {
|
||||||
|
from {
|
||||||
|
transform: translateX(0%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user