mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Make variables easier to declare on the fly (#6721)
This commit is contained in:
@@ -5,6 +5,7 @@ import VariableField, {
|
||||
getRootVariableName,
|
||||
renderVariableWithIcon,
|
||||
type VariableFieldInterface,
|
||||
type VariableDialogOpeningProps,
|
||||
} from './VariableField';
|
||||
import GlobalAndSceneVariablesDialog from '../../VariablesList/GlobalAndSceneVariablesDialog';
|
||||
import {
|
||||
@@ -18,7 +19,10 @@ import { mapFor } from '../../Utils/MapFor';
|
||||
export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
|
||||
function AnyVariableField(props: ParameterFieldProps, ref) {
|
||||
const field = React.useRef<?VariableFieldInterface>(null);
|
||||
const [editorOpen, setEditorOpen] = React.useState(false);
|
||||
const [
|
||||
editorOpen,
|
||||
setEditorOpen,
|
||||
] = React.useState<VariableDialogOpeningProps | null>(null);
|
||||
const focus: FieldFocusFunction = options => {
|
||||
if (field.current) field.current.focus(options);
|
||||
};
|
||||
@@ -66,7 +70,7 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
|
||||
if (selectedVariableName && selectedVariableName.startsWith(value)) {
|
||||
onChange(selectedVariableName);
|
||||
}
|
||||
setEditorOpen(false);
|
||||
setEditorOpen(null);
|
||||
// The variable editor may have refactor the events for a variable type
|
||||
// change which may have change the currently edited instruction type.
|
||||
if (onInstructionTypeChanged) onInstructionTypeChanged();
|
||||
@@ -97,7 +101,7 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
|
||||
onRequestClose={props.onRequestClose}
|
||||
onApply={props.onApply}
|
||||
ref={field}
|
||||
onOpenDialog={() => setEditorOpen(true)}
|
||||
onOpenDialog={setEditorOpen}
|
||||
globalObjectsContainer={props.globalObjectsContainer}
|
||||
objectsContainer={props.objectsContainer}
|
||||
projectScopedContainersAccessor={projectScopedContainersAccessor}
|
||||
@@ -113,10 +117,11 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
|
||||
<GlobalAndSceneVariablesDialog
|
||||
projectScopedContainersAccessor={projectScopedContainersAccessor}
|
||||
open
|
||||
onCancel={() => setEditorOpen(false)}
|
||||
onCancel={() => setEditorOpen(null)}
|
||||
onApply={onVariableEditorApply}
|
||||
isGlobalTabInitiallyOpen={isGlobal}
|
||||
initiallySelectedVariableName={props.value}
|
||||
initiallySelectedVariableName={editorOpen.variableName}
|
||||
shouldCreateInitiallySelectedVariable={editorOpen.shouldCreate}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
@@ -4,6 +4,7 @@ import { type ParameterInlineRendererProps } from './ParameterInlineRenderer.flo
|
||||
import VariableField, {
|
||||
renderVariableWithIcon,
|
||||
type VariableFieldInterface,
|
||||
type VariableDialogOpeningProps,
|
||||
} from './VariableField';
|
||||
import GlobalVariablesDialog from '../../VariablesList/GlobalVariablesDialog';
|
||||
import {
|
||||
@@ -17,7 +18,10 @@ import GlobalVariableIcon from '../../UI/CustomSvgIcons/GlobalVariable';
|
||||
export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
|
||||
function GlobalVariableField(props: ParameterFieldProps, ref) {
|
||||
const field = React.useRef<?VariableFieldInterface>(null);
|
||||
const [editorOpen, setEditorOpen] = React.useState(false);
|
||||
const [
|
||||
editorOpen,
|
||||
setEditorOpen,
|
||||
] = React.useState<VariableDialogOpeningProps | null>(null);
|
||||
const focus: FieldFocusFunction = options => {
|
||||
if (field.current) field.current.focus(options);
|
||||
};
|
||||
@@ -53,7 +57,7 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
|
||||
onRequestClose={props.onRequestClose}
|
||||
onApply={props.onApply}
|
||||
ref={field}
|
||||
onOpenDialog={() => setEditorOpen(true)}
|
||||
onOpenDialog={setEditorOpen}
|
||||
globalObjectsContainer={props.globalObjectsContainer}
|
||||
objectsContainer={props.objectsContainer}
|
||||
projectScopedContainersAccessor={projectScopedContainersAccessor}
|
||||
@@ -62,8 +66,8 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
|
||||
{editorOpen && project && (
|
||||
<GlobalVariablesDialog
|
||||
project={project}
|
||||
open={editorOpen}
|
||||
onCancel={() => setEditorOpen(false)}
|
||||
open
|
||||
onCancel={() => setEditorOpen(null)}
|
||||
onApply={(selectedVariableName: string | null) => {
|
||||
if (
|
||||
selectedVariableName &&
|
||||
@@ -71,10 +75,12 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
|
||||
) {
|
||||
props.onChange(selectedVariableName);
|
||||
}
|
||||
setEditorOpen(false);
|
||||
setEditorOpen(null);
|
||||
if (field.current) field.current.updateAutocompletions();
|
||||
}}
|
||||
preventRefactoringToDeleteInstructions
|
||||
initiallySelectedVariableName={editorOpen.variableName}
|
||||
shouldCreateInitiallySelectedVariable={editorOpen.shouldCreate}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
@@ -4,6 +4,7 @@ import { type ParameterInlineRendererProps } from './ParameterInlineRenderer.flo
|
||||
import VariableField, {
|
||||
renderVariableWithIcon,
|
||||
type VariableFieldInterface,
|
||||
type VariableDialogOpeningProps,
|
||||
} from './VariableField';
|
||||
import ObjectVariablesDialog from '../../VariablesList/ObjectVariablesDialog';
|
||||
import {
|
||||
@@ -59,7 +60,10 @@ export const getObjectOrGroupVariablesContainers = (
|
||||
export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
|
||||
function ObjectVariableField(props: ParameterFieldProps, ref) {
|
||||
const field = React.useRef<?VariableFieldInterface>(null);
|
||||
const [editorOpen, setEditorOpen] = React.useState(false);
|
||||
const [
|
||||
editorOpen,
|
||||
setEditorOpen,
|
||||
] = React.useState<VariableDialogOpeningProps | null>(null);
|
||||
const focus: FieldFocusFunction = options => {
|
||||
if (field.current) field.current.focus(options);
|
||||
};
|
||||
@@ -79,6 +83,8 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
|
||||
expression,
|
||||
parameterIndex,
|
||||
onInstructionTypeChanged,
|
||||
value,
|
||||
onChange,
|
||||
} = props;
|
||||
|
||||
const objectName = getLastObjectParameterValue({
|
||||
@@ -112,6 +118,20 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
|
||||
[variablesContainers]
|
||||
);
|
||||
|
||||
const onVariableEditorApply = React.useCallback(
|
||||
(selectedVariableName: string | null) => {
|
||||
if (selectedVariableName && selectedVariableName.startsWith(value)) {
|
||||
onChange(selectedVariableName);
|
||||
}
|
||||
setEditorOpen(null);
|
||||
// The variable editor may have refactored the events for a variable type
|
||||
// change which may have changed the currently edited instruction type.
|
||||
if (onInstructionTypeChanged) onInstructionTypeChanged();
|
||||
if (field.current) field.current.updateAutocompletions();
|
||||
},
|
||||
[onChange, onInstructionTypeChanged, value]
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<VariableField
|
||||
@@ -133,9 +153,7 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
|
||||
onApply={props.onApply}
|
||||
ref={field}
|
||||
// There is no variable editor for groups.
|
||||
onOpenDialog={
|
||||
variablesContainers.length === 1 ? () => setEditorOpen(true) : null
|
||||
}
|
||||
onOpenDialog={variablesContainers.length === 1 ? setEditorOpen : null}
|
||||
globalObjectsContainer={props.globalObjectsContainer}
|
||||
objectsContainer={props.objectsContainer}
|
||||
projectScopedContainersAccessor={projectScopedContainersAccessor}
|
||||
@@ -154,21 +172,12 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
|
||||
projectScopedContainersAccessor={projectScopedContainersAccessor}
|
||||
objectName={objectName}
|
||||
variablesContainer={variablesContainers[0]}
|
||||
open={editorOpen}
|
||||
onCancel={() => setEditorOpen(false)}
|
||||
onApply={(selectedVariableName: string | null) => {
|
||||
if (
|
||||
selectedVariableName &&
|
||||
selectedVariableName.startsWith(props.value)
|
||||
) {
|
||||
props.onChange(selectedVariableName);
|
||||
}
|
||||
setEditorOpen(false);
|
||||
if (onInstructionTypeChanged) onInstructionTypeChanged();
|
||||
if (field.current) field.current.updateAutocompletions();
|
||||
}}
|
||||
open
|
||||
onCancel={() => setEditorOpen(null)}
|
||||
onApply={onVariableEditorApply}
|
||||
preventRefactoringToDeleteInstructions
|
||||
initiallySelectedVariableName={props.value}
|
||||
initiallySelectedVariableName={editorOpen.variableName}
|
||||
shouldCreateInitiallySelectedVariable={editorOpen.shouldCreate}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
@@ -4,6 +4,7 @@ import { type ParameterInlineRendererProps } from './ParameterInlineRenderer.flo
|
||||
import VariableField, {
|
||||
renderVariableWithIcon,
|
||||
type VariableFieldInterface,
|
||||
type VariableDialogOpeningProps,
|
||||
} from './VariableField';
|
||||
import SceneVariablesDialog from '../../VariablesList/SceneVariablesDialog';
|
||||
import {
|
||||
@@ -17,7 +18,10 @@ import SceneVariableIcon from '../../UI/CustomSvgIcons/SceneVariable';
|
||||
export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
|
||||
function SceneVariableField(props: ParameterFieldProps, ref) {
|
||||
const field = React.useRef<?VariableFieldInterface>(null);
|
||||
const [editorOpen, setEditorOpen] = React.useState(false);
|
||||
const [
|
||||
editorOpen,
|
||||
setEditorOpen,
|
||||
] = React.useState<VariableDialogOpeningProps | null>(null);
|
||||
const focus: FieldFocusFunction = options => {
|
||||
if (field.current) field.current.focus(options);
|
||||
};
|
||||
@@ -54,7 +58,7 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
|
||||
onRequestClose={props.onRequestClose}
|
||||
onApply={props.onApply}
|
||||
ref={field}
|
||||
onOpenDialog={() => setEditorOpen(true)}
|
||||
onOpenDialog={setEditorOpen}
|
||||
globalObjectsContainer={props.globalObjectsContainer}
|
||||
objectsContainer={props.objectsContainer}
|
||||
projectScopedContainersAccessor={projectScopedContainersAccessor}
|
||||
@@ -70,7 +74,7 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
|
||||
project={project}
|
||||
layout={layout}
|
||||
open
|
||||
onCancel={() => setEditorOpen(false)}
|
||||
onCancel={() => setEditorOpen(null)}
|
||||
onApply={(selectedVariableName: string | null) => {
|
||||
if (
|
||||
selectedVariableName &&
|
||||
@@ -78,10 +82,14 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
|
||||
) {
|
||||
props.onChange(selectedVariableName);
|
||||
}
|
||||
setEditorOpen(false);
|
||||
setEditorOpen(null);
|
||||
if (field.current) field.current.updateAutocompletions();
|
||||
}}
|
||||
preventRefactoringToDeleteInstructions
|
||||
initiallySelectedVariableName={editorOpen.variableName}
|
||||
shouldCreateInitiallySelectedVariable={
|
||||
editorOpen.shouldCreate || false
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
@@ -42,12 +42,17 @@ import { ProjectScopedContainersAccessor } from '../../InstructionOrExpression/E
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
export type VariableDialogOpeningProps = {
|
||||
variableName: string,
|
||||
shouldCreate: boolean,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
...ParameterFieldProps,
|
||||
variablesContainers: Array<gdVariablesContainer>,
|
||||
enumerateVariables: () => Array<EnumeratedVariable>,
|
||||
forceDeclaration?: boolean,
|
||||
onOpenDialog: ?() => void,
|
||||
onOpenDialog: (VariableDialogOpeningProps => void) | null,
|
||||
};
|
||||
|
||||
type VariableNameQuickAnalyzeResult = 0 | 1 | 2 | 3 | 4;
|
||||
@@ -297,6 +302,31 @@ export default React.forwardRef<Props, VariableFieldInterface>(
|
||||
!errorText &&
|
||||
value;
|
||||
|
||||
const openVariableEditor = React.useCallback(
|
||||
() => {
|
||||
if (!onOpenDialog) {
|
||||
return;
|
||||
}
|
||||
// Access to the input directly because the value
|
||||
// may not have been sent to onChange yet.
|
||||
const fieldCurrentValue = field.current
|
||||
? field.current.getInputValue()
|
||||
: value;
|
||||
const isRootVariableDeclared =
|
||||
variablesContainers &&
|
||||
variablesContainers.some(variablesContainer =>
|
||||
variablesContainer.has(getRootVariableName(fieldCurrentValue))
|
||||
);
|
||||
|
||||
onChange(fieldCurrentValue);
|
||||
onOpenDialog({
|
||||
variableName: fieldCurrentValue,
|
||||
shouldCreate: !isRootVariableDeclared,
|
||||
});
|
||||
},
|
||||
[onChange, onOpenDialog, value, variablesContainers]
|
||||
);
|
||||
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
@@ -326,7 +356,7 @@ export default React.forwardRef<Props, VariableFieldInterface>(
|
||||
translatableValue: t`Add or edit variables...`,
|
||||
text: '',
|
||||
value: '',
|
||||
onClick: onOpenDialog,
|
||||
onClick: openVariableEditor,
|
||||
}
|
||||
: null,
|
||||
].filter(Boolean)}
|
||||
@@ -342,7 +372,14 @@ export default React.forwardRef<Props, VariableFieldInterface>(
|
||||
disabled={!onOpenDialog}
|
||||
primary
|
||||
style={style}
|
||||
onClick={onOpenDialog}
|
||||
onClick={() => {
|
||||
if (onOpenDialog) {
|
||||
onOpenDialog({
|
||||
variableName: value,
|
||||
shouldCreate: false,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
|
@@ -112,7 +112,6 @@ import {
|
||||
registerOnResourceExternallyChangedCallback,
|
||||
unregisterOnResourceExternallyChangedCallback,
|
||||
} from '../MainFrame/ResourcesWatcher';
|
||||
import { insertInVariablesContainer } from '../Utils/VariablesUtils';
|
||||
import { ProjectScopedContainersAccessor } from '../InstructionOrExpression/EventsScope.flow';
|
||||
import LocalVariablesDialog from '../VariablesList/LocalVariablesDialog';
|
||||
import GlobalAndSceneVariablesDialog from '../VariablesList/GlobalAndSceneVariablesDialog';
|
||||
@@ -183,6 +182,7 @@ type State = {|
|
||||
editedVariable: {
|
||||
variablesContainer: gdVariablesContainer,
|
||||
variableName: string,
|
||||
shouldCreateVariable: boolean,
|
||||
eventContext: ?EventContext,
|
||||
} | null,
|
||||
|
||||
@@ -475,28 +475,14 @@ export class EventsSheetComponentWithoutHandle extends React.Component<
|
||||
);
|
||||
if (!eventContext) return;
|
||||
|
||||
const variablesContainer = eventContext.event.getVariables();
|
||||
const { name: newName } = insertInVariablesContainer(
|
||||
variablesContainer,
|
||||
'Variable',
|
||||
null,
|
||||
variablesContainer.count(),
|
||||
null
|
||||
this.openVariablesEditor(
|
||||
eventContext,
|
||||
{
|
||||
variablesContainer: eventContext.event.getVariables(),
|
||||
variableName: 'Variable',
|
||||
},
|
||||
/* shouldCreateVariable: */ true
|
||||
);
|
||||
|
||||
this._eventsTree &&
|
||||
this._eventsTree.forceEventsUpdate(() => {
|
||||
const positions = this._getChangedEventRows([eventContext.event]);
|
||||
this._saveChangesToHistory('ADD', {
|
||||
positionsBeforeAction: positions,
|
||||
positionAfterAction: positions,
|
||||
});
|
||||
});
|
||||
|
||||
this.openVariablesEditor(eventContext, {
|
||||
variablesContainer,
|
||||
variableName: newName,
|
||||
});
|
||||
};
|
||||
|
||||
_selectionCanHaveLocalVariables = () => {
|
||||
@@ -711,12 +697,14 @@ export class EventsSheetComponentWithoutHandle extends React.Component<
|
||||
|
||||
openVariablesEditor = (
|
||||
eventContext: EventContext,
|
||||
variableDeclarationContext: VariableDeclarationContext
|
||||
variableDeclarationContext: VariableDeclarationContext,
|
||||
shouldCreateVariable = false
|
||||
) => {
|
||||
this.setState({
|
||||
editedVariable: {
|
||||
variablesContainer: variableDeclarationContext.variablesContainer,
|
||||
variableName: variableDeclarationContext.variableName,
|
||||
shouldCreateVariable,
|
||||
eventContext,
|
||||
},
|
||||
});
|
||||
@@ -2149,6 +2137,9 @@ export class EventsSheetComponentWithoutHandle extends React.Component<
|
||||
initiallySelectedVariableName={
|
||||
this.state.editedVariable.variableName
|
||||
}
|
||||
shouldCreateInitiallySelectedVariable={
|
||||
this.state.editedVariable.shouldCreateVariable
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{this.state.layoutVariablesDialogOpen && (
|
||||
|
@@ -72,6 +72,7 @@ type Props = {|
|
||||
export type SemiControlledAutoCompleteInterface = {|
|
||||
focus: FieldFocusFunction,
|
||||
forceInputValueTo: (newValue: string) => void,
|
||||
getInputValue: () => string,
|
||||
|};
|
||||
|
||||
export const autocompleteStyles = {
|
||||
@@ -270,6 +271,7 @@ export default React.forwardRef<Props, SemiControlledAutoCompleteInterface>(
|
||||
forceInputValueTo: (newValue: string) => {
|
||||
if (inputValue !== null) setInputValue(newValue);
|
||||
},
|
||||
getInputValue: () => (input.current ? input.current.value : ''),
|
||||
}));
|
||||
|
||||
const currentInputValue = inputValue !== null ? inputValue : props.value;
|
||||
|
@@ -178,26 +178,29 @@ export const useSerializableObjectsCancelableEditor = ({
|
||||
const preferences = React.useContext(PreferencesContext);
|
||||
const backdropClickBehavior = preferences.values.backdropClickBehavior;
|
||||
|
||||
const serializedElements = serializedElementsRef.current;
|
||||
if (serializedElements.size === 0) {
|
||||
for (const [id, serializableObject] of serializableObjects) {
|
||||
// Serialize the content of the object, to be used in case the user
|
||||
// want to cancel their changes.
|
||||
{
|
||||
const serializedElement = serializedElements.get(id);
|
||||
if (serializedElement) {
|
||||
serializedElement.delete();
|
||||
serializedElements.delete(id);
|
||||
}
|
||||
}
|
||||
if (resetThenClearPersistentUuid) {
|
||||
serializableObject.resetPersistentUuid();
|
||||
}
|
||||
const serializedElement = new gd.SerializerElement();
|
||||
serializableObject.serializeTo(serializedElement);
|
||||
serializedElements.set(id, serializedElement);
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
const serializedElements = serializedElementsRef.current;
|
||||
for (const [id, serializableObject] of serializableObjects) {
|
||||
// Serialize the content of the object, to be used in case the user
|
||||
// want to cancel their changes.
|
||||
{
|
||||
const serializedElement = serializedElements.get(id);
|
||||
if (serializedElement) {
|
||||
serializedElement.delete();
|
||||
serializedElements.delete(id);
|
||||
}
|
||||
}
|
||||
if (resetThenClearPersistentUuid) {
|
||||
serializableObject.resetPersistentUuid();
|
||||
}
|
||||
const serializedElement = new gd.SerializerElement();
|
||||
serializableObject.serializeTo(serializedElement);
|
||||
serializedElements.set(id, serializedElement);
|
||||
}
|
||||
return () => {
|
||||
for (const [id, serializedElement] of serializedElements) {
|
||||
serializedElement.delete();
|
||||
@@ -205,7 +208,7 @@ export const useSerializableObjectsCancelableEditor = ({
|
||||
}
|
||||
};
|
||||
},
|
||||
[serializableObjects, resetThenClearPersistentUuid]
|
||||
[serializedElements]
|
||||
);
|
||||
|
||||
const getOriginalContentSerializedElements = React.useCallback(() => {
|
||||
|
@@ -14,6 +14,7 @@ type Props = {|
|
||||
hotReloadPreviewButtonProps?: ?HotReloadPreviewButtonProps,
|
||||
isGlobalTabInitiallyOpen?: boolean,
|
||||
initiallySelectedVariableName?: string,
|
||||
shouldCreateInitiallySelectedVariable?: boolean,
|
||||
|};
|
||||
|
||||
const GlobalAndSceneVariablesDialog = ({
|
||||
@@ -24,6 +25,7 @@ const GlobalAndSceneVariablesDialog = ({
|
||||
hotReloadPreviewButtonProps,
|
||||
isGlobalTabInitiallyOpen,
|
||||
initiallySelectedVariableName,
|
||||
shouldCreateInitiallySelectedVariable,
|
||||
}: Props) => {
|
||||
const {
|
||||
project,
|
||||
@@ -119,6 +121,9 @@ const GlobalAndSceneVariablesDialog = ({
|
||||
isGlobalTabInitiallyOpen ? 'global-variables' : 'scene-variables'
|
||||
}
|
||||
initiallySelectedVariableName={initiallySelectedVariableName}
|
||||
shouldCreateInitiallySelectedVariable={
|
||||
shouldCreateInitiallySelectedVariable
|
||||
}
|
||||
helpPagePath={'/all-features/variables/scene-variables'}
|
||||
hotReloadPreviewButtonProps={hotReloadPreviewButtonProps}
|
||||
preventRefactoringToDeleteInstructions={true}
|
||||
|
@@ -17,6 +17,8 @@ type Props = {|
|
||||
* project must be refactored to delete any reference to it.
|
||||
*/
|
||||
preventRefactoringToDeleteInstructions?: boolean,
|
||||
initiallySelectedVariableName?: string,
|
||||
shouldCreateInitiallySelectedVariable?: boolean,
|
||||
|};
|
||||
|
||||
const GlobalVariablesDialog = ({
|
||||
@@ -26,6 +28,8 @@ const GlobalVariablesDialog = ({
|
||||
onApply,
|
||||
hotReloadPreviewButtonProps,
|
||||
preventRefactoringToDeleteInstructions,
|
||||
initiallySelectedVariableName,
|
||||
shouldCreateInitiallySelectedVariable,
|
||||
}: Props) => {
|
||||
const onComputeAllVariableNames = React.useCallback(
|
||||
() =>
|
||||
@@ -71,6 +75,10 @@ const GlobalVariablesDialog = ({
|
||||
onApply={onApply}
|
||||
title={<Trans>Global variables</Trans>}
|
||||
tabs={tabs}
|
||||
initiallySelectedVariableName={initiallySelectedVariableName}
|
||||
shouldCreateInitiallySelectedVariable={
|
||||
shouldCreateInitiallySelectedVariable
|
||||
}
|
||||
helpPagePath={'/all-features/variables/global-variables'}
|
||||
hotReloadPreviewButtonProps={hotReloadPreviewButtonProps}
|
||||
preventRefactoringToDeleteInstructions={
|
||||
|
@@ -12,6 +12,7 @@ type Props = {|
|
||||
onApply: (selectedVariableName: string | null) => void,
|
||||
onCancel: () => void,
|
||||
initiallySelectedVariableName: string,
|
||||
shouldCreateInitiallySelectedVariable?: boolean,
|
||||
|};
|
||||
|
||||
const LocalVariablesDialog = ({
|
||||
@@ -22,6 +23,7 @@ const LocalVariablesDialog = ({
|
||||
onCancel,
|
||||
onApply,
|
||||
initiallySelectedVariableName,
|
||||
shouldCreateInitiallySelectedVariable,
|
||||
}: Props) => {
|
||||
const tabs = React.useMemo(
|
||||
() => [
|
||||
@@ -48,6 +50,9 @@ const LocalVariablesDialog = ({
|
||||
preventRefactoringToDeleteInstructions
|
||||
id="local-variables-dialog"
|
||||
initiallySelectedVariableName={initiallySelectedVariableName}
|
||||
shouldCreateInitiallySelectedVariable={
|
||||
shouldCreateInitiallySelectedVariable
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@@ -22,6 +22,7 @@ type Props = {|
|
||||
*/
|
||||
preventRefactoringToDeleteInstructions?: boolean,
|
||||
initiallySelectedVariableName?: string,
|
||||
shouldCreateInitiallySelectedVariable?: boolean,
|
||||
|};
|
||||
|
||||
const ObjectVariablesDialog = ({
|
||||
@@ -35,6 +36,7 @@ const ObjectVariablesDialog = ({
|
||||
hotReloadPreviewButtonProps,
|
||||
preventRefactoringToDeleteInstructions,
|
||||
initiallySelectedVariableName,
|
||||
shouldCreateInitiallySelectedVariable,
|
||||
projectScopedContainersAccessor,
|
||||
}: Props) => {
|
||||
const onComputeAllVariableNames = React.useCallback(
|
||||
@@ -54,7 +56,7 @@ const ObjectVariablesDialog = ({
|
||||
() => [
|
||||
{
|
||||
id: 'object-variables',
|
||||
label: <Trans>Object variables</Trans>,
|
||||
label: '',
|
||||
variablesContainer: variablesContainer,
|
||||
emptyPlaceholderTitle: <Trans>Add your first object variable</Trans>,
|
||||
emptyPlaceholderDescription: (
|
||||
@@ -76,9 +78,12 @@ const ObjectVariablesDialog = ({
|
||||
open={open}
|
||||
onCancel={onCancel}
|
||||
onApply={onApply}
|
||||
title={<Trans>Object variables</Trans>}
|
||||
title={<Trans>{objectName} variables</Trans>}
|
||||
tabs={tabs}
|
||||
initiallySelectedVariableName={initiallySelectedVariableName}
|
||||
shouldCreateInitiallySelectedVariable={
|
||||
shouldCreateInitiallySelectedVariable
|
||||
}
|
||||
helpPagePath={'/all-features/variables/object-variables'}
|
||||
hotReloadPreviewButtonProps={hotReloadPreviewButtonProps}
|
||||
preventRefactoringToDeleteInstructions={
|
||||
|
@@ -18,6 +18,8 @@ type Props = {|
|
||||
* project must be refactored to delete any reference to it.
|
||||
*/
|
||||
preventRefactoringToDeleteInstructions?: boolean,
|
||||
initiallySelectedVariableName?: string,
|
||||
shouldCreateInitiallySelectedVariable?: boolean,
|
||||
|};
|
||||
|
||||
const SceneVariablesDialog = ({
|
||||
@@ -28,6 +30,8 @@ const SceneVariablesDialog = ({
|
||||
onApply,
|
||||
hotReloadPreviewButtonProps,
|
||||
preventRefactoringToDeleteInstructions,
|
||||
initiallySelectedVariableName,
|
||||
shouldCreateInitiallySelectedVariable,
|
||||
}: Props) => {
|
||||
const onComputeAllVariableNames = React.useCallback(
|
||||
() =>
|
||||
@@ -73,6 +77,10 @@ const SceneVariablesDialog = ({
|
||||
onApply={onApply}
|
||||
title={<Trans>{layout.getName()} variables</Trans>}
|
||||
tabs={tabs}
|
||||
initiallySelectedVariableName={initiallySelectedVariableName}
|
||||
shouldCreateInitiallySelectedVariable={
|
||||
shouldCreateInitiallySelectedVariable
|
||||
}
|
||||
helpPagePath={'/all-features/variables/scene-variables'}
|
||||
hotReloadPreviewButtonProps={hotReloadPreviewButtonProps}
|
||||
preventRefactoringToDeleteInstructions={
|
||||
|
@@ -62,6 +62,19 @@ export const getNodeIdFromVariableName = (variableName: string): string => {
|
||||
return knownVariablePart.replace(/\./g, '$$.$$');
|
||||
};
|
||||
|
||||
export const getNodeIdFromVariableContext = (
|
||||
variableContext: VariableContext
|
||||
): string | null => {
|
||||
const variableName = variableContext.name;
|
||||
if (!variableName) {
|
||||
return null;
|
||||
}
|
||||
const parentPart = variableContext.lineage
|
||||
.map(({ name }) => name)
|
||||
.join(separator);
|
||||
return (parentPart ? parentPart + separator : '') + variableName;
|
||||
};
|
||||
|
||||
export const getVariableContextFromNodeId = (
|
||||
nodeId: string,
|
||||
variablesContainer: gdVariablesContainer
|
||||
@@ -81,7 +94,9 @@ export const getVariableContextFromNodeId = (
|
||||
currentVariableName = removeInheritedPrefix(currentVariableName);
|
||||
}
|
||||
if (!parentVariable) {
|
||||
currentVariable = variablesContainer.get(currentVariableName);
|
||||
currentVariable = variablesContainer.has(currentVariableName)
|
||||
? variablesContainer.get(currentVariableName)
|
||||
: null;
|
||||
} else {
|
||||
if (parentVariable.getType() === gd.Variable.Array) {
|
||||
const index = parseInt(currentVariableName, 10);
|
||||
@@ -96,6 +111,9 @@ export const getVariableContextFromNodeId = (
|
||||
currentVariable = parentVariable.getChild(currentVariableName);
|
||||
}
|
||||
}
|
||||
if (!currentVariable) {
|
||||
break;
|
||||
}
|
||||
if (depth < bits.length - 1) {
|
||||
lineage.push({
|
||||
nodeId: bits.slice(0, depth + 1).join(separator),
|
||||
@@ -113,6 +131,25 @@ export const getVariableContextFromNodeId = (
|
||||
};
|
||||
};
|
||||
|
||||
export const getParentVariableContext = (
|
||||
variableContext: VariableContext
|
||||
): VariableContext => {
|
||||
if (variableContext.lineage.length === 0) {
|
||||
return variableContext;
|
||||
}
|
||||
const parentContext =
|
||||
variableContext.lineage[variableContext.lineage.length - 1];
|
||||
return {
|
||||
variable: parentContext.variable,
|
||||
name: parentContext.name,
|
||||
depth: variableContext.depth - 1,
|
||||
lineage: variableContext.lineage.slice(
|
||||
0,
|
||||
variableContext.lineage.length - 1
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export const updateListOfNodesFollowingChangeName = (
|
||||
list: string[],
|
||||
oldNodeId: string,
|
||||
|
@@ -15,6 +15,9 @@ import { getVariableContextFromNodeId } from './VariableToTreeNodeHandling';
|
||||
import { Tabs } from '../UI/Tabs';
|
||||
import { useResponsiveWindowSize } from '../UI/Responsive/ResponsiveWindowMeasurer';
|
||||
import { ProjectScopedContainersAccessor } from '../InstructionOrExpression/EventsScope.flow';
|
||||
import { insertInVariablesContainer } from '../Utils/VariablesUtils';
|
||||
import { getRootVariableName } from '../EventsSheet/ParameterFields/VariableField';
|
||||
import { getNodeIdFromVariableName } from './VariableToTreeNodeHandling';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
@@ -56,6 +59,7 @@ type Props = {|
|
||||
areObjectVariables?: boolean,
|
||||
initiallyOpenTabId?: string,
|
||||
initiallySelectedVariableName?: string,
|
||||
shouldCreateInitiallySelectedVariable?: boolean,
|
||||
|
||||
project: gdProject,
|
||||
hotReloadPreviewButtonProps?: ?HotReloadPreviewButtonProps,
|
||||
@@ -84,6 +88,7 @@ const VariablesEditorDialog = ({
|
||||
tabs,
|
||||
initiallyOpenTabId,
|
||||
initiallySelectedVariableName,
|
||||
shouldCreateInitiallySelectedVariable,
|
||||
projectScopedContainersAccessor,
|
||||
areObjectVariables,
|
||||
}: Props) => {
|
||||
@@ -103,6 +108,41 @@ const VariablesEditorDialog = ({
|
||||
onCancel,
|
||||
resetThenClearPersistentUuid: true,
|
||||
});
|
||||
|
||||
const lastSelectedVariableNodeId = React.useRef<string | null>(null);
|
||||
const onSelectedVariableChange = React.useCallback((nodes: Array<string>) => {
|
||||
lastSelectedVariableNodeId.current =
|
||||
nodes.length > 0 ? nodes[nodes.length - 1] : null;
|
||||
}, []);
|
||||
|
||||
const shouldCreateVariable = React.useRef<boolean>(
|
||||
shouldCreateInitiallySelectedVariable || false
|
||||
);
|
||||
const actualInitiallySelectedVariableName = React.useRef<?string>(
|
||||
initiallySelectedVariableName
|
||||
);
|
||||
if (shouldCreateVariable.current) {
|
||||
shouldCreateVariable.current = false;
|
||||
const tabIndex = Math.max(
|
||||
0,
|
||||
tabs.indexOf(({ id }) => id === initiallyOpenTabId)
|
||||
);
|
||||
const { variablesContainer, inheritedVariablesContainer } = tabs[tabIndex];
|
||||
const { name: actualVariableName } = insertInVariablesContainer(
|
||||
variablesContainer,
|
||||
initiallySelectedVariableName
|
||||
? getRootVariableName(initiallySelectedVariableName)
|
||||
: 'Variable',
|
||||
null,
|
||||
variablesContainer.count(),
|
||||
inheritedVariablesContainer
|
||||
);
|
||||
actualInitiallySelectedVariableName.current = actualVariableName;
|
||||
lastSelectedVariableNodeId.current = getNodeIdFromVariableName(
|
||||
actualVariableName
|
||||
);
|
||||
}
|
||||
|
||||
const { isMobile } = useResponsiveWindowSize();
|
||||
const { DismissableTutorialMessage } = useDismissableTutorialMessage(
|
||||
'intro-variables'
|
||||
@@ -111,12 +151,6 @@ const VariablesEditorDialog = ({
|
||||
initiallyOpenTabId || tabs[0].id
|
||||
);
|
||||
|
||||
const lastSelectedVariableNodeId = React.useRef<string | null>(null);
|
||||
const onSelectedVariableChange = React.useCallback((nodes: Array<string>) => {
|
||||
lastSelectedVariableNodeId.current =
|
||||
nodes.length > 0 ? nodes[nodes.length - 1] : null;
|
||||
}, []);
|
||||
|
||||
const onRefactorAndApply = React.useCallback(
|
||||
async () => {
|
||||
const originalContentSerializedElements = getOriginalContentSerializedElements();
|
||||
@@ -265,7 +299,9 @@ const VariablesEditorDialog = ({
|
||||
}
|
||||
variablesContainer={variablesContainer}
|
||||
areObjectVariables={areObjectVariables}
|
||||
initiallySelectedVariableName={initiallySelectedVariableName}
|
||||
initiallySelectedVariableName={
|
||||
actualInitiallySelectedVariableName.current
|
||||
}
|
||||
inheritedVariablesContainer={inheritedVariablesContainer}
|
||||
emptyPlaceholderTitle={emptyPlaceholderTitle}
|
||||
emptyPlaceholderDescription={emptyPlaceholderDescription}
|
||||
|
@@ -54,7 +54,9 @@ import {
|
||||
getMovementTypeWithinVariablesContainer,
|
||||
getOldestAncestryVariable,
|
||||
getNodeIdFromVariableName,
|
||||
getNodeIdFromVariableContext,
|
||||
getVariableContextFromNodeId,
|
||||
getParentVariableContext,
|
||||
inheritedPrefix,
|
||||
isAnAncestryOf,
|
||||
separator,
|
||||
@@ -102,7 +104,7 @@ type Props = {|
|
||||
variablesContainer: gdVariablesContainer,
|
||||
areObjectVariables?: boolean,
|
||||
inheritedVariablesContainer?: gdVariablesContainer,
|
||||
initiallySelectedVariableName?: string,
|
||||
initiallySelectedVariableName?: ?string,
|
||||
/** Callback executed at mount to compute suggestions. */
|
||||
onComputeAllVariableNames?: () => Array<string>,
|
||||
/** To specify if history should be handled by parent. */
|
||||
@@ -530,32 +532,6 @@ const VariablesList = (props: Props) => {
|
||||
})
|
||||
);
|
||||
|
||||
const [searchText, setSearchText] = React.useState<string>('');
|
||||
const { onComputeAllVariableNames, onSelectedVariableChange } = props;
|
||||
const allVariablesNames = React.useMemo<?Array<string>>(
|
||||
() => (onComputeAllVariableNames ? onComputeAllVariableNames() : null),
|
||||
[onComputeAllVariableNames]
|
||||
);
|
||||
// TODO Scroll to the initially selected variable and focus on the name.
|
||||
const [selectedNodes, doSetSelectedNodes] = React.useState<Array<string>>(
|
||||
props.initiallySelectedVariableName &&
|
||||
props.variablesContainer.has(props.initiallySelectedVariableName)
|
||||
? [getNodeIdFromVariableName(props.initiallySelectedVariableName)]
|
||||
: []
|
||||
);
|
||||
const setSelectedNodes = React.useCallback(
|
||||
(nodes: Array<string> | ((nodes: Array<string>) => Array<string>)) => {
|
||||
doSetSelectedNodes(selectedNodes => {
|
||||
const newNodes = Array.isArray(nodes) ? nodes : nodes(selectedNodes);
|
||||
if (onSelectedVariableChange) {
|
||||
onSelectedVariableChange(newNodes);
|
||||
}
|
||||
return newNodes;
|
||||
});
|
||||
},
|
||||
[onSelectedVariableChange]
|
||||
);
|
||||
|
||||
const [searchMatchingNodes, setSearchMatchingNodes] = React.useState<
|
||||
Array<string>
|
||||
>([]);
|
||||
@@ -574,6 +550,48 @@ const VariablesList = (props: Props) => {
|
||||
const draggedNodeId = React.useRef<?string>(null);
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
const [searchText, setSearchText] = React.useState<string>('');
|
||||
const { onComputeAllVariableNames, onSelectedVariableChange } = props;
|
||||
const allVariablesNames = React.useMemo<?Array<string>>(
|
||||
() => (onComputeAllVariableNames ? onComputeAllVariableNames() : null),
|
||||
[onComputeAllVariableNames]
|
||||
);
|
||||
const [selectedNodes, doSetSelectedNodes] = React.useState<Array<string>>(
|
||||
() => {
|
||||
if (!props.initiallySelectedVariableName) {
|
||||
return [];
|
||||
}
|
||||
let variableContext = getVariableContextFromNodeId(
|
||||
getNodeIdFromVariableName(props.initiallySelectedVariableName),
|
||||
props.variablesContainer
|
||||
);
|
||||
// When a child-variable is not declared, its direct parent is used.
|
||||
if (!variableContext.variable) {
|
||||
variableContext = getParentVariableContext(variableContext);
|
||||
}
|
||||
if (variableContext.variable) {
|
||||
// TODO Add ref to child-variables to allow to focus them.
|
||||
refocusNameField({ identifier: variableContext.variable.ptr });
|
||||
}
|
||||
const initialSelectedNodeId = variableContext.variable
|
||||
? getNodeIdFromVariableContext(variableContext)
|
||||
: null;
|
||||
return initialSelectedNodeId ? [initialSelectedNodeId] : [];
|
||||
}
|
||||
);
|
||||
const setSelectedNodes = React.useCallback(
|
||||
(nodes: Array<string> | ((nodes: Array<string>) => Array<string>)) => {
|
||||
doSetSelectedNodes(selectedNodes => {
|
||||
const newNodes = Array.isArray(nodes) ? nodes : nodes(selectedNodes);
|
||||
if (onSelectedVariableChange) {
|
||||
onSelectedVariableChange(newNodes);
|
||||
}
|
||||
return newNodes;
|
||||
});
|
||||
},
|
||||
[onSelectedVariableChange]
|
||||
);
|
||||
|
||||
const triggerSearch = React.useCallback(
|
||||
() => {
|
||||
let matchingInheritedNodes = [];
|
||||
|
Reference in New Issue
Block a user