Add content block edit modal

Also clean up the state store.
Also change the properties of the content block contents on the server
This commit is contained in:
Ramon Wenger 2018-09-19 15:40:21 +02:00
parent 8067ca439d
commit ef48f5afb6
15 changed files with 171 additions and 66 deletions

View File

@ -10,6 +10,7 @@
import SimpleLayout from '@/layouts/SimpleLayout';
import Modal from '@/components/Modal';
import NewContentBlockWizard from '@/components/content-block-form/NewContentBlockWizard';
import EditContentBlockWizard from '@/components/content-block-form/EditContentBlockWizard';
export default {
name: 'App',
@ -18,7 +19,8 @@
DefaultLayout,
SimpleLayout,
Modal,
NewContentBlockWizard
NewContentBlockWizard,
EditContentBlockWizard
},
computed: {

View File

@ -10,7 +10,9 @@
:content-block="contentBlock"
class="content-block__visibility-menu"
></visibility-popover>
<a><pen-icon class="content-block__action-icon"></pen-icon></a>
<a @click="editContentBlock()">
<pen-icon class="content-block__action-icon"></pen-icon>
</a>
</div>
<h4 class="content-block__title">{{contentBlock.title}}</h4>
@ -70,6 +72,9 @@
methods: {
toggleVisibility() {
this.showVisibility = !this.showVisibility;
},
editContentBlock() {
this.$store.dispatch('editContentBlock', this.contentBlock.id);
}
},

View File

@ -6,7 +6,7 @@
v-on:add-element="addElement"
:index="-1"
></add-content-element>
<div v-for="(element, index) in contentBlock.elements" :key="index" class="content-block-form__element">
<div v-for="(element, index) in contentBlock.contents" :key="index" class="content-block-form__element">
<component
class="content-block-form__element-component"
:is="type(element)"
@ -93,9 +93,13 @@
return 'content-block-element-chooser-widget'
},
_updateProperty(value, index, key) {
this.localContentBlock.elements.splice(index, 1, {
...this.localContentBlock.elements[index],
const content = this.localContentBlock.contents[index];
this.localContentBlock.contents.splice(index, 1, {
...content,
value: {
...content.value,
[key]: value
}
});
},
changeLinkUrl(value, index) {
@ -115,10 +119,10 @@
this._updateProperty(value, index, 'text')
},
removeElement(index) {
this.localContentBlock.elements.splice(index, 1);
this.localContentBlock.contents.splice(index, 1);
},
addElement(index) {
this.localContentBlock.elements.splice(index + 1, 0, {})
this.localContentBlock.contents.splice(index + 1, 0, {})
},
updateTitle(title) {
this.localContentBlock.title = title;
@ -126,37 +130,46 @@
},
changeType(index, type) {
let el = {
type: type
type: type,
value: {}
};
switch (type) {
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: {
url: ''
}
};
break;
}
this.localContentBlock.elements.splice(index, 1, el);
this.localContentBlock.contents.splice(index, 1, el);
},
save() {
if (!this.localContentBlock.title) {

View File

@ -0,0 +1,75 @@
<template>
<content-block-form
:content-block="contentBlock"
@save="saveContentBlock"
@hide="hideModal"
></content-block-form>
</template>
<script>
import ContentBlockForm from '@/components/content-block-form/ContentBlockForm';
import store from '@/store/index';
import NEW_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/addContentBlock.gql';
import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.gql';
import CONTENT_BLOCK_QUERY from '@/graphql/gql/contentBlockQuery.gql';
export default {
components: {
ContentBlockForm
},
methods: {
hideModal() {
this.$store.dispatch('resetCurrentContentBlock');
this.$store.dispatch('hideModal');
},
saveContentBlock(contentBlock) {
this.$apollo.mutate({
mutation: NEW_CONTENT_BLOCK_MUTATION,
variables: {
input: {
contentBlock: {
title: contentBlock.title,
contents: contentBlock.contents.filter(value => Object.keys(value).length > 0)
},
after: this.$store.state.contentBlockPosition.after,
parent: this.$store.state.contentBlockPosition.parent
}
},
refetchQueries: [{
query: MODULE_DETAILS_QUERY,
variables: {
slug: store.state.moduleSlug
}
}]
}).then(() => {
this.hideModal();
});
}
},
created() {
// debugger;
},
data() {
return {
contentBlock: {}
}
},
apollo: {
contentBlock() {
return {
query: CONTENT_BLOCK_QUERY,
variables: {
id: store.state.currentContentBlock
}
}
}
}
}
</script>

View File

@ -21,7 +21,7 @@
methods: {
hideModal() {
this.$store.dispatch('resetContentBlock');
this.$store.dispatch('resetContentBlockPosition');
this.$store.dispatch('hideModal');
},
saveContentBlock(contentBlock) {
@ -31,7 +31,7 @@
input: {
contentBlock: {
title: contentBlock.title,
contents: contentBlock.elements.filter(value => Object.keys(value).length > 0)
contents: contentBlock.contents.filter(value => Object.keys(value).length > 0)
},
after: this.$store.state.contentBlockPosition.after,
parent: this.$store.state.contentBlockPosition.parent
@ -53,7 +53,7 @@
return {
contentBlock: {
title: '',
elements: [
contents: [
{}
]
}

View File

@ -8,7 +8,7 @@
</p>
<input class="document-form__document-link skillbox-input"
:value="url" v-on:input="$emit('document-change-url', $event.target.value, index)"
:value="value.url" v-on:input="$emit('document-change-url', $event.target.value, index)"
placeholder="URL einfügen...">
</div>
@ -18,7 +18,7 @@
import InfoIcon from '@/components/icons/InfoIcon';
export default {
props: ['url', 'index'],
props: ['value', 'index'],
components: {
InfoIcon

View File

@ -1,14 +1,14 @@
<template>
<div class="link-form">
<input placeholder="Name erfassen..." class="link-form__text skillbox-input" :value="text" v-on:input="$emit('link-change-text', $event.target.value, index)">
<input placeholder="Name erfassen..." class="link-form__text skillbox-input" :value="value.text" v-on:input="$emit('link-change-text', $event.target.value, index)">
<input placeholder="URL einfügen..." class="link-form__url skillbox-input" :value="url" v-on:input="$emit('link-change-url', $event.target.value, index)">
<input placeholder="URL einfügen..." class="link-form__url skillbox-input" :value="value.url" v-on:input="$emit('link-change-url', $event.target.value, index)">
</div>
</template>
<script>
export default {
props: ['text', 'url', 'index']
props: ['value', 'index']
}
</script>

View File

@ -9,7 +9,13 @@
<script>
export default {
props: ['text', 'index']
props: ['value', 'index'],
computed: {
text() {
return this.value.text.replace(/(<([^>]+)>)/ig, '')
}
}
}
</script>

View File

@ -11,14 +11,14 @@
<input class="video-form__video-link skillbox-input"
placeholder="Bsp: https://www.youtube.com/watch?v=dQw4w9WgXcQ"
:value="url" v-on:input="$emit('video-change-url', $event.target.value, index)">
:value="value.url" v-on:input="$emit('video-change-url', $event.target.value, index)">
</div>
<div v-if="isYoutube">
<youtube-embed :url="url"></youtube-embed>
<youtube-embed :url="value.url"></youtube-embed>
</div>
<div v-if="isVimeo">
<vimeo-embed :url="url"></vimeo-embed>
<vimeo-embed :url="value.url"></vimeo-embed>
</div>
</div>
@ -31,7 +31,7 @@
import {isVimeoUrl, isYoutubeUrl} from '@/helpers/video';
export default {
props: ['url', 'index'],
props: ['value', 'index'],
components: {
InfoIcon,
@ -41,10 +41,10 @@
computed: {
isYoutube() {
return isYoutubeUrl(this.url);
return isYoutubeUrl(this.value.url);
},
isVimeo() {
return isVimeoUrl(this.url);
return isVimeoUrl(this.value.url);
}
}
}

View File

@ -25,10 +25,18 @@ const consoleLink = new ApolloLink((operation, forward) => {
const composedLink = ApolloLink.from([consoleLink, httpLink]);
const cache = new InMemoryCache({
cacheRedirects: {
Query: {
contentBlock: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ContentBlockNode', id: args.id})
}
}
});
// Create the apollo client
export default new ApolloClient({
link: composedLink,
// link: httpLink,
cache: new InMemoryCache(),
cache: cache,
connectToDevTools: true
})

View File

@ -0,0 +1,6 @@
#import "./fragments/contentBlockParts.gql"
query ContentBlockQuery($id: ID!) {
contentBlock(id: $id) {
...ContentBlockParts
}
}

View File

@ -29,7 +29,7 @@
manual: true,
result({data, loading, networkStatus}) {
if (!loading) {
const cleanedData = this.$getRidOfEdges(data)
const cleanedData = this.$getRidOfEdges(data);
this.module = cleanedData.modules[0] || {};
}
}
@ -37,21 +37,6 @@
}
},
created() {
this.$store.subscribe((mutation, state) => {
if (mutation.type === 'updateContentBlocks' && state.updateContentBlocks) {
this.updateQuery();
}
})
},
methods: {
updateQuery() {
this.$apollo.queries.moduleQuery.refetch();
this.$store.dispatch('resetUpdateContentBlocksFlag');
}
},
data() {
return {
module: {

View File

@ -12,8 +12,8 @@ export default new Vuex.Store({
contentBlockPosition: {},
scrollPosition: 0,
moduleSlug: 'mein-neues-umfeld',
updateContentBlocks: false,
filterForGroup: false
filterForGroup: false,
currentContentBlock: ''
},
getters: {},
@ -26,9 +26,16 @@ export default new Vuex.Store({
document.body.classList.remove('no-scroll'); // won't get at the body any other way
commit('setModal', false);
},
resetContentBlock({commit}) {
resetContentBlockPosition({commit}) {
commit('setContentBlockPosition', {});
},
resetCurrentContentBlock({commit}) {
commit('setCurrentContentBlock', '');
},
editContentBlock({commit, dispatch}, payload) {
commit('setCurrentContentBlock', payload);
dispatch('showModal', 'edit-content-block-wizard');
},
addContentBlock({commit, dispatch}, payload) {
commit('setContentBlockPosition', payload);
dispatch('showModal', 'new-content-block-wizard');
@ -37,12 +44,6 @@ export default new Vuex.Store({
document.body.classList.add('no-scroll'); // won't get at the body any other way
commit('setModal', payload);
},
updateContentBlocks({commit}) {
commit('updateContentBlocks', true);
},
resetUpdateContentBlocksFlag({commit}) {
commit('updateContentBlocks', false);
},
setFilterForGroup({commit}, payload) {
commit('setFilterForGroup', payload);
}
@ -61,12 +62,12 @@ export default new Vuex.Store({
setNewContentBlock(state, payload) {
state.newContentBlock = payload;
},
updateContentBlocks(state, payload) {
state.updateContentBlocks = payload;
},
setContentBlockPosition(state, payload) {
state.contentBlockPosition = payload;
},
setCurrentContentBlock(state, payload) {
state.currentContentBlock = payload;
},
setFilterForGroup(state, payload) {
state.filterForGroup = payload;
}

View File

@ -13,10 +13,9 @@ class InputTypes(graphene.Enum):
document_block = 'document_block'
class ContentElementInput(InputObjectType):
class ContentElementValueInput(InputObjectType):
# we'll handle this with a single input, even tho it would be nice to have a type for every different possibility
# see discussion at https://github.com/graphql/graphql-js/issues/207
type = InputTypes(required=True)
text = graphene.String(description='To be used for link_block, text_block types')
url = graphene.String(description='To be used for link, basic_knowledge, image_block types')
description = graphene.String(description='To be used for basic_knowledge type')
@ -24,6 +23,11 @@ class ContentElementInput(InputObjectType):
task_text = graphene.String(description='To be used for task type')
class ContentElementInput(InputObjectType):
type = InputTypes(required=True)
value = ContentElementValueInput()
class ContentBlockInput(InputObjectType):
title = graphene.String(required=True)
type = graphene.String()

View File

@ -21,7 +21,7 @@ def handle_content_blocks(content_data):
new_contents.append({
'type': 'text_block',
'value': {
'text': '<p>{}</p>'.format(bleach.clean(content['text']))
'text': '<p>{}</p>'.format(bleach.clean(content['value']['text']))
}})
elif content['type'] == 'student_entry':
pass
@ -31,8 +31,8 @@ def handle_content_blocks(content_data):
new_contents.append({
'type': 'link_block',
'value': {
'text': bleach.clean(content['text']),
'url': bleach.clean(content['url'])
'text': bleach.clean(content['value']['text']),
'url': bleach.clean(content['value']['url'])
}
})
elif content['type'] == 'task':
@ -41,13 +41,13 @@ def handle_content_blocks(content_data):
new_contents.append({
'type': 'video_block',
'value': {
'url': bleach.clean(content['url'])
'url': bleach.clean(content['value']['url'])
}})
elif content['type'] == 'document_block':
new_contents.append({
'type': 'document_block',
'value': {
'url': bleach.clean(content['url'])
'url': bleach.clean(content['value']['url'])
}})
return new_contents