vor ble umbau

This commit is contained in:
2026-03-12 07:07:00 +01:00
parent 96aed70fc6
commit 5bb0d345da
45 changed files with 3681 additions and 48 deletions

View File

@@ -0,0 +1,61 @@
<script lang="ts">
import { onMount } from "svelte";
import { isInitializing, isBluetoothSupported, isSerialSupported } from "../lib/store";
import { performHardwareCheck, getBrowserName } from "../lib/init";
import ToastContainer from "./ToastContainer.svelte";
import { injectDummyDevices } from "../lib/store";
let browserName = "";
onMount(() => {
browserName = getBrowserName();
performHardwareCheck();
injectDummyDevices(); // Fügt Dummy-Geräte für Testzwecke hinzu
});
</script>
{#if $isInitializing}
<div class="fixed inset-0 bg-slate-50 flex items-center justify-center z-[100]">
<p class="text-slate-600 font-mono animate-pulse">SYSTEM_CHECK_RUNNING...</p>
</div>
{:else if !$isBluetoothSupported && !$isSerialSupported}
<div class="min-h-[60vh] flex items-center justify-center p-4">
<div class="max-w-md w-full bg-white border-2 border-red-600 shadow-2xl rounded-sm p-8 pb-4">
<h1 class="text-2xl font-black text-red-600 mb-4 uppercase italic">Inkompatibler Browser</h1>
<p class="text-slate-800 mb-6">
Du nutzt aktuell <strong>{browserName}</strong>
. Dieser Browser unterstützt weder Bluetooth noch serielle USB-Verbindungen.
</p>
<div class="space-y-2 mb-4 text-sm font-mono">
<div class="flex justify-between border-b border-slate-100 pb-1">
<span>Web Bluetooth:</span>
<span class="text-red-600 font-bold">NICHT UNTERSTÜTZT</span>
</div>
<div class="flex justify-between border-b border-slate-100 pb-1">
<span>Web Serial:</span>
<span class="text-red-600 font-bold">NICHT UNTERSTÜTZT</span>
</div>
</div>
<p class="text-xs text-slate-500 mb-6 italic">
(Info: Firefox und Safari blockieren diese Hardware-Schnittstellen aus Prinzip.)
</p>
<a
href="https://www.google.com/chrome/"
class="block w-full text-center bg-blue-600 text-white py-3 font-bold hover:bg-blue-700 transition uppercase tracking-widest text-sm mb-4"
>
Googles Glanzeisen installieren
</a>
<p class="text-xs text-slate-500 mb-6 italic">
Gerüchten zufolge soll <b>Winzigweichs Kante</b>
-Browser diese Technologien auch unterstützen. Aber wer nutzt schon diese Weichware?
</p>
</div>
</div>
{:else}
<ToastContainer client:load />
<slot />
{/if}

View File

@@ -0,0 +1,22 @@
<script lang="ts">
import BLEListItem from "./BLEListItem.svelte";
import { pairedDevices } from "../lib/store";
</script>
<div class="bg-white shadow shadow-slate-300 rounded-lg border border-slate-100 overflow-hidden">
<div class="p-6 pb-4 border-b border-slate-100">
<h2 class="text-xl font-bold text-slate-800">Verfügbare Geräte</h2>
</div>
<div class="flex flex-col">
{#if $pairedDevices.length > 0}
{#each $pairedDevices as device (device.id)}
<BLEListItem {device} />
{/each}
{:else}
<div class="px-6 py-8 text-center text-slate-500 text-sm">
Keine gepairten Geräte gefunden. Bitte pairen Sie zunächst ein Gerät.
</div>
{/if}
</div>
</div>

View File

@@ -0,0 +1,55 @@
<script lang="ts">
import { BluetoothIcon, BluetoothSlashIcon, BluetoothXIcon } from "phosphor-svelte";
import { connectBuzzer, forgetDevice } from "../lib/bluetooth";
import { availableDevices, activeDeviceId, isConnected, targetDeviceId } from "../lib/store"; // targetDeviceId importiert
export let device: BluetoothDevice;
$: isAvailable = $availableDevices.has(device.id);
$: isActive = $activeDeviceId === device.id;
$: isTarget = $targetDeviceId === device.id;
$: showBlueBorder = isActive || (isTarget && !$isConnected);
</script>
<div
class="flex items-center border-b border-slate-200/50 last:border-b-0 transition-colors border-l-4
{showBlueBorder ? 'border-l-blue-600' : 'border-l-transparent'}
{isActive ? 'bg-blue-100' : isAvailable ? 'bg-blue-50 hover:bg-blue-100' : 'bg-white hover:bg-slate-50'}"
>
<div
class="flex-1 px-6 py-3 flex items-center cursor-pointer {isAvailable || isActive ? 'opacity-100' : 'opacity-50'}"
on:click={() => { if (isAvailable && !isActive) connectBuzzer(device); }}
>
{#if isAvailable || isActive}
<BluetoothIcon class="text-blue-600 mr-3 w-5 h-5" />
{:else}
<BluetoothSlashIcon class="text-slate-400 mr-3 w-5 h-5" />
{/if}
<div class="flex flex-col">
<span
class="font-medium {isActive
? 'text-blue-900'
: isAvailable
? 'text-blue-800'
: 'text-slate-500'}"
>
{device.name || "Unbekanntes Gerät"}
</span>
{#if isActive}
<span class="text-xs text-blue-700 font-semibold">Verbunden</span>
{:else if !isAvailable}
<span class="text-xs text-slate-400">Nicht in Reichweite</span>
{/if}
</div>
</div>
<button
class="px-4 h-full flex items-center text-slate-300 hover:text-red-500 transition-colors"
on:click|stopPropagation={() => forgetDevice(device)}
title="Gerät entkoppeln"
>
<BluetoothXIcon class="w-5 h-5" />
</button>
</div>

View File

@@ -0,0 +1,137 @@
<script lang="ts">
import { onMount } from "svelte";
import { pairBuzzer, connectBuzzer, disconnectBuzzer, restoreSession } from "../lib/bluetooth";
import {
isConnected,
isPaired,
isConnecting,
protocolInfo,
fsInfo,
loadConnectionState,
availableDevices,
} from "../lib/store";
import { refreshRemote } from "../lib/sync";
import { fetchFileThroughputTest } from "../lib/transport";
onMount(() => {
restoreSession();
});
// Automatischer Datenabruf, sobald der Transport-Layer isConnected auf true setzt
$: if ($isConnected) {
refreshRemote();
}
$: lastDeviceId = loadConnectionState()?.deviceId;
$: canQuickConnect = lastDeviceId ? $availableDevices.has(lastDeviceId) : false;
</script>
<div class="bg-white shadow shadow-slate-300 rounded-lg p-6 border border-slate-100">
<h2 class="text-xl font-bold mb-4 text-slate-800">Geräte-Status</h2>
<div
class="flex items-center gap-2 font-semibold mb-5 {$isConnected
? 'text-green-600'
: 'text-slate-500'}"
>
<div
class="w-2.5 h-2.5 rounded-full {$isConnected
? 'bg-green-500 animate-pulse'
: 'bg-slate-400'}"
></div>
{$isConnected ? "Bluetooth Verbunden" : "Bluetooth Getrennt"}
</div>
<div
class="bg-slate-50 border border-slate-200 p-4 rounded text-sm font-mono text-slate-600 mb-4 flex flex-col justify-center"
>
<div class="flex justify-between mb-1">
<span>Protokoll Version:</span>
<span class="font-semibold text-slate-800">
{$protocolInfo ? "v" + $protocolInfo.version : "unbekannt"}
</span>
</div>
<div class="flex justify-between">
<span>Max Chunk Size:</span>
<span class="font-semibold text-slate-800">
{$protocolInfo ? $protocolInfo.maxChunkSize + " B" : "unbekannt"}
</span>
</div>
<div class="flex justify-between">
<span>Flash-Grösse:</span>
<span class="font-semibold text-slate-800">
{$fsInfo ? $fsInfo.totalSize.toFixed(2) + " MB" : "unbekannt"}
</span>
</div>
<div class="flex justify-between">
<span>Freier Speicher:</span>
<span class="font-semibold text-slate-800">
{$fsInfo ? $fsInfo.freeSize.toFixed(2) + " MB" : "unbekannt"}
</span>
</div>
<div class="flex justify-between">
<span>Max. Pfadlänge:</span>
<span class="font-semibold text-slate-800">
{$fsInfo ? $fsInfo.maxPathLength : "unbekannt"}
</span>
</div>
<div class="flex justify-between">
<span>Systempfad:</span>
<span class="font-semibold text-slate-800">
{$fsInfo ? $fsInfo.sysPath : "unbekannt"}
</span>
</div>
<div class="flex justify-between">
<span>Audiopfad:</span>
<span class="font-semibold text-slate-800">
{$fsInfo ? $fsInfo.audioPath : "unbekannt"}
</span>
</div>
</div>
<div class="flex gap-3 mt-4">
{#if !$isConnected}
<button
class="bg-blue-600 text-white px-4 py-2.5 rounded shadow-sm hover:bg-blue-700 hover:shadow transition text-sm font-medium flex-1 disabled:opacity-50 disabled:cursor-not-allowed"
on:click={() => pairBuzzer()}
disabled={$isConnecting}
>
Pairen
</button>
<button
class="px-4 py-2.5 rounded transition text-sm font-medium flex-1
{$isPaired && canQuickConnect
? 'bg-emerald-600 text-white shadow-sm hover:bg-emerald-700 hover:shadow'
: 'bg-slate-50 text-slate-400 cursor-not-allowed opacity-70'}
{$isConnecting ? 'animate-pulse' : ''}"
on:click={() => connectBuzzer()}
disabled={!$isPaired || !canQuickConnect || $isConnecting}
>
{$isConnecting ? "Verbinde..." : "Verbinden"}
</button>
{:else}
<button
class="bg-slate-100 text-slate-700 px-4 py-2.5 rounded hover:bg-slate-200 transition text-sm font-medium flex-1"
on:click={() => refreshRemote()}
>
Werte neu laden
</button>
<button
class="bg-red-600 text-white px-4 py-2.5 rounded shadow-sm hover:bg-red-700 hover:shadow transition text-sm font-medium flex-1"
on:click={() => disconnectBuzzer()}
>
Trennen
</button>
{/if}
</div>
<button
class="mt-4 w-full text-left text-xs text-slate-500 hover:text-slate-700 transition"
on:click={() => {
fetchFileThroughputTest("/lfs/a/countdown");
}}
>
Durchsatztest mit /lfs/a/countdown
</button>
</div>

View File

@@ -0,0 +1,27 @@
<script lang="ts">
import { storageUsage } from "../lib/store";
</script>
<div class="mt-4 w-full h-3 bg-slate-100 rounded-sm overflow-hidden flex shadow-inner">
<div
class="h-full bg-slate-400 transition-all duration-500"
style="width: {$storageUsage?.systemPercent ?? 0}%"
></div>
<div class="mt-1 text-xs text-slate-400 flex justify-between uppercase tracking-tighter font-medium">
{#if $storageUsage}
<div class="flex gap-4">
<div>
<span class="font-semibold text-slate-400">
Rate: {($storageUsage.systemBytes / 1048576).toFixed(2)} MB
</span>
</div>
<div>
<span class="font-semibold text-slate-400">
{($storageUsage.freeBytes / 1048576).toFixed(2)} Sekunden
</span>
</div>
{:else}
<div class="text-slate-400">Kein Transfer aktiv</div>
{/if}
</div>

View File

@@ -0,0 +1,42 @@
<script lang="ts">
import { storageUsage } from "../lib/store";
</script>
<div class="mt-4 w-full h-3 bg-slate-100 rounded-sm overflow-hidden flex shadow-inner">
<div
class="h-full bg-slate-400 transition-all duration-500"
style="width: {$storageUsage?.systemPercent ?? 0}%"
></div>
<div
class="h-full bg-indigo-500 transition-all duration-500"
style="width: {$storageUsage?.audioPercent ?? 0}%"
></div>
<div
class="h-full bg-emerald-500 transition-all duration-500"
style="width: {$storageUsage?.freePercent ?? 0}%"
></div>
</div>
<div class="mt-1 text-xs text-slate-400 flex justify-between uppercase tracking-tighter font-medium">
{#if $storageUsage}
<div class="flex gap-4">
<div>
<span class="font-semibold text-slate-400">System:
{($storageUsage.systemBytes / 1048576).toFixed(2)} MB</span>
</div>
<div>
<span class="font-semibold text-indigo-500">Audio:
{($storageUsage.audioBytes / 1048576).toFixed(2)} MB</span>
</div>
</div>
<div>
<span class="font-semibold text-emerald-500">Frei:
{($storageUsage.freeBytes / 1048576).toFixed(2)} MB</span>
</div>
{:else}
<div class="text-slate-400">Speicherdaten nicht verfügbar</div>
{/if}
</div>

View File

@@ -0,0 +1,33 @@
<script lang="ts">
import { toasts, removeToast } from "../lib/toast";
import { XIcon } from "phosphor-svelte";
import { fly } from "svelte/transition"; // Import für DOM-Animationen hinzugefügt
// Debug-Ausgabe: Zeigt in der Konsole an, sobald ein Toast getriggert wird
$: console.debug("Aktuelle Toasts im Store:", $toasts);
</script>
<div class="fixed bottom-20 right-6 z-[9999] flex flex-col gap-3 pointer-events-none">
{#each $toasts as toast (toast.id)}
<div
in:fly={{ y: 20, duration: 300 }}
out:fly={{ y: -20, duration: 300 }}
class="pointer-events-auto flex items-center justify-between px-5 py-3 rounded-lg border-l-4 shadow-xl min-w-[280px]
{toast.type === 'success' ? 'bg-green-100/50 border-green-500 text-green-800' : ''}
{toast.type === 'info' ? 'bg-blue-100/50 border-blue-500 text-blue-800' : ''}
{toast.type === 'warning' ? 'bg-amber-100/50 border-amber-500 text-amber-800' : ''}
{toast.type === 'error' ? 'bg-red-100/50 border-red-500 text-red-800' : ''}
"
>
<span class="text-sm font-medium">{@html toast.message}</span>
{#if toast.dismissible}
<button
on:click={() => removeToast(toast.id)}
class="ml-4 opacity-50 hover:opacity-100 transition cursor-pointer"
>
<XIcon class="w-5 h-5" />
</button>
{/if}
</div>
{/each}
</div>