@refinedev/devtools 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +35 -0
  3. package/dist/components/devtools-pin.d.ts +10 -0
  4. package/dist/components/devtools-pin.d.ts.map +1 -0
  5. package/dist/components/devtools-selector.d.ts +8 -0
  6. package/dist/components/devtools-selector.d.ts.map +1 -0
  7. package/dist/components/icons/devtools-icon.d.ts +8 -0
  8. package/dist/components/icons/devtools-icon.d.ts.map +1 -0
  9. package/dist/components/icons/resize-handle-icon.d.ts +3 -0
  10. package/dist/components/icons/resize-handle-icon.d.ts.map +1 -0
  11. package/dist/components/icons/selector-button.d.ts +3 -0
  12. package/dist/components/icons/selector-button.d.ts.map +1 -0
  13. package/dist/components/resizable-pane.d.ts +19 -0
  14. package/dist/components/resizable-pane.d.ts.map +1 -0
  15. package/dist/components/selector-box.d.ts +8 -0
  16. package/dist/components/selector-box.d.ts.map +1 -0
  17. package/dist/components/selector-hint.d.ts +5 -0
  18. package/dist/components/selector-hint.d.ts.map +1 -0
  19. package/dist/esm/index.js +8 -0
  20. package/dist/esm/index.js.map +1 -0
  21. package/dist/iife/index.js +8 -0
  22. package/dist/iife/index.js.map +1 -0
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +8 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/interfaces/placement.d.ts +2 -0
  28. package/dist/interfaces/placement.d.ts.map +1 -0
  29. package/dist/panel.d.ts +2 -0
  30. package/dist/panel.d.ts.map +1 -0
  31. package/dist/provider.d.ts +3 -0
  32. package/dist/provider.d.ts.map +1 -0
  33. package/dist/utilities/index.d.ts +37 -0
  34. package/dist/utilities/index.d.ts.map +1 -0
  35. package/dist/utilities/use-selector.d.ts +10 -0
  36. package/dist/utilities/use-selector.d.ts.map +1 -0
  37. package/package.json +62 -0
  38. package/src/components/devtools-pin.tsx +62 -0
  39. package/src/components/devtools-selector.tsx +127 -0
  40. package/src/components/icons/devtools-icon.tsx +91 -0
  41. package/src/components/icons/resize-handle-icon.tsx +27 -0
  42. package/src/components/icons/selector-button.tsx +81 -0
  43. package/src/components/resizable-pane.tsx +264 -0
  44. package/src/components/selector-box.tsx +80 -0
  45. package/src/components/selector-hint.tsx +63 -0
  46. package/src/define.d.ts +1 -0
  47. package/src/index.ts +2 -0
  48. package/src/interfaces/placement.ts +1 -0
  49. package/src/panel.tsx +93 -0
  50. package/src/provider.tsx +11 -0
  51. package/src/utilities/index.ts +142 -0
  52. package/src/utilities/use-selector.tsx +242 -0
@@ -0,0 +1,80 @@
1
+ import React from "react";
2
+
3
+ export const SelectorBox = ({
4
+ width,
5
+ height,
6
+ x,
7
+ y,
8
+ name,
9
+ }: {
10
+ width: number;
11
+ height: number;
12
+ x: number;
13
+ y: number;
14
+ name: string;
15
+ }) => {
16
+ const namePosition = y - 6 > 25 ? "top" : "bottom";
17
+
18
+ const outlinePosition = x > 7 ? "outside" : "inside";
19
+
20
+ return (
21
+ <div
22
+ id="selector-box"
23
+ style={{
24
+ pointerEvents: "none",
25
+ position: "fixed",
26
+ opacity: name ? 1 : 0,
27
+ transition: "all 0.2s ease-in-out",
28
+ border: "1.5px solid #47EBEB",
29
+ borderRadius: "4px",
30
+ borderTopLeftRadius: 0,
31
+ background: "rgba(37,160,160, 0.25)",
32
+ backdropFilter: "sepia(30%) hue-rotate(180deg)",
33
+ zIndex: 99998,
34
+ ...(outlinePosition === "outside"
35
+ ? {
36
+ left: -6,
37
+ top: -6,
38
+ width: width + 10,
39
+ height: height + 10,
40
+ transform: `translate(${x}px, ${y}px)`,
41
+ }
42
+ : {
43
+ left: 0,
44
+ top: 0,
45
+ width: width - 10,
46
+ height: height - 20,
47
+ transform: `translate(${x + 4}px, ${y + 4}px)`,
48
+ }),
49
+ }}
50
+ >
51
+ <div
52
+ style={{
53
+ position: "absolute",
54
+ left: "-1.5px",
55
+ background: "#1D1E30",
56
+ padding: "4px 6px",
57
+ border: "1.5px solid #47EBEB",
58
+ fontSize: "13px",
59
+ lineHeight: "16px",
60
+ fontWeight: 600,
61
+ color: "#CFD7E2",
62
+ display: name ? "block" : "none",
63
+ ...(namePosition === "top"
64
+ ? {
65
+ top: "-27px",
66
+ borderTopLeftRadius: "4px",
67
+ borderTopRightRadius: "4px",
68
+ }
69
+ : {
70
+ top: "-1.5px",
71
+ borderBottomLeftRadius: "0",
72
+ borderBottomRightRadius: "4px",
73
+ }),
74
+ }}
75
+ >
76
+ {name}
77
+ </div>
78
+ </div>
79
+ );
80
+ };
@@ -0,0 +1,63 @@
1
+ import React from "react";
2
+
3
+ export const SelectorHint = ({
4
+ active,
5
+ groupHover,
6
+ }: {
7
+ active: boolean;
8
+ groupHover?: boolean;
9
+ }) => {
10
+ return (
11
+ <div
12
+ style={{
13
+ pointerEvents: "none",
14
+ position: "absolute",
15
+ left: "calc(-50% - 100px - 14px)",
16
+ top: "-50px",
17
+ width: "200px",
18
+ opacity: active ? 1 : 0,
19
+ transform: groupHover ? "translateX(0)" : "translateX(40px)",
20
+ transitionDuration: "0.2s",
21
+ transitionProperty: "transform,opacity",
22
+ transitionTimingFunction: "ease-in-out",
23
+ padding: "8px",
24
+ fontSize: "10px",
25
+ lineHeight: "12px",
26
+ fontWeight: 400,
27
+ textShadow:
28
+ "0 0 2px #1D1E30, 1px 0 2px #1D1E30, -1px 0 2px #1D1E30, 0 1px 2px #1D1E30, 0 -1px 2px #1D1E30",
29
+ }}
30
+ >
31
+ <kbd
32
+ style={{
33
+ marginLeft: "4px",
34
+ padding: "1px 2px",
35
+ borderRadius: "2px",
36
+ background: "whitesmoke",
37
+ color: "dimgray",
38
+ border: "0.5px solid silver",
39
+ boxShadow: "0 1px 1px silver",
40
+ textShadow: "none",
41
+ }}
42
+ >
43
+ shift
44
+ </kbd>{" "}
45
+ to move to parent.
46
+ <kbd
47
+ style={{
48
+ marginLeft: "4px",
49
+ padding: "1px 2px",
50
+ borderRadius: "2px",
51
+ background: "whitesmoke",
52
+ color: "dimgray",
53
+ border: "0.5px solid silver",
54
+ boxShadow: "0 1px 1px silver",
55
+ textShadow: "none",
56
+ }}
57
+ >
58
+ space
59
+ </kbd>{" "}
60
+ to highlight in monitor.
61
+ </div>
62
+ );
63
+ };
@@ -0,0 +1 @@
1
+ declare const __DEV_CONDITION__: string;
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { DevtoolsPanel } from "./panel";
2
+ export { DevtoolsProvider } from "./provider";
@@ -0,0 +1 @@
1
+ export type Placement = "bottom" | "left" | "right" | "top";
package/src/panel.tsx ADDED
@@ -0,0 +1,93 @@
1
+ import React from "react";
2
+ import { DevtoolsPin } from "./components/devtools-pin";
3
+ import { ResizablePane } from "./components/resizable-pane";
4
+
5
+ import { SIZE, getPinTransform } from "./utilities";
6
+
7
+ import { Placement } from "./interfaces/placement";
8
+ import {
9
+ DevToolsContext,
10
+ DevtoolsEvent,
11
+ send,
12
+ } from "@refinedev/devtools-shared";
13
+
14
+ export const DevtoolsPanel =
15
+ __DEV_CONDITION__ !== "development"
16
+ ? () => null
17
+ : () => {
18
+ const [visible, setVisible] = React.useState(false);
19
+ const [placement] = React.useState<Placement>("bottom");
20
+ const { devtoolsUrl, ws } = React.useContext(DevToolsContext);
21
+ const [hover, setHover] = React.useState(false);
22
+
23
+ const onSelectorHighlight = React.useCallback(
24
+ (name: string) => {
25
+ if (ws) {
26
+ send(
27
+ ws,
28
+ DevtoolsEvent.DEVTOOLS_HIGHLIGHT_IN_MONITOR,
29
+ {
30
+ name,
31
+ },
32
+ );
33
+ }
34
+ setVisible(true);
35
+ },
36
+ [ws],
37
+ );
38
+
39
+ const onSelectorOpen = React.useCallback(() => {
40
+ setVisible(false);
41
+ }, []);
42
+
43
+ return (
44
+ <div
45
+ style={{
46
+ position: "fixed",
47
+ left: 0,
48
+ top: 0,
49
+ zIndex: 99999,
50
+ width: `${SIZE * 2}px`,
51
+ height: `${SIZE}px`,
52
+
53
+ transform: getPinTransform(placement),
54
+ }}
55
+ onMouseEnter={() => setHover(true)}
56
+ onMouseLeave={() => setHover(false)}
57
+ >
58
+ <DevtoolsPin
59
+ active={visible}
60
+ onClick={() => setVisible((v) => !v)}
61
+ groupHover={hover}
62
+ onSelectorHighlight={onSelectorHighlight}
63
+ onSelectorOpen={onSelectorOpen}
64
+ />
65
+ <ResizablePane visible={visible} placement={placement}>
66
+ {({ resizing }) => (
67
+ <iframe
68
+ src={devtoolsUrl}
69
+ srcDoc={
70
+ devtoolsUrl
71
+ ? undefined
72
+ : `
73
+ <html style="height:100%;padding:0;margin:0;">
74
+ <body style="display:flex;justify-content:center;height:100%;padding:24px;margin:0;align-items:center;box-sizing:border-box;">
75
+ <h1 style="font-family:ui-monospace,monospace;color:#CFD7E2;text-align:center;">Could not connect to the devtools server</h1>
76
+ </body>
77
+ </html>
78
+ `
79
+ }
80
+ style={{
81
+ width: "100%",
82
+ height: "100%",
83
+ border: "none",
84
+ borderRadius: "7px",
85
+ pointerEvents: resizing ? "none" : "auto",
86
+ background: "#14141F",
87
+ }}
88
+ />
89
+ )}
90
+ </ResizablePane>
91
+ </div>
92
+ );
93
+ };
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { DevToolsContextProvider } from "@refinedev/devtools-shared";
3
+
4
+ export const DevtoolsProvider =
5
+ __DEV_CONDITION__ !== "development"
6
+ ? ({ children }: React.PropsWithChildren) => children as any
7
+ : ({ children }: React.PropsWithChildren) => {
8
+ return (
9
+ <DevToolsContextProvider>{children}</DevToolsContextProvider>
10
+ );
11
+ };
@@ -0,0 +1,142 @@
1
+ import { Placement } from "src/interfaces/placement";
2
+
3
+ export const getPanelToggleTransforms = (visible: boolean) => {
4
+ return visible ? "scaleX(1) translateY(0)" : `scaleX(0) translateY(25vw)`;
5
+ };
6
+
7
+ export const SIZE = 50;
8
+ export const BUFFER = 10;
9
+
10
+ const PREFERRED_DEFAULT_WIDTH = () =>
11
+ typeof window !== "undefined" ? window.innerWidth * 0.7 : 1440 * 0.7; // 70% of window width
12
+ const PREFERRED_DEFAULT_HEIGHT = () =>
13
+ typeof window !== "undefined" ? window.innerHeight * 0.7 : 900 * 0.7; // 70% of window height
14
+
15
+ export const MIN_PANEL_WIDTH = 640;
16
+ export const MIN_PANEL_HEIGHT = 360;
17
+
18
+ const verticalCenterTransform = `translateY(calc((100vh - ${SIZE}px) / 2))`;
19
+ const horizontalCenterTransform = `translateX(calc((100vw - ${
20
+ SIZE * 2
21
+ }px) / 2))`;
22
+ const rightAlignTransform = `translateX(calc((100vw - ${SIZE}px) - ${BUFFER}px))`;
23
+ const leftAlignTransform = `translateX(${BUFFER}px)`;
24
+ const topAlignTransform = `translateY(${BUFFER}px)`;
25
+ const bottomAlignTransform = `translateY(calc((100vh - ${SIZE}px) - ${0}px))`;
26
+
27
+ export const getPinTransform = (placement: Placement) => {
28
+ switch (placement) {
29
+ case "left":
30
+ return `${leftAlignTransform} ${verticalCenterTransform}`;
31
+ case "right":
32
+ return `${rightAlignTransform} ${verticalCenterTransform}`;
33
+ case "top":
34
+ return `${topAlignTransform} ${horizontalCenterTransform}`;
35
+ default:
36
+ case "bottom":
37
+ return `${bottomAlignTransform} ${horizontalCenterTransform}`;
38
+ }
39
+ };
40
+
41
+ export const getPinButtonTransform = (hover?: boolean) => {
42
+ return `translateY(${hover ? "0" : "50%"})`;
43
+ };
44
+
45
+ export const getPanelPosition = (placement: Placement) => {
46
+ switch (placement) {
47
+ case "left":
48
+ return {
49
+ left: `calc(${SIZE}px + ${BUFFER}px)`,
50
+ top: "50%",
51
+ transform: "translateY(-50%)",
52
+ };
53
+ case "right":
54
+ return {
55
+ right: `calc(${SIZE}px + ${BUFFER}px)`,
56
+ top: "50%",
57
+ transform: "translateY(-50%)",
58
+ };
59
+ case "top":
60
+ return {
61
+ left: "50%",
62
+ top: `calc(${SIZE}px + ${BUFFER}px)`,
63
+ transform: "translateX(-50%)",
64
+ };
65
+ default:
66
+ case "bottom":
67
+ return {
68
+ left: "50%",
69
+ bottom: `calc(${SIZE}px + ${BUFFER}px)`,
70
+ transform: "translateX(-50%)",
71
+ };
72
+ }
73
+ };
74
+
75
+ export const getMaxPanelWidth = (placement: Placement) => {
76
+ switch (placement) {
77
+ case "left":
78
+ case "right":
79
+ return (
80
+ -BUFFER -
81
+ SIZE -
82
+ BUFFER +
83
+ (typeof window !== "undefined" ? window.innerWidth : 1440) -
84
+ BUFFER
85
+ );
86
+ case "top":
87
+ case "bottom":
88
+ return (
89
+ -BUFFER +
90
+ (typeof window !== "undefined" ? window.innerWidth : 1440) -
91
+ BUFFER
92
+ );
93
+ }
94
+ };
95
+
96
+ export const getMaxPanelHeight = (placement: Placement) => {
97
+ switch (placement) {
98
+ case "left":
99
+ case "right":
100
+ return (
101
+ -BUFFER +
102
+ (typeof window !== "undefined" ? window.innerHeight : 900) -
103
+ BUFFER
104
+ );
105
+ case "top":
106
+ case "bottom":
107
+ return (
108
+ -BUFFER -
109
+ SIZE -
110
+ BUFFER +
111
+ (typeof window !== "undefined" ? window.innerHeight : 900) -
112
+ BUFFER
113
+ );
114
+ }
115
+ };
116
+
117
+ export const getDefaultPanelSize = (
118
+ placement: Placement,
119
+ preferredSize?: { width: number; height: number },
120
+ ): { width: number; height: number } => {
121
+ const defaultPreferred = {
122
+ width: PREFERRED_DEFAULT_WIDTH(),
123
+ height: PREFERRED_DEFAULT_HEIGHT(),
124
+ };
125
+
126
+ const maxPanelWidth = getMaxPanelWidth(placement);
127
+ const maxPanelHeight = getMaxPanelHeight(placement);
128
+
129
+ const width = Math.min(
130
+ maxPanelWidth,
131
+ (preferredSize ?? defaultPreferred).width,
132
+ );
133
+ const height = Math.min(
134
+ maxPanelHeight,
135
+ (preferredSize ?? defaultPreferred).height,
136
+ );
137
+
138
+ return {
139
+ width: width,
140
+ height: height,
141
+ };
142
+ };
@@ -0,0 +1,242 @@
1
+ import React from "react";
2
+ import debounce from "lodash/debounce";
3
+ import {
4
+ getElementFromFiber,
5
+ getFiberFromElement,
6
+ getFirstFiberHasName,
7
+ getFirstStateNodeFiber,
8
+ getNameFromFiber,
9
+ getParentOfFiber,
10
+ } from "@aliemir/dom-to-fiber-utils";
11
+
12
+ type Fiber = Exclude<ReturnType<typeof getFiberFromElement>, null>;
13
+
14
+ export const useSelector = (active: boolean) => {
15
+ const [traceItems, setTraceItems] = React.useState<string[]>([]);
16
+
17
+ React.useEffect(() => {
18
+ if (active) {
19
+ fetch("http://localhost:5001/api/unique-trace-items").then((res) =>
20
+ res
21
+ .json()
22
+ .then((data: { data: string[] }) =>
23
+ setTraceItems(data.data),
24
+ ),
25
+ );
26
+ }
27
+ }, [active]);
28
+
29
+ const [selectedFiber, setSelectedFiber] = React.useState<{
30
+ stateNode: Fiber | null;
31
+ nameFiber: Fiber | null;
32
+ }>({
33
+ stateNode: null,
34
+ nameFiber: null,
35
+ });
36
+ const [activeFiber, setActiveFiber] = React.useState<{
37
+ stateNode: Fiber | null;
38
+ nameFiber: Fiber | null;
39
+ derived?: boolean;
40
+ }>({
41
+ stateNode: null,
42
+ nameFiber: null,
43
+ derived: false,
44
+ });
45
+
46
+ React.useEffect(() => {
47
+ if (active) {
48
+ return () => {
49
+ setSelectedFiber({
50
+ stateNode: null,
51
+ nameFiber: null,
52
+ });
53
+ setActiveFiber({
54
+ stateNode: null,
55
+ nameFiber: null,
56
+ derived: false,
57
+ });
58
+ };
59
+ }
60
+
61
+ return () => 0;
62
+ }, [active]);
63
+
64
+ const selectAppropriateFiber = React.useCallback(
65
+ (start: Fiber | null) => {
66
+ let fiber = start;
67
+ let firstParentOfNodeWithName: Fiber | null;
68
+ let fiberWithStateNode: Fiber | null;
69
+
70
+ let acceptedName = false;
71
+
72
+ while (!acceptedName && fiber) {
73
+ // Get the first fiber node that has a name (look up the tree)
74
+ firstParentOfNodeWithName = getFirstFiberHasName(fiber);
75
+ // Get the first fiber node that has a state node (look up the tree)
76
+ fiberWithStateNode = getFirstStateNodeFiber(
77
+ firstParentOfNodeWithName,
78
+ );
79
+ acceptedName = traceItems.includes(
80
+ getNameFromFiber(firstParentOfNodeWithName) ?? "",
81
+ );
82
+ if (!acceptedName) {
83
+ fiber = getParentOfFiber(fiber);
84
+ }
85
+ }
86
+
87
+ if (fiberWithStateNode && firstParentOfNodeWithName) {
88
+ return {
89
+ stateNode: fiberWithStateNode,
90
+ nameFiber: firstParentOfNodeWithName,
91
+ };
92
+ } else {
93
+ return {
94
+ stateNode: null,
95
+ nameFiber: null,
96
+ };
97
+ }
98
+ },
99
+ [traceItems],
100
+ );
101
+
102
+ const pickFiber = React.useCallback(
103
+ (target: HTMLElement) => {
104
+ const fiber = getFiberFromElement(target);
105
+
106
+ setSelectedFiber(selectAppropriateFiber(fiber));
107
+ return;
108
+ },
109
+ [traceItems],
110
+ );
111
+
112
+ React.useEffect(() => {
113
+ if (
114
+ activeFiber.stateNode !== selectedFiber.stateNode ||
115
+ activeFiber.nameFiber !== selectedFiber.nameFiber
116
+ ) {
117
+ setActiveFiber({
118
+ stateNode: selectedFiber.stateNode,
119
+ nameFiber: selectedFiber.nameFiber,
120
+ derived: false,
121
+ });
122
+ }
123
+ }, [selectedFiber]);
124
+
125
+ const previousValues = React.useRef<{
126
+ rect: {
127
+ width: number;
128
+ height: number;
129
+ x: number;
130
+ y: number;
131
+ };
132
+ name: string;
133
+ }>({
134
+ rect: {
135
+ width: 0,
136
+ height: 0,
137
+ x: 0,
138
+ y: 0,
139
+ },
140
+ name: "",
141
+ });
142
+
143
+ const { rect, name } = React.useMemo(() => {
144
+ if (activeFiber.stateNode || activeFiber.nameFiber) {
145
+ // Get the element from the fiber with a state node
146
+ const element = activeFiber.stateNode
147
+ ? getElementFromFiber(activeFiber.stateNode)
148
+ : null;
149
+ // Get the name of the fiber node with a name
150
+ const fiberName = activeFiber.nameFiber
151
+ ? getNameFromFiber(activeFiber.nameFiber)
152
+ : null;
153
+
154
+ if (!element) {
155
+ return {
156
+ rect: previousValues.current.rect,
157
+ name: fiberName ?? previousValues.current.name,
158
+ };
159
+ }
160
+
161
+ const bounding = element.getBoundingClientRect?.();
162
+
163
+ if (!bounding) {
164
+ return {
165
+ rect: previousValues.current.rect,
166
+ name: fiberName ?? previousValues.current.name,
167
+ };
168
+ }
169
+
170
+ return {
171
+ rect: {
172
+ width: bounding.width,
173
+ height: bounding.height,
174
+ x: bounding.left,
175
+ y: bounding.top,
176
+ },
177
+ name: fiberName ?? previousValues.current.name,
178
+ };
179
+ }
180
+
181
+ return previousValues.current;
182
+ }, [activeFiber]);
183
+
184
+ previousValues.current = {
185
+ rect,
186
+ name,
187
+ };
188
+
189
+ React.useEffect(() => {
190
+ if (active) {
191
+ const listener = (e: KeyboardEvent) => {
192
+ // if user presses shift, toggle the derived state and set the active fiber to the parent
193
+ if (e.key === "Shift" && activeFiber.stateNode) {
194
+ e.stopPropagation();
195
+ e.preventDefault();
196
+
197
+ const parent = getParentOfFiber(activeFiber.nameFiber);
198
+
199
+ const fibers = selectAppropriateFiber(parent);
200
+
201
+ if (fibers.nameFiber && fibers.stateNode) {
202
+ setActiveFiber({
203
+ ...fibers,
204
+ derived: true,
205
+ });
206
+ return;
207
+ }
208
+ }
209
+ };
210
+
211
+ document.addEventListener("keydown", listener);
212
+ return () => document.removeEventListener("keydown", listener);
213
+ }
214
+ return () => 0;
215
+ }, [activeFiber, active]);
216
+
217
+ React.useEffect(() => {
218
+ if (active) {
219
+ let previousTarget: HTMLElement | null = null;
220
+ const listener = debounce((e: MouseEvent) => {
221
+ if (e?.target) {
222
+ if (previousTarget === e.target) {
223
+ return;
224
+ }
225
+ pickFiber(e.target as HTMLElement);
226
+ previousTarget = e.target as HTMLElement;
227
+ }
228
+ }, 30);
229
+
230
+ document.addEventListener("mousemove", listener);
231
+
232
+ return () => document.removeEventListener("mousemove", listener);
233
+ } else {
234
+ return () => 0;
235
+ }
236
+ }, [active, pickFiber]);
237
+
238
+ return {
239
+ rect,
240
+ name,
241
+ };
242
+ };