diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 3c53ec8..d0adbed 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -24,6 +24,8 @@ import { type Command, Palette } from './ui/Palette'; import { ExportMenu, useExportItems, useRunExport } from './ui/Exports'; import { ErrorBoundary } from './ui/ErrorBoundary'; import { Toasts } from './ui/Toasts'; +import { useContextMenu } from './ui/ContextMenu'; +import { appMenu } from './ui/app-menu'; import { worstTone } from './ui/ds'; import { attention } from './lib/model'; @@ -149,6 +151,7 @@ export function App({ onTheme }: { onTheme: () => void }) { const groups = d.studios.length ? groupByStudio(listed.map(a => ({ app: a })), d.studios) : [{ studio: null as string | null, rows: listed.map(a => ({ app: a })) }]; const ignoredCount = d.apps.filter(a => a.ignored).length + d.overrides.ignored.filter(i => i.startsWith('account:')).length; + const menu = useContextMenu(); const appItem = (a: UnifiedApp) => { const e = d.statusFor(a); const missing = !!a.project?.missing; @@ -156,7 +159,7 @@ export function App({ onTheme }: { onTheme: () => void }) { const bad = missing ? 1 : (e?.errors.length ?? 0) + (e?.check?.problems.length ?? 0); return (
- : undefined} /> @@ -27,7 +30,7 @@ export function Ignored() { {apps.map(a => { const s = d.studios.find(x => x.id === a.studio) ?? null; return ( -
+
appMenu(a, d, nav, { showIgnored: true }))}>
{a.name}{!a.project && } @@ -49,7 +52,7 @@ export function Ignored() { {accounts.length ? (
{accounts.map(a => ( -
+
[{ 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' }) }])}>
{a.label}{a.kind === 'play' ? t('acct.kindPlay') : t('acct.kindApple')} · {t('ignored.accountApps', { count: d.storeApps[a.id]?.value.apps.length ?? 0 })}
diff --git a/apps/desktop/src/screens/Portfolio.tsx b/apps/desktop/src/screens/Portfolio.tsx index 14ff770..1f6781f 100644 --- a/apps/desktop/src/screens/Portfolio.tsx +++ b/apps/desktop/src/screens/Portfolio.tsx @@ -12,6 +12,8 @@ import { useShowIgnored } from '../lib/show-ignored'; import { filterSort, groupByStudio, type ListRow, type SortBy, studioCounts, type UnifiedApp, withIgnored } from '../lib/identity'; import { type Attention, attention, healthSeries, type HealthSnapshot, isError, laneOf, PLAY_THRESHOLDS, STAGES, storeNow, type Tone } from '../lib/model'; import { type AppTab, useNav } from '../nav'; +import { useContextMenu } from '../ui/ContextMenu'; +import { appMenu } from '../ui/app-menu'; import type { IconName } from '../ui/icons'; import { useData } from '../store'; import { exportName, portfolioCsv } from '../lib/exports'; @@ -144,8 +146,11 @@ function AppCard({ r, icon, health, studio }: { r: CardRow; icon: string | null; const name = e?.app?.name && app.project ? e.app.name : app.name; const go = (tab: AppTab) => nav.go({ screen: 'app', appId: app.key, tab }); const style = tintStyle(hue); + const d = useData(); + const menu = useContextMenu(); + const [showIgnored] = useShowIgnored(); return ( -
+
appMenu(app, d, nav, { showIgnored }))}> @@ -285,6 +290,7 @@ export function Portfolio() { const visible = useMemo(() => d.apps.filter(a => !a.ignored), [d.apps]); const ignoredApps = useMemo(() => d.apps.filter(a => a.ignored), [d.apps]); const [showIgnored, setShowIgnored] = useShowIgnored(); + const menu = useContextMenu(); const [round, setRound] = useState(0); const [rechecking, setRechecking] = useState(false); const health = useCachedHealth(visible.map(a => a.target), round); @@ -349,7 +355,7 @@ export function Portfolio() { const ios = !!(app.ios || e?.app?.platforms.ios), android = !!(app.android || e?.app?.platforms.android); const sub = app.ios?.app.bundleId ?? e?.app?.platforms.ios?.bundleId ?? app.android?.app.id ?? e?.app?.platforms.android?.packageName ?? ''; return ( - open(app)}> + open(app)} {...menu(() => appMenu(app, d, nav, { showIgnored }))}>
@@ -435,7 +441,7 @@ export function Portfolio() { {showIgnored && ignoredApps.map(a => ( - + ))} diff --git a/apps/desktop/src/screens/tabs/Art.tsx b/apps/desktop/src/screens/tabs/Art.tsx index 43517fc..412dbac 100644 --- a/apps/desktop/src/screens/tabs/Art.tsx +++ b/apps/desktop/src/screens/tabs/Art.tsx @@ -2,6 +2,8 @@ // size (`shots gallery --json`, offline), store check issues outlined. Images load one by one as they scroll into view // (`project.render`, a data: URL each: the window loads no files itself). Re-render (dry run, dialog, real run), write // the review page, open it or the folder. +import { type MenuInput, useContextMenu } from '../../ui/ContextMenu'; +import { copyToast } from '../../ui/app-menu'; import '../../styles/art.css'; import { type KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { api, errorText, type Project, type RenderImage, type RendersResult, type RenderTile, type ToolResult } from '../../api'; @@ -41,7 +43,8 @@ function loadRender(projectId: string, file: string, stamp: string): Promise void }) { +function Thumb({ projectId, tile, stamp, onOpen, menu }: { projectId: string; tile: RenderTile; stamp: string; onOpen: () => void; menu: () => MenuInput }) { + const cm = useContextMenu(); const ref = useRef(null); const file = tile.kind === 'video' ? tile.poster : tile.src; const [src, setSrc] = useState(file ? cache.get(`${projectId}|${file}|${stamp}`) : null); @@ -61,7 +64,7 @@ function Thumb({ projectId, tile, stamp, onOpen }: { projectId: string; tile: Re const lvl = tile.issues.some(i => i.level === 'error') ? 'err' : tile.issues.length ? 'warn' : ''; const shape = tile.h > tile.w ? 'portrait' : 'landscape'; return ( -
+