desktop editors: edit in place what App Store Connect and Play Console let the owner edit (listings per language for both stores with live counters and the check / scan rules, store details, App info, age rating, App Review, Play store settings, Data safety and App Privacy answers, promotion blocks, catalog names / prices / offers, TestFlight information, scene order); storebound edit (project.edit) saves into the config by path keeping comments and formatting, the listing files and the review contact, dry run with the diff and the checks first; Apply shows each store's dry run in the window; unsaved edits survive tab switches with undo / redo; tests/edit.test.ts, apps/desktop/tests/edits.test.ts, boot check editor case

This commit is contained in:
CRTkafa 2026-10-14 00:35:29 +08:00
parent 714352ec18
commit 3cea2af4e1
37 changed files with 3020 additions and 54 deletions

View file

@ -23,6 +23,8 @@ import { PricesTab } from './tabs/Prices';
import { ReleasesTab } from './tabs/Releases';
import { ReviewsTab } from './tabs/Reviews';
import { TestFlightTab } from './tabs/TestFlight';
import { EDIT_TABS, TabEditor, useUnsavedGuard } from './editors/TabEditor';
import { useEdits } from '../ui/Editor';
export const ascUrl = (appId: string | null | undefined): string => (appId ? `https://appstoreconnect.apple.com/apps/${encodeURIComponent(appId)}/distribution` : 'https://appstoreconnect.apple.com/apps');
export const PLAY_CONSOLE = 'https://play.google.com/console/';
@ -99,6 +101,8 @@ export function AppView({ app, tab }: { app: UnifiedApp; tab: AppTab }) {
const android = e?.app?.platforms.android ?? (app.android ? { packageName: app.android.app.id } : null);
const name = (app.project && e?.app?.name) || app.name;
const tabs = tabsFor(!!app.project);
const ed = useEdits();
useUnsavedGuard();
const tabRefs = useRef<(HTMLButtonElement | null)[]>([]);
const studio = d.studios.find(s => s.id === app.studio) ?? null;
const setTab = (x: AppTab) => nav.go({ screen: 'app', appId: app.key, tab: x });
@ -146,6 +150,7 @@ export function AppView({ app, tab }: { app: UnifiedApp; tab: AppTab }) {
{tabs.map((x, i) => (
<button key={x} ref={el => { tabRefs.current[i] = el; }} role="tab" id={`tab-${x}`} aria-selected={tab === x} aria-controls="tabpanel" tabIndex={tab === x ? 0 : -1} className="tab" onClick={() => setTab(x)}>
<Icon name={TAB_ICON[x]} />{t(TAB_KEY[x])}
{app.project && EDIT_TABS[x] && ed.count(app.project.id, EDIT_TABS[x]) > 0 && <span className="ed-dot" title={t('ed.unsaved', { count: ed.count(app.project.id, EDIT_TABS[x]) })} />}
</button>
))}
</div>
@ -154,7 +159,7 @@ export function AppView({ app, tab }: { app: UnifiedApp; tab: AppTab }) {
{tab === 'releases' && <ReleasesTab app={app} />}
{tab === 'catalog' && app.project && <CatalogTab project={app.project} />}
{tab === 'prices' && app.project && <PricesTab project={app.project} />}
{tab === 'listings' && app.project && <ListingsTab project={app.project} />}
{tab === 'listings' && app.project && <><TabEditor tab={tab} project={app.project} /><ListingsTab project={app.project} /></>}
{tab === 'art' && app.project && <ArtTab project={app.project} />}
{tab === 'health' && <HealthTab app={app} />}
{tab === 'reviews' && <ReviewsTab app={app} />}
@ -162,6 +167,7 @@ export function AppView({ app, tab }: { app: UnifiedApp; tab: AppTab }) {
{tab === 'growth' && <GrowthTab app={app} />}
{tab === 'appinfo' && app.project && <AppInfoTab project={app.project} />}
{tab === 'checks' && app.project && <ChecksTab project={app.project} />}
{tab !== 'listings' && app.project && !app.project.missing && EDIT_TABS[tab] && <TabEditor tab={tab} project={app.project} />}
</ErrorBoundary>
</div>
</>}