🚧 Last change
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
/** Dispatch event on click outside of node */
|
||||
export function clickOutside(node) {
|
||||
const handleClick = (event) => {
|
||||
if (node && !node.contains(event.target) && !event.defaultPrevented) {
|
||||
node.dispatchEvent(new CustomEvent('click_outside', node));
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', handleClick, true);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
document.removeEventListener('click', handleClick, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
import Sidebar from '$lib/editor/sidebar.svelte';
|
||||
import SettingsCard from '$lib/editor/settings-card.svelte';
|
||||
import QuizCard from '$lib/editor/card.svelte';
|
||||
import Spinner from './Spinner.svelte';
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
@@ -14,6 +15,7 @@
|
||||
|
||||
export let data: EditorData;
|
||||
export let submit_button_text = 'Create';
|
||||
export let quiz_id: string | null;
|
||||
let selected_question = -1;
|
||||
let imgur_links_valid = false;
|
||||
|
||||
@@ -63,48 +65,75 @@
|
||||
right: false,
|
||||
answer: ''
|
||||
};
|
||||
let edit_id;
|
||||
|
||||
const getEditID = async () => {
|
||||
let res;
|
||||
if (quiz_id === null) {
|
||||
res = await fetch(`/api/v1/editor/start?edit=false`, {
|
||||
method: 'POST'
|
||||
});
|
||||
} else {
|
||||
res = await fetch(`/api/v1/editor/start?edit=true&quiz_id=${quiz_id}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
}
|
||||
if (res.status === 200) {
|
||||
const json = await res.json();
|
||||
edit_id = json.token;
|
||||
console.log(edit_id, json);
|
||||
} else {
|
||||
alert('Error!');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-6 h-screen w-screen">
|
||||
<div>
|
||||
<Sidebar bind:data bind:selected_question />
|
||||
</div>
|
||||
<div class="col-span-5 flex flex-col">
|
||||
<div class="h-10 w-full bg-white mb-10 flex align-middle justify-center rounded-br-lg">
|
||||
{#if schemaInvalid}
|
||||
<p class="text-center w-full text-red-600 h-full mt-0.5 font-semibold">
|
||||
{yupErrorMessage}
|
||||
</p>
|
||||
{:else}
|
||||
<p class="text-center w-full text-black h-full align-bottom mt-0.5">{data.title}</p>
|
||||
{/if}
|
||||
<button
|
||||
class="pr-2 align-middle bg-purple-400 pl-2 ml-auto whitespace-nowrap disabled:opacity-60 rounded-br-lg"
|
||||
disabled={schemaInvalid}
|
||||
>
|
||||
<span>Save</span>
|
||||
<svg
|
||||
class="w-6 h-6 inline-block"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{#await getEditID()}
|
||||
<Spinner />
|
||||
{:then _}
|
||||
<div class="grid grid-cols-6 h-screen w-screen">
|
||||
<div>
|
||||
<Sidebar bind:data bind:selected_question />
|
||||
</div>
|
||||
<div class="col-span-5 flex flex-col">
|
||||
<div class="h-10 w-full bg-white mb-10 flex align-middle justify-center rounded-br-lg">
|
||||
{#if schemaInvalid}
|
||||
<p class="text-center w-full text-red-600 h-full mt-0.5 font-semibold">
|
||||
{yupErrorMessage}
|
||||
</p>
|
||||
{:else}
|
||||
<p class="text-center w-full text-black h-full align-bottom mt-0.5">
|
||||
{data.title}
|
||||
</p>
|
||||
{/if}
|
||||
<button
|
||||
class="pr-2 align-middle bg-purple-400 pl-2 ml-auto whitespace-nowrap disabled:opacity-60 rounded-br-lg"
|
||||
disabled={schemaInvalid}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="w-full h-full">
|
||||
{#if selected_question === -1}
|
||||
<SettingsCard bind:data />
|
||||
{:else}
|
||||
<QuizCard bind:data bind:selected_question />
|
||||
{/if}
|
||||
<span>Save</span>
|
||||
<svg
|
||||
class="w-6 h-6 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="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="w-full h-full">
|
||||
{#if selected_question === -1}
|
||||
<SettingsCard bind:data />
|
||||
{:else}
|
||||
<QuizCard bind:data bind:selected_question bind:edit_id />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/await}
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
import CheckerBg from '$lib/editor/checker-bg.svg';
|
||||
import { reach } from 'yup';
|
||||
import { dataSchema } from '$lib/yupSchemas';
|
||||
import Spinner from '../Spinner.svelte';
|
||||
|
||||
export let data: EditorData;
|
||||
export let selected_question: number;
|
||||
export let edit_id: string;
|
||||
|
||||
let question = data.questions[selected_question];
|
||||
$: question = data.questions[selected_question];
|
||||
@@ -13,11 +15,14 @@
|
||||
right: false,
|
||||
answer: ''
|
||||
};
|
||||
console.log(question.image);
|
||||
let uppyOpen = false;
|
||||
$: console.log(uppyOpen);
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full pb-20 px-20">
|
||||
<div class="rounded-lg bg-white w-full h-full border-gray-500 drop-shadow-2xl">
|
||||
<div class="h-fit bg-gray-300 rounded-t-lg">
|
||||
<div class="rounded-lg bg-white w-full h-full border-gray-500 drop-shadow-2xl 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"
|
||||
@@ -36,15 +41,28 @@
|
||||
type="text"
|
||||
bind:value={question.question}
|
||||
placeholder="No title..."
|
||||
class="p-3 rounded-lg border-gray-500 border text-center w-2/3 text-lg font-semibold placeholder:italic placeholder:font-normal"
|
||||
class="p-3 rounded-lg border-gray-500 border text-center w-2/3 text-lg font-semibold placeholder:italic placeholder:font-normal dark:bg-gray-500"
|
||||
class:bg-yellow-500={!reach(dataSchema, 'questions[].question').isValidSync(
|
||||
question.question
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-center pt-10 w-full max-h-72 w-full">
|
||||
<img src={question.image} alt="not available" class="max-h-72 h-auto w-auto" />
|
||||
</div>
|
||||
{#if question.image != undefined && question.image !== ''}
|
||||
<div class="flex justify-center pt-10 w-full max-h-72 w-full">
|
||||
<img src={question.image} alt="not available" class="max-h-72 h-auto w-auto" />
|
||||
</div>
|
||||
{:else}
|
||||
{#await import('$lib/editor/uploader.svelte')}
|
||||
<Spinner />
|
||||
{:then c}
|
||||
<svelte:component
|
||||
this={c.default}
|
||||
bind:modalOpen={uppyOpen}
|
||||
bind:edit_id
|
||||
bind:data
|
||||
/>
|
||||
{/await}
|
||||
{/if}
|
||||
<div class="flex justify-center pt-10 w-full">
|
||||
<div class="grid grid-cols-2 gap-4 w-full px-10">
|
||||
{#each question.answers as answer, index}
|
||||
@@ -109,8 +127,7 @@
|
||||
{/each}
|
||||
{#if question.answers.length < 4}
|
||||
<button
|
||||
class="p-4 rounded-lg bg-transparent"
|
||||
style="background-image: url({CheckerBg})"
|
||||
class="p-4 rounded-lg bg-transparent border-gray-500 border-2"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
question.answers = [...question.answers, { empty_answer }];
|
||||
@@ -125,3 +142,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if uppyOpen}
|
||||
<span class="fixed w-screen h-screen bg-opacity-60 z-10">1</span>
|
||||
{/if}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full pb-20 px-20">
|
||||
<div class="rounded-lg bg-white w-full h-full border-gray-500">
|
||||
<div class="h-fit bg-gray-300 rounded-t-lg">
|
||||
<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"
|
||||
@@ -19,19 +19,19 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="dark:bg-gray-700">
|
||||
<div class="flex justify-center pt-10 w-full">
|
||||
<input
|
||||
type="text"
|
||||
bind:value={data.title}
|
||||
class="p-3 rounded-lg border-gray-500 border text-center w-1/3 text-lg font-semibold"
|
||||
class="p-3 rounded-lg border-gray-500 border text-center w-1/3 text-lg font-semibold dark:bg-gray-500"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-center pt-10 w-full max-h-32">
|
||||
<textarea
|
||||
type="text"
|
||||
bind:value={data.description}
|
||||
class="p-3 rounded-lg border-gray-500 border text-center w-1/3 h-20 resize-none"
|
||||
class="p-3 rounded-lg border-gray-500 border text-center w-1/3 h-20 resize-none dark:bg-gray-500"
|
||||
/>
|
||||
</div>
|
||||
<div class="pt-10">
|
||||
|
||||
@@ -45,9 +45,10 @@
|
||||
|
||||
<div class="h-screen border-r-2 pt-6 px-6 overflow-scroll">
|
||||
<div
|
||||
class="bg-white shadow rounded-lg h-40 p-2 mb-6 hover:cursor-pointer drop-shadow-2xl border border-gray-500"
|
||||
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"
|
||||
bind:this={propertyCard}
|
||||
class:bg-green-300={selected_question === -1}
|
||||
class:dark:bg-green-500={selected_question === -1}
|
||||
on:click={() => setSelectedQuestion(-1)}
|
||||
>
|
||||
<div
|
||||
@@ -59,7 +60,7 @@
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="whitespace-nowrap truncate text-center w-full bg-transparent rounded font-semibold"
|
||||
class="whitespace-nowrap truncate text-center w-full bg-transparent rounded font-semibold dark:text-black"
|
||||
bind:value={data.title}
|
||||
/>
|
||||
</div>
|
||||
@@ -72,10 +73,10 @@
|
||||
>
|
||||
<textarea
|
||||
bind:value={data.description}
|
||||
class="bg-transparent resize-none w-full rounded text-sm"
|
||||
class="bg-transparent resize-none w-full rounded text-sm dark:text-black"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full flex justify-center">
|
||||
<div class="w-full flex justify-center dark:text-black">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
@@ -121,8 +122,13 @@
|
||||
</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"
|
||||
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"
|
||||
class:bg-green-300={index === selected_question}
|
||||
class:dark:bg-green-500={index === selected_question}
|
||||
on:contextmenu|preventDefault={() => {
|
||||
data.questions.splice(index, 1);
|
||||
data.questions = data.questions;
|
||||
}}
|
||||
on:click={() => {
|
||||
setSelectedQuestion(index);
|
||||
}}
|
||||
@@ -133,7 +139,7 @@
|
||||
class="m-1 border border-gray-500 rounded-lg p-0.5"
|
||||
>
|
||||
<h1
|
||||
class="whitespace-nowrap truncate text-center rounded-lg"
|
||||
class="whitespace-nowrap truncate text-center rounded-lg dark:text-black"
|
||||
class:bg-yellow-500={!reach(dataSchema, 'questions[].question').isValidSync(
|
||||
question.question
|
||||
)}
|
||||
@@ -184,7 +190,7 @@
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="h-full flex justify-center w-full"
|
||||
class="h-full flex justify-center w-full dark:text-black"
|
||||
on:click={() => {
|
||||
data.questions = [...data.questions, { ...empty_question }];
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<script lang="ts">
|
||||
import { Dashboard as SvelteDashboard } from '@uppy/svelte';
|
||||
import Uppy from '@uppy/core';
|
||||
import DropTarget from '@uppy/drop-target';
|
||||
import XHRUpload from '@uppy/xhr-upload';
|
||||
import ImageEditor from '@uppy/image-editor';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Compressor from '@uppy/compressor';
|
||||
|
||||
// CSS imports
|
||||
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';
|
||||
|
||||
export let modalOpen = false;
|
||||
export let edit_id: string;
|
||||
export let data: EditorData;
|
||||
export let selected_question: number;
|
||||
|
||||
const uppy = new Uppy()
|
||||
.use(DropTarget, {
|
||||
target: document.body
|
||||
})
|
||||
.use(Dashboard)
|
||||
.use(ImageEditor, {
|
||||
target: Dashboard,
|
||||
quality: 0.8
|
||||
})
|
||||
.use(Compressor, {
|
||||
quality: 0.6
|
||||
})
|
||||
.use(XHRUpload, {
|
||||
endpoint: `/api/v1/editor/image?edit_id=${edit_id}`
|
||||
});
|
||||
const props = { inline: true };
|
||||
let image_id;
|
||||
uppy.on('upload-success', (file, response) => {
|
||||
image_id = response.body.id;
|
||||
});
|
||||
uppy.on('complete', (res) => {
|
||||
data.questions[
|
||||
selected_question
|
||||
].image = `https://${window.location.hostname}/api/v1/storage/download/${image_id}`;
|
||||
modalOpen = false;
|
||||
});
|
||||
console.log(edit_id);
|
||||
</script>
|
||||
|
||||
{#if modalOpen}
|
||||
<div class="w-full h-full absolute top-0 left-0 bg-opacity-60 z-20 flex justify-center">
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
modalOpen = false;
|
||||
}}>Close</button
|
||||
>
|
||||
<div>
|
||||
<SvelteDashboard {uppy} width="100%" {props} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
on:click={() => {
|
||||
modalOpen = true;
|
||||
}}>Add Image</button
|
||||
>
|
||||
{/if}
|
||||
@@ -50,6 +50,7 @@
|
||||
|
||||
let data: Data;
|
||||
let confirm_to_leave = true;
|
||||
let quiz_id = null;
|
||||
onMount(() => {
|
||||
const from_localstorage = localStorage.getItem('create_game');
|
||||
if (from_localstorage === null) {
|
||||
@@ -100,7 +101,7 @@
|
||||
|
||||
{#if data !== undefined}
|
||||
<form on:submit|preventDefault={submit} class="grid grid-cols-1 gap-2">
|
||||
<Editor bind:data />
|
||||
<Editor bind:data bind:quiz_id />
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
{:then _}
|
||||
{#if data !== undefined}
|
||||
<form on:submit|preventDefault={submit} class="grid grid-cols-1 gap-2">
|
||||
<Editor bind:data submit_button_text={$t('words.save')} />
|
||||
<Editor bind:data submit_button_text={$t('words.save')} bind:quiz_id />
|
||||
</form>
|
||||
{/if}
|
||||
{:catch err}
|
||||
|
||||
Reference in New Issue
Block a user