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>>()
|
||||
|
||||
type IMemo = {
|
||||
deps: undefined | unknown[]
|
||||
deps: undefined | unknown[] | ReadonlyArray<unknown>
|
||||
cachedValue: unknown
|
||||
}
|
||||
|
||||
|
@ -229,8 +229,8 @@ function effect(key: string, cb: () => () => void, deps?: unknown[]): void {
|
|||
}
|
||||
|
||||
function depsHaveChanged(
|
||||
oldDeps: undefined | unknown[],
|
||||
newDeps: undefined | unknown[],
|
||||
oldDeps: undefined | unknown[] | ReadonlyArray<unknown>,
|
||||
newDeps: undefined | unknown[] | ReadonlyArray<unknown>,
|
||||
): boolean {
|
||||
if (oldDeps === undefined || newDeps === undefined) {
|
||||
return true
|
||||
|
@ -244,7 +244,7 @@ function depsHaveChanged(
|
|||
function memo<T>(
|
||||
key: string,
|
||||
fn: () => T,
|
||||
deps: undefined | $IntentionalAny[],
|
||||
deps: undefined | $IntentionalAny[] | ReadonlyArray<$IntentionalAny>,
|
||||
): T {
|
||||
const scope = hookScopeStack.peek()
|
||||
if (!scope) {
|
||||
|
|
|
@ -62,19 +62,17 @@ const EditorScene = () => {
|
|||
)
|
||||
}
|
||||
|
||||
const Wrapper = styled.div<{visible: boolean}>`
|
||||
const Wrapper = styled.div`
|
||||
tab-size: 4;
|
||||
line-height: 1.15; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
margin: 0;
|
||||
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
z-index: 50;
|
||||
display: ${(props) => (props.visible ? 'block' : 'none')};
|
||||
`
|
||||
|
||||
const CanvasWrapper = styled.div`
|
||||
|
@ -83,7 +81,9 @@ const CanvasWrapper = styled.div`
|
|||
height: 100%;
|
||||
`
|
||||
|
||||
const Editor: VFC = () => {
|
||||
const SnapshotEditor: VFC = () => {
|
||||
console.log('Snapshot editor!!')
|
||||
|
||||
const [editorObject, sceneSnapshot, initialEditorCamera, createSnapshot] =
|
||||
useEditorStore(
|
||||
(state) => [
|
||||
|
@ -95,10 +95,18 @@ const Editor: VFC = () => {
|
|||
shallow,
|
||||
)
|
||||
|
||||
const editorOpen = !!useVal(editorObject?.props.isOpen)
|
||||
const editorOpen = true
|
||||
useLayoutEffect(() => {
|
||||
let timeout: NodeJS.Timeout | undefined
|
||||
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])
|
||||
|
||||
|
@ -109,8 +117,7 @@ const Editor: VFC = () => {
|
|||
<StyleSheetManager disableVendorPrefixes>
|
||||
<>
|
||||
<GlobalStyle />
|
||||
<Wrapper id="theatre-plugin-r3f-root" visible={true}>
|
||||
{/* <Toolbar /> */}
|
||||
<Wrapper>
|
||||
{sceneSnapshot ? (
|
||||
<>
|
||||
<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 studio from '@theatre/studio'
|
||||
import {getSelected} from '../useSelected'
|
||||
import {useVal} from '@theatre/dataverse-react'
|
||||
import {usePrism, useVal} from '@theatre/dataverse-react'
|
||||
import IconButton from './utils/IconButton'
|
||||
import styled from 'styled-components'
|
||||
|
||||
|
@ -19,6 +19,10 @@ const ToolGroup = styled.div`
|
|||
`
|
||||
|
||||
const Toolbar: VFC = () => {
|
||||
usePrism(() => {
|
||||
const panes = studio.getPanesOfType('snapshotEditor')
|
||||
}, [])
|
||||
|
||||
const [editorObject] = useEditorStore(
|
||||
(state) => [state.editorObject],
|
||||
shallow,
|
||||
|
@ -35,6 +39,15 @@ const Toolbar: VFC = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<ToolGroup>
|
||||
<button
|
||||
onClick={() => {
|
||||
studio.createPane('snapshotEditor')
|
||||
}}
|
||||
>
|
||||
Create snapshot
|
||||
</button>
|
||||
</ToolGroup>
|
||||
<ToolGroup>
|
||||
<TransformControlsModeSelect
|
||||
value={transformControlsMode}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import React from 'react'
|
||||
import {render} from 'react-dom'
|
||||
import Editor from './components/Editor'
|
||||
import SnapshotEditor from './components/SnapshotEditor'
|
||||
|
||||
export {default as EditorHelper} from './components/EditorHelper'
|
||||
export type {EditorHelperProps} from './components/EditorHelper'
|
||||
|
@ -10,6 +8,7 @@ export {bindToCanvas} from './store'
|
|||
export type {EditableState, BindFunction} from './store'
|
||||
import studio from '@theatre/studio'
|
||||
import Toolbar from './components/Toolbar/Toolbar'
|
||||
import {types} from '@theatre/core'
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
studio.extend({
|
||||
|
@ -17,9 +16,14 @@ if (process.env.NODE_ENV === 'development') {
|
|||
globalToolbar: {
|
||||
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 projectsSingleton from './projects/projectsSingleton'
|
||||
import {privateAPI} from './privateAPIs'
|
||||
import * as coreExports from './coreExports'
|
||||
|
||||
export type CoreBits = {
|
||||
projectsP: typeof projectsSingleton.atom.pointer.projects
|
||||
privateAPI: typeof privateAPI
|
||||
coreExports: typeof coreExports
|
||||
}
|
||||
|
||||
export default class CoreBundle {
|
||||
|
@ -27,6 +29,7 @@ export default class CoreBundle {
|
|||
const bits: CoreBits = {
|
||||
projectsP: projectsSingleton.atom.pointer.projects,
|
||||
privateAPI: privateAPI,
|
||||
coreExports,
|
||||
}
|
||||
|
||||
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 type {FullStudioState} from '@theatre/studio/store'
|
||||
import type {StudioHistoricState} from '@theatre/studio/store/types/historic'
|
||||
import UI from '@theatre/studio/UI'
|
||||
import type {Pointer} from '@theatre/dataverse'
|
||||
|
@ -14,10 +13,14 @@ import TheatreStudio from './TheatreStudio'
|
|||
import {nanoid} from 'nanoid/non-secure'
|
||||
import type Project from '@theatre/core/projects/Project'
|
||||
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 {
|
||||
readonly atomP: Pointer<FullStudioState>
|
||||
readonly ui!: UI
|
||||
readonly publicApi: IStudio
|
||||
readonly address: {studioId: string}
|
||||
|
@ -28,23 +31,27 @@ export class Studio {
|
|||
this._projectsProxy.pointer
|
||||
|
||||
private readonly _store = new StudioStore()
|
||||
private _corePrivateApi: typeof privateAPI | undefined
|
||||
private _corePrivateApi: CoreBits['privateAPI'] | undefined
|
||||
|
||||
private _extensions: Atom<{byId: Record<string, IExtension>}> = new Atom({
|
||||
byId: {},
|
||||
})
|
||||
readonly extensionsP = this._extensions.pointer.byId
|
||||
private readonly _cache = new SimpleCache()
|
||||
readonly paneManager: PaneManager
|
||||
|
||||
private _coreAtom = new Atom<{core?: CoreExports}>({})
|
||||
|
||||
get atomP() {
|
||||
return this._store.atomP
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.address = {studioId: nanoid(10)}
|
||||
this.publicApi = new TheatreStudio(this)
|
||||
this.atomP = this._store.atomP
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
this.ui = new UI(this)
|
||||
}
|
||||
|
||||
this._attachToIncomingProjects()
|
||||
this.paneManager = new PaneManager(this)
|
||||
}
|
||||
|
||||
get initialized() {
|
||||
|
@ -69,6 +76,7 @@ export class Studio {
|
|||
|
||||
setCoreBits(coreBits: CoreBits) {
|
||||
this._corePrivateApi = coreBits.privateAPI
|
||||
this._coreAtom.setIn(['core'], coreBits.coreExports)
|
||||
this._setProjectsP(coreBits.projectsP)
|
||||
}
|
||||
|
||||
|
@ -96,6 +104,14 @@ export class Studio {
|
|||
return this._corePrivateApi
|
||||
}
|
||||
|
||||
get core() {
|
||||
return this._coreAtom.getState().core
|
||||
}
|
||||
|
||||
get coreP() {
|
||||
return this._coreAtom.pointer.core
|
||||
}
|
||||
|
||||
extend(extension: IExtension) {
|
||||
if (!extension || typeof extension !== 'object') {
|
||||
throw new Error(`Extensions must be JS objects`)
|
||||
|
@ -105,12 +121,47 @@ export class Studio {
|
|||
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(
|
||||
`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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
* 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 {prism} from '@theatre/dataverse'
|
||||
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 {Studio} from '@theatre/studio/Studio'
|
||||
|
@ -22,8 +22,10 @@ export interface ITransactionAPI {
|
|||
unset<V>(pointer: Pointer<V>): void
|
||||
}
|
||||
|
||||
export interface IPanelType<DataType extends PropTypeConfig_Compound<{}>> {
|
||||
sheetName: string
|
||||
export interface PaneClassDefinition<
|
||||
DataType extends PropTypeConfig_Compound<{}>,
|
||||
> {
|
||||
class: string
|
||||
dataType: DataType
|
||||
component: React.ComponentType<{
|
||||
id: string
|
||||
|
@ -41,7 +43,16 @@ export type IExtension = {
|
|||
globalToolbar?: {
|
||||
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 {
|
||||
|
@ -63,6 +74,14 @@ export interface IStudio {
|
|||
readonly selection: Array<ISheetObject>
|
||||
|
||||
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 {
|
||||
|
@ -138,4 +157,15 @@ export default class TheatreStudio implements IStudio {
|
|||
scrub(): IScrub {
|
||||
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 React from 'react'
|
||||
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 panes = useVal(getStudio().paneManager.allPanesD)
|
||||
const paneEls = Object.entries(panes).map(([instanceId, paneInstance]) => {
|
||||
return (
|
||||
<PaneWrapper key={`pane-${instanceId}`} paneInstance={paneInstance!} />
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
{paneEls}
|
||||
<OutlinePanel />
|
||||
<ObjectEditorPanel />
|
||||
<SequenceEditorPanel />
|
||||
|
|
|
@ -2,7 +2,7 @@ import {val} from '@theatre/dataverse'
|
|||
import {usePrism} from '@theatre/dataverse-react'
|
||||
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
||||
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 useWindowSize from 'react-use/esm/useWindowSize'
|
||||
import styled from 'styled-components'
|
||||
|
@ -15,7 +15,7 @@ const Container = styled.div`
|
|||
`
|
||||
|
||||
type PanelStuff = {
|
||||
panelId: PanelId
|
||||
panelId: string
|
||||
dims: {
|
||||
width: number
|
||||
height: number
|
||||
|
@ -69,7 +69,7 @@ const PanelContext = React.createContext<PanelStuff>(null as $IntentionalAny)
|
|||
export const usePanel = () => useContext(PanelContext)
|
||||
|
||||
const BasePanel: React.FC<{
|
||||
panelId: PanelId
|
||||
panelId: string
|
||||
defaultPosition: PanelPosition
|
||||
minDims: {width: number; height: number}
|
||||
}> = ({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,
|
||||
coreByProject: {},
|
||||
panelInstanceDesceriptors: {},
|
||||
},
|
||||
ephemeral: {
|
||||
initialised: false,
|
||||
|
@ -36,6 +37,10 @@ const initialState: StudioState = {
|
|||
projects: {
|
||||
stateByProjectId: {},
|
||||
},
|
||||
extensions: {
|
||||
byId: {},
|
||||
paneClasses: {},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ import {graphEditorColors} from '@theatre/studio/panels/SequenceEditorPanel/Grap
|
|||
import type {
|
||||
OutlineSelectable,
|
||||
OutlineSelectionState,
|
||||
PanelId,
|
||||
PanelPosition,
|
||||
} from './types'
|
||||
import {uniq} from 'lodash-es'
|
||||
|
@ -67,7 +66,7 @@ namespace stateEditors {
|
|||
export namespace historic {
|
||||
export namespace panelPositions {
|
||||
export function setPanelPosition(p: {
|
||||
panelId: PanelId
|
||||
panelId: string
|
||||
position: PanelPosition
|
||||
}) {
|
||||
const h = drafts().historic
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
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 = {
|
||||
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 OutlineSelection = OutlineSelectable[]
|
||||
|
||||
export type PanelInstanceDescriptor = {
|
||||
instanceId: string
|
||||
paneClass: string
|
||||
}
|
||||
|
||||
export type StudioHistoricState = {
|
||||
projects: {
|
||||
stateByProjectId: StrictRecord<
|
||||
|
@ -77,7 +82,10 @@ export type StudioHistoricState = {
|
|||
>
|
||||
}
|
||||
panels?: Panels
|
||||
panelPositions?: {[panelId in PanelId]?: PanelPosition}
|
||||
panelPositions?: {[panelIdOrPaneId in string]?: PanelPosition}
|
||||
panelInstanceDesceriptors: {
|
||||
[instanceId in string]?: PanelInstanceDescriptor
|
||||
}
|
||||
autoKey: boolean
|
||||
coreByProject: {[projectId in string]: ProjectState_Historic}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,10 @@ const Container = styled.div`
|
|||
|
||||
const GlobalToolbar: React.FC<{}> = (props) => {
|
||||
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) {
|
||||
groups.push(
|
||||
<extension.globalToolbar.component
|
||||
|
|
Loading…
Reference in a new issue