diff --git a/lib/classes/JsonApi/Routes/Courseware/Authority.php b/lib/classes/JsonApi/Routes/Courseware/Authority.php index 04f955e52a34990125c70503ded8565f17ec2981..320648be8a487471bf4a628d41d6ffa877a3d161 100755 --- a/lib/classes/JsonApi/Routes/Courseware/Authority.php +++ b/lib/classes/JsonApi/Routes/Courseware/Authority.php @@ -56,7 +56,13 @@ class Authority public static function canUpdateBlock(User $user, Block $resource) { if ($resource->isBlocked()) { - return $resource->getBlockerUserId() == $user->id; + $perm = $GLOBALS['perm']->have_studip_perm( + $resource->container->structural_element->course->config->COURSEWARE_EDITING_PERMISSION, + $resource->container->structural_element->course->id, + $user->id + ); + + return $resource->getBlockerUserId() == $user->id || $perm; } return self::canUpdateContainer($user, $resource->container); @@ -68,8 +74,29 @@ class Authority } public static function canUpdateEditBlocker(User $user, $resource) - { - return $resource->edit_blocker_id == '' || $resource->edit_blocker_id === $user->id; + { + $structural_element = null; + if ($resource instanceof Block) { + $structural_element = $resource->container->structural_element; + } + if ($resource instanceof Container) { + $structural_element = $resource->structural_element; + } + if ($resource instanceof StructuralElement) { + $structural_element = $resource; + } + + if ($structural_element === null) { + return false; + } + + $perm = $GLOBALS['perm']->have_studip_perm( + $structural_element->course->config->COURSEWARE_EDITING_PERMISSION, + $structural_element->course->id, + $user->id + ); + + return $resource->edit_blocker_id == '' || $resource->edit_blocker_id === $user->id || $perm; } public static function canShowContainer(User $user, Container $resource) diff --git a/resources/assets/stylesheets/scss/courseware.scss b/resources/assets/stylesheets/scss/courseware.scss index eda8513c6042d79aadb6dea7d43e1c8953d4f318..1f04e4c82bccf9c5757ee27352ed500306806eed 100755 --- a/resources/assets/stylesheets/scss/courseware.scss +++ b/resources/assets/stylesheets/scss/courseware.scss @@ -746,6 +746,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 { @@ -842,12 +850,6 @@ form.cw-container-dialog-edit-form { .cw-default-block { display: flex; flex-flow: row; - .cw-default-block-invisible-info { - img { - vertical-align: text-bottom; - } - } - } .cw-content-wrapper { display: flex; @@ -882,10 +884,19 @@ form.cw-container-dialog-edit-form { padding: 4px 10px; span { + font-size: 1.1em; color: $base-color; 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 { @@ -898,6 +909,12 @@ form.cw-container-dialog-edit-form { } } +.cw-block-edit-warning { + font-style: italic; + color: $dark-gray-color; + margin: 1em 0 0.5em 0; +} + .cw-discuss-wrapper, .cw-block-features { @@ -3046,23 +3063,31 @@ a u d i o b l o c k padding-top: 106px; } } - .cw-audio-empty { - @include background-icon(file, info, 96); - border: solid thin $content-color-40; - background-position: center 1em; - background-repeat: no-repeat; - min-height: 140px; - padding: 1em; - p { - text-align: center; - padding-top: 106px; - } - } } /* * * * * * * * * * * * * * a u d i o b l o c k e n d * * * * * * * * * * * * * */ +/* * * * * * * * * * * * * * * * * * * * +f o r m u l t i m e d i a b l o c k s +* * * * * * * * * * * * * * * * * * * */ +.cw-file-empty { + @include background-icon(file, info, 96); + border: solid thin $content-color-40; + background-position: center 1em; + background-repeat: no-repeat; + min-height: 140px; + padding: 1em; + p { + text-align: center; + padding-top: 106px; + } +} + +/* * * * * * * * * * * * * * * * * * * * * * * * +f o r m u l t i m e d i a b l o c k s e n d +* * * * * * * * * * * * * * * * * * * * * * * */ + /* * * * * * * * * * v i d e o b l o c k * * * * * * * * * * */ diff --git a/resources/vue/components/courseware/CoursewareAccordionContainer.vue b/resources/vue/components/courseware/CoursewareAccordionContainer.vue index 5f58dc46ee8d5a83acb33270d9a17e98055863db..924a92b0745ec3666961588139382a6a342fb8a8 100755 --- a/resources/vue/components/courseware/CoursewareAccordionContainer.vue +++ b/resources/vue/components/courseware/CoursewareAccordionContainer.vue @@ -4,9 +4,10 @@ containerClass="cw-container-accordion" :canEdit="canEdit" :isTeacher="isTeacher" - @storeContainer="storeContainer" @closeEdit="initCurrentData" + @showEdit="setShowEdit" @sortBlocks="enableSort" + @storeContainer="storeContainer" > <template v-slot:containerContent> <courseware-collapsible-box @@ -114,6 +115,7 @@ export default { }, data() { return { + showEdit: false, currentContainer: {}, currentSections: [], sortMode: false, @@ -164,6 +166,9 @@ export default { this.currentSections = sections; }, + setShowEdit(state) { + this.showEdit = state; + }, addSection() { this.currentContainer.attributes.payload.sections.push({ name: '', icon: '', blocks: [] }); }, @@ -225,7 +230,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 fcac6864b2433b84177edcaf26f7c000115146b8..d95b32bdcda23a6150f0378aa46837240ed0fac7 100644 --- a/resources/vue/components/courseware/CoursewareActionWidget.vue +++ b/resources/vue/components/courseware/CoursewareActionWidget.vue @@ -78,10 +78,10 @@ export default { return this.structuralElement?.id; }, blocked() { - return this.structuralElement?.relationships['edit-blocker'].data !== null; + return this.structuralElement?.relationships?.['edit-blocker']?.data !== null; }, blockerId() { - return this.blocked ? this.structuralElement?.relationships['edit-blocker'].data?.id : null; + return this.blocked ? this.structuralElement?.relationships?.['edit-blocker']?.data?.id : null; }, blockedByThisUser() { return this.blocked && this.userId === this.blockerId; diff --git a/resources/vue/components/courseware/CoursewareAudioBlock.vue b/resources/vue/components/courseware/CoursewareAudioBlock.vue index fd860cdbaa1a92138cb88d721a2464a94e8f5ad8..5adb790e87b63d5762d10e466641d419e7c567ff 100755 --- a/resources/vue/components/courseware/CoursewareAudioBlock.vue +++ b/resources/vue/components/courseware/CoursewareAudioBlock.vue @@ -5,8 +5,9 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" - @storeEdit="storeBlock" @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> <div v-if="currentTitle !== ''" class="cw-block-title">{{ currentTitle }}</div> @@ -56,7 +57,7 @@ {{ file.name }} </li> </ul> - <div v-if="emptyAudio" class="cw-audio-empty"> + <div v-if="emptyAudio" class="cw-file-empty"> <p><translate>Es ist keine Audio-Datei verfügbar</translate></p> </div> <div v-if="showRecorder && canGetMediaDevices" class="cw-audio-playlist-recorder"> @@ -175,6 +176,7 @@ export default { }, data() { return { + showEdit: false, currentTitle: '', currentSource: '', currentFileId: '', @@ -341,6 +343,9 @@ export default { this.currentFolderId = this.folderId; this.currentRecorderEnabled = this.recorderEnabled; }, + setShowEdit(state) { + this.showEdit = state; + }, updateCurrentFile(file) { this.currentFile = file; this.currentFileId = file.id; @@ -593,6 +598,39 @@ export default { currentFolderId() { this.getFolderFiles(); }, + title() { + if (!this.showEdit) { + this.currentTitle = this.title; + } + }, + source() { + if (!this.showEdit) { + this.currentSource = this.source; + } + }, + fileId() { + if (!this.showEdit) { + this.currentFileId = this.fileId; + if (this.currentFileId !== '') { + this.loadFile(); + } + } + }, + webUrl() { + if (!this.showEdit) { + this.currentWebUrl = this.webUrl; + } + }, + folderId() { + if (!this.showEdit) { + this.currentFolderId = this.folderId; + } + }, + recorderEnabled() { + if (!this.showEdit) { + this.currentRecorderEnabled = this.recorderEnabled; + } + }, }, }; </script> diff --git a/resources/vue/components/courseware/CoursewareBeforeAfterBlock.vue b/resources/vue/components/courseware/CoursewareBeforeAfterBlock.vue index 2c68473b9cd45792d24318a78ead9d7415d50df5..95f4fb4c462b82c3f7a284812c5b592c6c0aada6 100755 --- a/resources/vue/components/courseware/CoursewareBeforeAfterBlock.vue +++ b/resources/vue/components/courseware/CoursewareBeforeAfterBlock.vue @@ -5,11 +5,12 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" - @storeEdit="storeBlock" @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <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=""> @@ -79,6 +80,7 @@ export default { }, data() { return { + showEdit: false, currentBeforeSource: '', currentBeforeFileId: '', currentBeforeFile: {}, @@ -111,40 +113,42 @@ export default { return this.block?.attributes?.payload?.after_web_url; }, currentBeforeUrl() { - if (this.currentBeforeSource === 'studip'&& this.currentBeforeFile?.meta) { - return this.currentBeforeFile.meta['download-url']; - } else if (this.currentBeforeSource === 'web') { + if (this.currentBeforeSource === 'studip' ) { + if (this.currentBeforeFile?.meta) { + return this.currentBeforeFile.meta['download-url']; + } + if (this.currentBeforeFile?.['download_url']) { + return this.currentBeforeFile['download_url'] + } + } + + if (this.currentBeforeSource === 'web') { return this.currentBeforeWebUrl; - } else { - return ''; } + + return ''; }, currentAfterUrl() { - if (this.currentAfterSource === 'studip'&& this.currentAfterFile?.meta) { - return this.currentAfterFile.meta['download-url']; - } else if (this.currentAfterSource === 'web') { + if (this.currentAfterSource === 'studip') { + if (this.currentAfterFile?.meta) { + return this.currentAfterFile.meta['download-url']; + } + if (this.currentAfterFile?.['download_url']) { + return this.currentAfterFile['download_url'] + } + } + if (this.currentAfterSource === 'web') { return this.currentAfterWebUrl; - } else { - return ''; } + + return ''; }, + isEmpty() { + return !this.currentAfterUrl || !this.currentBeforeUrl; + } }, mounted() { - 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; - }); - + this.loadImages(); this.initCurrentData(); }, methods: { @@ -153,6 +157,22 @@ 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; @@ -161,6 +181,9 @@ export default { this.currentAfterFileId = this.afterFileId; this.currentAfterWebUrl = this.afterWebUrl; }, + setShowEdit(state) { + this.showEdit = state; + }, updateCurrentBeforeFile(file) { this.currentBeforeFile = file; this.currentBeforeFileId = file.id; @@ -227,5 +250,39 @@ export default { } }, }, + watch: { + beforeSource() { + if (!this.showEdit) { + this.currentBeforeSource = this.beforeSource; + } + }, + beforeFileId() { + if (!this.showEdit) { + this.currentBeforeFileId = this.beforeFileId; + this.loadImages(); + } + }, + beforeWebUrl() { + if (!this.showEdit) { + this.currentBeforeWebUrl = this.beforeWebUrl; + } + }, + afterSource() { + if (!this.showEdit) { + this.currentAfterSource = this.afterSource; + } + }, + afterFileId() { + if (!this.showEdit) { + this.currentAfterFileId = this.afterFileId; + this.loadImages(); + } + }, + afterWebUrl() { + if (!this.showEdit) { + this.currentAfterWebUrl = this.afterWebUrl; + } + }, + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareBlockActions.vue b/resources/vue/components/courseware/CoursewareBlockActions.vue index bb2177edc0f8fb63a2f89a88c0707bb14ddcad3f..e5af4249507a5295e70bc45468483ef41e9f0092 100755 --- a/resources/vue/components/courseware/CoursewareBlockActions.vue +++ b/resources/vue/components/courseware/CoursewareBlockActions.vue @@ -2,10 +2,12 @@ <div class="cw-block-actions"> <studip-action-menu :items="menuItems" + collapseAt="2" @editBlock="editBlock" @setVisibility="setVisibility" @showInfo="showInfo" @deleteBlock="deleteBlock" + @removeLock="removeLock" /> </div> </template> @@ -27,57 +29,68 @@ 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({ @@ -116,12 +129,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 42e252de8b80eb2be4c62f49b0a797b2562ba141..6aed6daaad0a546f22a8299ab6aabb0fe5bd0dbd 100755 --- a/resources/vue/components/courseware/CoursewareCanvasBlock.vue +++ b/resources/vue/components/courseware/CoursewareCanvasBlock.vue @@ -5,8 +5,9 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" - @storeEdit="storeBlock" @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> <div v-if="currentTitle" class="cw-block-title"> @@ -163,6 +164,7 @@ export default { }, data() { return { + showEdit: false, currentTitle: '', currentImage: '', currentFileId: '', @@ -254,12 +256,7 @@ export default { }, }, mounted() { - this.loadFileRefs(this.block.id).then((response) => { - this.file = response[0]; - this.currentFile = this.file; - this.initCurrentData(); - this.buildCanvas(); - }); + this.loadImageFile(); }, methods: { ...mapActions({ @@ -285,6 +282,17 @@ export default { this.Text = JSON.parse(this.canvasDraw.Text); } }, + setShowEdit(state) { + this.showEdit = state; + }, + 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; @@ -595,5 +603,34 @@ export default { } }, }, + watch: { + title() { + if (!this.showEdit) { + this.currentTitle = this.title; + } + }, + image() { + if (!this.showEdit) { + this.currentImage = this.image; + this.buildCanvas(); + } + }, + fileId() { + if (!this.showEdit) { + this.currentFileId = this.fileId; + this.loadImageFile(); + } + }, + uploadFolderId() { + if (!this.showEdit) { + this.currentUploadFolderId = this.uploadFolderId; + } + }, + showUsersData() { + if (!this.showEdit) { + this.currentShowUserData = this.showUsersData; + } + }, + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareChartBlock.vue b/resources/vue/components/courseware/CoursewareChartBlock.vue index 8dd58ec2433dd07e3eb00ce62c09d4b69a076bb0..0c5de9719be2580ee7cc1b705f51f75ecb9ab0b3 100755 --- a/resources/vue/components/courseware/CoursewareChartBlock.vue +++ b/resources/vue/components/courseware/CoursewareChartBlock.vue @@ -5,8 +5,9 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" - @storeEdit="storeBlock" @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> <canvas class="cw-chart-block-canvas" ref="chartCanvas" /> @@ -87,9 +88,11 @@ import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import Chart from 'chart.js'; import { mapActions } from 'vuex'; import StudipIcon from '../StudipIcon.vue'; +import { blockMixin } from './block-mixin.js'; export default { name: 'courseware-chart-block', + mixins: [blockMixin], components: { CoursewareDefaultBlock, StudipIcon, @@ -101,6 +104,7 @@ export default { }, data() { return { + showEdit: false, chart: null, currentContent: [], currentLabel: '', @@ -146,6 +150,9 @@ export default { this.currentLabel = this.label; this.currentType = this.type; }, + setShowEdit(state) { + this.showEdit = state; + }, storeBlock() { let attributes = {}; attributes.payload = {}; @@ -281,12 +288,29 @@ export default { }); break; } - }, + } }, watch: { currentType() { this.buildChart(); }, + content() { + if (!this.showEdit && !this.contentsEqual(this.currentContent,this.content)) { + this.currentContent = this.content; + this.buildChart(); + } + }, + label() { + if (!this.showEdit) { + this.currentLabel = this.label; + this.buildChart(); + } + }, + type() { + if (!this.showEdit) { + this.currentType = this.type; + } + }, }, }; </script> diff --git a/resources/vue/components/courseware/CoursewareCodeBlock.vue b/resources/vue/components/courseware/CoursewareCodeBlock.vue index 275a77f5e876a04d6eae33520d1024f9f51dafa6..c5492452a63e34355ab9f1f999285e7d5b15f138 100755 --- a/resources/vue/components/courseware/CoursewareCodeBlock.vue +++ b/resources/vue/components/courseware/CoursewareCodeBlock.vue @@ -5,8 +5,9 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" - @storeEdit="storeBlock" @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> <pre v-show="currentContent !== ''" v-highlightjs="currentContent"><code ref="code" :class="[currentLang]"></code></pre> @@ -51,6 +52,7 @@ export default { }, data() { return { + showEdit: false, currentLang: '', currentContent: '', }; @@ -97,6 +99,9 @@ export default { this.currentLang = this.lang; this.currentContent = this.content; }, + setShowEdit(state) { + this.showEdit = state; + }, storeBlock() { let attributes = {}; attributes.payload = {}; @@ -110,5 +115,17 @@ export default { }); }, }, + watch: { + lang() { + if (!this.showEdit) { + this.currentLang = this.lang; + } + }, + content() { + if (!this.showEdit) { + this.currentContent = this.content; + } + } + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareConfirmBlock.vue b/resources/vue/components/courseware/CoursewareConfirmBlock.vue index 4e602f82e1f94eb854ac174a31fea3e67a72df0e..e420bda4820178a7cf0c12323c60a1bf7d937e48 100755 --- a/resources/vue/components/courseware/CoursewareConfirmBlock.vue +++ b/resources/vue/components/courseware/CoursewareConfirmBlock.vue @@ -6,8 +6,9 @@ :isTeacher="isTeacher" :preview="true" :defaultGrade="false" - @storeEdit="storeBlock" @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> <div class="cw-block-title"> @@ -56,6 +57,7 @@ export default { }, data() { return { + showEdit: false, currentText: '', confirm: false, }; @@ -85,6 +87,9 @@ export default { this.confirm = this.userData.attributes.payload.confirm; } }, + setShowEdit(state) { + this.showEdit = state; + }, async setConfirm() { let data = {}; data.type = 'courseware-user-data-fields'; @@ -114,5 +119,12 @@ export default { }); }, }, + watch: { + text() { + if (!this.showEdit) { + this.currentText = this.text; + } + } + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareContainerActions.vue b/resources/vue/components/courseware/CoursewareContainerActions.vue index e1da988467652f68abf2eaf458cc8df9dc2708e5..1c9cbf8ce96d29e5570212c023e11878f2a9772e 100755 --- a/resources/vue/components/courseware/CoursewareContainerActions.vue +++ b/resources/vue/components/courseware/CoursewareContainerActions.vue @@ -1,15 +1,18 @@ <template> <div v-if="canEdit" class="cw-container-actions"> <studip-action-menu - :items="menuItems" + :items="menuItems" @editContainer="editContainer" @deleteContainer="deleteContainer" @sortBlocks="sortBlocks" + @removeLock="removeLock" /> </div> </template> <script> +import { mapGetters } from 'vuex'; + export default { name: 'courseware-container-actions', props: { @@ -17,19 +20,45 @@ 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: { @@ -44,6 +73,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 da119891f81464a56f8cd175655e94679acd8765..49a1aea523c29a94b77d40db7b3b5b133a85b13d 100755 --- a/resources/vue/components/courseware/CoursewareDateBlock.vue +++ b/resources/vue/components/courseware/CoursewareDateBlock.vue @@ -5,8 +5,9 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" - @storeEdit="storeBlock" @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> <div v-if="currentStyle === 'countdown'" class="cw-date-countdown"> @@ -69,7 +70,7 @@ </label> <label> <translate>Layout</translate> - <select v-model="currentStyle"> + <select v-model="currentStyle" @change="computeTimestamp"> <option value="countdown"><translate>Countdown</translate></option> <option value="date"><translate>Datum</translate></option> </select> @@ -97,6 +98,7 @@ export default { }, data() { return { + showEdit: false, currentTitle: '', currentTimestamp: 0, currentStyle: '', @@ -138,20 +140,29 @@ export default { initCurrentData() { this.currentTitle = this.title; this.currentTimestamp = this.timestamp; + this.setCurrentDate(); + this.setCurrentDeDate(); + this.currentTime = ('0' + this.date.getHours()).slice(-2) + ':' + ('0' + this.date.getMinutes()).slice(-2); + this.currentStyle = this.style; + }, + setCurrentDate() { this.currentDate = this.date.getFullYear() + '-' + ('0' + (this.date.getMonth() + 1)).slice(-2) + '-' + ('0' + this.date.getDate()).slice(-2); + }, + setCurrentDeDate() { this.currentDeDate = ('0' + this.date.getDate()).slice(-2) + '.' + ('0' + (this.date.getMonth() + 1)).slice(-2) + '.' + this.date.getFullYear(); - this.currentTime = ('0' + this.date.getHours()).slice(-2) + ':' + ('0' + this.date.getMinutes()).slice(-2); - this.currentStyle = this.style; + }, + setShowEdit(state) { + this.showEdit = state; }, countdown() { let view = this; @@ -171,6 +182,11 @@ export default { }, computeTimestamp() { this.currentTimestamp = new Date(this.currentDate + ' ' + this.currentTime).getTime(); + this.setCurrentDate(); + this.setCurrentDeDate(); + if (this.currentStyle === 'countdown') { + this.countdown(); + } }, storeBlock() { let cmpInfo = false; @@ -196,9 +212,24 @@ export default { containerId: this.block.relationships.container.data.id, }); } - - }, }, + watch: { + title() { + if (!this.showEdit) { + this.currentTitle = this.title; + } + }, + timestamp() { + if (!this.showEdit) { + this.initCurrentData(); + } + }, + style() { + if (!this.showEdit) { + this.currentStyle = this.style; + } + }, + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareDefaultBlock.vue b/resources/vue/components/courseware/CoursewareDefaultBlock.vue index 458739901c61ef5c76654c3495f21bc086ba8710..123643aea14bd68faa9210ee9138fc487cf7f7c6 100755 --- a/resources/vue/components/courseware/CoursewareDefaultBlock.vue +++ b/resources/vue/components/courseware/CoursewareDefaultBlock.vue @@ -3,12 +3,14 @@ <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 +20,7 @@ @showInfo="displayFeature('Info')" @showExportOptions="displayFeature('ExportOptions')" @deleteBlock="displayDeleteDialog()" + @removeLock="displayRemoveLockDialog()" /> </header> <div v-if="showContent" class="cw-block-content"> @@ -61,6 +64,15 @@ @confirm="executeDelete" @close="showDeleteDialog = false" ></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,15 +130,19 @@ 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 Block wirklich aufheben? Der Bearbeitungsstand geht dabei unwiderruflich verloren.'), }; }, computed: { ...mapGetters({ blockTypes: 'blockTypes', userId: 'userId', + userById: 'users/byId', viewMode: 'viewMode', containerById: 'courseware-containers/byId', }), @@ -141,10 +157,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; @@ -152,6 +168,20 @@ export default { blockedByAnotherUser() { return this.blocked && this.userId !== this.blockerId; }, + blockingUser() { + if (this.blockedByAnotherUser) { + const user = this.$store.getters["users/related"]({ + parent: { type: this.block.type, id: this.block.id }, + relationship: "edit-blocker" + }); + return user ? user : null; + } + + return null; + }, + blockingUserName() { + return this.blockingUser ? this.blockingUser.attributes['formatted-name'] : ''; + }, blockTitle() { const type = this.block.attributes['block-type']; @@ -163,6 +193,9 @@ export default { if (this.blockedByThisUser) { this.displayFeature('Edit'); } + if (this.blockedByAnotherUser) { + this.loadUserById({ id: this.blockerId }); + } } if (this.userProgress && this.userProgress.attributes.grade === 0 && this.defaultGrade) { this.userProgress = 1; @@ -176,6 +209,7 @@ export default { unlockObject: 'unlockObject', loadContainer: 'loadContainer', updateContainer: 'updateContainer', + loadUserById: 'users/loadById' }), async displayFeature(element) { if (this.showEdit && element === 'Edit') { @@ -273,6 +307,18 @@ export default { containerId: containerId, }); }, + displayRemoveLockDialog() { + this.showRemoveLockDialog = true; + }, + executeRemoveLock() { + this.unlockObject({ id: this.block.id , type: 'courseware-blocks' }); + 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 a24e9a8bd510d76e96111b4765450eec0046eca8..774cc7f46a07f6177724f270c7e2b62a56c85f4b 100755 --- 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,16 @@ @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,11 +84,14 @@ 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 Block wirklich aufheben? Der Bearbeitungsstand geht dabei unwiderruflich verloren.'), }; }, computed: { @@ -87,10 +105,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,6 +116,20 @@ export default { blockedByAnotherUser() { return this.blocked && this.userId !== this.blockerId; }, + blockingUser() { + if (this.blockedByAnotherUser) { + const user = this.$store.getters["users/related"]({ + parent: { type: this.container.type, id: this.container.id }, + relationship: "edit-blocker" + }); + return user ? user : null; + } + + return null; + }, + blockingUserName() { + return this.blockingUser ? this.blockingUser.attributes['formatted-name'] : ''; + }, }, methods: { ...mapActions({ @@ -172,7 +204,25 @@ export default { return false; } this.$emit('sortBlocks'); - } + }, + displayRemoveLockDialog() { + this.showRemoveLockDialog = true; + }, + executeRemoveLock() { + this.unlockObject({ id: this.container.id , type: 'courseware-containers' }); + this.showRemoveLockDialog = false; + }, }, + watch: { + showEditDialog(state) { + this.$emit('showEdit', state); + }, + blockedByThisUser(newState, oldState) { + if (oldState && !newState) { + this.showDeleteDialog = false; + this.showEditDialog = false; + } + } + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue b/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue index 2256795d4ca461bb89bdeb1e0602f4f99d32816e..5c013d93d0d94ab8f44350cc3c0d8a0bea368eb3 100755 --- a/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue +++ b/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue @@ -5,8 +5,9 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" - @storeEdit="storeBlock" @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> <div class="cw-block-dialog-cards-content"> @@ -134,6 +135,7 @@ export default { }, data() { return { + showEdit: false, currentCards: [], }; }, @@ -168,10 +170,13 @@ export default { }), initCurrentData() { if (this.cards !== '') { - this.currentCards = JSON.parse(JSON.stringify(this.cards)); + this.currentCards = this.cards; } this.activateCard(0); }, + setShowEdit(state) { + this.showEdit = state; + }, storeBlock() { let cards = JSON.parse(JSON.stringify(this.currentCards)); // don't store the file object @@ -261,6 +266,21 @@ export default { } }); }, + cardsEqual(a1, a2) { + return a1.length === a2.length && a1.every((o, idx) => { + return o.back_file_id === a2[idx].back_file_id && + o.back_text === a2[idx].back_text && + o.front_file_id === a2[idx].front_file_id && + o.front_text === a2[idx].front_text; + }); + } + }, + watch: { + cards() { + if (!this.showEdit && !this.cardsEqual(this.currentCards, this.cards)) { + this.initCurrentData(); + } + }, }, }; </script> diff --git a/resources/vue/components/courseware/CoursewareDocumentBlock.vue b/resources/vue/components/courseware/CoursewareDocumentBlock.vue index 134e167ca45527d746aa626d46807f727103d2ec..f2d243dd3a4510bb0a1077bf67b32a1aba9e2745 100755 --- a/resources/vue/components/courseware/CoursewareDocumentBlock.vue +++ b/resources/vue/components/courseware/CoursewareDocumentBlock.vue @@ -5,8 +5,9 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="false" - @storeEdit="storeBlock" @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> <div v-if="hasFile" class="cw-pdf-header cw-block-title"> @@ -87,6 +88,7 @@ export default { }, data() { return { + showEdit: false, currentTitle: '', currentFileId: '', currentFile: {}, @@ -123,29 +125,16 @@ export default { currentUrl() { if (this.currentFile?.meta) { return this.currentFile.meta['download-url']; - } else { - return ''; } + if (this.currentFile?.['download_url']) { + return this.currentFile['download_url']; + } + return ''; }, hasFile() { return this.currentFileId !== ''; } }, - watch: { - browseDirection: function (val) { - if (val.length > 6) { - this.evaluateBrowseAction(); - } - }, - }, - mounted() { - this.loadFileRefs(this.block.id).then((response) => { - this.file = response[0]; - this.currentFile = this.file; - this.loadPdfViewer(); - }); - this.initCurrentData(); - }, methods: { ...mapActions({ updateBlock: 'updateBlockInContainer', @@ -158,6 +147,9 @@ export default { this.currentFileId = this.fileId; this.currentDocType = this.docType; }, + setShowEdit(state) { + this.showEdit = state; + }, updateCurrentFile(file) { this.currentFile = file; this.currentFileId = file.id; @@ -256,7 +248,44 @@ export default { containerId: this.block.relationships.container.data.id, }); } - + }, + loadDocument() { + this.loadFileRefs(this.block.id).then((response) => { + this.file = response[0]; + this.currentFile = this.file; + this.loadPdfViewer(); + }); + } + }, + mounted() { + this.loadDocument(); + this.initCurrentData(); + }, + watch: { + browseDirection: function (val) { + if (val.length > 6) { + this.evaluateBrowseAction(); + } + }, + title() { + if (!this.showEdit) { + this.currentTitle = this.title; + } + }, + downloadable() { + if (!this.showEdit) { + this.currentDownloadable = this.downloadable; + } + }, + fileId() { + if (!this.showEdit) { + this.currentFileId = this.fileId; + } + }, + title() { + if (!this.showEdit) { + this.currentDocType = this.docType; + } }, }, }; diff --git a/resources/vue/components/courseware/CoursewareDownloadBlock.vue b/resources/vue/components/courseware/CoursewareDownloadBlock.vue index 2094cfa7133cb9b63ae69be7cf98da1dfe32c9d5..c9ad453e23545bbe7c406407490375083a1d57c7 100755 --- a/resources/vue/components/courseware/CoursewareDownloadBlock.vue +++ b/resources/vue/components/courseware/CoursewareDownloadBlock.vue @@ -6,8 +6,9 @@ :isTeacher="isTeacher" :preview="true" :defaultGrade="false" - @storeEdit="storeBlock" @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> <div v-if="currentTitle !== ''" class="cw-block-title">{{ currentTitle }}</div> @@ -88,6 +89,7 @@ export default { }, data() { return { + showEdit: false, currentTitle: '', currentInfo: '', currentSuccess: '', @@ -152,6 +154,9 @@ export default { this.loadFile(); } }, + setShowEdit(state) { + this.showEdit = state; + }, async loadFile() { const id = `${this.currentFileId}`; const options = { include: 'terms-of-use' }; @@ -258,5 +263,35 @@ export default { this.userProgress = 1; }, }, + watch: { + title() { + if (!this.showEdit) { + this.currentTitle = this.title; + } + }, + info() { + if (!this.showEdit) { + this.currentInfo = this.info; + } + }, + fileId() { + if (!this.showEdit) { + this.currentFileId = this.fileId; + if (this.currentFileId !== '') { + this.loadFile(); + } + } + }, + success() { + if (!this.showEdit) { + this.currentSuccess = this.success; + } + }, + grade() { + if (!this.showEdit) { + this.currentGrade = this.grade; + } + }, + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareEmbedBlock.vue b/resources/vue/components/courseware/CoursewareEmbedBlock.vue index 2808ffa2478ecce82eb5b4b51671b6febcad8cd3..80795bc72effcd266cd92f291749d230d61426aa 100755 --- a/resources/vue/components/courseware/CoursewareEmbedBlock.vue +++ b/resources/vue/components/courseware/CoursewareEmbedBlock.vue @@ -5,8 +5,9 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="false" - @storeEdit="storeBlock" @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> <div v-if="currentTitle !== ''" class="cw-block-title">{{ currentTitle }}</div> @@ -26,7 +27,7 @@ <img :src="oembedData.fullsize_url" /> </div> </div> - <div class="cw-block-embed-info" v-if="oembedData !== null"> + <div class="cw-block-embed-info" v-if="oembedData"> <span class="cw-block-embed-title">{{ oembedData.title }}</span> <span class="cw-block-embed-author-name"> <translate>erstellt von</translate> @@ -102,6 +103,7 @@ export default { }, data() { return { + showEdit: false, currentTitle: '', currentSource: '', currentUrl: '', @@ -176,6 +178,9 @@ export default { this.updateTime(); } }, + setShowEdit(state) { + this.showEdit = state; + }, addTimeData(data) { if (this.currentSource === 'youtube') { if (this.currentStartTime !== '') { @@ -230,5 +235,41 @@ export default { }); }, }, + watch: { + title() { + if (!this.showEdit) { + this.currentTitle = this.title; + } + }, + source() { + if (!this.showEdit) { + this.currentSource = this.source; + } + }, + url() { + if (!this.showEdit) { + this.currentUrl = this.url; + } + }, + startTime() { + if (!this.showEdit) { + this.currentStartTime = this.startTime; + } + }, + endTime() { + if (!this.showEdit) { + this.currentEndTime = this.endTime; + } + }, + oembed() { + if (!this.showEdit) { + this.oembedData = this.oembed; + if (this.oembedData !== null) { + this.calcContentHeight(); + this.updateTime(); + } + } + }, + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareEmptyElementBox.vue b/resources/vue/components/courseware/CoursewareEmptyElementBox.vue index b64413d26c8b7f5db556ab0874622021bd28dd38..8e1ab2fc6d3b60376dfe6efe95d0760a7fcffd8c 100755 --- a/resources/vue/components/courseware/CoursewareEmptyElementBox.vue +++ b/resources/vue/components/courseware/CoursewareEmptyElementBox.vue @@ -2,8 +2,8 @@ <div class="cw-wellcome-screen"> <courseware-companion-box :msgCompanion="this.$gettext('Es wurden bisher noch keine Inhalte eingepflegt.')"> <template v-slot:companionActions> - <button v-if="canEdit && noContainers" class="button" @click="addContainer"><translate>Einen Abschnitt hinzufügen</translate></button> - <button v-if="canEdit && !noContainers && !editMode" class="button" @click="switchToEditView"><translate>Seite bearbeiten</translate></button> + <button v-show="canEdit && noContainers" class="button" @click="addContainer"><translate>Einen Abschnitt hinzufügen</translate></button> + <button v-show="canEdit && !noContainers && !editMode" class="button" @click="switchToEditView"><translate>Seite bearbeiten</translate></button> </template> </courseware-companion-box> </div> @@ -48,4 +48,4 @@ export default { } } -</script> \ No newline at end of file +</script> diff --git a/resources/vue/components/courseware/CoursewareFolderBlock.vue b/resources/vue/components/courseware/CoursewareFolderBlock.vue index a1b97490d09da6b5c9e9cbed9df1e5e03fac1c21..7f7276d4425b83c00c68081f3dae9b14beb76b8c 100755 --- a/resources/vue/components/courseware/CoursewareFolderBlock.vue +++ b/resources/vue/components/courseware/CoursewareFolderBlock.vue @@ -5,8 +5,9 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" - @storeEdit="storeBlock" @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> <div v-if="currentTitle !== ''" class="cw-block-title">{{ currentTitle }}</div> @@ -64,6 +65,7 @@ export default { }, data() { return { + showEdit: false, currentTitle: '', currentFolderId: '', currentFileType: '', @@ -99,13 +101,21 @@ export default { this.currentFolderId = this.folderId; this.currentFolderType = this.folderType; }, + setShowEdit(state) { + this.showEdit = state; + }, async getFolderFiles() { - const parent = { type: 'folders', id: `${this.currentFolderId}` }; - const relationship = 'file-refs'; - const options = { include: 'terms-of-use' }; - await this.loadRelatedFileRefs({ parent, relationship, options }); - const fileRefs = this.relatedFileRefs({ parent, relationship }) ?? []; - this.processFiles(fileRefs); + if (this.currentFolderId) { + const parent = { type: 'folders', id: `${this.currentFolderId}` }; + const relationship = 'file-refs'; + const options = { include: 'terms-of-use' }; + await this.loadRelatedFileRefs({ parent, relationship, options }); + const fileRefs = this.relatedFileRefs({ parent, relationship }) ?? []; + this.processFiles(fileRefs); + } else { + this.files = []; + } + }, processFiles(files) { this.files = files @@ -189,6 +199,21 @@ export default { currentFolderId() { this.getFolderFiles(); }, + title() { + if (!this.showEdit) { + this.currentTitle = this.title; + } + }, + folderId() { + if (!this.showEdit) { + this.currentFolderId = this.folderId; + } + }, + folderType() { + if (!this.showEdit) { + this.currentFolderType = this.folderType; + } + }, }, }; </script> diff --git a/resources/vue/components/courseware/CoursewareGalleryBlock.vue b/resources/vue/components/courseware/CoursewareGalleryBlock.vue index 9d0549b25c9f887f3c44de70246d0491b333e82c..1066e53b32d4d8fc396cccb7874a29032fc2db83 100755 --- a/resources/vue/components/courseware/CoursewareGalleryBlock.vue +++ b/resources/vue/components/courseware/CoursewareGalleryBlock.vue @@ -5,8 +5,9 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" - @storeEdit="storeBlock" @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> <div v-if="files.length !== 0" class="cw-block-gallery-content" :style="{ 'max-height': currentHeight + 'px' }"> @@ -99,6 +100,7 @@ export default { }, data() { return { + showEdit: false, currentFolderId: '', currentAutoplay: '', currentNav: '', @@ -150,6 +152,9 @@ export default { this.currentHeight = this.height; this.currentShowFileNames = this.showFileNames; }, + setShowEdit(state) { + this.showEdit = state; + }, startGallery() { this.slideIndex = 0; this.showSlides(0); @@ -254,6 +259,38 @@ export default { this.currentAutoplayTimer = '2'; } }, + folderId() { + if (!this.showEdit) { + this.currentFolderId = this.folderId; + } + }, + autoplay() { + if (!this.showEdit) { + this.currentAutoplay = this.autoplay; + this.startGallery(); + } + }, + autoplayTimer() { + if (!this.showEdit) { + this.currentAutoplayTimer = this.autoplayTimer; + this.startGallery(); + } + }, + nav() { + if (!this.showEdit) { + this.currentNav = this.nav; + } + }, + height() { + if (!this.showEdit) { + this.currentHeight = this.height; + } + }, + showFileNames() { + if (!this.showEdit) { + this.currentShowFileNames = this.showFileNames; + } + }, }, }; </script> diff --git a/resources/vue/components/courseware/CoursewareHeadlineBlock.vue b/resources/vue/components/courseware/CoursewareHeadlineBlock.vue index 9cc3e5557fc46578224a0caab59e3f0cb62a3fb9..ae95b4061da0d9cb5ab32cbbdde356ba14ec3095 100755 --- a/resources/vue/components/courseware/CoursewareHeadlineBlock.vue +++ b/resources/vue/components/courseware/CoursewareHeadlineBlock.vue @@ -5,8 +5,9 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" - @storeEdit="storeText" - @closeEdit="closeEdit" + @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> <div @@ -182,6 +183,7 @@ export default { }, data() { return { + showEdit: false, currentTitle: '', currentSubtitle: '', currentStyle: '', @@ -320,6 +322,9 @@ export default { this.loadFile(); } }, + setShowEdit(state) { + this.showEdit = state; + }, async loadFile() { const id = this.currentBackgroundImageId; const options = { include: 'terms-of-use' }; @@ -342,10 +347,7 @@ export default { this.currentBackgroundImageId = file.id; this.currentBackgroundURL = file.download_url; }, - closeEdit() { - this.initCurrentData(); - }, - storeText() { + storeBlock() { let attributes = {}; attributes.payload = {}; attributes.payload.title = this.currentTitle; @@ -407,5 +409,60 @@ export default { return hex.length === 1 ? '0' + hex : hex; }, }, + watch: { + title() { + if (!this.showEdit) { + this.currentTitle = this.title; + } + }, + subtitle() { + if (!this.showEdit) { + this.currentSubtitle = this.subtitle; + } + }, + style() { + if (!this.showEdit) { + this.currentStyle = this.style; + } + }, + height() { + if (!this.showEdit) { + this.currentHeight = this.height; + } + }, + backgroundColor() { + if (!this.showEdit) { + this.currentBackgroundColor = this.backgroundColor; + } + }, + textColor() { + if (!this.showEdit) { + this.currentTextColor = this.textColor; + } + }, + icon() { + if (!this.showEdit) { + this.currentIcon = this.icon; + } + }, + iconColor() { + if (!this.showEdit) { + this.currentIconColor = this.iconColor; + } + }, + backgroundType() { + if (!this.showEdit) { + this.currentBackgroundType = this.backgroundType; + } + }, + backgroundImageId() { + if (!this.showEdit) { + this.currentBackgroundImageId = this.backgroundImageId; + if (this.currentBackgroundImageId !== '') { + this.loadFile(); + } + } + }, + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareIframeBlock.vue b/resources/vue/components/courseware/CoursewareIframeBlock.vue index 1f0f5bf9b3a5e4bca8240bb352860c117c24ee1c..ff9c6475626184809b45205752e24de7ba28d4b7 100755 --- a/resources/vue/components/courseware/CoursewareIframeBlock.vue +++ b/resources/vue/components/courseware/CoursewareIframeBlock.vue @@ -5,8 +5,9 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" - @storeEdit="storeBlock" @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> <div v-if="currentTitle !== ''" class="cw-block-title">{{ currentTitle }}</div> @@ -18,7 +19,7 @@ allowfullscreen sandbox="allow-forms allow-popups allow-pointer-lock allow-same-origin allow-scripts" /> - <div v-if="currentCcInfo" class="cw-block-iframe-cc-data"> + <div v-if="currentCcInfo !== 'false'" class="cw-block-iframe-cc-data"> <span class="cw-block-iframe-cc" :class="['cw-block-iframe-cc-' + currentCcInfo]"></span> <div class="cw-block-iframe-cc-infos"> <p v-if="currentCcWork !== ''"><translate>Werk</translate> {{ currentCcWork }}</p> @@ -120,6 +121,7 @@ export default { }, data() { return { + showEdit: false, currentTitle: '', currentUrl: '', currentHeight: '', @@ -197,6 +199,9 @@ export default { this.currentCcBase = this.ccBase; this.setProtocol(); }, + setShowEdit(state) { + this.showEdit = state; + }, setProtocol() { if (location.protocol === 'https:') { if (!this.currentUrl.includes('https:')) { @@ -234,5 +239,58 @@ export default { }); }, }, + watch: { + title() { + if (!this.showEdit) { + this.currentTitle = this.title; + } + }, + url() { + if (!this.showEdit) { + this.currentUrl = this.url; + this.setProtocol(); + } + }, + height() { + if (!this.showEdit) { + this.currentHeight = this.height; + } + }, + submitUserId() { + if (!this.showEdit) { + this.currentSubmitUserId = this.submitUserId; + } + }, + submitParam() { + if (!this.showEdit) { + this.currentSubmitParam = this.submitParam; + } + }, + salt() { + if (!this.showEdit) { + this.currentSalt = this.salt; + } + }, + ccInfo() { + if (!this.showEdit) { + this.currentCcInfo = this.ccInfo; + } + }, + ccWork() { + if (!this.showEdit) { + this.currentCcWork = this.ccWork; + } + }, + ccAuthor() { + if (!this.showEdit) { + this.currentCcAuthor = this.ccAuthor; + } + }, + ccBase() { + if (!this.showEdit) { + this.currentCcBase = this.ccBase; + } + }, + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareImageMapBlock.vue b/resources/vue/components/courseware/CoursewareImageMapBlock.vue index 9ba60d662c1137e45b00033f15c9ebba8590b5f6..df16aceba3037b0d7d09bd86e0ec00fadc5717f4 100755 --- a/resources/vue/components/courseware/CoursewareImageMapBlock.vue +++ b/resources/vue/components/courseware/CoursewareImageMapBlock.vue @@ -5,11 +5,12 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" - @storeEdit="storeBlock" @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> - <img :src="currentUrl" class="cw-image-map-original-img" ref="original_img" @load="buildCanvas" /> + <img v-if="currentUrl" :src="currentUrl" class="cw-image-map-original-img" ref="original_img" @load="buildCanvas" /> <canvas class="cw-image-map-canvas" ref="canvas"></canvas> <img class="cw-image-from-canvas" @@ -178,6 +179,7 @@ export default { }, data() { return { + showEdit: false, currentFileId: '', currentFile: {}, currentShapes: {}, @@ -215,11 +217,11 @@ export default { return this.block?.attributes?.payload?.shapes; }, currentUrl() { - if (this.currentFile.download_url !== 'undefined') { + if (this.currentFile.download_url) { return this.currentFile.download_url; - } else { - return ''; } + + return ''; }, }, mounted() { @@ -236,10 +238,17 @@ export default { await this.loadFile(); this.buildCanvas(); }, + setShowEdit(state) { + this.showEdit = state; + }, async loadFile() { const id = this.currentFileId; - await this.loadFileRef({ id }); - const fileRef = this.fileRefById({ id }); + let fileRef = null; + + if (id) { + await this.loadFileRef({ id }); + fileRef = this.fileRefById({ id }); + } if (fileRef) { this.updateCurrentFile({ @@ -271,16 +280,18 @@ export default { }, buildCanvas() { - let canvas = this.$refs.canvas; - let original_img = this.$refs.original_img; - canvas.width = 1085; - if (original_img.height > 0) { - canvas.height = Math.round((canvas.width / original_img.width) * original_img.height); - } else { - canvas.height = 484; + if (this.currentUrl) { + let canvas = this.$refs.canvas; + let original_img = this.$refs.original_img; + canvas.width = 1085; + if (original_img.height > 0) { + canvas.height = Math.round((canvas.width / original_img.width) * original_img.height); + } else { + canvas.height = 484; + } + this.context = canvas.getContext('2d'); + this.drawScreen(); } - this.context = canvas.getContext('2d'); - this.drawScreen(); }, drawScreen() { let context = this.context; @@ -519,5 +530,17 @@ export default { this.currentShapes[index].target_external = url; }, }, + watch: { + fileId() { + if (!this.showEdit) { + this.initCurrentData(); + } + }, + shapes() { + if (!this.showEdit) { + this.initCurrentData(); + } + } + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareKeyPointBlock.vue b/resources/vue/components/courseware/CoursewareKeyPointBlock.vue index 39124e548b9d1e4b6de11ac93d738d9c550157f5..fab09e2f42df78bc5fb8099e4761e9e71bd3d2d7 100755 --- a/resources/vue/components/courseware/CoursewareKeyPointBlock.vue +++ b/resources/vue/components/courseware/CoursewareKeyPointBlock.vue @@ -5,8 +5,10 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" - @storeEdit="storeBlock" @closeEdit="closeEdit" + @showEdit="setShowEdit" + @storeEdit="storeBlock" + > <template #content> <div class="cw-keypoint-content" :class="['cw-keypoint-' + currentColor]"> @@ -94,6 +96,7 @@ export default { }, data() { return { + showEdit: false, currentText: '', currentColor: '', currentIcon: '', @@ -181,6 +184,9 @@ export default { this.currentColor = this.color; this.currentIcon = this.icon; }, + setShowEdit(state) { + this.showEdit = state; + }, storeBlock() { let attributes = {}; attributes.payload = {}; @@ -201,5 +207,22 @@ export default { mounted() { this.initCurrentData(); }, + watch: { + text() { + if (!this.showEdit) { + this.currentText = this.text; + } + }, + color() { + if (!this.showEdit) { + this.currentColor = this.color; + } + }, + icon() { + if (!this.showEdit) { + this.currentIcon = this.icon; + } + }, + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareLinkBlock.vue b/resources/vue/components/courseware/CoursewareLinkBlock.vue index a2fce7d0045edb8cfca864b155fd5100eed2f3ab..270660eea10bfe59e8ad52b78db89f4544ff439e 100755 --- a/resources/vue/components/courseware/CoursewareLinkBlock.vue +++ b/resources/vue/components/courseware/CoursewareLinkBlock.vue @@ -5,8 +5,9 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" - @storeEdit="storeBlock" @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> <div v-if="currentType === 'external'"> @@ -76,6 +77,7 @@ export default { }, data() { return { + showEdit: false, currentType: '', currentTarget: '', currentUrl: '', @@ -114,6 +116,9 @@ export default { this.currentTitle = this.title; this.fixUrl(); }, + setShowEdit(state) { + this.showEdit = state; + }, fixUrl() { if ( this.currentUrl.indexOf('http://') !== 0 && @@ -145,5 +150,27 @@ export default { }, }, + watch: { + type() { + if (!this.showEdit) { + this.currentType = this.type; + } + }, + target() { + if (!this.showEdit) { + this.currentTarget = this.target; + } + }, + url() { + if (!this.showEdit) { + this.currentUrl = this.url; + } + }, + title() { + if (!this.showEdit) { + this.currentTitle = this.title; + } + }, + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareStructuralElement.vue b/resources/vue/components/courseware/CoursewareStructuralElement.vue index 313e6a586ac5ffa87107f31c58a2567aa287f2e4..e1bfd938cad0e9a41deb1b43d5147c6313997a0c 100755 --- a/resources/vue/components/courseware/CoursewareStructuralElement.vue +++ b/resources/vue/components/courseware/CoursewareStructuralElement.vue @@ -720,16 +720,17 @@ export default { } }, containers() { - if (!this.structuralElement) { - return []; + let containers = []; + let relatedContainers = this.structuralElement?.relationships?.containers?.data; + + if (relatedContainers) { + for (const container of relatedContainers) { + containers.push(this.containerById({ id: container.id})); + } } - return ( - this.relatedContainers({ - parent: this.structuralElement, - relationship: 'containers', - }) ?? [] - ); + return containers; + }, noContainers() { if (this.containers === null) { @@ -960,10 +961,10 @@ export default { return ''; }, blocked() { - return this.structuralElement?.relationships['edit-blocker'].data !== null; + return this.structuralElement?.relationships?.['edit-blocker']?.data !== null; }, blockerId() { - return this.blocked ? this.structuralElement?.relationships['edit-blocker'].data?.id : null; + return this.blocked ? this.structuralElement?.relationships?.['edit-blocker']?.data?.id : null; }, blockedByThisUser() { return this.blocked && this.userId === this.blockerId; diff --git a/resources/vue/components/courseware/CoursewareTableOfContentsBlock.vue b/resources/vue/components/courseware/CoursewareTableOfContentsBlock.vue index 2b2e0b8e579d59d617c11cea6a7be443f18bbd89..a3e590c98a6d81ada255b0aea6b04497465cb6c2 100755 --- a/resources/vue/components/courseware/CoursewareTableOfContentsBlock.vue +++ b/resources/vue/components/courseware/CoursewareTableOfContentsBlock.vue @@ -5,8 +5,9 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" - @storeEdit="storeText" - @closeEdit="closeEdit" + @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> <div v-if="currentStyle !== 'tiles' && currentTitle !== ''" class="cw-block-title">{{ currentTitle }}</div> @@ -95,6 +96,7 @@ export default { }, data() { return { + showEdit: false, currentTitle: '', currentStyle: '', }; @@ -128,10 +130,10 @@ export default { this.currentTitle = this.title; this.currentStyle = this.style; }, - closeEdit() { - this.initCurrentData(); + setShowEdit(state) { + this.showEdit = state; }, - storeText() { + storeBlock() { let attributes = {}; attributes.payload = {}; attributes.payload.title = this.currentTitle; @@ -159,5 +161,17 @@ export default { return child.relationships?.image?.data !== null; }, }, + watch: { + title() { + if (!this.showEdit) { + this.currentTitle = this.title; + } + }, + style() { + if (!this.showEdit) { + this.currentStyle = this.style; + } + }, + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareTabsContainer.vue b/resources/vue/components/courseware/CoursewareTabsContainer.vue index 747b24a5930127c6148782093d80a9f7db27ab87..6615b0f69b9a830da0e70a1d073cc6ebe0efd4b7 100755 --- a/resources/vue/components/courseware/CoursewareTabsContainer.vue +++ b/resources/vue/components/courseware/CoursewareTabsContainer.vue @@ -4,9 +4,10 @@ containerClass="cw-container-tabs" :canEdit="canEdit" :isTeacher="isTeacher" - @storeContainer="storeContainer" @closeEdit="initCurrentData" + @showEdit="setShowEdit" @sortBlocks="enableSort" + @storeContainer="storeContainer" > <template v-slot:containerContent> <courseware-tabs v-if="!sortMode"> @@ -129,6 +130,7 @@ export default { }, data() { return { + showEdit: false, currentContainer: null, currentSections: [], textDeleteSection: this.$gettext('Sektion entfernen'), @@ -181,6 +183,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/CoursewareTypewriterBlock.vue b/resources/vue/components/courseware/CoursewareTypewriterBlock.vue index abdc8ebe368380db8955c1562f8543003ab25aef..5d131a280cc6a3abb8d9c3417e072ec0d6210c80 100755 --- a/resources/vue/components/courseware/CoursewareTypewriterBlock.vue +++ b/resources/vue/components/courseware/CoursewareTypewriterBlock.vue @@ -5,11 +5,12 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" - @storeEdit="storeText" - @closeEdit="closeEdit" + @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> - <div class="cw-typewriter-content"> + <div v-if="currentText !== ''" class="cw-typewriter-content"> <vue-typer :text="currentText" initial-action="typing" @@ -83,8 +84,8 @@ export default { }, data() { return { + showEdit: false, speeds: [200, 100, 50, 25], - typing: false, speedClasses: [ 'cw-typewriter-letter-fadein-slow', 'cw-typewriter-letter-fadein-normal', @@ -127,6 +128,9 @@ export default { this.currentFont = this.font; this.currentSize = this.size; }, + setShowEdit(state) { + this.showEdit = state; + }, restartTyping() { let text = this.currentText; this.currentText = ' '; @@ -134,10 +138,7 @@ export default { this.currentText = text; }); }, - closeEdit() { - this.initCurrentData(); - }, - storeText() { + storeBlock() { let attributes = {}; attributes.payload = {}; attributes.payload.text = this.currentText; @@ -152,5 +153,28 @@ export default { }); } }, + watch: { + text() { + if (!this.showEdit) { + this.currentText = this.text; + } + }, + speed() { + if (!this.showEdit) { + this.currentSpeed = this.speed; + this.restartTyping(); + } + }, + font() { + if (!this.showEdit) { + this.currentFont = this.font; + } + }, + size() { + if (!this.showEdit) { + this.currentSize = this.size; + } + }, + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareVideoBlock.vue b/resources/vue/components/courseware/CoursewareVideoBlock.vue index e5e31fcab435ae82a5997fb65e58e9a5c7ecfb5c..ee607e047dc8af7e1af03cd1fbb793a656d15c01 100755 --- a/resources/vue/components/courseware/CoursewareVideoBlock.vue +++ b/resources/vue/components/courseware/CoursewareVideoBlock.vue @@ -5,11 +5,13 @@ :canEdit="canEdit" :isTeacher="isTeacher" :preview="true" - @storeEdit="storeBlock" @closeEdit="initCurrentData" + @showEdit="setShowEdit" + @storeEdit="storeBlock" > <template #content> <div v-if="currentTitle !== '' && currentURL" class="cw-block-title">{{ currentTitle }}</div> + <div v-if="!currentURL" class="cw-block-title">{{ currentTitle ? currentTitle : $gettext('Video') }}</div> <video v-show="currentURL" :src="currentURL" @@ -18,6 +20,9 @@ :autoplay="currentAutoplay === 'enabled'" @contextmenu="contextHandler" /> + <div v-if="!currentURL" class="cw-file-empty"> + <p><translate>Es ist keine Video-Datei verfügbar</translate></p> + </div> </template> <template v-if="canEdit" #edit> <form class="default" @submit.prevent=""> @@ -77,6 +82,7 @@ import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFileChooser from './CoursewareFileChooser.vue'; import { mapActions, mapGetters } from 'vuex'; + export default { name: 'courseware-video-block', components: { @@ -90,6 +96,7 @@ export default { }, data() { return { + showEdit: false, currentSource: '', currentTitle: '', currentFile: {}, @@ -176,7 +183,7 @@ export default { containerId: this.block.relationships.container.data.id, }); }, - async initCurrentData() { + initCurrentData() { this.currentSource = this.source; this.currentTitle = this.title; this.currentWebUrl = this.webUrl; @@ -185,9 +192,12 @@ export default { this.currentContextMenu = this.contextMenu; this.currentAutoplay = this.autoplay; if (this.fileId !== '') { - await this.loadFile(); + this.loadFile(); } }, + setShowEdit(state) { + this.showEdit = state; + }, async loadFile() { const id = this.currentFileId; await this.loadFileRef({ id }); @@ -215,5 +225,45 @@ export default { } }, }, + watch: { + source() { + if (!this.showEdit) { + this.currentSource = this.source; + } + }, + title() { + if (!this.showEdit) { + this.currentTitle = this.title; + } + }, + webUrl() { + if (!this.showEdit) { + this.currentWebUrl = this.webUrl; + } + }, + fileId() { + if (!this.showEdit) { + this.currentFileId = this.fileId; + if (this.fileId !== '') { + this.loadFile(); + } + } + }, + aspect() { + if (!this.showEdit) { + this.currentAspect = this.aspect; + } + }, + contextMenu() { + if (!this.showEdit) { + this.currentContextMenu = this.contextMenu; + } + }, + autoplay() { + if (!this.showEdit) { + this.currentAutoplay = this.autoplay; + } + }, + } }; </script> diff --git a/resources/vue/components/courseware/IndexApp.vue b/resources/vue/components/courseware/IndexApp.vue index 432a4a6ed5a8001e28ba4bea316f7fea5dbbd747..1c39d2011b8d0527bb17de9290b06d18a9a2fc8c 100755 --- a/resources/vue/components/courseware/IndexApp.vue +++ b/resources/vue/components/courseware/IndexApp.vue @@ -52,7 +52,8 @@ export default { canVisit: null, selected: null, structureLoadingState: 'idle', - loadingErrorStatus: null + loadingErrorStatus: null, + polling: null }), computed: { ...mapGetters({ @@ -75,6 +76,9 @@ export default { default: return this.$gettext('Beim Laden der Seite ist ein Fehler aufgetreten.'); } + }, + selectedId() { + return this.$route.params?.id; } }, methods: { @@ -95,6 +99,11 @@ export default { this.canVisit = this.structuralElementLastMeta['can-visit']; this.selected = this.structuralElementById({ id }); }, + pollData () { + this.polling = setInterval(async () => { + await this.selectStructuralElement(this.selectedId); + }, 4000); + } }, async mounted() { this.structureLoadingState = 'loading'; @@ -112,6 +121,10 @@ export default { this.structureLoadingState = 'done'; const selectedId = this.$route.params?.id; await this.selectStructuralElement(selectedId); + this.pollData(); + }, + beforeDestroy () { + clearInterval(this.polling) }, watch: { $route(to) { diff --git a/resources/vue/components/courseware/block-mixin.js b/resources/vue/components/courseware/block-mixin.js index 2e084baf116885a4362940dd1618c28d575d47f3..eb544c77258ae5c0153d9ba5b3d5c11398b4354a 100755 --- a/resources/vue/components/courseware/block-mixin.js +++ b/resources/vue/components/courseware/block-mixin.js @@ -20,5 +20,11 @@ export const blockMixin = { ...mapActions({ updateUserProgress: 'courseware-user-progresses/update', }), + contentsEqual(o1, o2) { + return typeof o1 === 'object' && Object.keys(o1).length > 0 + ? Object.keys(o1).length === Object.keys(o2).length + && Object.keys(o1).every(p => this.contentsEqual(o1[p], o2[p])) + : o1 === o2; + } }, }; diff --git a/resources/vue/store/courseware/courseware.module.js b/resources/vue/store/courseware/courseware.module.js index 57f2f9aa29f846dd2cac47a30406dd3354300481..5a6e479620ef63b38766522e3bf6af0571df2929 100755 --- a/resources/vue/store/courseware/courseware.module.js +++ b/resources/vue/store/courseware/courseware.module.js @@ -202,7 +202,7 @@ export const state = { ...initialState }; export const actions = { loadContainer({ dispatch }, containerId) { const options = { - include: 'blocks', + include: 'blocks,blocks.edit-blocker', }; return dispatch('courseware-containers/loadById', { id: containerId, options }, { root: true });