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:
parent
162174568b
commit
c74aa1b930
10 changed files with 240 additions and 37 deletions
|
@ -5,9 +5,9 @@ import {getPointerParts} from '@theatre/dataverse'
|
|||
import type {Pointer} from '@theatre/dataverse'
|
||||
import last from 'lodash-es/last'
|
||||
import {darken, transparentize} from 'polished'
|
||||
import React from 'react'
|
||||
import React, {useMemo} from 'react'
|
||||
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 {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||
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 {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`
|
||||
--step: 15px;
|
||||
|
@ -23,15 +27,17 @@ const Container = styled.div`
|
|||
${pointerEventsAutoInNormalMode};
|
||||
`
|
||||
|
||||
const Header = styled.div`
|
||||
const Header = deriver(styled.div<{isHighlighted: PropHighlighted}>`
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
`
|
||||
|
||||
background-color: ${getDetailRowHighlightBackground};
|
||||
`)
|
||||
|
||||
const Padding = styled.div`
|
||||
padding-left: ${indentationFormula};
|
||||
padding-left: ${rowIndentationFormulaCSS};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
@ -99,12 +105,22 @@ function DetailCompoundPropEditor<
|
|||
|
||||
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".
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{contextMenu}
|
||||
<Header
|
||||
isHighlighted={isPropHighlightedD}
|
||||
// @ts-ignore
|
||||
style={{'--depth': visualIndentation - 1}}
|
||||
>
|
||||
|
@ -138,4 +154,4 @@ function DetailCompoundPropEditor<
|
|||
)
|
||||
}
|
||||
|
||||
export default DetailCompoundPropEditor
|
||||
export default React.memo(DetailCompoundPropEditor)
|
||||
|
|
|
@ -2,12 +2,14 @@ import type {
|
|||
IBasePropType,
|
||||
PropTypeConfig_AllSimples,
|
||||
} from '@theatre/core/propTypes'
|
||||
import React from 'react'
|
||||
import React, {useMemo} from 'react'
|
||||
import {useEditingToolsForSimplePropInDetailsPanel} from '@theatre/studio/propEditors/useEditingToolsForSimpleProp'
|
||||
import {SingleRowPropEditor} from '@theatre/studio/panels/DetailPanel/DeterminePropEditorForDetail/SingleRowPropEditor'
|
||||
import type {Pointer} from '@theatre/dataverse'
|
||||
import {getPointerParts} from '@theatre/dataverse'
|
||||
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
||||
import type {ISimplePropEditorReactProps} from '@theatre/studio/propEditors/simpleEditors/ISimplePropEditorReactProps'
|
||||
import {whatPropIsHighlighted} from '@theatre/studio/panels/SequenceEditorPanel/whatPropIsHighlighted'
|
||||
|
||||
export type IDetailSimplePropEditorProps<
|
||||
TPropTypeConfig extends IBasePropType<string, any>,
|
||||
|
@ -37,9 +39,23 @@ function DetailSimplePropEditor<
|
|||
propConfig,
|
||||
)
|
||||
|
||||
const isPropHighlightedD = useMemo(
|
||||
() =>
|
||||
whatPropIsHighlighted.getIsPropHighlightedD({
|
||||
...obj.address,
|
||||
pathToProp: getPointerParts(pointerToProp).path,
|
||||
}),
|
||||
[pointerToProp],
|
||||
)
|
||||
|
||||
return (
|
||||
<SingleRowPropEditor
|
||||
{...{editingTools: editingTools, propConfig, pointerToProp}}
|
||||
{...{
|
||||
editingTools: editingTools,
|
||||
propConfig,
|
||||
pointerToProp,
|
||||
isPropHighlightedD,
|
||||
}}
|
||||
>
|
||||
<EditorComponent
|
||||
editingTools={editingTools}
|
||||
|
@ -50,4 +66,4 @@ function DetailSimplePropEditor<
|
|||
)
|
||||
}
|
||||
|
||||
export default DetailSimplePropEditor
|
||||
export default React.memo(DetailSimplePropEditor)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type * as propTypes from '@theatre/core/propTypes'
|
||||
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 useRefAndState from '@theatre/studio/utils/useRefAndState'
|
||||
import {last} from 'lodash-es'
|
||||
|
@ -9,10 +9,14 @@ import type {useEditingToolsForSimplePropInDetailsPanel} from '@theatre/studio/p
|
|||
import styled from 'styled-components'
|
||||
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||
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 = styled.div`
|
||||
const Container = deriver(styled.div<{
|
||||
isHighlighted: PropHighlighted
|
||||
}>`
|
||||
display: flex;
|
||||
height: 30px;
|
||||
justify-content: flex-start;
|
||||
|
@ -28,11 +32,13 @@ const Container = styled.div`
|
|||
--right-width: 60%;
|
||||
position: relative;
|
||||
${pointerEventsAutoInNormalMode};
|
||||
`
|
||||
|
||||
background-color: ${getDetailRowHighlightBackground};
|
||||
`)
|
||||
|
||||
const Left = styled.div`
|
||||
box-sizing: border-box;
|
||||
padding-left: ${indentationFormula};
|
||||
padding-left: ${rowIndentationFormulaCSS};
|
||||
padding-right: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -84,6 +90,7 @@ type ISingleRowPropEditorProps<T> = {
|
|||
propConfig: propTypes.PropTypeConfig
|
||||
pointerToProp: Pointer<T>
|
||||
editingTools: ReturnType<typeof useEditingToolsForSimplePropInDetailsPanel>
|
||||
isPropHighlightedD: IDerivation<PropHighlighted>
|
||||
}
|
||||
|
||||
export function SingleRowPropEditor<T>({
|
||||
|
@ -91,6 +98,7 @@ export function SingleRowPropEditor<T>({
|
|||
pointerToProp,
|
||||
editingTools,
|
||||
children,
|
||||
isPropHighlightedD,
|
||||
}: React.PropsWithChildren<ISingleRowPropEditorProps<T>>): React.ReactElement<
|
||||
any,
|
||||
any
|
||||
|
@ -105,11 +113,10 @@ export function SingleRowPropEditor<T>({
|
|||
})
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Container isHighlighted={isPropHighlightedD}>
|
||||
{contextMenu}
|
||||
<Left>
|
||||
<ControlsContainer>{editingTools.controlIndicators}</ControlsContainer>
|
||||
|
||||
<PropNameContainer
|
||||
ref={propNameContainerRef}
|
||||
title={['obj', 'props', ...getPointerParts(pointerToProp).path].join(
|
||||
|
|
|
@ -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'
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export const rowIndentationFormulaCSS = `calc(var(--left-pad) + var(--depth) * var(--step))`
|
|
@ -1,10 +1,15 @@
|
|||
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 React from 'react'
|
||||
import React, {useRef} from 'react'
|
||||
import {HiOutlineChevronRight} from 'react-icons/all'
|
||||
import styled from 'styled-components'
|
||||
import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS'
|
||||
import {usePropHighlightMouseEnter} from './usePropHighlightMouseEnter'
|
||||
|
||||
export const LeftRowContainer = styled.li<{depth: number}>`
|
||||
--depth: ${(props) => props.depth};
|
||||
|
@ -66,7 +71,10 @@ const LeftRowChildren = styled.ul`
|
|||
`
|
||||
|
||||
const AnyCompositeRow: React.FC<{
|
||||
leaf: SequenceEditorTree_Row<string>
|
||||
leaf:
|
||||
| SequenceEditorTree_PrimitiveProp
|
||||
| SequenceEditorTree_PropWithChildren
|
||||
| SequenceEditorTree_SheetObject
|
||||
label: React.ReactNode
|
||||
toggleSelect?: VoidFn
|
||||
toggleCollapsed: VoidFn
|
||||
|
@ -85,8 +93,12 @@ const AnyCompositeRow: React.FC<{
|
|||
}) => {
|
||||
const hasChildren = Array.isArray(children) && children.length > 0
|
||||
|
||||
const containerRef = useRef<HTMLLIElement | null>(null)
|
||||
|
||||
usePropHighlightMouseEnter(containerRef.current, leaf)
|
||||
|
||||
return leaf.shouldRender ? (
|
||||
<LeftRowContainer depth={leaf.depth}>
|
||||
<LeftRowContainer depth={leaf.depth} ref={containerRef}>
|
||||
<LeftRowHeader
|
||||
style={{
|
||||
height: leaf.nodeHeight + 'px',
|
||||
|
|
|
@ -13,6 +13,7 @@ import {nextPrevCursorsTheme} from '@theatre/studio/propEditors/NextPrevKeyframe
|
|||
import {graphEditorColors} from '@theatre/studio/panels/SequenceEditorPanel/GraphEditor/GraphEditor'
|
||||
import {BaseHeader, LeftRowContainer as BaseContainer} from './AnyCompositeRow'
|
||||
import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS'
|
||||
import {usePropHighlightMouseEnter} from './usePropHighlightMouseEnter'
|
||||
|
||||
const theme = {
|
||||
label: {
|
||||
|
@ -131,8 +132,12 @@ const PrimitivePropRow: React.FC<{
|
|||
const label = leaf.pathToProp[leaf.pathToProp.length - 1]
|
||||
const isSelectable = true
|
||||
|
||||
const containerRef = useRef<HTMLLIElement | null>(null)
|
||||
|
||||
usePropHighlightMouseEnter(containerRef.current, leaf)
|
||||
|
||||
return (
|
||||
<PrimitivePropRowContainer depth={leaf.depth}>
|
||||
<PrimitivePropRowContainer depth={leaf.depth} ref={containerRef}>
|
||||
<PrimitivePropRowHead
|
||||
isEven={leaf.n % 2 === 0}
|
||||
style={{
|
||||
|
|
|
@ -2,7 +2,6 @@ import type {
|
|||
SequenceEditorTree_PrimitiveProp,
|
||||
SequenceEditorTree_PropWithChildren,
|
||||
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
||||
import {usePrism} from '@theatre/react'
|
||||
import React from 'react'
|
||||
import AnyCompositeRow from './AnyCompositeRow'
|
||||
import PrimitivePropRow from './PrimitivePropRow'
|
||||
|
@ -26,20 +25,18 @@ export const decideRowByPropType = (
|
|||
const PropWithChildrenRow: React.VFC<{
|
||||
leaf: SequenceEditorTree_PropWithChildren
|
||||
}> = ({leaf}) => {
|
||||
return usePrism(() => {
|
||||
return (
|
||||
<AnyCompositeRow
|
||||
leaf={leaf}
|
||||
label={leaf.pathToProp[leaf.pathToProp.length - 1]}
|
||||
isCollapsed={leaf.isCollapsed}
|
||||
toggleCollapsed={() =>
|
||||
setCollapsedSheetObjectOrCompoundProp(!leaf.isCollapsed, leaf)
|
||||
}
|
||||
>
|
||||
{leaf.children.map((propLeaf) => decideRowByPropType(propLeaf))}
|
||||
</AnyCompositeRow>
|
||||
)
|
||||
}, [leaf])
|
||||
return (
|
||||
<AnyCompositeRow
|
||||
leaf={leaf}
|
||||
label={leaf.pathToProp[leaf.pathToProp.length - 1]}
|
||||
isCollapsed={leaf.isCollapsed}
|
||||
toggleCollapsed={() =>
|
||||
setCollapsedSheetObjectOrCompoundProp(!leaf.isCollapsed, leaf)
|
||||
}
|
||||
>
|
||||
{leaf.children.map((propLeaf) => decideRowByPropType(propLeaf))}
|
||||
</AnyCompositeRow>
|
||||
)
|
||||
}
|
||||
|
||||
export default PropWithChildrenRow
|
||||
|
|
|
@ -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])
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in a new issue