skillbox/client/src/components/content-block-form/ContentElement.vue

364 lines
9.7 KiB
Vue

<!-- Element used to display Blocks in a ContentForm -->
<template>
<div class="content-element">
<!-- Element Chooser if element has chooser type or no type -->
<content-block-element-chooser-widget
:class="['content-element__component', 'content-element__chooser']"
v-bind="element"
v-if="isChooser"
@change-type="changeType"
@remove="$emit('remove', false)"
/>
<!-- Content Forms -->
<content-form-section
:title="title"
:actions="actions"
v-else
@top="$emit('top')"
@up="$emit('up')"
@down="$emit('down')"
@bottom="$emit('bottom')"
@remove="$emit('remove')"
>
<div class="content-element__section">
<!-- Form depending on type of element -->
<component
:class="['content-element__component']"
v-bind="element"
:is="component"
@change-text="changeText"
@link-change-url="changeUrl"
@change-url="changeUrl"
@switch-to-document="switchToDocument"
@assignment-change-title="changeAssignmentTitle"
@assignment-change-assignment="changeAssignmentAssignment"
@assignment-change-solution="changeAssignmentSolution"
/>
</div>
</content-form-section>
</div>
</template>
<script lang="ts">
import { defineAsyncComponent } from 'vue';
import ContentFormSection from '@/components/content-block-form/ContentFormSection.vue';
import ContentElementActions from '@/components/content-block-form/ContentElementActions.vue';
const TrashIcon = defineAsyncComponent(() => import('@/components/icons/TrashIcon.vue'));
const ContentBlockElementChooserWidget = defineAsyncComponent(
() => import('@/components/content-forms/ContentBlockElementChooserWidget.vue')
);
const LinkForm = defineAsyncComponent(() => import('@/components/content-forms/LinkForm.vue'));
const VideoForm = defineAsyncComponent(() => import('@/components/content-forms/VideoForm.vue'));
const ImageForm = defineAsyncComponent(() => import('@/components/content-forms/ImageForm.vue'));
const DocumentForm = defineAsyncComponent(() => import('@/components/content-forms/DocumentForm.vue'));
const AssignmentForm = defineAsyncComponent(() => import('@/components/content-forms/AssignmentForm.vue'));
const TextForm = defineAsyncComponent(
() => import(/* webpackChunkName: "content-forms" */ '@/components/content-forms/TipTap.vue')
);
const SubtitleForm = defineAsyncComponent(() => import('@/components/content-forms/SubtitleForm.vue'));
const SolutionForm = defineAsyncComponent(() => import('@/components/content-forms/SolutionForm.vue'));
// readonly blocks
const Assignment = defineAsyncComponent(() => import('@/components/content-blocks/assignment/Assignment.vue'));
const SurveyBlock = defineAsyncComponent(() => import('@/components/content-blocks/SurveyBlock.vue'));
const Solution = defineAsyncComponent(() => import('@/components/content-blocks/Solution.vue'));
const ImageBlock = defineAsyncComponent(() => import('@/components/content-blocks/ImageBlock.vue'));
const Instruction = defineAsyncComponent(() => import('@/components/content-blocks/Instruction.vue'));
const ModuleRoomSlug = defineAsyncComponent(() => import('@/components/content-blocks/ModuleRoomSlug.vue'));
const CmsDocumentBlock = defineAsyncComponent(() => import('@/components/content-blocks/CmsDocumentBlock.vue'));
const ThinglinkBlock = defineAsyncComponent(() => import('@/components/content-blocks/ThinglinkBlock.vue'));
const InfogramBlock = defineAsyncComponent(() => import('@/components/content-blocks/InfogramBlock.vue'));
const CHOOSER = 'content-block-element-chooser-widget';
export default {
props: {
element: {
type: Object,
default: null,
},
// is this element at the top level, or is it nested? we assume top level
topLevel: {
type: Boolean,
default: true,
},
firstElement: {
type: Boolean,
required: true,
},
lastElement: {
type: Boolean,
required: true,
},
},
components: {
ContentElementActions,
ContentFormSection,
TrashIcon,
ContentBlockElementChooserWidget,
LinkForm,
VideoForm,
ImageForm,
DocumentForm,
AssignmentForm,
TextForm,
SubtitleForm,
SurveyBlock,
Solution,
ImageBlock,
Instruction,
ModuleRoomSlug,
CmsDocumentBlock,
InfogramBlock,
ThinglinkBlock,
Assignment,
SolutionForm,
},
computed: {
actions() {
return {
up: !this.firstElement,
down: !this.lastElement,
extended: this.topLevel,
};
},
isChooser() {
return this.component === CHOOSER;
},
type() {
return this.getType(this.element);
},
component() {
return this.type.component;
},
title() {
return this.type.title;
},
icon() {
return this.type.icon;
},
},
methods: {
getType(element) {
switch (element.type) {
case 'subtitle':
return {
component: 'subtitle-form',
title: 'Untertitel',
icon: 'title-icon',
};
case 'link_block':
return {
component: 'link-form',
title: 'Link',
icon: 'link-icon',
};
case 'video_block':
return {
component: 'video-form',
title: 'Video',
icon: 'video-icon',
};
case 'image_url_block':
return {
component: 'image-form',
title: 'Bild',
icon: 'image-icon',
};
case 'text_block':
return {
component: 'text-form',
title: 'Text',
icon: 'text-icon',
};
case 'assignment':
return {
component: 'assignment-form',
title: 'Aufgabe & Ergebnis',
icon: 'speech-bubble-icon',
};
case 'document_block':
return {
component: 'document-form',
title: 'Dokument',
icon: 'document-icon',
};
case 'survey':
return {
component: 'survey-block',
title: 'Übung',
};
case 'solution':
return {
component: 'solution-form',
title: 'Lösung',
};
case 'image_block':
return {
component: 'image-block',
title: 'Bild',
};
case 'instruction':
return {
component: 'instruction',
title: 'Instruktion',
};
case 'module_room_slug':
return {
component: 'module-room-slug',
title: 'Raum',
};
case 'cms_document_block':
return {
component: 'cms-document-block',
title: 'Dokument',
};
case 'thinglink_block':
return {
component: 'thinglink-block',
title: 'Interaktive Grafik',
};
case 'infogram_block':
return {
component: 'infogram-block',
title: 'Interaktive Grafik',
};
}
return {
component: CHOOSER,
title: '',
icon: '',
};
},
_updateProperty(value, key) {
// const content = this.localContentBlock.contents[index];
const content = this.element;
this.update({
...content,
value: {
...content.value,
[key]: value,
},
});
},
changeUrl(value) {
this._updateProperty(value, 'url');
},
changeText(value) {
this._updateProperty(value, 'text');
},
changeAssignmentTitle(value) {
this._updateProperty(value, 'title');
},
changeAssignmentAssignment(value) {
this._updateProperty(value, 'assignment');
},
changeAssignmentSolution(value) {
this._updateProperty(value, 'solution');
},
changeType({ type, convertToList }, value) {
let el = {
type: type,
value: Object.assign({}, value),
};
switch (type) {
case 'subtitle':
el = {
...el,
value: {
text: '',
},
};
break;
case 'text_block':
el = {
...el,
value: {
text: '',
},
};
break;
case 'link_block':
el = {
...el,
value: {
text: '',
url: '',
},
};
break;
case 'video_block':
el = {
...el,
value: {
url: '',
},
};
break;
case 'document_block':
el = {
...el,
value: Object.assign(
{
url: '',
},
value
),
};
break;
case 'image_url_block':
el = {
...el,
value: {
url: '',
},
};
break;
}
if (convertToList) {
el = {
type: 'content_list_item',
contents: [el],
};
}
this.update(el);
},
update(element) {
this.$emit('update', element);
},
switchToDocument(value) {
this.changeType('document_block', value);
},
},
};
</script>
<style scoped lang="scss">
@import 'styles/helpers';
.content-element {
display: flex;
flex-direction: column;
&__actions {
display: inline-flex;
justify-self: flex-end;
align-self: flex-end;
}
&__section {
display: grid;
//grid-template-columns: 1fr 50px;
grid-auto-rows: auto;
/*width: 95%; // reserve space for scrollbar*/
}
&__chooser {
grid-column: 1 / span 2;
}
}
</style>