mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 18:41:47 +00:00
feat: createBackend, createPreload, createRenderer
Introduced the mentioned helper methods in order to help split big plugins into manageable chunks.
This commit is contained in:
28
src/plugins/captions-selector/back.ts
Normal file
28
src/plugins/captions-selector/back.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import prompt from 'custom-electron-prompt';
|
||||
|
||||
import promptOptions from '@/providers/prompt-options';
|
||||
import { createBackend } from '@/utils';
|
||||
|
||||
export default createBackend({
|
||||
start({ ipc: { handle }, window }) {
|
||||
handle(
|
||||
'captionsSelector',
|
||||
async (captionLabels: Record<string, string>, currentIndex: string) =>
|
||||
await prompt(
|
||||
{
|
||||
title: 'Choose Caption',
|
||||
label: `Current Caption: ${captionLabels[currentIndex] || 'None'}`,
|
||||
type: 'select',
|
||||
value: currentIndex,
|
||||
selectOptions: captionLabels,
|
||||
resizable: true,
|
||||
...promptOptions(),
|
||||
},
|
||||
window,
|
||||
),
|
||||
);
|
||||
},
|
||||
stop({ ipc: { removeHandler } }) {
|
||||
removeHandler('captionsSelector');
|
||||
},
|
||||
});
|
||||
@ -1,32 +1,8 @@
|
||||
import prompt from 'custom-electron-prompt';
|
||||
|
||||
import promptOptions from '@/providers/prompt-options';
|
||||
import { createPlugin } from '@/utils';
|
||||
import { ElementFromHtml } from '@/plugins/utils/renderer';
|
||||
|
||||
import CaptionsSettingsButtonHTML from './templates/captions-settings-template.html?raw';
|
||||
|
||||
import { YoutubePlayer } from '@/types/youtube-player';
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
interface CaptionsSelectorConfig {
|
||||
enabled: boolean;
|
||||
disableCaptions: boolean;
|
||||
autoload: boolean;
|
||||
lastCaptionsCode: string;
|
||||
}
|
||||
import backend from './back';
|
||||
import renderer, { CaptionsSelectorConfig, LanguageOptions } from './renderer';
|
||||
|
||||
export default createPlugin<
|
||||
unknown,
|
||||
@ -36,9 +12,9 @@ export default createPlugin<
|
||||
captionTrackList: LanguageOptions[] | null;
|
||||
api: YoutubePlayer | null;
|
||||
config: CaptionsSelectorConfig | null;
|
||||
setConfig: ((config: Partial<CaptionsSelectorConfig>) => void);
|
||||
videoChangeListener: (() => void);
|
||||
captionsButtonClickListener: (() => void);
|
||||
setConfig: (config: Partial<CaptionsSelectorConfig>) => void;
|
||||
videoChangeListener: () => void;
|
||||
captionsButtonClickListener: () => void;
|
||||
},
|
||||
CaptionsSelectorConfig
|
||||
>({
|
||||
@ -72,109 +48,6 @@ export default createPlugin<
|
||||
];
|
||||
},
|
||||
|
||||
backend: {
|
||||
start({ ipc: { handle }, window }) {
|
||||
handle(
|
||||
'captionsSelector',
|
||||
async (captionLabels: Record<string, string>, currentIndex: string) =>
|
||||
await prompt(
|
||||
{
|
||||
title: 'Choose Caption',
|
||||
label: `Current Caption: ${captionLabels[currentIndex] || 'None'}`,
|
||||
type: 'select',
|
||||
value: currentIndex,
|
||||
selectOptions: captionLabels,
|
||||
resizable: true,
|
||||
...promptOptions(),
|
||||
},
|
||||
window,
|
||||
),
|
||||
);
|
||||
},
|
||||
stop({ ipc: { removeHandler } }) {
|
||||
removeHandler('captionsSelector');
|
||||
}
|
||||
},
|
||||
|
||||
renderer: {
|
||||
captionsSettingsButton: ElementFromHtml(CaptionsSettingsButtonHTML),
|
||||
captionTrackList: null,
|
||||
api: null,
|
||||
config: null,
|
||||
setConfig: () => {},
|
||||
async captionsButtonClickListener() {
|
||||
if (this.captionTrackList?.length) {
|
||||
const currentCaptionTrack = this.api!.getOption<LanguageOptions>('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<YoutubePlayer & HTMLElement>('#movie_player')?.unloadModule('captions');
|
||||
document.querySelector('video')?.removeEventListener('srcChanged', 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<LanguageOptions[]>('captions', 'tracklist') ?? [];
|
||||
|
||||
document.querySelector('video')?.addEventListener('srcChanged', this.videoChangeListener);
|
||||
this.captionsSettingsButton.addEventListener('click', this.captionsButtonClickListener);
|
||||
},
|
||||
onConfigChange(newConfig) {
|
||||
this.config = newConfig;
|
||||
},
|
||||
},
|
||||
backend,
|
||||
renderer,
|
||||
});
|
||||
|
||||
151
src/plugins/captions-selector/renderer.ts
Normal file
151
src/plugins/captions-selector/renderer.ts
Normal file
@ -0,0 +1,151 @@
|
||||
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<CaptionsSelectorConfig>) => 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<LanguageOptions>(
|
||||
'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<YoutubePlayer & HTMLElement>('#movie_player')
|
||||
?.unloadModule('captions');
|
||||
document
|
||||
.querySelector('video')
|
||||
?.removeEventListener('srcChanged', 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<LanguageOptions[]>('captions', 'tracklist') ?? [];
|
||||
|
||||
document
|
||||
.querySelector('video')
|
||||
?.addEventListener('srcChanged', this.videoChangeListener);
|
||||
this.captionsSettingsButton.addEventListener(
|
||||
'click',
|
||||
this.captionsButtonClickListener,
|
||||
);
|
||||
},
|
||||
onConfigChange(newConfig) {
|
||||
this.config = newConfig;
|
||||
},
|
||||
});
|
||||
@ -9,6 +9,8 @@ import type {
|
||||
PluginConfig,
|
||||
PluginLifecycleExtra,
|
||||
PluginLifecycleSimple,
|
||||
PluginLifecycle,
|
||||
RendererPluginLifecycle,
|
||||
} from '@/types/plugins';
|
||||
|
||||
export const createPlugin = <
|
||||
@ -29,6 +31,37 @@ export const createPlugin = <
|
||||
},
|
||||
) => def;
|
||||
|
||||
export const createBackend = <
|
||||
BackendProperties,
|
||||
Config extends PluginConfig = PluginConfig,
|
||||
>(
|
||||
back: {
|
||||
[Key in keyof BackendProperties]: BackendProperties[Key];
|
||||
} & PluginLifecycle<Config, BackendContext<Config>, BackendProperties>,
|
||||
) => back;
|
||||
|
||||
export const createPreload = <
|
||||
PreloadProperties,
|
||||
Config extends PluginConfig = PluginConfig,
|
||||
>(
|
||||
preload: {
|
||||
[Key in keyof PreloadProperties]: PreloadProperties[Key];
|
||||
} & PluginLifecycle<Config, PreloadContext<Config>, PreloadProperties>,
|
||||
) => preload;
|
||||
|
||||
export const createRenderer = <
|
||||
RendererProperties,
|
||||
Config extends PluginConfig = PluginConfig,
|
||||
>(
|
||||
renderer: {
|
||||
[Key in keyof RendererProperties]: RendererProperties[Key];
|
||||
} & RendererPluginLifecycle<
|
||||
Config,
|
||||
RendererContext<Config>,
|
||||
RendererProperties
|
||||
>,
|
||||
) => renderer;
|
||||
|
||||
type Options<Config extends PluginConfig> =
|
||||
| { ctx: 'backend'; context: BackendContext<Config> }
|
||||
| { ctx: 'preload'; context: PreloadContext<Config> }
|
||||
|
||||
Reference in New Issue
Block a user