From feb3ad34b809c88d1d539adb0119b32b10dab312 Mon Sep 17 00:00:00 2001 From: Aria Date: Wed, 4 Jan 2023 13:43:49 +0200 Subject: [PATCH] Compact and collapsible compound prop editor * Create compact vector prop editor * MAke all compound props collapsible * Add collapsed indicator for compound props * Persist collapsed state accross component rerenders * Adjust dom playground to use the new vector prop Co-authored-by: Andrew Prifer --- packages/playground/src/shared/dom/Scene.tsx | 26 +-- .../DetailCompoundPropEditor.tsx | 154 +++++++++++++++--- .../src/uiComponents/icons/EllipsisFill.tsx | 21 +++ .../studio/src/uiComponents/icons/index.ts | 1 + 4 files changed, 168 insertions(+), 34 deletions(-) create mode 100644 theatre/studio/src/uiComponents/icons/EllipsisFill.tsx diff --git a/packages/playground/src/shared/dom/Scene.tsx b/packages/playground/src/shared/dom/Scene.tsx index db9f5bf..576e5bf 100644 --- a/packages/playground/src/shared/dom/Scene.tsx +++ b/packages/playground/src/shared/dom/Scene.tsx @@ -40,16 +40,20 @@ const boxObjectConfig = { amount: types.number(10, {range: [0, 1000], label: '$'}), }), }), - x: types.number(200), - y: types.number(200), + pos: { + x: types.number(200), + y: types.number(200), + }, color: types.rgba({r: 1, g: 0, b: 0, a: 1}), } // this can also be inferred with type _State = ShorthandCompoundPropsToInitialValue type State = { - x: number - y: number + pos: { + x: number + y: number + } test: string testLiteral: string bool: boolean @@ -78,8 +82,10 @@ const Box: React.FC<{ () => Object.assign({}, boxObjectConfig, { // give the box initial values offset from each other - x: ((id.codePointAt(0) ?? 0) % 15) * 100, - y: ((id.codePointAt(0) ?? 0) % 15) * 100, + pos: { + x: ((id.codePointAt(0) ?? 0) % 15) * 100, + y: ((id.codePointAt(0) ?? 0) % 15) * 100, + }, }), [id], ) @@ -95,7 +101,7 @@ const Box: React.FC<{ useLayoutEffect(() => { const unsubscribeFromChanges = onChange(obj.props, (newValues) => { - boxRef.current.style.transform = `translate(${newValues.x}px, ${newValues.y}px)` + boxRef.current.style.transform = `translate(${newValues.pos.x}px, ${newValues.pos.y}px)` preRef.current.innerText = JSON.stringify(newValues, null, 2) colorRef.current.style.background = newValues.color.toString() }) @@ -104,12 +110,12 @@ const Box: React.FC<{ const dragOpts = useMemo((): UseDragOpts => { let scrub: IScrub | undefined - let initial: typeof obj.value + let initial: typeof obj.value.pos let firstOnDragCalled = false return { onDragStart() { scrub = studio.scrub() - initial = obj.value + initial = obj.value.pos firstOnDragCalled = false }, onDrag(x, y) { @@ -118,7 +124,7 @@ const Box: React.FC<{ firstOnDragCalled = true } scrub!.capture(({set}) => { - set(obj.props, { + set(obj.props.pos, { ...initial, x: x + initial.x, y: y + initial.y, diff --git a/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/DetailCompoundPropEditor.tsx b/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/DetailCompoundPropEditor.tsx index ccf3847..c15087a 100644 --- a/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/DetailCompoundPropEditor.tsx +++ b/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/DetailCompoundPropEditor.tsx @@ -1,11 +1,14 @@ -import type {PropTypeConfig_Compound} from '@theatre/core/propTypes' +import type { + PropTypeConfig_Compound, + PropTypeConfig_Number, +} from '@theatre/core/propTypes' import {isPropConfigComposite} from '@theatre/shared/propTypes/utils' import type {$FixMe} from '@theatre/shared/utils/types' -import {getPointerParts} from '@theatre/dataverse' +import {Box, getPointerParts} from '@theatre/dataverse' import type {Pointer} from '@theatre/dataverse' import last from 'lodash-es/last' import {darken, transparentize} from 'polished' -import React, {useMemo} from 'react' +import React, {useLayoutEffect, useMemo} from 'react' import styled from 'styled-components' import {rowIndentationFormulaCSS} from '@theatre/studio/panels/DetailPanel/DeterminePropEditorForDetail/rowIndentationFormulaCSS' import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS' @@ -20,11 +23,18 @@ import type {PropHighlighted} from '@theatre/studio/panels/SequenceEditorPanel/w import {whatPropIsHighlighted} from '@theatre/studio/panels/SequenceEditorPanel/whatPropIsHighlighted' import {deriver} from '@theatre/studio/utils/derive-utils' import {getDetailRowHighlightBackground} from './getDetailRowHighlightBackground' +import NumberPropEditor from '@theatre/studio/propEditors/simpleEditors/NumberPropEditor' +import type {IDetailSimplePropEditorProps} from './DetailSimplePropEditor' +import {useEditingToolsForSimplePropInDetailsPanel} from '@theatre/studio/propEditors/useEditingToolsForSimpleProp' +import {EllipsisFill} from '@theatre/studio/uiComponents/icons' +import {usePrism} from '@theatre/react' +import {val} from '@theatre/dataverse' const Container = styled.div` --step: 15px; --left-pad: 15px; ${pointerEventsAutoInNormalMode}; + --right-width: 60%; ` const Header = deriver(styled.div<{isHighlighted: PropHighlighted}>` @@ -40,6 +50,7 @@ const Padding = styled.div` padding-left: ${rowIndentationFormulaCSS}; display: flex; align-items: center; + width: calc(100% - var(--right-width)); ` const PropName = deriver(styled.div<{isHighlighted: PropHighlighted}>` @@ -48,6 +59,7 @@ const PropName = deriver(styled.div<{isHighlighted: PropHighlighted}>` height: 100%; display: flex; align-items: center; + gap: 4px; user-select: none; &:hover { color: white; @@ -63,6 +75,51 @@ const SubProps = styled.div<{depth: number; lastSubIsComposite: boolean}>` /* padding: ${(props) => (props.lastSubIsComposite ? 0 : '4px')} 0; */ ` +const isVectorProp = (propConfig: PropTypeConfig_Compound) => { + const props = Object.entries(propConfig.props) + + return ( + props.length <= 3 && + props.every( + ([name, conf]) => + conf.type === 'number' && ['x', 'y', 'z'].includes(name), + ) + ) +} + +function VectorComponentEditor({ + propConfig, + pointerToProp, + obj, + SimpleEditorComponent: EditorComponent, +}: IDetailSimplePropEditorProps) { + const editingTools = useEditingToolsForSimplePropInDetailsPanel( + pointerToProp, + obj, + propConfig, + ) + + return ( + + ) +} + +const InputContainer = styled.div` + display: flex; + align-items: center; + justify-content: stretch; + padding: 0 8px 0 2px; + box-sizing: border-box; + height: 100%; + width: var(--right-width); + flex-shrink: 0; + flex-grow: 0; +` + export type ICompoundPropDetailEditorProps< TPropTypeConfig extends PropTypeConfig_Compound, > = { @@ -114,6 +171,24 @@ function DetailCompoundPropEditor< [pointerToProp], ) + const globalPointerPath = `${obj.address.projectId},${obj.address.sheetId},${ + obj.address.sheetInstanceId + },${obj.address.objectKey},${getPointerParts(pointerToProp).path.join()}` + + useLayoutEffect(() => { + if (!collapsedMap.has(globalPointerPath)) { + collapsedMap.set(globalPointerPath, new Box(isVectorProp(propConfig))) + } + }, []) + + const box = collapsedMap.get(globalPointerPath) + + const isCollapsed = usePrism(() => { + const box = collapsedMap.get(globalPointerPath) + + return box ? val(box.derivation) : isVectorProp(propConfig) + }, [box]) + return ( {contextMenu} @@ -127,34 +202,65 @@ function DetailCompoundPropEditor< { + box?.set(!box.get()) + }} > - {propName || 'Props'} + {propName || 'Props'} + {!isVectorProp(propConfig) && isCollapsed && ( + + )} + {isVectorProp(propConfig) && isCollapsed && ( + + {[...allSubs].map(([subPropKey, subPropConfig]) => { + return ( + } + obj={obj} + /> + ) + })} + + )} - - {[...nonCompositeSubs, ...compositeSubs].map( - ([subPropKey, subPropConfig]) => { - return ( - } - obj={obj} - visualIndentation={visualIndentation + 1} - /> - ) - }, - )} - + {!isCollapsed && ( + + {[...nonCompositeSubs, ...compositeSubs].map( + ([subPropKey, subPropConfig]) => { + return ( + } + obj={obj} + visualIndentation={visualIndentation + 1} + /> + ) + }, + )} + + )} ) } export default React.memo(DetailCompoundPropEditor) + +const collapsedMap = new Map>() diff --git a/theatre/studio/src/uiComponents/icons/EllipsisFill.tsx b/theatre/studio/src/uiComponents/icons/EllipsisFill.tsx new file mode 100644 index 0000000..842e8d4 --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/EllipsisFill.tsx @@ -0,0 +1,21 @@ +import * as React from 'react' + +function EllipsisFill(props: React.SVGProps) { + return ( + + + + ) +} + +export default EllipsisFill diff --git a/theatre/studio/src/uiComponents/icons/index.ts b/theatre/studio/src/uiComponents/icons/index.ts index eabc1ea..fedc42b 100644 --- a/theatre/studio/src/uiComponents/icons/index.ts +++ b/theatre/studio/src/uiComponents/icons/index.ts @@ -17,3 +17,4 @@ export {default as Package} from './Package' export {default as Bell} from './Bell' export {default as Trash} from './Trash' export {default as AddImage} from './AddImage' +export {default as EllipsisFill} from './EllipsisFill'