Document utilities and remove unused code
This commit is contained in:
parent
dd585b0790
commit
464ce24923
18 changed files with 170 additions and 124 deletions
|
@ -1,12 +1,23 @@
|
|||
import type {
|
||||
$IntentionalAny,
|
||||
SerializableMap,
|
||||
SerializableValue,
|
||||
} from '@theatre/shared/utils/types'
|
||||
import type {ObjectAddressKey, ProjectId, SheetId, SheetInstanceId} from './ids'
|
||||
import memoizeFn from './memoizeFn'
|
||||
import type {Nominal} from './Nominal'
|
||||
|
||||
/**
|
||||
* Addresses are used to identify projects, sheets, objects, and other things.
|
||||
*
|
||||
* For example, a project's address looks like `{projectId: 'my-project'}`, and a sheet's
|
||||
* address looks like `{projectId: 'my-project', sheetId: 'my-sheet'}`.
|
||||
*
|
||||
* As you see, a Sheet's address is a superset of a Project's address. This is so that we can
|
||||
* use the same address type for both. All addresses follow the same rule. An object's address
|
||||
* extends its sheet's address, which extends its project's address.
|
||||
*
|
||||
* For example, generating an object's address from a sheet's address is as simple as `{...sheetAddress, objectId: 'my-object'}`.
|
||||
*
|
||||
* Also, if you need the projectAddress of an object, you can just re-use the object's address:
|
||||
* `aFunctionThatRequiresProjectAddress(objectAddress)`.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the address to a project
|
||||
*/
|
||||
|
@ -23,6 +34,8 @@ export interface ProjectAddress {
|
|||
* sheet.address.sheetId === 'a sheet'
|
||||
* sheet.address.sheetInstanceId === 'sheetInstanceId'
|
||||
* ```
|
||||
*
|
||||
* See {@link WithoutSheetInstance} for a type that doesn't include the sheet instance id.
|
||||
*/
|
||||
export interface SheetAddress extends ProjectAddress {
|
||||
sheetId: SheetId
|
||||
|
@ -31,7 +44,9 @@ export interface SheetAddress extends ProjectAddress {
|
|||
|
||||
/**
|
||||
* Removes `sheetInstanceId` from an address, making it refer to
|
||||
* all instances of a certain `sheetId`
|
||||
* all instances of a certain `sheetId`.
|
||||
*
|
||||
* See {@link SheetAddress} for a type that includes the sheet instance id.
|
||||
*/
|
||||
export type WithoutSheetInstance<T extends SheetAddress> = Omit<
|
||||
T,
|
||||
|
@ -42,7 +57,10 @@ export type SheetInstanceOptional<T extends SheetAddress> =
|
|||
WithoutSheetInstance<T> & {sheetInstanceId?: SheetInstanceId | undefined}
|
||||
|
||||
/**
|
||||
* Represents the address to a Sheet's Object
|
||||
* Represents the address to a Sheet's Object.
|
||||
*
|
||||
* It includes the sheetInstance, so it's specific to a single instance of a sheet. If you
|
||||
* would like an address that doesn't include the sheetInstance, use `WithoutSheetInstance<SheetObjectAddress>`.
|
||||
*/
|
||||
export interface SheetObjectAddress extends SheetAddress {
|
||||
/**
|
||||
|
@ -57,15 +75,32 @@ export interface SheetObjectAddress extends SheetAddress {
|
|||
objectKey: ObjectAddressKey
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a simple array representing the path to a prop, without specifying the object.
|
||||
*/
|
||||
export type PathToProp = Array<string | number>
|
||||
|
||||
/**
|
||||
* Just like {@link PathToProp}, but encoded as a string. Since this type is nominal,
|
||||
* it can only be generated using {@link encodePathToProp}.
|
||||
*/
|
||||
export type PathToProp_Encoded = Nominal<'PathToProp_Encoded'>
|
||||
|
||||
/**
|
||||
* Encodes a {@link PathToProp} as a string, and caches the result, so as long
|
||||
* as the input is the same, the output won't have to be re-generated.
|
||||
*/
|
||||
export const encodePathToProp = memoizeFn(
|
||||
(p: PathToProp): PathToProp_Encoded =>
|
||||
// we're using JSON.stringify here, but we could use a faster alternative.
|
||||
// If you happen to do that, first make sure no `PathToProp_Encoded` is ever
|
||||
// used in the store, otherwise you'll have to write a migration.
|
||||
JSON.stringify(p) as PathToProp_Encoded,
|
||||
)
|
||||
|
||||
/**
|
||||
* The decoder of {@link encodePathToProp}.
|
||||
*/
|
||||
export const decodePathToProp = (s: PathToProp_Encoded): PathToProp =>
|
||||
JSON.parse(s)
|
||||
|
||||
|
@ -76,52 +111,35 @@ export interface PropAddress extends SheetObjectAddress {
|
|||
pathToProp: PathToProp
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the address of a certain sequence of a sheet.
|
||||
*
|
||||
* Since currently sheets are single-sequence only, `sequenceName` is always `'default'` for now.
|
||||
*/
|
||||
export interface SequenceAddress extends SheetAddress {
|
||||
sequenceName: string
|
||||
}
|
||||
|
||||
export const getValueByPropPath = (
|
||||
pathToProp: PathToProp,
|
||||
rootVal: SerializableMap,
|
||||
): undefined | SerializableValue => {
|
||||
const p = [...pathToProp]
|
||||
let cur: $IntentionalAny = rootVal
|
||||
|
||||
while (p.length !== 0) {
|
||||
const key = p.shift()!
|
||||
|
||||
if (cur !== null && typeof cur === 'object') {
|
||||
if (Array.isArray(cur)) {
|
||||
if (typeof key === 'number') {
|
||||
cur = cur[key]
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
} else {
|
||||
if (typeof key === 'string') {
|
||||
cur = cur[key]
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
return cur
|
||||
}
|
||||
|
||||
export function doesPathStartWith(
|
||||
path: (string | number)[],
|
||||
pathPrefix: (string | number)[],
|
||||
) {
|
||||
/**
|
||||
* Returns true if `path` starts with `pathPrefix`.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* const prefix: PathToProp = ['a', 'b']
|
||||
* console.log(doesPathStartWith(['a', 'b', 'c'], prefix)) // true
|
||||
* console.log(doesPathStartWith(['x', 'b', 'c'], prefix)) // false
|
||||
* ```
|
||||
*/
|
||||
export function doesPathStartWith(path: PathToProp, pathPrefix: PathToProp) {
|
||||
return pathPrefix.every((pathPart, i) => pathPart === path[i])
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if pathToPropA and pathToPropB are equal.
|
||||
*/
|
||||
export function arePathsEqual(
|
||||
pathToPropA: (string | number)[],
|
||||
pathToPropB: (string | number)[],
|
||||
pathToPropA: PathToProp,
|
||||
pathToPropB: PathToProp,
|
||||
) {
|
||||
if (pathToPropA.length !== pathToPropB.length) return false
|
||||
for (let i = 0; i < pathToPropA.length; i++) {
|
||||
|
@ -131,7 +149,9 @@ export function arePathsEqual(
|
|||
}
|
||||
|
||||
/**
|
||||
* e.g.
|
||||
* Given an array of `PathToProp`s, returns the longest common prefix.
|
||||
*
|
||||
* Example
|
||||
* ```
|
||||
* commonRootOfPathsToProps([
|
||||
* ['a','b','c','d','e'],
|
||||
|
@ -140,8 +160,8 @@ export function arePathsEqual(
|
|||
* ]) // = ['a','b']
|
||||
* ```
|
||||
*/
|
||||
export function commonRootOfPathsToProps(pathsToProps: (string | number)[][]) {
|
||||
const commonPathToProp: (string | number)[] = []
|
||||
export function commonRootOfPathsToProps(pathsToProps: PathToProp[]) {
|
||||
const commonPathToProp: PathToProp = []
|
||||
while (true) {
|
||||
const i = commonPathToProp.length
|
||||
let candidatePathPart = pathsToProps[0]?.[i]
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
export default function createWeakCache(): WeakCache {
|
||||
const cache = new Map()
|
||||
const cleanup = new FinalizationRegistry((key) => {
|
||||
const ref = cache.get(key)
|
||||
if (ref && !ref.deref()) cache.delete(key)
|
||||
})
|
||||
|
||||
return function getOrSet<T extends {}>(key: string, producer: () => T): T {
|
||||
const ref = cache.get(key)
|
||||
if (ref) {
|
||||
const cached = ref.deref()
|
||||
if (cached !== undefined) return cached
|
||||
}
|
||||
|
||||
const fresh = producer()
|
||||
cache.set(key, new WeakRef(fresh))
|
||||
cleanup.register(fresh, key)
|
||||
return fresh
|
||||
}
|
||||
}
|
||||
|
||||
export interface WeakCache {
|
||||
<T extends {}>(key: string, producer: () => T): T
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import type {DeepPartialOfSerializableValue, SerializableMap} from './types'
|
||||
|
||||
export default function deepMerge<T extends SerializableMap>(
|
||||
base: T,
|
||||
override: DeepPartialOfSerializableValue<T>,
|
||||
): T {
|
||||
const merged = {...base}
|
||||
for (const key of Object.keys(override)) {
|
||||
const valueInOverride = override[key]
|
||||
const valueInBase = base[key]
|
||||
|
||||
// @ts-ignore @todo
|
||||
merged[key] =
|
||||
typeof valueInOverride === 'object' && typeof valueInBase === 'object'
|
||||
? deepMerge(valueInBase, valueInOverride)
|
||||
: valueInOverride
|
||||
}
|
||||
|
||||
return merged
|
||||
}
|
|
@ -39,7 +39,7 @@ import type {DeepPartialOfSerializableValue, SerializableMap} from './types'
|
|||
*
|
||||
* 4. Both `base` and `override` must be plain JSON values and *NO* arrays, so: `boolean, string, number, undefined, {}`
|
||||
*
|
||||
* Rationale: This is used in {@link SheetObject.getValues()} to deep-merge static and sequenced
|
||||
* Rationale: This is used in {@link SheetObject.getValues} to deep-merge static and sequenced
|
||||
* and other types of overrides. If we were to do a deep-merge without a cache, we'd be creating and discarding
|
||||
* several JS objects on each frame for every Theatre object, and that would pressure the GC.
|
||||
* Plus, keeping the values referentially stable helps lib authors optimize how they patch these values
|
||||
|
|
|
@ -5,6 +5,28 @@ export interface Deferred<PromiseType> {
|
|||
status: 'pending' | 'resolved' | 'rejected'
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple imperative API for resolving/rejecting a promise.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* function doSomethingAsync() {
|
||||
* const deferred = defer()
|
||||
*
|
||||
* setTimeout(() => {
|
||||
* if (Math.random() > 0.5) {
|
||||
* deferred.resolve('success')
|
||||
* } else {
|
||||
* deferred.reject('Something went wrong')
|
||||
* }
|
||||
* }, 1000)
|
||||
*
|
||||
* // we're just returning the promise, so that the caller cannot resolve/reject it
|
||||
* return deferred.promise
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
export function defer<PromiseType>(): Deferred<PromiseType> {
|
||||
let resolve: (d: PromiseType) => void
|
||||
let reject: (d: unknown) => void
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import propose from 'propose'
|
||||
|
||||
/**
|
||||
* Proposes a suggestion to fix a typo in `str`, using the options provided in `dictionary`.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* didYouMean('helo', ['hello', 'world']) // 'Did you mean "hello"?'
|
||||
* ```
|
||||
*/
|
||||
export default function didYouMean(
|
||||
str: string,
|
||||
dictionary: string[],
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
/**
|
||||
* Truncates a string to a given length, adding an ellipsis if it was truncated.
|
||||
* Example:
|
||||
* ```ts
|
||||
* ellipsify('hello world', 5) // 'hello...'
|
||||
* ellipsify('hello world', 100) // 'hello world'
|
||||
* ```
|
||||
*/
|
||||
export default function ellipsify(str: string, maxLength: number) {
|
||||
if (str.length <= maxLength) return str
|
||||
return str.substr(0, maxLength - 3) + '...'
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
/**
|
||||
* All errors thrown to end-users should be an instance of this class.
|
||||
*/
|
||||
export class TheatreError extends Error {}
|
||||
|
||||
/**
|
||||
* If an end-user provided an invalid argument to a public API, the error thrown
|
||||
* should be an instance of this class.
|
||||
*/
|
||||
export class InvalidArgumentError extends TheatreError {}
|
||||
|
|
|
@ -1,6 +1,29 @@
|
|||
import type {PathToProp} from './addresses'
|
||||
import type {$IntentionalAny, SerializableMap} from './types'
|
||||
|
||||
/**
|
||||
* Iterates recursively over all props of an object (which should be a {@link SerializableMap}) and runs `fn`
|
||||
* on each prop that has a primitive value (string/number/boolean) and is _NOT_ null/undefined.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* forEachDeep(
|
||||
* // The object to iterate over. The `fn` is going to be called on `b` and `c`.
|
||||
* {a: {b: 1, c: 2, d: null, e: undefined}},
|
||||
* // the function to run on each prop
|
||||
* (value, pathToValue) => {
|
||||
* console.log(value, pathToValue)
|
||||
* },
|
||||
* // We can optionally pass a path prefix to prepend to the path of each prop
|
||||
* ['foo', 'bar'])
|
||||
*
|
||||
* // The above will log:
|
||||
* // 1 ['foo', 'bar', 'a', 'b']
|
||||
* // 2 ['foo', 'bar', 'a', 'c']
|
||||
* // Note that null and undefined values are skipped.
|
||||
* // Also note that `a` is also skippped, because it's not a primitive value.
|
||||
* ```
|
||||
*/
|
||||
export default function forEachDeep<
|
||||
Primitive extends string | number | boolean,
|
||||
>(
|
||||
|
|
|
@ -2,6 +2,18 @@ import lodashGet from 'lodash-es/get'
|
|||
import type {PathToProp} from './addresses'
|
||||
import type {SerializableValue} from './types'
|
||||
|
||||
/**
|
||||
* Returns the value at `path` of `v`.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* getDeep({a: {b: 1}}, ['a', 'b']) // 1
|
||||
* getDeep({a: {b: 1}}, ['a', 'c']) // undefined
|
||||
* getDeep({a: {b: 1}}, []) // {a: {b: 1}}
|
||||
* getDeep('hello', []) // 'hello''
|
||||
* getDeep('hello', ['a']) // undefined
|
||||
* ```
|
||||
*/
|
||||
export default function getDeep(
|
||||
v: SerializableValue,
|
||||
path: PathToProp,
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import type {$FixMe} from './types'
|
||||
|
||||
const getPropsInCommon = (a: $FixMe, b: $FixMe): (string | number)[] => {
|
||||
const keysInA = Object.keys(a)
|
||||
|
||||
const inCommon: (string | number)[] = []
|
||||
for (const key of keysInA) {
|
||||
if (b.hasOwnProperty(key)) {
|
||||
inCommon.push(key)
|
||||
}
|
||||
}
|
||||
|
||||
return inCommon
|
||||
}
|
||||
|
||||
export default getPropsInCommon
|
|
@ -1,3 +0,0 @@
|
|||
export default function identity<T>(a: T) {
|
||||
return a
|
||||
}
|
|
@ -1,6 +1,11 @@
|
|||
import type {VoidFn} from './types'
|
||||
|
||||
export const voidFn: VoidFn = () => {}
|
||||
|
||||
/**
|
||||
* This is just an empty object used in place of `{}` when you want to:
|
||||
* 1. Not create many new objects (less GC pressure)
|
||||
* 2. Have the empty object be a singleton (so that `===` works), so it can be fed to memoized functions.
|
||||
*/
|
||||
export const emptyObject = {}
|
||||
|
||||
/**
|
||||
* The array equivalent of {@link emptyObject}.
|
||||
*/
|
||||
export const emptyArray: ReadonlyArray<unknown> = []
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/**
|
||||
* Memoizes a unary function using a simple weakmap.
|
||||
* Memoizes a unary function using a simple weakmap. The argument to the unary
|
||||
* function must be WeakCache-able, which means it must be an object and not a plain
|
||||
* number/string/boolean/etc.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
|
|
|
@ -21,10 +21,6 @@ function typeOfValue(v: unknown): ValueType {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @remarks
|
||||
* TODO explain what this does.
|
||||
*/
|
||||
export default function minimalOverride<T>(base: T, override: T): T {
|
||||
const typeofOverride = typeOfValue(override)
|
||||
if (typeofOverride === ValueType.Opaque) {
|
||||
|
|
|
@ -7,6 +7,9 @@ export type ReduxReducer<State extends {}> = (
|
|||
|
||||
export type VoidFn = () => void
|
||||
|
||||
/**
|
||||
* A `SerializableMap` is a plain JS object that can be safely serialized to JSON.
|
||||
*/
|
||||
export type SerializableMap<
|
||||
Primitives extends SerializablePrimitive = SerializablePrimitive,
|
||||
> = {[Key in string]?: SerializableValue<Primitives>}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {voidFn} from '@theatre/shared/utils'
|
||||
import noop from '@theatre/shared/utils/noop'
|
||||
import React, {createContext, useCallback, useContext, useRef} from 'react'
|
||||
import styled from 'styled-components'
|
||||
import {zIndexes} from './SequenceEditorPanel'
|
||||
|
@ -22,7 +22,7 @@ const Container = styled.div`
|
|||
|
||||
type ReceiveVerticalWheelEventFn = (ev: Pick<WheelEvent, 'deltaY'>) => void
|
||||
|
||||
const ctx = createContext<ReceiveVerticalWheelEventFn>(voidFn)
|
||||
const ctx = createContext<ReceiveVerticalWheelEventFn>(noop)
|
||||
|
||||
/**
|
||||
* See {@link VerticalScrollContainer} and references for how to use this.
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import identity from '@theatre/shared/utils/identity'
|
||||
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
||||
|
||||
function identity<T>(a: T) {
|
||||
return a
|
||||
}
|
||||
|
||||
interface Transformer<
|
||||
Input extends $IntentionalAny,
|
||||
Output extends $IntentionalAny,
|
||||
|
|
Loading…
Reference in a new issue