Added underlying modulation to bookmarks, refactored bookmarks.
This commit is contained in:
parent
309274a6af
commit
ce9d93d4f3
|
|
@ -82,7 +82,8 @@ h1 {
|
||||||
|
|
||||||
/* col-1 */
|
/* col-1 */
|
||||||
.bookmarks table [data-editor="name"] {
|
.bookmarks table [data-editor="name"] {
|
||||||
width: 35%;
|
white-space: nowrap;
|
||||||
|
width: 29%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* col-2 */
|
/* col-2 */
|
||||||
|
|
@ -101,15 +102,28 @@ h1 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* col-4 */
|
/* col-4 */
|
||||||
.bookmarks table [data-editor="description"] {
|
.bookmarks table [data-editor="underlying"] {
|
||||||
width: 35%;
|
white-space: nowrap;
|
||||||
|
min-width: 160px;
|
||||||
|
width: 10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* col-5 */
|
/* col-5 */
|
||||||
|
.bookmarks table [data-editor="description"] {
|
||||||
|
width: 29%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* col-6 */
|
||||||
|
.bookmarks table [data-editor="scannable"] {
|
||||||
|
width: 2%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* col-7 */
|
||||||
.bookmarks table tr td:last-child, .bookmarks table tr th:last-child {
|
.bookmarks table tr td:last-child, .bookmarks table tr th:last-child {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 10%;
|
width: 10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookmarks table input, .bookmarks table select {
|
.bookmarks table input, .bookmarks table select {
|
||||||
text-align: inherit;
|
text-align: inherit;
|
||||||
|
|
|
||||||
|
|
@ -1512,17 +1512,16 @@ img.openwebrx-mirror-img
|
||||||
}
|
}
|
||||||
|
|
||||||
.openwebrx-dialog {
|
.openwebrx-dialog {
|
||||||
background-color: #575757;
|
background-color: #575757;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
color: white;
|
color: white;
|
||||||
position: fixed;
|
font-size: 10pt;
|
||||||
font-size: 10pt;
|
border-radius: 15px;
|
||||||
border-radius: 15px;
|
-moz-border-radius: 15px;
|
||||||
-moz-border-radius: 15px;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translate(-50%, 0);
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.openwebrx-dialog .form-field {
|
.openwebrx-dialog .form-field {
|
||||||
|
|
|
||||||
|
|
@ -378,12 +378,16 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field">
|
<div class="form-field">
|
||||||
<label for="frequency">Frequency:</label>
|
<label for="frequency">Frequency:</label>
|
||||||
<input type="number" id="frequency" name="frequency">
|
<input type="number" id="frequency" name="frequency" min="1">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field">
|
<div class="form-field">
|
||||||
<label for="modulation">Modulation:</label>
|
<label for="modulation">Modulation:</label>
|
||||||
<select name="modulation" id="modulation"></select>
|
<select name="modulation" id="modulation"></select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label for="underlying">Underlying:</label>
|
||||||
|
<select name="underlying" id="underlying"></select>
|
||||||
|
</div>
|
||||||
<div class="form-field">
|
<div class="form-field">
|
||||||
<label for="name">Description:</label>
|
<label for="name">Description:</label>
|
||||||
<input type="text" id="description" name="description">
|
<input type="text" id="description" name="description">
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ function BookmarkBar() {
|
||||||
var me = this;
|
var me = this;
|
||||||
me.modesToScan = ['lsb', 'usb', 'cw', 'am', 'sam', 'nfm'];
|
me.modesToScan = ['lsb', 'usb', 'cw', 'am', 'sam', 'nfm'];
|
||||||
me.localBookmarks = new BookmarkLocalStorage();
|
me.localBookmarks = new BookmarkLocalStorage();
|
||||||
me.$container = $("#openwebrx-bookmarks-container");
|
me.$container = $('#openwebrx-bookmarks-container');
|
||||||
me.bookmarks = {};
|
me.bookmarks = {};
|
||||||
|
|
||||||
me.$container.on('click', '.bookmark', function(e){
|
me.$container.on('click', '.bookmark', function(e){
|
||||||
|
|
@ -11,9 +11,7 @@ function BookmarkBar() {
|
||||||
var b = $bookmark.data();
|
var b = $bookmark.data();
|
||||||
if (!b || !b.frequency || !b.modulation) return;
|
if (!b || !b.frequency || !b.modulation) return;
|
||||||
me.getDemodulator().set_offset_frequency(b.frequency - center_freq);
|
me.getDemodulator().set_offset_frequency(b.frequency - center_freq);
|
||||||
if (b.modulation) {
|
me.getDemodulatorPanel().setMode(b.modulation, b.underlying);
|
||||||
me.getDemodulatorPanel().setMode(b.modulation, b.underlying);
|
|
||||||
}
|
|
||||||
$bookmark.addClass('selected');
|
$bookmark.addClass('selected');
|
||||||
stopScanner();
|
stopScanner();
|
||||||
});
|
});
|
||||||
|
|
@ -41,7 +39,7 @@ function BookmarkBar() {
|
||||||
me.showEditDialog();
|
me.showEditDialog();
|
||||||
});
|
});
|
||||||
|
|
||||||
me.$dialog = $("#openwebrx-dialog-bookmark");
|
me.$dialog = $('#openwebrx-dialog-bookmark');
|
||||||
me.$dialog.find('.openwebrx-button[data-action=cancel]').click(function(){
|
me.$dialog.find('.openwebrx-button[data-action=cancel]').click(function(){
|
||||||
me.$dialog.hide();
|
me.$dialog.hide();
|
||||||
});
|
});
|
||||||
|
|
@ -108,25 +106,60 @@ BookmarkBar.prototype.render = function(){
|
||||||
|
|
||||||
BookmarkBar.prototype.showEditDialog = function(bookmark) {
|
BookmarkBar.prototype.showEditDialog = function(bookmark) {
|
||||||
if (!bookmark) {
|
if (!bookmark) {
|
||||||
var mode = this.getDemodulator().get_secondary_demod() || this.getDemodulator().get_modulation();
|
var mode1 = this.getDemodulator().get_secondary_demod()
|
||||||
|
var mode2 = this.getDemodulator().get_modulation();
|
||||||
|
// if no secondary demod, use the primary one
|
||||||
|
if (!mode1) { mode1 = mode2; mode2 = ''; }
|
||||||
bookmark = {
|
bookmark = {
|
||||||
name: "",
|
name: '',
|
||||||
frequency: center_freq + this.getDemodulator().get_offset_frequency(),
|
frequency: center_freq + this.getDemodulator().get_offset_frequency(),
|
||||||
modulation: mode,
|
modulation: mode1,
|
||||||
description: "",
|
underlying: mode2,
|
||||||
scannable : this.modesToScan.indexOf(mode) >= 0
|
description: '',
|
||||||
|
scannable : this.modesToScan.indexOf(mode1) >= 0
|
||||||
}
|
}
|
||||||
|
this.sanitizeBookmark(bookmark);
|
||||||
}
|
}
|
||||||
this.$dialog.bookmarkDialog().setValues(bookmark);
|
this.$dialog.bookmarkDialog().setValues(bookmark);
|
||||||
this.$dialog.show();
|
this.$dialog.show();
|
||||||
this.$dialog.find('#name').focus();
|
this.$dialog.find('#name').focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
BookmarkBar.prototype.sanitizeBookmark = function(b) {
|
||||||
|
// must have name, frequency, and modulation
|
||||||
|
if (!b.name || !b.frequency || !b.modulation)
|
||||||
|
return "Must have name, frequency, and modulation.";
|
||||||
|
|
||||||
|
// must have non-empty name
|
||||||
|
b.name = b.name.trim();
|
||||||
|
if (b.name.length <= 0) return "Must have a non-empty name.";
|
||||||
|
|
||||||
|
// must have positive frequency
|
||||||
|
b.frequency = Number(b.frequency);
|
||||||
|
if (b.frequency <= 0) return "Frequency must be positive.";
|
||||||
|
|
||||||
|
// must have valid modulation
|
||||||
|
var mode = Modes.findByModulation(b.modulation);
|
||||||
|
if (!mode) return "Must have valid modulation."
|
||||||
|
|
||||||
|
// check that underlying demodulator is valid
|
||||||
|
if (!b.underlying)
|
||||||
|
b.underlying = '';
|
||||||
|
else if (!mode.underlying)
|
||||||
|
return "Must not have underlying modulation.";
|
||||||
|
else if (mode.underlying.indexOf(b.underlying) < 0)
|
||||||
|
return "Must have valid underlying modulation.";
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
BookmarkBar.prototype.storeBookmark = function() {
|
BookmarkBar.prototype.storeBookmark = function() {
|
||||||
var me = this;
|
var me = this;
|
||||||
var bookmark = this.$dialog.bookmarkDialog().getValues();
|
var bookmark = this.$dialog.bookmarkDialog().getValues();
|
||||||
if (!bookmark) return;
|
if (!bookmark) return;
|
||||||
bookmark.frequency = Number(bookmark.frequency);
|
|
||||||
|
var error = this.sanitizeBookmark(bookmark);
|
||||||
|
if (error) { alert(error); return; }
|
||||||
|
|
||||||
var bookmarks = me.localBookmarks.getBookmarks();
|
var bookmarks = me.localBookmarks.getBookmarks();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,25 @@ $.fn.bookmarkDialog = function() {
|
||||||
var $el = this;
|
var $el = this;
|
||||||
return {
|
return {
|
||||||
setModes: function(modes) {
|
setModes: function(modes) {
|
||||||
$el.find('#modulation').html(modes.filter(function(m){
|
$el.find('#modulation').html(modes.filter(function(m) {
|
||||||
return m.isAvailable();
|
return m.isAvailable();
|
||||||
}).map(function(m) {
|
}).map(function(m) {
|
||||||
return '<option value="' + m.modulation + '">' + m.name + '</option>';
|
return '<option value="' + m.modulation + '">' + m.name + '</option>';
|
||||||
}).join(''));
|
}).join(''));
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
setUnderlying: function(modes) {
|
||||||
|
$el.find('#underlying').html('<option value="">None</option>' +
|
||||||
|
modes.filter(function(m) {
|
||||||
|
return m.isAvailable() && !m.underlying && m.type === 'analog';
|
||||||
|
}).map(function(m) {
|
||||||
|
return '<option value="' + m.modulation + '">' + m.name + '</option>';
|
||||||
|
}).join(''));
|
||||||
|
return this;
|
||||||
|
},
|
||||||
setValues: function(bookmark) {
|
setValues: function(bookmark) {
|
||||||
var $form = $el.find('form');
|
var $form = $el.find('form');
|
||||||
['name', 'frequency', 'modulation', 'description', 'scannable'].forEach(function(key){
|
['name', 'frequency', 'modulation', 'underlying', 'description', 'scannable'].forEach(function(key) {
|
||||||
var $input = $form.find('#' + key);
|
var $input = $form.find('#' + key);
|
||||||
if ($input.is(':checkbox')) {
|
if ($input.is(':checkbox')) {
|
||||||
$input.prop('checked', bookmark[key]);
|
$input.prop('checked', bookmark[key]);
|
||||||
|
|
@ -25,7 +34,7 @@ $.fn.bookmarkDialog = function() {
|
||||||
getValues: function() {
|
getValues: function() {
|
||||||
var bookmark = {};
|
var bookmark = {};
|
||||||
var valid = true;
|
var valid = true;
|
||||||
['name', 'frequency', 'modulation', 'description', 'scannable'].forEach(function(key){
|
['name', 'frequency', 'modulation', 'underlying', 'description', 'scannable'].forEach(function(key) {
|
||||||
var $input = $el.find('#' + key);
|
var $input = $el.find('#' + key);
|
||||||
valid = valid && $input[0].checkValidity();
|
valid = valid && $input[0].checkValidity();
|
||||||
bookmark[key] = $input.is(':checkbox')? $input.is(':checked') : $input.val();
|
bookmark[key] = $input.is(':checkbox')? $input.is(':checked') : $input.val();
|
||||||
|
|
@ -38,4 +47,4 @@ $.fn.bookmarkDialog = function() {
|
||||||
return bookmark;
|
return bookmark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ var Modes = {
|
||||||
setModes:function(json){
|
setModes:function(json){
|
||||||
this.modes = json.map(function(m){ return new Mode(m); });
|
this.modes = json.map(function(m){ return new Mode(m); });
|
||||||
this.updatePanels();
|
this.updatePanels();
|
||||||
$('#openwebrx-dialog-bookmark').bookmarkDialog().setModes(this.modes);
|
var bookmarkDialog = $('#openwebrx-dialog-bookmark').bookmarkDialog();
|
||||||
|
bookmarkDialog.setUnderlying(this.modes);
|
||||||
|
bookmarkDialog.setModes(this.modes);
|
||||||
},
|
},
|
||||||
getModes:function(){
|
getModes:function(){
|
||||||
return this.modes;
|
return this.modes;
|
||||||
|
|
|
||||||
|
|
@ -177,8 +177,8 @@ ModulationEditor.prototype = new Editor();
|
||||||
|
|
||||||
ModulationEditor.prototype.getInputHtml = function() {
|
ModulationEditor.prototype.getInputHtml = function() {
|
||||||
return '<select class="form-control form-control-sm">' +
|
return '<select class="form-control form-control-sm">' +
|
||||||
$.map(this.modes, function(name, modulation) {
|
$.map(this.modes, function(mode, name) {
|
||||||
return '<option value="' + modulation + '">' + name + '</option>';
|
return '<option value="' + name + '">' + mode.name + '</option>';
|
||||||
}).join('') +
|
}).join('') +
|
||||||
'</select>';
|
'</select>';
|
||||||
};
|
};
|
||||||
|
|
@ -188,6 +188,30 @@ ModulationEditor.prototype.getHtml = function() {
|
||||||
return $option.html();
|
return $option.html();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function UnderlyingEditor(table) {
|
||||||
|
Editor.call(this, table);
|
||||||
|
this.modes = table.data('modes');
|
||||||
|
}
|
||||||
|
|
||||||
|
UnderlyingEditor.prototype = new Editor();
|
||||||
|
|
||||||
|
UnderlyingEditor.prototype.getInputHtml = function() {
|
||||||
|
return '<select class="form-control form-control-sm">' +
|
||||||
|
'<option value="">None</option>' +
|
||||||
|
$.map(this.modes, function(mode, name) {
|
||||||
|
if (mode.analog && !mode.underlying.length)
|
||||||
|
return '<option value="' + name + '">' + mode.name + '</option>';
|
||||||
|
else
|
||||||
|
return '';
|
||||||
|
}).join('') +
|
||||||
|
'</select>';
|
||||||
|
};
|
||||||
|
|
||||||
|
UnderlyingEditor.prototype.getHtml = function() {
|
||||||
|
var $option = this.input.find('option:selected')
|
||||||
|
return $option? $option.html() : '';
|
||||||
|
};
|
||||||
|
|
||||||
function DescriptionEditor(table) {
|
function DescriptionEditor(table) {
|
||||||
Editor.call(this, table);
|
Editor.call(this, table);
|
||||||
}
|
}
|
||||||
|
|
@ -220,16 +244,21 @@ ScannableEditor.prototype.getHtml = function() {
|
||||||
return this.getValue()? '✓' : '';
|
return this.getValue()? '✓' : '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var renderModulation = function(m, modes) {
|
||||||
|
return !m? 'None' : m in modes? modes[m].name : m;
|
||||||
|
}
|
||||||
|
|
||||||
$.fn.bookmarktable = function() {
|
$.fn.bookmarktable = function() {
|
||||||
var editors = {
|
var editors = {
|
||||||
name: NameEditor,
|
name: NameEditor,
|
||||||
frequency: FrequencyEditor,
|
frequency: FrequencyEditor,
|
||||||
modulation: ModulationEditor,
|
modulation: ModulationEditor,
|
||||||
|
underlying: UnderlyingEditor,
|
||||||
description: DescriptionEditor,
|
description: DescriptionEditor,
|
||||||
scannable: ScannableEditor
|
scannable: ScannableEditor
|
||||||
};
|
};
|
||||||
|
|
||||||
$.each(this, function(){
|
$.each(this, function() {
|
||||||
var $table = $(this).find('table');
|
var $table = $(this).find('table');
|
||||||
|
|
||||||
$table.on('dblclick', 'td', function(e) {
|
$table.on('dblclick', 'td', function(e) {
|
||||||
|
|
@ -252,9 +281,11 @@ $.fn.bookmarktable = function() {
|
||||||
data: JSON.stringify(Object.fromEntries([[name, editor.getValue()]])),
|
data: JSON.stringify(Object.fromEntries([[name, editor.getValue()]])),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
}).done(function(){
|
}).done(function() {
|
||||||
$cell.data('value', editor.getValue());
|
$cell.data('value', editor.getValue());
|
||||||
$cell.html(editor.getHtml());
|
$cell.html(editor.getHtml());
|
||||||
|
}).fail(function() {
|
||||||
|
$cell.html(html);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -337,7 +368,10 @@ $.fn.bookmarktable = function() {
|
||||||
data: JSON.stringify([data]),
|
data: JSON.stringify([data]),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
}).done(function(data){
|
}).fail(function(data) {
|
||||||
|
// adding failed, reenable inputs
|
||||||
|
$.map(inputs, function(input, name) { input.disable(false); });
|
||||||
|
}).done(function(data) {
|
||||||
if (data.length && data.length === 1 && 'bookmark_id' in data[0]) {
|
if (data.length && data.length === 1 && 'bookmark_id' in data[0]) {
|
||||||
row.attr('data-id', data[0]['bookmark_id']);
|
row.attr('data-id', data[0]['bookmark_id']);
|
||||||
var tds = row.find('td');
|
var tds = row.find('td');
|
||||||
|
|
@ -347,16 +381,16 @@ $.fn.bookmarktable = function() {
|
||||||
td.data('value', input.getValue());
|
td.data('value', input.getValue());
|
||||||
td.html(input.getHtml());
|
td.html(input.getHtml());
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var $cell = row.find('td').last();
|
// remove inputs
|
||||||
var $group = $cell.find('.btn-group');
|
var $cell = row.find('td').last();
|
||||||
if ($group.length) {
|
var $group = $cell.find('.btn-group');
|
||||||
$group.remove;
|
if ($group.length) {
|
||||||
$cell.html('<div class="btn btn-sm btn-danger bookmark-delete">delete</div>');
|
$group.remove;
|
||||||
}
|
$cell.html('<div class="btn btn-sm btn-danger bookmark-delete">delete</div>');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$table.append(row);
|
$table.append(row);
|
||||||
|
|
@ -372,16 +406,13 @@ $.fn.bookmarktable = function() {
|
||||||
var modes = $table.data('modes');
|
var modes = $table.data('modes');
|
||||||
var $list = $('<table class="table table-sm">');
|
var $list = $('<table class="table table-sm">');
|
||||||
$list.append(bookmarks.map(function(b) {
|
$list.append(bookmarks.map(function(b) {
|
||||||
var modulation = b.modulation;
|
|
||||||
if (modulation in modes) {
|
|
||||||
modulation = modes[modulation];
|
|
||||||
}
|
|
||||||
var row = $(
|
var row = $(
|
||||||
'<tr>' +
|
'<tr>' +
|
||||||
'<td><input class="form-check-input select" type="checkbox"></td>' +
|
'<td><input class="form-check-input select" type="checkbox"> </td>' +
|
||||||
'<td>' + b.name + '</td>' +
|
'<td>' + b.name + '</td>' +
|
||||||
'<td class="frequency">' + renderFrequency(b.frequency) + '</td>' +
|
'<td class="frequency">' + renderFrequency(b.frequency) + '</td>' +
|
||||||
'<td>' + modulation + '</td>' +
|
'<td>' + renderModulation(b.modulation, modes) + '</td>' +
|
||||||
|
// '<td>' + renderModulation(b.underlying, modes) + '</td>' +
|
||||||
'</tr>'
|
'</tr>'
|
||||||
);
|
);
|
||||||
row.data('bookmark', b);
|
row.data('bookmark', b);
|
||||||
|
|
@ -407,31 +438,30 @@ $.fn.bookmarktable = function() {
|
||||||
data: JSON.stringify(selected),
|
data: JSON.stringify(selected),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
}).done(function(data){
|
}).fail(function(data) {
|
||||||
|
// import failed
|
||||||
|
$table.find('.emptytext').remove();
|
||||||
|
}).done(function(data) {
|
||||||
$table.find('.emptytext').remove();
|
$table.find('.emptytext').remove();
|
||||||
var modes = $table.data('modes');
|
var modes = $table.data('modes');
|
||||||
if (data.length && data.length == selected.length) {
|
if (data.length && data.length == selected.length) {
|
||||||
$table.append(data.map(function(obj, index) {
|
$table.append(data.map(function(obj, index) {
|
||||||
var bookmark = selected[index];
|
var b = selected[index];
|
||||||
var modulation_name = bookmark.modulation;
|
// provide reasonable defaults for missing fields
|
||||||
if (modulation_name in modes) {
|
if (!('underlying' in b)) b.underlying = '';
|
||||||
modulation_name = modes[modulation_name];
|
if (!('description' in b)) b.description = '';
|
||||||
}
|
if (!('scannable' in b)) {
|
||||||
// provide reasonable default for missing fields
|
|
||||||
if (!('description' in bookmark)) {
|
|
||||||
bookmark.description = '';
|
|
||||||
}
|
|
||||||
if (!('scannable' in bookmark)) {
|
|
||||||
var modesToScan = ['lsb', 'usb', 'cw', 'am', 'sam', 'nfm'];
|
var modesToScan = ['lsb', 'usb', 'cw', 'am', 'sam', 'nfm'];
|
||||||
bookmark.scannable = modesToScan.indexOf(bookmark.modulation) >= 0;
|
b.scannable = modesToScan.indexOf(b.modulation) >= 0;
|
||||||
}
|
}
|
||||||
return $(
|
return $(
|
||||||
'<tr data-id="' + obj.bookmark_id + '">' +
|
'<tr data-id="' + obj.bookmark_id + '">' +
|
||||||
'<td data-editor="name" data-value="' + bookmark.name + '">' + bookmark.name + '</td>' +
|
'<td data-editor="name" data-value="' + b.name + '">' + b.name + '</td>' +
|
||||||
'<td data-editor="frequency" data-value="' + bookmark.frequency + '" class="frequency">' + renderFrequency(bookmark.frequency) +'</td>' +
|
'<td data-editor="frequency" data-value="' + b.frequency + '" class="frequency">' + renderFrequency(b.frequency) +'</td>' +
|
||||||
'<td data-editor="modulation" data-value="' + bookmark.modulation + '">' + modulation_name + '</td>' +
|
'<td data-editor="modulation" data-value="' + b.modulation + '">' + renderModulation(b.modulation, modes) + '</td>' +
|
||||||
'<td data-editor="description" data-value="' + bookmark.description + '">' + bookmark.description + '</td>' +
|
'<td data-editor="underlying" data-value="' + b.underlying + '">' + renderModulation(b.underlying, modes) + '</td>' +
|
||||||
'<td data-editor="scannable" data-value="' + bookmark.scannable + '">' + (bookmark.scannable? '✓':'') + '</td>' +
|
'<td data-editor="description" data-value="' + b.description + '">' + b.description + '</td>' +
|
||||||
|
'<td data-editor="scannable" data-value="' + b.scannable + '">' + (b.scannable? '✓':'') + '</td>' +
|
||||||
'<td>' +
|
'<td>' +
|
||||||
'<button type="button" class="btn btn-sm btn-danger bookmark-delete">delete</button>' +
|
'<button type="button" class="btn btn-sm btn-danger bookmark-delete">delete</button>' +
|
||||||
'</td>' +
|
'</td>' +
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ class Bookmark(object):
|
||||||
self.name = j["name"]
|
self.name = j["name"]
|
||||||
self.frequency = j["frequency"]
|
self.frequency = j["frequency"]
|
||||||
self.modulation = j["modulation"]
|
self.modulation = j["modulation"]
|
||||||
|
self.underlying = j["underlying"] if "underlying" in j else ""
|
||||||
self.description = j["description"] if "description" in j else ""
|
self.description = j["description"] if "description" in j else ""
|
||||||
self.srcFile = srcFile
|
self.srcFile = srcFile
|
||||||
# By default, only scan modulations that make sense to scan
|
# By default, only scan modulations that make sense to scan
|
||||||
|
|
@ -33,6 +34,9 @@ class Bookmark(object):
|
||||||
def getModulation(self):
|
def getModulation(self):
|
||||||
return self.modulation
|
return self.modulation
|
||||||
|
|
||||||
|
def getUnderlying(self):
|
||||||
|
return self.underlying
|
||||||
|
|
||||||
def getDescription(self):
|
def getDescription(self):
|
||||||
return self.description
|
return self.description
|
||||||
|
|
||||||
|
|
@ -47,6 +51,7 @@ class Bookmark(object):
|
||||||
"name": self.getName(),
|
"name": self.getName(),
|
||||||
"frequency": self.getFrequency(),
|
"frequency": self.getFrequency(),
|
||||||
"modulation": self.getModulation(),
|
"modulation": self.getModulation(),
|
||||||
|
"underlying": self.getUnderlying(),
|
||||||
"description": self.getDescription(),
|
"description": self.getDescription(),
|
||||||
"scannable": self.isScannable(),
|
"scannable": self.isScannable(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from owrx.controllers.template import WebpageController
|
||||||
from owrx.controllers.admin import AuthorizationMixin
|
from owrx.controllers.admin import AuthorizationMixin
|
||||||
from owrx.controllers.settings import SettingsBreadcrumb
|
from owrx.controllers.settings import SettingsBreadcrumb
|
||||||
from owrx.bookmarks import Bookmark, Bookmarks
|
from owrx.bookmarks import Bookmark, Bookmarks
|
||||||
from owrx.modes import Modes
|
from owrx.modes import Modes, AnalogMode
|
||||||
from owrx.breadcrumb import Breadcrumb, BreadcrumbItem, BreadcrumbMixin
|
from owrx.breadcrumb import Breadcrumb, BreadcrumbItem, BreadcrumbMixin
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
|
|
@ -24,7 +24,7 @@ class BookmarksController(AuthorizationMixin, BreadcrumbMixin, WebpageController
|
||||||
def render_table(self):
|
def render_table(self):
|
||||||
bookmarks = Bookmarks.getSharedInstance().getEditableBookmarks()
|
bookmarks = Bookmarks.getSharedInstance().getEditableBookmarks()
|
||||||
emptyText = """
|
emptyText = """
|
||||||
<tr class="emptytext"><td colspan="4">
|
<tr class="emptytext"><td colspan="7">
|
||||||
No bookmarks in storage. You can add new bookmarks using the buttons below.
|
No bookmarks in storage. You can add new bookmarks using the buttons below.
|
||||||
</td></tr>
|
</td></tr>
|
||||||
"""
|
"""
|
||||||
|
|
@ -35,6 +35,7 @@ class BookmarksController(AuthorizationMixin, BreadcrumbMixin, WebpageController
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th class="frequency">Frequency</th>
|
<th class="frequency">Frequency</th>
|
||||||
<th>Modulation</th>
|
<th>Modulation</th>
|
||||||
|
<th>Underlying</th>
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
<th>Scan</th>
|
<th>Scan</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
|
|
@ -43,7 +44,11 @@ class BookmarksController(AuthorizationMixin, BreadcrumbMixin, WebpageController
|
||||||
</table>
|
</table>
|
||||||
""".format(
|
""".format(
|
||||||
bookmarks="".join(self.render_bookmark(b) for b in bookmarks) if bookmarks else emptyText,
|
bookmarks="".join(self.render_bookmark(b) for b in bookmarks) if bookmarks else emptyText,
|
||||||
modes=json.dumps({m.modulation: m.name for m in Modes.getAvailableModes()}),
|
modes=json.dumps({m.modulation: {
|
||||||
|
"name" : m.name,
|
||||||
|
"analog" : isinstance(m, AnalogMode),
|
||||||
|
"underlying" : m.underlying if hasattr(m, "underlying") else []
|
||||||
|
} for m in Modes.getAvailableModes()}),
|
||||||
)
|
)
|
||||||
|
|
||||||
def render_bookmark(self, bookmark: Bookmark):
|
def render_bookmark(self, bookmark: Bookmark):
|
||||||
|
|
@ -65,13 +70,18 @@ class BookmarksController(AuthorizationMixin, BreadcrumbMixin, WebpageController
|
||||||
suffix = suffixes[exp]
|
suffix = suffixes[exp]
|
||||||
return "{num:g} {suffix}Hz".format(num=num, suffix=suffix)
|
return "{num:g} {suffix}Hz".format(num=num, suffix=suffix)
|
||||||
|
|
||||||
mode = Modes.findByModulation(bookmark.getModulation())
|
scan = bookmark.isScannable()
|
||||||
scan = bookmark.isScannable()
|
name1 = bookmark.getModulation()
|
||||||
|
name2 = bookmark.getUnderlying()
|
||||||
|
mode1 = Modes.findByModulation(name1)
|
||||||
|
mode2 = Modes.findByModulation(name2)
|
||||||
|
|
||||||
return """
|
return """
|
||||||
<tr data-id="{id}">
|
<tr data-id="{id}">
|
||||||
<td data-editor="name" data-value="{name}">{name}</td>
|
<td data-editor="name" data-value="{name}">{name}</td>
|
||||||
<td data-editor="frequency" data-value="{frequency}" class="frequency">{rendered_frequency}</td>
|
<td data-editor="frequency" data-value="{frequency}" class="frequency">{rendered_frequency}</td>
|
||||||
<td data-editor="modulation" data-value="{modulation}">{modulation_name}</td>
|
<td data-editor="modulation" data-value="{modulation}">{modulation_name}</td>
|
||||||
|
<td data-editor="underlying" data-value="{underlying}">{underlying_name}</td>
|
||||||
<td data-editor="description" data-value="{description}">{description}</td>
|
<td data-editor="description" data-value="{description}">{description}</td>
|
||||||
<td data-editor="scannable" data-value="{scannable}">{scannable_check}</td>
|
<td data-editor="scannable" data-value="{scannable}">{scannable_check}</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
@ -84,8 +94,10 @@ class BookmarksController(AuthorizationMixin, BreadcrumbMixin, WebpageController
|
||||||
# TODO render frequency in si units
|
# TODO render frequency in si units
|
||||||
frequency=bookmark.getFrequency(),
|
frequency=bookmark.getFrequency(),
|
||||||
rendered_frequency=render_frequency(bookmark.getFrequency()),
|
rendered_frequency=render_frequency(bookmark.getFrequency()),
|
||||||
modulation=bookmark.getModulation() if mode is None else mode.modulation,
|
modulation=name1 if mode1 is None else mode1.modulation,
|
||||||
modulation_name=bookmark.getModulation() if mode is None else mode.name,
|
underlying=name2 if mode2 is None else mode2.modulation,
|
||||||
|
modulation_name=name1 if mode1 is None else mode1.name,
|
||||||
|
underlying_name="None" if not name2 else name2 if mode2 is None else mode2.name,
|
||||||
description=bookmark.getDescription(),
|
description=bookmark.getDescription(),
|
||||||
scannable="true" if scan else "false",
|
scannable="true" if scan else "false",
|
||||||
scannable_check="✓" if scan else "",
|
scannable_check="✓" if scan else "",
|
||||||
|
|
@ -98,6 +110,45 @@ class BookmarksController(AuthorizationMixin, BreadcrumbMixin, WebpageController
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _sanitizeBookmark(self, data):
|
||||||
|
try:
|
||||||
|
# Must have name, frequency, modulation
|
||||||
|
if "name" not in data or "frequency" not in data or "modulation" not in data:
|
||||||
|
return "Bookmark missing required fields"
|
||||||
|
# Name must not be empty
|
||||||
|
data["name"] = data["name"].strip()
|
||||||
|
if len(data["name"]) == 0:
|
||||||
|
return "Empty bookmark name"
|
||||||
|
# Frequency must be integer
|
||||||
|
if not isinstance(data["frequency"], int):
|
||||||
|
data["frequency"] = int(data["frequency"])
|
||||||
|
# Frequency must be >0
|
||||||
|
if data["frequency"] <= 0:
|
||||||
|
return "Frequency must be positive"
|
||||||
|
# Get both modes
|
||||||
|
mode1 = Modes.findByModulation(data["modulation"]) if "modulation" in data else None
|
||||||
|
mode2 = Modes.findByModulation(data["underlying"]) if "underlying" in data else None
|
||||||
|
# Unknown main mode
|
||||||
|
if mode1 is None:
|
||||||
|
return "Invalid modulation"
|
||||||
|
# No underlying mode
|
||||||
|
if mode2 is None:
|
||||||
|
data["underlying"] = ""
|
||||||
|
else:
|
||||||
|
# Main mode has no underlying mode or underlying mode incorrect
|
||||||
|
if not hasattr(mode1, "underlying") or mode2.modulation not in mode1.underlying:
|
||||||
|
return "Incorrect underlying modulation"
|
||||||
|
# Underlying mode is at the default value
|
||||||
|
#if mode2.modulation == mode1.underlying[0]:
|
||||||
|
# data["underlying"] = ""
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Something else went horribly wrong
|
||||||
|
return str(e)
|
||||||
|
|
||||||
|
# Everything ok
|
||||||
|
return None
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
bookmark_id = int(self.request.matches.group(1))
|
bookmark_id = int(self.request.matches.group(1))
|
||||||
bookmark = self._findBookmark(bookmark_id)
|
bookmark = self._findBookmark(bookmark_id)
|
||||||
|
|
@ -105,18 +156,26 @@ class BookmarksController(AuthorizationMixin, BreadcrumbMixin, WebpageController
|
||||||
self.send_response("{}", content_type="application/json", code=404)
|
self.send_response("{}", content_type="application/json", code=404)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
newd = {}
|
||||||
data = json.loads(self.get_body().decode("utf-8"))
|
data = json.loads(self.get_body().decode("utf-8"))
|
||||||
for key in ["name", "frequency", "modulation", "description", "scannable"]:
|
for key in ["name", "frequency", "modulation", "underlying", "description", "scannable"]:
|
||||||
if key in data:
|
if key in data:
|
||||||
value = data[key]
|
newd[key] = data[key]
|
||||||
if key == "frequency":
|
elif hasattr(bookmark, key):
|
||||||
value = int(value)
|
newd[key] = getattr(bookmark, key)
|
||||||
setattr(bookmark, key, value)
|
# Make sure everything is correct
|
||||||
|
error = self._sanitizeBookmark(newd)
|
||||||
|
if error is not None:
|
||||||
|
raise ValueError(error)
|
||||||
|
# Update and store bookmark
|
||||||
|
for key in newd:
|
||||||
|
setattr(bookmark, key, newd[key])
|
||||||
Bookmarks.getSharedInstance().store()
|
Bookmarks.getSharedInstance().store()
|
||||||
# TODO this should not be called explicitly... bookmarks don't have any event capability right now, though
|
# TODO this should not be called explicitly... bookmarks don't have any event capability right now, though
|
||||||
Bookmarks.getSharedInstance().notifySubscriptions(bookmark)
|
Bookmarks.getSharedInstance().notifySubscriptions(bookmark)
|
||||||
self.send_response("{}", content_type="application/json", code=200)
|
self.send_response("{}", content_type="application/json", code=200)
|
||||||
except json.JSONDecodeError:
|
except (json.JSONDecodeError, ValueError) as e:
|
||||||
|
logger.warning("Failed updating bookmark: " + str(e))
|
||||||
self.send_response("{}", content_type="application/json", code=400)
|
self.send_response("{}", content_type="application/json", code=400)
|
||||||
|
|
||||||
def new(self):
|
def new(self):
|
||||||
|
|
@ -125,12 +184,12 @@ class BookmarksController(AuthorizationMixin, BreadcrumbMixin, WebpageController
|
||||||
def create(bookmark_data):
|
def create(bookmark_data):
|
||||||
# sanitize
|
# sanitize
|
||||||
data = {}
|
data = {}
|
||||||
for key in ["name", "frequency", "modulation", "description", "scannable"]:
|
for key in ["name", "frequency", "modulation", "underlying", "description", "scannable"]:
|
||||||
if key in bookmark_data:
|
if key in bookmark_data:
|
||||||
if key == "frequency":
|
data[key] = bookmark_data[key]
|
||||||
data[key] = int(bookmark_data[key])
|
error = self._sanitizeBookmark(data)
|
||||||
else:
|
if error is not None:
|
||||||
data[key] = bookmark_data[key]
|
raise ValueError(error)
|
||||||
bookmark = Bookmark(data)
|
bookmark = Bookmark(data)
|
||||||
bookmarks.addBookmark(bookmark)
|
bookmarks.addBookmark(bookmark)
|
||||||
return {"bookmark_id": id(bookmark)}
|
return {"bookmark_id": id(bookmark)}
|
||||||
|
|
@ -140,7 +199,8 @@ class BookmarksController(AuthorizationMixin, BreadcrumbMixin, WebpageController
|
||||||
result = [create(b) for b in data]
|
result = [create(b) for b in data]
|
||||||
bookmarks.store()
|
bookmarks.store()
|
||||||
self.send_response(json.dumps(result), content_type="application/json", code=200)
|
self.send_response(json.dumps(result), content_type="application/json", code=200)
|
||||||
except json.JSONDecodeError:
|
except (json.JSONDecodeError, ValueError) as e:
|
||||||
|
logger.warning("Failed creating bookmark: " + str(e))
|
||||||
self.send_response("{}", content_type="application/json", code=400)
|
self.send_response("{}", content_type="application/json", code=400)
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue