UX Improvements
* The cam state of SnapshotEditor cameras now persist * Title bar for the prop editor * Cleaned up the toolbar
This commit is contained in:
parent
d621794280
commit
215cd880f0
13 changed files with 354 additions and 170 deletions
|
@ -1,5 +1,5 @@
|
|||
import {editable as e, bindToCanvas} from '@theatre/plugin-r3f'
|
||||
import {OrbitControls, Stars} from '@react-three/drei'
|
||||
import {Stars} from '@react-three/drei'
|
||||
import {getProject} from '@theatre/core'
|
||||
import React, {Suspense} from 'react'
|
||||
import {Canvas} from '@react-three/fiber'
|
||||
|
@ -86,12 +86,12 @@ function App() {
|
|||
<Suspense fallback={null}>
|
||||
<Model url={sceneGLB} />
|
||||
</Suspense>
|
||||
<OrbitControls
|
||||
{/* <OrbitControls
|
||||
enablePan={false}
|
||||
enableZoom={true}
|
||||
maxPolarAngle={Math.PI / 2}
|
||||
minPolarAngle={Math.PI / 2}
|
||||
/>
|
||||
/> */}
|
||||
<Stars radius={500} depth={50} count={1000} factor={10} />
|
||||
</Canvas>
|
||||
</div>
|
||||
|
|
11
packages/plugin-r3f/src/.eslintrc.js
Normal file
11
packages/plugin-r3f/src/.eslintrc.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
module.exports = {
|
||||
rules: {
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
selector: `ImportDeclaration[source.value=/@theatre\\u002F(studio|core)\\u002F/]`,
|
||||
message: `Importing Theatre's submodules would not work in the production build.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
|
@ -9,7 +9,7 @@ import React, {
|
|||
import {useEditorStore} from '../store'
|
||||
import {createPortal} from '@react-three/fiber'
|
||||
import EditableProxy from './EditableProxy'
|
||||
import type {OrbitControls} from '@react-three/drei'
|
||||
import type {OrbitControls} from 'three-stdlib'
|
||||
import TransformControls from './TransformControls'
|
||||
import shallow from 'zustand/shallow'
|
||||
import type {Material, Mesh, Object3D} from 'three'
|
||||
|
@ -21,7 +21,7 @@ import {useSelected} from './useSelected'
|
|||
import {useVal} from '@theatre/dataverse-react'
|
||||
|
||||
export interface ProxyManagerProps {
|
||||
orbitControlsRef: React.MutableRefObject<typeof OrbitControls | undefined>
|
||||
orbitControlsRef: React.MutableRefObject<OrbitControls | null>
|
||||
}
|
||||
|
||||
type IEditableProxy = {
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import {useState} from 'react'
|
||||
import {useLayoutEffect} from 'react'
|
||||
import React, {useEffect, useRef} from 'react'
|
||||
import React from 'react'
|
||||
import {Canvas} from '@react-three/fiber'
|
||||
import {useEditorStore} from '../store'
|
||||
import {OrbitControls} from '@react-three/drei'
|
||||
import shallow from 'zustand/shallow'
|
||||
import root from 'react-shadow/styled-components'
|
||||
import ProxyManager from './ProxyManager'
|
||||
|
@ -11,6 +9,9 @@ import studio, {ToolbarIconButton} from '@theatre/studio'
|
|||
import {useVal} from '@theatre/dataverse-react'
|
||||
import styled, {createGlobalStyle, StyleSheetManager} from 'styled-components'
|
||||
import {IoCameraReverseOutline} from 'react-icons/all'
|
||||
import type {ISheetObject, ISheet} from '@theatre/core'
|
||||
import type {$FixMe} from '../types'
|
||||
import useSnapshotEditorCamera from './useSnapshotEditorCamera'
|
||||
|
||||
const GlobalStyle = createGlobalStyle`
|
||||
:host {
|
||||
|
@ -31,31 +32,29 @@ const GlobalStyle = createGlobalStyle`
|
|||
}
|
||||
`
|
||||
|
||||
const EditorScene = () => {
|
||||
const orbitControlsRef = useRef<typeof OrbitControls>()
|
||||
const EditorScene: React.FC<{snapshotEditorSheet: ISheet; paneId: string}> = ({
|
||||
snapshotEditorSheet,
|
||||
paneId,
|
||||
}) => {
|
||||
const [editorCamera, orbitControlsRef] = useSnapshotEditorCamera(
|
||||
snapshotEditorSheet,
|
||||
paneId,
|
||||
)
|
||||
|
||||
const [editorObject, helpersRoot, setOrbitControlsRef] = useEditorStore(
|
||||
(state) => [
|
||||
state.editorObject,
|
||||
state.helpersRoot,
|
||||
state.setOrbitControlsRef,
|
||||
],
|
||||
const [editorObject, helpersRoot] = useEditorStore(
|
||||
(state) => [state.editorObject, state.helpersRoot],
|
||||
shallow,
|
||||
)
|
||||
|
||||
const showGrid = useVal(editorObject?.props.viewport.showGrid) ?? true
|
||||
const showAxes = useVal(editorObject?.props.viewport.showAxes) ?? true
|
||||
|
||||
useEffect(() => {
|
||||
setOrbitControlsRef(orbitControlsRef)
|
||||
}, [setOrbitControlsRef])
|
||||
|
||||
return (
|
||||
<>
|
||||
{showGrid && <gridHelper args={[20, 20, '#6e6e6e', '#4a4b4b']} />}
|
||||
{showAxes && <axesHelper args={[500]} />}
|
||||
{/* @ts-ignore */}
|
||||
<OrbitControls ref={orbitControlsRef} enableDamping={false} />
|
||||
{editorCamera}
|
||||
|
||||
<primitive object={helpersRoot}></primitive>
|
||||
<ProxyManager orbitControlsRef={orbitControlsRef} />
|
||||
<color attach="background" args={[0.24, 0.24, 0.24]} />
|
||||
|
@ -96,82 +95,83 @@ const Tools = styled.div`
|
|||
pointer-events: auto;
|
||||
`
|
||||
|
||||
const SnapshotEditor: React.FC<{}> = () => {
|
||||
const [editorObject, sceneSnapshot, initialEditorCamera, createSnapshot] =
|
||||
useEditorStore(
|
||||
const SnapshotEditor: React.FC<{object: ISheetObject<$FixMe>; paneId: string}> =
|
||||
(props) => {
|
||||
const snapshotEditorSheet = props.object.sheet
|
||||
const paneId = props.paneId
|
||||
|
||||
const [editorObject, sceneSnapshot, createSnapshot] = useEditorStore(
|
||||
(state) => [
|
||||
state.editorObject,
|
||||
state.sceneSnapshot,
|
||||
state.initialEditorCamera,
|
||||
state.createSnapshot,
|
||||
],
|
||||
shallow,
|
||||
)
|
||||
|
||||
const editorOpen = true
|
||||
useLayoutEffect(() => {
|
||||
let timeout: NodeJS.Timeout | undefined
|
||||
if (editorOpen) {
|
||||
// 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)
|
||||
const editorOpen = true
|
||||
useLayoutEffect(() => {
|
||||
let timeout: NodeJS.Timeout | undefined
|
||||
if (editorOpen) {
|
||||
// a hack to make sure all the scene's props are
|
||||
// applied before we take a snapshot
|
||||
timeout = setTimeout(createSnapshot, 100)
|
||||
}
|
||||
}
|
||||
}, [editorOpen])
|
||||
return () => {
|
||||
if (timeout !== undefined) {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
}
|
||||
}, [editorOpen])
|
||||
|
||||
const [overlay, setOverlay] = useState<HTMLDivElement | null>(null)
|
||||
if (!editorObject) return <></>
|
||||
|
||||
if (!editorObject) return <></>
|
||||
return (
|
||||
<root.div>
|
||||
<StyleSheetManager disableVendorPrefixes>
|
||||
<>
|
||||
<GlobalStyle />
|
||||
<Wrapper>
|
||||
<Overlay>
|
||||
<Tools>
|
||||
<ToolbarIconButton
|
||||
icon={<IoCameraReverseOutline />}
|
||||
label="Refresh Snapshot"
|
||||
onClick={createSnapshot}
|
||||
></ToolbarIconButton>
|
||||
</Tools>
|
||||
</Overlay>
|
||||
|
||||
return (
|
||||
<root.div>
|
||||
<StyleSheetManager disableVendorPrefixes>
|
||||
<>
|
||||
<GlobalStyle />
|
||||
{/* <PortalContext.Provider value={overlay}> */}
|
||||
<Wrapper>
|
||||
<Overlay ref={setOverlay}>
|
||||
<Tools>
|
||||
<ToolbarIconButton
|
||||
icon={<IoCameraReverseOutline />}
|
||||
label="Refresh Snapshot"
|
||||
onClick={createSnapshot}
|
||||
></ToolbarIconButton>
|
||||
</Tools>
|
||||
</Overlay>
|
||||
|
||||
{sceneSnapshot ? (
|
||||
<>
|
||||
<CanvasWrapper>
|
||||
<Canvas
|
||||
// @ts-ignore
|
||||
colorManagement
|
||||
camera={initialEditorCamera}
|
||||
onCreated={({gl}) => {
|
||||
gl.setClearColor('white')
|
||||
}}
|
||||
shadowMap
|
||||
dpr={[1, 2]}
|
||||
fog={'red'}
|
||||
onPointerMissed={() =>
|
||||
studio.__experimental_setSelection([])
|
||||
}
|
||||
>
|
||||
<EditorScene />
|
||||
</Canvas>
|
||||
</CanvasWrapper>
|
||||
</>
|
||||
) : null}
|
||||
</Wrapper>
|
||||
{/* </PortalContext.Provider> */}
|
||||
</>
|
||||
</StyleSheetManager>
|
||||
</root.div>
|
||||
)
|
||||
}
|
||||
{sceneSnapshot ? (
|
||||
<>
|
||||
<CanvasWrapper>
|
||||
<Canvas
|
||||
// @ts-ignore
|
||||
colorManagement
|
||||
onCreated={({gl}) => {
|
||||
gl.setClearColor('white')
|
||||
}}
|
||||
shadowMap
|
||||
dpr={[1, 2]}
|
||||
fog={'red'}
|
||||
onPointerMissed={() =>
|
||||
studio.__experimental_setSelection([])
|
||||
}
|
||||
>
|
||||
<EditorScene
|
||||
snapshotEditorSheet={snapshotEditorSheet}
|
||||
paneId={paneId}
|
||||
/>
|
||||
</Canvas>
|
||||
</CanvasWrapper>
|
||||
</>
|
||||
) : null}
|
||||
</Wrapper>
|
||||
{/* </PortalContext.Provider> */}
|
||||
</>
|
||||
</StyleSheetManager>
|
||||
</root.div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SnapshotEditor
|
||||
|
|
|
@ -2,21 +2,13 @@ import type {VFC} from 'react'
|
|||
import React from 'react'
|
||||
import {useEditorStore} from '../../store'
|
||||
import shallow from 'zustand/shallow'
|
||||
import {GiPocketBow, IoCameraOutline, RiFocus3Line} from 'react-icons/all'
|
||||
import {Vector3} from 'three'
|
||||
import type {$FixMe} from '@theatre/shared/utils/types'
|
||||
import {IoCameraOutline} from 'react-icons/all'
|
||||
import studio, {ToolbarIconButton} from '@theatre/studio'
|
||||
import {getSelected} from '../useSelected'
|
||||
import {useVal} from '@theatre/dataverse-react'
|
||||
import styled from 'styled-components'
|
||||
import TransformControlsModeSelect from './TransformControlsModeSelect'
|
||||
import ViewportShadingSelect from './ViewportShadingSelect'
|
||||
import TransformControlsSpaceSelect from './TransformControlsSpaceSelect'
|
||||
|
||||
const ToolGroup = styled.div`
|
||||
pointer-events: auto;
|
||||
`
|
||||
|
||||
const Toolbar: VFC = () => {
|
||||
const [editorObject] = useEditorStore(
|
||||
(state) => [state.editorObject],
|
||||
|
@ -66,7 +58,7 @@ const Toolbar: VFC = () => {
|
|||
}}
|
||||
/>
|
||||
|
||||
<ToolbarIconButton
|
||||
{/* <ToolbarIconButton
|
||||
label="Focus on selected"
|
||||
icon={<RiFocus3Line />}
|
||||
onClick={() => {
|
||||
|
@ -88,9 +80,9 @@ const Toolbar: VFC = () => {
|
|||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<ToolbarIconButton
|
||||
{/* <ToolbarIconButton
|
||||
label="Align object to view"
|
||||
icon={<GiPocketBow />}
|
||||
onClick={() => {
|
||||
|
@ -121,7 +113,7 @@ const Toolbar: VFC = () => {
|
|||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
/> */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import React, {forwardRef, useLayoutEffect, useEffect, useMemo} from 'react'
|
|||
import type {ReactThreeFiber, Overwrite} from '@react-three/fiber'
|
||||
import {useThree} from '@react-three/fiber'
|
||||
import {TransformControls as TransformControlsImpl} from 'three/examples/jsm/controls/TransformControls'
|
||||
import type {OrbitControls} from '@react-three/drei'
|
||||
import type {OrbitControls} from 'three-stdlib'
|
||||
|
||||
type R3fTransformControls = Overwrite<
|
||||
ReactThreeFiber.Object3DNode<
|
||||
|
@ -15,7 +15,7 @@ type R3fTransformControls = Overwrite<
|
|||
|
||||
export interface TransformControlsProps extends R3fTransformControls {
|
||||
object: Object3D
|
||||
orbitControlsRef?: React.MutableRefObject<typeof OrbitControls | undefined>
|
||||
orbitControlsRef?: React.MutableRefObject<OrbitControls | null>
|
||||
onObjectChange?: (event: Event) => void
|
||||
onDraggingChange?: (event: Event) => void
|
||||
}
|
||||
|
|
37
packages/plugin-r3f/src/components/useRefAndState.ts
Normal file
37
packages/plugin-r3f/src/components/useRefAndState.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import type {MutableRefObject} from 'react'
|
||||
import {useMemo, useState} from 'react'
|
||||
|
||||
/**
|
||||
* Combines useRef() and useState().
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const [ref, val] = useRefAndState<HTMLDivElement | null>(null)
|
||||
*
|
||||
* useEffect(() => {
|
||||
* val.addEventListener(...)
|
||||
* }, [val])
|
||||
*
|
||||
* return <div ref={ref}></div>
|
||||
* ```
|
||||
*/
|
||||
export default function useRefAndState<T>(
|
||||
initialValue: T,
|
||||
): [ref: MutableRefObject<T>, state: T] {
|
||||
const ref = useMemo(() => {
|
||||
let current = initialValue
|
||||
return {
|
||||
get current() {
|
||||
return current
|
||||
},
|
||||
set current(v: T) {
|
||||
current = v
|
||||
setState(v)
|
||||
},
|
||||
}
|
||||
}, [])
|
||||
|
||||
const [state, setState] = useState<T>(() => initialValue)
|
||||
|
||||
return [ref, state]
|
||||
}
|
166
packages/plugin-r3f/src/components/useSnapshotEditorCamera.tsx
Normal file
166
packages/plugin-r3f/src/components/useSnapshotEditorCamera.tsx
Normal file
|
@ -0,0 +1,166 @@
|
|||
import {OrbitControls, PerspectiveCamera} from '@react-three/drei'
|
||||
import type {OrbitControls as OrbitControlsImpl} from 'three-stdlib'
|
||||
import type {MutableRefObject} from 'react'
|
||||
import {useLayoutEffect, useRef} from 'react'
|
||||
import React from 'react'
|
||||
import useRefAndState from './useRefAndState'
|
||||
import {studio} from '@theatre/studio'
|
||||
import type {PerspectiveCamera as PerspectiveCameraImpl} from 'three'
|
||||
import type {ISheet} from '@theatre/core'
|
||||
import {types} from '@theatre/core'
|
||||
import type {ISheetObject} from '@theatre/core'
|
||||
import {useThree} from '@react-three/fiber'
|
||||
|
||||
const camConf = types.compound({
|
||||
transform: types.compound({
|
||||
position: types.compound({
|
||||
x: types.number(0),
|
||||
y: types.number(10),
|
||||
z: types.number(0),
|
||||
}),
|
||||
target: types.compound({
|
||||
x: types.number(0),
|
||||
y: types.number(10),
|
||||
z: types.number(0),
|
||||
}),
|
||||
rotation: types.compound({
|
||||
x: types.number(0),
|
||||
y: types.number(0),
|
||||
z: types.number(0),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
export default function useSnapshotEditorCamera(
|
||||
snapshotEditorSheet: ISheet,
|
||||
paneId: string,
|
||||
): [
|
||||
node: React.ReactNode,
|
||||
orbitControlsRef: MutableRefObject<OrbitControlsImpl | null>,
|
||||
] {
|
||||
// OrbitControls and Cam might change later on, so we use useRefAndState()
|
||||
// instead of useRef() to catch those changes.
|
||||
const [orbitControlsRef, orbitControls] =
|
||||
useRefAndState<OrbitControlsImpl | null>(null)
|
||||
|
||||
const [camRef, cam] = useRefAndState<PerspectiveCameraImpl | undefined>(
|
||||
undefined,
|
||||
)
|
||||
|
||||
const objRef = useRef<ISheetObject<typeof camConf> | null>(null)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!objRef.current) {
|
||||
objRef.current = snapshotEditorSheet.object(
|
||||
`Editor Camera ${paneId}`,
|
||||
{},
|
||||
camConf,
|
||||
)
|
||||
}
|
||||
}, [paneId])
|
||||
|
||||
usePassValuesFromTheatreToCamera(cam, orbitControls, objRef)
|
||||
usePassValuesFromOrbitControlsToTheatre(cam, orbitControls, objRef)
|
||||
|
||||
const node = (
|
||||
<>
|
||||
<PerspectiveCamera makeDefault ref={camRef} position={[0, 10, 0]} />
|
||||
<OrbitControls
|
||||
makeDefault
|
||||
ref={orbitControlsRef}
|
||||
camera={cam}
|
||||
enableDamping={false}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
return [node, orbitControlsRef]
|
||||
}
|
||||
|
||||
function usePassValuesFromOrbitControlsToTheatre(
|
||||
cam: PerspectiveCameraImpl | undefined,
|
||||
orbitControls: OrbitControlsImpl | null,
|
||||
objRef: MutableRefObject<ISheetObject<typeof camConf> | null>,
|
||||
) {
|
||||
useLayoutEffect(() => {
|
||||
if (!cam || orbitControls == null) return
|
||||
|
||||
let currentScrub: undefined | ReturnType<typeof studio['debouncedScrub']>
|
||||
|
||||
let started = false
|
||||
|
||||
const onStart = () => {
|
||||
started = true
|
||||
if (!currentScrub) {
|
||||
currentScrub = studio.debouncedScrub(600)
|
||||
}
|
||||
}
|
||||
const onEnd = () => {
|
||||
started = false
|
||||
}
|
||||
|
||||
const onChange = () => {
|
||||
if (!started) return
|
||||
|
||||
const p = cam!.position
|
||||
const position = {x: p.x, y: p.y, z: p.z}
|
||||
|
||||
const r = cam!.rotation
|
||||
const rotation = {x: r.x, y: r.y, z: r.z}
|
||||
|
||||
const t = orbitControls!.target
|
||||
const target = {x: t.x, y: t.y, z: t.z}
|
||||
|
||||
const transform = {
|
||||
position,
|
||||
rotation,
|
||||
target,
|
||||
}
|
||||
|
||||
currentScrub!.capture(({set}) => {
|
||||
set(objRef.current!.props.transform, transform)
|
||||
})
|
||||
}
|
||||
|
||||
orbitControls.addEventListener('start', onStart)
|
||||
orbitControls.addEventListener('end', onEnd)
|
||||
orbitControls.addEventListener('change', onChange)
|
||||
|
||||
return () => {
|
||||
orbitControls.removeEventListener('start', onStart)
|
||||
orbitControls.removeEventListener('end', onEnd)
|
||||
orbitControls.removeEventListener('change', onChange)
|
||||
}
|
||||
}, [cam, orbitControls])
|
||||
}
|
||||
|
||||
function usePassValuesFromTheatreToCamera(
|
||||
cam: PerspectiveCameraImpl | undefined,
|
||||
orbitControls: OrbitControlsImpl | null,
|
||||
objRef: MutableRefObject<ISheetObject<typeof camConf> | null>,
|
||||
) {
|
||||
const invalidate = useThree(({invalidate}) => invalidate)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!cam || orbitControls === null) return
|
||||
|
||||
const obj = objRef.current!
|
||||
const setFromTheatre = ({
|
||||
transform,
|
||||
}: {
|
||||
transform: typeof camConf['valueType']['transform']
|
||||
}): void => {
|
||||
const {position, rotation, target} = transform
|
||||
cam.position.set(position.x, position.y, position.z)
|
||||
cam.rotation.set(rotation.x, rotation.y, rotation.z)
|
||||
orbitControls.target.set(target.x, target.y, target.z)
|
||||
orbitControls.update()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
const unsub = obj.onValuesChange(setFromTheatre)
|
||||
setFromTheatre(obj.value)
|
||||
|
||||
return unsub
|
||||
}, [cam, orbitControls, objRef, invalidate])
|
||||
}
|
|
@ -2,10 +2,6 @@ import type {StateCreator} from 'zustand'
|
|||
import create from 'zustand'
|
||||
import type {Object3D, Scene, WebGLRenderer} from 'three'
|
||||
import {Group} from 'three'
|
||||
import type {MutableRefObject} from 'react'
|
||||
import type {OrbitControls} from '@react-three/drei'
|
||||
// @ts-ignore TODO
|
||||
import type {ContainerProps} from '@react-three/fiber'
|
||||
import type {ISheet, ISheetObject} from '@theatre/core'
|
||||
import {types, getProject} from '@theatre/core'
|
||||
|
||||
|
@ -138,27 +134,21 @@ export type EditorStore = {
|
|||
scene: Scene | null
|
||||
gl: WebGLRenderer | null
|
||||
allowImplicitInstancing: boolean
|
||||
orbitControlsRef: MutableRefObject<typeof OrbitControls | undefined> | null
|
||||
helpersRoot: Group
|
||||
editables: Record<string, Editable>
|
||||
// this will come in handy when we start supporting multiple canvases
|
||||
canvasName: string
|
||||
sceneSnapshot: Scene | null
|
||||
editablesSnapshot: Record<string, EditableSnapshot> | null
|
||||
initialEditorCamera: ContainerProps['camera']
|
||||
|
||||
init: (
|
||||
scene: Scene,
|
||||
gl: WebGLRenderer,
|
||||
allowImplicitInstancing: boolean,
|
||||
editorCamera: ContainerProps['camera'],
|
||||
sheet: ISheet,
|
||||
editorObject: null | ISheetObject<typeof editorSheetObjectConfig>,
|
||||
) => void
|
||||
|
||||
setOrbitControlsRef: (
|
||||
orbitControlsRef: MutableRefObject<typeof OrbitControls | undefined>,
|
||||
) => void
|
||||
addEditable: <T extends EditableType>(type: T, uniqueName: string) => void
|
||||
removeEditable: (uniqueName: string) => void
|
||||
createSnapshot: () => void
|
||||
|
@ -177,7 +167,6 @@ const config: StateCreator<EditorStore> = (set, get) => {
|
|||
scene: null,
|
||||
gl: null,
|
||||
allowImplicitInstancing: false,
|
||||
orbitControlsRef: null,
|
||||
helpersRoot: new Group(),
|
||||
editables: {},
|
||||
canvasName: 'default',
|
||||
|
@ -185,19 +174,11 @@ const config: StateCreator<EditorStore> = (set, get) => {
|
|||
editablesSnapshot: null,
|
||||
initialEditorCamera: {},
|
||||
|
||||
init: (
|
||||
scene,
|
||||
gl,
|
||||
allowImplicitInstancing,
|
||||
editorCamera,
|
||||
sheet,
|
||||
editorObject,
|
||||
) => {
|
||||
init: (scene, gl, allowImplicitInstancing, sheet, editorObject) => {
|
||||
set({
|
||||
scene,
|
||||
gl,
|
||||
allowImplicitInstancing,
|
||||
initialEditorCamera: editorCamera,
|
||||
sheet,
|
||||
editorObject,
|
||||
})
|
||||
|
@ -238,9 +219,7 @@ const config: StateCreator<EditorStore> = (set, get) => {
|
|||
},
|
||||
}
|
||||
}),
|
||||
setOrbitControlsRef: (camera) => {
|
||||
set({orbitControlsRef: camera})
|
||||
},
|
||||
|
||||
removeEditable: (name) =>
|
||||
set((state) => {
|
||||
const {[name]: removed, ...rest} = state.editables
|
||||
|
@ -284,7 +263,6 @@ export const useEditorStore = create<EditorStore>(config)
|
|||
|
||||
export type BindFunction = (options: {
|
||||
allowImplicitInstancing?: boolean
|
||||
editorCamera?: ContainerProps['camera']
|
||||
sheet: ISheet
|
||||
}) => (options: {gl: WebGLRenderer; scene: Scene}) => void
|
||||
|
||||
|
@ -309,23 +287,6 @@ const editorSheetObjectConfig = types.compound({
|
|||
},
|
||||
{as: 'menu', label: 'Shading'},
|
||||
),
|
||||
mode: types.stringLiteral(
|
||||
'translate',
|
||||
{
|
||||
translate: 'Translate',
|
||||
rotate: 'Rotate',
|
||||
scale: 'Scale',
|
||||
},
|
||||
{as: 'switch', label: 'Mode'},
|
||||
),
|
||||
space: types.stringLiteral(
|
||||
'world',
|
||||
{
|
||||
local: 'Local',
|
||||
world: 'World',
|
||||
},
|
||||
{as: 'switch', label: 'Space'},
|
||||
),
|
||||
},
|
||||
{label: 'Viewport Config'},
|
||||
),
|
||||
|
@ -355,7 +316,6 @@ const editorSheetObjectConfig = types.compound({
|
|||
|
||||
export const bindToCanvas: BindFunction = ({
|
||||
allowImplicitInstancing = false,
|
||||
editorCamera = {},
|
||||
sheet,
|
||||
}) => {
|
||||
const uiSheet: null | ISheet =
|
||||
|
@ -368,13 +328,6 @@ export const bindToCanvas: BindFunction = ({
|
|||
|
||||
return ({gl, scene}) => {
|
||||
const init = useEditorStore.getState().init
|
||||
init(
|
||||
scene,
|
||||
gl,
|
||||
allowImplicitInstancing,
|
||||
{...{position: [20, 20, 20]}, ...editorCamera},
|
||||
sheet,
|
||||
editorSheetObject,
|
||||
)
|
||||
init(scene, gl, allowImplicitInstancing, sheet, editorSheetObject)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {$FixMe, VoidFn} from '@theatre/shared/utils/types'
|
||||
import type {$FixMe, $IntentionalAny, VoidFn} from '@theatre/shared/utils/types'
|
||||
import type {IScrub} from '@theatre/studio/Scrub'
|
||||
|
||||
import type {Studio} from '@theatre/studio/Studio'
|
||||
|
@ -16,6 +16,7 @@ import type {
|
|||
PropTypeConfig_Boolean,
|
||||
PropTypeConfig_Compound,
|
||||
} from '@theatre/core/propTypes'
|
||||
import {debounce} from 'lodash-es'
|
||||
|
||||
export interface ITransactionAPI {
|
||||
set<V>(pointer: Pointer<V>, value: V): void
|
||||
|
@ -28,7 +29,7 @@ export interface PaneClassDefinition<
|
|||
class: string
|
||||
dataType: DataType
|
||||
component: React.ComponentType<{
|
||||
id: string
|
||||
paneId: string
|
||||
object: ISheetObject<
|
||||
PropTypeConfig_Compound<{
|
||||
visible: PropTypeConfig_Boolean
|
||||
|
@ -65,6 +66,7 @@ export interface IStudio {
|
|||
|
||||
transaction(fn: (api: ITransactionAPI) => void): void
|
||||
scrub(): IScrub
|
||||
debouncedScrub(threshhold: number): Pick<IScrub, 'capture'>
|
||||
|
||||
__experimental_setSelection(selection: Array<ISheetObject>): void
|
||||
__experimental_onSelectionChange(
|
||||
|
@ -158,6 +160,37 @@ export default class TheatreStudio implements IStudio {
|
|||
return getStudio().scrub()
|
||||
}
|
||||
|
||||
debouncedScrub(threshold: number = 1000): Pick<IScrub, 'capture'> {
|
||||
let currentScrub: IScrub | undefined
|
||||
const scheduleCommit = debounce(() => {
|
||||
const s = currentScrub
|
||||
if (!s) return
|
||||
currentScrub = undefined
|
||||
s.commit()
|
||||
}, threshold)
|
||||
|
||||
const capture = (arg: $IntentionalAny) => {
|
||||
if (!currentScrub) {
|
||||
currentScrub = this.scrub()
|
||||
}
|
||||
let errored = true
|
||||
try {
|
||||
currentScrub.capture(arg)
|
||||
errored = false
|
||||
} finally {
|
||||
if (errored) {
|
||||
const s = currentScrub
|
||||
currentScrub = undefined
|
||||
s.discard()
|
||||
} else {
|
||||
scheduleCommit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {capture}
|
||||
}
|
||||
|
||||
getPanesOfType<PaneClass extends string>(
|
||||
paneClass: PaneClass,
|
||||
): PaneInstance<PaneClass>[] {
|
||||
|
|
|
@ -132,7 +132,7 @@ const Content: React.FC<{paneInstance: PaneInstance<$FixMe>}> = ({
|
|||
</PanelDragZone>
|
||||
<F2>
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<Comp id={paneInstance.instanceId} object={paneInstance.object} />
|
||||
<Comp paneId={paneInstance.instanceId} object={paneInstance.object} />
|
||||
</ErrorBoundary>
|
||||
</F2>
|
||||
</Container>
|
||||
|
|
|
@ -54,6 +54,10 @@ const Title = styled.div`
|
|||
font-weight: 500;
|
||||
font-size: 10px;
|
||||
user-select: none;
|
||||
pointer-events: auto;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`
|
||||
|
||||
const headerHeight = `32px`
|
||||
|
@ -106,7 +110,9 @@ const ObjectEditorPanel: React.FC<{}> = (props) => {
|
|||
<Container>
|
||||
<Content>
|
||||
<Header>
|
||||
<Title>
|
||||
<Title
|
||||
title={`${obj.sheet.address.sheetId}: ${obj.sheet.address.sheetInstanceId} > ${obj.address.objectKey}`}
|
||||
>
|
||||
<TitleBar_Piece>{obj.sheet.address.sheetId} </TitleBar_Piece>
|
||||
|
||||
<TitleBar_Punctuation>{':'} </TitleBar_Punctuation>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import type {PropTypeConfig_Compound} from '@theatre/core/propTypes'
|
||||
import {isPropConfigComposite} from '@theatre/shared/propTypes/utils'
|
||||
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
||||
import {theme} from '@theatre/studio/css'
|
||||
import {usePrism} from '@theatre/dataverse-react'
|
||||
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
||||
import {getPointerParts} from '@theatre/dataverse'
|
||||
|
@ -25,10 +24,8 @@ const Container = styled.div`
|
|||
|
||||
const Header = styled.div`
|
||||
height: 30px;
|
||||
/* padding-left: calc(var(--left-pad) + var(--depth) * var(--step)); */
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
/* color: ${theme.panel.body.compoudThing.label.color}; */
|
||||
position: relative;
|
||||
|
||||
${rowBg};
|
||||
|
@ -40,17 +37,6 @@ const Padding = styled.div`
|
|||
align-items: center;
|
||||
`
|
||||
|
||||
const IconContainer = styled.div`
|
||||
width: 12px;
|
||||
margin-right: -12px;
|
||||
/* margin-left: ${indentationFormula}; */
|
||||
font-size: 9px;
|
||||
text-align: center;
|
||||
transform: rotateZ(90deg);
|
||||
position: relative;
|
||||
left: -18px;
|
||||
`
|
||||
|
||||
const PropName = styled.div`
|
||||
margin-left: 4px;
|
||||
cursor: default;
|
||||
|
|
Loading…
Reference in a new issue