WIP aggregate editing popover working but !clean

This commit is contained in:
vezwork 2022-06-30 10:46:24 -04:00 committed by Elliot
parent 069902e054
commit 38e6a4ba36
7 changed files with 194 additions and 89 deletions

View file

@ -17,18 +17,86 @@ import type {KeyframeWithPathToPropFromCommonRoot} from '@theatre/studio/store/t
import {commonRootOfPathsToProps} from '@theatre/shared/utils/addresses'
import type {ILogger} from '@theatre/shared/logger'
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
import type {EditingOptionsTree} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/useSingleKeyframeInlineEditorPopover'
import {useKeyframeInlineEditorPopover} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/useSingleKeyframeInlineEditorPopover'
import type {
SequenceEditorTree_PrimitiveProp,
SequenceEditorTree_PropWithChildren,
SequenceEditorTree_SheetObject,
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
import type {KeyframeWithTrack} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes'
type IAggregateKeyframeDotProps = {
editorProps: IAggregateKeyframeEditorProps
utils: IAggregateKeyframeEditorUtils
}
function sheetObjectBuild(
viewModel: SequenceEditorTree_SheetObject,
keyframes: KeyframeWithTrack[],
): EditingOptionsTree | null {
const children = viewModel.children
.map((a) =>
a.type === 'propWithChildren'
? propWithChildrenBuild(a, keyframes)
: primitivePropBuild(a, keyframes),
)
.filter((a): a is EditingOptionsTree => a !== null)
if (children.length === 0) return null
return {
type: 'sheetObject',
sheetObject: viewModel.sheetObject,
children,
}
}
function propWithChildrenBuild(
viewModel: SequenceEditorTree_PropWithChildren,
keyframes: KeyframeWithTrack[],
): EditingOptionsTree | null {
const children = viewModel.children
.map((a) =>
a.type === 'propWithChildren'
? propWithChildrenBuild(a, keyframes)
: primitivePropBuild(a, keyframes),
)
.filter((a): a is EditingOptionsTree => a !== null)
if (children.length === 0) return null
return {
type: 'propWithChildren',
pathToProp: viewModel.pathToProp,
propConfig: viewModel.propConf,
children,
}
}
function primitivePropBuild(
viewModelLeaf: SequenceEditorTree_PrimitiveProp,
keyframes: KeyframeWithTrack[],
): EditingOptionsTree | null {
const keyframe = keyframes.find((kf) => kf.track.id === viewModelLeaf.trackId)
if (!keyframe) return null
return {
type: 'primitiveProp',
keyframe: keyframe.kf,
pathToProp: viewModelLeaf.pathToProp,
propConfig: viewModelLeaf.propConf,
sheetObject: viewModelLeaf.sheetObject,
trackId: viewModelLeaf.trackId,
}
}
export function AggregateKeyframeDot(
props: React.PropsWithChildren<IAggregateKeyframeDotProps>,
) {
const logger = useLogger('AggregateKeyframeDot')
const {cur} = props.utils
const [inlineEditorPopover, openEditor, _, isInlineEditorPopoverOpen] =
useKeyframeInlineEditorPopover(
props.editorProps.viewModel.type === 'sheetObject'
? sheetObjectBuild(props.editorProps.viewModel, cur.keyframes)
: propWithChildrenBuild(props.editorProps.viewModel, cur.keyframes),
)
const presence = usePresence(props.utils.itemKey)
presence.useRelations(
() =>
@ -57,6 +125,7 @@ export function AggregateKeyframeDot(
// Need this for the dragging logic to be able to get the keyframe props
// based on the position.
{...DopeSnap.includePositionSnapAttrs(cur.position)}
onClick={(e) => openEditor(e, ref.current!)}
/>
<AggregateKeyframeVisualDot
flag={presence.flag}
@ -64,6 +133,7 @@ export function AggregateKeyframeDot(
isSelected={cur.selected}
/>
{contextMenu}
{inlineEditorPopover}
</>
)
}

View file

@ -1,28 +1,19 @@
import React from 'react'
import styled from 'styled-components'
import type {
PropTypeConfig,
PropTypeConfig_AllSimples,
} from '@theatre/core/propTypes'
import type {IEditingTools} from '@theatre/studio/propEditors/utils/IEditingTools'
import type {PropConfigForType} from '@theatre/studio/propEditors/utils/PropConfigForType'
import type {PropTypeConfig_AllSimples} from '@theatre/core/propTypes'
import type {ISimplePropEditorReactProps} from '@theatre/studio/propEditors/simpleEditors/ISimplePropEditorReactProps'
import {simplePropEditorByPropType} from '@theatre/studio/propEditors/simpleEditors/simplePropEditorByPropType'
import SingleKeyframeSimplePropEditor from './DeterminePropEditorForSingleKeyframe/SingleKeyframeSimplePropEditor'
type IDeterminePropEditorForSingleKeyframeProps<
K extends PropTypeConfig['type'],
> = {
editingTools: IEditingTools<PropConfigForType<K>['valueType']>
propConfig: PropConfigForType<K>
keyframeValue: PropConfigForType<K>['valueType']
displayLabel?: string
}
import type {
EditingOptionsTree,
PrimitivePropEditingOptions,
} from './useSingleKeyframeInlineEditorPopover'
import last from 'lodash-es/last'
import {useTempTransactionEditingTools} from './useTempTransactionEditingTools'
const SingleKeyframePropEditorContainer = styled.div`
padding: 2px;
display: flex;
align-items: stretch;
@ -30,7 +21,7 @@ const SingleKeyframePropEditorContainer = styled.div`
min-width: 100px;
}
`
const SingleKeyframePropLabel = styled.span`
const SingleKeyframePropLabel = styled.div`
font-style: normal;
font-weight: 400;
font-size: 11px;
@ -42,6 +33,10 @@ const SingleKeyframePropLabel = styled.span`
color: #919191;
`
const IndentedThing = styled.div`
margin-left: 24px;
`
/**
* Given a propConfig, this function gives the corresponding prop editor for
* use in the dope sheet inline prop editor on a keyframe.
@ -52,35 +47,73 @@ const SingleKeyframePropLabel = styled.span`
*
* @param p - propConfig object for any type of prop.
*/
export function DeterminePropEditorForSingleKeyframe(
p: IDeterminePropEditorForSingleKeyframeProps<PropTypeConfig['type']>,
) {
const propConfig = p.propConfig
if (propConfig.type === 'compound') {
throw new Error(
'We do not yet support editing compound props for a keyframe',
export function DeterminePropEditorForKeyframeTree(p: EditingOptionsTree) {
if (p.type === 'sheetObject') {
return (
<>
<SingleKeyframePropLabel>
{p.sheetObject.address.objectKey}
</SingleKeyframePropLabel>
<IndentedThing>
{p.children.map((c, i) => (
<DeterminePropEditorForKeyframeTree key={i} {...c} />
))}
</IndentedThing>
</>
)
} else if (propConfig.type === 'enum') {
} else if (p.type === 'propWithChildren') {
const label = p.propConfig.label ?? last(p.pathToProp)
return (
<>
<SingleKeyframePropLabel>{label}</SingleKeyframePropLabel>
<IndentedThing>
{p.children.map((c, i) => (
<DeterminePropEditorForKeyframeTree key={i} {...c} />
))}
</IndentedThing>
</>
)
} else {
return <BeepBoop {...p} />
}
}
function BeepBoop(p: PrimitivePropEditingOptions) {
const label = p.propConfig.label ?? last(p.pathToProp)
const editingTools = useEditingToolsForKeyframeEditorPopover(p)
if (p.propConfig.type === 'enum') {
// notice: enums are not implemented, yet.
return <></>
} else {
const PropEditor = simplePropEditorByPropType[propConfig.type]
const PropEditor = simplePropEditorByPropType[
p.propConfig.type
] as React.VFC<ISimplePropEditorReactProps<PropTypeConfig_AllSimples>>
return (
<SingleKeyframePropEditorContainer>
<SingleKeyframePropLabel>{p.displayLabel}</SingleKeyframePropLabel>
<SingleKeyframePropLabel>{label}</SingleKeyframePropLabel>
<SingleKeyframeSimplePropEditor
SimpleEditorComponent={
PropEditor as React.VFC<
ISimplePropEditorReactProps<PropTypeConfig_AllSimples>
>
}
propConfig={propConfig}
editingTools={p.editingTools}
keyframeValue={p.keyframeValue}
SimpleEditorComponent={PropEditor}
propConfig={p.propConfig}
editingTools={editingTools}
keyframeValue={p.keyframe.value}
/>
</SingleKeyframePropEditorContainer>
)
}
}
function useEditingToolsForKeyframeEditorPopover(
props: PrimitivePropEditingOptions,
) {
const obj = props.sheetObject
return useTempTransactionEditingTools(({stateEditors}, value) => {
const newKeyframe = {...props.keyframe, value}
stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes({
...obj.address,
trackId: props.trackId,
keyframes: [newKeyframe],
snappingFunction: obj.sheet.getSequence().closestGridPosition,
})
})
}

View file

@ -23,7 +23,7 @@ import {
snapToNone,
snapToSome,
} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget'
import {useSingleKeyframeInlineEditorPopover} from './useSingleKeyframeInlineEditorPopover'
import {useKeyframeInlineEditorPopover} from './useSingleKeyframeInlineEditorPopover'
import usePresence, {
PresenceFlag,
} from '@theatre/studio/uiComponents/usePresence'
@ -95,22 +95,21 @@ const HitZone = styled.div<{isInlineEditorPopoverOpen: boolean}>`
type ISingleKeyframeDotProps = ISingleKeyframeEditorProps
/** The ◆ you can grab onto in "keyframe editor" (aka "dope sheet" in other programs) */
const SingleKeyframeDot: React.VFC<ISingleKeyframeDotProps> = React.memo(
(props) => {
const SingleKeyframeDot: React.VFC<ISingleKeyframeDotProps> = (props) => {
const logger = useLogger('SingleKeyframeDot', props.keyframe.id)
const presence = usePresence(props.itemKey)
const [ref, node] = useRefAndState<HTMLDivElement | null>(null)
const [contextMenu] = useSingleKeyframeContextMenu(node, logger, props)
const [inlineEditorPopover, openEditor, _, isInlineEditorPopoverOpen] =
useSingleKeyframeInlineEditorPopover({
useKeyframeInlineEditorPopover({
type: 'primitiveProp',
keyframe: props.keyframe,
pathToProp: props.leaf.pathToProp,
propConf: props.leaf.propConf,
propConfig: props.leaf.propConf,
sheetObject: props.leaf.sheetObject,
trackId: props.leaf.trackId,
})
const [isDragging] = useDragForSingleKeyframeDot(node, props, {
onClickFromDrag(dragStartEvent) {
openEditor(dragStartEvent, ref.current!)

View file

@ -1,53 +1,51 @@
import React from 'react'
import last from 'lodash-es/last'
import usePopover from '@theatre/studio/uiComponents/Popover/usePopover'
import BasicPopover from '@theatre/studio/uiComponents/Popover/BasicPopover'
import {useTempTransactionEditingTools} from './useTempTransactionEditingTools'
import {DeterminePropEditorForSingleKeyframe} from './DeterminePropEditorForSingleKeyframe'
import {DeterminePropEditorForKeyframeTree} from './DeterminePropEditorForSingleKeyframe'
import type {SequenceTrackId} from '@theatre/shared/utils/ids'
import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
import type {PropTypeConfig} from '@theatre/core/propTypes'
import type {
PropTypeConfig_AllSimples,
PropTypeConfig_Compound,
PropTypeConfig_Enum,
} from '@theatre/core/propTypes'
import type {PathToProp} from '@theatre/shared/utils/addresses'
import type {UnknownValidCompoundProps} from '@theatre/core/propTypes/internals'
/** The editor that pops up when directly clicking a Keyframe. */
export function useSingleKeyframeInlineEditorPopover(
props: SingleKeyframeEditingOptions,
export function useKeyframeInlineEditorPopover(
props: EditingOptionsTree | null,
) {
const editingTools = useEditingToolsForKeyframeEditorPopover(props)
const label = props.propConf.label ?? last(props.pathToProp)
return usePopover({debugName: 'useKeyframeInlineEditorPopover'}, () => (
<BasicPopover showPopoverEdgeTriangle>
<DeterminePropEditorForSingleKeyframe
propConfig={props.propConf}
editingTools={editingTools}
keyframeValue={props.keyframe.value}
displayLabel={label != null ? String(label) : undefined}
/>
{props === null ? undefined : (
<DeterminePropEditorForKeyframeTree {...props} />
)}
</BasicPopover>
))
}
type SingleKeyframeEditingOptions = {
export type EditingOptionsTree =
| SheetObjectEditingOptionsTree
| PropWithChildrenEditingOptionsTree
| PrimitivePropEditingOptions
type SheetObjectEditingOptionsTree = {
type: 'sheetObject'
sheetObject: SheetObject
children: EditingOptionsTree[]
}
type PropWithChildrenEditingOptionsTree = {
type: 'propWithChildren'
propConfig: PropTypeConfig_Compound<UnknownValidCompoundProps>
pathToProp: PathToProp
children: EditingOptionsTree[]
}
export type PrimitivePropEditingOptions = {
type: 'primitiveProp'
keyframe: Keyframe
propConf: PropTypeConfig
propConfig: PropTypeConfig_AllSimples | PropTypeConfig_Enum // note: enums are not implemented yet
sheetObject: SheetObject
trackId: SequenceTrackId
pathToProp: PathToProp
}
function useEditingToolsForKeyframeEditorPopover(
props: SingleKeyframeEditingOptions,
) {
const obj = props.sheetObject
return useTempTransactionEditingTools(({stateEditors}, value) => {
const newKeyframe = {...props.keyframe, value}
stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes({
...obj.address,
trackId: props.trackId,
keyframes: [newKeyframe],
snappingFunction: obj.sheet.getSequence().closestGridPosition,
})
})
}

View file

@ -16,7 +16,7 @@ import {
useCssCursorLock,
} from '@theatre/studio/uiComponents/PointerEventsHandler'
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
import {useSingleKeyframeInlineEditorPopover} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/useSingleKeyframeInlineEditorPopover'
import {useKeyframeInlineEditorPopover} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/useSingleKeyframeInlineEditorPopover'
import usePresence, {
PresenceFlag,
} from '@theatre/studio/uiComponents/usePresence'
@ -71,7 +71,7 @@ const GraphEditorDotNonScalar: React.VFC<IProps> = (props) => {
const curValue = props.which === 'left' ? 0 : 1
const [inlineEditorPopover, openEditor, _, _isInlineEditorPopoverOpen] =
useSingleKeyframeInlineEditorPopover({
useKeyframeInlineEditorPopover({
keyframe: props.keyframe,
pathToProp: props.pathToProp,
propConf: props.propConfig,

View file

@ -16,7 +16,7 @@ import {
useCssCursorLock,
} from '@theatre/studio/uiComponents/PointerEventsHandler'
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
import {useSingleKeyframeInlineEditorPopover} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/useSingleKeyframeInlineEditorPopover'
import {useKeyframeInlineEditorPopover} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/useSingleKeyframeInlineEditorPopover'
import usePresence, {
PresenceFlag,
} from '@theatre/studio/uiComponents/usePresence'
@ -71,7 +71,7 @@ const GraphEditorDotScalar: React.VFC<IProps> = (props) => {
const cyInExtremumSpace = props.extremumSpace.fromValueSpace(curValue)
const [inlineEditorPopover, openEditor, _, _isInlineEditorPopoverOpen] =
useSingleKeyframeInlineEditorPopover({
useKeyframeInlineEditorPopover({
keyframe: props.keyframe,
pathToProp: props.pathToProp,
propConf: props.propConfig,

View file

@ -18,6 +18,7 @@ import {prism, val, valueDerivation} from '@theatre/dataverse'
import logger from '@theatre/shared/logger'
import {titleBarHeight} from '@theatre/studio/panels/BasePanel/common'
import type {Studio} from '@theatre/studio/Studio'
import type {UnknownValidCompoundProps} from '@theatre/core/propTypes/internals'
/**
* Base "view model" for each row with common
@ -73,6 +74,7 @@ export type SequenceEditorTree_PropWithChildren =
SequenceEditorTree_Row<'propWithChildren'> & {
isCollapsed: boolean
sheetObject: SheetObject
propConf: PropTypeConfig_Compound<UnknownValidCompoundProps>
pathToProp: PathToProp
children: Array<
SequenceEditorTree_PropWithChildren | SequenceEditorTree_PrimitiveProp
@ -233,6 +235,7 @@ export const calculateSequenceEditorTree = (
addProp_compound(
sheetObject,
trackMapping,
conf,
pathToProp,
conf,
arrayOfChildren,
@ -259,6 +262,7 @@ export const calculateSequenceEditorTree = (
function addProp_compound(
sheetObject: SheetObject,
trackMapping: IPropPathToTrackIdTree,
propConf: PropTypeConfig_Compound<UnknownValidCompoundProps>,
pathToProp: PathToProp,
conf: PropTypeConfig_Compound<$FixMe>,
arrayOfChildren: Array<
@ -276,6 +280,7 @@ export const calculateSequenceEditorTree = (
const row: SequenceEditorTree_PropWithChildren = {
type: 'propWithChildren',
isCollapsed,
propConf,
pathToProp,
sheetItemKey: createStudioSheetItemKey.forSheetObjectProp(
sheetObject,