Add edit route for custom content blocks
This commit is contained in:
parent
fdb408de84
commit
f563483b79
|
|
@ -85,6 +85,7 @@
|
||||||
import {CONTENT_TYPE} from '@/consts/types';
|
import {CONTENT_TYPE} from '@/consts/types';
|
||||||
import PopoverLink from '@/components/ui/PopoverLink';
|
import PopoverLink from '@/components/ui/PopoverLink';
|
||||||
import {removeAtIndex} from '@/graphql/immutable-operations';
|
import {removeAtIndex} from '@/graphql/immutable-operations';
|
||||||
|
import {EDIT_CONTENT_BLOCK_PAGE} from '@/router/module.names';
|
||||||
const ContentComponent = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/ContentComponent');
|
const ContentComponent = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/ContentComponent');
|
||||||
|
|
||||||
const instruments = {
|
const instruments = {
|
||||||
|
|
@ -207,7 +208,13 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
editContentBlock(contentBlock) {
|
editContentBlock(contentBlock) {
|
||||||
this.$store.dispatch('editContentBlock', contentBlock.id);
|
const route = {
|
||||||
|
name: EDIT_CONTENT_BLOCK_PAGE,
|
||||||
|
params: {
|
||||||
|
id: contentBlock.id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.$router.push(route);
|
||||||
},
|
},
|
||||||
deleteContentBlock(contentBlock) {
|
deleteContentBlock(contentBlock) {
|
||||||
const parent = this.parent;
|
const parent = this.parent;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,308 @@
|
||||||
|
<template>
|
||||||
|
<div class="content-block-form content-list__parent">
|
||||||
|
<div class="content-block-form__content">
|
||||||
|
<h1
|
||||||
|
class="heading-1 content-block-form__heading"
|
||||||
|
data-cy="content-block-form-heading"
|
||||||
|
>
|
||||||
|
{{ title }}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<!-- Assignment toggle / checkbox -->
|
||||||
|
<toggle
|
||||||
|
:bordered="false"
|
||||||
|
:checked="localContentBlock.isAssignment"
|
||||||
|
class="content-block-form__task-toggle"
|
||||||
|
label="Inhaltsblock als Auftrag formatieren"
|
||||||
|
@input="localContentBlock.isAssignment=$event"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Form for title of content block -->
|
||||||
|
<content-form-section title="Titel">
|
||||||
|
<input-with-label
|
||||||
|
:value="localContentBlock.title"
|
||||||
|
label="Name"
|
||||||
|
placeholder="z.B. Auftrag 3"
|
||||||
|
@input="localContentBlock.title=$event"
|
||||||
|
/>
|
||||||
|
</content-form-section>
|
||||||
|
|
||||||
|
<!-- Add content at top of content block -->
|
||||||
|
<add-content-link
|
||||||
|
class="content-block-form__segment"
|
||||||
|
@click="addBlock(-1)"
|
||||||
|
/>
|
||||||
|
<!-- Loop for outer contents layer -->
|
||||||
|
<div
|
||||||
|
class="content-list content-list--creator content-block-form__segment"
|
||||||
|
v-for="(block, outer) in localContentBlock.contents"
|
||||||
|
:key="block.id"
|
||||||
|
>
|
||||||
|
<!-- If the block is a content list -->
|
||||||
|
<ol
|
||||||
|
class="content-list__item content-block-form__segment"
|
||||||
|
data-cy="content-list-item"
|
||||||
|
v-if="block.type === 'content_list_item'"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="content-block-form__segment"
|
||||||
|
v-for="(content, index) in block.contents"
|
||||||
|
:key="content.id"
|
||||||
|
>
|
||||||
|
<content-element
|
||||||
|
:element="content"
|
||||||
|
class="content-block-form__segment"
|
||||||
|
@update="update(index, $event, outer)"
|
||||||
|
@remove="remove(outer, index)"
|
||||||
|
@up="up(outer, index)"
|
||||||
|
@down="down(outer, index)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<add-content-link
|
||||||
|
class="content-block-form__add-button"
|
||||||
|
@click="addBlock(outer, index)"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<!-- If the block is a single element -->
|
||||||
|
<content-element
|
||||||
|
:element="block"
|
||||||
|
class="content-block-form__segment"
|
||||||
|
v-else
|
||||||
|
@update="update(outer, $event)"
|
||||||
|
@remove="remove(outer)"
|
||||||
|
@up="up(outer)"
|
||||||
|
@down="down(outer)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Add element after the looped item -->
|
||||||
|
<add-content-link
|
||||||
|
class="content-block-form__segment"
|
||||||
|
@click="addBlock(outer)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Save and Cancel buttons -->
|
||||||
|
<footer class="content-block-form__footer">
|
||||||
|
<button
|
||||||
|
:disabled="!isValid"
|
||||||
|
class="button button--primary"
|
||||||
|
@click="save(localContentBlock)"
|
||||||
|
>
|
||||||
|
Speichern
|
||||||
|
</button>
|
||||||
|
<a
|
||||||
|
class="button"
|
||||||
|
@click="$emit('back')"
|
||||||
|
>Abbrechen</a>
|
||||||
|
</footer>
|
||||||
|
</div><!-- -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Toggle from '@/components/ui/Toggle';
|
||||||
|
import ContentFormSection from '@/components/content-block-form/ContentFormSection';
|
||||||
|
import InputWithLabel from '@/components/ui/InputWithLabel';
|
||||||
|
import AddContentLink from '@/components/content-block-form/AddContentLink';
|
||||||
|
|
||||||
|
import ContentElement from '@/components/content-block-form/ContentElement';
|
||||||
|
import {removeAtIndex, replaceAtIndex, swapElements} from '@/graphql/immutable-operations';
|
||||||
|
|
||||||
|
import {CHOOSER, transformInnerContents} from '@/components/content-block-form/helpers';
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
contentBlock: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ContentElement,
|
||||||
|
AddContentLink,
|
||||||
|
InputWithLabel,
|
||||||
|
ContentFormSection,
|
||||||
|
Toggle,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
localContentBlock: Object.assign({}, {
|
||||||
|
title: this.contentBlock.title,
|
||||||
|
// contents: [...this.contentBlock.contents],
|
||||||
|
contents: transformInnerContents([...this.contentBlock.contents]),
|
||||||
|
id: this.contentBlock.id || undefined,
|
||||||
|
isAssignment: this.contentBlock.type && this.contentBlock.type.toLowerCase() === 'task',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isValid() {
|
||||||
|
return this.localContentBlock.title > '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
update(index, element, parent) {
|
||||||
|
if (parent === undefined) {
|
||||||
|
// element is top level
|
||||||
|
this.localContentBlock.contents = [
|
||||||
|
...this.localContentBlock.contents.slice(0, index),
|
||||||
|
element,
|
||||||
|
...this.localContentBlock.contents.slice(index + 1)
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
const parentBlock = this.localContentBlock.contents[parent];
|
||||||
|
|
||||||
|
this.localContentBlock.contents = [
|
||||||
|
...this.localContentBlock.contents.slice(0, parent),
|
||||||
|
{
|
||||||
|
...parentBlock,
|
||||||
|
contents: [
|
||||||
|
...parentBlock.contents.slice(0, index),
|
||||||
|
element,
|
||||||
|
...parentBlock.contents.slice(index + 1),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
...this.localContentBlock.contents.slice(parent + 1)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addBlock(afterOuterIndex, innerIndex) {
|
||||||
|
if (innerIndex !== undefined) {
|
||||||
|
const block = this.localContentBlock.contents[afterOuterIndex];
|
||||||
|
this.localContentBlock.contents = [
|
||||||
|
...this.localContentBlock.contents.slice(0, afterOuterIndex),
|
||||||
|
{
|
||||||
|
...block,
|
||||||
|
contents: [
|
||||||
|
...block.contents.slice(0, innerIndex + 1),
|
||||||
|
{
|
||||||
|
id: -1,
|
||||||
|
type: CHOOSER,
|
||||||
|
},
|
||||||
|
...block.contents.slice(innerIndex + 1)
|
||||||
|
]
|
||||||
|
},
|
||||||
|
...this.localContentBlock.contents.slice(afterOuterIndex + 1)
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
this.localContentBlock.contents = [
|
||||||
|
...this.localContentBlock.contents.slice(0, afterOuterIndex+1),
|
||||||
|
{
|
||||||
|
id: -1,
|
||||||
|
type: CHOOSER,
|
||||||
|
includeListOption: true
|
||||||
|
},
|
||||||
|
...this.localContentBlock.contents.slice(afterOuterIndex+1)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
remove(outer, inner) {
|
||||||
|
this.$modal.open('confirm')
|
||||||
|
.then(() => {
|
||||||
|
this.executeRemoval(outer, inner);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
shift(outer, inner, distance) {
|
||||||
|
if (inner === undefined) {
|
||||||
|
this.localContentBlock.contents = swapElements(this.localContentBlock.contents, outer, outer + distance);
|
||||||
|
} else {
|
||||||
|
const {contents} = this.localContentBlock;
|
||||||
|
const outerElement = contents[outer];
|
||||||
|
const newOuterElement = {
|
||||||
|
...outerElement,
|
||||||
|
contents: swapElements(outerElement.contents, inner, inner + distance)
|
||||||
|
};
|
||||||
|
this.localContentBlock.contents = replaceAtIndex(contents, outer, newOuterElement);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
up(outer, inner){
|
||||||
|
this.shift(outer, inner, -1);
|
||||||
|
},
|
||||||
|
down(outer, inner){
|
||||||
|
this.shift(outer, inner, 1);
|
||||||
|
},
|
||||||
|
executeRemoval(outer, inner) {
|
||||||
|
if (inner === undefined) {
|
||||||
|
// not a list item container, just remove the element from the outer array
|
||||||
|
this.localContentBlock.contents = removeAtIndex(this.localContentBlock.contents, outer);
|
||||||
|
} else {
|
||||||
|
let prevInnerContents = this.localContentBlock.contents[outer].contents;
|
||||||
|
let innerContents = removeAtIndex(prevInnerContents, inner);
|
||||||
|
|
||||||
|
if (innerContents.length) {
|
||||||
|
/*
|
||||||
|
there is still an element inside the outer element after removal,
|
||||||
|
so we replace the previous element in the outer array with the new one with fewer contents
|
||||||
|
*/
|
||||||
|
let element = {
|
||||||
|
...this.localContentBlock.contents[outer],
|
||||||
|
contents: innerContents
|
||||||
|
};
|
||||||
|
this.localContentBlock.contents = replaceAtIndex(this.localContentBlock.contents, outer, element);
|
||||||
|
} else {
|
||||||
|
// inner contents is now empty, remove the whole element from the outer array
|
||||||
|
this.localContentBlock.contents = removeAtIndex(this.localContentBlock.contents, outer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
save(contentBlock) {
|
||||||
|
this.$emit('save', contentBlock);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '~styles/helpers';
|
||||||
|
|
||||||
|
.content-block-form {
|
||||||
|
width: 800px;
|
||||||
|
max-width: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 800px;
|
||||||
|
grid-template-rows: 1fr auto;
|
||||||
|
grid-template-areas:
|
||||||
|
'content'
|
||||||
|
'footer';
|
||||||
|
|
||||||
|
&__heading {
|
||||||
|
@include heading-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__task-toggle {
|
||||||
|
margin-bottom: $large-spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__add-button {
|
||||||
|
}
|
||||||
|
|
||||||
|
&__segment {
|
||||||
|
margin-bottom: $large-spacing;
|
||||||
|
:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
grid-area: content;
|
||||||
|
overflow-x: visible;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
margin-top: auto;
|
||||||
|
grid-area: footer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
import store from '@/store/index';
|
import store from '@/store/index';
|
||||||
|
|
||||||
import EDIT_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/mutateContentBlock.gql';
|
import EDIT_CONTENT_BLOCK_MUTATION from 'gql/mutations/editContentBlock.gql';
|
||||||
import MODULE_DETAILS_QUERY from '@/graphql/gql/queries/modules/moduleDetailsQuery.gql';
|
import MODULE_DETAILS_QUERY from '@/graphql/gql/queries/modules/moduleDetailsQuery.gql';
|
||||||
import CONTENT_BLOCK_QUERY from '@/graphql/gql/queries/contentBlockQuery.gql';
|
import CONTENT_BLOCK_QUERY from '@/graphql/gql/queries/contentBlockQuery.gql';
|
||||||
import { setUserBlockType } from '@/helpers/content-block';
|
import { setUserBlockType } from '@/helpers/content-block';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import {setUserBlockType} from '@/helpers/content-block';
|
||||||
|
import NEW_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/addContentBlock.gql';
|
||||||
|
import MODULE_DETAILS_QUERY from '@/graphql/gql/queries/modules/moduleDetailsQuery.gql';
|
||||||
|
|
||||||
|
export const CHOOSER = 'content-block-element-chooser-widget';
|
||||||
|
export const chooserFilter = value => value.type !== CHOOSER;
|
||||||
|
|
||||||
|
export const cleanUpContents = (contents) => {
|
||||||
|
let filteredContents = contents
|
||||||
|
.filter(chooserFilter); // only use items that are not chooser elements
|
||||||
|
|
||||||
|
return filteredContents.map(content => {
|
||||||
|
// if the element has a contents property, it's a list of contents, filter them
|
||||||
|
if (content.contents) {
|
||||||
|
return {
|
||||||
|
...content,
|
||||||
|
contents: content.contents.filter(chooserFilter)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// else just return it
|
||||||
|
return content;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// transform value prop to contents, to better handle the input type on the server
|
||||||
|
export const transformInnerContents = (contents) => {
|
||||||
|
let ret = [];
|
||||||
|
for (let content of contents) {
|
||||||
|
if (Array.isArray(content.value)) {
|
||||||
|
const {value, ...contentWithoutValue} = content;
|
||||||
|
ret.push({
|
||||||
|
...contentWithoutValue,
|
||||||
|
contents: value
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ret.push(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
@ -5,7 +5,7 @@ import {
|
||||||
OBJECTIVE_GROUP_TYPE,
|
OBJECTIVE_GROUP_TYPE,
|
||||||
OBJECTIVE_TYPE
|
OBJECTIVE_TYPE
|
||||||
} from '@/consts/types';
|
} from '@/consts/types';
|
||||||
import CHANGE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/mutateContentBlock.gql';
|
import EDIT_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/editContentBlock.gql';
|
||||||
import UPDATE_OBJECTIVE_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateObjectiveVisibility.gql';
|
import UPDATE_OBJECTIVE_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateObjectiveVisibility.gql';
|
||||||
import UPDATE_OBJECTIVE_GROUP_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateObjectiveGroupVisibility.gql';
|
import UPDATE_OBJECTIVE_GROUP_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateObjectiveGroupVisibility.gql';
|
||||||
import UPDATE_CHAPTER_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateChapterVisibility.gql';
|
import UPDATE_CHAPTER_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateChapterVisibility.gql';
|
||||||
|
|
@ -14,7 +14,7 @@ export const createVisibilityMutation = (type, id, visibility) => {
|
||||||
let mutation, variables;
|
let mutation, variables;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case CONTENT_TYPE:
|
case CONTENT_TYPE:
|
||||||
mutation = CHANGE_CONTENT_BLOCK_MUTATION;
|
mutation = EDIT_CONTENT_BLOCK_MUTATION;
|
||||||
variables = {
|
variables = {
|
||||||
input: {
|
input: {
|
||||||
id,
|
id,
|
||||||
|
|
|
||||||
|
|
@ -1,108 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="create-content-block content-list__parent">
|
<content-block-form
|
||||||
<div class="create-content-block__content">
|
title="Inhaltsblock erfassen"
|
||||||
<h1 class="heading-1 create-content-block__heading">
|
:content-block="contentBlock"
|
||||||
Inhaltsblock erfassen
|
@back="goToModule"
|
||||||
</h1>
|
@save="save"
|
||||||
|
/>
|
||||||
<toggle
|
|
||||||
:bordered="false"
|
|
||||||
:checked="contentBlock.isAssignment"
|
|
||||||
class="create-content-block__task-toggle"
|
|
||||||
label="Inhaltsblock als Auftrag formatieren"
|
|
||||||
@input="contentBlock.isAssignment=$event"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<content-form-section title="Titel">
|
|
||||||
<input-with-label
|
|
||||||
:value="contentBlock.title"
|
|
||||||
label="Name"
|
|
||||||
placeholder="z.B. Auftrag 3"
|
|
||||||
@input="contentBlock.title=$event"
|
|
||||||
/>
|
|
||||||
</content-form-section>
|
|
||||||
|
|
||||||
<add-content-link
|
|
||||||
class="create-content-block__segment"
|
|
||||||
@click="addBlock(-1)"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="content-list content-list--creator create-content-block__segment"
|
|
||||||
v-for="(block, outer) in contentBlock.contents"
|
|
||||||
:key="block.id"
|
|
||||||
>
|
|
||||||
<ol
|
|
||||||
class="content-list__item create-content-block__segment"
|
|
||||||
v-if="block.type === 'content_list_item'"
|
|
||||||
>
|
|
||||||
<li
|
|
||||||
class="create-content-block__segment"
|
|
||||||
v-for="(content, index) in block.contents"
|
|
||||||
:key="content.id"
|
|
||||||
>
|
|
||||||
<content-element
|
|
||||||
:element="content"
|
|
||||||
class="create-content-block__segment"
|
|
||||||
@update="update(index, $event, outer)"
|
|
||||||
@remove="remove(outer, index)"
|
|
||||||
@up="up(outer, index)"
|
|
||||||
@down="down(outer, index)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<add-content-link
|
|
||||||
class="create-content-block__add-button"
|
|
||||||
@click="addBlock(outer, index)"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
<content-element
|
|
||||||
:element="block"
|
|
||||||
class="create-content-block__segment"
|
|
||||||
v-else
|
|
||||||
@update="update(outer, $event)"
|
|
||||||
@remove="remove(outer)"
|
|
||||||
@up="up(outer)"
|
|
||||||
@down="down(outer)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<add-content-link
|
|
||||||
class="create-content-block__segment"
|
|
||||||
@click="addBlock(outer)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer class="create-content-block__footer">
|
|
||||||
<button
|
|
||||||
:disabled="!isValid"
|
|
||||||
class="button button--primary"
|
|
||||||
@click="save(contentBlock)"
|
|
||||||
>
|
|
||||||
Speichern
|
|
||||||
</button>
|
|
||||||
<a
|
|
||||||
class="button"
|
|
||||||
@click="goToModule"
|
|
||||||
>Abbrechen</a>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Toggle from '@/components/ui/Toggle';
|
|
||||||
import ContentFormSection from '@/components/content-block-form/ContentFormSection';
|
|
||||||
import InputWithLabel from '@/components/ui/InputWithLabel';
|
|
||||||
import AddContentLink from '@/components/content-block-form/AddContentLink';
|
|
||||||
import ContentElement from '@/components/content-block-form/ContentElement';
|
|
||||||
|
|
||||||
import NEW_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/addContentBlock.gql';
|
import ContentBlockForm from '@/components/content-block-form/ContentBlockForm';
|
||||||
import {setUserBlockType} from '@/helpers/content-block';
|
import {setUserBlockType} from '@/helpers/content-block';
|
||||||
|
import NEW_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/addContentBlock.gql';
|
||||||
import MODULE_DETAILS_QUERY from '@/graphql/gql/queries/modules/moduleDetailsQuery.gql';
|
import MODULE_DETAILS_QUERY from '@/graphql/gql/queries/modules/moduleDetailsQuery.gql';
|
||||||
import {insertAtIndex, removeAtIndex, replaceAtIndex, swapElements} from '@/graphql/immutable-operations';
|
import {cleanUpContents} from '@/components/content-block-form/helpers';
|
||||||
|
|
||||||
const CHOOSER = 'content-block-element-chooser-widget';
|
|
||||||
const chooserFilter = value => value.type !== CHOOSER;
|
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -117,161 +29,32 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
ContentElement,
|
ContentBlockForm,
|
||||||
AddContentLink,
|
|
||||||
InputWithLabel,
|
|
||||||
ContentFormSection,
|
|
||||||
Toggle,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
contentBlock: {
|
contentBlock: {
|
||||||
title: '',
|
title: '',
|
||||||
isAssignment: false,
|
isAssignment: false,
|
||||||
contents: [
|
contents: [
|
||||||
{}
|
]},
|
||||||
]},
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
computed: {
|
|
||||||
isValid() {
|
|
||||||
return this.contentBlock.title > '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
update(index, element, parent) {
|
|
||||||
if (parent === undefined) {
|
|
||||||
this.contentBlock.contents = [
|
|
||||||
...this.contentBlock.contents.slice(0, index),
|
|
||||||
element,
|
|
||||||
...this.contentBlock.contents.slice(index + 1)
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
const parentBlock = this.contentBlock.contents[parent];
|
|
||||||
this.contentBlock.contents = [
|
|
||||||
...this.contentBlock.contents.slice(0, parent),
|
|
||||||
{
|
|
||||||
...parentBlock,
|
|
||||||
contents: [
|
|
||||||
...parentBlock.contents.slice(0, index),
|
|
||||||
element,
|
|
||||||
...parentBlock.contents.slice(index + 1),
|
|
||||||
]
|
|
||||||
},
|
|
||||||
...this.contentBlock.contents.slice(parent + 1)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addBlock(afterOuterIndex, innerIndex) {
|
|
||||||
if (innerIndex !== undefined) {
|
|
||||||
const block = this.contentBlock.contents[afterOuterIndex];
|
|
||||||
this.contentBlock.contents = [
|
|
||||||
...this.contentBlock.contents.slice(0, afterOuterIndex),
|
|
||||||
{
|
|
||||||
...block,
|
|
||||||
contents: [
|
|
||||||
...block.contents.slice(0, innerIndex + 1),
|
|
||||||
{
|
|
||||||
id: -1,
|
|
||||||
type: CHOOSER,
|
|
||||||
},
|
|
||||||
...block.contents.slice(innerIndex + 1)
|
|
||||||
]
|
|
||||||
},
|
|
||||||
...this.contentBlock.contents.slice(afterOuterIndex + 1)
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
this.contentBlock.contents = [
|
|
||||||
...this.contentBlock.contents.slice(0, afterOuterIndex+1),
|
|
||||||
{
|
|
||||||
id: -1,
|
|
||||||
type: CHOOSER,
|
|
||||||
includeListOption: true
|
|
||||||
},
|
|
||||||
...this.contentBlock.contents.slice(afterOuterIndex+1)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
remove(outer, inner) {
|
|
||||||
this.$modal.open('confirm')
|
|
||||||
.then(() => {
|
|
||||||
this.executeRemoval(outer, inner);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
|
|
||||||
});
|
|
||||||
},
|
|
||||||
shift(outer, inner, distance) {
|
|
||||||
if (inner === undefined) {
|
|
||||||
this.contentBlock.contents = swapElements(this.contentBlock.contents, outer, outer + distance);
|
|
||||||
} else {
|
|
||||||
const {contents} = this.contentBlock;
|
|
||||||
const outerElement = contents[outer];
|
|
||||||
const newOuterElement = {
|
|
||||||
...outerElement,
|
|
||||||
contents: swapElements(outerElement.contents, inner, inner + distance)
|
|
||||||
};
|
|
||||||
this.contentBlock.contents = replaceAtIndex(contents, outer, newOuterElement);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
up(outer, inner){
|
|
||||||
this.shift(outer, inner, -1);
|
|
||||||
},
|
|
||||||
down(outer, inner){
|
|
||||||
this.shift(outer, inner, 1);
|
|
||||||
},
|
|
||||||
executeRemoval(outer, inner) {
|
|
||||||
if (inner === undefined) {
|
|
||||||
// not a list item container, just remove the element from the outer array
|
|
||||||
this.contentBlock.contents = removeAtIndex(this.contentBlock.contents, outer);
|
|
||||||
} else {
|
|
||||||
let prevInnerContents = this.contentBlock.contents[outer].contents;
|
|
||||||
let innerContents = removeAtIndex(prevInnerContents, inner);
|
|
||||||
|
|
||||||
if (innerContents.length) {
|
|
||||||
/*
|
|
||||||
there is still an element inside the outer element after removal,
|
|
||||||
so we replace the previous element in the outer array with the new one with fewer contents
|
|
||||||
*/
|
|
||||||
let element = {
|
|
||||||
...this.contentBlock.contents[outer],
|
|
||||||
contents: innerContents
|
|
||||||
};
|
|
||||||
this.contentBlock.contents = replaceAtIndex(this.contentBlock.contents, outer, element);
|
|
||||||
} else {
|
|
||||||
// inner contents is now empty, remove the whole element from the outer array
|
|
||||||
this.contentBlock.contents = removeAtIndex(this.contentBlock.contents, outer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
save({title, contents, isAssignment}) {
|
save({title, contents, isAssignment}) {
|
||||||
let filteredContents = contents
|
let cleanedContents = cleanUpContents(contents);
|
||||||
.filter(chooserFilter); // only use items that are not chooser elements
|
|
||||||
|
|
||||||
let mappedContents = filteredContents.map(content => {
|
|
||||||
// if the element has a contents property, it's a list of contents, filter them
|
|
||||||
if (content.contents) {
|
|
||||||
return {
|
|
||||||
...content,
|
|
||||||
contents: content.contents.filter(chooserFilter)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// else just return it
|
|
||||||
return content;
|
|
||||||
});
|
|
||||||
const contentBlock = {
|
const contentBlock = {
|
||||||
title: title,
|
title: title,
|
||||||
contents: mappedContents,
|
contents: cleanedContents,
|
||||||
type: setUserBlockType(isAssignment),
|
type: setUserBlockType(isAssignment),
|
||||||
};
|
};
|
||||||
let input;
|
let input;
|
||||||
const { parent, after, slug} = this.$route.params;
|
const { parent, after, slug } = this.$route.params;
|
||||||
if(after) {
|
if(after) {
|
||||||
input = {
|
input = {
|
||||||
contentBlock,
|
contentBlock,
|
||||||
after
|
after
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
input = {
|
input = {
|
||||||
contentBlock,
|
contentBlock,
|
||||||
|
|
@ -295,51 +78,8 @@
|
||||||
// use the history, so the scroll position is preserved
|
// use the history, so the scroll position is preserved
|
||||||
this.$router.go(-1);
|
this.$router.go(-1);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
@import '~styles/helpers';
|
|
||||||
|
|
||||||
.create-content-block {
|
|
||||||
width: 800px;
|
|
||||||
max-width: 100%;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 800px;
|
|
||||||
grid-template-rows: 1fr auto;
|
|
||||||
grid-template-areas:
|
|
||||||
'content'
|
|
||||||
'footer';
|
|
||||||
|
|
||||||
&__heading {
|
|
||||||
@include heading-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__task-toggle {
|
|
||||||
margin-bottom: $large-spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__add-button {
|
|
||||||
}
|
|
||||||
|
|
||||||
&__segment {
|
|
||||||
margin-bottom: $large-spacing;
|
|
||||||
:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__content {
|
|
||||||
grid-area: content;
|
|
||||||
overflow-x: visible;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__footer {
|
|
||||||
margin-top: auto;
|
|
||||||
grid-area: footer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
<template>
|
||||||
|
<component
|
||||||
|
:is="component"
|
||||||
|
v-bind="componentProps"
|
||||||
|
@save="save"
|
||||||
|
@back="goToModule"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import CONTENT_BLOCK_QUERY from '@/graphql/gql/queries/contentBlockQuery.gql';
|
||||||
|
import ContentBlockForm from '@/components/content-block-form/ContentBlockForm';
|
||||||
|
|
||||||
|
import EDIT_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/editContentBlock.gql';
|
||||||
|
import MODULE_DETAILS_QUERY from '@/graphql/gql/queries/modules/moduleDetailsQuery.gql';
|
||||||
|
import {setUserBlockType} from '@/helpers/content-block';
|
||||||
|
import {cleanUpContents} from '@/components/content-block-form/helpers';
|
||||||
|
|
||||||
|
// import ContentBlockForm from '@/components/content-block-form/ContentBlockForm';
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {ContentBlockForm},
|
||||||
|
|
||||||
|
data: ()=>({
|
||||||
|
contentBlock: null,
|
||||||
|
}),
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
// only display component once the prop is loaded
|
||||||
|
component() {
|
||||||
|
if (this.contentBlock) {
|
||||||
|
return 'content-block-form';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
componentProps() {
|
||||||
|
return {
|
||||||
|
title: 'Inhalte bearbeiten',
|
||||||
|
contentBlock: this.contentBlock
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
apollo: {
|
||||||
|
contentBlock: {
|
||||||
|
query: CONTENT_BLOCK_QUERY,
|
||||||
|
variables() {
|
||||||
|
return {
|
||||||
|
id: this.id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
save({title, contents, isAssignment, id}) {
|
||||||
|
let cleanedContents = cleanUpContents(contents);
|
||||||
|
const contentBlock = {
|
||||||
|
title: title,
|
||||||
|
contents: cleanedContents,
|
||||||
|
type: setUserBlockType(isAssignment),
|
||||||
|
};
|
||||||
|
const { slug } = this.$route.params;
|
||||||
|
const input = {
|
||||||
|
id,
|
||||||
|
contentBlock
|
||||||
|
};
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: EDIT_CONTENT_BLOCK_MUTATION,
|
||||||
|
variables: {
|
||||||
|
input
|
||||||
|
},
|
||||||
|
refetchQueries: [{
|
||||||
|
query: MODULE_DETAILS_QUERY,
|
||||||
|
variables: { slug }
|
||||||
|
}]
|
||||||
|
}).then(this.goToModule);
|
||||||
|
},
|
||||||
|
goToModule() {
|
||||||
|
// use the history, so the scroll position is preserved
|
||||||
|
this.$router.go(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -6,3 +6,4 @@ export const SNAPSHOT_LIST = 'snapshot-list';
|
||||||
export const SNAPSHOT_DETAIL = 'snapshot-detail';
|
export const SNAPSHOT_DETAIL = 'snapshot-detail';
|
||||||
export const CREATE_CONTENT_BLOCK_AFTER_PAGE = 'create-content-block-after';
|
export const CREATE_CONTENT_BLOCK_AFTER_PAGE = 'create-content-block-after';
|
||||||
export const CREATE_CONTENT_BLOCK_UNDER_PARENT_PAGE = 'create-content-block-under-parent';
|
export const CREATE_CONTENT_BLOCK_UNDER_PARENT_PAGE = 'create-content-block-under-parent';
|
||||||
|
export const EDIT_CONTENT_BLOCK_PAGE = 'edit-content-block';
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,12 @@ import {
|
||||||
SNAPSHOT_LIST,
|
SNAPSHOT_LIST,
|
||||||
SUBMISSIONS_PAGE,
|
SUBMISSIONS_PAGE,
|
||||||
VISIBILITY_PAGE,
|
VISIBILITY_PAGE,
|
||||||
CREATE_CONTENT_BLOCK_AFTER_PAGE, CREATE_CONTENT_BLOCK_UNDER_PARENT_PAGE,
|
CREATE_CONTENT_BLOCK_AFTER_PAGE,
|
||||||
|
CREATE_CONTENT_BLOCK_UNDER_PARENT_PAGE, EDIT_CONTENT_BLOCK_PAGE,
|
||||||
} from '@/router/module.names';
|
} from '@/router/module.names';
|
||||||
import {LAYOUT_SIMPLE} from '@/router/core.constants';
|
import {LAYOUT_SIMPLE} from '@/router/core.constants';
|
||||||
import createContentBlock from '@/pages/createContentBlock';
|
import createContentBlock from '@/pages/createContentBlock';
|
||||||
|
import editContentBlock from '@/pages/editContentBlock';
|
||||||
|
|
||||||
const moduleBase = () => import(/* webpackChunkName: "modules" */'@/pages/module/module-base');
|
const moduleBase = () => import(/* webpackChunkName: "modules" */'@/pages/module/module-base');
|
||||||
const module = () => import(/* webpackChunkName: "modules" */'@/pages/module/module');
|
const module = () => import(/* webpackChunkName: "modules" */'@/pages/module/module');
|
||||||
|
|
@ -18,14 +20,15 @@ const settingsPage = () => import(/* webpackChunkName: "modules" */'@/pages/modu
|
||||||
const snapshots = () => import(/* webpackChunkName: "modules" */'@/pages/snapshot/snapshots');
|
const snapshots = () => import(/* webpackChunkName: "modules" */'@/pages/snapshot/snapshots');
|
||||||
const snapshot = () => import(/* webpackChunkName: "modules" */'@/pages/snapshot/snapshot');
|
const snapshot = () => import(/* webpackChunkName: "modules" */'@/pages/snapshot/snapshot');
|
||||||
|
|
||||||
|
const contentBlockFormMeta = {
|
||||||
|
// layout: LAYOUT_SIMPLE,
|
||||||
|
hideFooter: true,
|
||||||
|
hideHeader: true,
|
||||||
|
showSubNavigation: true
|
||||||
|
};
|
||||||
const createContentBlockRouteFragment = {
|
const createContentBlockRouteFragment = {
|
||||||
component: createContentBlock,
|
component: createContentBlock,
|
||||||
meta: {
|
meta: contentBlockFormMeta,
|
||||||
// layout: LAYOUT_SIMPLE,
|
|
||||||
hideFooter: true,
|
|
||||||
hideHeader: true,
|
|
||||||
showSubNavigation: true
|
|
||||||
},
|
|
||||||
props: true
|
props: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -85,6 +88,13 @@ export default [
|
||||||
fullWidth: true,
|
fullWidth: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'edit/:id',
|
||||||
|
meta: contentBlockFormMeta,
|
||||||
|
props: true,
|
||||||
|
component: editContentBlock,
|
||||||
|
name: EDIT_CONTENT_BLOCK_PAGE
|
||||||
|
},
|
||||||
{
|
{
|
||||||
...createContentBlockRouteFragment,
|
...createContentBlockRouteFragment,
|
||||||
path: 'add-after/:after',
|
path: 'add-after/:after',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue