Skip to content

Commit

Permalink
magic stuff for multiple entity Transform
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoecheza committed Nov 13, 2024
1 parent 4e72764 commit 93b1c94
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect } from 'react'

import { isValidNumericInput, useComponentInput } from '../../../hooks/sdk/useComponentInput'
import { isValidNumericInput, useComponentInput, useComponentInput2 } from '../../../hooks/sdk/useComponentInput'
import { useHasComponent } from '../../../hooks/sdk/useHasComponent'
import { withSdk } from '../../../hoc/withSdk'

Expand All @@ -20,8 +20,8 @@ export default withSdk<Props>(({ sdk, entities }) => {
const hasTransform = useHasComponent(entity, Transform)
const transform = Transform.getOrNull(entity) ?? undefined
const config = TransformConfig.getOrNull(entity) ?? undefined
const { getInputProps } = useComponentInput(
entity,
const { getInputProps } = useComponentInput2(
entities,
Transform,
fromTransform,
toTransform(transform, config),
Expand Down
136 changes: 136 additions & 0 deletions packages/@dcl/inspector/src/hooks/sdk/useComponentInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,139 @@ export const useComponentInput = <ComponentValueType extends object, InputType e

return { getInputProps: getProps, isValid }
}

const mergeComponentValues = <ComponentValueType extends object, InputType extends Input>(
values: ComponentValueType[],
fromComponentValueToInput: (componentValue: ComponentValueType) => InputType,

Check failure on line 119 in packages/@dcl/inspector/src/hooks/sdk/useComponentInput.ts

View workflow job for this annotation

GitHub Actions / lint

Delete `,`
): InputType => {
// Transform all component values to input format
const inputs = values.map(fromComponentValueToInput)

// Get first input as reference
const firstInput = inputs[0]

// Create result object with same shape as first input
const result = {} as InputType

// For each key in first input
for (const key in firstInput) {
const firstValue = firstInput[key]

// Check if all inputs have same value for this key
const allSame = inputs.every(input => {

Check failure on line 135 in packages/@dcl/inspector/src/hooks/sdk/useComponentInput.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `input` with `(input)`
const value = input[key]
if (typeof value === 'object' && value !== null) {
// For objects, compare stringified versions
return JSON.stringify(value) === JSON.stringify(firstValue)
}
return value === firstValue
})

// Set result value based on whether all inputs match
result[key] = allSame ? firstValue : '--' as any

Check failure on line 145 in packages/@dcl/inspector/src/hooks/sdk/useComponentInput.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `'--'·as·any` with `('--'·as·any)`
}

return result
}

export const useComponentInput2 = <ComponentValueType extends object, InputType extends Input>(
entities: Entity[],
component: Component<ComponentValueType>,
fromComponentValueToInput: (componentValue: ComponentValueType) => InputType,
fromInputToComponentValue: (input: InputType) => ComponentValueType,
validateInput: (input: InputType) => boolean = () => true,
deps: unknown[] = []
) => {
const componentValues = entities.map((entity) =>

Check failure on line 159 in packages/@dcl/inspector/src/hooks/sdk/useComponentInput.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `⏎····useComponentValue<ComponentValueType>(entity,·component)⏎··` with `·useComponentValue<ComponentValueType>(entity,·component)`
useComponentValue<ComponentValueType>(entity, component)
)
const [componentValue, _, isEqual] = componentValues[0]
const asd = mergeComponentValues(componentValues.map(([value]) => value), fromComponentValueToInput)

Check failure on line 163 in packages/@dcl/inspector/src/hooks/sdk/useComponentInput.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `componentValues.map(([value])·=>·value),·fromComponentValueToInput` with `⏎····componentValues.map(([value])·=>·value),⏎····fromComponentValueToInput⏎··`
const [input, setInput] = useState<InputType | null>(
componentValue === null ? null : fromComponentValueToInput(componentValue)
)
const [focusedOn, setFocusedOn] = useState<string | null>(null)
const skipSyncRef = useRef(false)
const [isValid, setIsValid] = useState(true)

const updateInputs = useCallback((value: InputType | null, skipSync = false) => {
skipSyncRef.current = skipSync
setInput(value)
}, [])

const handleUpdate =
(path: NestedKey<InputType>, getter: (event: React.ChangeEvent<HTMLInputElement>) => any = (e) => e.target.value) =>
(event: React.ChangeEvent<HTMLInputElement>) => {
if (input === null) return
const newInputs = setValue(input, path, getter(event))
updateInputs(newInputs)
}

const handleFocus = useCallback(
(path: NestedKey<InputType>) => () => {
setFocusedOn(path)
},
[]
)

const handleBlur = useCallback(() => {
if (componentValue === null) return
setFocusedOn(null)
updateInputs(fromComponentValueToInput(componentValue))
}, [componentValue])

const validate = useCallback(
(input: InputType | null): input is InputType => input !== null && validateInput(input),
[input, ...deps]
)

// sync inputs -> engine
useEffect(() => {
if (skipSyncRef.current) return
if (validate(input)) {
const newComponentValue = { ...componentValue, ...fromInputToComponentValue(input) }
if (isEqual(newComponentValue)) return

for (const [_, setComponentValue] of componentValues) {
setComponentValue(newComponentValue)
}
}
}, [input])

// sync engine -> inputs
useEffect(() => {
if (componentValue === null) return

let newInputs = fromComponentValueToInput(componentValue) as any
if (focusedOn) {
// skip sync from state while editing, to avoid overriding the user input
const current = getValue(input, focusedOn)
newInputs = setValue(newInputs, focusedOn, current)
}
// set "skipSync" to avoid cyclic component value change
updateInputs(newInputs, true)
}, [componentValue, ...deps])

useEffect(() => {
setIsValid(validate(input))
}, [input, ...deps])

const getProps = useCallback(
(
path: NestedKey<InputType>,
getter?: (event: React.ChangeEvent<HTMLInputElement>) => any
): Pick<InputHTMLAttributes<HTMLElement>, 'value' | 'onChange' | 'onFocus' | 'onBlur'> => {
const value = (getValue(asd, path) || '').toString()

return {
value,
onChange: handleUpdate(path, getter),
onFocus: handleFocus(path),
onBlur: handleBlur
}
},
[handleUpdate, handleFocus, handleBlur, input]
)

return { getInputProps: getProps, isValid }
}

0 comments on commit 93b1c94

Please sign in to comment.