Parcourir la source

feat: customizable badge colors (#1302)

tophf il y a 4 ans
Parent
commit
46c0ef9149

+ 9 - 0
src/_locales/en/messages.yml

@@ -269,6 +269,9 @@ labelAutoUpdate:
 labelBadge:
   description: Label for option to show number on badge.
   message: 'Display on badge: '
+labelBadgeColors:
+  description: Label for option group to set badge colors.
+  message: 'Badge colors: '
 labelBadgeNone:
   description: Option to display nothing on badge.
   message: none
@@ -673,6 +676,12 @@ sideMenuInstalled:
 sideMenuSettings:
   description: 'Side menu: Settings'
   message: Settings
+titleBadgeColor:
+  description: Tooltip for option to set badge color.
+  message: 'Normal badge color'
+titleBadgeColorBlocked:
+  description: Tooltip for option to set badge color of non-injectable tabs.
+  message: 'Badge color when the site is non-injectable (blacklisted or unsupported)'
 titleScriptUpdated:
   description: Notification title for script updates.
   message: Update

+ 39 - 10
src/background/utils/icon.js

@@ -42,9 +42,21 @@ const browserAction = (() => {
 })();
 
 const badges = {};
+const KEY_IS_APPLIED = 'isApplied';
+const KEY_SHOW_BADGE = 'showBadge';
+const KEY_BADGE_COLOR = 'badgeColor';
+const KEY_BADGE_COLOR_BLOCKED = 'badgeColorBlocked';
+/** @type boolean */
 let isApplied;
+/** @type VMBadgeMode */
 let showBadge;
+/** @type string */
+let badgeColor;
+/** @type string */
+let badgeColorBlocked;
+/** @type string */
 let titleBlacklisted;
+/** @type string */
 let titleNoninjectable;
 
 // We'll cache the icon data in Chrome as it doesn't cache the data and takes up to 40ms
@@ -52,23 +64,34 @@ let titleNoninjectable;
 const iconCache = ua.isChrome && {};
 
 hookOptions((changes) => {
-  if ('isApplied' in changes) {
-    isApplied = changes.isApplied;
+  let v;
+  if ((v = changes[KEY_IS_APPLIED]) != null) {
+    isApplied = v;
     setIcon(); // change the default icon
     forEachTab(setIcon); // change the current tabs' icons
   }
-  if ('showBadge' in changes) {
-    showBadge = changes.showBadge;
+  if ((v = changes[KEY_SHOW_BADGE]) != null) {
+    showBadge = v;
     forEachTab(updateBadge);
   }
+  if ((v = changes[KEY_BADGE_COLOR])) {
+    badgeColor = v;
+    forEachTab(updateBadgeColor);
+  }
+  if ((v = changes[KEY_BADGE_COLOR_BLOCKED])) {
+    badgeColorBlocked = v;
+    forEachTab(updateBadgeColor);
+  }
   if ('blacklist' in changes) {
     forEachTab(updateState);
   }
 });
 
 postInitialize.push(() => {
-  isApplied = getOption('isApplied');
-  showBadge = getOption('showBadge');
+  isApplied = getOption(KEY_IS_APPLIED);
+  showBadge = getOption(KEY_SHOW_BADGE);
+  badgeColor = getOption(KEY_BADGE_COLOR);
+  badgeColorBlocked = getOption(KEY_BADGE_COLOR_BLOCKED);
   titleBlacklisted = i18n('failureReasonBlacklisted');
   titleNoninjectable = i18n('failureReasonNoninjectable');
   forEachTab(updateState);
@@ -110,10 +133,7 @@ export function setBadge(ids, { tab, frameId }) {
     });
     data.unique = Object.keys(data.idMap).length;
   }
-  browserAction.setBadgeBackgroundColor({
-    color: data.blocked ? '#888' : '#808',
-    tabId,
-  });
+  updateBadgeColor(tab, data);
   updateBadge(tab, data);
 }
 
@@ -126,6 +146,15 @@ function updateBadge(tab, data = badges[tab.id]) {
   }
 }
 
+function updateBadgeColor(tab, data = badges[tab.id]) {
+  if (data) {
+    browserAction.setBadgeBackgroundColor({
+      color: data.blocked ? badgeColorBlocked : badgeColor,
+      tabId: tab.id,
+    });
+  }
+}
+
 // Chrome 79+ uses pendingUrl while the tab connects to the newly navigated URL
 // https://groups.google.com/a/chromium.org/forum/#!topic/chromium-extensions/5zu_PT0arls
 function updateState(tab, url = tab.pendingUrl || tab.url) {

+ 4 - 1
src/common/options-defaults.js

@@ -6,8 +6,11 @@ export default {
   // ignoreGrant: false,
   lastUpdate: 0,
   lastModified: 0,
-  /** @type 'unique' | 'total' | '' */
+  /** @typedef {'unique' | 'total' | ''} VMBadgeMode */
+  /** @type VMBadgeMode */
   showBadge: 'unique',
+  badgeColor: '#880088',
+  badgeColorBlocked: '#888888',
   exportValues: true,
   expose: { // use percent-encoding for '.'
     'greasyfork%2Eorg': true,

+ 30 - 3
src/options/views/tab-settings/index.vue

@@ -26,7 +26,7 @@
         <setting-check name="filtersPopup.enabledFirst" :label="i18n('optionPopupEnabledFirst')" />
         <setting-check name="filtersPopup.hideDisabled" :label="i18n('optionPopupHideDisabled')" />
       </div>
-      <div>
+      <div class="mr-2c">
         <label>
           <span v-text="i18n('labelBadge')"></span>
           <select v-model="settings.showBadge">
@@ -35,6 +35,14 @@
             <option value="total" v-text="i18n('labelBadgeTotal')" />
           </select>
         </label>
+        <label>
+          <span v-text="i18n('labelBadgeColors')"/>
+          <tooltip v-for="(title, name) in badgeColors" :key="name" :content="title">
+            <input type="color" v-model="settings[name]">
+          </tooltip>
+          <button v-text="i18n('buttonReset')" v-show="isCustomBadgeColor" class="ml-1"
+                  @click="onResetBadgeColors"/>
+        </label>
       </div>
     </section>
     <section class="mb-1c">
@@ -98,14 +106,16 @@
 
 <script>
 import Tooltip from 'vueleton/lib/tooltip/bundle';
-import { debounce } from '#/common';
+import { debounce, i18n } from '#/common';
 import {
   INJECT_AUTO,
   INJECT_PAGE,
   INJECT_CONTENT,
 } from '#/common/consts';
 import SettingCheck from '#/common/ui/setting-check';
+import { forEachKey } from '#/common/object';
 import options from '#/common/options';
+import optionsDefaults from '#/common/options-defaults';
 import hookSetting from '#/common/hook-setting';
 import Icon from '#/common/ui/icon';
 import LocaleGroup from '#/common/ui/locale-group';
@@ -123,6 +133,10 @@ const injectIntoOptions = [
   INJECT_PAGE,
   INJECT_CONTENT,
 ];
+const badgeColors = {
+  badgeColor: i18n('titleBadgeColor'),
+  badgeColorBlocked: i18n('titleBadgeColorBlocked'),
+};
 const items = [
   {
     name: 'showBadge',
@@ -138,13 +152,17 @@ const items = [
   {
     name: 'defaultInjectInto',
     normalize(value) {
-      return injectIntoOptions.includes(value) ? value : 'auto';
+      return injectIntoOptions.includes(value) ? value : optionsDefaults.defaultInjectInto;
     },
   },
   {
     name: 'filtersPopup.sort',
     normalize: value => value === 'exec' && value || 'alpha',
   },
+  ...['badgeColor', 'badgeColorBlocked'].map(name => ({
+    name,
+    normalize: value => (/^#[0-9a-f]{6}$/i.test(value) ? value : optionsDefaults[name]),
+  })),
 ];
 const settings = {};
 items.forEach(({ name }) => {
@@ -170,6 +188,7 @@ export default {
       showAdvanced: false,
       expose: null,
       settings,
+      badgeColors,
       injectIntoOptions,
     };
   },
@@ -177,6 +196,9 @@ export default {
     editorWindowHint() {
       return global.chrome.windows?.onBoundsChanged ? null : this.i18n('optionEditorWindowHint');
     },
+    isCustomBadgeColor() {
+      return Object.keys(badgeColors).some(name => settings[name] !== optionsDefaults[name]);
+    },
   },
   methods: {
     getUpdater({ name, normalize }) {
@@ -186,6 +208,11 @@ export default {
         if (value !== oldValue) options.set(name, value);
       };
     },
+    onResetBadgeColors() {
+      badgeColors::forEachKey(name => {
+        settings[name] = optionsDefaults[name];
+      });
+    },
   },
   created() {
     this.revokers = [];