2016-10-17 17:19:32 +00:00
import '../css/ProjectListItem.scss' ;
2016-10-11 17:42:17 +00:00
import React from 'react' ;
2016-11-28 20:18:53 +00:00
import update from 'immutability-helper' ;
2016-10-25 14:47:49 +00:00
import TaskList from './TaskList' ;
2017-07-18 20:07:53 +00:00
import NewTaskPanel from './NewTaskPanel' ;
2019-02-20 21:42:20 +00:00
import ImportTaskPanel from './ImportTaskPanel' ;
2016-10-17 17:19:32 +00:00
import UploadProgressBar from './UploadProgressBar' ;
2016-11-14 15:58:00 +00:00
import ErrorMessage from './ErrorMessage' ;
2016-11-14 21:32:05 +00:00
import EditProjectDialog from './EditProjectDialog' ;
2016-10-12 22:18:37 +00:00
import Dropzone from '../vendor/dropzone' ;
2016-10-13 20:28:32 +00:00
import csrf from '../django/csrf' ;
2017-04-13 21:03:42 +00:00
import HistoryNav from '../classes/HistoryNav' ;
2017-09-06 15:47:04 +00:00
import PropTypes from 'prop-types' ;
2019-06-27 15:29:46 +00:00
import ResizeModes from '../classes/ResizeModes' ;
2020-12-22 16:21:10 +00:00
import exifr from '../vendor/exifr' ;
2020-12-15 20:56:00 +00:00
import { _ , interpolate } from '../classes/gettext' ;
2016-10-12 22:18:37 +00:00
import $ from 'jquery' ;
2016-10-11 17:42:17 +00:00
class ProjectListItem extends React . Component {
2017-09-06 15:47:04 +00:00
static propTypes = {
history : PropTypes . object . isRequired ,
data : PropTypes . object . isRequired , // project json
2021-08-04 20:20:51 +00:00
onDelete : PropTypes . func ,
onTaskMoved : PropTypes . func ,
2017-09-06 15:47:04 +00:00
}
2016-10-11 20:37:00 +00:00
constructor ( props ) {
super ( props ) ;
2017-04-13 21:03:42 +00:00
this . historyNav = new HistoryNav ( props . history ) ;
2016-10-11 17:42:17 +00:00
this . state = {
2017-04-13 21:03:42 +00:00
showTaskList : this . historyNav . isValueInQSList ( "project_task_open" , props . data . id ) ,
2016-11-14 15:58:00 +00:00
upload : this . getDefaultUploadState ( ) ,
2016-11-14 21:32:05 +00:00
error : "" ,
2016-11-15 16:51:19 +00:00
data : props . data ,
2019-02-20 21:42:20 +00:00
refreshing : false ,
2019-08-29 02:16:39 +00:00
importing : false ,
buttons : [ ]
2016-10-11 17:42:17 +00:00
} ;
2016-10-25 14:47:49 +00:00
this . toggleTaskList = this . toggleTaskList . 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 ) ;
2016-11-09 21:13:43 +00:00
this . viewMap = this . viewMap . bind ( this ) ;
2016-11-14 15:58:00 +00:00
this . handleDelete = this . handleDelete . bind ( this ) ;
2016-11-14 21:32:05 +00:00
this . handleEditProject = this . handleEditProject . bind ( this ) ;
this . updateProject = this . updateProject . bind ( this ) ;
this . taskDeleted = this . taskDeleted . bind ( this ) ;
2021-08-04 17:09:27 +00:00
this . taskMoved = this . taskMoved . bind ( this ) ;
2017-06-15 19:47:00 +00:00
this . hasPermission = this . hasPermission . bind ( this ) ;
2016-10-11 17:42:17 +00:00
}
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 } ) ;
} ) ;
}
2016-10-22 15:23:37 +00:00
componentWillUnmount ( ) {
2016-11-14 15:58:00 +00:00
if ( this . deleteProjectRequest ) this . deleteProjectRequest . abort ( ) ;
2016-11-15 16:51:19 +00:00
if ( this . refreshRequest ) this . refreshRequest . abort ( ) ;
2016-10-22 15:23:37 +00:00
}
2016-10-17 17:19:32 +00:00
getDefaultUploadState ( ) {
return {
uploading : false ,
2017-07-18 20:07:53 +00:00
editing : false ,
2016-10-18 15:25:14 +00:00
error : "" ,
2016-10-17 17:19:32 +00:00
progress : 0 ,
2018-12-31 21:50:51 +00:00
files : [ ] ,
2016-10-17 17:19:32 +00:00
totalCount : 0 ,
2019-06-26 22:41:09 +00:00
uploadedCount : 0 ,
2016-10-17 17:19:32 +00:00
totalBytes : 0 ,
2018-12-04 23:40:38 +00:00
totalBytesSent : 0 ,
lastUpdated : 0
2016-10-17 17:19:32 +00:00
} ;
}
resetUploadState ( ) {
this . setUploadState ( this . getDefaultUploadState ( ) ) ;
}
setUploadState ( props ) {
this . setState ( update ( this . state , {
upload : {
$merge : props
}
} ) ) ;
}
2017-06-15 19:47:00 +00:00
hasPermission ( perm ) {
return this . state . data . permissions . indexOf ( perm ) !== - 1 ;
}
2016-10-17 17:19:32 +00:00
componentDidMount ( ) {
Dropzone . autoDiscover = false ;
2016-10-12 22:18:37 +00:00
2017-06-15 19:47:00 +00:00
if ( this . hasPermission ( "add" ) ) {
this . dz = new Dropzone ( this . dropzone , {
paramName : "images" ,
2019-06-26 22:41:09 +00:00
url : 'TO_BE_CHANGED' ,
2019-06-27 15:29:46 +00:00
parallelUploads : 6 ,
2019-06-26 22:41:09 +00:00
uploadMultiple : false ,
2018-03-31 17:08:56 +00:00
acceptedFiles : "image/*,text/*" ,
2017-11-20 20:52:23 +00:00
autoProcessQueue : false ,
2017-06-15 19:47:00 +00:00
createImageThumbnails : false ,
clickable : this . uploadButton ,
2017-11-25 15:45:27 +00:00
chunkSize : 2147483647 ,
timeout : 2147483647 ,
2017-06-15 19:47:00 +00:00
headers : {
[ csrf . header ] : csrf . token
2017-11-22 19:53:29 +00:00
}
2017-06-15 19:47:00 +00:00
} ) ;
2016-10-21 14:42:46 +00:00
2019-06-26 22:41:09 +00:00
this . dz . on ( "addedfiles" , files => {
let totalBytes = 0 ;
for ( let i = 0 ; i < files . length ; i ++ ) {
totalBytes += files [ i ] . size ;
files [ i ] . deltaBytesSent = 0 ;
files [ i ] . trackedBytesSent = 0 ;
files [ i ] . retries = 0 ;
2018-12-04 23:40:38 +00:00
}
2019-06-26 22:41:09 +00:00
2017-06-15 19:47:00 +00:00
this . setUploadState ( {
2017-11-22 19:53:29 +00:00
editing : true ,
2018-12-31 21:50:51 +00:00
totalCount : this . state . upload . totalCount + files . length ,
2019-06-26 22:41:09 +00:00
files ,
totalBytes : this . state . upload . totalBytes + totalBytes
2017-06-15 19:47:00 +00:00
} ) ;
2017-11-20 20:52:23 +00:00
} )
2019-06-26 22:41:09 +00:00
. on ( "uploadprogress" , ( file , progress , bytesSent ) => {
const now = new Date ( ) . getTime ( ) ;
2020-02-19 16:00:25 +00:00
if ( bytesSent > file . size ) bytesSent = file . size ;
if ( progress === 100 || now - this . state . upload . lastUpdated > 500 ) {
const deltaBytesSent = bytesSent - file . deltaBytesSent ;
file . trackedBytesSent += deltaBytesSent ;
2019-06-26 22:41:09 +00:00
2020-02-19 16:00:25 +00:00
const totalBytesSent = this . state . upload . totalBytesSent + deltaBytesSent ;
2019-06-26 22:41:09 +00:00
const progress = totalBytesSent / this . state . upload . totalBytes * 100 ;
this . setUploadState ( {
progress ,
totalBytesSent ,
lastUpdated : now
} ) ;
2020-02-19 16:00:25 +00:00
file . deltaBytesSent = bytesSent ;
2019-06-26 22:41:09 +00:00
}
} )
. on ( "complete" , ( file ) => {
// Retry
const retry = ( ) => {
const MAX _RETRIES = 10 ;
if ( file . retries < MAX _RETRIES ) {
// Update progress
const totalBytesSent = this . state . upload . totalBytesSent - file . trackedBytesSent ;
const progress = totalBytesSent / this . state . upload . totalBytes * 100 ;
this . setUploadState ( {
progress ,
totalBytesSent ,
} ) ;
file . status = Dropzone . QUEUED ;
file . deltaBytesSent = 0 ;
file . trackedBytesSent = 0 ;
file . retries ++ ;
this . dz . processQueue ( ) ;
} else {
2020-12-15 20:56:00 +00:00
throw new Error ( interpolate ( _ ( 'Cannot upload %(filename)s, exceeded max retries (%(max_retries)s)' ) , { filename : file . name , max _retries : MAX _RETRIES } ) ) ;
2019-06-26 22:41:09 +00:00
}
} ;
2017-06-15 19:47:00 +00:00
try {
2019-06-26 22:41:09 +00:00
if ( file . status === "error" ) {
retry ( ) ;
} else {
// Check response
let response = JSON . parse ( file . xhr . response ) ;
if ( response . success ) {
// Update progress by removing the tracked progress and
// use the file size as the true number of bytes
let totalBytesSent = this . state . upload . totalBytesSent + file . size ;
if ( file . trackedBytesSent ) totalBytesSent -= file . trackedBytesSent ;
const progress = totalBytesSent / this . state . upload . totalBytes * 100 ;
this . setUploadState ( {
progress ,
totalBytesSent ,
uploadedCount : this . state . upload . uploadedCount + 1
} ) ;
this . dz . processQueue ( ) ;
} else {
retry ( ) ;
}
}
2017-06-15 19:47:00 +00:00
} catch ( e ) {
2019-06-26 22:41:09 +00:00
this . setUploadState ( { error : ` ${ e . message } ` , uploading : false } ) ;
this . dz . cancelUpload ( ) ;
}
} )
. on ( "queuecomplete" , ( ) => {
const remainingFilesCount = this . state . upload . totalCount - this . state . upload . uploadedCount ;
if ( remainingFilesCount === 0 ) {
// All files have uploaded!
this . setUploadState ( { uploading : false } ) ;
$ . ajax ( {
url : ` /api/projects/ ${ this . state . data . id } /tasks/ ${ this . dz . _taskInfo . id } /commit/ ` ,
contentType : 'application/json' ,
dataType : 'json' ,
type : 'POST'
} ) . done ( ( task ) => {
if ( task && task . id ) {
this . newTaskAdded ( ) ;
} else {
2020-12-15 20:56:00 +00:00
this . setUploadState ( { error : interpolate ( _ ( 'Cannot create new task. Invalid response from server: %(error)s' ) , { error : JSON . stringify ( task ) } ) } ) ;
2019-06-26 22:41:09 +00:00
}
} ) . fail ( ( ) => {
2020-12-15 20:56:00 +00:00
this . setUploadState ( { error : _ ( "Cannot create new task. Please try again later." ) } ) ;
2019-06-26 22:41:09 +00:00
} ) ;
} else if ( this . dz . getQueuedFiles ( ) === 0 ) {
// Done but didn't upload all?
this . setUploadState ( {
totalCount : this . state . upload . totalCount - remainingFilesCount ,
uploading : false ,
2020-12-15 20:56:00 +00:00
error : interpolate ( _ ( '%(count)s files cannot be uploaded. As a reminder, only images (.jpg, .tif, .png) and GCP files (.txt) can be uploaded. Try again.' ) , { count : remainingFilesCount } )
2019-06-26 22:41:09 +00:00
} ) ;
2017-06-15 19:47:00 +00:00
}
} )
. on ( "reset" , ( ) => {
this . resetUploadState ( ) ;
} )
. on ( "dragenter" , ( ) => {
2018-01-18 18:03:02 +00:00
if ( ! this . state . upload . editing ) {
2017-11-22 19:53:29 +00:00
this . resetUploadState ( ) ;
}
2017-06-15 19:47:00 +00:00
} ) ;
}
2019-08-29 02:16:39 +00:00
PluginsAPI . Dashboard . triggerAddNewTaskButton ( { projectId : this . state . data . id , onNewTaskAdded : this . newTaskAdded } , ( button ) => {
if ( ! button ) return ;
2019-09-07 18:35:30 +00:00
2019-08-29 02:16:39 +00:00
this . setState ( update ( this . state , {
buttons : { $push : [ button ] }
} ) ) ;
} ) ;
2016-10-17 17:19:32 +00:00
}
2016-10-12 22:18:37 +00:00
2019-02-21 19:16:48 +00:00
newTaskAdded = ( ) => {
this . setState ( { importing : false } ) ;
if ( this . state . showTaskList ) {
this . taskList . refresh ( ) ;
} else {
this . setState ( { showTaskList : true } ) ;
}
this . resetUploadState ( ) ;
this . refresh ( ) ;
}
2016-10-17 17:19:32 +00:00
setRef ( prop ) {
return ( domNode ) => {
if ( domNode != null ) this [ prop ] = domNode ;
2016-10-12 22:18:37 +00:00
}
}
2016-10-25 14:47:49 +00:00
toggleTaskList ( ) {
2017-04-13 21:03:42 +00:00
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 ( {
2017-04-13 21:03:42 +00:00
showTaskList : showTaskList
2016-10-11 20:37:00 +00:00
} ) ;
2016-10-11 17:42:17 +00:00
}
2016-10-18 15:25:14 +00:00
closeUploadError ( ) {
this . setUploadState ( { error : "" } ) ;
}
cancelUpload ( e ) {
this . dz . removeAllFiles ( true ) ;
}
2016-11-14 21:32:05 +00:00
taskDeleted ( ) {
2016-11-15 16:51:19 +00:00
this . refresh ( ) ;
2016-11-14 21:32:05 +00:00
}
2021-08-04 20:20:51 +00:00
taskMoved ( task ) {
this . refresh ( ) ;
if ( this . props . onTaskMoved ) this . props . onTaskMoved ( task ) ;
2021-08-04 17:09:27 +00:00
}
2016-11-14 15:58:00 +00:00
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-11-14 15:58:00 +00:00
}
2016-10-21 14:42:46 +00:00
handleTaskSaved ( taskInfo ) {
2017-11-22 19:53:29 +00:00
this . dz . _taskInfo = taskInfo ; // Allow us to access the task info from dz
2019-06-27 15:19:52 +00:00
this . setUploadState ( { uploading : true , editing : false } ) ;
2017-11-22 19:53:29 +00:00
2019-06-26 22:41:09 +00:00
// Create task
const formData = {
name : taskInfo . name ,
options : taskInfo . options ,
processing _node : taskInfo . selectedNode . id ,
auto _processing _node : taskInfo . selectedNode . key == "auto" ,
partial : true
} ;
2019-06-27 15:29:46 +00:00
if ( taskInfo . resizeMode === ResizeModes . YES ) {
formData . resize _to = taskInfo . resizeSize ;
}
2019-06-26 22:41:09 +00:00
$ . ajax ( {
url : ` /api/projects/ ${ this . state . data . id } /tasks/ ` ,
contentType : 'application/json' ,
data : JSON . stringify ( formData ) ,
dataType : 'json' ,
type : 'POST'
} ) . done ( ( task ) => {
if ( task && task . id ) {
this . dz . _taskInfo . id = task . id ;
this . dz . options . url = ` /api/projects/ ${ this . state . data . id } /tasks/ ${ task . id } /upload/ ` ;
this . dz . processQueue ( ) ;
} else {
2020-12-15 20:56:00 +00:00
this . setState ( { error : interpolate ( _ ( 'Cannot create new task. Invalid response from server: %(error)s' ) , { error : JSON . stringify ( task ) } ) } ) ;
2019-06-26 22:41:09 +00:00
this . handleTaskCanceled ( ) ;
}
} ) . fail ( ( ) => {
2020-12-15 20:56:00 +00:00
this . setState ( { error : _ ( "Cannot create new task. Please try again later." ) } ) ;
2019-06-26 22:41:09 +00:00
this . handleTaskCanceled ( ) ;
} ) ;
2018-01-18 18:03:02 +00:00
}
handleTaskCanceled = ( ) => {
this . dz . removeAllFiles ( true ) ;
this . resetUploadState ( ) ;
}
handleUpload = ( ) => {
// Not a second click for adding more files?
if ( ! this . state . upload . editing ) {
this . handleTaskCanceled ( ) ;
}
2016-10-21 14:42:46 +00:00
}
2016-11-14 21:32:05 +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 ( ) ;
} ) ;
2016-11-14 21:32:05 +00:00
}
2016-11-09 21:13:43 +00:00
viewMap ( ) {
2016-11-15 16:51:19 +00:00
location . href = ` /map/project/ ${ this . state . data . id } / ` ;
2016-11-09 21:13:43 +00:00
}
2019-02-20 21:42:20 +00:00
handleImportTask = ( ) => {
this . setState ( { importing : true } ) ;
}
handleCancelImportTask = ( ) => {
this . setState ( { importing : false } ) ;
}
2020-12-15 20:56:00 +00:00
handleTaskTitleHint = ( ) => {
return new Promise ( ( resolve , reject ) => {
if ( this . state . upload . files . length > 0 ) {
// Find first image in list
let f = null ;
for ( let i = 0 ; i < this . state . upload . files . length ; i ++ ) {
if ( this . state . upload . files [ i ] . type . indexOf ( "image" ) === 0 ) {
f = this . state . upload . files [ i ] ;
break ;
}
}
if ( ! f ) {
reject ( ) ;
return ;
}
// Parse EXIF
const options = {
ifd0 : false ,
exif : [ 0x9003 ] ,
gps : [ 0x0001 , 0x0002 , 0x0003 , 0x0004 ] ,
interop : false ,
ifd1 : false // thumbnail
} ;
exifr . parse ( f , options ) . then ( gps => {
if ( ! gps . latitude || ! gps . longitude ) {
reject ( ) ;
return ;
}
let dateTime = gps [ "36867" ] ;
// Try to parse the date from EXIF to JS
const parts = dateTime . split ( " " ) ;
if ( parts . length == 2 ) {
let [ d , t ] = parts ;
2021-01-23 22:11:46 +00:00
d = d . replace ( /:/g , "-" ) ;
2020-12-15 20:56:00 +00:00
const tm = Date . parse ( ` ${ d } ${ t } ` ) ;
if ( ! isNaN ( tm ) ) {
dateTime = new Date ( tm ) . toLocaleDateString ( ) ;
}
}
// Fallback to file modified date if
// no exif info is available
if ( ! dateTime ) dateTime = f . lastModifiedDate . toLocaleDateString ( ) ;
// Query nominatim OSM
$ . ajax ( {
url : ` https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat= ${ gps . latitude } &lon= ${ gps . longitude } ` ,
contentType : 'application/json' ,
type : 'GET'
} ) . done ( json => {
if ( json . name ) resolve ( ` ${ json . name } - ${ dateTime } ` ) ;
2020-12-22 16:21:10 +00:00
else if ( json . address && json . address . road ) resolve ( ` ${ json . address . road } - ${ dateTime } ` ) ;
2020-12-15 20:56:00 +00:00
else reject ( new Error ( "Invalid json" ) ) ;
} ) . fail ( reject ) ;
} ) . catch ( reject ) ;
}
} ) ;
}
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 ;
2016-10-11 17:42:17 +00:00
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);"
2017-02-08 22:12:05 +00:00
ref = { this . setRef ( "dropzone" ) }
>
2016-11-14 21:32:05 +00:00
< EditProjectDialog
ref = { ( domNode ) => { this . editProjectDialog = domNode ; } }
2020-12-15 20:56:00 +00:00
title = { _ ( "Edit Project" ) }
saveLabel = { _ ( "Save Changes" ) }
savingLabel = { _ ( "Saving changes..." ) }
2019-11-07 16:22:49 +00:00
saveIcon = "far fa-edit"
2016-11-15 16:51:19 +00:00
projectName = { data . name }
projectDescr = { data . description }
2016-11-14 21:32:05 +00:00
saveAction = { this . updateProject }
2017-06-15 19:47:00 +00:00
deleteAction = { this . hasPermission ( "delete" ) ? this . handleDelete : undefined }
2016-11-14 21:32:05 +00:00
/ >
2016-10-18 15:25:14 +00:00
< div className = "row no-margin" >
2016-11-14 15:58:00 +00:00
< ErrorMessage bind = { [ this , 'error' ] } / >
2016-10-18 15:25:14 +00:00
< div className = "btn-group pull-right" >
2017-06-15 19:47:00 +00:00
{ this . hasPermission ( "add" ) ?
2019-02-20 21:42:20 +00:00
< div className = { "asset-download-buttons btn-group " + ( this . state . upload . uploading ? "hide" : "" ) } >
< button type = "button"
className = "btn btn-primary btn-sm"
2018-01-18 18:03:02 +00:00
onClick = { this . handleUpload }
2017-06-15 19:47:00 +00:00
ref = { this . setRef ( "uploadButton" ) } >
2019-05-15 19:50:36 +00:00
< i className = "glyphicon glyphicon-upload" > < / i >
2020-12-15 20:56:00 +00:00
{ _ ( "Select Images and GCP" ) }
2019-05-15 19:50:36 +00:00
< / button >
< button type = "button"
className = "btn btn-default btn-sm"
onClick = { this . handleImportTask } >
2020-12-15 20:56:00 +00:00
< i className = "glyphicon glyphicon-import" > < / i > { _ ( "Import" ) }
2019-05-15 19:50:36 +00:00
< / button >
2019-09-07 18:35:30 +00:00
{ this . state . buttons . map ( ( button , i ) => < React.Fragment key = { i } > { button } < / React.Fragment > ) }
2019-05-15 19:50:36 +00:00
< / div >
2017-06-15 19:47:00 +00:00
: "" }
2018-01-18 18:03:02 +00:00
2016-10-18 15:25:14 +00:00
< button disabled = { this . state . upload . error !== "" }
2018-01-18 18:03:02 +00:00
type = "button"
className = { "btn btn-danger btn-sm " + ( ! this . state . upload . uploading ? "hide" : "" ) }
2016-10-18 15:25:14 +00:00
onClick = { this . cancelUpload } >
< i className = "glyphicon glyphicon-remove-circle" > < / i >
Cancel Upload
< / button >
2016-11-09 21:13:43 +00:00
< button type = "button" className = "btn btn-default btn-sm" onClick = { this . viewMap } >
2020-12-15 20:56:00 +00:00
< 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 ?
2016-11-14 21:32:05 +00:00
< span >
< i className = 'fa fa-tasks' >
< / i > < a href = "javascript:void(0);" onClick = { this . toggleTaskList } >
2020-12-15 20:56:00 +00:00
{ interpolate ( _ ( "%(count)s Tasks" ) , { count : numTasks } ) } < i className = { 'fa fa-caret-' + ( this . state . showTaskList ? 'down' : 'right' ) } > < / i >
2016-11-14 21:32:05 +00:00
< / a >
< / span >
: "" }
2019-11-07 16:22:49 +00:00
< i className = 'far fa-edit' >
2020-12-15 20:56:00 +00:00
< / 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" >
2017-11-22 19:53:29 +00:00
{ this . state . upload . uploading ? < UploadProgressBar { ...this.state.upload } / > : "" }
2019-06-27 15:19:52 +00:00
2016-10-18 15:25:14 +00:00
{ this . state . upload . error !== "" ?
< div className = "alert alert-warning alert-dismissible" >
2020-12-16 19:37:35 +00:00
< button type = "button" className = "close" title = { _ ( "Close" ) } onClick = { this . closeUploadError } > < span aria - hidden = "true" > & times ; < / span > < / button >
2016-10-18 15:25:14 +00:00
{ this . state . upload . error }
2016-10-12 22:18:37 +00:00
< / div >
2016-10-18 15:25:14 +00:00
: "" }
2016-10-12 22:18:37 +00:00
2017-07-18 20:07:53 +00:00
{ this . state . upload . editing ?
2017-11-22 19:53:29 +00:00
< NewTaskPanel
2016-10-22 15:23:37 +00:00
onSave = { this . handleTaskSaved }
2018-01-18 18:03:02 +00:00
onCancel = { this . handleTaskCanceled }
2020-12-15 20:56:00 +00:00
suggestedTaskName = { this . handleTaskTitleHint }
2017-11-22 19:53:29 +00:00
filesCount = { this . state . upload . totalCount }
showResize = { true }
2018-12-31 21:50:51 +00:00
getFiles = { ( ) => this . state . upload . files }
2016-10-21 14:42:46 +00:00
/ >
2016-10-22 15:23:37 +00:00
: "" }
2019-02-20 21:42:20 +00:00
{ this . state . importing ?
< ImportTaskPanel
2019-02-21 19:16:48 +00:00
onImported = { this . newTaskAdded }
2019-02-20 21:42:20 +00:00
onCancel = { this . handleCancelImportTask }
projectId = { this . state . data . id }
/ >
: "" }
2016-11-14 21:32:05 +00:00
{ 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 ` }
2016-11-14 21:32:05 +00:00
onDelete = { this . taskDeleted }
2021-08-04 20:20:51 +00:00
onTaskMoved = { this . taskMoved }
2021-08-04 17:09:27 +00:00
hasPermission = { this . hasPermission }
2017-04-13 21:03:42 +00:00
history = { this . props . history }
2016-11-14 21:32:05 +00:00
/ > : " " }
2016-11-02 22:32:24 +00:00
2016-10-18 15:25:14 +00:00
< / div >
2016-10-11 20:37:00 +00:00
< / li >
2016-10-11 17:42:17 +00:00
) ;
}
}
export default ProjectListItem ;