diff --git a/frontend/src/pages/data-grid/app-main.js b/frontend/src/pages/data-grid/app-main.js index 2e86407d8b..18c02a54e4 100644 --- a/frontend/src/pages/data-grid/app-main.js +++ b/frontend/src/pages/data-grid/app-main.js @@ -1,207 +1,138 @@ import React from 'react'; -import PropTypes from 'prop-types'; +import isHotkey from 'is-hotkey'; import ReactDataGrid from '@seafile/react-data-grid/'; -import update from 'immutability-helper'; -import { Menu, Editors } from '@seafile/react-data-grid-addons'; -import GridHeaderContextMenu from './grid-header-contextmenu'; -import GridContentContextMenu from './grid-content-contextmenu'; +import { Menu } from '@seafile/react-data-grid-addons'; +import { seafileAPI } from '../../utils/seafile-api'; import ModalPortal from '../../components/modal-portal'; import NewColumnDialog from './new-column-dialog'; -import isHotkey from 'is-hotkey'; +import GridHeaderContextMenu from './grid-header-contextmenu'; +import GridContentContextMenu from './grid-content-contextmenu'; import DTableStore from './store/dtable-store'; -const propTypes = { - initData: PropTypes.object.isRequired, -}; +const { repoID, filePath } = window.app.pageOptions; + +const DEFAULT_DATA = { + columns: [ + { + key: 'name', + name: 'Name', + type: '', + width: 80, + editable: true, + resizable: true + } + ], + rows: [{name: 'name_' + 0}] +} class AppMain extends React.Component { constructor(props, context) { super(props, context); - this._columns = [ - { - key: 'name', - name: 'Name', - width: 80, - editable: true, - resizable: true - } - ]; - - let initData = props.initData; - this.state = { - columns: initData.columns.length ? this.deseralizeGridData(initData.columns) : this._columns, - rows: initData.rows.length ? initData.rows : this.createRows(1), + value: null, isNewColumnDialogShow: false, }; - - this.dTableStore = new DTableStore(initData); + this.dTableStore = new DTableStore(); } componentDidMount() { + + seafileAPI.getFileDownloadLink(repoID, filePath).then(res => { + let url = res.data; + seafileAPI.getFileContent(url).then(res => { + let data = res.data ? res.data : JSON.stringify(DEFAULT_DATA); + let value = this.dTableStore.deseralizeGridData(data); + this.setState({value}); + }); + }); + document.addEventListener('keydown', this.onHotKey); } - componentWillReceiveProps(nextProps) { - - if (nextProps.isContentChanged) { - return; - } - - let data = nextProps.initData; - this.deseralizeGridData(data); - } - componentWillUnmount() { document.removeEventListener('keydown', this.onHotKey); } - createRows = (numberOfRows) => { - let rows = []; - for (let i = 0; i < numberOfRows; i++) { - rows[i] = this.createFakeRowObjectData(i); - } - return rows; - }; - - createFakeRowObjectData = (index) => { - return {name: 'name_' + index}; - }; - - getColumns = () => { - let clonedColumns = this.state.columns.slice(); - return clonedColumns; - }; - - handleGridRowsUpdated = ({ fromRow, toRow, updated }) => { - let rows = this.state.rows.slice(); - - for (let i = fromRow; i <= toRow; i++) { - let rowToUpdate = rows[i]; - let updatedRow = update(rowToUpdate, {$merge: updated}); - rows[i] = updatedRow; - } - - this.setState({ rows }); - this.props.onContentChanged(); - }; - - handleAddRow = ({ newRowIndex }) => { - const newRow = { - name: 'name_' + newRowIndex, - }; - - let rows = this.state.rows.slice(); - rows = update(rows, {$push: [newRow]}); - this.setState({ rows }); - this.props.onContentChanged(); - }; - getRowAt = (index) => { if (index < 0 || index > this.getSize()) { return undefined; } - return this.state.rows[index]; + return this.state.value.rows[index]; }; getSize = () => { - return this.state.rows.length; + return this.state.value.rows.length; }; onInsertRow = () => { + let newRowIndex = this.getSize(); - let rows = this.dTableStore.insertRow(newRowIndex); - this.setState({rows}); + let value = this.dTableStore.insertRow(newRowIndex); + this.setState({value}); + this.props.onContentChanged(); } onInsertColumn = () => { this.setState({isNewColumnDialogShow: true}); - this.props.onContentChanged(); - } - - onColumnResize = (index, width) => { - let columns = this.state.columns.slice(); - columns[index - 1].width = width; - this.setState({columns: columns}); - this.props.onContentChanged(); } onNewColumn = (columnName, columnType) => { - let idx = this.state.columns.length; - let columns = this.dTableStore.insertColumn(idx, columnName, columnType); - columns = this.formatColumnsData(columns); - this.setState({columns: columns}); + + let idx = this.state.value.columns.length; + let value = this.dTableStore.insertColumn(idx, columnName, columnType); + this.setState({value}); this.onNewColumnCancel(); + this.props.onContentChanged(); } onNewColumnCancel = () => { this.setState({isNewColumnDialogShow: false}); + this.props.onContentChanged(); } onRowDelete = (e, data) => { + let { rowIdx } = data; - let rows = this.dTableStore.deleteRow(rowIdx); - this.setState({rows}); + let value = this.dTableStore.deleteRow(rowIdx); + this.setState({value}); + this.props.onContentChanged(); } onColumnDelete = (e, data) => { + let column = data.column; let idx = column.idx - 1; - let columns = this.dTableStore.deleteColumn(idx); - this.setState({columns}); + let value = this.dTableStore.deleteColumn(idx); + this.setState({value}); + + this.props.onContentChanged(); + } + + onColumnResize = (index, width) => { + + let idx = index - 1; + let value = this.dTableStore.resizeColumn(idx, width); + this.setState({value}); + + this.props.onContentChanged(); + } + + handleGridRowsUpdated = ({fromRow, updated}) => { + let rowIdx = fromRow; + let value = this.dTableStore.modifyCell(rowIdx, updated); + this.setState({value}); + this.props.onContentChanged(); } serializeGridData = () => { - let gridData = { - columns: JSON.stringify(this.state.columns), - rows: JSON.stringify(this.state.rows), - }; - return gridData; - } - - deseralizeGridData = (data) => { - let columns = JSON.parse(data.columns); - let rows = JSON.parse(data.rows); - columns = this.formatColumnsData(columns); - this.setState({ - columns: columns, - rows: rows, - }); - - this.dTableStore.updateStoreValues({columns, rows}); - } - - formatColumnsData = (columns) => { - return columns.map(column => { - if (column.editor) { - let editor = this.createEditor(column.editor); - column.editor = editor; - } - return column; - }); - } - - createEditor = (editorType) => { - let editor = null; - switch (editorType) { - case 'number': - editor = ; - break; - case 'text': - editor = null; - break; - default: - break; - } - - return editor; + return this.dTableStore.serializeGridData(); } onHotKey = (event) => { @@ -213,13 +144,17 @@ class AppMain extends React.Component { } render() { - let columns = this.getColumns(); + + if (!this.state.value) { + return ''; + } + return (
this.grid = node } enableCellSelect={true} - columns={columns} + columns={this.state.value.columns} rowGetter={this.getRowAt} rowsCount={this.getSize()} onGridRowsUpdated={this.handleGridRowsUpdated} @@ -249,6 +184,4 @@ class AppMain extends React.Component { } } -AppMain.propTypes = propTypes; - export default AppMain; diff --git a/frontend/src/pages/data-grid/model/grid-column.js b/frontend/src/pages/data-grid/model/grid-column.js index 50b02ef01a..89bbbf559e 100644 --- a/frontend/src/pages/data-grid/model/grid-column.js +++ b/frontend/src/pages/data-grid/model/grid-column.js @@ -3,7 +3,8 @@ export default class GridColumn { constructor(object) { this.key = object.columnName || object.name; this.name = object.columnName || object.name; - this.editor = object.columnType || null; + this.type = object.columnType || null; + this.editor = null; this.editable = object.editable || true; this.width = object.width || 200; this.resizable = object.resizable || true; diff --git a/frontend/src/pages/data-grid/store/apply.js b/frontend/src/pages/data-grid/store/apply.js index 3085811486..6393ef35e9 100644 --- a/frontend/src/pages/data-grid/store/apply.js +++ b/frontend/src/pages/data-grid/store/apply.js @@ -35,8 +35,10 @@ function apply(value, op) { } case OperationTypes.MODIFY_CELL : { - let { rowIdx, key, newCellValue } = op; - next[rowIdx][key] = newCellValue; + let { rowIdx, updated } = op; + let updateRow = next[rowIdx]; + updateRow = Object.assign({}, {...updateRow}, updated); // todo + next[rowIdx] = updateRow; return next; } @@ -46,6 +48,13 @@ function apply(value, op) { next[idx]['name'] = newColumnName; return next; } + + case OperationTypes.RESIZE_COLUMN : { + let { idx, width } = op; + next[idx].width = width; + + return next; + } } } diff --git a/frontend/src/pages/data-grid/store/dtable-store.js b/frontend/src/pages/data-grid/store/dtable-store.js index e24982cb45..a57414ad76 100644 --- a/frontend/src/pages/data-grid/store/dtable-store.js +++ b/frontend/src/pages/data-grid/store/dtable-store.js @@ -1,18 +1,49 @@ import Operation from './operation'; import OperationTypes from './operation-types'; +import editorFactory from '../utils/editor-factory'; // todo Immutable // Implement the current version with an array + export default class DTableStore { - constructor(value) { + constructor() { + this.value = {}; + this.value.columns = []; + this.value.rows = []; this.operations = []; - this.columns = value.columns || []; - this.rows = value.rows || []; } - updateStoreValues({ columns, rows }) { - this.columns = columns; - this.rows = rows; + serializeGridData() { + + let value = this.value; + let columns = value.columns.map(column => { + delete column.editor; // delete editor attr; + return column; + }); + + value.columns = columns; + + return JSON.stringify(value); + } + + deseralizeGridData(gridData) { + + gridData = JSON.parse(gridData); + let columns = gridData.columns; + let rows = gridData.rows; + + columns = columns.map(column => { + if (column.type) { + let editor = editorFactory.createEditor(column.type); + column.editor = editor; + } + return column; + }); + + this.value.columns = columns; + this.value.rows = rows; + + return this.value; } createOperation(op) { @@ -22,68 +53,82 @@ export default class DTableStore { deleteRow(rowIdx) { let type = OperationTypes.DELETE_ROW; let operation = this.createOperation({type, rowIdx}); - let next = operation.apply(this.rows); + let next = operation.apply(this.value.rows); this.operations.push(operation); - this.rows = next; + this.value.rows = next; - return next; + return this.value; } insertRow(newRowIdx) { let type = OperationTypes.INSERT_ROW; let operation = this.createOperation({type, newRowIdx}); - let next = operation.apply(this.rows); + let next = operation.apply(this.value.rows); this.operations.push(operation); - this.rows = next; + this.value.rows = next; - return next; + return this.value; } deleteColumn(idx) { let type = OperationTypes.DELETE_COLUMN; let operation = this.createOperation({type, idx}); - let next = operation.apply(this.columns); + let next = operation.apply(this.value.columns); this.operations.push(operation); - this.columns = next; + this.value.columns = next; - return next; + return this.value; } insertColumn(idx, columnName, columnType) { let type = OperationTypes.INSERT_COLUMN; let operation = this.createOperation({type, idx, columnName, columnType}); - let next = operation.apply(this.columns); + let next = operation.apply(this.value.columns); this.operations.push(operation); - this.columns = next; + this.value.columns = next; - return next; + let value = this.serializeGridData(); + this.deseralizeGridData(value); + + return this.value; } modifyColumn(idx, oldColumnName, newColumnName) { let type = OperationTypes.MODIFY_COLUMN; let operation = this.createOperation({type, idx, oldColumnName, newColumnName}); - let next = operation.apply(this.columns); + let next = operation.apply(this.value.columns); this.operations.push(operation); - this.columns = next; + this.value.columns = next; - return next; + return this.value; } - modifyCell(idx, rowIdx, oldCellValue, newCellValue) { + modifyCell(rowIdx, updated) { let type = OperationTypes.MODIFY_CELL; - let key = this.columns[idx].key; - let operation = this.createOperation({type, rowIdx, key, oldCellValue, newCellValue}); - let next = operation.apply(this.rows); + let operation = this.createOperation({type, rowIdx, updated}); + + let next = operation.apply(this.value.rows); this.operations.push(operation); - this.rows = next; + this.value.rows = next; - return next; + return this.value; + } + + resizeColumn(idx, width) { + let type = OperationTypes.RESIZE_COLUMN; + let operation = this.createOperation({type, idx, width}); + let next = operation.apply(this.value.columns); + + this.operations.push(operation); + this.value.columns = next; + + return this.value; } } diff --git a/frontend/src/pages/data-grid/store/operation-types.js b/frontend/src/pages/data-grid/store/operation-types.js index dd5842b79f..47d3a0c3be 100644 --- a/frontend/src/pages/data-grid/store/operation-types.js +++ b/frontend/src/pages/data-grid/store/operation-types.js @@ -5,6 +5,7 @@ const OperationTypes = { INSERT_COLUMN: 'INSERT_COLUMN', MODIFY_CELL: 'MODIFY_CELL', MODIFY_COLUMN: 'MODIFY_COLUMN', + RESIZE_COLUMN: 'RESIZE_COLUMN', }; export default OperationTypes; diff --git a/frontend/src/pages/data-grid/utils/editor-factory.js b/frontend/src/pages/data-grid/utils/editor-factory.js new file mode 100644 index 0000000000..f68ad64819 --- /dev/null +++ b/frontend/src/pages/data-grid/utils/editor-factory.js @@ -0,0 +1,28 @@ +import React from 'react' +import { Editors } from '@seafile/react-data-grid-addons'; + +const EDITOR_NUMBER = 'number'; +const EDITOR_TEXT = 'text'; + +class EditorFactory { + + createEditor(editorType) { + switch(editorType) { + case EDITOR_NUMBER: { + return + } + + case EDITOR_TEXT: { + return ''; + } + + default: { + return ''; + } + } + } +} + +let editorFactory = new EditorFactory(); + +export default editorFactory; \ No newline at end of file diff --git a/frontend/src/view-file-ctable.js b/frontend/src/view-file-ctable.js index 3a3af1e294..0e98a503c1 100644 --- a/frontend/src/view-file-ctable.js +++ b/frontend/src/view-file-ctable.js @@ -11,46 +11,30 @@ import './css/layout.css'; import './css/file-view-data-grid.css'; import './css/react-context-menu.css'; -const { repoID, fileName, filePath, err, enableWatermark, userNickName } = window.app.pageOptions; +const { repoID, fileName, filePath } = window.app.pageOptions; class ViewFileSDB extends React.Component { constructor(props) { super(props); this.state = { - initData: { - columns: [], - rows: [], - }, isContentChanged: false, }; - } - componentDidMount() { - seafileAPI.getFileDownloadLink(repoID, filePath).then(res => { - let url = res.data; - seafileAPI.getFileContent(url).then(res => { - let data = res.data; - if (data) { - this.setState({initData: data}); - } - }); - }); + onContentChanged = () => { + this.setState({isContentChanged: true}); } onSave = () => { + this.setState({isContentChanged: false}); + let data = this.refs.data_grid.serializeGridData(); - this.setState({ - initData: data, - isContentChanged: false - }) - + let dirPath = Utils.getDirName(filePath); seafileAPI.getUpdateLink(repoID, dirPath).then(res => { let updateLink = res.data; - let updateData = JSON.stringify(data); - seafileAPI.updateFile(updateLink, filePath, fileName, updateData).then(res => { + seafileAPI.updateFile(updateLink, filePath, fileName, JSON.stringify(data)).then(res => { toaster.success(gettext('File saved.')); }).catch(() => { toaster.success(gettext('File save failed.')); @@ -58,24 +42,11 @@ class ViewFileSDB extends React.Component { }); } - onContentChanged = () => { - this.setState({isContentChanged: true}); - } - render() { return ( - - + + ); }