Implement studio.globalToolbar

This commit is contained in:
Aria Minaei 2021-07-13 16:13:15 +02:00
parent 86547aa4cb
commit 921bc44270
18 changed files with 433 additions and 924 deletions

View file

@ -9,7 +9,7 @@ import type {
ITransactionPrivateApi,
} from './StudioStore/StudioStore'
import StudioStore from './StudioStore/StudioStore'
import type {IStudio} from './TheatreStudio'
import type {IExtension, IStudio} from './TheatreStudio'
import TheatreStudio from './TheatreStudio'
import {nanoid} from 'nanoid/non-secure'
import type Project from '@theatre/core/projects/Project'
@ -30,6 +30,11 @@ export class Studio {
private readonly _store = new StudioStore()
private _corePrivateApi: typeof privateAPI | undefined
private _extensions: Atom<{byId: Record<string, IExtension>}> = new Atom({
byId: {},
})
readonly extensionsP = this._extensions.pointer.byId
constructor() {
this.address = {studioId: nanoid(10)}
this.publicApi = new TheatreStudio(this)
@ -90,4 +95,22 @@ export class Studio {
get corePrivateAPI() {
return this._corePrivateApi
}
extend(extension: IExtension) {
if (!extension || typeof extension !== 'object') {
throw new Error(`Extensions must be JS objects`)
}
if (typeof extension.id !== 'string') {
throw new Error(`extension.id must be a string`)
}
if (this._extensions.getState().byId[extension.id]) {
throw new Error(
`An extension with the id of ${extension.id} already exists`,
)
}
this._extensions.setIn(['byId', extension.id], extension)
}
}

View file

@ -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 {VoidFn} from '@theatre/shared/utils/types'
import type {$IntentionalAny, VoidFn} from '@theatre/shared/utils/types'
import type {IScrub} from '@theatre/studio/Scrub'
import type {Studio} from '@theatre/studio/Studio'
@ -11,12 +11,39 @@ import {isSheetObjectPublicAPI} from '@theatre/shared/instanceTypes'
import {getOutlineSelection} from './selectors'
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
import getStudio from './getStudio'
import type React from 'react'
import type {
PropTypeConfig_Boolean,
PropTypeConfig_Compound,
} from '@theatre/core/propTypes'
export interface ITransactionAPI {
set<V>(pointer: Pointer<V>, value: V): void
unset<V>(pointer: Pointer<V>): void
}
export interface IPanelType<DataType extends PropTypeConfig_Compound<{}>> {
sheetName: string
dataType: DataType
component: React.ComponentType<{
id: string
object: ISheetObject<
PropTypeConfig_Compound<{
visible: PropTypeConfig_Boolean
data: DataType
}>
>
}>
}
export type IExtension = {
id: string
globalToolbar?: {
component: React.ComponentType<{}>
}
panes?: Record<string, IPanelType<$IntentionalAny>>
}
export interface IStudio {
readonly ui: {
show(): void
@ -34,6 +61,8 @@ export interface IStudio {
): VoidFunction
readonly selection: Array<ISheetObject>
extend(extension: IExtension): void
}
export default class TheatreStudio implements IStudio {
@ -62,6 +91,10 @@ export default class TheatreStudio implements IStudio {
*/
constructor(internals: Studio) {}
extend(extension: IExtension): void {
getStudio().extend(extension)
}
transaction(fn: (api: ITransactionAPI) => void): void {
return getStudio().transaction(({set, unset}) => {
return fn({set, unset})

View file

@ -1,4 +1,4 @@
import UIRootWrapper from '@theatre/studio/UIRoot/UIRootWrapper'
import UIRoot from '@theatre/studio/UIRoot/UIRoot'
import type {$IntentionalAny} from '@theatre/shared/utils/types'
import React from 'react'
import ReactDOM from 'react-dom'
@ -54,10 +54,7 @@ export default class UI {
this._renderTimeout = undefined
this._documentBodyUIIsRenderedIn = document.body
this._documentBodyUIIsRenderedIn.appendChild(this.containerEl)
ReactDOM.render(
React.createElement(UIRootWrapper, {studio: this.studio}),
this.containerShadow,
)
ReactDOM.render(React.createElement(UIRoot), this.containerShadow)
}
this._renderTimeout = setTimeout(renderCallback, 10)
}

View file

@ -1,13 +1,15 @@
import getStudio from '@theatre/studio/getStudio'
import type {Studio} from '@theatre/studio/Studio'
import {usePrism} from '@theatre/dataverse-react'
import {val} from '@theatre/dataverse'
import React from 'react'
import {createGlobalStyle, StyleSheetManager} from 'styled-components'
import EnsureProjectsDontHaveErrors from './EnsureProjectsDontHaveErrors'
import styled, {createGlobalStyle, StyleSheetManager} from 'styled-components'
import PanelsRoot from './PanelsRoot'
import ProvideTheme from './ProvideTheme'
import TheTrigger from './TheTrigger'
import GlobalToolbar from '@theatre/studio/toolbars/GlobalToolbar/GlobalToolbar'
import useRefAndState from '@theatre/studio/utils/useRefAndState'
import {PortalContext} from 'reakit'
import type {$IntentionalAny} from '@theatre/shared/utils/types'
const GlobalStyle = createGlobalStyle`
:host {
@ -28,13 +30,28 @@ const GlobalStyle = createGlobalStyle`
}
`
export default function UIRoot({studio}: {studio: Studio}) {
const Container = styled.div`
z-index: 50;
position: fixed;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
pointer-events: none;
`
export default function UIRoot() {
const studio = getStudio()
const [containerRef, container] = useRefAndState<HTMLDivElement>(
undefined as $IntentionalAny,
)
const inside = usePrism(() => {
const visiblityState = val(studio.atomP.ahistoric.visibilityState)
const initialised = val(studio.atomP.ephemeral.initialised)
const shouldShowTrigger = visiblityState === 'onlyTriggerIsVisible'
const shouldShowPanels = visiblityState === 'everythingIsVisible'
const shouldShowGlobalToolbar = visiblityState !== 'everythingIsHidden'
return !initialised ? null : (
<StyleSheetManager
@ -44,13 +61,18 @@ export default function UIRoot({studio}: {studio: Studio}) {
<>
<GlobalStyle />
<ProvideTheme>
{shouldShowTrigger && <TheTrigger />}
{shouldShowPanels && <PanelsRoot />}
<PortalContext.Provider value={container}>
<Container ref={containerRef}>
{shouldShowGlobalToolbar && <GlobalToolbar />}
{shouldShowTrigger && <TheTrigger />}
{shouldShowPanels && <PanelsRoot />}
</Container>
</PortalContext.Provider>
</ProvideTheme>
</>
</StyleSheetManager>
)
}, [studio])
}, [studio, containerRef, container])
return <EnsureProjectsDontHaveErrors>{inside}</EnsureProjectsDontHaveErrors>
return inside
}

View file

@ -1,28 +0,0 @@
import type {Studio} from '@theatre/studio/Studio'
import React from 'react'
import UIRoot from './UIRoot'
export default class UIRootWrapper extends React.Component<{
studio: Studio
}> {
state = {UIRoot}
componentDidMount() {
const self = this
if (
process.env.NODE_ENV !== 'production' &&
typeof module === 'object' &&
module &&
module.hot
) {
module.hot.accept('./UIRoot', () => {
const UIRoot = require('./UIRoot').default
self.setState({UIRoot})
})
}
}
render() {
const UIRoot = this.state.UIRoot
const rootEl = <UIRoot studio={this.props.studio} />
return rootEl
}
}

View file

@ -68,6 +68,7 @@ const propEditorByPropType: {
enum: () => <></>,
boolean: BooleanPropEditor,
stringLiteral: StringLiteralPropEditor,
cssrgba: () => <></>,
}
const DeterminePropEditor: React.FC<{

View file

@ -0,0 +1,35 @@
import {useVal} from '@theatre/dataverse-react'
import getStudio from '@theatre/studio/getStudio'
import React from 'react'
import styled from 'styled-components'
const Container = styled.div`
position: fixed;
z-index: 50;
top: 12px;
right: 12px;
left: 12px;
pointer-events: none;
display: flex;
gap: 1rem;
`
const GlobalToolbar: React.FC<{}> = (props) => {
const groups: Array<React.ReactNode> = []
const extensions = useVal(getStudio().extensionsP)
for (const [, extension] of Object.entries(extensions)) {
if (extension.globalToolbar) {
groups.push(
<extension.globalToolbar.component
key={'extensionToolbar-' + extension.id}
/>,
)
}
}
return <Container>{groups}</Container>
}
export default GlobalToolbar