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 { 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 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();
|
||||
}
|
||||
}
|
||||
const MOTD = process.env.MOTD || "50;";
|
||||
|
||||
export let displaySockets: ServerWebSocket<unknown>[] = [];
|
||||
|
||||
@@ -34,22 +28,7 @@ Bun.serve({
|
||||
async message(ws, message) {
|
||||
console.log("Received message:", message);
|
||||
const data = JSON.parse(message.toString());
|
||||
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 }));
|
||||
if (data.type === "hello") {
|
||||
} else if (data.type === "call-ticket") {
|
||||
// const ticket = await getTicket(data.ticket);
|
||||
// if(!ticket || ticket.status == "completed" || ticket.status == "no-show") {
|
||||
@@ -111,8 +90,16 @@ Bun.serve({
|
||||
if(!displaySockets.includes(ws)) {
|
||||
displaySockets.push(ws);
|
||||
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
|
||||
open(ws) {
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
import SearchForm from "./search-form.svelte";
|
||||
import VersionSwitcher from "./switcher.svelte";
|
||||
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 { eventTarget } from "./ws.svelte";
|
||||
let { ref = $bindable(null), ...restProps }: ComponentProps<typeof Sidebar.Root> = $props();
|
||||
|
||||
type NavData = {
|
||||
@@ -36,6 +37,10 @@
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Laden...",
|
||||
url: "#"
|
||||
},
|
||||
/*{
|
||||
title: "1",
|
||||
url: "/room/1",
|
||||
},
|
||||
@@ -74,7 +79,7 @@
|
||||
{
|
||||
title: "Empfang 2",
|
||||
url: "/room/Empfang%202",
|
||||
},
|
||||
},*/
|
||||
],
|
||||
},
|
||||
// {
|
||||
@@ -103,10 +108,10 @@
|
||||
title: "Logs",
|
||||
url: "/log",
|
||||
},
|
||||
{
|
||||
/*{
|
||||
title: "Admin",
|
||||
url: "/admin",
|
||||
},
|
||||
},*/
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -136,17 +141,37 @@
|
||||
title: "Logs",
|
||||
url: "/log",
|
||||
},
|
||||
{
|
||||
/*{
|
||||
title: "Admin",
|
||||
url: "/",
|
||||
isActive: true
|
||||
},
|
||||
},*/
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
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>
|
||||
<Sidebar.Root {...restProps} bind:ref variant="floating">
|
||||
<Sidebar.Header>
|
||||
|
||||
@@ -1,43 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { eventTarget } from "$lib/ws.svelte";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let noRoom = $state(false);
|
||||
<script>
|
||||
import { sidebarState } from "$lib/sidebar.svelte";
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
|
||||
onMount(() => {
|
||||
// ws.addEventListener("message", (e) => {
|
||||
// const msg = JSON.parse(e.data);
|
||||
// if(msg.type === "my-room") {
|
||||
// goto(`/room/${msg.room}`);
|
||||
// }
|
||||
// });
|
||||
// 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" } }));
|
||||
});
|
||||
sidebarState.open = true;
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
sidebarState.open = false;
|
||||
})
|
||||
</script>
|
||||
|
||||
<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 callAudioURI = "";
|
||||
let persisted: PersistedState<CallEntry[]> = new PersistedState("calls", []);
|
||||
let calls = $state<CallEntry[]>($state.snapshot(persisted.current));
|
||||
let newCalls = $state<CallEntry[]>([]);
|
||||
@@ -24,6 +25,13 @@
|
||||
withSound = location.search.includes("sound=true");
|
||||
eventTarget.addEventListener("display", (e) => {
|
||||
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(
|
||||
(call: CallEntry) => !calls.find((c) => c.num === call.num)
|
||||
);
|
||||
@@ -31,8 +39,8 @@
|
||||
persisted.current = calls;
|
||||
connected = true;
|
||||
if (!firstLoad) {
|
||||
if (_newCalls.length > 0 && withSound) {
|
||||
const audio = new Audio("/call.wav");
|
||||
if (_newCalls.length > 0 && withSound && callAudioURI) {
|
||||
const audio = new Audio(callAudioURI);
|
||||
audio.play();
|
||||
}
|
||||
newCalls.push(..._newCalls);
|
||||
@@ -46,11 +54,33 @@
|
||||
eventTarget.dispatchEvent(
|
||||
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_PER_TABLE = 10;
|
||||
|
||||
let marqueeSpeed = 50;
|
||||
let marqueeText = $state("");
|
||||
</script>
|
||||
|
||||
{#if newCalls.length > 0}
|
||||
@@ -59,15 +89,15 @@
|
||||
transition:fade
|
||||
>
|
||||
<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)}
|
||||
<span class="mx-4 flex items-center gap-10">
|
||||
{call.num}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="80"
|
||||
height="80"
|
||||
width="120"
|
||||
height="120"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
@@ -84,7 +114,11 @@
|
||||
</div>
|
||||
{/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> -->
|
||||
<table class="self-start">
|
||||
<tbody>
|
||||
@@ -136,7 +170,7 @@
|
||||
</table>
|
||||
|
||||
<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'
|
||||
: !wsState.connected
|
||||
? 'bg-amber-800'
|
||||
@@ -239,4 +273,28 @@
|
||||
background-color: var(--accent);
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user