Merge pull request #543 from Proxyfil/feat/keyboard-shortcut

This commit is contained in:
Marlon
2026-04-28 21:53:56 +02:00
committed by GitHub
6 changed files with 333 additions and 189 deletions
+60 -72
View File
@@ -5,150 +5,138 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import type { QuizData } from '$lib/quiz_types';
import { getLocalization } from '$lib/i18n';
import { get_question_title } from '$lib/admin.ts';
import type { PlayerAnswer } from '$lib/admin.ts';
import { socket } from './socket';
import { QuizQuestionType } from '$lib/quiz_types';
import Spinner from '$lib/Spinner.svelte';
import Controls from '$lib/play/admin/controls.svelte';
import Question from '$lib/play/admin/question.svelte';
import { SocketGameControls } from '$lib/play/admin/socket_game_controls.ts';
import type { IGameState } from '$lib/play/admin/game_state.ts';
const { t } = getLocalization();
const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58'];
let question_results = $state(null);
let selected_question = $state(-1);
let timer_res: string = $state();
let shown_question_now: number = $state();
let final_results_clicked = $state(false);
let timer_interval: NodeJS.Timeout;
let answer_count = $state(0);
interface Props {
game_token: string;
quiz_data: QuizData;
bg_color: string;
final_results?: Array<null> | Array<Array<PlayerAnswer>>;
control_visible: boolean;
player_scores: any;
game_state: IGameState;
}
let {
game_token,
quiz_data = $bindable(),
bg_color,
final_results = $bindable([null]),
control_visible,
player_scores = $bindable()
}: Props = $props();
let { game_token, bg_color, game_state = $bindable() }: Props = $props();
socket.on('get_question_results', () => {
console.log('get_question_results');
});
socket.on('set_question_number', (data) => {
timer_res = '0';
game_state.timer_res = '0';
game_state.question_results = null;
game_state.shown_question_now = data.question_index;
game_state.timer_res = game_state.quiz_data.questions[data.question_index].time;
game_state.selected_question = game_state.selected_question + 1;
game_state.answer_count = 0;
clearInterval(timer_interval);
question_results = null;
shown_question_now = data.question_index;
timer_res = quiz_data.questions[data.question_index].time;
selected_question = selected_question + 1;
answer_count = 0;
timer(timer_res);
timer(game_state.timer_res);
});
socket.on('solutions', (_) => {
timer_res = '0';
game_state.timer_res = '0';
clearInterval(timer_interval);
});
socket.on('final_results', (data) => {
// data = JSON.parse(data);
final_results_clicked = true;
timer_res = '0';
final_results = data;
game_state.timer_res = '0';
game_state.final_results = data;
});
socket.on('everyone_answered', (_) => {
timer_res = '0';
game_state.timer_res = '0';
});
socket.on('question_results', (data) => {
console.log('question_results:', data);
question_results = data;
timer_res = '0';
game_state.question_results = data;
game_state.timer_res = '0';
});
socket.on('player_answer', (_) => {
answer_count += 1;
game_state.answer_count += 1;
});
const timer = (time: string) => {
let seconds = Number(time);
timer_interval = setInterval(() => {
if (timer_res === '0') {
if (game_state.timer_res === '0') {
clearInterval(timer_interval);
return;
} else {
seconds--;
}
timer_res = seconds.toString();
game_state.timer_res = seconds.toString();
}, 1000);
};
const socket_game_controls: SocketGameControls = new SocketGameControls(socket);
</script>
{#if control_visible}
<Controls
{bg_color}
{selected_question}
{quiz_data}
bind:timer_res
{final_results}
{socket}
{question_results}
{game_token}
{shown_question_now}
/>
{#if game_state.control_visible}
<Controls {bg_color} {socket_game_controls} {game_token} bind:game_state />
{/if}
{#if timer_res !== '0' && selected_question >= 0}
{#if game_state.timer_res !== '0' && game_state.selected_question >= 0}
<span
class="fixed top-0 bg-red-500 h-8 transition-all"
class:mt-10={control_visible}
style="width: {(100 / parseInt(quiz_data.questions[selected_question].time)) *
parseInt(timer_res)}vw"
class:mt-10={game_state.control_visible}
style="width: {(100 /
parseInt(game_state.quiz_data.questions[game_state.selected_question].time)) *
parseInt(game_state.timer_res)}vw"
></span>
{/if}
<div class="w-full h-full" class:pt-28={control_visible} class:pt-12={!control_visible}>
{#if timer_res !== undefined && !final_results_clicked && !question_results}
<div
class="w-full h-full"
class:pt-28={game_state.control_visible}
class:pt-12={!game_state.control_visible}
>
{#if game_state.timer_res !== undefined && !final_results_clicked && !game_state.question_results}
<!-- Question is shown -->
{#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE}
{#if game_state.quiz_data.questions[game_state.selected_question].type === QuizQuestionType.SLIDE}
{#await import('$lib/play/admin/slide.svelte')}
<Spinner my_20={false} />
{:then c}
<c.default question={quiz_data.questions[selected_question]} />
<c.default
question={game_state.quiz_data.questions[game_state.selected_question]}
/>
{/await}
{:else}
<Question {quiz_data} {selected_question} {timer_res} {answer_count} {default_colors} />
<Question
quiz_data={game_state.quiz_data}
selected_question={game_state.selected_question}
timer_res={game_state.timer_res}
answer_count={game_state.answer_count}
{default_colors}
/>
{/if}
{/if}
<br />
{#if timer_res === '0' && JSON.stringify(final_results) === JSON.stringify( [null] ) && quiz_data.questions[selected_question].type !== QuizQuestionType.SLIDE && question_results !== null && quiz_data.questions[selected_question]?.hide_results !== true}
{#if question_results === undefined}
{#if game_state.timer_res === '0' && JSON.stringify(game_state.final_results) === JSON.stringify( [null] ) && game_state.quiz_data.questions[game_state.selected_question].type !== QuizQuestionType.SLIDE && game_state.question_results !== null && game_state.quiz_data.questions[game_state.selected_question]?.hide_results !== true}
{#if game_state.question_results === undefined}
{#if !final_results_clicked}
<div class="w-full flex justify-center">
<h1 class="text-3xl">{$t('admin_page.no_answers')}</h1>
</div>
{/if}
{:else if quiz_data.questions[selected_question].type === QuizQuestionType.VOTING}
{:else if game_state.quiz_data.questions[game_state.selected_question].type === QuizQuestionType.VOTING}
{#await import('$lib/play/admin/voting_results.svelte')}
<Spinner />
{:then c}
<c.default
data={question_results}
question={quiz_data.questions[selected_question]}
data={game_state.question_results}
question={game_state.quiz_data.questions[game_state.selected_question]}
/>
{/await}
{:else}
@@ -156,24 +144,24 @@ SPDX-License-Identifier: MPL-2.0
<Spinner />
{:then c}
<c.default
bind:data={player_scores}
question={quiz_data.questions[selected_question]}
new_data={question_results}
bind:data={game_state.player_scores}
question={game_state.quiz_data.questions[game_state.selected_question]}
new_data={game_state.question_results}
/>
{/await}
{/if}
{/if}
<br />
{#if selected_question === -1}
{#if game_state.selected_question === -1}
<div class="flex flex-col justify-center w-screen h-full">
<h1 class="text-7xl text-center">{@html quiz_data.title}</h1>
<p class="text-3xl pt-8 text-center">{@html quiz_data.description}</p>
{#if quiz_data.cover_image}
<h1 class="text-7xl text-center">{@html game_state.quiz_data.title}</h1>
<p class="text-3xl pt-8 text-center">{@html game_state.quiz_data.description}</p>
{#if game_state.quiz_data.cover_image}
<div class="flex justify-center align-middle items-center">
<div class="h-[30vh] m-auto w-auto mt-12">
<img
class="max-h-full max-w-full block"
src="/api/v1/storage/download/{quiz_data.cover_image}"
src="/api/v1/storage/download/{game_state.quiz_data.cover_image}"
alt="Not provided"
/>
</div>
+52 -57
View File
@@ -6,52 +6,24 @@ SPDX-License-Identifier: MPL-2.0
<script lang="ts">
import { QuizQuestionType } from '$lib/quiz_types';
import type { QuizData } from '$lib/quiz_types';
import type { Socket } from 'socket.io-client';
import { getLocalization } from '$lib/i18n';
import { SocketGameControls } from '$lib/play/admin/socket_game_controls.ts';
import type { GameState } from '$lib/play/admin/game_state.ts';
interface Props {
bg_color: string;
selected_question: number;
quiz_data: QuizData;
timer_res: string;
final_results: any;
socket: Socket;
socket_game_controls: SocketGameControls;
game_token: string;
question_results: any;
shown_question_now: number;
game_state: GameState;
}
let {
bg_color,
selected_question,
quiz_data,
timer_res = $bindable(),
final_results,
socket,
game_token,
question_results,
shown_question_now
}: Props = $props();
let { bg_color, socket_game_controls, game_token, game_state = $bindable() }: Props = $props();
const { t } = getLocalization();
const set_question_number = (q_number: number) => {
socket.emit('set_question_number', q_number.toString());
};
const get_question_results = () => {
socket.emit('get_question_results', {
game_id: game_token,
question_number: shown_question_now
});
};
const show_solutions = () => {
socket.emit('show_solutions', {});
timer_res = '0';
};
const get_final_results = () => {
socket.emit('get_final_results', {});
socket_game_controls.show_solutions();
game_state.timer_res = '0';
};
</script>
@@ -61,60 +33,83 @@ SPDX-License-Identifier: MPL-2.0
class:text-black={bg_color}
>
<p class="mr-auto ml-0 col-start-1 col-end-1">
{selected_question === -1 ? '0' : selected_question + 1}
/{quiz_data.questions.length}
{game_state.selected_question === -1 ? '0' : game_state.selected_question + 1}
/{game_state.quiz_data.questions.length}
</p>
<div class="justify-self-end ml-auto mr-0 col-start-3 col-end-3">
{#if selected_question + 1 === quiz_data.questions.length && ((timer_res === '0' && question_results !== null) || quiz_data?.questions?.[selected_question]?.type === QuizQuestionType.SLIDE)}
{#if JSON.stringify(final_results) === JSON.stringify([null])}
<button onclick={get_final_results} class="admin-button"
{#if game_state.selected_question + 1 === game_state.quiz_data.questions.length && ((game_state.timer_res === '0' && game_state.question_results !== null) || game_state.quiz_data?.questions?.[game_state.selected_question]?.type === QuizQuestionType.SLIDE)}
{#if JSON.stringify(game_state.final_results) === JSON.stringify([null])}
<button
onclick={() => socket_game_controls.get_final_results()}
class="admin-button"
>{$t('admin_page.get_final_results')}
</button>
{/if}
{:else if timer_res === '0' || selected_question === -1}
{#if (selected_question + 1 !== quiz_data.questions.length && question_results !== null) || selected_question === -1}
{:else if game_state.timer_res === '0' || game_state.selected_question === -1}
{#if (game_state.selected_question + 1 !== game_state.quiz_data.questions.length && game_state.question_results !== null) || game_state.selected_question === -1}
<button
onclick={() => {
set_question_number(selected_question + 1);
socket_game_controls.set_question_number(game_state.selected_question + 1);
}}
class="admin-button"
>{$t('admin_page.next_question', { question: selected_question + 2 })}
>{$t('admin_page.next_question', {
question: game_state.selected_question + 2
})}
</button>
{/if}
{#if question_results === null && selected_question !== -1}
{#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE}
{#if game_state.question_results === null && game_state.selected_question !== -1}
{#if game_state.quiz_data.questions[game_state.selected_question].type === QuizQuestionType.SLIDE}
<button
onclick={() => {
set_question_number(selected_question + 1);
socket_game_controls.set_question_number(
game_state.selected_question + 1
);
}}
class="admin-button"
>{$t('admin_page.next_question', { question: selected_question + 2 })}
>{$t('admin_page.next_question', {
question: game_state.selected_question + 2
})}
</button>
{:else if quiz_data.questions[selected_question]?.hide_results === true}
{:else if game_state.quiz_data.questions[game_state.selected_question]?.hide_results === true}
<button
onclick={() => {
get_question_results();
socket_game_controls.get_question_results(
game_token,
game_state.shown_question_now
);
setTimeout(() => {
set_question_number(selected_question + 1);
socket_game_controls.set_question_number(
game_state.selected_question + 1
);
}, 200);
}}
class="admin-button"
>{$t('admin_page.next_question', { question: selected_question + 2 })}
>{$t('admin_page.next_question', {
question: game_state.selected_question + 2
})}
</button>
{:else}
<button onclick={get_question_results} class="admin-button"
<button
onclick={() =>
socket_game_controls.get_question_results(
game_token,
game_state.shown_question_now
)}
class="admin-button"
>{$t('admin_page.show_results')}
</button>
{/if}
{/if}
{:else if selected_question !== -1}
{#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE}
{:else if game_state.selected_question !== -1}
{#if game_state.quiz_data.questions[game_state.selected_question].type === QuizQuestionType.SLIDE}
<button
onclick={() => {
set_question_number(selected_question + 1);
socket_game_controls.set_question_number(game_state.selected_question + 1);
}}
class="admin-button"
>{$t('admin_page.next_question', { question: selected_question + 2 })}
>{$t('admin_page.next_question', {
question: game_state.selected_question + 2
})}
</button>
{:else}
<button onclick={show_solutions} class="admin-button"
@@ -10,15 +10,22 @@ SPDX-License-Identifier: MPL-2.0
import { getLocalization } from '$lib/i18n';
import GrayButton from '$lib/components/buttons/gray.svelte';
import { fade } from 'svelte/transition';
import { SocketGameControls } from '$lib/play/admin/socket_game_controls.ts';
import type { GameState } from '$lib/play/admin/game_state';
interface Props {
game_pin: string;
players: any;
socket: any;
game_state: GameState;
socket_game_controls: SocketGameControls;
cqc_code: string;
}
let { game_pin, players = $bindable(), socket, cqc_code = $bindable() }: Props = $props();
let {
game_pin,
game_state = $bindable(),
socket_game_controls,
cqc_code = $bindable()
}: Props = $props();
let fullscreen_open = $state(false);
const { t } = getLocalization();
@@ -27,18 +34,6 @@ SPDX-License-Identifier: MPL-2.0
if (cqc_code === 'null') {
cqc_code = null;
}
const kick_player = (username: string) => {
socket.emit('kick_player', { username: username });
for (let i = 0; i < players.length; i++) {
console.log(players[i].username, username);
if (players[i].username === username) {
players.splice(i, 1);
break;
}
}
players = players;
};
</script>
<div class="w-full h-full">
@@ -66,13 +61,13 @@ SPDX-License-Identifier: MPL-2.0
<div class="m-auto">
<div class="flex justify-center my-4">
<p class="m-auto text-2xl">
{#if players.length <= 1}
{#if game_state.players.length <= 1}
{$t('play_page.players_waiting', {
count: players.length ?? 0
count: game_state.players.length ?? 0
})}
{:else}
{$t('play_page.players_waiting_plural', {
count: players.length ?? 0
count: game_state.players.length ?? 0
})}
{/if}
</p>
@@ -85,13 +80,13 @@ SPDX-License-Identifier: MPL-2.0
{:else}
<div class="flex justify-center">
<p class="m-auto text-2xl">
{#if players.length <= 1}
{#if game_state.players.length <= 1}
{$t('play_page.players_waiting', {
count: players.length ?? 0
count: game_state.players.length ?? 0
})}
{:else}
{$t('play_page.players_waiting_plural', {
count: players.length ?? 0
count: game_state.players.length ?? 0
})}
{/if}
</p>
@@ -104,22 +99,22 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex justify-center w-full mt-4">
<div>
<GrayButton
disabled={players.length < 1}
disabled={game_state.players.length < 1}
onclick={() => {
socket.emit('start_game', '');
socket_game_controls.start_game();
}}
>{$t('admin_page.start_game')}
</GrayButton>
</div>
</div>
<div class="flex flex-row w-full mt-4 px-10 flex-wrap">
{#if players.length > 0}
{#each players as player}
{#if game_state.players.length > 0}
{#each game_state.players as player}
<div class="p-2 m-2 border-2 border-[#B07156] rounded-sm hover:cursor-pointer">
<span
class="hover:line-through text-lg"
onclick={() => {
kick_player(player.username);
socket_game_controls.kick_player(player.username, game_state.players);
}}>{player.username}</span
>
<!-- <button>{$t('words.kick')}</button>-->
+27
View File
@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: 2026 Marlon W (Mawoka)
//
// SPDX-License-Identifier: MPL-2.0
import type { Player, PlayerAnswer } from '$lib/admin.ts';
import type { QuizData } from '$lib/quiz_types';
/**
* Interface that include all the game states for the admin page.
* Still in development and not used everywhere but should be a good practice
*/
export interface IGameState {
game_id: string;
players: Player[];
player_scores: Record<string, number>;
selected_question: number;
timer_res: string;
question_results: any;
answer_count: number;
shown_question_now: number;
final_results: Array<null> | Array<Array<PlayerAnswer>>;
game_started: boolean;
quiz_data: QuizData;
control_visible: boolean;
constructor(game_id: string);
}
@@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2026 Marlon W (Mawoka)
//
// SPDX-License-Identifier: MPL-2.0
import type { Socket } from 'socket.io-client';
/**
* Class that encapsulates all the socket calls related to game controls for the admin page.
* Still in development, but it will be used to avoid having socket calls directly in the svelte files, and to have a single place to manage all the game controls related socket calls.
*/
export class SocketGameControls {
socket: Socket;
constructor(socket: Socket) {
this.socket = socket;
}
set_question_number(q_number: number) {
this.socket.emit('set_question_number', q_number.toString());
}
get_question_results(game_id: string, question_number: number) {
this.socket.emit('get_question_results', {
game_id,
question_number
});
}
show_solutions() {
this.socket.emit('show_solutions', {});
}
get_final_results() {
this.socket.emit('get_final_results', {});
}
start_game() {
this.socket.emit('start_game', '');
}
kick_player(username: string, players: any[]) {
this.socket.emit('kick_player', { username: username });
for (let i = 0; i < players.length; i++) {
console.log(players[i].username, username);
if (players[i].username === username) {
players.splice(i, 1);
break;
}
}
return players;
}
}
+120 -34
View File
@@ -5,12 +5,9 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import type { QuizData } from '$lib/quiz_types';
import { socket } from '$lib/socket';
import { getLocalization } from '$lib/i18n';
import { navbarVisible } from '$lib/stores.svelte.ts';
import type { PlayerAnswer, Player } from '$lib/admin.ts';
import SomeAdminScreen from '$lib/admin.svelte';
import GameNotStarted from '$lib/play/admin/game_not_started.svelte';
import { browser } from '$app/environment';
@@ -18,6 +15,11 @@ SPDX-License-Identifier: MPL-2.0
import FinalResults from '$lib/play/admin/final_results.svelte';
import GrayButton from '$lib/components/buttons/gray.svelte';
import { page } from '$app/state';
import { SocketGameControls } from '$lib/play/admin/socket_game_controls.ts';
import type { IGameState } from '$lib/play/admin/game_state.ts';
import { QuizQuestionType, type QuizData } from '$lib/quiz_types';
import type { Player, PlayerAnswer } from '$lib/admin';
import { tinykeys } from '$lib/tinykeys';
navbarVisible.visible = false;
@@ -32,25 +34,83 @@ SPDX-License-Identifier: MPL-2.0
data: any;
}
class GameState implements IGameState {
public game_id: string;
public players: Player[];
public player_scores: Record<string, number>;
public selected_question: number;
public timer_res: string;
public question_results: any;
public answer_count: number;
public shown_question_now: number;
public final_results: Array<null> | Array<Array<PlayerAnswer>>;
public game_started: boolean;
public quiz_data: QuizData;
public control_visible: boolean;
constructor(game_id: string) {
this.game_id = game_id;
this.players = $state([]);
this.player_scores = $state({});
this.selected_question = $state(-1);
this.timer_res = $state(undefined);
this.quiz_data = $state(null);
this.control_visible = $state(true);
this.shown_question_now = $state(-1);
this.final_results = $state([null]);
this.game_started = $state(false);
this.question_results = $state(null);
this.answer_count = $state(0);
}
is_game_ready_to_start(): boolean {
return !this.game_started && this.players.length > 0;
}
is_game_starting(): boolean {
return this.game_started && this.selected_question === -1;
}
is_active_question_last_question(): boolean {
return this.selected_question + 1 === this.quiz_data.questions.length;
}
is_question_results_visible(): boolean {
return this.timer_res === '0' && this.question_results !== null;
}
is_active_question_slide(): boolean {
return (
this.quiz_data?.questions?.[this.selected_question]?.type === QuizQuestionType.SLIDE
);
}
is_question_ended(): boolean {
return (
this.timer_res === '0' &&
this.question_results === null &&
this.selected_question !== -1
);
}
is_question_still_ongoing(): boolean {
return this.timer_res !== '0' && this.selected_question !== -1;
}
}
let { data }: Props = $props();
let game_mode = $state();
let { auto_connect, game_token } = $state(data);
const game_pin = data.game_pin;
let players: Array<Player> = $state([]);
let player_scores = $state({});
let errorMessage = $state('');
let game_started = $state(false);
let quiz_data: QuizData = $state();
let control_visible = $state(true);
//let question_number = '0';
// let question_results = null;
let final_results: Array<null> | Array<Array<PlayerAnswer>> = $state([null]);
let success = $state(false);
let dataexport_download_a = $state();
let warnToLeave = true;
let export_token = $state(undefined);
const socket_game_controls: SocketGameControls = new SocketGameControls(socket);
let game_state: GameState = $state(new GameState(game_token));
const connect = async () => {
socket.emit('register_as_admin', {
game_pin: game_pin,
@@ -64,18 +124,22 @@ SPDX-License-Identifier: MPL-2.0
if (auto_connect) {
connect();
}
tinykeys(window, {
Enter: next_action,
Space: next_action
});
});
socket.on('session_id', (d) => {
const session_id = d.session_id;
});
socket.on('registered_as_admin', (data) => {
quiz_data = JSON.parse(data['game']);
console.log(quiz_data);
game_state.quiz_data = JSON.parse(data['game']);
console.log(game_state.quiz_data);
success = true;
});
socket.on('player_joined', (int_data) => {
players = [...players, int_data];
game_state.players = [...game_state.players, int_data];
});
socket.on('already_registered_as_admin', () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -84,11 +148,11 @@ SPDX-License-Identifier: MPL-2.0
});
socket.on('start_game', (_) => {
game_started = true;
game_state.game_started = true;
});
socket.on('control_visibility', (data) => {
control_visible = data.visible;
game_state.control_visible = data.visible;
});
/* socket.on('question_results', (int_data) => {
@@ -138,11 +202,40 @@ SPDX-License-Identifier: MPL-2.0
window.matchMedia('(prefers-color-scheme: dark)').matches);
}
let bg_color = $derived(quiz_data ? quiz_data.background_color : undefined);
let bg_image = $derived(quiz_data ? quiz_data.background_image : undefined);
let bg_color = $derived(
game_state.quiz_data ? game_state.quiz_data.background_color : undefined
);
let bg_image = $derived(
game_state.quiz_data ? game_state.quiz_data.background_image : undefined
);
let results_saved = $state(false);
let show_final_results = $derived(JSON.stringify(final_results) !== JSON.stringify([null]));
let show_final_results = $derived(
JSON.stringify(game_state.final_results) !== JSON.stringify([null])
);
// This function in called in every keyboard event in this page
const next_action = () => {
if (
game_state.is_active_question_last_question() &&
(game_state.is_question_results_visible() || game_state.is_active_question_slide())
) {
socket_game_controls.get_final_results();
} else if (
game_state.is_game_starting() ||
game_state.is_question_results_visible() ||
game_state.is_active_question_slide()
) {
socket_game_controls.set_question_number(game_state.selected_question + 1);
} else if (game_state.is_question_still_ongoing()) {
socket_game_controls.show_solutions();
game_state.timer_res = '0';
} else if (game_state.is_question_ended()) {
socket_game_controls.get_question_results(game_token, game_state.shown_question_now);
} else {
console.warn('No action available for this event');
}
};
</script>
<svelte:window onbeforeunload={confirmUnload} />
@@ -156,8 +249,8 @@ SPDX-License-Identifier: MPL-2.0
: `unset`}; background-color: {bg_color ? bg_color : 'transparent'}"
class:text-black={bg_color}
>
{#if JSON.stringify(final_results) !== JSON.stringify([null])}
{#if control_visible}
{#if JSON.stringify(game_state.final_results) !== JSON.stringify([null])}
{#if game_state.control_visible}
<div class="w-screen flex justify-center mt-16">
<div class="w-fit">
{#if export_token === undefined}
@@ -197,28 +290,21 @@ SPDX-License-Identifier: MPL-2.0
</div>
</div>
{/if}
<FinalResults bind:data={player_scores} {show_final_results} />
<FinalResults bind:data={game_state.player_scores} {show_final_results} />
{/if}
{#if !success}
{#if errorMessage !== ''}
<p class="text-red-700">{errorMessage}</p>
{/if}
{:else if !game_started}
{:else if !game_state.game_started}
<GameNotStarted
{game_pin}
bind:players
{socket}
bind:game_state
{socket_game_controls}
cqc_code={page.url.searchParams.get('cqc_code')}
/>
{:else}
<SomeAdminScreen
bind:final_results
{game_token}
bind:quiz_data
{bg_color}
bind:player_scores
{control_visible}
/>
<SomeAdminScreen {game_token} {bg_color} bind:game_state />
{/if}
</div>
<a