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