Merge pull request #443 from mawoka-myblock/frontend-dependency-update

This commit is contained in:
Marlon
2025-09-21 12:43:14 +02:00
committed by GitHub
164 changed files with 7848 additions and 5241 deletions
-1
View File
@@ -9,7 +9,6 @@ FROM node:19-bullseye as builder
ENV API_URL=https://mawoka.eu
#Ah, I've noticed that!!!
ENV REDIS_URL=redis://localhost:6379
ENV VITE_MAPBOX_ACCESS_TOKEN=pk.eyJ1IjoibWF3b2thIiwiYSI6ImNsMjBob3d4ZjBhcGszYnE0bWp4aXB1ZW4ifQ.IByxV1qeIuEWpHCWsuB88A
# This Mapbox-token is restricted to the following urls: classquiz.de, classquiz.mawoka.eu, test.com
ENV VITE_HCAPTCHA=ee81b2a1-acf3-4d20-b2a4-a7ea94c7eba5
# Hacaptcha secret ES_e0f8139cb5ad467d892e8d73a020cd2b
+74 -83
View File
@@ -15,95 +15,86 @@
"translations-scan": "i18next-scanner --config i18next-scanner.config.engine.cjs src/**/*.svelte"
},
"devDependencies": {
"@beyonk/svelte-mapbox": "^9.0.5",
"@ckeditor/ckeditor5-autoformat": "^41.1.0",
"@ckeditor/ckeditor5-basic-styles": "^41.1.0",
"@ckeditor/ckeditor5-build-balloon": "^41.1.0",
"@ckeditor/ckeditor5-editor-balloon": "^41.1.0",
"@ckeditor/ckeditor5-essentials": "^41.1.0",
"@ckeditor/ckeditor5-theme-lark": "^41.1.0",
"@felte/reporter-tippy": "^1.1.5",
"@felte/validator-yup": "^1.0.11",
"@ffmpeg/core": "^0.11.0",
"@ffmpeg/ffmpeg": "^0.11.6",
"@fontsource/marck-script": "^4.5.11",
"@sentry/browser": "^7.48.0",
"@sentry/tracing": "^7.48.0",
"@simplewebauthn/browser": "^7.2.0",
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-node": "^1.2.3",
"@sveltejs/kit": "^1.15.7",
"@tailwindcss/typography": "^0.5.9",
"@types/canvas-confetti": "^1.6.0",
"@types/cookie": "^0.5.1",
"@types/dompurify": "^3.0.2",
"@types/js-cookie": "^3.0.3",
"@types/luxon": "^3.3.0",
"@types/marked": "^4.3.0",
"@types/qrcode": "^1.5.0",
"@types/sortablejs": "^1.15.1",
"@types/ua-parser-js": "^0.7.36",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
"@unlazy/svelte": "^0.8.9",
"@uppy/compressor": "^1.0.2",
"@uppy/core": "^3.2.0",
"@uppy/dashboard": "^3.4.0",
"@uppy/drag-drop": "^3.0.2",
"@uppy/drop-target": "^2.0.1",
"@uppy/image-editor": "^2.1.2",
"@uppy/progress-bar": "^3.0.2",
"@uppy/status-bar": "^3.1.1",
"@uppy/svelte": "^3.0.2",
"@uppy/xhr-upload": "^3.2.0",
"autoprefixer": "^10.4.14",
"canvas-confetti": "^1.6.0",
"cookie": "^0.5.0",
"@felte/reporter-tippy": "^1.1.10",
"@felte/validator-yup": "^1.1.4",
"@ffmpeg/core": "^0.12.10",
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",
"@fontsource/marck-script": "^5.2.8",
"@sentry/browser": "^10.12.0",
"@sentry/tracing": "^7.120.4",
"@simplewebauthn/browser": "^13.2.0",
"@sveltejs/adapter-auto": "^6.1.0",
"@sveltejs/adapter-node": "^5.3.2",
"@sveltejs/kit": "^2.5.27",
"@sveltejs/vite-plugin-svelte": "^6.2.0",
"@tailwindcss/postcss": "^4.1.13",
"@tailwindcss/typography": "^0.5.18",
"@types/canvas-confetti": "^1.9.0",
"@types/js-cookie": "^3.0.6",
"@types/luxon": "^3.7.1",
"@types/qrcode": "^1.5.5",
"@types/sortablejs": "^1.15.8",
"@types/ua-parser-js": "^0.7.39",
"@typescript-eslint/eslint-plugin": "^8.44.0",
"@typescript-eslint/parser": "^8.44.0",
"@uppy/compressor": "^2.3.2",
"@uppy/core": "^4.5.3",
"@uppy/dashboard": "^4.4.3",
"@uppy/drag-drop": "^4.2.2",
"@uppy/drop-target": "^3.2.2",
"@uppy/image-editor": "^3.4.2",
"@uppy/progress-bar": "^4.3.2",
"@uppy/status-bar": "^4.2.3",
"@uppy/svelte": "^4.6.2",
"@uppy/xhr-upload": "^4.4.2",
"autoprefixer": "^10.4.21",
"canvas-confetti": "^1.9.3",
"ckeditor5": "^46.1.1",
"cookie": "^1.0.2",
"crypto-js": "^4.2.0",
"cssnano": "^6.0.0",
"dompurify": "^3.0.3",
"eslint": "^8.38.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-svelte3": "^4.0.0",
"felte": "^1.2.7",
"fuse.js": "^6.6.2",
"highlight.js": "^11.7.0",
"i18next": "^22.4.15",
"i18next-browser-languagedetector": "^7.0.1",
"js-cookie": "^3.0.1",
"cssnano": "^7.1.1",
"dompurify": "^3.2.7",
"eslint": "^9.36.0",
"eslint-config-prettier": "^10.1.8",
"felte": "^1.3.0",
"fuse.js": "^7.1.0",
"highlight.js": "^11.11.1",
"i18next": "^25.5.2",
"i18next-browser-languagedetector": "^8.2.0",
"js-cookie": "^3.0.5",
"jws": "^4.0.0",
"luxon": "^3.3.0",
"mapbox-gl": "^2.14.1",
"marked": "^5.0.0",
"mdsvex": "^0.10.6",
"minisearch": "^6.0.1",
"pikaso": "^2.7.6",
"plausible-tracker": "^0.3.8",
"postcss": "^8.4.31",
"postcss-import": "^15.1.0",
"postcss-load-config": "^4.0.1",
"prettier": "^2.8.8",
"prettier-plugin-svelte": "^2.10.1",
"qrcode": "^1.5.1",
"sass": "^1.62.0",
"socket.io-client": "^4.6.1",
"svelte": "^3.58.0",
"svelte-check": "^3.2.0",
"svelte-preprocess": "^5.0.3",
"svelte-range-slider-pips": "^2.1.1",
"luxon": "^3.7.2",
"marked": "^16.3.0",
"mdsvex": "^0.12.6",
"minisearch": "^7.2.0",
"pikaso": "^2.9.0",
"plausible-tracker": "^0.3.9",
"postcss": "^8.5.6",
"postcss-import": "^16.1.1",
"postcss-load-config": "^6.0.1",
"prettier": "^3.6.2",
"prettier-plugin-svelte": "^3.4.0",
"qrcode": "^1.5.4",
"sass": "^1.93.0",
"socket.io-client": "^4.8.1",
"svelte": "^5.0.0",
"svelte-check": "^4.3.1",
"svelte-preprocess": "^6.0.3",
"svelte-range-slider-pips": "^4.0.7",
"svelte-tippy": "^1.3.2",
"swiper": "^8.4.7",
"tailwindcss": "^3.3.1",
"swiper": "^12.0.2",
"tailwindcss": "^4.1.13",
"thumbhash": "^0.1.1",
"tinykeys": "^2.1.0",
"tinykeys": "^3.0.0",
"tippy.js": "^6.3.7",
"tslib": "^2.5.0",
"typescript": "~5.0.4",
"ua-parser-js": "^1.0.35",
"vite": "^4.2.3",
"tslib": "^2.8.1",
"typescript": "^5.5.0",
"ua-parser-js": "^2.0.5",
"vite": "^7.1.6",
"vite-plugin-cross-origin-isolation": "^0.1.6",
"vite-plugin-iso-import": "^1.0.0",
"yup": "^1.1.1"
"vite-plugin-iso-import": "^1.3.0",
"yup": "^1.7.0"
},
"type": "module"
}
+5446 -3121
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
#
# SPDX-License-Identifier: MPL-2.0
onlyBuiltDependencies:
- svelte-preprocess
+1 -1
View File
@@ -4,7 +4,7 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0
*/
const tailwindcss = require('tailwindcss');
const tailwindcss = require('@tailwindcss/postcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const postcss_import = require('postcss-import');
+21 -14
View File
@@ -7,8 +7,8 @@ SPDX-License-Identifier: MPL-2.0
@import 'tippy.js/animations/perspective-subtle.css';
@import 'tippy.js/dist/tippy.css';
/* Write your global styles here, in PostCSS syntax */
@tailwind base;
@tailwind components;
@import 'tailwindcss';
@plugin "@tailwindcss/typography";
@tailwind utilities;
.marck-script {
@@ -22,17 +22,24 @@ SPDX-License-Identifier: MPL-2.0
.link-hover:hover {
color: #4e6e58;
}
@layer utilities {
.normal-background {
@apply bg-gradient-to-r from-[#009444] via-[#39b54a] to-[#8dc63f] dark:bg-[#0f2702] dark:from-[#0f2702] dark:via-[#0f2702] dark:to[#0f2702];
}
.admin-button {
@apply px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded text-center hover:bg-gray-600 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50;
}
.action-button {
@apply px-4 py-2 leading-5 text-black dark:text-white transition-colors duration-200 transform bg-gray-50 dark:bg-gray-700 rounded text-center hover:bg-gray-300 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-gray-600;
@layer base {
button:not(:disabled),
[role='button']:not(:disabled) {
cursor: pointer;
}
}
@utility normal-background {
@apply bg-gradient-to-r from-[#009444] via-[#39b54a] to-[#8dc63f] dark:bg-[#0f2702] dark:from-[#0f2702] dark:via-[#0f2702] dark:to-[#0f2702];
}
@utility admin-button {
@apply px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded-sm text-center hover:bg-gray-600 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50;
}
@utility action-button {
@apply 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;
}
@utility btn-nav {
@apply text-lg font-medium px-3 text-gray-600 hover:text-green-600 py-1.5 transition-all duration-300;
}
-23
View File
@@ -1,23 +0,0 @@
<!--
SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { browser } from '$app/environment';
import { Map, Marker } from '@beyonk/svelte-mapbox';
export let lng: number;
export let lat: number;
export let markerText: string;
export let zoom = 12;
let center: [number, number];
$: center = [lng, lat];
</script>
{#if browser}
<Map accessToken={import.meta.env.VITE_MAPBOX_ACCESS_TOKEN} bind:center bind:zoom>
<Marker bind:lat bind:lng bind:label={markerText} />
</Map>
{/if}
+5 -1
View File
@@ -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">
+29 -20
View File
@@ -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}
+10 -5
View File
@@ -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;
@@ -26,11 +30,11 @@ SPDX-License-Identifier: MPL-2.0
{#if quiz_id}
<div
class="w-screen h-screen fixed top-0 left-0 bg-opacity-50 bg-black z-20 flex justify-center"
on:click={handle_on_click}
transition:fade|local={{ duration: 100 }}
class="w-screen h-screen fixed top-0 left-0 bg-black/50 z-20 flex justify-center"
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">
<div class="m-auto w-1/3 h-auto bg-white dark:bg-gray-700 p-4 rounded-sm">
<h1 class="text-3xl text-center mb-4">{$t('downloader.select_download_type')}</h1>
<div class="flex flex-row gap-4">
<div class="w-full flex justify-center">
@@ -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}
@@ -23,22 +38,22 @@ SPDX-License-Identifier: MPL-2.0
class:opacity-50={disabled}
class:cursor-not-allowed={disabled}
class:pointer-events-none={disabled}
class="text-black hover:bg-opacity-80 w-full px-4 py-2 leading-5 transition-all duration-200 transform bg-[#B07156] rounded text-center outline-none"
on:click
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"
onclick={bubble('click')}
class:flex
class:justify-center={flex}
>
<slot />
{@render children?.()}
</a>
{:else}
<button
{disabled}
{type}
class="text-black hover:opacity-80 w-full px-4 py-2 leading-5 transition-all duration-200 transform bg-[#B07156] rounded text-center focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 outline-none"
on:click
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"
onclick={bubble('click')}
class:flex
class:justify-center={flex}
>
<slot />
{@render children?.()}
</button>
{/if}
+24 -10
View File
@@ -5,33 +5,47 @@ 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}
<a
{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 text-center hover:bg-gray-300 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-gray-600"
on:click
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"
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 text-center hover:bg-gray-300 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-gray-600"
on:click
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"
onclick={bubble('click')}
class:flex
class:justify-center={flex}
>
<slot />
{@render children?.()}
</button>
{/if}
@@ -4,22 +4,23 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
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;
@@ -98,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();
@@ -205,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,39 +237,37 @@ This should be okay, right?
{#if open}
<div
class="fixed top-0 left-0 w-screen h-screen flex bg-black bg-opacity-50 z-50"
on:click={close_on_outside}
on:keyup={close_on_outside}
transition:fade={{ duration: 60 }}
class="fixed top-0 left-0 w-screen h-screen flex bg-black/50 z-50"
onclick={close_on_outside}
onkeyup={close_on_outside}
transition:fade|global={{ duration: 60 }}
>
<div class="m-auto w-1/3 h-2/3 rounded bg-black flex flex-col">
<div class="m-auto w-1/3 h-2/3 rounded-sm bg-black flex flex-col">
<div class="grid grid-cols-1 grid-rows-1 border-b border-b-white">
<p
class="col-start-1 row-start-1 w-full p-4 outline-none bg-gray-700 rounded-t text-gray-400"
class="col-start-1 row-start-1 w-full p-4 outline-hidden bg-gray-700 rounded-t text-gray-400"
>
{bg_text}
</p>
<input
type="text"
class="col-start-1 row-start-1 bg-transparent w-full p-4 outline-none bg-gray-700 rounded"
class="col-start-1 row-start-1 bg-transparent w-full p-4 outline-hidden bg-gray-700 rounded-sm"
bind:value={input}
/>
</div>
<div class="flex flex-col p-2 gap-2 overflow-scroll">
{#each visible_items as vi, i}
<div
transition:fade|local={{ duration: 60 }}
class="p-2 transition rounded"
transition:fade={{ duration: 60 }}
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>
<p
class="font-mono my-auto ml-auto h-fit bg-black bg-opacity-50 rounded p-0.5"
>
<p class="font-mono my-auto ml-auto h-fit bg-black/50 rounded-sm p-0.5">
/{vi.command}
{#if vi.args}
{#each vi.args as arg}
@@ -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,18 +16,26 @@ 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}
<div class="fixed w-screen top-10 z-[60] flex justify-center" transition:fly={{ y: -100 }}>
<div
class="fixed w-screen top-10 z-[60] 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 dark:text-gray-400 dark:bg-gray-800"
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">
@@ -46,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={{ y: -100 }}>
<div
class="flex items-center p-4 w-full max-w-xs text-gray-500 bg-white rounded-lg shadow dark: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}
+21 -21
View File
@@ -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,
@@ -94,9 +94,9 @@ SPDX-License-Identifier: MPL-2.0
</script>
<div
class="fixed top-0 left-0 flex justify-center w-screen h-screen bg-black bg-opacity-60 z-50 text-black"
transition:fade={{ duration: 100 }}
on:click={on_parent_click}
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 }}
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"
@@ -116,15 +116,15 @@ SPDX-License-Identifier: MPL-2.0
class="sr-only peer"
/>
<span
class="w-14 h-7 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
/>
class="w-14 h-7 bg-gray-200 peer-focus:outline-hidden peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
></span>
<span class="ml-3 text-sm font-medium text-gray-900"
>Captcha {captcha_selected ? 'enabled' : 'disabled'}</span
>
</label>
</div>
{#if captcha_selected}
<div class="flex justify-center mt-2" in:fade>
<div class="flex justify-center mt-2" in:fade|global>
<p class="w-1/3">
{$t('start_game.captcha_message')}
</p>
@@ -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';
}}
>
@@ -162,7 +162,7 @@ SPDX-License-Identifier: MPL-2.0
<label class="mr-4">{$t('result_page.custom_field')}</label>
<input
bind:value={custom_field}
class="rounded-lg p-2 outline-none placeholder:italic"
class="rounded-lg p-2 outline-hidden placeholder:italic"
placeholder="Phone Number or Email"
/>
</div>
@@ -175,8 +175,8 @@ SPDX-License-Identifier: MPL-2.0
class="sr-only peer"
/>
<span
class="w-14 h-7 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
/>
class="w-14 h-7 bg-gray-200 peer-focus:outline-hidden peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
></span>
<span class="ml-3 text-sm font-medium text-gray-900"
><a
href="/controller"
@@ -203,15 +203,15 @@ SPDX-License-Identifier: MPL-2.0
class="sr-only peer"
/>
<span
class="w-14 h-7 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
/>
class="w-14 h-7 bg-gray-200 peer-focus:outline-hidden peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
></span>
<span class="ml-3 text-sm font-medium text-gray-900"> Randomize answers</span>
</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);
}}
>
+24 -12
View File
@@ -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 />
+22 -14
View File
@@ -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,18 +59,18 @@ 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">
{#if Array.isArray(data.questions[selected_question].answers)}
{#each data.questions[selected_question].answers as answer, index}
<div
out:fade|local={{ duration: 150 }}
out:fade={{ duration: 150 }}
class="p-4 rounded-lg flex justify-center w-full transition relative"
class:bg-red-500={!answer.right}
class:bg-green-500={answer.right}
@@ -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;
@@ -97,7 +105,7 @@ SPDX-License-Identifier: MPL-2.0
<input
bind:value={answer.answer}
type="text"
class="border-b-2 border-dotted w-5/6 text-center rounded-lg bg-transparent outline-none focus:shadow-2xl transition-all"
class="border-b-2 border-dotted w-5/6 text-center rounded-lg bg-transparent outline-hidden focus:shadow-2xl transition-all"
style="background-color: {answer.color}; color: {get_foreground_color(
answer.color
)}"
@@ -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}
@@ -156,8 +164,8 @@ SPDX-License-Identifier: MPL-2.0
<button
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|local={{ duration: 150 }}
on:click={() => {
in:fade={{ duration: 150 }}
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(() => {
@@ -95,18 +99,20 @@ SPDX-License-Identifier: MPL-2.0
</script>
<div
class="fixed top-0 left-0 w-screen h-screen flex bg-black z-50 bg-opacity-50"
on:click={on_parent_click}
transition:fade|local={{ duration: 100 }}
class="fixed top-0 left-0 w-screen h-screen flex bg-black/50 z-50"
onclick={on_parent_click}
transition:fade={{ duration: 100 }}
>
<div class="m-auto w-2/3 h-5/6 rounded shadow-2xl bg-white dark:bg-gray-600 p-6 flex flex-col">
<div
class="m-auto w-2/3 h-5/6 rounded-sm shadow-2xl bg-white dark:bg-gray-600 p-6 flex flex-col"
>
<h1 class="text-center text-3xl mb-6">{$t('quiztivity.editor.select_page_type')}</h1>
<div class="grid grid-cols-4 gap-4 overflow-y-scroll">
{#each question_types as qt, i}
<div class="rounded p-6 border-[#B07156] border">
<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
>
+30 -18
View File
@@ -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) {
@@ -67,11 +79,11 @@ SPDX-License-Identifier: MPL-2.0
{:then data}
{#if type === 'img'}
<img
in:fade={{ duration: 300 }}
in:fade|global={{ duration: 300 }}
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
@@ -93,14 +105,14 @@ SPDX-License-Identifier: MPL-2.0
{#if fullscreen_open}
<div
class="fixed top-0 left-0 z-50 w-screen h-screen bg-black bg-opacity-50 fle p-2"
transition:fade={{ duration: 80 }}
on:click={() => (fullscreen_open = false)}
class="fixed top-0 left-0 z-50 w-screen h-screen bg-black/50 fle p-2"
transition:fade|global={{ duration: 80 }}
onclick={() => (fullscreen_open = false)}
>
<img
src={img_data.data}
alt={img_data.alt_text ?? 'Not available'}
class="object-cover rounded m-auto max-h-full max-w-full"
class="object-cover rounded-sm m-auto max-h-full max-w-full"
/>
</div>
{/if}
+20 -14
View File
@@ -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">
@@ -72,13 +78,13 @@ SPDX-License-Identifier: MPL-2.0
{#each data.questions[selected_question].answers as answer, i (answer.id)}
<div
animate:flip={{ duration: 200 }}
out:fade|local={{ duration: 150 }}
out:fade={{ duration: 150 }}
class="p-4 rounded-lg flex justify-center w-full transition relative border border-gray-600 flex-row gap-4 m-2"
>
<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"
@@ -154,7 +160,7 @@ SPDX-License-Identifier: MPL-2.0
<input
bind:value={answer.answer}
type="text"
class="border-b-2 border-dotted w-5/6 text-center rounded-lg bg-transparent outline-none"
class="border-b-2 border-dotted w-5/6 text-center rounded-lg bg-transparent outline-hidden"
style="background-color: {answer.color}; color: {get_foreground_color(
answer.color
)}"
@@ -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}
@@ -177,8 +183,8 @@ SPDX-License-Identifier: MPL-2.0
<button
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|local={{ duration: 150 }}
on:click={() => {
in:fade={{ duration: 150 }}
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
View File
@@ -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();
@@ -45,7 +49,7 @@ SPDX-License-Identifier: MPL-2.0
{#if Array.isArray(data.questions[selected_question].answers)}
{#each data.questions[selected_question].answers as answer, index}
<div
out:fade|local={{ duration: 150 }}
out:fade={{ duration: 150 }}
class="p-4 rounded-lg flex justify-center w-full transition relative"
class:dark:bg-gray-500={answer.answer}
class:bg-gray-300={answer.answer}
@@ -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;
@@ -80,12 +84,12 @@ SPDX-License-Identifier: MPL-2.0
<input
bind:value={answer.answer}
type="text"
class="border-b-2 border-dotted w-5/6 text-center rounded-lg bg-transparent outline-none"
class="border-b-2 border-dotted w-5/6 text-center rounded-lg bg-transparent outline-hidden"
placeholder={$t('editor.enter_answer')}
/>
<button
type="button"
on:click={() => {
onclick={() => {
answer.case_sensitive = !answer.case_sensitive;
}}
use:tippy={{ content: 'Case sensitive?', placement: 'top' }}
@@ -129,8 +133,8 @@ SPDX-License-Identifier: MPL-2.0
<button
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|local={{ duration: 150 }}
on:click={() => {
in:fade={{ duration: 150 }}
onclick={() => {
data.questions[selected_question].answers = [
...data.questions[selected_question].answers,
{ ...get_empty_answer() }
+16 -10
View File
@@ -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++) {
@@ -53,7 +59,7 @@ SPDX-License-Identifier: MPL-2.0
{#if Array.isArray(data.questions[selected_question].answers)}
{#each data.questions[selected_question].answers as answer, index}
<div
out:fade|local={{ duration: 150 }}
out:fade={{ duration: 150 }}
class="p-4 rounded-lg flex justify-center w-full transition relative"
class:bg-yellow-500={!reach(VotingQuestionSchema, 'answer').isValidSync(
answer.answer
@@ -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}
@@ -108,8 +114,8 @@ SPDX-License-Identifier: MPL-2.0
<button
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|local={{ duration: 150 }}
on:click={() => {
in:fade={{ duration: 150 }}
onclick={() => {
data.questions[selected_question].answers = [
...data.questions[selected_question].answers,
{
+46 -35
View File
@@ -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"
@@ -116,12 +124,12 @@ SPDX-License-Identifier: MPL-2.0
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
stroke-linecap="round"
stroke-linejoin="round"
></path>
/>
<path
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
stroke-linecap="round"
stroke-linejoin="round"
></path>
/>
</svg>
</button>
</div>
@@ -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
@@ -217,7 +223,7 @@ SPDX-License-Identifier: MPL-2.0
type="number"
max="999"
min="1"
class="w-20 bg-transparent rounded-lg text-lg border-2 border-gray-500 p-1 outline-none focus:shadow-2xl"
class="w-20 bg-transparent rounded-lg text-lg border-2 border-gray-500 p-1 outline-hidden focus:shadow-2xl"
bind:value={data.questions[selected_question].time}
/>
<p class="inline-block">s</p>
@@ -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>
@@ -268,18 +273,24 @@ SPDX-License-Identifier: MPL-2.0
{#if advanced_options_open}
<div
class="fixed top-0 left-0 w-full h-full bg-black/60 flex"
transition:fade={{ duration: 150 }}
transition:fade|global={{ duration: 150 }}
>
<div class="w-1/4 h-1/3 m-auto bg-white dark:bg-gray-700 rounded-lg flex flex-col p-2 gap-2">
<div
class="w-1/4 h-1/3 m-auto bg-white dark:bg-gray-700 rounded-lg flex flex-col p-2 gap-2"
>
<h1 class="text-3xl mx-auto">{$t('editor.advanced_settings')}</h1>
<label class="flex justify-around text-lg">
<span class="my-auto">{$t('editor.hide_question_results')}</span>
<input type="checkbox" bind:checked={data.questions[selected_question]["hide_results"]} />
<input
type="checkbox"
bind:checked={data.questions[selected_question]['hide_results']}
/>
</label>
<div class="mt-auto w-full">
<BrownButton on:click={() => (advanced_options_open = false)}>{$t('words.close')}</BrownButton>
<BrownButton on:click={() => (advanced_options_open = false)}
>{$t('words.close')}</BrownButton
>
</div>
</div>
</div>
{/if}
+29 -23
View File
@@ -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">
@@ -65,8 +73,8 @@ SPDX-License-Identifier: MPL-2.0
type="text"
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-none focus:shadow-2xl transition-all"
/>
class="p-3 rounded-lg border-gray-500 border text-center w-1/3 h-20 resize-none dark:bg-gray-500 outline-hidden focus:shadow-2xl transition-all"
></textarea>
</div>
{#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>
@@ -164,8 +171,8 @@ SPDX-License-Identifier: MPL-2.0
class="sr-only peer"
/>
<span
class="w-14 h-7 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-800 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
/>
class="w-14 h-7 bg-gray-200 peer-focus:outline-hidden peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-800 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
></span>
</label>
</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
+27 -23
View File
@@ -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,
@@ -82,10 +86,10 @@ SPDX-License-Identifier: MPL-2.0
<div class="border-r-2 pt-6 px-6 overflow-scroll h-full">
<div
bind:this={propertyCard}
class="bg-white shadow rounded-lg h-40 p-2 mb-6 hover:cursor-pointer drop-shadow-2xl border border-gray-500 dark:bg-gray-600 transition"
class="bg-white shadow-smrounded-lg h-40 p-2 mb-6 hover:cursor-pointer drop-shadow-2xl border border-gray-500 dark:bg-gray-600 transition"
class:bg-green-300={selected_question === -1}
class: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 }}
@@ -96,7 +100,7 @@ SPDX-License-Identifier: MPL-2.0
>
<p
type="text"
class="whitespace-nowrap truncate text-center w-full bg-transparent rounded dark:text-white"
class="whitespace-nowrap truncate text-center w-full bg-transparent rounded-sm dark:text-white"
class:dark:text-black={selected_question === -1}
>
{#if data.title}
@@ -117,9 +121,9 @@ SPDX-License-Identifier: MPL-2.0
>
<textarea
bind:value={data.description}
class="bg-transparent resize-none w-full rounded text-sm dark:text-white"
class="bg-transparent resize-none w-full rounded-sm text-sm dark:text-white"
class:dark:text-black={selected_question === -1}
/>
></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"
@@ -170,18 +174,18 @@ SPDX-License-Identifier: MPL-2.0
</div>
{#each data.questions as question, index}
<div
class="bg-white shadow rounded-lg h-40 p-2 mb-6 hover:cursor-pointer drop-shadow-2xl border border-gray-500 dark:bg-gray-600 transition relative"
class="bg-white shadow-smrounded-lg h-40 p-2 mb-6 hover:cursor-pointer drop-shadow-2xl border border-gray-500 dark:bg-gray-600 transition relative"
class:bg-green-300={index === selected_question}
class:dark:bg-green-500={index === selected_question}
on:click={() => {
onclick={() => {
setSelectedQuestion(index);
}}
bind:this={arr_of_cards[index]}
>
{#if reorder_mode}
<div
transition:fade={{ duration: 90 }}
class="absolute z-10 grid grid-cols-2 bg-transparent w-full rounded h-full"
transition:fade|global={{ duration: 90 }}
class="absolute z-10 grid grid-cols-2 bg-transparent w-full rounded-sm h-full"
>
<!-- Div is used, since it just put me on the dashboard when using button elements... Idk why and I hate it-->
<div
@@ -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);
@@ -298,7 +302,7 @@ SPDX-License-Identifier: MPL-2.0
class="h-10 border rounded-lg"
alt="Not available"
use:tippy={{
content: `<img src="/api/v1/storage/download/${question.image}" alt="Not available" class="rounded">`,
content: `<img src="/api/v1/storage/download/${question.image}" alt="Not available" class="rounded-sm">`,
allowHTML: true
}}
/>
@@ -375,12 +379,12 @@ SPDX-License-Identifier: MPL-2.0
</div>
{/each}
<div
class="bg-white shadow rounded-lg h-40 p-2 hover:cursor-pointer drop-shadow-2xl border border-gray-500 dark:bg-gray-600 grid grid-cols-2"
class="bg-white shadow-smrounded-lg h-40 p-2 hover:cursor-pointer drop-shadow-2xl border border-gray-500 dark:bg-gray-600 grid grid-cols-2"
>
<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 }];
}}
>
+20 -14
View File
@@ -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>
+24 -16
View File
@@ -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');
}}
>
@@ -101,9 +109,9 @@ SPDX-License-Identifier: MPL-2.0
{#if opened_dropdown === 'color'}
<div
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={{ duration: 100 }}
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')}
@@ -137,11 +145,11 @@ SPDX-License-Identifier: MPL-2.0
{#if opened_dropdown === 'font_size'}
<div
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={{ duration: 100 }}
transition:fade|global={{ duration: 100 }}
>
<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,
@@ -75,13 +75,13 @@ SPDX-License-Identifier: MPL-2.0
<div
class="bg-white m-auto rounded-lg shadow-lg p-4 flex flex-col dark:bg-gray-600 h-fit"
transition:fade={{ duration: 100 }}
transition:fade|global={{ duration: 100 }}
>
<ul>
{#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);
}
@@ -19,14 +22,14 @@ SPDX-License-Identifier: MPL-2.0
<div
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"
transition:fade={{ duration: 100 }}
transition:fade|global={{ duration: 100 }}
>
<label class="w-fit">
Time
<input
bind:value={time}
type="number"
class="w-20 bg-transparent rounded-lg text-lg border-2 border-gray-500 p-1 outline-none"
class="w-20 bg-transparent rounded-lg text-lg border-2 border-gray-500 p-1 outline-hidden"
/>
</label>
<label class="w-fit">
@@ -34,7 +37,7 @@ SPDX-License-Identifier: MPL-2.0
<input
bind:value={title}
type="text"
class="bg-transparent rounded-lg text-lg border-2 border-gray-500 p-1 transition outline-none"
class="bg-transparent rounded-lg text-lg border-2 border-gray-500 p-1 transition outline-hidden"
/>
</label>
</div>
@@ -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}
+28 -22
View File
@@ -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';
@@ -19,7 +18,6 @@ SPDX-License-Identifier: MPL-2.0
import '@uppy/core/dist/style.css';
import '@uppy/dashboard/dist/style.css';
import '@uppy/drop-target/dist/style.css';
// import '@uppy/file-input/dist/style.css'
import '@uppy/image-editor/dist/style.css';
import type { EditorData } from '../quiz_types';
import { getLocalization } from '$lib/i18n';
@@ -28,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 {
@@ -68,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,12 +140,12 @@ SPDX-License-Identifier: MPL-2.0
{#if modalOpen}
<div
class="w-screen h-screen fixed top-0 left-0 bg-opacity-50 bg-black z-20 flex justify-center"
on:click={handle_on_click}
transition:fade|local={{ duration: 100 }}
class="w-screen h-screen fixed top-0 left-0 bg-black/50 z-20 flex justify-center"
onclick={handle_on_click}
transition:fade={{ duration: 100 }}
>
{#if selected_type === null}
<div class="m-auto w-1/3 h-auto bg-white dark:bg-gray-700 p-4 rounded">
<div class="m-auto w-1/3 h-auto bg-white dark:bg-gray-700 p-4 rounded-sm">
<h1 class="text-3xl text-center mb-4">{$t('uploader.select_upload_type')}</h1>
<div class="flex flex-row gap-4">
<div class="w-full">
@@ -180,15 +186,15 @@ SPDX-License-Identifier: MPL-2.0
</div>
</div>
{:else if selected_type === AvailableUploadTypes.Image}
<div class="m-auto w-1/3 h-5/6" transition:fade|local={{ duration: 100 }}>
<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}
<div
class="m-auto w-1/3 h-auto bg-white dark:bg-gray-700 p-4 rounded"
transition:fade|local={{ duration: 100 }}
class="m-auto w-1/3 h-auto bg-white dark:bg-gray-700 p-4 rounded-sm"
transition:fade={{ duration: 100 }}
>
<h1 class="text-3xl text-center mb-4">{$t('uploader.upload_a_video')}</h1>
{#if video_popup}
@@ -212,11 +218,11 @@ SPDX-License-Identifier: MPL-2.0
{/if}
</div>
{/if}
<div class="flex justify-center w-full pt-10" transition:fade|local>
<div class="flex justify-center w-full pt-10" transition:fade>
<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();
@@ -40,16 +44,16 @@ SPDX-License-Identifier: MPL-2.0
{:then images}
<div class="flex w-screen p-8 h-screen">
<div
class="flex flex-col w-1/3 m-auto overflow-scroll h-full rounded p-4 gap-4 bg-white dark:bg-gray-700"
class="flex flex-col w-1/3 m-auto overflow-scroll h-full rounded-sm p-4 gap-4 bg-white dark:bg-gray-700"
>
{#each images as image}
<div class="rounded border-2 border-[#B07156] p-2 flex-col flex gap-2">
<div class="rounded-sm border-2 border-[#B07156] p-2 flex-col flex gap-2">
<div>
<img
src="/api/v1/storage/download/{image.id}"
loading="lazy"
alt={image.alt_text}
class="object-contain w-full h-full max-h-full rounded"
class="object-contain w-full h-full max-h-full rounded-sm"
/>
</div>
<p class="text-center">{image.filename ?? 'No name available'}</p>
+18 -12
View File
@@ -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}
@@ -53,7 +59,7 @@ SPDX-License-Identifier: MPL-2.0
{:else}
<div class="flex w-screen p-8 h-full mt-8 mb-1">
<div
class="flex flex-col w-1/3 m-auto overflow-scroll h-full rounded p-4 gap-2 bg-white dark:bg-gray-700"
class="flex flex-col w-1/3 m-auto overflow-scroll h-full rounded-sm p-4 gap-2 bg-white dark:bg-gray-700"
>
<h1 class="text-2xl text-center">{$t('uploader.images_by_pixabay')}</h1>
<div class="flex">
@@ -64,10 +70,10 @@ 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-none p-1 rounded dark:bg-gray-500 bg-gray-300"
class="w-full outline-hidden p-1 rounded-sm dark:bg-gray-500 bg-gray-300"
bind:value={search_term}
/>
<div class="w-fit">
@@ -77,13 +83,13 @@ SPDX-License-Identifier: MPL-2.0
<span class="italic text-center text-sm">{$t('uploader.search_english_only')}</span>
{#each data.hits as image}
<div class="rounded border-2 border-[#B07156] p-2 flex-col flex gap-2">
<div class="rounded-sm border-2 border-[#B07156] p-2 flex-col flex gap-2">
<div>
<img
src={image.webformatURL}
loading="lazy"
alt="unavailable"
class="object-contain w-full h-full rounded max-h-[80vh]"
class="object-contain w-full h-full rounded-sm max-h-[80vh]"
/>
</div>
<BrownButton
+5 -1
View File
@@ -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');
+3 -2
View File
@@ -45,7 +45,7 @@ export class I18nService {
initialize(): void {
this.i18n.use(LanguageDetector).init({
// lng: INITIAL_LANGUAGE,
compatibilityJSON: 'v3',
compatibilityJSON: 'v4',
fallbackLng: 'en',
debug: false,
defaultNS: 'translation',
@@ -61,7 +61,8 @@ export class I18nService {
detection: {
order: ['querystring', 'cookie', 'localStorage', 'navigator'],
lookupQuerystring: 'lng',
lookupLocalStorage: 'language'
lookupLocalStorage: 'language',
lookupSessionStorage: true
}
});
this.i18n.addResourceBundle('en', 'translation', en);
+7 -3
View File
@@ -22,15 +22,19 @@ export const getLocalization = () => {
return getContext<I18nContext>(CONTEXT_KEY);
};
export const initLocalizationContext = () => {
export const initLocalizationContext = (start_lanugage: string): { i18n: I18nService } => {
// Initialize our services
const i18n = new I18nService();
const tranlator = new I18NextTranslationService(i18n);
let locale: any;
if (start_lanugage) {
locale = start_lanugage;
}
tranlator.locale.set(locale);
// skipcq: JS-0357
setLocalization({
t: tranlator.translate,
currentLanguage: tranlator.locale
currentLanguage: locale
});
return {
+53 -8
View File
@@ -5,26 +5,47 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import BalloonEditor from '@ckeditor/ckeditor5-build-balloon';
import { run } from 'svelte/legacy';
import {
BalloonEditor,
Essentials,
Autoformat,
Bold,
Italic,
Paragraph,
TextTransformation,
Superscript,
Subscript,
Strikethrough
} from 'ckeditor5';
import 'ckeditor5/ckeditor5.css';
// import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
// import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold.js';
// import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic.js';
// 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,
@@ -54,8 +75,26 @@ SPDX-License-Identifier: MPL-2.0
};*/
let editor;
onMount(() => {
class Editor extends BalloonEditor {
static builtinPlugins = [
Essentials,
Autoformat,
Bold,
Italic,
Paragraph,
TextTransformation,
Strikethrough,
Subscript,
Superscript
];
static defaultConfig = {
language: 'en'
};
}
// BalloonEditor.builtinPlugins = [Strikethrough]
BalloonEditor.create(html_el, {
Editor.create(html_el, {
licenseKey: 'GPL',
// plugins: [Strikethrough],
config: {
enterMode: BalloonEditor.ENTER_DIV,
@@ -89,6 +128,12 @@ SPDX-License-Identifier: MPL-2.0
<div
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]"
/>
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>
:global(.ck-powered-by) {
display: none;
}
</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">
@@ -102,28 +102,28 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex justify-evenly lg:w-2/6">
{#if teacherTab}
<button
class="border border-blue-600 p-2 border-2 rounded hover:bg-gray-700 text-2xl"
on:click={() => {
class="border border-blue-600 p-2 border-2 rounded-sm hover:bg-gray-700 text-2xl"
onclick={() => {
teacherTab = true;
}}
>{$t('index_page.teachers_site')}
</button>
{:else}
<button
class="border border-black p-2 border-2 rounded hover:bg-gray-700 text-base"
class="border border-black p-2 border-2 rounded-sm hover:bg-gray-700 text-base"
class:border-black={!teacherTab}
class:text-base={!teacherTab}
on:click={() => {
onclick={() => {
teacherTab = true;
}}
>{$t('index_page.teachers_site')}
</button>
{/if}
<button
class="border border-black p-2 border-2 rounded hover:bg-gray-700"
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')}
+9 -7
View File
@@ -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"
@@ -41,7 +45,7 @@ SPDX-License-Identifier: MPL-2.0
name="email"
required
placeholder="E-mail"
class="m-0.5 border-b-2 border-gray-300 rounded-lg p-0.5 outline-none focus:outline-gray-400 bg-transparent"
class="m-0.5 border-b-2 border-gray-300 rounded-lg p-0.5 outline-hidden focus:outline-gray-400 bg-transparent"
/>
</p>
<p>
@@ -49,7 +53,7 @@ SPDX-License-Identifier: MPL-2.0
type="text"
name="name"
placeholder="Name (optional)"
class="m-0.5 border-b-2 border-gray-300 rounded-lg p-0.5 outline-none focus:outline-gray-400 bg-transparent"
class="m-0.5 border-b-2 border-gray-300 rounded-lg p-0.5 outline-hidden focus:outline-gray-400 bg-transparent"
/>
</p>
@@ -64,9 +68,7 @@ SPDX-License-Identifier: MPL-2.0
<label for="fec55">ClassQuiz-News</label>
</p>
<div class="bg-gradient-to-r from-[#009444] via-[#39b54a] to-[#8dc63f] w-fit rounded-lg">
<button
type="submit"
class="m-0.5 bg-opacity-70 bg-white rounded-lg py-0.5 px-1 text-black"
<button type="submit" class="m-0.5 bg-white/7 rounded-lg py-0.5 px-1 text-black"
>Subscribe!
</button>
</div>
+9 -5
View File
@@ -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"
+11 -7
View File
@@ -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>
@@ -20,7 +24,7 @@ By Flowbite, but changed: https://flowbite.com/docs/components/modal/#default-mo
<div tabindex="-1" class="overflow-y-auto overflow-x-hidden md:inset-0 lg:w-2/6 w-5/6">
<div class="relative p-4 w-full max-w-2xl h-full md:h-auto">
<!-- Modal content -->
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
<div class="relative bg-white rounded-lg shadow-smdark:bg-gray-700">
<!-- Modal header -->
<div
class="flex justify-between items-start p-4 rounded-t border-b dark:border-gray-600"
@@ -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,10 +66,10 @@ 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-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-hidden focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
Close
</button>
+12 -18
View File
@@ -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' ||
@@ -54,7 +54,7 @@ SPDX-License-Identifier: MPL-2.0
class="font-black tracking-tight text-xl lg:text-2xl text-black marck-script link-hover px-3 lg:px-5"
>ClassQuiz</a
>
<a class="btn-nav border-2 rounded" href="/play">{$t('words.play')}</a>
<a class="btn-nav border-2 rounded-sm" href="/play">{$t('words.play')}</a>
<a class="btn-nav" href="/explore">{$t('words.explore')}</a>
<a class="btn-nav" href="/search">{$t('words.search')}</a>
{#if $signedIn}
@@ -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
@@ -271,8 +271,8 @@ SPDX-License-Identifier: MPL-2.0
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><path in:draw={{ duration: 300 }} d="M18 6 6 18" /><path
in:draw={{ duration: 300 }}
><path in:draw|global={{ duration: 300 }} d="M18 6 6 18" /><path
in:draw|global={{ duration: 300 }}
d="m6 6 12 12"
/></svg
>
@@ -283,7 +283,7 @@ SPDX-License-Identifier: MPL-2.0
<!-- Navbar content -->
{#if !menuIsClosed}
<div class="flex flex-col" transition:slide={{ duration: 400 }}>
<div class="flex flex-col" transition:slide|global={{ duration: 400 }}>
<a class="btn-nav" href="/explore">{$t('words.explore')}</a>
<a class="btn-nav" href="/search">{$t('words.search')}</a>
{#if $signedIn}
@@ -340,9 +340,3 @@ SPDX-License-Identifier: MPL-2.0
{/if}
</div>
</nav>
<style lang="scss">
.btn-nav {
@apply text-lg font-medium px-3 text-gray-600 hover:text-green-600 py-1.5 transition-all duration-300;
}
</style>
+30 -16
View File
@@ -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,12 +58,12 @@ 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}
<p
in:fly={{ y: -300, delay: player_count_or_five * 1200 - (i + 1) * 1000 }}
in:fly|global={{ y: -300, delay: player_count_or_five * 1200 - (i + 1) * 1000 }}
style="font-size: {player_count_or_five - i / 2}rem"
class="text-center"
>
@@ -68,7 +78,7 @@ SPDX-License-Identifier: MPL-2.0
</div>
{#if data[username]}
<div class="fixed bottom-0 left-0 flex justify-center w-full mb-6">
<div class="mx-auto p-2 border-[#B07156] border-4 rounded">
<div class="mx-auto p-2 border-[#B07156] border-4 rounded-sm">
<p class="text-center">{$t('play_page.your_score', { score: data[username] })}</p>
{#each player_names as player, i}
{#if player === username}
@@ -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,10 +61,10 @@ 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 hover:cursor-pointer"
class="block mx-auto w-1/2 dark:bg-white shadow-2xl rounded-sm hover:cursor-pointer"
/>
{#if cqc_code}
<div class="m-auto">
@@ -98,10 +107,10 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex flex-row w-full mt-4 px-10 flex-wrap">
{#if players.length > 0}
{#each players as player}
<div class="p-2 m-2 border-2 border-[#B07156] rounded hover:cursor-pointer">
<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
>
@@ -114,14 +123,14 @@ SPDX-License-Identifier: MPL-2.0
{#if fullscreen_open}
<div
class="fixed top-0 left-0 z-50 w-screen h-screen bg-black bg-opacity-50 fle p-2"
transition:fade={{ duration: 80 }}
on:click={() => (fullscreen_open = false)}
class="fixed top-0 left-0 z-50 w-screen h-screen bg-black/50 flex p-2"
transition:fade|global={{ duration: 80 }}
onclick={() => (fullscreen_open = false)}
>
<img
alt="QR code to join the game"
src="/api/v1/utils/qr/{game_pin}"
class="object-contain rounded m-auto h-full bg-white"
class="object-contain rounded-sm m-auto h-full bg-white"
/>
</div>
{/if}
+23 -11
View File
@@ -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>
+58 -42
View File
@@ -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={{ 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={{ 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>
+8 -4
View File
@@ -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;
}
@@ -45,7 +49,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex gap-12">
{#each quiz_answers as answer, i}
<div
class="w-20 self-end flex justify-center border border-black shadow-xl rounded"
class="w-20 self-end flex justify-center border border-black shadow-xl rounded-sm"
class:shadow-blue-500={answer_correct[i] &&
question.type !== QuizQuestionType.VOTING}
class:shadow-yellow-500={!answer_correct[i] &&
@@ -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">
+17 -7
View File
@@ -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;
}}
>
+24 -12
View File
@@ -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">
+12 -6
View File
@@ -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>
+31 -18
View File
@@ -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,10 +207,10 @@ 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-none p-2 rounded-lg focus:shadow-2xl transition-all"
class="border border-gray-400 self-center text-center text-black ring-0 outline-hidden p-2 rounded-lg focus:shadow-2xl transition-all"
bind:value={game_pin}
maxlength="6"
inputmode="numeric"
@@ -213,19 +226,19 @@ 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>
<input
class="border border-gray-400 self-center text-center text-black ring-0 outline-none p-2 rounded-lg focus:shadow-2xl transition-all"
class="border border-gray-400 self-center text-center text-black ring-0 outline-hidden p-2 rounded-lg focus:shadow-2xl transition-all"
bind:value={username}
maxlength="17"
/>
{#if custom_field}
<h1 class="text-lg text-center">{custom_field}</h1>
<input
class="border border-gray-400 self-center text-center text-black ring-0 outline-none p-2 rounded-lg focus:shadow-2xl transition-all"
class="border border-gray-400 self-center text-center text-black ring-0 outline-hidden p-2 rounded-lg focus:shadow-2xl transition-all"
bind:value={custom_field_value}
/>
{/if}
@@ -244,4 +257,4 @@ SPDX-License-Identifier: MPL-2.0
data-sitekey={hcaptchaSitekey}
data-size="invisible"
data-theme="dark"
/>
></div>
+44 -29
View File
@@ -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>
@@ -247,7 +263,7 @@ SPDX-License-Identifier: MPL-2.0
type="text"
bind:value={text_input}
disabled={selected_answer}
class="bg-gray-50 focus:ring text-gray-900 rounded-lg focus:ring-blue-500 block w-full p-2 dark:bg-gray-700 dark:text-white dark:focus:ring-blue-500 outline-none transition text-center disabled:opacity-50 disabled:cursor-not-allowed"
class="bg-gray-50 focus:ring text-gray-900 rounded-lg focus:ring-blue-500 block w-full p-2 dark:bg-gray-700 dark:text-white dark:focus:ring-blue-500 outline-hidden transition text-center disabled:opacity-50 disabled:cursor-not-allowed"
/>
</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,10 +311,10 @@ 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 bg-opacity-30 w-full flex justify-center rounded-lg p-2 hover:bg-opacity-20 transition"
class="disabled:opacity-50 shadow-lg bg-black/30 w-full flex justify-center rounded-lg p-2 hover:bg-black/20 transition"
type="button"
disabled={i === 0 || selected_answer}
>
@@ -322,10 +338,10 @@ 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 bg-opacity-30 w-full flex justify-center rounded-lg p-2 hover:bg-opacity-20 transition"
class="disabled:opacity-50 shadow-lg bg-black/30 w-full flex justify-center rounded-lg p-2 hover:bg-black/20 transition"
type="button"
disabled={i === question.answers.length - 1 || selected_answer}
>
@@ -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
+17 -7
View File
@@ -11,14 +11,24 @@ SPDX-License-Identifier: MPL-2.0
import CircularTimer from '$lib/play/circular_progress.svelte';
// import CircularTimer from '$lib/play/circular_progress.svelte';
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]}
>
+23 -13
View File
@@ -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)) {
@@ -52,7 +62,7 @@ SPDX-License-Identifier: MPL-2.0
<div>
<div class="flex justify-center h-screen">
<div class="m-auto flex flex-col">
<p class="p-4 bg-black bg-opacity-40 rounded-lg text-2xl">
<p class="p-4 bg-black/40 rounded-lg text-2xl">
+{score_by_username[username] ?? '0'}
</p>
<p>Total score: {scores[username] ?? '0'}</p>
+16 -8
View File
@@ -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}
+7 -3
View File
@@ -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">
+34 -28
View File
@@ -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) {
@@ -91,7 +95,11 @@ SPDX-License-Identifier: MPL-2.0
};
</script>
<div class="w-full px-6 lg:px-20 h-[80vh] absolute" in:fly={{ x: 100 }} out:fly={{ x: -100 }}>
<div
class="w-full px-6 lg:px-20 h-[80vh] absolute"
in:fly|global={{ x: 100 }}
out:fly|global={{ x: -100 }}
>
<h1 class="text-3xl text-center">{@html question.question}</h1>
{#if question.image !== null}
<div>
@@ -124,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
@@ -134,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
>
@@ -147,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}
@@ -163,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}
@@ -178,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';
}}
@@ -193,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
@@ -207,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}
@@ -224,7 +230,7 @@ SPDX-License-Identifier: MPL-2.0
<input
type="text"
bind:value={text_input}
class="bg-gray-50 focus:ring text-gray-900 rounded-lg focus:ring-blue-500 block w-full p-2 dark:bg-gray-700 dark:text-white dark:focus:ring-blue-500 outline-none transition text-center"
class="bg-gray-50 focus:ring text-gray-900 rounded-lg focus:ring-blue-500 block w-full p-2 dark:bg-gray-700 dark:text-white dark:focus:ring-blue-500 outline-hidden transition text-center"
/>
</div>
<div class="flex justify-center">
@@ -232,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
@@ -248,10 +254,10 @@ 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 bg-opacity-30 w-full flex justify-center rounded-lg p-2 hover:bg-opacity-20 transition"
class="disabled:opacity-50 transition shadow-lg bg-black/30 w-full flex justify-center rounded-lg p-2 hover:bg-black/20 transition"
type="button"
disabled={i === 0 || order_corrected}
>
@@ -275,10 +281,10 @@ 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 bg-opacity-30 w-full flex justify-center rounded-lg p-2 hover:bg-opacity-20 transition"
class="disabled:opacity-50 transition shadow-lg bg-black/30 w-full flex justify-center rounded-lg p-2 hover:bg-black/20 transition"
type="button"
disabled={i === question.answers.length - 1 || order_corrected}
>
@@ -305,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();
}}
>
@@ -334,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
>
@@ -349,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,27 +5,29 @@ 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>
<div class="w-full px-6 lg:px-20 h-[80vh] absolute" in:fly={{ x: 100 }} out:fly={{ x: -100 }}>
<div class="w-full px-6 lg:px-20 h-[80vh] absolute" in:fly|global={{ x: 100 }} out:fly|global={{ x: -100 }}>
<div class="rounded-lg bg-white w-full h-full border-gray-500 dark:bg-gray-700">
<div class="h-fit bg-gray-300 rounded-t-lg dark:bg-gray-500">
<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 = [
/* {
@@ -41,18 +45,18 @@ SPDX-License-Identifier: MPL-2.0
</script>
<div
class="fixed top-0 left-0 z-50 bg-black bg-opacity-50 flex w-screen h-screen"
transition:fade={{ duration: 100 }}
class="fixed top-0 left-0 z-50 bg-black/50 flex w-screen h-screen"
transition:fade|global={{ duration: 100 }}
>
<div class="m-auto w-5/6 h-5/6">
<div class="rounded bg-white p-6 dark:bg-gray-600">
<div class="rounded-sm bg-white p-6 dark:bg-gray-600">
<h1 class="text-center text-3xl mb-6">{$t('quiztivity.editor.select_page_type')}</h1>
<div class="grid grid-cols-4 gap-4 overflow-y-scroll">
{#each PageTypes as pt}
<div class="rounded p-6 border-[#B07156] border">
<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 = {
@@ -24,22 +28,22 @@ SPDX-License-Identifier: MPL-2.0
<div>
<div class="flex justify-center">
<input
class="bg-transparent outline-none text-3xl text-center"
class="bg-transparent outline-hidden text-3xl text-center"
placeholder="Enter question here..."
bind:value={data.question}
/>
</div>
<div class="grid grid-cols-2 m-4 gap-4">
{#each data.answers as answer}
<div class="rounded p-6 bg-gray-700 flex">
<div class="rounded-sm p-6 bg-gray-700 flex">
<input
bind:value={answer.answer}
class="w-full my-auto bg-transparent outline-none text-center text-white"
class="w-full my-auto bg-transparent outline-hidden text-center text-white"
placeholder="Enter answer here"
/>
<button
type="button"
on:click={() => {
onclick={() => {
answer.correct = !answer.correct;
}}
>
@@ -78,7 +82,7 @@ SPDX-License-Identifier: MPL-2.0
</div>
{/each}
{#if data.answers.length < 4}
<div class="rounded p-6 bg-gray-700">
<div class="rounded-sm p-6 bg-gray-700">
<BrownButton
on:click={() => {
data.answers = [...data.answers, { ...{ answer: '', correct: false } }];
@@ -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;
@@ -23,8 +27,8 @@ SPDX-License-Identifier: MPL-2.0
<div class="grid grid-cols-1 lg:grid-cols-2 m-4 gap-4">
{#each data.answers as answer, i}
<button
class="rounded p-6 bg-gray-700 flex transition-all"
on:click={() => {
class="rounded-sm p-6 bg-gray-700 flex transition-all"
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,20 +23,22 @@ 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">
<textarea
class="w-full resize-none border-[#B07156] border-2 rounded outline-none p-2 bg-opacity-30 bg-white dark:placeholder-gray-300"
class="w-full resize-none border-[#B07156] border-2 rounded-sm outline-hidden p-2 bg-white/30 dark:placeholder-gray-300"
bind:value={data.markdown}
placeholder="Enter your markdown here!"
/>
></textarea>
<div class="w-full">
<div
class="aspect-video prose max-w-none border-[#B07156] border-2 rounded p-2 dark:prose-invert"
class="aspect-video prose max-w-none border-[#B07156] border-2 rounded-sm p-2 dark:prose-invert"
>
{@html rendered_html}
</div>
@@ -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 = {
@@ -61,32 +65,32 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex justify-center">
<div class="grid grid-cols-4 w-11/12 gap-4">
<div class="border-[#B07156] border-2 rounded">
<div class="border-[#B07156] border-2 rounded-sm">
<h2 class="text-center">{$t('quiztivity.memory.editor.add_card')}</h2>
<div class="grid grid-cols-2 py-2">
<div class="px-2 flex flex-col gap-2">
<textarea
type="text"
class="h-auto resize-none bg-transparent outline-none rounded outline-[#B07156] outline"
class="h-auto resize-none bg-transparent outline-hidden rounded-sm outline-[#B07156] outline"
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>
<div class="px-2 flex flex-col gap-2">
<textarea
type="text"
class="h-auto resize-none bg-transparent outline-none rounded outline-[#B07156] outline"
class="h-auto resize-none bg-transparent outline-hidden rounded-sm outline-[#B07156] outline"
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>
@@ -99,7 +103,7 @@ SPDX-License-Identifier: MPL-2.0
</div>
{#each data.cards as card_pair, i (card_pair[0].id)}
<div
class="border-[#B07156] border-2 rounded group flex flex-col"
class="border-[#B07156] border-2 rounded-sm group flex flex-col"
animate:flip={{ duration: 200 }}
>
<div class="grid grid-cols-2 py-2 h-full">
@@ -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) {
@@ -82,8 +86,8 @@ SPDX-License-Identifier: MPL-2.0
<div class="grid lg:grid-cols-6 grid-cols-2 gap-2 m-4">
{#each random_card_order as card}
<button
class="aspect-square flex border-[#B07156] border-2 rounded"
on:click={() => {
class="aspect-square flex border-[#B07156] border-2 rounded-sm"
onclick={() => {
flip_card(card.id);
}}
>
+18 -10
View File
@@ -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,10 +89,10 @@ SPDX-License-Identifier: MPL-2.0
>
</div>
{:else}
<span />
<span></span>
{/if}
<input
class="bg-transparent outline-none text-center mx-auto"
class="bg-transparent outline-hidden text-center mx-auto"
placeholder={$t('quiztivity.editor.title_placeholder')}
bind:value={data.title}
/>
@@ -123,7 +131,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="grid grid-cols-6 gap-6 w-11/12">
{#each data.pages as page, i (page.id)}
<div
class="border-[#B07156] border-2 rounded aspect-square flex flex-col group"
class="border-[#B07156] border-2 rounded-sm aspect-square flex flex-col group"
animate:flip={{ duration: 200 }}
>
<p class="m-auto">{page.type}</p>
@@ -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,17 +123,17 @@ 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 bg-opacity-50 z-50"
on:click={on_parent_click}
transition:fade|local={{ duration: 100 }}
class="fixed w-full h-full top-0 flex bg-black/50 z-50"
onclick={on_parent_click}
transition:fade={{ duration: 100 }}
>
<div
class="m-auto bg-white dark:bg-gray-600 rounded shadow-2xl flex p-4 flex-col w-2/3 h-5/6 gap-2 overflow-scroll"
class="m-auto bg-white dark:bg-gray-600 rounded-sm shadow-2xl flex p-4 flex-col w-2/3 h-5/6 gap-2 overflow-scroll"
>
<div class="flex justify-center flex-col">
<BrownButton
@@ -138,8 +144,8 @@ SPDX-License-Identifier: MPL-2.0
{#if add_shares_open}
<form
class="flex justify-center p-2 border-b-2 border-l-2 border-r-2 border-[#B07156] flex-col gap-2"
transition:fly|local={{ duration: 100, y: -10 }}
on:submit|preventDefault={create_share}
transition:fly={{ duration: 100, y: -10 }}
onsubmit={preventDefault(create_share)}
>
<div class="grid grid-cols-2">
<input
@@ -163,7 +169,7 @@ SPDX-License-Identifier: MPL-2.0
<Spinner />
{:then shares}
{#each shares as share}
<div class="grid grid-cols-4 w-full gap-2" in:fade={{ duration: 50 }}>
<div class="grid grid-cols-4 w-full gap-2" in:fade|global={{ duration: 50 }}>
<!-- <p>{share.name ?? "..."}</p>-->
<div class="w-full mx-auto">
<BrownButton
+2 -2
View File
@@ -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">
+6
View File
@@ -0,0 +1,6 @@
/*
SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0
*/
export const navbarVisible = $state({ visible: true });
-1
View File
@@ -4,7 +4,6 @@
import { writable } from 'svelte/store';
export const navbarVisible = writable(true);
export const signedIn = writable(false);
export const pathname = writable('/');
+8 -3
View File
@@ -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',
@@ -43,14 +47,14 @@ SPDX-License-Identifier: MPL-2.0
};
</script>
<div class="flex flex-col border-[#B07156] rounded border-2 p-2 gap-2">
<div class="flex flex-col border-[#B07156] rounded-sm border-2 p-2 gap-2">
<div class="grid grid-cols-2 gap-2 group mx-auto">
<Hoverable bind:hovering={FeedBackButtonsHovered.like}>
<button
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',
+4 -4
View File
@@ -5,12 +5,12 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { navbarVisible } from '$lib/stores';
import { page } from '$app/stores';
import { navbarVisible } from '$lib/stores.svelte.ts';
import { page } from '$app/state';
import { getLocalization } from '$lib/i18n';
navbarVisible.set(true);
let status = $page.status;
navbarVisible.visible = true;
let status = page.status;
const { t } = getLocalization();
</script>
+39 -11
View File
@@ -4,15 +4,21 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0
-->
<script>
<script lang="ts">
import '../app.css';
import Navbar from '$lib/navbar.svelte';
import { navbarVisible, pathname } from '$lib/stores';
import * as Sentry from '@sentry/browser';
import { BrowserTracing } from '@sentry/tracing';
import { pathname } from '$lib/stores';
import { navbarVisible } from '$lib/stores.svelte';
// import * as Sentry from '@sentry/browser';
// import { BrowserTracing } from '@sentry/tracing';
import { initLocalizationContext } from '$lib/i18n';
import { browser } from '$app/environment';
import CommandPalette from '$lib/components/commandpalette.svelte';
interface Props {
children?: import('svelte').Snippet;
}
let { children }: Props = $props();
// import Alert from '$lib/modals/alert.svelte';
/* afterNavigate(() => {
@@ -49,9 +55,12 @@ SPDX-License-Identifier: MPL-2.0
// // Whenever the user explicitly chooses to respect the OS preference
// localStorage.removeItem('theme');
}
initLocalizationContext();
let start_language = 'en';
if (browser) {
start_language = localStorage.getItem('language') ?? 'en';
}
initLocalizationContext(start_language);
/*
if (import.meta.env.VITE_SENTRY !== undefined && import.meta.env.PROD) {
Sentry.init({
dsn: String(import.meta.env.VITE_SENTRY),
@@ -63,24 +72,43 @@ SPDX-License-Identifier: MPL-2.0
tracesSampleRate: 0.5
});
}
*/
</script>
<svelte:head>
{#if plausible_data_url}
<script defer data-domain={plausible_data_url} src="https://plausible.nexus.mawoka.eu/js/script.file-downloads.outbound-links.pageview-props.tagged-events.js"></script>
<script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }</script>
<script
defer
data-domain={plausible_data_url}
src="https://plausible.nexus.mawoka.eu/js/script.file-downloads.outbound-links.pageview-props.tagged-events.js"
></script>
<script>
window.plausible =
window.plausible ||
function () {
(window.plausible.q = window.plausible.q || []).push(arguments);
};
</script>
{/if}
</svelte:head>
{#if $navbarVisible}
<!-- {#if navbarVisible.visible = true.visible}
<Navbar />
<div class="pt-16 h-screen">
<div class="z-40" />
<div class="z-40"></div>
<slot />
</div>
{:else}
<slot />
{/if} -->
{#if navbarVisible.visible}
<Navbar />
<div class="pt-16">
<div class="z-40"></div>
<!-- extra content above slot -->
</div>
{/if}
<slot />
<CommandPalette />
<!--{#if $alertModal.open ?? false}
+1 -1
View File
@@ -7,7 +7,7 @@ import { redirect } from '@sveltejs/kit';
export const load = async ({ parent }) => {
const { email } = await parent();
if (email) {
throw redirect(302, '/dashboard');
redirect(302, '/dashboard');
}
return {
email
+37 -37
View File
@@ -5,7 +5,7 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { navbarVisible } from '$lib/stores';
import { navbarVisible } from '$lib/stores.svelte.ts';
import { getLocalization } from '$lib/i18n';
import Footer from '$lib/footer.svelte';
import WebPOpenGraph from '$lib/assets/landing/opengraph-home.webp';
@@ -25,7 +25,7 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization();
navbarVisible.set(true);
navbarVisible.visible = true;
/* interface StatsData {
quiz_count: number;
@@ -36,7 +36,7 @@ SPDX-License-Identifier: MPL-2.0
const response = await fetch('/api/v1/stats/combined');
return await response.json();
};*/
let newsletterModalOpen;
let newsletterModalOpen = $state();
onMount(() => {
const ls = localStorage.getItem('newsletter');
newsletterModalOpen = ls === null;
@@ -62,8 +62,8 @@ SPDX-License-Identifier: MPL-2.0
Winners
}
let selected_create_thing = SelectedCreateThing.Create;
let selected_play_thing = SelectedPlayThing.Select;
let selected_create_thing = $state(SelectedCreateThing.Create);
let selected_play_thing = $state(SelectedPlayThing.Select);
/* <li>No;
Tracking < /li>
@@ -123,7 +123,7 @@ SPDX-License-Identifier: MPL-2.0
content: $t('index_page.community_driven_content')
}
];
let selected_classquiz_reason = 0;
let selected_classquiz_reason = $state(0);
</script>
<svelte:head>
@@ -216,12 +216,12 @@ SPDX-License-Identifier: MPL-2.0
<h2 class="text-center text-5xl mb-6">{$t('index_page.how_does_classquiz_work')}</h2>
<div class="flex justify-center w-full">
<h3 class="text-center text-3xl rounded-t-lg bg-opacity-40 bg-white py-2 px-6">
<h3 class="text-center text-3xl rounded-t-lg bg-white/40 py-2 px-6">
{$t('index_page.get_a_quiz')}
</h3>
</div>
<div
class="grid grid-rows-2 lg:grid-rows-1 lg:grid-cols-2 bg-opacity-40 bg-white shadow-lg mb-12 lg:mx-12 mx-4 rounded-lg"
class="grid grid-rows-2 lg:grid-rows-1 lg:grid-cols-2 bg-white/40 shadow-lg mb-12 lg:mx-12 mx-4 rounded-lg"
>
<div>
<div class="p-2 rounded-lg">
@@ -229,21 +229,21 @@ SPDX-License-Identifier: MPL-2.0
<img
class="rounded-lg relative"
src={EditScreenshot}
in:fade
in:fade|global
alt="Screenshot of the import-page showing an URL to Kahoot! entered"
/>
{:else if selected_create_thing === SelectedCreateThing.Find}
<img
class="rounded-lg relative"
src={FindScreenshot}
in:fade
in:fade|global
alt="Screenshot of the search-page showing one found quiz for the term 'Country'"
/>
{:else if selected_create_thing === SelectedCreateThing.Import}
<img
class="rounded-lg relative"
src={ImportScreenshot}
in:fade
in:fade|global
alt="Screenshot of the import-page showing an URL to Kahoot! entered"
/>
{:else}
@@ -255,11 +255,11 @@ SPDX-License-Identifier: MPL-2.0
class="lg:border-l lg:border-l-black lg:border-t-0 border-t border-t-black flex lg:flex-col flex-row stretch"
>
<div
class="m-2 rounded-lg p-2 bg-opacity-40 bg-white transition-all cursor-pointer lg:h-full"
on:click={() => {
class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
onclick={() => {
selected_create_thing = SelectedCreateThing.Create;
}}
on:keyup={() => {
onkeyup={() => {
selected_create_thing = SelectedCreateThing.Create;
}}
class:shadow-2xl={selected_create_thing === SelectedCreateThing.Create}
@@ -288,11 +288,11 @@ SPDX-License-Identifier: MPL-2.0
<p class="dark:text-black">{$t('index_page.create_a_quiz_from_scratch')}</p>
</div>
<div
class="m-2 rounded-lg p-2 bg-opacity-40 bg-white transition-all cursor-pointer lg:h-full"
on:click={() => {
class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
onclick={() => {
selected_create_thing = SelectedCreateThing.Find;
}}
on:keyup={() => {
onkeyup={() => {
selected_create_thing = SelectedCreateThing.Find;
}}
class:shadow-2xl={selected_create_thing === SelectedCreateThing.Find}
@@ -358,13 +358,13 @@ SPDX-License-Identifier: MPL-2.0
<section class="mt-24">
<div class="flex justify-center w-full">
<h2 class="text-center text-3xl rounded-t-lg bg-opacity-40 bg-white py-2 px-6">
<h2 class="text-center text-3xl rounded-t-lg bg-white/40 py-2 px-6">
{$t('index_page.play_quiz')}
</h2>
</div>
<div
class="grid grid-rows-2 lg:grid-rows-1 lg:grid-cols-2 bg-opacity-40 bg-white shadow-lg mb-12 lg:mx-12 mx-4 rounded-lg"
class="grid grid-rows-2 lg:grid-rows-1 lg:grid-cols-2 bg-white/40 shadow-lg mb-12 lg:mx-12 mx-4 rounded-lg"
>
<div>
<div class="p-2 rounded-lg">
@@ -372,21 +372,21 @@ SPDX-License-Identifier: MPL-2.0
<img
class="rounded-lg relative"
src={SelectScreenshot}
in:fade
in:fade|global
alt="Screenshot of the screen where an answer can be selected"
/>
{:else if selected_play_thing === SelectedPlayThing.Results}
<img
class="rounded-lg relative"
src={ResultScreenshot}
in:fade
in:fade|global
alt="Screenshot of the results with a table showing how many players chose which answer"
/>
{:else if selected_play_thing === SelectedPlayThing.Winners}
<img
class="rounded-lg relative"
src={WinnersScreenshot}
in:fade
in:fade|global
alt="Screenshot of the import-page showing an URL to Kahoot! entered"
/>
{:else}
@@ -398,11 +398,11 @@ SPDX-License-Identifier: MPL-2.0
class="lg:border-l lg:border-l-black lg:border-t-0 border-t border-t-black flex lg:flex-col flex-row stretch"
>
<div
class="m-2 rounded-lg p-2 bg-opacity-40 bg-white transition-all cursor-pointer lg:h-full"
on:click={() => {
class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
onclick={() => {
selected_play_thing = SelectedPlayThing.Select;
}}
on:keyup={() => {
onkeyup={() => {
selected_play_thing = SelectedPlayThing.Select;
}}
class:shadow-2xl={selected_play_thing === SelectedPlayThing.Select}
@@ -431,11 +431,11 @@ SPDX-License-Identifier: MPL-2.0
<p class="dark:text-black">{$t('index_page.choose_answer_wisely')}</p>
</div>
<div
class="m-2 rounded-lg p-2 bg-opacity-40 bg-white transition-all cursor-pointer lg:h-full"
on:click={() => {
class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
onclick={() => {
selected_play_thing = SelectedPlayThing.Results;
}}
on:keyup={() => {
onkeyup={() => {
selected_play_thing = SelectedPlayThing.Results;
}}
class:shadow-2xl={selected_play_thing === SelectedPlayThing.Results}
@@ -464,11 +464,11 @@ SPDX-License-Identifier: MPL-2.0
<p class="dark:text-black">{$t('index_page.check_if_chosen_wisely')}</p>
</div>
<div
class="m-2 rounded-lg p-2 bg-opacity-40 bg-white transition-all cursor-pointer lg:h-full"
on:click={() => {
class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
onclick={() => {
selected_play_thing = SelectedPlayThing.Winners;
}}
on:keyup={() => {
onkeyup={() => {
selected_play_thing = SelectedPlayThing.Winners;
}}
class:shadow-2xl={selected_play_thing === SelectedPlayThing.Winners}
@@ -502,13 +502,13 @@ SPDX-License-Identifier: MPL-2.0
<section class="mt-24">
<div class="flex justify-center w-full">
<h2 class="text-center text-3xl rounded-t-lg bg-opacity-40 bg-white py-2 px-6">
<h2 class="text-center text-3xl rounded-t-lg bg-white/40 py-2 px-6">
{$t('index_page.why_classquiz')}
</h2>
</div>
<div
class="grid grid-rows-2 lg:grid-rows-1 lg:grid-cols-2 bg-opacity-40 bg-white shadow-lg mb-12 lg:mx-12 mx-4 rounded-lg"
class="grid grid-rows-2 lg:grid-rows-1 lg:grid-cols-2 bg-white/40 shadow-lg mb-12 lg:mx-12 mx-4 rounded-lg"
>
<div>
<div class="p-12 rounded-lg flex justify-center items-center h-full">
@@ -522,11 +522,11 @@ SPDX-License-Identifier: MPL-2.0
>
{#each classquiz_reasons as reason, index}
<div
class="m-2 rounded-lg p-2 bg-opacity-40 bg-white transition-all cursor-pointer lg:h-full"
on:click={() => {
class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
onclick={() => {
selected_classquiz_reason = index;
}}
on:keyup={() => {
onkeyup={() => {
selected_classquiz_reason = index;
}}
class:shadow-2xl={selected_classquiz_reason === index}
@@ -542,7 +542,7 @@ SPDX-License-Identifier: MPL-2.0
{#if newsletterModalOpen}
<div
class="fixed bottom-8 right-5 bg-white rounded-lg h-fit w-11/12 ml-5 lg:w-2/12 z-50 p-2 bg-white dark:bg-gray-700"
transition:fly
transition:fly|global
>
<Newsletter bind:open={newsletterModalOpen} />
</div>
+8 -3
View File
@@ -5,9 +5,14 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { navbarVisible } from '$lib/stores';
import { navbarVisible} from '$lib/stores.svelte.ts';
interface Props {
children?: import('svelte').Snippet;
}
navbarVisible.set(true);
let { children }: Props = $props();
navbarVisible.visible= true;
</script>
<slot />
{@render children?.()}
@@ -9,7 +9,11 @@ SPDX-License-Identifier: MPL-2.0
import BrownButton from '$lib/components/buttons/brown.svelte';
import { getLocalization } from '$lib/i18n';
const { t } = getLocalization();
export let data: PageData;
interface Props {
data: PageData;
}
let { data }: Props = $props();
const controllers: [] = data.controllers;
</script>
@@ -33,45 +37,51 @@ SPDX-License-Identifier: MPL-2.0
</div>
</div>
<table class="w-full">
<tr class="border-b-2 dark:border-gray-500 text-left border-gray-300">
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('words.name')}</th
>
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('controllers.player_name')}
</th>
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('controllers.first_seen')}
</th>
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('controllers.last_seen')}
</th>
<th class="mx-auto p-1">{$t('words.version')}</th>
</tr>
{#each data.controllers as controller}
<tr class="text-left">
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
><a
href="/account/controllers/{controller.id}"
class="underline text-lg">{controller.name}</a
></td
<thead>
<tr class="border-b-2 dark:border-gray-500 text-left border-gray-300">
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('words.name')}</th
>
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{controller.player_name}</td
>
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{controller.first_seen
? new Date(controller.first_seen).toLocaleString()
: $t('words.never')}</td
>
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{controller.last_seen
? new Date(controller.last_seen).toLocaleString()
: $t('words.never')}</td
>
<td class="mx-auto p-1">{controller.os_version ?? $t('words.unknown')}</td>
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('controllers.player_name')}
</th>
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('controllers.first_seen')}
</th>
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{$t('controllers.last_seen')}
</th>
<th class="mx-auto p-1">{$t('words.version')}</th>
</tr>
{/each}
</thead>
<tbody>
{#each data.controllers as controller}
<tr class="text-left">
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
><a
href="/account/controllers/{controller.id}"
class="underline text-lg">{controller.name}</a
></td
>
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{controller.player_name}</td
>
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{controller.first_seen
? new Date(controller.first_seen).toLocaleString()
: $t('words.never')}</td
>
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
>{controller.last_seen
? new Date(controller.last_seen).toLocaleString()
: $t('words.never')}</td
>
<td class="mx-auto p-1"
>{controller.os_version ?? $t('words.unknown')}</td
>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
@@ -15,12 +15,16 @@ SPDX-License-Identifier: MPL-2.0
import { getLocalization } from '$lib/i18n';
const { t } = getLocalization();
export let data: PageData;
const controller = data.controller;
interface Props {
data: PageData;
}
let newest_version: string | undefined = undefined;
let { data }: Props = $props();
const controller = $state(data.controller);
let saved_status = SaveStatus.Unchanged;
let newest_version: string | undefined = $state(undefined);
let saved_status = $state(SaveStatus.Unchanged);
let save_player_timer;
const save_player_name_debounce = () => {
@@ -93,9 +97,9 @@ SPDX-License-Identifier: MPL-2.0
<h2 class="text-2xl mx-auto">{$t('controllers.controller_name')}</h2>
<div class="mx-auto flex">
<input
class="rounded p-1 transition-all outline-none text-center dark:bg-gray-700"
class="rounded-sm p-1 transition-all outline-hidden text-center dark:bg-gray-700"
bind:value={controller.name}
on:keyup={save_player_name_debounce}
onkeyup={save_player_name_debounce}
/>
<SaveIndicator status={saved_status} />
</div>
@@ -104,9 +108,9 @@ SPDX-License-Identifier: MPL-2.0
<h2 class="mx-auto text-2xl">{$t('controllers.player_name')}</h2>
<div class="mx-auto flex">
<input
class="rounded p-1 transition-all outline-none text-center dark:bg-gray-700"
class="rounded-sm p-1 transition-all outline-hidden text-center dark:bg-gray-700"
bind:value={controller.player_name}
on:keyup={save_player_name_debounce}
onkeyup={save_player_name_debounce}
/>
<SaveIndicator status={saved_status} />
</div>
@@ -8,7 +8,7 @@ import { error } from '@sveltejs/kit';
export const load = (async ({ fetch, params }) => {
const res = await fetch(`/api/v1/box-controller/web/controller?id=${params.controller_id}`);
if (res.status !== 200) {
throw error(res.status, await res.text());
error(res.status, await res.text());
}
const json: {
id: string;
@@ -6,7 +6,11 @@ SPDX-License-Identifier: MPL-2.0
<script lang="ts">
import { SaveStatus } from './commons';
export let status: SaveStatus;
interface Props {
status: SaveStatus;
}
let { status }: Props = $props();
</script>
<div class="h-6 w-6 grow-0 my-auto">
@@ -5,19 +5,27 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import { goto } from '$app/navigation';
import type { PageData } from './$types';
export let data: PageData;
let input_data = {
interface Props {
data: PageData;
}
let { data }: Props = $props();
let input_data = $state({
player_name: data.username,
name: ''
};
});
let isValid = false;
let isValid = $state(false);
let isSubmitting = false;
$: isValid = input_data.name.length !== 0 && input_data.player_name.length !== 0;
run(() => {
isValid = input_data.name.length !== 0 && input_data.player_name.length !== 0;
});
const submit = async () => {
if (!isValid) {
@@ -51,7 +59,7 @@ SPDX-License-Identifier: MPL-2.0
Add a controller
</h3>
<form on:submit|preventDefault={submit}>
<form onsubmit={preventDefault(submit)}>
<div class="w-full mt-4">
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
<div class="relative bg-inherit w-full">
@@ -59,7 +67,7 @@ SPDX-License-Identifier: MPL-2.0
id="player_name"
name="player_name"
type="text"
class="w-full peer bg-transparent h-10 rounded-lg text-gray-700 dark:text-white placeholder-transparent ring-2 px-2 ring-gray-500 focus:ring-sky-600 focus:outline-none focus:border-rose-600"
class="w-full peer bg-transparent h-10 rounded-lg text-gray-700 dark:text-white placeholder-transparent ring-2 px-2 ring-gray-500 focus:ring-sky-600 focus:outline-hidden focus:border-rose-600"
class:ring-red-700={input_data.player_name.length === 0}
class:ring-green-600={input_data.player_name.length !== 0}
bind:value={input_data.player_name}
@@ -78,7 +86,7 @@ SPDX-License-Identifier: MPL-2.0
id="name"
name="name"
type="text"
class="w-full peer bg-transparent h-10 rounded-lg text-gray-700 dark:text-white placeholder-transparent ring-2 px-2 ring-gray-500 focus:ring-sky-600 focus:outline-none focus:border-rose-600"
class="w-full peer bg-transparent h-10 rounded-lg text-gray-700 dark:text-white placeholder-transparent ring-2 px-2 ring-gray-500 focus:ring-sky-600 focus:outline-hidden focus:border-rose-600"
placeholder="Name"
class:ring-red-700={input_data.name.length === 0}
class:ring-green-600={input_data.name.length !== 0}
@@ -95,7 +103,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex items-center justify-center mt-4">
<button
class="px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded hover:bg-gray-600 focus:outline-none"
class="px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded-sm hover:bg-gray-600 focus:outline-hidden"
disabled={!isValid || isSubmitting}
class:cursor-not-allowed={!isValid || isSubmitting}
class:opacity-50={!isValid || isSubmitting}
@@ -9,7 +9,7 @@ export const load = (({ url }) => {
const code = url.searchParams.get('code');
const id = url.searchParams.get('id');
if (!id || !code) {
throw error(404, JSON.stringify({ detail: 'id and/or code are/is missing' }));
error(404, JSON.stringify({ detail: 'id and/or code are/is missing' }));
}
return {
id,
@@ -10,9 +10,13 @@ SPDX-License-Identifier: MPL-2.0
import Spinner from '$lib/Spinner.svelte';
import { onMount } from 'svelte';
export let data: PageData;
let controller_seen = false;
let check_tick = 0;
interface Props {
data: PageData;
}
let { data }: Props = $props();
let controller_seen = $state(false);
let check_tick = $state(0);
let interval;
const check_if_controller_was_seen = async () => {
@@ -11,7 +11,7 @@ export async function load({ parent, url }) {
const { email } = await parent();
if (email) {
throw redirect(302, returnTo);
redirect(302, returnTo);
}
return {
verified: verified !== null
+24 -17
View File
@@ -5,7 +5,10 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { alertModal, navbarVisible } from '$lib/stores';
import { run } from 'svelte/legacy';
import { alertModal } from '$lib/stores';
import { navbarVisible } from '$lib/stores.svelte';
import { slide } from 'svelte/transition';
import Footer from '$lib/footer.svelte';
import VerifiedBadge from './verified_badge.svelte';
@@ -17,23 +20,27 @@ SPDX-License-Identifier: MPL-2.0
import TotpComponent from './totp_component.svelte';
import { browserSupportsWebAuthn } from '@simplewebauthn/browser';
navbarVisible.set(true);
navbarVisible.visible = true;
export let data;
let { data } = $props();
const { verified }: boolean = data;
let session_data = {};
let step = 0;
let selected_method = null;
let done = false;
let session_data = $state({});
let step = $state(0);
let selected_method = $state(null);
let done = $state(false);
const redirect_back = (done_var: boolean) => {
if (done_var) {
window.location.reload();
setTimeout(() => {
window.location.reload();
}, 100);
}
};
let alertModalOpen = false;
$: redirect_back(done);
run(() => {
redirect_back(done);
});
alertModal.subscribe((data) => {
if (!alertModalOpen && data.open) {
@@ -72,10 +79,10 @@ SPDX-License-Identifier: MPL-2.0
}
}
};
$: {
run(() => {
check_auto();
step;
}
});
</script>
<svelte:head>
@@ -91,32 +98,32 @@ SPDX-License-Identifier: MPL-2.0
>
{#if step === 0}
<!-- <p>StartWindow</p>-->
<div transition:slide>
<div transition:slide|global>
<StartWindow bind:session_data bind:step />
</div>
{:else if selected_method === null}
<!-- <p>SelectWindow</p>-->
<div transition:slide>
<div transition:slide|global>
<SelectMethod bind:session_data bind:step bind:selected_method />
</div>
{:else if selected_method === 'PASSWORD'}
<!-- <p>PasswordWindow</p>-->
<div transition:slide>
<div transition:slide|global>
<PasswordComponent bind:session_data bind:done bind:step bind:selected_method />
</div>
{:else if selected_method === 'PASSKEY'}
<!-- <p>WebauthnWindow</p>-->
<div transition:slide>
<div transition:slide|global>
<WebauthnComponent bind:session_data bind:done bind:step bind:selected_method />
</div>
{:else if selected_method === 'BACKUP'}
<!-- <p>BackupWindow</p>-->
<div transition:slide>
<div transition:slide|global>
<BackupComponent bind:session_data bind:done bind:step bind:selected_method />
</div>
{:else if selected_method === 'TOTP'}
<!-- <p>TotpWindow</p>-->
<div transition:slide>
<div transition:slide|global>
<TotpComponent bind:session_data bind:done bind:step bind:selected_method />
</div>
{/if}
@@ -5,19 +5,25 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n';
const { t } = getLocalization();
export let session_data;
export let selected_method;
export let done;
export let step;
let {
session_data,
selected_method = $bindable(),
done = $bindable(),
step = $bindable()
} = $props();
let backup_code = '';
let isSubmitting = false;
let backup_code = $state('');
let isSubmitting = $state(false);
let backup_code_valid = false;
$: backup_code_valid = backup_code.length === 64;
let backup_code_valid = $state(false);
run(() => {
backup_code_valid = backup_code.length === 64;
});
const continue_in_login = async () => {
if (!backup_code_valid) {
@@ -32,6 +38,7 @@ SPDX-License-Identifier: MPL-2.0
body: JSON.stringify({ auth_type: 'BACKUP', data: backup_code })
});
if (res.status === 200) {
window.location.reload();
done = true;
} else {
step += 1;
@@ -43,7 +50,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="px-6 py-4">
<h2 class="text-3xl font-bold text-center text-gray-700 dark:text-white">ClassQuiz</h2>
<form on:submit|preventDefault={continue_in_login}>
<form onsubmit={preventDefault(continue_in_login)}>
<div class="w-full mt-4">
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
<div class="relative bg-inherit w-full">
@@ -52,7 +59,7 @@ SPDX-License-Identifier: MPL-2.0
bind:value={backup_code}
name="backup_code"
type="text"
class="w-full peer bg-transparent h-10 rounded-lg text-gray-700 dark:text-white placeholder-transparent ring-2 px-2 ring-gray-500 focus:ring-sky-600 focus:outline-none focus:border-rose-600"
class="w-full peer bg-transparent h-10 rounded-lg text-gray-700 dark:text-white placeholder-transparent ring-2 px-2 ring-gray-500 focus:ring-sky-600 focus:outline-hidden focus:border-rose-600"
placeholder={$t('words.backup_code')}
/>
<label
@@ -65,14 +72,14 @@ SPDX-License-Identifier: MPL-2.0
</div>
<div class="flex items-center justify-between mt-4">
<button
on:click={() => {
onclick={() => {
selected_method = 'BACKUP';
}}
class="text-sm text-gray-600 dark:text-gray-200 hover:text-gray-500"
>{$t('login_page.use_backup_code')}</button
>
<button
class="px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded hover:bg-gray-600 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
class="px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded-sm hover:bg-gray-600 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50"
disabled={!backup_code_valid}
type="submit"
>
@@ -5,16 +5,20 @@ SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n';
export let session_data;
export let selected_method;
export let done;
export let step;
let {
session_data,
selected_method = $bindable(),
done = $bindable(),
step = $bindable()
} = $props();
const { t } = getLocalization();
let isSubmitting;
let password;
let password = $state();
const continue_in_login = async () => {
if (!password) {
@@ -28,6 +32,7 @@ SPDX-License-Identifier: MPL-2.0
body: JSON.stringify({ auth_type: 'PASSWORD', data: password })
});
if (res.status === 200) {
window.location.reload();
done = true;
} else if (res.status === 202) {
step += 1;
@@ -55,7 +60,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="px-6 py-4">
<h2 class="text-3xl font-bold text-center text-gray-700 dark:text-white">ClassQuiz</h2>
<form on:submit|preventDefault={continue_in_login}>
<form onsubmit={preventDefault(continue_in_login)}>
<div class="w-full mt-4">
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
<div class="relative bg-inherit w-full">
@@ -64,7 +69,7 @@ SPDX-License-Identifier: MPL-2.0
bind:value={password}
name="password"
type="password"
class="w-full peer bg-transparent h-10 rounded-lg text-gray-700 dark:text-white placeholder-transparent ring-2 px-2 ring-gray-500 focus:ring-sky-600 focus:outline-none focus:border-rose-600"
class="w-full peer bg-transparent h-10 rounded-lg text-gray-700 dark:text-white placeholder-transparent ring-2 px-2 ring-gray-500 focus:ring-sky-600 focus:outline-hidden focus:border-rose-600"
placeholder={$t('words.password')}
autocomplete="current-password"
/>
@@ -78,14 +83,14 @@ SPDX-License-Identifier: MPL-2.0
</div>
<div class="flex items-center justify-between mt-4">
<button
on:click={() => {
onclick={() => {
selected_method = 'BACKUP';
}}
class="text-sm text-gray-600 dark:text-gray-200 hover:text-gray-500"
>{$t('login_page.use_backup_code')}</button
>
<button
class="px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded hover:bg-gray-600 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
class="px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded-sm hover:bg-gray-600 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50"
disabled={!password}
type="submit"
>

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