mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-12 02:51:46 +00:00
feat: enable context-isolation (#1361)
This commit is contained in:
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/await-thenable */
|
||||
/* renderer */
|
||||
|
||||
import { blockers } from './blocker-types';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { FastAverageColor } from 'fast-average-color';
|
||||
|
||||
import { ConfigType } from '../../config/dynamic';
|
||||
import type { ConfigType } from '../../config/dynamic';
|
||||
|
||||
function hexToHSL(H: string) {
|
||||
// Convert hex to RGB first
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
import { ConfigType } from '../../config/dynamic';
|
||||
import type { ConfigType } from '../../config/dynamic';
|
||||
|
||||
export default (config: ConfigType<'ambient-mode'>) => {
|
||||
let interpolationTime = config.interpolationTime; // interpolation time (ms)
|
||||
@ -30,7 +28,7 @@ export default (config: ConfigType<'ambient-mode'>) => {
|
||||
/* effect */
|
||||
let lastEffectWorkId: number | null = null;
|
||||
let lastImageData: ImageData | null = null;
|
||||
|
||||
|
||||
const onSync = () => {
|
||||
if (typeof lastEffectWorkId === 'number') cancelAnimationFrame(lastEffectWorkId);
|
||||
|
||||
@ -40,6 +38,7 @@ export default (config: ConfigType<'ambient-mode'>) => {
|
||||
const width = qualityRatio;
|
||||
let height = Math.max(Math.floor(blurCanvas.height / blurCanvas.width * width), 1);
|
||||
if (!Number.isFinite(height)) height = width;
|
||||
if (!height) return;
|
||||
|
||||
context.globalAlpha = 1;
|
||||
if (lastImageData) {
|
||||
@ -50,8 +49,7 @@ export default (config: ConfigType<'ambient-mode'>) => {
|
||||
}
|
||||
context.drawImage(video, 0, 0, width, height);
|
||||
|
||||
const nowImageData = context.getImageData(0, 0, width, height);
|
||||
lastImageData = nowImageData;
|
||||
lastImageData = context.getImageData(0, 0, width, height); // current image data
|
||||
|
||||
lastEffectWorkId = null;
|
||||
});
|
||||
@ -102,8 +100,8 @@ export default (config: ConfigType<'ambient-mode'>) => {
|
||||
|
||||
applyVideoAttributes();
|
||||
};
|
||||
ipcRenderer.on('ambient-mode:config-change', onConfigSync);
|
||||
|
||||
window.ipcRenderer.on('ambient-mode:config-change', onConfigSync);
|
||||
|
||||
/* hooking */
|
||||
let canvasInterval: NodeJS.Timeout | null = null;
|
||||
canvasInterval = setInterval(onSync, Math.max(1, Math.ceil(1000 / buffer)));
|
||||
@ -135,17 +133,17 @@ export default (config: ConfigType<'ambient-mode'>) => {
|
||||
|
||||
observer.disconnect();
|
||||
resizeObserver.disconnect();
|
||||
ipcRenderer.off('ambient-mode:config-change', onConfigSync);
|
||||
window.ipcRenderer.removeListener('ambient-mode:config-change', onConfigSync);
|
||||
window.removeEventListener('resize', applyVideoAttributes);
|
||||
|
||||
wrapper.removeChild(blurCanvas);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
const playerPage = document.querySelector<HTMLElement>('#player-page');
|
||||
const ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
|
||||
|
||||
|
||||
const observer = new MutationObserver((mutationsList) => {
|
||||
for (const mutation of mutationsList) {
|
||||
if (mutation.type === 'attributes') {
|
||||
@ -164,4 +162,4 @@ export default (config: ConfigType<'ambient-mode'>) => {
|
||||
if (playerPage) {
|
||||
observer.observe(playerPage, { attributes: true });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
4
src/plugins/captions-selector/config-renderer.ts
Normal file
4
src/plugins/captions-selector/config-renderer.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { PluginConfig } from '../../config/dynamic-renderer';
|
||||
|
||||
const configRenderer = new PluginConfig('captions-selector', { enableFront: true });
|
||||
export default configRenderer;
|
||||
@ -1,13 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/await-thenable */
|
||||
/* renderer */
|
||||
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
import configProvider from './config';
|
||||
import configProvider from './config-renderer';
|
||||
|
||||
import CaptionsSettingsButtonHTML from './templates/captions-settings-template.html';
|
||||
|
||||
import { ElementFromHtml } from '../utils';
|
||||
import { ElementFromHtml } from '../utils-renderer';
|
||||
import { YoutubePlayer } from '../../types/youtube-player';
|
||||
|
||||
import type { ConfigType } from '../../config/dynamic';
|
||||
@ -25,18 +20,17 @@ interface LanguageOptions {
|
||||
vss_id: string;
|
||||
}
|
||||
|
||||
let config: ConfigType<'captions-selector'>;
|
||||
let captionsSelectorConfig: ConfigType<'captions-selector'>;
|
||||
|
||||
const $ = <Element extends HTMLElement>(selector: string): Element => document.querySelector(selector)!;
|
||||
|
||||
const captionsSettingsButton = ElementFromHtml(CaptionsSettingsButtonHTML);
|
||||
|
||||
export default async () => {
|
||||
// RENDERER
|
||||
config = await configProvider.getAll();
|
||||
export default () => {
|
||||
captionsSelectorConfig = configProvider.getAll();
|
||||
|
||||
configProvider.subscribeAll((newConfig) => {
|
||||
config = newConfig;
|
||||
captionsSelectorConfig = newConfig;
|
||||
});
|
||||
document.addEventListener('apiLoaded', (event) => setup(event.detail), { once: true, passive: true });
|
||||
};
|
||||
@ -47,7 +41,7 @@ function setup(api: YoutubePlayer) {
|
||||
let captionTrackList = api.getOption<LanguageOptions[]>('captions', 'tracklist') ?? [];
|
||||
|
||||
$('video').addEventListener('srcChanged', () => {
|
||||
if (config.disableCaptions) {
|
||||
if (captionsSelectorConfig.disableCaptions) {
|
||||
setTimeout(() => api.unloadModule('captions'), 100);
|
||||
captionsSettingsButton.style.display = 'none';
|
||||
return;
|
||||
@ -58,9 +52,9 @@ function setup(api: YoutubePlayer) {
|
||||
setTimeout(() => {
|
||||
captionTrackList = api.getOption('captions', 'tracklist') ?? [];
|
||||
|
||||
if (config.autoload && config.lastCaptionsCode) {
|
||||
if (captionsSelectorConfig.autoload && captionsSelectorConfig.lastCaptionsCode) {
|
||||
api.setOption('captions', 'track', {
|
||||
languageCode: config.lastCaptionsCode,
|
||||
languageCode: captionsSelectorConfig.lastCaptionsCode,
|
||||
});
|
||||
}
|
||||
|
||||
@ -82,7 +76,7 @@ function setup(api: YoutubePlayer) {
|
||||
'None',
|
||||
];
|
||||
|
||||
currentIndex = await ipcRenderer.invoke('captionsSelector', captionLabels, currentIndex) as number;
|
||||
currentIndex = await window.ipcRenderer.invoke('captionsSelector', captionLabels, currentIndex) as number;
|
||||
if (currentIndex === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
4
src/plugins/crossfade/config-renderer.ts
Normal file
4
src/plugins/crossfade/config-renderer.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { PluginConfig } from '../../config/dynamic-renderer';
|
||||
|
||||
const config = new PluginConfig('crossfade', { enableFront: true });
|
||||
export default config;
|
||||
@ -1,13 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/await-thenable */
|
||||
/* renderer */
|
||||
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { Howl } from 'howler';
|
||||
|
||||
// Extracted from https://github.com/bitfasching/VolumeFader
|
||||
import { VolumeFader } from './fader';
|
||||
|
||||
import configProvider from './config';
|
||||
import configProvider from './config-renderer';
|
||||
|
||||
import defaultConfigs from '../../config/defaults';
|
||||
|
||||
@ -19,11 +15,11 @@ let waitForTransition: Promise<unknown>;
|
||||
|
||||
const defaultConfig = defaultConfigs.plugins.crossfade;
|
||||
|
||||
let config: ConfigType<'crossfade'>;
|
||||
let crossfadeConfig: ConfigType<'crossfade'>;
|
||||
|
||||
const configGetNumber = (key: keyof ConfigType<'crossfade'>): number => Number(config[key]) || (defaultConfig[key] as number);
|
||||
const configGetNumber = (key: keyof ConfigType<'crossfade'>): number => Number(crossfadeConfig[key]) || (defaultConfig[key] as number);
|
||||
|
||||
const getStreamURL = async (videoID: string) => ipcRenderer.invoke('audio-url', videoID) as Promise<string>;
|
||||
const getStreamURL = async (videoID: string) => window.ipcRenderer.invoke('audio-url', videoID) as Promise<string>;
|
||||
|
||||
const getVideoIDFromURL = (url: string) => new URLSearchParams(url.split('?')?.at(-1)).get('v');
|
||||
|
||||
@ -119,7 +115,7 @@ const onApiLoaded = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
await createAudioForCrossfade(url);
|
||||
createAudioForCrossfade(url);
|
||||
});
|
||||
};
|
||||
|
||||
@ -150,11 +146,11 @@ const crossfade = (cb: () => void) => {
|
||||
});
|
||||
};
|
||||
|
||||
export default async () => {
|
||||
config = await configProvider.getAll();
|
||||
export default () => {
|
||||
crossfadeConfig = configProvider.getAll();
|
||||
|
||||
configProvider.subscribeAll((newConfig) => {
|
||||
config = newConfig;
|
||||
crossfadeConfig = newConfig;
|
||||
});
|
||||
|
||||
document.addEventListener('apiLoaded', onApiLoaded, {
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
import downloadHTML from './templates/download.html';
|
||||
|
||||
import defaultConfig from '../../config/defaults';
|
||||
import { getSongMenu } from '../../providers/dom-elements';
|
||||
import { ElementFromHtml } from '../utils';
|
||||
import { ElementFromHtml } from '../utils-renderer';
|
||||
import { getSongInfo } from '../../providers/song-info-front';
|
||||
|
||||
let menu: Element | null = null;
|
||||
@ -13,55 +11,55 @@ const downloadButton = ElementFromHtml(downloadHTML);
|
||||
|
||||
let doneFirstLoad = false;
|
||||
|
||||
const menuObserver = new MutationObserver(() => {
|
||||
if (!menu) {
|
||||
menu = getSongMenu();
|
||||
if (!menu) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (menu.contains(downloadButton)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const menuUrl = document.querySelector<HTMLAnchorElement>('tp-yt-paper-listbox [tabindex="0"] #navigation-endpoint')?.href;
|
||||
if (!menuUrl?.includes('watch?') && doneFirstLoad) {
|
||||
return;
|
||||
}
|
||||
|
||||
menu.prepend(downloadButton);
|
||||
progress = document.querySelector('#ytmcustom-download');
|
||||
|
||||
if (doneFirstLoad) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => doneFirstLoad ||= true, 500);
|
||||
});
|
||||
|
||||
window.download = () => {
|
||||
let videoUrl = getSongMenu()
|
||||
// Selector of first button which is always "Start Radio"
|
||||
?.querySelector('ytmusic-menu-navigation-item-renderer[tabindex="-1"] #navigation-endpoint')
|
||||
?.getAttribute('href');
|
||||
if (videoUrl) {
|
||||
if (videoUrl.startsWith('watch?')) {
|
||||
videoUrl = defaultConfig.url + '/' + videoUrl;
|
||||
}
|
||||
|
||||
if (videoUrl.includes('?playlist=')) {
|
||||
ipcRenderer.send('download-playlist-request', videoUrl);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
videoUrl = getSongInfo().url || window.location.href;
|
||||
}
|
||||
|
||||
ipcRenderer.send('download-song', videoUrl);
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const menuObserver = new MutationObserver(() => {
|
||||
if (!menu) {
|
||||
menu = getSongMenu();
|
||||
if (!menu) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (menu.contains(downloadButton)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const menuUrl = document.querySelector<HTMLAnchorElement>('tp-yt-paper-listbox [tabindex="-1"] #navigation-endpoint')?.href;
|
||||
if (!menuUrl?.includes('watch?') && doneFirstLoad) {
|
||||
return;
|
||||
}
|
||||
|
||||
menu.prepend(downloadButton);
|
||||
progress = document.querySelector('#ytmcustom-download');
|
||||
|
||||
if (doneFirstLoad) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => doneFirstLoad ||= true, 500);
|
||||
});
|
||||
|
||||
window.download = () => {
|
||||
let videoUrl = getSongMenu()
|
||||
// Selector of first button which is always "Start Radio"
|
||||
?.querySelector('ytmusic-menu-navigation-item-renderer[tabindex="-1"] #navigation-endpoint')
|
||||
?.getAttribute('href');
|
||||
if (videoUrl) {
|
||||
if (videoUrl.startsWith('watch?')) {
|
||||
videoUrl = defaultConfig.url + '/' + videoUrl;
|
||||
}
|
||||
|
||||
if (videoUrl.includes('?playlist=')) {
|
||||
window.ipcRenderer.send('download-playlist-request', videoUrl);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
videoUrl = getSongInfo().url || window.location.href;
|
||||
}
|
||||
|
||||
window.ipcRenderer.send('download-song', videoUrl);
|
||||
};
|
||||
|
||||
document.addEventListener('apiLoaded', () => {
|
||||
menuObserver.observe(document.querySelector('ytmusic-popup-container')!, {
|
||||
childList: true,
|
||||
@ -69,7 +67,7 @@ export default () => {
|
||||
});
|
||||
}, { once: true, passive: true });
|
||||
|
||||
ipcRenderer.on('downloader-feedback', (_, feedback: string) => {
|
||||
window.ipcRenderer.on('downloader-feedback', (_, feedback: string) => {
|
||||
if (progress) {
|
||||
progress.innerHTML = feedback || 'Download';
|
||||
} else {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { register } from 'electron-localshortcut';
|
||||
|
||||
import { BrowserWindow, Menu, MenuItem, ipcMain } from 'electron';
|
||||
import { BrowserWindow, Menu, MenuItem, ipcMain, nativeImage } from 'electron';
|
||||
|
||||
import titlebarStyle from './titlebar.css';
|
||||
|
||||
@ -64,4 +64,9 @@ export default (win: BrowserWindow) => {
|
||||
win.on('maximize', () => win.webContents.send('window-maximize'));
|
||||
ipcMain.handle('window-unmaximize', () => win.unmaximize());
|
||||
win.on('unmaximize', () => win.webContents.send('window-unmaximize'));
|
||||
|
||||
ipcMain.handle('image-path-to-data-url', (_, imagePath: string) => {
|
||||
const nativeImageIcon = nativeImage.createFromPath(imagePath);
|
||||
return nativeImageIcon?.toDataURL();
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { ipcRenderer, Menu } from 'electron';
|
||||
|
||||
import { createPanel } from './menu/panel';
|
||||
|
||||
import logo from './assets/menu.svg';
|
||||
@ -8,8 +6,7 @@ import minimize from './assets/minimize.svg';
|
||||
import maximize from './assets/maximize.svg';
|
||||
import unmaximize from './assets/unmaximize.svg';
|
||||
|
||||
import { isEnabled } from '../../config/plugins';
|
||||
import config from '../../config';
|
||||
import type { Menu } from 'electron';
|
||||
|
||||
function $<E extends Element = Element>(selector: string) {
|
||||
return document.querySelector<E>(selector);
|
||||
@ -19,8 +16,8 @@ const isMacOS = navigator.userAgent.includes('Macintosh');
|
||||
const isNotWindowsOrMacOS = !navigator.userAgent.includes('Windows') && !isMacOS;
|
||||
|
||||
export default async () => {
|
||||
const hideDOMWindowControls = config.get('plugins.in-app-menu.hideDOMWindowControls');
|
||||
let hideMenu = config.get('options.hideMenu');
|
||||
const hideDOMWindowControls = window.mainConfig.get('plugins.in-app-menu.hideDOMWindowControls');
|
||||
let hideMenu = window.mainConfig.get('options.hideMenu');
|
||||
const titleBar = document.createElement('title-bar');
|
||||
const navBar = document.querySelector<HTMLDivElement>('#nav-bar-background');
|
||||
let maximizeButton: HTMLButtonElement;
|
||||
@ -42,7 +39,7 @@ export default async () => {
|
||||
};
|
||||
logo.onclick = logoClick;
|
||||
|
||||
ipcRenderer.on('toggleMenu', logoClick);
|
||||
window.ipcRenderer.on('toggleMenu', logoClick);
|
||||
|
||||
if (!isMacOS) titleBar.appendChild(logo);
|
||||
document.body.appendChild(titleBar);
|
||||
@ -55,10 +52,10 @@ export default async () => {
|
||||
const minimizeButton = document.createElement('button');
|
||||
minimizeButton.classList.add('window-control');
|
||||
minimizeButton.appendChild(minimize);
|
||||
minimizeButton.onclick = () => ipcRenderer.invoke('window-minimize');
|
||||
minimizeButton.onclick = () => window.ipcRenderer.invoke('window-minimize');
|
||||
|
||||
maximizeButton = document.createElement('button');
|
||||
if (await ipcRenderer.invoke('window-is-maximized')) {
|
||||
if (await window.ipcRenderer.invoke('window-is-maximized')) {
|
||||
maximizeButton.classList.add('window-control');
|
||||
maximizeButton.appendChild(unmaximize);
|
||||
} else {
|
||||
@ -66,27 +63,27 @@ export default async () => {
|
||||
maximizeButton.appendChild(maximize);
|
||||
}
|
||||
maximizeButton.onclick = async () => {
|
||||
if (await ipcRenderer.invoke('window-is-maximized')) {
|
||||
if (await window.ipcRenderer.invoke('window-is-maximized')) {
|
||||
// change icon to maximize
|
||||
maximizeButton.removeChild(maximizeButton.firstChild!);
|
||||
maximizeButton.appendChild(maximize);
|
||||
|
||||
// call unmaximize
|
||||
await ipcRenderer.invoke('window-unmaximize');
|
||||
await window.ipcRenderer.invoke('window-unmaximize');
|
||||
} else {
|
||||
// change icon to unmaximize
|
||||
maximizeButton.removeChild(maximizeButton.firstChild!);
|
||||
maximizeButton.appendChild(unmaximize);
|
||||
|
||||
// call maximize
|
||||
await ipcRenderer.invoke('window-maximize');
|
||||
await window.ipcRenderer.invoke('window-maximize');
|
||||
}
|
||||
};
|
||||
|
||||
const closeButton = document.createElement('button');
|
||||
closeButton.classList.add('window-control');
|
||||
closeButton.appendChild(close);
|
||||
closeButton.onclick = () => ipcRenderer.invoke('window-close');
|
||||
closeButton.onclick = () => window.ipcRenderer.invoke('window-close');
|
||||
|
||||
// Create a container div for the window control buttons
|
||||
const windowControlsContainer = document.createElement('div');
|
||||
@ -118,7 +115,7 @@ export default async () => {
|
||||
if (child !== logo) child.remove();
|
||||
});
|
||||
|
||||
const menu = await ipcRenderer.invoke('get-menu') as Menu | null;
|
||||
const menu = await window.ipcRenderer.invoke('get-menu') as Menu | null;
|
||||
if (!menu) return;
|
||||
|
||||
menu.items.forEach((menuItem) => {
|
||||
@ -137,22 +134,22 @@ export default async () => {
|
||||
|
||||
document.title = 'Youtube Music';
|
||||
|
||||
ipcRenderer.on('refreshMenu', () => updateMenu());
|
||||
ipcRenderer.on('window-maximize', () => {
|
||||
window.ipcRenderer.on('refreshMenu', () => updateMenu());
|
||||
window.ipcRenderer.on('window-maximize', () => {
|
||||
if (isNotWindowsOrMacOS && !hideDOMWindowControls && maximizeButton.firstChild) {
|
||||
maximizeButton.removeChild(maximizeButton.firstChild);
|
||||
maximizeButton.appendChild(unmaximize);
|
||||
}
|
||||
});
|
||||
ipcRenderer.on('window-unmaximize', () => {
|
||||
window.ipcRenderer.on('window-unmaximize', () => {
|
||||
if (isNotWindowsOrMacOS && !hideDOMWindowControls && maximizeButton.firstChild) {
|
||||
maximizeButton.removeChild(maximizeButton.firstChild);
|
||||
maximizeButton.appendChild(unmaximize);
|
||||
}
|
||||
});
|
||||
|
||||
if (isEnabled('picture-in-picture')) {
|
||||
ipcRenderer.on('pip-toggle', () => {
|
||||
if (window.mainConfig.plugins.isEnabled('picture-in-picture')) {
|
||||
window.ipcRenderer.on('pip-toggle', () => {
|
||||
updateMenu();
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { nativeImage, type MenuItem, ipcRenderer, Menu } from 'electron';
|
||||
|
||||
import Icons from './icons';
|
||||
|
||||
import { ElementFromHtml } from '../../utils';
|
||||
import { ElementFromHtml } from '../../utils-renderer';
|
||||
|
||||
import type { MenuItem } from 'electron';
|
||||
|
||||
interface PanelOptions {
|
||||
placement?: 'bottom' | 'right';
|
||||
@ -19,7 +19,7 @@ export const createPanel = (
|
||||
const panel = document.createElement('menu-panel');
|
||||
panel.style.zIndex = `${options.order}`;
|
||||
|
||||
const updateIconState = (iconWrapper: HTMLElement, item: MenuItem) => {
|
||||
const updateIconState = async (iconWrapper: HTMLElement, item: MenuItem) => {
|
||||
if (item.type === 'checkbox') {
|
||||
if (item.checked) iconWrapper.innerHTML = Icons.checkbox;
|
||||
else iconWrapper.innerHTML = '';
|
||||
@ -27,8 +27,8 @@ export const createPanel = (
|
||||
if (item.checked) iconWrapper.innerHTML = Icons.radio.checked;
|
||||
else iconWrapper.innerHTML = Icons.radio.unchecked;
|
||||
} else {
|
||||
const nativeImageIcon = typeof item.icon === 'string' ? nativeImage.createFromPath(item.icon) : item.icon;
|
||||
const iconURL = nativeImageIcon?.toDataURL();
|
||||
const iconURL = typeof item.icon === 'string' ?
|
||||
await window.ipcRenderer.invoke('image-path-to-data-url') as string : item.icon?.toDataURL();
|
||||
|
||||
if (iconURL) iconWrapper.style.background = `url(${iconURL})`;
|
||||
}
|
||||
@ -46,8 +46,8 @@ export const createPanel = (
|
||||
menu.append(item.label);
|
||||
|
||||
menu.addEventListener('click', async () => {
|
||||
await ipcRenderer.invoke('menu-event', item.commandId);
|
||||
const menuItem = await ipcRenderer.invoke('get-menu-by-id', item.commandId) as MenuItem | null;
|
||||
await window.ipcRenderer.invoke('menu-event', item.commandId);
|
||||
const menuItem = await window.ipcRenderer.invoke('get-menu-by-id', item.commandId) as MenuItem | null;
|
||||
|
||||
if (menuItem) {
|
||||
updateIconState(iconWrapper, menuItem);
|
||||
@ -56,7 +56,7 @@ export const createPanel = (
|
||||
await Promise.all(
|
||||
radioGroups.map(async ([item, iconWrapper]) => {
|
||||
if (item.commandId === menuItem.commandId) return;
|
||||
const newItem = await ipcRenderer.invoke('get-menu-by-id', item.commandId) as MenuItem | null;
|
||||
const newItem = await window.ipcRenderer.invoke('get-menu-by-id', item.commandId) as MenuItem | null;
|
||||
|
||||
if (newItem) updateIconState(iconWrapper, newItem);
|
||||
})
|
||||
|
||||
@ -1,6 +1,3 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
import is from 'electron-is';
|
||||
|
||||
import type { SongInfo } from '../../providers/song-info';
|
||||
|
||||
export default () => {
|
||||
@ -22,9 +19,9 @@ export default () => {
|
||||
}
|
||||
};
|
||||
|
||||
let unregister: (() => void) | null = null;
|
||||
let unregister: (() => void) | null = null;
|
||||
|
||||
ipcRenderer.on('update-song-info', (_, extractedSongInfo: SongInfo) => {
|
||||
window.ipcRenderer.on('update-song-info', (_, extractedSongInfo: SongInfo) => {
|
||||
unregister?.();
|
||||
|
||||
setTimeout(async () => {
|
||||
@ -38,7 +35,7 @@ export default () => {
|
||||
// Check if disabled
|
||||
if (!tabs.lyrics?.hasAttribute('disabled')) return;
|
||||
|
||||
const lyrics = await ipcRenderer.invoke(
|
||||
const lyrics = await window.ipcRenderer.invoke(
|
||||
'search-genius-lyrics',
|
||||
extractedSongInfo,
|
||||
) as string | null;
|
||||
@ -50,7 +47,7 @@ export default () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is.dev()) {
|
||||
if (window.electronIs.dev()) {
|
||||
console.log('Fetched lyrics from Genius');
|
||||
}
|
||||
|
||||
@ -58,7 +55,7 @@ export default () => {
|
||||
const lyricsContainer = document.querySelector(
|
||||
'[page-type="MUSIC_PAGE_TYPE_TRACK_LYRICS"] > ytmusic-message-renderer',
|
||||
);
|
||||
|
||||
|
||||
if (lyricsContainer) {
|
||||
callback?.();
|
||||
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
import forwardHTML from './templates/forward.html';
|
||||
import backHTML from './templates/back.html';
|
||||
|
||||
import { ElementFromHtml } from '../utils';
|
||||
import { ElementFromHtml } from '../utils-renderer';
|
||||
|
||||
export function run() {
|
||||
ipcRenderer.on('navigation-css-ready', () => {
|
||||
window.ipcRenderer.on('navigation-css-ready', () => {
|
||||
const forwardButton = ElementFromHtml(forwardHTML);
|
||||
const backButton = ElementFromHtml(backHTML);
|
||||
const menu = document.querySelector('#right-content');
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { toKeyEvent } from 'keyboardevent-from-electron-accelerator';
|
||||
import keyEventAreEqual from 'keyboardevents-areequal';
|
||||
|
||||
@ -6,7 +5,7 @@ import pipHTML from './templates/picture-in-picture.html';
|
||||
|
||||
import { getSongMenu } from '../../providers/dom-elements';
|
||||
|
||||
import { ElementFromHtml } from '../utils';
|
||||
import { ElementFromHtml } from '../utils-renderer';
|
||||
|
||||
import type { ConfigType } from '../../config/dynamic';
|
||||
|
||||
@ -85,7 +84,7 @@ const togglePictureInPicture = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
ipcRenderer.send('picture-in-picture');
|
||||
window.ipcRenderer.send('picture-in-picture');
|
||||
return false;
|
||||
};
|
||||
// For UI (HTML)
|
||||
@ -105,7 +104,7 @@ const listenForToggle = () => {
|
||||
|
||||
const titlebar = $<HTMLElement>('.cet-titlebar');
|
||||
|
||||
ipcRenderer.on('pip-toggle', (_, isPip: boolean) => {
|
||||
window.ipcRenderer.on('pip-toggle', (_, isPip: boolean) => {
|
||||
if (originalExitButton && player) {
|
||||
if (isPip) {
|
||||
replaceButton('.exit-fullscreen-button', originalExitButton)?.addEventListener('click', () => togglePictureInPicture());
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import sliderHTML from './templates/slider.html';
|
||||
|
||||
import { getSongMenu } from '../../providers/dom-elements';
|
||||
import { ElementFromHtml } from '../utils';
|
||||
import { ElementFromHtml } from '../utils-renderer';
|
||||
import { singleton } from '../../providers/decorators';
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,3 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
import { setOptions, setMenuOptions, isEnabled } from '../../config/plugins';
|
||||
import { debounce } from '../../providers/decorators';
|
||||
|
||||
import { YoutubePlayer } from '../../types/youtube-player';
|
||||
@ -18,15 +15,15 @@ export default (_options: ConfigType<'precise-volume'>) => {
|
||||
options = _options;
|
||||
document.addEventListener('apiLoaded', (e) => {
|
||||
api = e.detail;
|
||||
ipcRenderer.on('changeVolume', (_, toIncrease: boolean) => changeVolume(toIncrease));
|
||||
ipcRenderer.on('setVolume', (_, value: number) => setVolume(value));
|
||||
window.ipcRenderer.on('changeVolume', (_, toIncrease: boolean) => changeVolume(toIncrease));
|
||||
window.ipcRenderer.on('setVolume', (_, value: number) => setVolume(value));
|
||||
firstRun();
|
||||
}, { once: true, passive: true });
|
||||
};
|
||||
|
||||
// Without this function it would rewrite config 20 time when volume change by 20
|
||||
const writeOptions = debounce(() => {
|
||||
setOptions('precise-volume', options);
|
||||
window.mainConfig.plugins.setOptions('precise-volume', options);
|
||||
}, 1000);
|
||||
|
||||
export const moveVolumeHud = debounce((showVideo: boolean) => {
|
||||
@ -68,7 +65,7 @@ function firstRun() {
|
||||
injectVolumeHud(noVid);
|
||||
if (!noVid) {
|
||||
setupVideoPlayerOnwheel();
|
||||
if (!isEnabled('video-toggle')) {
|
||||
if (!window.mainConfig.plugins.isEnabled('video-toggle')) {
|
||||
// Video-toggle handles hud positioning on its own
|
||||
const videoMode = () => api.getPlayerResponse().videoDetails?.musicVideoType !== 'MUSIC_VIDEO_TYPE_ATV';
|
||||
$('video')?.addEventListener('srcChanged', () => moveVolumeHud(videoMode()));
|
||||
@ -76,9 +73,9 @@ function firstRun() {
|
||||
}
|
||||
|
||||
// Change options from renderer to keep sync
|
||||
ipcRenderer.on('setOptions', (_event, newOptions = {}) => {
|
||||
window.ipcRenderer.on('setOptions', (_event, newOptions = {}) => {
|
||||
Object.assign(options, newOptions);
|
||||
setMenuOptions('precise-volume', options);
|
||||
window.mainConfig.plugins.setMenuOptions('precise-volume', options);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
import qualitySettingsTemplate from './templates/qualitySettingsTemplate.html';
|
||||
|
||||
import { ElementFromHtml } from '../utils';
|
||||
import { ElementFromHtml } from '../utils-renderer';
|
||||
import { YoutubePlayer } from '../../types/youtube-player';
|
||||
|
||||
function $(selector: string): HTMLElement | null {
|
||||
@ -23,7 +21,7 @@ function setup(event: CustomEvent<YoutubePlayer>) {
|
||||
|
||||
const currentIndex = qualityLevels.indexOf(api.getPlaybackQuality());
|
||||
|
||||
ipcRenderer.invoke('qualityChanger', api.getAvailableQualityLabels(), currentIndex).then((promise: { response: number }) => {
|
||||
window.ipcRenderer.invoke('qualityChanger', api.getAvailableQualityLabels(), currentIndex).then((promise: { response: number }) => {
|
||||
if (promise.response === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
import is from 'electron-is';
|
||||
|
||||
import { Segment } from './types';
|
||||
|
||||
let currentSegments: Segment[] = [];
|
||||
|
||||
export default () => {
|
||||
ipcRenderer.on('sponsorblock-skip', (_, segments: Segment[]) => {
|
||||
window.ipcRenderer.on('sponsorblock-skip', (_, segments: Segment[]) => {
|
||||
currentSegments = segments;
|
||||
});
|
||||
|
||||
@ -24,7 +21,7 @@ export default () => {
|
||||
&& target.currentTime < segment[1]
|
||||
) {
|
||||
target.currentTime = segment[1];
|
||||
if (is.dev()) {
|
||||
if (window.electronIs.dev()) {
|
||||
console.log('SponsorBlock: skipping segment', segment);
|
||||
}
|
||||
}
|
||||
|
||||
8
src/plugins/utils-renderer.ts
Normal file
8
src/plugins/utils-renderer.ts
Normal file
@ -0,0 +1,8 @@
|
||||
// Creates a DOM element from an HTML string
|
||||
export const ElementFromHtml = (html: string): HTMLElement => {
|
||||
const template = document.createElement('template');
|
||||
html = html.trim(); // Never return a text node of whitespace as the result
|
||||
template.innerHTML = html;
|
||||
|
||||
return template.content.firstElementChild as HTMLElement;
|
||||
};
|
||||
@ -1,7 +1,7 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import { app, ipcMain, ipcRenderer } from 'electron';
|
||||
import { app } from 'electron';
|
||||
import is from 'electron-is';
|
||||
|
||||
import { ValueOf } from '../utils/type-utils';
|
||||
@ -34,31 +34,6 @@ export const saveMediaIcon = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Creates a DOM element from an HTML string
|
||||
export const ElementFromHtml = (html: string): HTMLElement => {
|
||||
const template = document.createElement('template');
|
||||
html = html.trim(); // Never return a text node of whitespace as the result
|
||||
template.innerHTML = html;
|
||||
|
||||
return template.content.firstElementChild as HTMLElement;
|
||||
};
|
||||
|
||||
// Creates a DOM element from a HTML file
|
||||
export const ElementFromFile = (filepath: fs.PathOrFileDescriptor) => ElementFromHtml(fs.readFileSync(filepath, 'utf8'));
|
||||
|
||||
export const templatePath = (pluginPath: string, name: string) => path.join(pluginPath, 'templates', name);
|
||||
|
||||
export const Actions = {
|
||||
NEXT: 'next',
|
||||
BACK: 'back',
|
||||
};
|
||||
|
||||
export const triggerAction = <Parameters extends unknown[]>(channel: string, action: ValueOf<typeof Actions>, ...args: Parameters) => ipcRenderer.send(channel, action, ...args);
|
||||
|
||||
export const triggerActionSync = <Parameters extends unknown[]>(channel: string, action: ValueOf<typeof Actions>, ...args: Parameters): unknown => ipcRenderer.sendSync(channel, action, ...args);
|
||||
|
||||
export const listenAction = (channel: string, callback: (event: Electron.IpcMainEvent, action: string) => void) => ipcMain.on(channel, callback);
|
||||
|
||||
export const fileExists = (
|
||||
path: fs.PathLike,
|
||||
callbackIfExists: { (): void; (): void; (): void; },
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import buttonTemplate from './templates/button_template.html';
|
||||
|
||||
import { ElementFromHtml } from '../utils';
|
||||
import { setOptions, isEnabled } from '../../config/plugins';
|
||||
import { ElementFromHtml } from '../utils-renderer';
|
||||
|
||||
import { moveVolumeHud as preciseVolumeMoveVolumeHud } from '../precise-volume/front';
|
||||
|
||||
@ -10,7 +9,7 @@ import { ThumbnailElement } from '../../types/get-player-response';
|
||||
|
||||
import type { ConfigType } from '../../config/dynamic';
|
||||
|
||||
const moveVolumeHud = isEnabled('precise-volume') ? preciseVolumeMoveVolumeHud : () => {};
|
||||
const moveVolumeHud = window.mainConfig.plugins.isEnabled('precise-volume') ? preciseVolumeMoveVolumeHud : () => {};
|
||||
|
||||
function $<E extends Element = Element>(selector: string): E | null {
|
||||
return document.querySelector<E>(selector);
|
||||
@ -99,7 +98,7 @@ function setup(e: CustomEvent<YoutubePlayer>) {
|
||||
|
||||
function setVideoState(showVideo: boolean) {
|
||||
options.hideVideo = !showVideo;
|
||||
setOptions('video-toggle', options);
|
||||
window.mainConfig.plugins.setOptions('video-toggle', options);
|
||||
|
||||
const checkbox = $<HTMLInputElement>('.video-switch-button-checkbox'); // custom mode
|
||||
if (checkbox) checkbox.checked = !options.hideVideo;
|
||||
|
||||
@ -3,7 +3,7 @@ import ButterchurnPresets from 'butterchurn-presets';
|
||||
|
||||
import { Visualizer } from './visualizer';
|
||||
|
||||
import { ConfigType } from '../../../config/dynamic';
|
||||
import type { ConfigType } from '../../../config/dynamic';
|
||||
|
||||
class ButterchurnVisualizer extends Visualizer<Butterchurn> {
|
||||
name = 'butterchurn';
|
||||
|
||||
Reference in New Issue
Block a user