feat: createBackend, createPreload, createRenderer

Introduced the mentioned helper methods in order to help split big plugins into manageable chunks.
This commit is contained in:
Angelos Bouklis
2023-11-28 00:06:52 +02:00
parent eaaf170cc8
commit 04d7b32d3f
4 changed files with 219 additions and 134 deletions

View 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');
},
});

View File

@ -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,
});

View 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;
},
});

View File

@ -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> }