From 7951192cd98263a2e5c9e1010f1299c651f82cef Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 16:11:05 +0300
Subject: [PATCH] Improve settings-modal async loading, update vue to 2.6.11 to
 be able to use Vue.observable, to implmement resettable async component

---
 package.json                                  |  4 +-
 src/components/big_spinner/big_spinner.vue    | 13 ++++++
 src/components/error_window/error_window.vue  | 41 +++++++++++++++++++
 .../settings_modal/settings_modal.js          | 13 +++++-
 src/i18n/en.json                              |  1 +
 src/services/resettable_async_component.js    | 32 +++++++++++++++
 yarn.lock                                     | 15 ++++---
 7 files changed, 110 insertions(+), 9 deletions(-)
 create mode 100644 src/components/big_spinner/big_spinner.vue
 create mode 100644 src/components/error_window/error_window.vue
 create mode 100644 src/services/resettable_async_component.js

diff --git a/package.json b/package.json
index 542086b4..4d68cc6e 100644
--- a/package.json
+++ b/package.json
@@ -29,11 +29,11 @@
     "portal-vue": "^2.1.4",
     "sanitize-html": "^1.13.0",
     "v-click-outside": "^2.1.1",
-    "vue": "^2.5.13",
+    "vue": "^2.6.11",
     "vue-chat-scroll": "^1.2.1",
     "vue-i18n": "^7.3.2",
     "vue-router": "^3.0.1",
-    "vue-template-compiler": "^2.3.4",
+    "vue-template-compiler": "^2.6.11",
     "vuelidate": "^0.7.4",
     "vuex": "^3.0.1",
     "whatwg-fetch": "^2.0.3"
diff --git a/src/components/big_spinner/big_spinner.vue b/src/components/big_spinner/big_spinner.vue
new file mode 100644
index 00000000..cda28de5
--- /dev/null
+++ b/src/components/big_spinner/big_spinner.vue
@@ -0,0 +1,13 @@
+<template>
+  <div class="big-spinner">
+    <i class="icon-spin4 animate-spin" />
+  </div>
+</template>
+
+<style lang="scss">
+.big-spinner {
+  font-size: 15em;
+  line-height: 0;
+  opacity: .6;
+}
+</style>
diff --git a/src/components/error_window/error_window.vue b/src/components/error_window/error_window.vue
new file mode 100644
index 00000000..ddb4ba00
--- /dev/null
+++ b/src/components/error_window/error_window.vue
@@ -0,0 +1,41 @@
+<template>
+  <div class="error-window panel">
+    <div class="panel-heading">
+      <span class="title">
+        {{ $t('general.generic_error') }}
+      </span>
+    </div>
+    <div class="panel-body">
+      <p>
+        {{ $t('general.error_retry') }}
+      </p>
+      <button
+        class="btn"
+        @click="closeAllModals"
+      >
+        {{ $t('general.close') }}
+      </button>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  methods: {
+    closeAllModals () {
+      // TODO make a global hook to close all modals?
+      this.$store.dispatch('closeSettingsModal')
+      this.$emit('resetAsyncComponent')
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.error-window {
+  .btn {
+    margin: .5em;
+    padding: .5em 2em;
+  }
+}
+</style>
diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js
index d38c5751..60d14649 100644
--- a/src/components/settings_modal/settings_modal.js
+++ b/src/components/settings_modal/settings_modal.js
@@ -1,9 +1,20 @@
 import Modal from 'src/components/modal/modal.vue'
+import BigSpinner from 'src/components/big_spinner/big_spinner.vue'
+import ErrorWindow from 'src/components/error_window/error_window.vue'
+import getResettableAsyncComponent from 'src/services/resettable_async_component.js'
 
 const SettingsModal = {
   components: {
     Modal,
-    SettingsModalContent: () => import('./settings_modal_content.vue')
+    SettingsModalContent: getResettableAsyncComponent(
+      () => import('./settings_modal_content.vue'),
+      {
+        loading: BigSpinner,
+        error: ErrorWindow,
+        delay: 0,
+        timeout: 3000
+      }
+    )
   },
   computed: {
     modalActivated () {
diff --git a/src/i18n/en.json b/src/i18n/en.json
index ad9c22bd..e3dc75d7 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -60,6 +60,7 @@
     "submit": "Submit",
     "more": "More",
     "generic_error": "An error occured",
+    "error_retry": "Please try again",
     "optional": "optional",
     "show_more": "Show more",
     "show_less": "Show less",
diff --git a/src/services/resettable_async_component.js b/src/services/resettable_async_component.js
new file mode 100644
index 00000000..517bbd88
--- /dev/null
+++ b/src/services/resettable_async_component.js
@@ -0,0 +1,32 @@
+import Vue from 'vue'
+
+/* By default async components don't have any way to recover, if component is
+ * failed, it is failed forever. This helper tries to remedy that by recreating
+ * async component when retry is requested (by user). You need to emit the
+ * `resetAsyncComponent` event from child to reset the component. Generally,
+ * this should be done from error component but could be done from loading or
+ * actual target component itself if needs to be.
+ */
+function getResettableAsyncComponent (asyncComponent, options) {
+  const asyncComponentFactory = () => () => ({
+    component: asyncComponent(),
+    ...options
+  })
+
+  const observe = Vue.observable({ c: asyncComponentFactory() })
+
+  return {
+    functional: true,
+    render (createElement, { data, children }) {
+      //  emit event resetAsyncComponent to reloading
+      data.on = {}
+      data.on.resetAsyncComponent = () => {
+        observe.c = asyncComponentFactory()
+        // parent.$forceUpdate()
+      }
+      return createElement(observe.c, data, children)
+    }
+  }
+}
+
+export default getResettableAsyncComponent
diff --git a/yarn.lock b/yarn.lock
index 0defefcb..61afa7ca 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2327,6 +2327,7 @@ dateformat@^1.0.6:
 de-indent@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
+  integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
 
 debug@2, debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
   version "2.6.9"
@@ -7903,9 +7904,10 @@ vue-style-loader@^4.0.0, vue-style-loader@^4.0.1:
     hash-sum "^1.0.2"
     loader-utils "^1.0.2"
 
-vue-template-compiler@^2.3.4:
-  version "2.5.21"
-  resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.21.tgz#a57ceb903177e8f643560a8d639a0f8db647054a"
+vue-template-compiler@^2.6.11:
+  version "2.6.11"
+  resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz#c04704ef8f498b153130018993e56309d4698080"
+  integrity sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA==
   dependencies:
     de-indent "^1.0.2"
     he "^1.1.0"
@@ -7914,9 +7916,10 @@ vue-template-es2015-compiler@^1.6.0:
   version "1.9.1"
   resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
 
-vue@^2.5.13:
-  version "2.5.21"
-  resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.21.tgz#3d33dcd03bb813912ce894a8303ab553699c4a85"
+vue@^2.6.11:
+  version "2.6.11"
+  resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5"
+  integrity sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==
 
 vuelidate@^0.7.4:
   version "0.7.4"