Make studio.ui available before the UI is loaded
This was a regression introduced by b83164f26f
This commit is contained in:
parent
bcfb91fbb7
commit
35fe1c375c
5 changed files with 104 additions and 67 deletions
11
packages/playground/src/shared/remote/index.html
Normal file
11
packages/playground/src/shared/remote/index.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Theatre.js Playground</title>
|
||||
<script src="./index.tsx" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,6 +1,6 @@
|
|||
import Scrub from '@theatre/studio/Scrub'
|
||||
import type {StudioHistoricState} from '@theatre/studio/store/types/historic'
|
||||
import type UI from '@theatre/studio/UI'
|
||||
import UI from '@theatre/studio/UI/UI'
|
||||
import type {Pointer, Ticker} from '@theatre/dataverse'
|
||||
import {Atom, PointerProxy, pointerToPrism} from '@theatre/dataverse'
|
||||
import type {
|
||||
|
@ -32,9 +32,6 @@ const DEFAULT_PERSISTENCE_KEY = 'theatre-0.4'
|
|||
|
||||
export type CoreExports = typeof _coreExports
|
||||
|
||||
const UIConstructorModule =
|
||||
typeof window !== 'undefined' ? import('./UI').then((M) => M.default) : null
|
||||
|
||||
const STUDIO_NOT_INITIALIZED_MESSAGE = `You seem to have imported '@theatre/studio' but haven't initialized it. You can initialize the studio by:
|
||||
\`\`\`
|
||||
import studio from '@theatre/studio'
|
||||
|
@ -64,16 +61,9 @@ studio.initialize()
|
|||
`
|
||||
|
||||
export class Studio {
|
||||
protected _ui: UI | null = null
|
||||
readonly ui: UI
|
||||
// this._uiInitDeferred.promise will resolve once this._ui is set
|
||||
private _uiInitDeferred = defer()
|
||||
get ui() {
|
||||
if (!this._ui) {
|
||||
debugger
|
||||
throw new Error(`Studio.ui called before UI is initialized`)
|
||||
}
|
||||
return this._ui
|
||||
}
|
||||
|
||||
readonly publicApi: IStudio
|
||||
readonly address: {studioId: string}
|
||||
readonly _projectsProxy: PointerProxy<Record<ProjectId, Project>> =
|
||||
|
@ -130,17 +120,7 @@ export class Studio {
|
|||
this.address = {studioId: nanoid(10)}
|
||||
this.publicApi = new TheatreStudio(this)
|
||||
|
||||
// initialize UI if we're in the browser
|
||||
if (process.env.NODE_ENV !== 'test' && typeof window !== 'undefined') {
|
||||
UIConstructorModule!
|
||||
.then((M) => {
|
||||
this._ui = new M(this)
|
||||
this._uiInitDeferred.resolve(null)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`Failed initializing the UI at @theatre/studio.`, error)
|
||||
})
|
||||
}
|
||||
this.ui = new UI(this)
|
||||
|
||||
this._attachToIncomingProjects()
|
||||
this.paneManager = new PaneManager(this)
|
||||
|
@ -215,13 +195,15 @@ export class Studio {
|
|||
return
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== 'test' && typeof window !== 'undefined') {
|
||||
await this.ui.ready
|
||||
}
|
||||
|
||||
this._initializedDeferred.resolve()
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
this._uiInitDeferred.promise.then(() => {
|
||||
this.ui.render()
|
||||
checkForUpdates()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
70
theatre/studio/src/UI/UI.ts
Normal file
70
theatre/studio/src/UI/UI.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
import type {Studio} from '@theatre/studio/Studio'
|
||||
import {val} from '@theatre/dataverse'
|
||||
|
||||
const NonSSRBitsClass =
|
||||
typeof window !== 'undefined'
|
||||
? import('./UINonSSRBits').then((M) => M.default)
|
||||
: null
|
||||
|
||||
export default class UI {
|
||||
private _rendered = false
|
||||
private _nonSSRBits = NonSSRBitsClass
|
||||
? NonSSRBitsClass.then((NonSSRBitsClass) => new NonSSRBitsClass())
|
||||
: Promise.reject()
|
||||
readonly ready: Promise<void> = this._nonSSRBits.then(
|
||||
() => undefined,
|
||||
() => undefined,
|
||||
)
|
||||
|
||||
constructor(readonly studio: Studio) {}
|
||||
|
||||
render() {
|
||||
if (this._rendered) {
|
||||
return
|
||||
}
|
||||
this._rendered = true
|
||||
|
||||
this._nonSSRBits.then((b) => {
|
||||
b.render()
|
||||
})
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.studio.transaction(({drafts}) => {
|
||||
drafts.ahistoric.visibilityState = 'everythingIsHidden'
|
||||
})
|
||||
}
|
||||
|
||||
restore() {
|
||||
this.render()
|
||||
this.studio.transaction(({drafts}) => {
|
||||
drafts.ahistoric.visibilityState = 'everythingIsVisible'
|
||||
})
|
||||
}
|
||||
|
||||
get isHidden() {
|
||||
return (
|
||||
val(this.studio.atomP.ahistoric.visibilityState) === 'everythingIsHidden'
|
||||
)
|
||||
}
|
||||
|
||||
renderToolset(toolsetId: string, htmlNode: HTMLElement) {
|
||||
let shouldUnmount = false
|
||||
|
||||
let unmount: null | (() => void) = null
|
||||
|
||||
this._nonSSRBits.then((nonSSRBits) => {
|
||||
if (shouldUnmount) return // unmount requested before the toolset is mounted, so, abort
|
||||
unmount = nonSSRBits.renderToolset(toolsetId, htmlNode)
|
||||
})
|
||||
|
||||
return () => {
|
||||
if (unmount) {
|
||||
unmount()
|
||||
return
|
||||
}
|
||||
if (shouldUnmount) return
|
||||
shouldUnmount = true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,20 +2,17 @@ import UIRoot from '@theatre/studio/UIRoot/UIRoot'
|
|||
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import type {Studio} from './Studio'
|
||||
import {val} from '@theatre/dataverse'
|
||||
import {getMounter} from './utils/renderInPortalInContext'
|
||||
import {withStyledShadow} from './css'
|
||||
import ExtensionToolbar from './toolbars/ExtensionToolbar/ExtensionToolbar'
|
||||
import {getMounter} from '@theatre/studio/utils/renderInPortalInContext'
|
||||
import {withStyledShadow} from '@theatre/studio/css'
|
||||
import ExtensionToolbar from '@theatre/studio/toolbars/ExtensionToolbar/ExtensionToolbar'
|
||||
|
||||
export default class UI {
|
||||
export default class UINonSSRBits {
|
||||
readonly containerEl = document.createElement('div')
|
||||
private _rendered = false
|
||||
private _renderTimeout: NodeJS.Timer | undefined = undefined
|
||||
private _documentBodyUIIsRenderedIn: HTMLElement | undefined = undefined
|
||||
readonly containerShadow: ShadowRoot & HTMLElement
|
||||
|
||||
constructor(readonly studio: Studio) {
|
||||
constructor() {
|
||||
// @todo we can't bootstrap Theatre.js (as in, to design Theatre.js using theatre), if we rely on IDed elements
|
||||
this.containerEl.id = 'theatrejs-studio-root'
|
||||
|
||||
|
@ -49,15 +46,6 @@ export default class UI {
|
|||
}
|
||||
|
||||
render() {
|
||||
if (this._rendered) {
|
||||
return
|
||||
}
|
||||
this._rendered = true
|
||||
|
||||
this._render()
|
||||
}
|
||||
|
||||
protected _render() {
|
||||
const renderCallback = () => {
|
||||
if (!document.body) {
|
||||
this._renderTimeout = setTimeout(renderCallback, 5)
|
||||
|
@ -66,30 +54,14 @@ export default class UI {
|
|||
this._renderTimeout = undefined
|
||||
this._documentBodyUIIsRenderedIn = document.body
|
||||
this._documentBodyUIIsRenderedIn.appendChild(this.containerEl)
|
||||
ReactDOM.render(React.createElement(UIRoot), this.containerShadow)
|
||||
ReactDOM.render(
|
||||
React.createElement(UIRoot, {containerShadow: this.containerShadow}),
|
||||
this.containerShadow,
|
||||
)
|
||||
}
|
||||
this._renderTimeout = setTimeout(renderCallback, 10)
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.studio.transaction(({drafts}) => {
|
||||
drafts.ahistoric.visibilityState = 'everythingIsHidden'
|
||||
})
|
||||
}
|
||||
|
||||
restore() {
|
||||
this.render()
|
||||
this.studio.transaction(({drafts}) => {
|
||||
drafts.ahistoric.visibilityState = 'everythingIsVisible'
|
||||
})
|
||||
}
|
||||
|
||||
get isHidden() {
|
||||
return (
|
||||
val(this.studio.atomP.ahistoric.visibilityState) === 'everythingIsHidden'
|
||||
)
|
||||
}
|
||||
|
||||
renderToolset(toolsetId: string, htmlNode: HTMLElement) {
|
||||
const s = getMounter()
|
||||
|
|
@ -46,7 +46,9 @@ const INTERNAL_LOGGING = /Playground.+Theatre\.js/.test(
|
|||
(typeof document !== 'undefined' ? document?.title : null) ?? '',
|
||||
)
|
||||
|
||||
export default function UIRoot() {
|
||||
export default function UIRoot(props: {
|
||||
containerShadow: ShadowRoot & HTMLElement
|
||||
}) {
|
||||
const studio = getStudio()
|
||||
const [portalLayerRef, portalLayer] = useRefAndState<HTMLDivElement>(
|
||||
undefined as $IntentionalAny,
|
||||
|
@ -87,7 +89,7 @@ export default function UIRoot() {
|
|||
target={
|
||||
window.__IS_VISUAL_REGRESSION_TESTING === true
|
||||
? undefined
|
||||
: getStudio()!.ui.containerShadow
|
||||
: props.containerShadow
|
||||
}
|
||||
>
|
||||
<>
|
||||
|
|
Loading…
Reference in a new issue