QOL: Move source code under the src directory. (#1318)

This commit is contained in:
Angelos Bouklis
2023-10-15 15:52:48 +03:00
committed by GitHub
parent 30c8dcf730
commit 7625a3aa52
159 changed files with 102 additions and 71 deletions

View File

@ -0,0 +1,3 @@
<svg fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="#ffffff" d="m4.21 4.387.083-.094a1 1 0 0 1 1.32-.083l.094.083L12 10.585l6.293-6.292a1 1 0 1 1 1.414 1.414L13.415 12l6.292 6.293a1 1 0 0 1 .083 1.32l-.083.094a1 1 0 0 1-1.32.083l-.094-.083L12 13.415l-6.293 6.292a1 1 0 0 1-1.414-1.414L10.585 12 4.293 5.707a1 1 0 0 1-.083-1.32l.083-.094-.083.094Z"/>
</svg>

After

Width:  |  Height:  |  Size: 392 B

View File

@ -0,0 +1,3 @@
<svg fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="#ffffff" d="M6 3h12a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3Zm0 2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H6Z"/>
</svg>

After

Width:  |  Height:  |  Size: 252 B

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M3 17h12a1 1 0 0 1 .117 1.993L15 19H3a1 1 0 0 1-.117-1.993L3 17h12H3Zm0-6h18a1 1 0 0 1 .117 1.993L21 13H3a1 1 0 0 1-.117-1.993L3 11h18H3Zm0-6h15a1 1 0 0 1 .117 1.993L18 7H3a1 1 0 0 1-.117-1.993L3 5h15H3Z" fill="#ffffff"/>
</svg>

After

Width:  |  Height:  |  Size: 338 B

View File

@ -0,0 +1,3 @@
<svg fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="#ffffff" d="M3.755 12.5h16.492a.75.75 0 0 0 0-1.5H3.755a.75.75 0 0 0 0 1.5Z" />
</svg>

After

Width:  |  Height:  |  Size: 174 B

View File

@ -0,0 +1,3 @@
<svg fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="#ffffff" d="M7.518 5H6.009a3.25 3.25 0 0 1 3.24-3h8.001A4.75 4.75 0 0 1 22 6.75v8a3.25 3.25 0 0 1-3 3.24v-1.508a1.75 1.75 0 0 0 1.5-1.732v-8a3.25 3.25 0 0 0-3.25-3.25h-8A1.75 1.75 0 0 0 7.518 5ZM5.25 6A3.25 3.25 0 0 0 2 9.25v9.5A3.25 3.25 0 0 0 5.25 22h9.5A3.25 3.25 0 0 0 18 18.75v-9.5A3.25 3.25 0 0 0 14.75 6h-9.5ZM3.5 9.25c0-.966.784-1.75 1.75-1.75h9.5c.967 0 1.75.784 1.75 1.75v9.5a1.75 1.75 0 0 1-1.75 1.75h-9.5a1.75 1.75 0 0 1-1.75-1.75v-9.5Z"/>
</svg>

After

Width:  |  Height:  |  Size: 546 B

View File

@ -0,0 +1,67 @@
import { register } from 'electron-localshortcut';
import { BrowserWindow, Menu, MenuItem, ipcMain } from 'electron';
import titlebarStyle from './titlebar.css';
import { injectCSS } from '../utils';
// Tracks menu visibility
export default (win: BrowserWindow) => {
injectCSS(win.webContents, titlebarStyle);
win.once('ready-to-show', () => {
register(win, '`', () => {
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),
);
});
ipcMain.handle('window-is-maximized', () => win.isMaximized());
ipcMain.handle('window-close', () => win.close());
ipcMain.handle('window-minimize', () => win.minimize());
ipcMain.handle('window-maximize', () => win.maximize());
win.on('maximize', () => win.webContents.send('window-maximize'));
ipcMain.handle('window-unmaximize', () => win.unmaximize());
win.on('unmaximize', () => win.webContents.send('window-unmaximize'));
};

View File

@ -0,0 +1,163 @@
import { ipcRenderer, Menu } from 'electron';
import { createPanel } from './menu/panel';
import logo from './assets/menu.svg';
import close from './assets/close.svg';
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';
function $<E extends Element = Element>(selector: string) {
return document.querySelector<E>(selector);
}
const isMacOS = navigator.userAgent.includes('Macintosh');
const isNotWindowsOrMacOS = !navigator.userAgent.includes('Windows') && !isMacOS;
export default async () => {
let hideMenu = config.get('options.hideMenu');
const titleBar = document.createElement('title-bar');
const navBar = document.querySelector<HTMLDivElement>('#nav-bar-background');
let maximizeButton: HTMLButtonElement;
if (isMacOS) titleBar.style.setProperty('--offset-left', '70px');
logo.classList.add('title-bar-icon');
const logoClick = () => {
hideMenu = !hideMenu;
let visibilityStyle: string;
if (hideMenu) {
visibilityStyle = 'hidden';
} else {
visibilityStyle = 'visible';
}
const menus = document.querySelectorAll<HTMLElement>('menu-button');
menus.forEach((menu) => {
menu.style.visibility = visibilityStyle;
});
};
logo.onclick = logoClick;
ipcRenderer.on('toggleMenu', logoClick);
if (!isMacOS) titleBar.appendChild(logo);
document.body.appendChild(titleBar);
titleBar.appendChild(logo);
const addWindowControls = async () => {
// Create window control buttons
const minimizeButton = document.createElement('button');
minimizeButton.classList.add('window-control');
minimizeButton.appendChild(minimize);
minimizeButton.onclick = () => ipcRenderer.invoke('window-minimize');
maximizeButton = document.createElement('button');
if (await ipcRenderer.invoke('window-is-maximized')) {
maximizeButton.classList.add('window-control');
maximizeButton.appendChild(unmaximize);
} else {
maximizeButton.classList.add('window-control');
maximizeButton.appendChild(maximize);
}
maximizeButton.onclick = async () => {
if (await ipcRenderer.invoke('window-is-maximized')) {
// change icon to maximize
maximizeButton.removeChild(maximizeButton.firstChild!);
maximizeButton.appendChild(maximize);
// call unmaximize
await ipcRenderer.invoke('window-unmaximize');
} else {
// change icon to unmaximize
maximizeButton.removeChild(maximizeButton.firstChild!);
maximizeButton.appendChild(unmaximize);
// call maximize
await ipcRenderer.invoke('window-maximize');
}
};
const closeButton = document.createElement('button');
closeButton.classList.add('window-control');
closeButton.appendChild(close);
closeButton.onclick = () => ipcRenderer.invoke('window-close');
// Create a container div for the window control buttons
const windowControlsContainer = document.createElement('div');
windowControlsContainer.classList.add('window-controls-container');
windowControlsContainer.appendChild(minimizeButton);
windowControlsContainer.appendChild(maximizeButton);
windowControlsContainer.appendChild(closeButton);
// Add window control buttons to the title bar
titleBar.appendChild(windowControlsContainer);
};
if (isNotWindowsOrMacOS) await addWindowControls();
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);
if (hideMenu) {
menu.style.visibility = 'hidden';
}
});
if (isNotWindowsOrMacOS) await addWindowControls();
};
await updateMenu();
document.title = 'Youtube Music';
ipcRenderer.on('refreshMenu', () => updateMenu());
ipcRenderer.on('window-maximize', () => {
maximizeButton.removeChild(maximizeButton.firstChild!);
maximizeButton.appendChild(unmaximize);
});
ipcRenderer.on('window-unmaximize', () => {
maximizeButton.removeChild(maximizeButton.firstChild!);
maximizeButton.appendChild(maximize);
});
if (isEnabled('picture-in-picture')) {
ipcRenderer.on('pip-toggle', () => {
updateMenu();
});
}
// 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', () => {
const htmlHeadStyle = $('head > div > style');
if (htmlHeadStyle) {
// HACK: This is a hack to remove the scrollbar width
htmlHeadStyle.innerHTML = htmlHeadStyle.innerHTML.replace('html::-webkit-scrollbar {width: var(--ytmusic-scrollbar-width);', 'html::-webkit-scrollbar {');
}
}, { once: true, passive: true });
};

View 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;

View File

@ -0,0 +1,134 @@
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');
// Children are placed below their parent item, which can cause
// long lists to squeeze their children at the bottom of the screen
// (This needs to be done *after* setAttribute)
panel.classList.remove('position-by-bottom');
if (options.placement === 'right' && panel.scrollHeight > panel.clientHeight ) {
panel.style.setProperty('--y', `${rect.y + rect.height}px`);
panel.classList.add('position-by-bottom');
}
};
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;
};

View File

@ -0,0 +1,180 @@
: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;
padding-left: var(--offset-left, 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-panel.position-by-bottom {
top: unset;
bottom: calc(100vh - var(--y, 100%));
max-height: calc(var(--y, 0) - var(--menu-bar-height, 36px) - 16px);
}
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;
}
/* Window control container */
.window-controls-container {
-webkit-app-region: no-drag;
display: flex;
justify-content: flex-end; /* Align to the right end of the title-bar */
align-items: center;
gap: 4px; /* Add spacing between the window control buttons */
position: absolute; /* Position it absolutely within title-bar */
right: 4px; /* Adjust the right position as needed */
}
/* Window control buttons */
.window-control {
width: 24px;
height: 24px;
background: none;
border: none;
cursor: pointer;
display: flex;
align-items: center;
color: #f1f1f1;
font-size: 14px;
padding: 0;
}
/* 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;
}
ytmusic-app-layout>[slot=player-page] {
margin-top: var(--menu-bar-height);
height: calc(100vh - var(--menu-bar-height) - var(--ytmusic-nav-bar-height) - var(--ytmusic-player-bar-height)) !important;
}
ytmusic-guide-renderer {
height: calc(100vh - var(--menu-bar-height) - var(--ytmusic-nav-bar-height)) !important;
}