This commit is contained in:
24
04_typescript/projects/typescript-task-manager-template/.gitignore
vendored
Normal file
24
04_typescript/projects/typescript-task-manager-template/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -0,0 +1,17 @@
|
||||
## License and terms of use
|
||||
|
||||
© Web Professional Institute Inc. All rights reserved.
|
||||
|
||||
This material is provided solely for students enrolled in courses offered by Web Professional Institute Inc. By accessing or using this code, you acknowledge that it is strictly for educational use within the context of Web Professional Institute Inc. programs.
|
||||
|
||||
**Usage Restrictions:**
|
||||
|
||||
- Redistribution, sharing, or copying of this material outside the course environment is strictly prohibited.
|
||||
- The content is designed to support your learning objectives and is not authorized for commercial projects, public repositories, or applications beyond the course scope.
|
||||
- Unauthorized commercial use or open-source distribution is strictly prohibited and may result in expulsion from the program and legal action.
|
||||
|
||||
**Agreement & Rights:**
|
||||
|
||||
As a student, you have agreed to these terms as part of your enrollment agreement, which includes additional details on permitted uses, restrictions, and policies. The author and Web Professional Institute Inc. retain all rights to this material, including the code base and instructional content.
|
||||
|
||||
For any questions regarding these terms, please contact Web Professional Institute Inc. for clarification.
|
||||
@@ -0,0 +1,84 @@
|
||||
## Projektarbeit 1: Aufgaben-Management-System
|
||||
|
||||
In dieser Projektarbeit entwickelst du ein Aufgabenmanagementsystem, mit dem Studierende der Informatik ihre Teamprojekte effizient verwalten können. Ein Team benötigt ein System, mit dem es Aufgaben wie das Design der Benutzeroberfläche, die Programmierung des Backends oder die Erstellung der Dokumentation nachverfolgen und Deadlines und Prioritäten im Auge behalten kann.
|
||||
|
||||
Du erstellst mit Hilfe von TypeScript ein vereinfachtes Aufgabenverwaltungssystem, mit dem die Teammitglieder Aufgaben hinzufügen, aktualisieren, löschen, filtern und überfällige Aufgaben finden können.
|
||||
|
||||
In dieser Projektarbeit wirst du zeigen, dass du das Typsystem von TypeScript verstehst, einschließlich Union Types, Type Annotations, Type Assertions, Interfaces und Literal Types. Du wirst auch mit Funktionen, Objekten und Arrays arbeiten, um die benötigten Funktionen zu implementieren.
|
||||
|
||||
root/
|
||||
│
|
||||
├── assets/
|
||||
│ ├── js/
|
||||
│ │ └── bundle.js // Bundled JavaScript file after running "npm run ts"
|
||||
│ └── index.html // Main HTML file to view your JavaScript code
|
||||
│
|
||||
├── src/
|
||||
│ ├── tests/
|
||||
│ │ └── taskManager.test.ts // Test file to check your code (DO NOT MODIFY)
|
||||
│ └── taskManager.ts // Solve the task in this file only (ADD YOUR CODE HERE)
|
||||
│
|
||||
├── .gitignore // Ignore files for git
|
||||
│
|
||||
└── package.json // Project metadata and dependencies
|
||||
|
||||
**Anforderungen**
|
||||
|
||||
1. Definiere ein Task Interface:
|
||||
|
||||
- id: Ein eindeutiger Identifikator (Nummer).
|
||||
- title: Ein String, der den Titel der Aufgabe darstellt.
|
||||
- description: Ein optionaler String mit der Aufgabenbeschreibung.
|
||||
- status: Ein Union Type, der aus den folgenden String Literals bestehen kann: 'not-started', 'in-progress', 'completed'.
|
||||
- priority: Ein Union Type, der die Priorität der Aufgabe darstellt. Er sollte 'low', 'medium', oder 'high' akzeptieren.
|
||||
- dueDate: Ein Datum, das als String im Format YYYY-MM-DD dargestellt wird.
|
||||
|
||||
2. Tasks hinzufügen: Erstelle eine Funktion, um dem System eine neue Aufgabe hinzuzufügen.
|
||||
|
||||
- Akzeptiere ein task Objekt (mit title, priority, dueDate und optional description).
|
||||
- Erstelle automatisch eine eindeutige id für jede Aufgabe.
|
||||
- Setze den Standardstatus auf 'not-started'.
|
||||
- Füge die neue Aufgabe zu einer internen Aufgabenliste hinzu (z. B. einem Array).
|
||||
- Gib das erstellte Task-Objekt mit der generierten id zurück.
|
||||
|
||||
3. Tasks aktualisieren: Erstelle eine Funktion, um den Status und die Priorität einer bestehenden Aufgabe zu aktualisieren. Verwende bei Bedarf Type Assertions, um sicherzustellen, dass nur gültige Werte aktualisiert werden.
|
||||
|
||||
- Die Funktion sollte ein Objekt als Parameter akzeptieren, das mindestens eine `id` (number) enthält und optional `status` und/oder `priority` Eigenschaften.
|
||||
- Verwende Union Types für `status` und `priority`, um nur gültige Werte zu akzeptieren.
|
||||
- Finde die Aufgabe über ihre id.
|
||||
- Aktualisiere die status und/oder priority der Aufgabe, wenn gültige Werte angegeben sind.
|
||||
- Gib die aktualisierte Aufgabe zurück oder undefined, wenn die Aufgabe nicht gefunden wird.
|
||||
|
||||
4. Tasks löschen: Erstelle eine Funktion, die es Teammitgliedern ermöglicht, eine Aufgabe über ihre id zu löschen.
|
||||
|
||||
- Die Funktion sollte true zurückgeben, wenn die Aufgabe erfolgreich gelöscht wurde, oder false, wenn die Aufgabe nicht gefunden werden konnte.
|
||||
|
||||
5. Tasks abrufen und filtern: Erstelle eine Funktion, mit der du alle Aufgaben abrufen oder nach ihrem Status filtern kannst (z. B. nur Aufgaben in Bearbeitung anzeigen).
|
||||
|
||||
- Wenn ein bestimmter Status (z. B. 'in-progress') angegeben wird, sollte die Funktion nur Aufgaben mit diesem Status zurückgeben.
|
||||
- Wenn kein Status angegeben wird, werden alle Aufgaben zurückgegeben.
|
||||
|
||||
6. Überfällige Tasks finden: Erstelle eine Funktion, um Aufgaben zu finden, die überfällig sind.
|
||||
|
||||
- Eine Aufgabe gilt als überfällig, wenn ihr dueDate früher als das aktuelle Datum ist und ihr Status nicht 'completed' lautet.
|
||||
|
||||
7. Static Type-Checking: Implementiere Static Type-Checking, um sicherzustellen, dass hinzugefügte oder aktualisierte Tasks mit der Task-Interface/dem Task-Typ übereinstimmen. Stelle sicher, dass falsche oder fehlende Eigenschaften bereits beim Kompilieren erkannt werden, um zu verhindern, dass ungültige Daten verarbeitet werden.
|
||||
|
||||
8. Export des tasks Arrays: Exportiere das interne tasks Array, damit es in den Tests verwendet werden kann. Das Array sollte direkt exportiert werden (nicht als Funktion), da die Tests `tasks.map()` verwenden.
|
||||
|
||||
**Wichtige Hinweise**
|
||||
|
||||
Du erhältst Zugang zu einer vorgefertigten **Testdatei**, mit der du die Kernfunktionen deines taskManager.ts Moduls testen kannst.
|
||||
|
||||
Die Testdatei wird:
|
||||
|
||||
- Aufgaben hinzufügen.
|
||||
- Aktualisiere den Status und die Prioritäten der Aufgaben.
|
||||
- Aufgaben löschen.
|
||||
- Aufgaben basierend auf ihrem Status abrufen.
|
||||
- Überfällige Aufgaben finden.
|
||||
- Edge Cases testen, z. B. das Aktualisieren oder Löschen nicht existierender Aufgaben.
|
||||
|
||||
**Du solltest die Testdatei nicht verändern**. Du kannst sie öffnen und dir den Code ansehen, um dir ein Bild von den Parametern der Funktionen zu machen. Die Datei dient dazu, deinen Code während der Entwicklung zu testen, aber du musst alle angebotenen Tests bestehen, indem du nur deine Implementierung in `taskManager.ts` verwendest. Du kannst die Namen der Funktionen in `taskManager.ts` ändern, aber es ist deine Aufgabe, die Exportfunktionen so beizubehalten, wie du sie in der Datei findest.
|
||||
|
||||
Du kannst die Testfälle mit verschiedenen Befehlen ausführen. Der empfohlene Befehl ist `npm run test`, der die Tests in Node.js ausführt. Alternativ kannst du mit `npm run ts-build` ein Bundle für den Browser erstellen und dann die Datei `assets/index.html` im Browser öffnen. Für eine kontinuierliche Entwicklung bietet sich `npm run test:watch` an, der Tests automatisch bei Änderungen ausführt. Die Ergebnisse der Testfälle werden in der Konsole ausgegeben.
|
||||
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Assignment 01: Task Manager</title>
|
||||
<script src="./js/bundle.js" defer></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Task Manager</h1>
|
||||
<p>Open the console to see the output</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
(() => {
|
||||
})();
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "ts-assignment-task-manager",
|
||||
"version": "1.1.0",
|
||||
"main": "src/taskManager.ts",
|
||||
"scripts": {
|
||||
"ts": "npx esbuild src/taskManager.ts --watch --bundle --outfile=assets/js/bundle.js --loader:.ts=ts",
|
||||
"ts-build": "npx esbuild src/tests/taskManager.test.ts --bundle --outfile=assets/js/test-bundle.js --loader:.ts=ts",
|
||||
"build:test": "npx esbuild src/tests/taskManager.test.ts --bundle --platform=node --outfile=dist/test-bundle.js",
|
||||
"run:test": "node dist/test-bundle.js",
|
||||
"test": "npm run build:test && npm run run:test",
|
||||
"test:watch": "npx nodemon --watch src --ext ts --exec \"npm run test\""
|
||||
},
|
||||
"author": "",
|
||||
"license": "SEE LICENSE IN README.md",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.12"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
let nextTaskId = 1; // Initialize the task ID counter (it has to be a number)
|
||||
|
||||
const tasks = () => {};
|
||||
|
||||
const addTask = () => {};
|
||||
|
||||
const updateTask = () => {};
|
||||
|
||||
const deleteTask = () => {};
|
||||
|
||||
const getTasks = () => {};
|
||||
|
||||
const findOverdueTasks = () => {};
|
||||
|
||||
// Uncomment the following code. Otherwise, the tests will fail. Make sure that the names of your functions are the same as in the commented code.
|
||||
export { tasks, addTask, updateTask, deleteTask, getTasks, findOverdueTasks };
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
This is a test file for the `taskManager` module. It is designed to test the core functionality that you have implemented in the `taskManager.ts` module.
|
||||
|
||||
IMPORTANT:
|
||||
- DO NOT MODIFY this file.
|
||||
- After implementing the `taskManager` module, you should be able to run this file by using the command `npm run test` or `npm run ts-build`.
|
||||
- Once the project is built, open the `index.html` file located in the `assets` folder in your browser to see the output of the test cases in the browser's console.
|
||||
|
||||
The test file will:
|
||||
- Add tasks.
|
||||
- Update task statuses and priorities.
|
||||
- Delete tasks.
|
||||
- Fetch tasks based on their status.
|
||||
- Find overdue tasks.
|
||||
- Test edge cases, such as updating or deleting non-existing tasks.
|
||||
|
||||
EXPECTED BEHAVIOR:
|
||||
- This file should run successfully without any modifications.
|
||||
- Ensure that your implementation in the `taskManager.ts` module meets the requirements of these tests.
|
||||
|
||||
DO NOT MODIFY this file. You should aim to pass all the tests provided here by only working on the `taskManager.ts` module.
|
||||
*/
|
||||
|
||||
import {
|
||||
tasks,
|
||||
addTask,
|
||||
updateTask,
|
||||
deleteTask,
|
||||
getTasks,
|
||||
findOverdueTasks,
|
||||
} from '../taskManager';
|
||||
|
||||
// ===== 1. Adding tasks =====
|
||||
console.warn('=== Adding Tasks ===');
|
||||
const task1 = addTask({
|
||||
title: 'Task 1',
|
||||
priority: 'high',
|
||||
dueDate: '2024-10-10',
|
||||
description: 'This is a high priority task.',
|
||||
});
|
||||
console.log('Added Task 1 (ID should be 1):');
|
||||
console.log(task1);
|
||||
|
||||
const task2 = addTask({
|
||||
title: 'Task 2',
|
||||
priority: 'medium',
|
||||
dueDate: '2024-10-15',
|
||||
});
|
||||
console.log('Added Task 2 (ID should be 2):');
|
||||
console.log(task2);
|
||||
|
||||
const task3 = addTask({
|
||||
title: 'Task 3',
|
||||
priority: 'low',
|
||||
dueDate: '2024-09-30',
|
||||
description: 'Low priority task, overdue.',
|
||||
});
|
||||
console.log('Added Task 3 (ID should be 3):');
|
||||
console.log(task3);
|
||||
|
||||
const task4 = addTask({
|
||||
title: 'Task 4',
|
||||
priority: 'high',
|
||||
dueDate: '2024-12-01',
|
||||
description: 'Upcoming high priority task.',
|
||||
});
|
||||
console.log('Added Task 4 (ID should be 4):');
|
||||
console.log(task4);
|
||||
|
||||
const task5 = addTask({
|
||||
title: 'Task 5',
|
||||
priority: 'medium',
|
||||
dueDate: '2024-09-25',
|
||||
description: 'Overdue medium task.',
|
||||
});
|
||||
console.log('Added Task 5 (ID should be 5):');
|
||||
console.log(task5);
|
||||
|
||||
// ===== 2. Fetching all tasks =====
|
||||
console.warn('=== Fetching All Tasks ===');
|
||||
console.log('Expecting 5 tasks:');
|
||||
console.log(getTasks());
|
||||
|
||||
// ===== 3. Updating a task's status and priority =====
|
||||
console.warn('=== Updating Task 1 Status and Priority ===');
|
||||
const updatedTask1 = updateTask({
|
||||
id: task1.id,
|
||||
status: 'in-progress',
|
||||
priority: 'medium',
|
||||
});
|
||||
console.log(
|
||||
'Updated Task 1 (status should be "in-progress", priority should be "medium"):'
|
||||
);
|
||||
console.log(updatedTask1);
|
||||
|
||||
console.warn('=== Updating Task 3 to Completed ===');
|
||||
const updatedTask3 = updateTask({
|
||||
id: task3.id,
|
||||
status: 'completed',
|
||||
});
|
||||
console.log('Updated Task 3 (status should be "completed"):');
|
||||
console.log(updatedTask3);
|
||||
|
||||
// ===== 4. Deleting a task =====
|
||||
console.warn('=== Deleting Task 2 ===');
|
||||
const isDeleted = deleteTask(task2.id);
|
||||
console.log('Task 2 deleted (should return true):');
|
||||
console.log(isDeleted);
|
||||
console.log(
|
||||
'Fetching all tasks after deletion (Task 2 should no longer be in the list):'
|
||||
);
|
||||
console.log(getTasks());
|
||||
|
||||
// ===== 5. Fetching tasks with a specific status =====
|
||||
console.warn('=== Fetching In-progress Tasks ===');
|
||||
console.log('Expecting 1 in-progress task (Task 1):');
|
||||
console.log(getTasks('in-progress'));
|
||||
|
||||
// ===== 6. Fetching overdue tasks =====
|
||||
console.warn('=== Fetching Overdue Tasks ===');
|
||||
console.log(
|
||||
'Expecting 3 overdue tasks (Task 1, Task 4, and Task 5, Task 3 is completed):'
|
||||
);
|
||||
console.log(findOverdueTasks());
|
||||
|
||||
// ===== 7. Edge Case: Updating a task that doesn't exist =====
|
||||
console.warn('=== Attempting to Update a Non-existing Task ===');
|
||||
const updateNonExistingTask = updateTask({
|
||||
id: 999,
|
||||
status: 'completed',
|
||||
});
|
||||
console.log('Trying to update non-existing task (should return undefined):');
|
||||
console.log(updateNonExistingTask);
|
||||
|
||||
// ===== 8. Edge Case: Deleting a task that doesn't exist =====
|
||||
console.warn('=== Attempting to Delete a Non-existing Task ===');
|
||||
const deleteNonExistingTask = deleteTask(999);
|
||||
console.log('Trying to delete non-existing task (should return false):');
|
||||
console.log(deleteNonExistingTask);
|
||||
|
||||
// ===== 9. Adding and Fetching Tasks After Deletion =====
|
||||
console.warn('=== Adding a New Task After Deletion ===');
|
||||
const task6 = addTask({
|
||||
title: 'Task 6',
|
||||
priority: 'low',
|
||||
dueDate: '2024-11-05',
|
||||
});
|
||||
console.log('Added Task 6 (ID should be 6):');
|
||||
console.log(task6);
|
||||
|
||||
console.warn('=== Fetching All Tasks After Adding Task 6 ===');
|
||||
console.log('Expecting 5 tasks (Task 6 should be added):');
|
||||
console.log(getTasks());
|
||||
|
||||
// ===== 10. Verifying if Task IDs are auto-incremented correctly =====
|
||||
console.warn('=== Verifying Task ID Continuity ===');
|
||||
console.log('Task 6 ID (should be 6):');
|
||||
console.log(task6.id);
|
||||
console.log('All Task IDs (should be [1, 3, 4, 5, 6]):');
|
||||
console.log(tasks.map((t) => t.id));
|
||||
13
06_js-debug/faq/getRandomInt.js
Normal file
13
06_js-debug/faq/getRandomInt.js
Normal file
@@ -0,0 +1,13 @@
|
||||
function getRandomInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
console.log(getRandomInt(2, 6));
|
||||
console.log(getRandomInt(2, 6));
|
||||
console.log(getRandomInt(2, 6));
|
||||
console.log(getRandomInt(2, 6));
|
||||
console.log(getRandomInt(2, 6));
|
||||
console.log(getRandomInt(2, 6));
|
||||
console.log(getRandomInt(2, 6));
|
||||
console.log(getRandomInt(2, 6));
|
||||
console.log(getRandomInt(2, 6));
|
||||
18
06_js-debug/uebungen/u15_product-sales-table/.eslintrc.cjs
Normal file
18
06_js-debug/uebungen/u15_product-sales-table/.eslintrc.cjs
Normal file
@@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
}
|
||||
10
06_js-debug/uebungen/u15_product-sales-table/README.md
Normal file
10
06_js-debug/uebungen/u15_product-sales-table/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Frontend Class: Debugging Product Sales Table Exercise
|
||||
|
||||
Install the dependencies and devDependencies and start the server.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
This will start the server on http://localhost:5173
|
||||
12
06_js-debug/uebungen/u15_product-sales-table/index.html
Normal file
12
06_js-debug/uebungen/u15_product-sales-table/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Product Sales Table</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
2660
06_js-debug/uebungen/u15_product-sales-table/package-lock.json
generated
Normal file
2660
06_js-debug/uebungen/u15_product-sales-table/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
06_js-debug/uebungen/u15_product-sales-table/package.json
Normal file
28
06_js-debug/uebungen/u15_product-sales-table/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "product-sales-table",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.18",
|
||||
"@types/react-dom": "^19.2.4",
|
||||
"@typescript-eslint/eslint-plugin": "^8.67.0",
|
||||
"@typescript-eslint/parser": "^8.67.0",
|
||||
"@vitejs/plugin-react": "^6.1.0",
|
||||
"eslint": "^10.8.1",
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.4",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^8.2.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
42
06_js-debug/uebungen/u15_product-sales-table/src/App.tsx
Normal file
42
06_js-debug/uebungen/u15_product-sales-table/src/App.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
import Table from './components/Table';
|
||||
|
||||
function App() {
|
||||
const [salesData, setSalesData] = useState(null);
|
||||
|
||||
const fetchSalesData = async () => {
|
||||
const response = await fetch('./src/lib/db/sales.json');
|
||||
const data = await response.json();
|
||||
setSalesData(data);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchSalesData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main>
|
||||
<div className="wrapper">
|
||||
<div className="header-container">
|
||||
<div className="header-title-wrapper">
|
||||
<h1 className="title">Product Sales</h1>
|
||||
<p className="subtitle">
|
||||
A table of product sales data for the current month.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="table-wrapper">
|
||||
<div className="table-container">
|
||||
<div className="table-box">
|
||||
{salesData && <Table salesData={salesData} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,32 @@
|
||||
import TableRow from './TableRow';
|
||||
import { Product } from '../lib/datatypes';
|
||||
|
||||
const Table = ({ salesData }: { salesData: Product[] }) => {
|
||||
return (
|
||||
<table className="divide-y divide-gray-300">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Product</th>
|
||||
<th scope="col">Code</th>
|
||||
<th scope="col">Quantity</th>
|
||||
<th scope="col">Price</th>
|
||||
<th scope="col">Profit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
{salesData.length > 0 &&
|
||||
salesData.map(
|
||||
(product) =>
|
||||
// Aro
|
||||
// wenn product == null (böse json datei :) wird trotzdem versucht auf die id zuzugreifen
|
||||
// ==> Fehler...
|
||||
product && <TableRow key={product.id} product={product} />,
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
export default Table;
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Product } from '../lib/datatypes';
|
||||
|
||||
const TableRow = ({ product }: { product: Product }) => {
|
||||
const {
|
||||
id,
|
||||
product: productName,
|
||||
productCode,
|
||||
quantity,
|
||||
price,
|
||||
profit,
|
||||
} = product;
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td className="id-column">{id}</td>
|
||||
<td className="product-info-column">{productName}</td>
|
||||
<td className="product-info-column">{productCode}</td>
|
||||
<td className="product-num-column">{quantity}</td>
|
||||
<td className="product-num-column">${price.toFixed(2)}</td>
|
||||
<td className="product-num-column">${profit.toFixed(2)}</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableRow;
|
||||
187
06_js-debug/uebungen/u15_product-sales-table/src/index.css
Normal file
187
06_js-debug/uebungen/u15_product-sales-table/src/index.css
Normal file
@@ -0,0 +1,187 @@
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
*,
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #171717;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.screen-reader-text {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
/* ===== App Component ===== */
|
||||
main {
|
||||
max-width: 80rem; /* 1280px */
|
||||
margin: 0 auto;
|
||||
padding: 3rem 0.75rem; /* 48px 12px */
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding: 0 1rem; /* 0 16px */
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.header-title-wrapper {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1rem; /* 16px */
|
||||
line-height: 1.5rem; /* 24px */
|
||||
line-height: 1.5rem; /* 24px */
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-top: 0.5rem; /* 8px */
|
||||
font-size: 0.875rem; /* 14px */
|
||||
line-height: 1.25rem; /* 20px */
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
display: flow-root;
|
||||
margin-top: 2rem; /* 32px */
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin: -0.5rem -1rem; /* -8px -16px */
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.table-box {
|
||||
display: inline-block;
|
||||
min-width: 100%;
|
||||
padding: 0.5rem 0 /* 8px 0 */;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* ===== Table Component ===== */
|
||||
table {
|
||||
min-width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th {
|
||||
white-space: nowrap;
|
||||
padding-top: 0.875rem /* 14px */;
|
||||
padding-bottom: 0.875rem /* 14px */;
|
||||
padding-left: 1rem /* 16px */;
|
||||
padding-right: 0.75rem /* 12px */;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem /* 14px */;
|
||||
line-height: 1.25rem /* 20px */;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
tbody {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
thead tr {
|
||||
border-bottom: 1px solid #d1d5db;
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.id-column {
|
||||
white-space: nowrap;
|
||||
padding-top: 0.5rem /* 8px */;
|
||||
padding-bottom: 0.5rem /* 8px */;
|
||||
padding-left: 1rem /* 16px */;
|
||||
padding-right: 0.75rem /* 12px */;
|
||||
font-size: 0.875rem /* 14px */;
|
||||
line-height: 1.25rem /* 20px */;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.product-info-column {
|
||||
white-space: nowrap;
|
||||
padding: 0.5rem /* 8px */;
|
||||
font-size: 0.875rem /* 14px */;
|
||||
line-height: 1.25rem /* 20px */;
|
||||
font-weight: 500;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.product-num-column {
|
||||
white-space: nowrap;
|
||||
padding: 0.5rem /* 8px */;
|
||||
font-size: 0.875rem /* 14px */;
|
||||
line-height: 1.25rem /* 20px */;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
/* ===== Breakpoints ===== */
|
||||
@media (min-width: 640px) {
|
||||
.wrapper {
|
||||
padding: 0 1.5rem; /* 0 24px */
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-title-wrapper {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin: -0.5rem -1.5rem; /* -8px -24px */
|
||||
}
|
||||
|
||||
.table-box {
|
||||
padding: 0.5rem 1.5rem /* 8px 24px */;
|
||||
}
|
||||
|
||||
th {
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
.id-column {
|
||||
padding-left: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.wrapper {
|
||||
padding: 0 2rem; /* 0 32px */
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin: -0.5rem -2rem; /* -8px -32px */
|
||||
}
|
||||
|
||||
.table-box {
|
||||
padding: 0.5rem 2rem /* 8px 32px */;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export interface Product {
|
||||
id: number;
|
||||
product: string;
|
||||
productCode: string;
|
||||
quantity: number;
|
||||
price: number;
|
||||
profit: number;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"product": "Laptop",
|
||||
"productCode": "LPT",
|
||||
"quantity": 352,
|
||||
"price": 1200.0,
|
||||
"profit": 200.0
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"product": "Smartphone",
|
||||
"productCode": "SPH",
|
||||
"quantity": 500,
|
||||
"price": 800.0,
|
||||
"profit": 123.0
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"product": "Headphones",
|
||||
"productCode": "HDP",
|
||||
"quantity": 432,
|
||||
"price": 150.0,
|
||||
"profit": 50.0
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"product": "Keyboard",
|
||||
"productCode": "KBD",
|
||||
"quantity": 431,
|
||||
"price": 75.0,
|
||||
"profit": 25.0
|
||||
},
|
||||
null,
|
||||
{
|
||||
"id": 6,
|
||||
"product": "Monitor",
|
||||
"productCode": "MNT",
|
||||
"quantity": 234,
|
||||
"price": 300.0,
|
||||
"profit": 50.0
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"product": "Tablet",
|
||||
"productCode": "TBL",
|
||||
"quantity": 68,
|
||||
"price": 350.0,
|
||||
"profit": 100.0
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"product": "Printer",
|
||||
"productCode": "PRT",
|
||||
"quantity": 642,
|
||||
"price": 200.0,
|
||||
"profit": 50.0
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"product": "Webcam",
|
||||
"productCode": "WCM",
|
||||
"quantity": 123,
|
||||
"price": 90.0,
|
||||
"profit": 20.0
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"product": "Desk Lamp",
|
||||
"productCode": "DLP",
|
||||
"quantity": 1921,
|
||||
"price": 30.0,
|
||||
"profit": 5.0
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"product": "Chair",
|
||||
"productCode": "CHR",
|
||||
"quantity": 4854,
|
||||
"price": 120.0,
|
||||
"profit": 20.0
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"product": "External Hard Drive",
|
||||
"productCode": "EHD",
|
||||
"quantity": 483,
|
||||
"price": 100.0,
|
||||
"profit": 20.0
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"product": "USB-C Adapter",
|
||||
"productCode": "USC",
|
||||
"quantity": 10239,
|
||||
"price": 19.99,
|
||||
"profit": 5.0
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"product": "Smart Watch",
|
||||
"productCode": "SWT",
|
||||
"quantity": 483,
|
||||
"price": 199.99,
|
||||
"profit": 50.0
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"product": "Speakers",
|
||||
"productCode": "SPK",
|
||||
"quantity": 394,
|
||||
"price": 85.0,
|
||||
"profit": 20.0
|
||||
}
|
||||
]
|
||||
10
06_js-debug/uebungen/u15_product-sales-table/src/main.tsx
Normal file
10
06_js-debug/uebungen/u15_product-sales-table/src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.tsx'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
1
06_js-debug/uebungen/u15_product-sales-table/src/vite-env.d.ts
vendored
Normal file
1
06_js-debug/uebungen/u15_product-sales-table/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
11
06_js-debug/uebungen/u15_product-sales-table/tsconfig.json
Normal file
11
06_js-debug/uebungen/u15_product-sales-table/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
18
06_js-debug/uebungen/u16_lexifind-exercise/.eslintrc.cjs
Normal file
18
06_js-debug/uebungen/u16_lexifind-exercise/.eslintrc.cjs
Normal file
@@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
}
|
||||
10
06_js-debug/uebungen/u16_lexifind-exercise/README.md
Normal file
10
06_js-debug/uebungen/u16_lexifind-exercise/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Frontend Class: LexiFind Exercise Starter
|
||||
|
||||
Install the dependencies and devDependencies and start the server.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
This will start the server on http://localhost:5173
|
||||
13
06_js-debug/uebungen/u16_lexifind-exercise/index.html
Normal file
13
06_js-debug/uebungen/u16_lexifind-exercise/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/LexiFind.webp" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>LexiFind - Your Personal Dictionary</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
2744
06_js-debug/uebungen/u16_lexifind-exercise/package-lock.json
generated
Normal file
2744
06_js-debug/uebungen/u16_lexifind-exercise/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
06_js-debug/uebungen/u16_lexifind-exercise/package.json
Normal file
30
06_js-debug/uebungen/u16_lexifind-exercise/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "lexifind-webmaster-exercise",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
"react-router-dom": "^7.18.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.18",
|
||||
"@types/react-dom": "^19.2.4",
|
||||
"@typescript-eslint/eslint-plugin": "^8.67.0",
|
||||
"@typescript-eslint/parser": "^8.67.0",
|
||||
"@vitejs/plugin-react": "^6.1.0",
|
||||
"eslint": "^10.8.1",
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.4",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^8.2.2"
|
||||
}
|
||||
}
|
||||
BIN
06_js-debug/uebungen/u16_lexifind-exercise/public/LexiFind.webp
Normal file
BIN
06_js-debug/uebungen/u16_lexifind-exercise/public/LexiFind.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,52 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { HeartFilled, HeartIcon } from './Icons';
|
||||
|
||||
interface FavoriteWordProps {
|
||||
word: string;
|
||||
}
|
||||
|
||||
const FavoriteWord: React.FC<FavoriteWordProps> = ({ word }) => {
|
||||
const [isFavorite, setIsFavorite] = useState(false);
|
||||
|
||||
const toggleFavorite = () => {
|
||||
const favorites = JSON.parse(localStorage.getItem('favoriteWords') || '[]');
|
||||
|
||||
if (favorites.includes(word)) {
|
||||
const filteredFavorites = favorites.filter((item: string) => item !== word);
|
||||
localStorage.setItem('favoriteWords', JSON.stringify(filteredFavorites));
|
||||
} else {
|
||||
favorites.push(word);
|
||||
localStorage.setItem('favoriteWords', JSON.stringify(favorites));
|
||||
}
|
||||
setIsFavorite(!isFavorite);
|
||||
};
|
||||
|
||||
// [x] Missing useEffect
|
||||
useEffect(() => {
|
||||
const favoriteWords = JSON.parse(localStorage.getItem('favoriteWords') || '[]');
|
||||
setIsFavorite(favoriteWords.includes(word));
|
||||
}, [word]);
|
||||
|
||||
return (
|
||||
<div className="fave-word-wrapper">
|
||||
<Link
|
||||
to={`/?word=${word}`}
|
||||
// to="/"
|
||||
className="fave-word-link">
|
||||
<p>{word}</p>
|
||||
</Link>
|
||||
|
||||
<button className="fave-icon-btn" onClick={toggleFavorite}>
|
||||
{isFavorite ? (
|
||||
<HeartFilled className="heart-icon-filled" aria-hidden="true" />
|
||||
) : (
|
||||
<HeartIcon className="heart-icon-unfilled" aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FavoriteWord;
|
||||
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { SERIF_FONTS, SANS_SERIF_FONTS } from '../lib/constants';
|
||||
|
||||
interface HeaderProps {
|
||||
font: string;
|
||||
setFont: React.Dispatch<React.SetStateAction<string>>;
|
||||
}
|
||||
|
||||
const Header: React.FC<HeaderProps> = ({ font, setFont }) => {
|
||||
return (
|
||||
<header className="header-container">
|
||||
<Link to="/" className="header-logo-wrapper">
|
||||
<img
|
||||
src="/LexiFind.webp"
|
||||
alt="LexiFind Logo"
|
||||
className="header-logo-img"
|
||||
/>
|
||||
<span className="header-logo-text">LexiFind</span>
|
||||
</Link>
|
||||
|
||||
<div>
|
||||
<Link to="/favorites" className="header-link-text">
|
||||
Favorites
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<select
|
||||
value={font}
|
||||
onChange={(e) => setFont(e.target.value)}
|
||||
className="header-select"
|
||||
style={{ fontFamily: font }}
|
||||
>
|
||||
<option value={SERIF_FONTS}>Serif</option>
|
||||
<option value={SANS_SERIF_FONTS}>Sans Serif</option>
|
||||
</select>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
@@ -0,0 +1,46 @@
|
||||
export function HeartIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function HeartFilled(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<path d="m11.645 20.91-.007-.003-.022-.012a15.247 15.247 0 0 1-.383-.218 25.18 25.18 0 0 1-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0 1 12 5.052 5.5 5.5 0 0 1 16.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 0 1-4.244 3.17 15.247 15.247 0 0 1-.383.219l-.022.012-.007.004-.003.001a.752.752 0 0 1-.704 0l-.003-.001Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function PlayIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
{...props}
|
||||
>
|
||||
<path d="m11.596 8.697-6.363 3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363 3.692a.802.802 0 0 1 0 1.393" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
|
||||
interface SearchbarProps {
|
||||
word: string;
|
||||
setWord: React.Dispatch<React.SetStateAction<string>>;
|
||||
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
|
||||
}
|
||||
|
||||
const Searchbar: React.FC<SearchbarProps> = ({
|
||||
word,
|
||||
setWord,
|
||||
handleSubmit,
|
||||
}) => {
|
||||
return (
|
||||
<div className="searchbar-wrapper">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<label htmlFor="searchWord" className="screen-reader-text">
|
||||
Search word
|
||||
</label>
|
||||
|
||||
<div className="searchbar-container">
|
||||
<input
|
||||
type="text"
|
||||
name="searchWord"
|
||||
id="searchWord"
|
||||
className="searchbar-input"
|
||||
placeholder="Type a word to search"
|
||||
value={word}
|
||||
onChange={(e) => setWord(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="searchbar-magnifying-glass"
|
||||
aria-label="Search"
|
||||
>
|
||||
<SearchIcon className="searchbar-icon" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function SearchIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
{...props}
|
||||
>
|
||||
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default Searchbar;
|
||||
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
|
||||
interface SourceFooterProps {
|
||||
source: string;
|
||||
}
|
||||
|
||||
const SourceFooter: React.FC<SourceFooterProps> = ({ source }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="footer-divider"></div>
|
||||
|
||||
<div className="footer-wrapper">
|
||||
<h4 className="footer-title">Source</h4>
|
||||
|
||||
<div>
|
||||
<a href={source} className="footer-link-wrapper">
|
||||
{source}
|
||||
<BoxArrowUpRightIcon
|
||||
className="footer-link-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function BoxArrowUpRightIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default SourceFooter;
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import { Meaning, WordData } from '../lib/datatypes';
|
||||
|
||||
interface WordDefinitionsProps {
|
||||
wordData: WordData;
|
||||
}
|
||||
|
||||
const WordDefinitions: React.FC<WordDefinitionsProps> = ({ wordData }) => {
|
||||
return (
|
||||
<div className="word-definitions-wrapper">
|
||||
{wordData.meanings.map((meaning: any, index: number) => (
|
||||
<WordDefinitionBlock key={index} data={meaning} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface WordDefinitionBlockProps {
|
||||
data: Meaning;
|
||||
}
|
||||
|
||||
const WordDefinitionBlock: React.FC<WordDefinitionBlockProps> = ({ data }) => {
|
||||
const { partOfSpeech, definitions, synonyms } = data;
|
||||
|
||||
return (
|
||||
<div className="word-block-wrapper">
|
||||
<div className="word-block-category-container">
|
||||
<h2 className="word-block-category-name">{partOfSpeech}</h2>
|
||||
<div className="word-block-category-divider"></div>
|
||||
</div>
|
||||
|
||||
<div className="word-block-meaning-container">
|
||||
<h3 className="word-block-meaning-title">Meaning</h3>
|
||||
|
||||
<ul className="word-block-meaning-list">
|
||||
{definitions.map((definition: any, index: number) => (
|
||||
<li key={index} className="word-block-meaning-list-item">
|
||||
{definition.definition}{' '}
|
||||
{definition.example && (
|
||||
<span className="word-block-meaning-list-item-example">
|
||||
"{definition.example}"
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{synonyms.length > 0 && (
|
||||
<div className="word-block-synonyms-container">
|
||||
<h3 className="word-block-synonyms-title">Synonyms</h3>
|
||||
|
||||
<ul className="word-block-synonyms-list">
|
||||
{synonyms.map((synonym: string, index: number) => (
|
||||
<li key={index} className="word-block-synonyms-list-item">
|
||||
{synonym}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WordDefinitions;
|
||||
@@ -0,0 +1,74 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { PlayIcon, HeartIcon, HeartFilled } from './Icons';
|
||||
import { Phonetic } from '../lib/datatypes';
|
||||
|
||||
interface WordHeroProps {
|
||||
data: {
|
||||
word: string;
|
||||
phonetics: Phonetic[];
|
||||
};
|
||||
}
|
||||
|
||||
const WordHero: React.FC<WordHeroProps> = ({ data }) => {
|
||||
const { word, phonetics } = data;
|
||||
|
||||
const [isFavorite, setIsFavorite] = useState(false);
|
||||
|
||||
const americanEnglishPhonetic = phonetics.find((phonetic) => phonetic.audio?.includes('-us'));
|
||||
const finalPhonetic = americanEnglishPhonetic || phonetics[0];
|
||||
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
|
||||
const playAudio = () => {
|
||||
if (audioRef.current) {
|
||||
audioRef.current.play();
|
||||
}
|
||||
};
|
||||
|
||||
const toggleFavorite = () => {
|
||||
const favorites = JSON.parse(localStorage.getItem('favoriteWords') || '[]');
|
||||
|
||||
if (favorites.includes(word)) {
|
||||
const filteredFavorites = favorites.filter((item: string) => item !== word);
|
||||
localStorage.setItem('favoriteWords', JSON.stringify(filteredFavorites));
|
||||
} else {
|
||||
favorites.push(word);
|
||||
localStorage.setItem('favoriteWords', JSON.stringify(favorites));
|
||||
}
|
||||
|
||||
setIsFavorite(!isFavorite);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const favoriteWords = JSON.parse(localStorage.getItem('favoriteWords') || '[]');
|
||||
setIsFavorite(favoriteWords.includes(word));
|
||||
}, [word]);
|
||||
|
||||
return (
|
||||
<div className="wordhero-wrapper">
|
||||
<div className="wordhero-word-container">
|
||||
<button className="heart-icon-btn" onClick={toggleFavorite}>
|
||||
{isFavorite ? (
|
||||
<HeartFilled className="heart-icon-filled" aria-hidden="true" />
|
||||
) : (
|
||||
<HeartIcon className="heart-icon-unfilled" aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
<h1 className="wordhero-word">{word}</h1>
|
||||
{finalPhonetic?.text && <p className="wordhero-word-phonetic">{finalPhonetic.text}</p>}
|
||||
</div>
|
||||
|
||||
{finalPhonetic?.audio && (
|
||||
<div>
|
||||
<button onClick={playAudio} className="wordhero-play-btn">
|
||||
<PlayIcon className="wordhero-play-icon" aria-hidden="true" />
|
||||
</button>
|
||||
<audio ref={audioRef} src={finalPhonetic.audio}></audio>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WordHero;
|
||||
430
06_js-debug/uebungen/u16_lexifind-exercise/src/index.css
Normal file
430
06_js-debug/uebungen/u16_lexifind-exercise/src/index.css
Normal file
@@ -0,0 +1,430 @@
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
*,
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.screen-reader-text {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.heart-icon-btn {
|
||||
background-color: transparent;
|
||||
margin-right: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.heart-icon-unfilled {
|
||||
width: 2rem; /* 32px */
|
||||
height: 2rem; /* 32px */
|
||||
stroke: #9333ea;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.heart-icon-filled {
|
||||
width: 2rem; /* 32px */
|
||||
height: 2rem; /* 32px */
|
||||
fill: #9333ea;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* ===== App Component ===== */
|
||||
.app-wrapper {
|
||||
margin: 0 auto;
|
||||
max-width: 42rem; /* 672px */
|
||||
padding: 3.5rem 1.5rem; /* 56px 24px */
|
||||
}
|
||||
|
||||
/* ===== Header Component ===== */
|
||||
.header-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.header-logo-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-logo-img {
|
||||
height: 2.5rem; /* 40px */
|
||||
}
|
||||
|
||||
.header-logo-text {
|
||||
font-family: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
|
||||
font-size: 1.875rem; /* 30px */
|
||||
line-height: 2.25rem; /* 36px */
|
||||
font-weight: 600;
|
||||
margin-left: 1rem; /* 16px */
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.header-select {
|
||||
border-radius: 0.375rem; /* 6px */
|
||||
border-width: 0px;
|
||||
padding: 0.625rem 0.5rem; /* 10px 8px */
|
||||
font-size: 1.125rem; /* 18px */
|
||||
line-height: 1.75rem; /* 28px */
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.header-select:focus {
|
||||
outline-color: #9333ea;
|
||||
}
|
||||
|
||||
.header-link-text {
|
||||
font-size: 1.125rem; /* 18px */
|
||||
line-height: 1.75rem; /* 28px */
|
||||
text-decoration: none;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.header-link-text:hover {
|
||||
color: #9333ea;
|
||||
}
|
||||
|
||||
/* ===== Searchbar Component ===== */
|
||||
.searchbar-wrapper {
|
||||
margin-top: 4rem; /* 64px */
|
||||
}
|
||||
|
||||
.searchbar-container {
|
||||
position: relative;
|
||||
margin-top: 0.5rem; /* 8px */
|
||||
border-radius: 0.75rem; /* 12px */
|
||||
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
}
|
||||
|
||||
.searchbar-input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border-radius: 0.75rem; /* 12px */
|
||||
border-width: 0px;
|
||||
padding: 1.25rem 0 1.25rem 1.5rem; /* 20px 0 20px 24px */
|
||||
font-size: 1.125rem; /* 18px */
|
||||
line-height: 1.75rem; /* 28px */
|
||||
font-weight: 600;
|
||||
outline-style: solid;
|
||||
outline: 2px solid transparent;
|
||||
outline-color: #f5f5f5;
|
||||
color: #171717;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.searchbar-input:focus {
|
||||
outline-color: #9333ea;
|
||||
}
|
||||
|
||||
.searchbar-input::placeholder {
|
||||
color: #737373;
|
||||
}
|
||||
|
||||
.searchbar-magnifying-glass {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-top-right-radius: 0.75rem; /* 12px */
|
||||
border-bottom-right-radius: 0.75rem; /* 12px */
|
||||
padding-left: 1rem; /* 16px */
|
||||
padding-right: 1rem; /* 16px */
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.searchbar-icon {
|
||||
width: 1.5rem; /* 24px */
|
||||
height: 1.5rem; /* 24px */
|
||||
fill: #6b21a8;
|
||||
}
|
||||
|
||||
/* ===== WordHero Component ===== */
|
||||
.wordhero-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 4rem; /* 64px */
|
||||
}
|
||||
|
||||
.wordhero-word-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.wordhero-word {
|
||||
font-size: 3.75rem; /* 60px */
|
||||
line-height: 1;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.wordhero-word-phonetic {
|
||||
font-size: 1.5rem; /* 24px */
|
||||
line-height: 2rem; /* 32px */
|
||||
margin-top: 0.75rem; /* 12px */
|
||||
margin-bottom: 0.75rem; /* 12px */
|
||||
color: #9333ea;
|
||||
}
|
||||
|
||||
.wordhero-play-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 9999px;
|
||||
padding: 1.25rem; /* 20px */
|
||||
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
color: white;
|
||||
background-color: #e9d5ff;
|
||||
}
|
||||
|
||||
.wordhero-play-btn:hover {
|
||||
background-color: #d8b4fe;
|
||||
}
|
||||
|
||||
.wordhero-play-btn:focus {
|
||||
outline: 2px solid transparent;
|
||||
outline-color: #9333ea;
|
||||
}
|
||||
|
||||
.wordhero-play-icon {
|
||||
height: 2.5rem; /* 40px */
|
||||
width: 2.5rem; /* 40px */
|
||||
fill: #9333ea;
|
||||
}
|
||||
|
||||
/* ===== WordDefinitions Component ===== */
|
||||
.word-definitions-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem; /* 32px */
|
||||
margin-top: 4rem; /* 64px */
|
||||
}
|
||||
|
||||
.word-block-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3.5rem; /* 56px */
|
||||
}
|
||||
|
||||
.word-block-category-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem; /* 8px */
|
||||
}
|
||||
|
||||
.word-block-category-name {
|
||||
padding-right: 0.75rem; /* 12px */
|
||||
font-size: 1.5rem; /* 24px */
|
||||
line-height: 2rem; /* 32px */
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.word-block-category-divider {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: #d4d4d4;
|
||||
}
|
||||
|
||||
.word-block-meaning-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 1rem; /* 16px */
|
||||
}
|
||||
|
||||
.word-block-meaning-title {
|
||||
font-size: 1.25rem; /* 20px */
|
||||
line-height: 1.75rem; /* 28px */
|
||||
font-weight: 400;
|
||||
color: #737373;
|
||||
}
|
||||
|
||||
.word-block-meaning-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-left: 1rem; /* 16px */
|
||||
font-size: 1.125rem; /* 18px */
|
||||
line-height: 1.75rem; /* 28px */
|
||||
row-gap: 2rem; /* 32px */
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.word-block-meaning-list-item::marker {
|
||||
padding-left: 2.5rem; /* 40px */
|
||||
color: #9333ea;
|
||||
}
|
||||
|
||||
.word-block-meaning-list-item-example {
|
||||
display: block;
|
||||
margin-top: 0.5rem; /* 8px */
|
||||
color: #9333ea;
|
||||
}
|
||||
|
||||
.word-block-synonyms-container {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
column-gap: 2rem; /* 32px */
|
||||
}
|
||||
|
||||
.word-block-synonyms-title {
|
||||
font-size: 1.25rem; /* 20px */
|
||||
line-height: 1.75rem; /* 28px */
|
||||
font-weight: 400;
|
||||
color: #737373;
|
||||
}
|
||||
|
||||
.word-block-synonyms-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.5rem; /* 8px */
|
||||
}
|
||||
|
||||
.word-block-synonyms-list-item {
|
||||
list-style: none;
|
||||
font-size: 1.125rem; /* 18px */
|
||||
line-height: 1.75rem; /* 28px */
|
||||
font-weight: 600;
|
||||
color: #9333ea;
|
||||
}
|
||||
|
||||
/* ===== SourceFooter Component ===== */
|
||||
.footer-divider {
|
||||
margin-top: 2.5rem; /* 40px */
|
||||
margin-bottom: 2.5rem; /* 40px */
|
||||
height: 1px;
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
|
||||
.footer-wrapper {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 1rem; /* 16px */
|
||||
font-size: 0.875rem; /* 14px */
|
||||
line-height: 1.25rem; /* 20px */
|
||||
}
|
||||
|
||||
.footer-title {
|
||||
font-weight: 400;
|
||||
color: #737373;
|
||||
}
|
||||
|
||||
.footer-link-wrapper {
|
||||
display: flex;
|
||||
text-decoration: underline;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.footer-link-icon {
|
||||
margin-left: 0.5rem; /* 8px */
|
||||
height: 1rem; /* 16px */
|
||||
width: 1rem; /* 16px */
|
||||
fill: #262626;
|
||||
}
|
||||
|
||||
/* ===== Favorites Page ===== */
|
||||
.favorites-list {
|
||||
margin-top: 1rem; /* 16px */
|
||||
border-top-width: 2px;
|
||||
border-bottom-width: 0px;
|
||||
border-color: rgb(229 229 229);
|
||||
}
|
||||
|
||||
.favorites-text {
|
||||
margin-top: 4rem; /* 64px */
|
||||
font-size: 1.125rem; /* 18px */
|
||||
line-height: 1.75rem; /* 28px */
|
||||
color: #525252;
|
||||
}
|
||||
|
||||
.favorites-item {
|
||||
list-style: none;
|
||||
padding: 0.5rem 0; /* Add some padding to each item */
|
||||
}
|
||||
|
||||
.favorites-item.with-divider {
|
||||
border-top: 1px solid #e5e5e5; /* Add divider */
|
||||
}
|
||||
|
||||
/* ===== Favorite Word ===== */
|
||||
.fave-word-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 2.5rem 0; /* 40px 0 */
|
||||
}
|
||||
|
||||
.fave-word-link {
|
||||
background-color: #e9d5ff;
|
||||
padding: 0.5rem 0.875rem; /* 8px 14px */
|
||||
border-radius: 0.375rem; /* 6px */
|
||||
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
}
|
||||
|
||||
.fave-word-link:hover {
|
||||
background-color: #d8b4fe;
|
||||
}
|
||||
|
||||
.fave-word-link:focus {
|
||||
outline: 2px solid #9333ea;
|
||||
}
|
||||
|
||||
.fave-word-link p {
|
||||
font-size: 1.25rem; /* 20px */
|
||||
line-height: 1.75rem; /* 28px */
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.fave-icon-btn {
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ===== Breakpoints ===== */
|
||||
@media (min-width: 640px) {
|
||||
.searchbar-input {
|
||||
line-height: 1.5rem; /* 24px */
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.word-block-meaning-list {
|
||||
padding-left: 2.5rem; /* 40px */
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.app-wrapper {
|
||||
padding: 3.5rem 0.5rem; /* 56px 8px */
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export const SERIF_FONTS =
|
||||
'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif';
|
||||
|
||||
export const SANS_SERIF_FONTS =
|
||||
'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"';
|
||||
@@ -0,0 +1,22 @@
|
||||
export interface Phonetic {
|
||||
text?: string;
|
||||
audio?: string;
|
||||
}
|
||||
|
||||
export interface Definition {
|
||||
definition: string;
|
||||
example?: string;
|
||||
}
|
||||
|
||||
export interface Meaning {
|
||||
partOfSpeech: string;
|
||||
definitions: Definition[];
|
||||
synonyms: string[];
|
||||
}
|
||||
|
||||
export interface WordData {
|
||||
word: string;
|
||||
phonetics: Phonetic[];
|
||||
meanings: Meaning[];
|
||||
sourceUrls: string[];
|
||||
}
|
||||
24
06_js-debug/uebungen/u16_lexifind-exercise/src/main.tsx
Normal file
24
06_js-debug/uebungen/u16_lexifind-exercise/src/main.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
|
||||
import Root from './pages/root.tsx';
|
||||
import Favorites from './pages/favorites.tsx';
|
||||
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
element: <Root />,
|
||||
},
|
||||
{
|
||||
path: '/favorites',
|
||||
element: <Favorites />,
|
||||
},
|
||||
]);
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
</React.StrictMode>
|
||||
);
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
import Header from '../components/Header';
|
||||
import FavoriteWord from '../components/FavoriteWord';
|
||||
|
||||
import { SERIF_FONTS } from '../lib/constants';
|
||||
|
||||
function Favorites() {
|
||||
const [font, setFont] = useState<string>(SERIF_FONTS);
|
||||
const [favoriteWords, setFavoriteWords] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const loadedFavorites = JSON.parse(
|
||||
localStorage.getItem('favoriteWords') || '[]'
|
||||
);
|
||||
setFavoriteWords(loadedFavorites);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="app-wrapper"
|
||||
style={{
|
||||
fontFamily: font,
|
||||
}}
|
||||
>
|
||||
<Header font={font} setFont={setFont} />
|
||||
|
||||
{favoriteWords.length > 0 ? (
|
||||
<ul className="favorites-list">
|
||||
{favoriteWords.map((word, index) => (
|
||||
<li
|
||||
className={`favorites-item ${index > 0 ? 'with-divider' : ''}`}
|
||||
key={index}
|
||||
>
|
||||
<FavoriteWord word={word} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="favorites-text">No favorite words added yet.</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Favorites;
|
||||
@@ -0,0 +1,66 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
import { WordData } from '../lib/datatypes';
|
||||
|
||||
import Header from '../components/Header';
|
||||
import Searchbar from '../components/Searchbar';
|
||||
import WordHero from '../components/WordHero';
|
||||
import WordDefinitions from '../components/WordDefinitions';
|
||||
import SourceFooter from '../components/SourceFooter';
|
||||
|
||||
import { SERIF_FONTS } from '../lib/constants';
|
||||
|
||||
function Root() {
|
||||
const [params] = useSearchParams();
|
||||
|
||||
const [font, setFont] = useState<string>(SERIF_FONTS);
|
||||
const [word, setWord] = useState<string>(params.get('word') || 'course'); // /?word=WERT
|
||||
const [wordData, setWordData] = useState<WordData | null>(null);
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const url = `https://api.dictionaryapi.dev/api/v2/entries/en/${word}`;
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
|
||||
setWordData(data[0]);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
fetchData();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="app-wrapper"
|
||||
style={{
|
||||
fontFamily: font,
|
||||
}}>
|
||||
<Header font={font} setFont={setFont} />
|
||||
|
||||
<Searchbar word={word} setWord={setWord} handleSubmit={handleSubmit} />
|
||||
|
||||
{wordData && (
|
||||
<div>
|
||||
<WordHero data={wordData} />
|
||||
<WordDefinitions wordData={wordData} />
|
||||
<SourceFooter source={wordData.sourceUrls[0]} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Root;
|
||||
1
06_js-debug/uebungen/u16_lexifind-exercise/src/vite-env.d.ts
vendored
Normal file
1
06_js-debug/uebungen/u16_lexifind-exercise/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
27
06_js-debug/uebungen/u16_lexifind-exercise/tsconfig.app.json
Normal file
27
06_js-debug/uebungen/u16_lexifind-exercise/tsconfig.app.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
11
06_js-debug/uebungen/u16_lexifind-exercise/tsconfig.json
Normal file
11
06_js-debug/uebungen/u16_lexifind-exercise/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
BIN
06_js-debug/uebungen/uebungen-15-16-js-debug.zip
Normal file
BIN
06_js-debug/uebungen/uebungen-15-16-js-debug.zip
Normal file
Binary file not shown.
@@ -5,8 +5,8 @@ const Counter = (elOrSelector = null) => {
|
||||
|
||||
// TODO: forEach for string selector
|
||||
const module = elOrSelector || document.querySelector(elOrSelector);
|
||||
const countEl = module.querySelector('input[id^="count"]');
|
||||
const btnEl = module.querySelector('button[id^="incrementButton"]');
|
||||
const countEl = module.querySelector('.input-count');
|
||||
const btnEl = module.querySelector('.button-increment'); // 'button[id^="incrementButton"]'
|
||||
|
||||
const id = v4();
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
readonly
|
||||
aria-label="Counter" />
|
||||
</div>
|
||||
<button id="incrementButton" class="btn btn-primary w-100">Increment</button>
|
||||
<button id="incrementButton" class="btn btn-primary w-100 button-increment">Increment</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
width: 100%;
|
||||
height: 40vh;
|
||||
|
||||
// Mobile First
|
||||
background: tomato url('https://dummyimage.com/900x450/f90/000.jpg') center no-repeat;
|
||||
background-size: cover;
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
'use strict';
|
||||
|
||||
(() => {
|
||||
// ===== DOM =====
|
||||
const DOM = {
|
||||
calculateForm: document.querySelector('#calculateForm'),
|
||||
firstNumberInput: document.querySelector('#firstNumber'),
|
||||
secondNumberInput: document.querySelector('#secondNumber'),
|
||||
result: document.querySelector('#result'),
|
||||
};
|
||||
|
||||
// console.log(DOM);
|
||||
|
||||
// ===== INIT =====
|
||||
const init = () => {
|
||||
DOM.calculateForm.noValidate = true; // DOM.calculateForm.setAttribute('novalidate','');
|
||||
DOM.calculateForm.addEventListener('submit', handleFormSubmit);
|
||||
};
|
||||
|
||||
// ===== EVENT HANDLERS =====
|
||||
function handleFormSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (hasInputsEmptyValues(DOM.firstNumberInput, DOM.secondNumberInput)) {
|
||||
alert('Please enter a number in both fields');
|
||||
return;
|
||||
}
|
||||
|
||||
const num1 = getNumberValue(DOM.firstNumberInput);
|
||||
const num2 = getNumberValue(DOM.secondNumberInput);
|
||||
|
||||
const sum = add(num1, num2);
|
||||
|
||||
displayResult(sum);
|
||||
DOM.calculateForm.reset();
|
||||
}
|
||||
|
||||
// ===== FUNCTIONS =====
|
||||
const hasInputsEmptyValues = (inputOne, inputTwo) => {
|
||||
// if (inputOne.value === '' || inputTwo.value === '') {
|
||||
// return true;
|
||||
// } else {
|
||||
// return false;
|
||||
// }
|
||||
return inputOne.value === '' || inputTwo.value === '';
|
||||
};
|
||||
|
||||
const getNumberValue = (input) => Number(input.value);
|
||||
const add = (a, b) => Number(a) + Number(b);
|
||||
|
||||
const displayResult = (result) => {
|
||||
DOM.result.textContent = result;
|
||||
};
|
||||
|
||||
// ===== CALL INIT =====
|
||||
init();
|
||||
})();
|
||||
41
06_js-debug/unterricht/tag49/01_demo-form-bug/index.html
Normal file
41
06_js-debug/unterricht/tag49/01_demo-form-bug/index.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Debugging Demo</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
<script src="assets/js/main.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container py-5">
|
||||
<h1>Debugging Demo</h1>
|
||||
|
||||
<form id="calculateForm" class="mt-4">
|
||||
<div class="col col-12 col-sm-10 col-lg-6">
|
||||
<div class="row mb-3 align-items-center">
|
||||
<label for="firstNumber" class="col-md-3 col-form-label">Number #1:</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" id="firstNumber" name="firstNumber" class="form-control" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col col-12 col-sm-10 col-lg-6">
|
||||
<div class="row mb-3 align-items-center">
|
||||
<label for="secondNumber" class="col-md-3 col-form-label">Number #2:</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" id="secondNumber" name="secondNumber" class="form-control" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 w-100">
|
||||
<button type="submit" id="calculateBtn" class="btn btn-primary">Add Numbers</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="alert alert-light mt-4"><strong>Result</strong>: <span id="result"></span></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
644
06_js-debug/unterricht/tag49/01_demo-form-bug/package-lock.json
generated
Normal file
644
06_js-debug/unterricht/tag49/01_demo-form-bug/package-lock.json
generated
Normal file
@@ -0,0 +1,644 @@
|
||||
{
|
||||
"name": "01_demo-form-bug",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "01_demo-form-bug",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"http-server": "^14.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/basic-auth": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
||||
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bound": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"get-intrinsic": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/corser": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
|
||||
"integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
|
||||
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
|
||||
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
|
||||
"integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-encoding": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/http-proxy": {
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
|
||||
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^4.0.0",
|
||||
"follow-redirects": "^1.0.0",
|
||||
"requires-port": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-server": {
|
||||
"version": "14.1.1",
|
||||
"resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz",
|
||||
"integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"basic-auth": "^2.0.1",
|
||||
"chalk": "^4.1.2",
|
||||
"corser": "^2.0.1",
|
||||
"he": "^1.2.0",
|
||||
"html-encoding-sniffer": "^3.0.0",
|
||||
"http-proxy": "^1.18.1",
|
||||
"mime": "^1.6.0",
|
||||
"minimist": "^1.2.6",
|
||||
"opener": "^1.5.1",
|
||||
"portfinder": "^1.0.28",
|
||||
"secure-compare": "3.0.1",
|
||||
"union": "~0.5.0",
|
||||
"url-join": "^4.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"http-server": "bin/http-server"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/opener": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
|
||||
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
|
||||
"dev": true,
|
||||
"license": "(WTFPL OR MIT)",
|
||||
"bin": {
|
||||
"opener": "bin/opener-bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/portfinder": {
|
||||
"version": "1.0.38",
|
||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz",
|
||||
"integrity": "sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"async": "^3.2.6",
|
||||
"debug": "^4.3.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.12"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.15.3",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
|
||||
"integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.1",
|
||||
"side-channel": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/secure-compare": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
|
||||
"integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
|
||||
"integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.4",
|
||||
"side-channel-list": "^1.0.1",
|
||||
"side-channel-map": "^1.0.1",
|
||||
"side-channel-weakmap": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-list": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
|
||||
"integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-map": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-weakmap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-map": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/union": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
|
||||
"integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"qs": "^6.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-join": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
|
||||
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/whatwg-encoding": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
|
||||
"integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
|
||||
"deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"iconv-lite": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
06_js-debug/unterricht/tag49/01_demo-form-bug/package.json
Normal file
14
06_js-debug/unterricht/tag49/01_demo-form-bug/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "01_demo-form-bug",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "npx http-server -c-1 -p 3000"
|
||||
},
|
||||
"keywords": [],
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"http-server": "^14.1.1"
|
||||
}
|
||||
}
|
||||
BIN
06_js-debug/unterricht/tag49/02_debug-sources.zip
Normal file
BIN
06_js-debug/unterricht/tag49/02_debug-sources.zip
Normal file
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
.lotto-numbers {
|
||||
display: flex;
|
||||
}
|
||||
span.lotto-ball {
|
||||
padding: 1rem;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #ccc;
|
||||
margin: 10px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
}
|
||||
191
06_js-debug/unterricht/tag49/02_debug-sources/assets/js/main.js
Normal file
191
06_js-debug/unterricht/tag49/02_debug-sources/assets/js/main.js
Normal file
@@ -0,0 +1,191 @@
|
||||
'use strict';
|
||||
|
||||
(() => {
|
||||
// === DOM & VARS =======
|
||||
const DOM = {
|
||||
btnLottoDraw: document.querySelector('.button-lotto-draw'),
|
||||
btnWordCounter: document.querySelector('.button-word-counter'),
|
||||
btnGetStudents: document.querySelector('.button-get-students'),
|
||||
lottoNumbers: document.querySelector('.lotto-numbers'),
|
||||
content: document.querySelector('.content'),
|
||||
calculateForm: document.querySelector('#calculateForm'),
|
||||
firstNumberInput: document.querySelector('#firstNumber'),
|
||||
secondNumberInput: document.querySelector('#secondNumber'),
|
||||
result: document.querySelector('#result'),
|
||||
sectionStudents: document.querySelector('.section-students'),
|
||||
};
|
||||
|
||||
const names = ['Adel (Admin)', 'Ersin', 'Kahleel (Admin)', 'Andreas', 'Philippe (Admin)'];
|
||||
|
||||
// if (text === true) {
|
||||
// console.log('Error');
|
||||
// }
|
||||
|
||||
// console.log(DOM);
|
||||
|
||||
// === INIT =============
|
||||
const init = () => {
|
||||
// EventListener
|
||||
DOM.btnLottoDraw.addEventListener('click', onClickLottoNumbers);
|
||||
DOM.btnWordCounter.addEventListener('click', onClickWordCounter);
|
||||
DOM.calculateForm.addEventListener('submit', handleFormSubmit);
|
||||
DOM.btnGetStudents.addEventListener('click', onClickGetStudents);
|
||||
};
|
||||
|
||||
// === EVENTHANDLER =====
|
||||
const onClickLottoNumbers = (e) => {
|
||||
const lottoNumbers = getLottoNumbers();
|
||||
console.log(lottoNumbers);
|
||||
createLottoNumbers(lottoNumbers);
|
||||
};
|
||||
|
||||
const onClickGetStudents = (e) => {
|
||||
const students = getAdmins();
|
||||
createStudents(students);
|
||||
};
|
||||
|
||||
const onClickWordCounter = (e) => {
|
||||
const text = DOM.content.innerText;
|
||||
const words = getWordOccurance(text);
|
||||
console.log(words);
|
||||
};
|
||||
|
||||
const handleFormSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (checkIfInputsAreEmpty(DOM.firstNumberInput, DOM.secondNumberInput)) {
|
||||
alert('Please enter a number in both fields');
|
||||
return;
|
||||
}
|
||||
|
||||
const num1 = getNumberValue(DOM.firstNumberInput);
|
||||
const num2 = getNumberValue(DOM.secondNumberInput);
|
||||
|
||||
// console.log(num1, typeof num1);
|
||||
// console.log(num2, typeof num2);
|
||||
|
||||
const sum = add(num1, num2);
|
||||
|
||||
displayResult(sum);
|
||||
|
||||
DOM.calculateForm.reset();
|
||||
};
|
||||
|
||||
// === XHR/FETCH ========
|
||||
|
||||
// === FUNCTIONS ========
|
||||
function getRandomInt(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
const getLottoNumbers = (sorted = true) => {
|
||||
const lottoNumbers = [];
|
||||
const MAX_DRAW = 6;
|
||||
|
||||
while (lottoNumbers.length < MAX_DRAW) {
|
||||
const lottoNumber = getRandomInt(1, 49);
|
||||
console.log(lottoNumber);
|
||||
if (!lottoNumbers.includes(lottoNumber)) {
|
||||
lottoNumbers.push(lottoNumber);
|
||||
} else {
|
||||
console.log(`Found duplicate: ${lottoNumber}.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (sorted) {
|
||||
lottoNumbers.sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
return lottoNumbers;
|
||||
};
|
||||
|
||||
const createLottoNumbers = (numbers = []) => {
|
||||
DOM.lottoNumbers.innerHTML = '';
|
||||
numbers.forEach((n) => {
|
||||
const ballEl = document.createElement('span');
|
||||
ballEl.classList.add('lotto-ball');
|
||||
ballEl.textContent = String(n);
|
||||
DOM.lottoNumbers.appendChild(ballEl);
|
||||
});
|
||||
};
|
||||
|
||||
const getWordOccurance = (text = '') => {
|
||||
const wordObj = text
|
||||
.toLowerCase()
|
||||
.replace(/[^\w\s]/g, '')
|
||||
.split(/\s+/)
|
||||
.filter((word) => word !== '')
|
||||
.reduce((obj, word) => {
|
||||
obj[word] = (obj[word] || 0) + 1;
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
return wordObj;
|
||||
// create Regexp: Alles was keine Wort-zeichen oder Leerzeichen ist
|
||||
|
||||
// Expected Output
|
||||
// {
|
||||
// "do": 1,
|
||||
// "you": 9,
|
||||
// }
|
||||
};
|
||||
|
||||
function checkIfInputsAreEmpty(inputOne, inputTwo) {
|
||||
if (inputOne.value && inputTwo.value) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function getNumberValue(input) {
|
||||
return Number(input.value);
|
||||
}
|
||||
|
||||
function add(a, b) {
|
||||
return Number(a) + Number(b);
|
||||
}
|
||||
|
||||
function displayResult(result) {
|
||||
DOM.result.textContent = result;
|
||||
}
|
||||
|
||||
const isAdmin = (name) => {
|
||||
return name.trim().endsWith('(Admin)');
|
||||
};
|
||||
|
||||
// [x]: Bug finden
|
||||
const getAdmins = () => {
|
||||
const adminNames = [...names].sort((a, b) => {
|
||||
if (isAdmin(a) && isAdmin(b)) return a > b ? 1 : -1;
|
||||
if (isAdmin(a)) {
|
||||
return -1;
|
||||
}
|
||||
if (isAdmin(b)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return a > b ? 1 : -1;
|
||||
});
|
||||
return adminNames;
|
||||
};
|
||||
|
||||
const createStudents = (items = []) => {
|
||||
DOM.sectionStudents.innerHTML = '';
|
||||
const ulEl = document.createElement('ul');
|
||||
ulEl.classList.add('list-group', 'list-students');
|
||||
|
||||
items.forEach((str) => {
|
||||
const liEl = document.createElement('li');
|
||||
liEl.classList.add('list-group-item', 'list-item-student');
|
||||
liEl.textContent = str;
|
||||
ulEl.appendChild(liEl);
|
||||
});
|
||||
|
||||
DOM.sectionStudents.appendChild(ulEl);
|
||||
};
|
||||
|
||||
init();
|
||||
})();
|
||||
70
06_js-debug/unterricht/tag49/02_debug-sources/index.html
Normal file
70
06_js-debug/unterricht/tag49/02_debug-sources/index.html
Normal file
@@ -0,0 +1,70 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Lotto Ziehung und Wörter zählen</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="assets/css/main.css" />
|
||||
<script src="assets/js/main.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="container py-5">
|
||||
<h1>Lotto Ziehung und Wörter zählen</h1>
|
||||
<hr />
|
||||
|
||||
<h2>Lotto Ziehung</h2>
|
||||
<div class="lotto-numbers"></div>
|
||||
<button class="btn btn-dark button-lotto-draw">Zahlen Ziehen</button>
|
||||
<hr />
|
||||
<h2>Wörter zählen</h2>
|
||||
<div class="content">
|
||||
<p>
|
||||
Do you see any Teletubbies in here? Do you see a slender plastic tag clipped to my shirt with my name
|
||||
printed on it? Do you see a little Asian child with a blank expression on his face sitting outside on a
|
||||
mechanical helicopter that shakes when you put quarters in it? No? Well, that's what you see at a toy store.
|
||||
And you must think you're in a toy store, because you're here shopping for an infant named Jeb. Now that
|
||||
there is the Tec-9, a crappy spray gun from South Miami. This gun is advertised as the most popular gun in
|
||||
American crime. Do you believe that shit? It actually says that in the little book that comes with it: the
|
||||
most popular gun in American crime. Like they're actually proud of that shit.
|
||||
</p>
|
||||
</div>
|
||||
<button class="btn btn-dark button-word-counter">Wörter zählen</button>
|
||||
<hr />
|
||||
<h2>Teilnehmer auslesen</h2>
|
||||
<section class="section-students py-3 mb-3"></section>
|
||||
|
||||
<button class="btn btn-dark button-get-students">Teilnehmer sortiert ausgeben</button>
|
||||
</div>
|
||||
<div class="container py-5">
|
||||
<h1>Debugging Demo</h1>
|
||||
<form id="calculateForm" class="mt-4">
|
||||
<div class="col col-12 col-sm-10 col-lg-6">
|
||||
<div class="row mb-3 align-items-center">
|
||||
<label for="firstNumber" class="col-md-3 col-form-label">Number #1:</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" id="firstNumber" name="firstNumber" class="form-control" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col col-12 col-sm-10 col-lg-6">
|
||||
<div class="row mb-3 align-items-center">
|
||||
<label for="secondNumber" class="col-md-3 col-form-label">Number #2:</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" id="secondNumber" name="secondNumber" class="form-control" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 w-100">
|
||||
<button type="submit" id="calculateBtn" class="btn btn-primary">Add Numbers</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="alert alert-light mt-4"><strong>Result</strong>: <span id="result"></span></div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
644
06_js-debug/unterricht/tag49/02_debug-sources/package-lock.json
generated
Normal file
644
06_js-debug/unterricht/tag49/02_debug-sources/package-lock.json
generated
Normal file
@@ -0,0 +1,644 @@
|
||||
{
|
||||
"name": "02_debug-sources",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "02_debug-sources",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"http-server": "^14.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/basic-auth": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
||||
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bound": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"get-intrinsic": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/corser": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
|
||||
"integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
|
||||
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
|
||||
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
|
||||
"integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-encoding": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/http-proxy": {
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
|
||||
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^4.0.0",
|
||||
"follow-redirects": "^1.0.0",
|
||||
"requires-port": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-server": {
|
||||
"version": "14.1.1",
|
||||
"resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz",
|
||||
"integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"basic-auth": "^2.0.1",
|
||||
"chalk": "^4.1.2",
|
||||
"corser": "^2.0.1",
|
||||
"he": "^1.2.0",
|
||||
"html-encoding-sniffer": "^3.0.0",
|
||||
"http-proxy": "^1.18.1",
|
||||
"mime": "^1.6.0",
|
||||
"minimist": "^1.2.6",
|
||||
"opener": "^1.5.1",
|
||||
"portfinder": "^1.0.28",
|
||||
"secure-compare": "3.0.1",
|
||||
"union": "~0.5.0",
|
||||
"url-join": "^4.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"http-server": "bin/http-server"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/opener": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
|
||||
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
|
||||
"dev": true,
|
||||
"license": "(WTFPL OR MIT)",
|
||||
"bin": {
|
||||
"opener": "bin/opener-bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/portfinder": {
|
||||
"version": "1.0.38",
|
||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz",
|
||||
"integrity": "sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"async": "^3.2.6",
|
||||
"debug": "^4.3.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.12"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.15.3",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
|
||||
"integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.1",
|
||||
"side-channel": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/secure-compare": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
|
||||
"integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
|
||||
"integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.4",
|
||||
"side-channel-list": "^1.0.1",
|
||||
"side-channel-map": "^1.0.1",
|
||||
"side-channel-weakmap": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-list": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
|
||||
"integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-map": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-weakmap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-map": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/union": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
|
||||
"integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"qs": "^6.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-join": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
|
||||
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/whatwg-encoding": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
|
||||
"integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
|
||||
"deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"iconv-lite": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
06_js-debug/unterricht/tag49/02_debug-sources/package.json
Normal file
14
06_js-debug/unterricht/tag49/02_debug-sources/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "02_debug-sources",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "npx http-server -c-1 -p 3000"
|
||||
},
|
||||
"keywords": [],
|
||||
"type": "commonjs",
|
||||
"devDependencies": {
|
||||
"http-server": "^14.1.1"
|
||||
}
|
||||
}
|
||||
15
06_js-debug/unterricht/tag49/03_debug-vscode/.vscode/launch.json
vendored
Normal file
15
06_js-debug/unterricht/tag49/03_debug-vscode/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"program": "${file}"
|
||||
}
|
||||
]
|
||||
}
|
||||
20
06_js-debug/unterricht/tag49/03_debug-vscode/index.html
Normal file
20
06_js-debug/unterricht/tag49/03_debug-vscode/index.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>JS Debug</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
<script src="sum-array.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="container py-5">
|
||||
<h1>JS Debug</h1>
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
'use strict';
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
27
06_js-debug/unterricht/tag49/03_debug-vscode/package-lock.json
generated
Normal file
27
06_js-debug/unterricht/tag49/03_debug-vscode/package-lock.json
generated
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "03_debug-vscode",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "03_debug-vscode",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"chalk": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-6.0.0.tgz",
|
||||
"integrity": "sha512-2uNTXIuTTxk7ciZgAU1BQcgnchcG0xXnrs6jzkQfj9SsRa9M2s5zE8WT96hS6KmG4MzWHSrvH43DF1m4XRkrFg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
06_js-debug/unterricht/tag49/03_debug-vscode/package.json
Normal file
14
06_js-debug/unterricht/tag49/03_debug-vscode/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "03_debug-vscode",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "sum-array.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"chalk": "^6.0.0"
|
||||
}
|
||||
}
|
||||
13
06_js-debug/unterricht/tag49/03_debug-vscode/sum-array.js
Normal file
13
06_js-debug/unterricht/tag49/03_debug-vscode/sum-array.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import color from 'chalk'; // node spezific module
|
||||
|
||||
function sumArray(arr) {
|
||||
let sum = 0;
|
||||
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
sum += arr[i];
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
console.log(color.magenta(sumArray([1, 2, 3, 4, 5])));
|
||||
BIN
06_js-debug/unterricht/tag49/04_lexifind-starter.zip
Normal file
BIN
06_js-debug/unterricht/tag49/04_lexifind-starter.zip
Normal file
Binary file not shown.
@@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
}
|
||||
15
06_js-debug/unterricht/tag49/04_lexifind-starter/.vscode/launch.json
vendored
Normal file
15
06_js-debug/unterricht/tag49/04_lexifind-starter/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:5173",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
10
06_js-debug/unterricht/tag49/04_lexifind-starter/README.md
Normal file
10
06_js-debug/unterricht/tag49/04_lexifind-starter/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Frontend Class: LexiFind Starter Template
|
||||
|
||||
Install the dependencies and devDependencies and start the server.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
This will start the server on http://localhost:5173
|
||||
13
06_js-debug/unterricht/tag49/04_lexifind-starter/index.html
Normal file
13
06_js-debug/unterricht/tag49/04_lexifind-starter/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/LexiFind.webp" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>LexiFind - Your Personal Dictionary</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
2660
06_js-debug/unterricht/tag49/04_lexifind-starter/package-lock.json
generated
Normal file
2660
06_js-debug/unterricht/tag49/04_lexifind-starter/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "lexifind-webmaster",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.18",
|
||||
"@types/react-dom": "^19.2.4",
|
||||
"@typescript-eslint/eslint-plugin": "^8.67.0",
|
||||
"@typescript-eslint/parser": "^8.67.0",
|
||||
"@vitejs/plugin-react": "^6.1.0",
|
||||
"eslint": "^10.8.1",
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.4",
|
||||
"typescript": "~6.0.3",
|
||||
"vite": "^8.2.2"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
59
06_js-debug/unterricht/tag49/04_lexifind-starter/src/App.tsx
Normal file
59
06_js-debug/unterricht/tag49/04_lexifind-starter/src/App.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { WordData } from './lib/datatypes';
|
||||
|
||||
import Header from './components/Header';
|
||||
import Searchbar from './components/Searchbar';
|
||||
import WordHero from './components/WordHero';
|
||||
import WordDefinitions from './components/WordDefinitions';
|
||||
import SourceFooter from './components/SourceFooter';
|
||||
|
||||
import { SERIF_FONTS } from './lib/constants';
|
||||
|
||||
function App() {
|
||||
const [font, setFont] = useState<string>(SERIF_FONTS);
|
||||
const [word, setWord] = useState<string>('course');
|
||||
const [wordData, setWordData] = useState<WordData | null>(null);
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const url = `https://api.dictionaryapi.dev/api/v2/entries/en/${word}`;
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
|
||||
setWordData(data[0]);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
fetchData();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []); // kein did update, sonst wird fetch bei jeder Buchstabeeingabe erneut ausgeführt
|
||||
|
||||
return (
|
||||
<div
|
||||
className="app-wrapper"
|
||||
style={{
|
||||
fontFamily: font,
|
||||
}}>
|
||||
<Header font={font} setFont={setFont} />
|
||||
|
||||
<Searchbar word={word} setWord={setWord} handleSubmit={handleSubmit} />
|
||||
|
||||
{wordData && (
|
||||
<div>
|
||||
<WordHero data={wordData} />
|
||||
<WordDefinitions wordData={wordData} />
|
||||
<SourceFooter source={wordData.sourceUrls[0]} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
|
||||
import { SERIF_FONTS, SANS_SERIF_FONTS } from '../lib/constants';
|
||||
|
||||
interface HeaderProps {
|
||||
font: string;
|
||||
setFont: React.Dispatch<React.SetStateAction<string>>;
|
||||
}
|
||||
|
||||
const Header: React.FC<HeaderProps> = ({ font, setFont }) => {
|
||||
return (
|
||||
<header className="header-container">
|
||||
<div className="header-logo-wrapper">
|
||||
<img
|
||||
src="/LexiFind.webp"
|
||||
alt="LexiFind Logo"
|
||||
className="header-logo-img"
|
||||
/>
|
||||
<span className="header-logo-text">LexiFind</span>
|
||||
</div>
|
||||
|
||||
<select
|
||||
value={font}
|
||||
onChange={(e) => setFont(e.target.vaule)}
|
||||
className="header-select"
|
||||
style={{ fontFamily: font }}
|
||||
>
|
||||
<option value={SERIF_FONTS}>Serif</option>
|
||||
<option value={SANS_SERIF_FONTS}>Sans Serif</option>
|
||||
</select>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
|
||||
interface SearchbarProps {
|
||||
word: string;
|
||||
setWord: React.Dispatch<React.SetStateAction<string>>;
|
||||
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
|
||||
}
|
||||
|
||||
const Searchbar: React.FC<SearchbarProps> = ({ word, setWord, handleSubmit }) => {
|
||||
return (
|
||||
<div className="searchbar-wrapper">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<label htmlFor="searchWord" className="screen-reader-text">
|
||||
Search word
|
||||
</label>
|
||||
|
||||
<div className="searchbar-container">
|
||||
<input
|
||||
type="text"
|
||||
name="searchWord"
|
||||
id="searchWord"
|
||||
className="searchbar-input"
|
||||
placeholder="Type a word to search"
|
||||
value={word}
|
||||
onChange={(e) => setWord(e.target.value)}
|
||||
/>
|
||||
<button type="submit" className="searchbar-magnifying-glass" aria-label="Search">
|
||||
<SearchIcon className="searchbar-icon" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function SearchIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" {...props}>
|
||||
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default Searchbar;
|
||||
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
|
||||
interface SourceFooterProps {
|
||||
source: string;
|
||||
}
|
||||
|
||||
const SourceFooter: React.FC<SourceFooterProps> = ({ source }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="footer-divider"></div>
|
||||
|
||||
<div className="footer-wrapper">
|
||||
<h4 className="footer-title">Source</h4>
|
||||
|
||||
<div>
|
||||
<a href={source} className="footer-link-wrapper">
|
||||
{source}
|
||||
<BoxArrowUpRightIcon
|
||||
className="footer-link-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function BoxArrowUpRightIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default SourceFooter;
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import { Meaning, WordData } from '../lib/datatypes';
|
||||
|
||||
interface WordDefinitionsProps {
|
||||
wordData: WordData;
|
||||
}
|
||||
|
||||
const WordDefinitions: React.FC<WordDefinitionsProps> = ({ wordData }) => {
|
||||
return (
|
||||
<div className="word-definitions-wrapper">
|
||||
{wordData.meanings.map((meaning: any, index: number) => (
|
||||
<WordDefinitionBlock key={index} data={meaning} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface WordDefinitionBlockProps {
|
||||
data: Meaning;
|
||||
}
|
||||
|
||||
const WordDefinitionBlock: React.FC<WordDefinitionBlockProps> = ({ data }) => {
|
||||
const { partOfSpeech, definitions, synonyms } = data;
|
||||
|
||||
return (
|
||||
<div className="word-block-wrapper">
|
||||
<div className="word-block-category-container">
|
||||
<h2 className="word-block-category-name">{partOfSpeech}</h2>
|
||||
<div className="word-block-category-divider"></div>
|
||||
</div>
|
||||
|
||||
<div className="word-block-meaning-container">
|
||||
<h3 className="word-block-meaning-title">Meaning</h3>
|
||||
|
||||
<ul className="word-block-meaning-list">
|
||||
{definitions.map((definition: any, index: number) => (
|
||||
<li key={index} className="word-block-meaning-list-item">
|
||||
{definition.definition}{' '}
|
||||
{definition.example && (
|
||||
<span className="word-block-meaning-list-item-example">
|
||||
"{definition.example}"
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{synonyms.length > 0 && (
|
||||
<div className="word-block-synonyms-container">
|
||||
<h3 className="word-block-synonyms-title">Synonyms</h3>
|
||||
|
||||
<ul className="word-block-synonyms-list">
|
||||
{synonyms.map((synonym: string, index: number) => (
|
||||
<li key={index} className="word-block-synonyms-list-item">
|
||||
{synonym}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WordDefinitions;
|
||||
@@ -0,0 +1,63 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { Phonetic } from '../lib/datatypes';
|
||||
|
||||
interface WordHeroProps {
|
||||
data: {
|
||||
word: string;
|
||||
phonetics: Phonetic[];
|
||||
};
|
||||
}
|
||||
|
||||
const WordHero: React.FC<WordHeroProps> = ({ data }) => {
|
||||
const { word, phonetics } = data;
|
||||
|
||||
const americanEnglishPhonetic = phonetics.find((phonetic) =>
|
||||
phonetic.audio?.includes('-us')
|
||||
);
|
||||
const finalPhonetic = americanEnglishPhonetic || phonetics[0];
|
||||
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
|
||||
const playAudio = () => {
|
||||
if (audioRef.current) {
|
||||
audioRef.current.play();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="wordhero-wrapper">
|
||||
<div className="wordhero-word-container">
|
||||
<h1 className="wordhero-word">{word}</h1>
|
||||
{finalPhonetic?.text && (
|
||||
<p className="wordhero-word-phonetic">{finalPhonetic.text}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{finalPhonetic?.audio && (
|
||||
<div>
|
||||
<button onClick={playAudio} className="wordhero-play-btn">
|
||||
<PlayIcon className="wordhero-play-icon" aria-hidden="true" />
|
||||
</button>
|
||||
<audio ref={audioRef} src={finalPhonetic.audio}></audio>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function PlayIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
{...props}
|
||||
>
|
||||
<path d="m11.596 8.697-6.363 3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363 3.692a.802.802 0 0 1 0 1.393" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default WordHero;
|
||||
337
06_js-debug/unterricht/tag49/04_lexifind-starter/src/index.css
Normal file
337
06_js-debug/unterricht/tag49/04_lexifind-starter/src/index.css
Normal file
@@ -0,0 +1,337 @@
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
*,
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.screen-reader-text {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
/* ===== App Component ===== */
|
||||
.app-wrapper {
|
||||
margin: 0 auto;
|
||||
max-width: 42rem; /* 672px */
|
||||
padding: 3.5rem 1.5rem; /* 56px 24px */
|
||||
}
|
||||
|
||||
/* ===== Header Component ===== */
|
||||
.header-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.header-logo-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-logo-img {
|
||||
height: 2.5rem; /* 40px */
|
||||
}
|
||||
|
||||
.header-logo-text {
|
||||
font-family: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
|
||||
font-size: 1.875rem; /* 30px */
|
||||
line-height: 2.25rem; /* 36px */
|
||||
font-weight: 600;
|
||||
margin-left: 1rem; /* 16px */
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.header-select {
|
||||
border-radius: 0.375rem; /* 6px */
|
||||
border-width: 0px;
|
||||
padding: 0.625rem 0.5rem; /* 10px 8px */
|
||||
font-size: 1.125rem; /* 18px */
|
||||
line-height: 1.75rem; /* 28px */
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.header-select:focus {
|
||||
outline-color: #9333ea;
|
||||
}
|
||||
|
||||
/* ===== Searchbar Component ===== */
|
||||
.searchbar-wrapper {
|
||||
margin-top: 4rem; /* 64px */
|
||||
}
|
||||
|
||||
.searchbar-container {
|
||||
position: relative;
|
||||
margin-top: 0.5rem; /* 8px */
|
||||
border-radius: 0.75rem; /* 12px */
|
||||
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
}
|
||||
|
||||
.searchbar-input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border-radius: 0.75rem; /* 12px */
|
||||
border-width: 0px;
|
||||
padding: 1.25rem 0 1.25rem 1.5rem; /* 20px 0 20px 24px */
|
||||
font-size: 1.125rem; /* 18px */
|
||||
line-height: 1.75rem; /* 28px */
|
||||
font-weight: 600;
|
||||
outline-style: solid;
|
||||
outline: 2px solid transparent;
|
||||
outline-color: #f5f5f5;
|
||||
color: #171717;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.searchbar-input:focus {
|
||||
outline-color: #9333ea;
|
||||
}
|
||||
|
||||
.searchbar-input::placeholder {
|
||||
color: #737373;
|
||||
}
|
||||
|
||||
.searchbar-magnifying-glass {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-top-right-radius: 0.75rem; /* 12px */
|
||||
border-bottom-right-radius: 0.75rem; /* 12px */
|
||||
padding-left: 1rem; /* 16px */
|
||||
padding-right: 1rem; /* 16px */
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.searchbar-icon {
|
||||
width: 1.5rem; /* 24px */
|
||||
height: 1.5rem; /* 24px */
|
||||
fill: #6b21a8;
|
||||
}
|
||||
|
||||
/* ===== WordHero Component ===== */
|
||||
.wordhero-wrapper {
|
||||
display: flex;
|
||||
align-items: top;
|
||||
justify-content: space-between;
|
||||
margin-top: 4rem; /* 64px */
|
||||
}
|
||||
|
||||
.wordhero-word-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.wordhero-word {
|
||||
font-size: 3.75rem; /* 60px */
|
||||
line-height: 1;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.wordhero-word-phonetic {
|
||||
font-size: 1.5rem; /* 24px */
|
||||
line-height: 2rem; /* 32px */
|
||||
margin-top: 0.75rem; /* 12px */
|
||||
margin-bottom: 0.75rem; /* 12px */
|
||||
color: #9333ea;
|
||||
}
|
||||
|
||||
.wordhero-play-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 9999px;
|
||||
padding: 1.25rem; /* 20px */
|
||||
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
color: white;
|
||||
background-color: #e9d5ff;
|
||||
}
|
||||
|
||||
.wordhero-play-btn:hover {
|
||||
background-color: #d8b4fe;
|
||||
}
|
||||
|
||||
.wordhero-play-btn:focus {
|
||||
outline: 2px solid transparent;
|
||||
outline-color: #9333ea;
|
||||
}
|
||||
|
||||
.wordhero-play-icon {
|
||||
height: 2.5rem; /* 40px */
|
||||
width: 2.5rem; /* 40px */
|
||||
fill: #9333ea;
|
||||
}
|
||||
|
||||
/* ===== WordDefinitions Component ===== */
|
||||
.word-definitions-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem; /* 32px */
|
||||
margin-top: 4rem; /* 64px */
|
||||
}
|
||||
|
||||
.word-block-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3.5rem; /* 56px */
|
||||
}
|
||||
|
||||
.word-block-category-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem; /* 8px */
|
||||
}
|
||||
|
||||
.word-block-category-name {
|
||||
padding-right: 0.75rem; /* 12px */
|
||||
font-size: 1.5rem; /* 24px */
|
||||
line-height: 2rem; /* 32px */
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.word-block-category-divider {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: #d4d4d4;
|
||||
}
|
||||
|
||||
.word-block-meaning-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 1rem; /* 16px */
|
||||
}
|
||||
|
||||
.word-block-meaning-title {
|
||||
font-size: 1.25rem; /* 20px */
|
||||
line-height: 1.75rem; /* 28px */
|
||||
font-weight: 400;
|
||||
color: #737373;
|
||||
}
|
||||
|
||||
.word-block-meaning-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-left: 1rem; /* 16px */
|
||||
font-size: 1.125rem; /* 18px */
|
||||
line-height: 1.75rem; /* 28px */
|
||||
row-gap: 2rem; /* 32px */
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.word-block-meaning-list-item::marker {
|
||||
padding-left: 2.5rem; /* 40px */
|
||||
color: #9333ea;
|
||||
}
|
||||
|
||||
.word-block-meaning-list-item-example {
|
||||
display: block;
|
||||
margin-top: 0.5rem; /* 8px */
|
||||
color: #9333ea;
|
||||
}
|
||||
|
||||
.word-block-synonyms-container {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
column-gap: 2rem; /* 32px */
|
||||
}
|
||||
|
||||
.word-block-synonyms-title {
|
||||
font-size: 1.25rem; /* 20px */
|
||||
line-height: 1.75rem; /* 28px */
|
||||
font-weight: 400;
|
||||
color: #737373;
|
||||
}
|
||||
|
||||
.word-block-synonyms-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.5rem; /* 8px */
|
||||
}
|
||||
|
||||
.word-block-synonyms-list-item {
|
||||
list-style: none;
|
||||
font-size: 1.125rem; /* 18px */
|
||||
line-height: 1.75rem; /* 28px */
|
||||
font-weight: 600;
|
||||
color: #9333ea;
|
||||
}
|
||||
|
||||
/* ===== SourceFooter Component ===== */
|
||||
.footer-divider {
|
||||
margin-top: 2.5rem; /* 40px */
|
||||
margin-bottom: 2.5rem; /* 40px */
|
||||
height: 1px;
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
|
||||
.footer-wrapper {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 1rem; /* 16px */
|
||||
font-size: 0.875rem; /* 14px */
|
||||
line-height: 1.25rem; /* 20px */
|
||||
}
|
||||
|
||||
.footer-title {
|
||||
font-weight: 400;
|
||||
color: #737373;
|
||||
}
|
||||
|
||||
.footer-link-wrapper {
|
||||
display: flex;
|
||||
text-decoration: underline;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.footer-link-icon {
|
||||
margin-left: 0.5rem; /* 8px */
|
||||
height: 1rem; /* 16px */
|
||||
width: 1rem; /* 16px */
|
||||
fill: #262626;
|
||||
}
|
||||
|
||||
/* ===== Breakpoints ===== */
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.searchbar-input {
|
||||
line-height: 1.5rem; /* 24px */
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.word-block-meaning-list {
|
||||
padding-left: 2.5rem; /* 40px */
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.app-wrapper {
|
||||
padding: 3.5rem 0.5rem; /* 56px 8px */
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export const SERIF_FONTS =
|
||||
'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif';
|
||||
|
||||
export const SANS_SERIF_FONTS =
|
||||
'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"';
|
||||
@@ -0,0 +1,22 @@
|
||||
export interface Phonetic {
|
||||
text?: string;
|
||||
audio?: string;
|
||||
}
|
||||
|
||||
export interface Definition {
|
||||
definition: string;
|
||||
example?: string;
|
||||
}
|
||||
|
||||
export interface Meaning {
|
||||
partOfSpeech: string;
|
||||
definitions: Definition[];
|
||||
synonyms: string[];
|
||||
}
|
||||
|
||||
export interface WordData {
|
||||
word: string;
|
||||
phonetics: Phonetic[];
|
||||
meanings: Meaning[];
|
||||
sourceUrls: string[];
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.tsx'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
1
06_js-debug/unterricht/tag49/04_lexifind-starter/src/vite-env.d.ts
vendored
Normal file
1
06_js-debug/unterricht/tag49/04_lexifind-starter/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
24
06_js-debug/unterricht/tag49/05_state-up-lifting/.gitignore
vendored
Normal file
24
06_js-debug/unterricht/tag49/05_state-up-lifting/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["react", "oxc"],
|
||||
"rules": {
|
||||
"react/rules-of-hooks": "error",
|
||||
"react/only-export-components": ["warn", { "allowConstantExport": true }]
|
||||
}
|
||||
}
|
||||
16
06_js-debug/unterricht/tag49/05_state-up-lifting/README.md
Normal file
16
06_js-debug/unterricht/tag49/05_state-up-lifting/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# React + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some Oxlint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
||||
|
||||
## React Compiler
|
||||
|
||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
||||
|
||||
## Expanding the Oxlint configuration
|
||||
|
||||
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and Oxlint's TypeScript related rules in your project.
|
||||
13
06_js-debug/unterricht/tag49/05_state-up-lifting/index.html
Normal file
13
06_js-debug/unterricht/tag49/05_state-up-lifting/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>05_state-up-lifting</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
1316
06_js-debug/unterricht/tag49/05_state-up-lifting/package-lock.json
generated
Normal file
1316
06_js-debug/unterricht/tag49/05_state-up-lifting/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "05_state-up-lifting",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "oxlint",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.17",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.4",
|
||||
"oxlint": "^1.75.0",
|
||||
"vite": "^8.2.0"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.3 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user