Commit
·
6ffcdd8
1
Parent(s):
a936c5a
merge main into image
Browse files
app/commit.json
CHANGED
@@ -1 +1 @@
|
|
1 |
-
{ "commit": "
|
|
|
1 |
+
{ "commit": "a936c5a99036e0ea65c976e19b3bdb39f8d7cd40" }
|
app/components/workbench/Preview.tsx
CHANGED
@@ -289,14 +289,20 @@ export const Preview = memo(() => {
|
|
289 |
>
|
290 |
{activePreview ? (
|
291 |
<>
|
292 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
293 |
<ScreenshotSelector
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
<div className="flex w-full h-full justify-center items-center bg-white">No preview available</div>
|
301 |
)}
|
302 |
|
|
|
289 |
>
|
290 |
{activePreview ? (
|
291 |
<>
|
292 |
+
<iframe
|
293 |
+
ref={iframeRef}
|
294 |
+
title="preview"
|
295 |
+
className="border-none w-full h-full bg-white"
|
296 |
+
src={iframeUrl}
|
297 |
+
allowFullScreen
|
298 |
+
/>
|
299 |
<ScreenshotSelector
|
300 |
+
isSelectionMode={isSelectionMode}
|
301 |
+
setIsSelectionMode={setIsSelectionMode}
|
302 |
+
containerRef={iframeRef}
|
303 |
+
/>
|
304 |
+
</>
|
305 |
+
) : (
|
306 |
<div className="flex w-full h-full justify-center items-center bg-white">No preview available</div>
|
307 |
)}
|
308 |
|
app/components/workbench/ScreenshotSelector.tsx
CHANGED
@@ -24,6 +24,7 @@ export const ScreenshotSelector = memo(
|
|
24 |
videoRef.current.remove();
|
25 |
videoRef.current = null;
|
26 |
}
|
|
|
27 |
if (mediaStreamRef.current) {
|
28 |
mediaStreamRef.current.getTracks().forEach((track) => track.stop());
|
29 |
mediaStreamRef.current = null;
|
@@ -52,10 +53,12 @@ export const ScreenshotSelector = memo(
|
|
52 |
videoRef.current.remove();
|
53 |
videoRef.current = null;
|
54 |
}
|
|
|
55 |
if (mediaStreamRef.current) {
|
56 |
mediaStreamRef.current.getTracks().forEach((track) => track.stop());
|
57 |
mediaStreamRef.current = null;
|
58 |
}
|
|
|
59 |
setIsSelectionMode(false);
|
60 |
setSelectionStart(null);
|
61 |
setSelectionEnd(null);
|
@@ -84,16 +87,23 @@ export const ScreenshotSelector = memo(
|
|
84 |
toast.error('Failed to initialize screen capture');
|
85 |
}
|
86 |
}
|
|
|
87 |
return mediaStreamRef.current;
|
88 |
};
|
89 |
|
90 |
const handleCopySelection = useCallback(async () => {
|
91 |
-
if (!isSelectionMode || !selectionStart || !selectionEnd || !containerRef.current)
|
|
|
|
|
92 |
|
93 |
setIsCapturing(true);
|
|
|
94 |
try {
|
95 |
const stream = await initializeStream();
|
96 |
-
|
|
|
|
|
|
|
97 |
|
98 |
// Wait for video to be ready
|
99 |
await new Promise((resolve) => setTimeout(resolve, 300));
|
@@ -102,6 +112,7 @@ export const ScreenshotSelector = memo(
|
|
102 |
const tempCanvas = document.createElement('canvas');
|
103 |
tempCanvas.width = videoRef.current.videoWidth;
|
104 |
tempCanvas.height = videoRef.current.videoHeight;
|
|
|
105 |
const tempCtx = tempCanvas.getContext('2d');
|
106 |
|
107 |
if (!tempCtx) {
|
@@ -140,6 +151,7 @@ export const ScreenshotSelector = memo(
|
|
140 |
const canvas = document.createElement('canvas');
|
141 |
canvas.width = Math.round(Math.abs(selectionEnd.x - selectionStart.x));
|
142 |
canvas.height = Math.round(Math.abs(selectionEnd.y - selectionStart.y));
|
|
|
143 |
const ctx = canvas.getContext('2d');
|
144 |
|
145 |
if (!ctx) {
|
@@ -152,18 +164,23 @@ export const ScreenshotSelector = memo(
|
|
152 |
// Convert to blob
|
153 |
const blob = await new Promise<Blob>((resolve, reject) => {
|
154 |
canvas.toBlob((blob) => {
|
155 |
-
if (blob)
|
156 |
-
|
|
|
|
|
|
|
157 |
}, 'image/png');
|
158 |
});
|
159 |
|
160 |
// Create a FileReader to convert blob to base64
|
161 |
const reader = new FileReader();
|
|
|
162 |
reader.onload = (e) => {
|
163 |
const base64Image = e.target?.result as string;
|
164 |
|
165 |
// Find the textarea element
|
166 |
const textarea = document.querySelector('textarea');
|
|
|
167 |
if (textarea) {
|
168 |
// Get the setters from the BaseChat component
|
169 |
const setUploadedFiles = (window as any).__BOLT_SET_UPLOADED_FILES__;
|
@@ -186,6 +203,7 @@ export const ScreenshotSelector = memo(
|
|
186 |
} catch (error) {
|
187 |
console.error('Failed to capture screenshot:', error);
|
188 |
toast.error('Failed to capture screenshot');
|
|
|
189 |
if (mediaStreamRef.current) {
|
190 |
mediaStreamRef.current.getTracks().forEach((track) => track.stop());
|
191 |
mediaStreamRef.current = null;
|
@@ -202,7 +220,11 @@ export const ScreenshotSelector = memo(
|
|
202 |
(e: React.MouseEvent) => {
|
203 |
e.preventDefault();
|
204 |
e.stopPropagation();
|
205 |
-
|
|
|
|
|
|
|
|
|
206 |
const rect = e.currentTarget.getBoundingClientRect();
|
207 |
const x = e.clientX - rect.left;
|
208 |
const y = e.clientY - rect.top;
|
@@ -216,7 +238,11 @@ export const ScreenshotSelector = memo(
|
|
216 |
(e: React.MouseEvent) => {
|
217 |
e.preventDefault();
|
218 |
e.stopPropagation();
|
219 |
-
|
|
|
|
|
|
|
|
|
220 |
const rect = e.currentTarget.getBoundingClientRect();
|
221 |
const x = e.clientX - rect.left;
|
222 |
const y = e.clientY - rect.top;
|
@@ -225,7 +251,9 @@ export const ScreenshotSelector = memo(
|
|
225 |
[isSelectionMode, selectionStart],
|
226 |
);
|
227 |
|
228 |
-
if (!isSelectionMode)
|
|
|
|
|
229 |
|
230 |
return (
|
231 |
<div
|
|
|
24 |
videoRef.current.remove();
|
25 |
videoRef.current = null;
|
26 |
}
|
27 |
+
|
28 |
if (mediaStreamRef.current) {
|
29 |
mediaStreamRef.current.getTracks().forEach((track) => track.stop());
|
30 |
mediaStreamRef.current = null;
|
|
|
53 |
videoRef.current.remove();
|
54 |
videoRef.current = null;
|
55 |
}
|
56 |
+
|
57 |
if (mediaStreamRef.current) {
|
58 |
mediaStreamRef.current.getTracks().forEach((track) => track.stop());
|
59 |
mediaStreamRef.current = null;
|
60 |
}
|
61 |
+
|
62 |
setIsSelectionMode(false);
|
63 |
setSelectionStart(null);
|
64 |
setSelectionEnd(null);
|
|
|
87 |
toast.error('Failed to initialize screen capture');
|
88 |
}
|
89 |
}
|
90 |
+
|
91 |
return mediaStreamRef.current;
|
92 |
};
|
93 |
|
94 |
const handleCopySelection = useCallback(async () => {
|
95 |
+
if (!isSelectionMode || !selectionStart || !selectionEnd || !containerRef.current) {
|
96 |
+
return;
|
97 |
+
}
|
98 |
|
99 |
setIsCapturing(true);
|
100 |
+
|
101 |
try {
|
102 |
const stream = await initializeStream();
|
103 |
+
|
104 |
+
if (!stream || !videoRef.current) {
|
105 |
+
return;
|
106 |
+
}
|
107 |
|
108 |
// Wait for video to be ready
|
109 |
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
|
112 |
const tempCanvas = document.createElement('canvas');
|
113 |
tempCanvas.width = videoRef.current.videoWidth;
|
114 |
tempCanvas.height = videoRef.current.videoHeight;
|
115 |
+
|
116 |
const tempCtx = tempCanvas.getContext('2d');
|
117 |
|
118 |
if (!tempCtx) {
|
|
|
151 |
const canvas = document.createElement('canvas');
|
152 |
canvas.width = Math.round(Math.abs(selectionEnd.x - selectionStart.x));
|
153 |
canvas.height = Math.round(Math.abs(selectionEnd.y - selectionStart.y));
|
154 |
+
|
155 |
const ctx = canvas.getContext('2d');
|
156 |
|
157 |
if (!ctx) {
|
|
|
164 |
// Convert to blob
|
165 |
const blob = await new Promise<Blob>((resolve, reject) => {
|
166 |
canvas.toBlob((blob) => {
|
167 |
+
if (blob) {
|
168 |
+
resolve(blob);
|
169 |
+
} else {
|
170 |
+
reject(new Error('Failed to create blob'));
|
171 |
+
}
|
172 |
}, 'image/png');
|
173 |
});
|
174 |
|
175 |
// Create a FileReader to convert blob to base64
|
176 |
const reader = new FileReader();
|
177 |
+
|
178 |
reader.onload = (e) => {
|
179 |
const base64Image = e.target?.result as string;
|
180 |
|
181 |
// Find the textarea element
|
182 |
const textarea = document.querySelector('textarea');
|
183 |
+
|
184 |
if (textarea) {
|
185 |
// Get the setters from the BaseChat component
|
186 |
const setUploadedFiles = (window as any).__BOLT_SET_UPLOADED_FILES__;
|
|
|
203 |
} catch (error) {
|
204 |
console.error('Failed to capture screenshot:', error);
|
205 |
toast.error('Failed to capture screenshot');
|
206 |
+
|
207 |
if (mediaStreamRef.current) {
|
208 |
mediaStreamRef.current.getTracks().forEach((track) => track.stop());
|
209 |
mediaStreamRef.current = null;
|
|
|
220 |
(e: React.MouseEvent) => {
|
221 |
e.preventDefault();
|
222 |
e.stopPropagation();
|
223 |
+
|
224 |
+
if (!isSelectionMode) {
|
225 |
+
return;
|
226 |
+
}
|
227 |
+
|
228 |
const rect = e.currentTarget.getBoundingClientRect();
|
229 |
const x = e.clientX - rect.left;
|
230 |
const y = e.clientY - rect.top;
|
|
|
238 |
(e: React.MouseEvent) => {
|
239 |
e.preventDefault();
|
240 |
e.stopPropagation();
|
241 |
+
|
242 |
+
if (!isSelectionMode || !selectionStart) {
|
243 |
+
return;
|
244 |
+
}
|
245 |
+
|
246 |
const rect = e.currentTarget.getBoundingClientRect();
|
247 |
const x = e.clientX - rect.left;
|
248 |
const y = e.clientY - rect.top;
|
|
|
251 |
[isSelectionMode, selectionStart],
|
252 |
);
|
253 |
|
254 |
+
if (!isSelectionMode) {
|
255 |
+
return null;
|
256 |
+
}
|
257 |
|
258 |
return (
|
259 |
<div
|