diff --git a/package.json b/package.json index 1a5310ef..510b88c9 100644 --- a/package.json +++ b/package.json @@ -298,6 +298,7 @@ "serve": "14.2.4", "simple-youtube-age-restriction-bypass": "github:organization/Simple-YouTube-Age-Restriction-Bypass#v2.5.9", "socks": "2.8.5", + "solid-element": "1.9.1", "solid-floating-ui": "0.3.1", "solid-js": "1.9.7", "solid-styled-components": "0.28.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac608c28..d4874d80 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -208,6 +208,9 @@ importers: socks: specifier: 2.8.5 version: 2.8.5 + solid-element: + specifier: 1.9.1 + version: 1.9.1(solid-js@1.9.7) solid-floating-ui: specifier: 0.3.1 version: 0.3.1(@floating-ui/dom@1.7.2)(solid-js@1.9.7) @@ -1976,6 +1979,9 @@ packages: resolution: {integrity: sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==} engines: {node: '>=0.10.0'} + component-register@0.8.7: + resolution: {integrity: sha512-clPS/o1RNfJw7L1/w4q+nkj6l7JV32kFHCx6vW5nSPOEly4B9olMeADNilEgpLV/DdeS7y8JXhHKx9YvSj8vqQ==} + compressible@2.0.18: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} @@ -4280,6 +4286,11 @@ packages: resolution: {integrity: sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + solid-element@1.9.1: + resolution: {integrity: sha512-baJy6Qz27oAUgkPlqOf3Y+7RsBiuVQrS51Nrh1ddDbrqrNPvJbIvehpUsTzLNFb2ZHIoHuNnDg330go/ZKcRdg==} + peerDependencies: + solid-js: ^1.9.3 + solid-floating-ui@0.3.1: resolution: {integrity: sha512-o/QmGsWPS2Z3KidAxP0nDvN7alI7Kqy0kU+wd85Fz+au5SYcnYm7I6Fk3M60Za35azsPX0U+5fEtqfOuk6Ao0Q==} engines: {node: '>=10'} @@ -6644,6 +6655,8 @@ snapshots: compare-version@0.1.2: {} + component-register@0.8.7: {} + compressible@2.0.18: dependencies: mime-db: 1.54.0 @@ -9218,6 +9231,11 @@ snapshots: ip-address: 9.0.5 smart-buffer: 4.2.0 + solid-element@1.9.1(solid-js@1.9.7): + dependencies: + component-register: 0.8.7 + solid-js: 1.9.7 + solid-floating-ui@0.3.1(@floating-ui/dom@1.7.2)(solid-js@1.9.7): dependencies: '@floating-ui/dom': 1.7.2 diff --git a/src/config/store.ts b/src/config/store.ts index 01df655b..3f6513ab 100644 --- a/src/config/store.ts +++ b/src/config/store.ts @@ -5,7 +5,7 @@ import defaults from './defaults'; import { DefaultPresetList, type Preset } from '@/plugins/downloader/types'; // prettier-ignore -export type IStore = InstanceType>>; +export type IStore = InstanceType>>; const migrations = { '>=3.3.0'(store: IStore) { diff --git a/src/i18n/resources/ar.json b/src/i18n/resources/ar.json index d3f49be9..969059d8 100644 --- a/src/i18n/resources/ar.json +++ b/src/i18n/resources/ar.json @@ -859,7 +859,7 @@ }, "name": "تفعيل الفيديو", "templates": { - "button": "أغنية" + "button-song": "أغنية" } }, "visualizer": { diff --git a/src/i18n/resources/bg.json b/src/i18n/resources/bg.json index bbc3fe97..192525a8 100644 --- a/src/i18n/resources/bg.json +++ b/src/i18n/resources/bg.json @@ -835,7 +835,7 @@ }, "name": "Превключване на видео", "templates": { - "button": "Песен" + "button-song": "Песен" } }, "visualizer": { diff --git a/src/i18n/resources/ca.json b/src/i18n/resources/ca.json index 3b6cb7fa..f3a9e20e 100644 --- a/src/i18n/resources/ca.json +++ b/src/i18n/resources/ca.json @@ -859,7 +859,7 @@ }, "name": "Botó de vídeo", "templates": { - "button": "Cançó" + "button-song": "Cançó" } }, "visualizer": { diff --git a/src/i18n/resources/cs.json b/src/i18n/resources/cs.json index 48a11e19..9e28ec02 100644 --- a/src/i18n/resources/cs.json +++ b/src/i18n/resources/cs.json @@ -765,7 +765,7 @@ }, "name": "Přepínač videa", "templates": { - "button": "Písnička" + "button-song": "Písnička" } }, "visualizer": { diff --git a/src/i18n/resources/de.json b/src/i18n/resources/de.json index 9f2044e1..fc9e2b50 100644 --- a/src/i18n/resources/de.json +++ b/src/i18n/resources/de.json @@ -859,7 +859,7 @@ }, "name": "Videoumschalter", "templates": { - "button": "Lied" + "button-song": "Lied" } }, "visualizer": { diff --git a/src/i18n/resources/el.json b/src/i18n/resources/el.json index 6a7c0749..595c4b5c 100644 --- a/src/i18n/resources/el.json +++ b/src/i18n/resources/el.json @@ -859,7 +859,7 @@ }, "name": "Εναλλαγή βίντεο", "templates": { - "button": "Τραγούδι" + "button-song": "Τραγούδι" } }, "visualizer": { diff --git a/src/i18n/resources/en.json b/src/i18n/resources/en.json index 41e1242e..7700c61c 100644 --- a/src/i18n/resources/en.json +++ b/src/i18n/resources/en.json @@ -381,6 +381,11 @@ }, "templates": { "title": "Open captions selector" + }, + "toast": { + "caption-changed": "Caption changed to {{language}}", + "caption-disabled": "Captions disabled", + "no-captions": "No captions available for this song" } }, "compact-sidebar": { @@ -600,7 +605,15 @@ }, "navigation": { "description": "Next/Back navigation arrows directly integrated in the interface, like in your favorite browser", - "name": "Navigation" + "name": "Navigation", + "templates": { + "back": { + "title": "Go to previous page" + }, + "forward": { + "title": "Go to next page" + } + } }, "no-google-login": { "description": "Remove Google login buttons and links from the interface", @@ -691,6 +704,11 @@ } } }, + "renderer": { + "quality-settings-button": { + "label": "Open player quality changer" + } + }, "description": "Allows changing the video quality with a button on the video overlay", "name": "Video Quality Changer" }, @@ -859,7 +877,8 @@ }, "name": "Video Toggle", "templates": { - "button": "Song" + "button-song": "Song", + "button-video": "Video" } }, "visualizer": { diff --git a/src/i18n/resources/es.json b/src/i18n/resources/es.json index 99a5685f..9d6b4450 100644 --- a/src/i18n/resources/es.json +++ b/src/i18n/resources/es.json @@ -859,7 +859,7 @@ }, "name": "Alternador de vídeo", "templates": { - "button": "Canción" + "button-song": "Canción" } }, "visualizer": { diff --git a/src/i18n/resources/fa.json b/src/i18n/resources/fa.json index b7253df4..faad3da9 100644 --- a/src/i18n/resources/fa.json +++ b/src/i18n/resources/fa.json @@ -831,7 +831,7 @@ }, "name": "ویدیو به آهنگ", "templates": { - "button": "ترانه" + "button-song": "ترانه" } }, "visualizer": { diff --git a/src/i18n/resources/fil.json b/src/i18n/resources/fil.json index 6aa8f038..1a328256 100644 --- a/src/i18n/resources/fil.json +++ b/src/i18n/resources/fil.json @@ -784,7 +784,7 @@ } }, "templates": { - "button": "Kanta" + "button-song": "Kanta" } }, "visualizer": { diff --git a/src/i18n/resources/fr.json b/src/i18n/resources/fr.json index cd78c200..5adc58bc 100644 --- a/src/i18n/resources/fr.json +++ b/src/i18n/resources/fr.json @@ -859,7 +859,7 @@ }, "name": "Basculer la vidéo", "templates": { - "button": "Musique" + "button-song": "Musique" } }, "visualizer": { diff --git a/src/i18n/resources/hu.json b/src/i18n/resources/hu.json index 4450b438..bda91585 100644 --- a/src/i18n/resources/hu.json +++ b/src/i18n/resources/hu.json @@ -831,7 +831,7 @@ }, "name": "Videó váltó", "templates": { - "button": "Zeneszám" + "button-song": "Zeneszám" } }, "visualizer": { diff --git a/src/i18n/resources/id.json b/src/i18n/resources/id.json index e1421e6e..15e04975 100644 --- a/src/i18n/resources/id.json +++ b/src/i18n/resources/id.json @@ -859,7 +859,7 @@ }, "name": "Peralih Video", "templates": { - "button": "Lagu" + "button-song": "Lagu" } }, "visualizer": { diff --git a/src/i18n/resources/is.json b/src/i18n/resources/is.json index f58bc147..33ae7a22 100644 --- a/src/i18n/resources/is.json +++ b/src/i18n/resources/is.json @@ -799,7 +799,7 @@ }, "name": "Myndbandsrofi", "templates": { - "button": "Lag" + "button-song": "Lag" } }, "visualizer": { diff --git a/src/i18n/resources/it.json b/src/i18n/resources/it.json index c4708b82..f469f39c 100644 --- a/src/i18n/resources/it.json +++ b/src/i18n/resources/it.json @@ -859,7 +859,7 @@ }, "name": "Selettore Brano/Video", "templates": { - "button": "Brano" + "button-song": "Brano" } }, "visualizer": { diff --git a/src/i18n/resources/ja.json b/src/i18n/resources/ja.json index 1d50777f..fe645ec7 100644 --- a/src/i18n/resources/ja.json +++ b/src/i18n/resources/ja.json @@ -857,7 +857,7 @@ }, "name": "動画の切り替え", "templates": { - "button": "曲" + "button-song": "曲" } }, "visualizer": { diff --git a/src/i18n/resources/ko.json b/src/i18n/resources/ko.json index 5534d42d..8d0ae5f3 100644 --- a/src/i18n/resources/ko.json +++ b/src/i18n/resources/ko.json @@ -859,7 +859,8 @@ }, "name": "영상 전환", "templates": { - "button": "노래" + "button-song": "노래", + "button-video": "영상" } }, "visualizer": { diff --git a/src/i18n/resources/lt.json b/src/i18n/resources/lt.json index b1c65c98..4fa68b61 100644 --- a/src/i18n/resources/lt.json +++ b/src/i18n/resources/lt.json @@ -618,7 +618,7 @@ }, "name": "Vaizdo įrašo perjungimas", "templates": { - "button": "Daina" + "button-song": "Daina" } }, "visualizer": { diff --git a/src/i18n/resources/ms.json b/src/i18n/resources/ms.json index 85d3bd27..8ff65735 100644 --- a/src/i18n/resources/ms.json +++ b/src/i18n/resources/ms.json @@ -381,7 +381,7 @@ } }, "templates": { - "button": "Lagu" + "button-song": "Lagu" } } } diff --git a/src/i18n/resources/nb.json b/src/i18n/resources/nb.json index bae16783..2b0c0bd3 100644 --- a/src/i18n/resources/nb.json +++ b/src/i18n/resources/nb.json @@ -577,7 +577,7 @@ }, "name": "Videoveksling", "templates": { - "button": "Spor" + "button-song": "Spor" } }, "visualizer": { diff --git a/src/i18n/resources/ne.json b/src/i18n/resources/ne.json index 92f6cf9f..fae0042d 100644 --- a/src/i18n/resources/ne.json +++ b/src/i18n/resources/ne.json @@ -859,7 +859,7 @@ }, "name": "भिडियो टगल", "templates": { - "button": "गीत" + "button-song": "गीत" } }, "visualizer": { diff --git a/src/i18n/resources/nl.json b/src/i18n/resources/nl.json index e44dcfe1..34e307db 100644 --- a/src/i18n/resources/nl.json +++ b/src/i18n/resources/nl.json @@ -859,7 +859,7 @@ }, "name": "Videoschakelaar", "templates": { - "button": "Nummer" + "button-song": "Nummer" } }, "visualizer": { diff --git a/src/i18n/resources/pl.json b/src/i18n/resources/pl.json index 560ba67b..86a1b715 100644 --- a/src/i18n/resources/pl.json +++ b/src/i18n/resources/pl.json @@ -859,7 +859,7 @@ }, "name": "Przełącznik wideo", "templates": { - "button": "Utwór" + "button-song": "Utwór" } }, "visualizer": { diff --git a/src/i18n/resources/pt-BR.json b/src/i18n/resources/pt-BR.json index f617f699..fb633efb 100644 --- a/src/i18n/resources/pt-BR.json +++ b/src/i18n/resources/pt-BR.json @@ -859,7 +859,7 @@ }, "name": "Alternar vídeo", "templates": { - "button": "Música" + "button-song": "Música" } }, "visualizer": { diff --git a/src/i18n/resources/pt.json b/src/i18n/resources/pt.json index 6bd82b3f..68af8af1 100644 --- a/src/i18n/resources/pt.json +++ b/src/i18n/resources/pt.json @@ -859,7 +859,7 @@ }, "name": "Botão de Alternar Vídeo", "templates": { - "button": "Música" + "button-song": "Música" } }, "visualizer": { diff --git a/src/i18n/resources/ro.json b/src/i18n/resources/ro.json index c7ffbe29..dba7f75e 100644 --- a/src/i18n/resources/ro.json +++ b/src/i18n/resources/ro.json @@ -859,7 +859,7 @@ }, "name": "Comutator video", "templates": { - "button": "Melodie" + "button-song": "Melodie" } }, "visualizer": { diff --git a/src/i18n/resources/ru.json b/src/i18n/resources/ru.json index 7b752b35..77e0bbb8 100644 --- a/src/i18n/resources/ru.json +++ b/src/i18n/resources/ru.json @@ -859,7 +859,7 @@ }, "name": "Переключатель видео", "templates": { - "button": "Песня" + "button-song": "Песня" } }, "visualizer": { diff --git a/src/i18n/resources/sv.json b/src/i18n/resources/sv.json index b213fbdb..15d6196f 100644 --- a/src/i18n/resources/sv.json +++ b/src/i18n/resources/sv.json @@ -222,7 +222,7 @@ } }, "templates": { - "button": "Låt" + "button-song": "Låt" } } } diff --git a/src/i18n/resources/ta.json b/src/i18n/resources/ta.json index acde8b81..f9b27d99 100644 --- a/src/i18n/resources/ta.json +++ b/src/i18n/resources/ta.json @@ -859,7 +859,7 @@ }, "name": "வீடியோ மாற்று", "templates": { - "button": "பாடல்" + "button-song": "பாடல்" } }, "visualizer": { diff --git a/src/i18n/resources/th.json b/src/i18n/resources/th.json index a2f05bc0..7b417682 100644 --- a/src/i18n/resources/th.json +++ b/src/i18n/resources/th.json @@ -859,7 +859,7 @@ }, "name": "ปุ่มวิดีโอ", "templates": { - "button": "เพลง" + "button-song": "เพลง" } }, "visualizer": { diff --git a/src/i18n/resources/tr.json b/src/i18n/resources/tr.json index 9ecbb09d..4e1e8939 100644 --- a/src/i18n/resources/tr.json +++ b/src/i18n/resources/tr.json @@ -859,7 +859,7 @@ }, "name": "Video Geçiş", "templates": { - "button": "Şarkı" + "button-song": "Şarkı" } }, "visualizer": { diff --git a/src/i18n/resources/uk.json b/src/i18n/resources/uk.json index edb2d251..9ea2e931 100644 --- a/src/i18n/resources/uk.json +++ b/src/i18n/resources/uk.json @@ -859,7 +859,7 @@ }, "name": "Перемикач відео", "templates": { - "button": "Пісня" + "button-song": "Пісня" } }, "visualizer": { diff --git a/src/i18n/resources/vi.json b/src/i18n/resources/vi.json index 89ed19a0..f53ef46a 100644 --- a/src/i18n/resources/vi.json +++ b/src/i18n/resources/vi.json @@ -857,7 +857,7 @@ }, "name": "Chuyển đổi video", "templates": { - "button": "Bài hát" + "button-song": "Bài hát" } }, "visualizer": { diff --git a/src/i18n/resources/zh-CN.json b/src/i18n/resources/zh-CN.json index d54ef98d..c96a1471 100644 --- a/src/i18n/resources/zh-CN.json +++ b/src/i18n/resources/zh-CN.json @@ -859,7 +859,7 @@ }, "name": "视频切换开关", "templates": { - "button": "歌曲" + "button-song": "歌曲" } }, "visualizer": { diff --git a/src/i18n/resources/zh-TW.json b/src/i18n/resources/zh-TW.json index c59d3f6a..ad0d190e 100644 --- a/src/i18n/resources/zh-TW.json +++ b/src/i18n/resources/zh-TW.json @@ -859,7 +859,7 @@ }, "name": "歌曲/影片切換", "templates": { - "button": "歌曲" + "button-song": "歌曲" } }, "visualizer": { diff --git a/src/index.ts b/src/index.ts index 307a7231..c297dbec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -763,7 +763,7 @@ app.whenReady().then(async () => { const splited = decodeURIComponent(command).split(' '); - handleProtocol(splited.shift()!, splited); + handleProtocol(splited.shift()!, ...splited); return; } diff --git a/src/plugins/album-actions/index.ts b/src/plugins/album-actions/index.tsx similarity index 58% rename from src/plugins/album-actions/index.ts rename to src/plugins/album-actions/index.tsx index ed27c6c2..7c1b0001 100644 --- a/src/plugins/album-actions/index.ts +++ b/src/plugins/album-actions/index.tsx @@ -1,12 +1,17 @@ +import { render } from 'solid-js/web'; + +import { createSignal, Show } from 'solid-js'; + import { t } from '@/i18n'; import { createPlugin } from '@/utils'; -import { ElementFromHtml } from '@/plugins/utils/renderer'; import { waitForElement } from '@/utils/wait-for-element'; -import undislikeHTML from './templates/undislike.html?raw'; -import dislikeHTML from './templates/dislike.html?raw'; -import likeHTML from './templates/like.html?raw'; -import unlikeHTML from './templates/unlike.html?raw'; +import { + DislikeButton, + LikeButton, + UnDislikeButton, + UnLikeButton, +} from './templates'; export default createPlugin< unknown, @@ -52,19 +57,69 @@ export default createPlugin< } const continuations = await waitForElement('#continuations'); this.waiting = false; - //Gets the for buttons - const buttons: Array = [ - ElementFromHtml(undislikeHTML), - ElementFromHtml(dislikeHTML), - ElementFromHtml(likeHTML), - ElementFromHtml(unlikeHTML), - ]; + + const [showUnDislike, setShowUnDislike] = createSignal(true); + const [showDislike, setShowDislike] = createSignal(true); + const [showLike, setShowLike] = createSignal(true); + const [showUnLike, setShowUnLike] = createSignal(true); + + const DEFAULT_MASK_SIZE = '100% 50%'; + const [unDislikeMaskSize, setUnDislikeMaskSize] = + createSignal(DEFAULT_MASK_SIZE); + const [dislikeMaskSize, setDislikeMaskSize] = + createSignal(DEFAULT_MASK_SIZE); + const [likeMaskSize, setLikeMaskSize] = createSignal(DEFAULT_MASK_SIZE); + const [unLikeMaskSize, setUnLikeMaskSize] = + createSignal(DEFAULT_MASK_SIZE); + + const buttonContainer = document.createElement('div'); + buttonContainer.style.display = 'flex'; + buttonContainer.style.flexDirection = 'row'; + + render( + () => ( + <> + + + + + + + + + + + + + + ), + buttonContainer, + ); + //Finds the playlist const playlist = document.querySelector('ytmusic-playlist-shelf-renderer') ?? - Array.prototype.at.call(document.querySelectorAll('ytmusic-shelf-renderer'), -1)!; + document.querySelector(':nth-last-child(1 of ytmusic-shelf-renderer)'); + + if (!playlist) { + return; + } + // Adds an observer for every button, so it gets updated when one is clicked this.changeObserver?.disconnect(); + this.changeObserver = new MutationObserver(() => { this.stop(); this.start(); @@ -84,34 +139,57 @@ export default createPlugin< '#button-shape-dislike > button', ).length; if (continuations.children.length == 0 && listsLength > 0) { - const counts = [ - playlist?.querySelectorAll( + const counts = { + dislike: playlist?.querySelectorAll( '#button-shape-dislike > button[aria-pressed=true]', ).length, - playlist?.querySelectorAll( + undislike: playlist?.querySelectorAll( '#button-shape-dislike > button[aria-pressed=false]', ).length, - playlist?.querySelectorAll( + unlike: playlist?.querySelectorAll( '#button-shape-like > button[aria-pressed=false]', ).length, - playlist?.querySelectorAll( + like: playlist?.querySelectorAll( '#button-shape-like > button[aria-pressed=true]', ).length, - ]; - let i = 0; - for (const count of counts) { - if (count == 0) { - buttons.splice(i, 1); - i--; - } else { - ( - buttons[i].children[0].children[0] as HTMLElement - ).style.setProperty( - '-webkit-mask-size', - `100% ${100 - (count / listsLength) * 100}%`, - ); + }; + for (const [name, size] of Object.entries(counts)) { + switch (name) { + case 'dislike': + if (size > 0) { + setShowDislike(true); + setDislikeMaskSize(`100% ${100 - (size / listsLength) * 100}%`); + } else { + setShowDislike(false); + } + break; + case 'undislike': + if (size > 0) { + setShowUnDislike(true); + setUnDislikeMaskSize( + `100% ${100 - (size / listsLength) * 100}%`, + ); + } else { + setShowUnDislike(false); + } + break; + case 'like': + if (size > 0) { + setShowLike(true); + setLikeMaskSize(`100% ${100 - (size / listsLength) * 100}%`); + } else { + setShowLike(false); + } + break; + case 'unlike': + if (size > 0) { + setShowUnLike(true); + setUnLikeMaskSize(`100% ${100 - (size / listsLength) * 100}%`); + } else { + setShowUnLike(false); + } + break; } - i++; } } const menuParent = @@ -126,10 +204,7 @@ export default createPlugin< menu, menuParent.children[menuParent.children.length - 1], ); - for (const button of buttons) { - menu.appendChild(button); - button.addEventListener('click', this.loadFullList); - } + menu.appendChild(buttonContainer); } }, loadFullList(event: MouseEvent) { @@ -159,7 +234,7 @@ export default createPlugin< let playlistButtons: NodeListOf | undefined; const playlist = document.querySelector('ytmusic-playlist-shelf-renderer') ?? - Array.prototype.at.call(document.querySelectorAll('ytmusic-shelf-renderer'), -1)!; + document.querySelector(':nth-last-child(1 of ytmusic-shelf-renderer)'); switch (id) { case 'allundislike': playlistButtons = playlist?.querySelectorAll( diff --git a/src/plugins/album-actions/templates/dislike-button.tsx b/src/plugins/album-actions/templates/dislike-button.tsx new file mode 100644 index 00000000..85da24ed --- /dev/null +++ b/src/plugins/album-actions/templates/dislike-button.tsx @@ -0,0 +1,99 @@ +export interface DislikeButtonProps { + onClick?: (e: MouseEvent) => void; + maskSize: string; +} + +export const DislikeButton = (props: DislikeButtonProps) => ( +
+ +
+); diff --git a/src/plugins/album-actions/templates/dislike.html b/src/plugins/album-actions/templates/dislike.html deleted file mode 100644 index 1d00412c..00000000 --- a/src/plugins/album-actions/templates/dislike.html +++ /dev/null @@ -1,76 +0,0 @@ -
- -
diff --git a/src/plugins/album-actions/templates/index.ts b/src/plugins/album-actions/templates/index.ts new file mode 100644 index 00000000..f6221326 --- /dev/null +++ b/src/plugins/album-actions/templates/index.ts @@ -0,0 +1,4 @@ +export * from './like-button'; +export * from './dislike-button'; +export * from './undislike-button'; +export * from './unlike-button'; diff --git a/src/plugins/album-actions/templates/like-button.tsx b/src/plugins/album-actions/templates/like-button.tsx new file mode 100644 index 00000000..eacb14c1 --- /dev/null +++ b/src/plugins/album-actions/templates/like-button.tsx @@ -0,0 +1,90 @@ +export interface LikeButtonProps { + onClick?: (e: MouseEvent) => void; + maskSize: string; +} + +export const LikeButton = (props: LikeButtonProps) => ( +
+ +
+); diff --git a/src/plugins/album-actions/templates/like.html b/src/plugins/album-actions/templates/like.html deleted file mode 100644 index 9de2cfbe..00000000 --- a/src/plugins/album-actions/templates/like.html +++ /dev/null @@ -1,76 +0,0 @@ -
- -
diff --git a/src/plugins/album-actions/templates/undislike-button.tsx b/src/plugins/album-actions/templates/undislike-button.tsx new file mode 100644 index 00000000..2757c146 --- /dev/null +++ b/src/plugins/album-actions/templates/undislike-button.tsx @@ -0,0 +1,104 @@ +export interface UnDislikeButtonProps { + onClick?: (e: MouseEvent) => void; + maskSize: string; +} + +export const UnDislikeButton = (props: UnDislikeButtonProps) => ( +
+ +
+); diff --git a/src/plugins/album-actions/templates/undislike.html b/src/plugins/album-actions/templates/undislike.html deleted file mode 100644 index 9716a3ca..00000000 --- a/src/plugins/album-actions/templates/undislike.html +++ /dev/null @@ -1,76 +0,0 @@ -
- -
diff --git a/src/plugins/album-actions/templates/unlike-button.tsx b/src/plugins/album-actions/templates/unlike-button.tsx new file mode 100644 index 00000000..e4e95882 --- /dev/null +++ b/src/plugins/album-actions/templates/unlike-button.tsx @@ -0,0 +1,104 @@ +export interface UnLikeButtonProps { + onClick?: (e: MouseEvent) => void; + maskSize: string; +} + +export const UnLikeButton = (props: UnLikeButtonProps) => ( +
+ +
+); diff --git a/src/plugins/album-actions/templates/unlike.html b/src/plugins/album-actions/templates/unlike.html deleted file mode 100644 index 5ca2952a..00000000 --- a/src/plugins/album-actions/templates/unlike.html +++ /dev/null @@ -1,76 +0,0 @@ -
- -
diff --git a/src/plugins/captions-selector/back.ts b/src/plugins/captions-selector/back.ts index ce587545..83b030ad 100644 --- a/src/plugins/captions-selector/back.ts +++ b/src/plugins/captions-selector/back.ts @@ -7,7 +7,7 @@ import { t } from '@/i18n'; export default createBackend({ start({ ipc: { handle }, window }) { handle( - 'captionsSelector', + 'ytmd:captions-selector', async (captionLabels: Record, currentIndex: string) => await prompt( { diff --git a/src/plugins/captions-selector/index.ts b/src/plugins/captions-selector/index.ts index 7ef9b22e..50252fcb 100644 --- a/src/plugins/captions-selector/index.ts +++ b/src/plugins/captions-selector/index.ts @@ -1,21 +1,20 @@ import { createPlugin } from '@/utils'; -import { YoutubePlayer } from '@/types/youtube-player'; +import { t } from '@/i18n'; import backend from './back'; import renderer, { CaptionsSelectorConfig, LanguageOptions } from './renderer'; -import { t } from '@/i18n'; + +import type { YoutubePlayer } from '@/types/youtube-player'; export default createPlugin< unknown, unknown, { - captionsSettingsButton: HTMLElement; + captionsSettingsButton?: HTMLElement; captionTrackList: LanguageOptions[] | null; api: YoutubePlayer | null; config: CaptionsSelectorConfig | null; - setConfig: (config: Partial) => void; videoChangeListener: () => void; - captionsButtonClickListener: () => void; }, CaptionsSelectorConfig >({ diff --git a/src/plugins/captions-selector/renderer.ts b/src/plugins/captions-selector/renderer.ts deleted file mode 100644 index 2d0bead6..00000000 --- a/src/plugins/captions-selector/renderer.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { ElementFromHtml } from '@/plugins/utils/renderer'; -import { createRenderer } from '@/utils'; - -import CaptionsSettingsButtonHTML from './templates/captions-settings-template.html?raw'; - -import { YoutubePlayer } from '@/types/youtube-player'; - -export interface LanguageOptions { - displayName: string; - id: string | null; - is_default: boolean; - is_servable: boolean; - is_translateable: boolean; - kind: string; - languageCode: string; // 2 length - languageName: string; - name: string | null; - vss_id: string; -} - -export interface CaptionsSelectorConfig { - enabled: boolean; - disableCaptions: boolean; - autoload: boolean; - lastCaptionsCode: string; -} - -export default createRenderer< - { - captionsSettingsButton: HTMLElement; - captionTrackList: LanguageOptions[] | null; - api: YoutubePlayer | null; - config: CaptionsSelectorConfig | null; - setConfig: (config: Partial) => void; - videoChangeListener: () => void; - captionsButtonClickListener: () => void; - }, - CaptionsSelectorConfig ->({ - captionsSettingsButton: ElementFromHtml(CaptionsSettingsButtonHTML), - captionTrackList: null, - api: null, - config: null, - setConfig: () => {}, - async captionsButtonClickListener() { - if (this.captionTrackList?.length) { - const currentCaptionTrack = this.api!.getOption( - 'captions', - 'track', - ); - let currentIndex = currentCaptionTrack - ? this.captionTrackList.indexOf( - this.captionTrackList.find( - (track) => - track.languageCode === currentCaptionTrack.languageCode, - )!, - ) - : null; - - const captionLabels = [ - ...this.captionTrackList.map((track) => track.displayName), - 'None', - ]; - - currentIndex = (await window.ipcRenderer.invoke( - 'captionsSelector', - captionLabels, - currentIndex, - )) as number; - if (currentIndex === null) { - return; - } - - const newCaptions = this.captionTrackList[currentIndex]; - this.setConfig({ lastCaptionsCode: newCaptions?.languageCode }); - if (newCaptions) { - this.api?.setOption('captions', 'track', { - languageCode: newCaptions.languageCode, - }); - } else { - this.api?.setOption('captions', 'track', {}); - } - - setTimeout(() => this.api?.playVideo()); - } - }, - videoChangeListener() { - if (this.config?.disableCaptions) { - setTimeout(() => this.api!.unloadModule('captions'), 100); - this.captionsSettingsButton.style.display = 'none'; - return; - } - - this.api!.loadModule('captions'); - - setTimeout(() => { - this.captionTrackList = - this.api!.getOption('captions', 'tracklist') ?? []; - - if (this.config!.autoload && this.config!.lastCaptionsCode) { - this.api?.setOption('captions', 'track', { - languageCode: this.config!.lastCaptionsCode, - }); - } - - this.captionsSettingsButton.style.display = this.captionTrackList?.length - ? 'inline-block' - : 'none'; - }, 250); - }, - async start({ getConfig, setConfig }) { - this.config = await getConfig(); - this.setConfig = setConfig; - }, - stop() { - document - .querySelector('.right-controls-buttons') - ?.removeChild(this.captionsSettingsButton); - document - .querySelector('#movie_player') - ?.unloadModule('captions'); - document - .querySelector('video') - ?.removeEventListener('ytmd:src-changed', this.videoChangeListener); - this.captionsSettingsButton.removeEventListener( - 'click', - this.captionsButtonClickListener, - ); - }, - onPlayerApiReady(playerApi) { - this.api = playerApi; - - document - .querySelector('.right-controls-buttons') - ?.append(this.captionsSettingsButton); - - this.captionTrackList = - this.api.getOption('captions', 'tracklist') ?? []; - - document - .querySelector('video') - ?.addEventListener('ytmd:src-changed', this.videoChangeListener); - this.captionsSettingsButton.addEventListener( - 'click', - this.captionsButtonClickListener, - ); - }, - onConfigChange(newConfig) { - this.config = newConfig; - }, -}); diff --git a/src/plugins/captions-selector/renderer.tsx b/src/plugins/captions-selector/renderer.tsx new file mode 100644 index 00000000..7fc98269 --- /dev/null +++ b/src/plugins/captions-selector/renderer.tsx @@ -0,0 +1,164 @@ +import { render } from 'solid-js/web'; +import { createSignal, Show } from 'solid-js'; + +import { createRenderer } from '@/utils'; +import { t } from '@/i18n'; + +import { CaptionsSettingButton } from './templates/captions-settings-template'; + +import type { YoutubePlayer } from '@/types/youtube-player'; +import type { AppElement } from '@/types/queue'; + +export interface LanguageOptions { + displayName: string; + id: string | null; + is_default: boolean; + is_servable: boolean; + is_translateable: boolean; + kind: string; + languageCode: string; // 2 length + languageName: string; + name: string | null; + vss_id: string; +} + +export interface CaptionsSelectorConfig { + enabled: boolean; + disableCaptions: boolean; + autoload: boolean; + lastCaptionsCode: string; +} + +const [hidden, setHidden] = createSignal(false); + +export default createRenderer< + { + captionsSettingsButton?: HTMLElement; + captionTrackList: LanguageOptions[] | null; + api: YoutubePlayer | null; + config: CaptionsSelectorConfig | null; + videoChangeListener: () => void; + }, + CaptionsSelectorConfig +>({ + captionTrackList: null, + api: null, + config: null, + videoChangeListener() { + if (this.config?.disableCaptions) { + setTimeout(() => this.api!.unloadModule('captions'), 100); + setHidden(true); + return; + } + + this.api!.loadModule('captions'); + + setTimeout(() => { + this.captionTrackList = + this.api!.getOption('captions', 'tracklist') ?? []; + + if (this.config!.autoload && this.config!.lastCaptionsCode) { + this.api?.setOption('captions', 'track', { + languageCode: this.config!.lastCaptionsCode, + }); + } + + setHidden(!this.captionTrackList?.length); + }, 250); + }, + async start({ getConfig }) { + this.config = await getConfig(); + }, + stop() { + this.api?.unloadModule('captions'); + document + .querySelector('video') + ?.removeEventListener('ytmd:src-changed', this.videoChangeListener); + if (this.captionsSettingsButton) { + document + .querySelector('.right-controls-buttons') + ?.removeChild(this.captionsSettingsButton); + } + }, + onPlayerApiReady(playerApi, { ipc, setConfig }) { + this.api = playerApi; + + render( + () => ( + + { + const appApi = document.querySelector('ytmusic-app'); + + if (this.captionTrackList?.length) { + const currentCaptionTrack = + playerApi.getOption('captions', 'track'); + + let currentIndex = currentCaptionTrack + ? this.captionTrackList.indexOf( + this.captionTrackList.find( + (track) => + track.languageCode === + currentCaptionTrack.languageCode, + )!, + ) + : null; + + const captionLabels = [ + ...this.captionTrackList.map((track) => track.displayName), + 'None', + ]; + + currentIndex = (await ipc.invoke( + 'ytmd:captions-selector', + captionLabels, + currentIndex, + )) as number; + if (currentIndex === null) { + return; + } + + const newCaptions = this.captionTrackList[currentIndex]; + setConfig({ lastCaptionsCode: newCaptions?.languageCode }); + if (newCaptions) { + playerApi.setOption('captions', 'track', { + languageCode: newCaptions.languageCode, + }); + appApi?.toastService?.show( + t('plugins.captions-selector.toast.caption-changed', { + language: newCaptions.displayName, + }), + ); + } else { + playerApi.setOption('captions', 'track', {}); + appApi?.toastService?.show( + t('plugins.captions-selector.toast.caption-disabled'), + ); + } + + setTimeout(() => playerApi.playVideo()); + } else { + appApi?.toastService?.show( + t('plugins.captions-selector.toast.no-captions'), + ); + } + }} + ref={this.captionsSettingsButton} + /> + + ), + document.querySelector('.right-controls-buttons')!, + ); + + this.captionTrackList = + this.api.getOption('captions', 'tracklist') ?? []; + + document + .querySelector('video') + ?.addEventListener('ytmd:src-changed', this.videoChangeListener); + }, + onConfigChange(newConfig) { + this.config = newConfig; + }, +}); diff --git a/src/plugins/captions-selector/templates/captions-settings-template.html b/src/plugins/captions-selector/templates/captions-settings-template.html deleted file mode 100644 index 6d30cdb4..00000000 --- a/src/plugins/captions-selector/templates/captions-settings-template.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - diff --git a/src/plugins/captions-selector/templates/captions-settings-template.tsx b/src/plugins/captions-selector/templates/captions-settings-template.tsx new file mode 100644 index 00000000..8a19bf92 --- /dev/null +++ b/src/plugins/captions-selector/templates/captions-settings-template.tsx @@ -0,0 +1,42 @@ +export interface CaptionsSettingsButtonProps { + label: string; + onClick: (event: MouseEvent) => void; +} + +export const CaptionsSettingButton = (props: CaptionsSettingsButtonProps) => ( + + +
+ + + + + +
+
+
+); diff --git a/src/plugins/downloader/renderer.ts b/src/plugins/downloader/renderer.tsx similarity index 53% rename from src/plugins/downloader/renderer.ts rename to src/plugins/downloader/renderer.tsx index 5fe8aa33..8ec9712d 100644 --- a/src/plugins/downloader/renderer.ts +++ b/src/plugins/downloader/renderer.tsx @@ -1,26 +1,24 @@ -import downloadHTML from './templates/download.html?raw'; +import { createSignal } from 'solid-js'; + +import { render } from 'solid-js/web'; import defaultConfig from '@/config/defaults'; import { getSongMenu } from '@/providers/dom-elements'; import { getSongInfo } from '@/providers/song-info-front'; - -import { LoggerPrefix } from '@/utils'; - import { t } from '@/i18n'; +import { isMusicOrVideoTrack } from '@/plugins/utils/renderer/check'; -import { defaultTrustedTypePolicy } from '@/utils/trusted-types'; - -import { ElementFromHtml } from '../utils/renderer'; +import { DownloadButton } from './templates/download'; import type { RendererContext } from '@/types/contexts'; - import type { DownloaderPluginConfig } from './index'; -let menu: Element | null = null; -let progress: Element | null = null; -const downloadButton = ElementFromHtml(downloadHTML); +let menu: HTMLElement | null = null; +let download: () => void; -let doneFirstLoad = false; +const [downloadButtonText, setDownloadButtonText] = createSignal(''); + +let buttonContainer: HTMLDivElement | null = null; const menuObserver = new MutationObserver(() => { if (!menu) { @@ -30,46 +28,24 @@ const menuObserver = new MutationObserver(() => { } } - if (menu.contains(downloadButton)) { + if ( + menu.contains(buttonContainer) || + !isMusicOrVideoTrack() || + !buttonContainer + ) { return; } - // check for video (or music) - let menuUrl = document.querySelector( - 'tp-yt-paper-listbox [tabindex="0"] #navigation-endpoint', - )?.href; - if (!menuUrl?.includes('watch?')) { - menuUrl = undefined; - // check for podcast - for (const it of document.querySelectorAll( - 'tp-yt-paper-listbox [tabindex="-1"] #navigation-endpoint', - )) { - if (it.getAttribute('href')?.includes('podcast/')) { - menuUrl = it.getAttribute('href')!; - break; - } - } - } - - if (!menuUrl && doneFirstLoad) { - return; - } - - menu.prepend(downloadButton); - progress = document.querySelector('#ytmcustom-download'); - - if (!doneFirstLoad) { - setTimeout(() => (doneFirstLoad ||= true), 500); - } + menu.prepend(buttonContainer); }); export const onRendererLoad = ({ ipc, }: RendererContext) => { - window.download = () => { + download = () => { const songMenu = getSongMenu(); + let videoUrl = songMenu - // Selector of first button which is always "Start Radio" ?.querySelector( 'ytmusic-menu-navigation-item-renderer[tabindex="0"] #navigation-endpoint', ) @@ -108,21 +84,30 @@ export const onRendererLoad = ({ }; ipc.on('downloader-feedback', (feedback: string) => { - if (progress) { - const targetHtml = feedback || t('plugins.downloader.templates.button'); - (progress.innerHTML as string | TrustedHTML) = defaultTrustedTypePolicy - ? defaultTrustedTypePolicy.createHTML(targetHtml) - : targetHtml; - } else { - console.warn( - LoggerPrefix, - t('plugins.downloader.renderer.can-not-update-progress'), - ); - } + const targetHtml = feedback || t('plugins.downloader.templates.button'); + setDownloadButtonText(targetHtml); }); }; export const onPlayerApiReady = () => { + setDownloadButtonText(t('plugins.downloader.templates.button')); + + buttonContainer = document.createElement('div'); + buttonContainer.classList.add( + 'style-scope', + 'menu-item', + 'ytmusic-menu-popup-renderer', + ); + buttonContainer.setAttribute('aria-disabled', 'false'); + buttonContainer.setAttribute('aria-selected', 'false'); + buttonContainer.setAttribute('role', 'option'); + buttonContainer.setAttribute('tabindex', '-1'); + + render( + () => , + buttonContainer, + ); + menuObserver.observe(document.querySelector('ytmusic-popup-container')!, { childList: true, subtree: true, diff --git a/src/plugins/downloader/templates/download.html b/src/plugins/downloader/templates/download.tsx similarity index 71% rename from src/plugins/downloader/templates/download.html rename to src/plugins/downloader/templates/download.tsx index 57a8830d..4b61e76a 100644 --- a/src/plugins/downloader/templates/download.html +++ b/src/plugins/downloader/templates/download.tsx @@ -1,24 +1,23 @@ -