Automated task hints based on GPS location of photos

pull/943/head
Piero Toffanin 2020-12-15 15:56:00 -05:00
rodzic c27d0583df
commit 04b873a0b6
15 zmienionych plików z 151 dodań i 58 usunięć

Wyświetl plik

@ -84,8 +84,7 @@ module.exports = {
"PluginsAPI": "PluginsAPI", "PluginsAPI": "PluginsAPI",
"leaflet": "leaflet", "leaflet": "leaflet",
"ReactDOM": "ReactDOM", "ReactDOM": "ReactDOM",
"React": "React", "React": "React"
"gettext": "gettext"
}, },
watchOptions: { watchOptions: {

Wyświetl plik

@ -8,6 +8,7 @@ import {
Route Route
} from 'react-router-dom'; } from 'react-router-dom';
import $ from 'jquery'; import $ from 'jquery';
import { _ } from './classes/gettext';
class Dashboard extends React.Component { class Dashboard extends React.Component {
constructor(){ constructor(){
@ -22,7 +23,7 @@ class Dashboard extends React.Component {
} }
addNewProject(project){ addNewProject(project){
if (!project.name) return (new $.Deferred()).reject("Name field is required"); if (!project.name) return (new $.Deferred()).reject(_("Name field is required"));
return $.ajax({ return $.ajax({
url: `/api/projects/`, url: `/api/projects/`,
@ -57,7 +58,7 @@ class Dashboard extends React.Component {
className="btn btn-primary btn-sm" className="btn btn-primary btn-sm"
onClick={this.handleAddProject}> onClick={this.handleAddProject}>
<i className="glyphicon glyphicon-plus"></i> <i className="glyphicon glyphicon-plus"></i>
Add Project {_("Add Project")}
</button> </button>
</div> </div>
@ -92,7 +93,7 @@ $(function(){
// Do nothing // Do nothing
} }
}); });
return found ? "Your changes will be lost. Are you sure you want to leave?" : undefined; return found ? _("Your changes will be lost. Are you sure you want to leave?") : undefined;
}; };
}); });

Wyświetl plik

@ -23,7 +23,6 @@ if (!window.PluginsAPI){
'leaflet': { loader: 'globals-loader', exports: 'L' }, 'leaflet': { loader: 'globals-loader', exports: 'L' },
'ReactDOM': { loader: 'globals-loader', exports: 'ReactDOM' }, 'ReactDOM': { loader: 'globals-loader', exports: 'ReactDOM' },
'React': { loader: 'globals-loader', exports: 'React' }, 'React': { loader: 'globals-loader', exports: 'React' },
'gettext': { loader: 'globals-loader', exports: 'gettext' },
'SystemJS': { loader: 'globals-loader', exports: 'SystemJS' } 'SystemJS': { loader: 'globals-loader', exports: 'SystemJS' }
} }
}); });

Wyświetl plik

@ -1,18 +1,17 @@
import React from 'react'; import React from 'react';
import ErrorMessage from './ErrorMessage';
import FormDialog from './FormDialog'; import FormDialog from './FormDialog';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import $ from 'jquery'; import { _ } from '../classes/gettext';
class EditProjectDialog extends React.Component { class EditProjectDialog extends React.Component {
static defaultProps = { static defaultProps = {
projectName: "", projectName: "",
projectDescr: "", projectDescr: "",
title: "New Project", title: _("New Project"),
saveLabel: "Create Project", saveLabel: _("Create Project"),
savingLabel: "Creating project...", savingLabel: _("Creating project..."),
saveIcon: "glyphicon glyphicon-plus", saveIcon: "glyphicon glyphicon-plus",
deleteWarning: "All tasks, images and models associated with this project will be permanently deleted. Are you sure you want to continue?", deleteWarning: _("All tasks, images and models associated with this project will be permanently deleted. Are you sure you want to continue?"),
show: false show: false
}; };
@ -83,13 +82,13 @@ class EditProjectDialog extends React.Component {
onShow={this.onShow} onShow={this.onShow}
ref={(domNode) => { this.dialog = domNode; }}> ref={(domNode) => { this.dialog = domNode; }}>
<div className="form-group"> <div className="form-group">
<label className="col-sm-2 control-label">Name</label> <label className="col-sm-2 control-label">{_("Name")}</label>
<div className="col-sm-10"> <div className="col-sm-10">
<input type="text" className="form-control" ref={(domNode) => { this.nameInput = domNode; }} value={this.state.name} onChange={this.handleChange('name')} /> <input type="text" className="form-control" ref={(domNode) => { this.nameInput = domNode; }} value={this.state.name} onChange={this.handleChange('name')} />
</div> </div>
</div> </div>
<div className="form-group"> <div className="form-group">
<label className="col-sm-2 control-label">Description (optional)</label> <label className="col-sm-2 control-label">{_("Description (optional)")}</label>
<div className="col-sm-10"> <div className="col-sm-10">
<textarea className="form-control" rows="3" value={this.state.descr} onChange={this.handleChange('descr')} /> <textarea className="form-control" rows="3" value={this.state.descr} onChange={this.handleChange('descr')} />
</div> </div>

Wyświetl plik

@ -25,20 +25,18 @@ class EditTaskForm extends React.Component {
onFormChanged: PropTypes.func, onFormChanged: PropTypes.func,
inReview: PropTypes.bool, inReview: PropTypes.bool,
task: PropTypes.object, task: PropTypes.object,
suggestedTaskName: PropTypes.string, suggestedTaskName: PropTypes.oneOfType([PropTypes.string, PropTypes.func])
}; };
constructor(props){ constructor(props){
super(props); super(props);
this.namePlaceholder = "Task of " + (new Date()).toISOString();
this.state = { this.state = {
error: "", error: "",
presetError: "", presetError: "",
presetActionPerforming: false, presetActionPerforming: false,
namePlaceholder: typeof props.suggestedTaskName === "string" ? props.suggestedTaskName : (props.task !== null ? (props.task.name || "") : "Task of " + (new Date()).toISOString()),
name: props.suggestedTaskName ? props.suggestedTaskName : (props.task !== null ? (props.task.name || "") : ""), name: typeof props.suggestedTaskName === "string" ? props.suggestedTaskName : (props.task !== null ? (props.task.name || "") : ""),
loadedProcessingNodes: false, loadedProcessingNodes: false,
loadedPresets: false, loadedPresets: false,
@ -47,7 +45,9 @@ class EditTaskForm extends React.Component {
selectedPreset: null, selectedPreset: null,
presets: [], presets: [],
editingPreset: false editingPreset: false,
loadingTaskName: false
}; };
this.handleNameChange = this.handleNameChange.bind(this); this.handleNameChange = this.handleNameChange.bind(this);
@ -272,6 +272,23 @@ class EditTaskForm extends React.Component {
}); });
} }
loadSuggestedName = () => {
if (typeof this.props.suggestedTaskName === "function"){
this.setState({loadingTaskName: true});
this.props.suggestedTaskName().then(name => {
if (this.state.loadingTaskName){
this.setState({loadingTaskName: false, name});
}else{
// User started typing its own name
}
}).catch(e => {
// Do Nothing
this.setState({loadingTaskName: false});
})
}
}
handleSelectPreset(e){ handleSelectPreset(e){
this.selectPresetById(e.target.value); this.selectPresetById(e.target.value);
} }
@ -284,6 +301,7 @@ class EditTaskForm extends React.Component {
componentDidMount(){ componentDidMount(){
this.loadProcessingNodes(); this.loadProcessingNodes();
this.loadPresets(); this.loadPresets();
this.loadSuggestedName();
} }
componentDidUpdate(prevProps, prevState){ componentDidUpdate(prevProps, prevState){
@ -304,7 +322,7 @@ class EditTaskForm extends React.Component {
} }
handleNameChange(e){ handleNameChange(e){
this.setState({name: e.target.value}); this.setState({name: e.target.value, loadingTaskName: false});
} }
selectNodeByKey(key){ selectNodeByKey(key){
@ -579,10 +597,13 @@ class EditTaskForm extends React.Component {
<div className="form-group"> <div className="form-group">
<label className="col-sm-2 control-label">Name</label> <label className="col-sm-2 control-label">Name</label>
<div className="col-sm-10"> <div className="col-sm-10">
{this.state.loadingTaskName ?
<i className="fa fa-circle-notch fa-spin fa-fw name-loading"></i>
: ""}
<input type="text" <input type="text"
onChange={this.handleNameChange} onChange={this.handleNameChange}
className="form-control" className="form-control"
placeholder={this.namePlaceholder} placeholder={this.state.namePlaceholder}
value={this.state.name} value={this.state.name}
/> />
</div> </div>

Wyświetl plik

@ -4,6 +4,7 @@ import ErrorMessage from './ErrorMessage';
import EditTaskForm from './EditTaskForm'; import EditTaskForm from './EditTaskForm';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import $ from 'jquery'; import $ from 'jquery';
import { _ } from '../classes/gettext';
class EditTaskPanel extends React.Component { class EditTaskPanel extends React.Component {
static defaultProps = { static defaultProps = {
@ -52,7 +53,7 @@ class EditTaskPanel extends React.Component {
this.setState({saving: false}); this.setState({saving: false});
this.props.onSave(json); this.props.onSave(json);
}).fail(() => { }).fail(() => {
this.setState({saving: false, error: "Could not update task information. Plese try again."}); this.setState({saving: false, error: _("Could not update task information. Plese try again.")});
}); });
} }
@ -71,14 +72,14 @@ class EditTaskPanel extends React.Component {
task={this.props.task} task={this.props.task}
/> />
<div className="actions"> <div className="actions">
<button type="button" className="btn btn-sm btn-default" onClick={this.handleCancel} disabled={this.state.saving}>Cancel</button> <button type="button" className="btn btn-sm btn-default" onClick={this.handleCancel} disabled={this.state.saving}>{_("Cancel")}</button>
<button type="button" className="btn btn-sm btn-primary save" onClick={this.handleSave} disabled={this.state.saving || !this.state.editTaskFormLoaded}> <button type="button" className="btn btn-sm btn-primary save" onClick={this.handleSave} disabled={this.state.saving || !this.state.editTaskFormLoaded}>
{this.state.saving ? {this.state.saving ?
<span> <span>
<i className="fa fa-circle-notch fa-spin"></i> Saving... <i className="fa fa-circle-notch fa-spin"></i> {_("Saving...")}
</span> </span>
: <span> : <span>
<i className="fa fa-edit"></i> Save <i className="fa fa-edit"></i> {_("Save")}
</span>} </span>}
</button> </button>
</div> </div>

Wyświetl plik

@ -3,14 +3,15 @@ import ErrorMessage from './ErrorMessage';
import '../css/FormDialog.scss'; import '../css/FormDialog.scss';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import $ from 'jquery'; import $ from 'jquery';
import { _ } from '../classes/gettext';
class FormDialog extends React.Component { class FormDialog extends React.Component {
static defaultProps = { static defaultProps = {
title: "Title", title: _("Title"),
saveLabel: "Save", saveLabel: _("Save"),
savingLabel: "Saving...", savingLabel: _("Saving..."),
saveIcon: "glyphicon glyphicon-plus", saveIcon: "glyphicon glyphicon-plus",
deleteWarning: "Are you sure?", deleteWarning: _("Are you sure?"),
show: false show: false
}; };
@ -105,7 +106,7 @@ class FormDialog extends React.Component {
if (this.props.getFormData) formData = this.props.getFormData(); if (this.props.getFormData) formData = this.props.getFormData();
this.props.saveAction(formData).fail(e => { this.props.saveAction(formData).fail(e => {
this.setState({error: e.message || (e.responseJSON || {}).detail || e.responseText || "Could not apply changes"}); this.setState({error: e.message || (e.responseJSON || {}).detail || e.responseText || _("Could not apply changes")});
}).always(() => { }).always(() => {
this.setState({saving: false}); this.setState({saving: false});
}).done(() => { }).done(() => {
@ -119,7 +120,7 @@ class FormDialog extends React.Component {
this.setState({deleting: true}); this.setState({deleting: true});
this.props.deleteAction() this.props.deleteAction()
.fail(e => { .fail(e => {
if (this._mounted) this.setState({error: e.message || (e.responseJSON || {}).detail || e.responseText || "Could not delete item"}); if (this._mounted) this.setState({error: e.message || (e.responseJSON || {}).detail || e.responseText || _("Could not delete item")});
}).always(() => { }).always(() => {
if (this._mounted) this.setState({deleting: false}); if (this._mounted) this.setState({deleting: false});
}); });
@ -147,7 +148,7 @@ class FormDialog extends React.Component {
</div> </div>
<div className="modal-footer"> <div className="modal-footer">
<div className="pull-right"> <div className="pull-right">
<button type="button" className="btn btn-default" onClick={this.hide} disabled={this.state.saving}>Cancel</button> <button type="button" className="btn btn-default" onClick={this.hide} disabled={this.state.saving}>{_("Cancel")}</button>
<button type="button" className="btn btn-primary save" onClick={this.handleSave} disabled={this.state.saving}> <button type="button" className="btn btn-primary save" onClick={this.handleSave} disabled={this.state.saving}>
{this.state.saving ? {this.state.saving ?
<span> <span>
@ -166,10 +167,10 @@ class FormDialog extends React.Component {
onClick={this.handleDelete}> onClick={this.handleDelete}>
{this.state.deleting ? {this.state.deleting ?
<span> <span>
<i className="fa fa-circle-notch fa-spin"></i> Deleting... <i className="fa fa-circle-notch fa-spin"></i> {_("Deleting...")}
</span> </span>
: <span> : <span>
<i className="glyphicon glyphicon-trash"></i> Delete <i className="glyphicon glyphicon-trash"></i> {_("Delete")}
</span>} </span>}
</button> </button>
</div> </div>

Wyświetl plik

@ -19,7 +19,7 @@ class NewTaskPanel extends React.Component {
filesCount: PropTypes.number, filesCount: PropTypes.number,
showResize: PropTypes.bool, showResize: PropTypes.bool,
getFiles: PropTypes.func, getFiles: PropTypes.func,
suggestedTaskName: PropTypes.string, suggestedTaskName: PropTypes.oneOfType([PropTypes.string, PropTypes.func])
}; };
constructor(props){ constructor(props){

Wyświetl plik

@ -6,7 +6,7 @@ import ProjectListItem from './ProjectListItem';
import Paginated from './Paginated'; import Paginated from './Paginated';
import Paginator from './Paginator'; import Paginator from './Paginator';
import ErrorMessage from './ErrorMessage'; import ErrorMessage from './ErrorMessage';
import { Route } from 'react-router-dom'; import { _, interpolate } from '../classes/gettext';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
class ProjectList extends Paginated { class ProjectList extends Paginated {
@ -53,14 +53,14 @@ class ProjectList extends Paginated {
this.updatePagination(this.PROJECTS_PER_PAGE, json.count); this.updatePagination(this.PROJECTS_PER_PAGE, json.count);
}else{ }else{
this.setState({ this.setState({
error: `Invalid JSON response: ${JSON.stringify(json)}`, error: interpolate(_("Invalid JSON response: %(error)s"), {error: JSON.stringify(json)}),
loading: false loading: false
}); });
} }
}) })
.fail((jqXHR, textStatus, errorThrown) => { .fail((jqXHR, textStatus, errorThrown) => {
this.setState({ this.setState({
error: `Could not load projects list: ${textStatus}`, error: interpolate(_("Could not load projects list: %(error)s"), {error: textStatus}),
loading: false loading: false
}); });
}) })

Wyświetl plik

@ -12,6 +12,8 @@ import csrf from '../django/csrf';
import HistoryNav from '../classes/HistoryNav'; import HistoryNav from '../classes/HistoryNav';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ResizeModes from '../classes/ResizeModes'; import ResizeModes from '../classes/ResizeModes';
import exifr from 'exifr/dist/mini.esm';
import { _, interpolate } from '../classes/gettext';
import $ from 'jquery'; import $ from 'jquery';
class ProjectListItem extends React.Component { class ProjectListItem extends React.Component {
@ -180,7 +182,7 @@ class ProjectListItem extends React.Component {
file.retries++; file.retries++;
this.dz.processQueue(); this.dz.processQueue();
}else{ }else{
throw new Error(`Cannot upload ${file.name}, exceeded max retries (${MAX_RETRIES})`); throw new Error(interpolate(_('Cannot upload %(filename)s, exceeded max retries (%(max_retries)s)'), {filename: file.name, max_retries: MAX_RETRIES}));
} }
}; };
@ -229,17 +231,17 @@ class ProjectListItem extends React.Component {
if (task && task.id){ if (task && task.id){
this.newTaskAdded(); this.newTaskAdded();
}else{ }else{
this.setUploadState({error: `Cannot create new task. Invalid response from server: ${JSON.stringify(task)}`}); this.setUploadState({error: interpolate(_('Cannot create new task. Invalid response from server: %(error)s'), { error: JSON.stringify(task) }) });
} }
}).fail(() => { }).fail(() => {
this.setUploadState({error: "Cannot create new task. Please try again later."}); this.setUploadState({error: _("Cannot create new task. Please try again later.")});
}); });
}else if (this.dz.getQueuedFiles() === 0){ }else if (this.dz.getQueuedFiles() === 0){
// Done but didn't upload all? // Done but didn't upload all?
this.setUploadState({ this.setUploadState({
totalCount: this.state.upload.totalCount - remainingFilesCount, totalCount: this.state.upload.totalCount - remainingFilesCount,
uploading: false, uploading: false,
error: `${remainingFilesCount} files cannot be uploaded. As a reminder, only images (.jpg, .tif, .png) and GCP files (.txt) can be uploaded. Try again.` 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 })
}); });
} }
}) })
@ -341,11 +343,11 @@ class ProjectListItem extends React.Component {
this.dz.options.url = `/api/projects/${this.state.data.id}/tasks/${task.id}/upload/`; this.dz.options.url = `/api/projects/${this.state.data.id}/tasks/${task.id}/upload/`;
this.dz.processQueue(); this.dz.processQueue();
}else{ }else{
this.setState({error: `Cannot create new task. Invalid response from server: ${JSON.stringify(task)}`}); this.setState({error: interpolate(_('Cannot create new task. Invalid response from server: %(error)s'), { error: JSON.stringify(task) }) });
this.handleTaskCanceled(); this.handleTaskCanceled();
} }
}).fail(() => { }).fail(() => {
this.setState({error: "Cannot create new task. Please try again later."}); this.setState({error: _("Cannot create new task. Please try again later.")});
this.handleTaskCanceled(); this.handleTaskCanceled();
}); });
} }
@ -393,6 +395,68 @@ class ProjectListItem extends React.Component {
this.setState({importing: false}); this.setState({importing: false});
} }
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;
d = d.replace(":", "-");
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}`);
else reject(new Error("Invalid json"));
}).fail(reject);
}).catch(reject);
}
});
}
render() { render() {
const { refreshing, data } = this.state; const { refreshing, data } = this.state;
const numTasks = data.tasks.length; const numTasks = data.tasks.length;
@ -405,9 +469,9 @@ class ProjectListItem extends React.Component {
<EditProjectDialog <EditProjectDialog
ref={(domNode) => { this.editProjectDialog = domNode; }} ref={(domNode) => { this.editProjectDialog = domNode; }}
title="Edit Project" title={_("Edit Project")}
saveLabel="Save Changes" saveLabel={_("Save Changes")}
savingLabel="Saving changes..." savingLabel={_("Saving changes...")}
saveIcon="far fa-edit" saveIcon="far fa-edit"
projectName={data.name} projectName={data.name}
projectDescr={data.description} projectDescr={data.description}
@ -425,12 +489,12 @@ class ProjectListItem extends React.Component {
onClick={this.handleUpload} onClick={this.handleUpload}
ref={this.setRef("uploadButton")}> ref={this.setRef("uploadButton")}>
<i className="glyphicon glyphicon-upload"></i> <i className="glyphicon glyphicon-upload"></i>
Select Images and GCP {_("Select Images and GCP")}
</button> </button>
<button type="button" <button type="button"
className="btn btn-default btn-sm" className="btn btn-default btn-sm"
onClick={this.handleImportTask}> onClick={this.handleImportTask}>
<i className="glyphicon glyphicon-import"></i> Import <i className="glyphicon glyphicon-import"></i> {_("Import")}
</button> </button>
{this.state.buttons.map((button, i) => <React.Fragment key={i}>{button}</React.Fragment>)} {this.state.buttons.map((button, i) => <React.Fragment key={i}>{button}</React.Fragment>)}
</div> </div>
@ -445,7 +509,7 @@ class ProjectListItem extends React.Component {
</button> </button>
<button type="button" className="btn btn-default btn-sm" onClick={this.viewMap}> <button type="button" className="btn btn-default btn-sm" onClick={this.viewMap}>
<i className="fa fa-globe"></i> View Map <i className="fa fa-globe"></i> {_("View Map")}
</button> </button>
</div> </div>
@ -460,13 +524,13 @@ class ProjectListItem extends React.Component {
<span> <span>
<i className='fa fa-tasks'> <i className='fa fa-tasks'>
</i> <a href="javascript:void(0);" onClick={this.toggleTaskList}> </i> <a href="javascript:void(0);" onClick={this.toggleTaskList}>
{numTasks} Tasks <i className={'fa fa-caret-' + (this.state.showTaskList ? 'down' : 'right')}></i> {interpolate(_("%(count)s Tasks"), { count: numTasks})} <i className={'fa fa-caret-' + (this.state.showTaskList ? 'down' : 'right')}></i>
</a> </a>
</span> </span>
: ""} : ""}
<i className='far fa-edit'> <i className='far fa-edit'>
</i> <a href="javascript:void(0);" onClick={this.handleEditProject}> Edit </i> <a href="javascript:void(0);" onClick={this.handleEditProject}> {_("Edit")}
</a> </a>
</div> </div>
</div> </div>
@ -476,7 +540,7 @@ class ProjectListItem extends React.Component {
{this.state.upload.error !== "" ? {this.state.upload.error !== "" ?
<div className="alert alert-warning alert-dismissible"> <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> <button type="button" className="close" aria-label={_("Close")} onClick={this.closeUploadError}><span aria-hidden="true">&times;</span></button>
{this.state.upload.error} {this.state.upload.error}
</div> </div>
: ""} : ""}
@ -485,6 +549,7 @@ class ProjectListItem extends React.Component {
<NewTaskPanel <NewTaskPanel
onSave={this.handleTaskSaved} onSave={this.handleTaskSaved}
onCancel={this.handleTaskCanceled} onCancel={this.handleTaskCanceled}
suggestedTaskName={this.handleTaskTitleHint}
filesCount={this.state.upload.totalCount} filesCount={this.state.upload.totalCount}
showResize={true} showResize={true}
getFiles={() => this.state.upload.files } getFiles={() => this.state.upload.files }

Wyświetl plik

@ -19,4 +19,11 @@
margin-top: 10px; margin-top: 10px;
margin-bottom: 14px; margin-bottom: 14px;
} }
.name-loading{
position: absolute;
right: 30px;
top: 15px;
opacity: 0.5;
}
} }

Wyświetl plik

@ -37,6 +37,7 @@
"enzyme": "^3.3.0", "enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.15.1", "enzyme-adapter-react-16": "^1.15.1",
"epsg": "^0.5.0", "epsg": "^0.5.0",
"exifr": "^6.0.0",
"extract-text-webpack-plugin": "^4.0.0-beta.0", "extract-text-webpack-plugin": "^4.0.0-beta.0",
"fbemitter": "^2.1.1", "fbemitter": "^2.1.1",
"file-loader": "^0.9.0", "file-loader": "^0.9.0",

Wyświetl plik

@ -3,7 +3,7 @@ import ErrorMessage from 'webodm/components/ErrorMessage';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './Dashboard.scss'; import './Dashboard.scss';
import $ from 'jquery'; import $ from 'jquery';
import _ from 'gettext'; import { _ } from 'webodm/classes/gettext';
export default class Dashboard extends React.Component { export default class Dashboard extends React.Component {
static defaultProps = { static defaultProps = {

Wyświetl plik

@ -12,7 +12,7 @@ if [[ "$1" == "extract" ]]; then
mkdir -p locale mkdir -p locale
django-admin makemessages --keep-pot $locale_param --ignore=build --ignore=app/templates/app/admin/* --ignore=app/templates/app/registration/* django-admin makemessages --keep-pot $locale_param --ignore=build --ignore=app/templates/app/admin/* --ignore=app/templates/app/registration/*
django-admin makemessages --keep-pot $locale_param -d djangojs --extension jsx --ignore=build python manage.py makemessages_djangojs --keep-pot $locale_param -d djangojs --extension jsx --ignore=build --language Python
fi fi
if [[ "$1" == "build" ]]; then if [[ "$1" == "build" ]]; then

Wyświetl plik

@ -88,8 +88,7 @@ module.exports = {
"SystemJS": "SystemJS", "SystemJS": "SystemJS",
"THREE": "THREE", "THREE": "THREE",
"React": "React", "React": "React",
"ReactDOM": "ReactDOM", "ReactDOM": "ReactDOM"
"gettext": "gettext"
}, },
watchOptions: { watchOptions: {