136 lines
4.1 KiB
TypeScript
136 lines
4.1 KiB
TypeScript
import type { ReactNode } from "react";
|
||
import {
|
||
AlignBottom,
|
||
AlignHorizontally,
|
||
AlignLeft,
|
||
AlignRight,
|
||
AlignTop,
|
||
AlignVertically,
|
||
} from "iconsax-react";
|
||
import { useEditorStore, type EditorObject } from "../../../store/editorStore";
|
||
import type { AlignKind } from "./alignmentUtils";
|
||
import {
|
||
deltasForAlign,
|
||
pageBounds,
|
||
unionBoundsOfAlignTargets,
|
||
computeGroupFrameUpdate,
|
||
updatesFromDeltas,
|
||
} from "./alignmentUtils";
|
||
|
||
type AlignmentSettingsProps = {
|
||
pageWidth: number;
|
||
pageHeight: number;
|
||
onUpdate: (id: string, updates: Partial<EditorObject>) => void;
|
||
};
|
||
|
||
type AlignButtonGroupProps = {
|
||
children: ReactNode;
|
||
};
|
||
|
||
type AlignButtonProps = {
|
||
title: string;
|
||
onClick: () => void;
|
||
children: ReactNode;
|
||
};
|
||
|
||
const AlignButtonGroup = ({ children }: AlignButtonGroupProps) => (
|
||
<div className="flex flex-1 overflow-hidden rounded-lg border border-[#E5E5E5] bg-[#F2F2F2] divide-x divide-[#E5E5E5]">
|
||
{children}
|
||
</div>
|
||
);
|
||
|
||
const AlignButton = ({ title, onClick, children }: AlignButtonProps) => (
|
||
<button
|
||
type="button"
|
||
title={title}
|
||
onClick={onClick}
|
||
className="flex flex-1 items-center justify-center py-2.5 text-[#333333] transition-colors hover:bg-black/4 active:bg-black/8"
|
||
>
|
||
{children}
|
||
</button>
|
||
);
|
||
|
||
const alignIconProps = { size: 20, color: "currentColor" as const, variant: "Linear" as const };
|
||
|
||
const AlignmentSettings = ({
|
||
pageWidth,
|
||
pageHeight,
|
||
onUpdate,
|
||
}: AlignmentSettingsProps) => {
|
||
const applyAlign = (kind: AlignKind) => {
|
||
useEditorStore.getState().commitObjectHistoryBeforeChange();
|
||
const { objects: objs, selectedObjectIds: ids, layerRef } = useEditorStore.getState();
|
||
const layer = layerRef?.current ?? null;
|
||
|
||
const initial = new Map<string, EditorObject>();
|
||
for (const id of ids) {
|
||
const o = objs.find((x) => x.id === id);
|
||
if (o && o.visible !== false) initial.set(id, { ...o });
|
||
}
|
||
if (initial.size === 0) return;
|
||
|
||
const initialList = [...initial.values()];
|
||
const useSelectionBounds = ids.length === 2;
|
||
const target =
|
||
useSelectionBounds && initialList.length === 2
|
||
? unionBoundsOfAlignTargets(initialList, objs, layer)
|
||
: pageBounds(pageWidth, pageHeight);
|
||
|
||
if (!target) return;
|
||
|
||
const alignedGroupIds = new Set<string>();
|
||
|
||
for (const id of initial.keys()) {
|
||
const obj = initial.get(id)!;
|
||
const { dx, dy } = deltasForAlign(obj, kind, target, objs, layer);
|
||
const updates = updatesFromDeltas(obj, dx, dy);
|
||
if (Object.keys(updates).length === 0) continue;
|
||
onUpdate(id, updates);
|
||
if (obj.type === "group") {
|
||
alignedGroupIds.add(id);
|
||
}
|
||
}
|
||
|
||
const { updateObject } = useEditorStore.getState();
|
||
const afterObjects = useEditorStore.getState().objects;
|
||
for (const gid of alignedGroupIds) {
|
||
const patch = computeGroupFrameUpdate(gid, afterObjects, layer);
|
||
if (patch) updateObject(gid, patch);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-3">
|
||
<h4 className="text-[13px] text-[#7A7A7A]">ترازبندی</h4>
|
||
|
||
<div className="flex gap-2">
|
||
<AlignButtonGroup>
|
||
<AlignButton title="تراز راست" onClick={() => applyAlign("right")}>
|
||
<AlignRight {...alignIconProps} />
|
||
</AlignButton>
|
||
<AlignButton title="تراز افقی وسط" onClick={() => applyAlign("centerH")}>
|
||
<AlignHorizontally {...alignIconProps} />
|
||
</AlignButton>
|
||
<AlignButton title="تراز چپ" onClick={() => applyAlign("left")}>
|
||
<AlignLeft {...alignIconProps} />
|
||
</AlignButton>
|
||
</AlignButtonGroup>
|
||
|
||
<AlignButtonGroup>
|
||
<AlignButton title="تراز بالا" onClick={() => applyAlign("top")}>
|
||
<AlignTop {...alignIconProps} />
|
||
</AlignButton>
|
||
<AlignButton title="تراز عمودی وسط" onClick={() => applyAlign("centerV")}>
|
||
<AlignVertically {...alignIconProps} />
|
||
</AlignButton>
|
||
<AlignButton title="تراز پایین" onClick={() => applyAlign("bottom")}>
|
||
<AlignBottom {...alignIconProps} />
|
||
</AlignButton>
|
||
</AlignButtonGroup>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default AlignmentSettings;
|