Add namespacing to Outline Panel

This commit is contained in:
Cole Lawrence 2022-05-16 16:22:00 -04:00 committed by Elliot
parent 0fa9608587
commit 6b18054750
4 changed files with 129 additions and 20 deletions

View file

@ -0,0 +1,49 @@
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
import {getObjectNamespacePath} from './getObjectNamespacePath'
/** See {@link addToNamespace} for adding to the namespace, easily. */
export type NamespacedObjects = Map<
string,
{
object?: SheetObject
nested?: NamespacedObjects
}
>
export function addToNamespace(
mutObjects: NamespacedObjects,
object: SheetObject,
) {
_addToNamespace(mutObjects, getObjectNamespacePath(object), object)
}
function _addToNamespace(
mutObjects: NamespacedObjects,
path: string[],
object: SheetObject,
) {
console.assert(path.length > 0, 'expecting path to not be empty')
const [next, ...rest] = path
let existing = mutObjects.get(next)
if (!existing) {
existing = {
nested: undefined,
object: undefined,
}
mutObjects.set(next, existing)
}
if (rest.length === 0) {
console.assert(
!existing.object,
'expect not to have existing object with same name',
{existing, object},
)
existing.object = object
} else {
if (!existing.nested) {
existing.nested = new Map()
}
_addToNamespace(existing.nested, rest, object)
}
}

View file

@ -5,10 +5,11 @@ import BaseItem from '@theatre/studio/panels/OutlinePanel/BaseItem'
import {usePrism} from '@theatre/react' import {usePrism} from '@theatre/react'
import {getOutlineSelection} from '@theatre/studio/selectors' import {getOutlineSelection} from '@theatre/studio/selectors'
export const ObjectItem: React.FC<{ export const ObjectItem: React.VFC<{
sheetObject: SheetObject sheetObject: SheetObject
depth: number depth: number
}> = ({sheetObject, depth}) => { overrideLabel?: string
}> = ({sheetObject, depth, overrideLabel}) => {
const select = () => { const select = () => {
getStudio()!.transaction(({stateEditors}) => { getStudio()!.transaction(({stateEditors}) => {
stateEditors.studio.historic.panels.outline.selection.set([sheetObject]) stateEditors.studio.historic.panels.outline.selection.set([sheetObject])
@ -20,7 +21,7 @@ export const ObjectItem: React.FC<{
return ( return (
<BaseItem <BaseItem
select={select} select={select}
label={sheetObject.address.objectKey} label={overrideLabel ?? sheetObject.address.objectKey}
depth={depth} depth={depth}
selectionStatus={ selectionStatus={
selection.includes(sheetObject) ? 'selected' : 'not-selected' selection.includes(sheetObject) ? 'selected' : 'not-selected'

View file

@ -4,6 +4,10 @@ import {val} from '@theatre/dataverse'
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import {ObjectItem} from './ObjectItem' import {ObjectItem} from './ObjectItem'
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
import BaseItem from '@theatre/studio/panels/OutlinePanel/BaseItem'
import type {NamespacedObjects} from './NamespacedObjects'
import {addToNamespace} from './NamespacedObjects'
export const Li = styled.li<{isSelected: boolean}>` export const Li = styled.li<{isSelected: boolean}>`
color: ${(props) => (props.isSelected ? 'white' : 'hsl(1, 1%, 80%)')}; color: ${(props) => (props.isSelected ? 'white' : 'hsl(1, 1%, 80%)')};
@ -14,25 +18,61 @@ const ObjectsList: React.FC<{
sheet: Sheet sheet: Sheet
}> = ({sheet, depth}) => { }> = ({sheet, depth}) => {
return usePrism(() => { return usePrism(() => {
const objects = val(sheet.objectsP) const objectsMap = val(sheet.objectsP)
const objectsEntries = Object.entries(objects) const objects = Object.values(objectsMap).filter(
(a): a is SheetObject => a != null,
)
const rootObject: NamespacedObjects = new Map()
objects.forEach((object) => {
addToNamespace(rootObject, object)
})
return <NamespaceTree namespace={rootObject} visualIndentation={depth} />
}, [sheet, depth])
}
function NamespaceTree(props: {
namespace: NamespacedObjects
visualIndentation: number
}) {
return ( return (
<> <>
{objectsEntries {[...props.namespace.entries()].map(([label, {object, nested}]) => {
.sort(([pathA], [pathB]) => (pathA > pathB ? 1 : -1)) const children = (
.map(([objectPath, object]) => { <>
return ( {object && (
<ObjectItem <ObjectItem
depth={depth} depth={props.visualIndentation}
key={'objectPath(' + objectPath + ')'} key={'objectPath(' + object.address.objectKey + ')'}
sheetObject={object!} // object entries should not allow this to be undefined
sheetObject={object}
overrideLabel={label}
/> />
)}
{nested && (
<NamespaceTree
namespace={nested}
visualIndentation={props.visualIndentation + 1}
/>
)}
</>
)
return nested ? (
<BaseItem
selectionStatus="not-selectable"
label={label}
depth={props.visualIndentation}
children={children}
/>
) : (
// if we don't have any nested items, just render the children with no wrapper, here.
children
) )
})} })}
</> </>
) )
}, [sheet, depth])
} }
export default ObjectsList export default ObjectsList

View file

@ -0,0 +1,19 @@
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
export function getObjectNamespacePath(object: SheetObject): string[] {
let existing = OBJECT_SPLITS_MEMO.get(object)
if (!existing) {
existing = object.address.objectKey.split(
RE_SPLIT_BY_SLASH_WITHOUT_WHITESPACE,
)
console.assert(existing.length > 0, 'expected not empty')
OBJECT_SPLITS_MEMO.set(object, existing)
}
return existing
}
/**
* Relying on the fact we try to "sanitize paths" earlier.
* Go look for `sanifySlashedPath` in a `utils/slashedPaths.ts`.
*/
const RE_SPLIT_BY_SLASH_WITHOUT_WHITESPACE = /\s*\/\s*/g
const OBJECT_SPLITS_MEMO = new WeakMap<SheetObject, string[]>()