@ox-content/code-play 3.0.0-alpha.1 → 3.0.0-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -6
- package/dist/browser.mjs +561 -58
- package/dist/client.mjs +192 -28
- package/dist/client.mjs.map +1 -1
- package/dist/config.d.mts +47 -2
- package/dist/config.d.mts.map +1 -1
- package/dist/hydrate.d.mts +5 -2
- package/dist/hydrate.d.mts.map +1 -1
- package/dist/hydrate.mjs +2 -2
- package/dist/hydrate2.d.mts +2 -2
- package/dist/hydrate2.mjs +369 -33
- package/dist/hydrate2.mjs.map +1 -1
- package/dist/index.d.mts +50 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +4 -4
- package/dist/payload.mjs +10 -2
- package/dist/payload.mjs.map +1 -1
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin2.mjs +712 -162
- package/dist/plugin2.mjs.map +1 -1
- package/package.json +1 -1
package/dist/browser.mjs
CHANGED
|
@@ -372,6 +372,9 @@ function escapeHtml(value) {
|
|
|
372
372
|
}
|
|
373
373
|
});
|
|
374
374
|
}
|
|
375
|
+
function escapeAttribute(value) {
|
|
376
|
+
return escapeHtml(value).replace(/`/g, "`");
|
|
377
|
+
}
|
|
375
378
|
//#endregion
|
|
376
379
|
//#region src/timing.ts
|
|
377
380
|
var PhaseTracker = class {
|
|
@@ -692,10 +695,115 @@ function buildJavaScriptSandboxDocument(code, messageId) {
|
|
|
692
695
|
})();
|
|
693
696
|
<\/script></body></html>`;
|
|
694
697
|
}
|
|
698
|
+
function buildJavaScriptWorkerSource() {
|
|
699
|
+
return `
|
|
700
|
+
(function () {
|
|
701
|
+
function format(args) {
|
|
702
|
+
return Array.prototype.map.call(args, function (value) {
|
|
703
|
+
if (typeof value === "string") return value;
|
|
704
|
+
if (value === undefined) return "undefined";
|
|
705
|
+
if (value === null) return "null";
|
|
706
|
+
try { return JSON.stringify(value); } catch (error) { return String(value); }
|
|
707
|
+
}).join(" ") + "\\n";
|
|
708
|
+
}
|
|
709
|
+
self.onmessage = function (event) {
|
|
710
|
+
var data = event.data || {};
|
|
711
|
+
if (!data.id) return;
|
|
712
|
+
var stdout = [];
|
|
713
|
+
var stderr = [];
|
|
714
|
+
var consoleLike = {
|
|
715
|
+
log: function () { stdout.push(format(arguments)); },
|
|
716
|
+
info: function () { stdout.push(format(arguments)); },
|
|
717
|
+
warn: function () { stderr.push(format(arguments)); },
|
|
718
|
+
error: function () { stderr.push(format(arguments)); }
|
|
719
|
+
};
|
|
720
|
+
try {
|
|
721
|
+
var run = new Function("console", '"use strict";\\n' + String(data.code || ""));
|
|
722
|
+
var value = run(consoleLike);
|
|
723
|
+
self.postMessage({
|
|
724
|
+
id: data.id,
|
|
725
|
+
stdout: stdout,
|
|
726
|
+
stderr: stderr,
|
|
727
|
+
value: value === undefined ? undefined : String(value)
|
|
728
|
+
});
|
|
729
|
+
} catch (error) {
|
|
730
|
+
var message = error && error.message ? String(error.message) : String(error);
|
|
731
|
+
self.postMessage({ id: data.id, stdout: stdout, stderr: stderr, error: message });
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
})();
|
|
735
|
+
`;
|
|
736
|
+
}
|
|
695
737
|
function applySandboxStreams(stdio, message) {
|
|
696
738
|
for (const text of message.stdout ?? []) stdio.push("stdout", text);
|
|
697
739
|
for (const text of message.stderr ?? []) stdio.push("stderr", text);
|
|
698
740
|
}
|
|
741
|
+
var WORKER_UNAVAILABLE_CODE = "ERR_SCRIPT_WORKER_UNAVAILABLE";
|
|
742
|
+
function workerUnavailableError(error) {
|
|
743
|
+
const message = error instanceof Error && error.message ? error.message : "JavaScript worker sandbox is unavailable.";
|
|
744
|
+
return Object.assign(new Error(message), { code: WORKER_UNAVAILABLE_CODE });
|
|
745
|
+
}
|
|
746
|
+
function isSandboxWorkerUnavailable(error) {
|
|
747
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === WORKER_UNAVAILABLE_CODE);
|
|
748
|
+
}
|
|
749
|
+
function canUseSandboxWorker() {
|
|
750
|
+
return typeof Worker !== "undefined" && typeof Blob !== "undefined" && typeof URL !== "undefined" && typeof URL.createObjectURL === "function" && typeof URL.revokeObjectURL === "function";
|
|
751
|
+
}
|
|
752
|
+
async function executeInSandboxWorker(code, timeoutMs, stdio, signal) {
|
|
753
|
+
if (!canUseSandboxWorker()) throw workerUnavailableError(/* @__PURE__ */ new Error("JavaScript worker sandbox needs Worker, Blob, and object URLs."));
|
|
754
|
+
if (signal?.aborted) throw abortError();
|
|
755
|
+
const messageId = `ox-code-play-${Math.random().toString(36).slice(2)}`;
|
|
756
|
+
const url = URL.createObjectURL(new Blob([buildJavaScriptWorkerSource()], { type: "text/javascript" }));
|
|
757
|
+
let worker;
|
|
758
|
+
try {
|
|
759
|
+
worker = new Worker(url);
|
|
760
|
+
} catch (error) {
|
|
761
|
+
URL.revokeObjectURL(url);
|
|
762
|
+
throw workerUnavailableError(error);
|
|
763
|
+
}
|
|
764
|
+
return new Promise((resolve, reject) => {
|
|
765
|
+
let settled = false;
|
|
766
|
+
const cleanup = () => {
|
|
767
|
+
if (settled) return;
|
|
768
|
+
settled = true;
|
|
769
|
+
clearTimeout(timer);
|
|
770
|
+
signal?.removeEventListener("abort", onAbort);
|
|
771
|
+
worker.onmessage = null;
|
|
772
|
+
worker.onerror = null;
|
|
773
|
+
worker.terminate();
|
|
774
|
+
URL.revokeObjectURL(url);
|
|
775
|
+
};
|
|
776
|
+
const onAbort = () => {
|
|
777
|
+
cleanup();
|
|
778
|
+
reject(abortError());
|
|
779
|
+
};
|
|
780
|
+
const onMessage = (event) => {
|
|
781
|
+
if (event.data?.id !== messageId) return;
|
|
782
|
+
cleanup();
|
|
783
|
+
applySandboxStreams(stdio, event.data);
|
|
784
|
+
if (event.data.error) {
|
|
785
|
+
reject(new Error(event.data.error));
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
resolve(event.data.value);
|
|
789
|
+
};
|
|
790
|
+
const onError = (event) => {
|
|
791
|
+
cleanup();
|
|
792
|
+
reject(workerUnavailableError(new Error(event.message || "JavaScript worker sandbox failed.")));
|
|
793
|
+
};
|
|
794
|
+
const timer = setTimeout(() => {
|
|
795
|
+
cleanup();
|
|
796
|
+
reject(Object.assign(/* @__PURE__ */ new Error("JavaScript execution timed out."), { code: "ERR_SCRIPT_EXECUTION_TIMEOUT" }));
|
|
797
|
+
}, timeoutMs);
|
|
798
|
+
worker.onmessage = onMessage;
|
|
799
|
+
worker.onerror = onError;
|
|
800
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
801
|
+
worker.postMessage({
|
|
802
|
+
id: messageId,
|
|
803
|
+
code
|
|
804
|
+
});
|
|
805
|
+
});
|
|
806
|
+
}
|
|
699
807
|
async function executeInSandboxIframe(code, timeoutMs, stdio, signal) {
|
|
700
808
|
if (typeof document === "undefined" || typeof window === "undefined") throw new Error("JavaScript sandbox iframe needs a document.");
|
|
701
809
|
if (signal?.aborted) throw abortError();
|
|
@@ -747,24 +855,23 @@ async function runJavaScript(request) {
|
|
|
747
855
|
const tracker = new PhaseTracker();
|
|
748
856
|
tracker.start("execute", "Execute");
|
|
749
857
|
const stdio = new StdioBuffer(tracker.startedAt);
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
runtime: hasNodeVm() ? "node:vm" : "iframe",
|
|
753
|
-
sandbox: hasNodeVm() ? "vm" : "srcdoc"
|
|
754
|
-
} };
|
|
858
|
+
const runtime = currentJavaScriptRuntime();
|
|
859
|
+
let executedRuntime = runtime;
|
|
755
860
|
try {
|
|
756
|
-
const
|
|
861
|
+
const result = await executeScriptWithRuntime(request.code, request.timeoutMs, stdio, request.signal, runtime);
|
|
862
|
+
executedRuntime = result.runtime;
|
|
757
863
|
tracker.stop();
|
|
758
864
|
return {
|
|
759
865
|
status: "ok",
|
|
760
866
|
stdio: stdio.snapshot(),
|
|
761
867
|
diagnostics: [],
|
|
762
|
-
provenance,
|
|
868
|
+
provenance: { execute: javascriptRuntimeProvenance(executedRuntime) },
|
|
763
869
|
timing: tracker.report(),
|
|
764
|
-
value: value === void 0 ? void 0 : String(value)
|
|
870
|
+
value: result.value === void 0 ? void 0 : String(result.value)
|
|
765
871
|
};
|
|
766
872
|
} catch (error) {
|
|
767
873
|
if (isAbortError(error) || request.signal?.aborted) throw error;
|
|
874
|
+
executedRuntime = executionRuntimeFromError(error) ?? executedRuntime;
|
|
768
875
|
tracker.stop();
|
|
769
876
|
const diagnostic = toDiagnostic(error);
|
|
770
877
|
stdio.push("stderr", `${diagnostic.message}\n`);
|
|
@@ -772,12 +879,30 @@ async function runJavaScript(request) {
|
|
|
772
879
|
status: isTimeout(error) ? "timeout" : "error",
|
|
773
880
|
stdio: stdio.snapshot(),
|
|
774
881
|
diagnostics: [diagnostic],
|
|
775
|
-
provenance,
|
|
882
|
+
provenance: { execute: javascriptRuntimeProvenance(executedRuntime) },
|
|
776
883
|
timing: tracker.report()
|
|
777
884
|
};
|
|
778
885
|
}
|
|
779
886
|
}
|
|
780
|
-
async function
|
|
887
|
+
async function executeScriptWithRuntime(code, timeoutMs, stdio, signal, runtime = currentJavaScriptRuntime()) {
|
|
888
|
+
try {
|
|
889
|
+
return {
|
|
890
|
+
value: await executeScriptInRuntime(code, timeoutMs, stdio, signal, runtime),
|
|
891
|
+
runtime
|
|
892
|
+
};
|
|
893
|
+
} catch (error) {
|
|
894
|
+
if (runtime === "worker" && isSandboxWorkerUnavailable(error) && typeof document !== "undefined") try {
|
|
895
|
+
return {
|
|
896
|
+
value: await executeScriptInRuntime(code, timeoutMs, stdio, signal, "iframe"),
|
|
897
|
+
runtime: "iframe"
|
|
898
|
+
};
|
|
899
|
+
} catch (fallbackError) {
|
|
900
|
+
throw withExecutionRuntime(fallbackError, "iframe");
|
|
901
|
+
}
|
|
902
|
+
throw withExecutionRuntime(error, runtime);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
async function executeScriptInRuntime(code, timeoutMs, stdio, signal, runtime) {
|
|
781
906
|
const consoleLike = {
|
|
782
907
|
log: (...args) => stdio.push("stdout", formatConsoleArgs(args)),
|
|
783
908
|
info: (...args) => stdio.push("stdout", formatConsoleArgs(args)),
|
|
@@ -785,7 +910,7 @@ async function executeScript(code, timeoutMs, stdio, signal) {
|
|
|
785
910
|
error: (...args) => stdio.push("stderr", formatConsoleArgs(args))
|
|
786
911
|
};
|
|
787
912
|
if (signal?.aborted) throw abortError();
|
|
788
|
-
if (
|
|
913
|
+
if (runtime === "vm") {
|
|
789
914
|
const vm = await import("node:vm");
|
|
790
915
|
const context = vm.createContext({ console: consoleLike });
|
|
791
916
|
return vm.runInContext(code, context, {
|
|
@@ -793,12 +918,45 @@ async function executeScript(code, timeoutMs, stdio, signal) {
|
|
|
793
918
|
displayErrors: true
|
|
794
919
|
});
|
|
795
920
|
}
|
|
921
|
+
if (runtime === "worker") return executeInSandboxWorker(code, timeoutMs, stdio, signal);
|
|
796
922
|
return executeInSandboxIframe(code, timeoutMs, stdio, signal);
|
|
797
923
|
}
|
|
798
|
-
function
|
|
924
|
+
function currentJavaScriptRuntime() {
|
|
925
|
+
return javascriptExecuteRuntime(hasNodeVm(), canUseSandboxWorker(), typeof document !== "undefined");
|
|
926
|
+
}
|
|
927
|
+
function javascriptExecuteRuntime(hasVm, hasWorker, hasDocument) {
|
|
799
928
|
if (hasVm) return "vm";
|
|
929
|
+
if (hasWorker) return "worker";
|
|
800
930
|
if (hasDocument) return "iframe";
|
|
801
|
-
throw new Error("JavaScript execute needs node:vm or a document for the sandbox iframe.");
|
|
931
|
+
throw new Error("JavaScript execute needs node:vm, a browser worker sandbox, or a document for the sandbox iframe.");
|
|
932
|
+
}
|
|
933
|
+
function javascriptRuntimeProvenance(runtime) {
|
|
934
|
+
if (runtime === "vm") return {
|
|
935
|
+
host: "local",
|
|
936
|
+
runtime: "node:vm",
|
|
937
|
+
sandbox: "vm"
|
|
938
|
+
};
|
|
939
|
+
if (runtime === "worker") return {
|
|
940
|
+
host: "local",
|
|
941
|
+
runtime: "web-worker",
|
|
942
|
+
sandbox: "worker"
|
|
943
|
+
};
|
|
944
|
+
return {
|
|
945
|
+
host: "local",
|
|
946
|
+
runtime: "iframe",
|
|
947
|
+
sandbox: "srcdoc"
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
function withExecutionRuntime(error, runtime) {
|
|
951
|
+
if (error instanceof Error) return Object.assign(error, { executionRuntime: runtime });
|
|
952
|
+
if (isErrorLike(error)) return Object.assign(error, { executionRuntime: runtime });
|
|
953
|
+
return Object.assign(new Error(String(error)), { executionRuntime: runtime });
|
|
954
|
+
}
|
|
955
|
+
function executionRuntimeFromError(error) {
|
|
956
|
+
if (error && typeof error === "object" && "executionRuntime" in error && isJavaScriptExecutionRuntime(error.executionRuntime)) return error.executionRuntime;
|
|
957
|
+
}
|
|
958
|
+
function isJavaScriptExecutionRuntime(value) {
|
|
959
|
+
return value === "vm" || value === "worker" || value === "iframe";
|
|
802
960
|
}
|
|
803
961
|
function isTimeout(error) {
|
|
804
962
|
return Boolean(error && typeof error === "object" && "code" in error && error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT");
|
|
@@ -1065,9 +1223,12 @@ async function runTypeScript(request) {
|
|
|
1065
1223
|
const stdio = new StdioBuffer(tracker.startedAt);
|
|
1066
1224
|
tracker.start("compile", "Strip types");
|
|
1067
1225
|
const javascript = stripTypeScript(request.code);
|
|
1226
|
+
const runtime = currentJavaScriptRuntime();
|
|
1227
|
+
let executedRuntime = runtime;
|
|
1068
1228
|
tracker.start("execute", "Execute");
|
|
1069
1229
|
try {
|
|
1070
|
-
const
|
|
1230
|
+
const result = await executeScriptWithRuntime(javascript, request.timeoutMs, stdio, request.signal, runtime);
|
|
1231
|
+
executedRuntime = result.runtime;
|
|
1071
1232
|
tracker.stop();
|
|
1072
1233
|
return {
|
|
1073
1234
|
status: "ok",
|
|
@@ -1078,14 +1239,10 @@ async function runTypeScript(request) {
|
|
|
1078
1239
|
host: "local",
|
|
1079
1240
|
runtime: "strip-types"
|
|
1080
1241
|
},
|
|
1081
|
-
execute:
|
|
1082
|
-
host: "local",
|
|
1083
|
-
runtime: hasNodeVm() ? "node:vm" : "iframe",
|
|
1084
|
-
sandbox: hasNodeVm() ? "vm" : "srcdoc"
|
|
1085
|
-
}
|
|
1242
|
+
execute: javascriptRuntimeProvenance(executedRuntime)
|
|
1086
1243
|
},
|
|
1087
1244
|
timing: tracker.report(),
|
|
1088
|
-
value: value === void 0 ? void 0 : String(value)
|
|
1245
|
+
value: result.value === void 0 ? void 0 : String(result.value)
|
|
1089
1246
|
};
|
|
1090
1247
|
} catch (error) {
|
|
1091
1248
|
if (isAbortError(error) || request.signal?.aborted) throw error;
|
|
@@ -1105,11 +1262,7 @@ async function runTypeScript(request) {
|
|
|
1105
1262
|
host: "local",
|
|
1106
1263
|
runtime: "strip-types"
|
|
1107
1264
|
},
|
|
1108
|
-
execute:
|
|
1109
|
-
host: "local",
|
|
1110
|
-
runtime: hasNodeVm() ? "node:vm" : "iframe",
|
|
1111
|
-
sandbox: hasNodeVm() ? "vm" : "srcdoc"
|
|
1112
|
-
}
|
|
1265
|
+
execute: javascriptRuntimeProvenance(executedRuntime)
|
|
1113
1266
|
},
|
|
1114
1267
|
timing: tracker.report()
|
|
1115
1268
|
};
|
|
@@ -1282,9 +1435,12 @@ function errorMessage(error) {
|
|
|
1282
1435
|
}
|
|
1283
1436
|
function friendlyTransportMessage(error) {
|
|
1284
1437
|
const message = errorMessage(error);
|
|
1285
|
-
if ((error
|
|
1438
|
+
if (isOfflineError(error)) return "The executor is offline or unreachable from this page (for example, CORS). Set endpoints to a host that allows browser POST, or use the Vite dev proxy.";
|
|
1286
1439
|
return message;
|
|
1287
1440
|
}
|
|
1441
|
+
function transportFailureStatus(error) {
|
|
1442
|
+
return isOfflineError(error) ? "offline" : "error";
|
|
1443
|
+
}
|
|
1288
1444
|
function errorResult(message, source = "code-play", status = "error") {
|
|
1289
1445
|
return withStdioText({
|
|
1290
1446
|
status,
|
|
@@ -1298,6 +1454,14 @@ function errorResult(message, source = "code-play", status = "error") {
|
|
|
1298
1454
|
timing: emptyTiming()
|
|
1299
1455
|
});
|
|
1300
1456
|
}
|
|
1457
|
+
function isOfflineError(error) {
|
|
1458
|
+
const message = errorMessage(error);
|
|
1459
|
+
const name = error && typeof error === "object" && "name" in error ? String(error.name) : void 0;
|
|
1460
|
+
const typeError = error instanceof TypeError || name === "TypeError";
|
|
1461
|
+
if (name === "MissingTransportError") return true;
|
|
1462
|
+
if (typeError && /failed to fetch|networkerror|load failed|network request failed/i.test(message)) return true;
|
|
1463
|
+
return /\boffline\b|no code play transport|network request failed/i.test(message);
|
|
1464
|
+
}
|
|
1301
1465
|
//#endregion
|
|
1302
1466
|
//#region src/session.ts
|
|
1303
1467
|
var CodePlaySession = class {
|
|
@@ -1317,6 +1481,7 @@ var CodePlaySession = class {
|
|
|
1317
1481
|
timeoutMs;
|
|
1318
1482
|
transport;
|
|
1319
1483
|
endpoints;
|
|
1484
|
+
project;
|
|
1320
1485
|
loadTypeScript;
|
|
1321
1486
|
listeners = /* @__PURE__ */ new Map();
|
|
1322
1487
|
abort;
|
|
@@ -1328,6 +1493,7 @@ var CodePlaySession = class {
|
|
|
1328
1493
|
this.timeoutMs = input.timeoutMs;
|
|
1329
1494
|
this.transport = input.transport;
|
|
1330
1495
|
this.endpoints = input.endpoints;
|
|
1496
|
+
this.project = input.project;
|
|
1331
1497
|
this.loadTypeScript = input.loadTypeScript;
|
|
1332
1498
|
}
|
|
1333
1499
|
on(event, listener) {
|
|
@@ -1368,6 +1534,7 @@ var CodePlaySession = class {
|
|
|
1368
1534
|
transport: this.transport,
|
|
1369
1535
|
loadTypeScript: this.loadTypeScript,
|
|
1370
1536
|
endpoints: this.endpoints,
|
|
1537
|
+
project: this.project,
|
|
1371
1538
|
signal
|
|
1372
1539
|
};
|
|
1373
1540
|
try {
|
|
@@ -1375,7 +1542,7 @@ var CodePlaySession = class {
|
|
|
1375
1542
|
return this.finish(result);
|
|
1376
1543
|
} catch (error) {
|
|
1377
1544
|
if (signal.aborted || isAbortError(error)) return this.finish(errorResult("Run cancelled.", "code-play", "cancelled"));
|
|
1378
|
-
return this.finish(errorResult(friendlyTransportMessage(error)));
|
|
1545
|
+
return this.finish(errorResult(friendlyTransportMessage(error), "code-play", transportFailureStatus(error)));
|
|
1379
1546
|
}
|
|
1380
1547
|
}
|
|
1381
1548
|
finish(result) {
|
|
@@ -1460,12 +1627,39 @@ async function runPlayAction(input) {
|
|
|
1460
1627
|
input.setBusy(false);
|
|
1461
1628
|
}
|
|
1462
1629
|
}
|
|
1630
|
+
function idleRunActionState() {
|
|
1631
|
+
return { phase: "idle" };
|
|
1632
|
+
}
|
|
1633
|
+
function runningRunActionState(action, startedAtMs = Date.now()) {
|
|
1634
|
+
return {
|
|
1635
|
+
phase: "running",
|
|
1636
|
+
action,
|
|
1637
|
+
startedAtMs
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
function resultRunActionState(action, result, finishedAtMs = Date.now()) {
|
|
1641
|
+
return {
|
|
1642
|
+
phase: result.status === "offline" ? "offline" : result.status === "ok" || result.status === "cancelled" ? "result" : "error",
|
|
1643
|
+
action,
|
|
1644
|
+
result,
|
|
1645
|
+
message: result.diagnostics[0]?.message,
|
|
1646
|
+
finishedAtMs
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
function errorRunActionState(action, error, finishedAtMs = Date.now()) {
|
|
1650
|
+
return {
|
|
1651
|
+
phase: "error",
|
|
1652
|
+
action,
|
|
1653
|
+
message: errorMessage(error),
|
|
1654
|
+
finishedAtMs
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1463
1657
|
//#endregion
|
|
1464
1658
|
//#region src/styles.ts
|
|
1465
1659
|
var CODE_PLAY_STYLES = `
|
|
1466
1660
|
.ox-code-play {
|
|
1467
1661
|
border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 16%, transparent));
|
|
1468
|
-
border-radius:
|
|
1662
|
+
border-radius: 8px;
|
|
1469
1663
|
background: var(--octc-color-bg-alt, var(--octc-color-bg, Canvas));
|
|
1470
1664
|
color: var(--octc-color-text, CanvasText);
|
|
1471
1665
|
overflow: hidden;
|
|
@@ -1479,22 +1673,147 @@ var CODE_PLAY_STYLES = `
|
|
|
1479
1673
|
padding: 0.6rem 0.8rem;
|
|
1480
1674
|
border-bottom: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 12%, transparent));
|
|
1481
1675
|
}
|
|
1676
|
+
.ox-code-play__summary {
|
|
1677
|
+
display: inline-flex;
|
|
1678
|
+
flex: 1 1 12rem;
|
|
1679
|
+
min-width: 9rem;
|
|
1680
|
+
gap: 0.45rem;
|
|
1681
|
+
align-items: baseline;
|
|
1682
|
+
}
|
|
1482
1683
|
.ox-code-play__lang {
|
|
1483
1684
|
font: 600 0.8rem/1.2 ui-sans-serif, system-ui, sans-serif;
|
|
1484
|
-
margin-right: auto;
|
|
1485
1685
|
color: var(--octc-color-text, CanvasText);
|
|
1486
1686
|
}
|
|
1687
|
+
.ox-code-play__title {
|
|
1688
|
+
min-width: 0;
|
|
1689
|
+
overflow-wrap: anywhere;
|
|
1690
|
+
font: 500 0.78rem/1.25 ui-sans-serif, system-ui, sans-serif;
|
|
1691
|
+
opacity: 0.72;
|
|
1692
|
+
}
|
|
1693
|
+
.ox-code-play__status {
|
|
1694
|
+
min-width: 5.25rem;
|
|
1695
|
+
text-align: center;
|
|
1696
|
+
border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 16%, transparent));
|
|
1697
|
+
border-radius: 6px;
|
|
1698
|
+
padding: 0.18rem 0.5rem;
|
|
1699
|
+
font: 650 0.68rem/1.25 ui-sans-serif, system-ui, sans-serif;
|
|
1700
|
+
color: var(--octc-color-text, CanvasText);
|
|
1701
|
+
background: var(--octc-color-bg, Canvas);
|
|
1702
|
+
}
|
|
1703
|
+
.ox-code-play[data-ox-run-state="running"] .ox-code-play__status {
|
|
1704
|
+
color: var(--octc-color-primary, var(--octc-accent, #4f46e5));
|
|
1705
|
+
}
|
|
1706
|
+
.ox-code-play[data-ox-run-state="error"] .ox-code-play__status,
|
|
1707
|
+
.ox-code-play[data-ox-run-state="offline"] .ox-code-play__status {
|
|
1708
|
+
color: var(--octc-danger, #b42318);
|
|
1709
|
+
}
|
|
1487
1710
|
.ox-code-play__toolbar button {
|
|
1488
1711
|
appearance: none;
|
|
1489
1712
|
border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 20%, transparent));
|
|
1490
1713
|
background: color-mix(in srgb, var(--octc-color-text, CanvasText) 8%, transparent);
|
|
1491
1714
|
color: var(--octc-color-text, CanvasText);
|
|
1492
|
-
border-radius:
|
|
1715
|
+
border-radius: 8px;
|
|
1493
1716
|
padding: 0.25rem 0.75rem;
|
|
1494
1717
|
font: 600 0.75rem/1.4 ui-sans-serif, system-ui, sans-serif;
|
|
1495
1718
|
cursor: pointer;
|
|
1496
1719
|
}
|
|
1497
1720
|
.ox-code-play__toolbar button:disabled { opacity: 0.55; cursor: progress; }
|
|
1721
|
+
.ox-code-play__toolbar button:focus-visible,
|
|
1722
|
+
.ox-code-play__tabs button:focus-visible,
|
|
1723
|
+
.ox-code-play__field input:focus-visible,
|
|
1724
|
+
.ox-code-play__field select:focus-visible,
|
|
1725
|
+
.ox-code-play__panel:focus-visible {
|
|
1726
|
+
outline: 2px solid var(--octc-color-primary, var(--octc-accent, #4f46e5));
|
|
1727
|
+
outline-offset: 2px;
|
|
1728
|
+
}
|
|
1729
|
+
.ox-code-play__runtime {
|
|
1730
|
+
display: flex;
|
|
1731
|
+
flex-wrap: wrap;
|
|
1732
|
+
gap: 0.45rem;
|
|
1733
|
+
align-items: center;
|
|
1734
|
+
padding: 0.55rem 0.8rem;
|
|
1735
|
+
border-bottom: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 10%, transparent));
|
|
1736
|
+
background: color-mix(in srgb, var(--octc-color-text, CanvasText) 4%, transparent);
|
|
1737
|
+
}
|
|
1738
|
+
.ox-code-play__runtime-chip {
|
|
1739
|
+
display: inline-grid;
|
|
1740
|
+
gap: 0.1rem;
|
|
1741
|
+
min-width: 7.5rem;
|
|
1742
|
+
padding: 0.35rem 0.5rem;
|
|
1743
|
+
border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 14%, transparent));
|
|
1744
|
+
border-radius: 6px;
|
|
1745
|
+
background: var(--octc-color-bg, Canvas);
|
|
1746
|
+
}
|
|
1747
|
+
.ox-code-play__runtime-chip span {
|
|
1748
|
+
font: 600 0.62rem/1.1 ui-sans-serif, system-ui, sans-serif;
|
|
1749
|
+
text-transform: uppercase;
|
|
1750
|
+
letter-spacing: 0.04em;
|
|
1751
|
+
opacity: 0.62;
|
|
1752
|
+
}
|
|
1753
|
+
.ox-code-play__runtime-chip strong {
|
|
1754
|
+
font: 650 0.78rem/1.2 ui-sans-serif, system-ui, sans-serif;
|
|
1755
|
+
}
|
|
1756
|
+
.ox-code-play__runtime-chip--ok strong { color: var(--octc-color-primary, var(--octc-accent, #4f46e5)); }
|
|
1757
|
+
.ox-code-play__runtime-chip--warn strong { color: var(--octc-warning, #b54708); }
|
|
1758
|
+
.ox-code-play__runtime-chip--muted strong { opacity: 0.72; }
|
|
1759
|
+
.ox-code-play__project {
|
|
1760
|
+
display: inline-flex;
|
|
1761
|
+
flex: 1 1 18rem;
|
|
1762
|
+
min-width: min(100%, 16rem);
|
|
1763
|
+
gap: 0.45rem;
|
|
1764
|
+
align-items: center;
|
|
1765
|
+
flex-wrap: wrap;
|
|
1766
|
+
margin-left: auto;
|
|
1767
|
+
padding: 0.35rem 0.5rem;
|
|
1768
|
+
border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 14%, transparent));
|
|
1769
|
+
border-radius: 6px;
|
|
1770
|
+
background: var(--octc-color-bg, Canvas);
|
|
1771
|
+
}
|
|
1772
|
+
.ox-code-play__project-main,
|
|
1773
|
+
.ox-code-play__project-entry {
|
|
1774
|
+
display: inline-grid;
|
|
1775
|
+
gap: 0.1rem;
|
|
1776
|
+
min-width: 0;
|
|
1777
|
+
}
|
|
1778
|
+
.ox-code-play__project-main { flex: 1 1 8.5rem; }
|
|
1779
|
+
.ox-code-play__project-entry { flex: 999 1 9rem; }
|
|
1780
|
+
.ox-code-play__project-main span,
|
|
1781
|
+
.ox-code-play__project-entry span {
|
|
1782
|
+
font: 600 0.62rem/1.1 ui-sans-serif, system-ui, sans-serif;
|
|
1783
|
+
text-transform: uppercase;
|
|
1784
|
+
letter-spacing: 0.04em;
|
|
1785
|
+
opacity: 0.62;
|
|
1786
|
+
}
|
|
1787
|
+
.ox-code-play__project-main strong,
|
|
1788
|
+
.ox-code-play__project-entry strong {
|
|
1789
|
+
min-width: 0;
|
|
1790
|
+
overflow-wrap: anywhere;
|
|
1791
|
+
font: 650 0.78rem/1.2 ui-sans-serif, system-ui, sans-serif;
|
|
1792
|
+
}
|
|
1793
|
+
.ox-code-play__project-files,
|
|
1794
|
+
.ox-code-play__project-warning,
|
|
1795
|
+
.ox-code-play__project-link {
|
|
1796
|
+
flex: 0 0 auto;
|
|
1797
|
+
border-radius: 6px;
|
|
1798
|
+
padding: 0.18rem 0.45rem;
|
|
1799
|
+
font: 650 0.68rem/1.25 ui-sans-serif, system-ui, sans-serif;
|
|
1800
|
+
}
|
|
1801
|
+
.ox-code-play__project-files {
|
|
1802
|
+
background: color-mix(in srgb, var(--octc-color-text, CanvasText) 8%, transparent);
|
|
1803
|
+
}
|
|
1804
|
+
.ox-code-play__project-warning {
|
|
1805
|
+
color: var(--octc-warning, #b54708);
|
|
1806
|
+
background: color-mix(in srgb, var(--octc-warning, #b54708) 12%, transparent);
|
|
1807
|
+
}
|
|
1808
|
+
.ox-code-play__project-link {
|
|
1809
|
+
color: var(--octc-color-primary, var(--octc-accent, #4f46e5));
|
|
1810
|
+
background: color-mix(in srgb, var(--octc-color-primary, var(--octc-accent, #4f46e5)) 12%, transparent);
|
|
1811
|
+
text-decoration: none;
|
|
1812
|
+
}
|
|
1813
|
+
.ox-code-play__project-link:focus-visible {
|
|
1814
|
+
outline: 2px solid var(--octc-color-primary, var(--octc-accent, #4f46e5));
|
|
1815
|
+
outline-offset: 2px;
|
|
1816
|
+
}
|
|
1498
1817
|
.ox-code-play .ox-code { margin: 0; }
|
|
1499
1818
|
.ox-code-play__source pre { margin: 0; border: 0; border-radius: 0; }
|
|
1500
1819
|
.ox-code-play__tabs {
|
|
@@ -1508,7 +1827,7 @@ var CODE_PLAY_STYLES = `
|
|
|
1508
1827
|
background: transparent;
|
|
1509
1828
|
color: var(--octc-color-text, CanvasText);
|
|
1510
1829
|
padding: 0.35rem 0.55rem;
|
|
1511
|
-
border-radius:
|
|
1830
|
+
border-radius: 6px 6px 0 0;
|
|
1512
1831
|
font: 600 0.75rem/1.2 ui-sans-serif, system-ui, sans-serif;
|
|
1513
1832
|
cursor: pointer;
|
|
1514
1833
|
}
|
|
@@ -1535,7 +1854,7 @@ var CODE_PLAY_STYLES = `
|
|
|
1535
1854
|
.ox-code-play__field input, .ox-code-play__field select {
|
|
1536
1855
|
font: inherit;
|
|
1537
1856
|
padding: 0.3rem 0.45rem;
|
|
1538
|
-
border-radius:
|
|
1857
|
+
border-radius: 6px;
|
|
1539
1858
|
border: 1px solid var(--octc-color-border, color-mix(in srgb, currentColor 18%, transparent));
|
|
1540
1859
|
background: var(--octc-color-bg, Canvas);
|
|
1541
1860
|
color: var(--octc-color-text, CanvasText);
|
|
@@ -1630,42 +1949,141 @@ function renderPlayUi(state) {
|
|
|
1630
1949
|
if (preset === "headless") return "";
|
|
1631
1950
|
const panel = state.panel ?? "stdio";
|
|
1632
1951
|
const canTypecheck = state.payload.capabilities.typecheck;
|
|
1952
|
+
const runState = state.runState ?? (state.busy ? {
|
|
1953
|
+
phase: "running",
|
|
1954
|
+
action: "execute"
|
|
1955
|
+
} : idleRunActionState());
|
|
1956
|
+
const isBusy = Boolean(state.busy) || runState.phase === "running";
|
|
1633
1957
|
const tabs = renderTabs(state, panel);
|
|
1634
1958
|
const viewers = state.payload.viewers;
|
|
1635
|
-
|
|
1959
|
+
const label = widgetLabel(state.payload, definition);
|
|
1960
|
+
return `<div class="ox-code-play ox-code-play--${preset}" data-ox-code-play-ui data-ox-run-state="${runState.phase}" role="region" aria-label="${escapeHtml(label)}" aria-busy="${isBusy ? "true" : "false"}">
|
|
1636
1961
|
<div class="ox-code-play__toolbar">
|
|
1637
|
-
<span class="ox-code-play__lang">${escapeHtml(definition?.name ?? state.payload.language)}</span>
|
|
1638
|
-
<
|
|
1639
|
-
|
|
1640
|
-
|
|
1962
|
+
<span class="ox-code-play__summary"><span class="ox-code-play__lang">${escapeHtml(definition?.name ?? state.payload.language)}</span>${state.payload.title ? `<span class="ox-code-play__title">${escapeHtml(state.payload.title)}</span>` : ""}</span>
|
|
1963
|
+
<span class="ox-code-play__status" data-ox-status role="status" aria-live="polite">${renderRunStatusText(runState)}</span>
|
|
1964
|
+
<button type="button" data-ox-action="run" aria-label="${escapeHtml(`Run ${label}`)}"${actionButtonAttrs("run", Boolean(state.busy))}>Run</button>
|
|
1965
|
+
${canTypecheck ? `<button type="button" data-ox-action="typecheck" aria-label="${escapeHtml(`Typecheck ${label}`)}"${actionButtonAttrs("typecheck", Boolean(state.busy))}>Typecheck</button>` : ""}
|
|
1966
|
+
<button type="button" data-ox-action="cancel" aria-label="${escapeHtml(`Cancel ${label}`)}"${actionButtonAttrs("cancel", Boolean(state.busy))}>Cancel</button>
|
|
1641
1967
|
</div>
|
|
1968
|
+
${renderRuntimeStrip(state.payload, definition)}
|
|
1642
1969
|
<div class="ox-code-play__source"></div>
|
|
1643
1970
|
${tabs}
|
|
1644
|
-
${viewers.stdio ?
|
|
1645
|
-
${viewers.stderr ?
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1971
|
+
${viewers.stdio ? renderPanel("stdio", "Stdio", panel, preset, `${renderDiagnosticsHtml(state.result)}${renderStdioHtml(state.result?.stdio ?? [])}`) : ""}
|
|
1972
|
+
${viewers.stderr ? renderPanel("stderr", "Stderr", panel, preset, renderStderrHtml(state.result)) : ""}
|
|
1973
|
+
${viewers.config ? renderPanel("config", "Config", panel, preset, renderConfigHtml(definition?.configSchema ?? [], state.payload.config)) : ""}
|
|
1974
|
+
${viewers.provenance ? renderPanel("provenance", "Provenance", panel, preset, renderProvenanceHtml(state.result?.provenance)) : ""}
|
|
1975
|
+
${viewers.timing ? renderPanel("timing", "Timing", panel, preset, renderTimingHtml(state.result?.timing)) : ""}
|
|
1649
1976
|
</div>`;
|
|
1650
1977
|
}
|
|
1978
|
+
function renderRunStatusText(state) {
|
|
1979
|
+
if (state.phase === "running") return state.action === "typecheck" ? "Typechecking" : "Running";
|
|
1980
|
+
if (state.phase === "offline") return "Offline";
|
|
1981
|
+
if (state.phase === "error") {
|
|
1982
|
+
if (state.result?.status === "timeout") return "Timed out";
|
|
1983
|
+
if (state.result?.status === "unsupported") return "Unsupported";
|
|
1984
|
+
return "Error";
|
|
1985
|
+
}
|
|
1986
|
+
if (state.phase === "result") {
|
|
1987
|
+
if (state.result?.status === "cancelled") return "Cancelled";
|
|
1988
|
+
return "Done";
|
|
1989
|
+
}
|
|
1990
|
+
return "Ready";
|
|
1991
|
+
}
|
|
1992
|
+
function renderRuntimeStrip(payload, definition) {
|
|
1993
|
+
const runtime = runtimeLabel(payload, definition);
|
|
1994
|
+
const executor = executorLabel(payload, definition);
|
|
1995
|
+
const checks = payload.capabilities.typecheck ? "Typecheck ready" : "Run only";
|
|
1996
|
+
return `<div class="ox-code-play__runtime" aria-label="Code Play runtime">${[
|
|
1997
|
+
runtimeChip("Runtime", runtime.label, runtime.kind),
|
|
1998
|
+
runtimeChip("Executor", executor.label, executor.kind),
|
|
1999
|
+
runtimeChip("Checks", checks, payload.capabilities.typecheck ? "ok" : "muted")
|
|
2000
|
+
].join("")}${renderProjectSandboxHtml(payload.project)}</div>`;
|
|
2001
|
+
}
|
|
2002
|
+
function runtimeLabel(payload, definition) {
|
|
2003
|
+
switch (definition?.backend) {
|
|
2004
|
+
case "javascript": return {
|
|
2005
|
+
label: "Browser sandbox",
|
|
2006
|
+
kind: "ok"
|
|
2007
|
+
};
|
|
2008
|
+
case "typescript": return {
|
|
2009
|
+
label: "TypeScript sandbox",
|
|
2010
|
+
kind: "ok"
|
|
2011
|
+
};
|
|
2012
|
+
case "framework": return {
|
|
2013
|
+
label: `${definition.name} iframe preview`,
|
|
2014
|
+
kind: "ok"
|
|
2015
|
+
};
|
|
2016
|
+
case "rust-playground": return {
|
|
2017
|
+
label: "Rust Playground",
|
|
2018
|
+
kind: payload.endpoints?.rust ? "ok" : "warn"
|
|
2019
|
+
};
|
|
2020
|
+
case "go-playground": return {
|
|
2021
|
+
label: "Go Playground",
|
|
2022
|
+
kind: payload.endpoints?.go ? "ok" : "warn"
|
|
2023
|
+
};
|
|
2024
|
+
case "remote": return payload.endpoint ? {
|
|
2025
|
+
label: "Piston-compatible",
|
|
2026
|
+
kind: "ok"
|
|
2027
|
+
} : {
|
|
2028
|
+
label: "Endpoint missing",
|
|
2029
|
+
kind: "warn"
|
|
2030
|
+
};
|
|
2031
|
+
default: return {
|
|
2032
|
+
label: definition?.name ?? payload.language,
|
|
2033
|
+
kind: "muted"
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
function executorLabel(payload, definition) {
|
|
2038
|
+
if (!payload.capabilities.execute) return {
|
|
2039
|
+
label: "Disabled",
|
|
2040
|
+
kind: "warn"
|
|
2041
|
+
};
|
|
2042
|
+
if (definition?.backend === "remote" && !payload.endpoint) return {
|
|
2043
|
+
label: "Configure endpoint",
|
|
2044
|
+
kind: "warn"
|
|
2045
|
+
};
|
|
2046
|
+
return {
|
|
2047
|
+
label: "On demand",
|
|
2048
|
+
kind: "ok"
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
function runtimeChip(label, value, kind) {
|
|
2052
|
+
return `<span class="ox-code-play__runtime-chip ox-code-play__runtime-chip--${kind}"><span>${escapeHtml(label)}</span><strong>${escapeHtml(value)}</strong></span>`;
|
|
2053
|
+
}
|
|
2054
|
+
function renderProjectSandboxHtml(project) {
|
|
2055
|
+
if (!project) return "";
|
|
2056
|
+
const fileCount = `${project.files.length} ${project.files.length === 1 ? "file" : "files"}`;
|
|
2057
|
+
const target = project.target === "browser" ? "Browser project" : project.target === "node" ? "Node-like project" : "External project";
|
|
2058
|
+
const url = project.openUrl ?? project.fallbackUrl;
|
|
2059
|
+
const warnings = project.warnings?.length ? `<span class="ox-code-play__project-warning" title="${escapeAttribute(project.warnings.join("\n"))}">Warnings</span>` : "";
|
|
2060
|
+
const link = url ? `<a class="ox-code-play__project-link" href="${escapeAttribute(url)}" target="_blank" rel="noopener noreferrer">Open</a>` : "";
|
|
2061
|
+
return `<span class="ox-code-play__project" data-ox-project-provider="${escapeAttribute(project.provider)}">
|
|
2062
|
+
<span class="ox-code-play__project-main"><span>${escapeHtml(project.label)}</span><strong>${escapeHtml(target)}</strong></span>
|
|
2063
|
+
${project.entry ? `<span class="ox-code-play__project-entry"><span>Entry</span><strong>${escapeHtml(project.entry)}</strong></span>` : ""}
|
|
2064
|
+
<span class="ox-code-play__project-files">${escapeHtml(fileCount)}</span>
|
|
2065
|
+
${warnings}${link}
|
|
2066
|
+
</span>`;
|
|
2067
|
+
}
|
|
1651
2068
|
function renderTabs(state, panel) {
|
|
1652
2069
|
if (state.payload.ui === "compact") return "";
|
|
1653
2070
|
const viewers = state.payload.viewers;
|
|
1654
2071
|
const buttons = [
|
|
1655
|
-
viewers.stdio ? tab("stdio", "
|
|
1656
|
-
viewers.stderr ? tab("stderr", "
|
|
1657
|
-
viewers.config ? tab("config", "
|
|
1658
|
-
viewers.provenance ? tab("provenance", "
|
|
1659
|
-
viewers.timing ? tab("timing", "
|
|
2072
|
+
viewers.stdio ? tab("stdio", "Stdio", panel) : "",
|
|
2073
|
+
viewers.stderr ? tab("stderr", "Stderr", panel) : "",
|
|
2074
|
+
viewers.config ? tab("config", "Config", panel) : "",
|
|
2075
|
+
viewers.provenance ? tab("provenance", "Provenance", panel) : "",
|
|
2076
|
+
viewers.timing ? tab("timing", "Timing", panel) : ""
|
|
1660
2077
|
].filter(Boolean).join("");
|
|
1661
2078
|
return buttons ? `<div class="ox-code-play__tabs" role="tablist">${buttons}</div>` : "";
|
|
1662
2079
|
}
|
|
1663
2080
|
function tab(id, label, selected) {
|
|
1664
|
-
|
|
2081
|
+
const isSelected = selected === id;
|
|
2082
|
+
return `<button type="button" role="tab" data-ox-panel="${id}" aria-selected="${isSelected ? "true" : "false"}" tabindex="${isSelected ? "0" : "-1"}">${label}</button>`;
|
|
1665
2083
|
}
|
|
1666
2084
|
function actionButtonAttrs(action, busy) {
|
|
1667
2085
|
const state = actionBusyState(action, busy);
|
|
1668
|
-
return `${state.disabled ? " disabled" : ""}${state.hidden ? " hidden" : ""}`;
|
|
2086
|
+
return `${state.disabled ? " disabled aria-disabled=\"true\"" : ""}${state.hidden ? " hidden" : ""}`;
|
|
1669
2087
|
}
|
|
1670
2088
|
function actionBusyState(action, busy) {
|
|
1671
2089
|
if (action === "cancel") return {
|
|
@@ -1682,15 +2100,30 @@ function applyActionBusy(root, busy) {
|
|
|
1682
2100
|
const state = actionBusyState(button.dataset.oxAction ?? "", busy);
|
|
1683
2101
|
button.disabled = state.disabled;
|
|
1684
2102
|
button.hidden = state.hidden;
|
|
2103
|
+
button.setAttribute("aria-disabled", state.disabled ? "true" : "false");
|
|
1685
2104
|
}
|
|
2105
|
+
for (const widget of queryPlayWidgets(root)) widget.setAttribute("aria-busy", busy ? "true" : "false");
|
|
2106
|
+
}
|
|
2107
|
+
function renderPanel(id, label, current, preset, html) {
|
|
2108
|
+
return `<div class="ox-code-play__panel" role="tabpanel" tabindex="0" aria-label="${escapeHtml(label)}" data-panel="${id}"${hidden(current, id, preset)}>${html}</div>`;
|
|
1686
2109
|
}
|
|
1687
2110
|
function hidden(current, id, preset) {
|
|
1688
2111
|
if (preset === "compact" && (id === "stdio" || id === "stderr")) return "";
|
|
1689
2112
|
return current === id ? "" : " hidden";
|
|
1690
2113
|
}
|
|
2114
|
+
function widgetLabel(payload, definition) {
|
|
2115
|
+
const language = definition?.name ?? payload.language;
|
|
2116
|
+
return payload.title ? `${payload.title} (${language})` : `${language} Code Play`;
|
|
2117
|
+
}
|
|
2118
|
+
function queryPlayWidgets(root) {
|
|
2119
|
+
const widgets = [...root.querySelectorAll(".ox-code-play")];
|
|
2120
|
+
if (typeof HTMLElement !== "undefined" && root instanceof HTMLElement && root.matches(".ox-code-play")) widgets.unshift(root);
|
|
2121
|
+
return widgets;
|
|
2122
|
+
}
|
|
1691
2123
|
//#endregion
|
|
1692
2124
|
//#region src/hydrate.ts
|
|
1693
2125
|
var STYLE_ID = "ox-code-play-styles";
|
|
2126
|
+
var widgetId = 0;
|
|
1694
2127
|
function hydrateCodePlay(root = defaultRoot(), options = {}) {
|
|
1695
2128
|
ensureStyles();
|
|
1696
2129
|
const client = options.client ?? createCodePlayFromPayloads(root);
|
|
@@ -1703,7 +2136,7 @@ function mountCodePlay(element, options = {}) {
|
|
|
1703
2136
|
const payload = readPlayPayload(element.getAttribute("data-ox-code-play") ?? "");
|
|
1704
2137
|
if (!payload || payload.ui === "headless") return;
|
|
1705
2138
|
const client = options.client ?? createCodePlay({
|
|
1706
|
-
languages: { [payload.language]:
|
|
2139
|
+
languages: { [payload.language]: languageEnableFromPayload(payload) },
|
|
1707
2140
|
endpoints: payload.endpoints
|
|
1708
2141
|
});
|
|
1709
2142
|
const source = element.innerHTML;
|
|
@@ -1715,14 +2148,17 @@ function mountCodePlay(element, options = {}) {
|
|
|
1715
2148
|
if (sourceSlot) sourceSlot.innerHTML = source;
|
|
1716
2149
|
element.replaceChildren(widget);
|
|
1717
2150
|
element.dataset.oxCodePlayMounted = "true";
|
|
2151
|
+
element.removeAttribute("inert");
|
|
1718
2152
|
bindWidget(element, payload, client);
|
|
2153
|
+
prepareA11y(element);
|
|
1719
2154
|
}
|
|
1720
2155
|
function bindWidget(element, payload, client) {
|
|
1721
2156
|
let current = payload;
|
|
1722
2157
|
const session = client.createSession({
|
|
1723
2158
|
language: payload.language,
|
|
1724
2159
|
code: payload.code,
|
|
1725
|
-
config: payload.config
|
|
2160
|
+
config: payload.config,
|
|
2161
|
+
project: payload.project
|
|
1726
2162
|
});
|
|
1727
2163
|
const runButton = element.querySelector("[data-ox-action=\"run\"]");
|
|
1728
2164
|
const checkButton = element.querySelector("[data-ox-action=\"typecheck\"]");
|
|
@@ -1730,6 +2166,9 @@ function bindWidget(element, payload, client) {
|
|
|
1730
2166
|
runButton?.addEventListener("click", () => void run("execute"));
|
|
1731
2167
|
checkButton?.addEventListener("click", () => void run("typecheck"));
|
|
1732
2168
|
cancelButton?.addEventListener("click", () => session.cancel());
|
|
2169
|
+
element.addEventListener("keydown", (event) => {
|
|
2170
|
+
if (event.target instanceof HTMLElement && event.target.matches("[role=\"tab\"]")) handleTabKeydown(element, event);
|
|
2171
|
+
});
|
|
1733
2172
|
element.addEventListener("click", (event) => {
|
|
1734
2173
|
const target = event.target;
|
|
1735
2174
|
if (!(target instanceof HTMLElement)) return;
|
|
@@ -1748,13 +2187,20 @@ function bindWidget(element, payload, client) {
|
|
|
1748
2187
|
async function run(action) {
|
|
1749
2188
|
await runPlayAction({
|
|
1750
2189
|
action: () => action === "typecheck" ? session.typecheck() : session.run(),
|
|
1751
|
-
setBusy: (busy) =>
|
|
1752
|
-
|
|
1753
|
-
|
|
2190
|
+
setBusy: (busy) => {
|
|
2191
|
+
applyActionBusy(element, busy);
|
|
2192
|
+
if (busy) paintRunState(element, runningRunActionState(action));
|
|
2193
|
+
},
|
|
2194
|
+
onResult: (result) => paintResult(element, current, result, action),
|
|
2195
|
+
onError: (error) => {
|
|
2196
|
+
paintRunState(element, errorRunActionState(action, error));
|
|
2197
|
+
paintResult(element, current, errorResult(errorMessage(error)), action);
|
|
2198
|
+
}
|
|
1754
2199
|
});
|
|
1755
2200
|
}
|
|
1756
2201
|
}
|
|
1757
|
-
function paintResult(element, _payload, result) {
|
|
2202
|
+
function paintResult(element, _payload, result, action) {
|
|
2203
|
+
paintRunState(element, resultRunActionState(action, result));
|
|
1758
2204
|
const stdio = element.querySelector("[data-panel=\"stdio\"]");
|
|
1759
2205
|
if (stdio) {
|
|
1760
2206
|
stdio.innerHTML = `${renderDiagnosticsHtml(result)}${renderStdioHtml(result.stdio)}`;
|
|
@@ -1775,11 +2221,27 @@ function paintResult(element, _payload, result) {
|
|
|
1775
2221
|
if (timing) timing.innerHTML = renderTimingHtml(result.timing);
|
|
1776
2222
|
const stderr = element.querySelector("[data-panel=\"stderr\"]");
|
|
1777
2223
|
if (stderr) stderr.innerHTML = renderStderrHtml(result);
|
|
1778
|
-
showPanel(element,
|
|
2224
|
+
showPanel(element, resultPanelToShow(result, Boolean(stderr)));
|
|
2225
|
+
}
|
|
2226
|
+
function paintRunState(element, state) {
|
|
2227
|
+
const widget = element.querySelector(".ox-code-play");
|
|
2228
|
+
widget?.setAttribute("data-ox-run-state", state.phase);
|
|
2229
|
+
widget?.setAttribute("aria-busy", state.phase === "running" ? "true" : "false");
|
|
2230
|
+
const status = element.querySelector("[data-ox-status]");
|
|
2231
|
+
if (status) status.textContent = renderRunStatusText(state);
|
|
2232
|
+
}
|
|
2233
|
+
function resultPanelToShow(result, hasStderrPanel) {
|
|
2234
|
+
if (!hasStderrPanel) return "stdio";
|
|
2235
|
+
if (result.diagnostics.some((diagnostic) => diagnostic.severity === "error") || result.status !== "ok" && Boolean(result.stderr)) return "stderr";
|
|
2236
|
+
return "stdio";
|
|
1779
2237
|
}
|
|
1780
2238
|
function showPanel(element, panel) {
|
|
1781
2239
|
const compact = Boolean(element.querySelector(".ox-code-play--compact"));
|
|
1782
|
-
for (const tab of element.querySelectorAll("[data-ox-panel]"))
|
|
2240
|
+
for (const tab of element.querySelectorAll("[data-ox-panel]")) {
|
|
2241
|
+
const selected = tab.getAttribute("data-ox-panel") === panel;
|
|
2242
|
+
tab.setAttribute("aria-selected", selected ? "true" : "false");
|
|
2243
|
+
tab.tabIndex = selected ? 0 : -1;
|
|
2244
|
+
}
|
|
1783
2245
|
for (const node of element.querySelectorAll(".ox-code-play__panel")) {
|
|
1784
2246
|
const id = node.dataset.panel;
|
|
1785
2247
|
if (compact && (id === "stdio" || id === "stderr")) {
|
|
@@ -1789,6 +2251,38 @@ function showPanel(element, panel) {
|
|
|
1789
2251
|
node.hidden = id !== panel;
|
|
1790
2252
|
}
|
|
1791
2253
|
}
|
|
2254
|
+
function handleTabKeydown(element, event) {
|
|
2255
|
+
if (![
|
|
2256
|
+
"ArrowLeft",
|
|
2257
|
+
"ArrowRight",
|
|
2258
|
+
"Home",
|
|
2259
|
+
"End"
|
|
2260
|
+
].includes(event.key)) return;
|
|
2261
|
+
const tabs = [...element.querySelectorAll("[data-ox-panel]")];
|
|
2262
|
+
const current = tabs.indexOf(event.target);
|
|
2263
|
+
if (current === -1) return;
|
|
2264
|
+
event.preventDefault();
|
|
2265
|
+
const next = tabs[event.key === "Home" ? 0 : event.key === "End" ? tabs.length - 1 : event.key === "ArrowRight" ? (current + 1) % tabs.length : (current - 1 + tabs.length) % tabs.length];
|
|
2266
|
+
const panel = next?.dataset.oxPanel;
|
|
2267
|
+
if (!next || !panel) return;
|
|
2268
|
+
showPanel(element, panel);
|
|
2269
|
+
next.focus();
|
|
2270
|
+
}
|
|
2271
|
+
function prepareA11y(element) {
|
|
2272
|
+
const widget = element.querySelector(".ox-code-play");
|
|
2273
|
+
if (!widget) return;
|
|
2274
|
+
if (!widget.id) widget.id = `ox-code-play-${++widgetId}`;
|
|
2275
|
+
for (const tab of element.querySelectorAll("[data-ox-panel]")) {
|
|
2276
|
+
const panelName = tab.dataset.oxPanel;
|
|
2277
|
+
const panel = panelName ? element.querySelector(`[data-panel="${panelName}"]`) : null;
|
|
2278
|
+
if (!panelName || !panel) continue;
|
|
2279
|
+
tab.id ||= `${widget.id}-${panelName}-tab`;
|
|
2280
|
+
panel.id ||= `${widget.id}-${panelName}-panel`;
|
|
2281
|
+
tab.setAttribute("aria-controls", panel.id);
|
|
2282
|
+
panel.setAttribute("aria-labelledby", tab.id);
|
|
2283
|
+
panel.removeAttribute("aria-label");
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
1792
2286
|
function readForm(form) {
|
|
1793
2287
|
const data = new FormData(form);
|
|
1794
2288
|
const config = {};
|
|
@@ -1801,7 +2295,7 @@ function createCodePlayFromPayloads(root) {
|
|
|
1801
2295
|
let endpoints;
|
|
1802
2296
|
for (const element of queryWidgets(root)) try {
|
|
1803
2297
|
const payload = decodePayload(element.getAttribute("data-ox-code-play") ?? "");
|
|
1804
|
-
languages[payload.language] =
|
|
2298
|
+
languages[payload.language] = languageEnableFromPayload(payload);
|
|
1805
2299
|
endpoints = payload.endpoints ?? endpoints;
|
|
1806
2300
|
} catch {}
|
|
1807
2301
|
return createCodePlay({
|
|
@@ -1809,6 +2303,15 @@ function createCodePlayFromPayloads(root) {
|
|
|
1809
2303
|
endpoints
|
|
1810
2304
|
});
|
|
1811
2305
|
}
|
|
2306
|
+
function languageEnableFromPayload(payload) {
|
|
2307
|
+
const enable = {
|
|
2308
|
+
execute: payload.capabilities.execute,
|
|
2309
|
+
typecheck: payload.capabilities.typecheck,
|
|
2310
|
+
config: payload.config
|
|
2311
|
+
};
|
|
2312
|
+
if (payload.endpoint) enable.endpoint = payload.endpoint;
|
|
2313
|
+
return enable;
|
|
2314
|
+
}
|
|
1812
2315
|
function queryWidgets(root) {
|
|
1813
2316
|
return [...root.querySelectorAll("[data-ox-code-play]")];
|
|
1814
2317
|
}
|