Add namespacing to Outline Panel
This commit is contained in:
parent
0fa9608587
commit
6b18054750
4 changed files with 129 additions and 20 deletions
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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'
|
||||||
|
|
|
@ -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,
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{objectsEntries
|
|
||||||
.sort(([pathA], [pathB]) => (pathA > pathB ? 1 : -1))
|
|
||||||
.map(([objectPath, object]) => {
|
|
||||||
return (
|
|
||||||
<ObjectItem
|
|
||||||
depth={depth}
|
|
||||||
key={'objectPath(' + objectPath + ')'}
|
|
||||||
sheetObject={object!}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const rootObject: NamespacedObjects = new Map()
|
||||||
|
objects.forEach((object) => {
|
||||||
|
addToNamespace(rootObject, object)
|
||||||
|
})
|
||||||
|
|
||||||
|
return <NamespaceTree namespace={rootObject} visualIndentation={depth} />
|
||||||
}, [sheet, depth])
|
}, [sheet, depth])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function NamespaceTree(props: {
|
||||||
|
namespace: NamespacedObjects
|
||||||
|
visualIndentation: number
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{[...props.namespace.entries()].map(([label, {object, nested}]) => {
|
||||||
|
const children = (
|
||||||
|
<>
|
||||||
|
{object && (
|
||||||
|
<ObjectItem
|
||||||
|
depth={props.visualIndentation}
|
||||||
|
key={'objectPath(' + object.address.objectKey + ')'}
|
||||||
|
// 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
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default ObjectsList
|
export default ObjectsList
|
||||||
|
|
|
@ -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[]>()
|
Loading…
Reference in a new issue