Allow extensions to set different icons for behaviors and objects (#8472)

This commit is contained in:
D8H
2026-04-02 20:14:19 +02:00
committed by GitHub
parent 24ae6c33de
commit 92e449558b
17 changed files with 530 additions and 290 deletions
@@ -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
+22 -2
View File
@@ -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.
*/
+22 -2
View File
@@ -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.
+3 -4
View File
@@ -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 {
+9
View File
@@ -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);
+9
View File
@@ -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;
+3
View File
@@ -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;
@@ -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}
@@ -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={
@@ -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>
);
};
@@ -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>