Updated to Svelte 5

This commit is contained in:
Mawoka
2025-09-21 12:42:04 +02:00
parent fa533257b3
commit a88cf13f82
137 changed files with 2279 additions and 1948 deletions
+4 -4
View File
@@ -5,12 +5,12 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { navbarVisible } from '$lib/stores';
import { page } from '$app/stores';
import { navbarVisible } from '$lib/stores.svelte.ts';
import { page } from '$app/state';
import { getLocalization } from '$lib/i18n';
navbarVisible.set(true);
let status = $page.status;
navbarVisible.visible = true;
let status = page.status;
const { t } = getLocalization();
</script>
+18 -4
View File
@@ -4,15 +4,21 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0
-->
<script>
<script lang="ts">
import '../app.css';
import Navbar from '$lib/navbar.svelte';
import { navbarVisible, pathname } from '$lib/stores';
import { pathname } from '$lib/stores';
import { navbarVisible } from '$lib/stores.svelte';
// import * as Sentry from '@sentry/browser';
// import { BrowserTracing } from '@sentry/tracing';
import { initLocalizationContext } from '$lib/i18n';
import { browser } from '$app/environment';
import CommandPalette from '$lib/components/commandpalette.svelte';
interface Props {
children?: import('svelte').Snippet;
}
let { children }: Props = $props();
// import Alert from '$lib/modals/alert.svelte';
/* afterNavigate(() => {
@@ -86,15 +92,23 @@ SPDX-License-Identifier: MPL-2.0
{/if}
</svelte:head>
{#if $navbarVisible}
<!-- {#if navbarVisible.visible = true.visible}
<Navbar />
<div class="pt-16 h-screen">
<div class="z-40" />
<div class="z-40"></div>
<slot />
</div>
{:else}
<slot />
{/if} -->
{#if navbarVisible.visible}
<Navbar />
<div class="pt-16">
<div class="z-40"></div>
<!-- extra content above slot -->
</div>
{/if}
<slot />
<CommandPalette />
<!--{#if $alertModal.open ?? false}
+18 -18
View File
@@ -5,7 +5,7 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { navbarVisible } from '$lib/stores';
import { navbarVisible } from '$lib/stores.svelte.ts';
import { getLocalization } from '$lib/i18n';
import Footer from '$lib/footer.svelte';
import WebPOpenGraph from '$lib/assets/landing/opengraph-home.webp';
@@ -25,7 +25,7 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization();
navbarVisible.set(true);
navbarVisible.visible = true;
/* interface StatsData {
quiz_count: number;
@@ -36,7 +36,7 @@ SPDX-License-Identifier: MPL-2.0
const response = await fetch('/api/v1/stats/combined');
return await response.json();
};*/
let newsletterModalOpen;
let newsletterModalOpen = $state();
onMount(() => {
const ls = localStorage.getItem('newsletter');
newsletterModalOpen = ls === null;
@@ -62,8 +62,8 @@ SPDX-License-Identifier: MPL-2.0
Winners
}
let selected_create_thing = SelectedCreateThing.Create;
let selected_play_thing = SelectedPlayThing.Select;
let selected_create_thing = $state(SelectedCreateThing.Create);
let selected_play_thing = $state(SelectedPlayThing.Select);
/* <li>No;
Tracking < /li>
@@ -123,7 +123,7 @@ SPDX-License-Identifier: MPL-2.0
content: $t('index_page.community_driven_content')
}
];
let selected_classquiz_reason = 0;
let selected_classquiz_reason = $state(0);
</script>
<svelte:head>
@@ -256,10 +256,10 @@ SPDX-License-Identifier: MPL-2.0
>
<div
class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
on:click={() => {
onclick={() => {
selected_create_thing = SelectedCreateThing.Create;
}}
on:keyup={() => {
onkeyup={() => {
selected_create_thing = SelectedCreateThing.Create;
}}
class:shadow-2xl={selected_create_thing === SelectedCreateThing.Create}
@@ -289,10 +289,10 @@ SPDX-License-Identifier: MPL-2.0
</div>
<div
class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
on:click={() => {
onclick={() => {
selected_create_thing = SelectedCreateThing.Find;
}}
on:keyup={() => {
onkeyup={() => {
selected_create_thing = SelectedCreateThing.Find;
}}
class:shadow-2xl={selected_create_thing === SelectedCreateThing.Find}
@@ -399,10 +399,10 @@ SPDX-License-Identifier: MPL-2.0
>
<div
class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
on:click={() => {
onclick={() => {
selected_play_thing = SelectedPlayThing.Select;
}}
on:keyup={() => {
onkeyup={() => {
selected_play_thing = SelectedPlayThing.Select;
}}
class:shadow-2xl={selected_play_thing === SelectedPlayThing.Select}
@@ -432,10 +432,10 @@ SPDX-License-Identifier: MPL-2.0
</div>
<div
class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
on:click={() => {
onclick={() => {
selected_play_thing = SelectedPlayThing.Results;
}}
on:keyup={() => {
onkeyup={() => {
selected_play_thing = SelectedPlayThing.Results;
}}
class:shadow-2xl={selected_play_thing === SelectedPlayThing.Results}
@@ -465,10 +465,10 @@ SPDX-License-Identifier: MPL-2.0
</div>
<div
class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
on:click={() => {
onclick={() => {
selected_play_thing = SelectedPlayThing.Winners;
}}
on:keyup={() => {
onkeyup={() => {
selected_play_thing = SelectedPlayThing.Winners;
}}
class:shadow-2xl={selected_play_thing === SelectedPlayThing.Winners}
@@ -523,10 +523,10 @@ SPDX-License-Identifier: MPL-2.0
{#each classquiz_reasons as reason, index}
<div
class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
on:click={() => {
onclick={() => {
selected_classquiz_reason = index;
}}
on:keyup={() => {
onkeyup={() => {
selected_classquiz_reason = index;
}}
class:shadow-2xl={selected_classquiz_reason === index}
+8 -3
View File
@@ -5,9 +5,14 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { navbarVisible } from '$lib/stores';
import { navbarVisible} from '$lib/stores.svelte.ts';
interface Props {
children?: import('svelte').Snippet;
}
navbarVisible.set(true);
let { children }: Props = $props();
navbarVisible.visible= true;
</script>
<slot />
{@render children?.()}
@@ -9,7 +9,11 @@ SPDX-License-Identifier: MPL-2.0
import BrownButton from '$lib/components/buttons/brown.svelte';
import { getLocalization } from '$lib/i18n';
const { t } = getLocalization();
export let data: PageData;
interface Props {
data: PageData;
}
let { data }: Props = $props();
const controllers: [] = data.controllers;
</script>
@@ -33,45 +37,51 @@ SPDX-License-Identifier: MPL-2.0
</div>
</div>
<table class="w-full">
<tr class="border-b-2 dark:border-gray-500 text-left border-gray-300">
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('words.name')}</th
>
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('controllers.player_name')}
</th>
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('controllers.first_seen')}
</th>
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('controllers.last_seen')}
</th>
<th class="mx-auto p-1">{$t('words.version')}</th>
</tr>
{#each data.controllers as controller}
<tr class="text-left">
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
><a
href="/account/controllers/{controller.id}"
class="underline text-lg">{controller.name}</a
></td
<thead>
<tr class="border-b-2 dark:border-gray-500 text-left border-gray-300">
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('words.name')}</th
>
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{controller.player_name}</td
>
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{controller.first_seen
? new Date(controller.first_seen).toLocaleString()
: $t('words.never')}</td
>
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{controller.last_seen
? new Date(controller.last_seen).toLocaleString()
: $t('words.never')}</td
>
<td class="mx-auto p-1">{controller.os_version ?? $t('words.unknown')}</td>
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('controllers.player_name')}
</th>
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('controllers.first_seen')}
</th>
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('controllers.last_seen')}
</th>
<th class="mx-auto p-1">{$t('words.version')}</th>
</tr>
{/each}
</thead>
<tbody>
{#each data.controllers as controller}
<tr class="text-left">
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
><a
href="/account/controllers/{controller.id}"
class="underline text-lg">{controller.name}</a
></td
>
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{controller.player_name}</td
>
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{controller.first_seen
? new Date(controller.first_seen).toLocaleString()
: $t('words.never')}</td
>
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{controller.last_seen
? new Date(controller.last_seen).toLocaleString()
: $t('words.never')}</td
>
<td class="mx-auto p-1"
>{controller.os_version ?? $t('words.unknown')}</td
>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
@@ -15,12 +15,16 @@ SPDX-License-Identifier: MPL-2.0
import { getLocalization } from '$lib/i18n';
const { t } = getLocalization();
export let data: PageData;
const controller = data.controller;
interface Props {
data: PageData;
}
let newest_version: string | undefined = undefined;
let { data }: Props = $props();
const controller = $state(data.controller);
let saved_status = SaveStatus.Unchanged;
let newest_version: string | undefined = $state(undefined);
let saved_status = $state(SaveStatus.Unchanged);
let save_player_timer;
const save_player_name_debounce = () => {
@@ -95,7 +99,7 @@ SPDX-License-Identifier: MPL-2.0
<input
class="rounded-sm p-1 transition-all outline-hidden text-center dark:bg-gray-700"
bind:value={controller.name}
on:keyup={save_player_name_debounce}
onkeyup={save_player_name_debounce}
/>
<SaveIndicator status={saved_status} />
</div>
@@ -106,7 +110,7 @@ SPDX-License-Identifier: MPL-2.0
<input
class="rounded-sm p-1 transition-all outline-hidden text-center dark:bg-gray-700"
bind:value={controller.player_name}
on:keyup={save_player_name_debounce}
onkeyup={save_player_name_debounce}
/>
<SaveIndicator status={saved_status} />
</div>
@@ -6,7 +6,11 @@ SPDX-License-Identifier: MPL-2.0
<script lang="ts">
import { SaveStatus } from './commons';
export let status: SaveStatus;
interface Props {
status: SaveStatus;
}
let { status }: Props = $props();
</script>
<div class="h-6 w-6 grow-0 my-auto">
@@ -5,19 +5,27 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import { goto } from '$app/navigation';
import type { PageData } from './$types';
export let data: PageData;
let input_data = {
interface Props {
data: PageData;
}
let { data }: Props = $props();
let input_data = $state({
player_name: data.username,
name: ''
};
});
let isValid = false;
let isValid = $state(false);
let isSubmitting = false;
$: isValid = input_data.name.length !== 0 && input_data.player_name.length !== 0;
run(() => {
isValid = input_data.name.length !== 0 && input_data.player_name.length !== 0;
});
const submit = async () => {
if (!isValid) {
@@ -51,7 +59,7 @@ SPDX-License-Identifier: MPL-2.0
Add a controller
</h3>
<form on:submit|preventDefault={submit}>
<form onsubmit={preventDefault(submit)}>
<div class="w-full mt-4">
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
<div class="relative bg-inherit w-full">
@@ -10,9 +10,13 @@ SPDX-License-Identifier: MPL-2.0
import Spinner from '$lib/Spinner.svelte';
import { onMount } from 'svelte';
export let data: PageData;
let controller_seen = false;
let check_tick = 0;
interface Props {
data: PageData;
}
let { data }: Props = $props();
let controller_seen = $state(false);
let check_tick = $state(0);
let interval;
const check_if_controller_was_seen = async () => {
+15 -10
View File
@@ -5,7 +5,10 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { alertModal, navbarVisible } from '$lib/stores';
import { run } from 'svelte/legacy';
import { alertModal } from '$lib/stores';
import { navbarVisible } from '$lib/stores.svelte';
import { slide } from 'svelte/transition';
import Footer from '$lib/footer.svelte';
import VerifiedBadge from './verified_badge.svelte';
@@ -17,15 +20,15 @@ SPDX-License-Identifier: MPL-2.0
import TotpComponent from './totp_component.svelte';
import { browserSupportsWebAuthn } from '@simplewebauthn/browser';
navbarVisible.set(true);
navbarVisible.visible = true;
export let data;
let { data } = $props();
const { verified }: boolean = data;
let session_data = {};
let step = 0;
let selected_method = null;
let done = false;
let session_data = $state({});
let step = $state(0);
let selected_method = $state(null);
let done = $state(false);
const redirect_back = (done_var: boolean) => {
if (done_var) {
@@ -35,7 +38,9 @@ SPDX-License-Identifier: MPL-2.0
}
};
let alertModalOpen = false;
$: redirect_back(done);
run(() => {
redirect_back(done);
});
alertModal.subscribe((data) => {
if (!alertModalOpen && data.open) {
@@ -74,10 +79,10 @@ SPDX-License-Identifier: MPL-2.0
}
}
};
$: {
run(() => {
check_auto();
step;
}
});
</script>
<svelte:head>
@@ -5,19 +5,25 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n';
const { t } = getLocalization();
export let session_data;
export let selected_method;
export let done;
export let step;
let {
session_data,
selected_method = $bindable(),
done = $bindable(),
step = $bindable()
} = $props();
let backup_code = '';
let isSubmitting = false;
let backup_code = $state('');
let isSubmitting = $state(false);
let backup_code_valid = false;
$: backup_code_valid = backup_code.length === 64;
let backup_code_valid = $state(false);
run(() => {
backup_code_valid = backup_code.length === 64;
});
const continue_in_login = async () => {
if (!backup_code_valid) {
@@ -44,7 +50,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="px-6 py-4">
<h2 class="text-3xl font-bold text-center text-gray-700 dark:text-white">ClassQuiz</h2>
<form on:submit|preventDefault={continue_in_login}>
<form onsubmit={preventDefault(continue_in_login)}>
<div class="w-full mt-4">
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
<div class="relative bg-inherit w-full">
@@ -66,7 +72,7 @@ SPDX-License-Identifier: MPL-2.0
</div>
<div class="flex items-center justify-between mt-4">
<button
on:click={() => {
onclick={() => {
selected_method = 'BACKUP';
}}
class="text-sm text-gray-600 dark:text-gray-200 hover:text-gray-500"
@@ -5,16 +5,20 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n';
export let session_data;
export let selected_method;
export let done;
export let step;
let {
session_data,
selected_method = $bindable(),
done = $bindable(),
step = $bindable()
} = $props();
const { t } = getLocalization();
let isSubmitting;
let password;
let password = $state();
const continue_in_login = async () => {
if (!password) {
@@ -56,7 +60,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="px-6 py-4">
<h2 class="text-3xl font-bold text-center text-gray-700 dark:text-white">ClassQuiz</h2>
<form on:submit|preventDefault={continue_in_login}>
<form onsubmit={preventDefault(continue_in_login)}>
<div class="w-full mt-4">
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
<div class="relative bg-inherit w-full">
@@ -79,7 +83,7 @@ SPDX-License-Identifier: MPL-2.0
</div>
<div class="flex items-center justify-between mt-4">
<button
on:click={() => {
onclick={() => {
selected_method = 'BACKUP';
}}
class="text-sm text-gray-600 dark:text-gray-200 hover:text-gray-500"
@@ -4,11 +4,11 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
export let session_data = {};
export let step;
export let selected_method;
import { run } from 'svelte/legacy';
let available_methods;
let { session_data = {}, step, selected_method = $bindable() } = $props();
let available_methods = $state();
const set_available_methods = (step_var: number) => {
if (step_var === 1) {
@@ -18,7 +18,9 @@ SPDX-License-Identifier: MPL-2.0
}
};
$: set_available_methods(step);
run(() => {
set_available_methods(step);
});
</script>
<div class="px-6 py-4">
@@ -30,10 +32,10 @@ SPDX-License-Identifier: MPL-2.0
{#if available_methods.includes('PASSKEY')}
<div
class="flex flex-row bg-gray-100 dark:bg-gray-700 rounded-lg p-2 hover:cursor-pointer hover:bg-gray-200 transition"
on:click={() => {
onclick={() => {
selected_method = 'PASSKEY';
}}
on:keyup={() => {
onkeyup={() => {
selected_method = 'PASSKEY';
}}
>
@@ -61,10 +63,10 @@ SPDX-License-Identifier: MPL-2.0
{#if available_methods.includes('PASSWORD')}
<div
class="flex flex-row bg-gray-100 dark:bg-gray-700 rounded-lg p-2 hover:cursor-pointer hover:bg-gray-200 transition"
on:click={() => {
onclick={() => {
selected_method = 'PASSWORD';
}}
on:keyup={() => {
onkeyup={() => {
selected_method = 'PASSWORD';
}}
>
@@ -107,10 +109,10 @@ SPDX-License-Identifier: MPL-2.0
{#if available_methods.includes('TOTP')}
<div
class="flex flex-row bg-gray-100 rounded-lg p-2 hover:cursor-pointer hover:bg-gray-200 transition"
on:click={() => {
onclick={() => {
selected_method = 'TOTP';
}}
on:keyup={() => {
onkeyup={() => {
selected_method = 'TOTP';
}}
>
@@ -5,18 +5,21 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n';
import OAuthBlock from './oauth_block.svelte';
export let session_data = {};
export let step;
let { session_data = $bindable({}), step = $bindable() } = $props();
const { t } = getLocalization();
let email = '';
let emailEmpty = true;
let isSubmitting = false;
let email = $state('');
let emailEmpty = $state(true);
let isSubmitting = $state(false);
$: emailEmpty = email === '';
run(() => {
emailEmpty = email === '';
});
const start_login = async (): Promise<void> => {
if (emailEmpty) {
@@ -47,7 +50,7 @@ SPDX-License-Identifier: MPL-2.0
{$t('login_page.login_or_create_account')}
</p>
<form on:submit|preventDefault={start_login}>
<form onsubmit={preventDefault(start_login)}>
<div class="w-full mt-4">
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
<div class="relative bg-inherit w-full">
@@ -5,21 +5,35 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { getLocalization } from '$lib/i18n';
// import { alertModal } from '$lib/stores';
import { run, preventDefault } from 'svelte/legacy';
export let session_data;
export let selected_method;
export let done;
export let step;
import { getLocalization } from '$lib/i18n';
interface Props {
// import { alertModal } from '$lib/stores';
session_data: any;
selected_method: any;
done: any;
step: any;
}
let {
session_data,
selected_method = $bindable(),
done = $bindable(),
step = $bindable()
}: Props = $props();
const { t } = getLocalization();
let isSubmitting;
let totp = '';
let totp = $state('');
let totp_valid = false;
let totp_valid = $state(false);
$: totp_valid = totp.length === 6;
run(() => {
totp_valid = totp.length === 6;
});
const continue_in_login = async () => {
if (!totp_valid) {
@@ -70,7 +84,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="px-6 py-4">
<h2 class="text-3xl font-bold text-center text-gray-700 dark:text-white">ClassQuiz</h2>
<form on:submit|preventDefault={continue_in_login}>
<form onsubmit={preventDefault(continue_in_login)}>
<div class="w-full mt-4">
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
<div class="relative bg-inherit w-full">
@@ -93,7 +107,7 @@ SPDX-License-Identifier: MPL-2.0
</div>
<div class="flex items-center justify-between mt-4">
<button
on:click={() => {
onclick={() => {
selected_method = 'BACKUP';
}}
class="text-sm text-gray-600 dark:text-gray-200 hover:text-gray-500"
@@ -10,12 +10,14 @@ SPDX-License-Identifier: MPL-2.0
// import { alertModal } from '$lib/stores';
const { t } = getLocalization();
export let session_data;
export let selected_method;
export let done;
export let step;
let {
session_data,
selected_method = $bindable(),
done = $bindable(),
step = $bindable()
} = $props();
let isLoading = false;
let isLoading = $state(false);
const start_thing = async () => {
const data = JSON.parse(session_data.webauthn_data);
@@ -84,7 +86,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="w-full mt-4">
<div class="flex items-center justify-between mt-4">
<button
on:click={() => {
onclick={() => {
selected_method = 'BACKUP';
}}
class="text-sm text-gray-600 dark:text-gray-200 hover:text-gray-500"
@@ -93,7 +95,7 @@ SPDX-License-Identifier: MPL-2.0
<button
class="px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded-sm hover:bg-gray-600 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50"
disabled={isLoading}
on:click={start_thing}
onclick={start_thing}
>
{#if isLoading}
<svg class="h-4 w-4 animate-spin mx-auto" viewBox="3 3 18 18">
@@ -5,7 +5,7 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
export let data;
let { data } = $props();
const { error } = data;
</script>
@@ -5,25 +5,29 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n';
const { t } = getLocalization();
export let data;
let { data } = $props();
let { token }: string = data;
let isSubmitting = false;
let isSubmitting = $state(false);
interface PasswordData {
password1: string;
password2: string;
}
let passwordData: PasswordData = {
let passwordData: PasswordData = $state({
password1: '',
password2: ''
};
let passwordsValid = false;
});
let passwordsValid = $state(false);
const checkIfPasswordsValid = (pwdata: PasswordData): void => {
passwordsValid = pwdata.password1 === pwdata.password2 && pwdata.password1.length >= 8;
};
$: checkIfPasswordsValid(passwordData);
run(() => {
checkIfPasswordsValid(passwordData);
});
const submit = async () => {
if (!passwordsValid) {
@@ -73,7 +77,7 @@ SPDX-License-Identifier: MPL-2.0
{$t('password_reset_page.reset_password')}
</p>
<form on:submit|preventDefault={submit}>
<form onsubmit={preventDefault(submit)}>
<div class="w-full mt-4">
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
<div class="relative bg-inherit w-full">
@@ -8,13 +8,13 @@ SPDX-License-Identifier: MPL-2.0
import { createForm } from 'felte';
import { getLocalization } from '$lib/i18n';
import { validateSchema } from '@felte/validator-yup';
import { navbarVisible } from '$lib/stores';
import { navbarVisible } from '$lib/stores.svelte.ts';
import Footer from '$lib/footer.svelte';
const { t } = getLocalization();
import reporter from '@felte/reporter-tippy';
navbarVisible.set(true);
navbarVisible.visible = true;
import * as yup from 'yup';
const registerSchema = yup.object({
@@ -71,10 +71,10 @@ SPDX-License-Identifier: MPL-2.0
responseData.open = true;
}
});
let responseData = {
let responseData = $state({
open: false,
data: ''
};
});
</script>
<svelte:head>
@@ -270,7 +270,7 @@ SPDX-License-Identifier: MPL-2.0
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 bg-gray-500/75 transition-opacity" aria-hidden="true" />
<div class="fixed inset-0 bg-gray-500/75 transition-opacity" aria-hidden="true"></div>
<!-- This element is to trick the browser into centering the modal contents. -->
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true"
@@ -355,7 +355,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
type="button"
on:click={() => {
onclick={() => {
responseData.open = false;
window.location.assign('/');
}}
@@ -5,14 +5,16 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { getLocalization } from '$lib/i18n';
import { navbarVisible } from '$lib/stores';
import { preventDefault } from 'svelte/legacy';
navbarVisible.set(true);
import { getLocalization } from '$lib/i18n';
import { navbarVisible } from '$lib/stores.svelte.ts';
navbarVisible.visible = true;
const { t } = getLocalization();
let isSubmitting = false;
let isSubmitting = $state(false);
const submit = async () => {
isSubmitting = true;
@@ -34,7 +36,7 @@ SPDX-License-Identifier: MPL-2.0
isSubmitting = false;
};
let email = '';
let email = $state('');
</script>
<svelte:head>
@@ -60,7 +62,7 @@ SPDX-License-Identifier: MPL-2.0
{$t('password_reset_page.reset_password')}
</p>
<form on:submit|preventDefault={submit}>
<form onsubmit={preventDefault(submit)}>
<div class="w-full mt-4">
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
<div class="relative bg-inherit w-full">
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n';
import { DateTime } from 'luxon';
import { UAParser } from 'ua-parser-js';
@@ -28,16 +30,16 @@ SPDX-License-Identifier: MPL-2.0
newPasswordConfirm: string;
}
let changePasswordData: ChangePasswordData = {
let changePasswordData: ChangePasswordData = $state({
oldPassword: '',
newPassword: '',
newPasswordConfirm: ''
};
});
let locationData;
let this_session;
let this_session = $state();
let passwordChangeDataValid = false;
let passwordChangeDataValid = $state(false);
const checkPasswords = (data: ChangePasswordData): void => {
passwordChangeDataValid =
data.newPassword === data.newPasswordConfirm &&
@@ -45,7 +47,9 @@ SPDX-License-Identifier: MPL-2.0
data.oldPassword !== data.newPassword &&
data.oldPassword !== '';
};
$: checkPasswords(changePasswordData);
run(() => {
checkPasswords(changePasswordData);
});
const changePassword = async () => {
if (!passwordChangeDataValid) {
return;
@@ -93,7 +97,7 @@ SPDX-License-Identifier: MPL-2.0
return api_keys_temp;
};
let api_keys;
let api_keys = $state();
const add_api_key = async () => {
await fetch('/api/v1/users/api_keys', { method: 'POST' });
@@ -180,7 +184,7 @@ SPDX-License-Identifier: MPL-2.0
</div>
</div>
<div>
<form class="flex flex-col md:flex-row" on:submit|preventDefault={changePassword}>
<form class="flex flex-col md:flex-row" onsubmit={preventDefault(changePassword)}>
<label
>{$t('settings_page.old_password')}:<input
type="password"
@@ -296,7 +300,7 @@ SPDX-License-Identifier: MPL-2.0
class="py-4 px-6 text-sm text-gray-500 whitespace-nowrap dark:text-gray-400"
>
<button
on:click={() => {
onclick={() => {
deleteSession(session.id);
}}>{$t('words.delete')}</button
>
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { run } from 'svelte/legacy';
import BrownButton from '$lib/components/buttons/brown.svelte';
import { fade, fly } from 'svelte/transition';
import { bounceOut } from 'svelte/easing';
@@ -43,7 +45,7 @@ SPDX-License-Identifier: MPL-2.0
clothe_graphic_type: $t('avatar_settings.clothe_graphic_type')
};
let data = {
let data = $state({
skin_color: 0,
top_type: 0,
hair_color: 0,
@@ -56,21 +58,22 @@ SPDX-License-Identifier: MPL-2.0
clothe_type: 0,
clothe_color: 0,
clothe_graphic_type: 0
};
});
const data_keys = Object.keys(data);
let index = 0;
let index = $state(0);
// let index = 10;
let save_finished: undefined | boolean = undefined;
let finished = false;
let image_url;
$: console.log('index', index);
let save_finished: undefined | boolean = $state(undefined);
let finished = $state(false);
const get_image_url = (input_data) => {
return `/api/v1/avatar/custom?${new URLSearchParams(input_data).toString()}`;
};
$: image_url = get_image_url(data);
let image_url = $derived(get_image_url(data));
run(() => {
console.log('index', index);
});
const save_avatar = async () => {
save_finished = false;
@@ -111,7 +114,7 @@ SPDX-License-Identifier: MPL-2.0
{#each Array.from(Array(item_count[data_keys[index]]).keys()) as key}
<button
class="hover:opacity-80 transition-all"
on:click={() => {
onclick={() => {
data[data_keys[index]] = key;
if (index < 11) {
index++;
@@ -15,11 +15,11 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization();
let user_data: object | undefined;
let security_keys: Array<{ id: number }> | undefined;
let totp_activated: boolean | undefined;
let totp_data;
let backup_code;
let user_data: object | undefined = $state();
let security_keys: Array<{ id: number }> | undefined = $state();
let totp_activated: boolean | undefined = $state();
let totp_data = $state();
let backup_code = $state();
const get_data = async () => {
const res1 = await fetch('/api/v1/users/me');
@@ -29,7 +29,7 @@ SPDX-License-Identifier: MPL-2.0
const res3 = await fetch('/api/v1/users/2fa/totp');
totp_activated = (await res3.json()).activated;
};
let data = get_data();
let data = $state(get_data());
const save_password_required = async () => {
console.log(user_data?.require_password, 'here');
@@ -47,12 +47,13 @@ SPDX-License-Identifier: MPL-2.0
user_data.require_password = (await res.json()).require_password;
};
const require_password = (): string => {
const require_password = (): string | null => {
return prompt('Please enter your password to continue');
};
const add_security_key = async () => {
const pw = require_password();
if (!pw) return;
const res1 = await fetch('/api/v1/users/webauthn/add_key_init', {
method: 'POST',
body: JSON.stringify({ password: pw }),
@@ -89,6 +90,7 @@ SPDX-License-Identifier: MPL-2.0
const remove_security_key = async (key_id: number) => {
const pw = require_password();
if (!pw) return;
const res = await fetch(`/api/v1/users/webauthn/key/${key_id}`, {
method: 'DELETE',
body: JSON.stringify({ password: pw }),
@@ -103,6 +105,7 @@ SPDX-License-Identifier: MPL-2.0
const disable_totp = async () => {
const pw = require_password();
if (!pw) return;
const res = await fetch(`/api/v1/users/2fa/totp`, {
method: 'DELETE',
body: JSON.stringify({ password: pw }),
@@ -117,6 +120,7 @@ SPDX-License-Identifier: MPL-2.0
const enable_totp = async () => {
const pw = require_password();
if (!pw) return;
const res = await fetch('/api/v1/users/2fa/totp', {
method: 'POST',
body: JSON.stringify({ password: pw }),
@@ -132,6 +136,7 @@ SPDX-License-Identifier: MPL-2.0
const get_backup_code = async () => {
const pw = require_password();
if (!pw) return;
if (!confirm('If you continue, your old backup-code will be removed.')) {
return;
}
@@ -176,7 +181,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex items-center space-x-2">
<button
disabled={!totp_activated}
on:click={() => {
onclick={() => {
user_data.require_password = !user_data.require_password;
save_password_required();
}}
@@ -188,7 +193,7 @@ SPDX-License-Identifier: MPL-2.0
<span
aria-hidden="true"
class="pointer-events-none inline-block h-4 w-4 translate-x-3 rounded-full bg-white transition will-change-transform"
/>
></span>
</button>
<span class="text-sm font-medium text-gray-700 dark:text-white"
>{$t('security_settings.2fa_activated')}</span
@@ -199,7 +204,7 @@ SPDX-License-Identifier: MPL-2.0
<button
disabled={!totp_activated}
type="button"
on:click={() => {
onclick={() => {
user_data.require_password = !user_data.require_password;
save_password_required();
}}
@@ -210,7 +215,7 @@ SPDX-License-Identifier: MPL-2.0
<span
aria-hidden="true"
class="pointer-events-none inline-block h-4 w-4 translate-x-0 rounded-full bg-white transition will-change-transform"
/>
></span>
</button>
<span class="text-sm font-medium text-gray-700 dark:text-white"
>{$t('security_settings.2fa_deactivated')}</span
@@ -243,7 +248,7 @@ SPDX-License-Identifier: MPL-2.0
{#each security_keys as key, i}
<li>
<button
on:click={() => {
onclick={() => {
remove_security_key(key.id);
}}
class="hover:line-through transition">{i + 1}</button
@@ -8,7 +8,7 @@ SPDX-License-Identifier: MPL-2.0
import { getLocalization } from '$lib/i18n';
const { t } = getLocalization();
export let backup_code;
let { backup_code = $bindable() } = $props();
let already_downloaded = false;
@@ -31,7 +31,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="w-full h-full">
<button
class="bg-gray-200 dark:bg-gray-900 px-2 py-1 rounded-t-lg hover:bg-gray-300 transition"
on:click={() => {
onclick={() => {
backup_code = undefined;
}}
>{$t('words.close')}
@@ -42,7 +42,7 @@ SPDX-License-Identifier: MPL-2.0
<h2 class="text-3xl m-auto">{$t('security_settings.backup_codes.your_backup_code')}</h2>
<p
class="select-all font-mono text-xl m-auto"
on:click={() => {
onclick={() => {
download_code(false);
}}
>
@@ -50,7 +50,7 @@ SPDX-License-Identifier: MPL-2.0
</p>
<p class="m-auto">{$t('security_settings.backup_codes.save_somewhere_save')}</p>
<button
on:click={() => {
onclick={() => {
download_code(true);
}}
class="m-auto p-2 bg-[#B07156] rounded-lg"
@@ -11,7 +11,11 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization();
export let totp_data: { url: string; secret: string } | undefined;
interface Props {
totp_data: { url: string; secret: string } | undefined;
}
let { totp_data = $bindable() }: Props = $props();
const get_image_url = async () => {
return await QRCode.toDataURL(totp_data.url);
@@ -22,7 +26,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="w-full h-full">
<button
class="bg-gray-200 dark:bg-gray-900 px-2 py-1 rounded-t-lg hover:bg-gray-300 transition"
on:click={() => {
onclick={() => {
totp_data = undefined;
}}
>{$t('words.close')}
@@ -30,7 +34,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="bg-white dark:bg-gray-700 rounded-b-lg rounded-tr-lg w-full h-full">
<div class="grid grid-cols-3 w-full h-full">
<div class="flex flex-col justify-center w-full h-5/6">
<span class="m-auto" />
<span class="m-auto"></span>
<div class="h-5/6 flex">
<p class="my-auto ml-auto">
{$t('security_settings.totp_setup.scan_to_set_up')}
+38 -30
View File
@@ -5,11 +5,13 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import type { QuizData } from '$lib/quiz_types';
import { socket } from '$lib/socket';
import { getLocalization } from '$lib/i18n';
import { navbarVisible } from '$lib/stores';
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';
@@ -17,34 +19,39 @@ SPDX-License-Identifier: MPL-2.0
import { onMount } from 'svelte';
import FinalResults from '$lib/play/admin/final_results.svelte';
import GrayButton from '$lib/components/buttons/gray.svelte';
import { page } from '$app/stores';
import { page } from '$app/state';
navbarVisible.set(false);
navbarVisible.visible = false;
const { t } = getLocalization();
// let gameData = {
// game_id: 'a7ddb6af-79ab-45e0-b996-6254c1ad9818',
// game_pin: '66190765'
// };
export let data;
let game_mode;
let { auto_connect, game_token } = data;
interface Props {
// };
data: any;
}
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> = [];
let player_scores = {};
let errorMessage = '';
let game_started = false;
let quiz_data: QuizData;
let control_visible = true;
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>> = [null];
let success = false;
let dataexport_download_a;
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 = undefined;
let export_token = $state(undefined);
const connect = async () => {
socket.emit('register_as_admin', {
@@ -60,9 +67,9 @@ SPDX-License-Identifier: MPL-2.0
connect();
}
});
socket.on("session_id", (d) => {
const session_id = d.session_id
})
socket.on('session_id', (d) => {
const session_id = d.session_id;
});
socket.on('registered_as_admin', (data) => {
quiz_data = JSON.parse(data['game']);
@@ -132,16 +139,17 @@ SPDX-License-Identifier: MPL-2.0
window.matchMedia('(prefers-color-scheme: dark)').matches);
}
let bg_color;
let bg_image;
let results_saved = false;
$: bg_color = quiz_data ? quiz_data.background_color : undefined;
$: bg_image = quiz_data ? quiz_data.background_image : undefined;
let show_final_results = false;
$: show_final_results = JSON.stringify(final_results) !== JSON.stringify([null]);
let bg_color = $derived(quiz_data ? quiz_data.background_color : undefined);
let bg_image = $derived(quiz_data ? quiz_data.background_image : undefined);
let results_saved = $state(false);
let show_final_results = $state(false);
run(() => {
show_final_results = JSON.stringify(final_results) !== JSON.stringify([null]);
});
</script>
<svelte:window on:beforeunload={confirmUnload} />
<svelte:window onbeforeunload={confirmUnload} />
<svelte:head>
<title>ClassQuiz - Host</title>
</svelte:head>
@@ -204,7 +212,7 @@ SPDX-License-Identifier: MPL-2.0
{game_pin}
bind:players
{socket}
cqc_code={$page.url.searchParams.get('cqc_code')}
cqc_code={page.url.searchParams.get('cqc_code')}
/>
{:else}
<SomeAdminScreen
@@ -220,7 +228,7 @@ SPDX-License-Identifier: MPL-2.0
{/if}
</div>
<a
on:click|preventDefault={request_answer_export}
onclick={preventDefault(request_answer_export)}
href="#"
target="_blank"
bind:this={dataexport_download_a}
+8 -8
View File
@@ -8,11 +8,11 @@ SPDX-License-Identifier: MPL-2.0
import { onMount } from 'svelte';
import Editor from '$lib/editor.svelte';
import { getLocalization } from '$lib/i18n';
import { navbarVisible } from '$lib/stores';
import { navbarVisible } from '$lib/stores.svelte.ts';
import type { Question } from '$lib/quiz_types';
import { page } from '$app/stores';
import { page } from '$app/state';
navbarVisible.set(false);
navbarVisible.visible = false;
const { t } = getLocalization();
@@ -27,12 +27,12 @@ SPDX-License-Identifier: MPL-2.0
open: false
};
let data: Data;
let quiz_id = null;
let data: Data = $state();
let quiz_id = $state(null);
onMount(() => {
const from_localstorage = localStorage.getItem('create_game');
if (from_localstorage === null) {
let title = $page.url.searchParams.get('title');
let title = page.url.searchParams.get('title');
title ??= '';
data = {
description: '',
@@ -71,7 +71,7 @@ SPDX-License-Identifier: MPL-2.0
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 bg-gray-500/75 transition-opacity" aria-hidden="true" />
<div class="fixed inset-0 bg-gray-500/75 transition-opacity" aria-hidden="true"></div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true"
>&#8203;</span
@@ -113,7 +113,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
type="button"
on:click={() => {
onclick={() => {
window.location.href = '/dashboard';
}}
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-xm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-hidden focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
+54 -30
View File
@@ -5,11 +5,14 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { run } from 'svelte/legacy';
import DownloadQuiz from '$lib/components/DownloadQuiz.svelte';
import type { QuizData } from '$lib/quiz_types';
import { getLocalization } from '$lib/i18n';
import Footer from '$lib/footer.svelte';
import { navbarVisible, signedIn } from '$lib/stores';
import { signedIn } from '$lib/stores';
import { navbarVisible } from '$lib/stores.svelte';
import CommandpaletteNotice from '$lib/components/popover/commandpalettenotice.svelte';
// import Spinner from "$lib/Spinner.svelte";
import Fuse from 'fuse.js';
@@ -20,19 +23,23 @@ SPDX-License-Identifier: MPL-2.0
import Analytics from './Analytics.svelte';
import MediaComponent from '$lib/editor/MediaComponent.svelte';
import { createTippy } from 'svelte-tippy';
import { onMount } from 'svelte';
// import GrayButton from "$lib/components/buttons/gray.svelte";
interface Props {
// import GrayButton from "$lib/components/buttons/gray.svelte";
data: PageData;
}
export let data: PageData;
let search_term = '';
let start_game = null;
let download_id: string | null = null;
let { data }: Props = $props();
let search_term = $state('');
let start_game = $state(null);
let download_id: string | null = $state(null);
signedIn.set(true);
navbarVisible.set(true);
navbarVisible.visible = true;
const { t } = getLocalization();
let items_to_show = [];
let all_items: Array<any>;
let items_to_show = $state([]);
let all_items: Array<any> = $state();
let fuse;
const tippy = createTippy({
arrow: true,
@@ -42,8 +49,8 @@ SPDX-License-Identifier: MPL-2.0
let id_to_position_map = {};
const getData = async (): Promise<Array<QuizData>> => {
items_to_show = [];
const getData = async (): Promise<{ items: Array<QuizData>; fuse: Fuse<any> }> => {
/* items_to_show = [];
for (let i = 0; i < data.quizzes.length; i++) {
items_to_show.push({ ...data.quizzes[i], type: 'quiz' });
}
@@ -59,25 +66,44 @@ SPDX-License-Identifier: MPL-2.0
id_to_position_map[all_items[i].id] = i;
}
return all_items;
*/
const items: any[] = [];
for (const q of data.quizzes) items.push({ ...q, type: 'quiz' });
for (const q of data.quiztivities) items.push({ ...q, type: 'quiztivity' });
const f = new Fuse(items, {
keys: ['title', 'description', 'questions.title'],
findAllMatches: true
});
// return *all* derived data instead of mutating directly
return { items, fuse: f };
};
const search = () => {
if (search_term === '') {
items_to_show = all_items;
} else {
const res = fuse.search(search_term);
items_to_show = [];
for (const quiz_data of res) {
items_to_show.push(quiz_data.item);
}
items_to_show = items_to_show;
return;
}
const res = fuse.search(search_term);
items_to_show = res.map((r) => r.item);
};
$: {
onMount(async () => {
const { items, fuse: f } = await getData();
all_items = items;
items_to_show = items;
fuse = f;
// fill id_to_position_map
id_to_position_map = {};
for (let i = 0; i < items.length; i++) {
id_to_position_map[items[i].id] = i;
}
search_term;
search();
}
});
const deleteQuiz = async (to_delete: string, type: 'quiz' | 'quiztivity') => {
if (!confirm('Do you really want to delete this quiz?')) {
@@ -94,9 +120,9 @@ SPDX-License-Identifier: MPL-2.0
}
window.location.reload();
};
let create_button_clicked = false;
let create_button_clicked = $state(false);
let analytics_quiz_selected: undefined | QuizData = undefined;
let analytics_quiz_selected: undefined | QuizData = $state(undefined);
</script>
<svelte:head>
@@ -105,7 +131,7 @@ SPDX-License-Identifier: MPL-2.0
<Analytics bind:quiz={analytics_quiz_selected} />
<CommandpaletteNotice />
<div class="min-h-screen flex flex-col">
{#await getData()}
{#if !all_items}
<svg class="h-8 w-8 animate-spin mx-auto my-20" viewBox="3 3 18 18">
<path
class="fill-black"
@@ -116,7 +142,7 @@ SPDX-License-Identifier: MPL-2.0
d="M16.9497 7.05015C14.2161 4.31648 9.78392 4.31648 7.05025 7.05015C6.65973 7.44067 6.02656 7.44067 5.63604 7.05015C5.24551 6.65962 5.24551 6.02646 5.63604 5.63593C9.15076 2.12121 14.8492 2.12121 18.364 5.63593C18.7545 6.02646 18.7545 6.65962 18.364 7.05015C17.9734 7.44067 17.3403 7.44067 16.9497 7.05015Z"
/>
</svg>
{:then quizzes}
{:else}
<div class="flex flex-col w-full mx-auto">
<!-- <button
class='px-4 py-2 font-medium tracking-wide text-gray-500 whitespace-nowrap dark:text-gray-400 capitalize transition-colors dark:bg-gray-700 duration-200 transform bg-[#B07156] rounded-md hover:bg-green-600 focus:outline-hidden focus:ring focus:ring-blue-300 focus:ring-opacity-80'>
@@ -149,7 +175,7 @@ SPDX-License-Identifier: MPL-2.0
</BrownButton>
</div>
</div>
{#if quizzes.length !== 0}
{#if all_items.length !== 0}
<div class="flex justify-center pt-4 w-full">
<div>
<div>
@@ -159,7 +185,7 @@ SPDX-License-Identifier: MPL-2.0
placeholder={$t('dashboard.search_for_own_quizzes')}
/>
<button
on:click={() => {
onclick={() => {
search_term = '';
items_to_show = all_items;
}}
@@ -390,9 +416,7 @@ SPDX-License-Identifier: MPL-2.0
</p>
{/if}
</div>
{:catch err}
<p>{err}</p>
{/await}
{/if}
</div>
<Footer />
{#if start_game !== null}
+30 -24
View File
@@ -43,35 +43,41 @@ SPDX-License-Identifier: MPL-2.0
<section class="flex flex-col gap-2 mt-8">
<h2 class="mx-auto text-2xl">{$t('words.rating')}</h2>
<table class="w-fit mx-auto">
<tr class="border-b-2 dark:border-gray-500 text-left border-gray-300">
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('words.like', { count: 2 })}</th
>
<th class="p-1 mx-auto">{$t('words.dislike', { count: 2 })}</th>
</tr>
<tr class="text-left">
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{quiz.likes}</td
>
<td class="mx-auto p-1">{quiz.dislikes}</td>
</tr>
<tbody>
<tr class="border-b-2 dark:border-gray-500 text-left border-gray-300">
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('words.like', { count: 2 })}</th
>
<th class="p-1 mx-auto">{$t('words.dislike', { count: 2 })}</th>
</tr>
<tr class="text-left">
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{quiz.likes}</td
>
<td class="mx-auto p-1">{quiz.dislikes}</td>
</tr>
</tbody>
</table>
</section>
<section class="flex flex-col gap-2 mt-8">
<h2 class="mx-auto text-2xl">{$t('dashboard.views_n_plays')}</h2>
<table class="w-fit mx-auto">
<tr class="border-b-2 dark:border-gray-500 text-left border-gray-300">
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('words.view', { count: 2 })}</th
>
<th class="p-1 mx-auto">{$t('words.play', { count: 2 })}</th>
</tr>
<tr class="text-left">
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{quiz.views}</td
>
<td class="mx-auto p-1">{quiz.plays}</td>
</tr>
<thead>
<tr class="border-b-2 dark:border-gray-500 text-left border-gray-300">
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('words.view', { count: 2 })}</th
>
<th class="p-1 mx-auto">{$t('words.play', { count: 2 })}</th>
</tr>
</thead>
<tbody>
<tr class="text-left">
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{quiz.views}</td
>
<td class="mx-auto p-1">{quiz.plays}</td>
</tr>
</tbody>
</table>
</section>
<section class="flex flex-col gap-2 mt-8">
@@ -7,9 +7,14 @@ SPDX-License-Identifier: MPL-2.0
<script lang="ts">
import type { PageData } from './$types';
// import FileDahboard from "$lib/files/dashboard.svelte"
// import { thumbHashToDataURL } from 'thumbhash';
export let data: PageData;
interface Props {
// import { thumbHashToDataURL } from 'thumbhash';
data: PageData;
}
let { data }: Props = $props();
const files = data.files;
/* const base64ToBytes = (value: string) => {
@@ -28,7 +33,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="w-full h-full">
<div class="grid grid-cols-2 w-full h-full gap-4">
{#each files as file, i}
<div class="w-full h-full flex" />
<div class="w-full h-full flex"></div>
{/each}
</div>
</div>
@@ -7,6 +7,11 @@ SPDX-License-Identifier: MPL-2.0
<script lang="ts">
import { onMount } from 'svelte';
import '$lib/hljs.css';
interface Props {
children?: import('svelte').Snippet;
}
let { children }: Props = $props();
onMount(async () => {
const { default: hljs } = await import('highlight.js/lib/common');
@@ -17,5 +22,5 @@ SPDX-License-Identifier: MPL-2.0
<article
class="prose prose-sm sm:prose lg:prose-lg xl:prose-xl mx-auto mt-10 prose-slate px-4 dark:prose-invert"
>
<slot />
{@render children?.()}
</article>
+8 -3
View File
@@ -6,13 +6,18 @@ SPDX-License-Identifier: MPL-2.0
<script lang="ts">
import Footer from '$lib/footer.svelte';
import { navbarVisible } from '$lib/stores';
import { navbarVisible } from '$lib/stores.svelte.ts';
interface Props {
children?: import('svelte').Snippet;
}
navbarVisible.set(true);
let { children }: Props = $props();
navbarVisible.visible = true;
</script>
<div class="min-h-screen flex flex-col">
<slot />
{@render children?.()}
</div>
<div class="pt-4">
<Footer />
+7 -7
View File
@@ -7,10 +7,10 @@ SPDX-License-Identifier: MPL-2.0
<script lang="ts">
import Editor from '$lib/editor.svelte';
import { getLocalization } from '$lib/i18n';
import { navbarVisible } from '$lib/stores';
import { navbarVisible} from '$lib/stores.svelte.ts';
import { QuizQuestionType } from '$lib/quiz_types';
navbarVisible.set(false);
navbarVisible.visible= false;
const { t } = getLocalization();
@@ -37,9 +37,9 @@ SPDX-License-Identifier: MPL-2.0
answer: string;
}
export let data;
let { quiz_id } = data;
let quiz_data: Data;
let { data } = $props();
let { quiz_id } = $state(data);
let quiz_data: Data = $state();
const get_quiz = async (): Promise<void> => {
const response = await fetch(`/api/v1/quiz/get/${quiz_id}`);
@@ -95,7 +95,7 @@ SPDX-License-Identifier: MPL-2.0
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 bg-gray-500/75 transition-opacity" aria-hidden="true" />
<div class="fixed inset-0 bg-gray-500/75 transition-opacity" aria-hidden="true"></div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true"
>&#8203;</span
@@ -139,7 +139,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
type="button"
on:click={() => {
onclick={() => {
window.location.href = '/dashboard';
}}
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-xm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-hidden focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
+10 -4
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import type { PageData } from './$types';
import { fade } from 'svelte/transition';
// import MediaComponent from '$lib/editor/MediaComponent.svelte';
@@ -15,8 +17,12 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization();
export let data: PageData;
let edit_popup = null;
interface Props {
data: PageData;
}
let { data }: Props = $props();
let edit_popup = $state(null);
const images = data.images;
const close_popup_handler = (e: Event) => {
@@ -121,11 +127,11 @@ SPDX-License-Identifier: MPL-2.0
<div
transition:fade={{ duration: 100 }}
class="fixed top-0 left-0 h-screen w-screen z-40 flex bg-black/50"
on:click={close_popup_handler}
onclick={close_popup_handler}
>
<div class="w-auto h-auto m-auto rounded-sm bg-white dark:bg-gray-700 p-4">
<h1 class="text-2xl text-center">{$t('file_dashboard.edit_the_image')}</h1>
<form class="flex flex-col" on:submit|preventDefault={save_image_metadata}>
<form class="flex flex-col" onsubmit={preventDefault(save_image_metadata)}>
<div class="flex flex-row">
<div class="flex flex-col mr-4">
<label for="name" class="m-auto">{$t('file_dashboard.filename_word')}</label
@@ -5,25 +5,26 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { run } from 'svelte/legacy';
import Spinner from '$lib/Spinner.svelte';
let uppyOpen = false;
let edit_id = null;
let selected_question = undefined;
let data = { cover_image: undefined };
let uppyOpen = $state(false);
let edit_id = $state(null);
let selected_question = $state(undefined);
let data = $state({ cover_image: undefined });
$: {
run(() => {
if (data.cover_image) {
window.location.reload();
}
}
});
</script>
{#await import('$lib/editor/uploader.svelte')}
<Spinner my_20={false} />
{:then c}
<svelte:component
this={c.default}
<c.default
bind:modalOpen={uppyOpen}
bind:edit_id
bind:data
+49 -37
View File
@@ -8,25 +8,25 @@ SPDX-License-Identifier: MPL-2.0
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile } from '@ffmpeg/util';
import BrownButton from '$lib/components/buttons/brown.svelte';
import { navbarVisible } from '$lib/stores';
import { navbarVisible } from '$lib/stores.svelte.ts';
import { getLocalization } from '$lib/i18n';
const { t } = getLocalization();
let file_input: HTMLInputElement;
navbarVisible.set(false);
let file_input: HTMLInputElement = $state();
navbarVisible.visible= false;
let stats: { progress: number; time_elapsed: number; speed: number } = {
let stats: { progress: number; time_elapsed: number; speed: number } = $state({
progress: 0,
time_elapsed: 0,
speed: 0
};
});
let upload_stats: { progress: number; upload_started: undefined | Date; time_elapsed: number } =
{
$state({
progress: 0,
upload_started: undefined,
time_elapsed: 0
};
});
const speed_extraction_regex = /speed=(\d+\.\d+)x(?![\s\S]*speed=\d+\.\d+x)/;
let last_time_elapsed = 0;
@@ -45,9 +45,9 @@ SPDX-License-Identifier: MPL-2.0
Done
}
let status: Status = Status.Idle;
let status: Status = $state(Status.Idle);
let file_size_in_mi: undefined | number = undefined;
let file_size_in_mi: undefined | number = $state(undefined);
let original_file_size_in_mi: undefined | number = undefined;
let file_data: undefined | Blob = undefined;
@@ -150,36 +150,48 @@ SPDX-License-Identifier: MPL-2.0
<div class="mt-auto flex justify-center">
{#if status === Status.Compressing || status === Status.CompressDone}
<table>
<tr>
<th>{$t('words.speed')}</th>
<th>{$t('words.progress')}</th>
<th>{$t('video_uploader.time_elapsed')}</th>
<th>{$t('video_uploader.time_remaining')}</th>
</tr>
<tr>
<td>{stats.speed}</td>
<td>{Math.round(stats.progress * 100)}%</td>
<td>{upload_stats.time_elapsed.toFixed(2)}s</td>
<td
>{(stats.time_elapsed / stats.progress - upload_stats.time_elapsed).toFixed(
2
)}s</td
>
</tr>
<thead>
<tr>
<th>{$t('words.speed')}</th>
<th>{$t('words.progress')}</th>
<th>{$t('video_uploader.time_elapsed')}</th>
<th>{$t('video_uploader.time_remaining')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{stats.speed}</td>
<td>{Math.round(stats.progress * 100)}%</td>
<td>{upload_stats.time_elapsed.toFixed(2)}s</td>
<td
>{(
stats.time_elapsed / stats.progress -
upload_stats.time_elapsed
).toFixed(2)}s</td
>
</tr></tbody
>
</table>
{:else if status === Status.Uploading}
<table>
<tr>
<th>{$t('words.progress')}</th>
<th>{$t('video_uploader.time_elapsed')}</th>
<th>{$t('video_uploader.time_remaining')}</th>
</tr>
<tr>
<td>{Math.round(upload_stats.progress * 100)}%</td>
<td>{stats.time_elapsed.toFixed(2)}s</td>
<td>{(stats.time_elapsed / stats.progress - stats.time_elapsed).toFixed(2)}s</td
>
</tr>
<thead>
<tr>
<th>{$t('words.progress')}</th>
<th>{$t('video_uploader.time_elapsed')}</th>
<th>{$t('video_uploader.time_remaining')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{Math.round(upload_stats.progress * 100)}%</td>
<td>{stats.time_elapsed.toFixed(2)}s</td>
<td
>{(stats.time_elapsed / stats.progress - stats.time_elapsed).toFixed(
2
)}s</td
>
</tr>
</tbody>
</table>
{/if}
</div>
@@ -193,7 +205,7 @@ SPDX-License-Identifier: MPL-2.0
<span
class="h-full transition-all absolute rounded-sm bg-black/50"
style="width: {100 - stats.progress * 100}%"
/>
></span>
</div>
<div class="flex justify-center">
<BrownButton on:click={compress_video} disabled={status !== Status.Idle}
+7 -3
View File
@@ -5,13 +5,17 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { navbarVisible } from '$lib/stores';
import { navbarVisible } from '$lib/stores.svelte.ts';
navbarVisible.set(true);
navbarVisible.visible = true;
import SearchCard from '$lib/search-card.svelte';
import type { PageData } from './$types';
export let data: PageData;
interface Props {
data: PageData;
}
let { data }: Props = $props();
const quizzes = data.results;
</script>
+23 -17
View File
@@ -5,22 +5,26 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { getLocalization } from '$lib/i18n';
import { navbarVisible } from '$lib/stores';
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { run, preventDefault } from 'svelte/legacy';
navbarVisible.set(true);
import { getLocalization } from '$lib/i18n';
import { navbarVisible } from '$lib/stores.svelte.ts';
import { onMount } from 'svelte';
import { page } from '$app/state';
navbarVisible.visible = true;
const { t } = getLocalization();
let url_input = '';
let file_input: File[];
let url_input = $state('');
let file_input: File[] = $state();
let url_valid = false;
let url_valid = $state(false);
let kahoot_regex = /^https:\/\/create\.kahoot\.it\/details\/([a-zA-Z-\d]{36})\/?$/;
let is_loading = false;
let is_loading = $state(false);
$: url_valid = kahoot_regex.test(url_input);
run(() => {
url_valid = kahoot_regex.test(url_input);
});
const submit = async () => {
if (!url_valid) {
@@ -91,10 +95,12 @@ SPDX-License-Identifier: MPL-2.0
is_loading = false;
};
$: console.log(file_input);
run(() => {
console.log(file_input);
});
onMount(() => {
let url_from_path = $page.url.searchParams.get('url');
let url_from_path = page.url.searchParams.get('url');
if (url_from_path === '') {
url_from_path = null;
}
@@ -125,7 +131,7 @@ SPDX-License-Identifier: MPL-2.0
</form>-->
<div class="flex items-center justify-center h-full px-4">
<div>
<span class="p-4" />
<span class="p-4"></span>
<div
class="lg:w-[64rem] lg:max-w-[64rem] w-screen max-w-screen mx-auto overflow-hidden bg-white rounded-lg shadow-md dark:bg-gray-800"
@@ -143,7 +149,7 @@ SPDX-License-Identifier: MPL-2.0
Login or create account
</p>-->
<div class="grid grid-cols-2">
<form on:submit|preventDefault={submit}>
<form onsubmit={preventDefault(submit)}>
<div class="w-full mt-4 h-full flex flex-col">
<h2 class="text-center text-2xl">{$t('import_page.a_kahoot_quiz')}</h2>
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
@@ -174,7 +180,7 @@ SPDX-License-Identifier: MPL-2.0
</div>
<div class="flex items-center justify-center mt-auto">
<span />
<span></span>
<button
class="px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded-sm hover:bg-gray-600 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50"
@@ -202,7 +208,7 @@ SPDX-License-Identifier: MPL-2.0
</div>
</div>
</form>
<form on:submit|preventDefault={file_submit}>
<form onsubmit={preventDefault(file_submit)}>
<div class="w-full mt-4 border-l-2 border-gray-600 h-full flex flex-col">
<h2 class="text-center text-2xl">{$t('import_page.classquiz_quiz')}</h2>
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
@@ -233,7 +239,7 @@ SPDX-License-Identifier: MPL-2.0
</div>
<div class="flex items-center justify-center mt-auto">
<span />
<span></span>
<button
class="px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded-sm hover:bg-gray-600 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50"
+5 -1
View File
@@ -8,7 +8,11 @@ SPDX-License-Identifier: MPL-2.0
import type { PageData } from './$types';
import BrownButton from '$lib/components/buttons/brown.svelte';
export let data: PageData;
interface Props {
data: PageData;
}
let { data }: Props = $props();
</script>
<div class="flex flex-col p-2">
+27 -23
View File
@@ -12,24 +12,28 @@ SPDX-License-Identifier: MPL-2.0
import ShowTitle from '$lib/play/title.svelte';
import Question from '$lib/play/question.svelte';
// import ShowResults from '$lib/play/show_results.svelte';
import { navbarVisible } from '$lib/stores';
import { navbarVisible} from '$lib/stores.svelte.ts';
import ShowEndScreen from '$lib/play/admin/final_results.svelte';
import KahootResults from '$lib/play/results_kahoot.svelte';
import { getLocalization } from '$lib/i18n';
import Cookies from 'js-cookie';
const { t } = getLocalization();
// Exports
export let data;
let { game_pin } = data;
interface Props {
// Exports
data: any;
}
let { data }: Props = $props();
let { game_pin } = $state(data);
// Types
interface GameMeta {
started: boolean;
}
let game_mode;
let final_results: Array<null> | Array<Array<PlayerAnswer>> = [null];
let game_mode = $state();
let final_results: Array<null> | Array<Array<PlayerAnswer>> = $state([null]);
interface PlayerAnswer {
username: string;
@@ -38,20 +42,20 @@ SPDX-License-Identifier: MPL-2.0
}
// Variables init
let question_index = '';
let unique = {};
navbarVisible.set(false);
let question_index = $state('');
let unique = $state({});
navbarVisible.visible=false;
let game_pin_valid: boolean;
let answer_results: Array<Answer>;
let gameData;
let solution: QuestionType;
let username = '';
let scores = {};
let gameMeta: GameMeta = {
let answer_results: Array<Answer> = $state();
let gameData = $state();
let solution: QuestionType = $state();
let username = $state('');
let scores = $state({});
let gameMeta: GameMeta = $state({
started: false
};
});
let question;
let question = $state();
let preventReload = true;
@@ -72,9 +76,9 @@ SPDX-License-Identifier: MPL-2.0
socket.on('time_sync', (data) => {
socket.emit('echo_time_sync', data);
});
socket.on("session_id", (d) => {
const session_id = d.session_id
})
socket.on('session_id', (d) => {
const session_id = d.session_id;
});
socket.on('connect', async () => {
console.log('Connected!');
@@ -157,12 +161,12 @@ SPDX-License-Identifier: MPL-2.0
solution = data;
});
let bg_color;
$: bg_color = gameData ? gameData.background_color : undefined;
let bg_color = $derived(gameData ? gameData.background_color : undefined);
// The rest
</script>
<svelte:window on:beforeunload={confirmUnload} />
<svelte:window onbeforeunload={confirmUnload} />
<svelte:head>
<title>ClassQuiz - Play</title>
<!-- {#if gameData !== undefined && game_mode !== 'kahoot'}
+7 -7
View File
@@ -4,19 +4,19 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { page } from '$app/stores';
import { page } from '$app/state';
import type { QuizData } from '$lib/quiz_types';
import Spinner from '$lib/Spinner.svelte';
import TitleScreen from '$lib/practice/title_screen.svelte';
import Question from '$lib/practice/question.svelte';
let quiz: QuizData;
let unique = {};
let quiz: QuizData = $state();
let unique = $state({});
let selected_question = -1;
let selected_question = $state(-1);
const get_quiz = async () => {
const res = await fetch(`/api/v1/quiz/get/public/${$page.url.searchParams.get('quiz_id')}`);
const res = await fetch(`/api/v1/quiz/get/public/${page.url.searchParams.get('quiz_id')}`);
if (!res.ok) {
throw res.status;
}
@@ -44,7 +44,7 @@ SPDX-License-Identifier: MPL-2.0
<button
class="flex justify-start transition-all disabled:opacity-60"
disabled={selected_question <= -1}
on:click={() => {
onclick={() => {
selected_question -= 1;
}}
>
@@ -66,7 +66,7 @@ SPDX-License-Identifier: MPL-2.0
<button
class="flex justify-end transition-all disabled:opacity-60"
disabled={selected_question >= quiz.questions.length - 1}
on:click={() => {
onclick={() => {
reload_q();
selected_question += 1;
}}
@@ -8,13 +8,13 @@ SPDX-License-Identifier: MPL-2.0
import type { Data } from '$lib/quiztivity/types';
import Editor from '$lib/quiztivity/editor.svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { page } from '$app/state';
let title = $page.url.searchParams.get('title');
let title = page.url.searchParams.get('title');
title ??= '';
let data: Data = { pages: [], id: undefined, title };
let saving = false;
let data: Data = $state({ pages: [], id: undefined, title });
let saving = $state(false);
const save_quiztivity = async () => {
saving = true;
@@ -10,12 +10,16 @@ SPDX-License-Identifier: MPL-2.0
import SharesPopover from '$lib/quiztivity/shares_popover.svelte';
import { goto } from '$app/navigation';
export let data: PageData;
interface Props {
data: PageData;
}
let saving = false;
let { data }: Props = $props();
let quiztivity = data.quiztivity;
let shares_menu_open = false;
let saving = $state(false);
let quiztivity = $state(data.quiztivity);
let shares_menu_open = $state(false);
const save_quiztivity = async () => {
saving = true;
@@ -5,12 +5,12 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { navbarVisible } from '$lib/stores';
import { page } from '$app/stores';
import { navbarVisible} from '$lib/stores.svelte.ts';
import { page } from '$app/state';
import { getLocalization } from '$lib/i18n';
navbarVisible.set(true);
let status = $page.status;
navbarVisible.visible = true;
let status = page.status;
const { t } = getLocalization();
</script>
@@ -5,37 +5,45 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { run } from 'svelte/legacy';
import type { PageData } from './$types';
import type { QuizTivityPage } from '$lib/quiztivity/types';
import { QuizTivityTypes } from '$lib/quiztivity/types';
import NavigationBar from './navigation_bar.svelte';
export let data: PageData;
let quiztivity = data.quiztivity;
interface Props {
data: PageData;
}
let { data }: Props = $props();
let quiztivity = $state(data.quiztivity);
for (let i = 0; i < quiztivity.pages.length; i++) {
const id = (Math.random() + 1).toString(36).substring(7);
const type: QuizTivityTypes = quiztivity.pages[i].type as QuizTivityTypes;
quiztivity.pages[i] = { ...quiztivity.pages[i], id, type };
}
let current_slide_index = 0;
let current_slide: QuizTivityPage = quiztivity.pages[current_slide_index];
$: current_slide = quiztivity.pages[current_slide_index];
let current_slide_index = $state(0);
let current_slide: QuizTivityPage = $state(quiztivity.pages[current_slide_index]);
run(() => {
current_slide = quiztivity.pages[current_slide_index];
});
</script>
<div class="w-full h-full flex flex-col overflow-scroll">
<div class="w-full h-full">
{#if current_slide.type === QuizTivityTypes.MARKDOWN}
{#await import('$lib/quiztivity/components/markdown/play.svelte') then c}
<svelte:component this={c.default} data={current_slide.data} />
<c.default data={current_slide.data} />
{/await}
{:else if current_slide.type === QuizTivityTypes.MEMORY}
{#await import('$lib/quiztivity/components/memory/play.svelte') then c}
<svelte:component this={c.default} data={current_slide.data} />
<c.default data={current_slide.data} />
{/await}
{:else if current_slide.type === QuizTivityTypes.ABCD}
{#await import('$lib/quiztivity/components/abcd/play.svelte') then c}
<svelte:component this={c.default} data={current_slide.data} />
<c.default data={current_slide.data} />
{/await}
{/if}
</div>
@@ -8,8 +8,12 @@ SPDX-License-Identifier: MPL-2.0
import BrownButton from '$lib/components/buttons/brown.svelte';
import { getLocalization } from '$lib/i18n';
export let current_slide_index: number;
export let question_count: number;
interface Props {
current_slide_index: number;
question_count: number;
}
let { current_slide_index = $bindable(), question_count }: Props = $props();
const { t } = getLocalization();
</script>
+34 -36
View File
@@ -5,37 +5,39 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { page } from '$app/stores';
import { run, preventDefault } from 'svelte/legacy';
import { page } from '$app/state';
import { socket } from '$lib/socket';
import type { QuizData } from '$lib/quiz_types';
import { QuizQuestionType } from '$lib/quiz_types.js';
import Spinner from '$lib/Spinner.svelte';
import CircularTimer from '$lib/play/circular_progress.svelte';
import { getLocalization } from '$lib/i18n';
import { navbarVisible } from '$lib/stores';
import { navbarVisible } from '$lib/stores.svelte.ts';
const data = {
game_pin: $page.url.searchParams.get('game_pin'),
game_id: $page.url.searchParams.get('game_id')
game_pin: page.url.searchParams.get('game_pin'),
game_id: page.url.searchParams.get('game_id')
};
navbarVisible.set(false);
navbarVisible.visible = false;
const { t } = getLocalization();
let timer_interval;
let timer_res = undefined;
let selected_question = -1;
let game_started = false;
let question_results;
let final_results = [null];
let timer_res = $state(undefined);
let selected_question = $state(-1);
let game_started = $state(false);
let question_results = $state();
let final_results = $state([null]);
let warnToLeave = true;
let dataexport_download_a;
let dataexport_download_a = $state();
let players: Array<{ sid: string; username: string }> = [];
let players: Array<{ sid: string; username: string }> = $state([]);
let game_data: QuizData;
let game_data: QuizData = $state();
let shown_question_now;
let control_visible = false;
let control_visible = $state(false);
if (!data.game_id || !data.game_pin) {
console.log('Error!');
@@ -102,8 +104,8 @@ SPDX-License-Identifier: MPL-2.0
}
};
let circular_progress = 0;
$: {
let circular_progress = $state(0);
run(() => {
try {
circular_progress =
1 -
@@ -111,7 +113,7 @@ SPDX-License-Identifier: MPL-2.0
} catch {
circular_progress = 0;
}
}
});
socket.on('solutions', (_) => {
timer_res = '0';
@@ -176,14 +178,14 @@ SPDX-License-Identifier: MPL-2.0
});
</script>
<svelte:window on:beforeunload={confirmUnload} />
<svelte:window onbeforeunload={confirmUnload} />
{#if game_started}
{#if selected_question + 1 === game_data.questions.length && ((timer_res === '0' && question_results !== null) || game_data?.questions?.[selected_question]?.type === QuizQuestionType.SLIDE)}
{#if JSON.stringify(final_results) === JSON.stringify([null])}
<button on:click={get_final_results} class="admin-button">Get final results </button>
<button onclick={get_final_results} class="admin-button">Get final results </button>
{:else}
<div class="w-screen flex justify-center mt-16">
<button on:click={request_answer_export} class="admin-button"
<button onclick={request_answer_export} class="admin-button"
>{$t('admin_page.export_results')}</button
>
</div>
@@ -191,7 +193,7 @@ SPDX-License-Identifier: MPL-2.0
{:else if timer_res === '0' || selected_question === -1}
{#if (selected_question + 1 !== game_data.questions.length && question_results !== null) || selected_question === -1}
<button
on:click={() => {
onclick={() => {
set_question_number(selected_question + 1);
}}
class="admin-button"
@@ -201,27 +203,27 @@ SPDX-License-Identifier: MPL-2.0
{#if question_results === null && selected_question !== -1}
{#if game_data.questions[selected_question].type === QuizQuestionType.SLIDE}
<button
on:click={() => {
onclick={() => {
set_question_number(selected_question + 1);
}}
class="admin-button"
>Next Question ({selected_question + 2})
</button>
{:else}
<button on:click={get_question_results} class="admin-button">Show results </button>
<button onclick={get_question_results} class="admin-button">Show results </button>
{/if}
{/if}
{:else if selected_question !== -1}
{#if game_data.questions[selected_question].type === QuizQuestionType.SLIDE}
<button
on:click={() => {
onclick={() => {
set_question_number(selected_question + 1);
}}
class="admin-button"
>Next Question ({selected_question + 2})
</button>
{:else}
<button on:click={show_solutions} class="admin-button"
<button onclick={show_solutions} class="admin-button"
>Stop time and show solutions
</button>
{/if}
@@ -233,10 +235,7 @@ SPDX-License-Identifier: MPL-2.0
{#await import('$lib/play/admin/slide.svelte')}
<Spinner my_20={false} />
{:then c}
<svelte:component
this={c.default}
bind:question={game_data.questions[selected_question]}
/>
<c.default bind:question={game_data.questions[selected_question]} />
{/await}
{:else}
<div class="flex flex-col justify-center w-screen h-1/6">
@@ -274,7 +273,7 @@ SPDX-License-Identifier: MPL-2.0
<span class="text-center text-2xl px-2 py-4 w-full text-black"
>{answer.answer}</span
>
<span class="pl-4 w-10" />
<span class="pl-4 w-10"></span>
</div>
{/each}
</div>
@@ -287,8 +286,7 @@ SPDX-License-Identifier: MPL-2.0
{#await import('$lib/play/admin/voting_results.svelte')}
<Spinner />
{:then c}
<svelte:component
this={c.default}
<c.default
bind:data={question_results}
bind:question={game_data.questions[selected_question]}
/>
@@ -296,7 +294,7 @@ SPDX-License-Identifier: MPL-2.0
{/if}
{/if}
{:else}
<button on:click={start_game} class="admin-button">Start Game!</button>
<button onclick={start_game} class="admin-button">Start Game!</button>
<h2>Players:</h2>
{#await get_already_joined_players()}
@@ -314,14 +312,14 @@ SPDX-License-Identifier: MPL-2.0
<div class="fixed top-0 right-0">
{#if control_visible}
<button
on:click={() => {
onclick={() => {
socket.emit('set_control_visibility', { visible: false });
}}
>Hide Controls
</button>
{:else}
<button
on:click={() => {
onclick={() => {
socket.emit('set_control_visibility', { visible: true });
}}
>Show Controls
@@ -330,7 +328,7 @@ SPDX-License-Identifier: MPL-2.0
</div>
<a
on:click|preventDefault={request_answer_export}
onclick={preventDefault(request_answer_export)}
href="#"
bind:this={dataexport_download_a}
class="absolute -top-3/4 -left-3/4 opacity-0 hidden">Download</a
+40 -32
View File
@@ -10,7 +10,11 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization();
export let data: PageData;
interface Props {
data: PageData;
}
let { data }: Props = $props();
</script>
<div class="w-full">
@@ -20,38 +24,42 @@ SPDX-License-Identifier: MPL-2.0
<p class="text-center text-3xl mt-8">{$t('results_page.no_results_so_far')}</p>
{:else}
<table class="w-full">
<tr class="border-b-2 dark:border-gray-500 text-left border-gray-300">
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('results_page.quiz_title')}
</th>
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('results_page.date_played')}
</th>
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('results_page.player_count')}
</th>
<th class="mx-auto p-1">{$t('words.note')}</th>
</tr>
{#each data.results as result}
<tr class="text-left">
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
><a href="/results/{result.id}" class="underline text-lg"
>{result.title}</a
></td
>
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{new Date(result.timestamp).toLocaleString()}</td
>
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{Object.keys(result.player_scores).length}</td
>
<td class:p-1={result.note}>
{#if result.note}
{result.note}
{/if}
</td>
<thead>
<tr class="border-b-2 dark:border-gray-500 text-left border-gray-300">
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('results_page.quiz_title')}
</th>
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('results_page.date_played')}
</th>
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('results_page.player_count')}
</th>
<th class="mx-auto p-1">{$t('words.note')}</th>
</tr>
{/each}
</thead>
<tbody>
{#each data.results as result}
<tr class="text-left">
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
><a href="/results/{result.id}" class="underline text-lg"
>{result.title}</a
></td
>
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{new Date(result.timestamp).toLocaleString()}</td
>
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{Object.keys(result.player_scores).length}</td
>
<td class:p-1={result.note}>
{#if result.note}
{result.note}
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
{/if}
</div>
@@ -14,7 +14,11 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization();
export let data: PageData;
interface Props {
data: PageData;
}
let { data }: Props = $props();
// eslint-disable-next-line no-unused-vars
enum SelectedTab {
@@ -26,7 +30,7 @@ SPDX-License-Identifier: MPL-2.0
Questions
}
let selected_tab: SelectedTab = SelectedTab.Overview;
let selected_tab: SelectedTab = $state(SelectedTab.Overview);
</script>
<div class="w-full">
@@ -37,7 +41,7 @@ SPDX-License-Identifier: MPL-2.0
class:opacity-60={selected_tab !== SelectedTab.Overview}
>
<button
on:click={() => {
onclick={() => {
selected_tab = SelectedTab.Overview;
}}
class="m-auto w-full h-full"
@@ -50,7 +54,7 @@ SPDX-License-Identifier: MPL-2.0
class:opacity-60={selected_tab !== SelectedTab.Players}
>
<button
on:click={() => {
onclick={() => {
selected_tab = SelectedTab.Players;
}}
class="m-auto w-full h-full"
@@ -64,7 +68,7 @@ SPDX-License-Identifier: MPL-2.0
class:opacity-60={selected_tab !== SelectedTab.Questions}
>
<button
on:click={() => {
onclick={() => {
selected_tab = SelectedTab.Questions;
}}
class="m-auto w-full h-full"
@@ -10,20 +10,30 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization();
export let questions: Question[];
export let answers: {
interface Props {
questions: Question[];
answers: {
username: string;
answer: string;
right: boolean;
tike_taken: number;
score: number;
}[][];
export let scores: {
scores: {
[key: string]: string;
};
export let title: string;
export let timestamp: string;
title: string;
timestamp: string;
}
let {
questions,
answers,
scores,
title,
timestamp
}: Props = $props();
const usernames = Object.keys(scores);
@@ -8,12 +8,16 @@ SPDX-License-Identifier: MPL-2.0
import { getLocalization } from '$lib/i18n';
const { t } = getLocalization();
export let scores: {
interface Props {
scores: {
[key: string]: string;
};
export let custom_field: {
custom_field: {
[key: string]: string;
};
}
let { scores, custom_field }: Props = $props();
let usernames = Object.keys(scores);
console.log(custom_field);
@@ -22,28 +26,32 @@ SPDX-License-Identifier: MPL-2.0
<div class="w-full">
<div class="flex justify-center w-full">
<table class="w-11/12 m-auto">
<tr class="border-b-2 dark:border-gray-500 text-left border-gray-300">
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('result_page.player_name')}
</th>
<th class="p-1 mx-auto">{$t('result_page.player_score')}</th>
{#if Object.keys(custom_field).length !== 0}
<th class="border-l dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('result_page.custom_field')}
<thead>
<tr class="border-b-2 dark:border-gray-500 text-left border-gray-300">
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('result_page.player_name')}
</th>
{/if}
</tr>
{#each usernames as uname}
<tr class="text-left">
<td class="border-r dark:border-gray-500 p-1 border-gray-300">{uname}</td>
<td class="p-1">{scores[uname]}</td>
{#if custom_field[uname]}
<td class="border-l dark:border-gray-500 p-1 border-gray-300"
>{custom_field[uname]}</td
>
<th class="p-1 mx-auto">{$t('result_page.player_score')}</th>
{#if Object.keys(custom_field).length !== 0}
<th class="border-l dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('result_page.custom_field')}
</th>
{/if}
</tr>
{/each}
</thead>
<tbody>
{#each usernames as uname}
<tr class="text-left">
<td class="border-r dark:border-gray-500 p-1 border-gray-300">{uname}</td>
<td class="p-1">{scores[uname]}</td>
{#if custom_field[uname]}
<td class="border-l dark:border-gray-500 p-1 border-gray-300"
>{custom_field[uname]}</td
>
{/if}
</tr>
{/each}
</tbody>
</table>
</div>
</div>
@@ -13,14 +13,18 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization();
export let questions: Question[];
export let answers: {
interface Props {
questions: Question[];
answers: {
username: string;
answer: string;
right: boolean;
tike_taken: number;
score: number;
}[][];
}
let { questions, answers }: Props = $props();
const get_average_score = (q_index: number): number => {
const q_answers = answers[q_index];
@@ -49,7 +53,7 @@ SPDX-License-Identifier: MPL-2.0
question_open = q_index;
}
};
let question_open: number | boolean = false;
let question_open: number | boolean = $state(false);
</script>
<div class="w-full flex justify-center">
@@ -61,7 +65,7 @@ SPDX-License-Identifier: MPL-2.0
>
<button
class="text-center underline text-xl"
on:click={() => {
onclick={() => {
toggle_dropdown(i);
}}>{@html question.question}</button
>
@@ -11,7 +11,6 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization();
export let question: Question;
interface Answer {
username: string;
@@ -21,7 +20,12 @@ SPDX-License-Identifier: MPL-2.0
score: number;
}
export let answers: Answer[];
interface Props {
question: Question;
answers: Answer[];
}
let { question, answers }: Props = $props();
// console.log(question);
const get_answer_count_for_answer = (answer: string): number => {
@@ -66,7 +70,7 @@ SPDX-License-Identifier: MPL-2.0
style="width: {(get_answer_count_for_answer(answer.answer) /
answers.length) *
100}%"
/>
></span>
</div>
<p>{get_answer_count_for_answer(answer.answer)}</p>
{#if question.type !== QuizQuestionType.VOTING && question.type !== QuizQuestionType.TEXT}
@@ -81,46 +85,50 @@ SPDX-License-Identifier: MPL-2.0
{/if}
<div>
<table class="w-full text-left">
<tr class="border-b-2 dark:border-gray-500 text-left border-gray-300">
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('result_page.player_name')}
</th>
{#if question.type !== QuizQuestionType.VOTING}
<thead>
<tr class="border-b-2 dark:border-gray-500 text-left border-gray-300">
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('words.score')}</th
>
{/if}
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('result_page.time_taken')}
</th>
<th class="p-1 mx-auto">{$t('words.answer')} </th>
{#if question.type !== QuizQuestionType.VOTING}
<th class="border-l dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('words.correct')}?
>{$t('result_page.player_name')}
</th>
{/if}
</tr>
{#each answers as answer}
<tr>
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{answer.username}</td
>
{#if question.type !== QuizQuestionType.VOTING}
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{answer.score}</td
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('words.score')}</th
>
{/if}
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{(answer.time_taken / 1000).toFixed(3)}s
</td>
<td class="p-1">{answer.answer}</td>
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('result_page.time_taken')}
</th>
<th class="p-1 mx-auto">{$t('words.answer')} </th>
{#if question.type !== QuizQuestionType.VOTING}
<td class="p-1 border-l dark:border-gray-500 border-gray-300">
{#if answer.right}{:else}{/if}
</td>
<th class="border-l dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('words.correct')}?
</th>
{/if}
</tr>
{/each}
</thead>
<tbody>
{#each answers as answer}
<tr>
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{answer.username}</td
>
{#if question.type !== QuizQuestionType.VOTING}
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{answer.score}</td
>
{/if}
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{(answer.time_taken / 1000).toFixed(3)}s
</td>
<td class="p-1">{answer.answer}</td>
{#if question.type !== QuizQuestionType.VOTING}
<td class="p-1 border-l dark:border-gray-500 border-gray-300">
{#if answer.right}{:else}{/if}
</td>
{/if}
</tr>
{/each}
</tbody>
</table>
</div>
</div>
+5 -3
View File
@@ -4,12 +4,14 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n';
const { t } = getLocalization();
import SearchCard from '$lib/search-card.svelte';
import { onMount } from 'svelte';
let search_term = '';
let resp_data = null;
let search_term = $state('');
let resp_data = $state(null);
const submit = async () => {
const res = await fetch('/api/v1/search/', {
@@ -50,7 +52,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="mb-3 xl:w-96">
<form
class="input-group relative flex items-stretch flex-row w-full mb-4"
on:submit|preventDefault={submit}
onsubmit={preventDefault(submit)}
>
<input
type="search"
@@ -14,7 +14,7 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization();
// import { DateTime } from 'luxon';
let start_game = null;
let start_game = $state(null);
const tippy = createTippy({
arrow: true,
@@ -30,7 +30,11 @@ SPDX-License-Identifier: MPL-2.0
onMount(() => {
document.body.addEventListener('keydown', close_start_game_if_esc_is_pressed);
});
export let data: PageData;
interface Props {
data: PageData;
}
let { data }: Props = $props();
</script>
<svelte:head>
@@ -110,7 +114,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="grid grid-cols-2 gap-3 w-1/3">
{#if $signedIn}
<button
on:click={() => {
onclick={() => {
start_game = quiz.id;
}}
class="action-button"
@@ -145,7 +149,7 @@ SPDX-License-Identifier: MPL-2.0
class="w-full"
>
<button
on:click={() => {
onclick={() => {
start_game = quiz.id;
}}
disabled
+4 -4
View File
@@ -5,10 +5,10 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { navbarVisible } from '$lib/stores';
import { page } from '$app/stores';
navbarVisible.set(true);
let status = $page.status;
import { navbarVisible } from '$lib/stores.svelte.ts';
import { page } from '$app/state';
navbarVisible.visible= true;
let status = page.status;
</script>
<svelte:head>
@@ -17,7 +17,7 @@ SPDX-License-Identifier: MPL-2.0
import GrayButton from '$lib/components/buttons/gray.svelte';
import MediaComponent from '$lib/editor/MediaComponent.svelte';
import RatingComponent from '$lib/view_quiz/RatingComponent.svelte';
import { page } from '$app/stores';
import { page } from '$app/state';
import ModComponent from './ModComponent.svelte';
import { get_foreground_color } from '$lib/helpers.ts';
@@ -29,9 +29,9 @@ SPDX-License-Identifier: MPL-2.0
placement: 'right'
});
let start_game = null;
let download_id: string | null = null;
const urlparams = $page.url.searchParams;
let start_game = $state(null);
let download_id: string | null = $state(null);
const urlparams = page.url.searchParams;
const mod_view = Boolean(urlparams.get('mod'));
const auto_expand = Boolean(urlparams.get('autoExpand'));
const auto_return = Boolean(urlparams.get('autoReturn'));
@@ -46,8 +46,8 @@ SPDX-License-Identifier: MPL-2.0
});
const { t } = getLocalization();
export let data;
let { quiz, logged_in }: { quiz: QuizData; logged_in: boolean } = data;
let { data } = $props();
let { quiz, logged_in }: { quiz: QuizData; logged_in: boolean } = $state(data);
interface Question {
time: string;
@@ -330,7 +330,7 @@ SPDX-License-Identifier: MPL-2.0
<Spinner my={false} />
{:then c}
<div class="max-h-[90%] max-w-[90%]">
<svelte:component this={c.default} bind:question />
<c.default bind:question={questions[index_question]} />
</div>
{/await}
{/if}
@@ -8,9 +8,13 @@ SPDX-License-Identifier: MPL-2.0
import BrownButton from '$lib/components/buttons/brown.svelte';
import GrayButton from '$lib/components/buttons/gray.svelte';
export let autoReturn = false;
export let quiz_id: string;
let mod_rating: number | undefined;
interface Props {
autoReturn?: boolean;
quiz_id: string;
}
let { autoReturn = false, quiz_id }: Props = $props();
let mod_rating: number | undefined = $state();
const submit = async () => {
const res = await fetch(`/api/v1/moderation/rating/set/${quiz_id}`, {