From 6511a744a29c760aa2613b0902db55be3b59dfa5 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 7 Oct 2019 23:46:40 +0300
Subject: [PATCH] arbitrary limit with option to overcome it

---
 src/App.scss                                  | 12 +++
 src/_variables.scss                           |  1 +
 src/components/emoji_picker/emoji_picker.js   | 80 ++++++++++++-------
 src/components/emoji_picker/emoji_picker.scss | 24 ++++--
 src/components/emoji_picker/emoji_picker.vue  | 11 +++
 .../style_switcher/style_switcher.js          |  3 +
 .../style_switcher/style_switcher.vue         |  7 ++
 src/i18n/en.json                              |  5 +-
 src/services/style_setter/style_setter.js     |  5 ++
 9 files changed, 113 insertions(+), 35 deletions(-)

diff --git a/src/App.scss b/src/App.scss
index 2190f91a..fe271ce1 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -658,6 +658,18 @@ nav {
       color: var(--alertErrorPanelText, $fallback--text);
     }
   }
+
+  &.warning {
+    background-color: $fallback--alertWarning;
+    background-color: var(--alertWarning, $fallback--alertWarning);
+    color: $fallback--text;
+    color: var(--alertWarningText, $fallback--text);
+
+    .panel-heading & {
+      color: $fallback--text;
+      color: var(--alertWarningPanelText, $fallback--text);
+    }
+  }
 }
 
 .faint {
diff --git a/src/_variables.scss b/src/_variables.scss
index 150e4fb5..e18101f0 100644
--- a/src/_variables.scss
+++ b/src/_variables.scss
@@ -17,6 +17,7 @@ $fallback--cGreen: #0fa00f;
 $fallback--cOrange: orange;
 
 $fallback--alertError: rgba(211,16,20,.5);
+$fallback--alertWarning: rgba(111,111,20,.5);
 
 $fallback--panelRadius: 10px;
 $fallback--checkboxRadius: 2px;
diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js
index 81f7437f..11dbdf44 100644
--- a/src/components/emoji_picker/emoji_picker.js
+++ b/src/components/emoji_picker/emoji_picker.js
@@ -1,5 +1,9 @@
 import { set } from 'vue'
 
+const LOAD_EMOJI_BY = 50
+const LOAD_EMOJI_INTERVAL = 100
+const LOAD_EMOJI_SANE_AMOUNT = 500
+
 const filterByKeyword = (list, keyword = '') => {
   return list.filter(x => x.displayText.includes(keyword))
 }
@@ -21,13 +25,17 @@ const EmojiPicker = {
       groupsScrolledClass: 'scrolled-top',
       keepOpen: false,
       customEmojiBuffer: [],
-      customEmojiInterval: null,
-      customEmojiCounter: 0
+      customEmojiTimeout: null,
+      customEmojiCounter: 0,
+      customEmojiLoadAllConfirmed: false
     }
   },
   components: {
     StickerPicker: () => import('../sticker_picker/sticker_picker.vue')
   },
+  mounted () {
+    this.startEmojiLoad()
+  },
   methods: {
     onEmoji (emoji) {
       const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement
@@ -61,35 +69,39 @@ const EmojiPicker = {
         })
       })
     },
-    restartInterval () {
-      const customEmojis = filterByKeyword(
-        this.$store.state.instance.customEmoji || [],
-        this.keyword
-      )
-      const amount = 50
-      const interval = 100
+    loadEmojiInsane () {
+      this.customEmojiLoadAllConfirmed = true
+      this.continueEmojiLoad()
+    },
+    loadEmoji () {
+      const allLoaded = this.customEmojiBuffer.length === this.filteredEmoji.length
+      const saneLoaded = this.customEmojiBuffer.length >= LOAD_EMOJI_SANE_AMOUNT &&
+            !this.customEmojiLoadAllConfirmed
 
-      if (this.customEmojiInterval) {
-        window.clearInterval(this.customEmojiInterval)
+      if (allLoaded || saneLoaded) {
+        return
       }
-      window.setTimeout(
-        window.clearInterval(this.customEmojiInterval),
-        1000
+
+      this.customEmojiBuffer.push(
+        ...this.filteredEmoji.slice(
+          this.customEmojiCounter,
+          this.customEmojiCounter + LOAD_EMOJI_BY
+        )
       )
+      this.customEmojiTimeout = window.setTimeout(this.loadEmoji, LOAD_EMOJI_INTERVAL)
+      this.customEmojiCounter += LOAD_EMOJI_BY
+    },
+    startEmojiLoad () {
+      if (this.customEmojiTimeout) {
+        window.clearTimeout(this.customEmojiTimeout)
+      }
+
       set(this, 'customEmojiBuffer', [])
       this.customEmojiCounter = 0
-      this.customEmojiInterval = window.setInterval(() => {
-        console.log(this.customEmojiBuffer.length)
-        console.log(customEmojis.length)
-        if (this.customEmojiBuffer.length < customEmojis.length) {
-          this.customEmojiBuffer.push(
-            ...customEmojis.slice(this.customEmojiCounter, this.customEmojiCounter + amount)
-          )
-        } else {
-          window.clearInterval(this.customEmojiInterval)
-        }
-        this.customEmojiCounter += amount
-      }, interval)
+      this.customEmojiTimeout = window.setTimeout(this.loadEmoji, LOAD_EMOJI_INTERVAL)
+    },
+    continueEmojiLoad () {
+      this.customEmojiTimeout = window.setTimeout(this.loadEmoji, LOAD_EMOJI_INTERVAL)
     },
     toggleStickers () {
       this.showingStickers = !this.showingStickers
@@ -107,7 +119,7 @@ const EmojiPicker = {
   watch: {
     keyword () {
       this.scrolledGroup()
-      this.restartInterval()
+      this.startEmojiLoad()
     }
   },
   computed: {
@@ -120,6 +132,20 @@ const EmojiPicker = {
       }
       return 0
     },
+    saneAmount () {
+      // for UI
+      return LOAD_EMOJI_SANE_AMOUNT
+    },
+    filteredEmoji () {
+      return filterByKeyword(
+        this.$store.state.instance.customEmoji || [],
+        this.keyword
+      )
+    },
+    askForSanity () {
+      return this.customEmojiBuffer.length >= LOAD_EMOJI_SANE_AMOUNT &&
+        !this.customEmojiLoadAllConfirmed
+    },
     emojis () {
       const standardEmojis = this.$store.state.instance.emoji || []
       const customEmojis = this.customEmojiBuffer
diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss
index b0ed00e9..6608f393 100644
--- a/src/components/emoji_picker/emoji_picker.scss
+++ b/src/components/emoji_picker/emoji_picker.scss
@@ -6,14 +6,20 @@
   position: absolute;
   right: 0;
   left: 0;
-  height: 320px;
   margin: 0 !important;
   z-index: 1;
 
-  .keep-open {
+  .keep-open,
+  .too-many-emoji {
     padding: 7px;
     line-height: normal;
   }
+
+  .too-many-emoji {
+    display: flex;
+    flex-direction: column;
+  }
+
   .keep-open-label {
     padding: 0 7px;
     display: flex;
@@ -28,7 +34,7 @@
   .content {
     display: flex;
     flex-direction: column;
-    flex: 1 1 0;
+    flex: 1 1 auto;
     min-height: 0px;
   }
 
@@ -36,12 +42,16 @@
     flex-grow: 1;
   }
 
+  .emoji-groups {
+    min-height: 200px;
+  }
+
   .additional-tabs {
     border-left: 1px solid;
     border-left-color: $fallback--icon;
     border-left-color: var(--icon, $fallback--icon);
     padding-left: 7px;
-    flex: 0 0 0;
+    flex: 0 0 auto;
   }
 
   .additional-tabs,
@@ -72,7 +82,7 @@
   }
 
   .sticker-picker {
-    flex: 1 1 0
+    flex: 1 1 auto
   }
 
   .stickers,
@@ -80,7 +90,7 @@
     &-content {
       display: flex;
       flex-direction: column;
-      flex: 1 1 0;
+      flex: 1 1 auto;
       min-height: 0;
 
       &.hidden {
@@ -94,7 +104,7 @@
   .emoji {
     &-search {
       padding: 5px;
-      flex: 0 0 0;
+      flex: 0 0 auto;
 
       input {
         width: 100%;
diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue
index 42f20130..d9daac3e 100644
--- a/src/components/emoji_picker/emoji_picker.vue
+++ b/src/components/emoji_picker/emoji_picker.vue
@@ -92,6 +92,17 @@
             </div>
           </label>
         </div>
+        <div class="too-many-emoji" v-if="askForSanity">
+          <div class="alert warning hint">
+            {{ $t('emoji.load_all_hint', { saneAmount } ) }}
+          </div>
+          <button
+            class="btn btn-default"
+            @click="loadEmojiInsane"
+            >
+            {{ $t('emoji.load_all', { emojiAmount: filteredEmoji.length } ) }}
+          </button>
+        </div>
       </div>
       <div
         v-if="showingStickers"
diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 8c3d4861..ef5704c1 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -73,6 +73,7 @@ export default {
       topBarLinkColorLocal: undefined,
 
       alertErrorColorLocal: undefined,
+      alertWarningColorLocal: undefined,
 
       badgeOpacityLocal: undefined,
       badgeNotificationColorLocal: undefined,
@@ -146,6 +147,7 @@ export default {
         btnText: this.btnTextColorLocal,
 
         alertError: this.alertErrorColorLocal,
+        alertWarning: this.alertWarningColorLocal,
         badgeNotification: this.badgeNotificationColorLocal,
 
         faint: this.faintColorLocal,
@@ -229,6 +231,7 @@ export default {
         topBar: hex2rgb(colors.topBar),
         input: hex2rgb(colors.input),
         alertError: hex2rgb(colors.alertError),
+        alertWarning: hex2rgb(colors.alertWarning),
         badgeNotification: hex2rgb(colors.badgeNotification)
       }
 
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index d24394a4..d32641c2 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -216,6 +216,13 @@
               :fallback="previewTheme.colors.alertError"
             />
             <ContrastRatio :contrast="previewContrast.alertError" />
+            <ColorInput
+              v-model="alertWarningColorLocal"
+              name="alertWarning"
+              :label="$t('settings.style.advanced_colors.alert_warning')"
+              :fallback="previewTheme.colors.alertWarning"
+            />
+            <ContrastRatio :contrast="previewContrast.alertWarning" />
           </div>
           <div class="color-item">
             <h4>{{ $t('settings.style.advanced_colors.badge') }}</h4>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 32c25e3e..c6c98c48 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -114,7 +114,9 @@
     "search_emoji": "Search for an emoji",
     "add_emoji": "Insert emoji",
     "custom": "Custom emoji",
-    "unicode": "Unicode emoji"
+    "unicode": "Unicode emoji",
+    "load_all_hint": "Loaded first {saneAmount} emoji, loading all emoji may cause performance issues.",
+    "load_all": "Loading all {emojiAmount} emoji"
   },
   "interactions": {
     "favs_repeats": "Repeats and Favorites",
@@ -387,6 +389,7 @@
         "_tab_label": "Advanced",
         "alert": "Alert background",
         "alert_error": "Error",
+        "alert_warning": "Warning",
         "badge": "Badge background",
         "badge_notification": "Notification",
         "panel_header": "Panel header",
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 1cf7edc3..eaa495c4 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -215,6 +215,10 @@ const generateColors = (input) => {
   colors.alertErrorText = getTextColor(alphaBlend(colors.alertError, opacity.alert, colors.bg), colors.text)
   colors.alertErrorPanelText = getTextColor(alphaBlend(colors.alertError, opacity.alert, colors.panel), colors.panelText)
 
+  colors.alertWarning = col.alertWarning || Object.assign({}, colors.cOrange)
+  colors.alertWarningText = getTextColor(alphaBlend(colors.alertWarning, opacity.alert, colors.bg), colors.text)
+  colors.alertWarningPanelText = getTextColor(alphaBlend(colors.alertWarning, opacity.alert, colors.panel), colors.panelText)
+
   colors.badgeNotification = col.badgeNotification || Object.assign({}, colors.cRed)
   colors.badgeNotificationText = contrastRatio(colors.badgeNotification).rgb
 
@@ -222,6 +226,7 @@ const generateColors = (input) => {
     if (typeof v === 'undefined') return
     if (k === 'alert') {
       colors.alertError.a = v
+      colors.alertWarning.a = v
       return
     }
     if (k === 'faint') {