it mostly works and and it will generate a json (you can see the json in the console) and that json can be pussed to the supabase table with some extra code witch i will now write!
This commit is contained in:
parent
52aab530e3
commit
b3ee5ebc78
6 changed files with 243 additions and 179 deletions
|
@ -1,5 +1,10 @@
|
|||
<script>
|
||||
import { DefaultQuestions } from "$lib/config.js";
|
||||
import { QuestionsData } from "./create.svelte";
|
||||
</script>
|
||||
|
||||
<div class="flex w-full justify-center gap-2 overflow-y-auto rounded border-2 p-3 pr-5">
|
||||
<button class="btn flex items-center gap-1">
|
||||
<button class="btn flex items-center gap-1" onclick={() => (QuestionsData.v = DefaultQuestions)}>
|
||||
<i class="nf nf-md-flash"></i> Use Demo Questions
|
||||
</button>
|
||||
<button class="btn flex items-center gap-1">
|
||||
|
|
|
@ -1,96 +1,56 @@
|
|||
<script>
|
||||
import { AnswersSymbolAndColorScheme } from "$lib/config.js";
|
||||
import { QuestionsData, selectedQuestionIndex } from "./create.svelte.js";
|
||||
</script>
|
||||
|
||||
<div class="flex h-full w-full flex-col gap-2 overflow-y-auto rounded border-2 p-3 pr-5">
|
||||
<h1 class="text-4xl">How it will look</h1>
|
||||
<h1 class="text-4xl">How Question number {selectedQuestionIndex.v + 1} will look</h1>
|
||||
|
||||
<div class="grid h-full place-items-center">
|
||||
<div class="card w-4/5">
|
||||
<h1 class="flex flex-col text-3xl">
|
||||
<span class="text-5xl">1.</span>What is the capital of France?
|
||||
<span class="text-5xl">{selectedQuestionIndex.v + 1}.</span>
|
||||
{QuestionsData.v[selectedQuestionIndex.v].questionText}
|
||||
</h1>
|
||||
{#if QuestionsData.v[selectedQuestionIndex.v].mediaURL && QuestionsData.v[selectedQuestionIndex.v].hasMedia}
|
||||
<div class="flex justify-center">
|
||||
<div class="mt-1 w-fit rounded border-2">
|
||||
{#if QuestionsData.v[selectedQuestionIndex.v].mediaURL.match(/\.(mp4|webm|ogg|mov|avi|mkv)$/i)}
|
||||
<!-- svelte-ignore a11y_media_has_caption -->
|
||||
<video
|
||||
src={QuestionsData.v[selectedQuestionIndex.v].mediaURL}
|
||||
controls
|
||||
class="h-75 rounded"
|
||||
></video>
|
||||
{:else}
|
||||
<img
|
||||
src={QuestionsData.v[selectedQuestionIndex.v].mediaURL}
|
||||
alt="Question media"
|
||||
class="h-75 rounded"
|
||||
/>{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="mt-5 grid grid-cols-2 gap-5 gap-x-3">
|
||||
<div class="flex">
|
||||
<input type="radio" id="O{0}" name="question" class="peer sr-only" value={0} />
|
||||
|
||||
<label
|
||||
for="O{1}"
|
||||
style="
|
||||
--border-color: {AnswersSymbolAndColorScheme[0].Color};
|
||||
--bg-color: {AnswersSymbolAndColorScheme[0].Color};
|
||||
--border-color-checked: {AnswersSymbolAndColorScheme[0].SelectedColor};
|
||||
--bg-color-checked: {AnswersSymbolAndColorScheme[0].SelectedColor};
|
||||
--border-color-hover: {AnswersSymbolAndColorScheme[0].HoverBorderColor};
|
||||
--border-color-checked: {AnswersSymbolAndColorScheme[0].SelectedBorderColor};
|
||||
--border-color-hover: {AnswersSymbolAndColorScheme[0].HoverBorderColor};
|
||||
{#each QuestionsData.v[selectedQuestionIndex.v].options as question, questionIndex}
|
||||
<div class="flex">
|
||||
<span
|
||||
style="
|
||||
--border-color: {AnswersSymbolAndColorScheme[questionIndex].Color};
|
||||
--bg-color: {AnswersSymbolAndColorScheme[questionIndex].Color};
|
||||
--border-color-checked: {AnswersSymbolAndColorScheme[questionIndex].SelectedColor};
|
||||
--bg-color-checked: {AnswersSymbolAndColorScheme[questionIndex].SelectedColor};
|
||||
--border-color-hover: {AnswersSymbolAndColorScheme[questionIndex].HoverBorderColor};
|
||||
--border-color-checked: {AnswersSymbolAndColorScheme[questionIndex].SelectedBorderColor};
|
||||
--border-color-hover: {AnswersSymbolAndColorScheme[questionIndex].HoverBorderColor};
|
||||
"
|
||||
class="w-full cursor-pointer rounded-lg border-[5px] border-[var(--border-color)] bg-[var(--bg-color)] pt-1 pr-2 pb-1 pl-2 text-center text-3xl transition-all peer-checked:border-[var(--border-color-checked)] peer-checked:bg-[var(--bg-color-checked)] hover:border-[var(--border-color-hover)]"
|
||||
>
|
||||
<i class="nf {AnswersSymbolAndColorScheme[0].Symbol}"></i>
|
||||
answer 1
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<input type="radio" id="O{1}" name="question" class="peer sr-only" value={1} />
|
||||
|
||||
<label
|
||||
for="O{1}"
|
||||
style="
|
||||
--border-color: {AnswersSymbolAndColorScheme[1].Color};
|
||||
--bg-color: {AnswersSymbolAndColorScheme[1].Color};
|
||||
--border-color-checked: {AnswersSymbolAndColorScheme[1].SelectedColor};
|
||||
--bg-color-checked: {AnswersSymbolAndColorScheme[1].SelectedColor};
|
||||
--border-color-hover: {AnswersSymbolAndColorScheme[1].HoverBorderColor};
|
||||
--border-color-checked: {AnswersSymbolAndColorScheme[1].SelectedBorderColor};
|
||||
--border-color-hover: {AnswersSymbolAndColorScheme[1].HoverBorderColor};
|
||||
"
|
||||
class="w-full cursor-pointer rounded-lg border-[5px] border-[var(--border-color)] bg-[var(--bg-color)] pt-1 pr-2 pb-1 pl-2 text-center text-3xl transition-all peer-checked:border-[var(--border-color-checked)] peer-checked:bg-[var(--bg-color-checked)] hover:border-[var(--border-color-hover)]"
|
||||
>
|
||||
<i class="nf {AnswersSymbolAndColorScheme[1].Symbol}"></i>
|
||||
answer 2
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<input type="radio" id="O{2}" name="question" class="peer sr-only" value={2} />
|
||||
|
||||
<label
|
||||
for="O{1}"
|
||||
style="
|
||||
--border-color: {AnswersSymbolAndColorScheme[2].Color};
|
||||
--bg-color: {AnswersSymbolAndColorScheme[2].Color};
|
||||
--border-color-checked: {AnswersSymbolAndColorScheme[2].SelectedColor};
|
||||
--bg-color-checked: {AnswersSymbolAndColorScheme[2].SelectedColor};
|
||||
--border-color-hover: {AnswersSymbolAndColorScheme[2].HoverBorderColor};
|
||||
--border-color-checked: {AnswersSymbolAndColorScheme[2].SelectedBorderColor};
|
||||
--border-color-hover: {AnswersSymbolAndColorScheme[2].HoverBorderColor};
|
||||
"
|
||||
class="w-full cursor-pointer rounded-lg border-[5px] border-[var(--border-color)] bg-[var(--bg-color)] pt-1 pr-2 pb-1 pl-2 text-center text-3xl transition-all peer-checked:border-[var(--border-color-checked)] peer-checked:bg-[var(--bg-color-checked)] hover:border-[var(--border-color-hover)]"
|
||||
>
|
||||
<i class="nf {AnswersSymbolAndColorScheme[2].Symbol}"></i>
|
||||
answer 3
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<input type="radio" id="O{3}" name="question" class="peer sr-only" value={3} />
|
||||
|
||||
<label
|
||||
for="O{1}"
|
||||
style="
|
||||
--border-color: {AnswersSymbolAndColorScheme[3].Color};
|
||||
--bg-color: {AnswersSymbolAndColorScheme[3].Color};
|
||||
--border-color-checked: {AnswersSymbolAndColorScheme[3].SelectedColor};
|
||||
--bg-color-checked: {AnswersSymbolAndColorScheme[3].SelectedColor};
|
||||
--border-color-hover: {AnswersSymbolAndColorScheme[3].HoverBorderColor};
|
||||
--border-color-checked: {AnswersSymbolAndColorScheme[3].SelectedBorderColor};
|
||||
--border-color-hover: {AnswersSymbolAndColorScheme[3].HoverBorderColor};
|
||||
"
|
||||
class="w-full cursor-pointer rounded-lg border-[5px] border-[var(--border-color)] bg-[var(--bg-color)] pt-1 pr-2 pb-1 pl-2 text-center text-3xl transition-all peer-checked:border-[var(--border-color-checked)] peer-checked:bg-[var(--bg-color-checked)] hover:border-[var(--border-color-hover)]"
|
||||
>
|
||||
<i class="nf {AnswersSymbolAndColorScheme[3].Symbol}"></i>
|
||||
answer 4
|
||||
</label>
|
||||
</div>
|
||||
class="w-full cursor-pointer rounded-lg border-[5px] border-[var(--border-color)] bg-[var(--bg-color)] pt-1 pr-2 pb-1 pl-2 text-center text-3xl transition-all peer-checked:border-[var(--border-color-checked)] peer-checked:bg-[var(--bg-color-checked)] hover:border-[var(--border-color-hover)]"
|
||||
>
|
||||
<i class="nf {AnswersSymbolAndColorScheme[questionIndex].Symbol}"></i>
|
||||
{question}
|
||||
</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,43 @@
|
|||
<script>
|
||||
import toast from "svelte-5-french-toast";
|
||||
import { supabase } from "$lib/supabase.js";
|
||||
|
||||
import { QuestionsData, selectedQuestionIndex } from "./create.svelte.js";
|
||||
|
||||
let files;
|
||||
|
||||
export async function UpLoadFiles(file) {
|
||||
if (!file) {
|
||||
toast.error("Please select a file to upload first.");
|
||||
return;
|
||||
}
|
||||
|
||||
const fileExt = file.name.split(".").pop();
|
||||
const fileName = `${Date.now()}.${fileExt}`;
|
||||
const filePath = `${fileName}`;
|
||||
|
||||
const uploadPromise = supabase.storage.from("useruploadedcontent").upload(filePath, file);
|
||||
|
||||
const result = await toast.promise(uploadPromise, {
|
||||
loading: "Uploading...",
|
||||
success: "Upload successful!",
|
||||
error: (error) => `Upload failed. ${error.message} Please try again.`,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
toast.error("Upload error:" + result.error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve public URL
|
||||
const { data: publicData } = supabase.storage
|
||||
.from("useruploadedcontent")
|
||||
.getPublicUrl(filePath);
|
||||
|
||||
return publicData.publicUrl;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex h-full w-[30%] flex-col justify-between gap-2 overflow-y-auto rounded-l border-2 p-3 pr-5"
|
||||
>
|
||||
|
@ -10,17 +50,23 @@
|
|||
type="text"
|
||||
id="QuetionText"
|
||||
placeholder="Whats the capital of france?"
|
||||
bind:value={QuestionsData.v[selectedQuestionIndex.v].questionText}
|
||||
class="input mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<label for="TimeLimit" class="text-lg leading-0.5 text-gray-500">Time limit?</label>
|
||||
<select name="TimeLimit" id="TimeLimit" class="input mt-1">
|
||||
<select
|
||||
name="TimeLimit"
|
||||
id="TimeLimit"
|
||||
class="input mt-1"
|
||||
bind:value={QuestionsData.v[selectedQuestionIndex.v].timeLimit}
|
||||
>
|
||||
<option value={null}>infinite</option>
|
||||
<option value={5}>5 sec</option>
|
||||
<option value={10}>10 sec</option>
|
||||
<option selected value={15}>15 sec</option>
|
||||
<option value={15}>15 sec</option>
|
||||
<option value={30}>30 sec</option>
|
||||
<option value={60}>1 min</option>
|
||||
<option value={120}>2 min</option>
|
||||
|
@ -30,43 +76,89 @@
|
|||
|
||||
<div class="mt-3">
|
||||
<label for="QuestionType" class="text-lg leading-0.5 text-gray-500">Question type?</label>
|
||||
<select name="QuestionType" id="QuestionType" class="input mt-1">
|
||||
<option selected value="SingleAnswer">Single Answer</option>
|
||||
<select
|
||||
name="QuestionType"
|
||||
id="QuestionType"
|
||||
class="input mt-1"
|
||||
bind:value={QuestionsData.v[selectedQuestionIndex.v].type}
|
||||
>
|
||||
<option value="SingleAnswer">Single Answer</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<label for="AddSomeMedia" class="text-lg leading-0.5 text-gray-500">Add Some Media?</label>
|
||||
<input type="checkbox" bind:checked={QuestionsData.v[selectedQuestionIndex.v].hasMedia} />
|
||||
|
||||
{#if QuestionsData.v[selectedQuestionIndex.v].hasMedia}
|
||||
<input
|
||||
type="file"
|
||||
class="sr-only"
|
||||
bind:files
|
||||
onchange={async () => {
|
||||
QuestionsData.v[selectedQuestionIndex.v].mediaURL = await UpLoadFiles(files[0]);
|
||||
}}
|
||||
accept="image/*,video/*"
|
||||
/>
|
||||
<label for="media-upload" class="btn dull mt-2 cursor-pointer">
|
||||
{QuestionsData.v[selectedQuestionIndex.v].mediaURL
|
||||
? "choose different file"
|
||||
: "upload files"}
|
||||
</label>
|
||||
<input
|
||||
id="media-upload"
|
||||
type="file"
|
||||
class="hidden"
|
||||
bind:files
|
||||
onchange={async () => {
|
||||
QuestionsData.v[selectedQuestionIndex.v].mediaURL = await UpLoadFiles(files[0]);
|
||||
}}
|
||||
accept="image/*,video/*"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
{#if "SingleAnswer" === "SingleAnswer"}
|
||||
<span class="text-lg leading-0.5 text-gray-500">Options</span>
|
||||
<div class="mt-1 grid grid-cols-1 gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="radio" name="options" class="ratio" />
|
||||
<input type="text" placeholder="Option 1" class="input w-1/3" />
|
||||
<button class="btn slim dull" aria-label="Delete Option">
|
||||
<i class="nf nf-md-delete"></i>
|
||||
</button>
|
||||
{#each QuestionsData.v[selectedQuestionIndex.v].options as Option, index}
|
||||
<div class="mt-1 grid grid-cols-1 gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
name="options"
|
||||
class="ratio"
|
||||
value={index}
|
||||
bind:group={QuestionsData.v[selectedQuestionIndex.v].CorrectOption}
|
||||
/>
|
||||
<input type="text" placeholder="Option 1" bind:value={Option} class="input w-1/3" />
|
||||
<button
|
||||
class="btn slim dull"
|
||||
aria-label="Delete Option"
|
||||
onclick={() => {
|
||||
if (QuestionsData.v[selectedQuestionIndex.v].options.length <= 2) {
|
||||
toast.error("You need to have a minimum of 2 options");
|
||||
} else {
|
||||
QuestionsData.v[selectedQuestionIndex.v].options.splice(index, 1);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i class="nf nf-md-delete"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1 grid grid-cols-1 gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="radio" name="options" class="ratio" />
|
||||
<input type="text" placeholder="Option 2" class="input w-1/3" />
|
||||
<button class="btn slim dull" aria-label="Delete Option">
|
||||
<i class="nf nf-md-delete"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1 grid grid-cols-1 gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="radio" name="options" class="ratio" />
|
||||
<input type="text" placeholder="Option 3" class="input w-1/3" />
|
||||
<button class="btn slim dull" aria-label="Delete Option">
|
||||
<i class="nf nf-md-delete"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="flex justify-center">
|
||||
<button class="btn dull mt-2 w-fit">Add Option</button>
|
||||
<button
|
||||
class="btn dull mt-2 w-fit"
|
||||
onclick={() => {
|
||||
if (QuestionsData.v[selectedQuestionIndex.v].options.length >= 8) {
|
||||
toast.error("You can only have a max of 8 options");
|
||||
} else {
|
||||
QuestionsData.v[selectedQuestionIndex.v].options.push("");
|
||||
}
|
||||
}}>Add Option</button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -74,7 +166,18 @@
|
|||
<div>
|
||||
<div class="mt-4">
|
||||
<label for="QuetionText" class="text-lg leading-0.5 text-gray-500">Danger</label>
|
||||
<button class="btn mt-1 dull w-full" >delete this question</button>
|
||||
<button
|
||||
class="btn dull mt-1 w-full"
|
||||
onclick={() => {
|
||||
if (QuestionsData.v.length <= 1) {
|
||||
toast.error("You need to have at least 1 question");
|
||||
} else if (confirm("Are you sure you want to delete this question?")) {
|
||||
QuestionsData.v.splice(selectedQuestionIndex.v, 1);
|
||||
selectedQuestionIndex.v = Math.max(0, selectedQuestionIndex.v - 1);
|
||||
toast.success("Question deleted successfully");
|
||||
}
|
||||
}}>delete this question</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,13 +1,31 @@
|
|||
<script>
|
||||
let questions = ["Question 1", "Question 2", "Question 3"];
|
||||
import { QuestionsData, selectedQuestionIndex } from "./create.svelte.js";
|
||||
</script>
|
||||
|
||||
<div class="flex h-full w-[15%] flex-col gap-2 overflow-y-auto rounded-r border-2 p-3 pr-5">
|
||||
<h1 class="text-4xl">Questions</h1>
|
||||
{#each questions as questions, questionIndex}
|
||||
<div class="card flex flex-col">
|
||||
<span class="text-3xl">{questionIndex + 1}.</span><span>{questions}</span>
|
||||
</div>
|
||||
{#each QuestionsData.v as question, questionIndex}
|
||||
<button
|
||||
class="card flex flex-col text-left"
|
||||
onclick={() => (selectedQuestionIndex.v = questionIndex)}
|
||||
>
|
||||
<span class="text-3xl">{questionIndex + 1}.</span><span>{question.questionText}</span>
|
||||
</button>
|
||||
{/each}
|
||||
<div class="flex justify-center mt-2"><button class="btn"><i class="nf nf-oct-diff_added"></i> New Question</button></div>
|
||||
<div class="mt-2 flex justify-center">
|
||||
<button
|
||||
class="btn"
|
||||
onclick={() => {
|
||||
QuestionsData.v.push({
|
||||
questionText: "",
|
||||
timeLimit: 15,
|
||||
type: "SingleAnswer",
|
||||
options: ["", "", "", ""],
|
||||
CorrectOption: null,
|
||||
hasMedia: false,
|
||||
mediaURL: null,
|
||||
});
|
||||
}}><i class="nf nf-oct-diff_added"></i> New Question</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
|
17
src/routes/kahootclone/create/create.svelte.js
Normal file
17
src/routes/kahootclone/create/create.svelte.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
export let selectedQuestionIndex = $state({ v: 0 });
|
||||
export let QuestionsData = $state({
|
||||
v: [
|
||||
{
|
||||
questionText: "",
|
||||
timeLimit: 15,
|
||||
type: "SingleAnswer",
|
||||
options: ["", "", "", ""],
|
||||
CorrectOption: null,
|
||||
hasMedia: false,
|
||||
mediaURL: null
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
$inspect(QuestionsData.v);
|
Loading…
Add table
Add a link
Reference in a new issue