|
185 | 185 | rel="stylesheet" |
186 | 186 | href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" |
187 | 187 | /> |
| 188 | + <!-- xterm.js --> |
| 189 | + <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script> |
| 190 | + <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.7.0/lib/xterm-addon-fit.min.js"></script> |
| 191 | + <link |
| 192 | + rel="stylesheet" |
| 193 | + href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" |
| 194 | + /> |
188 | 195 | <link id="light-theme" rel="stylesheet" href="css/styles.css" /> |
189 | 196 | <link id="dark-theme" rel="stylesheet" href="css/styledark.css" disabled /> |
190 | 197 | <link rel="icon" href="/img/favicon.png" /> |
|
238 | 245 | <button id="executeButton" class="icon-button" title="Execute"> |
239 | 246 | <i class="fas fa-play"></i> |
240 | 247 | </button> |
| 248 | + <button id="terminalButton" class="icon-button" title="Open Terminal"> |
| 249 | + <i class="fas fa-terminal"></i> |
| 250 | + </button> |
241 | 251 | <form |
242 | 252 | id="saveWorkspaceForm" |
243 | 253 | action="/saveWorkspace" |
|
286 | 296 | <div id="resultsText" style="user-select: text"> |
287 | 297 | Results will appear here... |
288 | 298 | </div> |
289 | | - |
290 | | - <button |
291 | | - id="copyButton" |
292 | | - class="btn btn-light" |
293 | | - onclick="showCopiedMessage()" |
294 | | - > |
295 | | - <i class="fas fa-copy"></i> |
296 | | - <span |
297 | | - id="copiedMsg" |
298 | | - style="display: none; color: green; margin-left: 10px" |
299 | | - >Copied!</span |
| 299 | + <div id="resultsButtons"> |
| 300 | + <!-- |
| 301 | + <button id="pasteToTerminalButton" class="btn btn-light"> |
| 302 | + <i class="fas fa-paste"></i> |
| 303 | + </button> |
| 304 | + --> |
| 305 | + <button |
| 306 | + id="copyButton" |
| 307 | + class="btn btn-light" |
| 308 | + onclick="showCopiedMessage()" |
300 | 309 | > |
301 | | - </button> |
| 310 | + <i class="fas fa-copy"></i> |
| 311 | + <span |
| 312 | + id="copiedMsg" |
| 313 | + style="display: none; color: green; margin-left: 10px" |
| 314 | + >Copied!</span |
| 315 | + > |
| 316 | + </button> |
| 317 | + </div> |
302 | 318 | </div> |
303 | 319 | </div> |
| 320 | + <div id="terminalWrapper" style="display: none"> |
| 321 | + <div id="terminalResizer"></div> |
| 322 | + <div id="terminalContainer"></div> |
| 323 | + </div> |
304 | 324 |
|
305 | 325 | <script> |
306 | 326 | const baseBlockStyles = { |
|
787 | 807 | ).weight = 3; |
788 | 808 | </script> |
789 | 809 | <script src="js/app.js"></script> |
| 810 | + <script> |
| 811 | + document.addEventListener('DOMContentLoaded', function () { |
| 812 | + const terminalWrapper = document.getElementById('terminalWrapper'); |
| 813 | + const terminalContainer = document.getElementById('terminalContainer'); |
| 814 | + const terminalResizer = document.getElementById('terminalResizer'); |
| 815 | + const terminalButton = document.getElementById('terminalButton'); |
| 816 | + |
| 817 | + let term = null; // Create later on click |
| 818 | + |
| 819 | + terminalWrapper.style.width = '100%'; |
| 820 | + terminalWrapper.style.height = '25vh'; |
| 821 | + terminalWrapper.style.backgroundColor = '#252526'; |
| 822 | + terminalWrapper.style.border = '1px solid #555'; |
| 823 | + terminalWrapper.style.borderRadius = '8px'; |
| 824 | + terminalWrapper.style.marginTop = '10px'; |
| 825 | + terminalWrapper.style.display = 'none'; // hidden initially |
| 826 | + terminalWrapper.style.flexDirection = 'column'; |
| 827 | + terminalWrapper.style.overflow = 'hidden'; |
| 828 | + |
| 829 | + terminalContainer.style.width = '100%'; |
| 830 | + terminalContainer.style.height = '100%'; |
| 831 | + terminalContainer.style.padding = '9px'; |
| 832 | + terminalContainer.style.overflowX = 'auto'; |
| 833 | + terminalContainer.style.overflowY = 'auto'; |
| 834 | + terminalContainer.style.boxSizing = 'border-box'; |
| 835 | + |
| 836 | + terminalResizer.style.height = '2px'; |
| 837 | + terminalResizer.style.background = '#333'; |
| 838 | + terminalResizer.style.cursor = 'ns-resize'; |
| 839 | + |
| 840 | + function checkTerminalServer(callback) { |
| 841 | + const testSocket = new WebSocket('ws://localhost:5000'); |
| 842 | + |
| 843 | + testSocket.onopen = () => { |
| 844 | + testSocket.close(); |
| 845 | + callback(true); |
| 846 | + }; |
| 847 | + |
| 848 | + testSocket.onerror = () => { |
| 849 | + callback(false); |
| 850 | + }; |
| 851 | + } |
| 852 | + //const pasteButton = document.getElementById('pasteToTerminalButton'); |
| 853 | + //pasteButton.addEventListener('click', function () { |
| 854 | + // if (!term || !ws || ws.readyState !== WebSocket.OPEN) { |
| 855 | + // alert('Terminal not initialized or socket disconnected.'); |
| 856 | + // return; |
| 857 | + // } |
| 858 | + |
| 859 | + // const text = document.getElementById('resultsText').textContent.trim(); |
| 860 | + // if (text.length === 0) { |
| 861 | + // alert('No command to send.'); |
| 862 | + // return; |
| 863 | + // } |
| 864 | + |
| 865 | + // ws.send(text + '\r'); // <- this simulates real terminal input, including "Enter" |
| 866 | + //}); |
| 867 | + |
| 868 | + terminalButton.addEventListener('click', function () { |
| 869 | + checkTerminalServer((isRunning) => { |
| 870 | + if (!isRunning) { |
| 871 | + if ( |
| 872 | + confirm( |
| 873 | + 'Terminal server is not running.\nWould you like to install it now?\n\n1. Open your terminal\n2. Paste the command\n3. Press Enter\n\nP.S If you have done this before, you just need to go back to the dir you did it and write ./install-ublocks.sh' |
| 874 | + ) |
| 875 | + ) { |
| 876 | + const installCommand = `curl -L -o install-ublocks.sh "https://www.dropbox.com/scl/fi/3t2aztovfbrd1xftcg4vq/install-ublocks.sh?rlkey=5i58ocs4mg3zhb1vlhnw9fz5h&st=gpsnmg1u&dl=1" && chmod +x install-ublocks.sh && ./install-ublocks.sh`; |
| 877 | + navigator.clipboard |
| 878 | + .writeText(installCommand) |
| 879 | + .then(() => { |
| 880 | + alert( |
| 881 | + '✅ Command copied to clipboard!\n\nNow open your terminal and paste it.' |
| 882 | + ); |
| 883 | + }) |
| 884 | + .catch(() => { |
| 885 | + alert( |
| 886 | + 'Here is the command to copy manually:\n\n' + |
| 887 | + installCommand |
| 888 | + ); |
| 889 | + }); |
| 890 | + } |
| 891 | + return; |
| 892 | + } |
| 893 | + |
| 894 | + if (terminalWrapper.style.display === 'none') { |
| 895 | + terminalWrapper.style.display = 'flex'; |
| 896 | + |
| 897 | + if (!term) { |
| 898 | + term = new Terminal({ |
| 899 | + cursorBlink: true, |
| 900 | + scrollback: 1000, |
| 901 | + scrollOnUserInput: true, |
| 902 | + scrollOnOutput: true, |
| 903 | + rendererType: 'canvas', |
| 904 | + wordWrap: true, |
| 905 | + theme: { |
| 906 | + background: '#000000', |
| 907 | + foreground: '#ffffff' |
| 908 | + } |
| 909 | + }); |
| 910 | + |
| 911 | + term.open(terminalContainer); |
| 912 | + term.focus(); |
| 913 | + |
| 914 | + const adjustedWidth = terminalContainer.clientWidth - 18; |
| 915 | + const adjustedHeight = terminalContainer.clientHeight - 18; |
| 916 | + term.resize( |
| 917 | + Math.floor(adjustedWidth / 9), |
| 918 | + Math.floor(adjustedHeight / 17) |
| 919 | + ); |
| 920 | + term.refresh(0, term.rows - 1); |
| 921 | + |
| 922 | + const socket = new WebSocket('ws://localhost:5000'); |
| 923 | + |
| 924 | + socket.onopen = () => { |
| 925 | + term.write('Connected to Ublocks Terminal\r\n'); |
| 926 | + term.onData((data) => socket.send(data)); |
| 927 | + }; |
| 928 | + |
| 929 | + socket.onmessage = (e) => { |
| 930 | + term.write(e.data); |
| 931 | + setTimeout(() => { |
| 932 | + term.scrollToBottom(); |
| 933 | + terminalContainer.scrollLeft = |
| 934 | + terminalContainer.scrollWidth; |
| 935 | + }, 10); |
| 936 | + }; |
| 937 | + |
| 938 | + term.setOption('scrollOnUserInput', true); |
| 939 | + term.setOption('scrollOnOutput', true); |
| 940 | + } |
| 941 | + } else { |
| 942 | + terminalWrapper.style.display = 'none'; |
| 943 | + } |
| 944 | + }); |
| 945 | + }); |
| 946 | + |
| 947 | + let isResizing = false; |
| 948 | + |
| 949 | + terminalResizer.addEventListener('mousedown', function (e) { |
| 950 | + isResizing = true; |
| 951 | + }); |
| 952 | + |
| 953 | + document.addEventListener('mousemove', function (e) { |
| 954 | + if (!isResizing) return; |
| 955 | + const newHeight = window.innerHeight - e.clientY; |
| 956 | + terminalWrapper.style.height = newHeight + 'px'; |
| 957 | + |
| 958 | + if (term) { |
| 959 | + setTimeout(() => { |
| 960 | + const adjustedWidth = terminalContainer.clientWidth - 18; |
| 961 | + const adjustedHeight = terminalContainer.clientHeight - 18; |
| 962 | + term.resize( |
| 963 | + Math.floor(adjustedWidth / 9), |
| 964 | + Math.floor(adjustedHeight / 17) |
| 965 | + ); |
| 966 | + }, 50); |
| 967 | + } |
| 968 | + }); |
| 969 | + |
| 970 | + document.addEventListener('mouseup', function (e) { |
| 971 | + isResizing = false; |
| 972 | + }); |
| 973 | + }); |
| 974 | + </script> |
790 | 975 | </body> |
791 | 976 | </html> |
0 commit comments