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:
Todor Imreorov
2019-02-19 21:19:27 +00:00
committed by Florian Rival
parent be950a7779
commit 72282d4e6d
13 changed files with 452 additions and 212 deletions

View File

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

View File

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

View File

@@ -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."
/>

View File

@@ -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."
/>

View 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;
}

View File

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

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

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
// @flow
export type VariableOrigin = 'parent' | 'inherited' | '';

View File

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

View File

@@ -1,7 +1,7 @@
export default {
toolColumnHeader: {
textAlign: 'right',
paddingRight: 8,
textAlign: 'left',
paddingRight: 4,
},
toolColumn: {
minWidth: 48,

View File

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