Fix logic to createOrReplaceObject when no object type is given

This commit is contained in:
Florian Rival
2026-04-16 01:05:52 +02:00
parent c613c03449
commit 7cfe407efc
3 changed files with 144 additions and 86 deletions
@@ -76,11 +76,11 @@ export const useSearchAndInstallAsset = ({
}
if (!assetShortHeader) {
if (!objectType && !exactOrPartialAssetId) {
if (!assetSearchOptions.searchTerms && !exactOrPartialAssetId) {
return {
status: 'error',
message:
'Cannot search for an asset without an object type. Specify either `object_type` or `asset_id`.',
'Cannot search for an asset without either `searchTerms` or `exactOrPartialAssetId`.',
createdObjects: [],
assetShortHeader: null,
isTheFirstOfItsTypeInProject: false,
@@ -113,7 +113,7 @@ describe('editorFunctions', () => {
project.delete();
});
it('creates a new object (from the asset store)', async () => {
it('creates a new object (from scratch, because only object_type was provided)', async () => {
// $FlowFixMe[underconstrained-implicit-instantiation]
const onObjectsModifiedOutsideEditor = jest.fn();
@@ -129,6 +129,33 @@ describe('editorFunctions', () => {
}
);
expect(result.message).toMatchInlineSnapshot(
`"Created a new object (from scratch) called \\"MyNewTextObject\\" of type \\"TextObject::Text\\" in scene \\"TestScene\\". It has the following properties: bold: false (boolean), characterSize: 20 (Pixel), color: 0;0;0 (color), font: (resource), isOutlineEnabled: false (boolean), isShadowEnabled: false (boolean), italic: false (boolean), lineHeight: 0 (Pixel), outlineColor: 255;255;255 (color), outlineThickness: 2 (Pixel), shadowAngle: 90 (DegreeAngle), shadowBlurRadius: 2 (Pixel), shadowColor: 0;0;0 (color), shadowDistance: 4 (Pixel), shadowOpacity: 127 (Pixel), text: Text (multilinestring), textAlignment: left (choice, one of: [\\"left\\", \\"center\\", \\"right\\"]), verticalTextAlignment: top (choice, one of: [\\"top\\", \\"center\\", \\"bottom\\"])."`
);
expect(result.success).toBe(true);
expect(onObjectsModifiedOutsideEditor).toHaveBeenCalledWith({
scene: testScene,
isNewObjectTypeUsed: true,
});
});
it('creates a new object (from the asset store, with search_terms and object_type provided)', async () => {
// $FlowFixMe[underconstrained-implicit-instantiation]
const onObjectsModifiedOutsideEditor = jest.fn();
const result: EditorFunctionGenericOutput = await editorFunctions.create_or_replace_object.launchFunction(
{
...makeFakeLaunchFunctionOptionsWithProject(project),
args: {
scene_name: 'TestScene',
object_type: 'TextObject::Text',
object_name: 'MyNewTextObject',
search_terms: 'Very cool text object',
},
onObjectsModifiedOutsideEditor,
}
);
expect(result.message).toMatchInlineSnapshot(
`"Created (from the asset store) object \\"MyNewTextObject\\" of type \\"TextObject::Text\\" in scene \\"TestScene\\". It has the following properties: bold: false (boolean), characterSize: 20 (Pixel), color: 0;0;0 (color), font: (resource), isOutlineEnabled: false (boolean), isShadowEnabled: false (boolean), italic: false (boolean), lineHeight: 0 (Pixel), outlineColor: 255;255;255 (color), outlineThickness: 2 (Pixel), shadowAngle: 90 (DegreeAngle), shadowBlurRadius: 2 (Pixel), shadowColor: 0;0;0 (color), shadowDistance: 4 (Pixel), shadowOpacity: 127 (Pixel), text: Text (multilinestring), textAlignment: left (choice, one of: [\\"left\\", \\"center\\", \\"right\\"]), verticalTextAlignment: top (choice, one of: [\\"top\\", \\"center\\", \\"bottom\\"])."`
);
@@ -139,7 +166,33 @@ describe('editorFunctions', () => {
});
});
it('creates a new object (from scratch if not found in the asset store)', async () => {
it('creates a new object (from the asset store, with search_terms but without any specified object type)', async () => {
// $FlowFixMe[underconstrained-implicit-instantiation]
const onObjectsModifiedOutsideEditor = jest.fn();
const result: EditorFunctionGenericOutput = await editorFunctions.create_or_replace_object.launchFunction(
{
...makeFakeLaunchFunctionOptionsWithProject(project),
args: {
scene_name: 'TestScene',
object_name: 'SomeNewObject',
search_terms: 'A very cool sprite for my player in my game',
},
onObjectsModifiedOutsideEditor,
}
);
expect(result.message).toMatchInlineSnapshot(
`"Created (from the asset store) object \\"SomeNewObject\\" of type \\"Sprite\\" in scene \\"TestScene\\". It has the following properties: ."`
);
expect(result.success).toBe(true);
expect(onObjectsModifiedOutsideEditor).toHaveBeenCalledWith({
scene: testScene,
isNewObjectTypeUsed: false,
});
});
it('creates a new object (from scratch, fallback if not found in the asset store)', async () => {
// $FlowFixMe[underconstrained-implicit-instantiation]
const onObjectsModifiedOutsideEditor = jest.fn();
@@ -556,7 +609,7 @@ describe('editorFunctions', () => {
expect(testScene.getObjects().hasObjectNamed('MyAssetObject')).toBe(true);
});
it('fails when creating a new object without object_type nor asset_id', async () => {
it('fails when creating a new object without object_type, search_terms nor asset_id', async () => {
const result: EditorFunctionGenericOutput = await editorFunctions.create_or_replace_object.launchFunction(
{
...makeFakeLaunchFunctionOptionsWithProject(project),
@@ -569,7 +622,7 @@ describe('editorFunctions', () => {
expect(result.success).toBe(false);
expect(result.message).toMatchInlineSnapshot(
`"Cannot create object \\"MyAssetObject\\": specify either \\"object_type\\" or \\"asset_id\\"."`
`"No search_terms or asset_id were provided to create the object \\"MyAssetObject\\". This object was not created."`
);
});
+85 -80
View File
@@ -811,98 +811,103 @@ const createOrReplaceObject: EditorFunction = {
);
}
if (!candidateType && !asset_id) {
return makeGenericFailure(
`Cannot create object "${targetObjectName}": specify either "object_type" or "asset_id".`
);
}
const targetObjectsContainer =
target_object_scope === 'global' ? globalObjects : layoutObjects;
// First try to search and install an object from the asset store.
try {
const {
status,
message,
createdObjects,
assetShortHeader,
isTheFirstOfItsTypeInProject,
} = await searchAndInstallAsset({
objectsContainer: targetObjectsContainer,
objectName: targetObjectName,
objectType: candidateType,
searchTerms: search_terms || '',
description: description || '',
twoDimensionalViewKind: two_dimensional_view_kind || '',
exactOrPartialAssetId: asset_id || null,
relatedAiRequestId,
...getRelatedAiRequestLastMessages(),
});
if (status === 'error') {
if (candidateType && !search_terms && !asset_id) {
// Do nothing: there is nothing given apart from an object type,
// which we can still use to fallback to create from scratch.
} else {
if (!search_terms && !asset_id) {
return makeGenericFailure(
`Unable to search and install object (${message}).`
`No search_terms or asset_id were provided to create the object "${targetObjectName}". This object was not created.`
);
} else if (status === 'asset-installed') {
// Update behaviors shared data for the scene where the object was created.
// Assets from the store can come with behaviors that have shared data.
if (target_object_scope === 'global') {
gd.WholeProjectRefactorer.updateBehaviorsSharedData(project);
} else {
layout.updateBehaviorsSharedData(project);
}
}
// /!\ Tell the editor that some objects have potentially been modified (and even removed).
// This will force the objects panel to refresh.
onObjectsModifiedOutsideEditor({
scene: layout,
isNewObjectTypeUsed: isTheFirstOfItsTypeInProject,
// First try to search and install an object from the asset store.
try {
const {
status,
message,
createdObjects,
assetShortHeader,
isTheFirstOfItsTypeInProject,
} = await searchAndInstallAsset({
objectsContainer: targetObjectsContainer,
objectName: targetObjectName,
objectType: candidateType,
searchTerms: search_terms || '',
description: description || '',
twoDimensionalViewKind: two_dimensional_view_kind || '',
exactOrPartialAssetId: asset_id || null,
relatedAiRequestId,
...getRelatedAiRequestLastMessages(),
});
if (createdObjects.length === 1) {
const object = createdObjects[0];
const result: EditorFunctionGenericOutput = {
success: true,
message: [
`Created (from the asset store) object "${object.getName()}" of type "${object.getType()}" in scene "${scene_name}".`,
getPropertiesText(object),
].join(' '),
objectSizeInfo: {
[object.getName()]: getObjectSizeInfo(
object,
project,
PixiResourcesLoader,
assetShortHeader
),
},
};
return result;
}
return makeGenericSuccess(
`Created (from the asset store) ${createdObjects
.map(
object =>
`object "${object.getName()}" of type "${object.getType()}"`
)
.join(', ')} in scene "${scene_name}".`
);
} else {
if (asset_id) {
if (status === 'error') {
return makeGenericFailure(
`No asset found with id "${asset_id}". The object was not created.`
`Unable to search and install object (${message}).`
);
}
} else if (status === 'asset-installed') {
// Update behaviors shared data for the scene where the object was created.
// Assets from the store can come with behaviors that have shared data.
if (target_object_scope === 'global') {
gd.WholeProjectRefactorer.updateBehaviorsSharedData(project);
} else {
layout.updateBehaviorsSharedData(project);
}
// No asset found - we'll create an object from scratch.
// /!\ Tell the editor that some objects have potentially been modified (and even removed).
// This will force the objects panel to refresh.
onObjectsModifiedOutsideEditor({
scene: layout,
isNewObjectTypeUsed: isTheFirstOfItsTypeInProject,
});
if (createdObjects.length === 1) {
const object = createdObjects[0];
const result: EditorFunctionGenericOutput = {
success: true,
message: [
`Created (from the asset store) object "${object.getName()}" of type "${object.getType()}" in scene "${scene_name}".`,
getPropertiesText(object),
].join(' '),
objectSizeInfo: {
[object.getName()]: getObjectSizeInfo(
object,
project,
PixiResourcesLoader,
assetShortHeader
),
},
};
return result;
}
return makeGenericSuccess(
`Created (from the asset store) ${createdObjects
.map(
object =>
`object "${object.getName()}" of type "${object.getType()}"`
)
.join(', ')} in scene "${scene_name}".`
);
} else {
if (asset_id) {
return makeGenericFailure(
`No asset found with id "${asset_id}". The object was not created.`
);
}
// No asset found - we'll create an object from scratch.
}
} catch (error) {
return makeGenericFailure(
`An unexpected error happened while search and installing objects (${
error.message
}).`
);
}
} catch (error) {
return makeGenericFailure(
`An unexpected error happened while search and installing objects (${
error.message
}).`
);
}
// Create an object from scratch: this requires a known object type.