1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-28 16:17:02 +00:00

delete ctable-module (#3842)

This commit is contained in:
杨顺强
2019-07-13 10:38:32 +08:00
committed by Daniel Pan
parent 6b01e56f47
commit f0d9ac813f
22 changed files with 50 additions and 3264 deletions

View File

@@ -1,26 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'reactstrap';
const propTypes = {
onSave: PropTypes.func.isRequired,
};
const { fileName } = window.app.pageOptions;
class AddHeader extends React.Component {
render() {
let {isContentChanged} = this.props;
return (
<div id="header">
<div className="sf-font">{fileName}</div>
<Button color="primary" onClick={this.props.onSave} disabled={!isContentChanged}>保存</Button>
</div>
);
}
}
AddHeader.propTypes = propTypes;
export default AddHeader;

View File

@@ -1,187 +0,0 @@
import React from 'react';
import isHotkey from 'is-hotkey';
import ReactDataGrid from '@seafile/react-data-grid/';
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 GridHeaderContextMenu from './grid-header-contextmenu';
import GridContentContextMenu from './grid-content-contextmenu';
import DTableStore from './store/dtable-store';
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.state = {
value: null,
isNewColumnDialogShow: false,
};
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);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.onHotKey);
}
getRowAt = (index) => {
if (index < 0 || index > this.getSize()) {
return undefined;
}
return this.state.value.rows[index];
};
getSize = () => {
return this.state.value.rows.length;
};
onInsertRow = () => {
let newRowIndex = this.getSize();
let value = this.dTableStore.insertRow(newRowIndex);
this.setState({value});
this.props.onContentChanged();
}
onInsertColumn = () => {
this.setState({isNewColumnDialogShow: true});
}
onNewColumn = (columnName, columnType) => {
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 value = this.dTableStore.deleteRow(rowIdx);
this.setState({value});
this.props.onContentChanged();
}
onColumnDelete = (e, data) => {
let column = data.column;
let idx = column.idx - 1;
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 = () => {
return this.dTableStore.serializeGridData();
}
onHotKey = (event) => {
if (isHotkey('mod+s', event)) {
event.preventDefault();
this.props.onSave();
return true;
}
}
render() {
if (!this.state.value) {
return '';
}
return (
<div id="main">
<ReactDataGrid
ref={ node => this.grid = node }
enableCellSelect={true}
columns={this.state.value.columns}
rowGetter={this.getRowAt}
rowsCount={this.getSize()}
onGridRowsUpdated={this.handleGridRowsUpdated}
enableRowSelect={true}
rowHeight={50}
minHeight={500}
rowScrollTimeout={200}
enableInsertColumn={true}
enableInsertRow={true}
onInsertRow={this.onInsertRow}
onInsertColumn={this.onInsertColumn}
RowsContainer={Menu.ContextMenuTrigger}
headerContextMenu={<GridHeaderContextMenu id="grid-header-contxtmenu" onColumnDelete={this.onColumnDelete} />}
contextMenu={<GridContentContextMenu id="grid-content-contxtmenu" onRowDelete={this.onRowDelete} />}
onColumnResize={this.onColumnResize}
/>
{this.state.isNewColumnDialogShow && (
<ModalPortal>
<NewColumnDialog
onNewColumnCancel={this.onNewColumnCancel}
onNewColumn={this.onNewColumn}
/>
</ModalPortal>
)}
</div>
);
}
}
export default AppMain;

View File

@@ -1,33 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Menu } from '@seafile/react-data-grid-addons';
const propTypes = {
id: PropTypes.string.isRequired,
idx: PropTypes.number,
rowIdx: PropTypes.number,
onRowDelete: PropTypes.func.isRequired,
};
class GridContentContextMenu extends React.Component {
onRowDelete = (e, data) => {
if (typeof(this.props.onRowDelete) === 'function') {
this.props.onRowDelete(e, data);
}
};
render() {
const { idx, id, rowIdx } = this.props;
return (
<Menu.ContextMenu id={id}>
<Menu.MenuItem data={{ rowIdx, idx }} onClick={this.onRowDelete}>Delete Row</Menu.MenuItem>
</Menu.ContextMenu>
);
}
}
GridContentContextMenu.propTypes = propTypes;
export default GridContentContextMenu;

View File

@@ -1,33 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Menu } from '@seafile/react-data-grid-addons';
const propTypes = {
id: PropTypes.string.isRequired,
column: PropTypes.object,
onColumnRename: PropTypes.func,
};
class GridHeaderContextMenu extends React.Component {
onColumnDelete = (e, data) => {
if (typeof(this.props.onColumnDelete) === 'function') {
this.props.onColumnDelete(e, data);
}
}
render() {
let { id, column } = this.props;
return (
<Menu.ContextMenu id={id}>
<Menu.MenuItem data={{ column }} onClick={this.onColumnDelete}>Delete</Menu.MenuItem>
</Menu.ContextMenu>
);
}
}
GridHeaderContextMenu.propTypes = propTypes;
export default GridHeaderContextMenu;

View File

@@ -1,13 +0,0 @@
export default class GridColumn {
constructor(object) {
this.key = object.columnName || object.name;
this.name = object.columnName || object.name;
this.type = object.columnType || null;
this.editor = null;
this.editable = object.editable || true;
this.width = object.width || 200;
this.resizable = object.resizable || true;
}
}

View File

@@ -1,7 +0,0 @@
export default class GridRow {
constructor(object) {
this.name = object.name || 'name_' + object.newRowIdx;
}
}

View File

@@ -1,76 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Label, Input, Alert, Button } from 'reactstrap';
import { gettext } from '../../utils/constants';
const propTypes = {
onNewColumnCancel: PropTypes.func,
onNewColumn: PropTypes.func,
};
class NewCoumnDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
columnType: 'text',
columnName: '',
errMessage: '',
};
}
handleChange = (event) => {
let value = event.target.value.trim();
this.setState({columnName: value});
}
onSelectChange = (event) => {
let type = event.target.value;
this.setState({columnType: type});
}
toggle = () => {
this.props.onNewColumnCancel();
}
handleSubmit = () => {
let { columnName, columnType } = this.state;
if (!columnName) {
this.setState({errMessage: gettext('Name is required.')});
return;
}
this.props.onNewColumn(columnName, columnType);
}
render() {
return (
<Modal isOpen={true} toggle={this.toggle}>
<ModalHeader>New Column</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<Label for="columnName">Name</Label>
<Input id="columnName" value={this.state.columnName} innerRef={input => {this.newInput = input;}} onChange={this.handleChange} />
</FormGroup>
<FormGroup>
<Label for="typeSelect">Type</Label>
<Input id="typeSelect" type='select' name="select" onChange={this.onSelectChange}>
<option vlaue="text">text</option>
<option value="number">number</option>
</Input>
</FormGroup>
</Form>
{this.state.errMessage && <Alert color="danger" className="mt-2">{this.state.errMessage}</Alert>}
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
</ModalFooter>
</Modal>
);
}
}
NewCoumnDialog.propTypes = propTypes;
export default NewCoumnDialog;

View File

@@ -1,61 +0,0 @@
import OperationTypes from './operation-types';
import GridColumn from '../model/grid-column';
import GridRow from '../model/grid-row';
function apply(value, op) {
let { type } = op;
let next = value.slice(0); // clone a copy
switch(type) {
case OperationTypes.DELETE_ROW : {
let { rowIdx } = op;
next.splice(rowIdx, 1);
return next;
}
case OperationTypes.INSERT_ROW : {
let { newRowIdx } = op;
let row = new GridRow({newRowIdx});
next.push(row);
return next;
}
case OperationTypes.DELETE_COLUMN : {
let { idx } = op;
next.splice(idx, 1);
return next;
}
case OperationTypes.INSERT_COLUMN : {
let { idx, columnName, columnType } = op;
let column = new GridColumn({idx, columnName, columnType});
next.push(column);
return next;
}
case OperationTypes.MODIFY_CELL : {
let { rowIdx, updated } = op;
let updateRow = next[rowIdx];
updateRow = Object.assign({}, {...updateRow}, updated); // todo
next[rowIdx] = updateRow;
return next;
}
case OperationTypes.MODIFY_COLUMN : {
let { idx, newColumnName } = op;
next[idx]['key'] = newColumnName;
next[idx]['name'] = newColumnName;
return next;
}
case OperationTypes.RESIZE_COLUMN : {
let { idx, width } = op;
next[idx].width = width;
return next;
}
}
}
export default apply;

View File

@@ -1,134 +0,0 @@
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() {
this.value = {};
this.value.columns = [];
this.value.rows = [];
this.operations = [];
}
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) {
return new Operation(op);
}
deleteRow(rowIdx) {
let type = OperationTypes.DELETE_ROW;
let operation = this.createOperation({type, rowIdx});
let next = operation.apply(this.value.rows);
this.operations.push(operation);
this.value.rows = next;
return this.value;
}
insertRow(newRowIdx) {
let type = OperationTypes.INSERT_ROW;
let operation = this.createOperation({type, newRowIdx});
let next = operation.apply(this.value.rows);
this.operations.push(operation);
this.value.rows = next;
return this.value;
}
deleteColumn(idx) {
let type = OperationTypes.DELETE_COLUMN;
let operation = this.createOperation({type, idx});
let next = operation.apply(this.value.columns);
this.operations.push(operation);
this.value.columns = 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.value.columns);
this.operations.push(operation);
this.value.columns = 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.value.columns);
this.operations.push(operation);
this.value.columns = next;
return this.value;
}
modifyCell(rowIdx, updated) {
let type = OperationTypes.MODIFY_CELL;
let operation = this.createOperation({type, rowIdx, updated});
let next = operation.apply(this.value.rows);
this.operations.push(operation);
this.value.rows = 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;
}
}

View File

@@ -1,35 +0,0 @@
import Operation from './operation';
import OperationTypes from './operation-types';
function invert(operation) {
let op = new Operation(operation);
let { type } = operation;
switch(type) {
case OperationTypes.DELETE_COLUMN : {
op.type = OperationTypes.INSERT_COLUMN;
return op;
}
case OperationTypes.INSERT_COLUMN : {
op.type = OperationTypes.DELETE_COLUMN;
return op;
}
case OperationTypes.DELETE_ROW : {
op.type = OperationTypes.INSERT_ROW;
return op;
}
case OperationTypes.INSERT_ROW : {
op.type = OperationTypes.DELETE_ROW;
return op;
}
default :
break;
}
}
export default invert;

View File

@@ -1,13 +0,0 @@
const OperationTypes = {
DELETE_ROW: 'DELETE_NODE',
INSERT_ROW: 'INSERT_ROW',
DELETE_COLUMN: 'DELETE_COLUMN',
INSERT_COLUMN: 'INSERT_COLUMN',
MODIFY_CELL: 'MODIFY_CELL',
MODIFY_COLUMN: 'MODIFY_COLUMN',
RESIZE_COLUMN: 'RESIZE_COLUMN',
};
export default OperationTypes;

View File

@@ -1,20 +0,0 @@
import apply from './apply';
import invert from './invert';
export default class Operation {
constructor(operation) {
this.operation = operation;
}
apply(value) {
let next = apply(value, this.operation);
return next;
}
invert() {
let inverted = invert(this);
return inverted;
}
}

View File

@@ -1,28 +0,0 @@
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 <Editors.NumberEditor />;
}
case EDITOR_TEXT: {
return '';
}
default: {
return '';
}
}
}
}
let editorFactory = new EditorFactory();
export default editorFactory;

View File

@@ -1,58 +0,0 @@
import React, { Fragment } from 'react';
import ReactDOM from 'react-dom';
import AppHeader from './pages/data-grid/app-header';
import AppMain from './pages/data-grid/app-main';
import { seafileAPI } from './utils/seafile-api';
import { Utils } from './utils/utils';
import { gettext } from './utils/constants';
import toaster from './components/toast';
import './css/layout.css';
import './css/file-view-data-grid.css';
import './css/react-context-menu.css';
const { repoID, fileName, filePath } = window.app.pageOptions;
class ViewFileSDB extends React.Component {
constructor(props) {
super(props);
this.state = {
isContentChanged: false,
};
}
onContentChanged = () => {
this.setState({isContentChanged: true});
}
onSave = () => {
this.setState({isContentChanged: false});
let data = this.refs.data_grid.serializeGridData();
let dirPath = Utils.getDirName(filePath);
seafileAPI.getUpdateLink(repoID, dirPath).then(res => {
let updateLink = res.data;
seafileAPI.updateFile(updateLink, filePath, fileName, JSON.stringify(data)).then(res => {
toaster.success(gettext('File saved.'));
}).catch(() => {
toaster.success(gettext('File save failed.'));
});
});
}
render() {
return (
<Fragment>
<AppHeader onSave={this.onSave} isContentChanged={this.state.isContentChanged} />
<AppMain ref="data_grid" onContentChanged={this.onContentChanged} onSave={this.onSave} />
</Fragment>
);
}
}
ReactDOM.render(
<ViewFileSDB />,
document.getElementById('wrapper')
);