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