[WIP] Add copy/paste/delete in VariablesList in newIDE

This commit is contained in:
Florian Rival
2018-04-01 19:44:18 +02:00
parent eed844357e
commit 10833aa45d
11 changed files with 299 additions and 81 deletions

View File

@@ -89,5 +89,6 @@
"newIDE/electron-app/app/www": true
},
// Support for Flowtype:
"javascript.validate.enable": false
"javascript.validate.enable": false,
"flow.useNPMPackagedFlow": true
}

View File

@@ -5,24 +5,23 @@
*/
#include "GDCore/Project/Variable.h"
#include "GDCore/String.h"
#include <sstream>
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
#include "GDCore/TinyXml/tinyxml.h"
#include <sstream>
using namespace std;
namespace gd
{
namespace gd {
/**
* Get value as a double
*/
double Variable::GetValue() const
{
if (!isNumber)
{
stringstream ss; ss << str;
if (!isNumber) {
stringstream ss;
ss << str;
ss >> value;
isNumber = true;
}
@@ -30,11 +29,11 @@ double Variable::GetValue() const
return value;
}
const gd::String & Variable::GetString() const
const gd::String& Variable::GetString() const
{
if (isNumber)
{
stringstream s; s << (value);
if (isNumber) {
stringstream s;
s << (value);
str = s.str();
isNumber = false;
}
@@ -42,7 +41,7 @@ const gd::String & Variable::GetString() const
return str;
}
bool Variable::HasChild(const gd::String & name) const
bool Variable::HasChild(const gd::String& name) const
{
return isStructure && children.find(name) != children.end();
}
@@ -53,10 +52,11 @@ bool Variable::HasChild(const gd::String & name) const
* If the variable is not a structure or has not
* the specified child, an empty variable is returned.
*/
Variable & Variable::GetChild(const gd::String & name)
Variable& Variable::GetChild(const gd::String& name)
{
std::map<gd::String, Variable>::iterator it = children.find(name);
if ( it != children.end() ) return it->second;
if (it != children.end())
return it->second;
isStructure = true;
Variable newEmptyVariable;
@@ -70,10 +70,11 @@ Variable & Variable::GetChild(const gd::String & name)
* If the variable is not a structure or has not
* the specified child, an empty variable is returned.
*/
const Variable & Variable::GetChild(const gd::String & name) const
const Variable& Variable::GetChild(const gd::String& name) const
{
std::map<gd::String, Variable>::iterator it = children.find(name);
if ( it != children.end() ) return it->second;
if (it != children.end())
return it->second;
isStructure = true;
Variable newEmptyVariable;
@@ -81,15 +82,17 @@ const Variable & Variable::GetChild(const gd::String & name) const
return children[name];
}
void Variable::RemoveChild(const gd::String & name)
void Variable::RemoveChild(const gd::String& name)
{
if ( !isStructure ) return;
if (!isStructure)
return;
children.erase(name);
}
bool Variable::RenameChild(const gd::String & oldName, const gd::String & newName)
bool Variable::RenameChild(const gd::String& oldName, const gd::String& newName)
{
if ( !isStructure || !HasChild(oldName)|| HasChild(newName) ) return false;
if (!isStructure || !HasChild(oldName) || HasChild(newName))
return false;
children[newName] = children[oldName];
children.erase(oldName);
@@ -99,63 +102,58 @@ bool Variable::RenameChild(const gd::String & oldName, const gd::String & newNam
void Variable::ClearChildren()
{
if ( !isStructure ) return;
if (!isStructure)
return;
children.clear();
}
void Variable::SerializeTo(SerializerElement & element) const
void Variable::SerializeTo(SerializerElement& element) const
{
if (!isStructure)
element.SetAttribute("value", GetString());
else
{
SerializerElement & childrenElement = element.AddChild("children");
else {
SerializerElement& childrenElement = element.AddChild("children");
childrenElement.ConsiderAsArrayOf("variable");
for (std::map<gd::String, gd::Variable>::iterator i = children.begin(); i != children.end(); ++i)
{
SerializerElement & variableElement = childrenElement.AddChild("variable");
for (std::map<gd::String, gd::Variable>::iterator i = children.begin(); i != children.end(); ++i) {
SerializerElement& variableElement = childrenElement.AddChild("variable");
variableElement.SetAttribute("name", i->first);
i->second.SerializeTo(variableElement);
}
}
}
void Variable::UnserializeFrom(const SerializerElement & element)
void Variable::UnserializeFrom(const SerializerElement& element)
{
isStructure = element.HasChild("children", "Children");
if (isStructure)
{
const SerializerElement & childrenElement = element.GetChild("children", 0, "Children");
if (isStructure) {
const SerializerElement& childrenElement = element.GetChild("children", 0, "Children");
childrenElement.ConsiderAsArrayOf("variable", "Variable");
for (int i = 0; i < childrenElement.GetChildrenCount(); ++i)
{
const SerializerElement & childElement = childrenElement.GetChild(i);
for (int i = 0; i < childrenElement.GetChildrenCount(); ++i) {
const SerializerElement& childElement = childrenElement.GetChild(i);
gd::String name = childElement.GetStringAttribute("name", "", "Name");
gd::Variable childVariable;
childVariable.UnserializeFrom(childElement);
children[name] = childVariable;
}
}
else
} else
SetString(element.GetStringAttribute("value", "", "Value"));
}
void Variable::SaveToXml(TiXmlElement * element) const
void Variable::SaveToXml(TiXmlElement* element) const
{
if (!element) return;
if (!element)
return;
if ( !isStructure )
if (!isStructure)
element->SetAttribute("Value", GetString().c_str());
else
{
TiXmlElement * childrenElem = new TiXmlElement( "Children" );
element->LinkEndChild( childrenElem );
for (std::map<gd::String, gd::Variable>::iterator i = children.begin(); i != children.end(); ++i)
{
TiXmlElement * variable = new TiXmlElement( "Variable" );
childrenElem->LinkEndChild( variable );
else {
TiXmlElement* childrenElem = new TiXmlElement("Children");
element->LinkEndChild(childrenElem);
for (std::map<gd::String, gd::Variable>::iterator i = children.begin(); i != children.end(); ++i) {
TiXmlElement* variable = new TiXmlElement("Variable");
childrenElem->LinkEndChild(variable);
variable->SetAttribute("Name", i->first.c_str());
i->second.SaveToXml(variable);
@@ -163,17 +161,16 @@ void Variable::SaveToXml(TiXmlElement * element) const
}
}
void Variable::LoadFromXml(const TiXmlElement * element)
void Variable::LoadFromXml(const TiXmlElement* element)
{
if (!element) return;
if (!element)
return;
isStructure = element->FirstChildElement("Children") != NULL;
if ( isStructure )
{
const TiXmlElement * child = element->FirstChildElement("Children")->FirstChildElement();
while ( child )
{
if (isStructure) {
const TiXmlElement* child = element->FirstChildElement("Children")->FirstChildElement();
while (child) {
gd::String name = child->Attribute("Name") ? child->Attribute("Name") : "";
gd::Variable childVariable;
childVariable.LoadFromXml(child);
@@ -181,9 +178,31 @@ void Variable::LoadFromXml(const TiXmlElement * element)
child = child->NextSiblingElement();
}
}
else if (element->Attribute("Value"))
} else if (element->Attribute("Value"))
SetString(element->Attribute("Value"));
}
bool Variable::Contains(const gd::Variable& variableToSearch, bool recursive) const
{
for (auto& it : children) {
if (&it.second == &variableToSearch)
return true;
if (recursive && it.second.Contains(variableToSearch, true))
return true;
}
return false;
}
void Variable::RemoveRecursively(const gd::Variable& variableToRemove)
{
for (auto it = children.begin(); it != children.end();) {
if (&(it->second) == &variableToRemove) {
it = children.erase(it);
} else {
it->second.RemoveRecursively(variableToRemove);
it++;
}
}
}
}

View File

@@ -153,6 +153,15 @@ public:
*/
const std::map<gd::String, Variable> & GetAllChildren() const { return children; }
/**
* \brief Search if a variable is part of the children, optionally recursively
*/
bool Contains(const gd::Variable & variableToSearch, bool recursive) const;
/**
* \brief Remove the specified variable if it can be found in the children
*/
void RemoveRecursively(const gd::Variable & variableToRemove);
///@}
/** \name Serialization

View File

@@ -102,6 +102,18 @@ void VariablesContainer::Remove(const gd::String & varName)
VariableHasName(varName)), variables.end() );
}
void VariablesContainer::RemoveRecursively(const gd::Variable & variableToRemove)
{
variables.erase(std::remove_if(variables.begin(), variables.end(),
[&variableToRemove](const std::pair<gd::String, gd::Variable> & nameAndVariable) {
return &variableToRemove == &nameAndVariable.second;
}), variables.end() );
for(auto & it: variables) {
it.second.RemoveRecursively(variableToRemove);
}
}
std::size_t VariablesContainer::GetPosition(const gd::String & name) const
{
for(std::size_t i = 0;i<variables.size();++i)

View File

@@ -94,10 +94,16 @@ public:
Variable & InsertNew(const gd::String & name, std::size_t position = -1);
/**
* \brief Remove the specified variable from the container.
* \brief Remove the variable with the specified name from the container.
* \note This operation is not recursive on variable children.
*/
void Remove(const gd::String & name);
/**
* \brief Remove the specified variable from the container.
*/
void RemoveRecursively(const gd::Variable & variable);
/**
* \brief Rename a variable.
* \return true if the variable was renamed, false otherwise.

View File

@@ -0,0 +1,43 @@
// @flow
/**
* Export functions to manipulate a selection of objects.
*/
import values from 'lodash/values';
type ObjectType = { ptr: number };
type SelectionState<T> = {
[number]: ?T,
};
export const getInitialSelection = () => ({});
export const clearSelection = () => getInitialSelection();
export const getSelection = <T: ObjectType>(
selection: SelectionState<T>
): Array<T> => values(selection).filter(value => !!value);
export const addToSelection = <T: ObjectType>(
selection: SelectionState<T>,
object: T,
select: boolean = true
): SelectionState<T> => {
console.log(object, select);
return {
...selection,
[object.ptr]: select ? object : null,
};
};
export const isSelected = <T: ObjectType>(
selection: SelectionState<T>,
object: T
): boolean => !!selection[object.ptr];
export const hasSelection = <T: ObjectType>(
selection: SelectionState<T>
): boolean => {
return !!values(selection).filter(value => !!value).length;
};

View File

@@ -0,0 +1 @@
export const CLIPBOARD_KIND = 'Variables';

View File

@@ -1,8 +1,9 @@
import React from 'react';
// @flow
import * as React from 'react';
import { TreeTableRow, TreeTableCell } from '../UI/TreeTable';
import DragHandle from '../UI/DragHandle';
import SemiControlledTextField from '../UI/SemiControlledTextField';
import Delete from 'material-ui/svg-icons/action/delete';
import Checkbox from 'material-ui/Checkbox';
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';
@@ -16,6 +17,25 @@ const Indent = ({ width }) => (
</div>
);
const InlineCheckbox = props => <Checkbox {...props} style={{ width: 32 }} />;
type Props = {|
name: string,
variable: gdVariable,
depth: number,
errorText?: ?string,
onBlur: () => void,
onRemove: () => void,
onAddChild: () => void,
onChangeValue: string => void,
children?: React.Node,
muiTheme: Object,
showHandle: boolean,
showSelectionCheckbox: boolean,
isSelected: boolean,
onSelect: boolean => void,
|};
const ThemableVariableRow = ({
name,
variable,
@@ -27,7 +47,11 @@ const ThemableVariableRow = ({
onChangeValue,
children,
muiTheme,
}) => {
showHandle,
showSelectionCheckbox,
isSelected,
onSelect,
}: Props) => {
const isStructure = variable.isStructure();
const key = '' + depth + name;
@@ -36,7 +60,13 @@ const ThemableVariableRow = ({
{depth > 0 && (
<Indent width={(depth + 1) * styles.tableChildIndentation} />
)}
{depth === 0 && <DragHandle />}
{depth === 0 && showHandle && <DragHandle />}
{showSelectionCheckbox && (
<InlineCheckbox
checked={isSelected}
onCheck={(e, checked) => onSelect(checked)}
/>
)}
<TextField
fullWidth
name={key + 'name'}
@@ -64,9 +94,6 @@ const ThemableVariableRow = ({
}
columns.push(
<TreeTableCell key="tools" style={styles.toolColumn}>
<IconButton onClick={onRemove}>
<Delete />
</IconButton>
<IconButton onClick={onAddChild}>
<AddCircle />
</IconButton>

View File

@@ -1,11 +1,15 @@
// @flow
import React, { Component } from 'react';
import {
Table,
TableHeader,
TableHeaderColumn,
TableRow,
TableRowColumn,
} 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';
@@ -14,12 +18,20 @@ import newNameGenerator from '../Utils/NewNameGenerator';
import VariableRow from './VariableRow';
import AddVariableRow from './AddVariableRow';
import styles from './styles';
import {
getInitialSelection,
hasSelection,
addToSelection,
getSelection,
} from '../Utils/SelectionHandler';
import { CLIPBOARD_KIND } from './ClipboardKind';
import Clipboard from '../Utils/Clipboard';
const gd = global.gd;
const SortableVariableRow = SortableElement(VariableRow);
const SortableAddVariableRow = SortableElement(AddVariableRow);
class VariablesListBody extends Component {
class VariablesListBody extends Component<*, *> {
render() {
return <div>{this.props.children}</div>;
}
@@ -28,16 +40,70 @@ class VariablesListBody extends Component {
const SortableVariablesListBody = SortableContainer(VariablesListBody);
SortableVariablesListBody.muiName = 'TableBody';
export default class VariablesList extends Component {
constructor() {
super();
type VariableAndName = {| name: string, ptr: number, variable: gdVariable |};
this.state = {
nameErrors: {},
};
}
type Props = {|
variablesContainer: gdVariablesContainer,
emptyExplanationMessage?: string,
emptyExplanationSecondMessage?: string,
|};
type State = {|
nameErrors: { [string]: string },
selectedVariables: { [number]: ?VariableAndName },
mode: 'select' | 'move',
|};
_renderVariableChildren(name, parentVariable, depth) {
export default class VariablesList extends Component<Props, State> {
state = {
nameErrors: {},
selectedVariables: getInitialSelection(),
mode: 'select',
};
_selectVariable = (variableAndName: VariableAndName, select: boolean) => {
this.setState({
selectedVariables: addToSelection(
this.state.selectedVariables,
variableAndName,
select
),
});
};
copySelection = () => {
Clipboard.set(CLIPBOARD_KIND, getSelection(this.state.selectedVariables));
};
paste = () => {
const { variablesContainer } = this.props;
if (!Clipboard.has(CLIPBOARD_KIND)) return;
const variables = Clipboard.get(CLIPBOARD_KIND);
variables.forEach(({ name, variable }) =>
variablesContainer.insert(name, variable, variablesContainer.count())
);
this.forceUpdate();
};
deleteSelection = () => {
const { variablesContainer } = this.props;
const selection: Array<VariableAndName> = getSelection(
this.state.selectedVariables
);
selection.forEach(({ variable }: VariableAndName) =>
variablesContainer.removeRecursively(variable)
);
this.setState({
selectedVariables: getInitialSelection(),
})
};
_renderVariableChildren(
name: string,
parentVariable: gdVariable,
depth: number
) {
const children = parentVariable.getAllChildren();
const names = children.keys().toJSArray();
@@ -55,7 +121,13 @@ export default class VariablesList extends Component {
);
}
_renderVariableAndChildrenRows(name, variable, depth, index, parentVariable) {
_renderVariableAndChildrenRows(
name: string,
variable: gdVariable,
depth: number,
index: number,
parentVariable: ?gdVariable
) {
const { variablesContainer } = this.props;
const isStructure = variable.isStructure();
@@ -119,6 +191,11 @@ export default class VariablesList extends Component {
? this._renderVariableChildren(name, variable, depth)
: null
}
showHandle={this.state.mode === 'move'}
showSelectionCheckbox={this.state.mode === 'select'}
isSelected={!!this.state.selectedVariables[variable.ptr]}
onSelect={select =>
this._selectVariable({ name, ptr: variable.ptr, variable }, select)}
/>
);
}
@@ -190,7 +267,26 @@ export default class VariablesList extends Component {
<TableRow>
<TableHeaderColumn>Name</TableHeaderColumn>
<TableHeaderColumn>Value</TableHeaderColumn>
<TableRowColumn />
<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>
</TableRow>
</TableHeader>
</Table>

View File

@@ -1,6 +1,10 @@
export default {
toolColumnHeader: {
textAlign: 'right',
paddingRight: 8,
},
toolColumn: {
minWidth: 72,
minWidth: 48,
flex: 0,
justifyContent: 'flex-end',
},

View File

@@ -23,8 +23,8 @@
"7zip-bin-win" "^2.1.1"
"@types/node@^7.0.18":
version "7.0.48"
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.48.tgz#24bfdc0aa82e8f6dbd017159c58094a2e06d0abb"
version "7.0.58"
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.58.tgz#ae852120137f40a29731a559e48003bd2d5d19f7"
ajv-keywords@^2.1.1:
version "2.1.1"