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:
parent
944b24fe59
commit
dece83c3e8
23 changed files with 839 additions and 28 deletions
|
|
@ -1,5 +1,7 @@
|
|||
// Workspace: app folders (one storebound.config.ts per app), which store accounts each app uses, and starting a new
|
||||
// folder from the templates (`storebound init` with a profile, confirmed in a native dialog).
|
||||
import { type MenuInput, useContextMenu } from '../ui/ContextMenu';
|
||||
import { copyToast } from '../ui/app-menu';
|
||||
import { failureText } from '../lib/results';
|
||||
import { type ReactNode, useEffect, useRef, useState } from 'react';
|
||||
import { api, errorText, newJob, onProgress, type Project, type ToolResult, type WriteOutcome } from '../api';
|
||||
|
|
@ -19,9 +21,10 @@ type Profile = 'app' | 'game' | 'subscription-app';
|
|||
interface Run { title: string; project: string; job: string; lines: string[]; result?: ToolResult; outcome?: WriteOutcome; error?: string }
|
||||
|
||||
/** Folder card wrapper carrying the app's tint (hooks cannot run inside the list map). */
|
||||
function Tinted({ src, name, children }: { src: string | null | undefined; name: string; children: ReactNode }) {
|
||||
function Tinted({ src, name, children, menu }: { src: string | null | undefined; name: string; children: ReactNode; menu: () => MenuInput }) {
|
||||
const hue = useTint(src, name);
|
||||
return <div className="li fold-card" style={tintStyle(hue)}>{children}</div>;
|
||||
const cm = useContextMenu();
|
||||
return <div className="li fold-card" style={tintStyle(hue)} {...cm(menu)}>{children}</div>;
|
||||
}
|
||||
|
||||
function LocateButton({ project }: { project: Project }) {
|
||||
|
|
@ -100,7 +103,21 @@ export function AppFolders() {
|
|||
const e = d.statusOf(p);
|
||||
const st = p.missing ? { text: t('folder.missingShort'), tone: 'bad' as const } : checkState(e);
|
||||
return (
|
||||
<Tinted key={p.id} src={d.icons[p.id]} name={e?.app?.name ?? p.name}>
|
||||
<Tinted key={p.id} src={d.icons[p.id]} name={e?.app?.name ?? p.name} menu={() => [
|
||||
!p.missing && { label: t('folders.open'), icon: 'arrowRight', onSelect: () => nav.go({ screen: 'app', appId: p.id, tab: 'releases' }) },
|
||||
!p.missing && { label: t('folders.reveal'), icon: 'folder', onSelect: () => { api.reveal(p.id).catch(x => setError(errorText(x))); } },
|
||||
'sep',
|
||||
!p.missing && { label: t('folders.check'), icon: 'shield', disabled: busy ? t('cm.busy') : false, onSelect: () => check(p) },
|
||||
!p.missing && { label: t('folders.gallery'), icon: 'image', disabled: busy ? t('cm.busy') : false, onSelect: () => gallery(p) },
|
||||
'sep',
|
||||
{ label: t('cm.copy'), icon: 'copy', submenu: [
|
||||
{ label: t('cm.copyPath'), icon: 'folder', onSelect: () => copyToast(d, p.path) },
|
||||
e?.app?.platforms.ios?.bundleId && { label: t('cm.copyBundleId'), icon: 'tag', shortcut: e.app.platforms.ios.bundleId, onSelect: () => copyToast(d, e.app!.platforms.ios!.bundleId) },
|
||||
e?.app?.platforms.android?.packageName && { label: t('cm.copyPackage'), icon: 'tag', shortcut: e.app.platforms.android.packageName, onSelect: () => copyToast(d, e.app!.platforms.android!.packageName) },
|
||||
] },
|
||||
'sep',
|
||||
{ label: t('folders.remove', { name: p.name }), icon: 'trash', danger: true, disabled: busy ? t('cm.busy') : false, onSelect: () => remove(p) },
|
||||
]}>
|
||||
<AppIcon name={p.name} src={d.icons[p.id]} className="fold-icon" />
|
||||
<div className="li-main">
|
||||
<span className="row" style={{ gap: 8 }}><span className="li-title">{e?.app?.name ?? p.name}</span><StatusPill tone={st.tone}>{st.text}</StatusPill></span>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue