refactor: Add working Nominal types, clarify identifiers
* Use more Nominal types to help with internal code id usage consistency * Broke apart StudioHistoricState type Co-authored-by: Aria <aria.minaei@gmail.com>
This commit is contained in:
parent
9d9fc1680e
commit
1387ce62d2
58 changed files with 647 additions and 299 deletions
|
@ -25,6 +25,8 @@ enum ValueTypes {
|
||||||
export interface IdentityDerivationProvider {
|
export interface IdentityDerivationProvider {
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
|
* Future: We could consider using a `Symbol.for("dataverse/IdentityDerivationProvider")` as a key here, similar to
|
||||||
|
* how {@link Iterable} works for `of`.
|
||||||
*/
|
*/
|
||||||
readonly $$isIdentityDerivationProvider: true
|
readonly $$isIdentityDerivationProvider: true
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -33,6 +33,11 @@ const cachedSubPathPointersWeakMap = new WeakMap<
|
||||||
* A wrapper type for the type a `Pointer` points to.
|
* A wrapper type for the type a `Pointer` points to.
|
||||||
*/
|
*/
|
||||||
export type PointerType<O> = {
|
export type PointerType<O> = {
|
||||||
|
/**
|
||||||
|
* Only accessible via the type system.
|
||||||
|
* This is a helper for getting the underlying pointer type
|
||||||
|
* via the type space.
|
||||||
|
*/
|
||||||
$$__pointer_type: O
|
$$__pointer_type: O
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,10 +58,18 @@ export type PointerType<O> = {
|
||||||
*
|
*
|
||||||
* The current solution is to just avoid using `any` with pointer-related code (or type-test it well).
|
* The current solution is to just avoid using `any` with pointer-related code (or type-test it well).
|
||||||
* But if you enjoy solving typescript puzzles, consider fixing this :)
|
* But if you enjoy solving typescript puzzles, consider fixing this :)
|
||||||
*
|
* Potentially, [TypeScript variance annotations in 4.7+](https://devblogs.microsoft.com/typescript/announcing-typescript-4-7-beta/#optional-variance-annotations-for-type-parameters)
|
||||||
|
* might be able to help us.
|
||||||
*/
|
*/
|
||||||
export type Pointer<O> = PointerType<O> &
|
export type Pointer<O> = PointerType<O> &
|
||||||
(O extends UnindexableTypesForPointer
|
// `Exclude<O, undefined>` will remove `undefined` from the first type
|
||||||
|
// `undefined extends O ? undefined : never` will give us `undefined` if `O` is `... | undefined`
|
||||||
|
PointerInner<Exclude<O, undefined>, undefined extends O ? undefined : never>
|
||||||
|
|
||||||
|
// By separating the `O` (non-undefined) from the `undefined` or `never`, we
|
||||||
|
// can properly use `O extends ...` to determine the kind of potential value
|
||||||
|
// without actually discarding optionality information.
|
||||||
|
type PointerInner<O, Optional> = O extends UnindexableTypesForPointer
|
||||||
? UnindexablePointer
|
? UnindexablePointer
|
||||||
: unknown extends O
|
: unknown extends O
|
||||||
? UnindexablePointer
|
? UnindexablePointer
|
||||||
|
@ -64,10 +77,9 @@ export type Pointer<O> = PointerType<O> &
|
||||||
? Pointer<T>[]
|
? Pointer<T>[]
|
||||||
: O extends {}
|
: O extends {}
|
||||||
? {
|
? {
|
||||||
[K in keyof O]-?: Pointer<O[K]>
|
[K in keyof O]-?: Pointer<O[K] | Optional>
|
||||||
} /*&
|
}
|
||||||
{[K in string | number]: Pointer<K extends keyof O ? O[K] : undefined>}*/
|
: UnindexablePointer
|
||||||
: UnindexablePointer)
|
|
||||||
|
|
||||||
const pointerMetaSymbol = Symbol('pointerMeta')
|
const pointerMetaSymbol = Symbol('pointerMeta')
|
||||||
|
|
||||||
|
|
106
packages/dataverse/src/pointer.typeTest.ts
Normal file
106
packages/dataverse/src/pointer.typeTest.ts
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import type {Pointer, UnindexablePointer} from './pointer'
|
||||||
|
import type {$IntentionalAny} from './types'
|
||||||
|
|
||||||
|
const nominal = Symbol()
|
||||||
|
type Nominal<Name> = string & {[nominal]: Name}
|
||||||
|
|
||||||
|
type Key = Nominal<'key'>
|
||||||
|
type Id = Nominal<'id'>
|
||||||
|
|
||||||
|
type IdObject = {
|
||||||
|
inner: true
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyObject = {
|
||||||
|
inner: {
|
||||||
|
byIds: Partial<Record<Id, IdObject>>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type NestedNominalThing = {
|
||||||
|
optional?: true
|
||||||
|
byKeys: Partial<Record<Key, KeyObject>>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TypeError<M> {}
|
||||||
|
|
||||||
|
type Debug<T extends 0> = T
|
||||||
|
type IsTrue<T extends true> = T
|
||||||
|
type IsFalse<F extends false> = F
|
||||||
|
type IsExtends<F, R extends F> = F
|
||||||
|
type IsExactly<F, R extends F> = F extends R
|
||||||
|
? true
|
||||||
|
: TypeError<[F, 'does not extend', R]>
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
const p = todo<Pointer<NestedNominalThing>>()
|
||||||
|
const key1 = todo<Key>()
|
||||||
|
const id1 = todo<Id>()
|
||||||
|
|
||||||
|
type A = UnindexablePointer[typeof key1]
|
||||||
|
type BaseChecks = [
|
||||||
|
IsExtends<any, any>,
|
||||||
|
IsExtends<undefined | 1, undefined>,
|
||||||
|
IsExtends<string, Key>,
|
||||||
|
IsTrue<IsExactly<UnindexablePointer[typeof key1], Pointer<undefined>>>,
|
||||||
|
IsTrue<
|
||||||
|
IsExactly<Pointer<undefined | true>['...']['...'], Pointer<undefined>>
|
||||||
|
>,
|
||||||
|
IsTrue<
|
||||||
|
IsExactly<
|
||||||
|
Pointer<Record<Key, true | undefined>>[Key],
|
||||||
|
Pointer<true | undefined>
|
||||||
|
>
|
||||||
|
>,
|
||||||
|
IsTrue<IsExactly<Pointer<undefined>[Key], Pointer<undefined>>>,
|
||||||
|
// Debug<Pointer<undefined | Record<string, true>>[Key]>,
|
||||||
|
IsTrue<IsExactly<Pointer<Record<string, true>>[string], Pointer<true>>>,
|
||||||
|
IsTrue<
|
||||||
|
IsExactly<
|
||||||
|
Pointer<undefined | Record<string, true>>[string],
|
||||||
|
Pointer<true | undefined>
|
||||||
|
>
|
||||||
|
>,
|
||||||
|
IsTrue<
|
||||||
|
IsExactly<
|
||||||
|
Pointer<undefined | Record<Key, true>>[Key],
|
||||||
|
Pointer<true | undefined>
|
||||||
|
>
|
||||||
|
>,
|
||||||
|
// Debug<Pointer<undefined | true>['...']['...']>,
|
||||||
|
// IsFalse<any extends Pointer<undefined | true> ? true : false>,
|
||||||
|
// what extends what
|
||||||
|
IsTrue<1 & undefined extends undefined ? true : false>,
|
||||||
|
IsFalse<1 | undefined extends undefined ? true : false>,
|
||||||
|
]
|
||||||
|
|
||||||
|
t<Pointer<undefined | true>>() //
|
||||||
|
.isExactly(p.optional).ok
|
||||||
|
|
||||||
|
t<Pointer<undefined | KeyObject>>() //
|
||||||
|
.isExactly(p.byKeys[key1]).ok
|
||||||
|
|
||||||
|
t<Pointer<undefined | KeyObject['inner']>>() //
|
||||||
|
.isExactly(p.byKeys[key1].inner).ok
|
||||||
|
|
||||||
|
t<Pointer<undefined | IdObject>>() //
|
||||||
|
.isExactly(p.byKeys[key1].inner.byIds[id1]).ok
|
||||||
|
|
||||||
|
p.byKeys[key1]
|
||||||
|
}
|
||||||
|
|
||||||
|
function todo<T>(hmm?: TemplateStringsArray): T {
|
||||||
|
return null as $IntentionalAny
|
||||||
|
}
|
||||||
|
function t<T>(): {
|
||||||
|
isExactly<R extends T>(
|
||||||
|
hmm: R,
|
||||||
|
): T extends R
|
||||||
|
? // any extends R
|
||||||
|
// ? TypeError<[R, 'is any']>
|
||||||
|
// :
|
||||||
|
{ok: true}
|
||||||
|
: TypeError<[T, 'does not extend', R]>
|
||||||
|
} {
|
||||||
|
return {isExactly: (hmm) => hmm as $IntentionalAny}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ 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'
|
||||||
import coreTicker from './coreTicker'
|
import coreTicker from './coreTicker'
|
||||||
|
import type {ProjectId} from '@theatre/shared/utils/ids'
|
||||||
export {types}
|
export {types}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,7 +46,7 @@ export {types}
|
||||||
*/
|
*/
|
||||||
export function getProject(id: string, config: IProjectConfig = {}): IProject {
|
export function getProject(id: string, config: IProjectConfig = {}): IProject {
|
||||||
const {...restOfConfig} = config
|
const {...restOfConfig} = config
|
||||||
const existingProject = projectsSingleton.get(id)
|
const existingProject = projectsSingleton.get(id as ProjectId)
|
||||||
if (existingProject) {
|
if (existingProject) {
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
if (!deepEqual(config, existingProject.config)) {
|
if (!deepEqual(config, existingProject.config)) {
|
||||||
|
@ -67,9 +68,9 @@ export function getProject(id: string, config: IProjectConfig = {}): IProject {
|
||||||
|
|
||||||
if (config.state) {
|
if (config.state) {
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
shallowValidateOnDiskState(id, config.state)
|
shallowValidateOnDiskState(id as ProjectId, config.state)
|
||||||
} else {
|
} else {
|
||||||
deepValidateOnDiskState(id, config.state)
|
deepValidateOnDiskState(id as ProjectId, config.state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +81,7 @@ export function getProject(id: string, config: IProjectConfig = {}): IProject {
|
||||||
* Lightweight validator that only makes sure the state's definitionVersion is correct.
|
* Lightweight validator that only makes sure the state's definitionVersion is correct.
|
||||||
* Does not do a thorough validation of the state.
|
* Does not do a thorough validation of the state.
|
||||||
*/
|
*/
|
||||||
const shallowValidateOnDiskState = (projectId: string, s: OnDiskState) => {
|
const shallowValidateOnDiskState = (projectId: ProjectId, s: OnDiskState) => {
|
||||||
if (
|
if (
|
||||||
Array.isArray(s) ||
|
Array.isArray(s) ||
|
||||||
s == null ||
|
s == null ||
|
||||||
|
@ -94,7 +95,7 @@ const shallowValidateOnDiskState = (projectId: string, s: OnDiskState) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const deepValidateOnDiskState = (projectId: string, s: OnDiskState) => {
|
const deepValidateOnDiskState = (projectId: ProjectId, s: OnDiskState) => {
|
||||||
shallowValidateOnDiskState(projectId, s)
|
shallowValidateOnDiskState(projectId, s)
|
||||||
// @TODO do a deep validation here
|
// @TODO do a deep validation here
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,12 @@ import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
||||||
import type {ISheetObject} from '@theatre/core/sheetObjects/TheatreSheetObject'
|
import type {ISheetObject} from '@theatre/core/sheetObjects/TheatreSheetObject'
|
||||||
import type Sheet from '@theatre/core/sheets/Sheet'
|
import type Sheet from '@theatre/core/sheets/Sheet'
|
||||||
import type {ISheet} from '@theatre/core/sheets/TheatreSheet'
|
import type {ISheet} from '@theatre/core/sheets/TheatreSheet'
|
||||||
|
import type {UnknownShorthandCompoundProps} from './propTypes/internals'
|
||||||
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
||||||
|
|
||||||
const publicAPIToPrivateAPIMap = new WeakMap()
|
const publicAPIToPrivateAPIMap = new WeakMap()
|
||||||
|
|
||||||
export function privateAPI<
|
export function privateAPI<P extends {type: string}>(
|
||||||
P extends IProject | ISheet | ISheetObject<$IntentionalAny> | ISequence,
|
|
||||||
>(
|
|
||||||
pub: P,
|
pub: P,
|
||||||
): P extends IProject
|
): P extends IProject
|
||||||
? Project
|
? Project
|
||||||
|
@ -29,8 +28,8 @@ export function privateAPI<
|
||||||
export function setPrivateAPI(pub: IProject, priv: Project): void
|
export function setPrivateAPI(pub: IProject, priv: Project): void
|
||||||
export function setPrivateAPI(pub: ISheet, priv: Sheet): void
|
export function setPrivateAPI(pub: ISheet, priv: Sheet): void
|
||||||
export function setPrivateAPI(pub: ISequence, priv: Sequence): void
|
export function setPrivateAPI(pub: ISequence, priv: Sequence): void
|
||||||
export function setPrivateAPI(
|
export function setPrivateAPI<Props extends UnknownShorthandCompoundProps>(
|
||||||
pub: ISheetObject<$IntentionalAny>,
|
pub: ISheetObject<Props>,
|
||||||
priv: SheetObject,
|
priv: SheetObject,
|
||||||
): void
|
): void
|
||||||
export function setPrivateAPI(pub: {}, priv: {}): void {
|
export function setPrivateAPI(pub: {}, priv: {}): void {
|
||||||
|
|
|
@ -13,6 +13,11 @@ import type {ProjectState} from './store/storeTypes'
|
||||||
import type {Deferred} from '@theatre/shared/utils/defer'
|
import type {Deferred} from '@theatre/shared/utils/defer'
|
||||||
import {defer} from '@theatre/shared/utils/defer'
|
import {defer} from '@theatre/shared/utils/defer'
|
||||||
import globals from '@theatre/shared/globals'
|
import globals from '@theatre/shared/globals'
|
||||||
|
import type {
|
||||||
|
ProjectId,
|
||||||
|
SheetId,
|
||||||
|
SheetInstanceId,
|
||||||
|
} from '@theatre/shared/utils/ids'
|
||||||
|
|
||||||
export type Conf = Partial<{
|
export type Conf = Partial<{
|
||||||
state: OnDiskState
|
state: OnDiskState
|
||||||
|
@ -44,7 +49,7 @@ export default class Project {
|
||||||
type: 'Theatre_Project' = 'Theatre_Project'
|
type: 'Theatre_Project' = 'Theatre_Project'
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
id: string,
|
id: ProjectId,
|
||||||
readonly config: Conf = {},
|
readonly config: Conf = {},
|
||||||
readonly publicApi: TheatreProject,
|
readonly publicApi: TheatreProject,
|
||||||
) {
|
) {
|
||||||
|
@ -150,7 +155,10 @@ export default class Project {
|
||||||
return this._readyDeferred.status === 'resolved'
|
return this._readyDeferred.status === 'resolved'
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrCreateSheet(sheetId: string, instanceId: string = 'default'): Sheet {
|
getOrCreateSheet(
|
||||||
|
sheetId: SheetId,
|
||||||
|
instanceId: SheetInstanceId = 'default' as SheetInstanceId,
|
||||||
|
): Sheet {
|
||||||
let template = this._sheetTemplates.getState()[sheetId]
|
let template = this._sheetTemplates.getState()[sheetId]
|
||||||
|
|
||||||
if (!template) {
|
if (!template) {
|
||||||
|
|
|
@ -2,6 +2,11 @@ import {privateAPI, setPrivateAPI} from '@theatre/core/privateAPIs'
|
||||||
import Project from '@theatre/core/projects/Project'
|
import Project from '@theatre/core/projects/Project'
|
||||||
import type {ISheet} from '@theatre/core/sheets/TheatreSheet'
|
import type {ISheet} from '@theatre/core/sheets/TheatreSheet'
|
||||||
import type {ProjectAddress} from '@theatre/shared/utils/addresses'
|
import type {ProjectAddress} from '@theatre/shared/utils/addresses'
|
||||||
|
import type {
|
||||||
|
ProjectId,
|
||||||
|
SheetId,
|
||||||
|
SheetInstanceId,
|
||||||
|
} from '@theatre/shared/utils/ids'
|
||||||
import {validateInstanceId} from '@theatre/shared/utils/sanitizers'
|
import {validateInstanceId} from '@theatre/shared/utils/sanitizers'
|
||||||
import {validateAndSanitiseSlashedPathOrThrow} from '@theatre/shared/utils/slashedPaths'
|
import {validateAndSanitiseSlashedPathOrThrow} from '@theatre/shared/utils/slashedPaths'
|
||||||
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
||||||
|
@ -58,7 +63,7 @@ export default class TheatreProject implements IProject {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
constructor(id: string, config: IProjectConfig = {}) {
|
constructor(id: string, config: IProjectConfig = {}) {
|
||||||
setPrivateAPI(this, new Project(id, config, this))
|
setPrivateAPI(this, new Project(id as ProjectId, config, this))
|
||||||
}
|
}
|
||||||
|
|
||||||
get ready(): Promise<void> {
|
get ready(): Promise<void> {
|
||||||
|
@ -87,7 +92,9 @@ export default class TheatreProject implements IProject {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return privateAPI(this).getOrCreateSheet(sanitizedPath, instanceId)
|
return privateAPI(this).getOrCreateSheet(
|
||||||
.publicApi
|
sanitizedPath as SheetId,
|
||||||
|
instanceId as SheetInstanceId,
|
||||||
|
).publicApi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import {Atom} from '@theatre/dataverse'
|
import {Atom} from '@theatre/dataverse'
|
||||||
|
import type {ProjectId} from '@theatre/shared/utils/ids'
|
||||||
import type Project from './Project'
|
import type Project from './Project'
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
projects: Record<string, Project>
|
projects: Record<ProjectId, Project>
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProjectsSingleton {
|
class ProjectsSingleton {
|
||||||
|
@ -12,15 +13,15 @@ class ProjectsSingleton {
|
||||||
/**
|
/**
|
||||||
* We're trusting here that each project id is unique
|
* We're trusting here that each project id is unique
|
||||||
*/
|
*/
|
||||||
add(id: string, project: Project) {
|
add(id: ProjectId, project: Project) {
|
||||||
this.atom.reduceState(['projects', id], () => project)
|
this.atom.reduceState(['projects', id], () => project)
|
||||||
}
|
}
|
||||||
|
|
||||||
get(id: string): Project | undefined {
|
get(id: ProjectId): Project | undefined {
|
||||||
return this.atom.getState().projects[id]
|
return this.atom.getState().projects[id]
|
||||||
}
|
}
|
||||||
|
|
||||||
has(id: string) {
|
has(id: ProjectId) {
|
||||||
return !!this.get(id)
|
return !!this.get(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type {SheetId} from '@theatre/shared/utils/ids'
|
||||||
import type {StrictRecord} from '@theatre/shared/utils/types'
|
import type {StrictRecord} from '@theatre/shared/utils/types'
|
||||||
import type {SheetState_Historic} from './types/SheetState_Historic'
|
import type {SheetState_Historic} from './types/SheetState_Historic'
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ export interface ProjectEphemeralState {
|
||||||
* at {@link StudioHistoricState.coreByProject}
|
* at {@link StudioHistoricState.coreByProject}
|
||||||
*/
|
*/
|
||||||
export interface ProjectState_Historic {
|
export interface ProjectState_Historic {
|
||||||
sheetsById: StrictRecord<string, SheetState_Historic>
|
sheetsById: StrictRecord<SheetId, SheetState_Historic>
|
||||||
/**
|
/**
|
||||||
* The last 50 revision IDs this state is based on, starting with the most recent one.
|
* The last 50 revision IDs this state is based on, starting with the most recent one.
|
||||||
* The most recent one is the revision ID of this state
|
* The most recent one is the revision ID of this state
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
import type {KeyframeId, SequenceTrackId} from '@theatre/shared/utils/ids'
|
import type {
|
||||||
import type {SerializableMap, StrictRecord} from '@theatre/shared/utils/types'
|
KeyframeId,
|
||||||
|
ObjectAddressKey,
|
||||||
|
SequenceTrackId,
|
||||||
|
} from '@theatre/shared/utils/ids'
|
||||||
|
import type {
|
||||||
|
SerializableMap,
|
||||||
|
SerializableValue,
|
||||||
|
StrictRecord,
|
||||||
|
} from '@theatre/shared/utils/types'
|
||||||
|
|
||||||
export interface SheetState_Historic {
|
export interface SheetState_Historic {
|
||||||
/**
|
/**
|
||||||
|
@ -10,14 +18,13 @@ export interface SheetState_Historic {
|
||||||
* of another state, it will be able to inherit the overrides from ancestor states.
|
* of another state, it will be able to inherit the overrides from ancestor states.
|
||||||
*/
|
*/
|
||||||
staticOverrides: {
|
staticOverrides: {
|
||||||
byObject: StrictRecord<string, SerializableMap>
|
byObject: StrictRecord<ObjectAddressKey, SerializableMap>
|
||||||
}
|
}
|
||||||
sequence?: Sequence
|
sequence?: HistoricPositionalSequence
|
||||||
}
|
}
|
||||||
|
|
||||||
type Sequence = PositionalSequence
|
// Question: What is this? The timeline position of a sequence?
|
||||||
|
export type HistoricPositionalSequence = {
|
||||||
type PositionalSequence = {
|
|
||||||
type: 'PositionalSequence'
|
type: 'PositionalSequence'
|
||||||
length: number
|
length: number
|
||||||
/**
|
/**
|
||||||
|
@ -27,7 +34,7 @@ type PositionalSequence = {
|
||||||
subUnitsPerUnit: number
|
subUnitsPerUnit: number
|
||||||
|
|
||||||
tracksByObject: StrictRecord<
|
tracksByObject: StrictRecord<
|
||||||
string,
|
ObjectAddressKey,
|
||||||
{
|
{
|
||||||
trackIdByPropPath: StrictRecord<string, SequenceTrackId>
|
trackIdByPropPath: StrictRecord<string, SequenceTrackId>
|
||||||
trackData: StrictRecord<SequenceTrackId, TrackData>
|
trackData: StrictRecord<SequenceTrackId, TrackData>
|
||||||
|
@ -39,7 +46,10 @@ export type TrackData = BasicKeyframedTrack
|
||||||
|
|
||||||
export type Keyframe = {
|
export type Keyframe = {
|
||||||
id: KeyframeId
|
id: KeyframeId
|
||||||
value: unknown
|
/** The `value` is the raw value type such as `Rgba` or `number`. See {@link SerializableValue} */
|
||||||
|
// Future: is there another layer that we may need to be able to store older values on the
|
||||||
|
// case of a prop config change? As keyframes can technically have their propConfig changed.
|
||||||
|
value: SerializableValue
|
||||||
position: number
|
position: number
|
||||||
handles: [leftX: number, leftY: number, rightX: number, rightY: number]
|
handles: [leftX: number, leftY: number, rightX: number, rightY: number]
|
||||||
connectedRight: boolean
|
connectedRight: boolean
|
||||||
|
@ -47,5 +57,9 @@ export type Keyframe = {
|
||||||
|
|
||||||
export type BasicKeyframedTrack = {
|
export type BasicKeyframedTrack = {
|
||||||
type: 'BasicKeyframedTrack'
|
type: 'BasicKeyframedTrack'
|
||||||
|
/**
|
||||||
|
* {@link Keyframe} is not provided an explicit generic value `T`, because
|
||||||
|
* a single track can technically have multiple different types for each keyframe.
|
||||||
|
*/
|
||||||
keyframes: Keyframe[]
|
keyframes: Keyframe[]
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@ import {
|
||||||
} from '@theatre/shared/utils/color'
|
} from '@theatre/shared/utils/color'
|
||||||
import {clamp, mapValues} from 'lodash-es'
|
import {clamp, mapValues} from 'lodash-es'
|
||||||
import type {
|
import type {
|
||||||
IShorthandCompoundProps,
|
UnknownShorthandCompoundProps,
|
||||||
IValidCompoundProps,
|
UnknownValidCompoundProps,
|
||||||
ShorthandCompoundPropsToLonghandCompoundProps,
|
ShorthandCompoundPropsToLonghandCompoundProps,
|
||||||
} from './internals'
|
} from './internals'
|
||||||
import {propTypeSymbol, sanitizeCompoundProps} from './internals'
|
import {propTypeSymbol, sanitizeCompoundProps} from './internals'
|
||||||
|
@ -88,7 +88,7 @@ const validateCommonOpts = (fnCallSignature: string, opts?: CommonOpts) => {
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const compound = <Props extends IShorthandCompoundProps>(
|
export const compound = <Props extends UnknownShorthandCompoundProps>(
|
||||||
props: Props,
|
props: Props,
|
||||||
opts: CommonOpts = {},
|
opts: CommonOpts = {},
|
||||||
): PropTypeConfig_Compound<
|
): PropTypeConfig_Compound<
|
||||||
|
@ -101,7 +101,7 @@ export const compound = <Props extends IShorthandCompoundProps>(
|
||||||
ShorthandCompoundPropsToLonghandCompoundProps<Props>
|
ShorthandCompoundPropsToLonghandCompoundProps<Props>
|
||||||
> = {
|
> = {
|
||||||
type: 'compound',
|
type: 'compound',
|
||||||
props: sanitizedProps,
|
props: sanitizedProps as $IntentionalAny,
|
||||||
valueType: null as $IntentionalAny,
|
valueType: null as $IntentionalAny,
|
||||||
[propTypeSymbol]: 'TheatrePropType',
|
[propTypeSymbol]: 'TheatrePropType',
|
||||||
label: opts.label,
|
label: opts.label,
|
||||||
|
@ -690,7 +690,7 @@ export interface PropTypeConfig_StringLiteral<T extends string>
|
||||||
|
|
||||||
export interface PropTypeConfig_Rgba extends ISimplePropType<'rgba', Rgba> {}
|
export interface PropTypeConfig_Rgba extends ISimplePropType<'rgba', Rgba> {}
|
||||||
|
|
||||||
type DeepPartialCompound<Props extends IValidCompoundProps> = {
|
type DeepPartialCompound<Props extends UnknownValidCompoundProps> = {
|
||||||
[K in keyof Props]?: DeepPartial<Props[K]>
|
[K in keyof Props]?: DeepPartial<Props[K]>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -701,13 +701,14 @@ type DeepPartial<Conf extends PropTypeConfig> =
|
||||||
? DeepPartialCompound<T>
|
? DeepPartialCompound<T>
|
||||||
: never
|
: never
|
||||||
|
|
||||||
export interface PropTypeConfig_Compound<Props extends IValidCompoundProps>
|
export interface PropTypeConfig_Compound<
|
||||||
extends IBasePropType<
|
Props extends UnknownValidCompoundProps,
|
||||||
|
> extends IBasePropType<
|
||||||
'compound',
|
'compound',
|
||||||
{[K in keyof Props]: Props[K]['valueType']},
|
{[K in keyof Props]: Props[K]['valueType']},
|
||||||
DeepPartialCompound<Props>
|
DeepPartialCompound<Props>
|
||||||
> {
|
> {
|
||||||
props: Record<string, PropTypeConfig>
|
props: Record<keyof Props, PropTypeConfig>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PropTypeConfig_Enum extends IBasePropType<'enum', {}> {
|
export interface PropTypeConfig_Enum extends IBasePropType<'enum', {}> {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import * as t from './index'
|
||||||
|
|
||||||
export const propTypeSymbol = Symbol('TheatrePropType_Basic')
|
export const propTypeSymbol = Symbol('TheatrePropType_Basic')
|
||||||
|
|
||||||
export type IValidCompoundProps = {
|
export type UnknownValidCompoundProps = {
|
||||||
[K in string]: PropTypeConfig
|
[K in string]: PropTypeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,18 +27,19 @@ export type IValidCompoundProps = {
|
||||||
* which would allow us to differentiate between values at runtime
|
* which would allow us to differentiate between values at runtime
|
||||||
* (e.g. `val.type = "Rgba"` vs `val.type = "Compound"` etc)
|
* (e.g. `val.type = "Rgba"` vs `val.type = "Compound"` etc)
|
||||||
*/
|
*/
|
||||||
type IShorthandProp =
|
type UnknownShorthandProp =
|
||||||
| string
|
| string
|
||||||
| number
|
| number
|
||||||
| boolean
|
| boolean
|
||||||
| PropTypeConfig
|
| PropTypeConfig
|
||||||
| IShorthandCompoundProps
|
| UnknownShorthandCompoundProps
|
||||||
|
|
||||||
export type IShorthandCompoundProps = {
|
/** Given an object like this, we have enough info to predict the compound prop */
|
||||||
[K in string]: IShorthandProp
|
export type UnknownShorthandCompoundProps = {
|
||||||
|
[K in string]: UnknownShorthandProp
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ShorthandPropToLonghandProp<P extends IShorthandProp> =
|
export type ShorthandPropToLonghandProp<P extends UnknownShorthandProp> =
|
||||||
P extends string
|
P extends string
|
||||||
? PropTypeConfig_String
|
? PropTypeConfig_String
|
||||||
: P extends number
|
: P extends number
|
||||||
|
@ -47,12 +48,31 @@ export type ShorthandPropToLonghandProp<P extends IShorthandProp> =
|
||||||
? PropTypeConfig_Boolean
|
? PropTypeConfig_Boolean
|
||||||
: P extends PropTypeConfig
|
: P extends PropTypeConfig
|
||||||
? P
|
? P
|
||||||
: P extends IShorthandCompoundProps
|
: P extends UnknownShorthandCompoundProps
|
||||||
? PropTypeConfig_Compound<ShorthandCompoundPropsToLonghandCompoundProps<P>>
|
? PropTypeConfig_Compound<ShorthandCompoundPropsToLonghandCompoundProps<P>>
|
||||||
: never
|
: never
|
||||||
|
|
||||||
|
export type ShorthandCompoundPropsToInitialValue<
|
||||||
|
P extends UnknownShorthandCompoundProps,
|
||||||
|
> = LonghandCompoundPropsToInitialValue<
|
||||||
|
ShorthandCompoundPropsToLonghandCompoundProps<P>
|
||||||
|
>
|
||||||
|
|
||||||
|
type LonghandCompoundPropsToInitialValue<P extends UnknownValidCompoundProps> =
|
||||||
|
{
|
||||||
|
[K in keyof P]: P[K]['valueType']
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PropsValue<P> = P extends UnknownValidCompoundProps
|
||||||
|
? LonghandCompoundPropsToInitialValue<P>
|
||||||
|
: P extends UnknownShorthandCompoundProps
|
||||||
|
? LonghandCompoundPropsToInitialValue<
|
||||||
|
ShorthandCompoundPropsToLonghandCompoundProps<P>
|
||||||
|
>
|
||||||
|
: never
|
||||||
|
|
||||||
export type ShorthandCompoundPropsToLonghandCompoundProps<
|
export type ShorthandCompoundPropsToLonghandCompoundProps<
|
||||||
P extends IShorthandCompoundProps,
|
P extends UnknownShorthandCompoundProps,
|
||||||
> = {
|
> = {
|
||||||
[K in keyof P]: ShorthandPropToLonghandProp<P[K]>
|
[K in keyof P]: ShorthandPropToLonghandProp<P[K]>
|
||||||
}
|
}
|
||||||
|
@ -89,9 +109,9 @@ export function toLonghandProp(p: unknown): PropTypeConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sanitizeCompoundProps(
|
export function sanitizeCompoundProps(
|
||||||
props: IShorthandCompoundProps,
|
props: UnknownShorthandCompoundProps,
|
||||||
): IValidCompoundProps {
|
): UnknownValidCompoundProps {
|
||||||
const sanitizedProps: IValidCompoundProps = {}
|
const sanitizedProps: UnknownValidCompoundProps = {}
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
if (typeof props !== 'object' || !props) {
|
if (typeof props !== 'object' || !props) {
|
||||||
throw new InvalidArgumentError(
|
throw new InvalidArgumentError(
|
||||||
|
|
|
@ -6,11 +6,15 @@ import type {
|
||||||
import type {IDerivation, Pointer} from '@theatre/dataverse'
|
import type {IDerivation, Pointer} from '@theatre/dataverse'
|
||||||
import {ConstantDerivation, prism, val} from '@theatre/dataverse'
|
import {ConstantDerivation, prism, val} from '@theatre/dataverse'
|
||||||
import logger from '@theatre/shared/logger'
|
import logger from '@theatre/shared/logger'
|
||||||
|
import type {SerializableValue} from '@theatre/shared/utils/types'
|
||||||
import UnitBezier from 'timing-function/lib/UnitBezier'
|
import UnitBezier from 'timing-function/lib/UnitBezier'
|
||||||
|
|
||||||
|
/** `left` and `right` are not necessarily the same type. */
|
||||||
export type InterpolationTriple = {
|
export type InterpolationTriple = {
|
||||||
left: unknown
|
/** `left` and `right` are not necessarily the same type. */
|
||||||
right?: unknown
|
left: SerializableValue
|
||||||
|
/** `left` and `right` are not necessarily the same type. */
|
||||||
|
right?: SerializableValue
|
||||||
progression: number
|
progression: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,10 +79,10 @@ function _forKeyframedTrack(
|
||||||
|
|
||||||
const undefinedConstD = new ConstantDerivation(undefined)
|
const undefinedConstD = new ConstantDerivation(undefined)
|
||||||
|
|
||||||
const updateState = (
|
function updateState(
|
||||||
progressionD: IDerivation<number>,
|
progressionD: IDerivation<number>,
|
||||||
track: BasicKeyframedTrack,
|
track: BasicKeyframedTrack,
|
||||||
): IStartedState => {
|
): IStartedState {
|
||||||
const progression = progressionD.getValue()
|
const progression = progressionD.getValue()
|
||||||
if (track.keyframes.length === 0) {
|
if (track.keyframes.length === 0) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -4,18 +4,19 @@
|
||||||
import {setupTestSheet} from '@theatre/shared/testUtils'
|
import {setupTestSheet} from '@theatre/shared/testUtils'
|
||||||
import {encodePathToProp} from '@theatre/shared/utils/addresses'
|
import {encodePathToProp} from '@theatre/shared/utils/addresses'
|
||||||
import {asKeyframeId, asSequenceTrackId} from '@theatre/shared/utils/ids'
|
import {asKeyframeId, asSequenceTrackId} from '@theatre/shared/utils/ids'
|
||||||
|
import type {ObjectAddressKey, SequenceTrackId} from '@theatre/shared/utils/ids'
|
||||||
import {iterateOver, prism} from '@theatre/dataverse'
|
import {iterateOver, prism} from '@theatre/dataverse'
|
||||||
import type {SheetState_Historic} from '@theatre/core/projects/store/types/SheetState_Historic'
|
import type {SheetState_Historic} from '@theatre/core/projects/store/types/SheetState_Historic'
|
||||||
|
|
||||||
describe(`SheetObject`, () => {
|
describe(`SheetObject`, () => {
|
||||||
describe('static overrides', () => {
|
describe('static overrides', () => {
|
||||||
const setup = async (
|
const setup = async (
|
||||||
staticOverrides: SheetState_Historic['staticOverrides']['byObject'][string] = {},
|
staticOverrides: SheetState_Historic['staticOverrides']['byObject'][ObjectAddressKey] = {},
|
||||||
) => {
|
) => {
|
||||||
const {studio, objPublicAPI} = await setupTestSheet({
|
const {studio, objPublicAPI} = await setupTestSheet({
|
||||||
staticOverrides: {
|
staticOverrides: {
|
||||||
byObject: {
|
byObject: {
|
||||||
obj: staticOverrides,
|
['obj' as ObjectAddressKey]: staticOverrides,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -260,12 +261,12 @@ describe(`SheetObject`, () => {
|
||||||
length: 20,
|
length: 20,
|
||||||
subUnitsPerUnit: 30,
|
subUnitsPerUnit: 30,
|
||||||
tracksByObject: {
|
tracksByObject: {
|
||||||
obj: {
|
['obj' as ObjectAddressKey]: {
|
||||||
trackIdByPropPath: {
|
trackIdByPropPath: {
|
||||||
[encodePathToProp(['position', 'y'])]: asSequenceTrackId('1'),
|
[encodePathToProp(['position', 'y'])]: asSequenceTrackId('1'),
|
||||||
},
|
},
|
||||||
trackData: {
|
trackData: {
|
||||||
'1': {
|
['1' as SequenceTrackId]: {
|
||||||
type: 'BasicKeyframedTrack',
|
type: 'BasicKeyframedTrack',
|
||||||
keyframes: [
|
keyframes: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,6 +9,7 @@ import SimpleCache from '@theatre/shared/utils/SimpleCache'
|
||||||
import type {
|
import type {
|
||||||
$FixMe,
|
$FixMe,
|
||||||
$IntentionalAny,
|
$IntentionalAny,
|
||||||
|
DeepPartialOfSerializableValue,
|
||||||
SerializableMap,
|
SerializableMap,
|
||||||
SerializableValue,
|
SerializableValue,
|
||||||
} from '@theatre/shared/utils/types'
|
} from '@theatre/shared/utils/types'
|
||||||
|
@ -25,14 +26,29 @@ import TheatreSheetObject from './TheatreSheetObject'
|
||||||
import type {Interpolator, PropTypeConfig} from '@theatre/core/propTypes'
|
import type {Interpolator, PropTypeConfig} from '@theatre/core/propTypes'
|
||||||
import {getPropConfigByPath} from '@theatre/shared/propTypes/utils'
|
import {getPropConfigByPath} from '@theatre/shared/propTypes/utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internally, the sheet's actual configured value is not a specific type, since we
|
||||||
|
* can change the prop config at will, as such this is an alias of {@link SerializableMap}.
|
||||||
|
*
|
||||||
|
* TODO: Incorporate this knowledge into SheetObject & TemplateSheetObject
|
||||||
|
*/
|
||||||
|
type SheetObjectPropsValue = SerializableMap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object on a sheet consisting of zero or more properties which can
|
||||||
|
* be overridden statically or overridden by being sequenced.
|
||||||
|
*
|
||||||
|
* Note that this cannot be generic over `Props`, since the user is
|
||||||
|
* able to change prop configs for the sheet object's properties.
|
||||||
|
*/
|
||||||
export default class SheetObject implements IdentityDerivationProvider {
|
export default class SheetObject implements IdentityDerivationProvider {
|
||||||
get type(): 'Theatre_SheetObject' {
|
get type(): 'Theatre_SheetObject' {
|
||||||
return 'Theatre_SheetObject'
|
return 'Theatre_SheetObject'
|
||||||
}
|
}
|
||||||
readonly $$isIdentityDerivationProvider: true = true
|
readonly $$isIdentityDerivationProvider: true = true
|
||||||
readonly address: SheetObjectAddress
|
readonly address: SheetObjectAddress
|
||||||
readonly publicApi: TheatreSheetObject<$IntentionalAny>
|
readonly publicApi: TheatreSheetObject
|
||||||
private readonly _initialValue = new Atom<SerializableMap>({})
|
private readonly _initialValue = new Atom<SheetObjectPropsValue>({})
|
||||||
private readonly _cache = new SimpleCache()
|
private readonly _cache = new SimpleCache()
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -48,7 +64,7 @@ export default class SheetObject implements IdentityDerivationProvider {
|
||||||
this.publicApi = new TheatreSheetObject(this)
|
this.publicApi = new TheatreSheetObject(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
getValues(): IDerivation<Pointer<SerializableMap>> {
|
getValues(): IDerivation<Pointer<SheetObjectPropsValue>> {
|
||||||
return this._cache.get('getValues()', () =>
|
return this._cache.get('getValues()', () =>
|
||||||
prism(() => {
|
prism(() => {
|
||||||
const defaults = val(this.template.getDefaultValues())
|
const defaults = val(this.template.getDefaultValues())
|
||||||
|
@ -101,7 +117,7 @@ export default class SheetObject implements IdentityDerivationProvider {
|
||||||
final = withSeqs
|
final = withSeqs
|
||||||
}
|
}
|
||||||
|
|
||||||
const a = valToAtom<SerializableMap>('finalAtom', final)
|
const a = valToAtom<SheetObjectPropsValue>('finalAtom', final)
|
||||||
|
|
||||||
return a.pointer
|
return a.pointer
|
||||||
}),
|
}),
|
||||||
|
@ -131,7 +147,7 @@ export default class SheetObject implements IdentityDerivationProvider {
|
||||||
/**
|
/**
|
||||||
* Returns values of props that are sequenced.
|
* Returns values of props that are sequenced.
|
||||||
*/
|
*/
|
||||||
getSequencedValues(): IDerivation<Pointer<SerializableMap>> {
|
getSequencedValues(): IDerivation<Pointer<SheetObjectPropsValue>> {
|
||||||
return prism(() => {
|
return prism(() => {
|
||||||
const tracksToProcessD = prism.memo(
|
const tracksToProcessD = prism.memo(
|
||||||
'tracksToProcess',
|
'tracksToProcess',
|
||||||
|
@ -140,7 +156,7 @@ export default class SheetObject implements IdentityDerivationProvider {
|
||||||
)
|
)
|
||||||
|
|
||||||
const tracksToProcess = val(tracksToProcessD)
|
const tracksToProcess = val(tracksToProcessD)
|
||||||
const valsAtom = new Atom<SerializableMap>({})
|
const valsAtom = new Atom<SheetObjectPropsValue>({})
|
||||||
|
|
||||||
prism.effect(
|
prism.effect(
|
||||||
'processTracks',
|
'processTracks',
|
||||||
|
@ -216,17 +232,20 @@ export default class SheetObject implements IdentityDerivationProvider {
|
||||||
return interpolationTripleAtPosition(trackP, timeD)
|
return interpolationTripleAtPosition(trackP, timeD)
|
||||||
}
|
}
|
||||||
|
|
||||||
get propsP(): Pointer<$FixMe> {
|
get propsP(): Pointer<SheetObjectPropsValue> {
|
||||||
return this._cache.get('propsP', () =>
|
return this._cache.get('propsP', () =>
|
||||||
pointer<{props: {}}>({root: this, path: []}),
|
pointer<{props: {}}>({root: this, path: []}),
|
||||||
) as $FixMe
|
) as $FixMe
|
||||||
}
|
}
|
||||||
|
|
||||||
validateValue(pointer: Pointer<$FixMe>, value: unknown) {
|
validateValue(
|
||||||
|
pointer: Pointer<SheetObjectPropsValue>,
|
||||||
|
value: DeepPartialOfSerializableValue<SheetObjectPropsValue>,
|
||||||
|
) {
|
||||||
// @todo
|
// @todo
|
||||||
}
|
}
|
||||||
|
|
||||||
setInitialValue(val: SerializableMap) {
|
setInitialValue(val: DeepPartialOfSerializableValue<SheetObjectPropsValue>) {
|
||||||
this.validateValue(this.propsP, val)
|
this.validateValue(this.propsP, val)
|
||||||
this._initialValue.setState(val)
|
this._initialValue.setState(val)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import {setupTestSheet} from '@theatre/shared/testUtils'
|
import {setupTestSheet} from '@theatre/shared/testUtils'
|
||||||
import {encodePathToProp} from '@theatre/shared/utils/addresses'
|
import {encodePathToProp} from '@theatre/shared/utils/addresses'
|
||||||
import {asSequenceTrackId} from '@theatre/shared/utils/ids'
|
import {asSequenceTrackId} from '@theatre/shared/utils/ids'
|
||||||
|
import type {ObjectAddressKey, SequenceTrackId} from '@theatre/shared/utils/ids'
|
||||||
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
||||||
import {iterateOver} from '@theatre/dataverse'
|
import {iterateOver} from '@theatre/dataverse'
|
||||||
|
|
||||||
|
@ -19,15 +20,15 @@ describe(`SheetObjectTemplate`, () => {
|
||||||
subUnitsPerUnit: 30,
|
subUnitsPerUnit: 30,
|
||||||
length: 10,
|
length: 10,
|
||||||
tracksByObject: {
|
tracksByObject: {
|
||||||
obj: {
|
['obj' as ObjectAddressKey]: {
|
||||||
trackIdByPropPath: {
|
trackIdByPropPath: {
|
||||||
[encodePathToProp(['position', 'x'])]: asSequenceTrackId('x'),
|
[encodePathToProp(['position', 'x'])]: asSequenceTrackId('x'),
|
||||||
[encodePathToProp(['position', 'invalid'])]:
|
[encodePathToProp(['position', 'invalid'])]:
|
||||||
asSequenceTrackId('invalidTrack'),
|
asSequenceTrackId('invalidTrack'),
|
||||||
},
|
},
|
||||||
trackData: {
|
trackData: {
|
||||||
x: null as $IntentionalAny,
|
['x' as SequenceTrackId]: null as $IntentionalAny,
|
||||||
invalid: null as $IntentionalAny,
|
['invalid' as SequenceTrackId]: null as $IntentionalAny,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -74,15 +75,15 @@ describe(`SheetObjectTemplate`, () => {
|
||||||
subUnitsPerUnit: 30,
|
subUnitsPerUnit: 30,
|
||||||
length: 10,
|
length: 10,
|
||||||
tracksByObject: {
|
tracksByObject: {
|
||||||
obj: {
|
['obj' as ObjectAddressKey]: {
|
||||||
trackIdByPropPath: {
|
trackIdByPropPath: {
|
||||||
[encodePathToProp(['position', 'x'])]: asSequenceTrackId('x'),
|
[encodePathToProp(['position', 'x'])]: asSequenceTrackId('x'),
|
||||||
[encodePathToProp(['position', 'invalid'])]:
|
[encodePathToProp(['position', 'invalid'])]:
|
||||||
asSequenceTrackId('invalidTrack'),
|
asSequenceTrackId('invalidTrack'),
|
||||||
},
|
},
|
||||||
trackData: {
|
trackData: {
|
||||||
x: null as $IntentionalAny,
|
['x' as SequenceTrackId]: null as $IntentionalAny,
|
||||||
invalid: null as $IntentionalAny,
|
['invalid' as SequenceTrackId]: null as $IntentionalAny,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type Project from '@theatre/core/projects/Project'
|
import type Project from '@theatre/core/projects/Project'
|
||||||
import type Sheet from '@theatre/core/sheets/Sheet'
|
import type Sheet from '@theatre/core/sheets/Sheet'
|
||||||
import type SheetTemplate from '@theatre/core/sheets/SheetTemplate'
|
import type SheetTemplate from '@theatre/core/sheets/SheetTemplate'
|
||||||
import type {SheetObjectConfig} from '@theatre/core/sheets/TheatreSheet'
|
import type {SheetObjectPropTypeConfig} from '@theatre/core/sheets/TheatreSheet'
|
||||||
import {emptyArray} from '@theatre/shared/utils'
|
import {emptyArray} from '@theatre/shared/utils'
|
||||||
import type {
|
import type {
|
||||||
PathToProp,
|
PathToProp,
|
||||||
|
@ -9,7 +9,7 @@ import type {
|
||||||
WithoutSheetInstance,
|
WithoutSheetInstance,
|
||||||
} from '@theatre/shared/utils/addresses'
|
} from '@theatre/shared/utils/addresses'
|
||||||
import getDeep from '@theatre/shared/utils/getDeep'
|
import getDeep from '@theatre/shared/utils/getDeep'
|
||||||
import type {SequenceTrackId} from '@theatre/shared/utils/ids'
|
import type {ObjectAddressKey, SequenceTrackId} from '@theatre/shared/utils/ids'
|
||||||
import SimpleCache from '@theatre/shared/utils/SimpleCache'
|
import SimpleCache from '@theatre/shared/utils/SimpleCache'
|
||||||
import type {
|
import type {
|
||||||
$FixMe,
|
$FixMe,
|
||||||
|
@ -23,7 +23,6 @@ import set from 'lodash-es/set'
|
||||||
import getPropDefaultsOfSheetObject from './getPropDefaultsOfSheetObject'
|
import getPropDefaultsOfSheetObject from './getPropDefaultsOfSheetObject'
|
||||||
import SheetObject from './SheetObject'
|
import SheetObject from './SheetObject'
|
||||||
import logger from '@theatre/shared/logger'
|
import logger from '@theatre/shared/logger'
|
||||||
import type {PropTypeConfig_Compound} from '@theatre/core/propTypes'
|
|
||||||
import {
|
import {
|
||||||
getPropConfigByPath,
|
getPropConfigByPath,
|
||||||
isPropConfSequencable,
|
isPropConfSequencable,
|
||||||
|
@ -34,12 +33,15 @@ export type IPropPathToTrackIdTree = {
|
||||||
[key in string]?: SequenceTrackId | IPropPathToTrackIdTree
|
[key in string]?: SequenceTrackId | IPropPathToTrackIdTree
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Add documentation, and share examples of sheet objects.
|
||||||
|
*
|
||||||
|
* See {@link SheetObject} for more information.
|
||||||
|
*/
|
||||||
export default class SheetObjectTemplate {
|
export default class SheetObjectTemplate {
|
||||||
readonly address: WithoutSheetInstance<SheetObjectAddress>
|
readonly address: WithoutSheetInstance<SheetObjectAddress>
|
||||||
readonly type: 'Theatre_SheetObjectTemplate' = 'Theatre_SheetObjectTemplate'
|
readonly type: 'Theatre_SheetObjectTemplate' = 'Theatre_SheetObjectTemplate'
|
||||||
protected _config: Atom<
|
protected _config: Atom<SheetObjectPropTypeConfig>
|
||||||
SheetObjectConfig<PropTypeConfig_Compound<$IntentionalAny>>
|
|
||||||
>
|
|
||||||
readonly _cache = new SimpleCache()
|
readonly _cache = new SimpleCache()
|
||||||
readonly project: Project
|
readonly project: Project
|
||||||
|
|
||||||
|
@ -49,9 +51,9 @@ export default class SheetObjectTemplate {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly sheetTemplate: SheetTemplate,
|
readonly sheetTemplate: SheetTemplate,
|
||||||
objectKey: string,
|
objectKey: ObjectAddressKey,
|
||||||
nativeObject: unknown,
|
nativeObject: unknown,
|
||||||
config: SheetObjectConfig<$IntentionalAny>,
|
config: SheetObjectPropTypeConfig,
|
||||||
) {
|
) {
|
||||||
this.address = {...sheetTemplate.address, objectKey}
|
this.address = {...sheetTemplate.address, objectKey}
|
||||||
this._config = new Atom(config)
|
this._config = new Atom(config)
|
||||||
|
@ -61,13 +63,13 @@ export default class SheetObjectTemplate {
|
||||||
createInstance(
|
createInstance(
|
||||||
sheet: Sheet,
|
sheet: Sheet,
|
||||||
nativeObject: unknown,
|
nativeObject: unknown,
|
||||||
config: SheetObjectConfig<$IntentionalAny>,
|
config: SheetObjectPropTypeConfig,
|
||||||
): SheetObject {
|
): SheetObject {
|
||||||
this._config.setState(config)
|
this._config.setState(config)
|
||||||
return new SheetObject(sheet, this, nativeObject)
|
return new SheetObject(sheet, this, nativeObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
overrideConfig(config: SheetObjectConfig<$IntentionalAny>) {
|
overrideConfig(config: SheetObjectPropTypeConfig) {
|
||||||
this._config.setState(config)
|
this._config.setState(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +101,7 @@ export default class SheetObjectTemplate {
|
||||||
pointerToSheetState.staticOverrides.byObject[
|
pointerToSheetState.staticOverrides.byObject[
|
||||||
this.address.objectKey
|
this.address.objectKey
|
||||||
],
|
],
|
||||||
) || {}
|
) ?? {}
|
||||||
|
|
||||||
const config = val(this._config.pointer)
|
const config = val(this._config.pointer)
|
||||||
const deserialized = config.deserializeAndSanitize(json) || {}
|
const deserialized = config.deserializeAndSanitize(json) || {}
|
||||||
|
|
|
@ -13,11 +13,13 @@ import type {IDerivation, Pointer} from '@theatre/dataverse'
|
||||||
import {prism, val} from '@theatre/dataverse'
|
import {prism, val} from '@theatre/dataverse'
|
||||||
import type SheetObject from './SheetObject'
|
import type SheetObject from './SheetObject'
|
||||||
import type {
|
import type {
|
||||||
IShorthandCompoundProps,
|
UnknownShorthandCompoundProps,
|
||||||
ShorthandPropToLonghandProp,
|
PropsValue,
|
||||||
} from '@theatre/core/propTypes/internals'
|
} from '@theatre/core/propTypes/internals'
|
||||||
|
|
||||||
export interface ISheetObject<Props extends IShorthandCompoundProps = {}> {
|
export interface ISheetObject<
|
||||||
|
Props extends UnknownShorthandCompoundProps = UnknownShorthandCompoundProps,
|
||||||
|
> {
|
||||||
/**
|
/**
|
||||||
* All Objects will have `object.type === 'Theatre_SheetObject_PublicAPI'`
|
* All Objects will have `object.type === 'Theatre_SheetObject_PublicAPI'`
|
||||||
*/
|
*/
|
||||||
|
@ -32,8 +34,14 @@ export interface ISheetObject<Props extends IShorthandCompoundProps = {}> {
|
||||||
* 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
|
||||||
* ```
|
* ```
|
||||||
|
*
|
||||||
|
* Future: Notice that if the user actually changes the Props config for one of the
|
||||||
|
* properties, then this type can't be guaranteed accurrate.
|
||||||
|
* * Right now the user can't change prop configs, but we'll probably enable that
|
||||||
|
* functionality later via (`object.overrideConfig()`). We need to educate the
|
||||||
|
* user that they can't rely on static types to know the type of object.value.
|
||||||
*/
|
*/
|
||||||
readonly value: ShorthandPropToLonghandProp<Props>['valueType']
|
readonly value: PropsValue<Props>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Pointer to the props of the object.
|
* A Pointer to the props of the object.
|
||||||
|
@ -100,7 +108,7 @@ export interface ISheetObject<Props extends IShorthandCompoundProps = {}> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class TheatreSheetObject<
|
export default class TheatreSheetObject<
|
||||||
Props extends IShorthandCompoundProps = {},
|
Props extends UnknownShorthandCompoundProps = UnknownShorthandCompoundProps,
|
||||||
> implements ISheetObject<Props>
|
> implements ISheetObject<Props>
|
||||||
{
|
{
|
||||||
get type(): 'Theatre_SheetObject_PublicAPI' {
|
get type(): 'Theatre_SheetObject_PublicAPI' {
|
||||||
|
@ -134,7 +142,7 @@ export default class TheatreSheetObject<
|
||||||
private _valuesDerivation(): IDerivation<this['value']> {
|
private _valuesDerivation(): IDerivation<this['value']> {
|
||||||
return this._cache.get('onValuesChangeDerivation', () => {
|
return this._cache.get('onValuesChangeDerivation', () => {
|
||||||
const sheetObject = privateAPI(this)
|
const sheetObject = privateAPI(this)
|
||||||
const d: IDerivation<Props> = prism(() => {
|
const d: IDerivation<PropsValue<Props>> = prism(() => {
|
||||||
return val(sheetObject.getValues().getValue()) as $FixMe
|
return val(sheetObject.getValues().getValue()) as $FixMe
|
||||||
})
|
})
|
||||||
return d
|
return d
|
||||||
|
@ -145,7 +153,7 @@ export default class TheatreSheetObject<
|
||||||
return this._valuesDerivation().tapImmediate(coreTicker, fn)
|
return this._valuesDerivation().tapImmediate(coreTicker, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
get value(): ShorthandPropToLonghandProp<Props>['valueType'] {
|
get value(): PropsValue<Props> {
|
||||||
return this._valuesDerivation().getValue()
|
return this._valuesDerivation().getValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import type {SheetObjectConfig} from '@theatre/core/sheets/TheatreSheet'
|
import type {SheetObjectPropTypeConfig} from '@theatre/core/sheets/TheatreSheet'
|
||||||
import type {
|
import type {
|
||||||
$FixMe,
|
$FixMe,
|
||||||
$IntentionalAny,
|
|
||||||
SerializableMap,
|
SerializableMap,
|
||||||
SerializableValue,
|
SerializableValue,
|
||||||
} from '@theatre/shared/utils/types'
|
} from '@theatre/shared/utils/types'
|
||||||
|
@ -17,9 +16,9 @@ const cachedDefaults = new WeakMap<PropTypeConfig, SerializableValue>()
|
||||||
* Generates and caches a default value for the config of a SheetObject.
|
* Generates and caches a default value for the config of a SheetObject.
|
||||||
*/
|
*/
|
||||||
export default function getPropDefaultsOfSheetObject(
|
export default function getPropDefaultsOfSheetObject(
|
||||||
config: SheetObjectConfig<$IntentionalAny>,
|
config: SheetObjectPropTypeConfig,
|
||||||
): SerializableMap {
|
): SerializableMap {
|
||||||
return getDefaultsOfPropTypeConfig(config) as $IntentionalAny
|
return getDefaultsOfPropTypeConfig(config) as SerializableMap // sheet objects result in non-primitive objects
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultsOfPropTypeConfig(
|
function getDefaultsOfPropTypeConfig(
|
||||||
|
|
|
@ -1,17 +1,28 @@
|
||||||
import type Project from '@theatre/core/projects/Project'
|
import type Project from '@theatre/core/projects/Project'
|
||||||
import Sequence from '@theatre/core/sequences/Sequence'
|
import Sequence from '@theatre/core/sequences/Sequence'
|
||||||
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
||||||
import type {SheetObjectConfig} from '@theatre/core/sheets/TheatreSheet'
|
import type {SheetObjectPropTypeConfig} from '@theatre/core/sheets/TheatreSheet'
|
||||||
import TheatreSheet from '@theatre/core/sheets/TheatreSheet'
|
import TheatreSheet from '@theatre/core/sheets/TheatreSheet'
|
||||||
import type {SheetAddress} from '@theatre/shared/utils/addresses'
|
import type {SheetAddress} from '@theatre/shared/utils/addresses'
|
||||||
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
|
||||||
import {Atom, valueDerivation} from '@theatre/dataverse'
|
import {Atom, valueDerivation} from '@theatre/dataverse'
|
||||||
import type SheetTemplate from './SheetTemplate'
|
import type SheetTemplate from './SheetTemplate'
|
||||||
|
import type {ObjectAddressKey, SheetInstanceId} from '@theatre/shared/utils/ids'
|
||||||
|
import type {StrictRecord} from '@theatre/shared/utils/types'
|
||||||
|
|
||||||
type IObjects = {[key: string]: SheetObject}
|
type SheetObjectMap = StrictRecord<ObjectAddressKey, SheetObject>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Future: `nativeObject` Idea is to potentially allow the user to provide their own
|
||||||
|
* object in to the object call as a way to keep a handle to an underlying object via
|
||||||
|
* the {@link ISheetObject}.
|
||||||
|
*
|
||||||
|
* For example, a THREEjs object or an HTMLElement is passed in.
|
||||||
|
*/
|
||||||
|
export type ObjectNativeObject = unknown
|
||||||
|
|
||||||
export default class Sheet {
|
export default class Sheet {
|
||||||
private readonly _objects: Atom<IObjects> = new Atom<IObjects>({})
|
private readonly _objects: Atom<SheetObjectMap> =
|
||||||
|
new Atom<SheetObjectMap>({})
|
||||||
private _sequence: undefined | Sequence
|
private _sequence: undefined | Sequence
|
||||||
readonly address: SheetAddress
|
readonly address: SheetAddress
|
||||||
readonly publicApi: TheatreSheet
|
readonly publicApi: TheatreSheet
|
||||||
|
@ -21,7 +32,7 @@ export default class Sheet {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly template: SheetTemplate,
|
readonly template: SheetTemplate,
|
||||||
public readonly instanceId: string,
|
public readonly instanceId: SheetInstanceId,
|
||||||
) {
|
) {
|
||||||
this.project = template.project
|
this.project = template.project
|
||||||
this.address = {
|
this.address = {
|
||||||
|
@ -37,24 +48,24 @@ export default class Sheet {
|
||||||
* with that of "an element."
|
* with that of "an element."
|
||||||
*/
|
*/
|
||||||
createObject(
|
createObject(
|
||||||
key: string,
|
objectKey: ObjectAddressKey,
|
||||||
nativeObject: unknown,
|
nativeObject: ObjectNativeObject,
|
||||||
config: SheetObjectConfig<$IntentionalAny>,
|
config: SheetObjectPropTypeConfig,
|
||||||
): SheetObject {
|
): SheetObject {
|
||||||
const objTemplate = this.template.getObjectTemplate(
|
const objTemplate = this.template.getObjectTemplate(
|
||||||
key,
|
objectKey,
|
||||||
nativeObject,
|
nativeObject,
|
||||||
config,
|
config,
|
||||||
)
|
)
|
||||||
|
|
||||||
const object = objTemplate.createInstance(this, nativeObject, config)
|
const object = objTemplate.createInstance(this, nativeObject, config)
|
||||||
|
|
||||||
this._objects.setIn([key], object)
|
this._objects.setIn([objectKey], object)
|
||||||
|
|
||||||
return object
|
return object
|
||||||
}
|
}
|
||||||
|
|
||||||
getObject(key: string): SheetObject | undefined {
|
getObject(key: ObjectAddressKey): SheetObject | undefined {
|
||||||
return this._objects.getState()[key]
|
return this._objects.getState()[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,27 +4,38 @@ import type {
|
||||||
SheetAddress,
|
SheetAddress,
|
||||||
WithoutSheetInstance,
|
WithoutSheetInstance,
|
||||||
} from '@theatre/shared/utils/addresses'
|
} from '@theatre/shared/utils/addresses'
|
||||||
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
|
||||||
import {Atom} from '@theatre/dataverse'
|
import {Atom} from '@theatre/dataverse'
|
||||||
|
import type {Pointer} from '@theatre/dataverse'
|
||||||
import Sheet from './Sheet'
|
import Sheet from './Sheet'
|
||||||
import type {SheetObjectConfig} from './TheatreSheet'
|
import type {ObjectNativeObject} from './Sheet'
|
||||||
|
import type {SheetObjectPropTypeConfig} from './TheatreSheet'
|
||||||
|
import type {
|
||||||
|
ObjectAddressKey,
|
||||||
|
SheetId,
|
||||||
|
SheetInstanceId,
|
||||||
|
} from '@theatre/shared/utils/ids'
|
||||||
|
import type {StrictRecord} from '@theatre/shared/utils/types'
|
||||||
|
|
||||||
|
type SheetTemplateObjectTemplateMap = StrictRecord<
|
||||||
|
ObjectAddressKey,
|
||||||
|
SheetObjectTemplate
|
||||||
|
>
|
||||||
|
|
||||||
export default class SheetTemplate {
|
export default class SheetTemplate {
|
||||||
readonly type: 'Theatre_SheetTemplate' = 'Theatre_SheetTemplate'
|
readonly type: 'Theatre_SheetTemplate' = 'Theatre_SheetTemplate'
|
||||||
readonly address: WithoutSheetInstance<SheetAddress>
|
readonly address: WithoutSheetInstance<SheetAddress>
|
||||||
private _instances = new Atom<{[instanceId: string]: Sheet}>({})
|
private _instances = new Atom<Record<SheetInstanceId, Sheet>>({})
|
||||||
readonly instancesP = this._instances.pointer
|
readonly instancesP: Pointer<Record<SheetInstanceId, Sheet>> =
|
||||||
|
this._instances.pointer
|
||||||
|
|
||||||
private _objectTemplates = new Atom<{
|
private _objectTemplates = new Atom<SheetTemplateObjectTemplateMap>({})
|
||||||
[objectKey: string]: SheetObjectTemplate
|
|
||||||
}>({})
|
|
||||||
readonly objectTemplatesP = this._objectTemplates.pointer
|
readonly objectTemplatesP = this._objectTemplates.pointer
|
||||||
|
|
||||||
constructor(readonly project: Project, sheetId: string) {
|
constructor(readonly project: Project, sheetId: SheetId) {
|
||||||
this.address = {...project.address, sheetId}
|
this.address = {...project.address, sheetId}
|
||||||
}
|
}
|
||||||
|
|
||||||
getInstance(instanceId: string): Sheet {
|
getInstance(instanceId: SheetInstanceId): Sheet {
|
||||||
let inst = this._instances.getState()[instanceId]
|
let inst = this._instances.getState()[instanceId]
|
||||||
|
|
||||||
if (!inst) {
|
if (!inst) {
|
||||||
|
@ -36,15 +47,15 @@ export default class SheetTemplate {
|
||||||
}
|
}
|
||||||
|
|
||||||
getObjectTemplate(
|
getObjectTemplate(
|
||||||
key: string,
|
objectKey: ObjectAddressKey,
|
||||||
nativeObject: unknown,
|
nativeObject: ObjectNativeObject,
|
||||||
config: SheetObjectConfig<$IntentionalAny>,
|
config: SheetObjectPropTypeConfig,
|
||||||
): SheetObjectTemplate {
|
): SheetObjectTemplate {
|
||||||
let template = this._objectTemplates.getState()[key]
|
let template = this._objectTemplates.getState()[objectKey]
|
||||||
|
|
||||||
if (!template) {
|
if (!template) {
|
||||||
template = new SheetObjectTemplate(this, key, nativeObject, config)
|
template = new SheetObjectTemplate(this, objectKey, nativeObject, config)
|
||||||
this._objectTemplates.setIn([key], template)
|
this._objectTemplates.setIn([objectKey], template)
|
||||||
}
|
}
|
||||||
|
|
||||||
return template
|
return template
|
||||||
|
|
|
@ -9,15 +9,18 @@ import type Sheet from '@theatre/core/sheets/Sheet'
|
||||||
import type {SheetAddress} from '@theatre/shared/utils/addresses'
|
import type {SheetAddress} from '@theatre/shared/utils/addresses'
|
||||||
import {InvalidArgumentError} from '@theatre/shared/utils/errors'
|
import {InvalidArgumentError} from '@theatre/shared/utils/errors'
|
||||||
import {validateAndSanitiseSlashedPathOrThrow} from '@theatre/shared/utils/slashedPaths'
|
import {validateAndSanitiseSlashedPathOrThrow} from '@theatre/shared/utils/slashedPaths'
|
||||||
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
import type {$FixMe, $IntentionalAny} from '@theatre/shared/utils/types'
|
||||||
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 {IShorthandCompoundProps} from '@theatre/core/propTypes/internals'
|
import type {
|
||||||
|
UnknownShorthandCompoundProps,
|
||||||
|
UnknownValidCompoundProps,
|
||||||
|
} from '@theatre/core/propTypes/internals'
|
||||||
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
||||||
|
import type {ObjectAddressKey} from '@theatre/shared/utils/ids'
|
||||||
|
|
||||||
export type SheetObjectConfig<
|
export type SheetObjectPropTypeConfig =
|
||||||
Props extends PropTypeConfig_Compound<$IntentionalAny>,
|
PropTypeConfig_Compound<UnknownValidCompoundProps>
|
||||||
> = Props
|
|
||||||
|
|
||||||
export interface ISheet {
|
export interface ISheet {
|
||||||
/**
|
/**
|
||||||
|
@ -62,7 +65,7 @@ export interface ISheet {
|
||||||
* obj.value.position // {x: 0, y: 0}
|
* obj.value.position // {x: 0, y: 0}
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
object<Props extends IShorthandCompoundProps>(
|
object<Props extends UnknownShorthandCompoundProps>(
|
||||||
key: string,
|
key: string,
|
||||||
props: Props,
|
props: Props,
|
||||||
): ISheetObject<Props>
|
): ISheetObject<Props>
|
||||||
|
@ -75,7 +78,7 @@ export interface ISheet {
|
||||||
|
|
||||||
const weakMapOfUnsanitizedProps = new WeakMap<
|
const weakMapOfUnsanitizedProps = new WeakMap<
|
||||||
SheetObject,
|
SheetObject,
|
||||||
IShorthandCompoundProps
|
UnknownShorthandCompoundProps
|
||||||
>()
|
>()
|
||||||
|
|
||||||
export default class TheatreSheet implements ISheet {
|
export default class TheatreSheet implements ISheet {
|
||||||
|
@ -89,7 +92,7 @@ export default class TheatreSheet implements ISheet {
|
||||||
setPrivateAPI(this, sheet)
|
setPrivateAPI(this, sheet)
|
||||||
}
|
}
|
||||||
|
|
||||||
object<Props extends IShorthandCompoundProps>(
|
object<Props extends UnknownShorthandCompoundProps>(
|
||||||
key: string,
|
key: string,
|
||||||
config: Props,
|
config: Props,
|
||||||
): ISheetObject<Props> {
|
): ISheetObject<Props> {
|
||||||
|
@ -99,8 +102,15 @@ export default class TheatreSheet implements ISheet {
|
||||||
`sheet.object("${key}", ...)`,
|
`sheet.object("${key}", ...)`,
|
||||||
)
|
)
|
||||||
|
|
||||||
const existingObject = internal.getObject(sanitizedPath)
|
const existingObject = internal.getObject(sanitizedPath as ObjectAddressKey)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Future: `nativeObject` Idea is to potentially allow the user to provide their own
|
||||||
|
* object in to the object call as a way to keep a handle to an underlying object via
|
||||||
|
* the {@link ISheetObject}.
|
||||||
|
*
|
||||||
|
* For example, a THREEjs object or an HTMLElement is passed in.
|
||||||
|
*/
|
||||||
const nativeObject = null
|
const nativeObject = null
|
||||||
|
|
||||||
if (existingObject) {
|
if (existingObject) {
|
||||||
|
@ -121,12 +131,12 @@ export default class TheatreSheet implements ISheet {
|
||||||
} else {
|
} else {
|
||||||
const sanitizedConfig = compound(config)
|
const sanitizedConfig = compound(config)
|
||||||
const object = internal.createObject(
|
const object = internal.createObject(
|
||||||
sanitizedPath,
|
sanitizedPath as ObjectAddressKey,
|
||||||
nativeObject,
|
nativeObject,
|
||||||
sanitizedConfig,
|
sanitizedConfig,
|
||||||
)
|
)
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
weakMapOfUnsanitizedProps.set(object, config)
|
weakMapOfUnsanitizedProps.set(object as $FixMe, config)
|
||||||
}
|
}
|
||||||
return object.publicApi as $IntentionalAny
|
return object.publicApi as $IntentionalAny
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import * as t from '@theatre/core/propTypes'
|
||||||
import getStudio from '@theatre/studio/getStudio'
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
import coreTicker from '@theatre/core/coreTicker'
|
import coreTicker from '@theatre/core/coreTicker'
|
||||||
import globals from './globals'
|
import globals from './globals'
|
||||||
|
import type {SheetId} from './utils/ids'
|
||||||
/* eslint-enable no-restricted-syntax */
|
/* eslint-enable no-restricted-syntax */
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
|
@ -32,7 +33,7 @@ export async function setupTestSheet(sheetState: SheetState_Historic) {
|
||||||
const projectState: ProjectState_Historic = {
|
const projectState: ProjectState_Historic = {
|
||||||
definitionVersion: globals.currentProjectStateDefinitionVersion,
|
definitionVersion: globals.currentProjectStateDefinitionVersion,
|
||||||
sheetsById: {
|
sheetsById: {
|
||||||
Sheet: sheetState,
|
['Sheet' as SheetId]: sheetState,
|
||||||
},
|
},
|
||||||
revisionHistory: [],
|
revisionHistory: [],
|
||||||
}
|
}
|
||||||
|
|
37
theatre/shared/src/utils/Nominal.ts
Normal file
37
theatre/shared/src/utils/Nominal.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/**
|
||||||
|
* Using a symbol, we can sort of add unique properties to arbitrary other types.
|
||||||
|
* So, we use this to our advantage to add a "marker" of information to strings using
|
||||||
|
* the {@link Nominal} type.
|
||||||
|
*
|
||||||
|
* Can be used with keys in pointers.
|
||||||
|
* This identifier shows in the expanded {@link Nominal} as `string & {[nominal]:"SequenceTrackId"}`,
|
||||||
|
* So, we're opting to keeping the identifier short.
|
||||||
|
*/
|
||||||
|
const nominal = Symbol()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This creates an "opaque"/"nominal" type.
|
||||||
|
*
|
||||||
|
* Our primary use case is to be able to use with keys in pointers.
|
||||||
|
*
|
||||||
|
* Numbers cannot be added together if they are "nominal"
|
||||||
|
*
|
||||||
|
* See {@link nominal} for more details.
|
||||||
|
*/
|
||||||
|
export type Nominal<N extends string> = string & {[nominal]: N}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// Fix Object.entries and Object.keys definitions for Nominal strict records
|
||||||
|
interface ObjectConstructor {
|
||||||
|
/** Nominal: Extension to the Object prototype definition to properly manage {@link Nominal} keyed records */
|
||||||
|
keys<T extends Record<Nominal<string>, any>>(
|
||||||
|
obj: T,
|
||||||
|
): any extends T ? never[] : Extract<keyof T, string>[]
|
||||||
|
/** Nominal: Extension to the Object prototype definition to properly manage {@link Nominal} keyed records */
|
||||||
|
entries<T extends Record<Nominal<string>, any>>(
|
||||||
|
obj: T,
|
||||||
|
): any extends T
|
||||||
|
? [never, never][]
|
||||||
|
: Array<{[P in keyof T]: [P, T[P]]}[Extract<keyof T, string>]>
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,12 +3,13 @@ import type {
|
||||||
SerializableMap,
|
SerializableMap,
|
||||||
SerializableValue,
|
SerializableValue,
|
||||||
} from '@theatre/shared/utils/types'
|
} from '@theatre/shared/utils/types'
|
||||||
|
import type {ObjectAddressKey, ProjectId, SheetId, SheetInstanceId} from './ids'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the address to a project
|
* Represents the address to a project
|
||||||
*/
|
*/
|
||||||
export interface ProjectAddress {
|
export interface ProjectAddress {
|
||||||
projectId: string
|
projectId: ProjectId
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,8 +23,8 @@ export interface ProjectAddress {
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export interface SheetAddress extends ProjectAddress {
|
export interface SheetAddress extends ProjectAddress {
|
||||||
sheetId: string
|
sheetId: SheetId
|
||||||
sheetInstanceId: string
|
sheetInstanceId: SheetInstanceId
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,7 +37,7 @@ export type WithoutSheetInstance<T extends SheetAddress> = Omit<
|
||||||
>
|
>
|
||||||
|
|
||||||
export type SheetInstanceOptional<T extends SheetAddress> =
|
export type SheetInstanceOptional<T extends SheetAddress> =
|
||||||
WithoutSheetInstance<T> & {sheetInstanceId?: string | undefined}
|
WithoutSheetInstance<T> & {sheetInstanceId?: SheetInstanceId | undefined}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the address to a Sheet's Object
|
* Represents the address to a Sheet's Object
|
||||||
|
@ -51,7 +52,7 @@ export interface SheetObjectAddress extends SheetAddress {
|
||||||
* obj.address.objectKey === 'foo'
|
* obj.address.objectKey === 'foo'
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
objectKey: string
|
objectKey: ObjectAddressKey
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PathToProp = Array<string | number>
|
export type PathToProp = Array<string | number>
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import {nanoid as generateNonSecure} from 'nanoid/non-secure'
|
import {nanoid as generateNonSecure} from 'nanoid/non-secure'
|
||||||
import type {$IntentionalAny, Nominal} from './types'
|
import type {Nominal} from './Nominal'
|
||||||
|
import type {$IntentionalAny} from './types'
|
||||||
|
|
||||||
export type KeyframeId = Nominal<string, 'KeyframeId'>
|
export type KeyframeId = Nominal<'KeyframeId'>
|
||||||
|
|
||||||
export function generateKeyframeId() {
|
export function generateKeyframeId(): KeyframeId {
|
||||||
return generateNonSecure(10) as KeyframeId
|
return generateNonSecure(10) as KeyframeId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,10 +12,16 @@ export function asKeyframeId(s: string): KeyframeId {
|
||||||
return s as $IntentionalAny
|
return s as $IntentionalAny
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo make nominal
|
export type ProjectId = Nominal<'ProjectId'>
|
||||||
export type SequenceTrackId = string
|
export type SheetId = Nominal<'SheetId'>
|
||||||
|
export type SheetInstanceId = Nominal<'SheetInstanceId'>
|
||||||
|
export type PaneInstanceId = Nominal<'PaneInstanceId'>
|
||||||
|
export type SequenceTrackId = Nominal<'SequenceTrackId'>
|
||||||
|
export type ObjectAddressKey = Nominal<'ObjectAddressKey'>
|
||||||
|
/** UI panels can contain a {@link PaneInstanceId} or something else. */
|
||||||
|
export type UIPanelId = Nominal<'UIPanelId'>
|
||||||
|
|
||||||
export function generateSequenceTrackId() {
|
export function generateSequenceTrackId(): SequenceTrackId {
|
||||||
return generateNonSecure(10) as $IntentionalAny as SequenceTrackId
|
return generateNonSecure(10) as $IntentionalAny as SequenceTrackId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,11 @@ import type {Pointer} from '@theatre/dataverse'
|
||||||
import type {PathToProp} from './addresses'
|
import type {PathToProp} from './addresses'
|
||||||
import type {$IntentionalAny} from './types'
|
import type {$IntentionalAny} from './types'
|
||||||
|
|
||||||
export default function pointerDeep(
|
export default function pointerDeep<T>(
|
||||||
base: Pointer<$IntentionalAny>,
|
base: Pointer<T>,
|
||||||
toAppend: PathToProp,
|
toAppend: PathToProp,
|
||||||
): Pointer<unknown> {
|
): Pointer<unknown> {
|
||||||
let p = base
|
let p = base as $IntentionalAny
|
||||||
for (const k of toAppend) {
|
for (const k of toAppend) {
|
||||||
p = p[k]
|
p = p[k]
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,13 @@ export type SerializablePrimitive =
|
||||||
| boolean
|
| boolean
|
||||||
| {r: number; g: number; b: number; a: number}
|
| {r: number; g: number; b: number; a: number}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This type represents all values that can be safely serialized.
|
||||||
|
* Also, it's notable that this type is compatible for dataverse pointer traversal (everything
|
||||||
|
* is path accessible [e.g. `a.b.c`]).
|
||||||
|
*
|
||||||
|
* One example usage is for keyframe values or static overrides such as `Rgba`, `string`, `number`, and "compound values".
|
||||||
|
*/
|
||||||
export type SerializableValue<
|
export type SerializableValue<
|
||||||
Primitives extends SerializablePrimitive = SerializablePrimitive,
|
Primitives extends SerializablePrimitive = SerializablePrimitive,
|
||||||
> = Primitives | SerializableMap
|
> = Primitives | SerializableMap
|
||||||
|
@ -57,15 +64,13 @@ export type DeepPartialOfSerializableValue<T extends SerializableValue> =
|
||||||
}
|
}
|
||||||
: T
|
: T
|
||||||
|
|
||||||
export type StrictRecord<Key extends string, V> = {[K in Key]?: V}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is supposed to create an "opaque" or "nominal" type, but since typescript
|
* This is equivalent to `Partial<Record<Key, V>>` being used to describe a sort of Map
|
||||||
* doesn't allow generic index signatures, we're leaving it be.
|
* where the keys might not have values.
|
||||||
*
|
*
|
||||||
* TODO fix this once https://github.com/microsoft/TypeScript/pull/26797 lands (likely typescript 4.4)
|
* We do not use `Map`s, because they add comlpexity with converting to `JSON.stringify` + pointer types
|
||||||
*/
|
*/
|
||||||
export type Nominal<T, N extends string> = T
|
export type StrictRecord<Key extends string, V> = {[K in Key]?: V}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: We should deprecate this and just use `[start: number, end: number]`
|
* TODO: We should deprecate this and just use `[start: number, end: number]`
|
||||||
|
@ -81,4 +86,4 @@ export type $IntentionalAny = any
|
||||||
* Represents the `x` or `y` value of getBoundingClientRect().
|
* Represents the `x` or `y` value of getBoundingClientRect().
|
||||||
* In other words, represents a distance from 0,0 in screen space.
|
* In other words, represents a distance from 0,0 in screen space.
|
||||||
*/
|
*/
|
||||||
export type PositionInScreenSpace = Nominal<number, 'ScreenSpaceDim'>
|
export type PositionInScreenSpace = number
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import {prism, val} from '@theatre/dataverse'
|
import {prism, val} from '@theatre/dataverse'
|
||||||
import {emptyArray} from '@theatre/shared/utils'
|
import {emptyArray} from '@theatre/shared/utils'
|
||||||
|
import type {PaneInstanceId} from '@theatre/shared/utils/ids'
|
||||||
import SimpleCache from '@theatre/shared/utils/SimpleCache'
|
import SimpleCache from '@theatre/shared/utils/SimpleCache'
|
||||||
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
import type {$IntentionalAny, StrictRecord} from '@theatre/shared/utils/types'
|
||||||
import type {Studio} from './Studio'
|
import type {Studio} from './Studio'
|
||||||
import type {PaneInstance} from './TheatreStudio'
|
import type {PaneInstance} from './TheatreStudio'
|
||||||
|
|
||||||
|
@ -21,7 +22,7 @@ export default class PaneManager {
|
||||||
|
|
||||||
private _getAllPanes() {
|
private _getAllPanes() {
|
||||||
return this._cache.get('_getAllPanels()', () =>
|
return this._cache.get('_getAllPanels()', () =>
|
||||||
prism((): {[instanceId in string]?: PaneInstance<string>} => {
|
prism((): StrictRecord<PaneInstanceId, PaneInstance<string>> => {
|
||||||
const core = val(this._studio.coreP)
|
const core = val(this._studio.coreP)
|
||||||
if (!core) return {}
|
if (!core) return {}
|
||||||
const instanceDescriptors = val(
|
const instanceDescriptors = val(
|
||||||
|
@ -31,17 +32,16 @@ export default class PaneManager {
|
||||||
this._studio.atomP.ephemeral.extensions.paneClasses,
|
this._studio.atomP.ephemeral.extensions.paneClasses,
|
||||||
)
|
)
|
||||||
|
|
||||||
const instances: {[instanceId in string]?: PaneInstance<string>} = {}
|
const instances: StrictRecord<PaneInstanceId, PaneInstance<string>> = {}
|
||||||
for (const [, instanceDescriptor] of Object.entries(
|
for (const instanceDescriptor of Object.values(instanceDescriptors)) {
|
||||||
instanceDescriptors,
|
if (!instanceDescriptor) continue
|
||||||
)) {
|
const panelClass = paneClasses[instanceDescriptor.paneClass]
|
||||||
const panelClass = paneClasses[instanceDescriptor!.paneClass]
|
|
||||||
if (!panelClass) continue
|
if (!panelClass) continue
|
||||||
const {instanceId} = instanceDescriptor!
|
const {instanceId} = instanceDescriptor
|
||||||
const {extensionId, classDefinition: definition} = panelClass
|
const {extensionId, classDefinition: definition} = panelClass
|
||||||
|
|
||||||
const instance = prism.memo(
|
const instance = prism.memo(
|
||||||
`instance-${instanceDescriptor!.instanceId}`,
|
`instance-${instanceDescriptor.instanceId}`,
|
||||||
() => {
|
() => {
|
||||||
const inst: PaneInstance<$IntentionalAny> = {
|
const inst: PaneInstance<$IntentionalAny> = {
|
||||||
extensionId,
|
extensionId,
|
||||||
|
@ -82,14 +82,14 @@ export default class PaneManager {
|
||||||
const allPaneInstances = val(
|
const allPaneInstances = val(
|
||||||
this._studio.atomP.historic.panelInstanceDesceriptors,
|
this._studio.atomP.historic.panelInstanceDesceriptors,
|
||||||
)
|
)
|
||||||
let instanceId!: string
|
let instanceId!: PaneInstanceId
|
||||||
for (let i = 1; i < 1000; i++) {
|
for (let i = 1; i < 1000; i++) {
|
||||||
instanceId = `${paneClass} #${i}`
|
instanceId = `${paneClass} #${i}` as PaneInstanceId
|
||||||
if (!allPaneInstances[instanceId]) break
|
if (!allPaneInstances[instanceId]) break
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!extensionId) {
|
if (!extensionId) {
|
||||||
throw new Error(`Pance class "${paneClass}" is not registered.`)
|
throw new Error(`Pane class "${paneClass}" is not registered.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
this._studio.transaction(({drafts}) => {
|
this._studio.transaction(({drafts}) => {
|
||||||
|
@ -102,7 +102,7 @@ export default class PaneManager {
|
||||||
return this._getAllPanes().getValue()[instanceId]!
|
return this._getAllPanes().getValue()[instanceId]!
|
||||||
}
|
}
|
||||||
|
|
||||||
destroyPane(instanceId: string): void {
|
destroyPane(instanceId: PaneInstanceId): void {
|
||||||
const core = this._studio.core
|
const core = this._studio.core
|
||||||
if (!core) {
|
if (!core) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
|
@ -20,6 +20,7 @@ import type * as _coreExports from '@theatre/core/coreExports'
|
||||||
import type {OnDiskState} from '@theatre/core/projects/store/storeTypes'
|
import type {OnDiskState} from '@theatre/core/projects/store/storeTypes'
|
||||||
import type {Deferred} from '@theatre/shared/utils/defer'
|
import type {Deferred} from '@theatre/shared/utils/defer'
|
||||||
import {defer} from '@theatre/shared/utils/defer'
|
import {defer} from '@theatre/shared/utils/defer'
|
||||||
|
import type {ProjectId} from '@theatre/shared/utils/ids'
|
||||||
|
|
||||||
export type CoreExports = typeof _coreExports
|
export type CoreExports = typeof _coreExports
|
||||||
|
|
||||||
|
@ -27,10 +28,10 @@ export class Studio {
|
||||||
readonly ui!: UI
|
readonly ui!: UI
|
||||||
readonly publicApi: IStudio
|
readonly publicApi: IStudio
|
||||||
readonly address: {studioId: string}
|
readonly address: {studioId: string}
|
||||||
readonly _projectsProxy: PointerProxy<Record<string, Project>> =
|
readonly _projectsProxy: PointerProxy<Record<ProjectId, Project>> =
|
||||||
new PointerProxy(new Atom({}).pointer)
|
new PointerProxy(new Atom({}).pointer)
|
||||||
|
|
||||||
readonly projectsP: Pointer<Record<string, Project>> =
|
readonly projectsP: Pointer<Record<ProjectId, Project>> =
|
||||||
this._projectsProxy.pointer
|
this._projectsProxy.pointer
|
||||||
|
|
||||||
private readonly _store = new StudioStore()
|
private readonly _store = new StudioStore()
|
||||||
|
@ -124,7 +125,7 @@ export class Studio {
|
||||||
this._setProjectsP(coreBits.projectsP)
|
this._setProjectsP(coreBits.projectsP)
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setProjectsP(projectsP: Pointer<Record<string, Project>>) {
|
private _setProjectsP(projectsP: Pointer<Record<ProjectId, Project>>) {
|
||||||
this._projectsProxy.setPointer(projectsP)
|
this._projectsProxy.setPointer(projectsP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,6 +219,6 @@ export class Studio {
|
||||||
}
|
}
|
||||||
|
|
||||||
createContentOfSaveFile(projectId: string): OnDiskState {
|
createContentOfSaveFile(projectId: string): OnDiskState {
|
||||||
return this._store.createContentOfSaveFile(projectId)
|
return this._store.createContentOfSaveFile(projectId as ProjectId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import type {OnDiskState} from '@theatre/core/projects/store/storeTypes'
|
||||||
import {generateDiskStateRevision} from './generateDiskStateRevision'
|
import {generateDiskStateRevision} from './generateDiskStateRevision'
|
||||||
|
|
||||||
import createTransactionPrivateApi from './createTransactionPrivateApi'
|
import createTransactionPrivateApi from './createTransactionPrivateApi'
|
||||||
|
import type {ProjectId} from '@theatre/shared/utils/ids'
|
||||||
|
|
||||||
export type Drafts = {
|
export type Drafts = {
|
||||||
historic: Draft<StudioHistoricState>
|
historic: Draft<StudioHistoricState>
|
||||||
|
@ -173,7 +174,7 @@ export default class StudioStore {
|
||||||
this._reduxStore.dispatch(studioActions.historic.redo())
|
this._reduxStore.dispatch(studioActions.historic.redo())
|
||||||
}
|
}
|
||||||
|
|
||||||
createContentOfSaveFile(projectId: string): OnDiskState {
|
createContentOfSaveFile(projectId: ProjectId): OnDiskState {
|
||||||
const projectState =
|
const projectState =
|
||||||
this._reduxStore.getState().$persistent.historic.innerState.coreByProject[
|
this._reduxStore.getState().$persistent.historic.innerState.coreByProject[
|
||||||
projectId
|
projectId
|
||||||
|
|
|
@ -16,6 +16,7 @@ import getStudio from './getStudio'
|
||||||
import type React from 'react'
|
import type React from 'react'
|
||||||
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'
|
||||||
|
import type {PaneInstanceId, ProjectId} from '@theatre/shared/utils/ids'
|
||||||
|
|
||||||
export interface ITransactionAPI {
|
export interface ITransactionAPI {
|
||||||
/**
|
/**
|
||||||
|
@ -116,7 +117,7 @@ export interface IExtension {
|
||||||
|
|
||||||
export type PaneInstance<ClassName extends string> = {
|
export type PaneInstance<ClassName extends string> = {
|
||||||
extensionId: string
|
extensionId: string
|
||||||
instanceId: string
|
instanceId: PaneInstanceId
|
||||||
definition: PaneClassDefinition
|
definition: PaneClassDefinition
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -471,10 +472,12 @@ export default class TheatreStudio implements IStudio {
|
||||||
}
|
}
|
||||||
|
|
||||||
destroyPane(paneId: string): void {
|
destroyPane(paneId: string): void {
|
||||||
return getStudio().paneManager.destroyPane(paneId)
|
return getStudio().paneManager.destroyPane(paneId as PaneInstanceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
createContentOfSaveFile(projectId: string): Record<string, unknown> {
|
createContentOfSaveFile(projectId: string): Record<string, unknown> {
|
||||||
return getStudio().createContentOfSaveFile(projectId) as $IntentionalAny
|
return getStudio().createContentOfSaveFile(
|
||||||
|
projectId as ProjectId,
|
||||||
|
) as $IntentionalAny
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {val} from '@theatre/dataverse'
|
import {val} from '@theatre/dataverse'
|
||||||
import {usePrism} from '@theatre/react'
|
import {usePrism} from '@theatre/react'
|
||||||
|
import type {UIPanelId} from '@theatre/shared/utils/ids'
|
||||||
import type {$IntentionalAny, VoidFn} from '@theatre/shared/utils/types'
|
import type {$IntentionalAny, VoidFn} from '@theatre/shared/utils/types'
|
||||||
import getStudio from '@theatre/studio/getStudio'
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
import type {PanelPosition} from '@theatre/studio/store/types'
|
import type {PanelPosition} from '@theatre/studio/store/types'
|
||||||
|
@ -8,7 +9,7 @@ import React, {useContext} from 'react'
|
||||||
import useWindowSize from 'react-use/esm/useWindowSize'
|
import useWindowSize from 'react-use/esm/useWindowSize'
|
||||||
|
|
||||||
type PanelStuff = {
|
type PanelStuff = {
|
||||||
panelId: string
|
panelId: UIPanelId
|
||||||
dims: {
|
dims: {
|
||||||
width: number
|
width: number
|
||||||
height: number
|
height: number
|
||||||
|
@ -64,7 +65,7 @@ const PanelContext = React.createContext<PanelStuff>(null as $IntentionalAny)
|
||||||
export const usePanel = () => useContext(PanelContext)
|
export const usePanel = () => useContext(PanelContext)
|
||||||
|
|
||||||
const BasePanel: React.FC<{
|
const BasePanel: React.FC<{
|
||||||
panelId: string
|
panelId: UIPanelId
|
||||||
defaultPosition: PanelPosition
|
defaultPosition: PanelPosition
|
||||||
minDims: {width: number; height: number}
|
minDims: {width: number; height: number}
|
||||||
}> = ({panelId, children, defaultPosition, minDims}) => {
|
}> = ({panelId, children, defaultPosition, minDims}) => {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {ErrorBoundary} from 'react-error-boundary'
|
||||||
import {IoClose} from 'react-icons/all'
|
import {IoClose} from 'react-icons/all'
|
||||||
import getStudio from '@theatre/studio/getStudio'
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
import {panelZIndexes} from '@theatre/studio/panels/BasePanel/common'
|
import {panelZIndexes} from '@theatre/studio/panels/BasePanel/common'
|
||||||
|
import type {PaneInstanceId, UIPanelId} from '@theatre/shared/utils/ids'
|
||||||
|
|
||||||
const defaultPosition: PanelPosition = {
|
const defaultPosition: PanelPosition = {
|
||||||
edges: {
|
edges: {
|
||||||
|
@ -28,7 +29,7 @@ const ExtensionPaneWrapper: React.FC<{
|
||||||
}> = ({paneInstance}) => {
|
}> = ({paneInstance}) => {
|
||||||
return (
|
return (
|
||||||
<BasePanel
|
<BasePanel
|
||||||
panelId={`pane-${paneInstance.instanceId}`}
|
panelId={`pane-${paneInstance.instanceId}` as UIPanelId}
|
||||||
defaultPosition={defaultPosition}
|
defaultPosition={defaultPosition}
|
||||||
minDims={minDims}
|
minDims={minDims}
|
||||||
>
|
>
|
||||||
|
@ -137,7 +138,9 @@ const Content: React.FC<{paneInstance: PaneInstance<$FixMe>}> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const Comp = paneInstance.definition.component
|
const Comp = paneInstance.definition.component
|
||||||
const closePane = useCallback(() => {
|
const closePane = useCallback(() => {
|
||||||
getStudio().paneManager.destroyPane(paneInstance.instanceId)
|
getStudio().paneManager.destroyPane(
|
||||||
|
paneInstance.instanceId as PaneInstanceId,
|
||||||
|
)
|
||||||
}, [paneInstance])
|
}, [paneInstance])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React, {useMemo} from 'react'
|
import React, {useMemo} from 'react'
|
||||||
import DeterminePropEditor from './propEditors/DeterminePropEditor'
|
import DeterminePropEditor from './propEditors/DeterminePropEditor'
|
||||||
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
||||||
|
import type {Pointer} from '@theatre/dataverse'
|
||||||
|
import type {$FixMe} from '@theatre/shared/utils/types'
|
||||||
|
|
||||||
const ObjectDetails: React.FC<{
|
const ObjectDetails: React.FC<{
|
||||||
objects: SheetObject[]
|
objects: SheetObject[]
|
||||||
|
@ -13,9 +15,9 @@ const ObjectDetails: React.FC<{
|
||||||
<DeterminePropEditor
|
<DeterminePropEditor
|
||||||
key={key}
|
key={key}
|
||||||
obj={obj}
|
obj={obj}
|
||||||
pointerToProp={obj.propsP}
|
pointerToProp={obj.propsP as Pointer<$FixMe>}
|
||||||
propConfig={obj.template.config}
|
propConfig={obj.template.config}
|
||||||
depth={1}
|
visualIndentation={1}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import useTooltip from '@theatre/studio/uiComponents/Popover/useTooltip'
|
||||||
import BasicTooltip from '@theatre/studio/uiComponents/Popover/BasicTooltip'
|
import BasicTooltip from '@theatre/studio/uiComponents/Popover/BasicTooltip'
|
||||||
import type {$FixMe} from '@theatre/shared/utils/types'
|
import type {$FixMe} from '@theatre/shared/utils/types'
|
||||||
import DetailPanelButton from '@theatre/studio/uiComponents/DetailPanelButton'
|
import DetailPanelButton from '@theatre/studio/uiComponents/DetailPanelButton'
|
||||||
|
import type {ProjectId} from '@theatre/shared/utils/ids'
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
|
@ -37,7 +38,7 @@ const ChooseStateRow = styled.div`
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const StateConflictRow: React.FC<{projectId: string}> = ({projectId}) => {
|
const StateConflictRow: React.FC<{projectId: ProjectId}> = ({projectId}) => {
|
||||||
const loadingState = useVal(
|
const loadingState = useVal(
|
||||||
getStudio().atomP.ephemeral.coreByProject[projectId].loadingState,
|
getStudio().atomP.ephemeral.coreByProject[projectId].loadingState,
|
||||||
)
|
)
|
||||||
|
@ -52,7 +53,7 @@ const StateConflictRow: React.FC<{projectId: string}> = ({projectId}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const InConflict: React.FC<{
|
const InConflict: React.FC<{
|
||||||
projectId: string
|
projectId: ProjectId
|
||||||
loadingState: Extract<
|
loadingState: Extract<
|
||||||
ProjectEphemeralState['loadingState'],
|
ProjectEphemeralState['loadingState'],
|
||||||
{type: 'browserStateIsNotBasedOnDiskState'}
|
{type: 'browserStateIsNotBasedOnDiskState'}
|
||||||
|
|
|
@ -61,7 +61,7 @@ const SubProps = styled.div<{depth: number; lastSubIsComposite: boolean}>`
|
||||||
|
|
||||||
const CompoundPropEditor: IPropEditorFC<
|
const CompoundPropEditor: IPropEditorFC<
|
||||||
PropTypeConfig_Compound<$IntentionalAny>
|
PropTypeConfig_Compound<$IntentionalAny>
|
||||||
> = ({pointerToProp, obj, propConfig, depth}) => {
|
> = ({pointerToProp, obj, propConfig, visualIndentation: depth}) => {
|
||||||
const propName = propConfig.label ?? last(getPointerParts(pointerToProp).path)
|
const propName = propConfig.label ?? last(getPointerParts(pointerToProp).path)
|
||||||
|
|
||||||
const allSubs = Object.entries(propConfig.props)
|
const allSubs = Object.entries(propConfig.props)
|
||||||
|
@ -154,7 +154,7 @@ const CompoundPropEditor: IPropEditorFC<
|
||||||
propConfig={subPropConfig}
|
propConfig={subPropConfig}
|
||||||
pointerToProp={pointerToProp[subPropKey]}
|
pointerToProp={pointerToProp[subPropKey]}
|
||||||
obj={obj}
|
obj={obj}
|
||||||
depth={depth + 1}
|
visualIndentation={depth + 1}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,15 +9,15 @@ import NumberPropEditor from './NumberPropEditor'
|
||||||
import StringLiteralPropEditor from './StringLiteralPropEditor'
|
import StringLiteralPropEditor from './StringLiteralPropEditor'
|
||||||
import StringPropEditor from './StringPropEditor'
|
import StringPropEditor from './StringPropEditor'
|
||||||
import RgbaPropEditor from './RgbaPropEditor'
|
import RgbaPropEditor from './RgbaPropEditor'
|
||||||
|
import type {UnknownShorthandCompoundProps} from '@theatre/core/propTypes/internals'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the PropTypeConfig by path. Assumes `path` is a valid prop path and that
|
* Returns the PropTypeConfig by path. Assumes `path` is a valid prop path and that
|
||||||
* it exists in obj.
|
* it exists in obj.
|
||||||
*/
|
*/
|
||||||
export const getPropTypeByPointer = (
|
export function getPropTypeByPointer<
|
||||||
pointerToProp: SheetObject['propsP'],
|
Props extends UnknownShorthandCompoundProps,
|
||||||
obj: SheetObject,
|
>(pointerToProp: SheetObject['propsP'], obj: SheetObject): PropTypeConfig {
|
||||||
): PropTypeConfig => {
|
|
||||||
const rootConf = obj.template.config
|
const rootConf = obj.template.config
|
||||||
|
|
||||||
const p = getPointerParts(pointerToProp).path
|
const p = getPointerParts(pointerToProp).path
|
||||||
|
@ -67,7 +67,7 @@ type IPropEditorByPropType = {
|
||||||
obj: SheetObject
|
obj: SheetObject
|
||||||
pointerToProp: Pointer<PropConfigByType<K>['valueType']>
|
pointerToProp: Pointer<PropConfigByType<K>['valueType']>
|
||||||
propConfig: PropConfigByType<K>
|
propConfig: PropConfigByType<K>
|
||||||
depth: number
|
visualIndentation: number
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,12 +81,20 @@ const propEditorByPropType: IPropEditorByPropType = {
|
||||||
rgba: RgbaPropEditor,
|
rgba: RgbaPropEditor,
|
||||||
}
|
}
|
||||||
|
|
||||||
const DeterminePropEditor: React.FC<{
|
export type IEditablePropertyProps<K extends PropTypeConfig['type']> = {
|
||||||
obj: SheetObject
|
obj: SheetObject
|
||||||
pointerToProp: SheetObject['propsP']
|
pointerToProp: Pointer<PropConfigByType<K>['valueType']>
|
||||||
propConfig?: PropTypeConfig
|
propConfig: PropConfigByType<K>
|
||||||
depth: number
|
}
|
||||||
}> = (p) => {
|
|
||||||
|
type IDeterminePropEditorProps<K extends PropTypeConfig['type']> =
|
||||||
|
IEditablePropertyProps<K> & {
|
||||||
|
visualIndentation: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeterminePropEditor: React.FC<
|
||||||
|
IDeterminePropEditorProps<PropTypeConfig['type']>
|
||||||
|
> = (p) => {
|
||||||
const propConfig =
|
const propConfig =
|
||||||
p.propConfig ?? getPropTypeByPointer(p.pointerToProp, p.obj)
|
p.propConfig ?? getPropTypeByPointer(p.pointerToProp, p.obj)
|
||||||
|
|
||||||
|
@ -95,7 +103,7 @@ const DeterminePropEditor: React.FC<{
|
||||||
return (
|
return (
|
||||||
<PropEditor
|
<PropEditor
|
||||||
obj={p.obj}
|
obj={p.obj}
|
||||||
depth={p.depth}
|
visualIndentation={p.visualIndentation}
|
||||||
// @ts-expect-error This is fine
|
// @ts-expect-error This is fine
|
||||||
pointerToProp={p.pointerToProp}
|
pointerToProp={p.pointerToProp}
|
||||||
// @ts-expect-error This is fine
|
// @ts-expect-error This is fine
|
||||||
|
|
|
@ -8,5 +8,5 @@ export type IPropEditorFC<TPropTypeConfig extends IBasePropType<string, any>> =
|
||||||
propConfig: TPropTypeConfig
|
propConfig: TPropTypeConfig
|
||||||
pointerToProp: Pointer<TPropTypeConfig['valueType']>
|
pointerToProp: Pointer<TPropTypeConfig['valueType']>
|
||||||
obj: SheetObject
|
obj: SheetObject
|
||||||
depth: number
|
visualIndentation: number
|
||||||
}>
|
}>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import type Scrub from '@theatre/studio/Scrub'
|
||||||
import type {IContextMenuItem} from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
|
import type {IContextMenuItem} from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
|
||||||
import getDeep from '@theatre/shared/utils/getDeep'
|
import getDeep from '@theatre/shared/utils/getDeep'
|
||||||
import {usePrism} from '@theatre/react'
|
import {usePrism} from '@theatre/react'
|
||||||
import type {$FixMe, SerializablePrimitive} from '@theatre/shared/utils/types'
|
import type {SerializablePrimitive} from '@theatre/shared/utils/types'
|
||||||
import {getPointerParts, prism, val} from '@theatre/dataverse'
|
import {getPointerParts, prism, val} from '@theatre/dataverse'
|
||||||
import type {Pointer} from '@theatre/dataverse'
|
import type {Pointer} from '@theatre/dataverse'
|
||||||
import get from 'lodash-es/get'
|
import get from 'lodash-es/get'
|
||||||
|
@ -15,6 +15,7 @@ import DefaultOrStaticValueIndicator from './DefaultValueIndicator'
|
||||||
import NextPrevKeyframeCursors from './NextPrevKeyframeCursors'
|
import NextPrevKeyframeCursors from './NextPrevKeyframeCursors'
|
||||||
import type {PropTypeConfig} from '@theatre/core/propTypes'
|
import type {PropTypeConfig} from '@theatre/core/propTypes'
|
||||||
import {isPropConfSequencable} from '@theatre/shared/propTypes/utils'
|
import {isPropConfSequencable} from '@theatre/shared/propTypes/utils'
|
||||||
|
import type {SequenceTrackId} from '@theatre/shared/utils/ids'
|
||||||
|
|
||||||
interface CommonStuff<T> {
|
interface CommonStuff<T> {
|
||||||
value: T
|
value: T
|
||||||
|
@ -147,7 +148,7 @@ export function useEditingToolsForPrimitiveProp<
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const sequenceTrcackId = possibleSequenceTrackId as $FixMe as string
|
const sequenceTrackId = possibleSequenceTrackId as SequenceTrackId
|
||||||
const nearbyKeyframes = prism.sub(
|
const nearbyKeyframes = prism.sub(
|
||||||
'lcr',
|
'lcr',
|
||||||
(): NearbyKeyframes => {
|
(): NearbyKeyframes => {
|
||||||
|
@ -155,7 +156,7 @@ export function useEditingToolsForPrimitiveProp<
|
||||||
obj.template.project.pointers.historic.sheetsById[
|
obj.template.project.pointers.historic.sheetsById[
|
||||||
obj.address.sheetId
|
obj.address.sheetId
|
||||||
].sequence.tracksByObject[obj.address.objectKey].trackData[
|
].sequence.tracksByObject[obj.address.objectKey].trackData[
|
||||||
sequenceTrcackId
|
sequenceTrackId
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
if (!track || track.keyframes.length === 0) return {}
|
if (!track || track.keyframes.length === 0) return {}
|
||||||
|
@ -186,7 +187,7 @@ export function useEditingToolsForPrimitiveProp<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[sequenceTrcackId],
|
[sequenceTrackId],
|
||||||
)
|
)
|
||||||
|
|
||||||
let shade: Shade
|
let shade: Shade
|
||||||
|
|
|
@ -26,7 +26,7 @@ const ObjectsList: React.FC<{
|
||||||
<ObjectItem
|
<ObjectItem
|
||||||
depth={depth}
|
depth={depth}
|
||||||
key={'objectPath(' + objectPath + ')'}
|
key={'objectPath(' + objectPath + ')'}
|
||||||
sheetObject={object}
|
sheetObject={object!}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -44,7 +44,10 @@ const BasicKeyframedTrack: React.FC<BasicKeyframedTracksProps> = React.memo(
|
||||||
selection: val(selectionAtom.pointer.current),
|
selection: val(selectionAtom.pointer.current),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {selectedKeyframeIds: {}, selection: undefined}
|
return {
|
||||||
|
selectedKeyframeIds: {},
|
||||||
|
selection: undefined,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [layoutP, leaf.trackId])
|
}, [layoutP, leaf.trackId])
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ const HitZone = styled.div`
|
||||||
type IKeyframeDotProps = IKeyframeEditorProps
|
type IKeyframeDotProps = IKeyframeEditorProps
|
||||||
|
|
||||||
/** The ◆ you can grab onto in "keyframe editor" (aka "dope sheet" in other programs) */
|
/** The ◆ you can grab onto in "keyframe editor" (aka "dope sheet" in other programs) */
|
||||||
const KeyframeDot: React.FC<IKeyframeDotProps> = (props) => {
|
const KeyframeDot: React.VFC<IKeyframeDotProps> = (props) => {
|
||||||
const [ref, node] = useRefAndState<HTMLDivElement | null>(null)
|
const [ref, node] = useRefAndState<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
const [isDragging] = useDragKeyframe(node, props)
|
const [isDragging] = useDragKeyframe(node, props)
|
||||||
|
|
|
@ -79,8 +79,8 @@ function useCaptureSelection(
|
||||||
|
|
||||||
val(layoutP.selectionAtom).setState({current: undefined})
|
val(layoutP.selectionAtom).setState({current: undefined})
|
||||||
},
|
},
|
||||||
onDrag(dx, dy, event) {
|
onDrag(_dx, _dy, event) {
|
||||||
const state = ref.current!
|
// const state = ref.current!
|
||||||
const rect = containerNode!.getBoundingClientRect()
|
const rect = containerNode!.getBoundingClientRect()
|
||||||
|
|
||||||
const posInScaledSpace = event.clientX - rect.left
|
const posInScaledSpace = event.clientX - rect.left
|
||||||
|
@ -97,25 +97,13 @@ function useCaptureSelection(
|
||||||
const selection = utils.boundsToSelection(layoutP, ref.current)
|
const selection = utils.boundsToSelection(layoutP, ref.current)
|
||||||
val(layoutP.selectionAtom).setState({current: selection})
|
val(layoutP.selectionAtom).setState({current: selection})
|
||||||
},
|
},
|
||||||
onDragEnd(dragHappened) {
|
onDragEnd(_dragHappened) {
|
||||||
ref.current = null
|
ref.current = null
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [layoutP, containerNode, ref]),
|
}, [layoutP, containerNode, ref]),
|
||||||
)
|
)
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (!containerNode) return
|
|
||||||
// const onClick = () => {
|
|
||||||
|
|
||||||
// }
|
|
||||||
// containerNode.addEventListener('click', onClick)
|
|
||||||
|
|
||||||
// return () => {
|
|
||||||
// containerNode.removeEventListener('click', onClick)
|
|
||||||
// }
|
|
||||||
// }, [containerNode])
|
|
||||||
|
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,16 +119,11 @@ namespace utils {
|
||||||
primitiveProp(layoutP, leaf, bounds, selection) {
|
primitiveProp(layoutP, leaf, bounds, selection) {
|
||||||
const {sheetObject, trackId} = leaf
|
const {sheetObject, trackId} = leaf
|
||||||
const trackData = val(
|
const trackData = val(
|
||||||
getStudio()!.atomP.historic.coreByProject[sheetObject.address.projectId]
|
getStudio().atomP.historic.coreByProject[sheetObject.address.projectId]
|
||||||
.sheetsById[sheetObject.address.sheetId].sequence.tracksByObject[
|
.sheetsById[sheetObject.address.sheetId].sequence.tracksByObject[
|
||||||
sheetObject.address.objectKey
|
sheetObject.address.objectKey
|
||||||
].trackData[trackId],
|
].trackData[trackId],
|
||||||
)!
|
)!
|
||||||
const toCollect = trackData!.keyframes.filter(
|
|
||||||
(kf) =>
|
|
||||||
kf.position >= bounds.positions[0] &&
|
|
||||||
kf.position <= bounds.positions[1],
|
|
||||||
)
|
|
||||||
|
|
||||||
for (const kf of trackData.keyframes) {
|
for (const kf of trackData.keyframes) {
|
||||||
if (kf.position <= bounds.positions[0]) continue
|
if (kf.position <= bounds.positions[0]) continue
|
||||||
|
|
|
@ -25,7 +25,7 @@ export type ExtremumSpace = {
|
||||||
lock(): VoidFn
|
lock(): VoidFn
|
||||||
}
|
}
|
||||||
|
|
||||||
const BasicKeyframedTrack: React.FC<{
|
const BasicKeyframedTrack: React.VFC<{
|
||||||
layoutP: Pointer<SequenceEditorPanelLayout>
|
layoutP: Pointer<SequenceEditorPanelLayout>
|
||||||
sheetObject: SheetObject
|
sheetObject: SheetObject
|
||||||
pathToProp: PathToProp
|
pathToProp: PathToProp
|
||||||
|
@ -102,7 +102,7 @@ const BasicKeyframedTrack: React.FC<{
|
||||||
sheetObject={sheetObject}
|
sheetObject={sheetObject}
|
||||||
trackId={trackId}
|
trackId={trackId}
|
||||||
isScalar={propConfig.type === 'number'}
|
isScalar={propConfig.type === 'number'}
|
||||||
key={'keyframe-' + kf.id}
|
key={kf.id}
|
||||||
extremumSpace={cachedExtremumSpace.current}
|
extremumSpace={cachedExtremumSpace.current}
|
||||||
color={color}
|
color={color}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -15,7 +15,7 @@ const SVGPath = styled.path`
|
||||||
|
|
||||||
type IProps = Parameters<typeof KeyframeEditor>[0]
|
type IProps = Parameters<typeof KeyframeEditor>[0]
|
||||||
|
|
||||||
const Curve: React.FC<IProps> = (props) => {
|
const Curve: React.VFC<IProps> = (props) => {
|
||||||
const {index, trackData} = props
|
const {index, trackData} = props
|
||||||
const cur = trackData.keyframes[index]
|
const cur = trackData.keyframes[index]
|
||||||
const next = trackData.keyframes[index + 1]
|
const next = trackData.keyframes[index + 1]
|
||||||
|
|
|
@ -49,7 +49,7 @@ type Which = 'left' | 'right'
|
||||||
|
|
||||||
type IProps = Parameters<typeof KeyframeEditor>[0] & {which: Which}
|
type IProps = Parameters<typeof KeyframeEditor>[0] & {which: Which}
|
||||||
|
|
||||||
const CurveHandle: React.FC<IProps> = (props) => {
|
const CurveHandle: React.VFC<IProps> = (props) => {
|
||||||
const [ref, node] = useRefAndState<SVGCircleElement | null>(null)
|
const [ref, node] = useRefAndState<SVGCircleElement | null>(null)
|
||||||
|
|
||||||
const {index, trackData} = props
|
const {index, trackData} = props
|
||||||
|
|
|
@ -55,7 +55,7 @@ const HitZone = styled.circle`
|
||||||
|
|
||||||
type IProps = Parameters<typeof KeyframeEditor>[0] & {which: 'left' | 'right'}
|
type IProps = Parameters<typeof KeyframeEditor>[0] & {which: 'left' | 'right'}
|
||||||
|
|
||||||
const GraphEditorDotNonScalar: React.FC<IProps> = (props) => {
|
const GraphEditorDotNonScalar: React.VFC<IProps> = (props) => {
|
||||||
const [ref, node] = useRefAndState<SVGCircleElement | null>(null)
|
const [ref, node] = useRefAndState<SVGCircleElement | null>(null)
|
||||||
|
|
||||||
const {index, trackData} = props
|
const {index, trackData} = props
|
||||||
|
|
|
@ -55,7 +55,7 @@ const HitZone = styled.circle`
|
||||||
|
|
||||||
type IProps = Parameters<typeof KeyframeEditor>[0]
|
type IProps = Parameters<typeof KeyframeEditor>[0]
|
||||||
|
|
||||||
const GraphEditorDotScalar: React.FC<IProps> = (props) => {
|
const GraphEditorDotScalar: React.VFC<IProps> = (props) => {
|
||||||
const [ref, node] = useRefAndState<SVGCircleElement | null>(null)
|
const [ref, node] = useRefAndState<SVGCircleElement | null>(null)
|
||||||
|
|
||||||
const {index, trackData} = props
|
const {index, trackData} = props
|
||||||
|
|
|
@ -17,7 +17,7 @@ const SVGPath = styled.path`
|
||||||
|
|
||||||
type IProps = Parameters<typeof KeyframeEditor>[0]
|
type IProps = Parameters<typeof KeyframeEditor>[0]
|
||||||
|
|
||||||
const GraphEditorNonScalarDash: React.FC<IProps> = (props) => {
|
const GraphEditorNonScalarDash: React.VFC<IProps> = (props) => {
|
||||||
const {index, trackData} = props
|
const {index, trackData} = props
|
||||||
|
|
||||||
const pathD = `M 0 0 L 1 1`
|
const pathD = `M 0 0 L 1 1`
|
||||||
|
|
|
@ -23,7 +23,7 @@ const Container = styled.g`
|
||||||
|
|
||||||
const noConnector = <></>
|
const noConnector = <></>
|
||||||
|
|
||||||
const KeyframeEditor: React.FC<{
|
type IKeyframeEditorProps = {
|
||||||
index: number
|
index: number
|
||||||
keyframe: Keyframe
|
keyframe: Keyframe
|
||||||
trackData: TrackData
|
trackData: TrackData
|
||||||
|
@ -34,7 +34,9 @@ const KeyframeEditor: React.FC<{
|
||||||
isScalar: boolean
|
isScalar: boolean
|
||||||
color: keyof typeof graphEditorColors
|
color: keyof typeof graphEditorColors
|
||||||
propConfig: PropTypeConfig_AllSimples
|
propConfig: PropTypeConfig_AllSimples
|
||||||
}> = (props) => {
|
}
|
||||||
|
|
||||||
|
const KeyframeEditor: React.VFC<IKeyframeEditorProps> = (props) => {
|
||||||
const {index, trackData, isScalar} = props
|
const {index, trackData, isScalar} = props
|
||||||
const cur = trackData.keyframes[index]
|
const cur = trackData.keyframes[index]
|
||||||
const next = trackData.keyframes[index + 1]
|
const next = trackData.keyframes[index + 1]
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {
|
||||||
TitleBar_Piece,
|
TitleBar_Piece,
|
||||||
TitleBar_Punctuation,
|
TitleBar_Punctuation,
|
||||||
} from '@theatre/studio/panels/BasePanel/common'
|
} from '@theatre/studio/panels/BasePanel/common'
|
||||||
|
import type {UIPanelId} from '@theatre/shared/utils/ids'
|
||||||
|
|
||||||
const Container = styled(PanelWrapper)`
|
const Container = styled(PanelWrapper)`
|
||||||
z-index: ${panelZIndexes.sequenceEditorPanel};
|
z-index: ${panelZIndexes.sequenceEditorPanel};
|
||||||
|
@ -58,7 +59,7 @@ export const zIndexes = (() => {
|
||||||
// sort the z-indexes
|
// sort the z-indexes
|
||||||
let i = -1
|
let i = -1
|
||||||
for (const key of Object.keys(s)) {
|
for (const key of Object.keys(s)) {
|
||||||
s[key as unknown as keyof typeof s] = i
|
s[key] = i
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,10 +84,10 @@ const defaultPosition: PanelPosition = {
|
||||||
|
|
||||||
const minDims = {width: 800, height: 200}
|
const minDims = {width: 800, height: 200}
|
||||||
|
|
||||||
const SequenceEditorPanel: React.FC<{}> = (props) => {
|
const SequenceEditorPanel: React.VFC<{}> = (props) => {
|
||||||
return (
|
return (
|
||||||
<BasePanel
|
<BasePanel
|
||||||
panelId="sequenceEditor"
|
panelId={'sequenceEditor' as UIPanelId}
|
||||||
defaultPosition={defaultPosition}
|
defaultPosition={defaultPosition}
|
||||||
minDims={minDims}
|
minDims={minDims}
|
||||||
>
|
>
|
||||||
|
@ -95,7 +96,7 @@ const SequenceEditorPanel: React.FC<{}> = (props) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Content: React.FC<{}> = () => {
|
const Content: React.VFC<{}> = () => {
|
||||||
const {dims} = usePanel()
|
const {dims} = usePanel()
|
||||||
|
|
||||||
return usePrism(() => {
|
return usePrism(() => {
|
||||||
|
|
|
@ -14,6 +14,11 @@ import {Atom, prism, val} from '@theatre/dataverse'
|
||||||
import type {SequenceEditorTree} from './tree'
|
import type {SequenceEditorTree} from './tree'
|
||||||
import {calculateSequenceEditorTree} from './tree'
|
import {calculateSequenceEditorTree} from './tree'
|
||||||
import {clamp} from 'lodash-es'
|
import {clamp} from 'lodash-es'
|
||||||
|
import type {
|
||||||
|
KeyframeId,
|
||||||
|
ObjectAddressKey,
|
||||||
|
SequenceTrackId,
|
||||||
|
} from '@theatre/shared/utils/ids'
|
||||||
|
|
||||||
// A Side is either the left side of the panel or the right side
|
// A Side is either the left side of the panel or the right side
|
||||||
type DimsOfPanelPart = {
|
type DimsOfPanelPart = {
|
||||||
|
@ -41,20 +46,20 @@ export type PanelDims = {
|
||||||
export type DopeSheetSelection = {
|
export type DopeSheetSelection = {
|
||||||
type: 'DopeSheetSelection'
|
type: 'DopeSheetSelection'
|
||||||
byObjectKey: StrictRecord<
|
byObjectKey: StrictRecord<
|
||||||
string,
|
ObjectAddressKey,
|
||||||
{
|
{
|
||||||
byTrackId: StrictRecord<
|
byTrackId: StrictRecord<
|
||||||
string,
|
SequenceTrackId,
|
||||||
{
|
{
|
||||||
byKeyframeId: StrictRecord<string, true>
|
byKeyframeId: StrictRecord<KeyframeId, true>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
getDragHandlers(
|
getDragHandlers(
|
||||||
origin: PropAddress & {
|
origin: PropAddress & {
|
||||||
trackId: string
|
trackId: SequenceTrackId
|
||||||
keyframeId: string
|
keyframeId: KeyframeId
|
||||||
positionAtStartOfDrag: number
|
positionAtStartOfDrag: number
|
||||||
domNode: Element
|
domNode: Element
|
||||||
},
|
},
|
||||||
|
@ -156,8 +161,8 @@ export type SequenceEditorPanelLayout = {
|
||||||
selectionAtom: Atom<{current?: DopeSheetSelection}>
|
selectionAtom: Atom<{current?: DopeSheetSelection}>
|
||||||
}
|
}
|
||||||
|
|
||||||
// type UnitSpaceProression = Nominal<number, 'unitSpaceProgression'>
|
// type UnitSpaceProression = number
|
||||||
// type ClippedSpaceProgression = Nominal<number, 'ClippedSpaceProgression'>
|
// type ClippedSpaceProgression = number
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This means the left side of the panel is 20% of its width, and the
|
* This means the left side of the panel is 20% of its width, and the
|
||||||
|
|
|
@ -87,9 +87,11 @@ export const calculateSequenceEditorTree = (
|
||||||
topSoFar += tree.nodeHeight
|
topSoFar += tree.nodeHeight
|
||||||
nSoFar += 1
|
nSoFar += 1
|
||||||
|
|
||||||
for (const [_, sheetObject] of Object.entries(val(sheet.objectsP))) {
|
for (const sheetObject of Object.values(val(sheet.objectsP))) {
|
||||||
|
if (sheetObject) {
|
||||||
addObject(sheetObject, tree.children, tree.depth + 1)
|
addObject(sheetObject, tree.children, tree.depth + 1)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
tree.heightIncludingChildren = topSoFar - tree.top
|
tree.heightIncludingChildren = topSoFar - tree.top
|
||||||
|
|
||||||
function addObject(
|
function addObject(
|
||||||
|
|
|
@ -3,7 +3,9 @@ import type Sequence from '@theatre/core/sequences/Sequence'
|
||||||
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
||||||
import type Sheet from '@theatre/core/sheets/Sheet'
|
import type Sheet from '@theatre/core/sheets/Sheet'
|
||||||
import {val} from '@theatre/dataverse'
|
import {val} from '@theatre/dataverse'
|
||||||
|
import type {$IntentionalAny} from '@theatre/dataverse/src/types'
|
||||||
import {isSheet, isSheetObject} from '@theatre/shared/instanceTypes'
|
import {isSheet, isSheetObject} from '@theatre/shared/instanceTypes'
|
||||||
|
import type {SheetId} from '@theatre/shared/utils/ids'
|
||||||
import {uniq} from 'lodash-es'
|
import {uniq} from 'lodash-es'
|
||||||
import getStudio from './getStudio'
|
import getStudio from './getStudio'
|
||||||
import type {OutlineSelectable, OutlineSelection} from './store/types'
|
import type {OutlineSelectable, OutlineSelection} from './store/types'
|
||||||
|
@ -46,7 +48,7 @@ export const getSelectedInstanceOfSheetId = (
|
||||||
]
|
]
|
||||||
|
|
||||||
const instanceId = val(
|
const instanceId = val(
|
||||||
projectStateP.stateBySheetId[selectedSheetId].selectedInstanceId,
|
projectStateP.stateBySheetId[selectedSheetId as SheetId].selectedInstanceId,
|
||||||
)
|
)
|
||||||
|
|
||||||
const template = val(project.sheetTemplatesP[selectedSheetId])
|
const template = val(project.sheetTemplatesP[selectedSheetId])
|
||||||
|
@ -59,10 +61,14 @@ export const getSelectedInstanceOfSheetId = (
|
||||||
// @todo #perf this will update every time an instance is added/removed.
|
// @todo #perf this will update every time an instance is added/removed.
|
||||||
const allInstances = val(template.instancesP)
|
const allInstances = val(template.instancesP)
|
||||||
|
|
||||||
return allInstances[Object.keys(allInstances)[0]]
|
return allInstances[keys(allInstances)[0]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function keys<T extends object>(obj: T): Exclude<keyof T, symbol | number>[] {
|
||||||
|
return Object.keys(obj) as $IntentionalAny
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* component instances could come and go all the time. This hook
|
* component instances could come and go all the time. This hook
|
||||||
* makes sure we don't cause re-renders
|
* makes sure we don't cause re-renders
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type {
|
import type {
|
||||||
|
HistoricPositionalSequence,
|
||||||
Keyframe,
|
Keyframe,
|
||||||
SheetState_Historic,
|
SheetState_Historic,
|
||||||
} from '@theatre/core/projects/store/types/SheetState_Historic'
|
} from '@theatre/core/projects/store/types/SheetState_Historic'
|
||||||
|
@ -11,7 +12,11 @@ import type {
|
||||||
WithoutSheetInstance,
|
WithoutSheetInstance,
|
||||||
} from '@theatre/shared/utils/addresses'
|
} from '@theatre/shared/utils/addresses'
|
||||||
import {encodePathToProp} from '@theatre/shared/utils/addresses'
|
import {encodePathToProp} from '@theatre/shared/utils/addresses'
|
||||||
import type {KeyframeId} from '@theatre/shared/utils/ids'
|
import type {
|
||||||
|
KeyframeId,
|
||||||
|
SequenceTrackId,
|
||||||
|
UIPanelId,
|
||||||
|
} from '@theatre/shared/utils/ids'
|
||||||
import {
|
import {
|
||||||
generateKeyframeId,
|
generateKeyframeId,
|
||||||
generateSequenceTrackId,
|
generateSequenceTrackId,
|
||||||
|
@ -72,7 +77,7 @@ namespace stateEditors {
|
||||||
export namespace historic {
|
export namespace historic {
|
||||||
export namespace panelPositions {
|
export namespace panelPositions {
|
||||||
export function setPanelPosition(p: {
|
export function setPanelPosition(p: {
|
||||||
panelId: string
|
panelId: UIPanelId
|
||||||
position: PanelPosition
|
position: PanelPosition
|
||||||
}) {
|
}) {
|
||||||
const h = drafts().historic
|
const h = drafts().historic
|
||||||
|
@ -429,7 +434,9 @@ namespace stateEditors {
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace sequence {
|
export namespace sequence {
|
||||||
export function _ensure(p: WithoutSheetInstance<SheetAddress>) {
|
export function _ensure(
|
||||||
|
p: WithoutSheetInstance<SheetAddress>,
|
||||||
|
): HistoricPositionalSequence {
|
||||||
const s = stateEditors.coreByProject.historic.sheetsById._ensure(p)
|
const s = stateEditors.coreByProject.historic.sheetsById._ensure(p)
|
||||||
s.sequence ??= {
|
s.sequence ??= {
|
||||||
subUnitsPerUnit: 30,
|
subUnitsPerUnit: 30,
|
||||||
|
@ -529,7 +536,9 @@ namespace stateEditors {
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getTrack(
|
function _getTrack(
|
||||||
p: WithoutSheetInstance<SheetObjectAddress> & {trackId: string},
|
p: WithoutSheetInstance<SheetObjectAddress> & {
|
||||||
|
trackId: SequenceTrackId
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
return _ensureTracksOfObject(p).trackData[p.trackId]
|
return _ensureTracksOfObject(p).trackData[p.trackId]
|
||||||
}
|
}
|
||||||
|
@ -540,7 +549,7 @@ namespace stateEditors {
|
||||||
*/
|
*/
|
||||||
export function setKeyframeAtPosition<T>(
|
export function setKeyframeAtPosition<T>(
|
||||||
p: WithoutSheetInstance<SheetObjectAddress> & {
|
p: WithoutSheetInstance<SheetObjectAddress> & {
|
||||||
trackId: string
|
trackId: SequenceTrackId
|
||||||
position: number
|
position: number
|
||||||
handles?: [number, number, number, number]
|
handles?: [number, number, number, number]
|
||||||
value: T
|
value: T
|
||||||
|
@ -585,7 +594,7 @@ namespace stateEditors {
|
||||||
|
|
||||||
export function unsetKeyframeAtPosition(
|
export function unsetKeyframeAtPosition(
|
||||||
p: WithoutSheetInstance<SheetObjectAddress> & {
|
p: WithoutSheetInstance<SheetObjectAddress> & {
|
||||||
trackId: string
|
trackId: SequenceTrackId
|
||||||
position: number
|
position: number
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
@ -604,7 +613,7 @@ namespace stateEditors {
|
||||||
|
|
||||||
export function transformKeyframes(
|
export function transformKeyframes(
|
||||||
p: WithoutSheetInstance<SheetObjectAddress> & {
|
p: WithoutSheetInstance<SheetObjectAddress> & {
|
||||||
trackId: string
|
trackId: SequenceTrackId
|
||||||
keyframeIds: KeyframeId[]
|
keyframeIds: KeyframeId[]
|
||||||
translate: number
|
translate: number
|
||||||
scale: number
|
scale: number
|
||||||
|
@ -633,7 +642,7 @@ namespace stateEditors {
|
||||||
|
|
||||||
export function deleteKeyframes(
|
export function deleteKeyframes(
|
||||||
p: WithoutSheetInstance<SheetObjectAddress> & {
|
p: WithoutSheetInstance<SheetObjectAddress> & {
|
||||||
trackId: string
|
trackId: SequenceTrackId
|
||||||
keyframeIds: KeyframeId[]
|
keyframeIds: KeyframeId[]
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
@ -645,9 +654,9 @@ namespace stateEditors {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function replaceKeyframes<T>(
|
export function replaceKeyframes(
|
||||||
p: WithoutSheetInstance<SheetObjectAddress> & {
|
p: WithoutSheetInstance<SheetObjectAddress> & {
|
||||||
trackId: string
|
trackId: SequenceTrackId
|
||||||
keyframes: Array<Keyframe>
|
keyframes: Array<Keyframe>
|
||||||
snappingFunction: SnappingFunction
|
snappingFunction: SnappingFunction
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import type {ProjectState} from '@theatre/core/projects/store/storeTypes'
|
import type {ProjectState} from '@theatre/core/projects/store/storeTypes'
|
||||||
import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
|
import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
|
||||||
|
import type {ProjectId} from '@theatre/shared/utils/ids'
|
||||||
import type {IRange, StrictRecord} from '@theatre/shared/utils/types'
|
import type {IRange, StrictRecord} from '@theatre/shared/utils/types'
|
||||||
|
|
||||||
export type StudioAhistoricState = {
|
export type StudioAhistoricState = {
|
||||||
|
@ -50,5 +51,5 @@ export type StudioAhistoricState = {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
coreByProject: {[projectId in string]: ProjectState['ahistoric']}
|
coreByProject: {[projectId in ProjectId]: ProjectState['ahistoric']}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,14 @@ import type {StrictRecord} from '@theatre/shared/utils/types'
|
||||||
import type Project from '@theatre/core/projects/Project'
|
import type Project from '@theatre/core/projects/Project'
|
||||||
import type Sheet from '@theatre/core/sheets/Sheet'
|
import type Sheet from '@theatre/core/sheets/Sheet'
|
||||||
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
||||||
|
import type {
|
||||||
|
ObjectAddressKey,
|
||||||
|
PaneInstanceId,
|
||||||
|
ProjectId,
|
||||||
|
SheetId,
|
||||||
|
SheetInstanceId,
|
||||||
|
UIPanelId,
|
||||||
|
} from '@theatre/shared/utils/ids'
|
||||||
|
|
||||||
export type PanelPosition = {
|
export type PanelPosition = {
|
||||||
edges: {
|
edges: {
|
||||||
|
@ -56,37 +64,55 @@ export type OutlineSelectionState =
|
||||||
export type OutlineSelectable = Project | Sheet | SheetObject
|
export type OutlineSelectable = Project | Sheet | SheetObject
|
||||||
export type OutlineSelection = OutlineSelectable[]
|
export type OutlineSelection = OutlineSelectable[]
|
||||||
|
|
||||||
export type PanelInstanceDescriptor = {
|
export type PaneInstanceDescriptor = {
|
||||||
instanceId: string
|
instanceId: PaneInstanceId
|
||||||
paneClass: string
|
paneClass: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StudioHistoricState = {
|
/**
|
||||||
projects: {
|
* See parent {@link StudioHistoricStateProject}.
|
||||||
stateByProjectId: StrictRecord<
|
* See root {@link StudioHistoricState}
|
||||||
string,
|
*/
|
||||||
{
|
export type StudioHistoricStateProjectSheet = {
|
||||||
stateBySheetId: StrictRecord<
|
selectedInstanceId: undefined | SheetInstanceId
|
||||||
string,
|
|
||||||
{
|
|
||||||
selectedInstanceId: undefined | string
|
|
||||||
sequenceEditor: {
|
sequenceEditor: {
|
||||||
selectedPropsByObject: StrictRecord<
|
selectedPropsByObject: StrictRecord<
|
||||||
string,
|
ObjectAddressKey,
|
||||||
StrictRecord<PathToProp_Encoded, keyof typeof graphEditorColors>
|
StrictRecord<PathToProp_Encoded, keyof typeof graphEditorColors>
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
|
||||||
}
|
/** See {@link StudioHistoricState} */
|
||||||
>
|
export type StudioHistoricStateProject = {
|
||||||
|
stateBySheetId: StrictRecord<SheetId, StudioHistoricStateProjectSheet>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link StudioHistoricState} includes both studio and project data, and
|
||||||
|
* contains data changed for an undo/redo history.
|
||||||
|
*
|
||||||
|
* ## Internally
|
||||||
|
*
|
||||||
|
* We use immer `Draft`s to encapsulate this whole state to then be operated
|
||||||
|
* on by each transaction. The derived values from the store will also include
|
||||||
|
* the application of the "temp transactions" stack.
|
||||||
|
*/
|
||||||
|
export type StudioHistoricState = {
|
||||||
|
projects: {
|
||||||
|
stateByProjectId: StrictRecord<ProjectId, StudioHistoricStateProject>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Panels can contain panes */
|
||||||
panels?: Panels
|
panels?: Panels
|
||||||
panelPositions?: {[panelIdOrPaneId in string]?: PanelPosition}
|
/** Panels can contain panes */
|
||||||
panelInstanceDesceriptors: {
|
panelPositions?: {[panelId in UIPanelId]?: PanelPosition}
|
||||||
[instanceId in string]?: PanelInstanceDescriptor
|
// This is misspelled, but I think some users are dependent on the exact shape of this stored JSON
|
||||||
}
|
// So, we cannot easily change it without providing backwards compatibility.
|
||||||
|
panelInstanceDesceriptors: StrictRecord<
|
||||||
|
PaneInstanceId,
|
||||||
|
PaneInstanceDescriptor
|
||||||
|
>
|
||||||
autoKey: boolean
|
autoKey: boolean
|
||||||
coreByProject: {[projectId in string]: ProjectState_Historic}
|
coreByProject: Record<ProjectId, ProjectState_Historic>
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue