From f2fb170e482370b7aa34f127dfad427fe488b903 Mon Sep 17 00:00:00 2001 From: Farbod Zamani <zamani@elan-ev.de> Date: Tue, 21 Feb 2023 10:29:11 +0000 Subject: [PATCH] CW-Canvas Block view button Closes #1877 Merge request studip/studip!1258 --- .../Courseware/UserDataFieldOfBlocksShow.php | 6 +- .../courseware/CoursewareCanvasBlock.vue | 128 +++++++++++++++--- resources/vue/courseware-index-app.js | 3 +- .../vue/store/courseware/courseware.module.js | 10 ++ 4 files changed, 124 insertions(+), 23 deletions(-) diff --git a/lib/classes/JsonApi/Routes/Courseware/UserDataFieldOfBlocksShow.php b/lib/classes/JsonApi/Routes/Courseware/UserDataFieldOfBlocksShow.php index b4e41db6ffc..51b60c1fe75 100644 --- a/lib/classes/JsonApi/Routes/Courseware/UserDataFieldOfBlocksShow.php +++ b/lib/classes/JsonApi/Routes/Courseware/UserDataFieldOfBlocksShow.php @@ -26,12 +26,16 @@ class UserDataFieldOfBlocksShow extends JsonApiController throw new RecordNotFoundException(); } // this is automatically scoped to the requesting user + // so we don't need to worry about the accessing the user data fields. $resource = UserDataField::getUserDataField($user = $this->getUser($request), $block); if (!Authority::canShowUserDataField($user, $resource)) { throw new AuthorizationFailedException(); } - return $this->getContentResponse($resource); + // however, as it is intended to list all user data fields of the block, we get it here and return it back. + $resources = UserDataField::findBySql('block_id = ?', [$block->id]); + + return $this->getContentResponse($resources); } } diff --git a/resources/vue/components/courseware/CoursewareCanvasBlock.vue b/resources/vue/components/courseware/CoursewareCanvasBlock.vue index 8e4bb651348..e1ea7e87cb5 100644 --- a/resources/vue/components/courseware/CoursewareCanvasBlock.vue +++ b/resources/vue/components/courseware/CoursewareCanvasBlock.vue @@ -18,6 +18,7 @@ <button class="cw-canvasblock-reset" :title="$gettext('ZurÞcksetzen')" @click="reset"></button> <button class="cw-canvasblock-undo" :title="$gettext('RÞckgÃĪngig')" @click="undo"></button> <button v-if="hasUploadFolder" class="cw-canvasblock-store" :title="$gettext('Bild im Dateibereich speichern')" @click="store"></button> + <button v-if="canSwitchView" :class="viewButtonClass" :title="viewButtonText" @click="switchView"></button> </div> <div class="cw-canvasblock-buttonset"> <button @@ -171,6 +172,7 @@ export default { currentFileId: '', currentUploadFolderId: '', currentShowUserData: '', + currentUserView: 'own', currentFile: {}, context: {}, @@ -210,6 +212,7 @@ export default { userId: 'userId', getUserDataById: 'courseware-user-data-fields/byId', usersById: 'users/byId', + relatedUserData: 'user-data-field/related' }), userData() { return this.getUserDataById({ id: this.block.relationships['user-data-field'].data.id }); @@ -221,6 +224,35 @@ export default { return false; } }, + allCanvasDraws() { + const parent = { type: 'courseware-blocks', id: this.block.id }; + const relationship = 'user-data-field'; + const userDataFields = this.relatedUserData({ + parent: parent, + relationship: relationship, + }); + let canvasDraws = []; + if (userDataFields?.length > 0) { + for (let userDataField of userDataFields) { + // extracting the canvas draws of the other users. + if (userDataField?.attributes?.payload?.canvas_draw && + userDataField?.relationships?.user?.data?.id !== this.userId ) { + let canvas_draw = userDataField.attributes.payload.canvas_draw; + let draw_obj = { + clickX: JSON.parse(canvas_draw.clickX), + clickY: JSON.parse(canvas_draw.clickY), + clickDrag: JSON.parse(canvas_draw.clickDrag), + clickColor: JSON.parse(canvas_draw.clickColor), + clickSize: JSON.parse(canvas_draw.clickSize), + clickTool: JSON.parse(canvas_draw.clickTool), + Text: JSON.parse(canvas_draw.Text), + }; + canvasDraws.push(draw_obj); + } + } + } + return canvasDraws; + }, title() { return this.block?.attributes?.payload?.title; }, @@ -255,6 +287,30 @@ export default { hasUploadFolder() { return this.currentUploadFolderId !== ""; }, + canSwitchView() { + // this feature is not something to offer in the Arbeitsplatz! + let context = this.$store.getters.context; + if (context.type !== 'courses') { + return false; + } + if (this.currentShowUserData === 'off') { + return false; + } + if (this.currentShowUserData === 'teacher' && !this.isTeacher) { + return false; + } + return true; + }, + viewButtonText() { + let text = this.$gettext('Werte anderer Nutzer anzeigen'); + if (this.currentUserView == 'own') { + text = this.$gettext('Nur eigene Werte anzeigen'); + } + return text; + }, + viewButtonClass() { + return 'cw-canvasblock-show-' + this.currentUserView; + } }, mounted() { this.loadFileRefs(this.block.id).then((response) => { @@ -272,7 +328,8 @@ export default { createFile: 'createFile', companionSuccess: 'companionSuccess', companionError: 'companionError', - updateUserDataFields: 'courseware-user-data-fields/update' + updateUserDataFields: 'courseware-user-data-fields/update', + loadUserDataFields: 'loadUserDataFields', }), initCurrentData() { this.currentTitle = this.title; @@ -361,8 +418,6 @@ export default { redraw() { let view = this; let context = view.context; - let clickX = view.clickX; - let clickY = view.clickY; context.clearRect(0, 0, context.canvas.width, context.canvas.height); // Clears the canvas context.fillStyle = '#ffffff'; context.fillRect(0, 0, context.canvas.width, context.canvas.height); // set background @@ -373,25 +428,41 @@ export default { } context.lineJoin = 'round'; - for (var i = 0; i < clickX.length; i++) { - if (view.clickTool[i] === 'pen') { - context.beginPath(); - if (view.clickDrag[i] && i) { - context.moveTo(clickX[i - 1], clickY[i - 1]); - } else { - context.moveTo(clickX[i] - 1, clickY[i]); + let ownCanvasDraw = { + clickX: view.clickX, + clickY: view.clickY, + clickDrag: view.clickDrag, + clickColor: view.clickColor, + clickSize: view.clickSize, + clickTool: view.clickTool, + Text: view.Text + } + let canvasDraws = [ownCanvasDraw]; + if (this.currentUserView === 'all') { + canvasDraws = [ ...canvasDraws, ...view.allCanvasDraws ]; + } + + for (let draw of canvasDraws) { + for (var j = 0; j < draw.clickX.length; j++) { + if (draw.clickTool[j] === 'pen') { + context.beginPath(); + if (draw.clickDrag[j] && j) { + context.moveTo(draw.clickX[j - 1], draw.clickY[j - 1]); + } else { + context.moveTo(draw.clickX[j] - 1, draw.clickY[j]); + } + context.lineTo(draw.clickX[j], draw.clickY[j]); + context.closePath(); + context.strokeStyle = draw.clickColor[j]; + context.lineWidth = draw.clickSize[j]; + context.stroke(); + } + if (draw.clickTool[j] === 'text') { + let fontsize = draw.clickSize[j] * 6; + context.font = fontsize + 'px Arial '; + context.fillStyle = draw.clickColor[j]; + context.fillText(draw.Text[j], draw.clickX[j], draw.clickY[j] + fontsize); } - context.lineTo(clickX[i], clickY[i]); - context.closePath(); - context.strokeStyle = view.clickColor[i]; - context.lineWidth = view.clickSize[i]; - context.stroke(); - } - if (view.clickTool[i] === 'text') { - let fontsize = view.clickSize[i] * 6; - context.font = fontsize + 'px Arial '; - context.fillStyle = view.clickColor[i]; - context.fillText(view.Text[i], clickX[i], clickY[i] + fontsize); } } }, @@ -602,6 +673,21 @@ export default { }); } }, + async switchView() { + if (['own', 'all'].includes(this.currentUserView)) { + let newView = 'own'; + if (this.currentUserView === 'own') { + // we will get the latest draws by loading them each time the view is going to be switched to all! + await this.loadUserDataFields(this.block.id).then(() => newView = 'all'); + } + this.currentUserView = newView; + } + } + }, + watch: { + currentUserView() { + this.redraw(); + } }, }; </script> diff --git a/resources/vue/courseware-index-app.js b/resources/vue/courseware-index-app.js index 57a8936da1b..337b56f83b1 100644 --- a/resources/vue/courseware-index-app.js +++ b/resources/vue/courseware-index-app.js @@ -115,7 +115,8 @@ const mountApp = async (STUDIP, createApp, element) => { 'semesters', 'sem-classes', 'sem-types', - 'terms-of-use' + 'terms-of-use', + 'user-data-field' ], httpClient, }), diff --git a/resources/vue/store/courseware/courseware.module.js b/resources/vue/store/courseware/courseware.module.js index 71e6d38408d..9aa42813ee6 100644 --- a/resources/vue/store/courseware/courseware.module.js +++ b/resources/vue/store/courseware/courseware.module.js @@ -296,6 +296,16 @@ export const actions = { ); }, + async loadUserDataFields({ dispatch }, blockId) { + const parent = { type: 'courseware-blocks', id: `${blockId}` }; + const relationship = 'user-data-field'; + const options = { + include: 'user', + }; + + return dispatch('user-data-field/loadRelated', { parent, relationship, options }, { root: true }); + }, + async loadCoursewareActivities({ dispatch, rootGetters }, { userId, courseId }) { const parent = { type: 'users', -- GitLab