Skip to content

Commit

Permalink
feat(editor): pass scene size to style plugins (#6715)
Browse files Browse the repository at this point in the history
This prep PR adds the ability to pass the Scene size to the Tailwind
plugin/
This was extracted from the spike since it added small changes in many
files.

**Details:**

- We need the Scene size in
[tailwind-style-plugin.ts](https://github.com/concrete-utopia/utopia/pull/6715/files#diff-ad30d4bf205861b9efac9bb4bdb2bb68707cace215f64915244c46dbfab0607dR23-R25)
to be able (in the next PR) to calculate the matching breakpoint.
- We get the Scene size in `getContainingSceneWidth` in
[responsive-utils.ts](https://github.com/concrete-utopia/utopia/pull/6715/files#diff-4f8de694cb6cb8852ff046766f5015a01f9567d4c3b7472bbac36358b52932aaR7-R16)
and we pass it to the Tailwind plugin.
- The rest of the changes are just passing down this data.

This PR doesn't change any functionality, but is merely a prep PR for
the next, functional one.

**Manual Tests:**
I hereby swear that:

- [X] I opened a hydrogen project and it loaded
- [X] I could navigate to various routes in Play mode
  • Loading branch information
liady authored Dec 11, 2024
1 parent accbe3d commit 84da0ab
Show file tree
Hide file tree
Showing 15 changed files with 90 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ export function pickCanvasStateFromEditorState(
propertyControlsInfo: editorState.propertyControlsInfo,
styleInfoReader: activePlugin.styleInfoFactory({
projectContents: editorState.projectContents,
jsxMetadata: editorState.jsxMetadata,
}),
}
}
Expand Down Expand Up @@ -255,6 +256,7 @@ export function pickCanvasStateFromEditorStateWithMetadata(
propertyControlsInfo: editorState.propertyControlsInfo,
styleInfoReader: activePlugin.styleInfoFactory({
projectContents: editorState.projectContents,
jsxMetadata: editorState.jsxMetadata,
}),
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,11 @@ export function controlWithProps<P>(value: ControlWithProps<P>): ControlWithProp

export type StyleInfoReader = (elementPath: ElementPath) => StyleInfo | null

export type StyleInfoFactory = (context: {
export type StyleInfoContext = {
projectContents: ProjectContentTreeRoot
}) => StyleInfoReader
jsxMetadata: ElementInstanceMetadataMap
}
export type StyleInfoFactory = (context: StyleInfoContext) => StyleInfoReader

export interface InteractionCanvasState {
interactionTarget: InteractionTarget
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export const runAdjustCssLengthProperties = (

const styleInfoReader = getActivePlugin(withConflictingPropertiesRemoved).styleInfoFactory({
projectContents: withConflictingPropertiesRemoved.projectContents,
jsxMetadata: withConflictingPropertiesRemoved.jsxMetadata,
})

const styleInfo = styleInfoReader(command.target)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export const runSetCssLengthProperty = (

const styleInfo = getActivePlugin(editorStateWithPropsDeleted).styleInfoFactory({
projectContents: editorStateWithPropsDeleted.projectContents,
jsxMetadata: editorStateWithPropsDeleted.jsxMetadata,
})(command.target)

if (styleInfo == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const borderRadiusSelector = createCachedSelector(
(store: StyleInfoSubstate) =>
getActivePlugin(store.editor).styleInfoFactory({
projectContents: store.editor.projectContents,
jsxMetadata: store.editor.jsxMetadata,
}),
(_: MetadataSubstate, x: ElementPath) => x,
(metadata, styleInfoReader, selectedElement) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export const FlexGapControl = controlForStrategyMemoized<FlexGapControlProps>((p
maybeFlexGapData(
getActivePlugin(store.editor).styleInfoFactory({
projectContents: store.editor.projectContents,
jsxMetadata: store.editor.jsxMetadata,
})(selectedElement),
MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, selectedElement),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ export const PaddingResizeControl = controlForStrategyMemoized((props: PaddingCo
const styleInfoReaderRef = useRefEditorState((store) =>
getActivePlugin(store.editor).styleInfoFactory({
projectContents: store.editor.projectContents,
jsxMetadata: store.editor.jsxMetadata,
}),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const SubduedFlexGapControl = React.memo<SubduedFlexGapControlProps>((pro
maybeFlexGapData(
getActivePlugin(store.editor).styleInfoFactory({
projectContents: store.editor.projectContents,
jsxMetadata: store.editor.jsxMetadata,
})(selectedElement),
MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, selectedElement),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const SubduedPaddingControl = React.memo<SubduedPaddingControlProps>((pro
const styleInfoReaderRef = useRefEditorState((store) =>
getActivePlugin(store.editor).styleInfoFactory({
projectContents: store.editor.projectContents,
jsxMetadata: store.editor.jsxMetadata,
}),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ function getStyleInfoFromInlineStyle(editor: EditorRenderResult) {

const styleInfoReader = InlineStylePlugin.styleInfoFactory({
projectContents: projectContents,
jsxMetadata: jsxMetadata,
})
const styleInfo = styleInfoReader(EP.fromString('sb/scene/div'))
return styleInfo
Expand Down
16 changes: 16 additions & 0 deletions editor/src/components/canvas/plugins/style-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,27 @@ export function deleteCSSProp(property: string): DeleteCSSProp {

export type StyleUpdate = UpdateCSSProp | DeleteCSSProp

export type SceneSize = { type: 'no-scene' } | { type: 'scene'; width: number }
export function noSceneSize(): SceneSize {
return { type: 'no-scene' }
}
export function sceneSize(width: number | undefined | null): SceneSize {
if (width == null) {
return noSceneSize()
}
return { type: 'scene', width: width }
}
export type StylePluginContext = {
sceneSize: SceneSize
}

export interface StylePlugin {
name: string
styleInfoFactory: StyleInfoFactory
readStyleFromElementProps: <T extends keyof StyleInfo>(
attributes: JSXAttributes,
prop: T,
context: StylePluginContext,
) => CSSStyleProperty<NonNullable<ParsedCSSProperties[T]>> | null
updateStyles: (
editorState: EditorState,
Expand Down Expand Up @@ -248,6 +263,7 @@ export function patchRemovedProperties(editorState: EditorState): EditorState {

const styleInfoReader = activePlugin.styleInfoFactory({
projectContents: editorState.projectContents,
jsxMetadata: editorState.jsxMetadata,
})

const propertiesUpdatedDuringInteraction = getPropertiesUpdatedDuringInteraction(editorState)
Expand Down
32 changes: 20 additions & 12 deletions editor/src/components/canvas/plugins/tailwind-style-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getElementFromProjectContents } from '../../editor/store/editor-state'
import type { ParsedCSSProperties } from '../../inspector/common/css-utils'
import { cssParsers } from '../../inspector/common/css-utils'
import { mapDropNulls } from '../../../core/shared/array-utils'
import type { StylePlugin } from './style-plugins'
import type { StylePlugin, StylePluginContext } from './style-plugins'
import type { Config } from 'tailwindcss/types/config'
import type { StyleInfo } from '../canvas-types'
import { cssStyleProperty, type CSSStyleProperty } from '../canvas-types'
Expand All @@ -18,17 +18,20 @@ import {
import { emptyComments, type JSXAttributes } from 'utopia-shared/src/types'
import * as PP from '../../../core/shared/property-path'
import { jsExpressionValue } from '../../../core/shared/element-template'

function parseTailwindProperty<T extends keyof StyleInfo>(
value: string | number | undefined,
prop: T,
): CSSStyleProperty<NonNullable<ParsedCSSProperties[T]>> | null {
const parsed = cssParsers[prop](value, null)
if (isLeft(parsed) || parsed.value == null) {
return null
import { getContainingSceneSize } from '../responsive-utils'

const parseTailwindPropertyFactory =
(config: Config | null, context: StylePluginContext) =>
<T extends keyof StyleInfo>(
value: string | number | undefined,
prop: T,
): CSSStyleProperty<NonNullable<ParsedCSSProperties[T]>> | null => {
const parsed = cssParsers[prop](value, null)
if (isLeft(parsed) || parsed.value == null) {
return null
}
return cssStyleProperty(parsed.value, jsExpressionValue(value, emptyComments))
}
return cssStyleProperty(parsed.value, jsExpressionValue(value, emptyComments))
}

const TailwindPropertyMapping: Record<string, string> = {
left: 'positionLeft',
Expand Down Expand Up @@ -100,6 +103,7 @@ export const TailwindPlugin = (config: Config | null): StylePlugin => ({
readStyleFromElementProps: <P extends keyof StyleInfo>(
attributes: JSXAttributes,
prop: P,
context: StylePluginContext,
): CSSStyleProperty<NonNullable<ParsedCSSProperties[P]>> | null => {
const classNameAttribute = defaultEither(
null,
Expand All @@ -114,10 +118,11 @@ export const TailwindPlugin = (config: Config | null): StylePlugin => ({
}

const mapping = getTailwindClassMapping(classNameAttribute.split(' '), config)
const parseTailwindProperty = parseTailwindPropertyFactory(config, context)
return parseTailwindProperty(mapping[TailwindPropertyMapping[prop]], prop)
},
styleInfoFactory:
({ projectContents }) =>
({ projectContents, jsxMetadata }) =>
(elementPath) => {
const classList = getClassNameAttribute(
getElementFromProjectContents(elementPath, projectContents),
Expand All @@ -128,6 +133,9 @@ export const TailwindPlugin = (config: Config | null): StylePlugin => ({
}

const mapping = getTailwindClassMapping(classList.split(' '), config)
const parseTailwindProperty = parseTailwindPropertyFactory(config, {
sceneSize: getContainingSceneSize(elementPath, jsxMetadata),
})

return {
gap: parseTailwindProperty(mapping[TailwindPropertyMapping.gap], 'gap'),
Expand Down
29 changes: 29 additions & 0 deletions editor/src/components/canvas/responsive-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,35 @@ import * as csstree from 'css-tree'
import type { StyleMediaSizeModifier, StyleModifier } from './canvas-types'
import { type CSSNumber, type CSSNumberUnit, cssNumber } from '../inspector/common/css-utils'
import { memoize } from '../../core/shared/memoize'
import type { ElementPath } from '../../core/shared/project-file-types'
import type { ElementInstanceMetadataMap } from '../../core/shared/element-template'
import { MetadataUtils } from '../../core/model/element-metadata-utils'
import { noSceneSize, sceneSize, type SceneSize } from './plugins/style-plugins'
import type { EditorState } from '../editor/store/editor-state'

export function getContainingSceneSize(
selectedElement: ElementPath,
jsxMetadata: ElementInstanceMetadataMap,
): SceneSize {
if (selectedElement == null) {
return noSceneSize()
}
const containingScene = MetadataUtils.getParentSceneMetadata(jsxMetadata, selectedElement)
return sceneSize(containingScene?.specialSizeMeasurements?.clientWidth)
}

export function getContainingSceneSizeFromEditorState(editor: EditorState): SceneSize {
if (editor == null) {
return noSceneSize()
}
// we're taking the first selected element because we're assuming elements are in the same scene
// TODO: support multiple selected elements that are in different scenes?
const selectedElement = editor.selectedViews.at(0)
if (selectedElement == null) {
return noSceneSize()
}
return getContainingSceneSize(selectedElement, editor.jsxMetadata)
}

/**
* Extracts the screen size from a CSS string, for example:
Expand Down
6 changes: 4 additions & 2 deletions editor/src/components/inspector/common/property-path-hooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as PP from '../../../core/shared/property-path'
import * as EP from '../../../core/shared/element-path'

import deepEqual from 'fast-deep-equal'
import * as ObjectPath from 'object-path'
Expand Down Expand Up @@ -96,6 +95,7 @@ import { getActivePlugin } from '../../canvas/plugins/style-plugins'
import { isStyleInfoKey, type StyleInfo } from '../../canvas/canvas-types'
import { assertNever } from '../../../core/shared/utils'
import { maybeCssPropertyFromInlineStyle } from '../../canvas/commands/utils/property-utils'
import { getContainingSceneSizeFromEditorState } from '../../canvas/responsive-utils'

export interface InspectorPropsContextData {
selectedViews: Array<ElementPath>
Expand Down Expand Up @@ -763,7 +763,9 @@ export function useGetMultiselectedProps<P extends ParsedPropertiesKeys>(
const styleInfoReaderRef = useRefEditorState(
(store) =>
(props: JSXAttributes, prop: keyof StyleInfo): GetModifiableAttributeResult => {
const elementStyle = getActivePlugin(store.editor).readStyleFromElementProps(props, prop)
const elementStyle = getActivePlugin(store.editor).readStyleFromElementProps(props, prop, {
sceneSize: getContainingSceneSizeFromEditorState(store.editor),
})
if (elementStyle == null) {
return right({ type: 'ATTRIBUTE_NOT_FOUND' })
}
Expand Down
7 changes: 7 additions & 0 deletions editor/src/core/model/element-metadata-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,13 @@ export const MetadataUtils = {
const elementMetadata = MetadataUtils.findElementByElementPath(metadata, path)
return elementMetadata != null && isSceneFromMetadata(elementMetadata)
},
getParentSceneMetadata(
metadata: ElementInstanceMetadataMap,
path: ElementPath,
): ElementInstanceMetadata | null {
const parentPath = MetadataUtils.findSceneOfTarget(path, metadata)
return parentPath == null ? null : MetadataUtils.findElementByElementPath(metadata, parentPath)
},
overflows(allElementProps: AllElementProps, path: ElementPath): boolean {
const elementProps = allElementProps[EP.toString(path)] ?? {}
const styleProps = elementProps.style ?? null
Expand Down

0 comments on commit 84da0ab

Please sign in to comment.