import React from "react";
import ReactDOM from "react-dom/client";
import browser from "webextension-polyfill";
import type { VNode } from "./lib/extract";
import { cssPropertiesDefault } from "./lib/CSSPropertiesDefaultValue";
console.log("Hello from sidebar!");
function getUniqueSelector(el: Element) {
if (!(el instanceof Element))
throw new Error("Invalid argument");
var path = [];
while (el.nodeType === Node.ELEMENT_NODE) {
let selector = el.nodeName.toLowerCase();
const parentElement = el.parentElement;
if (el.id) {
selector += '#' + el.id;
path.unshift(selector);
break;
}
if (!parentElement) {
break;
}
let nth = 1;
let sib = el.previousElementSibling;
while (sib) {
nth++;
sib = sib.previousElementSibling;
}
selector += ":nth-child(" + nth + ")";
path.unshift(selector);
el = parentElement;
}
return path.join(" > ");
}
const code = `
(() => {
let getUniqueSelector = ${getUniqueSelector.toString()};
return getUniqueSelector($0);
})()
`
function useSelectedElement() {
const [element, setElement] = React.useState<{
vnode: VNode,
inheritedStyle: { [key: string]: string }
} | null>();
React.useEffect(() => {
const handler = async () => {
const id = Math.floor(Math.random() * 1000000);
// get selector of the selected element
console.log("evaluating code");
const [selector, isException] = await browser.devtools.inspectedWindow.eval(code);
if (isException) {
console.error("Error evaluating code", isException);
return;
}
console.log(selector);
const tab = await browser.tabs.query({ active: true, currentWindow: true });
const tabId = browser.devtools.inspectedWindow.tabId;
console.log("sending message", tabId);
// post message to content script to get vnode of the selected element
const res = await browser.tabs.sendMessage(tabId, { type: "cloneWithStyle", elementSelector: selector, id });
console.log("got response", res);
if (!res) {
console.error("No response");
return;
}
if (res.type !== "cloneWithStyleResult") {
console.error("Unexpected response", res);
return;
}
console.log("got response");
setElement({
vnode: res.vnode,
inheritedStyle: res.inheritedStyle
});
};
if (!element) {
handler();
}
browser.devtools.panels.elements.onSelectionChanged.addListener(handler);
return () => {
browser.devtools.panels.elements.onSelectionChanged.removeListener(handler);
};
}, []);
return element;
}
function useGetComputedStyle() {
const [styles, setStyles] = React.useState<{ [key: string]: string } | null>(null);
React.useEffect(() => {
const handler = async () => {
const [styles, isException] = await browser.devtools.inspectedWindow.eval(`
(() => {
const element = $0;
const style = getComputedStyle(element);
const styles = {};
for (let i = 0; i < style.length; i++) {
const property = style.item(i);
styles[property] = style.getPropertyValue(property);
}
return styles;
})()
`);
if (isException) {
console.error("Error evaluating code", isException);
return;
}
let newStyles = {} as { [key: string]: string };
for (const key in styles) {
if (key in cssPropertiesDefault) {
const value = cssPropertiesDefault[key as keyof typeof cssPropertiesDefault];
if (styles[key] != value) {
newStyles[key] = value;
}
}
else {
newStyles[key] = styles[key];
}
}
setStyles(newStyles);
};
if (!styles) {
handler();
}
browser.devtools.panels.elements.onSelectionChanged.addListener(handler);
return () => {
browser.devtools.panels.elements.onSelectionChanged.removeListener(handler);
};
}, []);
return styles;
}
function kebabToCamel(str: string) {
return str.replace(/-./g, (match) => match[1].toUpperCase());
}
function htmlStyleToReactStyle(style: { [key: string]: string }) {
return Object.fromEntries(Object.entries(style).map(([key, value]) => [kebabToCamel(key), value]));
}
const HTMLComponentTable: { [key: string]: keyof JSX.IntrinsicElements } = {
"a": "a",
"abbr": "abbr",
"address": "address",
"area": "area",
"article": "article",
"aside": "aside",
// "audio": "audio",
"b": "b",
"base": "base",
"bdi": "bdi",
"bdo": "bdo",
"big": "big",
"blockquote": "blockquote",
"body": "body",
"br": "br",
"button": "button",
"canvas": "canvas",
"caption": "caption",
"center": "center",
"cite": "cite",
"code": "code",
"col": "col",
"colgroup": "colgroup",
"data": "data",
"datalist": "datalist",
"dd": "dd",
"del": "del",
"details": "details",
"dfn": "dfn",
"dialog": "dialog",
"div": "div",
"dl": "dl",
"dt": "dt",
"em": "em",
"embed": "embed",
"fieldset": "fieldset",
"figcaption": "figcaption",
"figure": "figure",
"footer": "footer",
"form": "form",
"h1": "h1",
"h2": "h2",
"h3": "h3",
"h4": "h4",
"h5": "h5",
"h6": "h6",
// "head": "head",
"header": "header",
"hgroup": "hgroup",
"hr": "hr",
// "html": "html",
"i": "i",
// "iframe": "iframe",
// "img": "img",
"input": "input",
"ins": "ins",
"kbd": "kbd",
"keygen": "keygen",
"label": "label",
"legend": "legend",
"li": "li",
"link": "link",
"main": "main",
"map": "map",
"mark": "mark",
"menu": "menu",
"menuitem": "menuitem",
// "meta": "meta",
"meter": "meter",
"nav": "nav",
"noindex": "noindex",
"noscript": "noscript",
"object": "object",
"ol": "ol",
"optgroup": "optgroup",
"option": "option",
"output": "output",
"p": "p",
"param": "param",
"picture": "picture",
"pre": "pre",
"progress": "progress",
"q": "q",
"rp": "rp",
"rt": "rt",
"ruby": "ruby",
"s": "s",
"samp": "samp",
"search": "search",
"slot": "slot",
"script": "script",
"section": "section",
"select": "select",
"small": "small",
"source": "source",
"span": "span",
"strong": "strong",
"style": "style",
"sub": "sub",
"summary": "summary",
"sup": "sup",
"table": "table",
"template": "template",
"tbody": "tbody",
"td": "td",
"textarea": "textarea",
"tfoot": "tfoot",
"th": "th",
"thead": "thead",
"time": "time",
"title": "title",
"tr": "tr",
"track": "track",
"u": "u",
"ul": "ul",
"var": "var",
// "video": "video",
"wbr": "wbr",
"webview": "webview",
}
function VnodeToReact({ vnode }: { vnode: VNode }): React.ReactNode {
if (vnode.type === "text") {
return vnode.text;
}
else if (vnode.type === "element") {
// TODO: support svg
const Tag = HTMLComponentTable[vnode.tagName] ?? "div";
const style = vnode.style;
const children = vnode.children;
return
{inheritedStyleText}
{html}
{data &&