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
+6 -5
View File
@@ -26,8 +26,8 @@
"@simplewebauthn/browser": "^13.2.0", "@simplewebauthn/browser": "^13.2.0",
"@sveltejs/adapter-auto": "^6.1.0", "@sveltejs/adapter-auto": "^6.1.0",
"@sveltejs/adapter-node": "^5.3.2", "@sveltejs/adapter-node": "^5.3.2",
"@sveltejs/kit": "^2.0.0", "@sveltejs/kit": "^2.5.27",
"@sveltejs/vite-plugin-svelte": "^3.1.0", "@sveltejs/vite-plugin-svelte": "^6.2.0",
"@tailwindcss/postcss": "^4.1.13", "@tailwindcss/postcss": "^4.1.13",
"@tailwindcss/typography": "^0.5.18", "@tailwindcss/typography": "^0.5.18",
"@types/canvas-confetti": "^1.9.0", "@types/canvas-confetti": "^1.9.0",
@@ -44,6 +44,7 @@
"@uppy/drag-drop": "^4.2.2", "@uppy/drag-drop": "^4.2.2",
"@uppy/drop-target": "^3.2.2", "@uppy/drop-target": "^3.2.2",
"@uppy/image-editor": "^3.4.2", "@uppy/image-editor": "^3.4.2",
"@uppy/progress-bar": "^4.3.2",
"@uppy/status-bar": "^4.2.3", "@uppy/status-bar": "^4.2.3",
"@uppy/svelte": "^4.6.2", "@uppy/svelte": "^4.6.2",
"@uppy/xhr-upload": "^4.4.2", "@uppy/xhr-upload": "^4.4.2",
@@ -77,7 +78,7 @@
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"sass": "^1.93.0", "sass": "^1.93.0",
"socket.io-client": "^4.8.1", "socket.io-client": "^4.8.1",
"svelte": "^4.2.20", "svelte": "^5.0.0",
"svelte-check": "^4.3.1", "svelte-check": "^4.3.1",
"svelte-preprocess": "^6.0.3", "svelte-preprocess": "^6.0.3",
"svelte-range-slider-pips": "^4.0.7", "svelte-range-slider-pips": "^4.0.7",
@@ -88,9 +89,9 @@
"tinykeys": "^3.0.0", "tinykeys": "^3.0.0",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "~5.0.4", "typescript": "^5.5.0",
"ua-parser-js": "^2.0.5", "ua-parser-js": "^2.0.5",
"vite": "^5.0.0", "vite": "^7.1.6",
"vite-plugin-cross-origin-isolation": "^0.1.6", "vite-plugin-cross-origin-isolation": "^0.1.6",
"vite-plugin-iso-import": "^1.3.0", "vite-plugin-iso-import": "^1.3.0",
"yup": "^1.7.0" "yup": "^1.7.0"
+353 -333
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
#
# SPDX-License-Identifier: MPL-2.0
onlyBuiltDependencies:
- svelte-preprocess
+1
View File
@@ -8,6 +8,7 @@ SPDX-License-Identifier: MPL-2.0
@import 'tippy.js/dist/tippy.css'; @import 'tippy.js/dist/tippy.css';
/* Write your global styles here, in PostCSS syntax */ /* Write your global styles here, in PostCSS syntax */
@import 'tailwindcss'; @import 'tailwindcss';
@plugin "@tailwindcss/typography";
@tailwind utilities; @tailwind utilities;
.marck-script { .marck-script {
+5 -1
View File
@@ -5,7 +5,11 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let my_20 = true; interface Props {
my_20?: boolean;
}
let { my_20 = true }: Props = $props();
</script> </script>
<svg class="h-8 w-8 animate-spin mx-auto" class:my-20={my_20} viewBox="3 3 18 18"> <svg class="h-8 w-8 animate-spin mx-auto" class:my-20={my_20} viewBox="3 3 18 18">
+29 -20
View File
@@ -15,25 +15,37 @@ SPDX-License-Identifier: MPL-2.0
import Controls from '$lib/play/admin/controls.svelte'; import Controls from '$lib/play/admin/controls.svelte';
import Question from '$lib/play/admin/question.svelte'; import Question from '$lib/play/admin/question.svelte';
export let game_token: string;
export let quiz_data: QuizData;
export let game_mode: string;
export let bg_color: string;
const { t } = getLocalization(); const { t } = getLocalization();
const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58']; const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58'];
let question_results = null; let question_results = $state(null);
export let final_results: Array<null> | Array<Array<PlayerAnswer>> = [null]; let selected_question = $state(-1);
let selected_question = -1; let timer_res: string = $state();
let timer_res: string; let shown_question_now: number = $state();
let shown_question_now: number; let final_results_clicked = $state(false);
let final_results_clicked = false;
let timer_interval; let timer_interval;
let answer_count = 0; let answer_count = $state(0);
export let control_visible: boolean;
export let player_scores; interface Props {
game_token: string;
quiz_data: QuizData;
game_mode: string;
bg_color: string;
final_results?: Array<null> | Array<Array<PlayerAnswer>>;
control_visible: boolean;
player_scores: any;
}
let {
game_token,
quiz_data = $bindable(),
game_mode,
bg_color,
final_results = $bindable([null]),
control_visible,
player_scores = $bindable()
}: Props = $props();
socket.on('get_question_results', () => { socket.on('get_question_results', () => {
console.log('get_question_results'); console.log('get_question_results');
@@ -110,7 +122,7 @@ SPDX-License-Identifier: MPL-2.0
class:mt-10={control_visible} class:mt-10={control_visible}
style="width: {(100 / parseInt(quiz_data.questions[selected_question].time)) * style="width: {(100 / parseInt(quiz_data.questions[selected_question].time)) *
parseInt(timer_res)}vw" parseInt(timer_res)}vw"
/> ></span>
{/if} {/if}
<div class="w-full h-full" class:pt-28={control_visible} class:pt-12={!control_visible}> <div class="w-full h-full" class:pt-28={control_visible} class:pt-12={!control_visible}>
@@ -120,8 +132,7 @@ SPDX-License-Identifier: MPL-2.0
{#await import('$lib/play/admin/slide.svelte')} {#await import('$lib/play/admin/slide.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<svelte:component <c.default
this={c.default}
bind:question={quiz_data.questions[selected_question]} bind:question={quiz_data.questions[selected_question]}
/> />
{/await} {/await}
@@ -141,8 +152,7 @@ SPDX-License-Identifier: MPL-2.0
{#await import('$lib/play/admin/voting_results.svelte')} {#await import('$lib/play/admin/voting_results.svelte')}
<Spinner /> <Spinner />
{:then c} {:then c}
<svelte:component <c.default
this={c.default}
bind:data={question_results} bind:data={question_results}
bind:question={quiz_data.questions[selected_question]} bind:question={quiz_data.questions[selected_question]}
/> />
@@ -151,8 +161,7 @@ SPDX-License-Identifier: MPL-2.0
{#await import('$lib/play/admin/results.svelte')} {#await import('$lib/play/admin/results.svelte')}
<Spinner /> <Spinner />
{:then c} {:then c}
<svelte:component <c.default
this={c.default}
bind:data={player_scores} bind:data={player_scores}
question={quiz_data.questions[selected_question]} question={quiz_data.questions[selected_question]}
bind:new_data={question_results} bind:new_data={question_results}
+10 -5
View File
@@ -4,17 +4,22 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0 SPDX-License-Identifier: MPL-2.0
--> -->
<script> <script lang="ts">
export let headerText;
export let expanded = false; interface Props {
headerText: any;
expanded?: boolean;
children?: import('svelte').Snippet;
}
let { headerText, expanded = $bindable(false), children }: Props = $props();
</script> </script>
<div> <div>
<h3 class="bg-transparent m-0"> <h3 class="bg-transparent m-0">
<button <button
aria-expanded={expanded} aria-expanded={expanded}
on:click={() => (expanded = !expanded)} onclick={() => (expanded = !expanded)}
class="bg-white dark:bg-gray-700 flex justify-between w-full border-none m-0 p-2 rounded-t-lg hover:bg-gray-200 dark:hover:bg-gray-500 transition" class="bg-white dark:bg-gray-700 flex justify-between w-full border-none m-0 p-2 rounded-t-lg hover:bg-gray-200 dark:hover:bg-gray-500 transition"
class:rounded-b-lg={!expanded} class:rounded-b-lg={!expanded}
><span>{@html headerText}</span> ><span>{@html headerText}</span>
@@ -31,7 +36,7 @@ SPDX-License-Identifier: MPL-2.0
</h3> </h3>
<div class:hidden={!expanded}> <div class:hidden={!expanded}>
<slot /> {@render children?.()}
</div> </div>
</div> </div>
@@ -10,7 +10,11 @@ SPDX-License-Identifier: MPL-2.0
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let quiz_id: string | null = null; interface Props {
quiz_id?: string | null;
}
let { quiz_id = $bindable(null) }: Props = $props();
const handle_on_click = () => { const handle_on_click = () => {
quiz_id = null; quiz_id = null;
@@ -27,7 +31,7 @@ SPDX-License-Identifier: MPL-2.0
{#if quiz_id} {#if quiz_id}
<div <div
class="w-screen h-screen fixed top-0 left-0 bg-black/50 z-20 flex justify-center" class="w-screen h-screen fixed top-0 left-0 bg-black/50 z-20 flex justify-center"
on:click={handle_on_click} onclick={handle_on_click}
transition:fade={{ duration: 100 }} transition:fade={{ duration: 100 }}
> >
<div class="m-auto w-1/3 h-auto bg-white dark:bg-gray-700 p-4 rounded-sm"> <div class="m-auto w-1/3 h-auto bg-white dark:bg-gray-700 p-4 rounded-sm">
@@ -5,14 +5,29 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let disabled = false; import { createBubbler } from 'svelte/legacy';
export let flex = false;
export let href: undefined | string = undefined; const bubble = createBubbler();
export let target: undefined | string = '_self';
export let type: undefined | string = 'button';
interface Props {
disabled?: boolean;
flex?: boolean;
href?: undefined | string;
target?: undefined | string;
type?: undefined | string;
children?: import('svelte').Snippet;
}
let {
disabled = false,
flex = false,
href = undefined,
target = '_self',
type = 'button',
children
}: Props = $props();
</script> </script>
{#if href} {#if href}
@@ -24,21 +39,21 @@ SPDX-License-Identifier: MPL-2.0
class:cursor-not-allowed={disabled} class:cursor-not-allowed={disabled}
class:pointer-events-none={disabled} class:pointer-events-none={disabled}
class="text-black hover:bg-bg-[#B07156]/80 w-full px-4 py-2 leading-5 transition-all duration-200 transform bg-[#B07156] rounded-sm text-center outline-hidden hover:cursor-pointer" class="text-black hover:bg-bg-[#B07156]/80 w-full px-4 py-2 leading-5 transition-all duration-200 transform bg-[#B07156] rounded-sm text-center outline-hidden hover:cursor-pointer"
on:click onclick={bubble('click')}
class:flex class:flex
class:justify-center={flex} class:justify-center={flex}
> >
<slot /> {@render children?.()}
</a> </a>
{:else} {:else}
<button <button
{disabled} {disabled}
{type} {type}
class="text-black hover:cursor-pointer hover:opacity-80 w-full px-4 py-2 leading-5 transition-all duration-200 transform bg-[#B07156] rounded-sm text-center focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 outline-hidden" class="text-black hover:cursor-pointer hover:opacity-80 w-full px-4 py-2 leading-5 transition-all duration-200 transform bg-[#B07156] rounded-sm text-center focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 outline-hidden"
on:click onclick={bubble('click')}
class:flex class:flex
class:justify-center={flex} class:justify-center={flex}
> >
<slot /> {@render children?.()}
</button> </button>
{/if} {/if}
@@ -5,11 +5,25 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let disabled = false; import { createBubbler } from 'svelte/legacy';
export let flex = false;
export let href: undefined | string = undefined; const bubble = createBubbler();
export let target: undefined | string = '_self';
interface Props {
disabled?: boolean;
flex?: boolean;
href?: undefined | string;
target?: undefined | string;
children?: import('svelte').Snippet;
}
let {
disabled = false,
flex = false,
href = undefined,
target = '_self',
children
}: Props = $props();
</script> </script>
{#if href} {#if href}
@@ -17,21 +31,21 @@ SPDX-License-Identifier: MPL-2.0
{href} {href}
{target} {target}
class="w-full px-4 py-2 leading-5 text-black dark:text-white transition-colors duration-200 transform bg-gray-50 dark:bg-gray-700 rounded-sm text-center hover:bg-gray-300 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-gray-600" class="w-full px-4 py-2 leading-5 text-black dark:text-white transition-colors duration-200 transform bg-gray-50 dark:bg-gray-700 rounded-sm text-center hover:bg-gray-300 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-gray-600"
on:click onclick={bubble('click')}
class:flex class:flex
class:block={!flex} class:block={!flex}
class:justify-center={flex} class:justify-center={flex}
> >
<slot /> {@render children?.()}
</a> </a>
{:else} {:else}
<button <button
{disabled} {disabled}
class="w-full px-4 py-2 leading-5 text-black dark:text-white transition-colors duration-200 transform bg-gray-50 dark:bg-gray-700 rounded-sm text-center hover:bg-gray-300 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-gray-600" class="w-full px-4 py-2 leading-5 text-black dark:text-white transition-colors duration-200 transform bg-gray-50 dark:bg-gray-700 rounded-sm text-center hover:bg-gray-300 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-gray-600"
on:click onclick={bubble('click')}
class:flex class:flex
class:justify-center={flex} class:justify-center={flex}
> >
<slot /> {@render children?.()}
</button> </button>
{/if} {/if}
@@ -8,17 +8,19 @@ SPDX-License-Identifier: MPL-2.0
This should be okay, right? This should be okay, right?
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { tinykeys } from '$lib/tinykeys'; import { tinykeys } from '$lib/tinykeys';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import MiniSearch from 'minisearch'; import MiniSearch from 'minisearch';
let open = false; let open = $state(false);
let input = ''; let input = $state('');
let bg_text = ''; let bg_text = $state('');
let title_ms: MiniSearch; let title_ms: MiniSearch;
let command_ms: MiniSearch; let command_ms: MiniSearch;
let selected: null | number = null; let selected: null | number = $state(null);
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
type ActionFunction = (args: string[]) => void; type ActionFunction = (args: string[]) => void;
@@ -97,7 +99,7 @@ This should be okay, right?
action: () => window.location.assign('/account/settings') action: () => window.location.assign('/account/settings')
} }
]; ];
let visible_items = actions; let visible_items = $state(actions);
const toggle_open = (e: KeyboardEvent | undefined) => { const toggle_open = (e: KeyboardEvent | undefined) => {
e.preventDefault(); e.preventDefault();
@@ -204,9 +206,13 @@ This should be okay, right?
input = ''; input = '';
}; };
$: search(input); run(() => {
search(input);
});
// $: input = lower_input(input) // $: input = lower_input(input)
$: input = input.toLowerCase(); run(() => {
input = input.toLowerCase();
});
onMount(async () => { onMount(async () => {
tinykeys(window, { tinykeys(window, {
'$mod+k': toggle_open, '$mod+k': toggle_open,
@@ -232,8 +238,8 @@ This should be okay, right?
{#if open} {#if open}
<div <div
class="fixed top-0 left-0 w-screen h-screen flex bg-black/50 z-50" class="fixed top-0 left-0 w-screen h-screen flex bg-black/50 z-50"
on:click={close_on_outside} onclick={close_on_outside}
on:keyup={close_on_outside} onkeyup={close_on_outside}
transition:fade|global={{ duration: 60 }} transition:fade|global={{ duration: 60 }}
> >
<div class="m-auto w-1/3 h-2/3 rounded-sm bg-black flex flex-col"> <div class="m-auto w-1/3 h-2/3 rounded-sm bg-black flex flex-col">
@@ -256,8 +262,8 @@ This should be okay, right?
class="p-2 transition rounded-sm" class="p-2 transition rounded-sm"
class:bg-[#B07156]={selected === i} class:bg-[#B07156]={selected === i}
class:bg-gray-700={selected !== i} class:bg-gray-700={selected !== i}
on:mouseenter={() => (selected = i)} onmouseenter={() => (selected = i)}
on:mousedown={execute_action} onmousedown={execute_action}
> >
<div class="flex"> <div class="flex">
<h3 class="text-lg my-auto">{vi.title}</h3> <h3 class="text-lg my-auto">{vi.title}</h3>
@@ -11,7 +11,11 @@ SPDX-License-Identifier: MPL-2.0
y: 'yellow', y: 'yellow',
b: 'blue' b: 'blue'
}; };
export let code: string; interface Props {
code: string;
}
let { code }: Props = $props();
</script> </script>
<div class="flex flex-row gap-2 mx-auto"> <div class="flex flex-row gap-2 mx-auto">
@@ -22,7 +26,7 @@ SPDX-License-Identifier: MPL-2.0
style="background-color: {color_map[ style="background-color: {color_map[
c.toLowerCase() c.toLowerCase()
]}; width: 2rem; height: {c.toLowerCase() == c ? '2' : '4'}rem" ]}; width: 2rem; height: {c.toLowerCase() == c ? '2' : '4'}rem"
/> ></span>
</div> </div>
{/each} {/each}
</div> </div>
@@ -13,7 +13,7 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
let open = false; let open = $state(false);
onMount(() => { onMount(() => {
if (Cookies.get('commandpalette_notice')) { if (Cookies.get('commandpalette_notice')) {
return; return;
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
@@ -14,12 +16,17 @@ SPDX-License-Identifier: MPL-2.0
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let open = false; interface Props {
export let type: PopoverTypes; open?: boolean;
export let data: undefined | { game_pin: number | string; game_id: string } | string = type: PopoverTypes;
undefined; data?: undefined | { game_pin: number | string; game_id: string } | string;
}
$: dispatch('open', open); let { open = $bindable(false), type, data = undefined }: Props = $props();
run(() => {
dispatch('open', open);
});
</script> </script>
{#if open} {#if open}
@@ -49,7 +56,7 @@ SPDX-License-Identifier: MPL-2.0
class="ml-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700" class="ml-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700"
data-dismiss-target="#toast-default" data-dismiss-target="#toast-default"
aria-label="Close" aria-label="Close"
on:click={() => { onclick={() => {
open = false; open = false;
}} }}
> >
@@ -1,413 +0,0 @@
<!--
SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { Swiper, SwiperSlide } from 'swiper/svelte';
import 'swiper/css';
import 'swiper/css/pagination';
import 'swiper/css/navigation';
import { Pagination, EffectCoverflow, Keyboard, Mousewheel, Navigation } from 'swiper';
import { fly } from 'svelte/transition';
import { QuizQuestionType } from '$lib/quiz_types.js';
import { getLocalization } from '$lib/i18n';
import StartGamePopup from './start_game.svelte';
// import { onMount } from 'svelte';
import viewport from './useViewportAction.js';
import Spinner from '$lib/Spinner.svelte';
import GrayButton from '$lib/components/buttons/gray.svelte';
let copy_toast_open = false;
let start_game = null;
const { t } = getLocalization();
export let quizzes = [];
const deleteQuiz = async (to_delete: string) => {
if (!confirm('Do you really want to delete this quiz?')) {
return;
}
await fetch(`/api/v1/quiz/delete/${to_delete}`, {
method: 'DELETE'
});
window.location.reload();
};
let visibleImages = Array.from(Array(quizzes.length), () => []);
const copy_id = (quiz_id: string) => {
navigator.clipboard.writeText(quiz_id);
copy_toast_open = true;
setTimeout(() => {
copy_toast_open = false;
}, 2000);
};
let game_in_lobby: { game_pin: string; game_id: string; quiz_title: string } | undefined =
undefined;
const get_game_in_lobby_fn = async () => {
const res = await fetch('/api/v1/remote/game_waiting');
console.log('Fetched.');
if (res.ok) {
game_in_lobby = await res.json();
}
};
// onMount(() => {
// document.body.addEventListener('keydown', close_start_game_if_esc_is_pressed);
// });
get_game_in_lobby_fn();
</script>
{#if copy_toast_open || game_in_lobby}
<div class="fixed w-screen top-10 z-50 flex justify-center" transition:fly|global={{ y: -100 }}>
<div
class="flex items-center p-4 w-full max-w-xs text-gray-500 bg-white rounded-lg shadow-smdark:text-gray-400 dark:bg-gray-800"
role="alert"
>
<div class="ml-3 text-sm font-normal">
{#if copy_toast_open}ID copied to clipboard!
{:else}A game is currently in the lobby. Click <a
class="underline"
href="/remote?game_pin={game_in_lobby.game_pin}&game_id={game_in_lobby.game_id}"
>here</a
> to join as a remote.
{/if}
</div>
<button
type="button"
class="ml-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700"
data-dismiss-target="#toast-default"
aria-label="Close"
on:click={() => {
copy_toast_open = false;
game_in_lobby = undefined;
}}
>
<span class="sr-only">Close</span>
<svg
aria-hidden="true"
class="w-5 h-5"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"
/>
</svg>
</button>
</div>
</div>
{/if}
<div class="w-screen p-8">
<Swiper
grabCursor={true}
effect={'coverflow'}
modules={[EffectCoverflow, Pagination, Mousewheel, Navigation]}
navigation={true}
slidesPerView={1}
mousewheel={true}
centeredSlides={true}
coverflowEffect={{
rotate: 50,
stretch: 0,
depth: 100,
modifier: 1,
slideShadows: false
}}
pagination={{
clickable: true
}}
>
{#each quizzes as quiz, i}
<SwiperSlide>
<div class="flex justify-center pt-4 px-4">
<a href="/edit?quiz_id={quiz.id}" class="text-3xl">{@html quiz.title}</a>
</div>
<div class="flex justify-center p-10 h-full">
<div class="border-2 border-black rounded-lg w-5/6">
<Swiper
pagination={{
clickable: true
}}
keyboard={{ enabled: true }}
grabCursor={true}
navigation={true}
modules={[Pagination, Keyboard, Navigation]}
>
<SwiperSlide>
<div class="grid grid-cols-6 h-[25vh]">
<p
style="writing-mode: vertical-lr"
class="text-center h-full text-xl p-2"
>
{@html quiz.title}
</p>
<div class="col-start-2 col-end-6">
<p class="text-center">
{@html quiz.description}
</p>
{#if quiz.cover_image}
<div
class="flex justify-center align-middle items-center"
>
<div class="h-[20vh] m-auto w-auto">
<img
class="max-h-full max-w-full block"
src="/api/v1/storage/download/{quiz.cover_image}"
alt="Not provided"
loading="lazy"
/>
</div>
</div>
{/if}
</div>
<div class="flex justify-end">
<p
style="writing-mode: sideways-lr"
class="text-center text-xl p-2"
>
{@html quiz.title}
</p>
</div>
</div>
<p class="text-center">
{quiz.questions.length}
{$t('words.question', { count: quiz.questions.length })}
</p>
<div class="flex justify-center mt-4">
{#if quiz.public}
<svg
class="w-8 h-8 inline-block"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>{$t('words.public')}</span>
{:else}
<svg
class="w-8 h-8 inline-block"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
/>
</svg>
<span>{$t('words.private')}</span>
{/if}
</div>
<div class="flex justify-center pb-10 pt-8">
<div class="grid grid-cols-2 gap-3 w-1/3">
<GrayButton href="/edit?quiz_id={quiz.id}"
>{$t('words.edit')}</GrayButton
>
<GrayButton
on:click={() => {
start_game = quiz.id;
}}
>
{$t('words.start')}
</GrayButton>
<GrayButton
on:click={() => {
deleteQuiz(quiz.id);
}}
flex={true}
>
<!-- heroicons/trash -->
<svg
class="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
</GrayButton>
<GrayButton href="/api/v1/eximport/{quiz.id}" flex={true}
><!-- heroicons/download -->
<svg
class="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
/>
</svg>
</GrayButton>
</div>
</div>
<div class="flex justify-center">
<p
class="text-sm font-mono select-all cursor-pointer"
on:click={() => {
copy_id(quiz.id);
}}
>
{quiz.id}
</p>
</div>
</SwiperSlide>
{#each quiz.questions as question, q}
<SwiperSlide>
<div class="h-full">
<h5 class="text-center text-2xl">
{@html question.question}
</h5>
{#if question.image}
<div
use:viewport
on:enterViewport={() => {
visibleImages[i][q] = true;
}}
on:exitViewport={() => {
visibleImages[i][q] = false;
}}
class="flex justify-center align-middle items-center"
>
<div class="h-[30vh] m-auto w-auto">
{#if visibleImages?.[i]?.[q]}
<img
class="max-h-full max-w-full block"
src="/api/v1/storage/download/{question.image}"
alt="Not provided"
/>
{/if}
</div>
</div>
{/if}
<p
class="m-1 flex flex-row gap-2 flex-nowrap whitespace-nowrap w-full justify-center"
>
<svg
class="w-8 h-8 inline-block"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span class="text-lg">{question.time}s</span>
</p>
{#if question.type === QuizQuestionType.ABCD || question.type === undefined}
<div class="grid grid-cols-2 gap-4 m-4 p-6">
{#each question.answers as answer, index_answer}
<div
class="p-1 rounded-lg py-4"
class:bg-green-500={answer.right}
class:bg-red-500={!answer.right}
>
<h4 class="text-center">
{answer.answer}
</h4>
</div>
{/each}
</div>
{:else if question.type === QuizQuestionType.RANGE}
<p class="m-1 text-center">
All numbers between {question.answers.min_correct}
and {question.answers.max_correct} are correct, where
numbers between {question.answers.min} and {question
.answers.max} can be selected.
</p>
{:else if question.type === QuizQuestionType.VOTING}
<div class="grid grid-cols-2 gap-4 m-4 p-6">
{#each question.answers as answer}
<div
class="p-1 rounded-lg py-4 dark:bg-gray-500 bg-gray-300"
>
<h4 class="text-center">
{answer.answer}
</h4>
</div>
{/each}
</div>
{:else if question.type === QuizQuestionType.ORDER}
<ul class="flex flex-col gap-4 m-4 p-6">
{#each question.answers as answer}
<li
class="p-1 rounded-lg py-3 dark:bg-gray-500 bg-gray-300"
>
<h4 class="text-center">
{answer.answer}
</h4>
</li>
{/each}
</ul>
{:else if question.type === QuizQuestionType.SLIDE}
<div
use:viewport
on:enterViewport={() => {
visibleImages[i][q] = true;
}}
on:exitViewport={() => {
visibleImages[i][q] = false;
}}
class="flex justify-center align-middle items-center"
>
{#await import('$lib/play/admin/slide.svelte')}
<Spinner my={false} />
{:then c}
<div class="max-h-[50%] max-w-[60%]">
<svelte:component
this={c.default}
bind:question
/>
</div>
{/await}
</div>
{/if}
</div>
</SwiperSlide>
{/each}
</Swiper>
</div>
</div>
</SwiperSlide>
{/each}
</Swiper>
</div>
{#if start_game !== null}
<StartGamePopup bind:quiz_id={start_game} />
{/if}
+14 -14
View File
@@ -15,13 +15,13 @@ SPDX-License-Identifier: MPL-2.0
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let quiz_id; let { quiz_id = $bindable() } = $props();
let captcha_selected = false; let captcha_selected = $state(false);
let selected_game_mode = 'kahoot'; let selected_game_mode = $state('kahoot');
let loading = false; let loading = $state(false);
let custom_field = ''; let custom_field = $state('');
let cqcs_enabled = false; let cqcs_enabled = $state(false);
let randomized_answers = false; let randomized_answers = $state(false);
const tippy = createTippy({ const tippy = createTippy({
arrow: true, arrow: true,
@@ -96,7 +96,7 @@ SPDX-License-Identifier: MPL-2.0
<div <div
class="fixed top-0 left-0 flex justify-center w-screen h-screen bg-black/60 z-50 text-black" class="fixed top-0 left-0 flex justify-center w-screen h-screen bg-black/60 z-50 text-black"
transition:fade|global={{ duration: 100 }} transition:fade|global={{ duration: 100 }}
on:click={on_parent_click} onclick={on_parent_click}
> >
<div <div
class="w-5/6 h-5/6 bg-black m-auto rounded-lg shadow-lg p-4 flex flex-col" class="w-5/6 h-5/6 bg-black m-auto rounded-lg shadow-lg p-4 flex flex-col"
@@ -117,7 +117,7 @@ SPDX-License-Identifier: MPL-2.0
/> />
<span <span
class="w-14 h-7 bg-gray-200 peer-focus:outline-hidden peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600" class="w-14 h-7 bg-gray-200 peer-focus:outline-hidden peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
/> ></span>
<span class="ml-3 text-sm font-medium text-gray-900" <span class="ml-3 text-sm font-medium text-gray-900"
>Captcha {captcha_selected ? 'enabled' : 'disabled'}</span >Captcha {captcha_selected ? 'enabled' : 'disabled'}</span
> >
@@ -136,7 +136,7 @@ SPDX-License-Identifier: MPL-2.0
<div <div
class="rounded-lg bg-white shadow-lg cursor-pointer transition-all p-2" class="rounded-lg bg-white shadow-lg cursor-pointer transition-all p-2"
class:opacity-50={selected_game_mode !== 'kahoot'} class:opacity-50={selected_game_mode !== 'kahoot'}
on:click={() => { onclick={() => {
selected_game_mode = 'kahoot'; selected_game_mode = 'kahoot';
}} }}
> >
@@ -148,7 +148,7 @@ SPDX-License-Identifier: MPL-2.0
<div <div
class="rounded-lg bg-white shadow-lg cursor-pointer transition-all p-2" class="rounded-lg bg-white shadow-lg cursor-pointer transition-all p-2"
class:opacity-50={selected_game_mode !== 'normal'} class:opacity-50={selected_game_mode !== 'normal'}
on:click={() => { onclick={() => {
selected_game_mode = 'normal'; selected_game_mode = 'normal';
}} }}
> >
@@ -176,7 +176,7 @@ SPDX-License-Identifier: MPL-2.0
/> />
<span <span
class="w-14 h-7 bg-gray-200 peer-focus:outline-hidden peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600" class="w-14 h-7 bg-gray-200 peer-focus:outline-hidden peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
/> ></span>
<span class="ml-3 text-sm font-medium text-gray-900" <span class="ml-3 text-sm font-medium text-gray-900"
><a ><a
href="/controller" href="/controller"
@@ -204,14 +204,14 @@ SPDX-License-Identifier: MPL-2.0
/> />
<span <span
class="w-14 h-7 bg-gray-200 peer-focus:outline-hidden peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600" class="w-14 h-7 bg-gray-200 peer-focus:outline-hidden peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
/> ></span>
<span class="ml-3 text-sm font-medium text-gray-900"> Randomize answers</span> <span class="ml-3 text-sm font-medium text-gray-900"> Randomize answers</span>
</label> </label>
</div> </div>
<button <button
class="mt-auto mx-auto bg-green-500 p-4 rounded-lg shadow-lg hover:bg-green-400 transition-all marck-script text-2xl" class="mt-auto mx-auto bg-green-500 p-4 rounded-lg shadow-lg hover:bg-green-400 transition-all marck-script text-2xl"
on:click={() => { onclick={() => {
start_game(quiz_id); start_game(quiz_id);
}} }}
> >
+24 -12
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
// import { mint } from '$lib/hashcash'; // import { mint } from '$lib/hashcash';
import { dataSchema } from '$lib/yupSchemas'; import { dataSchema } from '$lib/yupSchemas';
import type { EditorData, Question } from './quiz_types'; import type { EditorData, Question } from './quiz_types';
@@ -16,13 +18,17 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
let schemaInvalid = false; let schemaInvalid = $state(false);
let yupErrorMessage = ''; let yupErrorMessage = $state('');
export let data: EditorData; interface Props {
export let quiz_id: string | null; data: EditorData;
let selected_question = -1; quiz_id: string | null;
let imgur_links_valid = false; }
let { data = $bindable(), quiz_id }: Props = $props();
let selected_question = $state(-1);
let imgur_links_valid = $state(false);
const validateInput = async (data: EditorData) => { const validateInput = async (data: EditorData) => {
// console.log("input", data) // console.log("input", data)
@@ -36,7 +42,9 @@ SPDX-License-Identifier: MPL-2.0
yupErrorMessage = err.errors ? err.errors[0] : ''; yupErrorMessage = err.errors ? err.errors[0] : '';
} }
}; };
$: validateInput(data); run(() => {
validateInput(data);
});
const checkIfAllQuestionImagesComplyWithRegex = (questions: Array<Question>) => { const checkIfAllQuestionImagesComplyWithRegex = (questions: Array<Question>) => {
let NoteverythingValid = false; let NoteverythingValid = false;
@@ -54,11 +62,15 @@ SPDX-License-Identifier: MPL-2.0
return NoteverythingValid; return NoteverythingValid;
}; };
$: imgur_links_valid = checkIfAllQuestionImagesComplyWithRegex(data.questions); run(() => {
let edit_id; imgur_links_valid = checkIfAllQuestionImagesComplyWithRegex(data.questions);
});
let edit_id = $state();
let confirm_to_leave = true; let confirm_to_leave = true;
$: console.log('data', data); run(() => {
console.log('data', data);
});
const getEditID = async () => { const getEditID = async () => {
let res; let res;
@@ -110,11 +122,11 @@ SPDX-License-Identifier: MPL-2.0
}; };
</script> </script>
<svelte:window on:beforeunload={confirmUnload} /> <svelte:window onbeforeunload={confirmUnload} />
{#await getEditID()} {#await getEditID()}
<Spinner /> <Spinner />
{:then _} {:then _}
<form on:submit|preventDefault={saveQuiz}> <form onsubmit={preventDefault(saveQuiz)}>
<div class="grid grid-cols-6 h-screen w-screen"> <div class="grid grid-cols-6 h-screen w-screen">
<div> <div>
<Sidebar bind:data bind:selected_question /> <Sidebar bind:data bind:selected_question />
+19 -11
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import type { Answer, EditorData } from '../quiz_types'; import type { Answer, EditorData } from '../quiz_types';
import { QuizQuestionType } from '../quiz_types'; import { QuizQuestionType } from '../quiz_types';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
@@ -17,9 +19,13 @@ SPDX-License-Identifier: MPL-2.0
const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58']; const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58'];
export let selected_question: number; interface Props {
export let check_choice = false; selected_question: number;
export let data: EditorData; check_choice?: boolean;
data: EditorData;
}
let { selected_question, check_choice = false, data = $bindable() }: Props = $props();
if (!Array.isArray(data.questions[selected_question].answers)) { if (!Array.isArray(data.questions[selected_question].answers)) {
data.questions[selected_question].answers = []; data.questions[selected_question].answers = [];
} }
@@ -41,7 +47,9 @@ SPDX-License-Identifier: MPL-2.0
right: false right: false
}; };
}; };
$: save_colors(data); run(() => {
save_colors(data);
});
data.questions[selected_question].type = data.questions[selected_question].type =
check_choice === true ? QuizQuestionType.CHECK : QuizQuestionType.ABCD; check_choice === true ? QuizQuestionType.CHECK : QuizQuestionType.ABCD;
const set_colors_if_unset = () => { const set_colors_if_unset = () => {
@@ -51,11 +59,11 @@ SPDX-License-Identifier: MPL-2.0
} }
} }
}; };
$: { run(() => {
set_colors_if_unset(); set_colors_if_unset();
data; data;
selected_question; selected_question;
} });
</script> </script>
<div class="grid grid-rows-2 grid-flow-col auto-cols-auto gap-4 w-full px-10"> <div class="grid grid-rows-2 grid-flow-col auto-cols-auto gap-4 w-full px-10">
@@ -73,7 +81,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition" class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition"
type="button" type="button"
on:click={() => { onclick={() => {
data.questions[selected_question].answers.splice(index, 1); data.questions[selected_question].answers.splice(index, 1);
data.questions[selected_question].answers = data.questions[selected_question].answers =
data.questions[selected_question].answers; data.questions[selected_question].answers;
@@ -105,7 +113,7 @@ SPDX-License-Identifier: MPL-2.0
/> />
<button <button
type="button" type="button"
on:click={() => { onclick={() => {
answer.right = !answer.right; answer.right = !answer.right;
}} }}
> >
@@ -145,9 +153,9 @@ SPDX-License-Identifier: MPL-2.0
class="rounded-lg p-1 border-black border" class="rounded-lg p-1 border-black border"
type="color" type="color"
bind:value={answer.color} bind:value={answer.color}
on:contextmenu|preventDefault={() => { oncontextmenu={preventDefault(() => {
answer.color = default_colors[index]; answer.color = default_colors[index];
}} })}
/> />
</div> </div>
{/each} {/each}
@@ -157,7 +165,7 @@ SPDX-License-Identifier: MPL-2.0
class="p-4 rounded-lg bg-transparent border-gray-500 border-2 hover:bg-gray-300 transition dark:hover:bg-gray-600" class="p-4 rounded-lg bg-transparent border-gray-500 border-2 hover:bg-gray-300 transition dark:hover:bg-gray-600"
type="button" type="button"
in:fade={{ duration: 150 }} in:fade={{ duration: 150 }}
on:click={() => { onclick={() => {
data.questions[selected_question].answers = [ data.questions[selected_question].answers = [
...data.questions[selected_question].answers, ...data.questions[selected_question].answers,
{ ...get_empty_answer(data.questions[selected_question].answers.length) } { ...get_empty_answer(data.questions[selected_question].answers.length) }
@@ -11,10 +11,14 @@ SPDX-License-Identifier: MPL-2.0
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
export let questions: Question[];
export let open: boolean;
export let selected_question: number; interface Props {
questions: Question[];
open: boolean;
selected_question: number;
}
let { questions = $bindable(), open = $bindable(), selected_question = $bindable() }: Props = $props();
const { t } = getLocalization(); const { t } = getLocalization();
onMount(() => { onMount(() => {
@@ -96,7 +100,7 @@ SPDX-License-Identifier: MPL-2.0
<div <div
class="fixed top-0 left-0 w-screen h-screen flex bg-black/50 z-50" class="fixed top-0 left-0 w-screen h-screen flex bg-black/50 z-50"
on:click={on_parent_click} onclick={on_parent_click}
transition:fade={{ duration: 100 }} transition:fade={{ duration: 100 }}
> >
<div <div
@@ -108,7 +112,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="rounded-sm p-6 border-[#B07156] border"> <div class="rounded-sm p-6 border-[#B07156] border">
<button <button
class="text-xl text-black dark:text-white" class="text-xl text-black dark:text-white"
on:click={() => { onclick={() => {
add_question(i); add_question(i);
}}>{qt.name}</button }}>{qt.name}</button
> >
+26 -14
View File
@@ -5,19 +5,31 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { thumbHashToDataURL } from 'thumbhash'; import { thumbHashToDataURL } from 'thumbhash';
export let src: string; interface Props {
export let css_classes = 'max-h-64 h-auto w-auto'; src: string;
export let added_thumbhash_classes = 'h-full'; css_classes?: string;
export let muted = true; added_thumbhash_classes?: string;
export let allow_fullscreen = true; muted?: boolean;
let type: 'img' | 'video' | undefined = undefined; allow_fullscreen?: boolean;
}
let img_data; let {
let thumbhash_data: string; src,
css_classes = 'max-h-64 h-auto w-auto',
added_thumbhash_classes = 'h-full',
muted = true,
allow_fullscreen = true
}: Props = $props();
let type: 'img' | 'video' | undefined = $state(undefined);
let img_data = $state();
let thumbhash_data: string = $state();
function base64ToBytes(base64: string): Uint8Array { function base64ToBytes(base64: string): Uint8Array {
const binString = atob(base64); const binString = atob(base64);
@@ -46,13 +58,13 @@ SPDX-License-Identifier: MPL-2.0
const update_url = () => { const update_url = () => {
media = get_media(); media = get_media();
}; };
let media = get_media(); let media = $state(get_media());
$: { run(() => {
src; src;
update_url(); update_url();
} });
let fullscreen_open = false; let fullscreen_open = $state(false);
const open_fullscreen = () => { const open_fullscreen = () => {
if (!allow_fullscreen) { if (!allow_fullscreen) {
@@ -71,7 +83,7 @@ SPDX-License-Identifier: MPL-2.0
src={img_data.data} src={img_data.data}
alt={img_data.alt_text ?? 'Not available'} alt={img_data.alt_text ?? 'Not available'}
class={css_classes} class={css_classes}
on:click={() => open_fullscreen()} onclick={() => open_fullscreen()}
/> />
{:else if type === 'video'} {:else if type === 'video'}
<video <video
@@ -95,7 +107,7 @@ SPDX-License-Identifier: MPL-2.0
<div <div
class="fixed top-0 left-0 z-50 w-screen h-screen bg-black/50 fle p-2" class="fixed top-0 left-0 z-50 w-screen h-screen bg-black/50 fle p-2"
transition:fade|global={{ duration: 80 }} transition:fade|global={{ duration: 80 }}
on:click={() => (fullscreen_open = false)} onclick={() => (fullscreen_open = false)}
> >
<img <img
src={img_data.data} src={img_data.data}
+17 -11
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import type { EditorData, OrderQuizAnswer } from '$lib/quiz_types'; import type { EditorData, OrderQuizAnswer } from '$lib/quiz_types';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
@@ -13,10 +15,14 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
export let selected_question: number; interface Props {
export let data: EditorData; selected_question: number;
data: EditorData;
}
let parent_el: HTMLDivElement; let { selected_question, data = $bindable() }: Props = $props();
let parent_el: HTMLDivElement = $state();
const swapArrayElements = (arr: OrderQuizAnswer[], a: number, b: number): Array<any> => { const swapArrayElements = (arr: OrderQuizAnswer[], a: number, b: number): Array<any> => {
let _arr = [...arr]; let _arr = [...arr];
@@ -60,11 +66,11 @@ SPDX-License-Identifier: MPL-2.0
} }
} }
}; };
$: { run(() => {
set_colors_if_unset(); set_colors_if_unset();
data; data;
selected_question; selected_question;
} });
</script> </script>
<div class="w-full"> <div class="w-full">
@@ -78,7 +84,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition" class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition"
type="button" type="button"
on:click={() => { onclick={() => {
data.questions[selected_question].answers.splice(i, 1); data.questions[selected_question].answers.splice(i, 1);
data.questions[selected_question].answers = data.questions[selected_question].answers =
data.questions[selected_question].answers; data.questions[selected_question].answers;
@@ -101,7 +107,7 @@ SPDX-License-Identifier: MPL-2.0
</button> </button>
<div> <div>
<button <button
on:click={() => { onclick={() => {
move_item(true, i); move_item(true, i);
}} }}
class="disabled:opacity-50 transition" class="disabled:opacity-50 transition"
@@ -126,7 +132,7 @@ SPDX-License-Identifier: MPL-2.0
</svg> </svg>
</button> </button>
<button <button
on:click={() => { onclick={() => {
move_item(false, i); move_item(false, i);
}} }}
class="disabled:opacity-50 transition" class="disabled:opacity-50 transition"
@@ -164,9 +170,9 @@ SPDX-License-Identifier: MPL-2.0
class="rounded-lg p-1 border-black border" class="rounded-lg p-1 border-black border"
type="color" type="color"
bind:value={answer.color} bind:value={answer.color}
on:contextmenu|preventDefault={() => { oncontextmenu={preventDefault(() => {
answer.color = null; answer.color = null;
}} })}
/> />
</div> </div>
{/each} {/each}
@@ -178,7 +184,7 @@ SPDX-License-Identifier: MPL-2.0
class="p-4 rounded-lg bg-transparent border-gray-500 border-2 hover:bg-gray-300 transition dark:hover:bg-gray-600 m-2 w-full" class="p-4 rounded-lg bg-transparent border-gray-500 border-2 hover:bg-gray-300 transition dark:hover:bg-gray-600 m-2 w-full"
type="button" type="button"
in:fade={{ duration: 150 }} in:fade={{ duration: 150 }}
on:click={() => { onclick={() => {
data.questions[selected_question].answers = [ data.questions[selected_question].answers = [
...data.questions[selected_question].answers, ...data.questions[selected_question].answers,
{ {
@@ -5,11 +5,17 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import type { EditorData } from '../quiz_types'; import type { EditorData } from '../quiz_types';
import Spinner from '../Spinner.svelte'; import Spinner from '../Spinner.svelte';
export let selected_question: number; interface Props {
export let data: EditorData; selected_question: number;
data: EditorData;
}
let { selected_question, data = $bindable() }: Props = $props();
let question = data.questions[selected_question]; let question = data.questions[selected_question];
if (question.answers.max === undefined || question.answers.min_correct === undefined) { if (question.answers.max === undefined || question.answers.min_correct === undefined) {
@@ -35,17 +41,25 @@ SPDX-License-Identifier: MPL-2.0
*/ */
let answer = question.answers; let answer = question.answers;
let range_arr = [answer.min_correct, answer.max_correct]; let range_arr = $state([answer.min_correct, answer.max_correct]);
$: data.questions[selected_question].answers.min_correct = range_arr[0]; run(() => {
$: data.questions[selected_question].answers.max_correct = range_arr[1]; data.questions[selected_question].answers.min_correct = range_arr[0];
$: data.questions[selected_question].answers.min = });
run(() => {
data.questions[selected_question].answers.max_correct = range_arr[1];
});
run(() => {
data.questions[selected_question].answers.min =
data.questions[selected_question].answers.min === null data.questions[selected_question].answers.min === null
? 0 ? 0
: data.questions[selected_question].answers.min; : data.questions[selected_question].answers.min;
$: data.questions[selected_question].answers.max = });
run(() => {
data.questions[selected_question].answers.max =
data.questions[selected_question].answers.max === null data.questions[selected_question].answers.max === null
? 0 ? 0
: data.questions[selected_question].answers.max; : data.questions[selected_question].answers.max;
});
function sleep(ms) { function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
@@ -78,8 +92,7 @@ SPDX-License-Identifier: MPL-2.0
{#await sleep(100)} {#await sleep(100)}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then _} {:then _}
<svelte:component <c.default
this={c.default}
bind:values={range_arr} bind:values={range_arr}
bind:min={data.questions[selected_question].answers.min} bind:min={data.questions[selected_question].answers.min}
bind:max={data.questions[selected_question].answers.max} bind:max={data.questions[selected_question].answers.max}
@@ -12,8 +12,12 @@ SPDX-License-Identifier: MPL-2.0
import { TextQuestionSchema } from '$lib/yupSchemas'; import { TextQuestionSchema } from '$lib/yupSchemas';
import { createTippy } from 'svelte-tippy'; import { createTippy } from 'svelte-tippy';
export let selected_question: number; interface Props {
export let data: EditorData; selected_question: number;
data: EditorData;
}
let { selected_question, data = $bindable() }: Props = $props();
const { t } = getLocalization(); const { t } = getLocalization();
@@ -56,7 +60,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition" class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition"
type="button" type="button"
on:click={() => { onclick={() => {
data.questions[selected_question].answers.splice(index, 1); data.questions[selected_question].answers.splice(index, 1);
data.questions[selected_question].answers = data.questions[selected_question].answers =
data.questions[selected_question].answers; data.questions[selected_question].answers;
@@ -85,7 +89,7 @@ SPDX-License-Identifier: MPL-2.0
/> />
<button <button
type="button" type="button"
on:click={() => { onclick={() => {
answer.case_sensitive = !answer.case_sensitive; answer.case_sensitive = !answer.case_sensitive;
}} }}
use:tippy={{ content: 'Case sensitive?', placement: 'top' }} use:tippy={{ content: 'Case sensitive?', placement: 'top' }}
@@ -130,7 +134,7 @@ SPDX-License-Identifier: MPL-2.0
class="p-4 rounded-lg bg-transparent border-gray-500 border-2 hover:bg-gray-300 transition dark:hover:bg-gray-600" class="p-4 rounded-lg bg-transparent border-gray-500 border-2 hover:bg-gray-300 transition dark:hover:bg-gray-600"
type="button" type="button"
in:fade={{ duration: 150 }} in:fade={{ duration: 150 }}
on:click={() => { onclick={() => {
data.questions[selected_question].answers = [ data.questions[selected_question].answers = [
...data.questions[selected_question].answers, ...data.questions[selected_question].answers,
{ ...get_empty_answer() } { ...get_empty_answer() }
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import type { EditorData } from '../quiz_types'; import type { EditorData } from '../quiz_types';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { reach } from 'yup'; import { reach } from 'yup';
@@ -15,8 +17,12 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58']; const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58'];
export let selected_question: number; interface Props {
export let data: EditorData; selected_question: number;
data: EditorData;
}
let { selected_question, data = $bindable() }: Props = $props();
if (!Array.isArray(data.questions[selected_question].answers)) { if (!Array.isArray(data.questions[selected_question].answers)) {
data.questions[selected_question].answers = []; data.questions[selected_question].answers = [];
@@ -35,11 +41,11 @@ SPDX-License-Identifier: MPL-2.0
} }
} }
}; };
$: { run(() => {
set_colors_if_unset(); set_colors_if_unset();
data; data;
selected_question; selected_question;
} });
/*console.log(data.questions[selected_question].answers, 'moIn!', data.questions[selected_question].answers.length); /*console.log(data.questions[selected_question].answers, 'moIn!', data.questions[selected_question].answers.length);
onMount(() => { onMount(() => {
for (let i = 0; i < data.questions[selected_question].answers; i++) { for (let i = 0; i < data.questions[selected_question].answers; i++) {
@@ -64,7 +70,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition" class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition"
type="button" type="button"
on:click={() => { onclick={() => {
data.questions[selected_question].answers.splice(index, 1); data.questions[selected_question].answers.splice(index, 1);
data.questions[selected_question].answers = data.questions[selected_question].answers =
data.questions[selected_question].answers; data.questions[selected_question].answers;
@@ -97,9 +103,9 @@ SPDX-License-Identifier: MPL-2.0
class="rounded-lg p-1 border-black border" class="rounded-lg p-1 border-black border"
type="color" type="color"
bind:value={answer.color} bind:value={answer.color}
on:contextmenu|preventDefault={() => { oncontextmenu={preventDefault(() => {
answer.color = default_colors[index]; answer.color = default_colors[index];
}} })}
/> />
</div> </div>
{/each} {/each}
@@ -109,7 +115,7 @@ SPDX-License-Identifier: MPL-2.0
class="p-4 rounded-lg bg-transparent border-gray-500 border-2 hover:bg-gray-300 transition dark:hover:bg-gray-600" class="p-4 rounded-lg bg-transparent border-gray-500 border-2 hover:bg-gray-300 transition dark:hover:bg-gray-600"
type="button" type="button"
in:fade={{ duration: 150 }} in:fade={{ duration: 150 }}
on:click={() => { onclick={() => {
data.questions[selected_question].answers = [ data.questions[selected_question].answers = [
...data.questions[selected_question].answers, ...data.questions[selected_question].answers,
{ {
+32 -27
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import type { EditorData } from '$lib/quiz_types'; import type { EditorData } from '$lib/quiz_types';
import { QuizQuestionType } from '$lib/quiz_types'; import { QuizQuestionType } from '$lib/quiz_types';
import RangeEditor from '$lib/editor/RangeSelectorEditorPart.svelte'; import RangeEditor from '$lib/editor/RangeSelectorEditorPart.svelte';
@@ -26,14 +28,18 @@ SPDX-License-Identifier: MPL-2.0
placement: 'top' placement: 'top'
}); });
export let data: EditorData; interface Props {
export let selected_question: number; data: EditorData;
export let edit_id: string; selected_question: number;
edit_id: string;
}
let advanced_options_open = false; let { data = $bindable(), selected_question = $bindable(), edit_id = $bindable() }: Props = $props();
let uppyOpen = false; let advanced_options_open = $state(false);
let unique = {};
let uppyOpen = $state(false);
let unique = $state({});
/*eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }]*/ /*eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }]*/
const correctTimeInput = (_) => { const correctTimeInput = (_) => {
@@ -51,21 +57,23 @@ SPDX-License-Identifier: MPL-2.0
const set_unique = () => { const set_unique = () => {
unique = {}; unique = {};
}; };
$: correctTimeInput(data.questions[selected_question].time); run(() => {
$: { correctTimeInput(data.questions[selected_question].time);
});
run(() => {
selected_question; selected_question;
set_unique(); set_unique();
} });
let image_url = ''; let image_url = $state('');
const update_image_url = () => { const update_image_url = () => {
image_url = data.questions[selected_question].image; image_url = data.questions[selected_question].image;
}; };
$: { run(() => {
update_image_url(); update_image_url();
selected_question; selected_question;
data.questions; data.questions;
} });
const type_to_name = { const type_to_name = {
RANGE: $t('words.range'), RANGE: $t('words.range'),
@@ -90,18 +98,18 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex align-middle p-4 gap-3"> <div class="flex align-middle p-4 gap-3">
<span <span
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-red-400 transition" class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-red-400 transition"
/> ></span>
<span <span
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-yellow-400 transition" class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-yellow-400 transition"
/> ></span>
<span <span
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-green-400 transition" class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-green-400 transition"
/> ></span>
<button <button
class="ml-auto" class="ml-auto"
type="button" type="button"
use:tippy={{ content: $t('editor.advanced_settings') }} use:tippy={{ content: $t('editor.advanced_settings') }}
on:click={() => (advanced_options_open = true)} onclick={() => (advanced_options_open = true)}
> >
<svg <svg
class="text-white w-5 h-5" class="text-white w-5 h-5"
@@ -130,7 +138,7 @@ SPDX-License-Identifier: MPL-2.0
{#await import('./slide.svelte')} {#await import('./slide.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<svelte:component this={c.default} bind:data={data.questions[selected_question]} /> <c.default bind:data={data.questions[selected_question]} />
{/await} {/await}
{:else} {:else}
{@const type = data.questions[selected_question].type} {@const type = data.questions[selected_question].type}
@@ -147,8 +155,7 @@ SPDX-License-Identifier: MPL-2.0
'questions[].question' 'questions[].question'
).isValidSync(data.questions[selected_question].question)} ).isValidSync(data.questions[selected_question].question)}
> >
<svelte:component <c.default
this={c.default}
bind:text={data.questions[selected_question].question} bind:text={data.questions[selected_question].question}
/> />
</div> </div>
@@ -161,7 +168,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition" class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition"
type="button" type="button"
on:click={() => { onclick={() => {
data.questions[selected_question].image = null; data.questions[selected_question].image = null;
}} }}
> >
@@ -187,8 +194,7 @@ SPDX-License-Identifier: MPL-2.0
{#await import('$lib/editor/uploader.svelte')} {#await import('$lib/editor/uploader.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<svelte:component <c.default
this={c.default}
bind:modalOpen={uppyOpen} bind:modalOpen={uppyOpen}
bind:edit_id bind:edit_id
bind:data bind:data
@@ -231,8 +237,7 @@ SPDX-License-Identifier: MPL-2.0
{#await import('$lib/editor/ABCDEditorPart.svelte')} {#await import('$lib/editor/ABCDEditorPart.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<svelte:component <c.default
this={c.default}
bind:data bind:data
bind:selected_question bind:selected_question
check_choice={type === QuizQuestionType.CHECK} check_choice={type === QuizQuestionType.CHECK}
@@ -244,19 +249,19 @@ SPDX-License-Identifier: MPL-2.0
{#await import('$lib/editor/VotingEditorPart.svelte')} {#await import('$lib/editor/VotingEditorPart.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<svelte:component this={c.default} bind:data bind:selected_question /> <c.default bind:data bind:selected_question />
{/await} {/await}
{:else if type === QuizQuestionType.TEXT} {:else if type === QuizQuestionType.TEXT}
{#await import('$lib/editor/TextEditorPart.svelte')} {#await import('$lib/editor/TextEditorPart.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<svelte:component this={c.default} bind:data bind:selected_question /> <c.default bind:data bind:selected_question />
{/await} {/await}
{:else if type === QuizQuestionType.ORDER} {:else if type === QuizQuestionType.ORDER}
{#await import('$lib/editor/OrderEditorPart.svelte')} {#await import('$lib/editor/OrderEditorPart.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<svelte:component this={c.default} bind:data bind:selected_question /> <c.default bind:data bind:selected_question />
{/await} {/await}
{/if} {/if}
</div> </div>
+27 -21
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import type { EditorData } from '$lib/quiz_types'; import type { EditorData } from '$lib/quiz_types';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import Spinner from '$lib/Spinner.svelte'; import Spinner from '$lib/Spinner.svelte';
@@ -12,19 +14,25 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
let uppyOpen = false; let uppyOpen = $state(false);
let bg_uppy_open = false; let bg_uppy_open = $state(false);
export let edit_id: string; interface Props {
export let data: EditorData; edit_id: string;
data: EditorData;
}
let custom_bg_color = Boolean(data.background_color); let { edit_id = $bindable(), data = $bindable() }: Props = $props();
let custom_bg_color = $state(Boolean(data.background_color));
const tippy = createTippy({ const tippy = createTippy({
arrow: true, arrow: true,
animation: 'perspective-subtle' animation: 'perspective-subtle'
}); });
$: data.background_color = custom_bg_color ? data.background_color : undefined; run(() => {
data.background_color = custom_bg_color ? data.background_color : undefined;
});
</script> </script>
<div class="w-full h-full pb-20 px-20"> <div class="w-full h-full pb-20 px-20">
@@ -33,13 +41,13 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex align-middle p-4 gap-3"> <div class="flex align-middle p-4 gap-3">
<span <span
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-red-400 transition" class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-red-400 transition"
/> ></span>
<span <span
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-yellow-400 transition" class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-yellow-400 transition"
/> ></span>
<span <span
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-green-400 transition" class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-green-400 transition"
/> ></span>
</div> </div>
</div> </div>
<div <div
@@ -57,7 +65,7 @@ SPDX-License-Identifier: MPL-2.0
{#await import('$lib/inline-editor.svelte')} {#await import('$lib/inline-editor.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<svelte:component this={c.default} bind:text={data.title} /> <c.default bind:text={data.title} />
{/await} {/await}
</div> </div>
<div class="flex justify-center pt-10 w-full max-h-32"> <div class="flex justify-center pt-10 w-full max-h-32">
@@ -66,7 +74,7 @@ SPDX-License-Identifier: MPL-2.0
placeholder="Description" placeholder="Description"
bind:value={data.description} bind:value={data.description}
class="p-3 rounded-lg border-gray-500 border text-center w-1/3 h-20 resize-none dark:bg-gray-500 outline-hidden focus:shadow-2xl transition-all" class="p-3 rounded-lg border-gray-500 border text-center w-1/3 h-20 resize-none dark:bg-gray-500 outline-hidden focus:shadow-2xl transition-all"
/> ></textarea>
</div> </div>
{#if data.cover_image != undefined && data.cover_image !== ''} {#if data.cover_image != undefined && data.cover_image !== ''}
@@ -75,17 +83,16 @@ SPDX-License-Identifier: MPL-2.0
src="/api/v1/storage/download/{data.cover_image}" src="/api/v1/storage/download/{data.cover_image}"
alt="not available" alt="not available"
class="max-h-72 h-auto w-auto" class="max-h-72 h-auto w-auto"
on:contextmenu|preventDefault={() => { oncontextmenu={preventDefault(() => {
data.cover_image = null; data.cover_image = null;
}} })}
/> />
</div> </div>
{:else} {:else}
{#await import('$lib/editor/uploader.svelte')} {#await import('$lib/editor/uploader.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<svelte:component <c.default
this={c.default}
bind:modalOpen={uppyOpen} bind:modalOpen={uppyOpen}
bind:edit_id bind:edit_id
bind:data bind:data
@@ -96,7 +103,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="pt-10 w-full flex justify-center"> <div class="pt-10 w-full flex justify-center">
<button <button
type="button" type="button"
on:click={() => { onclick={() => {
data.public = !data.public; data.public = !data.public;
}} }}
class="text-center w-fit" class="text-center w-fit"
@@ -149,7 +156,7 @@ SPDX-License-Identifier: MPL-2.0
> >
<span <span
class="inline-block w-full h-full bg-[#d6edc9] dark:bg-[#4e6e58]" class="inline-block w-full h-full bg-[#d6edc9] dark:bg-[#4e6e58]"
/> ></span>
</div> </div>
</div> </div>
<div> <div>
@@ -165,7 +172,7 @@ SPDX-License-Identifier: MPL-2.0
/> />
<span <span
class="w-14 h-7 bg-gray-200 peer-focus:outline-hidden peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-800 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600" class="w-14 h-7 bg-gray-200 peer-focus:outline-hidden peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-800 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
/> ></span>
</label> </label>
</div> </div>
<div <div
@@ -188,7 +195,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="w-full flex justify-center -mt-8"> <div class="w-full flex justify-center -mt-8">
{#if data.background_image} {#if data.background_image}
<button <button
on:click={() => { onclick={() => {
data.background_image = undefined; data.background_image = undefined;
}} }}
class="mt-10 bg-red-500 p-2 rounded-lg border-2 border-black transition hover:bg-red-400" class="mt-10 bg-red-500 p-2 rounded-lg border-2 border-black transition hover:bg-red-400"
@@ -200,8 +207,7 @@ SPDX-License-Identifier: MPL-2.0
<Spinner my_20={false} /> <Spinner my_20={false} />
</div> </div>
{:then c} {:then c}
<svelte:component <c.default
this={c.default}
bind:modalOpen={bg_uppy_open} bind:modalOpen={bg_uppy_open}
bind:edit_id bind:edit_id
bind:data bind:data
+19 -15
View File
@@ -17,19 +17,23 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
export let data: EditorData; interface Props {
export let selected_question = -1; data: EditorData;
selected_question?: any;
}
let reorder_mode = false; let { data = $bindable(), selected_question = $bindable(-1) }: Props = $props();
let reorder_mode = $state(false);
const tippy = createTippy({ const tippy = createTippy({
arrow: true, arrow: true,
animation: 'perspective-subtle', animation: 'perspective-subtle',
placement: 'right' placement: 'right'
}); });
let arr_of_cards = Array(data.questions.length); let arr_of_cards = $state(Array(data.questions.length));
let propertyCard; let propertyCard = $state();
let add_new_question_popup_open = false; let add_new_question_popup_open = $state(false);
const empy_slide: Question = { const empy_slide: Question = {
type: QuizQuestionType.SLIDE, type: QuizQuestionType.SLIDE,
@@ -85,7 +89,7 @@ SPDX-License-Identifier: MPL-2.0
class="bg-white shadow-smrounded-lg h-40 p-2 mb-6 hover:cursor-pointer drop-shadow-2xl border border-gray-500 dark:bg-gray-600 transition" class="bg-white shadow-smrounded-lg h-40 p-2 mb-6 hover:cursor-pointer drop-shadow-2xl border border-gray-500 dark:bg-gray-600 transition"
class:bg-green-300={selected_question === -1} class:bg-green-300={selected_question === -1}
class:dark:bg-green-500={selected_question === -1} class:dark:bg-green-500={selected_question === -1}
on:click={() => setSelectedQuestion(-1)} onclick={() => setSelectedQuestion(-1)}
> >
<div <div
use:tippy={{ content: data.title === '' ? "It's empty!" : data.title }} use:tippy={{ content: data.title === '' ? "It's empty!" : data.title }}
@@ -119,7 +123,7 @@ SPDX-License-Identifier: MPL-2.0
bind:value={data.description} bind:value={data.description}
class="bg-transparent resize-none w-full rounded-sm text-sm dark:text-white" class="bg-transparent resize-none w-full rounded-sm text-sm dark:text-white"
class:dark:text-black={selected_question === -1} class:dark:text-black={selected_question === -1}
/> ></textarea>
</div> </div>
<div <div
class="w-full flex justify-center dark:text-white" class="w-full flex justify-center dark:text-white"
@@ -127,7 +131,7 @@ SPDX-License-Identifier: MPL-2.0
> >
<button <button
type="button" type="button"
on:click={() => { onclick={() => {
data.public = !data.public; data.public = !data.public;
}} }}
class="text-center" class="text-center"
@@ -173,7 +177,7 @@ SPDX-License-Identifier: MPL-2.0
class="bg-white shadow-smrounded-lg h-40 p-2 mb-6 hover:cursor-pointer drop-shadow-2xl border border-gray-500 dark:bg-gray-600 transition relative" class="bg-white shadow-smrounded-lg h-40 p-2 mb-6 hover:cursor-pointer drop-shadow-2xl border border-gray-500 dark:bg-gray-600 transition relative"
class:bg-green-300={index === selected_question} class:bg-green-300={index === selected_question}
class:dark:bg-green-500={index === selected_question} class:dark:bg-green-500={index === selected_question}
on:click={() => { onclick={() => {
setSelectedQuestion(index); setSelectedQuestion(index);
}} }}
bind:this={arr_of_cards[index]} bind:this={arr_of_cards[index]}
@@ -190,7 +194,7 @@ SPDX-License-Identifier: MPL-2.0
aria-label="Move card up" aria-label="Move card up"
class:opacity-50={index === 0} class:opacity-50={index === 0}
class:pointer-events-none={index === 0} class:pointer-events-none={index === 0}
on:click={() => onclick={() =>
(data.questions = swapArrayElements( (data.questions = swapArrayElements(
data.questions, data.questions,
index, index,
@@ -219,7 +223,7 @@ SPDX-License-Identifier: MPL-2.0
aria-label="Move card down" aria-label="Move card down"
class:opacity-50={index + 1 === data.questions.length} class:opacity-50={index + 1 === data.questions.length}
class:pointer-events-none={index + 1 === data.questions.length} class:pointer-events-none={index + 1 === data.questions.length}
on:click={() => onclick={() =>
(data.questions = swapArrayElements( (data.questions = swapArrayElements(
data.questions, data.questions,
index, index,
@@ -248,7 +252,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="rounded-full absolute -top-3 -right-3 opacity-70 hover:opacity-100 transition" class="rounded-full absolute -top-3 -right-3 opacity-70 hover:opacity-100 transition"
type="button" type="button"
on:click={() => { onclick={() => {
if (confirm('Do you really want to delete this Question?')) { if (confirm('Do you really want to delete this Question?')) {
selected_question = -1; selected_question = -1;
data.questions.splice(index, 1); data.questions.splice(index, 1);
@@ -380,7 +384,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
type="button" type="button"
class="h-full flex justify-center w-full flex-col border-r border-black dark:text-white" class="h-full flex justify-center w-full flex-col border-r border-black dark:text-white"
on:click={() => { onclick={() => {
add_new_question_popup_open = true; add_new_question_popup_open = true;
}} }}
> >
@@ -403,7 +407,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
type="button" type="button"
class="h-full flex justify-center w-full dark:text-white flex-col" class="h-full flex justify-center w-full dark:text-white flex-col"
on:click={() => { onclick={() => {
data.questions = [...data.questions, { ...empy_slide }]; data.questions = [...data.questions, { ...empy_slide }];
}} }}
> >
+20 -14
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import type { Question } from '$lib/quiz_types'; import type { Question } from '$lib/quiz_types';
import { ElementTypes, QuizQuestionType } from '$lib/quiz_types'; import { ElementTypes, QuizQuestionType } from '$lib/quiz_types';
import ElementSelection from './slides/element_selection.svelte'; import ElementSelection from './slides/element_selection.svelte';
@@ -15,21 +17,25 @@ SPDX-License-Identifier: MPL-2.0
import type { Konva, ShapeModel } from 'pikaso'; import type { Konva, ShapeModel } from 'pikaso';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
export let data: Question = { interface Props {
data?: Question;
}
let { data = $bindable({
type: QuizQuestionType.SLIDE, type: QuizQuestionType.SLIDE,
time: '120', time: '120',
question: '', question: '',
image: undefined, image: undefined,
answers: '' answers: ''
}; }) }: Props = $props();
let selected_element = undefined; let selected_element = $state(undefined);
let canvas_el: HTMLDivElement | undefined; let canvas_el: HTMLDivElement | undefined = $state();
let canvas: Pikaso; let canvas: Pikaso;
let selected_el: null | ShapeModel<Konva.Shape | Konva.Group, Konva.ShapeConfig> = null; let selected_el: null | ShapeModel<Konva.Shape | Konva.Group, Konva.ShapeConfig> = $state(null);
let elements_binds: Array<HTMLElement> | undefined = []; let elements_binds: Array<HTMLElement> | undefined = [];
let main_el: undefined | HTMLElement; let main_el: undefined | HTMLElement = $state();
let settings_menu_open = false; let settings_menu_open = $state(false);
const set_correct_height = new ResizeObserver((e) => { const set_correct_height = new ResizeObserver((e) => {
for (const i of e) { for (const i of e) {
@@ -87,11 +93,11 @@ SPDX-License-Identifier: MPL-2.0
} }
}; };
$: { run(() => {
if (selected_element) { if (selected_element) {
add_text_field(); add_text_field();
} }
} });
const assign_resize_handlers = () => { const assign_resize_handlers = () => {
for (let i = 0; i < elements_binds.length; i++) { for (let i = 0; i < elements_binds.length; i++) {
set_correct_height.observe(elements_binds[i]); set_correct_height.observe(elements_binds[i]);
@@ -105,10 +111,10 @@ SPDX-License-Identifier: MPL-2.0
} }
}*/ }*/
$: { run(() => {
elements_binds; elements_binds;
assign_resize_handlers(); assign_resize_handlers();
} });
onMount(() => { onMount(() => {
/* setTimeout(() => { /* setTimeout(() => {
@@ -172,7 +178,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex flex-col pl-2 rounded-t-lg z-40 pt-2"> <div class="flex flex-col pl-2 rounded-t-lg z-40 pt-2">
<button <button
class="mr-auto" class="mr-auto"
on:click={() => { onclick={() => {
settings_menu_open = !settings_menu_open; settings_menu_open = !settings_menu_open;
}} }}
type="button" type="button"
@@ -212,7 +218,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex flex-col pr-2 rounded-t-lg z-40 pt-2"> <div class="flex flex-col pr-2 rounded-t-lg z-40 pt-2">
<button <button
class="ml-auto" class="ml-auto"
on:click={() => { onclick={() => {
selected_element = selected_element === null ? undefined : null; selected_element = selected_element === null ? undefined : null;
}} }}
type="button" type="button"
@@ -240,5 +246,5 @@ SPDX-License-Identifier: MPL-2.0
{/if} {/if}
</div> </div>
</div> </div>
<div bind:this={canvas_el} class="w-full h-full block" /> <div bind:this={canvas_el} class="w-full h-full block"></div>
</div> </div>
+20 -12
View File
@@ -5,14 +5,20 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import type { Konva, ShapeModel } from 'pikaso'; import type { Konva, ShapeModel } from 'pikaso';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
export let selected_el: null | ShapeModel<Konva.Shape | Konva.Group, Konva.ShapeConfig>; interface Props {
selected_el: null | ShapeModel<Konva.Shape | Konva.Group, Konva.ShapeConfig>;
}
let opened_dropdown = null; let { selected_el }: Props = $props();
let available_modifiers: Array<string> = []; let opened_dropdown = $state(null);
let available_modifiers: Array<string> = $state([]);
const set_available_modifiers = () => { const set_available_modifiers = () => {
available_modifiers = []; available_modifiers = [];
@@ -32,10 +38,10 @@ SPDX-License-Identifier: MPL-2.0
} }
}; };
$: { run(() => {
selected_el; selected_el;
set_available_modifiers(); set_available_modifiers();
} });
const toggle_switch = (el: string) => { const toggle_switch = (el: string) => {
if (opened_dropdown) { if (opened_dropdown) {
@@ -62,9 +68,11 @@ SPDX-License-Identifier: MPL-2.0
}); });
}; };
$: if (!selected_el) { run(() => {
if (!selected_el) {
opened_dropdown = null; opened_dropdown = null;
} }
});
</script> </script>
<div class="flex flex-row justify-evenly z-40"> <div class="flex flex-row justify-evenly z-40">
@@ -76,7 +84,7 @@ SPDX-License-Identifier: MPL-2.0
available_modifiers.includes('fill_color') || available_modifiers.includes('fill_color') ||
available_modifiers.includes('text_color') available_modifiers.includes('text_color')
)} )}
on:click={() => { onclick={() => {
toggle_switch('color'); toggle_switch('color');
}} }}
> >
@@ -103,7 +111,7 @@ SPDX-License-Identifier: MPL-2.0
class="bg-white m-auto rounded-lg shadow-lg p-4 dark:bg-gray-600 h-fit gap-2 w-fit auto-cols-min flex absolute z-40" class="bg-white m-auto rounded-lg shadow-lg p-4 dark:bg-gray-600 h-fit gap-2 w-fit auto-cols-min flex absolute z-40"
transition:fade|global={{ duration: 100 }} transition:fade|global={{ duration: 100 }}
> >
<input type="color" on:change={change_color} /> <input type="color" onchange={change_color} />
</div> </div>
{/if} {/if}
</div> </div>
@@ -111,7 +119,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
type="button" type="button"
class="disabled:opacity-50 transition" class="disabled:opacity-50 transition"
on:click={() => { onclick={() => {
toggle_switch('font_size'); toggle_switch('font_size');
}} }}
disabled={!available_modifiers.includes('font_size')} disabled={!available_modifiers.includes('font_size')}
@@ -141,7 +149,7 @@ SPDX-License-Identifier: MPL-2.0
> >
<input <input
type="range" type="range"
on:change={change_fontsize} onchange={change_fontsize}
value={selected_el?.node?.children?.[1]?.attrs?.fontSize} value={selected_el?.node?.children?.[1]?.attrs?.fontSize}
min="10" min="10"
max="250" max="250"
@@ -153,7 +161,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
type="button" type="button"
class="disabled:opacity-50 transition" class="disabled:opacity-50 transition"
on:click={() => { onclick={() => {
selected_el.update({ zIndex: selected_el.node.getZIndex() + 1 }); selected_el.update({ zIndex: selected_el.node.getZIndex() + 1 });
}} }}
disabled={!selected_el} disabled={!selected_el}
@@ -179,7 +187,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
type="button" type="button"
class="disabled:opacity-50 transition" class="disabled:opacity-50 transition"
on:click={() => { onclick={() => {
selected_el.update({ zIndex: selected_el.node.getZIndex() - 1 }); selected_el.update({ zIndex: selected_el.node.getZIndex() - 1 });
}} }}
disabled={!selected_el} disabled={!selected_el}
@@ -11,7 +11,7 @@ SPDX-License-Identifier: MPL-2.0
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let selected_element; let { selected_element = $bindable() } = $props();
const keybinding_list = { const keybinding_list = {
t: ElementTypes.Text, t: ElementTypes.Text,
h: ElementTypes.Headline, h: ElementTypes.Headline,
@@ -81,7 +81,7 @@ SPDX-License-Identifier: MPL-2.0
{#each element_list as el} {#each element_list as el}
<li <li
class="flex flex-row mt-4 w-full bg-gray-200 shadow-xl rounded-lg p-2 hover:bg-gray-300 hover:shadow-2xl hover:cursor-pointer transition dark:bg-gray-800" class="flex flex-row mt-4 w-full bg-gray-200 shadow-xl rounded-lg p-2 hover:bg-gray-300 hover:shadow-2xl hover:cursor-pointer transition dark:bg-gray-800"
on:click={() => { onclick={() => {
selected_element = el.type; selected_element = el.type;
}} }}
> >
@@ -5,13 +5,16 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
export let title; let { title = $bindable(), time = $bindable() } = $props();
export let time; let time_local = $state(120);
let time_local = 120;
/*eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }]*/ /*eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }]*/
$: time = String(time_local); run(() => {
time = String(time_local);
});
if (time) { if (time) {
time_local = parseInt(time); time_local = parseInt(time);
} }
@@ -5,8 +5,12 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let data: string; interface Props {
export let editor = true; data: string;
editor?: boolean;
}
let { data = $bindable(), editor = true }: Props = $props();
if (!data) { if (!data) {
data = '#ed333b'; data = '#ed333b';
@@ -5,8 +5,12 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let data; interface Props {
export let editor = true; data: any;
editor?: boolean;
}
let { data = $bindable(), editor = true }: Props = $props();
</script> </script>
{#if editor} {#if editor}
@@ -5,8 +5,12 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let data: string; interface Props {
export let editor = true; data: string;
editor?: boolean;
}
let { data = $bindable(), editor = true }: Props = $props();
if (!data) { if (!data) {
data = '#ed333b'; data = '#ed333b';
@@ -5,12 +5,16 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let data; interface Props {
export let editor = true; data: any;
editor?: boolean;
}
let { data = $bindable(), editor = true }: Props = $props();
</script> </script>
{#if editor} {#if editor}
<textarea bind:value={data} class="w-full h-full dark:text-black resize-none" /> <textarea bind:value={data} class="w-full h-full dark:text-black resize-none"></textarea>
{:else} {:else}
<p>{data}</p> <p>{data}</p>
{/if} {/if}
+21 -14
View File
@@ -3,7 +3,6 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0 SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { Dashboard as SvelteDashboard } from '@uppy/svelte'; import { Dashboard as SvelteDashboard } from '@uppy/svelte';
import Uppy from '@uppy/core'; import Uppy from '@uppy/core';
@@ -27,18 +26,26 @@ SPDX-License-Identifier: MPL-2.0
import Pixabay from '$lib/editor/uploader/Pixabay.svelte'; import Pixabay from '$lib/editor/uploader/Pixabay.svelte';
const { t } = getLocalization(); const { t } = getLocalization();
let {
export let modalOpen = false; modalOpen = false,
export let edit_id: string; edit_id,
export let data: EditorData; data,
export let selected_question: number; selected_question,
export let video_upload = false; video_upload = false,
export let library_enabled = true; library_enabled = true
}: {
modalOpen: boolean;
edit_id: string;
data: EditorData;
selected_question: number;
video_upload: boolean;
library_enabled: boolean;
} = $props();
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
let video_popup: undefined | WindowProxy = undefined; let video_popup: undefined | WindowProxy = $state(undefined);
let selected_type: AvailableUploadTypes | null = null; let selected_type: AvailableUploadTypes | null = $state(null);
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
enum AvailableUploadTypes { enum AvailableUploadTypes {
@@ -67,7 +74,7 @@ SPDX-License-Identifier: MPL-2.0
.use(XHRUpload, { .use(XHRUpload, {
endpoint: `/api/v1/storage/` endpoint: `/api/v1/storage/`
}); });
const props = { const properties = {
inline: true, inline: true,
restrictions: { restrictions: {
maxFileSize: 10_490_000, maxFileSize: 10_490_000,
@@ -134,7 +141,7 @@ SPDX-License-Identifier: MPL-2.0
{#if modalOpen} {#if modalOpen}
<div <div
class="w-screen h-screen fixed top-0 left-0 bg-black/50 z-20 flex justify-center" class="w-screen h-screen fixed top-0 left-0 bg-black/50 z-20 flex justify-center"
on:click={handle_on_click} onclick={handle_on_click}
transition:fade={{ duration: 100 }} transition:fade={{ duration: 100 }}
> >
{#if selected_type === null} {#if selected_type === null}
@@ -181,7 +188,7 @@ SPDX-License-Identifier: MPL-2.0
{:else if selected_type === AvailableUploadTypes.Image} {:else if selected_type === AvailableUploadTypes.Image}
<div class="m-auto w-1/3 h-5/6" transition:fade={{ duration: 100 }}> <div class="m-auto w-1/3 h-5/6" transition:fade={{ duration: 100 }}>
<div> <div>
<SvelteDashboard {uppy} width="100%" {props} /> <SvelteDashboard {uppy} width="100%" {properties} />
</div> </div>
</div> </div>
{:else if selected_type === AvailableUploadTypes.Video} {:else if selected_type === AvailableUploadTypes.Video}
@@ -215,7 +222,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="rounded-lg p-4 flex justify-center bg-transparent border-gray-500 border-2 w-1/2 hover:bg-gray-300 dark:hover:bg-gray-600 transition" class="rounded-lg p-4 flex justify-center bg-transparent border-gray-500 border-2 w-1/2 hover:bg-gray-300 dark:hover:bg-gray-600 transition"
type="button" type="button"
on:click={() => { onclick={() => {
modalOpen = true; modalOpen = true;
}} }}
><span class="italic">{$t('uploader.add_image')}</span> ><span class="italic">{$t('uploader.add_image')}</span>
@@ -11,9 +11,13 @@ SPDX-License-Identifier: MPL-2.0
import BrownButton from '$lib/components/buttons/brown.svelte'; import BrownButton from '$lib/components/buttons/brown.svelte';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
export let data: EditorData; interface Props {
export let selected_question: number; data: EditorData;
export let modalOpen: boolean; selected_question: number;
modalOpen: boolean;
}
let { data = $bindable(), selected_question, modalOpen = $bindable() }: Props = $props();
const { t } = getLocalization(); const { t } = getLocalization();
@@ -5,18 +5,24 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { preventDefault } from 'svelte/legacy';
import type { EditorData } from '$lib/quiz_types'; import type { EditorData } from '$lib/quiz_types';
import Spinner from '$lib/Spinner.svelte'; import Spinner from '$lib/Spinner.svelte';
import BrownButton from '$lib/components/buttons/brown.svelte'; import BrownButton from '$lib/components/buttons/brown.svelte';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
export let data: EditorData; interface Props {
export let selected_question: number; data: EditorData;
export let modalOpen: boolean; selected_question: number;
modalOpen: boolean;
}
let page = 1; let { data = $bindable(), selected_question, modalOpen = $bindable() }: Props = $props();
let search_term = '';
let loading = false; let page = $state(1);
let search_term = $state('');
let loading = $state(false);
const { t } = getLocalization(); const { t } = getLocalization();
@@ -42,7 +48,7 @@ SPDX-License-Identifier: MPL-2.0
return await res.json(); return await res.json();
}; };
let fetched_data = fetch_data(); let fetched_data = $state(fetch_data());
</script> </script>
{#await fetched_data} {#await fetched_data}
@@ -64,7 +70,7 @@ SPDX-License-Identifier: MPL-2.0
<form <form
class="w-full flex gap-2" class="w-full flex gap-2"
on:submit|preventDefault={() => (fetched_data = fetch_data())} onsubmit={preventDefault(() => (fetched_data = fetch_data()))}
> >
<input <input
class="w-full outline-hidden p-1 rounded-sm dark:bg-gray-500 bg-gray-300" class="w-full outline-hidden p-1 rounded-sm dark:bg-gray-500 bg-gray-300"
+5 -1
View File
@@ -7,7 +7,8 @@ SPDX-License-Identifier: MPL-2.0
<script lang="ts"> <script lang="ts">
import Spinner from '$lib/Spinner.svelte'; import Spinner from '$lib/Spinner.svelte';
export let files: { interface Props {
files: {
id: string; id: string;
uploaded_at: string; uploaded_at: string;
mime_type?: string; mime_type?: string;
@@ -21,6 +22,9 @@ SPDX-License-Identifier: MPL-2.0
quizzes: { id: string }[]; quizzes: { id: string }[];
quiztivities: { id: string }[]; quiztivities: { id: string }[];
}[]; }[];
}
let { files = $bindable() }: Props = $props();
const get_files = async () => { const get_files = async () => {
const res = await fetch('/api/v1/storage/list'); const res = await fetch('/api/v1/storage/list');
+14 -5
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { import {
BalloonEditor, BalloonEditor,
Essentials, Essentials,
@@ -24,19 +26,26 @@ SPDX-License-Identifier: MPL-2.0
// import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough'; // import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough';
// import Subscript from '@ckeditor/ckeditor5-basic-styles/src/subscript.js'; // import Subscript from '@ckeditor/ckeditor5-basic-styles/src/subscript.js';
// import Superscript from '@ckeditor/ckeditor5-basic-styles/src/superscript.js'; // import Superscript from '@ckeditor/ckeditor5-basic-styles/src/superscript.js';
// import Autoformat from "@ckeditor/ckeditor5-autoformat/src/autoformat"
export let text = '';
const triggerChange = () => { const triggerChange = () => {
text = editor.getData(); text = editor.getData();
}; };
import { onMount } from 'svelte'; import { onMount } from 'svelte';
interface Props {
// import Autoformat from "@ckeditor/ckeditor5-autoformat/src/autoformat"
text?: string;
}
let html_el; let { text = $bindable('') }: Props = $props();
$: text = text.replace('<p>', '').replace('</p>', ''); let html_el = $state();
run(() => {
text = text.replace('<p>', '').replace('</p>', '');
});
/* Editor.builtinPlugins = [ /* Editor.builtinPlugins = [
Autoformat, Autoformat,
@@ -120,7 +129,7 @@ SPDX-License-Identifier: MPL-2.0
bind:this={html_el} bind:this={html_el}
contenteditable="true" contenteditable="true"
class="rounded-lg border-gray-500 border text-center w-fit h-fit resize-none dark:bg-gray-500 min-w-[5rem] dark:text-white" class="rounded-lg border-gray-500 border text-center w-fit h-fit resize-none dark:bg-gray-500 min-w-[5rem] dark:text-white"
/> ></div>
</div> </div>
<style> <style>
@@ -5,8 +5,10 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let text; import { run } from 'svelte/legacy';
let internal_text = '';
let { text } = $props();
let internal_text = $state('');
/* /*
const markSelection = (function() { const markSelection = (function() {
const markerTextChar = '\ufeff'; const markerTextChar = '\ufeff';
@@ -92,10 +94,10 @@ SPDX-License-Identifier: MPL-2.0
console.log(output); console.log(output);
}; };
$: { run(() => {
process_input(); process_input();
internal_text; internal_text;
} });
</script> </script>
<input bind:value={internal_text} /> <input bind:value={internal_text} />
@@ -20,7 +20,7 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
let teacherTab = false; let teacherTab = $state(false);
</script> </script>
<div class="grid grid-cols-1 overflow-hidden h-fit"> <div class="grid grid-cols-1 overflow-hidden h-fit">
@@ -103,7 +103,7 @@ SPDX-License-Identifier: MPL-2.0
{#if teacherTab} {#if teacherTab}
<button <button
class="border border-blue-600 p-2 border-2 rounded-sm hover:bg-gray-700 text-2xl" class="border border-blue-600 p-2 border-2 rounded-sm hover:bg-gray-700 text-2xl"
on:click={() => { onclick={() => {
teacherTab = true; teacherTab = true;
}} }}
>{$t('index_page.teachers_site')} >{$t('index_page.teachers_site')}
@@ -113,7 +113,7 @@ SPDX-License-Identifier: MPL-2.0
class="border border-black p-2 border-2 rounded-sm hover:bg-gray-700 text-base" class="border border-black p-2 border-2 rounded-sm hover:bg-gray-700 text-base"
class:border-black={!teacherTab} class:border-black={!teacherTab}
class:text-base={!teacherTab} class:text-base={!teacherTab}
on:click={() => { onclick={() => {
teacherTab = true; teacherTab = true;
}} }}
>{$t('index_page.teachers_site')} >{$t('index_page.teachers_site')}
@@ -123,7 +123,7 @@ SPDX-License-Identifier: MPL-2.0
class="border border-black p-2 border-2 rounded-sm hover:bg-gray-700" class="border border-black p-2 border-2 rounded-sm hover:bg-gray-700"
class:border-blue-600={!teacherTab} class:border-blue-600={!teacherTab}
class:text-2xl={!teacherTab} class:text-2xl={!teacherTab}
on:click={() => { onclick={() => {
teacherTab = false; teacherTab = false;
}} }}
>{$t('index_page.students_site')} >{$t('index_page.students_site')}
+6 -2
View File
@@ -5,7 +5,11 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let open: boolean; interface Props {
open: boolean;
}
let { open = $bindable() }: Props = $props();
const closeThing = () => { const closeThing = () => {
open = false; open = false;
@@ -15,7 +19,7 @@ SPDX-License-Identifier: MPL-2.0
<form method="post" action="https://newsletter.mawoka.eu/subscription/form" class="bg-transparent"> <form method="post" action="https://newsletter.mawoka.eu/subscription/form" class="bg-transparent">
<div class="absolute top-1 right-1"> <div class="absolute top-1 right-1">
<button type="button" on:click={closeThing}> <button type="button" onclick={closeThing}>
<svg <svg
class="w-6 h-6" class="w-6 h-6"
fill="none" fill="none"
+9 -5
View File
@@ -54,11 +54,15 @@ SPDX-License-Identifier: MPL-2.0
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
export let languages: Array<{ interface Props {
languages?: Array<{
flag: string; flag: string;
name: string; name: string;
code: string; code: string;
}> = [ }>;
}
let { languages = [
{ {
code: 'de', code: 'de',
name: 'Deutsch', name: 'Deutsch',
@@ -139,11 +143,11 @@ SPDX-License-Identifier: MPL-2.0
name: 'tiếng Việt', name: 'tiếng Việt',
flag: '🇻🇳' flag: '🇻🇳'
} }
]; ] }: Props = $props();
const get_selected_language = (): string => { const get_selected_language = (): string => {
return localStorage.getItem('language'); return localStorage.getItem('language');
}; };
let selected_language; let selected_language = $state();
onMount(() => { onMount(() => {
selected_language = get_selected_language(); selected_language = get_selected_language();
}); });
@@ -159,7 +163,7 @@ SPDX-License-Identifier: MPL-2.0
<div> <div>
<select <select
bind:value={selected_language} bind:value={selected_language}
on:change={() => { onchange={() => {
set_language(selected_language); set_language(selected_language);
}} }}
class="p-2 rounded-lg bg-gray-800 focus:ring-2 ring-blue-600 text-white" class="p-2 rounded-lg bg-gray-800 focus:ring-2 ring-blue-600 text-white"
+9 -5
View File
@@ -8,9 +8,13 @@ SPDX-License-Identifier: MPL-2.0
By Flowbite, but changed: https://flowbite.com/docs/components/modal/#default-modal By Flowbite, but changed: https://flowbite.com/docs/components/modal/#default-modal
--> -->
<script lang="ts"> <script lang="ts">
export let title: string; interface Props {
export let body: string; title: string;
export let open: boolean; body: string;
open: boolean;
}
let { title, body, open = $bindable() }: Props = $props();
console.log(open, title, body); console.log(open, title, body);
open = true; open = true;
</script> </script>
@@ -31,7 +35,7 @@ By Flowbite, but changed: https://flowbite.com/docs/components/modal/#default-mo
<button <button
type="button" type="button"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"
on:click={() => { onclick={() => {
open = false; open = false;
}} }}
> >
@@ -62,7 +66,7 @@ By Flowbite, but changed: https://flowbite.com/docs/components/modal/#default-mo
<button <button
data-modal-toggle="defaultModal" data-modal-toggle="defaultModal"
type="button" type="button"
on:click={() => { onclick={() => {
open = false; open = false;
}} }}
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-hidden focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-hidden focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
+8 -8
View File
@@ -22,7 +22,7 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
let menuIsClosed = true; let menuIsClosed = $state(true);
const toggleMenu = () => { const toggleMenu = () => {
menuIsClosed = !menuIsClosed; menuIsClosed = !menuIsClosed;
}; };
@@ -31,7 +31,7 @@ SPDX-License-Identifier: MPL-2.0
menuIsClosed = true; // Closes menu to let the user see the page beneath menuIsClosed = true; // Closes menu to let the user see the page beneath
}); });
let darkMode = false; let darkMode = $state(false);
if (browser) { if (browser) {
darkMode = darkMode =
localStorage.theme === 'dark' || localStorage.theme === 'dark' ||
@@ -108,7 +108,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="lg:flex items-center justify-center"> <div class="lg:flex items-center justify-center">
{#if darkMode} {#if darkMode}
<button <button
on:click={() => { onclick={() => {
switchDarkMode(); switchDarkMode();
}} }}
use:tippy={{ content: 'Switch light mode on' }} use:tippy={{ content: 'Switch light mode on' }}
@@ -134,7 +134,7 @@ SPDX-License-Identifier: MPL-2.0
</button> </button>
{:else} {:else}
<button <button
on:click={() => { onclick={() => {
switchDarkMode(); switchDarkMode();
}} }}
aria-label="Activate darkmode" aria-label="Activate darkmode"
@@ -180,7 +180,7 @@ SPDX-License-Identifier: MPL-2.0
<!-- Sun icon --> <!-- Sun icon -->
<button <button
class="px-3" class="px-3"
on:click={() => { onclick={() => {
switchDarkMode(); switchDarkMode();
}} }}
use:tippy={{ content: 'Switch light mode on' }} use:tippy={{ content: 'Switch light mode on' }}
@@ -208,7 +208,7 @@ SPDX-License-Identifier: MPL-2.0
<!-- Moon icon --> <!-- Moon icon -->
<button <button
class="px-3" class="px-3"
on:click={() => { onclick={() => {
switchDarkMode(); switchDarkMode();
}} }}
aria-label="Activate darkmode" aria-label="Activate darkmode"
@@ -237,7 +237,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="px-3" class="px-3"
id="open-menu" id="open-menu"
on:click={toggleMenu} onclick={toggleMenu}
aria-label="Open navbar" aria-label="Open navbar"
> >
<svg <svg
@@ -258,7 +258,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="px-3" class="px-3"
id="close-menu" id="close-menu"
on:click={toggleMenu} onclick={toggleMenu}
aria-label="Close navbar" aria-label="Close navbar"
> >
<svg <svg
+30 -16
View File
@@ -10,18 +10,32 @@ SPDX-License-Identifier: MPL-2.0
import type { Socket } from 'socket.io-client'; import type { Socket } from 'socket.io-client';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
export let bg_color: string;
export let selected_question: number;
export let quiz_data: QuizData;
export let timer_res: string;
export let final_results;
export let socket: Socket;
export let game_token: string;
export let question_results;
export let shown_question_now: number; interface Props {
bg_color: string;
selected_question: number;
quiz_data: QuizData;
timer_res: string;
final_results: any;
socket: Socket;
game_token: string;
question_results: any;
shown_question_now: number;
}
let {
bg_color,
selected_question,
quiz_data,
timer_res = $bindable(),
final_results,
socket,
game_token,
question_results,
shown_question_now
}: Props = $props();
const { t } = getLocalization(); const { t } = getLocalization();
const set_question_number = (q_number: number) => { const set_question_number = (q_number: number) => {
@@ -56,14 +70,14 @@ SPDX-License-Identifier: MPL-2.0
<div class="justify-self-end ml-auto mr-0 col-start-3 col-end-3"> <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 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])} {#if JSON.stringify(final_results) === JSON.stringify([null])}
<button on:click={get_final_results} class="admin-button" <button onclick={get_final_results} class="admin-button"
>{$t('admin_page.get_final_results')} >{$t('admin_page.get_final_results')}
</button> </button>
{/if} {/if}
{:else if timer_res === '0' || selected_question === -1} {:else if timer_res === '0' || selected_question === -1}
{#if (selected_question + 1 !== quiz_data.questions.length && question_results !== null) || selected_question === -1} {#if (selected_question + 1 !== quiz_data.questions.length && question_results !== null) || selected_question === -1}
<button <button
on:click={() => { onclick={() => {
set_question_number(selected_question + 1); set_question_number(selected_question + 1);
}} }}
class="admin-button" class="admin-button"
@@ -73,7 +87,7 @@ SPDX-License-Identifier: MPL-2.0
{#if question_results === null && selected_question !== -1} {#if question_results === null && selected_question !== -1}
{#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE} {#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE}
<button <button
on:click={() => { onclick={() => {
set_question_number(selected_question + 1); set_question_number(selected_question + 1);
}} }}
class="admin-button" class="admin-button"
@@ -81,7 +95,7 @@ SPDX-License-Identifier: MPL-2.0
</button> </button>
{:else if quiz_data.questions[selected_question]?.hide_results === true} {:else if quiz_data.questions[selected_question]?.hide_results === true}
<button <button
on:click={() => { onclick={() => {
get_question_results(); get_question_results();
setTimeout(() => { setTimeout(() => {
set_question_number(selected_question + 1); set_question_number(selected_question + 1);
@@ -91,7 +105,7 @@ SPDX-License-Identifier: MPL-2.0
>{$t('admin_page.next_question', { question: selected_question + 2 })} >{$t('admin_page.next_question', { question: selected_question + 2 })}
</button> </button>
{:else} {:else}
<button on:click={get_question_results} class="admin-button" <button onclick={get_question_results} class="admin-button"
>{$t('admin_page.show_results')} >{$t('admin_page.show_results')}
</button> </button>
{/if} {/if}
@@ -99,14 +113,14 @@ SPDX-License-Identifier: MPL-2.0
{:else if selected_question !== -1} {:else if selected_question !== -1}
{#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE} {#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE}
<button <button
on:click={() => { onclick={() => {
set_question_number(selected_question + 1); set_question_number(selected_question + 1);
}} }}
class="admin-button" class="admin-button"
>{$t('admin_page.next_question', { question: selected_question + 2 })} >{$t('admin_page.next_question', { question: selected_question + 2 })}
</button> </button>
{:else} {:else}
<button on:click={show_solutions} class="admin-button" <button onclick={show_solutions} class="admin-button"
>{$t('admin_page.stop_time_and_solutions')} >{$t('admin_page.stop_time_and_solutions')}
</button> </button>
{/if} {/if}
@@ -5,16 +5,22 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let data;
export let username;
export let show_final_results: boolean;
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import confetti from 'canvas-confetti'; import confetti from 'canvas-confetti';
interface Props {
data: any;
username: any;
show_final_results: boolean;
}
let { data = $bindable(), username, show_final_results }: Props = $props();
function sortObjectbyValue(obj) { function sortObjectbyValue(obj) {
const ret = {}; const ret = {};
@@ -24,18 +30,22 @@ SPDX-License-Identifier: MPL-2.0
return ret; return ret;
} }
$: data = sortObjectbyValue(data); run(() => {
$: console.log(data, 'sorted, fina'); data = sortObjectbyValue(data);
});
let player_names; run(() => {
$: player_names = Object.keys(data).sort(function (a, b) { console.log(data, 'sorted, fina');
return data[b] - data[a];
}); });
let player_count_or_five; let player_names = $derived(Object.keys(data).sort(function (a, b) {
$: player_count_or_five = player_names.length >= 5 ? 5 : player_names.length; return data[b] - data[a];
}));
let canvas;
let player_count_or_five = $derived(player_names.length >= 5 ? 5 : player_names.length);
let canvas = $state();
onMount(() => { onMount(() => {
setTimeout(() => { setTimeout(() => {
confetti.create(canvas, { confetti.create(canvas, {
@@ -48,7 +58,7 @@ SPDX-License-Identifier: MPL-2.0
</script> </script>
{#if show_final_results} {#if show_final_results}
<canvas bind:this={canvas} /> <canvas bind:this={canvas}></canvas>
<div> <div>
{#each player_names as player, i} {#each player_names as player, i}
{#if i <= player_count_or_five - 1} {#if i <= player_count_or_five - 1}
@@ -11,14 +11,23 @@ SPDX-License-Identifier: MPL-2.0
import GrayButton from '$lib/components/buttons/gray.svelte'; import GrayButton from '$lib/components/buttons/gray.svelte';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
export let game_pin: string; interface Props {
export let players; game_pin: string;
export let socket; players: any;
export let cqc_code: string; socket: any;
cqc_code: string;
}
let fullscreen_open = false; let {
game_pin,
players = $bindable(),
socket,
cqc_code = $bindable()
}: Props = $props();
let fullscreen_open = $state(false);
const { t } = getLocalization(); const { t } = getLocalization();
let play_music = false; let play_music = $state(false);
if (cqc_code === 'null') { if (cqc_code === 'null') {
cqc_code = null; cqc_code = null;
@@ -52,7 +61,7 @@ SPDX-License-Identifier: MPL-2.0
</p> </p>
</div> </div>
<img <img
on:click={() => (fullscreen_open = true)} onclick={() => (fullscreen_open = true)}
alt="QR code to join the game" alt="QR code to join the game"
src="/api/v1/utils/qr/{game_pin}" src="/api/v1/utils/qr/{game_pin}"
class="block mx-auto w-1/2 dark:bg-white shadow-2xl rounded-sm hover:cursor-pointer" class="block mx-auto w-1/2 dark:bg-white shadow-2xl rounded-sm hover:cursor-pointer"
@@ -101,7 +110,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="p-2 m-2 border-2 border-[#B07156] rounded-sm hover:cursor-pointer"> <div class="p-2 m-2 border-2 border-[#B07156] rounded-sm hover:cursor-pointer">
<span <span
class="hover:line-through text-lg" class="hover:line-through text-lg"
on:click={() => { onclick={() => {
kick_player(player.username); kick_player(player.username);
}}>{player.username}</span }}>{player.username}</span
> >
@@ -116,7 +125,7 @@ SPDX-License-Identifier: MPL-2.0
<div <div
class="fixed top-0 left-0 z-50 w-screen h-screen bg-black/50 flex p-2" class="fixed top-0 left-0 z-50 w-screen h-screen bg-black/50 flex p-2"
transition:fade|global={{ duration: 80 }} transition:fade|global={{ duration: 80 }}
on:click={() => (fullscreen_open = false)} onclick={() => (fullscreen_open = false)}
> >
<img <img
alt="QR code to join the game" alt="QR code to join the game"
+23 -11
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { QuizQuestionType } from '$lib/quiz_types'; import { QuizQuestionType } from '$lib/quiz_types';
import type { QuizData } from '$lib/quiz_types'; import type { QuizData } from '$lib/quiz_types';
import { get_foreground_color } from '$lib/helpers.js'; import { get_foreground_color } from '$lib/helpers.js';
@@ -13,17 +15,27 @@ SPDX-License-Identifier: MPL-2.0
import MediaComponent from '$lib/editor/MediaComponent.svelte'; import MediaComponent from '$lib/editor/MediaComponent.svelte';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
export let quiz_data: QuizData;
export let selected_question: number;
export let timer_res: string;
export let answer_count: number; interface Props {
export let default_colors: string[]; quiz_data: QuizData;
selected_question: number;
timer_res: string;
answer_count: number;
default_colors: string[];
}
let {
quiz_data,
selected_question,
timer_res = $bindable(),
answer_count,
default_colors
}: Props = $props();
const { t } = getLocalization(); const { t } = getLocalization();
let circular_progress = 0; let circular_progress = $state(0);
$: { run(() => {
try { try {
circular_progress = circular_progress =
1 - 1 -
@@ -33,7 +45,7 @@ SPDX-License-Identifier: MPL-2.0
} catch { } catch {
circular_progress = 0; circular_progress = 0;
} }
} });
</script> </script>
<div class="flex flex-col justify-center w-screen h-1/6"> <div class="flex flex-col justify-center w-screen h-1/6">
@@ -42,7 +54,7 @@ SPDX-License-Identifier: MPL-2.0
</h1> </h1>
<!-- <span class='text-center py-2 text-lg'>{$t('admin_page.time_left')}: {timer_res}</span>--> <!-- <span class='text-center py-2 text-lg'>{$t('admin_page.time_left')}: {timer_res}</span>-->
<div class="grid grid-cols-3 my-2"> <div class="grid grid-cols-3 my-2">
<span /> <span></span>
<div class="m-auto"> <div class="m-auto">
<CircularTimer <CircularTimer
bind:text={timer_res} bind:text={timer_res}
@@ -85,7 +97,7 @@ SPDX-License-Identifier: MPL-2.0
style="color: {get_foreground_color(answer.color ?? default_colors[i])}" style="color: {get_foreground_color(answer.color ?? default_colors[i])}"
>{answer.answer}</span >{answer.answer}</span
> >
<span class="pl-4 w-10" /> <span class="pl-4 w-10"></span>
</div> </div>
{/each} {/each}
</div> </div>
@@ -97,7 +109,7 @@ SPDX-License-Identifier: MPL-2.0
<span class="text-center text-2xl px-2 py-4 w-full text-black" <span class="text-center text-2xl px-2 py-4 w-full text-black"
>{answer.answer}</span >{answer.answer}</span
> >
<span class="pl-4 w-10" /> <span class="pl-4 w-10"></span>
</div> </div>
{/each} {/each}
</div> </div>
+30 -14
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import VotingResults from './voting_results.svelte'; import VotingResults from './voting_results.svelte';
import { flip } from 'svelte/animate'; import { flip } from 'svelte/animate';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
@@ -15,16 +17,19 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
export let data; interface Props {
export let question: Question; data: any;
question: Question;
export let new_data: Array<{ new_data: Array<{
username: string; username: string;
answer: string; answer: string;
right: boolean; right: boolean;
time_taken: number; time_taken: number;
score: number; score: number;
}>; }>;
}
let { data = $bindable(), question, new_data }: Props = $props();
function sortObjectbyValue(obj) { function sortObjectbyValue(obj) {
const ret = {}; const ret = {};
@@ -35,7 +40,7 @@ SPDX-License-Identifier: MPL-2.0
} }
// let data_by_username = {}; // let data_by_username = {};
let score_by_username = {}; let score_by_username = $state({});
const do_sth = () => { const do_sth = () => {
for (const i of new_data) { for (const i of new_data) {
@@ -43,13 +48,17 @@ SPDX-License-Identifier: MPL-2.0
} }
}; };
$: { run(() => {
new_data; new_data;
score_by_username; score_by_username;
do_sth(); do_sth();
} });
let player_names; let player_names = $derived(
Object.keys(data).sort(function (a, b) {
return data[b] - data[a];
})
);
if (JSON.stringify(data) === '{}') { if (JSON.stringify(data) === '{}') {
for (const i of new_data) { for (const i of new_data) {
@@ -57,12 +66,11 @@ SPDX-License-Identifier: MPL-2.0
} }
} }
$: data = sortObjectbyValue(data); run(() => {
$: player_names = Object.keys(data).sort(function (a, b) { data = sortObjectbyValue(data);
return data[b] - data[a];
}); });
let show_new_score_clicked = false; let show_new_score_clicked = $state(false);
const show_new_score = () => { const show_new_score = () => {
// for (let i = 0; i++; i < player_names.length) { // for (let i = 0; i++; i < player_names.length) {
@@ -98,20 +106,27 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex justify-center"> <div class="flex justify-center">
<div> <div>
<table class="table-auto text-xl"> <table class="table-auto text-xl">
<thead>
<tr> <tr>
<th class="p-2 border-r border-r-black border-b-2 border-b-black" <th class="p-2 border-r border-r-black border-b-2 border-b-black"
>{$t('words.name')}</th >{$t('words.name')}</th
> >
<th class="p-2 border-b-2 border-b-black">{$t('words.point', { count: 2 })}</th> <th class="p-2 border-b-2 border-b-black"
>{$t('words.point', { count: 2 })}</th
>
{#if show_new_score_clicked} {#if show_new_score_clicked}
<th in:fly|global={{ x: 300 }} class="p-2 border-b-2 border-b-black" <th in:fly|global={{ x: 300 }} class="p-2 border-b-2 border-b-black"
>{$t('play_page.points_added')} >{$t('play_page.points_added')}
</th> </th>
{/if} {/if}
</tr> </tr>
</thead>
<tbody>
{#each player_names as player, i (player)} {#each player_names as player, i (player)}
<tr animate:flip> <tr animate:flip>
<td class:hidden={i > 3} class="p-2 border-r border-r-black">{player}</td> <td class:hidden={i > 3} class="p-2 border-r border-r-black"
>{player}</td
>
<td class:hidden={i > 3} class="p-2">{data[player]}</td> <td class:hidden={i > 3} class="p-2">{data[player]}</td>
{#if show_new_score_clicked} {#if show_new_score_clicked}
<td <td
@@ -126,6 +141,7 @@ SPDX-License-Identifier: MPL-2.0
{/if} {/if}
</tr> </tr>
{/each} {/each}
</tbody>
</table> </table>
</div> </div>
</div> </div>
+8 -4
View File
@@ -9,11 +9,15 @@ SPDX-License-Identifier: MPL-2.0
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import Pikaso from 'pikaso'; import Pikaso from 'pikaso';
export let question: Question; interface Props {
question: Question;
}
let canvas_el: HTMLDivElement | undefined; let { question }: Props = $props();
let canvas_el: HTMLDivElement | undefined = $state();
let canvas: Pikaso; let canvas: Pikaso;
let img_src = ''; let img_src = $state('');
onMount(() => { onMount(() => {
canvas = new Pikaso({ canvas = new Pikaso({
@@ -33,7 +37,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="w-full h-full"> <div class="w-full h-full">
<div class="hidden"> <div class="hidden">
<div bind:this={canvas_el} class="w-full h-full block" /> <div bind:this={canvas_el} class="w-full h-full block"></div>
</div> </div>
<div class="w-full h-full flex justify-center"> <div class="w-full h-full flex justify-center">
<img src={img_src} /> <img src={img_src} />
@@ -8,8 +8,12 @@ SPDX-License-Identifier: MPL-2.0
import type { Question } from '$lib/quiz_types'; import type { Question } from '$lib/quiz_types';
import { QuizQuestionType } from '$lib/quiz_types'; import { QuizQuestionType } from '$lib/quiz_types';
export let data; interface Props {
export let question: Question; data: any;
question: Question;
}
let { data, question }: Props = $props();
let quiz_answers = []; let quiz_answers = [];
let quiz_colors = []; let quiz_colors = [];
@@ -21,7 +25,7 @@ SPDX-License-Identifier: MPL-2.0
answer_correct.push(i.right); answer_correct.push(i.right);
} }
let sorted_data = {}; let sorted_data = $state({});
for (const i of quiz_answers) { for (const i of quiz_answers) {
sorted_data[i] = 0; sorted_data[i] = 0;
} }
@@ -56,7 +60,7 @@ SPDX-License-Identifier: MPL-2.0
data.length}rem; background-color: {quiz_colors[i] data.length}rem; background-color: {quiz_colors[i]
? quiz_colors[i] ? quiz_colors[i]
: 'black'}" : 'black'}"
/> ></div>
{/each} {/each}
</div> </div>
<div class="flex gap-12"> <div class="flex gap-12">
+17 -7
View File
@@ -5,12 +5,18 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import Audio1 from '$lib/assets/music/1-128.mp3'; import Audio1 from '$lib/assets/music/1-128.mp3';
export let play = false; interface Props {
let volume = 100; play?: boolean;
}
const audio = new Audio(Audio1); let { play = $bindable(false) }: Props = $props();
let volume = $state(100);
const audio = $state(new Audio(Audio1));
const control_audio = (play_audio: boolean) => { const control_audio = (play_audio: boolean) => {
if (play_audio) { if (play_audio) {
audio.play(); audio.play();
@@ -19,14 +25,18 @@ SPDX-License-Identifier: MPL-2.0
audio.pause(); audio.pause();
} }
}; };
$: audio.volume = volume / 100; run(() => {
$: control_audio(play); audio.volume = volume / 100;
});
run(() => {
control_audio(play);
});
</script> </script>
<div class="fixed top-0 left-0"> <div class="fixed top-0 left-0">
{#if play} {#if play}
<button <button
on:click={() => { onclick={() => {
play = false; play = false;
}} }}
> >
@@ -48,7 +58,7 @@ SPDX-License-Identifier: MPL-2.0
</button> </button>
{:else} {:else}
<button <button
on:click={() => { onclick={() => {
play = true; play = true;
}} }}
> >
+22 -10
View File
@@ -5,24 +5,36 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let progress: number; import { run } from 'svelte/legacy';
export let text: string;
export let color: string;
let angle = 360 * progress;
$: angle = 360 * progress; interface Props {
$: console.log(angle); progress: number;
text: string;
color: string;
}
let { progress, text, color }: Props = $props();
let angle = $state(360 * progress);
run(() => {
angle = 360 * progress;
});
run(() => {
console.log(angle);
});
// Adapt the logic according to the approach // Adapt the logic according to the approach
let background = `radial-gradient(white 50%, transparent 51%), let background = $state(`radial-gradient(white 50%, transparent 51%),
conic-gradient(transparent 0deg ${angle}deg, gainsboro ${angle}deg 360deg), conic-gradient(transparent 0deg ${angle}deg, gainsboro ${angle}deg 360deg),
conic-gradient(green 0deg, green 90deg, green 180deg, green);`; conic-gradient(green 0deg, green 90deg, green 180deg, green);`);
$: background = `radial-gradient(white 50%, transparent 51%), run(() => {
background = `radial-gradient(white 50%, transparent 51%),
conic-gradient(transparent 0deg ${angle}deg, gainsboro ${angle}deg 360deg), conic-gradient(transparent 0deg ${angle}deg, gainsboro ${angle}deg 360deg),
conic-gradient(${color} 0deg, ${color} 90deg, ${color} 180deg, ${color});`; conic-gradient(${color} 0deg, ${color} 90deg, ${color} 180deg, ${color});`;
});
$: cssVarStyles = `--background:${background}`; let cssVarStyles = $derived(`--background:${background}`);
</script> </script>
<div id="progress-circle" style={cssVarStyles} class="transition-all text-4xl text-black"> <div id="progress-circle" style={cssVarStyles} class="transition-all text-4xl text-black">
+12 -6
View File
@@ -5,12 +5,18 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let question_count: number; interface Props {
export let final_results: Array<null> | Array<Array<PlayerAnswer>>; question_count: number;
final_results: Array<null> | Array<Array<PlayerAnswer>>;
}
let { question_count, final_results }: Props = $props();
interface PlayerAnswer { interface PlayerAnswer {
username: string; username: string;
@@ -18,8 +24,8 @@ SPDX-License-Identifier: MPL-2.0
right: string; right: string;
} }
let data_available = false; let data_available = $state(false);
let winners_arr; let winners_arr = $state();
const getWinnersSorted = () => { const getWinnersSorted = () => {
let winners = {}; let winners = {};
@@ -79,9 +85,9 @@ SPDX-License-Identifier: MPL-2.0
} }
}; };
let winners = getWinnersSorted(); let winners = getWinnersSorted();
$: { run(() => {
console.log(winners, winners_arr); console.log(winners, winners_arr);
} });
</script> </script>
<div> <div>
+25 -12
View File
@@ -5,6 +5,9 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run, createBubbler, preventDefault } from 'svelte/legacy';
const bubble = createBubbler();
import { socket } from '$lib/socket'; import { socket } from '$lib/socket';
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
@@ -15,13 +18,17 @@ SPDX-License-Identifier: MPL-2.0
import BrownButton from '$lib/components/buttons/brown.svelte'; import BrownButton from '$lib/components/buttons/brown.svelte';
const { t } = getLocalization(); const { t } = getLocalization();
export let game_pin: string;
export let game_mode;
export let username; interface Props {
let custom_field; game_pin: string;
let custom_field_value; game_mode: any;
let captcha_enabled; username: any;
}
let { game_pin = $bindable(), game_mode = $bindable(), username = $bindable() }: Props = $props();
let custom_field = $state();
let custom_field_value = $state();
let captcha_enabled = $state();
let hcaptchaSitekey = import.meta.env.VITE_HCAPTCHA; let hcaptchaSitekey = import.meta.env.VITE_HCAPTCHA;
@@ -105,10 +112,12 @@ SPDX-License-Identifier: MPL-2.0
} }
}; };
$: if (game_pin.length > 5) { run(() => {
if (game_pin.length > 5) {
console.log('Setting game pin'); console.log('Setting game pin');
set_game_pin(); set_game_pin();
} }
});
const setUsername = async () => { const setUsername = async () => {
if (username.length <= 3) { if (username.length <= 3) {
@@ -177,8 +186,12 @@ SPDX-License-Identifier: MPL-2.0
} }
}); });
$: console.log(game_pin, game_pin.length > 6); run(() => {
$: game_pin = game_pin.replace(/\D/g, ''); console.log(game_pin, game_pin.length > 6);
});
run(() => {
game_pin = game_pin.replace(/\D/g, '');
});
</script> </script>
<svelte:head> <svelte:head>
@@ -194,7 +207,7 @@ SPDX-License-Identifier: MPL-2.0
{#if game_pin === '' || game_pin.length < 6} {#if game_pin === '' || game_pin.length < 6}
<div class="flex flex-col justify-center align-center w-screen h-screen"> <div class="flex flex-col justify-center align-center w-screen h-screen">
<form on:submit|preventDefault class="flex-col flex justify-center align-center mx-auto"> <form onsubmit={preventDefault(bubble('submit'))} class="flex-col flex justify-center align-center mx-auto">
<h1 class="text-lg text-center">{$t('words.game_pin')}</h1> <h1 class="text-lg text-center">{$t('words.game_pin')}</h1>
<input <input
class="border border-gray-400 self-center text-center text-black ring-0 outline-hidden p-2 rounded-lg focus:shadow-2xl transition-all" class="border border-gray-400 self-center text-center text-black ring-0 outline-hidden p-2 rounded-lg focus:shadow-2xl transition-all"
@@ -213,7 +226,7 @@ SPDX-License-Identifier: MPL-2.0
{:else} {:else}
<div class="flex flex-col justify-center align-center w-screen h-screen"> <div class="flex flex-col justify-center align-center w-screen h-screen">
<form <form
on:submit|preventDefault={setUsername} onsubmit={preventDefault(setUsername)}
class="flex-col flex justify-center align-center mx-auto" class="flex-col flex justify-center align-center mx-auto"
> >
<h1 class="text-lg text-center">{$t('words.username')}</h1> <h1 class="text-lg text-center">{$t('words.username')}</h1>
@@ -244,4 +257,4 @@ SPDX-License-Identifier: MPL-2.0
data-sitekey={hcaptchaSitekey} data-sitekey={hcaptchaSitekey}
data-size="invisible" data-size="invisible"
data-theme="dark" data-theme="dark"
/> ></div>
+41 -26
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import type { Question } from '$lib/quiz_types'; import type { Question } from '$lib/quiz_types';
import { QuizQuestionType } from '$lib/quiz_types'; import { QuizQuestionType } from '$lib/quiz_types';
import { socket } from '$lib/socket'; import { socket } from '$lib/socket';
@@ -19,12 +21,23 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
export let question: Question; interface Props {
export let game_mode; question: Question;
export let question_index: string | number; game_mode: any;
export let solution; question_index: string | number;
solution: any;
}
$: console.log(question_index, question, 'hi!'); let {
question = $bindable(),
game_mode = $bindable(),
question_index,
solution
}: Props = $props();
run(() => {
console.log(question_index, question, 'hi!');
});
console.log(question); console.log(question);
if (question.type === undefined) { if (question.type === undefined) {
@@ -39,8 +52,8 @@ SPDX-License-Identifier: MPL-2.0
throw new Error('question_index must be a string or number'); throw new Error('question_index must be a string or number');
}*/ }*/
let timer_res = question.time; let timer_res = $state(question.time);
let selected_answer: string; let selected_answer: string = $state();
// Stop the timer if the question is answered // Stop the timer if the question is answered
const timer = (time: string) => { const timer = (time: string) => {
@@ -62,11 +75,11 @@ SPDX-License-Identifier: MPL-2.0
timer(question.time); timer(question.time);
$: { run(() => {
if (solution !== undefined) { if (solution !== undefined) {
timer_res = '0'; timer_res = '0';
} }
} });
const selectAnswer = (answer: string) => { const selectAnswer = (answer: string) => {
selected_answer = answer; selected_answer = answer;
@@ -90,9 +103,9 @@ SPDX-License-Identifier: MPL-2.0
}); });
}; };
let text_input = ''; let text_input = $state('');
let slider_value = [0]; let slider_value = $state([0]);
if (question.type === QuizQuestionType.RANGE) { if (question.type === QuizQuestionType.RANGE) {
slider_value[0] = (question.answers.max - question.answers.min) / 2 + question.answers.min; slider_value[0] = (question.answers.max - question.answers.min) / 2 + question.answers.min;
} }
@@ -119,15 +132,17 @@ SPDX-License-Identifier: MPL-2.0
_arr[b] = temp; _arr[b] = temp;
return _arr; return _arr;
}; };
$: set_answer_if_not_set_range(timer_res); run(() => {
let circular_progress = 0; set_answer_if_not_set_range(timer_res);
$: { });
let circular_progress = $state(0);
run(() => {
try { try {
circular_progress = 1 - ((100 / question.time) * parseInt(timer_res)) / 100; circular_progress = 1 - ((100 / question.time) * parseInt(timer_res)) / 100;
} catch { } catch {
circular_progress = 0; circular_progress = 0;
} }
} });
const get_div_height = (): string => { const get_div_height = (): string => {
if (game_mode === 'normal') { if (game_mode === 'normal') {
@@ -140,7 +155,9 @@ SPDX-License-Identifier: MPL-2.0
return '100'; return '100';
} }
}; };
$: console.log(slider_value, 'values'); run(() => {
console.log(slider_value, 'values');
});
const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58']; const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58'];
</script> </script>
@@ -188,7 +205,7 @@ SPDX-License-Identifier: MPL-2.0
answer.color ?? default_colors[i] answer.color ?? default_colors[i]
)}" )}"
disabled={selected_answer !== undefined} disabled={selected_answer !== undefined}
on:click={() => selectAnswer(answer.answer)} onclick={() => selectAnswer(answer.answer)}
> >
{#if game_mode === 'kahoot'} {#if game_mode === 'kahoot'}
<img <img
@@ -207,13 +224,12 @@ SPDX-License-Identifier: MPL-2.0
<span <span
class="fixed top-0 bg-red-500 h-8 transition-all" class="fixed top-0 bg-red-500 h-8 transition-all"
style="width: {(100 / parseInt(question.time)) * parseInt(timer_res)}vw" style="width: {(100 / parseInt(question.time)) * parseInt(timer_res)}vw"
/> ></span>
{#await import('svelte-range-slider-pips')} {#await import('svelte-range-slider-pips')}
<Spinner /> <Spinner />
{:then c} {:then c}
<div class:pointer-events-none={selected_answer !== undefined} class="mt-24"> <div class:pointer-events-none={selected_answer !== undefined} class="mt-24">
<svelte:component <c.default
this={c.default}
bind:values={slider_value} bind:values={slider_value}
bind:min={question.answers.min} bind:min={question.answers.min}
bind:max={question.answers.max} bind:max={question.answers.max}
@@ -238,7 +254,7 @@ SPDX-License-Identifier: MPL-2.0
<span <span
class="fixed top-0 bg-red-500 h-8 transition-all" class="fixed top-0 bg-red-500 h-8 transition-all"
style="width: {(100 / parseInt(question.time)) * parseInt(timer_res)}vw" style="width: {(100 / parseInt(question.time)) * parseInt(timer_res)}vw"
/> ></span>
<div class="flex justify-center mt-10"> <div class="flex justify-center mt-10">
<p class="text-black dark:text-white">Enter your answer</p> <p class="text-black dark:text-white">Enter your answer</p>
</div> </div>
@@ -286,7 +302,7 @@ SPDX-License-Identifier: MPL-2.0
<span <span
class="fixed top-0 bg-red-500 h-8 transition-all" class="fixed top-0 bg-red-500 h-8 transition-all"
style="width: {(100 / parseInt(question.time)) * parseInt(timer_res)}vw" style="width: {(100 / parseInt(question.time)) * parseInt(timer_res)}vw"
/> ></span>
<div class="flex flex-col w-full h-full gap-4 px-4 py-6 mt-10"> <div class="flex flex-col w-full h-full gap-4 px-4 py-6 mt-10">
{#each question.answers as answer, i (answer.id)} {#each question.answers as answer, i (answer.id)}
<div <div
@@ -295,7 +311,7 @@ SPDX-License-Identifier: MPL-2.0
style="background-color: {answer.color ?? '#b07156'}" style="background-color: {answer.color ?? '#b07156'}"
> >
<button <button
on:click={() => { onclick={() => {
question.answers = swapArrayElements(question.answers, i, i - 1); question.answers = swapArrayElements(question.answers, i, i - 1);
}} }}
class="disabled:opacity-50 shadow-lg bg-black/30 w-full flex justify-center rounded-lg p-2 hover:bg-black/20 transition" class="disabled:opacity-50 shadow-lg bg-black/30 w-full flex justify-center rounded-lg p-2 hover:bg-black/20 transition"
@@ -322,7 +338,7 @@ SPDX-License-Identifier: MPL-2.0
<p class="w-full text-center p-2 text-2xl">{answer.answer}</p> <p class="w-full text-center p-2 text-2xl">{answer.answer}</p>
<button <button
on:click={() => { onclick={() => {
question.answers = swapArrayElements(question.answers, i, i + 1); question.answers = swapArrayElements(question.answers, i, i + 1);
}} }}
class="disabled:opacity-50 shadow-lg bg-black/30 w-full flex justify-center rounded-lg p-2 hover:bg-black/20 transition" class="disabled:opacity-50 shadow-lg bg-black/30 w-full flex justify-center rounded-lg p-2 hover:bg-black/20 transition"
@@ -363,8 +379,7 @@ SPDX-License-Identifier: MPL-2.0
{#await import('./questions/check.svelte')} {#await import('./questions/check.svelte')}
<Spinner /> <Spinner />
{:then c} {:then c}
<svelte:component <c.default
this={c.default}
bind:question bind:question
bind:selected_answer bind:selected_answer
bind:game_mode bind:game_mode
+17 -7
View File
@@ -11,14 +11,24 @@ SPDX-License-Identifier: MPL-2.0
import CircularTimer from '$lib/play/circular_progress.svelte'; import CircularTimer from '$lib/play/circular_progress.svelte';
// import CircularTimer from '$lib/play/circular_progress.svelte'; // import CircularTimer from '$lib/play/circular_progress.svelte';
const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58']; const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58'];
export let question: Question;
export let selected_answer = '';
export let game_mode;
export let timer_res;
export let circular_progress; interface Props {
let _selected_answers = [false, false, false, false]; question: Question;
selected_answer?: string;
game_mode: any;
timer_res: any;
circular_progress: any;
}
let {
question,
selected_answer = $bindable(''),
game_mode,
timer_res = $bindable(),
circular_progress = $bindable()
}: Props = $props();
let _selected_answers = $state([false, false, false, false]);
const selectAnswer = (i: number) => { const selectAnswer = (i: number) => {
_selected_answers[i] = !_selected_answers[i]; _selected_answers[i] = !_selected_answers[i];
@@ -59,7 +69,7 @@ SPDX-License-Identifier: MPL-2.0
default_colors[i]}; color: {get_foreground_color( default_colors[i]}; color: {get_foreground_color(
answer.color ?? default_colors[i] answer.color ?? default_colors[i]
)}" )}"
on:click={() => selectAnswer(i)} onclick={() => selectAnswer(i)}
class:opacity-100={_selected_answers[i]} class:opacity-100={_selected_answers[i]}
class:opacity-50={!_selected_answers[i]} class:opacity-50={!_selected_answers[i]}
> >
+22 -12
View File
@@ -5,15 +5,9 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let scores; import { run } from 'svelte/legacy';
export let question_results: Array<{
username: string;
answer: string;
right: boolean;
time_taken: number;
score: number;
}>;
function sortObjectbyValue(obj) { function sortObjectbyValue(obj) {
const ret = {}; const ret = {};
@@ -23,8 +17,20 @@ SPDX-License-Identifier: MPL-2.0
return ret; return ret;
} }
export let username; interface Props {
let score_by_username = {}; scores: any;
question_results: Array<{
username: string;
answer: string;
right: boolean;
time_taken: number;
score: number;
}>;
username: any;
}
let { scores = $bindable(), question_results, username }: Props = $props();
let score_by_username = $state({});
if (JSON.stringify(scores) === '{}') { if (JSON.stringify(scores) === '{}') {
for (const i of question_results) { for (const i of question_results) {
@@ -32,12 +38,16 @@ SPDX-License-Identifier: MPL-2.0
} }
} }
$: console.log(score_by_username, scores, 'dieter'); run(() => {
console.log(score_by_username, scores, 'dieter');
});
for (const i of question_results) { for (const i of question_results) {
score_by_username[i.username] = i.score; score_by_username[i.username] = i.score;
} }
$: scores = sortObjectbyValue(scores); run(() => {
scores = sortObjectbyValue(scores);
});
const do_sth = () => { const do_sth = () => {
for (const i of Object.keys(score_by_username)) { for (const i of Object.keys(score_by_username)) {
+16 -8
View File
@@ -11,11 +11,20 @@ SPDX-License-Identifier: MPL-2.0
import Spinner from '$lib/Spinner.svelte'; import Spinner from '$lib/Spinner.svelte';
const { t } = getLocalization(); const { t } = getLocalization();
export let results: Array<Answer>; interface Props {
export let game_data: QuizData; results: Array<Answer>;
export let solution: Question; game_data: QuizData;
export let question_index: string; solution: Question;
let data_store = {}; question_index: string;
}
let {
results,
game_data,
solution = $bindable(),
question_index
}: Props = $props();
let data_store = $state({});
const question = solution.answers; const question = solution.answers;
for (let i = 0; i < question.length; i++) { for (let i = 0; i < question.length; i++) {
@@ -26,7 +35,7 @@ SPDX-License-Identifier: MPL-2.0
data_store[results[i].answer] += 1; data_store[results[i].answer] += 1;
} }
let slider_values = [solution.answers.min_correct ?? 0, solution.answers.max_correct ?? 0]; let slider_values = $state([solution.answers.min_correct ?? 0, solution.answers.max_correct ?? 0]);
console.log(slider_values, solution.answers); console.log(slider_values, solution.answers);
</script> </script>
@@ -99,8 +108,7 @@ SPDX-License-Identifier: MPL-2.0
<Spinner /> <Spinner />
{:then c} {:then c}
<div class="grayscale pointer-events-none w-full"> <div class="grayscale pointer-events-none w-full">
<svelte:component <c.default
this={c.default}
bind:values={slider_values} bind:values={slider_values}
bind:min={solution.answers.min} bind:min={solution.answers.min}
bind:max={solution.answers.max} bind:max={solution.answers.max}
+7 -3
View File
@@ -5,9 +5,13 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let title: string; interface Props {
export let description: string; title: string;
export let cover_image: string | undefined; description: string;
cover_image: string | undefined;
}
let { title, description, cover_image }: Props = $props();
</script> </script>
<div class="flex flex-col justify-center w-screen h-screen"> <div class="flex flex-col justify-center w-screen h-screen">
+26 -24
View File
@@ -14,13 +14,17 @@ SPDX-License-Identifier: MPL-2.0
import BrownButton from '$lib/components/buttons/brown.svelte'; import BrownButton from '$lib/components/buttons/brown.svelte';
import MediaComponent from '$lib/editor/MediaComponent.svelte'; import MediaComponent from '$lib/editor/MediaComponent.svelte';
export let question: Question; interface Props {
question: Question;
}
let { question = $bindable() }: Props = $props();
const { t } = getLocalization(); const { t } = getLocalization();
let selected_answer = undefined; let selected_answer = $state(undefined);
let timer_res = question.time; let timer_res = $state(question.time);
let show_results = false; let show_results = $state(false);
// Stop the timer if the question is answered // Stop the timer if the question is answered
const timer = (time: string) => { const timer = (time: string) => {
@@ -37,16 +41,16 @@ SPDX-License-Identifier: MPL-2.0
timer_res = seconds.toString(); timer_res = seconds.toString();
}, 1000); }, 1000);
}; };
let slider_value = [0]; let slider_value = $state([0]);
if (question.type === QuizQuestionType.RANGE) { if (question.type === QuizQuestionType.RANGE) {
slider_value[0] = (question.answers.max - question.answers.min) / 2 + question.answers.min; slider_value[0] = (question.answers.max - question.answers.min) / 2 + question.answers.min;
} }
let slider_values = [question.answers.min_correct ?? 0, question.answers.max_correct ?? 0]; let slider_values = $state([question.answers.min_correct ?? 0, question.answers.max_correct ?? 0]);
let text_input; let text_input = $state();
timer(question.time); timer(question.time);
let check_choice_selected = [false, false, false, false]; let check_choice_selected = $state([false, false, false, false]);
function shuffleArray(a) { function shuffleArray(a) {
for (let i = a.length - 1; i > 0; i--) { for (let i = a.length - 1; i > 0; i--) {
@@ -75,7 +79,7 @@ SPDX-License-Identifier: MPL-2.0
shuffleArray(question.answers); shuffleArray(question.answers);
} }
let order_corrected = false; let order_corrected = $state(false);
const select_complex_answer = () => { const select_complex_answer = () => {
/* const correct_order_ids = [] /* const correct_order_ids = []
for (const e of original_order) { for (const e of original_order) {
@@ -128,7 +132,7 @@ SPDX-License-Identifier: MPL-2.0
disabled={selected_answer !== undefined || timer_res === '0'} disabled={selected_answer !== undefined || timer_res === '0'}
type="button" type="button"
class="p-2 rounded-lg flex justify-center w-full transition bg-amber-300 my-5 disabled:grayscale text-black" class="p-2 rounded-lg flex justify-center w-full transition bg-amber-300 my-5 disabled:grayscale text-black"
on:click={() => { onclick={() => {
selected_answer = i; selected_answer = i;
timer_res = '0'; timer_res = '0';
}}>{answer.answer}</button }}>{answer.answer}</button
@@ -138,7 +142,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="bg-orange-500 p-2 rounded-lg flex justify-center w-full transition my-5 text-black" class="bg-orange-500 p-2 rounded-lg flex justify-center w-full transition my-5 text-black"
type="button" type="button"
on:click={() => { onclick={() => {
show_results = true; show_results = true;
}}>{$t('admin_page.get_results')}</button }}>{$t('admin_page.get_results')}</button
> >
@@ -151,8 +155,7 @@ SPDX-License-Identifier: MPL-2.0
<Spinner /> <Spinner />
{:then c} {:then c}
<div class="grayscale pointer-events-none w-full"> <div class="grayscale pointer-events-none w-full">
<svelte:component <c.default
this={c.default}
bind:values={slider_values} bind:values={slider_values}
bind:min={question.answers.min} bind:min={question.answers.min}
bind:max={question.answers.max} bind:max={question.answers.max}
@@ -167,8 +170,7 @@ SPDX-License-Identifier: MPL-2.0
<Spinner /> <Spinner />
{:then c} {:then c}
<div class:pointer-events-none={selected_answer !== undefined}> <div class:pointer-events-none={selected_answer !== undefined}>
<svelte:component <c.default
this={c.default}
bind:values={slider_value} bind:values={slider_value}
bind:min={question.answers.min} bind:min={question.answers.min}
bind:max={question.answers.max} bind:max={question.answers.max}
@@ -182,7 +184,7 @@ SPDX-License-Identifier: MPL-2.0
type="button" type="button"
class="w-1/3 text-3xl bg-[#B07156] my-2 disabled:opacity-60 rounded-lg p-1 transition" class="w-1/3 text-3xl bg-[#B07156] my-2 disabled:opacity-60 rounded-lg p-1 transition"
disabled={selected_answer !== undefined} disabled={selected_answer !== undefined}
on:click={() => { onclick={() => {
selected_answer = slider_value[0]; selected_answer = slider_value[0];
timer_res = '0'; timer_res = '0';
}} }}
@@ -197,7 +199,7 @@ SPDX-License-Identifier: MPL-2.0
type="button" type="button"
disabled={selected_answer !== undefined || timer_res === '0'} disabled={selected_answer !== undefined || timer_res === '0'}
class="p-2 rounded-lg flex justify-center w-full transition bg-amber-300 my-5 disabled:grayscale text-black" class="p-2 rounded-lg flex justify-center w-full transition bg-amber-300 my-5 disabled:grayscale text-black"
on:click={() => { onclick={() => {
selected_answer = i; selected_answer = i;
timer_res = '0'; timer_res = '0';
}}>{answer.answer}</button }}>{answer.answer}</button
@@ -211,7 +213,7 @@ SPDX-License-Identifier: MPL-2.0
<Spinner my={false} /> <Spinner my={false} />
{:then c} {:then c}
<div class="max-h-[90%] max-w-[90%]"> <div class="max-h-[90%] max-w-[90%]">
<svelte:component this={c.default} bind:question /> <c.default bind:question />
</div> </div>
{/await} {/await}
{:else if question.type === QuizQuestionType.TEXT} {:else if question.type === QuizQuestionType.TEXT}
@@ -236,7 +238,7 @@ SPDX-License-Identifier: MPL-2.0
type="button" type="button"
disabled={!text_input} disabled={!text_input}
class="w-1/3 text-3xl bg-[#B07156] my-2 disabled:opacity-60 rounded-lg p-1 transition" class="w-1/3 text-3xl bg-[#B07156] my-2 disabled:opacity-60 rounded-lg p-1 transition"
on:click={() => { onclick={() => {
selected_answer = text_input; selected_answer = text_input;
timer_res = '0'; timer_res = '0';
}}>{$t('words.submit')}</button }}>{$t('words.submit')}</button
@@ -252,7 +254,7 @@ SPDX-License-Identifier: MPL-2.0
style="background-color: {answer.color ?? '#b07156'}" style="background-color: {answer.color ?? '#b07156'}"
> >
<button <button
on:click={() => { onclick={() => {
question.answers = swapArrayElements(question.answers, i, i - 1); question.answers = swapArrayElements(question.answers, i, i - 1);
}} }}
class="disabled:opacity-50 transition shadow-lg bg-black/30 w-full flex justify-center rounded-lg p-2 hover:bg-black/20 transition" class="disabled:opacity-50 transition shadow-lg bg-black/30 w-full flex justify-center rounded-lg p-2 hover:bg-black/20 transition"
@@ -279,7 +281,7 @@ SPDX-License-Identifier: MPL-2.0
<p class="w-full text-center p-2 text-2xl">{answer.answer}</p> <p class="w-full text-center p-2 text-2xl">{answer.answer}</p>
<button <button
on:click={() => { onclick={() => {
question.answers = swapArrayElements(question.answers, i, i + 1); question.answers = swapArrayElements(question.answers, i, i + 1);
}} }}
class="disabled:opacity-50 transition shadow-lg bg-black/30 w-full flex justify-center rounded-lg p-2 hover:bg-black/20 transition" class="disabled:opacity-50 transition shadow-lg bg-black/30 w-full flex justify-center rounded-lg p-2 hover:bg-black/20 transition"
@@ -309,7 +311,7 @@ SPDX-License-Identifier: MPL-2.0
class="bg-[#B07156] hover:bg-amber-700 text-white font-bold py-2 px-4 rounded-lg mt-2 transition w-full" class="bg-[#B07156] hover:bg-amber-700 text-white font-bold py-2 px-4 rounded-lg mt-2 transition w-full"
type="button" type="button"
disabled={timer_res === '0'} disabled={timer_res === '0'}
on:click={() => { onclick={() => {
select_complex_answer(); select_complex_answer();
}} }}
> >
@@ -338,7 +340,7 @@ SPDX-License-Identifier: MPL-2.0
disabled={selected_answer !== undefined || timer_res === '0'} disabled={selected_answer !== undefined || timer_res === '0'}
class="p-2 rounded-lg flex justify-center w-full transition bg-amber-300 my-5 disabled:grayscale text-black opacity-50" class="p-2 rounded-lg flex justify-center w-full transition bg-amber-300 my-5 disabled:grayscale text-black opacity-50"
class:opacity-100={check_choice_selected[i]} class:opacity-100={check_choice_selected[i]}
on:click={() => { onclick={() => {
check_choice_selected[i] = !check_choice_selected[i]; check_choice_selected[i] = !check_choice_selected[i];
}}>{answer.answer}</button }}>{answer.answer}</button
> >
@@ -353,7 +355,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
type="button" type="button"
class="bg-orange-500 p-2 rounded-lg flex justify-center w-full transition my-5 text-black" class="bg-orange-500 p-2 rounded-lg flex justify-center w-full transition my-5 text-black"
on:click={() => { onclick={() => {
show_results = true; show_results = true;
}}>{$t('admin_page.get_results')}</button }}>{$t('admin_page.get_results')}</button
> >
@@ -5,10 +5,12 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
export let data;
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
let { data = $bindable() } = $props();
const { t } = getLocalization(); const { t } = getLocalization();
</script> </script>
@@ -19,13 +21,13 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex align-middle p-4 gap-3"> <div class="flex align-middle p-4 gap-3">
<span <span
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-red-400 transition" class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-red-400 transition"
/> ></span>
<span <span
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-yellow-400 transition" class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-yellow-400 transition"
/> ></span>
<span <span
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-green-400 transition" class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-green-400 transition"
/> ></span>
</div> </div>
</div> </div>
<div class="dark:bg-gray-700"> <div class="dark:bg-gray-700">
@@ -50,9 +52,9 @@ SPDX-License-Identifier: MPL-2.0
src="/api/v1/storage/download/{data.cover_image}" src="/api/v1/storage/download/{data.cover_image}"
alt="not available" alt="not available"
class="max-h-72 h-auto w-auto" class="max-h-72 h-auto w-auto"
on:contextmenu|preventDefault={() => { oncontextmenu={preventDefault(() => {
data.cover_image = ''; data.cover_image = '';
}} })}
/> />
</div> </div>
{/if} {/if}
@@ -10,7 +10,11 @@ SPDX-License-Identifier: MPL-2.0
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
const { t } = getLocalization(); const { t } = getLocalization();
export let type: QuizTivityTypes | undefined; interface Props {
type: QuizTivityTypes | undefined;
}
let { type = $bindable() }: Props = $props();
const PageTypes = [ const PageTypes = [
/* { /* {
@@ -52,7 +56,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="rounded-sm p-6 border-[#B07156] border"> <div class="rounded-sm p-6 border-[#B07156] border">
<button <button
class="text-xl text-black dark:text-white" class="text-xl text-black dark:text-white"
on:click={() => { onclick={() => {
type = pt.type; type = pt.type;
}}>{pt.name}</button }}>{pt.name}</button
> >
@@ -9,7 +9,11 @@ SPDX-License-Identifier: MPL-2.0
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import BrownButton from '$lib/components/buttons/brown.svelte'; import BrownButton from '$lib/components/buttons/brown.svelte';
export let data: Abcd | undefined; interface Props {
data: Abcd | undefined;
}
let { data = $bindable() }: Props = $props();
if (!data) { if (!data) {
data = { data = {
@@ -39,7 +43,7 @@ SPDX-License-Identifier: MPL-2.0
/> />
<button <button
type="button" type="button"
on:click={() => { onclick={() => {
answer.correct = !answer.correct; answer.correct = !answer.correct;
}} }}
> >
@@ -7,9 +7,13 @@ SPDX-License-Identifier: MPL-2.0
<script lang="ts"> <script lang="ts">
import type { Abcd } from '$lib/quiztivity/types'; import type { Abcd } from '$lib/quiztivity/types';
export let data: Abcd | undefined; interface Props {
data: Abcd | undefined;
}
let selected_answer: number | undefined; let { data }: Props = $props();
let selected_answer: number | undefined = $state();
const select_answer = (i: number) => { const select_answer = (i: number) => {
selected_answer = i; selected_answer = i;
@@ -24,7 +28,7 @@ SPDX-License-Identifier: MPL-2.0
{#each data.answers as answer, i} {#each data.answers as answer, i}
<button <button
class="rounded-sm p-6 bg-gray-700 flex transition-all" class="rounded-sm p-6 bg-gray-700 flex transition-all"
on:click={() => { onclick={() => {
select_answer(i); select_answer(i);
}} }}
class:opacity-50={selected_answer !== undefined && !answer.correct} class:opacity-50={selected_answer !== undefined && !answer.correct}
@@ -5,11 +5,17 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import type { Markdown } from '$lib/quiztivity/types'; import type { Markdown } from '$lib/quiztivity/types';
import { marked } from 'marked'; import { marked } from 'marked';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
export let data: Markdown | undefined; interface Props {
data: Markdown | undefined;
}
let { data = $bindable() }: Props = $props();
if (!data) { if (!data) {
data = { data = {
@@ -17,9 +23,11 @@ SPDX-License-Identifier: MPL-2.0
}; };
} }
let rendered_html = ''; let rendered_html = $state('');
$: rendered_html = browser ? marked.parse(data.markdown) : ''; run(() => {
rendered_html = browser ? marked.parse(data.markdown) : '';
});
</script> </script>
<div class="w-full h-[70vh] flex flex-row p-4 gap-4"> <div class="w-full h-[70vh] flex flex-row p-4 gap-4">
@@ -27,7 +35,7 @@ SPDX-License-Identifier: MPL-2.0
class="w-full resize-none border-[#B07156] border-2 rounded-sm outline-hidden p-2 bg-white/30 dark:placeholder-gray-300" class="w-full resize-none border-[#B07156] border-2 rounded-sm outline-hidden p-2 bg-white/30 dark:placeholder-gray-300"
bind:value={data.markdown} bind:value={data.markdown}
placeholder="Enter your markdown here!" placeholder="Enter your markdown here!"
/> ></textarea>
<div class="w-full"> <div class="w-full">
<div <div
class="aspect-video prose max-w-none border-[#B07156] border-2 rounded-sm p-2 dark:prose-invert" class="aspect-video prose max-w-none border-[#B07156] border-2 rounded-sm p-2 dark:prose-invert"
@@ -5,16 +5,24 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import type { Markdown } from '$lib/quiztivity/types'; import type { Markdown } from '$lib/quiztivity/types';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import { marked } from 'marked'; import { marked } from 'marked';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
export let data: Markdown | undefined; interface Props {
data: Markdown | undefined;
}
let rendered_html = ''; let { data }: Props = $props();
$: rendered_html = browser ? DOMPurify.sanitize(marked.parse(data.markdown ?? '')) : ''; let rendered_html = $state('');
run(() => {
rendered_html = browser ? DOMPurify.sanitize(marked.parse(data.markdown ?? '')) : '';
});
</script> </script>
<div class="prose dark:prose-invert"> <div class="prose dark:prose-invert">
@@ -12,11 +12,15 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
export let data: Memory | undefined; interface Props {
let new_pair_data = { data: Memory | undefined;
}
let { data = $bindable() }: Props = $props();
let new_pair_data = $state({
text_1: '', text_1: '',
text_2: '' text_2: ''
}; });
if (!data) { if (!data) {
data = { data = {
@@ -71,9 +75,9 @@ SPDX-License-Identifier: MPL-2.0
rows="3" rows="3"
contenteditable="true" contenteditable="true"
bind:value={new_pair_data.text_1} bind:value={new_pair_data.text_1}
/> ></textarea>
<div class="flex justify-center"> <div class="flex justify-center">
<span class="h-0.5 bg-black block w-11/12" /> <span class="h-0.5 bg-black block w-11/12"></span>
</div> </div>
<BrownButton>{$t('quiztivity.memory.editor.upload_image')}</BrownButton> <BrownButton>{$t('quiztivity.memory.editor.upload_image')}</BrownButton>
</div> </div>
@@ -84,9 +88,9 @@ SPDX-License-Identifier: MPL-2.0
rows="3" rows="3"
contenteditable="true" contenteditable="true"
bind:value={new_pair_data.text_2} bind:value={new_pair_data.text_2}
/> ></textarea>
<div class="flex justify-center"> <div class="flex justify-center">
<span class="h-0.5 bg-black block w-11/12" /> <span class="h-0.5 bg-black block w-11/12"></span>
</div> </div>
<BrownButton>{$t('quiztivity.memory.editor.upload_image')}</BrownButton> <BrownButton>{$t('quiztivity.memory.editor.upload_image')}</BrownButton>
</div> </div>
@@ -9,7 +9,11 @@ SPDX-License-Identifier: MPL-2.0
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let data: Memory | undefined; interface Props {
data: Memory | undefined;
}
let { data }: Props = $props();
const shuffle = <Type>(a: Array<Type>): Array<Type> => { const shuffle = <Type>(a: Array<Type>): Array<Type> => {
for (let i = a.length - 1; i > 0; i--) { for (let i = a.length - 1; i > 0; i--) {
@@ -19,7 +23,7 @@ SPDX-License-Identifier: MPL-2.0
return a; return a;
}; };
let card_opened = {}; let card_opened = $state({});
const get_all_cards_as_single_array = (): MemoryCard[] => { const get_all_cards_as_single_array = (): MemoryCard[] => {
console.log('data', data.cards); console.log('data', data.cards);
@@ -43,7 +47,7 @@ SPDX-License-Identifier: MPL-2.0
const found_cards: string[] = []; const found_cards: string[] = [];
let opened_active_cards: string[] = []; let opened_active_cards: string[] = [];
let try_count = 0; let try_count = $state(0);
let game_finished = false; let game_finished = false;
const flip_card = (id: string) => { const flip_card = (id: string) => {
if (found_cards.includes(id) || game_finished) { if (found_cards.includes(id) || game_finished) {
@@ -83,7 +87,7 @@ SPDX-License-Identifier: MPL-2.0
{#each random_card_order as card} {#each random_card_order as card}
<button <button
class="aspect-square flex border-[#B07156] border-2 rounded-sm" class="aspect-square flex border-[#B07156] border-2 rounded-sm"
on:click={() => { onclick={() => {
flip_card(card.id); flip_card(card.id);
}} }}
> >
+16 -8
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import type { Data } from './types'; import type { Data } from './types';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import BrownButton from '$lib/components/buttons/brown.svelte'; import BrownButton from '$lib/components/buttons/brown.svelte';
@@ -21,13 +23,17 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let data: Data; interface Props {
export let saving: boolean; data: Data;
saving: boolean;
}
let selected_slide = null; let { data = $bindable(), saving }: Props = $props();
let opened_slide = null;
let selected_type = undefined; let selected_slide = $state(null);
let shares_menu_open = false; let opened_slide = $state(null);
let selected_type = $state(undefined);
let shares_menu_open = $state(false);
for (let i = 0; i < data.pages.length; i++) { for (let i = 0; i < data.pages.length; i++) {
const id = (Math.random() + 1).toString(36).substring(7); const id = (Math.random() + 1).toString(36).substring(7);
@@ -42,7 +48,9 @@ SPDX-License-Identifier: MPL-2.0
data.pages.push({ title: undefined, data: undefined, type, id }); data.pages.push({ title: undefined, data: undefined, type, id });
opened_slide = data.pages.length - 1; opened_slide = data.pages.length - 1;
}; };
$: handle_slide_add(selected_type); run(() => {
handle_slide_add(selected_type);
});
const delete_slide = () => { const delete_slide = () => {
console.log(selected_slide); console.log(selected_slide);
@@ -81,7 +89,7 @@ SPDX-License-Identifier: MPL-2.0
> >
</div> </div>
{:else} {:else}
<span /> <span></span>
{/if} {/if}
<input <input
class="bg-transparent outline-hidden text-center mx-auto" class="bg-transparent outline-hidden text-center mx-auto"
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { preventDefault } from 'svelte/legacy';
import BrownButton from '$lib/components/buttons/brown.svelte'; import BrownButton from '$lib/components/buttons/brown.svelte';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import Spinner from '$lib/Spinner.svelte'; import Spinner from '$lib/Spinner.svelte';
@@ -16,9 +18,13 @@ SPDX-License-Identifier: MPL-2.0
import { fade, fly } from 'svelte/transition'; import { fade, fly } from 'svelte/transition';
const { t } = getLocalization(); const { t } = getLocalization();
export let open = false; interface Props {
export let id; open?: boolean;
let popover_open = false; id: any;
}
let { open = $bindable(false), id }: Props = $props();
let popover_open = $state(false);
const load_shares = async (): Promise<{ const load_shares = async (): Promise<{
id: string; id: string;
name?: string; name?: string;
@@ -73,8 +79,8 @@ SPDX-License-Identifier: MPL-2.0
onMount(() => { onMount(() => {
document.body.addEventListener('keydown', close_start_game_if_esc_is_pressed); document.body.addEventListener('keydown', close_start_game_if_esc_is_pressed);
}); });
let never_expires_checked = true; let never_expires_checked = $state(true);
let selected_date = undefined; let selected_date = $state(undefined);
const create_share = async () => { const create_share = async () => {
if (!selected_date && !never_expires_checked) { if (!selected_date && !never_expires_checked) {
return; return;
@@ -95,7 +101,7 @@ SPDX-License-Identifier: MPL-2.0
}); });
loaded_shares = load_shares(); loaded_shares = load_shares();
}; };
let loaded_shares = load_shares(); let loaded_shares = $state(load_shares());
const delete_share = async (id: string) => { const delete_share = async (id: string) => {
if (!confirm('Do you really want to delete this Share?')) { if (!confirm('Do you really want to delete this Share?')) {
@@ -117,13 +123,13 @@ SPDX-License-Identifier: MPL-2.0
return false; return false;
} }
}; };
let add_shares_open = false; let add_shares_open = $state(false);
</script> </script>
<SmallPopover bind:open={popover_open} type={PopoverTypes.Copy} /> <SmallPopover bind:open={popover_open} type={PopoverTypes.Copy} />
<div <div
class="fixed w-full h-full top-0 flex bg-black/50 z-50" class="fixed w-full h-full top-0 flex bg-black/50 z-50"
on:click={on_parent_click} onclick={on_parent_click}
transition:fade={{ duration: 100 }} transition:fade={{ duration: 100 }}
> >
<div <div
@@ -139,7 +145,7 @@ SPDX-License-Identifier: MPL-2.0
<form <form
class="flex justify-center p-2 border-b-2 border-l-2 border-r-2 border-[#B07156] flex-col gap-2" class="flex justify-center p-2 border-b-2 border-l-2 border-r-2 border-[#B07156] flex-col gap-2"
transition:fly={{ duration: 100, y: -10 }} transition:fly={{ duration: 100, y: -10 }}
on:submit|preventDefault={create_share} onsubmit={preventDefault(create_share)}
> >
<div class="grid grid-cols-2"> <div class="grid grid-cols-2">
<input <input
+2 -2
View File
@@ -4,12 +4,12 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0 SPDX-License-Identifier: MPL-2.0
--> -->
<script> <script lang="ts">
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let quiz;
import ImportedOrNot from '$lib/view_quiz/imported_or_not.svelte'; import ImportedOrNot from '$lib/view_quiz/imported_or_not.svelte';
let { quiz } = $props();
</script> </script>
<div class="flex justify-center"> <div class="flex justify-center">
+6
View File
@@ -0,0 +1,6 @@
/*
SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0
*/
export const navbarVisible = $state({ visible: true });
-1
View File
@@ -4,7 +4,6 @@
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
export const navbarVisible = writable(true);
export const signedIn = writable(false); export const signedIn = writable(false);
export const pathname = writable('/'); export const pathname = writable('/');
+8 -3
View File
@@ -5,7 +5,12 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let hovering: boolean; interface Props {
hovering: boolean;
children?: import('svelte').Snippet<[any]>;
}
let { hovering = $bindable(), children }: Props = $props();
function enter() { function enter() {
hovering = true; hovering = true;
@@ -16,6 +21,6 @@ SPDX-License-Identifier: MPL-2.0
} }
</script> </script>
<div on:mouseenter={enter} on:mouseleave={leave}> <div onmouseenter={enter} onmouseleave={leave}>
<slot {hovering} /> {@render children?.({ hovering, })}
</div> </div>
@@ -9,12 +9,16 @@ SPDX-License-Identifier: MPL-2.0
import Hoverable from '$lib/view_quiz/Hoverable.svelte'; import Hoverable from '$lib/view_quiz/Hoverable.svelte';
import { createTippy } from 'svelte-tippy'; import { createTippy } from 'svelte-tippy';
export let quiz: QuizData; interface Props {
quiz: QuizData;
}
let FeedBackButtonsHovered = { let { quiz = $bindable() }: Props = $props();
let FeedBackButtonsHovered = $state({
dislike: false, dislike: false,
like: false like: false
}; });
const tippy = createTippy({ const tippy = createTippy({
arrow: true, arrow: true,
animation: 'perspective-subtle', animation: 'perspective-subtle',
@@ -50,7 +54,7 @@ SPDX-License-Identifier: MPL-2.0
class="bg-green-500 rounded-full h-10 w-10 transition" class="bg-green-500 rounded-full h-10 w-10 transition"
use:tippy={{ content: 'Like this quiz!' }} use:tippy={{ content: 'Like this quiz!' }}
class:opacity-40={FeedBackButtonsHovered.dislike} class:opacity-40={FeedBackButtonsHovered.dislike}
on:click={() => complete_action(true)} onclick={() => complete_action(true)}
> >
<!-- heroicons/thumb-up --> <!-- heroicons/thumb-up -->
<svg <svg
@@ -74,7 +78,7 @@ SPDX-License-Identifier: MPL-2.0
class="rounded-full bg-red-500 h-10 w-10 transition" class="rounded-full bg-red-500 h-10 w-10 transition"
use:tippy={{ content: 'Dislike this quiz!' }} use:tippy={{ content: 'Dislike this quiz!' }}
class:opacity-40={FeedBackButtonsHovered.like} class:opacity-40={FeedBackButtonsHovered.like}
on:click={() => complete_action(false)} onclick={() => complete_action(false)}
> >
<!-- heroicons/thumb-down --> <!-- heroicons/thumb-down -->
<svg <svg
@@ -96,7 +100,7 @@ SPDX-License-Identifier: MPL-2.0
<span class="text-center">{quiz.likes}</span> <span class="text-center">{quiz.likes}</span>
<span class="text-center">{quiz.dislikes}</span> <span class="text-center">{quiz.dislikes}</span>
</div> </div>
<span class="w-full border-t-2 border-[#B07156]" /> <span class="w-full border-t-2 border-[#B07156]"></span>
<div class="mx-auto grid grid-cols-2 gap-2"> <div class="mx-auto grid grid-cols-2 gap-2">
<div class="flex flex-col"> <div class="flex flex-col">
<!-- heroicons/legacy-outline/Play --> <!-- heroicons/legacy-outline/Play -->
@@ -7,7 +7,11 @@ SPDX-License-Identifier: MPL-2.0
<script lang="ts"> <script lang="ts">
import { createTippy } from 'svelte-tippy'; import { createTippy } from 'svelte-tippy';
export let imported: boolean | undefined; interface Props {
imported: boolean | undefined;
}
let { imported }: Props = $props();
const tippy = createTippy({ const tippy = createTippy({
arrow: true, arrow: true,
animation: 'perspective-subtle', animation: 'perspective-subtle',
+4 -4
View File
@@ -5,12 +5,12 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { navbarVisible } from '$lib/stores'; import { navbarVisible } from '$lib/stores.svelte.ts';
import { page } from '$app/stores'; import { page } from '$app/state';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
navbarVisible.set(true); navbarVisible.visible = true;
let status = $page.status; let status = page.status;
const { t } = getLocalization(); const { t } = getLocalization();
</script> </script>
+18 -4
View File
@@ -4,15 +4,21 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0 SPDX-License-Identifier: MPL-2.0
--> -->
<script> <script lang="ts">
import '../app.css'; import '../app.css';
import Navbar from '$lib/navbar.svelte'; 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 * as Sentry from '@sentry/browser';
// import { BrowserTracing } from '@sentry/tracing'; // import { BrowserTracing } from '@sentry/tracing';
import { initLocalizationContext } from '$lib/i18n'; import { initLocalizationContext } from '$lib/i18n';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import CommandPalette from '$lib/components/commandpalette.svelte'; import CommandPalette from '$lib/components/commandpalette.svelte';
interface Props {
children?: import('svelte').Snippet;
}
let { children }: Props = $props();
// import Alert from '$lib/modals/alert.svelte'; // import Alert from '$lib/modals/alert.svelte';
/* afterNavigate(() => { /* afterNavigate(() => {
@@ -86,15 +92,23 @@ SPDX-License-Identifier: MPL-2.0
{/if} {/if}
</svelte:head> </svelte:head>
{#if $navbarVisible} <!-- {#if navbarVisible.visible = true.visible}
<Navbar /> <Navbar />
<div class="pt-16 h-screen"> <div class="pt-16 h-screen">
<div class="z-40" /> <div class="z-40"></div>
<slot /> <slot />
</div> </div>
{:else} {:else}
<slot /> <slot />
{/if} -->
{#if navbarVisible.visible}
<Navbar />
<div class="pt-16">
<div class="z-40"></div>
<!-- extra content above slot -->
</div>
{/if} {/if}
<slot />
<CommandPalette /> <CommandPalette />
<!--{#if $alertModal.open ?? false} <!--{#if $alertModal.open ?? false}
+18 -18
View File
@@ -5,7 +5,7 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { navbarVisible } from '$lib/stores'; import { navbarVisible } from '$lib/stores.svelte.ts';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import Footer from '$lib/footer.svelte'; import Footer from '$lib/footer.svelte';
import WebPOpenGraph from '$lib/assets/landing/opengraph-home.webp'; import WebPOpenGraph from '$lib/assets/landing/opengraph-home.webp';
@@ -25,7 +25,7 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
navbarVisible.set(true); navbarVisible.visible = true;
/* interface StatsData { /* interface StatsData {
quiz_count: number; quiz_count: number;
@@ -36,7 +36,7 @@ SPDX-License-Identifier: MPL-2.0
const response = await fetch('/api/v1/stats/combined'); const response = await fetch('/api/v1/stats/combined');
return await response.json(); return await response.json();
};*/ };*/
let newsletterModalOpen; let newsletterModalOpen = $state();
onMount(() => { onMount(() => {
const ls = localStorage.getItem('newsletter'); const ls = localStorage.getItem('newsletter');
newsletterModalOpen = ls === null; newsletterModalOpen = ls === null;
@@ -62,8 +62,8 @@ SPDX-License-Identifier: MPL-2.0
Winners Winners
} }
let selected_create_thing = SelectedCreateThing.Create; let selected_create_thing = $state(SelectedCreateThing.Create);
let selected_play_thing = SelectedPlayThing.Select; let selected_play_thing = $state(SelectedPlayThing.Select);
/* <li>No; /* <li>No;
Tracking < /li> Tracking < /li>
@@ -123,7 +123,7 @@ SPDX-License-Identifier: MPL-2.0
content: $t('index_page.community_driven_content') content: $t('index_page.community_driven_content')
} }
]; ];
let selected_classquiz_reason = 0; let selected_classquiz_reason = $state(0);
</script> </script>
<svelte:head> <svelte:head>
@@ -256,10 +256,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" 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; selected_create_thing = SelectedCreateThing.Create;
}} }}
on:keyup={() => { onkeyup={() => {
selected_create_thing = SelectedCreateThing.Create; selected_create_thing = SelectedCreateThing.Create;
}} }}
class:shadow-2xl={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>
<div <div
class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full" 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; selected_create_thing = SelectedCreateThing.Find;
}} }}
on:keyup={() => { onkeyup={() => {
selected_create_thing = SelectedCreateThing.Find; selected_create_thing = SelectedCreateThing.Find;
}} }}
class:shadow-2xl={selected_create_thing === SelectedCreateThing.Find} class:shadow-2xl={selected_create_thing === SelectedCreateThing.Find}
@@ -399,10 +399,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" 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; selected_play_thing = SelectedPlayThing.Select;
}} }}
on:keyup={() => { onkeyup={() => {
selected_play_thing = SelectedPlayThing.Select; selected_play_thing = SelectedPlayThing.Select;
}} }}
class:shadow-2xl={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>
<div <div
class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full" 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; selected_play_thing = SelectedPlayThing.Results;
}} }}
on:keyup={() => { onkeyup={() => {
selected_play_thing = SelectedPlayThing.Results; selected_play_thing = SelectedPlayThing.Results;
}} }}
class:shadow-2xl={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>
<div <div
class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full" 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; selected_play_thing = SelectedPlayThing.Winners;
}} }}
on:keyup={() => { onkeyup={() => {
selected_play_thing = SelectedPlayThing.Winners; selected_play_thing = SelectedPlayThing.Winners;
}} }}
class:shadow-2xl={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} {#each classquiz_reasons as reason, index}
<div <div
class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full" class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
on:click={() => { onclick={() => {
selected_classquiz_reason = index; selected_classquiz_reason = index;
}} }}
on:keyup={() => { onkeyup={() => {
selected_classquiz_reason = index; selected_classquiz_reason = index;
}} }}
class:shadow-2xl={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"> <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> </script>
<slot /> {@render children?.()}
@@ -9,7 +9,11 @@ SPDX-License-Identifier: MPL-2.0
import BrownButton from '$lib/components/buttons/brown.svelte'; import BrownButton from '$lib/components/buttons/brown.svelte';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let data: PageData; interface Props {
data: PageData;
}
let { data }: Props = $props();
const controllers: [] = data.controllers; const controllers: [] = data.controllers;
</script> </script>
@@ -33,6 +37,7 @@ SPDX-License-Identifier: MPL-2.0
</div> </div>
</div> </div>
<table class="w-full"> <table class="w-full">
<thead>
<tr class="border-b-2 dark:border-gray-500 text-left border-gray-300"> <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" <th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('words.name')}</th >{$t('words.name')}</th
@@ -48,6 +53,8 @@ SPDX-License-Identifier: MPL-2.0
</th> </th>
<th class="mx-auto p-1">{$t('words.version')}</th> <th class="mx-auto p-1">{$t('words.version')}</th>
</tr> </tr>
</thead>
<tbody>
{#each data.controllers as controller} {#each data.controllers as controller}
<tr class="text-left"> <tr class="text-left">
<td class="border-r dark:border-gray-500 p-1 border-gray-300" <td class="border-r dark:border-gray-500 p-1 border-gray-300"
@@ -69,9 +76,12 @@ SPDX-License-Identifier: MPL-2.0
? new Date(controller.last_seen).toLocaleString() ? new Date(controller.last_seen).toLocaleString()
: $t('words.never')}</td : $t('words.never')}</td
> >
<td class="mx-auto p-1">{controller.os_version ?? $t('words.unknown')}</td> <td class="mx-auto p-1"
>{controller.os_version ?? $t('words.unknown')}</td
>
</tr> </tr>
{/each} {/each}
</tbody>
</table> </table>
</div> </div>
{/if} {/if}
@@ -15,12 +15,16 @@ SPDX-License-Identifier: MPL-2.0
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let data: PageData; interface Props {
const controller = data.controller; 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; let save_player_timer;
const save_player_name_debounce = () => { const save_player_name_debounce = () => {
@@ -95,7 +99,7 @@ SPDX-License-Identifier: MPL-2.0
<input <input
class="rounded-sm p-1 transition-all outline-hidden text-center dark:bg-gray-700" class="rounded-sm p-1 transition-all outline-hidden text-center dark:bg-gray-700"
bind:value={controller.name} bind:value={controller.name}
on:keyup={save_player_name_debounce} onkeyup={save_player_name_debounce}
/> />
<SaveIndicator status={saved_status} /> <SaveIndicator status={saved_status} />
</div> </div>
@@ -106,7 +110,7 @@ SPDX-License-Identifier: MPL-2.0
<input <input
class="rounded-sm p-1 transition-all outline-hidden text-center dark:bg-gray-700" class="rounded-sm p-1 transition-all outline-hidden text-center dark:bg-gray-700"
bind:value={controller.player_name} bind:value={controller.player_name}
on:keyup={save_player_name_debounce} onkeyup={save_player_name_debounce}
/> />
<SaveIndicator status={saved_status} /> <SaveIndicator status={saved_status} />
</div> </div>
@@ -6,7 +6,11 @@ SPDX-License-Identifier: MPL-2.0
<script lang="ts"> <script lang="ts">
import { SaveStatus } from './commons'; import { SaveStatus } from './commons';
export let status: SaveStatus; interface Props {
status: SaveStatus;
}
let { status }: Props = $props();
</script> </script>
<div class="h-6 w-6 grow-0 my-auto"> <div class="h-6 w-6 grow-0 my-auto">
@@ -5,19 +5,27 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import type { PageData } from './$types'; import type { PageData } from './$types';
export let data: PageData; interface Props {
let input_data = { data: PageData;
}
let { data }: Props = $props();
let input_data = $state({
player_name: data.username, player_name: data.username,
name: '' name: ''
}; });
let isValid = false; let isValid = $state(false);
let isSubmitting = 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 () => { const submit = async () => {
if (!isValid) { if (!isValid) {
@@ -51,7 +59,7 @@ SPDX-License-Identifier: MPL-2.0
Add a controller Add a controller
</h3> </h3>
<form on:submit|preventDefault={submit}> <form onsubmit={preventDefault(submit)}>
<div class="w-full mt-4"> <div class="w-full mt-4">
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg"> <div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
<div class="relative bg-inherit w-full"> <div class="relative bg-inherit w-full">
@@ -10,9 +10,13 @@ SPDX-License-Identifier: MPL-2.0
import Spinner from '$lib/Spinner.svelte'; import Spinner from '$lib/Spinner.svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
export let data: PageData; interface Props {
let controller_seen = false; data: PageData;
let check_tick = 0; }
let { data }: Props = $props();
let controller_seen = $state(false);
let check_tick = $state(0);
let interval; let interval;
const check_if_controller_was_seen = async () => { const check_if_controller_was_seen = async () => {
+15 -10
View File
@@ -5,7 +5,10 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <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 { slide } from 'svelte/transition';
import Footer from '$lib/footer.svelte'; import Footer from '$lib/footer.svelte';
import VerifiedBadge from './verified_badge.svelte'; import VerifiedBadge from './verified_badge.svelte';
@@ -17,15 +20,15 @@ SPDX-License-Identifier: MPL-2.0
import TotpComponent from './totp_component.svelte'; import TotpComponent from './totp_component.svelte';
import { browserSupportsWebAuthn } from '@simplewebauthn/browser'; import { browserSupportsWebAuthn } from '@simplewebauthn/browser';
navbarVisible.set(true); navbarVisible.visible = true;
export let data; let { data } = $props();
const { verified }: boolean = data; const { verified }: boolean = data;
let session_data = {}; let session_data = $state({});
let step = 0; let step = $state(0);
let selected_method = null; let selected_method = $state(null);
let done = false; let done = $state(false);
const redirect_back = (done_var: boolean) => { const redirect_back = (done_var: boolean) => {
if (done_var) { if (done_var) {
@@ -35,7 +38,9 @@ SPDX-License-Identifier: MPL-2.0
} }
}; };
let alertModalOpen = false; let alertModalOpen = false;
$: redirect_back(done); run(() => {
redirect_back(done);
});
alertModal.subscribe((data) => { alertModal.subscribe((data) => {
if (!alertModalOpen && data.open) { if (!alertModalOpen && data.open) {
@@ -74,10 +79,10 @@ SPDX-License-Identifier: MPL-2.0
} }
} }
}; };
$: { run(() => {
check_auto(); check_auto();
step; step;
} });
</script> </script>
<svelte:head> <svelte:head>
@@ -5,19 +5,25 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let session_data; let {
export let selected_method; session_data,
export let done; selected_method = $bindable(),
export let step; done = $bindable(),
step = $bindable()
} = $props();
let backup_code = ''; let backup_code = $state('');
let isSubmitting = false; let isSubmitting = $state(false);
let backup_code_valid = false; let backup_code_valid = $state(false);
$: backup_code_valid = backup_code.length === 64; run(() => {
backup_code_valid = backup_code.length === 64;
});
const continue_in_login = async () => { const continue_in_login = async () => {
if (!backup_code_valid) { if (!backup_code_valid) {
@@ -44,7 +50,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="px-6 py-4"> <div class="px-6 py-4">
<h2 class="text-3xl font-bold text-center text-gray-700 dark:text-white">ClassQuiz</h2> <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="w-full mt-4">
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg"> <div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
<div class="relative bg-inherit w-full"> <div class="relative bg-inherit w-full">
@@ -66,7 +72,7 @@ SPDX-License-Identifier: MPL-2.0
</div> </div>
<div class="flex items-center justify-between mt-4"> <div class="flex items-center justify-between mt-4">
<button <button
on:click={() => { onclick={() => {
selected_method = 'BACKUP'; selected_method = 'BACKUP';
}} }}
class="text-sm text-gray-600 dark:text-gray-200 hover:text-gray-500" 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"> <script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
export let session_data; let {
export let selected_method; session_data,
export let done; selected_method = $bindable(),
export let step; done = $bindable(),
step = $bindable()
} = $props();
const { t } = getLocalization(); const { t } = getLocalization();
let isSubmitting; let isSubmitting;
let password; let password = $state();
const continue_in_login = async () => { const continue_in_login = async () => {
if (!password) { if (!password) {
@@ -56,7 +60,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="px-6 py-4"> <div class="px-6 py-4">
<h2 class="text-3xl font-bold text-center text-gray-700 dark:text-white">ClassQuiz</h2> <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="w-full mt-4">
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg"> <div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
<div class="relative bg-inherit w-full"> <div class="relative bg-inherit w-full">
@@ -79,7 +83,7 @@ SPDX-License-Identifier: MPL-2.0
</div> </div>
<div class="flex items-center justify-between mt-4"> <div class="flex items-center justify-between mt-4">
<button <button
on:click={() => { onclick={() => {
selected_method = 'BACKUP'; selected_method = 'BACKUP';
}} }}
class="text-sm text-gray-600 dark:text-gray-200 hover:text-gray-500" 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 SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let session_data = {}; import { run } from 'svelte/legacy';
export let step;
export let selected_method;
let available_methods; let { session_data = {}, step, selected_method = $bindable() } = $props();
let available_methods = $state();
const set_available_methods = (step_var: number) => { const set_available_methods = (step_var: number) => {
if (step_var === 1) { if (step_var === 1) {
@@ -18,7 +18,9 @@ SPDX-License-Identifier: MPL-2.0
} }
}; };
$: set_available_methods(step); run(() => {
set_available_methods(step);
});
</script> </script>
<div class="px-6 py-4"> <div class="px-6 py-4">
@@ -30,10 +32,10 @@ SPDX-License-Identifier: MPL-2.0
{#if available_methods.includes('PASSKEY')} {#if available_methods.includes('PASSKEY')}
<div <div
class="flex flex-row bg-gray-100 dark:bg-gray-700 rounded-lg p-2 hover:cursor-pointer hover:bg-gray-200 transition" 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'; selected_method = 'PASSKEY';
}} }}
on:keyup={() => { onkeyup={() => {
selected_method = 'PASSKEY'; selected_method = 'PASSKEY';
}} }}
> >
@@ -61,10 +63,10 @@ SPDX-License-Identifier: MPL-2.0
{#if available_methods.includes('PASSWORD')} {#if available_methods.includes('PASSWORD')}
<div <div
class="flex flex-row bg-gray-100 dark:bg-gray-700 rounded-lg p-2 hover:cursor-pointer hover:bg-gray-200 transition" 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'; selected_method = 'PASSWORD';
}} }}
on:keyup={() => { onkeyup={() => {
selected_method = 'PASSWORD'; selected_method = 'PASSWORD';
}} }}
> >
@@ -107,10 +109,10 @@ SPDX-License-Identifier: MPL-2.0
{#if available_methods.includes('TOTP')} {#if available_methods.includes('TOTP')}
<div <div
class="flex flex-row bg-gray-100 rounded-lg p-2 hover:cursor-pointer hover:bg-gray-200 transition" 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'; selected_method = 'TOTP';
}} }}
on:keyup={() => { onkeyup={() => {
selected_method = 'TOTP'; selected_method = 'TOTP';
}} }}
> >
@@ -5,18 +5,21 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import OAuthBlock from './oauth_block.svelte'; import OAuthBlock from './oauth_block.svelte';
export let session_data = {}; let { session_data = $bindable({}), step = $bindable() } = $props();
export let step;
const { t } = getLocalization(); const { t } = getLocalization();
let email = ''; let email = $state('');
let emailEmpty = true; let emailEmpty = $state(true);
let isSubmitting = false; let isSubmitting = $state(false);
$: emailEmpty = email === ''; run(() => {
emailEmpty = email === '';
});
const start_login = async (): Promise<void> => { const start_login = async (): Promise<void> => {
if (emailEmpty) { if (emailEmpty) {
@@ -47,7 +50,7 @@ SPDX-License-Identifier: MPL-2.0
{$t('login_page.login_or_create_account')} {$t('login_page.login_or_create_account')}
</p> </p>
<form on:submit|preventDefault={start_login}> <form onsubmit={preventDefault(start_login)}>
<div class="w-full mt-4"> <div class="w-full mt-4">
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg"> <div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
<div class="relative bg-inherit w-full"> <div class="relative bg-inherit w-full">
@@ -5,21 +5,35 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { getLocalization } from '$lib/i18n'; import { run, preventDefault } from 'svelte/legacy';
// import { alertModal } from '$lib/stores';
export let session_data; import { getLocalization } from '$lib/i18n';
export let selected_method;
export let done;
export let step; 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(); const { t } = getLocalization();
let isSubmitting; 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 () => { const continue_in_login = async () => {
if (!totp_valid) { if (!totp_valid) {
@@ -70,7 +84,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="px-6 py-4"> <div class="px-6 py-4">
<h2 class="text-3xl font-bold text-center text-gray-700 dark:text-white">ClassQuiz</h2> <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="w-full mt-4">
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg"> <div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
<div class="relative bg-inherit w-full"> <div class="relative bg-inherit w-full">
@@ -93,7 +107,7 @@ SPDX-License-Identifier: MPL-2.0
</div> </div>
<div class="flex items-center justify-between mt-4"> <div class="flex items-center justify-between mt-4">
<button <button
on:click={() => { onclick={() => {
selected_method = 'BACKUP'; selected_method = 'BACKUP';
}} }}
class="text-sm text-gray-600 dark:text-gray-200 hover:text-gray-500" 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'; // import { alertModal } from '$lib/stores';
const { t } = getLocalization(); const { t } = getLocalization();
export let session_data; let {
export let selected_method; session_data,
export let done; selected_method = $bindable(),
export let step; done = $bindable(),
step = $bindable()
} = $props();
let isLoading = false; let isLoading = $state(false);
const start_thing = async () => { const start_thing = async () => {
const data = JSON.parse(session_data.webauthn_data); 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="w-full mt-4">
<div class="flex items-center justify-between mt-4"> <div class="flex items-center justify-between mt-4">
<button <button
on:click={() => { onclick={() => {
selected_method = 'BACKUP'; selected_method = 'BACKUP';
}} }}
class="text-sm text-gray-600 dark:text-gray-200 hover:text-gray-500" 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 <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" 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} disabled={isLoading}
on:click={start_thing} onclick={start_thing}
> >
{#if isLoading} {#if isLoading}
<svg class="h-4 w-4 animate-spin mx-auto" viewBox="3 3 18 18"> <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"> <script lang="ts">
export let data; let { data } = $props();
const { error } = data; const { error } = data;
</script> </script>
@@ -5,25 +5,29 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let data; let { data } = $props();
let { token }: string = data; let { token }: string = data;
let isSubmitting = false; let isSubmitting = $state(false);
interface PasswordData { interface PasswordData {
password1: string; password1: string;
password2: string; password2: string;
} }
let passwordData: PasswordData = { let passwordData: PasswordData = $state({
password1: '', password1: '',
password2: '' password2: ''
}; });
let passwordsValid = false; let passwordsValid = $state(false);
const checkIfPasswordsValid = (pwdata: PasswordData): void => { const checkIfPasswordsValid = (pwdata: PasswordData): void => {
passwordsValid = pwdata.password1 === pwdata.password2 && pwdata.password1.length >= 8; passwordsValid = pwdata.password1 === pwdata.password2 && pwdata.password1.length >= 8;
}; };
$: checkIfPasswordsValid(passwordData); run(() => {
checkIfPasswordsValid(passwordData);
});
const submit = async () => { const submit = async () => {
if (!passwordsValid) { if (!passwordsValid) {
@@ -73,7 +77,7 @@ SPDX-License-Identifier: MPL-2.0
{$t('password_reset_page.reset_password')} {$t('password_reset_page.reset_password')}
</p> </p>
<form on:submit|preventDefault={submit}> <form onsubmit={preventDefault(submit)}>
<div class="w-full mt-4"> <div class="w-full mt-4">
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg"> <div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
<div class="relative bg-inherit w-full"> <div class="relative bg-inherit w-full">
@@ -8,13 +8,13 @@ SPDX-License-Identifier: MPL-2.0
import { createForm } from 'felte'; import { createForm } from 'felte';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import { validateSchema } from '@felte/validator-yup'; import { validateSchema } from '@felte/validator-yup';
import { navbarVisible } from '$lib/stores'; import { navbarVisible } from '$lib/stores.svelte.ts';
import Footer from '$lib/footer.svelte'; import Footer from '$lib/footer.svelte';
const { t } = getLocalization(); const { t } = getLocalization();
import reporter from '@felte/reporter-tippy'; import reporter from '@felte/reporter-tippy';
navbarVisible.set(true); navbarVisible.visible = true;
import * as yup from 'yup'; import * as yup from 'yup';
const registerSchema = yup.object({ const registerSchema = yup.object({
@@ -71,10 +71,10 @@ SPDX-License-Identifier: MPL-2.0
responseData.open = true; responseData.open = true;
} }
}); });
let responseData = { let responseData = $state({
open: false, open: false,
data: '' data: ''
}; });
</script> </script>
<svelte:head> <svelte:head>
@@ -270,7 +270,7 @@ SPDX-License-Identifier: MPL-2.0
<div <div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0" 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. --> <!-- 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" <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"> <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button <button
type="button" type="button"
on:click={() => { onclick={() => {
responseData.open = false; responseData.open = false;
window.location.assign('/'); window.location.assign('/');
}} }}
@@ -5,14 +5,16 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { getLocalization } from '$lib/i18n'; import { preventDefault } from 'svelte/legacy';
import { navbarVisible } from '$lib/stores';
navbarVisible.set(true); import { getLocalization } from '$lib/i18n';
import { navbarVisible } from '$lib/stores.svelte.ts';
navbarVisible.visible = true;
const { t } = getLocalization(); const { t } = getLocalization();
let isSubmitting = false; let isSubmitting = $state(false);
const submit = async () => { const submit = async () => {
isSubmitting = true; isSubmitting = true;
@@ -34,7 +36,7 @@ SPDX-License-Identifier: MPL-2.0
isSubmitting = false; isSubmitting = false;
}; };
let email = ''; let email = $state('');
</script> </script>
<svelte:head> <svelte:head>
@@ -60,7 +62,7 @@ SPDX-License-Identifier: MPL-2.0
{$t('password_reset_page.reset_password')} {$t('password_reset_page.reset_password')}
</p> </p>
<form on:submit|preventDefault={submit}> <form onsubmit={preventDefault(submit)}>
<div class="w-full mt-4"> <div class="w-full mt-4">
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg"> <div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
<div class="relative bg-inherit w-full"> <div class="relative bg-inherit w-full">
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { UAParser } from 'ua-parser-js'; import { UAParser } from 'ua-parser-js';
@@ -28,16 +30,16 @@ SPDX-License-Identifier: MPL-2.0
newPasswordConfirm: string; newPasswordConfirm: string;
} }
let changePasswordData: ChangePasswordData = { let changePasswordData: ChangePasswordData = $state({
oldPassword: '', oldPassword: '',
newPassword: '', newPassword: '',
newPasswordConfirm: '' newPasswordConfirm: ''
}; });
let locationData; let locationData;
let this_session; let this_session = $state();
let passwordChangeDataValid = false; let passwordChangeDataValid = $state(false);
const checkPasswords = (data: ChangePasswordData): void => { const checkPasswords = (data: ChangePasswordData): void => {
passwordChangeDataValid = passwordChangeDataValid =
data.newPassword === data.newPasswordConfirm && data.newPassword === data.newPasswordConfirm &&
@@ -45,7 +47,9 @@ SPDX-License-Identifier: MPL-2.0
data.oldPassword !== data.newPassword && data.oldPassword !== data.newPassword &&
data.oldPassword !== ''; data.oldPassword !== '';
}; };
$: checkPasswords(changePasswordData); run(() => {
checkPasswords(changePasswordData);
});
const changePassword = async () => { const changePassword = async () => {
if (!passwordChangeDataValid) { if (!passwordChangeDataValid) {
return; return;
@@ -93,7 +97,7 @@ SPDX-License-Identifier: MPL-2.0
return api_keys_temp; return api_keys_temp;
}; };
let api_keys; let api_keys = $state();
const add_api_key = async () => { const add_api_key = async () => {
await fetch('/api/v1/users/api_keys', { method: 'POST' }); await fetch('/api/v1/users/api_keys', { method: 'POST' });
@@ -180,7 +184,7 @@ SPDX-License-Identifier: MPL-2.0
</div> </div>
</div> </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 <label
>{$t('settings_page.old_password')}:<input >{$t('settings_page.old_password')}:<input
type="password" 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" class="py-4 px-6 text-sm text-gray-500 whitespace-nowrap dark:text-gray-400"
> >
<button <button
on:click={() => { onclick={() => {
deleteSession(session.id); deleteSession(session.id);
}}>{$t('words.delete')}</button }}>{$t('words.delete')}</button
> >

Some files were not shown because too many files have changed in this diff Show More