kopia lustrzana https://github.com/OpenDroneMap/WebODM
Sharing to OAM working
rodzic
a847edcd06
commit
8f0791aacc
|
@ -11,6 +11,7 @@ from string import Template
|
||||||
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
from app.models import Setting
|
||||||
from webodm import settings
|
from webodm import settings
|
||||||
|
|
||||||
logger = logging.getLogger('app.logger')
|
logger = logging.getLogger('app.logger')
|
||||||
|
@ -174,6 +175,10 @@ def get_dynamic_script_handler(script_path, callback=None, **kwargs):
|
||||||
return handleRequest
|
return handleRequest
|
||||||
|
|
||||||
|
|
||||||
|
def get_site_settings():
|
||||||
|
return Setting.objects.first()
|
||||||
|
|
||||||
|
|
||||||
def versionToInt(version):
|
def versionToInt(version):
|
||||||
"""
|
"""
|
||||||
Converts a WebODM version string (major.minor.build) to a integer value
|
Converts a WebODM version string (major.minor.build) to a integer value
|
||||||
|
|
|
@ -61,7 +61,10 @@ module.exports = {
|
||||||
|
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: ['node_modules', 'bower_components'],
|
modules: ['node_modules', 'bower_components'],
|
||||||
extensions: ['.js', '.jsx']
|
extensions: ['.js', '.jsx'],
|
||||||
|
alias: {
|
||||||
|
webodm: path.resolve(__dirname, '../../../app/static/app/js')
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
externals: {
|
externals: {
|
||||||
|
|
|
@ -553,21 +553,21 @@ class TaskListItem extends React.Component {
|
||||||
return (
|
return (
|
||||||
<div className="task-list-item">
|
<div className="task-list-item">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-5 name">
|
<div className="col-sm-5 name">
|
||||||
<i onClick={this.toggleExpanded} className={"clickable fa " + (this.state.expanded ? "fa-minus-square-o" : " fa-plus-square-o")}></i> <a href="javascript:void(0);" onClick={this.toggleExpanded}>{name}</a>
|
<i onClick={this.toggleExpanded} className={"clickable fa " + (this.state.expanded ? "fa-minus-square-o" : " fa-plus-square-o")}></i> <a href="javascript:void(0);" onClick={this.toggleExpanded}>{name}</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-1 details">
|
<div className="col-sm-1 details">
|
||||||
<i className="fa fa-image"></i> {task.images_count}
|
<i className="fa fa-image"></i> {task.images_count}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-2 details">
|
<div className="col-sm-2 details">
|
||||||
<i className="fa fa-clock-o"></i> {this.hoursMinutesSecs(this.state.time)}
|
<i className="fa fa-clock-o"></i> {this.hoursMinutesSecs(this.state.time)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-3">
|
<div className="col-sm-3">
|
||||||
{showEditLink ?
|
{showEditLink ?
|
||||||
<a href="javascript:void(0);" onClick={this.startEditing}>{statusLabel}</a>
|
<a href="javascript:void(0);" onClick={this.startEditing}>{statusLabel}</a>
|
||||||
: statusLabel}
|
: statusLabel}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-1 text-right">
|
<div className="col-sm-1 text-right">
|
||||||
<div className="status-icon">
|
<div className="status-icon">
|
||||||
<i className={statusIcon}></i>
|
<i className={statusIcon}></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,7 +16,7 @@ class TaskPluginActionButtons extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props){
|
constructor(props){
|
||||||
super();
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
buttons: []
|
buttons: []
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "WebODM",
|
"name": "WebODM",
|
||||||
"version": "0.5.3",
|
"version": "0.6.0",
|
||||||
"description": "Open Source Drone Image Processing",
|
"description": "Open Source Drone Image Processing",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
PluginsAPI.Map.willAddControls([
|
PluginsAPI.Map.willAddControls([
|
||||||
'measure/build/app.js',
|
'measure/build/app.js',
|
||||||
'measure/build/app.css'
|
'measure/build/app.css'
|
||||||
], function(options, App){
|
], function(args, App){
|
||||||
new App(options.map);
|
new App(args.map);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,26 @@
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
import os
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
|
import piexif
|
||||||
|
from PIL import Image
|
||||||
|
from rest_framework import serializers
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from app.plugins import GlobalDataStore
|
from app.models import ImageUpload
|
||||||
|
from app.plugins import GlobalDataStore, get_site_settings
|
||||||
from app.plugins.views import TaskView
|
from app.plugins.views import TaskView
|
||||||
from app.plugins.worker import task
|
from app.plugins.worker import task
|
||||||
|
|
||||||
|
from webodm import settings
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger('app.logger')
|
||||||
ds = GlobalDataStore('openaerialmap')
|
ds = GlobalDataStore('openaerialmap')
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,27 +42,77 @@ def set_task_info(task_id, json):
|
||||||
# TODO: task info cleanup when task is deleted via signal
|
# TODO: task info cleanup when task is deleted via signal
|
||||||
|
|
||||||
|
|
||||||
class ShareInfo(TaskView):
|
class Info(TaskView):
|
||||||
def get(self, request, pk=None):
|
def get(self, request, pk=None):
|
||||||
task = self.get_and_check_task(request, pk)
|
task = self.get_and_check_task(request, pk)
|
||||||
return Response(get_task_info(task.id), status=status.HTTP_200_OK)
|
|
||||||
|
task_info = get_task_info(task.id)
|
||||||
|
|
||||||
|
# Populate fields from first image in task
|
||||||
|
img = ImageUpload.objects.filter(task=task).exclude(image__iendswith='.txt').first()
|
||||||
|
img_path = os.path.join(settings.MEDIA_ROOT, img.path())
|
||||||
|
im = Image.open(img_path)
|
||||||
|
|
||||||
|
# TODO: for better data we could look over all images
|
||||||
|
# and find actual end and start time
|
||||||
|
# Here we're picking an image at random and assuming a one hour flight
|
||||||
|
|
||||||
|
task_info['endDate'] = datetime.utcnow().timestamp() * 1000
|
||||||
|
task_info['sensor'] = ''
|
||||||
|
task_info['title'] = task.name
|
||||||
|
task_info['provider'] = get_site_settings().organization_name
|
||||||
|
|
||||||
|
if 'exif' in im.info:
|
||||||
|
exif_dict = piexif.load(im.info['exif'])
|
||||||
|
if 'Exif' in exif_dict:
|
||||||
|
if piexif.ExifIFD.DateTimeOriginal in exif_dict['Exif']:
|
||||||
|
try:
|
||||||
|
parsed_date = datetime.strptime(exif_dict['Exif'][piexif.ExifIFD.DateTimeOriginal].decode('ascii'),
|
||||||
|
'%Y:%m:%d %H:%M:%S')
|
||||||
|
task_info['endDate'] = parsed_date.timestamp() * 1000
|
||||||
|
except ValueError:
|
||||||
|
# Ignore date field if we can't parse it
|
||||||
|
pass
|
||||||
|
if '0th' in exif_dict:
|
||||||
|
if piexif.ImageIFD.Make in exif_dict['0th']:
|
||||||
|
task_info['sensor'] = exif_dict['0th'][piexif.ImageIFD.Make].decode('ascii').strip(' \t\r\n\0')
|
||||||
|
|
||||||
|
if piexif.ImageIFD.Model in exif_dict['0th']:
|
||||||
|
task_info['sensor'] = (task_info['sensor'] + " " + exif_dict['0th'][piexif.ImageIFD.Model].decode('ascii')).strip(' \t\r\n\0')
|
||||||
|
|
||||||
|
task_info['startDate'] = task_info['endDate'] - 60 * 60 * 1000
|
||||||
|
set_task_info(task.id, task_info)
|
||||||
|
|
||||||
|
return Response(task_info, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
class JSONSerializer(serializers.Serializer):
|
||||||
|
oamParams = serializers.JSONField(help_text="OpenAerialMap share parameters (sensor, title, provider, etc.)")
|
||||||
|
|
||||||
|
|
||||||
class Share(TaskView):
|
class Share(TaskView):
|
||||||
def post(self, request, pk=None):
|
def post(self, request, pk=None):
|
||||||
task = self.get_and_check_task(request, pk)
|
task = self.get_and_check_task(request, pk)
|
||||||
|
|
||||||
upload_orthophoto_to_oam.delay(task.id, task.get_asset_download_path('orthophoto.tif'))
|
serializer = JSONSerializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
oam_params = serializer['oamParams'].value
|
||||||
|
|
||||||
|
upload_orthophoto_to_oam.delay(task.id,
|
||||||
|
task.get_asset_download_path('orthophoto.tif'),
|
||||||
|
oam_params)
|
||||||
|
|
||||||
task_info = get_task_info(task.id)
|
task_info = get_task_info(task.id)
|
||||||
task_info['sharing'] = True
|
task_info['sharing'] = True
|
||||||
|
task_info['oam_upload_id'] = ''
|
||||||
|
task_info['error'] = ''
|
||||||
set_task_info(task.id, task_info)
|
set_task_info(task.id, task_info)
|
||||||
|
|
||||||
return Response(task_info, status=status.HTTP_200_OK)
|
return Response(task_info, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
@task
|
@task
|
||||||
def upload_orthophoto_to_oam(task_id, orthophoto_path):
|
def upload_orthophoto_to_oam(task_id, orthophoto_path, oam_params):
|
||||||
# Upload to temporary central location since
|
# Upload to temporary central location since
|
||||||
# OAM requires a public URL and not all WebODM
|
# OAM requires a public URL and not all WebODM
|
||||||
# instances are public
|
# instances are public
|
||||||
|
@ -58,15 +122,28 @@ def upload_orthophoto_to_oam(task_id, orthophoto_path):
|
||||||
('file', ('orthophoto.tif', open(orthophoto_path, 'rb'), 'image/tiff')),
|
('file', ('orthophoto.tif', open(orthophoto_path, 'rb'), 'image/tiff')),
|
||||||
]).json()
|
]).json()
|
||||||
|
|
||||||
|
task_info = get_task_info(task_id)
|
||||||
|
|
||||||
if 'url' in res:
|
if 'url' in res:
|
||||||
orthophoto_public_url = res['url']
|
orthophoto_public_url = res['url']
|
||||||
|
logger.info("Orthophoto uploaded to intermediary public URL " + orthophoto_public_url)
|
||||||
|
|
||||||
import logging
|
# That's OK... we :heart: dronedeploy
|
||||||
logger = logging.getLogger('app.logger')
|
res = requests.post('https://api.openaerialmap.org/dronedeploy?{}'.format(urlencode(oam_params)),
|
||||||
|
json={
|
||||||
|
'download_path': orthophoto_public_url
|
||||||
|
}).json()
|
||||||
|
|
||||||
logger.info("UPLOADED TO " + orthophoto_public_url)
|
if 'results' in res and 'upload' in res['results']:
|
||||||
|
task_info['oam_upload_id'] = res['results']['upload']
|
||||||
|
task_info['shared'] = True
|
||||||
|
else:
|
||||||
|
task_info['error'] = 'Could not upload orthophoto to OAM. The server replied: {}'.format(json.dumps(res))
|
||||||
|
|
||||||
|
# Attempt to cleanup intermediate results
|
||||||
|
requests.get('https://www.webodm.org/oam/cleanup/{}'.format(os.path.basename(orthophoto_public_url)))
|
||||||
else:
|
else:
|
||||||
task_info = get_task_info(task_id)
|
task_info['error'] = 'Could not upload orthophoto to intermediate location: {}.'.format(json.dumps(res))
|
||||||
task_info['sharing'] = False
|
|
||||||
task_info['error'] = 'Could not upload orthophoto to intermediate location.'
|
task_info['sharing'] = False
|
||||||
set_task_info(task_id, task_info)
|
set_task_info(task_id, task_info)
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
PluginsAPI.Dashboard.addTaskActionButton([
|
PluginsAPI.Dashboard.addTaskActionButton([
|
||||||
'openaerialmap/build/ShareButton.js',
|
'openaerialmap/build/ShareButton.js',
|
||||||
'openaerialmap/build/ShareButton.css'
|
'openaerialmap/build/ShareButton.css'
|
||||||
],function(options, ShareButton){
|
],function(args, ShareButton){
|
||||||
var task = options.task;
|
var task = args.task;
|
||||||
|
|
||||||
if (task.available_assets.indexOf("orthophoto.tif") !== -1){
|
if (task.available_assets.indexOf("orthophoto.tif") !== -1){
|
||||||
console.log("INSTANTIATED");
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
button: React.createElement(ShareButton, {task: task, token: "${token}"}),
|
button: React.createElement(ShareButton, {task: task, token: "${token}"}),
|
||||||
task: task
|
task: task
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "OpenAerialMap",
|
"name": "OpenAerialMap",
|
||||||
"webodmMinVersion": "0.5.3",
|
"webodmMinVersion": "0.6.0",
|
||||||
"description": "A plugin to upload orthophotos to OpenAerialMap",
|
"description": "A plugin to upload orthophotos to OpenAerialMap",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"author": "Piero Toffanin",
|
"author": "Piero Toffanin",
|
||||||
|
|
|
@ -5,7 +5,7 @@ from app.plugins import PluginBase, Menu, MountPoint
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from plugins.openaerialmap.api import ShareInfo, Share
|
from plugins.openaerialmap.api import Info, Share
|
||||||
|
|
||||||
|
|
||||||
class TokenForm(forms.Form):
|
class TokenForm(forms.Form):
|
||||||
|
@ -45,7 +45,7 @@ class Plugin(PluginBase):
|
||||||
|
|
||||||
def api_mount_points(self):
|
def api_mount_points(self):
|
||||||
return [
|
return [
|
||||||
MountPoint('task/(?P<pk>[^/.]+)/shareinfo', ShareInfo.as_view()),
|
MountPoint('task/(?P<pk>[^/.]+)/info', Info.as_view()),
|
||||||
MountPoint('task/(?P<pk>[^/.]+)/share', Share.as_view())
|
MountPoint('task/(?P<pk>[^/.]+)/share', Share.as_view())
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import './ShareButton.scss';
|
import './ShareButton.scss';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import ShareDialog from './ShareDialog';
|
||||||
|
import Storage from 'webodm/classes/Storage';
|
||||||
|
import ErrorMessage from 'webodm/components/ErrorMessage';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
|
||||||
module.exports = class ShareButton extends React.Component{
|
module.exports = class ShareButton extends React.Component{
|
||||||
|
@ -19,41 +22,125 @@ module.exports = class ShareButton extends React.Component{
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
loading: true,
|
loading: true,
|
||||||
shared: false,
|
taskInfo: {},
|
||||||
error: ''
|
error: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("AH!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(){
|
componentDidMount(){
|
||||||
const { task } = this.props;
|
this.updateTaskInfo(false);
|
||||||
|
}
|
||||||
|
|
||||||
$.ajax({
|
updateTaskInfo = (showErrors) => {
|
||||||
type: 'GET',
|
const { task } = this.props;
|
||||||
url: `/api/plugins/openaerialmap/task/${task.id}/shareinfo`,
|
return $.ajax({
|
||||||
contentType: "application/json"
|
type: 'GET',
|
||||||
}).done(result => {
|
url: `/api/plugins/openaerialmap/task/${task.id}/info`,
|
||||||
this.setState({shared: result.shared, loading: false})
|
contentType: 'application/json'
|
||||||
}).fail(error => {
|
}).done(taskInfo => {
|
||||||
this.setState({error, loading: false});
|
// Allow a user to specify a better name for the sensor
|
||||||
});
|
// and remember it.
|
||||||
|
let sensor = Storage.getItem("oam_sensor_pref_" + taskInfo.sensor);
|
||||||
|
if (sensor) taskInfo.sensor = sensor;
|
||||||
|
|
||||||
|
// Allow a user to change the default provider name
|
||||||
|
let provider = Storage.getItem("oam_provider_pref");
|
||||||
|
if (provider) taskInfo.provider = provider;
|
||||||
|
|
||||||
|
this.setState({taskInfo, loading: false});
|
||||||
|
if (taskInfo.error && showErrors) this.setState({error: taskInfo.error});
|
||||||
|
}).fail(error => {
|
||||||
|
this.setState({error, loading: false});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(){
|
||||||
|
if (this.monitorTimeout) clearTimeout(this.monitorTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick = () => {
|
handleClick = () => {
|
||||||
console.log("HEY!", this.props.token);
|
const { taskInfo } = this.state;
|
||||||
|
if (!taskInfo.shared){
|
||||||
|
this.shareDialog.show();
|
||||||
|
}else if (taskInfo.oam_upload_id){
|
||||||
|
window.open(`https://map.openaerialmap.org/#/upload/status/${encodeURIComponent(taskInfo.oam_upload_id)}`, '_blank');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shareToOAM = (formData) => {
|
||||||
|
const { task } = this.props;
|
||||||
|
|
||||||
|
const oamParams = {
|
||||||
|
token: this.props.token,
|
||||||
|
sensor: formData.sensor,
|
||||||
|
acquisition_start: formData.startDate,
|
||||||
|
acquisition_end: formData.endDate,
|
||||||
|
title: formData.title,
|
||||||
|
provider: formData.provider,
|
||||||
|
tags: formData.tags
|
||||||
|
};
|
||||||
|
|
||||||
|
return $.ajax({
|
||||||
|
url: `/api/plugins/openaerialmap/task/${task.id}/share`,
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify({
|
||||||
|
oamParams: oamParams
|
||||||
|
}),
|
||||||
|
dataType: 'json',
|
||||||
|
type: 'POST'
|
||||||
|
}).done(taskInfo => {
|
||||||
|
// Allow a user to associate the sensor name coming from the EXIF tags
|
||||||
|
// to one that perhaps is more human readable.
|
||||||
|
Storage.setItem("oam_sensor_pref_" + taskInfo.sensor, formData.sensor);
|
||||||
|
Storage.setItem("oam_provider_pref", formData.provider);
|
||||||
|
|
||||||
|
this.setState({taskInfo});
|
||||||
|
this.monitorProgress();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
monitorProgress = () => {
|
||||||
|
if (this.state.taskInfo.sharing){
|
||||||
|
// Monitor progress
|
||||||
|
this.monitorTimeout = setTimeout(() => {
|
||||||
|
this.updateTaskInfo(true).always(this.monitorProgress);
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render(){
|
render(){
|
||||||
const { loading, shared } = this.state;
|
const { loading, taskInfo } = this.state;
|
||||||
|
|
||||||
return (<button
|
const getButtonIcon = () => {
|
||||||
|
if (loading || taskInfo.sharing) return "fa fa-circle-o-notch fa-spin fa-fw";
|
||||||
|
else return "oam-icon fa";
|
||||||
|
};
|
||||||
|
|
||||||
|
const getButtonLabel = () => {
|
||||||
|
if (loading) return "";
|
||||||
|
else if (taskInfo.sharing) return " Sharing...";
|
||||||
|
else if (taskInfo.shared) return " View In OAM";
|
||||||
|
else return " Share To OAM";
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = [
|
||||||
|
<ErrorMessage bind={[this, "error"]} />,
|
||||||
|
<button
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
disabled={loading || shared}
|
disabled={loading || taskInfo.sharing}
|
||||||
className="btn btn-sm btn-primary">
|
className="btn btn-sm btn-primary">
|
||||||
{loading ?
|
{[<i className={getButtonIcon()}></i>, getButtonLabel()]}
|
||||||
<i className="fa fa-circle-o-notch fa-spin fa-fw"></i> :
|
</button>];
|
||||||
[<i className="oam-icon fa"></i>, (shared ? "Shared To OAM" : " Share To OAM")]}
|
|
||||||
</button>);
|
if (taskInfo.sensor !== undefined){
|
||||||
|
result.unshift(<ShareDialog
|
||||||
|
ref={(domNode) => { this.shareDialog = domNode; }}
|
||||||
|
task={this.props.task}
|
||||||
|
taskInfo={taskInfo}
|
||||||
|
saveAction={this.shareToOAM}
|
||||||
|
/>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ErrorMessage from 'webodm/components/ErrorMessage';
|
||||||
|
import FormDialog from 'webodm/components/FormDialog';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import $ from 'jquery';
|
||||||
|
|
||||||
|
class ShareDialog extends React.Component {
|
||||||
|
static defaultProps = {
|
||||||
|
task: null,
|
||||||
|
taskInfo: null,
|
||||||
|
title: "Share To OpenAerialMap",
|
||||||
|
saveLabel: "Share",
|
||||||
|
savingLabel: "Sharing...",
|
||||||
|
saveIcon: "fa fa-share",
|
||||||
|
show: false
|
||||||
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
task: PropTypes.object.isRequired,
|
||||||
|
taskInfo: PropTypes.object.isRequired,
|
||||||
|
saveAction: PropTypes.func.isRequired,
|
||||||
|
title: PropTypes.string,
|
||||||
|
saveLabel: PropTypes.string,
|
||||||
|
savingLabel: PropTypes.string,
|
||||||
|
saveIcon: PropTypes.string,
|
||||||
|
show: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = this.getInitialState(props);
|
||||||
|
|
||||||
|
this.reset = this.reset.bind(this);
|
||||||
|
this.getFormData = this.getFormData.bind(this);
|
||||||
|
this.onShow = this.onShow.bind(this);
|
||||||
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
getInitialState = (props) => {
|
||||||
|
return {
|
||||||
|
sensor: props.taskInfo.sensor,
|
||||||
|
startDate: this.toDatetimeLocal(new Date(props.taskInfo.startDate)),
|
||||||
|
endDate: this.toDatetimeLocal(new Date(props.taskInfo.endDate)),
|
||||||
|
title: props.taskInfo.title,
|
||||||
|
provider: props.taskInfo.provider,
|
||||||
|
tags: ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Credits to https://gist.github.com/WebReflection/6076a40777b65c397b2b9b97247520f0
|
||||||
|
toDatetimeLocal = (date) => {
|
||||||
|
const ten = function (i) {
|
||||||
|
return (i < 10 ? '0' : '') + i;
|
||||||
|
};
|
||||||
|
|
||||||
|
const YYYY = date.getFullYear(),
|
||||||
|
MM = ten(date.getMonth() + 1),
|
||||||
|
DD = ten(date.getDate()),
|
||||||
|
HH = ten(date.getHours()),
|
||||||
|
II = ten(date.getMinutes()),
|
||||||
|
SS = ten(date.getSeconds())
|
||||||
|
|
||||||
|
return YYYY + '-' + MM + '-' + DD + 'T' +
|
||||||
|
HH + ':' + II + ':' + SS;
|
||||||
|
};
|
||||||
|
|
||||||
|
reset(){
|
||||||
|
this.setState(this.getInitialState(this.props));
|
||||||
|
}
|
||||||
|
|
||||||
|
getFormData(){
|
||||||
|
return this.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
onShow(){
|
||||||
|
this.titleInput.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
show(){
|
||||||
|
this.dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
hide(){
|
||||||
|
this.dialog.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(field){
|
||||||
|
return (e) => {
|
||||||
|
let state = {};
|
||||||
|
state[field] = e.target.value;
|
||||||
|
this.setState(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
// startDate, endDate, tags
|
||||||
|
return (
|
||||||
|
<FormDialog {...this.props}
|
||||||
|
getFormData={this.getFormData}
|
||||||
|
reset={this.reset}
|
||||||
|
ref={(domNode) => { this.dialog = domNode; }}>
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="col-sm-3 control-label">Title</label>
|
||||||
|
<div className="col-sm-9">
|
||||||
|
<input type="text" className="form-control" ref={(domNode) => { this.titleInput = domNode; }} value={this.state.title} onChange={this.handleChange('title')} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="col-sm-3 control-label">Sensor</label>
|
||||||
|
<div className="col-sm-9">
|
||||||
|
<input type="text" className="form-control" ref={(domNode) => { this.sensorInput = domNode; }} value={this.state.sensor} onChange={this.handleChange('sensor')} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="col-sm-3 control-label">Provider</label>
|
||||||
|
<div className="col-sm-9">
|
||||||
|
<input type="text" className="form-control" ref={(domNode) => { this.providerInput = domNode; }} value={this.state.provider} onChange={this.handleChange('provider')} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="col-sm-3 control-label">Flight Start Date</label>
|
||||||
|
<div className="col-sm-9">
|
||||||
|
<input type="datetime-local" className="form-control" ref={(domNode) => { this.startDateInput = domNode; }} value={this.state.startDate} onChange={this.handleChange('startDate')} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="col-sm-3 control-label">Flight End Date</label>
|
||||||
|
<div className="col-sm-9">
|
||||||
|
<input type="datetime-local" className="form-control" ref={(domNode) => { this.endDateInput = domNode; }} value={this.state.endDate} onChange={this.handleChange('endDate')} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="col-sm-3 control-label">Tags (comma separated)</label>
|
||||||
|
<div className="col-sm-9">
|
||||||
|
<input type="text" className="form-control" ref={(domNode) => { this.tagsInput = domNode; }} value={this.state.tags} onChange={this.handleChange('tags')} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FormDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ShareDialog;
|
Ładowanie…
Reference in New Issue