Show hovered prop from sequence editor in details panel

* Remove an un-needed prism

Co-authored-by: Cole Lawrence <cole@colelawrence.com>
Co-authored-by: Aria Minaei <aria.minaei@gmail.com>
This commit is contained in:
Aria Minaei 2022-06-07 12:56:51 +02:00 committed by Aria
parent 162174568b
commit c74aa1b930
10 changed files with 240 additions and 37 deletions

View file

@ -5,9 +5,9 @@ import {getPointerParts} from '@theatre/dataverse'
import type {Pointer} from '@theatre/dataverse' import type {Pointer} from '@theatre/dataverse'
import last from 'lodash-es/last' import last from 'lodash-es/last'
import {darken, transparentize} from 'polished' import {darken, transparentize} from 'polished'
import React from 'react' import React, {useMemo} from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import {indentationFormula} from '@theatre/studio/panels/DetailPanel/DeterminePropEditorForDetail/SingleRowPropEditor' import {rowIndentationFormulaCSS} from '@theatre/studio/panels/DetailPanel/DeterminePropEditorForDetail/rowIndentationFormulaCSS'
import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS' import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS'
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
import useRefAndState from '@theatre/studio/utils/useRefAndState' import useRefAndState from '@theatre/studio/utils/useRefAndState'
@ -16,6 +16,10 @@ import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
import {useEditingToolsForCompoundProp} from '@theatre/studio/propEditors/useEditingToolsForCompoundProp' import {useEditingToolsForCompoundProp} from '@theatre/studio/propEditors/useEditingToolsForCompoundProp'
import type {PropHighlighted} from '@theatre/studio/panels/SequenceEditorPanel/whatPropIsHighlighted'
import {whatPropIsHighlighted} from '@theatre/studio/panels/SequenceEditorPanel/whatPropIsHighlighted'
import {deriver} from '@theatre/studio/utils/derive-utils'
import {getDetailRowHighlightBackground} from './getDetailRowHighlightBackground'
const Container = styled.div` const Container = styled.div`
--step: 15px; --step: 15px;
@ -23,15 +27,17 @@ const Container = styled.div`
${pointerEventsAutoInNormalMode}; ${pointerEventsAutoInNormalMode};
` `
const Header = styled.div` const Header = deriver(styled.div<{isHighlighted: PropHighlighted}>`
height: 30px; height: 30px;
display: flex; display: flex;
align-items: stretch; align-items: stretch;
position: relative; position: relative;
`
background-color: ${getDetailRowHighlightBackground};
`)
const Padding = styled.div` const Padding = styled.div`
padding-left: ${indentationFormula}; padding-left: ${rowIndentationFormulaCSS};
display: flex; display: flex;
align-items: center; align-items: center;
` `
@ -99,12 +105,22 @@ function DetailCompoundPropEditor<
const lastSubPropIsComposite = compositeSubs.length > 0 const lastSubPropIsComposite = compositeSubs.length > 0
const isPropHighlightedD = useMemo(
() =>
whatPropIsHighlighted.getIsPropHighlightedD({
...obj.address,
pathToProp: getPointerParts(pointerToProp).path,
}),
[pointerToProp],
)
// previous versions of the DetailCompoundPropEditor had a context menu item for "Reset values". // previous versions of the DetailCompoundPropEditor had a context menu item for "Reset values".
return ( return (
<Container> <Container>
{contextMenu} {contextMenu}
<Header <Header
isHighlighted={isPropHighlightedD}
// @ts-ignore // @ts-ignore
style={{'--depth': visualIndentation - 1}} style={{'--depth': visualIndentation - 1}}
> >
@ -138,4 +154,4 @@ function DetailCompoundPropEditor<
) )
} }
export default DetailCompoundPropEditor export default React.memo(DetailCompoundPropEditor)

View file

@ -2,12 +2,14 @@ import type {
IBasePropType, IBasePropType,
PropTypeConfig_AllSimples, PropTypeConfig_AllSimples,
} from '@theatre/core/propTypes' } from '@theatre/core/propTypes'
import React from 'react' import React, {useMemo} from 'react'
import {useEditingToolsForSimplePropInDetailsPanel} from '@theatre/studio/propEditors/useEditingToolsForSimpleProp' import {useEditingToolsForSimplePropInDetailsPanel} from '@theatre/studio/propEditors/useEditingToolsForSimpleProp'
import {SingleRowPropEditor} from '@theatre/studio/panels/DetailPanel/DeterminePropEditorForDetail/SingleRowPropEditor' import {SingleRowPropEditor} from '@theatre/studio/panels/DetailPanel/DeterminePropEditorForDetail/SingleRowPropEditor'
import type {Pointer} from '@theatre/dataverse' import type {Pointer} from '@theatre/dataverse'
import {getPointerParts} from '@theatre/dataverse'
import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
import type {ISimplePropEditorReactProps} from '@theatre/studio/propEditors/simpleEditors/ISimplePropEditorReactProps' import type {ISimplePropEditorReactProps} from '@theatre/studio/propEditors/simpleEditors/ISimplePropEditorReactProps'
import {whatPropIsHighlighted} from '@theatre/studio/panels/SequenceEditorPanel/whatPropIsHighlighted'
export type IDetailSimplePropEditorProps< export type IDetailSimplePropEditorProps<
TPropTypeConfig extends IBasePropType<string, any>, TPropTypeConfig extends IBasePropType<string, any>,
@ -37,9 +39,23 @@ function DetailSimplePropEditor<
propConfig, propConfig,
) )
const isPropHighlightedD = useMemo(
() =>
whatPropIsHighlighted.getIsPropHighlightedD({
...obj.address,
pathToProp: getPointerParts(pointerToProp).path,
}),
[pointerToProp],
)
return ( return (
<SingleRowPropEditor <SingleRowPropEditor
{...{editingTools: editingTools, propConfig, pointerToProp}} {...{
editingTools: editingTools,
propConfig,
pointerToProp,
isPropHighlightedD,
}}
> >
<EditorComponent <EditorComponent
editingTools={editingTools} editingTools={editingTools}
@ -50,4 +66,4 @@ function DetailSimplePropEditor<
) )
} }
export default DetailSimplePropEditor export default React.memo(DetailSimplePropEditor)

View file

@ -1,6 +1,6 @@
import type * as propTypes from '@theatre/core/propTypes' import type * as propTypes from '@theatre/core/propTypes'
import {getPointerParts} from '@theatre/dataverse' import {getPointerParts} from '@theatre/dataverse'
import type {Pointer} from '@theatre/dataverse' import type {Pointer, IDerivation} from '@theatre/dataverse'
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
import useRefAndState from '@theatre/studio/utils/useRefAndState' import useRefAndState from '@theatre/studio/utils/useRefAndState'
import {last} from 'lodash-es' import {last} from 'lodash-es'
@ -9,10 +9,14 @@ import type {useEditingToolsForSimplePropInDetailsPanel} from '@theatre/studio/p
import styled from 'styled-components' import styled from 'styled-components'
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS' import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS'
import type {PropHighlighted} from '@theatre/studio/panels/SequenceEditorPanel/whatPropIsHighlighted'
import {deriver} from '@theatre/studio/utils/derive-utils'
import {rowIndentationFormulaCSS} from './rowIndentationFormulaCSS'
import {getDetailRowHighlightBackground} from './getDetailRowHighlightBackground'
export const indentationFormula = `calc(var(--left-pad) + var(--depth) * var(--step))` const Container = deriver(styled.div<{
isHighlighted: PropHighlighted
const Container = styled.div` }>`
display: flex; display: flex;
height: 30px; height: 30px;
justify-content: flex-start; justify-content: flex-start;
@ -28,11 +32,13 @@ const Container = styled.div`
--right-width: 60%; --right-width: 60%;
position: relative; position: relative;
${pointerEventsAutoInNormalMode}; ${pointerEventsAutoInNormalMode};
`
background-color: ${getDetailRowHighlightBackground};
`)
const Left = styled.div` const Left = styled.div`
box-sizing: border-box; box-sizing: border-box;
padding-left: ${indentationFormula}; padding-left: ${rowIndentationFormulaCSS};
padding-right: 4px; padding-right: 4px;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -84,6 +90,7 @@ type ISingleRowPropEditorProps<T> = {
propConfig: propTypes.PropTypeConfig propConfig: propTypes.PropTypeConfig
pointerToProp: Pointer<T> pointerToProp: Pointer<T>
editingTools: ReturnType<typeof useEditingToolsForSimplePropInDetailsPanel> editingTools: ReturnType<typeof useEditingToolsForSimplePropInDetailsPanel>
isPropHighlightedD: IDerivation<PropHighlighted>
} }
export function SingleRowPropEditor<T>({ export function SingleRowPropEditor<T>({
@ -91,6 +98,7 @@ export function SingleRowPropEditor<T>({
pointerToProp, pointerToProp,
editingTools, editingTools,
children, children,
isPropHighlightedD,
}: React.PropsWithChildren<ISingleRowPropEditorProps<T>>): React.ReactElement< }: React.PropsWithChildren<ISingleRowPropEditorProps<T>>): React.ReactElement<
any, any,
any any
@ -105,11 +113,10 @@ export function SingleRowPropEditor<T>({
}) })
return ( return (
<Container> <Container isHighlighted={isPropHighlightedD}>
{contextMenu} {contextMenu}
<Left> <Left>
<ControlsContainer>{editingTools.controlIndicators}</ControlsContainer> <ControlsContainer>{editingTools.controlIndicators}</ControlsContainer>
<PropNameContainer <PropNameContainer
ref={propNameContainerRef} ref={propNameContainerRef}
title={['obj', 'props', ...getPointerParts(pointerToProp).path].join( title={['obj', 'props', ...getPointerParts(pointerToProp).path].join(

View file

@ -0,0 +1,13 @@
import type {PropHighlighted} from '@theatre/studio/panels/SequenceEditorPanel/whatPropIsHighlighted'
export function getDetailRowHighlightBackground({
isHighlighted,
}: {
isHighlighted: PropHighlighted
}): string {
return isHighlighted === 'self'
? '#1857a4'
: isHighlighted === 'descendent'
? '#0a2f5c'
: 'initial'
}

View file

@ -0,0 +1 @@
export const rowIndentationFormulaCSS = `calc(var(--left-pad) + var(--depth) * var(--step))`

View file

@ -1,10 +1,15 @@
import {theme} from '@theatre/studio/css' import {theme} from '@theatre/studio/css'
import type {SequenceEditorTree_Row} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import type {
SequenceEditorTree_PrimitiveProp,
SequenceEditorTree_PropWithChildren,
SequenceEditorTree_SheetObject,
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
import type {VoidFn} from '@theatre/shared/utils/types' import type {VoidFn} from '@theatre/shared/utils/types'
import React from 'react' import React, {useRef} from 'react'
import {HiOutlineChevronRight} from 'react-icons/all' import {HiOutlineChevronRight} from 'react-icons/all'
import styled from 'styled-components' import styled from 'styled-components'
import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS' import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS'
import {usePropHighlightMouseEnter} from './usePropHighlightMouseEnter'
export const LeftRowContainer = styled.li<{depth: number}>` export const LeftRowContainer = styled.li<{depth: number}>`
--depth: ${(props) => props.depth}; --depth: ${(props) => props.depth};
@ -66,7 +71,10 @@ const LeftRowChildren = styled.ul`
` `
const AnyCompositeRow: React.FC<{ const AnyCompositeRow: React.FC<{
leaf: SequenceEditorTree_Row<string> leaf:
| SequenceEditorTree_PrimitiveProp
| SequenceEditorTree_PropWithChildren
| SequenceEditorTree_SheetObject
label: React.ReactNode label: React.ReactNode
toggleSelect?: VoidFn toggleSelect?: VoidFn
toggleCollapsed: VoidFn toggleCollapsed: VoidFn
@ -85,8 +93,12 @@ const AnyCompositeRow: React.FC<{
}) => { }) => {
const hasChildren = Array.isArray(children) && children.length > 0 const hasChildren = Array.isArray(children) && children.length > 0
const containerRef = useRef<HTMLLIElement | null>(null)
usePropHighlightMouseEnter(containerRef.current, leaf)
return leaf.shouldRender ? ( return leaf.shouldRender ? (
<LeftRowContainer depth={leaf.depth}> <LeftRowContainer depth={leaf.depth} ref={containerRef}>
<LeftRowHeader <LeftRowHeader
style={{ style={{
height: leaf.nodeHeight + 'px', height: leaf.nodeHeight + 'px',

View file

@ -13,6 +13,7 @@ import {nextPrevCursorsTheme} from '@theatre/studio/propEditors/NextPrevKeyframe
import {graphEditorColors} from '@theatre/studio/panels/SequenceEditorPanel/GraphEditor/GraphEditor' import {graphEditorColors} from '@theatre/studio/panels/SequenceEditorPanel/GraphEditor/GraphEditor'
import {BaseHeader, LeftRowContainer as BaseContainer} from './AnyCompositeRow' import {BaseHeader, LeftRowContainer as BaseContainer} from './AnyCompositeRow'
import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS' import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS'
import {usePropHighlightMouseEnter} from './usePropHighlightMouseEnter'
const theme = { const theme = {
label: { label: {
@ -131,8 +132,12 @@ const PrimitivePropRow: React.FC<{
const label = leaf.pathToProp[leaf.pathToProp.length - 1] const label = leaf.pathToProp[leaf.pathToProp.length - 1]
const isSelectable = true const isSelectable = true
const containerRef = useRef<HTMLLIElement | null>(null)
usePropHighlightMouseEnter(containerRef.current, leaf)
return ( return (
<PrimitivePropRowContainer depth={leaf.depth}> <PrimitivePropRowContainer depth={leaf.depth} ref={containerRef}>
<PrimitivePropRowHead <PrimitivePropRowHead
isEven={leaf.n % 2 === 0} isEven={leaf.n % 2 === 0}
style={{ style={{

View file

@ -2,7 +2,6 @@ import type {
SequenceEditorTree_PrimitiveProp, SequenceEditorTree_PrimitiveProp,
SequenceEditorTree_PropWithChildren, SequenceEditorTree_PropWithChildren,
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' } from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
import {usePrism} from '@theatre/react'
import React from 'react' import React from 'react'
import AnyCompositeRow from './AnyCompositeRow' import AnyCompositeRow from './AnyCompositeRow'
import PrimitivePropRow from './PrimitivePropRow' import PrimitivePropRow from './PrimitivePropRow'
@ -26,20 +25,18 @@ export const decideRowByPropType = (
const PropWithChildrenRow: React.VFC<{ const PropWithChildrenRow: React.VFC<{
leaf: SequenceEditorTree_PropWithChildren leaf: SequenceEditorTree_PropWithChildren
}> = ({leaf}) => { }> = ({leaf}) => {
return usePrism(() => { return (
return ( <AnyCompositeRow
<AnyCompositeRow leaf={leaf}
leaf={leaf} label={leaf.pathToProp[leaf.pathToProp.length - 1]}
label={leaf.pathToProp[leaf.pathToProp.length - 1]} isCollapsed={leaf.isCollapsed}
isCollapsed={leaf.isCollapsed} toggleCollapsed={() =>
toggleCollapsed={() => setCollapsedSheetObjectOrCompoundProp(!leaf.isCollapsed, leaf)
setCollapsedSheetObjectOrCompoundProp(!leaf.isCollapsed, leaf) }
} >
> {leaf.children.map((propLeaf) => decideRowByPropType(propLeaf))}
{leaf.children.map((propLeaf) => decideRowByPropType(propLeaf))} </AnyCompositeRow>
</AnyCompositeRow> )
)
}, [leaf])
} }
export default PropWithChildrenRow export default PropWithChildrenRow

View file

@ -0,0 +1,40 @@
import type {SequenceEditorTree_AllRowTypes} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
import type {PropAddress} from '@theatre/shared/utils/addresses'
import {useLayoutEffect} from 'react'
import {whatPropIsHighlighted} from '@theatre/studio/panels/SequenceEditorPanel/whatPropIsHighlighted'
/** This should ignore if */
export function usePropHighlightMouseEnter(
node: HTMLElement | null,
leaf: SequenceEditorTree_AllRowTypes,
) {
useLayoutEffect(() => {
if (!node) return
if (leaf.type !== 'propWithChildren' && leaf.type !== 'primitiveProp')
return
let unlock: null | (() => void) = null
const propAddress: PropAddress = {
...leaf.sheetObject.address,
pathToProp: leaf.pathToProp,
}
function onMouseEnter() {
unlock = whatPropIsHighlighted.replaceLock(propAddress, () => {
// cleanup on forced unlock
})
}
function onMouseLeave() {
unlock?.()
}
node.addEventListener('mouseenter', onMouseEnter)
node.addEventListener('mouseleave', onMouseLeave)
return () => {
unlock?.()
node.removeEventListener('mouseenter', onMouseEnter)
node.removeEventListener('mouseleave', onMouseLeave)
}
}, [node])
}

View file

@ -0,0 +1,96 @@
import type {IDerivation} from '@theatre/dataverse'
import {val} from '@theatre/dataverse'
import {Atom} from '@theatre/dataverse'
import {prism} from '@theatre/dataverse'
import type {
PropAddress,
WithoutSheetInstance,
} from '@theatre/shared/utils/addresses'
import pointerDeep from '@theatre/shared/utils/pointerDeep'
import type {$IntentionalAny, VoidFn} from '@theatre/shared/utils/types'
import lodashSet from 'lodash-es/set'
/** constant global manager */
export const whatPropIsHighlighted = createWhatPropIsHighlightedState()
export type PropHighlighted = 'self' | 'descendent' | null
/** Only used in prop highlighting with boolean. */
type PathToPropAsDeepObject<T extends boolean | number | string> = {
[key in string]: T | PathToPropAsDeepObject<T>
}
function createWhatPropIsHighlightedState() {
let lastLockId = 0
const whatIsHighlighted = new Atom<
| {hasLock: false; deepPath?: undefined}
| {
hasLock: true
lockId: number
cleanup: () => void
deepPath: PathToPropAsDeepObject<boolean>
}
>({hasLock: false})
return {
replaceLock(address: WithoutSheetInstance<PropAddress>, cleanup: VoidFn) {
const lockId = lastLockId++
const existingState = whatIsHighlighted.getState()
if (existingState.hasLock) existingState.cleanup()
whatIsHighlighted.setState({
hasLock: true,
lockId,
cleanup,
deepPath: arrayToDeepObject(addressToArray(address)),
})
return function unlock() {
const curr = whatIsHighlighted.getState()
if (curr.hasLock && curr.lockId === lockId) {
curr.cleanup()
whatIsHighlighted.setState({hasLock: false})
}
}
},
getIsPropHighlightedD(
address: WithoutSheetInstance<PropAddress>,
): IDerivation<PropHighlighted> {
const highlightedP = pointerDeep(
whatIsHighlighted.pointer.deepPath,
addressToArray(address),
)
return prism(() => {
const value = val(highlightedP)
return value === true
? 'self'
: // obj continues deep path prop from here
value
? 'descendent'
: // some other prop or no lock
null
})
},
}
}
function addressToArray(
address: WithoutSheetInstance<PropAddress>,
): Array<string | number> {
return [
address.projectId,
address.sheetId,
address.objectKey,
...address.pathToProp,
]
}
function arrayToDeepObject(
arr: Array<string | number>,
): PathToPropAsDeepObject<boolean> {
const obj = {}
lodashSet(obj, arr, true)
return obj as $IntentionalAny
}