41 lines
3.6 KiB
TypeScript
41 lines
3.6 KiB
TypeScript
import { useState } from "react";
|
||
|
||
import { useDialogFocus } from "./dialog-focus.js";
|
||
import { exportResultCopy, type ExportFlowStatus } from "./export-flow.js";
|
||
import { EXPORT_DIMENSIONS, type ExportFormat, type ExportRatio } from "./export-compositor.js";
|
||
|
||
export function ExportDialog(props: {
|
||
busy: boolean;
|
||
byteSize?: number;
|
||
onClose: () => void;
|
||
onExport: (input: { format: ExportFormat; quality?: number }) => void;
|
||
onRetry: () => void;
|
||
pendingEdit: boolean;
|
||
ratio: ExportRatio;
|
||
result?: ExportFlowStatus;
|
||
}) {
|
||
const [format, setFormat] = useState<ExportFormat>("jpg");
|
||
const [quality, setQuality] = useState(92);
|
||
const [pendingConfirmed, setPendingConfirmed] = useState(false);
|
||
const dimensions = EXPORT_DIMENSIONS[props.ratio];
|
||
const failure = props.result && props.result !== "downloaded_and_saved";
|
||
const dialogRef = useDialogFocus(props.onClose);
|
||
return <div className="editor-dialog-backdrop"><section aria-labelledby="editor-export-title" aria-modal="true" className="editor-export-dialog" ref={dialogRef} role="dialog" tabIndex={-1}>
|
||
<header><div><p>EXPORT</p><h2 id="editor-export-title">导出成品</h2></div></header>
|
||
{props.result ? <div className={`editor-export-result ${failure ? "failed" : "succeeded"}`}>
|
||
<p aria-live={failure ? "assertive" : "polite"} role={failure ? "alert" : "status"}>{exportResultCopy[props.result]}</p>
|
||
{props.byteSize ? <small>{(props.byteSize / 1024 / 1024).toFixed(2)} MB</small> : null}
|
||
<div>{props.result === "download_failed" ? <button disabled={props.busy} onClick={props.onRetry} type="button">重新下载同一次合成</button> : null}<button disabled={props.busy} onClick={props.onClose} type="button">关闭</button></div>
|
||
</div> : <>
|
||
<section className="editor-export-section"><h3>文件格式</h3><div aria-label="导出格式" className="editor-export-segments" role="group">
|
||
<button aria-pressed={format === "jpg"} data-dialog-initial-focus onClick={() => setFormat("jpg")} type="button">JPG</button>
|
||
<button aria-pressed={format === "png"} onClick={() => setFormat("png")} type="button">PNG</button>
|
||
</div></section>
|
||
{format === "jpg" ? <section className="editor-export-section"><div className="editor-export-label"><h3>JPG 质量</h3><output>{quality}</output></div><div className="editor-export-quality"><input aria-label="JPG 质量" max="100" min="80" onChange={(event) => setQuality(Number(event.target.value))} step="1" type="range" value={quality} /><input aria-label="JPG 质量数值" max="100" min="80" onChange={(event) => setQuality(Math.max(80, Math.min(100, Number(event.target.value))))} step="1" type="number" value={quality} /></div></section> : null}
|
||
<section className="editor-export-summary"><span>输出尺寸</span><strong>{dimensions.width} × {dimensions.height} px</strong><span>色彩空间</span><strong>sRGB</strong><span>预计文件大小</span><strong>合成后显示</strong></section>
|
||
{props.pendingEdit ? <label className="editor-export-pending"><strong>导出前需要提交当前修改</strong><span><input checked={pendingConfirmed} onChange={(event) => setPendingConfirmed(event.target.checked)} type="checkbox" />将应用当前修改并导出</span></label> : null}
|
||
<footer><button className="editor-primary" disabled={props.busy || (props.pendingEdit && !pendingConfirmed)} onClick={() => props.onExport({ format, ...(format === "jpg" ? { quality } : {}) })} type="button">{props.busy ? "正在导出" : "导出并下载"}</button><button disabled={props.busy} onClick={props.onClose} type="button">取消</button></footer>
|
||
</>}
|
||
</section></div>;
|
||
}
|