mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Show instance variables in panel + inherited object variables + fix crash
* Show instance variables in InstancePropertiesEditor * Display parent variables (not editable) and overriden variables in italic. * Also Fix (existing) crashes when deleting variables. This was wrongly written: * no recursive search (`contains` 2nd argument not passed) * comparing a VariableAndName with a gdVariable made no sense.
This commit is contained in:

committed by
Florian Rival

parent
be950a7779
commit
72282d4e6d
@@ -1,106 +1,117 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import React, { Component } from 'react';
|
||||
import * as React from 'react';
|
||||
import Background from '../../UI/Background';
|
||||
import enumerateLayers from '../../LayersList/EnumerateLayers';
|
||||
import EmptyMessage from '../../UI/EmptyMessage';
|
||||
import PropertiesEditor from '../../PropertiesEditor';
|
||||
import propertiesMapToSchema from '../../PropertiesEditor/PropertiesMapToSchema';
|
||||
import VariablesList from '../../VariablesList';
|
||||
import getObjectByName from '../../Utils/GetObjectByName';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import { Line, Column } from '../../UI/Grid';
|
||||
|
||||
export default class InstancePropertiesEditor extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
import OpenInNew from 'material-ui/svg-icons/action/open-in-new';
|
||||
|
||||
this.schema = [
|
||||
{
|
||||
name: 'Object name',
|
||||
valueType: 'string',
|
||||
disabled: true,
|
||||
getValue: instance => instance.getObjectName(),
|
||||
setValue: (instance, newValue) => instance.setObjectName(newValue),
|
||||
onEditButtonClick: instance =>
|
||||
this.props.onEditObjectByName(instance.getObjectName()),
|
||||
},
|
||||
{
|
||||
name: 'Position',
|
||||
type: 'row',
|
||||
children: [
|
||||
{
|
||||
name: 'X',
|
||||
valueType: 'number',
|
||||
getValue: instance => instance.getX(),
|
||||
setValue: (instance, newValue) => instance.setX(newValue),
|
||||
},
|
||||
{
|
||||
name: 'Y',
|
||||
valueType: 'number',
|
||||
getValue: instance => instance.getY(),
|
||||
setValue: (instance, newValue) => instance.setY(newValue),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Angle',
|
||||
valueType: 'number',
|
||||
getValue: instance => instance.getAngle(),
|
||||
setValue: (instance, newValue) => instance.setAngle(newValue),
|
||||
},
|
||||
{
|
||||
name: 'Lock position/angle in the editor',
|
||||
valueType: 'boolean',
|
||||
getValue: instance => instance.isLocked(),
|
||||
setValue: (instance, newValue) => instance.setLocked(newValue),
|
||||
},
|
||||
{
|
||||
name: 'Z Order',
|
||||
valueType: 'number',
|
||||
getValue: instance => instance.getZOrder(),
|
||||
setValue: (instance, newValue) => instance.setZOrder(newValue),
|
||||
},
|
||||
{
|
||||
name: 'Layer',
|
||||
valueType: 'string',
|
||||
getChoices: () => enumerateLayers(this.props.layout),
|
||||
getValue: instance => instance.getLayer(),
|
||||
setValue: (instance, newValue) => instance.setLayer(newValue),
|
||||
},
|
||||
{
|
||||
name: 'Custom size',
|
||||
type: 'row',
|
||||
children: [
|
||||
{
|
||||
name: 'Width',
|
||||
valueType: 'number',
|
||||
getValue: instance => instance.getCustomWidth(),
|
||||
setValue: (instance, newValue) => instance.setCustomWidth(newValue),
|
||||
},
|
||||
{
|
||||
name: 'Height',
|
||||
valueType: 'number',
|
||||
getValue: instance => instance.getCustomHeight(),
|
||||
setValue: (instance, newValue) =>
|
||||
instance.setCustomHeight(newValue),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Custom size?',
|
||||
valueType: 'boolean',
|
||||
getValue: instance => instance.hasCustomSize(),
|
||||
setValue: (instance, newValue) => instance.setHasCustomSize(newValue),
|
||||
},
|
||||
{
|
||||
name: 'Instance variables',
|
||||
children: [
|
||||
{
|
||||
name: 'Edit variables',
|
||||
getLabel: instance =>
|
||||
'Edit variables (' + instance.getVariables().count() + ')',
|
||||
onClick: instance => this.props.editInstanceVariables(instance),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
layout: gdLayout,
|
||||
instances: Array<gdInitialInstance>,
|
||||
onEditObjectByName: string => void,
|
||||
editObjectVariables: (?gdObject) => void,
|
||||
editInstanceVariables: gdInitialInstance => void,
|
||||
|};
|
||||
|
||||
export default class InstancePropertiesEditor extends React.Component<Props> {
|
||||
_instanceVariablesList: { current: null | VariablesList } = React.createRef();
|
||||
schema = [
|
||||
{
|
||||
name: 'Object name',
|
||||
valueType: 'string',
|
||||
disabled: true,
|
||||
getValue: (instance: gdInitialInstance) => instance.getObjectName(),
|
||||
setValue: (instance: gdInitialInstance, newValue: string) =>
|
||||
instance.setObjectName(newValue),
|
||||
onEditButtonClick: (instance: gdInitialInstance) =>
|
||||
this.props.onEditObjectByName(instance.getObjectName()),
|
||||
},
|
||||
{
|
||||
name: 'Position',
|
||||
type: 'row',
|
||||
children: [
|
||||
{
|
||||
name: 'X',
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) => instance.getX(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setX(newValue),
|
||||
},
|
||||
{
|
||||
name: 'Y',
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) => instance.getY(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setY(newValue),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Angle',
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) => instance.getAngle(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setAngle(newValue),
|
||||
},
|
||||
{
|
||||
name: 'Lock position/angle in the editor',
|
||||
valueType: 'boolean',
|
||||
getValue: (instance: gdInitialInstance) => instance.isLocked(),
|
||||
setValue: (instance: gdInitialInstance, newValue: boolean) =>
|
||||
instance.setLocked(newValue),
|
||||
},
|
||||
{
|
||||
name: 'Z Order',
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) => instance.getZOrder(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setZOrder(newValue),
|
||||
},
|
||||
{
|
||||
name: 'Layer',
|
||||
valueType: 'string',
|
||||
getChoices: () => enumerateLayers(this.props.layout),
|
||||
getValue: (instance: gdInitialInstance) => instance.getLayer(),
|
||||
setValue: (instance: gdInitialInstance, newValue: string) =>
|
||||
instance.setLayer(newValue),
|
||||
},
|
||||
{
|
||||
name: 'Custom size',
|
||||
type: 'row',
|
||||
children: [
|
||||
{
|
||||
name: 'Width',
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) => instance.getCustomWidth(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setCustomWidth(newValue),
|
||||
},
|
||||
{
|
||||
name: 'Height',
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) => instance.getCustomHeight(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setCustomHeight(newValue),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Custom size?',
|
||||
valueType: 'boolean',
|
||||
getValue: (instance: gdInitialInstance) => instance.hasCustomSize(),
|
||||
setValue: (instance: gdInitialInstance, newValue: boolean) =>
|
||||
instance.setHasCustomSize(newValue),
|
||||
},
|
||||
];
|
||||
|
||||
_renderEmpty() {
|
||||
return (
|
||||
@@ -114,24 +125,50 @@ export default class InstancePropertiesEditor extends Component {
|
||||
|
||||
_renderInstancesProperties() {
|
||||
const { project, layout, instances } = this.props;
|
||||
|
||||
const instance = instances[0];
|
||||
const associatedObjectName = instance.getObjectName();
|
||||
const object = getObjectByName(project, layout, associatedObjectName);
|
||||
//TODO: multiple instances support
|
||||
const properties = instances[0].getCustomProperties(project, layout);
|
||||
const properties = instance.getCustomProperties(project, layout);
|
||||
const instanceSchema = propertiesMapToSchema(
|
||||
properties,
|
||||
instance => instance.getCustomProperties(project, layout),
|
||||
(instance, name, value) =>
|
||||
(instance: gdInitialInstance) =>
|
||||
instance.getCustomProperties(project, layout),
|
||||
(instance: gdInitialInstance, name, value) =>
|
||||
instance.updateCustomProperty(name, value, project, layout)
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ padding: 10, overflowY: 'scroll', overflowX: 'hidden' }}
|
||||
key={instances.map(instance => '' + instance.ptr).join(';')}
|
||||
style={{ overflowY: 'scroll', overflowX: 'hidden' }}
|
||||
key={instances
|
||||
.map((instance: gdInitialInstance) => '' + instance.ptr)
|
||||
.join(';')}
|
||||
>
|
||||
<PropertiesEditor
|
||||
schema={this.schema.concat(instanceSchema)}
|
||||
instances={instances}
|
||||
<Column>
|
||||
<PropertiesEditor
|
||||
schema={this.schema.concat(instanceSchema)}
|
||||
instances={instances}
|
||||
/>
|
||||
<Line alignItems="center">
|
||||
<Trans>Instance Variables</Trans>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
this.props.editInstanceVariables(instance);
|
||||
}}
|
||||
>
|
||||
<OpenInNew />
|
||||
</IconButton>
|
||||
</Line>
|
||||
</Column>
|
||||
<VariablesList
|
||||
inheritedVariablesContainer={object ? object.getVariables() : null}
|
||||
variablesContainer={instance.getVariables()}
|
||||
onSizeUpdated={
|
||||
() =>
|
||||
this.forceUpdate() /*Force update to ensure dialog is properly positionned*/
|
||||
}
|
||||
ref={this._instanceVariablesList}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import gesture from 'pixi-simple-gesture';
|
||||
import ObjectsRenderingService from '../../ObjectsRendering/ObjectsRenderingService';
|
||||
import getObjectByName from '../../Utils/GetObjectByName';
|
||||
|
||||
import PIXI from 'pixi.js';
|
||||
const gd = global.gd;
|
||||
|
||||
@@ -100,13 +102,13 @@ export default class LayerRenderer {
|
||||
var renderedInstance = this.renderedInstances[instance.ptr];
|
||||
if (renderedInstance === undefined) {
|
||||
//No renderer associated yet, the instance must have been just created!...
|
||||
var associatedObjectName = instance.getObjectName();
|
||||
var associatedObject = null;
|
||||
if (this.layout.hasObjectNamed(associatedObjectName))
|
||||
associatedObject = this.layout.getObject(associatedObjectName);
|
||||
else if (this.project.hasObjectNamed(associatedObjectName))
|
||||
associatedObject = this.project.getObject(associatedObjectName);
|
||||
else return;
|
||||
const associatedObjectName = instance.getObjectName();
|
||||
const associatedObject = getObjectByName(
|
||||
this.project,
|
||||
this.layout,
|
||||
associatedObjectName
|
||||
);
|
||||
if (!associatedObject) return;
|
||||
|
||||
//...so let's create a renderer.
|
||||
renderedInstance = this.renderedInstances[
|
||||
|
@@ -505,6 +505,7 @@ export default class ObjectsListContainer extends React.Component<
|
||||
}
|
||||
onCancel={() => this._editVariables(null)}
|
||||
onApply={() => this._editVariables(null)}
|
||||
title="Object Variables"
|
||||
emptyExplanationMessage="When you add variables to an object, any instance of the object put on the scene or created during the game will have these variables attached to it."
|
||||
emptyExplanationSecondMessage="For example, you can have a variable called Life representing the health of the object."
|
||||
/>
|
||||
|
@@ -36,6 +36,7 @@ import ContextMenu from '../UI/Menu/ContextMenu';
|
||||
import { showWarningBox } from '../UI/Messages/MessageBox';
|
||||
import { shortenString } from '../Utils/StringHelpers';
|
||||
import { roundPosition } from '../Utils/GridHelpers';
|
||||
import getObjectByName from '../Utils/GetObjectByName';
|
||||
|
||||
import {
|
||||
type ResourceSource,
|
||||
@@ -103,6 +104,7 @@ type State = {|
|
||||
layerRemoved: ?string,
|
||||
editedObjectWithContext: ?ObjectWithContext,
|
||||
variablesEditedInstance: ?gdInitialInstance,
|
||||
variablesEditedObject: ?gdObject,
|
||||
selectedObjectNames: Array<string>,
|
||||
|
||||
editedGroup: ?gdObjectGroup,
|
||||
@@ -150,6 +152,7 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
layerRemoved: null,
|
||||
editedObjectWithContext: null,
|
||||
variablesEditedInstance: null,
|
||||
variablesEditedObject: null,
|
||||
selectedObjectNames: [],
|
||||
|
||||
editedGroup: null,
|
||||
@@ -298,6 +301,10 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
this.setState({ variablesEditedInstance: instance });
|
||||
};
|
||||
|
||||
editObjectVariables = (object: ?gdObject) => {
|
||||
this.setState({ variablesEditedObject: object });
|
||||
};
|
||||
|
||||
editLayoutVariables = (open: boolean = true) => {
|
||||
this.setState({ layoutVariablesDialogOpen: open });
|
||||
};
|
||||
@@ -842,8 +849,8 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
project={project}
|
||||
layout={layout}
|
||||
instances={selectedInstances}
|
||||
onInstancesModified={this._onInstancesModified}
|
||||
editInstanceVariables={this.editInstanceVariables}
|
||||
editObjectVariables={this.editObjectVariables}
|
||||
onEditObjectByName={this.editObjectByName}
|
||||
ref={propertiesEditor =>
|
||||
(this._propertiesEditor = propertiesEditor)
|
||||
@@ -1075,6 +1082,35 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
onCancel={() => this.editInstanceVariables(null)}
|
||||
onApply={() => this.editInstanceVariables(null)}
|
||||
emptyExplanationMessage="Instance variables will override the default values of the variables of the object."
|
||||
title="Instance Variables"
|
||||
onEditObjectVariables={() => {
|
||||
if (!this.instancesSelection.hasSelectedInstances()) {
|
||||
return;
|
||||
}
|
||||
const associatedObjectName = this.instancesSelection
|
||||
.getSelectedInstances()[0]
|
||||
.getObjectName();
|
||||
const object = getObjectByName(
|
||||
project,
|
||||
layout,
|
||||
associatedObjectName
|
||||
);
|
||||
if (object) {
|
||||
this.editObjectVariables(object);
|
||||
this.editInstanceVariables(null);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<VariablesEditorDialog
|
||||
open={!!this.state.variablesEditedObject}
|
||||
variablesContainer={
|
||||
this.state.variablesEditedObject &&
|
||||
this.state.variablesEditedObject.getVariables()
|
||||
}
|
||||
onCancel={() => this.editObjectVariables(null)}
|
||||
onApply={() => this.editObjectVariables(null)}
|
||||
emptyExplanationMessage="When you add variables to an object, any instance of the object put on the scene or created during the game will have these variables attached to it."
|
||||
title="Object Variables"
|
||||
/>
|
||||
<LayerRemoveDialog
|
||||
open={!!this.state.layerRemoveDialogOpen}
|
||||
@@ -1096,6 +1132,7 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
variablesContainer={layout.getVariables()}
|
||||
onCancel={() => this.editLayoutVariables(false)}
|
||||
onApply={() => this.editLayoutVariables(false)}
|
||||
title="Scene variables"
|
||||
emptyExplanationMessage="Scene variables can be used to store any value or text during the game."
|
||||
emptyExplanationSecondMessage="For example, you can have a variable called Score representing the current score of the player."
|
||||
/>
|
||||
|
14
newIDE/app/src/Utils/GetObjectByName.js
Normal file
14
newIDE/app/src/Utils/GetObjectByName.js
Normal file
@@ -0,0 +1,14 @@
|
||||
// @flow
|
||||
|
||||
export default function getObjectByName(
|
||||
project: gdProject,
|
||||
layout: gdLayout,
|
||||
associatedObjectName: string
|
||||
): ?gdObject {
|
||||
let associatedObject = null;
|
||||
if (layout.hasObjectNamed(associatedObjectName))
|
||||
associatedObject = layout.getObject(associatedObjectName);
|
||||
else if (project.hasObjectNamed(associatedObjectName))
|
||||
associatedObject = project.getObject(associatedObjectName);
|
||||
return associatedObject;
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
import React from 'react';
|
||||
import { TreeTableRow, TreeTableCell } from '../UI/TreeTable';
|
||||
import Add from 'material-ui/svg-icons/content/add';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import EmptyMessage from '../UI/EmptyMessage';
|
||||
import styles from './styles';
|
||||
|
||||
const VariableRow = ({ onAdd }) => (
|
||||
<TreeTableRow key="add-row">
|
||||
<TreeTableCell />
|
||||
<TreeTableCell>
|
||||
<EmptyMessage style={styles.addVariableMessage}>
|
||||
Click to add a variable:
|
||||
</EmptyMessage>
|
||||
</TreeTableCell>
|
||||
<TreeTableCell style={styles.toolColumn}>
|
||||
<IconButton onClick={onAdd}>
|
||||
<Add />
|
||||
</IconButton>
|
||||
</TreeTableCell>
|
||||
</TreeTableRow>
|
||||
);
|
||||
|
||||
export default VariableRow;
|
45
newIDE/app/src/VariablesList/EditVariableRow.js
Normal file
45
newIDE/app/src/VariablesList/EditVariableRow.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { TreeTableRow, TreeTableCell } from '../UI/TreeTable';
|
||||
import Add from 'material-ui/svg-icons/content/add';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import EmptyMessage from '../UI/EmptyMessage';
|
||||
import ContentCopy from 'material-ui/svg-icons/content/content-copy';
|
||||
import ContentPaste from 'material-ui/svg-icons/content/content-paste';
|
||||
import Delete from 'material-ui/svg-icons/action/delete';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
const EditVariableRow = ({
|
||||
onAdd,
|
||||
onCopy,
|
||||
hasSelection,
|
||||
onPaste,
|
||||
hasClipboard,
|
||||
onDeleteSelection,
|
||||
}) => (
|
||||
<TreeTableRow>
|
||||
<TreeTableCell style={styles.toolColumnHeader}>
|
||||
<IconButton onClick={onCopy} disabled={!hasSelection}>
|
||||
<ContentCopy />
|
||||
</IconButton>
|
||||
<IconButton onClick={onPaste} disabled={!hasClipboard}>
|
||||
<ContentPaste />
|
||||
</IconButton>
|
||||
<IconButton onClick={onDeleteSelection} disabled={!hasSelection}>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</TreeTableCell>
|
||||
|
||||
<TreeTableCell>
|
||||
<EmptyMessage style={styles.addVariableMessage} />
|
||||
</TreeTableCell>
|
||||
|
||||
<TreeTableCell style={styles.toolColumn}>
|
||||
<IconButton onClick={onAdd}>
|
||||
<Add />
|
||||
</IconButton>
|
||||
</TreeTableCell>
|
||||
</TreeTableRow>
|
||||
);
|
||||
|
||||
export default EditVariableRow;
|
@@ -8,8 +8,10 @@ import AddCircle from 'material-ui/svg-icons/content/add-circle';
|
||||
import SubdirectoryArrowRight from 'material-ui/svg-icons/navigation/subdirectory-arrow-right';
|
||||
import TextField from 'material-ui/TextField';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import Reset from 'material-ui/svg-icons/av/replay';
|
||||
import muiThemeable from 'material-ui/styles/muiThemeable';
|
||||
import styles from './styles';
|
||||
import { type VariableOrigin } from './VariablesList.flow';
|
||||
|
||||
//TODO: Refactor into TreeTable?
|
||||
const Indent = ({ width }) => (
|
||||
@@ -29,12 +31,14 @@ type Props = {|
|
||||
onRemove: () => void,
|
||||
onAddChild: () => void,
|
||||
onChangeValue: string => void,
|
||||
onResetToDefaultValue: () => void,
|
||||
children?: React.Node,
|
||||
muiTheme: Object,
|
||||
showHandle: boolean,
|
||||
showSelectionCheckbox: boolean,
|
||||
isSelected: boolean,
|
||||
onSelect: boolean => void,
|
||||
origin: VariableOrigin,
|
||||
|};
|
||||
|
||||
const ThemableVariableRow = ({
|
||||
@@ -46,34 +50,42 @@ const ThemableVariableRow = ({
|
||||
onRemove,
|
||||
onAddChild,
|
||||
onChangeValue,
|
||||
onResetToDefaultValue,
|
||||
children,
|
||||
muiTheme,
|
||||
showHandle,
|
||||
showSelectionCheckbox,
|
||||
isSelected,
|
||||
onSelect,
|
||||
origin,
|
||||
}: Props) => {
|
||||
const isStructure = variable.isStructure();
|
||||
const key = '' + depth + name;
|
||||
|
||||
const limitEditing = origin === 'parent' || origin === 'inherited';
|
||||
|
||||
const columns = [
|
||||
<TreeTableCell key="name">
|
||||
{depth > 0 && (
|
||||
<Indent width={(depth + 1) * styles.tableChildIndentation} />
|
||||
)}
|
||||
{depth === 0 && showHandle && <DragHandle />}
|
||||
{showSelectionCheckbox && (
|
||||
{showSelectionCheckbox && !limitEditing && (
|
||||
<InlineCheckbox
|
||||
checked={isSelected}
|
||||
onCheck={(e, checked) => onSelect(checked)}
|
||||
/>
|
||||
)}
|
||||
<TextField
|
||||
style={{
|
||||
fontStyle: origin !== 'inherited' ? 'normal' : 'italic',
|
||||
}}
|
||||
fullWidth
|
||||
name={key + 'name'}
|
||||
defaultValue={name}
|
||||
errorText={errorText}
|
||||
onBlur={onBlur}
|
||||
disabled={origin === 'parent'}
|
||||
/>
|
||||
</TreeTableCell>,
|
||||
];
|
||||
@@ -85,22 +97,47 @@ const ThemableVariableRow = ({
|
||||
fullWidth
|
||||
name={key + 'value'}
|
||||
value={variable.getString()}
|
||||
onChange={onChangeValue}
|
||||
onChange={text => {
|
||||
if (variable.getString() !== text) {
|
||||
onChangeValue(text);
|
||||
}
|
||||
}}
|
||||
multiLine
|
||||
disabled={depth !== 0 && limitEditing} //GD doesn't support deep merging
|
||||
/>
|
||||
</TreeTableCell>
|
||||
);
|
||||
} else {
|
||||
columns.push(<TreeTableCell key="value">(Structure)</TreeTableCell>);
|
||||
columns.push(
|
||||
<TreeTableCell
|
||||
key="value"
|
||||
style={limitEditing ? styles.fadedButton : undefined}
|
||||
>
|
||||
(Structure)
|
||||
</TreeTableCell>
|
||||
);
|
||||
}
|
||||
columns.push(
|
||||
<TreeTableCell key="tools" style={styles.toolColumn}>
|
||||
<IconButton
|
||||
onClick={onAddChild}
|
||||
style={isStructure ? undefined : styles.fadedButton}
|
||||
>
|
||||
<AddCircle />
|
||||
</IconButton>
|
||||
{origin === 'inherited' && !isStructure && (
|
||||
<IconButton
|
||||
onClick={onResetToDefaultValue}
|
||||
style={isStructure ? undefined : styles.fadedButton}
|
||||
tooltip={'Reset'}
|
||||
>
|
||||
<Reset />
|
||||
</IconButton>
|
||||
)}
|
||||
{!limitEditing && (
|
||||
<IconButton
|
||||
onClick={onAddChild}
|
||||
style={isStructure ? undefined : styles.fadedButton}
|
||||
disabled={limitEditing}
|
||||
tooltip={'Add child variable'}
|
||||
>
|
||||
<AddCircle />
|
||||
</IconButton>
|
||||
)}
|
||||
</TreeTableCell>
|
||||
);
|
||||
|
||||
|
@@ -4,38 +4,58 @@ import FlatButton from 'material-ui/FlatButton';
|
||||
import Dialog from '../UI/Dialog';
|
||||
import { withSerializableObject } from '../Utils/SerializableObjectEditorContainer';
|
||||
import VariablesList from './index';
|
||||
|
||||
const gd = global.gd;
|
||||
|
||||
export class VariablesEditorDialog extends Component {
|
||||
render() {
|
||||
const {
|
||||
onCancel,
|
||||
onApply,
|
||||
open,
|
||||
onEditObjectVariables,
|
||||
title,
|
||||
emptyExplanationMessage,
|
||||
emptyExplanationSecondMessage,
|
||||
variablesContainer,
|
||||
} = this.props;
|
||||
const actions = [
|
||||
<FlatButton
|
||||
label={<Trans>Cancel</Trans>}
|
||||
onClick={this.props.onCancel}
|
||||
key={'Cancel'}
|
||||
/>,
|
||||
<FlatButton
|
||||
label={<Trans>Apply</Trans>}
|
||||
primary
|
||||
keyboardFocused
|
||||
onClick={this.props.onApply}
|
||||
onClick={onApply}
|
||||
key={'Apply'}
|
||||
/>,
|
||||
];
|
||||
const secondaryActions = onEditObjectVariables ? (
|
||||
<FlatButton
|
||||
label={<Trans>Edit Object Variables</Trans>}
|
||||
primary={false}
|
||||
onClick={onEditObjectVariables}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
noMargin
|
||||
actions={actions}
|
||||
modal
|
||||
open={this.props.open}
|
||||
onRequestClose={this.props.onCancel}
|
||||
open={open}
|
||||
onRequestClose={onCancel}
|
||||
autoScrollBodyContent
|
||||
secondaryActions={secondaryActions}
|
||||
title={title}
|
||||
>
|
||||
<VariablesList
|
||||
variablesContainer={this.props.variablesContainer}
|
||||
emptyExplanationMessage={this.props.emptyExplanationMessage}
|
||||
emptyExplanationSecondMessage={
|
||||
this.props.emptyExplanationSecondMessage
|
||||
}
|
||||
variablesContainer={variablesContainer}
|
||||
emptyExplanationMessage={emptyExplanationMessage}
|
||||
emptyExplanationSecondMessage={emptyExplanationSecondMessage}
|
||||
onSizeUpdated={
|
||||
() =>
|
||||
this.forceUpdate() /*Force update to ensure dialog is properly positionned*/
|
||||
|
2
newIDE/app/src/VariablesList/VariablesList.flow.js
Normal file
2
newIDE/app/src/VariablesList/VariablesList.flow.js
Normal file
@@ -0,0 +1,2 @@
|
||||
// @flow
|
||||
export type VariableOrigin = 'parent' | 'inherited' | '';
|
@@ -6,17 +6,13 @@ import {
|
||||
TableHeaderColumn,
|
||||
TableRow,
|
||||
} from 'material-ui/Table';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import ContentCopy from 'material-ui/svg-icons/content/content-copy';
|
||||
import ContentPaste from 'material-ui/svg-icons/content/content-paste';
|
||||
import Delete from 'material-ui/svg-icons/action/delete';
|
||||
import flatten from 'lodash/flatten';
|
||||
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
|
||||
import { mapFor } from '../Utils/MapFor';
|
||||
import EmptyMessage from '../UI/EmptyMessage';
|
||||
import newNameGenerator from '../Utils/NewNameGenerator';
|
||||
import VariableRow from './VariableRow';
|
||||
import AddVariableRow from './AddVariableRow';
|
||||
import EditVariableRow from './EditVariableRow';
|
||||
import styles from './styles';
|
||||
import {
|
||||
getInitialSelection,
|
||||
@@ -30,10 +26,12 @@ import {
|
||||
serializeToJSObject,
|
||||
unserializeFromJSObject,
|
||||
} from '../Utils/Serializer';
|
||||
import { type VariableOrigin } from './VariablesList.flow';
|
||||
|
||||
const gd = global.gd;
|
||||
|
||||
const SortableVariableRow = SortableElement(VariableRow);
|
||||
const SortableAddVariableRow = SortableElement(AddVariableRow);
|
||||
const SortableAddVariableRow = SortableElement(EditVariableRow);
|
||||
|
||||
class VariablesListBody extends React.Component<*, *> {
|
||||
render() {
|
||||
@@ -48,6 +46,7 @@ type VariableAndName = {| name: string, ptr: number, variable: gdVariable |};
|
||||
|
||||
type Props = {|
|
||||
variablesContainer: gdVariablesContainer,
|
||||
inheritedVariablesContainer?: ?gdVariablesContainer,
|
||||
emptyExplanationMessage?: string,
|
||||
emptyExplanationSecondMessage?: string,
|
||||
onSizeUpdated?: () => void,
|
||||
@@ -86,14 +85,18 @@ export default class VariablesList extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
paste = () => {
|
||||
const { variablesContainer } = this.props;
|
||||
const { variablesContainer, inheritedVariablesContainer } = this.props;
|
||||
if (!Clipboard.has(CLIPBOARD_KIND)) return;
|
||||
|
||||
const variables = Clipboard.get(CLIPBOARD_KIND);
|
||||
variables.forEach(({ name, serializedVariable }) => {
|
||||
const newName = newNameGenerator(
|
||||
name,
|
||||
name => variablesContainer.has(name),
|
||||
name =>
|
||||
inheritedVariablesContainer
|
||||
? inheritedVariablesContainer.has(name) ||
|
||||
variablesContainer.has(name)
|
||||
: variablesContainer.has(name),
|
||||
'CopyOf'
|
||||
);
|
||||
const newVariable = new gd.Variable();
|
||||
@@ -116,11 +119,15 @@ export default class VariablesList extends React.Component<Props, State> {
|
||||
|
||||
// Only delete ancestor variables, as selection can be composed of variables
|
||||
// that are contained inside others.
|
||||
const ancestorOnlyVariables = selection.filter(({ variable }) => {
|
||||
return selection.filter(
|
||||
const ancestorOnlyVariables = selection.filter(variableAndName => {
|
||||
// Make sure that the variable has no ancestor containing it
|
||||
return !selection.find(
|
||||
otherVariableAndName =>
|
||||
variable !== otherVariableAndName &&
|
||||
otherVariableAndName.variable.contains(variable)
|
||||
variableAndName !== otherVariableAndName &&
|
||||
otherVariableAndName.variable.contains(
|
||||
variableAndName.variable,
|
||||
/*recursive=*/ true
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -129,15 +136,44 @@ export default class VariablesList extends React.Component<Props, State> {
|
||||
ancestorOnlyVariables.forEach(({ variable }: VariableAndName) =>
|
||||
variablesContainer.removeRecursively(variable)
|
||||
);
|
||||
this.clearSelection();
|
||||
};
|
||||
|
||||
clearSelection = () => {
|
||||
this.setState({
|
||||
selectedVariables: getInitialSelection(),
|
||||
});
|
||||
};
|
||||
|
||||
_updateOrDefineVariable = (
|
||||
name: string,
|
||||
variable: gdVariable,
|
||||
newValue: string,
|
||||
index: number,
|
||||
origin: ?VariableOrigin
|
||||
) => {
|
||||
const { variablesContainer, inheritedVariablesContainer } = this.props;
|
||||
|
||||
if (inheritedVariablesContainer && origin === 'parent') {
|
||||
const serializedVariable = serializeToJSObject(
|
||||
inheritedVariablesContainer.get(name)
|
||||
);
|
||||
const newVariable = new gd.Variable();
|
||||
unserializeFromJSObject(newVariable, serializedVariable);
|
||||
variablesContainer.insert(name, newVariable, index);
|
||||
newVariable.delete();
|
||||
|
||||
variablesContainer.get(name).setString(newValue);
|
||||
} else {
|
||||
variable.setString(newValue);
|
||||
}
|
||||
};
|
||||
|
||||
_renderVariableChildren(
|
||||
name: string,
|
||||
parentVariable: gdVariable,
|
||||
depth: number
|
||||
depth: number,
|
||||
origin: VariableOrigin
|
||||
): Array<React.Node> {
|
||||
const names = parentVariable.getAllChildrenNames().toJSArray();
|
||||
|
||||
@@ -149,22 +185,34 @@ export default class VariablesList extends React.Component<Props, State> {
|
||||
variable,
|
||||
depth + 1,
|
||||
index,
|
||||
parentVariable
|
||||
parentVariable,
|
||||
origin
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
_getVariableOrigin = (name: string) => {
|
||||
const { variablesContainer, inheritedVariablesContainer } = this.props;
|
||||
|
||||
if (!inheritedVariablesContainer || !inheritedVariablesContainer.has(name))
|
||||
return '';
|
||||
return variablesContainer.has(name) ? 'inherited' : 'parent';
|
||||
};
|
||||
|
||||
_renderVariableAndChildrenRows(
|
||||
name: string,
|
||||
variable: gdVariable,
|
||||
depth: number,
|
||||
index: number,
|
||||
parentVariable: ?gdVariable
|
||||
parentVariable: ?gdVariable,
|
||||
parentOrigin: ?VariableOrigin = null
|
||||
) {
|
||||
const { variablesContainer } = this.props;
|
||||
const isStructure = variable.isStructure();
|
||||
|
||||
const origin = parentOrigin ? parentOrigin : this._getVariableOrigin(name);
|
||||
|
||||
return (
|
||||
<SortableVariableRow
|
||||
name={name}
|
||||
@@ -173,13 +221,20 @@ export default class VariablesList extends React.Component<Props, State> {
|
||||
variable={variable}
|
||||
disabled={depth !== 0}
|
||||
depth={depth}
|
||||
origin={origin}
|
||||
errorText={
|
||||
this.state.nameErrors[variable.ptr]
|
||||
? 'This name is already taken'
|
||||
: undefined
|
||||
}
|
||||
onChangeValue={text => {
|
||||
variable.setString(text);
|
||||
this._updateOrDefineVariable(name, variable, text, index, origin);
|
||||
|
||||
this.forceUpdate();
|
||||
if (this.props.onSizeUpdated) this.props.onSizeUpdated();
|
||||
}}
|
||||
onResetToDefaultValue={() => {
|
||||
variablesContainer.removeRecursively(variable);
|
||||
this.forceUpdate();
|
||||
if (this.props.onSizeUpdated) this.props.onSizeUpdated();
|
||||
}}
|
||||
@@ -189,7 +244,7 @@ export default class VariablesList extends React.Component<Props, State> {
|
||||
|
||||
let success = true;
|
||||
if (!parentVariable) {
|
||||
success = this.props.variablesContainer.rename(name, text);
|
||||
success = variablesContainer.rename(name, text);
|
||||
} else {
|
||||
success = parentVariable.renameChild(name, text);
|
||||
}
|
||||
@@ -222,7 +277,7 @@ export default class VariablesList extends React.Component<Props, State> {
|
||||
}}
|
||||
children={
|
||||
isStructure
|
||||
? this._renderVariableChildren(name, variable, depth)
|
||||
? this._renderVariableChildren(name, variable, depth, origin)
|
||||
: null
|
||||
}
|
||||
showHandle={this.state.mode === 'move'}
|
||||
@@ -237,27 +292,48 @@ export default class VariablesList extends React.Component<Props, State> {
|
||||
|
||||
_renderEmpty() {
|
||||
return (
|
||||
<div>
|
||||
<EmptyMessage
|
||||
style={styles.emptyExplanation}
|
||||
messageStyle={styles.emptyExplanationMessage}
|
||||
>
|
||||
{this.props.emptyExplanationMessage || ''}
|
||||
</EmptyMessage>
|
||||
<EmptyMessage
|
||||
style={styles.emptyExplanation}
|
||||
messageStyle={styles.emptyExplanationMessage}
|
||||
>
|
||||
{this.props.emptyExplanationSecondMessage}
|
||||
</EmptyMessage>
|
||||
</div>
|
||||
!!this.props.emptyExplanationMessage && (
|
||||
<div>
|
||||
<EmptyMessage
|
||||
style={styles.emptyExplanation}
|
||||
messageStyle={styles.emptyExplanationMessage}
|
||||
>
|
||||
{this.props.emptyExplanationMessage}
|
||||
</EmptyMessage>
|
||||
<EmptyMessage
|
||||
style={styles.emptyExplanation}
|
||||
messageStyle={styles.emptyExplanationMessage}
|
||||
>
|
||||
{this.props.emptyExplanationSecondMessage}
|
||||
</EmptyMessage>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { variablesContainer } = this.props;
|
||||
const { variablesContainer, inheritedVariablesContainer } = this.props;
|
||||
if (!variablesContainer) return null;
|
||||
|
||||
// Display inherited variables, if any
|
||||
const containerInheritedVariablesTree = inheritedVariablesContainer
|
||||
? mapFor(0, inheritedVariablesContainer.count(), index => {
|
||||
const name = inheritedVariablesContainer.getNameAt(index);
|
||||
if (!variablesContainer.has(name)) {
|
||||
// Show only variables from parent container that are not redefined
|
||||
const variable = inheritedVariablesContainer.getAt(index);
|
||||
return this._renderVariableAndChildrenRows(
|
||||
name,
|
||||
variable,
|
||||
0,
|
||||
index,
|
||||
undefined,
|
||||
'parent'
|
||||
);
|
||||
}
|
||||
})
|
||||
: [];
|
||||
|
||||
const containerVariablesTree = mapFor(
|
||||
0,
|
||||
variablesContainer.count(),
|
||||
@@ -275,7 +351,7 @@ export default class VariablesList extends React.Component<Props, State> {
|
||||
}
|
||||
);
|
||||
|
||||
const addRow = (
|
||||
const editRow = (
|
||||
<SortableAddVariableRow
|
||||
index={0}
|
||||
key={'add-variable-row'}
|
||||
@@ -284,13 +360,20 @@ export default class VariablesList extends React.Component<Props, State> {
|
||||
const variable = new gd.Variable();
|
||||
variable.setString('');
|
||||
const name = newNameGenerator('Variable', name =>
|
||||
variablesContainer.has(name)
|
||||
inheritedVariablesContainer
|
||||
? inheritedVariablesContainer.has(name) ||
|
||||
variablesContainer.has(name)
|
||||
: variablesContainer.has(name)
|
||||
);
|
||||
variablesContainer.insert(name, variable, variablesContainer.count());
|
||||
|
||||
this.forceUpdate();
|
||||
if (this.props.onSizeUpdated) this.props.onSizeUpdated();
|
||||
}}
|
||||
onCopy={this.copySelection}
|
||||
onPaste={this.paste}
|
||||
onDeleteSelection={this.deleteSelection}
|
||||
hasSelection={hasSelection(this.state.selectedVariables)}
|
||||
hasClipboard={Clipboard.has(CLIPBOARD_KIND)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -301,26 +384,7 @@ export default class VariablesList extends React.Component<Props, State> {
|
||||
<TableRow>
|
||||
<TableHeaderColumn>Name</TableHeaderColumn>
|
||||
<TableHeaderColumn>Value</TableHeaderColumn>
|
||||
<TableHeaderColumn style={styles.toolColumnHeader}>
|
||||
<IconButton
|
||||
onClick={this.copySelection}
|
||||
disabled={!hasSelection(this.state.selectedVariables)}
|
||||
>
|
||||
<ContentCopy />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={this.paste}
|
||||
disabled={!Clipboard.has(CLIPBOARD_KIND)}
|
||||
>
|
||||
<ContentPaste />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={this.deleteSelection}
|
||||
disabled={!hasSelection(this.state.selectedVariables)}
|
||||
>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</TableHeaderColumn>
|
||||
<TableHeaderColumn style={styles.toolColumnHeader} />
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
</Table>
|
||||
@@ -334,9 +398,11 @@ export default class VariablesList extends React.Component<Props, State> {
|
||||
useDragHandle
|
||||
lockToContainerEdges
|
||||
>
|
||||
{!!containerInheritedVariablesTree.length &&
|
||||
containerInheritedVariablesTree}
|
||||
{!containerVariablesTree.length && this._renderEmpty()}
|
||||
{!!containerVariablesTree.length && containerVariablesTree}
|
||||
{addRow}
|
||||
{editRow}
|
||||
</SortableVariablesListBody>
|
||||
</div>
|
||||
);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
export default {
|
||||
toolColumnHeader: {
|
||||
textAlign: 'right',
|
||||
paddingRight: 8,
|
||||
textAlign: 'left',
|
||||
paddingRight: 4,
|
||||
},
|
||||
toolColumn: {
|
||||
minWidth: 48,
|
||||
|
@@ -1374,6 +1374,9 @@ storiesOf('InstancePropertiesEditor', module)
|
||||
project={project}
|
||||
layout={testLayout}
|
||||
instances={[testLayoutInstance1]}
|
||||
editInstanceVariables={action('edit instance variables')}
|
||||
editObjectVariables={action('edit object variables')}
|
||||
onEditObjectByName={action('edit object')}
|
||||
/>
|
||||
</SerializedObjectDisplay>
|
||||
));
|
||||
|
Reference in New Issue
Block a user