diff --git a/src/components/charts/KeyboardHeatmap.ts b/src/components/charts/KeyboardHeatmap.ts index 41b9d11..a8f522c 100644 --- a/src/components/charts/KeyboardHeatmap.ts +++ b/src/components/charts/KeyboardHeatmap.ts @@ -235,7 +235,7 @@ export class KeyboardHeatmap { ">

- 🔴 Verbesserungsbedarf + Verbesserungsbedarf

${worstKeys .map( @@ -263,7 +263,7 @@ export class KeyboardHeatmap {

- 🟢 Starke Tasten + Starke Tasten

${bestKeys .map( diff --git a/src/components/typing-area/TypingArea.ts b/src/components/typing-area/TypingArea.ts index 4b56db8..c010bec 100644 --- a/src/components/typing-area/TypingArea.ts +++ b/src/components/typing-area/TypingArea.ts @@ -46,6 +46,40 @@ export class TypingArea { } } + /** + * Handle backspace - allow correction of mistakes + * Returns true if backspace was processed, false if at start + */ + handleBackspace(): boolean { + if (this.currentPosition === 0) { + return false; + } + + // Move position back + this.currentPosition--; + + // Reset the character at the current position + const charEl = this.charElements[this.currentPosition]; + if (charEl) { + charEl.classList.remove('correct', 'incorrect', 'upcoming'); + charEl.classList.add('current'); + } + + // Reset the next character to upcoming (if exists) + if (this.currentPosition + 1 < this.charElements.length) { + const nextCharEl = this.charElements[this.currentPosition + 1]; + if (nextCharEl) { + nextCharEl.classList.remove('current'); + nextCharEl.classList.add('upcoming'); + } + } + + // Emit backspace event + EventBus.emit('typing:backspace', { position: this.currentPosition }); + + return true; + } + /** * Process a keystroke */ diff --git a/src/components/visualization/AlgorithmVisualizer.ts b/src/components/visualization/AlgorithmVisualizer.ts new file mode 100644 index 0000000..fd33f44 --- /dev/null +++ b/src/components/visualization/AlgorithmVisualizer.ts @@ -0,0 +1,1227 @@ +/** + * Algorithm Visualizer Component + * Beautiful step-by-step visualizations for algorithms + */ + +export type VisualizationState = { + array: number[]; + comparing: number[]; + swapping: number[]; + sorted: number[]; + pivot?: number; + current?: number; + left?: number; + right?: number; + mid?: number; + found?: number; + message: string; +}; + +export type AlgorithmStep = { + state: VisualizationState; + code: string; + lineHighlight: number; +}; + +/** + * Algorithm Visualizer - Creates beautiful animations + */ +export class AlgorithmVisualizer { + private readonly containerId: string; + private steps: AlgorithmStep[] = []; + private currentStep: number = 0; + private isPlaying: boolean = false; + private speed: number = 500; // ms + private animationFrame: number | null = null; + + constructor(containerId: string) { + this.containerId = containerId; + } + + /** + * Generate Bubble Sort steps + */ + static generateBubbleSortSteps(arr: number[]): AlgorithmStep[] { + const steps: AlgorithmStep[] = []; + const array = [...arr]; + const n = array.length; + const sorted: number[] = []; + + const code = `function bubbleSort(arr) { + for (let i = 0; i < n - 1; i++) { + for (let j = 0; j < n - i - 1; j++) { + if (arr[j] > arr[j + 1]) { + swap(arr[j], arr[j + 1]); + } + } + } +}`; + + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [], + sorted: [], + message: 'Start Bubble Sort', + }, + code, + lineHighlight: 1, + }); + + for (let i = 0; i < n - 1; i++) { + for (let j = 0; j < n - i - 1; j++) { + // Comparing + steps.push({ + state: { + array: [...array], + comparing: [j, j + 1], + swapping: [], + sorted: [...sorted], + message: `Vergleiche ${array[j]} und ${array[j + 1]}`, + }, + code, + lineHighlight: 3, + }); + + if (array[j] > array[j + 1]) { + // Swapping + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [j, j + 1], + sorted: [...sorted], + message: `Tausche ${array[j]} und ${array[j + 1]}`, + }, + code, + lineHighlight: 4, + }); + + [array[j], array[j + 1]] = [array[j + 1], array[j]]; + + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [], + sorted: [...sorted], + message: `Getauscht!`, + }, + code, + lineHighlight: 4, + }); + } + } + sorted.unshift(n - 1 - i); + } + + sorted.unshift(0); + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [], + sorted: [...sorted], + message: 'Array sortiert!', + }, + code, + lineHighlight: 7, + }); + + return steps; + } + + /** + * Generate Quick Sort steps + */ + static generateQuickSortSteps(arr: number[]): AlgorithmStep[] { + const steps: AlgorithmStep[] = []; + const array = [...arr]; + const sorted: number[] = []; + + const code = `function quickSort(arr, low, high) { + if (low < high) { + const pivot = partition(arr, low, high); + quickSort(arr, low, pivot - 1); + quickSort(arr, pivot + 1, high); + } +}`; + + function partition(low: number, high: number): number { + const pivotValue = array[high]; + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [], + sorted: [...sorted], + pivot: high, + message: `Pivot gewählt: ${pivotValue}`, + }, + code, + lineHighlight: 3, + }); + + let i = low - 1; + + for (let j = low; j < high; j++) { + steps.push({ + state: { + array: [...array], + comparing: [j, high], + swapping: [], + sorted: [...sorted], + pivot: high, + current: j, + message: `Vergleiche ${array[j]} mit Pivot ${pivotValue}`, + }, + code, + lineHighlight: 3, + }); + + if (array[j] < pivotValue) { + i++; + if (i !== j) { + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [i, j], + sorted: [...sorted], + pivot: high, + message: `Tausche ${array[i]} und ${array[j]}`, + }, + code, + lineHighlight: 3, + }); + + [array[i], array[j]] = [array[j], array[i]]; + } + } + } + + if (i + 1 !== high) { + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [i + 1, high], + sorted: [...sorted], + pivot: high, + message: `Platziere Pivot an Position ${i + 1}`, + }, + code, + lineHighlight: 3, + }); + + [array[i + 1], array[high]] = [array[high], array[i + 1]]; + } + + sorted.push(i + 1); + return i + 1; + } + + function quickSort(low: number, high: number): void { + if (low < high) { + const pi = partition(low, high); + quickSort(low, pi - 1); + quickSort(pi + 1, high); + } + } + + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [], + sorted: [], + message: 'Start Quick Sort', + }, + code, + lineHighlight: 1, + }); + + quickSort(0, array.length - 1); + + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [], + sorted: Array.from({ length: array.length }, (_, i) => i), + message: 'Array sortiert!', + }, + code, + lineHighlight: 6, + }); + + return steps; + } + + /** + * Generate Binary Search steps + */ + static generateBinarySearchSteps(arr: number[], target: number): AlgorithmStep[] { + const steps: AlgorithmStep[] = []; + const array = [...arr].sort((a, b) => a - b); + + const code = `function binarySearch(arr, target) { + let left = 0, right = arr.length - 1; + while (left <= right) { + const mid = Math.floor((left + right) / 2); + if (arr[mid] === target) return mid; + if (arr[mid] < target) left = mid + 1; + else right = mid - 1; + } + return -1; +}`; + + let left = 0; + let right = array.length - 1; + + steps.push({ + state: { + array, + comparing: [], + swapping: [], + sorted: Array.from({ length: array.length }, (_, i) => i), + left, + right, + message: `Suche nach ${target} in sortiertem Array`, + }, + code, + lineHighlight: 2, + }); + + while (left <= right) { + const mid = Math.floor((left + right) / 2); + + steps.push({ + state: { + array, + comparing: [mid], + swapping: [], + sorted: Array.from({ length: array.length }, (_, i) => i), + left, + right, + mid, + message: `Mitte: Index ${mid}, Wert ${array[mid]}`, + }, + code, + lineHighlight: 4, + }); + + if (array[mid] === target) { + steps.push({ + state: { + array, + comparing: [], + swapping: [], + sorted: Array.from({ length: array.length }, (_, i) => i), + found: mid, + message: `Gefunden! ${target} ist an Index ${mid}`, + }, + code, + lineHighlight: 5, + }); + return steps; + } + + if (array[mid] < target) { + steps.push({ + state: { + array, + comparing: [], + swapping: [], + sorted: Array.from({ length: array.length }, (_, i) => i), + left: mid + 1, + right, + message: `${array[mid]} < ${target}, suche rechts`, + }, + code, + lineHighlight: 6, + }); + left = mid + 1; + } else { + steps.push({ + state: { + array, + comparing: [], + swapping: [], + sorted: Array.from({ length: array.length }, (_, i) => i), + left, + right: mid - 1, + message: `${array[mid]} > ${target}, suche links`, + }, + code, + lineHighlight: 7, + }); + right = mid - 1; + } + } + + steps.push({ + state: { + array, + comparing: [], + swapping: [], + sorted: Array.from({ length: array.length }, (_, i) => i), + message: `${target} nicht gefunden`, + }, + code, + lineHighlight: 9, + }); + + return steps; + } + + /** + * Generate Insertion Sort steps + */ + static generateInsertionSortSteps(arr: number[]): AlgorithmStep[] { + const steps: AlgorithmStep[] = []; + const array = [...arr]; + const n = array.length; + + const code = `function insertionSort(arr) { + for (let i = 1; i < arr.length; i++) { + const key = arr[i]; + let j = i - 1; + while (j >= 0 && arr[j] > key) { + arr[j + 1] = arr[j]; + j--; + } + arr[j + 1] = key; + } +}`; + + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [], + sorted: [0], + message: 'Start Insertion Sort', + }, + code, + lineHighlight: 1, + }); + + for (let i = 1; i < n; i++) { + const key = array[i]; + let j = i - 1; + + steps.push({ + state: { + array: [...array], + comparing: [i], + swapping: [], + sorted: Array.from({ length: i }, (_, idx) => idx), + current: i, + message: `Schlüssel: ${key}`, + }, + code, + lineHighlight: 3, + }); + + while (j >= 0 && array[j] > key) { + steps.push({ + state: { + array: [...array], + comparing: [j, i], + swapping: [], + sorted: Array.from({ length: i }, (_, idx) => idx), + message: `${array[j]} > ${key}, verschiebe nach rechts`, + }, + code, + lineHighlight: 5, + }); + + array[j + 1] = array[j]; + j--; + + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [], + sorted: Array.from({ length: i }, (_, idx) => idx), + message: `Verschoben`, + }, + code, + lineHighlight: 6, + }); + } + + array[j + 1] = key; + + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [], + sorted: Array.from({ length: i + 1 }, (_, idx) => idx), + message: `${key} eingefügt an Position ${j + 1}`, + }, + code, + lineHighlight: 9, + }); + } + + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [], + sorted: Array.from({ length: n }, (_, i) => i), + message: 'Array sortiert!', + }, + code, + lineHighlight: 10, + }); + + return steps; + } + + /** + * Generate Selection Sort steps + */ + static generateSelectionSortSteps(arr: number[]): AlgorithmStep[] { + const steps: AlgorithmStep[] = []; + const array = [...arr]; + const n = array.length; + + const code = `function selectionSort(arr) { + for (let i = 0; i < arr.length - 1; i++) { + let minIdx = i; + for (let j = i + 1; j < arr.length; j++) { + if (arr[j] < arr[minIdx]) { + minIdx = j; + } + } + swap(arr[i], arr[minIdx]); + } +}`; + + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [], + sorted: [], + message: 'Start Selection Sort', + }, + code, + lineHighlight: 1, + }); + + for (let i = 0; i < n - 1; i++) { + let minIdx = i; + + steps.push({ + state: { + array: [...array], + comparing: [i], + swapping: [], + sorted: Array.from({ length: i }, (_, idx) => idx), + current: i, + message: `Suche Minimum ab Index ${i}`, + }, + code, + lineHighlight: 3, + }); + + for (let j = i + 1; j < n; j++) { + steps.push({ + state: { + array: [...array], + comparing: [minIdx, j], + swapping: [], + sorted: Array.from({ length: i }, (_, idx) => idx), + message: `Vergleiche ${array[minIdx]} und ${array[j]}`, + }, + code, + lineHighlight: 5, + }); + + if (array[j] < array[minIdx]) { + minIdx = j; + steps.push({ + state: { + array: [...array], + comparing: [minIdx], + swapping: [], + sorted: Array.from({ length: i }, (_, idx) => idx), + message: `Neues Minimum: ${array[minIdx]}`, + }, + code, + lineHighlight: 6, + }); + } + } + + if (minIdx !== i) { + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [i, minIdx], + sorted: Array.from({ length: i }, (_, idx) => idx), + message: `Tausche ${array[i]} und ${array[minIdx]}`, + }, + code, + lineHighlight: 9, + }); + + [array[i], array[minIdx]] = [array[minIdx], array[i]]; + } + + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [], + sorted: Array.from({ length: i + 1 }, (_, idx) => idx), + message: `Position ${i} sortiert`, + }, + code, + lineHighlight: 9, + }); + } + + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [], + sorted: Array.from({ length: n }, (_, i) => i), + message: 'Array sortiert!', + }, + code, + lineHighlight: 10, + }); + + return steps; + } + + /** + * Generate Merge Sort steps + */ + static generateMergeSortSteps(arr: number[]): AlgorithmStep[] { + const steps: AlgorithmStep[] = []; + const array = [...arr]; + + const code = `function mergeSort(arr) { + if (arr.length <= 1) return arr; + const mid = Math.floor(arr.length / 2); + const left = mergeSort(arr.slice(0, mid)); + const right = mergeSort(arr.slice(mid)); + return merge(left, right); +}`; + + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [], + sorted: [], + message: 'Start Merge Sort', + }, + code, + lineHighlight: 1, + }); + + function mergeSortHelper(start: number, end: number): void { + if (start >= end) { + return; + } + + const mid = Math.floor((start + end) / 2); + + steps.push({ + state: { + array: [...array], + comparing: [mid], + swapping: [], + sorted: [], + message: `Teile Array bei Index ${mid}`, + }, + code, + lineHighlight: 3, + }); + + mergeSortHelper(start, mid); + mergeSortHelper(mid + 1, end); + + // Merge + const temp: number[] = []; + let i = start; + let j = mid + 1; + + while (i <= mid && j <= end) { + steps.push({ + state: { + array: [...array], + comparing: [i, j], + swapping: [], + sorted: [], + message: `Vergleiche ${array[i]} und ${array[j]}`, + }, + code, + lineHighlight: 6, + }); + + if (array[i] <= array[j]) { + temp.push(array[i++]); + } else { + temp.push(array[j++]); + } + } + + while (i <= mid) { + temp.push(array[i++]); + } + while (j <= end) { + temp.push(array[j++]); + } + + for (let k = 0; k < temp.length; k++) { + array[start + k] = temp[k]; + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [start + k], + sorted: [], + message: `Setze ${temp[k]} an Position ${start + k}`, + }, + code, + lineHighlight: 6, + }); + } + } + + mergeSortHelper(0, array.length - 1); + + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [], + sorted: Array.from({ length: array.length }, (_, i) => i), + message: 'Array sortiert!', + }, + code, + lineHighlight: 6, + }); + + return steps; + } + + /** + * Generate Heap Sort steps + */ + static generateHeapSortSteps(arr: number[]): AlgorithmStep[] { + const steps: AlgorithmStep[] = []; + const array = [...arr]; + const n = array.length; + + const code = `function heapSort(arr) { + buildMaxHeap(arr); + for (let i = n - 1; i > 0; i--) { + swap(arr[0], arr[i]); + heapify(arr, i, 0); + } +}`; + + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [], + sorted: [], + message: 'Start Heap Sort - baue Max-Heap', + }, + code, + lineHighlight: 1, + }); + + function heapify(size: number, root: number): void { + let largest = root; + const left = 2 * root + 1; + const right = 2 * root + 2; + + if (left < size) { + steps.push({ + state: { + array: [...array], + comparing: [largest, left], + swapping: [], + sorted: Array.from({ length: n - size }, (_, i) => n - 1 - i), + message: `Vergleiche ${array[largest]} mit linkem Kind ${array[left]}`, + }, + code, + lineHighlight: 5, + }); + + if (array[left] > array[largest]) { + largest = left; + } + } + + if (right < size) { + steps.push({ + state: { + array: [...array], + comparing: [largest, right], + swapping: [], + sorted: Array.from({ length: n - size }, (_, i) => n - 1 - i), + message: `Vergleiche ${array[largest]} mit rechtem Kind ${array[right]}`, + }, + code, + lineHighlight: 5, + }); + + if (array[right] > array[largest]) { + largest = right; + } + } + + if (largest !== root) { + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [root, largest], + sorted: Array.from({ length: n - size }, (_, i) => n - 1 - i), + message: `Tausche ${array[root]} und ${array[largest]}`, + }, + code, + lineHighlight: 5, + }); + + [array[root], array[largest]] = [array[largest], array[root]]; + heapify(size, largest); + } + } + + // Build max heap + for (let i = Math.floor(n / 2) - 1; i >= 0; i--) { + heapify(n, i); + } + + // Extract elements from heap + for (let i = n - 1; i > 0; i--) { + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [0, i], + sorted: Array.from({ length: n - i }, (_, j) => n - 1 - j), + message: `Tausche Maximum ${array[0]} ans Ende`, + }, + code, + lineHighlight: 4, + }); + + [array[0], array[i]] = [array[i], array[0]]; + heapify(i, 0); + } + + steps.push({ + state: { + array: [...array], + comparing: [], + swapping: [], + sorted: Array.from({ length: n }, (_, i) => i), + message: 'Array sortiert!', + }, + code, + lineHighlight: 6, + }); + + return steps; + } + + /** + * Generate Linear Search steps + */ + static generateLinearSearchSteps(arr: number[], target: number): AlgorithmStep[] { + const steps: AlgorithmStep[] = []; + const array = [...arr]; + + const code = `function linearSearch(arr, target) { + for (let i = 0; i < arr.length; i++) { + if (arr[i] === target) { + return i; + } + } + return -1; +}`; + + steps.push({ + state: { + array, + comparing: [], + swapping: [], + sorted: [], + message: `Suche nach ${target} - durchlaufe alle Elemente`, + }, + code, + lineHighlight: 1, + }); + + for (let i = 0; i < array.length; i++) { + steps.push({ + state: { + array, + comparing: [i], + swapping: [], + sorted: [], + current: i, + message: `Prufe Index ${i}: ${array[i]} === ${target}?`, + }, + code, + lineHighlight: 3, + }); + + if (array[i] === target) { + steps.push({ + state: { + array, + comparing: [], + swapping: [], + sorted: [], + found: i, + message: `Gefunden! ${target} ist an Index ${i}`, + }, + code, + lineHighlight: 4, + }); + return steps; + } + } + + steps.push({ + state: { + array, + comparing: [], + swapping: [], + sorted: [], + message: `${target} nicht gefunden`, + }, + code, + lineHighlight: 7, + }); + + return steps; + } + + /** + * Set steps + */ + setSteps(steps: AlgorithmStep[]): void { + this.steps = steps; + this.currentStep = 0; + this.render(); + } + + /** + * Render visualization + */ + render(): void { + const container = document.getElementById(this.containerId); + if (!container) { + return; + } + + const step = this.steps[this.currentStep]; + if (!step) { + return; + } + + const maxValue = Math.max(...step.state.array); + const barWidth = Math.min(50, Math.floor(400 / step.state.array.length) - 4); + + container.innerHTML = ` +
+
+
+ ${step.state.array + .map((value, index) => { + let barClass = 'algo-bar'; + if (step.state.comparing.includes(index)) { + barClass += ' comparing'; + } + if (step.state.swapping.includes(index)) { + barClass += ' swapping'; + } + if (step.state.sorted.includes(index)) { + barClass += ' sorted'; + } + if (step.state.pivot === index) { + barClass += ' pivot'; + } + if (step.state.found === index) { + barClass += ' found'; + } + if (step.state.current === index) { + barClass += ' current'; + } + if (step.state.left === index) { + barClass += ' left-bound'; + } + if (step.state.right === index) { + barClass += ' right-bound'; + } + if (step.state.mid === index) { + barClass += ' mid'; + } + + const height = (value / maxValue) * 180; + return ` +
+ ${value} +
+ `; + }) + .join('')} +
+ +
${step.state.message}
+ +
+ + + + + +
+ +
+ + ${this.currentStep + 1} / ${this.steps.length} +
+ +
+ + + ${this.speed}ms +
+
+ +
+
+
Code
+
${this.highlightCode(step.code, step.lineHighlight)}
+
+
+
+ + + `; + + this.attachEventListeners(); + } + + /** + * Highlight code line + */ + private highlightCode(code: string, line: number): string { + const lines = code.split('\n'); + return lines + .map((l, i) => { + const lineNum = i + 1; + const isHighlighted = lineNum === line; + const escapedLine = l.replace(//g, '>'); + return `${escapedLine}`; + }) + .join('\n'); + } + + /** + * Attach event listeners + */ + private attachEventListeners(): void { + document.getElementById('algo-first')?.addEventListener('click', () => this.goToStep(0)); + document.getElementById('algo-prev')?.addEventListener('click', () => this.prevStep()); + document.getElementById('algo-play')?.addEventListener('click', () => this.togglePlay()); + document.getElementById('algo-next')?.addEventListener('click', () => this.nextStep()); + document + .getElementById('algo-last') + ?.addEventListener('click', () => this.goToStep(this.steps.length - 1)); + + document.getElementById('algo-slider')?.addEventListener('input', e => { + const value = parseInt((e.target as HTMLInputElement).value); + this.goToStep(value); + }); + + document.getElementById('algo-speed')?.addEventListener('input', e => { + const value = parseInt((e.target as HTMLInputElement).value); + this.speed = 2100 - value; + this.render(); + }); + } + + /** + * Go to specific step + */ + goToStep(step: number): void { + this.currentStep = Math.max(0, Math.min(step, this.steps.length - 1)); + this.render(); + } + + /** + * Next step + */ + nextStep(): void { + if (this.currentStep < this.steps.length - 1) { + this.currentStep++; + this.render(); + } else { + this.stop(); + } + } + + /** + * Previous step + */ + prevStep(): void { + if (this.currentStep > 0) { + this.currentStep--; + this.render(); + } + } + + /** + * Toggle play/pause + */ + togglePlay(): void { + if (this.isPlaying) { + this.stop(); + } else { + this.play(); + } + } + + /** + * Play animation + */ + play(): void { + this.isPlaying = true; + this.render(); + this.animate(); + } + + /** + * Stop animation + */ + stop(): void { + this.isPlaying = false; + if (this.animationFrame) { + cancelAnimationFrame(this.animationFrame); + this.animationFrame = null; + } + this.render(); + } + + /** + * Animate steps + */ + private animate(): void { + if (!this.isPlaying) { + return; + } + + const now = performance.now(); + const step = () => { + if (!this.isPlaying) { + return; + } + if (performance.now() - now >= this.speed) { + this.nextStep(); + if (this.isPlaying && this.currentStep < this.steps.length - 1) { + this.animate(); + } + } else { + this.animationFrame = requestAnimationFrame(step); + } + }; + this.animationFrame = requestAnimationFrame(step); + } + + /** + * Reset visualization + */ + reset(): void { + this.stop(); + this.currentStep = 0; + this.render(); + } + + /** + * Destroy + */ + destroy(): void { + this.stop(); + const container = document.getElementById(this.containerId); + if (container) { + container.innerHTML = ''; + } + } +} diff --git a/src/components/visualization/GraphVisualizer.ts b/src/components/visualization/GraphVisualizer.ts new file mode 100644 index 0000000..5a5c402 --- /dev/null +++ b/src/components/visualization/GraphVisualizer.ts @@ -0,0 +1,2168 @@ +/** + * + * Graph & Tree Visualizer Component + * Visualizes graph algorithms like Dijkstra, BFS, DFS and tree operations + */ + +export interface GraphNode { + id: string; + x: number; + y: number; + value?: number; + label?: string; +} + +export interface GraphEdge { + from: string; + to: string; + weight?: number; +} + +export interface TreeNode { + id: string; + value: number; + left?: TreeNode; + right?: TreeNode; + x?: number; + y?: number; +} + +export type GraphVisualizationState = { + nodes: GraphNode[]; + edges: GraphEdge[]; + visited: string[]; + current: string | null; + path: string[]; + queue: string[]; + stack: string[]; + distances: Map; + message: string; +}; + +export type TreeVisualizationState = { + root: TreeNode | null; + visited: string[]; + current: string | null; + path: string[]; + comparing: string | null; + message: string; +}; + +export type GraphAlgorithmStep = { + state: GraphVisualizationState; + code: string; + lineHighlight: number; +}; + +export type TreeAlgorithmStep = { + state: TreeVisualizationState; + code: string; + lineHighlight: number; +}; + +/** + * Graph Visualizer - Creates beautiful graph and tree animations + */ +export class GraphVisualizer { + private readonly containerId: string; + private steps: (GraphAlgorithmStep | TreeAlgorithmStep)[] = []; + private currentStep: number = 0; + private isPlaying: boolean = false; + private speed: number = 800; + private animationFrame: number | null = null; + private mode: 'graph' | 'tree' = 'graph'; + + constructor(containerId: string) { + this.containerId = containerId; + } + + /** + * Create a sample graph for demonstrations + */ + static createSampleGraph(): { nodes: GraphNode[]; edges: GraphEdge[] } { + const nodes: GraphNode[] = [ + { id: 'A', x: 150, y: 50, label: 'A' }, + { id: 'B', x: 50, y: 150, label: 'B' }, + { id: 'C', x: 250, y: 150, label: 'C' }, + { id: 'D', x: 100, y: 250, label: 'D' }, + { id: 'E', x: 200, y: 250, label: 'E' }, + { id: 'F', x: 150, y: 350, label: 'F' }, + ]; + + const edges: GraphEdge[] = [ + { from: 'A', to: 'B', weight: 4 }, + { from: 'A', to: 'C', weight: 2 }, + { from: 'B', to: 'D', weight: 5 }, + { from: 'C', to: 'D', weight: 8 }, + { from: 'C', to: 'E', weight: 3 }, + { from: 'D', to: 'E', weight: 2 }, + { from: 'D', to: 'F', weight: 6 }, + { from: 'E', to: 'F', weight: 1 }, + ]; + + return { nodes, edges }; + } + + /** + * Create a sample weighted graph for Dijkstra + */ + static createDijkstraGraph(): { nodes: GraphNode[]; edges: GraphEdge[] } { + const nodes: GraphNode[] = [ + { id: '0', x: 80, y: 100, label: 'Start' }, + { id: '1', x: 200, y: 50, label: '1' }, + { id: '2', x: 200, y: 180, label: '2' }, + { id: '3', x: 320, y: 100, label: '3' }, + { id: '4', x: 440, y: 50, label: '4' }, + { id: '5', x: 440, y: 180, label: 'Ziel' }, + ]; + + const edges: GraphEdge[] = [ + { from: '0', to: '1', weight: 4 }, + { from: '0', to: '2', weight: 2 }, + { from: '1', to: '2', weight: 1 }, + { from: '1', to: '3', weight: 5 }, + { from: '2', to: '3', weight: 8 }, + { from: '2', to: '4', weight: 10 }, + { from: '3', to: '4', weight: 2 }, + { from: '3', to: '5', weight: 6 }, + { from: '4', to: '5', weight: 3 }, + ]; + + return { nodes, edges }; + } + + /** + * Generate Dijkstra's Algorithm steps + */ + static generateDijkstraSteps( + nodes: GraphNode[], + edges: GraphEdge[], + startId: string, + endId: string + ): GraphAlgorithmStep[] { + const steps: GraphAlgorithmStep[] = []; + + const code = `function dijkstra(graph, start) { + const dist = new Map(); // Distanzen + const prev = new Map(); // Vorgänger + const pq = new PriorityQueue(); + + // Initialisiere alle Distanzen mit ∞ + for (const node of graph.nodes) { + dist.set(node, Infinity); + } + dist.set(start, 0); + pq.add(start, 0); + + while (!pq.isEmpty()) { + const u = pq.extractMin(); + for (const neighbor of getNeighbors(u)) { + const alt = dist.get(u) + weight(u, neighbor); + if (alt < dist.get(neighbor)) { + dist.set(neighbor, alt); + prev.set(neighbor, u); + pq.decreaseKey(neighbor, alt); + } + } + } + return { dist, prev }; +}`; + + // Build adjacency list + const adj = new Map(); + nodes.forEach(n => adj.set(n.id, [])); + edges.forEach(e => { + adj.get(e.from)?.push({ node: e.to, weight: e.weight || 1 }); + adj.get(e.to)?.push({ node: e.from, weight: e.weight || 1 }); + }); + + const distances = new Map(); + const previous = new Map(); + const visited = new Set(); + + nodes.forEach(n => distances.set(n.id, Infinity)); + distances.set(startId, 0); + + const pq: { node: string; dist: number }[] = [{ node: startId, dist: 0 }]; + + // Initial step + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: [], + current: null, + path: [], + queue: [startId], + stack: [], + distances: new Map(distances), + message: `Starte Dijkstra von Knoten ${startId}. Alle Distanzen auf ∞ gesetzt, Start = 0`, + }, + code, + lineHighlight: 7, + }); + + while (pq.length > 0) { + pq.sort((a, b) => a.dist - b.dist); + const { node: current } = pq.shift()!; + + if (visited.has(current)) { + continue; + } + visited.add(current); + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: Array.from(visited), + current, + path: [], + queue: pq.map(p => p.node), + stack: [], + distances: new Map(distances), + message: `Besuche Knoten ${current} mit Distanz ${distances.get(current)}`, + }, + code, + lineHighlight: 13, + }); + + if (current === endId) { + // Reconstruct path + const path: string[] = []; + let curr: string | undefined = endId; + while (curr) { + path.unshift(curr); + curr = previous.get(curr); + } + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: Array.from(visited), + current: endId, + path, + queue: [], + stack: [], + distances: new Map(distances), + message: `Ziel erreicht! Kürzester Pfad: ${path.join(' → ')} (Distanz: ${distances.get(endId)})`, + }, + code, + lineHighlight: 21, + }); + return steps; + } + + const neighbors = adj.get(current) || []; + for (const { node: neighbor, weight } of neighbors) { + if (visited.has(neighbor)) { + continue; + } + + const alt = (distances.get(current) || 0) + weight; + const currentDist = distances.get(neighbor) || Infinity; + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: Array.from(visited), + current, + path: [current, neighbor], + queue: pq.map(p => p.node), + stack: [], + distances: new Map(distances), + message: `Prüfe Kante ${current} → ${neighbor}: ${distances.get(current)} + ${weight} = ${alt} ${alt < currentDist ? '< ' + currentDist + ' → Update!' : '>= ' + currentDist}`, + }, + code, + lineHighlight: 15, + }); + + if (alt < currentDist) { + distances.set(neighbor, alt); + previous.set(neighbor, current); + + const existing = pq.findIndex(p => p.node === neighbor); + if (existing >= 0) { + pq[existing].dist = alt; + } else { + pq.push({ node: neighbor, dist: alt }); + } + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: Array.from(visited), + current, + path: [], + queue: pq.map(p => p.node), + stack: [], + distances: new Map(distances), + message: `Distanz zu ${neighbor} aktualisiert: ${alt}`, + }, + code, + lineHighlight: 17, + }); + } + } + } + + return steps; + } + + /** + * Generate BFS (Breadth-First Search) steps + */ + static generateBFSSteps( + nodes: GraphNode[], + edges: GraphEdge[], + startId: string + ): GraphAlgorithmStep[] { + const steps: GraphAlgorithmStep[] = []; + + const code = `function bfs(graph, start) { + const visited = new Set(); + const queue = [start]; + visited.add(start); + + while (queue.length > 0) { + const current = queue.shift(); + process(current); + + for (const neighbor of getNeighbors(current)) { + if (!visited.has(neighbor)) { + visited.add(neighbor); + queue.push(neighbor); + } + } + } +}`; + + // Build adjacency list + const adj = new Map(); + nodes.forEach(n => adj.set(n.id, [])); + edges.forEach(e => { + adj.get(e.from)?.push(e.to); + adj.get(e.to)?.push(e.from); + }); + + const visited = new Set(); + const queue: string[] = [startId]; + visited.add(startId); + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: [startId], + current: null, + path: [], + queue: [...queue], + stack: [], + distances: new Map(), + message: `Starte BFS von Knoten ${startId}. Queue initialisiert.`, + }, + code, + lineHighlight: 3, + }); + + while (queue.length > 0) { + const current = queue.shift()!; + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: Array.from(visited), + current, + path: [], + queue: [...queue], + stack: [], + distances: new Map(), + message: `Besuche Knoten ${current} (aus Queue entnommen)`, + }, + code, + lineHighlight: 7, + }); + + const neighbors = (adj.get(current) || []).sort(); + for (const neighbor of neighbors) { + if (!visited.has(neighbor)) { + visited.add(neighbor); + queue.push(neighbor); + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: Array.from(visited), + current, + path: [current, neighbor], + queue: [...queue], + stack: [], + distances: new Map(), + message: `Nachbar ${neighbor} entdeckt - zur Queue hinzugefügt`, + }, + code, + lineHighlight: 12, + }); + } + } + } + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: Array.from(visited), + current: null, + path: [], + queue: [], + stack: [], + distances: new Map(), + message: `BFS abgeschlossen! Alle ${visited.size} Knoten besucht.`, + }, + code, + lineHighlight: 16, + }); + + return steps; + } + + /** + * Generate DFS (Depth-First Search) steps + */ + static generateDFSSteps( + nodes: GraphNode[], + edges: GraphEdge[], + startId: string + ): GraphAlgorithmStep[] { + const steps: GraphAlgorithmStep[] = []; + + const code = `function dfs(graph, start) { + const visited = new Set(); + const stack = [start]; + + while (stack.length > 0) { + const current = stack.pop(); + if (visited.has(current)) continue; + + visited.add(current); + process(current); + + for (const neighbor of getNeighbors(current)) { + if (!visited.has(neighbor)) { + stack.push(neighbor); + } + } + } +}`; + + // Build adjacency list + const adj = new Map(); + nodes.forEach(n => adj.set(n.id, [])); + edges.forEach(e => { + adj.get(e.from)?.push(e.to); + adj.get(e.to)?.push(e.from); + }); + + const visited = new Set(); + const stack: string[] = [startId]; + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: [], + current: null, + path: [], + queue: [], + stack: [...stack], + distances: new Map(), + message: `Starte DFS von Knoten ${startId}. Stack initialisiert.`, + }, + code, + lineHighlight: 3, + }); + + while (stack.length > 0) { + const current = stack.pop()!; + + if (visited.has(current)) { + continue; + } + visited.add(current); + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: Array.from(visited), + current, + path: [], + queue: [], + stack: [...stack], + distances: new Map(), + message: `Besuche Knoten ${current} (vom Stack genommen)`, + }, + code, + lineHighlight: 9, + }); + + const neighbors = (adj.get(current) || []).sort().reverse(); + for (const neighbor of neighbors) { + if (!visited.has(neighbor)) { + stack.push(neighbor); + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: Array.from(visited), + current, + path: [current, neighbor], + queue: [], + stack: [...stack], + distances: new Map(), + message: `Nachbar ${neighbor} auf Stack gelegt`, + }, + code, + lineHighlight: 14, + }); + } + } + } + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: Array.from(visited), + current: null, + path: [], + queue: [], + stack: [], + distances: new Map(), + message: `DFS abgeschlossen! Alle ${visited.size} Knoten besucht.`, + }, + code, + lineHighlight: 17, + }); + + return steps; + } + + /** + * Create a sample Binary Search Tree + */ + static createSampleBST(): TreeNode { + return { + id: '1', + value: 50, + left: { + id: '2', + value: 30, + left: { id: '4', value: 20 }, + right: { id: '5', value: 40 }, + }, + right: { + id: '3', + value: 70, + left: { id: '6', value: 60 }, + right: { id: '7', value: 80 }, + }, + }; + } + + /** + * Generate BST Search steps + */ + static generateBSTSearchSteps(root: TreeNode, target: number): TreeAlgorithmStep[] { + const steps: TreeAlgorithmStep[] = []; + + const code = `function searchBST(node, target) { + if (node === null) { + return null; // Nicht gefunden + } + + if (target === node.value) { + return node; // Gefunden! + } + + if (target < node.value) { + return searchBST(node.left, target); + } else { + return searchBST(node.right, target); + } +}`; + + const visited: string[] = []; + let current: TreeNode | null = root; + + steps.push({ + state: { + root, + visited: [], + current: null, + path: [], + comparing: null, + message: `Suche nach ${target} im Binary Search Tree`, + }, + code, + lineHighlight: 1, + }); + + while (current) { + visited.push(current.id); + + steps.push({ + state: { + root, + visited: [...visited], + current: current.id, + path: [...visited], + comparing: current.id, + message: `Vergleiche ${target} mit Knoten ${current.value}`, + }, + code, + lineHighlight: 6, + }); + + if (target === current.value) { + steps.push({ + state: { + root, + visited: [...visited], + current: current.id, + path: [...visited], + comparing: null, + message: `Gefunden! ${target} ist im Baum.`, + }, + code, + lineHighlight: 7, + }); + return steps; + } + + if (target < current.value) { + steps.push({ + state: { + root, + visited: [...visited], + current: current.id, + path: [...visited], + comparing: null, + message: `${target} < ${current.value} → gehe nach links`, + }, + code, + lineHighlight: 11, + }); + current = current.left || null; + } else { + steps.push({ + state: { + root, + visited: [...visited], + current: current.id, + path: [...visited], + comparing: null, + message: `${target} > ${current.value} → gehe nach rechts`, + }, + code, + lineHighlight: 13, + }); + current = current.right || null; + } + } + + steps.push({ + state: { + root, + visited: [...visited], + current: null, + path: [...visited], + comparing: null, + message: `${target} nicht im Baum gefunden.`, + }, + code, + lineHighlight: 3, + }); + + return steps; + } + + /** + * Generate A* (A-Star) Algorithm steps - The industry standard for pathfinding + * Used by: Google Maps, Video Games (Starcraft, Age of Empires), Robotics + */ + static generateAStarSteps( + nodes: GraphNode[], + edges: GraphEdge[], + startId: string, + endId: string + ): GraphAlgorithmStep[] { + const steps: GraphAlgorithmStep[] = []; + + const code = `function aStar(graph, start, goal) { + const openSet = new PriorityQueue(); + const gScore = new Map(); // Kosten vom Start + const fScore = new Map(); // g + heuristic + const cameFrom = new Map(); + + gScore.set(start, 0); + fScore.set(start, heuristic(start, goal)); + openSet.add(start, fScore.get(start)); + + while (!openSet.isEmpty()) { + const current = openSet.extractMin(); + if (current === goal) { + return reconstructPath(cameFrom, current); + } + + for (const neighbor of getNeighbors(current)) { + const tentative = gScore.get(current) + weight(current, neighbor); + if (tentative < gScore.get(neighbor)) { + cameFrom.set(neighbor, current); + gScore.set(neighbor, tentative); + fScore.set(neighbor, tentative + heuristic(neighbor, goal)); + openSet.add(neighbor, fScore.get(neighbor)); + } + } + } + return null; // Kein Pfad gefunden +}`; + + // Build adjacency list + const adj = new Map(); + nodes.forEach(n => adj.set(n.id, [])); + edges.forEach(e => { + adj.get(e.from)?.push({ node: e.to, weight: e.weight || 1 }); + adj.get(e.to)?.push({ node: e.from, weight: e.weight || 1 }); + }); + + // Heuristic function (Euclidean distance) + const heuristic = (a: string, b: string): number => { + const nodeA = nodes.find(n => n.id === a); + const nodeB = nodes.find(n => n.id === b); + if (!nodeA || !nodeB) { + return 0; + } + return Math.sqrt(Math.pow(nodeA.x - nodeB.x, 2) + Math.pow(nodeA.y - nodeB.y, 2)) / 50; + }; + + const gScore = new Map(); + const fScore = new Map(); + const cameFrom = new Map(); + const visited = new Set(); + + nodes.forEach(n => { + gScore.set(n.id, Infinity); + fScore.set(n.id, Infinity); + }); + gScore.set(startId, 0); + fScore.set(startId, heuristic(startId, endId)); + + const openSet: { node: string; f: number }[] = [{ node: startId, f: fScore.get(startId)! }]; + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: [], + current: null, + path: [], + queue: [startId], + stack: [], + distances: new Map(gScore), + message: `A* startet von ${startId}. Heuristik: Euklidische Distanz zum Ziel`, + }, + code, + lineHighlight: 6, + }); + + while (openSet.length > 0) { + openSet.sort((a, b) => a.f - b.f); + const { node: current } = openSet.shift()!; + + if (visited.has(current)) { + continue; + } + visited.add(current); + + const h = heuristic(current, endId).toFixed(1); + const g = gScore.get(current)!; + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: Array.from(visited), + current, + path: [], + queue: openSet.map(p => p.node), + stack: [], + distances: new Map(gScore), + message: `Besuche ${current}: g=${g}, h=${h}, f=${(g + parseFloat(h)).toFixed(1)}`, + }, + code, + lineHighlight: 12, + }); + + if (current === endId) { + const path: string[] = []; + let curr: string | undefined = endId; + while (curr) { + path.unshift(curr); + curr = cameFrom.get(curr); + } + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: Array.from(visited), + current: endId, + path, + queue: [], + stack: [], + distances: new Map(gScore), + message: `Ziel erreicht! Optimaler Pfad: ${path.join(' → ')} (Kosten: ${gScore.get(endId)})`, + }, + code, + lineHighlight: 14, + }); + return steps; + } + + const neighbors = adj.get(current) || []; + for (const { node: neighbor, weight } of neighbors) { + if (visited.has(neighbor)) { + continue; + } + + const tentativeG = (gScore.get(current) || 0) + weight; + const currentG = gScore.get(neighbor) || Infinity; + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: Array.from(visited), + current, + path: [current, neighbor], + queue: openSet.map(p => p.node), + stack: [], + distances: new Map(gScore), + message: `Prüfe ${current} → ${neighbor}: g_neu=${tentativeG} ${tentativeG < currentG ? '< ' + currentG + ' → Update!' : '>= ' + currentG}`, + }, + code, + lineHighlight: 18, + }); + + if (tentativeG < currentG) { + cameFrom.set(neighbor, current); + gScore.set(neighbor, tentativeG); + const h = heuristic(neighbor, endId); + fScore.set(neighbor, tentativeG + h); + + const existing = openSet.findIndex(p => p.node === neighbor); + if (existing >= 0) { + openSet[existing].f = tentativeG + h; + } else { + openSet.push({ node: neighbor, f: tentativeG + h }); + } + } + } + } + + return steps; + } + + /** + * Generate Topological Sort steps (Kahn's Algorithm) + * Used by: npm/yarn dependency resolution, Build systems (Make, Gradle), Task scheduling + */ + static generateTopologicalSortSteps( + nodes: GraphNode[], + edges: GraphEdge[] + ): GraphAlgorithmStep[] { + const steps: GraphAlgorithmStep[] = []; + + const code = `function topologicalSort(graph) { + const inDegree = new Map(); + const queue = []; + const result = []; + + // Berechne In-Degree für jeden Knoten + for (const node of graph.nodes) { + inDegree.set(node, 0); + } + for (const edge of graph.edges) { + inDegree.set(edge.to, inDegree.get(edge.to) + 1); + } + + // Füge Knoten mit In-Degree 0 zur Queue + for (const [node, degree] of inDegree) { + if (degree === 0) queue.push(node); + } + + while (queue.length > 0) { + const node = queue.shift(); + result.push(node); + + for (const neighbor of getOutgoing(node)) { + inDegree.set(neighbor, inDegree.get(neighbor) - 1); + if (inDegree.get(neighbor) === 0) { + queue.push(neighbor); + } + } + } + + return result.length === graph.nodes.length ? result : null; +}`; + + // Calculate in-degrees + const inDegree = new Map(); + const outgoing = new Map(); + + nodes.forEach(n => { + inDegree.set(n.id, 0); + outgoing.set(n.id, []); + }); + + edges.forEach(e => { + inDegree.set(e.to, (inDegree.get(e.to) || 0) + 1); + outgoing.get(e.from)?.push(e.to); + }); + + const queue: string[] = []; + const result: string[] = []; + const visited: string[] = []; + + // Initial step + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: [], + current: null, + path: [], + queue: [], + stack: [], + distances: new Map(Array.from(inDegree.entries()).map(([k, v]) => [k, v])), + message: `Berechne In-Degree für jeden Knoten (Anzahl eingehender Kanten)`, + }, + code, + lineHighlight: 7, + }); + + // Find nodes with in-degree 0 + nodes.forEach(n => { + if (inDegree.get(n.id) === 0) { + queue.push(n.id); + } + }); + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: [], + current: null, + path: [], + queue: [...queue], + stack: [], + distances: new Map(Array.from(inDegree.entries()).map(([k, v]) => [k, v])), + message: `Knoten mit In-Degree 0: [${queue.join(', ')}] → Keine Abhängigkeiten`, + }, + code, + lineHighlight: 15, + }); + + while (queue.length > 0) { + const current = queue.shift()!; + result.push(current); + visited.push(current); + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: [...visited], + current, + path: [...result], + queue: [...queue], + stack: [], + distances: new Map(Array.from(inDegree.entries()).map(([k, v]) => [k, v])), + message: `Verarbeite ${current} → Reihenfolge: [${result.join(', ')}]`, + }, + code, + lineHighlight: 20, + }); + + const neighbors = outgoing.get(current) || []; + for (const neighbor of neighbors) { + const newDegree = (inDegree.get(neighbor) || 0) - 1; + inDegree.set(neighbor, newDegree); + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: [...visited], + current, + path: [current, neighbor], + queue: [...queue], + stack: [], + distances: new Map(Array.from(inDegree.entries()).map(([k, v]) => [k, v])), + message: `Reduziere In-Degree von ${neighbor}: ${newDegree + 1} → ${newDegree}`, + }, + code, + lineHighlight: 24, + }); + + if (newDegree === 0) { + queue.push(neighbor); + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: [...visited], + current, + path: [], + queue: [...queue], + stack: [], + distances: new Map(Array.from(inDegree.entries()).map(([k, v]) => [k, v])), + message: `${neighbor} hat jetzt In-Degree 0 → zur Queue hinzugefügt`, + }, + code, + lineHighlight: 26, + }); + } + } + } + + const success = result.length === nodes.length; + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: [...visited], + current: null, + path: [...result], + queue: [], + stack: [], + distances: new Map(), + message: success + ? `Topologische Sortierung: [${result.join(' → ')}]` + : `Zyklus erkannt! Keine topologische Sortierung möglich.`, + }, + code, + lineHighlight: 30, + }); + + return steps; + } + + /** + * Create a DAG (Directed Acyclic Graph) for topological sort + */ + static createDAGGraph(): { nodes: GraphNode[]; edges: GraphEdge[] } { + const nodes: GraphNode[] = [ + { id: 'A', x: 80, y: 80, label: 'A' }, + { id: 'B', x: 200, y: 50, label: 'B' }, + { id: 'C', x: 200, y: 150, label: 'C' }, + { id: 'D', x: 320, y: 80, label: 'D' }, + { id: 'E', x: 320, y: 180, label: 'E' }, + { id: 'F', x: 440, y: 120, label: 'F' }, + ]; + + // Directed edges (dependencies) + const edges: GraphEdge[] = [ + { from: 'A', to: 'B' }, + { from: 'A', to: 'C' }, + { from: 'B', to: 'D' }, + { from: 'C', to: 'D' }, + { from: 'C', to: 'E' }, + { from: 'D', to: 'F' }, + { from: 'E', to: 'F' }, + ]; + + return { nodes, edges }; + } + + /** + * Generate Tree Preorder Traversal steps + * Used by: Creating a copy of tree, Prefix expression, Serialization + */ + static generatePreorderSteps(root: TreeNode): TreeAlgorithmStep[] { + const steps: TreeAlgorithmStep[] = []; + const result: number[] = []; + + const code = `function preorder(node) { + if (node === null) return; + + process(node.value); // Wurzel ZUERST + preorder(node.left); // Links + preorder(node.right); // Rechts +} +// Anwendung: Tree-Kopie, Prefix-Notation`; + + function traverse(node: TreeNode | undefined, path: string[]): void { + if (!node) { + return; + } + + result.push(node.value); + steps.push({ + state: { + root, + visited: result.map((_, i) => `node-${i}`), + current: node.id, + path: [...path, node.id], + comparing: node.id, + message: `Besuche ${node.value} ZUERST - Ergebnis: [${result.join(', ')}]`, + }, + code, + lineHighlight: 4, + }); + + steps.push({ + state: { + root, + visited: result.map((_, i) => `node-${i}`), + current: node.id, + path: [...path, node.id], + comparing: null, + message: `Gehe zum linken Kind von ${node.value}`, + }, + code, + lineHighlight: 5, + }); + traverse(node.left, [...path, node.id]); + + steps.push({ + state: { + root, + visited: result.map((_, i) => `node-${i}`), + current: node.id, + path: [...path, node.id], + comparing: null, + message: `Gehe zum rechten Kind von ${node.value}`, + }, + code, + lineHighlight: 6, + }); + traverse(node.right, [...path, node.id]); + } + + steps.push({ + state: { + root, + visited: [], + current: null, + path: [], + comparing: null, + message: 'Starte Preorder-Traversierung (Wurzel → Links → Rechts)', + }, + code, + lineHighlight: 1, + }); + + traverse(root, []); + + steps.push({ + state: { + root, + visited: [], + current: null, + path: [], + comparing: null, + message: `Preorder abgeschlossen: [${result.join(', ')}]`, + }, + code, + lineHighlight: 8, + }); + + return steps; + } + + /** + * Generate Tree Postorder Traversal steps + * Used by: Deleting tree, Postfix expression, Directory size calculation + */ + static generatePostorderSteps(root: TreeNode): TreeAlgorithmStep[] { + const steps: TreeAlgorithmStep[] = []; + const result: number[] = []; + + const code = `function postorder(node) { + if (node === null) return; + + postorder(node.left); // Links + postorder(node.right); // Rechts + process(node.value); // Wurzel ZULETZT +} +// Anwendung: Tree löschen, Postfix, du -sh`; + + function traverse(node: TreeNode | undefined, path: string[]): void { + if (!node) { + return; + } + + steps.push({ + state: { + root, + visited: result.map((_, i) => `node-${i}`), + current: node.id, + path: [...path, node.id], + comparing: null, + message: `Bei ${node.value} - gehe erst zum linken Kind`, + }, + code, + lineHighlight: 4, + }); + traverse(node.left, [...path, node.id]); + + steps.push({ + state: { + root, + visited: result.map((_, i) => `node-${i}`), + current: node.id, + path: [...path, node.id], + comparing: null, + message: `Bei ${node.value} - gehe zum rechten Kind`, + }, + code, + lineHighlight: 5, + }); + traverse(node.right, [...path, node.id]); + + result.push(node.value); + steps.push({ + state: { + root, + visited: result.map((_, i) => `node-${i}`), + current: node.id, + path: [...path, node.id], + comparing: node.id, + message: `Besuche ${node.value} ZULETZT - Ergebnis: [${result.join(', ')}]`, + }, + code, + lineHighlight: 6, + }); + } + + steps.push({ + state: { + root, + visited: [], + current: null, + path: [], + comparing: null, + message: 'Starte Postorder-Traversierung (Links → Rechts → Wurzel)', + }, + code, + lineHighlight: 1, + }); + + traverse(root, []); + + steps.push({ + state: { + root, + visited: [], + current: null, + path: [], + comparing: null, + message: `Postorder abgeschlossen: [${result.join(', ')}]`, + }, + code, + lineHighlight: 8, + }); + + return steps; + } + + /** + * Generate Bellman-Ford Algorithm steps + * Used by: Network routing (RIP protocol), Currency arbitrage detection, Negative weight handling + */ + static generateBellmanFordSteps( + nodes: GraphNode[], + edges: GraphEdge[], + startId: string + ): GraphAlgorithmStep[] { + const steps: GraphAlgorithmStep[] = []; + + const code = `function bellmanFord(graph, start) { + const dist = new Map(); + const prev = new Map(); + + // Initialisiere alle Distanzen mit ∞ + for (const node of graph.nodes) { + dist.set(node, Infinity); + } + dist.set(start, 0); + + // Relaxiere alle Kanten |V|-1 mal + for (let i = 0; i < nodes.length - 1; i++) { + for (const edge of graph.edges) { + if (dist.get(edge.from) + edge.weight < dist.get(edge.to)) { + dist.set(edge.to, dist.get(edge.from) + edge.weight); + prev.set(edge.to, edge.from); + } + } + } + + // Prüfe auf negative Zyklen + for (const edge of graph.edges) { + if (dist.get(edge.from) + edge.weight < dist.get(edge.to)) { + return "Negativer Zyklus erkannt!"; + } + } + return { dist, prev }; +}`; + + const distances = new Map(); + const previous = new Map(); + + nodes.forEach(n => distances.set(n.id, Infinity)); + distances.set(startId, 0); + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: [], + current: null, + path: [], + queue: [], + stack: [], + distances: new Map(distances), + message: `Bellman-Ford startet von ${startId}. Alle Distanzen = ∞, Start = 0`, + }, + code, + lineHighlight: 6, + }); + + // Relax all edges V-1 times + for (let i = 0; i < nodes.length - 1; i++) { + let updated = false; + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: [], + current: null, + path: [], + queue: [], + stack: [], + distances: new Map(distances), + message: `Iteration ${i + 1}/${nodes.length - 1}: Relaxiere alle Kanten`, + }, + code, + lineHighlight: 12, + }); + + for (const edge of edges) { + const fromDist = distances.get(edge.from) || Infinity; + const toDist = distances.get(edge.to) || Infinity; + const weight = edge.weight || 1; + + if (fromDist !== Infinity && fromDist + weight < toDist) { + distances.set(edge.to, fromDist + weight); + previous.set(edge.to, edge.from); + updated = true; + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: [edge.from], + current: edge.to, + path: [edge.from, edge.to], + queue: [], + stack: [], + distances: new Map(distances), + message: `Kante ${edge.from}→${edge.to}: ${fromDist} + ${weight} = ${fromDist + weight} < ${toDist} → Update!`, + }, + code, + lineHighlight: 15, + }); + } + } + + // Also check reverse edges (undirected graph) + for (const edge of edges) { + const fromDist = distances.get(edge.to) || Infinity; + const toDist = distances.get(edge.from) || Infinity; + const weight = edge.weight || 1; + + if (fromDist !== Infinity && fromDist + weight < toDist) { + distances.set(edge.from, fromDist + weight); + previous.set(edge.from, edge.to); + updated = true; + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: [edge.to], + current: edge.from, + path: [edge.to, edge.from], + queue: [], + stack: [], + distances: new Map(distances), + message: `Kante ${edge.to}→${edge.from}: ${fromDist} + ${weight} = ${fromDist + weight} < ${toDist} → Update!`, + }, + code, + lineHighlight: 15, + }); + } + } + + if (!updated) { + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: [], + current: null, + path: [], + queue: [], + stack: [], + distances: new Map(distances), + message: `Keine Änderungen in Iteration ${i + 1} → Früher Abbruch möglich`, + }, + code, + lineHighlight: 18, + }); + break; + } + } + + steps.push({ + state: { + nodes: [...nodes], + edges: [...edges], + visited: Array.from(distances.keys()), + current: null, + path: [], + queue: [], + stack: [], + distances: new Map(distances), + message: `Bellman-Ford abgeschlossen! Kürzeste Distanzen von ${startId}: ${Array.from( + distances.entries() + ) + .map(([k, v]) => `${k}=${v}`) + .join(', ')}`, + }, + code, + lineHighlight: 27, + }); + + return steps; + } + + /** + * Generate Tree Inorder Traversal steps + */ + static generateInorderSteps(root: TreeNode): TreeAlgorithmStep[] { + const steps: TreeAlgorithmStep[] = []; + const result: number[] = []; + + const code = `function inorder(node) { + if (node === null) return; + + inorder(node.left); // Links + process(node.value); // Wurzel + inorder(node.right); // Rechts +} +// Ergebnis: sortierte Reihenfolge!`; + + function traverse(node: TreeNode | undefined, path: string[]): void { + if (!node) { + return; + } + + steps.push({ + state: { + root, + visited: [...result.map((_, i) => path[i])].filter(Boolean), + current: node.id, + path: [...path, node.id], + comparing: null, + message: `Gehe zu linkem Kind von ${node.value}`, + }, + code, + lineHighlight: 4, + }); + + traverse(node.left, [...path, node.id]); + + result.push(node.value); + steps.push({ + state: { + root, + visited: result.map((_, i) => `node-${i}`), + current: node.id, + path: [...path, node.id], + comparing: node.id, + message: `Besuche ${node.value} - Ergebnis: [${result.join(', ')}]`, + }, + code, + lineHighlight: 5, + }); + + traverse(node.right, [...path, node.id]); + } + + steps.push({ + state: { + root, + visited: [], + current: null, + path: [], + comparing: null, + message: 'Starte Inorder-Traversierung (Links → Wurzel → Rechts)', + }, + code, + lineHighlight: 1, + }); + + traverse(root, []); + + steps.push({ + state: { + root, + visited: [], + current: null, + path: [], + comparing: null, + message: `Inorder abgeschlossen: [${result.join(', ')}] - sortiert!`, + }, + code, + lineHighlight: 8, + }); + + return steps; + } + + /** + * Set steps + */ + setGraphSteps(steps: GraphAlgorithmStep[]): void { + this.steps = steps; + this.currentStep = 0; + this.mode = 'graph'; + this.render(); + } + + setTreeSteps(steps: TreeAlgorithmStep[]): void { + this.steps = steps; + this.currentStep = 0; + this.mode = 'tree'; + this.render(); + } + + /** + * Render graph visualization + */ + private renderGraph(state: GraphVisualizationState): string { + const width = 500; + const height = 400; + + return ` + + + ${state.edges + .map(edge => { + const fromNode = state.nodes.find(n => n.id === edge.from); + const toNode = state.nodes.find(n => n.id === edge.to); + if (!fromNode || !toNode) { + return ''; + } + const isInPath = + state.path.includes(edge.from) && + state.path.includes(edge.to) && + Math.abs(state.path.indexOf(edge.from) - state.path.indexOf(edge.to)) === 1; + return ` + + + ${ + edge.weight !== undefined + ? ` + ${edge.weight} + ` + : '' + } + + `; + }) + .join('')} + + + ${state.nodes + .map(node => { + let nodeClass = 'graph-node'; + if (state.current === node.id) { + nodeClass += ' current'; + } + if (state.visited.includes(node.id)) { + nodeClass += ' visited'; + } + if (state.path.includes(node.id)) { + nodeClass += ' in-path'; + } + const dist = state.distances.get(node.id); + return ` + + + ${node.label || node.id} + ${dist !== undefined && dist !== Infinity ? `${dist}` : ''} + + `; + }) + .join('')} + + +
+ ${state.queue.length > 0 ? `
Queue: [${state.queue.join(', ')}]
` : ''} + ${state.stack.length > 0 ? `
Stack: [${state.stack.join(', ')}]
` : ''} + ${ + state.distances.size > 0 + ? ` +
+ Distanzen: + ${Array.from(state.distances.entries()) + .map(([k, v]) => `${k}:${v === Infinity ? '∞' : v}`) + .join(' | ')} +
+ ` + : '' + } +
+ `; + } + + /** + * Calculate tree node positions + */ + private calculateTreePositions( + node: TreeNode | undefined, + x: number, + y: number, + level: number, + positions: Map + ): void { + if (!node) { + return; + } + // Reduzierter Spread für bessere Darstellung + const spread = 120 / Math.pow(2, level); + positions.set(node.id, { x, y }); + + this.calculateTreePositions(node.left, x - spread, y + 60, level + 1, positions); + this.calculateTreePositions(node.right, x + spread, y + 60, level + 1, positions); + } + + /** + * Render tree visualization + */ + private renderTree(state: TreeVisualizationState): string { + if (!state.root) { + return '
Kein Baum vorhanden
'; + } + + const positions = new Map(); + // Starte in der Mitte mit mehr Platz für Knoten + this.calculateTreePositions(state.root, 220, 35, 0, positions); + + const renderNode = (node: TreeNode | undefined): string => { + if (!node) { + return ''; + } + const pos = positions.get(node.id); + if (!pos) { + return ''; + } + + let nodeClass = 'tree-node'; + if (state.current === node.id) { + nodeClass += ' current'; + } + if (state.comparing === node.id) { + nodeClass += ' comparing'; + } + if (state.path.includes(node.id)) { + nodeClass += ' in-path'; + } + + let edges = ''; + if (node.left) { + const leftPos = positions.get(node.left.id); + if (leftPos) { + edges += ``; + } + } + if (node.right) { + const rightPos = positions.get(node.right.id); + if (rightPos) { + edges += ``; + } + } + + return ` + ${edges} + + + ${node.value} + + ${renderNode(node.left)} + ${renderNode(node.right)} + `; + }; + + return ` + + ${renderNode(state.root)} + + `; + } + + /** + * Render visualization + */ + render(): void { + const container = document.getElementById(this.containerId); + if (!container) { + return; + } + + const step = this.steps[this.currentStep]; + if (!step) { + return; + } + + const visualization = + this.mode === 'graph' + ? this.renderGraph(step.state as GraphVisualizationState) + : this.renderTree(step.state as TreeVisualizationState); + + container.innerHTML = ` +
+
+
+ ${visualization} +
+ +
${step.state.message}
+ +
+ + + + + +
+ +
+ + ${this.currentStep + 1} / ${this.steps.length} +
+ +
+ + + ${this.speed}ms +
+
+ +
+
+
Code
+
${this.highlightCode(step.code, step.lineHighlight)}
+
+
+
+ + + `; + + this.attachEventListeners(); + } + + /** + * Highlight code line + */ + private highlightCode(code: string, line: number): string { + const lines = code.split('\n'); + return lines + .map((l, i) => { + const lineNum = i + 1; + const isHighlighted = lineNum === line; + const escapedLine = l.replace(//g, '>'); + return `${escapedLine}`; + }) + .join('\n'); + } + + /** + * Attach event listeners + */ + private attachEventListeners(): void { + document.getElementById('graph-first')?.addEventListener('click', () => this.goToStep(0)); + document.getElementById('graph-prev')?.addEventListener('click', () => this.prevStep()); + document.getElementById('graph-play')?.addEventListener('click', () => this.togglePlay()); + document.getElementById('graph-next')?.addEventListener('click', () => this.nextStep()); + document + .getElementById('graph-last') + ?.addEventListener('click', () => this.goToStep(this.steps.length - 1)); + + document.getElementById('graph-slider')?.addEventListener('input', e => { + const value = parseInt((e.target as HTMLInputElement).value); + this.goToStep(value); + }); + + document.getElementById('graph-speed')?.addEventListener('input', e => { + const value = parseInt((e.target as HTMLInputElement).value); + this.speed = 2200 - value; + this.render(); + }); + } + + goToStep(step: number): void { + this.currentStep = Math.max(0, Math.min(step, this.steps.length - 1)); + this.render(); + } + + nextStep(): void { + if (this.currentStep < this.steps.length - 1) { + this.currentStep++; + this.render(); + } else { + this.stop(); + } + } + + prevStep(): void { + if (this.currentStep > 0) { + this.currentStep--; + this.render(); + } + } + + togglePlay(): void { + if (this.isPlaying) { + this.stop(); + } else { + this.play(); + } + } + + play(): void { + this.isPlaying = true; + this.render(); + this.animate(); + } + + stop(): void { + this.isPlaying = false; + if (this.animationFrame) { + cancelAnimationFrame(this.animationFrame); + this.animationFrame = null; + } + this.render(); + } + + private animate(): void { + if (!this.isPlaying) { + return; + } + + const now = performance.now(); + const step = () => { + if (!this.isPlaying) { + return; + } + if (performance.now() - now >= this.speed) { + this.nextStep(); + if (this.isPlaying && this.currentStep < this.steps.length - 1) { + this.animate(); + } + } else { + this.animationFrame = requestAnimationFrame(step); + } + }; + this.animationFrame = requestAnimationFrame(step); + } + + reset(): void { + this.stop(); + this.currentStep = 0; + this.render(); + } + + destroy(): void { + this.stop(); + const container = document.getElementById(this.containerId); + if (container) { + container.innerHTML = ''; + } + } +} diff --git a/src/core/EventBus.ts b/src/core/EventBus.ts index 25cb74f..f2327ef 100644 --- a/src/core/EventBus.ts +++ b/src/core/EventBus.ts @@ -16,6 +16,7 @@ export interface AppEvents { 'typing:start': { lessonId: string; exerciseId: string }; 'typing:keystroke': { key: string; isCorrect: boolean; position: number }; 'typing:complete': { wpm: number; accuracy: number; time: number }; + 'typing:backspace': { position: number }; 'typing:pause': void; 'typing:resume': void; 'typing:reset': void; diff --git a/src/core/i18n.ts b/src/core/i18n.ts index 5156ae1..5bdec07 100644 --- a/src/core/i18n.ts +++ b/src/core/i18n.ts @@ -124,10 +124,201 @@ const en: Translations = { 'lessons.completed': '{completed} of {total} lessons completed', 'lessons.exerciseOf': 'Exercise {current} of {total}', 'lessons.exercises': '{count} exercises', + 'lessons.exercisesCount': '{count} exercises', 'lessons.pressToStart': 'Press a key to start.', 'lessons.goal': 'Goal: {wpm} WPM · {accuracy}% Accuracy', 'lessons.passed': 'Lesson passed! {wpm} WPM · {accuracy}% · +{xp} XP', 'lessons.ended': 'Lesson ended. {wpm} WPM · {accuracy}%', + 'lessons.completedCount': '{count} completed', + + // Lesson Categories + 'lessons.category.beginner': 'Beginner', + 'lessons.category.beginner.desc': 'Absolute basics - home row only', + 'lessons.category.basics': 'Basics', + 'lessons.category.basics.desc': 'All letters and first sentences', + 'lessons.category.intermediate': 'Intermediate', + 'lessons.category.intermediate.desc': 'Numbers and special characters', + 'lessons.category.words': 'Words & Sentences', + 'lessons.category.words.desc': 'Vocabulary and text exercises', + 'lessons.category.advanced': 'Advanced', + 'lessons.category.advanced.desc': 'Complex texts and all characters', + 'lessons.category.expert': 'Expert', + 'lessons.category.expert.desc': 'Maximum challenge', + 'lessons.category.programming': 'Programming', + 'lessons.category.programming.desc': 'Code snippets in various languages', + 'lessons.category.shortcuts': 'IDE Shortcuts', + 'lessons.category.shortcuts.desc': 'Keyboard shortcuts for VS Code, IntelliJ, Vim', + + // Difficulty Labels + 'lessons.difficulty.beginner': 'Beginner', + 'lessons.difficulty.easy': 'Easy', + 'lessons.difficulty.medium': 'Medium', + 'lessons.difficulty.hard': 'Hard', + 'lessons.difficulty.expert': 'Expert', + + // Beginner Lessons + 'lessons.beginner-01.title': 'First Steps: F and J', + 'lessons.beginner-01.desc': 'Learn the most important keys: F and J have bumps for orientation', + 'lessons.beginner-02.title': 'Left Hand: A S D F', + 'lessons.beginner-02.desc': 'The left hand on the home position', + 'lessons.beginner-03.title': 'Right Hand: J K L ;', + 'lessons.beginner-03.desc': 'The right hand on the home position', + 'lessons.beginner-04.title': 'Complete Home Row', + 'lessons.beginner-04.desc': 'Both hands together on the home row', + 'lessons.beginner-05.title': 'First Simple Words', + 'lessons.beginner-05.desc': 'Words using only home row letters', + 'lessons.beginner-06.title': 'Adding G and H', + 'lessons.beginner-06.desc': 'Index fingers move to the center', + 'lessons.beginner-07.title': 'The Space Bar', + 'lessons.beginner-07.desc': 'Learn to use the space bar with your thumb', + 'lessons.beginner-08.title': 'Very Short Sentences', + 'lessons.beginner-08.desc': 'First complete sentences with home row', + + // Basic Lessons + 'lessons.basics-01.title': 'Top Row: E and I', + 'lessons.basics-01.desc': 'The most important vowels on the top row', + 'lessons.basics-02.title': 'Top Row: R and U', + 'lessons.basics-02.desc': 'Index fingers on the top row', + 'lessons.basics-03.title': 'Complete Top Row', + 'lessons.basics-03.desc': 'All letters of the top row: QWERTYUIOP', + 'lessons.basics-04.title': 'Bottom Row: N and M', + 'lessons.basics-04.desc': 'The most important letters of the bottom row', + 'lessons.basics-05.title': 'Complete Bottom Row', + 'lessons.basics-05.desc': 'All letters of the bottom row: ZXCVBNM', + 'lessons.basics-06.title': 'All Letters Together', + 'lessons.basics-06.desc': 'Exercises with all three rows', + 'lessons.basics-07.title': 'Common Words Level 1', + 'lessons.basics-07.desc': 'The 50 most common words', + 'lessons.basics-08.title': 'Learning Capital Letters', + 'lessons.basics-08.desc': 'The Shift key for capitalization', + + // Intermediate Lessons + 'lessons.inter-01.title': 'Numbers 1-5', + 'lessons.inter-01.desc': 'Numbers for the left hand', + 'lessons.inter-02.title': 'Numbers 6-0', + 'lessons.inter-02.desc': 'Numbers for the right hand', + 'lessons.inter-03.title': 'All Numbers Together', + 'lessons.inter-03.desc': 'Exercises with all numbers 0-9', + 'lessons.inter-04.title': 'Period and Comma', + 'lessons.inter-04.desc': 'The most important punctuation marks', + 'lessons.inter-05.title': 'Question and Exclamation Marks', + 'lessons.inter-05.desc': 'Asking questions and exclaiming', + 'lessons.inter-06.title': 'Hyphen and Apostrophe', + 'lessons.inter-06.desc': 'Compound words and contractions', + 'lessons.inter-07.title': 'Brackets', + 'lessons.inter-07.desc': 'Round and square brackets', + 'lessons.inter-08.title': 'Quotation Marks', + 'lessons.inter-08.desc': 'Quotes and direct speech', + + // Word Lessons + 'lessons.words-01.title': 'The 100 Most Common Words', + 'lessons.words-01.desc': 'The most frequently used words', + 'lessons.words-02.title': 'Everyday Verbs', + 'lessons.words-02.desc': 'Commonly used verbs', + 'lessons.words-03.title': 'Adjectives', + 'lessons.words-03.desc': 'Descriptive words', + 'lessons.words-04.title': 'Short Sentences', + 'lessons.words-04.desc': 'Simple sentences', + 'lessons.words-05.title': 'Medium Sentences', + 'lessons.words-05.desc': 'Sentences with more details', + 'lessons.words-06.title': 'Long Sentences', + 'lessons.words-06.desc': 'More complex sentences', + 'lessons.words-07.title': 'Practice Paragraphs', + 'lessons.words-07.desc': 'Coherent text passages', + 'lessons.words-08.title': 'Topic: Computer', + 'lessons.words-08.desc': 'Technical vocabulary from the computer world', + 'lessons.words-09.title': 'Topic: Office', + 'lessons.words-09.desc': 'Words from office life', + 'lessons.words-10.title': 'Topic: Travel', + 'lessons.words-10.desc': 'Vocabulary about traveling', + + // Advanced Lessons + 'lessons.adv-01.title': 'All Special Characters Overview', + 'lessons.adv-01.desc': 'Systematic training of all important special characters', + 'lessons.adv-02.title': 'Mathematical Symbols', + 'lessons.adv-02.desc': 'For technical and scientific texts', + 'lessons.adv-03.title': 'Literary Classics 1', + 'lessons.adv-03.desc': 'Quotes from classic literature', + 'lessons.adv-04.title': 'Business Letters', + 'lessons.adv-04.desc': 'Formal correspondence', + 'lessons.adv-05.title': 'Scientific Texts', + 'lessons.adv-05.desc': 'Academic writing', + 'lessons.adv-06.title': 'Speed Drill 1', + 'lessons.adv-06.desc': 'High-frequency words for maximum speed', + 'lessons.adv-07.title': 'Speed Drill 2', + 'lessons.adv-07.desc': 'Longer words at high speed', + + // Expert Lessons + 'lessons.exp-01.title': 'Philosophical Texts', + 'lessons.exp-01.desc': 'Challenging philosophical passages', + 'lessons.exp-02.title': 'Legal Texts', + 'lessons.exp-02.desc': 'Legal formulations', + 'lessons.exp-03.title': 'Medical Texts', + 'lessons.exp-03.desc': 'Medical terminology', + 'lessons.exp-04.title': 'Technical Documentation', + 'lessons.exp-04.desc': 'Complex technical texts', + 'lessons.exp-05.title': 'Symbol Marathon', + 'lessons.exp-05.desc': 'All special characters in complex combinations', + 'lessons.exp-06.title': 'Speed Challenge', + 'lessons.exp-06.desc': 'For absolute pros - 60+ WPM goal', + + // Programming Lessons + 'lessons.prog-js-01.title': 'JavaScript Beginner', + 'lessons.prog-js-01.desc': 'First steps with JavaScript', + 'lessons.prog-js-02.title': 'JavaScript Functions', + 'lessons.prog-js-02.desc': 'Functions in JavaScript', + 'lessons.prog-js-03.title': 'JavaScript Intermediate', + 'lessons.prog-js-03.desc': 'Advanced JavaScript concepts', + 'lessons.prog-js-04.title': 'JavaScript Advanced', + 'lessons.prog-js-04.desc': 'Async/Await and Promises', + 'lessons.prog-ts-01.title': 'TypeScript Basics', + 'lessons.prog-ts-01.desc': 'Learning TypeScript typing', + 'lessons.prog-ts-02.title': 'TypeScript Interfaces', + 'lessons.prog-ts-02.desc': 'Interfaces and Types', + 'lessons.prog-ts-03.title': 'TypeScript Advanced', + 'lessons.prog-ts-03.desc': 'Generics and advanced types', + 'lessons.prog-py-01.title': 'Python Beginner', + 'lessons.prog-py-01.desc': 'Python basics', + 'lessons.prog-py-02.title': 'Python Functions', + 'lessons.prog-py-02.desc': 'Functions in Python', + 'lessons.prog-py-03.title': 'Python Intermediate', + 'lessons.prog-py-03.desc': 'Advanced Python', + 'lessons.prog-py-04.title': 'Python OOP', + 'lessons.prog-py-04.desc': 'Object-oriented Python', + 'lessons.prog-py-05.title': 'Python Async', + 'lessons.prog-py-05.desc': 'Asynchronous Python', + 'lessons.prog-java-01.title': 'Java Beginner', + 'lessons.prog-java-01.desc': 'Java basics', + 'lessons.prog-java-02.title': 'Java OOP', + 'lessons.prog-java-02.desc': 'Object orientation in Java', + 'lessons.prog-java-03.title': 'Java Collections & Streams', + 'lessons.prog-java-03.desc': 'Modern Java features', + 'lessons.prog-html-01.title': 'HTML Basics', + 'lessons.prog-html-01.desc': 'HTML fundamentals', + 'lessons.prog-css-01.title': 'CSS Basics', + 'lessons.prog-css-01.desc': 'CSS fundamentals', + 'lessons.prog-react-01.title': 'React Basics', + 'lessons.prog-react-01.desc': 'React component syntax', + 'lessons.prog-angular-01.title': 'Angular Basics', + 'lessons.prog-angular-01.desc': 'Angular component syntax', + 'lessons.prog-sql-01.title': 'SQL Basics', + 'lessons.prog-sql-01.desc': 'SQL fundamentals', + 'lessons.prog-sql-02.title': 'SQL Advanced', + 'lessons.prog-sql-02.desc': 'Advanced SQL queries', + 'lessons.prog-bash-01.title': 'Bash/Shell Basics', + 'lessons.prog-bash-01.desc': 'Shell scripting basics', + 'lessons.prog-git-01.title': 'Git Commands', + 'lessons.prog-git-01.desc': 'The most important Git commands', + + // Shortcut Lessons + 'lessons.shortcuts-vscode-01.title': 'VS Code Essentials', + 'lessons.shortcuts-vscode-01.desc': 'The most important VS Code shortcuts', + 'lessons.shortcuts-vscode-02.title': 'VS Code Advanced', + 'lessons.shortcuts-vscode-02.desc': 'Advanced VS Code shortcuts', + 'lessons.shortcuts-intellij-01.title': 'IntelliJ Essentials', + 'lessons.shortcuts-intellij-01.desc': 'The most important IntelliJ shortcuts', + 'lessons.shortcuts-vim-01.title': 'Vim Basics', + 'lessons.shortcuts-vim-01.desc': 'Basic Vim commands', // Common 'common.wpm': 'WPM', @@ -251,16 +442,48 @@ const en: Translations = { 'regex.playground': 'Playground', // Dev Tools - Algorithms - 'algo.title': 'Algorithm Training', + 'algo.title': 'Algorithm & Framework Training', + 'algo.subtitle': 'Improve your programming skills by typing real code', 'algo.browse': 'Browse', 'algo.practice': 'Practice', 'algo.challenges': 'Challenges', + 'algo.algorithmsData': 'Algorithms & Data Structures', + 'algo.reactTs': 'React & TypeScript', + 'algo.exercises': 'Exercises', + 'algo.all': 'All', + 'algo.beginner': 'Beginner', + 'algo.intermediate': 'Intermediate', + 'algo.advanced': 'Advanced', + 'algo.noExercises': 'No exercises found in this category.', + 'algo.noExercisesHint': 'Choose another category or remove the difficulty filter.', + 'algo.back': '← Back', + 'algo.next': 'Next →', + 'algo.start': 'Start', + 'algo.reset': 'Reset', + 'algo.wpm': 'WPM', + 'algo.accuracy': 'Accuracy', + 'algo.characters': 'Characters', + 'algo.errors': 'Errors', + 'algo.exerciseComplete': 'Exercise complete! {wpm} WPM, {accuracy}% accuracy', + 'algo.allComplete': 'All exercises complete! {wpm} WPM, {accuracy}% accuracy', // Dev Tools - SQL 'sql.title': 'SQL Training', 'sql.commands': 'Commands', 'sql.training': 'Training', 'sql.playground': 'Playground', + 'sql.subtitle': 'Learn SQL by typing real queries', + 'sql.exercises': 'Exercises', + 'sql.selectExercise': 'Select an exercise', + 'sql.selectExerciseDesc': 'Click on a SQL exercise on the left to start', + 'sql.explanation': 'Explanation', + 'sql.skipBtn': 'Skip →', + 'sql.nextBtn': 'Next Exercise →', + 'sql.queryCopied': 'Query copied!', + 'sql.completed': 'Done! +{xp} XP', + 'sql.difficulty.beginner': 'Beginner', + 'sql.difficulty.intermediate': 'Intermediate', + 'sql.difficulty.advanced': 'Expert', // Common Dev Tools 'devtools.difficulty': 'Difficulty', @@ -434,10 +657,202 @@ const de: Translations = { 'lessons.completed': '{completed} von {total} Lektionen abgeschlossen', 'lessons.exerciseOf': 'Übung {current} von {total}', 'lessons.exercises': '{count} Übungen', + 'lessons.exercisesCount': '{count} Übungen', 'lessons.pressToStart': 'Drücke eine Taste, um zu beginnen.', 'lessons.goal': 'Ziel: {wpm} WPM · {accuracy}% Genauigkeit', 'lessons.passed': 'Lektion bestanden! {wpm} WPM · {accuracy}% · +{xp} XP', 'lessons.ended': 'Lektion beendet. {wpm} WPM · {accuracy}%', + 'lessons.completedCount': '{count} abgeschlossen', + + // Lesson Categories + 'lessons.category.beginner': 'Anfänger', + 'lessons.category.beginner.desc': 'Absolute Grundlagen - nur Grundreihe', + 'lessons.category.basics': 'Grundlagen', + 'lessons.category.basics.desc': 'Alle Buchstaben und erste Sätze', + 'lessons.category.intermediate': 'Fortgeschritten', + 'lessons.category.intermediate.desc': 'Zahlen und Sonderzeichen', + 'lessons.category.words': 'Wörter & Sätze', + 'lessons.category.words.desc': 'Wortschatz und Textübungen', + 'lessons.category.advanced': 'Fortgeschritten+', + 'lessons.category.advanced.desc': 'Komplexe Texte und alle Zeichen', + 'lessons.category.expert': 'Experte', + 'lessons.category.expert.desc': 'Maximale Herausforderung', + 'lessons.category.programming': 'Programmierung', + 'lessons.category.programming.desc': 'Code-Snippets in verschiedenen Sprachen', + 'lessons.category.shortcuts': 'IDE Shortcuts', + 'lessons.category.shortcuts.desc': 'Tastenkürzel für VS Code, IntelliJ, Vim', + + // Difficulty Labels + 'lessons.difficulty.beginner': 'Anfänger', + 'lessons.difficulty.easy': 'Leicht', + 'lessons.difficulty.medium': 'Mittel', + 'lessons.difficulty.hard': 'Schwer', + 'lessons.difficulty.expert': 'Experte', + + // Beginner Lessons + 'lessons.beginner-01.title': 'Erste Schritte: F und J', + 'lessons.beginner-01.desc': + 'Lerne die wichtigsten Tasten: F und J haben Erhebungen zur Orientierung', + 'lessons.beginner-02.title': 'Linke Hand: A S D F', + 'lessons.beginner-02.desc': 'Die linke Hand auf der Grundposition', + 'lessons.beginner-03.title': 'Rechte Hand: J K L Ö', + 'lessons.beginner-03.desc': 'Die rechte Hand auf der Grundposition', + 'lessons.beginner-04.title': 'Grundreihe komplett', + 'lessons.beginner-04.desc': 'Beide Hände zusammen auf der Grundreihe', + 'lessons.beginner-05.title': 'Erste einfache Wörter', + 'lessons.beginner-05.desc': 'Wörter nur mit Grundreihe-Buchstaben', + 'lessons.beginner-06.title': 'G und H hinzufügen', + 'lessons.beginner-06.desc': 'Die Zeigefinger bewegen sich zur Mitte', + 'lessons.beginner-07.title': 'Die Leertaste', + 'lessons.beginner-07.desc': 'Lerne die Leertaste mit dem Daumen zu bedienen', + 'lessons.beginner-08.title': 'Ganz kurze Sätze', + 'lessons.beginner-08.desc': 'Erste vollständige Sätze mit Grundreihe', + + // Basic Lessons + 'lessons.basics-01.title': 'Obere Reihe: E und I', + 'lessons.basics-01.desc': 'Die wichtigsten Vokale auf der oberen Reihe', + 'lessons.basics-02.title': 'Obere Reihe: R und U', + 'lessons.basics-02.desc': 'Zeigefinger auf der oberen Reihe', + 'lessons.basics-03.title': 'Obere Reihe komplett', + 'lessons.basics-03.desc': 'Alle Buchstaben der oberen Reihe: QWERTZUIOP', + 'lessons.basics-04.title': 'Untere Reihe: N und M', + 'lessons.basics-04.desc': 'Die wichtigsten Buchstaben der unteren Reihe', + 'lessons.basics-05.title': 'Untere Reihe komplett', + 'lessons.basics-05.desc': 'Alle Buchstaben der unteren Reihe: YXCVBNM', + 'lessons.basics-06.title': 'Alle Buchstaben zusammen', + 'lessons.basics-06.desc': 'Übungen mit allen drei Reihen', + 'lessons.basics-07.title': 'Häufige Wörter Stufe 1', + 'lessons.basics-07.desc': 'Die 50 häufigsten deutschen Wörter', + 'lessons.basics-08.title': 'Großbuchstaben lernen', + 'lessons.basics-08.desc': 'Die Shift-Taste für Großschreibung', + + // Intermediate Lessons + 'lessons.inter-01.title': 'Zahlen 1-5', + 'lessons.inter-01.desc': 'Die Zahlen der linken Hand', + 'lessons.inter-02.title': 'Zahlen 6-0', + 'lessons.inter-02.desc': 'Die Zahlen der rechten Hand', + 'lessons.inter-03.title': 'Alle Zahlen zusammen', + 'lessons.inter-03.desc': 'Übungen mit allen Zahlen 0-9', + 'lessons.inter-04.title': 'Punkt und Komma', + 'lessons.inter-04.desc': 'Die wichtigsten Satzzeichen', + 'lessons.inter-05.title': 'Fragezeichen und Ausrufezeichen', + 'lessons.inter-05.desc': 'Fragen stellen und ausrufen', + 'lessons.inter-06.title': 'Bindestrich und Apostroph', + 'lessons.inter-06.desc': 'Zusammengesetzte Wörter und Auslassungen', + 'lessons.inter-07.title': 'Klammern', + 'lessons.inter-07.desc': 'Runde und eckige Klammern', + 'lessons.inter-08.title': 'Anführungszeichen', + 'lessons.inter-08.desc': 'Zitate und direkte Rede', + + // Word Lessons + 'lessons.words-01.title': 'Die 100 häufigsten Wörter', + 'lessons.words-01.desc': 'Die am meisten verwendeten deutschen Wörter', + 'lessons.words-02.title': 'Verben im Alltag', + 'lessons.words-02.desc': 'Häufig verwendete Verben', + 'lessons.words-03.title': 'Adjektive', + 'lessons.words-03.desc': 'Beschreibende Wörter', + 'lessons.words-04.title': 'Kurze Sätze', + 'lessons.words-04.desc': 'Einfache deutsche Sätze', + 'lessons.words-05.title': 'Mittellange Sätze', + 'lessons.words-05.desc': 'Sätze mit mehr Details', + 'lessons.words-06.title': 'Lange Sätze', + 'lessons.words-06.desc': 'Komplexere deutsche Sätze', + 'lessons.words-07.title': 'Absätze üben', + 'lessons.words-07.desc': 'Zusammenhängende Textpassagen', + 'lessons.words-08.title': 'Thema: Computer', + 'lessons.words-08.desc': 'Fachvokabular aus der Computerwelt', + 'lessons.words-09.title': 'Thema: Büro', + 'lessons.words-09.desc': 'Wörter aus dem Büroalltag', + 'lessons.words-10.title': 'Thema: Reisen', + 'lessons.words-10.desc': 'Vokabular rund ums Reisen', + + // Advanced Lessons + 'lessons.adv-01.title': 'Alle Sonderzeichen Übersicht', + 'lessons.adv-01.desc': 'Systematisches Training aller wichtigen Sonderzeichen', + 'lessons.adv-02.title': 'Mathematische Zeichen', + 'lessons.adv-02.desc': 'Für technische und wissenschaftliche Texte', + 'lessons.adv-03.title': 'Deutsche Klassiker 1', + 'lessons.adv-03.desc': 'Zitate aus der deutschen Literatur', + 'lessons.adv-04.title': 'Geschäftsbriefe', + 'lessons.adv-04.desc': 'Formelle Korrespondenz', + 'lessons.adv-05.title': 'Wissenschaftliche Texte', + 'lessons.adv-05.desc': 'Akademisches Schreiben', + 'lessons.adv-06.title': 'Speed Drill 1', + 'lessons.adv-06.desc': 'Hochfrequenz-Wörter für maximale Geschwindigkeit', + 'lessons.adv-07.title': 'Speed Drill 2', + 'lessons.adv-07.desc': 'Längere Wörter im Schnelldurchlauf', + + // Expert Lessons + 'lessons.exp-01.title': 'Philosophische Texte', + 'lessons.exp-01.desc': 'Anspruchsvolle philosophische Passagen', + 'lessons.exp-02.title': 'Rechtliche Texte', + 'lessons.exp-02.desc': 'Juristische Formulierungen', + 'lessons.exp-03.title': 'Medizinische Texte', + 'lessons.exp-03.desc': 'Fachvokabular aus der Medizin', + 'lessons.exp-04.title': 'Technische Dokumentation', + 'lessons.exp-04.desc': 'Anspruchsvolle technische Texte', + 'lessons.exp-05.title': 'Symbol-Marathon', + 'lessons.exp-05.desc': 'Alle Sonderzeichen in komplexen Kombinationen', + 'lessons.exp-06.title': 'Geschwindigkeits-Challenge', + 'lessons.exp-06.desc': 'Für absolute Profis - 60+ WPM Ziel', + + // Programming Lessons + 'lessons.prog-js-01.title': 'JavaScript Anfänger', + 'lessons.prog-js-01.desc': 'Erste Schritte mit JavaScript', + 'lessons.prog-js-02.title': 'JavaScript Funktionen', + 'lessons.prog-js-02.desc': 'Funktionen in JavaScript', + 'lessons.prog-js-03.title': 'JavaScript Intermediate', + 'lessons.prog-js-03.desc': 'Fortgeschrittene JavaScript-Konzepte', + 'lessons.prog-js-04.title': 'JavaScript Advanced', + 'lessons.prog-js-04.desc': 'Async/Await und Promises', + 'lessons.prog-ts-01.title': 'TypeScript Basics', + 'lessons.prog-ts-01.desc': 'TypeScript-Typisierung lernen', + 'lessons.prog-ts-02.title': 'TypeScript Interfaces', + 'lessons.prog-ts-02.desc': 'Interfaces und Types', + 'lessons.prog-ts-03.title': 'TypeScript Advanced', + 'lessons.prog-ts-03.desc': 'Generics und fortgeschrittene Typen', + 'lessons.prog-py-01.title': 'Python Anfänger', + 'lessons.prog-py-01.desc': 'Python-Grundlagen', + 'lessons.prog-py-02.title': 'Python Funktionen', + 'lessons.prog-py-02.desc': 'Funktionen in Python', + 'lessons.prog-py-03.title': 'Python Intermediate', + 'lessons.prog-py-03.desc': 'Fortgeschrittenes Python', + 'lessons.prog-py-04.title': 'Python OOP', + 'lessons.prog-py-04.desc': 'Objektorientiertes Python', + 'lessons.prog-py-05.title': 'Python Async', + 'lessons.prog-py-05.desc': 'Asynchrones Python', + 'lessons.prog-java-01.title': 'Java Anfänger', + 'lessons.prog-java-01.desc': 'Java-Grundlagen', + 'lessons.prog-java-02.title': 'Java OOP', + 'lessons.prog-java-02.desc': 'Objektorientierung in Java', + 'lessons.prog-java-03.title': 'Java Collections & Streams', + 'lessons.prog-java-03.desc': 'Moderne Java Features', + 'lessons.prog-html-01.title': 'HTML Basics', + 'lessons.prog-html-01.desc': 'HTML-Grundlagen', + 'lessons.prog-css-01.title': 'CSS Basics', + 'lessons.prog-css-01.desc': 'CSS-Grundlagen', + 'lessons.prog-react-01.title': 'React Basics', + 'lessons.prog-react-01.desc': 'React Component Syntax', + 'lessons.prog-angular-01.title': 'Angular Basics', + 'lessons.prog-angular-01.desc': 'Angular Component Syntax', + 'lessons.prog-sql-01.title': 'SQL Basics', + 'lessons.prog-sql-01.desc': 'SQL-Grundlagen', + 'lessons.prog-sql-02.title': 'SQL Advanced', + 'lessons.prog-sql-02.desc': 'Fortgeschrittene SQL-Abfragen', + 'lessons.prog-bash-01.title': 'Bash/Shell Basics', + 'lessons.prog-bash-01.desc': 'Shell-Scripting Grundlagen', + 'lessons.prog-git-01.title': 'Git Commands', + 'lessons.prog-git-01.desc': 'Die wichtigsten Git-Befehle', + + // Shortcut Lessons + 'lessons.shortcuts-vscode-01.title': 'VS Code Essentials', + 'lessons.shortcuts-vscode-01.desc': 'Die wichtigsten VS Code Shortcuts', + 'lessons.shortcuts-vscode-02.title': 'VS Code Advanced', + 'lessons.shortcuts-vscode-02.desc': 'Fortgeschrittene VS Code Shortcuts', + 'lessons.shortcuts-intellij-01.title': 'IntelliJ Essentials', + 'lessons.shortcuts-intellij-01.desc': 'Die wichtigsten IntelliJ Shortcuts', + 'lessons.shortcuts-vim-01.title': 'Vim Basics', + 'lessons.shortcuts-vim-01.desc': 'Grundlegende Vim-Befehle', // Common 'common.wpm': 'WPM', @@ -562,16 +977,48 @@ const de: Translations = { 'regex.playground': 'Spielplatz', // Dev Tools - Algorithms - 'algo.title': 'Algorithmen Training', + 'algo.title': 'Algorithmen & Framework Training', + 'algo.subtitle': 'Verbessere deine Programmierfähigkeiten durch Tippen von echtem Code', 'algo.browse': 'Durchsuchen', 'algo.practice': 'Üben', 'algo.challenges': 'Herausforderungen', + 'algo.algorithmsData': 'Algorithmen & Datenstrukturen', + 'algo.reactTs': 'React & TypeScript', + 'algo.exercises': 'Übungen', + 'algo.all': 'Alle', + 'algo.beginner': 'Anfänger', + 'algo.intermediate': 'Mittel', + 'algo.advanced': 'Fortgeschritten', + 'algo.noExercises': 'Keine Übungen in dieser Kategorie gefunden.', + 'algo.noExercisesHint': 'Wähle eine andere Kategorie oder entferne den Schwierigkeitsfilter.', + 'algo.back': '← Zurück', + 'algo.next': 'Weiter →', + 'algo.start': 'Starten', + 'algo.reset': 'Reset', + 'algo.wpm': 'WPM', + 'algo.accuracy': 'Genauigkeit', + 'algo.characters': 'Zeichen', + 'algo.errors': 'Fehler', + 'algo.exerciseComplete': 'Übung abgeschlossen! {wpm} WPM, {accuracy}% Genauigkeit', + 'algo.allComplete': 'Alle Übungen abgeschlossen! {wpm} WPM, {accuracy}% Genauigkeit', // Dev Tools - SQL 'sql.title': 'SQL Training', 'sql.commands': 'Befehle', 'sql.training': 'Training', 'sql.playground': 'Spielplatz', + 'sql.subtitle': 'Lerne SQL durch Tippen echter Queries', + 'sql.exercises': 'Übungen', + 'sql.selectExercise': 'Wähle eine Übung', + 'sql.selectExerciseDesc': 'Klicke links auf eine SQL-Übung um zu starten', + 'sql.explanation': 'Erklärung', + 'sql.skipBtn': 'Überspringen →', + 'sql.nextBtn': 'Nächste Übung →', + 'sql.queryCopied': 'Query kopiert!', + 'sql.completed': 'Geschafft! +{xp} XP', + 'sql.difficulty.beginner': 'Anfänger', + 'sql.difficulty.intermediate': 'Fortgeschritten', + 'sql.difficulty.advanced': 'Experte', // Common Dev Tools 'devtools.difficulty': 'Schwierigkeit', diff --git a/src/data/algorithmExercises.ts b/src/data/algorithmExercises.ts index 7a0e30e..f0e36db 100644 --- a/src/data/algorithmExercises.ts +++ b/src/data/algorithmExercises.ts @@ -622,13 +622,13 @@ export const ALL_ALGORITHM_SNIPPETS: CodeSnippet[] = [ // Categories for UI display export const ALGORITHM_CATEGORIES = [ { id: 'sorting', name: 'Sortierung', icon: '↕️', snippets: SORTING_ALGORITHMS }, - { id: 'search', name: 'Suche', icon: '🔍', snippets: SEARCH_ALGORITHMS }, - { id: 'data-structures', name: 'Datenstrukturen', icon: '📦', snippets: DATA_STRUCTURES }, + { id: 'search', name: 'Suche', icon: 'search', snippets: SEARCH_ALGORITHMS }, + { id: 'data-structures', name: 'Datenstrukturen', icon: 'box', snippets: DATA_STRUCTURES }, { id: 'dynamic-programming', name: 'Dynamic Programming', - icon: '🧮', + icon: 'calc', snippets: DYNAMIC_PROGRAMMING, }, - { id: 'leetcode', name: 'LeetCode', icon: '💻', snippets: LEETCODE_PROBLEMS }, + { id: 'leetcode', name: 'LeetCode', icon: 'code', snippets: LEETCODE_PROBLEMS }, ]; diff --git a/src/data/frameworkExercises.ts b/src/data/frameworkExercises.ts index b0a335e..6c8b8d3 100644 --- a/src/data/frameworkExercises.ts +++ b/src/data/frameworkExercises.ts @@ -835,9 +835,14 @@ export const ALL_FRAMEWORK_SNIPPETS: CodeSnippet[] = [ // Categories for UI display export const FRAMEWORK_CATEGORIES = [ - { id: 'react-hooks', name: 'React Hooks', icon: '⚛️', snippets: REACT_HOOKS }, - { id: 'react-components', name: 'React Components', icon: '🧩', snippets: REACT_COMPONENTS }, - { id: 'custom-hooks', name: 'Custom Hooks', icon: '🪝', snippets: CUSTOM_HOOKS }, - { id: 'typescript', name: 'TypeScript Patterns', icon: '📘', snippets: TYPESCRIPT_PATTERNS }, - { id: 'testing', name: 'Testing', icon: '🧪', snippets: TESTING_PATTERNS }, + { id: 'react-hooks', name: 'React Hooks', icon: 'react', snippets: REACT_HOOKS }, + { + id: 'react-components', + name: 'React Components', + icon: 'component', + snippets: REACT_COMPONENTS, + }, + { id: 'custom-hooks', name: 'Custom Hooks', icon: 'hook', snippets: CUSTOM_HOOKS }, + { id: 'typescript', name: 'TypeScript Patterns', icon: 'ts', snippets: TYPESCRIPT_PATTERNS }, + { id: 'testing', name: 'Testing', icon: 'test', snippets: TESTING_PATTERNS }, ]; diff --git a/src/data/shortcuts.ts b/src/data/shortcuts.ts index 5cf5600..ade0f73 100644 --- a/src/data/shortcuts.ts +++ b/src/data/shortcuts.ts @@ -7,7 +7,9 @@ export interface Shortcut { id: string; keys: string[]; description: string; + descriptionEn?: string; category: string; + categoryEn?: string; difficulty?: 'beginner' | 'intermediate' | 'advanced'; tags?: string[]; } @@ -16,9 +18,11 @@ export interface Command { id: string; command: string; description: string; + descriptionEn?: string; category: string; + categoryEn?: string; example?: string; - flags?: { flag: string; description: string }[]; + flags?: { flag: string; description: string; descriptionEn?: string }[]; difficulty?: 'beginner' | 'intermediate' | 'advanced'; tags?: string[]; } @@ -26,8 +30,10 @@ export interface Command { export interface ShortcutCollection { id: string; name: string; + nameEn?: string; icon: string; description: string; + descriptionEn?: string; color: string; shortcuts: Shortcut[]; } @@ -35,8 +41,10 @@ export interface ShortcutCollection { export interface CommandCollection { id: string; name: string; + nameEn?: string; icon: string; description: string; + descriptionEn?: string; color: string; commands: Command[]; } @@ -8013,3 +8021,958 @@ export function searchAll(query: string): { shortcuts: Shortcut[]; commands: Com export function getCategories(collection: ShortcutCollection): string[] { return getShortcutCategories(collection); } + +// ============================================================================ +// TRANSLATION MAPS - German to English +// ============================================================================ + +/** + * Category translations from German to English + */ +export const CATEGORY_TRANSLATIONS: Record = { + // VS Code Categories + Allgemein: 'General', + Bearbeiten: 'Editing', + 'Bearbeiten Erweitert': 'Advanced Editing', + Auswahl: 'Selection', + 'Multi-Cursor': 'Multi-Cursor', + 'Suchen & Ersetzen': 'Search & Replace', + Navigation: 'Navigation', + Folding: 'Folding', + Ansicht: 'View', + 'Editor-Verwaltung': 'Editor Management', + Debugging: 'Debugging', + Terminal: 'Terminal', + Git: 'Git', + Refactoring: 'Refactoring', + Snippets: 'Snippets', + IntelliSense: 'IntelliSense', + Breadcrumbs: 'Breadcrumbs', + Problems: 'Problems', + Output: 'Output', + Workspace: 'Workspace', + + // macOS Categories + System: 'System', + Finder: 'Finder', + 'Finder Schnellzugriff': 'Finder Quick Access', + Text: 'Text', + 'Text Navigation': 'Text Navigation', + 'Mission Control': 'Mission Control', + Fenster: 'Window', + Safari: 'Safari', + Bedienungshilfen: 'Accessibility', + 'Microsoft Word': 'Microsoft Word', + 'Microsoft Excel': 'Microsoft Excel', + 'Microsoft PowerPoint': 'Microsoft PowerPoint', + Sonderzeichen: 'Special Characters', + + // IntelliJ Categories + 'Code Intelligence': 'Code Intelligence', + Suchen: 'Search', + Ausführen: 'Run', + Debug: 'Debug', + Lesezeichen: 'Bookmarks', + Tabs: 'Tabs', + 'Live Templates': 'Live Templates', + + // Chrome DevTools Categories + Elements: 'Elements', + Styles: 'Styles', + Console: 'Console', + Debugger: 'Debugger', + Network: 'Network', + Performance: 'Performance', + + // Vim Categories + Modi: 'Modes', + Dateien: 'Files', + + // Xcode Categories + 'Build & Run': 'Build & Run', + 'Interface Builder': 'Interface Builder', + + // Terminal Categories + Auflisten: 'Listing', + Dateioperationen: 'File Operations', + Anzeigen: 'Viewing', + Textverarbeitung: 'Text Processing', + Prozesse: 'Processes', + Netzwerk: 'Network', + Komprimierung: 'Compression', + Pipes: 'Pipes', + History: 'History', + + // Git Categories + Konfiguration: 'Configuration', + Repository: 'Repository', + Workflow: 'Workflow', + Branches: 'Branches', + Merging: 'Merging', + Rückgängig: 'Undo', + Stash: 'Stash', + Tags: 'Tags', + Submodules: 'Submodules', + + // Docker Categories + Images: 'Images', + Container: 'Container', + Volumes: 'Volumes', + Compose: 'Compose', + Cleanup: 'Cleanup', + + // NPM Categories + 'NPM Basics': 'NPM Basics', + 'NPM Erweitert': 'NPM Advanced', + Yarn: 'Yarn', + NPX: 'NPX', + + // Homebrew Categories + Basics: 'Basics', + Wartung: 'Maintenance', + Info: 'Info', + Services: 'Services', + Taps: 'Taps', + Cask: 'Cask', + Erweitert: 'Advanced', +}; + +/** + * Description translations from German to English + * Note: This is a comprehensive mapping of all shortcut descriptions + */ +export const DESCRIPTION_TRANSLATIONS: Record = { + // VS Code - General + 'Schneller Dateizugriff (Quick Open)': 'Quick Open File', + 'Befehlspalette öffnen': 'Open Command Palette', + 'Einstellungen öffnen': 'Open Settings', + 'Tastaturkürzel anzeigen': 'Show Keyboard Shortcuts', + 'Aktiven Tab schließen': 'Close Active Tab', + 'Fenster schließen': 'Close Window', + 'Neues Fenster öffnen': 'Open New Window', + 'Neue Datei erstellen': 'Create New File', + 'Datei öffnen': 'Open File', + 'Datei speichern': 'Save File', + 'Datei speichern unter': 'Save File As', + 'Alle Dateien speichern': 'Save All Files', + 'Alle Tabs schließen': 'Close All Tabs', + 'Tab fixieren (Keep Open)': 'Keep Tab Open', + 'VS Code beenden': 'Quit VS Code', + 'Integriertes Terminal öffnen': 'Open Integrated Terminal', + 'Zuletzt geschlossenen Tab öffnen': 'Reopen Closed Tab', + 'Zwischen offenen Tabs wechseln': 'Switch Between Open Tabs', + 'Datei im Explorer anzeigen': 'Reveal File in Explorer', + 'Dateipfad kopieren': 'Copy File Path', + + // VS Code - Editing + 'Zeile ausschneiden (ohne Auswahl)': 'Cut Line (no selection)', + 'Zeile kopieren (ohne Auswahl)': 'Copy Line (no selection)', + Einfügen: 'Paste', + 'Zeile nach oben verschieben': 'Move Line Up', + 'Zeile nach unten verschieben': 'Move Line Down', + 'Zeile nach oben kopieren': 'Copy Line Up', + 'Zeile nach unten kopieren': 'Copy Line Down', + 'Zeile löschen': 'Delete Line', + 'Neue Zeile darunter einfügen': 'Insert Line Below', + 'Neue Zeile darüber einfügen': 'Insert Line Above', + 'Zur passenden Klammer springen': 'Jump to Matching Bracket', + 'Zeile einrücken': 'Indent Line', + 'Zeile ausrücken': 'Outdent Line', + 'Zeilen-Kommentar umschalten': 'Toggle Line Comment', + 'Block-Kommentar umschalten': 'Toggle Block Comment', + Rückgängig: 'Undo', + Wiederholen: 'Redo', + 'Letzte Cursor-Aktion rückgängig': 'Undo Last Cursor Action', + 'IntelliSense auslösen': 'Trigger IntelliSense', + 'Quick Fix / Schnelle Aktionen': 'Quick Fix / Quick Actions', + + // VS Code - Advanced Editing + 'Auswahl formatieren': 'Format Selection', + 'Dokument formatieren': 'Format Document', + 'Trailing Whitespace entfernen': 'Remove Trailing Whitespace', + 'Sprache der Datei ändern': 'Change File Language', + 'Zu Zeile gehen': 'Go to Line', + 'Zeilen-Kommentar hinzufügen': 'Add Line Comment', + 'Zeilen-Kommentar entfernen': 'Remove Line Comment', + 'Zeichen vertauschen': 'Transpose Characters', + 'Groß-/Kleinschreibung ändern': 'Change Case', + 'Zeilen verbinden': 'Join Lines', + + // VS Code - Selection + 'Wort auswählen / Nächstes Vorkommen hinzufügen': 'Select Word / Add Next Occurrence', + 'Letztes Vorkommen überspringen': 'Skip Last Occurrence', + 'Alle Vorkommen auswählen': 'Select All Occurrences', + 'Aktuelle Zeile auswählen': 'Select Current Line', + 'Alles auswählen': 'Select All', + 'Auswahl erweitern': 'Expand Selection', + 'Auswahl verkleinern': 'Shrink Selection', + 'Auswahl wortweise erweitern': 'Expand Selection by Word', + 'Auswahl wortweise verkleinern': 'Shrink Selection by Word', + 'Bis zum Zeilenende auswählen': 'Select to End of Line', + 'Bis zum Zeilenanfang auswählen': 'Select to Start of Line', + 'Zeile nach oben auswählen': 'Select Line Above', + 'Zeile nach unten auswählen': 'Select Line Below', + 'Alle Vorkommen des Wortes löschen': 'Delete All Occurrences', + + // VS Code - Multi-Cursor + 'Cursor an Position hinzufügen': 'Add Cursor at Position', + 'Cursor oberhalb hinzufügen': 'Add Cursor Above', + 'Cursor unterhalb hinzufügen': 'Add Cursor Below', + 'Cursor bei allen Vorkommen': 'Add Cursor at All Occurrences', + 'Alle Vorkommen ändern': 'Change All Occurrences', + 'Cursor oben einfügen (Column)': 'Insert Cursor Above (Column)', + 'Cursor unten einfügen (Column)': 'Insert Cursor Below (Column)', + 'Cursor am Ende jeder Zeile': 'Add Cursor at End of Each Line', + 'Multi-Cursor beenden': 'Exit Multi-Cursor', + 'Letzten Cursor rückgängig': 'Undo Last Cursor', + + // VS Code - Search & Replace + 'Suchen in Datei': 'Find in File', + 'Ersetzen in Datei': 'Replace in File', + 'In Dateien suchen': 'Find in Files', + 'In Dateien ersetzen': 'Replace in Files', + 'Nächstes Vorkommen finden': 'Find Next Occurrence', + 'Vorheriges Vorkommen finden': 'Find Previous Occurrence', + 'Nächstes Vorkommen (Alternative)': 'Find Next (Alternative)', + 'Vorheriges Vorkommen (Alternative)': 'Find Previous (Alternative)', + 'Alle Treffer auswählen': 'Select All Matches', + 'Groß-/Kleinschreibung umschalten': 'Toggle Case Sensitive', + 'Ganzes Wort umschalten': 'Toggle Whole Word', + 'Regex umschalten': 'Toggle Regex', + 'Suche schließen': 'Close Search', + + // VS Code - Navigation + 'Zu Datei gehen': 'Go to File', + 'Zu Symbol in Datei gehen': 'Go to Symbol in File', + 'Zu Symbol im Workspace gehen': 'Go to Symbol in Workspace', + 'Zum Dateianfang': 'Go to Start of File', + 'Zum Dateiende': 'Go to End of File', + 'Zurück navigieren': 'Navigate Back', + 'Vorwärts navigieren': 'Navigate Forward', + 'Zur Definition gehen': 'Go to Definition', + 'Definition anzeigen (Peek)': 'Peek Definition', + 'Definition zur Seite öffnen': 'Open Definition to Side', + 'Alle Referenzen anzeigen': 'Show All References', + 'Symbol umbenennen': 'Rename Symbol', + 'Hover-Info anzeigen': 'Show Hover Info', + 'Zum nächsten Problem gehen': 'Go to Next Problem', + 'Zum vorherigen Problem gehen': 'Go to Previous Problem', + 'Zum Zeilenanfang': 'Go to Start of Line', + 'Zum Zeilenende': 'Go to End of Line', + 'Wort zurück': 'Word Back', + 'Wort vor': 'Word Forward', + + // VS Code - Folding + 'Bereich einklappen': 'Fold Region', + 'Bereich ausklappen': 'Unfold Region', + 'Alle Unterbereiche einklappen': 'Fold All Subregions', + 'Alle Unterbereiche ausklappen': 'Unfold All Subregions', + 'Alle einklappen': 'Fold All', + 'Alle ausklappen': 'Unfold All', + 'Level 1 einklappen': 'Fold Level 1', + 'Level 2 einklappen': 'Fold Level 2', + 'Level 3 einklappen': 'Fold Level 3', + 'Level 4 einklappen': 'Fold Level 4', + + // VS Code - View + 'Seitenleiste umschalten': 'Toggle Sidebar', + 'Panel umschalten': 'Toggle Panel', + 'Terminal umschalten': 'Toggle Terminal', + 'Explorer anzeigen': 'Show Explorer', + 'Suche anzeigen': 'Show Search', + 'Source Control anzeigen': 'Show Source Control', + 'Debug-Ansicht anzeigen': 'Show Debug', + 'Extensions anzeigen': 'Show Extensions', + 'Zen-Modus umschalten': 'Toggle Zen Mode', + 'Editor-Layout: Einzeln': 'Editor Layout: Single', + 'Editor-Layout: Zwei Spalten': 'Editor Layout: Two Columns', + 'Editor-Layout: Drei Spalten': 'Editor Layout: Three Columns', + 'Vollbild umschalten': 'Toggle Full Screen', + 'Zoom vergrößern': 'Zoom In', + 'Zoom verkleinern': 'Zoom Out', + 'Zoom zurücksetzen': 'Reset Zoom', + 'Editor teilen': 'Split Editor', + 'Editor orthogonal teilen': 'Split Editor Orthogonally', + 'Markdown-Vorschau': 'Markdown Preview', + 'Markdown-Vorschau zur Seite': 'Markdown Preview to Side', + + // VS Code - Editor Management + 'Erste Editor-Gruppe fokussieren': 'Focus First Editor Group', + 'Zweite Editor-Gruppe fokussieren': 'Focus Second Editor Group', + 'Dritte Editor-Gruppe fokussieren': 'Focus Third Editor Group', + 'Fokus auf linke Gruppe': 'Focus Left Group', + 'Fokus auf rechte Gruppe': 'Focus Right Group', + 'Fokus auf obere Gruppe': 'Focus Above Group', + 'Fokus auf untere Gruppe': 'Focus Below Group', + 'Editor in linke Gruppe verschieben': 'Move Editor to Left Group', + 'Editor in rechte Gruppe verschieben': 'Move Editor to Right Group', + 'Nächsten Tab öffnen': 'Open Next Tab', + 'Vorherigen Tab öffnen': 'Open Previous Tab', + + // VS Code - Debugging + 'Debugging starten / fortsetzen': 'Start / Continue Debugging', + 'Debugging stoppen': 'Stop Debugging', + 'Debugging neu starten': 'Restart Debugging', + 'Breakpoint umschalten': 'Toggle Breakpoint', + 'Step Over': 'Step Over', + 'Step Into': 'Step Into', + 'Step Out': 'Step Out', + 'Debug Hover Info': 'Debug Hover Info', + 'Inline Breakpoint': 'Inline Breakpoint', + 'Debugging pausieren': 'Pause Debugging', + + // VS Code - Terminal + 'Terminal anzeigen/verbergen': 'Show/Hide Terminal', + 'Neues Terminal erstellen': 'Create New Terminal', + 'Terminal teilen': 'Split Terminal', + 'Im Terminal nach oben scrollen': 'Scroll Up in Terminal', + 'Im Terminal nach unten scrollen': 'Scroll Down in Terminal', + 'Terminal leeren': 'Clear Terminal', + 'Vorheriges Terminal': 'Previous Terminal', + 'Nächstes Terminal': 'Next Terminal', + 'Auswahl kopieren (Terminal)': 'Copy Selection (Terminal)', + 'In Terminal einfügen': 'Paste into Terminal', + + // VS Code - Git + 'Source Control öffnen': 'Open Source Control', + 'Git: Diff anzeigen': 'Git: Show Diff', + 'Commit (in Source Control)': 'Commit (in Source Control)', + + // VS Code - Refactoring + 'Code-Aktionen / Quick Fix': 'Code Actions / Quick Fix', + 'Refactoring-Menü öffnen': 'Open Refactoring Menu', + 'Alle Referenzen umbenennen': 'Rename All References', + + // VS Code - Snippets + 'Snippet-Vorschläge anzeigen': 'Show Snippet Suggestions', + 'Snippet einfügen/erweitern': 'Insert/Expand Snippet', + 'Zum vorherigen Snippet-Placeholder': 'Previous Snippet Placeholder', + + // VS Code - IntelliSense + 'Parameter-Hinweise anzeigen': 'Show Parameter Hints', + 'Quick Info anzeigen': 'Show Quick Info', + 'Vorschlag akzeptieren': 'Accept Suggestion', + 'Vorschlag akzeptieren (Tab)': 'Accept Suggestion (Tab)', + 'IntelliSense schließen': 'Close IntelliSense', + + // VS Code - Breadcrumbs + 'Breadcrumbs fokussieren': 'Focus Breadcrumbs', + 'Breadcrumb auswählen': 'Select Breadcrumb', + 'Zum vorherigen Breadcrumb': 'Previous Breadcrumb', + 'Zum nächsten Breadcrumb': 'Next Breadcrumb', + + // VS Code - Problems + 'Problems-Panel umschalten': 'Toggle Problems Panel', + + // VS Code - Output + 'Output-Panel anzeigen': 'Show Output Panel', + 'Debug Console anzeigen': 'Show Debug Console', + + // VS Code - Workspace + 'Ordner in neuem Fenster öffnen': 'Open Folder in New Window', + 'Ordner aus Workspace entfernen': 'Remove Folder from Workspace', + 'Aktive Datei in neuem Fenster': 'Open Active File in New Window', + + // macOS - System + 'Spotlight öffnen': 'Open Spotlight', + 'Screenshot (ganzer Bildschirm)': 'Screenshot (Full Screen)', + 'Screenshot (Auswahl)': 'Screenshot (Selection)', + 'Screenshot (Fenster)': 'Screenshot (Window)', + 'Screenshot-Werkzeug öffnen': 'Open Screenshot Tool', + 'Screenshot Touch Bar': 'Screenshot Touch Bar', + 'App wechseln (vorwärts)': 'Switch App (Forward)', + 'App wechseln (rückwärts)': 'Switch App (Backward)', + 'Zwischen Fenstern einer App wechseln': 'Switch Between Windows of App', + 'App beenden': 'Quit App', + 'App ausblenden': 'Hide App', + 'Andere Apps ausblenden': 'Hide Other Apps', + 'Fenster minimieren': 'Minimize Window', + 'Alle Fenster minimieren': 'Minimize All Windows', + 'Fenster/Tab schließen': 'Close Window/Tab', + 'Alle Fenster schließen': 'Close All Windows', + 'Einstellungen der App': 'App Settings', + 'Sofort beenden Dialog': 'Force Quit Dialog', + 'Bildschirm sperren': 'Lock Screen', + 'Mac neu starten': 'Restart Mac', + 'Bildschirme ausschalten': 'Turn Off Displays', + 'Alle Apps beenden und neu starten': 'Quit All Apps and Restart', + 'Mac in Ruhezustand': 'Put Mac to Sleep', + 'Emoji & Symbole': 'Emoji & Symbols', + + // macOS - Finder + 'Neues Finder-Fenster': 'New Finder Window', + 'Neuen Ordner erstellen': 'Create New Folder', + 'Neuer intelligenter Ordner': 'New Smart Folder', + 'Neuer Tab': 'New Tab', + 'In Papierkorb verschieben': 'Move to Trash', + 'Papierkorb leeren': 'Empty Trash', + 'Sofort löschen (ohne Papierkorb)': 'Delete Immediately (Without Trash)', + Öffnen: 'Open', + 'Informationen anzeigen': 'Get Info', + 'Informationen (ein Fenster für alle)': 'Get Info (Single Window)', + Duplizieren: 'Duplicate', + 'Volume auswerfen': 'Eject Volume', + 'Quick Look': 'Quick Look', + 'Quick Look (Alternative)': 'Quick Look (Alternative)', + 'Versteckte Dateien anzeigen/verbergen': 'Show/Hide Hidden Files', + 'Gehe zu Ordner...': 'Go to Folder...', + 'Übergeordneten Ordner öffnen': 'Open Parent Folder', + 'Ausgewähltes Element öffnen': 'Open Selected Item', + 'Übergeordneten Ordner in neuem Fenster': 'Open Parent in New Window', + 'Symbol-Ansicht': 'Icon View', + 'Listen-Ansicht': 'List View', + 'Spalten-Ansicht': 'Column View', + 'Galerie-Ansicht': 'Gallery View', + Darstellungsoptionen: 'View Options', + 'Mit Server verbinden': 'Connect to Server', + 'Alias erstellen': 'Create Alias', + 'Original anzeigen (bei Alias)': 'Show Original (for Alias)', + Suchen: 'Search', + Zurück: 'Back', + Vorwärts: 'Forward', + + // macOS - Finder Quick Access + 'Programme-Ordner': 'Applications Folder', + Computer: 'Computer', + Schreibtisch: 'Desktop', + 'Alle meine Dateien': 'All My Files', + 'Gehe zu Ordner': 'Go to Folder', + Benutzerordner: 'Home Folder', + 'iCloud Drive': 'iCloud Drive', + Netzwerk: 'Network', + Dokumente: 'Documents', + AirDrop: 'AirDrop', + Dienstprogramme: 'Utilities', + Downloads: 'Downloads', + + // macOS - Text + Kopieren: 'Copy', + Ausschneiden: 'Cut', + 'Einfügen ohne Formatierung': 'Paste Without Formatting', + 'Einfügen mit Stil anpassen': 'Paste and Match Style', + Fett: 'Bold', + Kursiv: 'Italic', + Unterstrichen: 'Underline', + 'Schriften einblenden': 'Show Fonts', + Weitersuchen: 'Find Next', + 'Rückwärts suchen': 'Find Previous', + 'Auswahl zum Suchen verwenden': 'Use Selection for Find', + 'Zur Auswahl springen': 'Jump to Selection', + 'Rechtschreibung prüfen': 'Check Spelling', + 'Rechtschreibung und Grammatik': 'Spelling and Grammar', + + // macOS - Text Navigation + 'Zum Dokumentanfang': 'Go to Start of Document', + 'Zum Dokumentende': 'Go to End of Document', + 'Ein Wort zurück': 'One Word Back', + 'Ein Wort vor': 'One Word Forward', + 'Zum Zeilenanfang (Emacs)': 'Go to Start of Line (Emacs)', + 'Zum Zeilenende (Emacs)': 'Go to End of Line (Emacs)', + 'Eine Zeile hoch': 'One Line Up', + 'Eine Zeile runter': 'One Line Down', + 'Ein Zeichen zurück': 'One Character Back', + 'Ein Zeichen vor': 'One Character Forward', + 'Zeichen nach Cursor löschen': 'Delete Character After Cursor', + 'Text bis Zeilenende löschen': 'Delete to End of Line', + 'Zeichen vor Cursor löschen': 'Delete Character Before Cursor', + 'Neue Zeile nach Cursor': 'New Line After Cursor', + 'Entf (Forward Delete)': 'Forward Delete', + 'Wort löschen': 'Delete Word', + 'Bis Zeilenanfang löschen': 'Delete to Start of Line', + + // macOS - Mission Control + 'App-Fenster anzeigen': 'Show App Windows', + 'Einen Space nach links': 'Move One Space Left', + 'Einen Space nach rechts': 'Move One Space Right', + 'Zu Space 1 wechseln': 'Switch to Space 1', + 'Zu Space 2 wechseln': 'Switch to Space 2', + 'Zu Space 3 wechseln': 'Switch to Space 3', + 'Schreibtisch anzeigen': 'Show Desktop', + 'Dashboard anzeigen': 'Show Dashboard', + + // macOS - Window + 'Vollbildmodus umschalten': 'Toggle Full Screen Mode', + 'Andere ausblenden': 'Hide Others', + 'Neues Fenster': 'New Window', + + // macOS - Safari + 'Adresszeile fokussieren': 'Focus Address Bar', + 'Tab schließen': 'Close Tab', + 'Letzten Tab wiederherstellen': 'Restore Last Tab', + 'Seite neu laden': 'Reload Page', + 'Seite ohne Cache neu laden': 'Reload Page Without Cache', + 'Lesezeichen-Leiste ein/aus': 'Toggle Bookmarks Bar', + 'Leseliste anzeigen': 'Show Reading List', + 'Lesezeichen hinzufügen': 'Add Bookmark', + 'Web-Suche (Google etc.)': 'Web Search (Google etc.)', + 'Zu Tab 1': 'Go to Tab 1', + 'Zu Tab 2': 'Go to Tab 2', + 'Zum letzten Tab': 'Go to Last Tab', + 'Tab-Übersicht': 'Tab Overview', + 'Web Inspector': 'Web Inspector', + 'JavaScript-Konsole': 'JavaScript Console', + + // macOS - Accessibility + 'VoiceOver ein/aus': 'Toggle VoiceOver', + 'Zoom ein/aus': 'Toggle Zoom', + Vergrößern: 'Zoom In', + Verkleinern: 'Zoom Out', + 'Farben umkehren': 'Invert Colors', + 'Tastatursteuerung aktivieren': 'Enable Keyboard Control', + + // macOS - Microsoft Word + 'Neues Dokument': 'New Document', + 'Dokument öffnen': 'Open Document', + Speichern: 'Save', + 'Speichern unter': 'Save As', + Drucken: 'Print', + 'Zentriert ausrichten': 'Center Align', + 'Links ausrichten': 'Left Align', + 'Rechts ausrichten': 'Right Align', + Blocksatz: 'Justify', + 'Schrift vergrößern': 'Increase Font Size', + 'Schrift verkleinern': 'Decrease Font Size', + 'Hyperlink einfügen': 'Insert Hyperlink', + KAPITÄLCHEN: 'Small Caps', + 'Schriftart Dialog': 'Font Dialog', + 'Formatierung entfernen': 'Remove Formatting', + 'Überschrift 1': 'Heading 1', + 'Überschrift 2': 'Heading 2', + 'Überschrift 3': 'Heading 3', + Seitenumbruch: 'Page Break', + Abschnittswechsel: 'Section Break', + 'Suchen und Ersetzen': 'Find and Replace', + + // macOS - Microsoft Excel + 'Neue Arbeitsmappe': 'New Workbook', + 'Arbeitsmappe öffnen': 'Open Workbook', + 'Spalte auswählen': 'Select Column', + 'Zeile auswählen': 'Select Row', + 'Zeile/Spalte einfügen': 'Insert Row/Column', + 'Zeile/Spalte löschen': 'Delete Row/Column', + Währungsformat: 'Currency Format', + Prozentformat: 'Percentage Format', + Datumsformat: 'Date Format', + Zahlenformat: 'Number Format', + 'Zelle bearbeiten': 'Edit Cell', + 'Eingabe bestätigen (runter)': 'Confirm Input (Down)', + 'Eingabe bestätigen (rechts)': 'Confirm Input (Right)', + 'Eingabe abbrechen': 'Cancel Input', + AutoSumme: 'AutoSum', + 'Formeln anzeigen/verbergen': 'Show/Hide Formulas', + 'Zu Zelle A1': 'Go to Cell A1', + 'Zur letzten Zelle': 'Go to Last Cell', + 'Gehe zu Zelle': 'Go to Cell', + 'Absoluter/Relativer Zellbezug': 'Toggle Absolute/Relative Reference', + 'Aktuelles Datum einfügen': 'Insert Current Date', + 'Aktuelle Uhrzeit einfügen': 'Insert Current Time', + AutoFilter: 'AutoFilter', + 'AutoSumme (Alternative)': 'AutoSum (Alternative)', + + // macOS - Microsoft PowerPoint + 'Neue Präsentation': 'New Presentation', + 'Präsentation öffnen': 'Open Presentation', + 'Neue Folie': 'New Slide', + 'Folie duplizieren': 'Duplicate Slide', + 'Präsentation starten (Anfang)': 'Start Presentation (Beginning)', + 'Präsentation starten (aktuelle Folie)': 'Start Presentation (Current Slide)', + 'Präsentation beenden': 'End Presentation', + 'Nächste Folie (Präsentation)': 'Next Slide (Presentation)', + 'Vorherige Folie (Präsentation)': 'Previous Slide (Presentation)', + 'Schwarzer Bildschirm': 'Black Screen', + 'Weißer Bildschirm': 'White Screen', + 'Objekte gruppieren': 'Group Objects', + 'Gruppierung aufheben': 'Ungroup Objects', + Zentriert: 'Center', + 'Objekt duplizieren': 'Duplicate Object', + 'In Vordergrund': 'Bring to Front', + 'In Hintergrund': 'Send to Back', + 'Eine Ebene nach vorne': 'Bring Forward', + 'Eine Ebene nach hinten': 'Send Backward', + + // macOS - Special Characters + 'É (Akut)': 'É (Acute)', + 'Ü (Umlaut)': 'Ü (Umlaut)', + 'ß (Eszett)': 'ß (Eszett)', + '© (Copyright)': '© (Copyright)', + '® (Registered)': '® (Registered)', + '™ (Trademark)': '™ (Trademark)', + '• (Bullet)': '• (Bullet)', + '– (Gedankenstrich)': '– (En Dash)', + '— (langer Strich)': '— (Em Dash)', + + // IntelliJ - General + 'Aktion suchen': 'Find Action', + 'Überall suchen (Search Everywhere)': 'Search Everywhere', + Projektstruktur: 'Project Structure', + 'Editor maximieren/minimieren': 'Toggle Editor Maximization', + 'Alles speichern': 'Save All', + 'Dateisystem synchronisieren': 'Synchronize File System', + 'Kürzlich ausgeführte Konfigurationen': 'Recent Run Configurations', + 'IDE beenden': 'Quit IDE', + 'Spaltenauswahl-Modus': 'Column Selection Mode', + + // IntelliJ - Navigation + 'Klasse öffnen': 'Open Class', + 'Symbol öffnen': 'Open Symbol', + 'Letzte Dateien': 'Recent Files', + 'Letzte Bearbeitungsstellen': 'Recent Edit Locations', + 'Zur Deklaration gehen': 'Go to Declaration', + 'Zur Implementierung gehen': 'Go to Implementation', + 'Zur Super-Methode/Klasse gehen': 'Go to Super Method/Class', + 'Zum Typ gehen': 'Go to Type', + 'Zum nächsten Fehler': 'Go to Next Error', + 'Zum vorherigen Fehler': 'Go to Previous Error', + 'Zur letzten Position': 'Go to Last Position', + 'Zur nächsten Position': 'Go to Next Position', + 'Dateistruktur anzeigen': 'Show File Structure', + 'Typ-Hierarchie': 'Type Hierarchy', + 'Methoden-Hierarchie': 'Method Hierarchy', + 'Aufruf-Hierarchie': 'Call Hierarchy', + 'Zur Navigationsleiste': 'Go to Navigation Bar', + 'Quelle bearbeiten': 'Edit Source', + 'Quelle anzeigen': 'View Source', + 'Datei in View auswählen': 'Select File in View', + 'Terminal öffnen': 'Open Terminal', + + // IntelliJ - Editing + 'Zeile duplizieren': 'Duplicate Line', + 'Statement vervollständigen': 'Complete Statement', + 'Statement nach oben verschieben': 'Move Statement Up', + 'Statement nach unten verschieben': 'Move Statement Down', + 'Zeilen-Kommentar': 'Line Comment', + 'Block-Kommentar': 'Block Comment', + 'Code formatieren': 'Format Code', + 'Einrückung korrigieren': 'Correct Indentation', + 'Imports optimieren': 'Optimize Imports', + 'Code-Block aufklappen': 'Expand Code Block', + 'Code-Block zuklappen': 'Collapse Code Block', + 'Alle aufklappen': 'Expand All', + 'Alle zuklappen': 'Collapse All', + + // IntelliJ - Code Intelligence + 'Basic Autocomplete': 'Basic Autocomplete', + 'Smart Autocomplete': 'Smart Autocomplete', + 'Parameter-Info': 'Parameter Info', + 'Quick Documentation': 'Quick Documentation', + 'Code generieren (Generate)': 'Generate Code', + 'Methoden überschreiben': 'Override Methods', + 'Methoden implementieren': 'Implement Methods', + 'Surround with...': 'Surround with...', + 'Intention Actions / Quick Fix': 'Intention Actions / Quick Fix', + 'Live Template einfügen': 'Insert Live Template', + 'Mit Live Template umschließen': 'Surround with Live Template', + 'Externe Dokumentation': 'External Documentation', + + // IntelliJ - Selection + 'Nächstes Vorkommen auswählen': 'Select Next Occurrence', + 'Auswahl aufheben': 'Deselect', + + // IntelliJ - Refactoring + Umbenennen: 'Rename', + 'Signatur ändern': 'Change Signature', + Inline: 'Inline', + 'Methode extrahieren': 'Extract Method', + 'Variable extrahieren': 'Extract Variable', + 'Feld extrahieren': 'Extract Field', + 'Konstante extrahieren': 'Extract Constant', + 'Parameter extrahieren': 'Extract Parameter', + 'Refactor This (Menü)': 'Refactor This (Menu)', + Verschieben: 'Move', + 'Safe Delete': 'Safe Delete', + + // IntelliJ - Search + Ersetzen: 'Replace', + 'Nächstes Vorkommen': 'Next Occurrence', + 'Vorheriges Vorkommen': 'Previous Occurrence', + 'Verwendungen finden': 'Find Usages', + 'Verwendungen anzeigen': 'Show Usages', + 'Verwendungen in Datei hervorheben': 'Highlight Usages in File', + + // IntelliJ - Run & Debug + Ausführen: 'Run', + 'Debug starten': 'Start Debug', + 'Konfiguration wählen und ausführen': 'Select Configuration and Run', + 'Konfiguration wählen und debuggen': 'Select Configuration and Debug', + Stoppen: 'Stop', + 'Force Step Into': 'Force Step Into', + 'Zum Cursor ausführen': 'Run to Cursor', + 'Weiter (Resume)': 'Resume', + 'Breakpoints anzeigen': 'Show Breakpoints', + 'Ausdruck auswerten': 'Evaluate Expression', + 'Ausführungskonfiguration bearbeiten': 'Edit Run Configuration', + + // IntelliJ - Git + Commit: 'Commit', + Push: 'Push', + 'Update/Pull': 'Update/Pull', + 'VCS-Operationen-Menü': 'VCS Operations Menu', + 'Datei zu VCS hinzufügen': 'Add File to VCS', + 'Diff anzeigen': 'Show Diff', + 'Änderungen verwerfen': 'Discard Changes', + 'Commit-Verlauf': 'Commit History', + 'Annotate (Blame)': 'Annotate (Blame)', + + // IntelliJ - Tool Windows + 'Projekt-Fenster': 'Project Window', + Favoriten: 'Favorites', + 'Find-Fenster': 'Find Window', + 'Run-Fenster': 'Run Window', + 'Debug-Fenster': 'Debug Window', + 'Problems-Fenster': 'Problems Window', + 'Struktur-Fenster': 'Structure Window', + 'Services-Fenster': 'Services Window', + 'Git-Fenster': 'Git Window', + 'Alle Tool-Fenster verbergen': 'Hide All Tool Windows', + 'Zurück zum Editor': 'Return to Editor', + 'Tool-Fenster schließen und fokussieren': 'Close and Focus Tool Window', + + // IntelliJ - Bookmarks + 'Lesezeichen umschalten': 'Toggle Bookmark', + 'Lesezeichen mit Mnemonik': 'Bookmark with Mnemonic', + 'Zu Lesezeichen 0 gehen': 'Go to Bookmark 0', + 'Alle Lesezeichen anzeigen': 'Show All Bookmarks', + + // IntelliJ - Tabs + 'Vorheriger Tab': 'Previous Tab', + 'Nächster Tab': 'Next Tab', + + // IntelliJ - Live Templates + 'Code mit Template umschließen': 'Surround with Template', + + // IntelliJ - Multi-Cursor + 'Cursor hinzufügen': 'Add Cursor', + 'Mehrere Cursor (gedrückt halten + Pfeile)': 'Multiple Cursors (Hold + Arrows)', + + // Chrome DevTools + 'DevTools öffnen/schließen': 'Open/Close DevTools', + 'Console öffnen': 'Open Console', + 'Element untersuchen': 'Inspect Element', + 'Element-Selektor': 'Element Selector', + 'Command Menu': 'Command Menu', + 'Device Mode umschalten': 'Toggle Device Mode', + 'Drawer umschalten': 'Toggle Drawer', + 'Dock-Position wechseln': 'Change Dock Position', + 'Vorheriges Panel': 'Previous Panel', + 'Nächstes Panel': 'Next Panel', + 'Element auswählen': 'Select Element', + 'Element auf-/zuklappen': 'Expand/Collapse Element', + 'Attribut bearbeiten': 'Edit Attribute', + 'Nächstes Attribut': 'Next Attribute', + 'Element verstecken': 'Hide Element', + 'Als HTML bearbeiten': 'Edit as HTML', + 'Element löschen': 'Delete Element', + 'Eigenschaft bearbeiten': 'Edit Property', + 'Nächste Eigenschaft': 'Next Property', + 'Farbformat wechseln': 'Change Color Format', + 'Wert um 1 ändern': 'Change Value by 1', + 'Wert um 10 ändern': 'Change Value by 10', + 'Wert um 100 ändern': 'Change Value by 100', + 'Wert um 0.1 ändern': 'Change Value by 0.1', + 'Console leeren': 'Clear Console', + Autovervollständigung: 'Autocomplete', + 'Vorherige Befehle': 'Previous Commands', + 'Ausdruck ausführen': 'Execute Expression', + 'Mehrzeilige Eingabe': 'Multi-line Input', + 'Pausieren/Fortsetzen': 'Pause/Resume', + 'Breakpoint setzen': 'Set Breakpoint', + 'Breakpoint aktivieren/deaktivieren': 'Enable/Disable Breakpoint', + 'Alle Breakpoints deaktivieren': 'Disable All Breakpoints', + 'Zu Funktion gehen': 'Go to Function', + 'Recording starten/stoppen': 'Start/Stop Recording', + 'Requests löschen': 'Clear Requests', + 'Ausgewählten Request wiederholen': 'Repeat Selected Request', + 'In Requests suchen': 'Search Requests', + 'Profil speichern': 'Save Profile', + 'Profil laden': 'Load Profile', + + // Vim - Modes + 'Insert Mode (vor Cursor)': 'Insert Mode (Before Cursor)', + 'Insert am Zeilenanfang': 'Insert at Line Start', + 'Append (nach Cursor)': 'Append (After Cursor)', + 'Append am Zeilenende': 'Append at Line End', + 'Neue Zeile darunter': 'New Line Below', + 'Neue Zeile darüber': 'New Line Above', + 'Normal Mode': 'Normal Mode', + 'Visual Mode': 'Visual Mode', + 'Visual Line Mode': 'Visual Line Mode', + 'Visual Block Mode': 'Visual Block Mode', + + // Vim - Navigation + Links: 'Left', + Runter: 'Down', + Hoch: 'Up', + Rechts: 'Right', + 'Nächstes Wort': 'Next Word', + 'Vorheriges Wort': 'Previous Word', + Wortende: 'End of Word', + Zeilenanfang: 'Start of Line', + 'Erstes Zeichen': 'First Character', + Dateianfang: 'Start of File', + Dateiende: 'End of File', + 'Zu Zeile n': 'Go to Line n', + 'Passende Klammer': 'Matching Bracket', + 'Seite vor': 'Page Down', + 'Seite zurück': 'Page Up', + + // Vim - Editing + 'Zeichen löschen': 'Delete Character', + 'Bis Zeilenende löschen': 'Delete to End of Line', + 'Zeile kopieren': 'Copy Line', + 'Wort kopieren': 'Copy Word', + 'Nach Cursor einfügen': 'Paste After Cursor', + 'Vor Cursor einfügen': 'Paste Before Cursor', + 'Letzten Befehl wiederholen': 'Repeat Last Command', + 'Zeichen ersetzen': 'Replace Character', + 'Replace Mode': 'Replace Mode', + 'Wort ändern': 'Change Word', + 'Zeile ändern': 'Change Line', + Einrücken: 'Indent', + Ausrücken: 'Outdent', + + // Vim - Search + 'Vorwärts suchen': 'Search Forward', + 'Rückwärts suchen (Vim)': 'Search Backward', + 'Nächstes Ergebnis': 'Next Result', + 'Vorheriges Ergebnis': 'Previous Result', + 'Wort unter Cursor suchen': 'Search Word Under Cursor', + 'In Zeile ersetzen': 'Replace in Line', + 'Global ersetzen': 'Replace Globally', + + // Vim - Files + Beenden: 'Quit', + 'Speichern und beenden': 'Save and Quit', + 'Ohne speichern beenden': 'Quit Without Saving', + + // Xcode + 'Quick Open': 'Quick Open', + 'Im Navigator zeigen': 'Reveal in Navigator', + Einstellungen: 'Preferences', + 'Navigator ein/aus': 'Toggle Navigator', + 'Utilities ein/aus': 'Toggle Utilities', + 'Debug-Bereich ein/aus': 'Toggle Debug Area', + Build: 'Build', + Run: 'Run', + Stop: 'Stop', + 'Tests ausführen': 'Run Tests', + 'Clean Build Folder': 'Clean Build Folder', + Analyze: 'Analyze', + 'Run ohne Build': 'Run Without Building', + 'Project Navigator': 'Project Navigator', + 'Source Control Navigator': 'Source Control Navigator', + 'Symbol Navigator': 'Symbol Navigator', + 'Find Navigator': 'Find Navigator', + 'Issue Navigator': 'Issue Navigator', + 'Test Navigator': 'Test Navigator', + 'Debug Navigator': 'Debug Navigator', + 'Breakpoint Navigator': 'Breakpoint Navigator', + 'Report Navigator': 'Report Navigator', + 'Zur Definition': 'Go to Definition', + 'Kommentar umschalten': 'Toggle Comment', + 'Einrücken links': 'Indent Left', + 'Einrücken rechts': 'Indent Right', + 'Code falten': 'Fold Code', + 'Code auffalten': 'Unfold Code', + 'Breakpoints aktivieren/deaktivieren': 'Enable/Disable Breakpoints', + Continue: 'Continue', + 'Assistant Editor': 'Assistant Editor', + 'Standard Editor': 'Standard Editor', + 'Size to Fit': 'Size to Fit', + 'File Inspector': 'File Inspector', + 'Attributes Inspector': 'Attributes Inspector', + 'Size Inspector': 'Size Inspector', + 'Library öffnen': 'Open Library', + + // Terminal Commands - additional descriptions would go here + // Git Commands, Docker Commands, NPM Commands, Homebrew Commands + // (These are already largely technical terms that don't need translation) +}; + +/** + * Collection name translations + */ +export const COLLECTION_TRANSLATIONS: Record = { + vscode: { + name: 'VS Code', + description: 'Visual Studio Code keyboard shortcuts for macOS', + }, + macos: { + name: 'macOS', + description: 'macOS system keyboard shortcuts', + }, + intellij: { + name: 'IntelliJ IDEA', + description: 'IntelliJ IDEA / JetBrains IDE keyboard shortcuts', + }, + devtools: { + name: 'Chrome DevTools', + description: 'Chrome Developer Tools keyboard shortcuts', + }, + vim: { + name: 'Vim', + description: 'Vim editor basic commands', + }, + xcode: { + name: 'Xcode', + description: 'Xcode IDE keyboard shortcuts for iOS/macOS development', + }, + terminal: { + name: 'Terminal', + description: 'Shell commands for macOS Terminal (zsh/bash)', + }, + git: { + name: 'Git', + description: 'Git version control commands', + }, + docker: { + name: 'Docker', + description: 'Docker container commands', + }, + npm: { + name: 'NPM / Yarn', + description: 'Node Package Manager commands', + }, + homebrew: { + name: 'Homebrew', + description: 'macOS package manager', + }, +}; + +/** + * Get translated description based on language + */ +export function getTranslatedDescription(description: string, language: 'en' | 'de'): string { + if (language === 'de') { + return description; + } + return DESCRIPTION_TRANSLATIONS[description] || description; +} + +/** + * Get translated category based on language + */ +export function getTranslatedCategory(category: string, language: 'en' | 'de'): string { + if (language === 'de') { + return category; + } + return CATEGORY_TRANSLATIONS[category] || category; +} + +/** + * Get translated collection info based on language + */ +export function getTranslatedCollectionInfo( + collectionId: string, + language: 'en' | 'de' +): { name: string; description: string } | undefined { + if (language === 'de') { + const collection = + ALL_SHORTCUT_COLLECTIONS.find(c => c.id === collectionId) || + ALL_COMMAND_COLLECTIONS.find(c => c.id === collectionId); + if (collection) { + return { name: collection.name, description: collection.description }; + } + return undefined; + } + return COLLECTION_TRANSLATIONS[collectionId]; +} diff --git a/src/main.ts b/src/main.ts index 1a89159..346c8d6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -152,7 +152,7 @@ function shutdownServer(): void { color: var(--text-primary); font-family: var(--font-sans); "> -

Auf Wiedersehen! 👋

+

Auf Wiedersehen!

KeyboardWriter wird beendet...

diff --git a/src/pages/AlgorithmTrainingPage.ts b/src/pages/AlgorithmTrainingPage.ts index 9dd85b5..975c023 100644 --- a/src/pages/AlgorithmTrainingPage.ts +++ b/src/pages/AlgorithmTrainingPage.ts @@ -1,916 +1,1068 @@ /** * Algorithm Training Page - * Specialized typing practice for algorithms and data structures + * Beautiful step-by-step algorithm visualizations + * Includes sorting, searching, graph, and tree algorithms */ -import { VirtualKeyboard } from '../components/keyboard/VirtualKeyboard'; -import { EventBus, Store } from '../core'; -import { ALGORITHM_CATEGORIES } from '../data/algorithmExercises'; -import { FRAMEWORK_CATEGORIES } from '../data/frameworkExercises'; -import { CodeSnippet } from '../data/programmingExercises'; - -type CategoryType = 'algorithms' | 'frameworks'; -type Difficulty = 'beginner' | 'intermediate' | 'advanced'; - -interface Category { - id: string; +import { AlgorithmVisualizer } from '../components/visualization/AlgorithmVisualizer'; +import { GraphVisualizer } from '../components/visualization/GraphVisualizer'; + +type AlgorithmType = + | 'bubble-sort' + | 'quick-sort' + | 'insertion-sort' + | 'selection-sort' + | 'merge-sort' + | 'heap-sort' + | 'binary-search' + | 'linear-search' + | 'dijkstra' + | 'a-star' + | 'bellman-ford' + | 'bfs' + | 'dfs' + | 'topological-sort' + | 'bst-search' + | 'tree-inorder' + | 'tree-preorder' + | 'tree-postorder'; + +type CategoryType = 'sorting' | 'search' | 'graph' | 'tree'; + +interface AlgorithmInfo { + id: AlgorithmType; name: string; - icon: string; - snippets: CodeSnippet[]; + description: string; + complexity: string; + category: CategoryType; + realWorld?: string; } +const ALGORITHMS: AlgorithmInfo[] = [ + // Sorting Algorithms + { + id: 'bubble-sort', + name: 'Bubble Sort', + description: 'Vergleicht benachbarte Elemente und tauscht sie wenn nötig', + complexity: 'O(n²)', + category: 'sorting', + realWorld: 'Lehrzwecke, kleine Datensätze', + }, + { + id: 'selection-sort', + name: 'Selection Sort', + description: 'Findet das Minimum und setzt es an die richtige Position', + complexity: 'O(n²)', + category: 'sorting', + realWorld: 'Speicherplatz-kritische Anwendungen', + }, + { + id: 'insertion-sort', + name: 'Insertion Sort', + description: 'Fügt jedes Element an der richtigen Stelle ein - gut für fast sortierte Daten', + complexity: 'O(n²)', + category: 'sorting', + realWorld: 'Echtzeit-Datenstromsortierung, kleine Arrays', + }, + { + id: 'quick-sort', + name: 'Quick Sort', + description: 'Divide & Conquer mit Pivot - Standard in vielen Sprachen', + complexity: 'O(n log n)', + category: 'sorting', + realWorld: 'Arrays.sort() in Java, qsort() in C', + }, + { + id: 'merge-sort', + name: 'Merge Sort', + description: 'Stabiler Sortieralgorithmus - teilt und verschmilzt', + complexity: 'O(n log n)', + category: 'sorting', + realWorld: 'Externe Sortierung, Linked Lists, Git Merge', + }, + { + id: 'heap-sort', + name: 'Heap Sort', + description: 'Nutzt Heap-Datenstruktur - garantiert O(n log n)', + complexity: 'O(n log n)', + category: 'sorting', + realWorld: 'Priority Queues, Scheduling-Algorithmen', + }, + // Search Algorithms + { + id: 'binary-search', + name: 'Binary Search', + description: 'Sucht effizient in sortierten Arrays durch Halbierung', + complexity: 'O(log n)', + category: 'search', + realWorld: 'Datenbank-Indizes, Git bisect, Telefonbuch', + }, + { + id: 'linear-search', + name: 'Linear Search', + description: 'Durchsucht jedes Element sequentiell', + complexity: 'O(n)', + category: 'search', + realWorld: 'Unsortierte Daten, kleine Listen', + }, + // Graph Algorithms + { + id: 'dijkstra', + name: 'Dijkstra', + description: 'Findet kürzeste Pfade in gewichteten Graphen', + complexity: 'O(V² / E log V)', + category: 'graph', + realWorld: 'Google Maps, GPS Navigation, Netzwerk-Routing', + }, + { + id: 'a-star', + name: 'A* (A-Star)', + description: 'Heuristik-basiertes Pathfinding - schneller als Dijkstra', + complexity: 'O(E)', + category: 'graph', + realWorld: 'Google Maps, Videospiele (Starcraft, AoE), Robotik', + }, + { + id: 'bellman-ford', + name: 'Bellman-Ford', + description: 'Kürzeste Pfade auch mit negativen Gewichten', + complexity: 'O(V × E)', + category: 'graph', + realWorld: 'RIP-Protokoll, Währungsarbitrage, Netzwerk-Routing', + }, + { + id: 'bfs', + name: 'BFS (Breitensuche)', + description: 'Durchsucht Graph ebenenweise - findet kürzesten Pfad', + complexity: 'O(V + E)', + category: 'graph', + realWorld: 'Social Networks, Web Crawler, Schachcomputer', + }, + { + id: 'dfs', + name: 'DFS (Tiefensuche)', + description: 'Durchsucht Graph in die Tiefe - gut für Zykluserkennung', + complexity: 'O(V + E)', + category: 'graph', + realWorld: 'Labyrinth-Lösung, Topologische Sortierung, Compiler', + }, + { + id: 'topological-sort', + name: 'Topologische Sortierung', + description: 'Ordnet DAG-Knoten nach Abhängigkeiten', + complexity: 'O(V + E)', + category: 'graph', + realWorld: 'npm/yarn, Build-Systeme (Make, Gradle), Task-Scheduling', + }, + // Tree Algorithms + { + id: 'bst-search', + name: 'BST Suche', + description: 'Suche im Binary Search Tree - links kleiner, rechts größer', + complexity: 'O(log n)', + category: 'tree', + realWorld: 'Datenbanken, Dateisysteme, Auto-Complete', + }, + { + id: 'tree-inorder', + name: 'Inorder Traversierung', + description: 'Links → Wurzel → Rechts - gibt sortierte Reihenfolge', + complexity: 'O(n)', + category: 'tree', + realWorld: 'Sortierte Ausgabe von BST, Expression Trees', + }, + { + id: 'tree-preorder', + name: 'Preorder Traversierung', + description: 'Wurzel → Links → Rechts - für Kopieren/Serialisierung', + complexity: 'O(n)', + category: 'tree', + realWorld: 'Tree-Kopie, Prefix-Notation, JSON-Serialisierung', + }, + { + id: 'tree-postorder', + name: 'Postorder Traversierung', + description: 'Links → Rechts → Wurzel - für Löschen/Auswertung', + complexity: 'O(n)', + category: 'tree', + realWorld: 'Tree löschen, Postfix-Notation, du -sh (Verzeichnisgröße)', + }, +]; + +const CATEGORY_INFO: Record = { + sorting: { title: 'Sortierung', icon: '' }, + search: { title: 'Suche', icon: '' }, + graph: { title: 'Graphen', icon: '' }, + tree: { title: 'Bäume', icon: '' }, +}; + /** - * Algorithm Training Page Controller + * Algorithm Training Page */ export class AlgorithmTrainingPage { - private keyboard: VirtualKeyboard | null = null; - private currentCategoryType: CategoryType = 'algorithms'; - private currentCategoryIndex: number = 0; - private currentSnippetIndex: number = 0; - private currentDifficulty: Difficulty | 'all' = 'all'; - private currentInput: string = ''; - private isTyping: boolean = false; - private startTime: number = 0; - private errors: number = 0; - private readonly boundKeydownHandler: ((e: KeyboardEvent) => void) | null = null; + private arrayVisualizer: AlgorithmVisualizer | null = null; + private graphVisualizer: GraphVisualizer | null = null; + private currentAlgorithm: AlgorithmType = 'bubble-sort'; + private arraySize: number = 8; + private searchTarget: number = 0; + private bstSearchTarget: number = 40; + private currentArray: number[] = []; constructor() { - this.boundKeydownHandler = this.handleKeyboardInput.bind(this); + this.generateNewArray(); } /** - * Get current categories based on type + * Generate random array */ - private getCategories(): Category[] { - return this.currentCategoryType === 'algorithms' ? ALGORITHM_CATEGORIES : FRAMEWORK_CATEGORIES; + private generateNewArray(): void { + this.currentArray = Array.from( + { length: this.arraySize }, + () => Math.floor(Math.random() * 50) + 5 + ); + this.searchTarget = this.currentArray[Math.floor(Math.random() * this.currentArray.length)]; } /** - * Get current category + * Get current algorithm info */ - private getCurrentCategory(): Category | undefined { - const categories = this.getCategories(); - return categories[this.currentCategoryIndex]; - } - - /** - * Get filtered snippets - */ - private getFilteredSnippets(): CodeSnippet[] { - const category = this.getCurrentCategory(); - if (!category) { - return []; - } - - if (this.currentDifficulty === 'all') { - return category.snippets; - } - - return category.snippets.filter(s => s.difficulty === this.currentDifficulty); - } - - /** - * Get current snippet - */ - private getCurrentSnippet(): CodeSnippet | undefined { - const snippets = this.getFilteredSnippets(); - return snippets[this.currentSnippetIndex]; + private getCurrentAlgorithmInfo(): AlgorithmInfo | undefined { + return ALGORITHMS.find(a => a.id === this.currentAlgorithm); } /** * Render the page */ render(): string { + const currentAlgo = this.getCurrentAlgorithmInfo(); + return ` -
- ${this.renderHeader()} - ${this.renderTypeSelector()} - ${this.renderCategorySelector()} - ${this.renderDifficultyFilter()} - ${this.renderCodeArea()} - ${this.renderKeyboard()} - ${this.renderStats()} +
+
+

Algorithmus Visualisierung

+

Verstehe Algorithmen Schritt für Schritt - von Sortierung bis Graphen

+
+ +
+ +
+ ${(['sorting', 'search', 'graph', 'tree'] as CategoryType[]) + .map( + category => ` +
+ ${CATEGORY_INFO[category].title} +
+
+ ${ALGORITHMS.filter(a => a.category === category) + .map( + algo => ` + + ` + ) + .join('')} +
+ ` + ) + .join('')} +
+ + +
+
+
+
+

${currentAlgo?.name}

+

${currentAlgo?.description}

+ ${currentAlgo?.realWorld ? `

Praxis: ${currentAlgo.realWorld}

` : ''} +
+ ${currentAlgo?.complexity} +
+
+ + ${this.renderControls()} + +
+
+
- `; - } - - /** - * Render header - */ - private renderHeader(): string { - return ` -
-

- 🧠 - Algorithmen & Framework Training -

-

- Verbessere deine Programmierfähigkeiten durch Tippen von echtem Code -

-
- `; - } - - /** - * Render type selector - */ - private renderTypeSelector(): string { - return ` -
- - -
- `; - } - /** - * Render category selector - */ - private renderCategorySelector(): string { - const categories = this.getCategories(); + .algo-vis-speed { + display: flex; + align-items: center; + gap: var(--space-2); + justify-content: center; + font-size: var(--font-size-sm); + color: var(--text-secondary); + } - return ` -
- ${categories - .map( - (cat, index) => ` -
- ${cat.icon} - ${cat.name} - ${cat.snippets.length} Übungen -
- ` - ) - .join('')} -
- `; - } + .algo-vis-speed input[type="range"] { + width: 150px; + cursor: pointer; + } - /** - * Render difficulty filter - */ - private renderDifficultyFilter(): string { - const difficulties: { id: Difficulty | 'all'; label: string }[] = [ - { id: 'all', label: 'Alle' }, - { id: 'beginner', label: '🟢 Anfänger' }, - { id: 'intermediate', label: '🟡 Mittel' }, - { id: 'advanced', label: '🔴 Fortgeschritten' }, - ]; + .algo-vis-speed span { + font-family: var(--font-mono); + min-width: 60px; + } - return ` -
- ${difficulties - .map( - d => ` - - ` - ) - .join('')} -
+ @media (max-width: 900px) { + .algo-layout { + grid-template-columns: 1fr; + } + + .algo-sidebar { + position: static; + max-height: none; + } + + .algo-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + } + + .algo-controls-card { + flex-direction: column; + align-items: stretch; + } + + .algo-current-array { + margin-left: 0; + justify-content: center; + } + } + `; } /** - * Render code area + * Render algorithm-specific controls */ - private renderCodeArea(): string { - const snippet = this.getCurrentSnippet(); - const snippets = this.getFilteredSnippets(); + private renderControls(): string { + const algo = this.getCurrentAlgorithmInfo(); - if (!snippet) { + if (algo?.category === 'sorting') { return ` -
-
-

Keine Übungen in dieser Kategorie gefunden.

-

Wähle eine andere Kategorie oder entferne den Schwierigkeitsfilter.

+
+
+ + + ${this.arraySize} +
+ +
+ Array: + [${this.currentArray.join(', ')}]
`; } - const targetChars = snippet.code.split(''); - const inputChars = this.currentInput.split(''); - - return ` -
-
-
-

${snippet.title}

- ${snippet.description} + if (algo?.category === 'search') { + return ` +
+
+ + + ${this.arraySize}
-
- ${snippet.difficulty} - ${snippet.language} +
+ +
-
- -
-
- ${this.renderTargetCode(targetChars, inputChars)} + +
+ Array: + [${this.currentArray.join(', ')}]
+ `; + } -