diff --git a/devtools/background.js b/devtools/background.js index 6c8c76f..31c33df 100644 --- a/devtools/background.js +++ b/devtools/background.js @@ -1,24 +1,54 @@ console.log("background.js loaded"); -// Listen for messages from the devtools panel browser.runtime.onMessage.addListener((message, sender, sendResponse) => { - // You could add a check to see if the message is from the devtools panel. if (message.type === "GET_CONTEXT_STACK") { - // Forward the message to the content script running in the active tab. - browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => { - if (tabs.length) { - browser.tabs.sendMessage(tabs[0].id, message).then(sendResponse); - } - }); - // Return true to indicate you wish to send a response asynchronously + browser.tabs.sendMessage(message.tabId, message) + .then((res) => { + console.log("RESPONSE", res); + sendResponse(res); + }) + .catch((err) => { + console.log("Error sending message to content script:", err); + sendResponse({ error: err }); + }); + // browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => { + // if (tabs.length) { + // } + // }); return true; } else if (message.type === "UPDATE_CONTEXT_VALUE") { - // Forward update messages similarly - browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => { - if (tabs.length) { - browser.tabs.sendMessage(tabs[0].id, message).then(sendResponse); - } - }); + browser.tabs.sendMessage(message.tabId, message).then(sendResponse); + // browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => { + // if (tabs.length) { + // } + // }); return true; } }); + +let devtoolsPort = null; + +browser.runtime.onConnect.addListener((port) => { + if (port.name === "devtools") { + devtoolsPort = port; + console.log("Devtools panel connected."); + + // port.onMessage.addListener((msg) => { + // console.log("Received message from devtools panel:", msg); + // }); + + port.onDisconnect.addListener(() => { + devtoolsPort = null; + }); + } +}); + +// Relay messages from content scripts to the devtools panel. +browser.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.type === "PAGE_LOADED") { + console.log("Background received PAGE_LOADED message from content script."); + if (devtoolsPort) { + devtoolsPort.postMessage({ type: "PAGE_LOADED" }); + } + } +}); diff --git a/devtools/content.js b/devtools/content.js index b935d84..d6662ba 100644 --- a/devtools/content.js +++ b/devtools/content.js @@ -1,20 +1,12 @@ -console.log("content.js loaded", window.wrappedJSObject); - -// setTimeout(() => { -const getContextStack = () => window.wrappedJSObject.getContextStack(); -const updateContextValue = (key, value, depth) => - window.wrappedJSObject.updateContextValue(key, value, depth); - -console.log(getContextStack()); browser.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === "GET_CONTEXT_STACK") { - const contextStack = getContextStack(); + const contextStack = window.wrappedJSObject.getContextStack(); sendResponse({ contextStack }); } else if (message.type === "UPDATE_CONTEXT_VALUE") { const { key, value, depth } = message; - if (glowindow.updateContextValue) { - updateContextValue(key, value, depth); + if (window.wrappedJSObject.updateContextValue) { + window.wrappedJSObject.updateContextValue(key, value, depth); sendResponse({ success: true }); } else { sendResponse({ @@ -24,4 +16,5 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => { } } }); -// }, 0); + +browser.runtime.sendMessage({ type: "PAGE_LOADED" }); diff --git a/devtools/manifest.json b/devtools/manifest.json index 24558ae..89caac5 100644 --- a/devtools/manifest.json +++ b/devtools/manifest.json @@ -16,7 +16,8 @@ ], "js": [ "content.js" - ] + ], + "all_frames": true } ], "permissions": [ diff --git a/devtools/panel.html b/devtools/panel.html index c193569..fa87a46 100644 --- a/devtools/panel.html +++ b/devtools/panel.html @@ -1,39 +1,48 @@ + + + Context Stack + - + form { + margin-left: 1rem; + margin-right: 1rem; + } + label { + display: inline-block; + margin-right: 1rem; + } + + - -

Context Stack

-
- - - - - \ No newline at end of file + +

Context Stack

+
+ + + + diff --git a/devtools/panel.js b/devtools/panel.js index 820f5f5..68c548f 100644 --- a/devtools/panel.js +++ b/devtools/panel.js @@ -1,52 +1,127 @@ +const port = browser.runtime.connect({ name: "devtools" }); +port.onMessage.addListener((message) => { + if (message.type === "PAGE_LOADED") { + loadContextStack(); // Refresh your context stack here. + } +}); + +const tabId = browser.devtools.inspectedWindow.tabId; + document.getElementById("refresh").addEventListener("click", loadContextStack); function loadContextStack() { const container = document.getElementById("contextContainer"); container.innerHTML = ""; - browser.runtime.sendMessage({ type: "GET_CONTEXT_STACK" }).then( + browser.runtime.sendMessage({ type: "GET_CONTEXT_STACK", tabId }).then( (response) => { + if (response.error) { + container.innerHTML = `

Error: ${response.error}

`; + return; + } + if (!response || !response.contextStack) { container.innerHTML = "

No context stack found.

"; return; } - const contextStack = response.contextStack; - for (const key in contextStack) { - const row = document.createElement("div"); - row.classList.add("context-row"); - - const keyDiv = document.createElement("div"); - keyDiv.classList.add("context-key"); - keyDiv.textContent = key; - - const valueDiv = document.createElement("div"); - valueDiv.classList.add("context-value"); - - const input = document.createElement("input"); - input.value = contextStack[key]; - input.addEventListener("change", () => { - browser.runtime.sendMessage({ - type: "UPDATE_CONTEXT_VALUE", - key, - value: input.value, - }).then((updateResponse) => { - if (updateResponse && updateResponse.success) { - console.log(`Updated ${key} to ${input.value}`); - } else { - console.error(`Failed to update ${key}:`, updateResponse.error); - } - }); - }); - - valueDiv.appendChild(input); - row.appendChild(keyDiv); - row.appendChild(valueDiv); - container.appendChild(row); - } + const contextStack = coalesceContextStack(response.contextStack); + const form = generateObjectForm(contextStack); + container.appendChild(form); }, ); } -// Initial load when the panel opens. -loadContextStack(); +function generateObjectForm(obj, path = "") { + const detail = document.createElement("details"); + detail.open = path === ""; + const summary = document.createElement("summary"); + summary.textContent = path.split(".").at(-1); + detail.appendChild(summary); + const form = document.createElement("form"); + let count = 0; + for (const [key, value] of Object.entries(obj)) { + if (value == undefined || value === "[Circular]") continue; + const isObject = value.constructor === Object; + const isArray = Array.isArray(value); + if (isObject || isArray) { + const nestedForm = generateObjectForm( + value, + path ? path + "." + key : key, + ); + if (!nestedForm) { + continue; + } + const div = document.createElement("div"); + div.appendChild(nestedForm); + form.appendChild(div); + count++; + continue; + } + const div = document.createElement("div"); + const label = document.createElement("label"); + label.textContent = key; + div.appendChild(label); + const input = document.createElement("input"); + input.name = key; + const isBoolean = typeof value === "boolean"; + if (isBoolean) { + input.type = "checkbox"; + input.checked = value; + input.addEventListener("change", () => { + browser.runtime.sendMessage({ + type: "UPDATE_CONTEXT_VALUE", + key: path ? path + "." + key : key, + value: input.checked, + tabId, + }).then((updateResponse) => { + if (updateResponse && updateResponse.success) { + console.log(`Updated ${key} to ${input.checked}`); + } else { + console.error(`Failed to update ${key}:`, updateResponse.error); + } + }); + }); + } else { + input.type = "text"; + input.value = value; + input.addEventListener("change", () => { + browser.runtime.sendMessage({ + type: "UPDATE_CONTEXT_VALUE", + key: path ? path + "." + key : key, + value: input.value, + tabId, + }).then((updateResponse) => { + if (updateResponse && updateResponse.success) { + console.log(`Updated ${key} to ${input.value}`); + } else { + console.error(`Failed to update ${key}:`, updateResponse.error); + } + }); + }); + } + div.appendChild(input); + form.appendChild(div); + count++; + } + if (count === 0) { + const pre = document.createElement("pre"); + pre.textContent = JSON.stringify(obj, null, 2); + detail.appendChild(pre); + } else { + detail.appendChild(form); + } + return detail; +} + +function coalesceContextStack(contextStack) { + const obj = {}; + for (const ctx of contextStack) { + Object.assign(obj, ctx); + } + return obj; +} + +document.addEventListener("DOMContentLoaded", () => { + loadContextStack(); +}); diff --git a/lib/context.ts b/lib/context.ts deleted file mode 100644 index 9856aa5..0000000 --- a/lib/context.ts +++ /dev/null @@ -1,115 +0,0 @@ -type ContextStore = Record; - -const defaultContext: ContextStore = {}; -const contextStack: ContextStore[] = [defaultContext]; - -const debug = JSON.parse(localStorage.getItem("debug") || "false"); - -export function setDefaultContext(context: ContextStore) { - Object.assign(defaultContext, context); -} - -export function withContext(context: ContextStore, fn: () => T): T { - contextStack.push(context); - try { - return fn(); - } finally { - contextStack.pop(); - } -} - -export const ctx = new Proxy( - {}, - { - get(_, prop: string) { - for (let i = contextStack.length - 1; i >= 0; i--) { - if (prop in contextStack[i]) return contextStack[i][prop]; - } - if (prop in defaultContext) return defaultContext[prop]; - throw new Error(`Context variable '${prop}' is not defined.`); - }, - }, -) as Record; - -export function getContext() { - return ctx; -} -export function getContextItem(prop: string): T { - return ctx[prop] as T; -} -export function getContextItemOrDefault(prop: string, defaultValue: T): T { - try { - return ctx[prop] as T; - } catch { - return defaultValue; - } -} -export function setContextItem(prop: string, value: T) { - Object.assign(contextStack[contextStack.length - 1] ?? defaultContext, { - [prop]: value, - }); -} - -if (debug) { - setInterval(() => { - let ctxEl = document.getElementById("context"); - if (!ctxEl) { - ctxEl = document.createElement("div"); - ctxEl.id = "context"; - document.body.append(ctxEl); - } - ctxEl.innerHTML = ""; - const div = document.createElement("div"); - const pre = document.createElement("pre"); - const h3 = document.createElement("h3"); - h3.textContent = "Default"; - div.append(h3); - pre.textContent = safeStringify(defaultContext); - div.append(pre); - ctxEl.append(div); - for (const [idx, ctx] of contextStack.entries()) { - const div = document.createElement("div"); - const pre = document.createElement("pre"); - const h3 = document.createElement("h3"); - h3.textContent = "CTX " + idx; - div.append(h3); - pre.textContent = safeStringify(ctx); - div.append(pre); - ctxEl.append(div); - } - }, 1000); -} - -function safeStringify(obj: any) { - const seen = new WeakSet(); - return JSON.stringify(obj, (key, value) => { - if (typeof value === "object" && value !== null) { - if (seen.has(value)) { - return "[Circular]"; // Replace circular references - } - seen.add(value); - } - return value; - }, 2); -} - -const getContextStack = () => contextStack.slice(); -const updateContextValue = ( - key: string, - value: unknown, - depth = contextStack.length - 1, -) => { - const context = contextStack[depth] ?? defaultContext; - context[key] = value; -}; -if (location.hostname === "localhost") { - globalThis.getContextStack = getContextStack; - globalThis.updateContextValue = updateContextValue; -} - -console.log("globalThis.getContextStack", globalThis.getContextStack); - -declare global { - var getContextStack: () => ContextStore[]; - var updateContextValue: (key: string, value: unknown, depth?: number) => void; -} diff --git a/src/lib/context.ts b/src/lib/context.ts index 466ac72..700771e 100644 --- a/src/lib/context.ts +++ b/src/lib/context.ts @@ -1,3 +1,5 @@ +import { TrackSegment } from "../track/system.ts"; + type ContextStore = Record; const defaultContext: ContextStore = {}; @@ -83,6 +85,27 @@ if (debug) { function safeStringify(obj: any) { const seen = new WeakSet(); return JSON.stringify(obj, (key, value) => { + if (value instanceof Map) { + const val: Record = {}; + for (const [k, v] of value) { + if (typeof k !== "string") continue; + val[k] = v; + } + return val; + } + + if (value instanceof Set) { + return Array.from(value); + } + + if (value instanceof TrackSegment) { + return { + ...value, + frontNeighbours: value.frontNeighbours.map((n) => n.id), + backNeighbours: value.backNeighbours.map((n) => n.id), + }; + } + if (typeof value === "object" && value !== null) { if (seen.has(value)) { return "[Circular]"; // Replace circular references @@ -92,3 +115,35 @@ function safeStringify(obj: any) { return value; }, 2); } +const getContextStack = () => JSON.parse(safeStringify(contextStack)); +const updateContextValue = ( + key: string, + value: unknown, + depth = contextStack.length - 1, +) => { + const keys = key.split("."); + let context = contextStack[depth] ?? defaultContext; + for (let i = 0; i < keys.length - 1; i++) { + if (context instanceof Map) { + context = context.get(keys[i]); + } else { + context = context[keys[i]]; + } + } + if (context instanceof Map) { + context.set(keys.at(-1)!, value); + } else { + context[keys.at(-1)!] = value; + } +}; +if (location.hostname === "localhost") { + globalThis.getContextStack = getContextStack; + globalThis.updateContextValue = updateContextValue; + globalThis.contextStack = contextStack; +} + +declare global { + var getContextStack: () => ContextStore[]; + var updateContextValue: (key: string, value: unknown, depth?: number) => void; + var contextStack: ContextStore[]; +} diff --git a/src/state/states/LoadState.ts b/src/state/states/LoadState.ts index 8837ff5..12f5a0e 100644 --- a/src/state/states/LoadState.ts +++ b/src/state/states/LoadState.ts @@ -42,12 +42,12 @@ export class LoadState extends State { }); const doodler = getContextItem("doodler"); - this.layers.push(doodler.createLayer((_, __, dTime) => { - doodler.clearRect(new Vector(0, 0), doodler.width, doodler.height); - doodler.fillRect(new Vector(0, 0), doodler.width, doodler.height, { - color: "#302040", - }); - })); + // this.layers.push(doodler.createLayer((_, __, dTime) => { + // doodler.clearRect(new Vector(0, 0), doodler.width, doodler.height); + // doodler.fillRect(new Vector(0, 0), doodler.width, doodler.height, { + // color: "#302040", + // }); + // })); } override stop(): void { // noop