index.ts 11 KB

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