Implement studio.globalToolbar
This commit is contained in:
parent
86547aa4cb
commit
921bc44270
18 changed files with 433 additions and 924 deletions
|
@ -124,6 +124,25 @@ export const compound = <Props extends IValidCompoundProps>(
|
|||
}
|
||||
}
|
||||
|
||||
export interface PropTypeConfig_CSSRGBA
|
||||
extends IBasePropType<{r: number; g: number; b: number; a: number}> {
|
||||
type: 'cssrgba'
|
||||
default: {r: number; g: number; b: number; a: number}
|
||||
}
|
||||
|
||||
export const rgba = (
|
||||
defaultValue: {r: number; b: number; g: number; a: number},
|
||||
extras?: PropTypeConfigExtras,
|
||||
): PropTypeConfig_CSSRGBA => {
|
||||
return {
|
||||
type: 'cssrgba',
|
||||
valueType: null as $IntentionalAny,
|
||||
[s]: 'TheatrePropType',
|
||||
label: extras?.label,
|
||||
default: defaultValue,
|
||||
}
|
||||
}
|
||||
|
||||
export interface PropTypeConfig_Enum extends IBasePropType<{}> {
|
||||
type: 'enum'
|
||||
cases: Record<string, PropTypeConfig>
|
||||
|
@ -135,6 +154,7 @@ export type PropTypeConfig_AllPrimitives =
|
|||
| PropTypeConfig_Boolean
|
||||
| PropTypeConfig_String
|
||||
| PropTypeConfig_StringLiteral<$IntentionalAny>
|
||||
| PropTypeConfig_CSSRGBA
|
||||
|
||||
export type PropTypeConfig =
|
||||
| PropTypeConfig_AllPrimitives
|
||||
|
|
|
@ -63,7 +63,6 @@
|
|||
"json-touch-patch": "^0.11.2",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"microbundle": "^0.13.0",
|
||||
"nanoid": "^3.1.23",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"null-loader": "^4.0.1",
|
||||
|
@ -77,6 +76,7 @@
|
|||
"react-shadow": "^19.0.2",
|
||||
"react-use": "^17.2.4",
|
||||
"react-use-gesture": "^9.1.3",
|
||||
"reakit": "^1.3.8",
|
||||
"redux": "^3.7.2",
|
||||
"redux-actions": "^2.6.5",
|
||||
"rimraf": "^3.0.2",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -68,6 +68,7 @@ const propEditorByPropType: {
|
|||
enum: () => <></>,
|
||||
boolean: BooleanPropEditor,
|
||||
stringLiteral: StringLiteralPropEditor,
|
||||
cssrgba: () => <></>,
|
||||
}
|
||||
|
||||
const DeterminePropEditor: React.FC<{
|
||||
|
|
35
theatre/studio/src/toolbars/GlobalToolbar/GlobalToolbar.tsx
Normal file
35
theatre/studio/src/toolbars/GlobalToolbar/GlobalToolbar.tsx
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue