From 2daa270879e9273799713018080affdc40eab2de Mon Sep 17 00:00:00 2001 From: Aria Minaei Date: Sat, 31 Jul 2021 15:10:08 +0200 Subject: [PATCH] UX improvements * When clicking on empty space in the snapshot editor, the selection reverts to the sheet containing the scene --- packages/plugin-r3f/src/Wrapper.tsx | 24 ++++++++++++++ .../src/components/SnapshotEditor.tsx | 13 +++++--- packages/plugin-r3f/src/index.tsx | 32 +++---------------- theatre/core/src/privateAPIs.ts | 18 ++++++++--- theatre/core/src/sequences/TheatreSequence.ts | 8 ++++- theatre/studio/src/TheatreStudio.ts | 13 +++++--- 6 files changed, 65 insertions(+), 43 deletions(-) create mode 100644 packages/plugin-r3f/src/Wrapper.tsx diff --git a/packages/plugin-r3f/src/Wrapper.tsx b/packages/plugin-r3f/src/Wrapper.tsx new file mode 100644 index 0000000..df94b1b --- /dev/null +++ b/packages/plugin-r3f/src/Wrapper.tsx @@ -0,0 +1,24 @@ +import React, {useLayoutEffect} from 'react' +import {useThree} from '@react-three/fiber' +import type {ISheet} from '@theatre/core' +import {bindToCanvas} from './store' + +const Wrapper: React.FC<{ + getSheet: () => ISheet +}> = (props) => { + const {scene, gl} = useThree((s) => ({scene: s.scene, gl: s.gl})) + + useLayoutEffect(() => { + const sheet = props.getSheet() + if (!sheet || sheet.type !== 'Theatre_Sheet_PublicAPI') { + throw new Error( + `getSheet() in has returned an invalid value`, + ) + } + bindToCanvas({sheet})({gl, scene}) + }, [scene, gl]) + + return <>{props.children} +} + +export default Wrapper diff --git a/packages/plugin-r3f/src/components/SnapshotEditor.tsx b/packages/plugin-r3f/src/components/SnapshotEditor.tsx index e8b6ec6..e415d53 100644 --- a/packages/plugin-r3f/src/components/SnapshotEditor.tsx +++ b/packages/plugin-r3f/src/components/SnapshotEditor.tsx @@ -1,4 +1,4 @@ -import {useLayoutEffect} from 'react' +import {useCallback, useLayoutEffect} from 'react' import React from 'react' import {Canvas} from '@react-three/fiber' import {useEditorStore} from '../store' @@ -100,11 +100,12 @@ const SnapshotEditor: React.FC<{object: ISheetObject<$FixMe>; paneId: string}> = const snapshotEditorSheet = props.object.sheet const paneId = props.paneId - const [editorObject, sceneSnapshot, createSnapshot] = useEditorStore( + const [editorObject, sceneSnapshot, createSnapshot, sheet] = useEditorStore( (state) => [ state.editorObject, state.sceneSnapshot, state.createSnapshot, + state.sheet, ], shallow, ) @@ -124,6 +125,10 @@ const SnapshotEditor: React.FC<{object: ISheetObject<$FixMe>; paneId: string}> = } }, [editorOpen]) + const onPointerMissed = useCallback(() => { + if (sheet !== null) studio.__experimental_setSelection([sheet]) + }, [sheet]) + if (!editorObject) return <> return ( @@ -154,9 +159,7 @@ const SnapshotEditor: React.FC<{object: ISheetObject<$FixMe>; paneId: string}> = shadowMap dpr={[1, 2]} fog={'red'} - onPointerMissed={() => - studio.__experimental_setSelection([]) - } + onPointerMissed={onPointerMissed} > ISheet -}> = (props) => { - const {scene, gl} = useThree((s) => ({scene: s.scene, gl: s.gl})) - - useLayoutEffect(() => { - const sheet = props.getSheet() - if (!sheet || sheet.type !== 'Theatre_Sheet_PublicAPI') { - throw new Error( - `getSheet() in has returned an invalid value`, - ) - } - bindToCanvas({sheet})({gl, scene}) - }, [scene, gl]) - - return <>{props.children} -} +export {default as EditorHelper} from './components/EditorHelper' +export type {EditorHelperProps} from './components/EditorHelper' +export {default as editable} from './components/editable' +export type {EditableState, BindFunction} from './store' +export {default as Wrapper} from './Wrapper' if (process.env.NODE_ENV === 'development') { studio.extend({ diff --git a/theatre/core/src/privateAPIs.ts b/theatre/core/src/privateAPIs.ts index 50e4ec2..a524183 100644 --- a/theatre/core/src/privateAPIs.ts +++ b/theatre/core/src/privateAPIs.ts @@ -10,11 +10,19 @@ import type {$IntentionalAny} from '@theatre/shared/utils/types' const publicAPIToPrivateAPIMap = new WeakMap() -export function privateAPI(pub: IProject): Project -export function privateAPI(pub: ISheet): Sheet -export function privateAPI(pub: ISheetObject<$IntentionalAny>): SheetObject -export function privateAPI(pub: ISequence): Sequence -export function privateAPI(pub: {}): unknown { +export function privateAPI< + P extends IProject | ISheet | ISheetObject<$IntentionalAny> | ISequence, +>( + pub: P, +): P extends IProject + ? Project + : P extends ISheet + ? Sheet + : P extends ISheetObject<$IntentionalAny> + ? SheetObject + : P extends ISequence + ? Sequence + : never { return publicAPIToPrivateAPIMap.get(pub) } diff --git a/theatre/core/src/sequences/TheatreSequence.ts b/theatre/core/src/sequences/TheatreSequence.ts index 4ac22ae..0861b95 100644 --- a/theatre/core/src/sequences/TheatreSequence.ts +++ b/theatre/core/src/sequences/TheatreSequence.ts @@ -5,6 +5,8 @@ import type Sequence from './Sequence' import type {IPlaybackDirection, IPlaybackRange} from './Sequence' export interface ISequence { + readonly type: 'Theatre_Sequence_PublicAPI' + /** * Starts playback of a sequence. * Returns a promise that either resolves to true when the playback completes, @@ -24,7 +26,11 @@ export interface ISequence { time: number } -export default class TheatreSequence { +export default class TheatreSequence implements ISequence { + get type(): 'Theatre_Sequence_PublicAPI' { + return 'Theatre_Sequence_PublicAPI' + } + /** * @internal */ diff --git a/theatre/studio/src/TheatreStudio.ts b/theatre/studio/src/TheatreStudio.ts index 379de14..26d5aca 100644 --- a/theatre/studio/src/TheatreStudio.ts +++ b/theatre/studio/src/TheatreStudio.ts @@ -1,4 +1,4 @@ -import type {ISheetObject} from '@theatre/core' +import type {ISheet, ISheetObject} from '@theatre/core' import studioTicker from '@theatre/studio/studioTicker' import type {IDerivation, Pointer} from '@theatre/dataverse' import {prism} from '@theatre/dataverse' @@ -7,7 +7,10 @@ import type {$FixMe, $IntentionalAny, VoidFn} from '@theatre/shared/utils/types' import type {IScrub} from '@theatre/studio/Scrub' import type {Studio} from '@theatre/studio/Studio' -import {isSheetObjectPublicAPI} from '@theatre/shared/instanceTypes' +import { + isSheetObjectPublicAPI, + isSheetPublicAPI, +} from '@theatre/shared/instanceTypes' import {getOutlineSelection} from './selectors' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import getStudio from './getStudio' @@ -68,7 +71,7 @@ export interface IStudio { scrub(): IScrub debouncedScrub(threshhold: number): Pick - __experimental_setSelection(selection: Array): void + __experimental_setSelection(selection: Array): void __experimental_onSelectionChange( fn: (s: Array) => void, ): VoidFunction @@ -136,9 +139,9 @@ export default class TheatreStudio implements IStudio { return this._getSelectionDerivation().getValue() } - __experimental_setSelection(selection: Array): void { + __experimental_setSelection(selection: Array): void { const sanitizedSelection = [...selection] - .filter((s) => isSheetObjectPublicAPI(s)) + .filter((s) => isSheetObjectPublicAPI(s) || isSheetPublicAPI(s)) .map((s) => getStudio().corePrivateAPI!(s)) getStudio().transaction(({stateEditors}) => {