diff --git a/htdocs/index.html b/htdocs/index.html index 448be68f..03f61368 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -39,6 +39,7 @@ +
diff --git a/htdocs/map-google.html b/htdocs/map-google.html index ff73ec73..461f9539 100644 --- a/htdocs/map-google.html +++ b/htdocs/map-google.html @@ -9,6 +9,7 @@ + diff --git a/htdocs/map-leaflet.html b/htdocs/map-leaflet.html index 9251cc93..078909b4 100644 --- a/htdocs/map-leaflet.html +++ b/htdocs/map-leaflet.html @@ -9,6 +9,7 @@ + diff --git a/htdocs/plugins.js b/htdocs/plugins.js new file mode 100644 index 00000000..99850da3 --- /dev/null +++ b/htdocs/plugins.js @@ -0,0 +1,136 @@ +/** + * OpenWebRx+ Plugin loader + * + * You should load your plugins in "plugins/{type}/init.js", + * where {type} is 'receiver' or 'map'. + * See the init.js.sample. + * And check the "plugins/{type}/example" folder for example plugin. + */ + +// Wait for the page to load, then load the plugins. +$(document).ready(function () { + // detect which plugins to load + Plugins._type = (typeof mapManager !== 'undefined') ? 'map' : 'receiver'; + Plugins.init(); +}); + +// Initialize the Plugins class and some defaults +function Plugins () {} +Plugins._initialized = false; +Plugins._version = 0.1; // version of the plugin sub-system (keep it float) +Plugins._enable_debug = false; // print debug to the console + + +// Load plugin +Plugins.load = async function (name) { + var path = 'static/plugins/' + Plugins._type + "/" + name + "/"; + var remote = false; + + // check if we are loading an url + if (name.toLowerCase().startsWith('https://') || name.toLowerCase().startsWith('http://')) { + path = name; + name = path.split('/').pop().split('.').slice(0, -1); + path = path.split('/').slice(0, -1).join('/') + '/'; + remote = true; + } + + if (name in Plugins) { + console.warn(Plugins._get_banner(name) + 'is already loaded' + (Plugins[name]._script_loaded ? ' from ' + Plugins[name]._script_loaded : '')); + return; + } + + Plugins._debug(Plugins._get_banner(name) + 'loading.'); + + var script_src = path + name + ".js"; + var style_src = path + name + '.css'; + + // init plugin object with defaults + Plugins[name] = { + _version: 0, + _script_loaded: false, + _style_loaded: false, + _remote: remote, + }; + + + // try to load the plugin + await Plugins._load_script(script_src) + .then(function () { + // plugin script loaded successfully + Plugins[name]._script_loaded = script_src; + + // check if the plugin has init() method and execute it + if (typeof Plugins[name].init === 'function') { + if (!Plugins[name].init()) { + console.error(Plugins._get_banner(name) + 'cannot initialize.'); + return; + } + } + + // check if plugin has set the 'no_css', otherwise, load the plugin css style + if (!('no_css' in Plugins[name])) { + Plugins._load_style(style_src) + .then(function () { + Plugins[name]._style_loaded = style_src; + Plugins._debug(Plugins._get_banner(name) + 'loaded.'); + }).catch(function () { + console.warn(Plugins._get_banner(name) + 'script loaded, but css not found.'); + }); + } else { + // plugin has no_css + Plugins._debug(Plugins._get_banner(name) + 'loaded.'); + } + + }).catch(function () { + // plugin cannot be loaded + Plugins._debug(Plugins._get_banner(name) + 'cannot be loaded (does not exist or has errors).'); + }); +} + +// Check if plugin is loaded +Plugins.isLoaded = function (name, version = 0) { + if (typeof Plugins[name] === 'object') + return (Plugins[name]._script_loaded && Plugins[name]._version >= version); + return false; +} + +// Initialize plugin loading. We should load the init.js for the {type}. This init() is called onDomReady. +Plugins.init = function () { + Plugins._debug("Loading " + Plugins._type + " plugins."); + // load the init.js for the {type}... user should load their plugins there. + Plugins._load_script('static/plugins/' + Plugins._type + "/init.js").then(function () { + Plugins._initialized = true; + }).catch(function () { + Plugins._debug('no plugins to load.'); + }) +} + +// --------------------------------------------------------------------------- + +// internal utility methods +Plugins._load_script = function (src) { + return new Promise(function (resolve, reject) { + var script = document.createElement('script'); + script.onload = resolve; + script.onerror = reject; + script.src = src; + document.head.appendChild(script); + }); +} +Plugins._load_style = function (src) { + return new Promise(function (resolve, reject) { + var style = document.createElement('link'); + style.onload = resolve; + style.onerror = reject; + style.href = src; + style.type = 'text/css'; + style.rel = 'stylesheet'; + document.head.appendChild(style); + }); +} +Plugins._debug = function (msg) { + if (Plugins._enable_debug) console.debug(msg); +} +Plugins._get_banner = function (name) { + return 'PLUGINS: ' + (Plugins[name] && Plugins[name]._remote ? '[remote]' : '[local]') + ' "' + name + '" '; +} diff --git a/htdocs/plugins/receiver/example/example.js b/htdocs/plugins/receiver/example/example.js new file mode 100644 index 00000000..7c6ed717 --- /dev/null +++ b/htdocs/plugins/receiver/example/example.js @@ -0,0 +1,147 @@ +/* + * example UI plugin for OpenWebRx+ + */ + +// Disable CSS loading for this plugin +Plugins.example.no_css = true; + +// Init function of the plugin +Plugins.example.init = function () { + + // Check if utils plugin is loaded + if (!Plugins.isLoaded('utils', 0.1)) { + console.error('Example plugin depends on "utils >= 0.1".'); + return false; + } + + // Listen to profile change and print the new profile name to console. + // NOTE: you cannot manipulate the data in events, you will need to wrap the original + // function if you want to manipulate data. + $(document).on('event:profile_changed', function (e, data) { + console.log('profile change event: ' + data); + }); + + // Another events: + // event:owrx_initialized - called when OWRX is initialized + + // Server events are triggered when server sends data over the WS + // All server events have suffix ':before' or ':after', based on the original functoin call. + // :before events are before the original function call, + // :after events are after the original function call. + // Some interesting server events: + // server:config - server configuration + // server:bookmarks - the bookmarks from server + // server:clients - clients number change + // server:profiles - sdr profiles + // server:features - supported features + + + // Modify an existing OWRX function with utils plugin. + // See utils.js for documentation on wrap method. + // This will wrap profile changing function + Plugins.utils.wrap_func( + // function to wrap around + 'sdr_profile_changed', + + // before callback, to be run before the original function + // orig = original function + // thisArg = thisArg for the original function + // args = the arguments for the original function + // If you call the original function here (in the before_cb), always return false, + // so the wrap_func() will not call it later again. + // example of calling the original function: orig.apply(thisArg, args); + function (orig, thisArg, args) { + console.log("Before callback for: " + orig.name); + + // check if newly selected profile is the PMR profile + if ($('#openwebrx-sdr-profiles-listbox').find(':selected').text() === "[RTL] 446 PMR") { + // prevent changing to this profile + console.log('This profile is disabled by proxy function'); + + // restore the previous selected profile + $('#openwebrx-sdr-profiles-listbox').val(currentprofile.toString()); + + // return false to prevent execution of original function + return false; + } + + // return true to allow execution of original function + return true; + }, + + // after callback, to be run after the original function, + // but only if the before callback returns true + // res = result of the original function, if any + function (res) { + console.log('profile changed.'); + } + ); + + // this example will do the same (stop profile changing), but using another method + // replace the "onchange" handler of the profiles selectbox + // and call the original function "sdr_profile_changed" + $('#openwebrx-sdr-profiles-listbox')[0].onchange = function (e) { + + // check the index of the selected profile (0 is the first profile in the list) + if (e.target.options.selectedIndex === 0) { + // prevent changing to this profile + console.log('This profile is disabled by onchange.'); + + // restore the previous profile + $('#openwebrx-sdr-profiles-listbox').val(currentprofile.toString()); + e.preventDefault(); + e.stopPropagation(); + return false; + } + + // otherwise, call the original function + sdr_profile_changed(); + }; + + // this example will manipulate bookmarks data when the server sends the bookmarks + // We will wrap the bookmarks.replace_bookmarks() function, once OWRX is initialized. + // We cannot wrap the replace_bookmarks() function before the bookmarks object is created. + // So we wait for OWRX to initialize and then wrap the function. + $(document).on('event:owrx_initialized', function () { + + // Call the wrap method of utils plugin + Plugins.utils.wrap_func( + + // function to wrap + 'replace_bookmarks', + + // before callback + function (orig, thisArg, args) { + + // check if the bookmarks are "server bookmarks" + if (args[1] === 'server') { + + // check if we have array of bookmarks (will be empty if the profile has no bookmarks to show) + if (typeof (args[0]) === 'object' && args[0].length) + + // replace the name of the first bookmark + args[0][0].name = 'replaced'; + } + + // now call the original function + orig.apply(thisArg, args); + + // and return false, so the wrap_func() will not call the original for second time + return false; + }, + + // after callback + function (res) { + /* not executed because the before function returns false always */ + }, + + // this is the object, where the repalce_boomarks() function should be found + bookmarks + ); + }); + + + + // return true for plugin init() + return true; +} // end of init function diff --git a/htdocs/plugins/receiver/example_theme/example_theme.css b/htdocs/plugins/receiver/example_theme/example_theme.css new file mode 100644 index 00000000..df578478 --- /dev/null +++ b/htdocs/plugins/receiver/example_theme/example_theme.css @@ -0,0 +1,12 @@ +/* + * colors for the new theme + */ +body.theme-eye-piercer { + --theme-color1: #ff6262; + --theme-color2: #ff626252; + --theme-gradient-color1: #ff6262; + --theme-gradient-color2: #ff0000; +} + + + diff --git a/htdocs/plugins/receiver/example_theme/example_theme.js b/htdocs/plugins/receiver/example_theme/example_theme.js new file mode 100644 index 00000000..88ad04ff --- /dev/null +++ b/htdocs/plugins/receiver/example_theme/example_theme.js @@ -0,0 +1,14 @@ +/* + * example plugin, creating a new theme for OpenWebRx+ + */ + +// Add new entry in the Theme selectbox +$('#openwebrx-themes-listbox').append( + $('