OpenDroneMap-WebODM/app/static/app/js/components/ProjectListItem.jsx

378 wiersze
11 KiB
React
Czysty Zwykły widok Historia

import '../css/ProjectListItem.scss';
import React from 'react';
import update from 'immutability-helper';
2016-10-25 14:47:49 +00:00
import TaskList from './TaskList';
2016-10-18 15:25:14 +00:00
import EditTaskPanel from './EditTaskPanel';
import UploadProgressBar from './UploadProgressBar';
import ErrorMessage from './ErrorMessage';
import EditProjectDialog from './EditProjectDialog';
import Dropzone from '../vendor/dropzone';
import csrf from '../django/csrf';
import HistoryNav from '../classes/HistoryNav';
import $ from 'jquery';
class ProjectListItem extends React.Component {
2016-10-11 20:37:00 +00:00
constructor(props){
super(props);
this.historyNav = new HistoryNav(props.history);
this.state = {
showTaskList: this.historyNav.isValueInQSList("project_task_open", props.data.id),
updatingTask: false,
upload: this.getDefaultUploadState(),
error: "",
2016-11-15 16:51:19 +00:00
data: props.data,
refreshing: false
};
2016-10-25 14:47:49 +00:00
this.toggleTaskList = this.toggleTaskList.bind(this);
this.handleUpload = this.handleUpload.bind(this);
2016-10-18 15:25:14 +00:00
this.closeUploadError = this.closeUploadError.bind(this);
this.cancelUpload = this.cancelUpload.bind(this);
2016-10-21 14:42:46 +00:00
this.handleTaskSaved = this.handleTaskSaved.bind(this);
this.viewMap = this.viewMap.bind(this);
this.handleDelete = this.handleDelete.bind(this);
this.handleEditProject = this.handleEditProject.bind(this);
this.updateProject = this.updateProject.bind(this);
this.taskDeleted = this.taskDeleted.bind(this);
}
2016-11-15 16:51:19 +00:00
refresh(){
// Update project information based on server
this.setState({refreshing: true});
this.refreshRequest =
$.getJSON(`/api/projects/${this.state.data.id}/`)
.done((json) => {
this.setState({data: json});
})
.fail((_, __, e) => {
this.setState({error: e.message});
})
.always(() => {
this.setState({refreshing: false});
});
}
componentWillUnmount(){
if (this.updateTaskRequest) this.updateTaskRequest.abort();
if (this.deleteProjectRequest) this.deleteProjectRequest.abort();
2016-11-15 16:51:19 +00:00
if (this.refreshRequest) this.refreshRequest.abort();
}
getDefaultUploadState(){
return {
uploading: false,
2016-10-18 15:25:14 +00:00
showEditTask: false,
error: "",
progress: 0,
totalCount: 0,
totalBytes: 0,
2016-10-21 14:42:46 +00:00
totalBytesSent: 0,
savedTaskInfo: false,
2016-10-21 14:42:46 +00:00
taskId: null
};
}
resetUploadState(){
this.setUploadState(this.getDefaultUploadState());
}
setUploadState(props){
this.setState(update(this.state, {
upload: {
$merge: props
}
}));
}
componentDidMount(){
Dropzone.autoDiscover = false;
2016-10-18 15:25:14 +00:00
this.dz = new Dropzone(this.dropzone, {
paramName: "images",
2016-11-15 16:51:19 +00:00
url : `/api/projects/${this.state.data.id}/tasks/`,
parallelUploads: 9999999,
uploadMultiple: true,
acceptedFiles: "image/*, .txt",
autoProcessQueue: true,
createImageThumbnails: false,
clickable: this.uploadButton,
headers: {
[csrf.header]: csrf.token
}
});
2016-10-18 15:25:14 +00:00
this.dz.on("totaluploadprogress", (progress, totalBytes, totalBytesSent) => {
this.setUploadState({
progress, totalBytes, totalBytesSent
});
})
.on("addedfile", () => {
this.setUploadState({
totalCount: this.state.upload.totalCount + 1
});
})
.on("processingmultiple", () => {
this.setUploadState({
2016-10-18 15:25:14 +00:00
uploading: true,
showEditTask: true
})
})
2016-10-18 15:25:14 +00:00
.on("completemultiple", (files) => {
// Check
2016-10-21 14:42:46 +00:00
let success = files.length > 0 && files.filter(file => file.status !== "success").length === 0;
2016-10-18 15:25:14 +00:00
2016-10-21 14:42:46 +00:00
// All files have uploaded!
2016-10-18 15:25:14 +00:00
if (success){
this.setUploadState({uploading: false});
2016-10-21 14:42:46 +00:00
2017-02-10 17:40:09 +00:00
try{
2016-10-21 14:42:46 +00:00
let response = JSON.parse(files[0].xhr.response);
if (!response.id) throw new Error(`Expected id field, but none given (${response})`);
let taskId = response.id;
this.setUploadState({taskId});
// Update task information (if the user has completed this step)
if (this.state.upload.savedTaskInfo){
this.updateTaskInfo(taskId, this.editTaskPanel.getTaskInfo());
2016-10-21 14:42:46 +00:00
}else{
// Need to wait for user to confirm task options
}
2017-02-10 17:40:09 +00:00
}catch(e){
this.setUploadState({error: `Invalid response from server: ${e.message}`})
}
2016-10-21 14:42:46 +00:00
2016-10-18 15:25:14 +00:00
}else{
this.setUploadState({
uploading: false,
2016-10-21 14:42:46 +00:00
error: "Could not upload all files. An error occured. Please try again."
2016-10-18 15:25:14 +00:00
});
}
})
.on("reset", () => {
this.resetUploadState();
2016-11-11 18:30:11 +00:00
})
.on("dragenter", () => {
this.resetUploadState();
})
.on("sending", (file, xhr, formData) => {
formData.append('auto_processing_node', "false");
});
}
updateTaskInfo(taskId, taskInfo){
if (!taskId) throw new Error("taskId is not set");
if (!taskInfo) throw new Error("taskId is not set");
this.setUploadState({showEditTask: false});
this.setState({updatingTask: true});
this.updateTaskRequest =
$.ajax({
2016-11-15 16:51:19 +00:00
url: `/api/projects/${this.state.data.id}/tasks/${this.state.upload.taskId}/`,
contentType: 'application/json',
data: JSON.stringify({
name: taskInfo.name,
options: taskInfo.options,
processing_node: taskInfo.selectedNode.id,
auto_processing_node: taskInfo.selectedNode.key == "auto"
}),
dataType: 'json',
type: 'PATCH'
2016-11-15 16:51:19 +00:00
}).done((json) => {
2016-10-25 14:47:49 +00:00
if (this.state.showTaskList){
this.taskList.refresh();
}else{
2016-10-25 14:47:49 +00:00
this.setState({showTaskList: true});
}
2016-11-15 16:51:19 +00:00
this.refresh();
}).fail(() => {
this.setUploadState({error: "Could not update task information. Plese try again."});
}).always(() => {
this.setState({updatingTask: false});
});
2016-10-21 14:42:46 +00:00
}
setRef(prop){
return (domNode) => {
if (domNode != null) this[prop] = domNode;
}
}
2016-10-25 14:47:49 +00:00
toggleTaskList(){
const showTaskList = !this.state.showTaskList;
this.historyNav.toggleQSListItem("project_task_open", this.state.data.id, showTaskList);
2016-10-11 20:37:00 +00:00
this.setState({
showTaskList: showTaskList
2016-10-11 20:37:00 +00:00
});
}
2016-10-18 15:25:14 +00:00
closeUploadError(){
this.setUploadState({error: ""});
}
cancelUpload(e){
this.dz.removeAllFiles(true);
}
handleUpload(){
this.resetUploadState();
}
taskDeleted(){
2016-11-15 16:51:19 +00:00
this.refresh();
}
handleDelete(){
2016-11-15 16:51:19 +00:00
return $.ajax({
url: `/api/projects/${this.state.data.id}/`,
type: 'DELETE'
}).done(() => {
if (this.props.onDelete) this.props.onDelete(this.state.data.id);
});
}
2016-10-21 14:42:46 +00:00
handleTaskSaved(taskInfo){
this.setUploadState({savedTaskInfo: true});
2016-10-18 15:25:14 +00:00
2016-10-21 14:42:46 +00:00
// Has the upload finished?
if (!this.state.upload.uploading && this.state.upload.taskId !== null){
this.updateTaskInfo(this.state.upload.taskId, taskInfo);
2016-10-21 14:42:46 +00:00
}
}
handleEditProject(){
this.editProjectDialog.show();
}
updateProject(project){
2016-11-15 16:51:19 +00:00
return $.ajax({
url: `/api/projects/${this.state.data.id}/`,
contentType: 'application/json',
data: JSON.stringify({
name: project.name,
description: project.descr,
}),
dataType: 'json',
type: 'PATCH'
}).done(() => {
this.refresh();
});
}
viewMap(){
2016-11-15 16:51:19 +00:00
location.href = `/map/project/${this.state.data.id}/`;
}
2016-10-21 14:42:46 +00:00
render() {
2016-11-15 16:51:19 +00:00
const { refreshing, data } = this.state;
const numTasks = data.tasks.length;
return (
2016-11-15 16:51:19 +00:00
<li className={"project-list-item list-group-item " + (refreshing ? "refreshing" : "")}
2016-11-11 18:22:29 +00:00
href="javascript:void(0);"
ref={this.setRef("dropzone")}
>
<EditProjectDialog
ref={(domNode) => { this.editProjectDialog = domNode; }}
title="Edit Project"
saveLabel="Save Changes"
savingLabel="Saving changes..."
saveIcon="fa fa-edit"
2016-11-15 16:51:19 +00:00
projectName={data.name}
projectDescr={data.description}
saveAction={this.updateProject}
deleteAction={this.handleDelete}
/>
2016-10-18 15:25:14 +00:00
<div className="row no-margin">
<ErrorMessage bind={[this, 'error']} />
2016-10-18 15:25:14 +00:00
<div className="btn-group pull-right">
<button type="button"
className={"btn btn-primary btn-sm " + (this.state.upload.uploading ? "hide" : "")}
onClick={this.handleUpload}
ref={this.setRef("uploadButton")}>
<i className="glyphicon glyphicon-upload"></i>
Upload Images and GCP
2016-10-18 15:25:14 +00:00
</button>
<button disabled={this.state.upload.error !== ""}
type="button"
className={"btn btn-primary btn-sm " + (!this.state.upload.uploading ? "hide" : "")}
onClick={this.cancelUpload}>
<i className="glyphicon glyphicon-remove-circle"></i>
Cancel Upload
</button>
<button type="button" className="btn btn-default btn-sm" onClick={this.viewMap}>
<i className="fa fa-globe"></i> View Map
2016-10-18 15:25:14 +00:00
</button>
</div>
2016-10-11 20:37:00 +00:00
2016-10-25 14:47:49 +00:00
<span className="project-name">
2016-11-15 16:51:19 +00:00
{data.name}
2016-10-25 14:47:49 +00:00
</span>
<div className="project-description">
2016-11-15 16:51:19 +00:00
{data.description}
2016-10-25 14:47:49 +00:00
</div>
<div className="row project-links">
2016-11-15 16:51:19 +00:00
{numTasks > 0 ?
<span>
<i className='fa fa-tasks'>
</i> <a href="javascript:void(0);" onClick={this.toggleTaskList}>
2016-11-15 16:51:19 +00:00
{numTasks} Tasks <i className={'fa fa-caret-' + (this.state.showTaskList ? 'down' : 'right')}></i>
</a>
</span>
: ""}
<i className='fa fa-edit'>
</i> <a href="javascript:void(0);" onClick={this.handleEditProject}> Edit
2016-10-25 14:47:49 +00:00
</a>
</div>
2016-10-18 15:25:14 +00:00
</div>
2016-11-11 18:22:29 +00:00
<i className="drag-drop-icon fa fa-inbox"></i>
2016-10-18 15:25:14 +00:00
<div className="row">
2016-10-21 14:42:46 +00:00
{this.state.upload.showEditTask ? <UploadProgressBar {...this.state.upload}/> : ""}
2016-10-18 15:25:14 +00:00
{this.state.upload.error !== "" ?
<div className="alert alert-warning alert-dismissible">
<button type="button" className="close" aria-label="Close" onClick={this.closeUploadError}><span aria-hidden="true">&times;</span></button>
{this.state.upload.error}
</div>
2016-10-18 15:25:14 +00:00
: ""}
{this.state.upload.showEditTask ?
<EditTaskPanel
uploading={this.state.upload.uploading}
onSave={this.handleTaskSaved}
ref={this.setRef("editTaskPanel")}
2016-10-21 14:42:46 +00:00
/>
: ""}
{this.state.updatingTask ?
<span>Updating task information... <i className="fa fa-refresh fa-spin fa-fw"></i></span>
: ""}
{this.state.showTaskList ?
<TaskList
ref={this.setRef("taskList")}
2016-11-15 16:51:19 +00:00
source={`/api/projects/${data.id}/tasks/?ordering=-created_at`}
onDelete={this.taskDeleted}
history={this.props.history}
/> : ""}
2016-10-18 15:25:14 +00:00
</div>
2016-10-11 20:37:00 +00:00
</li>
);
}
}
export default ProjectListItem;