📝 Added documentation about development-setup

This commit is contained in:
Mawoka
2022-03-12 18:55:32 +01:00
parent d48ab734a5
commit c3d5e00a1b
21 changed files with 3755 additions and 2745 deletions
+9
View File
@@ -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.
+1168 -293
View File
File diff suppressed because it is too large Load Diff
+19 -15
View File
@@ -1,22 +1,26 @@
<!DOCTYPE html>
<html lang='en'>
<html lang="en">
<head>
<meta charset='utf-8' />
<meta name='description' content='' />
<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>
<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'>
<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>
+14 -2
View File
@@ -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"
+22 -24
View File
@@ -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: () => {}
};
}
});
@@ -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"
/>
+2 -3
View File
@@ -16,19 +16,18 @@
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'>
<style lang="scss">
:global(body) {
// height: 100%;
// width: 100%;
+1
View File
@@ -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>
+5 -3
View File
@@ -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} />
+5 -3
View File
@@ -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 />
+95
View File
@@ -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>
+11 -6
View File
@@ -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>
+13 -21
View File
@@ -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>
+21 -15
View File
@@ -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>
<pre><code class="language-bash">docker compose build && docker compose up -d</code></pre>
</article>
+1
View File
@@ -70,6 +70,7 @@
}
};
</script>
<svelte:head>
<title>ClassQuiz - Edit</title>
</svelte:head>
+38 -41
View File
@@ -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,15 +142,13 @@
</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'
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>
+4 -1
View File
@@ -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>
+50 -51
View File
@@ -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,93 +44,96 @@
};
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'
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'
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'
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'>
<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'
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'
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'
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'
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'
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'
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'
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>
@@ -143,53 +142,53 @@
<tbody>
<!-- Product 1 -->
{#each quizzes as quiz}
<tr class='bg-white border-b dark:bg-gray-800 dark:border-gray-700'>
<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'
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'
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'
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'
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'
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>
<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'
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'
class="py-4 px-6 text-sm text-gray-500 whitespace-nowrap dark:text-gray-400"
>
{#if quiz.public}
+1
View File
@@ -68,6 +68,7 @@
});
// The rest
</script>
<svelte:head>
<title>ClassQuiz - Play</title>
</svelte:head>