mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
5 Commits
v5.3.195
...
feat/updat
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5193b617b3 | ||
![]() |
1bb473b0b0 | ||
![]() |
4376b4f36e | ||
![]() |
6ecbae9c35 | ||
![]() |
93e9fc6aed |
@@ -40,38 +40,6 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
|
||||
.AddDefaultBehavior("ScalableCapability::ScalableBehavior")
|
||||
.AddDefaultBehavior("OpacityCapability::OpacityBehavior");
|
||||
|
||||
// Deprecated
|
||||
obj.AddAction("String",
|
||||
_("Modify the text"),
|
||||
_("Modify the text of a Text object."),
|
||||
_("the text"),
|
||||
"",
|
||||
"res/actions/text24_black.png",
|
||||
"res/actions/text_black.png")
|
||||
.SetHidden()
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardOperatorParameters(
|
||||
"string",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(_("Text")))
|
||||
.SetFunctionName("SetString")
|
||||
.SetGetter("GetString");
|
||||
|
||||
// Deprecated
|
||||
obj.AddCondition("String",
|
||||
_("Compare the text"),
|
||||
_("Compare the text of a Text object."),
|
||||
_("the text"),
|
||||
"",
|
||||
"res/conditions/text24_black.png",
|
||||
"res/conditions/text_black.png")
|
||||
.SetHidden()
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardRelationalOperatorParameters(
|
||||
"string",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Text to compare to")))
|
||||
.SetFunctionName("GetString");
|
||||
|
||||
obj.AddAction("Font",
|
||||
_("Font"),
|
||||
_("Change the font of the text."),
|
||||
@@ -84,94 +52,6 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
|
||||
.AddParameter("police", _("Font"))
|
||||
.SetFunctionName("ChangeFont");
|
||||
|
||||
// Deprecated
|
||||
obj.AddCondition("ScaleX",
|
||||
_("Scale on X axis"),
|
||||
_("Compare the scale of the text on the X axis"),
|
||||
_("the scale on the X axis"),
|
||||
"Scale",
|
||||
"res/conditions/scaleWidth24_black.png",
|
||||
"res/conditions/scaleWidth_black.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardRelationalOperatorParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Scale to compare to (1 by default)")))
|
||||
.SetHidden()
|
||||
.SetFunctionName("GetScaleX");
|
||||
|
||||
// Deprecated
|
||||
obj.AddAction(
|
||||
"ScaleX",
|
||||
_("Scale on X axis"),
|
||||
_("Modify the scale of the text on the X axis (default scale is 1)"),
|
||||
_("the scale on the X axis"),
|
||||
_("Scale"),
|
||||
"res/actions/scaleWidth24_black.png",
|
||||
"res/actions/scaleWidth_black.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardOperatorParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Scale (1 by default)")))
|
||||
.SetHidden()
|
||||
.SetFunctionName("SetScaleX");
|
||||
|
||||
// Deprecated
|
||||
obj.AddCondition("ScaleY",
|
||||
_("Scale on Y axis"),
|
||||
_("Compare the scale of the text on the Y axis"),
|
||||
_("the scale on the Y axis"),
|
||||
"Scale",
|
||||
"res/conditions/scaleHeight24_black.png",
|
||||
"res/conditions/scaleHeight_black.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardRelationalOperatorParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Scale to compare to (1 by default)")))
|
||||
.SetHidden()
|
||||
.SetFunctionName("GetScaleY");
|
||||
|
||||
// Deprecated
|
||||
obj.AddAction(
|
||||
"ScaleY",
|
||||
_("Scale on Y axis"),
|
||||
_("Modify the scale of the text on the Y axis (default scale is 1)"),
|
||||
_("the scale on the Y axis"),
|
||||
_("Scale"),
|
||||
"res/actions/scaleHeight24_black.png",
|
||||
"res/actions/scaleHeight_black.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardOperatorParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Scale (1 by default)")))
|
||||
.SetHidden()
|
||||
.SetFunctionName("SetScaleY");
|
||||
|
||||
// Deprecated
|
||||
obj.AddAction(
|
||||
"Scale",
|
||||
_("Scale"),
|
||||
_("Modify the scale of the specified object (default scale is 1)"),
|
||||
_("the scale"),
|
||||
_("Scale"),
|
||||
"res/actions/scale24_black.png",
|
||||
"res/actions/scale_black.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardOperatorParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Scale (1 by default)")))
|
||||
.SetHidden()
|
||||
.SetFunctionName("SetScale");
|
||||
|
||||
obj.AddAction(
|
||||
"ChangeColor",
|
||||
_("Color"),
|
||||
@@ -355,43 +235,6 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Blur radius")));
|
||||
|
||||
// Deprecated
|
||||
obj.AddAction("Opacity",
|
||||
_("Text opacity"),
|
||||
_("Change the opacity of a Text. 0 is fully transparent, 255 "
|
||||
"is opaque (default)."),
|
||||
_("the opacity"),
|
||||
"",
|
||||
"res/actions/opacity24.png",
|
||||
"res/actions/opacity.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardOperatorParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Opacity (0-255)")))
|
||||
.SetFunctionName("SetOpacity")
|
||||
.SetGetter("GetOpacity")
|
||||
.SetHidden();
|
||||
|
||||
// Deprecated
|
||||
obj.AddCondition("Opacity",
|
||||
_("Opacity"),
|
||||
_("Compare the opacity of a Text object, between 0 (fully "
|
||||
"transparent) to 255 (opaque)."),
|
||||
_("the opacity"),
|
||||
"",
|
||||
"res/conditions/opacity24.png",
|
||||
"res/conditions/opacity.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardRelationalOperatorParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Opacity to compare to (0-255)")))
|
||||
.SetFunctionName("GetOpacity")
|
||||
.SetHidden();
|
||||
|
||||
obj.AddAction("SetSmooth",
|
||||
_("Smoothing"),
|
||||
_("Activate or deactivate text smoothing."),
|
||||
@@ -484,37 +327,6 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.SetFunctionName("IsUnderlined");
|
||||
|
||||
obj.AddAction("Angle",
|
||||
_("Angle"),
|
||||
_("Modify the angle of a Text object."),
|
||||
_("the angle"),
|
||||
_("Rotation"),
|
||||
"res/actions/rotate24_black.png",
|
||||
"res/actions/rotate_black.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardOperatorParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Angle (in degrees)")))
|
||||
.SetFunctionName("SetAngle")
|
||||
.SetGetter("GetAngle");
|
||||
|
||||
obj.AddCondition("Angle",
|
||||
_("Angle"),
|
||||
_("Compare the value of the angle of a Text object."),
|
||||
_("the angle"),
|
||||
_("Rotation"),
|
||||
"res/conditions/rotate24_black.png",
|
||||
"res/conditions/rotate_black.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardRelationalOperatorParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Angle to compare to (in degrees)")))
|
||||
.SetFunctionName("GetAngle");
|
||||
|
||||
obj.AddCondition("Padding",
|
||||
_("Padding"),
|
||||
_("Compare the number of pixels around a text object. If "
|
||||
@@ -628,6 +440,143 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
|
||||
"res/actions/textPadding_black.png")
|
||||
.AddParameter("object", _("Object"), "Text");
|
||||
|
||||
obj.AddExpressionAndConditionAndAction("number",
|
||||
"FontSize",
|
||||
_("Font size"),
|
||||
_("the font size of a text object"),
|
||||
_("the font size"),
|
||||
"",
|
||||
"res/conditions/characterSize24.png")
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardParameters("number", gd::ParameterOptions::MakeNewOptions());
|
||||
|
||||
// Support for deprecated "Size" actions/conditions:
|
||||
obj.AddDuplicatedAction("Size", "Text::SetFontSize").SetHidden();
|
||||
obj.AddDuplicatedCondition("Size", "Text::FontSize").SetHidden();
|
||||
|
||||
// Deprecated
|
||||
obj.AddAction("Angle",
|
||||
_("Angle"),
|
||||
_("Modify the angle of a Text object."),
|
||||
_("the angle"),
|
||||
_("Rotation"),
|
||||
"res/actions/rotate24_black.png",
|
||||
"res/actions/rotate_black.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardOperatorParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Angle (in degrees)")))
|
||||
.SetHidden()
|
||||
.SetFunctionName("SetAngle")
|
||||
.SetGetter("GetAngle");
|
||||
|
||||
// Deprecated
|
||||
obj.AddCondition("Angle",
|
||||
_("Angle"),
|
||||
_("Compare the value of the angle of a Text object."),
|
||||
_("the angle"),
|
||||
_("Rotation"),
|
||||
"res/conditions/rotate24_black.png",
|
||||
"res/conditions/rotate_black.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardRelationalOperatorParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Angle to compare to (in degrees)")))
|
||||
.SetHidden()
|
||||
.SetFunctionName("GetAngle");
|
||||
|
||||
// Deprecated
|
||||
obj.AddCondition("ScaleX",
|
||||
_("Scale on X axis"),
|
||||
_("Compare the scale of the text on the X axis"),
|
||||
_("the scale on the X axis"),
|
||||
"Scale",
|
||||
"res/conditions/scaleWidth24_black.png",
|
||||
"res/conditions/scaleWidth_black.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardRelationalOperatorParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Scale to compare to (1 by default)")))
|
||||
.SetHidden()
|
||||
.SetFunctionName("GetScaleX");
|
||||
|
||||
// Deprecated
|
||||
obj.AddAction(
|
||||
"ScaleX",
|
||||
_("Scale on X axis"),
|
||||
_("Modify the scale of the text on the X axis (default scale is 1)"),
|
||||
_("the scale on the X axis"),
|
||||
_("Scale"),
|
||||
"res/actions/scaleWidth24_black.png",
|
||||
"res/actions/scaleWidth_black.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardOperatorParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Scale (1 by default)")))
|
||||
.SetHidden()
|
||||
.SetFunctionName("SetScaleX");
|
||||
|
||||
// Deprecated
|
||||
obj.AddCondition("ScaleY",
|
||||
_("Scale on Y axis"),
|
||||
_("Compare the scale of the text on the Y axis"),
|
||||
_("the scale on the Y axis"),
|
||||
"Scale",
|
||||
"res/conditions/scaleHeight24_black.png",
|
||||
"res/conditions/scaleHeight_black.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardRelationalOperatorParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Scale to compare to (1 by default)")))
|
||||
.SetHidden()
|
||||
.SetFunctionName("GetScaleY");
|
||||
|
||||
// Deprecated
|
||||
obj.AddAction(
|
||||
"ScaleY",
|
||||
_("Scale on Y axis"),
|
||||
_("Modify the scale of the text on the Y axis (default scale is 1)"),
|
||||
_("the scale on the Y axis"),
|
||||
_("Scale"),
|
||||
"res/actions/scaleHeight24_black.png",
|
||||
"res/actions/scaleHeight_black.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardOperatorParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Scale (1 by default)")))
|
||||
.SetHidden()
|
||||
.SetFunctionName("SetScaleY");
|
||||
|
||||
// Deprecated
|
||||
obj.AddAction(
|
||||
"Scale",
|
||||
_("Scale"),
|
||||
_("Modify the scale of the specified object (default scale is 1)"),
|
||||
_("the scale"),
|
||||
_("Scale"),
|
||||
"res/actions/scale24_black.png",
|
||||
"res/actions/scale_black.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardOperatorParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Scale (1 by default)")))
|
||||
.SetHidden()
|
||||
.SetFunctionName("SetScale");
|
||||
|
||||
// Deprecated
|
||||
obj.AddExpression("ScaleX",
|
||||
_("X Scale of a Text object"),
|
||||
@@ -648,6 +597,43 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
|
||||
.SetHidden()
|
||||
.SetFunctionName("GetScaleY");
|
||||
|
||||
// Deprecated
|
||||
obj.AddAction("Opacity",
|
||||
_("Text opacity"),
|
||||
_("Change the opacity of a Text. 0 is fully transparent, 255 "
|
||||
"is opaque (default)."),
|
||||
_("the opacity"),
|
||||
"",
|
||||
"res/actions/opacity24.png",
|
||||
"res/actions/opacity.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardOperatorParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Opacity (0-255)")))
|
||||
.SetFunctionName("SetOpacity")
|
||||
.SetGetter("GetOpacity")
|
||||
.SetHidden();
|
||||
|
||||
// Deprecated
|
||||
obj.AddCondition("Opacity",
|
||||
_("Opacity"),
|
||||
_("Compare the opacity of a Text object, between 0 (fully "
|
||||
"transparent) to 255 (opaque)."),
|
||||
_("the opacity"),
|
||||
"",
|
||||
"res/conditions/opacity24.png",
|
||||
"res/conditions/opacity.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardRelationalOperatorParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Opacity to compare to (0-255)")))
|
||||
.SetFunctionName("GetOpacity")
|
||||
.SetHidden();
|
||||
|
||||
// Deprecated
|
||||
obj.AddExpression("Opacity",
|
||||
_("Opacity of a Text object"),
|
||||
@@ -658,30 +644,52 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
|
||||
.SetFunctionName("GetOpacity")
|
||||
.SetHidden();
|
||||
|
||||
// Deprecated
|
||||
obj.AddExpression("Angle",
|
||||
_("Angle"),
|
||||
_("Angle"),
|
||||
_("Rotation"),
|
||||
"res/actions/rotate_black.png")
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.SetHidden()
|
||||
.SetFunctionName("GetAngle");
|
||||
|
||||
obj.AddExpressionAndConditionAndAction("number",
|
||||
"FontSize",
|
||||
_("Font size"),
|
||||
_("the font size of a text object"),
|
||||
_("the font size"),
|
||||
"",
|
||||
"res/conditions/characterSize24.png")
|
||||
// Deprecated
|
||||
obj.AddAction("String",
|
||||
_("Modify the text"),
|
||||
_("Modify the text of a Text object."),
|
||||
_("the text"),
|
||||
"",
|
||||
"res/actions/text24_black.png",
|
||||
"res/actions/text_black.png")
|
||||
.SetHidden()
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardParameters("number", gd::ParameterOptions::MakeNewOptions());
|
||||
.UseStandardOperatorParameters(
|
||||
"string",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(_("Text")))
|
||||
.SetFunctionName("SetString")
|
||||
.SetGetter("GetString");
|
||||
|
||||
// Support for deprecated "Size" actions/conditions:
|
||||
obj.AddDuplicatedAction("Size", "Text::SetFontSize").SetHidden();
|
||||
obj.AddDuplicatedCondition("Size", "Text::FontSize").SetHidden();
|
||||
// Deprecated
|
||||
obj.AddCondition("String",
|
||||
_("Compare the text"),
|
||||
_("Compare the text of a Text object."),
|
||||
_("the text"),
|
||||
"",
|
||||
"res/conditions/text24_black.png",
|
||||
"res/conditions/text_black.png")
|
||||
.SetHidden()
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardRelationalOperatorParameters(
|
||||
"string",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Text to compare to")))
|
||||
.SetFunctionName("GetString");
|
||||
|
||||
// Deprecated
|
||||
obj.AddStrExpression(
|
||||
"String", _("Text"), _("Text"), _("Text"), "res/texteicon.png")
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.SetHidden()
|
||||
.SetFunctionName("GetString");
|
||||
}
|
||||
|
@@ -29,24 +29,6 @@ class TextObjectJsExtension : public gd::PlatformExtension {
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Scale"]
|
||||
.SetFunctionName("setScale")
|
||||
.SetGetter("getScaleMean");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::ScaleX"]
|
||||
.SetFunctionName("setScaleX")
|
||||
.SetGetter("getScaleX");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::ScaleX"]
|
||||
.SetFunctionName("getScaleX");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::ScaleY"]
|
||||
.SetFunctionName("setScaleY")
|
||||
.SetGetter("getScaleY");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::ScaleY"]
|
||||
.SetFunctionName("getScaleY");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::String"]
|
||||
.SetFunctionName("setString")
|
||||
.SetGetter("getString");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::String"]
|
||||
.SetFunctionName("getString");
|
||||
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetFontSize"]
|
||||
.SetFunctionName("setCharacterSize")
|
||||
@@ -56,24 +38,6 @@ class TextObjectJsExtension : public gd::PlatformExtension {
|
||||
GetAllExpressionsForObject("TextObject::Text")["FontSize"]
|
||||
.SetFunctionName("getCharacterSize");
|
||||
|
||||
// Deprecated actions/conditions (use "FontSize"/"SetFontSize" instead):
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Size"]
|
||||
.SetFunctionName("setCharacterSize")
|
||||
.SetGetter("getCharacterSize");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Size"]
|
||||
.SetFunctionName("getCharacterSize");
|
||||
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Angle"]
|
||||
.SetFunctionName("setAngle")
|
||||
.SetGetter("getAngle");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Angle"]
|
||||
.SetFunctionName("getAngle");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Opacity"]
|
||||
.SetFunctionName("setOpacity")
|
||||
.SetGetter("getOpacity");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Opacity"]
|
||||
.SetFunctionName("getOpacity");
|
||||
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::SetBold"]
|
||||
.SetFunctionName("setBold");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::IsBold"]
|
||||
@@ -108,16 +72,6 @@ class TextObjectJsExtension : public gd::PlatformExtension {
|
||||
|
||||
GetAllExpressionsForObject("TextObject::Text")["Padding"]
|
||||
.SetFunctionName("getPadding");
|
||||
GetAllExpressionsForObject("TextObject::Text")["ScaleX"]
|
||||
.SetFunctionName("getScaleX");
|
||||
GetAllExpressionsForObject("TextObject::Text")["ScaleY"]
|
||||
.SetFunctionName("getScaleY");
|
||||
GetAllExpressionsForObject("TextObject::Text")["Opacity"]
|
||||
.SetFunctionName("getOpacity");
|
||||
GetAllExpressionsForObject("TextObject::Text")["Angle"]
|
||||
.SetFunctionName("getAngle");
|
||||
GetAllStrExpressionsForObject("TextObject::Text")["String"]
|
||||
.SetFunctionName("getString");
|
||||
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::ChangeColor"]
|
||||
.SetFunctionName("setColor");
|
||||
@@ -125,15 +79,13 @@ class TextObjectJsExtension : public gd::PlatformExtension {
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::SetGradient"]
|
||||
.SetFunctionName("setGradient");
|
||||
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::SetOutline"]
|
||||
.SetFunctionName("setOutline");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetOutlineEnabled"]
|
||||
.SetFunctionName("setOutlineEnabled");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::IsOutlineEnabled"]
|
||||
.SetFunctionName("isOutlineEnabled");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetOutlineColor"]
|
||||
.SetFunctionName("setOutlineColor");
|
||||
GetAllExpressionsForObject("TextObject::Text")["TextObject::Text::OutlineThickness"]
|
||||
GetAllExpressionsForObject("TextObject::Text")["OutlineThickness"]
|
||||
.SetFunctionName("getOutlineThickness");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::OutlineThickness"]
|
||||
.SetFunctionName("getOutlineThickness");
|
||||
@@ -141,8 +93,6 @@ class TextObjectJsExtension : public gd::PlatformExtension {
|
||||
.SetFunctionName("setOutlineThickness")
|
||||
.SetGetter("getOutlineThickness");
|
||||
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::SetShadow"]
|
||||
.SetFunctionName("setShadow");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::ShowShadow"]
|
||||
.SetFunctionName("showShadow");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::IsShadowEnabled"]
|
||||
@@ -150,7 +100,7 @@ class TextObjectJsExtension : public gd::PlatformExtension {
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetShadowColor"]
|
||||
.SetFunctionName("setShadowColor");
|
||||
|
||||
GetAllExpressionsForObject("TextObject::Text")["TextObject::Text::ShadowOpacity"]
|
||||
GetAllExpressionsForObject("TextObject::Text")["ShadowOpacity"]
|
||||
.SetFunctionName("getShadowOpacity");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::ShadowOpacity"]
|
||||
.SetFunctionName("getShadowOpacity");
|
||||
@@ -158,7 +108,7 @@ class TextObjectJsExtension : public gd::PlatformExtension {
|
||||
.SetFunctionName("setShadowOpacity")
|
||||
.SetGetter("getShadowOpacity");
|
||||
|
||||
GetAllExpressionsForObject("TextObject::Text")["TextObject::Text::ShadowDistance"]
|
||||
GetAllExpressionsForObject("TextObject::Text")["ShadowDistance"]
|
||||
.SetFunctionName("getShadowDistance");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::ShadowDistance"]
|
||||
.SetFunctionName("getShadowDistance");
|
||||
@@ -166,7 +116,7 @@ class TextObjectJsExtension : public gd::PlatformExtension {
|
||||
.SetFunctionName("setShadowDistance")
|
||||
.SetGetter("getShadowDistance");
|
||||
|
||||
GetAllExpressionsForObject("TextObject::Text")["TextObject::Text::ShadowAngle"]
|
||||
GetAllExpressionsForObject("TextObject::Text")["ShadowAngle"]
|
||||
.SetFunctionName("getShadowAngle");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::ShadowAngle"]
|
||||
.SetFunctionName("getShadowAngle");
|
||||
@@ -174,7 +124,7 @@ class TextObjectJsExtension : public gd::PlatformExtension {
|
||||
.SetFunctionName("setShadowAngle")
|
||||
.SetGetter("getShadowAngle");
|
||||
|
||||
GetAllExpressionsForObject("TextObject::Text")["TextObject::Text::ShadowBlurRadius"]
|
||||
GetAllExpressionsForObject("TextObject::Text")["ShadowBlurRadius"]
|
||||
.SetFunctionName("getShadowBlurRadius");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::ShadowBlurRadius"]
|
||||
.SetFunctionName("getShadowBlurRadius");
|
||||
@@ -182,6 +132,61 @@ class TextObjectJsExtension : public gd::PlatformExtension {
|
||||
.SetFunctionName("setShadowBlurRadius")
|
||||
.SetGetter("getShadowBlurRadius");
|
||||
|
||||
// Deprecated actions/conditions (use "FontSize"/"SetFontSize" instead):
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Size"]
|
||||
.SetFunctionName("setCharacterSize")
|
||||
.SetGetter("getCharacterSize");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Size"]
|
||||
.SetFunctionName("getCharacterSize");
|
||||
|
||||
// Deprecated: now available for all objects.
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Angle"]
|
||||
.SetFunctionName("setAngle")
|
||||
.SetGetter("getAngle");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Angle"]
|
||||
.SetFunctionName("getAngle");
|
||||
GetAllExpressionsForObject("TextObject::Text")["Angle"]
|
||||
.SetFunctionName("getAngle");
|
||||
|
||||
// Deprecated: available through capabilities.
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Scale"]
|
||||
.SetFunctionName("setScale")
|
||||
.SetGetter("getScaleMean");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::ScaleX"]
|
||||
.SetFunctionName("setScaleX")
|
||||
.SetGetter("getScaleX");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::ScaleX"]
|
||||
.SetFunctionName("getScaleX");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::ScaleY"]
|
||||
.SetFunctionName("setScaleY")
|
||||
.SetGetter("getScaleY");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::ScaleY"]
|
||||
.SetFunctionName("getScaleY");
|
||||
GetAllExpressionsForObject("TextObject::Text")["ScaleX"]
|
||||
.SetFunctionName("getScaleX");
|
||||
GetAllExpressionsForObject("TextObject::Text")["ScaleY"]
|
||||
.SetFunctionName("getScaleY");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::String"]
|
||||
.SetFunctionName("setString")
|
||||
.SetGetter("getString");
|
||||
GetAllStrExpressionsForObject("TextObject::Text")["String"]
|
||||
.SetFunctionName("getString");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::String"]
|
||||
.SetFunctionName("getString");
|
||||
GetAllExpressionsForObject("TextObject::Text")["Opacity"]
|
||||
.SetFunctionName("getOpacity");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Opacity"]
|
||||
.SetFunctionName("setOpacity")
|
||||
.SetGetter("getOpacity");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Opacity"]
|
||||
.SetFunctionName("getOpacity");
|
||||
|
||||
// Deprecated: split into several instructions.
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::SetOutline"]
|
||||
.SetFunctionName("setOutline");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::SetShadow"]
|
||||
.SetFunctionName("setShadow");
|
||||
|
||||
// Unimplemented actions and conditions:
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Font"]
|
||||
.SetFunctionName("");
|
||||
|
@@ -26,6 +26,8 @@ import {
|
||||
PrivateGameTemplateTile,
|
||||
} from './ShopTiles';
|
||||
import { useDebounce } from '../Utils/UseDebounce';
|
||||
import PromotionsSlideshow from '../Promotions/PromotionsSlideshow';
|
||||
import { ColumnStackLayout } from '../UI/Layout';
|
||||
|
||||
const cellSpacing = 2;
|
||||
|
||||
@@ -157,6 +159,7 @@ type Props = {|
|
||||
onCategorySelection: string => void,
|
||||
openedShopCategory: string | null,
|
||||
hideGameTemplates?: boolean,
|
||||
displayPromotions?: boolean,
|
||||
|};
|
||||
|
||||
export const AssetsHome = React.forwardRef<Props, AssetsHomeInterface>(
|
||||
@@ -171,6 +174,7 @@ export const AssetsHome = React.forwardRef<Props, AssetsHomeInterface>(
|
||||
onCategorySelection,
|
||||
openedShopCategory,
|
||||
hideGameTemplates,
|
||||
displayPromotions,
|
||||
}: Props,
|
||||
ref
|
||||
) => {
|
||||
@@ -383,6 +387,15 @@ export const AssetsHome = React.forwardRef<Props, AssetsHomeInterface>(
|
||||
</GridList>
|
||||
</>
|
||||
)}
|
||||
{displayPromotions ? (
|
||||
<ColumnStackLayout>
|
||||
<Text size="block-title">
|
||||
<Trans>Promotions</Trans>
|
||||
</Text>
|
||||
|
||||
<PromotionsSlideshow />
|
||||
</ColumnStackLayout>
|
||||
) : null}
|
||||
{allBundleTiles.length ? (
|
||||
<>
|
||||
<Column>
|
||||
|
@@ -17,7 +17,9 @@ export type ExampleStoreDialogProps = {|
|
||||
onSelectPrivateGameTemplateListingData: (
|
||||
privateGameTemplateListingData: ?PrivateGameTemplateListingData
|
||||
) => void,
|
||||
onOpenNewProjectSetupDialog: () => void,
|
||||
onOpenNewProjectSetupDialog: (
|
||||
initialTab: 'from-scratch' | 'ai' | 'example'
|
||||
) => void,
|
||||
isProjectOpening: boolean,
|
||||
|};
|
||||
|
||||
@@ -44,7 +46,7 @@ const ExampleStoreDialog = ({
|
||||
id="create-blank-project-button"
|
||||
label={<Trans>Create a blank project</Trans>}
|
||||
primary
|
||||
onClick={onOpenNewProjectSetupDialog}
|
||||
onClick={() => onOpenNewProjectSetupDialog('from-scratch')}
|
||||
/>,
|
||||
],
|
||||
[onClose, onOpenNewProjectSetupDialog]
|
||||
@@ -59,7 +61,7 @@ const ExampleStoreDialog = ({
|
||||
title={<Trans>Create a new project</Trans>}
|
||||
actions={actions}
|
||||
onRequestClose={onClose}
|
||||
onApply={onOpenNewProjectSetupDialog}
|
||||
onApply={() => onOpenNewProjectSetupDialog('from-scratch')}
|
||||
open={open}
|
||||
fullHeight
|
||||
flexColumnBody
|
||||
@@ -67,7 +69,9 @@ const ExampleStoreDialog = ({
|
||||
<ExampleStore
|
||||
focusOnMount
|
||||
isOpening={isProjectOpening}
|
||||
onOpenNewProjectSetupDialog={onOpenNewProjectSetupDialog}
|
||||
onOpenNewProjectSetupDialog={() =>
|
||||
onOpenNewProjectSetupDialog('example')
|
||||
}
|
||||
onSelectExampleShortHeader={onSelectExampleShortHeader}
|
||||
onSelectPrivateGameTemplateListingData={
|
||||
onSelectPrivateGameTemplateListingData
|
||||
|
@@ -59,6 +59,7 @@ import { PrivateGameTemplateStoreContext } from './PrivateGameTemplates/PrivateG
|
||||
|
||||
type Props = {|
|
||||
hideGameTemplates?: boolean, // TODO: if we add more options, use an array instead.
|
||||
displayPromotions?: boolean,
|
||||
onOpenPrivateGameTemplateListingData?: (
|
||||
privateGameTemplateListingData: PrivateGameTemplateListingData
|
||||
) => void,
|
||||
@@ -94,7 +95,14 @@ const identifyAssetPackKind = ({
|
||||
};
|
||||
|
||||
export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
|
||||
({ hideGameTemplates, onOpenPrivateGameTemplateListingData }: Props, ref) => {
|
||||
(
|
||||
{
|
||||
hideGameTemplates,
|
||||
displayPromotions,
|
||||
onOpenPrivateGameTemplateListingData,
|
||||
}: Props,
|
||||
ref
|
||||
) => {
|
||||
const {
|
||||
assetShortHeadersSearchResults,
|
||||
publicAssetPacksSearchResults,
|
||||
@@ -737,6 +745,7 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
|
||||
onCategorySelection={selectShopCategory}
|
||||
openedShopCategory={openedShopCategory}
|
||||
hideGameTemplates={hideGameTemplates}
|
||||
displayPromotions={displayPromotions}
|
||||
/>
|
||||
) : (
|
||||
<PlaceholderLoader />
|
||||
|
@@ -98,7 +98,9 @@ export type RenderEditorContainerProps = {|
|
||||
canInstallPrivateAsset: () => boolean,
|
||||
|
||||
// Project creation
|
||||
onOpenNewProjectSetupDialog: () => void,
|
||||
onOpenNewProjectSetupDialog: (
|
||||
initialTab: 'from-scratch' | 'ai' | 'example'
|
||||
) => void,
|
||||
|
||||
// Project save
|
||||
onSave: () => Promise<void>,
|
||||
|
@@ -63,6 +63,7 @@ import ContextMenu, {
|
||||
type ContextMenuInterface,
|
||||
} from '../../../../UI/Menu/ContextMenu';
|
||||
import type { ClientCoordinates } from '../../../../Utils/UseLongTouch';
|
||||
import PromotionsSlideshow from '../../../../Promotions/PromotionsSlideshow';
|
||||
const electron = optionalRequire('electron');
|
||||
const path = optionalRequire('path');
|
||||
|
||||
@@ -107,7 +108,7 @@ type Props = {|
|
||||
canOpen: boolean,
|
||||
onChooseProject: () => void,
|
||||
onOpenRecentFile: (file: FileMetadataAndStorageProviderName) => Promise<void>,
|
||||
onOpenNewProjectSetupDialog: () => void,
|
||||
onOpenNewProjectSetupDialog: (initialTab: 'ai' | 'from-scratch') => void,
|
||||
onSelectExampleShortHeader: (exampleShortHeader: ExampleShortHeader) => void,
|
||||
onSelectPrivateGameTemplateListingData: (
|
||||
privateGameTemplateListingData: PrivateGameTemplateListingData
|
||||
@@ -178,6 +179,8 @@ const BuildSection = ({
|
||||
setLastModifiedInfoByProjectId,
|
||||
] = React.useState({});
|
||||
|
||||
const isMediumOrSmallScreen =
|
||||
windowSize === 'small' || windowSize === 'medium';
|
||||
const columnsCount = getItemsColumns(windowSize, isLandscape);
|
||||
|
||||
const allGameTemplatesAndExamplesFlaggedAsGameCount = React.useMemo(
|
||||
@@ -419,7 +422,7 @@ const BuildSection = ({
|
||||
) : (
|
||||
<SectionContainer
|
||||
title={<Trans>My projects</Trans>}
|
||||
showAnnouncementsAndPromotions
|
||||
showUrgentAnnouncements
|
||||
renderFooter={
|
||||
limits && hasTooManyCloudProjects
|
||||
? () => (
|
||||
@@ -452,6 +455,10 @@ const BuildSection = ({
|
||||
roundedImages
|
||||
displayArrowsOnDesktop
|
||||
/>
|
||||
<Spacer />
|
||||
<Column noMargin>
|
||||
<PromotionsSlideshow />
|
||||
</Column>
|
||||
</SectionRow>
|
||||
<SectionRow>
|
||||
<ResponsiveLineStackLayout
|
||||
@@ -483,34 +490,41 @@ const BuildSection = ({
|
||||
primary
|
||||
fullWidth={!canOpen}
|
||||
label={
|
||||
isMobile ? (
|
||||
isMediumOrSmallScreen ? (
|
||||
<Trans>Create</Trans>
|
||||
) : (
|
||||
<Trans>Create a project</Trans>
|
||||
)
|
||||
}
|
||||
onClick={onOpenNewProjectSetupDialog}
|
||||
onClick={() => onOpenNewProjectSetupDialog('from-scratch')}
|
||||
icon={<Add fontSize="small" />}
|
||||
id="home-create-project-button"
|
||||
/>
|
||||
<RaisedButton
|
||||
primary
|
||||
fullWidth={!canOpen}
|
||||
label={
|
||||
isMediumOrSmallScreen ? (
|
||||
<Trans>✨ AI prototype</Trans>
|
||||
) : (
|
||||
<Trans>✨ Prototype with AI</Trans>
|
||||
)
|
||||
}
|
||||
onClick={() => onOpenNewProjectSetupDialog('ai')}
|
||||
id="home-create-project-button"
|
||||
/>
|
||||
{canOpen && (
|
||||
<>
|
||||
<Text>
|
||||
<Trans>or</Trans>
|
||||
</Text>
|
||||
<Spacer />
|
||||
<TextButton
|
||||
secondary
|
||||
label={
|
||||
isMobile ? (
|
||||
<Trans>Open</Trans>
|
||||
) : (
|
||||
<Trans>Open a project</Trans>
|
||||
)
|
||||
}
|
||||
onClick={onChooseProject}
|
||||
/>
|
||||
</>
|
||||
<TextButton
|
||||
secondary
|
||||
label={
|
||||
isMediumOrSmallScreen ? (
|
||||
<Trans>Import</Trans>
|
||||
) : (
|
||||
<Trans>Import a project</Trans>
|
||||
)
|
||||
}
|
||||
onClick={onChooseProject}
|
||||
/>
|
||||
)}
|
||||
</LineStackLayout>
|
||||
</Column>
|
||||
|
@@ -17,6 +17,7 @@ import List from '@material-ui/core/List';
|
||||
import ErrorBoundary from '../../../UI/ErrorBoundary';
|
||||
import { AnnouncementsFeed } from '../../../AnnouncementsFeed';
|
||||
import { AnnouncementsFeedContext } from '../../../AnnouncementsFeed/AnnouncementsFeedContext';
|
||||
import PromotionsSlideshow from '../../../Promotions/PromotionsSlideshow';
|
||||
|
||||
const styles = {
|
||||
list: {
|
||||
@@ -71,10 +72,7 @@ const CommunitySection = () => {
|
||||
announcements && announcements.length > 0;
|
||||
|
||||
return (
|
||||
<SectionContainer
|
||||
title={<Trans>Community</Trans>}
|
||||
showAnnouncementsAndPromotions
|
||||
>
|
||||
<SectionContainer title={<Trans>Community</Trans>} showUrgentAnnouncements>
|
||||
<SectionRow>
|
||||
<ColumnStackLayout noMargin expand>
|
||||
{shouldDisplayAnnouncementsTitle && (
|
||||
@@ -82,6 +80,7 @@ const CommunitySection = () => {
|
||||
<Trans>News and announcements</Trans>
|
||||
</Text>
|
||||
)}
|
||||
<PromotionsSlideshow />
|
||||
<AnnouncementsFeed canClose={false} level="normal" />
|
||||
<Text size="title">
|
||||
<Trans>Join the conversation</Trans>
|
||||
|
@@ -18,7 +18,7 @@ import {
|
||||
type WindowSizeType,
|
||||
} from '../../../../UI/Responsive/ResponsiveWindowMeasurer';
|
||||
import Text from '../../../../UI/Text';
|
||||
import { Column } from '../../../../UI/Grid';
|
||||
import { Column, Spacer } from '../../../../UI/Grid';
|
||||
import { type Tutorial } from '../../../../Utils/GDevelopServices/Tutorial';
|
||||
import { type SubscriptionPlanWithPricingSystems } from '../../../../Utils/GDevelopServices/Usage';
|
||||
import { CardWidget } from '../CardWidget';
|
||||
@@ -32,6 +32,7 @@ import PreferencesContext from '../../../Preferences/PreferencesContext';
|
||||
import PlanRecommendationRow from './PlanRecommendationRow';
|
||||
import { SurveyCard } from './SurveyCard';
|
||||
import PlaceholderLoader from '../../../../UI/PlaceholderLoader';
|
||||
import PromotionsSlideshow from '../../../../Promotions/PromotionsSlideshow';
|
||||
|
||||
const styles = {
|
||||
textTutorialContent: {
|
||||
@@ -300,6 +301,16 @@ const RecommendationList = ({
|
||||
</SectionRow>
|
||||
);
|
||||
|
||||
items.push(
|
||||
<SectionRow key="promotions">
|
||||
<Text size="section-title" noMargin>
|
||||
<Trans>Discover the ecosystem</Trans>
|
||||
</Text>
|
||||
<Spacer />
|
||||
<PromotionsSlideshow />
|
||||
</SectionRow>
|
||||
);
|
||||
|
||||
if (recommendedTextTutorials.length) {
|
||||
items.push(
|
||||
<SectionRow key="texts">
|
||||
|
@@ -628,16 +628,10 @@ const GetStartedSection = ({
|
||||
return (
|
||||
<>
|
||||
<SectionContainer
|
||||
title={
|
||||
profile && profile.username ? (
|
||||
<Trans>Hello {profile.username}!</Trans>
|
||||
) : (
|
||||
<Trans>Hello!</Trans>
|
||||
)
|
||||
}
|
||||
title={<Trans>Start making games</Trans>}
|
||||
renderSubtitle={renderSubtitle}
|
||||
flexBody
|
||||
showAnnouncementsAndPromotions
|
||||
showUrgentAnnouncements
|
||||
>
|
||||
<RecommendationList
|
||||
authenticatedUser={authenticatedUser}
|
||||
|
@@ -11,11 +11,11 @@ import ProjectManagerIcon from '../../../UI/CustomSvgIcons/ProjectManager';
|
||||
import FloppyIcon from '../../../UI/CustomSvgIcons/Floppy';
|
||||
import Window from '../../../Utils/Window';
|
||||
import optionalRequire from '../../../Utils/OptionalRequire';
|
||||
import { useResponsiveWindowSize } from '../../../UI/Responsive/ResponsiveWindowMeasurer';
|
||||
import TextButton from '../../../UI/TextButton';
|
||||
import IconButton from '../../../UI/IconButton';
|
||||
import { isNativeMobileApp } from '../../../Utils/Platform';
|
||||
import NotificationChip from '../../../UI/User/NotificationChip';
|
||||
import { useResponsiveWindowSize } from '../../../UI/Responsive/ResponsiveWindowMeasurer';
|
||||
const electron = optionalRequire('electron');
|
||||
|
||||
type Props = {|
|
||||
@@ -36,6 +36,7 @@ export const HomePageHeader = ({
|
||||
canSave,
|
||||
}: Props) => {
|
||||
const { isMobile } = useResponsiveWindowSize();
|
||||
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
@@ -73,9 +74,9 @@ export const HomePageHeader = ({
|
||||
</Column>
|
||||
<Column>
|
||||
<LineStackLayout noMargin alignItems="center">
|
||||
{!electron && !isNativeMobileApp() && !isMobile && (
|
||||
{!electron && !isNativeMobileApp() && (
|
||||
<FlatButton
|
||||
label={<Trans>Download desktop app</Trans>}
|
||||
label={<Trans>Get the app</Trans>}
|
||||
onClick={() =>
|
||||
Window.openExternalURL('https://gdevelop.io/download')
|
||||
}
|
||||
@@ -83,11 +84,17 @@ export const HomePageHeader = ({
|
||||
)}
|
||||
<UserChip onOpenProfile={onOpenProfile} />
|
||||
<NotificationChip />
|
||||
<TextButton
|
||||
label={i18n.language.toUpperCase()}
|
||||
onClick={onOpenLanguageDialog}
|
||||
icon={<TranslateIcon fontSize="small" />}
|
||||
/>
|
||||
{isMobile ? (
|
||||
<IconButton size="small" onClick={onOpenLanguageDialog}>
|
||||
<TranslateIcon fontSize="small" />
|
||||
</IconButton>
|
||||
) : (
|
||||
<TextButton
|
||||
label={i18n.language.toUpperCase()}
|
||||
onClick={onOpenLanguageDialog}
|
||||
icon={<TranslateIcon fontSize="small" />}
|
||||
/>
|
||||
)}
|
||||
</LineStackLayout>
|
||||
</Column>
|
||||
</LineStackLayout>
|
||||
|
@@ -79,7 +79,7 @@ const HomePageMenuBar = ({
|
||||
const theme = React.useContext(GDevelopThemeContext);
|
||||
const { profile } = React.useContext(AuthenticatedUserContext);
|
||||
const tabsToDisplay = getTabsToDisplay({ profile });
|
||||
const buttons: {
|
||||
const largeScreenOnlyButtons: {
|
||||
label: React.Node,
|
||||
getIcon: GetIconFunction,
|
||||
id: string,
|
||||
@@ -139,29 +139,6 @@ const HomePageMenuBar = ({
|
||||
</IconButton>
|
||||
);
|
||||
})}
|
||||
<span
|
||||
style={{
|
||||
width: 1,
|
||||
backgroundColor: theme.home.separator.color,
|
||||
height: '70%',
|
||||
margin: '0 3px',
|
||||
}}
|
||||
/>
|
||||
{buttons.map(({ label, onClick, getIcon, id }) => (
|
||||
<IconButton
|
||||
color="default"
|
||||
key={id}
|
||||
disableRipple
|
||||
disableFocusRipple
|
||||
style={styles.mobileButton}
|
||||
onClick={onClick}
|
||||
id={id}
|
||||
>
|
||||
<span style={styles.buttonLabel}>
|
||||
{getIcon({ color: 'secondary', fontSize: 'inherit' })}
|
||||
</span>
|
||||
</IconButton>
|
||||
))}
|
||||
</ToolbarGroup>
|
||||
</Toolbar>
|
||||
</Paper>
|
||||
@@ -198,7 +175,7 @@ const HomePageMenuBar = ({
|
||||
|
||||
<div style={styles.bottomButtonsContainer}>
|
||||
<Column>
|
||||
{buttons.map(({ label, getIcon, onClick, id }) => (
|
||||
{largeScreenOnlyButtons.map(({ label, getIcon, onClick, id }) => (
|
||||
<VerticalTabButton
|
||||
key={id}
|
||||
label={label}
|
||||
|
@@ -162,9 +162,6 @@ const MainPage = ({
|
||||
return (
|
||||
<SectionContainer title={<Trans>Help and guides</Trans>}>
|
||||
<SectionRow>
|
||||
<Text>
|
||||
<Trans>Quick search</Trans>
|
||||
</Text>
|
||||
<WikiSearchBar />
|
||||
</SectionRow>
|
||||
<SectionRow>
|
||||
|
@@ -9,7 +9,6 @@ import { Trans } from '@lingui/macro';
|
||||
import Paper from '../../../UI/Paper';
|
||||
import { LineStackLayout } from '../../../UI/Layout';
|
||||
import { AnnouncementsFeed } from '../../../AnnouncementsFeed';
|
||||
import PromotionsSlideshow from '../../../Promotions/PromotionsSlideshow';
|
||||
import { AnnouncementsFeedContext } from '../../../AnnouncementsFeed/AnnouncementsFeedContext';
|
||||
|
||||
export const SECTION_PADDING = 30;
|
||||
@@ -58,7 +57,7 @@ type Props = {|
|
||||
flexBody?: boolean,
|
||||
renderFooter?: () => React.Node,
|
||||
noScroll?: boolean,
|
||||
showAnnouncementsAndPromotions?: boolean,
|
||||
showUrgentAnnouncements?: boolean,
|
||||
|};
|
||||
|
||||
const SectionContainer = ({
|
||||
@@ -71,7 +70,7 @@ const SectionContainer = ({
|
||||
flexBody,
|
||||
renderFooter,
|
||||
noScroll,
|
||||
showAnnouncementsAndPromotions,
|
||||
showUrgentAnnouncements,
|
||||
}: Props) => {
|
||||
const { isMobile } = useResponsiveWindowSize();
|
||||
const { announcements } = React.useContext(AnnouncementsFeedContext);
|
||||
@@ -94,14 +93,10 @@ const SectionContainer = ({
|
||||
<Column useFullHeight noMargin expand>
|
||||
<Paper style={paperStyle} square background="dark">
|
||||
<Column noOverflowParent expand>
|
||||
{showAnnouncementsAndPromotions && (
|
||||
{showUrgentAnnouncements && (
|
||||
<>
|
||||
<AnnouncementsFeed canClose level="urgent" hideLoader />
|
||||
{announcements && announcements.length > 0 && <Spacer />}
|
||||
<Column noMargin>
|
||||
<PromotionsSlideshow />
|
||||
</Column>
|
||||
<Spacer />
|
||||
</>
|
||||
)}
|
||||
{backAction && (
|
||||
|
@@ -83,6 +83,7 @@ const StoreSection = ({
|
||||
onOpenPrivateGameTemplateListingData={
|
||||
onOpenPrivateGameTemplateListingData
|
||||
}
|
||||
displayPromotions
|
||||
/>
|
||||
{(openedAssetPack || openedAssetShortHeader) && (
|
||||
<Line justifyContent="flex-end">
|
||||
|
@@ -114,7 +114,9 @@ type Props = {|
|
||||
onOpenAbout: () => void,
|
||||
|
||||
// Project creation
|
||||
onOpenNewProjectSetupDialog: () => void,
|
||||
onOpenNewProjectSetupDialog: (
|
||||
initialTab: 'from-scratch' | 'ai' | 'example'
|
||||
) => void,
|
||||
|
||||
// Project save
|
||||
onSave: () => Promise<void>,
|
||||
|
@@ -12,7 +12,9 @@ import AuthenticatedUserContext from '../Profile/AuthenticatedUserContext';
|
||||
|
||||
type Props = {|
|
||||
isProjectOpening: boolean,
|
||||
onOpenNewProjectSetupDialog: () => void,
|
||||
onOpenNewProjectSetupDialog: (
|
||||
initialTab: 'from-scratch' | 'ai' | 'example'
|
||||
) => void,
|
||||
|};
|
||||
|
||||
const useExampleOrGameTemplateDialogs = ({
|
||||
@@ -137,7 +139,7 @@ const useExampleOrGameTemplateDialogs = ({
|
||||
<ExampleDialog
|
||||
isOpening={isProjectOpening}
|
||||
exampleShortHeader={selectedExampleShortHeader}
|
||||
onOpen={onOpenNewProjectSetupDialog}
|
||||
onOpen={() => onOpenNewProjectSetupDialog('example')}
|
||||
onClose={() => setSelectedExampleShortHeader(null)}
|
||||
/>
|
||||
)}
|
||||
@@ -148,7 +150,9 @@ const useExampleOrGameTemplateDialogs = ({
|
||||
selectedPrivateGameTemplate.privateGameTemplateListingData
|
||||
}
|
||||
isPurchaseDialogOpen={!!purchasingGameTemplateListingData}
|
||||
onCreateWithGameTemplate={onOpenNewProjectSetupDialog}
|
||||
onCreateWithGameTemplate={() =>
|
||||
onOpenNewProjectSetupDialog('example')
|
||||
}
|
||||
onGameTemplateOpen={privateGameTemplateListingData =>
|
||||
setSelectedPrivateGameTemplate({
|
||||
privateGameTemplateListingData,
|
||||
|
@@ -359,9 +359,9 @@ const MainFrame = (props: Props) => {
|
||||
openPreferencesDialog,
|
||||
] = React.useState<boolean>(false);
|
||||
const [
|
||||
newProjectSetupDialogOpen,
|
||||
setNewProjectSetupDialogOpen,
|
||||
] = React.useState<boolean>(false);
|
||||
newProjectSetupDialogInitialTab,
|
||||
setNewProjectSetupDialogInitialTab,
|
||||
] = React.useState<null | 'from-scratch' | 'ai' | 'example'>(null);
|
||||
|
||||
const [isProjectOpening, setIsProjectOpening] = React.useState<boolean>(
|
||||
false
|
||||
@@ -498,7 +498,7 @@ const MainFrame = (props: Props) => {
|
||||
openExampleStoreDialog,
|
||||
} = useExampleOrGameTemplateDialogs({
|
||||
isProjectOpening,
|
||||
onOpenNewProjectSetupDialog: () => setNewProjectSetupDialogOpen(true),
|
||||
onOpenNewProjectSetupDialog: setNewProjectSetupDialogInitialTab,
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -1144,7 +1144,7 @@ const MainFrame = (props: Props) => {
|
||||
},
|
||||
getStorageProviderOperations,
|
||||
afterCreatingProject: async ({ project, editorTabs, oldProjectId }) => {
|
||||
setNewProjectSetupDialogOpen(false);
|
||||
setNewProjectSetupDialogInitialTab(null);
|
||||
closeExampleStoreDialog({ deselectExampleAndGameTemplate: true });
|
||||
findLeaderboardsToReplace(project, oldProjectId);
|
||||
openSceneOrProjectManager({
|
||||
@@ -2960,7 +2960,7 @@ const MainFrame = (props: Props) => {
|
||||
onLaunchDebugPreview: launchDebuggerAndPreview,
|
||||
onLaunchNetworkPreview: launchNetworkPreview,
|
||||
onOpenHomePage: openHomePage,
|
||||
onCreateBlank: () => setNewProjectSetupDialogOpen(true),
|
||||
onCreateBlank: () => setNewProjectSetupDialogInitialTab('from-scratch'),
|
||||
onOpenProject: () => openOpenFromStorageProviderDialog(),
|
||||
onSaveProject: saveProject,
|
||||
onSaveProjectAs: saveProjectAs,
|
||||
@@ -3020,7 +3020,7 @@ const MainFrame = (props: Props) => {
|
||||
onExportProject: () => openShareDialog('publish'),
|
||||
onInviteCollaborators: () => openShareDialog('invite'),
|
||||
onCreateProject: openExampleStoreDialog,
|
||||
onCreateBlank: () => setNewProjectSetupDialogOpen(true),
|
||||
onCreateBlank: () => setNewProjectSetupDialogInitialTab('from-scratch'),
|
||||
onOpenProjectManager: () => openProjectManager(true),
|
||||
onOpenHomePage: openHomePage,
|
||||
onOpenDebugger: openDebugger,
|
||||
@@ -3224,9 +3224,7 @@ const MainFrame = (props: Props) => {
|
||||
canInstallPrivateAsset,
|
||||
onChooseProject: () => openOpenFromStorageProviderDialog(),
|
||||
onOpenRecentFile: openFromFileMetadataWithStorageProvider,
|
||||
onOpenNewProjectSetupDialog: () => {
|
||||
setNewProjectSetupDialogOpen(true);
|
||||
},
|
||||
onOpenNewProjectSetupDialog: setNewProjectSetupDialogInitialTab,
|
||||
onOpenProjectManager: () => openProjectManager(true),
|
||||
onCloseProject: () => askToCloseProject(),
|
||||
onOpenExampleStore: openExampleStoreDialog,
|
||||
@@ -3241,7 +3239,7 @@ const MainFrame = (props: Props) => {
|
||||
privateGameTemplateListingData,
|
||||
openDialog: false,
|
||||
});
|
||||
setNewProjectSetupDialogOpen(true);
|
||||
setNewProjectSetupDialogInitialTab('example');
|
||||
},
|
||||
onOpenProfile: () => openProfileDialog(true),
|
||||
onOpenLanguageDialog: () => openLanguageDialog(true),
|
||||
@@ -3366,11 +3364,12 @@ const MainFrame = (props: Props) => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{newProjectSetupDialogOpen && (
|
||||
{newProjectSetupDialogInitialTab && (
|
||||
<NewProjectSetupDialog
|
||||
initialTab={newProjectSetupDialogInitialTab}
|
||||
authenticatedUser={authenticatedUser}
|
||||
isOpeningProject={isProjectOpening}
|
||||
onClose={() => setNewProjectSetupDialogOpen(false)}
|
||||
onClose={() => setNewProjectSetupDialogInitialTab(null)}
|
||||
onCreateEmptyProject={createEmptyProject}
|
||||
onCreateFromExample={createProjectFromExample}
|
||||
onCreateProjectFromPrivateGameTemplate={
|
||||
|
@@ -201,15 +201,13 @@ type PolygonsListProps = {|
|
||||
|
||||
// Sprite size is useful to make sure polygon vertices
|
||||
// are not put outside the sprite bounding box, which is not supported:
|
||||
spriteWidth: number,
|
||||
spriteHeight: number,
|
||||
spriteSize: [number, number],
|
||||
|};
|
||||
|
||||
const PolygonsList = (props: PolygonsListProps) => {
|
||||
const {
|
||||
polygons,
|
||||
spriteHeight,
|
||||
spriteWidth,
|
||||
spriteSize,
|
||||
onPolygonsUpdated,
|
||||
onSetFullImageCollisionMask,
|
||||
onSetAutomaticallyAdaptCollisionMasks,
|
||||
@@ -218,6 +216,7 @@ const PolygonsList = (props: PolygonsListProps) => {
|
||||
selectedVerticePtr,
|
||||
} = props;
|
||||
|
||||
const [spriteWidth, spriteHeight] = spriteSize;
|
||||
const addCollisionMask = React.useCallback(
|
||||
() => {
|
||||
const newPolygon = gd.Polygon2d.createRectangle(
|
||||
|
@@ -83,8 +83,9 @@ const CollisionMasksEditor = ({
|
||||
null
|
||||
);
|
||||
|
||||
const [spriteWidth, setSpriteWidth] = React.useState(0);
|
||||
const [spriteHeight, setSpriteHeight] = React.useState(0);
|
||||
const [currentSpriteSize, setCurrentSpriteSize] = React.useState<
|
||||
[number, number]
|
||||
>([0, 0]);
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
const { showConfirmation } = useAlertDialog();
|
||||
@@ -245,11 +246,6 @@ const CollisionMasksEditor = ({
|
||||
[sameCollisionMasksForAnimations, updateCollisionMasks, showConfirmation]
|
||||
);
|
||||
|
||||
const setCurrentSpriteSize = (spriteWidth: number, spriteHeight: number) => {
|
||||
setSpriteWidth(spriteWidth);
|
||||
setSpriteHeight(spriteHeight);
|
||||
};
|
||||
|
||||
const onSetAutomaticallyAdaptCollisionMasks = React.useCallback(
|
||||
async value => {
|
||||
// If enabling automatic while custom was selected, then ask for confirmation.
|
||||
@@ -334,7 +330,7 @@ const CollisionMasksEditor = ({
|
||||
project,
|
||||
resourceName
|
||||
)}
|
||||
onSize={setCurrentSpriteSize}
|
||||
onImageSize={setCurrentSpriteSize}
|
||||
renderOverlay={overlayProps =>
|
||||
sprite && (
|
||||
<CollisionMasksPreview
|
||||
@@ -410,8 +406,7 @@ const CollisionMasksEditor = ({
|
||||
onHoverVertice={setHighlightedVerticePtr}
|
||||
onClickVertice={setSelectedVerticePtr}
|
||||
selectedVerticePtr={selectedVerticePtr}
|
||||
spriteWidth={spriteWidth}
|
||||
spriteHeight={spriteHeight}
|
||||
spriteSize={currentSpriteSize}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
@@ -1,10 +1,8 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import * as React from 'react';
|
||||
import { TableRow, TableRowColumn } from '../../../../UI/Table';
|
||||
import IconButton from '../../../../UI/IconButton';
|
||||
import SemiControlledTextField from '../../../../UI/SemiControlledTextField';
|
||||
import Text from '../../../../UI/Text';
|
||||
import { roundTo } from '../../../../Utils/Mathematics';
|
||||
import { Column } from '../../../../UI/Grid';
|
||||
import GDevelopThemeContext from '../../../../UI/Theme/GDevelopThemeContext';
|
||||
@@ -63,64 +61,50 @@ const PointRow = ({ pointX, pointY, ...props }: Props) => {
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.coordinateColumn} padding="none">
|
||||
<Column>
|
||||
{!props.isAutomatic ? (
|
||||
<SemiControlledTextField
|
||||
margin="none"
|
||||
inputStyle={
|
||||
props.selected
|
||||
? { color: gdevelopTheme.listItem.selectedTextColor }
|
||||
: undefined
|
||||
}
|
||||
value={roundTo(pointX, POINT_COORDINATE_PRECISION).toString()}
|
||||
type="number"
|
||||
step={0.5}
|
||||
id="point-x"
|
||||
onChange={value => {
|
||||
const valueAsNumber = parseFloat(value);
|
||||
if (!isNaN(valueAsNumber)) props.onChangePointX(valueAsNumber);
|
||||
}}
|
||||
onBlur={event => {
|
||||
props.onChangePointX(
|
||||
parseFloat(event.currentTarget.value) || 0
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Text noMargin>
|
||||
<Trans>(auto)</Trans>
|
||||
</Text>
|
||||
)}
|
||||
<SemiControlledTextField
|
||||
margin="none"
|
||||
inputStyle={
|
||||
props.selected
|
||||
? { color: gdevelopTheme.listItem.selectedTextColor }
|
||||
: undefined
|
||||
}
|
||||
value={roundTo(pointX, POINT_COORDINATE_PRECISION).toString()}
|
||||
type="number"
|
||||
step={0.5}
|
||||
id="point-x"
|
||||
onChange={value => {
|
||||
const valueAsNumber = parseFloat(value);
|
||||
if (!isNaN(valueAsNumber)) props.onChangePointX(valueAsNumber);
|
||||
}}
|
||||
onBlur={event => {
|
||||
props.onChangePointX(parseFloat(event.currentTarget.value) || 0);
|
||||
}}
|
||||
disabled={props.isAutomatic}
|
||||
/>
|
||||
</Column>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.coordinateColumn} padding="none">
|
||||
<Column>
|
||||
{!props.isAutomatic ? (
|
||||
<SemiControlledTextField
|
||||
margin="none"
|
||||
inputStyle={
|
||||
props.selected
|
||||
? { color: gdevelopTheme.listItem.selectedTextColor }
|
||||
: undefined
|
||||
}
|
||||
value={roundTo(pointY, POINT_COORDINATE_PRECISION).toString()}
|
||||
type="number"
|
||||
step={0.5}
|
||||
id="point-y"
|
||||
onChange={value => {
|
||||
const valueAsNumber = parseFloat(value);
|
||||
if (!isNaN(valueAsNumber)) props.onChangePointY(valueAsNumber);
|
||||
}}
|
||||
onBlur={event => {
|
||||
props.onChangePointY(
|
||||
parseFloat(event.currentTarget.value) || 0
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Text noMargin>
|
||||
<Trans>(auto)</Trans>
|
||||
</Text>
|
||||
)}
|
||||
<SemiControlledTextField
|
||||
margin="none"
|
||||
inputStyle={
|
||||
props.selected
|
||||
? { color: gdevelopTheme.listItem.selectedTextColor }
|
||||
: undefined
|
||||
}
|
||||
value={roundTo(pointY, POINT_COORDINATE_PRECISION).toString()}
|
||||
type="number"
|
||||
step={0.5}
|
||||
id="point-y"
|
||||
onChange={value => {
|
||||
const valueAsNumber = parseFloat(value);
|
||||
if (!isNaN(valueAsNumber)) props.onChangePointY(valueAsNumber);
|
||||
}}
|
||||
onBlur={event => {
|
||||
props.onChangePointY(parseFloat(event.currentTarget.value) || 0);
|
||||
}}
|
||||
disabled={props.isAutomatic}
|
||||
/>
|
||||
</Column>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.toolColumn}>
|
||||
|
@@ -26,6 +26,7 @@ type PointsListBodyProps = {|
|
||||
onSelectPoint: (pointName: string) => void,
|
||||
onRenamedPoint: (oldName: string, newName: string) => void,
|
||||
selectedPointName: ?string,
|
||||
spriteSize: [number, number],
|
||||
|};
|
||||
|
||||
const PointsListBody = (props: PointsListBodyProps) => {
|
||||
@@ -138,13 +139,19 @@ const PointsListBody = (props: PointsListBodyProps) => {
|
||||
selected={'Origin' === props.selectedPointName}
|
||||
/>
|
||||
);
|
||||
|
||||
const isDefaultCenterPoint = pointsContainer.isDefaultCenterPoint();
|
||||
const centerRow = (
|
||||
<PointRow
|
||||
key={'center-point-row'}
|
||||
pointName="Center"
|
||||
isAutomatic={pointsContainer.isDefaultCenterPoint()}
|
||||
pointX={centerPoint.getX()}
|
||||
pointY={centerPoint.getY()}
|
||||
isAutomatic={isDefaultCenterPoint}
|
||||
pointX={
|
||||
isDefaultCenterPoint ? props.spriteSize[0] / 2 : centerPoint.getX()
|
||||
}
|
||||
pointY={
|
||||
isDefaultCenterPoint ? props.spriteSize[1] / 2 : centerPoint.getY()
|
||||
}
|
||||
onChangePointX={updateCenterPointX}
|
||||
onChangePointY={updateCenterPointY}
|
||||
onPointerEnter={props.onHoverPoint}
|
||||
@@ -180,6 +187,7 @@ type PointsListProps = {|
|
||||
onSelectPoint: (pointName: ?string) => void,
|
||||
onRenamedPoint: (oldName: string, newName: string) => void,
|
||||
selectedPointName: ?string,
|
||||
spriteSize: [number, number],
|
||||
|};
|
||||
|
||||
const PointsList = (props: PointsListProps) => {
|
||||
@@ -207,6 +215,7 @@ const PointsList = (props: PointsListProps) => {
|
||||
selectedPointName={props.selectedPointName}
|
||||
onPointsUpdated={props.onPointsUpdated}
|
||||
onRenamedPoint={props.onRenamedPoint}
|
||||
spriteSize={props.spriteSize}
|
||||
/>
|
||||
</Table>
|
||||
<Spacer />
|
||||
|
@@ -79,6 +79,10 @@ const PointsEditor = ({
|
||||
setHighlightedPointName,
|
||||
] = React.useState<?string>(null);
|
||||
|
||||
const [currentSpriteSize, setCurrentSpriteSize] = React.useState<
|
||||
[number, number]
|
||||
>([0, 0]);
|
||||
|
||||
const forceUpdate = useForceUpdate();
|
||||
const { showConfirmation } = useAlertDialog();
|
||||
|
||||
@@ -224,6 +228,7 @@ const PointsEditor = ({
|
||||
project,
|
||||
resourceName
|
||||
)}
|
||||
onImageSize={setCurrentSpriteSize}
|
||||
renderOverlay={overlayProps =>
|
||||
sprite && (
|
||||
<PointsPreview
|
||||
@@ -289,6 +294,7 @@ const PointsEditor = ({
|
||||
onHoverPoint={setHighlightedPointName}
|
||||
onSelectPoint={setSelectedPointName}
|
||||
onRenamedPoint={onRenamedPoint}
|
||||
spriteSize={currentSpriteSize}
|
||||
/>
|
||||
)}
|
||||
{!sprite && (
|
||||
|
279
newIDE/app/src/Profile/Subscription/CancelReasonDialog.js
Normal file
279
newIDE/app/src/Profile/Subscription/CancelReasonDialog.js
Normal file
@@ -0,0 +1,279 @@
|
||||
// @flow
|
||||
import { Trans, t } from '@lingui/macro';
|
||||
import { I18n } from '@lingui/react';
|
||||
import * as React from 'react';
|
||||
import Dialog, { DialogPrimaryButton } from '../../UI/Dialog';
|
||||
import AuthenticatedUserContext from '../AuthenticatedUserContext';
|
||||
import { changeUserSubscription } from '../../Utils/GDevelopServices/Usage';
|
||||
import { ColumnStackLayout, LineStackLayout } from '../../UI/Layout';
|
||||
import useAlertDialog from '../../UI/Alert/useAlertDialog';
|
||||
import GDevelopGLogo from '../../UI/CustomSvgIcons/GDevelopGLogo';
|
||||
import Form from '../../UI/Form';
|
||||
import Text from '../../UI/Text';
|
||||
import { Spacer } from '../../UI/Grid';
|
||||
import TextField from '../../UI/TextField';
|
||||
import Checkbox from '../../UI/Checkbox';
|
||||
import FlatButton from '../../UI/FlatButton';
|
||||
import StarIcon from '../../UI/CustomSvgIcons/Star';
|
||||
|
||||
type Props = {|
|
||||
onClose: () => void,
|
||||
onCloseAfterSuccess: () => void,
|
||||
|};
|
||||
|
||||
const CancelReasonDialog = ({ onClose, onCloseAfterSuccess }: Props) => {
|
||||
const [isCancelingSubscription, setIsCancelingSubscription] = React.useState(
|
||||
false
|
||||
);
|
||||
const [hasCanceledSubscription, setHasCanceledSubscription] = React.useState(
|
||||
false
|
||||
);
|
||||
const [
|
||||
stoppedMakingGamesChecked,
|
||||
setStoppedMakingGamesChecked,
|
||||
] = React.useState(false);
|
||||
const [strugglingChecked, setStrugglingChecked] = React.useState(false);
|
||||
const [
|
||||
preferFreeVersionChecked,
|
||||
setPreferFreeVersionChecked,
|
||||
] = React.useState(false);
|
||||
const [missingFeatureChecked, setMissingFeatureChecked] = React.useState(
|
||||
false
|
||||
);
|
||||
const [qualityIssuesChecked, setQualityIssuesChecked] = React.useState(false);
|
||||
const [otherChecked, setOtherChecked] = React.useState(false);
|
||||
const [freeText, setFreeText] = React.useState('');
|
||||
const authenticatedUser = React.useContext(AuthenticatedUserContext);
|
||||
const { showAlert } = useAlertDialog();
|
||||
|
||||
const canSubmit =
|
||||
(stoppedMakingGamesChecked ||
|
||||
strugglingChecked ||
|
||||
preferFreeVersionChecked ||
|
||||
qualityIssuesChecked ||
|
||||
missingFeatureChecked ||
|
||||
otherChecked) &&
|
||||
((!missingFeatureChecked && !otherChecked) || freeText.trim().length > 0);
|
||||
|
||||
const cancelPlan = React.useCallback(
|
||||
async () => {
|
||||
if (isCancelingSubscription || !canSubmit) return;
|
||||
const {
|
||||
getAuthorizationHeader,
|
||||
subscription,
|
||||
profile,
|
||||
} = authenticatedUser;
|
||||
if (!profile || !subscription) return;
|
||||
setIsCancelingSubscription(true);
|
||||
try {
|
||||
await changeUserSubscription(
|
||||
getAuthorizationHeader,
|
||||
profile.id,
|
||||
{
|
||||
planId: null,
|
||||
},
|
||||
{
|
||||
cancelImmediately: false,
|
||||
cancelReasons: {
|
||||
'stopped-making-games': stoppedMakingGamesChecked,
|
||||
'struggling-with-gdevelop': strugglingChecked,
|
||||
'prefer-free-version': preferFreeVersionChecked,
|
||||
'missing-feature': missingFeatureChecked,
|
||||
'quality-issues': qualityIssuesChecked,
|
||||
other: otherChecked,
|
||||
freeText: freeText,
|
||||
},
|
||||
}
|
||||
);
|
||||
await authenticatedUser.onRefreshSubscription();
|
||||
setHasCanceledSubscription(true);
|
||||
} catch (rawError) {
|
||||
await authenticatedUser.onRefreshSubscription();
|
||||
console.error('Error while canceling subscription:', rawError);
|
||||
showAlert({
|
||||
title: t`Could not cancel your subscription`,
|
||||
message: t`There was an error while canceling your subscription. Verify your internet connection or try again later.`,
|
||||
});
|
||||
} finally {
|
||||
setIsCancelingSubscription(false);
|
||||
}
|
||||
},
|
||||
[
|
||||
authenticatedUser,
|
||||
showAlert,
|
||||
isCancelingSubscription,
|
||||
canSubmit,
|
||||
freeText,
|
||||
stoppedMakingGamesChecked,
|
||||
strugglingChecked,
|
||||
preferFreeVersionChecked,
|
||||
qualityIssuesChecked,
|
||||
missingFeatureChecked,
|
||||
otherChecked,
|
||||
]
|
||||
);
|
||||
|
||||
const isLoading =
|
||||
!authenticatedUser.subscription ||
|
||||
!authenticatedUser.profile ||
|
||||
isCancelingSubscription;
|
||||
|
||||
const actions = hasCanceledSubscription
|
||||
? [
|
||||
<DialogPrimaryButton
|
||||
label={<Trans>Close</Trans>}
|
||||
key="close"
|
||||
onClick={onCloseAfterSuccess}
|
||||
primary
|
||||
/>,
|
||||
]
|
||||
: [
|
||||
<DialogPrimaryButton
|
||||
label={<Trans>Submit and cancel</Trans>}
|
||||
key="submit"
|
||||
onClick={cancelPlan}
|
||||
disabled={!canSubmit || isLoading}
|
||||
primary
|
||||
/>,
|
||||
];
|
||||
|
||||
const secondaryActions = hasCanceledSubscription
|
||||
? []
|
||||
: [
|
||||
<FlatButton
|
||||
label={<Trans>Back</Trans>}
|
||||
key="back"
|
||||
onClick={onClose}
|
||||
disabled={isLoading}
|
||||
/>,
|
||||
];
|
||||
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<Dialog
|
||||
title={null}
|
||||
actions={actions}
|
||||
secondaryActions={secondaryActions}
|
||||
open
|
||||
cannotBeDismissed
|
||||
onApply={cancelPlan}
|
||||
maxWidth="sm"
|
||||
>
|
||||
{hasCanceledSubscription ? (
|
||||
<ColumnStackLayout
|
||||
expand
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<GDevelopGLogo fontSize="large" />
|
||||
<Text size="block-title" align="center">
|
||||
<Trans>Your subscription has been canceled</Trans>
|
||||
</Text>
|
||||
<LineStackLayout noMargin alignItems="center">
|
||||
<StarIcon />
|
||||
<Text size="sub-title" align="center">
|
||||
<Trans>Thank you for your feedback</Trans>
|
||||
</Text>
|
||||
</LineStackLayout>
|
||||
</ColumnStackLayout>
|
||||
) : (
|
||||
<ColumnStackLayout
|
||||
expand
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<GDevelopGLogo fontSize="large" />
|
||||
<Text size="block-title" align="center">
|
||||
<Trans>Before you go...</Trans>
|
||||
</Text>
|
||||
<Text size="body2" noMargin align="center">
|
||||
<Trans>
|
||||
Your feedback is valuable to help us improve our premium
|
||||
services. Why are you canceling your subscription?
|
||||
</Trans>
|
||||
</Text>
|
||||
<Spacer />
|
||||
<Form
|
||||
onSubmit={cancelPlan}
|
||||
autoComplete="off"
|
||||
name="cancel"
|
||||
fullWidth
|
||||
>
|
||||
<ColumnStackLayout noMargin expand>
|
||||
<Checkbox
|
||||
label={<Trans>I've stopped using GDevelop</Trans>}
|
||||
checked={stoppedMakingGamesChecked}
|
||||
onCheck={(e, checked) =>
|
||||
setStoppedMakingGamesChecked(checked)
|
||||
}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<Checkbox
|
||||
label={<Trans>I'm struggling to create what I want</Trans>}
|
||||
checked={strugglingChecked}
|
||||
onCheck={(e, checked) => setStrugglingChecked(checked)}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<Checkbox
|
||||
label={<Trans>The free version is enough for me</Trans>}
|
||||
checked={preferFreeVersionChecked}
|
||||
onCheck={(e, checked) =>
|
||||
setPreferFreeVersionChecked(checked)
|
||||
}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
label={
|
||||
<Trans>It's missing a feature (please specify)</Trans>
|
||||
}
|
||||
checked={missingFeatureChecked}
|
||||
onCheck={(e, checked) => setMissingFeatureChecked(checked)}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<Checkbox
|
||||
label={
|
||||
<Trans>
|
||||
I have encountered bugs or performance problems
|
||||
</Trans>
|
||||
}
|
||||
checked={qualityIssuesChecked}
|
||||
onCheck={(e, checked) => setQualityIssuesChecked(checked)}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<Checkbox
|
||||
label={<Trans>Other reason (please specify)</Trans>}
|
||||
checked={otherChecked}
|
||||
onCheck={(e, checked) => setOtherChecked(checked)}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<Spacer />
|
||||
<TextField
|
||||
autoFocus="desktop"
|
||||
value={freeText}
|
||||
multiline
|
||||
translatableHintText={t`Please tell us more`}
|
||||
floatingLabelText={<Trans>Details</Trans>}
|
||||
floatingLabelFixed
|
||||
onChange={(e, value) => {
|
||||
setFreeText(value);
|
||||
}}
|
||||
onBlur={event => {
|
||||
setFreeText(event.currentTarget.value.trim());
|
||||
}}
|
||||
fullWidth
|
||||
disabled={isLoading}
|
||||
rows={4}
|
||||
/>
|
||||
</ColumnStackLayout>
|
||||
</Form>
|
||||
</ColumnStackLayout>
|
||||
)}
|
||||
</Dialog>
|
||||
)}
|
||||
</I18n>
|
||||
);
|
||||
};
|
||||
|
||||
export default CancelReasonDialog;
|
@@ -297,10 +297,10 @@ const SubscriptionDetails = ({
|
||||
!hasMobileAppStoreSubscriptionPlan(subscription) &&
|
||||
!hasSubscriptionBeenManuallyAdded(subscription) ? (
|
||||
<FlatButton
|
||||
key="manage-online"
|
||||
key="manage-payments"
|
||||
label={
|
||||
<LeftLoader isLoading={isManageSubscriptionLoading}>
|
||||
<Trans>Manage online</Trans>
|
||||
<Trans>Manage payments</Trans>
|
||||
</LeftLoader>
|
||||
}
|
||||
primary
|
||||
@@ -309,8 +309,8 @@ const SubscriptionDetails = ({
|
||||
/>
|
||||
) : null,
|
||||
<RaisedButton
|
||||
key="manage"
|
||||
label={<Trans>Change subscription</Trans>}
|
||||
key="manage-subscription"
|
||||
label={<Trans>Manage subscription</Trans>}
|
||||
primary
|
||||
onClick={() =>
|
||||
openSubscriptionDialog({
|
||||
|
@@ -47,6 +47,7 @@ import { useResponsiveWindowSize } from '../../UI/Responsive/ResponsiveWindowMea
|
||||
import Link from '../../UI/Link';
|
||||
import { selectMessageByLocale } from '../../Utils/i18n/MessageByLocale';
|
||||
import uniq from 'lodash/uniq';
|
||||
import CancelReasonDialog from './CancelReasonDialog';
|
||||
|
||||
const styles = {
|
||||
descriptionText: {
|
||||
@@ -85,34 +86,39 @@ const styles = {
|
||||
};
|
||||
|
||||
const cancelConfirmationTexts = {
|
||||
title: t`Cancel your subscription`,
|
||||
message: t`Are you sure you want to cancel your subscription?`,
|
||||
confirmButtonLabel: t`Cancel my subscription`,
|
||||
dismissButtonLabel: t`Go back`,
|
||||
title: t`Cancel your subscription?`,
|
||||
message: t`By canceling your subscription, you will lose all your premium features at the end of the period you already paid for. Continue?`,
|
||||
confirmButtonLabel: t`Continue`,
|
||||
dismissButtonLabel: t`Keep subscription`,
|
||||
maxWidth: 'sm',
|
||||
};
|
||||
const cancelImmediatelyConfirmationTexts = {
|
||||
title: t`Cancel your subscription`,
|
||||
message: t`Are you sure you want to cancel your subscription? Your access to GDevelop premium features will end IMMEDIATELY.`,
|
||||
title: t`Cancel your subscription?`,
|
||||
message: t`By canceling your subscription you will lose all your premium features IMMEDIATELY. Continue?`,
|
||||
confirmButtonLabel: t`Cancel my subscription now`,
|
||||
dismissButtonLabel: t`Go back`,
|
||||
dismissButtonLabel: t`Keep subscription`,
|
||||
maxWidth: 'sm',
|
||||
};
|
||||
const seamlesslyChangeConfirmationTexts = {
|
||||
title: t`Update your subscription`,
|
||||
message: t`Are you sure you want to change your plan? Your next payment will be pro-rated.`,
|
||||
confirmButtonLabel: t`Update my subscription`,
|
||||
dismissButtonLabel: t`Go back`,
|
||||
maxWidth: 'sm',
|
||||
};
|
||||
const cancelAndChangeConfirmationTexts = {
|
||||
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`,
|
||||
maxWidth: 'sm',
|
||||
};
|
||||
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`,
|
||||
maxWidth: 'sm',
|
||||
};
|
||||
|
||||
const getPlanSpecificRequirements = (
|
||||
@@ -171,7 +177,10 @@ export default function SubscriptionDialog({
|
||||
] = React.useState(false);
|
||||
const [redeemCodeDialogOpen, setRedeemCodeDialogOpen] = React.useState(false);
|
||||
const authenticatedUser = React.useContext(AuthenticatedUserContext);
|
||||
const { showConfirmation, showAlert } = useAlertDialog();
|
||||
const { showConfirmation } = useAlertDialog();
|
||||
const [cancelReasonDialogOpen, setCancelReasonDialogOpen] = React.useState(
|
||||
false
|
||||
);
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
@@ -221,33 +230,7 @@ export default function SubscriptionDialog({
|
||||
);
|
||||
if (!answer) return;
|
||||
|
||||
setIsChangingSubscription(true);
|
||||
try {
|
||||
await changeUserSubscription(
|
||||
getAuthorizationHeader,
|
||||
profile.id,
|
||||
{
|
||||
planId: null,
|
||||
},
|
||||
{ cancelImmediately: false }
|
||||
);
|
||||
await authenticatedUser.onRefreshSubscription();
|
||||
showAlert({
|
||||
title: t`Subscription cancelled`,
|
||||
message: t`Your subscription is now cancelled.`,
|
||||
});
|
||||
} catch (rawError) {
|
||||
await authenticatedUser.onRefreshSubscription();
|
||||
showErrorBox({
|
||||
message: i18n._(
|
||||
t`Your subscription could not be cancelled. Please try again later!`
|
||||
),
|
||||
rawError,
|
||||
errorId: 'subscription-update-error',
|
||||
});
|
||||
} finally {
|
||||
setIsChangingSubscription(false);
|
||||
}
|
||||
setCancelReasonDialogOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -294,7 +277,12 @@ export default function SubscriptionDialog({
|
||||
{
|
||||
planId: null,
|
||||
},
|
||||
{ cancelImmediately: true }
|
||||
{
|
||||
cancelImmediately: true,
|
||||
cancelReasons: {
|
||||
'changing-subscription': true,
|
||||
},
|
||||
}
|
||||
);
|
||||
await authenticatedUser.onRefreshSubscription();
|
||||
} catch (rawError) {
|
||||
@@ -362,13 +350,23 @@ export default function SubscriptionDialog({
|
||||
})
|
||||
: null;
|
||||
|
||||
const dialogMaxWidth =
|
||||
!displayedSubscriptionPlanWithPricingSystems ||
|
||||
displayedSubscriptionPlanWithPricingSystems.length === 1
|
||||
? 'md'
|
||||
: displayedSubscriptionPlanWithPricingSystems.length < 4
|
||||
? 'lg'
|
||||
: displayedSubscriptionPlanWithPricingSystems.length < 5
|
||||
? 'xl'
|
||||
: false;
|
||||
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<>
|
||||
<Dialog
|
||||
title={null}
|
||||
maxWidth={false}
|
||||
maxWidth={dialogMaxWidth}
|
||||
actions={[
|
||||
<FlatButton
|
||||
label={<Trans>Close</Trans>}
|
||||
@@ -701,6 +699,17 @@ export default function SubscriptionDialog({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{cancelReasonDialogOpen && (
|
||||
<CancelReasonDialog
|
||||
onClose={() => {
|
||||
setCancelReasonDialogOpen(false);
|
||||
}}
|
||||
onCloseAfterSuccess={() => {
|
||||
setCancelReasonDialogOpen(false);
|
||||
onClose();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</I18n>
|
||||
|
@@ -17,8 +17,8 @@ import SelectField from '../UI/SelectField';
|
||||
import SelectOption from '../UI/SelectOption';
|
||||
import CreateProfile from '../Profile/CreateProfile';
|
||||
import Paper from '../UI/Paper';
|
||||
import { Column, Line } from '../UI/Grid';
|
||||
import LeftLoader from '../UI/LeftLoader';
|
||||
import { Column, LargeSpacer, Line, Spacer } from '../UI/Grid';
|
||||
import {
|
||||
checkIfHasTooManyCloudProjects,
|
||||
MaxProjectCountAlertMessage,
|
||||
@@ -42,7 +42,6 @@ import ResolutionOptions, {
|
||||
defaultCustomHeight,
|
||||
} from './ResolutionOptions';
|
||||
import Text from '../UI/Text';
|
||||
import DismissableAlertMessage from '../UI/DismissableAlertMessage';
|
||||
import generatePrompt from '../Utils/ProjectPromptGenerator';
|
||||
import ProjectGeneratingDialog from './ProjectGeneratingDialog';
|
||||
import useAlertDialog from '../UI/Alert/useAlertDialog';
|
||||
@@ -53,6 +52,7 @@ import { I18n } from '@lingui/react';
|
||||
import GetSubscriptionCard from '../Profile/Subscription/GetSubscriptionCard';
|
||||
import { type PrivateGameTemplateListingData } from '../Utils/GDevelopServices/Shop';
|
||||
import { extractGDevelopApiErrorStatusAndCode } from '../Utils/GDevelopServices/Errors';
|
||||
import { Tabs } from '../UI/Tabs';
|
||||
|
||||
const electron = optionalRequire('electron');
|
||||
const remote = optionalRequire('@electron/remote');
|
||||
@@ -71,6 +71,7 @@ export type NewProjectSetup = {|
|
||||
|};
|
||||
|
||||
type Props = {|
|
||||
initialTab?: 'ai' | 'from-scratch' | 'example',
|
||||
isOpeningProject?: boolean,
|
||||
onClose: () => void,
|
||||
onCreateEmptyProject: (newProjectSetup: NewProjectSetup) => Promise<void>,
|
||||
@@ -96,6 +97,7 @@ type Props = {|
|
||||
|};
|
||||
|
||||
const NewProjectSetupDialog = ({
|
||||
initialTab,
|
||||
isOpeningProject,
|
||||
onClose,
|
||||
onCreateEmptyProject,
|
||||
@@ -108,6 +110,10 @@ const NewProjectSetupDialog = ({
|
||||
storageProviders,
|
||||
authenticatedUser,
|
||||
}: Props): React.Node => {
|
||||
const [currentTab, setCurrentTab] = React.useState<
|
||||
'ai' | 'from-scratch' | 'example'
|
||||
>(initialTab || 'ai');
|
||||
|
||||
const generateProjectName = () =>
|
||||
selectedExampleShortHeader
|
||||
? `${generateName()} (${selectedExampleShortHeader.name})`
|
||||
@@ -220,7 +226,7 @@ const NewProjectSetupDialog = ({
|
||||
? authenticatedUser.limits.quotas['ai-project-generation']
|
||||
: null;
|
||||
const canGenerateProjectFromPrompt =
|
||||
generationCurrentUsage && !generationCurrentUsage.limitReached;
|
||||
!!generationCurrentUsage && !generationCurrentUsage.limitReached;
|
||||
|
||||
const needUserAuthenticationForStorage =
|
||||
storageProvider.needUserAuthentication && !authenticatedUser.authenticated;
|
||||
@@ -243,7 +249,7 @@ const NewProjectSetupDialog = ({
|
||||
|
||||
const isLoading = isGeneratingProject || isOpeningProject;
|
||||
|
||||
const isStartingProjectFromScratch =
|
||||
const isCreatingANewProject =
|
||||
!selectedExampleShortHeader && !selectedPrivateGameTemplateListingData;
|
||||
|
||||
// On the local app, prefer to always have something saved so that the user is not blocked.
|
||||
@@ -414,6 +420,24 @@ const NewProjectSetupDialog = ({
|
||||
title={<Trans>New Project</Trans>}
|
||||
id="project-pre-creation-dialog"
|
||||
maxWidth="sm"
|
||||
fixedContent={
|
||||
isCreatingANewProject ? (
|
||||
<Tabs
|
||||
value={currentTab}
|
||||
onChange={setCurrentTab}
|
||||
options={[
|
||||
{
|
||||
value: 'ai',
|
||||
label: <Trans>Create for me</Trans>,
|
||||
},
|
||||
{
|
||||
value: 'from-scratch',
|
||||
label: <Trans>Create from scratch</Trans>,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
actions={[
|
||||
<FlatButton
|
||||
disabled={isLoading}
|
||||
@@ -436,16 +460,20 @@ const NewProjectSetupDialog = ({
|
||||
onApply={() => onValidate(i18n)}
|
||||
>
|
||||
<ColumnStackLayout noMargin>
|
||||
{isStartingProjectFromScratch && (
|
||||
<ResolutionOptions
|
||||
onClick={key => setResolutionOption(key)}
|
||||
selectedOption={resolutionOption}
|
||||
disabled={isLoading}
|
||||
customHeight={customHeight}
|
||||
customWidth={customWidth}
|
||||
onCustomHeightChange={setCustomHeight}
|
||||
onCustomWidthChange={setCustomWidth}
|
||||
/>
|
||||
<LargeSpacer />
|
||||
{isCreatingANewProject && currentTab === 'from-scratch' && (
|
||||
<>
|
||||
<ResolutionOptions
|
||||
onClick={key => setResolutionOption(key)}
|
||||
selectedOption={resolutionOption}
|
||||
disabled={isLoading}
|
||||
customHeight={customHeight}
|
||||
customWidth={customWidth}
|
||||
onCustomHeightChange={setCustomHeight}
|
||||
onCustomWidthChange={setCustomWidth}
|
||||
/>
|
||||
<Spacer />
|
||||
</>
|
||||
)}
|
||||
<TextField
|
||||
type="text"
|
||||
@@ -467,6 +495,69 @@ const NewProjectSetupDialog = ({
|
||||
autoFocus="desktop"
|
||||
maxLength={100}
|
||||
/>
|
||||
{isCreatingANewProject && currentTab === 'ai' && (
|
||||
<ColumnStackLayout noMargin>
|
||||
{authenticatedUser.authenticated &&
|
||||
!canGenerateProjectFromPrompt && (
|
||||
<GetSubscriptionCard subscriptionDialogOpeningReason="Generate project from prompt">
|
||||
<Line>
|
||||
<Column noMargin>
|
||||
<Text noMargin>
|
||||
<Trans>
|
||||
You've used all your daily pre-made AI scenes!
|
||||
Generate as many as you want with a subscription.
|
||||
</Trans>
|
||||
</Text>
|
||||
</Column>
|
||||
</Line>
|
||||
</GetSubscriptionCard>
|
||||
)}
|
||||
<LineStackLayout
|
||||
expand
|
||||
noMargin
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<RobotIcon />
|
||||
<TextField
|
||||
type="text"
|
||||
multiline
|
||||
maxLength={200}
|
||||
fullWidth
|
||||
disabled={
|
||||
isLoading ||
|
||||
!authenticatedUser.authenticated ||
|
||||
!isOnline ||
|
||||
!canGenerateProjectFromPrompt
|
||||
}
|
||||
value={generationPrompt}
|
||||
onChange={(e, text) => setGenerationPrompt(text)}
|
||||
floatingLabelText={<Trans>AI prompt</Trans>}
|
||||
floatingLabelFixed
|
||||
translatableHintText={
|
||||
!authenticatedUser.authenticated || !isOnline
|
||||
? t`Log in to generate a project from a prompt`
|
||||
: t`Type a prompt yourself or generate a random one`
|
||||
}
|
||||
endAdornment={
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => setGenerationPrompt(generatePrompt())}
|
||||
tooltip={t`Generate random prompt`}
|
||||
disabled={
|
||||
isLoading ||
|
||||
!authenticatedUser.authenticated ||
|
||||
!isOnline ||
|
||||
!canGenerateProjectFromPrompt
|
||||
}
|
||||
>
|
||||
<Refresh />
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
</LineStackLayout>
|
||||
</ColumnStackLayout>
|
||||
)}
|
||||
<SelectField
|
||||
fullWidth
|
||||
disabled={isLoading}
|
||||
@@ -533,73 +624,8 @@ const NewProjectSetupDialog = ({
|
||||
setSaveAsLocation,
|
||||
newProjectsDefaultFolder,
|
||||
})}
|
||||
{isStartingProjectFromScratch && (
|
||||
{isCreatingANewProject && currentTab === 'from-scratch' && (
|
||||
<ColumnStackLayout noMargin expand>
|
||||
<DismissableAlertMessage
|
||||
kind="info"
|
||||
identifier="new-generate-project-from-prompt"
|
||||
>
|
||||
<Trans>NEW! Generate a pre-made AI scene with assets.</Trans>
|
||||
</DismissableAlertMessage>
|
||||
<LineStackLayout
|
||||
expand
|
||||
noMargin
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<RobotIcon />
|
||||
<TextField
|
||||
type="text"
|
||||
multiline
|
||||
maxLength={200}
|
||||
fullWidth
|
||||
disabled={
|
||||
isLoading ||
|
||||
!authenticatedUser.authenticated ||
|
||||
!isOnline ||
|
||||
!canGenerateProjectFromPrompt
|
||||
}
|
||||
value={generationPrompt}
|
||||
onChange={(e, text) => setGenerationPrompt(text)}
|
||||
floatingLabelText={<Trans>AI prompt</Trans>}
|
||||
floatingLabelFixed
|
||||
translatableHintText={
|
||||
!authenticatedUser.authenticated || !isOnline
|
||||
? t`Log in to generate a project from a prompt`
|
||||
: t`Type a prompt yourself or generate a random one`
|
||||
}
|
||||
endAdornment={
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => setGenerationPrompt(generatePrompt())}
|
||||
tooltip={t`Generate random prompt`}
|
||||
disabled={
|
||||
isLoading ||
|
||||
!authenticatedUser.authenticated ||
|
||||
!isOnline ||
|
||||
!canGenerateProjectFromPrompt
|
||||
}
|
||||
>
|
||||
<Refresh />
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
</LineStackLayout>
|
||||
{authenticatedUser.authenticated &&
|
||||
!canGenerateProjectFromPrompt && (
|
||||
<GetSubscriptionCard subscriptionDialogOpeningReason="Generate project from prompt">
|
||||
<Line>
|
||||
<Column noMargin>
|
||||
<Text noMargin>
|
||||
<Trans>
|
||||
You've used all your daily pre-made AI scenes!
|
||||
Generate as many as you want with a subscription.
|
||||
</Trans>
|
||||
</Text>
|
||||
</Column>
|
||||
</Line>
|
||||
</GetSubscriptionCard>
|
||||
)}
|
||||
<Text size="sub-title">
|
||||
<Trans>Advanced File options</Trans>
|
||||
</Text>
|
||||
|
@@ -93,7 +93,7 @@ type Props = {|
|
||||
forcedCursor: string | null,
|
||||
deactivateControls?: boolean,
|
||||
|}) => React.Node,
|
||||
onSize?: (number, number) => void,
|
||||
onImageSize?: ([number, number]) => void,
|
||||
hideCheckeredBackground?: boolean,
|
||||
deactivateControls?: boolean,
|
||||
isImagePrivate?: boolean,
|
||||
@@ -128,7 +128,7 @@ const ImagePreview = ({
|
||||
fixedHeight,
|
||||
fixedWidth,
|
||||
renderOverlay,
|
||||
onSize,
|
||||
onImageSize,
|
||||
hideCheckeredBackground,
|
||||
deactivateControls,
|
||||
displaySpacedView,
|
||||
@@ -418,10 +418,10 @@ const ImagePreview = ({
|
||||
: 0;
|
||||
setImageHeight(newImageHeight);
|
||||
setImageWidth(newImageWidth);
|
||||
if (onSize) onSize(newImageWidth, newImageHeight);
|
||||
if (onImageSize) onImageSize([newImageWidth, newImageHeight]);
|
||||
if (onImageLoaded) onImageLoaded();
|
||||
},
|
||||
[onImageLoaded, onSize]
|
||||
[onImageLoaded, onImageSize]
|
||||
);
|
||||
|
||||
const onTouchEnd = React.useCallback((event: TouchEvent) => {
|
||||
|
@@ -12,7 +12,6 @@ type Props = {|
|
||||
project: gdProject,
|
||||
resourceName: string,
|
||||
resourcesLoader: typeof ResourcesLoader,
|
||||
onSize?: (number, number) => void,
|
||||
|};
|
||||
|
||||
/**
|
||||
@@ -39,7 +38,6 @@ const ResourcePreview = (props: Props) => {
|
||||
project,
|
||||
resourceName
|
||||
)}
|
||||
onSize={props.onSize}
|
||||
/>
|
||||
);
|
||||
case 'audio':
|
||||
|
@@ -21,6 +21,7 @@ export type ShowConfirmDialogOptions = {|
|
||||
dismissButtonLabel?: MessageDescriptor,
|
||||
message: MessageDescriptor,
|
||||
level?: 'info' | 'warning',
|
||||
maxWidth?: 'xs' | 'sm' | 'md',
|
||||
makeDismissButtonPrimary?: boolean,
|
||||
|};
|
||||
export type ShowConfirmDialogOptionsWithCallback = {|
|
||||
|
@@ -117,6 +117,7 @@ function ConfirmProvider({ children }: Props) {
|
||||
title={confirmDialogConfig.title}
|
||||
message={confirmDialogConfig.message}
|
||||
level={confirmDialogConfig.level || 'info'}
|
||||
maxWidth={confirmDialogConfig.maxWidth}
|
||||
makeDismissButtonPrimary={
|
||||
confirmDialogConfig.makeDismissButtonPrimary
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ type Props = {|
|
||||
confirmButtonLabel?: MessageDescriptor,
|
||||
dismissButtonLabel?: MessageDescriptor,
|
||||
level: 'info' | 'warning' | 'error',
|
||||
maxWidth?: 'xs' | 'sm' | 'md',
|
||||
makeDismissButtonPrimary?: boolean,
|
||||
|};
|
||||
|
||||
@@ -78,7 +79,7 @@ function ConfirmDialog(props: Props) {
|
||||
title={i18n._(props.title)}
|
||||
open={props.open}
|
||||
actions={dialogActions}
|
||||
maxWidth="xs"
|
||||
maxWidth={props.maxWidth || 'xs'}
|
||||
noMobileFullScreen
|
||||
onRequestClose={props.onDismiss}
|
||||
onApply={props.onConfirm}
|
||||
|
18
newIDE/app/src/UI/CustomSvgIcons/Star.js
Normal file
18
newIDE/app/src/UI/CustomSvgIcons/Star.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import SvgIcon from '@material-ui/core/SvgIcon';
|
||||
|
||||
export default React.memo(props => (
|
||||
<SvgIcon
|
||||
{...props}
|
||||
width="27"
|
||||
height="26"
|
||||
viewBox="0 0 27 26"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M13.5 0L17.5487 8.42749L26.8148 9.67376L20.0509 16.1285L21.729 25.3262L13.5 20.888L5.27101 25.3262L6.94912 16.1285L0.185208 9.67376L9.45133 8.42749L13.5 0Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</SvgIcon>
|
||||
));
|
@@ -6,6 +6,7 @@ type Props = {|
|
||||
autoComplete?: 'on' | 'off',
|
||||
name: string,
|
||||
children: React.Node,
|
||||
fullWidth?: boolean,
|
||||
|};
|
||||
|
||||
const Form = ({
|
||||
@@ -13,6 +14,7 @@ const Form = ({
|
||||
autoComplete = 'off', // Default to 'off' to avoid browser autofill.
|
||||
name,
|
||||
children,
|
||||
fullWidth,
|
||||
}: Props) => {
|
||||
return (
|
||||
<form
|
||||
@@ -23,6 +25,7 @@ const Form = ({
|
||||
}}
|
||||
autoComplete={autoComplete}
|
||||
name={name}
|
||||
style={{ width: fullWidth ? '100%' : undefined }}
|
||||
>
|
||||
{children}
|
||||
{/*
|
||||
|
@@ -3,16 +3,13 @@ import * as React from 'react';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import { getGravatarUrl } from '../GravatarUrl';
|
||||
import DotBadge from '../DotBadge';
|
||||
import RaisedButton from '../RaisedButton';
|
||||
import { shortenString } from '../../Utils/StringHelpers';
|
||||
import TextButton from '../TextButton';
|
||||
import { LineStackLayout } from '../Layout';
|
||||
import FlatButton from '../FlatButton';
|
||||
import AuthenticatedUserContext from '../../Profile/AuthenticatedUserContext';
|
||||
import CircularProgress from '../CircularProgress';
|
||||
import User from '../CustomSvgIcons/User';
|
||||
import { hasPendingBadgeNotifications } from '../../Utils/GDevelopServices/Badge';
|
||||
|
||||
const styles = {
|
||||
avatar: {
|
||||
@@ -28,45 +25,25 @@ type Props = {|
|
||||
|
||||
const UserChip = ({ onOpenProfile }: Props) => {
|
||||
const authenticatedUser = React.useContext(AuthenticatedUserContext);
|
||||
const {
|
||||
profile,
|
||||
onOpenLoginDialog,
|
||||
onOpenCreateAccountDialog,
|
||||
loginState,
|
||||
} = authenticatedUser;
|
||||
// TODO: Remove the badge on the user chip and handle badge notifications
|
||||
// with user notifications.
|
||||
const displayNotificationBadge = hasPendingBadgeNotifications(
|
||||
authenticatedUser
|
||||
);
|
||||
const { profile, onOpenCreateAccountDialog, loginState } = authenticatedUser;
|
||||
|
||||
return !profile && loginState === 'loggingIn' ? (
|
||||
<CircularProgress size={25} />
|
||||
) : profile ? (
|
||||
<DotBadge overlap="circle" invisible={!displayNotificationBadge}>
|
||||
<TextButton
|
||||
label={shortenString(profile.username || profile.email, 20)}
|
||||
onClick={onOpenProfile}
|
||||
allowBrowserAutoTranslate={false}
|
||||
icon={
|
||||
<Avatar
|
||||
src={getGravatarUrl(profile.email || '', { size: 50 })}
|
||||
style={styles.avatar}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</DotBadge>
|
||||
<TextButton
|
||||
label={shortenString(profile.username || profile.email, 20)}
|
||||
onClick={onOpenProfile}
|
||||
allowBrowserAutoTranslate={false}
|
||||
icon={
|
||||
<Avatar
|
||||
src={getGravatarUrl(profile.email || '', { size: 50 })}
|
||||
style={styles.avatar}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div style={styles.buttonContainer}>
|
||||
<LineStackLayout noMargin alignItems="center">
|
||||
<FlatButton
|
||||
label={
|
||||
<span>
|
||||
<Trans>Log in</Trans>
|
||||
</span>
|
||||
}
|
||||
onClick={onOpenLoginDialog}
|
||||
leftIcon={<User fontSize="small" />}
|
||||
/>
|
||||
<RaisedButton
|
||||
label={
|
||||
<span>
|
||||
|
@@ -183,16 +183,3 @@ export const compareAchievements = (
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
export const hasPendingBadgeNotifications = (
|
||||
authenticatedUser: AuthenticatedUser
|
||||
): boolean => {
|
||||
if (!authenticatedUser.authenticated) return false;
|
||||
|
||||
const { badges } = authenticatedUser;
|
||||
if (badges && badges.length > 0) {
|
||||
return badges.some(badge => !badge.seen);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
@@ -13,6 +13,10 @@ export type Usage = {
|
||||
};
|
||||
export type Usages = Array<Usage>;
|
||||
|
||||
export type CancelReasons = {
|
||||
[key: string]: boolean | string,
|
||||
};
|
||||
|
||||
export type Subscription = {|
|
||||
userId: string,
|
||||
planId: string | null,
|
||||
@@ -34,6 +38,7 @@ export type Subscription = {|
|
||||
paypalPayerId?: string,
|
||||
|
||||
cancelAtPeriodEnd?: boolean,
|
||||
cancelReasons?: CancelReasons,
|
||||
|
||||
purchaselyPlan?: string,
|
||||
|
||||
@@ -270,7 +275,7 @@ export const changeUserSubscription = async (
|
||||
getAuthorizationHeader: () => Promise<string>,
|
||||
userId: string,
|
||||
newSubscriptionDetails: {| planId: string | null |},
|
||||
options: {| cancelImmediately: boolean |}
|
||||
options: {| cancelImmediately: boolean, cancelReasons: CancelReasons |}
|
||||
): Promise<Subscription> => {
|
||||
const authorizationHeader = await getAuthorizationHeader();
|
||||
|
||||
@@ -280,6 +285,7 @@ export const changeUserSubscription = async (
|
||||
...newSubscriptionDetails,
|
||||
prohibitSeamlessUpdate: true,
|
||||
cancelImmediately: options.cancelImmediately,
|
||||
cancelReasons: options.cancelReasons,
|
||||
},
|
||||
{
|
||||
params: {
|
||||
|
@@ -56,11 +56,18 @@ const Wrapper = ({ children }: { children: React.Node }) => {
|
||||
|
||||
export const Default = () => (
|
||||
<Wrapper>
|
||||
<AssetStore />
|
||||
<AssetStore displayPromotions />
|
||||
</Wrapper>
|
||||
);
|
||||
Default.parameters = apiDataFakePacks;
|
||||
|
||||
export const WithoutPromotions = () => (
|
||||
<Wrapper>
|
||||
<AssetStore displayPromotions={false} />
|
||||
</Wrapper>
|
||||
);
|
||||
WithoutPromotions.parameters = apiDataFakePacks;
|
||||
|
||||
export const LoadingError = () => (
|
||||
<Wrapper>
|
||||
<AssetStore />
|
||||
|
@@ -0,0 +1,36 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import muiDecorator from '../../../ThemeDecorator';
|
||||
import paperDecorator from '../../../PaperDecorator';
|
||||
import AuthenticatedUserContext from '../../../../Profile/AuthenticatedUserContext';
|
||||
import {
|
||||
fakeSilverAuthenticatedUser,
|
||||
fakeNotAuthenticatedUser,
|
||||
} from '../../../../fixtures/GDevelopServicesTestData';
|
||||
import CancelReasonDialog from '../../../../Profile/Subscription/CancelReasonDialog';
|
||||
|
||||
export default {
|
||||
title: 'Subscription/CancelReasonDialog',
|
||||
component: CancelReasonDialog,
|
||||
decorators: [paperDecorator, muiDecorator],
|
||||
};
|
||||
|
||||
export const Loading = () => (
|
||||
<AuthenticatedUserContext.Provider value={fakeNotAuthenticatedUser}>
|
||||
<CancelReasonDialog
|
||||
onClose={() => action('on close')()}
|
||||
onCloseAfterSuccess={action('on close after success')}
|
||||
/>
|
||||
</AuthenticatedUserContext.Provider>
|
||||
);
|
||||
|
||||
export const Default = () => (
|
||||
<AuthenticatedUserContext.Provider value={fakeSilverAuthenticatedUser}>
|
||||
<CancelReasonDialog
|
||||
onClose={() => action('on close')()}
|
||||
onCloseAfterSuccess={action('on close after success')}
|
||||
/>
|
||||
</AuthenticatedUserContext.Provider>
|
||||
);
|
@@ -23,9 +23,10 @@ export default {
|
||||
decorators: [paperDecorator, muiDecorator],
|
||||
};
|
||||
|
||||
export const OpenAndNotAuthenticated = () => {
|
||||
export const OpenOnAIAndNotAuthenticated = () => {
|
||||
return (
|
||||
<NewProjectSetupDialog
|
||||
initialTab="ai"
|
||||
authenticatedUser={fakeNotAuthenticatedUser}
|
||||
storageProviders={[
|
||||
UrlStorageProvider,
|
||||
@@ -47,9 +48,59 @@ export const OpenAndNotAuthenticated = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const OpenAndAuthenticated = () => {
|
||||
export const OpenOnAIAndAuthenticated = () => {
|
||||
return (
|
||||
<NewProjectSetupDialog
|
||||
initialTab="ai"
|
||||
authenticatedUser={fakeSilverAuthenticatedUser}
|
||||
storageProviders={[
|
||||
UrlStorageProvider,
|
||||
CloudStorageProvider,
|
||||
GoogleDriveStorageProvider,
|
||||
DownloadFileStorageProvider,
|
||||
]}
|
||||
onClose={() => action('click on close')()}
|
||||
onCreateEmptyProject={() => action('create empty')()}
|
||||
onCreateFromExample={() => action('create from example')()}
|
||||
onCreateWithLogin={() => action('create with login')()}
|
||||
onCreateFromAIGeneration={() => action('create from AI generation')()}
|
||||
onCreateProjectFromPrivateGameTemplate={() =>
|
||||
action('create project from private game template')()
|
||||
}
|
||||
selectedExampleShortHeader={null}
|
||||
selectedPrivateGameTemplateListingData={null}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export const OpenOnFromScratchAndNotAuthenticated = () => {
|
||||
return (
|
||||
<NewProjectSetupDialog
|
||||
initialTab="from-scratch"
|
||||
authenticatedUser={fakeNotAuthenticatedUser}
|
||||
storageProviders={[
|
||||
UrlStorageProvider,
|
||||
CloudStorageProvider,
|
||||
GoogleDriveStorageProvider,
|
||||
DownloadFileStorageProvider,
|
||||
]}
|
||||
onClose={() => action('click on close')()}
|
||||
onCreateEmptyProject={() => action('create empty')()}
|
||||
onCreateFromExample={() => action('create from example')()}
|
||||
onCreateWithLogin={() => action('create with login')()}
|
||||
onCreateFromAIGeneration={() => action('create from AI generation')()}
|
||||
onCreateProjectFromPrivateGameTemplate={() =>
|
||||
action('create project from private game template')()
|
||||
}
|
||||
selectedExampleShortHeader={null}
|
||||
selectedPrivateGameTemplateListingData={null}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const OpenOnFromScratchAndAuthenticated = () => {
|
||||
return (
|
||||
<NewProjectSetupDialog
|
||||
initialTab="from-scratch"
|
||||
authenticatedUser={fakeSilverAuthenticatedUser}
|
||||
storageProviders={[
|
||||
UrlStorageProvider,
|
||||
@@ -96,9 +147,37 @@ export const Opening = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const LimitsReached = () => {
|
||||
export const OpenOnFromScratchAndLimitsReached = () => {
|
||||
return (
|
||||
<NewProjectSetupDialog
|
||||
initialTab="from-scratch"
|
||||
authenticatedUser={
|
||||
fakeAuthenticatedUserWithNoSubscriptionAndTooManyCloudProjects
|
||||
}
|
||||
storageProviders={[
|
||||
CloudStorageProvider,
|
||||
UrlStorageProvider,
|
||||
GoogleDriveStorageProvider,
|
||||
DownloadFileStorageProvider,
|
||||
]}
|
||||
onClose={() => action('click on close')()}
|
||||
onCreateEmptyProject={() => action('create empty')()}
|
||||
onCreateFromExample={() => action('create from example')()}
|
||||
onCreateWithLogin={() => action('create with login')()}
|
||||
onCreateFromAIGeneration={() => action('create from AI generation')()}
|
||||
onCreateProjectFromPrivateGameTemplate={() =>
|
||||
action('create project from private game template')()
|
||||
}
|
||||
selectedExampleShortHeader={null}
|
||||
selectedPrivateGameTemplateListingData={null}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const OpenOnAIAndLimitsReached = () => {
|
||||
return (
|
||||
<NewProjectSetupDialog
|
||||
initialTab="ai"
|
||||
authenticatedUser={
|
||||
fakeAuthenticatedUserWithNoSubscriptionAndTooManyCloudProjects
|
||||
}
|
||||
|
Reference in New Issue
Block a user