feature: Add sequence editor row collapsing
* Add hover state for chevron * Add StudioSheetItemKey id for use with sequenceEditorCollapsableItems Co-authored-by: Elliot <key.draw@gmail.com>
This commit is contained in:
parent
6fd718a6e7
commit
f6e408f610
15 changed files with 290 additions and 60 deletions
|
@ -1,3 +1,6 @@
|
||||||
|
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
||||||
|
import type {PathToProp} from './addresses'
|
||||||
|
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'
|
||||||
|
|
||||||
|
@ -18,6 +21,33 @@ export type PaneInstanceId = Nominal<'PaneInstanceId'>
|
||||||
export type SequenceTrackId = Nominal<'SequenceTrackId'>
|
export type SequenceTrackId = Nominal<'SequenceTrackId'>
|
||||||
export type SequenceMarkerId = Nominal<'SequenceMarkerId'>
|
export type SequenceMarkerId = Nominal<'SequenceMarkerId'>
|
||||||
export type ObjectAddressKey = Nominal<'ObjectAddressKey'>
|
export type ObjectAddressKey = Nominal<'ObjectAddressKey'>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Studio consistent identifier for identifying any individual item on a sheet
|
||||||
|
* including a SheetObject, a SheetObject's prop, etc.
|
||||||
|
*
|
||||||
|
* See {@link createStudioSheetItemKey}.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* This is the kind of type which should not find itself in Project state,
|
||||||
|
* due to how it is lossy in the case of additional model layers being introduced.
|
||||||
|
* e.g. When we introduce an extra layer of multiple sequences per sheet,
|
||||||
|
* all the {@link StudioSheetItemKey}s will have different generated values,
|
||||||
|
* because they'll have additional information (the "sequence id"). This means
|
||||||
|
* that all data attached to those item keys will become detached.
|
||||||
|
*
|
||||||
|
* This kind of constraint might be mitigated by a sort of migrations ability,
|
||||||
|
* but for the most part it's just going to be easier to try not using
|
||||||
|
* {@link StudioSheetItemKey} for any data that needs to stick around after
|
||||||
|
* version updates to Theatre.
|
||||||
|
*
|
||||||
|
* Alternatively, if you did want some kind of universal identifier for any item
|
||||||
|
* that can be persisted and survive project model changes, it's probably going
|
||||||
|
* to be easier to simply generate a unique id for all items you want to use in
|
||||||
|
* this way, and don't do any of this concatenating/JSON.stringify "hashing"
|
||||||
|
* stuff.
|
||||||
|
*/
|
||||||
|
export type StudioSheetItemKey = Nominal<'StudioSheetItemKey'>
|
||||||
/** UI panels can contain a {@link PaneInstanceId} or something else. */
|
/** UI panels can contain a {@link PaneInstanceId} or something else. */
|
||||||
export type UIPanelId = Nominal<'UIPanelId'>
|
export type UIPanelId = Nominal<'UIPanelId'>
|
||||||
|
|
||||||
|
@ -32,3 +62,24 @@ export function asSequenceTrackId(s: string): SequenceTrackId {
|
||||||
export function generateSequenceMarkerId(): SequenceMarkerId {
|
export function generateSequenceMarkerId(): SequenceMarkerId {
|
||||||
return generateNonSecure(10) as SequenceMarkerId
|
return generateNonSecure(10) as SequenceMarkerId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will not necessarily maintain consistent key values if any
|
||||||
|
* versioning happens where something needs to
|
||||||
|
*/
|
||||||
|
export const createStudioSheetItemKey = {
|
||||||
|
forSheetObject(obj: SheetObject): StudioSheetItemKey {
|
||||||
|
return stableValueHash({
|
||||||
|
o: obj.address.objectKey,
|
||||||
|
}) as StudioSheetItemKey
|
||||||
|
},
|
||||||
|
forSheetObjectProp(
|
||||||
|
obj: SheetObject,
|
||||||
|
pathToProp: PathToProp,
|
||||||
|
): StudioSheetItemKey {
|
||||||
|
return stableValueHash({
|
||||||
|
o: obj.address.objectKey,
|
||||||
|
p: pathToProp,
|
||||||
|
}) as StudioSheetItemKey
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ export const rowBg = css`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const Row = styled.div`
|
const LeftRow = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
@ -126,7 +126,7 @@ export function SingleRowPropEditor<T>({
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<LeftRow>
|
||||||
{contextMenu}
|
{contextMenu}
|
||||||
<Left>
|
<Left>
|
||||||
<ControlsContainer>{editingTools.controlIndicators}</ControlsContainer>
|
<ControlsContainer>{editingTools.controlIndicators}</ControlsContainer>
|
||||||
|
@ -142,6 +142,6 @@ export function SingleRowPropEditor<T>({
|
||||||
</Left>
|
</Left>
|
||||||
|
|
||||||
<InputContainer>{children}</InputContainer>
|
<InputContainer>{children}</InputContainer>
|
||||||
</Row>
|
</LeftRow>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import React from 'react'
|
||||||
import {HiOutlineChevronRight} from 'react-icons/all'
|
import {HiOutlineChevronRight} from 'react-icons/all'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS'
|
import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS'
|
||||||
|
import type {ICollapsableItem} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/useSequenceEditorCollapsable'
|
||||||
|
import {useVal} from '@theatre/react'
|
||||||
|
|
||||||
export const Container = styled.li<{depth: number}>`
|
export const Container = styled.li<{depth: number}>`
|
||||||
--depth: ${(props) => props.depth};
|
--depth: ${(props) => props.depth};
|
||||||
|
@ -21,7 +23,7 @@ const Header = styled(BaseHeader)<{
|
||||||
isSelectable: boolean
|
isSelectable: boolean
|
||||||
isSelected: boolean
|
isSelected: boolean
|
||||||
}>`
|
}>`
|
||||||
padding-left: calc(16px + var(--depth) * 20px);
|
padding-left: calc(8px + var(--depth) * 20px);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
@ -42,14 +44,21 @@ const Head_Label = styled.span`
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
`
|
`
|
||||||
|
|
||||||
const Head_Icon = styled.span<{isOpen: boolean}>`
|
const Head_Icon = styled.span<{isCollapsed: boolean}>`
|
||||||
width: 12px;
|
width: 12px;
|
||||||
margin-right: 8px;
|
padding: 8px;
|
||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
transform: rotateZ(${(props) => (props.isOpen ? 90 : 0)}deg);
|
transition: transform 0.05s ease-out, color 0.1s ease-out;
|
||||||
|
transform: rotateZ(${(props) => (props.isCollapsed ? 0 : 90)}deg);
|
||||||
|
color: #66686a;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: rotateZ(${(props) => (props.isCollapsed ? 15 : 75)}deg);
|
||||||
|
color: #c0c4c9;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const Children = styled.ul`
|
const Children = styled.ul`
|
||||||
|
@ -64,8 +73,18 @@ const AnyCompositeRow: React.FC<{
|
||||||
toggleSelect?: VoidFn
|
toggleSelect?: VoidFn
|
||||||
isSelected?: boolean
|
isSelected?: boolean
|
||||||
isSelectable?: boolean
|
isSelectable?: boolean
|
||||||
}> = ({leaf, label, children, isSelectable, isSelected, toggleSelect}) => {
|
collapsable?: ICollapsableItem
|
||||||
|
}> = ({
|
||||||
|
leaf,
|
||||||
|
label,
|
||||||
|
children,
|
||||||
|
isSelectable,
|
||||||
|
isSelected,
|
||||||
|
toggleSelect,
|
||||||
|
collapsable,
|
||||||
|
}) => {
|
||||||
const hasChildren = Array.isArray(children) && children.length > 0
|
const hasChildren = Array.isArray(children) && children.length > 0
|
||||||
|
const isCollapsed = useVal(collapsable?.isCollapsed) ?? false
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container depth={leaf.depth}>
|
<Container depth={leaf.depth}>
|
||||||
|
@ -78,12 +97,15 @@ const AnyCompositeRow: React.FC<{
|
||||||
onClick={toggleSelect}
|
onClick={toggleSelect}
|
||||||
isEven={leaf.n % 2 === 0}
|
isEven={leaf.n % 2 === 0}
|
||||||
>
|
>
|
||||||
<Head_Icon isOpen={true}>
|
<Head_Icon
|
||||||
|
isCollapsed={isCollapsed}
|
||||||
|
onClick={() => collapsable?.toggleCollapsed()}
|
||||||
|
>
|
||||||
<HiOutlineChevronRight />
|
<HiOutlineChevronRight />
|
||||||
</Head_Icon>
|
</Head_Icon>
|
||||||
<Head_Label>{label}</Head_Label>
|
<Head_Label>{label}</Head_Label>
|
||||||
</Header>
|
</Header>
|
||||||
{hasChildren && <Children>{children}</Children>}
|
{hasChildren && !isCollapsed && <Children>{children}</Children>}
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,10 @@ import type {
|
||||||
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
||||||
import {usePrism} from '@theatre/react'
|
import {usePrism} from '@theatre/react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
|
||||||
import AnyCompositeRow from './AnyCompositeRow'
|
import AnyCompositeRow from './AnyCompositeRow'
|
||||||
import PrimitivePropRow from './PrimitivePropRow'
|
import PrimitivePropRow from './PrimitivePropRow'
|
||||||
|
import {useSequenceEditorCollapsable} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/useSequenceEditorCollapsable'
|
||||||
|
import {createStudioSheetItemKey} from '@theatre/shared/utils/ids'
|
||||||
|
|
||||||
export const decideRowByPropType = (
|
export const decideRowByPropType = (
|
||||||
leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_PrimitiveProp,
|
leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_PrimitiveProp,
|
||||||
|
@ -23,16 +24,22 @@ export const decideRowByPropType = (
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
const Container = styled.div``
|
const PropWithChildrenRow: React.VFC<{
|
||||||
|
|
||||||
const PropWithChildrenRow: React.FC<{
|
|
||||||
leaf: SequenceEditorTree_PropWithChildren
|
leaf: SequenceEditorTree_PropWithChildren
|
||||||
}> = ({leaf}) => {
|
}> = ({leaf}) => {
|
||||||
|
const collapsable = useSequenceEditorCollapsable(
|
||||||
|
createStudioSheetItemKey.forSheetObjectProp(
|
||||||
|
leaf.sheetObject,
|
||||||
|
leaf.pathToProp,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return usePrism(() => {
|
return usePrism(() => {
|
||||||
return (
|
return (
|
||||||
<AnyCompositeRow
|
<AnyCompositeRow
|
||||||
leaf={leaf}
|
leaf={leaf}
|
||||||
label={leaf.pathToProp[leaf.pathToProp.length - 1]}
|
label={leaf.pathToProp[leaf.pathToProp.length - 1]}
|
||||||
|
collapsable={collapsable}
|
||||||
>
|
>
|
||||||
{leaf.children.map((propLeaf) => decideRowByPropType(propLeaf))}
|
{leaf.children.map((propLeaf) => decideRowByPropType(propLeaf))}
|
||||||
</AnyCompositeRow>
|
</AnyCompositeRow>
|
||||||
|
|
|
@ -1,22 +1,29 @@
|
||||||
import type {SequenceEditorTree_SheetObject} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
import type {SequenceEditorTree_SheetObject} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
||||||
import {usePrism} from '@theatre/react'
|
import {usePrism} from '@theatre/react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import AnyCompositeRow from './AnyCompositeRow'
|
||||||
import CompoundRow from './AnyCompositeRow'
|
|
||||||
import {decideRowByPropType} from './PropWithChildrenRow'
|
import {decideRowByPropType} from './PropWithChildrenRow'
|
||||||
|
import {useSequenceEditorCollapsable} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/useSequenceEditorCollapsable'
|
||||||
|
import {createStudioSheetItemKey} from '@theatre/shared/utils/ids'
|
||||||
|
|
||||||
const Container = styled.div``
|
const LeftSheetObjectRow: React.VFC<{
|
||||||
|
|
||||||
const SheetObjectRow: React.FC<{
|
|
||||||
leaf: SequenceEditorTree_SheetObject
|
leaf: SequenceEditorTree_SheetObject
|
||||||
}> = ({leaf}) => {
|
}> = ({leaf}) => {
|
||||||
|
const collapsable = useSequenceEditorCollapsable(
|
||||||
|
createStudioSheetItemKey.forSheetObject(leaf.sheetObject),
|
||||||
|
)
|
||||||
|
|
||||||
return usePrism(() => {
|
return usePrism(() => {
|
||||||
return (
|
return (
|
||||||
<CompoundRow leaf={leaf} label={leaf.sheetObject.address.objectKey}>
|
<AnyCompositeRow
|
||||||
|
leaf={leaf}
|
||||||
|
label={leaf.sheetObject.address.objectKey}
|
||||||
|
collapsable={collapsable}
|
||||||
|
>
|
||||||
{leaf.children.map((leaf) => decideRowByPropType(leaf))}
|
{leaf.children.map((leaf) => decideRowByPropType(leaf))}
|
||||||
</CompoundRow>
|
</AnyCompositeRow>
|
||||||
)
|
)
|
||||||
}, [leaf])
|
}, [leaf])
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SheetObjectRow
|
export default LeftSheetObjectRow
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type {SequenceEditorTree_Sheet} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
import type {SequenceEditorTree_Sheet} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
||||||
import {usePrism} from '@theatre/react'
|
import {usePrism} from '@theatre/react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import SheetObjectRow from './SheetObjectRow'
|
import LeftSheetObjectRow from './SheetObjectRow'
|
||||||
|
|
||||||
const SheetRow: React.VFC<{
|
const SheetRow: React.VFC<{
|
||||||
leaf: SequenceEditorTree_Sheet
|
leaf: SequenceEditorTree_Sheet
|
||||||
|
@ -10,7 +10,7 @@ const SheetRow: React.VFC<{
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{leaf.children.map((sheetObjectLeaf) => (
|
{leaf.children.map((sheetObjectLeaf) => (
|
||||||
<SheetObjectRow
|
<LeftSheetObjectRow
|
||||||
key={'sheetObject-' + sheetObjectLeaf.sheetObject.address.objectKey}
|
key={'sheetObject-' + sheetObjectLeaf.sheetObject.address.objectKey}
|
||||||
leaf={sheetObjectLeaf}
|
leaf={sheetObjectLeaf}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -6,7 +6,7 @@ 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 KeyframedTrack from './BasicKeyframedTrack/BasicKeyframedTrack'
|
import KeyframedTrack from './BasicKeyframedTrack/BasicKeyframedTrack'
|
||||||
import Row from './Row'
|
import RightRow from './Row'
|
||||||
|
|
||||||
const PrimitivePropRow: React.FC<{
|
const PrimitivePropRow: React.FC<{
|
||||||
leaf: SequenceEditorTree_PrimitiveProp
|
leaf: SequenceEditorTree_PrimitiveProp
|
||||||
|
@ -27,13 +27,13 @@ const PrimitivePropRow: React.FC<{
|
||||||
console.error(
|
console.error(
|
||||||
`trackData type ${trackData?.type} is not yet supported on the sequence editor`,
|
`trackData type ${trackData?.type} is not yet supported on the sequence editor`,
|
||||||
)
|
)
|
||||||
return <Row leaf={leaf} node={<div />}></Row>
|
return <RightRow leaf={leaf} node={<div />}></RightRow>
|
||||||
} else {
|
} else {
|
||||||
const node = (
|
const node = (
|
||||||
<KeyframedTrack layoutP={layoutP} trackData={trackData} leaf={leaf} />
|
<KeyframedTrack layoutP={layoutP} trackData={trackData} leaf={leaf} />
|
||||||
)
|
)
|
||||||
|
|
||||||
return <Row leaf={leaf} node={node}></Row>
|
return <RightRow leaf={leaf} node={node}></RightRow>
|
||||||
}
|
}
|
||||||
}, [leaf, layoutP])
|
}, [leaf, layoutP])
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,9 @@ 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 PrimitivePropRow from './PrimitivePropRow'
|
import PrimitivePropRow from './PrimitivePropRow'
|
||||||
import Row from './Row'
|
import RightRow from './Row'
|
||||||
|
import {useSequenceEditorCollapsable} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/useSequenceEditorCollapsable'
|
||||||
|
import {createStudioSheetItemKey} from '@theatre/shared/utils/ids'
|
||||||
|
|
||||||
export const decideRowByPropType = (
|
export const decideRowByPropType = (
|
||||||
leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_PrimitiveProp,
|
leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_PrimitiveProp,
|
||||||
|
@ -27,21 +29,26 @@ export const decideRowByPropType = (
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
const PropWithChildrenRow: React.FC<{
|
const PropWithChildrenRow: React.VFC<{
|
||||||
leaf: SequenceEditorTree_PropWithChildren
|
leaf: SequenceEditorTree_PropWithChildren
|
||||||
layoutP: Pointer<SequenceEditorPanelLayout>
|
layoutP: Pointer<SequenceEditorPanelLayout>
|
||||||
}> = ({leaf, layoutP}) => {
|
}> = ({leaf, layoutP}) => {
|
||||||
|
const collapsable = useSequenceEditorCollapsable(
|
||||||
|
createStudioSheetItemKey.forSheetObjectProp(
|
||||||
|
leaf.sheetObject,
|
||||||
|
leaf.pathToProp,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return usePrism(() => {
|
return usePrism(() => {
|
||||||
const node = <div />
|
const node = <div />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row leaf={leaf} node={node}>
|
<RightRow leaf={leaf} node={node} collapsable={collapsable}>
|
||||||
{leaf.children.map((propLeaf) =>
|
{leaf.children.map((propLeaf) =>
|
||||||
decideRowByPropType(propLeaf, layoutP),
|
decideRowByPropType(propLeaf, layoutP),
|
||||||
)}
|
)}
|
||||||
</Row>
|
</RightRow>
|
||||||
)
|
)
|
||||||
}, [leaf, layoutP])
|
}, [leaf, layoutP])
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PropWithChildrenRow
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import type {SequenceEditorTree_Row} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
import type {SequenceEditorTree_Row} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
import type {ICollapsableItem} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/useSequenceEditorCollapsable'
|
||||||
|
import {useVal} from '@theatre/react'
|
||||||
|
|
||||||
const Container = styled.li<{}>`
|
const Container = styled.li<{}>`
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -35,11 +37,13 @@ const Children = styled.ul`
|
||||||
list-style: none;
|
list-style: none;
|
||||||
`
|
`
|
||||||
|
|
||||||
const Row: React.FC<{
|
const RightRow: React.FC<{
|
||||||
leaf: SequenceEditorTree_Row<unknown>
|
leaf: SequenceEditorTree_Row<unknown>
|
||||||
node: React.ReactElement
|
node: React.ReactElement
|
||||||
}> = ({leaf, children, node}) => {
|
collapsable?: ICollapsableItem
|
||||||
|
}> = ({leaf, children, node, collapsable}) => {
|
||||||
const hasChildren = Array.isArray(children) && children.length > 0
|
const hasChildren = Array.isArray(children) && children.length > 0
|
||||||
|
const isCollapsed = useVal(collapsable?.isCollapsed) ?? false
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
|
@ -49,9 +53,9 @@ const Row: React.FC<{
|
||||||
>
|
>
|
||||||
{node}
|
{node}
|
||||||
</NodeWrapper>
|
</NodeWrapper>
|
||||||
{hasChildren && <Children>{children}</Children>}
|
{hasChildren && !isCollapsed && <Children>{children}</Children>}
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Row
|
export default RightRow
|
||||||
|
|
|
@ -3,24 +3,26 @@ import type {SequenceEditorTree_SheetObject} from '@theatre/studio/panels/Sequen
|
||||||
import {usePrism} from '@theatre/react'
|
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 styled from 'styled-components'
|
|
||||||
import {decideRowByPropType} from './PropWithChildrenRow'
|
import {decideRowByPropType} from './PropWithChildrenRow'
|
||||||
import Row from './Row'
|
import RightRow from './Row'
|
||||||
|
import {useSequenceEditorCollapsable} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/useSequenceEditorCollapsable'
|
||||||
|
import {createStudioSheetItemKey} from '@theatre/shared/utils/ids'
|
||||||
|
|
||||||
const Container = styled.div``
|
const RightSheetObjectRow: React.VFC<{
|
||||||
|
|
||||||
const SheetObjectRow: React.FC<{
|
|
||||||
leaf: SequenceEditorTree_SheetObject
|
leaf: SequenceEditorTree_SheetObject
|
||||||
layoutP: Pointer<SequenceEditorPanelLayout>
|
layoutP: Pointer<SequenceEditorPanelLayout>
|
||||||
}> = ({leaf, layoutP}) => {
|
}> = ({leaf, layoutP}) => {
|
||||||
|
const collapsable = useSequenceEditorCollapsable(
|
||||||
|
createStudioSheetItemKey.forSheetObject(leaf.sheetObject),
|
||||||
|
)
|
||||||
return usePrism(() => {
|
return usePrism(() => {
|
||||||
const node = <div />
|
const node = <div />
|
||||||
return (
|
return (
|
||||||
<Row leaf={leaf} node={node}>
|
<RightRow leaf={leaf} node={node} collapsable={collapsable}>
|
||||||
{leaf.children.map((leaf) => decideRowByPropType(leaf, layoutP))}
|
{leaf.children.map((leaf) => decideRowByPropType(leaf, layoutP))}
|
||||||
</Row>
|
</RightRow>
|
||||||
)
|
)
|
||||||
}, [leaf, layoutP])
|
}, [leaf, layoutP])
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SheetObjectRow
|
export default RightSheetObjectRow
|
||||||
|
|
|
@ -3,7 +3,7 @@ import type {SequenceEditorTree_Sheet} from '@theatre/studio/panels/SequenceEdit
|
||||||
import {usePrism} from '@theatre/react'
|
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 SheetObjectRow from './SheetObjectRow'
|
import RightSheetObjectRow from './SheetObjectRow'
|
||||||
|
|
||||||
const SheetRow: React.FC<{
|
const SheetRow: React.FC<{
|
||||||
leaf: SequenceEditorTree_Sheet
|
leaf: SequenceEditorTree_Sheet
|
||||||
|
@ -13,7 +13,7 @@ const SheetRow: React.FC<{
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{leaf.children.map((sheetObjectLeaf) => (
|
{leaf.children.map((sheetObjectLeaf) => (
|
||||||
<SheetObjectRow
|
<RightSheetObjectRow
|
||||||
layoutP={layoutP}
|
layoutP={layoutP}
|
||||||
key={'sheetObject-' + sheetObjectLeaf.sheetObject.address.objectKey}
|
key={'sheetObject-' + sheetObjectLeaf.sheetObject.address.objectKey}
|
||||||
leaf={sheetObjectLeaf}
|
leaf={sheetObjectLeaf}
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
import React, {useContext, useMemo} from 'react'
|
||||||
|
import type {IDerivation, Pointer} from '@theatre/dataverse'
|
||||||
|
import {prism, val, valueDerivation} from '@theatre/dataverse'
|
||||||
|
import type {StudioSheetItemKey, SheetId} from '@theatre/shared/utils/ids'
|
||||||
|
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||||
|
import {usePrism} from '@theatre/react'
|
||||||
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provided via context provided by {@link ProvideCollapsable}.
|
||||||
|
*/
|
||||||
|
export function useSequenceEditorCollapsable(
|
||||||
|
sheetItemKey: StudioSheetItemKey,
|
||||||
|
): ICollapsableItem {
|
||||||
|
const collapsableContext = useContext(CollapsableContext)
|
||||||
|
return useMemo(
|
||||||
|
() => collapsableContext.getCollapsable(sheetItemKey),
|
||||||
|
[sheetItemKey, collapsableContext],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this via {@link useSequenceEditorCollapsable}
|
||||||
|
*/
|
||||||
|
export type ICollapsableItem = {
|
||||||
|
isCollapsed: IDerivation<boolean>
|
||||||
|
toggleCollapsed(): void
|
||||||
|
}
|
||||||
|
type ICollapsableContext = {
|
||||||
|
getCollapsable(sheetItemKey: StudioSheetItemKey): ICollapsableItem
|
||||||
|
}
|
||||||
|
const CollapsableContext = React.createContext<ICollapsableContext>(null!)
|
||||||
|
const ProviderChildrenMemo: React.FC<{}> = React.memo(({children}) => (
|
||||||
|
<>{children}</>
|
||||||
|
))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide a context for managing collapsable items
|
||||||
|
* which are useable from {@link useSequenceEditorCollapsable}.
|
||||||
|
*/
|
||||||
|
export function ProvideCollapsable(
|
||||||
|
props: React.PropsWithChildren<{
|
||||||
|
sheetId: SheetId
|
||||||
|
layoutP: Pointer<SequenceEditorPanelLayout>
|
||||||
|
}>,
|
||||||
|
) {
|
||||||
|
const contextValue = usePrism((): ICollapsableContext => {
|
||||||
|
const studio = getStudio()
|
||||||
|
const sheetAddress = val(props.layoutP.sheet.address)
|
||||||
|
const collapsableItemsSetP =
|
||||||
|
getStudio().atomP.ahistoric.projects.stateByProjectId[
|
||||||
|
sheetAddress.projectId
|
||||||
|
].stateBySheetId[sheetAddress.sheetId].sequence
|
||||||
|
.sequenceEditorCollapsableItems
|
||||||
|
const setIsCollapsed = prism.memo(
|
||||||
|
'setIsCollapsed',
|
||||||
|
() => {
|
||||||
|
return function setIsCollapsed(
|
||||||
|
studioSheetItemKey: StudioSheetItemKey,
|
||||||
|
isCollapsed: boolean,
|
||||||
|
): void {
|
||||||
|
studio.transaction(({stateEditors}) => {
|
||||||
|
stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.sequenceEditorCollapsableItems.set(
|
||||||
|
{...sheetAddress, studioSheetItemKey, isCollapsed},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[sheetAddress],
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
getCollapsable(itemId) {
|
||||||
|
const isCollapsedD = valueDerivation(
|
||||||
|
collapsableItemsSetP.byId[itemId].isCollapsed,
|
||||||
|
).map((value) => value ?? false)
|
||||||
|
|
||||||
|
return {
|
||||||
|
isCollapsed: isCollapsedD,
|
||||||
|
toggleCollapsed() {
|
||||||
|
setIsCollapsed(itemId, !isCollapsedD.getValue())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}, [props.sheetId])
|
||||||
|
return (
|
||||||
|
<CollapsableContext.Provider value={contextValue}>
|
||||||
|
<ProviderChildrenMemo children={props.children} />
|
||||||
|
</CollapsableContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ import {
|
||||||
TitleBar_Punctuation,
|
TitleBar_Punctuation,
|
||||||
} from '@theatre/studio/panels/BasePanel/common'
|
} from '@theatre/studio/panels/BasePanel/common'
|
||||||
import type {UIPanelId} from '@theatre/shared/utils/ids'
|
import type {UIPanelId} from '@theatre/shared/utils/ids'
|
||||||
|
import {ProvideCollapsable} from './DopeSheet/useSequenceEditorCollapsable'
|
||||||
|
|
||||||
const Container = styled(PanelWrapper)`
|
const Container = styled(PanelWrapper)`
|
||||||
z-index: ${panelZIndexes.sequenceEditorPanel};
|
z-index: ${panelZIndexes.sequenceEditorPanel};
|
||||||
|
@ -163,15 +164,17 @@ const Content: React.VFC<{}> = () => {
|
||||||
return (
|
return (
|
||||||
<Container ref={containerRef}>
|
<Container ref={containerRef}>
|
||||||
<LeftBackground style={{width: `${val(layoutP.leftDims.width)}px`}} />
|
<LeftBackground style={{width: `${val(layoutP.leftDims.width)}px`}} />
|
||||||
<FrameStampPositionProvider layoutP={layoutP}>
|
<ProvideCollapsable sheetId={sheet.address.sheetId} layoutP={layoutP}>
|
||||||
<Header layoutP={layoutP} />
|
<FrameStampPositionProvider layoutP={layoutP}>
|
||||||
<DopeSheet key={key + '-dopeSheet'} layoutP={layoutP} />
|
<Header layoutP={layoutP} />
|
||||||
{graphEditorOpen && (
|
<DopeSheet key={key + '-dopeSheet'} layoutP={layoutP} />
|
||||||
<GraphEditor key={key + '-graphEditor'} layoutP={layoutP} />
|
{graphEditorOpen && (
|
||||||
)}
|
<GraphEditor key={key + '-graphEditor'} layoutP={layoutP} />
|
||||||
{graphEditorAvailable && <GraphEditorToggle layoutP={layoutP} />}
|
)}
|
||||||
<RightOverlay layoutP={layoutP} />
|
{graphEditorAvailable && <GraphEditorToggle layoutP={layoutP} />}
|
||||||
</FrameStampPositionProvider>
|
<RightOverlay layoutP={layoutP} />
|
||||||
|
</FrameStampPositionProvider>
|
||||||
|
</ProvideCollapsable>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}, [dims])
|
}, [dims])
|
||||||
|
|
|
@ -13,6 +13,7 @@ import type {
|
||||||
} from '@theatre/shared/utils/addresses'
|
} from '@theatre/shared/utils/addresses'
|
||||||
import {encodePathToProp} from '@theatre/shared/utils/addresses'
|
import {encodePathToProp} from '@theatre/shared/utils/addresses'
|
||||||
import type {
|
import type {
|
||||||
|
StudioSheetItemKey,
|
||||||
KeyframeId,
|
KeyframeId,
|
||||||
SequenceMarkerId,
|
SequenceMarkerId,
|
||||||
SequenceTrackId,
|
SequenceTrackId,
|
||||||
|
@ -482,6 +483,35 @@ namespace stateEditors {
|
||||||
).clippedSpaceRange = {...p.range}
|
).clippedSpaceRange = {...p.range}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export namespace sequenceEditorCollapsableItems {
|
||||||
|
function _ensure(p: WithoutSheetInstance<SheetAddress>) {
|
||||||
|
const seq =
|
||||||
|
stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence._ensure(
|
||||||
|
p,
|
||||||
|
)
|
||||||
|
let existing = seq.sequenceEditorCollapsableItems
|
||||||
|
if (!existing) {
|
||||||
|
existing = seq.sequenceEditorCollapsableItems =
|
||||||
|
pointableSetUtil.create()
|
||||||
|
}
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
export function set(
|
||||||
|
p: WithoutSheetInstance<SheetAddress> & {
|
||||||
|
studioSheetItemKey: StudioSheetItemKey
|
||||||
|
isCollapsed: boolean
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const collapsableSet = _ensure(p)
|
||||||
|
Object.assign(
|
||||||
|
collapsableSet,
|
||||||
|
pointableSetUtil.add(collapsableSet, p.studioSheetItemKey, {
|
||||||
|
isCollapsed: p.isCollapsed,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
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 {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
|
||||||
import type {
|
import type {ProjectId, SheetId} from '@theatre/shared/utils/ids'
|
||||||
ProjectId,
|
|
||||||
SheetId,
|
|
||||||
} from '@theatre/shared/utils/ids'
|
|
||||||
import type {IRange, StrictRecord} from '@theatre/shared/utils/types'
|
import type {IRange, StrictRecord} from '@theatre/shared/utils/types'
|
||||||
|
import type {PointableSet} from '@theatre/shared/utils/PointableSet'
|
||||||
|
import type {StudioSheetItemKey} from '@theatre/shared/utils/ids'
|
||||||
|
|
||||||
export type StudioAhistoricState = {
|
export type StudioAhistoricState = {
|
||||||
visibilityState: 'everythingIsHidden' | 'everythingIsVisible'
|
visibilityState: 'everythingIsHidden' | 'everythingIsVisible'
|
||||||
|
@ -48,6 +47,13 @@ export type StudioAhistoricState = {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
range: IRange
|
range: IRange
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sequenceEditorCollapsableItems?: PointableSet<
|
||||||
|
StudioSheetItemKey,
|
||||||
|
{
|
||||||
|
isCollapsed: boolean
|
||||||
|
}
|
||||||
|
>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
Loading…
Reference in a new issue