Make variables easier to declare on the fly (#6721)

This commit is contained in:
D8H
2024-06-27 21:13:18 +02:00
committed by GitHub
parent 555ee61e63
commit 6b3faa42bb
16 changed files with 297 additions and 114 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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
}

View File

@@ -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 && (

View File

@@ -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;

View File

@@ -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(() => {

View File

@@ -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}

View File

@@ -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={

View File

@@ -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
}
/>
);
};

View File

@@ -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={

View File

@@ -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={

View File

@@ -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,

View File

@@ -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}

View File

@@ -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 = [];