Updated to Svelte 5
This commit is contained in:
@@ -5,7 +5,11 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
export let my_20 = true;
|
||||
interface Props {
|
||||
my_20?: boolean;
|
||||
}
|
||||
|
||||
let { my_20 = true }: Props = $props();
|
||||
</script>
|
||||
|
||||
<svg class="h-8 w-8 animate-spin mx-auto" class:my-20={my_20} viewBox="3 3 18 18">
|
||||
|
||||
@@ -15,25 +15,37 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import Controls from '$lib/play/admin/controls.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 default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58'];
|
||||
|
||||
let question_results = null;
|
||||
export let final_results: Array<null> | Array<Array<PlayerAnswer>> = [null];
|
||||
let selected_question = -1;
|
||||
let timer_res: string;
|
||||
let shown_question_now: number;
|
||||
let final_results_clicked = false;
|
||||
let question_results = $state(null);
|
||||
let selected_question = $state(-1);
|
||||
let timer_res: string = $state();
|
||||
let shown_question_now: number = $state();
|
||||
let final_results_clicked = $state(false);
|
||||
let timer_interval;
|
||||
let answer_count = 0;
|
||||
export let control_visible: boolean;
|
||||
let answer_count = $state(0);
|
||||
|
||||
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', () => {
|
||||
console.log('get_question_results');
|
||||
@@ -110,7 +122,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
class:mt-10={control_visible}
|
||||
style="width: {(100 / parseInt(quiz_data.questions[selected_question].time)) *
|
||||
parseInt(timer_res)}vw"
|
||||
/>
|
||||
></span>
|
||||
{/if}
|
||||
|
||||
<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')}
|
||||
<Spinner my_20={false} />
|
||||
{:then c}
|
||||
<svelte:component
|
||||
this={c.default}
|
||||
<c.default
|
||||
bind:question={quiz_data.questions[selected_question]}
|
||||
/>
|
||||
{/await}
|
||||
@@ -141,8 +152,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{#await import('$lib/play/admin/voting_results.svelte')}
|
||||
<Spinner />
|
||||
{:then c}
|
||||
<svelte:component
|
||||
this={c.default}
|
||||
<c.default
|
||||
bind:data={question_results}
|
||||
bind:question={quiz_data.questions[selected_question]}
|
||||
/>
|
||||
@@ -151,8 +161,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{#await import('$lib/play/admin/results.svelte')}
|
||||
<Spinner />
|
||||
{:then c}
|
||||
<svelte:component
|
||||
this={c.default}
|
||||
<c.default
|
||||
bind:data={player_scores}
|
||||
question={quiz_data.questions[selected_question]}
|
||||
bind:new_data={question_results}
|
||||
|
||||
@@ -4,17 +4,22 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script>
|
||||
export let headerText;
|
||||
<script lang="ts">
|
||||
|
||||
export let expanded = false;
|
||||
interface Props {
|
||||
headerText: any;
|
||||
expanded?: boolean;
|
||||
children?: import('svelte').Snippet;
|
||||
}
|
||||
|
||||
let { headerText, expanded = $bindable(false), children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<h3 class="bg-transparent m-0">
|
||||
<button
|
||||
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:rounded-b-lg={!expanded}
|
||||
><span>{@html headerText}</span>
|
||||
@@ -31,7 +36,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
</h3>
|
||||
|
||||
<div class:hidden={!expanded}>
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -10,7 +10,11 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import { getLocalization } from '$lib/i18n';
|
||||
|
||||
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 = () => {
|
||||
quiz_id = null;
|
||||
@@ -27,7 +31,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{#if quiz_id}
|
||||
<div
|
||||
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 }}
|
||||
>
|
||||
<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">
|
||||
export let disabled = false;
|
||||
export let flex = false;
|
||||
import { createBubbler } from 'svelte/legacy';
|
||||
|
||||
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>
|
||||
|
||||
{#if href}
|
||||
@@ -24,21 +39,21 @@ SPDX-License-Identifier: MPL-2.0
|
||||
class:cursor-not-allowed={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"
|
||||
on:click
|
||||
onclick={bubble('click')}
|
||||
class:flex
|
||||
class:justify-center={flex}
|
||||
>
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</a>
|
||||
{:else}
|
||||
<button
|
||||
{disabled}
|
||||
{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"
|
||||
on:click
|
||||
onclick={bubble('click')}
|
||||
class:flex
|
||||
class:justify-center={flex}
|
||||
>
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -5,11 +5,25 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
export let disabled = false;
|
||||
export let flex = false;
|
||||
import { createBubbler } from 'svelte/legacy';
|
||||
|
||||
export let href: undefined | string = undefined;
|
||||
export let target: undefined | string = '_self';
|
||||
const bubble = createBubbler();
|
||||
|
||||
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>
|
||||
|
||||
{#if href}
|
||||
@@ -17,21 +31,21 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{href}
|
||||
{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"
|
||||
on:click
|
||||
onclick={bubble('click')}
|
||||
class:flex
|
||||
class:block={!flex}
|
||||
class:justify-center={flex}
|
||||
>
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</a>
|
||||
{:else}
|
||||
<button
|
||||
{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"
|
||||
on:click
|
||||
onclick={bubble('click')}
|
||||
class:flex
|
||||
class:justify-center={flex}
|
||||
>
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -8,17 +8,19 @@ SPDX-License-Identifier: MPL-2.0
|
||||
This should be okay, right?
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
import { tinykeys } from '$lib/tinykeys';
|
||||
import { fade } from 'svelte/transition';
|
||||
import MiniSearch from 'minisearch';
|
||||
|
||||
let open = false;
|
||||
let input = '';
|
||||
let bg_text = '';
|
||||
let open = $state(false);
|
||||
let input = $state('');
|
||||
let bg_text = $state('');
|
||||
let title_ms: MiniSearch;
|
||||
let command_ms: MiniSearch;
|
||||
let selected: null | number = null;
|
||||
let selected: null | number = $state(null);
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
type ActionFunction = (args: string[]) => void;
|
||||
@@ -97,7 +99,7 @@ This should be okay, right?
|
||||
action: () => window.location.assign('/account/settings')
|
||||
}
|
||||
];
|
||||
let visible_items = actions;
|
||||
let visible_items = $state(actions);
|
||||
|
||||
const toggle_open = (e: KeyboardEvent | undefined) => {
|
||||
e.preventDefault();
|
||||
@@ -204,9 +206,13 @@ This should be okay, right?
|
||||
input = '';
|
||||
};
|
||||
|
||||
$: search(input);
|
||||
run(() => {
|
||||
search(input);
|
||||
});
|
||||
// $: input = lower_input(input)
|
||||
$: input = input.toLowerCase();
|
||||
run(() => {
|
||||
input = input.toLowerCase();
|
||||
});
|
||||
onMount(async () => {
|
||||
tinykeys(window, {
|
||||
'$mod+k': toggle_open,
|
||||
@@ -232,8 +238,8 @@ This should be okay, right?
|
||||
{#if open}
|
||||
<div
|
||||
class="fixed top-0 left-0 w-screen h-screen flex bg-black/50 z-50"
|
||||
on:click={close_on_outside}
|
||||
on:keyup={close_on_outside}
|
||||
onclick={close_on_outside}
|
||||
onkeyup={close_on_outside}
|
||||
transition:fade|global={{ duration: 60 }}
|
||||
>
|
||||
<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:bg-[#B07156]={selected === i}
|
||||
class:bg-gray-700={selected !== i}
|
||||
on:mouseenter={() => (selected = i)}
|
||||
on:mousedown={execute_action}
|
||||
onmouseenter={() => (selected = i)}
|
||||
onmousedown={execute_action}
|
||||
>
|
||||
<div class="flex">
|
||||
<h3 class="text-lg my-auto">{vi.title}</h3>
|
||||
|
||||
@@ -11,7 +11,11 @@ SPDX-License-Identifier: MPL-2.0
|
||||
y: 'yellow',
|
||||
b: 'blue'
|
||||
};
|
||||
export let code: string;
|
||||
interface Props {
|
||||
code: string;
|
||||
}
|
||||
|
||||
let { code }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row gap-2 mx-auto">
|
||||
@@ -22,7 +26,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
style="background-color: {color_map[
|
||||
c.toLowerCase()
|
||||
]}; width: 2rem; height: {c.toLowerCase() == c ? '2' : '4'}rem"
|
||||
/>
|
||||
></span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
let open = false;
|
||||
let open = $state(false);
|
||||
onMount(() => {
|
||||
if (Cookies.get('commandpalette_notice')) {
|
||||
return;
|
||||
|
||||
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import { fly } from 'svelte/transition';
|
||||
import { getLocalization } from '$lib/i18n';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
@@ -14,12 +16,17 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let open = false;
|
||||
export let type: PopoverTypes;
|
||||
export let data: undefined | { game_pin: number | string; game_id: string } | string =
|
||||
undefined;
|
||||
interface Props {
|
||||
open?: boolean;
|
||||
type: PopoverTypes;
|
||||
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>
|
||||
|
||||
{#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"
|
||||
data-dismiss-target="#toast-default"
|
||||
aria-label="Close"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
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}
|
||||
@@ -15,13 +15,13 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import { getLocalization } from '$lib/i18n';
|
||||
|
||||
const { t } = getLocalization();
|
||||
export let quiz_id;
|
||||
let captcha_selected = false;
|
||||
let selected_game_mode = 'kahoot';
|
||||
let loading = false;
|
||||
let custom_field = '';
|
||||
let cqcs_enabled = false;
|
||||
let randomized_answers = false;
|
||||
let { quiz_id = $bindable() } = $props();
|
||||
let captcha_selected = $state(false);
|
||||
let selected_game_mode = $state('kahoot');
|
||||
let loading = $state(false);
|
||||
let custom_field = $state('');
|
||||
let cqcs_enabled = $state(false);
|
||||
let randomized_answers = $state(false);
|
||||
|
||||
const tippy = createTippy({
|
||||
arrow: true,
|
||||
@@ -96,7 +96,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<div
|
||||
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 }}
|
||||
on:click={on_parent_click}
|
||||
onclick={on_parent_click}
|
||||
>
|
||||
<div
|
||||
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
|
||||
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"
|
||||
>Captcha {captcha_selected ? 'enabled' : 'disabled'}</span
|
||||
>
|
||||
@@ -136,7 +136,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<div
|
||||
class="rounded-lg bg-white shadow-lg cursor-pointer transition-all p-2"
|
||||
class:opacity-50={selected_game_mode !== 'kahoot'}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
selected_game_mode = 'kahoot';
|
||||
}}
|
||||
>
|
||||
@@ -148,7 +148,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<div
|
||||
class="rounded-lg bg-white shadow-lg cursor-pointer transition-all p-2"
|
||||
class:opacity-50={selected_game_mode !== 'normal'}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
selected_game_mode = 'normal';
|
||||
}}
|
||||
>
|
||||
@@ -176,7 +176,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
></span>
|
||||
<span class="ml-3 text-sm font-medium text-gray-900"
|
||||
><a
|
||||
href="/controller"
|
||||
@@ -204,14 +204,14 @@ SPDX-License-Identifier: MPL-2.0
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
></span>
|
||||
<span class="ml-3 text-sm font-medium text-gray-900"> Randomize answers</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<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"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
start_game(quiz_id);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run, preventDefault } from 'svelte/legacy';
|
||||
|
||||
// import { mint } from '$lib/hashcash';
|
||||
import { dataSchema } from '$lib/yupSchemas';
|
||||
import type { EditorData, Question } from './quiz_types';
|
||||
@@ -16,13 +18,17 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
let schemaInvalid = false;
|
||||
let yupErrorMessage = '';
|
||||
let schemaInvalid = $state(false);
|
||||
let yupErrorMessage = $state('');
|
||||
|
||||
export let data: EditorData;
|
||||
export let quiz_id: string | null;
|
||||
let selected_question = -1;
|
||||
let imgur_links_valid = false;
|
||||
interface Props {
|
||||
data: EditorData;
|
||||
quiz_id: string | null;
|
||||
}
|
||||
|
||||
let { data = $bindable(), quiz_id }: Props = $props();
|
||||
let selected_question = $state(-1);
|
||||
let imgur_links_valid = $state(false);
|
||||
|
||||
const validateInput = async (data: EditorData) => {
|
||||
// console.log("input", data)
|
||||
@@ -36,7 +42,9 @@ SPDX-License-Identifier: MPL-2.0
|
||||
yupErrorMessage = err.errors ? err.errors[0] : '';
|
||||
}
|
||||
};
|
||||
$: validateInput(data);
|
||||
run(() => {
|
||||
validateInput(data);
|
||||
});
|
||||
|
||||
const checkIfAllQuestionImagesComplyWithRegex = (questions: Array<Question>) => {
|
||||
let NoteverythingValid = false;
|
||||
@@ -54,11 +62,15 @@ SPDX-License-Identifier: MPL-2.0
|
||||
return NoteverythingValid;
|
||||
};
|
||||
|
||||
$: imgur_links_valid = checkIfAllQuestionImagesComplyWithRegex(data.questions);
|
||||
let edit_id;
|
||||
run(() => {
|
||||
imgur_links_valid = checkIfAllQuestionImagesComplyWithRegex(data.questions);
|
||||
});
|
||||
let edit_id = $state();
|
||||
let confirm_to_leave = true;
|
||||
|
||||
$: console.log('data', data);
|
||||
run(() => {
|
||||
console.log('data', data);
|
||||
});
|
||||
|
||||
const getEditID = async () => {
|
||||
let res;
|
||||
@@ -110,11 +122,11 @@ SPDX-License-Identifier: MPL-2.0
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:window on:beforeunload={confirmUnload} />
|
||||
<svelte:window onbeforeunload={confirmUnload} />
|
||||
{#await getEditID()}
|
||||
<Spinner />
|
||||
{:then _}
|
||||
<form on:submit|preventDefault={saveQuiz}>
|
||||
<form onsubmit={preventDefault(saveQuiz)}>
|
||||
<div class="grid grid-cols-6 h-screen w-screen">
|
||||
<div>
|
||||
<Sidebar bind:data bind:selected_question />
|
||||
|
||||
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run, preventDefault } from 'svelte/legacy';
|
||||
|
||||
import type { Answer, EditorData } from '../quiz_types';
|
||||
import { QuizQuestionType } from '../quiz_types';
|
||||
import { fade } from 'svelte/transition';
|
||||
@@ -17,9 +19,13 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58'];
|
||||
|
||||
export let selected_question: number;
|
||||
export let check_choice = false;
|
||||
export let data: EditorData;
|
||||
interface Props {
|
||||
selected_question: number;
|
||||
check_choice?: boolean;
|
||||
data: EditorData;
|
||||
}
|
||||
|
||||
let { selected_question, check_choice = false, data = $bindable() }: Props = $props();
|
||||
if (!Array.isArray(data.questions[selected_question].answers)) {
|
||||
data.questions[selected_question].answers = [];
|
||||
}
|
||||
@@ -41,7 +47,9 @@ SPDX-License-Identifier: MPL-2.0
|
||||
right: false
|
||||
};
|
||||
};
|
||||
$: save_colors(data);
|
||||
run(() => {
|
||||
save_colors(data);
|
||||
});
|
||||
data.questions[selected_question].type =
|
||||
check_choice === true ? QuizQuestionType.CHECK : QuizQuestionType.ABCD;
|
||||
const set_colors_if_unset = () => {
|
||||
@@ -51,11 +59,11 @@ SPDX-License-Identifier: MPL-2.0
|
||||
}
|
||||
}
|
||||
};
|
||||
$: {
|
||||
run(() => {
|
||||
set_colors_if_unset();
|
||||
data;
|
||||
selected_question;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<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
|
||||
class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
data.questions[selected_question].answers.splice(index, 1);
|
||||
data.questions[selected_question].answers =
|
||||
data.questions[selected_question].answers;
|
||||
@@ -105,7 +113,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
answer.right = !answer.right;
|
||||
}}
|
||||
>
|
||||
@@ -145,9 +153,9 @@ SPDX-License-Identifier: MPL-2.0
|
||||
class="rounded-lg p-1 border-black border"
|
||||
type="color"
|
||||
bind:value={answer.color}
|
||||
on:contextmenu|preventDefault={() => {
|
||||
oncontextmenu={preventDefault(() => {
|
||||
answer.color = default_colors[index];
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
{/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"
|
||||
type="button"
|
||||
in:fade={{ duration: 150 }}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
data.questions[selected_question].answers = [
|
||||
...data.questions[selected_question].answers,
|
||||
{ ...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 { 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();
|
||||
onMount(() => {
|
||||
@@ -96,7 +100,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
<div
|
||||
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 }}
|
||||
>
|
||||
<div
|
||||
@@ -108,7 +112,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<div class="rounded-sm p-6 border-[#B07156] border">
|
||||
<button
|
||||
class="text-xl text-black dark:text-white"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
add_question(i);
|
||||
}}>{qt.name}</button
|
||||
>
|
||||
|
||||
@@ -5,19 +5,31 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import { browser } from '$app/environment';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { thumbHashToDataURL } from 'thumbhash';
|
||||
|
||||
export let src: string;
|
||||
export let css_classes = 'max-h-64 h-auto w-auto';
|
||||
export let added_thumbhash_classes = 'h-full';
|
||||
export let muted = true;
|
||||
export let allow_fullscreen = true;
|
||||
let type: 'img' | 'video' | undefined = undefined;
|
||||
interface Props {
|
||||
src: string;
|
||||
css_classes?: string;
|
||||
added_thumbhash_classes?: string;
|
||||
muted?: boolean;
|
||||
allow_fullscreen?: boolean;
|
||||
}
|
||||
|
||||
let img_data;
|
||||
let thumbhash_data: string;
|
||||
let {
|
||||
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 {
|
||||
const binString = atob(base64);
|
||||
@@ -46,13 +58,13 @@ SPDX-License-Identifier: MPL-2.0
|
||||
const update_url = () => {
|
||||
media = get_media();
|
||||
};
|
||||
let media = get_media();
|
||||
$: {
|
||||
let media = $state(get_media());
|
||||
run(() => {
|
||||
src;
|
||||
update_url();
|
||||
}
|
||||
});
|
||||
|
||||
let fullscreen_open = false;
|
||||
let fullscreen_open = $state(false);
|
||||
|
||||
const open_fullscreen = () => {
|
||||
if (!allow_fullscreen) {
|
||||
@@ -71,7 +83,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
src={img_data.data}
|
||||
alt={img_data.alt_text ?? 'Not available'}
|
||||
class={css_classes}
|
||||
on:click={() => open_fullscreen()}
|
||||
onclick={() => open_fullscreen()}
|
||||
/>
|
||||
{:else if type === 'video'}
|
||||
<video
|
||||
@@ -95,7 +107,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<div
|
||||
class="fixed top-0 left-0 z-50 w-screen h-screen bg-black/50 fle p-2"
|
||||
transition:fade|global={{ duration: 80 }}
|
||||
on:click={() => (fullscreen_open = false)}
|
||||
onclick={() => (fullscreen_open = false)}
|
||||
>
|
||||
<img
|
||||
src={img_data.data}
|
||||
|
||||
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run, preventDefault } from 'svelte/legacy';
|
||||
|
||||
import { getLocalization } from '$lib/i18n';
|
||||
import type { EditorData, OrderQuizAnswer } from '$lib/quiz_types';
|
||||
import { fade } from 'svelte/transition';
|
||||
@@ -13,10 +15,14 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
export let selected_question: number;
|
||||
export let data: EditorData;
|
||||
interface Props {
|
||||
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> => {
|
||||
let _arr = [...arr];
|
||||
@@ -60,11 +66,11 @@ SPDX-License-Identifier: MPL-2.0
|
||||
}
|
||||
}
|
||||
};
|
||||
$: {
|
||||
run(() => {
|
||||
set_colors_if_unset();
|
||||
data;
|
||||
selected_question;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
@@ -78,7 +84,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<button
|
||||
class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
data.questions[selected_question].answers.splice(i, 1);
|
||||
data.questions[selected_question].answers =
|
||||
data.questions[selected_question].answers;
|
||||
@@ -101,7 +107,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
</button>
|
||||
<div>
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
move_item(true, i);
|
||||
}}
|
||||
class="disabled:opacity-50 transition"
|
||||
@@ -126,7 +132,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
move_item(false, i);
|
||||
}}
|
||||
class="disabled:opacity-50 transition"
|
||||
@@ -164,9 +170,9 @@ SPDX-License-Identifier: MPL-2.0
|
||||
class="rounded-lg p-1 border-black border"
|
||||
type="color"
|
||||
bind:value={answer.color}
|
||||
on:contextmenu|preventDefault={() => {
|
||||
oncontextmenu={preventDefault(() => {
|
||||
answer.color = null;
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
{/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"
|
||||
type="button"
|
||||
in:fade={{ duration: 150 }}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
data.questions[selected_question].answers = [
|
||||
...data.questions[selected_question].answers,
|
||||
{
|
||||
|
||||
@@ -5,11 +5,17 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import type { EditorData } from '../quiz_types';
|
||||
import Spinner from '../Spinner.svelte';
|
||||
|
||||
export let selected_question: number;
|
||||
export let data: EditorData;
|
||||
interface Props {
|
||||
selected_question: number;
|
||||
data: EditorData;
|
||||
}
|
||||
|
||||
let { selected_question, data = $bindable() }: Props = $props();
|
||||
|
||||
let question = data.questions[selected_question];
|
||||
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 range_arr = [answer.min_correct, answer.max_correct];
|
||||
$: data.questions[selected_question].answers.min_correct = range_arr[0];
|
||||
$: data.questions[selected_question].answers.max_correct = range_arr[1];
|
||||
$: data.questions[selected_question].answers.min =
|
||||
data.questions[selected_question].answers.min === null
|
||||
? 0
|
||||
: data.questions[selected_question].answers.min;
|
||||
$: data.questions[selected_question].answers.max =
|
||||
data.questions[selected_question].answers.max === null
|
||||
? 0
|
||||
: data.questions[selected_question].answers.max;
|
||||
let range_arr = $state([answer.min_correct, answer.max_correct]);
|
||||
run(() => {
|
||||
data.questions[selected_question].answers.min_correct = range_arr[0];
|
||||
});
|
||||
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
|
||||
? 0
|
||||
: data.questions[selected_question].answers.min;
|
||||
});
|
||||
run(() => {
|
||||
data.questions[selected_question].answers.max =
|
||||
data.questions[selected_question].answers.max === null
|
||||
? 0
|
||||
: data.questions[selected_question].answers.max;
|
||||
});
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
@@ -78,8 +92,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{#await sleep(100)}
|
||||
<Spinner my_20={false} />
|
||||
{:then _}
|
||||
<svelte:component
|
||||
this={c.default}
|
||||
<c.default
|
||||
bind:values={range_arr}
|
||||
bind:min={data.questions[selected_question].answers.min}
|
||||
bind:max={data.questions[selected_question].answers.max}
|
||||
|
||||
@@ -12,8 +12,12 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import { TextQuestionSchema } from '$lib/yupSchemas';
|
||||
import { createTippy } from 'svelte-tippy';
|
||||
|
||||
export let selected_question: number;
|
||||
export let data: EditorData;
|
||||
interface Props {
|
||||
selected_question: number;
|
||||
data: EditorData;
|
||||
}
|
||||
|
||||
let { selected_question, data = $bindable() }: Props = $props();
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
@@ -56,7 +60,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<button
|
||||
class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
data.questions[selected_question].answers.splice(index, 1);
|
||||
data.questions[selected_question].answers =
|
||||
data.questions[selected_question].answers;
|
||||
@@ -85,7 +89,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
answer.case_sensitive = !answer.case_sensitive;
|
||||
}}
|
||||
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"
|
||||
type="button"
|
||||
in:fade={{ duration: 150 }}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
data.questions[selected_question].answers = [
|
||||
...data.questions[selected_question].answers,
|
||||
{ ...get_empty_answer() }
|
||||
|
||||
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run, preventDefault } from 'svelte/legacy';
|
||||
|
||||
import type { EditorData } from '../quiz_types';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { reach } from 'yup';
|
||||
@@ -15,8 +17,12 @@ SPDX-License-Identifier: MPL-2.0
|
||||
const { t } = getLocalization();
|
||||
|
||||
const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58'];
|
||||
export let selected_question: number;
|
||||
export let data: EditorData;
|
||||
interface Props {
|
||||
selected_question: number;
|
||||
data: EditorData;
|
||||
}
|
||||
|
||||
let { selected_question, data = $bindable() }: Props = $props();
|
||||
|
||||
if (!Array.isArray(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();
|
||||
data;
|
||||
selected_question;
|
||||
}
|
||||
});
|
||||
/*console.log(data.questions[selected_question].answers, 'moIn!', data.questions[selected_question].answers.length);
|
||||
onMount(() => {
|
||||
for (let i = 0; i < data.questions[selected_question].answers; i++) {
|
||||
@@ -64,7 +70,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<button
|
||||
class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
data.questions[selected_question].answers.splice(index, 1);
|
||||
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"
|
||||
type="color"
|
||||
bind:value={answer.color}
|
||||
on:contextmenu|preventDefault={() => {
|
||||
oncontextmenu={preventDefault(() => {
|
||||
answer.color = default_colors[index];
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
{/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"
|
||||
type="button"
|
||||
in:fade={{ duration: 150 }}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
data.questions[selected_question].answers = [
|
||||
...data.questions[selected_question].answers,
|
||||
{
|
||||
|
||||
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import type { EditorData } from '$lib/quiz_types';
|
||||
import { QuizQuestionType } from '$lib/quiz_types';
|
||||
import RangeEditor from '$lib/editor/RangeSelectorEditorPart.svelte';
|
||||
@@ -26,14 +28,18 @@ SPDX-License-Identifier: MPL-2.0
|
||||
placement: 'top'
|
||||
});
|
||||
|
||||
export let data: EditorData;
|
||||
export let selected_question: number;
|
||||
export let edit_id: string;
|
||||
interface Props {
|
||||
data: EditorData;
|
||||
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 unique = {};
|
||||
let advanced_options_open = $state(false);
|
||||
|
||||
let uppyOpen = $state(false);
|
||||
let unique = $state({});
|
||||
|
||||
/*eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }]*/
|
||||
const correctTimeInput = (_) => {
|
||||
@@ -51,21 +57,23 @@ SPDX-License-Identifier: MPL-2.0
|
||||
const set_unique = () => {
|
||||
unique = {};
|
||||
};
|
||||
$: correctTimeInput(data.questions[selected_question].time);
|
||||
$: {
|
||||
run(() => {
|
||||
correctTimeInput(data.questions[selected_question].time);
|
||||
});
|
||||
run(() => {
|
||||
selected_question;
|
||||
set_unique();
|
||||
}
|
||||
let image_url = '';
|
||||
});
|
||||
let image_url = $state('');
|
||||
|
||||
const update_image_url = () => {
|
||||
image_url = data.questions[selected_question].image;
|
||||
};
|
||||
$: {
|
||||
run(() => {
|
||||
update_image_url();
|
||||
selected_question;
|
||||
data.questions;
|
||||
}
|
||||
});
|
||||
|
||||
const type_to_name = {
|
||||
RANGE: $t('words.range'),
|
||||
@@ -90,18 +98,18 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<div class="flex align-middle p-4 gap-3">
|
||||
<span
|
||||
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-red-400 transition"
|
||||
/>
|
||||
></span>
|
||||
<span
|
||||
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-yellow-400 transition"
|
||||
/>
|
||||
></span>
|
||||
<span
|
||||
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-green-400 transition"
|
||||
/>
|
||||
></span>
|
||||
<button
|
||||
class="ml-auto"
|
||||
type="button"
|
||||
use:tippy={{ content: $t('editor.advanced_settings') }}
|
||||
on:click={() => (advanced_options_open = true)}
|
||||
onclick={() => (advanced_options_open = true)}
|
||||
>
|
||||
<svg
|
||||
class="text-white w-5 h-5"
|
||||
@@ -130,7 +138,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{#await import('./slide.svelte')}
|
||||
<Spinner my_20={false} />
|
||||
{:then c}
|
||||
<svelte:component this={c.default} bind:data={data.questions[selected_question]} />
|
||||
<c.default bind:data={data.questions[selected_question]} />
|
||||
{/await}
|
||||
{:else}
|
||||
{@const type = data.questions[selected_question].type}
|
||||
@@ -147,8 +155,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
'questions[].question'
|
||||
).isValidSync(data.questions[selected_question].question)}
|
||||
>
|
||||
<svelte:component
|
||||
this={c.default}
|
||||
<c.default
|
||||
bind:text={data.questions[selected_question].question}
|
||||
/>
|
||||
</div>
|
||||
@@ -161,7 +168,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<button
|
||||
class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
data.questions[selected_question].image = null;
|
||||
}}
|
||||
>
|
||||
@@ -187,8 +194,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{#await import('$lib/editor/uploader.svelte')}
|
||||
<Spinner my_20={false} />
|
||||
{:then c}
|
||||
<svelte:component
|
||||
this={c.default}
|
||||
<c.default
|
||||
bind:modalOpen={uppyOpen}
|
||||
bind:edit_id
|
||||
bind:data
|
||||
@@ -231,8 +237,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{#await import('$lib/editor/ABCDEditorPart.svelte')}
|
||||
<Spinner my_20={false} />
|
||||
{:then c}
|
||||
<svelte:component
|
||||
this={c.default}
|
||||
<c.default
|
||||
bind:data
|
||||
bind:selected_question
|
||||
check_choice={type === QuizQuestionType.CHECK}
|
||||
@@ -244,19 +249,19 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{#await import('$lib/editor/VotingEditorPart.svelte')}
|
||||
<Spinner my_20={false} />
|
||||
{:then c}
|
||||
<svelte:component this={c.default} bind:data bind:selected_question />
|
||||
<c.default bind:data bind:selected_question />
|
||||
{/await}
|
||||
{:else if type === QuizQuestionType.TEXT}
|
||||
{#await import('$lib/editor/TextEditorPart.svelte')}
|
||||
<Spinner my_20={false} />
|
||||
{:then c}
|
||||
<svelte:component this={c.default} bind:data bind:selected_question />
|
||||
<c.default bind:data bind:selected_question />
|
||||
{/await}
|
||||
{:else if type === QuizQuestionType.ORDER}
|
||||
{#await import('$lib/editor/OrderEditorPart.svelte')}
|
||||
<Spinner my_20={false} />
|
||||
{:then c}
|
||||
<svelte:component this={c.default} bind:data bind:selected_question />
|
||||
<c.default bind:data bind:selected_question />
|
||||
{/await}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run, preventDefault } from 'svelte/legacy';
|
||||
|
||||
import type { EditorData } from '$lib/quiz_types';
|
||||
import { getLocalization } from '$lib/i18n';
|
||||
import Spinner from '$lib/Spinner.svelte';
|
||||
@@ -12,19 +14,25 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
let uppyOpen = false;
|
||||
let bg_uppy_open = false;
|
||||
let uppyOpen = $state(false);
|
||||
let bg_uppy_open = $state(false);
|
||||
|
||||
export let edit_id: string;
|
||||
export let data: EditorData;
|
||||
interface Props {
|
||||
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({
|
||||
arrow: true,
|
||||
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>
|
||||
|
||||
<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">
|
||||
<span
|
||||
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-red-400 transition"
|
||||
/>
|
||||
></span>
|
||||
<span
|
||||
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-yellow-400 transition"
|
||||
/>
|
||||
></span>
|
||||
<span
|
||||
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-green-400 transition"
|
||||
/>
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -57,7 +65,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{#await import('$lib/inline-editor.svelte')}
|
||||
<Spinner my_20={false} />
|
||||
{:then c}
|
||||
<svelte:component this={c.default} bind:text={data.title} />
|
||||
<c.default bind:text={data.title} />
|
||||
{/await}
|
||||
</div>
|
||||
<div class="flex justify-center pt-10 w-full max-h-32">
|
||||
@@ -66,7 +74,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
placeholder="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"
|
||||
/>
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
{#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}"
|
||||
alt="not available"
|
||||
class="max-h-72 h-auto w-auto"
|
||||
on:contextmenu|preventDefault={() => {
|
||||
oncontextmenu={preventDefault(() => {
|
||||
data.cover_image = null;
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
{#await import('$lib/editor/uploader.svelte')}
|
||||
<Spinner my_20={false} />
|
||||
{:then c}
|
||||
<svelte:component
|
||||
this={c.default}
|
||||
<c.default
|
||||
bind:modalOpen={uppyOpen}
|
||||
bind:edit_id
|
||||
bind:data
|
||||
@@ -96,7 +103,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<div class="pt-10 w-full flex justify-center">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
data.public = !data.public;
|
||||
}}
|
||||
class="text-center w-fit"
|
||||
@@ -149,7 +156,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
>
|
||||
<span
|
||||
class="inline-block w-full h-full bg-[#d6edc9] dark:bg-[#4e6e58]"
|
||||
/>
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -165,7 +172,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
></span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
@@ -188,7 +195,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<div class="w-full flex justify-center -mt-8">
|
||||
{#if data.background_image}
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
data.background_image = undefined;
|
||||
}}
|
||||
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} />
|
||||
</div>
|
||||
{:then c}
|
||||
<svelte:component
|
||||
this={c.default}
|
||||
<c.default
|
||||
bind:modalOpen={bg_uppy_open}
|
||||
bind:edit_id
|
||||
bind:data
|
||||
|
||||
@@ -17,19 +17,23 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
export let data: EditorData;
|
||||
export let selected_question = -1;
|
||||
interface Props {
|
||||
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({
|
||||
arrow: true,
|
||||
animation: 'perspective-subtle',
|
||||
placement: 'right'
|
||||
});
|
||||
let arr_of_cards = Array(data.questions.length);
|
||||
let propertyCard;
|
||||
let add_new_question_popup_open = false;
|
||||
let arr_of_cards = $state(Array(data.questions.length));
|
||||
let propertyCard = $state();
|
||||
let add_new_question_popup_open = $state(false);
|
||||
|
||||
const empy_slide: Question = {
|
||||
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-green-300={selected_question === -1}
|
||||
class:dark:bg-green-500={selected_question === -1}
|
||||
on:click={() => setSelectedQuestion(-1)}
|
||||
onclick={() => setSelectedQuestion(-1)}
|
||||
>
|
||||
<div
|
||||
use:tippy={{ content: data.title === '' ? "It's empty!" : data.title }}
|
||||
@@ -119,7 +123,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
bind:value={data.description}
|
||||
class="bg-transparent resize-none w-full rounded-sm text-sm dark:text-white"
|
||||
class:dark:text-black={selected_question === -1}
|
||||
/>
|
||||
></textarea>
|
||||
</div>
|
||||
<div
|
||||
class="w-full flex justify-center dark:text-white"
|
||||
@@ -127,7 +131,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
data.public = !data.public;
|
||||
}}
|
||||
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-green-300={index === selected_question}
|
||||
class:dark:bg-green-500={index === selected_question}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
setSelectedQuestion(index);
|
||||
}}
|
||||
bind:this={arr_of_cards[index]}
|
||||
@@ -190,7 +194,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
aria-label="Move card up"
|
||||
class:opacity-50={index === 0}
|
||||
class:pointer-events-none={index === 0}
|
||||
on:click={() =>
|
||||
onclick={() =>
|
||||
(data.questions = swapArrayElements(
|
||||
data.questions,
|
||||
index,
|
||||
@@ -219,7 +223,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
aria-label="Move card down"
|
||||
class:opacity-50={index + 1 === data.questions.length}
|
||||
class:pointer-events-none={index + 1 === data.questions.length}
|
||||
on:click={() =>
|
||||
onclick={() =>
|
||||
(data.questions = swapArrayElements(
|
||||
data.questions,
|
||||
index,
|
||||
@@ -248,7 +252,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<button
|
||||
class="rounded-full absolute -top-3 -right-3 opacity-70 hover:opacity-100 transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
if (confirm('Do you really want to delete this Question?')) {
|
||||
selected_question = -1;
|
||||
data.questions.splice(index, 1);
|
||||
@@ -380,7 +384,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<button
|
||||
type="button"
|
||||
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;
|
||||
}}
|
||||
>
|
||||
@@ -403,7 +407,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<button
|
||||
type="button"
|
||||
class="h-full flex justify-center w-full dark:text-white flex-col"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
data.questions = [...data.questions, { ...empy_slide }];
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import type { Question } from '$lib/quiz_types';
|
||||
import { ElementTypes, QuizQuestionType } from '$lib/quiz_types';
|
||||
import ElementSelection from './slides/element_selection.svelte';
|
||||
@@ -15,21 +17,25 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import type { Konva, ShapeModel } from 'pikaso';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export let data: Question = {
|
||||
interface Props {
|
||||
data?: Question;
|
||||
}
|
||||
|
||||
let { data = $bindable({
|
||||
type: QuizQuestionType.SLIDE,
|
||||
time: '120',
|
||||
question: '',
|
||||
image: undefined,
|
||||
answers: ''
|
||||
};
|
||||
let selected_element = undefined;
|
||||
let canvas_el: HTMLDivElement | undefined;
|
||||
}) }: Props = $props();
|
||||
let selected_element = $state(undefined);
|
||||
let canvas_el: HTMLDivElement | undefined = $state();
|
||||
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 main_el: undefined | HTMLElement;
|
||||
let settings_menu_open = false;
|
||||
let main_el: undefined | HTMLElement = $state();
|
||||
let settings_menu_open = $state(false);
|
||||
|
||||
const set_correct_height = new ResizeObserver((e) => {
|
||||
for (const i of e) {
|
||||
@@ -87,11 +93,11 @@ SPDX-License-Identifier: MPL-2.0
|
||||
}
|
||||
};
|
||||
|
||||
$: {
|
||||
run(() => {
|
||||
if (selected_element) {
|
||||
add_text_field();
|
||||
}
|
||||
}
|
||||
});
|
||||
const assign_resize_handlers = () => {
|
||||
for (let i = 0; i < elements_binds.length; i++) {
|
||||
set_correct_height.observe(elements_binds[i]);
|
||||
@@ -105,10 +111,10 @@ SPDX-License-Identifier: MPL-2.0
|
||||
}
|
||||
}*/
|
||||
|
||||
$: {
|
||||
run(() => {
|
||||
elements_binds;
|
||||
assign_resize_handlers();
|
||||
}
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
/* 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">
|
||||
<button
|
||||
class="mr-auto"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
settings_menu_open = !settings_menu_open;
|
||||
}}
|
||||
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">
|
||||
<button
|
||||
class="ml-auto"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
selected_element = selected_element === null ? undefined : null;
|
||||
}}
|
||||
type="button"
|
||||
@@ -240,5 +246,5 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{/if}
|
||||
</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>
|
||||
|
||||
@@ -5,14 +5,20 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import type { Konva, ShapeModel } from 'pikaso';
|
||||
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 = () => {
|
||||
available_modifiers = [];
|
||||
@@ -32,10 +38,10 @@ SPDX-License-Identifier: MPL-2.0
|
||||
}
|
||||
};
|
||||
|
||||
$: {
|
||||
run(() => {
|
||||
selected_el;
|
||||
set_available_modifiers();
|
||||
}
|
||||
});
|
||||
|
||||
const toggle_switch = (el: string) => {
|
||||
if (opened_dropdown) {
|
||||
@@ -62,9 +68,11 @@ SPDX-License-Identifier: MPL-2.0
|
||||
});
|
||||
};
|
||||
|
||||
$: if (!selected_el) {
|
||||
opened_dropdown = null;
|
||||
}
|
||||
run(() => {
|
||||
if (!selected_el) {
|
||||
opened_dropdown = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<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('text_color')
|
||||
)}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
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"
|
||||
transition:fade|global={{ duration: 100 }}
|
||||
>
|
||||
<input type="color" on:change={change_color} />
|
||||
<input type="color" onchange={change_color} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -111,7 +119,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<button
|
||||
type="button"
|
||||
class="disabled:opacity-50 transition"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
toggle_switch('font_size');
|
||||
}}
|
||||
disabled={!available_modifiers.includes('font_size')}
|
||||
@@ -141,7 +149,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
>
|
||||
<input
|
||||
type="range"
|
||||
on:change={change_fontsize}
|
||||
onchange={change_fontsize}
|
||||
value={selected_el?.node?.children?.[1]?.attrs?.fontSize}
|
||||
min="10"
|
||||
max="250"
|
||||
@@ -153,7 +161,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<button
|
||||
type="button"
|
||||
class="disabled:opacity-50 transition"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
selected_el.update({ zIndex: selected_el.node.getZIndex() + 1 });
|
||||
}}
|
||||
disabled={!selected_el}
|
||||
@@ -179,7 +187,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<button
|
||||
type="button"
|
||||
class="disabled:opacity-50 transition"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
selected_el.update({ zIndex: selected_el.node.getZIndex() - 1 });
|
||||
}}
|
||||
disabled={!selected_el}
|
||||
|
||||
@@ -11,7 +11,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import { getLocalization } from '$lib/i18n';
|
||||
|
||||
const { t } = getLocalization();
|
||||
export let selected_element;
|
||||
let { selected_element = $bindable() } = $props();
|
||||
const keybinding_list = {
|
||||
t: ElementTypes.Text,
|
||||
h: ElementTypes.Headline,
|
||||
@@ -81,7 +81,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{#each element_list as el}
|
||||
<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"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
selected_element = el.type;
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -5,13 +5,16 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
export let title;
|
||||
export let time;
|
||||
let time_local = 120;
|
||||
let { title = $bindable(), time = $bindable() } = $props();
|
||||
let time_local = $state(120);
|
||||
/*eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }]*/
|
||||
$: time = String(time_local);
|
||||
run(() => {
|
||||
time = String(time_local);
|
||||
});
|
||||
if (time) {
|
||||
time_local = parseInt(time);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,12 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
export let data: string;
|
||||
export let editor = true;
|
||||
interface Props {
|
||||
data: string;
|
||||
editor?: boolean;
|
||||
}
|
||||
|
||||
let { data = $bindable(), editor = true }: Props = $props();
|
||||
|
||||
if (!data) {
|
||||
data = '#ed333b';
|
||||
|
||||
@@ -5,8 +5,12 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
export let data;
|
||||
export let editor = true;
|
||||
interface Props {
|
||||
data: any;
|
||||
editor?: boolean;
|
||||
}
|
||||
|
||||
let { data = $bindable(), editor = true }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if editor}
|
||||
|
||||
@@ -5,8 +5,12 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
export let data: string;
|
||||
export let editor = true;
|
||||
interface Props {
|
||||
data: string;
|
||||
editor?: boolean;
|
||||
}
|
||||
|
||||
let { data = $bindable(), editor = true }: Props = $props();
|
||||
|
||||
if (!data) {
|
||||
data = '#ed333b';
|
||||
|
||||
@@ -5,12 +5,16 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
export let data;
|
||||
export let editor = true;
|
||||
interface Props {
|
||||
data: any;
|
||||
editor?: boolean;
|
||||
}
|
||||
|
||||
let { data = $bindable(), editor = true }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#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}
|
||||
<p>{data}</p>
|
||||
{/if}
|
||||
|
||||
@@ -3,7 +3,6 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
|
||||
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { Dashboard as SvelteDashboard } from '@uppy/svelte';
|
||||
import Uppy from '@uppy/core';
|
||||
@@ -27,18 +26,26 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import Pixabay from '$lib/editor/uploader/Pixabay.svelte';
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
export let modalOpen = false;
|
||||
export let edit_id: string;
|
||||
export let data: EditorData;
|
||||
export let selected_question: number;
|
||||
export let video_upload = false;
|
||||
export let library_enabled = true;
|
||||
let {
|
||||
modalOpen = false,
|
||||
edit_id,
|
||||
data,
|
||||
selected_question,
|
||||
video_upload = false,
|
||||
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
|
||||
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
|
||||
enum AvailableUploadTypes {
|
||||
@@ -67,7 +74,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
.use(XHRUpload, {
|
||||
endpoint: `/api/v1/storage/`
|
||||
});
|
||||
const props = {
|
||||
const properties = {
|
||||
inline: true,
|
||||
restrictions: {
|
||||
maxFileSize: 10_490_000,
|
||||
@@ -134,7 +141,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{#if modalOpen}
|
||||
<div
|
||||
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 }}
|
||||
>
|
||||
{#if selected_type === null}
|
||||
@@ -181,7 +188,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{:else if selected_type === AvailableUploadTypes.Image}
|
||||
<div class="m-auto w-1/3 h-5/6" transition:fade={{ duration: 100 }}>
|
||||
<div>
|
||||
<SvelteDashboard {uppy} width="100%" {props} />
|
||||
<SvelteDashboard {uppy} width="100%" {properties} />
|
||||
</div>
|
||||
</div>
|
||||
{:else if selected_type === AvailableUploadTypes.Video}
|
||||
@@ -215,7 +222,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<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"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
modalOpen = true;
|
||||
}}
|
||||
><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 { getLocalization } from '$lib/i18n';
|
||||
|
||||
export let data: EditorData;
|
||||
export let selected_question: number;
|
||||
export let modalOpen: boolean;
|
||||
interface Props {
|
||||
data: EditorData;
|
||||
selected_question: number;
|
||||
modalOpen: boolean;
|
||||
}
|
||||
|
||||
let { data = $bindable(), selected_question, modalOpen = $bindable() }: Props = $props();
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
|
||||
@@ -5,18 +5,24 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
|
||||
import type { EditorData } from '$lib/quiz_types';
|
||||
import Spinner from '$lib/Spinner.svelte';
|
||||
import BrownButton from '$lib/components/buttons/brown.svelte';
|
||||
import { getLocalization } from '$lib/i18n';
|
||||
|
||||
export let data: EditorData;
|
||||
export let selected_question: number;
|
||||
export let modalOpen: boolean;
|
||||
interface Props {
|
||||
data: EditorData;
|
||||
selected_question: number;
|
||||
modalOpen: boolean;
|
||||
}
|
||||
|
||||
let page = 1;
|
||||
let search_term = '';
|
||||
let loading = false;
|
||||
let { data = $bindable(), selected_question, modalOpen = $bindable() }: Props = $props();
|
||||
|
||||
let page = $state(1);
|
||||
let search_term = $state('');
|
||||
let loading = $state(false);
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
@@ -42,7 +48,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
return await res.json();
|
||||
};
|
||||
|
||||
let fetched_data = fetch_data();
|
||||
let fetched_data = $state(fetch_data());
|
||||
</script>
|
||||
|
||||
{#await fetched_data}
|
||||
@@ -64,7 +70,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
<form
|
||||
class="w-full flex gap-2"
|
||||
on:submit|preventDefault={() => (fetched_data = fetch_data())}
|
||||
onsubmit={preventDefault(() => (fetched_data = fetch_data()))}
|
||||
>
|
||||
<input
|
||||
class="w-full outline-hidden p-1 rounded-sm dark:bg-gray-500 bg-gray-300"
|
||||
|
||||
@@ -7,7 +7,8 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<script lang="ts">
|
||||
import Spinner from '$lib/Spinner.svelte';
|
||||
|
||||
export let files: {
|
||||
interface Props {
|
||||
files: {
|
||||
id: string;
|
||||
uploaded_at: string;
|
||||
mime_type?: string;
|
||||
@@ -21,6 +22,9 @@ SPDX-License-Identifier: MPL-2.0
|
||||
quizzes: { id: string }[];
|
||||
quiztivities: { id: string }[];
|
||||
}[];
|
||||
}
|
||||
|
||||
let { files = $bindable() }: Props = $props();
|
||||
|
||||
const get_files = async () => {
|
||||
const res = await fetch('/api/v1/storage/list');
|
||||
|
||||
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import {
|
||||
BalloonEditor,
|
||||
Essentials,
|
||||
@@ -24,19 +26,26 @@ SPDX-License-Identifier: MPL-2.0
|
||||
// import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough';
|
||||
// import Subscript from '@ckeditor/ckeditor5-basic-styles/src/subscript.js';
|
||||
// import Superscript from '@ckeditor/ckeditor5-basic-styles/src/superscript.js';
|
||||
// import Autoformat from "@ckeditor/ckeditor5-autoformat/src/autoformat"
|
||||
|
||||
export let text = '';
|
||||
|
||||
|
||||
const triggerChange = () => {
|
||||
text = editor.getData();
|
||||
};
|
||||
|
||||
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 = [
|
||||
Autoformat,
|
||||
@@ -120,7 +129,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
bind:this={html_el}
|
||||
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"
|
||||
/>
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -5,8 +5,10 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
export let text;
|
||||
let internal_text = '';
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
let { text } = $props();
|
||||
let internal_text = $state('');
|
||||
/*
|
||||
const markSelection = (function() {
|
||||
const markerTextChar = '\ufeff';
|
||||
@@ -92,10 +94,10 @@ SPDX-License-Identifier: MPL-2.0
|
||||
console.log(output);
|
||||
};
|
||||
|
||||
$: {
|
||||
run(() => {
|
||||
process_input();
|
||||
internal_text;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<input bind:value={internal_text} />
|
||||
|
||||
@@ -20,7 +20,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
let teacherTab = false;
|
||||
let teacherTab = $state(false);
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-1 overflow-hidden h-fit">
|
||||
@@ -103,7 +103,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{#if teacherTab}
|
||||
<button
|
||||
class="border border-blue-600 p-2 border-2 rounded-sm hover:bg-gray-700 text-2xl"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
teacherTab = true;
|
||||
}}
|
||||
>{$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-black={!teacherTab}
|
||||
class:text-base={!teacherTab}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
teacherTab = true;
|
||||
}}
|
||||
>{$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-blue-600={!teacherTab}
|
||||
class:text-2xl={!teacherTab}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
teacherTab = false;
|
||||
}}
|
||||
>{$t('index_page.students_site')}
|
||||
|
||||
@@ -5,7 +5,11 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
export let open: boolean;
|
||||
interface Props {
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
let { open = $bindable() }: Props = $props();
|
||||
|
||||
const closeThing = () => {
|
||||
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">
|
||||
<div class="absolute top-1 right-1">
|
||||
<button type="button" on:click={closeThing}>
|
||||
<button type="button" onclick={closeThing}>
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
fill="none"
|
||||
|
||||
@@ -54,11 +54,15 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import { onMount } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export let languages: Array<{
|
||||
interface Props {
|
||||
languages?: Array<{
|
||||
flag: string;
|
||||
name: string;
|
||||
code: string;
|
||||
}> = [
|
||||
}>;
|
||||
}
|
||||
|
||||
let { languages = [
|
||||
{
|
||||
code: 'de',
|
||||
name: 'Deutsch',
|
||||
@@ -139,11 +143,11 @@ SPDX-License-Identifier: MPL-2.0
|
||||
name: 'tiếng Việt',
|
||||
flag: '🇻🇳'
|
||||
}
|
||||
];
|
||||
] }: Props = $props();
|
||||
const get_selected_language = (): string => {
|
||||
return localStorage.getItem('language');
|
||||
};
|
||||
let selected_language;
|
||||
let selected_language = $state();
|
||||
onMount(() => {
|
||||
selected_language = get_selected_language();
|
||||
});
|
||||
@@ -159,7 +163,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<div>
|
||||
<select
|
||||
bind:value={selected_language}
|
||||
on:change={() => {
|
||||
onchange={() => {
|
||||
set_language(selected_language);
|
||||
}}
|
||||
class="p-2 rounded-lg bg-gray-800 focus:ring-2 ring-blue-600 text-white"
|
||||
|
||||
@@ -8,9 +8,13 @@ SPDX-License-Identifier: MPL-2.0
|
||||
By Flowbite, but changed: https://flowbite.com/docs/components/modal/#default-modal
|
||||
-->
|
||||
<script lang="ts">
|
||||
export let title: string;
|
||||
export let body: string;
|
||||
export let open: boolean;
|
||||
interface Props {
|
||||
title: string;
|
||||
body: string;
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
let { title, body, open = $bindable() }: Props = $props();
|
||||
console.log(open, title, body);
|
||||
open = true;
|
||||
</script>
|
||||
@@ -31,7 +35,7 @@ By Flowbite, but changed: https://flowbite.com/docs/components/modal/#default-mo
|
||||
<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"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
open = false;
|
||||
}}
|
||||
>
|
||||
@@ -62,7 +66,7 @@ By Flowbite, but changed: https://flowbite.com/docs/components/modal/#default-mo
|
||||
<button
|
||||
data-modal-toggle="defaultModal"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
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"
|
||||
|
||||
@@ -22,7 +22,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
let menuIsClosed = true;
|
||||
let menuIsClosed = $state(true);
|
||||
const toggleMenu = () => {
|
||||
menuIsClosed = !menuIsClosed;
|
||||
};
|
||||
@@ -31,7 +31,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
menuIsClosed = true; // Closes menu to let the user see the page beneath
|
||||
});
|
||||
|
||||
let darkMode = false;
|
||||
let darkMode = $state(false);
|
||||
if (browser) {
|
||||
darkMode =
|
||||
localStorage.theme === 'dark' ||
|
||||
@@ -108,7 +108,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<div class="lg:flex items-center justify-center">
|
||||
{#if darkMode}
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
switchDarkMode();
|
||||
}}
|
||||
use:tippy={{ content: 'Switch light mode on' }}
|
||||
@@ -134,7 +134,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
switchDarkMode();
|
||||
}}
|
||||
aria-label="Activate darkmode"
|
||||
@@ -180,7 +180,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<!-- Sun icon -->
|
||||
<button
|
||||
class="px-3"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
switchDarkMode();
|
||||
}}
|
||||
use:tippy={{ content: 'Switch light mode on' }}
|
||||
@@ -208,7 +208,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<!-- Moon icon -->
|
||||
<button
|
||||
class="px-3"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
switchDarkMode();
|
||||
}}
|
||||
aria-label="Activate darkmode"
|
||||
@@ -237,7 +237,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<button
|
||||
class="px-3"
|
||||
id="open-menu"
|
||||
on:click={toggleMenu}
|
||||
onclick={toggleMenu}
|
||||
aria-label="Open navbar"
|
||||
>
|
||||
<svg
|
||||
@@ -258,7 +258,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<button
|
||||
class="px-3"
|
||||
id="close-menu"
|
||||
on:click={toggleMenu}
|
||||
onclick={toggleMenu}
|
||||
aria-label="Close navbar"
|
||||
>
|
||||
<svg
|
||||
|
||||
@@ -10,18 +10,32 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import type { Socket } from 'socket.io-client';
|
||||
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 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">
|
||||
{#if selected_question + 1 === quiz_data.questions.length && ((timer_res === '0' && question_results !== null) || quiz_data?.questions?.[selected_question]?.type === QuizQuestionType.SLIDE)}
|
||||
{#if JSON.stringify(final_results) === JSON.stringify([null])}
|
||||
<button on:click={get_final_results} class="admin-button"
|
||||
<button onclick={get_final_results} class="admin-button"
|
||||
>{$t('admin_page.get_final_results')}
|
||||
</button>
|
||||
{/if}
|
||||
{:else if timer_res === '0' || selected_question === -1}
|
||||
{#if (selected_question + 1 !== quiz_data.questions.length && question_results !== null) || selected_question === -1}
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
set_question_number(selected_question + 1);
|
||||
}}
|
||||
class="admin-button"
|
||||
@@ -73,7 +87,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{#if question_results === null && selected_question !== -1}
|
||||
{#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE}
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
set_question_number(selected_question + 1);
|
||||
}}
|
||||
class="admin-button"
|
||||
@@ -81,7 +95,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
</button>
|
||||
{:else if quiz_data.questions[selected_question]?.hide_results === true}
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
get_question_results();
|
||||
setTimeout(() => {
|
||||
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 })}
|
||||
</button>
|
||||
{:else}
|
||||
<button on:click={get_question_results} class="admin-button"
|
||||
<button onclick={get_question_results} class="admin-button"
|
||||
>{$t('admin_page.show_results')}
|
||||
</button>
|
||||
{/if}
|
||||
@@ -99,14 +113,14 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{:else if selected_question !== -1}
|
||||
{#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE}
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
set_question_number(selected_question + 1);
|
||||
}}
|
||||
class="admin-button"
|
||||
>{$t('admin_page.next_question', { question: selected_question + 2 })}
|
||||
</button>
|
||||
{:else}
|
||||
<button on:click={show_solutions} class="admin-button"
|
||||
<button onclick={show_solutions} class="admin-button"
|
||||
>{$t('admin_page.stop_time_and_solutions')}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -5,16 +5,22 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
import { getLocalization } from '$lib/i18n';
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
export let data;
|
||||
export let username;
|
||||
export let show_final_results: boolean;
|
||||
import { fly } from 'svelte/transition';
|
||||
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) {
|
||||
const ret = {};
|
||||
@@ -24,18 +30,22 @@ SPDX-License-Identifier: MPL-2.0
|
||||
return ret;
|
||||
}
|
||||
|
||||
$: data = sortObjectbyValue(data);
|
||||
$: console.log(data, 'sorted, fina');
|
||||
|
||||
let player_names;
|
||||
$: player_names = Object.keys(data).sort(function (a, b) {
|
||||
return data[b] - data[a];
|
||||
run(() => {
|
||||
data = sortObjectbyValue(data);
|
||||
});
|
||||
run(() => {
|
||||
console.log(data, 'sorted, fina');
|
||||
});
|
||||
|
||||
let player_count_or_five;
|
||||
$: player_count_or_five = player_names.length >= 5 ? 5 : player_names.length;
|
||||
let player_names = $derived(Object.keys(data).sort(function (a, b) {
|
||||
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(() => {
|
||||
setTimeout(() => {
|
||||
confetti.create(canvas, {
|
||||
@@ -48,7 +58,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
</script>
|
||||
|
||||
{#if show_final_results}
|
||||
<canvas bind:this={canvas} />
|
||||
<canvas bind:this={canvas}></canvas>
|
||||
<div>
|
||||
{#each player_names as player, i}
|
||||
{#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 { fade } from 'svelte/transition';
|
||||
|
||||
export let game_pin: string;
|
||||
export let players;
|
||||
export let socket;
|
||||
export let cqc_code: string;
|
||||
interface Props {
|
||||
game_pin: string;
|
||||
players: any;
|
||||
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();
|
||||
let play_music = false;
|
||||
let play_music = $state(false);
|
||||
|
||||
if (cqc_code === 'null') {
|
||||
cqc_code = null;
|
||||
@@ -52,7 +61,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
on:click={() => (fullscreen_open = true)}
|
||||
onclick={() => (fullscreen_open = true)}
|
||||
alt="QR code to join the game"
|
||||
src="/api/v1/utils/qr/{game_pin}"
|
||||
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">
|
||||
<span
|
||||
class="hover:line-through text-lg"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
kick_player(player.username);
|
||||
}}>{player.username}</span
|
||||
>
|
||||
@@ -116,7 +125,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<div
|
||||
class="fixed top-0 left-0 z-50 w-screen h-screen bg-black/50 flex p-2"
|
||||
transition:fade|global={{ duration: 80 }}
|
||||
on:click={() => (fullscreen_open = false)}
|
||||
onclick={() => (fullscreen_open = false)}
|
||||
>
|
||||
<img
|
||||
alt="QR code to join the game"
|
||||
|
||||
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import { QuizQuestionType } from '$lib/quiz_types';
|
||||
import type { QuizData } from '$lib/quiz_types';
|
||||
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 { getLocalization } from '$lib/i18n';
|
||||
|
||||
export let quiz_data: QuizData;
|
||||
export let selected_question: number;
|
||||
export let timer_res: string;
|
||||
|
||||
export let answer_count: number;
|
||||
export let default_colors: string[];
|
||||
interface Props {
|
||||
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();
|
||||
|
||||
let circular_progress = 0;
|
||||
$: {
|
||||
let circular_progress = $state(0);
|
||||
run(() => {
|
||||
try {
|
||||
circular_progress =
|
||||
1 -
|
||||
@@ -33,7 +45,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
} catch {
|
||||
circular_progress = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col justify-center w-screen h-1/6">
|
||||
@@ -42,7 +54,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
</h1>
|
||||
<!-- <span class='text-center py-2 text-lg'>{$t('admin_page.time_left')}: {timer_res}</span>-->
|
||||
<div class="grid grid-cols-3 my-2">
|
||||
<span />
|
||||
<span></span>
|
||||
<div class="m-auto">
|
||||
<CircularTimer
|
||||
bind:text={timer_res}
|
||||
@@ -85,7 +97,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
style="color: {get_foreground_color(answer.color ?? default_colors[i])}"
|
||||
>{answer.answer}</span
|
||||
>
|
||||
<span class="pl-4 w-10" />
|
||||
<span class="pl-4 w-10"></span>
|
||||
</div>
|
||||
{/each}
|
||||
</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"
|
||||
>{answer.answer}</span
|
||||
>
|
||||
<span class="pl-4 w-10" />
|
||||
<span class="pl-4 w-10"></span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import VotingResults from './voting_results.svelte';
|
||||
import { flip } from 'svelte/animate';
|
||||
import { fly } from 'svelte/transition';
|
||||
@@ -15,16 +17,19 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
export let data;
|
||||
export let question: Question;
|
||||
interface Props {
|
||||
data: any;
|
||||
question: Question;
|
||||
new_data: Array<{
|
||||
username: string;
|
||||
answer: string;
|
||||
right: boolean;
|
||||
time_taken: number;
|
||||
score: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
export let new_data: Array<{
|
||||
username: string;
|
||||
answer: string;
|
||||
right: boolean;
|
||||
time_taken: number;
|
||||
score: number;
|
||||
}>;
|
||||
let { data = $bindable(), question, new_data }: Props = $props();
|
||||
|
||||
function sortObjectbyValue(obj) {
|
||||
const ret = {};
|
||||
@@ -35,7 +40,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
}
|
||||
|
||||
// let data_by_username = {};
|
||||
let score_by_username = {};
|
||||
let score_by_username = $state({});
|
||||
|
||||
const do_sth = () => {
|
||||
for (const i of new_data) {
|
||||
@@ -43,13 +48,17 @@ SPDX-License-Identifier: MPL-2.0
|
||||
}
|
||||
};
|
||||
|
||||
$: {
|
||||
run(() => {
|
||||
new_data;
|
||||
score_by_username;
|
||||
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) === '{}') {
|
||||
for (const i of new_data) {
|
||||
@@ -57,12 +66,11 @@ SPDX-License-Identifier: MPL-2.0
|
||||
}
|
||||
}
|
||||
|
||||
$: data = sortObjectbyValue(data);
|
||||
$: player_names = Object.keys(data).sort(function (a, b) {
|
||||
return data[b] - data[a];
|
||||
run(() => {
|
||||
data = sortObjectbyValue(data);
|
||||
});
|
||||
|
||||
let show_new_score_clicked = false;
|
||||
let show_new_score_clicked = $state(false);
|
||||
|
||||
const show_new_score = () => {
|
||||
// for (let i = 0; i++; i < player_names.length) {
|
||||
@@ -98,34 +106,42 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<div class="flex justify-center">
|
||||
<div>
|
||||
<table class="table-auto text-xl">
|
||||
<tr>
|
||||
<th class="p-2 border-r border-r-black border-b-2 border-b-black"
|
||||
>{$t('words.name')}</th
|
||||
>
|
||||
<th class="p-2 border-b-2 border-b-black">{$t('words.point', { count: 2 })}</th>
|
||||
{#if show_new_score_clicked}
|
||||
<th in:fly|global={{ x: 300 }} class="p-2 border-b-2 border-b-black"
|
||||
>{$t('play_page.points_added')}
|
||||
</th>
|
||||
{/if}
|
||||
</tr>
|
||||
{#each player_names as player, i (player)}
|
||||
<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">{data[player]}</td>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="p-2 border-r border-r-black border-b-2 border-b-black"
|
||||
>{$t('words.name')}</th
|
||||
>
|
||||
<th class="p-2 border-b-2 border-b-black"
|
||||
>{$t('words.point', { count: 2 })}</th
|
||||
>
|
||||
{#if show_new_score_clicked}
|
||||
<td
|
||||
in:fly|global={{ x: 300 }}
|
||||
class:hidden={i > 3}
|
||||
class="p-2"
|
||||
class:text-red-600={score_by_username[player] === 0 ||
|
||||
score_by_username[player] === undefined}
|
||||
>
|
||||
+{score_by_username[player] ?? '0'}
|
||||
</td>
|
||||
<th in:fly|global={{ x: 300 }} class="p-2 border-b-2 border-b-black"
|
||||
>{$t('play_page.points_added')}
|
||||
</th>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each player_names as player, i (player)}
|
||||
<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">{data[player]}</td>
|
||||
{#if show_new_score_clicked}
|
||||
<td
|
||||
in:fly|global={{ x: 300 }}
|
||||
class:hidden={i > 3}
|
||||
class="p-2"
|
||||
class:text-red-600={score_by_username[player] === 0 ||
|
||||
score_by_username[player] === undefined}
|
||||
>
|
||||
+{score_by_username[player] ?? '0'}
|
||||
</td>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,11 +9,15 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import { onMount } from 'svelte';
|
||||
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 img_src = '';
|
||||
let img_src = $state('');
|
||||
|
||||
onMount(() => {
|
||||
canvas = new Pikaso({
|
||||
@@ -33,7 +37,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
<div class="w-full h-full">
|
||||
<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 class="w-full h-full flex justify-center">
|
||||
<img src={img_src} />
|
||||
|
||||
@@ -8,8 +8,12 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import type { Question } from '$lib/quiz_types';
|
||||
import { QuizQuestionType } from '$lib/quiz_types';
|
||||
|
||||
export let data;
|
||||
export let question: Question;
|
||||
interface Props {
|
||||
data: any;
|
||||
question: Question;
|
||||
}
|
||||
|
||||
let { data, question }: Props = $props();
|
||||
|
||||
let quiz_answers = [];
|
||||
let quiz_colors = [];
|
||||
@@ -21,7 +25,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
answer_correct.push(i.right);
|
||||
}
|
||||
|
||||
let sorted_data = {};
|
||||
let sorted_data = $state({});
|
||||
for (const i of quiz_answers) {
|
||||
sorted_data[i] = 0;
|
||||
}
|
||||
@@ -56,7 +60,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
data.length}rem; background-color: {quiz_colors[i]
|
||||
? quiz_colors[i]
|
||||
: 'black'}"
|
||||
/>
|
||||
></div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex gap-12">
|
||||
|
||||
@@ -5,12 +5,18 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import Audio1 from '$lib/assets/music/1-128.mp3';
|
||||
|
||||
export let play = false;
|
||||
let volume = 100;
|
||||
interface Props {
|
||||
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) => {
|
||||
if (play_audio) {
|
||||
audio.play();
|
||||
@@ -19,14 +25,18 @@ SPDX-License-Identifier: MPL-2.0
|
||||
audio.pause();
|
||||
}
|
||||
};
|
||||
$: audio.volume = volume / 100;
|
||||
$: control_audio(play);
|
||||
run(() => {
|
||||
audio.volume = volume / 100;
|
||||
});
|
||||
run(() => {
|
||||
control_audio(play);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="fixed top-0 left-0">
|
||||
{#if play}
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
play = false;
|
||||
}}
|
||||
>
|
||||
@@ -48,7 +58,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
play = true;
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -5,24 +5,36 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
export let progress: number;
|
||||
export let text: string;
|
||||
export let color: string;
|
||||
let angle = 360 * progress;
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
$: angle = 360 * progress;
|
||||
$: console.log(angle);
|
||||
interface Props {
|
||||
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
|
||||
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(green 0deg, green 90deg, green 180deg, green);`;
|
||||
conic-gradient(green 0deg, green 90deg, green 180deg, green);`);
|
||||
|
||||
$: background = `radial-gradient(white 50%, transparent 51%),
|
||||
conic-gradient(transparent 0deg ${angle}deg, gainsboro ${angle}deg 360deg),
|
||||
conic-gradient(${color} 0deg, ${color} 90deg, ${color} 180deg, ${color});`;
|
||||
run(() => {
|
||||
background = `radial-gradient(white 50%, transparent 51%),
|
||||
conic-gradient(transparent 0deg ${angle}deg, gainsboro ${angle}deg 360deg),
|
||||
conic-gradient(${color} 0deg, ${color} 90deg, ${color} 180deg, ${color});`;
|
||||
});
|
||||
|
||||
$: cssVarStyles = `--background:${background}`;
|
||||
let cssVarStyles = $derived(`--background:${background}`);
|
||||
</script>
|
||||
|
||||
<div id="progress-circle" style={cssVarStyles} class="transition-all text-4xl text-black">
|
||||
|
||||
@@ -5,12 +5,18 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import { getLocalization } from '$lib/i18n';
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
export let question_count: number;
|
||||
export let final_results: Array<null> | Array<Array<PlayerAnswer>>;
|
||||
interface Props {
|
||||
question_count: number;
|
||||
final_results: Array<null> | Array<Array<PlayerAnswer>>;
|
||||
}
|
||||
|
||||
let { question_count, final_results }: Props = $props();
|
||||
|
||||
interface PlayerAnswer {
|
||||
username: string;
|
||||
@@ -18,8 +24,8 @@ SPDX-License-Identifier: MPL-2.0
|
||||
right: string;
|
||||
}
|
||||
|
||||
let data_available = false;
|
||||
let winners_arr;
|
||||
let data_available = $state(false);
|
||||
let winners_arr = $state();
|
||||
|
||||
const getWinnersSorted = () => {
|
||||
let winners = {};
|
||||
@@ -79,9 +85,9 @@ SPDX-License-Identifier: MPL-2.0
|
||||
}
|
||||
};
|
||||
let winners = getWinnersSorted();
|
||||
$: {
|
||||
run(() => {
|
||||
console.log(winners, winners_arr);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div>
|
||||
|
||||
@@ -5,6 +5,9 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run, createBubbler, preventDefault } from 'svelte/legacy';
|
||||
|
||||
const bubble = createBubbler();
|
||||
import { socket } from '$lib/socket';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
@@ -15,13 +18,17 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import BrownButton from '$lib/components/buttons/brown.svelte';
|
||||
|
||||
const { t } = getLocalization();
|
||||
export let game_pin: string;
|
||||
export let game_mode;
|
||||
|
||||
export let username;
|
||||
let custom_field;
|
||||
let custom_field_value;
|
||||
let captcha_enabled;
|
||||
interface Props {
|
||||
game_pin: string;
|
||||
game_mode: any;
|
||||
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;
|
||||
|
||||
@@ -105,10 +112,12 @@ SPDX-License-Identifier: MPL-2.0
|
||||
}
|
||||
};
|
||||
|
||||
$: if (game_pin.length > 5) {
|
||||
console.log('Setting game pin');
|
||||
set_game_pin();
|
||||
}
|
||||
run(() => {
|
||||
if (game_pin.length > 5) {
|
||||
console.log('Setting game pin');
|
||||
set_game_pin();
|
||||
}
|
||||
});
|
||||
|
||||
const setUsername = async () => {
|
||||
if (username.length <= 3) {
|
||||
@@ -177,8 +186,12 @@ SPDX-License-Identifier: MPL-2.0
|
||||
}
|
||||
});
|
||||
|
||||
$: console.log(game_pin, game_pin.length > 6);
|
||||
$: game_pin = game_pin.replace(/\D/g, '');
|
||||
run(() => {
|
||||
console.log(game_pin, game_pin.length > 6);
|
||||
});
|
||||
run(() => {
|
||||
game_pin = game_pin.replace(/\D/g, '');
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -194,7 +207,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
{#if game_pin === '' || game_pin.length < 6}
|
||||
<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>
|
||||
<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"
|
||||
@@ -213,7 +226,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{:else}
|
||||
<div class="flex flex-col justify-center align-center w-screen h-screen">
|
||||
<form
|
||||
on:submit|preventDefault={setUsername}
|
||||
onsubmit={preventDefault(setUsername)}
|
||||
class="flex-col flex justify-center align-center mx-auto"
|
||||
>
|
||||
<h1 class="text-lg text-center">{$t('words.username')}</h1>
|
||||
@@ -244,4 +257,4 @@ SPDX-License-Identifier: MPL-2.0
|
||||
data-sitekey={hcaptchaSitekey}
|
||||
data-size="invisible"
|
||||
data-theme="dark"
|
||||
/>
|
||||
></div>
|
||||
|
||||
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import type { Question } from '$lib/quiz_types';
|
||||
import { QuizQuestionType } from '$lib/quiz_types';
|
||||
import { socket } from '$lib/socket';
|
||||
@@ -19,12 +21,23 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
export let question: Question;
|
||||
export let game_mode;
|
||||
export let question_index: string | number;
|
||||
export let solution;
|
||||
interface Props {
|
||||
question: Question;
|
||||
game_mode: any;
|
||||
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);
|
||||
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');
|
||||
}*/
|
||||
|
||||
let timer_res = question.time;
|
||||
let selected_answer: string;
|
||||
let timer_res = $state(question.time);
|
||||
let selected_answer: string = $state();
|
||||
|
||||
// Stop the timer if the question is answered
|
||||
const timer = (time: string) => {
|
||||
@@ -62,11 +75,11 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
timer(question.time);
|
||||
|
||||
$: {
|
||||
run(() => {
|
||||
if (solution !== undefined) {
|
||||
timer_res = '0';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const selectAnswer = (answer: string) => {
|
||||
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) {
|
||||
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;
|
||||
return _arr;
|
||||
};
|
||||
$: set_answer_if_not_set_range(timer_res);
|
||||
let circular_progress = 0;
|
||||
$: {
|
||||
run(() => {
|
||||
set_answer_if_not_set_range(timer_res);
|
||||
});
|
||||
let circular_progress = $state(0);
|
||||
run(() => {
|
||||
try {
|
||||
circular_progress = 1 - ((100 / question.time) * parseInt(timer_res)) / 100;
|
||||
} catch {
|
||||
circular_progress = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const get_div_height = (): string => {
|
||||
if (game_mode === 'normal') {
|
||||
@@ -140,7 +155,9 @@ SPDX-License-Identifier: MPL-2.0
|
||||
return '100';
|
||||
}
|
||||
};
|
||||
$: console.log(slider_value, 'values');
|
||||
run(() => {
|
||||
console.log(slider_value, 'values');
|
||||
});
|
||||
const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58'];
|
||||
</script>
|
||||
|
||||
@@ -188,7 +205,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
answer.color ?? default_colors[i]
|
||||
)}"
|
||||
disabled={selected_answer !== undefined}
|
||||
on:click={() => selectAnswer(answer.answer)}
|
||||
onclick={() => selectAnswer(answer.answer)}
|
||||
>
|
||||
{#if game_mode === 'kahoot'}
|
||||
<img
|
||||
@@ -207,13 +224,12 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<span
|
||||
class="fixed top-0 bg-red-500 h-8 transition-all"
|
||||
style="width: {(100 / parseInt(question.time)) * parseInt(timer_res)}vw"
|
||||
/>
|
||||
></span>
|
||||
{#await import('svelte-range-slider-pips')}
|
||||
<Spinner />
|
||||
{:then c}
|
||||
<div class:pointer-events-none={selected_answer !== undefined} class="mt-24">
|
||||
<svelte:component
|
||||
this={c.default}
|
||||
<c.default
|
||||
bind:values={slider_value}
|
||||
bind:min={question.answers.min}
|
||||
bind:max={question.answers.max}
|
||||
@@ -238,7 +254,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<span
|
||||
class="fixed top-0 bg-red-500 h-8 transition-all"
|
||||
style="width: {(100 / parseInt(question.time)) * parseInt(timer_res)}vw"
|
||||
/>
|
||||
></span>
|
||||
<div class="flex justify-center mt-10">
|
||||
<p class="text-black dark:text-white">Enter your answer</p>
|
||||
</div>
|
||||
@@ -286,7 +302,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<span
|
||||
class="fixed top-0 bg-red-500 h-8 transition-all"
|
||||
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">
|
||||
{#each question.answers as answer, i (answer.id)}
|
||||
<div
|
||||
@@ -295,7 +311,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
style="background-color: {answer.color ?? '#b07156'}"
|
||||
>
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
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"
|
||||
@@ -322,7 +338,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<p class="w-full text-center p-2 text-2xl">{answer.answer}</p>
|
||||
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
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"
|
||||
@@ -363,8 +379,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{#await import('./questions/check.svelte')}
|
||||
<Spinner />
|
||||
{:then c}
|
||||
<svelte:component
|
||||
this={c.default}
|
||||
<c.default
|
||||
bind:question
|
||||
bind:selected_answer
|
||||
bind:game_mode
|
||||
|
||||
@@ -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';
|
||||
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;
|
||||
let _selected_answers = [false, false, false, false];
|
||||
interface Props {
|
||||
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) => {
|
||||
_selected_answers[i] = !_selected_answers[i];
|
||||
@@ -59,7 +69,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
default_colors[i]}; color: {get_foreground_color(
|
||||
answer.color ?? default_colors[i]
|
||||
)}"
|
||||
on:click={() => selectAnswer(i)}
|
||||
onclick={() => selectAnswer(i)}
|
||||
class:opacity-100={_selected_answers[i]}
|
||||
class:opacity-50={!_selected_answers[i]}
|
||||
>
|
||||
|
||||
@@ -5,15 +5,9 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<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) {
|
||||
const ret = {};
|
||||
@@ -23,8 +17,20 @@ SPDX-License-Identifier: MPL-2.0
|
||||
return ret;
|
||||
}
|
||||
|
||||
export let username;
|
||||
let score_by_username = {};
|
||||
interface Props {
|
||||
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) === '{}') {
|
||||
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) {
|
||||
score_by_username[i.username] = i.score;
|
||||
}
|
||||
|
||||
$: scores = sortObjectbyValue(scores);
|
||||
run(() => {
|
||||
scores = sortObjectbyValue(scores);
|
||||
});
|
||||
|
||||
const do_sth = () => {
|
||||
for (const i of Object.keys(score_by_username)) {
|
||||
|
||||
@@ -11,11 +11,20 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import Spinner from '$lib/Spinner.svelte';
|
||||
|
||||
const { t } = getLocalization();
|
||||
export let results: Array<Answer>;
|
||||
export let game_data: QuizData;
|
||||
export let solution: Question;
|
||||
export let question_index: string;
|
||||
let data_store = {};
|
||||
interface Props {
|
||||
results: Array<Answer>;
|
||||
game_data: QuizData;
|
||||
solution: Question;
|
||||
question_index: string;
|
||||
}
|
||||
|
||||
let {
|
||||
results,
|
||||
game_data,
|
||||
solution = $bindable(),
|
||||
question_index
|
||||
}: Props = $props();
|
||||
let data_store = $state({});
|
||||
|
||||
const question = solution.answers;
|
||||
for (let i = 0; i < question.length; i++) {
|
||||
@@ -26,7 +35,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
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);
|
||||
</script>
|
||||
|
||||
@@ -99,8 +108,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<Spinner />
|
||||
{:then c}
|
||||
<div class="grayscale pointer-events-none w-full">
|
||||
<svelte:component
|
||||
this={c.default}
|
||||
<c.default
|
||||
bind:values={slider_values}
|
||||
bind:min={solution.answers.min}
|
||||
bind:max={solution.answers.max}
|
||||
|
||||
@@ -5,9 +5,13 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
export let title: string;
|
||||
export let description: string;
|
||||
export let cover_image: string | undefined;
|
||||
interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
cover_image: string | undefined;
|
||||
}
|
||||
|
||||
let { title, description, cover_image }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col justify-center w-screen h-screen">
|
||||
|
||||
@@ -14,13 +14,17 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import BrownButton from '$lib/components/buttons/brown.svelte';
|
||||
import MediaComponent from '$lib/editor/MediaComponent.svelte';
|
||||
|
||||
export let question: Question;
|
||||
interface Props {
|
||||
question: Question;
|
||||
}
|
||||
|
||||
let { question = $bindable() }: Props = $props();
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
let selected_answer = undefined;
|
||||
let timer_res = question.time;
|
||||
let show_results = false;
|
||||
let selected_answer = $state(undefined);
|
||||
let timer_res = $state(question.time);
|
||||
let show_results = $state(false);
|
||||
|
||||
// Stop the timer if the question is answered
|
||||
const timer = (time: string) => {
|
||||
@@ -37,16 +41,16 @@ SPDX-License-Identifier: MPL-2.0
|
||||
timer_res = seconds.toString();
|
||||
}, 1000);
|
||||
};
|
||||
let slider_value = [0];
|
||||
let slider_value = $state([0]);
|
||||
if (question.type === QuizQuestionType.RANGE) {
|
||||
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);
|
||||
|
||||
let check_choice_selected = [false, false, false, false];
|
||||
let check_choice_selected = $state([false, false, false, false]);
|
||||
|
||||
function shuffleArray(a) {
|
||||
for (let i = a.length - 1; i > 0; i--) {
|
||||
@@ -75,7 +79,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
shuffleArray(question.answers);
|
||||
}
|
||||
|
||||
let order_corrected = false;
|
||||
let order_corrected = $state(false);
|
||||
const select_complex_answer = () => {
|
||||
/* const correct_order_ids = []
|
||||
for (const e of original_order) {
|
||||
@@ -128,7 +132,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
disabled={selected_answer !== undefined || timer_res === '0'}
|
||||
type="button"
|
||||
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;
|
||||
timer_res = '0';
|
||||
}}>{answer.answer}</button
|
||||
@@ -138,7 +142,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<button
|
||||
class="bg-orange-500 p-2 rounded-lg flex justify-center w-full transition my-5 text-black"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
show_results = true;
|
||||
}}>{$t('admin_page.get_results')}</button
|
||||
>
|
||||
@@ -151,8 +155,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<Spinner />
|
||||
{:then c}
|
||||
<div class="grayscale pointer-events-none w-full">
|
||||
<svelte:component
|
||||
this={c.default}
|
||||
<c.default
|
||||
bind:values={slider_values}
|
||||
bind:min={question.answers.min}
|
||||
bind:max={question.answers.max}
|
||||
@@ -167,8 +170,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<Spinner />
|
||||
{:then c}
|
||||
<div class:pointer-events-none={selected_answer !== undefined}>
|
||||
<svelte:component
|
||||
this={c.default}
|
||||
<c.default
|
||||
bind:values={slider_value}
|
||||
bind:min={question.answers.min}
|
||||
bind:max={question.answers.max}
|
||||
@@ -182,7 +184,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
type="button"
|
||||
class="w-1/3 text-3xl bg-[#B07156] my-2 disabled:opacity-60 rounded-lg p-1 transition"
|
||||
disabled={selected_answer !== undefined}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
selected_answer = slider_value[0];
|
||||
timer_res = '0';
|
||||
}}
|
||||
@@ -197,7 +199,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
type="button"
|
||||
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"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
selected_answer = i;
|
||||
timer_res = '0';
|
||||
}}>{answer.answer}</button
|
||||
@@ -211,7 +213,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<Spinner my={false} />
|
||||
{:then c}
|
||||
<div class="max-h-[90%] max-w-[90%]">
|
||||
<svelte:component this={c.default} bind:question />
|
||||
<c.default bind:question />
|
||||
</div>
|
||||
{/await}
|
||||
{:else if question.type === QuizQuestionType.TEXT}
|
||||
@@ -236,7 +238,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
type="button"
|
||||
disabled={!text_input}
|
||||
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;
|
||||
timer_res = '0';
|
||||
}}>{$t('words.submit')}</button
|
||||
@@ -252,7 +254,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
style="background-color: {answer.color ?? '#b07156'}"
|
||||
>
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
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"
|
||||
@@ -279,7 +281,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<p class="w-full text-center p-2 text-2xl">{answer.answer}</p>
|
||||
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
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"
|
||||
@@ -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"
|
||||
type="button"
|
||||
disabled={timer_res === '0'}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
select_complex_answer();
|
||||
}}
|
||||
>
|
||||
@@ -338,7 +340,7 @@ SPDX-License-Identifier: MPL-2.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:opacity-100={check_choice_selected[i]}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
check_choice_selected[i] = !check_choice_selected[i];
|
||||
}}>{answer.answer}</button
|
||||
>
|
||||
@@ -353,7 +355,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<button
|
||||
type="button"
|
||||
class="bg-orange-500 p-2 rounded-lg flex justify-center w-full transition my-5 text-black"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
show_results = true;
|
||||
}}>{$t('admin_page.get_results')}</button
|
||||
>
|
||||
|
||||
@@ -5,10 +5,12 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
|
||||
import { getLocalization } from '$lib/i18n';
|
||||
|
||||
export let data;
|
||||
import { fly } from 'svelte/transition';
|
||||
let { data = $bindable() } = $props();
|
||||
|
||||
const { t } = getLocalization();
|
||||
</script>
|
||||
@@ -19,13 +21,13 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<div class="flex align-middle p-4 gap-3">
|
||||
<span
|
||||
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-red-400 transition"
|
||||
/>
|
||||
></span>
|
||||
<span
|
||||
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-yellow-400 transition"
|
||||
/>
|
||||
></span>
|
||||
<span
|
||||
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-green-400 transition"
|
||||
/>
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dark:bg-gray-700">
|
||||
@@ -50,9 +52,9 @@ SPDX-License-Identifier: MPL-2.0
|
||||
src="/api/v1/storage/download/{data.cover_image}"
|
||||
alt="not available"
|
||||
class="max-h-72 h-auto w-auto"
|
||||
on:contextmenu|preventDefault={() => {
|
||||
oncontextmenu={preventDefault(() => {
|
||||
data.cover_image = '';
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -10,7 +10,11 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const { t } = getLocalization();
|
||||
export let type: QuizTivityTypes | undefined;
|
||||
interface Props {
|
||||
type: QuizTivityTypes | undefined;
|
||||
}
|
||||
|
||||
let { type = $bindable() }: Props = $props();
|
||||
|
||||
const PageTypes = [
|
||||
/* {
|
||||
@@ -52,7 +56,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<div class="rounded-sm p-6 border-[#B07156] border">
|
||||
<button
|
||||
class="text-xl text-black dark:text-white"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
type = pt.type;
|
||||
}}>{pt.name}</button
|
||||
>
|
||||
|
||||
@@ -9,7 +9,11 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import { getLocalization } from '$lib/i18n';
|
||||
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) {
|
||||
data = {
|
||||
@@ -39,7 +43,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
answer.correct = !answer.correct;
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -7,9 +7,13 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<script lang="ts">
|
||||
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) => {
|
||||
selected_answer = i;
|
||||
@@ -24,7 +28,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{#each data.answers as answer, i}
|
||||
<button
|
||||
class="rounded-sm p-6 bg-gray-700 flex transition-all"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
select_answer(i);
|
||||
}}
|
||||
class:opacity-50={selected_answer !== undefined && !answer.correct}
|
||||
|
||||
@@ -5,11 +5,17 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import type { Markdown } from '$lib/quiztivity/types';
|
||||
import { marked } from 'marked';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export let data: Markdown | undefined;
|
||||
interface Props {
|
||||
data: Markdown | undefined;
|
||||
}
|
||||
|
||||
let { data = $bindable() }: Props = $props();
|
||||
|
||||
if (!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>
|
||||
|
||||
<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"
|
||||
bind:value={data.markdown}
|
||||
placeholder="Enter your markdown here!"
|
||||
/>
|
||||
></textarea>
|
||||
<div class="w-full">
|
||||
<div
|
||||
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">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import type { Markdown } from '$lib/quiztivity/types';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { marked } from 'marked';
|
||||
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>
|
||||
|
||||
<div class="prose dark:prose-invert">
|
||||
|
||||
@@ -12,11 +12,15 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
export let data: Memory | undefined;
|
||||
let new_pair_data = {
|
||||
interface Props {
|
||||
data: Memory | undefined;
|
||||
}
|
||||
|
||||
let { data = $bindable() }: Props = $props();
|
||||
let new_pair_data = $state({
|
||||
text_1: '',
|
||||
text_2: ''
|
||||
};
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
data = {
|
||||
@@ -71,9 +75,9 @@ SPDX-License-Identifier: MPL-2.0
|
||||
rows="3"
|
||||
contenteditable="true"
|
||||
bind:value={new_pair_data.text_1}
|
||||
/>
|
||||
></textarea>
|
||||
<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>
|
||||
<BrownButton>{$t('quiztivity.memory.editor.upload_image')}</BrownButton>
|
||||
</div>
|
||||
@@ -84,9 +88,9 @@ SPDX-License-Identifier: MPL-2.0
|
||||
rows="3"
|
||||
contenteditable="true"
|
||||
bind:value={new_pair_data.text_2}
|
||||
/>
|
||||
></textarea>
|
||||
<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>
|
||||
<BrownButton>{$t('quiztivity.memory.editor.upload_image')}</BrownButton>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,11 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import { getLocalization } from '$lib/i18n';
|
||||
|
||||
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> => {
|
||||
for (let i = a.length - 1; i > 0; i--) {
|
||||
@@ -19,7 +23,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
return a;
|
||||
};
|
||||
|
||||
let card_opened = {};
|
||||
let card_opened = $state({});
|
||||
|
||||
const get_all_cards_as_single_array = (): MemoryCard[] => {
|
||||
console.log('data', data.cards);
|
||||
@@ -43,7 +47,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
const found_cards: string[] = [];
|
||||
let opened_active_cards: string[] = [];
|
||||
let try_count = 0;
|
||||
let try_count = $state(0);
|
||||
let game_finished = false;
|
||||
const flip_card = (id: string) => {
|
||||
if (found_cards.includes(id) || game_finished) {
|
||||
@@ -83,7 +87,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
{#each random_card_order as card}
|
||||
<button
|
||||
class="aspect-square flex border-[#B07156] border-2 rounded-sm"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
flip_card(card.id);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import type { Data } from './types';
|
||||
import { getLocalization } from '$lib/i18n';
|
||||
import BrownButton from '$lib/components/buttons/brown.svelte';
|
||||
@@ -21,13 +23,17 @@ SPDX-License-Identifier: MPL-2.0
|
||||
const { t } = getLocalization();
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let data: Data;
|
||||
export let saving: boolean;
|
||||
interface Props {
|
||||
data: Data;
|
||||
saving: boolean;
|
||||
}
|
||||
|
||||
let selected_slide = null;
|
||||
let opened_slide = null;
|
||||
let selected_type = undefined;
|
||||
let shares_menu_open = false;
|
||||
let { data = $bindable(), saving }: Props = $props();
|
||||
|
||||
let selected_slide = $state(null);
|
||||
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++) {
|
||||
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 });
|
||||
opened_slide = data.pages.length - 1;
|
||||
};
|
||||
$: handle_slide_add(selected_type);
|
||||
run(() => {
|
||||
handle_slide_add(selected_type);
|
||||
});
|
||||
|
||||
const delete_slide = () => {
|
||||
console.log(selected_slide);
|
||||
@@ -81,7 +89,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
<span />
|
||||
<span></span>
|
||||
{/if}
|
||||
<input
|
||||
class="bg-transparent outline-hidden text-center mx-auto"
|
||||
|
||||
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
|
||||
import BrownButton from '$lib/components/buttons/brown.svelte';
|
||||
import { getLocalization } from '$lib/i18n';
|
||||
import Spinner from '$lib/Spinner.svelte';
|
||||
@@ -16,9 +18,13 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import { fade, fly } from 'svelte/transition';
|
||||
|
||||
const { t } = getLocalization();
|
||||
export let open = false;
|
||||
export let id;
|
||||
let popover_open = false;
|
||||
interface Props {
|
||||
open?: boolean;
|
||||
id: any;
|
||||
}
|
||||
|
||||
let { open = $bindable(false), id }: Props = $props();
|
||||
let popover_open = $state(false);
|
||||
const load_shares = async (): Promise<{
|
||||
id: string;
|
||||
name?: string;
|
||||
@@ -73,8 +79,8 @@ SPDX-License-Identifier: MPL-2.0
|
||||
onMount(() => {
|
||||
document.body.addEventListener('keydown', close_start_game_if_esc_is_pressed);
|
||||
});
|
||||
let never_expires_checked = true;
|
||||
let selected_date = undefined;
|
||||
let never_expires_checked = $state(true);
|
||||
let selected_date = $state(undefined);
|
||||
const create_share = async () => {
|
||||
if (!selected_date && !never_expires_checked) {
|
||||
return;
|
||||
@@ -95,7 +101,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
});
|
||||
loaded_shares = load_shares();
|
||||
};
|
||||
let loaded_shares = load_shares();
|
||||
let loaded_shares = $state(load_shares());
|
||||
|
||||
const delete_share = async (id: string) => {
|
||||
if (!confirm('Do you really want to delete this Share?')) {
|
||||
@@ -117,13 +123,13 @@ SPDX-License-Identifier: MPL-2.0
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let add_shares_open = false;
|
||||
let add_shares_open = $state(false);
|
||||
</script>
|
||||
|
||||
<SmallPopover bind:open={popover_open} type={PopoverTypes.Copy} />
|
||||
<div
|
||||
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 }}
|
||||
>
|
||||
<div
|
||||
@@ -139,7 +145,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<form
|
||||
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 }}
|
||||
on:submit|preventDefault={create_share}
|
||||
onsubmit={preventDefault(create_share)}
|
||||
>
|
||||
<div class="grid grid-cols-2">
|
||||
<input
|
||||
|
||||
@@ -4,12 +4,12 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { getLocalization } from '$lib/i18n';
|
||||
|
||||
const { t } = getLocalization();
|
||||
export let quiz;
|
||||
import ImportedOrNot from '$lib/view_quiz/imported_or_not.svelte';
|
||||
let { quiz } = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex justify-center">
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
|
||||
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
export const navbarVisible = $state({ visible: true });
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const navbarVisible = writable(true);
|
||||
export const signedIn = writable(false);
|
||||
export const pathname = writable('/');
|
||||
|
||||
|
||||
@@ -5,7 +5,12 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
export let hovering: boolean;
|
||||
interface Props {
|
||||
hovering: boolean;
|
||||
children?: import('svelte').Snippet<[any]>;
|
||||
}
|
||||
|
||||
let { hovering = $bindable(), children }: Props = $props();
|
||||
|
||||
function enter() {
|
||||
hovering = true;
|
||||
@@ -16,6 +21,6 @@ SPDX-License-Identifier: MPL-2.0
|
||||
}
|
||||
</script>
|
||||
|
||||
<div on:mouseenter={enter} on:mouseleave={leave}>
|
||||
<slot {hovering} />
|
||||
<div onmouseenter={enter} onmouseleave={leave}>
|
||||
{@render children?.({ hovering, })}
|
||||
</div>
|
||||
|
||||
@@ -9,12 +9,16 @@ SPDX-License-Identifier: MPL-2.0
|
||||
import Hoverable from '$lib/view_quiz/Hoverable.svelte';
|
||||
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,
|
||||
like: false
|
||||
};
|
||||
});
|
||||
const tippy = createTippy({
|
||||
arrow: true,
|
||||
animation: 'perspective-subtle',
|
||||
@@ -50,7 +54,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
class="bg-green-500 rounded-full h-10 w-10 transition"
|
||||
use:tippy={{ content: 'Like this quiz!' }}
|
||||
class:opacity-40={FeedBackButtonsHovered.dislike}
|
||||
on:click={() => complete_action(true)}
|
||||
onclick={() => complete_action(true)}
|
||||
>
|
||||
<!-- heroicons/thumb-up -->
|
||||
<svg
|
||||
@@ -74,7 +78,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
class="rounded-full bg-red-500 h-10 w-10 transition"
|
||||
use:tippy={{ content: 'Dislike this quiz!' }}
|
||||
class:opacity-40={FeedBackButtonsHovered.like}
|
||||
on:click={() => complete_action(false)}
|
||||
onclick={() => complete_action(false)}
|
||||
>
|
||||
<!-- heroicons/thumb-down -->
|
||||
<svg
|
||||
@@ -96,7 +100,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<span class="text-center">{quiz.likes}</span>
|
||||
<span class="text-center">{quiz.dislikes}</span>
|
||||
</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="flex flex-col">
|
||||
<!-- heroicons/legacy-outline/Play -->
|
||||
|
||||
@@ -7,7 +7,11 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<script lang="ts">
|
||||
import { createTippy } from 'svelte-tippy';
|
||||
|
||||
export let imported: boolean | undefined;
|
||||
interface Props {
|
||||
imported: boolean | undefined;
|
||||
}
|
||||
|
||||
let { imported }: Props = $props();
|
||||
const tippy = createTippy({
|
||||
arrow: true,
|
||||
animation: 'perspective-subtle',
|
||||
|
||||
Reference in New Issue
Block a user