diff --git a/data/index.html b/data/index.html index 53830e4ed5bf15a7d443606a8581f9c6ff696fd7..1f75d39586d75e332d3dc78b6ee71723366a10d9 100644 --- a/data/index.html +++ b/data/index.html @@ -9,25 +9,66 @@ <link href="styles.css" rel="stylesheet"> </head> -<body class="w-100 h-100 d-flex align-items-center"> +<body class="w-100 h-100 d-flex align-items-center" data-bs-theme="dark"> <div class="container justify-content-center"> - <div class="col-lg-6 offset-lg-3"> + <div class="col-lg-4 offset-lg-4"> + + <div class="card"> + <h5 class="card-header"> + Matrix of Life <div id="spinner" class="spinner-border spinner-border-sm" role="status"> + <span class="visually-hidden">Loading...</span> + </div> + </h5> + <div class="card-body text-center"> + + <div class="row mb-3 px-3"> + <label for="rangeBrightness" class="col-form-label">Brightness</label> + <input type="range" class="form-range rangeConfig" id="rangeBrightness" data-name="brightness" min="1" max="100" steps="1"> + </div> + <div class="row mb-3 px-3"> + <label for="rangeSpeed" class="col-form-label">Speed</label> + <input type="range" class="form-range rangeConfig" id="rangeSpeed" data-name="interval" min="1" max="100" steps="10"> + </div> + + </div> + + <div class="card-footer text-body-secondary py-3"> + <div class="row"> + <div class="col-6"> + <button type="button" class="btn btn-sm btn-primary btnAction" data-action="addGlider">Add Glider</button> + </div> + <div class="col-6 text-end"> + <button type="button" class="btn btn-sm btn-outline-secondary me-1" data-bs-toggle="collapse" data-bs-target="#debug">Debug</button> + <button type="button" class="btn btn-sm btn-danger btnAction " data-action="reboot">Reboot</button> + </div> + </div> + </div> + - <div class="row mb-3"> - <label for="rangeBrightness" class="col-form-label">Brightness</label> - <input type="range" class="form-range" id="rangeBrightness" data-name="brightness" min="8" max="255" steps="1"> - </div> - <div class="row mb-3"> - <label for="rangeSpeed" class="col-form-label">Speed</label> - <input type="range" class="form-range" id="rangeSpeed" data-name="interval" min="10" max="1000" steps="10"> </div> - <button type="button" class="btn btn-primary" id="btnGlider">Add Glider</button> - <pre id="dbg"></pre> - <div id="input_div"> - $<input type="text" value="" id="input_el"> + </div> + + <div class="col-lg-4 offset-lg-4 mt-3 collapse" id="debug"> + + <div class="card"> + <h5 class="card-header"> + Debug + </h5> + <div class="card-body"> + + <textarea id="wsMessages" class="form-control mb-2 font-monospace" rows="10"></textarea> + <div id="input_div"> + <div class="input-group"> + <span class="input-group-text">➡️</span> + <input type="text" id="prompt" class="form-control form-control-sm fs-6 font-monospace"> + </div> + + </div> + + </div> </div> </div> @@ -35,7 +76,8 @@ </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script> - <script src="https://code.jquery.com/jquery-3.7.0.slim.min.js" integrity="sha256-tG5mcZUtJsZvyKAxYLVXrmjKBVLd6VpVccqz/r4ypFE=" crossorigin="anonymous"></script> + <script src="https://code.jquery.com/jquery-3.7.0.min.js" integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous"></script> + <script src="jquery.simple.websocket.min.js"></script> <script src="main.js"></script> </body> diff --git a/data/jquery.simple.websocket.min.js b/data/jquery.simple.websocket.min.js new file mode 100644 index 0000000000000000000000000000000000000000..7dd55392f0c1457ea69b012eeec08d6dbdc7eaee --- /dev/null +++ b/data/jquery.simple.websocket.min.js @@ -0,0 +1 @@ +!function(a){"object"==typeof module&&"string"===module.exports?module.exports=a(jQuery):a(jQuery)}(function(a){var b=function(b){if(this._isEmpty(b,"url"))throw new Error('Missing argument, example usage: $.simpleWebSocket({ url: "ws://127.0.0.1:3000" }); ');this._opt=b,this._ws=null,this._reConnectTries=60,this._reConnectDeferred=null,this._closeDeferred=null,this._dataType=this._prop(this._opt,"dataType","json"),this._listeners=[],this._onOpen=this._prop(this._opt,"onOpen",null),this._onClose=this._prop(this._opt,"onClose",null),this._onError=this._prop(this._opt,"onError",null);var c=this;return this._api=function(){return{connect:function(){return a.extend(c._api,c._reConnect.apply(c,[]))},isConnected:function(a){return a?(a.apply(this,[c._isConnected.apply(c,[])]),c._api):c._isConnected.apply(c,[])},send:function(b){return a.extend(c._api,c._send.apply(c,[b]))},listen:function(b){return a.extend(c._api,c._listenReconnect.apply(c,[b]))},remove:function(a){return c._remove.apply(c,[a]),c._api},removeAll:function(){return c._removeAll.apply(c,[]),c._api},close:function(){return c._reset.apply(c,[]),a.extend(c._api,c._close.apply(c,[]))},getWsAdapter:function(){return this._ws}}}(),this._api};return b.prototype={_createWebSocket:function(a){var b=null;if(a.protocols)if(void 0===window.MozWebSocket){if(!window.WebSocket)throw new Error("Error, websocket could not be initialized.");b=new WebSocket(a.url,a.protocols)}else b=new MozWebSocket(a.url,a.protocols);else if(void 0===window.MozWebSocket){if(!window.WebSocket)throw new Error("Error, websocket could not be initialized.");b=new WebSocket(a.url)}else b=new MozWebSocket(a.url);return b},_bindSocketEvents:function(b,c){var d=this;a(b).bind("open",c.open).bind("close",c.close).bind("message",function(a){try{if("function"==typeof c.message)if(d._dataType&&"json"===d._dataType.toLowerCase()){var b=JSON.parse(a.originalEvent.data);c.message.call(this,b)}else if(d._dataType&&"xml"===d._dataType.toLowerCase()){var e=new DOMParser,f=e.parseFromString(a.originalEvent.data,"text/xml");c.message.call(this,f)}else c.message.call(this,a.originalEvent.data)}catch(a){"function"==typeof c.error&&c.error.call(this,a)}}).bind("error",function(a){"function"==typeof c.error&&c.error.call(this,a)})},_webSocket:function(a){var b=this._createWebSocket(a);return this._bindSocketEvents(b,a),b},_getSocketEventHandler:function(a){var b=this;return{open:function(c){b._onOpen&&b._onOpen.apply(b,[c]);var d=this;a&&a.resolve(d)},close:function(c){b._closeDeferred&&b._closeDeferred.resolve(),b._onClose&&b._onClose.apply(b,[c]),a&&a.rejectWith(c)},message:function(a){for(var c=0,d=b._listeners.length;c<d;c++)try{b._listeners[c].deferred.notify.apply(b,[a])}catch(a){}},error:function(c){b._ws=null,b._onError&&b._onError.apply(b,[c]);for(var d=0,e=b._listeners.length;d<e;d++)b._listeners[d].deferred.reject.apply(b,[c]);a&&a.rejectWith.apply(b,[c])}}},_connect:function(){var b=a.Deferred();if(this._ws)if(2===this._ws.readyState)this._ws.close();else if(3===this._ws.readyState)this._ws.close();else{if(0===this._ws.readyState)return b.promise();if(1===this._ws.readyState)return b.resolve(this._ws),b.promise()}return this._ws=this._webSocket(a.extend(this._opt,this._getSocketEventHandler(b))),b.promise()},_reset:function(){this._reConnectTries=this._prop(this._opt,"attempts",60),this._reConnectDeferred=a.Deferred()},_close:function(){return this._closeDeferred=a.Deferred(),this._ws&&(this._ws.close(),this._ws=null),this._closeDeferred.promise()},_isConnected:function(){return null!==this._ws&&1===this._ws.readyState},_reConnectTry:function(){var a=this;this._connect().done(function(){a._reConnectDeferred.resolve.apply(a,[a._ws])}).fail(function(b){a._reConnectTries--,a._reConnectTries>0?window.setTimeout(function(){a._reConnect.apply(a,[])},a._prop.apply(a,[a._opt,"timeout",1e4])):a._reConnectDeferred.rejectWith.apply(a,[b])})},_reConnect:function(){var a=this;return null===this._reConnectDeferred?this._reset():"resolved"===this._reConnectDeferred.state()?this._reset():"rejected"===this._reConnectDeferred.state()&&this._reset(),this._ws&&1===this._ws.readyState?this._reConnectDeferred.resolve(this._ws):this._reConnectTry(),a._reConnectDeferred.promise.apply(a,[])},_preparePayload:function(a){return this._opt.dataType&&"text"===this._opt.dataType.toLowerCase()?a:this._opt.dataType&&"xml"===this._opt.dataType.toLowerCase()?a:(this._opt.dataType&&this._opt.dataType.toLowerCase(),JSON.stringify(a))},_send:function(b){var c=this,d=a.Deferred();return function(a){c._reConnect.apply(c,[]).done(function(b){b.send(a),d.resolve.apply(c,[c._api])}).fail(function(a){d.rejectWith.apply(c,[a])})}(this._preparePayload(b)),d.promise()},_indexOfListener:function(a){for(var b=0,c=this._listeners.length;b<c;b++)if(this._listeners[b].listener===a)return b;return-1},_isEmpty:function(a,b){return"string"===a||(null===a||(void 0===b||(null===b||(""===b||(void 0===a[b]||null===a[b])))))},_prop:function(a,b,c){return this._isEmpty(a,b)?c:a[b]},_listen:function(b){var c=this,d=a.Deferred();return c._reConnect.apply(c,[]).done(function(){d.progress(function(){b.apply(this,arguments)}),c._remove.apply(c,[b]),c._listeners.push({deferred:d,listener:b})}).fail(function(a){d.reject(a)}),d.promise()},_listenReconnect:function(b){var c=a.Deferred(),d=this;return this._listen(b).fail(function(){c.notify(arguments),d._listenReconnect.apply(d,[b])}).done(function(){c.resolve()}),c.promise()},_remove:function(a){var b=this._indexOfListener(a);0<=b&&(this._listeners[b].deferred.resolve(),this._listeners.splice(b,1))},_removeAll:function(){for(var a=0,b=this._listeners.length;a<b;a++)this._listeners[a].deferred.resolve();this._listeners=[]}},a.extend({simpleWebSocket:function(a){return new b(a)}}),a.simpleWebSocket}); \ No newline at end of file diff --git a/data/main.js b/data/main.js index 8540a25cdb74ff7027db3ec758ffab2ad0c38e9e..6be0cf0578d2f50e990aa0dc0327ba3bee0300d4 100644 --- a/data/main.js +++ b/data/main.js @@ -1,70 +1,116 @@ -var ws = null; -function ge(s) { return document.getElementById(s); } -function ce(s) { return document.createElement(s); } -function addMessage(m) { - var msg = ce("div"); - msg.innerText = m; - ge("dbg").appendChild(msg); -} +var wsUrl = 'ws://' + document.location.host + '/ws' +var ws = null; var config = { brightness: 0, interval: 0, }; -$(document).ready(function() { +$(document).ready(function () { + uiEnabled(false); + $("#wsMessages, #prompt").val(""); startSocket(); }); -$(document).on('input change', '#rangeBrightness, #rangeSpeed', function() { - config[$(this).data('name')] = $(this).val(); +$(document).on('input change', '.rangeConfig', function () { + var setting = $(this).data('name'); + var value = $(this).val(); + switch (setting) { + case 'brightness': + value = mapRange(value, 1, 100, 8, 255); + break; + case 'interval': + value = mapRange(value, 1, 100, 1000, 10); + break; + } + config[setting] = value; var payload = { - config: config + 'config': config }; - ws.send(JSON.stringify(payload)); + wsSend(payload); }); -$('#btnGlider').on('click', function() { +$('.btnAction').on('click', function () { + var action = $(this).data('action'); var payload = { - action: "addGlider" + 'action': action }; - ws.send(JSON.stringify(payload)); -}) + wsSend(payload); +}); + +$("#prompt").onkeydown = function (e) { + if (e.keyCode == 13 && $("#prompt").value != "") { + var val = $("#prompt").val(); + wsSend(val); + $("#prompt").val(""); + } +} + +function uiEnabled(enabled) { + if (enabled) { + $('#spinner').addClass('invisible'); + $('.btnAction').prop('disabled', false); + $('.rangeConfig').prop('disabled', false); + } else { + $('#spinner').removeClass('invisible'); + $('.btnAction').prop('disabled', true); + $('.rangeConfig').prop('disabled', true); + } +} + +function addMessage(msg) { + var txt = $("#wsMessages"); + txt.val($.trim(txt.val() + "\n" + msg)); + txt.scrollTop(txt[0].scrollHeight - txt.height()); +} + +function wsSend(msg) { + uiEnabled(false); + var json = JSON.stringify(msg); + ws.send(msg).done(function () { + addMessage("➡️ " + json); + uiEnabled(true); + }).fail(function (e) { + addMessage("❗️ " + json); + }); +} function startSocket() { - ws = new WebSocket('ws://' + document.location.host + '/ws', ['arduino']); - ws.onopen = function (e) { - addMessage("Connected"); - }; - ws.onclose = function (e) { - addMessage("Disconnected"); - }; - ws.onerror = function (e) { - console.log("ws error", e); - addMessage("Error"); - }; - ws.onmessage = function (e) { - var msg = "> " + e.data; + ws = $.simpleWebSocket({ + url: wsUrl, + onOpen: function (e) { + console.log("WS: connected"); + addMessage("✅ connected"); + }, + onClose: function (e) { + uiEnabled(false); + console.log("WS: disconnected"); + addMessage("❌ disconnected"); + }, + onError: function (e) { + uiEnabled(false); + console.log("WS: error"); + addMessage("❗️ error"); + }, + }); + ws.listen(function (data) { + var json = JSON.stringify(data); + var msg = "⬅️ " + json; addMessage(msg); - var data = jQuery.parseJSON(e.data); - console.log(data); - if(data.config) { + if (data.config) { loadConfig(data.config); - } else { - console.log("no config"); + uiEnabled(true); } - }; - ge("input_el").onkeydown = function (e) { - if (e.keyCode == 13 && ge("input_el").value != "") { - ws.send(ge("input_el").value); - ge("input_el").value = ""; - } - } + }); } function loadConfig(data) { config.brightness = data.brightness; config.interval = data.interval; - $('#rangeBrightness').val(data.brightness); - $('#rangeSpeed').val(data.interval); + $('#rangeBrightness').val(mapRange(data.brightness, 8, 255, 1, 100)); + $('#rangeSpeed').val(mapRange(data.interval, 1000, 10, 1, 100)); +} + +function mapRange (number, inMin, inMax, outMin, outMax) { + return Math.round((number - inMin) * (outMax - outMin) / (inMax - inMin) + outMin); } diff --git a/data/styles.css b/data/styles.css index 3bee0465785fd6c068be7522eaf6cf89483d3d82..d2502b5c1137a254b0c34acc1221d7bf11488db3 100644 --- a/data/styles.css +++ b/data/styles.css @@ -3,22 +3,6 @@ html { height: 100%; } -#dbg, #input_div, #input_el { - font-family: monaco; -} - -#dbg, #input_div { - margin:0; - padding:0; - padding-left:4px; -} - -#input_el { - width:98%; - background-color: rgba(0,0,0,0); - border: 0px; -} - -#input_el:focus { - outline: none; +#wsMessages { + font-size: 0.7rem; }