From d260f74c06b01e31b27764a9b37dd796f083156a Mon Sep 17 00:00:00 2001 From: Aria Minaei Date: Thu, 6 Oct 2022 14:05:04 +0200 Subject: [PATCH] WIP - Implement dynamic prop types TODO: Tests --- theatre/core/src/sheetObjects/SheetObject.ts | 5 +++-- .../src/sheetObjects/SheetObjectTemplate.ts | 16 +++++++++----- theatre/core/src/sheets/TheatreSheet.ts | 21 ++++++++++++++----- .../createTransactionPrivateApi.ts | 4 ++-- .../DeterminePropEditorForDetail.tsx | 19 +++++++---------- .../src/panels/DetailPanel/ObjectDetails.tsx | 4 +++- .../BasicKeyframedTrack.tsx | 3 ++- .../panels/SequenceEditorPanel/layout/tree.ts | 3 ++- .../utils/getPropTypeByPointer.tsx | 4 +++- 9 files changed, 50 insertions(+), 29 deletions(-) diff --git a/theatre/core/src/sheetObjects/SheetObject.ts b/theatre/core/src/sheetObjects/SheetObject.ts index 2a3a26c..4f8cca7 100644 --- a/theatre/core/src/sheetObjects/SheetObject.ts +++ b/theatre/core/src/sheetObjects/SheetObject.ts @@ -166,6 +166,7 @@ export default class SheetObject implements IdentityDerivationProvider { const tracksToProcess = val(tracksToProcessD) const valsAtom = new Atom({}) + const config = val(this.template.configPointer) prism.effect( 'processTracks', @@ -175,7 +176,7 @@ export default class SheetObject implements IdentityDerivationProvider { for (const {trackId, pathToProp} of tracksToProcess) { const derivation = this._trackIdToDerivation(trackId) const propConfig = getPropConfigByPath( - this.template.config, + config, pathToProp, )! as Extract @@ -222,7 +223,7 @@ export default class SheetObject implements IdentityDerivationProvider { } } }, - tracksToProcess, + [config, ...tracksToProcess], ) return valsAtom.pointer diff --git a/theatre/core/src/sheetObjects/SheetObjectTemplate.ts b/theatre/core/src/sheetObjects/SheetObjectTemplate.ts index 95d18e9..e280494 100644 --- a/theatre/core/src/sheetObjects/SheetObjectTemplate.ts +++ b/theatre/core/src/sheetObjects/SheetObjectTemplate.ts @@ -61,10 +61,14 @@ export default class SheetObjectTemplate { readonly _cache = new SimpleCache() readonly project: Project - get config() { + get staticConfig() { return this._config.getState() } + get configPointer() { + return this._config.pointer + } + constructor( readonly sheetTemplate: SheetTemplate, objectKey: ObjectAddressKey, @@ -95,7 +99,7 @@ export default class SheetObjectTemplate { getDefaultValues(): IDerivation { return this._cache.get('getDefaultValues()', () => prism(() => { - const config = val(this._config.pointer) + const config = val(this.configPointer) return getPropDefaultsOfSheetObject(config) }), ) @@ -119,7 +123,7 @@ export default class SheetObjectTemplate { ], ) ?? {} - const config = val(this._config.pointer) + const config = val(this.configPointer) const deserialized = config.deserializeAndSanitize(json) || {} return deserialized }), @@ -154,12 +158,14 @@ export default class SheetObjectTemplate { if (!trackIdByPropPath) return emptyArray as $IntentionalAny + const objectConfig = val(this.configPointer) + const _entries = Object.entries(trackIdByPropPath) for (const [pathToPropInString, trackId] of _entries) { const pathToProp = parsePathToProp(pathToPropInString) if (!pathToProp) continue - const propConfig = getPropConfigByPath(this.config, pathToProp) + const propConfig = getPropConfigByPath(objectConfig, pathToProp) const isSequencable = propConfig && isPropConfSequencable(propConfig) @@ -168,7 +174,7 @@ export default class SheetObjectTemplate { arrayOfIds.push({pathToProp, trackId: trackId!}) } - const mapping = getOrderingOfPropTypeConfig(this.config) + const mapping = getOrderingOfPropTypeConfig(objectConfig) arrayOfIds.sort((a, b) => { const pathToPropA = a.pathToProp diff --git a/theatre/core/src/sheets/TheatreSheet.ts b/theatre/core/src/sheets/TheatreSheet.ts index 129cf01..e5d8f4b 100644 --- a/theatre/core/src/sheets/TheatreSheet.ts +++ b/theatre/core/src/sheets/TheatreSheet.ts @@ -45,6 +45,7 @@ export interface ISheet { * * @param key - Each object is identified by a key, which is a non-empty string * @param props - The props of the object. See examples + * @param options - TODO * * @returns An Object * @@ -68,6 +69,7 @@ export interface ISheet { object( key: string, props: Props, + options?: {override?: boolean}, ): ISheetObject /** @@ -95,6 +97,7 @@ export default class TheatreSheet implements ISheet { object( key: string, config: Props, + opts?: {override?: boolean}, ): ISheetObject { const internal = privateAPI(this) const sanitizedPath = validateAndSanitiseSlashedPathOrThrow( @@ -118,11 +121,19 @@ export default class TheatreSheet implements ISheet { const prevConfig = weakMapOfUnsanitizedProps.get(existingObject) if (prevConfig) { if (!deepEqual(config, prevConfig)) { - throw new Error( - `You seem to have called sheet.object("${key}", config) twice, with different values for \`config\`. ` + - `This is disallowed because changing the config of an object on the fly would make it difficult to reason about.\n\n` + - `You can fix this by either re-using the existing object, or calling sheet.object("${key}", config) with the same config.`, - ) + if (opts?.override === true) { + const sanitizedConfig = compound(config) + existingObject.template.overrideConfig(sanitizedConfig) + weakMapOfUnsanitizedProps.set(existingObject, config) + return existingObject.publicApi as $IntentionalAny + } else { + throw new Error( + `You seem to have called sheet.object("${key}", config) twice, with different values for \`config\`. ` + + `This is disallowed because changing the config of an object on the fly would make it difficult to reason about.\n\n` + + `You can fix this by either re-using the existing object, or calling sheet.object("${key}", config) with the same config.\n\n` + + `If you mean to override the object's config, set \`{override: true}\` in sheet.object("${key}", config, {override: true})`, + ) + } } } } diff --git a/theatre/studio/src/StudioStore/createTransactionPrivateApi.ts b/theatre/studio/src/StudioStore/createTransactionPrivateApi.ts index b2fe39a..beaf477 100644 --- a/theatre/studio/src/StudioStore/createTransactionPrivateApi.ts +++ b/theatre/studio/src/StudioStore/createTransactionPrivateApi.ts @@ -89,7 +89,7 @@ export default function createTransactionPrivateApi( .getMapOfValidSequenceTracks_forStudio() .getValue() - const propConfig = getPropConfigByPath(root.template.config, path) + const propConfig = getPropConfigByPath(root.template.staticConfig, path) if (!propConfig) { throw new Error( @@ -205,7 +205,7 @@ export default function createTransactionPrivateApi( ) const propConfig = getPropConfigByPath( - root.template.config, + root.template.staticConfig, path, ) as PropTypeConfig diff --git a/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail.tsx b/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail.tsx index 2fe3f77..cbb903d 100644 --- a/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail.tsx +++ b/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail.tsx @@ -5,7 +5,6 @@ import type { PropTypeConfig_AllSimples, } from '@theatre/core/propTypes' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' -import {getPropTypeByPointer} from '@theatre/studio/propEditors/utils/getPropTypeByPointer' import {simplePropEditorByPropType} from '@theatre/studio/propEditors/simpleEditors/simplePropEditorByPropType' import type {PropConfigForType} from '@theatre/studio/propEditors/utils/PropConfigForType' import type {ISimplePropEditorReactProps} from '@theatre/studio/propEditors/simpleEditors/ISimplePropEditorReactProps' @@ -24,16 +23,13 @@ import DetailSimplePropEditor from './DeterminePropEditorForDetail/DetailSimpleP */ const DeterminePropEditorForDetail: React.VFC< IDeterminePropEditorForDetailProps -> = (p) => { - const propConfig = - p.propConfig ?? getPropTypeByPointer(p.pointerToProp, p.obj) - +> = ({propConfig, visualIndentation, pointerToProp, obj}) => { if (propConfig.type === 'compound') { return ( ) @@ -50,9 +46,9 @@ const DeterminePropEditorForDetail: React.VFC< ISimplePropEditorReactProps > } - obj={p.obj} - visualIndentation={p.visualIndentation} - pointerToProp={p.pointerToProp} + obj={obj} + visualIndentation={visualIndentation} + pointerToProp={pointerToProp} propConfig={propConfig} /> ) @@ -60,6 +56,7 @@ const DeterminePropEditorForDetail: React.VFC< } export default DeterminePropEditorForDetail + type IDeterminePropEditorForDetailProps = IDetailEditablePropertyProps & { visualIndentation: number diff --git a/theatre/studio/src/panels/DetailPanel/ObjectDetails.tsx b/theatre/studio/src/panels/DetailPanel/ObjectDetails.tsx index 0c24afc..c6b079e 100644 --- a/theatre/studio/src/panels/DetailPanel/ObjectDetails.tsx +++ b/theatre/studio/src/panels/DetailPanel/ObjectDetails.tsx @@ -3,6 +3,7 @@ import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import type {Pointer} from '@theatre/dataverse' import type {$FixMe} from '@theatre/shared/utils/types' import DeterminePropEditorForDetail from './DeterminePropEditorForDetail' +import {useVal} from '@theatre/react' const ObjectDetails: React.FC<{ /** TODO: add support for multiple objects (it would show their common props) */ @@ -10,13 +11,14 @@ const ObjectDetails: React.FC<{ }> = ({objects}) => { const obj = objects[0] const key = useMemo(() => JSON.stringify(obj.address), [obj]) + const config = useVal(obj.template.configPointer) return ( } - propConfig={obj.template.config} + propConfig={config} visualIndentation={1} /> ) diff --git a/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/BasicKeyframedTrack.tsx b/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/BasicKeyframedTrack.tsx index d1789de..e62b64f 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/BasicKeyframedTrack.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/BasicKeyframedTrack.tsx @@ -18,6 +18,7 @@ import { valueInProp, } from '@theatre/shared/propTypes/utils' import type {PropTypeConfig_AllSimples} from '@theatre/core/propTypes' +import {useVal} from '@theatre/react' export type ExtremumSpace = { fromValueSpace: (v: number) => number @@ -36,7 +37,7 @@ const BasicKeyframedTrack: React.VFC<{ }> = React.memo( ({layoutP, trackData, sheetObject, trackId, color, pathToProp}) => { const propConfig = getPropConfigByPath( - sheetObject.template.config, + useVal(sheetObject.template.configPointer), pathToProp, )! as PropTypeConfig_AllSimples diff --git a/theatre/studio/src/panels/SequenceEditorPanel/layout/tree.ts b/theatre/studio/src/panels/SequenceEditorPanel/layout/tree.ts index d7911bc..6e48eaa 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/layout/tree.ts +++ b/theatre/studio/src/panels/SequenceEditorPanel/layout/tree.ts @@ -157,6 +157,7 @@ export const calculateSequenceEditorTree = ( const trackSetups = val( sheetObject.template.getMapOfValidSequenceTracks_forStudio(), ) + const objectConfig = val(sheetObject.template.configPointer) if (Object.keys(trackSetups).length === 0) return @@ -192,7 +193,7 @@ export const calculateSequenceEditorTree = ( sheetObject, trackSetups, [], - sheetObject.template.config, + objectConfig, row.children, level + 1, shouldRender && !isCollapsed, diff --git a/theatre/studio/src/propEditors/utils/getPropTypeByPointer.tsx b/theatre/studio/src/propEditors/utils/getPropTypeByPointer.tsx index d9c49c6..e757f5d 100644 --- a/theatre/studio/src/propEditors/utils/getPropTypeByPointer.tsx +++ b/theatre/studio/src/propEditors/utils/getPropTypeByPointer.tsx @@ -3,6 +3,8 @@ import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import {getPointerParts} from '@theatre/dataverse' /** + * @deprecated because it uses obj.template.staticConfig + * * Returns the PropTypeConfig by path. Assumes `path` is a valid prop path and that * it exists in obj. * @@ -20,7 +22,7 @@ export function getPropTypeByPointer( pointerToProp: SheetObject['propsP'], obj: SheetObject, ): PropTypeConfig { - const rootConf = obj.template.config + const rootConf = obj.template.staticConfig const p = getPointerParts(pointerToProp).path let conf = rootConf as PropTypeConfig