1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-08 18:30:53 +00:00

dtable interface

This commit is contained in:
王健辉
2019-05-31 17:57:05 +08:00
parent ed7c2d10c5
commit 53f6b55bb8
7 changed files with 507 additions and 95 deletions

View File

@@ -0,0 +1,109 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, ModalHeader, Input, ModalBody, ModalFooter, Form, FormGroup, Label, Alert } from 'reactstrap';
import { gettext } from '../../utils/constants';
const propTypes = {
createTable: PropTypes.func.isRequired,
onAddTable: PropTypes.func.isRequired,
};
class CreateTableDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
tableName: '',
errMessage: '',
isSubmitBtnActive: false,
};
this.newInput = React.createRef();
}
componentDidMount() {
this.newInput.focus();
this.newInput.setSelectionRange(0,0);
}
handleChange = (e) => {
if (!e.target.value.trim()) {
this.setState({isSubmitBtnActive: false});
} else {
this.setState({isSubmitBtnActive: true});
}
this.setState({
tableName: e.target.value,
}) ;
}
handleSubmit = () => {
if (!this.state.isSubmitBtnActive) {
return;
}
let isValid = this.validateInputParams();
if (isValid) {
let newName = this.state.tableName.trim();
this.props.createTable(newName);
}
}
handleKeyPress = (e) => {
if (e.key === 'Enter') {
this.handleSubmit();
e.preventDefault();
}
}
toggle = () => {
this.props.onAddTable();
}
validateInputParams = () => {
let errMessage = '';
let tableName = this.state.tableName.trim();
if (!tableName.length) {
errMessage = gettext('Name is required');
this.setState({errMessage: errMessage});
return false;
}
if (tableName.indexOf('/') > -1) {
errMessage = gettext('Name should not include \'/\'.');
this.setState({errMessage: errMessage});
return false;
}
return true;
}
render() {
return (
<Modal isOpen={true} toggle={this.toggle}>
<ModalHeader toggle={this.toggle}>{gettext('New Table')}</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<Label for="tableName">{gettext('Name')}</Label>
<Input
id="tableName"
onKeyPress={this.handleKeyPress}
innerRef={input => {this.newInput = input;}}
value={this.state.tableName}
onChange={this.handleChange}
/>
</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} disabled={!this.state.isSubmitBtnActive}>{gettext('Submit')}</Button>
</ModalFooter>
</Modal>
);
}
}
CreateTableDialog.propTypes = propTypes;
export default CreateTableDialog;

View File

@@ -5,11 +5,11 @@ import { gettext } from '../../utils/constants';
const propTypes = {
createWorkSpace: PropTypes.func.isRequired,
onCreateToggle: PropTypes.func.isRequired,
createWorkspace: PropTypes.func.isRequired,
onAddWorkspace: PropTypes.func.isRequired,
};
class CreateWorkSpaceDialog extends React.Component {
class CreateWorkspaceDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
@@ -34,7 +34,7 @@ class CreateWorkSpaceDialog extends React.Component {
let isValid= this.validateInputParams();
if (isValid) {
let workspaceName = this.state.workspaceName.trim();
this.props.createWorkSpace(workspaceName);
this.props.createWorkspace(workspaceName);
}
}
@@ -46,7 +46,7 @@ class CreateWorkSpaceDialog extends React.Component {
}
toggle = () => {
this.props.onCreateToggle();
this.props.onAddWorkspace();
}
componentDidMount() {
@@ -72,7 +72,7 @@ class CreateWorkSpaceDialog extends React.Component {
render() {
return (
<Modal isOpen={true} toggle={this.toggle}>
<ModalHeader toggle={this.toggle}>{gettext('New Library')}</ModalHeader>
<ModalHeader toggle={this.toggle}>{gettext('New Workspace')}</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
@@ -97,6 +97,6 @@ class CreateWorkSpaceDialog extends React.Component {
}
}
CreateWorkSpaceDialog.propTypes = propTypes;
CreateWorkspaceDialog.propTypes = propTypes;
export default CreateWorkSpaceDialog;
export default CreateWorkspaceDialog;

View File

@@ -0,0 +1,39 @@
import React from 'react';
import PropTypes from 'prop-types';
import { gettext } from '../../utils/constants';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
const propTypes = {
currentTable: PropTypes.object.isRequired,
deleteCancel: PropTypes.func.isRequired,
handleSubmit: PropTypes.func.isRequired,
};
class DeleteTableDialog extends React.Component {
toggle = () => {
this.props.deleteCancel();
}
render() {
let currentTable = this.props.currentTable;
let name = currentTable.name;
return (
<Modal isOpen={true} toggle={this.toggle}>
<ModalHeader toggle={this.toggle}>{gettext('Delete Table')}</ModalHeader>
<ModalBody>
<p>{gettext('Are you sure to delete')}{' '}<b>{name}</b> ?</p>
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.props.handleSubmit}>{gettext('Delete')}</Button>
</ModalFooter>
</Modal>
);
}
}
DeleteTableDialog.propTypes = propTypes;
export default DeleteTableDialog;

View File

@@ -0,0 +1,39 @@
import React from 'react';
import PropTypes from 'prop-types';
import { gettext } from '../../utils/constants';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
const propTypes = {
currentWorkspace: PropTypes.object.isRequired,
deleteCancel: PropTypes.func.isRequired,
handleSubmit: PropTypes.func.isRequired,
};
class DeleteWorkspaceDialog extends React.Component {
toggle = () => {
this.props.deleteCancel();
}
render() {
let currentWorkspace = this.props.currentWorkspace;
let name = currentWorkspace.name;
return (
<Modal isOpen={true} toggle={this.toggle}>
<ModalHeader toggle={this.toggle}>{gettext('Delete Workspace')}</ModalHeader>
<ModalBody>
<p>{gettext('Are you sure to delete')}{' '}<b>{name}</b> ?</p>
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.props.handleSubmit}>{gettext('Delete')}</Button>
</ModalFooter>
</Modal>
);
}
}
DeleteWorkspaceDialog.propTypes = propTypes;
export default DeleteWorkspaceDialog;

View File

@@ -1,40 +1,220 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { Button, ModalHeader, ModalBody, ModalFooter, Alert, Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import { Button, Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import moment from 'moment';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import { gettext, siteRoot } from '../../utils/constants';
import Loading from '../../components/loading';
import ModalPortal from '../../components/modal-portal';
import CreateWorkSpaceDialog from '../../components/dialog/create-workspace-dialog';
import CreateWorkspaceDialog from '../../components/dialog/create-workspace-dialog';
import DeleteWorkspaceDialog from '../../components/dialog/delete-workspace-dialog';
import CreateTableDialog from '../../components/dialog/create-table-dialog';
import DeleteTableDialog from '../../components/dialog/delete-table-dialog';
import Rename from '../../components/rename';
moment.locale(window.app.config.lang);
const itemPropTypes = {
item: PropTypes.object.isRequired,
renameWorkSpace: PropTypes.func.isRequired,
deleteWorkSpace: PropTypes.func.isRequired,
const tablePropTypes = {
table: PropTypes.object.isRequired,
repoID: PropTypes.string.isRequired,
renameTable: PropTypes.func.isRequired,
deleteTable: PropTypes.func.isRequired,
};
class Item extends Component {
class Table extends Component {
constructor(props) {
super(props);
this.state = {
isTableRenaming: false,
isTableDeleting: false,
dropdownOpen: false,
newName: '',
active: false,
};
}
onRenameWorkSpace(workspace) {
let name = this.state.newName;
this.props.renameWorkSpace(workspace, name);
onRenameTableCancel = () => {
this.setState({isTableRenaming: !this.state.isTableRenaming});
}
onDeleteWorkSpace(workspace) {
this.props.deleteWorkSpace(workspace);
onRenameTableConfirm = (newTableName) => {
let oldTableName = this.props.table.name;
this.props.renameTable(oldTableName, newTableName);
this.onRenameTableCancel();
}
onDeleteTableCancel = () => {
this.setState({isTableDeleting: !this.state.isTableDeleting});
}
onDeleteTableSubmit = () => {
let tableName = this.props.table.name;
console.log(tableName);
this.props.deleteTable(tableName);
this.onDeleteTableCancel();
}
dropdownToggle = () => {
this.setState({ dropdownOpen: !this.state.dropdownOpen });
}
onMouseEnter = () => {
this.setState({
active: true
});
}
onMouseLeave = () => {
this.setState({
active: false
});
}
render() {
let table = this.props.table;
let tableHref = siteRoot + 'lib/' + this.props.repoID + '/file' + Utils.encodePath(Utils.joinPath('/', table.name));
return (
<tr onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<td></td>
<td>
{this.state.isTableRenaming &&
<Rename
hasSuffix={true}
name={table.name}
onRenameConfirm={this.onRenameTableConfirm}
onRenameCancel={this.onRenameTableCancel}
/>
}
{!this.state.isTableRenaming &&
<a href={tableHref} target="_blank">{table.name}</a>
}
</td>
<td>{table.modifier}</td>
<td>{moment(table.mtime).fromNow()}</td>
<td>
{this.state.active &&
<Dropdown isOpen={this.state.dropdownOpen} toggle={this.dropdownToggle} direction="down" className="mx-1 old-history-more-operation">
<DropdownToggle
tag='i'
className='fa fa-ellipsis-v'
title={gettext('More Operations')}
data-toggle="dropdown"
aria-expanded={this.state.dropdownOpen}
>
</DropdownToggle>
<DropdownMenu className="drop-list" right={true}>
<DropdownItem onClick={this.onRenameTableCancel}>{gettext('Rename')}</DropdownItem>
<DropdownItem onClick={this.onDeleteTableCancel}>{gettext('Delete')}</DropdownItem>
</DropdownMenu>
</Dropdown>
}
{this.state.isTableDeleting &&
<DeleteTableDialog
currentTable={table}
deleteCancel={this.onDeleteTableCancel}
handleSubmit={this.onDeleteTableSubmit}
/>
}
</td>
</tr>
);
}
}
Table.propTypes = tablePropTypes;
const workspacePropTypes = {
workspace: PropTypes.object.isRequired,
renameWorkspace: PropTypes.func.isRequired,
deleteWorkspace: PropTypes.func.isRequired,
};
class Workspace extends Component {
constructor(props) {
super(props);
this.state = {
tableList: this.props.workspace.table_list,
errorMsg: '',
isWorkspaceRenaming: false,
isWorkspaceDeleting: false,
isShowAddTableDialog: false,
dropdownOpen: false,
};
}
onAddTable = () => {
this.setState({
isShowAddTableDialog: !this.state.isShowAddTableDialog,
});
}
createTable = (tableName) => {
let workspaceID = this.props.workspace.id;
seafileAPI.createTable(workspaceID, tableName).then((res) => {
this.state.tableList.push(res.data.table);
this.setState({tableList: this.state.tableList});
}).catch((error) => {
if(error.response) {
this.setState({errorMsg: gettext('Error')});
}
});
this.onAddTable();
}
renameTable = (oldTableName, newTableName) => {
let workspaceID = this.props.workspace.id;
seafileAPI.renameTable(workspaceID, oldTableName, newTableName).then((res) => {
let tableList = this.state.tableList.map((table) => {
if (table.name === oldTableName) {
table = res.data.table;
}
return table;
});
this.setState({tableList: tableList});
}).catch((error) => {
if(error.response) {
this.setState({errorMsg: gettext('Error')});
}
});
}
deleteTable = (tableName) => {
let workspaceID = this.props.workspace.id;
seafileAPI.deleteTable(workspaceID, tableName).then(() => {
let tableList = this.state.tableList.filter(table => {
return table.name !== tableName;
});
this.setState({tableList: tableList});
}).catch((error) => {
if(error.response) {
this.setState({errorMsg: gettext('Error')});
}
});
}
onRenameWorkspaceCancel = () => {
this.setState({isWorkspaceRenaming: !this.state.isWorkspaceRenaming});
}
onRenameWorkspaceConfirm = (newName) => {
let workspace = this.props.workspace;
this.props.renameWorkspace(workspace, newName);
this.onRenameWorkspaceCancel();
}
onDeleteWorkspaceCancel = () => {
this.setState({isWorkspaceDeleting: !this.state.isWorkspaceDeleting});
}
onDeleteWorkspaceSubmit = () => {
let workspace = this.props.workspace;
this.props.deleteWorkspace(workspace);
this.onDeleteWorkspaceCancel();
}
dropdownToggle = () => {
@@ -42,63 +222,89 @@ class Item extends Component {
}
render() {
let item = this.props.item;
let workspace = this.props.workspace;
return(
<Fragment>
<tr>
<td colSpan='5'>
{item.name}
<Dropdown isOpen={this.state.dropdownOpen} toggle={this.dropdownToggle} direction="down">
<DropdownToggle
caret={true}
tag='i'
title={gettext('More Operations')}
data-toggle="dropdown"
aria-expanded={this.state.dropdownOpen}
>
</DropdownToggle>
<DropdownMenu className="drop-list" right={true}>
<DropdownItem onClick={this.onRenameWorkSpace.bind(this, item)}>{gettext('Rename')}</DropdownItem>
<DropdownItem onClick={this.onDeleteWorkSpace.bind(this, item)}>{gettext('Delete')}</DropdownItem>
</DropdownMenu>
</Dropdown>
{this.state.isWorkspaceRenaming &&
<Rename
hasSuffix={false}
name={workspace.name}
onRenameConfirm={this.onRenameWorkspaceConfirm}
onRenameCancel={this.onRenameWorkspaceCancel}
/>
}
{!this.state.isWorkspaceRenaming &&
<div>
{workspace.name}
<Dropdown isOpen={this.state.dropdownOpen} toggle={this.dropdownToggle} direction="down">
<DropdownToggle
caret={true}
tag='i'
title={gettext('More Operations')}
data-toggle="dropdown"
aria-expanded={this.state.dropdownOpen}
>
</DropdownToggle>
<DropdownMenu className="drop-list" right={true}>
<DropdownItem onClick={this.onRenameWorkspaceCancel}>{gettext('Rename')}</DropdownItem>
<DropdownItem onClick={this.onDeleteWorkspaceCancel}>{gettext('Delete')}</DropdownItem>
</DropdownMenu>
</Dropdown>
{this.state.isWorkspaceDeleting &&
<DeleteWorkspaceDialog
currentWorkspace={workspace}
deleteCancel={this.onDeleteWorkspaceCancel}
handleSubmit={this.onDeleteWorkspaceSubmit}
/>
}
</div>
}
</td>
</tr>
{item.table_list.map((table, index) => {
let tableHref = siteRoot + 'lib/' + item.repo_id + '/file' + Utils.encodePath(Utils.joinPath('/', table.name));
{this.state.tableList.map((table, index) => {
return (
<tr key={index}>
<td></td>
<td><a href={tableHref} target="_blank">{table.name}</a></td>
<td>{table.modifier}</td>
<td>{moment(table.mtime).fromNow()}</td>
<td></td>
</tr>
<Table
key={index}
table={table}
repoID={workspace.repo_id}
renameTable={this.renameTable}
deleteTable={this.deleteTable}
/>
);
})}
<tr>
<td><Button className="fa fa-plus"></Button></td>
<td colSpan='4' >{gettext('Add a table')}</td>
<td>
<Button className="fa fa-plus" onClick={this.onAddTable}></Button>
{this.state.isShowAddTableDialog &&
<CreateTableDialog
onAddTable={this.onAddTable}
createTable={this.createTable}
/>
}
</td>
<td colSpan='4' >{gettext('Add a DTable')}</td>
</tr>
</Fragment>
);
}
}
Item.propTypes = itemPropTypes;
Workspace.propTypes = workspacePropTypes;
const contentPropTypes = {
items: PropTypes.array.isRequired,
renameWorkSpace: PropTypes.func.isRequired,
deleteWorkSpace: PropTypes.func.isRequired,
workspaceList: PropTypes.array.isRequired,
renameWorkspace: PropTypes.func.isRequired,
deleteWorkspace: PropTypes.func.isRequired,
};
class Content extends Component {
render() {
let items = this.props.items;
let workspaceList = this.props.workspaceList;
return (
<table width="100%" className="table table-hover table-vcenter">
@@ -110,13 +316,13 @@ class Content extends Component {
<col width="5%"/>
</colgroup>
<tbody>
{items.map((item, index) => {
{workspaceList.map((workspace, index) => {
return (
<Item
<Workspace
key={index}
item={item}
renameWorkSpace={this.props.renameWorkSpace}
deleteWorkSpace={this.props.deleteWorkSpace}
workspace={workspace}
renameWorkspace={this.props.renameWorkspace}
deleteWorkspace={this.props.deleteWorkspace}
/>
);
})}
@@ -135,38 +341,39 @@ class DTable extends Component {
this.state = {
loading: true,
errorMsg: '',
items: [],
isShowCreateDialog: false,
workspaceList: [],
isShowAddWorkspaceDialog: false,
};
}
onCreateToggle = () => {
onAddWorkspace = () => {
this.setState({
isShowCreateDialog: !this.state.isShowCreateDialog,
isShowAddWorkspaceDialog: !this.state.isShowAddWorkspaceDialog,
});
}
createWorkSpace = (name) => {
seafileAPI.addWorkSpace(name).then((res) => {
this.state.items.push(res.data.workspace);
this.setState({items: this.state.items});
createWorkspace = (name) => {
seafileAPI.createWorkspace(name).then((res) => {
this.state.workspaceList.push(res.data.workspace);
this.setState({workspaceList: this.state.workspaceList});
}).catch((error) => {
if(error.response) {
this.setState({errorMsg: gettext('Error')});
}
});
this.onCreateToggle();
this.onAddWorkspace();
}
renameWorkSpace = (workspace, name) => {
seafileAPI.renameWorkSpace(workspace.id, name).then((res) => {
let items = this.state.items.map((item) => {
if (item.id === workspace.id) {
item = res.data;
renameWorkspace = (workspace, name) => {
let workspaceID = workspace.id;
seafileAPI.renameWorkspace(workspaceID, name).then((res) => {
let workspaceList = this.state.workspaceList.map((workspace) => {
if (workspace.id === workspaceID) {
workspace = res.data.workspace;
}
return item;
return workspace;
});
this.setState({items: items});
this.setState({workspaceList: workspaceList});
}).catch((error) => {
if(error.response) {
this.setState({errorMsg: gettext('Error')});
@@ -174,12 +381,13 @@ class DTable extends Component {
});
}
deleteWorkSpace = (workspace) => {
seafileAPI.deleteWorkSpace(workspace.id).then(() => {
let items = this.state.items.filter(item => {
return item.id !== workspace.id;
deleteWorkspace = (workspace) => {
let workspaceID = workspace.id;
seafileAPI.deleteWorkspace(workspaceID).then(() => {
let workspaceList = this.state.workspaceList.filter(workspace => {
return workspace.id !== workspaceID;
});
this.setState({items: items});
this.setState({workspaceList: workspaceList});
}).catch((error) => {
if(error.response) {
this.setState({errorMsg: gettext('Error')});
@@ -188,10 +396,10 @@ class DTable extends Component {
}
componentDidMount() {
seafileAPI.listWorkSpaces().then((res) => {
seafileAPI.listWorkspaces().then((res) => {
this.setState({
loading: false,
items: res.data.workspace_list,
workspaceList: res.data.workspace_list,
});
}).catch((error) => {
if (error.response) {
@@ -223,21 +431,21 @@ class DTable extends Component {
{!this.state.loading &&
<Fragment>
<Content
items={this.state.items}
renameWorkSpace={this.renameWorkSpace}
deleteWorkSpace={this.deleteWorkSpace}
workspaceList={this.state.workspaceList}
renameWorkspace={this.renameWorkspace}
deleteWorkspace={this.deleteWorkspace}
/>
<br />
<div>
{this.state.isShowCreateDialog &&
<CreateWorkSpaceDialog
createWorkSpace={this.createWorkSpace}
onCreateToggle={this.onCreateToggle}
<Button onClick={this.onAddWorkspace} className="fa fa-plus">
{gettext('Add a Workspace')}
</Button>
{this.state.isShowAddWorkspaceDialog &&
<CreateWorkspaceDialog
createWorkspace={this.createWorkspace}
onAddWorkspace={this.onAddWorkspace}
/>
}
<Button onClick={this.onCreateToggle} className="fa fa-plus">
{gettext('Add a workspace')}
</Button>
</div>
</Fragment>
}

View File

@@ -104,7 +104,11 @@ class WorkSpacesView(APIView):
error_msg = 'Internal Server Error.'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
return Response({"workspace": workspace.to_dict()}, status=status.HTTP_201_CREATED)
res = workspace.to_dict()
res["table_list"] = []
res["updated_at"] = workspace.updated_at
return Response({"workspace": res}, status=status.HTTP_201_CREATED)
class WorkSpaceView(APIView):
@@ -162,7 +166,20 @@ class WorkSpaceView(APIView):
error_msg = 'Internal Server Error.'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
return Response({"workspace": workspace.to_dict()}, status=status.HTTP_200_OK)
table_objs = seafile_api.list_dir_by_path(repo_id, '/')
table_list = list()
for table_obj in table_objs:
table = dict()
table["name"] = table_obj.obj_name
table["mtime"] = timestamp_to_isoformat_timestr(table_obj.mtime)
table["modifier"] = email2nickname(table_obj.modifier) if table_obj.modifier else email2nickname(owner)
table_list.append(table)
res = workspace.to_dict()
res["table_list"] = table_list
res["updated_at"] = workspace.updated_at
return Response({"workspace": res}, status=status.HTTP_200_OK)
def delete(self, request, workspace_id):
"""delete a workspace

View File

@@ -347,7 +347,7 @@ urlpatterns = [
# user: workspaces
url(r'^api/v2.1/workspaces/$', WorkSpacesView.as_view(), name='api-v2.1-workspaces'),
url(r'^api/v2.1/workspace/(?P<workspace_id>\d+)/$', WorkSpaceView.as_view(), name='api-v2.1-workspace'),
url(r'^api/v2.1/workspace/(?P<workspace_id>\d+)/table/$', DTableView.as_view(), name='api-v2.1-workspace-table'),
url(r'^api/v2.1/workspace/(?P<workspace_id>\d+)/dtable/$', DTableView.as_view(), name='api-v2.1-workspace-table'),
# Deprecated
url(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/tags/$', FileTagsView.as_view(), name="api-v2.1-filetags-view"),