From fc58238be6bab02630e3f03c128a30923789cd5c Mon Sep 17 00:00:00 2001 From: Eniko Bakos-Demeter Date: Mon, 27 Nov 2023 18:17:36 +0100 Subject: [PATCH] Fix/inspector group pin toggles (#4564) * fix(inspector) pin toggles for group children * fix(inspector) group pin section * fix(groups) set default active pins on selection change * fix(inspector) center pins are hidden for groups * fix(inspector) group children width/height style change * fix(inspector) oops, forgot the height pin control --- .../inspector/constraints-section.tsx | 160 +++++++++++++++++- .../inspector/controls/pin-control.tsx | 4 + .../utility-controls/pin-control.tsx | 47 ++++- 3 files changed, 204 insertions(+), 7 deletions(-) diff --git a/editor/src/components/inspector/constraints-section.tsx b/editor/src/components/inspector/constraints-section.tsx index f1252f1a9049..225bb9fdd77e 100644 --- a/editor/src/components/inspector/constraints-section.tsx +++ b/editor/src/components/inspector/constraints-section.tsx @@ -4,7 +4,11 @@ import { jsx } from '@emotion/react' import React from 'react' import { useContextSelector } from 'use-context-selector' -import type { LayoutPinnedPropIncludingCenter } from '../../core/layout/layout-helpers-new' +import { + isHorizontalLayoutPinnedProp, + type LayoutPinnedProp, + type LayoutPinnedPropIncludingCenter, +} from '../../core/layout/layout-helpers-new' import { NO_OP } from '../../core/shared/utils' import { when } from '../../utils/react-conditionals' import { @@ -46,6 +50,7 @@ import { } from './simplified-pinning-helpers' import { UIGridRow } from './widgets/ui-grid-row' import { allSelectedElementsContractSelector } from './editor-contract-section' +import * as EP from '../../core/shared/element-path' export const InspectorSectionConstraintsTestId = 'inspector-section-constraints' @@ -112,11 +117,21 @@ ConstraintsSection.displayName = 'ConstraintsSection' const GroupChildConstraintsSection = React.memo(() => { const pins = useDetectedConstraints('group-child') const framePinsInfo: FramePinsInfo = React.useMemo(() => getFixedPointsForPinning(pins), [pins]) + const selectedViews = useEditorState( + Substores.selectedViews, + (store) => store.editor.selectedViews, + 'FrameChildConstraintsSection selectedViews', + ) return ( - + @@ -145,6 +160,44 @@ const FrameChildConstraintsSection = React.memo(() => { }) FrameChildConstraintsSection.displayName = 'FrameChildConstraintsSection' +function detectedPinsToDefaultActivePins(detectedPins: DetectedPins): { + horizontal: Array + vertical: Array +} { + const horizontalActivePins = (() => { + let defaultActivePins: Array = [] + if (detectedPins.horizontal.includes('right')) { + defaultActivePins.push('right') + } + if (detectedPins.horizontal.includes('width')) { + defaultActivePins.push('width') + } + if (detectedPins.horizontal.includes('left')) { + defaultActivePins.push('left') + } + return defaultActivePins + })() + + const verticalActivePins = (() => { + let defaultActivePins: Array = [] + if (detectedPins.vertical.includes('bottom')) { + defaultActivePins.push('bottom') + } + if (detectedPins.vertical.includes('height')) { + defaultActivePins.push('height') + } + if (detectedPins.vertical.includes('top')) { + defaultActivePins.push('top') + } + return defaultActivePins + })() + + return { + horizontal: horizontalActivePins, + vertical: verticalActivePins, + } +} + export const ChildPinControl = React.memo( ({ isGroupChild, @@ -157,6 +210,13 @@ export const ChildPinControl = React.memo( }) => { const dispatch = useDispatch() + const [activeHorizontalPins, setActiveHorizontalPins] = React.useState< + Array + >(detectedPinsToDefaultActivePins(pins).horizontal) + const [activeVerticalPins, setActiveVerticalPins] = React.useState< + Array + >(detectedPinsToDefaultActivePins(pins).vertical) + const propertyTarget = useContextSelector(InspectorPropsContext, (contextData) => { return contextData.targetPath }) @@ -166,6 +226,85 @@ export const ChildPinControl = React.memo( const allElementPropsRef = useRefEditorState((store) => store.editor.allElementProps) const elementPathTreesRef = useRefEditorState((store) => store.editor.elementPathTree) + const requestedGroupPinChanges = React.useCallback( + (frameProp: LayoutPinnedPropIncludingCenter): RequestedPins | 'no-op' => { + if (frameProp === 'centerY' || frameProp === 'centerX') { + // for groups the center pins are hidden + return 'no-op' + } + if (isHorizontalLayoutPinnedProp(frameProp)) { + const newActivePins = (() => { + const pinIndex = activeHorizontalPins.indexOf(frameProp) + if (pinIndex === -1) { + const updatedActivePins = [...activeHorizontalPins, frameProp] + + // If more than two of 'left', 'width', 'right' are active, discard the oldest one + if (updatedActivePins.length === 3) { + updatedActivePins.shift() + } + return updatedActivePins + } else { + // Pin is active, deactivate it + const updatedHorizontalPins = [...activeHorizontalPins] + updatedHorizontalPins.splice(pinIndex, 1) + return updatedHorizontalPins + } + })() + + setActiveHorizontalPins(newActivePins) + + const hasLeft = newActivePins.includes('left') + const hasWidth = newActivePins.includes('width') + const hasRight = newActivePins.includes('right') + const isEmpty = newActivePins.length === 0 + + if (isEmpty) return 'scale-horizontal' + if (hasLeft && hasRight) return 'left-and-right' + if (hasLeft && hasWidth) return 'left-and-width' + if (hasRight && hasWidth) return 'right-and-width' + if (hasLeft) return 'left' + if (hasRight) return 'right' + if (hasWidth) return 'width' + return 'no-op' + } else { + const newActivePins = (() => { + const pinIndex = activeVerticalPins.indexOf(frameProp) + if (pinIndex === -1) { + const updatedActivePins = [...activeVerticalPins, frameProp] + + // If more than two of 'top', 'height', 'bottom' are active, discard the oldest one + if (updatedActivePins.length === 3) { + updatedActivePins.shift() + } + return updatedActivePins + } else { + // Pin is active, deactivate it + const updatedVerticalPins = [...activeVerticalPins] + updatedVerticalPins.splice(pinIndex, 1) + return updatedVerticalPins + } + })() + + setActiveVerticalPins(newActivePins) + + const hasTop = newActivePins.includes('top') + const hasHeight = newActivePins.includes('height') + const hasBottom = newActivePins.includes('bottom') + const isEmpty = newActivePins.length === 0 + + if (isEmpty) return 'scale-vertical' + if (hasTop && hasBottom) return 'top-and-bottom' + if (hasTop && hasHeight) return 'top-and-height' + if (hasBottom && hasHeight) return 'bottom-and-height' + if (hasTop) return 'top' + if (hasBottom) return 'bottom' + if (hasHeight) return 'height' + return 'no-op' + } + }, + [activeHorizontalPins, setActiveHorizontalPins, activeVerticalPins, setActiveVerticalPins], + ) + const onPinControlMouseDown = React.useCallback( ( frameProp: LayoutPinnedPropIncludingCenter, @@ -173,6 +312,10 @@ export const ChildPinControl = React.memo( ) => { const cmdPressed = event.metaKey const requestedPinChange: RequestedPins | 'no-op' = (() => { + if (isGroupChild === 'group-child') { + return requestedGroupPinChanges(frameProp) + } + switch (frameProp) { case 'left': { if (cmdPressed && pins.horizontal === 'right-and-width') { @@ -238,9 +381,17 @@ export const ChildPinControl = React.memo( // no-op, early return :) return } + dispatch( isGroupChild === 'group-child' - ? [] // nothing for Group children yet!! + ? getConstraintAndFrameChangeActionsForGroupChild( + metadataRef.current, + allElementPropsRef.current, + elementPathTreesRef.current, + propertyTarget, + selectedViewsRef.current, + requestedPinChange, + ) : getFrameChangeActionsForFrameChild( metadataRef.current, elementPathTreesRef.current, @@ -259,6 +410,8 @@ export const ChildPinControl = React.memo( selectedViewsRef, pins.horizontal, pins.vertical, + allElementPropsRef, + requestedGroupPinChanges, ], ) @@ -267,6 +420,7 @@ export const ChildPinControl = React.memo( handlePinMouseDown={onPinControlMouseDown} pins={pins} framePinsInfo={framePinsInfo} + isGroupChild={isGroupChild} /> ) }, diff --git a/editor/src/components/inspector/controls/pin-control.tsx b/editor/src/components/inspector/controls/pin-control.tsx index 3ba182c8c720..548d274d5f20 100644 --- a/editor/src/components/inspector/controls/pin-control.tsx +++ b/editor/src/components/inspector/controls/pin-control.tsx @@ -329,6 +329,7 @@ export interface CombinedPinControlProps { frameProp: LayoutPinnedPropIncludingCenter, event: React.MouseEvent, ) => void + isGroupChild: 'group-child' | 'frame-child' } export const CombinedPinControl = React.memo((props: CombinedPinControlProps) => { @@ -340,6 +341,7 @@ export const CombinedPinControl = React.memo((props: CombinedPinControlProps) => controlStatus={'simple'} framePoints={props.framePinsInfo} regularBorder={false} + exclude={{ center: props.isGroupChild === 'group-child' }} /> controlStatus={'simple'} framePins={props.framePinsInfo} handlePinMouseDown={props.handlePinMouseDown} + isGroupChild={props.isGroupChild} /> @@ -359,6 +362,7 @@ export const CombinedPinControl = React.memo((props: CombinedPinControlProps) => controlStatus={'simple'} framePins={props.framePinsInfo} handlePinMouseDown={props.handlePinMouseDown} + isGroupChild={props.isGroupChild} /> diff --git a/editor/src/components/inspector/utility-controls/pin-control.tsx b/editor/src/components/inspector/utility-controls/pin-control.tsx index cb857e52eea4..b9c58e0c88ac 100644 --- a/editor/src/components/inspector/utility-controls/pin-control.tsx +++ b/editor/src/components/inspector/utility-controls/pin-control.tsx @@ -46,9 +46,18 @@ function getStrokeColor( framePoints: FramePinsInfo, mixed: boolean | undefined, point: FramePoint, + isGroupChild?: 'group-child' | 'frame-child', ) { const isPrimary = Utils.propOr(false, 'isPrimaryPosition', framePoints[point]) + if (isGroupChild === 'group-child') { + // only set width/height is using main color + const isRelative = Utils.propOr(false, 'isRelativePosition', framePoints[point]) + + return (isPrimary && isRelative) || !isPrimary + ? controlStyles.secondaryColor + : controlStyles.mainColor + } if (isPrimary && !mixed) { return controlStyles.mainColor } else { @@ -60,7 +69,13 @@ function getStrokeDashArray( framePoints: FramePinsInfo, mixed: boolean | undefined, point: FramePoint, + isGroupChild?: 'group-child' | 'frame-child', ) { + // unset width/height for group children are not using dashed style + if (isGroupChild === 'group-child') { + return '0' + } + const isRelative = Utils.propOr(false, 'isRelativePosition', framePoints[point]) if (isRelative && !mixed) { @@ -245,6 +260,7 @@ interface PinWidthControlProps { frameProp: LayoutPinnedPropIncludingCenter, event: React.MouseEvent, ) => void + isGroupChild: 'group-child' | 'frame-child' } export const PinWidthSVG = React.memo(() => { @@ -325,7 +341,13 @@ export const PinWidthControl = React.memo((props: PinWidthControlProps) => { { , ) => void + isGroupChild: 'group-child' | 'frame-child' } export const PinHeightControl = React.memo((props: PinHeightControlProps) => { @@ -382,7 +410,13 @@ export const PinHeightControl = React.memo((props: PinHeightControlProps) => { {