diff --git a/src/lib/console.js b/src/lib/console.js index 8d399ea315..05ca41fa6a 100644 --- a/src/lib/console.js +++ b/src/lib/console.js @@ -18,7 +18,13 @@ import ConsoleExecutor, { let isExecuting = false; let viewportFrame = null; let viewportObserver = null; + let themeSyncTimer = null; + let themeSyncPending = false; + let themeSignature = null; const isStandaloneConsole = sessionStorage.getItem("__mode") === "console"; + const themeEndpoint = document.querySelector( + 'meta[name="acode-console-theme-endpoint"]', + )?.content; const originalConsole = console; const $input = tag("textarea", { id: "__c-input", @@ -149,6 +155,10 @@ import ConsoleExecutor, { if (isStandaloneConsole) { $console.setAttribute("standalone", ""); document.documentElement.setAttribute("console-only", ""); + startThemeSync(); + window.addEventListener("pagehide", stopThemeSync, { + once: true, + }); } const counter = {}; const timers = {}; @@ -183,6 +193,49 @@ import ConsoleExecutor, { document.addEventListener("hideconsole", hideConsole); } + function startThemeSync() { + if (!themeEndpoint || themeSyncTimer !== null) return; + void syncTheme(); + themeSyncTimer = setInterval(syncTheme, 1000); + } + + function stopThemeSync() { + if (themeSyncTimer === null) return; + clearInterval(themeSyncTimer); + themeSyncTimer = null; + } + + async function syncTheme() { + if (themeSyncPending) return; + themeSyncPending = true; + + try { + const response = await fetch(themeEndpoint, { cache: "no-store" }); + if (!response.ok) return; + const snapshot = await response.json(); + if (typeof snapshot.css !== "string") return; + + const signature = JSON.stringify([snapshot.type, snapshot.css]); + if (signature === themeSignature) return; + + let $theme = document.getElementById("console-live-theme"); + if (!$theme) { + $theme = tag("style", { id: "console-live-theme" }); + document.head.append($theme); + } + $theme.textContent = snapshot.css; + document.documentElement.setAttribute( + "theme-type", + snapshot.type === "light" ? "light" : "dark", + ); + themeSignature = signature; + } catch { + // Keep the last valid theme if the local endpoint is briefly unavailable. + } finally { + themeSyncPending = false; + } + } + function touchmove(e) { e.preventDefault(); $toggler.style.transform = "translate(" diff --git a/src/lib/run.js b/src/lib/run.js index a92a085c52..d130c11bae 100644 --- a/src/lib/run.js +++ b/src/lib/run.js @@ -22,6 +22,9 @@ import appSettings from "./settings"; /**@type {Server} */ let webServer; +// An open console may outlive the server instance that created it. +const CONSOLE_THEME_STATE = "__acode_console_theme.json"; + /** * Starts the server and run the active file in browser * @param {Boolean} isConsole @@ -73,6 +76,7 @@ async function run( const MIMETYPE_HTML = mimeType.lookup("html"); const CONSOLE_SCRIPT = uuid + "_console.js"; const CONSOLE_WORKER_SCRIPT = uuid + "_console_worker.js"; + const CONSOLE_THEME_STYLE = uuid + "_console_theme.css"; const MARKDOWN_STYLE = uuid + "_md.css"; const queue = []; @@ -211,6 +215,18 @@ async function run( sendFileContent(url, reqId, "application/javascript"); break; + case CONSOLE_THEME_STYLE: + sendText(getConsoleThemeSnapshot().css, reqId, "text/css"); + break; + + case CONSOLE_THEME_STATE: + sendText( + JSON.stringify(getConsoleThemeSnapshot()), + reqId, + "application/json", + ); + break; + case EXECUTING_SCRIPT: { const text = getDocText(activeFile?.session?.doc); sendText(text, reqId, "application/javascript"); @@ -235,7 +251,10 @@ async function run( mustache.render($_console, { CONSOLE_SCRIPT, CONSOLE_WORKER_SCRIPT, + CONSOLE_THEME_STYLE, + CONSOLE_THEME_STATE, EXECUTING_SCRIPT, + APP_THEME_TYPE: getConsoleThemeSnapshot().type, }), reqId, MIMETYPE_HTML, @@ -364,6 +383,13 @@ async function run( } } + function getConsoleThemeSnapshot() { + return { + css: document.head.querySelector("style#app-theme")?.textContent || "", + type: document.body.getAttribute("theme-type") || "dark", + }; + } + /** * Sends 404 error * @param {string} id diff --git a/src/styles/console.m.scss b/src/styles/console.m.scss index 47dbe4dbb8..e39f763605 100644 --- a/src/styles/console.m.scss +++ b/src/styles/console.m.scss @@ -4,10 +4,31 @@ html[console-only] body { height: 100%; margin: 0; overflow: hidden; - background: #313131 !important; + background: var(--primary-color, #313131) !important; } c-console { + --console-background: #313131; + --console-background-text: #eee; + --console-surface: #313131; + --console-surface-text: #eee; + --console-control: #414141; + --console-control-text: #d6d3d3; + --console-active: #555; + --console-active-text: #fff; + --console-border: rgba(204, 204, 204, 0.35); + --console-muted: #aaa; + --console-placeholder: #929292; + --console-scrollbar: #666; + --console-danger: #ff9b9b; + --console-danger-fill: #a00000; + --console-warning-fill: #ffb95c; + --console-error-surface: #422; + --console-warning-surface: #633; + --console-link: #99f; + --console-accent: rgb(120, 105, 225); + --console-function: rgb(145, 136, 168); + --console-success: rgb(59, 161, 59); box-sizing: border-box; display: flex; flex-direction: column; @@ -19,12 +40,47 @@ c-console { min-height: 0; overflow: hidden; z-index: 99998; - background: #313131; - color: #eee; + background: var(--console-background); + color: var(--console-background-text); font-family: var(--app-font-family, Verdana, Geneva, Tahoma, sans-serif); color-scheme: dark; } +html[console-only] c-console { + --console-background: var(--primary-color, #313131); + --console-background-text: var(--primary-text-color, #eee); + --console-surface: var(--secondary-color, var(--primary-color, #313131)); + --console-surface-text: var( + --secondary-text-color, + var(--primary-text-color, #eee) + ); + --console-control: var( + --popup-background-color, + var(--secondary-color, #414141) + ); + --console-control-text: var( + --popup-text-color, + var(--secondary-text-color, #d6d3d3) + ); + --console-active: var(--active-color, #555); + --console-active-text: var(--active-text-color, #fff); + --console-border: var(--border-color, rgba(204, 204, 204, 0.35)); + --console-muted: var(--secondary-text-color, #aaa); + --console-placeholder: var(--secondary-text-color, #929292); + --console-scrollbar: var(--scrollbar-color, #666); + --console-danger: var(--error-text-color, #ff9b9b); + --console-danger-fill: var(--danger-color, #a00000); + --console-warning-fill: var(--error-text-color, #ffb95c); + --console-link: var(--link-text-color, #99f); + --console-accent: var(--active-color, rgb(120, 105, 225)); + --console-function: var(--secondary-text-color, rgb(145, 136, 168)); + --console-success: var(--success-text-color, rgb(59, 161, 59)); +} + +html[theme-type="light"] c-console { + color-scheme: light; +} + // The console-only Activity already resizes its WebView for the keyboard. // Filling that layout directly avoids stale visualViewport pixels after the // keyboard closes; the floating page overlay still uses measured CSS variables. @@ -41,18 +97,19 @@ c-console[standalone] { border: 0; border-radius: 3px; background: transparent; - color: #d6d3d3; + color: var(--console-surface-text); font: inherit; font-size: 0.76rem; touch-action: manipulation; } .__c-action:active { - background: #555; + background: var(--console-active); + color: var(--console-active-text); } .__c-action-danger { - color: #ff9b9b; + color: var(--console-danger); } .__c-context { @@ -63,14 +120,14 @@ c-console[standalone] { border: 0; border-radius: 3px; outline: none; - background: #414141; - color: #d6d3d3; + background: var(--console-control); + color: var(--console-control-text); font: inherit; font-size: 0.7rem; } .__c-context:focus-visible { - outline: 1px solid #929292; + outline: 1px solid var(--console-active); outline-offset: -1px; } @@ -85,7 +142,7 @@ c-output { overflow: auto; overscroll-behavior: contain; scrollbar-width: thin; - scrollbar-color: #666 transparent; + scrollbar-color: var(--console-scrollbar) transparent; } c-output > .variable-virtual-spacer, @@ -102,14 +159,15 @@ c-input { width: 100%; min-height: 96px; padding: 7px 7px max(8px, env(safe-area-inset-bottom)); - background: #313131; + background: var(--console-surface); + color: var(--console-surface-text); } c-input::before { content: ">>"; align-self: flex-start; padding-top: 9px; - color: #d6d3d3; + color: var(--console-accent); font: italic 13px/1 "Courier New", Courier, monospace; } @@ -128,7 +186,7 @@ c-input::before { resize: none; overflow-y: auto; background: transparent; - color: #fff; + color: var(--console-surface-text); caret-color: currentColor !important; font: 13px/1.45 "Courier New", Courier, monospace; } @@ -142,7 +200,8 @@ c-input-actions { } #__c-input::placeholder { - color: #929292; + color: var(--console-placeholder); + opacity: 0.75; } #__c-input:disabled { @@ -156,17 +215,28 @@ c-message { flex-wrap: wrap; min-height: 45px; padding: 5px 7px 4px; - border-bottom: 1px solid rgba(204, 204, 204, 0.35); - background: #313131; + border-bottom: 1px solid var(--console-border); + background: var(--console-surface); + color: var(--console-surface-text); font-size: 0.9rem; } c-message[log-level="error"] { - background: #422; + background: var(--console-error-surface); + background: color-mix( + in srgb, + var(--console-danger-fill) 16%, + var(--console-surface) + ); } c-message[log-level="warn"] { - background: #633; + background: var(--console-warning-surface); + background: color-mix( + in srgb, + var(--console-warning-fill) 20%, + var(--console-surface) + ); } c-text, @@ -185,7 +255,7 @@ c-code { position: relative; padding-right: 42px; overflow-x: auto; - color: #d6d3d3; + color: var(--console-surface-text); font-family: "Courier New", Courier, monospace; white-space: pre-wrap; cursor: pointer; @@ -204,8 +274,8 @@ c-code::after { right: 1px; padding: 1px 6px; border-radius: 3px; - background: #666; - color: inherit; + background: var(--console-active); + color: var(--console-active-text); font: 0.62rem/1.4 var(--app-font-family, sans-serif); } @@ -218,7 +288,8 @@ c-stack { min-width: 0; margin-top: 3px; padding: 3px 4px 0; - color: #aaa; + color: var(--console-muted); + opacity: 0.8; font-size: 0.7rem; } @@ -236,7 +307,7 @@ c-trace { } c-object { - color: #99f; + color: var(--console-link); text-decoration: underline; } @@ -246,28 +317,28 @@ c-line { c-key { margin-right: 5px; - color: #c6f; + color: var(--console-accent); font-size: 0.9rem; } c-text.__c-boolean, c-text.__c-number, c-text.__c-symbol { - color: rgb(120, 105, 225); + color: var(--console-accent); } c-text.__c-function { - color: rgb(145, 136, 168); + color: var(--console-function); font-family: "Courier New", Courier, monospace; } c-text.__c-object, c-text.__c-undefined { - color: rgb(118, 163, 118); + color: var(--console-success); } c-text.__c-string { - color: rgb(59, 161, 59); + color: var(--console-success); } c-text.__c-string:not(.no-quotes)::before { @@ -316,12 +387,12 @@ c-table { display: table; width: 100%; border-collapse: collapse; - color: #d6d3d3; + color: var(--console-surface-text); } c-table c-row { display: table-row; - border-bottom: 1px solid rgba(204, 204, 204, 0.4); + border-bottom: 1px solid var(--console-border); } c-table c-cell { diff --git a/src/views/console.hbs b/src/views/console.hbs index b4589af735..40068d74c8 100644 --- a/src/views/console.hbs +++ b/src/views/console.hbs @@ -1,9 +1,11 @@ - + - + + + Console - - + +