diff --git a/src/config/defaults.ts b/src/config/defaults.ts index 000b9aa0..ccd00b0d 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -31,6 +31,7 @@ export interface DefaultConfig { likeButtons: string; proxy: string; startingPage: string; + backgroundMaterial?: 'none' | 'mica' | 'acrylic' | 'tabbed'; overrideUserAgent: boolean; usePodcastParticipantAsArtist: boolean; themes: string[]; diff --git a/src/i18n/resources/de.json b/src/i18n/resources/de.json index 5d7c429c..0abfb28e 100644 --- a/src/i18n/resources/de.json +++ b/src/i18n/resources/de.json @@ -853,6 +853,26 @@ "description": "Fügt ein TouchBar-Widget für macOS-Benutzer hinzu", "name": "TouchBar" }, + "transparent-player": { + "description": "Macht das Player-Fenster transparent", + "name": "Transparent Player", + "menu": { + "opacity": { + "label": "Hintergrund-Sichtbarkeit", + "submenu": { + "percent": "{{opacity}}%" + } + }, + "type": { + "label": "Typ", + "submenu": { + "acrylic": "Acrylic", + "mica": "Mica", + "tabbed": "Tabbed" + } + } + } + }, "tuna-obs": { "description": "Integration mit dem OBS-Plugin Tuna", "name": "Tuna OBS" diff --git a/src/i18n/resources/en.json b/src/i18n/resources/en.json index e7cf9704..a6f7e96e 100644 --- a/src/i18n/resources/en.json +++ b/src/i18n/resources/en.json @@ -874,6 +874,26 @@ "description": "Adds a TouchBar widget for macOS users", "name": "TouchBar" }, + "transparent-player": { + "description": "Makes the app window transparent", + "name": "Transparent Player", + "menu": { + "opacity": { + "label": "Opacity", + "submenu": { + "percent": "{{opacity}}%" + } + }, + "type": { + "label": "Type", + "submenu": { + "acrylic": "Acrylic", + "mica": "Mica", + "tabbed": "Tabbed" + } + } + } + }, "tuna-obs": { "description": "Integration with OBS's plugin Tuna", "name": "Tuna OBS" diff --git a/src/index.ts b/src/index.ts index c3239a6b..d9442eb0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -59,7 +59,7 @@ import ErrorHtmlAsset from '@assets/error.html?asset'; import { defaultAuthProxyConfig } from '@/plugins/auth-proxy-adapter/config'; -import { type PluginConfig } from '@/types/plugins'; +import type { PluginConfig } from '@/types/plugins'; // Catch errors and log them unhandled({ @@ -338,8 +338,8 @@ async function createMainWindow() { titleBarStyle: useInlineMenu ? 'hidden' : is.macOS() - ? 'hiddenInset' - : 'default', + ? 'hiddenInset' + : 'default', autoHideMenuBar: config.get('options.hideMenu'), }; @@ -349,7 +349,7 @@ async function createMainWindow() { delete decorations.titleBarStyle; } - const win = new BrowserWindow({ + const electronWindowSettings: Electron.BrowserWindowConstructorOptions = { icon, width: windowSize.width, height: windowSize.height, @@ -369,7 +369,10 @@ async function createMainWindow() { }), }, ...decorations, - }); + }; + + const win = new BrowserWindow(electronWindowSettings); + await initHook(win); initTheme(win); @@ -529,8 +532,8 @@ app.once('browser-window-created', (_event, win) => { const updatedUserAgent = is.macOS() ? userAgents.mac : is.windows() - ? userAgents.windows - : userAgents.linux; + ? userAgents.windows + : userAgents.linux; win.webContents.userAgent = updatedUserAgent; app.userAgentFallback = updatedUserAgent; @@ -951,15 +954,18 @@ function removeContentSecurityPolicy( betterSession.webRequest.setResolver( 'onHeadersReceived', async (listeners) => { - return listeners.reduce(async (accumulator, listener) => { - const acc = await accumulator; - if (acc.cancel) { - return acc; - } + return listeners.reduce( + async (accumulator, listener) => { + const acc = await accumulator; + if (acc.cancel) { + return acc; + } - const result = await listener.apply(); - return { ...accumulator, ...result }; - }, Promise.resolve({ cancel: false })); + const result = await listener.apply(); + return { ...accumulator, ...result }; + }, + Promise.resolve({ cancel: false }), + ); }, ); } diff --git a/src/plugins/album-color-theme/index.ts b/src/plugins/album-color-theme/index.ts index 2e50466b..e8a78945 100644 --- a/src/plugins/album-color-theme/index.ts +++ b/src/plugins/album-color-theme/index.ts @@ -31,7 +31,7 @@ export default createPlugin< alpha?: number, ratioMultiply?: number, ): string; - updateColor(): void; + updateColor(alpha: number): void; }, { enabled: boolean; @@ -143,7 +143,16 @@ export default createPlugin< document.documentElement.style.setProperty(DARK_COLOR_KEY, '0, 0, 0'); } - this.updateColor(); + let alpha: number | null = null; + if (await window.mainConfig.plugins.isEnabled('transparent-player')) { + const value: unknown = window.mainConfig.get( + 'plugins.transparent-player.opacity', + ); + if (typeof value === 'number' && value >= 0 && value <= 1) { + alpha = value; + } + } + this.updateColor(alpha ?? 1); }); }, onConfigChange(config) { @@ -163,7 +172,7 @@ export default createPlugin< } return `color-mix(in srgb, ${color} ${originalRatio}, ${keyColor} ${colorRatio})`; }, - updateColor() { + updateColor(alpha: number) { const variableMap = { '--ytmusic-color-black1': '#212121', '--ytmusic-color-black2': '#181818', @@ -202,19 +211,20 @@ export default createPlugin< Object.entries(variableMap).map(([variable, color]) => { document.documentElement.style.setProperty( variable, - this.getMixedColor(color, COLOR_KEY), + this.getMixedColor(color, COLOR_KEY, alpha), 'important', ); }); document.body.style.setProperty( 'background', - this.getMixedColor('#030303', COLOR_KEY), + this.getMixedColor('rgba(3, 3, 3)', DARK_COLOR_KEY, alpha), 'important', ); document.documentElement.style.setProperty( '--ytmusic-background', - this.getMixedColor('#030303', DARK_COLOR_KEY), + // #030303 + this.getMixedColor('rgba(3, 3, 3)', DARK_COLOR_KEY, alpha), 'important', ); }, diff --git a/src/plugins/in-app-menu/renderer/TitleBar.tsx b/src/plugins/in-app-menu/renderer/TitleBar.tsx index 74f9f6ee..923742c4 100644 --- a/src/plugins/in-app-menu/renderer/TitleBar.tsx +++ b/src/plugins/in-app-menu/renderer/TitleBar.tsx @@ -329,6 +329,7 @@ export const TitleBar = (props: TitleBarProps) => { data-macos={props.isMacOS} data-show={mouseY() < 32} data-ytmd-main-panel={true} + id={'ytmd-title-bar-main-panel'} > setCollapsed(!collapsed())} diff --git a/src/plugins/transparent-player/index.ts b/src/plugins/transparent-player/index.ts new file mode 100644 index 00000000..c1d32e14 --- /dev/null +++ b/src/plugins/transparent-player/index.ts @@ -0,0 +1,112 @@ +import { t } from '@/i18n'; +import { createPlugin } from '@/utils'; +import { Platform } from '@/types/plugins'; + +import { MaterialType, type TransparentPlayerConfig } from './types'; + +import style from './style.css?inline'; + +import type { BrowserWindow } from 'electron'; + +const defaultConfig: TransparentPlayerConfig = { + enabled: false, + opacity: 0.5, + type: MaterialType.ACRYLIC, +}; + +const opacityList = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]; +const typeList = Object.values(MaterialType); + +export default createPlugin({ + name: () => t('plugins.transparent-player.name'), + description: () => t('plugins.transparent-player.description'), + addedVersion: '3.10.x', + restartNeeded: true, + platform: Platform.Windows, + config: defaultConfig, + stylesheets: [style], + async menu({ getConfig, setConfig }) { + const config = await getConfig(); + return [ + { + label: t('plugins.transparent-player.menu.opacity.label'), + submenu: opacityList.map((opacity) => ({ + label: t('plugins.transparent-player.menu.opacity.submenu.percent', { + opacity: opacity * 100, + }), + type: 'radio', + checked: config.opacity === opacity, + click() { + setConfig({ opacity }); + }, + })), + }, + { + label: t('plugins.transparent-player.menu.type.label'), + submenu: typeList.map((type) => ({ + label: t(`plugins.transparent-player.menu.type.submenu.${type}`), + type: 'radio', + checked: config.type === type, + click() { + setConfig({ type }); + }, + })), + }, + ]; + }, + backend: { + mainWindow: null as BrowserWindow | null, + async start({ window, getConfig }) { + this.mainWindow = window; + + const config = await getConfig(); + window.setBackgroundMaterial?.(config.type); + window.setBackgroundColor?.(`rgba(0, 0, 0, ${config.opacity})`); + }, + onConfigChange(newConfig) { + this.mainWindow?.setBackgroundMaterial?.(newConfig.type); + }, + stop({ window }) { + window.setBackgroundMaterial?.('none'); + }, + }, + renderer: { + props: { + enabled: defaultConfig.enabled, + opacity: defaultConfig.opacity, + type: defaultConfig.type, + } as TransparentPlayerConfig, + async start({ getConfig }) { + const config = await getConfig(); + this.props = config; + if (config.enabled) { + document.body.classList.add('transparent-background-color'); + document.body.classList.add('transparent-player-backdrop-filter'); + + if (!(await window.mainConfig.plugins.isEnabled('album-color-theme'))) { + document.body.classList.add('transparent-player'); + } + this.applyVariables(); + } + }, + onConfigChange(newConfig) { + this.props = newConfig; + this.applyVariables(); + }, + stop() { + document.body.classList.remove('transparent-background-color'); + document.body.classList.remove('transparent-player-backdrop-filter'); + document.body.classList.remove('transparent-player'); + document.documentElement.style.removeProperty( + '--ytmd-transparent-player-opacity', + ); + }, + applyVariables(this: { props: TransparentPlayerConfig }) { + const { opacity } = this.props; + document.documentElement.style.setProperty( + '--ytmd-transparent-player-opacity', + opacity.toString(), + ); + }, + }, +}); diff --git a/src/plugins/transparent-player/style.css b/src/plugins/transparent-player/style.css new file mode 100644 index 00000000..9b02d7ef --- /dev/null +++ b/src/plugins/transparent-player/style.css @@ -0,0 +1,106 @@ +:root { + --ytmd-transparent-player-transparency-color: #111; + --ytmd-transparent-player-transparent-background: rgb( + from var(--ytmd-transparent-player-transparency-color) r g b / + var(--ytmd-transparent-player-opacity, 0.5) + ); + --ytmd-transparent-player-transparent-background-dark: rgb( + from var(--ytmd-transparent-player-transparency-color) r g b / 0.8 + ); + --ytmd-transparent-player-backdrop-blur: blur(20px); +} + +body.transparent-background-color { + background-color: var(--ytmd-transparent-player-transparent-background) !important; +} + +body.transparent-player-backdrop-filter { + #layout { + #nav-bar-background, + #player-bar-background { + backdrop-filter: var(--ytmd-transparent-player-backdrop-blur) !important; + } + } + + #search-page { + #tabs { + &.stuck { + backdrop-filter: var(--ytmd-transparent-player-backdrop-blur) !important; + } + } + } + + ytmusic-menu-popup-renderer { + backdrop-filter: var(--ytmd-transparent-player-backdrop-blur) !important; + } + + #ytmd-title-bar-main-panel { + backdrop-filter: var(--ytmd-transparent-player-backdrop-blur) !important; + } +} + +body.transparent-player { + ytmusic-app { + ytmusic-app-layout[player-page-open] { + #nav-bar-background.ytmusic-app-layout, + #player-bar-background.ytmusic-app-layout { + opacity: 0 !important; + } + } + + #layout { + #nav-bar-background, + #player-bar-background { + background: var(--ytmd-transparent-player-transparent-background-dark) !important; + } + + #mini-guide-background { + background: none !important; + border: 0 !important; + } + + #guide { + #guide-wrapper { + background: none !important; + border: 0 !important; + } + } + + ytmusic-player-bar { + background: none !important; + } + + #player-page { + background: none !important; + } + + #search-page { + #tabs { + &.stuck { + background: var(--ytmd-transparent-player-transparent-background) !important; + } + } + } + + #browse-page { + #background { + display: none !important; + } + + .background-gradient { + background: none !important; + } + } + } + } + + /* Window Top Panel */ + nav[data-ytmd-main-panel] { + background-color: transparent !important; + } + + /* Video Toggle Plugin */ + .av-toggle.ytmusic-av-toggle { + background-color: var(--ytmd-transparent-player-transparent-background); + } +} diff --git a/src/plugins/transparent-player/types.ts b/src/plugins/transparent-player/types.ts new file mode 100644 index 00000000..e0439225 --- /dev/null +++ b/src/plugins/transparent-player/types.ts @@ -0,0 +1,12 @@ +export enum MaterialType { + MICA = 'mica', + ACRYLIC = 'acrylic', + TABBED = 'tabbed', + NONE = 'none', +} + +export type TransparentPlayerConfig = { + enabled: boolean; + opacity: number; + type: MaterialType; +};