mirror of
https://github.com/HexCardGames/HexDeck.git
synced 2025-09-03 18:48:38 +02:00
style: add prettier configuration and format code
This commit is contained in:
8
frontend/.prettierrc.json
Normal file
8
frontend/.prettierrc.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"semi": true,
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 200,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always"
|
||||
}
|
@ -42,6 +42,6 @@ If you have state that's important to retain within a component, consider creati
|
||||
```ts
|
||||
// store.ts
|
||||
// An extremely simple external store
|
||||
import { writable } from 'svelte/store'
|
||||
export default writable(0)
|
||||
import { writable } from "svelte/store";
|
||||
export default writable(0);
|
||||
```
|
||||
|
@ -1,12 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/hexagon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/hexagon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
5082
frontend/package-lock.json
generated
Normal file
5082
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,42 +1,43 @@
|
||||
{
|
||||
"name": "hexdeck-frontend",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tailwindcss/forms": "^0.5.9",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@tsconfig/svelte": "^5.0.4",
|
||||
"flowbite": "^3.1.2",
|
||||
"flowbite-svelte": "^0.48.4",
|
||||
"flowbite-svelte-icons": "^2.0.2",
|
||||
"sass-embedded": "^1.85.1",
|
||||
"svelte": "^5.22.5",
|
||||
"svelte-check": "^4.1.4",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "~5.8.2",
|
||||
"vite": "^6.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@roxi/routify": "3.0.0-next.254",
|
||||
"@tailwindcss/vite": "^4.0.10",
|
||||
"lucide-svelte": "^0.477.0",
|
||||
"routify": "^2.0.1",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"svelte-exmarkdown": "^4.0.3",
|
||||
"svelte-i18n": "^4.0.1",
|
||||
"tailwindcss": "^4.0.10"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"esbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
"name": "hexdeck-frontend",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tailwindcss/forms": "^0.5.9",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@tsconfig/svelte": "^5.0.4",
|
||||
"flowbite": "^3.1.2",
|
||||
"flowbite-svelte": "^0.48.4",
|
||||
"flowbite-svelte-icons": "^2.0.2",
|
||||
"prettier": "3.5.3",
|
||||
"sass-embedded": "^1.85.1",
|
||||
"svelte": "^5.22.5",
|
||||
"svelte-check": "^4.1.4",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "~5.8.2",
|
||||
"vite": "^6.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@roxi/routify": "3.0.0-next.254",
|
||||
"@tailwindcss/vite": "^4.0.10",
|
||||
"lucide-svelte": "^0.477.0",
|
||||
"routify": "^2.0.1",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"svelte-exmarkdown": "^4.0.3",
|
||||
"svelte-i18n": "^4.0.1",
|
||||
"tailwindcss": "^4.0.10"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"esbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
6171
frontend/pnpm-lock.yaml
generated
6171
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,88 +1,90 @@
|
||||
@theme inline {
|
||||
--color-primary-50: color-mix(in srgb, var(--primary) 10%, white);
|
||||
--color-primary-100: color-mix(in srgb, var(--primary) 20%, white);
|
||||
--color-primary-200: color-mix(in srgb, var(--primary) 30%, white);
|
||||
--color-primary-300: color-mix(in srgb, var(--primary) 40%, white);
|
||||
--color-primary-400: color-mix(in srgb, var(--primary) 50%, white);
|
||||
--color-primary-500: color-mix(in srgb, var(--primary) 60%, white);
|
||||
--color-primary-600: color-mix(in srgb, var(--primary) 70%, white);
|
||||
--color-primary-700: color-mix(in srgb, var(--primary) 80%, white);
|
||||
--color-primary-800: color-mix(in srgb, var(--primary) 90%, white);
|
||||
--color-primary-900: var(--primary);
|
||||
--color-primary-950: color-mix(in srgb, var(--primary) 90%, black);
|
||||
--color-primary-50: color-mix(in srgb, var(--primary) 10%, white);
|
||||
--color-primary-100: color-mix(in srgb, var(--primary) 20%, white);
|
||||
--color-primary-200: color-mix(in srgb, var(--primary) 30%, white);
|
||||
--color-primary-300: color-mix(in srgb, var(--primary) 40%, white);
|
||||
--color-primary-400: color-mix(in srgb, var(--primary) 50%, white);
|
||||
--color-primary-500: color-mix(in srgb, var(--primary) 60%, white);
|
||||
--color-primary-600: color-mix(in srgb, var(--primary) 70%, white);
|
||||
--color-primary-700: color-mix(in srgb, var(--primary) 80%, white);
|
||||
--color-primary-800: color-mix(in srgb, var(--primary) 90%, white);
|
||||
--color-primary-900: var(--primary);
|
||||
--color-primary-950: color-mix(in srgb, var(--primary) 90%, black);
|
||||
|
||||
--color-secondary-50: color-mix(in srgb, var(--secondary) 10%, white);
|
||||
--color-secondary-100: color-mix(in srgb, var(--secondary) 20%, white);
|
||||
--color-secondary-200: color-mix(in srgb, var(--secondary) 30%, white);
|
||||
--color-secondary-300: color-mix(in srgb, var(--secondary) 40%, white);
|
||||
--color-secondary-400: color-mix(in srgb, var(--secondary) 50%, white);
|
||||
--color-secondary-500: color-mix(in srgb, var(--secondary) 60%, white);
|
||||
--color-secondary-600: color-mix(in srgb, var(--secondary) 70%, white);
|
||||
--color-secondary-700: color-mix(in srgb, var(--secondary) 80%, white);
|
||||
--color-secondary-800: color-mix(in srgb, var(--secondary) 90%, white);
|
||||
--color-secondary-900: var(--secondary);
|
||||
--color-secondary-950: color-mix(in srgb, var(--secondary) 90%, black);
|
||||
--color-secondary-50: color-mix(in srgb, var(--secondary) 10%, white);
|
||||
--color-secondary-100: color-mix(in srgb, var(--secondary) 20%, white);
|
||||
--color-secondary-200: color-mix(in srgb, var(--secondary) 30%, white);
|
||||
--color-secondary-300: color-mix(in srgb, var(--secondary) 40%, white);
|
||||
--color-secondary-400: color-mix(in srgb, var(--secondary) 50%, white);
|
||||
--color-secondary-500: color-mix(in srgb, var(--secondary) 60%, white);
|
||||
--color-secondary-600: color-mix(in srgb, var(--secondary) 70%, white);
|
||||
--color-secondary-700: color-mix(in srgb, var(--secondary) 80%, white);
|
||||
--color-secondary-800: color-mix(in srgb, var(--secondary) 90%, white);
|
||||
--color-secondary-900: var(--secondary);
|
||||
--color-secondary-950: color-mix(in srgb, var(--secondary) 90%, black);
|
||||
|
||||
--color-tertiary-50: color-mix(in srgb, var(--tertiary) 10%, white);
|
||||
--color-tertiary-100: color-mix(in srgb, var(--tertiary) 20%, white);
|
||||
--color-tertiary-200: color-mix(in srgb, var(--tertiary) 30%, white);
|
||||
--color-tertiary-300: color-mix(in srgb, var(--tertiary) 40%, white);
|
||||
--color-tertiary-400: color-mix(in srgb, var(--tertiary) 50%, white);
|
||||
--color-tertiary-500: color-mix(in srgb, var(--tertiary) 60%, white);
|
||||
--color-tertiary-600: color-mix(in srgb, var(--tertiary) 70%, white);
|
||||
--color-tertiary-700: color-mix(in srgb, var(--tertiary) 80%, white);
|
||||
--color-tertiary-800: color-mix(in srgb, var(--tertiary) 90%, white);
|
||||
--color-tertiary-900: var(--tertiary);
|
||||
--color-tertiary-950: color-mix(in srgb, var(--tertiary) 90%, black);
|
||||
--color-tertiary-50: color-mix(in srgb, var(--tertiary) 10%, white);
|
||||
--color-tertiary-100: color-mix(in srgb, var(--tertiary) 20%, white);
|
||||
--color-tertiary-200: color-mix(in srgb, var(--tertiary) 30%, white);
|
||||
--color-tertiary-300: color-mix(in srgb, var(--tertiary) 40%, white);
|
||||
--color-tertiary-400: color-mix(in srgb, var(--tertiary) 50%, white);
|
||||
--color-tertiary-500: color-mix(in srgb, var(--tertiary) 60%, white);
|
||||
--color-tertiary-600: color-mix(in srgb, var(--tertiary) 70%, white);
|
||||
--color-tertiary-700: color-mix(in srgb, var(--tertiary) 80%, white);
|
||||
--color-tertiary-800: color-mix(in srgb, var(--tertiary) 90%, white);
|
||||
--color-tertiary-900: var(--tertiary);
|
||||
--color-tertiary-950: color-mix(in srgb, var(--tertiary) 90%, black);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
button,
|
||||
[role='button'] {
|
||||
cursor: pointer;
|
||||
}
|
||||
button,
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
color: var(--default-element-color);
|
||||
background-color: var(--default-background-color);
|
||||
transition: color 0.4s ease, background-color 0.2s ease;
|
||||
margin: 0;
|
||||
color: var(--default-element-color);
|
||||
background-color: var(--default-background-color);
|
||||
transition:
|
||||
color 0.4s ease,
|
||||
background-color 0.2s ease;
|
||||
}
|
||||
|
||||
:root {
|
||||
font-family: "Lexend Deca", serif;
|
||||
font-optical-sizing: auto;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
font-family: "Lexend Deca", serif;
|
||||
font-optical-sizing: auto;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
--primary: #d5b6ff;
|
||||
--secondary: #ffb6f5;
|
||||
--tertiary: #08aeea;
|
||||
--primary: #d5b6ff;
|
||||
--secondary: #ffb6f5;
|
||||
--tertiary: #08aeea;
|
||||
|
||||
--default-element-color: #213547;
|
||||
--default-background-color: #ffffff;
|
||||
--default-element-color: #213547;
|
||||
--default-background-color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-theme {
|
||||
--primary: #53346a;
|
||||
--secondary: #710cff;
|
||||
--tertiary: #0b465c;
|
||||
--primary: #53346a;
|
||||
--secondary: #710cff;
|
||||
--tertiary: #0b465c;
|
||||
|
||||
--default-element-color: rgba(255, 255, 255, 0.87);
|
||||
--default-background-color: oklch(0.279 0.041 260.031);
|
||||
--default-element-color: rgba(255, 255, 255, 0.87);
|
||||
--default-background-color: oklch(0.279 0.041 260.031);
|
||||
}
|
||||
|
||||
body.light-theme {
|
||||
--primary: #d5b6ff;
|
||||
--secondary: #ffb6f5;
|
||||
--tertiary: #08aeea;
|
||||
--primary: #d5b6ff;
|
||||
--secondary: #ffb6f5;
|
||||
--tertiary: #08aeea;
|
||||
|
||||
--default-element-color: #213547;
|
||||
--default-background-color: #ffffff;
|
||||
}
|
||||
--default-element-color: #213547;
|
||||
--default-background-color: #ffffff;
|
||||
}
|
||||
|
@ -1,103 +1,103 @@
|
||||
{
|
||||
"page_name": "HexDeck",
|
||||
"header": {
|
||||
"theme_btn": {
|
||||
"tooltip": "Thema wechseln: {current_theme}",
|
||||
"dark": "Dunkel",
|
||||
"light": "Hell",
|
||||
"system": "System"
|
||||
"page_name": "HexDeck",
|
||||
"header": {
|
||||
"theme_btn": {
|
||||
"tooltip": "Thema wechseln: {current_theme}",
|
||||
"dark": "Dunkel",
|
||||
"light": "Hell",
|
||||
"system": "System"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"imprint": "Impressum",
|
||||
"github": "GitHub"
|
||||
},
|
||||
"404": {
|
||||
"404_page_not_found": "404 - Seite nicht gefunden",
|
||||
"page_not_found": "Die Seite {page} konnte nicht gefunden werden."
|
||||
},
|
||||
"imprint": {
|
||||
"title": "Impressum",
|
||||
"something_went_wrong": "Etwas ist schief gelaufen",
|
||||
"timeout_while_loading": "Zeitüberschreitung beim Laden",
|
||||
"retry": "Erneut versuchen",
|
||||
"go_back": "Zurück"
|
||||
},
|
||||
"landing_page": {
|
||||
"sub_title": "Multiplayer, kostenlos, für alle",
|
||||
"connect_room": {
|
||||
"rejoin_last_room": "Letztes Spiel erneut beitreten",
|
||||
"join_last_room": "Letztem Raum beitreten",
|
||||
"enter_room_code": "Raumcode eingeben",
|
||||
"join_room": "Beitreten",
|
||||
"enter_room_code_to_join": "Bitte geben Sie einen Raumcode ein, um beizutreten",
|
||||
"or": "oder",
|
||||
"create_a_room": "Einen Raum erstellen"
|
||||
},
|
||||
"open_source_container": {
|
||||
"title": "Open Source",
|
||||
"content": "Der Quellcode dieses Spiels ist auf GitHub verfügbar",
|
||||
"github": "GitHub"
|
||||
},
|
||||
"stats_container": {
|
||||
"title": "Statistiken",
|
||||
"online_player_count": "Aktuelle Spieler: {count}",
|
||||
"current_game_rooms": "Aktuelle Spiele: {count}",
|
||||
"games_played": "Gespielte Spiele: {count}",
|
||||
"no_data": "Keine Daten"
|
||||
},
|
||||
"show_footer": "Footer anzeigen"
|
||||
},
|
||||
"lobby": {
|
||||
"search_player": "Spieler suchen...",
|
||||
"kick_player": "Spieler entfernen",
|
||||
"confirm_kick_player_message": "Möchten Sie den Spieler {player_name} wirklich entfernen?",
|
||||
"confirm_kick_player": "Entfernen",
|
||||
"rename_yourself": "Sich selbst umbenennen",
|
||||
"rename_player": "Spieler umbenennen",
|
||||
"regenerate_join_code": "Beitrittscode neu generieren",
|
||||
"copy_join_code": "Beitrittscode kopieren",
|
||||
"room_join_code": "Raum Beitrittscode",
|
||||
"copy_code": "Code kopieren",
|
||||
"copy_join_link": "Link kopieren",
|
||||
"leave_game": "Spiel verlassen",
|
||||
"confirm_leave_message": "Möchten Sie das Spiel wirklich verlassen?",
|
||||
"confirm_leave": "Ja, verlassen",
|
||||
"cancel": "Abbrechen",
|
||||
"start_game": "Spiel starten",
|
||||
"copied": "Kopiert",
|
||||
"player_name": "Spielername",
|
||||
"status": "Status",
|
||||
"host": "Host",
|
||||
"you": "Du"
|
||||
},
|
||||
"player_status": {
|
||||
"connected": "Verbunden",
|
||||
"disconnected": "Getrennt"
|
||||
},
|
||||
"game_status": {
|
||||
"game_status": "Spielstatus: {game_status}",
|
||||
"lobby": "Lobby",
|
||||
"running": "Läuft",
|
||||
"ended": "Beendet"
|
||||
},
|
||||
"error_messages": {
|
||||
"no_room_found": "Kein Raum mit diesem Code gefunden",
|
||||
"request_timeout": "Internet fehlgeschlagen! (Zeitüberschreitung)",
|
||||
"invalid_player": "Ungültiger Spieler",
|
||||
"invalid_session": "Ungültige Sitzung",
|
||||
"game_not_running": "Das Spiel läuft nicht",
|
||||
"player_not_active": "Der Spieler ist nicht aktiv",
|
||||
"insufficient_permission": "Unzureichende Berechtigung",
|
||||
"username_taken": "Der Benutzername ist bereits vergeben",
|
||||
"game_already_started": "Das Spiel hat bereits begonnen",
|
||||
"missing_parameter": "Fehlender Parameter",
|
||||
"invalid_card_index": "Ungültige Karte ausgewählt (Index außerhalb der Grenzen)",
|
||||
"card_not_playable": "Die Karte ist nicht spielbar",
|
||||
"card_not_updatable": "Die Karte ist nicht aktualisierbar",
|
||||
"error_message": "Fehlermeldung: {error_message}"
|
||||
},
|
||||
"game_screen": {
|
||||
"loading": "Laden"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"imprint": "Impressum",
|
||||
"github": "GitHub"
|
||||
},
|
||||
"404": {
|
||||
"404_page_not_found": "404 - Seite nicht gefunden",
|
||||
"page_not_found": "Die Seite {page} konnte nicht gefunden werden."
|
||||
},
|
||||
"imprint": {
|
||||
"title": "Impressum",
|
||||
"something_went_wrong": "Etwas ist schief gelaufen",
|
||||
"timeout_while_loading": "Zeitüberschreitung beim Laden",
|
||||
"retry": "Erneut versuchen",
|
||||
"go_back": "Zurück"
|
||||
},
|
||||
"landing_page": {
|
||||
"sub_title": "Multiplayer, kostenlos, für alle",
|
||||
"connect_room": {
|
||||
"rejoin_last_room": "Letztes Spiel erneut beitreten",
|
||||
"join_last_room": "Letztem Raum beitreten",
|
||||
"enter_room_code": "Raumcode eingeben",
|
||||
"join_room": "Beitreten",
|
||||
"enter_room_code_to_join": "Bitte geben Sie einen Raumcode ein, um beizutreten",
|
||||
"or": "oder",
|
||||
"create_a_room": "Einen Raum erstellen"
|
||||
},
|
||||
"open_source_container": {
|
||||
"title": "Open Source",
|
||||
"content": "Der Quellcode dieses Spiels ist auf GitHub verfügbar",
|
||||
"github": "GitHub"
|
||||
},
|
||||
"stats_container": {
|
||||
"title": "Statistiken",
|
||||
"online_player_count": "Aktuelle Spieler: {count}",
|
||||
"current_game_rooms": "Aktuelle Spiele: {count}",
|
||||
"games_played": "Gespielte Spiele: {count}",
|
||||
"no_data": "Keine Daten"
|
||||
},
|
||||
"show_footer": "Footer anzeigen"
|
||||
},
|
||||
"lobby": {
|
||||
"search_player": "Spieler suchen...",
|
||||
"kick_player": "Spieler entfernen",
|
||||
"confirm_kick_player_message": "Möchten Sie den Spieler {player_name} wirklich entfernen?",
|
||||
"confirm_kick_player": "Entfernen",
|
||||
"rename_yourself": "Sich selbst umbenennen",
|
||||
"rename_player": "Spieler umbenennen",
|
||||
"regenerate_join_code": "Beitrittscode neu generieren",
|
||||
"copy_join_code": "Beitrittscode kopieren",
|
||||
"room_join_code": "Raum Beitrittscode",
|
||||
"copy_code": "Code kopieren",
|
||||
"copy_join_link": "Link kopieren",
|
||||
"leave_game": "Spiel verlassen",
|
||||
"confirm_leave_message": "Möchten Sie das Spiel wirklich verlassen?",
|
||||
"confirm_leave": "Ja, verlassen",
|
||||
"cancel": "Abbrechen",
|
||||
"start_game": "Spiel starten",
|
||||
"copied": "Kopiert",
|
||||
"player_name": "Spielername",
|
||||
"status": "Status",
|
||||
"host": "Host",
|
||||
"you": "Du"
|
||||
},
|
||||
"player_status": {
|
||||
"connected": "Verbunden",
|
||||
"disconnected": "Getrennt"
|
||||
},
|
||||
"game_status": {
|
||||
"game_status": "Spielstatus: {game_status}",
|
||||
"lobby": "Lobby",
|
||||
"running": "Läuft",
|
||||
"ended": "Beendet"
|
||||
},
|
||||
"error_messages": {
|
||||
"no_room_found": "Kein Raum mit diesem Code gefunden",
|
||||
"request_timeout": "Internet fehlgeschlagen! (Zeitüberschreitung)",
|
||||
"invalid_player": "Ungültiger Spieler",
|
||||
"invalid_session": "Ungültige Sitzung",
|
||||
"game_not_running": "Das Spiel läuft nicht",
|
||||
"player_not_active": "Der Spieler ist nicht aktiv",
|
||||
"insufficient_permission": "Unzureichende Berechtigung",
|
||||
"username_taken": "Der Benutzername ist bereits vergeben",
|
||||
"game_already_started": "Das Spiel hat bereits begonnen",
|
||||
"missing_parameter": "Fehlender Parameter",
|
||||
"invalid_card_index": "Ungültige Karte ausgewählt (Index außerhalb der Grenzen)",
|
||||
"card_not_playable": "Die Karte ist nicht spielbar",
|
||||
"card_not_updatable": "Die Karte ist nicht aktualisierbar",
|
||||
"error_message": "Fehlermeldung: {error_message}"
|
||||
},
|
||||
"game_screen": {
|
||||
"loading": "Laden"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,105 +1,105 @@
|
||||
{
|
||||
"page_name": "HexDeck",
|
||||
"header": {
|
||||
"theme_btn": {
|
||||
"tooltip": "Switch theme: {current_theme}",
|
||||
"dark": "Dark",
|
||||
"light": "Light",
|
||||
"system": "System"
|
||||
"page_name": "HexDeck",
|
||||
"header": {
|
||||
"theme_btn": {
|
||||
"tooltip": "Switch theme: {current_theme}",
|
||||
"dark": "Dark",
|
||||
"light": "Light",
|
||||
"system": "System"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"imprint": "Imprint",
|
||||
"github": "GitHub"
|
||||
},
|
||||
"404": {
|
||||
"404_page_not_found": "404 - Page not found",
|
||||
"page_not_found": "The page {page} could not be found."
|
||||
},
|
||||
"imprint": {
|
||||
"title": "Imprint",
|
||||
"something_went_wrong": "Something went wrong",
|
||||
"timeout_while_loading": "Timeout while loading",
|
||||
"retry": "Retry",
|
||||
"go_back": "Back"
|
||||
},
|
||||
"landing_page": {
|
||||
"sub_title": "Multiplayer, free, for everyone",
|
||||
"connect_room": {
|
||||
"rejoin_last_room": "Rejoin last game",
|
||||
"join_last_room": "Join last room",
|
||||
"enter_room_code": "Enter a room code",
|
||||
"join_room": "Join",
|
||||
"enter_room_code_to_join": "Please enter a room code to join",
|
||||
"or": "or",
|
||||
"create_a_room": "Create a room"
|
||||
},
|
||||
"open_source_container": {
|
||||
"title": "Open Source",
|
||||
"content": "The Source Code of this game is available on GitHub",
|
||||
"github": "GitHub"
|
||||
},
|
||||
"stats_container": {
|
||||
"title": "Stats",
|
||||
"online_player_count": "Current player: {count}",
|
||||
"current_game_rooms": "Current games: {count}",
|
||||
"games_played": "Games played: {count}",
|
||||
"no_data": "No data"
|
||||
},
|
||||
"show_footer": "Show footer"
|
||||
},
|
||||
"lobby": {
|
||||
"search_player": "Search player...",
|
||||
"kick_player": "Kick player",
|
||||
"confirm_kick_player_message": "Do you really want to kick the player {player_name}?",
|
||||
"confirm_kick_player": "Kick",
|
||||
"rename_yourself": "Rename yourself",
|
||||
"rename_player": "Rename player",
|
||||
"regenerate_join_code": "Regenerate join code",
|
||||
"copy_join_code": "Copy join code",
|
||||
"room_join_code": "Room Join Code",
|
||||
"copy_code": "Copy Code",
|
||||
"copy_join_link": "Copy Link",
|
||||
"leave_game": "Leave game",
|
||||
"confirm_leave_message": "Do you really want to leave the game?",
|
||||
"confirm_leave": "Yes, leave",
|
||||
"cancel": "Cancel",
|
||||
"start_game": "Start game",
|
||||
"copied": "Copied",
|
||||
"player_name": "Player Name",
|
||||
"status": "Status",
|
||||
"host": "Host",
|
||||
"you": "You",
|
||||
"player": "Player",
|
||||
"return_to_game": "Return to game"
|
||||
},
|
||||
"player_status": {
|
||||
"connected": "Connected",
|
||||
"disconnected": "Disconnected"
|
||||
},
|
||||
"game_status": {
|
||||
"game_status": "Game status:",
|
||||
"lobby": "Lobby",
|
||||
"running": "Running",
|
||||
"ended": "Ended"
|
||||
},
|
||||
"error_messages": {
|
||||
"no_room_found": "No room was found with this code",
|
||||
"request_timeout": "Internet failed! (Timeout)",
|
||||
"invalid_player": "Invalid player",
|
||||
"invalid_session": "Invalid session",
|
||||
"game_not_running": "The game is not running",
|
||||
"player_not_active": "The player ist not active",
|
||||
"insufficient_permission": "Insufficient permission",
|
||||
"username_taken": "The username is already taken",
|
||||
"game_already_started": "The game has already started",
|
||||
"missing_parameter": "Missing parameter",
|
||||
"invalid_card_index": "Invalid card selected (Index not in bounds)",
|
||||
"card_not_playable": "The card is not playable",
|
||||
"card_not_updatable": "The card is not updatable",
|
||||
"error_message": "Error message: {error_message}"
|
||||
},
|
||||
"game_screen": {
|
||||
"loading": "Loading"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"imprint": "Imprint",
|
||||
"github": "GitHub"
|
||||
},
|
||||
"404": {
|
||||
"404_page_not_found": "404 - Page not found",
|
||||
"page_not_found": "The page {page} could not be found."
|
||||
},
|
||||
"imprint": {
|
||||
"title": "Imprint",
|
||||
"something_went_wrong": "Something went wrong",
|
||||
"timeout_while_loading": "Timeout while loading",
|
||||
"retry": "Retry",
|
||||
"go_back": "Back"
|
||||
},
|
||||
"landing_page": {
|
||||
"sub_title": "Multiplayer, free, for everyone",
|
||||
"connect_room": {
|
||||
"rejoin_last_room": "Rejoin last game",
|
||||
"join_last_room": "Join last room",
|
||||
"enter_room_code": "Enter a room code",
|
||||
"join_room": "Join",
|
||||
"enter_room_code_to_join": "Please enter a room code to join",
|
||||
"or": "or",
|
||||
"create_a_room": "Create a room"
|
||||
},
|
||||
"open_source_container": {
|
||||
"title": "Open Source",
|
||||
"content": "The Source Code of this game is available on GitHub",
|
||||
"github": "GitHub"
|
||||
},
|
||||
"stats_container": {
|
||||
"title": "Stats",
|
||||
"online_player_count": "Current player: {count}",
|
||||
"current_game_rooms": "Current games: {count}",
|
||||
"games_played": "Games played: {count}",
|
||||
"no_data": "No data"
|
||||
},
|
||||
"show_footer": "Show footer"
|
||||
},
|
||||
"lobby": {
|
||||
"search_player": "Search player...",
|
||||
"kick_player": "Kick player",
|
||||
"confirm_kick_player_message": "Do you really want to kick the player {player_name}?",
|
||||
"confirm_kick_player": "Kick",
|
||||
"rename_yourself": "Rename yourself",
|
||||
"rename_player": "Rename player",
|
||||
"regenerate_join_code": "Regenerate join code",
|
||||
"copy_join_code": "Copy join code",
|
||||
"room_join_code": "Room Join Code",
|
||||
"copy_code": "Copy Code",
|
||||
"copy_join_link": "Copy Link",
|
||||
"leave_game": "Leave game",
|
||||
"confirm_leave_message": "Do you really want to leave the game?",
|
||||
"confirm_leave": "Yes, leave",
|
||||
"cancel": "Cancel",
|
||||
"start_game": "Start game",
|
||||
"copied": "Copied",
|
||||
"player_name": "Player Name",
|
||||
"status": "Status",
|
||||
"host": "Host",
|
||||
"you": "You",
|
||||
"player": "Player",
|
||||
"return_to_game": "Return to game"
|
||||
},
|
||||
"player_status": {
|
||||
"connected": "Connected",
|
||||
"disconnected": "Disconnected"
|
||||
},
|
||||
"game_status": {
|
||||
"game_status": "Game status:",
|
||||
"lobby": "Lobby",
|
||||
"running": "Running",
|
||||
"ended": "Ended"
|
||||
},
|
||||
"error_messages": {
|
||||
"no_room_found": "No room was found with this code",
|
||||
"request_timeout": "Internet failed! (Timeout)",
|
||||
"invalid_player": "Invalid player",
|
||||
"invalid_session": "Invalid session",
|
||||
"game_not_running": "The game is not running",
|
||||
"player_not_active": "The player ist not active",
|
||||
"insufficient_permission": "Insufficient permission",
|
||||
"username_taken": "The username is already taken",
|
||||
"game_already_started": "The game has already started",
|
||||
"missing_parameter": "Missing parameter",
|
||||
"invalid_card_index": "Invalid card selected (Index not in bounds)",
|
||||
"card_not_playable": "The card is not playable",
|
||||
"card_not_updatable": "The card is not updatable",
|
||||
"error_message": "Error message: {error_message}"
|
||||
},
|
||||
"game_screen": {
|
||||
"loading": "Loading"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { addMessages, register, init, getLocaleFromNavigator } from 'svelte-i18n';
|
||||
import { addMessages, register, init, getLocaleFromNavigator } from "svelte-i18n";
|
||||
|
||||
import en from './en.json';
|
||||
import de from './de.json';
|
||||
import en from "./en.json";
|
||||
import de from "./de.json";
|
||||
|
||||
addMessages('en', en);
|
||||
addMessages('de', de);
|
||||
addMessages("en", en);
|
||||
addMessages("de", de);
|
||||
|
||||
register('en', () => import('./en.json'));
|
||||
register('de', () => import('./de.json'));
|
||||
register("en", () => import("./en.json"));
|
||||
register("de", () => import("./de.json"));
|
||||
|
||||
const initialLocale = getLocaleFromNavigator();
|
||||
console.log('Initial locale:', initialLocale);
|
||||
console.log("Initial locale:", initialLocale);
|
||||
|
||||
init({
|
||||
fallbackLocale: 'en',
|
||||
initialLocale: initialLocale,
|
||||
});
|
||||
fallbackLocale: "en",
|
||||
initialLocale: initialLocale,
|
||||
});
|
||||
|
@ -1,12 +1,12 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Lexend+Deca:wght@100..900&display=swap');
|
||||
@import 'tailwindcss';
|
||||
@import url("https://fonts.googleapis.com/css2?family=Lexend+Deca:wght@100..900&display=swap");
|
||||
@import "tailwindcss";
|
||||
@plugin 'flowbite/plugin';
|
||||
@custom-variant dark {
|
||||
@media not print {
|
||||
.dark & {
|
||||
@slot;
|
||||
}
|
||||
.dark & {
|
||||
@slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@source "../node_modules/flowbite-svelte/dist";
|
||||
@import './app.scss';
|
||||
@import "./app.scss";
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { mount } from 'svelte'
|
||||
import './i18n/i18n'
|
||||
import './index.css'
|
||||
import App from './App.svelte'
|
||||
import { mount } from "svelte";
|
||||
import "./i18n/i18n";
|
||||
import "./index.css";
|
||||
import App from "./App.svelte";
|
||||
|
||||
const app = mount(App, {
|
||||
target: document.getElementById('app')!,
|
||||
})
|
||||
|
||||
export default app
|
||||
target: document.getElementById("app")!,
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
@ -5,11 +5,10 @@
|
||||
import { _ } from "svelte-i18n";
|
||||
import { onMount } from "svelte";
|
||||
import { GameState, sessionStore } from "../stores/sessionStore";
|
||||
import { Button, Spinner, Tooltip } from "flowbite-svelte";
|
||||
import { Spinner } from "flowbite-svelte";
|
||||
import { SvelteDate } from "svelte/reactivity";
|
||||
import { requestJoinRoom } from "../stores/roomStore";
|
||||
import gameStore, { toggleLobbyOverlay } from "../stores/gameStore";
|
||||
import { UsersRound } from "lucide-svelte";
|
||||
import gameStore from "../stores/gameStore";
|
||||
|
||||
onMount(async () => {
|
||||
// TODO: check if already connected to room, currently its overwriting the session
|
||||
@ -31,9 +30,7 @@
|
||||
|
||||
{#if !$sessionStore.connected}
|
||||
<div class="flex flex-row w-full mt-32 h-full justify-center items-center">
|
||||
<div
|
||||
class="flex flex-col items-center gap-6 p-7 md:flex-row md:gap-8 rounded-2xl"
|
||||
>
|
||||
<div class="flex flex-col items-center gap-6 p-7 md:flex-row md:gap-8 rounded-2xl">
|
||||
<div>
|
||||
<Spinner size="12" class="text-primary-100" />
|
||||
</div>
|
||||
@ -42,9 +39,7 @@
|
||||
{$_("game_screen.loading")}
|
||||
</span>
|
||||
<span class="font-medium text-sky-500">
|
||||
{$sessionStore.players?.find(
|
||||
(player) => player.PlayerId == $sessionStore.userId,
|
||||
)?.Username}
|
||||
{$sessionStore.players?.find((player) => player.PlayerId == $sessionStore.userId)?.Username}
|
||||
</span>
|
||||
<span class="flex gap-2 font-medium text-gray-600 dark:text-gray-400">
|
||||
<span>{new SvelteDate().toLocaleString()}</span>
|
||||
|
@ -88,11 +88,7 @@
|
||||
}
|
||||
|
||||
.Header {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
var(--default-background-color) 30%,
|
||||
transparent 100%
|
||||
);
|
||||
background: linear-gradient(180deg, var(--default-background-color) 30%, transparent 100%);
|
||||
opacity: 1;
|
||||
position: fixed;
|
||||
height: 100px;
|
||||
@ -103,11 +99,7 @@
|
||||
}
|
||||
|
||||
.Header-bg {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
var(--primary) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
background: linear-gradient(180deg, var(--primary) 50%, transparent 100%);
|
||||
z-index: -1;
|
||||
margin: 0px;
|
||||
position: absolute;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
interface GameState {
|
||||
isLobbyOverlayShown: boolean;
|
||||
@ -11,10 +11,10 @@ const initialState: GameState = {
|
||||
const gameStore = writable<GameState>(initialState);
|
||||
|
||||
export const toggleLobbyOverlay = () => {
|
||||
gameStore.update(state => ({
|
||||
gameStore.update((state) => ({
|
||||
...state,
|
||||
isLobbyOverlayShown: !state.isLobbyOverlayShown,
|
||||
}));
|
||||
};
|
||||
|
||||
export default gameStore;
|
||||
export default gameStore;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Gamepad2 } from 'lucide-svelte';
|
||||
import { Gamepad2 } from "lucide-svelte";
|
||||
|
||||
const options = {
|
||||
page_icon: Gamepad2,
|
||||
}
|
||||
};
|
||||
|
||||
export default options;
|
||||
export default options;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { sessionStore } from './sessionStore';
|
||||
import { writable } from "svelte/store";
|
||||
import { sessionStore } from "./sessionStore";
|
||||
|
||||
export const loading = writable<"join" | "create" | false>(false);
|
||||
export const join_error = writable<string | false>(false);
|
||||
@ -143,4 +143,4 @@ export async function checkSessionData() {
|
||||
rejoinRoomCode.set(lastSessionData.joinCode as string);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,303 +1,303 @@
|
||||
import { writable, get } from 'svelte/store';
|
||||
import { io, Socket } from 'socket.io-client';
|
||||
import { writable, get } from "svelte/store";
|
||||
import { io, Socket } from "socket.io-client";
|
||||
|
||||
export enum GameState {
|
||||
Undefined = -1,
|
||||
Lobby,
|
||||
Running,
|
||||
Ended
|
||||
Undefined = -1,
|
||||
Lobby,
|
||||
Running,
|
||||
Ended,
|
||||
}
|
||||
|
||||
interface PlayerPermissionObj {
|
||||
isHost: boolean;
|
||||
isHost: boolean;
|
||||
}
|
||||
|
||||
interface GameOptions { }
|
||||
interface GameOptions {}
|
||||
|
||||
interface PlayerObj {
|
||||
PlayerId: string;
|
||||
Username: string;
|
||||
Permissions: number;
|
||||
IsConnected: boolean;
|
||||
PlayerId: string;
|
||||
Username: string;
|
||||
Permissions: number;
|
||||
IsConnected: boolean;
|
||||
}
|
||||
|
||||
interface SessionData {
|
||||
roomId: string | null;
|
||||
joinCode: string | null;
|
||||
gameOptions: GameOptions;
|
||||
players: Array<PlayerObj>;
|
||||
cardDeckId: string | null;
|
||||
gameState: GameState;
|
||||
socket: Socket | null;
|
||||
connected: boolean;
|
||||
userId: string | null;
|
||||
messages: string[];
|
||||
sessionToken: string | null;
|
||||
roomId: string | null;
|
||||
joinCode: string | null;
|
||||
gameOptions: GameOptions;
|
||||
players: Array<PlayerObj>;
|
||||
cardDeckId: string | null;
|
||||
gameState: GameState;
|
||||
socket: Socket | null;
|
||||
connected: boolean;
|
||||
userId: string | null;
|
||||
messages: string[];
|
||||
sessionToken: string | null;
|
||||
}
|
||||
|
||||
interface RoomInfoObj {
|
||||
RoomId: string;
|
||||
JoinCode: string;
|
||||
TopCard: any;
|
||||
GameState: GameState;
|
||||
CardDeckId: number;
|
||||
Winner?: string;
|
||||
Players: PlayerObj[];
|
||||
RoomId: string;
|
||||
JoinCode: string;
|
||||
TopCard: any;
|
||||
GameState: GameState;
|
||||
CardDeckId: number;
|
||||
Winner?: string;
|
||||
Players: PlayerObj[];
|
||||
}
|
||||
|
||||
interface StatusInfoObj {
|
||||
IsError: boolean;
|
||||
StatusCode: string;
|
||||
Message: string;
|
||||
IsError: boolean;
|
||||
StatusCode: string;
|
||||
Message: string;
|
||||
}
|
||||
|
||||
class SessionManager {
|
||||
private store = writable<SessionData>({
|
||||
roomId: null,
|
||||
joinCode: null,
|
||||
gameState: -1,
|
||||
gameOptions: {},
|
||||
players: [],
|
||||
cardDeckId: null,
|
||||
socket: null,
|
||||
connected: false,
|
||||
userId: null,
|
||||
messages: [],
|
||||
sessionToken: null,
|
||||
});
|
||||
|
||||
private socket: Socket | null = null;
|
||||
|
||||
constructor() {
|
||||
const storedSessionIds = this.getStoredSessionIds();
|
||||
if (storedSessionIds) {
|
||||
console.info(`Found stored session: ${JSON.stringify(storedSessionIds)}`);
|
||||
// this.connect(storedSessionIds.sessionToken, storedSessionIds.userId);
|
||||
}
|
||||
}
|
||||
|
||||
getState() {
|
||||
return get(this.store);
|
||||
}
|
||||
|
||||
startGame() {
|
||||
this.socket?.emit("StartGame");
|
||||
}
|
||||
|
||||
hasSessionData(): boolean {
|
||||
const state = this.getState();
|
||||
if (state.sessionToken && state.userId) return true;
|
||||
const sessionIds = localStorage.getItem('currentSessionIds');
|
||||
if (!sessionIds) return false;
|
||||
const sessionIdsJson = JSON.parse(sessionIds);
|
||||
return typeof sessionIdsJson.userId === "string" && typeof sessionIdsJson.sessionToken === "string";
|
||||
}
|
||||
|
||||
private checkPermissionBit(permissionNumber: number, bitIndex: number): boolean {
|
||||
return (permissionNumber & (1 << bitIndex)) > 0;
|
||||
}
|
||||
|
||||
getPlayerPermissions(PlayerId?: string): PlayerPermissionObj {
|
||||
if (!PlayerId) PlayerId = this.getState().userId ?? undefined;
|
||||
const playerPermissionNumber: number = this.getState().players?.find((player) => player.PlayerId == PlayerId)?.Permissions ?? 0;
|
||||
return {
|
||||
isHost: this.checkPermissionBit(playerPermissionNumber, 0)
|
||||
};
|
||||
}
|
||||
|
||||
subscribe = this.store.subscribe;
|
||||
|
||||
private getStoredSessionIds(): { sessionToken: string, userId: string } | null {
|
||||
if (typeof window === 'undefined') return null;
|
||||
const sessionIds = localStorage.getItem('currentSessionIds');
|
||||
if (!sessionIds) return null;
|
||||
const sessionIdsJson = JSON.parse(sessionIds);
|
||||
if (typeof sessionIdsJson.userId !== "string" || typeof sessionIdsJson.sessionToken !== "string") {
|
||||
return null;
|
||||
}
|
||||
return { sessionToken: sessionIdsJson.sessionToken, userId: sessionIdsJson.userId };
|
||||
}
|
||||
|
||||
private saveSessionIds(sessionToken: string, userId: string) {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('currentSessionIds', JSON.stringify({ sessionToken, userId, joinCode: this.getState().joinCode }));
|
||||
}
|
||||
}
|
||||
|
||||
private saveJoinCode() {
|
||||
const sessionIds = localStorage.getItem('currentSessionIds');
|
||||
if (!sessionIds) return;
|
||||
const sessionIdsJson = JSON.parse(sessionIds);
|
||||
localStorage.setItem('currentSessionIds', JSON.stringify({ sessionToken: sessionIdsJson.sessionToken, userId: sessionIdsJson.userId, joinCode: this.getState().joinCode }));
|
||||
}
|
||||
|
||||
private clearSessionIds() {
|
||||
if (typeof window !== 'undefined') {
|
||||
const sessionIds = localStorage.getItem('currentSessionIds');
|
||||
if (!sessionIds) return;
|
||||
const sessionIdsJson = JSON.parse(sessionIds);
|
||||
const lastSessionData = { joinCode: sessionIdsJson.joinCode };
|
||||
localStorage.setItem('lastSessionIds', JSON.stringify(lastSessionData));
|
||||
localStorage.removeItem('currentSessionIds');
|
||||
}
|
||||
}
|
||||
|
||||
isConnected(): boolean {
|
||||
return this.socket?.connected ?? false;
|
||||
}
|
||||
|
||||
hasRoomData(): boolean {
|
||||
return get(this.store).gameState != -1;
|
||||
}
|
||||
|
||||
getUserId(): string | undefined {
|
||||
return this.getState().userId ?? undefined;
|
||||
}
|
||||
|
||||
getUser(playerId?: string): PlayerObj | undefined {
|
||||
if (!playerId) playerId = this.getUserId();
|
||||
return this.getState().players.find((player) => player.PlayerId == playerId);
|
||||
}
|
||||
|
||||
kickPlayer(playerId: string) {
|
||||
if (!this.getPlayerPermissions().isHost) return;
|
||||
this.socket?.emit("KickPlayer", JSON.stringify({ PlayerId: playerId }));
|
||||
}
|
||||
|
||||
renamePlayer(playerId: string | undefined, newName: string) {
|
||||
if (!playerId) playerId = this.getUserId();
|
||||
if (!this.getPlayerPermissions().isHost && playerId != this.getUserId()) return;
|
||||
this.socket?.emit("UpdatePlayer", JSON.stringify({ PlayerId: playerId, Username: newName }));
|
||||
}
|
||||
|
||||
isCurrentPlayer(playerId: string): boolean {
|
||||
return this.getState().userId == playerId;
|
||||
}
|
||||
|
||||
connect(sessionToken?: string, userId?: string) {
|
||||
if (!sessionToken) sessionToken = this.getState().sessionToken || undefined;
|
||||
if (!userId) userId = this.getState().userId || undefined;
|
||||
|
||||
if (!sessionToken || !userId) {
|
||||
const storedSessionIds = this.getStoredSessionIds();
|
||||
if (!sessionToken) sessionToken = storedSessionIds?.sessionToken;
|
||||
if (!userId) userId = storedSessionIds?.userId;
|
||||
}
|
||||
|
||||
if (this.socket) {
|
||||
console.warn(`Socket already connected! Rejecting new connection to ${sessionToken}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.socket = io({
|
||||
transports: ['websocket'],
|
||||
query: { sessionToken },
|
||||
private store = writable<SessionData>({
|
||||
roomId: null,
|
||||
joinCode: null,
|
||||
gameState: -1,
|
||||
gameOptions: {},
|
||||
players: [],
|
||||
cardDeckId: null,
|
||||
socket: null,
|
||||
connected: false,
|
||||
userId: null,
|
||||
messages: [],
|
||||
sessionToken: null,
|
||||
});
|
||||
|
||||
this.setupSocketEventHandlers(sessionToken, userId);
|
||||
}
|
||||
private socket: Socket | null = null;
|
||||
|
||||
private setupSocketEventHandlers(sessionToken: string, userId: string) {
|
||||
this.socket?.on('connect', () => this.handleConnect(sessionToken, userId));
|
||||
this.socket?.on('disconnect', this.handleDisconnect.bind(this));
|
||||
this.socket?.on('Status', this.handleStatus.bind(this));
|
||||
this.socket?.on('RoomInfo', this.handleRoomInfo.bind(this));
|
||||
this.socket?.on('error', this.handleError.bind(this));
|
||||
}
|
||||
|
||||
private handleConnect(sessionToken: string, userId: string) {
|
||||
console.info('Connected to room');
|
||||
this.saveSessionIds(sessionToken, userId);
|
||||
window.history.replaceState({}, "", "/Game");
|
||||
this.store.update((state) => ({
|
||||
...state,
|
||||
socket: this.socket,
|
||||
userId,
|
||||
connected: true,
|
||||
sessionToken,
|
||||
}));
|
||||
}
|
||||
|
||||
private handleDisconnect() {
|
||||
console.info('Disconnected from server');
|
||||
this.store.update((state) => ({ ...state, connected: false }));
|
||||
}
|
||||
|
||||
private handleStatus(message: StatusInfoObj) {
|
||||
console.log("Status: ", message);
|
||||
if (message.IsError && message.StatusCode !== "connection_from_different_socket") {
|
||||
this.leaveRoom();
|
||||
constructor() {
|
||||
const storedSessionIds = this.getStoredSessionIds();
|
||||
if (storedSessionIds) {
|
||||
console.info(`Found stored session: ${JSON.stringify(storedSessionIds)}`);
|
||||
// this.connect(storedSessionIds.sessionToken, storedSessionIds.userId);
|
||||
}
|
||||
}
|
||||
if (message.IsError) {
|
||||
this.socket = null;
|
||||
window.history.replaceState({}, "", "/");
|
||||
}
|
||||
this.store.update((state) => ({
|
||||
...state,
|
||||
messages: [...state.messages, message],
|
||||
}));
|
||||
}
|
||||
|
||||
private handleRoomInfo(message: RoomInfoObj) {
|
||||
console.log("RoomInfo: ", message);
|
||||
this.store.update((state) => ({
|
||||
...state,
|
||||
roomId: message.RoomId,
|
||||
joinCode: message.JoinCode,
|
||||
gameState: message.GameState,
|
||||
cardDeckId: message.CardDeckId,
|
||||
players: message.Players,
|
||||
}));
|
||||
this.saveJoinCode();
|
||||
this.store.update((state) => ({
|
||||
...state,
|
||||
messages: [...state.messages, message],
|
||||
}));
|
||||
}
|
||||
|
||||
private handleError(error: string) {
|
||||
console.error('Socket error:', error);
|
||||
}
|
||||
|
||||
sendMessage(message: string) {
|
||||
if (this.socket && message.trim()) {
|
||||
this.socket.emit('event', message);
|
||||
getState() {
|
||||
return get(this.store);
|
||||
}
|
||||
}
|
||||
|
||||
leaveRoom() {
|
||||
console.log("leave room");
|
||||
if (this.socket) {
|
||||
this.socket.disconnect();
|
||||
this.socket = null;
|
||||
startGame() {
|
||||
this.socket?.emit("StartGame");
|
||||
}
|
||||
if (this.getState().sessionToken) {
|
||||
fetch(`/api/room/leave`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
SessionToken: this.getState().sessionToken
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
hasSessionData(): boolean {
|
||||
const state = this.getState();
|
||||
if (state.sessionToken && state.userId) return true;
|
||||
const sessionIds = localStorage.getItem("currentSessionIds");
|
||||
if (!sessionIds) return false;
|
||||
const sessionIdsJson = JSON.parse(sessionIds);
|
||||
return typeof sessionIdsJson.userId === "string" && typeof sessionIdsJson.sessionToken === "string";
|
||||
}
|
||||
|
||||
private checkPermissionBit(permissionNumber: number, bitIndex: number): boolean {
|
||||
return (permissionNumber & (1 << bitIndex)) > 0;
|
||||
}
|
||||
|
||||
getPlayerPermissions(PlayerId?: string): PlayerPermissionObj {
|
||||
if (!PlayerId) PlayerId = this.getState().userId ?? undefined;
|
||||
const playerPermissionNumber: number = this.getState().players?.find((player) => player.PlayerId == PlayerId)?.Permissions ?? 0;
|
||||
return {
|
||||
isHost: this.checkPermissionBit(playerPermissionNumber, 0),
|
||||
};
|
||||
}
|
||||
|
||||
subscribe = this.store.subscribe;
|
||||
|
||||
private getStoredSessionIds(): { sessionToken: string; userId: string } | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
const sessionIds = localStorage.getItem("currentSessionIds");
|
||||
if (!sessionIds) return null;
|
||||
const sessionIdsJson = JSON.parse(sessionIds);
|
||||
if (typeof sessionIdsJson.userId !== "string" || typeof sessionIdsJson.sessionToken !== "string") {
|
||||
return null;
|
||||
}
|
||||
return { sessionToken: sessionIdsJson.sessionToken, userId: sessionIdsJson.userId };
|
||||
}
|
||||
|
||||
private saveSessionIds(sessionToken: string, userId: string) {
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.setItem("currentSessionIds", JSON.stringify({ sessionToken, userId, joinCode: this.getState().joinCode }));
|
||||
}
|
||||
}
|
||||
|
||||
private saveJoinCode() {
|
||||
const sessionIds = localStorage.getItem("currentSessionIds");
|
||||
if (!sessionIds) return;
|
||||
const sessionIdsJson = JSON.parse(sessionIds);
|
||||
localStorage.setItem("currentSessionIds", JSON.stringify({ sessionToken: sessionIdsJson.sessionToken, userId: sessionIdsJson.userId, joinCode: this.getState().joinCode }));
|
||||
}
|
||||
|
||||
private clearSessionIds() {
|
||||
if (typeof window !== "undefined") {
|
||||
const sessionIds = localStorage.getItem("currentSessionIds");
|
||||
if (!sessionIds) return;
|
||||
const sessionIdsJson = JSON.parse(sessionIds);
|
||||
const lastSessionData = { joinCode: sessionIdsJson.joinCode };
|
||||
localStorage.setItem("lastSessionIds", JSON.stringify(lastSessionData));
|
||||
localStorage.removeItem("currentSessionIds");
|
||||
}
|
||||
}
|
||||
|
||||
isConnected(): boolean {
|
||||
return this.socket?.connected ?? false;
|
||||
}
|
||||
|
||||
hasRoomData(): boolean {
|
||||
return get(this.store).gameState != -1;
|
||||
}
|
||||
|
||||
getUserId(): string | undefined {
|
||||
return this.getState().userId ?? undefined;
|
||||
}
|
||||
|
||||
getUser(playerId?: string): PlayerObj | undefined {
|
||||
if (!playerId) playerId = this.getUserId();
|
||||
return this.getState().players.find((player) => player.PlayerId == playerId);
|
||||
}
|
||||
|
||||
kickPlayer(playerId: string) {
|
||||
if (!this.getPlayerPermissions().isHost) return;
|
||||
this.socket?.emit("KickPlayer", JSON.stringify({ PlayerId: playerId }));
|
||||
}
|
||||
|
||||
renamePlayer(playerId: string | undefined, newName: string) {
|
||||
if (!playerId) playerId = this.getUserId();
|
||||
if (!this.getPlayerPermissions().isHost && playerId != this.getUserId()) return;
|
||||
this.socket?.emit("UpdatePlayer", JSON.stringify({ PlayerId: playerId, Username: newName }));
|
||||
}
|
||||
|
||||
isCurrentPlayer(playerId: string): boolean {
|
||||
return this.getState().userId == playerId;
|
||||
}
|
||||
|
||||
connect(sessionToken?: string, userId?: string) {
|
||||
if (!sessionToken) sessionToken = this.getState().sessionToken || undefined;
|
||||
if (!userId) userId = this.getState().userId || undefined;
|
||||
|
||||
if (!sessionToken || !userId) {
|
||||
const storedSessionIds = this.getStoredSessionIds();
|
||||
if (!sessionToken) sessionToken = storedSessionIds?.sessionToken;
|
||||
if (!userId) userId = storedSessionIds?.userId;
|
||||
}
|
||||
|
||||
if (this.socket) {
|
||||
console.warn(`Socket already connected! Rejecting new connection to ${sessionToken}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.socket = io({
|
||||
transports: ["websocket"],
|
||||
query: { sessionToken },
|
||||
});
|
||||
|
||||
this.setupSocketEventHandlers(sessionToken, userId);
|
||||
}
|
||||
|
||||
private setupSocketEventHandlers(sessionToken: string, userId: string) {
|
||||
this.socket?.on("connect", () => this.handleConnect(sessionToken, userId));
|
||||
this.socket?.on("disconnect", this.handleDisconnect.bind(this));
|
||||
this.socket?.on("Status", this.handleStatus.bind(this));
|
||||
this.socket?.on("RoomInfo", this.handleRoomInfo.bind(this));
|
||||
this.socket?.on("error", this.handleError.bind(this));
|
||||
}
|
||||
|
||||
private handleConnect(sessionToken: string, userId: string) {
|
||||
console.info("Connected to room");
|
||||
this.saveSessionIds(sessionToken, userId);
|
||||
window.history.replaceState({}, "", "/Game");
|
||||
this.store.update((state) => ({
|
||||
...state,
|
||||
socket: this.socket,
|
||||
userId,
|
||||
connected: true,
|
||||
sessionToken,
|
||||
}));
|
||||
}
|
||||
|
||||
private handleDisconnect() {
|
||||
console.info("Disconnected from server");
|
||||
this.store.update((state) => ({ ...state, connected: false }));
|
||||
}
|
||||
|
||||
private handleStatus(message: StatusInfoObj) {
|
||||
console.log("Status: ", message);
|
||||
if (message.IsError && message.StatusCode !== "connection_from_different_socket") {
|
||||
this.leaveRoom();
|
||||
}
|
||||
if (message.IsError) {
|
||||
this.socket = null;
|
||||
window.history.replaceState({}, "", "/");
|
||||
}
|
||||
this.store.update((state) => ({
|
||||
...state,
|
||||
messages: [...state.messages, message],
|
||||
}));
|
||||
}
|
||||
|
||||
private handleRoomInfo(message: RoomInfoObj) {
|
||||
console.log("RoomInfo: ", message);
|
||||
this.store.update((state) => ({
|
||||
...state,
|
||||
roomId: message.RoomId,
|
||||
joinCode: message.JoinCode,
|
||||
gameState: message.GameState,
|
||||
cardDeckId: message.CardDeckId,
|
||||
players: message.Players,
|
||||
}));
|
||||
this.saveJoinCode();
|
||||
this.store.update((state) => ({
|
||||
...state,
|
||||
messages: [...state.messages, message],
|
||||
}));
|
||||
}
|
||||
|
||||
private handleError(error: string) {
|
||||
console.error("Socket error:", error);
|
||||
}
|
||||
|
||||
sendMessage(message: string) {
|
||||
if (this.socket && message.trim()) {
|
||||
this.socket.emit("event", message);
|
||||
}
|
||||
}
|
||||
|
||||
leaveRoom() {
|
||||
console.log("leave room");
|
||||
if (this.socket) {
|
||||
this.socket.disconnect();
|
||||
this.socket = null;
|
||||
}
|
||||
if (this.getState().sessionToken) {
|
||||
fetch(`/api/room/leave`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
SessionToken: this.getState().sessionToken,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
this.clearSessionIds();
|
||||
this.store.set({
|
||||
roomId: null,
|
||||
joinCode: null,
|
||||
gameState: -1,
|
||||
gameOptions: {},
|
||||
players: [],
|
||||
cardDeckId: null,
|
||||
socket: null,
|
||||
connected: false,
|
||||
userId: null,
|
||||
messages: [],
|
||||
sessionToken: null,
|
||||
});
|
||||
window.history.replaceState({}, "", "/");
|
||||
}
|
||||
this.clearSessionIds();
|
||||
this.store.set({
|
||||
roomId: null,
|
||||
joinCode: null,
|
||||
gameState: -1,
|
||||
gameOptions: {},
|
||||
players: [],
|
||||
cardDeckId: null,
|
||||
socket: null,
|
||||
connected: false,
|
||||
userId: null,
|
||||
messages: [],
|
||||
sessionToken: null,
|
||||
});
|
||||
window.history.replaceState({}, "", "/");
|
||||
}
|
||||
}
|
||||
|
||||
export const sessionStore = new SessionManager();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
const getSystemTheme = () => window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
const getSystemTheme = () => (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
|
||||
|
||||
const storedTheme = localStorage.getItem("theme") as Theme | null;
|
||||
const initialTheme: Theme = storedTheme === "dark" || storedTheme === "light" ? storedTheme : "system";
|
||||
@ -10,7 +10,7 @@ export const theme = writable<Theme>(initialTheme);
|
||||
|
||||
const applyTheme = (value: Theme) => {
|
||||
const resolvedTheme = value === "system" ? getSystemTheme() : value;
|
||||
|
||||
|
||||
document.documentElement.classList.toggle("dark", resolvedTheme === "dark");
|
||||
document.documentElement.setAttribute("data-theme", resolvedTheme);
|
||||
document.body.classList.toggle("dark-theme", resolvedTheme === "dark");
|
||||
@ -24,7 +24,7 @@ theme.subscribe(applyTheme);
|
||||
// Watch for system theme changes when "system" mode is enabled
|
||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
mediaQuery.addEventListener("change", () => {
|
||||
theme.update(current => {
|
||||
theme.update((current) => {
|
||||
if (current === "system") applyTheme("system");
|
||||
return current;
|
||||
});
|
||||
@ -35,7 +35,7 @@ export const setTheme = (value: Theme) => {
|
||||
};
|
||||
|
||||
export const toggleTheme = () => {
|
||||
theme.update(current => {
|
||||
theme.update((current) => {
|
||||
if (current === "dark") return "light";
|
||||
if (current === "light") return "system";
|
||||
return "dark";
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
||||
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
||||
|
||||
export default {
|
||||
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
}
|
||||
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
};
|
||||
|
@ -1,21 +1,21 @@
|
||||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"resolveJsonModule": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||
* Note that setting allowJs false does not prevent the use
|
||||
* of JS in `.svelte` files.
|
||||
*/
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force"
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
|
||||
"exclude": [".routify"]
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"resolveJsonModule": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||
* Note that setting allowJs false does not prevent the use
|
||||
* of JS in `.svelte` files.
|
||||
*/
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force"
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
|
||||
"exclude": [".routify"]
|
||||
}
|
||||
|
@ -1,7 +1,4 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
"files": [],
|
||||
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
@ -1,24 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
|
@ -1,26 +1,28 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||
import routify from '@roxi/routify/vite-plugin'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import { defineConfig } from "vite";
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
import routify from "@roxi/routify/vite-plugin";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
routify({/* config */ }),
|
||||
tailwindcss(),
|
||||
svelte()
|
||||
],
|
||||
server: {
|
||||
host: true,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://10.10.39.145:3000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/socket.io': {
|
||||
target: 'http://10.10.39.145:3000',
|
||||
ws: true,
|
||||
changeOrigin: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
plugins: [
|
||||
routify({
|
||||
/* config */
|
||||
}),
|
||||
tailwindcss(),
|
||||
svelte(),
|
||||
],
|
||||
server: {
|
||||
host: true,
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "http://10.10.39.145:3000",
|
||||
changeOrigin: true,
|
||||
},
|
||||
"/socket.io": {
|
||||
target: "http://10.10.39.145:3000",
|
||||
ws: true,
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
Reference in New Issue
Block a user