refactor(plugin): new plugin system poc

This commit is contained in:
Su-Yong
2023-11-09 22:54:58 +09:00
parent b6e7e75ae8
commit afe6accab8
9 changed files with 279 additions and 192 deletions

View File

@ -1,4 +0,0 @@
import { PluginConfig } from '../../config/dynamic';
const config = new PluginConfig('ambient-mode');
export default config;

View File

@ -0,0 +1,36 @@
import style from './style.css';
import { createPluginBuilder } from '../utils/builder';
export type AmbientModePluginConfig = {
enabled: boolean;
quality: number;
buffer: number;
interpolationTime: number;
blur: number;
size: number;
opacity: number;
fullscreen: boolean;
};
const builder = createPluginBuilder('ambient-mode', {
name: 'Ambient Mode',
config: {
enabled: false,
quality: 50,
buffer: 30,
interpolationTime: 1500,
blur: 100,
size: 100,
opacity: 1,
fullscreen: false,
} as AmbientModePluginConfig,
styles: [style],
});
export default builder;
declare global {
interface PluginBuilderList {
[builder.id]: typeof builder;
}
}

View File

@ -1,14 +0,0 @@
import { BrowserWindow } from 'electron';
import config from './config';
import style from './style.css';
import { injectCSS } from '../utils/main';
export default (win: BrowserWindow) => {
config.subscribeAll((newConfig) => {
win.webContents.send('ambient-mode:config-change', newConfig);
});
injectCSS(win.webContents, style);
};

View File

@ -1,6 +1,4 @@
import config from './config'; import builder from './';
import { MenuTemplate } from '../../menu';
const interpolationTimeList = [0, 500, 1000, 1500, 2000, 3000, 4000, 5000]; const interpolationTimeList = [0, 500, 1000, 1500, 2000, 3000, 4000, 5000];
const qualityList = [10, 25, 50, 100, 200, 500, 1000]; const qualityList = [10, 25, 50, 100, 200, 500, 1000];
@ -9,15 +7,15 @@ const bufferList = [1, 5, 10, 20, 30];
const blurAmountList = [0, 5, 10, 25, 50, 100, 150, 200, 500]; const blurAmountList = [0, 5, 10, 25, 50, 100, 150, 200, 500];
const opacityList = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]; const opacityList = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1];
export default (): MenuTemplate => [ export default builder.createMenu(({ getConfig, setConfig }) => ([
{ {
label: 'Smoothness transition', label: 'Smoothness transition',
submenu: interpolationTimeList.map((interpolationTime) => ({ submenu: interpolationTimeList.map((interpolationTime) => ({
label: `During ${interpolationTime / 1000}s`, label: `During ${interpolationTime / 1000}s`,
type: 'radio', type: 'radio',
checked: config.get('interpolationTime') === interpolationTime, checked: getConfig().interpolationTime === interpolationTime,
click() { click() {
config.set('interpolationTime', interpolationTime); setConfig({ interpolationTime });
}, },
})), })),
}, },
@ -26,9 +24,9 @@ export default (): MenuTemplate => [
submenu: qualityList.map((quality) => ({ submenu: qualityList.map((quality) => ({
label: `${quality} pixels`, label: `${quality} pixels`,
type: 'radio', type: 'radio',
checked: config.get('quality') === quality, checked: getConfig().quality === quality,
click() { click() {
config.set('quality', quality); setConfig({ quality });
}, },
})), })),
}, },
@ -37,9 +35,9 @@ export default (): MenuTemplate => [
submenu: sizeList.map((size) => ({ submenu: sizeList.map((size) => ({
label: `${size}%`, label: `${size}%`,
type: 'radio', type: 'radio',
checked: config.get('size') === size, checked: getConfig().size === size,
click() { click() {
config.set('size', size); setConfig({ size });
}, },
})), })),
}, },
@ -48,9 +46,9 @@ export default (): MenuTemplate => [
submenu: bufferList.map((buffer) => ({ submenu: bufferList.map((buffer) => ({
label: `${buffer}`, label: `${buffer}`,
type: 'radio', type: 'radio',
checked: config.get('buffer') === buffer, checked: getConfig().buffer === buffer,
click() { click() {
config.set('buffer', buffer); setConfig({ buffer });
}, },
})), })),
}, },
@ -59,9 +57,9 @@ export default (): MenuTemplate => [
submenu: opacityList.map((opacity) => ({ submenu: opacityList.map((opacity) => ({
label: `${opacity * 100}%`, label: `${opacity * 100}%`,
type: 'radio', type: 'radio',
checked: config.get('opacity') === opacity, checked: getConfig().opacity === opacity,
click() { click() {
config.set('opacity', opacity); setConfig({ opacity });
}, },
})), })),
}, },
@ -70,18 +68,18 @@ export default (): MenuTemplate => [
submenu: blurAmountList.map((blur) => ({ submenu: blurAmountList.map((blur) => ({
label: `${blur} pixels`, label: `${blur} pixels`,
type: 'radio', type: 'radio',
checked: config.get('blur') === blur, checked: getConfig().blur === blur,
click() { click() {
config.set('blur', blur); setConfig({ blur });
}, },
})), })),
}, },
{ {
label: 'Using fullscreen', label: 'Using fullscreen',
type: 'checkbox', type: 'checkbox',
checked: config.get('fullscreen'), checked: getConfig().fullscreen,
click(item) { click(item) {
config.set('fullscreen', item.checked); setConfig({ fullscreen: item.checked });
}, },
}, },
]; ]));

View File

@ -1,14 +1,20 @@
import type { ConfigType } from '../../config/dynamic'; import builder from './index';
export default (config: ConfigType<'ambient-mode'>) => { export default builder.createRenderer(({ getConfig }) => {
let interpolationTime = config.interpolationTime; // interpolation time (ms) const initConfigData = getConfig();
let buffer = config.buffer; // frame
let qualityRatio = config.quality; // width size (pixel)
let sizeRatio = config.size / 100; // size ratio (percent)
let blur = config.blur; // blur (pixel)
let opacity = config.opacity; // opacity (percent)
let isFullscreen = config.fullscreen; // fullscreen (boolean)
let interpolationTime = initConfigData.interpolationTime;
let buffer = initConfigData.buffer;
let qualityRatio =initConfigData.quality;
let sizeRatio = initConfigData.size / 100;
let blur = initConfigData.blur;
let opacity = initConfigData.opacity;
let isFullscreen = initConfigData.fullscreen;
let update: (() => void) | null = null;
return {
onLoad() {
let unregister: (() => void) | null = null; let unregister: (() => void) | null = null;
const injectBlurVideo = (): (() => void) | null => { const injectBlurVideo = (): (() => void) | null => {
@ -78,6 +84,7 @@ export default (config: ConfigType<'ambient-mode'>) => {
blurCanvas.style.setProperty('--blur', `${blur}px`); blurCanvas.style.setProperty('--blur', `${blur}px`);
blurCanvas.style.setProperty('--opacity', `${opacity}`); blurCanvas.style.setProperty('--opacity', `${opacity}`);
}; };
update = applyVideoAttributes;
const observer = new MutationObserver((mutations) => { const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => { mutations.forEach((mutation) => {
@ -89,18 +96,6 @@ export default (config: ConfigType<'ambient-mode'>) => {
const resizeObserver = new ResizeObserver(() => { const resizeObserver = new ResizeObserver(() => {
applyVideoAttributes(); applyVideoAttributes();
}); });
const onConfigSync = (_: Electron.IpcRendererEvent, newConfig: ConfigType<'ambient-mode'>) => {
if (typeof newConfig.interpolationTime === 'number') interpolationTime = newConfig.interpolationTime;
if (typeof newConfig.buffer === 'number') buffer = newConfig.buffer;
if (typeof newConfig.quality === 'number') qualityRatio = newConfig.quality;
if (typeof newConfig.size === 'number') sizeRatio = newConfig.size / 100;
if (typeof newConfig.blur === 'number') blur = newConfig.blur;
if (typeof newConfig.opacity === 'number') opacity = newConfig.opacity;
if (typeof newConfig.fullscreen === 'boolean') isFullscreen = newConfig.fullscreen;
applyVideoAttributes();
};
window.ipcRenderer.on('ambient-mode:config-change', onConfigSync);
/* hooking */ /* hooking */
let canvasInterval: NodeJS.Timeout | null = null; let canvasInterval: NodeJS.Timeout | null = null;
@ -133,7 +128,6 @@ export default (config: ConfigType<'ambient-mode'>) => {
observer.disconnect(); observer.disconnect();
resizeObserver.disconnect(); resizeObserver.disconnect();
window.ipcRenderer.removeListener('ambient-mode:config-change', onConfigSync);
window.removeEventListener('resize', applyVideoAttributes); window.removeEventListener('resize', applyVideoAttributes);
wrapper.removeChild(blurCanvas); wrapper.removeChild(blurCanvas);
@ -162,4 +156,17 @@ export default (config: ConfigType<'ambient-mode'>) => {
if (playerPage) { if (playerPage) {
observer.observe(playerPage, { attributes: true }); observer.observe(playerPage, { attributes: true });
} }
}; },
onConfigChange(newConfig) {
if (typeof newConfig.interpolationTime === 'number') interpolationTime = newConfig.interpolationTime;
if (typeof newConfig.buffer === 'number') buffer = newConfig.buffer;
if (typeof newConfig.quality === 'number') qualityRatio = newConfig.quality;
if (typeof newConfig.size === 'number') sizeRatio = newConfig.size / 100;
if (typeof newConfig.blur === 'number') blur = newConfig.blur;
if (typeof newConfig.opacity === 'number') opacity = newConfig.opacity;
if (typeof newConfig.fullscreen === 'boolean') isFullscreen = newConfig.fullscreen;
update?.();
},
};
});

View File

@ -0,0 +1,61 @@
import type {
BrowserWindow,
MenuItemConstructorOptions,
} from 'electron';
export type PluginBaseConfig = {
enabled: boolean;
};
export type BasePlugin<Config extends PluginBaseConfig> = {
onLoad?: () => void;
onConfigChange?: (newConfig: Config) => void;
}
export type RendererPlugin<Config extends PluginBaseConfig> = BasePlugin<Config>;
export type MainPlugin<Config extends PluginBaseConfig> = Omit<BasePlugin<Config>, 'onLoad'> & {
onLoad?: (window: BrowserWindow) => void;
};
export type PreloadPlugin<Config extends PluginBaseConfig> = BasePlugin<Config>;
type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>;
};
export type PluginContext<Config extends PluginBaseConfig> = {
getConfig: () => Config;
setConfig: (config: DeepPartial<Config>) => void;
send: (event: string, ...args: unknown[]) => void;
on: (event: string, listener: (...args: unknown[]) => void) => void;
};
type IF<T> = (args: T) => T;
export type PluginBuilder<ID extends string, Config extends PluginBaseConfig> = {
createRenderer: IF<(context: PluginContext<Config>) => RendererPlugin<Config>>;
createMain: IF<(context: PluginContext<Config>) => MainPlugin<Config>>;
createPreload: IF<(context: PluginContext<Config>) => PreloadPlugin<Config>>;
createMenu: IF<(context: PluginContext<Config>) => MenuItemConstructorOptions[]>;
id: ID;
config: Config;
name?: string;
styles?: string[];
};
export type PluginBuilderOptions<Config extends PluginBaseConfig = PluginBaseConfig> = {
name?: string;
config: Config;
styles?: string[];
}
export const createPluginBuilder = <ID extends string, Config extends PluginBaseConfig>(
id: ID,
options: PluginBuilderOptions<Config>,
): PluginBuilder<ID, Config> => ({
createRenderer: (plugin) => plugin,
createMain: (plugin) => plugin,
createPreload: (plugin) => plugin,
createMenu: (plugin) => plugin,
id,
name: options.name,
config: options.config,
styles: options.styles,
});

View File

@ -26,4 +26,9 @@ export interface MenuPlugin<ConfigType extends Config> extends Plugin<ConfigType
onEnable: (config: ConfigType) => void; onEnable: (config: ConfigType) => void;
} }
export const defineConfig = <ConfigType extends Config>(config: ConfigType) => config; const defaultPluginConfig: Record<string, unknown> = {};
export const definePluginConfig = <T>(id: string, defaultValue: T): T => {
defaultPluginConfig[id] = defaultValue;
return defaultValue;
};

View File

@ -1,3 +0,0 @@
import type { Config, RendererPlugin } from '../common';
export const defineRendererPlugin = <ConfigType extends Config>(plugin: RendererPlugin<ConfigType>) => plugin;

View File

@ -0,0 +1 @@
export type PluginConfig<T extends keyof PluginBuilderList> = PluginBuilderList[T]['config'];