diff --git a/theatre/studio/src/panels/OutlinePanel/ObjectsList/NamespacedObjects.tsx b/theatre/studio/src/panels/OutlinePanel/ObjectsList/NamespacedObjects.tsx new file mode 100644 index 0000000..4cf1780 --- /dev/null +++ b/theatre/studio/src/panels/OutlinePanel/ObjectsList/NamespacedObjects.tsx @@ -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) + } +} diff --git a/theatre/studio/src/panels/OutlinePanel/ObjectsList/ObjectItem.tsx b/theatre/studio/src/panels/OutlinePanel/ObjectsList/ObjectItem.tsx index a7823fd..fc6d337 100644 --- a/theatre/studio/src/panels/OutlinePanel/ObjectsList/ObjectItem.tsx +++ b/theatre/studio/src/panels/OutlinePanel/ObjectsList/ObjectItem.tsx @@ -5,10 +5,11 @@ import BaseItem from '@theatre/studio/panels/OutlinePanel/BaseItem' import {usePrism} from '@theatre/react' import {getOutlineSelection} from '@theatre/studio/selectors' -export const ObjectItem: React.FC<{ +export const ObjectItem: React.VFC<{ sheetObject: SheetObject depth: number -}> = ({sheetObject, depth}) => { + overrideLabel?: string +}> = ({sheetObject, depth, overrideLabel}) => { const select = () => { getStudio()!.transaction(({stateEditors}) => { stateEditors.studio.historic.panels.outline.selection.set([sheetObject]) @@ -20,7 +21,7 @@ export const ObjectItem: React.FC<{ return ( ` color: ${(props) => (props.isSelected ? 'white' : 'hsl(1, 1%, 80%)')}; @@ -14,25 +18,61 @@ const ObjectsList: React.FC<{ sheet: Sheet }> = ({sheet, depth}) => { return usePrism(() => { - const objects = val(sheet.objectsP) - const objectsEntries = Object.entries(objects) - - return ( - <> - {objectsEntries - .sort(([pathA], [pathB]) => (pathA > pathB ? 1 : -1)) - .map(([objectPath, object]) => { - return ( - - ) - })} - + const objectsMap = val(sheet.objectsP) + const objects = Object.values(objectsMap).filter( + (a): a is SheetObject => a != null, ) + + const rootObject: NamespacedObjects = new Map() + objects.forEach((object) => { + addToNamespace(rootObject, object) + }) + + return }, [sheet, depth]) } +function NamespaceTree(props: { + namespace: NamespacedObjects + visualIndentation: number +}) { + return ( + <> + {[...props.namespace.entries()].map(([label, {object, nested}]) => { + const children = ( + <> + {object && ( + + )} + {nested && ( + + )} + + ) + + return nested ? ( + + ) : ( + // if we don't have any nested items, just render the children with no wrapper, here. + children + ) + })} + + ) +} + export default ObjectsList diff --git a/theatre/studio/src/panels/OutlinePanel/ObjectsList/getObjectNamespacePath.tsx b/theatre/studio/src/panels/OutlinePanel/ObjectsList/getObjectNamespacePath.tsx new file mode 100644 index 0000000..a823af3 --- /dev/null +++ b/theatre/studio/src/panels/OutlinePanel/ObjectsList/getObjectNamespacePath.tsx @@ -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()