vor ble umbau
This commit is contained in:
61
webpage/src/components/AppGuard.svelte
Normal file
61
webpage/src/components/AppGuard.svelte
Normal 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}
|
||||
22
webpage/src/components/BLEList.svelte
Normal file
22
webpage/src/components/BLEList.svelte
Normal 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>
|
||||
55
webpage/src/components/BLEListItem.svelte
Normal file
55
webpage/src/components/BLEListItem.svelte
Normal 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>
|
||||
137
webpage/src/components/BuzzerControl.svelte
Normal file
137
webpage/src/components/BuzzerControl.svelte
Normal 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>
|
||||
27
webpage/src/components/FileTransfer.svelte
Normal file
27
webpage/src/components/FileTransfer.svelte
Normal 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>
|
||||
42
webpage/src/components/FlashUsage.svelte
Normal file
42
webpage/src/components/FlashUsage.svelte
Normal 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>
|
||||
33
webpage/src/components/ToastContainer.svelte
Normal file
33
webpage/src/components/ToastContainer.svelte
Normal 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>
|
||||
Reference in New Issue
Block a user