CLASSROOMIO-T-14: Make exercises draggable + replace buttons with icons
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { dndzone } from 'svelte-dnd-action';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import {
|
import {
|
||||||
questionnaire,
|
questionnaire,
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
// import Subtract24 from "carbon-icons-svelte/lib/Subtract24";
|
// import Subtract24 from "carbon-icons-svelte/lib/Subtract24";
|
||||||
|
|
||||||
import TextField from '../../../../Form/TextField.svelte';
|
import TextField from '../../../../Form/TextField.svelte';
|
||||||
|
import Modal from '../../../../Modal/index.svelte';
|
||||||
import TextArea from '../../../../Form/TextArea.svelte';
|
import TextArea from '../../../../Form/TextArea.svelte';
|
||||||
import Checkbox from '../../../../Form/Checkbox.svelte';
|
import Checkbox from '../../../../Form/Checkbox.svelte';
|
||||||
import DateTime from '../../../../Form/DateTime.svelte';
|
import DateTime from '../../../../Form/DateTime.svelte';
|
||||||
@@ -43,11 +45,42 @@
|
|||||||
const extraActions = ['Code Snippets', 'Upload image'];
|
const extraActions = ['Code Snippets', 'Upload image'];
|
||||||
const initialQuestionsLength = $questionnaire.questions.length;
|
const initialQuestionsLength = $questionnaire.questions.length;
|
||||||
|
|
||||||
|
export let editDescription = false;
|
||||||
|
|
||||||
let errors = {};
|
let errors = {};
|
||||||
let questionIdToDelete = null;
|
let questionIdToDelete = null;
|
||||||
|
let questions = [];
|
||||||
|
let isDragging = false;
|
||||||
|
|
||||||
function shouldScrollToLast(index, questions) {
|
function handleDndConsider(e) {
|
||||||
|
questions = e.detail.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDndFinalize(e) {
|
||||||
|
const prevQuestionsByOrder = questions.reduce(
|
||||||
|
(acc, l, index) => ({ ...acc, [l.id]: l.order }),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
questions = e.detail.items;
|
||||||
|
|
||||||
|
// // Only update the lesson order that changed
|
||||||
|
e.detail.items.forEach((item, index) => {
|
||||||
|
const order = index + 1;
|
||||||
|
|
||||||
|
if (order !== prevQuestionsByOrder[item.id]) {
|
||||||
|
$questionnaire.questions.some((q) => {
|
||||||
|
if (q.id === item.id) {
|
||||||
|
q.is_dirty = true;
|
||||||
|
q.order = order;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldScrollToLast(questionId, questions) {
|
||||||
const currentQuestionsLength = questions.length;
|
const currentQuestionsLength = questions.length;
|
||||||
|
const index = questions.findIndex((question) => question.id === questionId);
|
||||||
const isLast = index + 1 === currentQuestionsLength;
|
const isLast = index + 1 === currentQuestionsLength;
|
||||||
|
|
||||||
return isLast && initialQuestionsLength !== currentQuestionsLength;
|
return isLast && initialQuestionsLength !== currentQuestionsLength;
|
||||||
@@ -84,6 +117,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: errors = $questionnaireValidation;
|
$: errors = $questionnaireValidation;
|
||||||
|
$: questions = filterOutDeleted($questionnaire.questions);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DeleteConfirmationModal
|
<DeleteConfirmationModal
|
||||||
@@ -91,64 +125,86 @@
|
|||||||
onDelete={onFinalDeleteClicked}
|
onDelete={onFinalDeleteClicked}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="w-11/12 m-auto">
|
<Modal
|
||||||
<QuestionContainer isTitle={true}>
|
onClose={() => (editDescription = false)}
|
||||||
<TextField
|
bind:open={editDescription}
|
||||||
placeholder="Title"
|
width="w-2/4"
|
||||||
bind:value={$questionnaire.title}
|
modalHeading="Update description"
|
||||||
className="mb-2"
|
>
|
||||||
onChange={() => ($questionnaire.is_title_dirty = true)}
|
<div>
|
||||||
/>
|
<QuestionContainer isTitle={true}>
|
||||||
<DateTime
|
<TextField
|
||||||
label="Due by"
|
placeholder="Title"
|
||||||
className="w-50"
|
bind:value={$questionnaire.title}
|
||||||
value={$questionnaire.due_by}
|
className="mb-2"
|
||||||
onInput={(e) => {
|
onChange={() => ($questionnaire.is_title_dirty = true)}
|
||||||
$questionnaire.due_by = e.target.value;
|
/>
|
||||||
$questionnaire.is_due_by_dirty = true;
|
<DateTime
|
||||||
}}
|
label="Due by"
|
||||||
/>
|
className="w-50"
|
||||||
<EditContent
|
value={$questionnaire.due_by}
|
||||||
writeLabel="Description"
|
onInput={(e) => {
|
||||||
bind:value={$questionnaire.description}
|
$questionnaire.due_by = e.target.value;
|
||||||
placeholder="Start typing your lesson"
|
$questionnaire.is_due_by_dirty = true;
|
||||||
textAreaHeight="100px"
|
}}
|
||||||
onInputChange={() => ($questionnaire.is_description_dirty = true)}
|
/>
|
||||||
/>
|
<EditContent
|
||||||
</QuestionContainer>
|
writeLabel="Description"
|
||||||
|
bind:value={$questionnaire.description}
|
||||||
|
placeholder="Start typing your lesson"
|
||||||
|
textAreaHeight="100px"
|
||||||
|
onInputChange={() => ($questionnaire.is_description_dirty = true)}
|
||||||
|
/>
|
||||||
|
</QuestionContainer>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<div class="w-full">
|
||||||
{#if Object.values(errors).length}
|
{#if Object.values(errors).length}
|
||||||
<div class="w-full flex justify-center mb-4">
|
<div class="w-full flex justify-center mb-4">
|
||||||
<ErrorMessage message="You have some errors" />
|
<ErrorMessage message="You have some errors" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
<div
|
||||||
|
use:dndzone={{
|
||||||
|
items: questions,
|
||||||
|
flipDurationMs: 300,
|
||||||
|
dropTargetStyle: {
|
||||||
|
border: '2px #1d4ed8 solid',
|
||||||
|
'border-style': 'dashed',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
on:consider={handleDndConsider}
|
||||||
|
on:finalize={handleDndFinalize}
|
||||||
|
class="questions pt-6 px-6"
|
||||||
|
>
|
||||||
|
{#each questions as question (question.id)}
|
||||||
|
<!-- {#key question.id} -->
|
||||||
|
<QuestionContainer
|
||||||
|
onClose={onInitDeleteClicked(question.id)}
|
||||||
|
scrollToQuestion={shouldScrollToLast(
|
||||||
|
question.id,
|
||||||
|
$questionnaire.questions
|
||||||
|
)}
|
||||||
|
bind:points={question.points}
|
||||||
|
hasError={!!errors[question.id]}
|
||||||
|
onPointsChange={() => {
|
||||||
|
question.is_dirty = true;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<div class="mr-2 w-3/5">
|
||||||
|
<TextField
|
||||||
|
placeholder="Question"
|
||||||
|
bind:value={question.title}
|
||||||
|
isRequired={true}
|
||||||
|
onChange={() => {
|
||||||
|
question.is_dirty = true;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#each filterOutDeleted($questionnaire.questions) as question, index}
|
<!-- <Dropdown
|
||||||
<!-- {#key question.id}
|
|
||||||
<div out:fly={{ x: 500, duration: 1000 }}> -->
|
|
||||||
<QuestionContainer
|
|
||||||
onClose={onInitDeleteClicked(question.id)}
|
|
||||||
scrollToQuestion={shouldScrollToLast(index, $questionnaire.questions)}
|
|
||||||
bind:points={question.points}
|
|
||||||
hasError={!!errors[question.id]}
|
|
||||||
onPointsChange={() => {
|
|
||||||
question.is_dirty = true;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<div class="mr-2 w-3/5">
|
|
||||||
<TextField
|
|
||||||
placeholder="Question"
|
|
||||||
bind:value={question.title}
|
|
||||||
isRequired={true}
|
|
||||||
onChange={() => {
|
|
||||||
console.log('title changed', question.title);
|
|
||||||
question.is_dirty = true;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <Dropdown
|
|
||||||
options={extraActions}
|
options={extraActions}
|
||||||
handleOptionClick={handleOptionClick(question.id)}
|
handleOptionClick={handleOptionClick(question.id)}
|
||||||
>
|
>
|
||||||
@@ -156,120 +212,132 @@
|
|||||||
<p class="ml-2 text-gray-600">Add</p>
|
<p class="ml-2 text-gray-600">Add</p>
|
||||||
</Dropdown> -->
|
</Dropdown> -->
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
bind:value={question.question_type}
|
bind:value={question.question_type}
|
||||||
options={QUESTION_TYPES}
|
options={QUESTION_TYPES}
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
question.is_dirty = true;
|
question.is_dirty = true;
|
||||||
}}
|
}}
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if typeof question.code === 'string'}
|
|
||||||
<div class="flex justify-between items-center my-3 w-3/5">
|
|
||||||
<TextArea
|
|
||||||
bind:value={question.code}
|
|
||||||
rows="2"
|
|
||||||
placeholder="Write your code"
|
|
||||||
/>
|
/>
|
||||||
<IconButton
|
|
||||||
value="write-code"
|
|
||||||
onClick={() => handleCode(question.id, false)}
|
|
||||||
>
|
|
||||||
<Delete24 class="carbon-icon" />
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="flex flex-col mt-2">
|
{#if typeof question.code === 'string'}
|
||||||
{#if QUESTION_TYPE.RADIO === question.question_type.id}
|
<div class="flex justify-between items-center my-3 w-3/5">
|
||||||
{#each filterOutDeleted(question.options) as option}
|
<TextArea
|
||||||
<RadioItem
|
bind:value={question.code}
|
||||||
isEditable={true}
|
rows="2"
|
||||||
name={question.title || 'radio-name'}
|
placeholder="Write your code"
|
||||||
bind:label={option.label}
|
/>
|
||||||
onChange={addDynamicValue(question.id, option.id)}
|
<IconButton
|
||||||
|
value="write-code"
|
||||||
|
onClick={() => handleCode(question.id, false)}
|
||||||
>
|
>
|
||||||
<div slot="iconbutton" class="flex items-center">
|
<Delete24 class="carbon-icon" />
|
||||||
<IconButton
|
</IconButton>
|
||||||
value={option.id}
|
</div>
|
||||||
onClick={handleRemoveOption(question.id, option.id)}
|
|
||||||
>
|
|
||||||
<Delete24 class="carbon-icon" />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
value={option.id}
|
|
||||||
onClick={handleAnswerSelect(question.id, option.id)}
|
|
||||||
buttonClassName={option.is_correct && 'success'}
|
|
||||||
>
|
|
||||||
{#if option.is_correct}
|
|
||||||
<CheckmarkFilled24 class="carbon-icon" />
|
|
||||||
{:else}
|
|
||||||
<CheckmarkOutline24 class="carbon-icon" />
|
|
||||||
{/if}
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
</RadioItem>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if QUESTION_TYPE.CHECKBOX === question.question_type.id}
|
<div class="flex flex-col mt-2">
|
||||||
{#each filterOutDeleted(question.options) as option}
|
{#if QUESTION_TYPE.RADIO === question.question_type.id}
|
||||||
<Checkbox
|
{#each filterOutDeleted(question.options) as option}
|
||||||
isEditable={true}
|
<RadioItem
|
||||||
name={question || 'checkbox-name'}
|
isEditable={true}
|
||||||
bind:label={option.label}
|
name={question.title || 'radio-name'}
|
||||||
onChange={addDynamicValue(question.id, option.id)}
|
bind:label={option.label}
|
||||||
>
|
onChange={addDynamicValue(question.id, option.id)}
|
||||||
<div slot="iconbutton" class="flex items-center">
|
>
|
||||||
<IconButton
|
<div slot="iconbutton" class="flex items-center">
|
||||||
value={option.id}
|
<IconButton
|
||||||
onClick={handleRemoveOption(question.id, option.id)}
|
value={option.id}
|
||||||
>
|
onClick={handleRemoveOption(question.id, option.id)}
|
||||||
<Delete24 class="carbon-icon" />
|
>
|
||||||
</IconButton>
|
<Delete24 class="carbon-icon" />
|
||||||
<IconButton
|
</IconButton>
|
||||||
value={option.id}
|
<IconButton
|
||||||
onClick={handleAnswerSelect(question.id, option.id)}
|
value={option.id}
|
||||||
buttonClassName={option.is_correct && 'success'}
|
onClick={handleAnswerSelect(question.id, option.id)}
|
||||||
>
|
buttonClassName={option.is_correct && 'success'}
|
||||||
{#if option.is_correct}
|
>
|
||||||
<CheckmarkFilled24 class="carbon-icon" />
|
{#if option.is_correct}
|
||||||
{:else}
|
<CheckmarkFilled24 class="carbon-icon" />
|
||||||
<CheckmarkOutline24 class="carbon-icon" />
|
{:else}
|
||||||
{/if}
|
<CheckmarkOutline24 class="carbon-icon" />
|
||||||
</IconButton>
|
{/if}
|
||||||
</div>
|
</IconButton>
|
||||||
</Checkbox>
|
</div>
|
||||||
{/each}
|
</RadioItem>
|
||||||
{/if}
|
{/each}
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if QUESTION_TYPE.TEXTAREA === question.question_type.id}
|
{#if QUESTION_TYPE.CHECKBOX === question.question_type.id}
|
||||||
<TextArea bind:value={question.value} />
|
{#each filterOutDeleted(question.options) as option}
|
||||||
{/if}
|
<Checkbox
|
||||||
|
isEditable={true}
|
||||||
|
name={question || 'checkbox-name'}
|
||||||
|
bind:label={option.label}
|
||||||
|
onChange={addDynamicValue(question.id, option.id)}
|
||||||
|
>
|
||||||
|
<div slot="iconbutton" class="flex items-center">
|
||||||
|
<IconButton
|
||||||
|
value={option.id}
|
||||||
|
onClick={handleRemoveOption(question.id, option.id)}
|
||||||
|
>
|
||||||
|
<Delete24 class="carbon-icon" />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
value={option.id}
|
||||||
|
onClick={handleAnswerSelect(question.id, option.id)}
|
||||||
|
buttonClassName={option.is_correct && 'success'}
|
||||||
|
>
|
||||||
|
{#if option.is_correct}
|
||||||
|
<CheckmarkFilled24 class="carbon-icon" />
|
||||||
|
{:else}
|
||||||
|
<CheckmarkOutline24 class="carbon-icon" />
|
||||||
|
{/if}
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</Checkbox>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if getQuestionErrorMsg(errors, question, 'option')}
|
{#if QUESTION_TYPE.TEXTAREA === question.question_type.id}
|
||||||
<ErrorMessage
|
<TextArea bind:value={question.value} />
|
||||||
message={getQuestionErrorMsg(errors, question, 'option')}
|
{/if}
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if QUESTION_TYPE.TEXTAREA !== question.question_type.id}
|
{#if getQuestionErrorMsg(errors, question, 'option')}
|
||||||
<div class="flex items-center mt-3">
|
<ErrorMessage
|
||||||
<PrimaryButton
|
message={getQuestionErrorMsg(errors, question, 'option')}
|
||||||
disablePadding={true}
|
/>
|
||||||
className="p-2"
|
{/if}
|
||||||
variant={VARIANTS.OUTLINED}
|
|
||||||
onClick={handleAddOption(question.id)}
|
|
||||||
>
|
|
||||||
<AddFilled24 class="carbon-icon" />
|
|
||||||
<p class="ml-2">Add option</p>
|
|
||||||
</PrimaryButton>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
</QuestionContainer>
|
{#if QUESTION_TYPE.TEXTAREA !== question.question_type.id}
|
||||||
<!-- </div>
|
<div class="flex items-center mt-3">
|
||||||
{/key} -->
|
<PrimaryButton
|
||||||
{/each}
|
disablePadding={true}
|
||||||
|
className="p-2"
|
||||||
|
variant={VARIANTS.OUTLINED}
|
||||||
|
onClick={handleAddOption(question.id)}
|
||||||
|
>
|
||||||
|
<AddFilled24 class="carbon-icon" />
|
||||||
|
<p class="ml-2">Add option</p>
|
||||||
|
</PrimaryButton>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</QuestionContainer>
|
||||||
|
|
||||||
|
<!-- {/key} -->
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.questions {
|
||||||
|
height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(#dnd-action-dragged-el div) {
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -91,7 +91,7 @@
|
|||||||
|
|
||||||
function getTotalPossibleGrade() {
|
function getTotalPossibleGrade() {
|
||||||
return $questionnaire.questions.reduce((acc, question) => {
|
return $questionnaire.questions.reduce((acc, question) => {
|
||||||
acc += parseInt(question.points, 10);
|
acc += parseFloat(question.points, 10);
|
||||||
return acc;
|
return acc;
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import AddAlt16 from 'carbon-icons-svelte/lib/AddAlt16';
|
import AddAlt24 from 'carbon-icons-svelte/lib/AddAlt24';
|
||||||
|
import Delete24 from 'carbon-icons-svelte/lib/Delete24';
|
||||||
|
import Settings24 from 'carbon-icons-svelte/lib/Settings24';
|
||||||
|
import View32 from 'carbon-icons-svelte/lib/View32';
|
||||||
|
import ViewFilled32 from 'carbon-icons-svelte/lib/ViewFilled32';
|
||||||
import {
|
import {
|
||||||
handleAddQuestion,
|
handleAddQuestion,
|
||||||
questionnaire,
|
questionnaire,
|
||||||
@@ -9,6 +13,7 @@
|
|||||||
} from '../store/exercise';
|
} from '../store/exercise';
|
||||||
import PrimaryButton from '../../../../PrimaryButton/index.svelte';
|
import PrimaryButton from '../../../../PrimaryButton/index.svelte';
|
||||||
import PageBody from '../../../../PageBody/index.svelte';
|
import PageBody from '../../../../PageBody/index.svelte';
|
||||||
|
import IconButton from '../../../../IconButton/index.svelte';
|
||||||
import RoleBasedSecurity from '../../../../RoleBasedSecurity/index.svelte';
|
import RoleBasedSecurity from '../../../../RoleBasedSecurity/index.svelte';
|
||||||
import ViewMode from './ViewMode.svelte';
|
import ViewMode from './ViewMode.svelte';
|
||||||
import EditMode from './EditMode.svelte';
|
import EditMode from './EditMode.svelte';
|
||||||
@@ -21,6 +26,7 @@
|
|||||||
|
|
||||||
let mode = MODES.view;
|
let mode = MODES.view;
|
||||||
let preview = false;
|
let preview = false;
|
||||||
|
let editDescription = false;
|
||||||
|
|
||||||
async function handleMode() {
|
async function handleMode() {
|
||||||
if (mode === MODES.edit) {
|
if (mode === MODES.edit) {
|
||||||
@@ -47,6 +53,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleDeleteExercise() {}
|
||||||
|
|
||||||
function onCancel() {
|
function onCancel() {
|
||||||
mode = MODES.view;
|
mode = MODES.view;
|
||||||
refetchExercise();
|
refetchExercise();
|
||||||
@@ -57,7 +65,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PageBody>
|
<PageBody padding="px-4">
|
||||||
<svelte:fragment slot="header">
|
<svelte:fragment slot="header">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<RoleBasedSecurity allowedRoles="[1,2]">
|
<RoleBasedSecurity allowedRoles="[1,2]">
|
||||||
@@ -78,25 +86,48 @@
|
|||||||
</RoleBasedSecurity>
|
</RoleBasedSecurity>
|
||||||
|
|
||||||
{#if mode !== MODES.edit}
|
{#if mode !== MODES.edit}
|
||||||
<PrimaryButton
|
<IconButton
|
||||||
variant={preview ? VARIANTS.CONTAINED : VARIANTS.OUTLINED}
|
|
||||||
label={'Toggle Preview'}
|
|
||||||
onClick={() => (preview = !preview)}
|
onClick={() => (preview = !preview)}
|
||||||
/>
|
toolTipProps={{ title: 'Toggle preview', direction: 'right' }}
|
||||||
|
selected={preview}
|
||||||
|
contained={true}
|
||||||
|
>
|
||||||
|
{#if preview}
|
||||||
|
<ViewFilled32 class="carbon-icon" />
|
||||||
|
{:else}
|
||||||
|
<View32 class="carbon-icon" />
|
||||||
|
{/if}
|
||||||
|
</IconButton>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if mode === MODES.edit}
|
{#if mode === MODES.edit}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<PrimaryButton variant={VARIANTS.OUTLINED} onClick={handleAddQuestion}>
|
<IconButton
|
||||||
Add Question <AddAlt16 class="carbon-icon" />
|
onClick={() => (editDescription = true)}
|
||||||
</PrimaryButton>
|
toolTipProps={{ title: 'Edit description', direction: 'bottom' }}
|
||||||
|
>
|
||||||
|
<Settings24 class="carbon-icon" />
|
||||||
|
</IconButton>
|
||||||
|
<!-- <IconButton
|
||||||
|
onClick={handleDeleteExercise}
|
||||||
|
toolTipProps={{ title: 'Delete questionnaire', direction: 'bottom' }}
|
||||||
|
>
|
||||||
|
<Delete24 class="carbon-icon" />
|
||||||
|
</IconButton> -->
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
onClick={handleAddQuestion}
|
||||||
|
toolTipProps={{ title: 'Add question', direction: 'bottom' }}
|
||||||
|
>
|
||||||
|
<AddAlt24 class="carbon-icon" />
|
||||||
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
|
||||||
{#if mode === MODES.edit && !preview}
|
{#if mode === MODES.edit && !preview}
|
||||||
<EditMode />
|
<EditMode bind:editDescription />
|
||||||
{:else}
|
{:else}
|
||||||
<ViewMode {preview} {exerciseId} />
|
<ViewMode {preview} {exerciseId} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
);
|
);
|
||||||
return question;
|
return question;
|
||||||
});
|
});
|
||||||
|
data.questions = data.questions.sort((a, b) => a.order - b.order);
|
||||||
|
|
||||||
data.is_title_dirty = false;
|
data.is_title_dirty = false;
|
||||||
data.is_description_dirty = false;
|
data.is_description_dirty = false;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<div class="root mt-4 mx-auto {width} {className} relative {padding}">
|
<div class="root mt-4 mx-auto {width} {className} relative {padding}">
|
||||||
{#if $$slots.header}
|
{#if $$slots.header}
|
||||||
<div
|
<div
|
||||||
class="head flex items-center justify-between sticky right-0 w-full py-3"
|
class="head flex items-center justify-between sticky right-0 w-full p-3"
|
||||||
>
|
>
|
||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
</div>
|
</div>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<style>
|
<style>
|
||||||
.root {
|
.root {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
height: 85vh;
|
height: 90vh;
|
||||||
}
|
}
|
||||||
.head {
|
.head {
|
||||||
top: 0px;
|
top: 0px;
|
||||||
|
|||||||
@@ -29,15 +29,15 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
class="{isTitle ? 'border' : 'border-2'} {hasError
|
class="bg-white border {hasError
|
||||||
? 'border-red-700'
|
? 'border-red-700'
|
||||||
: 'border-gray hover:border-blue-700'} border-r-2 rounded-md mb-6 relative root"
|
: 'border-gray hover:border-blue-700'} rounded-md mb-6 relative root"
|
||||||
>
|
>
|
||||||
{#if isTitle}
|
{#if isTitle}
|
||||||
<div class="title absolute bg-blue-700" />
|
<div class="title absolute bg-blue-700" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if onClose}
|
{#if onClose && !isTitle}
|
||||||
<div class="close-button hidden absolute -top-6 -right-6">
|
<div class="close-button hidden absolute -top-6 -right-6">
|
||||||
<CloseButton onClick={onClose} contained={true} />
|
<CloseButton onClick={onClose} contained={true} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,15 @@
|
|||||||
|
|
||||||
{#if !!title}
|
{#if !!title}
|
||||||
<div class="tooltip">
|
<div class="tooltip">
|
||||||
<span class="tooltiptext z-20 {direction === 'right' && 'right'}">
|
<span
|
||||||
|
class="tooltiptext z-20 {direction === 'right'
|
||||||
|
? 'right'
|
||||||
|
: direction === 'top'
|
||||||
|
? 'top'
|
||||||
|
: direction === 'bottom'
|
||||||
|
? 'bottom'
|
||||||
|
: 'left'}"
|
||||||
|
>
|
||||||
{title}
|
{title}
|
||||||
{#if Array.isArray(hotkeys) && hotkeys.length}
|
{#if Array.isArray(hotkeys) && hotkeys.length}
|
||||||
<span class="shortcut">
|
<span class="shortcut">
|
||||||
@@ -29,11 +37,17 @@
|
|||||||
left: 100%;
|
left: 100%;
|
||||||
right: unset !important;
|
right: unset !important;
|
||||||
}
|
}
|
||||||
|
.bottom {
|
||||||
|
bottom: -75% !important;
|
||||||
|
left: -80%;
|
||||||
|
right: unset !important;
|
||||||
|
top: unset !important;
|
||||||
|
}
|
||||||
.tooltip .tooltiptext {
|
.tooltip .tooltiptext {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
font-size: 0.625rem;
|
font-size: 0.625rem;
|
||||||
font-family: "Roboto", "Helvetica", "Arial", sans-serif;
|
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 1.4em;
|
line-height: 1.4em;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ export async function upsertExercise(questionnaire, exerciseId) {
|
|||||||
question_type,
|
question_type,
|
||||||
options,
|
options,
|
||||||
deleted_at,
|
deleted_at,
|
||||||
|
order,
|
||||||
points,
|
points,
|
||||||
is_dirty,
|
is_dirty,
|
||||||
} = question;
|
} = question;
|
||||||
@@ -239,6 +240,7 @@ export async function upsertExercise(questionnaire, exerciseId) {
|
|||||||
name: isNew(id) ? undefined : name,
|
name: isNew(id) ? undefined : name,
|
||||||
title,
|
title,
|
||||||
points,
|
points,
|
||||||
|
order,
|
||||||
question_type_id: question_type.id,
|
question_type_id: question_type.id,
|
||||||
exercise_id: exerciseId,
|
exercise_id: exerciseId,
|
||||||
};
|
};
|
||||||
@@ -256,7 +258,7 @@ export async function upsertExercise(questionnaire, exerciseId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (questionSupabaseRes) {
|
if (questionSupabaseRes) {
|
||||||
const { question_type_id, id, name } = questionSupabaseRes;
|
const { question_type_id, id, name, order } = questionSupabaseRes;
|
||||||
|
|
||||||
// Delete cause this is not a field in the table
|
// Delete cause this is not a field in the table
|
||||||
delete newQuestion.question_type_id;
|
delete newQuestion.question_type_id;
|
||||||
@@ -264,6 +266,7 @@ export async function upsertExercise(questionnaire, exerciseId) {
|
|||||||
newQuestion.question_type = { id: question_type_id };
|
newQuestion.question_type = { id: question_type_id };
|
||||||
newQuestion.id = id;
|
newQuestion.id = id;
|
||||||
newQuestion.name = name;
|
newQuestion.name = name;
|
||||||
|
newQuestion.order = order;
|
||||||
newQuestion.options = [];
|
newQuestion.options = [];
|
||||||
|
|
||||||
// Don't map options for 'Paragraph' questions
|
// Don't map options for 'Paragraph' questions
|
||||||
|
|||||||
Reference in New Issue
Block a user