@promakeai/inspector 0.0.1
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.
- package/README.md +316 -0
- package/dist/hook.d.ts +61 -0
- package/dist/hook.d.ts.map +1 -0
- package/dist/hook.js +155 -0
- package/dist/plugin.d.ts +36 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +625 -0
- package/dist/types.d.ts +66 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# @promakeai/inspector
|
|
2
|
+
|
|
3
|
+
Visual element inspector for React apps in iframe with AI prompt support. Perfect for visual editors, page builders, and low-code platforms.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎯 **Element Selection** - Click to select any element on the page
|
|
8
|
+
- 🔍 **React Component Detection** - Automatically detects React components via Fiber
|
|
9
|
+
- 📝 **Text Editing** - Edit text content directly
|
|
10
|
+
- 🤖 **AI Prompt Support** - Send prompts to AI for element modifications
|
|
11
|
+
- 🎨 **Visual Feedback** - Highlight selected elements with overlay
|
|
12
|
+
- 🔒 **Pause Mode** - Lock selection and disable scroll while editing
|
|
13
|
+
- 🌐 **URL Tracking** - Monitor navigation changes in iframe
|
|
14
|
+
- 🐛 **Error Tracking** - Capture JavaScript errors, promise rejections, and console errors
|
|
15
|
+
- 🎨 **Customizable Labels** - Multi-language support
|
|
16
|
+
- 📦 **TypeScript** - Full type safety
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @promakeai/inspector
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
### Template Side (iframe/vite app)
|
|
27
|
+
|
|
28
|
+
Add the plugin to your `vite.config.ts`:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { defineConfig } from "vite";
|
|
32
|
+
import react from "@vitejs/plugin-react";
|
|
33
|
+
import { inspectorPlugin } from "@promakeai/inspector/plugin";
|
|
34
|
+
|
|
35
|
+
export default defineConfig({
|
|
36
|
+
plugins: [react(), inspectorPlugin()],
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Parent App Side
|
|
41
|
+
|
|
42
|
+
Use the hook in your React app:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { useRef } from "react";
|
|
46
|
+
import { useInspector } from "@promakeai/inspector/hook";
|
|
47
|
+
|
|
48
|
+
function App() {
|
|
49
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
50
|
+
|
|
51
|
+
const { isInspecting, toggleInspector } = useInspector(
|
|
52
|
+
iframeRef,
|
|
53
|
+
{
|
|
54
|
+
onElementSelected: (data) => {
|
|
55
|
+
console.log("Element selected:", data);
|
|
56
|
+
// Access component info
|
|
57
|
+
console.log(data.component?.fileName);
|
|
58
|
+
console.log(data.component?.lineNumber);
|
|
59
|
+
},
|
|
60
|
+
onPromptSubmitted: (data) => {
|
|
61
|
+
console.log("AI Prompt:", data.prompt);
|
|
62
|
+
// Send to your AI service
|
|
63
|
+
fetch("/api/ai-edit", {
|
|
64
|
+
method: "POST",
|
|
65
|
+
body: JSON.stringify({
|
|
66
|
+
prompt: data.prompt,
|
|
67
|
+
element: data.element,
|
|
68
|
+
}),
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
onTextUpdated: (data) => {
|
|
72
|
+
console.log("Text updated:", data.text);
|
|
73
|
+
// Save to backend
|
|
74
|
+
},
|
|
75
|
+
onUrlChange: (data) => {
|
|
76
|
+
console.log("URL changed:", data.pathname);
|
|
77
|
+
},
|
|
78
|
+
onError: (data) => {
|
|
79
|
+
console.error("Error in template:", data);
|
|
80
|
+
// Send to error tracking service (Sentry, etc.)
|
|
81
|
+
// Sentry.captureException(new Error(data.message));
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
// Optional: Custom labels
|
|
86
|
+
editText: "Edit Text",
|
|
87
|
+
textPlaceholder: "Enter text...",
|
|
88
|
+
updateText: "Update",
|
|
89
|
+
promptPlaceholder: "Ask AI for changes...",
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div>
|
|
95
|
+
<button onClick={() => toggleInspector()}>
|
|
96
|
+
{isInspecting ? "Stop Inspector" : "Start Inspector"}
|
|
97
|
+
</button>
|
|
98
|
+
|
|
99
|
+
<iframe ref={iframeRef} src="http://localhost:5173" />
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## API
|
|
106
|
+
|
|
107
|
+
### `useInspector(iframeRef, callbacks?, labels?)`
|
|
108
|
+
|
|
109
|
+
#### Parameters
|
|
110
|
+
|
|
111
|
+
- **iframeRef**: `RefObject<HTMLIFrameElement>` - Reference to the iframe element
|
|
112
|
+
- **callbacks**: `InspectorCallbacks` (optional)
|
|
113
|
+
- `onElementSelected`: Called when an element is selected
|
|
114
|
+
- `onPromptSubmitted`: Called when AI prompt is submitted
|
|
115
|
+
- `onTextUpdated`: Called when text content is updated
|
|
116
|
+
- `onUrlChange`: Called when URL changes in iframe
|
|
117
|
+
- `onError`: Called when an error occurs in iframe
|
|
118
|
+
- **labels**: `InspectorLabels` (optional)
|
|
119
|
+
- `editText`: Label for text edit section
|
|
120
|
+
- `textPlaceholder`: Placeholder for text input
|
|
121
|
+
- `updateText`: Label for update button
|
|
122
|
+
- `promptPlaceholder`: Placeholder for prompt input
|
|
123
|
+
|
|
124
|
+
#### Returns
|
|
125
|
+
|
|
126
|
+
- **isInspecting**: `boolean` - Current inspection state
|
|
127
|
+
- **toggleInspector**: `(active?: boolean) => void` - Toggle inspection mode
|
|
128
|
+
- **startInspecting**: `() => void` - Start inspecting
|
|
129
|
+
- **stopInspecting**: `() => void` - Stop inspecting
|
|
130
|
+
|
|
131
|
+
## Types
|
|
132
|
+
|
|
133
|
+
### `SelectedElementData`
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
interface SelectedElementData {
|
|
137
|
+
tagName: string;
|
|
138
|
+
className: string;
|
|
139
|
+
id: string;
|
|
140
|
+
component: ComponentInfo | null;
|
|
141
|
+
position: ElementPosition;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### `ComponentInfo`
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
interface ComponentInfo {
|
|
149
|
+
componentName: string;
|
|
150
|
+
fileName: string;
|
|
151
|
+
lineNumber: number;
|
|
152
|
+
columnNumber: number;
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### `PromptSubmittedData`
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
interface PromptSubmittedData {
|
|
160
|
+
prompt: string;
|
|
161
|
+
element: SelectedElementData;
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### `TextUpdatedData`
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
interface TextUpdatedData {
|
|
169
|
+
text: string;
|
|
170
|
+
originalText: string;
|
|
171
|
+
element: SelectedElementData;
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### `ErrorData`
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
interface ErrorData {
|
|
179
|
+
type: "javascript" | "promise" | "console";
|
|
180
|
+
message: string;
|
|
181
|
+
stack?: string;
|
|
182
|
+
fileName?: string;
|
|
183
|
+
lineNumber?: number;
|
|
184
|
+
columnNumber?: number;
|
|
185
|
+
timestamp: number;
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Features in Detail
|
|
190
|
+
|
|
191
|
+
### Element Selection
|
|
192
|
+
|
|
193
|
+
Click any element to select it. The inspector will:
|
|
194
|
+
|
|
195
|
+
- Show a blue highlight overlay
|
|
196
|
+
- Display a control box below/above the element
|
|
197
|
+
- Pause scrolling
|
|
198
|
+
- Extract React component information
|
|
199
|
+
|
|
200
|
+
### Text Editing
|
|
201
|
+
|
|
202
|
+
For text-only elements:
|
|
203
|
+
|
|
204
|
+
- Textarea appears with current text
|
|
205
|
+
- Edit and click "Update"
|
|
206
|
+
- Event is sent to parent app
|
|
207
|
+
|
|
208
|
+
### AI Prompts
|
|
209
|
+
|
|
210
|
+
For any element:
|
|
211
|
+
|
|
212
|
+
- Enter a prompt describing desired changes
|
|
213
|
+
- Click the send button (↑)
|
|
214
|
+
- Prompt + element data sent to parent app
|
|
215
|
+
|
|
216
|
+
### Component Detection
|
|
217
|
+
|
|
218
|
+
Automatically extracts:
|
|
219
|
+
|
|
220
|
+
- Component name
|
|
221
|
+
- Source file path
|
|
222
|
+
- Line number
|
|
223
|
+
- Column number
|
|
224
|
+
|
|
225
|
+
**Note**: Works only in development mode with React DevTools enabled.
|
|
226
|
+
|
|
227
|
+
### Error Tracking
|
|
228
|
+
|
|
229
|
+
Automatically captures and reports:
|
|
230
|
+
|
|
231
|
+
- **JavaScript Errors**: Runtime errors with stack traces
|
|
232
|
+
- **Promise Rejections**: Unhandled promise rejections
|
|
233
|
+
- **Console Errors**: All `console.error()` calls
|
|
234
|
+
|
|
235
|
+
All errors are sent to the parent app with detailed information including:
|
|
236
|
+
|
|
237
|
+
- Error type and message
|
|
238
|
+
- Stack trace
|
|
239
|
+
- File name and line number
|
|
240
|
+
- Timestamp
|
|
241
|
+
|
|
242
|
+
Perfect for integrating with error tracking services like Sentry, LogRocket, or custom logging solutions.
|
|
243
|
+
|
|
244
|
+
## Examples
|
|
245
|
+
|
|
246
|
+
### Multi-language Support
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
const { toggleInspector } = useInspector(iframeRef, callbacks, {
|
|
250
|
+
editText: "Metni Düzenle",
|
|
251
|
+
textPlaceholder: "Metin girin...",
|
|
252
|
+
updateText: "Güncelle",
|
|
253
|
+
promptPlaceholder: "AI için komut girin...",
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Open File in Editor
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
onElementSelected: async (data) => {
|
|
261
|
+
if (data.component) {
|
|
262
|
+
await fetch("/api/open-in-editor", {
|
|
263
|
+
method: "POST",
|
|
264
|
+
body: JSON.stringify({
|
|
265
|
+
fileName: data.component.fileName,
|
|
266
|
+
lineNumber: data.component.lineNumber,
|
|
267
|
+
}),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Error Tracking with Sentry
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import * as Sentry from "@sentry/react";
|
|
277
|
+
|
|
278
|
+
const { toggleInspector } = useInspector(iframeRef, {
|
|
279
|
+
onError: (data) => {
|
|
280
|
+
// Send to Sentry
|
|
281
|
+
Sentry.captureException(new Error(data.message), {
|
|
282
|
+
extra: {
|
|
283
|
+
type: data.type,
|
|
284
|
+
stack: data.stack,
|
|
285
|
+
fileName: data.fileName,
|
|
286
|
+
lineNumber: data.lineNumber,
|
|
287
|
+
timestamp: data.timestamp,
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Browser Support
|
|
295
|
+
|
|
296
|
+
- Chrome/Edge: ✅
|
|
297
|
+
- Firefox: ✅
|
|
298
|
+
- Safari: ✅
|
|
299
|
+
|
|
300
|
+
## Requirements
|
|
301
|
+
|
|
302
|
+
- Vite 5.0+
|
|
303
|
+
- React 18.0+
|
|
304
|
+
- TypeScript (recommended)
|
|
305
|
+
|
|
306
|
+
## License
|
|
307
|
+
|
|
308
|
+
MIT
|
|
309
|
+
|
|
310
|
+
## Contributing
|
|
311
|
+
|
|
312
|
+
Issues and pull requests are welcome!
|
|
313
|
+
|
|
314
|
+
## Author
|
|
315
|
+
|
|
316
|
+
Promake
|
package/dist/hook.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useInspector Hook
|
|
3
|
+
*
|
|
4
|
+
* Ana app'ten template iframe'ine inspector komutları göndermek ve
|
|
5
|
+
* gelen olayları dinlemek için kullanılır.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { useRef } from 'react';
|
|
10
|
+
* import { useInspector } from '@promakeai/inspector/hook';
|
|
11
|
+
*
|
|
12
|
+
* function App() {
|
|
13
|
+
* const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
14
|
+
*
|
|
15
|
+
* const { isInspecting, toggleInspector } = useInspector(
|
|
16
|
+
* iframeRef,
|
|
17
|
+
* {
|
|
18
|
+
* onElementSelected: (data) => {
|
|
19
|
+
* console.log('Element selected:', data);
|
|
20
|
+
* },
|
|
21
|
+
* onPromptSubmitted: (data) => {
|
|
22
|
+
* console.log('AI Prompt:', data.prompt);
|
|
23
|
+
* // Send to AI
|
|
24
|
+
* fetch('/api/ai-edit', {
|
|
25
|
+
* method: 'POST',
|
|
26
|
+
* body: JSON.stringify(data)
|
|
27
|
+
* });
|
|
28
|
+
* },
|
|
29
|
+
* onTextUpdated: (data) => {
|
|
30
|
+
* console.log('Text updated:', data.text);
|
|
31
|
+
* },
|
|
32
|
+
* onError: (data) => {
|
|
33
|
+
* console.error('Error:', data);
|
|
34
|
+
* // Send to error tracking service
|
|
35
|
+
* }
|
|
36
|
+
* },
|
|
37
|
+
* {
|
|
38
|
+
* // Optional: Custom labels (defaults to English)
|
|
39
|
+
* editText: 'Edit Text',
|
|
40
|
+
* textPlaceholder: 'Enter text...',
|
|
41
|
+
* updateText: 'Update',
|
|
42
|
+
* promptPlaceholder: 'Ask AI for changes...'
|
|
43
|
+
* }
|
|
44
|
+
* );
|
|
45
|
+
*
|
|
46
|
+
* return (
|
|
47
|
+
* <div>
|
|
48
|
+
* <button onClick={() => toggleInspector()}>
|
|
49
|
+
* {isInspecting ? 'Stop' : 'Start'} Inspector
|
|
50
|
+
* </button>
|
|
51
|
+
* <iframe ref={iframeRef} src="http://localhost:5173" />
|
|
52
|
+
* </div>
|
|
53
|
+
* );
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
import { RefObject } from "react";
|
|
58
|
+
import type { InspectorCallbacks, InspectorLabels, UseInspectorReturn } from "./types";
|
|
59
|
+
export declare function useInspector(iframeRef: RefObject<HTMLIFrameElement>, callbacks?: InspectorCallbacks, labels?: InspectorLabels): UseInspectorReturn;
|
|
60
|
+
export type * from "./types";
|
|
61
|
+
//# sourceMappingURL=hook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,OAAO,EAAoC,SAAS,EAAE,MAAM,OAAO,CAAC;AACpE,OAAO,KAAK,EACV,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAMnB,MAAM,SAAS,CAAC;AA8BjB,wBAAgB,YAAY,CAC1B,SAAS,EAAE,SAAS,CAAC,iBAAiB,CAAC,EACvC,SAAS,CAAC,EAAE,kBAAkB,EAC9B,MAAM,CAAC,EAAE,eAAe,GACvB,kBAAkB,CAwHpB;AAGD,mBAAmB,SAAS,CAAC"}
|
package/dist/hook.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useInspector Hook
|
|
3
|
+
*
|
|
4
|
+
* Ana app'ten template iframe'ine inspector komutları göndermek ve
|
|
5
|
+
* gelen olayları dinlemek için kullanılır.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { useRef } from 'react';
|
|
10
|
+
* import { useInspector } from '@promakeai/inspector/hook';
|
|
11
|
+
*
|
|
12
|
+
* function App() {
|
|
13
|
+
* const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
14
|
+
*
|
|
15
|
+
* const { isInspecting, toggleInspector } = useInspector(
|
|
16
|
+
* iframeRef,
|
|
17
|
+
* {
|
|
18
|
+
* onElementSelected: (data) => {
|
|
19
|
+
* console.log('Element selected:', data);
|
|
20
|
+
* },
|
|
21
|
+
* onPromptSubmitted: (data) => {
|
|
22
|
+
* console.log('AI Prompt:', data.prompt);
|
|
23
|
+
* // Send to AI
|
|
24
|
+
* fetch('/api/ai-edit', {
|
|
25
|
+
* method: 'POST',
|
|
26
|
+
* body: JSON.stringify(data)
|
|
27
|
+
* });
|
|
28
|
+
* },
|
|
29
|
+
* onTextUpdated: (data) => {
|
|
30
|
+
* console.log('Text updated:', data.text);
|
|
31
|
+
* },
|
|
32
|
+
* onError: (data) => {
|
|
33
|
+
* console.error('Error:', data);
|
|
34
|
+
* // Send to error tracking service
|
|
35
|
+
* }
|
|
36
|
+
* },
|
|
37
|
+
* {
|
|
38
|
+
* // Optional: Custom labels (defaults to English)
|
|
39
|
+
* editText: 'Edit Text',
|
|
40
|
+
* textPlaceholder: 'Enter text...',
|
|
41
|
+
* updateText: 'Update',
|
|
42
|
+
* promptPlaceholder: 'Ask AI for changes...'
|
|
43
|
+
* }
|
|
44
|
+
* );
|
|
45
|
+
*
|
|
46
|
+
* return (
|
|
47
|
+
* <div>
|
|
48
|
+
* <button onClick={() => toggleInspector()}>
|
|
49
|
+
* {isInspecting ? 'Stop' : 'Start'} Inspector
|
|
50
|
+
* </button>
|
|
51
|
+
* <iframe ref={iframeRef} src="http://localhost:5173" />
|
|
52
|
+
* </div>
|
|
53
|
+
* );
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
import { useEffect, useState, useCallback } from "react";
|
|
58
|
+
export function useInspector(iframeRef, callbacks, labels) {
|
|
59
|
+
const [isInspecting, setIsInspecting] = useState(false);
|
|
60
|
+
/**
|
|
61
|
+
* Send message to iframe
|
|
62
|
+
*/
|
|
63
|
+
const sendMessage = useCallback((message) => {
|
|
64
|
+
const iframe = iframeRef.current;
|
|
65
|
+
if (!iframe || !iframe.contentWindow) {
|
|
66
|
+
console.warn("Inspector: iframe not found or not loaded");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
iframe.contentWindow.postMessage(message, "*");
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.error("Inspector: Failed to send message:", error);
|
|
74
|
+
}
|
|
75
|
+
}, [iframeRef]);
|
|
76
|
+
/**
|
|
77
|
+
* Toggle inspector mode
|
|
78
|
+
*/
|
|
79
|
+
const toggleInspector = useCallback((active) => {
|
|
80
|
+
const newState = active !== undefined ? active : !isInspecting;
|
|
81
|
+
setIsInspecting(newState);
|
|
82
|
+
sendMessage({
|
|
83
|
+
type: "TOGGLE_INSPECTOR",
|
|
84
|
+
active: newState,
|
|
85
|
+
labels: labels,
|
|
86
|
+
});
|
|
87
|
+
}, [isInspecting, sendMessage, labels]);
|
|
88
|
+
/**
|
|
89
|
+
* Start inspecting
|
|
90
|
+
*/
|
|
91
|
+
const startInspecting = useCallback(() => {
|
|
92
|
+
toggleInspector(true);
|
|
93
|
+
}, [toggleInspector]);
|
|
94
|
+
/**
|
|
95
|
+
* Stop inspecting
|
|
96
|
+
*/
|
|
97
|
+
const stopInspecting = useCallback(() => {
|
|
98
|
+
toggleInspector(false);
|
|
99
|
+
}, [toggleInspector]);
|
|
100
|
+
/**
|
|
101
|
+
* Listen for messages from iframe
|
|
102
|
+
*/
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
const handleMessage = (event) => {
|
|
105
|
+
// Security: Only handle expected message types
|
|
106
|
+
if (!event.data || typeof event.data.type !== "string") {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const { type, data } = event.data;
|
|
110
|
+
switch (type) {
|
|
111
|
+
case "INSPECTOR_ELEMENT_SELECTED":
|
|
112
|
+
callbacks?.onElementSelected?.(data);
|
|
113
|
+
break;
|
|
114
|
+
case "URL_CHANGED":
|
|
115
|
+
callbacks?.onUrlChange?.(data);
|
|
116
|
+
break;
|
|
117
|
+
case "INSPECTOR_PROMPT_SUBMITTED":
|
|
118
|
+
callbacks?.onPromptSubmitted?.(data);
|
|
119
|
+
break;
|
|
120
|
+
case "INSPECTOR_TEXT_UPDATED":
|
|
121
|
+
callbacks?.onTextUpdated?.(data);
|
|
122
|
+
break;
|
|
123
|
+
case "INSPECTOR_ERROR":
|
|
124
|
+
callbacks?.onError?.(data);
|
|
125
|
+
break;
|
|
126
|
+
default:
|
|
127
|
+
// Unknown message type - ignore
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
window.addEventListener("message", handleMessage);
|
|
132
|
+
return () => {
|
|
133
|
+
window.removeEventListener("message", handleMessage);
|
|
134
|
+
};
|
|
135
|
+
}, [callbacks]);
|
|
136
|
+
/**
|
|
137
|
+
* Cleanup: Turn off inspector on unmount
|
|
138
|
+
*/
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
return () => {
|
|
141
|
+
if (isInspecting) {
|
|
142
|
+
sendMessage({
|
|
143
|
+
type: "TOGGLE_INSPECTOR",
|
|
144
|
+
active: false,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}, [isInspecting, sendMessage]);
|
|
149
|
+
return {
|
|
150
|
+
isInspecting,
|
|
151
|
+
toggleInspector,
|
|
152
|
+
startInspecting,
|
|
153
|
+
stopInspecting,
|
|
154
|
+
};
|
|
155
|
+
}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite Inspector Plugin
|
|
3
|
+
*
|
|
4
|
+
* Template/iframe tarafında çalışır. Sayfaya element seçme, React component
|
|
5
|
+
* bilgisi çıkarma, hata yakalama ve AI prompt gönderme özellikleri ekler.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // vite.config.ts
|
|
10
|
+
* import { defineConfig } from 'vite';
|
|
11
|
+
* import react from '@vitejs/plugin-react';
|
|
12
|
+
* import { inspectorPlugin } from '@promakeai/inspector/plugin';
|
|
13
|
+
*
|
|
14
|
+
* export default defineConfig({
|
|
15
|
+
* plugins: [
|
|
16
|
+
* react(),
|
|
17
|
+
* inspectorPlugin()
|
|
18
|
+
* ]
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Plugin özellikler:
|
|
23
|
+
* - Element seçimi (click-to-select)
|
|
24
|
+
* - React Fiber üzerinden component bilgisi çıkarma
|
|
25
|
+
* - Text editing desteği
|
|
26
|
+
* - AI prompt gönderme
|
|
27
|
+
* - URL değişikliği takibi
|
|
28
|
+
* - JavaScript hata yakalama (error, promise rejection, console.error)
|
|
29
|
+
* - Özelleştirilebilir dil desteği
|
|
30
|
+
*
|
|
31
|
+
* Not: Bu plugin otomatik olarak template/iframe'e enjekte edilir.
|
|
32
|
+
* Ana app'te useInspector hook'u ile kontrol edilir.
|
|
33
|
+
*/
|
|
34
|
+
import { Plugin } from "vite";
|
|
35
|
+
export declare function inspectorPlugin(): Plugin;
|
|
36
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAE9B,wBAAgB,eAAe,IAAI,MAAM,CA+kBxC"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite Inspector Plugin
|
|
3
|
+
*
|
|
4
|
+
* Template/iframe tarafında çalışır. Sayfaya element seçme, React component
|
|
5
|
+
* bilgisi çıkarma, hata yakalama ve AI prompt gönderme özellikleri ekler.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // vite.config.ts
|
|
10
|
+
* import { defineConfig } from 'vite';
|
|
11
|
+
* import react from '@vitejs/plugin-react';
|
|
12
|
+
* import { inspectorPlugin } from '@promakeai/inspector/plugin';
|
|
13
|
+
*
|
|
14
|
+
* export default defineConfig({
|
|
15
|
+
* plugins: [
|
|
16
|
+
* react(),
|
|
17
|
+
* inspectorPlugin()
|
|
18
|
+
* ]
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Plugin özellikler:
|
|
23
|
+
* - Element seçimi (click-to-select)
|
|
24
|
+
* - React Fiber üzerinden component bilgisi çıkarma
|
|
25
|
+
* - Text editing desteği
|
|
26
|
+
* - AI prompt gönderme
|
|
27
|
+
* - URL değişikliği takibi
|
|
28
|
+
* - JavaScript hata yakalama (error, promise rejection, console.error)
|
|
29
|
+
* - Özelleştirilebilir dil desteği
|
|
30
|
+
*
|
|
31
|
+
* Not: Bu plugin otomatik olarak template/iframe'e enjekte edilir.
|
|
32
|
+
* Ana app'te useInspector hook'u ile kontrol edilir.
|
|
33
|
+
*/
|
|
34
|
+
export function inspectorPlugin() {
|
|
35
|
+
return {
|
|
36
|
+
name: "vite-plugin-inspector",
|
|
37
|
+
transformIndexHtml(html) {
|
|
38
|
+
// Inject inspector client script into HTML
|
|
39
|
+
return {
|
|
40
|
+
html,
|
|
41
|
+
tags: [
|
|
42
|
+
{
|
|
43
|
+
tag: "script",
|
|
44
|
+
attrs: {
|
|
45
|
+
type: "module",
|
|
46
|
+
},
|
|
47
|
+
children: `
|
|
48
|
+
(function() {
|
|
49
|
+
let inspectMode = false;
|
|
50
|
+
let isPaused = false;
|
|
51
|
+
let hoveredElement = null;
|
|
52
|
+
let selectedElement = null;
|
|
53
|
+
let selectedElementData = null;
|
|
54
|
+
let overlay = null;
|
|
55
|
+
let controlBox = null;
|
|
56
|
+
|
|
57
|
+
// Customizable labels
|
|
58
|
+
let labels = {
|
|
59
|
+
editText: 'Edit Text',
|
|
60
|
+
textPlaceholder: 'Enter text...',
|
|
61
|
+
updateText: 'Update',
|
|
62
|
+
promptPlaceholder: 'Ask Promake for changes...'
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Create overlay for highlighting
|
|
66
|
+
function createOverlay() {
|
|
67
|
+
if (overlay) return overlay;
|
|
68
|
+
|
|
69
|
+
overlay = document.createElement('div');
|
|
70
|
+
overlay.id = 'inspector-overlay';
|
|
71
|
+
overlay.style.cssText = \`
|
|
72
|
+
position: fixed;
|
|
73
|
+
pointer-events: none;
|
|
74
|
+
border: 2px solid #3b82f6;
|
|
75
|
+
background: rgba(59, 130, 246, 0.1);
|
|
76
|
+
z-index: 999999;
|
|
77
|
+
transition: all 0.1s ease;
|
|
78
|
+
display: none;
|
|
79
|
+
\`;
|
|
80
|
+
document.body.appendChild(overlay);
|
|
81
|
+
return overlay;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Create control box for editing
|
|
85
|
+
function createControlBox(element, elementData) {
|
|
86
|
+
// Remove existing control box
|
|
87
|
+
if (controlBox) {
|
|
88
|
+
controlBox.remove();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
controlBox = document.createElement('div');
|
|
92
|
+
controlBox.id = 'inspector-control-box';
|
|
93
|
+
|
|
94
|
+
const rect = element.getBoundingClientRect();
|
|
95
|
+
const isTextElement = element.textContent && element.children.length === 0;
|
|
96
|
+
const currentText = isTextElement ? element.textContent.trim() : '';
|
|
97
|
+
|
|
98
|
+
// Calculate position
|
|
99
|
+
const boxWidth = Math.max(320, Math.min(rect.width, 500));
|
|
100
|
+
const centerLeft = rect.left + (rect.width / 2) - (boxWidth / 2);
|
|
101
|
+
const viewportHeight = window.innerHeight;
|
|
102
|
+
const spaceBelow = viewportHeight - rect.bottom;
|
|
103
|
+
const spaceAbove = rect.top;
|
|
104
|
+
|
|
105
|
+
// Estimate box height
|
|
106
|
+
const estimatedBoxHeight = isTextElement ? 280 : 180;
|
|
107
|
+
|
|
108
|
+
// Show above if not enough space below
|
|
109
|
+
let topPosition;
|
|
110
|
+
if (spaceBelow < estimatedBoxHeight && spaceAbove > spaceBelow) {
|
|
111
|
+
// Show above element (positioned from top, showing above element)
|
|
112
|
+
topPosition = rect.top - estimatedBoxHeight - 10;
|
|
113
|
+
controlBox.setAttribute('data-position', 'above');
|
|
114
|
+
} else {
|
|
115
|
+
// Show below element
|
|
116
|
+
topPosition = rect.bottom + 10;
|
|
117
|
+
controlBox.setAttribute('data-position', 'below');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
controlBox.style.cssText = \`
|
|
121
|
+
position: fixed;
|
|
122
|
+
top: \${topPosition}px;
|
|
123
|
+
left: \${Math.max(10, centerLeft)}px;
|
|
124
|
+
width: \${boxWidth}px;
|
|
125
|
+
background: #ffffff;
|
|
126
|
+
border: 1px solid #e5e7eb;
|
|
127
|
+
border-radius: 14px;
|
|
128
|
+
padding: 12px;
|
|
129
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
130
|
+
z-index: 1000000;
|
|
131
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
132
|
+
font-size: 14px;
|
|
133
|
+
\`;
|
|
134
|
+
|
|
135
|
+
let html = '';
|
|
136
|
+
|
|
137
|
+
// Text input (if text element)
|
|
138
|
+
if (isTextElement) {
|
|
139
|
+
html += \`
|
|
140
|
+
<div style="margin-bottom: 12px;">
|
|
141
|
+
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px;">
|
|
142
|
+
<label style="font-weight: 500; color: #111827; font-size: 13px;">
|
|
143
|
+
\${labels.editText}
|
|
144
|
+
</label>
|
|
145
|
+
<button
|
|
146
|
+
id="inspector-close"
|
|
147
|
+
style="width: 32px; height: 32px; background: transparent; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; border-radius: 14px; transition: all 0.2s; padding: 0; flex-shrink: 0;"
|
|
148
|
+
onmouseover="this.style.background='#f3f4f6';"
|
|
149
|
+
onmouseout="this.style.background='transparent';"
|
|
150
|
+
title="Close"
|
|
151
|
+
>
|
|
152
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
153
|
+
<path d="M12 4L4 12M4 4L12 12" stroke="#6b7280" stroke-width="2" stroke-linecap="round"/>
|
|
154
|
+
</svg>
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
<textarea
|
|
158
|
+
id="inspector-text-input"
|
|
159
|
+
style="width: 100%; padding: 10px 12px; border: 1px solid #d1d5db; border-radius: 14px; font-size: 14px; color: #111827; background: #f9fafb; transition: all 0.2s; resize: vertical; min-height: 60px; max-height: 200px; font-family: inherit; line-height: 1.5;"
|
|
160
|
+
placeholder="\${labels.textPlaceholder}"
|
|
161
|
+
onfocus="this.style.background='#ffffff'; this.style.borderColor='#9ca3af';"
|
|
162
|
+
onblur="this.style.background='#f9fafb'; this.style.borderColor='#d1d5db';"
|
|
163
|
+
>\${currentText}</textarea>
|
|
164
|
+
</div>
|
|
165
|
+
<div style="margin-bottom: 10px;">
|
|
166
|
+
<button
|
|
167
|
+
id="inspector-text-submit"
|
|
168
|
+
style="width: 100%; padding: 10px 16px; background: #4417db; color: white; border: none; border-radius: 14px; cursor: pointer; font-weight: 500; font-size: 14px; transition: all 0.2s;"
|
|
169
|
+
onmouseover="this.style.background='#3712af';"
|
|
170
|
+
onmouseout="this.style.background='#4417db';"
|
|
171
|
+
title="\${labels.updateText}"
|
|
172
|
+
>
|
|
173
|
+
\${labels.updateText}
|
|
174
|
+
</button>
|
|
175
|
+
</div>
|
|
176
|
+
<div style="border-top: 1px solid #e5e7eb; margin: 12px 0;"></div>
|
|
177
|
+
\`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Prompt input and buttons (side by side)
|
|
181
|
+
html += \`
|
|
182
|
+
<div style="display: flex; gap: 8px; align-items: stretch;">
|
|
183
|
+
<input
|
|
184
|
+
type="text"
|
|
185
|
+
id="inspector-prompt-input"
|
|
186
|
+
style="flex: 1; padding: 10px 12px; border: 1px solid #d1d5db; border-radius: 14px; font-size: 14px; color: #111827; background: #f9fafb; transition: all 0.2s;"
|
|
187
|
+
placeholder="\${labels.promptPlaceholder}"
|
|
188
|
+
onfocus="this.style.background='#ffffff'; this.style.borderColor='#9ca3af';"
|
|
189
|
+
onblur="this.style.background='#f9fafb'; this.style.borderColor='#d1d5db';"
|
|
190
|
+
/>
|
|
191
|
+
<button
|
|
192
|
+
id="inspector-prompt-submit"
|
|
193
|
+
style="width: 44px; height: 44px; padding: 0; background: #4417db; color: white; border: none; border-radius: 14px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s; flex-shrink: 0;"
|
|
194
|
+
onmouseover="this.style.background='#3712af';"
|
|
195
|
+
onmouseout="this.style.background='#4417db';"
|
|
196
|
+
title="Send"
|
|
197
|
+
>
|
|
198
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
199
|
+
<path d="M10 15V5M10 5L5 10M10 5L15 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
200
|
+
</svg>
|
|
201
|
+
</button>
|
|
202
|
+
\`;
|
|
203
|
+
|
|
204
|
+
// Close button next to send button (only for non-text elements)
|
|
205
|
+
if (!isTextElement) {
|
|
206
|
+
html += \`
|
|
207
|
+
<button
|
|
208
|
+
id="inspector-close"
|
|
209
|
+
style="padding: 10px; background: #f3f4f6; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; border-radius: 14px; transition: all 0.2s;"
|
|
210
|
+
onmouseover="this.style.background='#e5e7eb';"
|
|
211
|
+
onmouseout="this.style.background='#f3f4f6';"
|
|
212
|
+
title="Close"
|
|
213
|
+
>
|
|
214
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
215
|
+
<path d="M12 4L4 12M4 4L12 12" stroke="#6b7280" stroke-width="2" stroke-linecap="round"/>
|
|
216
|
+
</svg>
|
|
217
|
+
</button>
|
|
218
|
+
\`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
html += \`</div>\`;
|
|
222
|
+
|
|
223
|
+
controlBox.innerHTML = html;
|
|
224
|
+
document.body.appendChild(controlBox);
|
|
225
|
+
|
|
226
|
+
// Event listeners
|
|
227
|
+
const promptInput = controlBox.querySelector('#inspector-prompt-input');
|
|
228
|
+
const promptSubmit = controlBox.querySelector('#inspector-prompt-submit');
|
|
229
|
+
const closeBtn = controlBox.querySelector('#inspector-close');
|
|
230
|
+
|
|
231
|
+
// Prompt submit
|
|
232
|
+
promptSubmit.addEventListener('click', (e) => {
|
|
233
|
+
e.stopPropagation();
|
|
234
|
+
const prompt = promptInput.value.trim();
|
|
235
|
+
if (prompt) {
|
|
236
|
+
if (window.parent !== window) {
|
|
237
|
+
window.parent.postMessage({
|
|
238
|
+
type: 'INSPECTOR_PROMPT_SUBMITTED',
|
|
239
|
+
data: {
|
|
240
|
+
prompt: prompt,
|
|
241
|
+
element: elementData
|
|
242
|
+
}
|
|
243
|
+
}, '*');
|
|
244
|
+
}
|
|
245
|
+
console.log('Prompt submitted:', elementData);
|
|
246
|
+
|
|
247
|
+
// Turn off inspect mode after sending
|
|
248
|
+
toggleInspectMode(false);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Enter key on prompt input
|
|
253
|
+
promptInput.addEventListener('keypress', (e) => {
|
|
254
|
+
if (e.key === 'Enter') {
|
|
255
|
+
e.stopPropagation();
|
|
256
|
+
promptSubmit.click();
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Text input handlers (if exists)
|
|
261
|
+
if (isTextElement) {
|
|
262
|
+
const textInput = controlBox.querySelector('#inspector-text-input');
|
|
263
|
+
const textSubmit = controlBox.querySelector('#inspector-text-submit');
|
|
264
|
+
|
|
265
|
+
textSubmit.addEventListener('click', (e) => {
|
|
266
|
+
e.stopPropagation();
|
|
267
|
+
const newText = textInput.value;
|
|
268
|
+
if (window.parent !== window) {
|
|
269
|
+
window.parent.postMessage({
|
|
270
|
+
type: 'INSPECTOR_TEXT_UPDATED',
|
|
271
|
+
data: {
|
|
272
|
+
text: newText,
|
|
273
|
+
originalText: currentText,
|
|
274
|
+
element: elementData
|
|
275
|
+
}
|
|
276
|
+
}, '*');
|
|
277
|
+
}
|
|
278
|
+
console.log('Text updated:', newText);
|
|
279
|
+
|
|
280
|
+
// Turn off inspect mode after updating
|
|
281
|
+
toggleInspectMode(false);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
textInput.addEventListener('keypress', (e) => {
|
|
285
|
+
if (e.key === 'Enter') {
|
|
286
|
+
e.stopPropagation();
|
|
287
|
+
textSubmit.click();
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Prevent input events from bubbling
|
|
292
|
+
textInput.addEventListener('click', (e) => e.stopPropagation());
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Prevent prompt input events from bubbling
|
|
296
|
+
promptInput.addEventListener('click', (e) => e.stopPropagation());
|
|
297
|
+
|
|
298
|
+
// Close button
|
|
299
|
+
closeBtn.addEventListener('click', (e) => {
|
|
300
|
+
e.stopPropagation();
|
|
301
|
+
unpauseInspection();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Focus prompt input
|
|
305
|
+
setTimeout(() => promptInput.focus(), 100);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Pause inspection
|
|
309
|
+
function pauseInspection(element, elementData) {
|
|
310
|
+
isPaused = true;
|
|
311
|
+
selectedElement = element;
|
|
312
|
+
selectedElementData = elementData;
|
|
313
|
+
|
|
314
|
+
// Disable scrolling
|
|
315
|
+
document.body.style.overflow = 'hidden';
|
|
316
|
+
|
|
317
|
+
// Keep overlay visible
|
|
318
|
+
highlightElement(element);
|
|
319
|
+
overlay.style.borderColor = '#10b981';
|
|
320
|
+
overlay.style.background = 'rgba(16, 185, 129, 0.1)';
|
|
321
|
+
|
|
322
|
+
// Remove hover listeners
|
|
323
|
+
document.removeEventListener('mousemove', handleMouseMove, true);
|
|
324
|
+
|
|
325
|
+
// Create control box
|
|
326
|
+
createControlBox(element, elementData);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Unpause inspection
|
|
330
|
+
function unpauseInspection() {
|
|
331
|
+
isPaused = false;
|
|
332
|
+
selectedElement = null;
|
|
333
|
+
selectedElementData = null;
|
|
334
|
+
|
|
335
|
+
// Re-enable scrolling
|
|
336
|
+
document.body.style.overflow = '';
|
|
337
|
+
|
|
338
|
+
// Remove control box
|
|
339
|
+
if (controlBox) {
|
|
340
|
+
controlBox.remove();
|
|
341
|
+
controlBox = null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Restore overlay style
|
|
345
|
+
if (overlay) {
|
|
346
|
+
overlay.style.borderColor = '#3b82f6';
|
|
347
|
+
overlay.style.background = 'rgba(59, 130, 246, 0.1)';
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Re-enable hover
|
|
351
|
+
if (inspectMode) {
|
|
352
|
+
document.addEventListener('mousemove', handleMouseMove, true);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Get React Fiber node from DOM element
|
|
357
|
+
function getReactFiber(element) {
|
|
358
|
+
const key = Object.keys(element).find(k =>
|
|
359
|
+
k.startsWith('__reactFiber') ||
|
|
360
|
+
k.startsWith('__reactInternalInstance')
|
|
361
|
+
);
|
|
362
|
+
return key ? element[key] : null;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Find component file and line info from fiber
|
|
366
|
+
function getComponentInfo(fiber) {
|
|
367
|
+
let current = fiber;
|
|
368
|
+
while (current) {
|
|
369
|
+
// Check if fiber has _debugSource (development mode)
|
|
370
|
+
if (current._debugSource) {
|
|
371
|
+
const source = current._debugSource;
|
|
372
|
+
return {
|
|
373
|
+
fileName: source.fileName,
|
|
374
|
+
lineNumber: source.lineNumber,
|
|
375
|
+
columnNumber: source.columnNumber,
|
|
376
|
+
componentName: current.type?.name || current.elementType?.name || 'Unknown'
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Check if fiber has _debugOwner
|
|
381
|
+
if (current._debugOwner) {
|
|
382
|
+
current = current._debugOwner;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Try return fiber (parent component)
|
|
387
|
+
if (current.return) {
|
|
388
|
+
current = current.return;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Highlight element
|
|
398
|
+
function highlightElement(element) {
|
|
399
|
+
if (!overlay || !element) return;
|
|
400
|
+
|
|
401
|
+
const rect = element.getBoundingClientRect();
|
|
402
|
+
overlay.style.display = 'block';
|
|
403
|
+
overlay.style.top = rect.top + window.scrollY + 'px';
|
|
404
|
+
overlay.style.left = rect.left + window.scrollX + 'px';
|
|
405
|
+
overlay.style.width = rect.width + 'px';
|
|
406
|
+
overlay.style.height = rect.height + 'px';
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Clear highlight
|
|
410
|
+
function clearHighlight() {
|
|
411
|
+
if (overlay) {
|
|
412
|
+
overlay.style.display = 'none';
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Handle mouse move
|
|
417
|
+
function handleMouseMove(e) {
|
|
418
|
+
if (!inspectMode || isPaused) return;
|
|
419
|
+
|
|
420
|
+
// Ignore control box and overlay
|
|
421
|
+
if (e.target.id === 'inspector-control-box' ||
|
|
422
|
+
e.target.closest('#inspector-control-box') ||
|
|
423
|
+
e.target.id === 'inspector-overlay') {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
hoveredElement = e.target;
|
|
428
|
+
highlightElement(hoveredElement);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Handle click
|
|
432
|
+
function handleClick(e) {
|
|
433
|
+
if (!inspectMode) return;
|
|
434
|
+
|
|
435
|
+
// Ignore clicks on control box
|
|
436
|
+
if (e.target.id === 'inspector-control-box' ||
|
|
437
|
+
e.target.closest('#inspector-control-box')) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// If already paused, ignore clicks (don't select new element)
|
|
442
|
+
if (isPaused) {
|
|
443
|
+
e.preventDefault();
|
|
444
|
+
e.stopPropagation();
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
e.preventDefault();
|
|
449
|
+
e.stopPropagation();
|
|
450
|
+
|
|
451
|
+
const element = e.target;
|
|
452
|
+
const fiber = getReactFiber(element);
|
|
453
|
+
const componentInfo = fiber ? getComponentInfo(fiber) : null;
|
|
454
|
+
|
|
455
|
+
const elementData = {
|
|
456
|
+
tagName: element.tagName,
|
|
457
|
+
className: element.className,
|
|
458
|
+
id: element.id,
|
|
459
|
+
component: componentInfo,
|
|
460
|
+
position: {
|
|
461
|
+
top: element.getBoundingClientRect().top,
|
|
462
|
+
left: element.getBoundingClientRect().left,
|
|
463
|
+
width: element.getBoundingClientRect().width,
|
|
464
|
+
height: element.getBoundingClientRect().height
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
// Send info to parent window
|
|
469
|
+
if (window.parent !== window) {
|
|
470
|
+
window.parent.postMessage({
|
|
471
|
+
type: 'INSPECTOR_ELEMENT_SELECTED',
|
|
472
|
+
data: elementData
|
|
473
|
+
}, '*');
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Log for debugging
|
|
477
|
+
console.log('Element selected:', {
|
|
478
|
+
element,
|
|
479
|
+
componentInfo
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// Pause inspection and show control box
|
|
483
|
+
pauseInspection(element, elementData);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Toggle inspect mode
|
|
487
|
+
function toggleInspectMode(active) {
|
|
488
|
+
inspectMode = active;
|
|
489
|
+
|
|
490
|
+
if (inspectMode) {
|
|
491
|
+
createOverlay();
|
|
492
|
+
document.body.style.cursor = 'crosshair';
|
|
493
|
+
document.addEventListener('mousemove', handleMouseMove, true);
|
|
494
|
+
document.addEventListener('click', handleClick, true);
|
|
495
|
+
} else {
|
|
496
|
+
// Clean up everything
|
|
497
|
+
clearHighlight();
|
|
498
|
+
unpauseInspection();
|
|
499
|
+
document.body.style.cursor = '';
|
|
500
|
+
document.removeEventListener('mousemove', handleMouseMove, true);
|
|
501
|
+
document.removeEventListener('click', handleClick, true);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Listen for messages from parent
|
|
506
|
+
window.addEventListener('message', (event) => {
|
|
507
|
+
if (event.data.type === 'TOGGLE_INSPECTOR') {
|
|
508
|
+
toggleInspectMode(event.data.active);
|
|
509
|
+
|
|
510
|
+
// Update labels if provided
|
|
511
|
+
if (event.data.labels) {
|
|
512
|
+
labels = { ...labels, ...event.data.labels };
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
// Track URL changes
|
|
518
|
+
let lastUrl = location.href;
|
|
519
|
+
|
|
520
|
+
function notifyUrlChange() {
|
|
521
|
+
const newUrl = location.href;
|
|
522
|
+
if (newUrl !== lastUrl) {
|
|
523
|
+
lastUrl = newUrl;
|
|
524
|
+
|
|
525
|
+
if (window.parent !== window) {
|
|
526
|
+
window.parent.postMessage({
|
|
527
|
+
type: 'URL_CHANGED',
|
|
528
|
+
data: {
|
|
529
|
+
url: newUrl,
|
|
530
|
+
pathname: location.pathname,
|
|
531
|
+
search: location.search,
|
|
532
|
+
hash: location.hash
|
|
533
|
+
}
|
|
534
|
+
}, '*');
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Monitor URL changes via pushState/replaceState
|
|
540
|
+
const originalPushState = history.pushState;
|
|
541
|
+
const originalReplaceState = history.replaceState;
|
|
542
|
+
|
|
543
|
+
history.pushState = function() {
|
|
544
|
+
originalPushState.apply(this, arguments);
|
|
545
|
+
notifyUrlChange();
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
history.replaceState = function() {
|
|
549
|
+
originalReplaceState.apply(this, arguments);
|
|
550
|
+
notifyUrlChange();
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
// Also listen to popstate
|
|
554
|
+
window.addEventListener('popstate', notifyUrlChange);
|
|
555
|
+
|
|
556
|
+
// Initial URL notification
|
|
557
|
+
setTimeout(() => {
|
|
558
|
+
notifyUrlChange();
|
|
559
|
+
}, 100);
|
|
560
|
+
|
|
561
|
+
// Error tracking
|
|
562
|
+
// Save original console.error FIRST before any functions
|
|
563
|
+
const originalConsoleError = console.error;
|
|
564
|
+
|
|
565
|
+
function sendError(errorData) {
|
|
566
|
+
if (window.parent !== window) {
|
|
567
|
+
window.parent.postMessage({
|
|
568
|
+
type: 'INSPECTOR_ERROR',
|
|
569
|
+
data: errorData
|
|
570
|
+
}, '*');
|
|
571
|
+
}
|
|
572
|
+
// Use original console.error to avoid infinite loop
|
|
573
|
+
originalConsoleError.call(console, '🔍 Inspector Error:', errorData);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Track JavaScript errors
|
|
577
|
+
window.addEventListener('error', (event) => {
|
|
578
|
+
sendError({
|
|
579
|
+
type: 'javascript',
|
|
580
|
+
message: event.message,
|
|
581
|
+
stack: event.error?.stack,
|
|
582
|
+
fileName: event.filename,
|
|
583
|
+
lineNumber: event.lineno,
|
|
584
|
+
columnNumber: event.colno,
|
|
585
|
+
timestamp: Date.now()
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
// Track unhandled promise rejections
|
|
590
|
+
window.addEventListener('unhandledrejection', (event) => {
|
|
591
|
+
sendError({
|
|
592
|
+
type: 'promise',
|
|
593
|
+
message: event.reason?.message || String(event.reason),
|
|
594
|
+
stack: event.reason?.stack,
|
|
595
|
+
timestamp: Date.now()
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// Track console.error calls
|
|
600
|
+
console.error = function(...args) {
|
|
601
|
+
const message = args.map(arg =>
|
|
602
|
+
typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
|
|
603
|
+
).join(' ');
|
|
604
|
+
|
|
605
|
+
sendError({
|
|
606
|
+
type: 'console',
|
|
607
|
+
message: message,
|
|
608
|
+
stack: new Error().stack,
|
|
609
|
+
timestamp: Date.now()
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// Call original console.error
|
|
613
|
+
originalConsoleError.apply(console, args);
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
console.log('🔍 Inspector plugin loaded');
|
|
617
|
+
})();
|
|
618
|
+
`,
|
|
619
|
+
injectTo: "body",
|
|
620
|
+
},
|
|
621
|
+
],
|
|
622
|
+
};
|
|
623
|
+
},
|
|
624
|
+
};
|
|
625
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for @promake/inspector
|
|
3
|
+
*/
|
|
4
|
+
export interface ComponentInfo {
|
|
5
|
+
componentName: string;
|
|
6
|
+
fileName: string;
|
|
7
|
+
lineNumber: number;
|
|
8
|
+
columnNumber: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ElementPosition {
|
|
11
|
+
top: number;
|
|
12
|
+
left: number;
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
}
|
|
16
|
+
export interface SelectedElementData {
|
|
17
|
+
tagName: string;
|
|
18
|
+
className: string;
|
|
19
|
+
id: string;
|
|
20
|
+
component: ComponentInfo | null;
|
|
21
|
+
position: ElementPosition;
|
|
22
|
+
}
|
|
23
|
+
export interface UrlChangeData {
|
|
24
|
+
url: string;
|
|
25
|
+
pathname: string;
|
|
26
|
+
search: string;
|
|
27
|
+
hash: string;
|
|
28
|
+
}
|
|
29
|
+
export interface PromptSubmittedData {
|
|
30
|
+
prompt: string;
|
|
31
|
+
element: SelectedElementData;
|
|
32
|
+
}
|
|
33
|
+
export interface TextUpdatedData {
|
|
34
|
+
text: string;
|
|
35
|
+
originalText: string;
|
|
36
|
+
element: SelectedElementData;
|
|
37
|
+
}
|
|
38
|
+
export interface ErrorData {
|
|
39
|
+
type: "javascript" | "promise" | "console";
|
|
40
|
+
message: string;
|
|
41
|
+
stack?: string;
|
|
42
|
+
fileName?: string;
|
|
43
|
+
lineNumber?: number;
|
|
44
|
+
columnNumber?: number;
|
|
45
|
+
timestamp: number;
|
|
46
|
+
}
|
|
47
|
+
export interface InspectorLabels {
|
|
48
|
+
editText?: string;
|
|
49
|
+
textPlaceholder?: string;
|
|
50
|
+
updateText?: string;
|
|
51
|
+
promptPlaceholder?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface InspectorCallbacks {
|
|
54
|
+
onElementSelected?: (data: SelectedElementData) => void;
|
|
55
|
+
onUrlChange?: (data: UrlChangeData) => void;
|
|
56
|
+
onPromptSubmitted?: (data: PromptSubmittedData) => void;
|
|
57
|
+
onTextUpdated?: (data: TextUpdatedData) => void;
|
|
58
|
+
onError?: (data: ErrorData) => void;
|
|
59
|
+
}
|
|
60
|
+
export interface UseInspectorReturn {
|
|
61
|
+
isInspecting: boolean;
|
|
62
|
+
toggleInspector: (active?: boolean) => void;
|
|
63
|
+
startInspecting: () => void;
|
|
64
|
+
stopInspecting: () => void;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAGD,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAGD,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,aAAa,GAAG,IAAI,CAAC;IAChC,QAAQ,EAAE,eAAe,CAAC;CAC3B;AAGD,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAGD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAGD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAGD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,YAAY,GAAG,SAAS,GAAG,SAAS,CAAC;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAGD,MAAM,WAAW,kBAAkB;IACjC,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACxD,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAC5C,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACxD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAC;IAChD,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;CACrC;AAGD,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,OAAO,CAAC;IACtB,eAAe,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,IAAI,CAAC;CAC5B"}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@promakeai/inspector",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Visual element inspector for React apps in iframe with AI prompt support",
|
|
5
|
+
"author": "Promake",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/hook.js",
|
|
8
|
+
"module": "./dist/hook.js",
|
|
9
|
+
"types": "./dist/hook.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
"./plugin": {
|
|
12
|
+
"types": "./dist/plugin.d.ts",
|
|
13
|
+
"import": "./dist/plugin.js"
|
|
14
|
+
},
|
|
15
|
+
"./hook": {
|
|
16
|
+
"types": "./dist/hook.d.ts",
|
|
17
|
+
"import": "./dist/hook.js"
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc && tsc -p tsconfig.plugin.json",
|
|
27
|
+
"prepublishOnly": "npm run build"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"vite",
|
|
31
|
+
"react",
|
|
32
|
+
"inspector",
|
|
33
|
+
"devtools",
|
|
34
|
+
"iframe",
|
|
35
|
+
"visual-editor",
|
|
36
|
+
"element-inspector",
|
|
37
|
+
"ai",
|
|
38
|
+
"prompt"
|
|
39
|
+
],
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"react": "^18.0.0",
|
|
42
|
+
"vite": "^5.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/react": "^18.2.0",
|
|
46
|
+
"typescript": "^5.3.0",
|
|
47
|
+
"vite": "^5.4.0"
|
|
48
|
+
}
|
|
49
|
+
}
|