diff --git a/packages/playground/src/shared/image/index.tsx b/packages/playground/src/shared/image/index.tsx index cc947fe..7b445de 100644 --- a/packages/playground/src/shared/image/index.tsx +++ b/packages/playground/src/shared/image/index.tsx @@ -8,12 +8,12 @@ import React, {useEffect, useState} from 'react' import {render} from 'react-dom' import styled from 'styled-components' -studio.initialize() const project = getProject('Image type playground', { assets: { - baseUrl: 'http://localhost:3000', + baseUrl: '/', }, }) +studio.initialize() const sheet = project.sheet('Image type') const Wrapper = styled.div` @@ -35,6 +35,7 @@ const ImageTypeExample: React.FC<{}> = (props) => { image2: types.image('', { label: 'another texture', }), + // audio: types.__genericAsset(''), something: 'asdf', color: types.rgba(), }) diff --git a/packages/theatric/package.json b/packages/theatric/package.json index 7221769..402511d 100644 --- a/packages/theatric/package.json +++ b/packages/theatric/package.json @@ -35,8 +35,8 @@ "@types/react-dom": "^17.0.6", "esbuild": "^0.12.15", "esbuild-register": "^2.5.0", - "npm-run-all": "^4.1.5", "lodash-es": "^4.17.21", + "npm-run-all": "^4.1.5", "typescript": "^4.4.2" }, "dependencies": { diff --git a/packages/theatric/src/index.ts b/packages/theatric/src/index.ts index ff873d8..1bd4d36 100644 --- a/packages/theatric/src/index.ts +++ b/packages/theatric/src/index.ts @@ -1,4 +1,5 @@ import type { + IProject, IProjectConfig, ISheetObject, UnknownShorthandCompoundProps, @@ -45,14 +46,37 @@ function equalityCheckWithFunctionsAlwaysEqual( } } -export function initialize(config: IProjectConfig) { - if (_projectConfig !== undefined) { +export function initialize(config: IProjectConfig): Promise { + if (_project) { console.warn( 'Theatric has already been initialized, either through another initialize call, or by calling useControls() before calling initialize().', ) - return + return _project.ready.then(() => {}) } _projectConfig = config + const project = callGetProject() + return project.ready.then(() => {}) +} + +export function getAssetUrl(asset: { + type: 'image' + id: string | undefined +}): string | undefined { + if (!_project) { + throw new Error( + 'Theatric has not been initialized yet. Please call initialize() before calling getAssetUrl().', + ) + } + if (!_project.isReady) { + throw new Error( + 'Calling `getAssetUrl()` before `initialize()` is resolved.\n' + + 'The best way to solve this is to delay rendering your react app until `project.ready` is resolved, like this: \n\n' + + '```\n' + + 'project.ready.then(() => {ReactDom.render(...)})\n' + + '```', + ) + } + return _project.getAssetUrl(asset) } const allProps: Record = {} @@ -171,10 +195,8 @@ export function useControls( [buttons, folder], ) - const sheet = useMemo( - () => getProject('Theatric', _projectConfig ?? undefined).sheet('Panels'), - [], - ) + const sheet = useMemo(() => callGetProject().sheet('Panels'), []) + const panel = options.panel ?? 'Default panel' const allPanelProps = allProps[panel] ?? (allProps[panel] = []) const allPanelActions = allActions[panel] ?? (allActions[panel] = []) @@ -298,3 +320,11 @@ export const button = (onClick: Button['onClick']) => { onClick, } } + +let _project: undefined | IProject + +function callGetProject() { + if (_project) return _project + _project = getProject('Theatric', _projectConfig ?? undefined) + return _project +} diff --git a/theatre/core/src/projects/TheatreProject.ts b/theatre/core/src/projects/TheatreProject.ts index 62af80a..537e5a9 100644 --- a/theatre/core/src/projects/TheatreProject.ts +++ b/theatre/core/src/projects/TheatreProject.ts @@ -107,6 +107,15 @@ export default class TheatreProject implements IProject { } getAssetUrl(asset: Asset): string | undefined { + // probably should put this in project.getAssetUrl but this will do for now + if (!this.isReady) { + console.error( + 'Calling `project.getAssetUrl()` before `project.ready` is resolved, will always return `undefined`. ' + + 'Either use `project.ready.then(() => project.getAssetUrl())` or `await project.ready` before calling `project.getAssetUrl()`.', + ) + return undefined + } + return asset.id ? privateAPI(this).assetStorage.getAssetUrl(asset.id) : undefined diff --git a/theatre/shared/src/utils/assets.ts b/theatre/shared/src/utils/assets.ts index 9a253f7..1aa717b 100644 --- a/theatre/shared/src/utils/assets.ts +++ b/theatre/shared/src/utils/assets.ts @@ -1,19 +1,32 @@ import type Project from '@theatre/core/projects/Project' import {val} from '@theatre/dataverse' +import forEachPropDeep from './forEachDeep' +import type {$IntentionalAny} from './types' export function getAllPossibleAssetIDs(project: Project, type?: string) { - // Apparently the value returned by val() can be undefined. Should fix TS. const sheets = Object.values(val(project.pointers.historic.sheetsById) ?? {}) const staticValues = sheets .flatMap((sheet) => Object.values(sheet?.staticOverrides.byObject ?? {})) .flatMap((overrides) => Object.values(overrides ?? {})) + const keyframeValues = sheets .flatMap((sheet) => Object.values(sheet?.sequence?.tracksByObject ?? {})) .flatMap((tracks) => Object.values(tracks?.trackData ?? {})) .flatMap((track) => track?.keyframes) .map((keyframe) => keyframe?.value) - const allAssets = [...staticValues, ...keyframeValues] + const allValues = [...keyframeValues] + staticValues.forEach((value) => { + forEachPropDeep( + value, + (v) => { + allValues.push(v as $IntentionalAny) + }, + [], + ) + }) + + const allAssets = allValues // value is Asset of the type provided .filter((value) => { return ( diff --git a/theatre/shared/src/utils/forEachDeep.ts b/theatre/shared/src/utils/forEachDeep.ts index 53f2952..7812529 100644 --- a/theatre/shared/src/utils/forEachDeep.ts +++ b/theatre/shared/src/utils/forEachDeep.ts @@ -1,5 +1,9 @@ +import type { + PropTypeConfig_AllSimples, + PropTypeConfig_Compound, +} from '@theatre/core/propTypes' import type {PathToProp} from './addresses' -import type {$IntentionalAny, SerializableMap} from './types' +import type {$IntentionalAny} from './types' /** * Iterates recursively over all props of an object (which should be a {@link SerializableMap}) and runs `fn` @@ -24,16 +28,28 @@ import type {$IntentionalAny, SerializableMap} from './types' * // Also note that `a` is also skippped, because it's not a primitive value. * ``` */ -export default function forEachDeep< - Primitive extends string | number | boolean, +export default function forEachPropDeep< + Primitive extends + | string + | number + | boolean + | PropTypeConfig_AllSimples['valueType'], >( - m: SerializableMap | Primitive | undefined | unknown, + m: + | PropTypeConfig_Compound<$IntentionalAny>['valueType'] + | Primitive + | undefined + | unknown, fn: (value: Primitive, path: PathToProp) => void, startingPath: PathToProp = [], ): void { if (typeof m === 'object' && m) { + if (isImage(m) || isRGBA(m)) { + fn(m as $IntentionalAny as Primitive, startingPath) + return + } for (const [key, value] of Object.entries(m)) { - forEachDeep(value!, fn, [...startingPath, key]) + forEachPropDeep(value!, fn, [...startingPath, key]) } } else if (m === undefined || m === null) { return @@ -41,3 +57,39 @@ export default function forEachDeep< fn(m as $IntentionalAny as Primitive, startingPath) } } + +const isImage = (value: unknown): value is {type: 'image'; id: string} => { + return ( + typeof value === 'object' && + value !== null && + Object.hasOwnProperty.call(value, 'type') && + // @ts-ignore + value.type === 'image' && + Object.hasOwnProperty.call(value, 'id') && + // @ts-ignore + typeof value.id === 'string' && + // @ts-ignore + value.id !== '' + ) +} + +const isRGBA = ( + value: unknown, +): value is {r: number; g: number; b: number; a: number} => { + return ( + typeof value === 'object' && + value !== null && + Object.hasOwnProperty.call(value, 'r') && + Object.hasOwnProperty.call(value, 'g') && + Object.hasOwnProperty.call(value, 'b') && + Object.hasOwnProperty.call(value, 'a') && + // @ts-ignore + typeof value.r === 'number' && + // @ts-ignore + typeof value.g === 'number' && + // @ts-ignore + typeof value.b === 'number' && + // @ts-ignore + typeof value.a === 'number' + ) +} diff --git a/theatre/studio/src/Scrub.ts b/theatre/studio/src/Scrub.ts index 5e43705..3b0b30c 100644 --- a/theatre/studio/src/Scrub.ts +++ b/theatre/studio/src/Scrub.ts @@ -1,4 +1,4 @@ -import forEachDeep from '@theatre/shared/utils/forEachDeep' +import forEachPropDeep from '@theatre/shared/utils/forEachDeep' import type {$FixMe} from '@theatre/shared/utils/types' import type {Pointer} from '@theatre/dataverse' import {getPointerParts} from '@theatre/dataverse' @@ -213,7 +213,7 @@ export default class Scrub implements IScrub { const defaultValueOfProp = root.template.getDefaultsAtPointer(pointer) - forEachDeep( + forEachPropDeep( defaultValueOfProp, (val, pathToProp) => { stateEditors.studio.ephemeral.projects.stateByProjectId.stateBySheetId.stateByObjectKey.propsBeingScrubbed.flag( diff --git a/theatre/studio/src/StudioStore/createTransactionPrivateApi.ts b/theatre/studio/src/StudioStore/createTransactionPrivateApi.ts index b7bc449..9639b70 100644 --- a/theatre/studio/src/StudioStore/createTransactionPrivateApi.ts +++ b/theatre/studio/src/StudioStore/createTransactionPrivateApi.ts @@ -3,7 +3,7 @@ import {isSheetObject} from '@theatre/shared/instanceTypes' import type {$FixMe, $IntentionalAny} from '@theatre/shared/utils/types' import get from 'lodash-es/get' import type {ITransactionPrivateApi} from './StudioStore' -import forEachDeep from '@theatre/shared/utils/forEachDeep' +import forEachPropDeep from '@theatre/shared/utils/forEachDeep' import getDeep from '@theatre/shared/utils/getDeep' import type {SequenceTrackId} from '@theatre/shared/utils/ids' import {getPointerParts} from '@theatre/dataverse' @@ -233,7 +233,7 @@ export default function createTransactionPrivateApi( } if (propConfig.type === 'compound') { - forEachDeep( + forEachPropDeep( defaultValue, (v, pathToProp) => { unsetStaticOrKeyframeProp(v, pathToProp) diff --git a/theatre/studio/src/propEditors/simpleEditors/ImagePropEditor.tsx b/theatre/studio/src/propEditors/simpleEditors/ImagePropEditor.tsx index 44cbab7..7724d11 100644 --- a/theatre/studio/src/propEditors/simpleEditors/ImagePropEditor.tsx +++ b/theatre/studio/src/propEditors/simpleEditors/ImagePropEditor.tsx @@ -60,7 +60,7 @@ const InputLabel = styled.label<{empty: boolean}>` ` // file input -const Input = styled.input.attrs({type: 'file', accept: 'image/*'})` +const Input = styled.input.attrs({type: 'file'})` display: none; ` @@ -145,7 +145,7 @@ function ImagePropEditor({ {previewUrl ? : } diff --git a/theatre/studio/src/propEditors/useEditingToolsForSimpleProp.tsx b/theatre/studio/src/propEditors/useEditingToolsForSimpleProp.tsx index 0c1241a..872f313 100644 --- a/theatre/studio/src/propEditors/useEditingToolsForSimpleProp.tsx +++ b/theatre/studio/src/propEditors/useEditingToolsForSimpleProp.tsx @@ -34,7 +34,7 @@ interface EditingToolsCommon { permanentlySetValue(v: T): void getAssetUrl: (asset: Asset) => string | undefined - createAsset(asset: Blob): Promise + createAsset(asset: File): Promise } interface EditingToolsDefault extends EditingToolsCommon { @@ -114,7 +114,8 @@ function createPrism( ) const editAssets = { - createAsset: obj.sheet.project.assetStorage.createAsset, + createAsset: (asset: File): Promise => + obj.sheet.project.assetStorage.createAsset(asset), getAssetUrl: (asset: Asset) => asset.id ? obj.sheet.project.assetStorage.getAssetUrl(asset.id) diff --git a/yarn.lock b/yarn.lock index 99bddb3..aa20d00 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5429,7 +5429,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.0": +"@babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.14.0": version: 7.14.0 resolution: "@babel/runtime@npm:7.14.0" dependencies: @@ -5447,6 +5447,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.13.10": + version: 7.20.13 + resolution: "@babel/runtime@npm:7.20.13" + dependencies: + regenerator-runtime: ^0.13.11 + checksum: 09b7a97a05c80540db6c9e4ddf8c5d2ebb06cae5caf3a87e33c33f27f8c4d49d9c67a2d72f1570e796045288fad569f98a26ceba0c4f5fad2af84b6ad855c4fb + languageName: node + linkType: hard + "@babel/runtime@npm:^7.14.6, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.9.2": version: 7.14.8 resolution: "@babel/runtime@npm:7.14.8" @@ -7613,14 +7622,15 @@ __metadata: linkType: hard "@react-three/fiber@npm:^7.0.6": - version: 7.0.6 - resolution: "@react-three/fiber@npm:7.0.6" + version: 7.0.29 + resolution: "@react-three/fiber@npm:7.0.29" dependencies: "@babel/runtime": ^7.13.10 + "@types/react-reconciler": ^0.26.2 react-merge-refs: ^1.1.0 react-reconciler: ^0.26.2 react-three-fiber: 0.0.0-deprecated - react-use-measure: ^2.0.4 + react-use-measure: ^2.1.1 resize-observer-polyfill: ^1.5.1 scheduler: ^0.20.2 use-asset: ^1.0.4 @@ -7629,11 +7639,11 @@ __metadata: peerDependencies: react: ">=17.0" react-dom: ">=17.0" - three: ">=0.126" + three: ">=0.133" peerDependenciesMeta: react-dom: optional: true - checksum: f213eef29a600688f03d3ffd2f41ea2ce4da9608326921f3fea15621f86bebbc8154ce5eca071188a78f9684751c7ee824353711e59affb9967f3f7fdeaf2f20 + checksum: 79b760b41c4076666b4546c9ee6713cdd815e6cb9e05a90702ba911306180f3968a31cb7ac024cc7683bc6e2af7dd0e55125db26b0dc238647628ad5d9dbd877 languageName: node linkType: hard @@ -8869,6 +8879,15 @@ __metadata: languageName: node linkType: hard +"@types/react-reconciler@npm:^0.26.2": + version: 0.26.7 + resolution: "@types/react-reconciler@npm:0.26.7" + dependencies: + "@types/react": "*" + checksum: 4122d2b08580f775d0aeae9bd10b68248f894096ed14c0ebbc143ef712e21b159e89d0c628bd95dd3329947fc1ee94a0cb1d2d32b32b1d5d225e70030e91e58f + languageName: node + linkType: hard + "@types/react@npm:^17.0.9": version: 17.0.9 resolution: "@types/react@npm:17.0.9" @@ -14174,7 +14193,7 @@ __metadata: languageName: node linkType: hard -"debounce@npm:^1.2.0": +"debounce@npm:^1.2.0, debounce@npm:^1.2.1": version: 1.2.1 resolution: "debounce@npm:1.2.1" checksum: 682a89506d9e54fb109526f4da255c5546102fbb8e3ae75eef3b04effaf5d4853756aee97475cd4650641869794e44f410eeb20ace2b18ea592287ab2038519e @@ -27998,6 +28017,18 @@ fsevents@^1.2.7: languageName: node linkType: hard +"react-use-measure@npm:^2.1.1": + version: 2.1.1 + resolution: "react-use-measure@npm:2.1.1" + dependencies: + debounce: ^1.2.1 + peerDependencies: + react: ">=16.13" + react-dom: ">=16.13" + checksum: b8e8939229d463c3c505f7b617925c0228efae0cd6f651371f463846417b06c9170be57df51293a61027c41770f8a090fdb8a08717c4e36290ccb496e0318f1f + languageName: node + linkType: hard + "react-use@npm:^15.3.3": version: 15.3.8 resolution: "react-use@npm:15.3.8" @@ -28296,6 +28327,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"regenerator-runtime@npm:^0.13.11": + version: 0.13.11 + resolution: "regenerator-runtime@npm:0.13.11" + checksum: 27481628d22a1c4e3ff551096a683b424242a216fee44685467307f14d58020af1e19660bf2e26064de946bad7eff28950eae9f8209d55723e2d9351e632bbb4 + languageName: node + linkType: hard + "regenerator-runtime@npm:^0.13.4": version: 0.13.8 resolution: "regenerator-runtime@npm:0.13.8"