From 50acea3bfd0345d82e66a7c48b4318b5daa6d6b2 Mon Sep 17 00:00:00 2001 From: Ron Lucke <lucke@elan-ev.de> Date: Mon, 26 Sep 2022 10:34:12 +0000 Subject: [PATCH] fix #887 Closes #887 and #1257 Merge request studip/studip!871 --- .../JsonApi/Routes/Courseware/BlocksShow.php | 2 +- .../Courseware/StructuralElementsShow.php | 2 + .../assets/stylesheets/scss/courseware.scss | 20 +++ .../CoursewareAccordionContainer.vue | 9 +- .../courseware/CoursewareActionWidget.vue | 77 +++++++--- .../courseware/CoursewareAudioBlock.vue | 4 +- .../courseware/CoursewareBeforeAfterBlock.vue | 23 ++- .../courseware/CoursewareBlockActions.vue | 97 +++++++----- .../courseware/CoursewareCanvasBlock.vue | 13 +- .../courseware/CoursewareChartBlock.vue | 3 + .../courseware/CoursewareCodeBlock.vue | 3 + .../courseware/CoursewareConfirmBlock.vue | 1 + .../courseware/CoursewareContainerActions.vue | 54 +++++-- .../courseware/CoursewareDateBlock.vue | 6 +- .../courseware/CoursewareDefaultBlock.vue | 126 +++++++++++---- .../courseware/CoursewareDefaultContainer.vue | 124 +++++++++++---- .../courseware/CoursewareDialogCardsBlock.vue | 4 +- .../courseware/CoursewareDocumentBlock.vue | 4 +- .../courseware/CoursewareDownloadBlock.vue | 4 +- .../courseware/CoursewareEmbedBlock.vue | 5 +- .../courseware/CoursewareFolderBlock.vue | 3 + .../courseware/CoursewareGalleryBlock.vue | 4 +- .../courseware/CoursewareHeadlineBlock.vue | 8 +- .../courseware/CoursewareIframeBlock.vue | 4 +- .../courseware/CoursewareImageMapBlock.vue | 4 +- .../courseware/CoursewareKeyPointBlock.vue | 8 +- .../courseware/CoursewareLinkBlock.vue | 3 + .../CoursewareStructuralElement.vue | 144 ++++++++++++++---- .../CoursewareTableOfContentsBlock.vue | 8 +- .../courseware/CoursewareTabsContainer.vue | 9 +- .../courseware/CoursewareTextBlock.vue | 12 +- .../courseware/CoursewareTypewriterBlock.vue | 8 +- .../courseware/CoursewareVideoBlock.vue | 3 + .../vue/components/courseware/block-mixin.js | 3 + .../vue/store/courseware/courseware.module.js | 32 +++- 35 files changed, 626 insertions(+), 208 deletions(-) diff --git a/lib/classes/JsonApi/Routes/Courseware/BlocksShow.php b/lib/classes/JsonApi/Routes/Courseware/BlocksShow.php index ba31717a7b8..4e5dd1724d8 100644 --- a/lib/classes/JsonApi/Routes/Courseware/BlocksShow.php +++ b/lib/classes/JsonApi/Routes/Courseware/BlocksShow.php @@ -18,7 +18,7 @@ class BlocksShow extends JsonApiController 'container', 'owner', 'editor', - 'edit_blocker', + 'edit-blocker', 'user-data-field', 'user-progress', ]; diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsShow.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsShow.php index f4c0766ecbe..4aa07166220 100644 --- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsShow.php +++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsShow.php @@ -19,6 +19,7 @@ class StructuralElementsShow extends JsonApiController 'ancestors', 'children', 'containers', + 'containers.edit-blocker', 'containers.blocks', 'containers.blocks.edit-blocker', 'containers.blocks.editor', @@ -27,6 +28,7 @@ class StructuralElementsShow extends JsonApiController 'containers.blocks.user-progress', 'course', 'editor', + 'edit-blocker', 'owner', 'parent', 'target' diff --git a/resources/assets/stylesheets/scss/courseware.scss b/resources/assets/stylesheets/scss/courseware.scss index ed57ee883aa..902072803a7 100644 --- a/resources/assets/stylesheets/scss/courseware.scss +++ b/resources/assets/stylesheets/scss/courseware.scss @@ -837,6 +837,14 @@ ribbon end font-weight: 700; line-height: 2em; font-size: 1.1em; + + &.cw-default-container-blocker-warning { + font-weight: 400; + } + } + + img { + vertical-align: text-bottom; } .cw-container-actions { @@ -977,6 +985,15 @@ form.cw-container-dialog-edit-form { font-weight: 700; line-height: 2em; font-size: 1.1em; + + &.cw-default-block-invisible-info, + &.cw-default-block-blocker-warning { + font-weight: 400; + } + } + + img { + vertical-align: text-bottom; } .cw-block-actions { @@ -2003,6 +2020,9 @@ v i e w w i d g e t .cw-action-widget-link { @include background-icon(group, clickable); } + .cw-action-widget-remove-lock{ + @include background-icon(lock-unlocked, clickable); + } } .cw-export-widget { .cw-export-widget-export{ diff --git a/resources/vue/components/courseware/CoursewareAccordionContainer.vue b/resources/vue/components/courseware/CoursewareAccordionContainer.vue index de50ae1db8b..7d9f7626232 100644 --- a/resources/vue/components/courseware/CoursewareAccordionContainer.vue +++ b/resources/vue/components/courseware/CoursewareAccordionContainer.vue @@ -4,6 +4,7 @@ containerClass="cw-container-accordion" :canEdit="canEdit" :isTeacher="isTeacher" + @showEdit="setShowEdit" @storeContainer="storeContainer" @closeEdit="initCurrentData" @sortBlocks="enableSort" @@ -114,6 +115,7 @@ export default { }, data() { return { + showEdit: false, currentContainer: {}, currentSections: [], unallocatedBlocks: [], @@ -180,6 +182,9 @@ export default { this.currentSections = sections; }, + setShowEdit(state) { + this.showEdit = state; + }, addSection() { this.currentContainer.attributes.payload.sections.push({ name: '', icon: '', blocks: [] }); }, @@ -243,7 +248,9 @@ export default { }, watch: { blocks() { - this.initCurrentData(); + if (!this.showEdit) { + this.initCurrentData(); + } } } }; diff --git a/resources/vue/components/courseware/CoursewareActionWidget.vue b/resources/vue/components/courseware/CoursewareActionWidget.vue index a32c7491d17..2db5c2f964d 100644 --- a/resources/vue/components/courseware/CoursewareActionWidget.vue +++ b/resources/vue/components/courseware/CoursewareActionWidget.vue @@ -9,37 +9,42 @@ </li> <li class="cw-action-widget-show-consume-mode"> <button @click="showConsumeMode"> - <translate>Vollbild einschalten</translate> + {{ $gettext('Vollbild einschalten') }} </button> </li> - <li v-if="canEdit" class="cw-action-widget-edit"> + <li v-if="canEdit && !blockedByAnotherUser" class="cw-action-widget-edit"> <button @click="editElement"> - <translate>Seite bearbeiten</translate> + {{ $gettext('Seite bearbeiten') }} </button> </li> - <li v-if="canEdit" class="cw-action-widget-sort"> + <li v-if="canEdit && blockedByAnotherUser" class="cw-action-widget-remove-lock"> + <button @click="removeElementLock"> + {{ $gettext('Sperre aufheben') }} + </button> + </li> + <li v-if="canEdit && !blockedByAnotherUser" class="cw-action-widget-sort"> <button @click="sortContainers"> - <translate>Abschnitte sortieren</translate> + {{ $gettext('Abschnitte sortieren') }} </button> </li> <li v-if="canEdit" class="cw-action-widget-add"> <button @click="addElement"> - <translate>Seite hinzufügen</translate> + {{ $gettext('Seite hinzufügen') }} </button> </li> <li class="cw-action-widget-info"> <button @click="showElementInfo"> - <translate>Informationen anzeigen</translate> + {{ $gettext('Informationen anzeigen') }} </button> </li> <li class="cw-action-widget-star"> <button @click="createBookmark"> - <translate>Lesezeichen setzen</translate> + {{ $gettext('Lesezeichen setzen') }} </button> </li> <li v-if="context.type === 'users'" class="cw-action-widget-link"> <button @click="linkElement"> - <translate>Öffentlichen Link erzeugen</translate> + {{ $gettext('Öffentlichen Link erzeugen') }} </button> </li> <li v-if="!isOwner" class="cw-action-widget-oer"> @@ -47,9 +52,9 @@ <translate>Material für den OER Campus vorschlagen</translate> </button> </li> - <li v-if="!isRoot && canEdit" class="cw-action-widget-trash"> + <li v-if="!isRoot && canEdit && !blockedByAnotherUser" class="cw-action-widget-trash"> <button @click="deleteElement"> - <translate>Seite löschen</translate> + {{ $gettext('Seite löschen') }} </button> </li> </ul> @@ -77,6 +82,11 @@ export default { consumeMode: 'consumeMode', showToolbar: 'showToolbar', context: 'context', + + blocked: 'currentElementBlocked', + blockerId: 'currentElementBlockerId', + blockedByThisUser: 'currentElementBlockedByThisUser', + blockedByAnotherUser: 'currentElementBlockedByAnotherUser', }), isRoot() { if (!this.structuralElement) { @@ -94,18 +104,6 @@ export default { currentId() { return this.structuralElement?.id; }, - blocked() { - return this.structuralElement?.relationships['edit-blocker'].data !== null; - }, - blockerId() { - return this.blocked ? this.structuralElement?.relationships['edit-blocker'].data?.id : null; - }, - blockedByThisUser() { - return this.blocked && this.userId === this.blockerId; - }, - blockedByAnotherUser() { - return this.blocked && this.userId !== this.blockerId; - }, tocText() { return this.showToolbar ? this.$gettext('Inhaltsverzeichnis ausblenden') : this.$gettext('Inhaltsverzeichnis anzeigen'); }, @@ -123,6 +121,7 @@ export default { showElementDeleteDialog: 'showElementDeleteDialog', showElementInfoDialog: 'showElementInfoDialog', showElementLinkDialog: 'showElementLinkDialog', + showElementRemoveLockDialog: 'showElementRemoveLockDialog', updateShowSuggestOerDialog: 'updateShowSuggestOerDialog', setStructuralElementSortMode: 'setStructuralElementSortMode', companionInfo: 'companionInfo', @@ -131,9 +130,11 @@ export default { setConsumeMode: 'coursewareConsumeMode', setViewMode: 'coursewareViewMode', setShowToolbar: 'coursewareShowToolbar', - setSelectedToolbarItem: 'coursewareSelectedToolbarItem' + setSelectedToolbarItem: 'coursewareSelectedToolbarItem', + loadStructuralElement: 'loadStructuralElement', }), async editElement() { + await this.loadStructuralElement(this.currentId); if (this.blockedByAnotherUser) { this.companionInfo({ info: this.$gettext('Diese Seite wird bereits bearbeitet.') }); @@ -152,10 +153,36 @@ export default { } this.showElementEditDialog(true); }, - sortContainers() { + async removeElementLock() { + this.showElementRemoveLockDialog(true); + }, + async sortContainers() { + await this.loadStructuralElement(this.currentId); + if (this.blockedByAnotherUser) { + this.companionInfo({ info: this.$gettext('Diese Seite wird bereits bearbeitet.') }); + + return false; + } + try { + await this.lockObject({ id: this.currentId, type: 'courseware-structural-elements' }); + } catch (error) { + if (error.status === 409) { + this.companionInfo({ info: this.$gettext('Diese Seite wird bereits bearbeitet.') }); + } else { + console.log(error); + } + + return false; + } this.setStructuralElementSortMode(true); }, async deleteElement() { + await this.loadStructuralElement(this.currentId); + if (this.blockedByAnotherUser) { + this.companionInfo({ info: this.$gettextInterpolate('Löschen nicht möglich, da %{blockingUserName} die Seite bearbeitet.', {blockingUserName: this.blockingUserName}) }); + + return false; + } await this.lockObject({ id: this.currentId, type: 'courseware-structural-elements' }); this.showElementDeleteDialog(true); }, diff --git a/resources/vue/components/courseware/CoursewareAudioBlock.vue b/resources/vue/components/courseware/CoursewareAudioBlock.vue index 218f11dc666..77e3166a590 100644 --- a/resources/vue/components/courseware/CoursewareAudioBlock.vue +++ b/resources/vue/components/courseware/CoursewareAudioBlock.vue @@ -5,6 +5,7 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" + @showEdit="initCurrentData" @storeEdit="storeBlock" @closeEdit="initCurrentData" > @@ -167,11 +168,12 @@ import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFileChooser from './CoursewareFileChooser.vue'; import CoursewareFolderChooser from './CoursewareFolderChooser.vue'; - +import { blockMixin } from './block-mixin.js'; import { mapActions, mapGetters } from 'vuex'; export default { name: 'courseware-audio-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, CoursewareFileChooser, diff --git a/resources/vue/components/courseware/CoursewareBeforeAfterBlock.vue b/resources/vue/components/courseware/CoursewareBeforeAfterBlock.vue index 2c68473b9cd..f714f25bc4a 100644 --- a/resources/vue/components/courseware/CoursewareBeforeAfterBlock.vue +++ b/resources/vue/components/courseware/CoursewareBeforeAfterBlock.vue @@ -5,11 +5,12 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" + @showEdit="initCurrentData" @storeEdit="storeBlock" @closeEdit="initCurrentData" > <template #content> - <TwentyTwenty :before="currentBeforeUrl" :after="currentAfterUrl" /> + <TwentyTwenty v-if="!isEmpty" :before="currentBeforeUrl" :after="currentAfterUrl" /> </template> <template v-if="canEdit" #edit> <form class="default" @submit.prevent=""> @@ -61,12 +62,14 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFileChooser from './CoursewareFileChooser.vue'; +import { blockMixin } from './block-mixin.js'; import TwentyTwenty from 'vue-twentytwenty'; import 'vue-twentytwenty/dist/vue-twentytwenty.css'; import { mapActions } from 'vuex'; export default { name: 'courseware-before-after-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, CoursewareFileChooser, @@ -145,6 +148,7 @@ export default { this.currentAfterFile = this.afterFile; }); + this.loadImages(); this.initCurrentData(); }, methods: { @@ -153,6 +157,23 @@ export default { loadFileRefs: 'loadFileRefs', companionWarning: 'companionWarning', }), + loadImages() { + this.loadFileRefs(this.block.id).then((response) => { + for (let i = 0; i < response.length; i++) { + if (response[i].id === this.beforeFileId) { + this.beforeFile = response[i]; + } + + if (response[i].id === this.afterFileId) { + this.afterFile = response[i]; + } + } + + this.currentBeforeFile = this.beforeFile; + this.currentAfterFile = this.afterFile; + }); + }, + initCurrentData() { this.currentBeforeSource = this.beforeSource; this.currentBeforeFileId = this.beforeFileId; diff --git a/resources/vue/components/courseware/CoursewareBlockActions.vue b/resources/vue/components/courseware/CoursewareBlockActions.vue index 2948369385e..031e3aa4d99 100644 --- a/resources/vue/components/courseware/CoursewareBlockActions.vue +++ b/resources/vue/components/courseware/CoursewareBlockActions.vue @@ -7,6 +7,7 @@ @setVisibility="setVisibility" @showInfo="showInfo" @deleteBlock="deleteBlock" + @removeLock="removeLock" /> </div> </template> @@ -28,57 +29,69 @@ export default { }, block: Object, }, - data() { - return { - menuItems: [], - }; - }, computed: { ...mapGetters({ userId: 'userId', + userIsTeacher: 'userIsTeacher', }), blocked() { - return this.block?.relationships['edit-blocker'].data !== null; + return this.block?.relationships?.['edit-blocker']?.data !== null; }, blockerId() { - return this.blocked ? this.block?.relationships['edit-blocker'].data?.id : null; + return this.blocked ? this.block?.relationships?.['edit-blocker']?.data?.id : null; }, - }, - mounted() { - if (this.canEdit) { - if (!this.deleteOnly) { - this.menuItems.push({ - id: 1, - label: this.$gettext('Block bearbeiten'), - icon: 'edit', - emit: 'editBlock', - }); - this.menuItems.push({ - id: 2, - label: this.block.attributes.visible - ? this.$gettext('unsichtbar setzen') - : this.$gettext('sichtbar setzen'), - icon: this.block.attributes.visible ? 'visibility-visible' : 'visibility-invisible', // do we change the icons ? - emit: 'setVisibility', - }); - this.menuItems.push({ - id: 7, - label: this.$gettext('Informationen zum Block'), - icon: 'info', - emit: 'showInfo', - }); + blockedByThisUser() { + return this.blocked && this.userId === this.blockerId; + }, + blockedByAnotherUser() { + return this.blocked && this.userId !== this.blockerId; + }, + menuItems() { + let menuItems = []; + if (this.canEdit) { + if (!this.deleteOnly) { + if (!this.blocked) { + menuItems.push({ id: 1, label: this.$gettext('Block bearbeiten'), icon: 'edit', emit: 'editBlock' }); + menuItems.push({ + id: 2, + label: this.block.attributes.visible + ? this.$gettext('unsichtbar setzen') + : this.$gettext('sichtbar setzen'), + icon: this.block.attributes.visible ? 'visibility-visible' : 'visibility-invisible', // do we change the icons ? + emit: 'setVisibility', + }); + } + if (this.blocked && this.blockedByAnotherUser && this.userIsTeacher) { + menuItems.push({ + id: 8, + label: this.$gettext('Sperre aufheben'), + icon: 'lock-unlocked', + emit: 'removeLock', + }); + } + if (!this.blocked || this.blockedByThisUser) { + menuItems.push({ + id: 9, + label: this.$gettext('Block löschen'), + icon: 'trash', + emit: 'deleteBlock' + }); + } + menuItems.push({ + id: 7, + label: this.$gettext('Informationen zum Block'), + icon: 'info', + emit: 'showInfo', + }); + } } - this.menuItems.push({ - id: 9, - label: this.$gettext('Block löschen'), - icon: 'trash', - emit: 'deleteBlock', + menuItems.sort((a, b) => { + return a.id > b.id ? 1 : b.id > a.id ? -1 : 0; }); + + return menuItems; } - this.menuItems.sort((a, b) => { - return a.id > b.id ? 1 : b.id > a.id ? -1 : 0; - }); }, methods: { ...mapActions({ @@ -117,12 +130,12 @@ export default { await this.unlockObject({ id: this.block.id, type: 'courseware-blocks' }); }, - copyToClipboard() { - // use JSONAPI to copy to clipboard - }, deleteBlock() { this.$emit('deleteBlock'); }, + removeLock() { + this.$emit('removeLock'); + } }, }; </script> diff --git a/resources/vue/components/courseware/CoursewareCanvasBlock.vue b/resources/vue/components/courseware/CoursewareCanvasBlock.vue index de474cee2f9..31c9258f50c 100644 --- a/resources/vue/components/courseware/CoursewareCanvasBlock.vue +++ b/resources/vue/components/courseware/CoursewareCanvasBlock.vue @@ -5,6 +5,7 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" + @showEdit="initCurrentData" @storeEdit="storeBlock" @closeEdit="initCurrentData" > @@ -147,11 +148,12 @@ import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFileChooser from './CoursewareFileChooser.vue'; import CoursewareFolderChooser from './CoursewareFolderChooser.vue'; - +import { blockMixin } from './block-mixin.js'; import { mapActions, mapGetters } from 'vuex'; export default { name: 'courseware-canvas-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, CoursewareFileChooser, @@ -261,6 +263,7 @@ export default { this.initCurrentData(); this.buildCanvas(); }); + this.loadImageFile(); }, methods: { ...mapActions({ @@ -286,6 +289,14 @@ export default { this.Text = JSON.parse(this.canvasDraw.Text); } }, + loadImageFile() { + this.loadFileRefs(this.block.id).then((response) => { + this.file = response[0]; + this.currentFile = this.file; + this.initCurrentData(); + this.buildCanvas(); + }); + }, updateCurrentFile(file) { this.currentFile = file; this.currentFileId = file.id; diff --git a/resources/vue/components/courseware/CoursewareChartBlock.vue b/resources/vue/components/courseware/CoursewareChartBlock.vue index 8dd58ec2433..4329659fe90 100644 --- a/resources/vue/components/courseware/CoursewareChartBlock.vue +++ b/resources/vue/components/courseware/CoursewareChartBlock.vue @@ -5,6 +5,7 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" + @showEdit="initCurrentData" @storeEdit="storeBlock" @closeEdit="initCurrentData" > @@ -84,12 +85,14 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; +import { blockMixin } from './block-mixin.js'; import Chart from 'chart.js'; import { mapActions } from 'vuex'; import StudipIcon from '../StudipIcon.vue'; export default { name: 'courseware-chart-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, StudipIcon, diff --git a/resources/vue/components/courseware/CoursewareCodeBlock.vue b/resources/vue/components/courseware/CoursewareCodeBlock.vue index 275a77f5e87..db383735a42 100644 --- a/resources/vue/components/courseware/CoursewareCodeBlock.vue +++ b/resources/vue/components/courseware/CoursewareCodeBlock.vue @@ -5,6 +5,7 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" + @showEdit="initCurrentData" @storeEdit="storeBlock" @closeEdit="initCurrentData" > @@ -35,12 +36,14 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; +import { blockMixin } from './block-mixin.js'; import hljs from 'highlight.js'; import { mapActions } from 'vuex'; export default { name: 'courseware-code-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, }, diff --git a/resources/vue/components/courseware/CoursewareConfirmBlock.vue b/resources/vue/components/courseware/CoursewareConfirmBlock.vue index 4e602f82e1f..6d37a576d4b 100644 --- a/resources/vue/components/courseware/CoursewareConfirmBlock.vue +++ b/resources/vue/components/courseware/CoursewareConfirmBlock.vue @@ -6,6 +6,7 @@ :isTeacher="isTeacher" :preview="true" :defaultGrade="false" + @showEdit="initCurrentData" @storeEdit="storeBlock" @closeEdit="initCurrentData" > diff --git a/resources/vue/components/courseware/CoursewareContainerActions.vue b/resources/vue/components/courseware/CoursewareContainerActions.vue index 40e618e24e2..fdea003c010 100644 --- a/resources/vue/components/courseware/CoursewareContainerActions.vue +++ b/resources/vue/components/courseware/CoursewareContainerActions.vue @@ -6,11 +6,13 @@ @editContainer="editContainer" @deleteContainer="deleteContainer" @sortBlocks="sortBlocks" + @removeLock="removeLock" /> </div> </template> <script> +import { mapGetters } from 'vuex'; export default { name: 'courseware-container-actions', props: { @@ -18,20 +20,47 @@ export default { container: Object, }, computed: { + ...mapGetters({ + userId: 'userId', + userIsTeacher: 'userIsTeacher', + }), + blocked() { + return this.container?.relationships?.['edit-blocker']?.data !== null; + }, + blockerId() { + return this.blocked ? this.container?.relationships?.['edit-blocker']?.data?.id : null; + }, + blockedByThisUser() { + return this.blocked && this.userId === this.blockerId; + }, + blockedByAnotherUser() { + return this.blocked && this.userId !== this.blockerId; + }, menuItems() { - if (this.container.attributes["container-type"] === 'list') { - return [ - { id: 1, label: this.$gettext('Blöcke sortieren'), icon: 'arr_1sort', emit: 'sortBlocks' }, - { id: 2, label: this.$gettext('Abschnitt löschen'), icon: 'trash', emit: 'deleteContainer' } - ]; - } else { - return [ - { id: 1, label: this.$gettext('Abschnitt bearbeiten'), icon: 'edit', emit: 'editContainer' }, - { id: 2, label: this.$gettext('Blöcke sortieren'), icon: 'arr_1sort', emit: 'sortBlocks' }, - { id: 3, label: this.$gettext('Abschnitt löschen'), icon: 'trash', emit: 'deleteContainer' }, - ]; + let menuItems = []; + if (!this.blockedByAnotherUser) { + if (this.container.attributes["container-type"] !== 'list') { + menuItems.push({ id: 1, label: this.$gettext('Abschnitt bearbeiten'), icon: 'edit', emit: 'editContainer' }); + } + menuItems.push({ id: 2, label: this.$gettext('Blöcke sortieren'), icon: 'arr_1sort', emit: 'sortBlocks' }); + menuItems.push({ id: 3, label: this.$gettext('Abschnitt löschen'), icon: 'trash', emit: 'deleteContainer' }); } + + if (this.blocked && this.blockedByAnotherUser && this.userIsTeacher) { + menuItems.push({ + id: 4, + label: this.$gettext('Sperre aufheben'), + icon: 'lock-unlocked', + emit: 'removeLock', + }); + } + + menuItems.sort((a, b) => { + return a.id > b.id ? 1 : b.id > a.id ? -1 : 0; + }); + return menuItems; }, + }, methods: { menuAction(action) { @@ -45,6 +74,9 @@ export default { }, sortBlocks() { this.$emit('sortBlocks'); + }, + removeLock() { + this.$emit('removeLock'); } }, }; diff --git a/resources/vue/components/courseware/CoursewareDateBlock.vue b/resources/vue/components/courseware/CoursewareDateBlock.vue index da119891f81..21623f75579 100644 --- a/resources/vue/components/courseware/CoursewareDateBlock.vue +++ b/resources/vue/components/courseware/CoursewareDateBlock.vue @@ -5,6 +5,7 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" + @showEdit="initCurrentData" @storeEdit="storeBlock" @closeEdit="initCurrentData" > @@ -84,9 +85,10 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import { mapActions } from 'vuex'; - +import { blockMixin } from './block-mixin.js'; export default { name: 'courseware-date-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, }, @@ -196,8 +198,6 @@ export default { containerId: this.block.relationships.container.data.id, }); } - - }, }, }; diff --git a/resources/vue/components/courseware/CoursewareDefaultBlock.vue b/resources/vue/components/courseware/CoursewareDefaultBlock.vue index dd56fbe5191..5df64c99a5a 100644 --- a/resources/vue/components/courseware/CoursewareDefaultBlock.vue +++ b/resources/vue/components/courseware/CoursewareDefaultBlock.vue @@ -3,12 +3,15 @@ <div class="cw-content-wrapper" :class="[showEditMode ? 'cw-content-wrapper-active' : '']"> <header v-if="showEditMode" class="cw-block-header"> <span class="cw-sortable-handle"></span> - <span v-if="!block.attributes.visible" class="cw-default-block-invisible-info"> - <studip-icon shape="visibility-invisible" /> - </span> + <studip-icon v-if="!block.attributes.visible" shape="visibility-invisible" /> + <studip-icon v-if="blockedByAnotherUser" shape="lock-locked" /> <span>{{ blockTitle }}</span> + <span v-if="blockedByAnotherUser" class="cw-default-block-blocker-warning"> + | {{ $gettextInterpolate('wird im Moment von %{ userName } bearbeitet', { userName: this.blockingUserName }) }} + </span> + <span v-if="!block.attributes.visible" class="cw-default-block-invisible-info"> - (<translate>unsichtbar für Nutzende ohne Schreibrecht</translate>) + | {{ $gettext('unsichtbar für Nutzende ohne Schreibrecht') }} </span> <courseware-block-actions :block="block" @@ -18,6 +21,7 @@ @showInfo="displayFeature('Info')" @showExportOptions="displayFeature('ExportOptions')" @deleteBlock="displayDeleteDialog()" + @removeLock="displayRemoveLockDialog()" /> </header> <div v-if="showContent" class="cw-block-content"> @@ -59,8 +63,18 @@ height="180" width="360" @confirm="executeDelete" - @close="showDeleteDialog = false" + @close="closeDeleteDialog" + ></studip-dialog> + <studip-dialog + v-if="showRemoveLockDialog" + :title="textRemoveLockTitle" + :question="textRemoveLockAlert" + height="200" + width="450" + @confirm="executeRemoveLock" + @close="showRemoveLockDialog = false" ></studip-dialog> + </div> </template> @@ -118,9 +132,12 @@ export default { showContent: true, showEditModeShortcut: false, showDeleteDialog: false, + showRemoveLockDialog: false, currentComments: [], textDeleteTitle: this.$gettext('Block unwiderruflich löschen'), textDeleteAlert: this.$gettext('Möchten Sie diesen Block wirklich löschen?'), + textRemoveLockTitle: this.$gettext('Sperre aufheben'), + textRemoveLockAlert: this.$gettext('Möchten Sie die Sperre dieses Blocks wirklich aufheben?'), }; }, computed: { @@ -129,6 +146,7 @@ export default { containerById: 'courseware-containers/byId', context: 'context', userId: 'userId', + userById: 'users/byId', viewMode: 'viewMode', }), showEditMode() { @@ -142,10 +160,10 @@ export default { return this.viewMode === 'discuss'; }, blocked() { - return this.block?.relationships['edit-blocker'].data !== null; + return this.block?.relationships?.['edit-blocker']?.data !== null; }, blockerId() { - return this.blocked ? this.block?.relationships['edit-blocker'].data?.id : null; + return this.blocked ? this.block?.relationships?.['edit-blocker']?.data?.id : null; }, blockedByThisUser() { return this.blocked && this.userId === this.blockerId; @@ -153,6 +171,16 @@ export default { blockedByAnotherUser() { return this.blocked && this.userId !== this.blockerId; }, + blockingUser() { + if (this.blockedByAnotherUser) { + return this.userById({id: this.blockerId}); + } + + return null; + }, + blockingUserName() { + return this.blockingUser ? this.blockingUser.attributes['formatted-name'] : ''; + }, blockTitle() { const type = this.block.attributes['block-type']; @@ -175,10 +203,12 @@ export default { methods: { ...mapActions({ companionInfo: 'companionInfo', + companionWarning: 'companionWarning', deleteBlock: 'deleteBlockInContainer', lockObject: 'lockObject', unlockObject: 'unlockObject', loadContainer: 'loadContainer', + loadBlock: 'courseware-blocks/loadById', updateContainer: 'updateContainer', }), async displayFeature(element) { @@ -192,26 +222,16 @@ export default { this.showContent = true; if (element) { if (element === 'Edit') { - await this.loadContainer(this.block.relationships.container.data.id); + await this.loadBlock({ id: this.block.id, options: { include: 'edit-blocker' } }); if (!this.blocked) { - try { - await this.lockObject({ id: this.block.id, type: 'courseware-blocks' }); - } catch(error) { - if (error.status === 403) { - this.companionInfo({ info: this.$gettext('Dieser Block wird bereits bearbeitet.') }); - } else { - console.log(error); - } - - return false; - } + await this.lockObject({ id: this.block.id, type: 'courseware-blocks' }); if (!this.preview) { this.showContent = false; } this['show' + element] = true; this.showFeatures = true; } else { - if (this.userId === this.blockerId) { + if (this.blockedByThisUser) { if (!this.preview) { this.showContent = false; } @@ -227,25 +247,64 @@ export default { } } }, + prepareStoreEdit() { + // storeEdit is only emitted when the block is not in deleting process. + if (!this.showDeleteDialog) { + this.storeBlock(); + } + }, + async storeBlock() { + await this.loadBlock({ id: this.block.id, options: { include: 'edit-blocker' } }); + + if (this.blockedByThisUser) { + this.$emit('storeEdit'); + } + + if (this.blockedByAnotherUser) { + this.companionWarning({ info: this.$gettextInterpolate('Ihre Änderungen konnten nicht gespeichert werden, da %{blockingUserName} die Bearbeitung übernommen hat.', {blockingUserName: this.blockingUserName}) }); + this.displayFeature(false); + this.$emit('closeEdit'); + } + if (this.blockerId === null) { + await this.lockObject({ id: this.block.id, type: 'courseware-blocks' }); + this.$emit('storeEdit'); + } + }, async closeEdit() { + await this.loadBlock({ id: this.block.id , options: { include: 'edit-blocker' } }); // has block editor lock changed? this.displayFeature(false); this.$emit('closeEdit'); - await this.unlockObject({ id: this.block.id, type: 'courseware-blocks' }); - this.loadContainer(this.block.relationships.container.data.id); // to update block editor lock + if (this.blockedByThisUser) { + await this.unlockObject({ id: this.block.id, type: 'courseware-blocks' }); + } + this.loadBlock({ id: this.block.id , options: { include: 'edit-blocker' } }); // to update block editor lock }, async displayDeleteDialog() { + await this.loadBlock({ id: this.block.id, options: { include: 'edit-blocker' } }); if (!this.blocked) { await this.lockObject({ id: this.block.id, type: 'courseware-blocks' }); this.showDeleteDialog = true; } else { - if (this.userId === this.blockerId) { + if (this.blockedByThisUser) { this.showDeleteDialog = true; } else { - this.companionInfo({ info: 'Dieser Block wird bereits bearbeitet.' }); + this.companionInfo({ info: this.$gettextInterpolate('Löschen nicht möglich, da %{blockingUserName} den Block bearbeitet.', {blockingUserName: this.blockingUserName}) }); } } }, + async closeDeleteDialog() { + await this.loadBlock({ id: this.block.id, options: { include: 'edit-blocker' } }); + if (this.blockedByThisUser) { + await this.unlockObject({ id: this.block.id, type: 'courseware-blocks' }); + } + this.showDeleteDialog = false; + }, async executeDelete() { + await this.loadBlock({ id: this.block.id, options: { include: 'edit-blocker' } }); + if (this.blockedByAnotherUser) { + this.companionInfo({ info: this.$gettextInterpolate('Löschen nicht möglich, da %{blockingUserName} die Bearbeitung übernommen hat.', {blockingUserName: this.blockingUserName}) }); + return false; + } const containerId = this.block.relationships.container.data.id; await this.loadContainer(containerId); let container = this.containerById({id: containerId}); @@ -277,13 +336,20 @@ export default { containerId: containerId, }); }, - - prepareStoreEdit() { - // storeEdit is only emitted when the block is not in deleting process. - if (!this.showDeleteDialog) { - this.$emit('storeEdit'); - } + displayRemoveLockDialog() { + this.showRemoveLockDialog = true; + }, + async executeRemoveLock() { + await this.unlockObject({ id: this.block.id , type: 'courseware-blocks' }); + await this.loadBlock({ id: this.block.id }); + this.showRemoveLockDialog = false; } + }, + watch: { + showEdit(state) { + this.$emit('showEdit', state); + } + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareDefaultContainer.vue b/resources/vue/components/courseware/CoursewareDefaultContainer.vue index a24e9a8bd51..962febba6f3 100644 --- a/resources/vue/components/courseware/CoursewareDefaultContainer.vue +++ b/resources/vue/components/courseware/CoursewareDefaultContainer.vue @@ -5,13 +5,18 @@ > <div class="cw-container-content"> <header v-if="showEditMode && canEdit" class="cw-container-header"> + <studip-icon v-if="blockedByAnotherUser" shape="lock-locked" /> <span>{{ container.attributes.title }} ({{container.attributes.width}})</span> + <span v-if="blockedByAnotherUser" class="cw-default-container-blocker-warning"> + | {{ $gettextInterpolate('wird im Moment von %{ userName } bearbeitet', { userName: this.blockingUserName }) }} + </span> <courseware-container-actions :canEdit="canEdit" :container="container" @editContainer="displayEditDialog" @deleteContainer="displayDeleteDialog" @sortBlocks="sortBlocks" + @removeLock="displayRemoveLockDialog" /> </header> <div class="cw-block-wrapper" :class="{ 'cw-block-wrapper-active': showEditMode }"> @@ -44,6 +49,17 @@ @confirm="executeDelete" @close="closeDeleteDialog" ></studip-dialog> + + <studip-dialog + v-if="showRemoveLockDialog" + :title="textRemoveLockTitle" + :question="textRemoveLockAlert" + height="200" + width="450" + @confirm="executeRemoveLock" + @close="showRemoveLockDialog = false" + ></studip-dialog> + </div> </div> </template> @@ -69,16 +85,20 @@ export default { return { showDeleteDialog: false, showEditDialog: false, + showRemoveLockDialog: false, textEditConfirm: this.$gettext('Speichern'), textEditClose: this.$gettext('Schließen'), textEditTitle: this.$gettext('Abschnitt bearbeiten'), textDeleteTitle: this.$gettext('Abschnitt unwiderruflich löschen'), textDeleteAlert: this.$gettext('Möchten Sie diesen Abschnitt wirklich löschen?'), + textRemoveLockTitle: this.$gettext('Sperre aufheben'), + textRemoveLockAlert: this.$gettext('Möchten Sie die Sperre dieses Abschnitts wirklich aufheben?'), }; }, computed: { ...mapGetters({ userId: 'userId', + userById: 'users/byId', }), showEditMode() { return this.$store.getters.viewMode === 'edit'; @@ -87,10 +107,10 @@ export default { return this.container.attributes.payload.colspan ? this.container.attributes.payload.colspan : 'full'; }, blocked() { - return this.container?.relationships['edit-blocker'].data !== null; + return this.container?.relationships?.['edit-blocker']?.data !== null; }, blockerId() { - return this.blocked ? this.container?.relationships['edit-blocker'].data?.id : null; + return this.blocked ? this.container?.relationships?.['edit-blocker']?.data?.id : null; }, blockedByThisUser() { return this.blocked && this.userId === this.blockerId; @@ -98,53 +118,88 @@ export default { blockedByAnotherUser() { return this.blocked && this.userId !== this.blockerId; }, + blockingUser() { + if (this.blockedByAnotherUser) { + return this.userById({id: this.blockerId}); + } + + return null; + }, + blockingUserName() { + return this.blockingUser ? this.blockingUser.attributes['formatted-name'] : ''; + }, }, methods: { ...mapActions({ + companionInfo: 'companionInfo', + companionWarning: 'companionWarning', + loadContainer: 'courseware-containers/loadById', deleteContainer: 'deleteContainer', lockObject: 'lockObject', unlockObject: 'unlockObject', companionInfo: 'companionInfo', }), async displayEditDialog() { + await this.loadContainer({ id: this.container.id, options: { include: 'edit-blocker' } }); if (this.blockedByAnotherUser) { this.companionInfo({ info: this.$gettext('Dieser Abschnitt wird bereits bearbeitet.') }); return false; } - try { - await this.lockObject({ id: this.container.id, type: 'courseware-containers' }); - } catch(error) { - if (error.status === 409) { - this.companionInfo({ info: this.$gettext('Dieser Abschnitt wird bereits bearbeitet.') }); - } else { - console.log(error); - } - - return false; - } + await this.lockObject({ id: this.container.id, type: 'courseware-containers' }); this.showEditDialog = true; }, async closeEdit() { + await this.loadContainer({ id: this.container.id }); this.$emit('closeEdit'); this.showEditDialog = false; - await this.unlockObject({ id: this.container.id, type: 'courseware-containers' }); + if (this.blockedByThisUser) { + await this.unlockObject({ id: this.container.id, type: 'courseware-containers' }); + } + await this.loadContainer({ id: this.container.id, options: { include: 'edit-blocker' } }); }, async storeContainer() { - this.$emit('storeContainer'); + await this.loadContainer({ id: this.container.id, options: { include: 'edit-blocker' } }); + if (this.blockedByThisUser) { + this.$emit('storeContainer'); + } + if (this.blockedByAnotherUser) { + this.companionWarning({ info: this.$gettextInterpolate('Ihre Änderungen konnten nicht gespeichert werden, da %{blockingUserName} die Bearbeitung übernommen hat.', {blockingUserName: this.blockingUserName}) }); + this.$emit('closeEdit'); + } + if (this.blockerId === null) { + await this.lockObject({ id: this.container.id, type: 'courseware-containers' }); + this.$emit('storeContainer'); + } this.showEditDialog = false; - // await this.unlockObject({ id: this.container.id, type: 'courseware-containers' }); }, async displayDeleteDialog() { - await this.lockObject({ id: this.container.id, type: 'courseware-containers' }); - this.showDeleteDialog = true; + await this.loadContainer({ id: this.container.id, options: { include: 'edit-blocker' } }); + if (!this.blocked) { + await this.lockObject({ id: this.container.id, type: 'courseware-containers' }); + this.showDeleteDialog = true; + } else { + if (this.blockedByThisUser) { + this.showDeleteDialog = true; + } else { + this.companionInfo({ info: this.$gettextInterpolate('Löschen nicht möglich, da %{blockingUserName} den Abschnitt bearbeitet.', {blockingUserName: this.blockingUserName}) }); + } + } }, async closeDeleteDialog() { - await this.unlockObject({ id: this.container.id, type: 'courseware-containers' }); + await this.loadContainer({ id: this.container.id, options: { include: 'edit-blocker' } }); + if (this.blockedByThisUser) { + await this.unlockObject({ id: this.container.id, type: 'courseware-containers' }); + } this.showDeleteDialog = false; }, async executeDelete() { + await this.loadContainer({ id: this.container.id, options: { include: 'edit-blocker' } }); + if (this.blockedByAnotherUser) { + this.companionInfo({ info: this.$gettextInterpolate('Löschen nicht möglich, da %{blockingUserName} die Bearbeitung übernommen hat.', {blockingUserName: this.blockingUserName}) }); + return false; + } await this.deleteContainer({ containerId: this.container.id, structuralElementId: this.container.relationships['structural-element'].data.id, @@ -155,24 +210,31 @@ export default { this.showDeleteDialog = false; }, async sortBlocks() { + await this.loadContainer({ id: this.container.id, options: { include: 'edit-blocker' } }); if (this.blockedByAnotherUser) { this.companionInfo({ info: this.$gettext('Dieser Abschnitt wird bereits bearbeitet.') }); return false; } - try { - await this.lockObject({ id: this.container.id, type: 'courseware-containers' }); - } catch(error) { - if (error.status === 409) { - this.companionInfo({ info: this.$gettext('Dieser Abschnitt wird bereits bearbeitet.') }); - } else { - console.log(error); - } - - return false; - } + await this.lockObject({ id: this.container.id, type: 'courseware-containers' }); this.$emit('sortBlocks'); - } + }, + displayRemoveLockDialog() { + this.showRemoveLockDialog = true; + }, + async executeRemoveLock() { + await this.unlockObject({ id: this.container.id , type: 'courseware-containers' }); + await this.loadContainer({ id: this.container.id }); + this.showRemoveLockDialog = false; + }, + }, + + watch: { + showEditDialog(state) { + this.$emit('showEdit', state); + } + } + }; </script> diff --git a/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue b/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue index a8173c7d193..022590bb005 100644 --- a/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue +++ b/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue @@ -5,6 +5,7 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" + @showEdit="initCurrentData" @storeEdit="storeBlock" @closeEdit="initCurrentData" > @@ -115,12 +116,13 @@ import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFileChooser from './CoursewareFileChooser.vue'; import CoursewareTabs from './CoursewareTabs.vue'; import CoursewareTab from './CoursewareTab.vue'; - +import { blockMixin } from './block-mixin.js'; import { mapActions } from 'vuex'; import StudipIcon from '../StudipIcon.vue'; export default { name: 'courseware-dialog-cards-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, CoursewareFileChooser, diff --git a/resources/vue/components/courseware/CoursewareDocumentBlock.vue b/resources/vue/components/courseware/CoursewareDocumentBlock.vue index 134e167ca45..f24f5f82155 100644 --- a/resources/vue/components/courseware/CoursewareDocumentBlock.vue +++ b/resources/vue/components/courseware/CoursewareDocumentBlock.vue @@ -5,6 +5,7 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="false" + @showEdit="initCurrentData" @storeEdit="storeBlock" @closeEdit="initCurrentData" > @@ -69,6 +70,7 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFileChooser from './CoursewareFileChooser.vue'; +import { blockMixin } from './block-mixin.js'; import * as pdfjsLib from 'pdfjs-dist'; import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry'; @@ -76,6 +78,7 @@ import { mapActions } from 'vuex'; export default { name: 'courseware-document-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, CoursewareFileChooser, @@ -256,7 +259,6 @@ export default { containerId: this.block.relationships.container.data.id, }); } - }, }, }; diff --git a/resources/vue/components/courseware/CoursewareDownloadBlock.vue b/resources/vue/components/courseware/CoursewareDownloadBlock.vue index 1f7a78beb90..d5c08d2eb35 100644 --- a/resources/vue/components/courseware/CoursewareDownloadBlock.vue +++ b/resources/vue/components/courseware/CoursewareDownloadBlock.vue @@ -6,6 +6,7 @@ :isTeacher="isTeacher" :preview="true" :defaultGrade="false" + @showEdit="initCurrentData" @storeEdit="storeBlock" @closeEdit="initCurrentData" > @@ -77,11 +78,12 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFileChooser from './CoursewareFileChooser.vue'; - +import { blockMixin } from './block-mixin.js'; import { mapActions, mapGetters } from 'vuex'; export default { name: 'courseware-download-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, CoursewareFileChooser, diff --git a/resources/vue/components/courseware/CoursewareEmbedBlock.vue b/resources/vue/components/courseware/CoursewareEmbedBlock.vue index ec8a4259985..8ee8808fb92 100644 --- a/resources/vue/components/courseware/CoursewareEmbedBlock.vue +++ b/resources/vue/components/courseware/CoursewareEmbedBlock.vue @@ -5,6 +5,7 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="false" + @showEdit="initCurrentData" @storeEdit="storeBlock" @closeEdit="initCurrentData" > @@ -87,11 +88,12 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; - +import { blockMixin } from './block-mixin.js'; import { mapActions } from 'vuex'; export default { name: 'courseware-embed-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, }, @@ -245,5 +247,6 @@ export default { }); }, }, + }; </script> diff --git a/resources/vue/components/courseware/CoursewareFolderBlock.vue b/resources/vue/components/courseware/CoursewareFolderBlock.vue index 2d1f355f5b5..2090c35358a 100644 --- a/resources/vue/components/courseware/CoursewareFolderBlock.vue +++ b/resources/vue/components/courseware/CoursewareFolderBlock.vue @@ -5,6 +5,7 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" + @showEdit="initCurrentData" @storeEdit="storeBlock" @closeEdit="initCurrentData" > @@ -141,10 +142,12 @@ import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFolderChooser from './CoursewareFolderChooser.vue'; import StudipDialog from '../StudipDialog.vue'; +import { blockMixin } from './block-mixin.js'; import { mapActions, mapGetters } from 'vuex'; export default { name: 'courseware-folder-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, CoursewareFolderChooser, diff --git a/resources/vue/components/courseware/CoursewareGalleryBlock.vue b/resources/vue/components/courseware/CoursewareGalleryBlock.vue index 9d0549b25c9..9e4fced7a0f 100644 --- a/resources/vue/components/courseware/CoursewareGalleryBlock.vue +++ b/resources/vue/components/courseware/CoursewareGalleryBlock.vue @@ -5,6 +5,7 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" + @showEdit="initCurrentData" @storeEdit="storeBlock" @closeEdit="initCurrentData" > @@ -83,11 +84,12 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFolderChooser from './CoursewareFolderChooser.vue'; - +import { blockMixin } from './block-mixin.js'; import { mapActions, mapGetters } from 'vuex'; export default { name: 'courseware-gallery-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, CoursewareFolderChooser, diff --git a/resources/vue/components/courseware/CoursewareHeadlineBlock.vue b/resources/vue/components/courseware/CoursewareHeadlineBlock.vue index 9cc3e5557fc..8a5bc6ce5d8 100644 --- a/resources/vue/components/courseware/CoursewareHeadlineBlock.vue +++ b/resources/vue/components/courseware/CoursewareHeadlineBlock.vue @@ -5,8 +5,9 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" + @showEdit="initCurrentData" @storeEdit="storeText" - @closeEdit="closeEdit" + @closeEdit="initCurrentData" > <template #content> <div @@ -166,11 +167,13 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFileChooser from './CoursewareFileChooser.vue'; +import { blockMixin } from './block-mixin.js'; import { mapGetters, mapActions } from 'vuex'; import contentIcons from './content-icons.js'; export default { name: 'courseware-headline-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, CoursewareFileChooser, @@ -342,9 +345,6 @@ export default { this.currentBackgroundImageId = file.id; this.currentBackgroundURL = file.download_url; }, - closeEdit() { - this.initCurrentData(); - }, storeText() { let attributes = {}; attributes.payload = {}; diff --git a/resources/vue/components/courseware/CoursewareIframeBlock.vue b/resources/vue/components/courseware/CoursewareIframeBlock.vue index 1f0f5bf9b3a..57c5c098b14 100644 --- a/resources/vue/components/courseware/CoursewareIframeBlock.vue +++ b/resources/vue/components/courseware/CoursewareIframeBlock.vue @@ -5,6 +5,7 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" + @showEdit="initCurrentData" @storeEdit="storeBlock" @closeEdit="initCurrentData" > @@ -104,12 +105,13 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; - +import { blockMixin } from './block-mixin.js'; import { mapActions, mapGetters } from 'vuex'; import md5 from 'md5'; export default { name: 'courseware-iframe-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, }, diff --git a/resources/vue/components/courseware/CoursewareImageMapBlock.vue b/resources/vue/components/courseware/CoursewareImageMapBlock.vue index 9ba60d662c1..f097e782bc6 100644 --- a/resources/vue/components/courseware/CoursewareImageMapBlock.vue +++ b/resources/vue/components/courseware/CoursewareImageMapBlock.vue @@ -5,6 +5,7 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" + @showEdit="initCurrentData" @storeEdit="storeBlock" @closeEdit="initCurrentData" > @@ -160,11 +161,12 @@ import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFileChooser from './CoursewareFileChooser.vue'; import CoursewareTabs from './CoursewareTabs.vue'; import CoursewareTab from './CoursewareTab.vue'; - +import { blockMixin } from './block-mixin.js'; import { mapActions, mapGetters } from 'vuex'; export default { name: 'courseware-image-map-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, CoursewareFileChooser, diff --git a/resources/vue/components/courseware/CoursewareKeyPointBlock.vue b/resources/vue/components/courseware/CoursewareKeyPointBlock.vue index 39124e548b9..05d5d2fd701 100644 --- a/resources/vue/components/courseware/CoursewareKeyPointBlock.vue +++ b/resources/vue/components/courseware/CoursewareKeyPointBlock.vue @@ -5,8 +5,9 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" + @showEdit="initCurrentData" @storeEdit="storeBlock" - @closeEdit="closeEdit" + @closeEdit="initCurrentData" > <template #content> <div class="cw-keypoint-content" :class="['cw-keypoint-' + currentColor]"> @@ -79,11 +80,13 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; +import { blockMixin } from './block-mixin.js'; import { mapActions } from 'vuex'; import contentIcons from './content-icons.js'; export default { name: 'courseware-key-point-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, }, @@ -194,9 +197,6 @@ export default { containerId: this.block.relationships.container.data.id, }); }, - closeEdit() { - this.initCurrentData(); - }, }, mounted() { this.initCurrentData(); diff --git a/resources/vue/components/courseware/CoursewareLinkBlock.vue b/resources/vue/components/courseware/CoursewareLinkBlock.vue index a2fce7d0045..9d65163c55e 100644 --- a/resources/vue/components/courseware/CoursewareLinkBlock.vue +++ b/resources/vue/components/courseware/CoursewareLinkBlock.vue @@ -5,6 +5,7 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" + @showEdit="initCurrentData" @storeEdit="storeBlock" @closeEdit="initCurrentData" > @@ -62,10 +63,12 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; +import { blockMixin } from './block-mixin.js'; import { mapActions, mapGetters } from 'vuex'; export default { name: 'courseware-link-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, }, diff --git a/resources/vue/components/courseware/CoursewareStructuralElement.vue b/resources/vue/components/courseware/CoursewareStructuralElement.vue index bdd01965f3c..5d468d55da4 100644 --- a/resources/vue/components/courseware/CoursewareStructuralElement.vue +++ b/resources/vue/components/courseware/CoursewareStructuralElement.vue @@ -62,6 +62,7 @@ @pdfExport="menuAction('pdfExport')" @showSuggest="menuAction('showSuggest')" @linkElement="menuAction('linkElement')" + @removeLock="menuAction('removeLock')" /> </template> </courseware-ribbon> @@ -75,6 +76,17 @@ }" > <div v-if="structuralElementLoaded" class="cw-companion-box-wrapper"> + <courseware-companion-box + v-if="blockedByAnotherUser" + :msgCompanion="$gettextInterpolate('Die Einstellungen dieser Seite werden im Moment von %{blockingUserName} bearbeitet', {blockingUserName: blockingUserName})" + mood="pointing" + > + <template #companionActions> + <button class="button" @click="menuAction('removeLock')"> + {{ textRemoveLock.title }} + </button> + </template> + </courseware-companion-box> <courseware-empty-element-box v-if="showEmptyElementBox" :canEdit="canEdit" @@ -589,6 +601,15 @@ </form> </template> </studip-dialog> + <studip-dialog + v-if="showRemoveLockDialog" + :title="textRemoveLock.title" + :question="textRemoveLock.alert" + height="200" + width="450" + @confirm="executeRemoveLock" + @close="showElementRemoveLockDialog(false)" + ></studip-dialog> </div> <div v-else> <courseware-companion-box @@ -685,6 +706,10 @@ export default { perv: this.$gettext('zurück'), next: this.$gettext('weiter'), }, + textRemoveLock: { + title: this.$gettext('Sperre aufheben'), + alert: this.$gettext('Möchten Sie die Sperre der Seite wirklich aufheben?'), + }, exportRunning: false, exportChildren: false, oerExportRunning: false, @@ -705,7 +730,7 @@ export default { publicLink: { passsword: '', 'expire-date': '' - } + }, }; }, @@ -731,6 +756,7 @@ export default { showOerDialog: 'showStructuralElementOerDialog', showSuggestOerDialog: 'showSuggestOerDialog', showLinkDialog: 'showStructuralElementLinkDialog', + showRemoveLockDialog: 'showStructuralElementRemoveLockDialog', oerEnabled: 'oerEnabled', licenses: 'licenses', exportState: 'exportState', @@ -740,6 +766,11 @@ export default { viewMode: 'viewMode', taskById: 'courseware-tasks/byId', userById: 'users/byId', + + blocked: 'currentElementBlocked', + blockerId: 'currentElementBlockerId', + blockedByThisUser: 'currentElementBlockedByThisUser', + blockedByAnotherUser: 'currentElementBlockedByAnotherUser', }), currentId() { @@ -927,25 +958,34 @@ export default { ]; if (this.canEdit) { - menu.push({ - id: 1, - label: this.$gettext('Seite bearbeiten'), - icon: 'edit', - emit: 'editCurrentElement', - }); - menu.push({ - id: 2, - label: this.$gettext('Abschnitte sortieren'), - icon: 'arr_1sort', - emit: 'sortContainers', - }); - + if (!this.blocked) { + menu.push({ + id: 1, + label: this.$gettext('Seite bearbeiten'), + icon: 'edit', + emit: 'editCurrentElement', + }); + menu.push({ + id: 2, + label: this.$gettext('Abschnitte sortieren'), + icon: 'arr_1sort', + emit: 'sortContainers', + }); + } + if (this.blocked && this.blockedByAnotherUser && this.userIsTeacher) { + menu.push({ + id: 1, + label: this.textRemoveLock.title, + icon: 'lock-unlocked', + emit: 'removeLock', + }); + } menu.push({ id: 3, label: this.$gettext('Seite hinzufügen'), icon: 'add', emit: 'addElement' }); } if (this.context.type === 'users') { menu.push({ id: 7, label: this.$gettext('Öffentlichen Link erzeugen'), icon: 'group', emit: 'linkElement' }); } - if (!this.isRoot && this.canEdit && !this.isTask) { + if (!this.isRoot && this.canEdit && !this.isTask && !this.blocked) { menu.push({ id: 8, label: this.$gettext('Seite löschen'), @@ -1116,17 +1156,15 @@ export default { return ''; }, - blocked() { - return this.structuralElement?.relationships['edit-blocker'].data !== null; - }, - blockerId() { - return this.blocked ? this.structuralElement?.relationships['edit-blocker'].data?.id : null; - }, - blockedByThisUser() { - return this.blocked && this.userId === this.blockerId; + blockingUser() { + if (this.blockedByAnotherUser) { + return this.userById({id: this.blockerId}); + } + + return null; }, - blockedByAnotherUser() { - return this.blocked && this.userId !== this.blockerId; + blockingUserName() { + return this.blockingUser ? this.blockingUser.attributes['formatted-name'] : ''; }, discussView() { return this.viewMode === 'discuss'; @@ -1256,6 +1294,7 @@ export default { unlockObject: 'unlockObject', addBookmark: 'addBookmark', companionInfo: 'companionInfo', + companionWarning: 'companionWarning', companionError: 'companionError', uploadImageForStructuralElement: 'uploadImageForStructuralElement', deleteImageForStructuralElement: 'deleteImageForStructuralElement', @@ -1268,6 +1307,7 @@ export default { showElementDeleteDialog: 'showElementDeleteDialog', showElementOerDialog: 'showElementOerDialog', showElementLinkDialog: 'showElementLinkDialog', + showElementRemoveLockDialog: 'showElementRemoveLockDialog', updateShowSuggestOerDialog: 'updateShowSuggestOerDialog', updateContainer: 'updateContainer', setStructuralElementSortMode: 'setStructuralElementSortMode', @@ -1275,6 +1315,7 @@ export default { loadTask: 'loadTask', loadStructuralElement: 'loadStructuralElement', createLink: 'createLink', + setCurrentElementId: 'coursewareCurrentElement', }), initCurrent() { @@ -1283,7 +1324,11 @@ export default { }, async menuAction(action) { switch (action) { + case 'removeLock': + this.displayRemoveLockDialog(); + break; case 'editCurrentElement': + await this.loadStructuralElement(this.currentId); if (this.blockedByAnotherUser) { this.companionInfo({ info: this.$gettext('Diese Seite wird bereits bearbeitet.') }); @@ -1291,7 +1336,7 @@ export default { } try { await this.lockObject({ id: this.currentId, type: 'courseware-structural-elements' }); - } catch (error) { + } catch(error) { if (error.status === 409) { this.companionInfo({ info: this.$gettext('Diese Seite wird bereits bearbeitet.') }); } else { @@ -1300,6 +1345,7 @@ export default { return false; } + this.initCurrent(); this.showElementEditDialog(true); break; case 'addElement': @@ -1309,6 +1355,12 @@ export default { this.showElementAddDialog(true); break; case 'deleteCurrentElement': + await this.loadStructuralElement(this.currentId); + if (this.blockedByAnotherUser) { + this.companionInfo({ info: this.$gettextInterpolate('Löschen nicht möglich, da %{blockingUserName} die Seite bearbeitet.', {blockingUserName: this.blockingUserName}) }); + + return false; + } await this.lockObject({ id: this.currentId, type: 'courseware-structural-elements' }); this.showElementDeleteDialog(true); break; @@ -1328,6 +1380,7 @@ export default { this.setBookmark(); break; case 'sortContainers': + await this.loadStructuralElement(this.currentId); if (this.blockedByAnotherUser) { this.companionInfo({ info: this.$gettext('Diese Seite wird bereits bearbeitet.') }); @@ -1352,7 +1405,11 @@ export default { } }, async closeEditDialog() { - await this.unlockObject({ id: this.currentId, type: 'courseware-structural-elements' }); + await this.loadStructuralElement(this.currentElement.id); + if (this.blockedByThisUser) { + await this.unlockObject({ id: this.currentId, type: 'courseware-structural-elements' }); + await this.loadStructuralElement(this.currentElement.id); + } this.showElementEditDialog(false); this.initCurrent(); }, @@ -1374,6 +1431,15 @@ export default { this.initCurrent(); }, async storeCurrentElement() { + await this.loadStructuralElement(this.currentElement.id); + if (this.blockedByAnotherUser) { + this.companionWarning({ info: this.$gettextInterpolate('Ihre Änderungen konnten nicht gespeichert werden, da %{blockingUserName} die Bearbeitung übernommen hat.', {blockingUserName: this.blockingUserName}) }); + this.showElementEditDialog(false); + return false; + } + if (!this.blocked) { + await this.lockObject({ id: this.currentId, type: 'courseware-structural-elements' }); + } const file = this.$refs?.upload_image?.files[0]; if (file) { if (file.size > 2097152) { @@ -1471,10 +1537,19 @@ export default { }, async closeDeleteDialog() { - await this.unlockObject({ id: this.currentId, type: 'courseware-structural-elements' }); + await this.loadStructuralElement(this.currentElement.id); + if (this.blockedByThisUser) { + await this.unlockObject({ id: this.currentId, type: 'courseware-structural-elements' }); + } this.showElementDeleteDialog(false); }, - deleteCurrentElement() { + async deleteCurrentElement() { + await this.loadStructuralElement(this.currentElement.id); + if (this.blockedByAnotherUser) { + this.companionWarning({ info: this.$gettextInterpolate('Löschen nicht möglich, da %{blockingUserName} die Bearbeitung übernommen hat.', {blockingUserName: this.blockingUserName}) }); + this.showElementDeleteDialog(false); + return false; + } let parent_id = this.structuralElement.relationships.parent.data.id; this.showElementDeleteDialog(false); this.companionInfo({ info: this.$gettext('Lösche Seite und alle darunter liegenden Elemente.') }); @@ -1561,6 +1636,14 @@ export default { 'expire-date': '' }; this.showElementLinkDialog(false); + }, + displayRemoveLockDialog() { + this.showElementRemoveLockDialog(true); + }, + async executeRemoveLock() { + await this.unlockObject({ id: this.currentId, type: 'courseware-structural-elements' }); + await this.loadStructuralElement(this.currentElement.id); + this.showElementRemoveLockDialog(false); } }, created() { @@ -1568,7 +1651,8 @@ export default { }, watch: { - structuralElement() { + async structuralElement() { + this.setCurrentElementId(this.structuralElement.id); this.initCurrent(); if (this.isTask) { this.loadTask({ diff --git a/resources/vue/components/courseware/CoursewareTableOfContentsBlock.vue b/resources/vue/components/courseware/CoursewareTableOfContentsBlock.vue index c5d268ef988..1a582808109 100644 --- a/resources/vue/components/courseware/CoursewareTableOfContentsBlock.vue +++ b/resources/vue/components/courseware/CoursewareTableOfContentsBlock.vue @@ -5,8 +5,9 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" + @showEdit="initCurrentData" @storeEdit="storeText" - @closeEdit="closeEdit" + @closeEdit="initCurrentData" > <template #content> <div v-if="currentStyle !== 'tiles' && currentTitle !== ''" class="cw-block-title">{{ currentTitle }}</div> @@ -88,10 +89,12 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; +import { blockMixin } from './block-mixin.js'; import { mapActions, mapGetters } from 'vuex'; export default { name: 'courseware-table-of-contents-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, }, @@ -168,9 +171,6 @@ export default { this.currentTitle = this.title; this.currentStyle = this.style; }, - closeEdit() { - this.initCurrentData(); - }, storeText() { let attributes = {}; attributes.payload = {}; diff --git a/resources/vue/components/courseware/CoursewareTabsContainer.vue b/resources/vue/components/courseware/CoursewareTabsContainer.vue index 66349d95d18..abe78f899e0 100644 --- a/resources/vue/components/courseware/CoursewareTabsContainer.vue +++ b/resources/vue/components/courseware/CoursewareTabsContainer.vue @@ -4,6 +4,7 @@ containerClass="cw-container-tabs" :canEdit="canEdit" :isTeacher="isTeacher" + @showEdit="setShowEdit" @storeContainer="storeContainer" @closeEdit="initCurrentData" @sortBlocks="enableSort" @@ -129,6 +130,7 @@ export default { }, data() { return { + showEdit: false, currentContainer: null, currentSections: [], unallocatedBlocks: [], @@ -197,6 +199,9 @@ export default { this.currentSections = sections; }, + setShowEdit(state) { + this.showEdit = state; + }, addSection() { this.currentContainer.attributes.payload.sections.push({ name: '', icon: '', blocks: [] }); }, @@ -261,7 +266,9 @@ export default { }, watch: { blocks() { - this.initCurrentData(); + if (!this.showEdit) { + this.initCurrentData(); + } } } }; diff --git a/resources/vue/components/courseware/CoursewareTextBlock.vue b/resources/vue/components/courseware/CoursewareTextBlock.vue index d051cb643d0..fdae56d00bd 100644 --- a/resources/vue/components/courseware/CoursewareTextBlock.vue +++ b/resources/vue/components/courseware/CoursewareTextBlock.vue @@ -6,8 +6,9 @@ :isTeacher="isTeacher" :preview="false" ref="defaultBlock" + @showEdit="initCurrent" @storeEdit="storeText" - @closeEdit="closeEdit" + @closeEdit="initCurrent" > <template #content> <section class="cw-block-content formatted-content" v-html="currentText" ref="content"></section> @@ -22,11 +23,13 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; +import { blockMixin } from './block-mixin.js'; import ClassicEditor from '../../../assets/javascripts/chunks/wysiwyg.js' import { mapActions } from 'vuex'; export default { name: 'courseware-text-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, }, @@ -45,19 +48,18 @@ export default { }; }, computed: { - text() { + text() { return this.block?.attributes?.payload?.text; }, }, mounted() { - this.currentText = this.text; - this.loadMathjax(); + this.initCurrent(); }, methods: { ...mapActions({ updateBlock: 'updateBlockInContainer', }), - closeEdit() { + initCurrent() { this.currentText = this.text; this.loadMathjax(); }, diff --git a/resources/vue/components/courseware/CoursewareTypewriterBlock.vue b/resources/vue/components/courseware/CoursewareTypewriterBlock.vue index abdc8ebe368..14c70720344 100644 --- a/resources/vue/components/courseware/CoursewareTypewriterBlock.vue +++ b/resources/vue/components/courseware/CoursewareTypewriterBlock.vue @@ -5,8 +5,9 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" + @showEdit="initCurrentData" @storeEdit="storeText" - @closeEdit="closeEdit" + @closeEdit="initCurrentData" > <template #content> <div class="cw-typewriter-content"> @@ -67,11 +68,13 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; +import { blockMixin } from './block-mixin.js'; import { VueTyper } from 'vue-typer'; import { mapActions } from 'vuex'; export default { name: 'courseware-typewriter-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, VueTyper, @@ -134,9 +137,6 @@ export default { this.currentText = text; }); }, - closeEdit() { - this.initCurrentData(); - }, storeText() { let attributes = {}; attributes.payload = {}; diff --git a/resources/vue/components/courseware/CoursewareVideoBlock.vue b/resources/vue/components/courseware/CoursewareVideoBlock.vue index e5e31fcab43..40f7e9679dc 100644 --- a/resources/vue/components/courseware/CoursewareVideoBlock.vue +++ b/resources/vue/components/courseware/CoursewareVideoBlock.vue @@ -5,6 +5,7 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" + @showEdit="initCurrentData" @storeEdit="storeBlock" @closeEdit="initCurrentData" > @@ -75,10 +76,12 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFileChooser from './CoursewareFileChooser.vue'; +import { blockMixin } from './block-mixin.js'; import { mapActions, mapGetters } from 'vuex'; export default { name: 'courseware-video-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, CoursewareFileChooser, diff --git a/resources/vue/components/courseware/block-mixin.js b/resources/vue/components/courseware/block-mixin.js index 45eb6316b0a..4e2dc77da59 100644 --- a/resources/vue/components/courseware/block-mixin.js +++ b/resources/vue/components/courseware/block-mixin.js @@ -28,5 +28,8 @@ export const blockMixin = { day: "2-digit", }); }, + setShowEdit(state) { + this.showEdit = state; + }, }, }; diff --git a/resources/vue/store/courseware/courseware.module.js b/resources/vue/store/courseware/courseware.module.js index 0c4811e28c3..93368305352 100644 --- a/resources/vue/store/courseware/courseware.module.js +++ b/resources/vue/store/courseware/courseware.module.js @@ -36,6 +36,7 @@ const getDefaultState = () => { showStructuralElementDeleteDialog: false, showStructuralElementOerDialog: false, showStructuralElementLinkDialog: false, + showStructuralElementRemoveLockDialog: false, showSuggestOerDialog: false, @@ -77,6 +78,22 @@ const getters = { currentElement(state) { return state.currentElement; }, + currentStructuralElement(state, getters, rootState, rootGetters) { + const id = getters.currentElement; + return rootGetters['courseware-structural-elements/byId']({ id }); + }, + currentElementBlocked(state, getters, rootState, rootGetters) { + return getters.currentStructuralElement?.relationships?.['edit-blocker']?.data !== null; + }, + currentElementBlockerId(state, getters) { + return getters.currentElementBlocked ? getters.currentStructuralElement?.relationships?.['edit-blocker']?.data?.id : null; + }, + currentElementBlockedByThisUser(state, getters) { + return getters.currentElementBlocked && getters.userId === getters.currentElementBlockerId; + }, + currentElementBlockedByAnotherUser(state, getters) { + return getters.currentElementBlocked && getters.userId !== getters.currentElementBlockerId; + }, oerEnabled(state) { return state.oerEnabled; }, @@ -173,6 +190,9 @@ const getters = { showStructuralElementLinkDialog(state) { return state.showStructuralElementLinkDialog; }, + showStructuralElementRemoveLockDialog(state) { + return state.showStructuralElementRemoveLockDialog; + }, showOverviewElementAddDialog(state) { return state.showOverviewElementAddDialog; }, @@ -228,7 +248,7 @@ export const state = { ...initialState }; export const actions = { loadContainer({ dispatch }, containerId) { const options = { - include: 'blocks', + include: 'blocks,blocks.edit-blocker','fields[users]': 'formatted-name', }; return dispatch('courseware-containers/loadById', { id: containerId, options }, { root: true }); @@ -237,7 +257,7 @@ export const actions = { loadStructuralElement({ dispatch }, structuralElementId) { const options = { include: - 'containers,containers.blocks,containers.blocks.editor,containers.blocks.owner,containers.blocks.user-data-field,containers.blocks.user-progress,editor,owner', + 'containers,containers.edit-blocker,containers.blocks,containers.blocks.editor,containers.blocks.owner,containers.blocks.user-data-field,containers.blocks.user-progress,containers.blocks.edit-blocker,editor,edit-blocker,owner', 'fields[users]': 'formatted-name', }; @@ -840,6 +860,10 @@ export const actions = { context.commit('setShowStructuralElementLinkDialog', bool); }, + showElementRemoveLockDialog(context, bool) { + context.commit('setShowStructuralElementRemoveLockDialog', bool); + }, + setShowOverviewElementAddDialog(context, bool) { context.commit('setShowOverviewElementAddDialog', bool); }, @@ -1398,6 +1422,10 @@ export const mutations = { state.showStructuralElementLinkDialog = showLink; }, + setShowStructuralElementRemoveLockDialog(state, showRemoveLock) { + state.showStructuralElementRemoveLockDialog = showRemoveLock; + }, + setStructuralElementSortMode(state, mode) { state.structuralElementSortMode = mode; }, -- GitLab