mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 18:41:47 +00:00
fix: apply fix from eslint
This commit is contained in:
@ -4,24 +4,12 @@ export interface InAppMenuConfig {
|
||||
}
|
||||
export const defaultInAppMenuConfig: InAppMenuConfig = {
|
||||
enabled:
|
||||
(
|
||||
(
|
||||
typeof window !== 'undefined' &&
|
||||
!window.navigator?.userAgent?.toLowerCase().includes('mac')
|
||||
) ||
|
||||
(
|
||||
typeof global !== 'undefined' &&
|
||||
global.process?.platform !== 'darwin'
|
||||
)
|
||||
) && (
|
||||
(
|
||||
typeof window !== 'undefined' &&
|
||||
!window.navigator?.userAgent?.toLowerCase().includes('linux')
|
||||
) ||
|
||||
(
|
||||
typeof global !== 'undefined' &&
|
||||
global.process?.platform !== 'linux'
|
||||
)
|
||||
),
|
||||
((typeof window !== 'undefined' &&
|
||||
!window.navigator?.userAgent?.toLowerCase().includes('mac')) ||
|
||||
(typeof global !== 'undefined' &&
|
||||
global.process?.platform !== 'darwin')) &&
|
||||
((typeof window !== 'undefined' &&
|
||||
!window.navigator?.userAgent?.toLowerCase().includes('linux')) ||
|
||||
(typeof global !== 'undefined' && global.process?.platform !== 'linux')),
|
||||
hideDOMWindowControls: false,
|
||||
};
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
import { register } from 'electron-localshortcut';
|
||||
|
||||
import { BrowserWindow, Menu, MenuItem, ipcMain, nativeImage } from 'electron';
|
||||
import {
|
||||
BrowserWindow,
|
||||
Menu,
|
||||
MenuItem,
|
||||
ipcMain,
|
||||
nativeImage,
|
||||
WebContents,
|
||||
} from 'electron';
|
||||
|
||||
import type { BackendContext } from '@/types/contexts';
|
||||
import type { InAppMenuConfig } from './constants';
|
||||
@ -50,11 +57,13 @@ export const onMainLoad = ({
|
||||
ipcMain.handle('ytmd:menu-event', (event, commandId: number) => {
|
||||
const target = getMenuItemById(commandId);
|
||||
if (target)
|
||||
target.click(
|
||||
undefined,
|
||||
BrowserWindow.fromWebContents(event.sender),
|
||||
event.sender,
|
||||
);
|
||||
(
|
||||
target.click as (
|
||||
args0: unknown,
|
||||
args1: BrowserWindow | null,
|
||||
args3: WebContents,
|
||||
) => void
|
||||
)(undefined, BrowserWindow.fromWebContents(event.sender), event.sender);
|
||||
});
|
||||
|
||||
handle('get-menu-by-id', (commandId: number) => {
|
||||
|
||||
@ -16,8 +16,9 @@ const isMacOS = navigator.userAgent.includes('Macintosh');
|
||||
const isNotWindowsOrMacOS =
|
||||
!navigator.userAgent.includes('Windows') && !isMacOS;
|
||||
|
||||
|
||||
const [config, setConfig] = createSignal<InAppMenuConfig>(defaultInAppMenuConfig);
|
||||
const [config, setConfig] = createSignal<InAppMenuConfig>(
|
||||
defaultInAppMenuConfig,
|
||||
);
|
||||
export const onRendererLoad = async ({
|
||||
getConfig,
|
||||
ipc,
|
||||
@ -29,14 +30,19 @@ export const onRendererLoad = async ({
|
||||
stylesheet.replaceSync(scrollStyle);
|
||||
document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet];
|
||||
|
||||
render(() => (
|
||||
<TitleBar
|
||||
ipc={ipc}
|
||||
isMacOS={isMacOS}
|
||||
enableController={isNotWindowsOrMacOS && !config().hideDOMWindowControls}
|
||||
initialCollapsed={window.mainConfig.get('options.hideMenu')}
|
||||
/>
|
||||
), document.body);
|
||||
render(
|
||||
() => (
|
||||
<TitleBar
|
||||
ipc={ipc}
|
||||
isMacOS={isMacOS}
|
||||
enableController={
|
||||
isNotWindowsOrMacOS && !config().hideDOMWindowControls
|
||||
}
|
||||
initialCollapsed={window.mainConfig.get('options.hideMenu')}
|
||||
/>
|
||||
),
|
||||
document.body,
|
||||
);
|
||||
};
|
||||
|
||||
export const onPlayerApiReady = () => {
|
||||
|
||||
@ -3,36 +3,38 @@ import { css } from 'solid-styled-components';
|
||||
|
||||
import { cacheNoArgs } from '@/providers/decorators';
|
||||
|
||||
const iconButton = cacheNoArgs(() => css`
|
||||
-webkit-app-region: none;
|
||||
const iconButton = cacheNoArgs(
|
||||
() => css`
|
||||
-webkit-app-region: none;
|
||||
|
||||
background: transparent;
|
||||
background: transparent;
|
||||
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
||||
padding: 2px;
|
||||
border-radius: 2px;
|
||||
padding: 2px;
|
||||
border-radius: 2px;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
color: white;
|
||||
color: white;
|
||||
|
||||
outline: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
border: none;
|
||||
|
||||
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
scale: 0.9;
|
||||
}
|
||||
`);
|
||||
&:active {
|
||||
scale: 0.9;
|
||||
}
|
||||
`,
|
||||
);
|
||||
|
||||
type CollapseIconButtonProps = JSX.HTMLAttributes<HTMLButtonElement>;
|
||||
export const IconButton = (props: CollapseIconButtonProps) => {
|
||||
|
||||
@ -3,31 +3,33 @@ import { css } from 'solid-styled-components';
|
||||
|
||||
import { cacheNoArgs } from '@/providers/decorators';
|
||||
|
||||
const menuStyle = cacheNoArgs(() => css`
|
||||
-webkit-app-region: none;
|
||||
const menuStyle = cacheNoArgs(
|
||||
() => css`
|
||||
-webkit-app-region: none;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
&:active {
|
||||
scale: 0.9;
|
||||
}
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
&:active {
|
||||
scale: 0.9;
|
||||
}
|
||||
|
||||
&[data-selected="true"] {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
`);
|
||||
&[data-selected='true'] {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
`,
|
||||
);
|
||||
|
||||
export type MenuButtonProps = JSX.HTMLAttributes<HTMLLIElement> & {
|
||||
text?: string;
|
||||
|
||||
@ -2,39 +2,48 @@ import { createSignal, JSX, Show, splitProps } from 'solid-js';
|
||||
import { mergeProps, Portal } from 'solid-js/web';
|
||||
import { css } from 'solid-styled-components';
|
||||
import { Transition } from 'solid-transition-group';
|
||||
import { autoUpdate, flip, offset, OffsetOptions, size } from '@floating-ui/dom';
|
||||
import {
|
||||
autoUpdate,
|
||||
flip,
|
||||
offset,
|
||||
OffsetOptions,
|
||||
size,
|
||||
} from '@floating-ui/dom';
|
||||
import { useFloating } from 'solid-floating-ui';
|
||||
|
||||
import { cacheNoArgs } from '@/providers/decorators';
|
||||
|
||||
const panelStyle = cacheNoArgs(() => css`
|
||||
position: fixed;
|
||||
top: var(--offset-y, 0);
|
||||
left: var(--offset-x, 0);
|
||||
const panelStyle = cacheNoArgs(
|
||||
() => css`
|
||||
position: fixed;
|
||||
top: var(--offset-y, 0);
|
||||
left: var(--offset-x, 0);
|
||||
|
||||
max-width: var(--max-width, 100%);
|
||||
max-height: var(--max-height, 100%);
|
||||
max-width: var(--max-width, 100%);
|
||||
max-height: var(--max-height, 100%);
|
||||
|
||||
z-index: 10000;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
z-index: 10000;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
|
||||
padding: 4px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
overflow: auto;
|
||||
padding: 4px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
overflow: auto;
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
transform-origin: var(--origin-x, 50%) var(--origin-y, 50%);
|
||||
`);
|
||||
transform-origin: var(--origin-x, 50%) var(--origin-y, 50%);
|
||||
`,
|
||||
);
|
||||
|
||||
const animationStyle = cacheNoArgs(() => ({
|
||||
enter: css`
|
||||
@ -42,19 +51,23 @@ const animationStyle = cacheNoArgs(() => ({
|
||||
transform: scale(0.9);
|
||||
`,
|
||||
enterActive: css`
|
||||
transition: opacity 0.225s cubic-bezier(0.33, 1, 0.68, 1), transform 0.225s cubic-bezier(0.33, 1, 0.68, 1);
|
||||
transition:
|
||||
opacity 0.225s cubic-bezier(0.33, 1, 0.68, 1),
|
||||
transform 0.225s cubic-bezier(0.33, 1, 0.68, 1);
|
||||
`,
|
||||
exitTo: css`
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
`,
|
||||
exitActive: css`
|
||||
transition: opacity 0.225s cubic-bezier(0.32, 0, 0.67, 0), transform 0.225s cubic-bezier(0.32, 0, 0.67, 0);
|
||||
transition:
|
||||
opacity 0.225s cubic-bezier(0.32, 0, 0.67, 0),
|
||||
transform 0.225s cubic-bezier(0.32, 0, 0.67, 0);
|
||||
`,
|
||||
}));
|
||||
|
||||
export type Placement =
|
||||
'top'
|
||||
| 'top'
|
||||
| 'bottom'
|
||||
| 'left'
|
||||
| 'right'
|
||||
@ -92,9 +105,15 @@ export const Panel = (props: PanelProps) => {
|
||||
size({
|
||||
padding: 8,
|
||||
apply({ elements, availableWidth, availableHeight }) {
|
||||
elements.floating.style.setProperty('--max-width', `${Math.max(200, availableWidth)}px`);
|
||||
elements.floating.style.setProperty('--max-height', `${Math.max(200, availableHeight)}px`);
|
||||
}
|
||||
elements.floating.style.setProperty(
|
||||
'--max-width',
|
||||
`${Math.max(200, availableWidth)}px`,
|
||||
);
|
||||
elements.floating.style.setProperty(
|
||||
'--max-height',
|
||||
`${Math.max(200, availableHeight)}px`,
|
||||
);
|
||||
},
|
||||
}),
|
||||
flip({ fallbackStrategy: 'initialPlacement' }),
|
||||
],
|
||||
@ -103,7 +122,10 @@ export const Panel = (props: PanelProps) => {
|
||||
const originX = () => {
|
||||
if (position.placement.includes('left')) return '100%';
|
||||
if (position.placement.includes('right')) return '0';
|
||||
if (position.placement.includes('top') || position.placement.includes('bottom')) {
|
||||
if (
|
||||
position.placement.includes('top') ||
|
||||
position.placement.includes('bottom')
|
||||
) {
|
||||
if (position.placement.includes('start')) return '0';
|
||||
if (position.placement.includes('end')) return '100%';
|
||||
}
|
||||
@ -113,7 +135,10 @@ export const Panel = (props: PanelProps) => {
|
||||
const originY = () => {
|
||||
if (position.placement.includes('top')) return '100%';
|
||||
if (position.placement.includes('bottom')) return '0';
|
||||
if (position.placement.includes('left') || position.placement.includes('right')) {
|
||||
if (
|
||||
position.placement.includes('left') ||
|
||||
position.placement.includes('right')
|
||||
) {
|
||||
if (position.placement.includes('start')) return '0';
|
||||
if (position.placement.includes('end')) return '100%';
|
||||
}
|
||||
|
||||
@ -10,100 +10,111 @@ import { autoUpdate, offset, size } from '@floating-ui/dom';
|
||||
import { Panel } from './Panel';
|
||||
import { cacheNoArgs } from '@/providers/decorators';
|
||||
|
||||
const itemStyle = cacheNoArgs(() => css`
|
||||
position: relative;
|
||||
const itemStyle = cacheNoArgs(
|
||||
() => css`
|
||||
position: relative;
|
||||
|
||||
-webkit-app-region: none;
|
||||
min-height: 32px;
|
||||
height: 32px;
|
||||
-webkit-app-region: none;
|
||||
min-height: 32px;
|
||||
height: 32px;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 32px 1fr auto minmax(32px, auto);
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: 32px 1fr auto minmax(32px, auto);
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
|
||||
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
&[data-selected="true"] {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
& * {
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`);
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
|
||||
const itemIconStyle = cacheNoArgs(() => css`
|
||||
height: 32px;
|
||||
padding: 4px;
|
||||
color: white;
|
||||
`);
|
||||
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
|
||||
const itemLabelStyle = cacheNoArgs(() => css`
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
`);
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
const itemChipStyle = cacheNoArgs(() => css`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
&:active {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
padding: 0 4px;
|
||||
margin-left: 8px;
|
||||
&[data-selected='true'] {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
border-radius: 4px;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
color: #f1f1f1;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
`);
|
||||
& * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`,
|
||||
);
|
||||
|
||||
const toolTipStyle = cacheNoArgs(() => css`
|
||||
min-width: 32px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
const itemIconStyle = cacheNoArgs(
|
||||
() => css`
|
||||
height: 32px;
|
||||
padding: 4px;
|
||||
color: white;
|
||||
`,
|
||||
);
|
||||
|
||||
padding: 4px;
|
||||
const itemLabelStyle = cacheNoArgs(
|
||||
() => css`
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
`,
|
||||
);
|
||||
|
||||
max-width: calc(var(--max-width, 100%) - 8px);
|
||||
max-height: calc(var(--max-height, 100%) - 8px);
|
||||
const itemChipStyle = cacheNoArgs(
|
||||
() => css`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
border-radius: 4px;
|
||||
background-color: rgba(25, 25, 25, 0.8);
|
||||
color: #f1f1f1;
|
||||
font-size: 10px;
|
||||
`);
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
padding: 0 4px;
|
||||
margin-left: 8px;
|
||||
|
||||
const popupStyle = cacheNoArgs(() => css`
|
||||
position: fixed;
|
||||
top: var(--offset-y, 0);
|
||||
left: var(--offset-x, 0);
|
||||
border-radius: 4px;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
color: #f1f1f1;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
`,
|
||||
);
|
||||
|
||||
max-width: var(--max-width, 100%);
|
||||
max-height: var(--max-height, 100%);
|
||||
const toolTipStyle = cacheNoArgs(
|
||||
() => css`
|
||||
min-width: 32px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
z-index: 100000000;
|
||||
pointer-events: none;
|
||||
padding: 4px;
|
||||
|
||||
`);
|
||||
max-width: calc(var(--max-width, 100%) - 8px);
|
||||
max-height: calc(var(--max-height, 100%) - 8px);
|
||||
|
||||
border-radius: 4px;
|
||||
background-color: rgba(25, 25, 25, 0.8);
|
||||
color: #f1f1f1;
|
||||
font-size: 10px;
|
||||
`,
|
||||
);
|
||||
|
||||
const popupStyle = cacheNoArgs(
|
||||
() => css`
|
||||
position: fixed;
|
||||
top: var(--offset-y, 0);
|
||||
left: var(--offset-x, 0);
|
||||
|
||||
max-width: var(--max-width, 100%);
|
||||
max-height: var(--max-height, 100%);
|
||||
|
||||
z-index: 100000000;
|
||||
pointer-events: none;
|
||||
`,
|
||||
);
|
||||
|
||||
const animationStyle = cacheNoArgs(() => ({
|
||||
enter: css`
|
||||
@ -111,14 +122,18 @@ const animationStyle = cacheNoArgs(() => ({
|
||||
transform: scale(0.9);
|
||||
`,
|
||||
enterActive: css`
|
||||
transition: opacity 0.225s cubic-bezier(0.33, 1, 0.68, 1), transform 0.225s cubic-bezier(0.33, 1, 0.68, 1);
|
||||
transition:
|
||||
opacity 0.225s cubic-bezier(0.33, 1, 0.68, 1),
|
||||
transform 0.225s cubic-bezier(0.33, 1, 0.68, 1);
|
||||
`,
|
||||
exitTo: css`
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
`,
|
||||
exitActive: css`
|
||||
transition: opacity 0.225s cubic-bezier(0.32, 0, 0.67, 0), transform 0.225s cubic-bezier(0.32, 0, 0.67, 0);
|
||||
transition:
|
||||
opacity 0.225s cubic-bezier(0.32, 0, 0.67, 0),
|
||||
transform 0.225s cubic-bezier(0.32, 0, 0.67, 0);
|
||||
`,
|
||||
}));
|
||||
|
||||
@ -160,7 +175,11 @@ type CheckboxPanelItemProps = BasePanelItemProps & {
|
||||
checked: boolean;
|
||||
onChange?: (checked: boolean) => void;
|
||||
};
|
||||
export type PanelItemProps = NormalPanelItemProps | SubmenuItemProps | RadioPanelItemProps | CheckboxPanelItemProps;
|
||||
export type PanelItemProps =
|
||||
| NormalPanelItemProps
|
||||
| SubmenuItemProps
|
||||
| RadioPanelItemProps
|
||||
| CheckboxPanelItemProps;
|
||||
export const PanelItem = (props: PanelItemProps) => {
|
||||
const [open, setOpen] = createSignal(false);
|
||||
const [toolTipOpen, setToolTipOpen] = createSignal(false);
|
||||
@ -176,17 +195,24 @@ export const PanelItem = (props: PanelItemProps) => {
|
||||
offset({ mainAxis: 8 }),
|
||||
size({
|
||||
apply({ rects, elements }) {
|
||||
elements.floating.style.setProperty('--max-width', `${rects.reference.width}px`);
|
||||
}
|
||||
elements.floating.style.setProperty(
|
||||
'--max-width',
|
||||
`${rects.reference.width}px`,
|
||||
);
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const handleHover = (event: MouseEvent) => {
|
||||
setToolTipOpen(true);
|
||||
event.target?.addEventListener('mouseleave', () => {
|
||||
setToolTipOpen(false);
|
||||
}, { once: true });
|
||||
event.target?.addEventListener(
|
||||
'mouseleave',
|
||||
() => {
|
||||
setToolTipOpen(false);
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
|
||||
if (props.type === 'submenu') {
|
||||
const timer = setTimeout(() => {
|
||||
@ -200,36 +226,54 @@ export const PanelItem = (props: PanelItemProps) => {
|
||||
};
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
|
||||
event.target?.addEventListener('mouseleave', () => {
|
||||
setTimeout(() => {
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
const parents = getParents(document.elementFromPoint(mouseX, mouseY));
|
||||
event.target?.addEventListener(
|
||||
'mouseleave',
|
||||
() => {
|
||||
setTimeout(() => {
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
const parents = getParents(
|
||||
document.elementFromPoint(mouseX, mouseY),
|
||||
);
|
||||
|
||||
if (!parents.includes(child())) {
|
||||
setOpen(false);
|
||||
} else {
|
||||
const onOtherHover = (event: MouseEvent) => {
|
||||
const parents = getParents(event.target as HTMLElement);
|
||||
const closestLevel = parents.find((it) => it?.dataset?.level)?.dataset.level ?? '';
|
||||
const path = event.composedPath();
|
||||
if (!parents.includes(child())) {
|
||||
setOpen(false);
|
||||
} else {
|
||||
const onOtherHover = (event: MouseEvent) => {
|
||||
const parents = getParents(event.target as HTMLElement);
|
||||
const closestLevel =
|
||||
parents.find((it) => it?.dataset?.level)?.dataset.level ??
|
||||
'';
|
||||
const path = event.composedPath();
|
||||
|
||||
const isOtherItem = path.some((it) => it instanceof HTMLElement && it.classList.contains(itemStyle()));
|
||||
const isChild = closestLevel.startsWith(props.level.join('/'));
|
||||
const isOtherItem = path.some(
|
||||
(it) =>
|
||||
it instanceof HTMLElement &&
|
||||
it.classList.contains(itemStyle()),
|
||||
);
|
||||
const isChild = closestLevel.startsWith(
|
||||
props.level.join('/'),
|
||||
);
|
||||
|
||||
if (isOtherItem && !isChild) {
|
||||
setOpen(false);
|
||||
document.removeEventListener('mousemove', onOtherHover);
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousemove', onOtherHover);
|
||||
}
|
||||
}, 225);
|
||||
}, { once: true });
|
||||
if (isOtherItem && !isChild) {
|
||||
setOpen(false);
|
||||
document.removeEventListener('mousemove', onOtherHover);
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousemove', onOtherHover);
|
||||
}
|
||||
}, 225);
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
}, 225);
|
||||
|
||||
event.target?.addEventListener('mouseleave', () => {
|
||||
clearTimeout(timer);
|
||||
}, { once: true });
|
||||
event.target?.addEventListener(
|
||||
'mouseleave',
|
||||
() => {
|
||||
clearTimeout(timer);
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -244,7 +288,6 @@ export const PanelItem = (props: PanelItemProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<li
|
||||
ref={setAnchor}
|
||||
@ -253,45 +296,66 @@ export const PanelItem = (props: PanelItemProps) => {
|
||||
onClick={handleClick}
|
||||
data-selected={open()}
|
||||
>
|
||||
<Switch fallback={<div class={itemIconStyle()}/>}>
|
||||
<Switch fallback={<div class={itemIconStyle()} />}>
|
||||
<Match when={props.type === 'checkbox' && props.checked}>
|
||||
<svg class={itemIconStyle()} 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
|
||||
class={itemIconStyle()}
|
||||
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>
|
||||
</Match>
|
||||
<Match when={props.type === 'radio' && props.checked}>
|
||||
<svg class={itemIconStyle()} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
style={{ padding: '6px' }}>
|
||||
<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
|
||||
class={itemIconStyle()}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
style={{ padding: '6px' }}
|
||||
>
|
||||
<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>
|
||||
</Match>
|
||||
<Match when={props.type === 'radio' && !props.checked}>
|
||||
<svg class={itemIconStyle()} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
style={{ padding: '6px' }}>
|
||||
<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
|
||||
class={itemIconStyle()}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
style={{ padding: '6px' }}
|
||||
>
|
||||
<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>
|
||||
</Match>
|
||||
</Switch>
|
||||
<span class={itemLabelStyle()}>
|
||||
{props.name}
|
||||
</span>
|
||||
<Show when={props.chip} fallback={<div/>}>
|
||||
<span class={itemChipStyle()}>
|
||||
{props.chip}
|
||||
</span>
|
||||
<span class={itemLabelStyle()}>{props.name}</span>
|
||||
<Show when={props.chip} fallback={<div />}>
|
||||
<span class={itemChipStyle()}>{props.chip}</span>
|
||||
</Show>
|
||||
<Show when={props.type === 'submenu'}>
|
||||
<svg class={itemIconStyle()} 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
|
||||
class={itemIconStyle()}
|
||||
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>
|
||||
<Panel
|
||||
ref={setChild}
|
||||
@ -322,9 +386,7 @@ export const PanelItem = (props: PanelItemProps) => {
|
||||
exitActiveClass={animationStyle().exitActive}
|
||||
>
|
||||
<Show when={toolTipOpen()}>
|
||||
<div class={toolTipStyle()}>
|
||||
{props.toolTip}
|
||||
</div>
|
||||
<div class={toolTipStyle()}>{props.toolTip}</div>
|
||||
</Show>
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,15 @@
|
||||
import { Menu, MenuItem } from 'electron';
|
||||
import { createEffect, createResource, createSignal, Index, Match, onCleanup, onMount, Show, Switch } from 'solid-js';
|
||||
import {
|
||||
createEffect,
|
||||
createResource,
|
||||
createSignal,
|
||||
Index,
|
||||
Match,
|
||||
onCleanup,
|
||||
onMount,
|
||||
Show,
|
||||
Switch,
|
||||
} from 'solid-js';
|
||||
import { css } from 'solid-styled-components';
|
||||
import { TransitionGroup } from 'solid-transition-group';
|
||||
|
||||
@ -14,49 +24,55 @@ import { cacheNoArgs } from '@/providers/decorators';
|
||||
import type { RendererContext } from '@/types/contexts';
|
||||
import type { InAppMenuConfig } from '../constants';
|
||||
|
||||
const titleStyle = cacheNoArgs(() => css`
|
||||
-webkit-app-region: drag;
|
||||
box-sizing: border-box;
|
||||
const titleStyle = cacheNoArgs(
|
||||
() => css`
|
||||
-webkit-app-region: drag;
|
||||
box-sizing: border-box;
|
||||
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 10000000;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 10000000;
|
||||
|
||||
width: 100%;
|
||||
height: var(--menu-bar-height, 32px);
|
||||
width: 100%;
|
||||
height: var(--menu-bar-height, 32px);
|
||||
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
color: #f1f1f1;
|
||||
font-size: 12px;
|
||||
padding: 4px 4px 4px var(--offset-left, 4px);
|
||||
background-color: var(--titlebar-background-color, #030303);
|
||||
user-select: none;
|
||||
color: #f1f1f1;
|
||||
font-size: 12px;
|
||||
padding: 4px 4px 4px var(--offset-left, 4px);
|
||||
background-color: var(--titlebar-background-color, #030303);
|
||||
user-select: none;
|
||||
|
||||
transition: opacity 200ms ease 0s,
|
||||
transform 300ms cubic-bezier(0.2, 0, 0.6, 1) 0s,
|
||||
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) 0s;
|
||||
transition:
|
||||
opacity 200ms ease 0s,
|
||||
transform 300ms cubic-bezier(0.2, 0, 0.6, 1) 0s,
|
||||
background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) 0s;
|
||||
|
||||
&[data-macos="true"] {
|
||||
padding: 4px 4px 4px 74px;
|
||||
}
|
||||
&[data-macos='true'] {
|
||||
padding: 4px 4px 4px 74px;
|
||||
}
|
||||
|
||||
ytmusic-app:has(ytmusic-player[player-ui-state=FULLSCREEN]) ~ &:not([data-show="true"]) {
|
||||
transform: translateY(calc(-1 * var(--menu-bar-height, 32px)));
|
||||
}
|
||||
`);
|
||||
ytmusic-app:has(ytmusic-player[player-ui-state='FULLSCREEN'])
|
||||
~ &:not([data-show='true']) {
|
||||
transform: translateY(calc(-1 * var(--menu-bar-height, 32px)));
|
||||
}
|
||||
`,
|
||||
);
|
||||
|
||||
const separatorStyle = cacheNoArgs(() => css`
|
||||
min-height: 1px;
|
||||
height: 1px;
|
||||
margin: 4px 0;
|
||||
const separatorStyle = cacheNoArgs(
|
||||
() => css`
|
||||
min-height: 1px;
|
||||
height: 1px;
|
||||
margin: 4px 0;
|
||||
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
`);
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
`,
|
||||
);
|
||||
|
||||
const animationStyle = cacheNoArgs(() => ({
|
||||
enter: css`
|
||||
@ -64,14 +80,18 @@ const animationStyle = cacheNoArgs(() => ({
|
||||
transform: translateX(-50%) scale(0.8);
|
||||
`,
|
||||
enterActive: css`
|
||||
transition: opacity 0.1s cubic-bezier(0.33, 1, 0.68, 1), transform 0.1s cubic-bezier(0.33, 1, 0.68, 1);
|
||||
transition:
|
||||
opacity 0.1s cubic-bezier(0.33, 1, 0.68, 1),
|
||||
transform 0.1s cubic-bezier(0.33, 1, 0.68, 1);
|
||||
`,
|
||||
exitTo: css`
|
||||
opacity: 0;
|
||||
transform: translateX(-50%) scale(0.8);
|
||||
`,
|
||||
exitActive: css`
|
||||
transition: opacity 0.1s cubic-bezier(0.32, 0, 0.67, 0), transform 0.1s cubic-bezier(0.32, 0, 0.67, 0);
|
||||
transition:
|
||||
opacity 0.1s cubic-bezier(0.32, 0, 0.67, 0),
|
||||
transform 0.1s cubic-bezier(0.32, 0, 0.67, 0);
|
||||
`,
|
||||
move: css`
|
||||
transition: all 0.1s cubic-bezier(0.65, 0, 0.35, 1);
|
||||
@ -89,7 +109,7 @@ export type PanelRendererProps = {
|
||||
items: Electron.Menu['items'];
|
||||
level?: number[];
|
||||
onClick?: (commandId: number, radioGroup?: MenuItem[]) => void;
|
||||
}
|
||||
};
|
||||
const PanelRenderer = (props: PanelRendererProps) => {
|
||||
const radioGroup = () => props.items.filter((it) => it.type === 'radio');
|
||||
|
||||
@ -114,12 +134,12 @@ const PanelRenderer = (props: PanelRendererProps) => {
|
||||
name={subItem().label}
|
||||
chip={subItem().sublabel}
|
||||
toolTip={subItem().toolTip}
|
||||
level={[...props.level ?? [], subItem().commandId]}
|
||||
level={[...(props.level ?? []), subItem().commandId]}
|
||||
commandId={subItem().commandId}
|
||||
>
|
||||
<PanelRenderer
|
||||
items={subItem().submenu?.items ?? []}
|
||||
level={[...props.level ?? [], subItem().commandId]}
|
||||
level={[...(props.level ?? []), subItem().commandId]}
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
</PanelItem>
|
||||
@ -143,11 +163,13 @@ const PanelRenderer = (props: PanelRendererProps) => {
|
||||
chip={subItem().sublabel}
|
||||
toolTip={subItem().toolTip}
|
||||
commandId={subItem().commandId}
|
||||
onChange={() => props.onClick?.(subItem().commandId, radioGroup())}
|
||||
onChange={() =>
|
||||
props.onClick?.(subItem().commandId, radioGroup())
|
||||
}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={subItem().type === 'separator'}>
|
||||
<hr class={separatorStyle()}/>
|
||||
<hr class={separatorStyle()} />
|
||||
</Match>
|
||||
</Switch>
|
||||
</Show>
|
||||
@ -169,8 +191,13 @@ export const TitleBar = (props: TitleBarProps) => {
|
||||
const [menu, setMenu] = createSignal<Menu | null>(null);
|
||||
const [mouseY, setMouseY] = createSignal(0);
|
||||
|
||||
const [data, { refetch }] = createResource(async () => await props.ipc.invoke('get-menu') as Promise<Menu | null>);
|
||||
const [isMaximized, { refetch: refetchMaximize }] = createResource(async () => await props.ipc.invoke('window-is-maximized') as Promise<boolean>);
|
||||
const [data, { refetch }] = createResource(
|
||||
async () => (await props.ipc.invoke('get-menu')) as Promise<Menu | null>,
|
||||
);
|
||||
const [isMaximized, { refetch: refetchMaximize }] = createResource(
|
||||
async () =>
|
||||
(await props.ipc.invoke('window-is-maximized')) as Promise<boolean>,
|
||||
);
|
||||
|
||||
const handleToggleMaximize = async () => {
|
||||
if (isMaximized()) {
|
||||
@ -194,10 +221,12 @@ export const TitleBar = (props: TitleBarProps) => {
|
||||
)) as MenuItem | null;
|
||||
|
||||
const newMenu = structuredClone(originalMenu);
|
||||
const stack = [...newMenu?.items ?? []];
|
||||
const stack = [...(newMenu?.items ?? [])];
|
||||
let now: MenuItem | undefined = stack.pop();
|
||||
while (now) {
|
||||
const index = now?.submenu?.items?.findIndex((it) => it.commandId === commandId) ?? -1;
|
||||
const index =
|
||||
now?.submenu?.items?.findIndex((it) => it.commandId === commandId) ??
|
||||
-1;
|
||||
|
||||
if (index >= 0) {
|
||||
if (menuItem) now?.submenu?.items?.splice(index, 1, menuItem);
|
||||
@ -213,13 +242,16 @@ export const TitleBar = (props: TitleBarProps) => {
|
||||
return newMenu;
|
||||
};
|
||||
|
||||
const handleItemClick = async (commandId: number, radioGroup?: MenuItem[]) => {
|
||||
const handleItemClick = async (
|
||||
commandId: number,
|
||||
radioGroup?: MenuItem[],
|
||||
) => {
|
||||
const menuData = menu();
|
||||
if (!menuData) return;
|
||||
|
||||
if (Array.isArray(radioGroup)) {
|
||||
let newMenu = menuData;
|
||||
for await (const item of radioGroup) {
|
||||
for (const item of radioGroup) {
|
||||
newMenu = await refreshMenuItem(newMenu, item.commandId);
|
||||
}
|
||||
|
||||
@ -272,18 +304,15 @@ export const TitleBar = (props: TitleBarProps) => {
|
||||
window.addEventListener('mousemove', listener);
|
||||
const ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
|
||||
ytmusicAppLayout?.addEventListener('scroll', () => {
|
||||
const scrollValue = ytmusicAppLayout.scrollTop;
|
||||
if (scrollValue > 20){
|
||||
ytmusicAppLayout.classList.add('content-scrolled');
|
||||
}
|
||||
else{
|
||||
ytmusicAppLayout.classList.remove('content-scrolled');
|
||||
}
|
||||
const scrollValue = ytmusicAppLayout.scrollTop;
|
||||
if (scrollValue > 20) {
|
||||
ytmusicAppLayout.classList.add('content-scrolled');
|
||||
} else {
|
||||
ytmusicAppLayout.classList.remove('content-scrolled');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
createEffect(() => {
|
||||
if (!menu() && data()) {
|
||||
setMenu(data() ?? null);
|
||||
@ -295,7 +324,12 @@ export const TitleBar = (props: TitleBarProps) => {
|
||||
});
|
||||
|
||||
return (
|
||||
<nav data-ytmd-main-panel={true} class={titleStyle()} data-macos={props.isMacOS} data-show={mouseY() < 32}>
|
||||
<nav
|
||||
data-ytmd-main-panel={true}
|
||||
class={titleStyle()}
|
||||
data-macos={props.isMacOS}
|
||||
data-show={mouseY() < 32}
|
||||
>
|
||||
<IconButton
|
||||
onClick={() => setCollapsed(!collapsed())}
|
||||
style={{
|
||||
@ -310,15 +344,34 @@ export const TitleBar = (props: TitleBarProps) => {
|
||||
</svg>
|
||||
</IconButton>
|
||||
<TransitionGroup
|
||||
enterClass={ignoreTransition() ? animationStyle().fakeTarget : animationStyle().enter}
|
||||
enterActiveClass={ignoreTransition() ? animationStyle().fake : animationStyle().enterActive}
|
||||
exitToClass={ignoreTransition() ? animationStyle().fakeTarget : animationStyle().exitTo}
|
||||
exitActiveClass={ignoreTransition() ? animationStyle().fake : animationStyle().exitActive}
|
||||
enterClass={
|
||||
ignoreTransition()
|
||||
? animationStyle().fakeTarget
|
||||
: animationStyle().enter
|
||||
}
|
||||
enterActiveClass={
|
||||
ignoreTransition()
|
||||
? animationStyle().fake
|
||||
: animationStyle().enterActive
|
||||
}
|
||||
exitToClass={
|
||||
ignoreTransition()
|
||||
? animationStyle().fakeTarget
|
||||
: animationStyle().exitTo
|
||||
}
|
||||
exitActiveClass={
|
||||
ignoreTransition()
|
||||
? animationStyle().fake
|
||||
: animationStyle().exitActive
|
||||
}
|
||||
onBeforeEnter={(element) => {
|
||||
if (ignoreTransition()) return;
|
||||
const index = Number(element.getAttribute('data-index') ?? 0);
|
||||
|
||||
(element as HTMLElement).style.setProperty('transition-delay', `${(index * 0.025)}s`);
|
||||
(element as HTMLElement).style.setProperty(
|
||||
'transition-delay',
|
||||
`${index * 0.025}s`,
|
||||
);
|
||||
}}
|
||||
onAfterEnter={(element) => {
|
||||
(element as HTMLElement).style.removeProperty('transition-delay');
|
||||
@ -328,13 +381,18 @@ export const TitleBar = (props: TitleBarProps) => {
|
||||
const index = Number(element.getAttribute('data-index') ?? 0);
|
||||
const length = Number(element.getAttribute('data-length') ?? 1);
|
||||
|
||||
(element as HTMLElement).style.setProperty('transition-delay', `${(length * 0.025) - (index * 0.025)}s`);
|
||||
(element as HTMLElement).style.setProperty(
|
||||
'transition-delay',
|
||||
`${length * 0.025 - index * 0.025}s`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Show when={!collapsed()}>
|
||||
<Index each={menu()?.items}>
|
||||
{(item, index) => {
|
||||
const [anchor, setAnchor] = createSignal<HTMLElement | null>(null);
|
||||
const [anchor, setAnchor] = createSignal<HTMLElement | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const handleClick = () => {
|
||||
if (openTarget() === anchor()) {
|
||||
@ -372,7 +430,7 @@ export const TitleBar = (props: TitleBarProps) => {
|
||||
</Show>
|
||||
</TransitionGroup>
|
||||
<Show when={props.enableController}>
|
||||
<div style={{ flex: 1 }}/>
|
||||
<div style={{ flex: 1 }} />
|
||||
<WindowController
|
||||
isMaximize={isMaximized()}
|
||||
onToggleMaximize={handleToggleMaximize}
|
||||
@ -383,4 +441,3 @@ export const TitleBar = (props: TitleBarProps) => {
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -4,19 +4,21 @@ import { Show } from 'solid-js';
|
||||
import { IconButton } from './IconButton';
|
||||
import { cacheNoArgs } from '@/providers/decorators';
|
||||
|
||||
const containerStyle = cacheNoArgs(() => css`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
const containerStyle = cacheNoArgs(
|
||||
() => css`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
& > *:last-of-type {
|
||||
border-top-right-radius: 4px;
|
||||
& > *:last-of-type {
|
||||
border-top-right-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 0, 0, 0.5);
|
||||
&:hover {
|
||||
background: rgba(255, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
`,
|
||||
);
|
||||
|
||||
export type WindowControllerProps = {
|
||||
isMaximize?: boolean;
|
||||
@ -24,20 +26,35 @@ export type WindowControllerProps = {
|
||||
onToggleMaximize?: () => void;
|
||||
onMinimize?: () => void;
|
||||
onClose?: () => void;
|
||||
}
|
||||
};
|
||||
export const WindowController = (props: WindowControllerProps) => {
|
||||
return (
|
||||
<div class={containerStyle()}>
|
||||
<IconButton onClick={props.onMinimize}>
|
||||
<svg width={16} height={16} fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor" d="M3.755 12.5h16.492a.75.75 0 0 0 0-1.5H3.755a.75.75 0 0 0 0 1.5Z"/>
|
||||
<svg
|
||||
width={16}
|
||||
height={16}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M3.755 12.5h16.492a.75.75 0 0 0 0-1.5H3.755a.75.75 0 0 0 0 1.5Z"
|
||||
/>
|
||||
</svg>
|
||||
</IconButton>
|
||||
<IconButton onClick={props.onToggleMaximize}>
|
||||
<Show
|
||||
when={props.isMaximize}
|
||||
fallback={
|
||||
<svg width={16} height={16} fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
width={16}
|
||||
height={16}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
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"
|
||||
@ -45,7 +62,13 @@ export const WindowController = (props: WindowControllerProps) => {
|
||||
</svg>
|
||||
}
|
||||
>
|
||||
<svg width={16} height={16} fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
width={16}
|
||||
height={16}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
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"
|
||||
@ -54,7 +77,13 @@ export const WindowController = (props: WindowControllerProps) => {
|
||||
</Show>
|
||||
</IconButton>
|
||||
<IconButton onClick={props.onClose}>
|
||||
<svg width={16} height={16} fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
width={16}
|
||||
height={16}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
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"
|
||||
|
||||
Reference in New Issue
Block a user