2026-03-24 11:14:33 +01:00
|
|
|
<!doctype html>
|
|
|
|
|
<html>
|
|
|
|
|
<head>
|
2026-03-24 12:40:25 +01:00
|
|
|
<title>MicroPythonOS WebREPL</title>
|
2026-03-24 11:14:33 +01:00
|
|
|
<style>:root{color-scheme:light;--bg:#ffffff;--panel:#f6f7f9;--panel-border:#e1e5ea;--text:#1c1f24;--muted:#5c6572;--accent:#f0a010;--accent-hover:#e08f00;--accent-active:#cc7f00;--shadow:0 10px 30px rgba(19,27,43,0.08);--radius:12px;--focus-ring:0 0 0 3px rgba(240,160,16,0.3);}*{box-sizing:border-box;}html{background:var(--bg);}body{margin:0;padding:24px 28px 32px;font:16px/1.6 "Inter","Segoe UI","Helvetica Neue",Arial,sans-serif;color:var(--text);background:var(--bg);}h1{margin:0 0 20px;font:600 24px/1.3 "Inter","Segoe UI","Helvetica Neue",Arial,sans-serif;color:var(--text);}form{display:flex;flex-wrap:wrap;gap:12px;align-items:center;margin-bottom:16px;}#term-wrapper{height:420px;}#term{height:100%;border-radius:var(--radius);border:1px solid var(--panel-border);background:#0f141b;color:#f1f5f9;padding:8px;box-shadow:var(--shadow);overflow:hidden;}#file-boxes{margin-left:20px;}.file-box{margin:0 0 16px;padding:14px 16px 16px;background:var(--panel);border:1px solid var(--panel-border);border-radius:var(--radius);box-shadow:var(--shadow);display:grid;gap:10px;}.file-box strong{font-size:15px;}#file-status{color:var(--muted);font-size:14px;}input[type="text"],input[type="file"]{width:100%;padding:10px 12px;border-radius:10px;border:1px solid var(--panel-border);background:#ffffff;color:var(--text);font-size:14px;}input[type="text"]:focus,input[type="file"]:focus{outline:none;border-color:var(--accent);box-shadow:var(--focus-ring);}input[type="submit"],input[type="button"]{padding:10px 16px;border-radius:999px;border:none;background:var(--accent);color:#1a1200;font-weight:600;font-size:14px;cursor:pointer;transition:transform 0.08s ease,background 0.15s ease,box-shadow 0.15s ease;box-shadow:0 8px 18px rgba(240,160,16,0.35);}input[type="submit"]:hover,input[type="button"]:hover{background:var(--accent-hover);transform:translateY(-1px);}input[type="submit"]:active,input[type="button"]:active{background:var(--accent-active);transform:translateY(0);}input[type="submit"]:focus,input[type="button"]:focus{outline:none;box-shadow:var(--focus-ring);}i{color:var(--muted);font-size:13px;}</style>
|
|
|
|
|
<script>;(function(){'use strict';var window = this,document = this.document;function EventEmitter(){this._events = this._events ||{};}EventEmitter.prototype.addListener = function(type,listener){this._events[type]= this._events[type]||[];this._events[type].push(listener);};EventEmitter.prototype.on = EventEmitter.prototype.addListener;EventEmitter.prototype.removeListener = function(type,listener){if(!this._events[type])return;var obj = this._events[type],i = obj.length;while(i--){if(obj[i]=== listener || obj[i].listener === listener){obj.splice(i,1);return;}}};EventEmitter.prototype.off = EventEmitter.prototype.removeListener;EventEmitter.prototype.removeAllListeners = function(type){if(this._events[type])delete this._events[type];};EventEmitter.prototype.once = function(type,listener){function on(){var args = Array.prototype.slice.call(arguments);this.removeListener(type,on);return listener.apply(this,args);}on.listener = listener;return this.on(type,on);};EventEmitter.prototype.emit = function(type){if(!this._events[type])return;var args = Array.prototype.slice.call(arguments,1),obj = this._events[type],l = obj.length,i = 0;for(;i < l;i++){obj[i].apply(this,args);}};EventEmitter.prototype.listeners = function(type){return this._events[type]= this._events[type]||[];};function Stream(){EventEmitter.call(this);}inherits(Stream,EventEmitter);Stream.prototype.pipe = function(dest,options){var src = this,ondata,onerror,onend;function unbind(){src.removeListener('data',ondata);src.removeListener('error',onerror);src.removeListener('end',onend);dest.removeListener('error',onerror);dest.removeListener('close',unbind);}src.on('data',ondata = function(data){dest.write(data);});src.on('error',onerror = function(err){unbind();if(!this.listeners('error').length){throw err;}});src.on('end',onend = function(){dest.end();unbind();});dest.on('error',onerror);dest.on('close',unbind);dest.emit('pipe',src);return dest;};var normal = 0,escaped = 1,csi = 2,osc = 3,charset = 4,dcs = 5,ignore = 6,UDK ={type: 'udk'};function Terminal(options){var self = this;if(!(this instanceof Terminal)){return new Terminal(arguments[0],arguments[1],arguments[2]);}Stream.call(this);if(typeof options === 'number'){options ={cols: arguments[0],rows: arguments[1],handler: arguments[2]};}options = options ||{};each(keys(Terminal.defaults),function(key){if(options[key]== null){options[key]= Terminal.options[key]; if(Terminal[key]!== Terminal.defaults[key]){options[key]= Terminal[key];}}self[key]= options[key];});if(options.colors.length === 8){options.colors = options.colors.concat(Terminal._colors.slice(8));}else if(options.colors.length === 16){options.colors = options.colors.concat(Terminal._colors.slice(16));}else if(options.colors.length === 10){options.colors = options.colors.slice(0,-2).concat(Terminal._colors.slice(8,-2),options.colors.slice(-2));}else if(options.colors.length === 18){options.colors = options.colors.slice(0,-2).concat(Terminal._colors.slice(16,-2),options.colors.slice(-2));}this.colors = options.colors;this.options = options; this.parent = options.body || options.parent ||(document ? document.getElementsByTagName('body')[0]: null);this.cols = options.cols || options.geometry[0];this.rows = options.rows || options.geometry[1]; this.setRawMode;this.isTTY = true;this.isRaw = true;this.columns = this.cols;this.rows = this.rows;if(options.handler){this.on('data',options.handler);}this.ybase = 0;this.ydisp = 0;this.x = 0;this.y = 0;this.cursorState = 0;this.cursorHidden = false;this.convertEol;this.state = 0;this.queue = '';this.scrollTop = 0;this.scrollBottom = this.rows - 1; this.applicationKeypad = false;this.applicationCursor = false;this.originMode = false;this.insertMode = false;this.wraparoundMode = false;this.normal = null; this.prefixMode = false;this.selectMode = false;this.visualMode = false;this.searchMode = false;this.searchDown;this.entry = '';this.entryPrefix = 'Search: ';this._real;this._selected;this._textarea; this.charset = null;this.gcharset = null;this.glevel = 0;this.charsets =[null]; this.decLocator;this.x10Mouse;this.v
|
|
|
|
|
<script>var saveAs = saveAs ||(function(view){"use strict"; if(typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE[1-9]\./.test(navigator.userAgent)){return;}var doc = view.document,get_URL = function(){return view.URL || view.webkitURL || view;},save_link = doc.createElementNS("http://www.w3.org/1999/xhtml","a"),can_use_save_link = "download" in save_link,click = function(node){var event = new MouseEvent("click");node.dispatchEvent(event);},is_safari = /constructor/i.test(view.HTMLElement),is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent),throw_outside = function(ex){(view.setImmediate || view.setTimeout)(function(){throw ex;},0);},force_saveable_type = "application/octet-stream",arbitrary_revoke_timeout = 1000 * 40,revoke = function(file){var revoker = function(){if(typeof file === "string"){ get_URL().revokeObjectURL(file);}else{ file.remove();}};setTimeout(revoker,arbitrary_revoke_timeout);},dispatch = function(filesaver,event_types,event){event_types =[].concat(event_types);var i = event_types.length;while(i--){var listener = filesaver["on" + event_types[i]];if(typeof listener === "function"){try{listener.call(filesaver,event || filesaver);}catch(ex){throw_outside(ex);}}}},auto_bom = function(blob){ if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)){return new Blob([String.fromCharCode(0xFEFF),blob],{type: blob.type});}return blob;},FileSaver = function(blob,name,no_auto_bom){if(!no_auto_bom){blob = auto_bom(blob);} var filesaver = this,type = blob.type,force = type === force_saveable_type,object_url,dispatch_all = function(){dispatch(filesaver,"writestart progress write writeend".split(" "));},fs_error = function(){if((is_chrome_ios ||(force && is_safari))&& view.FileReader){ var reader = new FileReader();reader.onloadend = function(){var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/,'data:attachment/file;');var popup = view.open(url,'_blank');if(!popup)view.location.href = url;url=undefined; filesaver.readyState = filesaver.DONE;dispatch_all();};reader.readAsDataURL(blob);filesaver.readyState = filesaver.INIT;return;} if(!object_url){object_url = get_URL().createObjectURL(blob);}if(force){view.location.href = object_url;}else{var opened = view.open(object_url,"_blank");if(!opened){ view.location.href = object_url;}}filesaver.readyState = filesaver.DONE;dispatch_all();revoke(object_url);};filesaver.readyState = filesaver.INIT;if(can_use_save_link){object_url = get_URL().createObjectURL(blob);setTimeout(function(){save_link.href = object_url;save_link.download = name;click(save_link);dispatch_all();revoke(object_url);filesaver.readyState = filesaver.DONE;});return;}fs_error();},FS_proto = FileSaver.prototype,saveAs = function(blob,name,no_auto_bom){return new FileSaver(blob,name || blob.name || "download",no_auto_bom);}; if(typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob){return function(blob,name,no_auto_bom){name = name || blob.name || "download";if(!no_auto_bom){blob = auto_bom(blob);}return navigator.msSaveOrOpenBlob(blob,name);};}FS_proto.abort = function(){};FS_proto.readyState = FS_proto.INIT = 0;FS_proto.WRITING = 1;FS_proto.DONE = 2;FS_proto.error = FS_proto.onwritestart = FS_proto.onprogress = FS_proto.onwrite = FS_proto.onabort = FS_proto.onerror = FS_proto.onwriteend = null;return saveAs;}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content)); if(typeof module !== "undefined" && module.exports){module.exports.saveAs = saveAs;}else if((typeof define !== "undefined" && define !== null)&&(define.amd !== null)){define([],function(){return saveAs;});}</script>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
2026-03-24 12:40:25 +01:00
|
|
|
<h1>MicroPythonOS WebREPL</h1>
|
2026-03-24 11:14:33 +01:00
|
|
|
<div style="display:inline-block; vertical-align:top;">
|
|
|
|
|
<form>
|
|
|
|
|
<input type="text" name="webrepl_url" id="url" value="" />
|
|
|
|
|
<input type="submit" id="button" value="Connect" onclick="button_click(); return false;" />
|
|
|
|
|
</form>
|
|
|
|
|
<div id="term-wrapper">
|
|
|
|
|
<div id="term"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="file-boxes" style="display:inline-block; vertical-align:top; width:230px;">
|
|
|
|
|
|
|
|
|
|
<div class="file-box">
|
|
|
|
|
<strong>Send a file</strong>
|
|
|
|
|
<input type="file" id="put-file-select" />
|
|
|
|
|
<div id="put-file-list"></div>
|
|
|
|
|
<input type="button" value="Send to device" id="put-file-button" onclick="put_file(); return false;" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="file-box">
|
|
|
|
|
<strong>Get a file</strong>
|
|
|
|
|
<input type="text" name="get_filename" id="get_filename" value="" size="13" />
|
|
|
|
|
<input type="button" value="Get from device" onclick="get_file(); return false;" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="file-box" id="file-status"><span style="color:#707070">(file operation status)</span></div>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<br clear="both" />
|
|
|
|
|
<i>Terminal widget should be focused (text cursor visible) to accept input. Click on it if not.</i><br/>
|
|
|
|
|
<i>To paste, press Ctrl+A, then Ctrl+V</i>
|
|
|
|
|
</body>
|
|
|
|
|
|
|
|
|
|
<script>var term;var ws;var connected = false;var binary_state = 0;var put_file_name = null;var put_file_data = null;var get_file_name = null;var get_file_data = null;function calculate_size(win){var cols = Math.max(80,Math.min(150,(win.innerWidth - 280)/ 7))| 0;var rows = Math.max(24,Math.min(80,(win.innerHeight - 180)/ 12))| 0;return[cols,rows];}(function(){window.onload = function(){var url = window.location.hash.substring(1);if(!url){ url = document.location.host;}document.getElementById('url').value = 'ws://' + url;var size = calculate_size(self);term = new Terminal({cols: size[0],rows: size[1],useStyle: true,screenKeys: true,cursorBlink: false});term.open(document.getElementById("term"));show_https_warning();};window.addEventListener('resize',function(){var size = calculate_size(self);term.resize(size[0],size[1]);});}).call(this);function show_https_warning(){if(window.location.protocol == 'https:'){var warningDiv = document.createElement('div');warningDiv.style.cssText = 'background:#f99;padding:5px;margin-bottom:10px;line-height:1.5em;text-align:center';warningDiv.innerHTML =['The WebREPL client cannot be accessed over HTTPS connections.','Load the WebREPL client from the device instead.'].join('<br>');document.body.insertBefore(warningDiv,document.body.childNodes[0]);term.resize(term.cols,term.rows - 7);}}function button_click(){if(connected){ws.close();}else{document.getElementById('url').disabled = true;document.getElementById('button').value = "Disconnect";connected = true;connect(document.getElementById('url').value);}}function prepare_for_connect(){document.getElementById('url').disabled = false;document.getElementById('button').value = "Connect";}function update_file_status(s){document.getElementById('file-status').innerHTML = s;}function connect(url){var hostport = url.substring(5);if(hostport === document.location.host){hostport = '';}window.location.hash = hostport;ws = new WebSocket(url);ws.binaryType = 'arraybuffer';ws.onopen = function(){term.removeAllListeners('data');term.on('data',function(data){ data = data.replace(/\n/g,"\r");ws.send(data);});term.on('title',function(title){document.title = title;});term.focus();term.element.focus();term.write('\x1b[31mWelcome to MicroPython!\x1b[m\r\n');ws.onmessage = function(event){if(event.data instanceof ArrayBuffer){var data = new Uint8Array(event.data);switch(binary_state){case 11: if(decode_resp(data)== 0){ for(var offset = 0;offset < put_file_data.length;offset += 1024){ws.send(put_file_data.slice(offset,offset + 1024));}binary_state = 12;}break;case 12: if(decode_resp(data)== 0){update_file_status('Sent ' + put_file_name + ', ' + put_file_data.length + ' bytes');}else{update_file_status('Failed sending ' + put_file_name);}binary_state = 0;break;case 21: if(decode_resp(data)== 0){binary_state = 22;var rec = new Uint8Array(1);rec[0]= 0;ws.send(rec);}break;case 22:{ var sz = data[0]|(data[1]<< 8);if(data.length == 2 + sz){ if(sz == 0){ binary_state = 23;}else{ var new_buf = new Uint8Array(get_file_data.length + sz);new_buf.set(get_file_data);new_buf.set(data.slice(2),get_file_data.length);get_file_data = new_buf;update_file_status('Getting ' + get_file_name + ', ' + get_file_data.length + ' bytes');var rec = new Uint8Array(1);rec[0]= 0;ws.send(rec);}}else{binary_state = 0;}break;}case 23: if(decode_resp(data)== 0){update_file_status('Got ' + get_file_name + ', ' + get_file_data.length + ' bytes');saveAs(new Blob([get_file_data],{type: "application/octet-stream"}),get_file_name);}else{update_file_status('Failed getting ' + get_file_name);}binary_state = 0;break;case 31: console.log('GET_VER',data);binary_state = 0;break;}}term.write(event.data);};};ws.onclose = function(){connected = false;if(term){term.write('\x1b[31mDisconnected\x1b[m\r\n');}term.off('data');prepare_for_connect();}}function decode_resp(data){if(data[0]== 'W'.charCodeAt(0)&& data[1]== 'B'.charCodeAt(0)){var code = data[2]|(data[3]<< 8);return code;}else{return -1;}}function put_file(){var dest_fname = put_file_name;var dest_fsize = put_file_data.length; var rec = new Uint8Array(2 + 1 + 1
|
|
|
|
|
<script>(function(){var maxReadyAttempts = 90;var readyAttempts = 0;var resizeScheduled = false;var resizeDebounce = null;function get_term_container(){return document.getElementById('term');} function get_term_wrapper(){return document.getElementById('term-wrapper')|| get_term_container();} function calculate_dynamic_rows(){var wrapper = get_term_wrapper();var termContainer = get_term_container();if(!wrapper || !termContainer){return null;}var rect = wrapper.getBoundingClientRect();var height = rect.height || wrapper.clientHeight || window.innerHeight;var style = window.getComputedStyle(termContainer);var paddingTop = parseFloat(style.paddingTop)|| 0;var paddingBottom = parseFloat(style.paddingBottom)|| 0;var contentHeight = height - paddingTop - paddingBottom;return Math.max(24,Math.min(80,(contentHeight - 20)/ 12))| 0;}function resize_term_rows(){if(!window.term || !window.term.resize){return false;}var rows = calculate_dynamic_rows();if(rows === null){return false;}var cols = window.term.cols || 80;if(window.term.rows !== rows){window.term.resize(cols,rows);}return true;}function schedule_resize(){if(resizeScheduled){return;}resizeScheduled = true;window.requestAnimationFrame(function(){resizeScheduled = false;resize_term_rows();});}function schedule_resize_debounced(){if(resizeDebounce){window.clearTimeout(resizeDebounce);}resizeDebounce = window.setTimeout(function(){resizeDebounce = null;schedule_resize();},80);}function wait_for_term_ready(){if(window.term && window.term.resize){schedule_resize_debounced();if(resize_term_rows()){return;}}if(readyAttempts >= maxReadyAttempts){return;}readyAttempts += 1;window.requestAnimationFrame(wait_for_term_ready);}window.addEventListener('load',function(){wait_for_term_ready();schedule_resize_debounced();});window.addEventListener('resize',function(){schedule_resize_debounced();});}).call(this);</script>
|
|
|
|
|
|
|
|
|
|
</html>
|