:wip: Quick Save

This commit is contained in:
Mawoka
2022-12-21 17:47:27 +01:00
parent 3420504076
commit 07f242e6cd
10 changed files with 461 additions and 105 deletions
+6
View File
@@ -11,6 +11,8 @@
import SettingsCard from '$lib/editor/settings-card.svelte';
import QuizCard from '$lib/editor/card.svelte';
import Spinner from './Spinner.svelte';
import { elementSelection } from '$lib/stores.js';
import ElementSelection from '$lib/editor/slides/element_selection.svelte';
let schemaInvalid = false;
let yupErrorMessage = '';
@@ -187,3 +189,7 @@
</div>
</form>
{/await}
{#if $elementSelection.data === null}
<ElementSelection />
{/if}
+111 -103
View File
@@ -12,6 +12,7 @@
import Spinner from '../Spinner.svelte';
import { createTippy } from 'svelte-tippy';
import { getLocalization } from '$lib/i18n';
import Slide from './slide.svelte';
const { t } = getLocalization();
@@ -51,9 +52,9 @@
*/
</script>
<div class="w-full h-full pb-20 px-20">
<div class="w-full max-h-full pb-20 px-20 h-full">
<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="h-12 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"
@@ -66,113 +67,120 @@
/>
</div>
</div>
<div class="flex flex-col">
<div class="flex justify-center pt-10 w-full">
{#await import('$lib/inline-editor.svelte')}
<Spinner my_20={false} />
{:then c}
<div
class="rounded-lg placeholder:italic placeholder:font-normal dark:bg-gray-500"
class:bg-yellow-500={!reach(dataSchema, 'questions[].question').isValidSync(
data.questions[selected_question].question
)}
>
<svelte:component
this={c.default}
bind:text={data.questions[selected_question].question}
{#if data.questions[selected_question].time}
<div class="flex flex-col">
<div class="flex justify-center pt-10 w-full">
{#await import('$lib/inline-editor.svelte')}
<Spinner my_20={false} />
{:then c}
<div
class="rounded-lg placeholder:italic placeholder:font-normal dark:bg-gray-500"
class:bg-yellow-500={!reach(
dataSchema,
'questions[].question'
).isValidSync(data.questions[selected_question].question)}
>
<svelte:component
this={c.default}
bind:text={data.questions[selected_question].question}
/>
</div>
{/await}
</div>
{#if data.questions[selected_question].image != undefined && data.questions[selected_question].image !== ''}
<div class="flex justify-center pt-10 w-full max-h-72 w-full">
<img
src={data.questions[selected_question].image}
alt="not available"
class="max-h-72 h-auto w-auto"
on:contextmenu|preventDefault={() => {
data.questions[selected_question].image = '';
}}
/>
</div>
{/await}
</div>
{#if data.questions[selected_question].image != undefined && data.questions[selected_question].image !== ''}
<div class="flex justify-center pt-10 w-full max-h-72 w-full">
<img
src={data.questions[selected_question].image}
alt="not available"
class="max-h-72 h-auto w-auto"
on:contextmenu|preventDefault={() => {
data.questions[selected_question].image = '';
}}
/>
</div>
{:else if pow_data === undefined}
<a
href="/docs/pow"
target="_blank"
use:tippy={{ content: "Click to learn why it's loading so long." }}
class="cursor-help"
>
<Spinner my_20={false} />
</a>
{:else}
{#await import('$lib/editor/uploader.svelte')}
<Spinner my_20={false} />
{:then c}
<svelte:component
this={c.default}
bind:modalOpen={uppyOpen}
bind:edit_id
bind:data
bind:selected_question
bind:pow_data
bind:pow_salt
/>
{/await}
{/if}
<div class="flex justify-center pt-10 w-full">
<div>
<svg
class="w-8 h-8 inline-block"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
{:else if pow_data === undefined}
<a
href="/docs/pow"
target="_blank"
use:tippy={{ content: "Click to learn why it's loading so long." }}
class="cursor-help"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
<Spinner my_20={false} />
</a>
{:else}
{#await import('$lib/editor/uploader.svelte')}
<Spinner my_20={false} />
{:then c}
<svelte:component
this={c.default}
bind:modalOpen={uppyOpen}
bind:edit_id
bind:data
bind:selected_question
bind:pow_data
bind:pow_salt
/>
</svg>
<input
type="number"
max="999"
min="1"
class="w-20 bg-transparent rounded-lg text-lg border-2 border-gray-500 p-1"
bind:value={data.questions[selected_question].time}
/>
</div>
</div>
<div class="flex justify-center pt-10">
<select
class="p-2 rounded-lg bg-gray-800 focus:ring-2 ring-blue-600 text-white"
name="Answer-Type"
bind:value={data.questions[selected_question].type}
>
<option value={QuizQuestionType.RANGE}>{$t('words.range')}</option>
<option value={QuizQuestionType.ABCD}>{$t('words.multiple_choice')}</option>
<option value={QuizQuestionType.VOTING}>{$t('words.voting')}</option>
</select>
</div>
<div class="flex justify-center pt-10 w-full">
{#if data.questions[selected_question].type === QuizQuestionType.ABCD}
{#await import('$lib/editor/ABCDEditorPart.svelte')}
<Spinner my_20={false} />
{:then c}
<svelte:component this={c.default} bind:data bind:selected_question />
{/await}
{:else if data.questions[selected_question].type === QuizQuestionType.RANGE}
<RangeEditor bind:selected_question bind:data />
{:else if data.questions[selected_question].type === QuizQuestionType.VOTING}
{#await import('$lib/editor/VotingEditorPart.svelte')}
<Spinner my_20={false} />
{:then c}
<svelte:component this={c.default} bind:data bind:selected_question />
{/await}
{/if}
<div class="flex justify-center pt-10 w-full">
<div>
<svg
class="w-8 h-8 inline-block"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<input
type="number"
max="999"
min="1"
class="w-20 bg-transparent rounded-lg text-lg border-2 border-gray-500 p-1"
bind:value={data.questions[selected_question].time}
/>
</div>
</div>
<div class="flex justify-center pt-10">
<select
class="p-2 rounded-lg bg-gray-800 focus:ring-2 ring-blue-600 text-white"
name="Answer-Type"
bind:value={data.questions[selected_question].type}
>
<option value={QuizQuestionType.RANGE}>{$t('words.range')}</option>
<option value={QuizQuestionType.ABCD}>{$t('words.multiple_choice')}</option>
<option value={QuizQuestionType.VOTING}>{$t('words.voting')}</option>
</select>
</div>
<div class="flex justify-center pt-10 w-full">
{#if data.questions[selected_question].type === QuizQuestionType.ABCD}
{#await import('$lib/editor/ABCDEditorPart.svelte')}
<Spinner my_20={false} />
{:then c}
<svelte:component this={c.default} bind:data bind:selected_question />
{/await}
{:else if data.questions[selected_question].type === QuizQuestionType.RANGE}
<RangeEditor bind:selected_question bind:data />
{:else if data.questions[selected_question].type === QuizQuestionType.VOTING}
{#await import('$lib/editor/VotingEditorPart.svelte')}
<Spinner my_20={false} />
{:then c}
<svelte:component this={c.default} bind:data bind:selected_question />
{/await}
{/if}
</div>
<p class="italic text-center mt-auto pt-4">
{$t('editor_page.right_click_to_delete')}
</p>
</div>
<p class="italic text-center mt-auto pt-4">{$t('editor_page.right_click_to_delete')}</p>
</div>
{:else}
<Slide bind:data={data.questions[selected_question]} />
{/if}
</div>
</div>
+23 -1
View File
@@ -242,7 +242,7 @@
</div>
{/each}
<div
class="bg-white shadow rounded-lg h-40 p-2 mb-6 hover:cursor-pointer drop-shadow-2xl border border-gray-500 dark:bg-gray-600"
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 grid grid-cols-2"
>
<button
type="button"
@@ -266,5 +266,27 @@
/>
</svg>
</button>
<button
type="button"
class="h-full flex justify-center w-full dark:text-black"
on:click={() => {
data.questions = [...data.questions, { ...{} }];
}}
>
<svg
class="h-full"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/>
</svg>
</button>
</div>
</div>
+205
View File
@@ -0,0 +1,205 @@
<!--
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
-->
<script lang="ts">
import TextElement from './slides/types/text.svelte';
import HeadlineElement from './slides/types/text.svelte';
import { ElementTypes } from './types';
import { elementSelection } from '$lib/stores';
export let data;
let selected_el_id = undefined;
elementSelection.set({ data: undefined });
const ghost_elements = new Array(72).fill({});
let elements: Array<{
height: number;
width: number;
col: number;
row: number;
type: ElementTypes;
data;
}> = [];
let elements_binds = [];
const get_location = (id: number): { row: number; col: number } => {
const numColumns = 12;
const row = Math.floor(id / numColumns);
const col = id % numColumns;
return { row, col };
};
const add_text_field = (id: number) => {};
const change_size = (position: string, id: number, increase: boolean) => {
if (position === 't') {
elements[id].row += increase ? -1 : 1;
elements[id].height += increase ? 1 : -1;
} else if (position === 'b') {
elements[id].height += increase ? 1 : -1;
} else if (position === 'l') {
elements[id].width += increase ? 1 : -1;
elements[id].col += increase ? -1 : 1;
} else if (position === 'r') {
elements[id].width += increase ? 1 : -1;
}
if (elements[id].width < 1 || elements[id].height < 1) {
elements.splice(id, 1);
}
if (elements[id].row < 0) {
elements[id].row = 0;
}
if (elements[id].col < 0) {
elements[id].col = 0;
}
console.log(elements[id]);
};
let occupied_fields = [];
elementSelection.subscribe((data) => {
if (data.data && selected_el_id) {
console.log('data!', data.data);
const { row, col } = get_location(selected_el_id);
elements = [...elements, { height: 1, width: 1, col, row, type: data.data, data: '' }];
elementSelection.set({ data: undefined });
occupied_fields.push(selected_el_id);
selected_el_id = undefined;
}
});
</script>
<div class="flex h-full">
<div
class="grid grid-cols-12 grid-rows-6 w-full h-full pb-12 absolute overflow-hidden relative max-h-full"
>
{#each elements as el, i}
<div
class="z-20 overflow-auto relative group"
element_id={i}
style="grid-column-start: {el.col + 1}; grid-row-start: {el.row +
1};grid-column-end: {el.col + 1 + el.width}; grid-row-end: {el.row +
1 +
el.height}"
bind:this={elements_binds[i]}
>
<div
class="absolute top-0 left-0 mx-auto justify-center w-full h-0 hidden group-hover:flex"
>
<div>
<button
class="text-black z-30"
on:click={() => {
change_size('t', i, true);
}}
disabled={el.row === 0}
>+
</button>
<button
class="text-black z-30"
on:click={() => {
change_size('t', i, false);
}}>-</button
>
</div>
</div>
<div
class="absolute bottom-6 left-0 mx-auto justify-center w-full h-0 hidden group-hover:flex"
>
<div>
<button
class="text-black z-30"
on:click={() => {
change_size('b', i, true);
}}
disabled={el.row + el.height === 6}
>+
</button>
<button
class="text-black z-30"
on:click={() => {
change_size('b', i, false);
}}>-</button
>
</div>
</div>
<div
class="absolute top-0 left-3 mx-auto justify-center h-full w-0 hidden group-hover:flex"
>
<div class="my-auto">
<button
class="text-black z-30"
on:click={() => {
change_size('l', i, true);
}}
disabled={el.col === 0}
>+
</button>
<button
class="text-black z-30"
on:click={() => {
change_size('l', i, false);
}}>-</button
>
</div>
</div>
<div
class="absolute top-0 right-3 mx-auto justify-center h-full w-0 hidden group-hover:flex"
>
<div class="my-auto">
<button
class="text-black z-30"
on:click={() => {
change_size('r', i, true);
}}
disabled={el.col + el.width === 12}
>+
</button>
<button
class="text-black z-30"
on:click={() => {
change_size('r', i, false);
}}>-</button
>
</div>
</div>
{#if el.type === ElementTypes.Text}
<p>Text</p>
<TextElement />
{:else if el.type === ElementTypes.Headline}
<p>Headline</p>
<HeadlineElement bind:data={el.data} />
{/if}
</div>
{/each}
</div>
<div class="grid grid-cols-12 grid-rows-6 w-full h-full pb-12 absolute overflow-hidden">
{#each ghost_elements as el, i}
<div class="w-full border border-gray-600 group">
<button
class="h-full flex justify-center w-full dark:text-black invisible group-hover:visible"
on:click={() => {
selected_el_id = i;
elementSelection.set({ data: null });
}}
>
{i}
<svg
class="h-12 m-auto"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/>
</svg>
</button>
</div>
{/each}
</div>
</div>
@@ -0,0 +1,77 @@
<!--
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
-->
<script lang="ts">
import { ElementTypes } from '$lib/editor/types';
import { elementSelection } from '$lib/stores';
import { onMount } from 'svelte';
const keybinding_list = {
t: ElementTypes.Text,
h: ElementTypes.Headline
// "i": ElementTypes.Image
};
const keypress_listener = (key: KeyboardEvent) => {
const sel_type = keybinding_list[key.key];
if (sel_type) {
elementSelection.set({ data: sel_type });
} else if (key.code === 'Escape') {
elementSelection.set({ data: undefined });
}
};
onMount(() => {
document.body.addEventListener('keydown', keypress_listener);
});
const element_list: Array<{
name: string;
description: string;
type: ElementTypes;
icon: string | undefined;
shortcut: string;
}> = [
{
name: 'Headline',
description: 'A bold text for headlines',
type: ElementTypes.Headline,
icon: undefined,
shortcut: 'h'
},
{
name: 'Text',
description: 'Smaller longer text',
type: ElementTypes.Text,
icon: undefined,
shortcut: 't'
}
];
</script>
<div
class="fixed top-0 left-0 flex justify-center w-screen h-screen bg-black bg-opacity-60 z-50 text-black"
>
<div class="w-5/6 h-5/6 bg-white m-auto rounded-lg shadow-lg p-4 flex flex-col">
<div class="flex justify-center">
<h1 class="text-2xl">Select the element you want to add</h1>
</div>
<ul>
{#each element_list as el}
<li
class="flex flex-row mt-4 w-full bg-gray-200 shadow-xl rounded-lg p-2 hover:bg-gray-300 hover:shadow-2xl hover:cursor-pointer transition"
on:click={() => {
elementSelection.set({ data: el.type });
}}
>
<div class="flex flex-col">
<h3 class="text-xl">{el.name}</h3>
<p>{el.description}</p>
</div>
<kbd class="my-auto ml-auto">{el.shortcut}</kbd>
</li>
{/each}
</ul>
</div>
</div>
@@ -0,0 +1,10 @@
<!--
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
-->
<script lang="ts">
export let data;
</script>
<input bind:value={data} type="text" class="w-full h-full dark:text-black text-4xl text-center" />
@@ -0,0 +1,16 @@
<!--
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
-->
<script lang="ts">
export let text;
</script>
<!--
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
-->
<textarea bind:value={text} class="w-full h-full dark:text-black resize-none" />
+11
View File
@@ -0,0 +1,11 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
export enum ElementTypes {
Text = 'TEXT',
Headline = 'HEADLINE',
Image = 'IMAGE'
}
+1 -1
View File
@@ -52,7 +52,7 @@ export interface EditorData {
public: boolean;
title: string;
description: string;
questions: Question[];
questions: (Question | object)[];
cover_image?: string;
background_color?: string;
}
+1
View File
@@ -11,6 +11,7 @@ export const signedIn = writable(false);
export const pathname = writable('/');
export const alertModal = writable({ open: false, title: '', body: '' });
export const elementSelection = writable({ data: undefined });
import { goto } from '$app/navigation';
import { page } from '$app/stores';