From 5dde6dfd2545ba49a11749cb97c15c43215288ae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Bie=C5=84kowski?= <piotr@fenerum.com>
Date: Mon, 13 Jun 2022 22:18:17 +0200
Subject: [PATCH] Migrate LU display to Vue.js

---
 .../js/components/LexicalUnitDisplay.js       | 90 +++++++++++++++++--
 .../static/entries/js/components/Spinner.js   | 12 +++
 entries/static/entries/js/components/main.js  | 33 +++++--
 .../entries/js/unification_entries_list.js    | 79 +---------------
 .../static/entries/js/unification_index.js    |  2 +-
 .../unification_lexical_unit_display.html     | 21 ++---
 users/templates/notes.html                    |  2 +-
 7 files changed, 132 insertions(+), 107 deletions(-)
 create mode 100644 entries/static/entries/js/components/Spinner.js

diff --git a/entries/static/entries/js/components/LexicalUnitDisplay.js b/entries/static/entries/js/components/LexicalUnitDisplay.js
index dd27685..a1bbbce 100644
--- a/entries/static/entries/js/components/LexicalUnitDisplay.js
+++ b/entries/static/entries/js/components/LexicalUnitDisplay.js
@@ -1,15 +1,95 @@
+import Spinner from './Spinner.js';
+
 export default {
+  components: {Spinner},
   props: {
+    entryId: Number,
     lexicalUnitId: Number
   },
   data() {
-    return { count: 0 }
+    return { frame: null, unifiedFrame: undefined, gettext: window.gettext, hasPermission: window.has_permission }
   },
+  emits: ['goToEdit', 'refresh'],
   methods: {
-    clicked () {
-      this.count++;
-      addSelectivePreference();
+    markAsInvalid () {
+      $.ajax({
+        type: 'post',
+        url: `/${lang}/semantics/frame_mark_as_invalid/${this.frame.id}/`,
+        dataType: 'json',
+        timeout: 60000,
+      }).then(() => { this.$emit('refresh'); });
+    },
+    take () {
+      $.ajax({
+        type     : 'post',
+        url      : `/${lang}/semantics/frame_assign/${this.frame.id}/`,
+        dataType : 'json',
+        timeout  : 60000,
+      }).then(() => { this.$emit('goToEdit'); });
+    },
+    confirmInvalid () {
+      $.ajax({
+        type: 'post',
+        url: `/${lang}/semantics/frame_confirm_invalid/${this.frame.id}/`,
+        dataType: 'json',
+        timeout: 60000,
+      }).then(() => { this.$emit('refresh'); });
+    },
+    rejectInvalid () {
+      $.ajax({
+        type: 'post',
+        url: `/${lang}/semantics/frame_reject_invalid/${this.frame.id}/`,
+        dataType: 'json',
+        timeout: 60000,
+      }).then(() => { this.$emit('refresh'); });
     }
   },
-  template: `<div @click="clicked">display {{ lexicalUnitId }} is {{ count }}</div> <div @click="$emit('goToEdit')">idź do edycji</div>`
+  created () {
+    $('#lexical-unit-notes').html('');
+    get_entry(this.entryId, false, this.lexicalUnitId).then(entry => {
+      this.frame = entry.frames[0];
+      this.unifiedFrame = entry.unified_frame;
+      setup_notes($('#lexical-unit-notes'), $('#lexical-unit-notes-template'), this.lexicalUnitId, this.entryId);
+    });
+  },
+  template: `
+    <spinner />
+    <div id="semantics-frames"></div>
+    <div class="text-center mb-3">
+      <div v-if="unifiedFrame">
+        <a class="btn btn-sm btn-outline-dark mr-2" v-if="frame?.status === 'N'" @click="markAsInvalid">{{ gettext('Błędna') }}</a>
+        <a class="btn btn-sm btn-outline-dark mr-2" v-if="frame?.status === 'N'" @click="take">{{ gettext('Pobierz') }}</a>
+        <a
+          class="btn btn-sm btn-outline-dark mr-2"
+          v-if="frame?.status === 'O' || frame?.status === 'G' && unifiedFrame?.status === 'O'"
+          @click="$emit('goToEdit')"
+        >
+          {{ gettext('Obrabiaj') }}
+        </a>
+        <a
+          class="btn btn-sm btn-outline-dark mr-2"
+          v-if="frame?.status === 'S' && unifiedFrame?.status === 'S'"
+          @click="$emit('goToEdit')"
+        >
+          {{ gettext('Obejrzyj') }}
+        </a>
+        <a
+          class="btn btn-sm btn-outline-dark mr-2"
+          v-if="frame?.status === 'B' && hasPermission('semantics.manage_invalid_lexical_units')"
+          @click="confirmInvalid"
+        >
+          {{ gettext('Potwierdź') }}
+        </a>
+        <a
+          class="btn btn-sm btn-outline-dark mr-2"
+          v-if="frame?.status === 'B' && hasPermission('semantics.manage_invalid_lexical_units')"
+          @click="rejectInvalid"
+        >
+          {{ gettext('Odrzuć') }}
+        </a>
+      </div>
+      <span v-if="unifiedFrame === null">{{ gettext('Brak ramy unifikacyjnej') }}</span>
+    </div>
+    <div id="lexical-unit-notes"></div>
+  `
 }
diff --git a/entries/static/entries/js/components/Spinner.js b/entries/static/entries/js/components/Spinner.js
new file mode 100644
index 0000000..a4ac913
--- /dev/null
+++ b/entries/static/entries/js/components/Spinner.js
@@ -0,0 +1,12 @@
+export default {
+  data() {
+    return { gettext: window.gettext }
+  },
+  template: `
+    <div class="d-flex justify-content-center wait-spinner">
+      <div class="spinner-border text-primary m-5" style="width: 3rem; height: 3rem;" role="status">
+        <span class="sr-only">{{ gettext('Proszę czekać...') }}</span>
+      </div>
+    </div>
+  `
+}
diff --git a/entries/static/entries/js/components/main.js b/entries/static/entries/js/components/main.js
index d2aec4e..46fbc9d 100644
--- a/entries/static/entries/js/components/main.js
+++ b/entries/static/entries/js/components/main.js
@@ -4,30 +4,45 @@ import LexicalUnitEdit from './LexicalUnitEdit.js';
 export default {
   components: {LexicalUnitDisplay, LexicalUnitEdit},
   props: {
-    lexicalUnitId: Number
+    entryId: Number,
+    lexicalUnitId: Number,
   },
   data () {
     return this.getInitialData();
   },
   methods: {
     getInitialData () {
-      return { isEdit: false }
+      return { isEdit: false, key: this.lexicalUnitId };
     },
     goToEdit () {
       this.isEdit = true;
+    },
+    refresh () {
+      this.key = null;
+      setTimeout(() => { this.key = this.lexicalUnitId; }, 0);
+      update_entries();
     }
   },
   watch: {
     lexicalUnitId () {
-      const freshData = this.getInitialData();
-      Object.keys(freshData).map(key => {
-        this[key] = freshData[key];
-      });
+      Object.assign(this, this.getInitialData());
     }
   },
-
   template: `
-    <lexical-unit-display v-if="lexicalUnitId && !isEdit" :lexicalUnitId="lexicalUnitId" @go-to-edit="goToEdit" />
-    <lexical-unit-edit v-if="isEdit" :lexicalUnitId="lexicalUnitId" />
+    <div class="col h-100 px-1 pt-0 pb-2 overflow-auto" id="semantics-frames-pane">
+      <div v-if="key" :key="key">
+        <lexical-unit-display
+          v-if="lexicalUnitId && !isEdit"
+          :entryId="entryId"
+          :lexicalUnitId="lexicalUnitId"
+          @go-to-edit="goToEdit"
+          @refresh="refresh"
+        />
+        <lexical-unit-edit v-if="isEdit" :lexicalUnitId="lexicalUnitId" />
+      </div>
+    </div>
+    <div class="col h-100 px-1 pt-0 pb-0 overflow-auto" id="semantics-schemata-pane">
+      <div id="semantics-schemata"></div>
+    </div>
   `
 }
diff --git a/entries/static/entries/js/unification_entries_list.js b/entries/static/entries/js/unification_entries_list.js
index dd8b00b..4589fba 100644
--- a/entries/static/entries/js/unification_entries_list.js
+++ b/entries/static/entries/js/unification_entries_list.js
@@ -51,8 +51,8 @@ function setup_lexical_units_table(drilldown, lexical_units, can_see_assignees)
             </tr>
         `).click(function () {
             $(this).addClass('table-primary').siblings().removeClass("table-primary");
+            window.unificationApp.$.props.entryId = $(drilldown.data("row")).data("entry");
             window.unificationApp.$.props.lexicalUnitId = lexical_unit.pk;
-            get_lexical_unit(lexical_unit.pk, $(drilldown.data("row")).data("entry"));
         });
     }
     var table = $(`
@@ -83,7 +83,6 @@ function setup_notes($container, $template, lexical_unit_pk, entry_pk) {
         $('.note-form', $container).html($('#note-form-template > div', $container).clone());
         $('.hide-note-form', $container).click(function () { $('.note-form', $container).html(''); });
         $('.add-note', $container).click(function () {
-            console.log($('textarea[name=note]', $container).val());
             $.ajax({
                 type     : 'post',
                 url      : $('#note-form-template').data('url').replace('MODEL', 'meanings.LexicalUnit').replace('PK', lexical_unit_pk),
@@ -91,7 +90,7 @@ function setup_notes($container, $template, lexical_unit_pk, entry_pk) {
                 data     : { note: $('.note-form textarea[name=note]').val() },
                 timeout  : 5000,
                 success  : function (response) {
-                    get_lexical_unit(lexical_unit_pk, entry_pk);
+                    window.unificationApp.refresh();
                 },
                 error    : function () {
                     alert(gettext('Nie udało się dodać notatki.'));
@@ -110,77 +109,3 @@ function setup_notes($container, $template, lexical_unit_pk, entry_pk) {
         }
     })
 }
-
-function setup_unified_frame($container, frame, unified_frame, refresh) {
-    $container.html('');
-    if (unified_frame) {
-        var $button_container = $('<div>').addClass("text-center");
-        var $button = $('<a>').addClass('btn btn-sm btn-outline-dark mr-2');
-        function go_to_unified_frame () {
-            window.location = `/${window.lang}/entries/unification_frames`;  // TODO redirect properly
-        }
-        function invalid () {
-            $.ajax({
-                type     : 'post',
-                url      : `/${lang}/semantics/frame_mark_as_invalid/${frame.id}/`,
-                dataType : 'json',
-                timeout  : 60000,
-            }).then(refresh);
-        }
-        function take () {
-            $.ajax({
-                type     : 'post',
-                url      : `/${lang}/semantics/frame_assign/${frame.id}/`,
-                dataType : 'json',
-                timeout  : 60000,
-            }).then(go_to_unified_frame);
-        }
-        function confirm_invalid () {
-            $.ajax({
-                type     : 'post',
-                url      : `/${lang}/semantics/frame_confirm_invalid/${frame.id}/`,
-                dataType : 'json',
-                timeout  : 60000,
-            }).then(refresh);
-        }
-        function reject_invalid () {
-            $.ajax({
-                type     : 'post',
-                url      : `/${lang}/semantics/frame_reject_invalid/${frame.id}/`,
-                dataType : 'json',
-                timeout  : 60000,
-            }).then(refresh);
-        }
-        if (frame.status === 'N') {
-            $button_container.append($button.clone().html(gettext("Błędna")).click(invalid));
-            $button_container.append($button.clone().html(gettext("Pobierz")).click(take));
-        } else if (frame.status === 'O') {
-            $button_container.append($button.clone().html(gettext("Obrabiaj")).click(go_to_unified_frame));
-        } else if (frame.status === 'G' && unified_frame.status === 'O') {
-            $button_container.append($button.clone().html(gettext("Obrabiaj")).click(go_to_unified_frame));
-        } else if (frame.status === 'S' && unified_frame.status === 'S') {
-            $button_container.append($button.clone().html(gettext("Obejrzyj")).click(go_to_unified_frame));
-        } else if (frame.status === 'B' && has_permission('semantics.manage_invalid_lexical_units')) {
-            $button_container.append($button.clone().html(gettext("Potwierdź")).click(confirm_invalid));
-            $button_container.append($button.clone().html(gettext("Odrzuć")).click(reject_invalid));
-        }
-        $container.append($button_container);
-    } else {
-        $container.append($('<p>').html(gettext("Brak ramy unifikacyjnej.")));
-    }
-}
-
-function get_lexical_unit(lexical_unit_pk, entry_pk) {
-    $('#lexical-unit-notes').html('');
-    $('#unified-frame').html('');
-    get_entry(entry_pk, false, lexical_unit_pk).then(function (entry) {
-        var frame = entry.frames[0];
-        setup_unified_frame(
-            $('#unified-frame'),
-            frame,
-            entry.unified_frame,
-            function () { get_lexical_unit(lexical_unit_pk, entry_pk); update_entries(); }
-        );
-        setup_notes($('#lexical-unit-notes'), $('#lexical-unit-notes-template'), lexical_unit_pk, entry_pk);
-    });
-}
diff --git a/entries/static/entries/js/unification_index.js b/entries/static/entries/js/unification_index.js
index ac4cd9f..227b3b6 100644
--- a/entries/static/entries/js/unification_index.js
+++ b/entries/static/entries/js/unification_index.js
@@ -1,4 +1,4 @@
   import Main from './components/main.js';
   const { createApp } = Vue;
 
-window.unificationApp = createApp(Main).mount('#app');
+window.unificationApp = createApp(Main).mount('#semantics-top-pane');
diff --git a/entries/templates/unification_lexical_unit_display.html b/entries/templates/unification_lexical_unit_display.html
index 275b7c4..dc5efc7 100644
--- a/entries/templates/unification_lexical_unit_display.html
+++ b/entries/templates/unification_lexical_unit_display.html
@@ -1,22 +1,15 @@
 {% load i18n static %}
 
-<script src="https://unpkg.com/vue@3"></script>
-<div id="app"></div>
-<script type="module" src="{% static 'entries/js/unification_index.js' %}"></script>
+<div id="lexical-unit-notes-template" class="d-none">{% include 'notes.html' %}</div>
 
 <div class="tab-content h-100 w-100 p-0" id="entryTabsContent">
     <div class="col h-100 w-100 p-0 tab-pane show active" id="semantics" role="tabpanel" aria-labelledby="semantics-tab">
-        <div class="row m-0 p-0" id="semantics-top-pane">
-            <div class="col h-100 px-1 pt-0 pb-0 overflow-auto" id="semantics-frames-pane">
-                <div id="semantics-frames"></div>
-                <div id="unified-frame" class="mb-3"></div>
-                <div id="lexical-unit-notes-template" class="d-none">{% include 'notes.html' %}</div>
-                <div id="lexical-unit-notes"></div>
-            </div>
-            <div class="col h-100 px-1 pt-0 pb-0 overflow-auto" id="semantics-schemata-pane">
-                <div id="semantics-schemata"></div>
-            </div>
-        </div>
+        <!-- Vue.js app -->
+        <script src="https://unpkg.com/vue@3"></script>
+        <div class="row m-0 p-0 overflow-auto" id="semantics-top-pane"></div>
+        <script type="module" src="{% static 'entries/js/unification_index.js' %}"></script>
+        <!-- Vue.js app -->
+
         <div class="row m-0 p-0 overflow-auto" id="semantics-examples-pane">
             <table id="semantics-examples" class="table table-sm table-hover">
                 <thead>
diff --git a/users/templates/notes.html b/users/templates/notes.html
index 361ded9..75a7198 100644
--- a/users/templates/notes.html
+++ b/users/templates/notes.html
@@ -19,7 +19,7 @@
             <h6 class="mt-4 d-inline">{% trans 'Notatki' %}</h6>
             <a href="#" class="show-note-form btn btn-xs btn-outline-dark ml-2">{% trans 'Dodaj' %}</a>
         </div>
-        <table class="table table-sm table-striped border notes-table" data-url="{% url 'users:get_notes' model='MODEL' pk='PK' %}">
+        <table class="table table-sm table-striped border notes-table mb-0" data-url="{% url 'users:get_notes' model='MODEL' pk='PK' %}">
             <tbody></tbody>
         </table>
     </div>
-- 
GitLab