Put SnapshotEditor inside a Pane
This commit is contained in:
parent
921bc44270
commit
64273366ed
17 changed files with 395 additions and 50 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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
|
|
@ -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}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
121
theatre/studio/src/PaneManager.ts
Normal file
121
theatre/studio/src/PaneManager.ts
Normal 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]!
|
||||||
|
}
|
||||||
|
}
|
|
@ -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}) => {
|
||||||
throw new Error(
|
if (drafts.ephemeral.extensions.byId[extension.id]) {
|
||||||
`An extension with the id of ${extension.id} already exists`,
|
throw new Error(`Extension id "${extension.id}" is already defined`)
|
||||||
)
|
}
|
||||||
}
|
drafts.ephemeral.extensions.byId[extension.id] = extension
|
||||||
|
|
||||||
this._extensions.setIn(['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(
|
||||||
|
`pane.class should be a string with 3 or more characters`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 />
|
||||||
|
|
|
@ -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}) => {
|
||||||
|
|
71
theatre/studio/src/panels/BasePanel/PaneWrapper.tsx
Normal file
71
theatre/studio/src/panels/BasePanel/PaneWrapper.tsx
Normal 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
|
|
@ -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: {},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue