mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 18:41:47 +00:00
13
assets/youtube-music.svg
Normal file
13
assets/youtube-music.svg
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<svg xmlns:x="http://ns.adobe.com/Extensibility/1.0/" xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/" xmlns:graph="http://ns.adobe.com/Graphs/1.0/" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 176 176" enable-background="new 0 0 176 176" xml:space="preserve">
|
||||||
|
<metadata>
|
||||||
|
<sfw xmlns="http://ns.adobe.com/SaveForWeb/1.0/">
|
||||||
|
<slices/>
|
||||||
|
<sliceSourceBounds bottomLeftOrigin="true" height="176" width="176" x="8" y="-184"/>
|
||||||
|
</sfw>
|
||||||
|
</metadata>
|
||||||
|
<g id="XMLID_167_">
|
||||||
|
<circle id="XMLID_791_" fill="#FF0000" cx="88" cy="88" r="88"/>
|
||||||
|
<path id="XMLID_42_" fill="#FFFFFF" d="M88,46c23.1,0,42,18.8,42,42s-18.8,42-42,42s-42-18.8-42-42S64.9,46,88,46 M88,42 c-25.4,0-46,20.6-46,46s20.6,46,46,46s46-20.6,46-46S113.4,42,88,42L88,42z"/>
|
||||||
|
<polygon id="XMLID_274_" fill="#FFFFFF" points="72,111 111,87 72,65 "/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 902 B |
7
index.ts
7
index.ts
@ -136,7 +136,12 @@ function createMainWindow() {
|
|||||||
sandbox: false,
|
sandbox: false,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
frame: !is.macOS() && !useInlineMenu,
|
frame: !is.macOS() && !is.linux() && !useInlineMenu,
|
||||||
|
titleBarOverlay: {
|
||||||
|
color: '#00000000',
|
||||||
|
symbolColor: '#ffffff',
|
||||||
|
height: 36,
|
||||||
|
},
|
||||||
titleBarStyle: useInlineMenu
|
titleBarStyle: useInlineMenu
|
||||||
? 'hidden'
|
? 'hidden'
|
||||||
: (is.macOS()
|
: (is.macOS()
|
||||||
|
|||||||
9
package-lock.json
generated
9
package-lock.json
generated
@ -20,7 +20,6 @@
|
|||||||
"butterchurn-presets": "2.4.7",
|
"butterchurn-presets": "2.4.7",
|
||||||
"conf": "10.2.0",
|
"conf": "10.2.0",
|
||||||
"custom-electron-prompt": "1.5.7",
|
"custom-electron-prompt": "1.5.7",
|
||||||
"custom-electron-titlebar": "4.1.6",
|
|
||||||
"electron-better-web-request": "1.0.1",
|
"electron-better-web-request": "1.0.1",
|
||||||
"electron-debug": "3.2.0",
|
"electron-debug": "3.2.0",
|
||||||
"electron-is": "3.0.0",
|
"electron-is": "3.0.0",
|
||||||
@ -3184,14 +3183,6 @@
|
|||||||
"electron": ">=10.0.0"
|
"electron": ">=10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/custom-electron-titlebar": {
|
|
||||||
"version": "4.1.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/custom-electron-titlebar/-/custom-electron-titlebar-4.1.6.tgz",
|
|
||||||
"integrity": "sha512-AGULUZMxhEZDpl0Z1jfZzXgQEdhAPe8YET0dYQA/19t8oCrTFzF2PzdvJNCmxGU4Ai3jPWVeCPKg4vM7ffU0Mg==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"electron": ">20"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/dbus-next": {
|
"node_modules/dbus-next": {
|
||||||
"version": "0.9.2",
|
"version": "0.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/dbus-next/-/dbus-next-0.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/dbus-next/-/dbus-next-0.9.2.tgz",
|
||||||
|
|||||||
@ -142,7 +142,6 @@
|
|||||||
"butterchurn-presets": "2.4.7",
|
"butterchurn-presets": "2.4.7",
|
||||||
"conf": "10.2.0",
|
"conf": "10.2.0",
|
||||||
"custom-electron-prompt": "1.5.7",
|
"custom-electron-prompt": "1.5.7",
|
||||||
"custom-electron-titlebar": "4.1.6",
|
|
||||||
"electron-better-web-request": "1.0.1",
|
"electron-better-web-request": "1.0.1",
|
||||||
"electron-debug": "3.2.0",
|
"electron-debug": "3.2.0",
|
||||||
"electron-is": "3.0.0",
|
"electron-is": "3.0.0",
|
||||||
|
|||||||
@ -1,27 +1,58 @@
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
import { register } from 'electron-localshortcut';
|
import { register } from 'electron-localshortcut';
|
||||||
// eslint-disable-next-line import/no-unresolved
|
|
||||||
import { attachTitlebarToWindow, setupTitlebar } from 'custom-electron-titlebar/main';
|
|
||||||
|
|
||||||
import { BrowserWindow } from 'electron';
|
import { BrowserWindow, Menu, MenuItem, ipcMain } from 'electron';
|
||||||
|
|
||||||
import { injectCSS } from '../utils';
|
import { injectCSS } from '../utils';
|
||||||
|
|
||||||
|
|
||||||
setupTitlebar();
|
|
||||||
|
|
||||||
// Tracks menu visibility
|
// Tracks menu visibility
|
||||||
|
|
||||||
export default (win: BrowserWindow) => {
|
export default (win: BrowserWindow) => {
|
||||||
// Css for custom scrollbar + disable drag area(was causing bugs)
|
injectCSS(win.webContents, path.join(__dirname, 'titlebar.css'));
|
||||||
injectCSS(win.webContents, path.join(__dirname, 'style.css'));
|
|
||||||
|
|
||||||
win.once('ready-to-show', () => {
|
win.once('ready-to-show', () => {
|
||||||
attachTitlebarToWindow(win);
|
|
||||||
|
|
||||||
register(win, '`', () => {
|
register(win, '`', () => {
|
||||||
win.webContents.send('toggleMenu');
|
win.webContents.send('toggleMenu');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle(
|
||||||
|
'get-menu',
|
||||||
|
() => JSON.parse(JSON.stringify(
|
||||||
|
Menu.getApplicationMenu(),
|
||||||
|
(key: string, value: unknown) => (key !== 'commandsMap' && key !== 'menu') ? value : undefined),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const getMenuItemById = (commandId: number): MenuItem | null => {
|
||||||
|
const menu = Menu.getApplicationMenu();
|
||||||
|
|
||||||
|
let target: MenuItem | null = null;
|
||||||
|
const stack = [...menu?.items ?? []];
|
||||||
|
while (stack.length > 0) {
|
||||||
|
const now = stack.shift();
|
||||||
|
now?.submenu?.items.forEach((item) => stack.push(item));
|
||||||
|
|
||||||
|
if (now?.commandId === commandId) {
|
||||||
|
target = now;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
|
||||||
|
ipcMain.handle('menu-event', (event, commandId: number) => {
|
||||||
|
const target = getMenuItemById(commandId);
|
||||||
|
if (target) target.click(undefined, BrowserWindow.fromWebContents(event.sender), event.sender);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-menu-by-id', (_, commandId: number) => {
|
||||||
|
const result = getMenuItemById(commandId);
|
||||||
|
|
||||||
|
return JSON.parse(JSON.stringify(
|
||||||
|
result,
|
||||||
|
(key: string, value: unknown) => (key !== 'commandsMap' && key !== 'menu') ? value : undefined),
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
declare module 'custom-electron-titlebar' {
|
|
||||||
// eslint-disable-next-line import/no-unresolved
|
|
||||||
import OriginalTitlebar from 'custom-electron-titlebar/dist/titlebar';
|
|
||||||
// eslint-disable-next-line import/no-unresolved
|
|
||||||
import { Color as OriginalColor } from 'custom-electron-titlebar/dist/vs/base/common/color';
|
|
||||||
|
|
||||||
export const Color: typeof OriginalColor;
|
|
||||||
export const Titlebar: typeof OriginalTitlebar;
|
|
||||||
}
|
|
||||||
@ -1,12 +1,12 @@
|
|||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
import { ipcRenderer, Menu } from 'electron';
|
import { ipcRenderer, Menu } from 'electron';
|
||||||
// eslint-disable-next-line import/no-unresolved
|
|
||||||
import { Color, Titlebar } from 'custom-electron-titlebar';
|
|
||||||
|
|
||||||
import config from '../../config';
|
import { createPanel } from './menu/panel';
|
||||||
|
|
||||||
|
import { ElementFromFile } from '../utils';
|
||||||
import { isEnabled } from '../../config/plugins';
|
import { isEnabled } from '../../config/plugins';
|
||||||
|
|
||||||
import type { FastAverageColorResult } from 'fast-average-color';
|
|
||||||
|
|
||||||
type ElectronCSSStyleDeclaration = CSSStyleDeclaration & { webkitAppRegion: 'drag' | 'no-drag' };
|
type ElectronCSSStyleDeclaration = CSSStyleDeclaration & { webkitAppRegion: 'drag' | 'no-drag' };
|
||||||
type ElectronHTMLElement = HTMLElement & { style: ElectronCSSStyleDeclaration };
|
type ElectronHTMLElement = HTMLElement & { style: ElectronCSSStyleDeclaration };
|
||||||
|
|
||||||
@ -15,60 +15,60 @@ function $<E extends Element = Element>(selector: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const visible = () => !!($('.cet-menubar')?.firstChild);
|
const titleBar = document.createElement('title-bar');
|
||||||
const bar = new Titlebar({
|
const navBar = document.querySelector<HTMLDivElement>('#nav-bar-background');
|
||||||
icon: 'https://cdn-icons-png.flaticon.com/512/5358/5358672.png',
|
|
||||||
backgroundColor: Color.fromHex('#050505'),
|
const logo = ElementFromFile(path.join(__dirname, '../../assets/youtube-music.svg'));
|
||||||
itemBackgroundColor: Color.fromHex('#1d1d1d') ,
|
logo.classList.add('title-bar-icon');
|
||||||
svgColor: Color.WHITE,
|
|
||||||
menu: config.get('options.hideMenu') ? null as unknown as Menu : undefined,
|
titleBar.appendChild(logo);
|
||||||
});
|
document.body.appendChild(titleBar);
|
||||||
bar.updateTitle(' ');
|
|
||||||
|
if (navBar) {
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach(() => {
|
||||||
|
titleBar.style.setProperty('--titlebar-background-color', navBar.style.backgroundColor);
|
||||||
|
document.querySelector('html')!.style.setProperty('--titlebar-background-color', navBar.style.backgroundColor);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(navBar, { attributes : true, attributeFilter : ['style'] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateMenu = async () => {
|
||||||
|
const children = [...titleBar.children];
|
||||||
|
children.forEach((child) => {
|
||||||
|
if (child !== logo) child.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
const menu = await ipcRenderer.invoke('get-menu') as Menu | null;
|
||||||
|
if (!menu) return;
|
||||||
|
|
||||||
|
menu.items.forEach((menuItem) => {
|
||||||
|
const menu = document.createElement('menu-button');
|
||||||
|
createPanel(titleBar, menu, menuItem.submenu?.items ?? []);
|
||||||
|
|
||||||
|
menu.append(menuItem.label);
|
||||||
|
titleBar.appendChild(menu);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
updateMenu();
|
||||||
|
|
||||||
document.title = 'Youtube Music';
|
document.title = 'Youtube Music';
|
||||||
|
|
||||||
const toggleMenu = () => {
|
|
||||||
if (visible()) {
|
|
||||||
bar.updateMenu(null as unknown as Menu);
|
|
||||||
} else {
|
|
||||||
bar.refreshMenu();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.cet-window-icon')?.addEventListener('click', toggleMenu);
|
|
||||||
ipcRenderer.on('toggleMenu', toggleMenu);
|
|
||||||
|
|
||||||
ipcRenderer.on('refreshMenu', () => {
|
ipcRenderer.on('refreshMenu', () => {
|
||||||
if (visible()) {
|
updateMenu();
|
||||||
bar.refreshMenu();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isEnabled('album-color-theme')) {
|
|
||||||
ipcRenderer.on('album-color-changed', (_, albumColor: FastAverageColorResult) => {
|
|
||||||
if (albumColor) {
|
|
||||||
bar.updateBackground(Color.fromHex(albumColor.hexa));
|
|
||||||
} else {
|
|
||||||
bar.updateBackground(Color.fromHex('#050505'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEnabled('picture-in-picture')) {
|
if (isEnabled('picture-in-picture')) {
|
||||||
ipcRenderer.on('pip-toggle', () => {
|
ipcRenderer.on('pip-toggle', () => {
|
||||||
bar.refreshMenu();
|
updateMenu();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increases the right margin of Navbar background when the scrollbar is visible to avoid blocking it (z-index doesn't affect it)
|
// Increases the right margin of Navbar background when the scrollbar is visible to avoid blocking it (z-index doesn't affect it)
|
||||||
document.addEventListener('apiLoaded', () => {
|
document.addEventListener('apiLoaded', () => {
|
||||||
setNavbarMargin();
|
|
||||||
const playPageObserver = new MutationObserver(setNavbarMargin);
|
|
||||||
const appLayout = $('ytmusic-app-layout');
|
|
||||||
if (appLayout) {
|
|
||||||
playPageObserver.observe(appLayout, { attributeFilter: ['player-page-open_', 'playerPageOpen_'] });
|
|
||||||
}
|
|
||||||
setupSearchOpenObserver();
|
setupSearchOpenObserver();
|
||||||
setupMenuOpenObserver();
|
|
||||||
}, { once: true, passive: true });
|
}, { once: true, passive: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -84,33 +84,3 @@ function setupSearchOpenObserver() {
|
|||||||
searchOpenObserver.observe(searchBox, { attributeFilter: ['opened'] });
|
searchOpenObserver.observe(searchBox, { attributeFilter: ['opened'] });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupMenuOpenObserver() {
|
|
||||||
const cetMenubar = $('.cet-menubar');
|
|
||||||
if (cetMenubar) {
|
|
||||||
const menuOpenObserver = new MutationObserver(() => {
|
|
||||||
let isOpen = false;
|
|
||||||
for (const child of cetMenubar.children) {
|
|
||||||
if (child.classList.contains('open')) {
|
|
||||||
isOpen = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const navBarBackground = $<ElectronHTMLElement>('#nav-bar-background');
|
|
||||||
if (navBarBackground) {
|
|
||||||
navBarBackground.style.webkitAppRegion = isOpen ? 'no-drag' : 'drag';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
menuOpenObserver.observe(cetMenubar, { subtree: true, attributeFilter: ['class'] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setNavbarMargin() {
|
|
||||||
const navBarBackground = $<HTMLElement>('#nav-bar-background');
|
|
||||||
if (navBarBackground) {
|
|
||||||
navBarBackground.style.right
|
|
||||||
= $<HTMLElement & { playerPageOpen_: boolean }>('ytmusic-app-layout')?.playerPageOpen_
|
|
||||||
? '0px'
|
|
||||||
: '12px';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
10
plugins/in-app-menu/menu/icons.ts
Normal file
10
plugins/in-app-menu/menu/icons.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
const Icons = {
|
||||||
|
submenu: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><polyline points="9 6 15 12 9 18" /></svg>',
|
||||||
|
checkbox: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l5 5l10 -10" /></svg>',
|
||||||
|
radio: {
|
||||||
|
checked: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" style="padding: 2px"><path fill="currentColor" d="M10,5 C7.2,5 5,7.2 5,10 C5,12.8 7.2,15 10,15 C12.8,15 15,12.8 15,10 C15,7.2 12.8,5 10,5 L10,5 Z M10,0 C4.5,0 0,4.5 0,10 C0,15.5 4.5,20 10,20 C15.5,20 20,15.5 20,10 C20,4.5 15.5,0 10,0 L10,0 Z M10,18 C5.6,18 2,14.4 2,10 C2,5.6 5.6,2 10,2 C14.4,2 18,5.6 18,10 C18,14.4 14.4,18 10,18 L10,18 Z" /></svg>',
|
||||||
|
unchecked: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" style="padding: 2px"><path fill="currentColor" d="M10,0 C4.5,0 0,4.5 0,10 C0,15.5 4.5,20 10,20 C15.5,20 20,15.5 20,10 C20,4.5 15.5,0 10,0 L10,0 Z M10,18 C5.6,18 2,14.4 2,10 C2,5.6 5.6,2 10,2 C14.4,2 18,5.6 18,10 C18,14.4 14.4,18 10,18 L10,18 Z" /></svg>',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Icons;
|
||||||
125
plugins/in-app-menu/menu/panel.ts
Normal file
125
plugins/in-app-menu/menu/panel.ts
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import { nativeImage, type MenuItem, ipcRenderer, Menu } from 'electron';
|
||||||
|
|
||||||
|
import Icons from './icons';
|
||||||
|
|
||||||
|
import { ElementFromHtml } from '../../utils';
|
||||||
|
|
||||||
|
interface PanelOptions {
|
||||||
|
placement?: 'bottom' | 'right';
|
||||||
|
order?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createPanel = (
|
||||||
|
parent: HTMLElement,
|
||||||
|
anchor: HTMLElement,
|
||||||
|
items: MenuItem[],
|
||||||
|
options: PanelOptions = { placement: 'bottom', order: 0 },
|
||||||
|
) => {
|
||||||
|
const childPanels: HTMLElement[] = [];
|
||||||
|
const panel = document.createElement('menu-panel');
|
||||||
|
panel.style.zIndex = `${options.order}`;
|
||||||
|
|
||||||
|
const updateIconState = (iconWrapper: HTMLElement, item: MenuItem) => {
|
||||||
|
if (item.type === 'checkbox') {
|
||||||
|
if (item.checked) iconWrapper.innerHTML = Icons.checkbox;
|
||||||
|
else iconWrapper.innerHTML = '';
|
||||||
|
} else if (item.type === 'radio') {
|
||||||
|
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();
|
||||||
|
|
||||||
|
if (iconURL) iconWrapper.style.background = `url(${iconURL})`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const radioGroups: [MenuItem, HTMLElement][] = [];
|
||||||
|
items.map((item) => {
|
||||||
|
if (item.type === 'separator') return panel.appendChild(document.createElement('menu-separator'));
|
||||||
|
|
||||||
|
const menu = document.createElement('menu-item');
|
||||||
|
const iconWrapper = document.createElement('menu-icon');
|
||||||
|
|
||||||
|
updateIconState(iconWrapper, item);
|
||||||
|
menu.appendChild(iconWrapper);
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (menuItem) {
|
||||||
|
updateIconState(iconWrapper, menuItem);
|
||||||
|
|
||||||
|
if (menuItem.type === 'radio') {
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (newItem) updateIconState(iconWrapper, newItem);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (item.type === 'radio') {
|
||||||
|
radioGroups.push([item, iconWrapper]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.type === 'submenu') {
|
||||||
|
const subMenuIcon = document.createElement('menu-icon');
|
||||||
|
subMenuIcon.appendChild(ElementFromHtml(Icons.submenu));
|
||||||
|
menu.appendChild(subMenuIcon);
|
||||||
|
|
||||||
|
const [child, , children] = createPanel(parent, menu, item.submenu?.items ?? [], {
|
||||||
|
placement: 'right',
|
||||||
|
order: (options?.order ?? 0) + 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
childPanels.push(child);
|
||||||
|
children.push(...children);
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.appendChild(menu);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* methods */
|
||||||
|
const isOpened = () => panel.getAttribute('open') === 'true';
|
||||||
|
const close = () => panel.setAttribute('open', 'false');
|
||||||
|
const open = () => {
|
||||||
|
const rect = anchor.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (options.placement === 'bottom') {
|
||||||
|
panel.style.setProperty('--x', `${rect.x}px`);
|
||||||
|
panel.style.setProperty('--y', `${rect.y + rect.height}px`);
|
||||||
|
} else {
|
||||||
|
panel.style.setProperty('--x', `${rect.x + rect.width}px`);
|
||||||
|
panel.style.setProperty('--y', `${rect.y}px`);
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.setAttribute('open', 'true');
|
||||||
|
};
|
||||||
|
|
||||||
|
anchor.addEventListener('click', () => {
|
||||||
|
if (isOpened()) close();
|
||||||
|
else open();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.addEventListener('click', (event) => {
|
||||||
|
const path = event.composedPath();
|
||||||
|
const isInside = path.some((it) => it === panel || it === anchor || childPanels.includes(it as HTMLElement));
|
||||||
|
|
||||||
|
if (!isInside) close();
|
||||||
|
});
|
||||||
|
|
||||||
|
parent.appendChild(panel);
|
||||||
|
|
||||||
|
return [
|
||||||
|
panel,
|
||||||
|
{ isOpened, close, open },
|
||||||
|
childPanels,
|
||||||
|
] as const;
|
||||||
|
};
|
||||||
@ -1,118 +0,0 @@
|
|||||||
/* increase font size for menu and menuItems */
|
|
||||||
.titlebar,
|
|
||||||
.menubar-menu-container .action-label {
|
|
||||||
font-size: 14px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* fixes nav-bar-background opacity bug, reposition it, and allows clicking scrollbar through it */
|
|
||||||
#nav-bar-background {
|
|
||||||
opacity: 1 !important;
|
|
||||||
pointer-events: none !important;
|
|
||||||
top: 30px !important;
|
|
||||||
height: 75px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* fix top gap between nav-bar and browse-page */
|
|
||||||
#browse-page {
|
|
||||||
padding-top: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* https://github.com/organization/youtube-music-next/issues/4 */
|
|
||||||
#sections {
|
|
||||||
padding-top: 30px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* fix navbar hiding library items */
|
|
||||||
ytmusic-section-list-renderer[page-type="MUSIC_PAGE_TYPE_LIBRARY_CONTENT_LANDING_PAGE"],
|
|
||||||
ytmusic-section-list-renderer[page-type="MUSIC_PAGE_TYPE_PRIVATELY_OWNED_CONTENT_LANDING_PAGE"] {
|
|
||||||
top: 50px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* remove window dragging for nav bar (conflict with titlebar drag) */
|
|
||||||
ytmusic-nav-bar,
|
|
||||||
.tab-titleiron-icon,
|
|
||||||
ytmusic-pivot-bar-item-renderer {
|
|
||||||
-webkit-app-region: unset !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* move up item selection renderers */
|
|
||||||
ytmusic-item-section-renderer.stuck #header.ytmusic-item-section-renderer,
|
|
||||||
ytmusic-tabs.stuck {
|
|
||||||
top: calc(var(--ytmusic-nav-bar-height) - 15px) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* fix weird positioning in search screen*/
|
|
||||||
ytmusic-header-renderer.ytmusic-search-page {
|
|
||||||
position: unset !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Move navBar downwards */
|
|
||||||
ytmusic-nav-bar[slot="nav-bar"] {
|
|
||||||
top: 17px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* fix page progress bar position*/
|
|
||||||
yt-page-navigation-progress,
|
|
||||||
#progress.yt-page-navigation-progress {
|
|
||||||
top: 30px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* custom scrollbar */
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 12px;
|
|
||||||
background-color: #030303;
|
|
||||||
border-radius: 100px;
|
|
||||||
-moz-border-radius: 100px;
|
|
||||||
-webkit-border-radius: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* hover effect for both scrollbar area, and scrollbar 'thumb' */
|
|
||||||
::-webkit-scrollbar:hover {
|
|
||||||
background-color: rgba(15, 15, 15, 0.699);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* the scrollbar 'thumb' ...that marque oval shape in a scrollbar */
|
|
||||||
::-webkit-scrollbar-thumb:vertical {
|
|
||||||
border: 2px solid rgba(0, 0, 0, 0);
|
|
||||||
|
|
||||||
background: #3a3a3a;
|
|
||||||
background-clip: padding-box;
|
|
||||||
border-radius: 100px;
|
|
||||||
-moz-border-radius: 100px;
|
|
||||||
-webkit-border-radius: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:vertical:active {
|
|
||||||
background: #4d4c4c; /* some darker color when you click it */
|
|
||||||
border-radius: 100px;
|
|
||||||
-moz-border-radius: 100px;
|
|
||||||
-webkit-border-radius: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cet-menubar-menu-container .cet-action-item {
|
|
||||||
background-color: inherit
|
|
||||||
}
|
|
||||||
|
|
||||||
/** hideMenu toggler **/
|
|
||||||
.cet-window-icon {
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cet-window-icon img {
|
|
||||||
-webkit-user-drag: none;
|
|
||||||
filter: invert(50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** make navbar draggable **/
|
|
||||||
#nav-bar-background {
|
|
||||||
-webkit-app-region: drag;
|
|
||||||
}
|
|
||||||
|
|
||||||
ytmusic-nav-bar input,
|
|
||||||
ytmusic-nav-bar span,
|
|
||||||
ytmusic-nav-bar [role="button"],
|
|
||||||
ytmusic-nav-bar yt-icon,
|
|
||||||
tp-yt-iron-dropdown {
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
}
|
|
||||||
157
plugins/in-app-menu/titlebar.css
Normal file
157
plugins/in-app-menu/titlebar.css
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
:root {
|
||||||
|
--titlebar-background-color: #030303;
|
||||||
|
--menu-bar-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
title-bar {
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10000000;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: var(--menu-bar-height, 36px);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
color: #f1f1f1;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
background-color: var(--titlebar-background-color, #030303);
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
transition: opacity 200ms ease 0s, background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu-button {
|
||||||
|
-webkit-app-region: none;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
align-self: stretch;
|
||||||
|
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
menu-button:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu-panel {
|
||||||
|
position: fixed;
|
||||||
|
top: var(--y, 0);
|
||||||
|
left: var(--x, 0);
|
||||||
|
|
||||||
|
max-height: calc(100vh - var(--menu-bar-height, 36px) - 16px - var(--y, 0));
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 0;
|
||||||
|
|
||||||
|
overflow: auto;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 8px;
|
||||||
|
pointer-events: none;
|
||||||
|
background-color: color-mix(in srgb, var(--titlebar-background-color, #030303) 50%, rgba(0, 0, 0, 0.1));
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
|
z-index: 0;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8);
|
||||||
|
transform-origin: top left;
|
||||||
|
|
||||||
|
transition: opacity 200ms ease 0s, transform 200ms ease 0s;
|
||||||
|
}
|
||||||
|
menu-panel[open="true"] {
|
||||||
|
pointer-events: all;
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu-item {
|
||||||
|
-webkit-app-region: none;
|
||||||
|
min-height: 32px;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 32px 1fr minmax(32px, auto);
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
menu-item:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu-item > menu-icon {
|
||||||
|
height: 32px;
|
||||||
|
padding: 4px;
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu-separator {
|
||||||
|
min-height: 1px;
|
||||||
|
height: 1px;
|
||||||
|
margin: 4px 0;
|
||||||
|
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* classes */
|
||||||
|
|
||||||
|
.title-bar-icon {
|
||||||
|
height: calc(100% - 8px);
|
||||||
|
object-fit: cover;
|
||||||
|
margin-left: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* youtube-music style */
|
||||||
|
|
||||||
|
ytmusic-app-layout {
|
||||||
|
margin-top: var(--menu-bar-height, 36px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
ytmusic-app-layout>[slot=nav-bar], #nav-bar-background.ytmusic-app-layout {
|
||||||
|
top: var(--menu-bar-height, 36px) !important;
|
||||||
|
}
|
||||||
|
#nav-bar-divider.ytmusic-app-layout {
|
||||||
|
top: calc(var(--ytmusic-nav-bar-height) + var(--menu-bar-height, 36px)) !important;
|
||||||
|
}
|
||||||
|
ytmusic-app[is-bauhaus-sidenav-enabled] #guide-spacer.ytmusic-app,
|
||||||
|
ytmusic-app[is-bauhaus-sidenav-enabled] #mini-guide-spacer.ytmusic-app {
|
||||||
|
margin-top: calc(var(--ytmusic-nav-bar-height) + var(--menu-bar-height, 36px)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
html::-webkit-scrollbar {
|
||||||
|
width: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html::-webkit-scrollbar-thumb {
|
||||||
|
border: solid 2px var(--titlebar-background-color, #030303) !important;
|
||||||
|
border-radius: 100px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html::-webkit-scrollbar-track {
|
||||||
|
background: var(--titlebar-background-color, #030303) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html::-webkit-scrollbar-track-piece:start {
|
||||||
|
background: transparent;
|
||||||
|
margin-top: var(--menu-bar-height, 36px);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user