fix(in-app-menu): fix app crash in production

This commit is contained in:
Su-Yong
2024-02-05 23:14:00 +09:00
parent b3c05c8647
commit febc63edef
7 changed files with 143 additions and 132 deletions

View File

@ -1,5 +1,6 @@
import { createSignal } from 'solid-js';
import { render } from 'solid-js/web';
import { extractCss } from 'solid-styled-components';
import { TitleBar } from './renderer/TitleBar';
import { defaultInAppMenuConfig, InAppMenuConfig } from './constants';

View File

@ -1,7 +1,9 @@
import { JSX } from 'solid-js';
import { css } from 'solid-styled-components';
const iconButton = css`
import { cache } from '@/providers/decorators';
const iconButton = cache(() => css`
background: transparent;
width: 24px;
@ -28,12 +30,12 @@ const iconButton = css`
&:active {
scale: 0.9;
}
`;
`);
type CollapseIconButtonProps = JSX.HTMLAttributes<HTMLButtonElement>;
export const IconButton = (props: CollapseIconButtonProps) => {
return (
<button {...props} class={iconButton}>
<button {...props} class={iconButton()}>
{props.children}
</button>
);

View File

@ -1,7 +1,8 @@
import { JSX, splitProps } from 'solid-js';
import { css } from 'solid-styled-components';
import { cache } from '@/providers/decorators';
const menuStyle = css`
const menuStyle = cache(() => css`
-webkit-app-region: none;
display: flex;
@ -25,7 +26,7 @@ const menuStyle = css`
&[data-selected="true"] {
background-color: rgba(255, 255, 255, 0.2);
}
`;
`);
export type MenuButtonProps = JSX.HTMLAttributes<HTMLLIElement> & {
text?: string;
@ -35,7 +36,7 @@ export const MenuButton = (props: MenuButtonProps) => {
const [local, leftProps] = splitProps(props, ['text']);
return (
<li {...leftProps} class={menuStyle} data-selected={props.selected}>
<li {...leftProps} class={menuStyle()} data-selected={props.selected}>
{local.text}
</li>
);

View File

@ -4,8 +4,9 @@ import { css } from 'solid-styled-components';
import { Transition } from 'solid-transition-group';
import { autoUpdate, flip, offset, OffsetOptions, size } from '@floating-ui/dom';
import { useFloating } from 'solid-floating-ui';
import { cache } from '@/providers/decorators';
const panelStyle = css`
const panelStyle = cache(() => css`
position: fixed;
top: var(--offset-y, 0);
left: var(--offset-x, 0);
@ -32,9 +33,9 @@ const panelStyle = css`
0 2px 8px rgba(0, 0, 0, 0.2);
transform-origin: var(--origin-x, 50%) var(--origin-y, 50%);
`;
`);
const animationStyle = {
const animationStyle = cache(() => ({
enter: css`
opacity: 0;
transform: scale(0.9);
@ -49,7 +50,7 @@ const animationStyle = {
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);
`,
};
}));
export type Placement =
'top'
@ -122,16 +123,16 @@ export const Panel = (props: PanelProps) => {
<Portal>
<Transition
appear
enterClass={animationStyle.enter}
enterActiveClass={animationStyle.enterActive}
exitToClass={animationStyle.exitTo}
exitActiveClass={animationStyle.exitActive}
enterClass={animationStyle().enter}
enterActiveClass={animationStyle().enterActive}
exitToClass={animationStyle().exitTo}
exitActiveClass={animationStyle().exitActive}
>
<Show when={local.open}>
<ul
{...leftProps}
ref={setPanel}
class={panelStyle}
class={panelStyle()}
style={{
'--offset-x': `${position.x}px`,
'--offset-y': `${position.y}px`,

View File

@ -8,117 +8,119 @@ 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 = css`
position: relative;
const itemStyle = cache(() => 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;
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);
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
&:hover {
background-color: rgba(255, 255, 255, 0.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 = css`
height: 32px;
padding: 4px;
color: white;
`;
const itemLabelStyle = css`
font-size: 12px;
color: white;
`;
const itemChipStyle = css`
display: flex;
justify-content: center;
align-items: center;
min-width: 16px;
height: 16px;
padding: 0 4px;
margin-left: 8px;
border-radius: 4px;
&:active {
background-color: rgba(255, 255, 255, 0.2);
color: #f1f1f1;
font-size: 10px;
font-weight: 500;
line-height: 1;
`;
}
const toolTipStyle = css`
min-width: 32px;
width: 100%;
height: 100%;
&[data-selected="true"] {
background-color: rgba(255, 255, 255, 0.2);
}
padding: 4px;
& * {
box-sizing: border-box;
}
`);
max-width: calc(var(--max-width, 100%) - 8px);
max-height: calc(var(--max-height, 100%) - 8px);
const itemIconStyle = cache(() => css`
height: 32px;
padding: 4px;
color: white;
`);
border-radius: 4px;
background-color: rgba(25, 25, 25, 0.8);
color: #f1f1f1;
font-size: 10px;
`;
const itemLabelStyle = cache(() => css`
font-size: 12px;
color: white;
`);
const popupStyle = css`
position: fixed;
top: var(--offset-y, 0);
left: var(--offset-x, 0);
const itemChipStyle = cache(() => css`
display: flex;
justify-content: center;
align-items: center;
max-width: var(--max-width, 100%);
max-height: var(--max-height, 100%);
min-width: 16px;
height: 16px;
padding: 0 4px;
margin-left: 8px;
z-index: 100000000;
pointer-events: none;
border-radius: 4px;
background-color: rgba(255, 255, 255, 0.2);
color: #f1f1f1;
font-size: 10px;
font-weight: 500;
line-height: 1;
`);
`;
const animationStyle = {
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);
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);
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);
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);
`,
};
}));
const getParents = (element: Element | null): (HTMLElement | null)[] => {
const parents: (HTMLElement | null)[] = [];
@ -211,7 +213,7 @@ export const PanelItem = (props: PanelItemProps) => {
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 isOtherItem = path.some((it) => it instanceof HTMLElement && it.classList.contains(itemStyle()));
const isChild = closestLevel.startsWith(props.level.join('/'));
if (isOtherItem && !isChild) {
@ -246,14 +248,14 @@ export const PanelItem = (props: PanelItemProps) => {
return (
<li
ref={setAnchor}
class={itemStyle}
class={itemStyle()}
onMouseEnter={handleHover}
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"
<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"/>
@ -261,28 +263,30 @@ export const PanelItem = (props: PanelItemProps) => {
</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' }}>
<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' }}>
<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}>
<span class={itemLabelStyle()}>
{props.name}
</span>
<Show when={props.chip} fallback={<div/>}>
<span class={itemChipStyle}>
<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"
<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">
@ -304,7 +308,7 @@ export const PanelItem = (props: PanelItemProps) => {
<Portal>
<div
ref={setToolTip}
class={popupStyle}
class={popupStyle()}
style={{
'--offset-x': `${position.x}px`,
'--offset-y': `${position.y}px`,
@ -312,13 +316,13 @@ export const PanelItem = (props: PanelItemProps) => {
>
<Transition
appear
enterClass={animationStyle.enter}
enterActiveClass={animationStyle.enterActive}
exitToClass={animationStyle.exitTo}
exitActiveClass={animationStyle.exitActive}
enterClass={animationStyle().enter}
enterActiveClass={animationStyle().enterActive}
exitToClass={animationStyle().exitTo}
exitActiveClass={animationStyle().exitActive}
>
<Show when={toolTipOpen()}>
<div class={toolTipStyle}>
<div class={toolTipStyle()}>
{props.toolTip}
</div>
</Show>

View File

@ -11,8 +11,9 @@ import { WindowController } from './WindowController';
import type { RendererContext } from '@/types/contexts';
import type { InAppMenuConfig } from '../constants';
import { cache } from '@/providers/decorators';
const titleStyle = css`
const titleStyle = cache(() => css`
-webkit-app-region: drag;
box-sizing: border-box;
@ -41,17 +42,17 @@ const titleStyle = css`
&[data-macos="true"] {
padding: 4px 4px 4px 74px;
}
`;
`);
const separatorStyle = css`
const separatorStyle = cache(() => css`
min-height: 1px;
height: 1px;
margin: 4px 0;
background-color: rgba(255, 255, 255, 0.2);
`;
`);
const animationStyle = {
const animationStyle = cache(() => ({
enter: css`
opacity: 0;
transform: translateX(-50%) scale(0.8);
@ -76,7 +77,7 @@ const animationStyle = {
fake: css`
transition: all 0.00000000001s;
`,
};
}));
export type PanelRendererProps = {
items: Electron.Menu['items'];
@ -140,7 +141,7 @@ const PanelRenderer = (props: PanelRendererProps) => {
/>
</Match>
<Match when={subItem().type === 'separator'}>
<hr class={separatorStyle}/>
<hr class={separatorStyle()}/>
</Match>
</Switch>
</Show>
@ -251,7 +252,7 @@ export const TitleBar = (props: TitleBarProps) => {
});
return (
<nav class={titleStyle} data-macos={props.isMacOS}>
<nav class={titleStyle()} data-macos={props.isMacOS}>
<IconButton
onClick={() => setCollapsed(!collapsed())}
style={{
@ -266,10 +267,10 @@ 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);

View File

@ -2,8 +2,9 @@ import { css } from 'solid-styled-components';
import { Show } from 'solid-js';
import { IconButton } from './IconButton';
import { cache } from '@/providers/decorators';
const containerStyle = css`
const containerStyle = cache(() => css`
display: flex;
justify-content: flex-end;
align-items: center;
@ -15,7 +16,7 @@ const containerStyle = css`
background: rgba(255, 0, 0, 0.5);
}
}
`;
`);
export type WindowControllerProps = {
isMaximize?: boolean;
@ -26,7 +27,7 @@ export type WindowControllerProps = {
}
export const WindowController = (props: WindowControllerProps) => {
return (
<div class={containerStyle}>
<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"/>