index.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. import { getPreferredLocale, resourceToHTMLString, t, tp } from "../utils";
  2. import langMapping from "../../assets/locales.json" assert { type: "json" };
  3. import { remSongMinPlayTime } from "./behavior";
  4. import { clearLyricsCache, getLyricsCache } from "./lyrics";
  5. import { FeatureInfo } from "../types";
  6. import { getFeatures } from "src/config";
  7. export * from "./layout";
  8. export * from "./behavior";
  9. export * from "./input";
  10. export * from "./lyrics";
  11. export * from "./songLists";
  12. export * from "./versionCheck";
  13. type SelectOption = { value: number | string, label: string };
  14. //#MARKER feature dependencies
  15. const localeOptions = Object.entries(langMapping).reduce((a, [locale, { name }]) => {
  16. return [...a, {
  17. value: locale,
  18. label: name,
  19. }];
  20. }, [] as SelectOption[])
  21. .sort((a, b) => a.label.localeCompare(b.label));
  22. //#MARKER features
  23. /**
  24. * Contains all possible features with their default values and other configuration.
  25. *
  26. * **Required props:**
  27. * | Property | Description |
  28. * | :-- | :-- |
  29. * | `type` | type of the feature configuration element - use autocomplete or check `FeatureTypeProps` in `src/types.ts` |
  30. * | `category` | category of the feature - use autocomplete or check `FeatureCategory` in `src/types.ts` |
  31. * | `default` | default value of the feature - type of the value depends on the given `type` |
  32. * | `enable(value: any)` | function that will be called when the feature is enabled / initialized for the first time |
  33. *
  34. * **Optional props:**
  35. * | Property | Description |
  36. * | :-- | :-- |
  37. * | `disable(newValue: any)` | for type `toggle` only - function that will be called when the feature is disabled - can be a synchronous or asynchronous function |
  38. * | `change(prevValue: any, newValue: any)` | for types `number`, `select`, `slider` and `hotkey` only - function that will be called when the value is changed |
  39. * | `click: () => void` | for type `button` only - function that will be called when the button is clicked |
  40. * | `helpText(): string / () => string` | function that returns an HTML string or the literal string itself that will be the help text for this feature - writing as function is useful for pluralizing or inserting values into the translation at runtime - if not set, translation with key `feature_helptext_featureKey` will be used instead, if available |
  41. * | `textAdornment(): string / Promise<string>` | function that returns an HTML string that will be appended to the text in the config menu as an adornment element - TODO: to be replaced in the big menu rework |
  42. * | `hidden` | if true, the feature will not be shown in the settings - default is undefined (false) |
  43. * | `min` | Only if type is `number` or `slider` - Overwrites the default of the `min` property of the HTML input element |
  44. * | `max` | Only if type is `number` or `slider` - Overwrites the default of the `max` property of the HTML input element |
  45. * | `step` | Only if type is `number` or `slider` - Overwrites the default of the `step` property of the HTML input element |
  46. * | `unit: string / (val: number) => string` | Only if type is `number` or `slider` - The unit text that is displayed next to the input element, i.e. "px" |
  47. *
  48. * **Notes:**
  49. * - If no `disable()` or `change()` function is present, the page needs to be reloaded for the changes to take effect
  50. */
  51. export const featInfo = {
  52. //#SECTION layout
  53. removeUpgradeTab: {
  54. type: "toggle",
  55. category: "layout",
  56. default: true,
  57. enable: () => void "TODO",
  58. },
  59. volumeSliderLabel: {
  60. type: "toggle",
  61. category: "layout",
  62. default: true,
  63. enable: () => void "TODO",
  64. disable: () => void "TODO",
  65. },
  66. volumeSliderSize: {
  67. type: "number",
  68. category: "layout",
  69. min: 50,
  70. max: 500,
  71. step: 5,
  72. default: 150,
  73. unit: "px",
  74. enable: () => void "TODO",
  75. change: () => void "TODO",
  76. },
  77. volumeSliderStep: {
  78. type: "slider",
  79. category: "layout",
  80. min: 1,
  81. max: 25,
  82. default: 2,
  83. unit: "%",
  84. enable: () => void "TODO",
  85. change: () => void "TODO",
  86. },
  87. volumeSliderScrollStep: {
  88. type: "slider",
  89. category: "layout",
  90. min: 1,
  91. max: 25,
  92. default: 10,
  93. unit: "%",
  94. enable: () => void "TODO",
  95. change: () => void "TODO",
  96. },
  97. watermarkEnabled: {
  98. type: "toggle",
  99. category: "layout",
  100. default: true,
  101. enable: () => void "TODO",
  102. disable: () => void "TODO",
  103. },
  104. removeShareTrackingParam: {
  105. type: "toggle",
  106. category: "layout",
  107. default: true,
  108. enable: () => void "TODO",
  109. disable: () => void "TODO",
  110. },
  111. fixSpacing: {
  112. type: "toggle",
  113. category: "layout",
  114. default: true,
  115. enable: () => void "TODO",
  116. disable: () => void "TODO",
  117. },
  118. scrollToActiveSongBtn: {
  119. type: "toggle",
  120. category: "layout",
  121. default: true,
  122. enable: () => void "TODO",
  123. disable: () => void "TODO",
  124. },
  125. //#SECTION song lists
  126. lyricsQueueButton: {
  127. type: "toggle",
  128. category: "songLists",
  129. default: true,
  130. enable: () => void "TODO",
  131. disable: () => void "TODO",
  132. },
  133. deleteFromQueueButton: {
  134. type: "toggle",
  135. category: "songLists",
  136. default: true,
  137. enable: () => void "TODO",
  138. disable: () => void "TODO",
  139. },
  140. listButtonsPlacement: {
  141. type: "select",
  142. category: "songLists",
  143. options: () => [
  144. { value: "queueOnly", label: t("list_button_placement_queue_only") },
  145. { value: "everywhere", label: t("list_button_placement_everywhere") },
  146. ],
  147. default: "everywhere",
  148. enable: () => void "TODO",
  149. disable: () => void "TODO",
  150. },
  151. //#SECTION behavior
  152. disableBeforeUnloadPopup: {
  153. type: "toggle",
  154. category: "behavior",
  155. default: false,
  156. enable: () => void "TODO",
  157. },
  158. closeToastsTimeout: {
  159. type: "number",
  160. category: "behavior",
  161. min: 0,
  162. max: 30,
  163. step: 0.5,
  164. default: 0,
  165. unit: "s",
  166. enable: () => void "TODO",
  167. change: () => void "TODO",
  168. },
  169. rememberSongTime: {
  170. type: "toggle",
  171. category: "behavior",
  172. default: true,
  173. enable: () => void "TODO",
  174. disable: () => void "TODO", // TODO: feasible?
  175. helpText: () => tp("feature_helptext_rememberSongTime", remSongMinPlayTime, remSongMinPlayTime)
  176. },
  177. rememberSongTimeSites: {
  178. type: "select",
  179. category: "behavior",
  180. options: () => [
  181. { value: "all", label: t("remember_song_time_sites_all") },
  182. { value: "yt", label: t("remember_song_time_sites_yt") },
  183. { value: "ytm", label: t("remember_song_time_sites_ytm") },
  184. ],
  185. default: "ytm",
  186. enable: () => void "TODO",
  187. change: () => void "TODO",
  188. },
  189. //#SECTION input
  190. arrowKeySupport: {
  191. type: "toggle",
  192. category: "input",
  193. default: true,
  194. enable: () => void "TODO",
  195. disable: () => void "TODO",
  196. },
  197. arrowKeySkipBy: {
  198. type: "number",
  199. category: "input",
  200. min: 0.5,
  201. max: 60,
  202. step: 0.5,
  203. default: 5,
  204. enable: () => void "TODO",
  205. change: () => void "TODO",
  206. },
  207. switchBetweenSites: {
  208. type: "toggle",
  209. category: "input",
  210. default: true,
  211. enable: () => void "TODO",
  212. disable: () => void "TODO",
  213. },
  214. switchSitesHotkey: {
  215. type: "hotkey",
  216. category: "input",
  217. default: {
  218. code: "F9",
  219. shift: false,
  220. ctrl: false,
  221. alt: false,
  222. },
  223. enable: () => void "TODO",
  224. change: () => void "TODO",
  225. },
  226. anchorImprovements: {
  227. type: "toggle",
  228. category: "input",
  229. default: true,
  230. enable: () => void "TODO",
  231. disable: () => void "TODO",
  232. },
  233. numKeysSkipToTime: {
  234. type: "toggle",
  235. category: "input",
  236. default: true,
  237. enable: () => void "TODO",
  238. disable: () => void "TODO",
  239. },
  240. //#SECTION lyrics
  241. geniusLyrics: {
  242. type: "toggle",
  243. category: "lyrics",
  244. default: true,
  245. enable: () => void "TODO",
  246. disable: () => void "TODO",
  247. },
  248. geniUrlBase: {
  249. type: "text",
  250. category: "lyrics",
  251. default: "https://api.sv443.net/geniurl",
  252. normalize: (val: string) => val.trim().replace(/\/+$/, ""),
  253. advanced: true,
  254. // TODO: to be reworked or removed in the big menu rework
  255. textAdornment: getAdvancedModeAdornment,
  256. },
  257. lyricsCacheMaxSize: {
  258. type: "slider",
  259. category: "lyrics",
  260. default: 500,
  261. min: 50,
  262. max: 2000,
  263. step: 50,
  264. unit: (val: number) => tp("unit_entries", val),
  265. enable: () => void "TODO",
  266. change: () => void "TODO",
  267. advanced: true,
  268. // TODO: to be reworked or removed in the big menu rework
  269. textAdornment: getAdvancedModeAdornment,
  270. },
  271. lyricsCacheTTL: {
  272. type: "slider",
  273. category: "lyrics",
  274. default: 21,
  275. min: 3,
  276. max: 100,
  277. step: 1,
  278. unit: (val: number) => tp("unit_days", val),
  279. enable: () => void "TODO",
  280. change: () => void "TODO",
  281. advanced: true,
  282. // TODO: to be reworked or removed in the big menu rework
  283. textAdornment: getAdvancedModeAdornment,
  284. },
  285. clearLyricsCache: {
  286. type: "button",
  287. category: "lyrics",
  288. default: undefined,
  289. click() {
  290. const entries = getLyricsCache().length;
  291. if(confirm(tp("lyrics_clear_cache_confirm_prompt", entries, entries))) {
  292. clearLyricsCache();
  293. alert(t("lyrics_clear_cache_success"));
  294. }
  295. },
  296. advanced: true,
  297. // TODO: to be reworked or removed in the big menu rework
  298. textAdornment: getAdvancedModeAdornment,
  299. },
  300. //#SECTION general
  301. locale: {
  302. type: "select",
  303. category: "general",
  304. options: localeOptions,
  305. default: getPreferredLocale(),
  306. enable: () => void "TODO",
  307. // TODO: to be reworked or removed in the big menu rework
  308. textAdornment: async () => await resourceToHTMLString("img-globe") ?? "",
  309. },
  310. versionCheck: {
  311. type: "toggle",
  312. category: "general",
  313. default: true,
  314. enable: () => void "TODO",
  315. disable: () => void "TODO",
  316. },
  317. logLevel: {
  318. type: "select",
  319. category: "general",
  320. options: () => [
  321. { value: 0, label: t("log_level_debug") },
  322. { value: 1, label: t("log_level_info") },
  323. ],
  324. default: 1,
  325. enable: () => void "TODO",
  326. },
  327. advancedMode: {
  328. type: "toggle",
  329. category: "general",
  330. default: false,
  331. enable: () => void "TODO",
  332. disable: () => void "TODO",
  333. // TODO: to be reworked or removed in the big menu rework
  334. textAdornment: () => getFeatures().advancedMode ? getAdvancedModeAdornment() : undefined,
  335. },
  336. } as const satisfies FeatureInfo;
  337. async function getAdvancedModeAdornment() {
  338. return `<span class="advanced-mode-icon" title="${t("advanced_mode_tooltip")}">${await resourceToHTMLString("img-add_circle_small") ?? ""}</span>`;
  339. }