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' ;
2023-02-22 16:56:38 +00:00
import SortPanel from './SortPanel' ;
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' ;
2023-03-07 18:24:26 +00:00
import Tags from '../classes/Tags' ;
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 ,
2021-08-06 15:33:43 +00:00
onProjectDuplicated : 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 ,
2023-02-22 16:56:38 +00:00
buttons : [ ] ,
2023-03-13 19:15:02 +00:00
sortKey : "-created_at" ,
2023-03-14 15:10:38 +00:00
filterTags : [ ] ,
selectedTags : [ ] ,
filterText : ""
2016-10-11 17:42:17 +00:00
} ;
2023-02-22 16:56:38 +00:00
this . sortItems = [ {
key : "created_at" ,
2023-03-08 19:40:35 +00:00
label : _ ( "Created on" )
2023-02-22 16:56:38 +00:00
} , {
key : "name" ,
label : _ ( "Name" )
2023-03-07 16:56:17 +00:00
} , {
key : "tags" ,
label : _ ( "Tags" )
2023-02-22 16:56:38 +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 ) ;
2023-11-06 15:35:02 +00:00
this . handleCancel = this . handleCancel . 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
}
2023-03-14 15:10:38 +00:00
componentDidUpdate ( prevProps , prevState ) {
if ( prevState . filterText !== this . state . filterText ||
prevState . selectedTags . length !== this . state . selectedTags . length ) {
if ( this . taskList ) this . taskList . applyFilter ( this . state . filterText , this . state . selectedTags ) ;
}
}
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 ,
2023-01-24 16:10:14 +00:00
acceptedFiles : "image/*,text/*,.las,.laz,video/*,.srt" ,
2017-11-20 20:52:23 +00:00
autoProcessQueue : false ,
2017-06-15 19:47:00 +00:00
createImageThumbnails : false ,
clickable : this . uploadButton ,
2023-06-19 06:24:14 +00:00
maxFilesize : 131072 , // 128G
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 ++ ;
2023-12-21 14:00:12 +00:00
setTimeout ( ( ) => {
this . dz . processQueue ( ) ;
} , 5000 * file . retries ) ;
2019-06-26 22:41:09 +00:00
} 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" ) {
2023-06-19 06:24:14 +00:00
if ( ( file . size / 1024 ) > this . dz . options . maxFilesize ) {
// Delete from upload queue
this . setUploadState ( {
totalCount : this . state . upload . totalCount - 1 ,
totalBytes : this . state . upload . totalBytes - file . size
} ) ;
2024-02-16 04:54:23 +00:00
throw new Error ( interpolate ( _ ( 'Cannot upload %(filename)s, file is too large! Default MaxFileSize is %(maxFileSize)s MB!' ) , { filename : file . name , maxFileSize : this . dz . options . maxFilesize } ) ) ;
2023-06-19 06:26:18 +00:00
}
2019-06-26 22:41:09 +00:00
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 ) {
2023-11-06 15:35:02 +00:00
if ( this . manuallyCanceled ) {
// Manually canceled, ignore error
this . setUploadState ( { uploading : false } ) ;
} else {
this . setUploadState ( { error : ` ${ e . message } ` , uploading : false } ) ;
}
if ( this . dz . files . length ) this . dz . cancelUpload ( ) ;
2019-06-26 22:41:09 +00:00
}
} )
. on ( "queuecomplete" , ( ) => {
const remainingFilesCount = this . state . upload . totalCount - this . state . upload . uploadedCount ;
2023-11-06 15:35:02 +00:00
if ( remainingFilesCount === 0 && this . state . upload . uploadedCount > 0 ) {
2019-06-26 22:41:09 +00:00
// 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 : "" } ) ;
}
2023-11-06 15:35:02 +00:00
cancelUpload ( ) {
2016-10-18 15:25:14 +00:00
this . dz . removeAllFiles ( true ) ;
}
2023-11-06 15:35:02 +00:00
handleCancel ( ) {
this . manuallyCanceled = true ;
this . cancelUpload ( ) ;
if ( this . dz . _taskInfo && this . dz . _taskInfo . id !== undefined ) {
$ . ajax ( {
url : ` /api/projects/ ${ this . state . data . id } /tasks/ ${ this . dz . _taskInfo . id } /remove/ ` ,
contentType : 'application/json' ,
dataType : 'json' ,
type : 'POST'
} ) ;
}
setTimeout ( ( ) => {
this . manuallyCanceled = false ;
} , 500 ) ;
}
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 ( ) ;
}
2023-06-18 14:57:13 +00:00
handleHideProject = ( deleteWarning , deleteAction ) => {
return ( ) => {
if ( window . confirm ( deleteWarning ) ) {
this . setState ( { error : "" , refreshing : true } ) ;
deleteAction ( )
. fail ( e => {
this . setState ( { error : e . message || ( e . responseJSON || { } ) . detail || e . responseText || _ ( "Could not delete item" ) } ) ;
} ) . always ( ( ) => {
this . setState ( { refreshing : false } ) ;
} ) ;
}
}
}
2016-11-14 21:32:05 +00:00
updateProject ( project ) {
2016-11-15 16:51:19 +00:00
return $ . ajax ( {
2021-10-19 16:40:23 +00:00
url : ` /api/projects/ ${ this . state . data . id } /edit/ ` ,
2016-11-15 16:51:19 +00:00
contentType : 'application/json' ,
data : JSON . stringify ( {
name : project . name ,
description : project . descr ,
2023-03-07 18:24:26 +00:00
tags : project . tags ,
2021-10-19 16:40:23 +00:00
permissions : project . permissions
2016-11-15 16:51:19 +00:00
} ) ,
dataType : 'json' ,
2021-10-19 16:40:23 +00:00
type : 'POST'
2016-11-15 16:51:19 +00:00
} ) . 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 ) ;
}
} ) ;
}
2023-02-22 16:56:38 +00:00
sortChanged = key => {
if ( this . taskList ) {
this . setState ( { sortKey : key } ) ;
setTimeout ( ( ) => {
this . taskList . refresh ( ) ;
} , 0 ) ;
}
}
2023-03-13 16:28:18 +00:00
handleTagClick = tag => {
return e => {
const evt = new CustomEvent ( "onProjectListTagClicked" , { detail : tag } ) ;
document . dispatchEvent ( evt ) ;
}
}
2023-03-13 19:15:02 +00:00
tagsChanged = ( filterTags ) => {
2023-03-14 15:10:38 +00:00
this . setState ( { filterTags , selectedTags : [ ] } ) ;
}
handleFilterTextChange = e => {
this . setState ( { filterText : e . target . value } ) ;
}
toggleTag = t => {
return ( ) => {
if ( this . state . selectedTags . indexOf ( t ) === - 1 ) {
this . setState ( update ( this . state , { selectedTags : { $push : [ t ] } } ) ) ;
} else {
this . setState ( { selectedTags : this . state . selectedTags . filter ( tag => tag !== t ) } ) ;
}
}
}
selectTag = t => {
if ( this . state . selectedTags . indexOf ( t ) === - 1 ) {
this . setState ( update ( this . state , { selectedTags : { $push : [ t ] } } ) ) ;
}
}
clearFilter = ( ) => {
this . setState ( {
filterText : "" ,
selectedTags : [ ]
} ) ;
}
onOpenFilter = ( ) => {
if ( this . state . filterTags . length === 0 ) {
setTimeout ( ( ) => {
this . filterTextInput . focus ( ) ;
} , 0 ) ;
}
2023-03-13 19:15:02 +00:00
}
2016-10-21 14:42:46 +00:00
render ( ) {
2023-03-13 19:15:02 +00:00
const { refreshing , data , filterTags } = this . state ;
2016-11-15 16:51:19 +00:00
const numTasks = data . tasks . length ;
2021-10-18 19:19:16 +00:00
const canEdit = this . hasPermission ( "change" ) ;
2023-03-07 18:24:26 +00:00
const userTags = Tags . userTags ( data . tags ) ;
2023-03-22 15:35:43 +00:00
let deleteWarning = _ ( "All tasks, images and models associated with this project will be permanently deleted. Are you sure you want to continue?" ) ;
if ( ! data . owned ) deleteWarning = _ ( "This project was shared with you. It will not be deleted, but simply hidden from your dashboard. Continue?" )
2016-11-15 16:51:19 +00:00
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" ) }
>
2021-10-18 19:19:16 +00:00
{ canEdit ?
< EditProjectDialog
ref = { ( domNode ) => { this . editProjectDialog = domNode ; } }
title = { _ ( "Edit Project" ) }
saveLabel = { _ ( "Save Changes" ) }
savingLabel = { _ ( "Saving changes..." ) }
saveIcon = "far fa-edit"
showDuplicate = { true }
onDuplicated = { this . props . onProjectDuplicated }
projectName = { data . name }
projectDescr = { data . description }
projectId = { data . id }
2023-03-07 18:24:26 +00:00
projectTags = { data . tags }
2023-03-22 15:35:43 +00:00
deleteWarning = { deleteWarning }
2021-10-18 19:19:16 +00:00
saveAction = { this . updateProject }
showPermissions = { this . hasPermission ( "change" ) }
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' ] } / >
2022-02-02 18:46:53 +00:00
< div className = "btn-group project-buttons" >
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" : "" ) }
2023-11-06 15:35:02 +00:00
onClick = { this . handleCancel } >
2016-10-18 15:25:14 +00:00
< i className = "glyphicon glyphicon-remove-circle" > < / i >
Cancel Upload
< / button >
< / div >
2016-10-11 20:37:00 +00:00
2022-02-02 18:46:53 +00:00
< div className = "project-name" >
2016-11-15 16:51:19 +00:00
{ data . name }
2023-03-07 18:24:26 +00:00
{ userTags . length > 0 ?
2023-03-13 16:28:18 +00:00
userTags . map ( ( t , i ) => < div key = { i } className = "tag-badge small-badge" onClick = { this . handleTagClick ( t ) } > { t } < / div > )
2023-03-07 18:24:26 +00:00
: "" }
2022-02-02 18:46:53 +00:00
< / div >
2016-10-25 14:47:49 +00:00
< 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 >
2022-01-28 12:01:51 +00:00
< i className = 'fa fa-tasks' > < / i >
2023-02-21 17:06:47 +00:00
< 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 >
: "" }
2023-02-21 17:06:47 +00:00
{ this . state . showTaskList && numTasks > 1 ?
< div className = "task-filters" >
2023-03-14 15:10:38 +00:00
< div className = "btn-group" >
{ this . state . selectedTags . length || this . state . filterText !== "" ?
< a className = "quick-clear-filter" href = "javascript:void(0)" onClick = { this . clearFilter } > × < / a >
: "" }
< i className = 'fa fa-filter' > < / i >
< a href = "javascript:void(0);" onClick = { this . onOpenFilter } className = "dropdown-toggle" data - toggle - outside data - toggle = "dropdown" aria - haspopup = "true" aria - expanded = "false" >
{ _ ( "Filter" ) }
< / a >
< ul className = "dropdown-menu dropdown-menu-right filter-dropdown" >
< li className = "filter-text-container" >
< input type = "text" className = "form-control filter-text theme-border-secondary-07"
value = { this . state . filterText }
ref = { domNode => { this . filterTextInput = domNode } }
placeholder = ""
spellCheck = "false"
autoComplete = "false"
onChange = { this . handleFilterTextChange } / >
< / li >
{ filterTags . map ( t => < li key = { t } className = "tag-selection" >
< input type = "checkbox"
className = "filter-checkbox"
id = { "filter-tag-" + data . id + "-" + t }
checked = { this . state . selectedTags . indexOf ( t ) !== - 1 }
onChange = { this . toggleTag ( t ) } / > < label className = "filter-checkbox-label" htmlFor = { "filter-tag-" + data . id + "-" + t } > { t } < / label >
< / li > ) }
< li className = "clear-container" > < input type = "button" onClick = { this . clearFilter } className = "btn btn-default btn-xs" value = { _ ( "Clear" ) } / > < / li >
< / ul >
< / div >
2023-02-21 17:06:47 +00:00
< div className = "btn-group" >
< i className = 'fa fa-sort-alpha-down' > < / i >
< a href = "javascript:void(0);" className = "dropdown-toggle" data - toggle = "dropdown" aria - haspopup = "true" aria - expanded = "false" >
2023-02-22 16:56:38 +00:00
{ _ ( "Sort" ) }
2023-02-21 17:06:47 +00:00
< / a >
2023-03-08 19:40:35 +00:00
< SortPanel selected = "-created_at" items = { this . sortItems } onChange = { this . sortChanged } / >
2023-02-21 17:06:47 +00:00
< / div >
< / div > : "" }
2016-11-14 21:32:05 +00:00
2023-03-08 17:21:23 +00:00
{ numTasks > 0 ?
[ < i key = "edit-icon" className = 'fa fa-globe' > < / i >
, < a key = "edit-text" href = "javascript:void(0);" onClick = { this . viewMap } >
{ _ ( "View Map" ) }
< / a > ]
: "" }
2021-10-18 19:19:16 +00:00
{ canEdit ?
2022-01-28 12:01:51 +00:00
[ < i key = "edit-icon" className = 'far fa-edit' > < / i >
, < a key = "edit-text" href = "javascript:void(0);" onClick = { this . handleEditProject } > { _ ( "Edit" ) }
2021-10-18 19:19:16 +00:00
< / a > ]
: "" }
2023-03-08 17:21:23 +00:00
2023-06-18 14:57:13 +00:00
{ ! canEdit && ! data . owned ?
[ < i key = "edit-icon" className = 'far fa-eye-slash' > < / i >
, < a key = "edit-text" href = "javascript:void(0);" onClick = { this . handleHideProject ( deleteWarning , this . handleDelete ) } > { _ ( "Delete" ) }
< / a > ]
: "" }
2016-10-25 14:47:49 +00:00
< / 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" ) }
2023-02-22 16:56:38 +00:00
source = { ` /api/projects/ ${ data . id } /tasks/?ordering= ${ this . state . sortKey } ` }
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 }
2023-03-13 19:15:02 +00:00
onTagsChanged = { this . tagsChanged }
2023-03-14 15:10:38 +00:00
onTagClicked = { this . selectTag }
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 ;