Desktop: custom context menus everywhere, never the browser's menu

One ContextMenu in the design system (keyboard, type-ahead, submenus, check items,
disabled reasons, Shift+F10 / Menu key, viewport placement, light and dark). The
native menu is prevented globally; text fields keep Cut / Copy / Paste / Select all.
Menus on sidebar and portfolio apps, app hero, releases, store art, live art,
reviews, checks, editor fields, catalog, prices, accounts, app folders, ignored.
This commit is contained in:
CRTkafa 2026-10-23 08:59:14 +08:00
parent 944b24fe59
commit dece83c3e8
23 changed files with 839 additions and 28 deletions

View file

@ -1,5 +1,7 @@
// Ignored: apps and whole store accounts left out of the portfolio, the sidebar and store reads. Restore brings them
// back (nothing on the stores changes either way).
import { useContextMenu } from '../ui/ContextMenu';
import { appMenu } from '../ui/app-menu';
import { t } from '../i18n';
import { withIgnored, withSideIgnored } from '../lib/identity';
import { useNav } from '../nav';
@ -16,6 +18,7 @@ export function Ignored() {
const accounts = d.accounts.filter(a => d.overrides.ignored.includes(`account:${a.id}`));
const restoreAll = () => d.updateOverrides(o => ({ ...o, ignored: [] }));
const empty = !apps.length && !accounts.length;
const cm = useContextMenu();
return (
<div className="page scr-ignored">
<PageHeader icon="eyeOff" eyebrow={t('v2cfg.workspace')} title={t('nav.ignored')} subtitle={t('ignored.lead')} actions={!empty ? <><button className="btn" onClick={restoreAll}><Icon name="eye" />{t('ignored.restoreAll')}</button></> : undefined} />
@ -27,7 +30,7 @@ export function Ignored() {
{apps.map(a => {
const s = d.studios.find(x => x.id === a.studio) ?? null;
return (
<div className="li" key={a.key}>
<div className="li" key={a.key} {...cm(() => appMenu(a, d, nav, { showIgnored: true }))}>
<AppIcon name={a.name} src={d.iconFor(a)} />
<div className="li-main">
<span className="li-title row" style={{ gap: 8 }}>{a.name}{!a.project && <StoreOnlyTag />}</span>
@ -49,7 +52,7 @@ export function Ignored() {
{accounts.length ? (
<div className="list">
{accounts.map(a => (
<div className="li" key={a.id}>
<div className="li" key={a.id} {...cm(() => [{ label: t('ignored.restore'), icon: 'eye', onSelect: () => { void d.updateOverrides(o => withIgnored(o, [`account:${a.id}`], false)); } }, { label: t('cm.openAccounts'), icon: 'key', onSelect: () => nav.go({ screen: 'accounts' }) }])}>
<span className="li-icon tone-bg-neutral"><StoreGlyph store={a.kind === 'play' ? 'android' : 'ios'} /></span>
<div className="li-main"><span className="li-title">{a.label}</span><span className="li-sub">{a.kind === 'play' ? t('acct.kindPlay') : t('acct.kindApple')} · {t('ignored.accountApps', { count: d.storeApps[a.id]?.value.apps.length ?? 0 })}</span></div>
<button className="btn btn-sm" onClick={() => d.updateOverrides(o => withIgnored(o, [`account:${a.id}`], false))}><Icon name="eye" />{t('ignored.restore')}</button>