diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 353b04608c06572a50f3f6537cb114477cc2de8c..68e240af5d71175d1f096781aa31ca65d75f45e8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -216,6 +216,8 @@ jobs: rsync -amv --include='*/' --include='*.html' + --include='*.css' + --include='*.mjs' --include='*.js' --include='*.wasm' --exclude='*' diff --git a/.gitignore b/.gitignore index 708338eced79c691e79b85eef5c0fb8138a0822d..735ed65b7c9f1967b928c55fc74971bc91004ca6 100644 --- a/.gitignore +++ b/.gitignore @@ -34,14 +34,16 @@ !doc/**/*.md # examples directory: -!examples/**/*.txt !examples/**/*.cpp +!examples/**/*.css !examples/**/*.hpp -!examples/**/*.ipp !examples/**/*.html -!examples/**/*.py -!examples/**/*.js !examples/**/*.html.disabled +!examples/**/*.ipp +!examples/**/*.js +!examples/**/*.mjs +!examples/**/*.py +!examples/**/*.txt # include directory: !include/ftxui/**/*.hpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index dbea321e13e773e69807817c1d31f8a96757acdb..953efa98ad52c75b06a2329ca90bdb1fdb437651 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -21,6 +21,8 @@ if (EMSCRIPTEN) get_property(EXAMPLES GLOBAL PROPERTY FTXUI::EXAMPLES) foreach(file "index.html" + "index.mjs" + "index.css" "sw.js" "run_webassembly.py") configure_file(${file} ${file}) diff --git a/examples/index.css b/examples/index.css new file mode 100644 index 0000000000000000000000000000000000000000..32b44750f62a1f25c8d6c8072385e699dbd5c628 --- /dev/null +++ b/examples/index.css @@ -0,0 +1,107 @@ +@import url(https://fonts.googleapis.com/css?family=Khula:700); + +body { + background-color:#EEE; + padding:0px; + margin:0px; + font-family: Khula, Helvetica, sans-serif; + font-size: 130%; +} + +.page { + max-width:1300px; + margin: auto; + padding: 10px; +} + +a { + box-shadow: inset 0 0 0 0 #54b3d6; + color: #0087b9; + margin: 0 -.25rem; + padding: 0 .25rem; + transition: color .3s ease-in-out, + box-shadow .3s ease-in-out; +} + +a:hover { + box-shadow: inset 120px 0 0 0 #54b3d6; + color: white; +} + +h1 { + text-decoration: underline; + width:100%; + background-color: rgba(100,100,255,0.5); + padding: 10px; + margin: 0; +} + + +#selectExample { + flex:1; +} + +#selectExample, #selectExample option { + font-size: 16px; + font-family: sans-serif; + font-weight: 700; + line-height: 1.3; + border:0px; + background-color: #bbb; + color:black; +} + +#selectExample:focus { + outline:none; +} + +#terminal { + width:100%; + height 500px; + height: calc(clamp(200px, 100vh - 300px, 900px)); + overflow: hidden; + border:none; + background-color:black; +} + +#terminalContainer { + overflow: hidden; + border-radius: 10px; + box-shadow: 0px 2px 10px 0px rgba(0,0,0,0.75), + 0px 2px 80px 0px rgba(0,0,0,0.50); +} + +.fakeButtons { + height: 10px; + width: 10px; + border-radius: 50%; + border: 1px solid #000; + margin:6px; + background-color: #ff3b47; + border-color: #9d252b; + display: inline-block; +} + +.fakeMinimize { + left: 11px; + background-color: #ffc100; + border-color: #9d802c; +} + +.fakeZoom { + left: 16px; + background-color: #00d742; + border-color: #049931; +} + +.fakeMenu { + display:flex; + flex-direction: row; + width:100%; + box-sizing: border-box; + height: 25px; + background-color: #bbb; + color:black; + margin: 0 auto; + overflow: hidden; +} diff --git a/examples/index.html b/examples/index.html index a5e0a48644342d367ad11813fcbe876388022c47..8e5498c31adeeba9c0fe7a7b1865f4f11304ca7b 100644 --- a/examples/index.html +++ b/examples/index.html @@ -1,173 +1,32 @@ -<!DOCTYPE html> <html lang="en"> +<!DOCTYPE html> +<html lang="en"> <head> <meta charset="utf-8"> <title>FTXUI examples WebAssembly</title> - <script src="https://cdn.jsdelivr.net/npm/xterm@4.18.0/lib/xterm.min.js"></script> - <script src="https://cdn.jsdelivr.net/npm/xterm-addon-webgl@0.11.4/lib/xterm-addon-webgl.min.js"></script> - <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.5.0/lib/xterm-addon-fit.min.js"></script> + <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>➡️</text></svg>"> + <link rel="stylesheet" href="index.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@4.11.0/css/xterm.css"></link> - <!--Add COOP/COEP via a ServiceWorker to use SharedArrayBuffer--> - <script> - if ("serviceWorker" in navigator && !window.crossOriginIsolated) { - navigator.serviceWorker.register(new URL("./sw.js", location.href)).then( - registration => { - if (registration.active && !navigator.serviceWorker.controller) { - window.location.reload(); - } - }, - ); - } - </script> + <script type="module" src="index.mjs"></script> </head> <body> <script id="example_script"></script> + <div class="page"> - <h1>FTXUI WebAssembly Example </h1> - <p> - <a href="https://github.com/ArthurSonzogni/FTXUI">FTXUI</a> is a single - C++ library for terminal user interface. - </p> <p> - On this page, you can try all the examples contained in: <a - href="https://github.com/ArthurSonzogni/FTXUI/tree/master/examples">./example/</a> - Those are compiled using WebAssembly. + <a href="https://github.com/ArthurSonzogni/FTXUI">FTXUI</a> is a simple + functional C++ library for terminal user interface. <br/> + This showcase the: <a href="https://github.com/ArthurSonzogni/FTXUI/tree/master/examples">./example/</a> folder. <br/> </p> - <select id="selectExample"></select> - <div id="terminal"></div> + + <div id="terminalContainer"> + <div class="fakeMenu"> + <div class="fakeButtons fakeClose"></div> + <div class="fakeButtons fakeMinimize"></div> + <div class="fakeButtons fakeZoom"></div> + <select id="selectExample"></select> + </div> + <div id="terminal"></div> + </div> </div> </body> - <script> - const example_list = "@EXAMPLES@".split(";"); - - const url_search_params = new URLSearchParams(window.location.search); - const example = url_search_params.get("file") || "dom/color_gallery"; - const select = document.getElementById("selectExample"); - - for(var i = 0; i < example_list.length; i++) { - var opt = example_list[i]; - var el = document.createElement("option"); - el.textContent = opt; - el.value = opt; - select.appendChild(el); - } - select.selectedIndex = example_list.findIndex(path => path == example) || 0; - select.addEventListener("change", () => { - location.href = (location.href).split('?')[0] + "?file=" + - example_list[select.selectedIndex]; - }); - - let stdin_buffer = []; - const stdin = () => { - return stdin_buffer.shift() || 0; - } - - let stdout_buffer = []; - const stdout = code => { - if (code == 0) { - term.write(new Uint8Array(stdout_buffer)); - stdout_buffer = []; - } else { - stdout_buffer.push(code) - } - } - let stderrbuffer = []; - const stderr = code => { - if (code == 0 || code == 10) { - console.error(String.fromCodePoint(...stderrbuffer)); - stderrbuffer = []; - } else { - stderrbuffer.push(code) - } - } - const term = new Terminal(); - const term_element = document.querySelector('#terminal'); - term.open(term_element); - - const webgl_addon = new (WebglAddon.WebglAddon)(); - term.loadAddon(webgl_addon); - - const onBinary = e => { - for(c of e) - stdin_buffer.push(c.charCodeAt(0)); - } - term.onBinary(onBinary); - term.onData(onBinary) - term.resize(140,43); - window.Module = { - preRun: () => { - FS.init(stdin, stdout, stderr); - }, - postRun: [], - onRuntimeInitialized: () => { - if (window.Module._ftxui_on_resize == undefined) - return; - - const fit_addon = new (FitAddon.FitAddon)(); - term.loadAddon(fit_addon); - fit_addon.fit(); - const resize_handler = () => { - const {cols, rows} = fit_addon.proposeDimensions(); - term.resize(cols, rows); - window.Module._ftxui_on_resize(cols, rows); - }; - const resize_observer = new ResizeObserver(resize_handler); - resize_observer.observe(term_element); - resize_handler(); - - // Disable scrollbar - term.write('\x1b[?47h') - }, - }; - - const words = example.split('/') - words[1] = "ftxui_example_" + words[1] + ".js" - document.querySelector("#example_script").src = words.join('/'); - </script> - - <style> - - body { - background-color:#EEE; - padding:20px; - font-family: Helvetica, sans-serif; - font-size: 130%; - } - - .page { - max-width:1300px; - margin: auto; - } - - h1 { - text-decoration: underline; - } - - select { - display:block; - padding: .6em 1.4em .5em .8em; - border-radius: 20px 20px 0px 0px; - font-size: 16px; - font-family: sans-serif; - font-weight: 700; - - color: #444; - line-height: 1.3; - background-color:black; - border:0px; - color:white; - transition: color 0.2s linear; - transition: background-color 0.2s linear; - } - - #terminal { - width:100%; - height: 500px; - height: calc(clamp(200px, 100vh - 300px, 900px)); - overflow: hidden; - border:none; - padding:auto; - } - - </style> - </html> diff --git a/examples/index.mjs b/examples/index.mjs new file mode 100644 index 0000000000000000000000000000000000000000..722b224471feb22837a571139a02019c2b868a16 --- /dev/null +++ b/examples/index.mjs @@ -0,0 +1,102 @@ +import xterm from 'https://cdn.jsdelivr.net/npm/xterm@4.18.0/+esm' +import xterm_addon_webgl from 'https://cdn.jsdelivr.net/npm/xterm-addon-webgl@0.11.4/+esm' +import xterm_addon_fit from 'https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.5.0/+esm' + +// Add COOP/COEP via a ServiceWorker to use SharedArrayBuffer +if ("serviceWorker" in navigator && !window.crossOriginIsolated) { + const url_sw = new URL("./sw.js", location.href); + const registration = await navigator.serviceWorker.register(url_sw); + if (registration.active && !navigator.serviceWorker.controller) { + window.location.reload(); // Reload to ensure the COOP/COEP headers are set. + } +} + +const example_list = "@EXAMPLES@".split(";"); +const url_search_params = new URLSearchParams(window.location.search); + +const select = document.getElementById("selectExample"); +for(const example of example_list) { + const option = document.createElement("option"); + option.textContent = example; + option.value = example; + select.appendChild(option); +} +const example = url_search_params.get("file") || "dom/color_gallery"; +select.selectedIndex = example_list.findIndex(path => path == example) || 0; +select.addEventListener("change", () => { + history.pushState({}, "", "?file=" + example_list[select.selectedIndex]); + location.reload(); +}); + +const term_element = document.querySelector('#terminal'); +const term = new xterm.Terminal(); +term.options.scrollback = 0; +term.open(term_element); +const fit_addon = new xterm_addon_fit.FitAddon(); +const webgl_addon = new xterm_addon_webgl.WebglAddon(); +term.loadAddon(webgl_addon); +term.loadAddon(fit_addon); + +const stdin_buffer = []; +const stdout_buffer = []; +const stderr_buffer = []; + +const stdin = () => { + return stdin_buffer.shift() || 0; +} + +const stdout = code => { + if (code == 0) { + term.write(new Uint8Array(stdout_buffer)); + stdout_buffer.length = 0; + } else { + stdout_buffer.push(code) + } +} + +const stderr = code => { + if (code == 0 || code == 10) { + console.error(String.fromCodePoint(...stderr_buffer)); + stderr_buffer = []; + } else { + stderr_buffer.push(code) + } +} + +const onBinary = e => { + for(const c of e) + stdin_buffer.push(c.charCodeAt(0)); +} + +term.onBinary(onBinary); +term.onData(onBinary) +term.resize(140,43); + +window.Module = { + preRun: () => { + FS.init(stdin, stdout, stderr); + }, + postRun: [], + onRuntimeInitialized: () => { + if (window.Module._ftxui_on_resize == undefined) + return; + fit_addon.fit(); + + const resize_handler = () => { + const {cols, rows} = fit_addon.proposeDimensions(); + term.resize(cols, rows); + window.Module._ftxui_on_resize(cols, rows); + fit_addon.fit(); + }; + const resize_observer = new ResizeObserver(resize_handler); + resize_observer.observe(term_element); + resize_handler(); + + // Disable scrollbar + //term.write('\x1b[?47h') + }, +}; + +const words = example.split('/') +words[1] = "ftxui_example_" + words[1] + ".js" +document.querySelector("#example_script").src = words.join('/');