mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-14 22:33:17 +00:00
Revert "Combine dtable"
This commit is contained in:
@@ -220,9 +220,9 @@ module.exports = {
|
||||
paths.appSrc + "/pages/sys-admin",
|
||||
],
|
||||
viewDataGrid: [
|
||||
// require.resolve('./polyfills'),
|
||||
require.resolve('./polyfills'),
|
||||
require.resolve('react-dev-utils/webpackHotDevClient'),
|
||||
paths.appSrc + "/view-file-dtable.js",
|
||||
paths.appSrc + "/view-file-ctable.js",
|
||||
],
|
||||
viewCdoc: [
|
||||
require.resolve('./polyfills'),
|
||||
|
@@ -92,7 +92,7 @@ module.exports = {
|
||||
repoFolderTrash: [require.resolve('./polyfills'), paths.appSrc + "/repo-folder-trash.js"],
|
||||
orgAdmin: [require.resolve('./polyfills'), paths.appSrc + "/pages/org-admin"],
|
||||
sysAdmin: [require.resolve('./polyfills'), paths.appSrc + "/pages/sys-admin"],
|
||||
viewDataGrid: [require.resolve('./polyfills'), paths.appSrc + "/view-file-dtable.js"],
|
||||
viewDataGrid: [require.resolve('./polyfills'), paths.appSrc + "/view-file-ctable.js"],
|
||||
viewCdoc: [require.resolve('./polyfills'), paths.appSrc + "/view-file-cdoc.js"],
|
||||
search: [require.resolve('./polyfills'), paths.appSrc + "/pages/search"]
|
||||
},
|
||||
|
2912
frontend/package-lock.json
generated
2912
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@reach/router": "^1.2.0",
|
||||
"@seafile/dtable": "0.0.25",
|
||||
"@seafile/react-data-grid": "^6.1.5",
|
||||
"@seafile/react-data-grid-addons": "^6.1.5",
|
||||
"@seafile/resumablejs": "^1.1.9",
|
||||
@@ -30,15 +29,15 @@
|
||||
"prismjs": "^1.15.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"raf": "3.4.0",
|
||||
"react": "^16.8.6",
|
||||
"react": "^16.4.2",
|
||||
"react-codemirror": "^1.0.0",
|
||||
"react-cookies": "^0.1.0",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-dom": "^16.5.2",
|
||||
"react-image-lightbox": "^5.1.0",
|
||||
"react-moment": "^0.7.9",
|
||||
"react-responsive": "^6.1.1",
|
||||
"react-select": "^2.4.1",
|
||||
"reactstrap": "^8.0.0",
|
||||
"reactstrap": "^6.4.0",
|
||||
"seafile-js": "^0.2.91",
|
||||
"socket.io-client": "^2.2.0",
|
||||
"sw-precache-webpack-plugin": "0.11.4",
|
||||
|
@@ -1,3 +0,0 @@
|
||||
.rc-calendar table tbody tr {
|
||||
height: fit-content;
|
||||
}
|
26
frontend/src/pages/data-grid/app-header.js
Normal file
26
frontend/src/pages/data-grid/app-header.js
Normal file
@@ -0,0 +1,26 @@
|
||||
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;
|
187
frontend/src/pages/data-grid/app-main.js
Normal file
187
frontend/src/pages/data-grid/app-main.js
Normal file
@@ -0,0 +1,187 @@
|
||||
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;
|
33
frontend/src/pages/data-grid/grid-content-contextmenu.js
Normal file
33
frontend/src/pages/data-grid/grid-content-contextmenu.js
Normal file
@@ -0,0 +1,33 @@
|
||||
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;
|
33
frontend/src/pages/data-grid/grid-header-contextmenu.js
Normal file
33
frontend/src/pages/data-grid/grid-header-contextmenu.js
Normal file
@@ -0,0 +1,33 @@
|
||||
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;
|
||||
|
||||
|
13
frontend/src/pages/data-grid/model/grid-column.js
Normal file
13
frontend/src/pages/data-grid/model/grid-column.js
Normal file
@@ -0,0 +1,13 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
7
frontend/src/pages/data-grid/model/grid-row.js
Normal file
7
frontend/src/pages/data-grid/model/grid-row.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export default class GridRow {
|
||||
|
||||
constructor(object) {
|
||||
this.name = object.name || 'name_' + object.newRowIdx;
|
||||
}
|
||||
|
||||
}
|
76
frontend/src/pages/data-grid/new-column-dialog.js
Normal file
76
frontend/src/pages/data-grid/new-column-dialog.js
Normal file
@@ -0,0 +1,76 @@
|
||||
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;
|
61
frontend/src/pages/data-grid/store/apply.js
Normal file
61
frontend/src/pages/data-grid/store/apply.js
Normal file
@@ -0,0 +1,61 @@
|
||||
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;
|
134
frontend/src/pages/data-grid/store/dtable-store.js
Normal file
134
frontend/src/pages/data-grid/store/dtable-store.js
Normal file
@@ -0,0 +1,134 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
35
frontend/src/pages/data-grid/store/invert.js
Normal file
35
frontend/src/pages/data-grid/store/invert.js
Normal file
@@ -0,0 +1,35 @@
|
||||
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;
|
13
frontend/src/pages/data-grid/store/operation-types.js
Normal file
13
frontend/src/pages/data-grid/store/operation-types.js
Normal file
@@ -0,0 +1,13 @@
|
||||
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;
|
||||
|
||||
|
20
frontend/src/pages/data-grid/store/operation.js
Normal file
20
frontend/src/pages/data-grid/store/operation.js
Normal file
@@ -0,0 +1,20 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
28
frontend/src/pages/data-grid/utils/editor-factory.js
Normal file
28
frontend/src/pages/data-grid/utils/editor-factory.js
Normal file
@@ -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 <Editors.NumberEditor />;
|
||||
}
|
||||
|
||||
case EDITOR_TEXT: {
|
||||
return '';
|
||||
}
|
||||
|
||||
default: {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let editorFactory = new EditorFactory();
|
||||
|
||||
export default editorFactory;
|
58
frontend/src/view-file-ctable.js
Normal file
58
frontend/src/view-file-ctable.js
Normal file
@@ -0,0 +1,58 @@
|
||||
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')
|
||||
);
|
@@ -1,20 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { seafileAPI } from './utils/seafile-api';
|
||||
import SeafileTable from '@seafile/dtable/lib';
|
||||
|
||||
import "./css/dtable.css";
|
||||
|
||||
class ViewFileSDB extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SeafileTable pageOptions={window.app.pageOptions} seafileAPI={seafileAPI}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<ViewFileSDB />,
|
||||
document.getElementById('wrapper')
|
||||
);
|
5672
package-lock.json
generated
5672
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -136,7 +136,7 @@ PREVIEW_FILEEXT = {
|
||||
AUDIO: ('mp3', 'oga', 'ogg'),
|
||||
#'3D': ('stl', 'obj'),
|
||||
XMIND: ('xmind',),
|
||||
DTABLE: ('dtable',),
|
||||
CTABLE: ('ctable',),
|
||||
CDOC: ('cdoc',),
|
||||
}
|
||||
|
||||
|
@@ -10,5 +10,5 @@ AUDIO = 'Audio'
|
||||
SPREADSHEET = 'SpreadSheet'
|
||||
DRAW = 'Draw'
|
||||
XMIND = 'XMind'
|
||||
DTABLE = 'dtable'
|
||||
CTABLE = 'ctable'
|
||||
CDOC = 'cdoc'
|
||||
|
@@ -63,7 +63,7 @@ from seahub.utils import render_error, is_org_context, \
|
||||
from seahub.utils.ip import get_remote_ip
|
||||
from seahub.utils.timeutils import utc_to_local
|
||||
from seahub.utils.file_types import (IMAGE, PDF, SVG,
|
||||
DOCUMENT, SPREADSHEET, AUDIO, MARKDOWN, TEXT, VIDEO, DRAW, XMIND, DTABLE, CDOC)
|
||||
DOCUMENT, SPREADSHEET, AUDIO, MARKDOWN, TEXT, VIDEO, DRAW, XMIND, CTABLE, CDOC)
|
||||
from seahub.utils.star import is_file_starred
|
||||
from seahub.utils.http import json_response, \
|
||||
BadRequestException, RequestForbbiddenException
|
||||
@@ -734,7 +734,7 @@ def view_lib_file(request, repo_id, path):
|
||||
|
||||
return render(request, template, return_dict)
|
||||
|
||||
elif filetype == DTABLE:
|
||||
elif filetype == CTABLE:
|
||||
return render(request, template, return_dict)
|
||||
elif filetype == CDOC:
|
||||
return render(request, template, return_dict)
|
||||
|
Reference in New Issue
Block a user