Enable variables to be re-order with drag'n'drop in VariablesList in newIDE

This commit is contained in:
Florian Rival
2017-06-18 12:51:33 +02:00
parent 7dbd836073
commit 0d0ce7d44d
13 changed files with 183 additions and 74 deletions

View File

@@ -139,6 +139,16 @@ void VariablesContainer::Swap(std::size_t firstVariableIndex, std::size_t second
variables[firstVariableIndex] = variables[secondVariableIndex];
variables[secondVariableIndex] = temp;
}
void VariablesContainer::Move(std::size_t oldIndex, std::size_t newIndex)
{
if ( oldIndex >= variables.size() || newIndex >= variables.size() )
return;
auto nameAndVariable = variables[oldIndex];
variables.erase(variables.begin() + oldIndex);
Insert(nameAndVariable.first, nameAndVariable.second, newIndex);
}
#endif
void VariablesContainer::SerializeTo(SerializerElement & element) const

View File

@@ -108,6 +108,11 @@ public:
* \brief Swap the position of the specified variables.
*/
void Swap(std::size_t firstVariableIndex, std::size_t secondVariableIndex);
/**
* \brief Move the specified variable at a new position in the list.
*/
void Move(std::size_t oldIndex, std::size_t newIndex);
#endif
/**

View File

@@ -1,9 +1,8 @@
export default {
handleColumn: {
width: 10,
width: 24,
paddingLeft: 8,
paddingRight: 0,
marginRight: -8,
},
visibleColumn: {
width: 48,

View File

@@ -43,7 +43,9 @@ a[href^="ftp://"] {
user-drag: auto; /* Technically not supported in Electron yet */
}
/* Used for react-sortable-hoc SortableContainer, to make sure the
* dragged element is visible (and not hidden behind a drawer for example)
*/
.sortable-helper {
z-index: 9999;
}

View File

@@ -18,6 +18,7 @@ import ExternalLayoutEditor from '../SceneEditor/ExternalLayoutEditor';
import ProjectManager from '../ProjectManager';
import LoaderModal from '../UI/LoaderModal';
import EditorBar from '../UI/EditorBar';
import Window from '../Utils/Window';
import defaultTheme from '../UI/Theme/DefaultTheme';
import { Tabs, Tab } from '../UI/Tabs';
import {
@@ -56,7 +57,7 @@ export default class MainFrame extends Component {
componentWillMount() {
if (!this.props.integratedEditor) this.openStartPage();
if (this.props.introDialog) this._openIntroDialog(true);
if (this.props.introDialog && !Window.isDev()) this._openIntroDialog(true);
}
loadFullProject = (serializedProject, cb) => {

View File

@@ -1,13 +1,18 @@
import React from 'react';
import DragHandleIcon from 'material-ui/svg-icons/editor/drag-handle';
import { SortableHandle } from 'react-sortable-hoc';
const styles = {
handle: {
color: '#BBB',
fontSize: 15,
cursor: 'move',
marginRight: 4,
},
handleColor: '#DDD',
};
const DragHandle = SortableHandle(() => <span style={styles.handle}>::</span>);
const DragHandle = SortableHandle(() => (
<span style={styles.handle}>
<DragHandleIcon color={styles.handleColor} />
</span>
));
export default DragHandle;

View File

@@ -1,23 +1,24 @@
import React from 'react';
const styles = {
messageStyle: {
opacity: 0.4,
textAlign: 'center',
fontSize: '13px',
textShadow: '1px 1px 0px white',
},
containerStyle: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flex: 1,
padding: 10,
},
};
export default props => (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flex: 1,
padding: 10,
}}
>
<span
style={{
opacity: 0.4,
textAlign: 'center',
fontSize: '13px',
textShadow: '1px 1px 0px white',
}}
>
<div style={{...styles.containerStyle, ...props.style}}>
<span style={styles.messageStyle}>
{props.children}
</span>
</div>

View File

@@ -0,0 +1,32 @@
import React from 'react';
const styles = {
row: {
display: 'flex',
},
cell: {
display: 'flex',
flex: 1,
alignItems: 'center',
paddingLeft: 8,
paddingRight: 8,
}
};
export const TreeTable = props => (
<div>
{props.children}
</div>
);
export const TreeTableRow = props => (
<div style={{...styles.row, ...props.style}}>
{props.children}
</div>
);
export const TreeTableCell = props => (
<div style={{...styles.cell, ...props.style}}>
{props.children}
</div>
);

View File

@@ -1,19 +1,24 @@
import React from 'react';
import { TableRow, TableRowColumn } from 'material-ui/Table';
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 }) => (
<TableRow key="add-row">
<TableRowColumn />
<TableRowColumn />
<TableRowColumn style={styles.toolColumn}>
<TreeTableRow key="add-row">
<TreeTableCell />
<TreeTableCell>
<EmptyMessage style={styles.addVariableMessage}>
Click to add a variable:
</EmptyMessage>
</TreeTableCell>
<TreeTableCell style={styles.toolColumn}>
<IconButton onTouchTap={onAdd}>
<Add />
</IconButton>
</TableRowColumn>
</TableRow>
</TreeTableCell>
</TreeTableRow>
);
export default VariableRow;

View File

@@ -1,70 +1,85 @@
import React from 'react';
import { TableRow, TableRowColumn } from 'material-ui/Table';
import { TreeTableRow, TreeTableCell } from '../UI/TreeTable';
import DragHandle from '../UI/DragHandle';
import Delete from 'material-ui/svg-icons/action/delete';
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 styles from './styles';
const Indent = ({width}) => (
<div style={{ ...styles.indent, width }}>
<SubdirectoryArrowRight color={styles.indentIconColor} />
</div>
)
const VariableRow = (
{
name,
variable,
depth,
index,
parentVariable,
errorText,
onBlur,
onRemove,
onAddChild,
onChangeValue,
children,
}
) => {
const isStructure = variable.isStructure();
const key = '' + depth + name;
const columns = [
<TableRowColumn
<TreeTableCell
key="name"
style={{ paddingLeft: (depth + 1) * styles.tableChildIndentation }}
>
{depth > 0 && <Indent width={(depth + 1) * styles.tableChildIndentation} />}
{depth === 0 && <DragHandle />}
<TextField
fullWidth
name={key + 'name'}
defaultValue={name}
errorText={errorText}
onBlur={onBlur}
/>
</TableRowColumn>,
</TreeTableCell>,
];
if (!isStructure) {
columns.push(
<TableRowColumn key="value">
<TreeTableCell key="value">
<TextField
fullWidth
name={key + 'value'}
value={variable.getString()}
onChange={onChangeValue}
multiLine
/>
</TableRowColumn>
</TreeTableCell>
);
} else {
columns.push(<TableRowColumn key="value">(Structure)</TableRowColumn>);
columns.push(<TreeTableCell key="value">(Structure)</TreeTableCell>);
}
columns.push(
<TableRowColumn key="tools" style={styles.toolColumn}>
<TreeTableCell key="tools" style={styles.toolColumn}>
<IconButton onTouchTap={onRemove}>
<Delete />
</IconButton>
<IconButton onTouchTap={onAddChild}>
<AddCircle />
</IconButton>
</TableRowColumn>
</TreeTableCell>
);
return (
<TableRow>
{columns}
</TableRow>
<div>
<TreeTableRow
style={styles.variableRow}
>
{columns}
</TreeTableRow>
{children}
</div>
);
};

View File

@@ -1,7 +1,6 @@
import React, { Component } from 'react';
import {
Table,
TableBody,
TableHeader,
TableHeaderColumn,
TableRow,
@@ -11,9 +10,26 @@ import { mapFor } from '../Utils/MapFor';
import newNameGenerator from '../Utils/NewNameGenerator';
import VariableRow from './VariableRow';
import AddVariableRow from './AddVariableRow';
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
import flatten from 'lodash/flatten';
const gd = global.gd;
const SortableVariableRow = SortableElement(VariableRow);
const SortableAddVariableRow = SortableElement(AddVariableRow);
class VariablesListBody extends Component {
render() {
return (
<div>
{this.props.children}
</div>
);
}
}
const SortableVariablesListBody = SortableContainer(VariablesListBody);
SortableVariablesListBody.muiName = 'TableBody';
export default class VariablesList extends Component {
constructor() {
super();
@@ -23,18 +39,18 @@ export default class VariablesList extends Component {
};
}
_renderVariableChildren(name, parentVariable, depth, index) {
_renderVariableChildren(name, parentVariable, depth) {
const children = parentVariable.getAllChildren();
const names = children.keys().toJSArray();
return flatten(
names.map(name => {
names.map((name, index) => {
const variable = children.get(name);
return this._renderVariableAndChildrenRows(
name,
variable,
depth + 1,
undefined,
index,
parentVariable
);
})
@@ -44,16 +60,15 @@ export default class VariablesList extends Component {
_renderVariableAndChildrenRows(name, variable, depth, index, parentVariable) {
const { variablesContainer } = this.props;
const isStructure = variable.isStructure();
const key = '' + depth + name;
const variableRow = (
<VariableRow
return (
<SortableVariableRow
name={name}
variable={variable}
depth={depth}
index={index}
key={key}
parentVariable={parentVariable}
key={'variable-' + name}
variable={variable}
disabled={depth !== 0}
depth={depth}
errorText={
this.state.nameErrors[variable.ptr]
? 'This name is already taken'
@@ -95,15 +110,9 @@ export default class VariablesList extends Component {
variable.getChild(name).setString('');
this.forceUpdate();
}}
children={isStructure ? this._renderVariableChildren(name, variable, depth) : null}
/>
);
return !isStructure
? [variableRow]
: [
variableRow,
this._renderVariableChildren(name, variable, depth, index),
];
}
render() {
@@ -129,8 +138,10 @@ export default class VariablesList extends Component {
);
const addRow = (
<AddVariableRow
<SortableAddVariableRow
index={0}
key={'add-variable-row'}
disabled
onAdd={() => {
const variable = new gd.Variable();
variable.setString('');
@@ -153,14 +164,18 @@ export default class VariablesList extends Component {
<TableRowColumn />
</TableRow>
</TableHeader>
<TableBody
displayRowCheckbox={false}
style={{
backgroundColor: 'white',
<SortableVariablesListBody
variablesContainer={this.props.variablesContainer}
onSortEnd={({ oldIndex, newIndex }) => {
this.props.variablesContainer.move(oldIndex, newIndex);
this.forceUpdate();
}}
helperClass="sortable-helper"
useDragHandle
lockToContainerEdges
>
{flatten(containerVariablesTree).concat(addRow)}
</TableBody>
{containerVariablesTree.concat(addRow)}
</SortableVariablesListBody>
</Table>
);
}

View File

@@ -1,7 +1,21 @@
export default {
toolColumn: {
width: 192,
textAlign: 'right',
minWidth: 72,
flex: 0,
justifyContent: 'flex-end',
},
tableChildIndentation: 24,
};
indent: {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
},
indentIconColor: '#DDD',
variableRow: {
backgroundColor: 'white',
height: 42,
},
addVariableMessage: {
justifyContent: 'flex-end',
},
};

View File

@@ -10,6 +10,7 @@ import StartPage from '../MainFrame/StartPage';
import AboutDialog from '../MainFrame/AboutDialog';
import LocalCreateDialog from '../ProjectCreation/LocalCreateDialog';
import { Tabs, Tab } from '../UI/Tabs';
import DragHandle from '../UI/DragHandle';
import LocalFolderPicker from '../UI/LocalFolderPicker';
import LocalExport from '../Export/LocalExport';
import LocalMobileExport from '../Export/LocalMobileExport';
@@ -117,4 +118,8 @@ storiesOf('AboutDialog', module)
storiesOf('LocalCreateDialog', module)
.addDecorator(muiDecorator)
.add('default', () => <LocalCreateDialog open />);
.add('default', () => <LocalCreateDialog open />);
storiesOf('DragHandle', module)
.addDecorator(muiDecorator)
.add('default', () => <DragHandle />);