More docs and annotations
This commit is contained in:
parent
60974c4273
commit
631bcba724
15 changed files with 214 additions and 46 deletions
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,3 +5,7 @@ declare module '*.png' {
|
|||
declare module '*.glb' {
|
||||
export default string
|
||||
}
|
||||
|
||||
declare module '*.gltf' {
|
||||
export default string
|
||||
}
|
||||
|
|
|
@ -218,6 +218,10 @@ const ProxyManager: VFC<ProxyManagerProps> = ({orbitControlsRef}) => {
|
|||
})
|
||||
}, [viewportShading, renderMaterials, sceneProxy])
|
||||
|
||||
const scrub = useMemo(() => {
|
||||
return studio.debouncedScrub(1000)
|
||||
}, [selected, editableProxyOfSelected])
|
||||
|
||||
if (!sceneProxy) {
|
||||
return null
|
||||
}
|
||||
|
@ -235,7 +239,7 @@ const ProxyManager: VFC<ProxyManagerProps> = ({orbitControlsRef}) => {
|
|||
const sheetObject = editableProxyOfSelected.sheetObject
|
||||
const obj = editableProxyOfSelected.object
|
||||
|
||||
studio.transaction(({set}) => {
|
||||
scrub.capture(({set}) => {
|
||||
set(sheetObject.props, {
|
||||
position: {
|
||||
x: obj.position.x,
|
||||
|
|
|
@ -8,7 +8,7 @@ import useRefreshSnapshot from './useRefreshSnapshot'
|
|||
* Alternatively you can use
|
||||
* @link useRefreshSnapshot()
|
||||
*
|
||||
* @example
|
||||
* Usage
|
||||
* ```jsx
|
||||
* <Suspense fallback={null}>
|
||||
* <RefreshSnapshot />
|
||||
|
|
|
@ -4,8 +4,8 @@ import {useMemo, useState} from 'react'
|
|||
/**
|
||||
* Combines useRef() and useState().
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* Usage:
|
||||
* ```ts
|
||||
* const [ref, val] = useRefAndState<HTMLDivElement | null>(null)
|
||||
*
|
||||
* useEffect(() => {
|
||||
|
|
|
@ -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<O, P extends PointerType<O> | IDerivation<O>>(
|
||||
export function onChange<P extends PointerType<$IntentionalAny>>(
|
||||
pointer: P,
|
||||
callback: (value: O) => void,
|
||||
callback: (value: P extends PointerType<infer T> ? T : unknown) => void,
|
||||
): VoidFn {
|
||||
if (isPointer(pointer)) {
|
||||
const derivation = valueDerivation(pointer)
|
||||
|
|
|
@ -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"})
|
||||
|
|
|
@ -26,7 +26,7 @@ export interface ISheetObject<Props extends IShorthandCompoundProps = {}> {
|
|||
/**
|
||||
* 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<Props extends IShorthandCompoundProps = {}> {
|
|||
*
|
||||
* @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<Props extends IShorthandCompoundProps = {}> {
|
|||
* overrides it in the UI with a static or animated value.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* Usage:
|
||||
* ```ts
|
||||
* const obj = sheet.object("obj", {position: {x: 0, y: 0}})
|
||||
*
|
||||
|
|
|
@ -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", {})
|
||||
|
|
|
@ -64,12 +64,6 @@ export default class PaneManager {
|
|||
return this._getAllPanes()
|
||||
}
|
||||
|
||||
getPanesOfType<PaneClass extends string>(
|
||||
paneClass: PaneClass,
|
||||
): PaneInstance<PaneClass>[] {
|
||||
return []
|
||||
}
|
||||
|
||||
createPane<PaneClass extends string>(
|
||||
paneClass: PaneClass,
|
||||
): PaneInstance<PaneClass> {
|
||||
|
|
|
@ -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<T>(pointer: Pointer<T>, 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
|
||||
}
|
||||
|
||||
|
|
|
@ -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<V>(pointer: Pointer<V>, 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<V>(pointer: Pointer<V>): 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<PaneClassDefinition<$FixMe>>
|
||||
panes?: Array<PaneClassDefinition>
|
||||
}
|
||||
|
||||
export type PaneInstance<ClassName extends string> = {
|
||||
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<IScrub, 'capture'>
|
||||
|
||||
/**
|
||||
|
@ -163,10 +283,11 @@ export interface IStudio {
|
|||
extension: IExtension,
|
||||
): void
|
||||
|
||||
getPanesOfType<PaneClass extends string>(
|
||||
paneClass: PaneClass,
|
||||
): Array<PaneInstance<PaneClass>>
|
||||
|
||||
/**
|
||||
* Creates a new pane
|
||||
*
|
||||
* @param paneClass The class name of the pane (provided by an extension)
|
||||
*/
|
||||
createPane<PaneClass extends string>(
|
||||
paneClass: PaneClass,
|
||||
): PaneInstance<PaneClass>
|
||||
|
@ -299,12 +420,6 @@ export default class TheatreStudio implements IStudio {
|
|||
return {capture}
|
||||
}
|
||||
|
||||
getPanesOfType<PaneClass extends string>(
|
||||
paneClass: PaneClass,
|
||||
): PaneInstance<PaneClass>[] {
|
||||
return getStudio().paneManager.getPanesOfType(paneClass)
|
||||
}
|
||||
|
||||
createPane<PaneClass extends string>(
|
||||
paneClass: PaneClass,
|
||||
): PaneInstance<PaneClass> {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ import {useMemo, useState} from 'react'
|
|||
/**
|
||||
* Combines useRef() and useState().
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* Usage:
|
||||
* ```ts
|
||||
* const [ref, val] = useRefAndState<HTMLDivElement | null>(null)
|
||||
*
|
||||
* useEffect(() => {
|
||||
|
|
Loading…
Reference in a new issue