mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
1568 lines
50 KiB
JavaScript
1568 lines
50 KiB
JavaScript
// @flow
|
|
import { Trans } from '@lingui/macro';
|
|
import { t } from '@lingui/macro';
|
|
import { I18n } from '@lingui/react';
|
|
import { type I18n as I18nType } from '@lingui/core';
|
|
|
|
import * as React from 'react';
|
|
import EventsSheet, { type EventsSheetInterface } from '../EventsSheet';
|
|
import EditorMosaic, {
|
|
type EditorMosaicInterface,
|
|
mosaicContainsNode,
|
|
} from '../UI/EditorMosaic';
|
|
import EmptyMessage from '../UI/EmptyMessage';
|
|
import EventsFunctionConfigurationEditor from './EventsFunctionConfigurationEditor';
|
|
import EventsFunctionsListWithErrorBoundary, {
|
|
type EventsFunctionsListInterface,
|
|
} from '../EventsFunctionsList';
|
|
import { type EventsFunctionCreationParameters } from '../EventsFunctionsList/EventsFunctionTreeViewItemContent';
|
|
import { type EventsBasedObjectCreationParameters } from '../EventsFunctionsList/EventsBasedObjectTreeViewItemContent';
|
|
import Background from '../UI/Background';
|
|
import OptionsEditorDialog from './OptionsEditorDialog';
|
|
import EventsBasedBehaviorEditorPanel from '../EventsBasedBehaviorEditor/EventsBasedBehaviorEditorPanel';
|
|
import EventsBasedObjectEditorPanel from '../EventsBasedObjectEditor/EventsBasedObjectEditorPanel';
|
|
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
|
|
import BehaviorMethodSelectorDialog from './BehaviorMethodSelectorDialog';
|
|
import ObjectMethodSelectorDialog from './ObjectMethodSelectorDialog';
|
|
import ExtensionFunctionSelectorDialog from './ExtensionFunctionSelectorDialog';
|
|
import EventsBasedObjectSelectorDialog from './EventsBasedObjectSelectorDialog';
|
|
import { ResponsiveWindowMeasurer } from '../UI/Responsive/ResponsiveWindowMeasurer';
|
|
import EditorNavigator, {
|
|
type EditorNavigatorInterface,
|
|
} from '../UI/EditorMosaic/EditorNavigator';
|
|
import { type UnsavedChanges } from '../MainFrame/UnsavedChangesContext';
|
|
import PreferencesContext from '../MainFrame/Preferences/PreferencesContext';
|
|
import { ParametersIndexOffsets } from '../EventsFunctionsExtensionsLoader';
|
|
import { sendEventsExtractedAsFunction } from '../Utils/Analytics/EventSender';
|
|
import { ToolbarGroup } from '../UI/Toolbar';
|
|
import IconButton from '../UI/IconButton';
|
|
import ExtensionEditIcon from '../UI/CustomSvgIcons/ExtensionEdit';
|
|
import Tune from '../UI/CustomSvgIcons/Tune';
|
|
import Mark from '../UI/CustomSvgIcons/Mark';
|
|
import newNameGenerator from '../Utils/NewNameGenerator';
|
|
import { ProjectScopedContainersAccessor } from '../InstructionOrExpression/EventsScope.flow';
|
|
import GlobalAndSceneVariablesDialog from '../VariablesList/GlobalAndSceneVariablesDialog';
|
|
|
|
const gd: libGDevelop = global.gd;
|
|
|
|
export type ExtensionItemConfigurationAttribute =
|
|
| 'type'
|
|
| 'isPrivate'
|
|
| 'isAsync';
|
|
|
|
type Props = {|
|
|
project: gdProject,
|
|
eventsFunctionsExtension: gdEventsFunctionsExtension,
|
|
setToolbar: (?React.Node) => void,
|
|
resourceManagementProps: ResourceManagementProps,
|
|
openInstructionOrExpression: (
|
|
extension: gdPlatformExtension,
|
|
type: string
|
|
) => void,
|
|
onCreateEventsFunction: (
|
|
extensionName: string,
|
|
eventsFunction: gdEventsFunction,
|
|
editorIdentifier:
|
|
| 'scene-events-editor'
|
|
| 'extension-events-editor'
|
|
| 'external-events-editor'
|
|
) => void,
|
|
onBehaviorEdited?: () => void,
|
|
onObjectEdited?: () => void,
|
|
onFunctionEdited?: () => void,
|
|
initiallyFocusedFunctionName: ?string,
|
|
initiallyFocusedBehaviorName: ?string,
|
|
initiallyFocusedObjectName: ?string,
|
|
unsavedChanges?: ?UnsavedChanges,
|
|
onOpenCustomObjectEditor: gdEventsBasedObject => void,
|
|
|};
|
|
|
|
type State = {|
|
|
selectedEventsFunction: ?gdEventsFunction,
|
|
selectedEventsBasedBehavior: ?gdEventsBasedBehavior,
|
|
editedEventsBasedBehavior: ?gdEventsBasedBehavior,
|
|
selectedEventsBasedObject: ?gdEventsBasedObject,
|
|
editedEventsBasedObject: ?gdEventsBasedObject,
|
|
editOptionsDialogOpen: boolean,
|
|
behaviorMethodSelectorDialogOpen: boolean,
|
|
objectMethodSelectorDialogOpen: boolean,
|
|
extensionFunctionSelectorDialogOpen: boolean,
|
|
eventsBasedObjectSelectorDialogOpen: boolean,
|
|
variablesEditorOpen: { isGlobalTabInitiallyOpen: boolean } | null,
|
|
onAddEventsFunctionCb: ?(
|
|
parameters: ?EventsFunctionCreationParameters
|
|
) => void,
|
|
onAddEventsBasedObjectCb: ?(
|
|
parameters: ?EventsBasedObjectCreationParameters
|
|
) => void,
|
|
|};
|
|
|
|
const extensionEditIconReactNode = <ExtensionEditIcon />;
|
|
|
|
// The event based object editor is hidden in releases
|
|
// because it's not handled by GDJS.
|
|
const getInitialMosaicEditorNodes = () => ({
|
|
direction: 'row',
|
|
first: 'functions-list',
|
|
second: {
|
|
direction: 'column',
|
|
first: 'parameters',
|
|
second: 'events-sheet',
|
|
splitPercentage: 25,
|
|
},
|
|
splitPercentage: 25,
|
|
});
|
|
|
|
export default class EventsFunctionsExtensionEditor extends React.Component<
|
|
Props,
|
|
State
|
|
> {
|
|
state = {
|
|
selectedEventsFunction: null,
|
|
selectedEventsBasedBehavior: null,
|
|
editedEventsBasedBehavior: null,
|
|
selectedEventsBasedObject: null,
|
|
editedEventsBasedObject: null,
|
|
editOptionsDialogOpen: false,
|
|
behaviorMethodSelectorDialogOpen: false,
|
|
objectMethodSelectorDialogOpen: false,
|
|
extensionFunctionSelectorDialogOpen: false,
|
|
eventsBasedObjectSelectorDialogOpen: false,
|
|
variablesEditorOpen: null,
|
|
onAddEventsFunctionCb: null,
|
|
onAddEventsBasedObjectCb: null,
|
|
};
|
|
editor: ?EventsSheetInterface;
|
|
eventsFunctionList: ?EventsFunctionsListInterface;
|
|
_editorMosaic: ?EditorMosaicInterface;
|
|
_editorNavigator: ?EditorNavigatorInterface;
|
|
// Create an empty "context" of objects.
|
|
// Avoid recreating containers if they were already created, so that
|
|
// we keep the same objects in memory and avoid remounting components
|
|
// (like ObjectGroupsList) because objects "ptr" changed.
|
|
_globalObjectsContainer: gdObjectsContainer = new gd.ObjectsContainer();
|
|
_objectsContainer: gdObjectsContainer = new gd.ObjectsContainer();
|
|
_projectScopedContainersAccessor: ProjectScopedContainersAccessor | null = null;
|
|
|
|
componentDidMount() {
|
|
if (this.props.initiallyFocusedFunctionName) {
|
|
this.selectEventsFunctionByName(
|
|
this.props.initiallyFocusedFunctionName,
|
|
this.props.initiallyFocusedBehaviorName,
|
|
this.props.initiallyFocusedObjectName
|
|
);
|
|
} else if (this.props.initiallyFocusedBehaviorName) {
|
|
this.selectEventsBasedBehaviorByName(
|
|
this.props.initiallyFocusedBehaviorName
|
|
);
|
|
} else if (this.props.initiallyFocusedObjectName) {
|
|
this.selectEventsBasedObjectByName(this.props.initiallyFocusedObjectName);
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
if (this._globalObjectsContainer) this._globalObjectsContainer.delete();
|
|
if (this._objectsContainer) this._objectsContainer.delete();
|
|
}
|
|
|
|
_updateProjectScopedContainer = () => {
|
|
this._updateProjectScopedContainerFrom({
|
|
eventsFunction: this.state.selectedEventsFunction,
|
|
eventsBasedBehavior: this.state.selectedEventsBasedBehavior,
|
|
eventsBasedObject: this.state.selectedEventsBasedObject,
|
|
});
|
|
};
|
|
|
|
_updateProjectScopedContainerFrom = ({
|
|
eventsBasedBehavior,
|
|
eventsBasedObject,
|
|
eventsFunction,
|
|
}: {|
|
|
eventsBasedBehavior?: ?gdEventsBasedBehavior,
|
|
eventsBasedObject?: ?gdEventsBasedObject,
|
|
eventsFunction?: ?gdEventsFunction,
|
|
|}) => {
|
|
const scope = {
|
|
project: this.props.project,
|
|
layout: null,
|
|
externalEvents: null,
|
|
eventsFunctionsExtension: this.props.eventsFunctionsExtension,
|
|
eventsBasedBehavior,
|
|
eventsBasedObject,
|
|
eventsFunction,
|
|
};
|
|
this._projectScopedContainersAccessor = new ProjectScopedContainersAccessor(
|
|
scope,
|
|
this._objectsContainer
|
|
);
|
|
};
|
|
|
|
updateToolbar = () => {
|
|
if (this.editor) {
|
|
// If the scene editor is open, let it handle the toolbar.
|
|
this.editor.updateToolbar();
|
|
} else {
|
|
// Otherwise, show the extension settings buttons.
|
|
this.props.setToolbar(
|
|
<ToolbarGroup lastChild>
|
|
<IconButton
|
|
size="small"
|
|
color="default"
|
|
onClick={this._editOptions}
|
|
tooltip={t`Open extension settings`}
|
|
>
|
|
<ExtensionEditIcon />
|
|
</IconButton>
|
|
</ToolbarGroup>
|
|
);
|
|
}
|
|
};
|
|
|
|
selectEventsFunctionByName = (
|
|
functionName: string,
|
|
behaviorName: ?string,
|
|
objectName: ?string
|
|
) => {
|
|
const { eventsFunctionsExtension } = this.props;
|
|
|
|
if (behaviorName) {
|
|
// Behavior function
|
|
const eventsBasedBehaviors = eventsFunctionsExtension.getEventsBasedBehaviors();
|
|
if (eventsBasedBehaviors.has(behaviorName)) {
|
|
const eventsBasedBehavior = eventsBasedBehaviors.get(behaviorName);
|
|
const behaviorEventsFunctions = eventsBasedBehavior.getEventsFunctions();
|
|
if (behaviorEventsFunctions.hasEventsFunctionNamed(functionName)) {
|
|
this._selectEventsFunction(
|
|
behaviorEventsFunctions.getEventsFunction(functionName),
|
|
eventsBasedBehavior,
|
|
null
|
|
);
|
|
}
|
|
}
|
|
} else if (objectName) {
|
|
const eventsBasedObjects = eventsFunctionsExtension.getEventsBasedObjects();
|
|
if (eventsBasedObjects.has(objectName)) {
|
|
const eventsBasedObject = eventsBasedObjects.get(objectName);
|
|
const eventsFunctions = eventsBasedObject.getEventsFunctions();
|
|
if (eventsFunctions.hasEventsFunctionNamed(functionName)) {
|
|
this._selectEventsFunction(
|
|
eventsFunctions.getEventsFunction(functionName),
|
|
null,
|
|
eventsBasedObject
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
// Free function
|
|
if (eventsFunctionsExtension.hasEventsFunctionNamed(functionName)) {
|
|
this._selectEventsFunction(
|
|
eventsFunctionsExtension.getEventsFunction(functionName),
|
|
null,
|
|
null
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
_selectEventsFunction = (
|
|
selectedEventsFunction: ?gdEventsFunction,
|
|
selectedEventsBasedBehavior: ?gdEventsBasedBehavior,
|
|
selectedEventsBasedObject: ?gdEventsBasedObject
|
|
) => {
|
|
this.onSelectionChanged(null, null);
|
|
if (!selectedEventsFunction) {
|
|
this.setState(
|
|
{
|
|
selectedEventsFunction: null,
|
|
selectedEventsBasedBehavior,
|
|
selectedEventsBasedObject,
|
|
},
|
|
() => this.updateToolbar()
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Users may have change a function declaration.
|
|
// Reload metadata just in case.
|
|
if (this.props.onFunctionEdited) {
|
|
this.props.onFunctionEdited();
|
|
}
|
|
|
|
this._updateProjectScopedContainerFrom({
|
|
eventsFunction: selectedEventsFunction,
|
|
eventsBasedBehavior: selectedEventsBasedBehavior,
|
|
eventsBasedObject: selectedEventsBasedObject,
|
|
});
|
|
this.setState(
|
|
{
|
|
selectedEventsFunction,
|
|
selectedEventsBasedBehavior,
|
|
selectedEventsBasedObject,
|
|
},
|
|
() => {
|
|
this.updateToolbar();
|
|
|
|
if (this._editorMosaic) {
|
|
this._editorMosaic.uncollapseEditor('parameters', 25);
|
|
}
|
|
if (this._editorNavigator) {
|
|
// Open the parameters of the function if it's a new, empty function.
|
|
if (
|
|
selectedEventsFunction &&
|
|
!selectedEventsFunction.getEvents().getEventsCount()
|
|
) {
|
|
this._editorNavigator.openEditor('parameters');
|
|
} else {
|
|
this._editorNavigator.openEditor('events-sheet');
|
|
}
|
|
}
|
|
}
|
|
);
|
|
};
|
|
|
|
_makeRenameEventsFunction = (i18n: I18nType) => (
|
|
eventsBasedBehavior: ?gdEventsBasedBehavior,
|
|
eventsBasedObject: ?gdEventsBasedObject,
|
|
eventsFunction: gdEventsFunction,
|
|
newName: string,
|
|
done: boolean => void
|
|
) => {
|
|
if (eventsBasedBehavior) {
|
|
this._renameBehaviorEventsFunction(
|
|
i18n,
|
|
eventsBasedBehavior,
|
|
eventsFunction,
|
|
newName,
|
|
done
|
|
);
|
|
} else if (eventsBasedObject) {
|
|
this._renameObjectEventsFunction(
|
|
i18n,
|
|
eventsBasedObject,
|
|
eventsFunction,
|
|
newName,
|
|
done
|
|
);
|
|
} else {
|
|
this._renameFreeEventsFunction(i18n, eventsFunction, newName, done);
|
|
}
|
|
};
|
|
|
|
_renameFreeEventsFunction = (
|
|
i18n: I18nType,
|
|
eventsFunction: gdEventsFunction,
|
|
newName: string,
|
|
done: boolean => void
|
|
) => {
|
|
const { project, eventsFunctionsExtension } = this.props;
|
|
|
|
const safeAndUniqueNewName = newNameGenerator(
|
|
gd.Project.getSafeName(newName),
|
|
tentativeNewName => {
|
|
if (
|
|
gd.MetadataDeclarationHelper.isExtensionLifecycleEventsFunction(
|
|
tentativeNewName
|
|
) ||
|
|
eventsFunctionsExtension.hasEventsFunctionNamed(tentativeNewName)
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
);
|
|
|
|
gd.WholeProjectRefactorer.renameEventsFunction(
|
|
project,
|
|
eventsFunctionsExtension,
|
|
eventsFunction.getName(),
|
|
safeAndUniqueNewName
|
|
);
|
|
eventsFunction.setName(safeAndUniqueNewName);
|
|
|
|
done(true);
|
|
if (this.props.onFunctionEdited) {
|
|
this.props.onFunctionEdited();
|
|
}
|
|
};
|
|
|
|
_renameBehaviorEventsFunction = (
|
|
i18n: I18nType,
|
|
eventsBasedBehavior: gdEventsBasedBehavior,
|
|
eventsFunction: gdEventsFunction,
|
|
newName: string,
|
|
done: boolean => void
|
|
) => {
|
|
const safeAndUniqueNewName = newNameGenerator(
|
|
gd.Project.getSafeName(newName),
|
|
tentativeNewName => {
|
|
if (
|
|
gd.MetadataDeclarationHelper.isBehaviorLifecycleEventsFunction(
|
|
tentativeNewName
|
|
) ||
|
|
eventsBasedBehavior
|
|
.getEventsFunctions()
|
|
.hasEventsFunctionNamed(tentativeNewName)
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
);
|
|
|
|
const { project, eventsFunctionsExtension } = this.props;
|
|
gd.WholeProjectRefactorer.renameBehaviorEventsFunction(
|
|
project,
|
|
eventsFunctionsExtension,
|
|
eventsBasedBehavior,
|
|
eventsFunction.getName(),
|
|
safeAndUniqueNewName
|
|
);
|
|
eventsFunction.setName(safeAndUniqueNewName);
|
|
|
|
done(true);
|
|
if (this.props.onFunctionEdited) {
|
|
this.props.onFunctionEdited();
|
|
}
|
|
};
|
|
|
|
_renameObjectEventsFunction = (
|
|
i18n: I18nType,
|
|
eventsBasedObject: gdEventsBasedObject,
|
|
eventsFunction: gdEventsFunction,
|
|
newName: string,
|
|
done: boolean => void
|
|
) => {
|
|
const safeAndUniqueNewName = newNameGenerator(
|
|
gd.Project.getSafeName(newName),
|
|
tentativeNewName => {
|
|
if (
|
|
gd.MetadataDeclarationHelper.isObjectLifecycleEventsFunction(
|
|
tentativeNewName
|
|
) ||
|
|
eventsBasedObject
|
|
.getEventsFunctions()
|
|
.hasEventsFunctionNamed(tentativeNewName)
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
);
|
|
|
|
const { project, eventsFunctionsExtension } = this.props;
|
|
gd.WholeProjectRefactorer.renameObjectEventsFunction(
|
|
project,
|
|
eventsFunctionsExtension,
|
|
eventsBasedObject,
|
|
eventsFunction.getName(),
|
|
safeAndUniqueNewName
|
|
);
|
|
eventsFunction.setName(safeAndUniqueNewName);
|
|
|
|
done(true);
|
|
if (this.props.onFunctionEdited) {
|
|
this.props.onFunctionEdited();
|
|
}
|
|
};
|
|
|
|
_makeMoveFreeEventsParameter = (i18n: I18nType) => (
|
|
eventsFunction: gdEventsFunction,
|
|
oldIndex: number,
|
|
newIndex: number,
|
|
done: boolean => void
|
|
) => {
|
|
// Don't ask for user confirmation as this change is easy to revert.
|
|
|
|
const { project, eventsFunctionsExtension } = this.props;
|
|
gd.WholeProjectRefactorer.moveEventsFunctionParameter(
|
|
project,
|
|
eventsFunctionsExtension,
|
|
eventsFunction.getName(),
|
|
oldIndex + ParametersIndexOffsets.FreeFunction,
|
|
newIndex + ParametersIndexOffsets.FreeFunction
|
|
);
|
|
|
|
done(true);
|
|
};
|
|
|
|
_makeMoveBehaviorEventsParameter = (i18n: I18nType) => (
|
|
eventsBasedBehavior: gdEventsBasedBehavior,
|
|
eventsFunction: gdEventsFunction,
|
|
oldIndex: number,
|
|
newIndex: number,
|
|
done: boolean => void
|
|
) => {
|
|
// Don't ask for user confirmation as this change is easy to revert.
|
|
|
|
const { project, eventsFunctionsExtension } = this.props;
|
|
gd.WholeProjectRefactorer.moveBehaviorEventsFunctionParameter(
|
|
project,
|
|
eventsFunctionsExtension,
|
|
eventsBasedBehavior,
|
|
eventsFunction.getName(),
|
|
oldIndex,
|
|
newIndex
|
|
);
|
|
|
|
done(true);
|
|
};
|
|
|
|
_makeMoveObjectEventsParameter = (i18n: I18nType) => (
|
|
eventsBasedObject: gdEventsBasedObject,
|
|
eventsFunction: gdEventsFunction,
|
|
oldIndex: number,
|
|
newIndex: number,
|
|
done: boolean => void
|
|
) => {
|
|
// Don't ask for user confirmation as this change is easy to revert.
|
|
|
|
const { project, eventsFunctionsExtension } = this.props;
|
|
gd.WholeProjectRefactorer.moveObjectEventsFunctionParameter(
|
|
project,
|
|
eventsFunctionsExtension,
|
|
eventsBasedObject,
|
|
eventsFunction.getName(),
|
|
oldIndex,
|
|
newIndex
|
|
);
|
|
|
|
done(true);
|
|
};
|
|
|
|
_onDeleteEventsFunction = (
|
|
eventsFunction: gdEventsFunction,
|
|
cb: boolean => void
|
|
) => {
|
|
if (
|
|
this.state.selectedEventsFunction &&
|
|
gd.compare(eventsFunction, this.state.selectedEventsFunction)
|
|
) {
|
|
this._selectEventsFunction(null, null, null);
|
|
}
|
|
|
|
cb(true);
|
|
};
|
|
|
|
selectEventsBasedBehaviorByName = (behaviorName: string) => {
|
|
const { eventsFunctionsExtension } = this.props;
|
|
const eventsBasedBehaviorsList = eventsFunctionsExtension.getEventsBasedBehaviors();
|
|
if (eventsBasedBehaviorsList.has(behaviorName)) {
|
|
this._selectEventsBasedBehavior(
|
|
eventsBasedBehaviorsList.get(behaviorName)
|
|
);
|
|
}
|
|
};
|
|
|
|
selectEventsBasedObjectByName = (eventBasedObjectName: string) => {
|
|
const { eventsFunctionsExtension } = this.props;
|
|
const eventsBasedObjectsList = eventsFunctionsExtension.getEventsBasedObjects();
|
|
if (eventsBasedObjectsList.has(eventBasedObjectName)) {
|
|
this._selectEventsBasedObject(
|
|
eventsBasedObjectsList.get(eventBasedObjectName)
|
|
);
|
|
}
|
|
};
|
|
|
|
onSelectionChanged = (
|
|
selectedEventsBasedBehavior: ?gdEventsBasedBehavior,
|
|
selectedEventsBasedObject: ?gdEventsBasedObject
|
|
) => {
|
|
this._editBehavior(selectedEventsBasedBehavior);
|
|
this._editObject(selectedEventsBasedObject);
|
|
};
|
|
|
|
_selectEventsBasedBehavior = (
|
|
selectedEventsBasedBehavior: ?gdEventsBasedBehavior
|
|
) => {
|
|
this.onSelectionChanged(selectedEventsBasedBehavior, null);
|
|
this._updateProjectScopedContainerFrom({
|
|
eventsBasedBehavior: selectedEventsBasedBehavior,
|
|
});
|
|
this.setState(
|
|
{
|
|
selectedEventsBasedBehavior,
|
|
selectedEventsFunction: null,
|
|
selectedEventsBasedObject: null,
|
|
},
|
|
() => {
|
|
this.updateToolbar();
|
|
if (selectedEventsBasedBehavior) {
|
|
if (this._editorMosaic) {
|
|
this._editorMosaic.collapseEditor('parameters');
|
|
}
|
|
if (this._editorNavigator) {
|
|
this._editorNavigator.openEditor('events-sheet');
|
|
}
|
|
}
|
|
}
|
|
);
|
|
};
|
|
|
|
_selectEventsBasedObject = (
|
|
selectedEventsBasedObject: ?gdEventsBasedObject
|
|
) => {
|
|
this.onSelectionChanged(null, selectedEventsBasedObject);
|
|
this._updateProjectScopedContainerFrom({
|
|
eventsBasedObject: selectedEventsBasedObject,
|
|
});
|
|
this.setState(
|
|
{
|
|
selectedEventsBasedObject,
|
|
selectedEventsFunction: null,
|
|
selectedEventsBasedBehavior: null,
|
|
},
|
|
() => {
|
|
this.updateToolbar();
|
|
if (selectedEventsBasedObject) {
|
|
if (this._editorMosaic) {
|
|
this._editorMosaic.collapseEditor('parameters');
|
|
}
|
|
if (this._editorNavigator)
|
|
this._editorNavigator.openEditor('events-sheet');
|
|
}
|
|
}
|
|
);
|
|
};
|
|
|
|
_makeRenameEventsBasedBehavior = (i18n: I18nType) => (
|
|
eventsBasedBehavior: gdEventsBasedBehavior,
|
|
newName: string,
|
|
done: boolean => void
|
|
) => {
|
|
const { project, eventsFunctionsExtension } = this.props;
|
|
const safeAndUniqueNewName = newNameGenerator(
|
|
gd.Project.getSafeName(newName),
|
|
tentativeNewName => {
|
|
if (
|
|
eventsFunctionsExtension
|
|
.getEventsBasedBehaviors()
|
|
.has(tentativeNewName)
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
);
|
|
|
|
gd.WholeProjectRefactorer.renameEventsBasedBehavior(
|
|
project,
|
|
eventsFunctionsExtension,
|
|
eventsBasedBehavior.getName(),
|
|
safeAndUniqueNewName
|
|
);
|
|
eventsBasedBehavior.setName(safeAndUniqueNewName);
|
|
|
|
done(true);
|
|
};
|
|
|
|
_makeRenameEventsBasedObject = (i18n: I18nType) => (
|
|
eventsBasedObject: gdEventsBasedObject,
|
|
newName: string,
|
|
done: boolean => void
|
|
) => {
|
|
const { project, eventsFunctionsExtension } = this.props;
|
|
const safeAndUniqueNewName = newNameGenerator(
|
|
gd.Project.getSafeName(newName),
|
|
tentativeNewName => {
|
|
if (
|
|
eventsFunctionsExtension.getEventsBasedObjects().has(tentativeNewName)
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
);
|
|
|
|
gd.WholeProjectRefactorer.renameEventsBasedObject(
|
|
project,
|
|
eventsFunctionsExtension,
|
|
eventsBasedObject.getName(),
|
|
safeAndUniqueNewName
|
|
);
|
|
eventsBasedObject.setName(safeAndUniqueNewName);
|
|
|
|
done(true);
|
|
};
|
|
|
|
_onEventsBasedBehaviorPasted = (
|
|
eventsBasedBehavior: gdEventsBasedBehavior,
|
|
sourceExtensionName: string
|
|
) => {
|
|
const { project, eventsFunctionsExtension } = this.props;
|
|
if (eventsFunctionsExtension.getName() === sourceExtensionName) {
|
|
return;
|
|
}
|
|
gd.WholeProjectRefactorer.updateExtensionNameInEventsBasedBehavior(
|
|
project,
|
|
eventsFunctionsExtension,
|
|
eventsBasedBehavior,
|
|
sourceExtensionName
|
|
);
|
|
};
|
|
|
|
_onEventsBasedBehaviorRenamed = () => {
|
|
// Name of a behavior changed, so notify parent
|
|
// that a behavior was edited (to trigger reload of extensions)
|
|
if (this.props.onBehaviorEdited) {
|
|
this.props.onBehaviorEdited();
|
|
}
|
|
|
|
// Reload the selected events function, if any, as the behavior was
|
|
// changed so objects containers need to be re-created (otherwise,
|
|
// objects from objects containers will still refer to the old behavior name,
|
|
// done before the call to gd.WholeProjectRefactorer.renameEventsBasedBehavior).
|
|
if (this.state.selectedEventsFunction) {
|
|
this._updateProjectScopedContainer();
|
|
}
|
|
};
|
|
|
|
_onEventsBasedObjectRenamed = () => {
|
|
// Name of an object changed, so notify parent
|
|
// that an object was edited (to trigger reload of extensions)
|
|
if (this.props.onObjectEdited) {
|
|
this.props.onObjectEdited();
|
|
}
|
|
|
|
// Reload the selected events function, if any, as the parent-object was
|
|
// changed so child-objects containers need to be re-created (otherwise,
|
|
// child-objects from child-objects containers will still refer to the old parent-object name,
|
|
// done before the call to gd.WholeProjectRefactorer.renameEventsBasedObject).
|
|
if (this.state.selectedEventsFunction) {
|
|
this._updateProjectScopedContainer();
|
|
}
|
|
};
|
|
|
|
_onDeleteEventsBasedBehavior = (
|
|
eventsBasedBehavior: gdEventsBasedBehavior,
|
|
cb: boolean => void
|
|
) => {
|
|
if (
|
|
this.state.selectedEventsBasedBehavior &&
|
|
gd.compare(eventsBasedBehavior, this.state.selectedEventsBasedBehavior)
|
|
) {
|
|
this._selectEventsBasedBehavior(null);
|
|
}
|
|
|
|
cb(true);
|
|
};
|
|
|
|
_onDeleteEventsBasedObject = (
|
|
eventsBasedObject: gdEventsBasedObject,
|
|
cb: boolean => void
|
|
) => {
|
|
if (
|
|
this.state.selectedEventsBasedObject &&
|
|
gd.compare(eventsBasedObject, this.state.selectedEventsBasedObject)
|
|
) {
|
|
this._selectEventsBasedObject(null);
|
|
}
|
|
|
|
cb(true);
|
|
};
|
|
|
|
_onCloseExtensionFunctionSelectorDialog = (
|
|
parameters: ?EventsFunctionCreationParameters
|
|
) => {
|
|
const { onAddEventsFunctionCb } = this.state;
|
|
this.setState(
|
|
{
|
|
extensionFunctionSelectorDialogOpen: false,
|
|
onAddEventsFunctionCb: null,
|
|
},
|
|
() => {
|
|
if (onAddEventsFunctionCb) onAddEventsFunctionCb(parameters);
|
|
}
|
|
);
|
|
};
|
|
|
|
_onCloseEventsBasedObjectSelectorDialog = (
|
|
parameters: ?EventsBasedObjectCreationParameters
|
|
) => {
|
|
const { onAddEventsBasedObjectCb } = this.state;
|
|
this.setState(
|
|
{
|
|
eventsBasedObjectSelectorDialogOpen: false,
|
|
onAddEventsBasedObjectCb: null,
|
|
},
|
|
() => {
|
|
if (onAddEventsBasedObjectCb) onAddEventsBasedObjectCb(parameters);
|
|
}
|
|
);
|
|
};
|
|
|
|
_onAddEventsBasedObject = (
|
|
onAddEventsBasedObjectCb: (
|
|
parameters: ?EventsBasedObjectCreationParameters
|
|
) => void
|
|
) => {
|
|
this.setState({
|
|
eventsBasedObjectSelectorDialogOpen: true,
|
|
onAddEventsBasedObjectCb,
|
|
});
|
|
};
|
|
|
|
_onAddEventsFunction = (
|
|
eventsBasedBehavior: ?gdEventsBasedBehavior,
|
|
eventsBasedObject: ?gdEventsBasedObject,
|
|
onAddEventsFunctionCb: (
|
|
parameters: ?EventsFunctionCreationParameters
|
|
) => void
|
|
) => {
|
|
if (eventsBasedBehavior) {
|
|
this._onAddBehaviorEventsFunction(
|
|
eventsBasedBehavior,
|
|
onAddEventsFunctionCb
|
|
);
|
|
} else if (eventsBasedObject) {
|
|
this._onAddObjectEventsFunction(eventsBasedObject, onAddEventsFunctionCb);
|
|
} else {
|
|
this._onAddFreeEventsFunction(onAddEventsFunctionCb);
|
|
}
|
|
};
|
|
|
|
_onAddFreeEventsFunction = (
|
|
onAddEventsFunctionCb: (
|
|
parameters: ?EventsFunctionCreationParameters
|
|
) => void
|
|
) => {
|
|
this.setState({
|
|
extensionFunctionSelectorDialogOpen: true,
|
|
onAddEventsFunctionCb,
|
|
});
|
|
};
|
|
|
|
_onAddBehaviorEventsFunction = (
|
|
eventsBasedBehavior: gdEventsBasedBehavior,
|
|
onAddEventsFunctionCb: (
|
|
parameters: ?EventsFunctionCreationParameters
|
|
) => void
|
|
) => {
|
|
this.setState({
|
|
behaviorMethodSelectorDialogOpen: true,
|
|
onAddEventsFunctionCb: parameters => {
|
|
onAddEventsFunctionCb(parameters);
|
|
this._onBehaviorEventsFunctionAdded(eventsBasedBehavior);
|
|
},
|
|
});
|
|
};
|
|
|
|
_onAddObjectEventsFunction = (
|
|
eventsBasedObject: gdEventsBasedObject,
|
|
onAddEventsFunctionCb: (
|
|
parameters: ?EventsFunctionCreationParameters
|
|
) => void
|
|
) => {
|
|
this.setState({
|
|
objectMethodSelectorDialogOpen: true,
|
|
onAddEventsFunctionCb: parameters => {
|
|
onAddEventsFunctionCb(parameters);
|
|
this._onObjectEventsFunctionAdded(eventsBasedObject);
|
|
},
|
|
});
|
|
};
|
|
|
|
_onCloseBehaviorMethodSelectorDialog = (
|
|
parameters: ?EventsFunctionCreationParameters
|
|
) => {
|
|
const { onAddEventsFunctionCb } = this.state;
|
|
this.setState(
|
|
{
|
|
behaviorMethodSelectorDialogOpen: false,
|
|
onAddEventsFunctionCb: null,
|
|
},
|
|
() => {
|
|
if (onAddEventsFunctionCb) onAddEventsFunctionCb(parameters);
|
|
}
|
|
);
|
|
};
|
|
|
|
_onCloseObjectMethodSelectorDialog = (
|
|
parameters: ?EventsFunctionCreationParameters
|
|
) => {
|
|
const { onAddEventsFunctionCb } = this.state;
|
|
this.setState(
|
|
{
|
|
objectMethodSelectorDialogOpen: false,
|
|
onAddEventsFunctionCb: null,
|
|
},
|
|
() => {
|
|
if (onAddEventsFunctionCb) onAddEventsFunctionCb(parameters);
|
|
}
|
|
);
|
|
};
|
|
|
|
_onEventsFunctionAdded = (
|
|
selectedEventsFunction: gdEventsFunction,
|
|
eventsBasedBehavior: ?gdEventsBasedBehavior,
|
|
eventsBasedObject: ?gdEventsBasedObject
|
|
) => {
|
|
if (eventsBasedBehavior) {
|
|
this._onBehaviorEventsFunctionAdded(eventsBasedBehavior);
|
|
} else if (eventsBasedObject) {
|
|
this._onObjectEventsFunctionAdded(eventsBasedObject);
|
|
}
|
|
};
|
|
|
|
_onBehaviorEventsFunctionAdded = (
|
|
eventsBasedBehavior: gdEventsBasedBehavior
|
|
) => {
|
|
// This will create the mandatory parameters for the newly added function.
|
|
gd.WholeProjectRefactorer.ensureBehaviorEventsFunctionsProperParameters(
|
|
this.props.eventsFunctionsExtension,
|
|
eventsBasedBehavior
|
|
);
|
|
};
|
|
|
|
_onObjectEventsFunctionAdded = (eventsBasedObject: gdEventsBasedObject) => {
|
|
// This will create the mandatory parameters for the newly added function.
|
|
gd.WholeProjectRefactorer.ensureObjectEventsFunctionsProperParameters(
|
|
this.props.eventsFunctionsExtension,
|
|
eventsBasedObject
|
|
);
|
|
};
|
|
|
|
_onBehaviorPropertyRenamed = (
|
|
eventsBasedBehavior: gdEventsBasedBehavior,
|
|
oldName: string,
|
|
newName: string
|
|
) => {
|
|
const { project, eventsFunctionsExtension } = this.props;
|
|
gd.WholeProjectRefactorer.renameEventsBasedBehaviorProperty(
|
|
project,
|
|
eventsFunctionsExtension,
|
|
eventsBasedBehavior,
|
|
oldName,
|
|
newName
|
|
);
|
|
};
|
|
|
|
_onBehaviorSharedPropertyRenamed = (
|
|
eventsBasedBehavior: gdEventsBasedBehavior,
|
|
oldName: string,
|
|
newName: string
|
|
) => {
|
|
const { project, eventsFunctionsExtension } = this.props;
|
|
gd.WholeProjectRefactorer.renameEventsBasedBehaviorSharedProperty(
|
|
project,
|
|
eventsFunctionsExtension,
|
|
eventsBasedBehavior,
|
|
oldName,
|
|
newName
|
|
);
|
|
};
|
|
|
|
_onObjectPropertyRenamed = (
|
|
eventsBasedObject: gdEventsBasedObject,
|
|
oldName: string,
|
|
newName: string
|
|
) => {
|
|
const { project, eventsFunctionsExtension } = this.props;
|
|
gd.WholeProjectRefactorer.renameEventsBasedObjectProperty(
|
|
project,
|
|
eventsFunctionsExtension,
|
|
eventsBasedObject,
|
|
oldName,
|
|
newName
|
|
);
|
|
};
|
|
|
|
_editOptions = (open: boolean = true) => {
|
|
this.setState({
|
|
editOptionsDialogOpen: open,
|
|
});
|
|
};
|
|
|
|
_editVariables = (
|
|
options: { isGlobalTabInitiallyOpen: boolean } | null = {
|
|
isGlobalTabInitiallyOpen: false,
|
|
}
|
|
) => {
|
|
this.setState({
|
|
variablesEditorOpen: options,
|
|
});
|
|
};
|
|
|
|
_editBehavior = (editedEventsBasedBehavior: ?gdEventsBasedBehavior) => {
|
|
this.setState(
|
|
state => {
|
|
// If we're closing the properties of a behavior, ensure parameters
|
|
// are up-to-date in all event functions of the behavior (the object
|
|
// type might have changed).
|
|
if (state.editedEventsBasedBehavior && !editedEventsBasedBehavior) {
|
|
gd.WholeProjectRefactorer.ensureBehaviorEventsFunctionsProperParameters(
|
|
this.props.eventsFunctionsExtension,
|
|
state.editedEventsBasedBehavior
|
|
);
|
|
}
|
|
|
|
return {
|
|
editedEventsBasedBehavior,
|
|
};
|
|
},
|
|
async () => {
|
|
// TODO: Is this logic the same as in _onEventsBasedBehaviorRenamed?
|
|
|
|
if (!editedEventsBasedBehavior) {
|
|
// If we're closing the properties of a behavior, notify parent
|
|
// that a behavior was edited (to trigger reload of extensions)
|
|
if (this.props.onBehaviorEdited) {
|
|
await this.props.onBehaviorEdited();
|
|
|
|
// Once extensions are reloaded, ensure the project stays valid by
|
|
// filling any invalid required behavior property in the objects
|
|
// of the project.
|
|
//
|
|
// We need to do that as "required behavior" properties may have been
|
|
// added (or the type of the required behavior changed) in the dialog.
|
|
gd.WholeProjectRefactorer.fixInvalidRequiredBehaviorProperties(
|
|
this.props.project
|
|
);
|
|
}
|
|
|
|
// Reload the selected events function, if any, as the behavior was
|
|
// changed so objects containers need to be re-created. Notably, the
|
|
// type of the object that is handled by the behavior may have changed.
|
|
if (this.state.selectedEventsFunction) {
|
|
this._updateProjectScopedContainer();
|
|
}
|
|
}
|
|
}
|
|
);
|
|
};
|
|
|
|
_editObject = (editedEventsBasedObject: ?gdEventsBasedObject) => {
|
|
this.setState(
|
|
state => {
|
|
// If we're closing the properties of an object, ensure parameters
|
|
// are up-to-date in all event functions of the object.
|
|
if (state.editedEventsBasedObject && !editedEventsBasedObject) {
|
|
gd.WholeProjectRefactorer.ensureObjectEventsFunctionsProperParameters(
|
|
this.props.eventsFunctionsExtension,
|
|
state.editedEventsBasedObject
|
|
);
|
|
}
|
|
|
|
return {
|
|
editedEventsBasedObject,
|
|
};
|
|
},
|
|
async () => {
|
|
// TODO: Is this logic the same as in _onEventsBasedObjectRenamed?
|
|
|
|
if (!editedEventsBasedObject) {
|
|
// If we're closing the properties of a object, notify parent
|
|
// that a object was edited (to trigger reload of extensions)
|
|
if (this.props.onObjectEdited) {
|
|
await this.props.onObjectEdited();
|
|
}
|
|
|
|
// Reload the selected events function, if any, as the object was
|
|
// changed so objects containers need to be re-created. Notably, the
|
|
// type of the object that is handled by the object may have changed.
|
|
if (this.state.selectedEventsFunction) {
|
|
this._updateProjectScopedContainer();
|
|
}
|
|
}
|
|
}
|
|
);
|
|
};
|
|
|
|
_onEditorNavigatorEditorChanged = (editorName: string) => {
|
|
// It's important that this method is the same across renders,
|
|
// to avoid confusing EditorNavigator into thinking it's changed
|
|
// and immediately calling it, which would trigger an infinite loop.
|
|
// Search for "callback-prevent-infinite-rerendering" in the codebase.
|
|
|
|
this.updateToolbar();
|
|
|
|
if (editorName === 'behaviors-list') {
|
|
this._selectEventsBasedBehavior(null);
|
|
} else if (
|
|
editorName === 'free-functions-list' ||
|
|
editorName === 'behavior-functions-list'
|
|
) {
|
|
this._selectEventsFunction(null, this.state.selectedEventsBasedBehavior);
|
|
}
|
|
};
|
|
|
|
_getFunctionGroupNames = (): Array<string> => {
|
|
const groupNames = new Set<string>();
|
|
// Look only in the edited function container because
|
|
// functions from the extension or different behaviors
|
|
// won't use the same groups names.
|
|
// An independent autocompletion is done for each of them.
|
|
const {
|
|
selectedEventsBasedBehavior,
|
|
selectedEventsBasedObject,
|
|
} = this.state;
|
|
if (selectedEventsBasedBehavior) {
|
|
const eventFunctionContainer = selectedEventsBasedBehavior.getEventsFunctions();
|
|
for (
|
|
let index = 0;
|
|
index < eventFunctionContainer.getEventsFunctionsCount();
|
|
index++
|
|
) {
|
|
const groupName = eventFunctionContainer
|
|
.getEventsFunctionAt(index)
|
|
.getGroup();
|
|
if (groupName) {
|
|
groupNames.add(groupName);
|
|
}
|
|
}
|
|
} else if (selectedEventsBasedObject) {
|
|
const eventFunctionContainer = selectedEventsBasedObject.getEventsFunctions();
|
|
for (
|
|
let index = 0;
|
|
index < eventFunctionContainer.getEventsFunctionsCount();
|
|
index++
|
|
) {
|
|
const groupName = eventFunctionContainer
|
|
.getEventsFunctionAt(index)
|
|
.getGroup();
|
|
if (groupName) {
|
|
groupNames.add(groupName);
|
|
}
|
|
}
|
|
} else {
|
|
const { eventsFunctionsExtension } = this.props;
|
|
for (
|
|
let index = 0;
|
|
index < eventsFunctionsExtension.getEventsFunctionsCount();
|
|
index++
|
|
) {
|
|
const groupName = eventsFunctionsExtension
|
|
.getEventsFunctionAt(index)
|
|
.getGroup();
|
|
if (groupName) {
|
|
groupNames.add(groupName);
|
|
}
|
|
}
|
|
}
|
|
return [...groupNames].sort((a, b) => a.localeCompare(b));
|
|
};
|
|
|
|
_onConfigurationUpdated = (
|
|
attribute: ?ExtensionItemConfigurationAttribute
|
|
) => {
|
|
if (
|
|
attribute === 'type' ||
|
|
attribute === 'isPrivate' ||
|
|
attribute === 'isAsync'
|
|
) {
|
|
// Force an update to ensure the icon of the edited function is updated.
|
|
this.forceUpdate();
|
|
}
|
|
|
|
// Do nothing otherwise to avoid costly and useless extra renders.
|
|
};
|
|
|
|
onBeginCreateEventsFunction = () => {
|
|
sendEventsExtractedAsFunction({
|
|
step: 'begin',
|
|
parentEditor: 'extension-events-editor',
|
|
});
|
|
};
|
|
|
|
onCreateEventsFunction = (
|
|
extensionName: string,
|
|
eventsFunction: gdEventsFunction
|
|
) => {
|
|
this.props.onCreateEventsFunction(
|
|
extensionName,
|
|
eventsFunction,
|
|
'extension-events-editor'
|
|
);
|
|
};
|
|
|
|
render() {
|
|
const { project, eventsFunctionsExtension } = this.props;
|
|
|
|
const {
|
|
selectedEventsFunction,
|
|
selectedEventsBasedBehavior,
|
|
selectedEventsBasedObject,
|
|
editOptionsDialogOpen,
|
|
behaviorMethodSelectorDialogOpen,
|
|
objectMethodSelectorDialogOpen,
|
|
extensionFunctionSelectorDialogOpen,
|
|
eventsBasedObjectSelectorDialogOpen,
|
|
variablesEditorOpen,
|
|
} = this.state;
|
|
|
|
const scope = {
|
|
project,
|
|
layout: null,
|
|
externalEvents: null,
|
|
eventsFunctionsExtension,
|
|
eventsBasedBehavior: selectedEventsBasedBehavior,
|
|
eventsBasedObject: selectedEventsBasedObject,
|
|
eventsFunction: selectedEventsFunction,
|
|
};
|
|
|
|
const selectedEventsBasedEntity =
|
|
selectedEventsBasedBehavior || selectedEventsBasedObject;
|
|
|
|
const editors = {
|
|
parameters: {
|
|
type: 'primary',
|
|
title: t`Function Configuration`,
|
|
toolbarControls: [],
|
|
renderEditor: () => (
|
|
<I18n>
|
|
{({ i18n }) => (
|
|
<Background maxWidth>
|
|
{selectedEventsFunction &&
|
|
this._objectsContainer &&
|
|
this._projectScopedContainersAccessor ? (
|
|
<EventsFunctionConfigurationEditor
|
|
project={project}
|
|
projectScopedContainersAccessor={
|
|
this._projectScopedContainersAccessor
|
|
}
|
|
eventsFunction={selectedEventsFunction}
|
|
eventsBasedBehavior={selectedEventsBasedBehavior}
|
|
eventsBasedObject={selectedEventsBasedObject}
|
|
eventsFunctionsContainer={
|
|
(selectedEventsBasedEntity &&
|
|
selectedEventsBasedEntity.getEventsFunctions()) ||
|
|
eventsFunctionsExtension
|
|
}
|
|
eventsFunctionsExtension={eventsFunctionsExtension}
|
|
objectsContainer={this._objectsContainer}
|
|
onConfigurationUpdated={this._onConfigurationUpdated}
|
|
helpPagePath={
|
|
selectedEventsBasedObject
|
|
? '/behaviors/events-based-objects'
|
|
: selectedEventsBasedBehavior
|
|
? '/behaviors/events-based-behaviors'
|
|
: '/events/functions'
|
|
}
|
|
onParametersOrGroupsUpdated={() => {
|
|
this._updateProjectScopedContainer();
|
|
this.forceUpdate();
|
|
}}
|
|
onMoveFreeEventsParameter={this._makeMoveFreeEventsParameter(
|
|
i18n
|
|
)}
|
|
onMoveBehaviorEventsParameter={this._makeMoveBehaviorEventsParameter(
|
|
i18n
|
|
)}
|
|
onMoveObjectEventsParameter={this._makeMoveObjectEventsParameter(
|
|
i18n
|
|
)}
|
|
unsavedChanges={this.props.unsavedChanges}
|
|
getFunctionGroupNames={this._getFunctionGroupNames}
|
|
/>
|
|
) : (
|
|
<EmptyMessage>
|
|
<Trans>
|
|
Choose a function, or a function of a behavior, to set the
|
|
parameters that it accepts.
|
|
</Trans>
|
|
</EmptyMessage>
|
|
)}
|
|
</Background>
|
|
)}
|
|
</I18n>
|
|
),
|
|
},
|
|
'events-sheet': {
|
|
type: 'primary',
|
|
noTitleBar:
|
|
!!selectedEventsFunction ||
|
|
(!selectedEventsBasedBehavior && !selectedEventsBasedObject),
|
|
noSoftKeyboardAvoidance: true,
|
|
title: selectedEventsBasedBehavior
|
|
? t`Behavior Configuration`
|
|
: selectedEventsBasedObject
|
|
? t`Object Configuration`
|
|
: null,
|
|
toolbarControls: [],
|
|
renderEditor: () =>
|
|
selectedEventsFunction &&
|
|
this._projectScopedContainersAccessor &&
|
|
this._globalObjectsContainer &&
|
|
this._objectsContainer ? (
|
|
<Background>
|
|
<EventsSheet
|
|
key={selectedEventsFunction.ptr}
|
|
ref={editor => (this.editor = editor)}
|
|
project={project}
|
|
scope={scope}
|
|
globalObjectsContainer={this._globalObjectsContainer}
|
|
objectsContainer={this._objectsContainer}
|
|
projectScopedContainersAccessor={
|
|
this._projectScopedContainersAccessor
|
|
}
|
|
events={selectedEventsFunction.getEvents()}
|
|
onOpenExternalEvents={() => {}}
|
|
onOpenLayout={() => {}}
|
|
resourceManagementProps={this.props.resourceManagementProps}
|
|
openInstructionOrExpression={
|
|
this.props.openInstructionOrExpression
|
|
}
|
|
setToolbar={this.props.setToolbar}
|
|
onBeginCreateEventsFunction={this.onBeginCreateEventsFunction}
|
|
onCreateEventsFunction={this.onCreateEventsFunction}
|
|
onOpenSettings={this._editOptions}
|
|
settingsIcon={extensionEditIconReactNode}
|
|
unsavedChanges={this.props.unsavedChanges}
|
|
isActive={true}
|
|
/>
|
|
</Background>
|
|
) : selectedEventsBasedBehavior ? (
|
|
<EventsBasedBehaviorEditorPanel
|
|
project={project}
|
|
eventsFunctionsExtension={eventsFunctionsExtension}
|
|
eventsBasedBehavior={selectedEventsBasedBehavior}
|
|
unsavedChanges={this.props.unsavedChanges}
|
|
onRenameProperty={(oldName, newName) =>
|
|
this._onBehaviorPropertyRenamed(
|
|
selectedEventsBasedBehavior,
|
|
oldName,
|
|
newName
|
|
)
|
|
}
|
|
onRenameSharedProperty={(oldName, newName) =>
|
|
this._onBehaviorSharedPropertyRenamed(
|
|
selectedEventsBasedBehavior,
|
|
oldName,
|
|
newName
|
|
)
|
|
}
|
|
onEventsFunctionsAdded={() => {
|
|
if (this.eventsFunctionList) {
|
|
this.eventsFunctionList.forceUpdateList();
|
|
}
|
|
}}
|
|
onConfigurationUpdated={this._onConfigurationUpdated}
|
|
/>
|
|
) : selectedEventsBasedObject &&
|
|
this._projectScopedContainersAccessor ? (
|
|
<EventsBasedObjectEditorPanel
|
|
project={project}
|
|
projectScopedContainersAccessor={
|
|
this._projectScopedContainersAccessor
|
|
}
|
|
eventsFunctionsExtension={eventsFunctionsExtension}
|
|
eventsBasedObject={selectedEventsBasedObject}
|
|
unsavedChanges={this.props.unsavedChanges}
|
|
onRenameProperty={(oldName, newName) =>
|
|
this._onObjectPropertyRenamed(
|
|
selectedEventsBasedObject,
|
|
oldName,
|
|
newName
|
|
)
|
|
}
|
|
onEventsFunctionsAdded={() => {
|
|
if (this.eventsFunctionList) {
|
|
this.eventsFunctionList.forceUpdateList();
|
|
}
|
|
}}
|
|
onOpenCustomObjectEditor={() =>
|
|
this.props.onOpenCustomObjectEditor(selectedEventsBasedObject)
|
|
}
|
|
/>
|
|
) : (
|
|
<Background>
|
|
<EmptyMessage>
|
|
<Trans>
|
|
Choose a function, or a function of a behavior, to edit its
|
|
events.
|
|
</Trans>
|
|
</EmptyMessage>
|
|
</Background>
|
|
),
|
|
},
|
|
'functions-list': {
|
|
type: 'primary',
|
|
title: t`Functions`,
|
|
toolbarControls: [],
|
|
renderEditor: () => (
|
|
<I18n>
|
|
{({ i18n }) => (
|
|
<EventsFunctionsListWithErrorBoundary
|
|
ref={eventsFunctionList =>
|
|
(this.eventsFunctionList = eventsFunctionList)
|
|
}
|
|
project={project}
|
|
eventsFunctionsExtension={eventsFunctionsExtension}
|
|
unsavedChanges={this.props.unsavedChanges}
|
|
forceUpdateEditor={() => this.forceUpdate()}
|
|
// Free functions
|
|
selectedEventsFunction={selectedEventsFunction}
|
|
onSelectEventsFunction={this._selectEventsFunction}
|
|
onDeleteEventsFunction={this._onDeleteEventsFunction}
|
|
onRenameEventsFunction={this._makeRenameEventsFunction(i18n)}
|
|
onAddEventsFunction={this._onAddEventsFunction}
|
|
onEventsFunctionAdded={this._onEventsFunctionAdded}
|
|
// Behaviors
|
|
selectedEventsBasedBehavior={selectedEventsBasedBehavior}
|
|
onSelectEventsBasedBehavior={this._selectEventsBasedBehavior}
|
|
onDeleteEventsBasedBehavior={this._onDeleteEventsBasedBehavior}
|
|
onRenameEventsBasedBehavior={this._makeRenameEventsBasedBehavior(
|
|
i18n
|
|
)}
|
|
onEventsBasedBehaviorRenamed={
|
|
this._onEventsBasedBehaviorRenamed
|
|
}
|
|
onEventsBasedBehaviorPasted={this._onEventsBasedBehaviorPasted}
|
|
// Objects
|
|
selectedEventsBasedObject={selectedEventsBasedObject}
|
|
onSelectEventsBasedObject={this._selectEventsBasedObject}
|
|
onDeleteEventsBasedObject={this._onDeleteEventsBasedObject}
|
|
onRenameEventsBasedObject={this._makeRenameEventsBasedObject(
|
|
i18n
|
|
)}
|
|
onEventsBasedObjectRenamed={this._onEventsBasedObjectRenamed}
|
|
onAddEventsBasedObject={this._onAddEventsBasedObject}
|
|
onSelectExtensionProperties={() => this._editOptions(true)}
|
|
onSelectExtensionGlobalVariables={() =>
|
|
this._editVariables({ isGlobalTabInitiallyOpen: true })
|
|
}
|
|
onSelectExtensionSceneVariables={() => this._editVariables()}
|
|
onOpenCustomObjectEditor={this.props.onOpenCustomObjectEditor}
|
|
/>
|
|
)}
|
|
</I18n>
|
|
),
|
|
},
|
|
};
|
|
|
|
return (
|
|
<React.Fragment>
|
|
<ResponsiveWindowMeasurer>
|
|
{({ isMobile }) =>
|
|
isMobile ? (
|
|
<EditorNavigator
|
|
ref={editorNavigator =>
|
|
(this._editorNavigator = editorNavigator)
|
|
}
|
|
editors={editors}
|
|
initialEditorName={'functions-list'}
|
|
transitions={{
|
|
'events-sheet': {
|
|
nextIcon: <Tune />,
|
|
nextLabel: <Trans>Parameters</Trans>,
|
|
nextEditor: 'parameters',
|
|
previousEditor: () => 'functions-list',
|
|
},
|
|
parameters: {
|
|
nextIcon: <Mark />,
|
|
nextLabel: <Trans>Validate these parameters</Trans>,
|
|
nextEditor: 'events-sheet',
|
|
},
|
|
}}
|
|
onEditorChanged={
|
|
// It's important that this callback is the same across renders,
|
|
// to avoid confusing EditorNavigator into thinking it's changed
|
|
// and immediately calling it, which would trigger an infinite loop.
|
|
// Search for "callback-prevent-infinite-rerendering" in the codebase.
|
|
this._onEditorNavigatorEditorChanged
|
|
}
|
|
/>
|
|
) : (
|
|
<PreferencesContext.Consumer>
|
|
{({
|
|
getDefaultEditorMosaicNode,
|
|
setDefaultEditorMosaicNode,
|
|
}) => (
|
|
<EditorMosaic
|
|
ref={editorMosaic => (this._editorMosaic = editorMosaic)}
|
|
editors={editors}
|
|
onPersistNodes={node =>
|
|
setDefaultEditorMosaicNode(
|
|
'events-functions-extension-editor',
|
|
node
|
|
)
|
|
}
|
|
initialNodes={
|
|
// Settings from older release may not have the unified
|
|
// function list.
|
|
mosaicContainsNode(
|
|
getDefaultEditorMosaicNode(
|
|
'events-functions-extension-editor'
|
|
) || getInitialMosaicEditorNodes(),
|
|
'functions-list'
|
|
)
|
|
? getDefaultEditorMosaicNode(
|
|
'events-functions-extension-editor'
|
|
) || getInitialMosaicEditorNodes()
|
|
: // Force the mosaic to reset to default.
|
|
getInitialMosaicEditorNodes()
|
|
}
|
|
/>
|
|
)}
|
|
</PreferencesContext.Consumer>
|
|
)
|
|
}
|
|
</ResponsiveWindowMeasurer>
|
|
{editOptionsDialogOpen && (
|
|
<OptionsEditorDialog
|
|
project={project}
|
|
eventsFunctionsExtension={eventsFunctionsExtension}
|
|
open
|
|
onClose={() => this._editOptions(false)}
|
|
/>
|
|
)}
|
|
{variablesEditorOpen && project && (
|
|
<GlobalAndSceneVariablesDialog
|
|
isGlobalTabInitiallyOpen={
|
|
variablesEditorOpen.isGlobalTabInitiallyOpen
|
|
}
|
|
projectScopedContainersAccessor={
|
|
new ProjectScopedContainersAccessor({
|
|
project,
|
|
eventsFunctionsExtension,
|
|
})
|
|
}
|
|
open
|
|
onCancel={() => this._editVariables(null)}
|
|
onApply={() => this._editVariables(null)}
|
|
/>
|
|
)}
|
|
{objectMethodSelectorDialogOpen && selectedEventsBasedObject && (
|
|
<ObjectMethodSelectorDialog
|
|
eventsBasedObject={selectedEventsBasedObject}
|
|
onCancel={() => this._onCloseObjectMethodSelectorDialog(null)}
|
|
onChoose={parameters =>
|
|
this._onCloseObjectMethodSelectorDialog(parameters)
|
|
}
|
|
/>
|
|
)}
|
|
{behaviorMethodSelectorDialogOpen && selectedEventsBasedBehavior && (
|
|
<BehaviorMethodSelectorDialog
|
|
eventsBasedBehavior={selectedEventsBasedBehavior}
|
|
onCancel={() => this._onCloseBehaviorMethodSelectorDialog(null)}
|
|
onChoose={parameters =>
|
|
this._onCloseBehaviorMethodSelectorDialog(parameters)
|
|
}
|
|
/>
|
|
)}
|
|
{extensionFunctionSelectorDialogOpen && eventsFunctionsExtension && (
|
|
<ExtensionFunctionSelectorDialog
|
|
eventsFunctionsExtension={eventsFunctionsExtension}
|
|
onCancel={() => this._onCloseExtensionFunctionSelectorDialog(null)}
|
|
onChoose={parameters =>
|
|
this._onCloseExtensionFunctionSelectorDialog(parameters)
|
|
}
|
|
/>
|
|
)}
|
|
{eventsBasedObjectSelectorDialogOpen && (
|
|
<EventsBasedObjectSelectorDialog
|
|
onCancel={() => this._onCloseEventsBasedObjectSelectorDialog(null)}
|
|
onChoose={parameters =>
|
|
this._onCloseEventsBasedObjectSelectorDialog(parameters)
|
|
}
|
|
/>
|
|
)}
|
|
</React.Fragment>
|
|
);
|
|
}
|
|
}
|