store-only apps edit their listing on the store (storebound listing live / set: old -> new per field with the checks, a PATCH of only the edited fields; serve listing.set is the one store-only write, its token bound to the store ids; policy.rs STORE_ONLY_WRITES, write_with_confirm for store targets); screenshot captions per language and scene edited as a text edit of the shots copy JSON (JSON files keep their layout); Play content rating, target audience and App content answers in App info; docs/console-parity.md after the editors (73 rows: 55 complete, 18 with the reason) and tests/console-parity.test.ts keeping it honest
This commit is contained in:
parent
3cea2af4e1
commit
7ac028421a
33 changed files with 852 additions and 157 deletions
|
|
@ -10,7 +10,7 @@
|
|||
import { useCallback, useEffect, useState, useSyncExternalStore, type ReactNode } from 'react';
|
||||
import { api, type EditableResult, type EditResult, type Project, type ToolResult } from '../api';
|
||||
import { t } from '../i18n';
|
||||
import { type EditArea, type EditOp, edits, keyOf } from '../lib/edits';
|
||||
import { type EditArea, type EditOp, edits, type FieldOp, keyOf } from '../lib/edits';
|
||||
import { failureText } from '../lib/results';
|
||||
import { useRead, useWrite } from '../store';
|
||||
import { Icon, type IconName } from './icons';
|
||||
|
|
@ -61,7 +61,7 @@ interface FieldBase { project: Project; area: EditArea; label: string; hint?: Re
|
|||
/** A text field: one line or several, a live character counter against the store limit. */
|
||||
export function TextField({ project, area, op, saved, label, limit, multiline, rows = 3, placeholder, hint, issues = [], mono, id, disabled, kind }: FieldBase & {
|
||||
/** The edit without its value: { kind: 'listing', store, lang, field } or { kind: 'config', path } or contact. */
|
||||
op: Omit<Extract<EditOp, { kind: 'listing' }>, 'value'> | { kind: 'config'; path: string } | { kind: 'contact'; field: Extract<EditOp, { kind: 'contact' }>['field'] };
|
||||
op: FieldOp;
|
||||
saved: string; limit?: number; multiline?: boolean; rows?: number; placeholder?: string; mono?: boolean; kind?: 'url' | 'email' | 'text';
|
||||
}) {
|
||||
const e = useEdits();
|
||||
|
|
@ -263,9 +263,10 @@ export function EditBar({ project, area, data, files, onSaved }: { project: Proj
|
|||
}
|
||||
|
||||
/** A store write with its dry run in the window first: the plan (old -> new per store and language), then Apply. */
|
||||
export function ApplyPanel({ project, method, args = {}, label, icon = 'upload', done, disabled, hint, blocked }: {
|
||||
export function ApplyPanel({ project, method, args = {}, label, icon = 'upload', done, disabled, hint, blocked, onApplied }: {
|
||||
project: Project; method: string; args?: Record<string, unknown>; label: string; icon?: IconName; done: string; disabled?: boolean; hint?: string;
|
||||
/** Unsaved edits that the store write would not include. */ blocked?: string | null;
|
||||
/** After the real run went through. */ onApplied?: () => void;
|
||||
}) {
|
||||
const w = useWrite(project.id);
|
||||
const [st, setSt] = useState<{ loading: boolean; text: string; ok: boolean } | null>(null);
|
||||
|
|
@ -289,7 +290,7 @@ export function ApplyPanel({ project, method, args = {}, label, icon = 'upload',
|
|||
<pre className={`selectable ed-dry${st.ok ? '' : ' bad'}`}>{st.text}</pre>
|
||||
<div className="row" style={{ gap: 8, justifyContent: 'flex-end' }}>
|
||||
<button className="btn btn-sm btn-ghost" onClick={() => setSt(null)}>{t('ed.close')}</button>
|
||||
{st.ok && <WriteButton run={w} primary icon={icon} disabled={!!blocked} hint={blocked ?? undefined} w={{ method, args, title: label, done, after: () => setSt(null) }}>{t('ed.applyNow')}</WriteButton>}
|
||||
{st.ok && <WriteButton run={w} primary icon={icon} disabled={!!blocked} hint={blocked ?? undefined} w={{ method, args, title: label, done, after: () => { setSt(null); onApplied?.(); } }}>{t('ed.applyNow')}</WriteButton>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue