Refactor collapsing to be calculated in tree.

Co-authored-by: Fülöp <fulopkovacs@users.noreply.github.com>
This commit is contained in:
vezwork 2022-05-18 14:47:49 -04:00 committed by Cole Lawrence
parent bc5e687250
commit b84f2eb106
14 changed files with 132 additions and 192 deletions

View file

@ -5,8 +5,6 @@ import React from 'react'
import {HiOutlineChevronRight} from 'react-icons/all'
import styled from 'styled-components'
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}>`
--depth: ${(props) => props.depth};
@ -71,9 +69,10 @@ const AnyCompositeRow: React.FC<{
leaf: SequenceEditorTree_Row<unknown>
label: React.ReactNode
toggleSelect?: VoidFn
toggleCollapsed: VoidFn
isSelected?: boolean
isSelectable?: boolean
collapsable?: ICollapsableItem
isCollapsed: boolean
}> = ({
leaf,
label,
@ -81,10 +80,10 @@ const AnyCompositeRow: React.FC<{
isSelectable,
isSelected,
toggleSelect,
collapsable,
toggleCollapsed,
isCollapsed,
}) => {
const hasChildren = Array.isArray(children) && children.length > 0
const isCollapsed = useVal(collapsable?.isCollapsed) ?? false
return (
<Container depth={leaf.depth}>
@ -97,15 +96,12 @@ const AnyCompositeRow: React.FC<{
onClick={toggleSelect}
isEven={leaf.n % 2 === 0}
>
<Head_Icon
isCollapsed={isCollapsed}
onClick={() => collapsable?.toggleCollapsed()}
>
<Head_Icon isCollapsed={isCollapsed} onClick={toggleCollapsed}>
<HiOutlineChevronRight />
</Head_Icon>
<Head_Label>{label}</Head_Label>
</Header>
{hasChildren && !isCollapsed && <Children>{children}</Children>}
{hasChildren && <Children>{children}</Children>}
</Container>
)
}

View file

@ -6,9 +6,7 @@ import {usePrism} from '@theatre/react'
import React from 'react'
import AnyCompositeRow from './AnyCompositeRow'
import PrimitivePropRow from './PrimitivePropRow'
import {useSequenceEditorCollapsable} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/useSequenceEditorCollapsable'
import {createStudioSheetItemKey} from '@theatre/shared/utils/ids'
import {setCollapsedSheetObjectOrCompoundProp} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp'
export const decideRowByPropType = (
leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_PrimitiveProp,
): React.ReactElement =>
@ -27,19 +25,15 @@ export const decideRowByPropType = (
const PropWithChildrenRow: React.VFC<{
leaf: SequenceEditorTree_PropWithChildren
}> = ({leaf}) => {
const collapsable = useSequenceEditorCollapsable(
createStudioSheetItemKey.forSheetObjectProp(
leaf.sheetObject,
leaf.pathToProp,
),
)
return usePrism(() => {
return (
<AnyCompositeRow
leaf={leaf}
label={leaf.pathToProp[leaf.pathToProp.length - 1]}
collapsable={collapsable}
isCollapsed={leaf.isCollapsed}
toggleCollapsed={() =>
setCollapsedSheetObjectOrCompoundProp(!leaf.isCollapsed, leaf)
}
>
{leaf.children.map((propLeaf) => decideRowByPropType(propLeaf))}
</AnyCompositeRow>

View file

@ -3,22 +3,20 @@ import {usePrism} from '@theatre/react'
import React from 'react'
import AnyCompositeRow from './AnyCompositeRow'
import {decideRowByPropType} from './PropWithChildrenRow'
import {useSequenceEditorCollapsable} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/useSequenceEditorCollapsable'
import {createStudioSheetItemKey} from '@theatre/shared/utils/ids'
import {setCollapsedSheetObjectOrCompoundProp} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp'
const LeftSheetObjectRow: React.VFC<{
leaf: SequenceEditorTree_SheetObject
}> = ({leaf}) => {
const collapsable = useSequenceEditorCollapsable(
createStudioSheetItemKey.forSheetObject(leaf.sheetObject),
)
return usePrism(() => {
return (
<AnyCompositeRow
leaf={leaf}
label={leaf.sheetObject.address.objectKey}
collapsable={collapsable}
isCollapsed={leaf.isCollapsed}
toggleCollapsed={() =>
setCollapsedSheetObjectOrCompoundProp(!leaf.isCollapsed, leaf)
}
>
{leaf.children.map((leaf) => decideRowByPropType(leaf))}
</AnyCompositeRow>

View file

@ -27,13 +27,15 @@ const PrimitivePropRow: React.FC<{
console.error(
`trackData type ${trackData?.type} is not yet supported on the sequence editor`,
)
return <RightRow leaf={leaf} node={<div />}></RightRow>
return (
<RightRow leaf={leaf} isCollapsed={false} node={<div />}></RightRow>
)
} else {
const node = (
<KeyframedTrack layoutP={layoutP} trackData={trackData} leaf={leaf} />
)
return <RightRow leaf={leaf} node={node}></RightRow>
return <RightRow leaf={leaf} isCollapsed={false} node={node}></RightRow>
}
}, [leaf, layoutP])
}

View file

@ -8,8 +8,6 @@ import type {Pointer} from '@theatre/dataverse'
import React from 'react'
import PrimitivePropRow from './PrimitivePropRow'
import RightRow from './Row'
import {useSequenceEditorCollapsable} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/useSequenceEditorCollapsable'
import {createStudioSheetItemKey} from '@theatre/shared/utils/ids'
export const decideRowByPropType = (
leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_PrimitiveProp,
@ -33,18 +31,11 @@ const PropWithChildrenRow: React.VFC<{
leaf: SequenceEditorTree_PropWithChildren
layoutP: Pointer<SequenceEditorPanelLayout>
}> = ({leaf, layoutP}) => {
const collapsable = useSequenceEditorCollapsable(
createStudioSheetItemKey.forSheetObjectProp(
leaf.sheetObject,
leaf.pathToProp,
),
)
return usePrism(() => {
const node = <div />
return (
<RightRow leaf={leaf} node={node} collapsable={collapsable}>
<RightRow leaf={leaf} node={node} isCollapsed={leaf.isCollapsed}>
{leaf.children.map((propLeaf) =>
decideRowByPropType(propLeaf, layoutP),
)}

View file

@ -1,8 +1,6 @@
import type {SequenceEditorTree_Row} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
import React from 'react'
import styled from 'styled-components'
import type {ICollapsableItem} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/useSequenceEditorCollapsable'
import {useVal} from '@theatre/react'
const Container = styled.li<{}>`
margin: 0;
@ -40,10 +38,9 @@ const Children = styled.ul`
const RightRow: React.FC<{
leaf: SequenceEditorTree_Row<unknown>
node: React.ReactElement
collapsable?: ICollapsableItem
}> = ({leaf, children, node, collapsable}) => {
isCollapsed: boolean
}> = ({leaf, children, node, isCollapsed}) => {
const hasChildren = Array.isArray(children) && children.length > 0
const isCollapsed = useVal(collapsable?.isCollapsed) ?? false
return (
<Container>
@ -53,7 +50,7 @@ const RightRow: React.FC<{
>
{node}
</NodeWrapper>
{hasChildren && !isCollapsed && <Children>{children}</Children>}
{hasChildren && <Children>{children}</Children>}
</Container>
)
}

View file

@ -5,20 +5,15 @@ import type {Pointer} from '@theatre/dataverse'
import React from 'react'
import {decideRowByPropType} from './PropWithChildrenRow'
import RightRow from './Row'
import {useSequenceEditorCollapsable} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/useSequenceEditorCollapsable'
import {createStudioSheetItemKey} from '@theatre/shared/utils/ids'
const RightSheetObjectRow: React.VFC<{
leaf: SequenceEditorTree_SheetObject
layoutP: Pointer<SequenceEditorPanelLayout>
}> = ({leaf, layoutP}) => {
const collapsable = useSequenceEditorCollapsable(
createStudioSheetItemKey.forSheetObject(leaf.sheetObject),
)
return usePrism(() => {
const node = <div />
return (
<RightRow leaf={leaf} node={node} collapsable={collapsable}>
<RightRow leaf={leaf} node={node} isCollapsed={leaf.isCollapsed}>
{leaf.children.map((leaf) => decideRowByPropType(leaf, layoutP))}
</RightRow>
)

View file

@ -0,0 +1,36 @@
import type {StudioSheetItemKey} from '@theatre/shared/utils/ids'
import {createStudioSheetItemKey} from '@theatre/shared/utils/ids'
import getStudio from '@theatre/studio/getStudio'
import type {PathToProp} from '@theatre/shared/utils/addresses'
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
// discriminated union
export function setCollapsedSheetObjectOrCompoundProp(
isCollapsed: boolean,
toCollapse:
| {
sheetObject: SheetObject
}
| {
sheetObject: SheetObject
pathToProp: PathToProp
},
) {
const itemKey: StudioSheetItemKey =
'pathToProp' in toCollapse
? createStudioSheetItemKey.forSheetObjectProp(
toCollapse.sheetObject,
toCollapse.pathToProp,
)
: createStudioSheetItemKey.forSheetObject(toCollapse.sheetObject)
getStudio().transaction(({stateEditors}) => {
stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.sequenceEditorCollapsableItems.set(
{
...toCollapse.sheetObject.address,
studioSheetItemKey: itemKey,
isCollapsed,
},
)
})
}

View file

@ -1,91 +0,0 @@
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>
)
}

View file

@ -28,7 +28,6 @@ import {
TitleBar_Punctuation,
} from '@theatre/studio/panels/BasePanel/common'
import type {UIPanelId} from '@theatre/shared/utils/ids'
import {ProvideCollapsable} from './DopeSheet/useSequenceEditorCollapsable'
const Container = styled(PanelWrapper)`
z-index: ${panelZIndexes.sequenceEditorPanel};
@ -164,17 +163,15 @@ const Content: React.VFC<{}> = () => {
return (
<Container ref={containerRef}>
<LeftBackground style={{width: `${val(layoutP.leftDims.width)}px`}} />
<ProvideCollapsable sheetId={sheet.address.sheetId} layoutP={layoutP}>
<FrameStampPositionProvider layoutP={layoutP}>
<Header layoutP={layoutP} />
<DopeSheet key={key + '-dopeSheet'} layoutP={layoutP} />
{graphEditorOpen && (
<GraphEditor key={key + '-graphEditor'} layoutP={layoutP} />
)}
{graphEditorAvailable && <GraphEditorToggle layoutP={layoutP} />}
<RightOverlay layoutP={layoutP} />
</FrameStampPositionProvider>
</ProvideCollapsable>
<FrameStampPositionProvider layoutP={layoutP}>
<Header layoutP={layoutP} />
<DopeSheet key={key + '-dopeSheet'} layoutP={layoutP} />
{graphEditorOpen && (
<GraphEditor key={key + '-graphEditor'} layoutP={layoutP} />
)}
{graphEditorAvailable && <GraphEditorToggle layoutP={layoutP} />}
<RightOverlay layoutP={layoutP} />
</FrameStampPositionProvider>
</Container>
)
}, [dims])

View file

@ -186,7 +186,11 @@ export function sequenceEditorPanelLayout(
.stateBySheetId[sheet.address.sheetId]
return prism(() => {
const tree = subPrism('tree', () => calculateSequenceEditorTree(sheet), [])
const tree = subPrism(
'tree',
() => calculateSequenceEditorTree(sheet, studio),
[],
)
const panelDims = val(panelDimsP)
const graphEditorState = val(

View file

@ -9,10 +9,12 @@ import type {IPropPathToTrackIdTree} from '@theatre/core/sheetObjects/SheetObjec
import type Sheet from '@theatre/core/sheets/Sheet'
import type {PathToProp} from '@theatre/shared/utils/addresses'
import type {SequenceTrackId} from '@theatre/shared/utils/ids'
import {createStudioSheetItemKey} from '@theatre/shared/utils/ids'
import type {$FixMe, $IntentionalAny} from '@theatre/shared/utils/types'
import {prism, val} from '@theatre/dataverse'
import {prism, val, valueDerivation} from '@theatre/dataverse'
import logger from '@theatre/shared/logger'
import {titleBarHeight} from '@theatre/studio/panels/BasePanel/common'
import type {Studio} from '@theatre/studio/Studio'
export type SequenceEditorTree_Row<Type> = {
type: Type
@ -32,6 +34,7 @@ export type SequenceEditorTree_Sheet = SequenceEditorTree_Row<'sheet'> & {
export type SequenceEditorTree_SheetObject =
SequenceEditorTree_Row<'sheetObject'> & {
isCollapsed: boolean
sheetObject: SheetObject
children: Array<
SequenceEditorTree_PropWithChildren | SequenceEditorTree_PrimitiveProp
@ -40,6 +43,7 @@ export type SequenceEditorTree_SheetObject =
export type SequenceEditorTree_PropWithChildren =
SequenceEditorTree_Row<'propWithChildren'> & {
isCollapsed: boolean
sheetObject: SheetObject
pathToProp: PathToProp
children: Array<
@ -62,13 +66,14 @@ export type SequenceEditorTree_AllRowTypes =
| SequenceEditorTree_PropWithChildren
| SequenceEditorTree_PrimitiveProp
const heightOfAnyTitle = 28
const HEIGHT_OF_ANY_TITLE = 28
/**
* Must run inside prism()
*/
export const calculateSequenceEditorTree = (
sheet: Sheet,
studio: Studio,
): SequenceEditorTree => {
prism.ensurePrism()
let topSoFar = titleBarHeight
@ -81,12 +86,15 @@ export const calculateSequenceEditorTree = (
top: topSoFar,
depth: -1,
n: nSoFar,
nodeHeight: 0,
heightIncludingChildren: -1, // will defined this later
nodeHeight: 0, // always 0
heightIncludingChildren: -1, // will define this later
}
topSoFar += tree.nodeHeight
nSoFar += 1
const collapsableP =
studio.atomP.ahistoric.projects.stateByProjectId[sheet.address.projectId]
.stateBySheetId[sheet.address.sheetId].sequence.collapsableItems
for (const sheetObject of Object.values(val(sheet.objectsP))) {
if (sheetObject) {
addObject(sheetObject, tree.children, tree.depth + 1)
@ -105,29 +113,35 @@ export const calculateSequenceEditorTree = (
if (Object.keys(trackSetups).length === 0) return
const isCollapsedP =
collapsableP.byId[createStudioSheetItemKey.forSheetObject(sheetObject)]
.isCollapsed
const isCollapsed = valueDerivation(isCollapsedP).getValue() ?? false
const row: SequenceEditorTree_SheetObject = {
type: 'sheetObject',
isCollapsed: isCollapsed,
top: topSoFar,
children: [],
depth: level,
n: nSoFar,
sheetObject: sheetObject,
nodeHeight: heightOfAnyTitle,
nodeHeight: HEIGHT_OF_ANY_TITLE,
heightIncludingChildren: -1,
}
arrayOfChildren.push(row)
nSoFar += 1
topSoFar += heightOfAnyTitle
addProps(
sheetObject,
trackSetups,
[],
sheetObject.template.config,
row.children,
level + 1,
)
topSoFar += HEIGHT_OF_ANY_TITLE
if (!isCollapsed) {
addProps(
sheetObject,
trackSetups,
[],
sheetObject.template.config,
row.children,
level + 1,
)
}
row.heightIncludingChildren = topSoFar - row.top
}
@ -201,31 +215,39 @@ export const calculateSequenceEditorTree = (
>,
level: number,
) {
const isCollapsedP =
collapsableP.byId[
createStudioSheetItemKey.forSheetObjectProp(sheetObject, pathToProp)
].isCollapsed
const isCollapsed = valueDerivation(isCollapsedP).getValue() ?? false
const row: SequenceEditorTree_PropWithChildren = {
type: 'propWithChildren',
isCollapsed: isCollapsed,
pathToProp,
sheetObject: sheetObject,
top: topSoFar,
children: [],
nodeHeight: heightOfAnyTitle,
nodeHeight: HEIGHT_OF_ANY_TITLE,
heightIncludingChildren: -1,
depth: level,
trackMapping,
n: nSoFar,
}
topSoFar += heightOfAnyTitle
nSoFar += 1
arrayOfChildren.push(row)
topSoFar += HEIGHT_OF_ANY_TITLE
if (!isCollapsed) {
nSoFar += 1
addProps(
sheetObject,
trackMapping,
pathToProp,
conf,
row.children,
level + 1,
)
addProps(
sheetObject,
trackMapping,
pathToProp,
conf,
row.children,
level + 1,
)
}
row.heightIncludingChildren = topSoFar - row.top
}
@ -246,14 +268,14 @@ export const calculateSequenceEditorTree = (
sheetObject: sheetObject,
pathToProp,
top: topSoFar,
nodeHeight: heightOfAnyTitle,
heightIncludingChildren: heightOfAnyTitle,
nodeHeight: HEIGHT_OF_ANY_TITLE,
heightIncludingChildren: HEIGHT_OF_ANY_TITLE,
trackId,
n: nSoFar,
}
arrayOfChildren.push(row)
nSoFar += 1
topSoFar += heightOfAnyTitle
topSoFar += HEIGHT_OF_ANY_TITLE
}
return tree

View file

@ -490,10 +490,9 @@ namespace stateEditors {
stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence._ensure(
p,
)
let existing = seq.sequenceEditorCollapsableItems
let existing = seq.collapsableItems
if (!existing) {
existing = seq.sequenceEditorCollapsableItems =
pointableSetUtil.create()
existing = seq.collapsableItems = pointableSetUtil.create()
}
return existing
}

View file

@ -48,7 +48,7 @@ export type StudioAhistoricState = {
range: IRange
}
sequenceEditorCollapsableItems?: PointableSet<
collapsableItems?: PointableSet<
StudioSheetItemKey,
{
isCollapsed: boolean