Skip to content

Commit 2cf09be

Browse files
bajrangCoderclaude
andcommitted
feat: show a spinner while Python loads or code runs
Adds Acode's header loader to the console page so it is clear whether a program is still running or has finished. It hides while the program is waiting for input(). Also drops the tooltip and active state from the wrap toggle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 6bf0719 commit 2cf09be

4 files changed

Lines changed: 36 additions & 17 deletions

File tree

‎changelog.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Errors written to stderr are now shown in red
88
- Console output can now be selected and copied
99
- Header button to toggle line wrap in the console (remembered between sessions)
10+
- Loading indicator in the console header while Python loads or code is running
1011
- Fixed Python failing to load (or staying on "loading...") on some devices
1112
- Clear error message when Python fails to load, with automatic retry on the next run
1213
- Smaller plugin size (removed stale Pyodide files)

‎plugin.zip‎

304 Bytes
Binary file not shown.

‎src/main.js‎

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@ class Python {
1919
$page = null;
2020
$runBtn = null;
2121
$wrapBtn = null;
22+
$status = null;
2223
$style = null;
2324
#codes = [];
2425
#niddle = 0;
2526
#inputCount = 0;
2627
#state = 0;
2728
#initPromise = Promise.resolve(false);
29+
/** number of code runs still waiting for the worker */
30+
#running = 0;
2831

2932
INITIALIZING = 1;
3033
INITIALIZED = 2;
@@ -46,6 +49,9 @@ class Python {
4649
$page.onhide = () => {
4750
this.#state = this.NOT_INTIALIZED;
4851
this.#worker?.terminate();
52+
// runs on the terminated worker never reply
53+
this.#running = 0;
54+
this.#isInput = false;
4955
this.initWorker();
5056
onhide();
5157
};
@@ -69,10 +75,12 @@ class Python {
6975
this.$wrapBtn = tag("span", {
7076
className: "icon wrap_py",
7177
innerHTML: WRAP_ICON,
72-
attr: { action: "toggle-wrap", title: "Wrap lines", role: "button" },
78+
attr: { action: "toggle-wrap", role: "button" },
7379
onclick: () => this.#setWrap(!this.$page.classList.contains("wrap")),
7480
});
75-
this.$page.header?.append(this.$wrapBtn);
81+
// Acode's spinner, shown while Python loads or code runs
82+
this.$status = tag("span", { className: "icon hidden" });
83+
this.$page.header?.append(this.$status, this.$wrapBtn);
7684
this.#setWrap(loadWrap());
7785
this.$style = tag("style", { textContent: style });
7886
this.$input = tag("div", {
@@ -105,6 +113,7 @@ class Python {
105113

106114
async #startWorker() {
107115
this.#state = this.INITIALIZING;
116+
this.#updateStatus();
108117
this.$page.settitle(strings["loading..."]);
109118
this.#worker?.terminate();
110119

@@ -135,6 +144,7 @@ class Python {
135144
return false;
136145
} finally {
137146
this.$page.settitle("Python");
147+
this.#updateStatus();
138148
}
139149
}
140150

@@ -151,29 +161,45 @@ class Python {
151161
}
152162

153163
async runCode(code) {
154-
if (!(await this.initWorker())) return;
155-
this.#worker.postMessage({
156-
action: "run",
157-
code,
158-
});
164+
this.#running += 1;
165+
this.#updateStatus();
159166
try {
167+
if (!(await this.initWorker())) return;
168+
this.#worker.postMessage({
169+
action: "run",
170+
code,
171+
});
160172
const res = await new Promise((resolve, error) => {
161173
this.#onRunSuccess = resolve;
162174
this.#onRunError = error;
163175
});
164176
this.print(res, "output");
165177
} catch (error) {
166178
this.print(error, "error");
179+
} finally {
180+
this.#running = Math.max(0, this.#running - 1);
181+
this.#updateStatus();
167182
}
168183
}
169184

185+
/** Spinner while Python is busy, hidden while it waits for input() */
186+
#updateStatus() {
187+
if (!this.$status) return;
188+
const busy =
189+
(this.#state === this.INITIALIZING || this.#running > 0) &&
190+
!this.#isInput;
191+
this.$status.classList.toggle("loading", busy);
192+
this.$status.classList.toggle("hidden", !busy);
193+
}
194+
170195
destroy() {
171196
if (this.$runBtn) {
172197
this.$runBtn.onclick = null;
173198
this.$runBtn.remove();
174199
}
175200

176201
this.$wrapBtn?.remove();
202+
this.$status?.remove();
177203
this.#worker?.terminate();
178204
editorManager.off("switch-file", this.checkRunnable.bind(this));
179205
editorManager.off("rename-file", this.checkRunnable.bind(this));
@@ -197,8 +223,6 @@ class Python {
197223
/** Wraps long output lines instead of scrolling them horizontally */
198224
#setWrap(wrap) {
199225
this.$page.classList.toggle("wrap", wrap);
200-
this.$wrapBtn.classList.toggle("active", wrap);
201-
this.$wrapBtn.setAttribute("aria-pressed", String(wrap));
202226
saveWrap(wrap);
203227
}
204228

@@ -253,6 +277,7 @@ class Python {
253277

254278
case "input":
255279
this.#isInput = true;
280+
this.#updateStatus();
256281
if (text) this.print(text);
257282
await this.#cacheFile.writeFile("");
258283
this.$input.get("textarea").focus();
@@ -324,6 +349,7 @@ class Python {
324349
if (value.endsWith("\n")) {
325350
if (this.#isInput) {
326351
this.#isInput = false;
352+
this.#updateStatus();
327353
value = value.slice(0, -1);
328354
this.#cacheFile.writeFile(`${value}\0${this.#inputCount++}`);
329355
this.print(value, "input");

‎src/style.css‎

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,6 @@
5050
white-space: pre-wrap;
5151
overflow-wrap: anywhere;
5252
}
53-
54-
.wrap_py {
55-
opacity: 0.5;
56-
57-
&.active {
58-
opacity: 1;
59-
}
60-
}
6153
}
6254

6355
@font-face {

0 commit comments

Comments
 (0)