统一工作区清空遮罩入口
- 移除 Canvas 右下角旧清空遮罩和应用分类按钮,清空入口统一到左侧工具栏 - 清空遮罩优先作用于当前帧选中 mask,无选中时作用于当前帧全部 mask - 目标 mask 无传播链结果时直接清当前帧,有传播链结果时弹窗选择只清当前帧、清空传播所有帧或取消 - 保留布尔工具右下角合并/去除操作区,避免旧分类按钮误改整帧 - 更新 Canvas、工具栏、工作区测试,覆盖直接清空、传播链范围选择和取消路径 - 同步更新前端审计、需求冻结、设计冻结、测试计划和 AGENTS 说明
This commit is contained in:
@@ -9,7 +9,6 @@ import type { Frame, Mask } from '../store/useStore';
|
||||
interface CanvasAreaProps {
|
||||
activeTool: string;
|
||||
frame: Frame | null;
|
||||
onClearMasks?: () => void;
|
||||
onDeleteMaskAnnotations?: (annotationIds: string[]) => Promise<void> | void;
|
||||
}
|
||||
|
||||
@@ -417,7 +416,7 @@ function geometriesOverlap(first: MultiPolygon, second: MultiPolygon): boolean {
|
||||
return polygonClipping.intersection(first, second).length > 0;
|
||||
}
|
||||
|
||||
export function CanvasArea({ activeTool, frame, onClearMasks, onDeleteMaskAnnotations }: CanvasAreaProps) {
|
||||
export function CanvasArea({ activeTool, frame, onDeleteMaskAnnotations }: CanvasAreaProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [stageSize, setStageSize] = useState({ width: 800, height: 600 });
|
||||
const [scale, setScale] = useState(1);
|
||||
@@ -448,7 +447,6 @@ export function CanvasArea({ activeTool, frame, onClearMasks, onDeleteMaskAnnota
|
||||
const masks = useStore((state) => state.masks);
|
||||
const addMask = useStore((state) => state.addMask);
|
||||
const updateMask = useStore((state) => state.updateMask);
|
||||
const clearMasks = useStore((state) => state.clearMasks);
|
||||
const setMasks = useStore((state) => state.setMasks);
|
||||
const setGlobalSelectedMaskIds = useStore((state) => state.setSelectedMaskIds);
|
||||
const maskPreviewOpacity = useStore((state) => state.maskPreviewOpacity);
|
||||
@@ -971,37 +969,6 @@ export function CanvasArea({ activeTool, frame, onClearMasks, onDeleteMaskAnnota
|
||||
}
|
||||
}, [activeClass, activeTemplateId, addMask, aiModel, frame?.height, frame?.id, frame?.width, image?.height, image?.naturalHeight, image?.naturalWidth, image?.width, masks, samCandidateMaskId, setMasks, updateMask]);
|
||||
|
||||
const handleApplyActiveClass = () => {
|
||||
if (!frame?.id || !activeClass) return;
|
||||
const seedIds = selectedMaskIds.length > 0
|
||||
? selectedMaskIds
|
||||
: frameMasks.map((mask) => mask.id);
|
||||
const targetIds = findPropagationChainMaskIds(seedIds, masks);
|
||||
setMasks(masks.map((mask) => {
|
||||
if (!targetIds.has(mask.id)) return mask;
|
||||
return {
|
||||
...mask,
|
||||
templateId: activeTemplateId || mask.templateId,
|
||||
classId: activeClass.id,
|
||||
className: activeClass.name,
|
||||
classZIndex: activeClass.zIndex,
|
||||
classMaskId: activeClass.maskId,
|
||||
label: activeClass.name,
|
||||
color: activeClass.color,
|
||||
saveStatus: mask.annotationId ? 'dirty' : 'draft',
|
||||
saved: Boolean(mask.annotationId) ? false : mask.saved,
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
||||
const handleClearMasks = () => {
|
||||
if (onClearMasks) {
|
||||
onClearMasks();
|
||||
return;
|
||||
}
|
||||
clearMasks();
|
||||
};
|
||||
|
||||
const deleteMasksById = useCallback((maskIds: string[]) => {
|
||||
if (maskIds.length === 0) return;
|
||||
const idSet = expandedPropagationDeletionMaskIds(maskIds, masks);
|
||||
@@ -1772,36 +1739,20 @@ export function CanvasArea({ activeTool, frame, onClearMasks, onDeleteMaskAnnota
|
||||
<span>待更新: {dirtyMaskCount}</span>
|
||||
</div>
|
||||
|
||||
{frameMasks.length > 0 && (
|
||||
{frameMasks.length > 0 && isBooleanTool && (
|
||||
<div className="absolute bottom-4 right-4 flex gap-2">
|
||||
{isBooleanTool && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs bg-white/5 text-gray-300 border border-white/10 px-2.5 py-1.5 rounded">
|
||||
已选 {booleanSelectedMasks.length}
|
||||
</span>
|
||||
<button
|
||||
onClick={handleBooleanOperation}
|
||||
disabled={booleanSelectedMasks.length < 2}
|
||||
className="text-xs bg-emerald-500/10 hover:bg-emerald-500/20 text-emerald-300 border border-emerald-500/20 px-3 py-1.5 rounded transition-colors disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-emerald-500/10"
|
||||
>
|
||||
{effectiveTool === 'area_merge' ? '合并选中' : '从主区域去除'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{activeClass && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs bg-white/5 text-gray-300 border border-white/10 px-2.5 py-1.5 rounded">
|
||||
已选 {booleanSelectedMasks.length}
|
||||
</span>
|
||||
<button
|
||||
onClick={handleApplyActiveClass}
|
||||
className="text-xs bg-cyan-500/10 hover:bg-cyan-500/20 text-cyan-300 border border-cyan-500/20 px-3 py-1.5 rounded transition-colors"
|
||||
onClick={handleBooleanOperation}
|
||||
disabled={booleanSelectedMasks.length < 2}
|
||||
className="text-xs bg-emerald-500/10 hover:bg-emerald-500/20 text-emerald-300 border border-emerald-500/20 px-3 py-1.5 rounded transition-colors disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-emerald-500/10"
|
||||
>
|
||||
应用分类
|
||||
{effectiveTool === 'area_merge' ? '合并选中' : '从主区域去除'}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={handleClearMasks}
|
||||
className="text-xs bg-red-500/10 hover:bg-red-500/20 text-red-400 border border-red-500/20 px-3 py-1.5 rounded transition-colors"
|
||||
>
|
||||
清空遮罩
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user