diff --git a/src/components/atoms/BlockField.tsx b/src/components/atoms/BlockField.tsx index 0069281a..0f263344 100644 --- a/src/components/atoms/BlockField.tsx +++ b/src/components/atoms/BlockField.tsx @@ -1,13 +1,16 @@ interface Props { label: string; + fullBody?: boolean; children?: React.ReactNode; } -export const BlockField: React.FC = ({ label, children }) => { +export const BlockField: React.FC = ({ label, fullBody, children }) => { + const bodyClass = fullBody ? "ml-2 w-full" : "ml-auto"; + return (
{label}: -
{children}
+
{children}
); }; diff --git a/src/components/shapeInspectorPanel/AttachmentInspector.tsx b/src/components/shapeInspectorPanel/AttachmentInspector.tsx new file mode 100644 index 00000000..ad3093e0 --- /dev/null +++ b/src/components/shapeInspectorPanel/AttachmentInspector.tsx @@ -0,0 +1,82 @@ +import { useCallback } from "react"; +import { BlockGroupField } from "../atoms/BlockGroupField"; +import { InlineField } from "../atoms/InlineField"; +import { ToggleInput } from "../atoms/inputs/ToggleInput"; +import { Shape } from "../../models"; +import { SliderInput } from "../atoms/inputs/SliderInput"; +import { BlockField } from "../atoms/BlockField"; + +interface Props { + targetShape: Shape; + updateTargetShape: (patch: Partial) => void; +} + +export const AttachmentInspector: React.FC = ({ targetShape, updateTargetShape }) => { + const attachment = targetShape.attachment; + + const handleAnchorRateChange = useCallback( + (val: number) => { + if (!attachment) return; + updateTargetShape({ attachment: { ...attachment, to: { x: val, y: attachment.to.y } } }); + }, + [attachment, updateTargetShape], + ); + + const handleRelativeRotationChange = useCallback( + (val: boolean) => { + if (!attachment) return; + updateTargetShape({ attachment: { ...attachment, rotationType: val ? "relative" : "absolute" } }); + }, + [attachment, updateTargetShape], + ); + + const handleAnchorXChange = useCallback( + (val: number) => { + if (!attachment) return; + updateTargetShape({ attachment: { ...attachment, anchor: { x: val, y: attachment.anchor.y } } }); + }, + [attachment, updateTargetShape], + ); + const handleAnchorYChange = useCallback( + (val: number) => { + if (!attachment) return; + updateTargetShape({ attachment: { ...attachment, anchor: { x: attachment.anchor.x, y: val } } }); + }, + [attachment, updateTargetShape], + ); + + if (!attachment) return; + + return ( + + + + + + + + + + + + + + + + + ); +}; diff --git a/src/components/shapeInspectorPanel/ConventionalShapeInspector.tsx b/src/components/shapeInspectorPanel/ConventionalShapeInspector.tsx index 78cd5e5e..1ab9c77e 100644 --- a/src/components/shapeInspectorPanel/ConventionalShapeInspector.tsx +++ b/src/components/shapeInspectorPanel/ConventionalShapeInspector.tsx @@ -9,6 +9,7 @@ import { InlineField } from "../atoms/InlineField"; import { useShapeComposite, useShapeCompositeWithoutTmpInfo } from "../../hooks/storeHooks"; import { resizeShapeTrees } from "../../composables/shapeResizing"; import { BlockGroupField } from "../atoms/BlockGroupField"; +import { getAttachmentByUpdatingRotation } from "../../shapes"; interface Props { targetShape: Shape; @@ -80,9 +81,20 @@ export const ConventionalShapeInspector: React.FC = ({ const handleChangeRotation = useCallback( (val: number, draft = false) => { const affine = getRotateToAffine(subShapeComposite, targetShape, (val * Math.PI) / 180); - handleResize(affine, draft); + if (draft) { + readyState(); + + const patch = resizeShapeTrees(shapeComposite, [targetShape.id], affine); + const attachment = getAttachmentByUpdatingRotation(targetShape, patch[targetShape.id].rotation); + if (attachment) { + patch[targetShape.id].attachment = attachment; + } + updateTmpShapes(patch); + } else { + commit(); + } }, - [targetShape, subShapeComposite, handleResize], + [targetShape, subShapeComposite, commit, readyState, updateTmpShapes, shapeComposite], ); return ( diff --git a/src/components/shapeInspectorPanel/ShapeInspectorPanel.tsx b/src/components/shapeInspectorPanel/ShapeInspectorPanel.tsx index c1663134..54185653 100644 --- a/src/components/shapeInspectorPanel/ShapeInspectorPanel.tsx +++ b/src/components/shapeInspectorPanel/ShapeInspectorPanel.tsx @@ -14,6 +14,7 @@ import { GroupShape, isGroupShape } from "../../shapes/group"; import { ClipInspector } from "./ClipInspector"; import { AlphaField } from "./AlphaField"; import { HighlightShapeMeta } from "../../composables/states/appCanvas/core"; +import { AttachmentInspector } from "./AttachmentInspector"; export const ShapeInspectorPanel: React.FC = () => { const targetShape = useSelectedShape(); @@ -147,6 +148,30 @@ const ShapeInspectorPanelWithShape: React.FC [targetShapes, getShapeComposite, patchShapes], ); + // Only shapes already having "attachment" will be updated. + const updateAttachmentBySamePatch = useCallback( + (patch: Partial, draft = false) => { + const shapeComposite = getShapeComposite(); + + const layoutPatch = getPatchByLayouts(shapeComposite, { + update: targetShapes.reduce<{ [id: string]: Partial }>((p, s) => { + if (s.attachment) { + p[s.id] = patch; + } + return p; + }, {}), + }); + + if (draft) { + setTmpShapeMap(layoutPatch); + } else { + setTmpShapeMap({}); + patchShapes(layoutPatch); + } + }, + [targetShapes, getShapeComposite, patchShapes, setTmpShapeMap], + ); + const alphaField = ; return ( @@ -194,6 +219,7 @@ const ShapeInspectorPanelWithShape: React.FC updateTargetGroupShape={updateGroupShapesBySamePatch} /> ) : undefined} +