From 631bcba7248fae067e48ac90898735d9a243c9fb Mon Sep 17 00:00:00 2001 From: Aria Minaei Date: Sat, 18 Sep 2021 21:43:29 +0200 Subject: [PATCH] More docs and annotations --- packages/playground/devEnv/servePlayground.ts | 7 +- packages/playground/src/dom/Scene.tsx | 3 +- .../playground/src/playground-globals.d.ts | 4 + packages/r3f/src/components/ProxyManager.tsx | 6 +- .../r3f/src/components/RefreshSnapshot.tsx | 2 +- packages/r3f/src/components/useRefAndState.ts | 4 +- theatre/core/src/coreExports.ts | 6 +- theatre/core/src/sequences/TheatreSequence.ts | 4 +- .../src/sheetObjects/TheatreSheetObject.ts | 6 +- theatre/core/src/sheets/TheatreSheet.ts | 2 +- theatre/studio/src/PaneManager.ts | 6 - theatre/studio/src/Scrub.ts | 49 ++++++ theatre/studio/src/TheatreStudio.ts | 149 ++++++++++++++++-- theatre/studio/src/store/types/ephemeral.ts | 8 +- theatre/studio/src/utils/useRefAndState.ts | 4 +- 15 files changed, 214 insertions(+), 46 deletions(-) diff --git a/packages/playground/devEnv/servePlayground.ts b/packages/playground/devEnv/servePlayground.ts index 900f43f..1550eb3 100644 --- a/packages/playground/devEnv/servePlayground.ts +++ b/packages/playground/devEnv/servePlayground.ts @@ -14,7 +14,12 @@ require('esbuild') { entryPoints: [path.join(playgroundDir, 'src/index.tsx')], target: ['firefox88'], - loader: {'.png': 'file', '.glb': 'file', '.svg': 'dataurl'}, + loader: { + '.png': 'file', + '.glb': 'file', + '.gltf': 'file', + '.svg': 'dataurl', + }, bundle: true, sourcemap: true, define: definedGlobals, diff --git a/packages/playground/src/dom/Scene.tsx b/packages/playground/src/dom/Scene.tsx index e08d92b..0809b8a 100644 --- a/packages/playground/src/dom/Scene.tsx +++ b/packages/playground/src/dom/Scene.tsx @@ -3,6 +3,7 @@ import type {UseDragOpts} from './useDrag' import useDrag from './useDrag' import React, {useLayoutEffect, useMemo, useState} from 'react' import type {IProject, ISheet} from '@theatre/core' +import {onChange} from '@theatre/core' import type {IScrub, IStudio} from '@theatre/studio' studio.initialize() @@ -25,7 +26,7 @@ const Box: React.FC<{ const [pos, setPos] = useState<{x: number; y: number}>({x: 0, y: 0}) useLayoutEffect(() => { - const unsubscribeFromChanges = obj.onValuesChange((newValues) => { + const unsubscribeFromChanges = onChange(obj.props, (newValues) => { setPos(newValues) }) return unsubscribeFromChanges diff --git a/packages/playground/src/playground-globals.d.ts b/packages/playground/src/playground-globals.d.ts index 28516bb..60bacdc 100644 --- a/packages/playground/src/playground-globals.d.ts +++ b/packages/playground/src/playground-globals.d.ts @@ -5,3 +5,7 @@ declare module '*.png' { declare module '*.glb' { export default string } + +declare module '*.gltf' { + export default string +} diff --git a/packages/r3f/src/components/ProxyManager.tsx b/packages/r3f/src/components/ProxyManager.tsx index d8d8918..d86ba75 100644 --- a/packages/r3f/src/components/ProxyManager.tsx +++ b/packages/r3f/src/components/ProxyManager.tsx @@ -218,6 +218,10 @@ const ProxyManager: VFC = ({orbitControlsRef}) => { }) }, [viewportShading, renderMaterials, sceneProxy]) + const scrub = useMemo(() => { + return studio.debouncedScrub(1000) + }, [selected, editableProxyOfSelected]) + if (!sceneProxy) { return null } @@ -235,7 +239,7 @@ const ProxyManager: VFC = ({orbitControlsRef}) => { const sheetObject = editableProxyOfSelected.sheetObject const obj = editableProxyOfSelected.object - studio.transaction(({set}) => { + scrub.capture(({set}) => { set(sheetObject.props, { position: { x: obj.position.x, diff --git a/packages/r3f/src/components/RefreshSnapshot.tsx b/packages/r3f/src/components/RefreshSnapshot.tsx index 4f105d0..a091761 100644 --- a/packages/r3f/src/components/RefreshSnapshot.tsx +++ b/packages/r3f/src/components/RefreshSnapshot.tsx @@ -8,7 +8,7 @@ import useRefreshSnapshot from './useRefreshSnapshot' * Alternatively you can use * @link useRefreshSnapshot() * - * @example + * Usage * ```jsx * * diff --git a/packages/r3f/src/components/useRefAndState.ts b/packages/r3f/src/components/useRefAndState.ts index c567c6a..a40f12b 100644 --- a/packages/r3f/src/components/useRefAndState.ts +++ b/packages/r3f/src/components/useRefAndState.ts @@ -4,8 +4,8 @@ import {useMemo, useState} from 'react' /** * Combines useRef() and useState(). * - * @example - * ```typescript + * Usage: + * ```ts * const [ref, val] = useRefAndState(null) * * useEffect(() => { diff --git a/theatre/core/src/coreExports.ts b/theatre/core/src/coreExports.ts index 9b04e1f..c363a77 100644 --- a/theatre/core/src/coreExports.ts +++ b/theatre/core/src/coreExports.ts @@ -11,7 +11,7 @@ import {InvalidArgumentError} from '@theatre/shared/utils/errors' import {validateName} from '@theatre/shared/utils/sanitizers' import userReadableTypeOfValue from '@theatre/shared/utils/userReadableTypeOfValue' import deepEqual from 'fast-deep-equal' -import type {IDerivation, PointerType} from '@theatre/dataverse' +import type {PointerType} from '@theatre/dataverse' import {isPointer} from '@theatre/dataverse' import {isDerivation, valueDerivation} from '@theatre/dataverse' import type {$IntentionalAny, VoidFn} from '@theatre/shared/utils/types' @@ -126,9 +126,9 @@ const validateProjectIdOrThrow = (value: string) => { * @param callback The callback is called every time the value of pointerOrDerivation changes * @returns An unsubscribe function */ -export function onChange | IDerivation>( +export function onChange

>( pointer: P, - callback: (value: O) => void, + callback: (value: P extends PointerType ? T : unknown) => void, ): VoidFn { if (isPointer(pointer)) { const derivation = valueDerivation(pointer) diff --git a/theatre/core/src/sequences/TheatreSequence.ts b/theatre/core/src/sequences/TheatreSequence.ts index c752bc6..1d17c48 100644 --- a/theatre/core/src/sequences/TheatreSequence.ts +++ b/theatre/core/src/sequences/TheatreSequence.ts @@ -31,7 +31,7 @@ export interface ISequence { * * @returns A promise that resolves when the playback is finished, or rejects if interruped * - * @example + * Usage: * ```ts * // plays the sequence from the current position to sequence.length * sheet.sequence.play() @@ -93,7 +93,7 @@ export interface ISequence { * * @returns A promise that resolves once the audio source is loaded and decoded * - * @example + * Usage: * ```ts * // Loads and decodes audio from the URL and then attaches it to the sequence * await sheet.sequence.attachAudio({source: "https://localhost/audio.ogg"}) diff --git a/theatre/core/src/sheetObjects/TheatreSheetObject.ts b/theatre/core/src/sheetObjects/TheatreSheetObject.ts index bae1983..d5bb9e6 100644 --- a/theatre/core/src/sheetObjects/TheatreSheetObject.ts +++ b/theatre/core/src/sheetObjects/TheatreSheetObject.ts @@ -26,7 +26,7 @@ export interface ISheetObject { /** * The current values of the props. * - * @example + * Usage: * ```ts * const obj = sheet.object("obj", {x: 0}) * console.log(obj.value.x) // prints 0 or the current numeric value @@ -61,7 +61,7 @@ export interface ISheetObject { * * @returns an Unsubscribe function * - * @example + * Usage: * ```ts * const obj = sheet.object("Box", {position: {x: 0, y: 0}}) * const div = document.getElementById("box") @@ -82,7 +82,7 @@ export interface ISheetObject { * overrides it in the UI with a static or animated value. * * - * @example + * Usage: * ```ts * const obj = sheet.object("obj", {position: {x: 0, y: 0}}) * diff --git a/theatre/core/src/sheets/TheatreSheet.ts b/theatre/core/src/sheets/TheatreSheet.ts index 76469fd..f2673fb 100644 --- a/theatre/core/src/sheets/TheatreSheet.ts +++ b/theatre/core/src/sheets/TheatreSheet.ts @@ -45,7 +45,7 @@ export interface ISheet { * * @returns An Object * - * @example + * Usage: * ```ts * // Create an object named "a unique key" with no props * const obj = sheet.object("a unique key", {}) diff --git a/theatre/studio/src/PaneManager.ts b/theatre/studio/src/PaneManager.ts index 1dae54f..94c2a20 100644 --- a/theatre/studio/src/PaneManager.ts +++ b/theatre/studio/src/PaneManager.ts @@ -64,12 +64,6 @@ export default class PaneManager { return this._getAllPanes() } - getPanesOfType( - paneClass: PaneClass, - ): PaneInstance[] { - return [] - } - createPane( paneClass: PaneClass, ): PaneInstance { diff --git a/theatre/studio/src/Scrub.ts b/theatre/studio/src/Scrub.ts index 49a4ce8..3f7532f 100644 --- a/theatre/studio/src/Scrub.ts +++ b/theatre/studio/src/Scrub.ts @@ -23,14 +23,63 @@ type State = let lastScrubIdAsNumber = 0 +/** + * The scrub API + */ export interface IScrubApi { + /** + * Set the value of a prop by its pointer. If the prop is sequenced, the value + * will be a keyframe at the current sequence position. + * + * Usage: + * ```ts + * const obj = sheet.object("box", {x: 0, y: 0}) + * const scrub = studio.scrub() + * scrub.capture(({set}) => { + * // set a specific prop's value + * set(obj.props.x, 10) // New value is {x: 10, y: 0} + * // values are set partially + * set(obj.props, {y: 11}) // New value is {x: 10, y: 11} + * + * // this will error, as there is no such prop as 'z' + * set(obj.props.z, 10) + * }) + * ``` + * @param pointer A Pointer, like object.props + * @param value The value to override the existing value. This is treated as a deep partial value. + */ set(pointer: Pointer, value: T): void } export interface IScrub { + /** + * Clears all the ops in the scrub, but keeps the scrub open so you can call + * `scrub.capture()` again. + */ reset(): void + /** + * Commits the scrub and creates a single undo level. + */ commit(): void + /** + * Captures operations for the scrub. + * + * Note that running `scrub.capture()` multiple times means all the older + * calls of `scrub.capture()` will be reset. + * + * Usage: + * ```ts + * scrub.capture(({set}) => { + * set(obj.props.x, 10) // set the value of obj.props.x to 10 + * }) + * ``` + */ capture(fn: (api: IScrubApi) => void): void + + /** + * Clearts the ops of the scrub and destroys it. After calling this, + * you won't be able to call `scrub.capture()` anymore. + */ discard(): void } diff --git a/theatre/studio/src/TheatreStudio.ts b/theatre/studio/src/TheatreStudio.ts index 9abe132..6f0db06 100644 --- a/theatre/studio/src/TheatreStudio.ts +++ b/theatre/studio/src/TheatreStudio.ts @@ -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, $IntentionalAny, 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' import { @@ -14,22 +14,67 @@ import {getOutlineSelection} from './selectors' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import getStudio from './getStudio' import type React from 'react' -import type {PropTypeConfig_Compound} from '@theatre/core/propTypes' import {debounce} from 'lodash-es' import type Sheet from '@theatre/core/sheets/Sheet' export interface ITransactionAPI { + /** + * Set the value of a prop by its pointer. If the prop is sequenced, the value + * will be a keyframe at the current sequence position. + * + * Usage: + * ```ts + * const obj = sheet.object("box", {x: 0, y: 0}) + * studio.transaction(({set}) => { + * // set a specific prop's value + * set(obj.props.x, 10) // New value is {x: 10, y: 0} + * // values are set partially + * set(obj.props, {y: 11}) // New value is {x: 10, y: 11} + * + * // this will error, as there is no such prop as 'z' + * set(obj.props.z, 10) + * }) + * ``` + * @param pointer A Pointer, like object.props + * @param value The value to override the existing value. This is treated as a deep partial value. + */ set(pointer: Pointer, value: V): void + /** + * Unsets the value of a prop by its pointer. + * + * Usage: + * ```ts + * const obj = sheet.object("box", {x: 0, y: 0}) + * studio.transaction(({set}) => { + * // set props.x to its default value + * unset(obj.props.x) + * // set all props to their default value + * set(obj.props) + * }) + * ``` + * @param pointer A pointer, like object.props + */ unset(pointer: Pointer): void } /** * */ -export interface PaneClassDefinition< - DataType extends PropTypeConfig_Compound<{}>, -> { +export interface PaneClassDefinition { + /** + * Each pane has a `class`, which is a string. + */ class: string + /** + * A react component that renders the content of the pane. It is given + * a single prop, `paneId`, which is a unique identifier for the pane. + * + * If you wish to store and persist the state of the pane, + * simply use a sheet and an object. + */ component: React.ComponentType<{ + /** + * The unique identifier of the pane + */ paneId: string }> } @@ -56,13 +101,13 @@ export interface IExtension { /** * Introduces new pane types. */ - panes?: Array> + panes?: Array } export type PaneInstance = { extensionId: string instanceId: string - definition: PaneClassDefinition<$FixMe> + definition: PaneClassDefinition } /** * This is the public api of Theatre's studio. It is exposed through: @@ -119,8 +164,83 @@ export interface IStudio { usePersistentStorage?: boolean }): void + /** + * Runs an undo-able transaction. Creates a single undo level for all + * the operations inside the transaction. + * + * Will roll back if an error is thrown. + * + * Usage: + * ```ts + * studio.transaction(({set, unset}) => { + * set(obj.props.x, 10) // set the value of obj.props.x to 10 + * unset(obj.props.y) // unset the override at obj.props.y + * }) + * ``` + */ transaction(fn: (api: ITransactionAPI) => void): void + + /** + * Creates a scrub, which is just like a transaction, except you + * can run it multiple times without creating extra undo levels. + * + * Usage: + * ```ts + * const scrub = studio.scrub() + * scrub.capture(({set}) => { + * set(obj.props.x, 10) // set the value of obj.props.x to 10 + * }) + * + * // half a second later... + * scrub.capture(({set}) => { + * set(obj.props.y, 11) // set the value of obj.props.y to 11 + * // note that since we're not setting obj.props.x, its value reverts back to its old value (ie. not 10) + * }) + * + * // then either: + * scrub.commit() // commits the scrub and creates a single undo level + * // or: + * scrub.reset() // clear all the ops in the scrub so we can run scrub.capture() again + * // or: + * scrub.discard() // clears the ops and destroys it (ie. can't call scrub.capture() anymore) + * ``` + */ scrub(): IScrub + + /** + * Creates a debounced scrub, which is just like a normal scrub, but + * automatically runs scrub.commit() after `threshhold` milliseconds have + * passed after the last `scrub.capture`. + * + * @param threshhold How long to wait before committing the scrub + * + * Usage: + * ```ts + * // Will create a new undo-level after 2 seconds have passed + * // since the last scrub.capture() + * const scrub = studio.debouncedScrub(2000) + * + * // capture some ops + * scrub.capture(...) + * // wait one second + * await delay(1000) + * // capture more ops but no new undo level is made, + * // because the last scrub.capture() was called less than 2 seconds ago + * scrub.capture(...) + * + * // wait another seonc and half + * await delay(1500) + * // still no new undo level, because less than 2 seconds have passed + * // since the last capture + * scrub.capture(...) + * + * // wait 3 seconds + * await delay(3000) // at this point, one undo level is created. + * + * // this call to capture will start a new undo level + * scrub.capture(...) + * ``` + */ debouncedScrub(threshhold: number): Pick /** @@ -163,10 +283,11 @@ export interface IStudio { extension: IExtension, ): void - getPanesOfType( - paneClass: PaneClass, - ): Array> - + /** + * Creates a new pane + * + * @param paneClass The class name of the pane (provided by an extension) + */ createPane( paneClass: PaneClass, ): PaneInstance @@ -299,12 +420,6 @@ export default class TheatreStudio implements IStudio { return {capture} } - getPanesOfType( - paneClass: PaneClass, - ): PaneInstance[] { - return getStudio().paneManager.getPanesOfType(paneClass) - } - createPane( paneClass: PaneClass, ): PaneInstance { diff --git a/theatre/studio/src/store/types/ephemeral.ts b/theatre/studio/src/store/types/ephemeral.ts index 1e59237..5589c28 100644 --- a/theatre/studio/src/store/types/ephemeral.ts +++ b/theatre/studio/src/store/types/ephemeral.ts @@ -1,9 +1,5 @@ import type {ProjectState} from '@theatre/core/projects/store/storeTypes' -import type { - $IntentionalAny, - SerializableMap, - StrictRecord, -} from '@theatre/shared/utils/types' +import type {SerializableMap, StrictRecord} from '@theatre/shared/utils/types' import type { IExtension, PaneClassDefinition, @@ -35,7 +31,7 @@ export type StudioEphemeralState = { paneClasses: { [paneClassName in string]?: { extensionId: string - classDefinition: PaneClassDefinition<$IntentionalAny> + classDefinition: PaneClassDefinition } } } diff --git a/theatre/studio/src/utils/useRefAndState.ts b/theatre/studio/src/utils/useRefAndState.ts index c567c6a..a40f12b 100644 --- a/theatre/studio/src/utils/useRefAndState.ts +++ b/theatre/studio/src/utils/useRefAndState.ts @@ -4,8 +4,8 @@ import {useMemo, useState} from 'react' /** * Combines useRef() and useState(). * - * @example - * ```typescript + * Usage: + * ```ts * const [ref, val] = useRefAndState(null) * * useEffect(() => {