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

Tailwind conversion using StyleInfo #6669

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
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
60 changes: 59 additions & 1 deletion editor/src/components/canvas/canvas-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ import type {
import { InteractionSession } from './canvas-strategies/interaction-state'
import type { CanvasStrategyId } from './canvas-strategies/canvas-strategy-types'
import type { MouseButtonsPressed } from '../../utils/mouse'
import type { CSSNumber, CSSPadding, FlexDirection } from '../inspector/common/css-utils'
import {
printCSSNumber,
type CSSNumber,
type CSSPadding,
type FlexDirection,
} from '../inspector/common/css-utils'
import { optionalMap } from '../../core/shared/optional-utils'

export const CanvasContainerID = 'canvas-container'

Expand Down Expand Up @@ -633,3 +639,55 @@ const emptyStyleInfo: StyleInfo = {
}

export const isStyleInfoKey = (key: string): key is keyof StyleInfo => key in emptyStyleInfo

function mapCSSStyleProperty<T, U>(
property: CSSStyleProperty<T> | null,
map: (value: T) => U,
): U | null {
if (property === null || property.type !== 'property') {
return null
}
return map(property.value)
}

export function stringifyStyleInfo(
styleInfo: StyleInfo,
): Record<keyof StyleInfo, string | number | null> {
return {
gap: mapCSSStyleProperty(styleInfo.gap, (gap) => printCSSNumber(gap, null)),
flexDirection: mapCSSStyleProperty(styleInfo.flexDirection, (flexDirection) => flexDirection),
left: mapCSSStyleProperty(styleInfo.left, (left) => printCSSNumber(left, null)),
right: mapCSSStyleProperty(styleInfo.right, (right) => printCSSNumber(right, null)),
top: mapCSSStyleProperty(styleInfo.top, (top) => printCSSNumber(top, null)),
bottom: mapCSSStyleProperty(styleInfo.bottom, (bottom) => printCSSNumber(bottom, null)),
width: mapCSSStyleProperty(styleInfo.width, (width) => printCSSNumber(width, null)),
height: mapCSSStyleProperty(styleInfo.height, (height) => printCSSNumber(height, null)),
flexBasis: mapCSSStyleProperty(styleInfo.flexBasis, (flexBasis) =>
printCSSNumber(flexBasis, null),
),
padding: mapCSSStyleProperty(
styleInfo.padding,
(padding) =>
`${printCSSNumber(padding.paddingTop, null)} ${printCSSNumber(
padding.paddingRight,
null,
)} ${printCSSNumber(padding.paddingBottom, null)} ${printCSSNumber(
padding.paddingLeft,
null,
)}`,
),
paddingTop: mapCSSStyleProperty(styleInfo.paddingTop, (paddingTop) =>
printCSSNumber(paddingTop, null),
),
paddingRight: mapCSSStyleProperty(styleInfo.paddingRight, (paddingRight) =>
printCSSNumber(paddingRight, null),
),
paddingBottom: mapCSSStyleProperty(styleInfo.paddingBottom, (paddingBottom) =>
printCSSNumber(paddingBottom, null),
),
paddingLeft: mapCSSStyleProperty(styleInfo.paddingLeft, (paddingLeft) =>
printCSSNumber(paddingLeft, null),
),
zIndex: mapCSSStyleProperty(styleInfo.zIndex, (zIndex) => printCSSNumber(zIndex, null)),
}
}
5 changes: 5 additions & 0 deletions editor/src/components/canvas/commands/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ import {
runShowGridControlsCommand,
type ShowGridControlsCommand,
} from './show-grid-controls-command'
import type { InlineStyleTailwindConversionCommand } from './inline-style-tailwind-conversion-command'
import { runInlineStyleTailwindConversionCommand } from './inline-style-tailwind-conversion-command'

export interface CommandFunctionResult {
editorStatePatches: Array<EditorStatePatch>
Expand Down Expand Up @@ -129,6 +131,7 @@ export type CanvasCommand =
| SetActiveFrames
| UpdateBulkProperties
| ShowGridControlsCommand
| InlineStyleTailwindConversionCommand

export function runCanvasCommand(
editorState: EditorState,
Expand Down Expand Up @@ -208,6 +211,8 @@ export function runCanvasCommand(
return runSetActiveFrames(editorState, command)
case 'SHOW_GRID_CONTROLS':
return runShowGridControlsCommand(editorState, command)
case 'INLINE_STYLE_TAILWIND_CONVERSION':
return runInlineStyleTailwindConversionCommand(editorState, command)
default:
const _exhaustiveCheck: never = command
throw new Error(`Unhandled canvas command ${JSON.stringify(command)}`)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import type { ElementPath } from 'utopia-shared/src/types'
import type { BaseCommand, CommandFunctionResult } from './commands'
import type { EditorState, EditorStatePatch } from '../../editor/store/editor-state'
import type { DeleteCSSProp, EditorStateWithPatches, UpdateCSSProp } from '../plugins/style-plugins'
import { InlineStylePlugin } from '../plugins/inline-style-plugin'
import type { StyleInfo } from '../canvas-types'
import { stringifyStyleInfo } from '../canvas-types'
import { TailwindPlugin } from '../plugins/tailwind-style-plugin'
import { getTailwindConfigCached } from '../../../core/tailwind/tailwind-compilation'
import { mapDropNulls } from '../../../core/shared/array-utils'
import { assertNever } from '../../../core/shared/utils'

export interface InlineStyleTailwindConversionCommand extends BaseCommand {
type: 'INLINE_STYLE_TAILWIND_CONVERSION'

direction: 'TO_INLINE_STYLE' | 'TO_TAILWIND'
elementPaths: ElementPath[]
}

export function inlineStyleTailwindConversionCommand(
whenToRun: 'always' | 'on-complete',
direction: 'TO_INLINE_STYLE' | 'TO_TAILWIND',
elementPaths: ElementPath[],
): InlineStyleTailwindConversionCommand {
return {
type: 'INLINE_STYLE_TAILWIND_CONVERSION',
whenToRun: whenToRun,
direction: direction,
elementPaths: elementPaths,
}
}

function getStyleInfoUpdates(styleInfo: StyleInfo): {
stylesToAdd: UpdateCSSProp[]
stylesToRemove: DeleteCSSProp[]
} {
const styleInfoString = stringifyStyleInfo(styleInfo)
const stylesToAdd: UpdateCSSProp[] = mapDropNulls(
([property, value]) =>
value == null
? null
: {
property: property,
value: value,
type: 'set',
},
Object.entries(styleInfoString),
)

const stylesToRemove: DeleteCSSProp[] = stylesToAdd.map((style) => ({
property: style.property,
type: 'delete',
}))

return { stylesToAdd, stylesToRemove }
}

function convertInlineStyleToTailwindViaStyleInfo(
editorState: EditorState,
elementPaths: ElementPath[],
): EditorStateWithPatches {
let patches: EditorStatePatch[] = []
let editorStateWithChanges: EditorState = editorState

elementPaths.forEach((elementPath) => {
const styleInfo = InlineStylePlugin.styleInfoFactory({
projectContents: editorState.projectContents,
})(elementPath)

if (styleInfo == null) {
return
}

const { stylesToAdd, stylesToRemove } = getStyleInfoUpdates(styleInfo)

const { editorStateWithChanges: updatedEditorState } = TailwindPlugin(
getTailwindConfigCached(editorStateWithChanges),
).updateStyles(editorStateWithChanges, elementPath, stylesToAdd)

const { editorStatePatch: editorStatePatchToRemove, editorStateWithChanges: finalEditorState } =
InlineStylePlugin.updateStyles(updatedEditorState, elementPath, stylesToRemove)

patches = [editorStatePatchToRemove]
editorStateWithChanges = finalEditorState
})

return {
editorStateWithChanges: editorStateWithChanges,
editorStatePatches: patches,
}
}

function convertTailwindToInlineStyleViaStyleInfo(
editorState: EditorState,
elementPaths: ElementPath[],
): EditorStateWithPatches {
let patches: EditorStatePatch[] = []
let editorStateWithChanges: EditorState = editorState

elementPaths.forEach((elementPath) => {
const styleInfo = TailwindPlugin(
getTailwindConfigCached(editorStateWithChanges),
).styleInfoFactory({
projectContents: editorStateWithChanges.projectContents,
})(elementPath)

if (styleInfo == null) {
return
}

const { stylesToAdd, stylesToRemove } = getStyleInfoUpdates(styleInfo)

const { editorStateWithChanges: updatedEditorState } = InlineStylePlugin.updateStyles(
editorStateWithChanges,
elementPath,
stylesToAdd,
)
const { editorStatePatch: editorStatePatchToRemove, editorStateWithChanges: finalEditorState } =
TailwindPlugin(getTailwindConfigCached(updatedEditorState)).updateStyles(
updatedEditorState,
elementPath,
stylesToRemove,
)
patches = [editorStatePatchToRemove]
editorStateWithChanges = finalEditorState
})

return {
editorStateWithChanges: editorStateWithChanges,
editorStatePatches: patches,
}
}

function runConversionWithStylePluginsViaStyleInfo(
editorState: EditorState,
elementPaths: ElementPath[],
direction: 'TO_INLINE_STYLE' | 'TO_TAILWIND',
): EditorStateWithPatches {
switch (direction) {
case 'TO_INLINE_STYLE':
return convertTailwindToInlineStyleViaStyleInfo(editorState, elementPaths)
case 'TO_TAILWIND':
return convertInlineStyleToTailwindViaStyleInfo(editorState, elementPaths)
default:
assertNever(direction)
}
}

export function runInlineStyleTailwindConversionCommand(
editorState: EditorState,
command: InlineStyleTailwindConversionCommand,
): CommandFunctionResult {
const { editorStatePatches } = runConversionWithStylePluginsViaStyleInfo(
editorState,
command.elementPaths,
command.direction,
)
return {
commandDescription: 'Inline Style Tailwind Conversion',
editorStatePatches: editorStatePatches,
}
}
4 changes: 2 additions & 2 deletions editor/src/components/canvas/plugins/style-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface UpdateCSSProp {
value: string | number
}

interface DeleteCSSProp {
export interface DeleteCSSProp {
type: 'delete'
property: string
}
Expand Down Expand Up @@ -104,7 +104,7 @@ function ensureElementPathInUpdatedPropertiesGlobal(
return updatedPropertiesToExtend
}

interface EditorStateWithPatches {
export interface EditorStateWithPatches {
editorStateWithChanges: EditorState
editorStatePatches: EditorStatePatch[]
}
Expand Down
Loading