From 7f311a11e3b7dd46a76d2b544e14ddf515181781 Mon Sep 17 00:00:00 2001 From: weareoutman Date: Sun, 8 Oct 2023 17:15:38 +0800 Subject: [PATCH] feat: inspect page context and template state --- src/hook/getContext.ts | 23 +++++++++ src/hook/index.ts | 2 + src/hook/traverse.spec.ts | 29 +++++++++-- src/hook/traverse.ts | 16 +++++- src/panel/components/ContextPanel.spec.tsx | 10 ++++ src/panel/components/ContextPanel.tsx | 59 ++++++++++++++++++++++ src/panel/components/Layout.tsx | 15 ++++-- src/panel/components/PanelSelector.tsx | 2 +- src/panel/components/PropView.tsx | 1 + src/panel/style.css | 12 +++++ src/shared/interfaces.ts | 7 ++- 11 files changed, 165 insertions(+), 11 deletions(-) create mode 100644 src/hook/getContext.ts create mode 100644 src/panel/components/ContextPanel.spec.tsx create mode 100644 src/panel/components/ContextPanel.tsx diff --git a/src/hook/getContext.ts b/src/hook/getContext.ts new file mode 100644 index 0000000..0433e24 --- /dev/null +++ b/src/hook/getContext.ts @@ -0,0 +1,23 @@ +interface DLL { + (moduleId: "tYg3"): { + developHelper: { + getAllContextValues(options: { + tplContextId?: string; + }): Map; + }; + }; +} + +export function getContext( + tplContextId?: string +): Record | null { + const { dll } = (window as unknown) as { dll?: DLL }; + if (typeof dll === "function") { + return Object.fromEntries( + [...dll("tYg3").developHelper.getAllContextValues({ tplContextId })] + .map(([key, { value }]) => [key, value]) + .sort(([k1], [k2]) => (k1 > k2 ? 1 : -1)) + ); + } + return null; +} diff --git a/src/hook/index.ts b/src/hook/index.ts index 61781f5..96e961e 100644 --- a/src/hook/index.ts +++ b/src/hook/index.ts @@ -4,6 +4,7 @@ import { inspectElement, dismissInspections } from "./inspector"; import { emit } from "./emit"; import { overrideProps } from "./overrideProps"; import { restoreDehydrated } from "./dehydrate"; +import { getContext } from "./getContext"; function injectHook(): void { if (Object.prototype.hasOwnProperty.call(window, HOOK_NAME)) { @@ -27,6 +28,7 @@ function injectHook(): void { // Methods below are exposed to Brick Next itself, keep compatible. emit, restoreDehydrated, + getContext, }; Object.defineProperty(hook, "pageHasBricks", { diff --git a/src/hook/traverse.spec.ts b/src/hook/traverse.spec.ts index 54fe808..f470910 100644 --- a/src/hook/traverse.spec.ts +++ b/src/hook/traverse.spec.ts @@ -5,7 +5,7 @@ import { DehydratedBrickInfo, } from "../shared/interfaces"; -customElements.define("your.awesome-tpl", class Tmp1 extends HTMLElement {}); +customElements.define("your.tpl-awesome", class Tmp1 extends HTMLElement {}); customElements.define("your.inner-brick", class Tmp1 extends HTMLElement {}); customElements.define( "your.awesome-provider", @@ -16,6 +16,16 @@ customElements.define( class Tmp1 extends HTMLElement {} ); +(global as any).dll = () => { + return { + developHelper: { + getAllContextValues() { + return new Map([["myState", "myStateValue"]]); + }, + }, + }; +}; + describe("traverse", () => { beforeEach(() => { const main = document.createElement("div"); @@ -30,7 +40,6 @@ describe("traverse", () => { const BrickProto = { constructor: { - // eslint-disable-next-line @typescript-eslint/camelcase _dev_only_definedProperties: ["propA", "propB"], }, }; @@ -48,12 +57,15 @@ describe("traverse", () => { { $$brick: { element: Object.assign(Object.create(BrickProto), { - tagName: "YOUR.AWESOME-TPL", + tagName: "YOUR.TPL-AWESOME", $$typeof: "custom-template", propB: "b in tpl", propA: "a in tpl", id: "tpl-1", $$eventListeners: [], + dataset: { + tplContextId: "tpl-context-1", + }, }), }, children: [ @@ -142,7 +154,7 @@ describe("traverse", () => { main: [ { uid: 1, - tagName: "your.awesome-tpl", + tagName: "your.tpl-awesome", invalid: false, children: [ { @@ -205,7 +217,7 @@ describe("traverse", () => { repo: [], }; - expect(getBrickByUid(1).tagName).toBe("YOUR.AWESOME-TPL"); + expect(getBrickByUid(1).tagName).toBe("YOUR.TPL-AWESOME"); expect(getBrickInfo(1)).toEqual({ info: { @@ -217,6 +229,13 @@ describe("traverse", () => { propB: "b in tpl", }, events: [], + tplState: { + myState: { + $$brickNextDevtoolsDehydrated: { + type: "undefined", + }, + }, + }, }, repo: [], }); diff --git a/src/hook/traverse.ts b/src/hook/traverse.ts index ac42b0f..d1f7d44 100644 --- a/src/hook/traverse.ts +++ b/src/hook/traverse.ts @@ -6,8 +6,10 @@ import { RichBrickData, MountPointElement, BrickNode, + BrickInfo, } from "../shared/interfaces"; import { dehydrate } from "./dehydrate"; +import { getContext } from "./getContext"; function isV3OrAbove(): boolean { const { "brick-container": version } = @@ -41,6 +43,7 @@ export function getBrickInfo(uid: number): DehydratedBrickInfo { const nativeProperties: Record = {}; let properties: Record = {}; let events: [string, any][] = []; + let tplState: Record | undefined; const element = uidToBrick.get(uid); if (element) { if (element.id) { @@ -52,6 +55,13 @@ export function getBrickInfo(uid: number): DehydratedBrickInfo { if (element.slot) { nativeProperties.slot = element.slot; } + if (element.tagName.includes(".TPL-")) { + const tplContextId = + element.dataset[isV3OrAbove() ? "tplStateStoreId" : "tplContextId"]; + if (tplContextId) { + tplState = getContext(tplContextId); + } + } if (isBrickElement(element)) { const props: string[] = (element.constructor as BrickElementConstructor) @@ -70,7 +80,11 @@ export function getBrickInfo(uid: number): DehydratedBrickInfo { } } const repo: any[] = []; - const info = dehydrate({ nativeProperties, properties, events }, repo); + const originalInfo: BrickInfo = { nativeProperties, properties, events }; + if (tplState) { + originalInfo.tplState = tplState; + } + const info = dehydrate(originalInfo, repo); return { info, repo }; } diff --git a/src/panel/components/ContextPanel.spec.tsx b/src/panel/components/ContextPanel.spec.tsx new file mode 100644 index 0000000..15b3d94 --- /dev/null +++ b/src/panel/components/ContextPanel.spec.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import { shallow } from "enzyme"; +import { ContextPanel } from "./ContextPanel"; + +describe("ContextPanel", () => { + it("should work", () => { + const wrapper = shallow(); + expect(wrapper.hasClass("context-panel")).toBe(true); + }); +}); diff --git a/src/panel/components/ContextPanel.tsx b/src/panel/components/ContextPanel.tsx new file mode 100644 index 0000000..af3d844 --- /dev/null +++ b/src/panel/components/ContextPanel.tsx @@ -0,0 +1,59 @@ +import React from "react"; +import { InspectContextSelector } from "./InspectContextSelector"; +import { PanelSelector } from "./PanelSelector"; +import { HOOK_NAME } from "../../shared/constants"; +import { useEvalOptions } from "../libs/useEvalOptions"; +import { PropList } from "./PropList"; +import { Button, ButtonGroup, Tooltip } from "@blueprintjs/core"; + +export function ContextPanel(): React.ReactElement { + const [values, setValues] = React.useState | null>( + {} + ); + const evalOptions = useEvalOptions(); + + const handleRefresh = React.useCallback(() => { + chrome.devtools.inspectedWindow.eval( + `window.${HOOK_NAME} && window.${HOOK_NAME}.getContext()`, + evalOptions, + function (result: Record, error) { + // istanbul ignore if + if (error) { + console.error("getContext()", error); + setValues(null); + } else { + setValues(result); + } + } + ); + }, [evalOptions]); + + React.useEffect(() => { + handleRefresh(); + }, [handleRefresh]); + + return ( +
+
+
+ + + + +
+
+
+
+ {values ? ( + + ) : ( +

Inspect context failed

+ )} +
+
+
+ ); +} diff --git a/src/panel/components/Layout.tsx b/src/panel/components/Layout.tsx index 79bf61d..37c8955 100644 --- a/src/panel/components/Layout.tsx +++ b/src/panel/components/Layout.tsx @@ -22,9 +22,10 @@ import { PANEL_CHANGE, } from "../../shared/constants"; import { TransformationsContext } from "../libs/TransformationsContext"; -import { Storage } from "../libs/Storage"; +import { LocalJsonStorage, Storage } from "../libs/Storage"; import { hydrate } from "../libs/hydrate"; import { SelectedInspectContext } from "../libs/SelectedInspectContext"; +import { ContextPanel } from "./ContextPanel"; let uniqueIdCounter = 0; function getUniqueId(): number { @@ -42,7 +43,7 @@ export function Layout(): React.ReactElement { Record >({ 0: [] }); const [logNumber, setLogNumber] = React.useState( - Storage.getItem("logNumber") ?? 50 + LocalJsonStorage.getItem("logNumber") ?? 50 ); const [preserveLogs, savePreserveLogs] = React.useState(false); const [inspectFrameIndex, setInspectFrameIndex] = React.useState( @@ -275,7 +276,13 @@ export function Layout(): React.ReactElement { } window.addEventListener("message", onMessage); return (): void => window.removeEventListener("message", onMessage); - }, [inspectFrameIndex, selectedPanel, logNumber, setEvaluationsByFrameId]); + }, [ + inspectFrameIndex, + selectedPanel, + logNumber, + setEvaluationsByFrameId, + getFrameIdByFrameIndex, + ]); React.useEffect(() => { function onMessage(event: MessageEvent): void { @@ -454,6 +461,8 @@ export function Layout(): React.ReactElement { + ) : selectedPanel === "Context" ? ( + ) : ( )} diff --git a/src/panel/components/PanelSelector.tsx b/src/panel/components/PanelSelector.tsx index 0dba7d3..65b9327 100644 --- a/src/panel/components/PanelSelector.tsx +++ b/src/panel/components/PanelSelector.tsx @@ -19,7 +19,7 @@ export function PanelSelector({ { diff --git a/src/panel/components/PropView.tsx b/src/panel/components/PropView.tsx index c3bfa5e..70c283a 100644 --- a/src/panel/components/PropView.tsx +++ b/src/panel/components/PropView.tsx @@ -31,6 +31,7 @@ const tagsToDisplay: [keyof BrickInfo, string][] = [ ["nativeProperties", "native properties"], ["properties", "properties"], ["events", "events"], + ["tplState", "template state"], ]; export function PropView(): React.ReactElement { diff --git a/src/panel/style.css b/src/panel/style.css index 26c56d5..c9550a8 100644 --- a/src/panel/style.css +++ b/src/panel/style.css @@ -58,6 +58,7 @@ body { box-sizing: border-box; } +.context-panel, .evaluations-panel, .transformations-panel { flex-direction: column; @@ -74,6 +75,7 @@ body { border-left: 1px solid var(--pane-border-color); } +.context-toolbar, .evaluations-toolbar, .transformations-toolbar, .tree-toolbar, @@ -113,6 +115,7 @@ body { } .brick-tree, +.context-view, .prop-view, .table-view { flex: 1 0 auto; @@ -126,6 +129,10 @@ body { height: 100%; } +.context-view > .scroll-container { + padding: 10px 0; +} + .source-code { font-family: Menlo, monospace; } @@ -279,3 +286,8 @@ body { background-color: var(--root-bg-color); border-bottom: 1px solid var(--pane-border-color); } + +.context-view > .scroll-container > .error-message { + color: #f5498b; + padding: 0 10px; +} diff --git a/src/shared/interfaces.ts b/src/shared/interfaces.ts index a4c9f5e..a6c2df2 100644 --- a/src/shared/interfaces.ts +++ b/src/shared/interfaces.ts @@ -47,6 +47,7 @@ export interface BrickInfo { nativeProperties?: Record; properties?: Record; events?: [string, any][]; + tplState?: Record; } export type BrowserTheme = "dark" | "light"; @@ -124,4 +125,8 @@ export interface FrameData { frameURL: string; } -export type PanelType = "Bricks" | "Evaluations" | "Transformations"; +export type PanelType = + | "Bricks" + | "Context" + | "Evaluations" + | "Transformations";