Desktop v2 screens: releases (store cards, stage stepper, rollout ring), submit checklist, TestFlight cards; listings as store pages with completeness, framed screenshots with issue badges, product cards, price matrix with gap shading; health tiles and issue cards, store-style review feed with rating distribution, growth funnel, readiness score with fix-first, tools grid, store-info sheet; settings, accounts, folders, dependencies and onboarding in the v2 language
This commit is contained in:
parent
ce939ee063
commit
f131de9df2
47 changed files with 2309 additions and 927 deletions
|
|
@ -1,13 +1,15 @@
|
|||
// Store art editor (Store art tab): the order of the screenshot scenes (the store shows them in this order) and the
|
||||
// raw frame each scene shows (replace a screenshot by pointing the scene at another frame). Saved into the config;
|
||||
// then render, and upload to each store with the dry run in the window (uploads replace the store's sets).
|
||||
import '../../styles/art.css';
|
||||
import { useState } from 'react';
|
||||
import { api, errorText, type EditableResult, type Project } from '../../api';
|
||||
import { useData, useWrite } from '../../store';
|
||||
import { t } from '../../i18n';
|
||||
import { ApplyPanel, EditBar, TextField, useEdits } from '../../ui/Editor';
|
||||
import { Icon, StoreGlyph } from '../../ui/icons';
|
||||
import { Card, Empty } from '../../ui/kit';
|
||||
import { Empty } from '../../ui/kit';
|
||||
import { Section } from '../../ui/ds';
|
||||
import { keyOf } from '../../lib/edits';
|
||||
|
||||
type Scene = { id: string; palette?: unknown; frames?: { src: string }[] } & Record<string, unknown>;
|
||||
|
|
@ -33,7 +35,7 @@ export function ArtEditor({ project, data, onSaved }: { project: Project; data:
|
|||
const p = e.get(project.id, keyOf(op));
|
||||
const scenes = (p && p.op.kind === 'config' ? p.op.value : saved) as Scene[];
|
||||
const unsaved = e.count(project.id, 'art') ? t('ed.saveFirst') : null;
|
||||
if (!saved.length) return <Card title={t('ed.artTitle')} icon="image"><Empty compact illo="text" title={t('ed.artEmpty')}>{t('ed.artEmptyText')}</Empty></Card>;
|
||||
if (!saved.length) return <Section className="scr-art" title={t('ed.artTitle')} icon="image"><Empty compact illo="text" title={t('ed.artEmpty')}>{t('ed.artEmptyText')}</Empty></Section>;
|
||||
const move = (i: number, d: -1 | 1) => {
|
||||
const next = [...scenes];
|
||||
const j = i + d;
|
||||
|
|
@ -42,8 +44,8 @@ export function ArtEditor({ project, data, onSaved }: { project: Project; data:
|
|||
e.set(project.id, { ...op, value: next }, 'art', saved);
|
||||
};
|
||||
return (
|
||||
<div className="stack" id="art-editor" style={{ gap: 16 }}>
|
||||
<Card title={t('ed.artTitle')} icon="image" sub={t('ed.artSub')} pad={false}
|
||||
<div className="stack scr-art art-editor" id="art-editor" style={{ gap: 16 }}>
|
||||
<Section title={t('ed.artTitle')} icon="image" sub={t('ed.artSub')} pad={false}
|
||||
actions={langs.length > 1 ? <label className="row small" style={{ gap: 6 }}>{t('ed.frameLang')}<select value={fl} onChange={ev => setFrameLang(ev.target.value)} data-frame-lang>{langs.map(l => <option key={l} value={l}>{l}</option>)}</select></label> : undefined}>
|
||||
<ol className="ed-scenes">
|
||||
{scenes.map((s, i) => (
|
||||
|
|
@ -61,16 +63,16 @@ export function ArtEditor({ project, data, onSaved }: { project: Project; data:
|
|||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</Card>
|
||||
</Section>
|
||||
{data.shotsCopy && <Captions project={project} data={data} scenes={scenes.map(s => s.id)} />}
|
||||
<EditBar project={project} area="art" data={data} files={[data.files.config, data.files.shotsCopy ?? null]} onSaved={onSaved} />
|
||||
<Card title={t('ed.applyTitle')} icon="upload" sub={t('ed.applyArtSub')}>
|
||||
<Section title={t('ed.applyTitle')} icon="upload" sub={t('ed.applyArtSub')}>
|
||||
<div className="act-grid">
|
||||
<div className="act-col"><div className="act-head"><Icon name="wand" />{t('act.render')}</div><ApplyPanel project={project} method="shots.render" label={t('act.renderShots')} done={t('act.rendered')} blocked={unsaved} /></div>
|
||||
{data.play && <div className="act-col"><div className="act-head"><StoreGlyph store="android" />Google Play</div><ApplyPanel project={project} method="play.images" label={t('act.playImages')} done={t('act.uploaded')} blocked={unsaved} /></div>}
|
||||
{data.apple && <div className="act-col"><div className="act-head"><StoreGlyph store="ios" />App Store</div><ApplyPanel project={project} method="apple.screenshots" label={t('act.appleShots')} done={t('act.uploaded')} blocked={unsaved} /><ApplyPanel project={project} method="apple.previews" label={t('act.applePreviews')} done={t('act.uploaded')} blocked={unsaved} /></div>}
|
||||
</div>
|
||||
</Card>
|
||||
</Section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -82,7 +84,7 @@ function Captions({ project, data, scenes }: { project: Project; data: EditableR
|
|||
const [lang, setLang] = useState(langs[0] ?? '');
|
||||
const L = langs.includes(lang) ? lang : langs[0] ?? '';
|
||||
return (
|
||||
<Card title={t('ed.captionsTitle')} icon="text" sub={t('ed.captionsSub', { file: data.files.shotsCopy ?? '' })} id="captions-editor">
|
||||
<Section title={t('ed.captionsTitle')} icon="text" sub={t('ed.captionsSub', { file: data.files.shotsCopy ?? '' })} id="captions-editor">
|
||||
<div className="row" style={{ gap: 8, flexWrap: 'wrap', marginBottom: 12 }} role="tablist" aria-label={t('col.language')}>
|
||||
{langs.map(x => <button key={x} role="tab" aria-selected={x === L} className={`btn btn-sm${x === L ? ' btn-primary' : ''}`} onClick={() => setLang(x)}><Icon name="globe" />{x}</button>)}
|
||||
</div>
|
||||
|
|
@ -103,6 +105,6 @@ function Captions({ project, data, scenes }: { project: Project; data: EditableR
|
|||
);
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue