This commit is contained in:
hamid zarghami
2026-01-04 11:27:53 +03:30
parent aa6153d289
commit 0c6bf2b52b
12 changed files with 758 additions and 291 deletions
@@ -6,28 +6,35 @@ type MaskSettingsProps = {
};
/**
* MaskSettings - تنظیمات mask mode برای shapes
* MaskSettings - تنظیمات masking برای objects
*
* این component اجازه می‌ده کاربر یک shape رو به mask تبدیل کنه
* فقط برای shapes قابل استفاده است (rectangle, circle, triangle, abstract)
* دو حالت دارد:
* 1. برای shapes: تبدیل shape به mask (isMask)
* 2. برای objects: انتخاب یک mask برای apply کردن (maskId)
*/
const MaskSettings = ({ objectId }: MaskSettingsProps) => {
const { objects, updateObject, selectedObjectId, setSelectedObjectId } = useEditorStore();
const object = objects.find((obj) => obj.id === objectId);
if (!object) return null;
const canBeMask = object.type === "rectangle";
if (!canBeMask) return null;
const isMask = object.isMask || false;
const currentShowGuide = object.showMaskGuide !== false;
// Get all available masks (excluding self if it's a shape)
const availableMasks = objects.filter(
(obj) => obj.isMask && obj.id !== objectId && obj.visible !== false
);
const handleToggleMask = (checked: boolean) => {
updateObject(objectId, { isMask: checked, showMaskGuide: checked ? true : undefined });
updateObject(objectId, {
isMask: checked,
showMaskGuide: checked ? true : undefined,
maskId: undefined // Clear maskId when becoming a mask
});
if (checked && selectedObjectId === objectId) {
setSelectedObjectId(null);
}
@@ -37,37 +44,119 @@ const MaskSettings = ({ objectId }: MaskSettingsProps) => {
updateObject(objectId, { showMaskGuide: checked });
};
const handleSelectMask = (maskId: string) => {
updateObject(objectId, { maskId: maskId || undefined });
};
const handleToggleMaskInvert = (checked: boolean) => {
updateObject(objectId, { maskInvert: checked });
};
return (
<div className="p-4 border-b">
<div className="flex items-center justify-between mb-3">
<div className="flex flex-col gap-1">
<span className="text-sm font-medium">استفاده به عنوان ماسک</span>
<span className="text-xs text-gray-500">
ناحیهای که با ماسک همپوشانی دارد مخفی میشود
</span>
</div>
<Switch
checked={isMask}
onCheckedChange={handleToggleMask}
/>
</div>
{isMask && (
<>
<div className="p-4 border-b space-y-4">
{/* Section 1: Convert shape to mask (only for shapes) */}
{canBeMask && (
<div>
<div className="flex items-center justify-between mb-3">
<span className="text-sm">نمایش راهنمای ماسک</span>
<div className="flex flex-col gap-1">
<span className="text-sm font-medium">استفاده به عنوان ماسک</span>
<span className="text-xs text-gray-500">
این شکل را به ماسک تبدیل کن
</span>
</div>
<Switch
checked={currentShowGuide}
onCheckedChange={handleToggleGuide}
checked={isMask}
onCheckedChange={handleToggleMask}
/>
</div>
<div className="p-2 bg-blue-50 rounded-md">
<p className="text-xs text-blue-700">
💡 قسمتهای همپوشانی با این ماسک مخفی میشوند.
{isMask && (
<>
<div className="flex items-center justify-between mb-3">
<span className="text-sm">نمایش راهنمای ماسک</span>
<Switch
checked={currentShowGuide}
onCheckedChange={handleToggleGuide}
/>
</div>
<div className="p-2 bg-blue-50 rounded-md">
<p className="text-xs text-blue-700">
💡 این شکل به ماسک تبدیل شد. از تنظیمات objects دیگر میتوانید این ماسک را apply کنید.
</p>
</div>
</>
)}
</div>
)}
{/* Section 2: Apply mask to object (for all non-mask objects) */}
{!isMask && availableMasks.length > 0 && (
<div>
<div className="mb-3">
<span className="text-sm font-medium">اعمال ماسک</span>
<p className="text-xs text-gray-500 mt-1">
یک ماسک برای این object انتخاب کنید
</p>
</div>
</>
<div className="space-y-2 max-h-48 overflow-y-auto">
{availableMasks.map((mask) => {
const isActive = object.maskId === mask.id;
const maskLabel = `${mask.shapeType === "circle" ? "دایره" : mask.shapeType === "triangle" ? "مثلث" : mask.shapeType === "abstract" ? "ستاره" : "مربع"} `;
return (
<div
key={mask.id}
className={`flex items-center justify-between p-2 rounded-lg border transition-colors ${isActive ? "border-blue-500 bg-blue-50" : "border-gray-200 hover:border-gray-300"
}`}
>
<span className="text-sm flex-1">{maskLabel}</span>
<Switch
checked={isActive}
onCheckedChange={(checked) => handleSelectMask(checked ? mask.id : "")}
/>
</div>
);
})}
</div>
{object.maskId && (
<>
<div className="flex items-center justify-between mt-4 pt-3 border-t">
<span className="text-xs">نمایش فقط همپوشانی</span>
<Switch
checked={object.maskInvert === false}
onCheckedChange={(checked) => handleToggleMaskInvert(!checked)}
/>
</div>
{/* نمایش وضعیت گروه‌بندی */}
{(() => {
const maskObj = objects.find(m => m.id === object.maskId);
const isGrouped = object.groupId && maskObj?.groupId === object.groupId;
return (
<div className={`mt-3 p-2 rounded-md ${isGrouped ? "bg-purple-50" : "bg-green-50"}`}>
<p className={`text-xs ${isGrouped ? "text-purple-700" : "text-green-700"}`}>
{isGrouped ? (
<>
🔗 گروهبندی شده: Object و ماسک با هم حرکت میکنند. برای لغو، object را انتخاب کنید و دکمه "لغو گروه‌بندی" را بزنید.
</>
) : (
<>
ماسک اعمال شد. {object.maskInvert === false ? "فقط قسمت‌های همپوشانی نمایش داده می‌شوند." : "قسمت‌های همپوشانی مخفی می‌شوند."}
<br />
💡 برای گروهبندی با ماسک، object را انتخاب کنید و دکمه "گروه‌بندی با ماسک" را بزنید.
</>
)}
</p>
</div>
);
})()}
</>
)}
</div>
)}
</div>
);