Add new solution form to element chooser workflow
This commit is contained in:
parent
9e84908d10
commit
db628eec24
|
|
@ -1,3 +1,4 @@
|
|||
<!-- Element used to display Blocks in a ContentForm -->
|
||||
<template>
|
||||
<div class="content-element">
|
||||
<!-- Element Chooser if element has chooser type or no type -->
|
||||
|
|
@ -38,65 +39,35 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<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';
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
|
||||
const TrashIcon = defineAsyncComponent(() => import('@/components/icons/TrashIcon.vue'));
|
||||
const ContentBlockElementChooserWidget = defineAsyncComponent(() =>
|
||||
import('@/components/content-forms/ContentBlockElementChooserWidget.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 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 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 {
|
||||
|
|
@ -141,8 +112,8 @@ export default {
|
|||
InfogramBlock,
|
||||
ThinglinkBlock,
|
||||
Assignment,
|
||||
SolutionForm,
|
||||
},
|
||||
|
||||
computed: {
|
||||
actions() {
|
||||
return {
|
||||
|
|
@ -220,7 +191,7 @@ export default {
|
|||
};
|
||||
case 'solution':
|
||||
return {
|
||||
component: 'solution',
|
||||
component: 'solution-form',
|
||||
title: 'Lösung',
|
||||
};
|
||||
case 'image_block':
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
|
||||
interface Props {
|
||||
type: string;
|
||||
icon: string;
|
||||
|
|
@ -29,15 +29,15 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
|
||||
const emit = defineEmits(['select']);
|
||||
|
||||
const defaultTitle = (type:string) => {
|
||||
return type.replace(/^\w/, (c :string) => c.toUpperCase());
|
||||
}
|
||||
const defaultTitle = (type: string) => {
|
||||
return type.replace(/^\w/, (c: string) => c.toUpperCase());
|
||||
};
|
||||
|
||||
const defaultIcon = (type:string) => {
|
||||
const defaultIcon = (type: string) => {
|
||||
return `${type}-icon`;
|
||||
}
|
||||
};
|
||||
|
||||
const subclass =`chooser-element--${props.type}`;
|
||||
const subclass = `chooser-element--${props.type}`;
|
||||
const cy = `choose-${props.type}-widget`;
|
||||
|
||||
const realTitle = computed(() => {
|
||||
|
|
@ -55,7 +55,7 @@ export default {
|
|||
components: {
|
||||
...formElementIcons,
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
|
@ -65,19 +65,20 @@ export default {
|
|||
cursor: pointer;
|
||||
border: 1px solid $color-silver;
|
||||
border-radius: 4px;
|
||||
height: 105px;
|
||||
width: 105px;
|
||||
height: 120px;
|
||||
flex-direction: column;
|
||||
flex: 0 0 calc(25% - $medium-spacing * 3 / 4);
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr 45px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
gap: $small-spacing;
|
||||
|
||||
&__icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
align-self: end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import Checkbox from '@/components/ui/Checkbox.vue';
|
||||
|
||||
import formElementIcons from '@/components/ui/form-element-icons';
|
||||
|
|
@ -112,6 +112,12 @@ export default {
|
|||
title: 'Dokument',
|
||||
show: hasDefaultFeatures,
|
||||
},
|
||||
{
|
||||
type: 'solution',
|
||||
block: 'solution',
|
||||
title: 'Lösung',
|
||||
icon: 'tick-circle-icon',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
|
|
@ -143,14 +149,11 @@ export default {
|
|||
@import 'styles/helpers';
|
||||
|
||||
.content-block-element-chooser-widget {
|
||||
display: -ms-grid;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@supports (display: grid) {
|
||||
display: grid;
|
||||
}
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
-ms-grid-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
grid-column-gap: 0;
|
||||
-ms-grid-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
gap: $medium-spacing;
|
||||
font-family: $sans-serif-font-family;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
<template>
|
||||
<div
|
||||
class="document-input"
|
||||
ref="documentform"
|
||||
>
|
||||
<div
|
||||
v-if="!url"
|
||||
ref="uploadcarePanel"
|
||||
/>
|
||||
<div
|
||||
class="document-input__spinner"
|
||||
v-if="loading"
|
||||
>
|
||||
<loading-icon class="document-input__loading-icon" />
|
||||
</div>
|
||||
<div
|
||||
class="document-input__uploaded"
|
||||
v-if="url"
|
||||
>
|
||||
<document-icon class="document-input__icon" />
|
||||
<a
|
||||
:href="previewUrl"
|
||||
class="document-input__link"
|
||||
target="_blank"
|
||||
>{{ previewLink }}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import LoadingIcon from '@/components/icons/LoadingIcon.vue';
|
||||
import { uploadcare } from '@/helpers/uploadcare';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
const DocumentIcon = defineAsyncComponent(() => import('@/components/icons/DocumentIcon.vue'));
|
||||
const loading = ref(false);
|
||||
const documentform = ref(null);
|
||||
const uploadcarePanel = ref(null);
|
||||
|
||||
export interface Props {
|
||||
url: string;
|
||||
}
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const previewUrl = computed(() => {
|
||||
return props.url ? props.url : '';
|
||||
});
|
||||
const previewLink = computed(() => {
|
||||
const url = props.url;
|
||||
if (url > '') {
|
||||
const parts = url.split('/');
|
||||
return parts[parts.length - 1];
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update']);
|
||||
|
||||
onMounted(() => {
|
||||
uploadcare(
|
||||
{
|
||||
$refs: {
|
||||
'uploadcare-panel': uploadcarePanel.value,
|
||||
documentform: documentform.value,
|
||||
},
|
||||
},
|
||||
(url: string) => {
|
||||
emit('update', url);
|
||||
loading.value = false;
|
||||
},
|
||||
() => {
|
||||
loading.value = true;
|
||||
}
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
/* todo: do this with a mixin, but in postcss (we have one in scss) */
|
||||
@keyframes spin {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.document-input {
|
||||
&__uploaded {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
&__link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
&__spinner {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__loading-icon {
|
||||
/* todo: make this work with postcss
|
||||
// @include spin;
|
||||
*/
|
||||
animation: spin 2.5s linear infinite;
|
||||
fill: var(--color-silver-dark);
|
||||
}
|
||||
|
||||
&__icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-right: var(--small-spacing);
|
||||
}
|
||||
|
||||
&__file-input {
|
||||
width: 0.1px;
|
||||
height: 0.1px;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
|
||||
& + label {
|
||||
cursor: pointer;
|
||||
background-color: var(--color-silver-light);
|
||||
height: 150px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-family: var(--sans-serif-font-family);
|
||||
font-weight: var(--regular-font-weight);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<div class="solution-form">
|
||||
<textarea
|
||||
placeholder="Lösung erfassen"
|
||||
class="skillbox-textarea"
|
||||
:value="text"
|
||||
@input="$emit('change-text', $event.target.value, index)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
export interface Props {
|
||||
value: any;
|
||||
index: number;
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
value: null,
|
||||
index: -1,
|
||||
});
|
||||
defineEmits(['change-text']);
|
||||
|
||||
const text = computed(() => {
|
||||
// todo: refactor this and use the helper, just copied it from TextForm.vue
|
||||
return props.value.text ? props.value.text.replace(/<br(\/)?>/, '\n').replace(/(<([^>]+)>)/gi, '') : '';
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.solution-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--small-spacing);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -3,29 +3,16 @@ const LinkIcon = defineAsyncComponent(() => import('@/components/icons/LinkIcon.
|
|||
const VideoIcon = defineAsyncComponent(() => import('@/components/icons/VideoIcon.vue'));
|
||||
const ImageIcon = defineAsyncComponent(() => import('@/components/icons/ImageIcon.vue'));
|
||||
const TextIcon = defineAsyncComponent(() => import('@/components/icons/TextIcon.vue'));
|
||||
const SpeechBubbleIcon = defineAsyncComponent(() =>
|
||||
import('@/components/icons/SpeechBubbleIcon.vue')
|
||||
);
|
||||
const DocumentIcon = defineAsyncComponent(() =>
|
||||
import('@/components/icons/DocumentIcon.vue')
|
||||
);
|
||||
const SpeechBubbleIcon = defineAsyncComponent(() => import('@/components/icons/SpeechBubbleIcon.vue'));
|
||||
const DocumentIcon = defineAsyncComponent(() => import('@/components/icons/DocumentIcon.vue'));
|
||||
const TitleIcon = defineAsyncComponent(() => import('@/components/icons/TitleIcon.vue'));
|
||||
const DocumentWithLinesIcon = defineAsyncComponent(() =>
|
||||
import('@/components/icons/DocumentWithLinesIcon.vue')
|
||||
);
|
||||
const ArrowThinBottom = defineAsyncComponent(() =>
|
||||
import('@/components/icons/ArrowThinBottom.vue')
|
||||
);
|
||||
const ArrowThinDown = defineAsyncComponent(() =>
|
||||
import('@/components/icons/ArrowThinDown.vue')
|
||||
);
|
||||
const ArrowThinTop = defineAsyncComponent(() =>
|
||||
import('@/components/icons/ArrowThinTop.vue')
|
||||
);
|
||||
const ArrowThinUp = defineAsyncComponent(() =>
|
||||
import('@/components/icons/ArrowThinUp.vue')
|
||||
);
|
||||
const DocumentWithLinesIcon = defineAsyncComponent(() => import('@/components/icons/DocumentWithLinesIcon.vue'));
|
||||
const ArrowThinBottom = defineAsyncComponent(() => import('@/components/icons/ArrowThinBottom.vue'));
|
||||
const ArrowThinDown = defineAsyncComponent(() => import('@/components/icons/ArrowThinDown.vue'));
|
||||
const ArrowThinTop = defineAsyncComponent(() => import('@/components/icons/ArrowThinTop.vue'));
|
||||
const ArrowThinUp = defineAsyncComponent(() => import('@/components/icons/ArrowThinUp.vue'));
|
||||
const TrashIcon = defineAsyncComponent(() => import('@/components/icons/TrashIcon.vue'));
|
||||
const TickCircleIcon = defineAsyncComponent(() => import('@/components/icons/TickCircleIcon.vue'));
|
||||
|
||||
/*
|
||||
for icons with a single word, leave the *-icon name, to prevent conflicts
|
||||
|
|
@ -45,4 +32,5 @@ export default {
|
|||
ArrowThinTop,
|
||||
ArrowThinUp,
|
||||
TrashIcon,
|
||||
TickCircleIcon,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue