Add floating canvas panels, theme settings, and SVG line-spacing analysis.
Canvas Studio now uses dockable floating panels, app settings/help navigation, and improved SVG export; the backend adds an SVG line-spacing analysis API with SciPy acceleration and new design templates.
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
import { useState } from 'react';
|
||||
import { WordcloudMaskSource, WordcloudStickerPayload } from '../types';
|
||||
import { apiUrl, ensureOk } from '../lib/api';
|
||||
|
||||
interface ExportPanelProps {
|
||||
jobId: string | null;
|
||||
apiBase: string;
|
||||
svgUrl?: string;
|
||||
imageUrl?: string;
|
||||
onOpenCanvas?: () => void;
|
||||
maskSource?: WordcloudMaskSource | null;
|
||||
onImportWordcloudSticker?: (payload: WordcloudStickerPayload) => void;
|
||||
embedded?: boolean;
|
||||
}
|
||||
|
||||
type Format = 'jpg' | 'png';
|
||||
@@ -16,12 +17,12 @@ type FillMode = 'fill' | 'dot' | 'line' | 'ring';
|
||||
|
||||
export default function ExportPanel({
|
||||
jobId,
|
||||
apiBase,
|
||||
svgUrl,
|
||||
imageUrl,
|
||||
onOpenCanvas,
|
||||
maskSource,
|
||||
onImportWordcloudSticker,
|
||||
embedded = false,
|
||||
}: ExportPanelProps) {
|
||||
const [bmpFormat, setBmpFormat] = useState<Format>('png');
|
||||
const [exportW, setExportW] = useState('1920');
|
||||
@@ -40,8 +41,8 @@ export default function ExportPanel({
|
||||
const [ringSpacing, setRingSpacing] = useState(8);
|
||||
|
||||
const resolveUrl = (field: string | undefined, kind: string) => {
|
||||
if (field) return field.startsWith('http') ? field : `${apiBase}${field}`;
|
||||
if (jobId) return `${apiBase}/api/jobs/${jobId}/files/${kind}`;
|
||||
if (field) return apiUrl(field);
|
||||
if (jobId) return apiUrl(`/api/jobs/${jobId}/files/${kind}`);
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -64,7 +65,7 @@ export default function ExportPanel({
|
||||
params.set('ring_width', String(ringWidth));
|
||||
params.set('ring_spacing', String(ringSpacing));
|
||||
}
|
||||
return `${apiBase}/api/jobs/${jobId}/custom.svg?${params}`;
|
||||
return apiUrl(`/api/jobs/${jobId}/custom.svg?${params}`);
|
||||
};
|
||||
|
||||
const handleExportSvg = () => {
|
||||
@@ -79,8 +80,7 @@ export default function ExportPanel({
|
||||
|
||||
setIsSavingSticker(true);
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) throw new Error(`读取 SVG 失败 (${res.status})`);
|
||||
const res = await ensureOk(await fetch(url), '读取 SVG 失败');
|
||||
const svg = await res.text();
|
||||
if (onImportWordcloudSticker) {
|
||||
onImportWordcloudSticker({ svg, mask: maskSource || undefined });
|
||||
@@ -114,9 +114,11 @@ export default function ExportPanel({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="panel-header">
|
||||
<div className="panel-title">导出</div>
|
||||
</div>
|
||||
{!embedded && (
|
||||
<div className="panel-header">
|
||||
<div className="panel-title">导出</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="panel-body">
|
||||
|
||||
{/* ── SVG 导出 ── */}
|
||||
|
||||
Reference in New Issue
Block a user