mirror of
https://github.com/4ian/GDevelop.git
synced 2026-10-12 02:01:17 +00:00
Allow extensions to set different icons for behaviors and objects (#8472)
This commit is contained in:
@@ -26,6 +26,9 @@ void AbstractEventsBasedEntity::SerializeTo(SerializerElement& element) const {
|
||||
if (isPrivate) {
|
||||
element.SetBoolAttribute("private", isPrivate);
|
||||
}
|
||||
element.SetAttribute("previewIconUrl", previewIconUrl);
|
||||
element.SetAttribute("iconUrl", iconUrl);
|
||||
element.SetAttribute("helpPath", helpPath);
|
||||
|
||||
gd::SerializerElement& eventsFunctionsElement =
|
||||
element.AddChild("eventsFunctions");
|
||||
@@ -45,6 +48,9 @@ void AbstractEventsBasedEntity::UnserializeFrom(
|
||||
name = element.GetStringAttribute("name");
|
||||
fullName = element.GetStringAttribute("fullName");
|
||||
isPrivate = element.GetBoolAttribute("private");
|
||||
previewIconUrl = element.GetStringAttribute("previewIconUrl");
|
||||
iconUrl = element.GetStringAttribute("iconUrl");
|
||||
helpPath = element.GetStringAttribute("helpPath");
|
||||
|
||||
const gd::SerializerElement& eventsFunctionsElement =
|
||||
element.GetChild("eventsFunctions");
|
||||
|
||||
@@ -76,7 +76,7 @@ class GD_CORE_API AbstractEventsBasedEntity {
|
||||
/**
|
||||
* \brief Set the internal name of the behavior or object.
|
||||
*/
|
||||
AbstractEventsBasedEntity& SetName(const gd::String& name_) {
|
||||
virtual AbstractEventsBasedEntity& SetName(const gd::String& name_) {
|
||||
name = name_;
|
||||
return *this;
|
||||
}
|
||||
@@ -89,11 +89,39 @@ class GD_CORE_API AbstractEventsBasedEntity {
|
||||
/**
|
||||
* \brief Set the name of the behavior or object, to be displayed in the editor.
|
||||
*/
|
||||
AbstractEventsBasedEntity& SetFullName(const gd::String& fullName_) {
|
||||
virtual AbstractEventsBasedEntity& SetFullName(const gd::String& fullName_) {
|
||||
fullName = fullName_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const gd::String &GetPreviewIconUrl() const { return previewIconUrl; };
|
||||
virtual AbstractEventsBasedEntity &
|
||||
SetPreviewIconUrl(const gd::String &previewIconUrl_) {
|
||||
previewIconUrl = previewIconUrl_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const gd::String &GetIconUrl() const { return iconUrl; };
|
||||
virtual AbstractEventsBasedEntity &SetIconUrl(const gd::String &iconUrl_) {
|
||||
iconUrl = iconUrl_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get the help path relative to the GDevelop
|
||||
* documentation root.
|
||||
*/
|
||||
const gd::String &GetHelpPath() const { return helpPath; };
|
||||
|
||||
/**
|
||||
* \brief Set the help path relative to the GDevelop
|
||||
* documentation root.
|
||||
*/
|
||||
virtual AbstractEventsBasedEntity &SetHelpPath(const gd::String &helpPath_) {
|
||||
helpPath = helpPath_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Return a reference to the functions of the events based behavior or object.
|
||||
*/
|
||||
@@ -166,6 +194,10 @@ class GD_CORE_API AbstractEventsBasedEntity {
|
||||
gd::PropertiesContainer propertyDescriptors;
|
||||
gd::String extensionName;
|
||||
bool isPrivate = false;
|
||||
gd::String previewIconUrl;
|
||||
gd::String iconUrl;
|
||||
gd::String helpPath; ///< The relative path to the help in
|
||||
///< the documentation (or an absolute URL).
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
||||
@@ -47,7 +47,7 @@ class GD_CORE_API EventsBasedBehavior: public AbstractEventsBasedEntity {
|
||||
/**
|
||||
* \brief Set the internal name of the behavior.
|
||||
*/
|
||||
EventsBasedBehavior& SetName(const gd::String& name_) {
|
||||
EventsBasedBehavior& SetName(const gd::String& name_) override {
|
||||
AbstractEventsBasedEntity::SetName(name_);
|
||||
return *this;
|
||||
}
|
||||
@@ -55,11 +55,31 @@ class GD_CORE_API EventsBasedBehavior: public AbstractEventsBasedEntity {
|
||||
/**
|
||||
* \brief Set the name of the behavior, to be displayed in the editor.
|
||||
*/
|
||||
EventsBasedBehavior& SetFullName(const gd::String& fullName_) {
|
||||
EventsBasedBehavior& SetFullName(const gd::String& fullName_) override {
|
||||
AbstractEventsBasedEntity::SetFullName(fullName_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
EventsBasedBehavior &
|
||||
SetPreviewIconUrl(const gd::String &previewIconUrl_) override {
|
||||
AbstractEventsBasedEntity::SetPreviewIconUrl(previewIconUrl_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
EventsBasedBehavior &SetIconUrl(const gd::String &iconUrl_) override {
|
||||
AbstractEventsBasedEntity::SetIconUrl(iconUrl_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set the help path relative to the GDevelop
|
||||
* documentation root.
|
||||
*/
|
||||
EventsBasedBehavior &SetHelpPath(const gd::String &helpPath_) override {
|
||||
AbstractEventsBasedEntity::SetHelpPath(helpPath_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get the object type the behavior should be used with.
|
||||
*/
|
||||
|
||||
@@ -61,7 +61,7 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
|
||||
/**
|
||||
* \brief Set the internal name of the object.
|
||||
*/
|
||||
EventsBasedObject& SetName(const gd::String& name_) {
|
||||
EventsBasedObject& SetName(const gd::String& name_) override {
|
||||
AbstractEventsBasedEntity::SetName(name_);
|
||||
return *this;
|
||||
}
|
||||
@@ -69,11 +69,31 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
|
||||
/**
|
||||
* \brief Set the name of the object, to be displayed in the editor.
|
||||
*/
|
||||
EventsBasedObject& SetFullName(const gd::String& fullName_) {
|
||||
EventsBasedObject &SetFullName(const gd::String &fullName_) override {
|
||||
AbstractEventsBasedEntity::SetFullName(fullName_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
EventsBasedObject &
|
||||
SetPreviewIconUrl(const gd::String &previewIconUrl_) override {
|
||||
AbstractEventsBasedEntity::SetPreviewIconUrl(previewIconUrl_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
EventsBasedObject &SetIconUrl(const gd::String &iconUrl_) override {
|
||||
AbstractEventsBasedEntity::SetIconUrl(iconUrl_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set the help path relative to the GDevelop
|
||||
* documentation root.
|
||||
*/
|
||||
EventsBasedObject &SetHelpPath(const gd::String &helpPath_) override {
|
||||
AbstractEventsBasedEntity::SetHelpPath(helpPath_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get the tag from which to choose assets from when creating a new
|
||||
* object.
|
||||
|
||||
@@ -79,10 +79,15 @@ const gd::String MetadataDeclarationHelper::defaultExtensionIconPath =
|
||||
"res/functions/extension_black.svg";
|
||||
|
||||
const gd::String &MetadataDeclarationHelper::GetExtensionIconUrl(
|
||||
gd::PlatformExtension &extension) {
|
||||
auto &test = extension.GetIconUrl() ||
|
||||
MetadataDeclarationHelper::defaultExtensionIconPath;
|
||||
return test;
|
||||
const gd::PlatformExtension &extension) {
|
||||
return extension.GetIconUrl() ||
|
||||
MetadataDeclarationHelper::defaultExtensionIconPath;
|
||||
}
|
||||
|
||||
const gd::String &MetadataDeclarationHelper::GetEntityIconUrl(
|
||||
const gd::PlatformExtension &extension,
|
||||
const gd::AbstractEventsBasedEntity &eventsBasedEntity) {
|
||||
return eventsBasedEntity.GetIconUrl() || GetExtensionIconUrl(extension);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,7 +107,7 @@ gd::BehaviorMetadata &MetadataDeclarationHelper::DeclareBehaviorMetadata(
|
||||
eventsBasedBehavior.GetName(),
|
||||
GetTranslation(eventsBasedBehavior.GetDescription()),
|
||||
"",
|
||||
GetExtensionIconUrl(extension), "",
|
||||
eventsBasedBehavior.GetIconUrl() || GetExtensionIconUrl(extension), "",
|
||||
std::make_shared<gd::CustomBehavior>(
|
||||
eventsBasedBehavior.GetName(), project,
|
||||
PlatformExtension::GetBehaviorFullType(
|
||||
@@ -129,11 +134,12 @@ gd::ObjectMetadata &MetadataDeclarationHelper::DeclareObjectMetadata(
|
||||
const gd::EventsBasedObject &eventsBasedObject) {
|
||||
auto &objectMetadata =
|
||||
extension
|
||||
.AddEventsBasedObject(eventsBasedObject.GetName(),
|
||||
GetTranslation(eventsBasedObject.GetFullName()) ||
|
||||
eventsBasedObject.GetName(),
|
||||
GetTranslation(eventsBasedObject.GetDescription()),
|
||||
GetExtensionIconUrl(extension))
|
||||
.AddEventsBasedObject(
|
||||
eventsBasedObject.GetName(),
|
||||
GetTranslation(eventsBasedObject.GetFullName()) ||
|
||||
eventsBasedObject.GetName(),
|
||||
GetTranslation(eventsBasedObject.GetDescription()),
|
||||
eventsBasedObject.GetIconUrl() || GetExtensionIconUrl(extension))
|
||||
// TODO Change the metadata model to only set a category on the
|
||||
// extension. If an extension has behavior or object across
|
||||
// several categories, we can assume it"s not scoped correctly.
|
||||
@@ -523,6 +529,7 @@ MetadataDeclarationHelper::DeclareExpressionMetadata(
|
||||
gd::PlatformExtension &extension,
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::EventsFunction &eventsFunction) {
|
||||
auto &iconURL = GetExtensionIconUrl(extension);
|
||||
auto functionType = eventsFunction.GetFunctionType();
|
||||
auto &freeEventsFunctions = eventsFunctionsExtension.GetEventsFunctions();
|
||||
if (functionType == gd::EventsFunction::ExpressionAndCondition) {
|
||||
@@ -535,7 +542,7 @@ MetadataDeclarationHelper::DeclareExpressionMetadata(
|
||||
GetFullName(eventsFunction),
|
||||
// An operator and an operand are inserted before user parameters.
|
||||
ShiftSentenceParamIndexes(GetFreeFunctionSentence(eventsFunction), 2),
|
||||
eventsFunction.GetGroup(), GetExtensionIconUrl(extension));
|
||||
eventsFunction.GetGroup(), iconURL);
|
||||
// By convention, first parameter is always the Runtime Scene.
|
||||
expressionAndCondition.AddCodeOnlyParameter("currentScene", "");
|
||||
DeclareEventsFunctionParameters(freeEventsFunctions, eventsFunction,
|
||||
@@ -553,13 +560,13 @@ MetadataDeclarationHelper::DeclareExpressionMetadata(
|
||||
MetadataDeclarationHelper::GetFullName(eventsFunction),
|
||||
eventsFunction.GetDescription() ||
|
||||
GetFullName(eventsFunction),
|
||||
eventsFunction.GetGroup(), GetExtensionIconUrl(extension))
|
||||
eventsFunction.GetGroup(), iconURL)
|
||||
: extension.AddStrExpression(
|
||||
eventsFunction.GetName(),
|
||||
MetadataDeclarationHelper::GetFullName(eventsFunction),
|
||||
eventsFunction.GetDescription() ||
|
||||
GetFullName(eventsFunction),
|
||||
eventsFunction.GetGroup(), GetExtensionIconUrl(extension));
|
||||
eventsFunction.GetGroup(), iconURL);
|
||||
// By convention, first parameter is always the Runtime Scene.
|
||||
expression.AddCodeOnlyParameter("currentScene", "");
|
||||
DeclareEventsFunctionParameters(freeEventsFunctions, eventsFunction,
|
||||
@@ -579,6 +586,7 @@ gd::InstructionMetadata &MetadataDeclarationHelper::DeclareInstructionMetadata(
|
||||
gd::PlatformExtension &extension,
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::EventsFunction &eventsFunction) {
|
||||
auto &iconURL = GetExtensionIconUrl(extension);
|
||||
auto functionType = eventsFunction.GetFunctionType();
|
||||
auto &freeEventsFunctions = eventsFunctionsExtension.GetEventsFunctions();
|
||||
if (functionType == gd::EventsFunction::Condition) {
|
||||
@@ -587,7 +595,7 @@ gd::InstructionMetadata &MetadataDeclarationHelper::DeclareInstructionMetadata(
|
||||
GetFullName(eventsFunction),
|
||||
GetTranslation(eventsFunction.GetDescription()) || GetFullName(eventsFunction),
|
||||
GetFreeFunctionSentence(eventsFunction), eventsFunction.GetGroup(),
|
||||
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
|
||||
iconURL, iconURL);
|
||||
// By convention, first parameter is always the Runtime Scene.
|
||||
condition.AddCodeOnlyParameter("currentScene", "");
|
||||
DeclareEventsFunctionParameters(freeEventsFunctions, eventsFunction,
|
||||
@@ -609,8 +617,7 @@ gd::InstructionMetadata &MetadataDeclarationHelper::DeclareInstructionMetadata(
|
||||
(getterFunction.GetDescription() || GetFullName(getterFunction)),
|
||||
// An operator and an operand are inserted before user parameters.
|
||||
ShiftSentenceParamIndexes(GetFreeFunctionSentence(getterFunction), 2),
|
||||
getterFunction.GetGroup(), GetExtensionIconUrl(extension),
|
||||
GetExtensionIconUrl(extension));
|
||||
getterFunction.GetGroup(), iconURL, iconURL);
|
||||
action
|
||||
.SetManipulatedType(gd::ValueTypeMetadata::GetPrimitiveValueType(
|
||||
getterFunction.GetExpressionType().GetName()))
|
||||
@@ -632,8 +639,7 @@ gd::InstructionMetadata &MetadataDeclarationHelper::DeclareInstructionMetadata(
|
||||
_("Change <subject>")
|
||||
.FindAndReplace("<subject>", GetFullName(eventsFunction)),
|
||||
// An operator and an operand are inserted before user parameters.
|
||||
"", "", GetExtensionIconUrl(extension),
|
||||
GetExtensionIconUrl(extension));
|
||||
"", "", iconURL, iconURL);
|
||||
// By convention, first parameter is always the Runtime Scene.
|
||||
action.AddCodeOnlyParameter("currentScene", "");
|
||||
DeclareEventsFunctionParameters(freeEventsFunctions, eventsFunction,
|
||||
@@ -649,7 +655,7 @@ gd::InstructionMetadata &MetadataDeclarationHelper::DeclareInstructionMetadata(
|
||||
GetFullName(eventsFunction),
|
||||
GetTranslation(eventsFunction.GetDescription()) || GetFullName(eventsFunction),
|
||||
GetFreeFunctionSentence(eventsFunction), eventsFunction.GetGroup(),
|
||||
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
|
||||
iconURL, iconURL);
|
||||
// By convention, first parameter is always the Runtime Scene.
|
||||
action.AddCodeOnlyParameter("currentScene", "");
|
||||
DeclareEventsFunctionParameters(freeEventsFunctions, eventsFunction,
|
||||
@@ -733,6 +739,7 @@ MetadataDeclarationHelper::DeclareBehaviorExpressionMetadata(
|
||||
gd::PlatformExtension &extension, gd::BehaviorMetadata &behaviorMetadata,
|
||||
const gd::EventsBasedBehavior &eventsBasedBehavior,
|
||||
const gd::EventsFunction &eventsFunction) {
|
||||
auto &iconURL = GetEntityIconUrl(extension, eventsBasedBehavior);
|
||||
auto functionType = eventsFunction.GetFunctionType();
|
||||
if (functionType == gd::EventsFunction::ExpressionAndCondition) {
|
||||
auto expressionAndCondition = behaviorMetadata.AddExpressionAndCondition(
|
||||
@@ -746,7 +753,7 @@ MetadataDeclarationHelper::DeclareBehaviorExpressionMetadata(
|
||||
ShiftSentenceParamIndexes(GetBehaviorFunctionSentence(eventsFunction, true), 2),
|
||||
eventsFunction.GetGroup() || eventsBasedBehavior.GetFullName() ||
|
||||
eventsBasedBehavior.GetName(),
|
||||
GetExtensionIconUrl(extension));
|
||||
iconURL);
|
||||
DeclareEventsFunctionParameters(eventsBasedBehavior.GetEventsFunctions(),
|
||||
eventsFunction, expressionAndCondition, 2);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
@@ -765,7 +772,7 @@ MetadataDeclarationHelper::DeclareBehaviorExpressionMetadata(
|
||||
eventsFunction.GetGroup() ||
|
||||
eventsBasedBehavior.GetFullName() ||
|
||||
eventsBasedBehavior.GetName(),
|
||||
GetExtensionIconUrl(extension))
|
||||
iconURL)
|
||||
: behaviorMetadata.AddStrExpression(
|
||||
eventsFunction.GetName(),
|
||||
MetadataDeclarationHelper::GetFullName(eventsFunction),
|
||||
@@ -774,7 +781,7 @@ MetadataDeclarationHelper::DeclareBehaviorExpressionMetadata(
|
||||
eventsFunction.GetGroup() ||
|
||||
eventsBasedBehavior.GetFullName() ||
|
||||
eventsBasedBehavior.GetName(),
|
||||
GetExtensionIconUrl(extension));
|
||||
iconURL);
|
||||
DeclareEventsFunctionParameters(eventsBasedBehavior.GetEventsFunctions(),
|
||||
eventsFunction, expression, 2);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
@@ -793,6 +800,7 @@ MetadataDeclarationHelper::DeclareBehaviorInstructionMetadata(
|
||||
gd::PlatformExtension &extension, gd::BehaviorMetadata &behaviorMetadata,
|
||||
const gd::EventsBasedBehavior &eventsBasedBehavior,
|
||||
const gd::EventsFunction &eventsFunction) {
|
||||
auto &iconURL = GetEntityIconUrl(extension, eventsBasedBehavior);
|
||||
auto functionType = eventsFunction.GetFunctionType();
|
||||
if (functionType == gd::EventsFunction::Condition) {
|
||||
// Use the new "scoped" way to declare an instruction, because
|
||||
@@ -805,7 +813,7 @@ MetadataDeclarationHelper::DeclareBehaviorInstructionMetadata(
|
||||
GetBehaviorFunctionSentence(eventsFunction),
|
||||
eventsFunction.GetGroup() || eventsBasedBehavior.GetFullName() ||
|
||||
eventsBasedBehavior.GetName(),
|
||||
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
|
||||
iconURL, iconURL);
|
||||
DeclareEventsFunctionParameters(eventsBasedBehavior.GetEventsFunctions(),
|
||||
eventsFunction, condition, 2);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
@@ -828,7 +836,7 @@ MetadataDeclarationHelper::DeclareBehaviorInstructionMetadata(
|
||||
ShiftSentenceParamIndexes(GetBehaviorFunctionSentence(getterFunction, true), 2),
|
||||
getterFunction.GetGroup() || eventsBasedBehavior.GetFullName() ||
|
||||
eventsBasedBehavior.GetName(),
|
||||
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
|
||||
iconURL, iconURL);
|
||||
action
|
||||
.SetManipulatedType(gd::ValueTypeMetadata::GetPrimitiveValueType(
|
||||
getterFunction.GetExpressionType().GetName()))
|
||||
@@ -848,7 +856,7 @@ MetadataDeclarationHelper::DeclareBehaviorInstructionMetadata(
|
||||
// An operator and an operand are inserted before user parameters.
|
||||
"",
|
||||
eventsBasedBehavior.GetFullName() || eventsBasedBehavior.GetName(),
|
||||
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
|
||||
iconURL, iconURL);
|
||||
|
||||
DeclareEventsFunctionParameters(eventsBasedBehavior.GetEventsFunctions(),
|
||||
eventsFunction, action, 2);
|
||||
@@ -868,7 +876,7 @@ MetadataDeclarationHelper::DeclareBehaviorInstructionMetadata(
|
||||
GetBehaviorFunctionSentence(eventsFunction),
|
||||
eventsFunction.GetGroup() || eventsBasedBehavior.GetFullName() ||
|
||||
eventsBasedBehavior.GetName(),
|
||||
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
|
||||
iconURL, iconURL);
|
||||
|
||||
DeclareEventsFunctionParameters(eventsBasedBehavior.GetEventsFunctions(),
|
||||
eventsFunction, action, 2);
|
||||
@@ -924,6 +932,7 @@ MetadataDeclarationHelper::DeclareObjectExpressionMetadata(
|
||||
gd::PlatformExtension &extension, gd::ObjectMetadata &objectMetadata,
|
||||
const gd::EventsBasedObject &eventsBasedObject,
|
||||
const gd::EventsFunction &eventsFunction) {
|
||||
auto &iconURL = GetEntityIconUrl(extension, eventsBasedObject);
|
||||
auto functionType = eventsFunction.GetFunctionType();
|
||||
if (functionType == gd::EventsFunction::ExpressionAndCondition) {
|
||||
auto expressionAndCondition = objectMetadata.AddExpressionAndCondition(
|
||||
@@ -937,7 +946,7 @@ MetadataDeclarationHelper::DeclareObjectExpressionMetadata(
|
||||
ShiftSentenceParamIndexes(GetObjectFunctionSentence(eventsFunction, true), 2),
|
||||
eventsFunction.GetGroup() || eventsBasedObject.GetFullName() ||
|
||||
eventsBasedObject.GetName(),
|
||||
GetExtensionIconUrl(extension));
|
||||
iconURL);
|
||||
|
||||
DeclareEventsFunctionParameters(eventsBasedObject.GetEventsFunctions(),
|
||||
eventsFunction, expressionAndCondition, 1);
|
||||
@@ -957,7 +966,7 @@ MetadataDeclarationHelper::DeclareObjectExpressionMetadata(
|
||||
eventsFunction.GetGroup() ||
|
||||
eventsBasedObject.GetFullName() ||
|
||||
eventsBasedObject.GetName(),
|
||||
GetExtensionIconUrl(extension))
|
||||
iconURL)
|
||||
: objectMetadata.AddStrExpression(
|
||||
eventsFunction.GetName(),
|
||||
MetadataDeclarationHelper::GetFullName(eventsFunction),
|
||||
@@ -966,7 +975,7 @@ MetadataDeclarationHelper::DeclareObjectExpressionMetadata(
|
||||
eventsFunction.GetGroup() ||
|
||||
eventsBasedObject.GetFullName() ||
|
||||
eventsBasedObject.GetName(),
|
||||
GetExtensionIconUrl(extension));
|
||||
iconURL);
|
||||
|
||||
DeclareEventsFunctionParameters(eventsBasedObject.GetEventsFunctions(),
|
||||
eventsFunction, expression, 1);
|
||||
@@ -986,6 +995,7 @@ MetadataDeclarationHelper::DeclareObjectInstructionMetadata(
|
||||
gd::PlatformExtension &extension, gd::ObjectMetadata &objectMetadata,
|
||||
const gd::EventsBasedObject &eventsBasedObject,
|
||||
const gd::EventsFunction &eventsFunction) {
|
||||
auto &iconURL = GetEntityIconUrl(extension, eventsBasedObject);
|
||||
auto functionType = eventsFunction.GetFunctionType();
|
||||
if (functionType == gd::EventsFunction::Condition) {
|
||||
// Use the new "scoped" way to declare an instruction, because
|
||||
@@ -998,7 +1008,7 @@ MetadataDeclarationHelper::DeclareObjectInstructionMetadata(
|
||||
GetObjectFunctionSentence(eventsFunction),
|
||||
eventsFunction.GetGroup() || eventsBasedObject.GetFullName() ||
|
||||
eventsBasedObject.GetName(),
|
||||
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
|
||||
iconURL, iconURL);
|
||||
|
||||
DeclareEventsFunctionParameters(eventsBasedObject.GetEventsFunctions(),
|
||||
eventsFunction, condition, 1);
|
||||
@@ -1021,7 +1031,7 @@ MetadataDeclarationHelper::DeclareObjectInstructionMetadata(
|
||||
ShiftSentenceParamIndexes(GetObjectFunctionSentence(getterFunction, true), 2),
|
||||
getterFunction.GetGroup() || eventsBasedObject.GetFullName() ||
|
||||
eventsBasedObject.GetName(),
|
||||
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
|
||||
iconURL, iconURL);
|
||||
action
|
||||
.SetManipulatedType(gd::ValueTypeMetadata::GetPrimitiveValueType(
|
||||
getterFunction.GetExpressionType().GetName()))
|
||||
@@ -1040,7 +1050,7 @@ MetadataDeclarationHelper::DeclareObjectInstructionMetadata(
|
||||
.FindAndReplace("<subject>", GetFullName(eventsFunction)),
|
||||
// An operator and an operand are inserted before user parameters.
|
||||
"", eventsBasedObject.GetFullName() || eventsBasedObject.GetName(),
|
||||
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
|
||||
iconURL, iconURL);
|
||||
|
||||
DeclareEventsFunctionParameters(eventsBasedObject.GetEventsFunctions(),
|
||||
eventsFunction, action, 1);
|
||||
@@ -1060,7 +1070,7 @@ MetadataDeclarationHelper::DeclareObjectInstructionMetadata(
|
||||
GetObjectFunctionSentence(eventsFunction),
|
||||
eventsFunction.GetGroup() || eventsBasedObject.GetFullName() ||
|
||||
eventsBasedObject.GetName(),
|
||||
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
|
||||
iconURL, iconURL);
|
||||
|
||||
DeclareEventsFunctionParameters(eventsBasedObject.GetEventsFunctions(),
|
||||
eventsFunction, action, 1);
|
||||
@@ -1105,6 +1115,7 @@ void MetadataDeclarationHelper::DeclarePropertyInstructionAndExpression(
|
||||
gd::AbstractFunctionMetadata &instructionOrExpression)>
|
||||
addObjectAndBehaviorParameters,
|
||||
bool isSharedProperty) {
|
||||
auto &iconURL = GetEntityIconUrl(extension, eventsBasedEntity);
|
||||
auto &propertyType = property.GetType();
|
||||
|
||||
auto group = (eventsBasedEntity.GetFullName() || eventsBasedEntity.GetName())
|
||||
@@ -1118,7 +1129,7 @@ void MetadataDeclarationHelper::DeclarePropertyInstructionAndExpression(
|
||||
_("Property <property_name> of _PARAM0_ is true")
|
||||
.FindAndReplace("<property_name>", property.GetName()),
|
||||
group,
|
||||
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
|
||||
iconURL, iconURL);
|
||||
addObjectAndBehaviorParameters(conditionMetadata);
|
||||
conditionMetadata.SetFunctionName(getterName);
|
||||
if (!isSharedProperty) {
|
||||
@@ -1136,7 +1147,7 @@ void MetadataDeclarationHelper::DeclarePropertyInstructionAndExpression(
|
||||
"_PARAM" + gd::String::From(valueParameterIndex) +
|
||||
"_"),
|
||||
group,
|
||||
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
|
||||
iconURL, iconURL);
|
||||
addObjectAndBehaviorParameters(setterActionMetadata);
|
||||
setterActionMetadata
|
||||
.AddParameter("yesorno", _("New value to set"), "", false)
|
||||
@@ -1154,7 +1165,7 @@ void MetadataDeclarationHelper::DeclarePropertyInstructionAndExpression(
|
||||
_("Toggle property <property_name> of _PARAM0_")
|
||||
.FindAndReplace("<property_name>", property.GetName()),
|
||||
group,
|
||||
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
|
||||
iconURL, iconURL);
|
||||
addObjectAndBehaviorParameters(toggleActionMetadata);
|
||||
toggleActionMetadata.SetFunctionName(toggleFunctionName);
|
||||
if (!isSharedProperty) {
|
||||
@@ -1175,7 +1186,7 @@ void MetadataDeclarationHelper::DeclarePropertyInstructionAndExpression(
|
||||
_("the property value for <property_name>")
|
||||
.FindAndReplace("<property_name>", property.GetName()),
|
||||
group,
|
||||
GetExtensionIconUrl(extension));
|
||||
iconURL);
|
||||
addObjectAndBehaviorParameters(propertyInstructionMetadata);
|
||||
propertyInstructionMetadata
|
||||
.UseStandardParameters(
|
||||
|
||||
@@ -124,7 +124,11 @@ public:
|
||||
|
||||
private:
|
||||
static const gd::String &
|
||||
GetExtensionIconUrl(gd::PlatformExtension &extension);
|
||||
GetExtensionIconUrl(const gd::PlatformExtension &extension);
|
||||
|
||||
static const gd::String &
|
||||
GetEntityIconUrl(const gd::PlatformExtension &extension,
|
||||
const gd::AbstractEventsBasedEntity &eventsBasedEntity);
|
||||
|
||||
/**
|
||||
* Declare the dependencies of an extension from an events based extension.
|
||||
|
||||
@@ -3584,14 +3584,13 @@ namespace gdjs {
|
||||
|
||||
if (this._currentScene) {
|
||||
this._currentScene._updateObjectsForInGameEditor();
|
||||
for (let i = 0; i < gdjs.callbacksInGameEditorPostStep.length; ++i) {
|
||||
gdjs.callbacksInGameEditorPostStep[i](this);
|
||||
}
|
||||
this._currentScene.render();
|
||||
}
|
||||
|
||||
this._isFirstFrame = false;
|
||||
|
||||
for (let i = 0; i < gdjs.callbacksInGameEditorPostStep.length; ++i) {
|
||||
gdjs.callbacksInGameEditorPostStep[i](this);
|
||||
}
|
||||
}
|
||||
|
||||
private _getEditorCamera(): EditorCamera {
|
||||
|
||||
@@ -3372,6 +3372,9 @@ interface AbstractEventsBasedEntity {
|
||||
[Const, Ref] DOMString GetFullName();
|
||||
[Const, Ref] DOMString GetDescription();
|
||||
boolean IsPrivate();
|
||||
[Const, Ref] DOMString GetPreviewIconUrl();
|
||||
[Const, Ref] DOMString GetIconUrl();
|
||||
[Const, Ref] DOMString GetHelpPath();
|
||||
|
||||
void SerializeTo([Ref] SerializerElement element);
|
||||
void UnserializeFrom([Ref] Project project, [Const, Ref] SerializerElement element);
|
||||
@@ -3384,6 +3387,9 @@ interface EventsBasedBehavior {
|
||||
[Ref] EventsBasedBehavior SetFullName([Const] DOMString fullName);
|
||||
[Ref] EventsBasedBehavior SetDescription([Const] DOMString description);
|
||||
[Ref] EventsBasedBehavior SetPrivate(boolean isPrivate);
|
||||
[Ref] EventsBasedBehavior SetPreviewIconUrl([Const] DOMString previewIconUrl);
|
||||
[Ref] EventsBasedBehavior SetIconUrl([Const] DOMString iconUrl);
|
||||
[Ref] EventsBasedBehavior SetHelpPath([Const] DOMString helpPath);
|
||||
|
||||
[Ref] EventsBasedBehavior SetObjectType([Const] DOMString fullName);
|
||||
[Const, Ref] DOMString GetObjectType();
|
||||
@@ -3425,6 +3431,9 @@ interface EventsBasedObject {
|
||||
[Ref] EventsBasedObject SetFullName([Const] DOMString fullName);
|
||||
[Ref] EventsBasedObject SetDescription([Const] DOMString description);
|
||||
[Ref] EventsBasedObject SetPrivate(boolean isPrivate);
|
||||
[Ref] EventsBasedObject SetPreviewIconUrl([Const] DOMString previewIconUrl);
|
||||
[Ref] EventsBasedObject SetIconUrl([Const] DOMString iconUrl);
|
||||
[Ref] EventsBasedObject SetHelpPath([Const] DOMString helpPath);
|
||||
|
||||
|
||||
[Ref] EventsBasedObject SetDefaultName([Const] DOMString defaultName);
|
||||
|
||||
Vendored
+9
@@ -2441,6 +2441,9 @@ export class AbstractEventsBasedEntity extends EmscriptenObject {
|
||||
getFullName(): string;
|
||||
getDescription(): string;
|
||||
isPrivate(): boolean;
|
||||
getPreviewIconUrl(): string;
|
||||
getIconUrl(): string;
|
||||
getHelpPath(): string;
|
||||
serializeTo(element: SerializerElement): void;
|
||||
unserializeFrom(project: Project, element: SerializerElement): void;
|
||||
}
|
||||
@@ -2451,6 +2454,9 @@ export class EventsBasedBehavior extends AbstractEventsBasedEntity {
|
||||
setFullName(fullName: string): EventsBasedBehavior;
|
||||
setDescription(description: string): EventsBasedBehavior;
|
||||
setPrivate(isPrivate: boolean): EventsBasedBehavior;
|
||||
setPreviewIconUrl(previewIconUrl: string): EventsBasedBehavior;
|
||||
setIconUrl(iconUrl: string): EventsBasedBehavior;
|
||||
setHelpPath(helpPath: string): EventsBasedBehavior;
|
||||
setObjectType(fullName: string): EventsBasedBehavior;
|
||||
getObjectType(): string;
|
||||
setQuickCustomizationVisibility(visibility: QuickCustomization_Visibility): EventsBasedBehavior;
|
||||
@@ -2486,6 +2492,9 @@ export class EventsBasedObject extends AbstractEventsBasedEntity {
|
||||
setFullName(fullName: string): EventsBasedObject;
|
||||
setDescription(description: string): EventsBasedObject;
|
||||
setPrivate(isPrivate: boolean): EventsBasedObject;
|
||||
setPreviewIconUrl(previewIconUrl: string): EventsBasedObject;
|
||||
setIconUrl(iconUrl: string): EventsBasedObject;
|
||||
setHelpPath(helpPath: string): EventsBasedObject;
|
||||
setDefaultName(defaultName: string): EventsBasedObject;
|
||||
setAssetStoreTag(assetStoreTag: string): EventsBasedObject;
|
||||
getDefaultName(): string;
|
||||
|
||||
@@ -6,6 +6,9 @@ declare class gdAbstractEventsBasedEntity {
|
||||
getFullName(): string;
|
||||
getDescription(): string;
|
||||
isPrivate(): boolean;
|
||||
getPreviewIconUrl(): string;
|
||||
getIconUrl(): string;
|
||||
getHelpPath(): string;
|
||||
serializeTo(element: gdSerializerElement): void;
|
||||
unserializeFrom(project: gdProject, element: gdSerializerElement): void;
|
||||
delete(): void;
|
||||
|
||||
@@ -5,6 +5,9 @@ declare class gdEventsBasedBehavior extends gdAbstractEventsBasedEntity {
|
||||
setFullName(fullName: string): gdEventsBasedBehavior;
|
||||
setDescription(description: string): gdEventsBasedBehavior;
|
||||
setPrivate(isPrivate: boolean): gdEventsBasedBehavior;
|
||||
setPreviewIconUrl(previewIconUrl: string): gdEventsBasedBehavior;
|
||||
setIconUrl(iconUrl: string): gdEventsBasedBehavior;
|
||||
setHelpPath(helpPath: string): gdEventsBasedBehavior;
|
||||
setObjectType(fullName: string): gdEventsBasedBehavior;
|
||||
getObjectType(): string;
|
||||
setQuickCustomizationVisibility(visibility: QuickCustomization_Visibility): gdEventsBasedBehavior;
|
||||
|
||||
@@ -5,6 +5,9 @@ declare class gdEventsBasedObject extends gdAbstractEventsBasedEntity {
|
||||
setFullName(fullName: string): gdEventsBasedObject;
|
||||
setDescription(description: string): gdEventsBasedObject;
|
||||
setPrivate(isPrivate: boolean): gdEventsBasedObject;
|
||||
setPreviewIconUrl(previewIconUrl: string): gdEventsBasedObject;
|
||||
setIconUrl(iconUrl: string): gdEventsBasedObject;
|
||||
setHelpPath(helpPath: string): gdEventsBasedObject;
|
||||
setDefaultName(defaultName: string): gdEventsBasedObject;
|
||||
setAssetStoreTag(assetStoreTag: string): gdEventsBasedObject;
|
||||
getDefaultName(): string;
|
||||
|
||||
+19
@@ -20,6 +20,7 @@ import { CompactTextAreaField } from '../../UI/CompactTextAreaField';
|
||||
import CompactSemiControlledTextField from '../../UI/CompactSemiControlledTextField';
|
||||
import CompactSelectField from '../../UI/CompactSelectField';
|
||||
import { CompactToggleField } from '../../UI/CompactToggleField';
|
||||
import { CompactIconField } from '../OptionsEditorDialog/CompactIconField';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
@@ -41,6 +42,7 @@ export default function EventsBasedBehaviorEditor({
|
||||
onConfigurationUpdated,
|
||||
}: Props): React.Node {
|
||||
const forceUpdate = useForceUpdate();
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
|
||||
const onChange = React.useCallback(
|
||||
() => {
|
||||
@@ -113,6 +115,23 @@ export default function EventsBasedBehaviorEditor({
|
||||
onChange();
|
||||
}}
|
||||
/>
|
||||
<CompactIconField
|
||||
onLoadChange={setIsLoading}
|
||||
isLoading={isLoading}
|
||||
getPreviewIconUrl={() => eventsBasedBehavior.getPreviewIconUrl()}
|
||||
setPreviewIconUrl={value => {
|
||||
eventsBasedBehavior.setPreviewIconUrl(value);
|
||||
}}
|
||||
setIconUrl={value => {
|
||||
eventsBasedBehavior.setIconUrl(value);
|
||||
}}
|
||||
disabled={!eventsFunctionsExtension.getIconUrl()}
|
||||
placeholder={
|
||||
eventsFunctionsExtension.getIconUrl()
|
||||
? ''
|
||||
: i18n._(t`Set an icon to the extension first`)
|
||||
}
|
||||
/>
|
||||
<CompactObjectTypeSelector
|
||||
project={project}
|
||||
eventsFunctionsExtension={eventsFunctionsExtension}
|
||||
|
||||
+19
@@ -17,6 +17,7 @@ import CompactPropertiesEditorRowField from '../../CompactPropertiesEditor/Compa
|
||||
import { CompactTextAreaField } from '../../UI/CompactTextAreaField';
|
||||
import CompactSemiControlledTextField from '../../UI/CompactSemiControlledTextField';
|
||||
import { CompactToggleField } from '../../UI/CompactToggleField';
|
||||
import { CompactIconField } from '../OptionsEditorDialog/CompactIconField';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
@@ -40,6 +41,7 @@ export default function EventsBasedObjectEditor({
|
||||
onEventsBasedObjectChildrenEdited,
|
||||
}: Props): React.Node {
|
||||
const forceUpdate = useForceUpdate();
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
|
||||
const onChange = React.useCallback(
|
||||
() => {
|
||||
@@ -98,6 +100,23 @@ export default function EventsBasedObjectEditor({
|
||||
onChange();
|
||||
}}
|
||||
/>
|
||||
<CompactIconField
|
||||
onLoadChange={setIsLoading}
|
||||
isLoading={isLoading}
|
||||
getPreviewIconUrl={() => eventsBasedObject.getPreviewIconUrl()}
|
||||
setPreviewIconUrl={value => {
|
||||
eventsBasedObject.setPreviewIconUrl(value);
|
||||
}}
|
||||
setIconUrl={value => {
|
||||
eventsBasedObject.setIconUrl(value);
|
||||
}}
|
||||
disabled={!eventsFunctionsExtension.getIconUrl()}
|
||||
placeholder={
|
||||
eventsFunctionsExtension.getIconUrl()
|
||||
? ''
|
||||
: i18n._(t`Set an icon to the extension first`)
|
||||
}
|
||||
/>
|
||||
<CompactPropertiesEditorRowField
|
||||
label={i18n._(t`Default name for created objects`)}
|
||||
field={
|
||||
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
//@flow
|
||||
import React from 'react';
|
||||
import axios from 'axios';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { t } from '@lingui/macro';
|
||||
import { I18n } from '@lingui/react';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
|
||||
import FlatButton from '../../UI/FlatButton';
|
||||
import { ResourceStore } from '../../AssetStore/ResourceStore';
|
||||
import Dialog, { DialogPrimaryButton } from '../../UI/Dialog';
|
||||
import { useIsMounted } from '../../Utils/UseIsMounted';
|
||||
import { showErrorBox } from '../../UI/Messages/MessageBox';
|
||||
import { ResourceStoreContext } from '../../AssetStore/ResourceStore/ResourceStoreContext';
|
||||
import CompactPropertiesEditorRowField from '../../CompactPropertiesEditor/CompactPropertiesEditorRowField';
|
||||
import CompactSemiControlledTextField from '../../UI/CompactSemiControlledTextField';
|
||||
import IconButton from '../../UI/IconButton';
|
||||
import EditIcon from '../../UI/CustomSvgIcons/Edit';
|
||||
import { LineStackLayout } from '../../UI/Layout';
|
||||
|
||||
const styles = {
|
||||
icon: {
|
||||
fontSize: 18,
|
||||
},
|
||||
};
|
||||
|
||||
const downloadSvgAsBase64 = async (url: string): Promise<string> => {
|
||||
try {
|
||||
// $FlowFixMe[underconstrained-implicit-instantiation]
|
||||
const response = await axios.get(url, { responseType: 'arraybuffer' });
|
||||
|
||||
const image = btoa(
|
||||
new Uint8Array(response.data).reduce(
|
||||
(data, byte) => data + String.fromCharCode(byte),
|
||||
''
|
||||
)
|
||||
);
|
||||
if (image.length > 100 * 1024) {
|
||||
throw new Error(
|
||||
`Icon is too big (size after base64 conversion: ${image.length})`
|
||||
);
|
||||
}
|
||||
|
||||
const contentType = response.headers
|
||||
? response.headers['content-type'].toLowerCase()
|
||||
: '';
|
||||
if (contentType !== 'image/svg+xml')
|
||||
throw new Error(
|
||||
`Wrong content type. Got: "${contentType}", expected "image/svg+xml"`
|
||||
);
|
||||
|
||||
return `data:${contentType};base64,${image}`;
|
||||
} catch (err) {
|
||||
console.error('Unable to import the icon.', err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {|
|
||||
onLoadChange: (isLoading: boolean) => void,
|
||||
isLoading: boolean,
|
||||
getPreviewIconUrl: () => string,
|
||||
setPreviewIconUrl: string => void,
|
||||
setIconUrl: string => void,
|
||||
placeholder?: string,
|
||||
disabled?: boolean,
|
||||
|};
|
||||
|
||||
export const CompactIconField = ({
|
||||
onLoadChange,
|
||||
isLoading,
|
||||
getPreviewIconUrl,
|
||||
setPreviewIconUrl,
|
||||
setIconUrl,
|
||||
placeholder,
|
||||
disabled,
|
||||
}: Props): React.Node => {
|
||||
const [resourceStoreOpen, setResourceStoreOpen] = React.useState(false);
|
||||
const isMounted = useIsMounted();
|
||||
const { searchResults } = React.useContext(ResourceStoreContext);
|
||||
const [
|
||||
selectedSvgResourceIndex,
|
||||
setSelectedSvgResourceIndex,
|
||||
] = React.useState<?number>(null);
|
||||
|
||||
const onUseIcon = React.useCallback(
|
||||
(i18n: I18nType) => {
|
||||
if (typeof selectedSvgResourceIndex !== 'number') return;
|
||||
const selectedSvgResource = searchResults
|
||||
? searchResults[selectedSvgResourceIndex]
|
||||
: null;
|
||||
if (!selectedSvgResource) return;
|
||||
setResourceStoreOpen(false);
|
||||
onLoadChange(true);
|
||||
downloadSvgAsBase64(selectedSvgResource.url)
|
||||
.then(
|
||||
base64Svg => {
|
||||
if (!isMounted.current) return;
|
||||
|
||||
setPreviewIconUrl(selectedSvgResource.url);
|
||||
setIconUrl(base64Svg);
|
||||
},
|
||||
rawError => {
|
||||
if (!isMounted.current) return;
|
||||
|
||||
showErrorBox({
|
||||
message: i18n._(
|
||||
t`Unable to download the icon. Verify your internet connection or try again later.`
|
||||
),
|
||||
rawError,
|
||||
errorId: 'icon-download-error',
|
||||
});
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
if (!isMounted.current) return;
|
||||
|
||||
onLoadChange(false);
|
||||
});
|
||||
},
|
||||
[
|
||||
selectedSvgResourceIndex,
|
||||
searchResults,
|
||||
onLoadChange,
|
||||
isMounted,
|
||||
setPreviewIconUrl,
|
||||
setIconUrl,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<React.Fragment>
|
||||
<LineStackLayout noMargin expand>
|
||||
<CompactPropertiesEditorRowField
|
||||
label={i18n._(t`Icon URL`)}
|
||||
field={
|
||||
<CompactSemiControlledTextField
|
||||
value={getPreviewIconUrl()}
|
||||
onChange={text => {
|
||||
setPreviewIconUrl(text);
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
disabled
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setResourceStoreOpen(true);
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
<EditIcon style={styles.icon} />
|
||||
</IconButton>
|
||||
</LineStackLayout>
|
||||
{resourceStoreOpen && (
|
||||
<Dialog
|
||||
title={<Trans>Choose an icon</Trans>}
|
||||
actions={[
|
||||
<FlatButton
|
||||
key="cancel"
|
||||
label={<Trans>Cancel</Trans>}
|
||||
primary={false}
|
||||
onClick={() => {
|
||||
setResourceStoreOpen(false);
|
||||
}}
|
||||
/>,
|
||||
<DialogPrimaryButton
|
||||
primary
|
||||
key="apply"
|
||||
label={<Trans>Use icon</Trans>}
|
||||
onClick={() => onUseIcon(i18n)}
|
||||
/>,
|
||||
]}
|
||||
flexColumnBody
|
||||
fullHeight
|
||||
open
|
||||
onRequestClose={() => {
|
||||
setResourceStoreOpen(false);
|
||||
}}
|
||||
>
|
||||
<ResourceStore
|
||||
selectedResourceIndex={selectedSvgResourceIndex}
|
||||
onSelectResource={setSelectedSvgResourceIndex}
|
||||
resourceKind={'svg'}
|
||||
/>
|
||||
</Dialog>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</I18n>
|
||||
);
|
||||
};
|
||||
+128
-242
@@ -1,64 +1,27 @@
|
||||
//@flow
|
||||
import React from 'react';
|
||||
import axios from 'axios';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { t } from '@lingui/macro';
|
||||
import { I18n } from '@lingui/react';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
|
||||
import TextField from '../../UI/TextField';
|
||||
import { ColumnStackLayout, TextFieldWithButtonLayout } from '../../UI/Layout';
|
||||
import SemiControlledTextField from '../../UI/SemiControlledTextField';
|
||||
import RaisedButton from '../../UI/RaisedButton';
|
||||
import FlatButton from '../../UI/FlatButton';
|
||||
import { ResourceStore } from '../../AssetStore/ResourceStore';
|
||||
import Dialog, { DialogPrimaryButton } from '../../UI/Dialog';
|
||||
|
||||
import { ColumnStackLayout } from '../../UI/Layout';
|
||||
import useForceUpdate from '../../Utils/UseForceUpdate';
|
||||
import {
|
||||
getHelpLink,
|
||||
isRelativePathToDocumentationRoot,
|
||||
isDocumentationAbsoluteUrl,
|
||||
} from '../../Utils/HelpLink';
|
||||
import { useIsMounted } from '../../Utils/UseIsMounted';
|
||||
import { showErrorBox } from '../../UI/Messages/MessageBox';
|
||||
import { UsersAutocomplete } from '../../Profile/UsersAutocomplete';
|
||||
import SelectField from '../../UI/SelectField';
|
||||
import SelectOption from '../../UI/SelectOption';
|
||||
import SemiControlledAutoComplete from '../../UI/SemiControlledAutoComplete';
|
||||
import { ResourceStoreContext } from '../../AssetStore/ResourceStore/ResourceStoreContext';
|
||||
|
||||
const downloadSvgAsBase64 = async (url: string): Promise<string> => {
|
||||
try {
|
||||
// $FlowFixMe[underconstrained-implicit-instantiation]
|
||||
const response = await axios.get(url, { responseType: 'arraybuffer' });
|
||||
|
||||
const image = btoa(
|
||||
new Uint8Array(response.data).reduce(
|
||||
(data, byte) => data + String.fromCharCode(byte),
|
||||
''
|
||||
)
|
||||
);
|
||||
if (image.length > 100 * 1024) {
|
||||
throw new Error(
|
||||
`Icon is too big (size after base64 conversion: ${image.length})`
|
||||
);
|
||||
}
|
||||
|
||||
const contentType = response.headers
|
||||
? response.headers['content-type'].toLowerCase()
|
||||
: '';
|
||||
if (contentType !== 'image/svg+xml')
|
||||
throw new Error(
|
||||
`Wrong content type. Got: "${contentType}", expected "image/svg+xml"`
|
||||
);
|
||||
|
||||
return `data:${contentType};base64,${image}`;
|
||||
} catch (err) {
|
||||
console.error('Unable to import the icon.', err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
import { CompactIconField } from './CompactIconField';
|
||||
import CompactPropertiesEditorRowField from '../../CompactPropertiesEditor/CompactPropertiesEditorRowField';
|
||||
import CompactTextField from '../../UI/CompactTextField';
|
||||
import { CompactTextAreaField } from '../../UI/CompactTextAreaField';
|
||||
import CompactSemiControlledTextField from '../../UI/CompactSemiControlledTextField';
|
||||
import CompactSelectField from '../../UI/CompactSelectField';
|
||||
import { MarkdownText } from '../../UI/MarkdownText';
|
||||
|
||||
type HelpPathTextFieldProps = {|
|
||||
i18n: I18nType,
|
||||
@@ -80,26 +43,34 @@ const HelpPathTextField = ({
|
||||
)
|
||||
: i18n._(t`This is link to a webpage.`)) +
|
||||
` [${i18n._(t`Click here to test the link.`)}](${getHelpLink(helpPath)})`
|
||||
: i18n._(
|
||||
t`This can either be a URL to a web page, or a path starting with a slash that will be opened in the GDevelop wiki. Leave empty if there is no help page, although it's recommended you eventually write one if you distribute the extension.`
|
||||
);
|
||||
: '';
|
||||
const hasError = !!helpPath && !isAbsoluteUrl && !isRelativePath;
|
||||
|
||||
return (
|
||||
<TextField
|
||||
floatingLabelText={<Trans>Help page URL</Trans>}
|
||||
value={helpPath}
|
||||
onChange={(e, text) => {
|
||||
onChangeHelpPath(text);
|
||||
}}
|
||||
errorText={
|
||||
!!helpPath && !isAbsoluteUrl && !isRelativePath ? (
|
||||
<Trans>
|
||||
This is not a URL starting with "http://" or "https://".
|
||||
</Trans>
|
||||
) : null
|
||||
<CompactPropertiesEditorRowField
|
||||
label={i18n._(t`Help page URL`)}
|
||||
markdownDescription={i18n._(
|
||||
t`This can either be a URL to a web page, or a path starting with a slash that will be opened in the GDevelop wiki. Leave empty if there is no help page, although it's recommended you eventually write one if you distribute the extension.`
|
||||
)}
|
||||
field={
|
||||
<ColumnStackLayout noMargin expand>
|
||||
<CompactSemiControlledTextField
|
||||
value={helpPath}
|
||||
onChange={text => {
|
||||
onChangeHelpPath(text);
|
||||
}}
|
||||
errored={hasError}
|
||||
errorText={
|
||||
hasError ? (
|
||||
<Trans>
|
||||
This is not a URL starting with "http://" or "https://".
|
||||
</Trans>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
{helperText && !hasError && <MarkdownText source={helperText} />}
|
||||
</ColumnStackLayout>
|
||||
}
|
||||
helperMarkdownText={helperText}
|
||||
fullWidth
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -116,136 +87,126 @@ export const ExtensionOptionsEditor = ({
|
||||
isLoading,
|
||||
}: Props): React.Node => {
|
||||
const forceUpdate = useForceUpdate();
|
||||
const [resourceStoreOpen, setResourceStoreOpen] = React.useState(false);
|
||||
const isMounted = useIsMounted();
|
||||
const { searchResults } = React.useContext(ResourceStoreContext);
|
||||
const [
|
||||
selectedSvgResourceIndex,
|
||||
setSelectedSvgResourceIndex,
|
||||
] = React.useState<?number>(null);
|
||||
|
||||
const onUseIcon = React.useCallback(
|
||||
(i18n: I18nType) => {
|
||||
if (typeof selectedSvgResourceIndex !== 'number') return;
|
||||
const selectedSvgResource = searchResults
|
||||
? searchResults[selectedSvgResourceIndex]
|
||||
: null;
|
||||
if (!selectedSvgResource) return;
|
||||
setResourceStoreOpen(false);
|
||||
onLoadChange(true);
|
||||
downloadSvgAsBase64(selectedSvgResource.url)
|
||||
.then(
|
||||
base64Svg => {
|
||||
if (!isMounted.current) return;
|
||||
|
||||
eventsFunctionsExtension.setPreviewIconUrl(selectedSvgResource.url);
|
||||
eventsFunctionsExtension.setIconUrl(base64Svg);
|
||||
},
|
||||
rawError => {
|
||||
if (!isMounted.current) return;
|
||||
|
||||
showErrorBox({
|
||||
message: i18n._(
|
||||
t`Unable to download the icon. Verify your internet connection or try again later.`
|
||||
),
|
||||
rawError,
|
||||
errorId: 'icon-download-error',
|
||||
});
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
if (!isMounted.current) return;
|
||||
|
||||
onLoadChange(false);
|
||||
});
|
||||
},
|
||||
[
|
||||
selectedSvgResourceIndex,
|
||||
searchResults,
|
||||
eventsFunctionsExtension,
|
||||
onLoadChange,
|
||||
isMounted,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<ColumnStackLayout noMargin expand>
|
||||
<TextField
|
||||
floatingLabelText={<Trans>Name</Trans>}
|
||||
value={eventsFunctionsExtension.getName()}
|
||||
disabled
|
||||
fullWidth
|
||||
/>
|
||||
<TextFieldWithButtonLayout
|
||||
renderButton={style => (
|
||||
<RaisedButton
|
||||
onClick={() => {
|
||||
setResourceStoreOpen(true);
|
||||
}}
|
||||
primary
|
||||
label={<Trans>Choose</Trans>}
|
||||
disabled={isLoading}
|
||||
style={style}
|
||||
/>
|
||||
)}
|
||||
renderTextField={() => (
|
||||
<SemiControlledTextField
|
||||
floatingLabelText={<Trans>Icon URL</Trans>}
|
||||
value={eventsFunctionsExtension.getPreviewIconUrl()}
|
||||
onChange={text => {
|
||||
eventsFunctionsExtension.setPreviewIconUrl(text);
|
||||
}}
|
||||
<CompactPropertiesEditorRowField
|
||||
label={i18n._(t`Name`)}
|
||||
field={
|
||||
<CompactTextField
|
||||
value={eventsFunctionsExtension.getName()}
|
||||
onChange={() => {}}
|
||||
disabled
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
}
|
||||
/>
|
||||
<TextField
|
||||
floatingLabelText={<Trans>Name displayed in editor</Trans>}
|
||||
value={eventsFunctionsExtension.getFullName()}
|
||||
onChange={(e, text) => {
|
||||
eventsFunctionsExtension.setFullName(text);
|
||||
forceUpdate();
|
||||
<CompactIconField
|
||||
onLoadChange={onLoadChange}
|
||||
isLoading={isLoading}
|
||||
getPreviewIconUrl={() =>
|
||||
eventsFunctionsExtension.getPreviewIconUrl()
|
||||
}
|
||||
setPreviewIconUrl={value => {
|
||||
eventsFunctionsExtension.setPreviewIconUrl(value);
|
||||
}}
|
||||
setIconUrl={value => {
|
||||
eventsFunctionsExtension.setIconUrl(value);
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
<TextField
|
||||
floatingLabelText={<Trans>Short description</Trans>}
|
||||
<CompactPropertiesEditorRowField
|
||||
label={i18n._(t`Name displayed in editor`)}
|
||||
field={
|
||||
<CompactTextField
|
||||
value={eventsFunctionsExtension.getFullName()}
|
||||
onChange={(e, text) => {
|
||||
eventsFunctionsExtension.setFullName(text);
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<CompactTextAreaField
|
||||
label={i18n._(t`Short description`)}
|
||||
value={eventsFunctionsExtension.getShortDescription()}
|
||||
onChange={(e, text) => {
|
||||
onChange={text => {
|
||||
eventsFunctionsExtension.setShortDescription(text);
|
||||
forceUpdate();
|
||||
}}
|
||||
multiline
|
||||
fullWidth
|
||||
rows={2}
|
||||
rowsMax={2}
|
||||
/>
|
||||
<TextField
|
||||
floatingLabelText={<Trans>Description (markdown supported)</Trans>}
|
||||
<CompactTextAreaField
|
||||
label={i18n._(t`Description (markdown supported)`)}
|
||||
value={eventsFunctionsExtension.getDescription()}
|
||||
onChange={(e, text) => {
|
||||
onChange={text => {
|
||||
eventsFunctionsExtension.setDescription(text);
|
||||
forceUpdate();
|
||||
}}
|
||||
multiline
|
||||
fullWidth
|
||||
rows={5}
|
||||
rowsMax={15}
|
||||
helperMarkdownText={i18n._(
|
||||
placeholder={i18n._(
|
||||
t`Explain and give some examples of what can be achieved with this extension.`
|
||||
)}
|
||||
rows={6}
|
||||
/>
|
||||
<TextField
|
||||
floatingLabelText={<Trans>Version</Trans>}
|
||||
value={eventsFunctionsExtension.getVersion()}
|
||||
onChange={(e, text) => {
|
||||
eventsFunctionsExtension.setVersion(text);
|
||||
<CompactPropertiesEditorRowField
|
||||
label={i18n._(t`Version`)}
|
||||
field={
|
||||
<CompactTextField
|
||||
value={eventsFunctionsExtension.getVersion()}
|
||||
onChange={(e, text) => {
|
||||
eventsFunctionsExtension.setVersion(text);
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<CompactPropertiesEditorRowField
|
||||
label={i18n._(t`Dimension`)}
|
||||
field={
|
||||
<CompactSelectField
|
||||
value={eventsFunctionsExtension.getDimension()}
|
||||
onChange={value => {
|
||||
eventsFunctionsExtension.setDimension(value);
|
||||
forceUpdate();
|
||||
}}
|
||||
>
|
||||
<SelectOption value="" label={t`Not applicable`} />
|
||||
<SelectOption value="2D" label="2D" />
|
||||
<SelectOption value="3D" label="3D" />
|
||||
<SelectOption value="2D/3D" label="2D/3D" />
|
||||
</CompactSelectField>
|
||||
}
|
||||
/>
|
||||
<CompactPropertiesEditorRowField
|
||||
label={i18n._(t`Tags (comma separated)`)}
|
||||
field={
|
||||
<CompactSemiControlledTextField
|
||||
value={eventsFunctionsExtension
|
||||
.getTags()
|
||||
.toJSArray()
|
||||
.join(', ')}
|
||||
onChange={text => {
|
||||
const tags = eventsFunctionsExtension.getTags();
|
||||
tags.clear();
|
||||
text
|
||||
.split(',')
|
||||
.map(tag => tag.trim())
|
||||
.filter(Boolean)
|
||||
.forEach(tag => {
|
||||
tags.push_back(tag);
|
||||
});
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<HelpPathTextField
|
||||
i18n={i18n}
|
||||
helpPath={eventsFunctionsExtension.getHelpPath()}
|
||||
onChangeHelpPath={helpPath => {
|
||||
eventsFunctionsExtension.setHelpPath(helpPath);
|
||||
forceUpdate();
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
<SemiControlledAutoComplete
|
||||
floatingLabelText={<Trans>Category (shown in the editor)</Trans>}
|
||||
@@ -319,48 +280,6 @@ export const ExtensionOptionsEditor = ({
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<SelectField
|
||||
floatingLabelText={<Trans>Dimension</Trans>}
|
||||
value={eventsFunctionsExtension.getDimension()}
|
||||
onChange={(e, i, value) => {
|
||||
eventsFunctionsExtension.setDimension(value);
|
||||
forceUpdate();
|
||||
}}
|
||||
fullWidth
|
||||
>
|
||||
<SelectOption value="" label={t`Not applicable`} />
|
||||
<SelectOption value="2D" label="2D" />
|
||||
<SelectOption value="3D" label="3D" />
|
||||
<SelectOption value="2D/3D" label="2D/3D" />
|
||||
</SelectField>
|
||||
<SemiControlledTextField
|
||||
floatingLabelText={<Trans>Tags (comma separated)</Trans>}
|
||||
value={eventsFunctionsExtension
|
||||
.getTags()
|
||||
.toJSArray()
|
||||
.join(', ')}
|
||||
onChange={text => {
|
||||
const tags = eventsFunctionsExtension.getTags();
|
||||
tags.clear();
|
||||
text
|
||||
.split(',')
|
||||
.map(tag => tag.trim())
|
||||
.filter(Boolean)
|
||||
.forEach(tag => {
|
||||
tags.push_back(tag);
|
||||
});
|
||||
forceUpdate();
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
<HelpPathTextField
|
||||
i18n={i18n}
|
||||
helpPath={eventsFunctionsExtension.getHelpPath()}
|
||||
onChangeHelpPath={helpPath => {
|
||||
eventsFunctionsExtension.setHelpPath(helpPath);
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
<UsersAutocomplete
|
||||
userIds={eventsFunctionsExtension.getAuthorIds().toJSArray()}
|
||||
onChange={userIdAndUsernames => {
|
||||
@@ -379,39 +298,6 @@ export const ExtensionOptionsEditor = ({
|
||||
</Trans>
|
||||
}
|
||||
/>
|
||||
{resourceStoreOpen && (
|
||||
<Dialog
|
||||
title={<Trans>Choose an icon for the extension</Trans>}
|
||||
actions={[
|
||||
<FlatButton
|
||||
key="cancel"
|
||||
label={<Trans>Cancel</Trans>}
|
||||
primary={false}
|
||||
onClick={() => {
|
||||
setResourceStoreOpen(false);
|
||||
}}
|
||||
/>,
|
||||
<DialogPrimaryButton
|
||||
primary
|
||||
key="apply"
|
||||
label={<Trans>Use icon</Trans>}
|
||||
onClick={() => onUseIcon(i18n)}
|
||||
/>,
|
||||
]}
|
||||
flexColumnBody
|
||||
fullHeight
|
||||
open
|
||||
onRequestClose={() => {
|
||||
setResourceStoreOpen(false);
|
||||
}}
|
||||
>
|
||||
<ResourceStore
|
||||
selectedResourceIndex={selectedSvgResourceIndex}
|
||||
onSelectResource={setSelectedSvgResourceIndex}
|
||||
resourceKind={'svg'}
|
||||
/>
|
||||
</Dialog>
|
||||
)}
|
||||
</ColumnStackLayout>
|
||||
)}
|
||||
</I18n>
|
||||
|
||||
@@ -87,6 +87,7 @@ export default function OptionsEditorDialog({
|
||||
}
|
||||
cannotBeDismissed={isLoading}
|
||||
onRequestClose={isLoading ? () => {} : onClose}
|
||||
maxWidth="md"
|
||||
>
|
||||
{currentTab === 'options' && (
|
||||
<Line>
|
||||
|
||||
Reference in New Issue
Block a user