Skip to content

Commit

Permalink
Fix/inspector group pin toggles (#4564)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
enidemi authored Nov 27, 2023
1 parent d528797 commit fc58238
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 7 deletions.
160 changes: 157 additions & 3 deletions editor/src/components/inspector/constraints-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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'

Expand Down Expand Up @@ -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 (
<FlexColumn css={{ paddingBottom: UtopiaTheme.layout.rowHorizontalPadding }}>
<UIGridRow padded variant='<-auto-><----------1fr--------->'>
<ChildPinControl isGroupChild='group-child' pins={pins} framePinsInfo={framePinsInfo} />
<ChildPinControl
key={selectedViews.map(EP.toString).join('-')}
isGroupChild='group-child'
pins={pins}
framePinsInfo={framePinsInfo}
/>
<FlexColumn css={{ gap: 8 }}>
<ChildConstraintSelect isGroupChild='group-child' dimension={'width'} />
<ChildConstraintSelect isGroupChild='group-child' dimension={'height'} />
Expand Down Expand Up @@ -145,6 +160,44 @@ const FrameChildConstraintsSection = React.memo(() => {
})
FrameChildConstraintsSection.displayName = 'FrameChildConstraintsSection'

function detectedPinsToDefaultActivePins(detectedPins: DetectedPins): {
horizontal: Array<LayoutPinnedPropIncludingCenter>
vertical: Array<LayoutPinnedPropIncludingCenter>
} {
const horizontalActivePins = (() => {
let defaultActivePins: Array<LayoutPinnedProp> = []
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<LayoutPinnedProp> = []
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,
Expand All @@ -157,6 +210,13 @@ export const ChildPinControl = React.memo(
}) => {
const dispatch = useDispatch()

const [activeHorizontalPins, setActiveHorizontalPins] = React.useState<
Array<LayoutPinnedPropIncludingCenter>
>(detectedPinsToDefaultActivePins(pins).horizontal)
const [activeVerticalPins, setActiveVerticalPins] = React.useState<
Array<LayoutPinnedPropIncludingCenter>
>(detectedPinsToDefaultActivePins(pins).vertical)

const propertyTarget = useContextSelector(InspectorPropsContext, (contextData) => {
return contextData.targetPath
})
Expand All @@ -166,13 +226,96 @@ 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,
event: React.MouseEvent<Element, MouseEvent>,
) => {
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') {
Expand Down Expand Up @@ -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,
Expand All @@ -259,6 +410,8 @@ export const ChildPinControl = React.memo(
selectedViewsRef,
pins.horizontal,
pins.vertical,
allElementPropsRef,
requestedGroupPinChanges,
],
)

Expand All @@ -267,6 +420,7 @@ export const ChildPinControl = React.memo(
handlePinMouseDown={onPinControlMouseDown}
pins={pins}
framePinsInfo={framePinsInfo}
isGroupChild={isGroupChild}
/>
)
},
Expand Down
4 changes: 4 additions & 0 deletions editor/src/components/inspector/controls/pin-control.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ export interface CombinedPinControlProps {
frameProp: LayoutPinnedPropIncludingCenter,
event: React.MouseEvent<Element, MouseEvent>,
) => void
isGroupChild: 'group-child' | 'frame-child'
}

export const CombinedPinControl = React.memo((props: CombinedPinControlProps) => {
Expand All @@ -340,6 +341,7 @@ export const CombinedPinControl = React.memo((props: CombinedPinControlProps) =>
controlStatus={'simple'}
framePoints={props.framePinsInfo}
regularBorder={false}
exclude={{ center: props.isGroupChild === 'group-child' }}
/>
<FlexCol
css={{
Expand All @@ -352,13 +354,15 @@ export const CombinedPinControl = React.memo((props: CombinedPinControlProps) =>
controlStatus={'simple'}
framePins={props.framePinsInfo}
handlePinMouseDown={props.handlePinMouseDown}
isGroupChild={props.isGroupChild}
/>
</FlexRow>
<FlexRow css={{ flexGrow: 1, alignItems: 'center' }}>
<PinHeightControl
controlStatus={'simple'}
framePins={props.framePinsInfo}
handlePinMouseDown={props.handlePinMouseDown}
isGroupChild={props.isGroupChild}
/>
</FlexRow>
</FlexCol>
Expand Down
47 changes: 43 additions & 4 deletions editor/src/components/inspector/utility-controls/pin-control.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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) {
Expand Down Expand Up @@ -245,6 +260,7 @@ interface PinWidthControlProps {
frameProp: LayoutPinnedPropIncludingCenter,
event: React.MouseEvent<Element, MouseEvent>,
) => void
isGroupChild: 'group-child' | 'frame-child'
}

export const PinWidthSVG = React.memo(() => {
Expand Down Expand Up @@ -325,7 +341,13 @@ export const PinWidthControl = React.memo((props: PinWidthControlProps) => {
<svg width='20' height='20'>
<g
id='dimensioncontrols-pin-width'
stroke={getStrokeColor(controlStyles, props.framePins, props.mixed, FramePoint.Width)}
stroke={getStrokeColor(
controlStyles,
props.framePins,
props.mixed,
FramePoint.Width,
props.isGroupChild,
)}
>
<path
d={`M${DimensionStart},${VerticalDimensionButtStart} l0,${DimensionButt}`}
Expand All @@ -340,7 +362,12 @@ export const PinWidthControl = React.memo((props: PinWidthControlProps) => {
<path
d={`M${DimensionStart},${DimensionVerticalMid} L${HorizontalDimensionEnd},${DimensionVerticalMid}`}
id='dimensioncontrols-pin-width-line'
strokeDasharray={getStrokeDashArray(props.framePins, props.mixed, FramePoint.Width)}
strokeDasharray={getStrokeDashArray(
props.framePins,
props.mixed,
FramePoint.Width,
props.isGroupChild,
)}
strokeLinecap='round'
/>
<path
Expand All @@ -364,6 +391,7 @@ interface PinHeightControlProps {
frameProp: LayoutPinnedPropIncludingCenter,
event: React.MouseEvent<Element, MouseEvent>,
) => void
isGroupChild: 'group-child' | 'frame-child'
}

export const PinHeightControl = React.memo((props: PinHeightControlProps) => {
Expand All @@ -382,7 +410,13 @@ export const PinHeightControl = React.memo((props: PinHeightControlProps) => {
<svg width='20' height='20'>
<g
id='dimensioncontrols-pin-height'
stroke={getStrokeColor(controlStyles, props.framePins, props.mixed, FramePoint.Height)}
stroke={getStrokeColor(
controlStyles,
props.framePins,
props.mixed,
FramePoint.Height,
props.isGroupChild,
)}
>
<path
d={`M${HorizontalDimensionButtStart},${DimensionStart} l${DimensionButt},0`}
Expand All @@ -397,7 +431,12 @@ export const PinHeightControl = React.memo((props: PinHeightControlProps) => {
<path
d={`M${DimensionHorizontalMid},${DimensionStart} L${DimensionHorizontalMid},${VerticalDimensionEnd}`}
id='dimensioncontrols-pin-height-line'
strokeDasharray={getStrokeDashArray(props.framePins, props.mixed, FramePoint.Height)}
strokeDasharray={getStrokeDashArray(
props.framePins,
props.mixed,
FramePoint.Height,
props.isGroupChild,
)}
strokeLinecap='round'
/>
<path
Expand Down

0 comments on commit fc58238

Please sign in to comment.