From 44464d808c5f9ed2b240508d3027ca58d07aa568 Mon Sep 17 00:00:00 2001 From: Berci Kormendy Date: Fri, 25 Oct 2024 17:09:09 +0200 Subject: [PATCH] refacc adjust css length command --- .../commands/adjust-css-length-command.ts | 387 +++++++----------- 1 file changed, 138 insertions(+), 249 deletions(-) diff --git a/editor/src/components/canvas/commands/adjust-css-length-command.ts b/editor/src/components/canvas/commands/adjust-css-length-command.ts index 6563a9288bb5..7304cf04fb28 100644 --- a/editor/src/components/canvas/commands/adjust-css-length-command.ts +++ b/editor/src/components/canvas/commands/adjust-css-length-command.ts @@ -1,31 +1,16 @@ import { MetadataUtils } from '../../../core/model/element-metadata-utils' -import type { Either } from '../../../core/shared/either' -import { foldEither, isLeft, isRight, left, mapEither } from '../../../core/shared/either' import * as EP from '../../../core/shared/element-path' -import type { JSXAttributes, JSXElement } from '../../../core/shared/element-template' -import { - emptyComments, - isJSXElement, - jsExpressionValue, -} from '../../../core/shared/element-template' -import type { ValueAtPath } from '../../../core/shared/jsx-attributes' -import { setJSXValuesAtPaths } from '../../../core/shared/jsx-attributes' -import { - getModifiableJSXAttributeAtPath, - jsxSimpleAttributeToValue, -} from '../../../core/shared/jsx-attribute-utils' import { roundTo, roundToNearestWhole } from '../../../core/shared/math-utils' import type { ElementPath, PropertyPath } from '../../../core/shared/project-file-types' import * as PP from '../../../core/shared/property-path' import type { EditorState } from '../../editor/store/editor-state' -import { modifyUnderlyingForOpenFile } from '../../editor/store/editor-state' import type { CSSNumber, FlexDirection } from '../../inspector/common/css-utils' -import { parseCSSPercent, parseCSSPx, printCSSNumber } from '../../inspector/common/css-utils' +import { printCSSNumber } from '../../inspector/common/css-utils' import type { BaseCommand, CommandFunctionResult, WhenToRun } from './commands' -import { patchParseSuccessAtElementPath } from './patch-utils' import { mapDropNulls } from '../../../core/shared/array-utils' -import { maybeCssPropertyFromInlineStyle } from './utils/property-utils' -import { runStyleUpdateForStrategy } from '../plugins/style-plugins' +import { getCSSNumberFromStyleInfo, maybeCssPropertyFromInlineStyle } from './utils/property-utils' +import type { StyleUpdate } from '../plugins/style-plugins' +import { getActivePlugin, runStyleUpdateForStrategy } from '../plugins/style-plugins' import type { InteractionLifecycle } from '../canvas-strategies/canvas-strategy-types' export type CreateIfNotExistant = 'create-if-not-existing' | 'do-not-create-if-doesnt-exist' @@ -73,11 +58,6 @@ export function adjustCssLengthProperties( } } -interface UpdatedPropsAndCommandDescription { - updatedProps: JSXAttributes - commandDescription: string -} - export const runAdjustCssLengthProperties = ( editorState: EditorState, command: AdjustCssLengthProperties, @@ -91,215 +71,146 @@ export const runAdjustCssLengthProperties = ( command.parentFlexDirection, ) + const styleInfoReader = getActivePlugin(withConflictingPropertiesRemoved).styleInfoFactory({ + projectContents: withConflictingPropertiesRemoved.projectContents, + metadata: withConflictingPropertiesRemoved.jsxMetadata, + elementPathTree: withConflictingPropertiesRemoved.elementPathTree, + }) + + const styleInfo = styleInfoReader(command.target) + if (styleInfo == null) { + return { + editorStatePatches: [], + commandDescription: `Adjust CSS Length Properties: Element at ${EP.toString( + command.target, + )} not found`, + } + } + let commandDescriptions: Array = [] - const updatedEditorState: EditorState = modifyUnderlyingForOpenFile( - command.target, - withConflictingPropertiesRemoved, - (element) => { - if (isJSXElement(element)) { - return command.properties.reduce((workingElement, property) => { - // Get the current value of the property... - const currentValue = getModifiableJSXAttributeAtPath( - workingElement.props, - property.property, - ) - // ...If the value is not writeable then escape out. - if (isLeft(currentValue)) { - commandDescriptions.push( - `Adjust Css Length Prop: ${EP.toUid(command.target)}/${PP.toString( - property.property, - )} not applied as value is not writeable.`, - ) - return workingElement - } - // ...Determine some other facts about the current value. - const currentModifiableValue = currentValue.value - const simpleValueResult = jsxSimpleAttributeToValue(currentModifiableValue) - const valueProbablyExpression = isLeft(simpleValueResult) - const targetPropertyNonExistant: boolean = - currentModifiableValue.type === 'ATTRIBUTE_NOT_FOUND' + const propsToUpdate: StyleUpdate[] = mapDropNulls((propertyUpdate) => { + const property = maybeCssPropertyFromInlineStyle(propertyUpdate.property) + if (property == null) { + commandDescriptions.push( + `Adjust Css Length Prop: ${EP.toUid(command.target)}/${PP.toString( + propertyUpdate.property, + )} not found.`, + ) + return null + } - // ...If the current value does not exist and we shouldn't create it if it doesn't exist - // then exit early from handling this property. - if ( - targetPropertyNonExistant && - property.createIfNonExistant === 'do-not-create-if-doesnt-exist' - ) { - commandDescriptions.push( - `Adjust Css Length Prop: ${EP.toUid(command.target)}/${PP.toString( - property.property, - )} not applied as the property does not exist.`, - ) - return workingElement - } + const currentValue = getCSSNumberFromStyleInfo(styleInfo, property) + if (currentValue.type === 'not-css-number') { + commandDescriptions.push( + `Adjust Css Length Prop: ${EP.toUid(command.target)}/${PP.toString( + propertyUpdate.property, + )} not applied as value is not writeable.`, + ) + return null + } - // ...If the value is an expression then we can't update it. - if (valueProbablyExpression) { - // TODO add option to override expressions!!! - commandDescriptions.push( - `Adjust Css Length Prop: ${EP.toUid(command.target)}/${PP.toString( - property.property, - )} not applied as the property is an expression we did not want to override.`, - ) - return workingElement - } + if ( + currentValue.type === 'not-found' && + propertyUpdate.createIfNonExistant === 'do-not-create-if-doesnt-exist' + ) { + commandDescriptions.push( + `Adjust Css Length Prop: ${EP.toUid(command.target)}/${PP.toString( + propertyUpdate.property, + )} not applied as the property does not exist.`, + ) + return null + } - // Commonly used function for handling the updates. - function handleUpdateResult( - result: Either, - ): JSXElement { - return foldEither( - (error) => { - commandDescriptions.push(error) - return workingElement - }, - (updatedProps) => { - commandDescriptions.push(updatedProps.commandDescription) - return { - ...workingElement, - props: updatedProps.updatedProps, - } - }, - result, - ) - } + if ( + currentValue.type === 'css-number' && + (currentValue.number.unit == null || currentValue.number.unit === 'px') + ) { + const { commandDescription, styleUpdate } = updatePixelValueByPixel( + command.target, + property, + currentValue.number, + propertyUpdate.valuePx, + ) + commandDescriptions.push(commandDescription) + return styleUpdate + } - // Parse the current value as a pixel value... - const parsePxResult = parseCSSPx(simpleValueResult.value) // TODO make type contain px - // ...If the value can be parsed as a pixel value then update it. - if (isRight(parsePxResult)) { - return handleUpdateResult( - updatePixelValueByPixel( - workingElement.props, - command.target, - property.property, - parsePxResult.value, - property.valuePx, - ), - ) - } + if (currentValue.type === 'css-number' && currentValue.number.unit === '%') { + const { commandDescription, styleUpdate } = updatePercentageValueByPixel( + command.target, + property, + propertyUpdate.parentDimensionPx, + currentValue.number, + propertyUpdate.valuePx, + ) + commandDescriptions.push(commandDescription) + return styleUpdate ?? null + } - // Parse the current value as a percentage value... - const parsePercentResult = parseCSSPercent(simpleValueResult.value) // TODO make type contain % - // ...If the value can be parsed as a percentage value then update it. - if (isRight(parsePercentResult)) { - return handleUpdateResult( - updatePercentageValueByPixel( - workingElement.props, - command.target, - property.property, - property.parentDimensionPx, - parsePercentResult.value, - property.valuePx, - ), - ) - } + if ( + currentValue.type === 'not-found' && + propertyUpdate.createIfNonExistant === 'create-if-not-existing' + ) { + const { commandDescription, styleUpdate } = setPixelValue( + command.target, + property, + propertyUpdate.valuePx, + ) + commandDescriptions.push(commandDescription) + return styleUpdate + } - // Otherwise if it is permitted to create it if it doesn't exist, then do so. - if (property.createIfNonExistant === 'create-if-not-existing') { - return handleUpdateResult( - setPixelValue( - workingElement.props, - command.target, - property.property, - property.valuePx, - ), - ) - } + commandDescriptions.push( + `Adjust Css Length Prop: ${EP.toUid(command.target)}/${PP.toString( + propertyUpdate.property, + )} not applied as the property is in a CSS unit we do not support.`, + ) + return null + }, command.properties) - // Updating the props fallback. - commandDescriptions.push( - `Adjust Css Length Prop: ${EP.toUid(command.target)}/${PP.toString( - property.property, - )} not applied as the property is in a CSS unit we do not support. (${ - simpleValueResult.value - })`, - ) - return workingElement - }, element) - } + if (propsToUpdate.length === 0) { + return { editorStatePatches: [], commandDescription: 'No props to update' } + } - // Final fallback. - return element - }, + const { editorStatePatch } = runStyleUpdateForStrategy( + interactionLifecycle, + editorState, + command.target, + propsToUpdate, ) - if (commandDescriptions.length === 0) { - // Cater for no updates at all happened. - return { - editorStatePatches: [], - commandDescription: `No JSXElement was found at path ${EP.toString(command.target)}.`, - } - } else { - if (updatedEditorState === editorState) { - // As the `EditorState` never changed, return an empty patch. - return { - editorStatePatches: [], - commandDescription: commandDescriptions.join('\n'), - } - } else { - // Build the patch for the changes. - const editorStatePatch = patchParseSuccessAtElementPath( - command.target, - updatedEditorState, - (success) => { - return { - topLevelElements: { - $set: success.topLevelElements, - }, - imports: { - $set: success.imports, - }, - } - }, - ) - return { - editorStatePatches: [editorStatePatch], - commandDescription: commandDescriptions.join('\n'), - } - } + return { + editorStatePatches: [editorStatePatch], + commandDescription: commandDescriptions.join('\n'), } } function setPixelValue( - properties: JSXAttributes, targetElement: ElementPath, - targetProperty: PropertyPath, + targetProperty: string, value: number, -): Either { +): { commandDescription: string; styleUpdate: StyleUpdate } { const newValueCssNumber: CSSNumber = { value: value, unit: null, } const newValue = printCSSNumber(newValueCssNumber, null) - const propsToUpdate: Array = [ - { - path: targetProperty, - value: jsExpressionValue(newValue, emptyComments), - }, - ] - - const updatePropsResult = setJSXValuesAtPaths(properties, propsToUpdate) - - return mapEither((updatedProps) => { - return { - updatedProps: updatedProps, - commandDescription: `Set css Length Prop: ${EP.toUid(targetElement)}/${PP.toString( - targetProperty, - )} to ${value}.`, - } - }, updatePropsResult) + return { + styleUpdate: { type: 'set', property: targetProperty, value: newValue }, + commandDescription: `Set css Length Prop: ${EP.toUid( + targetElement, + )}/${targetProperty} to ${value}.`, + } } function updatePixelValueByPixel( - properties: JSXAttributes, targetElement: ElementPath, - targetProperty: PropertyPath, + targetProperty: string, currentValue: CSSNumber, byValue: number, -): Either { +): { commandDescription: string; styleUpdate: StyleUpdate } { if (currentValue.unit != null && currentValue.unit !== 'px') { throw new Error('updatePixelValueByPixel called with a non-pixel cssnumber') } @@ -310,48 +221,37 @@ function updatePixelValueByPixel( } const newValue = printCSSNumber(newValueCssNumber, null) - const propsToUpdate: Array = [ - { - path: targetProperty, - value: jsExpressionValue(newValue, emptyComments), - }, - ] - const updatePropsResult = setJSXValuesAtPaths(properties, propsToUpdate) - - return mapEither((updatedProps) => { - return { - updatedProps: updatedProps, - commandDescription: `Adjust Css Length Prop: ${EP.toUid(targetElement)}/${PP.toString( - targetProperty, - )} by ${byValue}`, - } - }, updatePropsResult) + return { + styleUpdate: { type: 'set', property: targetProperty, value: newValue }, + commandDescription: `Adjust Css Length Prop: ${EP.toUid( + targetElement, + )}/${targetProperty} by ${byValue}`, + } } function updatePercentageValueByPixel( - properties: JSXAttributes, targetElement: ElementPath, - targetProperty: PropertyPath, + targetProperty: string, parentDimensionPx: number | undefined, currentValue: CSSNumber, // TODO restrict to percentage numbers byValue: number, -): Either { +): { commandDescription: string; styleUpdate?: StyleUpdate } { if (currentValue.unit == null || currentValue.unit !== '%') { throw new Error('updatePercentageValueByPixel called with a non-percentage cssnumber') } if (parentDimensionPx == null) { - return left( - `Adjust Css Length Prop: ${EP.toUid(targetElement)}/${PP.toString( - targetProperty, - )} not applied because the parent dimensions are unknown for some reason.`, - ) + return { + commandDescription: `Adjust Css Length Prop: ${EP.toUid( + targetElement, + )}/${targetProperty} not applied because the parent dimensions are unknown for some reason.`, + } } if (parentDimensionPx === 0) { - return left( - `Adjust Css Length Prop: ${EP.toUid(targetElement)}/${PP.toString( - targetProperty, - )} not applied because the parent dimension is 0.`, - ) + return { + commandDescription: `Adjust Css Length Prop: ${EP.toUid( + targetElement, + )}/${targetProperty} not applied because the parent dimension is 0.`, + } } const currentValuePercent = currentValue.value const offsetInPercent = (byValue / parentDimensionPx) * 100 @@ -361,23 +261,12 @@ function updatePercentageValueByPixel( } const newValue = printCSSNumber(newValueCssNumber, null) - const propsToUpdate: Array = [ - { - path: targetProperty, - value: jsExpressionValue(newValue, emptyComments), - }, - ] - - const updatePropsResult = setJSXValuesAtPaths(properties, propsToUpdate) - - return mapEither((updatedProps) => { - return { - updatedProps: updatedProps, - commandDescription: `Adjust Css Length Prop: ${EP.toUid(targetElement)}/${PP.toString( - targetProperty, - )} by ${byValue}`, - } - }, updatePropsResult) + return { + styleUpdate: { type: 'set', property: targetProperty, value: newValue }, + commandDescription: `Adjust Css Length Prop: ${EP.toUid( + targetElement, + )}/${targetProperty} by ${byValue}`, + } } const FlexSizeProperties: Array = [