import { createSignal, Match, Show, Switch } from 'solid-js'; import { JSX } from 'solid-js/jsx-runtime'; import { css } from 'solid-styled-components'; import { Portal } from 'solid-js/web'; import { Transition } from 'solid-transition-group'; import { useFloating } from 'solid-floating-ui'; import { autoUpdate, offset, size } from '@floating-ui/dom'; import { Panel } from './Panel'; import { cache } from '@/providers/decorators'; const itemStyle = cache(() => css` position: relative; -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; 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); } & * { box-sizing: border-box; } `); const itemIconStyle = cache(() => css` height: 32px; padding: 4px; color: white; `); const itemLabelStyle = cache(() => css` font-size: 12px; color: white; `); const itemChipStyle = cache(() => css` display: flex; justify-content: center; align-items: center; min-width: 16px; height: 16px; padding: 0 4px; margin-left: 8px; border-radius: 4px; background-color: rgba(255, 255, 255, 0.2); color: #f1f1f1; font-size: 10px; font-weight: 500; line-height: 1; `); const toolTipStyle = cache(() => css` min-width: 32px; width: 100%; height: 100%; 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 = cache(() => 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 = cache(() => ({ enter: css` opacity: 0; 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); `, 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); `, })); const getParents = (element: Element | null): (HTMLElement | null)[] => { const parents: (HTMLElement | null)[] = []; let now = element; while (now) { parents.push(now as HTMLElement | null); now = now.parentElement; } return parents; }; type BasePanelItemProps = { name: string; label?: string; chip?: string; toolTip?: string; commandId?: number; }; type NormalPanelItemProps = BasePanelItemProps & { type: 'normal'; onClick?: () => void; }; type SubmenuItemProps = BasePanelItemProps & { type: 'submenu'; level: number[]; children: JSX.Element; }; type RadioPanelItemProps = BasePanelItemProps & { type: 'radio'; checked: boolean; onChange?: (checked: boolean) => void; }; type CheckboxPanelItemProps = BasePanelItemProps & { type: 'checkbox'; checked: boolean; onChange?: (checked: boolean) => void; }; export type PanelItemProps = NormalPanelItemProps | SubmenuItemProps | RadioPanelItemProps | CheckboxPanelItemProps; export const PanelItem = (props: PanelItemProps) => { const [open, setOpen] = createSignal(false); const [toolTipOpen, setToolTipOpen] = createSignal(false); const [toolTip, setToolTip] = createSignal(null); const [anchor, setAnchor] = createSignal(null); const [child, setChild] = createSignal(null); const position = useFloating(anchor, toolTip, { whileElementsMounted: autoUpdate, placement: 'bottom-start', strategy: 'fixed', middleware: [ offset({ mainAxis: 8 }), size({ apply({ rects, elements }) { elements.floating.style.setProperty('--max-width', `${rects.reference.width}px`); } }), ], }); const handleHover = (event: MouseEvent) => { setToolTipOpen(true); event.target?.addEventListener('mouseleave', () => { setToolTipOpen(false); }, { once: true }); if (props.type === 'submenu') { const timer = setTimeout(() => { setOpen(true); let mouseX = event.clientX; let mouseY = event.clientY; const onMouseMove = (event: MouseEvent) => { mouseX = event.clientX; mouseY = event.clientY; }; document.addEventListener('mousemove', onMouseMove); 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(); 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 }); }, 225); event.target?.addEventListener('mouseleave', () => { clearTimeout(timer); }, { once: true }); } }; const handleClick = async () => { await window.ipcRenderer.invoke('ytmd:menu-event', props.commandId); if (props.type === 'radio') { props.onChange?.(!props.checked); } else if (props.type === 'checkbox') { props.onChange?.(!props.checked); } else if (props.type === 'normal') { props.onClick?.(); } }; return (
  • }> {props.name} }> {props.chip} {props.type === 'submenu' && props.children}
    {props.toolTip}
  • ); };