Skip to content

Commit

Permalink
Hide constraints section for non-absolute children (#4437)
Browse files Browse the repository at this point in the history
  • Loading branch information
ruggi authored Oct 27, 2023
1 parent b1fa262 commit 0bc349e
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1806,6 +1806,7 @@ describe('inspector tests with real metadata', () => {
<div
className='customClassName'
data-uid={'bbb'}
style={{ position: 'absolute' }}
></div>
</div>
)
Expand Down Expand Up @@ -2129,9 +2130,6 @@ describe('inspector tests with real metadata', () => {
`"simple"`,
)

expectSelectControlValue(renderResult, 'frame-child-constraint-width-popuplist', 'Mixed') // TODO this should be Left
expectSelectControlValue(renderResult, 'frame-child-constraint-height-popuplist', 'Mixed') // TODO this should be Top

matchInlineSnapshotBrowser(metadata.computedStyle?.['opacity'], `"1"`)
matchInlineSnapshotBrowser(opacityControl.value, `"1"`)
matchInlineSnapshotBrowser(
Expand All @@ -2152,7 +2150,7 @@ describe('inspector tests with real metadata', () => {
}}
data-uid={'aaa'}
>
<div data-uid={'bbb'}>hello</div>
<div data-uid={'bbb'} style={{ position: 'absolute' }}>hello</div>
</div>
`),
'await-first-dom-report',
Expand Down Expand Up @@ -2193,7 +2191,7 @@ describe('inspector tests with real metadata', () => {
}}
data-uid={'aaa'}
>
<div data-uid={'bbb'} style={{flex: '1 0 15px'}}>hello</div>
<div data-uid={'bbb'} style={{ flex: '1 0 15px' }}>hello</div>
</div>
`),
'await-first-dom-report',
Expand Down Expand Up @@ -2237,9 +2235,6 @@ describe('inspector tests with real metadata', () => {
// flexShrink.attributes.getNamedItemNS(null, 'data-controlstatus')?.value,
// `"simple"`,
// )

expectSelectControlValue(renderResult, 'frame-child-constraint-width-popuplist', 'Mixed') // TODO this should be Left
expectSelectControlValue(renderResult, 'frame-child-constraint-height-popuplist', 'Mixed') // TODO this should be Top
})

it('Flex longhand properties', async () => {
Expand Down Expand Up @@ -2302,9 +2297,6 @@ describe('inspector tests with real metadata', () => {
// flexShrink.attributes.getNamedItemNS(null, 'data-controlstatus')?.value,
// `"simple"`,
// )

expectSelectControlValue(renderResult, 'frame-child-constraint-width-popuplist', 'Mixed') // TODO this should be Left
expectSelectControlValue(renderResult, 'frame-child-constraint-height-popuplist', 'Mixed') // TODO this should be Top
})
it('Flex longhand in style props using a simple expression', async () => {
const renderResult = await renderTestEditorWithCode(
Expand Down Expand Up @@ -2353,9 +2345,6 @@ describe('inspector tests with real metadata', () => {
heightControl.attributes.getNamedItemNS(null, 'data-controlstatus')?.value,
`"simple"`,
)

expectSelectControlValue(renderResult, 'frame-child-constraint-width-popuplist', 'Mixed') // TODO this should be Left
expectSelectControlValue(renderResult, 'frame-child-constraint-height-popuplist', 'Mixed') // TODO this should be Top
})
it('Shows multifile selected element properties', async () => {
let projectContents: ProjectContents = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ describe('Constraints Section', () => {
it(
'Span without size shows up as Scale / Scale constrained',
testGroupChild({
snippet: `<span data-uid='target'>An implicitly constrained span</span>`,
snippet: `<span data-uid='target' style={{ position: 'absolute' }}>An implicitly constrained span</span>`,
expectedWidthConstraintDropdownOption: 'Scale',
expectedHeightConstraintDropdownOption: 'Scale',
}),
Expand All @@ -176,7 +176,7 @@ describe('Constraints Section', () => {
it(
'Span with only width shows up as Scale / Scale',
testGroupChild({
snippet: `<span data-uid='target' style={{ width: 150 }}>An implicitly constrained span</span>`,
snippet: `<span data-uid='target' style={{ width: 150, position: 'absolute' }}>An implicitly constrained span</span>`,
expectedWidthConstraintDropdownOption: 'Scale',
expectedHeightConstraintDropdownOption: 'Scale',
}),
Expand All @@ -185,7 +185,7 @@ describe('Constraints Section', () => {
it(
'Span with max-content width shows up as Width / Scale',
testGroupChild({
snippet: `<span data-uid='target' style={{ width: 'max-content' }}>An implicitly constrained span</span>`,
snippet: `<span data-uid='target' style={{ width: 'max-content', position: 'absolute' }}>An implicitly constrained span</span>`,
expectedWidthConstraintDropdownOption: 'Width',
expectedHeightConstraintDropdownOption: 'Scale',
}),
Expand All @@ -194,7 +194,7 @@ describe('Constraints Section', () => {
it(
'Element with max-content width does not change if selecting the same option again',
testGroupChild({
snippet: `<span data-uid='target' data-testid='target' style={{ width: 'max-content' }}>An implicitly constrained span</span>`,
snippet: `<span data-uid='target' data-testid='target' style={{ width: 'max-content', position: 'absolute' }}>An implicitly constrained span</span>`,
expectedWidthConstraintDropdownOption: 'Width',
expectedHeightConstraintDropdownOption: 'Scale',
after: async (renderResult) => {
Expand Down Expand Up @@ -222,7 +222,7 @@ describe('Constraints Section', () => {
it(
'Span with max-content width height shows up as Width / Height',
testGroupChild({
snippet: `<span data-uid='target' style={{ width: 'max-content', height: 'max-content' }}>An implicitly constrained span</span>`,
snippet: `<span data-uid='target' style={{ width: 'max-content', height: 'max-content', position: 'absolute' }}>An implicitly constrained span</span>`,
expectedWidthConstraintDropdownOption: 'Width',
expectedHeightConstraintDropdownOption: 'Height',
}),
Expand All @@ -231,7 +231,7 @@ describe('Constraints Section', () => {
it(
'Span with size shows up as Scale',
testGroupChild({
snippet: `<span data-uid='target' style={{ width: 150, height: 30 }}>An implicitly constrained span</span>`,
snippet: `<span data-uid='target' style={{ width: 150, height: 30, position: 'absolute' }}>An implicitly constrained span</span>`,
expectedWidthConstraintDropdownOption: 'Scale',
expectedHeightConstraintDropdownOption: 'Scale',
}),
Expand All @@ -240,7 +240,7 @@ describe('Constraints Section', () => {
it(
'Div with right and bottom pins and size shows up as Right / Bottom',
testGroupChild({
snippet: `<span data-uid='target' style={{ right: 150, bottom: 50, width: 150, height: 30 }} data-constraints={['right', 'bottom']}>An implicitly constrained span</span>`,
snippet: `<span data-uid='target' style={{ right: 150, bottom: 50, width: 150, height: 30, position: 'absolute' }} data-constraints={['right', 'bottom']}>An implicitly constrained span</span>`,
expectedWidthConstraintDropdownOption: 'Right',
expectedHeightConstraintDropdownOption: 'Bottom',
}),
Expand Down Expand Up @@ -273,7 +273,6 @@ describe('Constraints Section', () => {
const section = screen.queryByTestId(InspectorSectionConstraintsTestId)
expect(section).toBeNull()
})

it('is hidden when the selection contains groups', async () => {
const renderResult = await renderTestEditorWithCode(
formatTestProjectCode(`
Expand Down Expand Up @@ -329,6 +328,73 @@ describe('Constraints Section', () => {
true,
)

const section = screen.queryByTestId(InspectorSectionConstraintsTestId)
expect(section).toBeNull()
})
it('is hidden when the selection contains non-absolute elements', async () => {
const renderResult = await renderTestEditorWithCode(
formatTestProjectCode(`
import * as React from 'react'
import { Group, Storyboard } from 'utopia-api'
var storyboard = () => {
return (
<Storyboard data-uid='sb'>
<Group data-uid='group' style={{ position: 'absolute', left: 0, top: 0, width: 164, height: 129 }}>
<div style={{ backgroundColor: '#aaaaaa33', position: 'absolute', left: 0, top: 0, width: 70, height: 70 }} />
<div style={{ backgroundColor: '#aaaaaa33', position: 'absolute', left: 84, top: 49, width: 80, height: 80 }} />
</Group>
<div data-uid='flex' style={{ position: 'absolute', left: 200, top: 200, width: 'max-content', height: 'max-content', display: 'flex', gap: 10 }}>
<div data-uid='foo1' style={{ backgroundColor: '#f0f', width: 70, height: 70 }} />
<div data-uid='foo2' style={{ backgroundColor: '#f0f', width: 70, height: 70 }} />
<div data-uid='foo3' style={{ backgroundColor: '#f0f', width: 70, height: 70 }} />
</div>
</Storyboard>
)
}
`),
'await-first-dom-report',
)

await renderResult.dispatch(
[selectComponents([EP.fromString('sb/group'), EP.fromString('sb/flex/foo2')], true)],
true,
)

const section = screen.queryByTestId(InspectorSectionConstraintsTestId)
expect(section).toBeNull()
})
it('is hidden when the selection contains non-absolute elements (bad group child)', async () => {
// most likely this will never happen in reality, because group children are always absolute, but
// this is to validate the intersection with the other conditions.
const renderResult = await renderTestEditorWithCode(
formatTestProjectCode(`
import * as React from 'react'
import { Group, Storyboard } from 'utopia-api'
var storyboard = () => {
return (
<Storyboard data-uid='sb'>
<Group data-uid='group1' style={{ position: 'absolute', left: 0, top: 0, width: 164, height: 129 }}>
<div style={{ backgroundColor: '#aaaaaa33', position: 'absolute', left: 0, top: 0, width: 70, height: 70 }} />
<div style={{ backgroundColor: '#aaaaaa33', position: 'absolute', left: 84, top: 49, width: 80, height: 80 }} />
</Group>
<Group data-uid='group2' style={{ position: 'absolute', left: 0, top: 0, width: 164, height: 129 }}>
<div style={{ backgroundColor: '#aaaaaa33', position: 'absolute', left: 0, top: 0, width: 70, height: 70 }} />
<div data-uid='bad' style={{ backgroundColor: '#f00', position: 'relative', left: 84, top: 49, width: 80, height: 80 }} />
</Group>
</Storyboard>
)
}
`),
'await-first-dom-report',
)

await renderResult.dispatch(
[selectComponents([EP.fromString('sb/group'), EP.fromString('sb/grop2/bad')], true)],
true,
)

const section = screen.queryByTestId(InspectorSectionConstraintsTestId)
expect(section).toBeNull()
})
Expand Down
17 changes: 15 additions & 2 deletions editor/src/components/inspector/constraints-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { InspectorPropsContext } from './common/property-path-hooks'
import { PinControl, PinHeightControl, PinWidthControl } from './controls/pin-control'
import {
allElementsAreGroupChildren,
allElementsArePositionedAbsolutelySelector,
anySelectedElementGroupOrChildOfGroup,
} from './fill-hug-fixed-control'
import { selectedViewsSelector } from './inpector-selectors'
Expand Down Expand Up @@ -51,10 +52,22 @@ export const ConstraintsSection = React.memo(() => {
allElementsAreGroupChildren,
'ConstraintsSection onlyGroupChildrenSelected',
)
const allElementsArePositionedAbsolutely = useEditorState(
Substores.metadata,
allElementsArePositionedAbsolutelySelector,
'ConstraintsSection allElementsArePositionedAbsolutely',
)

const showSection = React.useMemo(() => {
return noGroupOrGroupChildrenSelected || onlyGroupChildrenSelected
}, [noGroupOrGroupChildrenSelected, onlyGroupChildrenSelected])
return (
allElementsArePositionedAbsolutely &&
(noGroupOrGroupChildrenSelected || onlyGroupChildrenSelected)
)
}, [
noGroupOrGroupChildrenSelected,
onlyGroupChildrenSelected,
allElementsArePositionedAbsolutely,
])

if (!showSection) {
return null
Expand Down
102 changes: 48 additions & 54 deletions editor/src/components/inspector/fill-hug-fixed-control.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,49 @@
/** @jsxRuntime classic */
/** @jsx jsx */
import { jsx } from '@emotion/react'
import createCachedSelector from 're-reselect'
import React from 'react'
import { createSelector } from 'reselect'
import { FlexCol } from 'utopia-api'
import type { LayoutPinnedPropIncludingCenter } from '../../core/layout/layout-helpers-new'
import { type LayoutPinnedProp } from '../../core/layout/layout-helpers-new'
import { MetadataUtils } from '../../core/model/element-metadata-utils'
import { mapDropNulls, safeIndex, strictEvery } from '../../core/shared/array-utils'
import * as EP from '../../core/shared/element-path'
import type { ElementInstanceMetadataMap } from '../../core/shared/element-template'
import { emptyComments, jsExpressionValue } from '../../core/shared/element-template'
import { optionalMap } from '../../core/shared/optional-utils'
import type { ElementPath } from '../../core/shared/project-file-types'
import * as PP from '../../core/shared/property-path'
import { intersection } from '../../core/shared/set-utils'
import { assertNever } from '../../core/shared/utils'
import {
FlexRow,
InspectorSubsectionHeader,
NumberInput,
PopupList,
useColorTheme,
UtopiaTheme,
} from '../../uuiui'
import type {
ControlStatus,
ControlStyles,
OnSubmitValueOrUnknownOrEmpty,
SelectOption,
} from '../../uuiui-deps'
import { NumberInput, PopupList, useColorTheme, UtopiaTheme } from '../../uuiui'
import type { SelectOption } from '../../uuiui-deps'
import { getControlStyles, InspectorRowHoverCSS } from '../../uuiui-deps'
import { MixedPlaceholder } from '../../uuiui/inputs/base-input'
import {
convertGroupToFrameCommands,
getInstanceForGroupToFrameConversion,
isConversionForbidden,
} from '../canvas/canvas-strategies/strategies/group-conversion-helpers'
import { treatElementAsGroupLike } from '../canvas/canvas-strategies/strategies/group-helpers'
import { notice } from '../common/notice'
import type { EditorAction, EditorDispatch } from '../editor/action-types'
import {
applyCommandsAction,
setProp_UNSAFE,
showToast,
unsetProperty,
} from '../editor/actions/action-creators'
import { useDispatch } from '../editor/store/dispatch-context'
import type { AllElementProps } from '../editor/store/editor-state'
import { Substores, useEditorState, useRefEditorState } from '../editor/store/store-hook'
import type { MetadataSubstate } from '../editor/store/store-hook-substore-types'
import { fixedSizeDimensionHandlingText } from '../text-editor/text-handling'
import type { CSSNumber, CSSNumberType, UnknownOrEmptyInput } from './common/css-utils'
import { cssNumber } from './common/css-utils'
import type { FramePinsInfo } from './common/layout-property-path-hooks'
import { PinControl } from './controls/pin-control'
import {
metadataSelector,
pathTreesSelector,
Expand All @@ -45,40 +64,8 @@ import {
} from './inspector-strategies/inspector-strategies'
import type { InspectorStrategy } from './inspector-strategies/inspector-strategy'
import { executeFirstApplicableStrategy } from './inspector-strategies/inspector-strategy'
import type { MetadataSubstate } from '../editor/store/store-hook-substore-types'
import type { ElementPath } from '../../core/shared/project-file-types'
import { treatElementAsGroupLike } from '../canvas/canvas-strategies/strategies/group-helpers'
import * as EP from '../../core/shared/element-path'
import * as PP from '../../core/shared/property-path'
import type { GridRowVariant } from './widgets/ui-grid-row'
import { UIGridRow } from './widgets/ui-grid-row'
import { mapDropNulls, safeIndex, uniqBy } from '../../core/shared/array-utils'
import { fixedSizeDimensionHandlingText } from '../text-editor/text-handling'
import { when } from '../../utils/react-conditionals'
import type { LayoutPinnedPropIncludingCenter } from '../../core/layout/layout-helpers-new'
import { isLayoutPinnedProp, type LayoutPinnedProp } from '../../core/layout/layout-helpers-new'
import type { AllElementProps, EditorState } from '../editor/store/editor-state'
import type { ElementInstanceMetadataMap } from '../../core/shared/element-template'
import { jsExpressionValue, emptyComments } from '../../core/shared/element-template'
import type { EditorDispatch, EditorAction } from '../editor/action-types'
import {
unsetProperty,
setProp_UNSAFE,
applyCommandsAction,
showToast,
} from '../editor/actions/action-creators'
import { FlexCol } from 'utopia-api'
import { PinControl } from './controls/pin-control'
import type { FramePinsInfo } from './common/layout-property-path-hooks'
import { MixedPlaceholder } from '../../uuiui/inputs/base-input'
import { PinHeightSVG, PinWidthSVG } from './utility-controls/pin-control'
import {
convertGroupToFrameCommands,
getInstanceForGroupToFrameConversion,
isConversionForbidden,
} from '../canvas/canvas-strategies/strategies/group-conversion-helpers'
import { notice } from '../common/notice'
import createCachedSelector from 're-reselect'

export const FillFixedHugControlId = (segment: 'width' | 'height'): string =>
`hug-fixed-fill-${segment}`
Expand Down Expand Up @@ -730,16 +717,23 @@ export const anySelectedElementGroupOrChildOfGroup = createSelector(

export const allElementsAreGroupChildren = createSelector(
metadataSelector,
(store: MetadataSubstate) => store.editor.elementPathTree,
selectedViewsSelector,
(metadata, pathTrees, selectedViews): boolean => {
if (selectedViews.length === 0) {
return false
}
function elementOrAnyChildGroup(path: ElementPath) {
(metadata, selectedViews): boolean => {
return strictEvery(selectedViews, (path: ElementPath) => {
return treatElementAsGroupLike(metadata, EP.parentPath(path))
}
return selectedViews.every(elementOrAnyChildGroup)
})
},
)

export const allElementsArePositionedAbsolutelySelector = createSelector(
metadataSelector,
selectedViewsSelector,
(metadata, selectedViews): boolean => {
return strictEvery(selectedViews, (path) => {
return MetadataUtils.isPositionAbsolute(
MetadataUtils.findElementByElementPath(metadata, path),
)
})
},
)

Expand Down
Loading

0 comments on commit 0bc349e

Please sign in to comment.