Files
wordcloud/frontend/src/components/CanvasArea.tsx
T
broccoli f8a907e7c5 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.
2026-07-13 22:01:01 +08:00

84 lines
2.6 KiB
TypeScript

import { useRef, useEffect, useState } from 'react';
import { NameLocation, JobResult } from '../types';
import { apiUrl } from '../lib/api';
import { IconCloudy } from './Icons';
interface CanvasAreaProps {
maskFile: File | null;
jobResult: JobResult | null;
viewMode: '2d' | '3d';
zoom: number;
highlightLocation: NameLocation | null;
}
export default function CanvasArea({
maskFile, jobResult, viewMode, zoom, highlightLocation
}: CanvasAreaProps) {
const [maskPreviewUrl, setMaskPreviewUrl] = useState<string | null>(null);
const wrapperRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!maskFile) { setMaskPreviewUrl(null); return; }
const url = URL.createObjectURL(maskFile);
setMaskPreviewUrl(url);
return () => URL.revokeObjectURL(url);
}, [maskFile]);
// 图片 URL 可能来自后端相对路径,也可能是本地预览地址。
const resolveImageUrl = (): string | null => {
if (!jobResult) return maskPreviewUrl;
const raw = jobResult.image_url;
if (!raw || !jobResult.job_id) return maskPreviewUrl;
return apiUrl(raw);
};
const displayUrl = resolveImageUrl();
const isEmpty = !displayUrl;
return (
<div className="canvas-inner" ref={wrapperRef}>
{isEmpty ? (
<div className="canvas-placeholder">
<div className="canvas-placeholder-icon"><IconCloudy /></div>
<div className="canvas-placeholder-text">导入底图或名单后可预览词云</div>
</div>
) : viewMode === '3d' ? (
<div className="view-3d-container">
<img src={displayUrl} alt="wordcloud 3D" className="view-3d-image" />
</div>
) : (
<div className="canvas-image-wrapper">
<img
src={displayUrl}
alt="wordcloud"
className="canvas-image"
style={{ transform: `scale(${zoom})` }}
/>
{highlightLocation && jobResult && (
<HighlightBox location={highlightLocation} zoom={zoom} />
)}
</div>
)}
</div>
);
}
function HighlightBox({ location, zoom }: { location: NameLocation; zoom: number }) {
const x = location.box_x ?? location.x;
const y = location.box_y ?? location.y;
const width = location.box_width ?? location.width ?? location.font_size ?? 24;
const height = location.box_height ?? location.height ?? location.font_size ?? 24;
return (
<div
className="highlight-box"
style={{
left: x * zoom,
top: y * zoom,
width: width * zoom,
height: height * zoom,
}}
/>
);
}