More docs and annotations

This commit is contained in:
Aria Minaei 2021-09-18 21:43:29 +02:00
parent 60974c4273
commit 631bcba724
15 changed files with 214 additions and 46 deletions

View file

@ -14,7 +14,12 @@ require('esbuild')
{ {
entryPoints: [path.join(playgroundDir, 'src/index.tsx')], entryPoints: [path.join(playgroundDir, 'src/index.tsx')],
target: ['firefox88'], target: ['firefox88'],
loader: {'.png': 'file', '.glb': 'file', '.svg': 'dataurl'}, loader: {
'.png': 'file',
'.glb': 'file',
'.gltf': 'file',
'.svg': 'dataurl',
},
bundle: true, bundle: true,
sourcemap: true, sourcemap: true,
define: definedGlobals, define: definedGlobals,

View file

@ -3,6 +3,7 @@ import type {UseDragOpts} from './useDrag'
import useDrag from './useDrag' import useDrag from './useDrag'
import React, {useLayoutEffect, useMemo, useState} from 'react' import React, {useLayoutEffect, useMemo, useState} from 'react'
import type {IProject, ISheet} from '@theatre/core' import type {IProject, ISheet} from '@theatre/core'
import {onChange} from '@theatre/core'
import type {IScrub, IStudio} from '@theatre/studio' import type {IScrub, IStudio} from '@theatre/studio'
studio.initialize() studio.initialize()
@ -25,7 +26,7 @@ const Box: React.FC<{
const [pos, setPos] = useState<{x: number; y: number}>({x: 0, y: 0}) const [pos, setPos] = useState<{x: number; y: number}>({x: 0, y: 0})
useLayoutEffect(() => { useLayoutEffect(() => {
const unsubscribeFromChanges = obj.onValuesChange((newValues) => { const unsubscribeFromChanges = onChange(obj.props, (newValues) => {
setPos(newValues) setPos(newValues)
}) })
return unsubscribeFromChanges return unsubscribeFromChanges

View file

@ -5,3 +5,7 @@ declare module '*.png' {
declare module '*.glb' { declare module '*.glb' {
export default string export default string
} }
declare module '*.gltf' {
export default string
}

View file

@ -218,6 +218,10 @@ const ProxyManager: VFC<ProxyManagerProps> = ({orbitControlsRef}) => {
}) })
}, [viewportShading, renderMaterials, sceneProxy]) }, [viewportShading, renderMaterials, sceneProxy])
const scrub = useMemo(() => {
return studio.debouncedScrub(1000)
}, [selected, editableProxyOfSelected])
if (!sceneProxy) { if (!sceneProxy) {
return null return null
} }
@ -235,7 +239,7 @@ const ProxyManager: VFC<ProxyManagerProps> = ({orbitControlsRef}) => {
const sheetObject = editableProxyOfSelected.sheetObject const sheetObject = editableProxyOfSelected.sheetObject
const obj = editableProxyOfSelected.object const obj = editableProxyOfSelected.object
studio.transaction(({set}) => { scrub.capture(({set}) => {
set(sheetObject.props, { set(sheetObject.props, {
position: { position: {
x: obj.position.x, x: obj.position.x,

View file

@ -8,7 +8,7 @@ import useRefreshSnapshot from './useRefreshSnapshot'
* Alternatively you can use * Alternatively you can use
* @link useRefreshSnapshot() * @link useRefreshSnapshot()
* *
* @example * Usage
* ```jsx * ```jsx
* <Suspense fallback={null}> * <Suspense fallback={null}>
* <RefreshSnapshot /> * <RefreshSnapshot />

View file

@ -4,8 +4,8 @@ import {useMemo, useState} from 'react'
/** /**
* Combines useRef() and useState(). * Combines useRef() and useState().
* *
* @example * Usage:
* ```typescript * ```ts
* const [ref, val] = useRefAndState<HTMLDivElement | null>(null) * const [ref, val] = useRefAndState<HTMLDivElement | null>(null)
* *
* useEffect(() => { * useEffect(() => {

View file

@ -11,7 +11,7 @@ import {InvalidArgumentError} from '@theatre/shared/utils/errors'
import {validateName} from '@theatre/shared/utils/sanitizers' import {validateName} from '@theatre/shared/utils/sanitizers'
import userReadableTypeOfValue from '@theatre/shared/utils/userReadableTypeOfValue' import userReadableTypeOfValue from '@theatre/shared/utils/userReadableTypeOfValue'
import deepEqual from 'fast-deep-equal' 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 {isPointer} from '@theatre/dataverse'
import {isDerivation, valueDerivation} from '@theatre/dataverse' import {isDerivation, valueDerivation} from '@theatre/dataverse'
import type {$IntentionalAny, VoidFn} from '@theatre/shared/utils/types' 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 * @param callback The callback is called every time the value of pointerOrDerivation changes
* @returns An unsubscribe function * @returns An unsubscribe function
*/ */
export function onChange<O, P extends PointerType<O> | IDerivation<O>>( export function onChange<P extends PointerType<$IntentionalAny>>(
pointer: P, pointer: P,
callback: (value: O) => void, callback: (value: P extends PointerType<infer T> ? T : unknown) => void,
): VoidFn { ): VoidFn {
if (isPointer(pointer)) { if (isPointer(pointer)) {
const derivation = valueDerivation(pointer) const derivation = valueDerivation(pointer)

View file

@ -31,7 +31,7 @@ export interface ISequence {
* *
* @returns A promise that resolves when the playback is finished, or rejects if interruped * @returns A promise that resolves when the playback is finished, or rejects if interruped
* *
* @example * Usage:
* ```ts * ```ts
* // plays the sequence from the current position to sequence.length * // plays the sequence from the current position to sequence.length
* sheet.sequence.play() * sheet.sequence.play()
@ -93,7 +93,7 @@ export interface ISequence {
* *
* @returns A promise that resolves once the audio source is loaded and decoded * @returns A promise that resolves once the audio source is loaded and decoded
* *
* @example * Usage:
* ```ts * ```ts
* // Loads and decodes audio from the URL and then attaches it to the sequence * // Loads and decodes audio from the URL and then attaches it to the sequence
* await sheet.sequence.attachAudio({source: "https://localhost/audio.ogg"}) * await sheet.sequence.attachAudio({source: "https://localhost/audio.ogg"})

View file

@ -26,7 +26,7 @@ export interface ISheetObject<Props extends IShorthandCompoundProps = {}> {
/** /**
* The current values of the props. * The current values of the props.
* *
* @example * Usage:
* ```ts * ```ts
* const obj = sheet.object("obj", {x: 0}) * const obj = sheet.object("obj", {x: 0})
* console.log(obj.value.x) // prints 0 or the current numeric value * 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 * @returns an Unsubscribe function
* *
* @example * Usage:
* ```ts * ```ts
* const obj = sheet.object("Box", {position: {x: 0, y: 0}}) * const obj = sheet.object("Box", {position: {x: 0, y: 0}})
* const div = document.getElementById("box") * 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. * overrides it in the UI with a static or animated value.
* *
* *
* @example * Usage:
* ```ts * ```ts
* const obj = sheet.object("obj", {position: {x: 0, y: 0}}) * const obj = sheet.object("obj", {position: {x: 0, y: 0}})
* *

View file

@ -45,7 +45,7 @@ export interface ISheet {
* *
* @returns An Object * @returns An Object
* *
* @example * Usage:
* ```ts * ```ts
* // Create an object named "a unique key" with no props * // Create an object named "a unique key" with no props
* const obj = sheet.object("a unique key", {}) * const obj = sheet.object("a unique key", {})

View file

@ -64,12 +64,6 @@ export default class PaneManager {
return this._getAllPanes() return this._getAllPanes()
} }
getPanesOfType<PaneClass extends string>(
paneClass: PaneClass,
): PaneInstance<PaneClass>[] {
return []
}
createPane<PaneClass extends string>( createPane<PaneClass extends string>(
paneClass: PaneClass, paneClass: PaneClass,
): PaneInstance<PaneClass> { ): PaneInstance<PaneClass> {

View file

@ -23,14 +23,63 @@ type State =
let lastScrubIdAsNumber = 0 let lastScrubIdAsNumber = 0
/**
* The scrub API
*/
export interface IScrubApi { 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 set<T>(pointer: Pointer<T>, value: T): void
} }
export interface IScrub { export interface IScrub {
/**
* Clears all the ops in the scrub, but keeps the scrub open so you can call
* `scrub.capture()` again.
*/
reset(): void reset(): void
/**
* Commits the scrub and creates a single undo level.
*/
commit(): void 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 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 discard(): void
} }

View file

@ -3,7 +3,7 @@ import studioTicker from '@theatre/studio/studioTicker'
import type {IDerivation, Pointer} from '@theatre/dataverse' import type {IDerivation, Pointer} from '@theatre/dataverse'
import {prism} from '@theatre/dataverse' import {prism} from '@theatre/dataverse'
import SimpleCache from '@theatre/shared/utils/SimpleCache' 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 {IScrub} from '@theatre/studio/Scrub'
import type {Studio} from '@theatre/studio/Studio' import type {Studio} from '@theatre/studio/Studio'
import { import {
@ -14,22 +14,67 @@ import {getOutlineSelection} from './selectors'
import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
import getStudio from './getStudio' import getStudio from './getStudio'
import type React from 'react' import type React from 'react'
import type {PropTypeConfig_Compound} from '@theatre/core/propTypes'
import {debounce} from 'lodash-es' import {debounce} from 'lodash-es'
import type Sheet from '@theatre/core/sheets/Sheet' import type Sheet from '@theatre/core/sheets/Sheet'
export interface ITransactionAPI { 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 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 unset<V>(pointer: Pointer<V>): void
} }
/** /**
* *
*/ */
export interface PaneClassDefinition< export interface PaneClassDefinition {
DataType extends PropTypeConfig_Compound<{}>, /**
> { * Each pane has a `class`, which is a string.
*/
class: 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<{ component: React.ComponentType<{
/**
* The unique identifier of the pane
*/
paneId: string paneId: string
}> }>
} }
@ -56,13 +101,13 @@ export interface IExtension {
/** /**
* Introduces new pane types. * Introduces new pane types.
*/ */
panes?: Array<PaneClassDefinition<$FixMe>> panes?: Array<PaneClassDefinition>
} }
export type PaneInstance<ClassName extends string> = { export type PaneInstance<ClassName extends string> = {
extensionId: string extensionId: string
instanceId: string instanceId: string
definition: PaneClassDefinition<$FixMe> definition: PaneClassDefinition
} }
/** /**
* This is the public api of Theatre's studio. It is exposed through: * This is the public api of Theatre's studio. It is exposed through:
@ -119,8 +164,83 @@ export interface IStudio {
usePersistentStorage?: boolean usePersistentStorage?: boolean
}): void }): 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 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 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'> debouncedScrub(threshhold: number): Pick<IScrub, 'capture'>
/** /**
@ -163,10 +283,11 @@ export interface IStudio {
extension: IExtension, extension: IExtension,
): void ): void
getPanesOfType<PaneClass extends string>( /**
paneClass: PaneClass, * Creates a new pane
): Array<PaneInstance<PaneClass>> *
* @param paneClass The class name of the pane (provided by an extension)
*/
createPane<PaneClass extends string>( createPane<PaneClass extends string>(
paneClass: PaneClass, paneClass: PaneClass,
): PaneInstance<PaneClass> ): PaneInstance<PaneClass>
@ -299,12 +420,6 @@ export default class TheatreStudio implements IStudio {
return {capture} return {capture}
} }
getPanesOfType<PaneClass extends string>(
paneClass: PaneClass,
): PaneInstance<PaneClass>[] {
return getStudio().paneManager.getPanesOfType(paneClass)
}
createPane<PaneClass extends string>( createPane<PaneClass extends string>(
paneClass: PaneClass, paneClass: PaneClass,
): PaneInstance<PaneClass> { ): PaneInstance<PaneClass> {

View file

@ -1,9 +1,5 @@
import type {ProjectState} from '@theatre/core/projects/store/storeTypes' import type {ProjectState} from '@theatre/core/projects/store/storeTypes'
import type { import type {SerializableMap, StrictRecord} from '@theatre/shared/utils/types'
$IntentionalAny,
SerializableMap,
StrictRecord,
} from '@theatre/shared/utils/types'
import type { import type {
IExtension, IExtension,
PaneClassDefinition, PaneClassDefinition,
@ -35,7 +31,7 @@ export type StudioEphemeralState = {
paneClasses: { paneClasses: {
[paneClassName in string]?: { [paneClassName in string]?: {
extensionId: string extensionId: string
classDefinition: PaneClassDefinition<$IntentionalAny> classDefinition: PaneClassDefinition
} }
} }
} }

View file

@ -4,8 +4,8 @@ import {useMemo, useState} from 'react'
/** /**
* Combines useRef() and useState(). * Combines useRef() and useState().
* *
* @example * Usage:
* ```typescript * ```ts
* const [ref, val] = useRefAndState<HTMLDivElement | null>(null) * const [ref, val] = useRefAndState<HTMLDivElement | null>(null)
* *
* useEffect(() => { * useEffect(() => {