Compare commits

...

24 Commits

Author SHA1 Message Date
Clément Pasteau
ba4a37813e Refactor into usage 2023-02-14 15:37:14 +01:00
Clément Pasteau
3f36054d54 Reduce allowed size for online web build to avoid abuse 2023-02-14 15:04:25 +01:00
AlexandreS
cdfd7a7ab1 Autotranslate improvements (#4951)
Don't show in changelog
2023-02-14 14:08:57 +01:00
Florian Rival
a944ac43db Update README
Don't show in changelog
2023-02-14 12:40:59 +01:00
Clément Pasteau
b7a1a96e7b Update storybook urls (#4949)
Do not show in changelog
2023-02-14 09:54:02 +01:00
Clément Pasteau
29d71796cc Remove unused ravenjs (sentry) (#4940)
Do not show in changelog
2023-02-13 16:41:28 +01:00
AlexandreS
78a1361897 Use form in account related actions to simplify mobile experience (#4917)
Don't show in changelog
2023-02-13 14:27:05 +01:00
AlexandreS
e340784ad6 Prevent google translate issue (#4937)
Don't show in changelog
2023-02-13 14:22:08 +01:00
github-actions[bot]
511466f0b8 Update translations [skip ci] (#4909)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2023-02-13 10:39:08 +01:00
Florian Rival
3f4e372acb Fix check of subscriptions
Don't show in changelog
2023-02-10 17:01:18 +01:00
D8H
a73a92d748 Fix tile map loading for collision mask objects (#4916) 2023-02-10 15:52:06 +01:00
AlexandreS
6e131d8a17 Display asset pack categories in asset store homepage (#4905) 2023-02-10 14:57:24 +01:00
D8H
9ac4c021e0 Fix the number parameter color in the events (#4915) 2023-02-10 11:37:38 +01:00
AlexandreS
a1fbf91ac7 Add Eye icon to switch password visibility (#4914) 2023-02-09 17:16:13 +01:00
Clément Pasteau
3f8d01e25e Improve subscription messages when a redeem code has been used (#4912) 2023-02-09 16:56:54 +01:00
AlexandreS
03b4170d74 Fix resource store display for extension icon (#4913)
Don't show in changelog
2023-02-09 15:34:13 +01:00
AlexandreS
0146ad9c38 Add error boundary at the app level (#4911)
Don't show in changelog
2023-02-09 12:19:26 +01:00
Clément Pasteau
fbb23f86cd Rename and add more actions, conditions and expressions to manipulate array variables (#4904) 2023-02-08 16:53:57 +01:00
github-actions[bot]
1fdaeea4f8 Update translations [skip ci] (#4893)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-02-08 11:21:06 +01:00
Clément Pasteau
32b97f2c40 Improve search accuracy in the app (instructions, examples, objects, tags) (#4906)
* Use tokenized search to improve search with multiple words looking into multiple keys (ex: name & description)
2023-02-08 09:41:27 +01:00
AlexandreS
d61f8336a8 Fix firebase extension tests (#4907)
Don't show in changelog
2023-02-07 17:11:16 +01:00
Clément Pasteau
3448cd57fe Allow showing an alertMessage without an icon (#4903) 2023-02-06 16:41:55 +01:00
Clément Pasteau
b7333612aa Prompt for email verification when logging in or signing up (#4898) 2023-02-06 11:45:32 +01:00
Aurélien Vivet
de82182b37 fix typo (#4899)
Do not show in changelog
2023-02-06 08:47:53 +01:00
237 changed files with 2919 additions and 1985 deletions

View File

@@ -57,8 +57,8 @@ jobs:
- name: Log urls to the Storybook
run: |
echo "Find the latest Storybook for this branch on http://gdevelop-storybook.s3-website-us-east-1.amazonaws.com/$(git rev-parse --abbrev-ref HEAD)/latest/index.html"
echo "Find the Storybook for this commit on http://gdevelop-storybook.s3-website-us-east-1.amazonaws.com/$(git rev-parse --abbrev-ref HEAD)/commit/$(git rev-parse HEAD)/index.html"
echo "Find the latest Storybook for this branch on https://gdevelop-storybook.s3.amazonaws.com/$(git rev-parse --abbrev-ref HEAD)/latest/index.html"
echo "Find the Storybook for this commit on https://gdevelop-storybook.s3.amazonaws.com/$(git rev-parse --abbrev-ref HEAD)/commit/$(git rev-parse HEAD)/index.html"
# Publish on Chromatic, only when manually launched (too costly to run on every commit).
- name: Publish Storybook to Chromatic

View File

@@ -406,8 +406,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.MarkAsAdvanced();
obj.AddAction("ModVarObjet",
_("Value of an object variable"),
_("Change the value of an object variable."),
_("Change number variable"),
_("Modify the number value of an object variable."),
_("the variable _PARAM1_"),
_("Variables"),
"res/actions/var24.png",
@@ -419,8 +419,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
ParameterOptions::MakeNewOptions());
obj.AddAction("ModVarObjetTxt",
_("Text of an object variable"),
_("Change the text of an object variable."),
_("Change text variable"),
_("Modify the text of an object variable."),
_("the text of variable _PARAM1_"),
_("Variables"),
"res/actions/var24.png",
@@ -432,8 +432,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
ParameterOptions::MakeNewOptions());
obj.AddAction("SetObjectVariableAsBoolean",
_("Boolean value of an object variable"),
_("Change the boolean value of an object variable."),
_("Change boolean variable"),
_("Modify the boolean value of an object variable."),
_("Set the boolean value of variable _PARAM1_ of "
"_PARAM0_ to _PARAM2_"),
_("Variables"),
@@ -446,7 +446,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
obj.AddAction(
"ToggleObjectVariableAsBoolean",
_("Toggle the boolean value of an object variable"),
_("Toggle boolean variable"),
_("Toggles the boolean value of an object variable.") + "\n" +
_("If it was true, it will become false, and if it was false "
"it will become true."),
@@ -461,37 +461,39 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
obj.AddCondition("ObjectVariableChildExists",
_("Child existence"),
_("Check if the specified child of the variable exists."),
_("Check if the specified child of the object "
"structure variable exists."),
_("Child _PARAM2_ of variable _PARAM1_ of _PARAM0_ exists"),
_("Variables/Collections/Structures"),
_("Variables/Arrays and structures"),
"res/conditions/var24.png",
"res/conditions/var.png")
.AddParameter("object", _("Object"))
.AddParameter("objectvar", _("Variable"))
.AddParameter("objectvar", _("Structure variable"))
.AddParameter("string", _("Name of the child"))
.MarkAsAdvanced();
obj.AddAction("ObjectVariableRemoveChild",
_("Remove a child"),
_("Remove a child from an object variable."),
_("Remove a child from an object structure variable."),
_("Remove child _PARAM2_ from variable _PARAM1_ of _PARAM0_"),
_("Variables/Collections/Structures"),
_("Variables/Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("object", _("Object"))
.AddParameter("objectvar", _("Variable"))
.AddParameter("objectvar", _("Structure variable"))
.AddParameter("string", _("Child's name"))
.MarkAsAdvanced();
obj.AddAction("ObjectVariableClearChildren",
_("Clear variable"),
_("Remove all the children from the object variable."),
_("Clear children"),
_("Remove all the children from the object array or structure "
"variable."),
_("Clear children from variable _PARAM1_ of _PARAM0_"),
_("Variables/Collections"),
_("Variables/Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("object", _("Object"))
.AddParameter("objectvar", _("Variable"))
.AddParameter("objectvar", _("Array or structure variable"))
.MarkAsAdvanced();
obj.AddAction("Cache",
@@ -619,8 +621,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.MarkAsAdvanced();
obj.AddCondition("VarObjet",
_("Value of an object variable"),
_("Compare the value of an object variable."),
_("Number variable"),
_("Compare the number value of an object variable."),
_("the variable _PARAM1_"),
_("Variables"),
"res/conditions/var24.png",
@@ -632,7 +634,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"number", ParameterOptions::MakeNewOptions());
obj.AddCondition("VarObjetTxt",
_("Text of an object variable"),
_("Text variable"),
_("Compare the text of an object variable."),
_("the text of variable _PARAM1_"),
_("Variables"),
@@ -645,7 +647,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"string", ParameterOptions::MakeNewOptions());
obj.AddCondition("ObjectVariableAsBoolean",
_("Boolean value of an object variable"),
_("Boolean variable"),
_("Compare the boolean value of an object variable."),
_("The boolean value of variable _PARAM1_ of object "
"_PARAM0_ is _PARAM2_"),
@@ -659,7 +661,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
obj.AddCondition("VarObjetDef",
"Variable defined",
"Check if the variable is defined.",
"Check if the object variable is defined.",
"Variable _PARAM1 of _PARAM0_ is defined",
_("Variables"),
"res/conditions/var24.png",
@@ -667,78 +669,131 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.AddParameter("object", _("Object"))
.AddParameter("string", _("Variable"))
.SetHidden();
.SetHidden(); // Deprecated.
obj.AddAction(
"ObjectVariablePush",
_("Append variable to an object array"),
_("Appends a variable to the end of an object array variable."),
_("Append variable _PARAM2_ to array variable _PARAM1_ of _PARAM0_"),
_("Variables/Collections/Arrays"),
_("Add existing variable"),
_("Adds an existing variable to the end of an object array variable."),
_("Add variable _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("scenevar", _("Scene variable with the content to append"))
.SetParameterLongDescription(
_("The content of the variable will *be copied* and appended at the "
"end of the array."))
.AddParameter("scenevar", _("Scene variable with the content to add"))
.SetParameterLongDescription(_("The content of the object variable will "
"*be copied* and added at the "
"end of the array."))
.MarkAsAdvanced();
obj.AddAction(
"ObjectVariablePushString",
_("Append a string to an object array"),
_("Appends a string to the end of an object array variable."),
_("Append string _PARAM2_ to array variable _PARAM1_ of _PARAM0_"),
_("Variables/Collections/Arrays"),
_("Add text variable"),
_("Adds a text (string) to the end of an object array variable."),
_("Add text _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("string", _("String to append"))
.AddParameter("string", _("Text to add"))
.MarkAsAdvanced();
obj.AddAction(
"ObjectVariablePushNumber",
_("Append a number to an object array"),
_("Appends a number to the end of an object array variable."),
_("Append number _PARAM2_ to array variable _PARAM1_ of _PARAM0_"),
_("Variables/Collections/Arrays"),
"res/actions/var24.png",
"res/actions/var.png")
obj.AddAction("ObjectVariablePushNumber",
_("Add number variable"),
_("Adds a number to the end of an object array variable."),
_("Add number _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("expression", _("Number to append"))
.AddParameter("expression", _("Number to add"))
.MarkAsAdvanced();
obj.AddAction(
"ObjectVariablePushBool",
_("Append a boolean to an object array"),
_("Appends a boolean to the end of an object array variable."),
_("Append boolean _PARAM2_ to array variable _PARAM1_ of _PARAM0_"),
_("Variables/Collections/Arrays"),
_("Add boolean variable"),
_("Adds a boolean to the end of an object array variable."),
_("Add boolean _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 append"))
.AddParameter("trueorfalse", _("Boolean to add"))
.MarkAsAdvanced();
obj.AddAction(
"ObjectVariableRemoveAt",
_("Remove variable from an object array (by index)"),
_("Remove variable by index"),
_("Removes a variable at the specified index of an object array "
"variable."),
_("Remove variable at index _PARAM2_ from array variable _PARAM1_ of "
"_PARAM0_"),
_("Variables/Collections/Arrays"),
_("Variables/Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("object", _("Object"))
.AddParameter("objectvar", _("Variable"))
.AddParameter("objectvar", _("Array variable"))
.AddParameter("expression", _("Index to remove"))
.MarkAsAdvanced();
obj.AddCondition(
"ObjectVariableChildCount",
_("Number of children"),
_("Compare the number of children in an object array variable."),
_("The number of children in the array variable _PARAM1_"),
_("Variables/Arrays and structures"),
"res/conditions/var24.png",
"res/conditions/var.png")
.AddParameter("object", _("Object"))
.AddParameter("objectvar", _("Variable"))
.UseStandardRelationalOperatorParameters(
"number", ParameterOptions::MakeNewOptions())
.MarkAsAdvanced();
obj.AddStrExpression(
"ArrayVariableFirstString",
_("First text child"),
_("Get the value of the first element of an object array variable, if "
"it is a text (string) variable."),
_("Variables/Arrays and structures"),
"res/actions/var.png")
.AddParameter("object", _("Object"))
.AddParameter("objectvar", _("Array variable"));
obj.AddExpression(
"ArrayVariableFirstNumber",
_("First number child"),
_("Get the value of the first element of an object array variable, if "
"it is a number variable."),
_("Variables/Arrays and structures"),
"res/actions/var.png")
.AddParameter("object", _("Object"))
.AddParameter("objectvar", _("Array variable"));
obj.AddStrExpression(
"ArrayVariableLastString",
_("Last text child"),
_("Get the value of the last element of an object array variable, if "
"it is a text (string) variable."),
_("Variables/Arrays and structures"),
"res/actions/var.png")
.AddParameter("object", _("Object"))
.AddParameter("objectvar", _("Array variable"));
obj.AddExpression(
"ArrayVariableLastNumber",
_("Last number child"),
_("Get the value of the last element of an object array variable, if "
"it is a number variable."),
_("Variables/Arrays and structures"),
"res/actions/var.png")
.AddParameter("object", _("Object"))
.AddParameter("objectvar", _("Array variable"));
obj.AddCondition("BehaviorActivated",
_("Behavior activated"),
_("Check if the behavior is activated for the object."),
@@ -1115,23 +1170,24 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.AddParameter("expression", _("Target Y position"));
obj.AddExpression("Variable",
_("Value of an object variable"),
_("Value of an object variable"),
_("Number variable"),
_("Number value of an object variable"),
_("Variables"),
"res/actions/var.png")
.AddParameter("object", _("Object"))
.AddParameter("objectvar", _("Variable"));
obj.AddExpression("VariableChildCount",
_("Number of children of an object variable"),
_("Number of children of an object variable"),
_("Variables"),
"res/actions/var.png")
obj.AddExpression(
"VariableChildCount",
_("Number of children"),
_("Number of children in an object array or structure variable"),
_("Variables/Arrays and structures"),
"res/actions/var.png")
.AddParameter("object", _("Object"))
.AddParameter("objectvar", _("Variable"));
.AddParameter("objectvar", _("Array or structure variable"));
obj.AddStrExpression("VariableString",
_("Text of an object variable"),
_("Text variable"),
_("Text of an object variable"),
_("Variables"),
"res/actions/var.png")
@@ -1293,7 +1349,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"res/actions/create24.png",
"res/actions/create24.png")
.AddCodeOnlyParameter("objectsContext", "")
.AddParameter("objectListOrEmptyIfJustDeclared", _("Group of potential objects"))
.AddParameter("objectListOrEmptyIfJustDeclared",
_("Group of potential objects"))
.SetParameterLongDescription(
_("Group containing objects that can be created by the action."))
.AddParameter("string", _("Name of the object to create"))

View File

@@ -27,9 +27,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
extension
.AddCondition("VarScene",
_("Value of a scene variable"),
_("Compare the value of a scene variable."),
_("the scene variable _PARAM0_"),
_("Number variable"),
_("Compare the number value of a scene variable."),
_("The number of scene variable _PARAM0_"),
_("Scene variables"),
"res/conditions/var24.png",
"res/conditions/var.png")
@@ -39,9 +39,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
extension
.AddCondition("VarSceneTxt",
_("Text of a scene variable"),
_("Compare the text of a scene variable."),
_("the text of scene variable _PARAM0_"),
_("Text variable"),
_("Compare the text (string) of a scene variable."),
_("The text of scene variable _PARAM0_"),
_("Scene variables"),
"res/conditions/var24.png",
"res/conditions/var.png")
@@ -52,7 +52,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
extension
.AddCondition(
"SceneVariableAsBoolean",
_("Boolean value of a scene variable"),
_("Boolean variable"),
_("Compare the boolean value of a scene variable."),
_("The boolean value of scene variable _PARAM0_ is _PARAM1_"),
_("Scene variables"),
@@ -63,14 +63,14 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
.SetDefaultValue("true");
extension
.AddCondition(
"VariableChildExists",
_("Child existence"),
_("Check if the specified child of the scene variable exists."),
_("Child _PARAM1_ of scene variable _PARAM0_ exists"),
_("Scene variables/Collections/Structures"),
"res/conditions/var24.png",
"res/conditions/var.png")
.AddCondition("VariableChildExists",
_("Child existence"),
_("Check if the specified child of the scene structure "
"variable exists."),
_("Child _PARAM1_ of scene variable _PARAM0_ exists"),
_("Scene variables/Arrays and structures"),
"res/conditions/var24.png",
"res/conditions/var.png")
.AddParameter("scenevar", _("Variable"))
.AddParameter("string", _("Name of the child"))
.MarkAsAdvanced();
@@ -78,10 +78,10 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
extension
.AddCondition("GlobalVariableChildExists",
_("Child existence"),
_("Check if the specified child of the global "
_("Check if the specified child of the global structure "
"variable exists."),
_("Child _PARAM1_ of global variable _PARAM0_ exists"),
_("Global variables/Collections/Structures"),
_("Global variables/Arrays and structures"),
"res/conditions/var24.png",
"res/conditions/var.png")
.AddParameter("globalvar", _("Variable"))
@@ -90,7 +90,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
extension
.AddCondition("VarSceneDef",
"Test if a scene variable is defined",
"Variable defined",
"Test if the scene variable exists.",
"Scene variable _PARAM0_ is defined",
_("Scene variables"),
@@ -98,12 +98,12 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
"res/conditions/var.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("string", _("Variable"))
.SetHidden();
.SetHidden(); // Deprecated.
extension
.AddCondition("VarGlobal",
_("Value of a global variable"),
_("Compare the value of a global variable."),
_("Number variable"),
_("Compare the number value of a global variable."),
_("the global variable _PARAM0_"),
_("Global variables"),
"res/conditions/var24.png",
@@ -115,8 +115,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
extension
.AddCondition("VarGlobalTxt",
_("Text of a global variable"),
_("Compare the text of a global variable."),
_("Text variable"),
_("Compare the text (string) of a global variable."),
_("the text of the global variable _PARAM0_"),
_("Global variables"),
"res/conditions/var24.png",
@@ -129,7 +129,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
extension
.AddCondition(
"GlobalVariableAsBoolean",
_("Boolean value of a global variable"),
_("Boolean variable"),
_("Compare the boolean value of a global variable."),
_("The boolean value of global variable _PARAM0_ is _PARAM1_"),
_("Global variables"),
@@ -141,8 +141,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
extension
.AddCondition("VarGlobalDef",
"Test if a global variable is defined",
"Test if a global variable exists",
"Variable defined",
"Test if a global variable exists.",
"Global variable _PARAM0_ is defined",
_("Global variables"),
"res/conditions/var24.png",
@@ -150,12 +150,12 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("string", _("Variable"))
.MarkAsAdvanced()
.SetHidden();
.SetHidden(); // Deprecated.
extension
.AddAction("ModVarScene",
_("Value of a scene variable"),
_("Change the value of a scene variable."),
_("Change number variable"),
_("Modify the number value of a scene variable."),
_("the scene variable _PARAM0_"),
_("Scene variables"),
"res/actions/var24.png",
@@ -166,8 +166,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
extension
.AddAction("ModVarSceneTxt",
_("String of a scene variable"),
_("Modify the text of a scene variable."),
_("Change text variable"),
_("Modify the text (string) of a scene variable."),
_("the text of scene variable _PARAM0_"),
_("Scene variables"),
"res/actions/var24.png",
@@ -179,7 +179,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
extension
.AddAction(
"SetSceneVariableAsBoolean",
_("Boolean value of a scene variable"),
_("Change boolean variable"),
_("Modify the boolean value of a scene variable."),
_("Set the boolean value of scene variable _PARAM0_ to _PARAM1_"),
_("Scene variables"),
@@ -190,7 +190,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
extension
.AddAction("ToggleSceneVariableAsBoolean",
_("Toggle boolean value of a scene variable"),
_("Toggle boolean variable"),
_("Toggle the boolean value of a scene variable.") + "\n" +
_("If it was true, it will become false, and if it was "
"false it will become true."),
@@ -202,8 +202,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
extension
.AddAction("ModVarGlobal",
_("Value of a global variable"),
_("Change the value of a global variable"),
_("Change number variable"),
_("Modify the number value of a global variable."),
_("the global variable _PARAM0_"),
_("Global variables"),
"res/actions/var24.png",
@@ -215,8 +215,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
extension
.AddAction("ModVarGlobalTxt",
_("String of a global variable"),
_("Modify the text of a global variable."),
_("Change text variable"),
_("Modify the text (string) of a global variable."),
_("the text of global variable _PARAM0_"),
_("Global variables"),
"res/actions/var24.png",
@@ -229,7 +229,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
extension
.AddAction(
"SetGlobalVariableAsBoolean",
_("Boolean value of a global variable"),
_("Change boolean variable"),
_("Modify the boolean value of a global variable."),
_("Set the boolean value of global variable _PARAM0_ to _PARAM1_"),
_("Global variables"),
@@ -240,7 +240,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
extension
.AddAction("ToggleGlobalVariableAsBoolean",
_("Toggle boolean value of a global variable"),
_("Toggle boolean variable"),
_("Toggle the boolean value of a global variable.") + "\n" +
_("If it was true, it will become false, and if it was "
"false it will become true."),
@@ -251,202 +251,324 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
.AddParameter("globalvar", _("Variable"));
extension
.AddAction("VariableRemoveChild",
_("Remove a child"),
_("Remove a child from a scene variable."),
_("Remove child _PARAM1_ from scene variable _PARAM0_"),
_("Scene variables/Collections/Structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("scenevar", _("Variable"))
.AddAction(
"VariableRemoveChild",
_("Remove a child"),
_("Remove a child from a scene structure variable."),
_("Remove child _PARAM1_ from scene structure variable _PARAM0_"),
_("Scene variables/Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("scenevar", _("Structure variable"))
.AddParameter("string", _("Child's name"))
.MarkAsAdvanced();
extension
.AddAction("GlobalVariableRemoveChild",
_("Remove a child"),
_("Remove a child from a global variable."),
_("Remove child _PARAM1_ from global variable _PARAM0_"),
_("Global variables/Collections/Structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("globalvar", _("Variable"))
.AddAction(
"GlobalVariableRemoveChild",
_("Remove a child"),
_("Remove a child from a global structure variable."),
_("Remove child _PARAM1_ from global structure variable _PARAM0_"),
_("Global variables/Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("globalvar", _("Structure variable"))
.AddParameter("string", _("Child's name"))
.MarkAsAdvanced();
extension
.AddAction("VariableClearChildren",
_("Clear scene variable"),
_("Remove all the children from the scene variable."),
_("Clear children"),
_("Remove all the children from the scene structure or array "
"variable."),
_("Clear children from scene variable _PARAM0_"),
_("Scene variables/Collections"),
_("Scene variables/Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("scenevar", _("Variable"))
.AddParameter("scenevar", _("Structure or array variable"))
.MarkAsAdvanced();
extension
.AddAction("GlobalVariableClearChildren",
_("Clear global variable"),
_("Remove all the children from the global variable."),
_("Clear children"),
_("Remove all the children from the global structure or array "
"variable."),
_("Clear children from global variable _PARAM0_"),
_("Global variables/Collections"),
_("Global variables/Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("globalvar", _("Variable"))
.AddParameter("globalvar", _("Structure or array variable"))
.MarkAsAdvanced();
extension
.AddAction("SceneVariablePush",
_("Append variable to a scene array"),
_("Appends a variable at the end of a scene array variable."),
_("Append variable _PARAM1_ to array variable _PARAM0_"),
_("Scene variables/Collections/Arrays"),
_("Add existing variable"),
_("Adds an existing variable at the end of a scene array "
"variable."),
_("Add variable _PARAM1_ to array variable _PARAM0_"),
_("Scene variables/Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("scenevar", _("Array variable"))
.AddParameter("scenevar", _("Scene variable with the content to append"))
.SetParameterLongDescription(_("The content of the variable will *be copied* and appended at the end of the array."))
.AddParameter("scenevar", _("Scene variable with the content to add"))
.SetParameterLongDescription(
_("The content of the variable will *be copied* and added at the "
"end of the array."))
.MarkAsAdvanced();
extension
.AddAction("SceneVariablePushString",
_("Append a string to a scene array"),
_("Appends a string at the end of a scene array variable."),
_("Append string _PARAM1_ to array variable _PARAM0_"),
_("Scene variables/Collections/Arrays"),
"res/actions/var24.png",
"res/actions/var.png")
.AddAction(
"SceneVariablePushString",
_("Add text variable"),
_("Adds a text (string) at the end of a scene array variable."),
_("Add text _PARAM1_ to array variable _PARAM0_"),
_("Scene variables/Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("scenevar", _("Array variable"))
.AddParameter("string", _("String to append"))
.AddParameter("string", _("Text to add"))
.MarkAsAdvanced();
extension
.AddAction("SceneVariablePushNumber",
_("Append a number to a scene array"),
_("Appends a number at the end of a scene array variable."),
_("Append number _PARAM1_ to array variable _PARAM0_"),
_("Scene variables/Collections/Arrays"),
_("Add number variable"),
_("Adds a number at the end of a scene array variable."),
_("Add number _PARAM1_ to array variable _PARAM0_"),
_("Scene variables/Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("scenevar", _("Array variable"))
.AddParameter("expression", _("Number to append"))
.AddParameter("expression", _("Number to add"))
.MarkAsAdvanced();
extension
.AddAction("SceneVariablePushBool",
_("Append a boolean to a scene array"),
_("Appends a boolean at the end of a scene array variable."),
_("Append boolean _PARAM1_ to array variable _PARAM0_"),
_("Scene variables/Collections/Arrays"),
_("Add boolean variable"),
_("Adds a boolean at the end of a scene array variable."),
_("Add boolean _PARAM1_ to array variable _PARAM0_"),
_("Scene variables/Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("scenevar", _("Array variable"))
.AddParameter("trueorfalse", _("Boolean to append"))
.AddParameter("trueorfalse", _("Boolean to add"))
.MarkAsAdvanced();
extension
.AddAction(
"SceneVariableRemoveAt",
_("Remove variable from a scene array (by index)"),
_("Removes a variable at the specified index of a scene array variable."),
_("Remove variable at index _PARAM1_ from scene array variable _PARAM0_"),
_("Scene variables/Collections/Arrays"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("scenevar", _("Variable"))
.AddAction("SceneVariableRemoveAt",
_("Remove variable by index"),
_("Removes a variable at the specified index of a scene array "
"variable."),
_("Remove variable at index _PARAM1_ from scene array "
"variable _PARAM0_"),
_("Scene variables/Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("scenevar", _("Array variable"))
.AddParameter("expression", _("Index to remove"))
.MarkAsAdvanced();
extension
.AddAction("GlobalVariablePush",
_("Append variable to a global array"),
_("Appends a variable at the end of a global array variable."),
_("Append variable _PARAM1_ to array variable _PARAM0_"),
_("Global variables/Collections/Arrays"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("globalvar", _("Array variable"))
.AddParameter("scenevar", _("Scene variable with the content to append"))
.SetParameterLongDescription(_("The content of the variable will *be copied* and appended at the end of the array."))
.AddCondition(
"SceneVariableChildCount",
_("Number of children"),
_("Compare the number of children in a scene array variable."),
_("The number of children in the array variable _PARAM0_"),
_("Scene variables/Arrays and structures"),
"res/conditions/var24.png",
"res/conditions/var.png")
.AddParameter("scenevar", _("Array variable"))
.UseStandardRelationalOperatorParameters(
"number", ParameterOptions::MakeNewOptions())
.MarkAsAdvanced();
extension
.AddStrExpression(
"SceneVariableFirstString",
_("First text child"),
_("Get the value of the first element of a scene array variable, if "
"it is a text (string)."),
_("Scene variables/Arrays and structures"),
"res/actions/var.png")
.AddParameter("scenevar", _("Array variable"));
extension
.AddExpression(
"SceneVariableFirstNumber",
_("First number child"),
_("Get the value of the first element of a scene array variable, if "
"it is a number."),
_("Scene variables/Arrays and structures"),
"res/actions/var.png")
.AddParameter("scenevar", _("Array variable"));
extension
.AddStrExpression(
"SceneVariableLastString",
_("Last text child"),
_("Get the value of the last element of a scene array variable, if "
"it is a text (string)."),
_("Scene variables/Arrays and structures"),
"res/actions/var.png")
.AddParameter("scenevar", _("Array variable"));
extension
.AddExpression(
"SceneVariableLastNumber",
_("Last number child"),
_("Get the value of the last element of a scene array variable, if "
"it is a number."),
_("Scene variables/Arrays and structures"),
"res/actions/var.png")
.AddParameter("scenevar", _("Array variable"));
extension
.AddAction(
"GlobalVariableRemoveAt",
_("Remove variable from a global array (by index)"),
_("Removes a variable at the specified index of a global array variable."),
_("Remove variable at index _PARAM1_ from global array variable _PARAM0_"),
_("Global variables/Collections/Arrays"),
"GlobalVariablePush",
_("Add existing variable"),
_("Adds an existing variable at the end of a global array variable."),
_("Add variable _PARAM1_ to array variable _PARAM0_"),
_("Global variables/Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("globalvar", _("Variable"))
.AddParameter("globalvar", _("Array variable"))
.AddParameter("scenevar", _("Scene variable with the content to add"))
.SetParameterLongDescription(
_("The content of the variable will *be copied* and added at the "
"end of the array."))
.MarkAsAdvanced();
extension
.AddAction("GlobalVariableRemoveAt",
_("Remove variable by index"),
_("Removes a variable at the specified index of a global "
"array variable."),
_("Remove variable at index _PARAM1_ from global array "
"variable _PARAM0_"),
_("Global variables/Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("globalvar", _("Array variable"))
.AddParameter("expression", _("Index to remove"))
.MarkAsAdvanced();
extension
.AddAction("GlobalVariablePushString",
_("Append a string to a global array"),
_("Appends a string at the end of a global array variable."),
_("Append string _PARAM1_ to array variable _PARAM0_"),
_("Global variables/Collections/Arrays"),
"res/actions/var24.png",
"res/actions/var.png")
.AddAction(
"GlobalVariablePushString",
_("Add text variable"),
_("Adds a text (string) at the end of a global array variable."),
_("Add text _PARAM1_ to array variable _PARAM0_"),
_("Global variables/Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("globalvar", _("Array variable"))
.AddParameter("string", _("String to append"))
.AddParameter("string", _("Text to add"))
.MarkAsAdvanced();
extension
.AddAction("GlobalVariablePushNumber",
_("Append a number to a global array"),
_("Appends a number at the end of a global array variable."),
_("Append number _PARAM1_ to array variable _PARAM0_"),
_("Global variables/Collections/Arrays"),
_("Add number variable"),
_("Adds a number at the end of a global array variable."),
_("Add number _PARAM1_ to array variable _PARAM0_"),
_("Global variables/Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("globalvar", _("Array variable"))
.AddParameter("expression", _("Number to append"))
.AddParameter("expression", _("Number to add"))
.MarkAsAdvanced();
extension
.AddAction("GlobalVariablePushBool",
_("Append a boolean to a global array"),
_("Appends a boolean at the end of a global array variable."),
_("Append boolean _PARAM1_ to array variable _PARAM0_"),
_("Global variables/Collections/Arrays"),
_("Add boolean variable"),
_("Adds a boolean at the end of a global array variable."),
_("Add boolean _PARAM1_ to array variable _PARAM0_"),
_("Global variables/Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("globalvar", _("Array variable"))
.AddParameter("trueorfalse", _("Boolean to append"))
.AddParameter("trueorfalse", _("Boolean to add"))
.MarkAsAdvanced();
extension
.AddExpression("GlobalVariableChildCount",
_("Number of children of a global variable"),
_("Number of children of a global variable"),
_("Global variables"),
.AddCondition(
"GlobalVariableChildCount",
_("Number of children"),
_("Compare the number of children in a global array variable."),
_("The number of children of the array variable _PARAM0_"),
_("Global variables/Arrays and structures"),
"res/conditions/var24.png",
"res/conditions/var.png")
.AddParameter("globalvar", _("Array variable"))
.UseStandardRelationalOperatorParameters(
"number", ParameterOptions::MakeNewOptions())
.MarkAsAdvanced();
extension
.AddStrExpression("GlobalVariableFirstString",
_("First text child"),
_("Value of the first element of a global array "
"variable, if it is a text (string) variable."),
_("Global variables/Arrays and structures"),
"res/actions/var.png")
.AddParameter("globalvar", _("Array variable"));
extension
.AddExpression("GlobalVariableFirstNumber",
_("First number child"),
_("Value of the first element of a global array "
"variable, if it is a number variable"),
_("Global variables/Arrays and structures"),
"res/actions/var.png")
.AddParameter("globalvar", _("Variable"));
.AddParameter("globalvar", _("Array variable"));
extension
.AddStrExpression(
"GlobalVariableLastString",
_("Last text child"),
_("Value of the last element of a global array variable, if "
"it is a text (string) variable."),
_("Global variables/Arrays and structures"),
"res/actions/var.png")
.AddParameter("globalvar", _("Array variable"));
extension
.AddExpression(
"GlobalVariableLastNumber",
_("Last number child"),
_("Value of the last element of a global array variable, if "
"it is a number variable"),
_("Global variables/Arrays and structures"),
"res/actions/var.png")
.AddParameter("globalvar", _("Array variable"));
extension
.AddExpression("GlobalVariableChildCount",
_("Number of children"),
_("Number of children in a global array or "
"structure variable"),
_("Global variables/Arrays and structures"),
"res/actions/var.png")
.AddParameter("globalvar", _("Array or structure variable"));
extension
.AddExpression("VariableChildCount",
_("Number of children of a scene variable"),
_("Number of children of a scene variable"),
_("Scene variables"),
_("Number of children"),
_("Number of children in a scene array or "
"structure variable"),
_("Scene variables/Arrays and structures"),
"res/actions/var.png")
.AddParameter("scenevar", _("Variable"));
.AddParameter("scenevar", _("Array or structure variable"));
extension
.AddExpression("Variable",
_("Value of a scene variable"),
_("Value of a scene variable"),
_("Number variable"),
_("Number value of a scene variable"),
_("Scene variables"),
"res/actions/var.png")
.AddParameter("scenevar", _("Variable"));
extension
.AddStrExpression("VariableString",
_("Text of a scene variable"),
_("Text variable"),
_("Text of a scene variable"),
_("Scene variables"),
"res/actions/var.png")
@@ -454,15 +576,15 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
extension
.AddExpression("GlobalVariable",
_("Value of a global variable"),
_("Value of a global variable"),
_("Number variable"),
_("Number value of a global variable"),
_("Global variables"),
"res/actions/var.png")
.AddParameter("globalvar", _("Name of the global variable"));
extension
.AddStrExpression("GlobalVariableString",
_("Text of a global variable"),
_("Text variable"),
_("Text of a global variable"),
_("Global variables"),
"res/actions/var.png")

View File

@@ -24,23 +24,23 @@ const variable = new gdjs.Variable().fromJSObject({
it: ['is', true],
});
/**
* A firebase configuration of a project made only for those tests.
*/
const firebaseConfig = {
apiKey: 'AIzaSyBwPnGpfEBXDjwQrWfU0wqgp4m9qEt7YM8',
authDomain: 'gdtest-e11a5.firebaseapp.com',
databaseURL: 'https://gdtest-e11a5.firebaseio.com',
projectId: 'gdtest-e11a5',
storageBucket: 'gdtest-e11a5.appspot.com',
messagingSenderId: '254035412678',
appId: '1:254035412678:web:2ddd6b83019b7f259b79c7',
measurementId: 'G-4REML26L59',
};
// The tests require an internet connection, as a real Firebase instance is used.
const describeIfOnline = navigator.onLine ? describe : describe.skip;
describeIfOnline('Firebase extension end-to-end tests', function () {
/**
* A firebase configuration of a project made only for those tests.
*/
const firebaseConfig = {
apiKey: 'AIzaSyBwPnGpfEBXDjwQrWfU0wqgp4m9qEt7YM8',
authDomain: 'gdtest-e11a5.firebaseapp.com',
databaseURL: 'https://gdtest-e11a5.firebaseio.com',
projectId: 'gdtest-e11a5',
storageBucket: 'gdtest-e11a5.appspot.com',
messagingSenderId: '254035412678',
appId: '1:254035412678:web:2ddd6b83019b7f259b79c7',
measurementId: 'G-4REML26L59',
};
// Increase the timeout to work on low connections as well.
this.timeout('5s');
@@ -48,8 +48,8 @@ describeIfOnline('Firebase extension end-to-end tests', function () {
.toString()
.replace('.', '-')}-${Date.now()}`;
before(function setupFirebase() {
gdjs.evtTools.firebaseTools._setupFirebase({
before(async function setupFirebase() {
await gdjs.evtTools.firebaseTools._setupFirebase({
getGame: () => ({
getExtensionProperty: () => JSON.stringify(firebaseConfig),
}),

View File

@@ -1294,9 +1294,8 @@ module.exports = {
tilemapJsonFile,
tilesetJsonFile
) {
let tileMapJsonData = null;
try {
tileMapJsonData = await this._pixiResourcesLoader.getResourceJsonData(
const tileMapJsonData = await this._pixiResourcesLoader.getResourceJsonData(
this._project,
tilemapJsonFile
);
@@ -1320,6 +1319,7 @@ module.exports = {
} catch (err) {
console.error('Unable to load a Tilemap JSON data: ', err);
}
return null;
};
/**
@@ -1540,34 +1540,38 @@ module.exports = {
// GDJS doesn't use Promise to avoid allocation.
RenderedCollisionMaskInstance.prototype._loadTiledMapWithCallback =
function (tilemapJsonFile, tilesetJsonFile, callback) {
this._loadTiledMap(tilemapJsonFile, tilesetJsonFile).then(callback);
this._loadTileMap(tilemapJsonFile, tilesetJsonFile).then(callback);
};
RenderedCollisionMaskInstance.prototype._loadTiledMap = async function (
RenderedCollisionMaskInstance.prototype._loadTileMap = async function (
tilemapJsonFile,
tilesetJsonFile
) {
let tileMapJsonData = null;
try {
tileMapJsonData = await this._pixiResourcesLoader.getResourceJsonData(
const tileMapJsonData = await this._pixiResourcesLoader.getResourceJsonData(
this._project,
tilemapJsonFile
);
const tilesetJsonData = tilesetJsonFile
? await this._pixiResourcesLoader.getResourceJsonData(
this._project,
tilesetJsonFile
)
: null;
const tileMap = TilemapHelper.TileMapManager.identify(tileMapJsonData);
if (tilesetJsonData) {
tileMapJsonData.tilesets = [tilesetJsonData];
if (tileMap.kind === 'tiled') {
const tilesetJsonData = tilesetJsonFile
? await this._pixiResourcesLoader.getResourceJsonData(
this._project,
tilesetJsonFile
)
: null;
if (tilesetJsonData) {
tileMapJsonData.tilesets = [tilesetJsonData];
}
}
return tileMap;
} catch (err) {
console.error('Unable to load a Tilemap JSON data: ', err);
}
return tileMapJsonData;
return null;
};
/**

View File

@@ -67,7 +67,9 @@ namespace gdjs {
tileMapJsonResourceName: string,
tileSetJsonResourceName: string,
levelIndex: number,
callback: (tileMap: TileMapHelper.EditableTileMap | null) => void
callback: (
tileMapFileContent: TileMapHelper.EditableTileMap | null
) => void
): void {
this._manager.getOrLoadTileMap(
this._loadTileMap.bind(this),
@@ -113,7 +115,9 @@ namespace gdjs {
private _loadTileMap(
tileMapJsonResourceName: string,
tileSetJsonResourceName: string,
callback: (tileMap: TileMapHelper.TileMap | null) => void
callback: (
tileMapFileContent: TileMapHelper.TileMapFileContent | null
) => void
): void {
this._instanceContainer
.getGame()
@@ -127,14 +131,17 @@ namespace gdjs {
callback(null);
return;
}
const tileMap = TileMapHelper.TileMapManager.identify(
const tileMapFileContent = TileMapHelper.TileMapManager.identify(
tileMapJsonData
);
if (!tileMap) {
if (!tileMapFileContent) {
callback(null);
return;
}
if (tileMap.kind === 'tiled' && tileSetJsonResourceName) {
if (
tileMapFileContent.kind === 'tiled' &&
tileSetJsonResourceName
) {
this._instanceContainer
.getGame()
.getJsonManager()
@@ -147,14 +154,14 @@ namespace gdjs {
callback(null);
return;
}
const tiledMap = tileMap.data;
const tiledMap = tileMapFileContent.data;
const tileSet = tileSetJsonData as TileMapHelper.TiledTileset;
tileSet.firstgid = tiledMap.tilesets[0].firstgid;
tiledMap.tilesets = [tileSet];
callback(tileMap);
callback(tileMapFileContent);
});
} else {
callback(tileMap);
callback(tileMapFileContent);
}
});
}

View File

@@ -3,7 +3,7 @@ import {
EditableTileMapLayer,
PixiTileMapHelper,
TileDefinition,
TileMap,
TileMapFileContent,
TileMapManager,
TileTextureCache,
TiledTileset,
@@ -16,7 +16,7 @@ declare global {
EditableTileMapLayer,
PixiTileMapHelper,
TileDefinition,
TileMap,
TileMapFileContent,
TileMapManager,
TileTextureCache,
TiledTileset,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -10,7 +10,7 @@ export {
export { TileMapManager } from './render/TileMapManager';
export { TileTextureCache } from './render/TileTextureCache';
export { PixiTileMapHelper } from './render/TileMapPixiHelper';
export * from './types/index';
export * from './load/TileMapFileContent';
export * from './model/CommonTypes';
export { TiledTileset } from './load/tiled/TiledFormat';
//# sourceMappingURL=index.d.ts.map

View File

@@ -1 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,cAAc,GACf,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAE/D,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC"}
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,cAAc,GACf,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAE/D,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC"}

View File

@@ -0,0 +1,12 @@
import { LDtkTileMap } from '../load/ldtk/LDtkFormat';
import { TiledTileMap } from '../load/tiled/TiledFormat';
export declare type TileMapFileContent =
| {
kind: 'tiled';
data: TiledTileMap;
}
| {
kind: 'ldtk';
data: LDtkTileMap;
};
//# sourceMappingURL=TileMapFileContent.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"TileMapFileContent.d.ts","sourceRoot":"","sources":["../../src/load/TileMapFileContent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,oBAAY,kBAAkB,GAC1B;IACE,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,YAAY,CAAC;CACpB,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC"}

View File

@@ -1,16 +1,16 @@
import type { EditableTileMap } from '../model/TileMapModel';
import { TileMap } from '../types';
import { TileMapFileContent } from './TileMapFileContent';
export declare namespace TileMapLoader {
/**
* Create a {@link EditableTileMap} from the raw data.
*
* @param tiledMap The data exported from Tiled/LDtk.
* @param tileMapFileContent The data exported from Tiled/LDtk.
* @param levelIndex The level of the tile map to load from.
* @param pako The zlib library.
* @returns A {@link EditableTileMap}
*/
function load(
tileMap: TileMap,
tileMapFileContent: TileMapFileContent,
levelIndex: number,
pako: any
): EditableTileMap | null;

View File

@@ -1 +1 @@
{"version":3,"file":"TileMapLoader.d.ts","sourceRoot":"","sources":["../../src/load/TileMapLoader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAInC,yBAAiB,aAAa,CAAC;IAC7B;;;;;;;OAOG;IACH,SAAgB,IAAI,CAClB,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,GAAG,GACR,eAAe,GAAG,IAAI,CAaxB;CACF"}
{"version":3,"file":"TileMapLoader.d.ts","sourceRoot":"","sources":["../../src/load/TileMapLoader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAI1D,yBAAiB,aAAa,CAAC;IAC7B;;;;;;;OAOG;IACH,SAAgB,IAAI,CAClB,kBAAkB,EAAE,kBAAkB,EACtC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,GAAG,GACR,eAAe,GAAG,IAAI,CAaxB;CACF"}

View File

@@ -2,7 +2,7 @@ import { integer } from '../../model/CommonTypes';
/**
* version 1.1.3 - https://github.com/deepnight/ldtk/blob/66fff7199932357f3ab9b044c2fc2a856f527831/docs/JSON_SCHEMA.json
*/
export type LDtkTileMap = {
export declare type LDtkTileMap = {
/** LDtk application build identifier.<br/> This is only used to identify the LDtk version that generated this particular project file, which can be useful for specific bug fixing. Note that the build identifier is just the date of the release, so it's not unique to each user (one single global ID per LDtk public release), and as a result, completely anonymous. */
appBuildId: number;
/** Number of backup files to keep, if the `backupOnSave` is TRUE */
@@ -72,7 +72,7 @@ export type LDtkTileMap = {
| null;
};
/** Auto-layer rule group */
type LDtkAutoLayerRuleGroup = {
declare type LDtkAutoLayerRuleGroup = {
/** */
active: boolean;
/** *This field was removed in 1.0.0 and should no longer be used.* */
@@ -87,9 +87,9 @@ type LDtkAutoLayerRuleGroup = {
uid: integer;
};
/** This complex section isn't meant to be used by game devs at all, as these rules are completely resolved internally by the editor before any saving. You should just ignore this part. */
type LDtkAutoRuleDef = {};
declare type LDtkAutoRuleDef = {};
/** If you're writing your own LDtk importer, you should probably just ignore *most* stuff in the `defs` section, as it contains data that are mostly important to the editor. To keep you away from the `defs` section and avoid some unnecessary JSON parsing, important data from definitions is often duplicated in fields prefixed with a double underscore (eg. `__identifier` or `__type`). The 2 only definition types you might need here are **Tilesets** and **Enums**. */
type LDtkDefinition = {
declare type LDtkDefinition = {
/** All entities definitions, including their custom fields */
entities: LDtkEntityDef[];
/** All internal enums */
@@ -104,7 +104,7 @@ type LDtkDefinition = {
tilesets: LDtkTilesetDef[];
};
/** Entity definition */
type LDtkEntityDef = {
declare type LDtkEntityDef = {
/** Base entity color */
color: string;
/** Array of field definitions */
@@ -166,7 +166,7 @@ type LDtkEntityDef = {
width: integer;
};
/** Entity instance */
type LDtkEntityInstance = {
declare type LDtkEntityInstance = {
/** Grid-based coordinates (`[x,y]` format) */
__grid: integer[];
/** Entity definition identifier */
@@ -193,7 +193,7 @@ type LDtkEntityInstance = {
width: integer;
};
/** Enum definition */
type LDtkEnumDef = {
declare type LDtkEnumDef = {
/** */
externalFileChecksum: string | null;
/** Relative path to the external file providing this Enum */
@@ -210,7 +210,7 @@ type LDtkEnumDef = {
values: LDtkEnumDefValues[];
};
/** Enum value definition */
type LDtkEnumDefValues = {
declare type LDtkEnumDefValues = {
/** An array of 4 Int values that refers to the tile in the tileset image: `[ x, y, width, height ]` */
__tileSrcRect: integer[] | null;
/** Optional color */
@@ -221,14 +221,14 @@ type LDtkEnumDefValues = {
tileId: integer | null;
};
/** In a tileset definition, enum based tag infos */
type LDtkEnumTagValue = {
declare type LDtkEnumTagValue = {
/** */
enumValueId: string;
/** */
tileIds: integer[];
};
/** This section is mostly only intended for the LDtk editor app itself. You can safely ignore it. */
type LDtkFieldDef = {
declare type LDtkFieldDef = {
/** Human readable value type. Possible values: `Int, Float, String, Bool, Color, ExternEnum.XXX, LocalEnum.XXX, Point, FilePath`.<br/> If the field is an array, this field will look like `Array<...>` (eg. `Array<Int>`, `Array<Point>` etc.)<br/> NOTE: if you enable the advanced option **Use Multilines type**, you will have \"*Multilines*\" instead of \"*String*\" when relevant. */
__type: string;
/** Optional list of accepted file extensions for FilePath value type. Includes the dot: `.ext` */
@@ -310,7 +310,7 @@ type LDtkFieldDef = {
useForSmartColor: boolean;
};
/** Field instance */
type LDtkFieldInstance = {
declare type LDtkFieldInstance = {
/** Field definition identifier */
__identifier: string;
/** Optional TilesetRect used to display this field (this can be the field own Tile, or some other Tile guessed from the value, like an Enum). */
@@ -324,7 +324,7 @@ type LDtkFieldInstance = {
/** Editor internal raw values */
realEditorValues: any[];
};
type LDtkFlag =
declare type LDtkFlag =
| 'DiscardPreCsvIntGrid'
| 'ExportPreCsvIntGridFormat'
| 'IgnoreBackupSuggest'
@@ -332,7 +332,7 @@ type LDtkFlag =
| 'MultiWorlds'
| 'UseMultilinesType';
/** IntGrid value definition */
type LDtkIntGridValueDef = {
declare type LDtkIntGridValueDef = {
/** */
color: string;
/** User defined unique identifier */
@@ -341,14 +341,14 @@ type LDtkIntGridValueDef = {
value: integer;
};
/** IntGrid value instance */
type LDtkIntGridValueInstance = {
declare type LDtkIntGridValueInstance = {
/** Coordinate ID in the layer grid */
coordId: integer;
/** IntGrid value */
v: integer;
};
/** Layer definition */
type LDtkLayerDef = {
declare type LDtkLayerDef = {
/** Type of the layer (*IntGrid, Entities, Tiles or AutoLayer*) */
__type: string;
/** Contains all the auto-layer rule definitions. */
@@ -401,7 +401,7 @@ type LDtkLayerDef = {
uid: integer;
};
/** Layer instance */
type LDtkLayerInstance = {
declare type LDtkLayerInstance = {
/** Grid-based height */
__cHei: integer;
/** Grid-based width */
@@ -452,7 +452,7 @@ type LDtkLayerInstance = {
visible: boolean;
};
/** This section contains all the level data. It can be found in 2 distinct forms, depending on Project current settings: - If \"*Separate level files*\" is **disabled** (default): full level data is *embedded* inside the main Project JSON file, - If \"*Separate level files*\" is **enabled**: level data is stored in *separate* standalone `.ldtkl` files (one per level). In this case, the main Project JSON file will still contain most level data, except heavy sections, like the `layerInstances` array (which will be null). The `externalRelPath` string points to the `ldtkl` file. A `ldtkl` file is just a JSON file containing exactly what is described below. */
type LDtkLevel = {
declare type LDtkLevel = {
/** Background color of the level (same as `bgColor`, except the default value is automatically used here if its value is `null`) */
__bgColor: string;
/** Position informations of the background image, if there is one. */
@@ -497,7 +497,7 @@ type LDtkLevel = {
worldY: integer;
};
/** Level background image position info */
type LDtkLevelBgPosInfos = {
declare type LDtkLevelBgPosInfos = {
/** An array of 4 float values describing the cropped sub-rectangle of the displayed background image. This cropping happens when original is larger than the level bounds. Array format: `[ cropX, cropY, cropWidth, cropHeight ]` */
cropRect: number[];
/** An array containing the `[scaleX,scaleY]` values of the **cropped** background image, depending on `bgPos` option. */
@@ -506,7 +506,7 @@ type LDtkLevelBgPosInfos = {
topLeftPx: integer[];
};
/** Nearby level info */
type LDtkNeighbourLevel = {
declare type LDtkNeighbourLevel = {
/** A single lowercase character tipping on the level location (`n`orth, `s`outh, `w`est, `e`ast). */
dir: string;
/** Neighbour Instance Identifier */
@@ -515,7 +515,7 @@ type LDtkNeighbourLevel = {
levelUid: integer;
};
/** This structure represents a single tile from a given Tileset. */
type LDtkTile = {
declare type LDtkTile = {
/** Internal data used by the editor.<br/> For auto-layer tiles: `[ruleId, coordId]`.<br/> For tile-layer tiles: `[coordId]`. */
d: integer[];
/** \"Flip bits\", a 2-bits integer to represent the mirror transformations of the tile.<br/> - Bit 0 = X flip<br/> - Bit 1 = Y flip<br/> Examples: f=0 (no flip), f=1 (X flip only), f=2 (Y flip only), f=3 (both flips) */
@@ -528,7 +528,7 @@ type LDtkTile = {
t: integer;
};
/** The `Tileset` definition is the most important part among project definitions. It contains some extra informations about each integrated tileset. If you only had to parse one definition section, that would be the one. */
export type LDtkTilesetDef = {
export declare type LDtkTilesetDef = {
/** Grid-based height */
__cHei: integer;
/** Grid-based width */
@@ -565,14 +565,14 @@ export type LDtkTilesetDef = {
uid: integer;
};
/** In a tileset definition, user defined meta-data of a tile. */
type LDtkTileCustomMetadata = {
declare type LDtkTileCustomMetadata = {
/** */
data: string;
/** */
tileId: integer;
};
/** This object represents a custom sub rectangle in a Tileset image. */
type LDtkTilesetRect = {
declare type LDtkTilesetRect = {
/** Height in pixels */
h: integer;
/** UID of the tileset */
@@ -584,6 +584,6 @@ type LDtkTilesetRect = {
/** Y pixels coordinate of the top-left corner in the Tileset image */
y: integer;
};
type LDtkWorld = {};
declare type LDtkWorld = {};
export {};
//# sourceMappingURL=LDtkFormat.d.ts.map

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@ import { float, integer } from '../../model/CommonTypes';
/**
* Tiled JSON format (https://github.com/mapeditor/tiled/blob/master/docs/reference/json-map-format.rst).
*/
export type TiledTileMap = {
export declare type TiledTileMap = {
/** Hex-formatted color (#RRGGBB or #AARRGGBB) (optional) */
backgroundcolor?: string;
/** The compression level to use for tile layer data (defaults to -1, which means to use the algorithm default) */
@@ -44,7 +44,7 @@ export type TiledTileMap = {
/** Number of tile columns */
width: integer;
};
export type TiledLayer = {
export declare type TiledLayer = {
/** Array of {@link TiledChunk} (optional). `tilelayer` only. */
chunks?: Array<TiledChunk>;
/** `zlib`, `gzip`, `zstd` (since Tiled 1.3) or empty (default). `tilelayer` only. */
@@ -98,7 +98,7 @@ export type TiledLayer = {
/** Vertical layer offset in tiles. Always 0. */
y: integer;
};
export type TiledChunk = {
export declare type TiledChunk = {
/** Array of `unsigned` `integer` (GIDs) or base64-encoded data */
data: Array<integer> | string;
/** Height in tiles */
@@ -110,7 +110,7 @@ export type TiledChunk = {
/** Y coordinate in tiles */
y: integer;
};
export type TiledObject = {
export declare type TiledObject = {
/** The class of the object (renamed from type since 1.9, optional) */
class?: string;
/** Used to mark an object as an ellipse */
@@ -146,7 +146,7 @@ export type TiledObject = {
/** Y coordinate in pixels */
y: float;
};
export type TiledText = {
export declare type TiledText = {
/** Whether to use a bold font (default: `false`) */
bold: boolean;
/** Hex-formatted color (#RRGGBB or #AARRGGBB) (default: `#000000`) */
@@ -172,7 +172,7 @@ export type TiledText = {
/** Whether the text is wrapped within the object bounds (default: `false`) */
wrap: boolean;
};
export type TiledTileset = {
export declare type TiledTileset = {
/** Hex-formatted color (#RRGGBB or #AARRGGBB) (optional) */
backgroundcolor?: string;
/** The number of tile columns in the tileset */
@@ -224,7 +224,7 @@ export type TiledTileset = {
/** Array of {@link TiledWangSet} (since 1.1.5) */
wangsets?: Array<TiledWangSet>;
};
export type TiledGrid = {
export declare type TiledGrid = {
/** Cell height of tile grid */
height: integer;
/** `orthogonal` (default) or `isometric` */
@@ -232,13 +232,13 @@ export type TiledGrid = {
/** Cell width of tile grid */
width: integer;
};
export type TileOffset = {
export declare type TileOffset = {
/** Horizontal offset in pixels */
x: integer;
/** Vertical offset in pixels (positive is down) */
y: integer;
};
export type TiledTransformations = {
export declare type TiledTransformations = {
/** Tiles can be flipped horizontally */
hflip: boolean;
/** Tiles can be flipped vertically */
@@ -248,7 +248,7 @@ export type TiledTransformations = {
/** Whether untransformed tiles remain preferred, otherwise transformed tiles are used to produce more variations */
preferuntransformed: boolean;
};
export type TiledTileDefinition = {
export declare type TiledTileDefinition = {
/** Array of {@link TiledTiles} */
animation?: Array<TiledTileDefinition>;
/** The class of the tile (renamed from type since 1.9, optional) */
@@ -270,13 +270,13 @@ export type TiledTileDefinition = {
/** Index of terrain for each corner of tile (optional) */
terrain?: Array<integer>;
};
export type TiledFrame = {
export declare type TiledFrame = {
/** Frame duration in milliseconds */
duration: integer;
/** Local tile ID representing this frame */
tileid: integer;
};
export type TiledTerrain = {
export declare type TiledTerrain = {
/** Name of terrain */
name: string;
/** Array of {@link TiledProperty} */
@@ -284,7 +284,7 @@ export type TiledTerrain = {
/** Local ID of tile representing terrain */
tile: integer;
};
export type TiledWangSet = {
export declare type TiledWangSet = {
/** Array of {@link TiledWangColor} */
colors: Array<TiledWangColor>;
/** Name of the Wang set */
@@ -296,7 +296,7 @@ export type TiledWangSet = {
/** Array of {@link TiledWangTile} */
wangtiles: Array<TiledWangTile>;
};
export type TiledWangColor = {
export declare type TiledWangColor = {
/** Hex-formatted color (#RRGGBB or #AARRGGBB) */
color: string;
/** Name of the Wang color */
@@ -308,13 +308,13 @@ export type TiledWangColor = {
/** Local ID of tile representing the Wang color */
tile: integer;
};
export type TiledWangTile = {
export declare type TiledWangTile = {
/** Local ID of tile */
tileid: integer;
/** Array of Wang color indexes (`uchar[8]`) */
wangid: Array<integer>;
};
export type TiledObjectTemplate = {
export declare type TiledObjectTemplate = {
/** `template` */
type: string;
/** External tileset used by the template (optional) */
@@ -322,7 +322,7 @@ export type TiledObjectTemplate = {
/** The object instantiated by this template */
object: Object;
};
export type TiledProperty = {
export declare type TiledProperty = {
/** Name of the property */
name: string;
/** type of the property (`string` (default), `integer`, `float`, `boolean`, `color` or `file` (since 0.16, with `color` and `file` added in 0.17)) */
@@ -330,7 +330,7 @@ export type TiledProperty = {
/** Value of the property */
value: string | number;
};
export type TiledPoint = {
export declare type TiledPoint = {
/** X coordinate in pixels */
x: float;
/** Y coordinate in pixels */

File diff suppressed because one or more lines are too long

View File

@@ -12,7 +12,7 @@ export declare const decodeBase64LayerData: (
pako: any,
tiledLayer: TiledLayer
) => number[];
export type TiledGID = {
export declare type TiledGID = {
id: integer;
flippedHorizontally: boolean;
flippedVertically: boolean;

View File

@@ -1 +1 @@
{"version":3,"file":"TiledTileMapLoaderHelper.d.ts","sourceRoot":"","sources":["../../../src/load/tiled/TiledTileMapLoaderHelper.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,SAAU,GAAG,cAAc,UAAU,aAgDtE,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,EAAE,EAAE,OAAO,CAAC;IACZ,mBAAmB,EAAE,OAAO,CAAC;IAC7B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,iBAAiB,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,kBACvB,OAAO,KACrB,QAmBF,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,GAAG,SAAS,CAEpB"}
{"version":3,"file":"TiledTileMapLoaderHelper.d.ts","sourceRoot":"","sources":["../../../src/load/tiled/TiledTileMapLoaderHelper.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,SAAU,GAAG,cAAc,UAAU,aAgDtE,CAAC;AAEF,oBAAY,QAAQ,GAAG;IACrB,EAAE,EAAE,OAAO,CAAC;IACZ,mBAAmB,EAAE,OAAO,CAAC;IAC7B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,iBAAiB,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,kBACvB,OAAO,KACrB,QAmBF,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,GAAG,SAAS,CAEpB"}

View File

@@ -1,5 +1,5 @@
export declare type integer = number;
export declare type float = number;
export type FloatPoint = [float, float];
export type PolygonVertices = FloatPoint[];
export declare type FloatPoint = [float, float];
export declare type PolygonVertices = FloatPoint[];
//# sourceMappingURL=CommonTypes.d.ts.map

View File

@@ -1 +1 @@
{"version":3,"file":"CommonTypes.d.ts","sourceRoot":"","sources":["../../src/model/CommonTypes.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,MAAM,OAAO,GAAG,MAAM,CAAC;AACrC,MAAM,CAAC,OAAO,MAAM,KAAK,GAAG,MAAM,CAAC;AACnC,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAExC,MAAM,MAAM,eAAe,GAAG,UAAU,EAAE,CAAC"}
{"version":3,"file":"CommonTypes.d.ts","sourceRoot":"","sources":["../../src/model/CommonTypes.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,MAAM,OAAO,GAAG,MAAM,CAAC;AACrC,MAAM,CAAC,OAAO,MAAM,KAAK,GAAG,MAAM,CAAC;AACnC,oBAAY,UAAU,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAExC,oBAAY,eAAe,GAAG,UAAU,EAAE,CAAC"}

View File

@@ -1,7 +1,7 @@
import { EditableTileMap } from '../model/TileMapModel';
import { TileTextureCache } from './TileTextureCache';
import PIXI = GlobalPIXIModule.PIXI;
import { TileMap } from '../types';
import { TileMapFileContent } from '../load/TileMapFileContent';
/**
* A holder to share tile maps across the 2 extension objects.
*
@@ -23,7 +23,7 @@ export declare class TileMapManager {
* @param data JSON data.
* @returns The data enclosed with its detected kind.
*/
static identify(data: any): TileMap | null;
static identify(data: any): TileMapFileContent | null;
/**
* @param loadTileMap The method that loads the Tiled JSON file in memory.
* @param tileMapJsonResourceName The resource name of the tile map.
@@ -36,7 +36,7 @@ export declare class TileMapManager {
loadTileMap: (
tileMapJsonResourceName: string,
tileSetJsonResourceName: string,
callback: (tileMap: TileMap | null) => void
callback: (tileMapFileContent: TileMapFileContent | null) => void
) => void,
tileMapJsonResourceName: string,
tileSetJsonResourceName: string,
@@ -57,7 +57,7 @@ export declare class TileMapManager {
loadTileMap: (
tileMapJsonResourceName: string,
tileSetJsonResourceName: string,
callback: (tileMap: TileMap | null) => void
callback: (tileMapFileContent: TileMapFileContent | null) => void
) => void,
getTexture: (textureName: string) => PIXI.BaseTexture<PIXI.Resource>,
atlasImageResourceName: string,

View File

@@ -1 +1 @@
{"version":3,"file":"TileMapManager.d.ts","sourceRoot":"","sources":["../../src/render/TileMapManager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC;AAEpC,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC;;;;;;;GAOG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,mBAAmB,CAAkC;;IAO7D;;;OAGG;IACH,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,cAAc;IAWzD;;;OAGG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,GAAG,IAAI;IAyB1C;;;;;;;OAOG;IACH,gBAAgB,CACd,WAAW,EAAE,CACX,uBAAuB,EAAE,MAAM,EAC/B,uBAAuB,EAAE,MAAM,EAC/B,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,KAAK,IAAI,KACxC,IAAI,EACT,uBAAuB,EAAE,MAAM,EAC/B,uBAAuB,EAAE,MAAM,EAC/B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,GAAG,EACT,QAAQ,EAAE,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,KAAK,IAAI,GAClD,IAAI;IAiCP;;;;;;;;OAQG;IACH,qBAAqB,CACnB,WAAW,EAAE,CACX,uBAAuB,EAAE,MAAM,EAC/B,uBAAuB,EAAE,MAAM,EAC/B,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,KAAK,IAAI,KACxC,IAAI,EACT,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,EACpE,sBAAsB,EAAE,MAAM,EAC9B,uBAAuB,EAAE,MAAM,EAC/B,uBAAuB,EAAE,MAAM,EAC/B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,CAAC,YAAY,EAAE,gBAAgB,GAAG,IAAI,KAAK,IAAI,GACxD,IAAI;CAuCR"}
{"version":3,"file":"TileMapManager.d.ts","sourceRoot":"","sources":["../../src/render/TileMapManager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC;AAEpC,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAEhE;;;;;;;GAOG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,mBAAmB,CAAkC;;IAO7D;;;OAGG;IACH,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,cAAc;IAWzD;;;OAGG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,GAAG,kBAAkB,GAAG,IAAI;IAwBrD;;;;;;;OAOG;IACH,gBAAgB,CACd,WAAW,EAAE,CACX,uBAAuB,EAAE,MAAM,EAC/B,uBAAuB,EAAE,MAAM,EAC/B,QAAQ,EAAE,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,KAC9D,IAAI,EACT,uBAAuB,EAAE,MAAM,EAC/B,uBAAuB,EAAE,MAAM,EAC/B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,GAAG,EACT,QAAQ,EAAE,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,KAAK,IAAI,GAClD,IAAI;IAiCP;;;;;;;;OAQG;IACH,qBAAqB,CACnB,WAAW,EAAE,CACX,uBAAuB,EAAE,MAAM,EAC/B,uBAAuB,EAAE,MAAM,EAC/B,QAAQ,EAAE,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,KAC9D,IAAI,EACT,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,EACpE,sBAAsB,EAAE,MAAM,EAC9B,uBAAuB,EAAE,MAAM,EAC/B,uBAAuB,EAAE,MAAM,EAC/B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,CAAC,YAAY,EAAE,gBAAgB,GAAG,IAAI,KAAK,IAAI,GACxD,IAAI;CAuCR"}

View File

@@ -1,6 +1,6 @@
import { integer, float } from '../model/CommonTypes';
import { EditableTileMap } from '../model/TileMapModel';
import { TileMap } from '../types';
import { TileMapFileContent } from '../load/TileMapFileContent';
import { TileTextureCache } from './TileTextureCache';
import PIXI = GlobalPIXIModule.PIXI;
export declare namespace PixiTileMapHelper {
@@ -14,7 +14,7 @@ export declare namespace PixiTileMapHelper {
* @returns A textures cache.
*/
function parseAtlas(
tileMap: TileMap,
tileMap: TileMapFileContent,
levelIndex: number,
atlasTexture: PIXI.BaseTexture<PIXI.Resource> | null,
getTexture: (textureName: string) => PIXI.BaseTexture<PIXI.Resource>

View File

@@ -1 +1 @@
{"version":3,"file":"TileMapPixiHelper.d.ts","sourceRoot":"","sources":["../../src/render/TileMapPixiHelper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAEL,eAAe,EAEhB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC;AAEpC,yBAAiB,iBAAiB,CAAC;IACjC;;;;;;;;OAQG;IACH,SAAgB,UAAU,CACxB,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,EACpD,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,GACnE,gBAAgB,GAAG,IAAI,CAuBzB;IAED;;;;;;;;;;;;OAYG;IACH,SAAgB,iBAAiB,CAC/B,kBAAkB,EAAE,GAAG,EACvB,OAAO,EAAE,eAAe,EACxB,YAAY,EAAE,gBAAgB,EAC9B,WAAW,EAAE,OAAO,GAAG,SAAS,GAAG,KAAK,EACxC,UAAU,EAAE,MAAM,GACjB,IAAI,CA0GN;IAED;;OAEG;IACH,SAAgB,uBAAuB,CACrC,YAAY,EAAE,IAAI,CAAC,QAAQ,EAC3B,OAAO,EAAE,eAAe,EACxB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,OAAO,EACpB,YAAY,EAAE,OAAO,EACrB,cAAc,EAAE,KAAK,EACrB,SAAS,EAAE,OAAO,EAClB,WAAW,EAAE,KAAK,GACjB,IAAI,CAgEN;CACF"}
{"version":3,"file":"TileMapPixiHelper.d.ts","sourceRoot":"","sources":["../../src/render/TileMapPixiHelper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAEL,eAAe,EAEhB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC;AAEpC,yBAAiB,iBAAiB,CAAC;IACjC;;;;;;;;OAQG;IACH,SAAgB,UAAU,CACxB,OAAO,EAAE,kBAAkB,EAC3B,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,EACpD,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,GACnE,gBAAgB,GAAG,IAAI,CAuBzB;IAED;;;;;;;;;;;;OAYG;IACH,SAAgB,iBAAiB,CAC/B,kBAAkB,EAAE,GAAG,EACvB,OAAO,EAAE,eAAe,EACxB,YAAY,EAAE,gBAAgB,EAC9B,WAAW,EAAE,OAAO,GAAG,SAAS,GAAG,KAAK,EACxC,UAAU,EAAE,MAAM,GACjB,IAAI,CA0GN;IAED;;OAEG;IACH,SAAgB,uBAAuB,CACrC,YAAY,EAAE,IAAI,CAAC,QAAQ,EAC3B,OAAO,EAAE,eAAe,EACxB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,OAAO,EACpB,YAAY,EAAE,OAAO,EACrB,cAAc,EAAE,KAAK,EACrB,SAAS,EAAE,OAAO,EAClB,WAAW,EAAE,KAAK,GACjB,IAAI,CAgEN;CACF"}

View File

@@ -1,8 +1,10 @@
import { TileTextureCache } from '../TileTextureCache';
import { LDtkTileMap } from '../../load/ldtk/LDtkFormat';
import PIXI = GlobalPIXIModule.PIXI;
type Texture = PIXI.BaseTexture<PIXI.Resource>;
type TextureLoader = (textureName: string) => PIXI.BaseTexture<PIXI.Resource>;
declare type Texture = PIXI.BaseTexture<PIXI.Resource>;
declare type TextureLoader = (
textureName: string
) => PIXI.BaseTexture<PIXI.Resource>;
export declare namespace LDtkPixiHelper {
/**
* Split an atlas image into Pixi textures.

View File

@@ -1 +1 @@
{"version":3,"file":"LDtkPixiHelper.d.ts","sourceRoot":"","sources":["../../../src/render/ldtk/LDtkPixiHelper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAkB,MAAM,4BAA4B,CAAC;AAEzE,OAAO,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC;AAEpC,KAAK,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC/C,KAAK,aAAa,GAAG,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAmC9E,yBAAiB,cAAc,CAAC;IAC9B;;;;;;;;OAQG;IACH,SAAgB,UAAU,CACxB,OAAO,EAAE,WAAW,EACpB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,OAAO,GAAG,IAAI,EAC5B,UAAU,EAAE,aAAa,GACxB,gBAAgB,GAAG,IAAI,CAoFzB;CACF"}
{"version":3,"file":"LDtkPixiHelper.d.ts","sourceRoot":"","sources":["../../../src/render/ldtk/LDtkPixiHelper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAkB,MAAM,4BAA4B,CAAC;AAEzE,OAAO,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC;AAEpC,aAAK,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC/C,aAAK,aAAa,GAAG,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAmC9E,yBAAiB,cAAc,CAAC;IAC9B;;;;;;;;OAQG;IACH,SAAgB,UAAU,CACxB,OAAO,EAAE,WAAW,EACpB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,OAAO,GAAG,IAAI,EAC5B,UAAU,EAAE,aAAa,GACxB,gBAAgB,GAAG,IAAI,CAoFzB;CACF"}

View File

@@ -1,6 +1,6 @@
import { LDtkTileMap } from '../load/ldtk/LDtkFormat';
import { TiledTileMap } from '../load/tiled/TiledFormat';
export type TileMap =
export declare type TileMap =
| {
kind: 'tiled';
data: TiledTileMap;

View File

@@ -1 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,MAAM,MAAM,OAAO,GACf;IACE,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,YAAY,CAAC;CACpB,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC"}
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,oBAAY,OAAO,GACf;IACE,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,YAAY,CAAC;CACpB,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC"}

View File

@@ -164,7 +164,7 @@ BaseObjectExtension::BaseObjectExtension() {
objectConditions["CollisionPoint"]
.SetFunctionName("isCollidingWithPoint")
.SetIncludeFile("runtimeobject.js");
objectConditions["ObjectTimer"] // deprecated
objectConditions["ObjectTimer"] // deprecated
.SetFunctionName("timerElapsedTime")
.SetIncludeFile("runtimeobject.js");
objectConditions["CompareObjectTimer"]
@@ -223,6 +223,18 @@ BaseObjectExtension::BaseObjectExtension() {
objectExpressions["VariableChildCount"]
.SetFunctionName("gdjs.RuntimeObject.getVariableChildCount")
.SetStatic();
objectExpressions["ArrayVariableFirstNumber"]
.SetFunctionName("gdjs.RuntimeObject.getFirstVariableNumber")
.SetStatic();
objectStrExpressions["ArrayVariableFirstString"]
.SetFunctionName("gdjs.RuntimeObject.getFirstVariableString")
.SetStatic();
objectExpressions["ArrayVariableLastNumber"]
.SetFunctionName("gdjs.RuntimeObject.getLastVariableNumber")
.SetStatic();
objectStrExpressions["ArrayVariableLastString"]
.SetFunctionName("gdjs.RuntimeObject.getLastVariableString")
.SetStatic();
objectExpressions["ForceX"].SetFunctionName("getAverageForce().getX");
objectExpressions["ForceY"].SetFunctionName("getAverageForce().getY");
objectExpressions["ForceAngle"].SetFunctionName("getAverageForce().getAngle");
@@ -254,9 +266,9 @@ BaseObjectExtension::BaseObjectExtension() {
"gdjs.evtTools.object.createObjectFromGroupOnScene");
GetAllExpressions()["Count"].SetFunctionName(
"gdjs.evtTools.object.pickedObjectsCount"); // Deprecated
"gdjs.evtTools.object.pickedObjectsCount"); // Deprecated
GetAllConditions()["NbObjet"].SetFunctionName(
"gdjs.evtTools.object.pickedObjectsCount"); // Deprecated
"gdjs.evtTools.object.pickedObjectsCount"); // Deprecated
GetAllExpressions()["SceneInstancesCount"].SetFunctionName(
"gdjs.evtTools.object.getSceneInstancesCount");
@@ -335,6 +347,9 @@ BaseObjectExtension::BaseObjectExtension() {
objectActions["ObjectVariableRemoveAt"]
.SetFunctionName("variableRemoveAt")
.SetIncludeFile("runtimeobject.js");
objectConditions["ObjectVariableChildCount"]
.SetFunctionName("getVariableChildCount")
.SetIncludeFile("runtimeobject.js");
GetAllActions()["MoveObjects"].codeExtraInformation.SetCustomCodeGenerator(
[](gd::Instruction &,

View File

@@ -34,23 +34,46 @@ VariablesExtension::VariablesExtension() {
"gdjs.evtTools.variable.getVariableString");
GetAllConditions()["GlobalVariableAsBoolean"].SetFunctionName(
"gdjs.evtTools.variable.getVariableBoolean");
GetAllExpressions()["Variable"].SetFunctionName(
"gdjs.evtTools.variable.getVariableNumber");
GetAllStrExpressions()["VariableString"].SetFunctionName(
"gdjs.evtTools.variable.getVariableString");
GetAllExpressions()["VariableChildCount"].SetFunctionName(
"gdjs.evtTools.variable.getVariableChildCount");
GetAllExpressions()["GlobalVariableChildCount"].SetFunctionName(
"gdjs.evtTools.variable.getVariableChildCount");
GetAllExpressions()["Variable"].SetFunctionName(
"gdjs.evtTools.variable.getVariableNumber");
GetAllStrExpressions()["VariableString"].SetFunctionName(
"gdjs.evtTools.variable.getVariableString");
GetAllExpressions()["GlobalVariable"].SetFunctionName(
"gdjs.evtTools.variable.getVariableNumber");
GetAllStrExpressions()["GlobalVariableString"].SetFunctionName(
"gdjs.evtTools.variable.getVariableString");
GetAllStrExpressions()["GlobalVariableFirstString"].SetFunctionName(
"gdjs.evtTools.variable.getFirstVariableString");
GetAllExpressions()["GlobalVariableFirstNumber"].SetFunctionName(
"gdjs.evtTools.variable.getFirstVariableNumber");
GetAllStrExpressions()["GlobalVariableLastString"].SetFunctionName(
"gdjs.evtTools.variable.getLastVariableString");
GetAllExpressions()["GlobalVariableLastNumber"].SetFunctionName(
"gdjs.evtTools.variable.getLastVariableNumber");
GetAllStrExpressions()["SceneVariableFirstString"].SetFunctionName(
"gdjs.evtTools.variable.getFirstVariableString");
GetAllExpressions()["SceneVariableFirstNumber"].SetFunctionName(
"gdjs.evtTools.variable.getFirstVariableNumber");
GetAllStrExpressions()["SceneVariableLastString"].SetFunctionName(
"gdjs.evtTools.variable.getLastVariableString");
GetAllExpressions()["SceneVariableLastNumber"].SetFunctionName(
"gdjs.evtTools.variable.getLastVariableNumber");
GetAllConditions()["VarSceneDef"].SetFunctionName(
"gdjs.evtTools.variable.sceneVariableExists");
GetAllConditions()["VarGlobalDef"].SetFunctionName(
"gdjs.evtTools.variable.globalVariableExists");
GetAllConditions()["SceneVariableChildCount"].SetFunctionName(
"gdjs.evtTools.variable.getVariableChildCount");
GetAllConditions()["GlobalVariableChildCount"].SetFunctionName(
"gdjs.evtTools.variable.getVariableChildCount");
GetAllActions()["SetSceneVariableAsBoolean"].SetFunctionName(
"gdjs.evtTools.variable.setVariableBoolean");

View File

@@ -170,6 +170,54 @@ namespace gdjs {
): number {
return variable.getChildrenCount();
};
/**
* Shortcut to get the first value of an array variable as a number.
*/
export const getFirstVariableNumber = function (
array: gdjs.Variable
): number {
if (array.getChildrenCount() === 0) {
return 0;
}
return array.getAllChildrenArray()[0].getAsNumber();
};
/**
* Shortcut to get the last value of an array variable as a string.
*/
export const getFirstVariableString = function (
array: gdjs.Variable
): string {
if (array.getChildrenCount() === 0) {
return '';
}
return array.getAllChildrenArray()[0].getAsString();
};
/**
* Shortcut to get the last value of an array variable as a number.
*/
export const getLastVariableNumber = function (
array: gdjs.Variable
): number {
const children = array.getAllChildrenArray();
return children.length === 0
? 0
: children[children.length - 1].getAsNumber();
};
/**
* Shortcut to get the last value of an array variable as a string.
*/
export const getLastVariableString = function (
array: gdjs.Variable
): string {
const children = array.getAllChildrenArray();
return children.length === 0
? ''
: children[children.length - 1].getAsString();
};
}
export namespace common {

View File

@@ -940,6 +940,46 @@ namespace gdjs {
array.removeAtIndex(index);
};
/**
* Shortcut to get the first value of an array variable as a string.
*/
static getFirstVariableString = function (array: gdjs.Variable): string {
if (array.getChildrenCount() === 0) {
return '';
}
return array.getAllChildrenArray()[0].getAsString();
};
/**
* Shortcut to get the first value of an array variable as a number.
*/
static getFirstVariableNumber = function (array: gdjs.Variable): number {
if (array.getChildrenCount() === 0) {
return 0;
}
return array.getAllChildrenArray()[0].getAsNumber();
};
/**
* Shortcut to get the last value of an array variable as a string.
*/
static getLastVariableString = function (array: gdjs.Variable): string {
const children = array.getAllChildrenArray();
return children.length === 0
? ''
: children[children.length - 1].getAsString();
};
/**
* Shortcut to get the last value of an array variable as a number.
*/
static getLastVariableNumber = function (array: gdjs.Variable): number {
const children = array.getAllChildrenArray();
return children.length === 0
? 0
: children[children.length - 1].getAsNumber();
};
/**
* Shortcut to test if a variable exists for the object.
* @param name The variable to be tested
@@ -2531,6 +2571,11 @@ namespace gdjs {
setVariableString = RuntimeObject.setVariableString;
getVariableBoolean = RuntimeObject.getVariableBoolean;
setVariableBoolean = RuntimeObject.setVariableBoolean;
getVariableChildCount = RuntimeObject.getVariableChildCount;
getFirstVariableNumber = RuntimeObject.getFirstVariableNumber;
getFirstVariableString = RuntimeObject.getFirstVariableString;
getLastVariableNumber = RuntimeObject.getLastVariableNumber;
getLastVariableString = RuntimeObject.getLastVariableString;
toggleVariableBoolean = RuntimeObject.toggleVariableBoolean;
variableChildExists = RuntimeObject.variableChildExists;
variableRemoveChild = RuntimeObject.variableRemoveChild;

View File

@@ -13,6 +13,6 @@ export { TileMapManager } from "./render/TileMapManager";
export { TileTextureCache } from "./render/TileTextureCache";
export { PixiTileMapHelper } from "./render/TileMapPixiHelper";
export * from "./types/index";
export * from "./load/TileMapFileContent";
export * from "./model/CommonTypes";
export { TiledTileset } from "./load/tiled/TiledFormat";

View File

@@ -1,7 +1,7 @@
import { LDtkTileMap } from "../load/ldtk/LDtkFormat";
import { TiledTileMap } from "../load/tiled/TiledFormat";
export type TileMap =
export type TileMapFileContent =
| {
kind: "tiled";
data: TiledTileMap;

View File

@@ -1,5 +1,5 @@
import type { EditableTileMap } from "../model/TileMapModel";
import { TileMap } from "../types";
import { TileMapFileContent } from "./TileMapFileContent";
import { LDtkTileMapLoader } from "./ldtk/LDtkTileMapLoader";
import { TiledTileMapLoader } from "./tiled/TiledTileMapLoader";
@@ -7,21 +7,21 @@ export namespace TileMapLoader {
/**
* Create a {@link EditableTileMap} from the raw data.
*
* @param tiledMap The data exported from Tiled/LDtk.
* @param tileMapFileContent The data exported from Tiled/LDtk.
* @param levelIndex The level of the tile map to load from.
* @param pako The zlib library.
* @returns A {@link EditableTileMap}
*/
export function load(
tileMap: TileMap,
tileMapFileContent: TileMapFileContent,
levelIndex: number,
pako: any
): EditableTileMap | null {
if (tileMap.kind === "ldtk") {
return LDtkTileMapLoader.load(tileMap.data, levelIndex);
if (tileMapFileContent.kind === "ldtk") {
return LDtkTileMapLoader.load(tileMapFileContent.data, levelIndex);
}
if (tileMap.kind === "tiled") {
return TiledTileMapLoader.load(tileMap.data, pako);
if (tileMapFileContent.kind === "tiled") {
return TiledTileMapLoader.load(tileMapFileContent.data, pako);
}
console.warn(

View File

@@ -1,8 +1,8 @@
import { integer } from '../../model/CommonTypes';
import { EditableTileMap, TileDefinition } from '../../model/TileMapModel';
import { getLDtkTileId } from './LDtkTileMapLoaderHelper';
import { LDtkTileMap } from './LDtkFormat';
import { getTileGID } from '../../model/GID';
import { integer } from "../../model/CommonTypes";
import { EditableTileMap, TileDefinition } from "../../model/TileMapModel";
import { getLDtkTileId } from "./LDtkTileMapLoaderHelper";
import { LDtkTileMap } from "./LDtkFormat";
import { getTileGID } from "../../model/GID";
export namespace LDtkTileMapLoader {
/**
@@ -54,9 +54,9 @@ export namespace LDtkTileMapLoader {
}
if (
layer.__type === 'IntGrid' ||
layer.__type === 'AutoLayer' ||
layer.__type === 'Tiles'
layer.__type === "IntGrid" ||
layer.__type === "AutoLayer" ||
layer.__type === "Tiles"
) {
if (gridSize === 0) {
gridSize = layer.__gridSize;
@@ -64,7 +64,7 @@ export namespace LDtkTileMapLoader {
dimY = layer.__cHei;
} else if (layer.__gridSize !== gridSize) {
console.warn(
'Grid size is different across layers. Only the first layer grid size will be followed.'
"Grid size is different across layers. Only the first layer grid size will be followed."
);
}
}
@@ -124,7 +124,7 @@ export namespace LDtkTileMapLoader {
const hash = `${oldTileDef
.getStackedTiles()
.map((tileId) => `${tileId}`)
.join(';')};${tileGID}`;
.join(";")};${tileGID}`;
const tileDef = composedTileMap.get(hash);
if (tileDef) {
editableTileLayer.setTile(x, y, tileDef.getStackTileId());

View File

@@ -5,7 +5,7 @@ import { PixiTileMapHelper } from "./TileMapPixiHelper";
import PIXI = GlobalPIXIModule.PIXI;
import { TileMapLoader } from "../load/TileMapLoader";
import { TileMap } from "../types";
import { TileMapFileContent } from "../load/TileMapFileContent";
/**
* A holder to share tile maps across the 2 extension objects.
@@ -43,7 +43,7 @@ export class TileMapManager {
* @param data JSON data.
* @returns The data enclosed with its detected kind.
*/
static identify(data: any): TileMap | null {
static identify(data: any): TileMapFileContent | null {
if (data.tiledversion) {
console.info("Detected the json file was created in Tiled");
return {
@@ -63,7 +63,6 @@ export class TileMapManager {
console.warn(
"The loaded Tile Map data does not contain a 'tiledversion' or '__header__' key. Are you sure this file has been exported from Tiled (mapeditor.org) or LDtk (ldtk.io)?"
);
console.log("The data that failed Tile Map identification is: ", data);
return null;
}
@@ -80,7 +79,7 @@ export class TileMapManager {
loadTileMap: (
tileMapJsonResourceName: string,
tileSetJsonResourceName: string,
callback: (tileMap: TileMap | null) => void
callback: (tileMapFileContent: TileMapFileContent | null) => void
) => void,
tileMapJsonResourceName: string,
tileSetJsonResourceName: string,
@@ -101,14 +100,14 @@ export class TileMapManager {
loadTileMap(
tileMapJsonResourceName,
tileSetJsonResourceName,
(tileMap: TileMap | null) => {
if (!tileMap) {
(tileMapFileContent: TileMapFileContent | null) => {
if (!tileMapFileContent) {
callback(null);
return;
}
const editableTileMap = TileMapLoader.load(
tileMap,
tileMapFileContent,
levelIndex,
pako
);
@@ -133,7 +132,7 @@ export class TileMapManager {
loadTileMap: (
tileMapJsonResourceName: string,
tileSetJsonResourceName: string,
callback: (tileMap: TileMap | null) => void
callback: (tileMapFileContent: TileMapFileContent | null) => void
) => void,
getTexture: (textureName: string) => PIXI.BaseTexture<PIXI.Resource>,
atlasImageResourceName: string,
@@ -157,8 +156,8 @@ export class TileMapManager {
loadTileMap(
tileMapJsonResourceName,
tileSetJsonResourceName,
(tileMap: TileMap | null) => {
if (!tileMap) {
(tileMapFileContent: TileMapFileContent | null) => {
if (!tileMapFileContent) {
// loadTileMap already log errors.
callback(null);
return;
@@ -168,7 +167,7 @@ export class TileMapManager {
? getTexture(atlasImageResourceName)
: null;
const textureCache = PixiTileMapHelper.parseAtlas(
tileMap,
tileMapFileContent,
levelIndex,
atlasTexture,
getTexture

View File

@@ -6,7 +6,7 @@ import {
} from "../model/TileMapModel";
import { TiledPixiHelper } from "./tiled/TiledPixiHelper";
import { LDtkPixiHelper } from "./ldtk/LDtkPixiHelper";
import { TileMap } from "../types";
import { TileMapFileContent } from "../load/TileMapFileContent";
import { TileTextureCache } from "./TileTextureCache";
import { FlippingHelper, getPixiRotate } from "../model/GID";
@@ -23,7 +23,7 @@ export namespace PixiTileMapHelper {
* @returns A textures cache.
*/
export function parseAtlas(
tileMap: TileMap,
tileMap: TileMapFileContent,
levelIndex: number,
atlasTexture: PIXI.BaseTexture<PIXI.Resource> | null,
getTexture: (textureName: string) => PIXI.BaseTexture<PIXI.Resource>

View File

@@ -1,6 +1,6 @@
# GDevelop IDE
This is the GDevelop 5 editor. It is based on [React](https://facebook.github.io/react/), [Material-UI](http://www.material-ui.com), [Pixi.js](https://github.com/pixijs/pixi.js) and [Electron](https://electron.atom.io/).
This is the GDevelop 5 editor. It is based on [React](https://facebook.github.io/react/), [Material-UI](http://www.material-ui.com), [Pixi.js](https://github.com/pixijs/pixi.js) and [Electron](https://electron.atom.io/) for the desktop app.
It uses GDevelop [core C++ classes compiled to Javascript](https://github.com/4ian/GDevelop.js) to work with GDevelop games.
![GDevelop editor](https://raw.githubusercontent.com/4ian/GDevelop/master/newIDE/GDevelop%20screenshot.png "GDevelop editor")

View File

@@ -1,235 +0,0 @@
// flow-typed signature: 4850a295b0a6cab60f9421a95997888f
// flow-typed version: 5b150db5b6/raven-js_v3.17.x/flow_>=v0.38.x
type LogLevel = "critical" | "error" | "warning" | "info" | "debug";
type AutoBreadcrumbOptions = {
xhr?: boolean,
console?: boolean,
dom?: boolean,
location?: boolean
};
type RavenInstrumentationOptions = {
tryCatch?: boolean
};
type Breadcrumb = {
message?: string,
category?: string,
level?: LogLevel,
data?: any,
type?: BreadcrumbType
};
type BreadcrumbType = "navigation" | "http";
type RavenOptions = {
/** The log level associated with this event. Default: error */
level?: LogLevel,
/** The name of the logger used by Sentry. Default: javascript */
logger?: string,
/** The environment of the application you are monitoring with Sentry */
environment?: string,
/** The release version of the application you are monitoring with Sentry */
release?: string,
/** The name of the server or device that the client is running on */
serverName?: string,
/** List of messages to be filtered out before being sent to Sentry. */
ignoreErrors?: (RegExp | string)[],
/** Similar to ignoreErrors, but will ignore errors from whole urls patching a regex pattern. */
ignoreUrls?: (RegExp | string)[],
/** The inverse of ignoreUrls. Only report errors from whole urls matching a regex pattern. */
whitelistUrls?: (RegExp | string)[],
/** An array of regex patterns to indicate which urls are a part of your app. */
includePaths?: (RegExp | string)[],
/** Additional data to be tagged onto the error. */
tags?: {
[id: string]: string
},
/** set to true to get the stack trace of your message */
stacktrace?: boolean,
extra?: any,
/** In some cases you may see issues where Sentry groups multiple events together when they should be separate entities. In other cases, Sentry simply doesnt group events together because theyre so sporadic that they never look the same. */
fingerprint?: string[],
/** A function which allows mutation of the data payload right before being sent to Sentry */
dataCallback?: (data: any) => any,
/** A callback function that allows you to apply your own filters to determine if the message should be sent to Sentry. */
shouldSendCallback?: (data: any) => boolean,
/** By default, Raven does not truncate messages. If you need to truncate characters for whatever reason, you may set this to limit the length. */
maxMessageLength?: number,
/** By default, Raven will truncate URLs as they appear in breadcrumbs and other meta interfaces to 250 characters in order to minimize bytes over the wire. This does *not* affect URLs in stack traces. */
maxUrlLength?: number,
/** Override the default HTTP data transport handler. */
transport?: (options: RavenTransportOptions) => void,
/** Allow use of private/secretKey. */
allowSecretKey?: boolean,
/** Enables/disables instrumentation of globals. */
instrument?: boolean | RavenInstrumentationOptions,
/** Enables/disables automatic collection of breadcrumbs. */
autoBreadcrumbs?: boolean | AutoBreadcrumbOptions
};
type RavenTransportOptions = {
url: string,
data: any,
auth: {
sentry_version: string,
sentry_client: string,
sentry_key: string
},
onSuccess: () => void,
onFailure: () => void
};
declare module "raven-js" {
declare type RavenPlugin = {
(raven: Raven, ...args: any[]): Raven
};
declare class Raven {
/** Raven.js version. */
VERSION: string,
Plugins: { [id: string]: RavenPlugin },
/*
* Allow Raven to be configured as soon as it is loaded
* It uses a global RavenConfig = {dsn: '...', config: {}}
*/
afterLoad(): void,
/*
* Allow multiple versions of Raven to be installed.
* Strip Raven from the global context and returns the instance.
*/
noConflict(): this,
/** Configure Raven with a DSN and extra options */
config(dsn: string, options?: RavenOptions): this,
/*
* Installs a global window.onerror error handler
* to capture and report uncaught exceptions.
* At this point, install() is required to be called due
* to the way TraceKit is set up.
*/
install(): this,
/** Adds a plugin to Raven */
addPlugin(plugin: RavenPlugin, ...pluginArgs: any[]): this,
/*
* Wrap code within a context so Raven can capture errors
* reliably across domains that is executed immediately.
*/
context(func: Function, ...args: any[]): void,
context(options: RavenOptions, func: Function, ...args: any[]): void,
/** Wrap code within a context and returns back a new function to be executed */
wrap(func: Function): Function,
wrap(options: RavenOptions, func: Function): Function,
wrap<T: Function>(func: T): T,
wrap<T: Function>(options: RavenOptions, func: T): T,
/** Uninstalls the global error handler. */
uninstall(): this,
/** Manually capture an exception and send it over to Sentry */
captureException(ex: Error, options?: RavenOptions): this,
/** Manually send a message to Sentry */
captureMessage(msg: string, options?: RavenOptions): this,
/** Log a breadcrumb */
captureBreadcrumb(crumb: Breadcrumb): this,
/**
* Clear the user context, removing the user data that would be sent to Sentry.
*/
setUserContext(): this,
/** Set a user to be sent along with the payload. */
setUserContext(user: {
id?: string,
username?: string,
email?: string
}): this,
/** Merge extra attributes to be sent along with the payload. */
setExtraContext(context: Object): this,
/** Merge tags to be sent along with the payload. */
setTagsContext(tags: Object): this,
/** Clear all of the context. */
clearContext(): this,
/** Get a copy of the current context. This cannot be mutated.*/
getContext(): Object,
/** Override the default HTTP data transport handler. */
setTransport(
transportFunction: (options: RavenTransportOptions) => void
): this,
/** Set environment of application */
setEnvironment(environment: string): this,
/** Set release version of application */
setRelease(release: string): this,
/** Get the latest raw exception that was captured by Raven.*/
lastException(): Error,
/** An event id is a globally unique id for the event that was just sent. This event id can be used to find the exact event from within Sentry. */
lastEventId(): string,
/** If you need to conditionally check if raven needs to be initialized or not, you can use the isSetup function. It will return true if Raven is already initialized. */
isSetup(): boolean,
/** Specify a function that allows mutation of the data payload right before being sent to Sentry. */
setDataCallback(data: any, orig?: any): this,
/** Specify a callback function that allows you to mutate or filter breadcrumbs when they are captured. */
setBreadcrumbCallback(data: any, orig?: any): this,
/** Specify a callback function that allows you to apply your own filters to determine if the message should be sent to Sentry. */
setShouldSendCallback(data: any, orig?: any): this,
/** Show Sentry user feedback dialog */
showReportDialog(options?: {
eventId?: string,
dsn?: string,
user?: {
name?: string,
email?: string
}
}): void,
/** Configure Raven DSN */
setDSN(dsn: string): void
}
declare export default Raven
}

View File

@@ -36,7 +36,6 @@
"pixi.js-legacy": "^6.1.2",
"posthog-js": "^1.34.0",
"prop-types": "^15.5.10",
"raven-js": "^3.19.1",
"react": "16.14.0",
"react-color": "2.13.8",
"react-dnd": "7.7.0",
@@ -33480,12 +33479,6 @@
"node": ">= 0.6"
}
},
"node_modules/raven-js": {
"version": "3.27.2",
"resolved": "https://registry.npmjs.org/raven-js/-/raven-js-3.27.2.tgz",
"integrity": "sha512-mFWQcXnhRFEQe5HeFroPaEghlnqy7F5E2J3Fsab189ondqUzcjwSVi7el7F36cr6PvQYXoZ1P2F5CSF2/azeMQ==",
"deprecated": "Please upgrade to @sentry/browser. See the migration guide https://bit.ly/3ybOlo7"
},
"node_modules/raw-body": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
@@ -67952,11 +67945,6 @@
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"dev": true
},
"raven-js": {
"version": "3.27.2",
"resolved": "https://registry.npmjs.org/raven-js/-/raven-js-3.27.2.tgz",
"integrity": "sha512-mFWQcXnhRFEQe5HeFroPaEghlnqy7F5E2J3Fsab189ondqUzcjwSVi7el7F36cr6PvQYXoZ1P2F5CSF2/azeMQ=="
},
"raw-body": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",

View File

@@ -59,7 +59,6 @@
"pixi.js-legacy": "^6.1.2",
"posthog-js": "^1.34.0",
"prop-types": "^15.5.10",
"raven-js": "^3.19.1",
"react": "16.14.0",
"react-color": "2.13.8",
"react-dnd": "7.7.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

View File

@@ -365,7 +365,7 @@
"value": "#93e500"
}
},
"expression": {
"number": {
"color": {
"value": "#e8dc59"
}

View File

@@ -66,7 +66,7 @@ export const AnnouncementsFeed = ({
return (
<AlertMessage
kind={announcement.type === 'warning' ? 'warning' : 'info'}
kind={announcement.type}
renderRightButton={
buttonLabelByLocale && buttonUrl
? () => (

View File

@@ -117,6 +117,7 @@ export const AssetStoreContext = React.createContext<AssetStoreState>({
clearHistory: () => {},
openSearchResultPage: () => {},
openTagPage: tag => {},
openAssetCategoryPage: category => {},
openPackPage: assetPack => {},
openDetailPage: assetShortHeader => {},
openPrivateAssetPackInformationPage: privateAssetPackListingData => {},

View File

@@ -10,10 +10,12 @@ import { type PrivateAssetPackListingData } from '../Utils/GDevelopServices/Shop
export type AssetStorePageState = {|
openedAssetPack: PublicAssetPack | PrivateAssetPack | null,
openedAssetCategory: string | null,
openedAssetShortHeader: ?AssetShortHeader,
openedPrivateAssetPackListingData: ?PrivateAssetPackListingData,
filtersState: FiltersState,
scrollPosition?: ?number,
displayAssets: boolean,
|};
export type NavigationState = {|
@@ -23,6 +25,7 @@ export type NavigationState = {|
clearHistory: () => void,
openSearchResultPage: () => void,
openTagPage: string => void,
openAssetCategoryPage: string => void,
openPackPage: (PublicAssetPack | PrivateAssetPack) => void,
openPrivateAssetPackInformationPage: PrivateAssetPackListingData => void,
openDetailPage: AssetShortHeader => void,
@@ -38,20 +41,31 @@ const noFilter: FiltersState = {
export const assetStoreHomePageState: AssetStorePageState = {
openedAssetShortHeader: null,
openedAssetCategory: null,
openedAssetPack: null,
openedPrivateAssetPackListingData: null,
filtersState: noFilter,
displayAssets: false,
};
const searchPageState: AssetStorePageState = {
openedAssetShortHeader: null,
openedAssetCategory: null,
openedAssetPack: null,
openedPrivateAssetPackListingData: null,
filtersState: noFilter,
displayAssets: true,
};
export const isHomePage = (pageState: AssetStorePageState) => {
return pageState === assetStoreHomePageState;
return (
pageState === assetStoreHomePageState ||
(!pageState.openedAssetShortHeader &&
!pageState.openedPrivateAssetPackListingData &&
!pageState.openedAssetPack &&
pageState.filtersState === noFilter &&
!pageState.displayAssets)
);
};
export const isSearchResultPage = (pageState: AssetStorePageState) => {
@@ -122,8 +136,10 @@ export const useNavigation = (): NavigationState => {
...previousHistory.previousPages,
{
openedAssetShortHeader: null,
openedAssetCategory: null,
openedAssetPack: null,
openedPrivateAssetPackListingData: null,
displayAssets: true,
filtersState: {
chosenCategory: {
node: { name: tag, allChildrenTags: [], children: [] },
@@ -138,33 +154,58 @@ export const useNavigation = (): NavigationState => {
],
}));
},
openPackPage: (assetPack: PublicAssetPack | PrivateAssetPack) => {
openAssetCategoryPage: (category: string) => {
setHistory(previousHistory => ({
...previousHistory,
previousPages: [
...previousHistory.previousPages,
{
openedAssetShortHeader: null,
openedAssetPack: assetPack,
openedAssetCategory: category,
openedAssetPack: null,
openedPrivateAssetPackListingData: null,
filtersState: {
chosenCategory: {
node: {
name: assetPack.tag,
allChildrenTags: [],
children: [],
},
parentNodes: [],
},
chosenFilters: new Set(),
addFilter: () => {},
removeFilter: () => {},
setChosenCategory: () => {},
},
filtersState: noFilter,
displayAssets: false,
},
],
}));
},
openPackPage: (assetPack: PublicAssetPack | PrivateAssetPack) => {
setHistory(previousHistory => {
const currentPage =
previousHistory.previousPages[
previousHistory.previousPages.length - 1
];
return {
...previousHistory,
previousPages: [
...previousHistory.previousPages,
{
openedAssetShortHeader: null,
openedAssetCategory:
(currentPage && currentPage.openedAssetCategory) || null,
openedAssetPack: assetPack,
openedPrivateAssetPackListingData: null,
displayAssets: true,
filtersState: {
chosenCategory: {
node: {
name: assetPack.tag,
allChildrenTags: [],
children: [],
},
parentNodes: [],
},
chosenFilters: new Set(),
addFilter: () => {},
removeFilter: () => {},
setChosenCategory: () => {},
},
},
],
};
});
},
openPrivateAssetPackInformationPage: (
assetPack: PrivateAssetPackListingData
) => {
@@ -174,9 +215,11 @@ export const useNavigation = (): NavigationState => {
...previousHistory.previousPages,
{
openedAssetShortHeader: null,
openedAssetCategory: null,
openedAssetPack: null,
openedPrivateAssetPackListingData: assetPack,
filtersState: noFilter,
displayAssets: false,
},
],
}));
@@ -188,9 +231,11 @@ export const useNavigation = (): NavigationState => {
...previousHistory.previousPages,
{
openedAssetShortHeader: assetShortHeader,
openedAssetCategory: null,
openedAssetPack: null,
openedPrivateAssetPackListingData: null,
filtersState: noFilter,
displayAssets: false,
},
],
}));

View File

@@ -19,12 +19,54 @@ import { useResponsiveWindowWidth } from '../UI/Reponsive/ResponsiveWindowMeasur
import AuthenticatedUserContext from '../Profile/AuthenticatedUserContext';
import Paper from '../UI/Paper';
import { mergeArraysPerGroup } from '../Utils/Array';
import { textEllipsisStyle } from '../UI/TextEllipsis';
const columns = 3;
const columnsForSmallWindow = 1;
const columnsForMediumWindow = 2;
const categoryColumns = 4;
const categoryColumnsForSmallWindow = 2;
const categoryColumnsForMediumWindow = 3;
const cellSpacing = 2;
export const assetCategories = {
'full-game-pack': {
title: <Trans>Full Game Packs</Trans>,
imageAlt: 'Full game asset packs category',
imageSource: 'res/asset-categories/Full_game_pack.jpeg',
},
character: {
title: <Trans>Characters</Trans>,
imageAlt: 'Characters asset packs category',
imageSource: 'res/asset-categories/Characters.jpeg',
},
props: {
title: <Trans>Props</Trans>,
imageAlt: 'Props asset packs category',
imageSource: 'res/asset-categories/Props.jpeg',
},
background: {
title: <Trans>Backgrounds</Trans>,
imageAlt: 'Backgrounds asset packs category',
imageSource: 'res/asset-categories/Backgrounds.jpeg',
},
'visual-effect': {
title: <Trans>Visual Effects</Trans>,
imageAlt: 'Visual effects asset packs category',
imageSource: 'res/asset-categories/Visual_Effects.jpeg',
},
interface: {
title: <Trans>UI/Interface</Trans>,
imageAlt: 'User Interface asset packs category',
imageSource: 'res/asset-categories/Interface.jpeg',
},
prefab: {
title: <Trans>Prefabs (Ready-to-use Objects)</Trans>,
imageAlt: 'Prefabs asset packs category',
imageSource: 'res/asset-categories/Prefabs.jpeg',
},
};
const styles = {
grid: { margin: '0 10px' },
priceTagContainer: {
@@ -49,10 +91,8 @@ const styles = {
margin: 4,
},
packTitle: {
textOverflow: 'ellipsis',
overflow: 'hidden',
...textEllipsisStyle,
overflowWrap: 'break-word',
whiteSpace: 'nowrap',
},
};
@@ -75,14 +115,13 @@ const PublicAssetPackTile = ({
}: {|
assetPack: PublicAssetPack,
onSelect: () => void,
/** Props needed so that GidList component can adjust tile size */
/** Props needed so that GridList component can adjust tile size */
style?: any,
|}) => {
const classesForGridListItem = useStylesForGridListItem();
return (
<GridListTile
classes={classesForGridListItem}
key={assetPack.tag}
tabIndex={0}
onKeyPress={(event: SyntheticKeyboardEvent<HTMLLIElement>): void => {
if (shouldValidate(event)) {
@@ -133,7 +172,6 @@ export const PrivateAssetPackTile = ({
return (
<GridListTile
classes={classesForGridListItem}
key={assetPackListingData.id}
tabIndex={0}
onKeyPress={(event: SyntheticKeyboardEvent<HTMLLIElement>): void => {
if (shouldValidate(event)) {
@@ -150,14 +188,13 @@ export const PrivateAssetPackTile = ({
src={assetPackListingData.thumbnailUrls[0]}
alt={`Preview image of asset pack ${assetPackListingData.name}`}
/>
{!owned && (
<div style={styles.priceTagContainer}>
<PriceTag
value={assetPackListingData.prices[0].value}
withOverlay
/>
</div>
)}
<div style={styles.priceTagContainer}>
<PriceTag
value={assetPackListingData.prices[0].value}
withOverlay
owned={owned}
/>
</div>
<Column>
<Line justifyContent="space-between" noMargin>
<Text style={styles.packTitle} size="body2">
@@ -173,6 +210,51 @@ export const PrivateAssetPackTile = ({
);
};
export const CategoryTile = ({
title,
imageSource,
imageAlt,
onSelect,
style,
}: {|
title: React.Node,
imageSource: string,
imageAlt: string,
onSelect: () => void,
/** Props needed so that GridList component can adjust tile size */
style?: any,
|}) => {
const classesForGridListItem = useStylesForGridListItem();
return (
<GridListTile
classes={classesForGridListItem}
tabIndex={0}
onKeyPress={(event: SyntheticKeyboardEvent<HTMLLIElement>): void => {
if (shouldValidate(event)) {
onSelect();
}
}}
style={style}
onClick={onSelect}
>
<Paper elevation={2} style={styles.paper} background="light">
<CorsAwareImage
style={styles.previewImage}
src={imageSource}
alt={imageAlt}
/>
<Column>
<Line justifyContent="center" noMargin>
<Text style={styles.packTitle} size="sub-title">
{title}
</Text>
</Line>
</Column>
</Paper>
</GridListTile>
);
};
export type AssetsHomeInterface = {|
getScrollPosition: () => number,
scrollToPosition: (y: number) => void,
@@ -184,6 +266,8 @@ type Props = {|
assetPackRandomOrdering: Array<number>,
onPublicAssetPackSelection: PublicAssetPack => void,
onPrivateAssetPackSelection: PrivateAssetPackListingData => void,
onCategorySelection: string => void,
openedAssetCategory: string | null,
|};
export const AssetsHome = React.forwardRef<Props, AssetsHomeInterface>(
@@ -194,6 +278,8 @@ export const AssetsHome = React.forwardRef<Props, AssetsHomeInterface>(
assetPackRandomOrdering,
onPublicAssetPackSelection,
onPrivateAssetPackSelection,
onCategorySelection,
openedAssetCategory,
}: Props,
ref
) => {
@@ -219,18 +305,27 @@ export const AssetsHome = React.forwardRef<Props, AssetsHomeInterface>(
},
}));
const starterPacksTiles: Array<React.Node> = starterPacks.map(
(assetPack, index) => (
const starterPacksTiles: Array<React.Node> = starterPacks
.filter(
assetPack =>
!openedAssetCategory ||
assetPack.categories.includes(openedAssetCategory)
)
.map((assetPack, index) => (
<PublicAssetPackTile
assetPack={assetPack}
onSelect={() => onPublicAssetPackSelection(assetPack)}
key={`${assetPack.tag}-${index}`}
/>
)
);
));
const privateAssetPacksTiles: Array<React.Node> = privateAssetPacksListingData.map(
assetPackListingData => (
const privateAssetPacksTiles: Array<React.Node> = privateAssetPacksListingData
.filter(
assetPackListingData =>
!openedAssetCategory ||
assetPackListingData.categories.includes(openedAssetCategory)
)
.map(assetPackListingData => (
<PrivateAssetPackTile
assetPackListingData={assetPackListingData}
onSelect={() => {
@@ -244,8 +339,7 @@ export const AssetsHome = React.forwardRef<Props, AssetsHomeInterface>(
}
key={assetPackListingData.id}
/>
)
);
));
const allTiles = mergeArraysPerGroup(
privateAssetPacksTiles,
@@ -257,8 +351,63 @@ export const AssetsHome = React.forwardRef<Props, AssetsHomeInterface>(
1
);
const categoryTiles = Object.entries(assetCategories).map(
// $FlowExpectedError - Object.entries does not infer well the type of the value.
([id, { title, imageSource, imageAlt }]) => (
<CategoryTile
key={id}
imageSource={imageSource}
imageAlt={imageAlt}
title={title}
onSelect={() => {
onCategorySelection(id);
}}
/>
)
);
const openedAssetCategoryTitle = openedAssetCategory
? assetCategories[openedAssetCategory].title
: null;
return (
<ScrollView ref={scrollView}>
{openedAssetCategory ? null : (
<>
<Column>
<Line>
<Text size="block-title">
<Trans>Explore by category</Trans>
</Text>
</Line>
</Column>
<GridList
cols={
windowWidth === 'small'
? categoryColumnsForSmallWindow
: windowWidth === 'medium'
? categoryColumnsForMediumWindow
: categoryColumns
}
style={styles.grid}
cellHeight="auto"
spacing={cellSpacing}
>
{categoryTiles}
</GridList>
</>
)}
<Column>
<Line>
<Text size="block-title">
{openedAssetCategoryTitle ? (
openedAssetCategoryTitle
) : (
<Trans>All asset packs</Trans>
)}
</Text>
</Line>
</Column>
<GridList
cols={
windowWidth === 'small'

View File

@@ -197,7 +197,7 @@ const getMergedInstalledWithDefaultEnumeratedObjectMetadataByCategory = ({
name: 'PanelSpriteSlider::PanelSpriteSlider',
fullName: i18n._(t`Slider`),
description: i18n._(
t`Let users select a numerical value byb dragging a slider.`
t`Let users select a numerical value by dragging a slider.`
),
iconFilename:
'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPGNpcmNsZSBjbGFzcz0ic3QwIiBjeD0iMjMiIGN5PSI3IiByPSIzIi8+DQo8bGluZSBjbGFzcz0ic3QwIiB4MT0iMyIgeTE9IjciIHgyPSIyMCIgeTI9IjciLz4NCjxsaW5lIGNsYXNzPSJzdDAiIHgxPSIyOSIgeTE9IjciIHgyPSIyNiIgeTI9IjciLz4NCjxjaXJjbGUgY2xhc3M9InN0MCIgY3g9IjEyIiBjeT0iMTYiIHI9IjMiLz4NCjxsaW5lIGNsYXNzPSJzdDAiIHgxPSIzIiB5MT0iMTYiIHgyPSI5IiB5Mj0iMTYiLz4NCjxsaW5lIGNsYXNzPSJzdDAiIHgxPSIyOSIgeTE9IjE2IiB4Mj0iMTUiIHkyPSIxNiIvPg0KPGNpcmNsZSBjbGFzcz0ic3QwIiBjeD0iMjMiIGN5PSIyNSIgcj0iMyIvPg0KPGxpbmUgY2xhc3M9InN0MCIgeDE9IjMiIHkxPSIyNSIgeDI9IjIwIiB5Mj0iMjUiLz4NCjxsaW5lIGNsYXNzPSJzdDAiIHgxPSIyOSIgeTE9IjI1IiB4Mj0iMjYiIHkyPSIyNSIvPg0KPC9zdmc+DQo=',

View File

@@ -264,7 +264,9 @@ const PrivateAssetPackPurchaseDialog = ({
<Line justifyContent="center" alignItems="center">
<CircularProgress size={20} />
<Spacer />
<Text>Waiting for the purchase confirmation...</Text>
<Text>
<Trans>Waiting for the purchase confirmation...</Trans>
</Text>
</Line>
<Spacer />
<Line justifyContent="center">

View File

@@ -99,6 +99,7 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
const {
openedAssetPack,
openedAssetShortHeader,
openedAssetCategory,
openedPrivateAssetPackListingData,
filtersState,
} = navigationState.getCurrentPage();
@@ -164,7 +165,11 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
return;
}
const scrollPosition = navigationState.getCurrentPage().scrollPosition;
scrollPosition && scrollView.scrollToPosition(scrollPosition);
if (scrollPosition) scrollView.scrollToPosition(scrollPosition);
// If no saved scroll position, force scroll to 0 in case the displayed component
// is the same as the previous page so the scroll is naturally kept between pages
// although the user navigated and the scroll should be reset.
else scrollView.scrollToPosition(0);
hasAppliedSavedScrollPosition.current = true;
},
[getScrollView, navigationState]
@@ -278,6 +283,14 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
[receivedAssetPacks, saveScrollPosition, navigationState, setSearchText]
);
const selectAssetCategory = React.useCallback(
(category: string) => {
saveScrollPosition();
navigationState.openAssetCategoryPage(category);
},
[navigationState, saveScrollPosition]
);
// If the user has received the pack they are currently viewing,
// we update the window to show it if they are not already on the pack page.
React.useEffect(
@@ -424,18 +437,15 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
/>
</Column>
</LineStackLayout>
{!isOnHomePage && <Spacer />}
<Spacer />
<Column noMargin>
<Line
justifyContent="space-between"
noMargin
alignItems="center"
>
{isOnHomePage ? (
<Text size="block-title">
<Trans>Discover</Trans>
</Text>
) : (
{(!isOnHomePage ||
(isOnHomePage && !!openedAssetCategory)) && (
<>
<Column expand alignItems="flex-start" noMargin>
<TextButton
@@ -559,6 +569,8 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
assetPackRandomOrdering={assetPackRandomOrdering}
onPublicAssetPackSelection={selectPublicAssetPack}
onPrivateAssetPackSelection={selectPrivateAssetPack}
onCategorySelection={selectAssetCategory}
openedAssetCategory={openedAssetCategory}
/>
) : (
<PlaceholderLoader />

View File

@@ -110,7 +110,11 @@ const MeasuresTable = (props: Props) => {
) : (
<div style={{ width: 24 }} />
)}
{rowData.name}
{/*
The name is wrapped in a span to prevent crashes when Google Translate
translates the website. See https://github.com/4ian/GDevelop/issues/3453.
*/}
<span>{rowData.name}</span>
</div>
);
};

View File

@@ -325,6 +325,8 @@ export const ExtensionOptionsEditor = ({
}}
/>,
]}
flexColumnBody
fullHeight
open
onRequestClose={() => {
setResourceStoreOpen(false);

View File

@@ -162,7 +162,12 @@ const Instruction = (props: Props) => {
}
const parameterMetadata = metadata.getParameter(parameterIndex);
const parameterType = parameterMetadata.getType();
// TODO Remove the ternary when any parameter declaration uses
// 'number' instead of 'expression'.
const parameterType: string =
parameterMetadata.getType() === 'expression'
? 'number'
: parameterMetadata.getType();
let expressionIsValid = true;
if (
gd.ParameterMetadata.isExpression('number', parameterType) ||

View File

@@ -27,6 +27,7 @@ import {
tuneMatches,
type SearchResult,
sharedFuseConfiguration,
getFuseSearchQueryForMultipleKeys,
} from '../../../UI/Search/UseSearchStructuredItem';
const gd: libGDevelop = global.gd;
@@ -107,17 +108,19 @@ export default class InstructionOrExpressionSelector<
} = this.props;
const { searchText } = this.state;
const extendSearchText = `'${searchText
.trim()
.split(' ')
.join(" '")}`;
const displayedInstructionsList: Array<SearchResult<T>> =
!!extendSearchText && this.searchApi
? this.searchApi.search(extendSearchText).map(result => ({
item: result.item,
matches: tuneMatches(result, searchText),
}))
!!searchText && this.searchApi
? this.searchApi
.search(
getFuseSearchQueryForMultipleKeys(searchText, [
'displayedName',
'fullGroupName',
])
)
.map(result => ({
item: result.item,
matches: tuneMatches(result, searchText),
}))
: [];
const hasResults = !searchText || !!displayedInstructionsList.length;

View File

@@ -55,6 +55,8 @@ import {
type SearchResult,
tuneMatches,
sharedFuseConfiguration,
getFuseSearchQueryForSimpleArray,
getFuseSearchQueryForMultipleKeys,
} from '../../UI/Search/UseSearchStructuredItem';
import { Column, Line } from '../../UI/Grid';
@@ -193,33 +195,37 @@ export default class InstructionOrObjectSelector extends React.PureComponent<
_search = (searchText: string) => {
if (searchText === '') return;
const extendSearchText = `'${searchText
.trim()
.split(' ')
.join(" '")}`;
const extendedSearchText = getFuseSearchQueryForSimpleArray(searchText);
this.setState({
searchResults: {
objects: this.objectSearchApi
? this.objectSearchApi.search(extendSearchText).map(result => ({
? this.objectSearchApi.search(extendedSearchText).map(result => ({
item: result.item,
matches: tuneMatches(result, searchText),
}))
: [],
groups: this.groupSearchApi
? this.groupSearchApi.search(extendSearchText).map(result => ({
? this.groupSearchApi.search(extendedSearchText).map(result => ({
item: result.item,
matches: tuneMatches(result, searchText),
}))
: [],
instructions: this.instructionSearchApi
? this.instructionSearchApi.search(extendSearchText).map(result => ({
item: result.item,
matches: tuneMatches(result, searchText),
}))
? this.instructionSearchApi
.search(
getFuseSearchQueryForMultipleKeys(searchText, [
'displayedName',
'fullGroupName',
])
)
.map(result => ({
item: result.item,
matches: tuneMatches(result, searchText),
}))
: [],
tags: this.tagSearchApi
? this.tagSearchApi.search(extendSearchText).map(result => ({
? this.tagSearchApi.search(extendedSearchText).map(result => ({
item: result.item,
matches: tuneMatches(result, searchText),
}))

View File

@@ -15,6 +15,7 @@ import { Column, Line, Spacer } from '../../../UI/Grid';
import ObjectsRenderingService from '../../../ObjectsRendering/ObjectsRenderingService';
import Paper from '../../../UI/Paper';
import { mapVector } from '../../../Utils/MapFor';
import { Trans } from '@lingui/macro';
const defaultTextStyle = {
// Break words if they are too long to fit on a single line.
@@ -414,7 +415,9 @@ export default function ExpressionAutocompletionsDisplayer({
)}
{remainingCount > 0 && (
<Column justifyContent="flex-start">
<Text>And others...</Text>
<Text>
<Trans>And others...</Trans>
</Text>
</Column>
)}
</ScrollView>

View File

@@ -26,6 +26,10 @@ import {
ExplanationHeader,
OnlineGameLink,
} from '../GenericExporters/OnlineWebExport';
import {
hasValidSubscriptionPlan,
onlineWebExportSizeOptions,
} from '../../Utils/GDevelopServices/Usage';
const gd: libGDevelop = global.gd;
type ExportState = null;
@@ -152,12 +156,16 @@ export const browserOnlineWebExportPipeline: ExportPipeline<
context: ExportPipelineContext<ExportState>,
{ textFiles, blobFiles }: ResourcesDownloadOutput
): Promise<Blob> => {
const hasValidSubscription = hasValidSubscriptionPlan(context.subscription);
return archiveFiles({
blobFiles,
textFiles,
basePath: '/export/',
onProgress: context.updateStepProgress,
sizeLimit: 250 * 1000 * 1000,
// Higher limit for users with a subscription.
sizeOptions: hasValidSubscription
? onlineWebExportSizeOptions.subscribed
: onlineWebExportSizeOptions.guest,
});
},

View File

@@ -257,6 +257,7 @@ export default class ExportLauncher extends Component<Props, State> {
project,
updateStepProgress: this._updateStepProgress,
exportState: this.state.exportState,
subscription: authenticatedUser.subscription,
};
setStep('export');
this.setState({

View File

@@ -170,7 +170,7 @@ const ExportDialog = ({
<TutorialButton
key="tutorial"
tutorialId="export-to-itch"
label="How to export to Itch.io"
label={<Trans>How to export to Itch.io"</Trans>}
/>
) : null,
<FlatButton

View File

@@ -3,11 +3,13 @@ import * as React from 'react';
import { type Build } from '../Utils/GDevelopServices/Build';
import { type AuthenticatedUser } from '../Profile/AuthenticatedUserContext';
import { type BuildStep } from './Builds/BuildStepsProgress';
import { type Subscription } from '../Utils/GDevelopServices/Usage';
export type ExportPipelineContext<ExportState> = {|
project: gdProject,
exportState: ExportState,
updateStepProgress: (count: number, total: number) => void,
subscription: ?Subscription,
|};
/**

View File

@@ -22,6 +22,10 @@ import {
OnlineGameLink,
} from '../GenericExporters/OnlineWebExport';
import { downloadUrlsToLocalFiles } from '../../Utils/LocalFileDownloader';
import {
hasValidSubscriptionPlan,
onlineWebExportSizeOptions,
} from '../../Utils/GDevelopServices/Usage';
const path = optionalRequire('path');
const os = optionalRequire('os');
const gd: libGDevelop = global.gd;
@@ -156,11 +160,15 @@ export const localOnlineWebExportPipeline: ExportPipeline<
context: ExportPipelineContext<ExportState>,
{ temporaryOutputDir }: ResourcesDownloadOutput
): Promise<CompressionOutput> => {
const hasValidSubscription = hasValidSubscriptionPlan(context.subscription);
const archiveOutputDir = os.tmpdir();
return archiveLocalFolder({
path: temporaryOutputDir,
outputFilename: path.join(archiveOutputDir, 'game-archive.zip'),
sizeLimit: 250 * 1000 * 1000,
// Higher limit for users with a subscription.
sizeOptions: hasValidSubscription
? onlineWebExportSizeOptions.subscribed
: onlineWebExportSizeOptions.guest,
});
},

View File

@@ -23,7 +23,7 @@ import AuthenticatedUserContext from '../Profile/AuthenticatedUserContext';
import PlaceholderError from '../UI/PlaceholderError';
import SelectField from '../UI/SelectField';
import SelectOption from '../UI/SelectOption';
import { Chip } from '@material-ui/core';
import Chip from '@material-ui/core/Chip';
import Builds from '../Export/Builds';
import AlertMessage from '../UI/AlertMessage';
import RaisedButton from '../UI/RaisedButton';
@@ -431,6 +431,7 @@ export const GameDetailsDialog = ({
undefined
)
}
className="notranslate"
label={username}
color={index === 0 ? 'primary' : 'default'}
/>

View File

@@ -194,7 +194,7 @@ export default class DocSearchArea extends React.Component<Props, State> {
</React.Fragment>
)}
<Text align="right">
This search is powered by{' '}
<Trans>This search is powered by</Trans>{' '}
<FlatButton
onClick={() => Window.openExternalURL('http://algolia.com/')}
label={'Algolia'}

View File

@@ -140,14 +140,18 @@ const LayerEditorDialog = (props: Props) => {
</DismissableAlertMessage>
) : null}
<Text>
There are {instancesCount} instances of objects on this layer.
<Trans>
There are {instancesCount} instances of objects on this layer.
</Trans>
</Text>
{!props.project.getUseDeprecatedZeroAsDefaultZOrder() && (
<Text>
Objects created using events on this layer will be given a "Z
order" of {highestZOrder + 1}, so that they appear in front of all
objects of this layer. You can change this using the action to
change an object Z order, after using an action to create it.
<Trans>
Objects created using events on this layer will be given a "Z
order" of {highestZOrder + 1}, so that they appear in front of
all objects of this layer. You can change this using the action
to change an object Z order, after using an action to create it.
</Trans>
</Text>
)}
<InlineCheckbox

View File

@@ -179,7 +179,7 @@ export class ExternalEventsEditorContainer extends React.Component<
<Line justifyContent="flex-start" noMargin>
<TutorialButton
tutorialId="Intermediate-externals"
label="Watch the tutorial"
label={<Trans>Watch tutorial</Trans>}
renderIfNotFound={
<HelpButton helpPagePath="/interface/events-editor/external-events" />
}

View File

@@ -208,7 +208,7 @@ export class ExternalLayoutEditorContainer extends React.Component<
<Line justifyContent="flex-start" noMargin>
<TutorialButton
tutorialId="Intermediate-externals"
label="Watch the tutorial"
label={<Trans>Watch tutorial</Trans>}
renderIfNotFound={
<HelpButton helpPagePath="/interface/events-editor/external-events" />
}

View File

@@ -37,6 +37,7 @@ import PrivateAssetsAuthorizationProvider from '../AssetStore/PrivateAssets/Priv
import InAppTutorialProvider from '../InAppTutorial/InAppTutorialProvider';
import { SubscriptionSuggestionProvider } from '../Profile/Subscription/SubscriptionSuggestionContext';
import { RouterContextProvider } from './RouterContext';
import ErrorBoundary from '../UI/ErrorBoundary';
// Add the rtl plugin to the JSS instance to support RTL languages in material-ui components.
const jss = create({
@@ -82,52 +83,57 @@ const Providers = ({
<GDevelopThemeContext.Provider value={theme.gdevelopTheme}>
<StylesProvider jss={jss}>
<ThemeProvider theme={theme.muiTheme}>
<InAppTutorialProvider>
<AuthenticatedUserProvider
authentication={authentication}
>
<PublicProfileProvider>
<I18n update>
{({ i18n }) => (
<EventsFunctionsExtensionsProvider
i18n={i18n}
makeEventsFunctionCodeWriter={
makeEventsFunctionCodeWriter
}
eventsFunctionsExtensionWriter={
eventsFunctionsExtensionWriter
}
eventsFunctionsExtensionOpener={
eventsFunctionsExtensionOpener
}
>
<AlertProvider>
<SubscriptionSuggestionProvider>
<CommandsContextProvider>
<AssetStoreStateProvider>
<ResourceStoreStateProvider>
<ExampleStoreStateProvider>
<ExtensionStoreStateProvider>
<TutorialStateProvider>
<AnnouncementsFeedStateProvider>
<PrivateAssetsAuthorizationProvider>
{children({ i18n })}
</PrivateAssetsAuthorizationProvider>
</AnnouncementsFeedStateProvider>
</TutorialStateProvider>
</ExtensionStoreStateProvider>
</ExampleStoreStateProvider>
</ResourceStoreStateProvider>
</AssetStoreStateProvider>
</CommandsContextProvider>
</SubscriptionSuggestionProvider>
</AlertProvider>
</EventsFunctionsExtensionsProvider>
)}
</I18n>
</PublicProfileProvider>
</AuthenticatedUserProvider>
</InAppTutorialProvider>
<ErrorBoundary
title="GDevelop encountered an issue"
scope="app"
>
<InAppTutorialProvider>
<AuthenticatedUserProvider
authentication={authentication}
>
<PublicProfileProvider>
<I18n update>
{({ i18n }) => (
<EventsFunctionsExtensionsProvider
i18n={i18n}
makeEventsFunctionCodeWriter={
makeEventsFunctionCodeWriter
}
eventsFunctionsExtensionWriter={
eventsFunctionsExtensionWriter
}
eventsFunctionsExtensionOpener={
eventsFunctionsExtensionOpener
}
>
<AlertProvider>
<SubscriptionSuggestionProvider>
<CommandsContextProvider>
<AssetStoreStateProvider>
<ResourceStoreStateProvider>
<ExampleStoreStateProvider>
<ExtensionStoreStateProvider>
<TutorialStateProvider>
<AnnouncementsFeedStateProvider>
<PrivateAssetsAuthorizationProvider>
{children({ i18n })}
</PrivateAssetsAuthorizationProvider>
</AnnouncementsFeedStateProvider>
</TutorialStateProvider>
</ExtensionStoreStateProvider>
</ExampleStoreStateProvider>
</ResourceStoreStateProvider>
</AssetStoreStateProvider>
</CommandsContextProvider>
</SubscriptionSuggestionProvider>
</AlertProvider>
</EventsFunctionsExtensionsProvider>
)}
</I18n>
</PublicProfileProvider>
</AuthenticatedUserProvider>
</InAppTutorialProvider>
</ErrorBoundary>
</ThemeProvider>
</StylesProvider>
</GDevelopThemeContext.Provider>

View File

@@ -2897,7 +2897,10 @@ const MainFrame = (props: Props) => {
return (
<TabContentContainer key={editorTab.key} active={isCurrentTab}>
<CommandsContextScopedProvider active={isCurrentTab}>
<ErrorBoundary>
<ErrorBoundary
title="This editor encountered a problem"
scope="mainframe"
>
{editorTab.renderEditorContainer({
isActive: isCurrentTab,
extraEditorProps: editorTab.extraEditorProps,

View File

@@ -167,9 +167,11 @@ const PolygonSection = (props: PolygonSectionProps) => {
<Accordion defaultExpanded>
<AccordionHeader actions={polygonActions}>
<Text displayInlineAsSpan>
{verticesCount === 3 && `Triangle`}
{verticesCount === 4 && `Quadrilateral`}
{verticesCount >= 5 && `Polygon with ${verticesCount} vertices`}
{verticesCount === 3 && <Trans>Triangle</Trans>}
{verticesCount === 4 && <Trans>Quadrilateral</Trans>}
{verticesCount >= 5 && (
<Trans>Polygon with ${verticesCount} vertices</Trans>
)}
</Text>
</AccordionHeader>
<AccordionBody disableGutters>

View File

@@ -730,13 +730,13 @@ const ObjectsList = React.forwardRef<Props, ObjectsListInterface>(
{
label: i18n._(t`Tags`),
submenu: buildTagsMenuTemplate({
noTagLabel: 'No tags',
noTagLabel: i18n._(t`No tags`),
getAllTags: getAllObjectTags,
selectedTags: getTagsFromString(object.getTags()),
onChange: objectTags => {
changeObjectTags(object, objectTags);
},
editTagsLabel: 'Add/edit tags...',
editTagsLabel: i18n._(t`Add/edit tags...`),
onEditTags: () => openEditTagDialog(object),
}),
},

View File

@@ -40,6 +40,10 @@ export type AuthenticatedUser = {|
onSubscriptionUpdated: () => Promise<void>,
onPurchaseSuccessful: () => Promise<void>,
onSendEmailVerification: () => Promise<void>,
onOpenEmailVerificationDialog: ({|
sendEmailAutomatically: boolean,
showSendEmailButton: boolean,
|}) => void,
onAcceptGameStatsEmail: () => Promise<void>,
getAuthorizationHeader: () => Promise<string>,
|};
@@ -70,6 +74,7 @@ export const initialAuthenticatedUser = {
onSubscriptionUpdated: async () => {},
onPurchaseSuccessful: async () => {},
onSendEmailVerification: async () => {},
onOpenEmailVerificationDialog: () => {},
onAcceptGameStatsEmail: async () => {},
getAuthorizationHeader: () => Promise.reject(new Error('Unimplemented')),
};

View File

@@ -3,12 +3,11 @@ import { Trans } from '@lingui/macro';
import * as React from 'react';
import PlaceholderLoader from '../UI/PlaceholderLoader';
import FlatButton from '../UI/FlatButton';
import { ColumnStackLayout } from '../UI/Layout';
import AlertMessage from '../UI/AlertMessage';
import { type AuthenticatedUser } from './AuthenticatedUserContext';
import { useIsMounted } from '../Utils/UseIsMounted';
import ProfileDetails from './ProfileDetails';
import RaisedButton from '../UI/RaisedButton';
type Props = {|
onEditProfile: () => void,
@@ -23,18 +22,14 @@ const AuthenticatedUserProfileDetails = ({
}: Props) => {
const profile = authenticatedUser.profile;
const firebaseUser = authenticatedUser.firebaseUser;
const isMounted = useIsMounted();
const [emailSent, setEmailSent] = React.useState<boolean>(false);
const sendEmail = React.useCallback(
const openEmailVerificationDialog = React.useCallback(
() => {
authenticatedUser.onSendEmailVerification();
setEmailSent(true);
setTimeout(() => {
if (!isMounted.current) return;
setEmailSent(false);
}, 3000);
authenticatedUser.onOpenEmailVerificationDialog({
sendEmailAutomatically: true,
showSendEmailButton: false,
});
},
[authenticatedUser, isMounted]
[authenticatedUser]
);
return firebaseUser && profile ? (
@@ -43,23 +38,16 @@ const AuthenticatedUserProfileDetails = ({
<AlertMessage
kind="info"
renderRightButton={() => (
<FlatButton
label={
emailSent ? (
<Trans>Email sent!</Trans>
) : (
<Trans>Send it again</Trans>
)
}
onClick={sendEmail}
disabled={emailSent}
<RaisedButton
label={<Trans>Send it again</Trans>}
onClick={openEmailVerificationDialog}
primary
/>
)}
>
<Trans>
It looks like your email is not verified. Click on the link received
by email to verify your account. Didn't receive it?
You are missing out on asset store discounts and other benefits!
Verify your email address. Didn't receive it?
</Trans>
</AlertMessage>
)}

View File

@@ -26,7 +26,7 @@ import AuthenticatedUserContext, {
import CreateAccountDialog from './CreateAccountDialog';
import EditProfileDialog from './EditProfileDialog';
import ChangeEmailDialog from './ChangeEmailDialog';
import EmailVerificationPendingDialog from './EmailVerificationPendingDialog';
import EmailVerificationDialog from './EmailVerificationDialog';
import PreferencesContext, {
type PreferencesValues,
} from '../MainFrame/Preferences/PreferencesContext';
@@ -58,7 +58,11 @@ type State = {|
additionalUserInfoDialogOpen: boolean,
authError: ?AuthError,
resetPasswordDialogOpen: boolean,
emailVerificationPendingDialogOpen: boolean,
emailVerificationDialogOpen: boolean,
emailVerificationDialogProps: {|
sendEmailAutomatically: boolean,
showSendEmailButton: boolean,
|},
forgotPasswordInProgress: boolean,
changeEmailDialogOpen: boolean,
changeEmailInProgress: boolean,
@@ -80,7 +84,11 @@ export default class AuthenticatedUserProvider extends React.Component<
additionalUserInfoDialogOpen: false,
authError: null,
resetPasswordDialogOpen: false,
emailVerificationPendingDialogOpen: false,
emailVerificationDialogOpen: false,
emailVerificationDialogProps: {
sendEmailAutomatically: false,
showSendEmailButton: false,
},
forgotPasswordInProgress: false,
changeEmailDialogOpen: false,
changeEmailInProgress: false,
@@ -88,6 +96,7 @@ export default class AuthenticatedUserProvider extends React.Component<
};
_automaticallyUpdateUserProfile = true;
_hasNotifiedUserAboutAdditionalInfo = false;
_hasNotifiedUserAboutEmailVerification = false;
componentDidMount() {
this._resetAuthenticatedUser();
@@ -152,12 +161,25 @@ export default class AuthenticatedUserProvider extends React.Component<
onSubscriptionUpdated: this._fetchUserSubscriptionLimitsAndUsages,
onPurchaseSuccessful: this._fetchUserAssets,
onSendEmailVerification: this._doSendEmailVerification,
onOpenEmailVerificationDialog: ({
sendEmailAutomatically,
showSendEmailButton,
}: {|
sendEmailAutomatically: boolean,
showSendEmailButton: boolean,
|}) =>
this.openEmailVerificationDialog({
open: true,
sendEmailAutomatically,
showSendEmailButton,
}),
onAcceptGameStatsEmail: this._doAcceptGameStatsEmail,
getAuthorizationHeader: () =>
this.props.authentication.getAuthorizationHeader(),
},
}));
this._hasNotifiedUserAboutAdditionalInfo = false;
this._hasNotifiedUserAboutEmailVerification = false;
}
_reloadFirebaseProfile = async (): Promise<?FirebaseUser> => {
@@ -359,17 +381,6 @@ export default class AuthenticatedUserProvider extends React.Component<
}
}
// If the user has not filled their additional information, show
// the dialog to fill it.
// Use a state value to show the dialog only once.
if (
userProfile &&
!this._hasNotifiedUserAboutAdditionalInfo &&
shouldAskForAdditionalUserInfo(userProfile)
) {
setTimeout(() => this.openAdditionalUserInfoDialog(true), 1000);
}
this.setState(({ authenticatedUser }) => ({
authenticatedUser: {
...authenticatedUser,
@@ -377,6 +388,8 @@ export default class AuthenticatedUserProvider extends React.Component<
loginState: 'done',
},
}));
this._notifyUserAboutEmailVerificationAndAdditionalInfo();
};
_fetchUserSubscriptionLimitsAndUsages = async () => {
@@ -528,6 +541,51 @@ export default class AuthenticatedUserProvider extends React.Component<
}
};
_notifyUserAboutEmailVerificationAndAdditionalInfo = () => {
const { profile } = this.state.authenticatedUser;
if (!profile) return;
// If the user has not verified their email when logging in we show a dialog to do so.
// - If they just registered, we don't send the email again as it will be sent automatically,
// nor do we show a button to send again.
// - If they are just logging in, we don't send the email but we show a button to send again.
// Use a boolean to show the dialog only once.
const accountAgeInMs = Date.now() - profile.createdAt;
const hasJustCreatedAccount = accountAgeInMs < 1000 * 10; // 10 seconds.
if (
this.state.authenticatedUser.firebaseUser &&
!this.state.authenticatedUser.firebaseUser.emailVerified &&
!this._hasNotifiedUserAboutEmailVerification
) {
setTimeout(
() =>
this.openEmailVerificationDialog({
open: true,
sendEmailAutomatically: false,
showSendEmailButton: !hasJustCreatedAccount,
}),
1000
);
} else {
// If the user has not filled additional info, we show a dialog to do so.
this._notifyUserAboutAdditionalInfo();
}
};
_notifyUserAboutAdditionalInfo = () => {
const profile = this.state.authenticatedUser.profile;
if (!profile) return;
// If the user has not filled their additional information, show
// the dialog to fill it, but ensure they have closed the email verification dialog first.
// Use a boolean to show the dialog only once.
if (
profile &&
!this._hasNotifiedUserAboutAdditionalInfo &&
shouldAskForAdditionalUserInfo(profile)
) {
setTimeout(() => this.openAdditionalUserInfoDialog(true), 1000);
}
};
_doLogout = async () => {
if (this.props.authentication) {
await this.props.authentication.logout();
@@ -704,7 +762,6 @@ export default class AuthenticatedUserProvider extends React.Component<
if (!authentication) return;
await authentication.sendFirebaseEmailVerification();
this.openEmailVerificationPendingDialog(true);
};
_doAcceptGameStatsEmail = async () => {
@@ -755,10 +812,28 @@ export default class AuthenticatedUserProvider extends React.Component<
this._automaticallyUpdateUserProfile = true;
};
openEmailVerificationPendingDialog = (open: boolean = true) => {
openEmailVerificationDialog = ({
open = true,
sendEmailAutomatically = false,
showSendEmailButton = false,
}: {|
open?: boolean,
sendEmailAutomatically?: boolean,
showSendEmailButton?: boolean,
|}) => {
this.setState({
emailVerificationPendingDialogOpen: open,
emailVerificationDialogOpen: open,
emailVerificationDialogProps: {
sendEmailAutomatically: open ? sendEmailAutomatically : false, // reset to false when closing dialog.
showSendEmailButton: open ? showSendEmailButton : false, // reset to false when closing dialog.
},
});
// We save the fact that the user has seen the dialog when they close it.
// And we show them the additional info dialog if they haven't seen it yet.
if (!open) {
this._hasNotifiedUserAboutEmailVerification = true;
this._notifyUserAboutAdditionalInfo();
}
};
openResetPassword = (open: boolean = true) => {
@@ -787,7 +862,9 @@ export default class AuthenticatedUserProvider extends React.Component<
showUserSnackbar = ({ message }: { message: ?React.Node }) => {
this.setState({
userSnackbarMessage: message,
// The message is wrapped here to prevent crashes when Google Translate
// translates the website. See https://github.com/4ian/GDevelop/issues/3453.
userSnackbarMessage: message ? <span>{message}</span> : null,
});
};
@@ -875,17 +952,21 @@ export default class AuthenticatedUserProvider extends React.Component<
updateInProgress={this.state.editInProgress}
/>
)}
{this.state.emailVerificationPendingDialogOpen && (
<EmailVerificationPendingDialog
{this.state.emailVerificationDialogOpen && (
<EmailVerificationDialog
authenticatedUser={this.state.authenticatedUser}
onClose={() => {
this.openEmailVerificationPendingDialog(false);
this.openEmailVerificationDialog({
open: false,
});
this.state.authenticatedUser
.onRefreshFirebaseProfile()
.catch(() => {
// Ignore any error, we can't do much.
});
}}
{...this.state.emailVerificationDialogProps}
onSendEmail={this._doSendEmailVerification}
/>
)}
<Snackbar

View File

@@ -1,7 +1,7 @@
// @flow
import { Trans } from '@lingui/macro';
import React, { Component } from 'react';
import React from 'react';
import FlatButton from '../UI/FlatButton';
import Dialog, { DialogPrimaryButton } from '../UI/Dialog';
import { User as FirebaseUser } from 'firebase/auth';
@@ -13,6 +13,7 @@ import LeftLoader from '../UI/LeftLoader';
import { ColumnStackLayout } from '../UI/Layout';
import TextField from '../UI/TextField';
import { getEmailErrorText } from './CreateAccountDialog';
import { emailRegex } from './ForgotPasswordDialog';
type Props = {|
firebaseUser: FirebaseUser,
@@ -22,72 +23,88 @@ type Props = {|
error: ?AuthError,
|};
type State = {|
form: ChangeEmailForm,
|};
const ChangeEmailDialog = ({
onClose,
onChangeEmail,
firebaseUser,
changeEmailInProgress,
error,
}: Props) => {
const [email, setEmail] = React.useState(firebaseUser.email);
const [isEmailValid, setIsEmailValid] = React.useState<boolean>(true);
export default class ChangeEmailDialog extends Component<Props, State> {
state = {
form: {
email: this.props.firebaseUser.email,
},
const doChangeEmail = async () => {
const trimmedEmail = email.trim();
setEmail(trimmedEmail);
setIsEmailValid(emailRegex.test(trimmedEmail));
if (changeEmailInProgress || !email || !isEmailValid) return;
await onChangeEmail({
email,
});
};
_onChangeEmail = () => {
if (this.props.changeEmailInProgress) return;
const { form } = this.state;
this.props.onChangeEmail(form);
};
render() {
const { onClose, changeEmailInProgress, error } = this.props;
const actions = [
<FlatButton
label={<Trans>Back</Trans>}
disabled={changeEmailInProgress}
key="back"
primary={false}
onClick={onClose}
/>,
<LeftLoader isLoading={changeEmailInProgress} key="change-email">
<DialogPrimaryButton
label={<Trans>Save</Trans>}
primary
onClick={this._onChangeEmail}
return (
<Dialog
title={<Trans>Change your email</Trans>}
actions={[
<FlatButton
label={<Trans>Back</Trans>}
disabled={changeEmailInProgress}
/>
</LeftLoader>,
];
return (
<Dialog
title={<Trans>Change your email</Trans>}
actions={actions}
maxWidth="sm"
cannotBeDismissed={changeEmailInProgress}
onRequestClose={onClose}
onApply={this._onChangeEmail}
open
key="back"
primary={false}
onClick={onClose}
/>,
<LeftLoader isLoading={changeEmailInProgress} key="change-email">
<DialogPrimaryButton
label={<Trans>Save</Trans>}
primary
onClick={doChangeEmail}
disabled={changeEmailInProgress}
/>
</LeftLoader>,
]}
maxWidth="xs"
cannotBeDismissed={changeEmailInProgress}
onRequestClose={onClose}
onApply={doChangeEmail}
open
>
<form
onSubmit={event => {
// Prevent browser to navigate on form submission.
event.preventDefault();
doChangeEmail();
}}
autoComplete="on"
name="changeEmail"
>
<ColumnStackLayout noMargin>
<TextField
value={this.state.form.email}
value={email}
floatingLabelText={<Trans>Email</Trans>}
errorText={getEmailErrorText(error)}
errorText={
getEmailErrorText(error) ||
(!isEmailValid ? <Trans>Invalid email address</Trans> : null)
}
fullWidth
disabled={changeEmailInProgress}
required
onChange={(e, value) => {
this.setState({
form: {
...this.state.form,
email: value,
},
});
if (!isEmailValid) setIsEmailValid(true);
setEmail(value);
}}
onBlur={event => {
const trimmedEmail = event.currentTarget.value.trim();
setEmail(trimmedEmail);
setIsEmailValid(emailRegex.test(trimmedEmail));
}}
/>
</ColumnStackLayout>
</Dialog>
);
}
}
</form>
</Dialog>
);
};
export default ChangeEmailDialog;

View File

@@ -121,7 +121,7 @@ const CreateAccountDialog = ({
if (!canCreateAccount) return;
try {
await onCreateAccount({
email,
email: email.trim(),
password,
username,
getNewsletterEmail,
@@ -194,50 +194,68 @@ const CreateAccountDialog = ({
</LineStackLayout>
</Column>
<div style={styles.formContainer}>
<ColumnStackLayout noMargin>
<UsernameField
value={username}
onChange={(e, value) => {
setUsername(value);
}}
allowEmpty
onAvailabilityChecked={setUsernameAvailability}
onAvailabilityCheckLoading={setIsValidatingUsername}
isValidatingUsername={isValidatingUsername}
disabled={createAccountInProgress}
/>
<TextField
value={email}
floatingLabelText={<Trans>Email</Trans>}
errorText={getEmailErrorText(error)}
fullWidth
required
onChange={(e, value) => {
setEmail(value);
}}
disabled={createAccountInProgress}
/>
<TextField
value={password}
floatingLabelText={<Trans>Password</Trans>}
errorText={getPasswordErrorText(error)}
type="password"
fullWidth
required
onChange={(e, value) => {
setPassword(value);
}}
disabled={createAccountInProgress}
/>
<Checkbox
label={<Trans>I want to receive the GDevelop Newsletter</Trans>}
checked={getNewsletterEmail}
onCheck={(e, value) => {
setGetNewsletterEmail(value);
}}
disabled={createAccountInProgress}
/>
</ColumnStackLayout>
<form
onSubmit={event => {
// Prevent browser to navigate on form submission.
event.preventDefault();
createAccount();
}}
autoComplete="on"
name="createAccount"
>
<ColumnStackLayout noMargin>
<UsernameField
value={username}
onChange={(e, value) => {
setUsername(value);
}}
allowEmpty
onAvailabilityChecked={setUsernameAvailability}
onAvailabilityCheckLoading={setIsValidatingUsername}
isValidatingUsername={isValidatingUsername}
disabled={createAccountInProgress}
/>
<TextField
value={email}
floatingLabelText={<Trans>Email</Trans>}
errorText={getEmailErrorText(error)}
fullWidth
required
onChange={(e, value) => {
setEmail(value);
}}
onBlur={event => {
setEmail(event.currentTarget.value.trim());
}}
disabled={createAccountInProgress}
/>
<TextField
value={password}
floatingLabelText={<Trans>Password</Trans>}
errorText={getPasswordErrorText(error)}
type="password"
fullWidth
required
onChange={(e, value) => {
setPassword(value);
}}
disabled={createAccountInProgress}
/>
<Checkbox
label={<Trans>I want to receive the GDevelop Newsletter</Trans>}
checked={getNewsletterEmail}
onCheck={(e, value) => {
setGetNewsletterEmail(value);
}}
disabled={createAccountInProgress}
/>
{/*
This input is needed so that the browser submits the form when
Enter key is pressed. See https://stackoverflow.com/questions/4196681/form-not-submitting-when-pressing-enter
*/}
<input type="submit" value="Submit" style={{ display: 'none' }} />
</ColumnStackLayout>
</form>
</div>
<BackgroundText>
<MarkdownText

View File

@@ -5,6 +5,7 @@ import * as React from 'react';
import RaisedButton from '../UI/RaisedButton';
import { Column, Line } from '../UI/Grid';
import {
hasValidSubscriptionPlan,
type CurrentUsage,
type Subscription,
} from '../Utils/GDevelopServices/Usage';
@@ -27,8 +28,9 @@ const CurrentUsageDisplayer = ({
SubscriptionSuggestionContext
);
if (!currentUsage) return <PlaceholderLoader />;
const hasSubscription = subscription && !!subscription.planId;
const noSubscription = subscription && !subscription.planId;
const hasSubscription = hasValidSubscriptionPlan(subscription);
const loadedButHasNoSubscription =
subscription && !hasValidSubscriptionPlan(subscription);
return (
<Column noMargin>
@@ -58,14 +60,14 @@ const CurrentUsageDisplayer = ({
/>
</Line>
)}
{noSubscription && (
{loadedButHasNoSubscription && (
<Text>
<Trans>
You don't have a subscription. Get one to increase the limits!
</Trans>
</Text>
)}
{noSubscription && (
{loadedButHasNoSubscription && (
<Line justifyContent="center" alignItems="center">
<RaisedButton
label={<Trans>Get a subscription</Trans>}

View File

@@ -124,67 +124,82 @@ const EditProfileDialog = ({
onApply={edit}
open
>
<ColumnStackLayout noMargin>
<UsernameField
initialUsername={profile.username}
value={username}
onChange={(e, value) => {
setUsername(value);
}}
errorText={getUsernameErrorText(error)}
onAvailabilityChecked={setUsernameAvailability}
onAvailabilityCheckLoading={setIsValidatingUsername}
isValidatingUsername={isValidatingUsername}
disabled={updateProfileInProgress}
/>
<TextField
value={description}
floatingLabelText={<Trans>Bio</Trans>}
fullWidth
multiline
rows={3}
rowsMax={5}
translatableHintText={t`What are you using GDevelop for?`}
onChange={(e, value) => {
setDescription(value);
}}
disabled={updateProfileInProgress}
floatingLabelFixed
/>
<TextField
value={donateLink}
floatingLabelText={<Trans>Donate link</Trans>}
fullWidth
translatableHintText={t`Do you have a Patreon? Ko-fi? Paypal?`}
onChange={(e, value) => {
setDonateLink(value);
}}
disabled={updateProfileInProgress}
floatingLabelFixed
helperMarkdownText={i18n._(
t`Add a link to your donation page. It will be displayed on your Liluo.io profile and game pages.`
)}
errorText={donateLinkFormattingError}
/>
<Checkbox
label={<Trans>I want to receive the GDevelop Newsletter</Trans>}
checked={getNewsletterEmail}
onCheck={(e, value) => {
setGetNewsletterEmail(value);
}}
disabled={updateProfileInProgress}
/>
<Checkbox
label={
<Trans>I want to receive weekly stats about my games</Trans>
}
checked={getGameStatsEmail}
onCheck={(e, value) => {
setGetGameStatsEmail(value);
}}
disabled={updateProfileInProgress}
/>
</ColumnStackLayout>
<form
onSubmit={event => {
// Prevent browser to navigate on form submission.
event.preventDefault();
edit();
}}
autoComplete="on"
name="editProfile"
>
<ColumnStackLayout noMargin>
<UsernameField
initialUsername={profile.username}
value={username}
onChange={(e, value) => {
setUsername(value);
}}
errorText={getUsernameErrorText(error)}
onAvailabilityChecked={setUsernameAvailability}
onAvailabilityCheckLoading={setIsValidatingUsername}
isValidatingUsername={isValidatingUsername}
disabled={updateProfileInProgress}
/>
<TextField
value={description}
floatingLabelText={<Trans>Bio</Trans>}
fullWidth
multiline
rows={3}
rowsMax={5}
translatableHintText={t`What are you using GDevelop for?`}
onChange={(e, value) => {
setDescription(value);
}}
disabled={updateProfileInProgress}
floatingLabelFixed
/>
<TextField
value={donateLink}
floatingLabelText={<Trans>Donate link</Trans>}
fullWidth
translatableHintText={t`Do you have a Patreon? Ko-fi? Paypal?`}
onChange={(e, value) => {
setDonateLink(value);
}}
disabled={updateProfileInProgress}
floatingLabelFixed
helperMarkdownText={i18n._(
t`Add a link to your donation page. It will be displayed on your Liluo.io profile and game pages.`
)}
errorText={donateLinkFormattingError}
/>
<Checkbox
label={<Trans>I want to receive the GDevelop Newsletter</Trans>}
checked={getNewsletterEmail}
onCheck={(e, value) => {
setGetNewsletterEmail(value);
}}
disabled={updateProfileInProgress}
/>
<Checkbox
label={
<Trans>I want to receive weekly stats about my games</Trans>
}
checked={getGameStatsEmail}
onCheck={(e, value) => {
setGetGameStatsEmail(value);
}}
disabled={updateProfileInProgress}
/>
{/*
This input is needed so that the browser submits the form when
Enter key is pressed. See https://stackoverflow.com/questions/4196681/form-not-submitting-when-pressing-enter
*/}
<input type="submit" value="Submit" style={{ display: 'none' }} />
</ColumnStackLayout>
</form>
</Dialog>
)}
</I18n>

View File

@@ -0,0 +1,137 @@
// @flow
import { Trans } from '@lingui/macro';
import React from 'react';
import FlatButton from '../UI/FlatButton';
import Dialog, { DialogPrimaryButton } from '../UI/Dialog';
import { type AuthenticatedUser } from './AuthenticatedUserContext';
import BackgroundText from '../UI/BackgroundText';
import Text from '../UI/Text';
import { useInterval } from '../Utils/UseInterval';
import CircularProgress from '../UI/CircularProgress';
import GDevelopGLogo from '../UI/CustomSvgIcons/GDevelopGLogo';
import { ColumnStackLayout, LineStackLayout } from '../UI/Layout';
import RaisedButton from '../UI/RaisedButton';
import UserVerified from '../UI/CustomSvgIcons/UserVerified';
type Props = {|
onClose: () => void,
authenticatedUser: AuthenticatedUser,
sendEmailAutomatically: boolean,
showSendEmailButton: boolean,
onSendEmail: () => Promise<void>,
|};
export default function EmailVerificationDialog({
onClose,
authenticatedUser,
sendEmailAutomatically,
showSendEmailButton,
onSendEmail,
}: Props) {
const isVerified =
!!authenticatedUser.firebaseUser &&
!!authenticatedUser.firebaseUser.emailVerified;
const [hasSentEmailManually, setHasSentEmailManually] = React.useState(false);
// Send the email once on dialog opening if configured as so.
React.useEffect(
() => {
if (!isVerified && sendEmailAutomatically) {
onSendEmail();
}
},
[isVerified, onSendEmail, sendEmailAutomatically]
);
// Check every 5 seconds if the email has been verified.
useInterval(
() => {
authenticatedUser.onRefreshFirebaseProfile().catch(() => {
// Ignore any error, will be retried anyway.
});
},
isVerified ? null : 5000
);
return (
<Dialog
title={null} // This dialog has a custom design to be more welcoming, the title is set in the content.
actions={[
isVerified ? (
<DialogPrimaryButton
label={<Trans>Done!</Trans>}
key="close"
primary
onClick={onClose}
/>
) : (
<FlatButton
label={<Trans>I'll do it later</Trans>}
key="close"
primary={false}
onClick={onClose}
/>
),
]}
maxWidth="sm"
open
onRequestClose={onClose}
onApply={onClose}
>
<ColumnStackLayout
noMargin
expand
justifyContent="center"
alignItems="center"
>
<GDevelopGLogo fontSize="large" />
{!authenticatedUser.firebaseUser ? null : !authenticatedUser
.firebaseUser.emailVerified ? (
<ColumnStackLayout noMargin alignItems="center">
<Text size="title" align="center">
<Trans>Confirm your email</Trans>
</Text>
<Text align="center">
<Trans>
You will get access to special discounts on the GDevelop asset
store, as well as weekly stats about your games.
</Trans>
</Text>
{!hasSentEmailManually && showSendEmailButton ? (
<RaisedButton
primary
label={<Trans>Send it again</Trans>}
onClick={() => {
setHasSentEmailManually(true);
onSendEmail();
}}
/>
) : (
<BackgroundText>
<LineStackLayout justifyContent="center" alignItems="center">
<CircularProgress size={20} />
<Text>
<Trans>
Email sent to
{authenticatedUser.firebaseUser.email}, waiting for
validation...
</Trans>
</Text>
</LineStackLayout>
</BackgroundText>
)}
</ColumnStackLayout>
) : (
<LineStackLayout justifyContent="center" alignItems="center">
<UserVerified />
<Text size="title" align="center">
<Trans>Email verified</Trans>
</Text>
</LineStackLayout>
)}
</ColumnStackLayout>
</Dialog>
);
}

View File

@@ -1,92 +0,0 @@
// @flow
import { Trans } from '@lingui/macro';
import React from 'react';
import FlatButton from '../UI/FlatButton';
import Dialog, { DialogPrimaryButton } from '../UI/Dialog';
import { type AuthenticatedUser } from './AuthenticatedUserContext';
import { Column, Line, Spacer } from '../UI/Grid';
import BackgroundText from '../UI/BackgroundText';
import VerifiedUser from '@material-ui/icons/VerifiedUser';
import Text from '../UI/Text';
import { useInterval } from '../Utils/UseInterval';
import CircularProgress from '../UI/CircularProgress';
type Props = {|
onClose: () => void,
authenticatedUser: AuthenticatedUser,
|};
export default function EmailVerificationPendingDialog({
onClose,
authenticatedUser,
}: Props) {
const isVerified =
!!authenticatedUser &&
!!authenticatedUser.firebaseUser &&
!!authenticatedUser.firebaseUser.emailVerified;
useInterval(
() => {
authenticatedUser.onRefreshFirebaseProfile().catch(() => {
// Ignore any error, will be retried anyway.
});
},
isVerified ? null : 3900
);
return (
<Dialog
title={<Trans>Verifying your email</Trans>}
actions={[
isVerified ? (
<DialogPrimaryButton
label={<Trans>Done!</Trans>}
key="close"
primary
onClick={onClose}
/>
) : (
<FlatButton
label={<Trans>Cancel and close</Trans>}
key="close"
primary={false}
onClick={onClose}
/>
),
]}
maxWidth="sm"
open
onRequestClose={onClose}
onApply={onClose}
>
{!isVerified ? (
<Column>
<Line justifyContent="center" alignItems="center">
<CircularProgress size={20} />
<Spacer />
<Text>Waiting for the email verification...</Text>
</Line>
<Spacer />
<Line justifyContent="center">
<BackgroundText>
<Trans>
Check your inbox and click the link to verify your email address
- and come back here when it's done.
</Trans>
</BackgroundText>
</Line>
</Column>
) : (
<Column>
<Line justifyContent="center" alignItems="center">
<VerifiedUser />
<Spacer />
<Text>
<Trans>Your email is now verified!</Trans>
</Text>
</Line>
</Column>
)}
</Dialog>
);
}

View File

@@ -10,6 +10,8 @@ import { type ForgotPasswordForm } from '../Utils/GDevelopServices/Authenticatio
import LeftLoader from '../UI/LeftLoader';
import Text from '../UI/Text';
export const emailRegex = /^(.+)@(.+)$/;
type Props = {|
onClose: () => void,
onForgotPassword: ForgotPasswordForm => Promise<void>,
@@ -19,10 +21,13 @@ const ForgotPasswordDialog = ({ onClose, onForgotPassword }: Props) => {
const [email, setEmail] = React.useState('');
const [resetDone, setResetDone] = React.useState(false);
const [resetInProgress, setResetInProgress] = React.useState(false);
const emailRegex = /^(.+)@(.+)$/;
const isEmailValid = emailRegex.test(email);
const [isEmailValid, setIsEmailValid] = React.useState<boolean>(true);
const doResetPassword = async () => {
const trimmedEmail = email.trim();
setEmail(trimmedEmail);
setIsEmailValid(emailRegex.test(trimmedEmail));
if (resetInProgress || !email || !isEmailValid) return;
setResetInProgress(true);
@@ -61,8 +66,8 @@ const ForgotPasswordDialog = ({ onClose, onForgotPassword }: Props) => {
maxWidth="xs"
onApply={doResetPassword}
>
<Column noMargin>
{resetDone ? (
{resetDone ? (
<Column noMargin>
<Text>
<Trans>
You should have received an email containing instructions to reset
@@ -70,18 +75,43 @@ const ForgotPasswordDialog = ({ onClose, onForgotPassword }: Props) => {
password in GDevelop.
</Trans>
</Text>
) : (
<TextField
autoFocus="desktop"
value={email}
floatingLabelText={<Trans>Email</Trans>}
onChange={(e, value) => {
setEmail(value);
}}
fullWidth
/>
)}
</Column>
</Column>
) : (
<form
onSubmit={event => {
// Prevent browser to navigate on form submission.
event.preventDefault();
doResetPassword();
}}
autoComplete="on"
name="resetPassword"
>
<Column noMargin>
<TextField
autoFocus="desktop"
value={email}
floatingLabelText={<Trans>Email</Trans>}
onChange={(e, value) => {
if (!isEmailValid) setIsEmailValid(true);
setEmail(value);
}}
errorText={
!isEmailValid ? (
<Trans>Invalid email address.</Trans>
) : (
undefined
)
}
fullWidth
onBlur={event => {
const trimmedEmail = event.currentTarget.value.trim();
setEmail(trimmedEmail);
setIsEmailValid(emailRegex.test(trimmedEmail));
}}
/>
</Column>
</form>
)}
</Dialog>
);
};

View File

@@ -55,7 +55,7 @@ const LoginDialog = ({
if (loginInProgress) return;
onLogin({
email,
email: email.trim(),
password,
});
};
@@ -105,7 +105,7 @@ const LoginDialog = ({
alignItems="center"
>
<GDevelopGLogo fontSize="large" />
<Text size="title">
<Text size="title" align="center">
<Trans>Log in to your account</Trans>
</Text>
<Column noMargin alignItems="center">
@@ -123,30 +123,48 @@ const LoginDialog = ({
</Link>
</Column>
<div style={styles.formContainer}>
<ColumnStackLayout noMargin>
<TextField
autoFocus="desktop"
value={email}
floatingLabelText={<Trans>Email</Trans>}
errorText={getEmailErrorText(error)}
onChange={(e, value) => {
setEmail(value);
}}
fullWidth
disabled={loginInProgress}
/>
<TextField
value={password}
floatingLabelText={<Trans>Password</Trans>}
errorText={getPasswordErrorText(error)}
type="password"
onChange={(e, value) => {
setPassword(value);
}}
fullWidth
disabled={loginInProgress}
/>
</ColumnStackLayout>
<form
onSubmit={event => {
// Prevent browser to navigate on form submission.
event.preventDefault();
doLogin();
}}
autoComplete="on"
name="login"
>
<ColumnStackLayout noMargin>
<TextField
autoFocus="desktop"
value={email}
floatingLabelText={<Trans>Email</Trans>}
errorText={getEmailErrorText(error)}
onChange={(e, value) => {
setEmail(value);
}}
onBlur={event => {
setEmail(event.currentTarget.value.trim());
}}
fullWidth
disabled={loginInProgress}
/>
<TextField
value={password}
floatingLabelText={<Trans>Password</Trans>}
errorText={getPasswordErrorText(error)}
type="password"
onChange={(e, value) => {
setPassword(value);
}}
fullWidth
disabled={loginInProgress}
/>
{/*
This input is needed so that the browser submits the form when
Enter key is pressed. See https://stackoverflow.com/questions/4196681/form-not-submitting-when-pressing-enter
*/}
<input type="submit" value="Submit" style={{ display: 'none' }} />
</ColumnStackLayout>
</form>
</div>
<Link
href=""

View File

@@ -2,15 +2,15 @@
import { I18n } from '@lingui/react';
import { t, Trans } from '@lingui/macro';
import React from 'react';
import FlatButton from '../../UI/FlatButton';
import Dialog, { DialogPrimaryButton } from '../../UI/Dialog';
import { type AuthenticatedUser } from '../AuthenticatedUserContext';
import { ColumnStackLayout } from '../../UI/Layout';
import SemiControlledTextField from '../../UI/SemiControlledTextField';
import LeftLoader from '../../UI/LeftLoader';
import { redeemCode } from '../../Utils/GDevelopServices/Usage';
import { extractGDevelopApiErrorStatusAndCode } from '../../Utils/GDevelopServices/Errors';
import AlertMessage from '../../UI/AlertMessage';
import FlatButton from '../UI/FlatButton';
import Dialog, { DialogPrimaryButton } from '../UI/Dialog';
import { type AuthenticatedUser } from './AuthenticatedUserContext';
import { ColumnStackLayout } from '../UI/Layout';
import SemiControlledTextField from '../UI/SemiControlledTextField';
import LeftLoader from '../UI/LeftLoader';
import { redeemCode } from '../Utils/GDevelopServices/Usage';
import { extractGDevelopApiErrorStatusAndCode } from '../Utils/GDevelopServices/Errors';
import AlertMessage from '../UI/AlertMessage';
type Props = {|
onClose: (hasJustRedeemedCode: boolean) => Promise<void>,
@@ -122,37 +122,49 @@ export default function RedeemCodeDialog({
maxWidth="sm"
open
>
<ColumnStackLayout noMargin>
<SemiControlledTextField
value={redemptionCode}
onChange={setRedemptionCode}
translatableHintText={t`Enter your code here`}
floatingLabelText={<Trans>Redemption code</Trans>}
floatingLabelFixed
errorText={getRedeemCodeErrorText(error)}
/>
{!subscription ||
!subscription.planId ? null : !!subscription.redemptionCodeValidUntil ? (
<AlertMessage kind="warning">
<Trans>
You currently have a subscription, applied thanks to a
redemption code, valid until{' '}
{i18n.date(subscription.redemptionCodeValidUntil)}. If you
redeem another code, your existing subscription will be
canceled and not redeemable anymore!
</Trans>
</AlertMessage>
) : (
<AlertMessage kind="info">
<Trans>
You currently have a subscription. If you redeem a code, the
existing subscription will be cancelled and replaced by the
one given by the code.
</Trans>
</AlertMessage>
)}
</ColumnStackLayout>
<form
onSubmit={event => {
event.preventDefault();
onRedeemCode();
}}
autoComplete="on"
name="redeemSubscriptionCoupon"
>
<ColumnStackLayout noMargin>
<SemiControlledTextField
value={redemptionCode}
onChange={setRedemptionCode}
translatableHintText={t`Enter your code here`}
floatingLabelText={<Trans>Redemption code</Trans>}
floatingLabelFixed
errorText={getRedeemCodeErrorText(error)}
autoFocus="desktop"
/>
{!subscription ||
!subscription.planId ? null : !!subscription.redemptionCodeValidUntil ? ( // No subscription, do not show a warning.
subscription.redemptionCodeValidUntil > Date.now() ? ( // Has valid subscription.
<AlertMessage kind="warning">
<Trans>
You currently have a subscription, applied thanks to a
redemption code, valid until{' '}
{i18n.date(subscription.redemptionCodeValidUntil)}. If you
redeem another code, your existing subscription will be
canceled and not redeemable anymore!
</Trans>
</AlertMessage>
) : null // Has expired subscription, do not show a warning.
) : (
// Has a subscription, but not applied thanks to a redemption code.
<AlertMessage kind="info">
<Trans>
You currently have a subscription. If you redeem a code, the
existing subscription will be cancelled and replaced by the
one given by the code.
</Trans>
</AlertMessage>
)}
</ColumnStackLayout>
</form>
</Dialog>
)}
</I18n>

View File

@@ -14,6 +14,7 @@ import {
} from '../../Utils/Analytics/EventSender';
import { SubscriptionSuggestionContext } from './SubscriptionSuggestionContext';
import Text from '../../UI/Text';
import { hasValidSubscriptionPlan } from '../../Utils/GDevelopServices/Usage';
export type SubscriptionCheckerInterface = {|
checkUserHasSubscription: () => boolean,
@@ -51,12 +52,9 @@ const SubscriptionChecker = React.forwardRef<
};
const checkUserHasSubscription = () => {
if (authenticatedUser.subscription) {
const hasPlan = !!authenticatedUser.subscription.planId;
if (hasPlan) {
setDialogOpen(false);
return true;
}
if (hasValidSubscriptionPlan(authenticatedUser.subscription)) {
setDialogOpen(false);
return true;
}
setDialogOpen(true);

View File

@@ -37,6 +37,20 @@ type Props = {
isManageSubscriptionLoading: boolean,
};
/**
* Here are the possible cases:
* - Subscription null: loading. (The backend always return a subscription)
* - Subscription with no plan: show a message to invite the user to subscribe.
* - Subscription bought:
* - with Stripe, they can easily manage it only as well as upgrade/downgrade.
* - with Paypal, in order to upgrade/downgrade, the subscription need to be canceled first.
* - Subscription with a redemption code:
* - If the code is still valid, show a message to indicate when it will expire
* and show a warning if trying to buy a new one as it will cancel the current one.
* - If the code is expired, show a message to invite the user to re-subscribe.
* We will need to cancel the current expired subscription, but don't show a warning.
*/
const SubscriptionDetails = ({
subscription,
isManageSubscriptionLoading,
@@ -57,28 +71,41 @@ const SubscriptionDetails = ({
[subscription]
);
return subscription ? (
const redemptionCodeExpirationDate =
subscription && subscription.redemptionCodeValidUntil;
const isSubscriptionExpired =
!!redemptionCodeExpirationDate && redemptionCodeExpirationDate < Date.now();
if (!subscription) return <PlaceholderLoader />;
return (
<Column noMargin>
<Line alignItems="center">
<Text size="block-title">My online services subscription</Text>
<Text size="block-title">
<Trans>My online services subscription</Trans>
</Text>
</Line>
{userPlan && userPlan.planId ? (
{userPlan && userPlan.planId && !isSubscriptionExpired ? (
<ColumnStackLayout noMargin>
<PlanCard
plan={userPlan}
hidePrice={!!subscription.redemptionCodeValidUntil}
hidePrice={!!redemptionCodeExpirationDate}
actions={[
<LeftLoader
key="manage-online"
isLoading={isManageSubscriptionLoading}
>
<FlatButton
label={<Trans>Manage online</Trans>}
primary
onClick={onManageSubscription}
disabled={isManageSubscriptionLoading}
/>
</LeftLoader>,
!redemptionCodeExpirationDate ? (
<LeftLoader
key="manage-online"
isLoading={isManageSubscriptionLoading}
>
<FlatButton
label={<Trans>Manage online</Trans>}
primary
onClick={onManageSubscription}
disabled={isManageSubscriptionLoading}
/>
</LeftLoader>
) : (
undefined
),
<RaisedButton
key="manage"
label={<Trans>Manage subscription</Trans>}
@@ -88,11 +115,11 @@ const SubscriptionDetails = ({
}
disabled={isManageSubscriptionLoading}
/>,
]}
].filter(Boolean)}
isHighlighted={false}
background="medium"
/>
{!!subscription.redemptionCodeValidUntil && (
{!!redemptionCodeExpirationDate && (
<I18n>
{({ i18n }) => (
<Paper background="dark" variant="outlined">
@@ -118,12 +145,12 @@ const SubscriptionDetails = ({
)}
</ColumnStackLayout>
) : (
<>
<Paper background="medium" variant="outlined" style={styles.paper}>
<LineStackLayout alignItems="center">
<img src="res/diamond.svg" style={styles.diamondIcon} alt="" />
<Column expand>
<Line>
<Paper background="medium" variant="outlined" style={styles.paper}>
<LineStackLayout alignItems="center">
<img src="res/diamond.svg" style={styles.diamondIcon} alt="" />
<Column expand>
<Line>
{!isSubscriptionExpired ? (
<Column noMargin>
<Text noMargin>
<Trans>
@@ -138,24 +165,30 @@ const SubscriptionDetails = ({
</Trans>
</Text>
</Column>
</Line>
<Line justifyContent="flex-end">
<RaisedButton
label={<Trans>Choose a subscription</Trans>}
primary
onClick={() =>
openSubscriptionDialog({ reason: 'Consult profile' })
}
/>
</Line>
</Column>
</LineStackLayout>
</Paper>
</>
) : (
<Text noMargin>
<Trans>
Oh no! Your subscription from the redemption code has
expired. You can renew it by redeeming a new code or
getting a new subscription.
</Trans>
</Text>
)}
</Line>
<Line justifyContent="flex-end">
<RaisedButton
label={<Trans>Choose a subscription</Trans>}
primary
onClick={() =>
openSubscriptionDialog({ reason: 'Consult profile' })
}
/>
</Line>
</Column>
</LineStackLayout>
</Paper>
)}
</Column>
) : (
<PlaceholderLoader />
);
};

View File

@@ -13,6 +13,7 @@ import {
getRedirectToCheckoutUrl,
canSeamlesslyChangeSubscription,
businessPlan,
hasValidSubscriptionPlan,
} from '../../Utils/GDevelopServices/Usage';
import EmptyMessage from '../../UI/EmptyMessage';
import { showErrorBox } from '../../UI/Messages/MessageBox';
@@ -60,11 +61,17 @@ const seamlesslyChangeConfirmationTexts = {
dismissButtonLabel: t`Go back`,
};
const cancelAndChangeConfirmationTexts = {
title: t`Cancel then get a new subscription`,
message: t`To get a new subscription, we need to cancel your existing one before you can pay for the new one. The change will be immediate but your payment will NOT be pro-rated (you will have to pay as for a new subscription).`,
title: t`Update your subscription`,
message: t`To get this new subscription, we need to cancel your existing one before you can pay for the new one. The change will be immediate but your payment will NOT be pro-rated (you will have to pay as for a new subscription).`,
confirmButtonLabel: t`Cancel my subscription`,
dismissButtonLabel: t`Go back`,
};
const cancelAndChangeWithValidRedeemedCodeConfirmationTexts = {
title: t`Update your subscription`,
message: t`To get this new subscription, we need to cancel your existing one before you can pay for the new one. The change will be immediate. You will also lose your redeemed code.`,
confirmButtonLabel: t`Update my subscription`,
dismissButtonLabel: t`Go back`,
};
type Props = {|
open: boolean,
@@ -100,7 +107,7 @@ export default function SubscriptionDialog({
[open, analyticsMetadata]
);
const choosePlan = async (
const buyUpdateOrCancelPlan = async (
i18n: I18nType,
plan: { planId: null | string }
) => {
@@ -108,7 +115,7 @@ export default function SubscriptionDialog({
if (!profile || !subscription) return;
sendChoosePlanClicked(plan.planId);
// Subscribing from an account without a subscription.
// Subscribing from an account without a subscription
if (!subscription.planId) {
setSubscriptionPendingDialogOpen(true);
Window.openExternalURL(
@@ -146,16 +153,33 @@ export default function SubscriptionDialog({
setIsChangingSubscription(false);
}
} else {
const needToCancelSubscription = !canSeamlesslyChangeSubscription(
subscription
);
const hasValidRedeemedSubscription =
!!subscription.redemptionCodeValidUntil &&
subscription.redemptionCodeValidUntil > Date.now();
const hasExpiredRedeemedSubscription =
!!subscription.redemptionCodeValidUntil &&
subscription.redemptionCodeValidUntil < Date.now();
const shouldShowAlert =
(needToCancelSubscription && !hasExpiredRedeemedSubscription) || // we don't show an alert if the redeemed code is expired
hasValidRedeemedSubscription;
// Changing the existing subscription.
const isSeamlessChange = canSeamlesslyChangeSubscription(subscription);
const confirmDialogTexts = isSeamlessChange
? seamlesslyChangeConfirmationTexts
: cancelAndChangeConfirmationTexts;
const confirmDialogTexts =
!needToCancelSubscription || hasExpiredRedeemedSubscription
? seamlesslyChangeConfirmationTexts
: hasValidRedeemedSubscription
? cancelAndChangeWithValidRedeemedCodeConfirmationTexts
: cancelAndChangeConfirmationTexts;
const answer = await showConfirmation(confirmDialogTexts);
if (!answer) return;
if (shouldShowAlert) {
const answer = await showConfirmation(confirmDialogTexts);
if (!answer) return;
}
if (isSeamlessChange) {
if (!needToCancelSubscription) {
// Changing the existing subscription without asking for payment details again.
setIsChangingSubscription(true);
try {
@@ -212,6 +236,8 @@ export default function SubscriptionDialog({
!authenticatedUser.profile ||
isChangingSubscription;
const isPlanValid = hasValidSubscriptionPlan(authenticatedUser.subscription);
return (
<I18n>
{({ i18n }) => (
@@ -262,13 +288,16 @@ export default function SubscriptionDialog({
!!authenticatedUser.subscription &&
authenticatedUser.subscription.planId === plan.planId;
// If no plan (free usage), do not display button.
const button = !plan.planId ? null : isCurrentPlan ? (
const button = !plan.planId ? null : isCurrentPlan &&
isPlanValid ? (
<React.Fragment key="cancel">
<LeftLoader isLoading={isLoading}>
<FlatButton
disabled={isLoading}
label={<Trans>Cancel your subscription</Trans>}
onClick={() => choosePlan(i18n, { planId: null })}
onClick={() =>
buyUpdateOrCancelPlan(i18n, { planId: null })
}
/>
</LeftLoader>
</React.Fragment>
@@ -279,7 +308,7 @@ export default function SubscriptionDialog({
primary
disabled={isLoading}
label={<Trans>Choose this plan</Trans>}
onClick={() => choosePlan(i18n, plan)}
onClick={() => buyUpdateOrCancelPlan(i18n, plan)}
/>
</LeftLoader>
</React.Fragment>
@@ -290,7 +319,7 @@ export default function SubscriptionDialog({
plan={plan}
actions={[button]}
isPending={isLoading}
isHighlighted={isCurrentPlan}
isHighlighted={isCurrentPlan} // highlight the plan even if it's expired.
background="medium"
/>
);
@@ -351,8 +380,11 @@ export default function SubscriptionDialog({
]}
>
<Text>
It's free and you'll get access to online services: cloud
projects, leaderboards, player feedbacks, cloud builds...
<Trans>
It is free and you will get access to online services:
cloud projects, leaderboards, player feedbacks, cloud
builds...
</Trans>
</Text>
</Dialog>
)}

View File

@@ -76,7 +76,9 @@ export default function SubscriptionPendingDialog({
<Line justifyContent="center" alignItems="center">
<CircularProgress size={20} />
<Spacer />
<Text>Waiting for the subscription confirmation...</Text>
<Text>
<Trans>Waiting for the subscription confirmation...</Trans>
</Text>
</Line>
<Spacer />
<Line justifyContent="center">

View File

@@ -23,6 +23,7 @@ import Text from '../UI/Text';
import AlertMessage from '../UI/AlertMessage';
import GetSubscriptionCard from '../Profile/Subscription/GetSubscriptionCard';
import AuthenticatedUserContext from '../Profile/AuthenticatedUserContext';
import { hasValidSubscriptionPlan } from '../Utils/GDevelopServices/Usage';
type Props = {|
loadingScreen: gdLoadingScreen,
@@ -55,8 +56,9 @@ export const LoadingScreenEditor = ({
const subscriptionChecker = React.useRef<?SubscriptionCheckerInterface>(null);
const authenticatedUser = React.useContext(AuthenticatedUserContext);
const forceUpdate = useForceUpdate();
const shouldDisplayGetSubscriptionCard =
!authenticatedUser.subscription || !authenticatedUser.subscription.planId;
const shouldDisplayGetSubscriptionCard = !hasValidSubscriptionPlan(
authenticatedUser.subscription
);
const onUpdate = () => {
forceUpdate();
@@ -180,8 +182,10 @@ export const LoadingScreenEditor = ({
{shouldDisplayGetSubscriptionCard && (
<GetSubscriptionCard subscriptionDialogOpeningReason="Disable GDevelop splash at startup">
<Text>
Get a silver or gold subscription to disable GDevelop
branding.
<Trans>
Get a silver or gold subscription to disable GDevelop
branding.
</Trans>
</Text>
</GetSubscriptionCard>
)}

View File

@@ -192,7 +192,11 @@ export const Item = ({
<div
style={{ display: 'inline-flex', width: '100%', alignItems: 'center' }}
>
<span style={textEllipsisStyle} title={primaryText}>
<span
style={textEllipsisStyle}
title={primaryText}
className="notranslate"
>
{primaryText}
</span>
{textEndAdornment && (

View File

@@ -9,7 +9,7 @@ import {
} from '../../Utils/BlobDownloader';
import { makeTestProject } from '../../fixtures/TestProject';
import { moveUrlResourcesToCloudFilesIfPrivate } from './CloudResourceFetcher';
import { fakeIndieAuthenticatedUser } from '../../fixtures/GDevelopServicesTestData';
import { fakeSilverAuthenticatedUser } from '../../fixtures/GDevelopServicesTestData';
const gd: libGDevelop = global.gd;
jest.mock('../../Utils/GDevelopServices/Project');
@@ -61,7 +61,7 @@ const makeMoveUrlResourcesToCloudFilesIfPrivateOptions = (
) => ({
project,
fileMetadata: { fileIdentifier: 'fake-cloud-project-id' },
authenticatedUser: fakeIndieAuthenticatedUser,
authenticatedUser: fakeSilverAuthenticatedUser,
onProgress: jest.fn(),
});
@@ -174,11 +174,11 @@ describe('CloudResourceFetcher', () => {
expect.any(Function) // onError
);
expect(getCredentialsForCloudProject).toHaveBeenCalledWith(
fakeIndieAuthenticatedUser,
fakeSilverAuthenticatedUser,
'fake-cloud-project-id'
);
expect(uploadProjectResourceFiles).toHaveBeenCalledWith(
fakeIndieAuthenticatedUser,
fakeSilverAuthenticatedUser,
'fake-cloud-project-id',
['some file', 'some other file'],
expect.any(Function) // onProgress

View File

@@ -20,7 +20,7 @@ const styles = {
};
type Props = {|
kind: 'info' | 'warning' | 'error',
kind?: 'info' | 'warning' | 'error',
children: React.Node,
onHide?: ?() => void,
renderLeftIcon?: () => React.Node,

Some files were not shown because too many files have changed in this diff Show More