From 29c0f7148a6bf0c0946b0c12de242658b15991c3 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sat, 27 Mar 2021 23:08:43 +0100 Subject: [PATCH] re-work the bookmarks table to incorporate the improved frequency input --- htdocs/lib/settings/BookmarkTable.js | 293 ++++++++++++++++++++----- owrx/controllers/settings/bookmarks.py | 43 ++-- 2 files changed, 256 insertions(+), 80 deletions(-) diff --git a/htdocs/lib/settings/BookmarkTable.js b/htdocs/lib/settings/BookmarkTable.js index 66a8975..ca1e71d 100644 --- a/htdocs/lib/settings/BookmarkTable.js +++ b/htdocs/lib/settings/BookmarkTable.js @@ -1,56 +1,218 @@ +function Editor(table) { + this.table = table; +} + +Editor.prototype.getInputHtml = function() { + return ''; +} + +Editor.prototype.render = function(el) { + this.input = $(this.getInputHtml()); + el.append(this.input); + this.setupEvents(); +}; + +Editor.prototype.setupEvents = function() { + var me = this; + this.input.on('blur', function() { me.submit(); }).on('change', function() { me.submit(); }).on('keydown', function(e){ + if (e.keyCode == 13) return me.submit(); + if (e.keyCode == 27) return me.cancel(); + }); +}; + +Editor.prototype.submit = function() { + if (!this.onSubmit) return; + var submit = this.onSubmit; + delete this.onSubmit; + submit(); +}; + +Editor.prototype.cancel = function() { + if (!this.onCancel) return; + var cancel = this.onCancel; + delete this.onCancel; + cancel(); +}; + +Editor.prototype.focus = function() { + this.input.focus(); +}; + +Editor.prototype.disable = function(flag) { + this.input.prop('disabled', flag); +}; + +Editor.prototype.setValue = function(value) { + this.input.val(value); +}; + +Editor.prototype.getValue = function() { + return this.input.val(); +}; + +Editor.prototype.getHtml = function() { + return this.getValue(); +}; + +function NameEditor(table) { + Editor.call(this, table); +} + +NameEditor.prototype = new Editor(); + +NameEditor.prototype.getInputHtml = function() { + return ''; +} + +function FrequencyEditor(table) { + Editor.call(this, table); + this.suffixes = { + 'K': 3, + 'M': 6, + 'G': 9, + 'T': 12 + }; +} + +FrequencyEditor.prototype = new Editor(); + +FrequencyEditor.prototype.getInputHtml = function() { + return '
' + + '' + + '
' + + '' + + '
' + + '
'; +}; + +FrequencyEditor.prototype.render = function(el) { + this.input = $(this.getInputHtml()); + el.append(this.input); + this.freqInput = el.find('input'); + this.expInput = el.find('select'); + this.setupEvents(); +}; + +FrequencyEditor.prototype.setupEvents = function() { + var me = this; + var inputs = [this.freqInput, this.expInput].map(function(i) { return i[0]; }); + inputs.forEach(function(input) { + $(input).on('blur', function(e){ + if (inputs.indexOf(e.relatedTarget) >= 0) { + return; + } + me.submit(); + }); + }); + + var me = this; + this.freqInput.on('keydown', function(e){ + if (e.keyCode == 13) return me.submit(); + if (e.keyCode == 27) return me.cancel(); + var c = String.fromCharCode(e.which); + if (c in me.suffixes) { + me.expInput.val(me.suffixes[c]); + } + }); +} + +FrequencyEditor.prototype.getValue = function() { + var frequency = parseFloat(this.freqInput.val()); + var exp = parseInt(this.expInput.val()); + return Math.floor(frequency * 10 ** exp); +}; + +FrequencyEditor.prototype.setValue = function(value) { + var value = parseFloat(value); + var exp = 0; + if (!Number.isNaN(value)) { + exp = Math.floor(Math.log10(value) / 3) * 3; + } + this.freqInput.val(value / 10 ** exp); + this.expInput.val(exp); +}; + +FrequencyEditor.prototype.focus = function() { + this.freqInput.focus(); +}; + +FrequencyEditor.prototype.getHtml = function() { + var value = this.getValue(); + var exp = 0; + if (!Number.isNaN(value)) { + exp = Math.floor(Math.log10(value) / 3) * 3; + } + var frequency = value / 10 ** exp; + var expString = this.expInput.find('option[value=' + exp + ']').html(); + return frequency + ' ' + expString; +}; + +function ModulationEditor(table) { + Editor.call(this, table); + this.modes = table.data('modes'); +} + +ModulationEditor.prototype = new Editor(); + +ModulationEditor.prototype.getInputHtml = function() { + return ''; +}; + +ModulationEditor.prototype.getHtml = function() { + var $option = this.input.find('option:selected') + return $option.html(); +}; + $.fn.bookmarktable = function() { + var editors = { + name: NameEditor, + frequency: FrequencyEditor, + modulation: ModulationEditor + }; + $.each(this, function(){ var $table = $(this).find('table'); - var inputs = $table.find('tr.inputs td').map(function(){ - var candidates = $(this).find('input, select') - return candidates.length ? candidates.first() : false; - }).toArray(); - $table.find('tr.inputs').remove(); - - var transformToHtml = function($cell) { - var $input = $cell.find('input, select'); - var $option = $input.find('option:selected') - if ($option.length) { - $cell.html($option.html()); - } else { - $cell.html($input.val()); - } - }; - $table.on('dblclick', 'td', function(e) { var $cell = $(e.target); var html = $cell.html(); var $row = $cell.parents('tr'); - var index = $row.find('td').index($cell); + var name = $cell.data('editor'); + var EditorCls = editors[name]; + if (!EditorCls) return; - var $input = inputs[index]; - if (!$input) return; + var editor = new EditorCls($table); + editor.render($cell.html('')); + editor.setValue($cell.data('value')); + editor.focus(); - $table.find('tr[data-id="new"]').remove(); - $input.val($cell.data('value') || html); - $input.prop('disabled', false); - $cell.html($input); - $input.focus(); - - var submit = function() { - $input.prop('disabled', true); + editor.onSubmit = function() { + editor.disable(true); $.ajax(document.location.href + "/" + $row.data('id'), { - data: JSON.stringify(Object.fromEntries([[$input.prop('name'), $input.val()]])), + data: JSON.stringify(Object.fromEntries([[name, editor.getValue()]])), contentType: 'application/json', method: 'POST' }).then(function(){ - transformToHtml($cell); + $cell.data('value', editor.getValue()); + $cell.html(editor.getHtml()); }); }; - $input.on('blur', submit).on('change', submit).on('keyup', function(e){ - if (e.keyCode == 13) return submit(); - if (e.keyCode == 27) { - $cell.html(html); - } - }); + editor.onCancel = function() { + $cell.html(html); + }; }); $table.on('click', '.bookmark-delete', function(e) { @@ -70,22 +232,26 @@ $.fn.bookmarktable = function() { if ($table.find('tr[data-id="new"]').length) return; var row = $(''); - row.append(inputs.map(function(i){ - var cell = $(''); - if (i) { - i.prop('disabled', false); - i.val(''); - cell.html(i); - } else { - cell.html( - '
' + - '' + - '' + - '
' - ); - } + + var inputs = Object.fromEntries( + Object.entries(editors).map(function(e) { + return [e[0], new e[1]($table)]; + }) + ); + + row.append($.map(inputs, function(editor, name){ + var cell = $(''); + editor.render(cell); return cell; })); + row.append($( + '' + + '
' + + '' + + '' + + '
' + + '' + )); row.on('click', '.bookmark-cancel', function() { row.remove(); @@ -93,10 +259,10 @@ $.fn.bookmarktable = function() { row.on('click', '.bookmark-save', function() { var data = Object.fromEntries( - row.find('input, select').toArray().map(function(input){ - var $input = $(input); - $input.prop('disabled', true); - return [$input.prop('name'), $input.val()] + $.map(inputs, function(input, name){ + input.disable(true); + // double wrapped because jQuery.map() flattens the result + return [[name, input.getValue()]]; }) ); @@ -107,15 +273,20 @@ $.fn.bookmarktable = function() { }).then(function(data){ if ('bookmark_id' in data) { row.attr('data-id', data['bookmark_id']); - row.find('td').each(function(){ - var $cell = $(this); - var $group = $cell.find('.btn-group') - if ($group.length) { - $group.remove; - $cell.html('
delete
'); - } - transformToHtml($cell); + var tds = row.find('td'); + + Object.values(inputs).forEach(function(input, index) { + var td = $(tds[index]); + td.data('value', input.getValue()); + td.html(input.getHtml()); }); + + var $cell = row.find('td').last(); + var $group = $cell.find('.btn-group'); + if ($group.length) { + $group.remove; + $cell.html('
delete
'); + } } }); diff --git a/owrx/controllers/settings/bookmarks.py b/owrx/controllers/settings/bookmarks.py index 2d4dd82..2a4fb3a 100644 --- a/owrx/controllers/settings/bookmarks.py +++ b/owrx/controllers/settings/bookmarks.py @@ -3,6 +3,7 @@ from owrx.controllers.admin import AuthorizationMixin from owrx.bookmarks import Bookmark, Bookmarks from owrx.modes import Modes import json +import math import logging @@ -18,16 +19,8 @@ class BookmarksController(AuthorizationMixin, WebpageController): def render_table(self): bookmarks = Bookmarks.getSharedInstance() - def render_mode(m): - return """ - - """.format( - mode=m.modulation, - name=m.name, - ) - return """ - +
@@ -35,25 +28,35 @@ class BookmarksController(AuthorizationMixin, WebpageController): {bookmarks} - - - - - -
Name FrequencyActions
""".format( bookmarks="".join(self.render_bookmark(b) for b in bookmarks.getBookmarks()), - options="".join(render_mode(m) for m in Modes.getAvailableModes()), + modes=json.dumps({m.modulation: m.name for m in Modes.getAvailableModes()}), ) def render_bookmark(self, bookmark: Bookmark): + def render_frequency(freq): + suffixes = { + 0: "", + 3: "k", + 6: "M", + 9: "G", + 12: "T", + } + exp = int(math.log10(freq) / 3) * 3 + num = freq + suffix = "" + if exp in suffixes: + num = freq / 10 ** exp + suffix = suffixes[exp] + return "{num:g} {suffix}Hz".format(num=num, suffix=suffix) + mode = Modes.findByModulation(bookmark.getModulation()) return """ - {name} - {frequency} - {modulation_name} + {name} + {rendered_frequency} + {modulation_name} @@ -61,7 +64,9 @@ class BookmarksController(AuthorizationMixin, WebpageController): """.format( id=id(bookmark), name=bookmark.getName(), + # TODO render frequency in si units frequency=bookmark.getFrequency(), + rendered_frequency=render_frequency(bookmark.getFrequency()), modulation=bookmark.getModulation() if mode is None else mode.modulation, modulation_name=bookmark.getModulation() if mode is None else mode.name, )