kopia lustrzana https://github.com/OpenDroneMap/WebODM
Presets interface working, still needs more testing
rodzic
81871d1149
commit
4acb13829a
|
@ -1,6 +1,5 @@
|
|||
import '../css/EditPresetDialog.scss';
|
||||
import React from 'react';
|
||||
import ErrorMessage from './ErrorMessage';
|
||||
import FormDialog from './FormDialog';
|
||||
import ProcessingNodeOption from './ProcessingNodeOption';
|
||||
import PresetUtils from '../classes/PresetUtils';
|
||||
|
@ -13,6 +12,8 @@ class EditPresetDialog extends React.Component {
|
|||
static propTypes = {
|
||||
preset: React.PropTypes.object.isRequired,
|
||||
availableOptions: React.PropTypes.array.isRequired,
|
||||
saveAction: React.PropTypes.func.isRequired,
|
||||
deleteAction: React.PropTypes.func.isRequired,
|
||||
onHide: React.PropTypes.func
|
||||
};
|
||||
|
||||
|
@ -30,7 +31,7 @@ class EditPresetDialog extends React.Component {
|
|||
this.onShow = this.onShow.bind(this);
|
||||
this.setOptionRef = this.setOptionRef.bind(this);
|
||||
this.getOptions = this.getOptions.bind(this);
|
||||
this.handleSave = this.handleSave.bind(this);
|
||||
this.isCustomPreset = this.isCustomPreset.bind(this);
|
||||
}
|
||||
|
||||
setOptionRef(optionName){
|
||||
|
@ -51,11 +52,19 @@ class EditPresetDialog extends React.Component {
|
|||
}
|
||||
|
||||
getFormData(){
|
||||
return this.state; // TODO: necessary?
|
||||
return {
|
||||
id: this.props.preset.id,
|
||||
name: this.state.name,
|
||||
options: this.getOptions()
|
||||
};
|
||||
}
|
||||
|
||||
isCustomPreset(){
|
||||
return this.props.preset.id === -1;
|
||||
}
|
||||
|
||||
onShow(){
|
||||
this.nameInput.focus();
|
||||
if (!this.isCustomPreset()) this.nameInput.focus();
|
||||
}
|
||||
|
||||
handleChange(field){
|
||||
|
@ -66,10 +75,6 @@ class EditPresetDialog extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleSave(){
|
||||
|
||||
}
|
||||
|
||||
render(){
|
||||
let options = PresetUtils.getAvailableOptions(this.props.preset.options, this.props.availableOptions);
|
||||
|
||||
|
@ -82,13 +87,17 @@ class EditPresetDialog extends React.Component {
|
|||
onShow={this.onShow}
|
||||
saveIcon="fa fa-edit"
|
||||
title="Edit Options"
|
||||
saveAction={this.handleSave}>
|
||||
<div className="row preset-name">
|
||||
<label className="col-sm-2 control-label">Name</label>
|
||||
<div className="col-sm-10">
|
||||
<input type="text" className="form-control" ref={(domNode) => { this.nameInput = domNode; }} value={this.state.name} onChange={this.handleChange('name')} />
|
||||
saveAction={this.props.saveAction}
|
||||
deleteWarning={false}
|
||||
deleteAction={(this.props.preset.id !== -1 && !this.props.preset.system) ? this.props.deleteAction : undefined}>
|
||||
{!this.isCustomPreset() ?
|
||||
<div className="row preset-name">
|
||||
<label className="col-sm-2 control-label">Name</label>
|
||||
<div className="col-sm-10">
|
||||
<input type="text" className="form-control" ref={(domNode) => { this.nameInput = domNode; }} value={this.state.name} onChange={this.handleChange('name')} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
: ""}
|
||||
<div className="row">
|
||||
<label className="col-sm-2 control-label">Options</label>
|
||||
<div className="col-sm-10">
|
||||
|
|
|
@ -3,6 +3,8 @@ import React from 'react';
|
|||
import values from 'object.values';
|
||||
import Utils from '../classes/Utils';
|
||||
import EditPresetDialog from './EditPresetDialog';
|
||||
import ErrorMessage from './ErrorMessage';
|
||||
import $ from 'jquery';
|
||||
|
||||
if (!Object.values) {
|
||||
values.shim();
|
||||
|
@ -30,6 +32,9 @@ class EditTaskForm extends React.Component {
|
|||
|
||||
this.state = {
|
||||
error: "",
|
||||
presetError: "",
|
||||
presetActionPerforming: false,
|
||||
|
||||
name: props.task !== null ? (props.task.name || "") : "",
|
||||
loadedProcessingNodes: false,
|
||||
loadedPresets: false,
|
||||
|
@ -54,6 +59,11 @@ class EditTaskForm extends React.Component {
|
|||
this.selectPresetById = this.selectPresetById.bind(this);
|
||||
this.handleEditPreset = this.handleEditPreset.bind(this);
|
||||
this.handleCancelEditPreset = this.handleCancelEditPreset.bind(this);
|
||||
this.handlePresetSave = this.handlePresetSave.bind(this);
|
||||
this.handleDuplicateSavePreset = this.handleDuplicateSavePreset.bind(this);
|
||||
this.handleDeletePreset = this.handleDeletePreset.bind(this);
|
||||
this.findFirstPresetMatching = this.findFirstPresetMatching.bind(this);
|
||||
this.getAvailableOptionsOnly = this.getAvailableOptionsOnly.bind(this);
|
||||
}
|
||||
|
||||
notifyFormLoaded(){
|
||||
|
@ -165,6 +175,33 @@ class EditTaskForm extends React.Component {
|
|||
this.loadPresets();
|
||||
}
|
||||
|
||||
findFirstPresetMatching(presets, options){
|
||||
for (let i = 0; i < presets.length; i++){
|
||||
const preset = presets[i];
|
||||
|
||||
if (options.length === preset.options.length){
|
||||
let dict = {};
|
||||
options.forEach(opt => {
|
||||
dict[opt.name] = opt.value;
|
||||
});
|
||||
|
||||
let matchingOptions = 0;
|
||||
for (let j = 0; j < preset.options.length; j++){
|
||||
if (dict[preset.options[j].name] !== preset.options[j].value){
|
||||
break;
|
||||
}else{
|
||||
matchingOptions++;
|
||||
}
|
||||
}
|
||||
|
||||
// If we terminated the loop above, all options match
|
||||
if (matchingOptions === options.length) return preset;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
loadPresets(){
|
||||
function failed(){
|
||||
// Try again
|
||||
|
@ -172,19 +209,33 @@ class EditTaskForm extends React.Component {
|
|||
}
|
||||
|
||||
this.presetsRequest =
|
||||
$.getJSON("/api/presets/", presets => {
|
||||
$.getJSON("/api/presets/?ordering=-created_at", presets => {
|
||||
if (Array.isArray(presets)){
|
||||
// In case somebody decides to remove all presets...
|
||||
if (presets.length === 0){
|
||||
this.setState({error: "There are no presets. Please create a system preset from the Administration -- Presets page, then try again."});
|
||||
return;
|
||||
}
|
||||
// Add custom preset
|
||||
const customPreset = {
|
||||
id: -1,
|
||||
name: "(Custom)",
|
||||
options: [],
|
||||
system: true
|
||||
};
|
||||
presets.push(customPreset);
|
||||
|
||||
// Choose preset
|
||||
let selectedPreset = presets[0],
|
||||
defaultPreset = presets.find(p => p.name === "Default"); // Do not translate Default
|
||||
if (defaultPreset) selectedPreset = defaultPreset;
|
||||
// TODO: look at task options
|
||||
|
||||
// If task's options are set attempt
|
||||
// to find a preset that matches the current task options
|
||||
if (this.props.task && Array.isArray(this.props.task.options) && this.props.task.options.length > 0){
|
||||
const taskPreset = this.findFirstPresetMatching(presets, this.props.task.options);
|
||||
if (taskPreset !== null){
|
||||
selectedPreset = taskPreset;
|
||||
}else{
|
||||
customPreset.options = Utils.clone(this.props.task.options);
|
||||
selectedPreset = customPreset;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
loadedPresets: true,
|
||||
|
@ -237,23 +288,148 @@ class EditTaskForm extends React.Component {
|
|||
this.selectNodeByKey(e.target.value);
|
||||
}
|
||||
|
||||
// Filter a list of options based on the ones that
|
||||
// are available (usually options are from a preset and availableOptions
|
||||
// from a processing node)
|
||||
getAvailableOptionsOnly(options, availableOptions){
|
||||
const optionNames = {};
|
||||
|
||||
availableOptions.forEach(opt => optionNames[opt.name] = true);
|
||||
return options.filter(opt => optionNames[opt.name]);
|
||||
}
|
||||
|
||||
getTaskInfo(){
|
||||
const { name, selectedNode, selectedPreset } = this.state;
|
||||
|
||||
return {
|
||||
name: this.state.name !== "" ? this.state.name : this.namePlaceholder,
|
||||
selectedNode: this.state.selectedNode,
|
||||
options: this.state.selectedPreset.options
|
||||
name: name !== "" ? name : this.namePlaceholder,
|
||||
selectedNode: selectedNode,
|
||||
options: this.getAvailableOptionsOnly(selectedPreset.options, selectedNode.options)
|
||||
};
|
||||
}
|
||||
|
||||
handleEditPreset(){
|
||||
// If the user tries to edit a system preset
|
||||
// set the "Custom..." options to it
|
||||
const { selectedPreset, presets } = this.state;
|
||||
|
||||
if (selectedPreset.system){
|
||||
let customPreset = presets.find(p => p.id === -1);
|
||||
// Might have been deleted
|
||||
if (!customPreset){
|
||||
customPreset = {
|
||||
id: -1,
|
||||
name: "(Custom)",
|
||||
options: [],
|
||||
system: true
|
||||
};
|
||||
presets.push(customPreset);
|
||||
this.setState({presets});
|
||||
}
|
||||
customPreset.options = Utils.clone(selectedPreset.options);
|
||||
this.setState({selectedPreset: customPreset});
|
||||
}
|
||||
|
||||
this.setState({editingPreset: true});
|
||||
// this.editPresetDialog.show();
|
||||
}
|
||||
|
||||
handleCancelEditPreset(){
|
||||
this.setState({editingPreset: false});
|
||||
}
|
||||
|
||||
handlePresetSave(preset){
|
||||
const done = () => {
|
||||
// Update presets and selected preset
|
||||
let p = this.state.presets.find(p => p.id === preset.id);
|
||||
p.name = preset.name;
|
||||
p.options = preset.options;
|
||||
|
||||
this.setState({selectedPreset: p});
|
||||
};
|
||||
|
||||
// If it's a custom preset do not update server-side
|
||||
if (preset.id === -1){
|
||||
done();
|
||||
return $.Deferred().resolve();
|
||||
}else{
|
||||
return $.ajax({
|
||||
url: `/api/presets/${preset.id}/`,
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({
|
||||
name: preset.name,
|
||||
options: preset.options
|
||||
}),
|
||||
dataType: 'json',
|
||||
type: 'PATCH'
|
||||
}).done(done);
|
||||
}
|
||||
}
|
||||
|
||||
handleDuplicateSavePreset(){
|
||||
// Create a new preset with the same settings as the
|
||||
// currently selected preset
|
||||
const { selectedPreset, presets } = this.state;
|
||||
this.setState({presetActionPerforming: true});
|
||||
|
||||
const isCustom = selectedPreset.id === -1,
|
||||
name = isCustom ? "My Preset" : "Copy of " + selectedPreset.name;
|
||||
|
||||
$.ajax({
|
||||
url: `/api/presets/`,
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({
|
||||
name: name,
|
||||
options: selectedPreset.options
|
||||
}),
|
||||
dataType: 'json',
|
||||
type: 'POST'
|
||||
}).done(preset => {
|
||||
// If the original preset was a custom one,
|
||||
// we remove it from the list (since we just saved it)
|
||||
if (isCustom){
|
||||
presets.splice(presets.indexOf(selectedPreset), 1);
|
||||
}
|
||||
|
||||
// Add new preset to list, select it, then edit
|
||||
presets.push(preset);
|
||||
this.setState({presets, selectedPreset: preset});
|
||||
this.handleEditPreset();
|
||||
}).fail(() => {
|
||||
this.setState({presetError: "Could not duplicate the preset. Please try to refresh the page."});
|
||||
}).always(() => {
|
||||
this.setState({presetActionPerforming: false});
|
||||
});
|
||||
}
|
||||
|
||||
handleDeletePreset(){
|
||||
const { selectedPreset, presets } = this.state;
|
||||
if (selectedPreset.system){
|
||||
this.setState({presetError: "System presets can only be removed by a staff member from the Administration panel."});
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.confirm(`Are you sure you want to delete "${selectedPreset.name}"?`)){
|
||||
this.setState({presetActionPerforming: true});
|
||||
|
||||
return $.ajax({
|
||||
url: `/api/presets/${selectedPreset.id}/`,
|
||||
contentType: 'application/json',
|
||||
type: 'DELETE'
|
||||
}).done(() => {
|
||||
presets.splice(presets.indexOf(selectedPreset), 1);
|
||||
|
||||
// Select first by default
|
||||
this.setState({presets, selectedPreset: presets[0], editingPreset: false});
|
||||
}).fail(() => {
|
||||
this.setState({presetError: "Could not delete the preset. Please try to refresh the page."});
|
||||
}).always(() => {
|
||||
this.setState({presetActionPerforming: false});
|
||||
});
|
||||
}else{
|
||||
return $.Deferred().resolve();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.error){
|
||||
return (<div className="edit-task-panel">
|
||||
|
@ -291,38 +467,48 @@ class EditTaskForm extends React.Component {
|
|||
{this.state.presets.map(preset =>
|
||||
<option value={preset.id} key={preset.id}>{preset.name}</option>
|
||||
)}
|
||||
</select>
|
||||
<div className="btn-group presets-dropdown">
|
||||
<button type="button" className="btn btn-default" onClick={this.handleEditPreset}>
|
||||
<i className="fa fa-sliders"></i>
|
||||
</button>
|
||||
<button type="button" className="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span className="caret"></span>
|
||||
</button>
|
||||
<ul className="dropdown-menu">
|
||||
<li>
|
||||
<a href="javascript:void(0);" onClick={this.handleEditPreset}><i className="fa fa-sliders"></i> Edit</a>
|
||||
</li>
|
||||
<li className="divider"></li>
|
||||
<li>
|
||||
<a href="javascript:void(0);" onClick={this.handleDuplicatePreset}><i className="fa fa-copy"></i> Duplicate</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:void(0);" onClick={this.handleDeletePreset}><i className="fa fa-trash-o"></i> Delete</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</select>
|
||||
|
||||
{!this.state.presetActionPerforming ?
|
||||
<div className="btn-group presets-dropdown">
|
||||
<button type="button" className="btn btn-default" onClick={this.handleEditPreset}>
|
||||
<i className="fa fa-sliders"></i>
|
||||
</button>
|
||||
<button type="button" className="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span className="caret"></span>
|
||||
</button>
|
||||
<ul className="dropdown-menu">
|
||||
<li>
|
||||
<a href="javascript:void(0);" onClick={this.handleEditPreset}><i className="fa fa-sliders"></i> Edit</a>
|
||||
</li>
|
||||
<li className="divider"></li>
|
||||
|
||||
{this.state.selectedPreset.id !== -1 ?
|
||||
<li>
|
||||
<a href="javascript:void(0);" onClick={this.handleDuplicateSavePreset}><i className="fa fa-copy"></i> Duplicate</a>
|
||||
</li>
|
||||
:
|
||||
<li>
|
||||
<a href="javascript:void(0);" onClick={this.handleDuplicateSavePreset}><i className="fa fa-save"></i> Save</a>
|
||||
</li>
|
||||
}
|
||||
<li className={this.state.selectedPreset.system ? "disabled" : ""}>
|
||||
<a href="javascript:void(0);" onClick={this.handleDeletePreset}><i className="fa fa-trash-o"></i> Delete</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
: <i className="preset-performing-action-icon fa fa-cog fa-spin fa-fw"></i>}
|
||||
<ErrorMessage className="preset-error" bind={[this, 'presetError']} />
|
||||
</div>
|
||||
</div>
|
||||
PRESET:
|
||||
{JSON.stringify(this.state.selectedPreset.options)}<br/>
|
||||
|
||||
|
||||
{this.state.editingPreset ?
|
||||
<EditPresetDialog
|
||||
preset={this.state.selectedPreset}
|
||||
availableOptions={this.state.selectedNode.options}
|
||||
onHide={this.handleCancelEditPreset}
|
||||
saveAction={this.handlePresetSave}
|
||||
deleteAction={this.handleDeletePreset}
|
||||
ref={(domNode) => { if (domNode) this.editPresetDialog = domNode; }}
|
||||
/>
|
||||
: ""}
|
||||
|
|
|
@ -23,7 +23,7 @@ class ErrorMessage extends React.Component {
|
|||
|
||||
if (parent.state[prop]){
|
||||
return (
|
||||
<div className="alert alert-warning alert-dismissible">
|
||||
<div className={"alert alert-warning alert-dismissible " + (this.props.className ? this.props.className : "")}>
|
||||
<button type="button" className="close" aria-label="Close" onClick={this.close}><span aria-hidden="true">×</span></button>
|
||||
{parent.state[prop]}
|
||||
</div>
|
||||
|
|
|
@ -24,7 +24,10 @@ class FormDialog extends React.Component {
|
|||
saveLabel: React.PropTypes.string,
|
||||
savingLabel: React.PropTypes.string,
|
||||
saveIcon: React.PropTypes.string,
|
||||
deleteWarning: React.PropTypes.string,
|
||||
deleteWarning: React.PropTypes.oneOfType([
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.bool
|
||||
]),
|
||||
show: React.PropTypes.bool
|
||||
};
|
||||
|
||||
|
@ -50,6 +53,8 @@ class FormDialog extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount(){
|
||||
this._mounted = true;
|
||||
|
||||
$(this.modal)
|
||||
// Ensure state is kept up to date when
|
||||
// the user presses the escape key
|
||||
|
@ -66,6 +71,8 @@ class FormDialog extends React.Component {
|
|||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
this._mounted = false;
|
||||
|
||||
$(this.modal).off('hidden.bs.modal hidden.bs.modal')
|
||||
.modal('hide');
|
||||
}
|
||||
|
@ -107,11 +114,13 @@ class FormDialog extends React.Component {
|
|||
|
||||
handleDelete(){
|
||||
if (this.props.deleteAction){
|
||||
if (window.confirm(this.props.deleteWarning)){
|
||||
if (this.props.deleteWarning === false || window.confirm(this.props.deleteWarning)){
|
||||
this.setState({deleting: true});
|
||||
this.props.deleteAction()
|
||||
.fail(e => {
|
||||
this.setState({error: e.message || (e.responseJSON || {}).detail || e.responseText || "Could not delete item", deleting: false});
|
||||
if (this._mounted) this.setState({error: e.message || (e.responseJSON || {}).detail || e.responseText || "Could not delete item"});
|
||||
}).always(() => {
|
||||
if (this._mounted) this.setState({deleting: false});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,4 +2,12 @@
|
|||
.presets-dropdown{
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.preset-performing-action-icon{
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.preset-error{
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
Ładowanie…
Reference in New Issue