Add keyframe copy and paste draft (#105)
* Add keyframe copy and paste draft Author: vezwork <elliot@theatrejs.com> Date: Mon Mar 21 15:48:06 2022 -0400 * add first pass for copy and paste keyframes Author: vezwork <elliot@theatrejs.com> Date: Tue Mar 22 10:35:17 2022 -0400 * add clipboard with keyframes to ahistoric data * Refactor keyframe context menu * fix type error * refactor context menus * cleanup small bits of code * reorder function defs * Add connector copy keyframes and fix highlight left margin * modify keyframe positioning Co-authored-by: Elliot <elliot@Elliots-MacBook-Pro.local>
This commit is contained in:
parent
89133c5e6f
commit
8a9b26eb41
9 changed files with 288 additions and 96 deletions
|
@ -1,21 +1,35 @@
|
||||||
import type {TrackData} from '@theatre/core/projects/store/types/SheetState_Historic'
|
import type {TrackData} from '@theatre/core/projects/store/types/SheetState_Historic'
|
||||||
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||||
import type {SequenceEditorTree_PrimitiveProp} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
import type {SequenceEditorTree_PrimitiveProp} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
||||||
|
import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
|
||||||
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 React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import KeyframeEditor from './KeyframeEditor/KeyframeEditor'
|
import KeyframeEditor from './KeyframeEditor/KeyframeEditor'
|
||||||
|
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
|
||||||
|
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
||||||
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
|
|
||||||
const Container = styled.div``
|
const Container = styled.div`
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
`
|
||||||
|
|
||||||
const BasicKeyframedTrack: React.FC<{
|
type BasicKeyframedTracksProps = {
|
||||||
leaf: SequenceEditorTree_PrimitiveProp
|
leaf: SequenceEditorTree_PrimitiveProp
|
||||||
|
|
||||||
layoutP: Pointer<SequenceEditorPanelLayout>
|
layoutP: Pointer<SequenceEditorPanelLayout>
|
||||||
trackData: TrackData
|
trackData: TrackData
|
||||||
}> = React.memo(({layoutP, trackData, leaf}) => {
|
}
|
||||||
|
|
||||||
|
const BasicKeyframedTrack: React.FC<BasicKeyframedTracksProps> = React.memo(
|
||||||
|
(props) => {
|
||||||
|
const {layoutP, trackData, leaf} = props
|
||||||
|
const [containerRef, containerNode] = useRefAndState<HTMLDivElement | null>(
|
||||||
|
null,
|
||||||
|
)
|
||||||
const {selectedKeyframeIds, selection} = usePrism(() => {
|
const {selectedKeyframeIds, selection} = usePrism(() => {
|
||||||
const selectionAtom = val(layoutP.selectionAtom)
|
const selectionAtom = val(layoutP.selectionAtom)
|
||||||
const selectedKeyframeIds = val(
|
const selectedKeyframeIds = val(
|
||||||
|
@ -33,6 +47,11 @@ const BasicKeyframedTrack: React.FC<{
|
||||||
}
|
}
|
||||||
}, [layoutP, leaf.trackId])
|
}, [layoutP, leaf.trackId])
|
||||||
|
|
||||||
|
const [contextMenu, _, isOpen] = useBasicKeyframedTrackContextMenu(
|
||||||
|
containerNode,
|
||||||
|
props,
|
||||||
|
)
|
||||||
|
|
||||||
const keyframeEditors = trackData.keyframes.map((kf, index) => (
|
const keyframeEditors = trackData.keyframes.map((kf, index) => (
|
||||||
<KeyframeEditor
|
<KeyframeEditor
|
||||||
keyframe={kf}
|
keyframe={kf}
|
||||||
|
@ -45,7 +64,76 @@ const BasicKeyframedTrack: React.FC<{
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
|
|
||||||
return <>{keyframeEditors}</>
|
return (
|
||||||
})
|
<Container
|
||||||
|
ref={containerRef}
|
||||||
|
style={{
|
||||||
|
background: isOpen ? '#444850 ' : 'unset',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{keyframeEditors}
|
||||||
|
{contextMenu}
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
export default BasicKeyframedTrack
|
export default BasicKeyframedTrack
|
||||||
|
|
||||||
|
function useBasicKeyframedTrackContextMenu(
|
||||||
|
node: HTMLDivElement | null,
|
||||||
|
props: BasicKeyframedTracksProps,
|
||||||
|
) {
|
||||||
|
return useContextMenu(node, {
|
||||||
|
items: () => {
|
||||||
|
const selectionKeyframes =
|
||||||
|
val(getStudio()!.atomP.ahistoric.clipboard.keyframes) || []
|
||||||
|
|
||||||
|
if (selectionKeyframes.length > 0) {
|
||||||
|
return [pasteKeyframesContextMenuItem(props, selectionKeyframes)]
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function pasteKeyframesContextMenuItem(
|
||||||
|
props: BasicKeyframedTracksProps,
|
||||||
|
keyframes: Keyframe[],
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
label: 'Paste Keyframes',
|
||||||
|
callback: () => {
|
||||||
|
const sheet = val(props.layoutP.sheet)
|
||||||
|
const sequence = sheet.getSequence()
|
||||||
|
|
||||||
|
getStudio()!.transaction(({stateEditors}) => {
|
||||||
|
sequence.position = sequence.closestGridPosition(sequence.position)
|
||||||
|
const keyframeOffset = earliestKeyframe(keyframes)?.position!
|
||||||
|
|
||||||
|
for (const keyframe of keyframes) {
|
||||||
|
stateEditors.coreByProject.historic.sheetsById.sequence.setKeyframeAtPosition(
|
||||||
|
{
|
||||||
|
...props.leaf.sheetObject.address,
|
||||||
|
trackId: props.leaf.trackId,
|
||||||
|
position: sequence.position + keyframe.position - keyframeOffset,
|
||||||
|
value: keyframe.value,
|
||||||
|
snappingFunction: sequence.closestGridPosition,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function earliestKeyframe(keyframes: Keyframe[]) {
|
||||||
|
let curEarliest: Keyframe | null = null
|
||||||
|
for (const keyframe of keyframes) {
|
||||||
|
if (curEarliest === null || keyframe.position < curEarliest.position) {
|
||||||
|
curEarliest = keyframe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return curEarliest
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,9 @@ import type Sequence from '@theatre/core/sequences/Sequence'
|
||||||
import usePopover from '@theatre/studio/uiComponents/Popover/usePopover'
|
import usePopover from '@theatre/studio/uiComponents/Popover/usePopover'
|
||||||
import BasicPopover from '@theatre/studio/uiComponents/Popover/BasicPopover'
|
import BasicPopover from '@theatre/studio/uiComponents/Popover/BasicPopover'
|
||||||
import CurveEditorPopover from './CurveEditorPopover/CurveEditorPopover'
|
import CurveEditorPopover from './CurveEditorPopover/CurveEditorPopover'
|
||||||
|
import selectedKeyframeIdsIfInSingleTrack from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/selectedKeyframeIdsIfInSingleTrack'
|
||||||
|
import type {OpenFn} from '@theatre/studio/src/uiComponents/Popover/usePopover'
|
||||||
|
import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
|
||||||
|
|
||||||
const connectorHeight = dotSize / 2 + 1
|
const connectorHeight = dotSize / 2 + 1
|
||||||
const connectorWidthUnscaled = 1000
|
const connectorWidthUnscaled = 1000
|
||||||
|
@ -89,37 +92,13 @@ const Connector: React.FC<IProps> = (props) => {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const [contextMenu] = useContextMenu(node, {
|
const [contextMenu] = useConnectorContextMenu(
|
||||||
items: () => {
|
props,
|
||||||
return [
|
node,
|
||||||
{
|
cur,
|
||||||
label: props.selection ? 'Delete Selection' : 'Delete both Keyframes',
|
next,
|
||||||
callback: () => {
|
openPopover,
|
||||||
if (props.selection) {
|
|
||||||
props.selection.delete()
|
|
||||||
} else {
|
|
||||||
getStudio()!.transaction(({stateEditors}) => {
|
|
||||||
stateEditors.coreByProject.historic.sheetsById.sequence.deleteKeyframes(
|
|
||||||
{
|
|
||||||
...props.leaf.sheetObject.address,
|
|
||||||
keyframeIds: [cur.id, next.id],
|
|
||||||
trackId: props.leaf.trackId,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Open Easing Palette',
|
|
||||||
callback: (e) => {
|
|
||||||
openPopover(e, node!)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
useDragKeyframe(node, props)
|
useDragKeyframe(node, props)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -228,3 +207,69 @@ function useDragKeyframe(node: HTMLDivElement | null, props: IProps) {
|
||||||
|
|
||||||
useDrag(node, gestureHandlers)
|
useDrag(node, gestureHandlers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useConnectorContextMenu(
|
||||||
|
props: IProps,
|
||||||
|
node: HTMLDivElement | null,
|
||||||
|
cur: Keyframe,
|
||||||
|
next: Keyframe,
|
||||||
|
openPopover: OpenFn,
|
||||||
|
) {
|
||||||
|
const maybeKeyframeIds = selectedKeyframeIdsIfInSingleTrack(props.selection)
|
||||||
|
return useContextMenu(node, {
|
||||||
|
items: () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: maybeKeyframeIds ? 'Copy Selection' : 'Copy both Keyframes',
|
||||||
|
callback: () => {
|
||||||
|
if (maybeKeyframeIds) {
|
||||||
|
const keyframes = maybeKeyframeIds.map(
|
||||||
|
(keyframeId) =>
|
||||||
|
props.trackData.keyframes.find(
|
||||||
|
(keyframe) => keyframe.id === keyframeId,
|
||||||
|
)!,
|
||||||
|
)
|
||||||
|
|
||||||
|
getStudio!().transaction((api) => {
|
||||||
|
api.stateEditors.studio.ahistoric.setClipboardKeyframes(
|
||||||
|
keyframes,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
getStudio!().transaction((api) => {
|
||||||
|
api.stateEditors.studio.ahistoric.setClipboardKeyframes([
|
||||||
|
cur,
|
||||||
|
next,
|
||||||
|
])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: props.selection ? 'Delete Selection' : 'Delete both Keyframes',
|
||||||
|
callback: () => {
|
||||||
|
if (props.selection) {
|
||||||
|
props.selection.delete()
|
||||||
|
} else {
|
||||||
|
getStudio()!.transaction(({stateEditors}) => {
|
||||||
|
stateEditors.coreByProject.historic.sheetsById.sequence.deleteKeyframes(
|
||||||
|
{
|
||||||
|
...props.leaf.sheetObject.address,
|
||||||
|
keyframeIds: [cur.id, next.id],
|
||||||
|
trackId: props.leaf.trackId,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Open Easing Palette',
|
||||||
|
callback: (e) => {
|
||||||
|
openPopover(e, node!)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPa
|
||||||
import {attributeNameThatLocksFramestamp} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
import {attributeNameThatLocksFramestamp} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||||
import {useCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler'
|
import {useCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler'
|
||||||
import SnapCursor from './SnapCursor.svg'
|
import SnapCursor from './SnapCursor.svg'
|
||||||
|
import selectedKeyframeIdsIfInSingleTrack from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/selectedKeyframeIdsIfInSingleTrack'
|
||||||
|
|
||||||
export const dotSize = 6
|
export const dotSize = 6
|
||||||
const hitZoneSize = 12
|
const hitZoneSize = 12
|
||||||
|
@ -106,28 +107,17 @@ const Dot: React.FC<IProps> = (props) => {
|
||||||
export default Dot
|
export default Dot
|
||||||
|
|
||||||
function useKeyframeContextMenu(node: HTMLDivElement | null, props: IProps) {
|
function useKeyframeContextMenu(node: HTMLDivElement | null, props: IProps) {
|
||||||
|
const maybeKeyframeIds = selectedKeyframeIdsIfInSingleTrack(props.selection)
|
||||||
|
|
||||||
|
const keyframeSelectionItems = maybeKeyframeIds
|
||||||
|
? [copyKeyFrameContextMenuItem(props, maybeKeyframeIds)]
|
||||||
|
: []
|
||||||
|
|
||||||
|
const deleteItem = deleteSelectionOrKeyframeContextMenuItem(props)
|
||||||
|
|
||||||
return useContextMenu(node, {
|
return useContextMenu(node, {
|
||||||
items: () => {
|
items: () => {
|
||||||
return [
|
return [...keyframeSelectionItems, deleteItem]
|
||||||
{
|
|
||||||
label: props.selection ? 'Delete Selection' : 'Delete Keyframe',
|
|
||||||
callback: () => {
|
|
||||||
if (props.selection) {
|
|
||||||
props.selection.delete()
|
|
||||||
} else {
|
|
||||||
getStudio()!.transaction(({stateEditors}) => {
|
|
||||||
stateEditors.coreByProject.historic.sheetsById.sequence.deleteKeyframes(
|
|
||||||
{
|
|
||||||
...props.leaf.sheetObject.address,
|
|
||||||
keyframeIds: [props.keyframe.id],
|
|
||||||
trackId: props.leaf.trackId,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -249,3 +239,42 @@ function useDragKeyframe(
|
||||||
|
|
||||||
return [isDragging]
|
return [isDragging]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteSelectionOrKeyframeContextMenuItem(props: IProps) {
|
||||||
|
return {
|
||||||
|
label: props.selection ? 'Delete Selection' : 'Delete Keyframe',
|
||||||
|
callback: () => {
|
||||||
|
if (props.selection) {
|
||||||
|
props.selection.delete()
|
||||||
|
} else {
|
||||||
|
getStudio()!.transaction(({stateEditors}) => {
|
||||||
|
stateEditors.coreByProject.historic.sheetsById.sequence.deleteKeyframes(
|
||||||
|
{
|
||||||
|
...props.leaf.sheetObject.address,
|
||||||
|
keyframeIds: [props.keyframe.id],
|
||||||
|
trackId: props.leaf.trackId,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyKeyFrameContextMenuItem(props: IProps, keyframeIds: string[]) {
|
||||||
|
return {
|
||||||
|
label: 'Copy Keyframes',
|
||||||
|
callback: () => {
|
||||||
|
const keyframes = keyframeIds.map(
|
||||||
|
(keyframeId) =>
|
||||||
|
props.trackData.keyframes.find(
|
||||||
|
(keyframe) => keyframe.id === keyframeId,
|
||||||
|
)!,
|
||||||
|
)
|
||||||
|
|
||||||
|
getStudio!().transaction((api) => {
|
||||||
|
api.stateEditors.studio.ahistoric.setClipboardKeyframes(keyframes)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import type {
|
||||||
} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||||
import type {SequenceEditorTree_PrimitiveProp} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
import type {SequenceEditorTree_PrimitiveProp} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
||||||
import type {Pointer} from '@theatre/dataverse'
|
import type {Pointer} from '@theatre/dataverse'
|
||||||
|
import {val} from '@theatre/dataverse'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import Connector from './Connector'
|
import Connector from './Connector'
|
||||||
|
@ -37,7 +38,11 @@ const KeyframeEditor: React.FC<{
|
||||||
<Container
|
<Container
|
||||||
style={{
|
style={{
|
||||||
top: `${props.leaf.nodeHeight / 2}px`,
|
top: `${props.leaf.nodeHeight / 2}px`,
|
||||||
left: `calc(var(--unitSpaceToScaledSpaceMultiplier) * ${cur.position}px)`,
|
left: `calc(${val(
|
||||||
|
props.layoutP.scaledSpace.leftPadding,
|
||||||
|
)}px + calc(var(--unitSpaceToScaledSpaceMultiplier) * ${
|
||||||
|
cur.position
|
||||||
|
}px))`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Dot {...props} />
|
<Dot {...props} />
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import type {DopeSheetSelection} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param selection - selection on the dope sheet, or undefined if there isn't a selection
|
||||||
|
* @returns If the selection exists and contains one or more keyframes only in a single track,
|
||||||
|
* then a list of those keyframe's ids; otherwise null
|
||||||
|
*/
|
||||||
|
export default function selectedKeyframeIdsIfInSingleTrack(
|
||||||
|
selection: DopeSheetSelection | undefined,
|
||||||
|
): string[] | null {
|
||||||
|
if (!selection) return null
|
||||||
|
const objectKeys = Object.keys(selection.byObjectKey)
|
||||||
|
if (objectKeys.length !== 1) return null
|
||||||
|
const object = selection.byObjectKey[objectKeys[0]]
|
||||||
|
if (!object) return null
|
||||||
|
const trackIds = Object.keys(object.byTrackId)
|
||||||
|
const firstTrack = object.byTrackId[trackIds[0]]
|
||||||
|
if (trackIds.length !== 1 && firstTrack) return null
|
||||||
|
|
||||||
|
return Object.keys(firstTrack!.byKeyframeId)
|
||||||
|
}
|
|
@ -30,10 +30,6 @@ const Container = styled.div`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const ShiftRight = styled.div`
|
|
||||||
position: absolute;
|
|
||||||
`
|
|
||||||
|
|
||||||
const HorizontallyScrollableArea: React.FC<{
|
const HorizontallyScrollableArea: React.FC<{
|
||||||
layoutP: Pointer<SequenceEditorPanelLayout>
|
layoutP: Pointer<SequenceEditorPanelLayout>
|
||||||
height: number
|
height: number
|
||||||
|
@ -65,14 +61,8 @@ const HorizontallyScrollableArea: React.FC<{
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
'--unitSpaceToScaledSpaceMultiplier': unitSpaceToScaledSpaceMultiplier,
|
'--unitSpaceToScaledSpaceMultiplier': unitSpaceToScaledSpaceMultiplier,
|
||||||
}}
|
}}
|
||||||
>
|
|
||||||
<ShiftRight
|
|
||||||
style={{
|
|
||||||
left: val(layoutP.scaledSpace.leftPadding) + 'px',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</ShiftRight>
|
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -317,6 +317,16 @@ namespace stateEditors {
|
||||||
) {
|
) {
|
||||||
drafts().ahistoric.visibilityState = visibilityState
|
drafts().ahistoric.visibilityState = visibilityState
|
||||||
}
|
}
|
||||||
|
export function setClipboardKeyframes(keyframes: Keyframe[]) {
|
||||||
|
const draft = drafts()
|
||||||
|
if (draft.ahistoric.clipboard) {
|
||||||
|
draft.ahistoric.clipboard.keyframes = keyframes
|
||||||
|
} else {
|
||||||
|
draft.ahistoric.clipboard = {
|
||||||
|
keyframes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
export namespace projects {
|
export namespace projects {
|
||||||
export namespace stateByProjectId {
|
export namespace stateByProjectId {
|
||||||
export function _ensure(p: ProjectAddress) {
|
export function _ensure(p: ProjectAddress) {
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import type {ProjectState} from '@theatre/core/projects/store/storeTypes'
|
import type {ProjectState} from '@theatre/core/projects/store/storeTypes'
|
||||||
|
import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
|
||||||
import type {IRange, StrictRecord} from '@theatre/shared/utils/types'
|
import type {IRange, StrictRecord} from '@theatre/shared/utils/types'
|
||||||
|
|
||||||
export type StudioAhistoricState = {
|
export type StudioAhistoricState = {
|
||||||
visibilityState: 'everythingIsHidden' | 'everythingIsVisible'
|
visibilityState: 'everythingIsHidden' | 'everythingIsVisible'
|
||||||
|
clipboard?: {
|
||||||
|
keyframes?: Keyframe[]
|
||||||
|
// future clipboard data goes here
|
||||||
|
}
|
||||||
theTrigger: {
|
theTrigger: {
|
||||||
position: {
|
position: {
|
||||||
closestCorner: 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'
|
closestCorner: 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {createPortal} from 'react-dom'
|
||||||
import {PortalContext} from 'reakit'
|
import {PortalContext} from 'reakit'
|
||||||
import TooltipWrapper from './TooltipWrapper'
|
import TooltipWrapper from './TooltipWrapper'
|
||||||
|
|
||||||
type OpenFn = (e: React.MouseEvent, target: HTMLElement) => void
|
export type OpenFn = (e: React.MouseEvent, target: HTMLElement) => void
|
||||||
type CloseFn = () => void
|
type CloseFn = () => void
|
||||||
type State =
|
type State =
|
||||||
| {isOpen: false}
|
| {isOpen: false}
|
||||||
|
|
Loading…
Reference in a new issue