Compare commits

..

5 Commits

Author SHA1 Message Date
Florian Rival
5193b617b3 Improve create new project dialog
TODO: disable button if no prompt entered
2024-03-25 13:55:56 +01:00
Clément Pasteau
1bb473b0b0 Display questions when canceling a subscription (#6471) 2024-03-22 14:07:59 +01:00
Florian Rival
4376b4f36e Improve first screen layouts (#6468) 2024-03-22 10:33:42 +01:00
Florian Rival
6ecbae9c35 Display the coordinates of the center point of a Sprite even when set automatically
Fix #6472
2024-03-22 10:33:05 +01:00
D8H
93e9fc6aed Fix missing expressions for text object (#6470) 2024-03-21 10:30:25 +01:00
40 changed files with 1072 additions and 610 deletions

View File

@@ -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");
}

View File

@@ -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("");

View File

@@ -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>

View File

@@ -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

View File

@@ -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 />

View File

@@ -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>,

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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}

View File

@@ -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>

View File

@@ -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}

View File

@@ -162,9 +162,6 @@ const MainPage = ({
return (
<SectionContainer title={<Trans>Help and guides</Trans>}>
<SectionRow>
<Text>
<Trans>Quick search</Trans>
</Text>
<WikiSearchBar />
</SectionRow>
<SectionRow>

View File

@@ -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 && (

View File

@@ -83,6 +83,7 @@ const StoreSection = ({
onOpenPrivateGameTemplateListingData={
onOpenPrivateGameTemplateListingData
}
displayPromotions
/>
{(openedAssetPack || openedAssetShortHeader) && (
<Line justifyContent="flex-end">

View File

@@ -114,7 +114,9 @@ type Props = {|
onOpenAbout: () => void,
// Project creation
onOpenNewProjectSetupDialog: () => void,
onOpenNewProjectSetupDialog: (
initialTab: 'from-scratch' | 'ai' | 'example'
) => void,
// Project save
onSave: () => Promise<void>,

View File

@@ -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,

View File

@@ -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={

View File

@@ -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(

View File

@@ -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>
)}

View File

@@ -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}>

View File

@@ -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 />

View File

@@ -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 && (

View 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;

View File

@@ -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({

View File

@@ -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>

View File

@@ -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>

View File

@@ -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) => {

View File

@@ -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':

View File

@@ -21,6 +21,7 @@ export type ShowConfirmDialogOptions = {|
dismissButtonLabel?: MessageDescriptor,
message: MessageDescriptor,
level?: 'info' | 'warning',
maxWidth?: 'xs' | 'sm' | 'md',
makeDismissButtonPrimary?: boolean,
|};
export type ShowConfirmDialogOptionsWithCallback = {|

View File

@@ -117,6 +117,7 @@ function ConfirmProvider({ children }: Props) {
title={confirmDialogConfig.title}
message={confirmDialogConfig.message}
level={confirmDialogConfig.level || 'info'}
maxWidth={confirmDialogConfig.maxWidth}
makeDismissButtonPrimary={
confirmDialogConfig.makeDismissButtonPrimary
}

View File

@@ -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}

View 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>
));

View File

@@ -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}
{/*

View File

@@ -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>

View File

@@ -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;
};

View File

@@ -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: {

View File

@@ -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 />

View File

@@ -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>
);

View File

@@ -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
}