Procházet zdrojové kódy

feat: reload adornment in cfg menu & support for multiple adornments

Sv443 před 1 rokem
rodič
revize
695d511095

+ 1 - 0
assets/icons/refresh.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#ffffff" d="M483.077-200q-117.25 0-198.625-81.339-81.375-81.34-81.375-198.539 0-117.199 81.375-198.661Q365.827-760 483.077-760q71.308 0 133.538 33.884 62.231 33.885 100.308 94.577V-760h40v209.231H547.692v-40h148q-31.231-59.846-87.884-94.539Q551.154-720 483.077-720q-100 0-170 70t-70 170q0 100 70 170t170 70q77 0 139-44t87-116h42.462Q725.077-310.154 651-255.077T483.077-200Z"/></svg>

+ 2 - 1
assets/resources.json

@@ -9,10 +9,11 @@
   "icon-experimental": "icons/beaker_small.svg",
   "icon-globe": "icons/globe.svg",
   "icon-help": "icons/help.svg",
-  "icon-image": "icons/image.svg",
   "icon-image_filled": "icons/image_filled.svg",
+  "icon-image": "icons/image.svg",
   "icon-link": "icons/link.svg",
   "icon-lyrics": "icons/lyrics.svg",
+  "icon-reload": "icons/refresh.svg",
   "icon-skip_to": "icons/skip_to.svg",
   "icon-spinner": "icons/spinner.svg",
   "img-logo": "images/logo/logo_48.png",

+ 65 - 10
assets/translations/README.md

@@ -20,15 +20,15 @@ To submit or edit a translation, please follow [this guide](../../contributing.m
 ### Translation progress:
 | &nbsp; | Locale | Translated keys | Based on |
 | :----: | ------ | --------------- | :------: |
-| ─ | [`en_US`](./en_US.json) | 203 (default locale) |  |
-| ✅ | [`de_DE`](./de_DE.json) | `203/203` (100%) | ─ |
-| ─ | [`en_UK`](./en_UK.json) | `203/203` (100%) | `en_US` |
-| ✅ | [`es_ES`](./es_ES.json) | `203/203` (100%) | ─ |
-| ✅ | [`fr_FR`](./fr_FR.json) | `203/203` (100%) | ─ |
-| ✅ | [`hi_IN`](./hi_IN.json) | `203/203` (100%) | ─ |
-| ✅ | [`ja_JA`](./ja_JA.json) | `203/203` (100%) | ─ |
-| ✅ | [`pt_BR`](./pt_BR.json) | `203/203` (100%) | ─ |
-| ✅ | [`zh_CN`](./zh_CN.json) | `203/203` (100%) | ─ |
+| ─ | [`en_US`](./en_US.json) | 204 (default locale) |  |
+| ⚠ | [`de_DE`](./de_DE.json) | `203/204` (99.5%) | ─ |
+| ─ | [`en_UK`](./en_UK.json) | `204/204` (100%) | `en_US` |
+| ⚠ | [`es_ES`](./es_ES.json) | `203/204` (99.5%) | ─ |
+| ⚠ | [`fr_FR`](./fr_FR.json) | `203/204` (99.5%) | ─ |
+| ⚠ | [`hi_IN`](./hi_IN.json) | `203/204` (99.5%) | ─ |
+| ⚠ | [`ja_JA`](./ja_JA.json) | `203/204` (99.5%) | ─ |
+| ⚠ | [`pt_BR`](./pt_BR.json) | `203/204` (99.5%) | ─ |
+| ⚠ | [`zh_CN`](./zh_CN.json) | `203/204` (99.5%) | ─ |
 
 <sub>
 ✅ - Fully translated
@@ -48,4 +48,59 @@ This means to figure out which keys are untranslated, you will need to manually
 <br>
 
 ### Missing keys:
-No missing keys
+
+<details><summary><code>de_DE</code> - 1 missing key <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `feature_requires_reload` | `Changing this feature requires a page reload` |
+
+<br></details>
+
+<details><summary><code>es_ES</code> - 1 missing key <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `feature_requires_reload` | `Changing this feature requires a page reload` |
+
+<br></details>
+
+<details><summary><code>fr_FR</code> - 1 missing key <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `feature_requires_reload` | `Changing this feature requires a page reload` |
+
+<br></details>
+
+<details><summary><code>hi_IN</code> - 1 missing key <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `feature_requires_reload` | `Changing this feature requires a page reload` |
+
+<br></details>
+
+<details><summary><code>ja_JA</code> - 1 missing key <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `feature_requires_reload` | `Changing this feature requires a page reload` |
+
+<br></details>
+
+<details><summary><code>pt_BR</code> - 1 missing key <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `feature_requires_reload` | `Changing this feature requires a page reload` |
+
+<br></details>
+
+<details><summary><code>zh_CN</code> - 1 missing key <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `feature_requires_reload` | `Changing this feature requires a page reload` |
+
+<br></details>

+ 1 - 0
assets/translations/en_US.json

@@ -10,6 +10,7 @@
     "reload_hint": "Your changes were saved, but you need to reload the page to apply them",
     "reload_now": "Reload now",
     "reload_tooltip": "Reload the page",
+    "feature_requires_reload": "Changing this feature requires a page reload",
     "version_tooltip": "Version %1 (build %2) - click to open the changelog",
     "export": "Export",
     "export_hint": "Copy the following text to export your configuration.\nWarning: it may contain sensitive data.",

+ 5 - 0
src/dialogs/dialogs.css

@@ -3,6 +3,7 @@
   --bytm-advanced-mode-color: #c5a73b;
   --bytm-experimental-col: #d07ff0;
   --bytm-warning-col: #ff5233;
+  --bytm-reload-col: #42b045;
 }
 
 /* TODO(v1.2): leave only dialog */
@@ -95,6 +96,10 @@
   fill: var(--bytm-warning-col, #fff);
 }
 
+.bytm-reload-icon svg path {
+  fill: var(--bytm-reload-col, #fff);
+}
+
 /* #SECTION welcome dialog */
 
 #bytm-welcome-menu-title-wrapper {

+ 51 - 9
src/features/index.ts

@@ -30,7 +30,22 @@ const localeOptions = Object.entries(langMapping).reduce((a, [locale, { name }])
   .sort((a, b) => a.label.localeCompare(b.label));
 
 const getAdornHtml = async (className: string, title: string, resource: ResourceKey, extraParams?: string) =>
-  `<span class="${className}" title="${title}" aria-label="${title}"${extraParams ? " " + extraParams : ""}>${await resourceToHTMLString(resource) ?? ""}</span>`;
+  `<span class="${className} bytm-adorn-icon" title="${title}" aria-label="${title}"${extraParams ? " " + extraParams : ""}>${await resourceToHTMLString(resource) ?? ""}</span>`;
+
+const combineAdornments = (
+  adornments: Array<(
+    | (() => Promise<string | undefined>)
+    | Promise<string | undefined>
+  )>
+) =>
+  new Promise<string>(async (resolve) => {
+    const html = [] as string[];
+    for(const adornment of adornments) {
+      const val = typeof adornment === "function" ? await adornment() : await adornment;
+      val && html.push(val);
+    }
+    resolve(html.join(""));
+  });
 
 /** Decoration elements that can be added next to the label */
 const adornments = {
@@ -38,7 +53,8 @@ const adornments = {
   experimental: async () => getAdornHtml("bytm-experimental-icon", t("experimental_feature"), "icon-experimental"),
   globe: async () => await resourceToHTMLString("icon-globe") ?? "",
   warning: async (title: string) => getAdornHtml("bytm-warning-icon", title, "icon-error", "role=\"alert\""),
-};
+  reloadRequired: async () => getFeatures().advancedMode ? getAdornHtml("bytm-reload-icon", t("feature_requires_reload"), "icon-reload") : undefined,
+} satisfies Record<string, (...args: any[]) => Promise<string | undefined>>;
 
 /** Common options for config items of type "select" */
 const options = {
@@ -89,32 +105,38 @@ export const featInfo = {
     type: "toggle",
     category: "layout",
     default: true,
+    textAdornment: adornments.reloadRequired,
   },
   removeShareTrackingParam: {
     type: "toggle",
     category: "layout",
     default: true,
+    textAdornment: adornments.reloadRequired,
   },
   removeShareTrackingParamSites: {
     type: "select",
     category: "layout",
     options: options.siteSelection,
     default: "all",
+    textAdornment: adornments.reloadRequired,
   },
   fixSpacing: {
     type: "toggle",
     category: "layout",
     default: true,
+    textAdornment: adornments.reloadRequired,
   },
   scrollToActiveSongBtn: {
     type: "toggle",
     category: "layout",
     default: true,
+    textAdornment: adornments.reloadRequired,
   },
   removeUpgradeTab: {
     type: "toggle",
     category: "layout",
     default: true,
+    textAdornment: adornments.reloadRequired,
   },
   thumbnailOverlayBehavior: {
     type: "select",
@@ -133,13 +155,14 @@ export const featInfo = {
     type: "toggle",
     category: "layout",
     default: true,
+    textAdornment: adornments.reloadRequired,
   },
   thumbnailOverlayShowIndicator: {
     type: "toggle",
     category: "layout",
     default: true,
     advanced: true,
-    textAdornment: adornments.advanced,
+    textAdornment: () => combineAdornments([adornments.advanced, adornments.reloadRequired]),
   },
   thumbnailOverlayImageFit: {
     type: "select",
@@ -151,7 +174,7 @@ export const featInfo = {
     ],
     default: "cover",
     advanced: true,
-    textAdornment: adornments.advanced,
+    textAdornment: () => combineAdornments([adornments.advanced, adornments.reloadRequired]),
   },
   hideCursorOnIdle: {
     type: "toggle",
@@ -179,6 +202,7 @@ export const featInfo = {
     type: "toggle",
     category: "volume",
     default: true,
+    textAdornment: adornments.reloadRequired,
   },
   volumeSliderSize: {
     type: "number",
@@ -188,6 +212,7 @@ export const featInfo = {
     step: 5,
     default: 150,
     unit: "px",
+    textAdornment: adornments.reloadRequired,
   },
   volumeSliderStep: {
     type: "slider",
@@ -196,6 +221,7 @@ export const featInfo = {
     max: 25,
     default: 2,
     unit: "%",
+    textAdornment: adornments.reloadRequired,
   },
   volumeSliderScrollStep: {
     type: "slider",
@@ -204,6 +230,7 @@ export const featInfo = {
     max: 25,
     default: 10,
     unit: "%",
+    textAdornment: adornments.reloadRequired,
   },
   volumeSharedBetweenTabs: {
     type: "toggle",
@@ -217,7 +244,9 @@ export const featInfo = {
     type: "toggle",
     category: "volume",
     default: false,
-    textAdornment: () => getFeatures().volumeSharedBetweenTabs ? adornments.warning(t("feature_warning_setInitialTabVolume_volumeSharedBetweenTabs_incompatible").replace(/"/g, "'")) : undefined,
+    textAdornment: () => getFeatures().volumeSharedBetweenTabs
+      ? combineAdornments([adornments.warning(t("feature_warning_setInitialTabVolume_volumeSharedBetweenTabs_incompatible").replace(/"/g, "'")), adornments.reloadRequired])
+      : undefined,
   },
   initialTabVolumeLevel: {
     type: "slider",
@@ -227,7 +256,9 @@ export const featInfo = {
     step: 1,
     default: 100,
     unit: "%",
-    textAdornment: () => getFeatures().volumeSharedBetweenTabs ? adornments.warning(t("feature_warning_setInitialTabVolume_volumeSharedBetweenTabs_incompatible").replace(/"/g, "'")) : undefined,
+    textAdornment: () => getFeatures().volumeSharedBetweenTabs
+      ? combineAdornments([adornments.warning(t("feature_warning_setInitialTabVolume_volumeSharedBetweenTabs_incompatible").replace(/"/g, "'")), adornments.reloadRequired])
+      : undefined,
     reloadRequired: false,
     enable: noop,
   },
@@ -237,11 +268,13 @@ export const featInfo = {
     type: "toggle",
     category: "songLists",
     default: true,
+    textAdornment: adornments.reloadRequired,
   },
   deleteFromQueueButton: {
     type: "toggle",
     category: "songLists",
     default: true,
+    textAdornment: adornments.reloadRequired,
   },
   listButtonsPlacement: {
     type: "select",
@@ -251,6 +284,7 @@ export const featInfo = {
       { value: "everywhere", label: t("list_button_placement_everywhere") },
     ],
     default: "everywhere",
+    textAdornment: adornments.reloadRequired,
   },
 
   //#region behavior
@@ -258,6 +292,7 @@ export const featInfo = {
     type: "toggle",
     category: "behavior",
     default: false,
+    textAdornment: adornments.reloadRequired,
   },
   closeToastsTimeout: {
     type: "number",
@@ -274,13 +309,15 @@ export const featInfo = {
     type: "toggle",
     category: "behavior",
     default: true,
-    helpText: () => tp("feature_helptext_rememberSongTime", getFeatures().rememberSongTimeMinPlayTime, getFeatures().rememberSongTimeMinPlayTime)
+    helpText: () => tp("feature_helptext_rememberSongTime", getFeatures().rememberSongTimeMinPlayTime, getFeatures().rememberSongTimeMinPlayTime),
+    textAdornment: adornments.reloadRequired,
   },
   rememberSongTimeSites: {
     type: "select",
     category: "behavior",
     options: options.siteSelection,
     default: "ytm",
+    textAdornment: adornments.reloadRequired,
   },
   rememberSongTimeDuration: {
     type: "number",
@@ -363,6 +400,7 @@ export const featInfo = {
     type: "toggle",
     category: "input",
     default: true,
+    textAdornment: adornments.reloadRequired,
   },
   numKeysSkipToTime: {
     type: "toggle",
@@ -455,12 +493,13 @@ export const featInfo = {
     category: "general",
     options: localeOptions,
     default: getPreferredLocale(),
-    textAdornment: adornments.globe,
+    textAdornment: () => combineAdornments([adornments.globe, adornments.reloadRequired]),
   },
   versionCheck: {
     type: "toggle",
     category: "general",
     default: true,
+    textAdornment: adornments.reloadRequired,
   },
   checkVersionNow: {
     type: "button",
@@ -475,12 +514,15 @@ export const featInfo = {
       { value: 1, label: t("log_level_info") },
     ],
     default: 1,
+    textAdornment: adornments.reloadRequired,
   },
   advancedMode: {
     type: "toggle",
     category: "general",
     default: mode === "development",
-    textAdornment: () => getFeatures().advancedMode ? adornments.advanced() : undefined,
+    textAdornment: () => getFeatures().advancedMode
+      ? combineAdornments([adornments.advanced, adornments.reloadRequired])
+      : undefined,
   },
 } as const satisfies FeatureInfo;
 

+ 5 - 2
src/menu/menu_old.ts

@@ -327,8 +327,11 @@ async function addCfgMenu() {
         const featLeftSideElem = document.createElement("div");
         featLeftSideElem.classList.add("bytm-ftitem-leftside");
         if(getFeatures().advancedMode) {
-          const valFmtd = fmtVal(ftDefault);
-          featLeftSideElem.title = `${featKey}${ftInfo.advanced ? " (advanced)" : ""} - Default: ${valFmtd.length === 0 ? "(empty)" : valFmtd}`;
+          const defVal = fmtVal(ftDefault);
+          // @ts-ignore
+          const rel = ftInfo.reloadRequired === false ? "" : " (reload required)";
+          const adv = ftInfo.advanced ? " (advanced feature)" : "";
+          featLeftSideElem.title = `${featKey}${rel}${adv} - default value: ${defVal.length === 0 ? "(undefined)" : defVal}`;
         }
 
         const textElem = document.createElement("span");

+ 1 - 4
src/types.ts

@@ -302,10 +302,7 @@ export type FeatureInfo = Record<
     helpText?: string | (() => string);
     /** Whether the value should be hidden in the config menu and from plugins */
     valueHidden?: boolean;
-    /**
-     * HTML string that is appended to the end of a feature's text description
-     * @deprecated TODO:FIXME: To be removed or changed in the big menu rework
-     */
+    /** HTML string that is appended to the end of a feature's text description */
     textAdornment?: () => (Promise<string | undefined> | string | undefined);
 
     /** Whether to only show this feature when advanced mode is activated (default false) */