mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
41 Commits
feat/agent
...
codex/upda
Author | SHA1 | Date | |
---|---|---|---|
![]() |
08f00bb893 | ||
![]() |
279d41cdb7 | ||
![]() |
5cf65a9f62 | ||
![]() |
08b05c13b6 | ||
![]() |
eb55c85f4e | ||
![]() |
8a243440db | ||
![]() |
b3e4e6b89c | ||
![]() |
a1a25f6df4 | ||
![]() |
6114a6cec1 | ||
![]() |
5058964937 | ||
![]() |
4488675540 | ||
![]() |
6a2d2c9e67 | ||
![]() |
b43c42d763 | ||
![]() |
69112183d4 | ||
![]() |
a4c2778b8d | ||
![]() |
f26e56c3bf | ||
![]() |
f5f9944fc4 | ||
![]() |
9467caf1e9 | ||
![]() |
00376f39d5 | ||
![]() |
40b6a34dc5 | ||
![]() |
17d2b8c2c2 | ||
![]() |
935af42d23 | ||
![]() |
d4a8d468cb | ||
![]() |
b16099aee0 | ||
![]() |
c17b918a43 | ||
![]() |
d58e8c7ef9 | ||
![]() |
ddd6b6e3a8 | ||
![]() |
e629c132ea | ||
![]() |
b80e03f153 | ||
![]() |
11e36ff3f1 | ||
![]() |
22de356413 | ||
![]() |
caefa04fbe | ||
![]() |
cf2e7d67d7 | ||
![]() |
685e444b2d | ||
![]() |
a9c1045afd | ||
![]() |
24e0d37583 | ||
![]() |
d44997d372 | ||
![]() |
062aa888f8 | ||
![]() |
de4c2ae4ad | ||
![]() |
29ad7308c3 | ||
![]() |
19b21c280e |
@@ -18,13 +18,13 @@ jobs:
|
||||
# Build the **entire** app for macOS (including the GDevelop.js library).
|
||||
build-macos:
|
||||
macos:
|
||||
xcode: 14.2.0
|
||||
resource_class: macos.m1.large.gen1
|
||||
xcode: 16.4.0
|
||||
resource_class: m4pro.medium
|
||||
steps:
|
||||
- checkout
|
||||
# Install Rosetta for AWS CLI and disable TSO to speed up S3 uploads (https://support.circleci.com/hc/en-us/articles/19334402064027-Troubleshooting-slow-uploads-to-S3-for-jobs-using-an-m1-macOS-resource-class)
|
||||
- macos/install-rosetta
|
||||
- run: sudo sysctl net.inet.tcp.tso=0
|
||||
# - run: sudo sysctl net.inet.tcp.tso=0
|
||||
|
||||
# Install a recent version of npm to workaround a notarization issue because of a symlink made by npm: https://github.com/electron-userland/electron-builder/issues/7755
|
||||
# Node.js v20.14.0 comes with npm v10.7.0.
|
||||
@@ -88,13 +88,35 @@ jobs:
|
||||
- store_artifacts:
|
||||
path: newIDE/electron-app/dist
|
||||
|
||||
|
||||
# Upload artifacts (AWS)
|
||||
- run:
|
||||
name: Deploy to S3 (specific commit)
|
||||
command: export PATH=~/.local/bin:$PATH && aws s3 sync newIDE/electron-app/dist s3://gdevelop-releases/$(git rev-parse --abbrev-ref HEAD)/commit/$(git rev-parse HEAD)/
|
||||
command: |
|
||||
export PATH=~/.local/bin:$PATH
|
||||
for i in 1 2 3 4 5 6 7; do
|
||||
aws s3 sync newIDE/electron-app/dist s3://gdevelop-releases/$(git rev-parse --abbrev-ref HEAD)/commit/$(git rev-parse HEAD)/ && break
|
||||
echo "Retry $i failed... retrying in 10 seconds"
|
||||
sleep 10
|
||||
done
|
||||
if [ $i -eq 7 ]; then
|
||||
echo "All retries for deployment failed!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- run:
|
||||
name: Deploy to S3 (latest)
|
||||
command: export PATH=~/.local/bin:$PATH && aws s3 sync newIDE/electron-app/dist s3://gdevelop-releases/$(git rev-parse --abbrev-ref HEAD)/latest/
|
||||
command: |
|
||||
export PATH=~/.local/bin:$PATH
|
||||
for i in 1 2 3 4 5 6 7; do
|
||||
aws s3 sync newIDE/electron-app/dist s3://gdevelop-releases/$(git rev-parse --abbrev-ref HEAD)/latest/ && break
|
||||
echo "Retry $i failed... retrying in 10 seconds"
|
||||
sleep 10
|
||||
done
|
||||
if [ $i -eq 7 ]; then
|
||||
echo "All retries for deployment failed!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build the app for Linux (using a pre-built GDevelop.js library).
|
||||
build-linux:
|
||||
@@ -368,7 +390,7 @@ jobs:
|
||||
npm -v
|
||||
|
||||
Remove-Item package-lock.json
|
||||
|
||||
|
||||
$Env:REQUIRES_EXACT_LIBGD_JS_VERSION = "true"
|
||||
|
||||
npm install
|
||||
@@ -425,10 +447,10 @@ jobs:
|
||||
|
||||
$Env:GD_SIGNTOOL_THUMBPRINT = ''
|
||||
|
||||
$Env:GD_SIGNTOOL_SUBJECT_NAME = ''
|
||||
|
||||
$Env:GD_SIGNTOOL_SUBJECT_NAME = ''
|
||||
|
||||
$Env:CSC_LINK = ''
|
||||
|
||||
|
||||
$Env:CSC_KEY_PASSWORD = ''
|
||||
|
||||
node scripts/build.js --skip-app-build --win appx --publish=never
|
||||
@@ -445,16 +467,16 @@ jobs:
|
||||
name: Install AWS CLI
|
||||
command: |
|
||||
# Install the CLI for the current user
|
||||
|
||||
|
||||
pip install --quiet --upgrade --user awscli
|
||||
|
||||
|
||||
# Add the user-Scripts dir to PATH for this step and the next.
|
||||
|
||||
|
||||
$binDir = (python -m site --user-base) + "\Scripts"
|
||||
$Env:Path += ";$binDir"
|
||||
|
||||
# Sanity check:
|
||||
aws --version
|
||||
aws --version
|
||||
|
||||
# Upload artifacts (S3)
|
||||
- run:
|
||||
|
@@ -37,8 +37,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
.SetIcon("res/actions/position24_black.png");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Angle"))
|
||||
.SetIcon("res/actions/direction24_black.png");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Size"))
|
||||
.SetIcon("res/actions/scale24_black.png");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Size")).SetIcon(
|
||||
"res/actions/scale24_black.png");
|
||||
|
||||
gd::ObjectMetadata& obj = extension.AddObject<gd::ObjectConfiguration>(
|
||||
"", _("Base object"), _("Base object"), "res/objeticon24.png");
|
||||
@@ -235,7 +235,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
|
||||
obj.AddAction("SetAngle",
|
||||
_("Angle"),
|
||||
_("Change the angle of rotation of an object (in degrees)."),
|
||||
_("Change the angle of rotation of an object (in degrees). For "
|
||||
"3D objects, this is the rotation around the Z axis."),
|
||||
_("the angle"),
|
||||
_("Angle"),
|
||||
"res/actions/direction24_black.png",
|
||||
@@ -250,7 +251,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
obj.AddAction("Rotate",
|
||||
_("Rotate"),
|
||||
_("Rotate an object, clockwise if the speed is positive, "
|
||||
"counterclockwise otherwise."),
|
||||
"counterclockwise otherwise. For 3D objects, this is the "
|
||||
"rotation around the Z axis."),
|
||||
_("Rotate _PARAM0_ at speed _PARAM1_ deg/second"),
|
||||
_("Angle"),
|
||||
"res/actions/rotate24_black.png",
|
||||
@@ -634,7 +636,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
|
||||
obj.AddCondition("Angle",
|
||||
_("Angle"),
|
||||
_("Compare the angle of the specified object."),
|
||||
_("Compare the angle, in degrees, of the specified object. "
|
||||
"For 3D objects, this is the angle around the Z axis."),
|
||||
_("the angle (in degrees)"),
|
||||
_("Angle"),
|
||||
"res/conditions/direction24_black.png",
|
||||
@@ -835,14 +838,13 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
.MarkAsAdvanced()
|
||||
.SetRelevantForLayoutEventsOnly();
|
||||
|
||||
obj.AddAction(
|
||||
"PushBooleanToObjectVariable",
|
||||
_("Add value to object array variable"),
|
||||
_("Adds a boolean to the end of an object array variable."),
|
||||
_("Add value _PARAM2_ to array variable _PARAM1_ of _PARAM0_"),
|
||||
_("Variables ❯ Arrays and structures"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
obj.AddAction("PushBooleanToObjectVariable",
|
||||
_("Add value to object array variable"),
|
||||
_("Adds a boolean to the end of an object array variable."),
|
||||
_("Add value _PARAM2_ to array variable _PARAM1_ of _PARAM0_"),
|
||||
_("Variables ❯ Arrays and structures"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("objectvar", _("Array variable"))
|
||||
.AddParameter("trueorfalse", _("Boolean to add"))
|
||||
@@ -1268,7 +1270,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
|
||||
obj.AddExpression("Angle",
|
||||
_("Angle"),
|
||||
_("Current angle, in degrees, of the object"),
|
||||
_("Current angle, in degrees, of the object. For 3D "
|
||||
"objects, this is the angle around the Z axis."),
|
||||
_("Angle"),
|
||||
"res/actions/direction_black.png")
|
||||
.AddParameter("object", _("Object"));
|
||||
@@ -1571,7 +1574,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
extension
|
||||
.AddAction("Create",
|
||||
_("Create an object"),
|
||||
_("Create an object at specified position"),
|
||||
_("Create an instance of the object at the specified position."
|
||||
"The created object instance will be available for the next "
|
||||
"actions and sub-events."),
|
||||
_("Create object _PARAM1_ at position _PARAM2_;_PARAM3_ "
|
||||
"(layer: _PARAM4_)"),
|
||||
"",
|
||||
|
@@ -72,7 +72,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
|
||||
extension
|
||||
.AddExpression("normalize",
|
||||
_("Normalize a value between `min` and `max` to a value between 0 and 1."),
|
||||
_("Normalize a value between `min` and `max` to a value "
|
||||
"between 0 and 1."),
|
||||
_("Remap a value between 0 and 1."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
@@ -124,7 +125,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
extension
|
||||
.AddExpression("mod",
|
||||
_("Modulo"),
|
||||
_("x mod y"),
|
||||
_("Compute \"x mod y\". GDevelop does NOT support the \% "
|
||||
"operator. Use this mod(x, y) function instead."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("x (as in x mod y)"))
|
||||
@@ -184,11 +186,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("asinh",
|
||||
_("Arcsine"),
|
||||
_("Arcsine"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"asinh", _("Arcsine"), _("Arcsine"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -218,11 +217,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("cbrt",
|
||||
_("Cube root"),
|
||||
_("Cube root"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"cbrt", _("Cube root"), _("Cube root"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -260,12 +256,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"), "", true);
|
||||
|
||||
extension
|
||||
.AddExpression("cos",
|
||||
_("Cosine"),
|
||||
_("Cosine of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"cos",
|
||||
_("Cosine"),
|
||||
_("Cosine of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -293,29 +290,20 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("int",
|
||||
_("Round"),
|
||||
_("Round a number"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"int", _("Round"), _("Round a number"), "", "res/mathfunction.png")
|
||||
.SetHidden()
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("rint",
|
||||
_("Round"),
|
||||
_("Round a number"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"rint", _("Round"), _("Round a number"), "", "res/mathfunction.png")
|
||||
.SetHidden()
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("round",
|
||||
_("Round"),
|
||||
_("Round a number"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"round", _("Round"), _("Round a number"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -324,8 +312,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
_("Round a number to the Nth decimal place"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"))
|
||||
.AddParameter("expression", _("Expression"), "", true);
|
||||
.AddParameter("expression", _("Number to Round"))
|
||||
.AddParameter("expression", _("Decimal Places"), "", true);
|
||||
|
||||
extension
|
||||
.AddExpression("exp",
|
||||
@@ -336,19 +324,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("log",
|
||||
_("Logarithm"),
|
||||
_("Logarithm"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"log", _("Logarithm"), _("Logarithm"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("ln",
|
||||
_("Logarithm"),
|
||||
_("Logarithm"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"ln", _("Logarithm"), _("Logarithm"), "", "res/mathfunction.png")
|
||||
.SetHidden()
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
@@ -387,11 +369,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("The exponent (n in x^n)"));
|
||||
|
||||
extension
|
||||
.AddExpression("sec",
|
||||
_("Secant"),
|
||||
_("Secant"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"sec", _("Secant"), _("Secant"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -403,12 +382,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("sin",
|
||||
_("Sine"),
|
||||
_("Sine of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"sin",
|
||||
_("Sine"),
|
||||
_("Sine of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -428,12 +408,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("tan",
|
||||
_("Tangent"),
|
||||
_("Tangent of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `tan(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"tan",
|
||||
_("Tangent"),
|
||||
_("Tangent of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `tan(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -463,26 +444,28 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("x (in a+(b-a)*x)"));
|
||||
|
||||
extension
|
||||
.AddExpression("XFromAngleAndDistance",
|
||||
_("X position from angle and distance"),
|
||||
_("Compute the X position when given an angle and distance "
|
||||
"relative to the origin (0;0). This is also known as "
|
||||
"getting the cartesian coordinates of a 2D vector, using "
|
||||
"its polar coordinates."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"XFromAngleAndDistance",
|
||||
_("X position from angle and distance"),
|
||||
_("Compute the X position when given an angle and distance "
|
||||
"relative to the origin (0;0). This is also known as "
|
||||
"getting the cartesian coordinates of a 2D vector, using "
|
||||
"its polar coordinates."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Angle, in degrees"))
|
||||
.AddParameter("expression", _("Distance"));
|
||||
|
||||
extension
|
||||
.AddExpression("YFromAngleAndDistance",
|
||||
_("Y position from angle and distance"),
|
||||
_("Compute the Y position when given an angle and distance "
|
||||
"relative to the origin (0;0). This is also known as "
|
||||
"getting the cartesian coordinates of a 2D vector, using "
|
||||
"its polar coordinates."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"YFromAngleAndDistance",
|
||||
_("Y position from angle and distance"),
|
||||
_("Compute the Y position when given an angle and distance "
|
||||
"relative to the origin (0;0). This is also known as "
|
||||
"getting the cartesian coordinates of a 2D vector, using "
|
||||
"its polar coordinates."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Angle, in degrees"))
|
||||
.AddParameter("expression", _("Distance"));
|
||||
|
||||
@@ -497,7 +480,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
extension
|
||||
.AddExpression("lerpAngle",
|
||||
_("Lerp (Linear interpolation) between two angles"),
|
||||
_("Linearly interpolates between two angles (in degrees) by taking the shortest direction around the circle."),
|
||||
_("Linearly interpolates between two angles (in degrees) "
|
||||
"by taking the shortest direction around the circle."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Starting angle, in degrees"))
|
||||
|
@@ -250,25 +250,28 @@ void CustomObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker& wor
|
||||
}
|
||||
const auto &eventsBasedObject = project->GetEventsBasedObject(GetType());
|
||||
|
||||
if (isMarkedAsOverridingEventsBasedObjectChildrenConfiguration) {
|
||||
if (IsForcedToOverrideEventsBasedObjectChildrenConfiguration()) {
|
||||
for (auto &childObject : eventsBasedObject.GetObjects().GetObjects()) {
|
||||
auto &configuration = GetChildObjectConfiguration(childObject->GetName());
|
||||
configuration.ExposeResources(worker);
|
||||
}
|
||||
}
|
||||
else if (eventsBasedObject.GetVariants().HasVariantNamed(variantName)) {
|
||||
for (auto &childObject : eventsBasedObject.GetVariants()
|
||||
.GetVariant(variantName)
|
||||
.GetObjects()
|
||||
.GetObjects()) {
|
||||
childObject->GetConfiguration().ExposeResources(worker);
|
||||
}
|
||||
} else if (isMarkedAsOverridingEventsBasedObjectChildrenConfiguration) {
|
||||
for (auto &childObject : eventsBasedObject.GetObjects().GetObjects()) {
|
||||
auto &configuration = GetChildObjectConfiguration(childObject->GetName());
|
||||
configuration.ExposeResources(worker);
|
||||
}
|
||||
} else {
|
||||
if (variantName.empty() ||
|
||||
!eventsBasedObject.GetVariants().HasVariantNamed(variantName)) {
|
||||
for (auto &childObject :
|
||||
eventsBasedObject.GetDefaultVariant().GetObjects().GetObjects()) {
|
||||
childObject->GetConfiguration().ExposeResources(worker);
|
||||
}
|
||||
} else {
|
||||
for (auto &childObject : eventsBasedObject.GetVariants()
|
||||
.GetVariant(variantName)
|
||||
.GetObjects()
|
||||
.GetObjects()) {
|
||||
childObject->GetConfiguration().ExposeResources(worker);
|
||||
}
|
||||
for (auto &childObject :
|
||||
eventsBasedObject.GetDefaultVariant().GetObjects().GetObjects()) {
|
||||
childObject->GetConfiguration().ExposeResources(worker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -78,6 +78,15 @@ public:
|
||||
variantName = variantName_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy events-based objects don't have any instance in their default
|
||||
* variant since there wasn't a graphical editor at the time. In this case,
|
||||
* the editor doesn't allow to choose a variant, but a variant may have stayed
|
||||
* after a user rolled back the extension. This variant must be ignored.
|
||||
*
|
||||
* @return true when its events-based object doesn't have any initial
|
||||
* instance.
|
||||
*/
|
||||
bool IsForcedToOverrideEventsBasedObjectChildrenConfiguration() const;
|
||||
|
||||
bool IsMarkedAsOverridingEventsBasedObjectChildrenConfiguration() const {
|
||||
|
@@ -365,6 +365,8 @@ class GD_CORE_API InitialInstance {
|
||||
* the same initial instance between serialization.
|
||||
*/
|
||||
InitialInstance& ResetPersistentUuid();
|
||||
|
||||
const gd::String& GetPersistentUuid() const { return persistentUuid; }
|
||||
///@}
|
||||
|
||||
private:
|
||||
|
@@ -34,7 +34,6 @@ namespace gdjs {
|
||||
objectData: gdjs.Object3DData & gdjs.CustomObjectConfiguration
|
||||
) {
|
||||
super(parent, objectData);
|
||||
this._renderer.reinitialize(this, parent);
|
||||
}
|
||||
|
||||
protected override _createRender() {
|
||||
|
@@ -44,10 +44,7 @@ namespace gdjs {
|
||||
) {
|
||||
this._object = object;
|
||||
this._isContainerDirty = true;
|
||||
const layer = parent.getLayer('');
|
||||
if (layer) {
|
||||
layer.getRenderer().add3DRendererObject(this._threeGroup);
|
||||
}
|
||||
this._threeGroup.clear();
|
||||
}
|
||||
|
||||
_updateThreeGroup() {
|
||||
|
@@ -14,6 +14,7 @@ describe('gdjs.AnchorRuntimeBehavior', () => {
|
||||
effects: [],
|
||||
content: {},
|
||||
childrenContent: {},
|
||||
isInnerAreaFollowingParentSize: false,
|
||||
});
|
||||
runtimeScene.addObject(customObject);
|
||||
customObject.setPosition(500, 250);
|
||||
|
@@ -35,25 +35,32 @@ void DeclareDraggableBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
std::make_shared<DraggableBehavior>(),
|
||||
std::shared_ptr<gd::BehaviorsSharedData>());
|
||||
|
||||
aut.AddCondition("Dragged",
|
||||
_("Being dragged"),
|
||||
_("Check if the object is being dragged."),
|
||||
_("_PARAM0_ is being dragged"),
|
||||
_("Draggable"),
|
||||
"CppPlatform/Extensions/draggableicon24.png",
|
||||
"CppPlatform/Extensions/draggableicon16.png")
|
||||
aut.AddCondition(
|
||||
"Dragged",
|
||||
_("Being dragged"),
|
||||
_("Check if the object is being dragged. This means the mouse button "
|
||||
"or touch is pressed on it. When the mouse button or touch is "
|
||||
"released, the object is no longer being considered dragged (use "
|
||||
"the condition \"Was just dropped\" to check when the dragging is "
|
||||
"ending)."),
|
||||
_("_PARAM0_ is being dragged"),
|
||||
_("Draggable"),
|
||||
"CppPlatform/Extensions/draggableicon24.png",
|
||||
"CppPlatform/Extensions/draggableicon16.png")
|
||||
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "Draggable")
|
||||
.SetFunctionName("IsDragged");
|
||||
|
||||
aut.AddCondition("Dropped",
|
||||
_("Was just dropped"),
|
||||
_("Check if the object was just dropped after being dragged."),
|
||||
_("_PARAM0_ was just dropped"),
|
||||
_("Draggable"),
|
||||
"CppPlatform/Extensions/draggableicon24.png",
|
||||
"CppPlatform/Extensions/draggableicon16.png")
|
||||
aut.AddCondition(
|
||||
"Dropped",
|
||||
_("Was just dropped"),
|
||||
_("Check if the object was just dropped after being dragged (the "
|
||||
"mouse button or touch was just released this frame)."),
|
||||
_("_PARAM0_ was just dropped"),
|
||||
_("Draggable"),
|
||||
"CppPlatform/Extensions/draggableicon24.png",
|
||||
"CppPlatform/Extensions/draggableicon16.png")
|
||||
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "Draggable")
|
||||
|
@@ -818,7 +818,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('yesorno', _('Treat as bullet?'), '', false)
|
||||
.addParameter('yesorno', _('Treat as bullet'), '', false)
|
||||
.setDefaultValue('false')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setBullet');
|
||||
@@ -852,7 +852,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('yesorno', _('Fixed rotation?'), '', false)
|
||||
.addParameter('yesorno', _('Fixed rotation'), '', false)
|
||||
.setDefaultValue('false')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setFixedRotation');
|
||||
@@ -886,7 +886,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('yesorno', _('Can sleep?'), '', false)
|
||||
.addParameter('yesorno', _('Can sleep'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setSleepingAllowed');
|
||||
@@ -1296,7 +1296,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Layer (1 - 16)'))
|
||||
.addParameter('yesorno', _('Enable?'), '', false)
|
||||
.addParameter('yesorno', _('Enable'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableLayer');
|
||||
@@ -1332,7 +1332,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Mask (1 - 16)'))
|
||||
.addParameter('yesorno', _('Enable?'), '', false)
|
||||
.addParameter('yesorno', _('Enable'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableMask');
|
||||
@@ -2409,7 +2409,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableRevoluteJointLimits');
|
||||
|
||||
@@ -2488,7 +2488,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableRevoluteJointMotor');
|
||||
|
||||
@@ -2727,7 +2727,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enablePrismaticJointLimits');
|
||||
|
||||
@@ -2806,7 +2806,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enablePrismaticJointMotor');
|
||||
|
||||
@@ -3486,7 +3486,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableWheelJointMotor');
|
||||
|
||||
|
@@ -274,7 +274,7 @@ module.exports = {
|
||||
.setLabel('Fixed Rotation')
|
||||
.setDescription(
|
||||
_(
|
||||
"If enabled, the object won't rotate and will stay at the same angle. Useful for characters for example."
|
||||
"If enabled, the object won't rotate and will stay at the same angle."
|
||||
)
|
||||
)
|
||||
.setGroup(_('Movement'));
|
||||
@@ -845,7 +845,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.addParameter('yesorno', _('Treat as bullet?'), '', false)
|
||||
.addParameter('yesorno', _('Treat as bullet'), '', false)
|
||||
.setDefaultValue('false')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setBullet');
|
||||
@@ -870,7 +870,7 @@ module.exports = {
|
||||
'SetFixedRotation',
|
||||
_('Fixed rotation'),
|
||||
_(
|
||||
"Enable or disable an object fixed rotation. If enabled the object won't be able to rotate."
|
||||
"Enable or disable an object fixed rotation. If enabled the object won't be able to rotate. This action has no effect on characters."
|
||||
),
|
||||
_('Set _PARAM0_ fixed rotation: _PARAM2_'),
|
||||
_('Dynamics'),
|
||||
@@ -879,7 +879,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.addParameter('yesorno', _('Fixed rotation?'), '', false)
|
||||
.addParameter('yesorno', _('Fixed rotation'), '', false)
|
||||
.setDefaultValue('false')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setFixedRotation');
|
||||
@@ -1054,7 +1054,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.addParameter('expression', _('Layer (1 - 8)'))
|
||||
.addParameter('yesorno', _('Enable?'), '', false)
|
||||
.addParameter('yesorno', _('Enable'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableLayer');
|
||||
@@ -1090,7 +1090,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.addParameter('expression', _('Mask (1 - 8)'))
|
||||
.addParameter('yesorno', _('Enable?'), '', false)
|
||||
.addParameter('yesorno', _('Enable'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableMask');
|
||||
@@ -1270,7 +1270,7 @@ module.exports = {
|
||||
.addParameter('expression', _('Application point on Z axis'))
|
||||
.setParameterLongDescription(
|
||||
_(
|
||||
'Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'
|
||||
'Use `MassCenterX`, `MassCenterY` and `MassCenterZ` expressions to avoid any rotation.'
|
||||
)
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
@@ -1544,6 +1544,19 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('getMassCenterY');
|
||||
|
||||
aut
|
||||
.addExpression(
|
||||
'MassCenterZ',
|
||||
_('Mass center Z'),
|
||||
_('Mass center Z'),
|
||||
'',
|
||||
'JsPlatform/Extensions/physics3d.svg'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('getMassCenterZ');
|
||||
}
|
||||
// Collision
|
||||
extension
|
||||
|
@@ -927,9 +927,7 @@ namespace gdjs {
|
||||
const angularVelocityY = angularVelocity.GetY();
|
||||
const angularVelocityZ = angularVelocity.GetZ();
|
||||
|
||||
let bodyID = this._body.GetID();
|
||||
bodyInterface.RemoveBody(bodyID);
|
||||
bodyInterface.DestroyBody(bodyID);
|
||||
this.bodyUpdater.destroyBody();
|
||||
this._contactsEndedThisFrame.length = 0;
|
||||
this._contactsStartedThisFrame.length = 0;
|
||||
this._currentContacts.length = 0;
|
||||
@@ -938,7 +936,7 @@ namespace gdjs {
|
||||
if (!this._body) {
|
||||
return;
|
||||
}
|
||||
bodyID = this._body.GetID();
|
||||
const bodyID = this._body.GetID();
|
||||
bodyInterface.SetLinearVelocity(
|
||||
bodyID,
|
||||
this.getVec3(linearVelocityX, linearVelocityY, linearVelocityZ)
|
||||
|
@@ -733,7 +733,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
setWheelOffsetZ(wheelOffsetZ: float): void {
|
||||
this._wheelOffsetY = wheelOffsetZ;
|
||||
this._wheelOffsetZ = wheelOffsetZ;
|
||||
this._updateWheels();
|
||||
}
|
||||
|
||||
@@ -783,11 +783,11 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
hasFrontWheelDrive(): boolean {
|
||||
return this._hasBackWheelDrive;
|
||||
return this._hasFrontWheelDrive;
|
||||
}
|
||||
|
||||
setFrontWheelDrive(hasFrontWheelDrive: boolean): void {
|
||||
this._hasBackWheelDrive = hasFrontWheelDrive;
|
||||
this._hasFrontWheelDrive = hasFrontWheelDrive;
|
||||
this.invalidateShape();
|
||||
}
|
||||
|
||||
|
@@ -14,6 +14,7 @@ namespace gdjs {
|
||||
animatable?: SpriteAnimationData[];
|
||||
variant: string;
|
||||
childrenContent: { [objectName: string]: ObjectConfiguration & any };
|
||||
isInnerAreaFollowingParentSize: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -111,9 +112,19 @@ namespace gdjs {
|
||||
name: '',
|
||||
};
|
||||
}
|
||||
// Legacy events-based objects don't have any instance in their default
|
||||
// variant since there wasn't a graphical editor at the time.
|
||||
// In this case, the editor doesn't allow to choose a variant, but a
|
||||
// variant may have stayed after a user rolled back the extension.
|
||||
// This variant must be ignored to match what the editor shows.
|
||||
const isForcedToOverrideEventsBasedObjectChildrenConfiguration =
|
||||
eventsBasedObjectData.defaultVariant.instances.length == 0;
|
||||
let usedVariantData: EventsBasedObjectVariantData =
|
||||
eventsBasedObjectData.defaultVariant;
|
||||
if (customObjectData.variant) {
|
||||
if (
|
||||
customObjectData.variant &&
|
||||
!isForcedToOverrideEventsBasedObjectChildrenConfiguration
|
||||
) {
|
||||
for (
|
||||
let variantIndex = 0;
|
||||
variantIndex < eventsBasedObjectData.variants.length;
|
||||
@@ -154,10 +165,12 @@ namespace gdjs {
|
||||
override reinitialize(objectData: ObjectData & CustomObjectConfiguration) {
|
||||
super.reinitialize(objectData);
|
||||
|
||||
this._initializeFromObjectData(objectData);
|
||||
this._reinitializeRenderer();
|
||||
this._initializeFromObjectData(objectData);
|
||||
|
||||
// The generated code calls the onCreated super implementation at the end.
|
||||
// When changing the variant, the instance is like a new instance.
|
||||
// We call again `onCreated` at the end, like done by the constructor
|
||||
// the first time it's created.
|
||||
this.onCreated();
|
||||
}
|
||||
|
||||
@@ -172,6 +185,34 @@ namespace gdjs {
|
||||
newObjectData.animatable || []
|
||||
);
|
||||
}
|
||||
if (oldObjectData.variant !== newObjectData.variant) {
|
||||
const width = this.getWidth();
|
||||
const height = this.getHeight();
|
||||
const hasInnerAreaChanged =
|
||||
oldObjectData.isInnerAreaFollowingParentSize &&
|
||||
this._instanceContainer._initialInnerArea &&
|
||||
this._innerArea &&
|
||||
(this._instanceContainer._initialInnerArea.min[0] !==
|
||||
this._innerArea.min[0] ||
|
||||
this._instanceContainer._initialInnerArea.min[1] !==
|
||||
this._innerArea.min[1] ||
|
||||
this._instanceContainer._initialInnerArea.max[0] !==
|
||||
this._innerArea.max[0] ||
|
||||
this._instanceContainer._initialInnerArea.max[1] !==
|
||||
this._innerArea.max[1]);
|
||||
|
||||
this._reinitializeRenderer();
|
||||
this._initializeFromObjectData(newObjectData);
|
||||
|
||||
// The generated code calls the onCreated super implementation at the end.
|
||||
this.onCreated();
|
||||
|
||||
// Keep the custom size
|
||||
if (hasInnerAreaChanged) {
|
||||
this.setWidth(width);
|
||||
this.setHeight(height);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -8,7 +8,6 @@ namespace gdjs {
|
||||
objectData: ObjectData & CustomObjectConfiguration
|
||||
) {
|
||||
super(parent, objectData);
|
||||
this.getRenderer().reinitialize(this, parent);
|
||||
}
|
||||
|
||||
protected override _createRender(): gdjs.CustomRuntimeObject2DRenderer {
|
||||
|
@@ -24,7 +24,7 @@ namespace gdjs {
|
||||
*
|
||||
* @see gdjs.CustomRuntimeObject._innerArea
|
||||
**/
|
||||
private _initialInnerArea: {
|
||||
_initialInnerArea: {
|
||||
min: [float, float, float];
|
||||
max: [float, float, float];
|
||||
} | null = null;
|
||||
@@ -47,6 +47,9 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
addLayer(layerData: LayerData) {
|
||||
if (this._layers.containsKey(layerData.name)) {
|
||||
return;
|
||||
}
|
||||
const layer = new gdjs.RuntimeCustomObjectLayer(layerData, this);
|
||||
this._layers.put(layerData.name, layer);
|
||||
this._orderedLayers.push(layer);
|
||||
@@ -71,6 +74,10 @@ namespace gdjs {
|
||||
this.onDestroyFromScene(this._parent);
|
||||
}
|
||||
|
||||
const isForcedToOverrideEventsBasedObjectChildrenConfiguration =
|
||||
!eventsBasedObjectVariantData.name &&
|
||||
eventsBasedObjectVariantData.instances.length == 0;
|
||||
|
||||
this._setOriginalInnerArea(eventsBasedObjectVariantData);
|
||||
|
||||
// Registering objects
|
||||
@@ -83,7 +90,8 @@ namespace gdjs {
|
||||
// The children configuration override only applies to the default variant.
|
||||
if (
|
||||
customObjectData.childrenContent &&
|
||||
!eventsBasedObjectVariantData.name
|
||||
(!eventsBasedObjectVariantData.name ||
|
||||
isForcedToOverrideEventsBasedObjectChildrenConfiguration)
|
||||
) {
|
||||
this.registerObject({
|
||||
...childObjectData,
|
||||
|
@@ -811,6 +811,8 @@ namespace gdjs {
|
||||
this._objectsCtor = new Hashtable();
|
||||
this._allInstancesList = [];
|
||||
this._instancesRemoved = [];
|
||||
this._layersCameraCoordinates = {};
|
||||
this._initialBehaviorSharedData = new Hashtable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -222,7 +222,9 @@ namespace gdjs {
|
||||
kind: 'fatal',
|
||||
message:
|
||||
'Unexpected error happened while hot-reloading: ' +
|
||||
error.message,
|
||||
error.message +
|
||||
'\n' +
|
||||
error.stack,
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -472,13 +474,24 @@ namespace gdjs {
|
||||
newExternalLayoutData.associatedLayout
|
||||
);
|
||||
|
||||
const oldObjectDataList =
|
||||
HotReloader.resolveCustomObjectConfigurations(
|
||||
oldProjectData,
|
||||
oldLayoutData ? oldLayoutData.objects : []
|
||||
);
|
||||
const newObjectDataList =
|
||||
HotReloader.resolveCustomObjectConfigurations(
|
||||
newProjectData,
|
||||
newLayoutData ? newLayoutData.objects : []
|
||||
);
|
||||
|
||||
sceneStack._stack.forEach((runtimeScene) => {
|
||||
this._hotReloadRuntimeSceneInstances(
|
||||
oldProjectData,
|
||||
newProjectData,
|
||||
changedRuntimeBehaviors,
|
||||
oldLayoutData ? oldLayoutData.objects : [],
|
||||
newLayoutData ? newLayoutData.objects : [],
|
||||
oldObjectDataList,
|
||||
newObjectDataList,
|
||||
oldExternalLayoutData.instances,
|
||||
newExternalLayoutData.instances,
|
||||
runtimeScene
|
||||
|
@@ -50,12 +50,7 @@ namespace gdjs {
|
||||
) {
|
||||
this._object = object;
|
||||
this._isContainerDirty = true;
|
||||
const layer = parent.getLayer('');
|
||||
if (layer) {
|
||||
layer
|
||||
.getRenderer()
|
||||
.addRendererObject(this._pixiContainer, object.getZOrder());
|
||||
}
|
||||
this._pixiContainer.removeChildren();
|
||||
}
|
||||
|
||||
getRendererObject() {
|
||||
|
2
GDJS/Runtime/types/project-data.d.ts
vendored
2
GDJS/Runtime/types/project-data.d.ts
vendored
@@ -229,7 +229,7 @@ declare interface EventsBasedObjectVariantData extends InstanceContainerData {
|
||||
/**
|
||||
* A value shared by every object instances.
|
||||
*
|
||||
* @see gdjs.CustomRuntimeObjectInstanceContainer._originalInnerArea
|
||||
* @see gdjs.CustomRuntimeObjectInstanceContainer._initialInnerArea
|
||||
**/
|
||||
_initialInnerArea: {
|
||||
min: [float, float, float];
|
||||
|
@@ -12,9 +12,8 @@ npm install
|
||||
Then launch tests:
|
||||
|
||||
```bash
|
||||
npm run test:watch # This will use Chrome Headless
|
||||
npm run test:watch # Runs tests in a headless browser using Vitest
|
||||
npm run test-benchmark:watch # This will also run benchmarks
|
||||
npm run test:firefox:watch # To run tests using Firefox
|
||||
```
|
||||
|
||||
> ⚠️ If you're working on GDJS or extensions, make sure to have the development version of GDevelop running so that changes in GDJS or extension files are rebuilt (or run `npm run build` in `GDJS/`, but better run GDevelop so that any changes are watched).
|
||||
@@ -23,7 +22,7 @@ npm run test:firefox:watch # To run tests using Firefox
|
||||
|
||||
### Unit tests
|
||||
|
||||
Tests are launched using Chrome. You need Chrome installed to run them. You can change the browser by modifying the package.json "test" command and install the appropriate karma package.
|
||||
Tests are launched using [Vitest](https://vitest.dev/) in a headless browser. You can change the browser by editing the `vitest.config.js` file.
|
||||
|
||||
Tests are located in the **tests** folder for the game engine, or directly in the folder of the tested extensions.
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
describe('gdjs.Force', function() {
|
||||
it('benchmark setting angle and length', function(){
|
||||
this.timeout(20000);
|
||||
it('benchmark setting angle and length', function(){
|
||||
vi.setTimeout(20000);
|
||||
var layer = new gdjs.Force();
|
||||
|
||||
const benchmarkSuite = makeBenchmarkSuite();
|
||||
|
@@ -5,7 +5,7 @@ describe('gdjs.Layer', function() {
|
||||
var runtimeScene = new gdjs.RuntimeScene(runtimeGame);
|
||||
|
||||
it('benchmark convertCoords and convertInverseCoords', function() {
|
||||
this.timeout(30000);
|
||||
vi.setTimeout(30000);
|
||||
var layer = new gdjs.Layer(
|
||||
{ name: 'My layer',
|
||||
visibility: true,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
describe('gdjs.Polygon', function() {
|
||||
it('benchmark gdjs.Polygon.collisionTest between two polygons', function() {
|
||||
this.timeout(20000);
|
||||
vi.setTimeout(20000);
|
||||
var rect1 = gdjs.Polygon.createRectangle(32, 40);
|
||||
var rect2 = gdjs.Polygon.createRectangle(32, 40);
|
||||
var rect3 = gdjs.Polygon.createRectangle(32, 40);
|
||||
|
@@ -1,8 +1,8 @@
|
||||
describe('gdjs.RuntimeObject', function() {
|
||||
const runtimeScene = new gdjs.RuntimeScene(null);
|
||||
|
||||
it('benchmark getAABB of rotated vs non rotated objects', function(){
|
||||
this.timeout(20000);
|
||||
it('benchmark getAABB of rotated vs non rotated objects', function(){
|
||||
vi.setTimeout(20000);
|
||||
var object = new gdjs.RuntimeObject(runtimeScene, {name: "obj1", type: "", behaviors: [], effects: []});
|
||||
object.getWidth = function() { return 10; };
|
||||
object.getHeight = function() { return 20; };
|
||||
@@ -25,8 +25,8 @@ describe('gdjs.RuntimeObject', function() {
|
||||
console.log(benchmarkSuite.run());
|
||||
});
|
||||
|
||||
it('benchmark getAABB of rotated vs non rotated objects, with non default center', function(){
|
||||
this.timeout(20000);
|
||||
it('benchmark getAABB of rotated vs non rotated objects, with non default center', function(){
|
||||
vi.setTimeout(20000);
|
||||
var object = new gdjs.RuntimeObject(runtimeScene, {name: "obj1", type: "", behaviors: [], effects: []});
|
||||
object.getWidth = function() { return 10; };
|
||||
object.getHeight = function() { return 20; };
|
||||
|
@@ -65,7 +65,7 @@ describe('gdjs.SpriteRuntimeObject', function () {
|
||||
});
|
||||
|
||||
it('benchmark getAABB of rotated vs non rotated sprite, with custom hitboxes, origin and center', function () {
|
||||
this.timeout(20000);
|
||||
vi.setTimeout(20000);
|
||||
const object = makeSpriteRuntimeObjectWithCustomHitBox(runtimeScene);
|
||||
|
||||
const benchmarkSuite = makeBenchmarkSuite({
|
||||
|
@@ -1,6 +1,6 @@
|
||||
describe('gdjs.VariablesContainer', function() {
|
||||
it('benchmark get', function() {
|
||||
this.timeout(20000);
|
||||
vi.setTimeout(20000);
|
||||
var container = new gdjs.VariablesContainer();
|
||||
|
||||
const benchmarkSuite = makeBenchmarkSuite();
|
||||
|
@@ -5,16 +5,10 @@
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "karma start --browsers ChromeHeadless --single-run",
|
||||
"test:watch": "karma start --browsers ChromeHeadless",
|
||||
"test-benchmark": "karma start --browsers ChromeHeadless --single-run --enableBenchmarks",
|
||||
"test-benchmark:watch": "karma start --browsers ChromeHeadless --enableBenchmarks",
|
||||
"test:firefox": "karma start --browsers Firefox --single-run",
|
||||
"test:firefox:watch": "karma start --browsers Firefox",
|
||||
"test:chrome": "karma start --browsers Chrome --single-run",
|
||||
"test:chrome:watch": "karma start --browsers Chrome",
|
||||
"test:edge": "karma start --browsers EdgeHeadless --single-run",
|
||||
"test:edge:watch": "karma start --browsers EdgeHeadless"
|
||||
"test": "vitest run --browser",
|
||||
"test:watch": "vitest --browser",
|
||||
"test-benchmark": "vitest run --browser",
|
||||
"test-benchmark:watch": "vitest --browser"
|
||||
},
|
||||
"keywords": [
|
||||
"HTML5",
|
||||
@@ -30,12 +24,9 @@
|
||||
"mocha": "^1.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chiragrupani/karma-chromium-edge-launcher": "2.1.1",
|
||||
"karma": "^1.7.1",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-firefox-launcher": "^1.1.0",
|
||||
"karma-mocha": "^1.3.0",
|
||||
"karma-sinon": "^1.0.5",
|
||||
"vitest": "^0.34.0",
|
||||
"vite": "^4.4.9",
|
||||
"@vitest/browser": "^0.34.0",
|
||||
"sinon": "^15.0.1"
|
||||
}
|
||||
}
|
||||
|
@@ -16,6 +16,7 @@ describe('gdjs.CustomRuntimeObject', function () {
|
||||
name: 'MyCustomObject',
|
||||
type: 'MyExtension::MyEventsBasedObject',
|
||||
variant: '',
|
||||
isInnerAreaFollowingParentSize: false,
|
||||
variables: [],
|
||||
behaviors: [],
|
||||
effects: [],
|
||||
|
@@ -27,7 +27,7 @@
|
||||
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
|
||||
const asyncTasksManager = runtimeScene.getAsyncTasksManager();
|
||||
|
||||
this.beforeEach(() => asyncTasksManager.clearTasks());
|
||||
beforeEach(() => asyncTasksManager.clearTasks());
|
||||
|
||||
it('should call a resolved callback', function () {
|
||||
const cb = createMockCallback();
|
||||
|
@@ -100,6 +100,7 @@ describe('gdjs.HotReloader._hotReloadRuntimeGame', () => {
|
||||
effects: [],
|
||||
content: {},
|
||||
childrenContent: {},
|
||||
isInnerAreaFollowingParentSize: false,
|
||||
};
|
||||
|
||||
/** @type {LayerData} */
|
||||
|
19
GDJS/tests/vitest.config.js
Normal file
19
GDJS/tests/vitest.config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
browser: {
|
||||
enabled: true,
|
||||
name: 'chrome',
|
||||
headless: true,
|
||||
},
|
||||
setupFiles: [
|
||||
'./vitest.setup.js',
|
||||
'./tests-utils/init.js',
|
||||
'./tests-utils/init.pixiruntimegamewithassets.js',
|
||||
'./tests-utils/init.pixiruntimegame.js',
|
||||
'./tests-utils/MockedCustomObject.js',
|
||||
],
|
||||
include: ['tests/**/*.js', 'benchmarks/**/*.js', '../Extensions/**/tests/**/*.js'],
|
||||
},
|
||||
});
|
9
GDJS/tests/vitest.setup.js
Normal file
9
GDJS/tests/vitest.setup.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import expect from 'expect.js';
|
||||
import sinon from 'sinon';
|
||||
|
||||
// Expose expect.js and sinon globally like Karma used to do.
|
||||
// Vitest provides its own expect, but tests rely on expect.js syntax.
|
||||
// eslint-disable-next-line no-undef
|
||||
global.expect = expect;
|
||||
// eslint-disable-next-line no-undef
|
||||
global.sinon = sinon;
|
@@ -1405,6 +1405,7 @@ interface InitialInstance {
|
||||
double GetCustomDepth();
|
||||
|
||||
[Ref] InitialInstance ResetPersistentUuid();
|
||||
[Const, Ref] DOMString GetPersistentUuid();
|
||||
|
||||
void UpdateCustomProperty(
|
||||
[Const] DOMString name,
|
||||
|
1
GDevelop.js/types.d.ts
vendored
1
GDevelop.js/types.d.ts
vendored
@@ -1167,6 +1167,7 @@ export class InitialInstance extends EmscriptenObject {
|
||||
setCustomDepth(depth: number): void;
|
||||
getCustomDepth(): number;
|
||||
resetPersistentUuid(): InitialInstance;
|
||||
getPersistentUuid(): string;
|
||||
updateCustomProperty(name: string, value: string, globalObjectsContainer: ObjectsContainer, objectsContainer: ObjectsContainer): void;
|
||||
getCustomProperties(globalObjectsContainer: ObjectsContainer, objectsContainer: ObjectsContainer): MapStringPropertyDescriptor;
|
||||
getRawDoubleProperty(name: string): number;
|
||||
|
@@ -44,6 +44,7 @@ declare class gdInitialInstance {
|
||||
setCustomDepth(depth: number): void;
|
||||
getCustomDepth(): number;
|
||||
resetPersistentUuid(): gdInitialInstance;
|
||||
getPersistentUuid(): string;
|
||||
updateCustomProperty(name: string, value: string, globalObjectsContainer: gdObjectsContainer, objectsContainer: gdObjectsContainer): void;
|
||||
getCustomProperties(globalObjectsContainer: gdObjectsContainer, objectsContainer: gdObjectsContainer): gdMapStringPropertyDescriptor;
|
||||
getRawDoubleProperty(name: string): number;
|
||||
|
@@ -30,6 +30,21 @@
|
||||
justify-content: center;
|
||||
|
||||
animation: new-chat-appear 0.5s;
|
||||
|
||||
margin-bottom: var(--safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.aiRequestChatContainer {
|
||||
display: flex;
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
flex: 1 1 0%;
|
||||
min-height: 0px;
|
||||
|
||||
margin-bottom: var(--safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.thinkingText {
|
||||
|
@@ -11,7 +11,7 @@ import { Tooltip } from '@material-ui/core';
|
||||
import Text from '../../UI/Text';
|
||||
import RaisedButton from '../../UI/RaisedButton';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import RaisedButtonWithSplitMenu from '../../UI/RaisedButtonWithSplitMenu';
|
||||
import FlatButtonWithSplitMenu from '../../UI/FlatButtonWithSplitMenu';
|
||||
import Check from '../../UI/CustomSvgIcons/Check';
|
||||
import Error from '../../UI/CustomSvgIcons/Error';
|
||||
import GDevelopThemeContext from '../../UI/Theme/GDevelopThemeContext';
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
type EditorCallbacks,
|
||||
} from '../../EditorFunctions';
|
||||
import Link from '../../UI/Link';
|
||||
import { LineStackLayout } from '../../UI/Layout';
|
||||
import { LineStackLayout, ResponsiveLineStackLayout } from '../../UI/Layout';
|
||||
import ChevronArrowRight from '../../UI/CustomSvgIcons/ChevronArrowRight';
|
||||
import ChevronArrowBottom from '../../UI/CustomSvgIcons/ChevronArrowBottom';
|
||||
import Paper from '../../UI/Paper';
|
||||
@@ -108,6 +108,7 @@ export const FunctionCallRow = React.memo<Props>(function FunctionCallRow({
|
||||
details = result.details;
|
||||
hasDetailsToShow = result.hasDetailsToShow;
|
||||
} catch (error) {
|
||||
console.error('Error rendering function call:', error);
|
||||
text = (
|
||||
<Trans>
|
||||
The editor was unable to display the operation ({functionCall.name})
|
||||
@@ -141,60 +142,73 @@ export const FunctionCallRow = React.memo<Props>(function FunctionCallRow({
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<LineStackLayout noMargin alignItems="baseline">
|
||||
<Text>{text || 'Working...'}</Text>
|
||||
{hasDetailsToShow && (
|
||||
<Text size="body-small" color="secondary">
|
||||
<Link
|
||||
color="inherit"
|
||||
href={'#'}
|
||||
onClick={() => setShowDetails(!showDetails)}
|
||||
>
|
||||
<Trans>Details</Trans>
|
||||
{details ? (
|
||||
<ChevronArrowBottom
|
||||
fontSize="small"
|
||||
style={{
|
||||
verticalAlign: 'middle',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ChevronArrowRight
|
||||
fontSize="small"
|
||||
style={{
|
||||
verticalAlign: 'middle',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
</Text>
|
||||
)}
|
||||
</LineStackLayout>
|
||||
{!isFinished && (
|
||||
<RaisedButtonWithSplitMenu
|
||||
primary
|
||||
disabled={isWorking}
|
||||
onClick={() => onProcessFunctionCalls([functionCall])}
|
||||
label={<Trans>Apply</Trans>}
|
||||
buildMenuTemplate={i18n => [
|
||||
{
|
||||
label: i18n._(t`Ignore this`),
|
||||
click: () => {
|
||||
onProcessFunctionCalls([functionCall], {
|
||||
ignore: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
{functionCallResultIsErrored && (
|
||||
<RaisedButton
|
||||
color="primary"
|
||||
onClick={() => onProcessFunctionCalls([functionCall])}
|
||||
label={<Trans>Retry</Trans>}
|
||||
/>
|
||||
)}
|
||||
<ResponsiveLineStackLayout
|
||||
justifyContent="space-between"
|
||||
expand
|
||||
noOverflowParent
|
||||
>
|
||||
<LineStackLayout noMargin alignItems="baseline">
|
||||
<Text>{text || 'Working...'}</Text>
|
||||
{hasDetailsToShow && (
|
||||
<Text size="body-small" color="secondary">
|
||||
<Link
|
||||
color="inherit"
|
||||
href={'#'}
|
||||
onClick={() => setShowDetails(!showDetails)}
|
||||
>
|
||||
<Trans>Details</Trans>
|
||||
{details ? (
|
||||
<ChevronArrowBottom
|
||||
fontSize="small"
|
||||
style={{
|
||||
verticalAlign: 'middle',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ChevronArrowRight
|
||||
fontSize="small"
|
||||
style={{
|
||||
verticalAlign: 'middle',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
</Text>
|
||||
)}
|
||||
</LineStackLayout>
|
||||
<LineStackLayout
|
||||
noMargin
|
||||
alignItems="baseline"
|
||||
justifyContent="flex-end"
|
||||
neverShrink
|
||||
>
|
||||
{!isFinished && !isWorking && (
|
||||
<FlatButtonWithSplitMenu
|
||||
primary
|
||||
style={{ flexShrink: 0 }}
|
||||
onClick={() => onProcessFunctionCalls([functionCall])}
|
||||
label={<Trans>Execute this action</Trans>}
|
||||
buildMenuTemplate={i18n => [
|
||||
{
|
||||
label: i18n._(t`Ignore this`),
|
||||
click: () => {
|
||||
onProcessFunctionCalls([functionCall], {
|
||||
ignore: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
{functionCallResultIsErrored && (
|
||||
<RaisedButton
|
||||
color="primary"
|
||||
onClick={() => onProcessFunctionCalls([functionCall])}
|
||||
label={<Trans>Retry</Trans>}
|
||||
/>
|
||||
)}
|
||||
</LineStackLayout>
|
||||
</ResponsiveLineStackLayout>
|
||||
</LineStackLayout>
|
||||
{details && (
|
||||
<div className={classes.detailsPaperContainer}>
|
||||
|
@@ -37,7 +37,7 @@ import Hammer from '../../UI/CustomSvgIcons/Hammer';
|
||||
import { ChatMessages } from './ChatMessages';
|
||||
import Send from '../../UI/CustomSvgIcons/Send';
|
||||
import { FeedbackBanner } from './FeedbackBanner';
|
||||
import LeftLoader from '../../UI/LeftLoader';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const TOO_MANY_USER_MESSAGES_WARNING_COUNT = 5;
|
||||
const TOO_MANY_USER_MESSAGES_ERROR_COUNT = 10;
|
||||
@@ -79,6 +79,7 @@ type Props = {
|
||||
hasOpenedProject: boolean,
|
||||
isAutoProcessingFunctionCalls: boolean,
|
||||
setAutoProcessFunctionCalls: boolean => void,
|
||||
onStartNewChat: () => void,
|
||||
|
||||
onProcessFunctionCalls: (
|
||||
functionCalls: Array<AiRequestMessageAssistantFunctionCall>,
|
||||
@@ -232,6 +233,7 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
onStartNewAiRequest,
|
||||
onSendMessage,
|
||||
onSendFeedback,
|
||||
onStartNewChat,
|
||||
quota,
|
||||
increaseQuotaOffering,
|
||||
lastSendError,
|
||||
@@ -257,6 +259,7 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
setUserRequestTextPerRequestId,
|
||||
] = React.useState<{ [string]: string }>({});
|
||||
const scrollViewRef = React.useRef<ScrollViewInterface | null>(null);
|
||||
const requiredGameId = (aiRequest && aiRequest.gameId) || null;
|
||||
|
||||
const newChatPlaceholder = React.useMemo(
|
||||
() => {
|
||||
@@ -266,7 +269,7 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
? [
|
||||
t`Add solid rocks that falls from the sky at a random position around the player every 0.5 seconds`,
|
||||
t`Add a score and display it on the screen`,
|
||||
t`Create an explosion when the player is hit`,
|
||||
t`Create a 3D explosion when the player is hit`,
|
||||
]
|
||||
: [
|
||||
t`Build a platformer game with a score and coins to collect`,
|
||||
@@ -328,6 +331,7 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
const subscriptionBanner =
|
||||
quota && quota.limitReached && increaseQuotaOffering !== 'none' ? (
|
||||
<GetSubscriptionCard
|
||||
placementId="ai-requests"
|
||||
subscriptionDialogOpeningReason={
|
||||
increaseQuotaOffering === 'subscribe'
|
||||
? 'AI requests (subscribe)'
|
||||
@@ -384,7 +388,13 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
|
||||
if (!aiRequest) {
|
||||
return (
|
||||
<div className={classes.newChatContainer}>
|
||||
<div
|
||||
className={classNames({
|
||||
[classes.newChatContainer]: true,
|
||||
// Move the entire screen up when the soft keyboard is open:
|
||||
'avoid-soft-keyboard': true,
|
||||
})}
|
||||
>
|
||||
<ColumnStackLayout justifyContent="center" expand>
|
||||
<Line noMargin justifyContent="center">
|
||||
<RobotIcon rotating size={40} />
|
||||
@@ -434,6 +444,8 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
maxLength={6000}
|
||||
value={userRequestTextPerAiRequestId[''] || ''}
|
||||
disabled={isSending}
|
||||
hasNeonCorner
|
||||
hasAnimatedNeonCorner={isSending}
|
||||
errored={!!lastSendError}
|
||||
onChange={userRequestText =>
|
||||
setUserRequestTextPerRequestId(
|
||||
@@ -457,35 +469,32 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
alignItems="center"
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<LeftLoader isLoading={isSending}>
|
||||
<RaisedButton
|
||||
color="primary"
|
||||
icon={<Send />}
|
||||
label={
|
||||
newAiRequestMode === 'agent' ? (
|
||||
hasOpenedProject ? (
|
||||
<Trans>Build this on my game</Trans>
|
||||
) : (
|
||||
<Trans>Start building the game</Trans>
|
||||
)
|
||||
<RaisedButton
|
||||
color="primary"
|
||||
icon={<Send />}
|
||||
label={
|
||||
newAiRequestMode === 'agent' ? (
|
||||
hasOpenedProject ? (
|
||||
<Trans>Build this on my game</Trans>
|
||||
) : (
|
||||
<Trans>Send question</Trans>
|
||||
<Trans>Start building the game</Trans>
|
||||
)
|
||||
}
|
||||
style={{ flexShrink: 0 }}
|
||||
disabled={
|
||||
isSending ||
|
||||
!userRequestTextPerAiRequestId[aiRequestId]
|
||||
}
|
||||
onClick={() => {
|
||||
onStartNewAiRequest({
|
||||
mode: newAiRequestMode,
|
||||
userRequest:
|
||||
userRequestTextPerAiRequestId[''],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</LeftLoader>
|
||||
) : (
|
||||
<Trans>Send question</Trans>
|
||||
)
|
||||
}
|
||||
style={{ flexShrink: 0 }}
|
||||
disabled={
|
||||
isSending ||
|
||||
!userRequestTextPerAiRequestId[aiRequestId]
|
||||
}
|
||||
onClick={() => {
|
||||
onStartNewAiRequest({
|
||||
mode: newAiRequestMode,
|
||||
userRequest: userRequestTextPerAiRequestId[''],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</LineStackLayout>
|
||||
</Column>
|
||||
}
|
||||
@@ -517,10 +526,10 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
<Trans>
|
||||
The AI agent will build simple games or features for you.{' '}
|
||||
<Link
|
||||
href={getHelpLink('/interface/ask-ai')}
|
||||
href={getHelpLink('/interface/ai')}
|
||||
color="secondary"
|
||||
onClick={() =>
|
||||
Window.openExternalURL(getHelpLink('/interface/ask-ai'))
|
||||
Window.openExternalURL(getHelpLink('/interface/ai'))
|
||||
}
|
||||
>
|
||||
It can inspect your game objects and events.
|
||||
@@ -532,10 +541,10 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
<Trans>
|
||||
The AI chat is experimental and still being improved.{' '}
|
||||
<Link
|
||||
href={getHelpLink('/interface/ask-ai')}
|
||||
href={getHelpLink('/interface/ai')}
|
||||
color="secondary"
|
||||
onClick={() =>
|
||||
Window.openExternalURL(getHelpLink('/interface/ask-ai'))
|
||||
Window.openExternalURL(getHelpLink('/interface/ai'))
|
||||
}
|
||||
>
|
||||
It has access to your game objects but not events.
|
||||
@@ -572,11 +581,14 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
aiRequest,
|
||||
editorFunctionCallResults,
|
||||
});
|
||||
const isPausedAndHasFunctionCallsToProcess =
|
||||
!isAutoProcessingFunctionCalls && allFunctionCallsToProcess.length > 0;
|
||||
|
||||
const lastMessageIndex = aiRequest.output.length - 1;
|
||||
const lastMessage = aiRequest.output[lastMessageIndex];
|
||||
const shouldDisplayFeedbackBanner =
|
||||
!hasWorkingFunctionCalls &&
|
||||
!isPausedAndHasFunctionCallsToProcess &&
|
||||
!isSending &&
|
||||
aiRequest.status === 'ready' &&
|
||||
aiRequest.mode === 'agent' &&
|
||||
@@ -600,12 +612,26 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
/>
|
||||
);
|
||||
|
||||
const isForAnotherProject =
|
||||
!!requiredGameId &&
|
||||
(!project || requiredGameId !== project.getProjectUuid());
|
||||
const isForAnotherProjectText = isForAnotherProject ? (
|
||||
<Text size="body-small" color="secondary" align="center" noMargin>
|
||||
<Trans>
|
||||
This request is for another project.{' '}
|
||||
<Link href="#" onClick={onStartNewChat}>
|
||||
Start a new chat
|
||||
</Link>{' '}
|
||||
to build on a new project.
|
||||
</Trans>
|
||||
</Text>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<Column
|
||||
expand
|
||||
alignItems="stretch"
|
||||
justifyContent="stretch"
|
||||
useFullHeight
|
||||
<div
|
||||
className={classNames({
|
||||
[classes.aiRequestChatContainer]: true,
|
||||
})}
|
||||
>
|
||||
<ScrollView ref={scrollViewRef} style={styles.chatScrollView}>
|
||||
<ChatMessages
|
||||
@@ -644,24 +670,29 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
userMessage: userRequestTextPerAiRequestId[aiRequestId] || '',
|
||||
});
|
||||
}}
|
||||
className={classNames({
|
||||
// Move the form up when the soft keyboard is open:
|
||||
'avoid-soft-keyboard': true,
|
||||
})}
|
||||
>
|
||||
<ColumnStackLayout
|
||||
justifyContent="stretch"
|
||||
alignItems="stretch"
|
||||
noMargin
|
||||
>
|
||||
{isAutoProcessingFunctionCalls &&
|
||||
{aiRequest.mode === 'agent' &&
|
||||
isAutoProcessingFunctionCalls &&
|
||||
(hasWorkingFunctionCalls ||
|
||||
isSending ||
|
||||
aiRequest.status === 'working') ? (
|
||||
<Paper background="dark" variant="outlined" square>
|
||||
<Paper background="dark" variant="outlined">
|
||||
<Column>
|
||||
<LineStackLayout
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<LineStackLayout alignItems="center" noMargin>
|
||||
<CircularProgress variant="indeterminate" size={10} />
|
||||
<CircularProgress variant="indeterminate" size={12} />
|
||||
<Text size="body" color="secondary" noMargin>
|
||||
<Trans>The AI is building your request.</Trans>
|
||||
</Text>
|
||||
@@ -680,9 +711,9 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
</LineStackLayout>
|
||||
</Column>
|
||||
</Paper>
|
||||
) : !isAutoProcessingFunctionCalls &&
|
||||
allFunctionCallsToProcess.length > 0 ? (
|
||||
<Paper background="dark" variant="outlined" square>
|
||||
) : aiRequest.mode === 'agent' &&
|
||||
isPausedAndHasFunctionCallsToProcess ? (
|
||||
<Paper background="dark" variant="outlined">
|
||||
<Column>
|
||||
<LineStackLayout
|
||||
justifyContent="space-between"
|
||||
@@ -702,9 +733,7 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
onProcessFunctionCalls(allFunctionCallsToProcess);
|
||||
}}
|
||||
>
|
||||
<Trans>
|
||||
Apply everything and continue autonomously
|
||||
</Trans>
|
||||
<Trans>Resume all</Trans>
|
||||
</Link>
|
||||
</Text>
|
||||
</LineStackLayout>
|
||||
@@ -714,8 +743,10 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
<CompactTextAreaFieldWithControls
|
||||
maxLength={6000}
|
||||
value={userRequestTextPerAiRequestId[aiRequestId] || ''}
|
||||
disabled={isSending}
|
||||
disabled={isSending || isForAnotherProject}
|
||||
errored={!!lastSendError}
|
||||
hasNeonCorner
|
||||
hasAnimatedNeonCorner={isSending}
|
||||
onChange={userRequestText =>
|
||||
setUserRequestTextPerRequestId(
|
||||
userRequestTextPerAiRequestId => ({
|
||||
@@ -726,7 +757,9 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
}
|
||||
placeholder={
|
||||
aiRequest.mode === 'agent'
|
||||
? t`Specify something more to the AI to build`
|
||||
? isForAnotherProject
|
||||
? t`You must re-open the project to continue this chat.`
|
||||
: t`Specify something more to the AI to build`
|
||||
: t`Ask a follow up question`
|
||||
}
|
||||
rows={2}
|
||||
@@ -746,6 +779,7 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
disabled={
|
||||
aiRequest.status === 'working' ||
|
||||
isSending ||
|
||||
isForAnotherProject ||
|
||||
!userRequestTextPerAiRequestId[aiRequestId]
|
||||
}
|
||||
icon={<Send />}
|
||||
@@ -767,13 +801,15 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
{errorText || priceText}
|
||||
{errorText ? null : quotaOrCreditsText}
|
||||
{isForAnotherProjectText || errorText || priceText}
|
||||
{errorText || isForAnotherProjectText
|
||||
? null
|
||||
: quotaOrCreditsText}
|
||||
</LineStackLayout>
|
||||
</Column>
|
||||
</ColumnStackLayout>
|
||||
</form>
|
||||
</Column>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@@ -112,15 +112,18 @@ const useProcessFunctionCalls = ({
|
||||
i18n,
|
||||
project,
|
||||
resourceManagementProps,
|
||||
editorCallbacks,
|
||||
selectedAiRequest,
|
||||
onSendEditorFunctionCallResults,
|
||||
getEditorFunctionCallResults,
|
||||
addEditorFunctionCallResults,
|
||||
onSceneEventsModifiedOutsideEditor,
|
||||
onExtensionInstalled,
|
||||
}: {|
|
||||
i18n: I18nType,
|
||||
project: gdProject | null,
|
||||
resourceManagementProps: ResourceManagementProps,
|
||||
editorCallbacks: EditorCallbacks,
|
||||
selectedAiRequest: ?AiRequest,
|
||||
onSendEditorFunctionCallResults: () => Promise<void>,
|
||||
getEditorFunctionCallResults: string => Array<EditorFunctionCallResult> | null,
|
||||
@@ -129,6 +132,7 @@ const useProcessFunctionCalls = ({
|
||||
Array<EditorFunctionCallResult>
|
||||
) => void,
|
||||
onSceneEventsModifiedOutsideEditor: (scene: gdLayout) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|}) => {
|
||||
const { ensureExtensionInstalled } = useEnsureExtensionInstalled({
|
||||
project,
|
||||
@@ -137,6 +141,7 @@ const useProcessFunctionCalls = ({
|
||||
const { searchAndInstallAsset } = useSearchAndInstallAsset({
|
||||
project,
|
||||
resourceManagementProps,
|
||||
onExtensionInstalled,
|
||||
});
|
||||
const { generateEvents } = useGenerateEvents({ project });
|
||||
|
||||
@@ -187,6 +192,7 @@ const useProcessFunctionCalls = ({
|
||||
|
||||
const editorFunctionCallResults = await processEditorFunctionCalls({
|
||||
project,
|
||||
editorCallbacks,
|
||||
functionCalls: functionCalls.map(functionCall => ({
|
||||
name: functionCall.name,
|
||||
arguments: functionCall.arguments,
|
||||
@@ -222,6 +228,7 @@ const useProcessFunctionCalls = ({
|
||||
generateEvents,
|
||||
onSceneEventsModifiedOutsideEditor,
|
||||
triggerSendEditorFunctionCallResults,
|
||||
editorCallbacks,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -454,9 +461,20 @@ type Props = {|
|
||||
i18n: I18nType,
|
||||
isQuickCustomization?: boolean
|
||||
) => Promise<void>,
|
||||
onOpenLayout: (sceneName: string) => void,
|
||||
onOpenEvents: (sceneName: string) => void,
|
||||
onOpenLayout: (
|
||||
sceneName: string,
|
||||
options: {|
|
||||
openEventsEditor: boolean,
|
||||
openSceneEditor: boolean,
|
||||
focusWhenOpened:
|
||||
| 'scene-or-events-otherwise'
|
||||
| 'scene'
|
||||
| 'events'
|
||||
| 'none',
|
||||
|}
|
||||
) => void,
|
||||
onSceneEventsModifiedOutsideEditor: (scene: gdLayout) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|};
|
||||
|
||||
export type AskAiEditorInterface = {|
|
||||
@@ -470,6 +488,7 @@ export type AskAiEditorInterface = {|
|
||||
) => void,
|
||||
onSceneObjectsDeleted: (scene: gdLayout) => void,
|
||||
onSceneEventsModifiedOutsideEditor: (scene: gdLayout) => void,
|
||||
startNewChat: () => void,
|
||||
|};
|
||||
|
||||
export type NewAiRequestOptions = {|
|
||||
@@ -493,17 +512,16 @@ export const AskAiEditor = React.memo<Props>(
|
||||
onCreateEmptyProject,
|
||||
onCreateProjectFromExample,
|
||||
onOpenLayout,
|
||||
onOpenEvents,
|
||||
onSceneEventsModifiedOutsideEditor,
|
||||
onExtensionInstalled,
|
||||
}: Props,
|
||||
ref
|
||||
) => {
|
||||
const editorCallbacks: EditorCallbacks = React.useMemo(
|
||||
() => ({
|
||||
onOpenLayout,
|
||||
onOpenEvents,
|
||||
}),
|
||||
[onOpenLayout, onOpenEvents]
|
||||
[onOpenLayout]
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -580,6 +598,7 @@ export const AskAiEditor = React.memo<Props>(
|
||||
onSceneObjectEdited: noop,
|
||||
onSceneObjectsDeleted: noop,
|
||||
onSceneEventsModifiedOutsideEditor: noop,
|
||||
startNewChat: onStartNewChat,
|
||||
}));
|
||||
|
||||
const aiRequestChatRef = React.useRef<AiRequestChatInterface | null>(
|
||||
@@ -770,6 +789,19 @@ export const AskAiEditor = React.memo<Props>(
|
||||
]
|
||||
);
|
||||
|
||||
const hasFunctionsCallsToProcess = React.useMemo(
|
||||
() =>
|
||||
selectedAiRequest
|
||||
? getFunctionCallsToProcess({
|
||||
aiRequest: selectedAiRequest,
|
||||
editorFunctionCallResults: getEditorFunctionCallResults(
|
||||
selectedAiRequest.id
|
||||
),
|
||||
}).length > 0
|
||||
: false,
|
||||
[selectedAiRequest, getEditorFunctionCallResults]
|
||||
);
|
||||
|
||||
// Send the results of the function call outputs, if any, and the user message (if any).
|
||||
const onSendMessage = React.useCallback(
|
||||
async ({ userMessage }: {| userMessage: string |}) => {
|
||||
@@ -792,6 +824,7 @@ export const AskAiEditor = React.memo<Props>(
|
||||
// If anything is not finished yet, stop there (we only send all
|
||||
// results at once, AI do not support partial results).
|
||||
if (hasUnfinishedResult) return;
|
||||
if (hasFunctionsCallsToProcess) return;
|
||||
|
||||
// If nothing to send, stop there.
|
||||
if (functionCallOutputs.length === 0 && !userMessage) return;
|
||||
@@ -894,6 +927,7 @@ export const AskAiEditor = React.memo<Props>(
|
||||
setLastSendError,
|
||||
onRefreshLimits,
|
||||
project,
|
||||
hasFunctionsCallsToProcess,
|
||||
]
|
||||
);
|
||||
const onSendEditorFunctionCallResults = React.useCallback(
|
||||
@@ -939,11 +973,13 @@ export const AskAiEditor = React.memo<Props>(
|
||||
project,
|
||||
resourceManagementProps,
|
||||
selectedAiRequest,
|
||||
editorCallbacks,
|
||||
onSendEditorFunctionCallResults,
|
||||
getEditorFunctionCallResults,
|
||||
addEditorFunctionCallResults,
|
||||
onSceneEventsModifiedOutsideEditor,
|
||||
i18n,
|
||||
onExtensionInstalled,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -990,6 +1026,7 @@ export const AskAiEditor = React.memo<Props>(
|
||||
}}
|
||||
i18n={i18n}
|
||||
editorCallbacks={editorCallbacks}
|
||||
onStartNewChat={onStartNewChat}
|
||||
/>
|
||||
</div>
|
||||
</Paper>
|
||||
@@ -1034,10 +1071,10 @@ export const renderAskAiEditorContainer = (
|
||||
onCreateEmptyProject={props.onCreateEmptyProject}
|
||||
onCreateProjectFromExample={props.onCreateProjectFromExample}
|
||||
onOpenLayout={props.onOpenLayout}
|
||||
onOpenEvents={props.onOpenEvents}
|
||||
onSceneEventsModifiedOutsideEditor={
|
||||
props.onSceneEventsModifiedOutsideEditor
|
||||
}
|
||||
onExtensionInstalled={props.onExtensionInstalled}
|
||||
/>
|
||||
)}
|
||||
</I18n>
|
||||
|
@@ -12,6 +12,7 @@ import { type ExampleShortHeader } from '../Utils/GDevelopServices/Example';
|
||||
import UrlStorageProvider from '../ProjectsStorage/UrlStorageProvider';
|
||||
import { generateProjectName } from '../ProjectCreation/NewProjectSetupDialog';
|
||||
import { type NewProjectSetup } from '../ProjectCreation/NewProjectSetupDialog';
|
||||
import { Spacer } from '../UI/Grid';
|
||||
|
||||
type RenderCreateAiProjectDialogProps = {
|
||||
onCreateEmptyProject: (newProjectSetup: NewProjectSetup) => Promise<void>,
|
||||
@@ -50,12 +51,10 @@ const CreateAiProjectDialog = ({
|
||||
/>,
|
||||
]}
|
||||
onRequestClose={onClose}
|
||||
onApply={() => {
|
||||
// TODO
|
||||
}}
|
||||
flexColumnBody
|
||||
>
|
||||
<ColumnStackLayout noMargin>
|
||||
<Spacer />
|
||||
<EmptyAndStartingPointProjects
|
||||
onSelectExampleShortHeader={exampleShortHeader => {
|
||||
onSelectExampleShortHeader(exampleShortHeader);
|
||||
@@ -64,6 +63,8 @@ const CreateAiProjectDialog = ({
|
||||
onSelectEmptyProject();
|
||||
}}
|
||||
/>
|
||||
{/* Use a spacer to avoid extra scrollbars when template tiles are hovered. */}
|
||||
<Spacer />
|
||||
</ColumnStackLayout>
|
||||
</Dialog>
|
||||
);
|
||||
|
@@ -33,7 +33,7 @@ export const useEnsureExtensionInstalled = ({
|
||||
const extensionShortHeader =
|
||||
translatedExtensionShortHeadersByName[extensionName];
|
||||
if (!extensionShortHeader) {
|
||||
throw new Error("Can't find extension with the required name.");
|
||||
throw new Error(`Can't find extension named ${extensionName}.`);
|
||||
}
|
||||
|
||||
await installExtension(
|
||||
|
@@ -16,9 +16,11 @@ import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
|
||||
export const useSearchAndInstallAsset = ({
|
||||
project,
|
||||
resourceManagementProps,
|
||||
onExtensionInstalled,
|
||||
}: {|
|
||||
project: gdProject | null,
|
||||
resourceManagementProps: ResourceManagementProps,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|}) => {
|
||||
const { profile, getAuthorizationHeader } = React.useContext(
|
||||
AuthenticatedUserContext
|
||||
@@ -26,6 +28,7 @@ export const useSearchAndInstallAsset = ({
|
||||
const installAsset = useInstallAsset({
|
||||
project,
|
||||
resourceManagementProps,
|
||||
onExtensionInstalled,
|
||||
});
|
||||
|
||||
return {
|
||||
|
@@ -49,6 +49,7 @@ type Props = {|
|
||||
addedAssetIds: Set<string>,
|
||||
onClose: () => void,
|
||||
onAssetsAdded: (createdObjects: gdObject[]) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
project: gdProject,
|
||||
objectsContainer: ?gdObjectsContainer,
|
||||
resourceManagementProps: ResourceManagementProps,
|
||||
@@ -61,6 +62,7 @@ const AssetPackInstallDialog = ({
|
||||
addedAssetIds,
|
||||
onClose,
|
||||
onAssetsAdded,
|
||||
onExtensionInstalled,
|
||||
project,
|
||||
objectsContainer,
|
||||
resourceManagementProps,
|
||||
@@ -176,6 +178,7 @@ const AssetPackInstallDialog = ({
|
||||
shouldUpdateExtension: extensionUpdateAction === 'update',
|
||||
eventsFunctionsExtensionsState,
|
||||
project,
|
||||
onExtensionInstalled,
|
||||
});
|
||||
|
||||
// Use a pool to avoid installing an unbounded amount of assets at the same time.
|
||||
@@ -253,6 +256,7 @@ const AssetPackInstallDialog = ({
|
||||
eventsFunctionsExtensionsState,
|
||||
resourceManagementProps,
|
||||
onAssetsAdded,
|
||||
onExtensionInstalled,
|
||||
installPrivateAsset,
|
||||
targetObjectsContainer,
|
||||
targetObjectFolderOrObjectWithContext,
|
||||
|
@@ -29,6 +29,7 @@ type Props = {|
|
||||
onClose: ({ swappingDone: boolean }) => void,
|
||||
// Use minimal UI to hide filters & the details page (useful for Quick Customization)
|
||||
minimalUI?: boolean,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|};
|
||||
|
||||
function AssetSwappingDialog({
|
||||
@@ -40,6 +41,7 @@ function AssetSwappingDialog({
|
||||
resourceManagementProps,
|
||||
onClose,
|
||||
minimalUI,
|
||||
onExtensionInstalled,
|
||||
}: Props) {
|
||||
const shopNavigationState = React.useContext(AssetStoreNavigatorContext);
|
||||
const { openedAssetShortHeader } = shopNavigationState.getCurrentPage();
|
||||
@@ -51,6 +53,7 @@ function AssetSwappingDialog({
|
||||
const installAsset = useInstallAsset({
|
||||
project,
|
||||
resourceManagementProps,
|
||||
onExtensionInstalled,
|
||||
});
|
||||
const { showAlert } = useAlertDialog();
|
||||
|
||||
|
@@ -27,13 +27,20 @@ import ThreeDotsMenu from '../../UI/CustomSvgIcons/ThreeDotsMenu';
|
||||
import useAlertDialog from '../../UI/Alert/useAlertDialog';
|
||||
import ExtensionInstallDialog from '../ExtensionStore/ExtensionInstallDialog';
|
||||
import { getIDEVersion } from '../../Version';
|
||||
import InAppTutorialContext from '../../InAppTutorial/InAppTutorialContext';
|
||||
|
||||
export const useExtensionUpdateAlertDialog = () => {
|
||||
const { showConfirmation } = useAlertDialog();
|
||||
const { currentlyRunningInAppTutorial } = React.useContext(
|
||||
InAppTutorialContext
|
||||
);
|
||||
return async (
|
||||
project: gdProject,
|
||||
behaviorShortHeader: BehaviorShortHeader
|
||||
): Promise<boolean> => {
|
||||
if (currentlyRunningInAppTutorial) {
|
||||
return false;
|
||||
}
|
||||
return await showConfirmation({
|
||||
title: t`Extension update`,
|
||||
message:
|
||||
|
@@ -25,7 +25,7 @@ type Props = {|
|
||||
project: gdProject,
|
||||
onClose: () => void,
|
||||
onInstallExtension: (extensionName: string) => void,
|
||||
onExtensionInstalled: (extensionName: string) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
onCreateNew?: () => void,
|
||||
|};
|
||||
|
||||
@@ -84,7 +84,7 @@ const ExtensionsSearchDialog = ({
|
||||
|
||||
if (installedOrImportedExtensionName) {
|
||||
setExtensionWasInstalled(true);
|
||||
onExtensionInstalled(installedOrImportedExtensionName);
|
||||
onExtensionInstalled([installedOrImportedExtensionName]);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -381,6 +381,7 @@ export type InstallRequiredExtensionsArgs = {|
|
||||
shouldUpdateExtension: boolean,
|
||||
eventsFunctionsExtensionsState: EventsFunctionsExtensionsState,
|
||||
project: gdProject,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|};
|
||||
|
||||
export const installRequiredExtensions = async ({
|
||||
@@ -388,6 +389,7 @@ export const installRequiredExtensions = async ({
|
||||
shouldUpdateExtension,
|
||||
eventsFunctionsExtensionsState,
|
||||
project,
|
||||
onExtensionInstalled,
|
||||
}: InstallRequiredExtensionsArgs): Promise<void> => {
|
||||
const {
|
||||
requiredExtensionShortHeaders,
|
||||
@@ -417,6 +419,9 @@ export const installRequiredExtensions = async ({
|
||||
project,
|
||||
serializedExtensions
|
||||
);
|
||||
onExtensionInstalled(
|
||||
neededExtensions.map(extensionShortHeader => extensionShortHeader.name)
|
||||
);
|
||||
|
||||
const stillMissingExtensions = filterMissingExtensions(
|
||||
gd,
|
||||
|
@@ -1034,6 +1034,7 @@ describe('InstallAsset', () => {
|
||||
shouldUpdateExtension: true,
|
||||
eventsFunctionsExtensionsState: mockEventsFunctionsExtensionsState,
|
||||
project,
|
||||
onExtensionInstalled: () => {},
|
||||
})
|
||||
).rejects.toMatchObject({
|
||||
// It's just because the mock doesn't reloadProjectEventsFunctionsExtensions.
|
||||
@@ -1069,6 +1070,7 @@ describe('InstallAsset', () => {
|
||||
shouldUpdateExtension: true,
|
||||
eventsFunctionsExtensionsState: mockEventsFunctionsExtensionsState,
|
||||
project,
|
||||
onExtensionInstalled: () => {},
|
||||
})
|
||||
).rejects.toMatchObject({
|
||||
message: 'These extensions could not be installed: Flash',
|
||||
@@ -1109,6 +1111,7 @@ describe('InstallAsset', () => {
|
||||
shouldUpdateExtension: true,
|
||||
eventsFunctionsExtensionsState: mockEventsFunctionsExtensionsState,
|
||||
project,
|
||||
onExtensionInstalled: () => {},
|
||||
});
|
||||
|
||||
// No extensions fetched because the extension is already installed.
|
||||
|
@@ -49,11 +49,15 @@ import ErrorBoundary from '../UI/ErrorBoundary';
|
||||
import type { ObjectFolderOrObjectWithContext } from '../ObjectsList/EnumerateObjectFolderOrObject';
|
||||
import LoaderModal from '../UI/LoaderModal';
|
||||
import { AssetStoreNavigatorContext } from './AssetStoreNavigator';
|
||||
import InAppTutorialContext from '../InAppTutorial/InAppTutorialContext';
|
||||
|
||||
const isDev = Window.isDev();
|
||||
|
||||
export const useExtensionUpdateAlertDialog = () => {
|
||||
const { showConfirmation, showDeleteConfirmation } = useAlertDialog();
|
||||
const { currentlyRunningInAppTutorial } = React.useContext(
|
||||
InAppTutorialContext
|
||||
);
|
||||
return async ({
|
||||
project,
|
||||
outOfDateExtensionShortHeaders,
|
||||
@@ -61,6 +65,9 @@ export const useExtensionUpdateAlertDialog = () => {
|
||||
project: gdProject,
|
||||
outOfDateExtensionShortHeaders: Array<ExtensionShortHeader>,
|
||||
|}): Promise<string> => {
|
||||
if (currentlyRunningInAppTutorial) {
|
||||
return 'skip';
|
||||
}
|
||||
const breakingChanges = new Map<
|
||||
ExtensionShortHeader,
|
||||
Array<ExtensionChange>
|
||||
@@ -174,10 +181,12 @@ export const useInstallAsset = ({
|
||||
project,
|
||||
targetObjectFolderOrObjectWithContext,
|
||||
resourceManagementProps,
|
||||
onExtensionInstalled,
|
||||
}: {|
|
||||
project: gdProject | null,
|
||||
targetObjectFolderOrObjectWithContext?: ?ObjectFolderOrObjectWithContext,
|
||||
resourceManagementProps: ResourceManagementProps,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|}) => {
|
||||
const shopNavigationState = React.useContext(AssetStoreNavigatorContext);
|
||||
const { openedAssetPack } = shopNavigationState.getCurrentPage();
|
||||
@@ -242,6 +251,7 @@ export const useInstallAsset = ({
|
||||
shouldUpdateExtension: extensionUpdateAction === 'update',
|
||||
eventsFunctionsExtensionsState,
|
||||
project,
|
||||
onExtensionInstalled,
|
||||
});
|
||||
const isPrivate = isPrivateAsset(assetShortHeader);
|
||||
const installOutput = isPrivate
|
||||
@@ -309,6 +319,7 @@ type Props = {|
|
||||
onCreateNewObject: (type: string) => void,
|
||||
onObjectsAddedFromAssets: (Array<gdObject>) => void,
|
||||
targetObjectFolderOrObjectWithContext?: ?ObjectFolderOrObjectWithContext,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|};
|
||||
|
||||
function NewObjectDialog({
|
||||
@@ -321,6 +332,7 @@ function NewObjectDialog({
|
||||
onCreateNewObject,
|
||||
onObjectsAddedFromAssets,
|
||||
targetObjectFolderOrObjectWithContext,
|
||||
onExtensionInstalled,
|
||||
}: Props) {
|
||||
const { isMobile } = useResponsiveWindowSize();
|
||||
const {
|
||||
@@ -378,6 +390,7 @@ function NewObjectDialog({
|
||||
project,
|
||||
resourceManagementProps,
|
||||
targetObjectFolderOrObjectWithContext,
|
||||
onExtensionInstalled,
|
||||
});
|
||||
|
||||
const onInstallAsset = React.useCallback(
|
||||
@@ -436,6 +449,7 @@ function NewObjectDialog({
|
||||
shouldUpdateExtension: extensionUpdateAction === 'update',
|
||||
eventsFunctionsExtensionsState,
|
||||
project,
|
||||
onExtensionInstalled,
|
||||
});
|
||||
|
||||
onCreateNewObject(enumeratedObjectMetadata.name);
|
||||
@@ -457,6 +471,7 @@ function NewObjectDialog({
|
||||
showExtensionUpdateConfirmation,
|
||||
eventsFunctionsExtensionsState,
|
||||
showAlert,
|
||||
onExtensionInstalled,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -671,6 +686,7 @@ function NewObjectDialog({
|
||||
targetObjectFolderOrObjectWithContext={
|
||||
targetObjectFolderOrObjectWithContext
|
||||
}
|
||||
onExtensionInstalled={onExtensionInstalled}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
@@ -656,6 +656,7 @@ const PrivateAssetPackInformationPage = ({
|
||||
analyticsMetadata: {
|
||||
reason: 'Claim asset pack',
|
||||
recommendedPlanId: 'gdevelop_gold',
|
||||
placementId: 'claim-asset-pack',
|
||||
},
|
||||
filter: 'individual',
|
||||
})
|
||||
|
@@ -32,7 +32,7 @@ type Props = {|
|
||||
open: boolean,
|
||||
onClose: () => void,
|
||||
onChoose: (type: string, defaultName: string) => void,
|
||||
onExtensionInstalled: (extensionName: string) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|};
|
||||
|
||||
export default function NewBehaviorDialog({
|
||||
@@ -190,7 +190,7 @@ export default function NewBehaviorDialog({
|
||||
behaviorShortHeader
|
||||
);
|
||||
if (wasExtensionInstalled) {
|
||||
onExtensionInstalled(behaviorShortHeader.extensionName);
|
||||
onExtensionInstalled([behaviorShortHeader.extensionName]);
|
||||
}
|
||||
return wasExtensionInstalled;
|
||||
} finally {
|
||||
|
@@ -325,7 +325,7 @@ export const useManageObjectBehaviors = ({
|
||||
onSizeUpdated?: ?() => void,
|
||||
onBehaviorsUpdated?: ?() => void,
|
||||
onUpdateBehaviorsSharedData: () => void,
|
||||
onExtensionInstalled: (extensionName: string) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
}): UseManageBehaviorsState => {
|
||||
const [
|
||||
justAddedBehaviorName,
|
||||
@@ -623,7 +623,7 @@ type Props = {|
|
||||
extensionName: string,
|
||||
behaviorName: string
|
||||
) => Promise<void>,
|
||||
onExtensionInstalled: (extensionName: string) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
isListLocked: boolean,
|
||||
|};
|
||||
|
||||
|
@@ -174,6 +174,7 @@ const LockedCourseChapterPreview = React.forwardRef<Props, HTMLDivElement>(
|
||||
analyticsMetadata: {
|
||||
reason: 'Unlock course chapter',
|
||||
recommendedPlanId: 'gdevelop_silver',
|
||||
placementId: 'unlock-course-chapter',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ import { type EventsGenerationResult } from '.';
|
||||
import {
|
||||
editorFunctions,
|
||||
type EditorFunction,
|
||||
type EditorCallbacks,
|
||||
type EditorFunctionCall,
|
||||
type EditorFunctionGenericOutput,
|
||||
type EventsGenerationOptions,
|
||||
@@ -29,6 +30,7 @@ export type EditorFunctionCallResult =
|
||||
export type ProcessEditorFunctionCallsOptions = {|
|
||||
project: gdProject,
|
||||
functionCalls: Array<EditorFunctionCall>,
|
||||
editorCallbacks: EditorCallbacks,
|
||||
ignore: boolean,
|
||||
generateEvents: (
|
||||
options: EventsGenerationOptions
|
||||
@@ -45,6 +47,7 @@ export type ProcessEditorFunctionCallsOptions = {|
|
||||
export const processEditorFunctionCalls = async ({
|
||||
functionCalls,
|
||||
project,
|
||||
editorCallbacks,
|
||||
generateEvents,
|
||||
onSceneEventsModifiedOutsideEditor,
|
||||
ignore,
|
||||
@@ -139,6 +142,16 @@ export const processEditorFunctionCalls = async ({
|
||||
success,
|
||||
output,
|
||||
});
|
||||
|
||||
if (success && args) {
|
||||
if (typeof args.scene_name === 'string') {
|
||||
editorCallbacks.onOpenLayout(args.scene_name, {
|
||||
openEventsEditor: true,
|
||||
openSceneEditor: true,
|
||||
focusWhenOpened: 'none',
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
results.push({
|
||||
status: 'finished',
|
||||
|
@@ -23,6 +23,7 @@ type SimplifiedObject = {|
|
||||
objectType: string,
|
||||
behaviors?: Array<SimplifiedBehavior>,
|
||||
objectVariables?: Array<SimplifiedVariable>,
|
||||
animationNames?: string,
|
||||
|};
|
||||
|
||||
type SimplifiedObjectGroup = {|
|
||||
@@ -165,6 +166,21 @@ export const makeSimplifiedProjectBuilder = (gd: libGDevelop) => {
|
||||
simplifiedObject.objectVariables = objectVariables;
|
||||
}
|
||||
|
||||
const objectConfiguration = object.getConfiguration();
|
||||
const animationNames = mapFor(
|
||||
0,
|
||||
objectConfiguration.getAnimationsCount(),
|
||||
i => {
|
||||
return (
|
||||
objectConfiguration.getAnimationName(i) ||
|
||||
`(animation without name, animation index is: ${i})`
|
||||
);
|
||||
}
|
||||
);
|
||||
if (animationNames.length > 0) {
|
||||
simplifiedObject.animationNames = animationNames.join(', ');
|
||||
}
|
||||
|
||||
return simplifiedObject;
|
||||
};
|
||||
|
||||
|
@@ -400,6 +400,7 @@ describe('SimplifiedProject', () => {
|
||||
"objectType": "Sprite",
|
||||
},
|
||||
Object {
|
||||
"animationNames": "My animation, My other animation, (animation without name, animation index is: 2)",
|
||||
"behaviors": Array [
|
||||
Object {
|
||||
"behaviorName": "Animation",
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -89,7 +89,7 @@ type Props = {|
|
||||
eventsFunctionsExtension: gdEventsFunctionsExtension,
|
||||
name: string
|
||||
) => void,
|
||||
onExtensionInstalled: (extensionName: string) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|};
|
||||
|
||||
type State = {|
|
||||
|
@@ -71,7 +71,7 @@ type Props = {|
|
||||
anchorEl?: any, // Unused
|
||||
canPasteInstructions: boolean, // Unused
|
||||
onPasteInstructions: () => void, // Unused
|
||||
onExtensionInstalled: (extensionName: string) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|};
|
||||
|
||||
const getInitialStepName = (isNewInstruction: boolean): StepName => {
|
||||
|
@@ -56,7 +56,7 @@ type Props = {|
|
||||
i18n: I18nType,
|
||||
canPasteInstructions: boolean, // Unused
|
||||
onPasteInstructions: () => void, // Unused
|
||||
onExtensionInstalled: (extensionName: string) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|};
|
||||
|
||||
/**
|
||||
|
@@ -154,7 +154,7 @@ type Props = {|
|
||||
unsavedChanges?: ?UnsavedChanges,
|
||||
isActive: boolean,
|
||||
hotReloadPreviewButtonProps: HotReloadPreviewButtonProps,
|
||||
onExtensionInstalled: (extensionName: string) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|};
|
||||
|
||||
type ComponentProps = {|
|
||||
|
@@ -371,6 +371,7 @@ export default class LocalPreviewLauncher extends React.Component<
|
||||
}
|
||||
id="Preview over wifi"
|
||||
title={<Trans>Preview over wifi</Trans>}
|
||||
placementId="preview-wifi"
|
||||
mode="try"
|
||||
isNotShownDuringInAppTutorial
|
||||
/>
|
||||
@@ -382,6 +383,7 @@ export default class LocalPreviewLauncher extends React.Component<
|
||||
title={
|
||||
<Trans>Live preview (apply changes to the running preview)</Trans>
|
||||
}
|
||||
placementId="hot-reloading"
|
||||
mode="try"
|
||||
isNotShownDuringInAppTutorial
|
||||
/>
|
||||
|
@@ -326,6 +326,7 @@ const InviteHome = ({ cloudProjectId }: Props) => {
|
||||
<GetSubscriptionCard
|
||||
subscriptionDialogOpeningReason="Add collaborators on project"
|
||||
recommendedPlanIdIfNoSubscription="gdevelop_startup"
|
||||
placementId="invite-collaborators"
|
||||
>
|
||||
<Text>
|
||||
<Trans>
|
||||
|
@@ -350,6 +350,7 @@ function LeaderboardAppearanceDialog({
|
||||
<GetSubscriptionCard
|
||||
subscriptionDialogOpeningReason="Leaderboard customization"
|
||||
recommendedPlanIdIfNoSubscription="gdevelop_silver"
|
||||
placementId="leaderboards-customization"
|
||||
>
|
||||
<Line>
|
||||
<Column noMargin>
|
||||
@@ -406,6 +407,7 @@ function LeaderboardAppearanceDialog({
|
||||
<GetSubscriptionCard
|
||||
subscriptionDialogOpeningReason="Leaderboard customization"
|
||||
recommendedPlanIdIfNoSubscription="gdevelop_startup"
|
||||
placementId="leaderboards-customization"
|
||||
>
|
||||
<Line>
|
||||
<Column noMargin>
|
||||
|
@@ -256,6 +256,7 @@ function LeaderboardOptionsDialog({
|
||||
<GetSubscriptionCard
|
||||
subscriptionDialogOpeningReason="Leaderboard customization"
|
||||
recommendedPlanIdIfNoSubscription="gdevelop_startup"
|
||||
placementId="leaderboards-customization"
|
||||
>
|
||||
<Line>
|
||||
<Column noMargin>
|
||||
|
@@ -47,6 +47,7 @@ const MaxLeaderboardCountAlertMessage = () => {
|
||||
<Column expand>
|
||||
<GetSubscriptionCard
|
||||
subscriptionDialogOpeningReason="Leaderboard count per game limit reached"
|
||||
placementId="leaderboards"
|
||||
label={
|
||||
!hasValidSubscription ? (
|
||||
<Trans>Upgrade to GDevelop Premium</Trans>
|
||||
|
@@ -98,6 +98,7 @@ const ServicesWidget = ({
|
||||
analyticsMetadata: {
|
||||
reason: 'Leaderboard count per game limit reached',
|
||||
recommendedPlanId: 'gdevelop_silver',
|
||||
placementId: 'leaderboards',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@@ -55,7 +55,18 @@ export type RenderEditorContainerProps = {|
|
||||
|
||||
// Opening other editors:
|
||||
onOpenExternalEvents: string => void,
|
||||
onOpenLayout: string => void,
|
||||
onOpenLayout: (
|
||||
sceneName: string,
|
||||
options?: {|
|
||||
openEventsEditor: boolean,
|
||||
openSceneEditor: boolean,
|
||||
focusWhenOpened:
|
||||
| 'scene-or-events-otherwise'
|
||||
| 'scene'
|
||||
| 'events'
|
||||
| 'none',
|
||||
|}
|
||||
) => void,
|
||||
onOpenEvents: (sceneName: string) => void,
|
||||
openInstructionOrExpression: (
|
||||
extension: gdPlatformExtension,
|
||||
@@ -171,7 +182,7 @@ export type RenderEditorContainerProps = {|
|
||||
eventsBasedObjectName: string,
|
||||
variantName: string
|
||||
) => void,
|
||||
onExtensionInstalled: (extensionName: string) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
onDeleteEventsBasedObjectVariant: (
|
||||
eventsFunctionsExtension: gdEventsFunctionsExtension,
|
||||
eventBasedObject: gdEventsBasedObject,
|
||||
|
@@ -105,6 +105,7 @@ export class DebuggerEditorContainer extends React.Component<
|
||||
}
|
||||
id="Debugger"
|
||||
title={<Trans>Debugger</Trans>}
|
||||
placementId="debugger"
|
||||
mode="try"
|
||||
/>
|
||||
</React.Fragment>
|
||||
|
@@ -55,6 +55,7 @@ export const MaxProjectCountAlertMessage = ({ margin }: Props) => {
|
||||
}
|
||||
hideButton={!canMaximumCountBeIncreased}
|
||||
recommendedPlanIdIfNoSubscription="gdevelop_silver"
|
||||
placementId="max-projects-reached"
|
||||
>
|
||||
<Line>
|
||||
<Column noMargin expand>
|
||||
|
@@ -241,6 +241,7 @@ const EducationMarketingSection = ({
|
||||
analyticsMetadata: {
|
||||
reason: 'Callout in Classroom tab',
|
||||
recommendedPlanId: 'gdevelop_education',
|
||||
placementId: 'education',
|
||||
},
|
||||
});
|
||||
},
|
||||
|
@@ -21,6 +21,7 @@ type Props = {|
|
||||
privateGameTemplateListingData: PrivateGameTemplateListingData
|
||||
) => void,
|
||||
onOpenProfile: () => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|};
|
||||
|
||||
const StoreSection = ({
|
||||
@@ -28,6 +29,7 @@ const StoreSection = ({
|
||||
resourceManagementProps,
|
||||
onOpenPrivateGameTemplateListingData,
|
||||
onOpenProfile,
|
||||
onExtensionInstalled,
|
||||
}: Props) => {
|
||||
const [
|
||||
isAssetPackDialogInstallOpen,
|
||||
@@ -129,6 +131,7 @@ const StoreSection = ({
|
||||
project={project}
|
||||
objectsContainer={null}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
onExtensionInstalled={onExtensionInstalled}
|
||||
/>
|
||||
)}
|
||||
</SectionContainer>
|
||||
|
@@ -638,6 +638,7 @@ const ManageEducationAccountDialog = ({ onClose }: Props) => {
|
||||
analyticsMetadata: {
|
||||
reason: 'Manage subscription as teacher',
|
||||
recommendedPlanId: 'gdevelop_education',
|
||||
placementId: 'education',
|
||||
},
|
||||
filter: 'education',
|
||||
})
|
||||
|
@@ -152,6 +152,9 @@ type Props = {|
|
||||
templateId?: string
|
||||
) => Promise<void>,
|
||||
|
||||
// Asset store
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|
||||
// Project save
|
||||
onSave: () => Promise<void>,
|
||||
canSave: boolean,
|
||||
@@ -205,6 +208,7 @@ export const HomePage = React.memo<Props>(
|
||||
onOpenTemplateFromCourseChapter,
|
||||
gamesList,
|
||||
gamesPlatformFrameTools,
|
||||
onExtensionInstalled,
|
||||
}: Props,
|
||||
ref
|
||||
) => {
|
||||
@@ -648,6 +652,7 @@ export const HomePage = React.memo<Props>(
|
||||
onOpenPrivateGameTemplateListingData
|
||||
}
|
||||
onOpenProfile={onOpenProfile}
|
||||
onExtensionInstalled={onExtensionInstalled}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'team-view' &&
|
||||
@@ -733,5 +738,6 @@ export const renderHomePageContainer = (
|
||||
resourceManagementProps={props.resourceManagementProps}
|
||||
gamesList={props.gamesList}
|
||||
gamesPlatformFrameTools={props.gamesPlatformFrameTools}
|
||||
onExtensionInstalled={props.onExtensionInstalled}
|
||||
/>
|
||||
);
|
||||
|
@@ -485,3 +485,12 @@ export const hasEditorTabOpenedWithKey = (
|
||||
) => {
|
||||
return !!editorTabsState.editors.find(editor => editor.key === key);
|
||||
};
|
||||
|
||||
export const getOpenedAskAiEditor = (
|
||||
state: EditorTabsState
|
||||
): AskAiEditorInterface | null => {
|
||||
const editor = state.editors.find(editor => editor.key === 'ask-ai');
|
||||
|
||||
// $FlowFixMe - the key ensures that the editor is an AskAiEditorInterface.
|
||||
return (editor && editor.editorRef) || null;
|
||||
};
|
||||
|
@@ -263,7 +263,7 @@ export const buildMainMenuDeclarativeTemplate = ({
|
||||
? []
|
||||
: [
|
||||
{
|
||||
label: i18n._(t`Ask AI (GDevelop chatbot)`),
|
||||
label: i18n._(t`Ask AI (AI agent and chatbot)`),
|
||||
onClickSendEvent: 'main-menu-open-ask-ai',
|
||||
},
|
||||
]),
|
||||
|
@@ -49,6 +49,7 @@ import {
|
||||
moveTabToTheRightOfHoveredTab,
|
||||
getCustomObjectEditor,
|
||||
hasEditorTabOpenedWithKey,
|
||||
getOpenedAskAiEditor,
|
||||
} from './EditorTabs/EditorTabsHandler';
|
||||
import { renderDebuggerEditorContainer } from './EditorContainers/DebuggerEditorContainer';
|
||||
import { renderEventsEditorContainer } from './EditorContainers/EventsEditorContainer';
|
||||
@@ -1226,13 +1227,21 @@ const MainFrame = (props: Props) => {
|
||||
|
||||
const openAskAi = React.useCallback(
|
||||
() => {
|
||||
setState(state => ({
|
||||
...state,
|
||||
editorTabs: openEditorTab(
|
||||
state.editorTabs,
|
||||
getEditorOpeningOptions({ kind: 'ask-ai', name: '' })
|
||||
),
|
||||
}));
|
||||
setState(state => {
|
||||
const askAiEditor = getOpenedAskAiEditor(state.editorTabs);
|
||||
if (askAiEditor) {
|
||||
askAiEditor.startNewChat();
|
||||
}
|
||||
|
||||
// Open or focus the AI editor.
|
||||
return {
|
||||
...state,
|
||||
editorTabs: openEditorTab(
|
||||
state.editorTabs,
|
||||
getEditorOpeningOptions({ kind: 'ask-ai', name: '' })
|
||||
),
|
||||
};
|
||||
});
|
||||
},
|
||||
[setState, getEditorOpeningOptions]
|
||||
);
|
||||
@@ -1284,28 +1293,6 @@ const MainFrame = (props: Props) => {
|
||||
toolbar.current.setEditorToolbar(editorToolbar);
|
||||
};
|
||||
|
||||
const onInstallExtension = (extensionName: string) => {
|
||||
const { currentProject } = state;
|
||||
if (!currentProject) return;
|
||||
|
||||
// Close the extension tab before updating/reinstalling the extension.
|
||||
const eventsFunctionsExtensionName = extensionName;
|
||||
|
||||
if (
|
||||
currentProject.hasEventsFunctionsExtensionNamed(
|
||||
eventsFunctionsExtensionName
|
||||
)
|
||||
) {
|
||||
setState(state => ({
|
||||
...state,
|
||||
editorTabs: closeEventsFunctionsExtensionTabs(
|
||||
state.editorTabs,
|
||||
eventsFunctionsExtensionName
|
||||
),
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const deleteLayout = (layout: gdLayout) => {
|
||||
const { currentProject } = state;
|
||||
const { i18n } = props;
|
||||
@@ -1412,31 +1399,60 @@ const MainFrame = (props: Props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const onExtensionInstalled = (extensionName: string) => {
|
||||
const onInstallExtension = (extensionName: string) => {
|
||||
const { currentProject } = state;
|
||||
if (!currentProject) return;
|
||||
|
||||
// Close the extension tab before updating/reinstalling the extension.
|
||||
// This is especially important when the extension tab in selected.
|
||||
const eventsFunctionsExtensionName = extensionName;
|
||||
|
||||
if (
|
||||
currentProject.hasEventsFunctionsExtensionNamed(
|
||||
eventsFunctionsExtensionName
|
||||
)
|
||||
) {
|
||||
setState(state => ({
|
||||
...state,
|
||||
editorTabs: closeEventsFunctionsExtensionTabs(
|
||||
state.editorTabs,
|
||||
eventsFunctionsExtensionName
|
||||
),
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const onExtensionInstalled = (extensionNames: Array<string>) => {
|
||||
const { currentProject } = state;
|
||||
if (!currentProject) {
|
||||
return;
|
||||
}
|
||||
const eventsBasedObjects = currentProject
|
||||
.getEventsFunctionsExtension(extensionName)
|
||||
.getEventsBasedObjects();
|
||||
for (let index = 0; index < eventsBasedObjects.getCount(); index++) {
|
||||
const eventsBasedObject = eventsBasedObjects.getAt(index);
|
||||
gd.EventsBasedObjectVariantHelper.complyVariantsToEventsBasedObject(
|
||||
currentProject,
|
||||
eventsBasedObject
|
||||
);
|
||||
for (const extensionName of extensionNames) {
|
||||
const eventsBasedObjects = currentProject
|
||||
.getEventsFunctionsExtension(extensionName)
|
||||
.getEventsBasedObjects();
|
||||
for (let index = 0; index < eventsBasedObjects.getCount(); index++) {
|
||||
const eventsBasedObject = eventsBasedObjects.getAt(index);
|
||||
gd.EventsBasedObjectVariantHelper.complyVariantsToEventsBasedObject(
|
||||
currentProject,
|
||||
eventsBasedObject
|
||||
);
|
||||
}
|
||||
|
||||
// Close extension tab because `onInstallExtension` is not necessarily
|
||||
// called when the extension tab is not selected.
|
||||
|
||||
// TODO Open the closed tabs back
|
||||
// It would be safer to close the tabs before the extension is installed
|
||||
// but it would make opening them back more complicated.
|
||||
setState(state => ({
|
||||
...state,
|
||||
editorTabs: closeEventsFunctionsExtensionTabs(
|
||||
state.editorTabs,
|
||||
extensionName
|
||||
),
|
||||
}));
|
||||
}
|
||||
// TODO Open the closed tabs back
|
||||
// It would be safer to close the tabs before the extension is installed
|
||||
// but it would make opening them back more complicated.
|
||||
setState(state => ({
|
||||
...state,
|
||||
editorTabs: closeEventsFunctionsExtensionTabs(
|
||||
state.editorTabs,
|
||||
extensionName
|
||||
),
|
||||
}));
|
||||
};
|
||||
|
||||
const renameLayout = (oldName: string, newName: string) => {
|
||||
@@ -1916,16 +1932,32 @@ const MainFrame = (props: Props) => {
|
||||
{
|
||||
openEventsEditor,
|
||||
openSceneEditor,
|
||||
}: {| openEventsEditor: boolean, openSceneEditor: boolean |}
|
||||
focusWhenOpened,
|
||||
}: {|
|
||||
openEventsEditor: boolean,
|
||||
openSceneEditor: boolean,
|
||||
focusWhenOpened:
|
||||
| 'scene-or-events-otherwise'
|
||||
| 'scene'
|
||||
| 'events'
|
||||
| 'none',
|
||||
|}
|
||||
): EditorTabsState => {
|
||||
const sceneEditorOptions = getEditorOpeningOptions({
|
||||
kind: 'layout',
|
||||
name,
|
||||
dontFocusTab: !(
|
||||
focusWhenOpened === 'scene' ||
|
||||
focusWhenOpened === 'scene-or-events-otherwise'
|
||||
),
|
||||
});
|
||||
const eventsEditorOptions = getEditorOpeningOptions({
|
||||
kind: 'layout events',
|
||||
name,
|
||||
dontFocusTab: openSceneEditor,
|
||||
dontFocusTab: !(
|
||||
focusWhenOpened === 'events' ||
|
||||
(focusWhenOpened === 'scene-or-events-otherwise' && !openSceneEditor)
|
||||
),
|
||||
});
|
||||
|
||||
const tabsWithSceneEditor = openSceneEditor
|
||||
@@ -1941,9 +1973,18 @@ const MainFrame = (props: Props) => {
|
||||
const openLayout = React.useCallback(
|
||||
(
|
||||
name: string,
|
||||
options?: {| openEventsEditor: boolean, openSceneEditor: boolean |} = {
|
||||
options?: {|
|
||||
openEventsEditor: boolean,
|
||||
openSceneEditor: boolean,
|
||||
focusWhenOpened:
|
||||
| 'scene-or-events-otherwise'
|
||||
| 'scene'
|
||||
| 'events'
|
||||
| 'none',
|
||||
|} = {
|
||||
openEventsEditor: true,
|
||||
openSceneEditor: true,
|
||||
focusWhenOpened: 'scene',
|
||||
},
|
||||
editorTabs?: EditorTabsState
|
||||
): void => {
|
||||
@@ -1955,6 +1996,7 @@ const MainFrame = (props: Props) => {
|
||||
{
|
||||
openEventsEditor: options.openEventsEditor,
|
||||
openSceneEditor: options.openSceneEditor,
|
||||
focusWhenOpened: options.focusWhenOpened,
|
||||
}
|
||||
),
|
||||
}));
|
||||
@@ -2450,6 +2492,7 @@ const MainFrame = (props: Props) => {
|
||||
{
|
||||
openSceneEditor: true,
|
||||
openEventsEditor: true,
|
||||
focusWhenOpened: 'scene',
|
||||
},
|
||||
editorTabs
|
||||
);
|
||||
@@ -2483,6 +2526,7 @@ const MainFrame = (props: Props) => {
|
||||
{
|
||||
openSceneEditor: true,
|
||||
openEventsEditor: true,
|
||||
focusWhenOpened: 'scene',
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -2513,6 +2557,7 @@ const MainFrame = (props: Props) => {
|
||||
openLayout(firstLayout, {
|
||||
openSceneEditor: true,
|
||||
openEventsEditor: true,
|
||||
focusWhenOpened: 'scene',
|
||||
});
|
||||
|
||||
setIsLoadingProject(false);
|
||||
@@ -3973,14 +4018,10 @@ const MainFrame = (props: Props) => {
|
||||
openLayout(sceneName, {
|
||||
openEventsEditor: true,
|
||||
openSceneEditor: false,
|
||||
focusWhenOpened: 'events',
|
||||
});
|
||||
},
|
||||
onOpenLayout: (sceneName: string) => {
|
||||
openLayout(sceneName, {
|
||||
openEventsEditor: false,
|
||||
openSceneEditor: true,
|
||||
});
|
||||
},
|
||||
onOpenLayout: openLayout,
|
||||
onOpenTemplateFromTutorial: openTemplateFromTutorial,
|
||||
onOpenTemplateFromCourseChapter: openTemplateFromCourseChapter,
|
||||
previewDebuggerServer,
|
||||
@@ -4395,6 +4436,7 @@ const MainFrame = (props: Props) => {
|
||||
currentProject.getProjectUuid()
|
||||
)}
|
||||
onScreenshotsClaimed={onGameScreenshotsClaimed}
|
||||
onExtensionInstalled={onExtensionInstalled}
|
||||
/>
|
||||
)}
|
||||
<CustomDragLayer />
|
||||
|
@@ -215,7 +215,7 @@ type Props = {|
|
||||
|
||||
objects: Array<gdObject>,
|
||||
onEditObject: (object: gdObject, initialTab: ?ObjectEditorTab) => void,
|
||||
onExtensionInstalled: (extensionName: string) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
isVariableListLocked: boolean,
|
||||
isBehaviorListLocked: boolean,
|
||||
|};
|
||||
|
@@ -401,10 +401,11 @@ const CustomObjectPropertiesEditor = (props: Props) => {
|
||||
</ColumnStackLayout>
|
||||
</>
|
||||
)}
|
||||
{!getVariantName(
|
||||
{(!getVariantName(
|
||||
eventBasedObject,
|
||||
customObjectConfiguration
|
||||
) &&
|
||||
) ||
|
||||
customObjectConfiguration.isForcedToOverrideEventsBasedObjectChildrenConfiguration()) &&
|
||||
(eventBasedObject &&
|
||||
(!customObjectConfiguration.isForcedToOverrideEventsBasedObjectChildrenConfiguration() &&
|
||||
!customObjectConfiguration.isMarkedAsOverridingEventsBasedObjectChildrenConfiguration() ? (
|
||||
|
@@ -60,7 +60,7 @@ type Props = {|
|
||||
// Preview:
|
||||
hotReloadPreviewButtonProps: HotReloadPreviewButtonProps,
|
||||
openBehaviorEvents: (extensionName: string, behaviorName: string) => void,
|
||||
onExtensionInstalled: (extensionName: string) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
onOpenEventBasedObjectEditor: (
|
||||
extensionName: string,
|
||||
eventsBasedObjectName: string
|
||||
|
@@ -479,6 +479,7 @@ type Props = {|
|
||||
onObjectPasted?: gdObject => void,
|
||||
getValidatedObjectOrGroupName: (newName: string, global: boolean) => string,
|
||||
onAddObjectInstance: (objectName: string) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|
||||
getThumbnail: (
|
||||
project: gdProject,
|
||||
@@ -518,6 +519,7 @@ const ObjectsList = React.forwardRef<Props, ObjectsListInterface>(
|
||||
onObjectPasted,
|
||||
getValidatedObjectOrGroupName,
|
||||
onAddObjectInstance,
|
||||
onExtensionInstalled,
|
||||
|
||||
getThumbnail,
|
||||
unsavedChanges,
|
||||
@@ -1591,6 +1593,7 @@ const ObjectsList = React.forwardRef<Props, ObjectsListInterface>(
|
||||
objectsContainer={objectsContainer}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
targetObjectFolderOrObjectWithContext={newObjectDialogOpen.from}
|
||||
onExtensionInstalled={onExtensionInstalled}
|
||||
/>
|
||||
)}
|
||||
{objectAssetSwappingDialogOpen && (
|
||||
@@ -1606,6 +1609,7 @@ const ObjectsList = React.forwardRef<Props, ObjectsListInterface>(
|
||||
objectsContainer={objectsContainer}
|
||||
object={objectAssetSwappingDialogOpen.objectWithContext.object}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
onExtensionInstalled={onExtensionInstalled}
|
||||
/>
|
||||
)}
|
||||
</Background>
|
||||
|
@@ -26,6 +26,32 @@ const getVariant = (
|
||||
: eventBasedObject.getDefaultVariant();
|
||||
};
|
||||
|
||||
const getChildObjectConfiguration = (
|
||||
childObjectName: string,
|
||||
eventBasedObject: gdEventsBasedObject,
|
||||
customObjectConfiguration: gdCustomObjectConfiguration,
|
||||
variant: gdEventsBasedObjectVariant
|
||||
): gdObjectConfiguration | null => {
|
||||
// Legacy events-based objects don't have any instance in their default
|
||||
// variant since there wasn't a graphical editor at the time.
|
||||
// In this case, the editor doesn't allow to choose a variant, but a
|
||||
// variant may have stayed after a user rolled back the extension.
|
||||
// This variant must be ignored to match what the editor shows.
|
||||
if (
|
||||
customObjectConfiguration.isForcedToOverrideEventsBasedObjectChildrenConfiguration() ||
|
||||
(variant === eventBasedObject.getDefaultVariant() &&
|
||||
customObjectConfiguration.isMarkedAsOverridingEventsBasedObjectChildrenConfiguration())
|
||||
) {
|
||||
return customObjectConfiguration.getChildObjectConfiguration(
|
||||
childObjectName
|
||||
);
|
||||
}
|
||||
const childObjects = variant.getObjects();
|
||||
return childObjects.hasObjectNamed(childObjectName)
|
||||
? childObjects.getObject(childObjectName).getConfiguration()
|
||||
: null;
|
||||
};
|
||||
|
||||
type PropertyMappingRule = {
|
||||
targetChild: string,
|
||||
targetProperty: string,
|
||||
@@ -189,6 +215,28 @@ export default class RenderedCustomObjectInstance extends Rendered3DInstance
|
||||
};
|
||||
}
|
||||
|
||||
_getChildObjectConfiguration = (
|
||||
childObjectName: string
|
||||
): gdObjectConfiguration | null => {
|
||||
const eventBasedObject = this.eventBasedObject;
|
||||
if (!eventBasedObject) {
|
||||
return null;
|
||||
}
|
||||
const customObjectConfiguration = gd.asCustomObjectConfiguration(
|
||||
this._associatedObjectConfiguration
|
||||
);
|
||||
const variant = getVariant(eventBasedObject, customObjectConfiguration);
|
||||
if (!variant) {
|
||||
return null;
|
||||
}
|
||||
return getChildObjectConfiguration(
|
||||
childObjectName,
|
||||
eventBasedObject,
|
||||
customObjectConfiguration,
|
||||
variant
|
||||
);
|
||||
};
|
||||
|
||||
getRendererOfInstance = (
|
||||
instance: gdInitialInstance
|
||||
): RenderedInstance | Rendered3DInstance => {
|
||||
@@ -199,23 +247,6 @@ export default class RenderedCustomObjectInstance extends Rendered3DInstance
|
||||
const customObjectConfiguration = gd.asCustomObjectConfiguration(
|
||||
this._associatedObjectConfiguration
|
||||
);
|
||||
|
||||
let childObjectConfiguration = null;
|
||||
const variant = this.getVariant();
|
||||
if (variant) {
|
||||
const childObjects = variant.getObjects();
|
||||
if (childObjects.hasObjectNamed(instance.getObjectName())) {
|
||||
const childObject = childObjects.getObject(instance.getObjectName());
|
||||
childObjectConfiguration =
|
||||
this.eventBasedObject &&
|
||||
customObjectConfiguration.isMarkedAsOverridingEventsBasedObjectChildrenConfiguration() &&
|
||||
variant === this.eventBasedObject.getDefaultVariant()
|
||||
? customObjectConfiguration.getChildObjectConfiguration(
|
||||
instance.getObjectName()
|
||||
)
|
||||
: childObject.getConfiguration();
|
||||
}
|
||||
}
|
||||
// Apply property mapping rules on the child instance.
|
||||
const childPropertyOverridings = new Map<string, string>();
|
||||
const customObjectProperties = customObjectConfiguration.getProperties();
|
||||
@@ -238,6 +269,9 @@ export default class RenderedCustomObjectInstance extends Rendered3DInstance
|
||||
}
|
||||
}
|
||||
//...so let's create a renderer.
|
||||
const childObjectConfiguration = this._getChildObjectConfiguration(
|
||||
instance.getObjectName()
|
||||
);
|
||||
renderedInstance = childObjectConfiguration
|
||||
? ObjectsRenderingService.createNewInstanceRenderer(
|
||||
this._project,
|
||||
@@ -362,16 +396,16 @@ export default class RenderedCustomObjectInstance extends Rendered3DInstance
|
||||
const childObjects = variant.getObjects();
|
||||
for (let i = 0; i < childObjects.getObjectsCount(); i++) {
|
||||
const childObject = childObjects.getObjectAt(i);
|
||||
const childObjectConfiguration =
|
||||
customObjectConfiguration.isForcedToOverrideEventsBasedObjectChildrenConfiguration() ||
|
||||
customObjectConfiguration.isMarkedAsOverridingEventsBasedObjectChildrenConfiguration()
|
||||
? customObjectConfiguration.getChildObjectConfiguration(
|
||||
childObject.getName()
|
||||
)
|
||||
: variant
|
||||
.getObjects()
|
||||
.getObject(childObject.getName())
|
||||
.getConfiguration();
|
||||
|
||||
const childObjectConfiguration = getChildObjectConfiguration(
|
||||
childObject.getName(),
|
||||
eventBasedObject,
|
||||
customObjectConfiguration,
|
||||
variant
|
||||
);
|
||||
if (!childObjectConfiguration) {
|
||||
continue;
|
||||
}
|
||||
const childType = childObjectConfiguration.getType();
|
||||
if (
|
||||
childType === 'Sprite' ||
|
||||
|
@@ -173,6 +173,7 @@ const CurrentUsageDisplayer = ({
|
||||
}
|
||||
hideButton={cannotUpgradeSubscription}
|
||||
recommendedPlanIdIfNoSubscription="gdevelop_silver"
|
||||
placementId="builds"
|
||||
>
|
||||
<Line>
|
||||
{!isFeatureLocked ? (
|
||||
@@ -219,6 +220,7 @@ const CurrentUsageDisplayer = ({
|
||||
}
|
||||
}
|
||||
recommendedPlanIdIfNoSubscription="gdevelop_silver"
|
||||
placementId="builds"
|
||||
>
|
||||
<Line>
|
||||
{!isFeatureLocked ? (
|
||||
|
@@ -3,7 +3,10 @@ import * as React from 'react';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { Column, Line } from '../../UI/Grid';
|
||||
import { ResponsiveLineStackLayout } from '../../UI/Layout';
|
||||
import { type SubscriptionDialogDisplayReason } from '../../Utils/Analytics/EventSender';
|
||||
import {
|
||||
type SubscriptionDialogDisplayReason,
|
||||
type SubscriptionPlacementId,
|
||||
} from '../../Utils/Analytics/EventSender';
|
||||
import { SubscriptionSuggestionContext } from './SubscriptionSuggestionContext';
|
||||
import RaisedButton from '../../UI/RaisedButton';
|
||||
import FlatButton from '../../UI/FlatButton';
|
||||
@@ -53,6 +56,7 @@ type Props = {|
|
||||
| 'gdevelop_startup'
|
||||
| 'gdevelop_education',
|
||||
canHide?: boolean,
|
||||
placementId: SubscriptionPlacementId,
|
||||
|};
|
||||
|
||||
const GetSubscriptionCard = ({
|
||||
@@ -66,6 +70,7 @@ const GetSubscriptionCard = ({
|
||||
filter,
|
||||
recommendedPlanIdIfNoSubscription,
|
||||
canHide,
|
||||
placementId,
|
||||
}: Props) => {
|
||||
const [isHidden, setIsHidden] = React.useState(false);
|
||||
const { subscription } = React.useContext(AuthenticatedUserContext);
|
||||
@@ -116,6 +121,7 @@ const GetSubscriptionCard = ({
|
||||
analyticsMetadata: {
|
||||
reason: subscriptionDialogOpeningReason,
|
||||
recommendedPlanId: actualPlanIdToRecommend,
|
||||
placementId,
|
||||
},
|
||||
filter,
|
||||
});
|
||||
|
@@ -16,6 +16,7 @@ import { isNativeMobileApp } from '../../Utils/Platform';
|
||||
import InAppTutorialContext from '../../InAppTutorial/InAppTutorialContext';
|
||||
import GetSubscriptionCard from './GetSubscriptionCard';
|
||||
import { ColumnStackLayout } from '../../UI/Layout';
|
||||
import { type SubscriptionPlacementId } from '../../Utils/Analytics/EventSender';
|
||||
|
||||
export type SubscriptionCheckerInterface = {|
|
||||
checkUserHasSubscription: () => boolean,
|
||||
@@ -29,6 +30,7 @@ type Props = {|
|
||||
| 'Debugger'
|
||||
| 'Hot reloading'
|
||||
| 'Preview over wifi',
|
||||
placementId: SubscriptionPlacementId,
|
||||
onChangeSubscription?: () => Promise<void> | void,
|
||||
mode: 'try' | 'mandatory',
|
||||
isNotShownDuringInAppTutorial?: boolean,
|
||||
@@ -39,7 +41,14 @@ const SubscriptionChecker = React.forwardRef<
|
||||
SubscriptionCheckerInterface
|
||||
>(
|
||||
(
|
||||
{ mode, id, title, onChangeSubscription, isNotShownDuringInAppTutorial },
|
||||
{
|
||||
mode,
|
||||
id,
|
||||
title,
|
||||
onChangeSubscription,
|
||||
placementId,
|
||||
isNotShownDuringInAppTutorial,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const authenticatedUser = React.useContext(AuthenticatedUserContext);
|
||||
@@ -122,6 +131,7 @@ const SubscriptionChecker = React.forwardRef<
|
||||
setDialogOpen(false);
|
||||
}}
|
||||
recommendedPlanIdIfNoSubscription="gdevelop_silver"
|
||||
placementId={placementId}
|
||||
>
|
||||
<Column noMargin expand>
|
||||
<Text>
|
||||
|
@@ -341,7 +341,10 @@ const SubscriptionDetails = ({
|
||||
primary
|
||||
onClick={() => {
|
||||
openSubscriptionDialog({
|
||||
analyticsMetadata: { reason: 'Consult profile' },
|
||||
analyticsMetadata: {
|
||||
reason: 'Consult profile',
|
||||
placementId: 'profile',
|
||||
},
|
||||
});
|
||||
}}
|
||||
disabled={isManageSubscriptionLoading}
|
||||
@@ -390,6 +393,7 @@ const SubscriptionDetails = ({
|
||||
<GetSubscriptionCard
|
||||
label={<Trans>Choose a subscription</Trans>}
|
||||
subscriptionDialogOpeningReason="Consult profile"
|
||||
placementId="profile"
|
||||
>
|
||||
<Text noMargin>
|
||||
<Trans>
|
||||
@@ -440,7 +444,10 @@ const SubscriptionDetails = ({
|
||||
color={buttonColor}
|
||||
onClick={() =>
|
||||
openSubscriptionDialog({
|
||||
analyticsMetadata: { reason: 'Consult profile' },
|
||||
analyticsMetadata: {
|
||||
reason: 'Consult profile',
|
||||
placementId: 'profile',
|
||||
},
|
||||
filter: key,
|
||||
})
|
||||
}
|
||||
@@ -458,6 +465,7 @@ const SubscriptionDetails = ({
|
||||
label={<Trans>Choose a subscription</Trans>}
|
||||
subscriptionDialogOpeningReason="Consult profile"
|
||||
recommendedPlanIdIfNoSubscription="gdevelop_silver"
|
||||
placementId="profile"
|
||||
>
|
||||
<Text noMargin>
|
||||
<Trans>
|
||||
|
@@ -5,6 +5,7 @@ import SubscriptionDialog from './SubscriptionDialog';
|
||||
import {
|
||||
sendSubscriptionDialogShown,
|
||||
type SubscriptionDialogDisplayReason,
|
||||
type SubscriptionPlacementId,
|
||||
} from '../../Utils/Analytics/EventSender';
|
||||
import { isNativeMobileApp } from '../../Utils/Platform';
|
||||
import {
|
||||
@@ -26,6 +27,7 @@ export type SubscriptionType = 'individual' | 'team' | 'education';
|
||||
export type SubscriptionAnalyticsMetadata = {|
|
||||
reason: SubscriptionDialogDisplayReason,
|
||||
recommendedPlanId?: string,
|
||||
placementId: SubscriptionPlacementId,
|
||||
preStep?: 'subscriptionChecker',
|
||||
|};
|
||||
|
||||
|
@@ -238,6 +238,7 @@ export const LoadingScreenEditor = ({
|
||||
<GetSubscriptionCard
|
||||
subscriptionDialogOpeningReason="Disable GDevelop splash at startup"
|
||||
recommendedPlanIdIfNoSubscription="gdevelop_silver"
|
||||
placementId="gdevelop-branding"
|
||||
>
|
||||
<Text>
|
||||
<Trans>
|
||||
@@ -555,6 +556,7 @@ export const LoadingScreenEditor = ({
|
||||
mode="mandatory"
|
||||
id="Disable GDevelop splash at startup"
|
||||
title={<Trans>Disable GDevelop splash at startup</Trans>}
|
||||
placementId="gdevelop-branding"
|
||||
/>
|
||||
</ColumnStackLayout>
|
||||
)}
|
||||
|
@@ -29,6 +29,11 @@ export type SceneTreeViewItemCallbacks = {|
|
||||
options?: {|
|
||||
openEventsEditor: boolean,
|
||||
openSceneEditor: boolean,
|
||||
focusWhenOpened:
|
||||
| 'scene-or-events-otherwise'
|
||||
| 'scene'
|
||||
| 'events'
|
||||
| 'none',
|
||||
|}
|
||||
) => void,
|
||||
|};
|
||||
@@ -91,7 +96,11 @@ export class SceneTreeViewItemContent implements TreeViewItemContent {
|
||||
}
|
||||
|
||||
onClick(): void {
|
||||
this.props.onOpenLayout(this.scene.getName());
|
||||
this.props.onOpenLayout(this.scene.getName(), {
|
||||
openEventsEditor: true,
|
||||
openSceneEditor: true,
|
||||
focusWhenOpened: 'scene',
|
||||
});
|
||||
}
|
||||
|
||||
rename(newName: string): void {
|
||||
@@ -115,6 +124,7 @@ export class SceneTreeViewItemContent implements TreeViewItemContent {
|
||||
this.props.onOpenLayout(this.scene.getName(), {
|
||||
openSceneEditor: true,
|
||||
openEventsEditor: false,
|
||||
focusWhenOpened: 'scene',
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -124,6 +134,7 @@ export class SceneTreeViewItemContent implements TreeViewItemContent {
|
||||
this.props.onOpenLayout(this.scene.getName(), {
|
||||
openSceneEditor: false,
|
||||
openEventsEditor: true,
|
||||
focusWhenOpened: 'events',
|
||||
}),
|
||||
},
|
||||
{
|
||||
|
@@ -422,7 +422,7 @@ type Props = {|
|
||||
onShareProject: () => void,
|
||||
onOpenHomePage: () => void,
|
||||
toggleProjectManager: () => void,
|
||||
onExtensionInstalled: (extensionName: string) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|
||||
// Main menu
|
||||
mainMenuCallbacks: MainMenuCallbacks,
|
||||
|
@@ -31,6 +31,7 @@ type Props = {|
|
||||
sourceGameId: string,
|
||||
gameScreenshotUrls: Array<string>,
|
||||
onScreenshotsClaimed: () => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|};
|
||||
|
||||
export const QuickCustomizationDialog = ({
|
||||
@@ -46,6 +47,7 @@ export const QuickCustomizationDialog = ({
|
||||
sourceGameId,
|
||||
gameScreenshotUrls,
|
||||
onScreenshotsClaimed,
|
||||
onExtensionInstalled,
|
||||
}: Props) => {
|
||||
const { triggerUnsavedChanges } = React.useContext(UnsavedChangesContext);
|
||||
const gameAndBuildsManager = useGameAndBuildsManager({
|
||||
@@ -75,6 +77,7 @@ export const QuickCustomizationDialog = ({
|
||||
onContinueQuickCustomization,
|
||||
gameScreenshotUrls,
|
||||
onScreenshotsClaimed,
|
||||
onExtensionInstalled,
|
||||
});
|
||||
|
||||
const name = project.getName();
|
||||
|
@@ -14,6 +14,7 @@ import TipCard from './TipCard';
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
resourceManagementProps: ResourceManagementProps,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|};
|
||||
|
||||
const styles = {
|
||||
@@ -29,6 +30,7 @@ const styles = {
|
||||
export const QuickObjectReplacer = ({
|
||||
project,
|
||||
resourceManagementProps,
|
||||
onExtensionInstalled,
|
||||
}: Props) => {
|
||||
const [selectedObjectToSwap, setSelectedObjectToSwap] = React.useState(null);
|
||||
|
||||
@@ -96,6 +98,7 @@ export const QuickObjectReplacer = ({
|
||||
setSelectedObjectToSwap(null);
|
||||
}}
|
||||
minimalUI
|
||||
onExtensionInstalled={onExtensionInstalled}
|
||||
/>
|
||||
)}
|
||||
</ColumnStackLayout>
|
||||
|
@@ -149,6 +149,7 @@ type Props = {|
|
||||
onContinueQuickCustomization: () => void,
|
||||
gameScreenshotUrls: Array<string>,
|
||||
onScreenshotsClaimed: () => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|};
|
||||
|
||||
export const renderQuickCustomization = ({
|
||||
@@ -165,6 +166,7 @@ export const renderQuickCustomization = ({
|
||||
onContinueQuickCustomization,
|
||||
gameScreenshotUrls,
|
||||
onScreenshotsClaimed,
|
||||
onExtensionInstalled,
|
||||
}: Props) => {
|
||||
return {
|
||||
title: quickCustomizationState.step.title,
|
||||
@@ -174,6 +176,7 @@ export const renderQuickCustomization = ({
|
||||
<QuickObjectReplacer
|
||||
project={project}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
onExtensionInstalled={onExtensionInstalled}
|
||||
/>
|
||||
) : quickCustomizationState.step.name === 'tweak-behaviors' ? (
|
||||
<QuickBehaviorsTweaker
|
||||
|
@@ -156,7 +156,7 @@ export const ProjectResourceCard = ({
|
||||
>
|
||||
{renderResourcePreview()}
|
||||
<div style={styles.titleContainer}>
|
||||
<Text noMargin style={styles.title}>
|
||||
<Text noMargin style={styles.title} color="inherit">
|
||||
{resourceName}
|
||||
</Text>
|
||||
</div>
|
||||
|
@@ -98,7 +98,7 @@ export type SceneEditorsDisplayProps = {|
|
||||
i18n: I18nType,
|
||||
objectOrGroupName: string
|
||||
) => boolean,
|
||||
onExtensionInstalled: (extensionName: string) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|
||||
updateBehaviorsSharedData: () => void,
|
||||
onInstancesAdded: (Array<gdInitialInstance>) => void,
|
||||
|
@@ -42,7 +42,7 @@ type Props = {|
|
||||
objects: Array<gdObject>,
|
||||
onEditObject: (object: gdObject, initialTab: ?ObjectEditorTab) => void,
|
||||
onUpdateBehaviorsSharedData: () => void,
|
||||
onExtensionInstalled: (extensionName: string) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
isBehaviorListLocked: boolean,
|
||||
|
||||
// For instances:
|
||||
|
@@ -96,6 +96,7 @@ const MosaicEditorsDisplay = React.forwardRef<
|
||||
initialInstances,
|
||||
selectedLayer,
|
||||
onSelectInstances,
|
||||
onExtensionInstalled,
|
||||
} = props;
|
||||
const { isMobile } = useResponsiveWindowSize();
|
||||
const {
|
||||
@@ -427,6 +428,7 @@ const MosaicEditorsDisplay = React.forwardRef<
|
||||
unsavedChanges={props.unsavedChanges}
|
||||
hotReloadPreviewButtonProps={props.hotReloadPreviewButtonProps}
|
||||
isListLocked={isCustomVariant}
|
||||
onExtensionInstalled={onExtensionInstalled}
|
||||
/>
|
||||
)}
|
||||
</I18n>
|
||||
|
@@ -72,6 +72,7 @@ const SwipeableDrawerEditorsDisplay = React.forwardRef<
|
||||
initialInstances,
|
||||
selectedLayer,
|
||||
onSelectInstances,
|
||||
onExtensionInstalled,
|
||||
} = props;
|
||||
const selectedInstances = props.instancesSelection.getSelectedInstances();
|
||||
const { values } = React.useContext(PreferencesContext);
|
||||
@@ -373,6 +374,7 @@ const SwipeableDrawerEditorsDisplay = React.forwardRef<
|
||||
props.hotReloadPreviewButtonProps
|
||||
}
|
||||
isListLocked={isCustomVariant}
|
||||
onExtensionInstalled={onExtensionInstalled}
|
||||
/>
|
||||
)}
|
||||
</I18n>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user