|
13 | 13 | <link rel="preconnect" href="https://fonts.googleapis.com"> |
14 | 14 | <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
15 | 15 | <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Space+Grotesk:wght@500;600;700&display=swap"> |
| 16 | + <link rel="stylesheet" href="/vendor/xterm/xterm.css"> |
16 | 17 | <link rel="stylesheet" href="simulator.css"> |
17 | | - <!-- LZ-String for compressed URL hash sharing --> |
18 | | - <script src="https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.5.0/lz-string.min.js"></script> |
19 | | - <!-- Monaco Editor AMD Loader --> |
20 | | - <script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs/loader.js"></script> |
| 18 | + <!-- Monaco Editor AMD Loader, vendored so a cold offline install still has |
| 19 | + an editor. Google Fonts above is now the only cross-origin load, and it |
| 20 | + falls back to the local stacks when it fails. --> |
| 21 | + <script src="/vendor/monaco/vs/loader.js"></script> |
21 | 22 | </head> |
22 | 23 | <body> |
23 | 24 | <!-- Top Global App Bar --> |
|
52 | 53 | <!-- Resolution & Shape Selector --> |
53 | 54 | <div class="sim-select-wrap"> |
54 | 55 | <select id="resolution-select" aria-label="Select Screen Resolution"> |
55 | | - <option value="320x240" selected>320 × 240 (QVGA Landscape)</option> |
56 | | - <option value="240x320">240 × 320 (QVGA Portrait)</option> |
57 | | - <option value="240x240-square">240 × 240 (Square Watch)</option> |
58 | | - <option value="240x240-round">240 × 240 (Round Watch)</option> |
59 | | - <option value="480x320">480 × 320 (HVGA Landscape)</option> |
60 | | - <option value="320x480">320 × 480 (HVGA Portrait)</option> |
61 | | - <option value="800x480">800 × 480 (WVGA Landscape)</option> |
| 56 | + <option value="320x240" selected>320 × 240</option> |
| 57 | + <option value="240x320">240 × 320</option> |
| 58 | + <!-- Both 240 × 240 entries would read identically without the |
| 59 | + one word that actually distinguishes them. --> |
| 60 | + <option value="240x240-square">240 × 240</option> |
| 61 | + <option value="240x240-round">240 × 240 round</option> |
| 62 | + <option value="480x320">480 × 320</option> |
| 63 | + <option value="320x480">320 × 480</option> |
| 64 | + <option value="800x480">800 × 480</option> |
62 | 65 | <option value="custom">Custom...</option> |
63 | 66 | </select> |
64 | 67 | </div> |
65 | 68 |
|
66 | 69 | </div> |
67 | 70 |
|
68 | 71 | <div class="sim-header-right"> |
69 | | - <button type="button" class="sim-btn" id="enable-audio">Enable Audio</button> |
70 | | - <button type="button" class="sim-btn" id="enable-microphone">Enable Microphone</button> |
71 | | - <button type="button" class="sim-btn" id="refresh-application" title="Clear the simulator cache and reload the latest version"> |
72 | | - <span>Refresh app</span> |
73 | | - </button> |
74 | 72 | <!-- Run Editor Button --> |
75 | 73 | <button type="button" class="sim-btn sim-btn-primary" id="run-btn" title="Run editor in the REPL globals (Ctrl+Enter)"> |
76 | 74 | <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m8 5 11 7-11 7z"/></svg> |
77 | 75 | <span>RUN</span> |
78 | 76 | <kbd>Ctrl+↵</kbd> |
79 | 77 | </button> |
80 | 78 |
|
| 79 | + <button type="button" class="sim-btn" id="stop-btn" title="Interrupt the running program (Ctrl+C)" disabled> |
| 80 | + <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="12" height="12" rx="2"/></svg> |
| 81 | + <span>Stop</span> |
| 82 | + <kbd>Ctrl+C</kbd> |
| 83 | + </button> |
| 84 | + |
81 | 85 | <button type="button" class="sim-btn" id="reset-vm" title="Reset the MicroPython virtual machine"> |
82 | 86 | <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg> |
83 | 87 | <span>Reset VM</span> |
84 | 88 | </button> |
85 | 89 |
|
86 | | - <!-- Share URL Button --> |
87 | | - <button type="button" class="sim-btn" id="share-btn" title="Share Interactive Code Link"> |
88 | | - <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg> |
89 | | - <span>Share</span> |
| 90 | + <!-- Saved Programs --> |
| 91 | + <button type="button" class="sim-btn" id="save-btn" title="Save this program in the browser or download it (Ctrl+S)"> |
| 92 | + <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg> |
| 93 | + <span>Save</span> |
| 94 | + <kbd>Ctrl+S</kbd> |
90 | 95 | </button> |
91 | 96 |
|
92 | 97 | <!-- Theme Toggle --> |
93 | 98 | <button type="button" class="sim-btn sim-icon-btn" id="theme-toggle" aria-label="Toggle Color Theme" title="Toggle Light/Dark Theme"> |
94 | 99 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg> |
95 | 100 | </button> |
| 101 | + |
| 102 | + <!-- Overflow menu: everything that is not part of the edit/run loop --> |
| 103 | + <div class="sim-menu-wrap"> |
| 104 | + <button type="button" class="sim-btn sim-icon-btn" id="more-menu-btn" |
| 105 | + aria-label="More actions" title="More actions" |
| 106 | + aria-haspopup="menu" aria-expanded="false" aria-controls="more-menu"> |
| 107 | + <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg> |
| 108 | + </button> |
| 109 | + <div class="sim-menu" id="more-menu" role="menu" hidden> |
| 110 | + <button type="button" class="sim-menu-item" role="menuitem" id="enable-audio"> |
| 111 | + <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M15.5 8.5a5 5 0 0 1 0 7"/></svg> |
| 112 | + <span>Enable Audio</span> |
| 113 | + </button> |
| 114 | + <button type="button" class="sim-menu-item" role="menuitem" id="enable-microphone"> |
| 115 | + <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="2" width="6" height="11" rx="3"/><path d="M5 10a7 7 0 0 0 14 0M12 17v5"/></svg> |
| 116 | + <span>Enable Microphone</span> |
| 117 | + </button> |
| 118 | + <hr class="sim-menu-sep"> |
| 119 | + <button type="button" class="sim-menu-item" role="menuitem" id="refresh-application" |
| 120 | + title="Clear the simulator cache and reload the latest version"> |
| 121 | + <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 2v6h-6"/><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M3 22v-6h6"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/></svg> |
| 122 | + <span>Refresh app</span> |
| 123 | + </button> |
| 124 | + </div> |
| 125 | + </div> |
96 | 126 | </div> |
97 | 127 | </header> |
98 | 128 |
|
|
107 | 137 | <!-- Left Pane: Monaco Code Editor --> |
108 | 138 | <section class="sim-editor-pane" id="editor-pane" aria-label="Code Editor"> |
109 | 139 | <div class="sim-pane-header"> |
110 | | - <div class="sim-tab"> |
111 | | - <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg> |
112 | | - <span>main.py</span> |
113 | | - </div> |
| 140 | + <!-- simulator.js renders one tab per file; main.py is the entry |
| 141 | + point and cannot be renamed or removed. --> |
| 142 | + <div class="sim-tabs" id="file-tabs" role="tablist" aria-label="Program files"></div> |
| 143 | + <button type="button" class="sim-tab-add" id="add-file-btn" title="New file" aria-label="New file"> |
| 144 | + <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg> |
| 145 | + </button> |
114 | 146 | <div class="sim-status-indicator"> |
115 | 147 | <span class="sim-status-dot is-ready" id="status-dot"></span> |
116 | 148 | <span id="status-text">Ready</span> |
|
126 | 158 | <section class="sim-preview-pane" id="preview-pane" aria-label="Device Preview and Console"> |
127 | 159 | <!-- Top Sub-Pane: Hardware Canvas Bezel Stage --> |
128 | 160 | <div class="sim-device-stage" id="device-stage"> |
| 161 | + <!-- Shown when the display size changes after a run: the panel the |
| 162 | + program already created keeps the size it booted with. --> |
| 163 | + <div class="sim-stage-notice" id="stage-notice" role="status" hidden> |
| 164 | + <span>Display size changed — reset the VM to apply it.</span> |
| 165 | + <button type="button" class="sim-btn sim-btn-primary" id="stage-notice-reset">Reset VM</button> |
| 166 | + <button type="button" class="sim-stage-notice-dismiss" id="stage-notice-dismiss" aria-label="Dismiss">×</button> |
| 167 | + </div> |
129 | 168 | <div class="sim-device-bezel" id="device-bezel"> |
130 | 169 | <canvas id="display_canvas" width="320" height="240" tabindex="0"></canvas> |
131 | 170 | <canvas id="aux_canvas" width="1" height="1" tabindex="0"></canvas> |
|
146 | 185 | <svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg> |
147 | 186 | </button> |
148 | 187 | </div> |
149 | | - <div class="sim-console-body sim-terminal-body" id="console-log"> |
150 | | - <!-- simulator.js mounts the direct MicroPython REPL here. --> |
| 188 | + <div class="sim-terminal-body" id="console-log"> |
| 189 | + <!-- simulator.js mounts the xterm-backed MicroPython REPL here. --> |
151 | 190 | </div> |
152 | 191 | </section> |
153 | 192 | </section> |
154 | 193 | </main> |
155 | 194 |
|
| 195 | + <!-- Custom resolution entry. A <dialog> rather than prompt(), which is |
| 196 | + blocked outright in some standalone-PWA contexts. --> |
| 197 | + <dialog class="sim-dialog" id="custom-res-dialog" aria-label="Custom display size"> |
| 198 | + <form id="custom-res-form" method="dialog"> |
| 199 | + <h2 class="sim-dialog-title">Custom display size</h2> |
| 200 | + <div class="sim-dialog-fields"> |
| 201 | + <label>Width |
| 202 | + <input type="number" id="custom-res-w" min="16" max="2048" step="1" required> |
| 203 | + </label> |
| 204 | + <label>Height |
| 205 | + <input type="number" id="custom-res-h" min="16" max="2048" step="1" required> |
| 206 | + </label> |
| 207 | + </div> |
| 208 | + <p class="sim-dialog-hint">Reset the VM after applying so the running program picks up the new size.</p> |
| 209 | + <div class="sim-dialog-actions"> |
| 210 | + <button type="button" class="sim-btn" id="custom-res-cancel">Cancel</button> |
| 211 | + <button type="submit" class="sim-btn sim-btn-primary">Apply</button> |
| 212 | + </div> |
| 213 | + </form> |
| 214 | + </dialog> |
| 215 | + |
| 216 | + <!-- New file / rename. Flat .py names only, so `import helper` resolves |
| 217 | + against the working directory the runtime already has on sys.path. --> |
| 218 | + <dialog class="sim-dialog" id="file-name-dialog" aria-label="File name"> |
| 219 | + <form id="file-name-form" method="dialog"> |
| 220 | + <h2 class="sim-dialog-title" id="file-name-title">New file</h2> |
| 221 | + <div class="sim-dialog-fields"> |
| 222 | + <label>File name |
| 223 | + <input type="text" id="file-name-input" required autocomplete="off" |
| 224 | + spellcheck="false" placeholder="helper.py"> |
| 225 | + </label> |
| 226 | + </div> |
| 227 | + <p class="sim-dialog-hint" id="file-name-error" hidden></p> |
| 228 | + <div class="sim-dialog-actions"> |
| 229 | + <button type="button" class="sim-btn" id="file-name-cancel">Cancel</button> |
| 230 | + <button type="submit" class="sim-btn sim-btn-primary" id="file-name-ok">Create</button> |
| 231 | + </div> |
| 232 | + </form> |
| 233 | + </dialog> |
| 234 | + |
| 235 | + <!-- Saved programs. Everything lives in this browser; the download link is |
| 236 | + the way a program leaves it. There is no upload counterpart -- paste |
| 237 | + code straight into the editor instead. --> |
| 238 | + <dialog class="sim-dialog sim-dialog-wide" id="save-dialog" aria-label="Saved programs"> |
| 239 | + <h2 class="sim-dialog-title">Saved programs</h2> |
| 240 | + <form id="save-form" method="dialog"> |
| 241 | + <div class="sim-save-row"> |
| 242 | + <label class="sim-save-name">Name |
| 243 | + <input type="text" id="save-name" maxlength="60" required |
| 244 | + autocomplete="off" spellcheck="false" placeholder="my-program"> |
| 245 | + </label> |
| 246 | + <button type="submit" class="sim-btn sim-btn-primary">Save</button> |
| 247 | + </div> |
| 248 | + </form> |
| 249 | + |
| 250 | + <ul class="sim-save-list" id="save-list"></ul> |
| 251 | + <p class="sim-save-empty" id="save-empty">Nothing saved yet — saved programs are kept in this browser.</p> |
| 252 | + |
| 253 | + <div class="sim-dialog-actions"> |
| 254 | + <a class="sim-btn" id="download-py" download="main.py" href="#">Download .py</a> |
| 255 | + <button type="button" class="sim-btn" id="save-close">Close</button> |
| 256 | + </div> |
| 257 | + </dialog> |
| 258 | + |
156 | 259 | <!-- Toast Notification --> |
157 | | - <div class="sim-toast" id="sim-toast">✓ Link copied to clipboard!</div> |
| 260 | + <div class="sim-toast" id="sim-toast">✓ Saved</div> |
158 | 261 |
|
159 | 262 | <!-- Simulator Application Scripts --> |
160 | 263 | <script src="./templates.js"></script> |
|
0 commit comments