Add Sheet aggregate track (#284)
* Add Sheet aggregate track * Update aggregate track keyframe copy algorithm * Fix keyframe value sanitization * Fix aggregate selections to be properly undefined * Fix TS errors * Remove incorrect comment and improve var name
This commit is contained in:
parent
743254a6c6
commit
39eb528af4
25 changed files with 491 additions and 300 deletions
|
@ -3,6 +3,7 @@ import type {PathToProp} from './addresses'
|
||||||
import stableValueHash from './stableJsonStringify'
|
import stableValueHash from './stableJsonStringify'
|
||||||
import {nanoid as generateNonSecure} from 'nanoid/non-secure'
|
import {nanoid as generateNonSecure} from 'nanoid/non-secure'
|
||||||
import type {Nominal} from './Nominal'
|
import type {Nominal} from './Nominal'
|
||||||
|
import type Sheet from '@theatre/core/sheets/Sheet'
|
||||||
|
|
||||||
export type KeyframeId = Nominal<'KeyframeId'>
|
export type KeyframeId = Nominal<'KeyframeId'>
|
||||||
|
|
||||||
|
@ -106,6 +107,12 @@ export const createStudioSheetItemKey = {
|
||||||
position,
|
position,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
forSheetAggregateKeyframe(obj: Sheet, position: number): StudioSheetItemKey {
|
||||||
|
return stableValueHash({
|
||||||
|
o: obj.address.sheetId,
|
||||||
|
pos: position,
|
||||||
|
}) as StudioSheetItemKey
|
||||||
|
},
|
||||||
forCompoundPropAggregateKeyframe(
|
forCompoundPropAggregateKeyframe(
|
||||||
obj: SheetObject,
|
obj: SheetObject,
|
||||||
pathToProp: PathToProp,
|
pathToProp: PathToProp,
|
||||||
|
|
|
@ -1,14 +1,30 @@
|
||||||
## The keyframe copy/paste algorithm
|
## The keyframe copy/paste algorithm
|
||||||
|
|
||||||
```
|
The copy and paste algorithms are specified below. Note that the copy algorithm
|
||||||
copy algorithm: find the closest common acnestor for the tracks selected
|
is written with some capital letters to emphasize organization, its not an
|
||||||
|
actual language or anything. The copy algorithm changed recently and the
|
||||||
|
examples are more up-to-date than the paste algorithm because pasting stayed the
|
||||||
|
same.
|
||||||
|
|
||||||
- obj1.props.transform.position.x => simple
|
```
|
||||||
|
ALGORITHM copy:
|
||||||
|
|
||||||
|
LET PATH =
|
||||||
|
CASE copy selection / single track THEN the path relative to the closest common ancestor for the tracks selected
|
||||||
|
CASE copy aggregate track THEN the path relative the aggregate track compoundProp/sheetObject/sheet
|
||||||
|
|
||||||
|
FOR EXAMPLE CASE copy selection / single track:
|
||||||
|
- obj1.props.transform.position.x => x
|
||||||
- obj1.props.transform.position.{x, z} => {x, z}
|
- obj1.props.transform.position.{x, z} => {x, z}
|
||||||
- obj1.props.transform.position.{x, z} + obj1.props.transform.rotation.z =>
|
- obj1.props.transform.position.{x, z} + obj1.props.transform.rotation.z =>
|
||||||
{position: {x, z}, rotation: {z}}
|
{position: {x, z}, rotation: {z}}
|
||||||
|
|
||||||
paste:
|
FOR EXAMPLE CASE copy aggregate track:
|
||||||
|
- sheet.obj1.props.transform.position => {x, y, z}
|
||||||
|
- sheet.obj1.props.transform => {position: {x, y, z}, rotation: {x, y, z}}
|
||||||
|
- sheet => { obj1: { props: { transform: {position: {x, y, z}, rotation: {x, y, z}}}}}
|
||||||
|
|
||||||
|
ALGORITHM: paste:
|
||||||
|
|
||||||
- simple => simple => 1-1
|
- simple => simple => 1-1
|
||||||
- simple => {x, y} => {x: simple, y: simple} (distribute to all)
|
- simple => {x, y} => {x: simple, y: simple} (distribute to all)
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {theme} from '@theatre/studio/css'
|
||||||
import type {
|
import type {
|
||||||
SequenceEditorTree_PrimitiveProp,
|
SequenceEditorTree_PrimitiveProp,
|
||||||
SequenceEditorTree_PropWithChildren,
|
SequenceEditorTree_PropWithChildren,
|
||||||
|
SequenceEditorTree_Sheet,
|
||||||
SequenceEditorTree_SheetObject,
|
SequenceEditorTree_SheetObject,
|
||||||
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
||||||
import type {VoidFn} from '@theatre/shared/utils/types'
|
import type {VoidFn} from '@theatre/shared/utils/types'
|
||||||
|
@ -76,6 +77,7 @@ const LeftRowChildren = styled.ul`
|
||||||
|
|
||||||
const AnyCompositeRow: React.FC<{
|
const AnyCompositeRow: React.FC<{
|
||||||
leaf:
|
leaf:
|
||||||
|
| SequenceEditorTree_Sheet
|
||||||
| SequenceEditorTree_PrimitiveProp
|
| SequenceEditorTree_PrimitiveProp
|
||||||
| SequenceEditorTree_PropWithChildren
|
| SequenceEditorTree_PropWithChildren
|
||||||
| SequenceEditorTree_SheetObject
|
| SequenceEditorTree_SheetObject
|
||||||
|
|
|
@ -5,7 +5,7 @@ import type {
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import AnyCompositeRow from './AnyCompositeRow'
|
import AnyCompositeRow from './AnyCompositeRow'
|
||||||
import PrimitivePropRow from './PrimitivePropRow'
|
import PrimitivePropRow from './PrimitivePropRow'
|
||||||
import {setCollapsedSheetObjectOrCompoundProp} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp'
|
import {setCollapsedSheetItem} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp'
|
||||||
|
|
||||||
export const decideRowByPropType = (
|
export const decideRowByPropType = (
|
||||||
leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_PrimitiveProp,
|
leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_PrimitiveProp,
|
||||||
|
@ -31,7 +31,7 @@ const PropWithChildrenRow: React.VFC<{
|
||||||
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, {
|
setCollapsedSheetItem(!leaf.isCollapsed, {
|
||||||
sheetAddress: leaf.sheetObject.address,
|
sheetAddress: leaf.sheetObject.address,
|
||||||
sheetItemKey: leaf.sheetItemKey,
|
sheetItemKey: leaf.sheetItemKey,
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type {SequenceEditorTree_SheetObject} from '@theatre/studio/panels/Sequen
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import AnyCompositeRow from './AnyCompositeRow'
|
import AnyCompositeRow from './AnyCompositeRow'
|
||||||
import {decideRowByPropType} from './PropWithChildrenRow'
|
import {decideRowByPropType} from './PropWithChildrenRow'
|
||||||
import {setCollapsedSheetObjectOrCompoundProp} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp'
|
import {setCollapsedSheetItem} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp'
|
||||||
import getStudio from '@theatre/studio/getStudio'
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
|
|
||||||
const LeftSheetObjectRow: React.VFC<{
|
const LeftSheetObjectRow: React.VFC<{
|
||||||
|
@ -22,7 +22,7 @@ const LeftSheetObjectRow: React.VFC<{
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
toggleCollapsed={() =>
|
toggleCollapsed={() =>
|
||||||
setCollapsedSheetObjectOrCompoundProp(!leaf.isCollapsed, {
|
setCollapsedSheetItem(!leaf.isCollapsed, {
|
||||||
sheetAddress: leaf.sheetObject.address,
|
sheetAddress: leaf.sheetObject.address,
|
||||||
sheetItemKey: leaf.sheetItemKey,
|
sheetItemKey: leaf.sheetItemKey,
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,20 +2,32 @@ import type {SequenceEditorTree_Sheet} from '@theatre/studio/panels/SequenceEdit
|
||||||
import {usePrism} from '@theatre/react'
|
import {usePrism} from '@theatre/react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import LeftSheetObjectRow from './SheetObjectRow'
|
import LeftSheetObjectRow from './SheetObjectRow'
|
||||||
|
import AnyCompositeRow from './AnyCompositeRow'
|
||||||
|
import {setCollapsedSheetItem} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp'
|
||||||
|
|
||||||
const SheetRow: React.VFC<{
|
const SheetRow: React.VFC<{
|
||||||
leaf: SequenceEditorTree_Sheet
|
leaf: SequenceEditorTree_Sheet
|
||||||
}> = ({leaf}) => {
|
}> = ({leaf}) => {
|
||||||
return usePrism(() => {
|
return usePrism(() => {
|
||||||
return (
|
return (
|
||||||
<>
|
<AnyCompositeRow
|
||||||
|
leaf={leaf}
|
||||||
|
label={leaf.sheet.address.sheetId}
|
||||||
|
isCollapsed={leaf.isCollapsed}
|
||||||
|
toggleCollapsed={() => {
|
||||||
|
setCollapsedSheetItem(!leaf.isCollapsed, {
|
||||||
|
sheetAddress: leaf.sheet.address,
|
||||||
|
sheetItemKey: leaf.sheetItemKey,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
{leaf.children.map((sheetObjectLeaf) => (
|
{leaf.children.map((sheetObjectLeaf) => (
|
||||||
<LeftSheetObjectRow
|
<LeftSheetObjectRow
|
||||||
key={'sheetObject-' + sheetObjectLeaf.sheetObject.address.objectKey}
|
key={'sheetObject-' + sheetObjectLeaf.sheetObject.address.objectKey}
|
||||||
leaf={sheetObjectLeaf}
|
leaf={sheetObjectLeaf}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</AnyCompositeRow>
|
||||||
)
|
)
|
||||||
}, [leaf])
|
}, [leaf])
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,16 +124,20 @@ function useDragKeyframe(
|
||||||
|
|
||||||
const keyframes = props.aggregateKeyframes[props.index].keyframes
|
const keyframes = props.aggregateKeyframes[props.index].keyframes
|
||||||
|
|
||||||
|
const {selection, viewModel} = props
|
||||||
|
const address =
|
||||||
|
viewModel.type === 'sheet'
|
||||||
|
? viewModel.sheet.address
|
||||||
|
: viewModel.sheetObject.address
|
||||||
|
|
||||||
if (
|
if (
|
||||||
props.selection &&
|
selection &&
|
||||||
props.aggregateKeyframes[props.index].selected ===
|
props.aggregateKeyframes[props.index].selected ===
|
||||||
AggregateKeyframePositionIsSelected.AllSelected
|
AggregateKeyframePositionIsSelected.AllSelected
|
||||||
) {
|
) {
|
||||||
const {selection, viewModel} = props
|
|
||||||
const {sheetObject} = viewModel
|
|
||||||
return selection
|
return selection
|
||||||
.getDragHandlers({
|
.getDragHandlers({
|
||||||
...sheetObject.address,
|
...address,
|
||||||
domNode: node!,
|
domNode: node!,
|
||||||
positionAtStartOfDrag:
|
positionAtStartOfDrag:
|
||||||
props.aggregateKeyframes[props.index].position,
|
props.aggregateKeyframes[props.index].position,
|
||||||
|
@ -159,7 +163,7 @@ function useDragKeyframe(
|
||||||
for (const keyframe of keyframes) {
|
for (const keyframe of keyframes) {
|
||||||
stateEditors.coreByProject.historic.sheetsById.sequence.transformKeyframes(
|
stateEditors.coreByProject.historic.sheetsById.sequence.transformKeyframes(
|
||||||
{
|
{
|
||||||
...propsAtStartOfDrag.viewModel.sheetObject.address,
|
...keyframe.track.sheetObject.address,
|
||||||
trackId: keyframe.track.id,
|
trackId: keyframe.track.id,
|
||||||
keyframeIds: [
|
keyframeIds: [
|
||||||
keyframe.kf.id,
|
keyframe.kf.id,
|
||||||
|
@ -209,8 +213,7 @@ function useConnectorContextMenu(
|
||||||
(acc, con) =>
|
(acc, con) =>
|
||||||
acc.concat(
|
acc.concat(
|
||||||
keyframesWithPaths({
|
keyframesWithPaths({
|
||||||
...props.editorProps.viewModel.sheetObject.address,
|
...con,
|
||||||
trackId: con.trackId,
|
|
||||||
keyframeIds: [con.left.id, con.right.id],
|
keyframeIds: [con.left.id, con.right.id],
|
||||||
}) ?? [],
|
}) ?? [],
|
||||||
),
|
),
|
||||||
|
@ -226,14 +229,20 @@ function useConnectorContextMenu(
|
||||||
pathToProp: pathToProp.slice(commonPath.length),
|
pathToProp: pathToProp.slice(commonPath.length),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
const viewModel = props.editorProps.viewModel
|
||||||
|
const address =
|
||||||
|
viewModel.type === 'sheet'
|
||||||
|
? viewModel.sheet.address
|
||||||
|
: viewModel.sheetObject.address
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: 'Copy',
|
label: 'Copy',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
if (props.editorProps.selection) {
|
if (props.editorProps.selection) {
|
||||||
const copyableKeyframes = copyableKeyframesFromSelection(
|
const copyableKeyframes = copyableKeyframesFromSelection(
|
||||||
props.editorProps.viewModel.sheetObject.address.projectId,
|
address.projectId,
|
||||||
props.editorProps.viewModel.sheetObject.address.sheetId,
|
address.sheetId,
|
||||||
props.editorProps.selection,
|
props.editorProps.selection,
|
||||||
)
|
)
|
||||||
getStudio().transaction((api) => {
|
getStudio().transaction((api) => {
|
||||||
|
@ -260,7 +269,8 @@ function useConnectorContextMenu(
|
||||||
for (const con of props.utils.allConnections) {
|
for (const con of props.utils.allConnections) {
|
||||||
stateEditors.coreByProject.historic.sheetsById.sequence.deleteKeyframes(
|
stateEditors.coreByProject.historic.sheetsById.sequence.deleteKeyframes(
|
||||||
{
|
{
|
||||||
...props.editorProps.viewModel.sheetObject.address,
|
...address,
|
||||||
|
objectKey: con.objectKey,
|
||||||
keyframeIds: [con.left.id, con.right.id],
|
keyframeIds: [con.left.id, con.right.id],
|
||||||
trackId: con.trackId,
|
trackId: con.trackId,
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,6 @@ import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
||||||
import usePresence, {
|
import usePresence, {
|
||||||
PresenceFlag,
|
PresenceFlag,
|
||||||
} from '@theatre/studio/uiComponents/usePresence'
|
} from '@theatre/studio/uiComponents/usePresence'
|
||||||
import {useLogger} from '@theatre/studio/uiComponents/useLogger'
|
|
||||||
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
|
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
|
||||||
import type {IAggregateKeyframeEditorProps} from './AggregateKeyframeEditor'
|
import type {IAggregateKeyframeEditorProps} from './AggregateKeyframeEditor'
|
||||||
import type {IAggregateKeyframeEditorUtils} from './useAggregateKeyframeEditorUtils'
|
import type {IAggregateKeyframeEditorUtils} from './useAggregateKeyframeEditorUtils'
|
||||||
|
@ -15,7 +14,6 @@ import {
|
||||||
} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/selections'
|
} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/selections'
|
||||||
import type {KeyframeWithPathToPropFromCommonRoot} from '@theatre/studio/store/types/ahistoric'
|
import type {KeyframeWithPathToPropFromCommonRoot} from '@theatre/studio/store/types/ahistoric'
|
||||||
import {commonRootOfPathsToProps} from '@theatre/shared/utils/addresses'
|
import {commonRootOfPathsToProps} from '@theatre/shared/utils/addresses'
|
||||||
import type {ILogger} from '@theatre/shared/logger'
|
|
||||||
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
|
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
|
||||||
import type {
|
import type {
|
||||||
PrimitivePropEditingOptions,
|
PrimitivePropEditingOptions,
|
||||||
|
@ -96,11 +94,12 @@ function primitivePropBuild(
|
||||||
export function AggregateKeyframeDot(
|
export function AggregateKeyframeDot(
|
||||||
props: React.PropsWithChildren<IAggregateKeyframeDotProps>,
|
props: React.PropsWithChildren<IAggregateKeyframeDotProps>,
|
||||||
) {
|
) {
|
||||||
const logger = useLogger('AggregateKeyframeDot')
|
|
||||||
const {cur} = props.utils
|
const {cur} = props.utils
|
||||||
|
|
||||||
const inlineEditorPopover = useKeyframeInlineEditorPopover(
|
const inlineEditorPopover = useKeyframeInlineEditorPopover(
|
||||||
props.editorProps.viewModel.type === 'sheetObject'
|
props.editorProps.viewModel.type === 'sheet'
|
||||||
|
? null
|
||||||
|
: props.editorProps.viewModel.type === 'sheetObject'
|
||||||
? sheetObjectBuild(props.editorProps.viewModel, cur.keyframes)
|
? sheetObjectBuild(props.editorProps.viewModel, cur.keyframes)
|
||||||
?.children ?? null
|
?.children ?? null
|
||||||
: propWithChildrenBuild(props.editorProps.viewModel, cur.keyframes)
|
: propWithChildrenBuild(props.editorProps.viewModel, cur.keyframes)
|
||||||
|
@ -125,7 +124,7 @@ export function AggregateKeyframeDot(
|
||||||
|
|
||||||
const [ref, node] = useRefAndState<HTMLDivElement | null>(null)
|
const [ref, node] = useRefAndState<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
const [contextMenu] = useAggregateKeyframeContextMenu(props, logger, node)
|
const [contextMenu] = useAggregateKeyframeContextMenu(props, node)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -135,7 +134,11 @@ export function AggregateKeyframeDot(
|
||||||
// Need this for the dragging logic to be able to get the keyframe props
|
// Need this for the dragging logic to be able to get the keyframe props
|
||||||
// based on the position.
|
// based on the position.
|
||||||
{...DopeSnap.includePositionSnapAttrs(cur.position)}
|
{...DopeSnap.includePositionSnapAttrs(cur.position)}
|
||||||
onClick={(e) => inlineEditorPopover.open(e, ref.current!)}
|
onClick={(e) =>
|
||||||
|
props.editorProps.viewModel.type !== 'sheet'
|
||||||
|
? inlineEditorPopover.open(e, ref.current!)
|
||||||
|
: null
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<AggregateKeyframeVisualDot
|
<AggregateKeyframeVisualDot
|
||||||
flag={presence.flag}
|
flag={presence.flag}
|
||||||
|
@ -150,45 +153,30 @@ export function AggregateKeyframeDot(
|
||||||
|
|
||||||
function useAggregateKeyframeContextMenu(
|
function useAggregateKeyframeContextMenu(
|
||||||
props: IAggregateKeyframeDotProps,
|
props: IAggregateKeyframeDotProps,
|
||||||
logger: ILogger,
|
|
||||||
target: HTMLDivElement | null,
|
target: HTMLDivElement | null,
|
||||||
) {
|
) {
|
||||||
return useContextMenu(target, {
|
return useContextMenu(target, {
|
||||||
displayName: 'Aggregate Keyframe',
|
displayName: 'Aggregate Keyframe',
|
||||||
menuItems: () => {
|
menuItems: () => {
|
||||||
// see AGGREGATE_COPY_PASTE.md for explanation of this
|
const viewModel = props.editorProps.viewModel
|
||||||
// code that makes some keyframes with paths for copying
|
const selection = props.editorProps.selection
|
||||||
// to clipboard
|
|
||||||
const kfs = props.utils.cur.keyframes.reduce(
|
|
||||||
(acc, kfWithTrack) =>
|
|
||||||
acc.concat(
|
|
||||||
keyframesWithPaths({
|
|
||||||
...props.editorProps.viewModel.sheetObject.address,
|
|
||||||
trackId: kfWithTrack.track.id,
|
|
||||||
keyframeIds: [kfWithTrack.kf.id],
|
|
||||||
}) ?? [],
|
|
||||||
),
|
|
||||||
[] as KeyframeWithPathToPropFromCommonRoot[],
|
|
||||||
)
|
|
||||||
|
|
||||||
const commonPath = commonRootOfPathsToProps(
|
|
||||||
kfs.map((kf) => kf.pathToProp),
|
|
||||||
)
|
|
||||||
|
|
||||||
const keyframesWithCommonRootPath = kfs.map(({keyframe, pathToProp}) => ({
|
|
||||||
keyframe,
|
|
||||||
pathToProp: pathToProp.slice(commonPath.length),
|
|
||||||
}))
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: props.editorProps.selection ? 'Copy (selection)' : 'Copy',
|
label: selection ? 'Copy (selection)' : 'Copy',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
if (props.editorProps.selection) {
|
// see AGGREGATE_COPY_PASTE.md for explanation of this
|
||||||
|
// code that makes some keyframes with paths for copying
|
||||||
|
// to clipboard
|
||||||
|
if (selection) {
|
||||||
|
const {projectId, sheetId} =
|
||||||
|
viewModel.type === 'sheet'
|
||||||
|
? viewModel.sheet.address
|
||||||
|
: viewModel.sheetObject.address
|
||||||
const copyableKeyframes = copyableKeyframesFromSelection(
|
const copyableKeyframes = copyableKeyframesFromSelection(
|
||||||
props.editorProps.viewModel.sheetObject.address.projectId,
|
projectId,
|
||||||
props.editorProps.viewModel.sheetObject.address.sheetId,
|
sheetId,
|
||||||
props.editorProps.selection,
|
selection,
|
||||||
)
|
)
|
||||||
getStudio().transaction((api) => {
|
getStudio().transaction((api) => {
|
||||||
api.stateEditors.studio.ahistoric.setClipboardKeyframes(
|
api.stateEditors.studio.ahistoric.setClipboardKeyframes(
|
||||||
|
@ -196,6 +184,38 @@ function useAggregateKeyframeContextMenu(
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
const kfs: KeyframeWithPathToPropFromCommonRoot[] =
|
||||||
|
props.utils.cur.keyframes.flatMap(
|
||||||
|
(kfWithTrack) =>
|
||||||
|
keyframesWithPaths({
|
||||||
|
...kfWithTrack.track.sheetObject.address,
|
||||||
|
trackId: kfWithTrack.track.id,
|
||||||
|
keyframeIds: [kfWithTrack.kf.id],
|
||||||
|
}) ?? [],
|
||||||
|
)
|
||||||
|
|
||||||
|
const basePathRelativeToSheet =
|
||||||
|
viewModel.type === 'sheet'
|
||||||
|
? []
|
||||||
|
: viewModel.type === 'sheetObject'
|
||||||
|
? [viewModel.sheetObject.address.objectKey]
|
||||||
|
: viewModel.type === 'propWithChildren'
|
||||||
|
? [
|
||||||
|
viewModel.sheetObject.address.objectKey,
|
||||||
|
...viewModel.pathToProp,
|
||||||
|
]
|
||||||
|
: [] // should be unreachable unless new viewModel/leaf types are added
|
||||||
|
const commonPath = commonRootOfPathsToProps([
|
||||||
|
basePathRelativeToSheet,
|
||||||
|
...kfs.map((kf) => kf.pathToProp),
|
||||||
|
])
|
||||||
|
|
||||||
|
const keyframesWithCommonRootPath = kfs.map(
|
||||||
|
({keyframe, pathToProp}) => ({
|
||||||
|
keyframe,
|
||||||
|
pathToProp: pathToProp.slice(commonPath.length),
|
||||||
|
}),
|
||||||
|
)
|
||||||
getStudio().transaction((api) => {
|
getStudio().transaction((api) => {
|
||||||
api.stateEditors.studio.ahistoric.setClipboardKeyframes(
|
api.stateEditors.studio.ahistoric.setClipboardKeyframes(
|
||||||
keyframesWithCommonRootPath,
|
keyframesWithCommonRootPath,
|
||||||
|
@ -205,16 +225,16 @@ function useAggregateKeyframeContextMenu(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: props.editorProps.selection ? 'Delete (selection)' : 'Delete',
|
label: selection ? 'Delete (selection)' : 'Delete',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
if (props.editorProps.selection) {
|
if (selection) {
|
||||||
props.editorProps.selection.delete()
|
selection.delete()
|
||||||
} else {
|
} else {
|
||||||
getStudio().transaction(({stateEditors}) => {
|
getStudio().transaction(({stateEditors}) => {
|
||||||
for (const kfWithTrack of props.utils.cur.keyframes) {
|
for (const kfWithTrack of props.utils.cur.keyframes) {
|
||||||
stateEditors.coreByProject.historic.sheetsById.sequence.deleteKeyframes(
|
stateEditors.coreByProject.historic.sheetsById.sequence.deleteKeyframes(
|
||||||
{
|
{
|
||||||
...props.editorProps.viewModel.sheetObject.address,
|
...kfWithTrack.track.sheetObject.address,
|
||||||
keyframeIds: [kfWithTrack.kf.id],
|
keyframeIds: [kfWithTrack.kf.id],
|
||||||
trackId: kfWithTrack.track.id,
|
trackId: kfWithTrack.track.id,
|
||||||
},
|
},
|
||||||
|
@ -226,8 +246,5 @@ function useAggregateKeyframeContextMenu(
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
onOpen() {
|
|
||||||
logger._debug('Show aggregate keyframe', props)
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import type {
|
||||||
} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||||
import type {
|
import type {
|
||||||
SequenceEditorTree_PropWithChildren,
|
SequenceEditorTree_PropWithChildren,
|
||||||
|
SequenceEditorTree_Sheet,
|
||||||
SequenceEditorTree_SheetObject,
|
SequenceEditorTree_SheetObject,
|
||||||
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
||||||
import type {Pointer} from '@theatre/dataverse'
|
import type {Pointer} from '@theatre/dataverse'
|
||||||
|
@ -44,6 +45,7 @@ export type IAggregateKeyframeEditorProps = {
|
||||||
viewModel:
|
viewModel:
|
||||||
| SequenceEditorTree_PropWithChildren
|
| SequenceEditorTree_PropWithChildren
|
||||||
| SequenceEditorTree_SheetObject
|
| SequenceEditorTree_SheetObject
|
||||||
|
| SequenceEditorTree_Sheet
|
||||||
selection: undefined | DopeSheetSelection
|
selection: undefined | DopeSheetSelection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,13 +23,12 @@ export function useAggregateKeyframeEditorUtils(
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
const {index, aggregateKeyframes, selection} = props
|
const {index, aggregateKeyframes, selection} = props
|
||||||
const sheetObjectAddress = props.viewModel.sheetObject.address
|
|
||||||
|
|
||||||
return usePrism(getAggregateKeyframeEditorUtilsPrismFn(props), [
|
return usePrism(getAggregateKeyframeEditorUtilsPrismFn(props), [
|
||||||
index,
|
index,
|
||||||
aggregateKeyframes,
|
aggregateKeyframes,
|
||||||
selection,
|
selection,
|
||||||
sheetObjectAddress,
|
props.viewModel,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +41,11 @@ export function getAggregateKeyframeEditorUtilsPrismFn(
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
const {index, aggregateKeyframes, selection} = props
|
const {index, aggregateKeyframes, selection} = props
|
||||||
const sheetObjectAddress = props.viewModel.sheetObject.address
|
|
||||||
|
const {projectId, sheetId} =
|
||||||
|
props.viewModel.type === 'sheet'
|
||||||
|
? props.viewModel.sheet.address
|
||||||
|
: props.viewModel.sheetObject.address
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const cur = aggregateKeyframes[index]
|
const cur = aggregateKeyframes[index]
|
||||||
|
@ -65,24 +68,17 @@ export function getAggregateKeyframeEditorUtilsPrismFn(
|
||||||
const aggregatedConnections: AggregatedKeyframeConnection[] = !connected
|
const aggregatedConnections: AggregatedKeyframeConnection[] = !connected
|
||||||
? []
|
? []
|
||||||
: cur.keyframes.map(({kf, track}, i) => ({
|
: cur.keyframes.map(({kf, track}, i) => ({
|
||||||
...sheetObjectAddress,
|
...track.sheetObject.address,
|
||||||
trackId: track.id,
|
trackId: track.id,
|
||||||
left: kf,
|
left: kf,
|
||||||
right: next.keyframes[i].kf,
|
right: next.keyframes[i].kf,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const allConnections = iif(() => {
|
const allConnections = iif(() => {
|
||||||
const {projectId, sheetId} = sheetObjectAddress
|
|
||||||
|
|
||||||
const selectedConnections = prism
|
const selectedConnections = prism
|
||||||
.memo(
|
.memo(
|
||||||
'selectedConnections',
|
'selectedConnections',
|
||||||
() =>
|
() => selectedKeyframeConnections(projectId, sheetId, selection),
|
||||||
selectedKeyframeConnections(
|
|
||||||
sheetObjectAddress.projectId,
|
|
||||||
sheetObjectAddress.sheetId,
|
|
||||||
selection,
|
|
||||||
),
|
|
||||||
[projectId, sheetId, selection],
|
[projectId, sheetId, selection],
|
||||||
)
|
)
|
||||||
.getValue()
|
.getValue()
|
||||||
|
@ -97,7 +93,12 @@ export function getAggregateKeyframeEditorUtilsPrismFn(
|
||||||
const itemKey = prism.memo(
|
const itemKey = prism.memo(
|
||||||
'itemKey',
|
'itemKey',
|
||||||
() => {
|
() => {
|
||||||
if (props.viewModel.type === 'sheetObject') {
|
if (props.viewModel.type === 'sheet') {
|
||||||
|
return createStudioSheetItemKey.forSheetAggregateKeyframe(
|
||||||
|
props.viewModel.sheet,
|
||||||
|
cur.position,
|
||||||
|
)
|
||||||
|
} else if (props.viewModel.type === 'sheetObject') {
|
||||||
return createStudioSheetItemKey.forSheetObjectAggregateKeyframe(
|
return createStudioSheetItemKey.forSheetObjectAggregateKeyframe(
|
||||||
props.viewModel.sheetObject,
|
props.viewModel.sheetObject,
|
||||||
cur.position,
|
cur.position,
|
||||||
|
@ -110,7 +111,7 @@ export function getAggregateKeyframeEditorUtilsPrismFn(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[props.viewModel.sheetObject, cur.position],
|
[props.viewModel, cur.position],
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -4,10 +4,11 @@ import type {
|
||||||
} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||||
import type {
|
import type {
|
||||||
SequenceEditorTree_PropWithChildren,
|
SequenceEditorTree_PropWithChildren,
|
||||||
|
SequenceEditorTree_Sheet,
|
||||||
SequenceEditorTree_SheetObject,
|
SequenceEditorTree_SheetObject,
|
||||||
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
||||||
import {usePrism, useVal} from '@theatre/react'
|
import {usePrism, useVal} from '@theatre/react'
|
||||||
import type {Pointer} from '@theatre/dataverse'
|
import type {IDerivation, Pointer} from '@theatre/dataverse'
|
||||||
import {prism, val, valueDerivation} from '@theatre/dataverse'
|
import {prism, val, valueDerivation} from '@theatre/dataverse'
|
||||||
import React, {useMemo, Fragment} from 'react'
|
import React, {useMemo, Fragment} from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
@ -19,7 +20,11 @@ import type {
|
||||||
IAggregateKeyframeEditorProps,
|
IAggregateKeyframeEditorProps,
|
||||||
} from './AggregateKeyframeEditor/AggregateKeyframeEditor'
|
} from './AggregateKeyframeEditor/AggregateKeyframeEditor'
|
||||||
import AggregateKeyframeEditor from './AggregateKeyframeEditor/AggregateKeyframeEditor'
|
import AggregateKeyframeEditor from './AggregateKeyframeEditor/AggregateKeyframeEditor'
|
||||||
import type {AggregatedKeyframes} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes'
|
import type {
|
||||||
|
AggregatedKeyframes,
|
||||||
|
KeyframeWithTrack,
|
||||||
|
} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes'
|
||||||
|
import {collectAggregateSnapPositionsObjectOrCompound} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes'
|
||||||
import {useLogger} from '@theatre/studio/uiComponents/useLogger'
|
import {useLogger} from '@theatre/studio/uiComponents/useLogger'
|
||||||
import {getAggregateKeyframeEditorUtilsPrismFn} from './AggregateKeyframeEditor/useAggregateKeyframeEditorUtils'
|
import {getAggregateKeyframeEditorUtilsPrismFn} from './AggregateKeyframeEditor/useAggregateKeyframeEditorUtils'
|
||||||
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
|
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
|
||||||
|
@ -35,7 +40,7 @@ import {
|
||||||
doesPathStartWith,
|
doesPathStartWith,
|
||||||
encodePathToProp,
|
encodePathToProp,
|
||||||
} from '@theatre/shared/utils/addresses'
|
} from '@theatre/shared/utils/addresses'
|
||||||
import type {SequenceTrackId} from '@theatre/shared/utils/ids'
|
import type {ObjectAddressKey, SequenceTrackId} from '@theatre/shared/utils/ids'
|
||||||
import type Sequence from '@theatre/core/sequences/Sequence'
|
import type Sequence from '@theatre/core/sequences/Sequence'
|
||||||
import KeyframeSnapTarget, {
|
import KeyframeSnapTarget, {
|
||||||
snapPositionsStateD,
|
snapPositionsStateD,
|
||||||
|
@ -47,7 +52,7 @@ import {
|
||||||
snapToNone,
|
snapToNone,
|
||||||
snapToSome,
|
snapToSome,
|
||||||
} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget'
|
} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget'
|
||||||
import {collectAggregateSnapPositions} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes'
|
import {collectAggregateSnapPositionsSheet} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes'
|
||||||
import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
|
import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
|
||||||
|
|
||||||
const AggregatedKeyframeTrackContainer = styled.div`
|
const AggregatedKeyframeTrackContainer = styled.div`
|
||||||
|
@ -60,6 +65,7 @@ type IAggregatedKeyframeTracksProps = {
|
||||||
viewModel:
|
viewModel:
|
||||||
| SequenceEditorTree_PropWithChildren
|
| SequenceEditorTree_PropWithChildren
|
||||||
| SequenceEditorTree_SheetObject
|
| SequenceEditorTree_SheetObject
|
||||||
|
| SequenceEditorTree_Sheet
|
||||||
aggregatedKeyframes: AggregatedKeyframes
|
aggregatedKeyframes: AggregatedKeyframes
|
||||||
layoutP: Pointer<SequenceEditorPanelLayout>
|
layoutP: Pointer<SequenceEditorPanelLayout>
|
||||||
}
|
}
|
||||||
|
@ -83,7 +89,6 @@ function AggregatedKeyframeTrack_memo(props: IAggregatedKeyframeTracksProps) {
|
||||||
|
|
||||||
const {selectedPositions, selection} = useCollectedSelectedPositions(
|
const {selectedPositions, selection} = useCollectedSelectedPositions(
|
||||||
layoutP,
|
layoutP,
|
||||||
viewModel,
|
|
||||||
aggregatedKeyframes,
|
aggregatedKeyframes,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -118,7 +123,13 @@ function AggregatedKeyframeTrack_memo(props: IAggregatedKeyframeTracksProps) {
|
||||||
: emptyObject
|
: emptyObject
|
||||||
|
|
||||||
const aggregateSnapPositions = useMemo(
|
const aggregateSnapPositions = useMemo(
|
||||||
() => collectAggregateSnapPositions(viewModel, snapPositions),
|
() =>
|
||||||
|
viewModel.type === 'sheet'
|
||||||
|
? collectAggregateSnapPositionsSheet(viewModel, snapPositions)
|
||||||
|
: collectAggregateSnapPositionsObjectOrCompound(
|
||||||
|
viewModel,
|
||||||
|
snapPositions,
|
||||||
|
),
|
||||||
[snapPositions],
|
[snapPositions],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -208,19 +219,22 @@ const {AllSelected, AtLeastOneUnselected, NoneSelected} =
|
||||||
/** Helper to put together the selected positions */
|
/** Helper to put together the selected positions */
|
||||||
function useCollectedSelectedPositions(
|
function useCollectedSelectedPositions(
|
||||||
layoutP: Pointer<SequenceEditorPanelLayout>,
|
layoutP: Pointer<SequenceEditorPanelLayout>,
|
||||||
viewModel:
|
|
||||||
| SequenceEditorTree_PropWithChildren
|
|
||||||
| SequenceEditorTree_SheetObject,
|
|
||||||
aggregatedKeyframes: AggregatedKeyframes,
|
aggregatedKeyframes: AggregatedKeyframes,
|
||||||
): _AggSelection {
|
): _AggSelection {
|
||||||
return usePrism(() => {
|
return usePrism(
|
||||||
const selectionAtom = val(layoutP.selectionAtom)
|
() => val(collectedSelectedPositions(layoutP, aggregatedKeyframes)),
|
||||||
const sheetObjectSelection = val(
|
[layoutP, aggregatedKeyframes],
|
||||||
selectionAtom.pointer.current.byObjectKey[
|
|
||||||
viewModel.sheetObject.address.objectKey
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
if (!sheetObjectSelection) return EMPTY_SELECTION
|
}
|
||||||
|
|
||||||
|
function collectedSelectedPositions(
|
||||||
|
layoutP: Pointer<SequenceEditorPanelLayout>,
|
||||||
|
aggregatedKeyframes: AggregatedKeyframes,
|
||||||
|
): IDerivation<_AggSelection> {
|
||||||
|
return prism(() => {
|
||||||
|
const selectionAtom = val(layoutP.selectionAtom)
|
||||||
|
const selection = val(selectionAtom.pointer.current)
|
||||||
|
if (!selection) return EMPTY_SELECTION
|
||||||
|
|
||||||
const selectedAtPositions = new Map<
|
const selectedAtPositions = new Map<
|
||||||
number,
|
number,
|
||||||
|
@ -228,17 +242,38 @@ function useCollectedSelectedPositions(
|
||||||
>()
|
>()
|
||||||
|
|
||||||
for (const [position, kfsWithTrack] of aggregatedKeyframes.byPosition) {
|
for (const [position, kfsWithTrack] of aggregatedKeyframes.byPosition) {
|
||||||
|
const positionIsSelected = allOrSomeOrNoneSelected(
|
||||||
|
kfsWithTrack,
|
||||||
|
selection,
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
positionIsSelected !== undefined &&
|
||||||
|
positionIsSelected !== NoneSelected
|
||||||
|
) {
|
||||||
|
selectedAtPositions.set(position, positionIsSelected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedPositions: selectedAtPositions,
|
||||||
|
selection: val(selectionAtom.pointer.current),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function allOrSomeOrNoneSelected(
|
||||||
|
keyframeWithTracks: KeyframeWithTrack[],
|
||||||
|
selection: DopeSheetSelection,
|
||||||
|
): AggregateKeyframePositionIsSelected | undefined {
|
||||||
let positionIsSelected: undefined | AggregateKeyframePositionIsSelected =
|
let positionIsSelected: undefined | AggregateKeyframePositionIsSelected =
|
||||||
undefined
|
undefined
|
||||||
for (const kfWithTrack of kfsWithTrack) {
|
|
||||||
|
for (const {track, kf} of keyframeWithTracks) {
|
||||||
const kfIsSelected =
|
const kfIsSelected =
|
||||||
sheetObjectSelection.byTrackId[kfWithTrack.track.id]?.byKeyframeId?.[
|
selection.byObjectKey[track.sheetObject.address.objectKey]?.byTrackId[
|
||||||
kfWithTrack.kf.id
|
track.id
|
||||||
] === true
|
]?.byKeyframeId?.[kf.id] === true
|
||||||
// -1/10: This sux
|
|
||||||
// undefined = have not encountered
|
|
||||||
if (positionIsSelected === undefined) {
|
if (positionIsSelected === undefined) {
|
||||||
// first item
|
|
||||||
if (kfIsSelected) {
|
if (kfIsSelected) {
|
||||||
positionIsSelected = AllSelected
|
positionIsSelected = AllSelected
|
||||||
} else {
|
} else {
|
||||||
|
@ -254,17 +289,7 @@ function useCollectedSelectedPositions(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return positionIsSelected
|
||||||
if (positionIsSelected != null) {
|
|
||||||
selectedAtPositions.set(position, positionIsSelected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
selectedPositions: selectedAtPositions,
|
|
||||||
selection: val(selectionAtom.pointer.current),
|
|
||||||
}
|
|
||||||
}, [layoutP, aggregatedKeyframes])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function useAggregatedKeyframeTrackContextMenu(
|
function useAggregatedKeyframeTrackContextMenu(
|
||||||
|
@ -297,7 +322,11 @@ function pasteKeyframesContextMenuItem(
|
||||||
const sheet = val(props.layoutP.sheet)
|
const sheet = val(props.layoutP.sheet)
|
||||||
const sequence = sheet.getSequence()
|
const sequence = sheet.getSequence()
|
||||||
|
|
||||||
pasteKeyframes(props.viewModel, keyframes, sequence)
|
if (props.viewModel.type === 'sheet') {
|
||||||
|
pasteKeyframesSheet(props.viewModel, keyframes, sequence)
|
||||||
|
} else {
|
||||||
|
pasteKeyframesObjectOrCompound(props.viewModel, keyframes, sequence)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -313,7 +342,74 @@ function pasteKeyframesContextMenuItem(
|
||||||
* @see StudioAhistoricState.clipboard
|
* @see StudioAhistoricState.clipboard
|
||||||
* @see setClipboardNestedKeyframes
|
* @see setClipboardNestedKeyframes
|
||||||
*/
|
*/
|
||||||
function pasteKeyframes(
|
function pasteKeyframesSheet(
|
||||||
|
viewModel: SequenceEditorTree_Sheet,
|
||||||
|
keyframes: KeyframeWithPathToPropFromCommonRoot[],
|
||||||
|
sequence: Sequence,
|
||||||
|
) {
|
||||||
|
const {projectId, sheetId, sheetInstanceId} = viewModel.sheet.address
|
||||||
|
|
||||||
|
const areKeyframesAllOnSingleTrack = keyframes.every(
|
||||||
|
({pathToProp}) => pathToProp.length === 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (areKeyframesAllOnSingleTrack) {
|
||||||
|
for (const object of viewModel.children.map((child) => child.sheetObject)) {
|
||||||
|
const tracksByObject = valueDerivation(
|
||||||
|
getStudio().atomP.historic.coreByProject[projectId].sheetsById[sheetId]
|
||||||
|
.sequence.tracksByObject[object.address.objectKey],
|
||||||
|
).getValue()
|
||||||
|
|
||||||
|
const trackIdsOnObject = Object.keys(tracksByObject?.trackData ?? {})
|
||||||
|
|
||||||
|
pasteKeyframesToMultipleTracks(
|
||||||
|
object.address,
|
||||||
|
trackIdsOnObject,
|
||||||
|
keyframes,
|
||||||
|
sequence,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const tracksByObject = valueDerivation(
|
||||||
|
getStudio().atomP.historic.coreByProject[projectId].sheetsById[sheetId]
|
||||||
|
.sequence.tracksByObject,
|
||||||
|
).getValue()
|
||||||
|
|
||||||
|
const placeableKeyframes = keyframes
|
||||||
|
.map(({keyframe, pathToProp}) => {
|
||||||
|
const objectKey = pathToProp[0] as ObjectAddressKey
|
||||||
|
const relativePathToProp = pathToProp.slice(1)
|
||||||
|
const pathToPropEncoded = encodePathToProp([...relativePathToProp])
|
||||||
|
|
||||||
|
const trackIdByPropPath =
|
||||||
|
tracksByObject?.[objectKey]?.trackIdByPropPath ?? {}
|
||||||
|
|
||||||
|
const maybeTrackId = trackIdByPropPath[pathToPropEncoded]
|
||||||
|
|
||||||
|
return maybeTrackId
|
||||||
|
? {
|
||||||
|
keyframe,
|
||||||
|
trackId: maybeTrackId,
|
||||||
|
address: {
|
||||||
|
objectKey,
|
||||||
|
projectId,
|
||||||
|
sheetId,
|
||||||
|
sheetInstanceId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
})
|
||||||
|
.filter((result) => result !== null) as {
|
||||||
|
keyframe: Keyframe
|
||||||
|
trackId: SequenceTrackId
|
||||||
|
address: SheetObjectAddress
|
||||||
|
}[]
|
||||||
|
|
||||||
|
pasteKeyframesToSpecificTracks(placeableKeyframes, sequence)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pasteKeyframesObjectOrCompound(
|
||||||
viewModel:
|
viewModel:
|
||||||
| SequenceEditorTree_PropWithChildren
|
| SequenceEditorTree_PropWithChildren
|
||||||
| SequenceEditorTree_SheetObject,
|
| SequenceEditorTree_SheetObject,
|
||||||
|
@ -322,7 +418,7 @@ function pasteKeyframes(
|
||||||
) {
|
) {
|
||||||
const {projectId, sheetId, objectKey} = viewModel.sheetObject.address
|
const {projectId, sheetId, objectKey} = viewModel.sheetObject.address
|
||||||
|
|
||||||
const tracksByObject = valueDerivation(
|
const trackRecords = valueDerivation(
|
||||||
getStudio().atomP.historic.coreByProject[projectId].sheetsById[sheetId]
|
getStudio().atomP.historic.coreByProject[projectId].sheetsById[sheetId]
|
||||||
.sequence.tracksByObject[objectKey],
|
.sequence.tracksByObject[objectKey],
|
||||||
).getValue()
|
).getValue()
|
||||||
|
@ -332,7 +428,7 @@ function pasteKeyframes(
|
||||||
)
|
)
|
||||||
|
|
||||||
if (areKeyframesAllOnSingleTrack) {
|
if (areKeyframesAllOnSingleTrack) {
|
||||||
const trackIdsOnObject = Object.keys(tracksByObject?.trackData ?? {})
|
const trackIdsOnObject = Object.keys(trackRecords?.trackData ?? {})
|
||||||
|
|
||||||
if (viewModel.type === 'sheetObject') {
|
if (viewModel.type === 'sheetObject') {
|
||||||
pasteKeyframesToMultipleTracks(
|
pasteKeyframesToMultipleTracks(
|
||||||
|
@ -342,7 +438,7 @@ function pasteKeyframes(
|
||||||
sequence,
|
sequence,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
const trackIdByPropPath = tracksByObject?.trackIdByPropPath || {}
|
const trackIdByPropPath = trackRecords?.trackIdByPropPath || {}
|
||||||
|
|
||||||
const trackIdsOnCompoundProp = Object.entries(trackIdByPropPath)
|
const trackIdsOnCompoundProp = Object.entries(trackIdByPropPath)
|
||||||
.filter(
|
.filter(
|
||||||
|
@ -364,7 +460,7 @@ function pasteKeyframes(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const trackIdByPropPath = tracksByObject?.trackIdByPropPath || {}
|
const trackIdByPropPath = trackRecords?.trackIdByPropPath || {}
|
||||||
|
|
||||||
const rootPath =
|
const rootPath =
|
||||||
viewModel.type === 'propWithChildren' ? viewModel.pathToProp : []
|
viewModel.type === 'propWithChildren' ? viewModel.pathToProp : []
|
||||||
|
@ -382,19 +478,17 @@ function pasteKeyframes(
|
||||||
? {
|
? {
|
||||||
keyframe,
|
keyframe,
|
||||||
trackId: maybeTrackId,
|
trackId: maybeTrackId,
|
||||||
|
address: viewModel.sheetObject.address,
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
})
|
})
|
||||||
.filter((result) => result !== null) as {
|
.filter((result) => result !== null) as {
|
||||||
keyframe: Keyframe
|
keyframe: Keyframe
|
||||||
trackId: SequenceTrackId
|
trackId: SequenceTrackId
|
||||||
|
address: SheetObjectAddress
|
||||||
}[]
|
}[]
|
||||||
|
|
||||||
pasteKeyframesToSpecificTracks(
|
pasteKeyframesToSpecificTracks(placeableKeyframes, sequence)
|
||||||
viewModel.sheetObject.address,
|
|
||||||
placeableKeyframes,
|
|
||||||
sequence,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,10 +522,10 @@ function pasteKeyframesToMultipleTracks(
|
||||||
}
|
}
|
||||||
|
|
||||||
function pasteKeyframesToSpecificTracks(
|
function pasteKeyframesToSpecificTracks(
|
||||||
address: SheetObjectAddress,
|
|
||||||
keyframesWithTracksToPlaceThemIn: {
|
keyframesWithTracksToPlaceThemIn: {
|
||||||
keyframe: Keyframe
|
keyframe: Keyframe
|
||||||
trackId: SequenceTrackId
|
trackId: SequenceTrackId
|
||||||
|
address: SheetObjectAddress
|
||||||
}[],
|
}[],
|
||||||
sequence: Sequence,
|
sequence: Sequence,
|
||||||
) {
|
) {
|
||||||
|
@ -441,7 +535,11 @@ function pasteKeyframesToSpecificTracks(
|
||||||
)?.position!
|
)?.position!
|
||||||
|
|
||||||
getStudio()!.transaction(({stateEditors}) => {
|
getStudio()!.transaction(({stateEditors}) => {
|
||||||
for (const {keyframe, trackId} of keyframesWithTracksToPlaceThemIn) {
|
for (const {
|
||||||
|
keyframe,
|
||||||
|
trackId,
|
||||||
|
address,
|
||||||
|
} of keyframesWithTracksToPlaceThemIn) {
|
||||||
stateEditors.coreByProject.historic.sheetsById.sequence.setKeyframeAtPosition(
|
stateEditors.coreByProject.historic.sheetsById.sequence.setKeyframeAtPosition(
|
||||||
{
|
{
|
||||||
...address,
|
...address,
|
||||||
|
@ -499,11 +597,14 @@ function useDragForAggregateKeyframeDot(
|
||||||
getAggregateKeyframeEditorUtilsPrismFn(props),
|
getAggregateKeyframeEditorUtilsPrismFn(props),
|
||||||
).getValue().cur.keyframes
|
).getValue().cur.keyframes
|
||||||
|
|
||||||
|
const address =
|
||||||
|
props.viewModel.type === 'sheet'
|
||||||
|
? props.viewModel.sheet.address
|
||||||
|
: props.viewModel.sheetObject.address
|
||||||
|
|
||||||
const tracksByObject = val(
|
const tracksByObject = val(
|
||||||
getStudio()!.atomP.historic.coreByProject[
|
getStudio()!.atomP.historic.coreByProject[address.projectId]
|
||||||
props.viewModel.sheetObject.address.projectId
|
.sheetsById[address.sheetId].sequence.tracksByObject,
|
||||||
].sheetsById[props.viewModel.sheetObject.address.sheetId].sequence
|
|
||||||
.tracksByObject,
|
|
||||||
)!
|
)!
|
||||||
|
|
||||||
// Calculate all the valid snap positions in the sequence editor,
|
// Calculate all the valid snap positions in the sequence editor,
|
||||||
|
@ -537,10 +638,9 @@ function useDragForAggregateKeyframeDot(
|
||||||
AggregateKeyframePositionIsSelected.AllSelected
|
AggregateKeyframePositionIsSelected.AllSelected
|
||||||
) {
|
) {
|
||||||
const {selection, viewModel} = props
|
const {selection, viewModel} = props
|
||||||
const {sheetObject} = viewModel
|
|
||||||
const handlers = selection
|
const handlers = selection
|
||||||
.getDragHandlers({
|
.getDragHandlers({
|
||||||
...sheetObject.address,
|
...address,
|
||||||
domNode: containerNode!,
|
domNode: containerNode!,
|
||||||
positionAtStartOfDrag: keyframes[0].kf.position,
|
positionAtStartOfDrag: keyframes[0].kf.position,
|
||||||
})
|
})
|
||||||
|
@ -587,7 +687,7 @@ function useDragForAggregateKeyframeDot(
|
||||||
const original = keyframe.kf
|
const original = keyframe.kf
|
||||||
stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes(
|
stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes(
|
||||||
{
|
{
|
||||||
...propsAtStartOfDrag.viewModel.sheetObject.address,
|
...keyframe.track.sheetObject.address,
|
||||||
trackId: keyframe.track.id,
|
trackId: keyframe.track.id,
|
||||||
keyframes: [{...original, position: newPosition}],
|
keyframes: [{...original, position: newPosition}],
|
||||||
snappingFunction: val(
|
snappingFunction: val(
|
||||||
|
|
|
@ -74,7 +74,11 @@ const BasicKeyframedTrack: React.VFC<BasicKeyframedTracksProps> = React.memo(
|
||||||
const snapToAllKeyframes = snapPositionsState.mode === 'snapToAll'
|
const snapToAllKeyframes = snapPositionsState.mode === 'snapToAll'
|
||||||
|
|
||||||
const track = useMemo(
|
const track = useMemo(
|
||||||
() => ({data: trackData, id: leaf.trackId}),
|
() => ({
|
||||||
|
data: trackData,
|
||||||
|
id: leaf.trackId,
|
||||||
|
sheetObject: props.leaf.sheetObject,
|
||||||
|
}),
|
||||||
[trackData, leaf.trackId],
|
[trackData, leaf.trackId],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import type {
|
||||||
} from './useSingleKeyframeInlineEditorPopover'
|
} from './useSingleKeyframeInlineEditorPopover'
|
||||||
import last from 'lodash-es/last'
|
import last from 'lodash-es/last'
|
||||||
import {useTempTransactionEditingTools} from './useTempTransactionEditingTools'
|
import {useTempTransactionEditingTools} from './useTempTransactionEditingTools'
|
||||||
|
import {valueInProp} from '@theatre/shared/propTypes/utils'
|
||||||
|
|
||||||
const SingleKeyframePropEditorContainer = styled.div`
|
const SingleKeyframePropEditorContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -127,7 +128,7 @@ function PrimitivePropEditor(
|
||||||
<PropEditor
|
<PropEditor
|
||||||
editingTools={editingTools}
|
editingTools={editingTools}
|
||||||
propConfig={p.propConfig}
|
propConfig={p.propConfig}
|
||||||
value={p.keyframe.value}
|
value={valueInProp(p.keyframe.value, p.propConfig)}
|
||||||
autoFocus={p.autoFocusInput}
|
autoFocus={p.autoFocusInput}
|
||||||
/>
|
/>
|
||||||
</SingleKeyframeSimplePropEditorContainer>
|
</SingleKeyframeSimplePropEditorContainer>
|
||||||
|
@ -136,6 +137,10 @@ function PrimitivePropEditor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These editing tools are distinct from the editing tools used in the
|
||||||
|
// prop editors in the details panel: These editing tools edit the value of a keyframe
|
||||||
|
// while the details editing tools edit the value of the sequence at the playhead
|
||||||
|
// (potentially creating a new keyframe).
|
||||||
function useEditingToolsForKeyframeEditorPopover(
|
function useEditingToolsForKeyframeEditorPopover(
|
||||||
props: PrimitivePropEditingOptions,
|
props: PrimitivePropEditingOptions,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -18,6 +18,7 @@ import type {
|
||||||
import type {
|
import type {
|
||||||
SequenceEditorTree_AllRowTypes,
|
SequenceEditorTree_AllRowTypes,
|
||||||
SequenceEditorTree_PropWithChildren,
|
SequenceEditorTree_PropWithChildren,
|
||||||
|
SequenceEditorTree_Sheet,
|
||||||
SequenceEditorTree_SheetObject,
|
SequenceEditorTree_SheetObject,
|
||||||
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
||||||
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
|
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
|
||||||
|
@ -157,28 +158,25 @@ namespace utils {
|
||||||
const collectForAggregatedChildren = (
|
const collectForAggregatedChildren = (
|
||||||
logger: IUtilLogger,
|
logger: IUtilLogger,
|
||||||
layout: SequenceEditorPanelLayout,
|
layout: SequenceEditorPanelLayout,
|
||||||
leaf: SequenceEditorTree_SheetObject | SequenceEditorTree_PropWithChildren,
|
leaf:
|
||||||
|
| SequenceEditorTree_SheetObject
|
||||||
|
| SequenceEditorTree_PropWithChildren
|
||||||
|
| SequenceEditorTree_Sheet,
|
||||||
bounds: SelectionBounds,
|
bounds: SelectionBounds,
|
||||||
selectionByObjectKey: DopeSheetSelection['byObjectKey'],
|
selectionByObjectKey: DopeSheetSelection['byObjectKey'],
|
||||||
) => {
|
) => {
|
||||||
const sheetObject = leaf.sheetObject
|
const aggregatedKeyframes = collectAggregateKeyframesInPrism(leaf)
|
||||||
const aggregatedKeyframes = collectAggregateKeyframesInPrism(logger, leaf)
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
leaf.top + leaf.nodeHeight / 2 + HITBOX_SIZE_PX > bounds.v[0] &&
|
leaf.top + leaf.nodeHeight / 2 + HITBOX_SIZE_PX > bounds.v[0] &&
|
||||||
leaf.top + leaf.nodeHeight / 2 - HITBOX_SIZE_PX < bounds.v[1]
|
leaf.top + leaf.nodeHeight / 2 - HITBOX_SIZE_PX < bounds.v[1]
|
||||||
) {
|
) {
|
||||||
for (const [position, keyframes] of aggregatedKeyframes.byPosition) {
|
for (const [position, keyframes] of aggregatedKeyframes.byPosition) {
|
||||||
if (
|
const hitboxWidth = layout.scaledSpace.toUnitSpace(HITBOX_SIZE_PX)
|
||||||
position + layout.scaledSpace.toUnitSpace(HITBOX_SIZE_PX) <=
|
const isHitboxOutsideSelection =
|
||||||
bounds.h[0]
|
position + hitboxWidth <= bounds.h[0] ||
|
||||||
)
|
position - hitboxWidth >= bounds.h[1]
|
||||||
continue
|
if (isHitboxOutsideSelection) continue
|
||||||
if (
|
|
||||||
position - layout.scaledSpace.toUnitSpace(HITBOX_SIZE_PX) >=
|
|
||||||
bounds.h[1]
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
for (const keyframeWithTrack of keyframes) {
|
for (const keyframeWithTrack of keyframes) {
|
||||||
mutableSetDeep(
|
mutableSetDeep(
|
||||||
|
@ -186,9 +184,11 @@ namespace utils {
|
||||||
(selectionByObjectKeyP) =>
|
(selectionByObjectKeyP) =>
|
||||||
// convenience for accessing a deep path which might not actually exist
|
// convenience for accessing a deep path which might not actually exist
|
||||||
// through the use of pointer proxy (so we don't have to deal with undeifned )
|
// through the use of pointer proxy (so we don't have to deal with undeifned )
|
||||||
selectionByObjectKeyP[sheetObject.address.objectKey].byTrackId[
|
selectionByObjectKeyP[
|
||||||
keyframeWithTrack.track.id
|
keyframeWithTrack.track.sheetObject.address.objectKey
|
||||||
].byKeyframeId[keyframeWithTrack.kf.id],
|
].byTrackId[keyframeWithTrack.track.id].byKeyframeId[
|
||||||
|
keyframeWithTrack.kf.id
|
||||||
|
],
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -207,6 +207,15 @@ namespace utils {
|
||||||
selectionByObjectKey: DopeSheetSelection['byObjectKey'],
|
selectionByObjectKey: DopeSheetSelection['byObjectKey'],
|
||||||
) => void
|
) => void
|
||||||
} = {
|
} = {
|
||||||
|
sheet(logger, layout, leaf, bounds, selectionByObjectKey) {
|
||||||
|
collectForAggregatedChildren(
|
||||||
|
logger,
|
||||||
|
layout,
|
||||||
|
leaf,
|
||||||
|
bounds,
|
||||||
|
selectionByObjectKey,
|
||||||
|
)
|
||||||
|
},
|
||||||
propWithChildren(logger, layout, leaf, bounds, selectionByObjectKey) {
|
propWithChildren(logger, layout, leaf, bounds, selectionByObjectKey) {
|
||||||
collectForAggregatedChildren(
|
collectForAggregatedChildren(
|
||||||
logger,
|
logger,
|
||||||
|
|
|
@ -39,10 +39,7 @@ const RightPropWithChildrenRow: React.VFC<{
|
||||||
viewModel.pathToProp.join(),
|
viewModel.pathToProp.join(),
|
||||||
)
|
)
|
||||||
return usePrism(() => {
|
return usePrism(() => {
|
||||||
const aggregatedKeyframes = collectAggregateKeyframesInPrism(
|
const aggregatedKeyframes = collectAggregateKeyframesInPrism(viewModel)
|
||||||
logger.utilFor.internal(),
|
|
||||||
viewModel,
|
|
||||||
)
|
|
||||||
|
|
||||||
const node = (
|
const node = (
|
||||||
<AggregatedKeyframeTrack
|
<AggregatedKeyframeTrack
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
import {theme} from '@theatre/studio/css'
|
|
||||||
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||||
import {usePrism} from '@theatre/react'
|
import {usePrism} from '@theatre/react'
|
||||||
import type {Pointer} from '@theatre/dataverse'
|
import type {Pointer} from '@theatre/dataverse'
|
||||||
import {val} from '@theatre/dataverse'
|
import {val} from '@theatre/dataverse'
|
||||||
import {darken} from 'polished'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import {zIndexes} from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel'
|
|
||||||
import DopeSheetSelectionView from './DopeSheetSelectionView'
|
import DopeSheetSelectionView from './DopeSheetSelectionView'
|
||||||
import HorizontallyScrollableArea from './HorizontallyScrollableArea'
|
import HorizontallyScrollableArea from './HorizontallyScrollableArea'
|
||||||
import SheetRow from './SheetRow'
|
import SheetRow from './SheetRow'
|
||||||
|
@ -22,17 +19,6 @@ const ListContainer = styled.ul`
|
||||||
width: ${contentWidth}px;
|
width: ${contentWidth}px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const Background = styled.div<{width: number}>`
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
width: ${(props) => props.width}px;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: ${() => zIndexes.rightBackground};
|
|
||||||
overflow: hidden;
|
|
||||||
background: ${darken(1 * 0.03, theme.panel.bg)};
|
|
||||||
`
|
|
||||||
|
|
||||||
const Right: React.FC<{
|
const Right: React.FC<{
|
||||||
layoutP: Pointer<SequenceEditorPanelLayout>
|
layoutP: Pointer<SequenceEditorPanelLayout>
|
||||||
}> = ({layoutP}) => {
|
}> = ({layoutP}) => {
|
||||||
|
|
|
@ -5,7 +5,6 @@ import type {Pointer} from '@theatre/dataverse'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {decideRowByPropType} from './PropWithChildrenRow'
|
import {decideRowByPropType} from './PropWithChildrenRow'
|
||||||
import RightRow from './Row'
|
import RightRow from './Row'
|
||||||
import {useLogger} from '@theatre/studio/uiComponents/useLogger'
|
|
||||||
import {collectAggregateKeyframesInPrism} from './collectAggregateKeyframes'
|
import {collectAggregateKeyframesInPrism} from './collectAggregateKeyframes'
|
||||||
import AggregatedKeyframeTrack from './AggregatedKeyframeTrack/AggregatedKeyframeTrack'
|
import AggregatedKeyframeTrack from './AggregatedKeyframeTrack/AggregatedKeyframeTrack'
|
||||||
|
|
||||||
|
@ -13,15 +12,8 @@ const RightSheetObjectRow: React.VFC<{
|
||||||
leaf: SequenceEditorTree_SheetObject
|
leaf: SequenceEditorTree_SheetObject
|
||||||
layoutP: Pointer<SequenceEditorPanelLayout>
|
layoutP: Pointer<SequenceEditorPanelLayout>
|
||||||
}> = ({leaf, layoutP}) => {
|
}> = ({leaf, layoutP}) => {
|
||||||
const logger = useLogger(
|
|
||||||
`RightSheetObjectRow`,
|
|
||||||
leaf.sheetObject.address.objectKey,
|
|
||||||
)
|
|
||||||
return usePrism(() => {
|
return usePrism(() => {
|
||||||
const aggregatedKeyframes = collectAggregateKeyframesInPrism(
|
const aggregatedKeyframes = collectAggregateKeyframesInPrism(leaf)
|
||||||
logger.utilFor.internal(),
|
|
||||||
leaf,
|
|
||||||
)
|
|
||||||
|
|
||||||
const node = (
|
const node = (
|
||||||
<AggregatedKeyframeTrack
|
<AggregatedKeyframeTrack
|
||||||
|
|
|
@ -4,14 +4,27 @@ import {usePrism} from '@theatre/react'
|
||||||
import type {Pointer} from '@theatre/dataverse'
|
import type {Pointer} from '@theatre/dataverse'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import RightSheetObjectRow from './SheetObjectRow'
|
import RightSheetObjectRow from './SheetObjectRow'
|
||||||
|
import RightRow from './Row'
|
||||||
|
import {collectAggregateKeyframesInPrism} from './collectAggregateKeyframes'
|
||||||
|
import AggregatedKeyframeTrack from './AggregatedKeyframeTrack/AggregatedKeyframeTrack'
|
||||||
|
|
||||||
const SheetRow: React.FC<{
|
const SheetRow: React.FC<{
|
||||||
leaf: SequenceEditorTree_Sheet
|
leaf: SequenceEditorTree_Sheet
|
||||||
layoutP: Pointer<SequenceEditorPanelLayout>
|
layoutP: Pointer<SequenceEditorPanelLayout>
|
||||||
}> = ({leaf, layoutP}) => {
|
}> = ({leaf, layoutP}) => {
|
||||||
return usePrism(() => {
|
return usePrism(() => {
|
||||||
|
const aggregatedKeyframes = collectAggregateKeyframesInPrism(leaf)
|
||||||
|
|
||||||
|
const node = (
|
||||||
|
<AggregatedKeyframeTrack
|
||||||
|
layoutP={layoutP}
|
||||||
|
aggregatedKeyframes={aggregatedKeyframes}
|
||||||
|
viewModel={leaf}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<RightRow leaf={leaf} node={node} isCollapsed={leaf.isCollapsed}>
|
||||||
{leaf.children.map((sheetObjectLeaf) => (
|
{leaf.children.map((sheetObjectLeaf) => (
|
||||||
<RightSheetObjectRow
|
<RightSheetObjectRow
|
||||||
layoutP={layoutP}
|
layoutP={layoutP}
|
||||||
|
@ -19,7 +32,7 @@ const SheetRow: React.FC<{
|
||||||
leaf={sheetObjectLeaf}
|
leaf={sheetObjectLeaf}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</RightRow>
|
||||||
)
|
)
|
||||||
}, [leaf, layoutP])
|
}, [leaf, layoutP])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import getStudio from '@theatre/studio/getStudio'
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
import {val} from '@theatre/dataverse'
|
import {val} from '@theatre/dataverse'
|
||||||
import type {
|
import type {
|
||||||
|
SequenceEditorTree_PrimitiveProp,
|
||||||
SequenceEditorTree_PropWithChildren,
|
SequenceEditorTree_PropWithChildren,
|
||||||
|
SequenceEditorTree_Sheet,
|
||||||
SequenceEditorTree_SheetObject,
|
SequenceEditorTree_SheetObject,
|
||||||
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
||||||
import type {
|
import type {
|
||||||
|
@ -13,9 +15,9 @@ import type {
|
||||||
Keyframe,
|
Keyframe,
|
||||||
TrackData,
|
TrackData,
|
||||||
} from '@theatre/core/projects/store/types/SheetState_Historic'
|
} from '@theatre/core/projects/store/types/SheetState_Historic'
|
||||||
import type {IUtilLogger} from '@theatre/shared/logger'
|
|
||||||
import {encodePathToProp} from '@theatre/shared/utils/addresses'
|
import {encodePathToProp} from '@theatre/shared/utils/addresses'
|
||||||
import {uniq} from 'lodash-es'
|
import {uniq} from 'lodash-es'
|
||||||
|
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An index over a series of keyframes that have been collected from different tracks.
|
* An index over a series of keyframes that have been collected from different tracks.
|
||||||
|
@ -30,6 +32,7 @@ export type AggregatedKeyframes = {
|
||||||
export type TrackWithId = {
|
export type TrackWithId = {
|
||||||
id: SequenceTrackId
|
id: SequenceTrackId
|
||||||
data: TrackData
|
data: TrackData
|
||||||
|
sheetObject: SheetObject
|
||||||
}
|
}
|
||||||
|
|
||||||
export type KeyframeWithTrack = {
|
export type KeyframeWithTrack = {
|
||||||
|
@ -59,67 +62,27 @@ export type KeyframeWithTrack = {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export function collectAggregateKeyframesInPrism(
|
export function collectAggregateKeyframesInPrism(
|
||||||
logger: IUtilLogger,
|
leaf:
|
||||||
leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_SheetObject,
|
| SequenceEditorTree_Sheet
|
||||||
|
| SequenceEditorTree_PropWithChildren
|
||||||
|
| SequenceEditorTree_SheetObject,
|
||||||
): AggregatedKeyframes {
|
): AggregatedKeyframes {
|
||||||
const sheetObject = leaf.sheetObject
|
const tracks =
|
||||||
|
leaf.type === 'sheet'
|
||||||
|
? collectAggregateKeyframesSheet(leaf)
|
||||||
|
: collectAggregateKeyframesCompoundOrObject(leaf)
|
||||||
|
|
||||||
const projectId = sheetObject.address.projectId
|
return {
|
||||||
|
byPosition: keyframesByPositionFromTrackWithIds(tracks),
|
||||||
const sheetObjectTracksP =
|
tracks,
|
||||||
getStudio().atomP.historic.coreByProject[projectId].sheetsById[
|
|
||||||
sheetObject.address.sheetId
|
|
||||||
].sequence.tracksByObject[sheetObject.address.objectKey]
|
|
||||||
|
|
||||||
const aggregatedKeyframes: AggregatedKeyframes[] = []
|
|
||||||
const childSimpleTracks: TrackWithId[] = []
|
|
||||||
for (const childLeaf of leaf.children) {
|
|
||||||
if (childLeaf.type === 'primitiveProp') {
|
|
||||||
const trackId = val(
|
|
||||||
sheetObjectTracksP.trackIdByPropPath[
|
|
||||||
encodePathToProp(childLeaf.pathToProp)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
if (!trackId) {
|
|
||||||
logger.trace('missing track id?', {childLeaf})
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const trackData = val(sheetObjectTracksP.trackData[trackId])
|
function keyframesByPositionFromTrackWithIds(tracks: TrackWithId[]) {
|
||||||
if (!trackData) {
|
|
||||||
logger.trace('missing track data?', {trackId, childLeaf})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
childSimpleTracks.push({id: trackId, data: trackData})
|
|
||||||
} else if (childLeaf.type === 'propWithChildren') {
|
|
||||||
aggregatedKeyframes.push(
|
|
||||||
collectAggregateKeyframesInPrism(
|
|
||||||
logger.named('propWithChildren', childLeaf.pathToProp.join()),
|
|
||||||
childLeaf,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
const _exhaustive: never = childLeaf
|
|
||||||
logger.error('unexpected kind of prop', {childLeaf})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.trace('see collected of children', {
|
|
||||||
aggregatedKeyframes,
|
|
||||||
childSimpleTracks,
|
|
||||||
})
|
|
||||||
|
|
||||||
const tracks = aggregatedKeyframes
|
|
||||||
.flatMap((a) => a.tracks)
|
|
||||||
.concat(childSimpleTracks)
|
|
||||||
|
|
||||||
const byPosition = new Map<number, KeyframeWithTrack[]>()
|
const byPosition = new Map<number, KeyframeWithTrack[]>()
|
||||||
|
|
||||||
for (const track of tracks) {
|
for (const track of tracks) {
|
||||||
const kfs = track.data.keyframes
|
for (const kf of track.data.keyframes) {
|
||||||
for (let i = 0; i < kfs.length; i++) {
|
|
||||||
const kf = kfs[i]
|
|
||||||
let existing = byPosition.get(kf.position)
|
let existing = byPosition.get(kf.position)
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
existing = []
|
existing = []
|
||||||
|
@ -129,7 +92,7 @@ export function collectAggregateKeyframesInPrism(
|
||||||
kf,
|
kf,
|
||||||
track,
|
track,
|
||||||
itemKey: createStudioSheetItemKey.forTrackKeyframe(
|
itemKey: createStudioSheetItemKey.forTrackKeyframe(
|
||||||
sheetObject,
|
track.sheetObject,
|
||||||
track.id,
|
track.id,
|
||||||
kf.id,
|
kf.id,
|
||||||
),
|
),
|
||||||
|
@ -137,19 +100,28 @@ export function collectAggregateKeyframesInPrism(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return byPosition
|
||||||
byPosition,
|
|
||||||
tracks,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function collectAggregateKeyframesSheet(
|
||||||
* Collects all the snap positions for an aggregate track.
|
leaf: SequenceEditorTree_Sheet,
|
||||||
*/
|
): TrackWithId[] {
|
||||||
export function collectAggregateSnapPositions(
|
return leaf.children.flatMap(collectAggregateKeyframesCompoundOrObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectAggregateKeyframesCompoundOrObject(
|
||||||
leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_SheetObject,
|
leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_SheetObject,
|
||||||
snapTargetPositions: {[key: string]: {[key: string]: number[]}},
|
): TrackWithId[] {
|
||||||
): number[] {
|
return leaf.children.flatMap((childLeaf) =>
|
||||||
|
childLeaf.type === 'propWithChildren'
|
||||||
|
? collectAggregateKeyframesCompoundOrObject(childLeaf)
|
||||||
|
: collectAggregateKeyframesPrimitiveProp(childLeaf),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectAggregateKeyframesPrimitiveProp(
|
||||||
|
leaf: SequenceEditorTree_PrimitiveProp,
|
||||||
|
): TrackWithId[] {
|
||||||
const sheetObject = leaf.sheetObject
|
const sheetObject = leaf.sheetObject
|
||||||
|
|
||||||
const projectId = sheetObject.address.projectId
|
const projectId = sheetObject.address.projectId
|
||||||
|
@ -158,29 +130,67 @@ export function collectAggregateSnapPositions(
|
||||||
getStudio().atomP.historic.coreByProject[projectId].sheetsById[
|
getStudio().atomP.historic.coreByProject[projectId].sheetsById[
|
||||||
sheetObject.address.sheetId
|
sheetObject.address.sheetId
|
||||||
].sequence.tracksByObject[sheetObject.address.objectKey]
|
].sequence.tracksByObject[sheetObject.address.objectKey]
|
||||||
|
|
||||||
const positions: number[] = []
|
|
||||||
for (const childLeaf of leaf.children) {
|
|
||||||
if (childLeaf.type === 'primitiveProp') {
|
|
||||||
const trackId = val(
|
const trackId = val(
|
||||||
sheetObjectTracksP.trackIdByPropPath[
|
sheetObjectTracksP.trackIdByPropPath[encodePathToProp(leaf.pathToProp)],
|
||||||
encodePathToProp(childLeaf.pathToProp)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
if (!trackId) {
|
if (!trackId) return []
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
positions.push(
|
const trackData = val(sheetObjectTracksP.trackData[trackId])
|
||||||
...(snapTargetPositions[sheetObject.address.objectKey]?.[trackId] ??
|
if (!trackData) return []
|
||||||
[]),
|
|
||||||
)
|
|
||||||
} else if (childLeaf.type === 'propWithChildren') {
|
|
||||||
positions.push(
|
|
||||||
...collectAggregateSnapPositions(childLeaf, snapTargetPositions),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return uniq(positions)
|
return [{id: trackId, data: trackData, sheetObject}]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects all the snap positions for an aggregate track.
|
||||||
|
*/
|
||||||
|
export function collectAggregateSnapPositionsSheet(
|
||||||
|
leaf: SequenceEditorTree_Sheet,
|
||||||
|
snapTargetPositions: {[key: string]: {[key: string]: number[]}},
|
||||||
|
): number[] {
|
||||||
|
return uniq(
|
||||||
|
leaf.children.flatMap((childLeaf) =>
|
||||||
|
collectAggregateSnapPositionsObjectOrCompound(
|
||||||
|
childLeaf,
|
||||||
|
snapTargetPositions,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function collectAggregateSnapPositionsObjectOrCompound(
|
||||||
|
leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_SheetObject,
|
||||||
|
snapTargetPositions: {[key: string]: {[key: string]: number[]}},
|
||||||
|
): number[] {
|
||||||
|
return uniq(
|
||||||
|
leaf.children.flatMap((childLeaf) =>
|
||||||
|
childLeaf.type === 'propWithChildren'
|
||||||
|
? collectAggregateSnapPositionsObjectOrCompound(
|
||||||
|
childLeaf,
|
||||||
|
snapTargetPositions,
|
||||||
|
)
|
||||||
|
: collectAggregateSnapPositionsPrimitiveProp(
|
||||||
|
childLeaf,
|
||||||
|
snapTargetPositions,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectAggregateSnapPositionsPrimitiveProp(
|
||||||
|
leaf: SequenceEditorTree_PrimitiveProp,
|
||||||
|
snapTargetPositions: {[key: string]: {[key: string]: number[]}},
|
||||||
|
): number[] {
|
||||||
|
const sheetObject = leaf.sheetObject
|
||||||
|
const projectId = sheetObject.address.projectId
|
||||||
|
const sheetObjectTracksP =
|
||||||
|
getStudio().atomP.historic.coreByProject[projectId].sheetsById[
|
||||||
|
sheetObject.address.sheetId
|
||||||
|
].sequence.tracksByObject[sheetObject.address.objectKey]
|
||||||
|
const trackId = val(
|
||||||
|
sheetObjectTracksP.trackIdByPropPath[encodePathToProp(leaf.pathToProp)],
|
||||||
|
)
|
||||||
|
if (!trackId) return []
|
||||||
|
|
||||||
|
return snapTargetPositions[sheetObject.address.objectKey]?.[trackId] ?? []
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,9 +108,6 @@ export function selectedKeyframeConnections(
|
||||||
* {keyframe, pathToProp: ['scale', 'x']},
|
* {keyframe, pathToProp: ['scale', 'x']},
|
||||||
* ]
|
* ]
|
||||||
* ```
|
* ```
|
||||||
*
|
|
||||||
* TODO - we don't yet support copying/pasting keyframes from multiple objects to multiple objects.
|
|
||||||
* The main reason is that we don't yet have an aggregate track for several objects.
|
|
||||||
*/
|
*/
|
||||||
export function copyableKeyframesFromSelection(
|
export function copyableKeyframesFromSelection(
|
||||||
projectId: ProjectId,
|
projectId: ProjectId,
|
||||||
|
@ -175,7 +172,7 @@ export function keyframesWithPaths({
|
||||||
const encodedPropPath = propPathByTrackId[trackId]
|
const encodedPropPath = propPathByTrackId[trackId]
|
||||||
|
|
||||||
if (!encodedPropPath) return null
|
if (!encodedPropPath) return null
|
||||||
const pathToProp = decodePathToProp(encodedPropPath)
|
const pathToProp = [objectKey, ...decodePathToProp(encodedPropPath)]
|
||||||
|
|
||||||
return keyframeIds
|
return keyframeIds
|
||||||
.map((keyframeId) => ({
|
.map((keyframeId) => ({
|
||||||
|
|
|
@ -5,7 +5,7 @@ import type {
|
||||||
WithoutSheetInstance,
|
WithoutSheetInstance,
|
||||||
} from '@theatre/shared/utils/addresses'
|
} from '@theatre/shared/utils/addresses'
|
||||||
|
|
||||||
export function setCollapsedSheetObjectOrCompoundProp(
|
export function setCollapsedSheetItem(
|
||||||
isCollapsed: boolean,
|
isCollapsed: boolean,
|
||||||
toCollapse: {
|
toCollapse: {
|
||||||
sheetAddress: WithoutSheetInstance<SheetAddress>
|
sheetAddress: WithoutSheetInstance<SheetAddress>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type Sheet from '@theatre/core/sheets/Sheet'
|
import type Sheet from '@theatre/core/sheets/Sheet'
|
||||||
import getStudio from '@theatre/studio/getStudio'
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
import type useDrag from '@theatre/studio/uiComponents/useDrag'
|
import type useDrag from '@theatre/studio/uiComponents/useDrag'
|
||||||
import type {SheetObjectAddress} from '@theatre/shared/utils/addresses'
|
import type {SheetAddress} from '@theatre/shared/utils/addresses'
|
||||||
import subPrism from '@theatre/shared/utils/subPrism'
|
import subPrism from '@theatre/shared/utils/subPrism'
|
||||||
import type {
|
import type {
|
||||||
IRange,
|
IRange,
|
||||||
|
@ -57,7 +57,7 @@ export type DopeSheetSelection = {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
getDragHandlers(
|
getDragHandlers(
|
||||||
origin: SheetObjectAddress & {
|
origin: SheetAddress & {
|
||||||
positionAtStartOfDrag: number
|
positionAtStartOfDrag: number
|
||||||
domNode: Element
|
domNode: Element
|
||||||
},
|
},
|
||||||
|
|
|
@ -58,6 +58,7 @@ export type SequenceEditorTree = SequenceEditorTree_Sheet
|
||||||
|
|
||||||
export type SequenceEditorTree_Sheet = SequenceEditorTree_Row<'sheet'> & {
|
export type SequenceEditorTree_Sheet = SequenceEditorTree_Row<'sheet'> & {
|
||||||
sheet: Sheet
|
sheet: Sheet
|
||||||
|
isCollapsed: boolean
|
||||||
children: SequenceEditorTree_SheetObject[]
|
children: SequenceEditorTree_SheetObject[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,34 +107,44 @@ export const calculateSequenceEditorTree = (
|
||||||
studio: Studio,
|
studio: Studio,
|
||||||
): SequenceEditorTree => {
|
): SequenceEditorTree => {
|
||||||
prism.ensurePrism()
|
prism.ensurePrism()
|
||||||
let topSoFar = titleBarHeight
|
|
||||||
let nSoFar = 0
|
|
||||||
const rootShouldRender = true
|
const rootShouldRender = true
|
||||||
|
let topSoFar = titleBarHeight + (rootShouldRender ? HEIGHT_OF_ANY_TITLE : 0)
|
||||||
|
let nSoFar = 0
|
||||||
|
|
||||||
|
const collapsableItemSetP =
|
||||||
|
studio.atomP.ahistoric.projects.stateByProjectId[sheet.address.projectId]
|
||||||
|
.stateBySheetId[sheet.address.sheetId].sequence.collapsableItems
|
||||||
|
|
||||||
|
const isCollapsedP =
|
||||||
|
collapsableItemSetP.byId[createStudioSheetItemKey.forSheet()].isCollapsed
|
||||||
|
const isCollapsed = valueDerivation(isCollapsedP).getValue() ?? false
|
||||||
|
|
||||||
const tree: SequenceEditorTree = {
|
const tree: SequenceEditorTree = {
|
||||||
type: 'sheet',
|
type: 'sheet',
|
||||||
|
isCollapsed,
|
||||||
sheet,
|
sheet,
|
||||||
children: [],
|
children: [],
|
||||||
sheetItemKey: createStudioSheetItemKey.forSheet(),
|
sheetItemKey: createStudioSheetItemKey.forSheet(),
|
||||||
shouldRender: rootShouldRender,
|
shouldRender: rootShouldRender,
|
||||||
top: topSoFar,
|
top: titleBarHeight,
|
||||||
depth: -1,
|
depth: 0,
|
||||||
n: nSoFar,
|
n: nSoFar,
|
||||||
nodeHeight: 0, // always 0
|
nodeHeight: rootShouldRender ? HEIGHT_OF_ANY_TITLE : 0,
|
||||||
heightIncludingChildren: -1, // will define this later
|
heightIncludingChildren: -1, // calculated below
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rootShouldRender) {
|
if (rootShouldRender) {
|
||||||
nSoFar += 1
|
nSoFar += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
const collapsableItemSetP =
|
|
||||||
studio.atomP.ahistoric.projects.stateByProjectId[sheet.address.projectId]
|
|
||||||
.stateBySheetId[sheet.address.sheetId].sequence.collapsableItems
|
|
||||||
|
|
||||||
for (const sheetObject of Object.values(val(sheet.objectsP))) {
|
for (const sheetObject of Object.values(val(sheet.objectsP))) {
|
||||||
if (sheetObject) {
|
if (sheetObject) {
|
||||||
addObject(sheetObject, tree.children, tree.depth + 1, rootShouldRender)
|
addObject(
|
||||||
|
sheetObject,
|
||||||
|
tree.children,
|
||||||
|
tree.depth + 1,
|
||||||
|
rootShouldRender && !isCollapsed,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tree.heightIncludingChildren = topSoFar - tree.top
|
tree.heightIncludingChildren = topSoFar - tree.top
|
||||||
|
@ -167,9 +178,7 @@ export const calculateSequenceEditorTree = (
|
||||||
n: nSoFar,
|
n: nSoFar,
|
||||||
sheetObject: sheetObject,
|
sheetObject: sheetObject,
|
||||||
nodeHeight: shouldRender ? HEIGHT_OF_ANY_TITLE : 0,
|
nodeHeight: shouldRender ? HEIGHT_OF_ANY_TITLE : 0,
|
||||||
// Question: Why -1? Is this relevant for "shouldRender"?
|
heightIncludingChildren: -1, // calculated below
|
||||||
// Perhaps this is to indicate this does not have a valid value.
|
|
||||||
heightIncludingChildren: -1,
|
|
||||||
}
|
}
|
||||||
arrayOfChildren.push(row)
|
arrayOfChildren.push(row)
|
||||||
|
|
||||||
|
@ -350,5 +359,6 @@ export const calculateSequenceEditorTree = (
|
||||||
topSoFar += row.nodeHeight
|
topSoFar += row.nodeHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('tree :)', tree)
|
||||||
return tree
|
return tree
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,7 +257,7 @@ function ControlIndicators({
|
||||||
...s,
|
...s,
|
||||||
nearbies: getNearbyKeyframesOfTrack(
|
nearbies: getNearbyKeyframesOfTrack(
|
||||||
obj,
|
obj,
|
||||||
{id: s.trackId, data: s.track!},
|
{id: s.trackId, data: s.track!, sheetObject: obj},
|
||||||
sequencePosition,
|
sequencePosition,
|
||||||
),
|
),
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -176,6 +176,7 @@ function createDerivation<T extends SerializablePrimitive>(
|
||||||
track && {
|
track && {
|
||||||
data: track,
|
data: track,
|
||||||
id: sequenceTrackId,
|
id: sequenceTrackId,
|
||||||
|
sheetObject: obj,
|
||||||
},
|
},
|
||||||
sequencePosition,
|
sequencePosition,
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue