瀏覽代碼

feat: add circular button component to interface

Sv443 11 月之前
父節點
當前提交
0311992077
共有 5 個文件被更改,包括 85 次插入33 次删除
  1. 53 18
      contributing.md
  2. 20 12
      src/components/circularButton.ts
  3. 9 1
      src/components/hotkeyInput.ts
  4. 1 1
      src/components/index.ts
  5. 2 1
      src/interface.ts

+ 53 - 18
contributing.md

@@ -5,20 +5,16 @@ If you have any questions or need help, feel free to contact me, [see my homepag
 
 <br>
 
-- [BetterYTM - Contributing Guide](#betterytm---contributing-guide)
-  - [Submitting translations:](#submitting-translations)
-    - [Adding translations for a new language:](#adding-translations-for-a-new-language)
-    - [Editing an existing translation:](#editing-an-existing-translation)
-  - [Setting up the project for local development:](#setting-up-the-project-for-local-development)
-    - [Requirements:](#requirements)
-    - [These are the CLI commands available after setting up the project:](#these-are-the-cli-commands-available-after-setting-up-the-project)
-    - [Extras:](#extras)
-  - [Developing a plugin that interfaces with BetterYTM:](#developing-a-plugin-that-interfaces-with-betterytm)
-    - [Example:](#example)
-    - [Basic format:](#basic-format)
-    - [Practical Example:](#practical-example)
-  - [Shimming for TypeScript without errors \& with autocomplete:](#shimming-for-typescript-without-errors--with-autocomplete)
-  - [Global functions:](#global-functions)
+- [Submitting translations](#submitting-translations)
+  - [Adding translations for a new language](#adding-translations-for-a-new-language)
+  - [Editing an existing translation](#editing-an-existing-translation)
+- [Setting up the project for local development](#setting-up-the-project-for-local-development)
+  - [Requirements](#requirements)
+  - [CLI commands](#these-are-the-cli-commands-available-after-setting-up-the-project)
+  - [Extras](#extras)
+- [Developing a plugin that interfaces with BetterYTM](#developing-a-plugin-that-interfaces-with-betterytm)
+  - [Shimming for TypeScript without errors & with autocomplete](#shimming-for-typescript-without-errors--with-autocomplete)
+  - [Global functions and classes on the plugin interface](#global-functions-and-classes)
 
 <br><br>
 
@@ -258,8 +254,8 @@ An easy way to do this might be to include BetterYTM as a Git submodule, as long
 
 <!-- #region global interface functions -->
 
-### Global functions:
-These are the global functions that are exposed by BetterYTM through the `unsafeWindow.BYTM` object.  
+### Global functions and classes:
+These are the global functions and classes that are exposed by BetterYTM through the `unsafeWindow.BYTM` object.  
 The usage and example blocks on each are written in TypeScript but can be used in JavaScript as well, after removing all type annotations.  
   
 - Meta:
@@ -274,6 +270,7 @@ The usage and example blocks on each are written in TypeScript but can be used i
   - [BytmDialog](#bytmdialog) - A class for creating and managing dialogs
   - [createHotkeyInput()](#createhotkeyinput) - Creates a hotkey input element
   - [createToggleInput()](#createtoggleinput) - Creates a toggle input element
+  - [createCircularBtn()](#createcircularbtn) - Creates a generic, circular button element
 - Translations:
   - [setLocale()](#setlocale) - Sets the locale for BetterYTM
   - [getLocale()](#getlocale) - Returns the currently set locale
@@ -291,8 +288,8 @@ The usage and example blocks on each are written in TypeScript but can be used i
   - [sanitizeSong()](#sanitizesong) - Sanitizes the specified song title string to be used in fetching a lyrics URL
 - Other:
   - [NanoEmitter](#nanoemitter) - Abstract class for creating lightweight, type safe event emitting classes
-  - [compareVersions](#compareversions) - Crudely compares two semver version strings and returns which one is newer
-  - [compareVersionArrays](#compareversionarrays) - Crudely compares two semver version number arrays and returns which one is newer
+  - [compareVersions()](#compareversions) - Crudely compares two semver version strings and returns which one is newer
+  - [compareVersionArrays()](#compareversionarrays) - Crudely compares two semver version number arrays and returns which one is newer
 
 <br><br>
 
@@ -1106,6 +1103,44 @@ The usage and example blocks on each are written in TypeScript but can be used i
 
 <br>
 
+> #### createCircularBtn()
+> Usage:
+> ```ts
+> unsafeWindow.BYTM.createCircularBtn(btnProps: {
+>   title: string,
+>   // either resourceName or src has to be specified:
+>   resourceName: string | undefined,
+>   src: string | undefined,
+>   // either href or onClick has to be specified:
+>   href: string | undefined,
+>   onClick: (event: MouseEvent | KeyboardEvent) => void | undefined,
+> }): HTMLElement
+> ```
+>
+> Creates a circular button element that can be used to trigger an action or navigate to a different page.  
+> - `title` - The title of the button that is displayed when hovering over it. Also used as a description for accessibility.
+> - `resourceName` - The name of the resource to use as the button icon (`src` can't be specified).
+> - `src` - The URL of the image to use as the button icon (`resourceName` can't be specified).
+> - `href` - The URL to navigate to when the button is interacted with (`onClick` can't be specified).
+> - `onClick` - The function that is called when the button is clicked or interacted with (`href` can't be specified).
+>
+> <details><summary><b>Example <i>(click to expand)</i></b></summary>
+>
+> ```ts
+> const circularBtn = unsafeWindow.BYTM.createCircularBtn({
+>   title: "My cool button",
+>   resourceName: "icon-help",
+>   onClick() {
+>     console.log("The button was clicked");
+>   },
+> });
+> 
+> document.querySelector("#my-element").appendChild(circularBtn);
+> ```
+> </details>
+
+<br>
+
 > ### compareVersions()
 > Usage:
 > ```ts

+ 20 - 12
src/components/genericButton.ts → src/components/circularButton.ts

@@ -1,12 +1,19 @@
 import { getResourceUrl, onInteraction } from "../utils";
 import type { ResourceKey } from "../types";
 
-type CreateGenericBtnOptions = {
-  /** Resource key for the button icon */
-  resourceName: ResourceKey | "_";
-  /** Tooltip and aria-label of the button */
-  title: string;
-}
+type CircularBtnOptions = (
+  | {
+    /** Resource key for the button icon */
+    resourceName: ResourceKey | "_";
+    /** Tooltip and aria-label of the button */
+    title: string;
+  }
+  | {
+    src: string;
+    /** Tooltip and aria-label of the button */
+    title: string;
+  }
+)
 & (
   {
     /** URL to navigate to when the button is clicked */
@@ -20,16 +27,17 @@ type CreateGenericBtnOptions = {
 );
 
 /**
- * Creates a generic button element.  
+ * Creates a generic, circular button element.  
  * If `href` is provided, the button will be an anchor element.  
- * If `onClick` is provided, the button will be a div element.
+ * If `onClick` is provided, the button will be a div element.  
+ * Provide either `resourceName` or `src` to specify the icon inside the button.
  */
-export async function createGenericBtn({
-  resourceName,
+export async function createCircularBtn({
   title,
   href,
   onClick,
-}: CreateGenericBtnOptions) {
+  ...rest
+}: CircularBtnOptions) {
   let btnElem: HTMLElement;
   if(href) {
     btnElem = document.createElement("a");
@@ -48,7 +56,7 @@ export async function createGenericBtn({
 
   const imgElem = document.createElement("img");
   imgElem.classList.add("bytm-generic-btn-img");
-  imgElem.src = await getResourceUrl(resourceName);
+  imgElem.src = "src" in rest ? rest.src : await getResourceUrl(rest.resourceName);
 
   btnElem.appendChild(imgElem);
 

+ 9 - 1
src/components/hotkeyInput.ts

@@ -8,6 +8,8 @@ interface HotkeyInputProps {
   onChange: (hotkey: HotkeyObj) => void;
 }
 
+let otherHotkeyInputActive = false;
+
 const reservedKeys = ["ShiftLeft", "ShiftRight", "ControlLeft", "ControlRight", "AltLeft", "AltRight", "Meta", "Tab", "Space", " "];
 
 /** Creates a hotkey input element */
@@ -20,7 +22,7 @@ export function createHotkeyInput({ initialValue, onChange }: HotkeyInputProps):
 
   const infoElem = document.createElement("span");
   infoElem.classList.add("bytm-hotkey-info");
-  
+
   const inputElem = document.createElement("input");
   inputElem.type = "button";
   inputElem.classList.add("bytm-ftconf-input", "bytm-hotkey-input", "bytm-btn");
@@ -36,7 +38,10 @@ export function createHotkeyInput({ initialValue, onChange }: HotkeyInputProps):
   resetElem.ariaLabel = resetElem.title = t("reset");
 
   const deactivate = () => {
+    if(!otherHotkeyInputActive)
+      return;
     siteEvents.emit("hotkeyInputActive", false);
+    otherHotkeyInputActive = false;
     const curHk = currentHotkey ?? initialValue;
     inputElem.value = curHk?.code ?? t("hotkey_input_click_to_change");
     inputElem.dataset.state = "inactive";
@@ -45,7 +50,10 @@ export function createHotkeyInput({ initialValue, onChange }: HotkeyInputProps):
   };
 
   const activate = () => {
+    if(otherHotkeyInputActive)
+      return;
     siteEvents.emit("hotkeyInputActive", true);
+    otherHotkeyInputActive = true;
     inputElem.value = "< ... >";
     inputElem.dataset.state = "active";
     inputElem.ariaLabel = inputElem.title = t("hotkey_input_click_to_cancel_tooltip");

+ 1 - 1
src/components/index.ts

@@ -1,4 +1,4 @@
 export * from "./BytmDialog";
-export * from "./genericButton";
+export * from "./circularButton";
 export * from "./hotkeyInput";
 export * from "./toggleInput";

+ 2 - 1
src/interface.ts

@@ -7,7 +7,7 @@ import { getFeatures, setFeatures } from "./config";
 import { compareVersionArrays, compareVersions, featInfo, fetchLyricsUrlTop, getLyricsCacheEntry, sanitizeArtists, sanitizeSong, type LyricsCache } from "./features";
 import { allSiteEvents, siteEvents, type SiteEventsMap } from "./siteEvents";
 import { LogLevel, type FeatureConfig, type FeatureInfo, type LyricsCacheEntry, type PluginDef, type PluginInfo, type PluginRegisterResult, type PluginDefResolvable, type PluginEventMap, type PluginItem, type BytmObject } from "./types";
-import { BytmDialog, createHotkeyInput, createToggleInput } from "./components";
+import { BytmDialog, createCircularBtn, createHotkeyInput, createToggleInput } from "./components";
 
 const { getUnsafeWindow } = UserUtils;
 
@@ -92,6 +92,7 @@ export function initInterface() {
     BytmDialog,
     createHotkeyInput,
     createToggleInput,
+    createCircularBtn,
   };
 
   for(const [key, value] of Object.entries(props))