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 {
|
||||
/**
|
||||
* @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
|
||||
/**
|
||||
|
|
|
@ -33,6 +33,11 @@ const cachedSubPathPointersWeakMap = new WeakMap<
|
|||
* A wrapper type for the type a `Pointer` points to.
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -53,21 +58,28 @@ export type PointerType<O> = {
|
|||
*
|
||||
* 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 :)
|
||||
*
|
||||
* 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> &
|
||||
(O extends UnindexableTypesForPointer
|
||||
? UnindexablePointer
|
||||
: unknown extends O
|
||||
? UnindexablePointer
|
||||
: O extends (infer T)[]
|
||||
? Pointer<T>[]
|
||||
: O extends {}
|
||||
? {
|
||||
[K in keyof O]-?: Pointer<O[K]>
|
||||
} /*&
|
||||
{[K in string | number]: Pointer<K extends keyof O ? O[K] : undefined>}*/
|
||||
: UnindexablePointer)
|
||||
// `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
|
||||
: unknown extends O
|
||||
? UnindexablePointer
|
||||
: O extends (infer T)[]
|
||||
? Pointer<T>[]
|
||||
: O extends {}
|
||||
? {
|
||||
[K in keyof O]-?: Pointer<O[K] | Optional>
|
||||
}
|
||||
: UnindexablePointer
|
||||
|
||||
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 type {$IntentionalAny, VoidFn} from '@theatre/shared/utils/types'
|
||||
import coreTicker from './coreTicker'
|
||||
import type {ProjectId} from '@theatre/shared/utils/ids'
|
||||
export {types}
|
||||
|
||||
/**
|
||||
|
@ -45,7 +46,7 @@ export {types}
|
|||
*/
|
||||
export function getProject(id: string, config: IProjectConfig = {}): IProject {
|
||||
const {...restOfConfig} = config
|
||||
const existingProject = projectsSingleton.get(id)
|
||||
const existingProject = projectsSingleton.get(id as ProjectId)
|
||||
if (existingProject) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (!deepEqual(config, existingProject.config)) {
|
||||
|
@ -67,9 +68,9 @@ export function getProject(id: string, config: IProjectConfig = {}): IProject {
|
|||
|
||||
if (config.state) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
shallowValidateOnDiskState(id, config.state)
|
||||
shallowValidateOnDiskState(id as ProjectId, config.state)
|
||||
} 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.
|
||||
* Does not do a thorough validation of the state.
|
||||
*/
|
||||
const shallowValidateOnDiskState = (projectId: string, s: OnDiskState) => {
|
||||
const shallowValidateOnDiskState = (projectId: ProjectId, s: OnDiskState) => {
|
||||
if (
|
||||
Array.isArray(s) ||
|
||||
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)
|
||||
// @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 Sheet from '@theatre/core/sheets/Sheet'
|
||||
import type {ISheet} from '@theatre/core/sheets/TheatreSheet'
|
||||
import type {UnknownShorthandCompoundProps} from './propTypes/internals'
|
||||
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
||||
|
||||
const publicAPIToPrivateAPIMap = new WeakMap()
|
||||
|
||||
export function privateAPI<
|
||||
P extends IProject | ISheet | ISheetObject<$IntentionalAny> | ISequence,
|
||||
>(
|
||||
export function privateAPI<P extends {type: string}>(
|
||||
pub: P,
|
||||
): P extends IProject
|
||||
? Project
|
||||
|
@ -29,8 +28,8 @@ export function privateAPI<
|
|||
export function setPrivateAPI(pub: IProject, priv: Project): void
|
||||
export function setPrivateAPI(pub: ISheet, priv: Sheet): void
|
||||
export function setPrivateAPI(pub: ISequence, priv: Sequence): void
|
||||
export function setPrivateAPI(
|
||||
pub: ISheetObject<$IntentionalAny>,
|
||||
export function setPrivateAPI<Props extends UnknownShorthandCompoundProps>(
|
||||
pub: ISheetObject<Props>,
|
||||
priv: SheetObject,
|
||||
): 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 {defer} from '@theatre/shared/utils/defer'
|
||||
import globals from '@theatre/shared/globals'
|
||||
import type {
|
||||
ProjectId,
|
||||
SheetId,
|
||||
SheetInstanceId,
|
||||
} from '@theatre/shared/utils/ids'
|
||||
|
||||
export type Conf = Partial<{
|
||||
state: OnDiskState
|
||||
|
@ -44,7 +49,7 @@ export default class Project {
|
|||
type: 'Theatre_Project' = 'Theatre_Project'
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
id: ProjectId,
|
||||
readonly config: Conf = {},
|
||||
readonly publicApi: TheatreProject,
|
||||
) {
|
||||
|
@ -150,7 +155,10 @@ export default class Project {
|
|||
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]
|
||||
|
||||
if (!template) {
|
||||
|
|
|
@ -2,6 +2,11 @@ import {privateAPI, setPrivateAPI} from '@theatre/core/privateAPIs'
|
|||
import Project from '@theatre/core/projects/Project'
|
||||
import type {ISheet} from '@theatre/core/sheets/TheatreSheet'
|
||||
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 {validateAndSanitiseSlashedPathOrThrow} from '@theatre/shared/utils/slashedPaths'
|
||||
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
||||
|
@ -58,7 +63,7 @@ export default class TheatreProject implements IProject {
|
|||
* @internal
|
||||
*/
|
||||
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> {
|
||||
|
@ -87,7 +92,9 @@ export default class TheatreProject implements IProject {
|
|||
)
|
||||
}
|
||||
|
||||
return privateAPI(this).getOrCreateSheet(sanitizedPath, instanceId)
|
||||
.publicApi
|
||||
return privateAPI(this).getOrCreateSheet(
|
||||
sanitizedPath as SheetId,
|
||||
instanceId as SheetInstanceId,
|
||||
).publicApi
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import {Atom} from '@theatre/dataverse'
|
||||
import type {ProjectId} from '@theatre/shared/utils/ids'
|
||||
import type Project from './Project'
|
||||
|
||||
interface State {
|
||||
projects: Record<string, Project>
|
||||
projects: Record<ProjectId, Project>
|
||||
}
|
||||
|
||||
class ProjectsSingleton {
|
||||
|
@ -12,15 +13,15 @@ class ProjectsSingleton {
|
|||
/**
|
||||
* 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)
|
||||
}
|
||||
|
||||
get(id: string): Project | undefined {
|
||||
get(id: ProjectId): Project | undefined {
|
||||
return this.atom.getState().projects[id]
|
||||
}
|
||||
|
||||
has(id: string) {
|
||||
has(id: ProjectId) {
|
||||
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 {SheetState_Historic} from './types/SheetState_Historic'
|
||||
|
||||
|
@ -31,7 +32,7 @@ export interface ProjectEphemeralState {
|
|||
* at {@link StudioHistoricState.coreByProject}
|
||||
*/
|
||||
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 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 {SerializableMap, StrictRecord} from '@theatre/shared/utils/types'
|
||||
import type {
|
||||
KeyframeId,
|
||||
ObjectAddressKey,
|
||||
SequenceTrackId,
|
||||
} from '@theatre/shared/utils/ids'
|
||||
import type {
|
||||
SerializableMap,
|
||||
SerializableValue,
|
||||
StrictRecord,
|
||||
} from '@theatre/shared/utils/types'
|
||||
|
||||
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.
|
||||
*/
|
||||
staticOverrides: {
|
||||
byObject: StrictRecord<string, SerializableMap>
|
||||
byObject: StrictRecord<ObjectAddressKey, SerializableMap>
|
||||
}
|
||||
sequence?: Sequence
|
||||
sequence?: HistoricPositionalSequence
|
||||
}
|
||||
|
||||
type Sequence = PositionalSequence
|
||||
|
||||
type PositionalSequence = {
|
||||
// Question: What is this? The timeline position of a sequence?
|
||||
export type HistoricPositionalSequence = {
|
||||
type: 'PositionalSequence'
|
||||
length: number
|
||||
/**
|
||||
|
@ -27,7 +34,7 @@ type PositionalSequence = {
|
|||
subUnitsPerUnit: number
|
||||
|
||||
tracksByObject: StrictRecord<
|
||||
string,
|
||||
ObjectAddressKey,
|
||||
{
|
||||
trackIdByPropPath: StrictRecord<string, SequenceTrackId>
|
||||
trackData: StrictRecord<SequenceTrackId, TrackData>
|
||||
|
@ -39,7 +46,10 @@ export type TrackData = BasicKeyframedTrack
|
|||
|
||||
export type Keyframe = {
|
||||
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
|
||||
handles: [leftX: number, leftY: number, rightX: number, rightY: number]
|
||||
connectedRight: boolean
|
||||
|
@ -47,5 +57,9 @@ export type Keyframe = {
|
|||
|
||||
export 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[]
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ import {
|
|||
} from '@theatre/shared/utils/color'
|
||||
import {clamp, mapValues} from 'lodash-es'
|
||||
import type {
|
||||
IShorthandCompoundProps,
|
||||
IValidCompoundProps,
|
||||
UnknownShorthandCompoundProps,
|
||||
UnknownValidCompoundProps,
|
||||
ShorthandCompoundPropsToLonghandCompoundProps,
|
||||
} 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,
|
||||
opts: CommonOpts = {},
|
||||
): PropTypeConfig_Compound<
|
||||
|
@ -101,7 +101,7 @@ export const compound = <Props extends IShorthandCompoundProps>(
|
|||
ShorthandCompoundPropsToLonghandCompoundProps<Props>
|
||||
> = {
|
||||
type: 'compound',
|
||||
props: sanitizedProps,
|
||||
props: sanitizedProps as $IntentionalAny,
|
||||
valueType: null as $IntentionalAny,
|
||||
[propTypeSymbol]: 'TheatrePropType',
|
||||
label: opts.label,
|
||||
|
@ -690,7 +690,7 @@ export interface PropTypeConfig_StringLiteral<T extends string>
|
|||
|
||||
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]>
|
||||
}
|
||||
|
||||
|
@ -701,13 +701,14 @@ type DeepPartial<Conf extends PropTypeConfig> =
|
|||
? DeepPartialCompound<T>
|
||||
: never
|
||||
|
||||
export interface PropTypeConfig_Compound<Props extends IValidCompoundProps>
|
||||
extends IBasePropType<
|
||||
export interface PropTypeConfig_Compound<
|
||||
Props extends UnknownValidCompoundProps,
|
||||
> extends IBasePropType<
|
||||
'compound',
|
||||
{[K in keyof Props]: Props[K]['valueType']},
|
||||
DeepPartialCompound<Props>
|
||||
> {
|
||||
props: Record<string, PropTypeConfig>
|
||||
props: Record<keyof Props, PropTypeConfig>
|
||||
}
|
||||
|
||||
export interface PropTypeConfig_Enum extends IBasePropType<'enum', {}> {
|
||||
|
|
|
@ -13,7 +13,7 @@ import * as t from './index'
|
|||
|
||||
export const propTypeSymbol = Symbol('TheatrePropType_Basic')
|
||||
|
||||
export type IValidCompoundProps = {
|
||||
export type UnknownValidCompoundProps = {
|
||||
[K in string]: PropTypeConfig
|
||||
}
|
||||
|
||||
|
@ -27,18 +27,19 @@ export type IValidCompoundProps = {
|
|||
* which would allow us to differentiate between values at runtime
|
||||
* (e.g. `val.type = "Rgba"` vs `val.type = "Compound"` etc)
|
||||
*/
|
||||
type IShorthandProp =
|
||||
type UnknownShorthandProp =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| PropTypeConfig
|
||||
| IShorthandCompoundProps
|
||||
| UnknownShorthandCompoundProps
|
||||
|
||||
export type IShorthandCompoundProps = {
|
||||
[K in string]: IShorthandProp
|
||||
/** Given an object like this, we have enough info to predict the compound prop */
|
||||
export type UnknownShorthandCompoundProps = {
|
||||
[K in string]: UnknownShorthandProp
|
||||
}
|
||||
|
||||
export type ShorthandPropToLonghandProp<P extends IShorthandProp> =
|
||||
export type ShorthandPropToLonghandProp<P extends UnknownShorthandProp> =
|
||||
P extends string
|
||||
? PropTypeConfig_String
|
||||
: P extends number
|
||||
|
@ -47,12 +48,31 @@ export type ShorthandPropToLonghandProp<P extends IShorthandProp> =
|
|||
? PropTypeConfig_Boolean
|
||||
: P extends PropTypeConfig
|
||||
? P
|
||||
: P extends IShorthandCompoundProps
|
||||
: P extends UnknownShorthandCompoundProps
|
||||
? PropTypeConfig_Compound<ShorthandCompoundPropsToLonghandCompoundProps<P>>
|
||||
: 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<
|
||||
P extends IShorthandCompoundProps,
|
||||
P extends UnknownShorthandCompoundProps,
|
||||
> = {
|
||||
[K in keyof P]: ShorthandPropToLonghandProp<P[K]>
|
||||
}
|
||||
|
@ -89,9 +109,9 @@ export function toLonghandProp(p: unknown): PropTypeConfig {
|
|||
}
|
||||
|
||||
export function sanitizeCompoundProps(
|
||||
props: IShorthandCompoundProps,
|
||||
): IValidCompoundProps {
|
||||
const sanitizedProps: IValidCompoundProps = {}
|
||||
props: UnknownShorthandCompoundProps,
|
||||
): UnknownValidCompoundProps {
|
||||
const sanitizedProps: UnknownValidCompoundProps = {}
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (typeof props !== 'object' || !props) {
|
||||
throw new InvalidArgumentError(
|
||||
|
|
|
@ -6,11 +6,15 @@ import type {
|
|||
import type {IDerivation, Pointer} from '@theatre/dataverse'
|
||||
import {ConstantDerivation, prism, val} from '@theatre/dataverse'
|
||||
import logger from '@theatre/shared/logger'
|
||||
import type {SerializableValue} from '@theatre/shared/utils/types'
|
||||
import UnitBezier from 'timing-function/lib/UnitBezier'
|
||||
|
||||
/** `left` and `right` are not necessarily the same type. */
|
||||
export type InterpolationTriple = {
|
||||
left: unknown
|
||||
right?: unknown
|
||||
/** `left` and `right` are not necessarily the same type. */
|
||||
left: SerializableValue
|
||||
/** `left` and `right` are not necessarily the same type. */
|
||||
right?: SerializableValue
|
||||
progression: number
|
||||
}
|
||||
|
||||
|
@ -75,10 +79,10 @@ function _forKeyframedTrack(
|
|||
|
||||
const undefinedConstD = new ConstantDerivation(undefined)
|
||||
|
||||
const updateState = (
|
||||
function updateState(
|
||||
progressionD: IDerivation<number>,
|
||||
track: BasicKeyframedTrack,
|
||||
): IStartedState => {
|
||||
): IStartedState {
|
||||
const progression = progressionD.getValue()
|
||||
if (track.keyframes.length === 0) {
|
||||
return {
|
||||
|
|
|
@ -4,18 +4,19 @@
|
|||
import {setupTestSheet} from '@theatre/shared/testUtils'
|
||||
import {encodePathToProp} from '@theatre/shared/utils/addresses'
|
||||
import {asKeyframeId, asSequenceTrackId} from '@theatre/shared/utils/ids'
|
||||
import type {ObjectAddressKey, SequenceTrackId} from '@theatre/shared/utils/ids'
|
||||
import {iterateOver, prism} from '@theatre/dataverse'
|
||||
import type {SheetState_Historic} from '@theatre/core/projects/store/types/SheetState_Historic'
|
||||
|
||||
describe(`SheetObject`, () => {
|
||||
describe('static overrides', () => {
|
||||
const setup = async (
|
||||
staticOverrides: SheetState_Historic['staticOverrides']['byObject'][string] = {},
|
||||
staticOverrides: SheetState_Historic['staticOverrides']['byObject'][ObjectAddressKey] = {},
|
||||
) => {
|
||||
const {studio, objPublicAPI} = await setupTestSheet({
|
||||
staticOverrides: {
|
||||
byObject: {
|
||||
obj: staticOverrides,
|
||||
['obj' as ObjectAddressKey]: staticOverrides,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
@ -260,12 +261,12 @@ describe(`SheetObject`, () => {
|
|||
length: 20,
|
||||
subUnitsPerUnit: 30,
|
||||
tracksByObject: {
|
||||
obj: {
|
||||
['obj' as ObjectAddressKey]: {
|
||||
trackIdByPropPath: {
|
||||
[encodePathToProp(['position', 'y'])]: asSequenceTrackId('1'),
|
||||
},
|
||||
trackData: {
|
||||
'1': {
|
||||
['1' as SequenceTrackId]: {
|
||||
type: 'BasicKeyframedTrack',
|
||||
keyframes: [
|
||||
{
|
||||
|
|
|
@ -9,6 +9,7 @@ import SimpleCache from '@theatre/shared/utils/SimpleCache'
|
|||
import type {
|
||||
$FixMe,
|
||||
$IntentionalAny,
|
||||
DeepPartialOfSerializableValue,
|
||||
SerializableMap,
|
||||
SerializableValue,
|
||||
} from '@theatre/shared/utils/types'
|
||||
|
@ -25,14 +26,29 @@ import TheatreSheetObject from './TheatreSheetObject'
|
|||
import type {Interpolator, PropTypeConfig} from '@theatre/core/propTypes'
|
||||
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 {
|
||||
get type(): 'Theatre_SheetObject' {
|
||||
return 'Theatre_SheetObject'
|
||||
}
|
||||
readonly $$isIdentityDerivationProvider: true = true
|
||||
readonly address: SheetObjectAddress
|
||||
readonly publicApi: TheatreSheetObject<$IntentionalAny>
|
||||
private readonly _initialValue = new Atom<SerializableMap>({})
|
||||
readonly publicApi: TheatreSheetObject
|
||||
private readonly _initialValue = new Atom<SheetObjectPropsValue>({})
|
||||
private readonly _cache = new SimpleCache()
|
||||
|
||||
constructor(
|
||||
|
@ -48,7 +64,7 @@ export default class SheetObject implements IdentityDerivationProvider {
|
|||
this.publicApi = new TheatreSheetObject(this)
|
||||
}
|
||||
|
||||
getValues(): IDerivation<Pointer<SerializableMap>> {
|
||||
getValues(): IDerivation<Pointer<SheetObjectPropsValue>> {
|
||||
return this._cache.get('getValues()', () =>
|
||||
prism(() => {
|
||||
const defaults = val(this.template.getDefaultValues())
|
||||
|
@ -101,7 +117,7 @@ export default class SheetObject implements IdentityDerivationProvider {
|
|||
final = withSeqs
|
||||
}
|
||||
|
||||
const a = valToAtom<SerializableMap>('finalAtom', final)
|
||||
const a = valToAtom<SheetObjectPropsValue>('finalAtom', final)
|
||||
|
||||
return a.pointer
|
||||
}),
|
||||
|
@ -131,7 +147,7 @@ export default class SheetObject implements IdentityDerivationProvider {
|
|||
/**
|
||||
* Returns values of props that are sequenced.
|
||||
*/
|
||||
getSequencedValues(): IDerivation<Pointer<SerializableMap>> {
|
||||
getSequencedValues(): IDerivation<Pointer<SheetObjectPropsValue>> {
|
||||
return prism(() => {
|
||||
const tracksToProcessD = prism.memo(
|
||||
'tracksToProcess',
|
||||
|
@ -140,7 +156,7 @@ export default class SheetObject implements IdentityDerivationProvider {
|
|||
)
|
||||
|
||||
const tracksToProcess = val(tracksToProcessD)
|
||||
const valsAtom = new Atom<SerializableMap>({})
|
||||
const valsAtom = new Atom<SheetObjectPropsValue>({})
|
||||
|
||||
prism.effect(
|
||||
'processTracks',
|
||||
|
@ -216,17 +232,20 @@ export default class SheetObject implements IdentityDerivationProvider {
|
|||
return interpolationTripleAtPosition(trackP, timeD)
|
||||
}
|
||||
|
||||
get propsP(): Pointer<$FixMe> {
|
||||
get propsP(): Pointer<SheetObjectPropsValue> {
|
||||
return this._cache.get('propsP', () =>
|
||||
pointer<{props: {}}>({root: this, path: []}),
|
||||
) as $FixMe
|
||||
}
|
||||
|
||||
validateValue(pointer: Pointer<$FixMe>, value: unknown) {
|
||||
validateValue(
|
||||
pointer: Pointer<SheetObjectPropsValue>,
|
||||
value: DeepPartialOfSerializableValue<SheetObjectPropsValue>,
|
||||
) {
|
||||
// @todo
|
||||
}
|
||||
|
||||
setInitialValue(val: SerializableMap) {
|
||||
setInitialValue(val: DeepPartialOfSerializableValue<SheetObjectPropsValue>) {
|
||||
this.validateValue(this.propsP, val)
|
||||
this._initialValue.setState(val)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import {setupTestSheet} from '@theatre/shared/testUtils'
|
||||
import {encodePathToProp} from '@theatre/shared/utils/addresses'
|
||||
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 {iterateOver} from '@theatre/dataverse'
|
||||
|
||||
|
@ -19,15 +20,15 @@ describe(`SheetObjectTemplate`, () => {
|
|||
subUnitsPerUnit: 30,
|
||||
length: 10,
|
||||
tracksByObject: {
|
||||
obj: {
|
||||
['obj' as ObjectAddressKey]: {
|
||||
trackIdByPropPath: {
|
||||
[encodePathToProp(['position', 'x'])]: asSequenceTrackId('x'),
|
||||
[encodePathToProp(['position', 'invalid'])]:
|
||||
asSequenceTrackId('invalidTrack'),
|
||||
},
|
||||
trackData: {
|
||||
x: null as $IntentionalAny,
|
||||
invalid: null as $IntentionalAny,
|
||||
['x' as SequenceTrackId]: null as $IntentionalAny,
|
||||
['invalid' as SequenceTrackId]: null as $IntentionalAny,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -74,15 +75,15 @@ describe(`SheetObjectTemplate`, () => {
|
|||
subUnitsPerUnit: 30,
|
||||
length: 10,
|
||||
tracksByObject: {
|
||||
obj: {
|
||||
['obj' as ObjectAddressKey]: {
|
||||
trackIdByPropPath: {
|
||||
[encodePathToProp(['position', 'x'])]: asSequenceTrackId('x'),
|
||||
[encodePathToProp(['position', 'invalid'])]:
|
||||
asSequenceTrackId('invalidTrack'),
|
||||
},
|
||||
trackData: {
|
||||
x: null as $IntentionalAny,
|
||||
invalid: null as $IntentionalAny,
|
||||
['x' as SequenceTrackId]: null as $IntentionalAny,
|
||||
['invalid' as SequenceTrackId]: null as $IntentionalAny,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type Project from '@theatre/core/projects/Project'
|
||||
import type Sheet from '@theatre/core/sheets/Sheet'
|
||||
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 type {
|
||||
PathToProp,
|
||||
|
@ -9,7 +9,7 @@ import type {
|
|||
WithoutSheetInstance,
|
||||
} from '@theatre/shared/utils/addresses'
|
||||
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 type {
|
||||
$FixMe,
|
||||
|
@ -23,7 +23,6 @@ import set from 'lodash-es/set'
|
|||
import getPropDefaultsOfSheetObject from './getPropDefaultsOfSheetObject'
|
||||
import SheetObject from './SheetObject'
|
||||
import logger from '@theatre/shared/logger'
|
||||
import type {PropTypeConfig_Compound} from '@theatre/core/propTypes'
|
||||
import {
|
||||
getPropConfigByPath,
|
||||
isPropConfSequencable,
|
||||
|
@ -34,12 +33,15 @@ export type 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 {
|
||||
readonly address: WithoutSheetInstance<SheetObjectAddress>
|
||||
readonly type: 'Theatre_SheetObjectTemplate' = 'Theatre_SheetObjectTemplate'
|
||||
protected _config: Atom<
|
||||
SheetObjectConfig<PropTypeConfig_Compound<$IntentionalAny>>
|
||||
>
|
||||
protected _config: Atom<SheetObjectPropTypeConfig>
|
||||
readonly _cache = new SimpleCache()
|
||||
readonly project: Project
|
||||
|
||||
|
@ -49,9 +51,9 @@ export default class SheetObjectTemplate {
|
|||
|
||||
constructor(
|
||||
readonly sheetTemplate: SheetTemplate,
|
||||
objectKey: string,
|
||||
objectKey: ObjectAddressKey,
|
||||
nativeObject: unknown,
|
||||
config: SheetObjectConfig<$IntentionalAny>,
|
||||
config: SheetObjectPropTypeConfig,
|
||||
) {
|
||||
this.address = {...sheetTemplate.address, objectKey}
|
||||
this._config = new Atom(config)
|
||||
|
@ -61,13 +63,13 @@ export default class SheetObjectTemplate {
|
|||
createInstance(
|
||||
sheet: Sheet,
|
||||
nativeObject: unknown,
|
||||
config: SheetObjectConfig<$IntentionalAny>,
|
||||
config: SheetObjectPropTypeConfig,
|
||||
): SheetObject {
|
||||
this._config.setState(config)
|
||||
return new SheetObject(sheet, this, nativeObject)
|
||||
}
|
||||
|
||||
overrideConfig(config: SheetObjectConfig<$IntentionalAny>) {
|
||||
overrideConfig(config: SheetObjectPropTypeConfig) {
|
||||
this._config.setState(config)
|
||||
}
|
||||
|
||||
|
@ -99,7 +101,7 @@ export default class SheetObjectTemplate {
|
|||
pointerToSheetState.staticOverrides.byObject[
|
||||
this.address.objectKey
|
||||
],
|
||||
) || {}
|
||||
) ?? {}
|
||||
|
||||
const config = val(this._config.pointer)
|
||||
const deserialized = config.deserializeAndSanitize(json) || {}
|
||||
|
|
|
@ -13,11 +13,13 @@ import type {IDerivation, Pointer} from '@theatre/dataverse'
|
|||
import {prism, val} from '@theatre/dataverse'
|
||||
import type SheetObject from './SheetObject'
|
||||
import type {
|
||||
IShorthandCompoundProps,
|
||||
ShorthandPropToLonghandProp,
|
||||
UnknownShorthandCompoundProps,
|
||||
PropsValue,
|
||||
} 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'`
|
||||
*/
|
||||
|
@ -32,8 +34,14 @@ export interface ISheetObject<Props extends IShorthandCompoundProps = {}> {
|
|||
* const obj = sheet.object("obj", {x: 0})
|
||||
* 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.
|
||||
|
@ -100,7 +108,7 @@ export interface ISheetObject<Props extends IShorthandCompoundProps = {}> {
|
|||
}
|
||||
|
||||
export default class TheatreSheetObject<
|
||||
Props extends IShorthandCompoundProps = {},
|
||||
Props extends UnknownShorthandCompoundProps = UnknownShorthandCompoundProps,
|
||||
> implements ISheetObject<Props>
|
||||
{
|
||||
get type(): 'Theatre_SheetObject_PublicAPI' {
|
||||
|
@ -134,7 +142,7 @@ export default class TheatreSheetObject<
|
|||
private _valuesDerivation(): IDerivation<this['value']> {
|
||||
return this._cache.get('onValuesChangeDerivation', () => {
|
||||
const sheetObject = privateAPI(this)
|
||||
const d: IDerivation<Props> = prism(() => {
|
||||
const d: IDerivation<PropsValue<Props>> = prism(() => {
|
||||
return val(sheetObject.getValues().getValue()) as $FixMe
|
||||
})
|
||||
return d
|
||||
|
@ -145,7 +153,7 @@ export default class TheatreSheetObject<
|
|||
return this._valuesDerivation().tapImmediate(coreTicker, fn)
|
||||
}
|
||||
|
||||
get value(): ShorthandPropToLonghandProp<Props>['valueType'] {
|
||||
get value(): PropsValue<Props> {
|
||||
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 {
|
||||
$FixMe,
|
||||
$IntentionalAny,
|
||||
SerializableMap,
|
||||
SerializableValue,
|
||||
} 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.
|
||||
*/
|
||||
export default function getPropDefaultsOfSheetObject(
|
||||
config: SheetObjectConfig<$IntentionalAny>,
|
||||
config: SheetObjectPropTypeConfig,
|
||||
): SerializableMap {
|
||||
return getDefaultsOfPropTypeConfig(config) as $IntentionalAny
|
||||
return getDefaultsOfPropTypeConfig(config) as SerializableMap // sheet objects result in non-primitive objects
|
||||
}
|
||||
|
||||
function getDefaultsOfPropTypeConfig(
|
||||
|
|
|
@ -1,17 +1,28 @@
|
|||
import type Project from '@theatre/core/projects/Project'
|
||||
import Sequence from '@theatre/core/sequences/Sequence'
|
||||
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 type {SheetAddress} from '@theatre/shared/utils/addresses'
|
||||
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
||||
import {Atom, valueDerivation} from '@theatre/dataverse'
|
||||
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 {
|
||||
private readonly _objects: Atom<IObjects> = new Atom<IObjects>({})
|
||||
private readonly _objects: Atom<SheetObjectMap> =
|
||||
new Atom<SheetObjectMap>({})
|
||||
private _sequence: undefined | Sequence
|
||||
readonly address: SheetAddress
|
||||
readonly publicApi: TheatreSheet
|
||||
|
@ -21,7 +32,7 @@ export default class Sheet {
|
|||
|
||||
constructor(
|
||||
readonly template: SheetTemplate,
|
||||
public readonly instanceId: string,
|
||||
public readonly instanceId: SheetInstanceId,
|
||||
) {
|
||||
this.project = template.project
|
||||
this.address = {
|
||||
|
@ -37,24 +48,24 @@ export default class Sheet {
|
|||
* with that of "an element."
|
||||
*/
|
||||
createObject(
|
||||
key: string,
|
||||
nativeObject: unknown,
|
||||
config: SheetObjectConfig<$IntentionalAny>,
|
||||
objectKey: ObjectAddressKey,
|
||||
nativeObject: ObjectNativeObject,
|
||||
config: SheetObjectPropTypeConfig,
|
||||
): SheetObject {
|
||||
const objTemplate = this.template.getObjectTemplate(
|
||||
key,
|
||||
objectKey,
|
||||
nativeObject,
|
||||
config,
|
||||
)
|
||||
|
||||
const object = objTemplate.createInstance(this, nativeObject, config)
|
||||
|
||||
this._objects.setIn([key], object)
|
||||
this._objects.setIn([objectKey], object)
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
getObject(key: string): SheetObject | undefined {
|
||||
getObject(key: ObjectAddressKey): SheetObject | undefined {
|
||||
return this._objects.getState()[key]
|
||||
}
|
||||
|
||||
|
|
|
@ -4,27 +4,38 @@ import type {
|
|||
SheetAddress,
|
||||
WithoutSheetInstance,
|
||||
} from '@theatre/shared/utils/addresses'
|
||||
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
||||
import {Atom} from '@theatre/dataverse'
|
||||
import type {Pointer} from '@theatre/dataverse'
|
||||
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 {
|
||||
readonly type: 'Theatre_SheetTemplate' = 'Theatre_SheetTemplate'
|
||||
readonly address: WithoutSheetInstance<SheetAddress>
|
||||
private _instances = new Atom<{[instanceId: string]: Sheet}>({})
|
||||
readonly instancesP = this._instances.pointer
|
||||
private _instances = new Atom<Record<SheetInstanceId, Sheet>>({})
|
||||
readonly instancesP: Pointer<Record<SheetInstanceId, Sheet>> =
|
||||
this._instances.pointer
|
||||
|
||||
private _objectTemplates = new Atom<{
|
||||
[objectKey: string]: SheetObjectTemplate
|
||||
}>({})
|
||||
private _objectTemplates = new Atom<SheetTemplateObjectTemplateMap>({})
|
||||
readonly objectTemplatesP = this._objectTemplates.pointer
|
||||
|
||||
constructor(readonly project: Project, sheetId: string) {
|
||||
constructor(readonly project: Project, sheetId: SheetId) {
|
||||
this.address = {...project.address, sheetId}
|
||||
}
|
||||
|
||||
getInstance(instanceId: string): Sheet {
|
||||
getInstance(instanceId: SheetInstanceId): Sheet {
|
||||
let inst = this._instances.getState()[instanceId]
|
||||
|
||||
if (!inst) {
|
||||
|
@ -36,15 +47,15 @@ export default class SheetTemplate {
|
|||
}
|
||||
|
||||
getObjectTemplate(
|
||||
key: string,
|
||||
nativeObject: unknown,
|
||||
config: SheetObjectConfig<$IntentionalAny>,
|
||||
objectKey: ObjectAddressKey,
|
||||
nativeObject: ObjectNativeObject,
|
||||
config: SheetObjectPropTypeConfig,
|
||||
): SheetObjectTemplate {
|
||||
let template = this._objectTemplates.getState()[key]
|
||||
let template = this._objectTemplates.getState()[objectKey]
|
||||
|
||||
if (!template) {
|
||||
template = new SheetObjectTemplate(this, key, nativeObject, config)
|
||||
this._objectTemplates.setIn([key], template)
|
||||
template = new SheetObjectTemplate(this, objectKey, nativeObject, config)
|
||||
this._objectTemplates.setIn([objectKey], template)
|
||||
}
|
||||
|
||||
return template
|
||||
|
|
|
@ -9,15 +9,18 @@ import type Sheet from '@theatre/core/sheets/Sheet'
|
|||
import type {SheetAddress} from '@theatre/shared/utils/addresses'
|
||||
import {InvalidArgumentError} from '@theatre/shared/utils/errors'
|
||||
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 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 {ObjectAddressKey} from '@theatre/shared/utils/ids'
|
||||
|
||||
export type SheetObjectConfig<
|
||||
Props extends PropTypeConfig_Compound<$IntentionalAny>,
|
||||
> = Props
|
||||
export type SheetObjectPropTypeConfig =
|
||||
PropTypeConfig_Compound<UnknownValidCompoundProps>
|
||||
|
||||
export interface ISheet {
|
||||
/**
|
||||
|
@ -62,7 +65,7 @@ export interface ISheet {
|
|||
* obj.value.position // {x: 0, y: 0}
|
||||
* ```
|
||||
*/
|
||||
object<Props extends IShorthandCompoundProps>(
|
||||
object<Props extends UnknownShorthandCompoundProps>(
|
||||
key: string,
|
||||
props: Props,
|
||||
): ISheetObject<Props>
|
||||
|
@ -75,7 +78,7 @@ export interface ISheet {
|
|||
|
||||
const weakMapOfUnsanitizedProps = new WeakMap<
|
||||
SheetObject,
|
||||
IShorthandCompoundProps
|
||||
UnknownShorthandCompoundProps
|
||||
>()
|
||||
|
||||
export default class TheatreSheet implements ISheet {
|
||||
|
@ -89,7 +92,7 @@ export default class TheatreSheet implements ISheet {
|
|||
setPrivateAPI(this, sheet)
|
||||
}
|
||||
|
||||
object<Props extends IShorthandCompoundProps>(
|
||||
object<Props extends UnknownShorthandCompoundProps>(
|
||||
key: string,
|
||||
config: Props,
|
||||
): ISheetObject<Props> {
|
||||
|
@ -99,8 +102,15 @@ export default class TheatreSheet implements ISheet {
|
|||
`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
|
||||
|
||||
if (existingObject) {
|
||||
|
@ -121,12 +131,12 @@ export default class TheatreSheet implements ISheet {
|
|||
} else {
|
||||
const sanitizedConfig = compound(config)
|
||||
const object = internal.createObject(
|
||||
sanitizedPath,
|
||||
sanitizedPath as ObjectAddressKey,
|
||||
nativeObject,
|
||||
sanitizedConfig,
|
||||
)
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
weakMapOfUnsanitizedProps.set(object, config)
|
||||
weakMapOfUnsanitizedProps.set(object as $FixMe, config)
|
||||
}
|
||||
return object.publicApi as $IntentionalAny
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import * as t from '@theatre/core/propTypes'
|
|||
import getStudio from '@theatre/studio/getStudio'
|
||||
import coreTicker from '@theatre/core/coreTicker'
|
||||
import globals from './globals'
|
||||
import type {SheetId} from './utils/ids'
|
||||
/* eslint-enable no-restricted-syntax */
|
||||
|
||||
const defaultProps = {
|
||||
|
@ -32,7 +33,7 @@ export async function setupTestSheet(sheetState: SheetState_Historic) {
|
|||
const projectState: ProjectState_Historic = {
|
||||
definitionVersion: globals.currentProjectStateDefinitionVersion,
|
||||
sheetsById: {
|
||||
Sheet: sheetState,
|
||||
['Sheet' as SheetId]: sheetState,
|
||||
},
|
||||
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,
|
||||
SerializableValue,
|
||||
} from '@theatre/shared/utils/types'
|
||||
import type {ObjectAddressKey, ProjectId, SheetId, SheetInstanceId} from './ids'
|
||||
|
||||
/**
|
||||
* Represents the address to a project
|
||||
*/
|
||||
export interface ProjectAddress {
|
||||
projectId: string
|
||||
projectId: ProjectId
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,8 +23,8 @@ export interface ProjectAddress {
|
|||
* ```
|
||||
*/
|
||||
export interface SheetAddress extends ProjectAddress {
|
||||
sheetId: string
|
||||
sheetInstanceId: string
|
||||
sheetId: SheetId
|
||||
sheetInstanceId: SheetInstanceId
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -36,7 +37,7 @@ export type WithoutSheetInstance<T extends SheetAddress> = Omit<
|
|||
>
|
||||
|
||||
export type SheetInstanceOptional<T extends SheetAddress> =
|
||||
WithoutSheetInstance<T> & {sheetInstanceId?: string | undefined}
|
||||
WithoutSheetInstance<T> & {sheetInstanceId?: SheetInstanceId | undefined}
|
||||
|
||||
/**
|
||||
* Represents the address to a Sheet's Object
|
||||
|
@ -51,7 +52,7 @@ export interface SheetObjectAddress extends SheetAddress {
|
|||
* obj.address.objectKey === 'foo'
|
||||
* ```
|
||||
*/
|
||||
objectKey: string
|
||||
objectKey: ObjectAddressKey
|
||||
}
|
||||
|
||||
export type PathToProp = Array<string | number>
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -11,10 +12,16 @@ export function asKeyframeId(s: string): KeyframeId {
|
|||
return s as $IntentionalAny
|
||||
}
|
||||
|
||||
// @todo make nominal
|
||||
export type SequenceTrackId = string
|
||||
export type ProjectId = Nominal<'ProjectId'>
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@ import type {Pointer} from '@theatre/dataverse'
|
|||
import type {PathToProp} from './addresses'
|
||||
import type {$IntentionalAny} from './types'
|
||||
|
||||
export default function pointerDeep(
|
||||
base: Pointer<$IntentionalAny>,
|
||||
export default function pointerDeep<T>(
|
||||
base: Pointer<T>,
|
||||
toAppend: PathToProp,
|
||||
): Pointer<unknown> {
|
||||
let p = base
|
||||
let p = base as $IntentionalAny
|
||||
for (const k of toAppend) {
|
||||
p = p[k]
|
||||
}
|
||||
|
|
|
@ -44,6 +44,13 @@ export type SerializablePrimitive =
|
|||
| boolean
|
||||
| {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<
|
||||
Primitives extends SerializablePrimitive = SerializablePrimitive,
|
||||
> = Primitives | SerializableMap
|
||||
|
@ -57,15 +64,13 @@ export type DeepPartialOfSerializableValue<T extends SerializableValue> =
|
|||
}
|
||||
: 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
|
||||
* doesn't allow generic index signatures, we're leaving it be.
|
||||
* This is equivalent to `Partial<Record<Key, V>>` being used to describe a sort of Map
|
||||
* 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]`
|
||||
|
@ -81,4 +86,4 @@ export type $IntentionalAny = any
|
|||
* Represents the `x` or `y` value of getBoundingClientRect().
|
||||
* 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 {emptyArray} from '@theatre/shared/utils'
|
||||
import type {PaneInstanceId} from '@theatre/shared/utils/ids'
|
||||
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 {PaneInstance} from './TheatreStudio'
|
||||
|
||||
|
@ -21,7 +22,7 @@ export default class PaneManager {
|
|||
|
||||
private _getAllPanes() {
|
||||
return this._cache.get('_getAllPanels()', () =>
|
||||
prism((): {[instanceId in string]?: PaneInstance<string>} => {
|
||||
prism((): StrictRecord<PaneInstanceId, PaneInstance<string>> => {
|
||||
const core = val(this._studio.coreP)
|
||||
if (!core) return {}
|
||||
const instanceDescriptors = val(
|
||||
|
@ -31,17 +32,16 @@ export default class PaneManager {
|
|||
this._studio.atomP.ephemeral.extensions.paneClasses,
|
||||
)
|
||||
|
||||
const instances: {[instanceId in string]?: PaneInstance<string>} = {}
|
||||
for (const [, instanceDescriptor] of Object.entries(
|
||||
instanceDescriptors,
|
||||
)) {
|
||||
const panelClass = paneClasses[instanceDescriptor!.paneClass]
|
||||
const instances: StrictRecord<PaneInstanceId, PaneInstance<string>> = {}
|
||||
for (const instanceDescriptor of Object.values(instanceDescriptors)) {
|
||||
if (!instanceDescriptor) continue
|
||||
const panelClass = paneClasses[instanceDescriptor.paneClass]
|
||||
if (!panelClass) continue
|
||||
const {instanceId} = instanceDescriptor!
|
||||
const {instanceId} = instanceDescriptor
|
||||
const {extensionId, classDefinition: definition} = panelClass
|
||||
|
||||
const instance = prism.memo(
|
||||
`instance-${instanceDescriptor!.instanceId}`,
|
||||
`instance-${instanceDescriptor.instanceId}`,
|
||||
() => {
|
||||
const inst: PaneInstance<$IntentionalAny> = {
|
||||
extensionId,
|
||||
|
@ -82,14 +82,14 @@ export default class PaneManager {
|
|||
const allPaneInstances = val(
|
||||
this._studio.atomP.historic.panelInstanceDesceriptors,
|
||||
)
|
||||
let instanceId!: string
|
||||
let instanceId!: PaneInstanceId
|
||||
for (let i = 1; i < 1000; i++) {
|
||||
instanceId = `${paneClass} #${i}`
|
||||
instanceId = `${paneClass} #${i}` as PaneInstanceId
|
||||
if (!allPaneInstances[instanceId]) break
|
||||
}
|
||||
|
||||
if (!extensionId) {
|
||||
throw new Error(`Pance class "${paneClass}" is not registered.`)
|
||||
throw new Error(`Pane class "${paneClass}" is not registered.`)
|
||||
}
|
||||
|
||||
this._studio.transaction(({drafts}) => {
|
||||
|
@ -102,7 +102,7 @@ export default class PaneManager {
|
|||
return this._getAllPanes().getValue()[instanceId]!
|
||||
}
|
||||
|
||||
destroyPane(instanceId: string): void {
|
||||
destroyPane(instanceId: PaneInstanceId): void {
|
||||
const core = this._studio.core
|
||||
if (!core) {
|
||||
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 {Deferred} 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
|
||||
|
||||
|
@ -27,10 +28,10 @@ export class Studio {
|
|||
readonly ui!: UI
|
||||
readonly publicApi: IStudio
|
||||
readonly address: {studioId: string}
|
||||
readonly _projectsProxy: PointerProxy<Record<string, Project>> =
|
||||
readonly _projectsProxy: PointerProxy<Record<ProjectId, Project>> =
|
||||
new PointerProxy(new Atom({}).pointer)
|
||||
|
||||
readonly projectsP: Pointer<Record<string, Project>> =
|
||||
readonly projectsP: Pointer<Record<ProjectId, Project>> =
|
||||
this._projectsProxy.pointer
|
||||
|
||||
private readonly _store = new StudioStore()
|
||||
|
@ -124,7 +125,7 @@ export class Studio {
|
|||
this._setProjectsP(coreBits.projectsP)
|
||||
}
|
||||
|
||||
private _setProjectsP(projectsP: Pointer<Record<string, Project>>) {
|
||||
private _setProjectsP(projectsP: Pointer<Record<ProjectId, Project>>) {
|
||||
this._projectsProxy.setPointer(projectsP)
|
||||
}
|
||||
|
||||
|
@ -218,6 +219,6 @@ export class Studio {
|
|||
}
|
||||
|
||||
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 createTransactionPrivateApi from './createTransactionPrivateApi'
|
||||
import type {ProjectId} from '@theatre/shared/utils/ids'
|
||||
|
||||
export type Drafts = {
|
||||
historic: Draft<StudioHistoricState>
|
||||
|
@ -173,7 +174,7 @@ export default class StudioStore {
|
|||
this._reduxStore.dispatch(studioActions.historic.redo())
|
||||
}
|
||||
|
||||
createContentOfSaveFile(projectId: string): OnDiskState {
|
||||
createContentOfSaveFile(projectId: ProjectId): OnDiskState {
|
||||
const projectState =
|
||||
this._reduxStore.getState().$persistent.historic.innerState.coreByProject[
|
||||
projectId
|
||||
|
|
|
@ -16,6 +16,7 @@ import getStudio from './getStudio'
|
|||
import type React from 'react'
|
||||
import {debounce} from 'lodash-es'
|
||||
import type Sheet from '@theatre/core/sheets/Sheet'
|
||||
import type {PaneInstanceId, ProjectId} from '@theatre/shared/utils/ids'
|
||||
|
||||
export interface ITransactionAPI {
|
||||
/**
|
||||
|
@ -116,7 +117,7 @@ export interface IExtension {
|
|||
|
||||
export type PaneInstance<ClassName extends string> = {
|
||||
extensionId: string
|
||||
instanceId: string
|
||||
instanceId: PaneInstanceId
|
||||
definition: PaneClassDefinition
|
||||
}
|
||||
|
||||
|
@ -471,10 +472,12 @@ export default class TheatreStudio implements IStudio {
|
|||
}
|
||||
|
||||
destroyPane(paneId: string): void {
|
||||
return getStudio().paneManager.destroyPane(paneId)
|
||||
return getStudio().paneManager.destroyPane(paneId as PaneInstanceId)
|
||||
}
|
||||
|
||||
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 {usePrism} from '@theatre/react'
|
||||
import type {UIPanelId} from '@theatre/shared/utils/ids'
|
||||
import type {$IntentionalAny, VoidFn} from '@theatre/shared/utils/types'
|
||||
import getStudio from '@theatre/studio/getStudio'
|
||||
import type {PanelPosition} from '@theatre/studio/store/types'
|
||||
|
@ -8,7 +9,7 @@ import React, {useContext} from 'react'
|
|||
import useWindowSize from 'react-use/esm/useWindowSize'
|
||||
|
||||
type PanelStuff = {
|
||||
panelId: string
|
||||
panelId: UIPanelId
|
||||
dims: {
|
||||
width: number
|
||||
height: number
|
||||
|
@ -64,7 +65,7 @@ const PanelContext = React.createContext<PanelStuff>(null as $IntentionalAny)
|
|||
export const usePanel = () => useContext(PanelContext)
|
||||
|
||||
const BasePanel: React.FC<{
|
||||
panelId: string
|
||||
panelId: UIPanelId
|
||||
defaultPosition: PanelPosition
|
||||
minDims: {width: number; height: number}
|
||||
}> = ({panelId, children, defaultPosition, minDims}) => {
|
||||
|
|
|
@ -11,6 +11,7 @@ import {ErrorBoundary} from 'react-error-boundary'
|
|||
import {IoClose} from 'react-icons/all'
|
||||
import getStudio from '@theatre/studio/getStudio'
|
||||
import {panelZIndexes} from '@theatre/studio/panels/BasePanel/common'
|
||||
import type {PaneInstanceId, UIPanelId} from '@theatre/shared/utils/ids'
|
||||
|
||||
const defaultPosition: PanelPosition = {
|
||||
edges: {
|
||||
|
@ -28,7 +29,7 @@ const ExtensionPaneWrapper: React.FC<{
|
|||
}> = ({paneInstance}) => {
|
||||
return (
|
||||
<BasePanel
|
||||
panelId={`pane-${paneInstance.instanceId}`}
|
||||
panelId={`pane-${paneInstance.instanceId}` as UIPanelId}
|
||||
defaultPosition={defaultPosition}
|
||||
minDims={minDims}
|
||||
>
|
||||
|
@ -137,7 +138,9 @@ const Content: React.FC<{paneInstance: PaneInstance<$FixMe>}> = ({
|
|||
}) => {
|
||||
const Comp = paneInstance.definition.component
|
||||
const closePane = useCallback(() => {
|
||||
getStudio().paneManager.destroyPane(paneInstance.instanceId)
|
||||
getStudio().paneManager.destroyPane(
|
||||
paneInstance.instanceId as PaneInstanceId,
|
||||
)
|
||||
}, [paneInstance])
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React, {useMemo} from 'react'
|
||||
import DeterminePropEditor from './propEditors/DeterminePropEditor'
|
||||
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<{
|
||||
objects: SheetObject[]
|
||||
|
@ -13,9 +15,9 @@ const ObjectDetails: React.FC<{
|
|||
<DeterminePropEditor
|
||||
key={key}
|
||||
obj={obj}
|
||||
pointerToProp={obj.propsP}
|
||||
pointerToProp={obj.propsP as Pointer<$FixMe>}
|
||||
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 type {$FixMe} from '@theatre/shared/utils/types'
|
||||
import DetailPanelButton from '@theatre/studio/uiComponents/DetailPanelButton'
|
||||
import type {ProjectId} from '@theatre/shared/utils/ids'
|
||||
|
||||
const Container = styled.div`
|
||||
padding: 8px 10px;
|
||||
|
@ -37,7 +38,7 @@ const ChooseStateRow = styled.div`
|
|||
gap: 8px;
|
||||
`
|
||||
|
||||
const StateConflictRow: React.FC<{projectId: string}> = ({projectId}) => {
|
||||
const StateConflictRow: React.FC<{projectId: ProjectId}> = ({projectId}) => {
|
||||
const loadingState = useVal(
|
||||
getStudio().atomP.ephemeral.coreByProject[projectId].loadingState,
|
||||
)
|
||||
|
@ -52,7 +53,7 @@ const StateConflictRow: React.FC<{projectId: string}> = ({projectId}) => {
|
|||
}
|
||||
|
||||
const InConflict: React.FC<{
|
||||
projectId: string
|
||||
projectId: ProjectId
|
||||
loadingState: Extract<
|
||||
ProjectEphemeralState['loadingState'],
|
||||
{type: 'browserStateIsNotBasedOnDiskState'}
|
||||
|
|
|
@ -61,7 +61,7 @@ const SubProps = styled.div<{depth: number; lastSubIsComposite: boolean}>`
|
|||
|
||||
const CompoundPropEditor: IPropEditorFC<
|
||||
PropTypeConfig_Compound<$IntentionalAny>
|
||||
> = ({pointerToProp, obj, propConfig, depth}) => {
|
||||
> = ({pointerToProp, obj, propConfig, visualIndentation: depth}) => {
|
||||
const propName = propConfig.label ?? last(getPointerParts(pointerToProp).path)
|
||||
|
||||
const allSubs = Object.entries(propConfig.props)
|
||||
|
@ -154,7 +154,7 @@ const CompoundPropEditor: IPropEditorFC<
|
|||
propConfig={subPropConfig}
|
||||
pointerToProp={pointerToProp[subPropKey]}
|
||||
obj={obj}
|
||||
depth={depth + 1}
|
||||
visualIndentation={depth + 1}
|
||||
/>
|
||||
)
|
||||
},
|
||||
|
|
|
@ -9,15 +9,15 @@ import NumberPropEditor from './NumberPropEditor'
|
|||
import StringLiteralPropEditor from './StringLiteralPropEditor'
|
||||
import StringPropEditor from './StringPropEditor'
|
||||
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
|
||||
* it exists in obj.
|
||||
*/
|
||||
export const getPropTypeByPointer = (
|
||||
pointerToProp: SheetObject['propsP'],
|
||||
obj: SheetObject,
|
||||
): PropTypeConfig => {
|
||||
export function getPropTypeByPointer<
|
||||
Props extends UnknownShorthandCompoundProps,
|
||||
>(pointerToProp: SheetObject['propsP'], obj: SheetObject): PropTypeConfig {
|
||||
const rootConf = obj.template.config
|
||||
|
||||
const p = getPointerParts(pointerToProp).path
|
||||
|
@ -67,7 +67,7 @@ type IPropEditorByPropType = {
|
|||
obj: SheetObject
|
||||
pointerToProp: Pointer<PropConfigByType<K>['valueType']>
|
||||
propConfig: PropConfigByType<K>
|
||||
depth: number
|
||||
visualIndentation: number
|
||||
}>
|
||||
}
|
||||
|
||||
|
@ -81,12 +81,20 @@ const propEditorByPropType: IPropEditorByPropType = {
|
|||
rgba: RgbaPropEditor,
|
||||
}
|
||||
|
||||
const DeterminePropEditor: React.FC<{
|
||||
export type IEditablePropertyProps<K extends PropTypeConfig['type']> = {
|
||||
obj: SheetObject
|
||||
pointerToProp: SheetObject['propsP']
|
||||
propConfig?: PropTypeConfig
|
||||
depth: number
|
||||
}> = (p) => {
|
||||
pointerToProp: Pointer<PropConfigByType<K>['valueType']>
|
||||
propConfig: PropConfigByType<K>
|
||||
}
|
||||
|
||||
type IDeterminePropEditorProps<K extends PropTypeConfig['type']> =
|
||||
IEditablePropertyProps<K> & {
|
||||
visualIndentation: number
|
||||
}
|
||||
|
||||
const DeterminePropEditor: React.FC<
|
||||
IDeterminePropEditorProps<PropTypeConfig['type']>
|
||||
> = (p) => {
|
||||
const propConfig =
|
||||
p.propConfig ?? getPropTypeByPointer(p.pointerToProp, p.obj)
|
||||
|
||||
|
@ -95,7 +103,7 @@ const DeterminePropEditor: React.FC<{
|
|||
return (
|
||||
<PropEditor
|
||||
obj={p.obj}
|
||||
depth={p.depth}
|
||||
visualIndentation={p.visualIndentation}
|
||||
// @ts-expect-error This is fine
|
||||
pointerToProp={p.pointerToProp}
|
||||
// @ts-expect-error This is fine
|
||||
|
|
|
@ -8,5 +8,5 @@ export type IPropEditorFC<TPropTypeConfig extends IBasePropType<string, any>> =
|
|||
propConfig: TPropTypeConfig
|
||||
pointerToProp: Pointer<TPropTypeConfig['valueType']>
|
||||
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 getDeep from '@theatre/shared/utils/getDeep'
|
||||
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 type {Pointer} from '@theatre/dataverse'
|
||||
import get from 'lodash-es/get'
|
||||
|
@ -15,6 +15,7 @@ import DefaultOrStaticValueIndicator from './DefaultValueIndicator'
|
|||
import NextPrevKeyframeCursors from './NextPrevKeyframeCursors'
|
||||
import type {PropTypeConfig} from '@theatre/core/propTypes'
|
||||
import {isPropConfSequencable} from '@theatre/shared/propTypes/utils'
|
||||
import type {SequenceTrackId} from '@theatre/shared/utils/ids'
|
||||
|
||||
interface CommonStuff<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(
|
||||
'lcr',
|
||||
(): NearbyKeyframes => {
|
||||
|
@ -155,7 +156,7 @@ export function useEditingToolsForPrimitiveProp<
|
|||
obj.template.project.pointers.historic.sheetsById[
|
||||
obj.address.sheetId
|
||||
].sequence.tracksByObject[obj.address.objectKey].trackData[
|
||||
sequenceTrcackId
|
||||
sequenceTrackId
|
||||
],
|
||||
)
|
||||
if (!track || track.keyframes.length === 0) return {}
|
||||
|
@ -186,7 +187,7 @@ export function useEditingToolsForPrimitiveProp<
|
|||
}
|
||||
}
|
||||
},
|
||||
[sequenceTrcackId],
|
||||
[sequenceTrackId],
|
||||
)
|
||||
|
||||
let shade: Shade
|
||||
|
|
|
@ -26,7 +26,7 @@ const ObjectsList: React.FC<{
|
|||
<ObjectItem
|
||||
depth={depth}
|
||||
key={'objectPath(' + objectPath + ')'}
|
||||
sheetObject={object}
|
||||
sheetObject={object!}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
|
|
@ -44,7 +44,10 @@ const BasicKeyframedTrack: React.FC<BasicKeyframedTracksProps> = React.memo(
|
|||
selection: val(selectionAtom.pointer.current),
|
||||
}
|
||||
} else {
|
||||
return {selectedKeyframeIds: {}, selection: undefined}
|
||||
return {
|
||||
selectedKeyframeIds: {},
|
||||
selection: undefined,
|
||||
}
|
||||
}
|
||||
}, [layoutP, leaf.trackId])
|
||||
|
||||
|
|
|
@ -92,7 +92,7 @@ const HitZone = styled.div`
|
|||
type IKeyframeDotProps = IKeyframeEditorProps
|
||||
|
||||
/** 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 [isDragging] = useDragKeyframe(node, props)
|
||||
|
|
|
@ -79,8 +79,8 @@ function useCaptureSelection(
|
|||
|
||||
val(layoutP.selectionAtom).setState({current: undefined})
|
||||
},
|
||||
onDrag(dx, dy, event) {
|
||||
const state = ref.current!
|
||||
onDrag(_dx, _dy, event) {
|
||||
// const state = ref.current!
|
||||
const rect = containerNode!.getBoundingClientRect()
|
||||
|
||||
const posInScaledSpace = event.clientX - rect.left
|
||||
|
@ -97,25 +97,13 @@ function useCaptureSelection(
|
|||
const selection = utils.boundsToSelection(layoutP, ref.current)
|
||||
val(layoutP.selectionAtom).setState({current: selection})
|
||||
},
|
||||
onDragEnd(dragHappened) {
|
||||
onDragEnd(_dragHappened) {
|
||||
ref.current = null
|
||||
},
|
||||
}
|
||||
}, [layoutP, containerNode, ref]),
|
||||
)
|
||||
|
||||
// useEffect(() => {
|
||||
// if (!containerNode) return
|
||||
// const onClick = () => {
|
||||
|
||||
// }
|
||||
// containerNode.addEventListener('click', onClick)
|
||||
|
||||
// return () => {
|
||||
// containerNode.removeEventListener('click', onClick)
|
||||
// }
|
||||
// }, [containerNode])
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
|
@ -131,16 +119,11 @@ namespace utils {
|
|||
primitiveProp(layoutP, leaf, bounds, selection) {
|
||||
const {sheetObject, trackId} = leaf
|
||||
const trackData = val(
|
||||
getStudio()!.atomP.historic.coreByProject[sheetObject.address.projectId]
|
||||
getStudio().atomP.historic.coreByProject[sheetObject.address.projectId]
|
||||
.sheetsById[sheetObject.address.sheetId].sequence.tracksByObject[
|
||||
sheetObject.address.objectKey
|
||||
].trackData[trackId],
|
||||
)!
|
||||
const toCollect = trackData!.keyframes.filter(
|
||||
(kf) =>
|
||||
kf.position >= bounds.positions[0] &&
|
||||
kf.position <= bounds.positions[1],
|
||||
)
|
||||
|
||||
for (const kf of trackData.keyframes) {
|
||||
if (kf.position <= bounds.positions[0]) continue
|
||||
|
|
|
@ -25,7 +25,7 @@ export type ExtremumSpace = {
|
|||
lock(): VoidFn
|
||||
}
|
||||
|
||||
const BasicKeyframedTrack: React.FC<{
|
||||
const BasicKeyframedTrack: React.VFC<{
|
||||
layoutP: Pointer<SequenceEditorPanelLayout>
|
||||
sheetObject: SheetObject
|
||||
pathToProp: PathToProp
|
||||
|
@ -102,7 +102,7 @@ const BasicKeyframedTrack: React.FC<{
|
|||
sheetObject={sheetObject}
|
||||
trackId={trackId}
|
||||
isScalar={propConfig.type === 'number'}
|
||||
key={'keyframe-' + kf.id}
|
||||
key={kf.id}
|
||||
extremumSpace={cachedExtremumSpace.current}
|
||||
color={color}
|
||||
/>
|
||||
|
|
|
@ -15,7 +15,7 @@ const SVGPath = styled.path`
|
|||
|
||||
type IProps = Parameters<typeof KeyframeEditor>[0]
|
||||
|
||||
const Curve: React.FC<IProps> = (props) => {
|
||||
const Curve: React.VFC<IProps> = (props) => {
|
||||
const {index, trackData} = props
|
||||
const cur = trackData.keyframes[index]
|
||||
const next = trackData.keyframes[index + 1]
|
||||
|
|
|
@ -49,7 +49,7 @@ type Which = 'left' | 'right'
|
|||
|
||||
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 {index, trackData} = props
|
||||
|
|
|
@ -55,7 +55,7 @@ const HitZone = styled.circle`
|
|||
|
||||
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 {index, trackData} = props
|
||||
|
|
|
@ -55,7 +55,7 @@ const HitZone = styled.circle`
|
|||
|
||||
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 {index, trackData} = props
|
||||
|
|
|
@ -17,7 +17,7 @@ const SVGPath = styled.path`
|
|||
|
||||
type IProps = Parameters<typeof KeyframeEditor>[0]
|
||||
|
||||
const GraphEditorNonScalarDash: React.FC<IProps> = (props) => {
|
||||
const GraphEditorNonScalarDash: React.VFC<IProps> = (props) => {
|
||||
const {index, trackData} = props
|
||||
|
||||
const pathD = `M 0 0 L 1 1`
|
||||
|
|
|
@ -23,7 +23,7 @@ const Container = styled.g`
|
|||
|
||||
const noConnector = <></>
|
||||
|
||||
const KeyframeEditor: React.FC<{
|
||||
type IKeyframeEditorProps = {
|
||||
index: number
|
||||
keyframe: Keyframe
|
||||
trackData: TrackData
|
||||
|
@ -34,7 +34,9 @@ const KeyframeEditor: React.FC<{
|
|||
isScalar: boolean
|
||||
color: keyof typeof graphEditorColors
|
||||
propConfig: PropTypeConfig_AllSimples
|
||||
}> = (props) => {
|
||||
}
|
||||
|
||||
const KeyframeEditor: React.VFC<IKeyframeEditorProps> = (props) => {
|
||||
const {index, trackData, isScalar} = props
|
||||
const cur = trackData.keyframes[index]
|
||||
const next = trackData.keyframes[index + 1]
|
||||
|
|
|
@ -27,6 +27,7 @@ import {
|
|||
TitleBar_Piece,
|
||||
TitleBar_Punctuation,
|
||||
} from '@theatre/studio/panels/BasePanel/common'
|
||||
import type {UIPanelId} from '@theatre/shared/utils/ids'
|
||||
|
||||
const Container = styled(PanelWrapper)`
|
||||
z-index: ${panelZIndexes.sequenceEditorPanel};
|
||||
|
@ -58,7 +59,7 @@ export const zIndexes = (() => {
|
|||
// sort the z-indexes
|
||||
let i = -1
|
||||
for (const key of Object.keys(s)) {
|
||||
s[key as unknown as keyof typeof s] = i
|
||||
s[key] = i
|
||||
i++
|
||||
}
|
||||
|
||||
|
@ -83,10 +84,10 @@ const defaultPosition: PanelPosition = {
|
|||
|
||||
const minDims = {width: 800, height: 200}
|
||||
|
||||
const SequenceEditorPanel: React.FC<{}> = (props) => {
|
||||
const SequenceEditorPanel: React.VFC<{}> = (props) => {
|
||||
return (
|
||||
<BasePanel
|
||||
panelId="sequenceEditor"
|
||||
panelId={'sequenceEditor' as UIPanelId}
|
||||
defaultPosition={defaultPosition}
|
||||
minDims={minDims}
|
||||
>
|
||||
|
@ -95,7 +96,7 @@ const SequenceEditorPanel: React.FC<{}> = (props) => {
|
|||
)
|
||||
}
|
||||
|
||||
const Content: React.FC<{}> = () => {
|
||||
const Content: React.VFC<{}> = () => {
|
||||
const {dims} = usePanel()
|
||||
|
||||
return usePrism(() => {
|
||||
|
|
|
@ -14,6 +14,11 @@ import {Atom, prism, val} from '@theatre/dataverse'
|
|||
import type {SequenceEditorTree} from './tree'
|
||||
import {calculateSequenceEditorTree} from './tree'
|
||||
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
|
||||
type DimsOfPanelPart = {
|
||||
|
@ -41,20 +46,20 @@ export type PanelDims = {
|
|||
export type DopeSheetSelection = {
|
||||
type: 'DopeSheetSelection'
|
||||
byObjectKey: StrictRecord<
|
||||
string,
|
||||
ObjectAddressKey,
|
||||
{
|
||||
byTrackId: StrictRecord<
|
||||
string,
|
||||
SequenceTrackId,
|
||||
{
|
||||
byKeyframeId: StrictRecord<string, true>
|
||||
byKeyframeId: StrictRecord<KeyframeId, true>
|
||||
}
|
||||
>
|
||||
}
|
||||
>
|
||||
getDragHandlers(
|
||||
origin: PropAddress & {
|
||||
trackId: string
|
||||
keyframeId: string
|
||||
trackId: SequenceTrackId
|
||||
keyframeId: KeyframeId
|
||||
positionAtStartOfDrag: number
|
||||
domNode: Element
|
||||
},
|
||||
|
@ -156,8 +161,8 @@ export type SequenceEditorPanelLayout = {
|
|||
selectionAtom: Atom<{current?: DopeSheetSelection}>
|
||||
}
|
||||
|
||||
// type UnitSpaceProression = Nominal<number, 'unitSpaceProgression'>
|
||||
// type ClippedSpaceProgression = Nominal<number, 'ClippedSpaceProgression'>
|
||||
// type UnitSpaceProression = number
|
||||
// type ClippedSpaceProgression = number
|
||||
|
||||
/**
|
||||
* This means the left side of the panel is 20% of its width, and the
|
||||
|
|
|
@ -87,8 +87,10 @@ export const calculateSequenceEditorTree = (
|
|||
topSoFar += tree.nodeHeight
|
||||
nSoFar += 1
|
||||
|
||||
for (const [_, sheetObject] of Object.entries(val(sheet.objectsP))) {
|
||||
addObject(sheetObject, tree.children, tree.depth + 1)
|
||||
for (const sheetObject of Object.values(val(sheet.objectsP))) {
|
||||
if (sheetObject) {
|
||||
addObject(sheetObject, tree.children, tree.depth + 1)
|
||||
}
|
||||
}
|
||||
tree.heightIncludingChildren = topSoFar - tree.top
|
||||
|
||||
|
|
|
@ -3,7 +3,9 @@ import type Sequence from '@theatre/core/sequences/Sequence'
|
|||
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
||||
import type Sheet from '@theatre/core/sheets/Sheet'
|
||||
import {val} from '@theatre/dataverse'
|
||||
import type {$IntentionalAny} from '@theatre/dataverse/src/types'
|
||||
import {isSheet, isSheetObject} from '@theatre/shared/instanceTypes'
|
||||
import type {SheetId} from '@theatre/shared/utils/ids'
|
||||
import {uniq} from 'lodash-es'
|
||||
import getStudio from './getStudio'
|
||||
import type {OutlineSelectable, OutlineSelection} from './store/types'
|
||||
|
@ -46,7 +48,7 @@ export const getSelectedInstanceOfSheetId = (
|
|||
]
|
||||
|
||||
const instanceId = val(
|
||||
projectStateP.stateBySheetId[selectedSheetId].selectedInstanceId,
|
||||
projectStateP.stateBySheetId[selectedSheetId as SheetId].selectedInstanceId,
|
||||
)
|
||||
|
||||
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.
|
||||
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
|
||||
* makes sure we don't cause re-renders
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type {
|
||||
HistoricPositionalSequence,
|
||||
Keyframe,
|
||||
SheetState_Historic,
|
||||
} from '@theatre/core/projects/store/types/SheetState_Historic'
|
||||
|
@ -11,7 +12,11 @@ import type {
|
|||
WithoutSheetInstance,
|
||||
} 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 {
|
||||
generateKeyframeId,
|
||||
generateSequenceTrackId,
|
||||
|
@ -72,7 +77,7 @@ namespace stateEditors {
|
|||
export namespace historic {
|
||||
export namespace panelPositions {
|
||||
export function setPanelPosition(p: {
|
||||
panelId: string
|
||||
panelId: UIPanelId
|
||||
position: PanelPosition
|
||||
}) {
|
||||
const h = drafts().historic
|
||||
|
@ -429,7 +434,9 @@ namespace stateEditors {
|
|||
}
|
||||
|
||||
export namespace sequence {
|
||||
export function _ensure(p: WithoutSheetInstance<SheetAddress>) {
|
||||
export function _ensure(
|
||||
p: WithoutSheetInstance<SheetAddress>,
|
||||
): HistoricPositionalSequence {
|
||||
const s = stateEditors.coreByProject.historic.sheetsById._ensure(p)
|
||||
s.sequence ??= {
|
||||
subUnitsPerUnit: 30,
|
||||
|
@ -529,7 +536,9 @@ namespace stateEditors {
|
|||
}
|
||||
|
||||
function _getTrack(
|
||||
p: WithoutSheetInstance<SheetObjectAddress> & {trackId: string},
|
||||
p: WithoutSheetInstance<SheetObjectAddress> & {
|
||||
trackId: SequenceTrackId
|
||||
},
|
||||
) {
|
||||
return _ensureTracksOfObject(p).trackData[p.trackId]
|
||||
}
|
||||
|
@ -540,7 +549,7 @@ namespace stateEditors {
|
|||
*/
|
||||
export function setKeyframeAtPosition<T>(
|
||||
p: WithoutSheetInstance<SheetObjectAddress> & {
|
||||
trackId: string
|
||||
trackId: SequenceTrackId
|
||||
position: number
|
||||
handles?: [number, number, number, number]
|
||||
value: T
|
||||
|
@ -585,7 +594,7 @@ namespace stateEditors {
|
|||
|
||||
export function unsetKeyframeAtPosition(
|
||||
p: WithoutSheetInstance<SheetObjectAddress> & {
|
||||
trackId: string
|
||||
trackId: SequenceTrackId
|
||||
position: number
|
||||
},
|
||||
) {
|
||||
|
@ -604,7 +613,7 @@ namespace stateEditors {
|
|||
|
||||
export function transformKeyframes(
|
||||
p: WithoutSheetInstance<SheetObjectAddress> & {
|
||||
trackId: string
|
||||
trackId: SequenceTrackId
|
||||
keyframeIds: KeyframeId[]
|
||||
translate: number
|
||||
scale: number
|
||||
|
@ -633,7 +642,7 @@ namespace stateEditors {
|
|||
|
||||
export function deleteKeyframes(
|
||||
p: WithoutSheetInstance<SheetObjectAddress> & {
|
||||
trackId: string
|
||||
trackId: SequenceTrackId
|
||||
keyframeIds: KeyframeId[]
|
||||
},
|
||||
) {
|
||||
|
@ -645,9 +654,9 @@ namespace stateEditors {
|
|||
)
|
||||
}
|
||||
|
||||
export function replaceKeyframes<T>(
|
||||
export function replaceKeyframes(
|
||||
p: WithoutSheetInstance<SheetObjectAddress> & {
|
||||
trackId: string
|
||||
trackId: SequenceTrackId
|
||||
keyframes: Array<Keyframe>
|
||||
snappingFunction: SnappingFunction
|
||||
},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type {ProjectState} from '@theatre/core/projects/store/storeTypes'
|
||||
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'
|
||||
|
||||
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 Sheet from '@theatre/core/sheets/Sheet'
|
||||
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
||||
import type {
|
||||
ObjectAddressKey,
|
||||
PaneInstanceId,
|
||||
ProjectId,
|
||||
SheetId,
|
||||
SheetInstanceId,
|
||||
UIPanelId,
|
||||
} from '@theatre/shared/utils/ids'
|
||||
|
||||
export type PanelPosition = {
|
||||
edges: {
|
||||
|
@ -56,37 +64,55 @@ export type OutlineSelectionState =
|
|||
export type OutlineSelectable = Project | Sheet | SheetObject
|
||||
export type OutlineSelection = OutlineSelectable[]
|
||||
|
||||
export type PanelInstanceDescriptor = {
|
||||
instanceId: string
|
||||
export type PaneInstanceDescriptor = {
|
||||
instanceId: PaneInstanceId
|
||||
paneClass: string
|
||||
}
|
||||
|
||||
export type StudioHistoricState = {
|
||||
projects: {
|
||||
stateByProjectId: StrictRecord<
|
||||
string,
|
||||
{
|
||||
stateBySheetId: StrictRecord<
|
||||
string,
|
||||
{
|
||||
selectedInstanceId: undefined | string
|
||||
sequenceEditor: {
|
||||
selectedPropsByObject: StrictRecord<
|
||||
string,
|
||||
StrictRecord<PathToProp_Encoded, keyof typeof graphEditorColors>
|
||||
>
|
||||
}
|
||||
}
|
||||
>
|
||||
}
|
||||
/**
|
||||
* See parent {@link StudioHistoricStateProject}.
|
||||
* See root {@link StudioHistoricState}
|
||||
*/
|
||||
export type StudioHistoricStateProjectSheet = {
|
||||
selectedInstanceId: undefined | SheetInstanceId
|
||||
sequenceEditor: {
|
||||
selectedPropsByObject: StrictRecord<
|
||||
ObjectAddressKey,
|
||||
StrictRecord<PathToProp_Encoded, keyof typeof graphEditorColors>
|
||||
>
|
||||
}
|
||||
|
||||
panels?: Panels
|
||||
panelPositions?: {[panelIdOrPaneId in string]?: PanelPosition}
|
||||
panelInstanceDesceriptors: {
|
||||
[instanceId in string]?: PanelInstanceDescriptor
|
||||
}
|
||||
autoKey: boolean
|
||||
coreByProject: {[projectId in string]: ProjectState_Historic}
|
||||
}
|
||||
|
||||
/** 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 can contain panes */
|
||||
panelPositions?: {[panelId in UIPanelId]?: PanelPosition}
|
||||
// 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
|
||||
coreByProject: Record<ProjectId, ProjectState_Historic>
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue