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

Add multi entity transform + gizmos refactor #1037

Merged
merged 6 commits into from
Nov 25, 2024
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
magic stuff for multiple entity Transform
nicoecheza committed Nov 22, 2024
commit 212b957aee1a95d4eff4301eda646932740c7bc6
Original file line number Diff line number Diff line change
@@ -176,10 +176,9 @@ const SingleEntityInspector = withSdk<{ entity: Entity | null }>(({ sdk, entity
)
)}
</>
) : null}
</div>
)
}
)
) : null}
</div>
)
})

export default EntityInspector
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'

@@ -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),
137 changes: 137 additions & 0 deletions packages/@dcl/inspector/src/hooks/sdk/useComponentInput.ts
Original file line number Diff line number Diff line change
@@ -113,3 +113,140 @@ 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
): 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) => {
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)
}

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) => useComponentValue<ComponentValueType>(entity, component))
const [componentValue, _, isEqual] = componentValues[0]
const asd = mergeComponentValues(
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 }
}