📝 Added documentation about development-setup
This commit is contained in:
@@ -6,16 +6,25 @@ You can create quizzes and play them remotely with other people.
|
||||
It is mainly made for teachers, who create a quiz, so students can compete with their knowledge against each other.
|
||||
|
||||
## Try it
|
||||
|
||||
There is a hosted version at [ClassQuiz.Mawoka.eu](https://classquiz.mawoka.eu?utm_medium=Github&utm_source=Readme).
|
||||
It is not intended for real-life usage, although it should work.
|
||||
The server is located in Karlsruhe, Germany and hosted by [netcup](https://mawoka.eu/redir?token=2).
|
||||
If you want to use it in real-life,
|
||||
please contact me on [my website](https://mawoka.eu/contact?utm_medium=Github&utm_source=Readme).
|
||||
|
||||
## Self-Host
|
||||
|
||||
Please see https://classquiz.mawoka.eu/docs/self-host
|
||||
|
||||
## Development
|
||||
|
||||
See https://classquiz.mawoka.eu/docs/develop
|
||||
|
||||
## Docs
|
||||
The docs are https://classquiz.mawoka.eu/docs on the website
|
||||
### Things to know about the structure
|
||||
|
||||
1. Everything depends on Redis (API and frontend).
|
||||
2. The proxy-container is optional (Caddy), but makes your life easier
|
||||
3. You need to set your reverse proxy up, so that it allows Socket.io-connections.
|
||||
|
||||
Generated
+3301
-2426
File diff suppressed because it is too large
Load Diff
+26
-22
@@ -1,25 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<meta name='description' content='' />
|
||||
<!-- <link rel="icon" href="%svelte.assets%/favicon.png" />-->
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1' />
|
||||
<script async='' defer='' data-domain='classquiz.mawoka.eu'
|
||||
src='https://sugar.mawoka.eu.org/js/plausible.hash.outbound-links.js'></script>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="description" content="" />
|
||||
<!-- <link rel="icon" href="%svelte.assets%/favicon.png" />-->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script
|
||||
async=""
|
||||
defer=""
|
||||
data-domain="classquiz.mawoka.eu"
|
||||
src="https://sugar.mawoka.eu.org/js/plausible.hash.outbound-links.js"
|
||||
></script>
|
||||
|
||||
<link rel='apple-touch-icon' sizes='180x180' href='%svelte.assets%/apple-touch-icon.png'>
|
||||
<link rel='icon' type='image/png' sizes='32x32' href='%svelte.assets%/favicon-32x32.png'>
|
||||
<link rel='icon' type='image/png' sizes='16x16' href='%svelte.assets%/favicon-16x16.png'>
|
||||
<link rel='manifest' href='%svelte.assets%/site.webmanifest'>
|
||||
<link rel='mask-icon' href='%svelte.assets%/safari-pinned-tab.svg' color='#5bbad5'>
|
||||
<meta name='apple-mobile-web-app-title' content='ClassQuiz'>
|
||||
<meta name='application-name' content='ClassQuiz'>
|
||||
<meta name='msapplication-TileColor' content='#00a300'>
|
||||
<meta name='theme-color' content='#ffffff'>
|
||||
%svelte.head%
|
||||
</head>
|
||||
<body>
|
||||
<div>%svelte.body%</div>
|
||||
</body>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="%svelte.assets%/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="%svelte.assets%/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="%svelte.assets%/favicon-16x16.png" />
|
||||
<link rel="manifest" href="%svelte.assets%/site.webmanifest" />
|
||||
<link rel="mask-icon" href="%svelte.assets%/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
<meta name="apple-mobile-web-app-title" content="ClassQuiz" />
|
||||
<meta name="application-name" content="ClassQuiz" />
|
||||
<meta name="msapplication-TileColor" content="#00a300" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
%svelte.head%
|
||||
</head>
|
||||
<body>
|
||||
<div>%svelte.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -23,7 +23,13 @@
|
||||
|
||||
<!-- Our open/close buttons -->
|
||||
<!-- Open menu -->
|
||||
<button class="lg:hidden" id="open-menu" on:click={toggleMenu} class:hidden={!openMenu} aria-label='Open navbar'>
|
||||
<button
|
||||
class="lg:hidden"
|
||||
id="open-menu"
|
||||
on:click={toggleMenu}
|
||||
class:hidden={!openMenu}
|
||||
aria-label="Open navbar"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
@@ -42,7 +48,13 @@
|
||||
</button>
|
||||
|
||||
<!-- Close menu -->
|
||||
<button class="hidden" id="close-menu" class:hidden={!closeMenu} on:click={toggleMenu} aria-label='Close navbar'>
|
||||
<button
|
||||
class="hidden"
|
||||
id="close-menu"
|
||||
class:hidden={!closeMenu}
|
||||
on:click={toggleMenu}
|
||||
aria-label="Close navbar"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="14"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang='ts'>
|
||||
<script lang="ts">
|
||||
import { socket } from '$lib/socket';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { browser } from '$app/env';
|
||||
import * as Sentry from "@sentry/browser";
|
||||
import * as Sentry from '@sentry/browser';
|
||||
|
||||
export let game_pin: string;
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
|
||||
let hcaptcha = {
|
||||
execute: async (_a, _b) => ({ response: '' }),
|
||||
render: (_a, _b) => {
|
||||
}
|
||||
render: (_a, _b) => {}
|
||||
};
|
||||
let hcaptchaWidgetID;
|
||||
|
||||
@@ -33,8 +32,7 @@
|
||||
if (browser) {
|
||||
hcaptcha = {
|
||||
execute: async () => ({ response: '' }),
|
||||
render: () => {
|
||||
}
|
||||
render: () => {}
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -50,7 +48,7 @@
|
||||
});
|
||||
captcha_resp = response;
|
||||
} catch (e) {
|
||||
if (import.meta.env.VITE_SENTRY !== null){
|
||||
if (import.meta.env.VITE_SENTRY !== null) {
|
||||
Sentry.captureException(e);
|
||||
}
|
||||
alert('Captcha failed, reloading the page probably helps!');
|
||||
@@ -69,27 +67,27 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<script src='https://js.hcaptcha.com/1/api.js?render=explicit' async defer></script>
|
||||
<script src="https://js.hcaptcha.com/1/api.js?render=explicit" async defer></script>
|
||||
</svelte:head>
|
||||
|
||||
{#if game_pin === '' || game_pin.length <= 7}
|
||||
<div class='flex flex-col justify-center align-center w-screen h-screen'>
|
||||
<div class="flex flex-col justify-center align-center w-screen h-screen">
|
||||
<form
|
||||
on:submit|preventDefault={() => {}}
|
||||
class='flex-col flex justify-center align-center mx-auto'
|
||||
class="flex-col flex justify-center align-center mx-auto"
|
||||
>
|
||||
<h1 class='text-lg text-center'>Game Pin</h1>
|
||||
<h1 class="text-lg text-center">Game Pin</h1>
|
||||
<input
|
||||
class='border border-amber-800 self-center text-center text-black'
|
||||
class="border border-amber-800 self-center text-center text-black"
|
||||
bind:value={game_pin}
|
||||
maxlength='8'
|
||||
maxlength="8"
|
||||
/>
|
||||
<!-- use:tippy={{content: "Please enter the game pin", sticky: true, placement: 'top'}}-->
|
||||
|
||||
<br />
|
||||
<button
|
||||
class='bg-amber-800 hover:bg-amber-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50 disabled:cursor-not-allowed mt-2'
|
||||
type='submit'
|
||||
class="bg-amber-800 hover:bg-amber-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50 disabled:cursor-not-allowed mt-2"
|
||||
type="submit"
|
||||
disabled={game_pin.length <= 7}
|
||||
>
|
||||
Submit
|
||||
@@ -97,16 +95,16 @@
|
||||
</form>
|
||||
</div>
|
||||
{:else}
|
||||
<div class='flex flex-col justify-center align-center w-screen h-screen'>
|
||||
<div class="flex flex-col justify-center align-center w-screen h-screen">
|
||||
<form
|
||||
on:submit|preventDefault={setUsername}
|
||||
class='flex-col flex justify-center align-center mx-auto'
|
||||
class="flex-col flex justify-center align-center mx-auto"
|
||||
>
|
||||
<h1 class='text-lg text-center'>Username</h1>
|
||||
<input class='border border-amber-800 text-black text-center' bind:value={username} />
|
||||
<h1 class="text-lg text-center">Username</h1>
|
||||
<input class="border border-amber-800 text-black text-center" bind:value={username} />
|
||||
<button
|
||||
class='bg-amber-800 hover:bg-amber-700 text-white font-bold py-2 px-4 rounded disabled:cursor-not-allowed disabled:opacity-50 mt-2'
|
||||
type='submit'
|
||||
class="bg-amber-800 hover:bg-amber-700 text-white font-bold py-2 px-4 rounded disabled:cursor-not-allowed disabled:opacity-50 mt-2"
|
||||
type="submit"
|
||||
disabled={username.length <= 3}
|
||||
>
|
||||
Submit
|
||||
@@ -115,9 +113,9 @@
|
||||
</div>
|
||||
{/if}
|
||||
<div
|
||||
id='hcaptcha'
|
||||
class='h-captcha'
|
||||
id="hcaptcha"
|
||||
class="h-captcha"
|
||||
data-sitekey={hcaptchaSitekey}
|
||||
data-size='invisible'
|
||||
data-theme='dark'
|
||||
data-size="invisible"
|
||||
data-theme="dark"
|
||||
/>
|
||||
|
||||
@@ -16,42 +16,41 @@
|
||||
tracesSampleRate: 0.5
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#if $navbarVisible}
|
||||
<Navbar />
|
||||
<div class='pt-16 h-screen'>
|
||||
<div class="pt-16 h-screen">
|
||||
<slot />
|
||||
</div>
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
|
||||
<style lang='scss'>
|
||||
:global(body) {
|
||||
// height: 100%;
|
||||
// width: 100%;
|
||||
background: linear-gradient(to right, #009444, #39b54a, #8dc63f) repeat-y;
|
||||
background-size: cover;
|
||||
/*background: linear-gradient(-225deg, #231557 0%, #44107A 29%, #FF1361 67%, #FFF800 100%); */
|
||||
/*background: linear-gradient(-225deg, #22E1FF 0%, #1D8FE1 48%, #625EB1 100%); */
|
||||
color: beige;
|
||||
<style lang="scss">
|
||||
:global(body) {
|
||||
// height: 100%;
|
||||
// width: 100%;
|
||||
background: linear-gradient(to right, #009444, #39b54a, #8dc63f) repeat-y;
|
||||
background-size: cover;
|
||||
/*background: linear-gradient(-225deg, #231557 0%, #44107A 29%, #FF1361 67%, #FFF800 100%); */
|
||||
/*background: linear-gradient(-225deg, #22E1FF 0%, #1D8FE1 48%, #625EB1 100%); */
|
||||
color: beige;
|
||||
|
||||
// background-size: 400% 400%;
|
||||
// background-size: 400% 400%;
|
||||
|
||||
//animation: background_animation 5s ease infinite;
|
||||
}
|
||||
//animation: background_animation 5s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes background_animation {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
@keyframes background_animation {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
isSubmitting = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>ClassQuiz - Login</title>
|
||||
</svelte:head>
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
data: ''
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>ClassQuiz - Register</title>
|
||||
</svelte:head>
|
||||
|
||||
@@ -21,9 +21,7 @@
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<svelte:head>
|
||||
<title>ClassQuiz - Host</title>
|
||||
</svelte:head>
|
||||
|
||||
<script lang="ts">
|
||||
import type { QuizData } from '../app';
|
||||
|
||||
@@ -106,6 +104,10 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>ClassQuiz - Host</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if !success}
|
||||
<input placeholder="game id" bind:value={game_token} />
|
||||
<input placeholder="game pin" bind:value={game_pin} />
|
||||
|
||||
@@ -21,9 +21,7 @@
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<svelte:head>
|
||||
<title>ClassQuiz - Create</title>
|
||||
</svelte:head>
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import Editor from '$lib/editor.svelte';
|
||||
@@ -82,6 +80,10 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>ClassQuiz - Create</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if data !== undefined}
|
||||
<form on:submit|preventDefault={submit} class="grid grid-cols-1 gap-2">
|
||||
<Editor bind:data />
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<svelte:head>
|
||||
<title>ClassQuiz/docs - Development-setup</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="How to import quizzes from KAHOOT! into ClassQuiz, the open-source quiz-application, easily"
|
||||
/>
|
||||
</svelte:head>
|
||||
<article
|
||||
class="prose prose-sm sm:prose lg:prose-lg xl:prose-xl mx-auto mt-10 prose-pink text-yellow-50 px-4"
|
||||
>
|
||||
<h1>Development-setup</h1>
|
||||
|
||||
<h2>Requirements</h2>
|
||||
<h4>Backend</h4>
|
||||
<ul>
|
||||
<li><a href="https://caddyserver.com/">Caddy</a></li>
|
||||
<li><a href="https://pipenv.pypa.io/en/latest/">Pipenv</a></li>
|
||||
<li><a href="https://redis.io/">Redis</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>Frontend</h4>
|
||||
<ul>
|
||||
<li><a href="https://pnpm.io/">pNpM</a></li>
|
||||
</ul>
|
||||
|
||||
<h2>Setup</h2>
|
||||
<ul>
|
||||
<li>
|
||||
Clone the repo:
|
||||
<pre><code>git clone https://github.com/mawoka-myblock/classquiz</code></pre>
|
||||
</li>
|
||||
<li>
|
||||
Install the dependencies
|
||||
<ul>
|
||||
<li>
|
||||
Python:
|
||||
<pre><code>pipenv install</code></pre>
|
||||
</li>
|
||||
<li>
|
||||
JS:
|
||||
<pre><code>cd frontend && pnpm i</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Start the other services
|
||||
<ul>
|
||||
<li>
|
||||
Caddy:
|
||||
<pre><code>caddy run</code></pre>
|
||||
</li>
|
||||
<li>
|
||||
Redis:
|
||||
<pre><code>redis-server</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
hcaptcha_key: str mail_address: str mail_password: str mail_username: str mail_server: str mail_port:
|
||||
int secret_key: str
|
||||
<li>
|
||||
Set your config up in your .env-file. What you have to set up can you see in the
|
||||
<code>classquiz/config.py</code>-file. The things you have to set are the following:
|
||||
<ul>
|
||||
<li><code>hcaptcha_key</code></li>
|
||||
<li><code>mail_address</code></li>
|
||||
<li><code>mail_password</code></li>
|
||||
<li><code>mail_username</code></li>
|
||||
<li><code>mail_server</code></li>
|
||||
<li><code>mail_port</code></li>
|
||||
<li><code>secret_key</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Start the server
|
||||
<ul>
|
||||
<li>
|
||||
Backend:
|
||||
<pre><code>pipenv run uvicorn classquiz:app --reload</code></pre>
|
||||
</li>
|
||||
<li>
|
||||
Frontend:
|
||||
<pre><code>cd frontend && pnpm dev</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>BEFORE you submit a Pull-Request</h2>
|
||||
<h4>Frontend</h4>
|
||||
<p>Please format and lint your code with</p>
|
||||
<pre><code>pnpm run format && pnpm run lint</code></pre>
|
||||
|
||||
<h4>Backend</h4>
|
||||
<p>IDK, just check that it works!</p>
|
||||
</article>
|
||||
@@ -1,17 +1,25 @@
|
||||
<svelte:head>
|
||||
<title>ClassQuiz/docs - Import from Kahoot</title>
|
||||
<meta name=description content='How to import quizzes from KAHOOT! into ClassQuiz, the open-source quiz-application, easily'>
|
||||
<meta
|
||||
name="description"
|
||||
content="How to import quizzes from KAHOOT! into ClassQuiz, the open-source quiz-application, easily"
|
||||
/>
|
||||
</svelte:head>
|
||||
<article
|
||||
class='prose prose-sm sm:prose lg:prose-lg xl:prose-xl mx-auto mt-10 prose-pink text-yellow-50 px-4'
|
||||
class="prose prose-sm sm:prose lg:prose-lg xl:prose-xl mx-auto mt-10 prose-pink text-yellow-50 px-4"
|
||||
>
|
||||
<h1>Import Quizzes from Kahoot into ClassQuiz</h1>
|
||||
|
||||
<p>All in all, this procedure is pretty simple, so just go to <a href='https://create.kahoot.it/discover'
|
||||
rel='nofollow' target='_blank'>create.kahoot.it/discover</a>,
|
||||
find the quiz you want to import, so click on it and copy the URL into your clipboard. Then go to <a
|
||||
href='https://classquiz.mawoka.eu/import' rel='nofollow' target='_blank'>classquiz.mawoka.eu/import</a> and paste
|
||||
the url into the text field.
|
||||
<p>
|
||||
All in all, this procedure is pretty simple, so just go to <a
|
||||
href="https://create.kahoot.it/discover"
|
||||
rel="nofollow"
|
||||
target="_blank">create.kahoot.it/discover</a
|
||||
>, find the quiz you want to import, so click on it and copy the URL into your clipboard.
|
||||
Then go to
|
||||
<a href="https://classquiz.mawoka.eu/import" rel="nofollow" target="_blank"
|
||||
>classquiz.mawoka.eu/import</a
|
||||
> and paste the url into the text field.
|
||||
</p>
|
||||
|
||||
<h2>Limitations</h2>
|
||||
@@ -21,4 +29,4 @@
|
||||
<li>Metadata doesn't get imported</li>
|
||||
<li>Point-multipliers don't get imported</li>
|
||||
</ul>
|
||||
</article>
|
||||
</article>
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
<svelte:head>
|
||||
<title>ClassQuiz/docs - Index</title>
|
||||
<meta name=description content='The overview about the docs for ClassQuiz, the open-source quiz-application'>
|
||||
<meta
|
||||
name="description"
|
||||
content="The overview about the docs for ClassQuiz, the open-source quiz-application"
|
||||
/>
|
||||
</svelte:head>
|
||||
<article
|
||||
class='prose prose-sm sm:prose lg:prose-lg xl:prose-xl mx-auto mt-10 prose-pink text-yellow-50 px-4'
|
||||
class="prose prose-sm sm:prose lg:prose-lg xl:prose-xl mx-auto mt-10 prose-pink text-yellow-50 px-4"
|
||||
>
|
||||
<h1>Welcome to the ClassQuiz docs!</h1>
|
||||
<h2>Index</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href='/docs/roadmap'>Roadmap</a>
|
||||
<a href="/docs/roadmap">Roadmap</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='/docs/import-from-kahoot'>Import from Kahoot</a>
|
||||
<a href="/docs/import-from-kahoot">Import from Kahoot</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='/docs/self-host'>Self-hosting</a>
|
||||
<a href="/docs/self-host">Self-hosting</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/docs/develop">Development-Setup</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</article>
|
||||
</article>
|
||||
|
||||
@@ -1,34 +1,26 @@
|
||||
<svelte:head>
|
||||
<title>ClassQuiz/docs - Roadmap</title>
|
||||
<meta name=description content='The roadmap for ClassQuiz, the open-source quiz-application'>
|
||||
<meta
|
||||
name="description"
|
||||
content="The roadmap for ClassQuiz, the open-source quiz-application"
|
||||
/>
|
||||
</svelte:head>
|
||||
<article
|
||||
class='prose prose-sm sm:prose lg:prose-lg xl:prose-xl mx-auto mt-10 prose-pink text-yellow-50 px-4'
|
||||
class="prose prose-sm sm:prose lg:prose-lg xl:prose-xl mx-auto mt-10 prose-pink text-yellow-50 px-4"
|
||||
>
|
||||
<h1>Roadmap</h1>
|
||||
|
||||
<ul>
|
||||
<li>User-management
|
||||
<li>
|
||||
User-management
|
||||
<ul>
|
||||
<li>
|
||||
Password-reset
|
||||
</li>
|
||||
<li>
|
||||
Session-management
|
||||
</li>
|
||||
<li>Password-reset</li>
|
||||
<li>Session-management</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Images
|
||||
</li>
|
||||
<li>
|
||||
(Better) Admin-screen styling
|
||||
</li>
|
||||
<li>
|
||||
(Better) Edit/Create-screen styling
|
||||
</li>
|
||||
<li>
|
||||
Public quiz-discovery
|
||||
</li>
|
||||
<li>Images</li>
|
||||
<li>(Better) Admin-screen styling</li>
|
||||
<li>(Better) Edit/Create-screen styling</li>
|
||||
<li>Public quiz-discovery</li>
|
||||
</ul>
|
||||
</article>
|
||||
</article>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang='ts'>
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import '$lib/hljs.css';
|
||||
|
||||
@@ -10,30 +10,36 @@
|
||||
|
||||
<svelte:head>
|
||||
<title>ClassQuiz/docs - Self-Host</title>
|
||||
<meta name=description content='How to self-host ClassQuiz, the open-source quiz-application'>
|
||||
<meta
|
||||
name="description"
|
||||
content="How to self-host ClassQuiz, the open-source quiz-application"
|
||||
/>
|
||||
</svelte:head>
|
||||
<article
|
||||
class='prose prose-sm sm:prose lg:prose-lg xl:prose-xl mx-auto mt-10 prose-pink text-yellow-50 px-4'
|
||||
class="prose prose-sm sm:prose lg:prose-lg xl:prose-xl mx-auto mt-10 prose-pink text-yellow-50 px-4"
|
||||
>
|
||||
<h1>Self-Hosting</h1>
|
||||
<p>Since ClassQuiz is open-source, it can also be self-hosted.</p>
|
||||
|
||||
<h2>Requirements</h2>
|
||||
<ul>
|
||||
<li><a href='https://docker.com'>Docker</a></li>
|
||||
<li><a href='https://git-scm.com/'>Git</a></li>
|
||||
<li>A <a href='https://redis.com'>Redis</a>-Server (I recommend <a href='https://upstash.com'>Upstash</a>)</li>
|
||||
<li><a href="https://docker.com">Docker</a></li>
|
||||
<li><a href="https://git-scm.com/">Git</a></li>
|
||||
<li>
|
||||
A <a href="https://redis.com">Redis</a>-Server (I recommend
|
||||
<a href="https://upstash.com">Upstash</a>)
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>Installation</h2>
|
||||
<p>
|
||||
At first, clone the repo:
|
||||
</p>
|
||||
<p>At first, clone the repo:</p>
|
||||
|
||||
<pre><code class='language-bash'>git clone https://github.com/mawoka-myblock/classquiz && cd ClassQuiz</code></pre>
|
||||
<pre><code class="language-bash"
|
||||
>git clone https://github.com/mawoka-myblock/classquiz && cd ClassQuiz</code
|
||||
></pre>
|
||||
<p>
|
||||
Now, set a <b>VALID</b> Redis-URI in <code>frontend/Dockerfile</code> and, if you Sentry want, a valid
|
||||
Sentry-DSN.
|
||||
Now, set a <b>VALID</b> Redis-URI in <code>frontend/Dockerfile</code> and, if you Sentry want,
|
||||
a valid Sentry-DSN.
|
||||
</p>
|
||||
<p>
|
||||
You must set a valid hCaptcha-Sitekey in the <code>frontend/Dockerfile</code>.
|
||||
@@ -44,7 +50,8 @@
|
||||
Before you can start your stack, you have to set some environment-variables in your
|
||||
<code>docker-compose.yml</code>.
|
||||
</p>
|
||||
<pre><code class='language-yaml'>version: "3"
|
||||
<pre><code class="language-yaml"
|
||||
>version: "3"
|
||||
|
||||
services:
|
||||
frontend:
|
||||
@@ -112,6 +119,5 @@ volumes:
|
||||
data:
|
||||
</code></pre>
|
||||
<p>Now build and deploy:</p>
|
||||
<pre><code class='language-bash'>docker compose build && docker compose up -d</code></pre>
|
||||
|
||||
</article>
|
||||
<pre><code class="language-bash">docker compose build && docker compose up -d</code></pre>
|
||||
</article>
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>ClassQuiz - Edit</title>
|
||||
</svelte:head>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script context='module' lang='ts'>
|
||||
<script context="module" lang="ts">
|
||||
export async function load({ session }) {
|
||||
if (!session.authenticated) {
|
||||
return {
|
||||
@@ -13,13 +13,9 @@
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<svelte:head>
|
||||
<title>ClassQuiz - Import</title>
|
||||
</svelte:head>
|
||||
<script lang='ts'>
|
||||
let url_input = '';
|
||||
|
||||
import tippy from 'sveltejs-tippy';
|
||||
<script lang="ts">
|
||||
let url_input = '';
|
||||
|
||||
let url_valid = false;
|
||||
let kahoot_regex = /^https:\/\/create\.kahoot\.it\/details\/([a-zA-Z-\d]{36})\/?$/;
|
||||
@@ -51,6 +47,10 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>ClassQuiz - Import</title>
|
||||
</svelte:head>
|
||||
|
||||
<!--{#if is_loading}
|
||||
<svg class='h-8 w-8 animate-spin mx-auto my-20' viewBox='3 3 18 18'>
|
||||
<path
|
||||
@@ -68,70 +68,69 @@
|
||||
<input type='text' class="text-black w-2/5" bind:value={url_input} />
|
||||
<button type='submit' disabled={!url_valid}>Submit</button>
|
||||
</form>-->
|
||||
<div class='flex items-center justify-center h-full px-4'>
|
||||
<div class="flex items-center justify-center h-full px-4">
|
||||
<div>
|
||||
<span class='p-4' />
|
||||
<span class="p-4" />
|
||||
|
||||
<div
|
||||
class='lg:w-[64rem] lg:max-w-[64rem] w-screen max-w-screen mx-auto overflow-hidden bg-white rounded-lg shadow-md dark:bg-gray-800'
|
||||
|
||||
class="lg:w-[64rem] lg:max-w-[64rem] w-screen max-w-screen mx-auto overflow-hidden bg-white rounded-lg shadow-md dark:bg-gray-800"
|
||||
>
|
||||
<div class='px-6 py-4'>
|
||||
<h2 class='text-3xl font-bold text-center text-gray-700 dark:text-white'>
|
||||
<div class="px-6 py-4">
|
||||
<h2 class="text-3xl font-bold text-center text-gray-700 dark:text-white">
|
||||
ClassQuiz
|
||||
</h2>
|
||||
|
||||
<h3 class='mt-1 text-xl font-medium text-center text-gray-600 dark:text-gray-200'>
|
||||
<h3 class="mt-1 text-xl font-medium text-center text-gray-600 dark:text-gray-200">
|
||||
Welcome Back
|
||||
</h3>
|
||||
|
||||
<p class='mt-1 text-center text-gray-500 dark:text-gray-400'>
|
||||
<p class="mt-1 text-center text-gray-500 dark:text-gray-400">
|
||||
Login or create account
|
||||
</p>
|
||||
|
||||
<form on:submit|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'>
|
||||
<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">
|
||||
<input
|
||||
id='url'
|
||||
id="url"
|
||||
bind:value={url_input}
|
||||
name='email'
|
||||
type='url'
|
||||
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'
|
||||
placeholder='https://create.kahoot.it/details/something'
|
||||
name="email"
|
||||
type="url"
|
||||
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"
|
||||
placeholder="https://create.kahoot.it/details/something"
|
||||
class:ring-red-700={!url_valid}
|
||||
class:ring-green-600={url_valid}
|
||||
/>
|
||||
<label
|
||||
for='url'
|
||||
class='absolute cursor-text left-0 -top-3 text-sm text-gray-700 dark:text-white bg-inherit mx-1 px-1 peer-placeholder-shown:text-base peer-placeholder-shown:text-gray-500 peer-placeholder-shown:top-2 peer-focus:-top-3 peer-focus:text-sky-600 peer-focus:text-sm transition-all'
|
||||
for="url"
|
||||
class="absolute cursor-text left-0 -top-3 text-sm text-gray-700 dark:text-white bg-inherit mx-1 px-1 peer-placeholder-shown:text-base peer-placeholder-shown:text-gray-500 peer-placeholder-shown:top-2 peer-focus:-top-3 peer-focus:text-sky-600 peer-focus:text-sm transition-all"
|
||||
>
|
||||
URL
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='flex items-center justify-between mt-4'>
|
||||
<span></span>
|
||||
<div class="flex items-center justify-between mt-4">
|
||||
<span />
|
||||
|
||||
<button
|
||||
class='px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded hover:bg-gray-600 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50'
|
||||
class="px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded hover:bg-gray-600 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={!url_valid}
|
||||
type='submit'
|
||||
type="submit"
|
||||
>
|
||||
{#if is_loading}
|
||||
<svg
|
||||
class='h-4 w-4 animate-spin mx-auto my-20'
|
||||
viewBox='3 3 18 18'
|
||||
class="h-4 w-4 animate-spin mx-auto my-20"
|
||||
viewBox="3 3 18 18"
|
||||
>
|
||||
<path
|
||||
class='fill-black'
|
||||
d='M12 5C8.13401 5 5 8.13401 5 12C5 15.866 8.13401 19 12 19C15.866 19 19 15.866 19 12C19 8.13401 15.866 5 12 5ZM3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12Z'
|
||||
class="fill-black"
|
||||
d="M12 5C8.13401 5 5 8.13401 5 12C5 15.866 8.13401 19 12 19C15.866 19 19 15.866 19 12C19 8.13401 15.866 5 12 5ZM3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12Z"
|
||||
/>
|
||||
<path
|
||||
class='fill-blue-100'
|
||||
d='M16.9497 7.05015C14.2161 4.31648 9.78392 4.31648 7.05025 7.05015C6.65973 7.44067 6.02656 7.44067 5.63604 7.05015C5.24551 6.65962 5.24551 6.02646 5.63604 5.63593C9.15076 2.12121 14.8492 2.12121 18.364 5.63593C18.7545 6.02646 18.7545 6.65962 18.364 7.05015C17.9734 7.44067 17.3403 7.44067 16.9497 7.05015Z'
|
||||
class="fill-blue-100"
|
||||
d="M16.9497 7.05015C14.2161 4.31648 9.78392 4.31648 7.05025 7.05015C6.65973 7.44067 6.02656 7.44067 5.63604 7.05015C5.24551 6.65962 5.24551 6.02646 5.63604 5.63593C9.15076 2.12121 14.8492 2.12121 18.364 5.63593C18.7545 6.02646 18.7545 6.65962 18.364 7.05015C17.9734 7.44067 17.3403 7.44067 16.9497 7.05015Z"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
@@ -143,19 +142,17 @@
|
||||
</form>
|
||||
</div>
|
||||
<div
|
||||
class='flex items-center justify-center py-4 text-center bg-gray-50 dark:bg-gray-700'
|
||||
class="flex items-center justify-center py-4 text-center bg-gray-50 dark:bg-gray-700"
|
||||
>
|
||||
<span class='text-sm text-gray-600 dark:text-gray-200'
|
||||
>Need help?
|
||||
</span>
|
||||
<span class="text-sm text-gray-600 dark:text-gray-200">Need help? </span>
|
||||
|
||||
<a
|
||||
href='/docs/import-from-kahoot'
|
||||
class='mx-2 text-sm font-bold text-blue-500 dark:text-blue-400 hover:underline'
|
||||
>Visit the docs</a
|
||||
href="/docs/import-from-kahoot"
|
||||
class="mx-2 text-sm font-bold text-blue-500 dark:text-blue-400 hover:underline"
|
||||
>Visit the docs</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--{/if}-->
|
||||
<!--{/if}-->
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
|
||||
<svelte:head>
|
||||
<title>ClassQuiz - Home</title>
|
||||
<meta name="description" content="ClassQuiz is a quiz app like KAHOOT! for students, which is open source and free to use.">
|
||||
<meta
|
||||
name="description"
|
||||
content="ClassQuiz is a quiz app like KAHOOT! for students, which is open source and free to use."
|
||||
/>
|
||||
</svelte:head>
|
||||
|
||||
<section>
|
||||
|
||||
+124
-125
@@ -1,4 +1,4 @@
|
||||
<script context='module' lang='ts'>
|
||||
<script context="module" lang="ts">
|
||||
export async function load({ session }) {
|
||||
if (!session.authenticated) {
|
||||
return {
|
||||
@@ -14,11 +14,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>ClassQuiz - Overview</title>
|
||||
</svelte:head>
|
||||
|
||||
<script lang='ts'>
|
||||
<script lang="ts">
|
||||
import type { QuizData } from '../app';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
@@ -48,157 +44,160 @@
|
||||
};
|
||||
|
||||
const deleteQuiz = async (to_delete: string) => {
|
||||
const res = await fetch(`/api/v1/quiz/delete/${to_delete}`, {
|
||||
await fetch(`/api/v1/quiz/delete/${to_delete}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
window.location.reload();
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>ClassQuiz - Overview</title>
|
||||
</svelte:head>
|
||||
|
||||
{#await getData()}
|
||||
<svg class='h-8 w-8 animate-spin mx-auto my-20' viewBox='3 3 18 18'>
|
||||
<svg class="h-8 w-8 animate-spin mx-auto my-20" viewBox="3 3 18 18">
|
||||
<path
|
||||
class='fill-black'
|
||||
d='M12 5C8.13401 5 5 8.13401 5 12C5 15.866 8.13401 19 12 19C15.866 19 19 15.866 19 12C19 8.13401 15.866 5 12 5ZM3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12Z'
|
||||
class="fill-black"
|
||||
d="M12 5C8.13401 5 5 8.13401 5 12C5 15.866 8.13401 19 12 19C15.866 19 19 15.866 19 12C19 8.13401 15.866 5 12 5ZM3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12Z"
|
||||
/>
|
||||
<path
|
||||
class='fill-blue-100'
|
||||
d='M16.9497 7.05015C14.2161 4.31648 9.78392 4.31648 7.05025 7.05015C6.65973 7.44067 6.02656 7.44067 5.63604 7.05015C5.24551 6.65962 5.24551 6.02646 5.63604 5.63593C9.15076 2.12121 14.8492 2.12121 18.364 5.63593C18.7545 6.02646 18.7545 6.65962 18.364 7.05015C17.9734 7.44067 17.3403 7.44067 16.9497 7.05015Z'
|
||||
class="fill-blue-100"
|
||||
d="M16.9497 7.05015C14.2161 4.31648 9.78392 4.31648 7.05025 7.05015C6.65973 7.44067 6.02656 7.44067 5.63604 7.05015C5.24551 6.65962 5.24551 6.02646 5.63604 5.63593C9.15076 2.12121 14.8492 2.12121 18.364 5.63593C18.7545 6.02646 18.7545 6.65962 18.364 7.05015C17.9734 7.44067 17.3403 7.44067 16.9497 7.05015Z"
|
||||
/>
|
||||
</svg>
|
||||
{:then quizzes}
|
||||
<div class='flex flex-col w-fit mx-auto'>
|
||||
<div class="flex flex-col w-fit mx-auto">
|
||||
<!-- <button
|
||||
class='px-4 py-2 font-medium tracking-wide text-gray-500 whitespace-nowrap dark:text-gray-400 capitalize transition-colors dark:bg-gray-700 duration-200 transform bg-gray-50 rounded-md hover:bg-green-600 focus:outline-none focus:ring focus:ring-blue-300 focus:ring-opacity-80'>
|
||||
Primary
|
||||
</button>-->
|
||||
<div class='w-full grid grid-cols-3 gap-2'>
|
||||
<div class="w-full grid grid-cols-3 gap-2">
|
||||
<a
|
||||
href='/create'
|
||||
class='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'
|
||||
>Create
|
||||
href="/create"
|
||||
class="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"
|
||||
>Create
|
||||
</a>
|
||||
<a
|
||||
href='/import'
|
||||
class='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'
|
||||
>Import
|
||||
href="/import"
|
||||
class="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"
|
||||
>Import
|
||||
</a>
|
||||
<a
|
||||
href='/api/v1/users/logout'
|
||||
class='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'
|
||||
>Logout
|
||||
href="/api/v1/users/logout"
|
||||
class="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"
|
||||
>Logout
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div class='overflow-x-auto sm:-mx-8 lg:-mx-8'>
|
||||
<div class='inline-block py-2 min-w-full sm:px-6 lg:px-8'>
|
||||
<div class='overflow-hidden shadow-md sm:rounded-lg'>
|
||||
<table class='min-w-full'>
|
||||
<thead class='bg-gray-50 dark:bg-gray-700'>
|
||||
<tr>
|
||||
<th
|
||||
scope='col'
|
||||
class='py-3 px-6 text-xs font-medium tracking-wider text-left text-gray-700 uppercase dark:text-gray-400'
|
||||
>
|
||||
Title
|
||||
</th>
|
||||
<th
|
||||
scope='col'
|
||||
class='py-3 px-6 text-xs font-medium tracking-wider text-left text-gray-700 uppercase dark:text-gray-400'
|
||||
>
|
||||
Created at
|
||||
</th>
|
||||
<th
|
||||
scope='col'
|
||||
class='py-3 px-6 text-xs font-medium tracking-wider text-left text-gray-700 uppercase dark:text-gray-400'
|
||||
>
|
||||
Question count
|
||||
</th>
|
||||
<th
|
||||
scope='col'
|
||||
class='py-3 px-6 text-xs font-medium tracking-wider text-left text-gray-700 uppercase dark:text-gray-400'
|
||||
>
|
||||
Play
|
||||
</th>
|
||||
<th
|
||||
scope='col'
|
||||
class='py-3 px-6 text-xs font-medium tracking-wider text-left text-gray-700 uppercase dark:text-gray-400'
|
||||
>
|
||||
Edit
|
||||
</th>
|
||||
<th
|
||||
scope='col'
|
||||
class='py-3 px-6 text-xs font-medium tracking-wider text-left text-gray-700 uppercase dark:text-gray-400'
|
||||
>
|
||||
Delete
|
||||
</th>
|
||||
<th
|
||||
scope='col'
|
||||
class='py-3 px-6 text-xs font-medium tracking-wider text-left text-gray-700 uppercase dark:text-gray-400'
|
||||
>
|
||||
Public
|
||||
</th>
|
||||
</tr>
|
||||
<div class="overflow-x-auto sm:-mx-8 lg:-mx-8">
|
||||
<div class="inline-block py-2 min-w-full sm:px-6 lg:px-8">
|
||||
<div class="overflow-hidden shadow-md sm:rounded-lg">
|
||||
<table class="min-w-full">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
class="py-3 px-6 text-xs font-medium tracking-wider text-left text-gray-700 uppercase dark:text-gray-400"
|
||||
>
|
||||
Title
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="py-3 px-6 text-xs font-medium tracking-wider text-left text-gray-700 uppercase dark:text-gray-400"
|
||||
>
|
||||
Created at
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="py-3 px-6 text-xs font-medium tracking-wider text-left text-gray-700 uppercase dark:text-gray-400"
|
||||
>
|
||||
Question count
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="py-3 px-6 text-xs font-medium tracking-wider text-left text-gray-700 uppercase dark:text-gray-400"
|
||||
>
|
||||
Play
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="py-3 px-6 text-xs font-medium tracking-wider text-left text-gray-700 uppercase dark:text-gray-400"
|
||||
>
|
||||
Edit
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="py-3 px-6 text-xs font-medium tracking-wider text-left text-gray-700 uppercase dark:text-gray-400"
|
||||
>
|
||||
Delete
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="py-3 px-6 text-xs font-medium tracking-wider text-left text-gray-700 uppercase dark:text-gray-400"
|
||||
>
|
||||
Public
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Product 1 -->
|
||||
{#each quizzes as quiz}
|
||||
<tr class='bg-white border-b dark:bg-gray-800 dark:border-gray-700'>
|
||||
<td
|
||||
class='py-4 px-6 text-sm font-medium text-gray-900 whitespace-nowrap dark:text-white'
|
||||
>
|
||||
{quiz.title}
|
||||
</td>
|
||||
<td
|
||||
class='py-4 px-6 text-sm text-gray-500 whitespace-nowrap dark:text-gray-400'
|
||||
>
|
||||
{formatDate(quiz.created_at)}
|
||||
</td>
|
||||
<td
|
||||
class='py-4 px-6 text-sm text-gray-500 whitespace-nowrap dark:text-gray-400'
|
||||
>
|
||||
{quiz.questions.length}
|
||||
</td>
|
||||
<td
|
||||
class='py-4 px-6 text-sm text-gray-500 whitespace-nowrap dark:text-gray-400'
|
||||
>
|
||||
<button
|
||||
on:click={() => {
|
||||
<!-- Product 1 -->
|
||||
{#each quizzes as quiz}
|
||||
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
|
||||
<td
|
||||
class="py-4 px-6 text-sm font-medium text-gray-900 whitespace-nowrap dark:text-white"
|
||||
>
|
||||
{quiz.title}
|
||||
</td>
|
||||
<td
|
||||
class="py-4 px-6 text-sm text-gray-500 whitespace-nowrap dark:text-gray-400"
|
||||
>
|
||||
{formatDate(quiz.created_at)}
|
||||
</td>
|
||||
<td
|
||||
class="py-4 px-6 text-sm text-gray-500 whitespace-nowrap dark:text-gray-400"
|
||||
>
|
||||
{quiz.questions.length}
|
||||
</td>
|
||||
<td
|
||||
class="py-4 px-6 text-sm text-gray-500 whitespace-nowrap dark:text-gray-400"
|
||||
>
|
||||
<button
|
||||
on:click={() => {
|
||||
startGame(quiz.id);
|
||||
}}
|
||||
class='border border-green-600'
|
||||
class="border border-green-600"
|
||||
>
|
||||
Start
|
||||
</button>
|
||||
</td>
|
||||
<td
|
||||
class="py-4 px-6 text-sm text-gray-500 whitespace-nowrap dark:text-gray-400"
|
||||
>
|
||||
Start
|
||||
</button>
|
||||
</td>
|
||||
<td
|
||||
class='py-4 px-6 text-sm text-gray-500 whitespace-nowrap dark:text-gray-400'
|
||||
>
|
||||
<a href='/edit?quiz_id={quiz.id}'>Edit</a>
|
||||
</td>
|
||||
<td
|
||||
class='py-4 px-6 text-sm text-gray-500 whitespace-nowrap dark:text-gray-400'
|
||||
>
|
||||
<button
|
||||
on:click={() => {
|
||||
<a href="/edit?quiz_id={quiz.id}">Edit</a>
|
||||
</td>
|
||||
<td
|
||||
class="py-4 px-6 text-sm text-gray-500 whitespace-nowrap dark:text-gray-400"
|
||||
>
|
||||
<button
|
||||
on:click={() => {
|
||||
deleteQuiz(quiz.id);
|
||||
}}
|
||||
class='border border-green-600'
|
||||
class="border border-green-600"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
<td
|
||||
class="py-4 px-6 text-sm text-gray-500 whitespace-nowrap dark:text-gray-400"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
<td
|
||||
class='py-4 px-6 text-sm text-gray-500 whitespace-nowrap dark:text-gray-400'
|
||||
>
|
||||
{#if quiz.public}
|
||||
✅
|
||||
{:else}
|
||||
❌
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{#if quiz.public}
|
||||
✅
|
||||
{:else}
|
||||
❌
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
});
|
||||
// The rest
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>ClassQuiz - Play</title>
|
||||
</svelte:head>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"name": "ClassQuiz",
|
||||
"short_name": "ClassQuiz",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
"name": "ClassQuiz",
|
||||
"short_name": "ClassQuiz",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user