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
+3 -3
View File
@@ -79,9 +79,9 @@ def verify_webauthn(data, fidocredentialss: list[FidoCredentials], login_session
print("user_cred not in DB") print("user_cred not in DB")
raise HTTPException(401) raise HTTPException(401)
credential.id = base64.urlsafe_b64encode(credential.raw_id).decode("utf-8").replace("=", "") credential.id = base64.urlsafe_b64encode(credential.raw_id).decode("utf-8").replace("=", "")
credential.response.client_data_json = base64.b64decode(credential.response.client_data_json + b"==") credential.response.client_data_json = credential.response.client_data_json
credential.response.authenticator_data = base64.urlsafe_b64decode(credential.response.authenticator_data + b"==") credential.response.authenticator_data = credential.response.authenticator_data
credential.response.signature = base64.urlsafe_b64decode(credential.response.signature + b"==") credential.response.signature = credential.response.signature
try: try:
verify_authentication_response( verify_authentication_response(
credential=credential, credential=credential,
+2 -2
View File
@@ -64,8 +64,8 @@ async def confirm_add_key_data(credential: RegistrationCredential, user: User =
raise HTTPException(401) raise HTTPException(401)
current_registration_challenge = base64.b64decode(redis_res) current_registration_challenge = base64.b64decode(redis_res)
credential.id = base64.urlsafe_b64encode(credential.raw_id).decode("utf-8").replace("=", "") credential.id = base64.urlsafe_b64encode(credential.raw_id).decode("utf-8").replace("=", "")
credential.response.client_data_json = base64.b64decode(credential.response.client_data_json + b"==") credential.response.client_data_json = credential.response.client_data_json
credential.response.attestation_object = base64.urlsafe_b64decode(credential.response.attestation_object + b"==") credential.response.attestation_object = credential.response.attestation_object
verification = verify_registration_response( verification = verify_registration_response(
credential=credential, credential=credential,
expected_challenge=current_registration_challenge, expected_challenge=current_registration_challenge,
+1 -1
View File
@@ -59,7 +59,7 @@ class IpResponse(BaseModel):
@router.get("/ip-lookup/{ip}", response_model=IpResponse) @router.get("/ip-lookup/{ip}", response_model=IpResponse)
async def get_ip_data(ip: str, _: User = Depends(get_current_user)): async def get_ip_data(ip: str, _: User = Depends(get_current_user)):
async with ClientSession() as session, session.get(f"http://ip-api.com/json/{ip}") as response: async with ClientSession() as session, session.get(f"http://ip-api.com/json/{ip}") as response:
data = await response.model_dump_json() data = await response.json()
try: try:
return IpResponse(**data) return IpResponse(**data)
except ValidationError: except ValidationError:
-1
View File
@@ -9,7 +9,6 @@ FROM node:19-bullseye as builder
ENV API_URL=https://mawoka.eu ENV API_URL=https://mawoka.eu
#Ah, I've noticed that!!! #Ah, I've noticed that!!!
ENV REDIS_URL=redis://localhost:6379 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 # This Mapbox-token is restricted to the following urls: classquiz.de, classquiz.mawoka.eu, test.com
ENV VITE_HCAPTCHA=ee81b2a1-acf3-4d20-b2a4-a7ea94c7eba5 ENV VITE_HCAPTCHA=ee81b2a1-acf3-4d20-b2a4-a7ea94c7eba5
# Hacaptcha secret ES_e0f8139cb5ad467d892e8d73a020cd2b # Hacaptcha secret ES_e0f8139cb5ad467d892e8d73a020cd2b
+74 -83
View File
@@ -15,95 +15,86 @@
"translations-scan": "i18next-scanner --config i18next-scanner.config.engine.cjs src/**/*.svelte" "translations-scan": "i18next-scanner --config i18next-scanner.config.engine.cjs src/**/*.svelte"
}, },
"devDependencies": { "devDependencies": {
"@beyonk/svelte-mapbox": "^9.0.5", "@felte/reporter-tippy": "^1.1.10",
"@ckeditor/ckeditor5-autoformat": "^41.1.0", "@felte/validator-yup": "^1.1.4",
"@ckeditor/ckeditor5-basic-styles": "^41.1.0", "@ffmpeg/core": "^0.12.10",
"@ckeditor/ckeditor5-build-balloon": "^41.1.0", "@ffmpeg/ffmpeg": "^0.12.15",
"@ckeditor/ckeditor5-editor-balloon": "^41.1.0", "@ffmpeg/util": "^0.12.2",
"@ckeditor/ckeditor5-essentials": "^41.1.0", "@fontsource/marck-script": "^5.2.8",
"@ckeditor/ckeditor5-theme-lark": "^41.1.0", "@sentry/browser": "^10.12.0",
"@felte/reporter-tippy": "^1.1.5", "@sentry/tracing": "^7.120.4",
"@felte/validator-yup": "^1.0.11", "@simplewebauthn/browser": "^13.2.0",
"@ffmpeg/core": "^0.11.0", "@sveltejs/adapter-auto": "^6.1.0",
"@ffmpeg/ffmpeg": "^0.11.6", "@sveltejs/adapter-node": "^5.3.2",
"@fontsource/marck-script": "^4.5.11", "@sveltejs/kit": "^2.5.27",
"@sentry/browser": "^7.48.0", "@sveltejs/vite-plugin-svelte": "^6.2.0",
"@sentry/tracing": "^7.48.0", "@tailwindcss/postcss": "^4.1.13",
"@simplewebauthn/browser": "^7.2.0", "@tailwindcss/typography": "^0.5.18",
"@sveltejs/adapter-auto": "^2.0.0", "@types/canvas-confetti": "^1.9.0",
"@sveltejs/adapter-node": "^1.2.3", "@types/js-cookie": "^3.0.6",
"@sveltejs/kit": "^1.15.7", "@types/luxon": "^3.7.1",
"@tailwindcss/typography": "^0.5.9", "@types/qrcode": "^1.5.5",
"@types/canvas-confetti": "^1.6.0", "@types/sortablejs": "^1.15.8",
"@types/cookie": "^0.5.1", "@types/ua-parser-js": "^0.7.39",
"@types/dompurify": "^3.0.2", "@typescript-eslint/eslint-plugin": "^8.44.0",
"@types/js-cookie": "^3.0.3", "@typescript-eslint/parser": "^8.44.0",
"@types/luxon": "^3.3.0", "@uppy/compressor": "^2.3.2",
"@types/marked": "^4.3.0", "@uppy/core": "^4.5.3",
"@types/qrcode": "^1.5.0", "@uppy/dashboard": "^4.4.3",
"@types/sortablejs": "^1.15.1", "@uppy/drag-drop": "^4.2.2",
"@types/ua-parser-js": "^0.7.36", "@uppy/drop-target": "^3.2.2",
"@typescript-eslint/eslint-plugin": "^5.59.0", "@uppy/image-editor": "^3.4.2",
"@typescript-eslint/parser": "^5.59.0", "@uppy/progress-bar": "^4.3.2",
"@unlazy/svelte": "^0.8.9", "@uppy/status-bar": "^4.2.3",
"@uppy/compressor": "^1.0.2", "@uppy/svelte": "^4.6.2",
"@uppy/core": "^3.2.0", "@uppy/xhr-upload": "^4.4.2",
"@uppy/dashboard": "^3.4.0", "autoprefixer": "^10.4.21",
"@uppy/drag-drop": "^3.0.2", "canvas-confetti": "^1.9.3",
"@uppy/drop-target": "^2.0.1", "ckeditor5": "^46.1.1",
"@uppy/image-editor": "^2.1.2", "cookie": "^1.0.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",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"cssnano": "^6.0.0", "cssnano": "^7.1.1",
"dompurify": "^3.0.3", "dompurify": "^3.2.7",
"eslint": "^8.38.0", "eslint": "^9.36.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte3": "^4.0.0", "felte": "^1.3.0",
"felte": "^1.2.7", "fuse.js": "^7.1.0",
"fuse.js": "^6.6.2", "highlight.js": "^11.11.1",
"highlight.js": "^11.7.0", "i18next": "^25.5.2",
"i18next": "^22.4.15", "i18next-browser-languagedetector": "^8.2.0",
"i18next-browser-languagedetector": "^7.0.1", "js-cookie": "^3.0.5",
"js-cookie": "^3.0.1",
"jws": "^4.0.0", "jws": "^4.0.0",
"luxon": "^3.3.0", "luxon": "^3.7.2",
"mapbox-gl": "^2.14.1", "marked": "^16.3.0",
"marked": "^5.0.0", "mdsvex": "^0.12.6",
"mdsvex": "^0.10.6", "minisearch": "^7.2.0",
"minisearch": "^6.0.1", "pikaso": "^2.9.0",
"pikaso": "^2.7.6", "plausible-tracker": "^0.3.9",
"plausible-tracker": "^0.3.8", "postcss": "^8.5.6",
"postcss": "^8.4.31", "postcss-import": "^16.1.1",
"postcss-import": "^15.1.0", "postcss-load-config": "^6.0.1",
"postcss-load-config": "^4.0.1", "prettier": "^3.6.2",
"prettier": "^2.8.8", "prettier-plugin-svelte": "^3.4.0",
"prettier-plugin-svelte": "^2.10.1", "qrcode": "^1.5.4",
"qrcode": "^1.5.1", "sass": "^1.93.0",
"sass": "^1.62.0", "socket.io-client": "^4.8.1",
"socket.io-client": "^4.6.1", "svelte": "^5.0.0",
"svelte": "^3.58.0", "svelte-check": "^4.3.1",
"svelte-check": "^3.2.0", "svelte-preprocess": "^6.0.3",
"svelte-preprocess": "^5.0.3", "svelte-range-slider-pips": "^4.0.7",
"svelte-range-slider-pips": "^2.1.1",
"svelte-tippy": "^1.3.2", "svelte-tippy": "^1.3.2",
"swiper": "^8.4.7", "swiper": "^12.0.2",
"tailwindcss": "^3.3.1", "tailwindcss": "^4.1.13",
"thumbhash": "^0.1.1", "thumbhash": "^0.1.1",
"tinykeys": "^2.1.0", "tinykeys": "^3.0.0",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"tslib": "^2.5.0", "tslib": "^2.8.1",
"typescript": "~5.0.4", "typescript": "^5.5.0",
"ua-parser-js": "^1.0.35", "ua-parser-js": "^2.0.5",
"vite": "^4.2.3", "vite": "^7.1.6",
"vite-plugin-cross-origin-isolation": "^0.1.6", "vite-plugin-cross-origin-isolation": "^0.1.6",
"vite-plugin-iso-import": "^1.0.0", "vite-plugin-iso-import": "^1.3.0",
"yup": "^1.1.1" "yup": "^1.7.0"
}, },
"type": "module" "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 SPDX-License-Identifier: MPL-2.0
*/ */
const tailwindcss = require('tailwindcss'); const tailwindcss = require('@tailwindcss/postcss');
const autoprefixer = require('autoprefixer'); const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano'); const cssnano = require('cssnano');
const postcss_import = require('postcss-import'); 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/animations/perspective-subtle.css';
@import 'tippy.js/dist/tippy.css'; @import 'tippy.js/dist/tippy.css';
/* Write your global styles here, in PostCSS syntax */ /* Write your global styles here, in PostCSS syntax */
@tailwind base; @import 'tailwindcss';
@tailwind components; @plugin "@tailwindcss/typography";
@tailwind utilities; @tailwind utilities;
.marck-script { .marck-script {
@@ -22,17 +22,24 @@ SPDX-License-Identifier: MPL-2.0
.link-hover:hover { .link-hover:hover {
color: #4e6e58; color: #4e6e58;
} }
@layer base {
@layer utilities { button:not(:disabled),
.normal-background { [role='button']:not(:disabled) {
@apply bg-gradient-to-r from-[#009444] via-[#39b54a] to-[#8dc63f] dark:bg-[#0f2702] dark:from-[#0f2702] dark:via-[#0f2702] dark:to[#0f2702]; cursor: pointer;
}
.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;
} }
} }
@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"> <script lang="ts">
export let my_20 = true; interface Props {
my_20?: boolean;
}
let { my_20 = true }: Props = $props();
</script> </script>
<svg class="h-8 w-8 animate-spin mx-auto" class:my-20={my_20} viewBox="3 3 18 18"> <svg class="h-8 w-8 animate-spin mx-auto" class:my-20={my_20} viewBox="3 3 18 18">
+29 -20
View File
@@ -15,25 +15,37 @@ SPDX-License-Identifier: MPL-2.0
import Controls from '$lib/play/admin/controls.svelte'; import Controls from '$lib/play/admin/controls.svelte';
import Question from '$lib/play/admin/question.svelte'; import Question from '$lib/play/admin/question.svelte';
export let game_token: string;
export let quiz_data: QuizData;
export let game_mode: string;
export let bg_color: string;
const { t } = getLocalization(); const { t } = getLocalization();
const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58']; const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58'];
let question_results = null; let question_results = $state(null);
export let final_results: Array<null> | Array<Array<PlayerAnswer>> = [null]; let selected_question = $state(-1);
let selected_question = -1; let timer_res: string = $state();
let timer_res: string; let shown_question_now: number = $state();
let shown_question_now: number; let final_results_clicked = $state(false);
let final_results_clicked = false;
let timer_interval; let timer_interval;
let answer_count = 0; let answer_count = $state(0);
export let control_visible: boolean;
export let player_scores; interface Props {
game_token: string;
quiz_data: QuizData;
game_mode: string;
bg_color: string;
final_results?: Array<null> | Array<Array<PlayerAnswer>>;
control_visible: boolean;
player_scores: any;
}
let {
game_token,
quiz_data = $bindable(),
game_mode,
bg_color,
final_results = $bindable([null]),
control_visible,
player_scores = $bindable()
}: Props = $props();
socket.on('get_question_results', () => { socket.on('get_question_results', () => {
console.log('get_question_results'); console.log('get_question_results');
@@ -110,7 +122,7 @@ SPDX-License-Identifier: MPL-2.0
class:mt-10={control_visible} class:mt-10={control_visible}
style="width: {(100 / parseInt(quiz_data.questions[selected_question].time)) * style="width: {(100 / parseInt(quiz_data.questions[selected_question].time)) *
parseInt(timer_res)}vw" parseInt(timer_res)}vw"
/> ></span>
{/if} {/if}
<div class="w-full h-full" class:pt-28={control_visible} class:pt-12={!control_visible}> <div class="w-full h-full" class:pt-28={control_visible} class:pt-12={!control_visible}>
@@ -120,8 +132,7 @@ SPDX-License-Identifier: MPL-2.0
{#await import('$lib/play/admin/slide.svelte')} {#await import('$lib/play/admin/slide.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<svelte:component <c.default
this={c.default}
bind:question={quiz_data.questions[selected_question]} bind:question={quiz_data.questions[selected_question]}
/> />
{/await} {/await}
@@ -141,8 +152,7 @@ SPDX-License-Identifier: MPL-2.0
{#await import('$lib/play/admin/voting_results.svelte')} {#await import('$lib/play/admin/voting_results.svelte')}
<Spinner /> <Spinner />
{:then c} {:then c}
<svelte:component <c.default
this={c.default}
bind:data={question_results} bind:data={question_results}
bind:question={quiz_data.questions[selected_question]} bind:question={quiz_data.questions[selected_question]}
/> />
@@ -151,8 +161,7 @@ SPDX-License-Identifier: MPL-2.0
{#await import('$lib/play/admin/results.svelte')} {#await import('$lib/play/admin/results.svelte')}
<Spinner /> <Spinner />
{:then c} {:then c}
<svelte:component <c.default
this={c.default}
bind:data={player_scores} bind:data={player_scores}
question={quiz_data.questions[selected_question]} question={quiz_data.questions[selected_question]}
bind:new_data={question_results} bind:new_data={question_results}
+10 -5
View File
@@ -4,17 +4,22 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0 SPDX-License-Identifier: MPL-2.0
--> -->
<script> <script lang="ts">
export let headerText;
export let expanded = false; interface Props {
headerText: any;
expanded?: boolean;
children?: import('svelte').Snippet;
}
let { headerText, expanded = $bindable(false), children }: Props = $props();
</script> </script>
<div> <div>
<h3 class="bg-transparent m-0"> <h3 class="bg-transparent m-0">
<button <button
aria-expanded={expanded} aria-expanded={expanded}
on:click={() => (expanded = !expanded)} onclick={() => (expanded = !expanded)}
class="bg-white dark:bg-gray-700 flex justify-between w-full border-none m-0 p-2 rounded-t-lg hover:bg-gray-200 dark:hover:bg-gray-500 transition" class="bg-white dark:bg-gray-700 flex justify-between w-full border-none m-0 p-2 rounded-t-lg hover:bg-gray-200 dark:hover:bg-gray-500 transition"
class:rounded-b-lg={!expanded} class:rounded-b-lg={!expanded}
><span>{@html headerText}</span> ><span>{@html headerText}</span>
@@ -31,7 +36,7 @@ SPDX-License-Identifier: MPL-2.0
</h3> </h3>
<div class:hidden={!expanded}> <div class:hidden={!expanded}>
<slot /> {@render children?.()}
</div> </div>
</div> </div>
@@ -10,7 +10,11 @@ SPDX-License-Identifier: MPL-2.0
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let quiz_id: string | null = null; interface Props {
quiz_id?: string | null;
}
let { quiz_id = $bindable(null) }: Props = $props();
const handle_on_click = () => { const handle_on_click = () => {
quiz_id = null; quiz_id = null;
@@ -26,11 +30,11 @@ SPDX-License-Identifier: MPL-2.0
{#if quiz_id} {#if quiz_id}
<div <div
class="w-screen h-screen fixed top-0 left-0 bg-opacity-50 bg-black z-20 flex justify-center" class="w-screen h-screen fixed top-0 left-0 bg-black/50 z-20 flex justify-center"
on:click={handle_on_click} onclick={handle_on_click}
transition:fade|local={{ duration: 100 }} 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> <h1 class="text-3xl text-center mb-4">{$t('downloader.select_download_type')}</h1>
<div class="flex flex-row gap-4"> <div class="flex flex-row gap-4">
<div class="w-full flex justify-center"> <div class="w-full flex justify-center">
@@ -5,14 +5,29 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let disabled = false; import { createBubbler } from 'svelte/legacy';
export let flex = false;
export let href: undefined | string = undefined; const bubble = createBubbler();
export let target: undefined | string = '_self';
export let type: undefined | string = 'button';
interface Props {
disabled?: boolean;
flex?: boolean;
href?: undefined | string;
target?: undefined | string;
type?: undefined | string;
children?: import('svelte').Snippet;
}
let {
disabled = false,
flex = false,
href = undefined,
target = '_self',
type = 'button',
children
}: Props = $props();
</script> </script>
{#if href} {#if href}
@@ -23,22 +38,22 @@ SPDX-License-Identifier: MPL-2.0
class:opacity-50={disabled} class:opacity-50={disabled}
class:cursor-not-allowed={disabled} class:cursor-not-allowed={disabled}
class:pointer-events-none={disabled} class:pointer-events-none={disabled}
class="text-black hover:bg-opacity-80 w-full px-4 py-2 leading-5 transition-all duration-200 transform bg-[#B07156] rounded text-center outline-none" class="text-black hover:bg-bg-[#B07156]/80 w-full px-4 py-2 leading-5 transition-all duration-200 transform bg-[#B07156] rounded-sm text-center outline-hidden hover:cursor-pointer"
on:click onclick={bubble('click')}
class:flex class:flex
class:justify-center={flex} class:justify-center={flex}
> >
<slot /> {@render children?.()}
</a> </a>
{:else} {:else}
<button <button
{disabled} {disabled}
{type} {type}
class="text-black hover: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" class="text-black hover:cursor-pointer hover:opacity-80 w-full px-4 py-2 leading-5 transition-all duration-200 transform bg-[#B07156] rounded-sm text-center focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 outline-hidden"
on:click onclick={bubble('click')}
class:flex class:flex
class:justify-center={flex} class:justify-center={flex}
> >
<slot /> {@render children?.()}
</button> </button>
{/if} {/if}
+24 -10
View File
@@ -5,33 +5,47 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let disabled = false; import { createBubbler } from 'svelte/legacy';
export let flex = false;
export let href: undefined | string = undefined; const bubble = createBubbler();
export let target: undefined | string = '_self';
interface Props {
disabled?: boolean;
flex?: boolean;
href?: undefined | string;
target?: undefined | string;
children?: import('svelte').Snippet;
}
let {
disabled = false,
flex = false,
href = undefined,
target = '_self',
children
}: Props = $props();
</script> </script>
{#if href} {#if href}
<a <a
{href} {href}
{target} {target}
class="w-full px-4 py-2 leading-5 text-black dark:text-white transition-colors duration-200 transform bg-gray-50 dark:bg-gray-700 rounded text-center hover:bg-gray-300 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-gray-600" class="w-full px-4 py-2 leading-5 text-black dark:text-white transition-colors duration-200 transform bg-gray-50 dark:bg-gray-700 rounded-sm text-center hover:bg-gray-300 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-gray-600"
on:click onclick={bubble('click')}
class:flex class:flex
class:block={!flex} class:block={!flex}
class:justify-center={flex} class:justify-center={flex}
> >
<slot /> {@render children?.()}
</a> </a>
{:else} {:else}
<button <button
{disabled} {disabled}
class="w-full px-4 py-2 leading-5 text-black dark:text-white transition-colors duration-200 transform bg-gray-50 dark:bg-gray-700 rounded text-center hover:bg-gray-300 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-gray-600" class="w-full px-4 py-2 leading-5 text-black dark:text-white transition-colors duration-200 transform bg-gray-50 dark:bg-gray-700 rounded-sm text-center hover:bg-gray-300 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-gray-600"
on:click onclick={bubble('click')}
class:flex class:flex
class:justify-center={flex} class:justify-center={flex}
> >
<slot /> {@render children?.()}
</button> </button>
{/if} {/if}
@@ -4,22 +4,23 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0 SPDX-License-Identifier: MPL-2.0
--> -->
<!-- <!--
This should be okay, right? This should be okay, right?
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { tinykeys } from '$lib/tinykeys'; import { tinykeys } from '$lib/tinykeys';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import MiniSearch from 'minisearch'; import MiniSearch from 'minisearch';
let open = false; let open = $state(false);
let input = ''; let input = $state('');
let bg_text = ''; let bg_text = $state('');
let title_ms: MiniSearch; let title_ms: MiniSearch;
let command_ms: MiniSearch; let command_ms: MiniSearch;
let selected: null | number = null; let selected: null | number = $state(null);
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
type ActionFunction = (args: string[]) => void; type ActionFunction = (args: string[]) => void;
@@ -98,7 +99,7 @@ This should be okay, right?
action: () => window.location.assign('/account/settings') action: () => window.location.assign('/account/settings')
} }
]; ];
let visible_items = actions; let visible_items = $state(actions);
const toggle_open = (e: KeyboardEvent | undefined) => { const toggle_open = (e: KeyboardEvent | undefined) => {
e.preventDefault(); e.preventDefault();
@@ -205,9 +206,13 @@ This should be okay, right?
input = ''; input = '';
}; };
$: search(input); run(() => {
search(input);
});
// $: input = lower_input(input) // $: input = lower_input(input)
$: input = input.toLowerCase(); run(() => {
input = input.toLowerCase();
});
onMount(async () => { onMount(async () => {
tinykeys(window, { tinykeys(window, {
'$mod+k': toggle_open, '$mod+k': toggle_open,
@@ -232,39 +237,37 @@ This should be okay, right?
{#if open} {#if open}
<div <div
class="fixed top-0 left-0 w-screen h-screen flex bg-black bg-opacity-50 z-50" class="fixed top-0 left-0 w-screen h-screen flex bg-black/50 z-50"
on:click={close_on_outside} onclick={close_on_outside}
on:keyup={close_on_outside} onkeyup={close_on_outside}
transition:fade={{ duration: 60 }} 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"> <div class="grid grid-cols-1 grid-rows-1 border-b border-b-white">
<p <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} {bg_text}
</p> </p>
<input <input
type="text" 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} bind:value={input}
/> />
</div> </div>
<div class="flex flex-col p-2 gap-2 overflow-scroll"> <div class="flex flex-col p-2 gap-2 overflow-scroll">
{#each visible_items as vi, i} {#each visible_items as vi, i}
<div <div
transition:fade|local={{ duration: 60 }} transition:fade={{ duration: 60 }}
class="p-2 transition rounded" class="p-2 transition rounded-sm"
class:bg-[#B07156]={selected === i} class:bg-[#B07156]={selected === i}
class:bg-gray-700={selected !== i} class:bg-gray-700={selected !== i}
on:mouseenter={() => (selected = i)} onmouseenter={() => (selected = i)}
on:mousedown={execute_action} onmousedown={execute_action}
> >
<div class="flex"> <div class="flex">
<h3 class="text-lg my-auto">{vi.title}</h3> <h3 class="text-lg my-auto">{vi.title}</h3>
<p <p class="font-mono my-auto ml-auto h-fit bg-black/50 rounded-sm p-0.5">
class="font-mono my-auto ml-auto h-fit bg-black bg-opacity-50 rounded p-0.5"
>
/{vi.command} /{vi.command}
{#if vi.args} {#if vi.args}
{#each vi.args as arg} {#each vi.args as arg}
@@ -11,7 +11,11 @@ SPDX-License-Identifier: MPL-2.0
y: 'yellow', y: 'yellow',
b: 'blue' b: 'blue'
}; };
export let code: string; interface Props {
code: string;
}
let { code }: Props = $props();
</script> </script>
<div class="flex flex-row gap-2 mx-auto"> <div class="flex flex-row gap-2 mx-auto">
@@ -22,7 +26,7 @@ SPDX-License-Identifier: MPL-2.0
style="background-color: {color_map[ style="background-color: {color_map[
c.toLowerCase() c.toLowerCase()
]}; width: 2rem; height: {c.toLowerCase() == c ? '2' : '4'}rem" ]}; width: 2rem; height: {c.toLowerCase() == c ? '2' : '4'}rem"
/> ></span>
</div> </div>
{/each} {/each}
</div> </div>
@@ -13,7 +13,7 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
let open = false; let open = $state(false);
onMount(() => { onMount(() => {
if (Cookies.get('commandpalette_notice')) { if (Cookies.get('commandpalette_notice')) {
return; return;
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
@@ -14,18 +16,26 @@ SPDX-License-Identifier: MPL-2.0
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let open = false; interface Props {
export let type: PopoverTypes; open?: boolean;
export let data: undefined | { game_pin: number | string; game_id: string } | string = type: PopoverTypes;
undefined; data?: undefined | { game_pin: number | string; game_id: string } | string;
}
$: dispatch('open', open); let { open = $bindable(false), type, data = undefined }: Props = $props();
run(() => {
dispatch('open', open);
});
</script> </script>
{#if open} {#if open}
<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 <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" role="alert"
> >
<div class="ml-3 text-sm font-normal"> <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" class="ml-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700"
data-dismiss-target="#toast-default" data-dismiss-target="#toast-default"
aria-label="Close" aria-label="Close"
on:click={() => { onclick={() => {
open = false; open = false;
}} }}
> >
@@ -1,413 +0,0 @@
<!--
SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0
-->
<script lang="ts">
import { Swiper, SwiperSlide } from 'swiper/svelte';
import 'swiper/css';
import 'swiper/css/pagination';
import 'swiper/css/navigation';
import { Pagination, EffectCoverflow, Keyboard, Mousewheel, Navigation } from 'swiper';
import { fly } from 'svelte/transition';
import { QuizQuestionType } from '$lib/quiz_types.js';
import { getLocalization } from '$lib/i18n';
import StartGamePopup from './start_game.svelte';
// import { onMount } from 'svelte';
import viewport from './useViewportAction.js';
import Spinner from '$lib/Spinner.svelte';
import GrayButton from '$lib/components/buttons/gray.svelte';
let copy_toast_open = false;
let start_game = null;
const { t } = getLocalization();
export let quizzes = [];
const deleteQuiz = async (to_delete: string) => {
if (!confirm('Do you really want to delete this quiz?')) {
return;
}
await fetch(`/api/v1/quiz/delete/${to_delete}`, {
method: 'DELETE'
});
window.location.reload();
};
let visibleImages = Array.from(Array(quizzes.length), () => []);
const copy_id = (quiz_id: string) => {
navigator.clipboard.writeText(quiz_id);
copy_toast_open = true;
setTimeout(() => {
copy_toast_open = false;
}, 2000);
};
let game_in_lobby: { game_pin: string; game_id: string; quiz_title: string } | undefined =
undefined;
const get_game_in_lobby_fn = async () => {
const res = await fetch('/api/v1/remote/game_waiting');
console.log('Fetched.');
if (res.ok) {
game_in_lobby = await res.json();
}
};
// onMount(() => {
// document.body.addEventListener('keydown', close_start_game_if_esc_is_pressed);
// });
get_game_in_lobby_fn();
</script>
{#if copy_toast_open || game_in_lobby}
<div class="fixed w-screen top-10 z-50 flex justify-center" transition:fly={{ 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'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let quiz_id; let { quiz_id = $bindable() } = $props();
let captcha_selected = false; let captcha_selected = $state(false);
let selected_game_mode = 'kahoot'; let selected_game_mode = $state('kahoot');
let loading = false; let loading = $state(false);
let custom_field = ''; let custom_field = $state('');
let cqcs_enabled = false; let cqcs_enabled = $state(false);
let randomized_answers = false; let randomized_answers = $state(false);
const tippy = createTippy({ const tippy = createTippy({
arrow: true, arrow: true,
@@ -94,9 +94,9 @@ SPDX-License-Identifier: MPL-2.0
</script> </script>
<div <div
class="fixed top-0 left-0 flex justify-center w-screen h-screen bg-black bg-opacity-60 z-50 text-black" class="fixed top-0 left-0 flex justify-center w-screen h-screen bg-black/60 z-50 text-black"
transition:fade={{ duration: 100 }} transition:fade|global={{ duration: 100 }}
on:click={on_parent_click} onclick={on_parent_click}
> >
<div <div
class="w-5/6 h-5/6 bg-black m-auto rounded-lg shadow-lg p-4 flex flex-col" class="w-5/6 h-5/6 bg-black m-auto rounded-lg shadow-lg p-4 flex flex-col"
@@ -116,15 +116,15 @@ SPDX-License-Identifier: MPL-2.0
class="sr-only peer" class="sr-only peer"
/> />
<span <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" <span class="ml-3 text-sm font-medium text-gray-900"
>Captcha {captcha_selected ? 'enabled' : 'disabled'}</span >Captcha {captcha_selected ? 'enabled' : 'disabled'}</span
> >
</label> </label>
</div> </div>
{#if captcha_selected} {#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"> <p class="w-1/3">
{$t('start_game.captcha_message')} {$t('start_game.captcha_message')}
</p> </p>
@@ -136,7 +136,7 @@ SPDX-License-Identifier: MPL-2.0
<div <div
class="rounded-lg bg-white shadow-lg cursor-pointer transition-all p-2" class="rounded-lg bg-white shadow-lg cursor-pointer transition-all p-2"
class:opacity-50={selected_game_mode !== 'kahoot'} class:opacity-50={selected_game_mode !== 'kahoot'}
on:click={() => { onclick={() => {
selected_game_mode = 'kahoot'; selected_game_mode = 'kahoot';
}} }}
> >
@@ -148,7 +148,7 @@ SPDX-License-Identifier: MPL-2.0
<div <div
class="rounded-lg bg-white shadow-lg cursor-pointer transition-all p-2" class="rounded-lg bg-white shadow-lg cursor-pointer transition-all p-2"
class:opacity-50={selected_game_mode !== 'normal'} class:opacity-50={selected_game_mode !== 'normal'}
on:click={() => { onclick={() => {
selected_game_mode = 'normal'; selected_game_mode = 'normal';
}} }}
> >
@@ -162,7 +162,7 @@ SPDX-License-Identifier: MPL-2.0
<label class="mr-4">{$t('result_page.custom_field')}</label> <label class="mr-4">{$t('result_page.custom_field')}</label>
<input <input
bind:value={custom_field} 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" placeholder="Phone Number or Email"
/> />
</div> </div>
@@ -175,8 +175,8 @@ SPDX-License-Identifier: MPL-2.0
class="sr-only peer" class="sr-only peer"
/> />
<span <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" <span class="ml-3 text-sm font-medium text-gray-900"
><a ><a
href="/controller" href="/controller"
@@ -203,15 +203,15 @@ SPDX-License-Identifier: MPL-2.0
class="sr-only peer" class="sr-only peer"
/> />
<span <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> <span class="ml-3 text-sm font-medium text-gray-900"> Randomize answers</span>
</label> </label>
</div> </div>
<button <button
class="mt-auto mx-auto bg-green-500 p-4 rounded-lg shadow-lg hover:bg-green-400 transition-all marck-script text-2xl" class="mt-auto mx-auto bg-green-500 p-4 rounded-lg shadow-lg hover:bg-green-400 transition-all marck-script text-2xl"
on:click={() => { onclick={() => {
start_game(quiz_id); start_game(quiz_id);
}} }}
> >
+24 -12
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
// import { mint } from '$lib/hashcash'; // import { mint } from '$lib/hashcash';
import { dataSchema } from '$lib/yupSchemas'; import { dataSchema } from '$lib/yupSchemas';
import type { EditorData, Question } from './quiz_types'; import type { EditorData, Question } from './quiz_types';
@@ -16,13 +18,17 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
let schemaInvalid = false; let schemaInvalid = $state(false);
let yupErrorMessage = ''; let yupErrorMessage = $state('');
export let data: EditorData; interface Props {
export let quiz_id: string | null; data: EditorData;
let selected_question = -1; quiz_id: string | null;
let imgur_links_valid = false; }
let { data = $bindable(), quiz_id }: Props = $props();
let selected_question = $state(-1);
let imgur_links_valid = $state(false);
const validateInput = async (data: EditorData) => { const validateInput = async (data: EditorData) => {
// console.log("input", data) // console.log("input", data)
@@ -36,7 +42,9 @@ SPDX-License-Identifier: MPL-2.0
yupErrorMessage = err.errors ? err.errors[0] : ''; yupErrorMessage = err.errors ? err.errors[0] : '';
} }
}; };
$: validateInput(data); run(() => {
validateInput(data);
});
const checkIfAllQuestionImagesComplyWithRegex = (questions: Array<Question>) => { const checkIfAllQuestionImagesComplyWithRegex = (questions: Array<Question>) => {
let NoteverythingValid = false; let NoteverythingValid = false;
@@ -54,11 +62,15 @@ SPDX-License-Identifier: MPL-2.0
return NoteverythingValid; return NoteverythingValid;
}; };
$: imgur_links_valid = checkIfAllQuestionImagesComplyWithRegex(data.questions); run(() => {
let edit_id; imgur_links_valid = checkIfAllQuestionImagesComplyWithRegex(data.questions);
});
let edit_id = $state();
let confirm_to_leave = true; let confirm_to_leave = true;
$: console.log('data', data); run(() => {
console.log('data', data);
});
const getEditID = async () => { const getEditID = async () => {
let res; let res;
@@ -110,11 +122,11 @@ SPDX-License-Identifier: MPL-2.0
}; };
</script> </script>
<svelte:window on:beforeunload={confirmUnload} /> <svelte:window onbeforeunload={confirmUnload} />
{#await getEditID()} {#await getEditID()}
<Spinner /> <Spinner />
{:then _} {:then _}
<form on:submit|preventDefault={saveQuiz}> <form onsubmit={preventDefault(saveQuiz)}>
<div class="grid grid-cols-6 h-screen w-screen"> <div class="grid grid-cols-6 h-screen w-screen">
<div> <div>
<Sidebar bind:data bind:selected_question /> <Sidebar bind:data bind:selected_question />
+22 -14
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import type { Answer, EditorData } from '../quiz_types'; import type { Answer, EditorData } from '../quiz_types';
import { QuizQuestionType } from '../quiz_types'; import { QuizQuestionType } from '../quiz_types';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
@@ -17,9 +19,13 @@ SPDX-License-Identifier: MPL-2.0
const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58']; const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58'];
export let selected_question: number; interface Props {
export let check_choice = false; selected_question: number;
export let data: EditorData; check_choice?: boolean;
data: EditorData;
}
let { selected_question, check_choice = false, data = $bindable() }: Props = $props();
if (!Array.isArray(data.questions[selected_question].answers)) { if (!Array.isArray(data.questions[selected_question].answers)) {
data.questions[selected_question].answers = []; data.questions[selected_question].answers = [];
} }
@@ -41,7 +47,9 @@ SPDX-License-Identifier: MPL-2.0
right: false right: false
}; };
}; };
$: save_colors(data); run(() => {
save_colors(data);
});
data.questions[selected_question].type = data.questions[selected_question].type =
check_choice === true ? QuizQuestionType.CHECK : QuizQuestionType.ABCD; check_choice === true ? QuizQuestionType.CHECK : QuizQuestionType.ABCD;
const set_colors_if_unset = () => { const set_colors_if_unset = () => {
@@ -51,18 +59,18 @@ SPDX-License-Identifier: MPL-2.0
} }
} }
}; };
$: { run(() => {
set_colors_if_unset(); set_colors_if_unset();
data; data;
selected_question; selected_question;
} });
</script> </script>
<div class="grid grid-rows-2 grid-flow-col auto-cols-auto gap-4 w-full px-10"> <div class="grid grid-rows-2 grid-flow-col auto-cols-auto gap-4 w-full px-10">
{#if Array.isArray(data.questions[selected_question].answers)} {#if Array.isArray(data.questions[selected_question].answers)}
{#each data.questions[selected_question].answers as answer, index} {#each data.questions[selected_question].answers as answer, index}
<div <div
out:fade|local={{ duration: 150 }} out:fade={{ duration: 150 }}
class="p-4 rounded-lg flex justify-center w-full transition relative" class="p-4 rounded-lg flex justify-center w-full transition relative"
class:bg-red-500={!answer.right} class:bg-red-500={!answer.right}
class:bg-green-500={answer.right} class:bg-green-500={answer.right}
@@ -73,7 +81,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition" class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition"
type="button" type="button"
on:click={() => { onclick={() => {
data.questions[selected_question].answers.splice(index, 1); data.questions[selected_question].answers.splice(index, 1);
data.questions[selected_question].answers = data.questions[selected_question].answers =
data.questions[selected_question].answers; data.questions[selected_question].answers;
@@ -97,7 +105,7 @@ SPDX-License-Identifier: MPL-2.0
<input <input
bind:value={answer.answer} bind:value={answer.answer}
type="text" 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( style="background-color: {answer.color}; color: {get_foreground_color(
answer.color answer.color
)}" )}"
@@ -105,7 +113,7 @@ SPDX-License-Identifier: MPL-2.0
/> />
<button <button
type="button" type="button"
on:click={() => { onclick={() => {
answer.right = !answer.right; answer.right = !answer.right;
}} }}
> >
@@ -145,9 +153,9 @@ SPDX-License-Identifier: MPL-2.0
class="rounded-lg p-1 border-black border" class="rounded-lg p-1 border-black border"
type="color" type="color"
bind:value={answer.color} bind:value={answer.color}
on:contextmenu|preventDefault={() => { oncontextmenu={preventDefault(() => {
answer.color = default_colors[index]; answer.color = default_colors[index];
}} })}
/> />
</div> </div>
{/each} {/each}
@@ -156,8 +164,8 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="p-4 rounded-lg bg-transparent border-gray-500 border-2 hover:bg-gray-300 transition dark:hover:bg-gray-600" class="p-4 rounded-lg bg-transparent border-gray-500 border-2 hover:bg-gray-300 transition dark:hover:bg-gray-600"
type="button" type="button"
in:fade|local={{ duration: 150 }} in:fade={{ duration: 150 }}
on:click={() => { onclick={() => {
data.questions[selected_question].answers = [ data.questions[selected_question].answers = [
...data.questions[selected_question].answers, ...data.questions[selected_question].answers,
{ ...get_empty_answer(data.questions[selected_question].answers.length) } { ...get_empty_answer(data.questions[selected_question].answers.length) }
@@ -11,10 +11,14 @@ SPDX-License-Identifier: MPL-2.0
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
export let questions: Question[];
export let open: boolean;
export let selected_question: number; interface Props {
questions: Question[];
open: boolean;
selected_question: number;
}
let { questions = $bindable(), open = $bindable(), selected_question = $bindable() }: Props = $props();
const { t } = getLocalization(); const { t } = getLocalization();
onMount(() => { onMount(() => {
@@ -95,18 +99,20 @@ SPDX-License-Identifier: MPL-2.0
</script> </script>
<div <div
class="fixed top-0 left-0 w-screen h-screen flex bg-black z-50 bg-opacity-50" class="fixed top-0 left-0 w-screen h-screen flex bg-black/50 z-50"
on:click={on_parent_click} onclick={on_parent_click}
transition:fade|local={{ duration: 100 }} 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> <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"> <div class="grid grid-cols-4 gap-4 overflow-y-scroll">
{#each question_types as qt, i} {#each question_types as qt, i}
<div class="rounded p-6 border-[#B07156] border"> <div class="rounded-sm p-6 border-[#B07156] border">
<button <button
class="text-xl text-black dark:text-white" class="text-xl text-black dark:text-white"
on:click={() => { onclick={() => {
add_question(i); add_question(i);
}}>{qt.name}</button }}>{qt.name}</button
> >
+30 -18
View File
@@ -5,19 +5,31 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { thumbHashToDataURL } from 'thumbhash'; import { thumbHashToDataURL } from 'thumbhash';
export let src: string; interface Props {
export let css_classes = 'max-h-64 h-auto w-auto'; src: string;
export let added_thumbhash_classes = 'h-full'; css_classes?: string;
export let muted = true; added_thumbhash_classes?: string;
export let allow_fullscreen = true; muted?: boolean;
let type: 'img' | 'video' | undefined = undefined; allow_fullscreen?: boolean;
}
let img_data; let {
let thumbhash_data: string; src,
css_classes = 'max-h-64 h-auto w-auto',
added_thumbhash_classes = 'h-full',
muted = true,
allow_fullscreen = true
}: Props = $props();
let type: 'img' | 'video' | undefined = $state(undefined);
let img_data = $state();
let thumbhash_data: string = $state();
function base64ToBytes(base64: string): Uint8Array { function base64ToBytes(base64: string): Uint8Array {
const binString = atob(base64); const binString = atob(base64);
@@ -46,13 +58,13 @@ SPDX-License-Identifier: MPL-2.0
const update_url = () => { const update_url = () => {
media = get_media(); media = get_media();
}; };
let media = get_media(); let media = $state(get_media());
$: { run(() => {
src; src;
update_url(); update_url();
} });
let fullscreen_open = false; let fullscreen_open = $state(false);
const open_fullscreen = () => { const open_fullscreen = () => {
if (!allow_fullscreen) { if (!allow_fullscreen) {
@@ -67,11 +79,11 @@ SPDX-License-Identifier: MPL-2.0
{:then data} {:then data}
{#if type === 'img'} {#if type === 'img'}
<img <img
in:fade={{ duration: 300 }} in:fade|global={{ duration: 300 }}
src={img_data.data} src={img_data.data}
alt={img_data.alt_text ?? 'Not available'} alt={img_data.alt_text ?? 'Not available'}
class={css_classes} class={css_classes}
on:click={() => open_fullscreen()} onclick={() => open_fullscreen()}
/> />
{:else if type === 'video'} {:else if type === 'video'}
<video <video
@@ -93,14 +105,14 @@ SPDX-License-Identifier: MPL-2.0
{#if fullscreen_open} {#if fullscreen_open}
<div <div
class="fixed top-0 left-0 z-50 w-screen h-screen bg-black bg-opacity-50 fle p-2" class="fixed top-0 left-0 z-50 w-screen h-screen bg-black/50 fle p-2"
transition:fade={{ duration: 80 }} transition:fade|global={{ duration: 80 }}
on:click={() => (fullscreen_open = false)} onclick={() => (fullscreen_open = false)}
> >
<img <img
src={img_data.data} src={img_data.data}
alt={img_data.alt_text ?? 'Not available'} 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> </div>
{/if} {/if}
+20 -14
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import type { EditorData, OrderQuizAnswer } from '$lib/quiz_types'; import type { EditorData, OrderQuizAnswer } from '$lib/quiz_types';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
@@ -13,10 +15,14 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
export let selected_question: number; interface Props {
export let data: EditorData; selected_question: number;
data: EditorData;
}
let parent_el: HTMLDivElement; let { selected_question, data = $bindable() }: Props = $props();
let parent_el: HTMLDivElement = $state();
const swapArrayElements = (arr: OrderQuizAnswer[], a: number, b: number): Array<any> => { const swapArrayElements = (arr: OrderQuizAnswer[], a: number, b: number): Array<any> => {
let _arr = [...arr]; let _arr = [...arr];
@@ -60,11 +66,11 @@ SPDX-License-Identifier: MPL-2.0
} }
} }
}; };
$: { run(() => {
set_colors_if_unset(); set_colors_if_unset();
data; data;
selected_question; selected_question;
} });
</script> </script>
<div class="w-full"> <div class="w-full">
@@ -72,13 +78,13 @@ SPDX-License-Identifier: MPL-2.0
{#each data.questions[selected_question].answers as answer, i (answer.id)} {#each data.questions[selected_question].answers as answer, i (answer.id)}
<div <div
animate:flip={{ duration: 200 }} 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" class="p-4 rounded-lg flex justify-center w-full transition relative border border-gray-600 flex-row gap-4 m-2"
> >
<button <button
class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition" class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition"
type="button" type="button"
on:click={() => { onclick={() => {
data.questions[selected_question].answers.splice(i, 1); data.questions[selected_question].answers.splice(i, 1);
data.questions[selected_question].answers = data.questions[selected_question].answers =
data.questions[selected_question].answers; data.questions[selected_question].answers;
@@ -101,7 +107,7 @@ SPDX-License-Identifier: MPL-2.0
</button> </button>
<div> <div>
<button <button
on:click={() => { onclick={() => {
move_item(true, i); move_item(true, i);
}} }}
class="disabled:opacity-50 transition" class="disabled:opacity-50 transition"
@@ -126,7 +132,7 @@ SPDX-License-Identifier: MPL-2.0
</svg> </svg>
</button> </button>
<button <button
on:click={() => { onclick={() => {
move_item(false, i); move_item(false, i);
}} }}
class="disabled:opacity-50 transition" class="disabled:opacity-50 transition"
@@ -154,7 +160,7 @@ SPDX-License-Identifier: MPL-2.0
<input <input
bind:value={answer.answer} bind:value={answer.answer}
type="text" 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( style="background-color: {answer.color}; color: {get_foreground_color(
answer.color answer.color
)}" )}"
@@ -164,9 +170,9 @@ SPDX-License-Identifier: MPL-2.0
class="rounded-lg p-1 border-black border" class="rounded-lg p-1 border-black border"
type="color" type="color"
bind:value={answer.color} bind:value={answer.color}
on:contextmenu|preventDefault={() => { oncontextmenu={preventDefault(() => {
answer.color = null; answer.color = null;
}} })}
/> />
</div> </div>
{/each} {/each}
@@ -177,8 +183,8 @@ SPDX-License-Identifier: MPL-2.0
<button <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" class="p-4 rounded-lg bg-transparent border-gray-500 border-2 hover:bg-gray-300 transition dark:hover:bg-gray-600 m-2 w-full"
type="button" type="button"
in:fade|local={{ duration: 150 }} in:fade={{ duration: 150 }}
on:click={() => { onclick={() => {
data.questions[selected_question].answers = [ data.questions[selected_question].answers = [
...data.questions[selected_question].answers, ...data.questions[selected_question].answers,
{ {
@@ -5,11 +5,17 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import type { EditorData } from '../quiz_types'; import type { EditorData } from '../quiz_types';
import Spinner from '../Spinner.svelte'; import Spinner from '../Spinner.svelte';
export let selected_question: number; interface Props {
export let data: EditorData; selected_question: number;
data: EditorData;
}
let { selected_question, data = $bindable() }: Props = $props();
let question = data.questions[selected_question]; let question = data.questions[selected_question];
if (question.answers.max === undefined || question.answers.min_correct === undefined) { if (question.answers.max === undefined || question.answers.min_correct === undefined) {
@@ -35,17 +41,25 @@ SPDX-License-Identifier: MPL-2.0
*/ */
let answer = question.answers; let answer = question.answers;
let range_arr = [answer.min_correct, answer.max_correct]; let range_arr = $state([answer.min_correct, answer.max_correct]);
$: data.questions[selected_question].answers.min_correct = range_arr[0]; run(() => {
$: data.questions[selected_question].answers.max_correct = range_arr[1]; data.questions[selected_question].answers.min_correct = range_arr[0];
$: data.questions[selected_question].answers.min = });
data.questions[selected_question].answers.min === null run(() => {
? 0 data.questions[selected_question].answers.max_correct = range_arr[1];
: data.questions[selected_question].answers.min; });
$: data.questions[selected_question].answers.max = run(() => {
data.questions[selected_question].answers.max === null data.questions[selected_question].answers.min =
? 0 data.questions[selected_question].answers.min === null
: data.questions[selected_question].answers.max; ? 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) { function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
@@ -78,8 +92,7 @@ SPDX-License-Identifier: MPL-2.0
{#await sleep(100)} {#await sleep(100)}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then _} {:then _}
<svelte:component <c.default
this={c.default}
bind:values={range_arr} bind:values={range_arr}
bind:min={data.questions[selected_question].answers.min} bind:min={data.questions[selected_question].answers.min}
bind:max={data.questions[selected_question].answers.max} bind:max={data.questions[selected_question].answers.max}
+12 -8
View File
@@ -12,8 +12,12 @@ SPDX-License-Identifier: MPL-2.0
import { TextQuestionSchema } from '$lib/yupSchemas'; import { TextQuestionSchema } from '$lib/yupSchemas';
import { createTippy } from 'svelte-tippy'; import { createTippy } from 'svelte-tippy';
export let selected_question: number; interface Props {
export let data: EditorData; selected_question: number;
data: EditorData;
}
let { selected_question, data = $bindable() }: Props = $props();
const { t } = getLocalization(); const { t } = getLocalization();
@@ -45,7 +49,7 @@ SPDX-License-Identifier: MPL-2.0
{#if Array.isArray(data.questions[selected_question].answers)} {#if Array.isArray(data.questions[selected_question].answers)}
{#each data.questions[selected_question].answers as answer, index} {#each data.questions[selected_question].answers as answer, index}
<div <div
out:fade|local={{ duration: 150 }} out:fade={{ duration: 150 }}
class="p-4 rounded-lg flex justify-center w-full transition relative" class="p-4 rounded-lg flex justify-center w-full transition relative"
class:dark:bg-gray-500={answer.answer} class:dark:bg-gray-500={answer.answer}
class:bg-gray-300={answer.answer} class:bg-gray-300={answer.answer}
@@ -56,7 +60,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition" class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition"
type="button" type="button"
on:click={() => { onclick={() => {
data.questions[selected_question].answers.splice(index, 1); data.questions[selected_question].answers.splice(index, 1);
data.questions[selected_question].answers = data.questions[selected_question].answers =
data.questions[selected_question].answers; data.questions[selected_question].answers;
@@ -80,12 +84,12 @@ SPDX-License-Identifier: MPL-2.0
<input <input
bind:value={answer.answer} bind:value={answer.answer}
type="text" 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')} placeholder={$t('editor.enter_answer')}
/> />
<button <button
type="button" type="button"
on:click={() => { onclick={() => {
answer.case_sensitive = !answer.case_sensitive; answer.case_sensitive = !answer.case_sensitive;
}} }}
use:tippy={{ content: 'Case sensitive?', placement: 'top' }} use:tippy={{ content: 'Case sensitive?', placement: 'top' }}
@@ -129,8 +133,8 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="p-4 rounded-lg bg-transparent border-gray-500 border-2 hover:bg-gray-300 transition dark:hover:bg-gray-600" class="p-4 rounded-lg bg-transparent border-gray-500 border-2 hover:bg-gray-300 transition dark:hover:bg-gray-600"
type="button" type="button"
in:fade|local={{ duration: 150 }} in:fade={{ duration: 150 }}
on:click={() => { onclick={() => {
data.questions[selected_question].answers = [ data.questions[selected_question].answers = [
...data.questions[selected_question].answers, ...data.questions[selected_question].answers,
{ ...get_empty_answer() } { ...get_empty_answer() }
+16 -10
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import type { EditorData } from '../quiz_types'; import type { EditorData } from '../quiz_types';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { reach } from 'yup'; import { reach } from 'yup';
@@ -15,8 +17,12 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58']; const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58'];
export let selected_question: number; interface Props {
export let data: EditorData; selected_question: number;
data: EditorData;
}
let { selected_question, data = $bindable() }: Props = $props();
if (!Array.isArray(data.questions[selected_question].answers)) { if (!Array.isArray(data.questions[selected_question].answers)) {
data.questions[selected_question].answers = []; data.questions[selected_question].answers = [];
@@ -35,11 +41,11 @@ SPDX-License-Identifier: MPL-2.0
} }
} }
}; };
$: { run(() => {
set_colors_if_unset(); set_colors_if_unset();
data; data;
selected_question; selected_question;
} });
/*console.log(data.questions[selected_question].answers, 'moIn!', data.questions[selected_question].answers.length); /*console.log(data.questions[selected_question].answers, 'moIn!', data.questions[selected_question].answers.length);
onMount(() => { onMount(() => {
for (let i = 0; i < data.questions[selected_question].answers; i++) { for (let i = 0; i < data.questions[selected_question].answers; i++) {
@@ -53,7 +59,7 @@ SPDX-License-Identifier: MPL-2.0
{#if Array.isArray(data.questions[selected_question].answers)} {#if Array.isArray(data.questions[selected_question].answers)}
{#each data.questions[selected_question].answers as answer, index} {#each data.questions[selected_question].answers as answer, index}
<div <div
out:fade|local={{ duration: 150 }} out:fade={{ duration: 150 }}
class="p-4 rounded-lg flex justify-center w-full transition relative" class="p-4 rounded-lg flex justify-center w-full transition relative"
class:bg-yellow-500={!reach(VotingQuestionSchema, 'answer').isValidSync( class:bg-yellow-500={!reach(VotingQuestionSchema, 'answer').isValidSync(
answer.answer answer.answer
@@ -64,7 +70,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition" class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition"
type="button" type="button"
on:click={() => { onclick={() => {
data.questions[selected_question].answers.splice(index, 1); data.questions[selected_question].answers.splice(index, 1);
data.questions[selected_question].answers = data.questions[selected_question].answers =
data.questions[selected_question].answers; data.questions[selected_question].answers;
@@ -97,9 +103,9 @@ SPDX-License-Identifier: MPL-2.0
class="rounded-lg p-1 border-black border" class="rounded-lg p-1 border-black border"
type="color" type="color"
bind:value={answer.color} bind:value={answer.color}
on:contextmenu|preventDefault={() => { oncontextmenu={preventDefault(() => {
answer.color = default_colors[index]; answer.color = default_colors[index];
}} })}
/> />
</div> </div>
{/each} {/each}
@@ -108,8 +114,8 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="p-4 rounded-lg bg-transparent border-gray-500 border-2 hover:bg-gray-300 transition dark:hover:bg-gray-600" class="p-4 rounded-lg bg-transparent border-gray-500 border-2 hover:bg-gray-300 transition dark:hover:bg-gray-600"
type="button" type="button"
in:fade|local={{ duration: 150 }} in:fade={{ duration: 150 }}
on:click={() => { onclick={() => {
data.questions[selected_question].answers = [ data.questions[selected_question].answers = [
...data.questions[selected_question].answers, ...data.questions[selected_question].answers,
{ {
+46 -35
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import type { EditorData } from '$lib/quiz_types'; import type { EditorData } from '$lib/quiz_types';
import { QuizQuestionType } from '$lib/quiz_types'; import { QuizQuestionType } from '$lib/quiz_types';
import RangeEditor from '$lib/editor/RangeSelectorEditorPart.svelte'; import RangeEditor from '$lib/editor/RangeSelectorEditorPart.svelte';
@@ -26,14 +28,18 @@ SPDX-License-Identifier: MPL-2.0
placement: 'top' placement: 'top'
}); });
export let data: EditorData; interface Props {
export let selected_question: number; data: EditorData;
export let edit_id: string; selected_question: number;
edit_id: string;
}
let advanced_options_open = false; let { data = $bindable(), selected_question = $bindable(), edit_id = $bindable() }: Props = $props();
let uppyOpen = false; let advanced_options_open = $state(false);
let unique = {};
let uppyOpen = $state(false);
let unique = $state({});
/*eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }]*/ /*eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }]*/
const correctTimeInput = (_) => { const correctTimeInput = (_) => {
@@ -51,21 +57,23 @@ SPDX-License-Identifier: MPL-2.0
const set_unique = () => { const set_unique = () => {
unique = {}; unique = {};
}; };
$: correctTimeInput(data.questions[selected_question].time); run(() => {
$: { correctTimeInput(data.questions[selected_question].time);
});
run(() => {
selected_question; selected_question;
set_unique(); set_unique();
} });
let image_url = ''; let image_url = $state('');
const update_image_url = () => { const update_image_url = () => {
image_url = data.questions[selected_question].image; image_url = data.questions[selected_question].image;
}; };
$: { run(() => {
update_image_url(); update_image_url();
selected_question; selected_question;
data.questions; data.questions;
} });
const type_to_name = { const type_to_name = {
RANGE: $t('words.range'), RANGE: $t('words.range'),
@@ -90,18 +98,18 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex align-middle p-4 gap-3"> <div class="flex align-middle p-4 gap-3">
<span <span
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-red-400 transition" class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-red-400 transition"
/> ></span>
<span <span
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-yellow-400 transition" class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-yellow-400 transition"
/> ></span>
<span <span
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-green-400 transition" class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-green-400 transition"
/> ></span>
<button <button
class="ml-auto" class="ml-auto"
type="button" type="button"
use:tippy={{ content: $t('editor.advanced_settings') }} use:tippy={{ content: $t('editor.advanced_settings') }}
on:click={() => (advanced_options_open = true)} onclick={() => (advanced_options_open = true)}
> >
<svg <svg
class="text-white w-5 h-5" class="text-white w-5 h-5"
@@ -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" 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-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
></path> />
<path <path
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
></path> />
</svg> </svg>
</button> </button>
</div> </div>
@@ -130,7 +138,7 @@ SPDX-License-Identifier: MPL-2.0
{#await import('./slide.svelte')} {#await import('./slide.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<svelte:component this={c.default} bind:data={data.questions[selected_question]} /> <c.default bind:data={data.questions[selected_question]} />
{/await} {/await}
{:else} {:else}
{@const type = data.questions[selected_question].type} {@const type = data.questions[selected_question].type}
@@ -147,8 +155,7 @@ SPDX-License-Identifier: MPL-2.0
'questions[].question' 'questions[].question'
).isValidSync(data.questions[selected_question].question)} ).isValidSync(data.questions[selected_question].question)}
> >
<svelte:component <c.default
this={c.default}
bind:text={data.questions[selected_question].question} bind:text={data.questions[selected_question].question}
/> />
</div> </div>
@@ -161,7 +168,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition" class="rounded-full absolute -top-2 -right-2 opacity-70 hover:opacity-100 transition"
type="button" type="button"
on:click={() => { onclick={() => {
data.questions[selected_question].image = null; data.questions[selected_question].image = null;
}} }}
> >
@@ -187,8 +194,7 @@ SPDX-License-Identifier: MPL-2.0
{#await import('$lib/editor/uploader.svelte')} {#await import('$lib/editor/uploader.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<svelte:component <c.default
this={c.default}
bind:modalOpen={uppyOpen} bind:modalOpen={uppyOpen}
bind:edit_id bind:edit_id
bind:data bind:data
@@ -217,7 +223,7 @@ SPDX-License-Identifier: MPL-2.0
type="number" type="number"
max="999" max="999"
min="1" 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} bind:value={data.questions[selected_question].time}
/> />
<p class="inline-block">s</p> <p class="inline-block">s</p>
@@ -231,8 +237,7 @@ SPDX-License-Identifier: MPL-2.0
{#await import('$lib/editor/ABCDEditorPart.svelte')} {#await import('$lib/editor/ABCDEditorPart.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<svelte:component <c.default
this={c.default}
bind:data bind:data
bind:selected_question bind:selected_question
check_choice={type === QuizQuestionType.CHECK} check_choice={type === QuizQuestionType.CHECK}
@@ -244,19 +249,19 @@ SPDX-License-Identifier: MPL-2.0
{#await import('$lib/editor/VotingEditorPart.svelte')} {#await import('$lib/editor/VotingEditorPart.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<svelte:component this={c.default} bind:data bind:selected_question /> <c.default bind:data bind:selected_question />
{/await} {/await}
{:else if type === QuizQuestionType.TEXT} {:else if type === QuizQuestionType.TEXT}
{#await import('$lib/editor/TextEditorPart.svelte')} {#await import('$lib/editor/TextEditorPart.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<svelte:component this={c.default} bind:data bind:selected_question /> <c.default bind:data bind:selected_question />
{/await} {/await}
{:else if type === QuizQuestionType.ORDER} {:else if type === QuizQuestionType.ORDER}
{#await import('$lib/editor/OrderEditorPart.svelte')} {#await import('$lib/editor/OrderEditorPart.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<svelte:component this={c.default} bind:data bind:selected_question /> <c.default bind:data bind:selected_question />
{/await} {/await}
{/if} {/if}
</div> </div>
@@ -268,18 +273,24 @@ SPDX-License-Identifier: MPL-2.0
{#if advanced_options_open} {#if advanced_options_open}
<div <div
class="fixed top-0 left-0 w-full h-full bg-black/60 flex" 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> <h1 class="text-3xl mx-auto">{$t('editor.advanced_settings')}</h1>
<label class="flex justify-around text-lg"> <label class="flex justify-around text-lg">
<span class="my-auto">{$t('editor.hide_question_results')}</span> <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> </label>
<div class="mt-auto w-full"> <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> </div>
</div> </div>
{/if} {/if}
+29 -23
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import type { EditorData } from '$lib/quiz_types'; import type { EditorData } from '$lib/quiz_types';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import Spinner from '$lib/Spinner.svelte'; import Spinner from '$lib/Spinner.svelte';
@@ -12,19 +14,25 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
let uppyOpen = false; let uppyOpen = $state(false);
let bg_uppy_open = false; let bg_uppy_open = $state(false);
export let edit_id: string; interface Props {
export let data: EditorData; edit_id: string;
data: EditorData;
}
let custom_bg_color = Boolean(data.background_color); let { edit_id = $bindable(), data = $bindable() }: Props = $props();
let custom_bg_color = $state(Boolean(data.background_color));
const tippy = createTippy({ const tippy = createTippy({
arrow: true, arrow: true,
animation: 'perspective-subtle' animation: 'perspective-subtle'
}); });
$: data.background_color = custom_bg_color ? data.background_color : undefined; run(() => {
data.background_color = custom_bg_color ? data.background_color : undefined;
});
</script> </script>
<div class="w-full h-full pb-20 px-20"> <div class="w-full h-full pb-20 px-20">
@@ -33,13 +41,13 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex align-middle p-4 gap-3"> <div class="flex align-middle p-4 gap-3">
<span <span
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-red-400 transition" class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-red-400 transition"
/> ></span>
<span <span
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-yellow-400 transition" class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-yellow-400 transition"
/> ></span>
<span <span
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-green-400 transition" class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-green-400 transition"
/> ></span>
</div> </div>
</div> </div>
<div <div
@@ -57,7 +65,7 @@ SPDX-License-Identifier: MPL-2.0
{#await import('$lib/inline-editor.svelte')} {#await import('$lib/inline-editor.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<svelte:component this={c.default} bind:text={data.title} /> <c.default bind:text={data.title} />
{/await} {/await}
</div> </div>
<div class="flex justify-center pt-10 w-full max-h-32"> <div class="flex justify-center pt-10 w-full max-h-32">
@@ -65,8 +73,8 @@ SPDX-License-Identifier: MPL-2.0
type="text" type="text"
placeholder="Description" placeholder="Description"
bind:value={data.description} bind:value={data.description}
class="p-3 rounded-lg border-gray-500 border text-center w-1/3 h-20 resize-none dark:bg-gray-500 outline-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> </div>
{#if data.cover_image != undefined && data.cover_image !== ''} {#if data.cover_image != undefined && data.cover_image !== ''}
@@ -75,17 +83,16 @@ SPDX-License-Identifier: MPL-2.0
src="/api/v1/storage/download/{data.cover_image}" src="/api/v1/storage/download/{data.cover_image}"
alt="not available" alt="not available"
class="max-h-72 h-auto w-auto" class="max-h-72 h-auto w-auto"
on:contextmenu|preventDefault={() => { oncontextmenu={preventDefault(() => {
data.cover_image = null; data.cover_image = null;
}} })}
/> />
</div> </div>
{:else} {:else}
{#await import('$lib/editor/uploader.svelte')} {#await import('$lib/editor/uploader.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<svelte:component <c.default
this={c.default}
bind:modalOpen={uppyOpen} bind:modalOpen={uppyOpen}
bind:edit_id bind:edit_id
bind:data bind:data
@@ -96,7 +103,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="pt-10 w-full flex justify-center"> <div class="pt-10 w-full flex justify-center">
<button <button
type="button" type="button"
on:click={() => { onclick={() => {
data.public = !data.public; data.public = !data.public;
}} }}
class="text-center w-fit" class="text-center w-fit"
@@ -149,7 +156,7 @@ SPDX-License-Identifier: MPL-2.0
> >
<span <span
class="inline-block w-full h-full bg-[#d6edc9] dark:bg-[#4e6e58]" class="inline-block w-full h-full bg-[#d6edc9] dark:bg-[#4e6e58]"
/> ></span>
</div> </div>
</div> </div>
<div> <div>
@@ -164,8 +171,8 @@ SPDX-License-Identifier: MPL-2.0
class="sr-only peer" class="sr-only peer"
/> />
<span <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> </label>
</div> </div>
<div <div
@@ -188,7 +195,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="w-full flex justify-center -mt-8"> <div class="w-full flex justify-center -mt-8">
{#if data.background_image} {#if data.background_image}
<button <button
on:click={() => { onclick={() => {
data.background_image = undefined; data.background_image = undefined;
}} }}
class="mt-10 bg-red-500 p-2 rounded-lg border-2 border-black transition hover:bg-red-400" class="mt-10 bg-red-500 p-2 rounded-lg border-2 border-black transition hover:bg-red-400"
@@ -200,8 +207,7 @@ SPDX-License-Identifier: MPL-2.0
<Spinner my_20={false} /> <Spinner my_20={false} />
</div> </div>
{:then c} {:then c}
<svelte:component <c.default
this={c.default}
bind:modalOpen={bg_uppy_open} bind:modalOpen={bg_uppy_open}
bind:edit_id bind:edit_id
bind:data bind:data
+27 -23
View File
@@ -17,19 +17,23 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
export let data: EditorData; interface Props {
export let selected_question = -1; data: EditorData;
selected_question?: any;
}
let reorder_mode = false; let { data = $bindable(), selected_question = $bindable(-1) }: Props = $props();
let reorder_mode = $state(false);
const tippy = createTippy({ const tippy = createTippy({
arrow: true, arrow: true,
animation: 'perspective-subtle', animation: 'perspective-subtle',
placement: 'right' placement: 'right'
}); });
let arr_of_cards = Array(data.questions.length); let arr_of_cards = $state(Array(data.questions.length));
let propertyCard; let propertyCard = $state();
let add_new_question_popup_open = false; let add_new_question_popup_open = $state(false);
const empy_slide: Question = { const empy_slide: Question = {
type: QuizQuestionType.SLIDE, type: QuizQuestionType.SLIDE,
@@ -82,10 +86,10 @@ SPDX-License-Identifier: MPL-2.0
<div class="border-r-2 pt-6 px-6 overflow-scroll h-full"> <div class="border-r-2 pt-6 px-6 overflow-scroll h-full">
<div <div
bind:this={propertyCard} 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:bg-green-300={selected_question === -1}
class:dark:bg-green-500={selected_question === -1} class:dark:bg-green-500={selected_question === -1}
on:click={() => setSelectedQuestion(-1)} onclick={() => setSelectedQuestion(-1)}
> >
<div <div
use:tippy={{ content: data.title === '' ? "It's empty!" : data.title }} use:tippy={{ content: data.title === '' ? "It's empty!" : data.title }}
@@ -96,7 +100,7 @@ SPDX-License-Identifier: MPL-2.0
> >
<p <p
type="text" 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} class:dark:text-black={selected_question === -1}
> >
{#if data.title} {#if data.title}
@@ -117,9 +121,9 @@ SPDX-License-Identifier: MPL-2.0
> >
<textarea <textarea
bind:value={data.description} 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} class:dark:text-black={selected_question === -1}
/> ></textarea>
</div> </div>
<div <div
class="w-full flex justify-center dark:text-white" class="w-full flex justify-center dark:text-white"
@@ -127,7 +131,7 @@ SPDX-License-Identifier: MPL-2.0
> >
<button <button
type="button" type="button"
on:click={() => { onclick={() => {
data.public = !data.public; data.public = !data.public;
}} }}
class="text-center" class="text-center"
@@ -170,18 +174,18 @@ SPDX-License-Identifier: MPL-2.0
</div> </div>
{#each data.questions as question, index} {#each data.questions as question, index}
<div <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:bg-green-300={index === selected_question}
class:dark:bg-green-500={index === selected_question} class:dark:bg-green-500={index === selected_question}
on:click={() => { onclick={() => {
setSelectedQuestion(index); setSelectedQuestion(index);
}} }}
bind:this={arr_of_cards[index]} bind:this={arr_of_cards[index]}
> >
{#if reorder_mode} {#if reorder_mode}
<div <div
transition:fade={{ duration: 90 }} transition:fade|global={{ duration: 90 }}
class="absolute z-10 grid grid-cols-2 bg-transparent w-full rounded h-full" 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 is used, since it just put me on the dashboard when using button elements... Idk why and I hate it-->
<div <div
@@ -190,7 +194,7 @@ SPDX-License-Identifier: MPL-2.0
aria-label="Move card up" aria-label="Move card up"
class:opacity-50={index === 0} class:opacity-50={index === 0}
class:pointer-events-none={index === 0} class:pointer-events-none={index === 0}
on:click={() => onclick={() =>
(data.questions = swapArrayElements( (data.questions = swapArrayElements(
data.questions, data.questions,
index, index,
@@ -219,7 +223,7 @@ SPDX-License-Identifier: MPL-2.0
aria-label="Move card down" aria-label="Move card down"
class:opacity-50={index + 1 === data.questions.length} class:opacity-50={index + 1 === data.questions.length}
class:pointer-events-none={index + 1 === data.questions.length} class:pointer-events-none={index + 1 === data.questions.length}
on:click={() => onclick={() =>
(data.questions = swapArrayElements( (data.questions = swapArrayElements(
data.questions, data.questions,
index, index,
@@ -248,7 +252,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="rounded-full absolute -top-3 -right-3 opacity-70 hover:opacity-100 transition" class="rounded-full absolute -top-3 -right-3 opacity-70 hover:opacity-100 transition"
type="button" type="button"
on:click={() => { onclick={() => {
if (confirm('Do you really want to delete this Question?')) { if (confirm('Do you really want to delete this Question?')) {
selected_question = -1; selected_question = -1;
data.questions.splice(index, 1); data.questions.splice(index, 1);
@@ -298,7 +302,7 @@ SPDX-License-Identifier: MPL-2.0
class="h-10 border rounded-lg" class="h-10 border rounded-lg"
alt="Not available" alt="Not available"
use:tippy={{ 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 allowHTML: true
}} }}
/> />
@@ -375,12 +379,12 @@ SPDX-License-Identifier: MPL-2.0
</div> </div>
{/each} {/each}
<div <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 <button
type="button" type="button"
class="h-full flex justify-center w-full flex-col border-r border-black dark:text-white" class="h-full flex justify-center w-full flex-col border-r border-black dark:text-white"
on:click={() => { onclick={() => {
add_new_question_popup_open = true; add_new_question_popup_open = true;
}} }}
> >
@@ -403,7 +407,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
type="button" type="button"
class="h-full flex justify-center w-full dark:text-white flex-col" class="h-full flex justify-center w-full dark:text-white flex-col"
on:click={() => { onclick={() => {
data.questions = [...data.questions, { ...empy_slide }]; data.questions = [...data.questions, { ...empy_slide }];
}} }}
> >
+20 -14
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import type { Question } from '$lib/quiz_types'; import type { Question } from '$lib/quiz_types';
import { ElementTypes, QuizQuestionType } from '$lib/quiz_types'; import { ElementTypes, QuizQuestionType } from '$lib/quiz_types';
import ElementSelection from './slides/element_selection.svelte'; import ElementSelection from './slides/element_selection.svelte';
@@ -15,21 +17,25 @@ SPDX-License-Identifier: MPL-2.0
import type { Konva, ShapeModel } from 'pikaso'; import type { Konva, ShapeModel } from 'pikaso';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
export let data: Question = { interface Props {
data?: Question;
}
let { data = $bindable({
type: QuizQuestionType.SLIDE, type: QuizQuestionType.SLIDE,
time: '120', time: '120',
question: '', question: '',
image: undefined, image: undefined,
answers: '' answers: ''
}; }) }: Props = $props();
let selected_element = undefined; let selected_element = $state(undefined);
let canvas_el: HTMLDivElement | undefined; let canvas_el: HTMLDivElement | undefined = $state();
let canvas: Pikaso; let canvas: Pikaso;
let selected_el: null | ShapeModel<Konva.Shape | Konva.Group, Konva.ShapeConfig> = null; let selected_el: null | ShapeModel<Konva.Shape | Konva.Group, Konva.ShapeConfig> = $state(null);
let elements_binds: Array<HTMLElement> | undefined = []; let elements_binds: Array<HTMLElement> | undefined = [];
let main_el: undefined | HTMLElement; let main_el: undefined | HTMLElement = $state();
let settings_menu_open = false; let settings_menu_open = $state(false);
const set_correct_height = new ResizeObserver((e) => { const set_correct_height = new ResizeObserver((e) => {
for (const i of e) { for (const i of e) {
@@ -87,11 +93,11 @@ SPDX-License-Identifier: MPL-2.0
} }
}; };
$: { run(() => {
if (selected_element) { if (selected_element) {
add_text_field(); add_text_field();
} }
} });
const assign_resize_handlers = () => { const assign_resize_handlers = () => {
for (let i = 0; i < elements_binds.length; i++) { for (let i = 0; i < elements_binds.length; i++) {
set_correct_height.observe(elements_binds[i]); set_correct_height.observe(elements_binds[i]);
@@ -105,10 +111,10 @@ SPDX-License-Identifier: MPL-2.0
} }
}*/ }*/
$: { run(() => {
elements_binds; elements_binds;
assign_resize_handlers(); assign_resize_handlers();
} });
onMount(() => { onMount(() => {
/* setTimeout(() => { /* setTimeout(() => {
@@ -172,7 +178,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex flex-col pl-2 rounded-t-lg z-40 pt-2"> <div class="flex flex-col pl-2 rounded-t-lg z-40 pt-2">
<button <button
class="mr-auto" class="mr-auto"
on:click={() => { onclick={() => {
settings_menu_open = !settings_menu_open; settings_menu_open = !settings_menu_open;
}} }}
type="button" type="button"
@@ -212,7 +218,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex flex-col pr-2 rounded-t-lg z-40 pt-2"> <div class="flex flex-col pr-2 rounded-t-lg z-40 pt-2">
<button <button
class="ml-auto" class="ml-auto"
on:click={() => { onclick={() => {
selected_element = selected_element === null ? undefined : null; selected_element = selected_element === null ? undefined : null;
}} }}
type="button" type="button"
@@ -240,5 +246,5 @@ SPDX-License-Identifier: MPL-2.0
{/if} {/if}
</div> </div>
</div> </div>
<div bind:this={canvas_el} class="w-full h-full block" /> <div bind:this={canvas_el} class="w-full h-full block"></div>
</div> </div>
+24 -16
View File
@@ -5,14 +5,20 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import type { Konva, ShapeModel } from 'pikaso'; import type { Konva, ShapeModel } from 'pikaso';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
export let selected_el: null | ShapeModel<Konva.Shape | Konva.Group, Konva.ShapeConfig>; interface Props {
selected_el: null | ShapeModel<Konva.Shape | Konva.Group, Konva.ShapeConfig>;
}
let opened_dropdown = null; let { selected_el }: Props = $props();
let available_modifiers: Array<string> = []; let opened_dropdown = $state(null);
let available_modifiers: Array<string> = $state([]);
const set_available_modifiers = () => { const set_available_modifiers = () => {
available_modifiers = []; available_modifiers = [];
@@ -32,10 +38,10 @@ SPDX-License-Identifier: MPL-2.0
} }
}; };
$: { run(() => {
selected_el; selected_el;
set_available_modifiers(); set_available_modifiers();
} });
const toggle_switch = (el: string) => { const toggle_switch = (el: string) => {
if (opened_dropdown) { if (opened_dropdown) {
@@ -62,9 +68,11 @@ SPDX-License-Identifier: MPL-2.0
}); });
}; };
$: if (!selected_el) { run(() => {
opened_dropdown = null; if (!selected_el) {
} opened_dropdown = null;
}
});
</script> </script>
<div class="flex flex-row justify-evenly z-40"> <div class="flex flex-row justify-evenly z-40">
@@ -76,7 +84,7 @@ SPDX-License-Identifier: MPL-2.0
available_modifiers.includes('fill_color') || available_modifiers.includes('fill_color') ||
available_modifiers.includes('text_color') available_modifiers.includes('text_color')
)} )}
on:click={() => { onclick={() => {
toggle_switch('color'); toggle_switch('color');
}} }}
> >
@@ -101,9 +109,9 @@ SPDX-License-Identifier: MPL-2.0
{#if opened_dropdown === 'color'} {#if opened_dropdown === 'color'}
<div <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" 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> </div>
{/if} {/if}
</div> </div>
@@ -111,7 +119,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
type="button" type="button"
class="disabled:opacity-50 transition" class="disabled:opacity-50 transition"
on:click={() => { onclick={() => {
toggle_switch('font_size'); toggle_switch('font_size');
}} }}
disabled={!available_modifiers.includes('font_size')} disabled={!available_modifiers.includes('font_size')}
@@ -137,11 +145,11 @@ SPDX-License-Identifier: MPL-2.0
{#if opened_dropdown === 'font_size'} {#if opened_dropdown === 'font_size'}
<div <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" 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 <input
type="range" type="range"
on:change={change_fontsize} onchange={change_fontsize}
value={selected_el?.node?.children?.[1]?.attrs?.fontSize} value={selected_el?.node?.children?.[1]?.attrs?.fontSize}
min="10" min="10"
max="250" max="250"
@@ -153,7 +161,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
type="button" type="button"
class="disabled:opacity-50 transition" class="disabled:opacity-50 transition"
on:click={() => { onclick={() => {
selected_el.update({ zIndex: selected_el.node.getZIndex() + 1 }); selected_el.update({ zIndex: selected_el.node.getZIndex() + 1 });
}} }}
disabled={!selected_el} disabled={!selected_el}
@@ -179,7 +187,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
type="button" type="button"
class="disabled:opacity-50 transition" class="disabled:opacity-50 transition"
on:click={() => { onclick={() => {
selected_el.update({ zIndex: selected_el.node.getZIndex() - 1 }); selected_el.update({ zIndex: selected_el.node.getZIndex() - 1 });
}} }}
disabled={!selected_el} disabled={!selected_el}
@@ -11,7 +11,7 @@ SPDX-License-Identifier: MPL-2.0
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let selected_element; let { selected_element = $bindable() } = $props();
const keybinding_list = { const keybinding_list = {
t: ElementTypes.Text, t: ElementTypes.Text,
h: ElementTypes.Headline, h: ElementTypes.Headline,
@@ -75,13 +75,13 @@ SPDX-License-Identifier: MPL-2.0
<div <div
class="bg-white m-auto rounded-lg shadow-lg p-4 flex flex-col dark:bg-gray-600 h-fit" 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> <ul>
{#each element_list as el} {#each element_list as el}
<li <li
class="flex flex-row mt-4 w-full bg-gray-200 shadow-xl rounded-lg p-2 hover:bg-gray-300 hover:shadow-2xl hover:cursor-pointer transition dark:bg-gray-800" class="flex flex-row mt-4 w-full bg-gray-200 shadow-xl rounded-lg p-2 hover:bg-gray-300 hover:shadow-2xl hover:cursor-pointer transition dark:bg-gray-800"
on:click={() => { onclick={() => {
selected_element = el.type; selected_element = el.type;
}} }}
> >
@@ -5,13 +5,16 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
export let title; let { title = $bindable(), time = $bindable() } = $props();
export let time; let time_local = $state(120);
let time_local = 120;
/*eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }]*/ /*eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }]*/
$: time = String(time_local); run(() => {
time = String(time_local);
});
if (time) { if (time) {
time_local = parseInt(time); time_local = parseInt(time);
} }
@@ -19,14 +22,14 @@ SPDX-License-Identifier: MPL-2.0
<div <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" 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"> <label class="w-fit">
Time Time
<input <input
bind:value={time} bind:value={time}
type="number" 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>
<label class="w-fit"> <label class="w-fit">
@@ -34,7 +37,7 @@ SPDX-License-Identifier: MPL-2.0
<input <input
bind:value={title} bind:value={title}
type="text" 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> </label>
</div> </div>
@@ -5,8 +5,12 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let data: string; interface Props {
export let editor = true; data: string;
editor?: boolean;
}
let { data = $bindable(), editor = true }: Props = $props();
if (!data) { if (!data) {
data = '#ed333b'; data = '#ed333b';
@@ -5,8 +5,12 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let data; interface Props {
export let editor = true; data: any;
editor?: boolean;
}
let { data = $bindable(), editor = true }: Props = $props();
</script> </script>
{#if editor} {#if editor}
@@ -5,8 +5,12 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let data: string; interface Props {
export let editor = true; data: string;
editor?: boolean;
}
let { data = $bindable(), editor = true }: Props = $props();
if (!data) { if (!data) {
data = '#ed333b'; data = '#ed333b';
@@ -5,12 +5,16 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let data; interface Props {
export let editor = true; data: any;
editor?: boolean;
}
let { data = $bindable(), editor = true }: Props = $props();
</script> </script>
{#if editor} {#if editor}
<textarea bind:value={data} class="w-full h-full dark:text-black resize-none" /> <textarea bind:value={data} class="w-full h-full dark:text-black resize-none"></textarea>
{:else} {:else}
<p>{data}</p> <p>{data}</p>
{/if} {/if}
+28 -22
View File
@@ -3,7 +3,6 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0 SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { Dashboard as SvelteDashboard } from '@uppy/svelte'; import { Dashboard as SvelteDashboard } from '@uppy/svelte';
import Uppy from '@uppy/core'; import Uppy from '@uppy/core';
@@ -19,7 +18,6 @@ SPDX-License-Identifier: MPL-2.0
import '@uppy/core/dist/style.css'; import '@uppy/core/dist/style.css';
import '@uppy/dashboard/dist/style.css'; import '@uppy/dashboard/dist/style.css';
import '@uppy/drop-target/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 '@uppy/image-editor/dist/style.css';
import type { EditorData } from '../quiz_types'; import type { EditorData } from '../quiz_types';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
@@ -28,18 +26,26 @@ SPDX-License-Identifier: MPL-2.0
import Pixabay from '$lib/editor/uploader/Pixabay.svelte'; import Pixabay from '$lib/editor/uploader/Pixabay.svelte';
const { t } = getLocalization(); const { t } = getLocalization();
let {
export let modalOpen = false; modalOpen = false,
export let edit_id: string; edit_id,
export let data: EditorData; data,
export let selected_question: number; selected_question,
export let video_upload = false; video_upload = false,
export let library_enabled = true; library_enabled = true
}: {
modalOpen: boolean;
edit_id: string;
data: EditorData;
selected_question: number;
video_upload: boolean;
library_enabled: boolean;
} = $props();
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
let video_popup: undefined | WindowProxy = undefined; let video_popup: undefined | WindowProxy = $state(undefined);
let selected_type: AvailableUploadTypes | null = null; let selected_type: AvailableUploadTypes | null = $state(null);
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
enum AvailableUploadTypes { enum AvailableUploadTypes {
@@ -68,7 +74,7 @@ SPDX-License-Identifier: MPL-2.0
.use(XHRUpload, { .use(XHRUpload, {
endpoint: `/api/v1/storage/` endpoint: `/api/v1/storage/`
}); });
const props = { const properties = {
inline: true, inline: true,
restrictions: { restrictions: {
maxFileSize: 10_490_000, maxFileSize: 10_490_000,
@@ -134,12 +140,12 @@ SPDX-License-Identifier: MPL-2.0
{#if modalOpen} {#if modalOpen}
<div <div
class="w-screen h-screen fixed top-0 left-0 bg-opacity-50 bg-black z-20 flex justify-center" class="w-screen h-screen fixed top-0 left-0 bg-black/50 z-20 flex justify-center"
on:click={handle_on_click} onclick={handle_on_click}
transition:fade|local={{ duration: 100 }} transition:fade={{ duration: 100 }}
> >
{#if selected_type === null} {#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> <h1 class="text-3xl text-center mb-4">{$t('uploader.select_upload_type')}</h1>
<div class="flex flex-row gap-4"> <div class="flex flex-row gap-4">
<div class="w-full"> <div class="w-full">
@@ -180,15 +186,15 @@ SPDX-License-Identifier: MPL-2.0
</div> </div>
</div> </div>
{:else if selected_type === AvailableUploadTypes.Image} {: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> <div>
<SvelteDashboard {uppy} width="100%" {props} /> <SvelteDashboard {uppy} width="100%" {properties} />
</div> </div>
</div> </div>
{:else if selected_type === AvailableUploadTypes.Video} {:else if selected_type === AvailableUploadTypes.Video}
<div <div
class="m-auto w-1/3 h-auto bg-white dark:bg-gray-700 p-4 rounded" class="m-auto w-1/3 h-auto bg-white dark:bg-gray-700 p-4 rounded-sm"
transition:fade|local={{ duration: 100 }} transition:fade={{ duration: 100 }}
> >
<h1 class="text-3xl text-center mb-4">{$t('uploader.upload_a_video')}</h1> <h1 class="text-3xl text-center mb-4">{$t('uploader.upload_a_video')}</h1>
{#if video_popup} {#if video_popup}
@@ -212,11 +218,11 @@ SPDX-License-Identifier: MPL-2.0
{/if} {/if}
</div> </div>
{/if} {/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 <button
class="rounded-lg p-4 flex justify-center bg-transparent border-gray-500 border-2 w-1/2 hover:bg-gray-300 dark:hover:bg-gray-600 transition" class="rounded-lg p-4 flex justify-center bg-transparent border-gray-500 border-2 w-1/2 hover:bg-gray-300 dark:hover:bg-gray-600 transition"
type="button" type="button"
on:click={() => { onclick={() => {
modalOpen = true; modalOpen = true;
}} }}
><span class="italic">{$t('uploader.add_image')}</span> ><span class="italic">{$t('uploader.add_image')}</span>
@@ -11,9 +11,13 @@ SPDX-License-Identifier: MPL-2.0
import BrownButton from '$lib/components/buttons/brown.svelte'; import BrownButton from '$lib/components/buttons/brown.svelte';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
export let data: EditorData; interface Props {
export let selected_question: number; data: EditorData;
export let modalOpen: boolean; selected_question: number;
modalOpen: boolean;
}
let { data = $bindable(), selected_question, modalOpen = $bindable() }: Props = $props();
const { t } = getLocalization(); const { t } = getLocalization();
@@ -40,16 +44,16 @@ SPDX-License-Identifier: MPL-2.0
{:then images} {:then images}
<div class="flex w-screen p-8 h-screen"> <div class="flex w-screen p-8 h-screen">
<div <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} {#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> <div>
<img <img
src="/api/v1/storage/download/{image.id}" src="/api/v1/storage/download/{image.id}"
loading="lazy" loading="lazy"
alt={image.alt_text} 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> </div>
<p class="text-center">{image.filename ?? 'No name available'}</p> <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"> <script lang="ts">
import { preventDefault } from 'svelte/legacy';
import type { EditorData } from '$lib/quiz_types'; import type { EditorData } from '$lib/quiz_types';
import Spinner from '$lib/Spinner.svelte'; import Spinner from '$lib/Spinner.svelte';
import BrownButton from '$lib/components/buttons/brown.svelte'; import BrownButton from '$lib/components/buttons/brown.svelte';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
export let data: EditorData; interface Props {
export let selected_question: number; data: EditorData;
export let modalOpen: boolean; selected_question: number;
modalOpen: boolean;
}
let page = 1; let { data = $bindable(), selected_question, modalOpen = $bindable() }: Props = $props();
let search_term = '';
let loading = false; let page = $state(1);
let search_term = $state('');
let loading = $state(false);
const { t } = getLocalization(); const { t } = getLocalization();
@@ -42,7 +48,7 @@ SPDX-License-Identifier: MPL-2.0
return await res.json(); return await res.json();
}; };
let fetched_data = fetch_data(); let fetched_data = $state(fetch_data());
</script> </script>
{#await fetched_data} {#await fetched_data}
@@ -53,7 +59,7 @@ SPDX-License-Identifier: MPL-2.0
{:else} {:else}
<div class="flex w-screen p-8 h-full mt-8 mb-1"> <div class="flex w-screen p-8 h-full mt-8 mb-1">
<div <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> <h1 class="text-2xl text-center">{$t('uploader.images_by_pixabay')}</h1>
<div class="flex"> <div class="flex">
@@ -64,10 +70,10 @@ SPDX-License-Identifier: MPL-2.0
<form <form
class="w-full flex gap-2" class="w-full flex gap-2"
on:submit|preventDefault={() => (fetched_data = fetch_data())} onsubmit={preventDefault(() => (fetched_data = fetch_data()))}
> >
<input <input
class="w-full outline-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} bind:value={search_term}
/> />
<div class="w-fit"> <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> <span class="italic text-center text-sm">{$t('uploader.search_english_only')}</span>
{#each data.hits as image} {#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> <div>
<img <img
src={image.webformatURL} src={image.webformatURL}
loading="lazy" loading="lazy"
alt="unavailable" 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> </div>
<BrownButton <BrownButton
+5 -1
View File
@@ -7,7 +7,8 @@ SPDX-License-Identifier: MPL-2.0
<script lang="ts"> <script lang="ts">
import Spinner from '$lib/Spinner.svelte'; import Spinner from '$lib/Spinner.svelte';
export let files: { interface Props {
files: {
id: string; id: string;
uploaded_at: string; uploaded_at: string;
mime_type?: string; mime_type?: string;
@@ -21,6 +22,9 @@ SPDX-License-Identifier: MPL-2.0
quizzes: { id: string }[]; quizzes: { id: string }[];
quiztivities: { id: string }[]; quiztivities: { id: string }[];
}[]; }[];
}
let { files = $bindable() }: Props = $props();
const get_files = async () => { const get_files = async () => {
const res = await fetch('/api/v1/storage/list'); const res = await fetch('/api/v1/storage/list');
+3 -2
View File
@@ -45,7 +45,7 @@ export class I18nService {
initialize(): void { initialize(): void {
this.i18n.use(LanguageDetector).init({ this.i18n.use(LanguageDetector).init({
// lng: INITIAL_LANGUAGE, // lng: INITIAL_LANGUAGE,
compatibilityJSON: 'v3', compatibilityJSON: 'v4',
fallbackLng: 'en', fallbackLng: 'en',
debug: false, debug: false,
defaultNS: 'translation', defaultNS: 'translation',
@@ -61,7 +61,8 @@ export class I18nService {
detection: { detection: {
order: ['querystring', 'cookie', 'localStorage', 'navigator'], order: ['querystring', 'cookie', 'localStorage', 'navigator'],
lookupQuerystring: 'lng', lookupQuerystring: 'lng',
lookupLocalStorage: 'language' lookupLocalStorage: 'language',
lookupSessionStorage: true
} }
}); });
this.i18n.addResourceBundle('en', 'translation', en); this.i18n.addResourceBundle('en', 'translation', en);
+7 -3
View File
@@ -22,15 +22,19 @@ export const getLocalization = () => {
return getContext<I18nContext>(CONTEXT_KEY); return getContext<I18nContext>(CONTEXT_KEY);
}; };
export const initLocalizationContext = () => { export const initLocalizationContext = (start_lanugage: string): { i18n: I18nService } => {
// Initialize our services // Initialize our services
const i18n = new I18nService(); const i18n = new I18nService();
const tranlator = new I18NextTranslationService(i18n); const tranlator = new I18NextTranslationService(i18n);
let locale: any;
if (start_lanugage) {
locale = start_lanugage;
}
tranlator.locale.set(locale);
// skipcq: JS-0357 // skipcq: JS-0357
setLocalization({ setLocalization({
t: tranlator.translate, t: tranlator.translate,
currentLanguage: tranlator.locale currentLanguage: locale
}); });
return { return {
+53 -8
View File
@@ -5,26 +5,47 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <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 Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
// import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold.js'; // import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold.js';
// import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic.js'; // import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic.js';
// import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough'; // import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough';
// import Subscript from '@ckeditor/ckeditor5-basic-styles/src/subscript.js'; // import Subscript from '@ckeditor/ckeditor5-basic-styles/src/subscript.js';
// import Superscript from '@ckeditor/ckeditor5-basic-styles/src/superscript.js'; // import Superscript from '@ckeditor/ckeditor5-basic-styles/src/superscript.js';
// import Autoformat from "@ckeditor/ckeditor5-autoformat/src/autoformat"
export let text = '';
const triggerChange = () => { const triggerChange = () => {
text = editor.getData(); text = editor.getData();
}; };
import { onMount } from 'svelte'; import { onMount } from 'svelte';
interface Props {
// import Autoformat from "@ckeditor/ckeditor5-autoformat/src/autoformat"
text?: string;
}
let html_el; let { text = $bindable('') }: Props = $props();
$: text = text.replace('<p>', '').replace('</p>', ''); let html_el = $state();
run(() => {
text = text.replace('<p>', '').replace('</p>', '');
});
/* Editor.builtinPlugins = [ /* Editor.builtinPlugins = [
Autoformat, Autoformat,
@@ -54,8 +75,26 @@ SPDX-License-Identifier: MPL-2.0
};*/ };*/
let editor; let editor;
onMount(() => { onMount(() => {
class Editor extends BalloonEditor {
static builtinPlugins = [
Essentials,
Autoformat,
Bold,
Italic,
Paragraph,
TextTransformation,
Strikethrough,
Subscript,
Superscript
];
static defaultConfig = {
language: 'en'
};
}
// BalloonEditor.builtinPlugins = [Strikethrough] // BalloonEditor.builtinPlugins = [Strikethrough]
BalloonEditor.create(html_el, { Editor.create(html_el, {
licenseKey: 'GPL',
// plugins: [Strikethrough], // plugins: [Strikethrough],
config: { config: {
enterMode: BalloonEditor.ENTER_DIV, enterMode: BalloonEditor.ENTER_DIV,
@@ -89,6 +128,12 @@ SPDX-License-Identifier: MPL-2.0
<div <div
bind:this={html_el} bind:this={html_el}
contenteditable="true" contenteditable="true"
class="rounded-lg border-gray-500 border text-center w-fit h-fit resize-none dark:bg-gray-500 min-w-[5rem]" class="rounded-lg border-gray-500 border text-center w-fit h-fit resize-none dark:bg-gray-500 min-w-[5rem] dark:text-white"
/> ></div>
</div> </div>
<style>
:global(.ck-powered-by) {
display: none;
}
</style>
@@ -5,8 +5,10 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let text; import { run } from 'svelte/legacy';
let internal_text = '';
let { text } = $props();
let internal_text = $state('');
/* /*
const markSelection = (function() { const markSelection = (function() {
const markerTextChar = '\ufeff'; const markerTextChar = '\ufeff';
@@ -92,10 +94,10 @@ SPDX-License-Identifier: MPL-2.0
console.log(output); console.log(output);
}; };
$: { run(() => {
process_input(); process_input();
internal_text; internal_text;
} });
</script> </script>
<input bind:value={internal_text} /> <input bind:value={internal_text} />
@@ -20,7 +20,7 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
let teacherTab = false; let teacherTab = $state(false);
</script> </script>
<div class="grid grid-cols-1 overflow-hidden h-fit"> <div class="grid grid-cols-1 overflow-hidden h-fit">
@@ -102,28 +102,28 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex justify-evenly lg:w-2/6"> <div class="flex justify-evenly lg:w-2/6">
{#if teacherTab} {#if teacherTab}
<button <button
class="border border-blue-600 p-2 border-2 rounded hover:bg-gray-700 text-2xl" class="border border-blue-600 p-2 border-2 rounded-sm hover:bg-gray-700 text-2xl"
on:click={() => { onclick={() => {
teacherTab = true; teacherTab = true;
}} }}
>{$t('index_page.teachers_site')} >{$t('index_page.teachers_site')}
</button> </button>
{:else} {:else}
<button <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:border-black={!teacherTab}
class:text-base={!teacherTab} class:text-base={!teacherTab}
on:click={() => { onclick={() => {
teacherTab = true; teacherTab = true;
}} }}
>{$t('index_page.teachers_site')} >{$t('index_page.teachers_site')}
</button> </button>
{/if} {/if}
<button <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:border-blue-600={!teacherTab}
class:text-2xl={!teacherTab} class:text-2xl={!teacherTab}
on:click={() => { onclick={() => {
teacherTab = false; teacherTab = false;
}} }}
>{$t('index_page.students_site')} >{$t('index_page.students_site')}
+9 -7
View File
@@ -5,7 +5,11 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let open: boolean; interface Props {
open: boolean;
}
let { open = $bindable() }: Props = $props();
const closeThing = () => { const closeThing = () => {
open = false; open = false;
@@ -15,7 +19,7 @@ SPDX-License-Identifier: MPL-2.0
<form method="post" action="https://newsletter.mawoka.eu/subscription/form" class="bg-transparent"> <form method="post" action="https://newsletter.mawoka.eu/subscription/form" class="bg-transparent">
<div class="absolute top-1 right-1"> <div class="absolute top-1 right-1">
<button type="button" on:click={closeThing}> <button type="button" onclick={closeThing}>
<svg <svg
class="w-6 h-6" class="w-6 h-6"
fill="none" fill="none"
@@ -41,7 +45,7 @@ SPDX-License-Identifier: MPL-2.0
name="email" name="email"
required required
placeholder="E-mail" 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>
<p> <p>
@@ -49,7 +53,7 @@ SPDX-License-Identifier: MPL-2.0
type="text" type="text"
name="name" name="name"
placeholder="Name (optional)" 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> </p>
@@ -64,9 +68,7 @@ SPDX-License-Identifier: MPL-2.0
<label for="fec55">ClassQuiz-News</label> <label for="fec55">ClassQuiz-News</label>
</p> </p>
<div class="bg-gradient-to-r from-[#009444] via-[#39b54a] to-[#8dc63f] w-fit rounded-lg"> <div class="bg-gradient-to-r from-[#009444] via-[#39b54a] to-[#8dc63f] w-fit rounded-lg">
<button <button type="submit" class="m-0.5 bg-white/7 rounded-lg py-0.5 px-1 text-black"
type="submit"
class="m-0.5 bg-opacity-70 bg-white rounded-lg py-0.5 px-1 text-black"
>Subscribe! >Subscribe!
</button> </button>
</div> </div>
+9 -5
View File
@@ -54,11 +54,15 @@ SPDX-License-Identifier: MPL-2.0
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
export let languages: Array<{ interface Props {
languages?: Array<{
flag: string; flag: string;
name: string; name: string;
code: string; code: string;
}> = [ }>;
}
let { languages = [
{ {
code: 'de', code: 'de',
name: 'Deutsch', name: 'Deutsch',
@@ -139,11 +143,11 @@ SPDX-License-Identifier: MPL-2.0
name: 'tiếng Việt', name: 'tiếng Việt',
flag: '🇻🇳' flag: '🇻🇳'
} }
]; ] }: Props = $props();
const get_selected_language = (): string => { const get_selected_language = (): string => {
return localStorage.getItem('language'); return localStorage.getItem('language');
}; };
let selected_language; let selected_language = $state();
onMount(() => { onMount(() => {
selected_language = get_selected_language(); selected_language = get_selected_language();
}); });
@@ -159,7 +163,7 @@ SPDX-License-Identifier: MPL-2.0
<div> <div>
<select <select
bind:value={selected_language} bind:value={selected_language}
on:change={() => { onchange={() => {
set_language(selected_language); set_language(selected_language);
}} }}
class="p-2 rounded-lg bg-gray-800 focus:ring-2 ring-blue-600 text-white" class="p-2 rounded-lg bg-gray-800 focus:ring-2 ring-blue-600 text-white"
+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 By Flowbite, but changed: https://flowbite.com/docs/components/modal/#default-modal
--> -->
<script lang="ts"> <script lang="ts">
export let title: string; interface Props {
export let body: string; title: string;
export let open: boolean; body: string;
open: boolean;
}
let { title, body, open = $bindable() }: Props = $props();
console.log(open, title, body); console.log(open, title, body);
open = true; open = true;
</script> </script>
@@ -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 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"> <div class="relative p-4 w-full max-w-2xl h-full md:h-auto">
<!-- Modal content --> <!-- 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 --> <!-- Modal header -->
<div <div
class="flex justify-between items-start p-4 rounded-t border-b dark:border-gray-600" 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 <button
type="button" type="button"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"
on:click={() => { onclick={() => {
open = false; open = false;
}} }}
> >
@@ -62,10 +66,10 @@ By Flowbite, but changed: https://flowbite.com/docs/components/modal/#default-mo
<button <button
data-modal-toggle="defaultModal" data-modal-toggle="defaultModal"
type="button" type="button"
on:click={() => { onclick={() => {
open = false; open = false;
}} }}
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-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 Close
</button> </button>
+12 -18
View File
@@ -22,7 +22,7 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
let menuIsClosed = true; let menuIsClosed = $state(true);
const toggleMenu = () => { const toggleMenu = () => {
menuIsClosed = !menuIsClosed; menuIsClosed = !menuIsClosed;
}; };
@@ -31,7 +31,7 @@ SPDX-License-Identifier: MPL-2.0
menuIsClosed = true; // Closes menu to let the user see the page beneath menuIsClosed = true; // Closes menu to let the user see the page beneath
}); });
let darkMode = false; let darkMode = $state(false);
if (browser) { if (browser) {
darkMode = darkMode =
localStorage.theme === 'dark' || localStorage.theme === 'dark' ||
@@ -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" class="font-black tracking-tight text-xl lg:text-2xl text-black marck-script link-hover px-3 lg:px-5"
>ClassQuiz</a >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="/explore">{$t('words.explore')}</a>
<a class="btn-nav" href="/search">{$t('words.search')}</a> <a class="btn-nav" href="/search">{$t('words.search')}</a>
{#if $signedIn} {#if $signedIn}
@@ -108,7 +108,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="lg:flex items-center justify-center"> <div class="lg:flex items-center justify-center">
{#if darkMode} {#if darkMode}
<button <button
on:click={() => { onclick={() => {
switchDarkMode(); switchDarkMode();
}} }}
use:tippy={{ content: 'Switch light mode on' }} use:tippy={{ content: 'Switch light mode on' }}
@@ -134,7 +134,7 @@ SPDX-License-Identifier: MPL-2.0
</button> </button>
{:else} {:else}
<button <button
on:click={() => { onclick={() => {
switchDarkMode(); switchDarkMode();
}} }}
aria-label="Activate darkmode" aria-label="Activate darkmode"
@@ -180,7 +180,7 @@ SPDX-License-Identifier: MPL-2.0
<!-- Sun icon --> <!-- Sun icon -->
<button <button
class="px-3" class="px-3"
on:click={() => { onclick={() => {
switchDarkMode(); switchDarkMode();
}} }}
use:tippy={{ content: 'Switch light mode on' }} use:tippy={{ content: 'Switch light mode on' }}
@@ -208,7 +208,7 @@ SPDX-License-Identifier: MPL-2.0
<!-- Moon icon --> <!-- Moon icon -->
<button <button
class="px-3" class="px-3"
on:click={() => { onclick={() => {
switchDarkMode(); switchDarkMode();
}} }}
aria-label="Activate darkmode" aria-label="Activate darkmode"
@@ -237,7 +237,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="px-3" class="px-3"
id="open-menu" id="open-menu"
on:click={toggleMenu} onclick={toggleMenu}
aria-label="Open navbar" aria-label="Open navbar"
> >
<svg <svg
@@ -258,7 +258,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="px-3" class="px-3"
id="close-menu" id="close-menu"
on:click={toggleMenu} onclick={toggleMenu}
aria-label="Close navbar" aria-label="Close navbar"
> >
<svg <svg
@@ -271,8 +271,8 @@ SPDX-License-Identifier: MPL-2.0
stroke-width="2" stroke-width="2"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
><path in:draw={{ duration: 300 }} d="M18 6 6 18" /><path ><path in:draw|global={{ duration: 300 }} d="M18 6 6 18" /><path
in:draw={{ duration: 300 }} in:draw|global={{ duration: 300 }}
d="m6 6 12 12" d="m6 6 12 12"
/></svg /></svg
> >
@@ -283,7 +283,7 @@ SPDX-License-Identifier: MPL-2.0
<!-- Navbar content --> <!-- Navbar content -->
{#if !menuIsClosed} {#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="/explore">{$t('words.explore')}</a>
<a class="btn-nav" href="/search">{$t('words.search')}</a> <a class="btn-nav" href="/search">{$t('words.search')}</a>
{#if $signedIn} {#if $signedIn}
@@ -340,9 +340,3 @@ SPDX-License-Identifier: MPL-2.0
{/if} {/if}
</div> </div>
</nav> </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 type { Socket } from 'socket.io-client';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
export let bg_color: string;
export let selected_question: number;
export let quiz_data: QuizData;
export let timer_res: string;
export let final_results;
export let socket: Socket;
export let game_token: string;
export let question_results;
export let shown_question_now: number; interface Props {
bg_color: string;
selected_question: number;
quiz_data: QuizData;
timer_res: string;
final_results: any;
socket: Socket;
game_token: string;
question_results: any;
shown_question_now: number;
}
let {
bg_color,
selected_question,
quiz_data,
timer_res = $bindable(),
final_results,
socket,
game_token,
question_results,
shown_question_now
}: Props = $props();
const { t } = getLocalization(); const { t } = getLocalization();
const set_question_number = (q_number: number) => { const set_question_number = (q_number: number) => {
@@ -56,14 +70,14 @@ SPDX-License-Identifier: MPL-2.0
<div class="justify-self-end ml-auto mr-0 col-start-3 col-end-3"> <div class="justify-self-end ml-auto mr-0 col-start-3 col-end-3">
{#if selected_question + 1 === quiz_data.questions.length && ((timer_res === '0' && question_results !== null) || quiz_data?.questions?.[selected_question]?.type === QuizQuestionType.SLIDE)} {#if selected_question + 1 === quiz_data.questions.length && ((timer_res === '0' && question_results !== null) || quiz_data?.questions?.[selected_question]?.type === QuizQuestionType.SLIDE)}
{#if JSON.stringify(final_results) === JSON.stringify([null])} {#if JSON.stringify(final_results) === JSON.stringify([null])}
<button on:click={get_final_results} class="admin-button" <button onclick={get_final_results} class="admin-button"
>{$t('admin_page.get_final_results')} >{$t('admin_page.get_final_results')}
</button> </button>
{/if} {/if}
{:else if timer_res === '0' || selected_question === -1} {:else if timer_res === '0' || selected_question === -1}
{#if (selected_question + 1 !== quiz_data.questions.length && question_results !== null) || selected_question === -1} {#if (selected_question + 1 !== quiz_data.questions.length && question_results !== null) || selected_question === -1}
<button <button
on:click={() => { onclick={() => {
set_question_number(selected_question + 1); set_question_number(selected_question + 1);
}} }}
class="admin-button" class="admin-button"
@@ -73,7 +87,7 @@ SPDX-License-Identifier: MPL-2.0
{#if question_results === null && selected_question !== -1} {#if question_results === null && selected_question !== -1}
{#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE} {#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE}
<button <button
on:click={() => { onclick={() => {
set_question_number(selected_question + 1); set_question_number(selected_question + 1);
}} }}
class="admin-button" class="admin-button"
@@ -81,7 +95,7 @@ SPDX-License-Identifier: MPL-2.0
</button> </button>
{:else if quiz_data.questions[selected_question]?.hide_results === true} {:else if quiz_data.questions[selected_question]?.hide_results === true}
<button <button
on:click={() => { onclick={() => {
get_question_results(); get_question_results();
setTimeout(() => { setTimeout(() => {
set_question_number(selected_question + 1); set_question_number(selected_question + 1);
@@ -91,7 +105,7 @@ SPDX-License-Identifier: MPL-2.0
>{$t('admin_page.next_question', { question: selected_question + 2 })} >{$t('admin_page.next_question', { question: selected_question + 2 })}
</button> </button>
{:else} {:else}
<button on:click={get_question_results} class="admin-button" <button onclick={get_question_results} class="admin-button"
>{$t('admin_page.show_results')} >{$t('admin_page.show_results')}
</button> </button>
{/if} {/if}
@@ -99,14 +113,14 @@ SPDX-License-Identifier: MPL-2.0
{:else if selected_question !== -1} {:else if selected_question !== -1}
{#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE} {#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE}
<button <button
on:click={() => { onclick={() => {
set_question_number(selected_question + 1); set_question_number(selected_question + 1);
}} }}
class="admin-button" class="admin-button"
>{$t('admin_page.next_question', { question: selected_question + 2 })} >{$t('admin_page.next_question', { question: selected_question + 2 })}
</button> </button>
{:else} {:else}
<button on:click={show_solutions} class="admin-button" <button onclick={show_solutions} class="admin-button"
>{$t('admin_page.stop_time_and_solutions')} >{$t('admin_page.stop_time_and_solutions')}
</button> </button>
{/if} {/if}
@@ -5,16 +5,22 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let data;
export let username;
export let show_final_results: boolean;
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import confetti from 'canvas-confetti'; import confetti from 'canvas-confetti';
interface Props {
data: any;
username: any;
show_final_results: boolean;
}
let { data = $bindable(), username, show_final_results }: Props = $props();
function sortObjectbyValue(obj) { function sortObjectbyValue(obj) {
const ret = {}; const ret = {};
@@ -24,18 +30,22 @@ SPDX-License-Identifier: MPL-2.0
return ret; return ret;
} }
$: data = sortObjectbyValue(data); run(() => {
$: console.log(data, 'sorted, fina'); data = sortObjectbyValue(data);
});
let player_names; run(() => {
$: player_names = Object.keys(data).sort(function (a, b) { console.log(data, 'sorted, fina');
return data[b] - data[a];
}); });
let player_count_or_five; let player_names = $derived(Object.keys(data).sort(function (a, b) {
$: player_count_or_five = player_names.length >= 5 ? 5 : player_names.length; return data[b] - data[a];
}));
let canvas;
let player_count_or_five = $derived(player_names.length >= 5 ? 5 : player_names.length);
let canvas = $state();
onMount(() => { onMount(() => {
setTimeout(() => { setTimeout(() => {
confetti.create(canvas, { confetti.create(canvas, {
@@ -48,12 +58,12 @@ SPDX-License-Identifier: MPL-2.0
</script> </script>
{#if show_final_results} {#if show_final_results}
<canvas bind:this={canvas} /> <canvas bind:this={canvas}></canvas>
<div> <div>
{#each player_names as player, i} {#each player_names as player, i}
{#if i <= player_count_or_five - 1} {#if i <= player_count_or_five - 1}
<p <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" style="font-size: {player_count_or_five - i / 2}rem"
class="text-center" class="text-center"
> >
@@ -68,7 +78,7 @@ SPDX-License-Identifier: MPL-2.0
</div> </div>
{#if data[username]} {#if data[username]}
<div class="fixed bottom-0 left-0 flex justify-center w-full mb-6"> <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> <p class="text-center">{$t('play_page.your_score', { score: data[username] })}</p>
{#each player_names as player, i} {#each player_names as player, i}
{#if player === username} {#if player === username}
@@ -11,14 +11,23 @@ SPDX-License-Identifier: MPL-2.0
import GrayButton from '$lib/components/buttons/gray.svelte'; import GrayButton from '$lib/components/buttons/gray.svelte';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
export let game_pin: string; interface Props {
export let players; game_pin: string;
export let socket; players: any;
export let cqc_code: string; socket: any;
cqc_code: string;
}
let fullscreen_open = false; let {
game_pin,
players = $bindable(),
socket,
cqc_code = $bindable()
}: Props = $props();
let fullscreen_open = $state(false);
const { t } = getLocalization(); const { t } = getLocalization();
let play_music = false; let play_music = $state(false);
if (cqc_code === 'null') { if (cqc_code === 'null') {
cqc_code = null; cqc_code = null;
@@ -52,10 +61,10 @@ SPDX-License-Identifier: MPL-2.0
</p> </p>
</div> </div>
<img <img
on:click={() => (fullscreen_open = true)} onclick={() => (fullscreen_open = true)}
alt="QR code to join the game" alt="QR code to join the game"
src="/api/v1/utils/qr/{game_pin}" src="/api/v1/utils/qr/{game_pin}"
class="block mx-auto w-1/2 dark:bg-white shadow-2xl rounded hover:cursor-pointer" class="block mx-auto w-1/2 dark:bg-white shadow-2xl rounded-sm hover:cursor-pointer"
/> />
{#if cqc_code} {#if cqc_code}
<div class="m-auto"> <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"> <div class="flex flex-row w-full mt-4 px-10 flex-wrap">
{#if players.length > 0} {#if players.length > 0}
{#each players as player} {#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 <span
class="hover:line-through text-lg" class="hover:line-through text-lg"
on:click={() => { onclick={() => {
kick_player(player.username); kick_player(player.username);
}}>{player.username}</span }}>{player.username}</span
> >
@@ -114,14 +123,14 @@ SPDX-License-Identifier: MPL-2.0
{#if fullscreen_open} {#if fullscreen_open}
<div <div
class="fixed top-0 left-0 z-50 w-screen h-screen bg-black bg-opacity-50 fle p-2" class="fixed top-0 left-0 z-50 w-screen h-screen bg-black/50 flex p-2"
transition:fade={{ duration: 80 }} transition:fade|global={{ duration: 80 }}
on:click={() => (fullscreen_open = false)} onclick={() => (fullscreen_open = false)}
> >
<img <img
alt="QR code to join the game" alt="QR code to join the game"
src="/api/v1/utils/qr/{game_pin}" 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> </div>
{/if} {/if}
+23 -11
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { QuizQuestionType } from '$lib/quiz_types'; import { QuizQuestionType } from '$lib/quiz_types';
import type { QuizData } from '$lib/quiz_types'; import type { QuizData } from '$lib/quiz_types';
import { get_foreground_color } from '$lib/helpers.js'; import { get_foreground_color } from '$lib/helpers.js';
@@ -13,17 +15,27 @@ SPDX-License-Identifier: MPL-2.0
import MediaComponent from '$lib/editor/MediaComponent.svelte'; import MediaComponent from '$lib/editor/MediaComponent.svelte';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
export let quiz_data: QuizData;
export let selected_question: number;
export let timer_res: string;
export let answer_count: number; interface Props {
export let default_colors: string[]; quiz_data: QuizData;
selected_question: number;
timer_res: string;
answer_count: number;
default_colors: string[];
}
let {
quiz_data,
selected_question,
timer_res = $bindable(),
answer_count,
default_colors
}: Props = $props();
const { t } = getLocalization(); const { t } = getLocalization();
let circular_progress = 0; let circular_progress = $state(0);
$: { run(() => {
try { try {
circular_progress = circular_progress =
1 - 1 -
@@ -33,7 +45,7 @@ SPDX-License-Identifier: MPL-2.0
} catch { } catch {
circular_progress = 0; circular_progress = 0;
} }
} });
</script> </script>
<div class="flex flex-col justify-center w-screen h-1/6"> <div class="flex flex-col justify-center w-screen h-1/6">
@@ -42,7 +54,7 @@ SPDX-License-Identifier: MPL-2.0
</h1> </h1>
<!-- <span class='text-center py-2 text-lg'>{$t('admin_page.time_left')}: {timer_res}</span>--> <!-- <span class='text-center py-2 text-lg'>{$t('admin_page.time_left')}: {timer_res}</span>-->
<div class="grid grid-cols-3 my-2"> <div class="grid grid-cols-3 my-2">
<span /> <span></span>
<div class="m-auto"> <div class="m-auto">
<CircularTimer <CircularTimer
bind:text={timer_res} bind:text={timer_res}
@@ -85,7 +97,7 @@ SPDX-License-Identifier: MPL-2.0
style="color: {get_foreground_color(answer.color ?? default_colors[i])}" style="color: {get_foreground_color(answer.color ?? default_colors[i])}"
>{answer.answer}</span >{answer.answer}</span
> >
<span class="pl-4 w-10" /> <span class="pl-4 w-10"></span>
</div> </div>
{/each} {/each}
</div> </div>
@@ -97,7 +109,7 @@ SPDX-License-Identifier: MPL-2.0
<span class="text-center text-2xl px-2 py-4 w-full text-black" <span class="text-center text-2xl px-2 py-4 w-full text-black"
>{answer.answer}</span >{answer.answer}</span
> >
<span class="pl-4 w-10" /> <span class="pl-4 w-10"></span>
</div> </div>
{/each} {/each}
</div> </div>
+58 -42
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import VotingResults from './voting_results.svelte'; import VotingResults from './voting_results.svelte';
import { flip } from 'svelte/animate'; import { flip } from 'svelte/animate';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
@@ -15,16 +17,19 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
export let data; interface Props {
export let question: Question; data: any;
question: Question;
new_data: Array<{
username: string;
answer: string;
right: boolean;
time_taken: number;
score: number;
}>;
}
export let new_data: Array<{ let { data = $bindable(), question, new_data }: Props = $props();
username: string;
answer: string;
right: boolean;
time_taken: number;
score: number;
}>;
function sortObjectbyValue(obj) { function sortObjectbyValue(obj) {
const ret = {}; const ret = {};
@@ -35,7 +40,7 @@ SPDX-License-Identifier: MPL-2.0
} }
// let data_by_username = {}; // let data_by_username = {};
let score_by_username = {}; let score_by_username = $state({});
const do_sth = () => { const do_sth = () => {
for (const i of new_data) { for (const i of new_data) {
@@ -43,13 +48,17 @@ SPDX-License-Identifier: MPL-2.0
} }
}; };
$: { run(() => {
new_data; new_data;
score_by_username; score_by_username;
do_sth(); do_sth();
} });
let player_names; let player_names = $derived(
Object.keys(data).sort(function (a, b) {
return data[b] - data[a];
})
);
if (JSON.stringify(data) === '{}') { if (JSON.stringify(data) === '{}') {
for (const i of new_data) { for (const i of new_data) {
@@ -57,12 +66,11 @@ SPDX-License-Identifier: MPL-2.0
} }
} }
$: data = sortObjectbyValue(data); run(() => {
$: player_names = Object.keys(data).sort(function (a, b) { data = sortObjectbyValue(data);
return data[b] - data[a];
}); });
let show_new_score_clicked = false; let show_new_score_clicked = $state(false);
const show_new_score = () => { const show_new_score = () => {
// for (let i = 0; i++; i < player_names.length) { // for (let i = 0; i++; i < player_names.length) {
@@ -98,34 +106,42 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex justify-center"> <div class="flex justify-center">
<div> <div>
<table class="table-auto text-xl"> <table class="table-auto text-xl">
<tr> <thead>
<th class="p-2 border-r border-r-black border-b-2 border-b-black" <tr>
>{$t('words.name')}</th <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 class="p-2 border-b-2 border-b-black"
<th in:fly={{ x: 300 }} class="p-2 border-b-2 border-b-black" >{$t('words.point', { count: 2 })}</th
>{$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>
{#if show_new_score_clicked} {#if show_new_score_clicked}
<td <th in:fly|global={{ x: 300 }} class="p-2 border-b-2 border-b-black"
in:fly={{ x: 300 }} >{$t('play_page.points_added')}
class:hidden={i > 3} </th>
class="p-2"
class:text-red-600={score_by_username[player] === 0 ||
score_by_username[player] === undefined}
>
+{score_by_username[player] ?? '0'}
</td>
{/if} {/if}
</tr> </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> </table>
</div> </div>
</div> </div>
+8 -4
View File
@@ -9,11 +9,15 @@ SPDX-License-Identifier: MPL-2.0
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import Pikaso from 'pikaso'; import Pikaso from 'pikaso';
export let question: Question; interface Props {
question: Question;
}
let canvas_el: HTMLDivElement | undefined; let { question }: Props = $props();
let canvas_el: HTMLDivElement | undefined = $state();
let canvas: Pikaso; let canvas: Pikaso;
let img_src = ''; let img_src = $state('');
onMount(() => { onMount(() => {
canvas = new Pikaso({ canvas = new Pikaso({
@@ -33,7 +37,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="w-full h-full"> <div class="w-full h-full">
<div class="hidden"> <div class="hidden">
<div bind:this={canvas_el} class="w-full h-full block" /> <div bind:this={canvas_el} class="w-full h-full block"></div>
</div> </div>
<div class="w-full h-full flex justify-center"> <div class="w-full h-full flex justify-center">
<img src={img_src} /> <img src={img_src} />
@@ -8,8 +8,12 @@ SPDX-License-Identifier: MPL-2.0
import type { Question } from '$lib/quiz_types'; import type { Question } from '$lib/quiz_types';
import { QuizQuestionType } from '$lib/quiz_types'; import { QuizQuestionType } from '$lib/quiz_types';
export let data; interface Props {
export let question: Question; data: any;
question: Question;
}
let { data, question }: Props = $props();
let quiz_answers = []; let quiz_answers = [];
let quiz_colors = []; let quiz_colors = [];
@@ -21,7 +25,7 @@ SPDX-License-Identifier: MPL-2.0
answer_correct.push(i.right); answer_correct.push(i.right);
} }
let sorted_data = {}; let sorted_data = $state({});
for (const i of quiz_answers) { for (const i of quiz_answers) {
sorted_data[i] = 0; sorted_data[i] = 0;
} }
@@ -45,7 +49,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex gap-12"> <div class="flex gap-12">
{#each quiz_answers as answer, i} {#each quiz_answers as answer, i}
<div <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] && class:shadow-blue-500={answer_correct[i] &&
question.type !== QuizQuestionType.VOTING} question.type !== QuizQuestionType.VOTING}
class:shadow-yellow-500={!answer_correct[i] && 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] data.length}rem; background-color: {quiz_colors[i]
? quiz_colors[i] ? quiz_colors[i]
: 'black'}" : 'black'}"
/> ></div>
{/each} {/each}
</div> </div>
<div class="flex gap-12"> <div class="flex gap-12">
+17 -7
View File
@@ -5,12 +5,18 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import Audio1 from '$lib/assets/music/1-128.mp3'; import Audio1 from '$lib/assets/music/1-128.mp3';
export let play = false; interface Props {
let volume = 100; play?: boolean;
}
const audio = new Audio(Audio1); let { play = $bindable(false) }: Props = $props();
let volume = $state(100);
const audio = $state(new Audio(Audio1));
const control_audio = (play_audio: boolean) => { const control_audio = (play_audio: boolean) => {
if (play_audio) { if (play_audio) {
audio.play(); audio.play();
@@ -19,14 +25,18 @@ SPDX-License-Identifier: MPL-2.0
audio.pause(); audio.pause();
} }
}; };
$: audio.volume = volume / 100; run(() => {
$: control_audio(play); audio.volume = volume / 100;
});
run(() => {
control_audio(play);
});
</script> </script>
<div class="fixed top-0 left-0"> <div class="fixed top-0 left-0">
{#if play} {#if play}
<button <button
on:click={() => { onclick={() => {
play = false; play = false;
}} }}
> >
@@ -48,7 +58,7 @@ SPDX-License-Identifier: MPL-2.0
</button> </button>
{:else} {:else}
<button <button
on:click={() => { onclick={() => {
play = true; play = true;
}} }}
> >
+24 -12
View File
@@ -5,24 +5,36 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let progress: number; import { run } from 'svelte/legacy';
export let text: string;
export let color: string;
let angle = 360 * progress;
$: angle = 360 * progress; interface Props {
$: console.log(angle); progress: number;
text: string;
color: string;
}
let { progress, text, color }: Props = $props();
let angle = $state(360 * progress);
run(() => {
angle = 360 * progress;
});
run(() => {
console.log(angle);
});
// Adapt the logic according to the approach // Adapt the logic according to the approach
let background = `radial-gradient(white 50%, transparent 51%), let background = $state(`radial-gradient(white 50%, transparent 51%),
conic-gradient(transparent 0deg ${angle}deg, gainsboro ${angle}deg 360deg), conic-gradient(transparent 0deg ${angle}deg, gainsboro ${angle}deg 360deg),
conic-gradient(green 0deg, green 90deg, green 180deg, green);`; conic-gradient(green 0deg, green 90deg, green 180deg, green);`);
$: background = `radial-gradient(white 50%, transparent 51%), run(() => {
conic-gradient(transparent 0deg ${angle}deg, gainsboro ${angle}deg 360deg), background = `radial-gradient(white 50%, transparent 51%),
conic-gradient(${color} 0deg, ${color} 90deg, ${color} 180deg, ${color});`; 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> </script>
<div id="progress-circle" style={cssVarStyles} class="transition-all text-4xl text-black"> <div id="progress-circle" style={cssVarStyles} class="transition-all text-4xl text-black">
+12 -6
View File
@@ -5,12 +5,18 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let question_count: number; interface Props {
export let final_results: Array<null> | Array<Array<PlayerAnswer>>; question_count: number;
final_results: Array<null> | Array<Array<PlayerAnswer>>;
}
let { question_count, final_results }: Props = $props();
interface PlayerAnswer { interface PlayerAnswer {
username: string; username: string;
@@ -18,8 +24,8 @@ SPDX-License-Identifier: MPL-2.0
right: string; right: string;
} }
let data_available = false; let data_available = $state(false);
let winners_arr; let winners_arr = $state();
const getWinnersSorted = () => { const getWinnersSorted = () => {
let winners = {}; let winners = {};
@@ -79,9 +85,9 @@ SPDX-License-Identifier: MPL-2.0
} }
}; };
let winners = getWinnersSorted(); let winners = getWinnersSorted();
$: { run(() => {
console.log(winners, winners_arr); console.log(winners, winners_arr);
} });
</script> </script>
<div> <div>
+31 -18
View File
@@ -5,6 +5,9 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run, createBubbler, preventDefault } from 'svelte/legacy';
const bubble = createBubbler();
import { socket } from '$lib/socket'; import { socket } from '$lib/socket';
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
@@ -15,13 +18,17 @@ SPDX-License-Identifier: MPL-2.0
import BrownButton from '$lib/components/buttons/brown.svelte'; import BrownButton from '$lib/components/buttons/brown.svelte';
const { t } = getLocalization(); const { t } = getLocalization();
export let game_pin: string;
export let game_mode;
export let username; interface Props {
let custom_field; game_pin: string;
let custom_field_value; game_mode: any;
let captcha_enabled; username: any;
}
let { game_pin = $bindable(), game_mode = $bindable(), username = $bindable() }: Props = $props();
let custom_field = $state();
let custom_field_value = $state();
let captcha_enabled = $state();
let hcaptchaSitekey = import.meta.env.VITE_HCAPTCHA; let hcaptchaSitekey = import.meta.env.VITE_HCAPTCHA;
@@ -105,10 +112,12 @@ SPDX-License-Identifier: MPL-2.0
} }
}; };
$: if (game_pin.length > 5) { run(() => {
console.log('Setting game pin'); if (game_pin.length > 5) {
set_game_pin(); console.log('Setting game pin');
} set_game_pin();
}
});
const setUsername = async () => { const setUsername = async () => {
if (username.length <= 3) { if (username.length <= 3) {
@@ -177,8 +186,12 @@ SPDX-License-Identifier: MPL-2.0
} }
}); });
$: console.log(game_pin, game_pin.length > 6); run(() => {
$: game_pin = game_pin.replace(/\D/g, ''); console.log(game_pin, game_pin.length > 6);
});
run(() => {
game_pin = game_pin.replace(/\D/g, '');
});
</script> </script>
<svelte:head> <svelte:head>
@@ -194,10 +207,10 @@ SPDX-License-Identifier: MPL-2.0
{#if game_pin === '' || game_pin.length < 6} {#if game_pin === '' || game_pin.length < 6}
<div class="flex flex-col justify-center align-center w-screen h-screen"> <div class="flex flex-col justify-center align-center w-screen h-screen">
<form on:submit|preventDefault class="flex-col flex justify-center align-center mx-auto"> <form onsubmit={preventDefault(bubble('submit'))} class="flex-col flex justify-center align-center mx-auto">
<h1 class="text-lg text-center">{$t('words.game_pin')}</h1> <h1 class="text-lg text-center">{$t('words.game_pin')}</h1>
<input <input
class="border border-gray-400 self-center text-center text-black ring-0 outline-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} bind:value={game_pin}
maxlength="6" maxlength="6"
inputmode="numeric" inputmode="numeric"
@@ -213,19 +226,19 @@ SPDX-License-Identifier: MPL-2.0
{:else} {:else}
<div class="flex flex-col justify-center align-center w-screen h-screen"> <div class="flex flex-col justify-center align-center w-screen h-screen">
<form <form
on:submit|preventDefault={setUsername} onsubmit={preventDefault(setUsername)}
class="flex-col flex justify-center align-center mx-auto" class="flex-col flex justify-center align-center mx-auto"
> >
<h1 class="text-lg text-center">{$t('words.username')}</h1> <h1 class="text-lg text-center">{$t('words.username')}</h1>
<input <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} bind:value={username}
maxlength="17" maxlength="17"
/> />
{#if custom_field} {#if custom_field}
<h1 class="text-lg text-center">{custom_field}</h1> <h1 class="text-lg text-center">{custom_field}</h1>
<input <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} bind:value={custom_field_value}
/> />
{/if} {/if}
@@ -244,4 +257,4 @@ SPDX-License-Identifier: MPL-2.0
data-sitekey={hcaptchaSitekey} data-sitekey={hcaptchaSitekey}
data-size="invisible" data-size="invisible"
data-theme="dark" data-theme="dark"
/> ></div>
+44 -29
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import type { Question } from '$lib/quiz_types'; import type { Question } from '$lib/quiz_types';
import { QuizQuestionType } from '$lib/quiz_types'; import { QuizQuestionType } from '$lib/quiz_types';
import { socket } from '$lib/socket'; import { socket } from '$lib/socket';
@@ -19,12 +21,23 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
export let question: Question; interface Props {
export let game_mode; question: Question;
export let question_index: string | number; game_mode: any;
export let solution; question_index: string | number;
solution: any;
}
$: console.log(question_index, question, 'hi!'); let {
question = $bindable(),
game_mode = $bindable(),
question_index,
solution
}: Props = $props();
run(() => {
console.log(question_index, question, 'hi!');
});
console.log(question); console.log(question);
if (question.type === undefined) { if (question.type === undefined) {
@@ -39,8 +52,8 @@ SPDX-License-Identifier: MPL-2.0
throw new Error('question_index must be a string or number'); throw new Error('question_index must be a string or number');
}*/ }*/
let timer_res = question.time; let timer_res = $state(question.time);
let selected_answer: string; let selected_answer: string = $state();
// Stop the timer if the question is answered // Stop the timer if the question is answered
const timer = (time: string) => { const timer = (time: string) => {
@@ -62,11 +75,11 @@ SPDX-License-Identifier: MPL-2.0
timer(question.time); timer(question.time);
$: { run(() => {
if (solution !== undefined) { if (solution !== undefined) {
timer_res = '0'; timer_res = '0';
} }
} });
const selectAnswer = (answer: string) => { const selectAnswer = (answer: string) => {
selected_answer = answer; selected_answer = answer;
@@ -90,9 +103,9 @@ SPDX-License-Identifier: MPL-2.0
}); });
}; };
let text_input = ''; let text_input = $state('');
let slider_value = [0]; let slider_value = $state([0]);
if (question.type === QuizQuestionType.RANGE) { if (question.type === QuizQuestionType.RANGE) {
slider_value[0] = (question.answers.max - question.answers.min) / 2 + question.answers.min; slider_value[0] = (question.answers.max - question.answers.min) / 2 + question.answers.min;
} }
@@ -119,15 +132,17 @@ SPDX-License-Identifier: MPL-2.0
_arr[b] = temp; _arr[b] = temp;
return _arr; return _arr;
}; };
$: set_answer_if_not_set_range(timer_res); run(() => {
let circular_progress = 0; set_answer_if_not_set_range(timer_res);
$: { });
let circular_progress = $state(0);
run(() => {
try { try {
circular_progress = 1 - ((100 / question.time) * parseInt(timer_res)) / 100; circular_progress = 1 - ((100 / question.time) * parseInt(timer_res)) / 100;
} catch { } catch {
circular_progress = 0; circular_progress = 0;
} }
} });
const get_div_height = (): string => { const get_div_height = (): string => {
if (game_mode === 'normal') { if (game_mode === 'normal') {
@@ -140,7 +155,9 @@ SPDX-License-Identifier: MPL-2.0
return '100'; return '100';
} }
}; };
$: console.log(slider_value, 'values'); run(() => {
console.log(slider_value, 'values');
});
const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58']; const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58'];
</script> </script>
@@ -188,7 +205,7 @@ SPDX-License-Identifier: MPL-2.0
answer.color ?? default_colors[i] answer.color ?? default_colors[i]
)}" )}"
disabled={selected_answer !== undefined} disabled={selected_answer !== undefined}
on:click={() => selectAnswer(answer.answer)} onclick={() => selectAnswer(answer.answer)}
> >
{#if game_mode === 'kahoot'} {#if game_mode === 'kahoot'}
<img <img
@@ -207,13 +224,12 @@ SPDX-License-Identifier: MPL-2.0
<span <span
class="fixed top-0 bg-red-500 h-8 transition-all" class="fixed top-0 bg-red-500 h-8 transition-all"
style="width: {(100 / parseInt(question.time)) * parseInt(timer_res)}vw" style="width: {(100 / parseInt(question.time)) * parseInt(timer_res)}vw"
/> ></span>
{#await import('svelte-range-slider-pips')} {#await import('svelte-range-slider-pips')}
<Spinner /> <Spinner />
{:then c} {:then c}
<div class:pointer-events-none={selected_answer !== undefined} class="mt-24"> <div class:pointer-events-none={selected_answer !== undefined} class="mt-24">
<svelte:component <c.default
this={c.default}
bind:values={slider_value} bind:values={slider_value}
bind:min={question.answers.min} bind:min={question.answers.min}
bind:max={question.answers.max} bind:max={question.answers.max}
@@ -238,7 +254,7 @@ SPDX-License-Identifier: MPL-2.0
<span <span
class="fixed top-0 bg-red-500 h-8 transition-all" class="fixed top-0 bg-red-500 h-8 transition-all"
style="width: {(100 / parseInt(question.time)) * parseInt(timer_res)}vw" style="width: {(100 / parseInt(question.time)) * parseInt(timer_res)}vw"
/> ></span>
<div class="flex justify-center mt-10"> <div class="flex justify-center mt-10">
<p class="text-black dark:text-white">Enter your answer</p> <p class="text-black dark:text-white">Enter your answer</p>
</div> </div>
@@ -247,7 +263,7 @@ SPDX-License-Identifier: MPL-2.0
type="text" type="text"
bind:value={text_input} bind:value={text_input}
disabled={selected_answer} 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> </div>
@@ -286,7 +302,7 @@ SPDX-License-Identifier: MPL-2.0
<span <span
class="fixed top-0 bg-red-500 h-8 transition-all" class="fixed top-0 bg-red-500 h-8 transition-all"
style="width: {(100 / parseInt(question.time)) * parseInt(timer_res)}vw" style="width: {(100 / parseInt(question.time)) * parseInt(timer_res)}vw"
/> ></span>
<div class="flex flex-col w-full h-full gap-4 px-4 py-6 mt-10"> <div class="flex flex-col w-full h-full gap-4 px-4 py-6 mt-10">
{#each question.answers as answer, i (answer.id)} {#each question.answers as answer, i (answer.id)}
<div <div
@@ -295,10 +311,10 @@ SPDX-License-Identifier: MPL-2.0
style="background-color: {answer.color ?? '#b07156'}" style="background-color: {answer.color ?? '#b07156'}"
> >
<button <button
on:click={() => { onclick={() => {
question.answers = swapArrayElements(question.answers, i, i - 1); question.answers = swapArrayElements(question.answers, i, i - 1);
}} }}
class="disabled:opacity-50 transition shadow-lg bg-black 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" type="button"
disabled={i === 0 || selected_answer} 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> <p class="w-full text-center p-2 text-2xl">{answer.answer}</p>
<button <button
on:click={() => { onclick={() => {
question.answers = swapArrayElements(question.answers, i, i + 1); question.answers = swapArrayElements(question.answers, i, i + 1);
}} }}
class="disabled:opacity-50 transition shadow-lg bg-black 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" type="button"
disabled={i === question.answers.length - 1 || selected_answer} disabled={i === question.answers.length - 1 || selected_answer}
> >
@@ -363,8 +379,7 @@ SPDX-License-Identifier: MPL-2.0
{#await import('./questions/check.svelte')} {#await import('./questions/check.svelte')}
<Spinner /> <Spinner />
{:then c} {:then c}
<svelte:component <c.default
this={c.default}
bind:question bind:question
bind:selected_answer bind:selected_answer
bind:game_mode bind:game_mode
+17 -7
View File
@@ -11,14 +11,24 @@ SPDX-License-Identifier: MPL-2.0
import CircularTimer from '$lib/play/circular_progress.svelte'; import CircularTimer from '$lib/play/circular_progress.svelte';
// import CircularTimer from '$lib/play/circular_progress.svelte'; // import CircularTimer from '$lib/play/circular_progress.svelte';
const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58']; const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58'];
export let question: Question;
export let selected_answer = '';
export let game_mode;
export let timer_res;
export let circular_progress; interface Props {
let _selected_answers = [false, false, false, false]; question: Question;
selected_answer?: string;
game_mode: any;
timer_res: any;
circular_progress: any;
}
let {
question,
selected_answer = $bindable(''),
game_mode,
timer_res = $bindable(),
circular_progress = $bindable()
}: Props = $props();
let _selected_answers = $state([false, false, false, false]);
const selectAnswer = (i: number) => { const selectAnswer = (i: number) => {
_selected_answers[i] = !_selected_answers[i]; _selected_answers[i] = !_selected_answers[i];
@@ -59,7 +69,7 @@ SPDX-License-Identifier: MPL-2.0
default_colors[i]}; color: {get_foreground_color( default_colors[i]}; color: {get_foreground_color(
answer.color ?? default_colors[i] answer.color ?? default_colors[i]
)}" )}"
on:click={() => selectAnswer(i)} onclick={() => selectAnswer(i)}
class:opacity-100={_selected_answers[i]} class:opacity-100={_selected_answers[i]}
class:opacity-50={!_selected_answers[i]} class:opacity-50={!_selected_answers[i]}
> >
+23 -13
View File
@@ -5,15 +5,9 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let scores; import { run } from 'svelte/legacy';
export let question_results: Array<{
username: string;
answer: string;
right: boolean;
time_taken: number;
score: number;
}>;
function sortObjectbyValue(obj) { function sortObjectbyValue(obj) {
const ret = {}; const ret = {};
@@ -23,8 +17,20 @@ SPDX-License-Identifier: MPL-2.0
return ret; return ret;
} }
export let username; interface Props {
let score_by_username = {}; scores: any;
question_results: Array<{
username: string;
answer: string;
right: boolean;
time_taken: number;
score: number;
}>;
username: any;
}
let { scores = $bindable(), question_results, username }: Props = $props();
let score_by_username = $state({});
if (JSON.stringify(scores) === '{}') { if (JSON.stringify(scores) === '{}') {
for (const i of question_results) { for (const i of question_results) {
@@ -32,12 +38,16 @@ SPDX-License-Identifier: MPL-2.0
} }
} }
$: console.log(score_by_username, scores, 'dieter'); run(() => {
console.log(score_by_username, scores, 'dieter');
});
for (const i of question_results) { for (const i of question_results) {
score_by_username[i.username] = i.score; score_by_username[i.username] = i.score;
} }
$: scores = sortObjectbyValue(scores); run(() => {
scores = sortObjectbyValue(scores);
});
const do_sth = () => { const do_sth = () => {
for (const i of Object.keys(score_by_username)) { for (const i of Object.keys(score_by_username)) {
@@ -52,7 +62,7 @@ SPDX-License-Identifier: MPL-2.0
<div> <div>
<div class="flex justify-center h-screen"> <div class="flex justify-center h-screen">
<div class="m-auto flex flex-col"> <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'} +{score_by_username[username] ?? '0'}
</p> </p>
<p>Total score: {scores[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'; import Spinner from '$lib/Spinner.svelte';
const { t } = getLocalization(); const { t } = getLocalization();
export let results: Array<Answer>; interface Props {
export let game_data: QuizData; results: Array<Answer>;
export let solution: Question; game_data: QuizData;
export let question_index: string; solution: Question;
let data_store = {}; question_index: string;
}
let {
results,
game_data,
solution = $bindable(),
question_index
}: Props = $props();
let data_store = $state({});
const question = solution.answers; const question = solution.answers;
for (let i = 0; i < question.length; i++) { for (let i = 0; i < question.length; i++) {
@@ -26,7 +35,7 @@ SPDX-License-Identifier: MPL-2.0
data_store[results[i].answer] += 1; data_store[results[i].answer] += 1;
} }
let slider_values = [solution.answers.min_correct ?? 0, solution.answers.max_correct ?? 0]; let slider_values = $state([solution.answers.min_correct ?? 0, solution.answers.max_correct ?? 0]);
console.log(slider_values, solution.answers); console.log(slider_values, solution.answers);
</script> </script>
@@ -99,8 +108,7 @@ SPDX-License-Identifier: MPL-2.0
<Spinner /> <Spinner />
{:then c} {:then c}
<div class="grayscale pointer-events-none w-full"> <div class="grayscale pointer-events-none w-full">
<svelte:component <c.default
this={c.default}
bind:values={slider_values} bind:values={slider_values}
bind:min={solution.answers.min} bind:min={solution.answers.min}
bind:max={solution.answers.max} bind:max={solution.answers.max}
+7 -3
View File
@@ -5,9 +5,13 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let title: string; interface Props {
export let description: string; title: string;
export let cover_image: string | undefined; description: string;
cover_image: string | undefined;
}
let { title, description, cover_image }: Props = $props();
</script> </script>
<div class="flex flex-col justify-center w-screen h-screen"> <div class="flex flex-col justify-center w-screen h-screen">
+34 -28
View File
@@ -14,13 +14,17 @@ SPDX-License-Identifier: MPL-2.0
import BrownButton from '$lib/components/buttons/brown.svelte'; import BrownButton from '$lib/components/buttons/brown.svelte';
import MediaComponent from '$lib/editor/MediaComponent.svelte'; import MediaComponent from '$lib/editor/MediaComponent.svelte';
export let question: Question; interface Props {
question: Question;
}
let { question = $bindable() }: Props = $props();
const { t } = getLocalization(); const { t } = getLocalization();
let selected_answer = undefined; let selected_answer = $state(undefined);
let timer_res = question.time; let timer_res = $state(question.time);
let show_results = false; let show_results = $state(false);
// Stop the timer if the question is answered // Stop the timer if the question is answered
const timer = (time: string) => { const timer = (time: string) => {
@@ -37,16 +41,16 @@ SPDX-License-Identifier: MPL-2.0
timer_res = seconds.toString(); timer_res = seconds.toString();
}, 1000); }, 1000);
}; };
let slider_value = [0]; let slider_value = $state([0]);
if (question.type === QuizQuestionType.RANGE) { if (question.type === QuizQuestionType.RANGE) {
slider_value[0] = (question.answers.max - question.answers.min) / 2 + question.answers.min; slider_value[0] = (question.answers.max - question.answers.min) / 2 + question.answers.min;
} }
let slider_values = [question.answers.min_correct ?? 0, question.answers.max_correct ?? 0]; let slider_values = $state([question.answers.min_correct ?? 0, question.answers.max_correct ?? 0]);
let text_input; let text_input = $state();
timer(question.time); timer(question.time);
let check_choice_selected = [false, false, false, false]; let check_choice_selected = $state([false, false, false, false]);
function shuffleArray(a) { function shuffleArray(a) {
for (let i = a.length - 1; i > 0; i--) { for (let i = a.length - 1; i > 0; i--) {
@@ -75,7 +79,7 @@ SPDX-License-Identifier: MPL-2.0
shuffleArray(question.answers); shuffleArray(question.answers);
} }
let order_corrected = false; let order_corrected = $state(false);
const select_complex_answer = () => { const select_complex_answer = () => {
/* const correct_order_ids = [] /* const correct_order_ids = []
for (const e of original_order) { for (const e of original_order) {
@@ -91,7 +95,11 @@ SPDX-License-Identifier: MPL-2.0
}; };
</script> </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> <h1 class="text-3xl text-center">{@html question.question}</h1>
{#if question.image !== null} {#if question.image !== null}
<div> <div>
@@ -124,7 +132,7 @@ SPDX-License-Identifier: MPL-2.0
disabled={selected_answer !== undefined || timer_res === '0'} disabled={selected_answer !== undefined || timer_res === '0'}
type="button" type="button"
class="p-2 rounded-lg flex justify-center w-full transition bg-amber-300 my-5 disabled:grayscale text-black" class="p-2 rounded-lg flex justify-center w-full transition bg-amber-300 my-5 disabled:grayscale text-black"
on:click={() => { onclick={() => {
selected_answer = i; selected_answer = i;
timer_res = '0'; timer_res = '0';
}}>{answer.answer}</button }}>{answer.answer}</button
@@ -134,7 +142,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
class="bg-orange-500 p-2 rounded-lg flex justify-center w-full transition my-5 text-black" class="bg-orange-500 p-2 rounded-lg flex justify-center w-full transition my-5 text-black"
type="button" type="button"
on:click={() => { onclick={() => {
show_results = true; show_results = true;
}}>{$t('admin_page.get_results')}</button }}>{$t('admin_page.get_results')}</button
> >
@@ -147,8 +155,7 @@ SPDX-License-Identifier: MPL-2.0
<Spinner /> <Spinner />
{:then c} {:then c}
<div class="grayscale pointer-events-none w-full"> <div class="grayscale pointer-events-none w-full">
<svelte:component <c.default
this={c.default}
bind:values={slider_values} bind:values={slider_values}
bind:min={question.answers.min} bind:min={question.answers.min}
bind:max={question.answers.max} bind:max={question.answers.max}
@@ -163,8 +170,7 @@ SPDX-License-Identifier: MPL-2.0
<Spinner /> <Spinner />
{:then c} {:then c}
<div class:pointer-events-none={selected_answer !== undefined}> <div class:pointer-events-none={selected_answer !== undefined}>
<svelte:component <c.default
this={c.default}
bind:values={slider_value} bind:values={slider_value}
bind:min={question.answers.min} bind:min={question.answers.min}
bind:max={question.answers.max} bind:max={question.answers.max}
@@ -178,7 +184,7 @@ SPDX-License-Identifier: MPL-2.0
type="button" type="button"
class="w-1/3 text-3xl bg-[#B07156] my-2 disabled:opacity-60 rounded-lg p-1 transition" class="w-1/3 text-3xl bg-[#B07156] my-2 disabled:opacity-60 rounded-lg p-1 transition"
disabled={selected_answer !== undefined} disabled={selected_answer !== undefined}
on:click={() => { onclick={() => {
selected_answer = slider_value[0]; selected_answer = slider_value[0];
timer_res = '0'; timer_res = '0';
}} }}
@@ -193,7 +199,7 @@ SPDX-License-Identifier: MPL-2.0
type="button" type="button"
disabled={selected_answer !== undefined || timer_res === '0'} disabled={selected_answer !== undefined || timer_res === '0'}
class="p-2 rounded-lg flex justify-center w-full transition bg-amber-300 my-5 disabled:grayscale text-black" class="p-2 rounded-lg flex justify-center w-full transition bg-amber-300 my-5 disabled:grayscale text-black"
on:click={() => { onclick={() => {
selected_answer = i; selected_answer = i;
timer_res = '0'; timer_res = '0';
}}>{answer.answer}</button }}>{answer.answer}</button
@@ -207,7 +213,7 @@ SPDX-License-Identifier: MPL-2.0
<Spinner my={false} /> <Spinner my={false} />
{:then c} {:then c}
<div class="max-h-[90%] max-w-[90%]"> <div class="max-h-[90%] max-w-[90%]">
<svelte:component this={c.default} bind:question /> <c.default bind:question />
</div> </div>
{/await} {/await}
{:else if question.type === QuizQuestionType.TEXT} {:else if question.type === QuizQuestionType.TEXT}
@@ -224,7 +230,7 @@ SPDX-License-Identifier: MPL-2.0
<input <input
type="text" type="text"
bind:value={text_input} 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>
<div class="flex justify-center"> <div class="flex justify-center">
@@ -232,7 +238,7 @@ SPDX-License-Identifier: MPL-2.0
type="button" type="button"
disabled={!text_input} disabled={!text_input}
class="w-1/3 text-3xl bg-[#B07156] my-2 disabled:opacity-60 rounded-lg p-1 transition" class="w-1/3 text-3xl bg-[#B07156] my-2 disabled:opacity-60 rounded-lg p-1 transition"
on:click={() => { onclick={() => {
selected_answer = text_input; selected_answer = text_input;
timer_res = '0'; timer_res = '0';
}}>{$t('words.submit')}</button }}>{$t('words.submit')}</button
@@ -248,10 +254,10 @@ SPDX-License-Identifier: MPL-2.0
style="background-color: {answer.color ?? '#b07156'}" style="background-color: {answer.color ?? '#b07156'}"
> >
<button <button
on:click={() => { onclick={() => {
question.answers = swapArrayElements(question.answers, i, i - 1); question.answers = swapArrayElements(question.answers, i, i - 1);
}} }}
class="disabled:opacity-50 transition shadow-lg bg-black 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" type="button"
disabled={i === 0 || order_corrected} 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> <p class="w-full text-center p-2 text-2xl">{answer.answer}</p>
<button <button
on:click={() => { onclick={() => {
question.answers = swapArrayElements(question.answers, i, i + 1); question.answers = swapArrayElements(question.answers, i, i + 1);
}} }}
class="disabled:opacity-50 transition shadow-lg bg-black 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" type="button"
disabled={i === question.answers.length - 1 || order_corrected} 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" class="bg-[#B07156] hover:bg-amber-700 text-white font-bold py-2 px-4 rounded-lg mt-2 transition w-full"
type="button" type="button"
disabled={timer_res === '0'} disabled={timer_res === '0'}
on:click={() => { onclick={() => {
select_complex_answer(); select_complex_answer();
}} }}
> >
@@ -334,7 +340,7 @@ SPDX-License-Identifier: MPL-2.0
disabled={selected_answer !== undefined || timer_res === '0'} disabled={selected_answer !== undefined || timer_res === '0'}
class="p-2 rounded-lg flex justify-center w-full transition bg-amber-300 my-5 disabled:grayscale text-black opacity-50" class="p-2 rounded-lg flex justify-center w-full transition bg-amber-300 my-5 disabled:grayscale text-black opacity-50"
class:opacity-100={check_choice_selected[i]} class:opacity-100={check_choice_selected[i]}
on:click={() => { onclick={() => {
check_choice_selected[i] = !check_choice_selected[i]; check_choice_selected[i] = !check_choice_selected[i];
}}>{answer.answer}</button }}>{answer.answer}</button
> >
@@ -349,7 +355,7 @@ SPDX-License-Identifier: MPL-2.0
<button <button
type="button" type="button"
class="bg-orange-500 p-2 rounded-lg flex justify-center w-full transition my-5 text-black" class="bg-orange-500 p-2 rounded-lg flex justify-center w-full transition my-5 text-black"
on:click={() => { onclick={() => {
show_results = true; show_results = true;
}}>{$t('admin_page.get_results')}</button }}>{$t('admin_page.get_results')}</button
> >
@@ -5,27 +5,29 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
export let data;
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
let { data = $bindable() } = $props();
const { t } = getLocalization(); const { t } = getLocalization();
</script> </script>
<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="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="h-fit bg-gray-300 rounded-t-lg dark:bg-gray-500">
<div class="flex align-middle p-4 gap-3"> <div class="flex align-middle p-4 gap-3">
<span <span
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-red-400 transition" class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-red-400 transition"
/> ></span>
<span <span
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-yellow-400 transition" class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-yellow-400 transition"
/> ></span>
<span <span
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-green-400 transition" class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-green-400 transition"
/> ></span>
</div> </div>
</div> </div>
<div class="dark:bg-gray-700"> <div class="dark:bg-gray-700">
@@ -50,9 +52,9 @@ SPDX-License-Identifier: MPL-2.0
src="/api/v1/storage/download/{data.cover_image}" src="/api/v1/storage/download/{data.cover_image}"
alt="not available" alt="not available"
class="max-h-72 h-auto w-auto" class="max-h-72 h-auto w-auto"
on:contextmenu|preventDefault={() => { oncontextmenu={preventDefault(() => {
data.cover_image = ''; data.cover_image = '';
}} })}
/> />
</div> </div>
{/if} {/if}
@@ -10,7 +10,11 @@ SPDX-License-Identifier: MPL-2.0
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
const { t } = getLocalization(); const { t } = getLocalization();
export let type: QuizTivityTypes | undefined; interface Props {
type: QuizTivityTypes | undefined;
}
let { type = $bindable() }: Props = $props();
const PageTypes = [ const PageTypes = [
/* { /* {
@@ -41,18 +45,18 @@ SPDX-License-Identifier: MPL-2.0
</script> </script>
<div <div
class="fixed top-0 left-0 z-50 bg-black bg-opacity-50 flex w-screen h-screen" class="fixed top-0 left-0 z-50 bg-black/50 flex w-screen h-screen"
transition:fade={{ duration: 100 }} transition:fade|global={{ duration: 100 }}
> >
<div class="m-auto w-5/6 h-5/6"> <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> <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"> <div class="grid grid-cols-4 gap-4 overflow-y-scroll">
{#each PageTypes as pt} {#each PageTypes as pt}
<div class="rounded p-6 border-[#B07156] border"> <div class="rounded-sm p-6 border-[#B07156] border">
<button <button
class="text-xl text-black dark:text-white" class="text-xl text-black dark:text-white"
on:click={() => { onclick={() => {
type = pt.type; type = pt.type;
}}>{pt.name}</button }}>{pt.name}</button
> >
@@ -9,7 +9,11 @@ SPDX-License-Identifier: MPL-2.0
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import BrownButton from '$lib/components/buttons/brown.svelte'; import BrownButton from '$lib/components/buttons/brown.svelte';
export let data: Abcd | undefined; interface Props {
data: Abcd | undefined;
}
let { data = $bindable() }: Props = $props();
if (!data) { if (!data) {
data = { data = {
@@ -24,22 +28,22 @@ SPDX-License-Identifier: MPL-2.0
<div> <div>
<div class="flex justify-center"> <div class="flex justify-center">
<input <input
class="bg-transparent outline-none text-3xl text-center" class="bg-transparent outline-hidden text-3xl text-center"
placeholder="Enter question here..." placeholder="Enter question here..."
bind:value={data.question} bind:value={data.question}
/> />
</div> </div>
<div class="grid grid-cols-2 m-4 gap-4"> <div class="grid grid-cols-2 m-4 gap-4">
{#each data.answers as answer} {#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 <input
bind:value={answer.answer} 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" placeholder="Enter answer here"
/> />
<button <button
type="button" type="button"
on:click={() => { onclick={() => {
answer.correct = !answer.correct; answer.correct = !answer.correct;
}} }}
> >
@@ -78,7 +82,7 @@ SPDX-License-Identifier: MPL-2.0
</div> </div>
{/each} {/each}
{#if data.answers.length < 4} {#if data.answers.length < 4}
<div class="rounded p-6 bg-gray-700"> <div class="rounded-sm p-6 bg-gray-700">
<BrownButton <BrownButton
on:click={() => { on:click={() => {
data.answers = [...data.answers, { ...{ answer: '', correct: false } }]; data.answers = [...data.answers, { ...{ answer: '', correct: false } }];
@@ -7,9 +7,13 @@ SPDX-License-Identifier: MPL-2.0
<script lang="ts"> <script lang="ts">
import type { Abcd } from '$lib/quiztivity/types'; import type { Abcd } from '$lib/quiztivity/types';
export let data: Abcd | undefined; interface Props {
data: Abcd | undefined;
}
let selected_answer: number | undefined; let { data }: Props = $props();
let selected_answer: number | undefined = $state();
const select_answer = (i: number) => { const select_answer = (i: number) => {
selected_answer = i; selected_answer = i;
@@ -23,8 +27,8 @@ SPDX-License-Identifier: MPL-2.0
<div class="grid grid-cols-1 lg:grid-cols-2 m-4 gap-4"> <div class="grid grid-cols-1 lg:grid-cols-2 m-4 gap-4">
{#each data.answers as answer, i} {#each data.answers as answer, i}
<button <button
class="rounded p-6 bg-gray-700 flex transition-all" class="rounded-sm p-6 bg-gray-700 flex transition-all"
on:click={() => { onclick={() => {
select_answer(i); select_answer(i);
}} }}
class:opacity-50={selected_answer !== undefined && !answer.correct} class:opacity-50={selected_answer !== undefined && !answer.correct}
@@ -5,11 +5,17 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import type { Markdown } from '$lib/quiztivity/types'; import type { Markdown } from '$lib/quiztivity/types';
import { marked } from 'marked'; import { marked } from 'marked';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
export let data: Markdown | undefined; interface Props {
data: Markdown | undefined;
}
let { data = $bindable() }: Props = $props();
if (!data) { if (!data) {
data = { data = {
@@ -17,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> </script>
<div class="w-full h-[70vh] flex flex-row p-4 gap-4"> <div class="w-full h-[70vh] flex flex-row p-4 gap-4">
<textarea <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} bind:value={data.markdown}
placeholder="Enter your markdown here!" placeholder="Enter your markdown here!"
/> ></textarea>
<div class="w-full"> <div class="w-full">
<div <div
class="aspect-video prose max-w-none border-[#B07156] border-2 rounded 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} {@html rendered_html}
</div> </div>
@@ -5,16 +5,24 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import type { Markdown } from '$lib/quiztivity/types'; import type { Markdown } from '$lib/quiztivity/types';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import { marked } from 'marked'; import { marked } from 'marked';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
export let data: Markdown | undefined; interface Props {
data: Markdown | undefined;
}
let rendered_html = ''; let { data }: Props = $props();
$: rendered_html = browser ? DOMPurify.sanitize(marked.parse(data.markdown ?? '')) : ''; let rendered_html = $state('');
run(() => {
rendered_html = browser ? DOMPurify.sanitize(marked.parse(data.markdown ?? '')) : '';
});
</script> </script>
<div class="prose dark:prose-invert"> <div class="prose dark:prose-invert">
@@ -12,11 +12,15 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
export let data: Memory | undefined; interface Props {
let new_pair_data = { data: Memory | undefined;
}
let { data = $bindable() }: Props = $props();
let new_pair_data = $state({
text_1: '', text_1: '',
text_2: '' text_2: ''
}; });
if (!data) { if (!data) {
data = { data = {
@@ -61,32 +65,32 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex justify-center"> <div class="flex justify-center">
<div class="grid grid-cols-4 w-11/12 gap-4"> <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> <h2 class="text-center">{$t('quiztivity.memory.editor.add_card')}</h2>
<div class="grid grid-cols-2 py-2"> <div class="grid grid-cols-2 py-2">
<div class="px-2 flex flex-col gap-2"> <div class="px-2 flex flex-col gap-2">
<textarea <textarea
type="text" 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" rows="3"
contenteditable="true" contenteditable="true"
bind:value={new_pair_data.text_1} bind:value={new_pair_data.text_1}
/> ></textarea>
<div class="flex justify-center"> <div class="flex justify-center">
<span class="h-0.5 bg-black block w-11/12" /> <span class="h-0.5 bg-black block w-11/12"></span>
</div> </div>
<BrownButton>{$t('quiztivity.memory.editor.upload_image')}</BrownButton> <BrownButton>{$t('quiztivity.memory.editor.upload_image')}</BrownButton>
</div> </div>
<div class="px-2 flex flex-col gap-2"> <div class="px-2 flex flex-col gap-2">
<textarea <textarea
type="text" 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" rows="3"
contenteditable="true" contenteditable="true"
bind:value={new_pair_data.text_2} bind:value={new_pair_data.text_2}
/> ></textarea>
<div class="flex justify-center"> <div class="flex justify-center">
<span class="h-0.5 bg-black block w-11/12" /> <span class="h-0.5 bg-black block w-11/12"></span>
</div> </div>
<BrownButton>{$t('quiztivity.memory.editor.upload_image')}</BrownButton> <BrownButton>{$t('quiztivity.memory.editor.upload_image')}</BrownButton>
</div> </div>
@@ -99,7 +103,7 @@ SPDX-License-Identifier: MPL-2.0
</div> </div>
{#each data.cards as card_pair, i (card_pair[0].id)} {#each data.cards as card_pair, i (card_pair[0].id)}
<div <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 }} animate:flip={{ duration: 200 }}
> >
<div class="grid grid-cols-2 py-2 h-full"> <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'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let data: Memory | undefined; interface Props {
data: Memory | undefined;
}
let { data }: Props = $props();
const shuffle = <Type>(a: Array<Type>): Array<Type> => { const shuffle = <Type>(a: Array<Type>): Array<Type> => {
for (let i = a.length - 1; i > 0; i--) { for (let i = a.length - 1; i > 0; i--) {
@@ -19,7 +23,7 @@ SPDX-License-Identifier: MPL-2.0
return a; return a;
}; };
let card_opened = {}; let card_opened = $state({});
const get_all_cards_as_single_array = (): MemoryCard[] => { const get_all_cards_as_single_array = (): MemoryCard[] => {
console.log('data', data.cards); console.log('data', data.cards);
@@ -43,7 +47,7 @@ SPDX-License-Identifier: MPL-2.0
const found_cards: string[] = []; const found_cards: string[] = [];
let opened_active_cards: string[] = []; let opened_active_cards: string[] = [];
let try_count = 0; let try_count = $state(0);
let game_finished = false; let game_finished = false;
const flip_card = (id: string) => { const flip_card = (id: string) => {
if (found_cards.includes(id) || game_finished) { if (found_cards.includes(id) || game_finished) {
@@ -82,8 +86,8 @@ SPDX-License-Identifier: MPL-2.0
<div class="grid lg:grid-cols-6 grid-cols-2 gap-2 m-4"> <div class="grid lg:grid-cols-6 grid-cols-2 gap-2 m-4">
{#each random_card_order as card} {#each random_card_order as card}
<button <button
class="aspect-square flex border-[#B07156] border-2 rounded" class="aspect-square flex border-[#B07156] border-2 rounded-sm"
on:click={() => { onclick={() => {
flip_card(card.id); flip_card(card.id);
}} }}
> >
+18 -10
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import type { Data } from './types'; import type { Data } from './types';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import BrownButton from '$lib/components/buttons/brown.svelte'; import BrownButton from '$lib/components/buttons/brown.svelte';
@@ -21,13 +23,17 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let data: Data; interface Props {
export let saving: boolean; data: Data;
saving: boolean;
}
let selected_slide = null; let { data = $bindable(), saving }: Props = $props();
let opened_slide = null;
let selected_type = undefined; let selected_slide = $state(null);
let shares_menu_open = false; let opened_slide = $state(null);
let selected_type = $state(undefined);
let shares_menu_open = $state(false);
for (let i = 0; i < data.pages.length; i++) { for (let i = 0; i < data.pages.length; i++) {
const id = (Math.random() + 1).toString(36).substring(7); const id = (Math.random() + 1).toString(36).substring(7);
@@ -42,7 +48,9 @@ SPDX-License-Identifier: MPL-2.0
data.pages.push({ title: undefined, data: undefined, type, id }); data.pages.push({ title: undefined, data: undefined, type, id });
opened_slide = data.pages.length - 1; opened_slide = data.pages.length - 1;
}; };
$: handle_slide_add(selected_type); run(() => {
handle_slide_add(selected_type);
});
const delete_slide = () => { const delete_slide = () => {
console.log(selected_slide); console.log(selected_slide);
@@ -81,10 +89,10 @@ SPDX-License-Identifier: MPL-2.0
> >
</div> </div>
{:else} {:else}
<span /> <span></span>
{/if} {/if}
<input <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')} placeholder={$t('quiztivity.editor.title_placeholder')}
bind:value={data.title} 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"> <div class="grid grid-cols-6 gap-6 w-11/12">
{#each data.pages as page, i (page.id)} {#each data.pages as page, i (page.id)}
<div <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 }} animate:flip={{ duration: 200 }}
> >
<p class="m-auto">{page.type}</p> <p class="m-auto">{page.type}</p>
@@ -5,6 +5,8 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { preventDefault } from 'svelte/legacy';
import BrownButton from '$lib/components/buttons/brown.svelte'; import BrownButton from '$lib/components/buttons/brown.svelte';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import Spinner from '$lib/Spinner.svelte'; import Spinner from '$lib/Spinner.svelte';
@@ -16,9 +18,13 @@ SPDX-License-Identifier: MPL-2.0
import { fade, fly } from 'svelte/transition'; import { fade, fly } from 'svelte/transition';
const { t } = getLocalization(); const { t } = getLocalization();
export let open = false; interface Props {
export let id; open?: boolean;
let popover_open = false; id: any;
}
let { open = $bindable(false), id }: Props = $props();
let popover_open = $state(false);
const load_shares = async (): Promise<{ const load_shares = async (): Promise<{
id: string; id: string;
name?: string; name?: string;
@@ -73,8 +79,8 @@ SPDX-License-Identifier: MPL-2.0
onMount(() => { onMount(() => {
document.body.addEventListener('keydown', close_start_game_if_esc_is_pressed); document.body.addEventListener('keydown', close_start_game_if_esc_is_pressed);
}); });
let never_expires_checked = true; let never_expires_checked = $state(true);
let selected_date = undefined; let selected_date = $state(undefined);
const create_share = async () => { const create_share = async () => {
if (!selected_date && !never_expires_checked) { if (!selected_date && !never_expires_checked) {
return; return;
@@ -95,7 +101,7 @@ SPDX-License-Identifier: MPL-2.0
}); });
loaded_shares = load_shares(); loaded_shares = load_shares();
}; };
let loaded_shares = load_shares(); let loaded_shares = $state(load_shares());
const delete_share = async (id: string) => { const delete_share = async (id: string) => {
if (!confirm('Do you really want to delete this Share?')) { if (!confirm('Do you really want to delete this Share?')) {
@@ -117,17 +123,17 @@ SPDX-License-Identifier: MPL-2.0
return false; return false;
} }
}; };
let add_shares_open = false; let add_shares_open = $state(false);
</script> </script>
<SmallPopover bind:open={popover_open} type={PopoverTypes.Copy} /> <SmallPopover bind:open={popover_open} type={PopoverTypes.Copy} />
<div <div
class="fixed w-full h-full top-0 flex bg-black bg-opacity-50 z-50" class="fixed w-full h-full top-0 flex bg-black/50 z-50"
on:click={on_parent_click} onclick={on_parent_click}
transition:fade|local={{ duration: 100 }} transition:fade={{ duration: 100 }}
> >
<div <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"> <div class="flex justify-center flex-col">
<BrownButton <BrownButton
@@ -138,8 +144,8 @@ SPDX-License-Identifier: MPL-2.0
{#if add_shares_open} {#if add_shares_open}
<form <form
class="flex justify-center p-2 border-b-2 border-l-2 border-r-2 border-[#B07156] flex-col gap-2" class="flex justify-center p-2 border-b-2 border-l-2 border-r-2 border-[#B07156] flex-col gap-2"
transition:fly|local={{ duration: 100, y: -10 }} transition:fly={{ duration: 100, y: -10 }}
on:submit|preventDefault={create_share} onsubmit={preventDefault(create_share)}
> >
<div class="grid grid-cols-2"> <div class="grid grid-cols-2">
<input <input
@@ -163,7 +169,7 @@ SPDX-License-Identifier: MPL-2.0
<Spinner /> <Spinner />
{:then shares} {:then shares}
{#each shares as share} {#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>--> <!-- <p>{share.name ?? "..."}</p>-->
<div class="w-full mx-auto"> <div class="w-full mx-auto">
<BrownButton <BrownButton
+2 -2
View File
@@ -4,12 +4,12 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0 SPDX-License-Identifier: MPL-2.0
--> -->
<script> <script lang="ts">
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let quiz;
import ImportedOrNot from '$lib/view_quiz/imported_or_not.svelte'; import ImportedOrNot from '$lib/view_quiz/imported_or_not.svelte';
let { quiz } = $props();
</script> </script>
<div class="flex justify-center"> <div class="flex justify-center">
+6
View File
@@ -0,0 +1,6 @@
/*
SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0
*/
export const navbarVisible = $state({ visible: true });
-1
View File
@@ -4,7 +4,6 @@
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
export const navbarVisible = writable(true);
export const signedIn = writable(false); export const signedIn = writable(false);
export const pathname = writable('/'); export const pathname = writable('/');
+8 -3
View File
@@ -5,7 +5,12 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
export let hovering: boolean; interface Props {
hovering: boolean;
children?: import('svelte').Snippet<[any]>;
}
let { hovering = $bindable(), children }: Props = $props();
function enter() { function enter() {
hovering = true; hovering = true;
@@ -16,6 +21,6 @@ SPDX-License-Identifier: MPL-2.0
} }
</script> </script>
<div on:mouseenter={enter} on:mouseleave={leave}> <div onmouseenter={enter} onmouseleave={leave}>
<slot {hovering} /> {@render children?.({ hovering, })}
</div> </div>
@@ -9,12 +9,16 @@ SPDX-License-Identifier: MPL-2.0
import Hoverable from '$lib/view_quiz/Hoverable.svelte'; import Hoverable from '$lib/view_quiz/Hoverable.svelte';
import { createTippy } from 'svelte-tippy'; import { createTippy } from 'svelte-tippy';
export let quiz: QuizData; interface Props {
quiz: QuizData;
}
let FeedBackButtonsHovered = { let { quiz = $bindable() }: Props = $props();
let FeedBackButtonsHovered = $state({
dislike: false, dislike: false,
like: false like: false
}; });
const tippy = createTippy({ const tippy = createTippy({
arrow: true, arrow: true,
animation: 'perspective-subtle', animation: 'perspective-subtle',
@@ -43,14 +47,14 @@ SPDX-License-Identifier: MPL-2.0
}; };
</script> </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"> <div class="grid grid-cols-2 gap-2 group mx-auto">
<Hoverable bind:hovering={FeedBackButtonsHovered.like}> <Hoverable bind:hovering={FeedBackButtonsHovered.like}>
<button <button
class="bg-green-500 rounded-full h-10 w-10 transition" class="bg-green-500 rounded-full h-10 w-10 transition"
use:tippy={{ content: 'Like this quiz!' }} use:tippy={{ content: 'Like this quiz!' }}
class:opacity-40={FeedBackButtonsHovered.dislike} class:opacity-40={FeedBackButtonsHovered.dislike}
on:click={() => complete_action(true)} onclick={() => complete_action(true)}
> >
<!-- heroicons/thumb-up --> <!-- heroicons/thumb-up -->
<svg <svg
@@ -74,7 +78,7 @@ SPDX-License-Identifier: MPL-2.0
class="rounded-full bg-red-500 h-10 w-10 transition" class="rounded-full bg-red-500 h-10 w-10 transition"
use:tippy={{ content: 'Dislike this quiz!' }} use:tippy={{ content: 'Dislike this quiz!' }}
class:opacity-40={FeedBackButtonsHovered.like} class:opacity-40={FeedBackButtonsHovered.like}
on:click={() => complete_action(false)} onclick={() => complete_action(false)}
> >
<!-- heroicons/thumb-down --> <!-- heroicons/thumb-down -->
<svg <svg
@@ -96,7 +100,7 @@ SPDX-License-Identifier: MPL-2.0
<span class="text-center">{quiz.likes}</span> <span class="text-center">{quiz.likes}</span>
<span class="text-center">{quiz.dislikes}</span> <span class="text-center">{quiz.dislikes}</span>
</div> </div>
<span class="w-full border-t-2 border-[#B07156]" /> <span class="w-full border-t-2 border-[#B07156]"></span>
<div class="mx-auto grid grid-cols-2 gap-2"> <div class="mx-auto grid grid-cols-2 gap-2">
<div class="flex flex-col"> <div class="flex flex-col">
<!-- heroicons/legacy-outline/Play --> <!-- heroicons/legacy-outline/Play -->
@@ -7,7 +7,11 @@ SPDX-License-Identifier: MPL-2.0
<script lang="ts"> <script lang="ts">
import { createTippy } from 'svelte-tippy'; import { createTippy } from 'svelte-tippy';
export let imported: boolean | undefined; interface Props {
imported: boolean | undefined;
}
let { imported }: Props = $props();
const tippy = createTippy({ const tippy = createTippy({
arrow: true, arrow: true,
animation: 'perspective-subtle', animation: 'perspective-subtle',
+4 -4
View File
@@ -5,12 +5,12 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { navbarVisible } from '$lib/stores'; import { navbarVisible } from '$lib/stores.svelte.ts';
import { page } from '$app/stores'; import { page } from '$app/state';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
navbarVisible.set(true); navbarVisible.visible = true;
let status = $page.status; let status = page.status;
const { t } = getLocalization(); const { t } = getLocalization();
</script> </script>
+39 -11
View File
@@ -4,15 +4,21 @@ SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
SPDX-License-Identifier: MPL-2.0 SPDX-License-Identifier: MPL-2.0
--> -->
<script> <script lang="ts">
import '../app.css'; import '../app.css';
import Navbar from '$lib/navbar.svelte'; import Navbar from '$lib/navbar.svelte';
import { navbarVisible, pathname } from '$lib/stores'; import { pathname } from '$lib/stores';
import * as Sentry from '@sentry/browser'; import { navbarVisible } from '$lib/stores.svelte';
import { BrowserTracing } from '@sentry/tracing'; // import * as Sentry from '@sentry/browser';
// import { BrowserTracing } from '@sentry/tracing';
import { initLocalizationContext } from '$lib/i18n'; import { initLocalizationContext } from '$lib/i18n';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import CommandPalette from '$lib/components/commandpalette.svelte'; import CommandPalette from '$lib/components/commandpalette.svelte';
interface Props {
children?: import('svelte').Snippet;
}
let { children }: Props = $props();
// import Alert from '$lib/modals/alert.svelte'; // import Alert from '$lib/modals/alert.svelte';
/* afterNavigate(() => { /* afterNavigate(() => {
@@ -49,9 +55,12 @@ SPDX-License-Identifier: MPL-2.0
// // Whenever the user explicitly chooses to respect the OS preference // // Whenever the user explicitly chooses to respect the OS preference
// localStorage.removeItem('theme'); // localStorage.removeItem('theme');
} }
let start_language = 'en';
initLocalizationContext(); if (browser) {
start_language = localStorage.getItem('language') ?? 'en';
}
initLocalizationContext(start_language);
/*
if (import.meta.env.VITE_SENTRY !== undefined && import.meta.env.PROD) { if (import.meta.env.VITE_SENTRY !== undefined && import.meta.env.PROD) {
Sentry.init({ Sentry.init({
dsn: String(import.meta.env.VITE_SENTRY), dsn: String(import.meta.env.VITE_SENTRY),
@@ -63,24 +72,43 @@ SPDX-License-Identifier: MPL-2.0
tracesSampleRate: 0.5 tracesSampleRate: 0.5
}); });
} }
*/
</script> </script>
<svelte:head> <svelte:head>
{#if plausible_data_url} {#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
<script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }</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} {/if}
</svelte:head> </svelte:head>
{#if $navbarVisible} <!-- {#if navbarVisible.visible = true.visible}
<Navbar /> <Navbar />
<div class="pt-16 h-screen"> <div class="pt-16 h-screen">
<div class="z-40" /> <div class="z-40"></div>
<slot /> <slot />
</div> </div>
{:else} {:else}
<slot /> <slot />
{/if} -->
{#if navbarVisible.visible}
<Navbar />
<div class="pt-16">
<div class="z-40"></div>
<!-- extra content above slot -->
</div>
{/if} {/if}
<slot />
<CommandPalette /> <CommandPalette />
<!--{#if $alertModal.open ?? false} <!--{#if $alertModal.open ?? false}
+1 -1
View File
@@ -7,7 +7,7 @@ import { redirect } from '@sveltejs/kit';
export const load = async ({ parent }) => { export const load = async ({ parent }) => {
const { email } = await parent(); const { email } = await parent();
if (email) { if (email) {
throw redirect(302, '/dashboard'); redirect(302, '/dashboard');
} }
return { return {
email email
+37 -37
View File
@@ -5,7 +5,7 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { navbarVisible } from '$lib/stores'; import { navbarVisible } from '$lib/stores.svelte.ts';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import Footer from '$lib/footer.svelte'; import Footer from '$lib/footer.svelte';
import WebPOpenGraph from '$lib/assets/landing/opengraph-home.webp'; import WebPOpenGraph from '$lib/assets/landing/opengraph-home.webp';
@@ -25,7 +25,7 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
navbarVisible.set(true); navbarVisible.visible = true;
/* interface StatsData { /* interface StatsData {
quiz_count: number; quiz_count: number;
@@ -36,7 +36,7 @@ SPDX-License-Identifier: MPL-2.0
const response = await fetch('/api/v1/stats/combined'); const response = await fetch('/api/v1/stats/combined');
return await response.json(); return await response.json();
};*/ };*/
let newsletterModalOpen; let newsletterModalOpen = $state();
onMount(() => { onMount(() => {
const ls = localStorage.getItem('newsletter'); const ls = localStorage.getItem('newsletter');
newsletterModalOpen = ls === null; newsletterModalOpen = ls === null;
@@ -62,8 +62,8 @@ SPDX-License-Identifier: MPL-2.0
Winners Winners
} }
let selected_create_thing = SelectedCreateThing.Create; let selected_create_thing = $state(SelectedCreateThing.Create);
let selected_play_thing = SelectedPlayThing.Select; let selected_play_thing = $state(SelectedPlayThing.Select);
/* <li>No; /* <li>No;
Tracking < /li> Tracking < /li>
@@ -123,7 +123,7 @@ SPDX-License-Identifier: MPL-2.0
content: $t('index_page.community_driven_content') content: $t('index_page.community_driven_content')
} }
]; ];
let selected_classquiz_reason = 0; let selected_classquiz_reason = $state(0);
</script> </script>
<svelte:head> <svelte:head>
@@ -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> <h2 class="text-center text-5xl mb-6">{$t('index_page.how_does_classquiz_work')}</h2>
<div class="flex justify-center w-full"> <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')} {$t('index_page.get_a_quiz')}
</h3> </h3>
</div> </div>
<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>
<div class="p-2 rounded-lg"> <div class="p-2 rounded-lg">
@@ -229,21 +229,21 @@ SPDX-License-Identifier: MPL-2.0
<img <img
class="rounded-lg relative" class="rounded-lg relative"
src={EditScreenshot} src={EditScreenshot}
in:fade in:fade|global
alt="Screenshot of the import-page showing an URL to Kahoot! entered" alt="Screenshot of the import-page showing an URL to Kahoot! entered"
/> />
{:else if selected_create_thing === SelectedCreateThing.Find} {:else if selected_create_thing === SelectedCreateThing.Find}
<img <img
class="rounded-lg relative" class="rounded-lg relative"
src={FindScreenshot} src={FindScreenshot}
in:fade in:fade|global
alt="Screenshot of the search-page showing one found quiz for the term 'Country'" alt="Screenshot of the search-page showing one found quiz for the term 'Country'"
/> />
{:else if selected_create_thing === SelectedCreateThing.Import} {:else if selected_create_thing === SelectedCreateThing.Import}
<img <img
class="rounded-lg relative" class="rounded-lg relative"
src={ImportScreenshot} src={ImportScreenshot}
in:fade in:fade|global
alt="Screenshot of the import-page showing an URL to Kahoot! entered" alt="Screenshot of the import-page showing an URL to Kahoot! entered"
/> />
{:else} {: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" 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 <div
class="m-2 rounded-lg p-2 bg-opacity-40 bg-white transition-all cursor-pointer lg:h-full" class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
on:click={() => { onclick={() => {
selected_create_thing = SelectedCreateThing.Create; selected_create_thing = SelectedCreateThing.Create;
}} }}
on:keyup={() => { onkeyup={() => {
selected_create_thing = SelectedCreateThing.Create; selected_create_thing = SelectedCreateThing.Create;
}} }}
class:shadow-2xl={selected_create_thing === SelectedCreateThing.Create} class:shadow-2xl={selected_create_thing === SelectedCreateThing.Create}
@@ -288,11 +288,11 @@ SPDX-License-Identifier: MPL-2.0
<p class="dark:text-black">{$t('index_page.create_a_quiz_from_scratch')}</p> <p class="dark:text-black">{$t('index_page.create_a_quiz_from_scratch')}</p>
</div> </div>
<div <div
class="m-2 rounded-lg p-2 bg-opacity-40 bg-white transition-all cursor-pointer lg:h-full" class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
on:click={() => { onclick={() => {
selected_create_thing = SelectedCreateThing.Find; selected_create_thing = SelectedCreateThing.Find;
}} }}
on:keyup={() => { onkeyup={() => {
selected_create_thing = SelectedCreateThing.Find; selected_create_thing = SelectedCreateThing.Find;
}} }}
class:shadow-2xl={selected_create_thing === SelectedCreateThing.Find} class:shadow-2xl={selected_create_thing === SelectedCreateThing.Find}
@@ -358,13 +358,13 @@ SPDX-License-Identifier: MPL-2.0
<section class="mt-24"> <section class="mt-24">
<div class="flex justify-center w-full"> <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')} {$t('index_page.play_quiz')}
</h2> </h2>
</div> </div>
<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>
<div class="p-2 rounded-lg"> <div class="p-2 rounded-lg">
@@ -372,21 +372,21 @@ SPDX-License-Identifier: MPL-2.0
<img <img
class="rounded-lg relative" class="rounded-lg relative"
src={SelectScreenshot} src={SelectScreenshot}
in:fade in:fade|global
alt="Screenshot of the screen where an answer can be selected" alt="Screenshot of the screen where an answer can be selected"
/> />
{:else if selected_play_thing === SelectedPlayThing.Results} {:else if selected_play_thing === SelectedPlayThing.Results}
<img <img
class="rounded-lg relative" class="rounded-lg relative"
src={ResultScreenshot} src={ResultScreenshot}
in:fade in:fade|global
alt="Screenshot of the results with a table showing how many players chose which answer" alt="Screenshot of the results with a table showing how many players chose which answer"
/> />
{:else if selected_play_thing === SelectedPlayThing.Winners} {:else if selected_play_thing === SelectedPlayThing.Winners}
<img <img
class="rounded-lg relative" class="rounded-lg relative"
src={WinnersScreenshot} src={WinnersScreenshot}
in:fade in:fade|global
alt="Screenshot of the import-page showing an URL to Kahoot! entered" alt="Screenshot of the import-page showing an URL to Kahoot! entered"
/> />
{:else} {: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" 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 <div
class="m-2 rounded-lg p-2 bg-opacity-40 bg-white transition-all cursor-pointer lg:h-full" class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
on:click={() => { onclick={() => {
selected_play_thing = SelectedPlayThing.Select; selected_play_thing = SelectedPlayThing.Select;
}} }}
on:keyup={() => { onkeyup={() => {
selected_play_thing = SelectedPlayThing.Select; selected_play_thing = SelectedPlayThing.Select;
}} }}
class:shadow-2xl={selected_play_thing === SelectedPlayThing.Select} class:shadow-2xl={selected_play_thing === SelectedPlayThing.Select}
@@ -431,11 +431,11 @@ SPDX-License-Identifier: MPL-2.0
<p class="dark:text-black">{$t('index_page.choose_answer_wisely')}</p> <p class="dark:text-black">{$t('index_page.choose_answer_wisely')}</p>
</div> </div>
<div <div
class="m-2 rounded-lg p-2 bg-opacity-40 bg-white transition-all cursor-pointer lg:h-full" class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
on:click={() => { onclick={() => {
selected_play_thing = SelectedPlayThing.Results; selected_play_thing = SelectedPlayThing.Results;
}} }}
on:keyup={() => { onkeyup={() => {
selected_play_thing = SelectedPlayThing.Results; selected_play_thing = SelectedPlayThing.Results;
}} }}
class:shadow-2xl={selected_play_thing === SelectedPlayThing.Results} class:shadow-2xl={selected_play_thing === SelectedPlayThing.Results}
@@ -464,11 +464,11 @@ SPDX-License-Identifier: MPL-2.0
<p class="dark:text-black">{$t('index_page.check_if_chosen_wisely')}</p> <p class="dark:text-black">{$t('index_page.check_if_chosen_wisely')}</p>
</div> </div>
<div <div
class="m-2 rounded-lg p-2 bg-opacity-40 bg-white transition-all cursor-pointer lg:h-full" class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
on:click={() => { onclick={() => {
selected_play_thing = SelectedPlayThing.Winners; selected_play_thing = SelectedPlayThing.Winners;
}} }}
on:keyup={() => { onkeyup={() => {
selected_play_thing = SelectedPlayThing.Winners; selected_play_thing = SelectedPlayThing.Winners;
}} }}
class:shadow-2xl={selected_play_thing === SelectedPlayThing.Winners} class:shadow-2xl={selected_play_thing === SelectedPlayThing.Winners}
@@ -502,13 +502,13 @@ SPDX-License-Identifier: MPL-2.0
<section class="mt-24"> <section class="mt-24">
<div class="flex justify-center w-full"> <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')} {$t('index_page.why_classquiz')}
</h2> </h2>
</div> </div>
<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>
<div class="p-12 rounded-lg flex justify-center items-center h-full"> <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} {#each classquiz_reasons as reason, index}
<div <div
class="m-2 rounded-lg p-2 bg-opacity-40 bg-white transition-all cursor-pointer lg:h-full" class="m-2 rounded-lg p-2 bg-white/40 transition-all cursor-pointer lg:h-full"
on:click={() => { onclick={() => {
selected_classquiz_reason = index; selected_classquiz_reason = index;
}} }}
on:keyup={() => { onkeyup={() => {
selected_classquiz_reason = index; selected_classquiz_reason = index;
}} }}
class:shadow-2xl={selected_classquiz_reason === index} class:shadow-2xl={selected_classquiz_reason === index}
@@ -542,7 +542,7 @@ SPDX-License-Identifier: MPL-2.0
{#if newsletterModalOpen} {#if newsletterModalOpen}
<div <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" 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} /> <Newsletter bind:open={newsletterModalOpen} />
</div> </div>
+8 -3
View File
@@ -5,9 +5,14 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { navbarVisible } from '$lib/stores'; import { navbarVisible} from '$lib/stores.svelte.ts';
interface Props {
children?: import('svelte').Snippet;
}
navbarVisible.set(true); let { children }: Props = $props();
navbarVisible.visible= true;
</script> </script>
<slot /> {@render children?.()}
@@ -9,7 +9,11 @@ SPDX-License-Identifier: MPL-2.0
import BrownButton from '$lib/components/buttons/brown.svelte'; import BrownButton from '$lib/components/buttons/brown.svelte';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let data: PageData; interface Props {
data: PageData;
}
let { data }: Props = $props();
const controllers: [] = data.controllers; const controllers: [] = data.controllers;
</script> </script>
@@ -33,45 +37,51 @@ SPDX-License-Identifier: MPL-2.0
</div> </div>
</div> </div>
<table class="w-full"> <table class="w-full">
<tr class="border-b-2 dark:border-gray-500 text-left border-gray-300"> <thead>
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300" <tr class="border-b-2 dark:border-gray-500 text-left border-gray-300">
>{$t('words.name')}</th <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
> >
<td class="border-r dark:border-gray-500 p-1 border-gray-300" <th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{controller.player_name}</td >{$t('controllers.player_name')}
> </th>
<td class="border-r dark:border-gray-500 p-1 border-gray-300" <th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
>{controller.first_seen >{$t('controllers.first_seen')}
? new Date(controller.first_seen).toLocaleString() </th>
: $t('words.never')}</td <th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
> >{$t('controllers.last_seen')}
<td class="border-r dark:border-gray-500 p-1 border-gray-300" </th>
>{controller.last_seen <th class="mx-auto p-1">{$t('words.version')}</th>
? new Date(controller.last_seen).toLocaleString()
: $t('words.never')}</td
>
<td class="mx-auto p-1">{controller.os_version ?? $t('words.unknown')}</td>
</tr> </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> </table>
</div> </div>
{/if} {/if}
@@ -15,12 +15,16 @@ SPDX-License-Identifier: MPL-2.0
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
const { t } = getLocalization(); const { t } = getLocalization();
export let data: PageData; interface Props {
const controller = data.controller; data: PageData;
}
let newest_version: string | undefined = undefined; let { data }: Props = $props();
const controller = $state(data.controller);
let saved_status = SaveStatus.Unchanged; let newest_version: string | undefined = $state(undefined);
let saved_status = $state(SaveStatus.Unchanged);
let save_player_timer; let save_player_timer;
const save_player_name_debounce = () => { const save_player_name_debounce = () => {
@@ -93,9 +97,9 @@ SPDX-License-Identifier: MPL-2.0
<h2 class="text-2xl mx-auto">{$t('controllers.controller_name')}</h2> <h2 class="text-2xl mx-auto">{$t('controllers.controller_name')}</h2>
<div class="mx-auto flex"> <div class="mx-auto flex">
<input <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} bind:value={controller.name}
on:keyup={save_player_name_debounce} onkeyup={save_player_name_debounce}
/> />
<SaveIndicator status={saved_status} /> <SaveIndicator status={saved_status} />
</div> </div>
@@ -104,9 +108,9 @@ SPDX-License-Identifier: MPL-2.0
<h2 class="mx-auto text-2xl">{$t('controllers.player_name')}</h2> <h2 class="mx-auto text-2xl">{$t('controllers.player_name')}</h2>
<div class="mx-auto flex"> <div class="mx-auto flex">
<input <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} bind:value={controller.player_name}
on:keyup={save_player_name_debounce} onkeyup={save_player_name_debounce}
/> />
<SaveIndicator status={saved_status} /> <SaveIndicator status={saved_status} />
</div> </div>
@@ -8,7 +8,7 @@ import { error } from '@sveltejs/kit';
export const load = (async ({ fetch, params }) => { export const load = (async ({ fetch, params }) => {
const res = await fetch(`/api/v1/box-controller/web/controller?id=${params.controller_id}`); const res = await fetch(`/api/v1/box-controller/web/controller?id=${params.controller_id}`);
if (res.status !== 200) { if (res.status !== 200) {
throw error(res.status, await res.text()); error(res.status, await res.text());
} }
const json: { const json: {
id: string; id: string;
@@ -6,7 +6,11 @@ SPDX-License-Identifier: MPL-2.0
<script lang="ts"> <script lang="ts">
import { SaveStatus } from './commons'; import { SaveStatus } from './commons';
export let status: SaveStatus; interface Props {
status: SaveStatus;
}
let { status }: Props = $props();
</script> </script>
<div class="h-6 w-6 grow-0 my-auto"> <div class="h-6 w-6 grow-0 my-auto">
@@ -5,19 +5,27 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import type { PageData } from './$types'; import type { PageData } from './$types';
export let data: PageData; interface Props {
let input_data = { data: PageData;
}
let { data }: Props = $props();
let input_data = $state({
player_name: data.username, player_name: data.username,
name: '' name: ''
}; });
let isValid = false; let isValid = $state(false);
let isSubmitting = false; let isSubmitting = false;
$: isValid = input_data.name.length !== 0 && input_data.player_name.length !== 0; run(() => {
isValid = input_data.name.length !== 0 && input_data.player_name.length !== 0;
});
const submit = async () => { const submit = async () => {
if (!isValid) { if (!isValid) {
@@ -51,7 +59,7 @@ SPDX-License-Identifier: MPL-2.0
Add a controller Add a controller
</h3> </h3>
<form on:submit|preventDefault={submit}> <form onsubmit={preventDefault(submit)}>
<div class="w-full mt-4"> <div class="w-full mt-4">
<div class="dark:bg-gray-800 bg-white p-4 rounded-lg"> <div class="dark:bg-gray-800 bg-white p-4 rounded-lg">
<div class="relative bg-inherit w-full"> <div class="relative bg-inherit w-full">
@@ -59,7 +67,7 @@ SPDX-License-Identifier: MPL-2.0
id="player_name" id="player_name"
name="player_name" name="player_name"
type="text" 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-red-700={input_data.player_name.length === 0}
class:ring-green-600={input_data.player_name.length !== 0} class:ring-green-600={input_data.player_name.length !== 0}
bind:value={input_data.player_name} bind:value={input_data.player_name}
@@ -78,7 +86,7 @@ SPDX-License-Identifier: MPL-2.0
id="name" id="name"
name="name" name="name"
type="text" 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" placeholder="Name"
class:ring-red-700={input_data.name.length === 0} class:ring-red-700={input_data.name.length === 0}
class:ring-green-600={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"> <div class="flex items-center justify-center mt-4">
<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" 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} disabled={!isValid || isSubmitting}
class:cursor-not-allowed={!isValid || isSubmitting} class:cursor-not-allowed={!isValid || isSubmitting}
class:opacity-50={!isValid || isSubmitting} class:opacity-50={!isValid || isSubmitting}
@@ -9,7 +9,7 @@ export const load = (({ url }) => {
const code = url.searchParams.get('code'); const code = url.searchParams.get('code');
const id = url.searchParams.get('id'); const id = url.searchParams.get('id');
if (!id || !code) { 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 { return {
id, id,
@@ -10,9 +10,13 @@ SPDX-License-Identifier: MPL-2.0
import Spinner from '$lib/Spinner.svelte'; import Spinner from '$lib/Spinner.svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
export let data: PageData; interface Props {
let controller_seen = false; data: PageData;
let check_tick = 0; }
let { data }: Props = $props();
let controller_seen = $state(false);
let check_tick = $state(0);
let interval; let interval;
const check_if_controller_was_seen = async () => { const check_if_controller_was_seen = async () => {
@@ -11,7 +11,7 @@ export async function load({ parent, url }) {
const { email } = await parent(); const { email } = await parent();
if (email) { if (email) {
throw redirect(302, returnTo); redirect(302, returnTo);
} }
return { return {
verified: verified !== null verified: verified !== null

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