Phase 12 Hook-System (hooks.rs + HooksPanel):
- HookManager mit Event-Registry + Ausfuehrungs-Log
- 5 Built-in Hooks (SessionStart, PreToolUse, PostToolUse,
BeforeCompacting, AfterCompacting)
- Tauri-Commands: list_hooks, set_hook_enabled, get_hook_executions, fire_hook
- HooksPanel.svelte mit Live-Ausfuehrungs-Log
Phase 13 VSCodium-Integration:
- vscode-extension/: WebSocket-Server auf Port 7890
(Commands: openFile, goToLine, formatDocument, findInFiles,
openTerminal, getStatus, executeCommand, ping)
- src-tauri/src/ide.rs: WebSocket-Client via tokio-tungstenite
- IdePanel.svelte: Status, Port-Konfig, Ping-Test, Live-Anzeige aktive Datei
Phase 14 Programm-Steuerung (programs.rs + ProgramsPanel):
- D-Bus: dbus_call + dbus_list_services
- Xvfb: start/stop/status + screenshot (scrot)
- Playwright-Info (MCP-Verweis)
- ProgramsPanel mit 4 Sektionen (VSCodium, Playwright, D-Bus, Xvfb)
Phase 15 Schulungsmodus (teaching.rs + presentation/+page.svelte):
- Separates Tauri-Webview-Fenster
- MermaidDiagram.svelte (dynamic import mermaid)
- AnimatedCode.svelte mit WPM-Steuerung
- Tauri-Commands: presentation_open/close/send_slide/clear
- 🎓-Button in der Titelbar
- Capabilities um core:webview:allow-create-webview-window erweitert
Deps:
- Cargo: +tokio-tungstenite 0.23, +futures-util 0.3
- npm: +mermaid ^11.4.0 (npm install erforderlich)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
153 lines
2.9 KiB
Svelte
153 lines
2.9 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
|
|
interface Props {
|
|
code: string;
|
|
language?: string;
|
|
wpm?: number;
|
|
autoStart?: boolean;
|
|
}
|
|
|
|
let { code, language = 'text', wpm = 180, autoStart = true }: Props = $props();
|
|
|
|
let displayed = $state('');
|
|
let playing = $state(false);
|
|
let index = $state(0);
|
|
let timer: number | null = null;
|
|
|
|
function charDelay(): number {
|
|
// ~5 Zeichen pro Wort
|
|
return 60000 / (wpm * 5);
|
|
}
|
|
|
|
function play() {
|
|
if (playing) return;
|
|
playing = true;
|
|
step();
|
|
}
|
|
|
|
function pause() {
|
|
playing = false;
|
|
if (timer !== null) {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
}
|
|
|
|
function reset() {
|
|
pause();
|
|
displayed = '';
|
|
index = 0;
|
|
}
|
|
|
|
function step() {
|
|
if (!playing || index >= code.length) {
|
|
playing = false;
|
|
return;
|
|
}
|
|
displayed += code[index];
|
|
index++;
|
|
timer = window.setTimeout(step, charDelay());
|
|
}
|
|
|
|
function skipToEnd() {
|
|
pause();
|
|
displayed = code;
|
|
index = code.length;
|
|
}
|
|
|
|
onMount(() => {
|
|
if (autoStart) play();
|
|
return () => {
|
|
if (timer !== null) clearTimeout(timer);
|
|
};
|
|
});
|
|
</script>
|
|
|
|
<div class="animated-code">
|
|
<div class="toolbar">
|
|
<span class="lang">{language}</span>
|
|
<div class="controls">
|
|
{#if playing}
|
|
<button onclick={pause} title="Pause">⏸</button>
|
|
{:else}
|
|
<button onclick={play} title="Abspielen">▶</button>
|
|
{/if}
|
|
<button onclick={reset} title="Zurücksetzen">↺</button>
|
|
<button onclick={skipToEnd} title="Zum Ende">⏭</button>
|
|
<span class="speed">{wpm} WPM</span>
|
|
</div>
|
|
</div>
|
|
<pre class="code-block"><code>{displayed}{#if playing}<span class="cursor">|</span>{/if}</code></pre>
|
|
</div>
|
|
|
|
<style>
|
|
.animated-code {
|
|
background: var(--bg-secondary);
|
|
border-radius: var(--radius-sm);
|
|
overflow: hidden;
|
|
border: 1px solid var(--bg-tertiary);
|
|
}
|
|
|
|
.toolbar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: var(--spacing-xs) var(--spacing-sm);
|
|
background: var(--bg-tertiary);
|
|
font-size: 0.78rem;
|
|
}
|
|
|
|
.lang {
|
|
color: var(--accent);
|
|
font-family: monospace;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.controls {
|
|
display: flex;
|
|
gap: var(--spacing-xs);
|
|
align-items: center;
|
|
}
|
|
|
|
.controls button {
|
|
background: transparent;
|
|
border: 1px solid var(--bg-primary);
|
|
color: var(--text-primary);
|
|
padding: 2px 8px;
|
|
cursor: pointer;
|
|
border-radius: var(--radius-sm);
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.controls button:hover {
|
|
background: var(--bg-primary);
|
|
}
|
|
|
|
.speed {
|
|
color: var(--text-secondary);
|
|
font-size: 0.72rem;
|
|
margin-left: var(--spacing-xs);
|
|
}
|
|
|
|
.code-block {
|
|
margin: 0;
|
|
padding: var(--spacing-md);
|
|
font-family: 'JetBrains Mono', 'Cascadia Code', monospace;
|
|
font-size: 0.85rem;
|
|
line-height: 1.5;
|
|
white-space: pre-wrap;
|
|
overflow-x: auto;
|
|
min-height: 60px;
|
|
max-height: 400px;
|
|
}
|
|
|
|
.cursor {
|
|
animation: blink 1s step-end infinite;
|
|
color: var(--accent);
|
|
}
|
|
|
|
@keyframes blink {
|
|
50% { opacity: 0; }
|
|
}
|
|
</style>
|