mirror of
https://github.com/Heretek-AI/GDevelop-Heretek.git
synced 2026-10-12 02:02:42 +00:00
Add support for setting a custom help URL for extension actions/conditions/expressions (#8103)
Only show in developer changelog
This commit is contained in:
@@ -75,6 +75,9 @@ void EventsFunction::SerializeTo(SerializerElement& element) const {
|
||||
if (isAsync) {
|
||||
element.SetBoolAttribute("async", isAsync);
|
||||
}
|
||||
if (!helpUrl.empty()) {
|
||||
element.SetAttribute("helpUrl", helpUrl);
|
||||
}
|
||||
events.SerializeTo(element.AddChild("events"));
|
||||
|
||||
gd::String functionTypeStr = "Action";
|
||||
@@ -116,6 +119,7 @@ void EventsFunction::UnserializeFrom(gd::Project& project,
|
||||
getterName = element.GetStringAttribute("getterName");
|
||||
isPrivate = element.GetBoolAttribute("private");
|
||||
isAsync = element.GetBoolAttribute("async");
|
||||
helpUrl = element.GetStringAttribute("helpUrl");
|
||||
events.UnserializeFrom(project, element.GetChild("events"));
|
||||
|
||||
gd::String functionTypeStr = element.GetStringAttribute("functionType");
|
||||
|
||||
@@ -223,6 +223,19 @@ class GD_CORE_API EventsFunction {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get the help URL for this function.
|
||||
*/
|
||||
const gd::String& GetHelpUrl() const { return helpUrl; }
|
||||
|
||||
/**
|
||||
* \brief Set the help URL for this function.
|
||||
*/
|
||||
EventsFunction& SetHelpUrl(const gd::String& helpUrl_) {
|
||||
helpUrl = helpUrl_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Return the events.
|
||||
*/
|
||||
@@ -304,6 +317,7 @@ class GD_CORE_API EventsFunction {
|
||||
gd::ObjectGroupsContainer objectGroups;
|
||||
bool isPrivate = false;
|
||||
bool isAsync = false;
|
||||
gd::String helpUrl;
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival ([email protected]). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
/**
|
||||
* @file Tests covering EventsFunction
|
||||
*/
|
||||
#include "GDCore/Project/EventsFunction.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
#include "catch.hpp"
|
||||
|
||||
TEST_CASE("EventsFunction", "[common]") {
|
||||
SECTION("Basic properties") {
|
||||
gd::EventsFunction eventsFunction;
|
||||
|
||||
eventsFunction.SetName("MyFunction");
|
||||
eventsFunction.SetFullName("My Function");
|
||||
eventsFunction.SetDescription("A test function");
|
||||
eventsFunction.SetGroup("Test Group");
|
||||
eventsFunction.SetSentence("Do something with _PARAM1_");
|
||||
|
||||
REQUIRE(eventsFunction.GetName() == "MyFunction");
|
||||
REQUIRE(eventsFunction.GetFullName() == "My Function");
|
||||
REQUIRE(eventsFunction.GetDescription() == "A test function");
|
||||
REQUIRE(eventsFunction.GetGroup() == "Test Group");
|
||||
REQUIRE(eventsFunction.GetSentence() == "Do something with _PARAM1_");
|
||||
}
|
||||
|
||||
SECTION("Help URL") {
|
||||
gd::EventsFunction eventsFunction;
|
||||
|
||||
// Default should be empty
|
||||
REQUIRE(eventsFunction.GetHelpUrl() == "");
|
||||
|
||||
// Can set a help URL
|
||||
eventsFunction.SetHelpUrl("https://example.com/help");
|
||||
REQUIRE(eventsFunction.GetHelpUrl() == "https://example.com/help");
|
||||
|
||||
// Can clear the help URL
|
||||
eventsFunction.SetHelpUrl("");
|
||||
REQUIRE(eventsFunction.GetHelpUrl() == "");
|
||||
}
|
||||
|
||||
SECTION("Serialization with help URL") {
|
||||
gd::Project project;
|
||||
|
||||
gd::EventsFunction eventsFunction;
|
||||
eventsFunction.SetName("MyFunction");
|
||||
eventsFunction.SetFullName("My Function");
|
||||
eventsFunction.SetDescription("A test function");
|
||||
eventsFunction.SetHelpUrl("https://example.com/custom-help");
|
||||
|
||||
gd::SerializerElement element;
|
||||
eventsFunction.SerializeTo(element);
|
||||
|
||||
gd::EventsFunction eventsFunction2;
|
||||
eventsFunction2.UnserializeFrom(project, element);
|
||||
|
||||
REQUIRE(eventsFunction2.GetName() == "MyFunction");
|
||||
REQUIRE(eventsFunction2.GetFullName() == "My Function");
|
||||
REQUIRE(eventsFunction2.GetDescription() == "A test function");
|
||||
REQUIRE(eventsFunction2.GetHelpUrl() == "https://example.com/custom-help");
|
||||
}
|
||||
|
||||
SECTION("Serialization without help URL") {
|
||||
gd::Project project;
|
||||
|
||||
gd::EventsFunction eventsFunction;
|
||||
eventsFunction.SetName("MyFunction");
|
||||
eventsFunction.SetFullName("My Function");
|
||||
eventsFunction.SetDescription("A test function");
|
||||
// No help URL set
|
||||
|
||||
gd::SerializerElement element;
|
||||
eventsFunction.SerializeTo(element);
|
||||
|
||||
gd::EventsFunction eventsFunction2;
|
||||
eventsFunction2.UnserializeFrom(project, element);
|
||||
|
||||
REQUIRE(eventsFunction2.GetName() == "MyFunction");
|
||||
REQUIRE(eventsFunction2.GetFullName() == "My Function");
|
||||
REQUIRE(eventsFunction2.GetDescription() == "A test function");
|
||||
REQUIRE(eventsFunction2.GetHelpUrl() == "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,6 +535,9 @@ MetadataDeclarationHelper::DeclareExpressionMetadata(
|
||||
expressionAndCondition.AddCodeOnlyParameter("currentScene", "");
|
||||
DeclareEventsFunctionParameters(freeEventsFunctions, eventsFunction,
|
||||
expressionAndCondition, 0);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
expressionAndCondition.SetHelpPath(eventsFunction.GetHelpUrl());
|
||||
}
|
||||
expressionAndConditions.push_back(expressionAndCondition);
|
||||
return expressionAndConditions.back();
|
||||
} else {
|
||||
@@ -556,6 +559,9 @@ MetadataDeclarationHelper::DeclareExpressionMetadata(
|
||||
expression.AddCodeOnlyParameter("currentScene", "");
|
||||
DeclareEventsFunctionParameters(freeEventsFunctions, eventsFunction,
|
||||
expression, 0);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
expression.SetHelpPath(eventsFunction.GetHelpUrl());
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
@@ -581,6 +587,9 @@ gd::InstructionMetadata &MetadataDeclarationHelper::DeclareInstructionMetadata(
|
||||
condition.AddCodeOnlyParameter("currentScene", "");
|
||||
DeclareEventsFunctionParameters(freeEventsFunctions, eventsFunction,
|
||||
condition, 0);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
condition.SetHelpPath(eventsFunction.GetHelpUrl());
|
||||
}
|
||||
return condition;
|
||||
} else if (functionType == gd::EventsFunction::ActionWithOperator) {
|
||||
if (freeEventsFunctions.HasEventsFunctionNamed(
|
||||
@@ -607,6 +616,9 @@ gd::InstructionMetadata &MetadataDeclarationHelper::DeclareInstructionMetadata(
|
||||
action.AddCodeOnlyParameter("currentScene", "");
|
||||
DeclareEventsFunctionParameters(freeEventsFunctions, eventsFunction,
|
||||
action, 0);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
action.SetHelpPath(eventsFunction.GetHelpUrl());
|
||||
}
|
||||
return action;
|
||||
} else {
|
||||
|
||||
@@ -621,6 +633,9 @@ gd::InstructionMetadata &MetadataDeclarationHelper::DeclareInstructionMetadata(
|
||||
action.AddCodeOnlyParameter("currentScene", "");
|
||||
DeclareEventsFunctionParameters(freeEventsFunctions, eventsFunction,
|
||||
action, 0);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
action.SetHelpPath(eventsFunction.GetHelpUrl());
|
||||
}
|
||||
return action;
|
||||
}
|
||||
} else {
|
||||
@@ -634,6 +649,9 @@ gd::InstructionMetadata &MetadataDeclarationHelper::DeclareInstructionMetadata(
|
||||
action.AddCodeOnlyParameter("currentScene", "");
|
||||
DeclareEventsFunctionParameters(freeEventsFunctions, eventsFunction,
|
||||
action, 0);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
action.SetHelpPath(eventsFunction.GetHelpUrl());
|
||||
}
|
||||
return action;
|
||||
}
|
||||
}
|
||||
@@ -726,6 +744,9 @@ MetadataDeclarationHelper::DeclareBehaviorExpressionMetadata(
|
||||
GetExtensionIconUrl(extension));
|
||||
DeclareEventsFunctionParameters(eventsBasedBehavior.GetEventsFunctions(),
|
||||
eventsFunction, expressionAndCondition, 2);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
expressionAndCondition.SetHelpPath(eventsFunction.GetHelpUrl());
|
||||
}
|
||||
expressionAndConditions.push_back(expressionAndCondition);
|
||||
return expressionAndConditions.back();
|
||||
} else {
|
||||
@@ -751,6 +772,9 @@ MetadataDeclarationHelper::DeclareBehaviorExpressionMetadata(
|
||||
GetExtensionIconUrl(extension));
|
||||
DeclareEventsFunctionParameters(eventsBasedBehavior.GetEventsFunctions(),
|
||||
eventsFunction, expression, 2);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
expression.SetHelpPath(eventsFunction.GetHelpUrl());
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
@@ -779,6 +803,9 @@ MetadataDeclarationHelper::DeclareBehaviorInstructionMetadata(
|
||||
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
|
||||
DeclareEventsFunctionParameters(eventsBasedBehavior.GetEventsFunctions(),
|
||||
eventsFunction, condition, 2);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
condition.SetHelpPath(eventsFunction.GetHelpUrl());
|
||||
}
|
||||
return condition;
|
||||
} else if (functionType == gd::EventsFunction::ActionWithOperator) {
|
||||
auto &eventsFunctionsContainer = eventsBasedBehavior.GetEventsFunctions();
|
||||
@@ -804,6 +831,9 @@ MetadataDeclarationHelper::DeclareBehaviorInstructionMetadata(
|
||||
|
||||
DeclareEventsFunctionParameters(eventsBasedBehavior.GetEventsFunctions(),
|
||||
eventsFunction, action, 2);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
action.SetHelpPath(eventsFunction.GetHelpUrl());
|
||||
}
|
||||
return action;
|
||||
} else {
|
||||
auto &action = behaviorMetadata.AddScopedAction(
|
||||
@@ -817,6 +847,9 @@ MetadataDeclarationHelper::DeclareBehaviorInstructionMetadata(
|
||||
|
||||
DeclareEventsFunctionParameters(eventsBasedBehavior.GetEventsFunctions(),
|
||||
eventsFunction, action, 2);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
action.SetHelpPath(eventsFunction.GetHelpUrl());
|
||||
}
|
||||
return action;
|
||||
}
|
||||
} else {
|
||||
@@ -834,6 +867,9 @@ MetadataDeclarationHelper::DeclareBehaviorInstructionMetadata(
|
||||
|
||||
DeclareEventsFunctionParameters(eventsBasedBehavior.GetEventsFunctions(),
|
||||
eventsFunction, action, 2);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
action.SetHelpPath(eventsFunction.GetHelpUrl());
|
||||
}
|
||||
return action;
|
||||
}
|
||||
}
|
||||
@@ -900,6 +936,9 @@ MetadataDeclarationHelper::DeclareObjectExpressionMetadata(
|
||||
|
||||
DeclareEventsFunctionParameters(eventsBasedObject.GetEventsFunctions(),
|
||||
eventsFunction, expressionAndCondition, 1);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
expressionAndCondition.SetHelpPath(eventsFunction.GetHelpUrl());
|
||||
}
|
||||
expressionAndConditions.push_back(expressionAndCondition);
|
||||
return expressionAndConditions.back();
|
||||
} else {
|
||||
@@ -926,6 +965,9 @@ MetadataDeclarationHelper::DeclareObjectExpressionMetadata(
|
||||
|
||||
DeclareEventsFunctionParameters(eventsBasedObject.GetEventsFunctions(),
|
||||
eventsFunction, expression, 1);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
expression.SetHelpPath(eventsFunction.GetHelpUrl());
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
@@ -955,6 +997,9 @@ MetadataDeclarationHelper::DeclareObjectInstructionMetadata(
|
||||
|
||||
DeclareEventsFunctionParameters(eventsBasedObject.GetEventsFunctions(),
|
||||
eventsFunction, condition, 1);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
condition.SetHelpPath(eventsFunction.GetHelpUrl());
|
||||
}
|
||||
return condition;
|
||||
} else if (functionType == gd::EventsFunction::ActionWithOperator) {
|
||||
auto &eventsFunctionsContainer = eventsBasedObject.GetEventsFunctions();
|
||||
@@ -979,6 +1024,9 @@ MetadataDeclarationHelper::DeclareObjectInstructionMetadata(
|
||||
|
||||
DeclareEventsFunctionParameters(eventsBasedObject.GetEventsFunctions(),
|
||||
eventsFunction, action, 1);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
action.SetHelpPath(eventsFunction.GetHelpUrl());
|
||||
}
|
||||
return action;
|
||||
} else {
|
||||
auto &action = objectMetadata.AddScopedAction(
|
||||
@@ -991,6 +1039,9 @@ MetadataDeclarationHelper::DeclareObjectInstructionMetadata(
|
||||
|
||||
DeclareEventsFunctionParameters(eventsBasedObject.GetEventsFunctions(),
|
||||
eventsFunction, action, 1);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
action.SetHelpPath(eventsFunction.GetHelpUrl());
|
||||
}
|
||||
return action;
|
||||
}
|
||||
} else {
|
||||
@@ -1008,6 +1059,9 @@ MetadataDeclarationHelper::DeclareObjectInstructionMetadata(
|
||||
|
||||
DeclareEventsFunctionParameters(eventsBasedObject.GetEventsFunctions(),
|
||||
eventsFunction, action, 1);
|
||||
if (!eventsFunction.GetHelpUrl().empty()) {
|
||||
action.SetHelpPath(eventsFunction.GetHelpUrl());
|
||||
}
|
||||
return action;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3206,6 +3206,8 @@ interface EventsFunction {
|
||||
boolean IsPrivate();
|
||||
[Ref] EventsFunction SetAsync(boolean isAsync);
|
||||
boolean IsAsync();
|
||||
[Ref] EventsFunction SetHelpUrl([Const] DOMString helpUrl);
|
||||
[Const, Ref] DOMString GetHelpUrl();
|
||||
boolean IsAction();
|
||||
boolean IsExpression();
|
||||
boolean IsCondition();
|
||||
|
||||
@@ -4579,6 +4579,15 @@ describe('libGD.js', function () {
|
||||
expect(eventsFunction.getDescription()).toBe('My description');
|
||||
eventsFunction.delete();
|
||||
});
|
||||
it('can have a help URL', function () {
|
||||
const eventsFunction = new gd.EventsFunction();
|
||||
expect(eventsFunction.getHelpUrl()).toBe('');
|
||||
eventsFunction.setHelpUrl('https://example.com/help');
|
||||
expect(eventsFunction.getHelpUrl()).toBe('https://example.com/help');
|
||||
eventsFunction.setHelpUrl('');
|
||||
expect(eventsFunction.getHelpUrl()).toBe('');
|
||||
eventsFunction.delete();
|
||||
});
|
||||
});
|
||||
|
||||
describe('gd.EventsFunctionsExtension', () => {
|
||||
|
||||
@@ -43,6 +43,72 @@ describe('MetadataDeclarationHelper', () => {
|
||||
project.delete();
|
||||
});
|
||||
|
||||
it('propagates helpUrl to action metadata', () => {
|
||||
const extension = new gd.PlatformExtension();
|
||||
const project = new gd.Project();
|
||||
|
||||
const eventExtension = project.insertNewEventsFunctionsExtension(
|
||||
'MyExtension',
|
||||
0
|
||||
);
|
||||
const freeEventsFunctions = eventExtension.getEventsFunctions();
|
||||
const eventFunction = freeEventsFunctions.insertNewEventsFunction(
|
||||
'MyFunction',
|
||||
0
|
||||
);
|
||||
eventFunction.setFunctionType(gd.EventsFunction.Action);
|
||||
eventFunction.setHelpUrl('https://example.com/custom-help');
|
||||
|
||||
const metadataDeclarationHelper = new gd.MetadataDeclarationHelper();
|
||||
metadataDeclarationHelper.generateFreeFunctionMetadata(
|
||||
project,
|
||||
extension,
|
||||
eventExtension,
|
||||
eventFunction
|
||||
);
|
||||
metadataDeclarationHelper.delete();
|
||||
|
||||
expect(extension.getAllActions().has('MyFunction')).toBe(true);
|
||||
const action = extension.getAllActions().get('MyFunction');
|
||||
expect(action.getHelpPath()).toBe('https://example.com/custom-help');
|
||||
|
||||
extension.delete();
|
||||
project.delete();
|
||||
});
|
||||
|
||||
it('does not set helpPath when helpUrl is empty', () => {
|
||||
const extension = new gd.PlatformExtension();
|
||||
const project = new gd.Project();
|
||||
|
||||
const eventExtension = project.insertNewEventsFunctionsExtension(
|
||||
'MyExtension',
|
||||
0
|
||||
);
|
||||
const freeEventsFunctions = eventExtension.getEventsFunctions();
|
||||
const eventFunction = freeEventsFunctions.insertNewEventsFunction(
|
||||
'MyFunction',
|
||||
0
|
||||
);
|
||||
eventFunction.setFunctionType(gd.EventsFunction.Action);
|
||||
// No helpUrl set (empty by default)
|
||||
|
||||
const metadataDeclarationHelper = new gd.MetadataDeclarationHelper();
|
||||
metadataDeclarationHelper.generateFreeFunctionMetadata(
|
||||
project,
|
||||
extension,
|
||||
eventExtension,
|
||||
eventFunction
|
||||
);
|
||||
metadataDeclarationHelper.delete();
|
||||
|
||||
expect(extension.getAllActions().has('MyFunction')).toBe(true);
|
||||
const action = extension.getAllActions().get('MyFunction');
|
||||
expect(action.getHelpPath()).toBe('');
|
||||
|
||||
extension.delete();
|
||||
project.delete();
|
||||
});
|
||||
|
||||
it('can create metadata for free actions with an underscore and unicode characters', () => {
|
||||
const extension = new gd.PlatformExtension();
|
||||
const project = new gd.Project();
|
||||
@@ -114,6 +180,78 @@ describe('MetadataDeclarationHelper', () => {
|
||||
project.delete();
|
||||
});
|
||||
|
||||
it('can create metadata for free actions with a help URL', () => {
|
||||
const extension = new gd.PlatformExtension();
|
||||
const project = new gd.Project();
|
||||
|
||||
const eventExtension = project.insertNewEventsFunctionsExtension(
|
||||
'MyExtension',
|
||||
0
|
||||
);
|
||||
const freeEventsFunctions = eventExtension.getEventsFunctions();
|
||||
const eventFunction = freeEventsFunctions.insertNewEventsFunction(
|
||||
'MyFunction',
|
||||
0
|
||||
);
|
||||
eventFunction.setFunctionType(gd.EventsFunction.Action);
|
||||
eventFunction.setFullName('My function');
|
||||
eventFunction.setDescription('My function description.');
|
||||
eventFunction.setSentence('My function sentence');
|
||||
eventFunction.setHelpUrl('https://example.com/help');
|
||||
|
||||
const metadataDeclarationHelper = new gd.MetadataDeclarationHelper();
|
||||
metadataDeclarationHelper.generateFreeFunctionMetadata(
|
||||
project,
|
||||
extension,
|
||||
eventExtension,
|
||||
eventFunction
|
||||
);
|
||||
metadataDeclarationHelper.delete();
|
||||
|
||||
expect(extension.getAllActions().has('MyFunction')).toBe(true);
|
||||
const action = extension.getAllActions().get('MyFunction');
|
||||
expect(action.getHelpPath()).toBe('https://example.com/help');
|
||||
|
||||
extension.delete();
|
||||
project.delete();
|
||||
});
|
||||
|
||||
it('does not set help path when help URL is empty', () => {
|
||||
const extension = new gd.PlatformExtension();
|
||||
const project = new gd.Project();
|
||||
|
||||
const eventExtension = project.insertNewEventsFunctionsExtension(
|
||||
'MyExtension',
|
||||
0
|
||||
);
|
||||
const freeEventsFunctions = eventExtension.getEventsFunctions();
|
||||
const eventFunction = freeEventsFunctions.insertNewEventsFunction(
|
||||
'MyFunction',
|
||||
0
|
||||
);
|
||||
eventFunction.setFunctionType(gd.EventsFunction.Action);
|
||||
eventFunction.setFullName('My function');
|
||||
eventFunction.setDescription('My function description.');
|
||||
eventFunction.setSentence('My function sentence');
|
||||
// Help URL is not set (empty by default)
|
||||
|
||||
const metadataDeclarationHelper = new gd.MetadataDeclarationHelper();
|
||||
metadataDeclarationHelper.generateFreeFunctionMetadata(
|
||||
project,
|
||||
extension,
|
||||
eventExtension,
|
||||
eventFunction
|
||||
);
|
||||
metadataDeclarationHelper.delete();
|
||||
|
||||
expect(extension.getAllActions().has('MyFunction')).toBe(true);
|
||||
const action = extension.getAllActions().get('MyFunction');
|
||||
expect(action.getHelpPath()).toBe('');
|
||||
|
||||
extension.delete();
|
||||
project.delete();
|
||||
});
|
||||
|
||||
it('can create metadata for free expressions', () => {
|
||||
const extension = new gd.PlatformExtension();
|
||||
const project = new gd.Project();
|
||||
|
||||
Vendored
+2
@@ -2306,6 +2306,8 @@ export class EventsFunction extends EmscriptenObject {
|
||||
isPrivate(): boolean;
|
||||
setAsync(isAsync: boolean): EventsFunction;
|
||||
isAsync(): boolean;
|
||||
setHelpUrl(helpUrl: string): EventsFunction;
|
||||
getHelpUrl(): string;
|
||||
isAction(): boolean;
|
||||
isExpression(): boolean;
|
||||
isCondition(): boolean;
|
||||
|
||||
@@ -25,6 +25,8 @@ declare class gdEventsFunction {
|
||||
isPrivate(): boolean;
|
||||
setAsync(isAsync: boolean): gdEventsFunction;
|
||||
isAsync(): boolean;
|
||||
setHelpUrl(helpUrl: string): gdEventsFunction;
|
||||
getHelpUrl(): string;
|
||||
isAction(): boolean;
|
||||
isExpression(): boolean;
|
||||
isCondition(): boolean;
|
||||
|
||||
+39
@@ -24,6 +24,10 @@ import useForceUpdate from '../../Utils/UseForceUpdate';
|
||||
import Checkbox from '../../UI/Checkbox';
|
||||
import { type ExtensionItemConfigurationAttribute } from '../../EventsFunctionsExtensionEditor';
|
||||
import { useResponsiveWindowSize } from '../../UI/Responsive/ResponsiveWindowMeasurer';
|
||||
import {
|
||||
isRelativePathToDocumentationRoot,
|
||||
isDocumentationAbsoluteUrl,
|
||||
} from '../../Utils/HelpLink';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
@@ -446,6 +450,41 @@ export const EventsFunctionPropertiesEditor = ({
|
||||
/>
|
||||
)}
|
||||
</Line>
|
||||
<Line noMargin>
|
||||
{(() => {
|
||||
const helpUrl = eventsFunction.getHelpUrl();
|
||||
const isValidHelpUrl =
|
||||
!helpUrl ||
|
||||
isDocumentationAbsoluteUrl(helpUrl) ||
|
||||
isRelativePathToDocumentationRoot(helpUrl);
|
||||
return (
|
||||
<SemiControlledTextField
|
||||
commitOnBlur
|
||||
floatingLabelText={<Trans>Help page URL</Trans>}
|
||||
translatableHintText={t`Enter a URL to a help page for this action/condition/expression`}
|
||||
helperMarkdownText={i18n._(
|
||||
t`Optional. Enter a full URL (starting with https://) to a help page. A help icon will appear next to the action/condition/expression title in the editor, allowing users to quickly access documentation.`
|
||||
)}
|
||||
errorText={
|
||||
!isValidHelpUrl ? (
|
||||
<Trans>
|
||||
This is not a URL starting with "http://" or
|
||||
"https://".
|
||||
</Trans>
|
||||
) : null
|
||||
}
|
||||
fullWidth
|
||||
value={helpUrl}
|
||||
onChange={text => {
|
||||
eventsFunction.setHelpUrl(text);
|
||||
if (onConfigurationUpdated)
|
||||
onConfigurationUpdated();
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
</Line>
|
||||
{type === gd.EventsFunction.ActionWithOperator ? (
|
||||
<Line noMargin>
|
||||
<SemiControlledTextField
|
||||
|
||||
@@ -16,6 +16,7 @@ import InstructionOrObjectSelector, {
|
||||
} from './InstructionOrObjectSelector';
|
||||
import InstructionOrExpressionSelector from './InstructionOrExpressionSelector';
|
||||
import HelpButton from '../../UI/HelpButton';
|
||||
import { isRelativePathToDocumentationRoot } from '../../Utils/HelpLink';
|
||||
import { type EventsScope } from '../../InstructionOrExpression/EventsScope';
|
||||
import { SelectColumns } from '../../UI/Responsive/SelectColumns';
|
||||
import { useResponsiveWindowSize } from '../../UI/Responsive/ResponsiveWindowMeasurer';
|
||||
@@ -338,9 +339,15 @@ const InstructionEditorDialog = ({
|
||||
) : null,
|
||||
<HelpButton
|
||||
key="help"
|
||||
helpPagePath={instructionHelpPage || '/events'}
|
||||
helpPagePath={
|
||||
instructionHelpPage &&
|
||||
isRelativePathToDocumentationRoot(instructionHelpPage)
|
||||
? instructionHelpPage
|
||||
: '/events'
|
||||
}
|
||||
label={
|
||||
!instructionHelpPage ||
|
||||
!isRelativePathToDocumentationRoot(instructionHelpPage) ||
|
||||
(isMobile || step === 'object-or-free-instructions') ? (
|
||||
<Trans>Help</Trans>
|
||||
) : isCondition ? (
|
||||
|
||||
@@ -10,6 +10,11 @@ import { mapFor } from '../../Utils/MapFor';
|
||||
import EmptyMessage from '../../UI/EmptyMessage';
|
||||
import ParameterRenderingService from '../ParameterRenderingService';
|
||||
import HelpButton from '../../UI/HelpButton';
|
||||
import HelpIcon from '../../UI/HelpIcon';
|
||||
import {
|
||||
isRelativePathToDocumentationRoot,
|
||||
isDocumentationAbsoluteUrl,
|
||||
} from '../../Utils/HelpLink';
|
||||
import { type ResourceManagementProps } from '../../ResourcesList/ResourceSource';
|
||||
import { Column, Line, Spacer } from '../../UI/Grid';
|
||||
import AlertMessage from '../../UI/AlertMessage';
|
||||
@@ -293,8 +298,8 @@ const InstructionParametersEditor = React.forwardRef<
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<ScrollView autoHideScrollbar id={id}>
|
||||
<Column expand>
|
||||
<Line alignItems="flex-start">
|
||||
<ColumnStackLayout expand>
|
||||
<Line alignItems="flex-start" noMargin>
|
||||
<img
|
||||
src={iconFilename}
|
||||
alt=""
|
||||
@@ -306,9 +311,14 @@ const InstructionParametersEditor = React.forwardRef<
|
||||
}}
|
||||
/>
|
||||
<Column expand>
|
||||
<Text style={styles.description}>
|
||||
{instructionMetadata.getDescription()}
|
||||
</Text>
|
||||
<Line noMargin alignItems="flex-start">
|
||||
<Text style={styles.description} noMargin>
|
||||
{instructionMetadata.getDescription()}
|
||||
</Text>
|
||||
{helpPage && isDocumentationAbsoluteUrl(helpPage) && (
|
||||
<HelpIcon size="small" helpPagePath={helpPage} />
|
||||
)}
|
||||
</Line>
|
||||
</Column>
|
||||
</Line>
|
||||
{instructionExtraInformation && (
|
||||
@@ -464,20 +474,22 @@ const InstructionParametersEditor = React.forwardRef<
|
||||
)}
|
||||
</div>
|
||||
<Line>
|
||||
{!noHelpButton && helpPage && (
|
||||
<HelpButton
|
||||
helpPagePath={instructionMetadata.getHelpPath()}
|
||||
label={
|
||||
isCondition ? (
|
||||
<Trans>Help for this condition</Trans>
|
||||
) : (
|
||||
<Trans>Help for this action</Trans>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{!noHelpButton &&
|
||||
helpPage &&
|
||||
isRelativePathToDocumentationRoot(helpPage) && (
|
||||
<HelpButton
|
||||
helpPagePath={helpPage}
|
||||
label={
|
||||
isCondition ? (
|
||||
<Trans>Help for this condition</Trans>
|
||||
) : (
|
||||
<Trans>Help for this action</Trans>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Line>
|
||||
</Column>
|
||||
</ColumnStackLayout>
|
||||
</ScrollView>
|
||||
)}
|
||||
</I18n>
|
||||
|
||||
@@ -39,7 +39,10 @@ const HelpIcon = (props: PropsType) => {
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
onClick={() => Window.openExternalURL(getHelpLink(helpPagePath))}
|
||||
onClick={event => {
|
||||
event.stopPropagation();
|
||||
Window.openExternalURL(getHelpLink(helpPagePath));
|
||||
}}
|
||||
disabled={props.disabled}
|
||||
style={props.style}
|
||||
size={props.size}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// @flow
|
||||
import {
|
||||
isRelativePathToDocumentationRoot,
|
||||
isDocumentationAbsoluteUrl,
|
||||
getHelpLink,
|
||||
} from './HelpLink';
|
||||
|
||||
describe('HelpLink', () => {
|
||||
describe('isRelativePathToDocumentationRoot', () => {
|
||||
it('returns true for paths starting with /', () => {
|
||||
expect(isRelativePathToDocumentationRoot('/test')).toBe(true);
|
||||
expect(isRelativePathToDocumentationRoot('/all-features/audio')).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('returns false for absolute URLs', () => {
|
||||
expect(isRelativePathToDocumentationRoot('https://example.com')).toBe(
|
||||
false
|
||||
);
|
||||
expect(isRelativePathToDocumentationRoot('http://example.com')).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it('returns false for empty string', () => {
|
||||
expect(isRelativePathToDocumentationRoot('')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isDocumentationAbsoluteUrl', () => {
|
||||
it('returns true for https URLs', () => {
|
||||
expect(isDocumentationAbsoluteUrl('https://example.com/help')).toBe(true);
|
||||
expect(isDocumentationAbsoluteUrl('https://wiki.gdevelop.io')).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for http URLs', () => {
|
||||
expect(isDocumentationAbsoluteUrl('http://example.com/help')).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for relative paths', () => {
|
||||
expect(isDocumentationAbsoluteUrl('/test')).toBe(false);
|
||||
expect(isDocumentationAbsoluteUrl('/all-features/audio')).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false for empty string', () => {
|
||||
expect(isDocumentationAbsoluteUrl('')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHelpLink', () => {
|
||||
it('returns wiki link for relative paths', () => {
|
||||
expect(getHelpLink('/test')).toBe(
|
||||
'https://wiki.gdevelop.io/gdevelop5/test?utm_source=gdevelop&utm_medium=help-link'
|
||||
);
|
||||
});
|
||||
|
||||
it('returns wiki link with anchor for relative paths', () => {
|
||||
expect(getHelpLink('/test', 'section1')).toBe(
|
||||
'https://wiki.gdevelop.io/gdevelop5/test?utm_source=gdevelop&utm_medium=help-link#section1'
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the absolute URL as-is', () => {
|
||||
expect(getHelpLink('https://example.com/custom-help')).toBe(
|
||||
'https://example.com/custom-help'
|
||||
);
|
||||
});
|
||||
|
||||
it('returns empty string for invalid paths', () => {
|
||||
expect(getHelpLink('')).toBe('');
|
||||
expect(getHelpLink('invalid')).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -914,7 +914,11 @@ storiesOf('UI Building Blocks/HelpButton', module)
|
||||
|
||||
storiesOf('UI Building Blocks/HelpIcon', module)
|
||||
.addDecorator(paperDecorator)
|
||||
.add('default', () => <HelpIcon helpPagePath="/test" />);
|
||||
.add('default (wiki path)', () => <HelpIcon helpPagePath="/test" />)
|
||||
.add('absolute URL (custom help)', () => (
|
||||
<HelpIcon helpPagePath="https://example.com/custom-help" />
|
||||
))
|
||||
.add('small size', () => <HelpIcon helpPagePath="/test" size="small" />);
|
||||
|
||||
storiesOf('PropertiesEditor', module)
|
||||
.addDecorator(paperDecorator)
|
||||
|
||||
Reference in New Issue
Block a user