diff --git a/src/components/EvaluationsPanel.tsx b/src/components/EvaluationsPanel.tsx
index 9f232c4..deed726 100644
--- a/src/components/EvaluationsPanel.tsx
+++ b/src/components/EvaluationsPanel.tsx
@@ -1,68 +1,62 @@
import React from "react";
+import { Button, Tooltip, ButtonGroup } from "@blueprintjs/core";
import { PanelSelector } from "./PanelSelector";
import { PropTree, StandaloneValue } from "./PropTree";
+import { useEvaluationsContext } from "../libs/EvaluationsContext";
export function EvaluationsPanel(): React.ReactElement {
+ const { evaluations, setEvaluations } = useEvaluationsContext();
+
+ const handleClear = React.useCallback(() => {
+ setEvaluations([]);
+ }, [setEvaluations]);
+
return (
-
-
-
- Expression |
- Context |
- Result |
-
-
-
-
-
-
- {/* {'"'}
- {'<% EVENT.detail %>'}
- {'"'} */}
- |
-
-
- |
-
-
- |
-
-
-
-
- |
-
-
- |
-
-
- |
-
-
-
+
+
+
+
+ Expression |
+ Context |
+ Result |
+
+
+
+ {evaluations.map((item, key) => (
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+ ))}
+
+
+
);
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
index 0e3e472..7c0415b 100644
--- a/src/components/Layout.tsx
+++ b/src/components/Layout.tsx
@@ -5,9 +5,52 @@ import { BricksPanel } from "./BricksPanel";
import { SelectedPanelContext } from "../libs/SelectedPanelContext";
import { EvaluationsPanel } from "./EvaluationsPanel";
import { TransformationsPanel } from "./TransformationsPanel";
+import { Evaluation, Transformation, RepoWrapped } from "../libs/interfaces";
+import { EvaluationsContext } from "../libs/EvaluationsContext";
+import { MESSAGE_SOURCE_HOOK } from "../shared";
+import { TransformationsContext } from "../libs/TransformationsContext";
+import { Storage } from "../libs/Storage";
export function Layout(): React.ReactElement {
- const [selectedPanel, setSelectedPanel] = React.useState("Bricks");
+ const [selectedPanel, setSelectedPanel] = React.useState(
+ Storage.getItem("selectedPanel") ?? "Bricks"
+ );
+ const [evaluations, setEvaluations] = React.useState<
+ RepoWrapped[]
+ >([]);
+ const [transformations, setTransformations] = React.useState<
+ RepoWrapped[]
+ >([]);
+
+ React.useEffect(() => {
+ function onMessage(event: MessageEvent): void {
+ if (
+ event.data?.source === MESSAGE_SOURCE_HOOK &&
+ event.data.payload?.type === "evaluation"
+ ) {
+ setEvaluations(evaluations.concat(event.data.payload));
+ }
+ }
+ window.addEventListener("message", onMessage);
+ return (): void => window.removeEventListener("message", onMessage);
+ }, [evaluations]);
+
+ React.useEffect(() => {
+ function onMessage(event: MessageEvent): void {
+ if (
+ event.data?.source === MESSAGE_SOURCE_HOOK &&
+ event.data.payload?.type === "transformation"
+ ) {
+ setTransformations(transformations.concat(event.data.payload));
+ }
+ }
+ window.addEventListener("message", onMessage);
+ return (): void => window.removeEventListener("message", onMessage);
+ }, [transformations]);
+
+ React.useEffect(() => {
+ Storage.setItem("selectedPanel", selectedPanel);
+ }, [selectedPanel]);
const theme = chrome.devtools.panels.themeName === "dark" ? "dark" : "light";
@@ -22,9 +65,17 @@ export function Layout(): React.ReactElement {
value={{ selectedPanel, setSelectedPanel }}
>
{selectedPanel === "Evaluations" ? (
-
+
+
+
) : selectedPanel === "Transformations" ? (
-
+
+
+
) : (
)}
diff --git a/src/components/PropTree.tsx b/src/components/PropTree.tsx
index b083395..1974606 100644
--- a/src/components/PropTree.tsx
+++ b/src/components/PropTree.tsx
@@ -1,6 +1,13 @@
import React from "react";
import classNames from "classnames";
import { Icon } from "@blueprintjs/core";
+import { TransferSerializedWrapper } from "../libs/interfaces";
+
+function isTransferSerializedWrapper(
+ value: any
+): value is TransferSerializedWrapper {
+ return !!value?.$$brickNextDevtoolsSerialized;
+}
function isObject(value: any): value is Record {
return typeof value === "object" && value;
@@ -8,17 +15,31 @@ function isObject(value: any): value is Record {
interface PropTreeProps {
properties: any[] | Record;
+ repo?: any[];
}
-export function PropTree({ properties }: PropTreeProps): React.ReactElement {
+export function PropTree({
+ properties,
+ repo,
+}: PropTreeProps): React.ReactElement {
return (
{Array.isArray(properties)
? properties.map((item, index) => (
-
+
))
: Object.entries(properties).map((entry) => (
-
+
))}
);
@@ -27,11 +48,13 @@ export function PropTree({ properties }: PropTreeProps): React.ReactElement {
interface PropItemProps {
propName: string;
propValue: any;
+ repo?: any[];
}
export function PropItem({
propName,
propValue,
+ repo,
}: PropItemProps): React.ReactElement {
const [expanded, setExpanded] = React.useState(false);
@@ -39,7 +62,8 @@ export function PropItem({
setExpanded(!expanded);
}, [expanded]);
- const hasChildren = isObject(propValue);
+ const hasChildren =
+ isObject(propValue) && !isTransferSerializedWrapper(propValue);
return (
@@ -56,22 +80,30 @@ export function PropItem({
{propName}
:{" "}
-
+
- {isObject(propValue) && expanded && }
+ {hasChildren && expanded && (
+
+ )}
);
}
-export function StandaloneValue({ value }: { value: any }): React.ReactElement {
+export function StandaloneValue({
+ value,
+ repo,
+}: {
+ value: any;
+ repo?: any[];
+}): React.ReactElement {
const [expanded, setExpanded] = React.useState(false);
const handleClick = React.useCallback(() => {
setExpanded(!expanded);
}, [expanded]);
- const hasChildren = isObject(value);
+ const hasChildren = isObject(value) && !isTransferSerializedWrapper(value);
return (
@@ -83,10 +115,10 @@ export function StandaloneValue({ value }: { value: any }): React.ReactElement {
)}
-
+
- {isObject(value) && expanded && }
+ {hasChildren && expanded && }
);
}
@@ -94,12 +126,39 @@ export function StandaloneValue({ value }: { value: any }): React.ReactElement {
interface ValueStringifyProps {
value: any;
expanded?: boolean;
+ repo?: any[];
}
export function ValueStringify({
value,
expanded,
+ repo,
}: ValueStringifyProps): React.ReactElement {
+ if (isTransferSerializedWrapper(value)) {
+ const serialized = value.$$brickNextDevtoolsSerialized;
+ switch (serialized.type) {
+ case "object":
+ return (
+
+ {serialized.constructorName}
+ {" {}"}
+
+ );
+ case "function":
+ return ƒ;
+ case "ref":
+ return (
+
+ );
+ default:
+ return Unexpected;
+ }
+ }
+
if (Array.isArray(value)) {
if (expanded) {
return Array({value.length});
@@ -111,7 +170,7 @@ export function ValueStringify({
[
{value.map((item, index, array) => (
-
+
{index < array.length - 1 && ", "}
))}
@@ -122,7 +181,7 @@ export function ValueStringify({
if (isObject(value)) {
if (expanded) {
- return null;
+ return Object;
}
return (
@@ -130,7 +189,11 @@ export function ValueStringify({
{"{"}
{Object.entries(value).map((entry, index, array) => (
-
+
{index < array.length - 1 && ", "}
))}
@@ -143,7 +206,9 @@ export function ValueStringify({
return (
<>
{'"'}
- {value}
+
+ {value}
+
{'"'}
>
);
@@ -166,11 +231,32 @@ export function ValueStringify({
interface ValueItemStringifyProps {
item: any;
+ repo?: any[];
}
export function ValueItemStringify({
item,
+ repo,
}: ValueItemStringifyProps): React.ReactElement {
+ if (isTransferSerializedWrapper(item)) {
+ const serialized = item.$$brickNextDevtoolsSerialized;
+ switch (serialized.type) {
+ case "object":
+ return (
+
+ {serialized.constructorName}
+ {" {}"}
+
+ );
+ case "function":
+ return ƒ;
+ case "ref":
+ return ;
+ default:
+ return Unexpected;
+ }
+ }
+
if (Array.isArray(item)) {
return {`Array(${item.length})`};
}
@@ -201,18 +287,20 @@ export function ValueItemStringify({
interface ObjectPropStringifyProps {
propName: string;
propValue: any;
+ repo?: any[];
}
export function ObjectPropStringify({
propName,
propValue,
+ repo,
}: ObjectPropStringifyProps): React.ReactElement {
return (
{propName}
:{" "}
-
+
);
diff --git a/src/components/PropView.tsx b/src/components/PropView.tsx
index 81088ba..a90b2f9 100644
--- a/src/components/PropView.tsx
+++ b/src/components/PropView.tsx
@@ -5,13 +5,6 @@ import { HOOK_NAME } from "../shared";
import { PropTree } from "./PropTree";
import { BrickInfo } from "../libs/interfaces";
-// `Function`s can't be passed through `chrome.devtools.inspectedWindow.eval`.
-// Use a noop function to mock the event listener.
-// istanbul ignore next
-function noop(): void {
- // noop
-}
-
export function PropView(): React.ReactElement {
const { selectedBrick } = useSelectedBrickContext();
const [brickInfo, setBrickInfo] = React.useState({});
@@ -23,7 +16,7 @@ export function PropView(): React.ReactElement {
function (result: BrickInfo, error) {
// istanbul ignore if
if (error) {
- console.error("getBrickInfo()", error);
+ console.error("getBrickInfo()", error, result);
}
setBrickInfo(result);
@@ -49,9 +42,7 @@ export function PropView(): React.ReactElement {
events
diff --git a/src/components/TransformationsPanel.tsx b/src/components/TransformationsPanel.tsx
index 4dae1a7..a516c1f 100644
--- a/src/components/TransformationsPanel.tsx
+++ b/src/components/TransformationsPanel.tsx
@@ -1,13 +1,58 @@
import React from "react";
+import { Button, Tooltip, ButtonGroup } from "@blueprintjs/core";
import { PanelSelector } from "./PanelSelector";
+import { StandaloneValue } from "./PropTree";
+import { useTransformationsContext } from "../libs/TransformationsContext";
export function TransformationsPanel(): React.ReactElement {
+ const { transformations, setTransformations } = useTransformationsContext();
+
+ const handleClear = React.useCallback(() => {
+ setTransformations([]);
+ }, [setTransformations]);
+
return (
+
+
+
+
+
+ Transform |
+ Data |
+ Result |
+ Options |
+
+
+
+ {transformations.map((item, key) => (
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+ ))}
+
+
+
-
TransformationsPanel
);
}
diff --git a/src/hook.ts b/src/hook.ts
index 8108665..cf92f58 100644
--- a/src/hook.ts
+++ b/src/hook.ts
@@ -60,9 +60,9 @@ function injectHook(): void {
function getBrickInfo(uid: number): BrickInfo {
let properties: Record = {};
- let events: string[];
+ let events: [string, any][];
const element = uidToBrick.get(uid);
- if (["brick", "provider", "custom-element"].includes(element?.$$typeof)) {
+ if (["brick", "provider", "custom-template"].includes(element?.$$typeof)) {
const props: string[] =
(element.constructor as BrickElementConstructor)
._dev_only_definedProperties || [];
@@ -75,7 +75,7 @@ function injectHook(): void {
])
);
events = Array.isArray(element.$$eventListeners)
- ? element.$$eventListeners.map((item) => item[0])
+ ? element.$$eventListeners.map((item) => [item[0], item[2]])
: [];
}
return { properties, events };
@@ -184,14 +184,83 @@ function injectHook(): void {
});
}
+ function processPayload(payload: any, repo: any[], ref = new WeakMap()): any {
+ if (ref.has(payload)) {
+ const processed = ref.get(payload);
+ let repoIndex = repo.indexOf(processed);
+ if (repoIndex === -1) {
+ repoIndex = repo.length;
+ repo.push(processed);
+ }
+ return {
+ $$brickNextDevtoolsSerialized: {
+ type: "ref",
+ ref: repoIndex,
+ },
+ };
+ // repo[] = ref.get(payload);
+ // return ref.get(payload);
+ // return processed;
+ }
+ if (Array.isArray(payload)) {
+ const processed: any[] = [];
+ ref.set(payload, processed);
+ payload.forEach((item) => {
+ processed.push(processPayload(item, repo, ref));
+ });
+ return processed;
+ }
+ if (typeof payload === "object" && payload) {
+ if (payload.constructor === Object) {
+ const processed: Record = {};
+ ref.set(payload, processed);
+ Object.entries(payload).forEach((entry) => {
+ processed[entry[0]] = processPayload(entry[1], repo, ref);
+ });
+ return processed;
+ }
+ const processed = {
+ $$brickNextDevtoolsSerialized: {
+ type: "object",
+ constructorName: payload.constructor?.name || "UnknownObject",
+ cleanedValue: {},
+ },
+ };
+ ref.set(payload, processed);
+ return processed;
+ }
+ if (typeof payload === "function") {
+ const processed = {
+ $$brickNextDevtoolsSerialized: {
+ type: "function",
+ constructorName: payload.constructor?.name || "UnknownObject",
+ cleanedValue: {},
+ },
+ };
+ ref.set(payload, processed);
+ return processed;
+ }
+ return payload;
+ }
+
function emit(payload: any): void {
- window.postMessage(
- {
- source: MESSAGE_SOURCE_HOOK,
- payload,
- },
- "*"
- );
+ try {
+ const repo: any[] = [];
+ const processed = processPayload(payload.payload, repo);
+ window.postMessage(
+ {
+ source: MESSAGE_SOURCE_HOOK,
+ payload: {
+ ...payload,
+ payload: processed,
+ repo,
+ },
+ },
+ "*"
+ );
+ } catch (error) {
+ console.warn("brick-next-devtools emit failed:", error);
+ }
}
const hook = {
diff --git a/src/libs/EvaluationsContext.ts b/src/libs/EvaluationsContext.ts
new file mode 100644
index 0000000..b47f4d9
--- /dev/null
+++ b/src/libs/EvaluationsContext.ts
@@ -0,0 +1,13 @@
+import React from "react";
+import { Evaluation, RepoWrapped } from "./interfaces";
+
+export interface ContextOfEvaluations {
+ evaluations?: RepoWrapped[];
+ setEvaluations?: React.Dispatch[]>;
+}
+
+export const EvaluationsContext = React.createContext({});
+
+// istanbul ignore next
+export const useEvaluationsContext = (): ContextOfEvaluations =>
+ React.useContext(EvaluationsContext);
diff --git a/src/libs/Storage.ts b/src/libs/Storage.ts
new file mode 100644
index 0000000..b4d9531
--- /dev/null
+++ b/src/libs/Storage.ts
@@ -0,0 +1,25 @@
+function setItem(key: string, value: any): void {
+ sessionStorage.setItem(key, JSON.stringify(value));
+}
+
+function getItem(key: string): any {
+ const value = sessionStorage.getItem(key);
+ if (value === null) {
+ return null;
+ }
+ try {
+ return JSON.parse(value);
+ } catch (e) {
+ return null;
+ }
+}
+
+function clear(): void {
+ sessionStorage.clear();
+}
+
+export const Storage = {
+ setItem,
+ getItem,
+ clear,
+};
diff --git a/src/libs/TransformationsContext.ts b/src/libs/TransformationsContext.ts
new file mode 100644
index 0000000..42f8374
--- /dev/null
+++ b/src/libs/TransformationsContext.ts
@@ -0,0 +1,15 @@
+import React from "react";
+import { Transformation, RepoWrapped } from "./interfaces";
+
+export interface ContextOfTransformations {
+ transformations?: RepoWrapped[];
+ setTransformations?: React.Dispatch[]>;
+}
+
+export const TransformationsContext = React.createContext<
+ ContextOfTransformations
+>({});
+
+// istanbul ignore next
+export const useTransformationsContext = (): ContextOfTransformations =>
+ React.useContext(TransformationsContext);
diff --git a/src/libs/interfaces.ts b/src/libs/interfaces.ts
index 0a007df..800c13d 100644
--- a/src/libs/interfaces.ts
+++ b/src/libs/interfaces.ts
@@ -24,7 +24,7 @@ export interface RuntimeBrick {
export interface BrickElement extends HTMLElement {
$$typeof?: "brick" | "custom-template";
- $$eventListeners: [string, Function][];
+ $$eventListeners: [string, Function, any][];
}
export interface BrickElementConstructor extends Function {
@@ -37,7 +37,39 @@ export interface MountPointElement extends HTMLElement {
export interface BrickInfo {
properties?: Record;
- events?: string[];
+ events?: [string, any][];
}
export type BrowserTheme = "dark" | "light";
+
+export interface RepoWrapped {
+ payload: T;
+ repo: any[];
+}
+
+export interface Evaluation {
+ raw: string;
+ context: Record;
+ result: any;
+}
+
+export interface Transformation {
+ transform: any;
+ data: any;
+ options: {
+ from: string | string[];
+ mapArray: boolean | "auto";
+ };
+ result: any;
+}
+
+export interface TransferSerializedWrapper {
+ $$brickNextDevtoolsSerialized: TransferSerialized;
+}
+
+export interface TransferSerialized {
+ type: "function" | "object" | "number" | "undefined" | "ref";
+ constructorName?: string;
+ ref?: number;
+ cleanedValue?: any;
+}
diff --git a/src/style.css b/src/style.css
index c81021b..091135e 100644
--- a/src/style.css
+++ b/src/style.css
@@ -131,6 +131,7 @@ body {
}
.prop-item-label > .bp3-icon {
+ /* margin-top: -1px; */
vertical-align: middle;
color: var(--caret-color);
}