Put SnapshotEditor inside a Pane

This commit is contained in:
Aria Minaei 2021-07-14 18:37:32 +02:00
parent 921bc44270
commit 64273366ed
17 changed files with 395 additions and 50 deletions

View file

@ -172,7 +172,7 @@ type IEffect = {
const memosWeakMap = new WeakMap<PrismScope, Record<string, IMemo>>() const memosWeakMap = new WeakMap<PrismScope, Record<string, IMemo>>()
type IMemo = { type IMemo = {
deps: undefined | unknown[] deps: undefined | unknown[] | ReadonlyArray<unknown>
cachedValue: unknown cachedValue: unknown
} }
@ -229,8 +229,8 @@ function effect(key: string, cb: () => () => void, deps?: unknown[]): void {
} }
function depsHaveChanged( function depsHaveChanged(
oldDeps: undefined | unknown[], oldDeps: undefined | unknown[] | ReadonlyArray<unknown>,
newDeps: undefined | unknown[], newDeps: undefined | unknown[] | ReadonlyArray<unknown>,
): boolean { ): boolean {
if (oldDeps === undefined || newDeps === undefined) { if (oldDeps === undefined || newDeps === undefined) {
return true return true
@ -244,7 +244,7 @@ function depsHaveChanged(
function memo<T>( function memo<T>(
key: string, key: string,
fn: () => T, fn: () => T,
deps: undefined | $IntentionalAny[], deps: undefined | $IntentionalAny[] | ReadonlyArray<$IntentionalAny>,
): T { ): T {
const scope = hookScopeStack.peek() const scope = hookScopeStack.peek()
if (!scope) { if (!scope) {

View file

@ -62,19 +62,17 @@ const EditorScene = () => {
) )
} }
const Wrapper = styled.div<{visible: boolean}>` const Wrapper = styled.div`
tab-size: 4; tab-size: 4;
line-height: 1.15; /* 1 */ line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */
margin: 0; margin: 0;
position: fixed; position: absolute;
top: 0px; top: 0px;
right: 0px; right: 0px;
bottom: 0px; bottom: 0px;
left: 0px; left: 0px;
z-index: 50;
display: ${(props) => (props.visible ? 'block' : 'none')};
` `
const CanvasWrapper = styled.div` const CanvasWrapper = styled.div`
@ -83,7 +81,9 @@ const CanvasWrapper = styled.div`
height: 100%; height: 100%;
` `
const Editor: VFC = () => { const SnapshotEditor: VFC = () => {
console.log('Snapshot editor!!')
const [editorObject, sceneSnapshot, initialEditorCamera, createSnapshot] = const [editorObject, sceneSnapshot, initialEditorCamera, createSnapshot] =
useEditorStore( useEditorStore(
(state) => [ (state) => [
@ -95,10 +95,18 @@ const Editor: VFC = () => {
shallow, shallow,
) )
const editorOpen = !!useVal(editorObject?.props.isOpen) const editorOpen = true
useLayoutEffect(() => { useLayoutEffect(() => {
let timeout: NodeJS.Timeout | undefined
if (editorOpen) { if (editorOpen) {
createSnapshot() // a hack to make sure all the scene's props are
// applied before we take a snapshot
timeout = setTimeout(createSnapshot, 100)
}
return () => {
if (timeout !== undefined) {
clearTimeout(timeout)
}
} }
}, [editorOpen]) }, [editorOpen])
@ -109,8 +117,7 @@ const Editor: VFC = () => {
<StyleSheetManager disableVendorPrefixes> <StyleSheetManager disableVendorPrefixes>
<> <>
<GlobalStyle /> <GlobalStyle />
<Wrapper id="theatre-plugin-r3f-root" visible={true}> <Wrapper>
{/* <Toolbar /> */}
{sceneSnapshot ? ( {sceneSnapshot ? (
<> <>
<CanvasWrapper> <CanvasWrapper>
@ -140,4 +147,4 @@ const Editor: VFC = () => {
) )
} }
export default Editor export default SnapshotEditor

View file

@ -10,7 +10,7 @@ import {Vector3} from 'three'
import type {$FixMe} from '@theatre/shared/utils/types' import type {$FixMe} from '@theatre/shared/utils/types'
import studio from '@theatre/studio' import studio from '@theatre/studio'
import {getSelected} from '../useSelected' import {getSelected} from '../useSelected'
import {useVal} from '@theatre/dataverse-react' import {usePrism, useVal} from '@theatre/dataverse-react'
import IconButton from './utils/IconButton' import IconButton from './utils/IconButton'
import styled from 'styled-components' import styled from 'styled-components'
@ -19,6 +19,10 @@ const ToolGroup = styled.div`
` `
const Toolbar: VFC = () => { const Toolbar: VFC = () => {
usePrism(() => {
const panes = studio.getPanesOfType('snapshotEditor')
}, [])
const [editorObject] = useEditorStore( const [editorObject] = useEditorStore(
(state) => [state.editorObject], (state) => [state.editorObject],
shallow, shallow,
@ -35,6 +39,15 @@ const Toolbar: VFC = () => {
return ( return (
<> <>
<ToolGroup>
<button
onClick={() => {
studio.createPane('snapshotEditor')
}}
>
Create snapshot
</button>
</ToolGroup>
<ToolGroup> <ToolGroup>
<TransformControlsModeSelect <TransformControlsModeSelect
value={transformControlsMode} value={transformControlsMode}

View file

@ -1,6 +1,4 @@
import React from 'react' import SnapshotEditor from './components/SnapshotEditor'
import {render} from 'react-dom'
import Editor from './components/Editor'
export {default as EditorHelper} from './components/EditorHelper' export {default as EditorHelper} from './components/EditorHelper'
export type {EditorHelperProps} from './components/EditorHelper' export type {EditorHelperProps} from './components/EditorHelper'
@ -10,6 +8,7 @@ export {bindToCanvas} from './store'
export type {EditableState, BindFunction} from './store' export type {EditableState, BindFunction} from './store'
import studio from '@theatre/studio' import studio from '@theatre/studio'
import Toolbar from './components/Toolbar/Toolbar' import Toolbar from './components/Toolbar/Toolbar'
import {types} from '@theatre/core'
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
studio.extend({ studio.extend({
@ -17,9 +16,14 @@ if (process.env.NODE_ENV === 'development') {
globalToolbar: { globalToolbar: {
component: Toolbar, component: Toolbar,
}, },
panes: [
{
class: 'snapshotEditor',
dataType: types.compound({
grosse: types.number(20),
}),
component: SnapshotEditor,
},
],
}) })
const editorRoot = document.createElement('div')
document.body.appendChild(editorRoot)
render(<Editor />, editorRoot)
} }

View file

@ -1,10 +1,12 @@
import type {Studio} from '@theatre/studio/Studio' import type {Studio} from '@theatre/studio/Studio'
import projectsSingleton from './projects/projectsSingleton' import projectsSingleton from './projects/projectsSingleton'
import {privateAPI} from './privateAPIs' import {privateAPI} from './privateAPIs'
import * as coreExports from './coreExports'
export type CoreBits = { export type CoreBits = {
projectsP: typeof projectsSingleton.atom.pointer.projects projectsP: typeof projectsSingleton.atom.pointer.projects
privateAPI: typeof privateAPI privateAPI: typeof privateAPI
coreExports: typeof coreExports
} }
export default class CoreBundle { export default class CoreBundle {
@ -27,6 +29,7 @@ export default class CoreBundle {
const bits: CoreBits = { const bits: CoreBits = {
projectsP: projectsSingleton.atom.pointer.projects, projectsP: projectsSingleton.atom.pointer.projects,
privateAPI: privateAPI, privateAPI: privateAPI,
coreExports,
} }
callback(bits) callback(bits)

View file

@ -0,0 +1,121 @@
import {prism, val} from '@theatre/dataverse'
import {emptyArray} from '@theatre/shared/utils'
import SimpleCache from '@theatre/shared/utils/SimpleCache'
import type {$FixMe, $IntentionalAny} from '@theatre/shared/utils/types'
import type {Studio} from './Studio'
import type {PaneInstance} from './TheatreStudio'
export default class PaneManager {
private readonly _cache = new SimpleCache()
constructor(private readonly _studio: Studio) {
this._instantiatePanesAsTheyComeIn()
}
private _instantiatePanesAsTheyComeIn() {
const allPanesD = this._getAllPanes()
allPanesD.changesWithoutValues().tap(() => {
allPanesD.getValue()
})
}
private _getAllPanes() {
return this._cache.get('_getAllPanels()', () =>
prism((): {[instanceId in string]?: PaneInstance<string>} => {
const core = val(this._studio.coreP)
if (!core) return {}
const instanceDescriptors = val(
this._studio.atomP.historic.panelInstanceDesceriptors,
)
const paneClasses = val(
this._studio.atomP.ephemeral.extensions.paneClasses,
)
const instances: {[instanceId in string]?: PaneInstance<string>} = {}
for (const [, instanceDescriptor] of Object.entries(
instanceDescriptors,
)) {
const panelClass = paneClasses[instanceDescriptor!.paneClass]
if (!panelClass) continue
const {instanceId} = instanceDescriptor!
const {extensionId, classDefinition: definition} = panelClass
const instance = prism.memo(
`instance-${instanceDescriptor!.instanceId}`,
() => {
const object = this._studio
.getExtensionSheet(extensionId, core)
.object(
'Pane: ' + instanceId,
null,
core.types.compound({
panelThingy: core.types.boolean(false),
}),
) as $FixMe
const inst: PaneInstance<$IntentionalAny> = {
extensionId,
instanceId,
object,
definition,
}
return inst
},
emptyArray,
)
instances[instanceId] = instance
}
return instances
}),
)
}
get allPanesD() {
return this._getAllPanes()
}
getPanesOfType<PaneClass extends string>(
paneClass: PaneClass,
): PaneInstance<PaneClass>[] {
return []
}
createPane<PaneClass extends string>(
paneClass: PaneClass,
): PaneInstance<PaneClass> {
const core = this._studio.core
if (!core) {
throw new Error(
`Can't create a pane because @theatre/core is not yet loaded`,
)
}
const extensionId = val(
this._studio.atomP.ephemeral.extensions.paneClasses[paneClass]
.extensionId,
)
const allPaneInstances = val(
this._studio.atomP.historic.panelInstanceDesceriptors,
)
let instanceId!: string
for (let i = 1; i < 1000; i++) {
instanceId = `${paneClass} #${i}`
if (!allPaneInstances[instanceId]) break
}
if (!extensionId) {
throw new Error(`Pance class "${paneClass}" is not registered.`)
}
this._studio.transaction(({drafts}) => {
drafts.historic.panelInstanceDesceriptors[instanceId] = {
instanceId,
paneClass,
}
})
return this._getAllPanes().getValue()[instanceId]!
}
}

View file

@ -1,5 +1,4 @@
import Scrub from '@theatre/studio/Scrub' import Scrub from '@theatre/studio/Scrub'
import type {FullStudioState} from '@theatre/studio/store'
import type {StudioHistoricState} from '@theatre/studio/store/types/historic' import type {StudioHistoricState} from '@theatre/studio/store/types/historic'
import UI from '@theatre/studio/UI' import UI from '@theatre/studio/UI'
import type {Pointer} from '@theatre/dataverse' import type {Pointer} from '@theatre/dataverse'
@ -14,10 +13,14 @@ import TheatreStudio from './TheatreStudio'
import {nanoid} from 'nanoid/non-secure' import {nanoid} from 'nanoid/non-secure'
import type Project from '@theatre/core/projects/Project' import type Project from '@theatre/core/projects/Project'
import type {CoreBits} from '@theatre/core/CoreBundle' import type {CoreBits} from '@theatre/core/CoreBundle'
import type {privateAPI} from '@theatre/core/privateAPIs' import SimpleCache from '@theatre/shared/utils/SimpleCache'
import type {IProject, ISheet} from '@theatre/core'
import PaneManager from './PaneManager'
import type * as _coreExports from '@theatre/core/coreExports'
export type CoreExports = typeof _coreExports
export class Studio { export class Studio {
readonly atomP: Pointer<FullStudioState>
readonly ui!: UI readonly ui!: UI
readonly publicApi: IStudio readonly publicApi: IStudio
readonly address: {studioId: string} readonly address: {studioId: string}
@ -28,23 +31,27 @@ export class Studio {
this._projectsProxy.pointer this._projectsProxy.pointer
private readonly _store = new StudioStore() private readonly _store = new StudioStore()
private _corePrivateApi: typeof privateAPI | undefined private _corePrivateApi: CoreBits['privateAPI'] | undefined
private _extensions: Atom<{byId: Record<string, IExtension>}> = new Atom({ private readonly _cache = new SimpleCache()
byId: {}, readonly paneManager: PaneManager
})
readonly extensionsP = this._extensions.pointer.byId private _coreAtom = new Atom<{core?: CoreExports}>({})
get atomP() {
return this._store.atomP
}
constructor() { constructor() {
this.address = {studioId: nanoid(10)} this.address = {studioId: nanoid(10)}
this.publicApi = new TheatreStudio(this) this.publicApi = new TheatreStudio(this)
this.atomP = this._store.atomP
if (process.env.NODE_ENV !== 'test') { if (process.env.NODE_ENV !== 'test') {
this.ui = new UI(this) this.ui = new UI(this)
} }
this._attachToIncomingProjects() this._attachToIncomingProjects()
this.paneManager = new PaneManager(this)
} }
get initialized() { get initialized() {
@ -69,6 +76,7 @@ export class Studio {
setCoreBits(coreBits: CoreBits) { setCoreBits(coreBits: CoreBits) {
this._corePrivateApi = coreBits.privateAPI this._corePrivateApi = coreBits.privateAPI
this._coreAtom.setIn(['core'], coreBits.coreExports)
this._setProjectsP(coreBits.projectsP) this._setProjectsP(coreBits.projectsP)
} }
@ -96,6 +104,14 @@ export class Studio {
return this._corePrivateApi return this._corePrivateApi
} }
get core() {
return this._coreAtom.getState().core
}
get coreP() {
return this._coreAtom.pointer.core
}
extend(extension: IExtension) { extend(extension: IExtension) {
if (!extension || typeof extension !== 'object') { if (!extension || typeof extension !== 'object') {
throw new Error(`Extensions must be JS objects`) throw new Error(`Extensions must be JS objects`)
@ -105,12 +121,47 @@ export class Studio {
throw new Error(`extension.id must be a string`) throw new Error(`extension.id must be a string`)
} }
if (this._extensions.getState().byId[extension.id]) { this.transaction(({drafts}) => {
if (drafts.ephemeral.extensions.byId[extension.id]) {
throw new Error(`Extension id "${extension.id}" is already defined`)
}
drafts.ephemeral.extensions.byId[extension.id] = extension
const allPaneClasses = drafts.ephemeral.extensions.paneClasses
extension.panes?.forEach((classDefinition) => {
if (typeof classDefinition.class !== 'string') {
throw new Error(`pane.class must be a string`)
}
if (classDefinition.class.length < 3) {
throw new Error( throw new Error(
`An extension with the id of ${extension.id} already exists`, `pane.class should be a string with 3 or more characters`,
) )
} }
this._extensions.setIn(['byId', extension.id], extension) const existing = allPaneClasses[classDefinition.class]
if (existing) {
throw new Error(
`Pane class "${classDefinition.class}" already exists and is supplied by extension ${existing}`,
)
}
allPaneClasses[classDefinition.class] = {
extensionId: extension.id,
classDefinition: classDefinition,
}
})
})
}
getStudioProject(core: CoreExports): IProject {
return this._cache.get('getStudioProject', () => core.getProject('Studio'))
}
getExtensionSheet(extensionId: string, core: CoreExports): ISheet {
return this._cache.get('extensionSheet-' + extensionId, () =>
this.getStudioProject(core)!.sheet('Extension ' + extensionId),
)
} }
} }

View file

@ -78,6 +78,10 @@ export default class StudioStore {
} }
} }
getState(): FullStudioState {
return this._reduxStore.getState()
}
/** /**
* This method causes the store to start the history from scratch. This is useful * This method causes the store to start the history from scratch. This is useful
* for testing and development where you want to explicitly provide a state to the * for testing and development where you want to explicitly provide a state to the

View file

@ -3,7 +3,7 @@ import studioTicker from '@theatre/studio/studioTicker'
import type {IDerivation, Pointer} from '@theatre/dataverse' import type {IDerivation, Pointer} from '@theatre/dataverse'
import {prism} from '@theatre/dataverse' import {prism} from '@theatre/dataverse'
import SimpleCache from '@theatre/shared/utils/SimpleCache' import SimpleCache from '@theatre/shared/utils/SimpleCache'
import type {$IntentionalAny, VoidFn} from '@theatre/shared/utils/types' import type {$FixMe, VoidFn} from '@theatre/shared/utils/types'
import type {IScrub} from '@theatre/studio/Scrub' import type {IScrub} from '@theatre/studio/Scrub'
import type {Studio} from '@theatre/studio/Studio' import type {Studio} from '@theatre/studio/Studio'
@ -22,8 +22,10 @@ export interface ITransactionAPI {
unset<V>(pointer: Pointer<V>): void unset<V>(pointer: Pointer<V>): void
} }
export interface IPanelType<DataType extends PropTypeConfig_Compound<{}>> { export interface PaneClassDefinition<
sheetName: string DataType extends PropTypeConfig_Compound<{}>,
> {
class: string
dataType: DataType dataType: DataType
component: React.ComponentType<{ component: React.ComponentType<{
id: string id: string
@ -41,7 +43,16 @@ export type IExtension = {
globalToolbar?: { globalToolbar?: {
component: React.ComponentType<{}> component: React.ComponentType<{}>
} }
panes?: Record<string, IPanelType<$IntentionalAny>> panes?: Array<PaneClassDefinition<$FixMe>>
}
export type PaneInstance<ClassName extends string> = {
extensionId: string
instanceId: string
object: ISheetObject<
PropTypeConfig_Compound<{data: $FixMe; visible: PropTypeConfig_Boolean}>
>
definition: PaneClassDefinition<$FixMe>
} }
export interface IStudio { export interface IStudio {
@ -63,6 +74,14 @@ export interface IStudio {
readonly selection: Array<ISheetObject> readonly selection: Array<ISheetObject>
extend(extension: IExtension): void extend(extension: IExtension): void
getPanesOfType<PaneClass extends string>(
paneClass: PaneClass,
): Array<PaneInstance<PaneClass>>
createPane<PaneClass extends string>(
paneClass: PaneClass,
): PaneInstance<PaneClass>
} }
export default class TheatreStudio implements IStudio { export default class TheatreStudio implements IStudio {
@ -138,4 +157,15 @@ export default class TheatreStudio implements IStudio {
scrub(): IScrub { scrub(): IScrub {
return getStudio().scrub() return getStudio().scrub()
} }
getPanesOfType<PaneClass extends string>(
paneClass: PaneClass,
): PaneInstance<PaneClass>[] {
return getStudio().paneManager.getPanesOfType(paneClass)
}
createPane<PaneClass extends string>(
paneClass: PaneClass,
): PaneInstance<PaneClass> {
return getStudio().paneManager.createPane(paneClass)
}
} }

View file

@ -2,10 +2,21 @@ import OutlinePanel from '@theatre/studio/panels/OutlinePanel/OutlinePanel'
import ObjectEditorPanel from '@theatre/studio/panels/ObjectEditorPanel/ObjectEditorPanel' import ObjectEditorPanel from '@theatre/studio/panels/ObjectEditorPanel/ObjectEditorPanel'
import React from 'react' import React from 'react'
import SequenceEditorPanel from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel' import SequenceEditorPanel from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel'
import getStudio from '@theatre/studio/getStudio'
import {useVal} from '@theatre/dataverse-react'
import PaneWrapper from '@theatre/studio/panels/BasePanel/PaneWrapper'
const PanelsRoot: React.FC = () => { const PanelsRoot: React.FC = () => {
const panes = useVal(getStudio().paneManager.allPanesD)
const paneEls = Object.entries(panes).map(([instanceId, paneInstance]) => {
return (
<PaneWrapper key={`pane-${instanceId}`} paneInstance={paneInstance!} />
)
})
return ( return (
<> <>
{paneEls}
<OutlinePanel /> <OutlinePanel />
<ObjectEditorPanel /> <ObjectEditorPanel />
<SequenceEditorPanel /> <SequenceEditorPanel />

View file

@ -2,7 +2,7 @@ import {val} from '@theatre/dataverse'
import {usePrism} from '@theatre/dataverse-react' import {usePrism} from '@theatre/dataverse-react'
import type {$IntentionalAny} from '@theatre/shared/utils/types' import type {$IntentionalAny} from '@theatre/shared/utils/types'
import getStudio from '@theatre/studio/getStudio' import getStudio from '@theatre/studio/getStudio'
import type {PanelId, PanelPosition} from '@theatre/studio/store/types' import type {PanelPosition} from '@theatre/studio/store/types'
import React, {useContext} from 'react' import React, {useContext} from 'react'
import useWindowSize from 'react-use/esm/useWindowSize' import useWindowSize from 'react-use/esm/useWindowSize'
import styled from 'styled-components' import styled from 'styled-components'
@ -15,7 +15,7 @@ const Container = styled.div`
` `
type PanelStuff = { type PanelStuff = {
panelId: PanelId panelId: string
dims: { dims: {
width: number width: number
height: number height: number
@ -69,7 +69,7 @@ const PanelContext = React.createContext<PanelStuff>(null as $IntentionalAny)
export const usePanel = () => useContext(PanelContext) export const usePanel = () => useContext(PanelContext)
const BasePanel: React.FC<{ const BasePanel: React.FC<{
panelId: PanelId panelId: string
defaultPosition: PanelPosition defaultPosition: PanelPosition
minDims: {width: number; height: number} minDims: {width: number; height: number}
}> = ({panelId, children, defaultPosition, minDims}) => { }> = ({panelId, children, defaultPosition, minDims}) => {

View file

@ -0,0 +1,71 @@
import type {$FixMe} from '@theatre/shared/utils/types'
import type {PanelPosition} from '@theatre/studio/store/types'
import type {PaneInstance} from '@theatre/studio/TheatreStudio'
import React from 'react'
import styled from 'styled-components'
import {
F1,
F2 as F2Impl,
} from '@theatre/studio/panels/ObjectEditorPanel/ObjectEditorPanel'
import BasePanel from './BasePanel'
import PanelDragZone from './PanelDragZone'
import PanelWrapper from './PanelWrapper'
const defaultPosition: PanelPosition = {
edges: {
left: {from: 'screenLeft', distance: 0.3},
right: {from: 'screenRight', distance: 0.3},
top: {from: 'screenTop', distance: 0.3},
bottom: {from: 'screenBottom', distance: 0.3},
},
}
const minDims = {width: 300, height: 300}
const PaneWrapper: React.FC<{
paneInstance: PaneInstance<$FixMe>
}> = ({paneInstance}) => {
return (
<BasePanel
panelId={`pane-${paneInstance.instanceId}`}
defaultPosition={defaultPosition}
minDims={minDims}
>
<Content paneInstance={paneInstance} />
</BasePanel>
)
}
const Container = styled(PanelWrapper)`
overflow-y: hidden;
display: flex;
flex-direction: column;
`
const Title = styled.div`
width: 100%;
`
const F2 = styled(F2Impl)`
position: relative;
`
const Content: React.FC<{paneInstance: PaneInstance<$FixMe>}> = ({
paneInstance,
}) => {
const Comp = paneInstance.definition.component
return (
<Container>
<PanelDragZone>
<F1>
<Title>{paneInstance.instanceId}</Title>
</F1>
</PanelDragZone>
<F2>
<Comp id={paneInstance.instanceId} object={paneInstance.object} />
</F2>
</Container>
)
}
export default PaneWrapper

View file

@ -29,6 +29,7 @@ const initialState: StudioState = {
}, },
autoKey: true, autoKey: true,
coreByProject: {}, coreByProject: {},
panelInstanceDesceriptors: {},
}, },
ephemeral: { ephemeral: {
initialised: false, initialised: false,
@ -36,6 +37,10 @@ const initialState: StudioState = {
projects: { projects: {
stateByProjectId: {}, stateByProjectId: {},
}, },
extensions: {
byId: {},
paneClasses: {},
},
}, },
} }

View file

@ -29,7 +29,6 @@ import {graphEditorColors} from '@theatre/studio/panels/SequenceEditorPanel/Grap
import type { import type {
OutlineSelectable, OutlineSelectable,
OutlineSelectionState, OutlineSelectionState,
PanelId,
PanelPosition, PanelPosition,
} from './types' } from './types'
import {uniq} from 'lodash-es' import {uniq} from 'lodash-es'
@ -67,7 +66,7 @@ namespace stateEditors {
export namespace historic { export namespace historic {
export namespace panelPositions { export namespace panelPositions {
export function setPanelPosition(p: { export function setPanelPosition(p: {
panelId: PanelId panelId: string
position: PanelPosition position: PanelPosition
}) { }) {
const h = drafts().historic const h = drafts().historic

View file

@ -1,5 +1,13 @@
import type {ProjectState} from '@theatre/core/projects/store/storeTypes' import type {ProjectState} from '@theatre/core/projects/store/storeTypes'
import type {SerializableMap, StrictRecord} from '@theatre/shared/utils/types' import type {
$IntentionalAny,
SerializableMap,
StrictRecord,
} from '@theatre/shared/utils/types'
import type {
IExtension,
PaneClassDefinition,
} from '@theatre/studio/TheatreStudio'
export type StudioEphemeralState = { export type StudioEphemeralState = {
initialised: boolean initialised: boolean
@ -22,4 +30,13 @@ export type StudioEphemeralState = {
} }
> >
} }
extensions: {
byId: {[extensionId in string]?: IExtension}
paneClasses: {
[paneClassName in string]?: {
extensionId: string
classDefinition: PaneClassDefinition<$IntentionalAny>
}
}
}
} }

View file

@ -56,6 +56,11 @@ export type OutlineSelectionState =
export type OutlineSelectable = Project | Sheet | SheetObject export type OutlineSelectable = Project | Sheet | SheetObject
export type OutlineSelection = OutlineSelectable[] export type OutlineSelection = OutlineSelectable[]
export type PanelInstanceDescriptor = {
instanceId: string
paneClass: string
}
export type StudioHistoricState = { export type StudioHistoricState = {
projects: { projects: {
stateByProjectId: StrictRecord< stateByProjectId: StrictRecord<
@ -77,7 +82,10 @@ export type StudioHistoricState = {
> >
} }
panels?: Panels panels?: Panels
panelPositions?: {[panelId in PanelId]?: PanelPosition} panelPositions?: {[panelIdOrPaneId in string]?: PanelPosition}
panelInstanceDesceriptors: {
[instanceId in string]?: PanelInstanceDescriptor
}
autoKey: boolean autoKey: boolean
coreByProject: {[projectId in string]: ProjectState_Historic} coreByProject: {[projectId in string]: ProjectState_Historic}
} }

View file

@ -17,9 +17,10 @@ const Container = styled.div`
const GlobalToolbar: React.FC<{}> = (props) => { const GlobalToolbar: React.FC<{}> = (props) => {
const groups: Array<React.ReactNode> = [] const groups: Array<React.ReactNode> = []
const extensions = useVal(getStudio().extensionsP) const extensionsById = useVal(getStudio().atomP.ephemeral.extensions.byId)
for (const [, extension] of Object.entries(extensions)) { for (const [, extension] of Object.entries(extensionsById)) {
if (!extension) continue
if (extension.globalToolbar) { if (extension.globalToolbar) {
groups.push( groups.push(
<extension.globalToolbar.component <extension.globalToolbar.component