From 1293bec77e5137acf64d3536c286f8ba3df284f4 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Thu, 2 Jul 2020 10:40:41 +0300
Subject: [PATCH] change storage error one-off into a global notice system

---
 src/App.js                                    | 10 +--
 src/App.scss                                  |  9 ---
 src/App.vue                                   | 11 +--
 .../global_notice_list/global_notice_list.js  | 15 ++++
 .../global_notice_list/global_notice_list.vue | 77 +++++++++++++++++++
 src/main.js                                   | 16 ++--
 src/modules/interface.js                      | 33 ++++++--
 src/services/theme_data/pleromafe.js          | 36 ++++++++-
 8 files changed, 167 insertions(+), 40 deletions(-)
 create mode 100644 src/components/global_notice_list/global_notice_list.js
 create mode 100644 src/components/global_notice_list/global_notice_list.vue

diff --git a/src/App.js b/src/App.js
index da66fe214..92c4e2f58 100644
--- a/src/App.js
+++ b/src/App.js
@@ -13,6 +13,7 @@ import MobilePostStatusButton from './components/mobile_post_status_button/mobil
 import MobileNav from './components/mobile_nav/mobile_nav.vue'
 import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
 import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
+import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
 import { windowWidth } from './services/window_utils/window_utils'
 
 export default {
@@ -32,7 +33,8 @@ export default {
     MobileNav,
     SettingsModal,
     UserReportingModal,
-    PostStatusModal
+    PostStatusModal,
+    GlobalNoticeList
   },
   data: () => ({
     mobileActivePanel: 'timeline',
@@ -107,9 +109,6 @@ export default {
       return {
         'order': this.$store.state.instance.sidebarRight ? 99 : 0
       }
-    },
-    showStorageError () {
-      return this.$store.state.interface.storageError === 'show'
     }
   },
   methods: {
@@ -132,9 +131,6 @@ export default {
       if (changed) {
         this.$store.dispatch('setMobileLayout', mobileLayout)
       }
-    },
-    hideStorageError () {
-      this.$store.dispatch('setStorageError', 'hide')
     }
   }
 }
diff --git a/src/App.scss b/src/App.scss
index db447f1c3..f2972eda5 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -806,15 +806,6 @@ nav {
   }
 }
 
-.storage-error-notice {
-  text-align: center;
-  i {
-    cursor: pointer;
-    color: $fallback--text;
-    color: var(--alertErrorText, $fallback--text);
-  }
-}
-
 .button-icon {
   font-size: 1.2em;
 }
diff --git a/src/App.vue b/src/App.vue
index 23991eacc..03b632ecc 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -101,16 +101,6 @@
         </div>
       </div>
       <div class="main">
-        <div
-          v-if="showStorageError"
-          class="alert error storage-error-notice"
-        >
-          {{ $t("errors.storage_unavailable") }}
-          <i
-            class="icon-cancel"
-            @click="hideStorageError"
-          />
-        </div>
         <div
           v-if="!currentUser"
           class="login-hint panel panel-default"
@@ -138,6 +128,7 @@
     <PostStatusModal />
     <SettingsModal />
     <portal-target name="modal" />
+    <GlobalNoticeList />
   </div>
 </template>
 
diff --git a/src/components/global_notice_list/global_notice_list.js b/src/components/global_notice_list/global_notice_list.js
new file mode 100644
index 000000000..3af29c234
--- /dev/null
+++ b/src/components/global_notice_list/global_notice_list.js
@@ -0,0 +1,15 @@
+
+const GlobalNoticeList = {
+  computed: {
+    notices () {
+      return this.$store.state.interface.globalNotices
+    }
+  },
+  methods: {
+    closeNotice (notice) {
+      this.$store.dispatch('removeGlobalNotice', notice)
+    }
+  }
+}
+
+export default GlobalNoticeList
diff --git a/src/components/global_notice_list/global_notice_list.vue b/src/components/global_notice_list/global_notice_list.vue
new file mode 100644
index 000000000..0e4285ccf
--- /dev/null
+++ b/src/components/global_notice_list/global_notice_list.vue
@@ -0,0 +1,77 @@
+<template>
+  <div class="global-notice-list">
+    <div
+      v-for="(notice, index) in notices"
+      :key="index"
+      class="alert global-notice"
+      :class="{ ['global-' + notice.level]: true }"
+    >
+      <div class="notice-message">
+        {{ $t(notice.messageKey, notice.messageArgs) }}
+      </div>
+      <i
+        class="button-icon icon-cancel"
+        @click="closeNotice(notice)"
+      />
+    </div>
+  </div>
+</template>
+
+<script src="./global_notice_list.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.global-notice-list {
+  position: fixed;
+  top: 50px;
+  width: 100%;
+  pointer-events: none;
+  z-index: 1001;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+
+  .global-notice {
+    pointer-events: auto;
+    text-align: center;
+    width: 40em;
+    max-width: calc(100% - 3em);
+    display: flex;
+    padding-left: 1.5em;
+    line-height: 2em;
+    .notice-message {
+      flex: 1 1 100%;
+    }
+    i {
+      flex: 0 0;
+      width: 1.5em;
+      cursor: pointer;
+    }
+  }
+
+  .global-error {
+    background-color: var(--alertPopupError, $fallback--cRed);
+    color: var(--alertPopupErrorText, $fallback--text);
+    i {
+      color: var(--alertPopupErrorText, $fallback--text);
+    }
+  }
+
+  .global-warning {
+    background-color: var(--alertPopupWarning, $fallback--cOrange);
+    color: var(--alertPopupWarningText, $fallback--text);
+    i {
+      color: var(--alertPopupWarningText, $fallback--text);
+    }
+  }
+
+  .global-info {
+    background-color: var(--alertPopupNeutral, $fallback--fg);
+    color: var(--alertPopupNeutralText, $fallback--text);
+    i {
+      color: var(--alertPopupNeutralText, $fallback--text);
+    }
+  }
+}
+</style>
diff --git a/src/main.js b/src/main.js
index a7294ea03..5bddc76e4 100644
--- a/src/main.js
+++ b/src/main.js
@@ -62,14 +62,14 @@ const persistedStateOptions = {
 };
 
 (async () => {
-  let persistedState
-  let storageError = 'none'
+  let storageError = false
+  const plugins = [pushNotifications]
   try {
-    persistedState = await createPersistedState(persistedStateOptions)
+    const persistedState = await createPersistedState(persistedStateOptions)
+    plugins.push(persistedState)
   } catch (e) {
     console.error(e)
-    storageError = 'show'
-    persistedState = _ => _
+    storageError = true
   }
   const store = new Vuex.Store({
     modules: {
@@ -93,11 +93,13 @@ const persistedStateOptions = {
       polls: pollsModule,
       postStatus: postStatusModule
     },
-    plugins: [persistedState, pushNotifications],
+    plugins,
     strict: false // Socket modifies itself, let's ignore this for now.
     // strict: process.env.NODE_ENV !== 'production'
   })
-  store.dispatch('setStorageError', storageError)
+  if (storageError) {
+    store.dispatch('pushGlobalNotice', { messageKey: 'errors.storage_unavailable', level: 'error' })
+  }
   afterStoreSetup({ store, i18n })
 })()
 
diff --git a/src/modules/interface.js b/src/modules/interface.js
index 4b5b5b5d9..338ef6515 100644
--- a/src/modules/interface.js
+++ b/src/modules/interface.js
@@ -8,14 +8,14 @@ const defaultState = {
     noticeClearTimeout: null,
     notificationPermission: null
   },
-  storageError: 'none',
   browserSupport: {
     cssFilter: window.CSS && window.CSS.supports && (
       window.CSS.supports('filter', 'drop-shadow(0 0)') ||
       window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)')
     )
   },
-  mobileLayout: false
+  mobileLayout: false,
+  globalNotices: []
 }
 
 const interfaceMod = {
@@ -60,8 +60,11 @@ const interfaceMod = {
         state.settingsModalLoaded = true
       }
     },
-    setStorageError (state, value) {
-      state.storageError = value
+    pushGlobalNotice (state, notice) {
+      state.globalNotices.push(notice)
+    },
+    removeGlobalNotice (state, notice) {
+      state.globalNotices = state.globalNotices.filter(n => n !== notice)
     }
   },
   actions: {
@@ -86,8 +89,26 @@ const interfaceMod = {
     togglePeekSettingsModal ({ commit }) {
       commit('togglePeekSettingsModal')
     },
-    setStorageError ({ commit }, value) {
-      commit('setStorageError', value)
+    pushGlobalNotice (
+      { commit, dispatch },
+      {
+        messageKey,
+        messageArgs = {},
+        level = 'error',
+        timeout = 0
+      }) {
+      const notice = {
+        messageKey,
+        messageArgs,
+        level
+      }
+      if (timeout) {
+        setTimeout(() => dispatch('removeGlobalNotice', notice), timeout)
+      }
+      commit('pushGlobalNotice', notice)
+    },
+    removeGlobalNotice ({ commit }, notice) {
+      commit('removeGlobalNotice', notice)
     }
   }
 }
diff --git a/src/services/theme_data/pleromafe.js b/src/services/theme_data/pleromafe.js
index b577cfab8..83c878ed9 100644
--- a/src/services/theme_data/pleromafe.js
+++ b/src/services/theme_data/pleromafe.js
@@ -34,7 +34,8 @@ export const DEFAULT_OPACITY = {
   alert: 0.5,
   input: 0.5,
   faint: 0.5,
-  underlay: 0.15
+  underlay: 0.15,
+  alertPopup: 0.85
 }
 
 /**  SUBJECT TO CHANGE IN THE FUTURE, this is all beta
@@ -627,6 +628,39 @@ export const SLOT_INHERITANCE = {
     textColor: true
   },
 
+  alertPopupError: {
+    depends: ['alertError'],
+    opacity: 'alertPopup'
+  },
+  alertPopupErrorText: {
+    depends: ['alertErrorText'],
+    layer: 'popover',
+    variant: 'alertPopupError',
+    textColor: true
+  },
+
+  alertPopupWarning: {
+    depends: ['alertWarning'],
+    opacity: 'alertPopup'
+  },
+  alertPopupWarningText: {
+    depends: ['alertWarningText'],
+    layer: 'popover',
+    variant: 'alertPopupWarning',
+    textColor: true
+  },
+
+  alertPopupNeutral: {
+    depends: ['alertNeutral'],
+    opacity: 'alertPopup'
+  },
+  alertPopupNeutralText: {
+    depends: ['alertNeutralText'],
+    layer: 'popover',
+    variant: 'alertPopupNeutral',
+    textColor: true
+  },
+
   badgeNotification: '--cRed',
   badgeNotificationText: {
     depends: ['text', 'badgeNotification'],
-- 
GitLab