Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix some issues with the zero sized controls. #4294

Merged
merged 2 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export function absoluteResizeBoundingBoxStrategy(
const originalTargets = flattenSelection(
getTargetPathsFromInteractionTarget(canvasState.interactionTarget),
)

const retargetedTargets = flattenSelection(
retargetStrategyToChildrenOfFragmentLikeElements(canvasState),
)
Expand Down
34 changes: 27 additions & 7 deletions editor/src/components/canvas/controls/bounding-box-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,29 @@ interface NotNullRefObject<T> {

export function useBoundingBox<T = HTMLDivElement>(
selectedElements: ReadonlyArray<ElementPath>,
onChangeCallback: (ref: NotNullRefObject<T>, boundingBox: CanvasRectangle, scale: number) => void,
onChangeCallback: (
ref: NotNullRefObject<T>,
safeGappedBoundingBox: CanvasRectangle,
realBoundingBox: CanvasRectangle,
scale: number,
) => void,
): React.RefObject<T> {
const controlRef = React.useRef<T>(null)
const boundingBoxCallback = React.useCallback(
(boundingBox: CanvasRectangle | null, scale: number) => {
const maybeZeroBoundingBox = zeroRectIfNullOrInfinity(boundingBox)
(
safeGappedBoundingBox: CanvasRectangle | null,
realBoundingBox: CanvasRectangle | null,
scale: number,
) => {
const maybeZeroSafeGappedBoundingBox = zeroRectIfNullOrInfinity(safeGappedBoundingBox)
const maybeZeroRealBoundingBox = zeroRectIfNullOrInfinity(realBoundingBox)
if (controlRef.current != null) {
onChangeCallback(controlRef as NotNullRefObject<T>, maybeZeroBoundingBox, scale)
onChangeCallback(
controlRef as NotNullRefObject<T>,
maybeZeroSafeGappedBoundingBox,
maybeZeroRealBoundingBox,
scale,
)
}
},
[onChangeCallback],
Expand All @@ -48,7 +63,11 @@ export const RESIZE_CONTROL_SAFE_GAP = 4 // safe gap applied when the dimension

function useBoundingBoxFromMetadataRef(
selectedElements: ReadonlyArray<ElementPath>,
boundingBoxCallback: (boundingRectangle: CanvasRectangle | null, scale: number) => void,
boundingBoxCallback: (
safeGappedBoundingRectangle: CanvasRectangle | null,
realBoundingRectangle: CanvasRectangle | null,
scale: number,
) => void,
): void {
const metadataRef = useRefEditorState((store) => getMetadata(store.editor))
const scaleRef = useRefEditorState((store) => store.editor.canvas.scale)
Expand Down Expand Up @@ -120,9 +139,10 @@ function useBoundingBoxFromMetadataRef(
}
return canvasRectangle(adjustedBoundingBox)
}
const boundingBox = getAdjustedBoundingBox(boundingRectangleArray(frames))
const realBoundingBox = boundingRectangleArray(frames)
const safeGappedBoundingBox = getAdjustedBoundingBox(realBoundingBox)

boundingBoxCallbackRef.current(boundingBox, scaleRef.current)
boundingBoxCallbackRef.current(safeGappedBoundingBox, realBoundingBox, scaleRef.current)
}, [selectedElements, metadataRef, scaleRef, shouldApplySafeGap])

useSelectorWithCallback(
Expand Down
22 changes: 14 additions & 8 deletions editor/src/components/canvas/controls/highlight-control.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface HighlightControlProps {
canvasOffset: CanvasPoint
scale: number
color?: string
displayZeroSized: 'display-zero-sized' | 'do-not-display-zero-sized'
}

export const HighlightControl = React.memo((props: HighlightControlProps) => {
Expand All @@ -18,14 +19,19 @@ export const HighlightControl = React.memo((props: HighlightControlProps) => {
props.color === null ? colorTheme.canvasSelectionPrimaryOutline.value : props.color

if (isZeroSizedElement(props.frame)) {
return (
<ZeroSizeHighlightControl
frame={props.frame}
canvasOffset={props.canvasOffset}
scale={props.scale}
color={outlineColor}
/>
)
// Only display the zero size higlight if it should be displayed.
if (props.displayZeroSized === 'display-zero-sized') {
return (
<ZeroSizeHighlightControl
frame={props.frame}
canvasOffset={props.canvasOffset}
scale={props.scale}
color={outlineColor}
/>
)
} else {
return null
}
} else {
return (
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ const NewCanvasControlsInner = (props: NewCanvasControlsInnerProps) => {
frame={frame}
scale={scale}
canvasOffset={canvasOffset}
displayZeroSized={'display-zero-sized'}
/>
)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,15 @@ export const AbsoluteResizeControl = controlForStrategyMemoized(
'AbsoluteResizeControl scale',
)

const controlRef = useBoundingBox(targets, (ref, boundingBox) => {
if (isZeroSizedElement(boundingBox)) {
const controlRef = useBoundingBox(targets, (ref, safeGappedBoundingBox, realBoundingBox) => {
if (isZeroSizedElement(realBoundingBox)) {
ref.current.style.display = 'none'
} else {
ref.current.style.display = 'block'
ref.current.style.left = boundingBox.x + 'px'
ref.current.style.top = boundingBox.y + 'px'
ref.current.style.width = boundingBox.width + 'px'
ref.current.style.height = boundingBox.height + 'px'
ref.current.style.left = safeGappedBoundingBox.x + 'px'
ref.current.style.top = safeGappedBoundingBox.y + 'px'
ref.current.style.width = safeGappedBoundingBox.width + 'px'
ref.current.style.height = safeGappedBoundingBox.height + 'px'
}
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,20 @@ export const BorderRadiusControl = controlForStrategyMemoized<BorderRadiusContro

const backgroundShown = hoveredViews.some((p) => EP.pathsEqual(p, selectedElement))

const controlRef = useBoundingBox([selectedElement], (ref, boundingBox) => {
if (isZeroSizedElement(boundingBox)) {
ref.current.style.display = 'none'
} else {
ref.current.style.display = 'block'
ref.current.style.left = boundingBox.x + 'px'
ref.current.style.top = boundingBox.y + 'px'
ref.current.style.width = boundingBox.width + 'px'
ref.current.style.height = boundingBox.height + 'px'
}
})
const controlRef = useBoundingBox(
[selectedElement],
(ref, safeGappedBoundingBox, realBoundingBox) => {
if (isZeroSizedElement(realBoundingBox)) {
ref.current.style.display = 'none'
} else {
ref.current.style.display = 'block'
ref.current.style.left = safeGappedBoundingBox.x + 'px'
ref.current.style.top = safeGappedBoundingBox.y + 'px'
ref.current.style.width = safeGappedBoundingBox.width + 'px'
ref.current.style.height = safeGappedBoundingBox.height + 'px'
}
},
)

const borderRadius = useEditorState(
Substores.metadata,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,17 +284,20 @@ export const PaddingResizeControl = controlForStrategyMemoized((props: PaddingCo

const numberToPxValue = (n: number) => n + 'px'

const controlRef = useBoundingBox(selectedElements, (ref, boundingBox) => {
if (isZeroSizedElement(boundingBox)) {
ref.current.style.display = 'none'
} else {
ref.current.style.display = 'block'
ref.current.style.left = boundingBox.x + 'px'
ref.current.style.top = boundingBox.y + 'px'
ref.current.style.width = boundingBox.width + 'px'
ref.current.style.height = boundingBox.height + 'px'
}
})
const controlRef = useBoundingBox(
selectedElements,
(ref, safeGappedBoundingBox, realBoundingBox) => {
if (isZeroSizedElement(realBoundingBox)) {
ref.current.style.display = 'none'
} else {
ref.current.style.display = 'block'
ref.current.style.left = safeGappedBoundingBox.x + 'px'
ref.current.style.top = safeGappedBoundingBox.y + 'px'
ref.current.style.width = safeGappedBoundingBox.width + 'px'
ref.current.style.height = safeGappedBoundingBox.height + 'px'
}
},
)

const timeoutRef = React.useRef<NodeJS.Timeout | null>(null)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,20 @@ export const OutlineControl = React.memo<OutlineControlProps>((props) => {
'OutlineControl scale',
)

const outlineRef = useBoundingBox(targets, (ref, boundingBox, canvasScale) => {
if (isZeroSizedElement(boundingBox)) {
ref.current.style.display = 'none'
} else {
ref.current.style.display = 'block'
ref.current.style.left = `${boundingBox.x - 0.5 / canvasScale}px`
ref.current.style.top = `${boundingBox.y - 0.5 / canvasScale}px`
ref.current.style.width = `${boundingBox.width + 1 / canvasScale}px`
ref.current.style.height = `${boundingBox.height + 1 / canvasScale}px`
}
})
const outlineRef = useBoundingBox(
targets,
(ref, safeGappedBoundingBox, realBoundingBox, canvasScale) => {
if (isZeroSizedElement(realBoundingBox)) {
ref.current.style.display = 'none'
} else {
ref.current.style.display = 'block'
ref.current.style.left = `${safeGappedBoundingBox.x - 0.5 / canvasScale}px`
ref.current.style.top = `${safeGappedBoundingBox.y - 0.5 / canvasScale}px`
ref.current.style.width = `${safeGappedBoundingBox.width + 1 / canvasScale}px`
ref.current.style.height = `${safeGappedBoundingBox.height + 1 / canvasScale}px`
}
},
)

if (targets.length > 0) {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const StaticReparentTargetOutlineIndicator = controlForStrategyMemoized((
scale={scale}
color={colorTheme.canvasSelectionPrimaryOutline.value}
frame={parentFrame}
displayZeroSized={'do-not-display-zero-sized'}
/>
)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,27 @@ import { mouseDoubleClickAtPoint } from '../event-helpers.test-utils'
import * as EP from '../../../core/shared/element-path'
import { BakedInStoryboardUID } from '../../../core/model/scene-utils'
import { selectComponents } from '../../editor/actions/meta-actions'
import { ZeroSizedControlTestID } from './zero-sized-element-controls'
import type { ElementPath } from '../../../core/shared/project-file-types'
import { ZeroSizedEventsControlTestID } from './zero-sized-element-controls'

async function selectTargetAndDoubleClickOnZeroSizeControl(
renderResult: EditorRenderResult,
target: ElementPath,
testID: string,
shiftX: number = 0,
shiftY: number = 0,
) {
await renderResult.dispatch(selectComponents([target], false), true)

const zeroSizeControl = renderResult.renderedDOM.getByTestId(ZeroSizedControlTestID)
const zeroSizeControl = renderResult.renderedDOM.getByTestId(ZeroSizedEventsControlTestID)
const element = renderResult.renderedDOM.getByTestId(testID)
const elementBounds = element.getBoundingClientRect()

const topLeftCorner = {
x: elementBounds.x,
y: elementBounds.y,
const targetPoint = {
x: elementBounds.x + shiftX,
y: elementBounds.y + shiftY,
}
await mouseDoubleClickAtPoint(zeroSizeControl, topLeftCorner)
await mouseDoubleClickAtPoint(zeroSizeControl, targetPoint)

await renderResult.getDispatchFollowUpActionsFinished()
}
Expand Down Expand Up @@ -70,6 +72,27 @@ describe('Zero sized element controls', () => {
const target = EP.fromString(`${BakedInStoryboardUID}/${TestSceneUID}/${TestAppUID}:aaa/bbb`)
await selectTargetAndDoubleClickOnZeroSizeControl(renderResult, target, 'bbb')

expect(getPrintedUiJsCode(renderResult.getEditorState())).toEqual(
makeTestProjectCodeWithSnippet(
`<div style={{ ...props.style }} data-uid='aaa'>
<div style={{ position: 'absolute', top: 20, left: 20, width: 100, height: 100 }} data-uid='bbb' data-testid='bbb' />
</div>`,
),
)
})
it('double click slightly outside a div element (where the zero sized border is visually) adds size', async () => {
const renderResult = await renderTestEditorWithCode(
makeTestProjectCodeWithSnippet(
`<div style={{ ...props.style }} data-uid='aaa'>
<div style={{ position: 'absolute', top: 20, left: 20 }} data-uid='bbb' data-testid='bbb' />
</div>`,
),
'await-first-dom-report',
)

const target = EP.fromString(`${BakedInStoryboardUID}/${TestSceneUID}/${TestAppUID}:aaa/bbb`)
await selectTargetAndDoubleClickOnZeroSizeControl(renderResult, target, 'bbb', -2, -2)

expect(getPrintedUiJsCode(renderResult.getEditorState())).toEqual(
makeTestProjectCodeWithSnippet(
`<div style={{ ...props.style }} data-uid='aaa'>
Expand Down
Loading
Loading