From 22c72c63551e4aeec7fc0eca869eeaea83fc06b9 Mon Sep 17 00:00:00 2001
From: Thilo Wischmeyer <thwischm@gmail.com>
Date: Sun, 18 Jul 2021 17:52:28 +0200
Subject: [PATCH] Added MathRenderer component

---
 frontend/src/components/MathRenderer.vue      | 51 +++++++++++++
 .../submission_notes/SubmissionCorrection.vue | 76 ++++++++++---------
 .../AnnotatedSubmissionTopToolbar.vue         | 33 ++------
 .../submission_type/SubmissionType.vue        | 19 ++---
 .../pages/student/StudentSubmissionPage.vue   | 64 +++++++---------
 5 files changed, 134 insertions(+), 109 deletions(-)
 create mode 100644 frontend/src/components/MathRenderer.vue

diff --git a/frontend/src/components/MathRenderer.vue b/frontend/src/components/MathRenderer.vue
new file mode 100644
index 00000000..667a5987
--- /dev/null
+++ b/frontend/src/components/MathRenderer.vue
@@ -0,0 +1,51 @@
+<template>
+  <div
+    ref="wrapper"
+    :key="key"
+  >
+    <slot />
+  </div>
+</template>
+
+<script lang="ts">
+import { Vue, Component, Prop, Watch } from 'vue-property-decorator'
+
+@Component
+export default class MathRenderer extends Vue {
+  @Prop({ type: Boolean, default: true }) enabled!: boolean
+
+  key: Boolean = false
+
+  forceRefresh() {
+    this.key = !this.key
+  }
+
+  renderMath() {
+    window.MathJax.typeset([this.$refs.wrapper])
+  }
+
+  resetMath() {
+    window.MathJax.typesetClear([this.$refs.wrapper])
+    // typesetClear only clears the cache but leaves the DOM untouched, so we
+    // need to force it to rerender.
+    this.forceRefresh()
+  }
+
+  mounted() {
+    if (this.enabled)
+      this.renderMath()
+  }
+
+  beforeDestroy() {
+    this.resetMath()
+  }
+
+  @Watch('enabled')
+  onEnabledChanged(changedToEnabled: boolean) {
+    if (changedToEnabled)
+      this.renderMath()
+    else
+      this.resetMath()
+  }
+}
+</script>
diff --git a/frontend/src/components/submission_notes/SubmissionCorrection.vue b/frontend/src/components/submission_notes/SubmissionCorrection.vue
index 41bcdfbf..d63cff8e 100644
--- a/frontend/src/components/submission_notes/SubmissionCorrection.vue
+++ b/frontend/src/components/submission_notes/SubmissionCorrection.vue
@@ -3,6 +3,7 @@
     <base-annotated-submission>
       <template #header>
         <annotated-submission-top-toolbar
+          v-model="mathIsRendered"
           :of-student="submissionObj && submissionObj.ofStudent"
           :show-clipboard="true"
           :show-correction-help="true"
@@ -17,45 +18,47 @@
         id="sub-lines"
         #table-content
       >
-        <tr
-          v-for="(code, lineNo) in submission"
-          :id="`sub-line-${lineNo}`"
-          :key="`${submissionObj.pk}${lineNo}`"
-        >
-          <submission-line
-            :hint="hasHiddenComment(lineNo)"
-            :code="code"
-            :line-no="lineNo"
-            @toggleEditor="toggleEditorOnLine(lineNo)"
+        <math-renderer :enabled="mathIsRendered">
+          <tr
+            v-for="(code, lineNo) in submission"
+            :id="`sub-line-${lineNo}`"
+            :key="`${submissionObj.pk}${lineNo}`"
           >
-            <template v-if="showFeedback">
-              <div v-if="origFeedback[lineNo]">
+            <submission-line
+              :hint="hasHiddenComment(lineNo)"
+              :code="code"
+              :line-no="lineNo"
+              @toggleEditor="toggleEditorOnLine(lineNo)"
+            >
+              <template v-if="showFeedback">
+                <div v-if="origFeedback[lineNo]">
+                  <feedback-comment
+                    v-for="(comment, index) in getSortedComments(lineNo)"
+                    :key="index"
+                    v-bind="comment"
+                    :visible-to-student-bool="updatedFeedback[lineNo] ? false : comment.visibleToStudent"
+                    :line-no="lineNo"
+                    :deletable="comment.ofTutor === user || isReviewer"
+                    @click.native="toggleEditorOnLine(lineNo, comment)"
+                  />
+                </div>
                 <feedback-comment
-                  v-for="(comment, index) in getSortedComments(lineNo)"
-                  :key="index"
-                  v-bind="comment"
-                  :visible-to-student-bool="updatedFeedback[lineNo] ? false : comment.visibleToStudent"
+                  v-if="updatedFeedback[lineNo]"
+                  v-bind="updatedFeedback[lineNo]"
                   :line-no="lineNo"
-                  :deletable="comment.ofTutor === user || isReviewer"
-                  @click.native="toggleEditorOnLine(lineNo, comment)"
+                  :deletable="true"
+                  @click.native="toggleEditorOnLine(lineNo, updatedFeedback[lineNo])"
                 />
-              </div>
-              <feedback-comment
-                v-if="updatedFeedback[lineNo]"
-                v-bind="updatedFeedback[lineNo]"
+              </template>
+              <comment-form
+                v-if="showEditorOnLine[lineNo]"
+                :feedback="selectedComment[lineNo].text"
                 :line-no="lineNo"
-                :deletable="true"
-                @click.native="toggleEditorOnLine(lineNo, updatedFeedback[lineNo])"
+                @collapseFeedbackForm="toggleEditorOnLine(lineNo)"
               />
-            </template>
-            <comment-form
-              v-if="showEditorOnLine[lineNo]"
-              :feedback="selectedComment[lineNo].text"
-              :line-no="lineNo"
-              @collapseFeedbackForm="toggleEditorOnLine(lineNo)"
-            />
-          </submission-line>
-        </tr>
+            </submission-line>
+          </tr>
+        </math-renderer>
       </template>
       <template #labels>
         <label-selector
@@ -94,6 +97,7 @@ import { SubmissionType } from '@/models'
 import { Authentication } from '@/store/modules/authentication'
 import { actions } from '@/store/actions'
 import { fetchFeedback } from '@/api'
+import MathRenderer from '@/components/MathRenderer.vue'
 
 export default {
   name: 'SubmissionCorrection',
@@ -104,7 +108,8 @@ export default {
     AnnotatedSubmissionTopToolbar,
     FeedbackComment,
     LabelSelector,
-    CommentForm },
+    CommentForm,
+    MathRenderer },
   props: {
     assignment: {
       default: () => {},
@@ -127,7 +132,8 @@ export default {
   data () {
     return {
       loading: false,
-      feedbackShortPollInterval: null
+      feedbackShortPollInterval: null,
+      mathIsRendered: true,
     }
   },
   computed: {
diff --git a/frontend/src/components/submission_notes/toolbars/AnnotatedSubmissionTopToolbar.vue b/frontend/src/components/submission_notes/toolbars/AnnotatedSubmissionTopToolbar.vue
index 5e2581c4..4f42298d 100644
--- a/frontend/src/components/submission_notes/toolbars/AnnotatedSubmissionTopToolbar.vue
+++ b/frontend/src/components/submission_notes/toolbars/AnnotatedSubmissionTopToolbar.vue
@@ -24,7 +24,7 @@
     <v-spacer />
     <toggle-feedback-visibility-button />
     <div v-if="isMarkdown">
-      <v-btn @click="mathIsRendered = !mathIsRendered">
+      <v-btn @click="$emit('input', !mathIsRendered)">
         {{ mathIsRendered ? 'Reset Math' : 'Render Math' }}
       </v-btn>
     </div>
@@ -180,6 +180,10 @@ export default {
     submissionLanguage: {
       type: String,
       default: null
+    },
+    value: {
+      type: Boolean,
+      default: true
     }
   },
   data () {
@@ -188,38 +192,17 @@ export default {
       copyMessage: 'Copy to clipboard',
       originalSubmissionDialog: false,
       originalSubmission: '',
-      mathIsRendered: this.isMarkdown,
     }
   },
   computed: {
     solutionHidden () {
       return UI.state.showSubmissionType === false
+    },
+    mathIsRendered() {
+      return this.value
     }
   },
-  watch: {
-    mathIsRendered: {
-      immediate: true,
-      handler(newValue) {
-        if (newValue)
-          this.renderMath()
-        else
-          this.resetSubmission()
-      }
-    }
-  },
-  created () {
-    subNotesEventBus.$on('submissionChanged', () => {
-      if (this.mathIsRendered) {
-        this.$nextTick(() => {
-          this.renderMath()
-        })
-      }
-    })
-  },
   methods: {
-    renderMath () {
-      window.MathJax.typeset()
-    },
     resetSubmission () {
       subNotesEventBus.$emit('resetSubmission')
     },
diff --git a/frontend/src/components/submission_type/SubmissionType.vue b/frontend/src/components/submission_type/SubmissionType.vue
index cb5cde28..3e845b98 100644
--- a/frontend/src/components/submission_type/SubmissionType.vue
+++ b/frontend/src/components/submission_type/SubmissionType.vue
@@ -35,8 +35,10 @@
           v-if="item.title === 'Description'"
           class="type-description"
         >
-          <!-- eslint-disable-next-line -->
-          <div class="description-content" v-html="item.text"/>
+          <math-renderer>
+            <!-- eslint-disable-next-line -->
+            <div class="description-content" v-html="item.text" />
+          </math-renderer>
         </v-expansion-panel-content>
         <v-expansion-panel-content v-else-if="item.title === 'Solution'">
           <solution
@@ -62,9 +64,10 @@ import { UI } from '@/store/modules/ui'
 import { SolutionComment } from '../../models'
 import Solution from '@/components/submission_type/solution/Solution.vue'
 import { Authentication } from '@/store/modules/authentication'
+import MathRenderer from '@/components/MathRenderer.vue'
 
 @Component({
-  components: {Solution}
+  components: { Solution, MathRenderer }
 })
 export default class SubmissionType extends Vue {
   @Prop({
@@ -148,16 +151,6 @@ export default class SubmissionType extends Vue {
     return highlight(this.programmingLanguage, this.solution, true).value
   }
 
-  @Watch('description')
-  onDescriptionChanged(newVal: string, oldVal: string) {
-    console.log(`desc rerender old  ${oldVal} new ${newVal}`)
-    if (oldVal !== newVal) {
-      this.$nextTick().then(() => {
-        window.MathJax.typeset()
-      })
-    }
-  }
-
   close() {
     UI.SET_SHOW_SUBMISSIONTYPE(false)
   }
diff --git a/frontend/src/pages/student/StudentSubmissionPage.vue b/frontend/src/pages/student/StudentSubmissionPage.vue
index 38f5fa15..755983c6 100644
--- a/frontend/src/pages/student/StudentSubmissionPage.vue
+++ b/frontend/src/pages/student/StudentSubmissionPage.vue
@@ -16,10 +16,11 @@
     </v-row>
     <v-row>
       <v-col :md="solutionHidden ? 12 : 6">
-        <base-annotated-submission :key="mathRerender">
+        <base-annotated-submission>
           <template #header>
             <annotated-submission-top-toolbar
               v-if="feedback"
+              v-model="mathIsRendered"
               :score="feedback.score"
               :notebook-available="notebookAvailable"
               :submission="submission"
@@ -29,28 +30,30 @@
             />
           </template>
           <template #table-content>
-            <tr
-              v-for="(code, lineNo) in submissionText"
-              :key="lineNo"
-            >
-              <submission-line
-                :code="code"
-                :line-no="lineNo"
+            <math-renderer :enabled="mathIsRendered">
+              <tr
+                v-for="(code, lineNo) in submissionText"
+                :key="lineNo"
               >
-                <template v-if="feedback">
-                  <template v-for="(comment, index) in feedback.feedbackLines[lineNo]">
-                    <feedback-comment
-                      v-if="showFeedback"
-                      :key="comment.pk + index"
-                      v-bind="comment"
-                      :line-no="lineNo"
-                      :show-visibility-icon="false"
-                      :corrector-view="false"
-                    />
+                <submission-line
+                  :code="code"
+                  :line-no="lineNo"
+                >
+                  <template v-if="feedback">
+                    <template v-for="(comment, index) in feedback.feedbackLines[lineNo]">
+                      <feedback-comment
+                        v-if="showFeedback"
+                        :key="comment.pk + index"
+                        v-bind="comment"
+                        :line-no="lineNo"
+                        :show-visibility-icon="false"
+                        :corrector-view="false"
+                      />
+                    </template>
                   </template>
-                </template>
-              </submission-line>
-            </tr>
+                </submission-line>
+              </tr>
+            </math-renderer>
           </template>
         </base-annotated-submission>
         <v-card>
@@ -108,6 +111,7 @@ import { fetchNotebookSubmissionAsHtml } from '@/api.ts'
 import store from '@/store/store'
 import { SubmissionType as SubType } from '@/models'
 import { UI } from '@/store/modules/ui'
+import MathRenderer from '@/components/MathRenderer.vue'
 
 export default {
   name: 'StudentSubmissionPage',
@@ -118,13 +122,13 @@ export default {
     BaseAnnotatedSubmission,
     AnnotatedSubmissionTopToolbar,
     FeedbackLabel,
-    SubmissionType },
+    SubmissionType,
+    MathRenderer },
   data () {
     return {
       originalSubmissionDialog: false,
       originalSubmission: '',
       mathIsRendered: false,
-      mathRerender: 0
     }
   },
   computed: {
@@ -168,20 +172,8 @@ export default {
       let submission = {...StudentPage.state.submissionData[routeId]}
       submission.type = submission.type.pk
       SubmissionNotes.SET_SUBMISSION(submission)
-      this.renderMath()
-    },
-    renderMath() {
-      this.$nextTick(() => {
-        if (this.isMarkdown) {
-          window.MathJax.typeset()
-          this.mathIsRendered = true
-        }
-      })
+      this.mathIsRendered = true
     },
-    resetMath () {
-      this.mathRerender++
-      this.mathIsRendered = false
-    }
   },
   beforeRouteUpdate (to, from, next) {
     this.onRouteMountOrUpdate(to.params.id)
-- 
GitLab