slice-machine-ui 2.19.2-alpha.jp-figma-to-prismic.2 → 2.19.2-alpha.jp-figma-to-prismic.4
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/out/404.html +1 -1
- package/out/_next/static/{j0_D1z-ZN75hJO-JvwC0X → J2YngfgTVf7j_xF1hSI57}/_buildManifest.js +1 -1
- package/out/_next/static/chunks/20-169231cb23a752ff.js +1 -0
- package/out/_next/static/chunks/{489-ce3053e1d81ade83.js → 489-b4b2ce029ea444c1.js} +1 -1
- package/out/_next/static/chunks/907-fc580cc28042182f.js +1 -0
- package/out/_next/static/chunks/pages/{_app-b73cf0344465689d.js → _app-9abe37b33f3ca7c2.js} +1 -1
- package/out/changelog.html +1 -1
- package/out/changes.html +1 -1
- package/out/custom-types/[customTypeId].html +1 -1
- package/out/custom-types.html +1 -1
- package/out/index.html +1 -1
- package/out/labs.html +1 -1
- package/out/page-types/[pageTypeId].html +1 -1
- package/out/slices/[lib]/[sliceName]/[variation]/simulator.html +1 -1
- package/out/slices/[lib]/[sliceName]/[variation].html +1 -1
- package/out/slices.html +1 -1
- package/package.json +3 -3
- package/src/features/customTypes/customTypesBuilder/CreateSliceFromImageModal/CreateSliceFromImageModal.tsx +405 -237
- package/src/features/customTypes/customTypesBuilder/CreateSliceFromImageModal/SliceCard.tsx +23 -4
- package/src/icons/FigmaIcon.tsx +16 -6
- package/src/legacy/lib/builders/CustomTypeBuilder/SliceZone/index.tsx +1 -1
- package/test/src/modules/__fixtures__/serverState.ts +0 -1
- package/out/_next/static/chunks/20-4cb8941c8aafa019.js +0 -1
- package/out/_next/static/chunks/907-88dafe5c1e80dead.js +0 -1
- /package/out/_next/static/{j0_D1z-ZN75hJO-JvwC0X → J2YngfgTVf7j_xF1hSI57}/_ssgManifest.js +0 -0
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
Box,
|
|
5
5
|
Button,
|
|
6
6
|
Dialog,
|
|
7
|
+
DialogActionButton,
|
|
7
8
|
DialogActions,
|
|
8
9
|
DialogCancelButton,
|
|
9
10
|
DialogContent,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
DialogHeader,
|
|
12
13
|
FileDropZone,
|
|
13
14
|
FileUploadButton,
|
|
15
|
+
ProgressCircle,
|
|
14
16
|
ScrollArea,
|
|
15
17
|
Text,
|
|
16
18
|
} from "@prismicio/editor-ui";
|
|
@@ -24,6 +26,7 @@ import { getState, telemetry } from "@/apiClient";
|
|
|
24
26
|
import { addAiFeedback } from "@/features/aiFeedback";
|
|
25
27
|
import { useOnboarding } from "@/features/onboarding/useOnboarding";
|
|
26
28
|
import { useAutoSync } from "@/features/sync/AutoSyncProvider";
|
|
29
|
+
import { useExperimentVariant } from "@/hooks/useExperimentVariant";
|
|
27
30
|
import { FigmaIcon } from "@/icons/FigmaIcon";
|
|
28
31
|
import { managerClient } from "@/managerClient";
|
|
29
32
|
import useSliceMachineActions from "@/modules/useSliceMachineActions";
|
|
@@ -41,31 +44,27 @@ const IMAGE_UPLOAD_LIMIT = 10;
|
|
|
41
44
|
interface CreateSliceFromImageModalProps {
|
|
42
45
|
open: boolean;
|
|
43
46
|
location: "custom_type" | "page_type" | "slices";
|
|
44
|
-
onSuccess: (args: {
|
|
45
|
-
slices: {
|
|
46
|
-
model: SharedSlice;
|
|
47
|
-
langSmithUrl?: string;
|
|
48
|
-
}[];
|
|
49
|
-
library: string;
|
|
50
|
-
}) => void;
|
|
47
|
+
onSuccess: (args: { slices: SharedSlice[]; library: string }) => void;
|
|
51
48
|
onClose: () => void;
|
|
52
49
|
}
|
|
53
50
|
|
|
54
51
|
export function CreateSliceFromImageModal(
|
|
55
52
|
props: CreateSliceFromImageModalProps,
|
|
56
53
|
) {
|
|
57
|
-
const { open, location, onClose } = props;
|
|
54
|
+
const { open, location, onClose, onSuccess } = props;
|
|
58
55
|
const [slices, setSlices] = useState<Slice[]>([]);
|
|
56
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
59
57
|
const { syncChanges } = useAutoSync();
|
|
60
58
|
const { createSliceSuccess } = useSliceMachineActions();
|
|
61
59
|
const { completeStep } = useOnboarding();
|
|
62
60
|
const existingSlices = useExistingSlices({ open });
|
|
63
|
-
|
|
61
|
+
const { libraryID, isLoading: isLoadingLibraryID } = useLibraryID();
|
|
64
62
|
/**
|
|
65
63
|
* Keeps track of the current instance id.
|
|
66
64
|
* When the modal is closed, the id is reset.
|
|
67
65
|
*/
|
|
68
66
|
const id = useRef(crypto.randomUUID());
|
|
67
|
+
const isFigmaEnabled = useIsFigmaEnabled();
|
|
69
68
|
|
|
70
69
|
useHotkeys(
|
|
71
70
|
["meta+v", "ctrl+v"],
|
|
@@ -73,15 +72,9 @@ export function CreateSliceFromImageModal(
|
|
|
73
72
|
event.preventDefault();
|
|
74
73
|
void handlePaste();
|
|
75
74
|
},
|
|
76
|
-
{ enabled: open },
|
|
75
|
+
{ enabled: open && isFigmaEnabled },
|
|
77
76
|
);
|
|
78
77
|
|
|
79
|
-
useEffect(() => {
|
|
80
|
-
if (slices.every((slice) => slice.status === "success")) {
|
|
81
|
-
void onAllComplete();
|
|
82
|
-
}
|
|
83
|
-
}, [slices]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
84
|
-
|
|
85
78
|
const setSlice = (args: {
|
|
86
79
|
index: number;
|
|
87
80
|
slice: (prevSlice: Slice) => Slice;
|
|
@@ -98,6 +91,8 @@ export function CreateSliceFromImageModal(
|
|
|
98
91
|
};
|
|
99
92
|
|
|
100
93
|
const onImagesSelected = (images: File[]) => {
|
|
94
|
+
if (hasTriggeredGeneration) return;
|
|
95
|
+
|
|
101
96
|
if (images.length > IMAGE_UPLOAD_LIMIT) {
|
|
102
97
|
toast.error(
|
|
103
98
|
`You can only upload ${IMAGE_UPLOAD_LIMIT} images at a time.`,
|
|
@@ -105,22 +100,39 @@ export function CreateSliceFromImageModal(
|
|
|
105
100
|
return;
|
|
106
101
|
}
|
|
107
102
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
image
|
|
113
|
-
|
|
114
|
-
|
|
103
|
+
const startIndex = slices.length;
|
|
104
|
+
setSlices((prevSlices) => [
|
|
105
|
+
...prevSlices,
|
|
106
|
+
...images.map(
|
|
107
|
+
(image): Slice => ({
|
|
108
|
+
source: "upload",
|
|
109
|
+
status: "uploading",
|
|
110
|
+
image,
|
|
111
|
+
}),
|
|
112
|
+
),
|
|
113
|
+
]);
|
|
115
114
|
|
|
116
|
-
images.forEach((imageData,
|
|
117
|
-
|
|
115
|
+
images.forEach((imageData, relativeIndex) => {
|
|
116
|
+
const index = startIndex + relativeIndex;
|
|
117
|
+
void uploadImage({ index, imageData, source: "upload" });
|
|
118
118
|
});
|
|
119
119
|
};
|
|
120
120
|
|
|
121
121
|
const handlePaste = async () => {
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
if (
|
|
123
|
+
!open ||
|
|
124
|
+
!isFigmaEnabled ||
|
|
125
|
+
// For now we only support one Figma slice at a time
|
|
126
|
+
slices.some((slice) => slice.source === "figma")
|
|
127
|
+
) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Don't allow pasting while uploads or generation are in progress
|
|
132
|
+
const isLoading = slices.some(
|
|
133
|
+
(slice) => slice.status === "uploading" || slice.status === "generating",
|
|
134
|
+
);
|
|
135
|
+
if (isLoading) return;
|
|
124
136
|
|
|
125
137
|
const supportsClipboardRead =
|
|
126
138
|
typeof navigator.clipboard?.read === "function";
|
|
@@ -217,7 +229,7 @@ export function CreateSliceFromImageModal(
|
|
|
217
229
|
]);
|
|
218
230
|
|
|
219
231
|
// Start uploading the new image
|
|
220
|
-
void
|
|
232
|
+
void uploadImage({ index: newIndex, imageData, source: "figma" });
|
|
221
233
|
|
|
222
234
|
toast.success(`Pasted ${imageName}${success ? " from Figma" : ""}`);
|
|
223
235
|
} catch (error) {
|
|
@@ -228,7 +240,7 @@ export function CreateSliceFromImageModal(
|
|
|
228
240
|
}
|
|
229
241
|
};
|
|
230
242
|
|
|
231
|
-
const
|
|
243
|
+
const uploadImage = async (args: {
|
|
232
244
|
index: number;
|
|
233
245
|
imageData: File;
|
|
234
246
|
source: "figma" | "upload";
|
|
@@ -236,22 +248,28 @@ export function CreateSliceFromImageModal(
|
|
|
236
248
|
const { index, imageData, source } = args;
|
|
237
249
|
const currentId = id.current;
|
|
238
250
|
|
|
239
|
-
const smConfig = await managerClient.project.getSliceMachineConfig();
|
|
240
|
-
const libraryID = smConfig?.libraries?.[0];
|
|
241
|
-
if (libraryID === undefined) {
|
|
242
|
-
throw new Error("No library found in the config.");
|
|
243
|
-
}
|
|
244
|
-
|
|
245
251
|
setSlice({
|
|
246
252
|
index,
|
|
247
|
-
slice: (prevSlice) => ({
|
|
253
|
+
slice: (prevSlice) => ({
|
|
254
|
+
...prevSlice,
|
|
255
|
+
status: "uploading",
|
|
256
|
+
image: imageData,
|
|
257
|
+
source,
|
|
258
|
+
}),
|
|
248
259
|
});
|
|
249
260
|
|
|
250
261
|
try {
|
|
251
262
|
const imageUrl = await getImageUrl({ image: imageData });
|
|
252
263
|
if (currentId !== id.current) return;
|
|
253
264
|
|
|
254
|
-
|
|
265
|
+
setSlice({
|
|
266
|
+
index,
|
|
267
|
+
slice: (prevSlice) => ({
|
|
268
|
+
...prevSlice,
|
|
269
|
+
status: "pending",
|
|
270
|
+
thumbnailUrl: imageUrl,
|
|
271
|
+
}),
|
|
272
|
+
});
|
|
255
273
|
} catch {
|
|
256
274
|
if (currentId !== id.current) return;
|
|
257
275
|
setSlice({
|
|
@@ -259,12 +277,28 @@ export function CreateSliceFromImageModal(
|
|
|
259
277
|
slice: (prevSlice) => ({
|
|
260
278
|
...prevSlice,
|
|
261
279
|
status: "uploadError",
|
|
262
|
-
onRetry: () => void
|
|
280
|
+
onRetry: () => void uploadImage({ index, imageData, source }),
|
|
263
281
|
}),
|
|
264
282
|
});
|
|
265
283
|
}
|
|
266
284
|
};
|
|
267
285
|
|
|
286
|
+
const generateAllPendingSlices = () => {
|
|
287
|
+
if (libraryID === undefined) return;
|
|
288
|
+
|
|
289
|
+
// Generate all pending slices simultaneously
|
|
290
|
+
slices.forEach((slice, index) => {
|
|
291
|
+
if (slice.status === "pending") {
|
|
292
|
+
void inferSlice({
|
|
293
|
+
index,
|
|
294
|
+
libraryID,
|
|
295
|
+
imageUrl: slice.thumbnailUrl,
|
|
296
|
+
source: slice.source,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
};
|
|
301
|
+
|
|
268
302
|
const inferSlice = async (args: {
|
|
269
303
|
index: number;
|
|
270
304
|
imageUrl: string;
|
|
@@ -272,7 +306,7 @@ export function CreateSliceFromImageModal(
|
|
|
272
306
|
source: "figma" | "upload";
|
|
273
307
|
}) => {
|
|
274
308
|
const { index, imageUrl, libraryID, source } = args;
|
|
275
|
-
|
|
309
|
+
let currentId = id.current;
|
|
276
310
|
|
|
277
311
|
setSlice({
|
|
278
312
|
index,
|
|
@@ -292,6 +326,13 @@ export function CreateSliceFromImageModal(
|
|
|
292
326
|
|
|
293
327
|
if (currentId !== id.current) return;
|
|
294
328
|
|
|
329
|
+
const resolvedModel = sliceWithoutConflicts({
|
|
330
|
+
existingSlices: existingSlices.current,
|
|
331
|
+
newSlices: slices,
|
|
332
|
+
slice: inferResult.slice,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Update slice state with success
|
|
295
336
|
setSlices((prevSlices) => {
|
|
296
337
|
return prevSlices.map((prevSlice, i) => {
|
|
297
338
|
if (i !== index) return prevSlice;
|
|
@@ -299,16 +340,57 @@ export function CreateSliceFromImageModal(
|
|
|
299
340
|
...prevSlice,
|
|
300
341
|
status: "success",
|
|
301
342
|
thumbnailUrl: imageUrl,
|
|
302
|
-
model:
|
|
303
|
-
|
|
304
|
-
newSlices: slices,
|
|
305
|
-
slice: inferResult.slice,
|
|
306
|
-
}),
|
|
343
|
+
model: resolvedModel,
|
|
344
|
+
langSmithUrl: inferResult.langSmithUrl,
|
|
307
345
|
};
|
|
308
346
|
});
|
|
309
347
|
});
|
|
348
|
+
|
|
349
|
+
if (source === "upload") {
|
|
350
|
+
currentId = id.current;
|
|
351
|
+
const currentSlice = slices[index];
|
|
352
|
+
|
|
353
|
+
const { errors } = await managerClient.slices.createSlice({
|
|
354
|
+
libraryID,
|
|
355
|
+
model: resolvedModel,
|
|
356
|
+
});
|
|
357
|
+
if (errors.length) {
|
|
358
|
+
throw new Error(`Failed to create slice ${resolvedModel.id}.`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Set the variation screenshot
|
|
362
|
+
await managerClient.slices.updateSliceScreenshot({
|
|
363
|
+
libraryID,
|
|
364
|
+
sliceID: resolvedModel.id,
|
|
365
|
+
variationID: resolvedModel.variations[0].id,
|
|
366
|
+
data: currentSlice.image,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
if (currentId !== id.current) return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
void completeStep("createSlice");
|
|
373
|
+
|
|
374
|
+
void telemetry.track({
|
|
375
|
+
event: "slice:created",
|
|
376
|
+
id: resolvedModel.id,
|
|
377
|
+
name: resolvedModel.name,
|
|
378
|
+
library: libraryID,
|
|
379
|
+
location,
|
|
380
|
+
mode: "ai",
|
|
381
|
+
langSmithUrl: inferResult.langSmithUrl,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
addAiFeedback({
|
|
385
|
+
type: "model",
|
|
386
|
+
library: libraryID,
|
|
387
|
+
sliceId: resolvedModel.id,
|
|
388
|
+
variationId: resolvedModel.variations[0].id,
|
|
389
|
+
langSmithUrl: inferResult.langSmithUrl,
|
|
390
|
+
});
|
|
310
391
|
} catch {
|
|
311
392
|
if (currentId !== id.current) return;
|
|
393
|
+
|
|
312
394
|
setSlice({
|
|
313
395
|
index,
|
|
314
396
|
slice: (prevSlice) => ({
|
|
@@ -323,163 +405,230 @@ export function CreateSliceFromImageModal(
|
|
|
323
405
|
}
|
|
324
406
|
};
|
|
325
407
|
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
return acc;
|
|
332
|
-
}, []);
|
|
408
|
+
const resetState = () => {
|
|
409
|
+
id.current = crypto.randomUUID();
|
|
410
|
+
setSlices([]);
|
|
411
|
+
};
|
|
333
412
|
|
|
334
|
-
|
|
413
|
+
const handleClose = () => {
|
|
414
|
+
resetState();
|
|
415
|
+
onClose();
|
|
416
|
+
};
|
|
335
417
|
|
|
336
|
-
|
|
418
|
+
const onSubmit = async () => {
|
|
337
419
|
try {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
newSlices.filter((slice) => slice.source === "upload"),
|
|
341
|
-
);
|
|
342
|
-
if (currentId !== id.current) return;
|
|
343
|
-
|
|
344
|
-
id.current = crypto.randomUUID();
|
|
420
|
+
setIsSubmitting(true);
|
|
421
|
+
if (libraryID === undefined) return;
|
|
345
422
|
|
|
346
423
|
const serverState = await getState();
|
|
347
424
|
createSliceSuccess(serverState.libraries);
|
|
348
425
|
syncChanges();
|
|
349
426
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
name: model.name,
|
|
357
|
-
library,
|
|
358
|
-
location,
|
|
359
|
-
mode: "ai",
|
|
360
|
-
langSmithUrl,
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
addAiFeedback({
|
|
364
|
-
type: "model",
|
|
365
|
-
library,
|
|
366
|
-
sliceId: model.id,
|
|
367
|
-
variationId: model.variations[0].id,
|
|
368
|
-
langSmithUrl,
|
|
369
|
-
});
|
|
370
|
-
}
|
|
427
|
+
onSuccess({
|
|
428
|
+
slices: slices.flatMap((slice) =>
|
|
429
|
+
slice.status === "success" ? slice.model : [],
|
|
430
|
+
),
|
|
431
|
+
library: libraryID,
|
|
432
|
+
});
|
|
371
433
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
} successfully generated.`,
|
|
376
|
-
);
|
|
377
|
-
} catch {
|
|
378
|
-
if (currentId !== id.current) return;
|
|
379
|
-
toast.error("An unexpected error happened while adding slices.");
|
|
434
|
+
resetState();
|
|
435
|
+
} finally {
|
|
436
|
+
setIsSubmitting(false);
|
|
380
437
|
}
|
|
381
438
|
};
|
|
382
439
|
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
);
|
|
440
|
+
const loadingSliceCount = slices.filter((slice) => {
|
|
441
|
+
return slice.status === "uploading" || slice.status === "generating";
|
|
442
|
+
}).length;
|
|
443
|
+
|
|
444
|
+
const pendingSliceCount = slices.filter((slice) => {
|
|
445
|
+
return slice.status === "pending";
|
|
446
|
+
}).length;
|
|
447
|
+
|
|
448
|
+
const hasTriggeredGeneration = slices.some((slice) => {
|
|
449
|
+
return slice.status === "generating" || slice.status === "success";
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
const completedSliceCount = slices.filter((slice) => {
|
|
453
|
+
return slice.status === "success";
|
|
454
|
+
}).length;
|
|
386
455
|
|
|
456
|
+
const generateSliceCount = loadingSliceCount + pendingSliceCount;
|
|
457
|
+
|
|
458
|
+
console.log({
|
|
459
|
+
slices,
|
|
460
|
+
generateSliceCount,
|
|
461
|
+
loadingSliceCount,
|
|
462
|
+
pendingSliceCount,
|
|
463
|
+
});
|
|
387
464
|
return (
|
|
388
|
-
<Dialog
|
|
389
|
-
|
|
465
|
+
<Dialog
|
|
466
|
+
open={open}
|
|
467
|
+
onOpenChange={loadingSliceCount > 0 ? undefined : onOpenChange}
|
|
468
|
+
>
|
|
469
|
+
<DialogHeader title="Generate with AI" />
|
|
390
470
|
<DialogContent gap={0}>
|
|
391
471
|
<DialogDescription hidden>
|
|
392
472
|
Upload images to generate slices with AI
|
|
393
473
|
</DialogDescription>
|
|
394
|
-
{
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
474
|
+
{!isLoadingLibraryID ? (
|
|
475
|
+
<>
|
|
476
|
+
{slices.length === 0 ? (
|
|
477
|
+
<Box
|
|
478
|
+
padding={16}
|
|
479
|
+
height="100%"
|
|
480
|
+
gap={16}
|
|
481
|
+
display="flex"
|
|
482
|
+
flexDirection="column"
|
|
483
|
+
>
|
|
484
|
+
{isFigmaEnabled && (
|
|
485
|
+
<Box
|
|
486
|
+
display="flex"
|
|
487
|
+
gap={16}
|
|
488
|
+
alignItems="center"
|
|
489
|
+
backgroundColor="grey2"
|
|
490
|
+
padding={16}
|
|
491
|
+
borderRadius={12}
|
|
492
|
+
>
|
|
493
|
+
<Box
|
|
494
|
+
display="flex"
|
|
495
|
+
gap={8}
|
|
496
|
+
alignItems="center"
|
|
497
|
+
flexGrow={1}
|
|
498
|
+
>
|
|
499
|
+
<Box
|
|
500
|
+
width={48}
|
|
501
|
+
height={48}
|
|
502
|
+
backgroundColor="grey12"
|
|
503
|
+
borderRadius="100%"
|
|
504
|
+
display="flex"
|
|
505
|
+
alignItems="center"
|
|
506
|
+
justifyContent="center"
|
|
507
|
+
>
|
|
508
|
+
<FigmaIcon variant="original" height={25} />
|
|
509
|
+
</Box>
|
|
510
|
+
<Box display="flex" flexDirection="column" flexGrow={1}>
|
|
511
|
+
<Text variant="bold">Want to work faster?</Text>
|
|
512
|
+
<Text variant="small" color="grey11">
|
|
513
|
+
Copy frames from Figma with the Slice Machine plugin
|
|
514
|
+
and paste them here.
|
|
515
|
+
</Text>
|
|
516
|
+
</Box>
|
|
517
|
+
</Box>
|
|
518
|
+
<Button
|
|
519
|
+
endIcon="arrowForward"
|
|
520
|
+
color="indigo"
|
|
521
|
+
onClick={() =>
|
|
522
|
+
window.open(
|
|
523
|
+
"https://www.figma.com/community/plugin/TODO",
|
|
524
|
+
"_blank",
|
|
525
|
+
)
|
|
526
|
+
}
|
|
527
|
+
sx={{ marginRight: 8 }}
|
|
528
|
+
invisible
|
|
529
|
+
>
|
|
530
|
+
Install plugin
|
|
531
|
+
</Button>
|
|
532
|
+
</Box>
|
|
533
|
+
)}
|
|
534
|
+
<FileDropZone
|
|
535
|
+
onFilesSelected={onImagesSelected}
|
|
536
|
+
assetType="image"
|
|
537
|
+
maxFiles={IMAGE_UPLOAD_LIMIT}
|
|
538
|
+
overlay={
|
|
539
|
+
<UploadBlankSlate
|
|
540
|
+
onFilesSelected={onImagesSelected}
|
|
541
|
+
onPaste={() => void handlePaste()}
|
|
542
|
+
droppingFiles
|
|
543
|
+
/>
|
|
544
|
+
}
|
|
545
|
+
>
|
|
546
|
+
<UploadBlankSlate
|
|
547
|
+
onFilesSelected={onImagesSelected}
|
|
548
|
+
onPaste={() => void handlePaste()}
|
|
549
|
+
/>
|
|
550
|
+
</FileDropZone>
|
|
551
|
+
</Box>
|
|
552
|
+
) : (
|
|
553
|
+
<>
|
|
411
554
|
<Box
|
|
412
|
-
width={48}
|
|
413
|
-
height={48}
|
|
414
|
-
backgroundColor="grey12"
|
|
415
|
-
borderRadius="100%"
|
|
416
555
|
display="flex"
|
|
417
556
|
alignItems="center"
|
|
418
|
-
justifyContent="
|
|
557
|
+
justifyContent="space-between"
|
|
558
|
+
padding={16}
|
|
419
559
|
>
|
|
420
|
-
<
|
|
560
|
+
<Text variant="h3">Design</Text>
|
|
561
|
+
<FileUploadButton
|
|
562
|
+
size="medium"
|
|
563
|
+
color="grey"
|
|
564
|
+
onFilesSelected={onImagesSelected}
|
|
565
|
+
startIcon="attachFile"
|
|
566
|
+
disabled={hasTriggeredGeneration}
|
|
567
|
+
>
|
|
568
|
+
Add images
|
|
569
|
+
</FileUploadButton>
|
|
421
570
|
</Box>
|
|
422
|
-
<
|
|
423
|
-
<
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
571
|
+
<ScrollArea stableScrollbar={false}>
|
|
572
|
+
<Box
|
|
573
|
+
display="grid"
|
|
574
|
+
gridTemplateColumns="1fr 1fr"
|
|
575
|
+
gap={16}
|
|
576
|
+
padding={16}
|
|
577
|
+
>
|
|
578
|
+
{slices.map((slice, index) => (
|
|
579
|
+
<SliceCard slice={slice} key={`slice-${index}`} />
|
|
580
|
+
))}
|
|
581
|
+
</Box>
|
|
582
|
+
</ScrollArea>
|
|
583
|
+
</>
|
|
584
|
+
)}
|
|
585
|
+
<DialogActions>
|
|
586
|
+
<DialogCancelButton
|
|
587
|
+
size="medium"
|
|
588
|
+
onClick={handleClose}
|
|
589
|
+
disabled={loadingSliceCount > 0}
|
|
441
590
|
>
|
|
442
|
-
|
|
443
|
-
</
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
591
|
+
Close
|
|
592
|
+
</DialogCancelButton>
|
|
593
|
+
{completedSliceCount === 0 ? (
|
|
594
|
+
<DialogActionButton
|
|
595
|
+
color="purple"
|
|
596
|
+
startIcon="autoFixHigh"
|
|
597
|
+
onClick={() => void generateAllPendingSlices()}
|
|
598
|
+
disabled={
|
|
599
|
+
hasTriggeredGeneration ||
|
|
600
|
+
loadingSliceCount > 0 ||
|
|
601
|
+
pendingSliceCount === 0
|
|
602
|
+
}
|
|
603
|
+
loading={loadingSliceCount > 0}
|
|
604
|
+
size="medium"
|
|
605
|
+
>
|
|
606
|
+
Generate{" "}
|
|
607
|
+
{generateSliceCount > 0 ? `(${generateSliceCount}) ` : ""}
|
|
608
|
+
{generateSliceCount === 1 ? "Slice" : "Slices"}
|
|
609
|
+
</DialogActionButton>
|
|
610
|
+
) : (
|
|
611
|
+
<DialogActionButton
|
|
612
|
+
color="purple"
|
|
613
|
+
onClick={() => void onSubmit()}
|
|
614
|
+
loading={isSubmitting}
|
|
615
|
+
size="medium"
|
|
616
|
+
>
|
|
617
|
+
{getSubmitButtonLabel(location, completedSliceCount)}
|
|
618
|
+
</DialogActionButton>
|
|
619
|
+
)}
|
|
620
|
+
</DialogActions>
|
|
621
|
+
</>
|
|
463
622
|
) : (
|
|
464
|
-
<
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
<SliceCard slice={slice} key={`slice-${index}`} />
|
|
473
|
-
))}
|
|
474
|
-
</Box>
|
|
475
|
-
</ScrollArea>
|
|
623
|
+
<Box
|
|
624
|
+
display="flex"
|
|
625
|
+
justifyContent="center"
|
|
626
|
+
alignItems="center"
|
|
627
|
+
height="100%"
|
|
628
|
+
>
|
|
629
|
+
<ProgressCircle color="purple9" />
|
|
630
|
+
</Box>
|
|
476
631
|
)}
|
|
477
|
-
|
|
478
|
-
<DialogActions>
|
|
479
|
-
<DialogCancelButton disabled={areSlicesLoading}>
|
|
480
|
-
Close
|
|
481
|
-
</DialogCancelButton>
|
|
482
|
-
</DialogActions>
|
|
483
632
|
</DialogContent>
|
|
484
633
|
</Dialog>
|
|
485
634
|
);
|
|
@@ -491,6 +640,7 @@ function UploadBlankSlate(props: {
|
|
|
491
640
|
onPaste: () => void;
|
|
492
641
|
}) {
|
|
493
642
|
const { droppingFiles = false, onFilesSelected, onPaste } = props;
|
|
643
|
+
const isFigmaEnabled = useIsFigmaEnabled();
|
|
494
644
|
|
|
495
645
|
return (
|
|
496
646
|
<Box
|
|
@@ -518,30 +668,53 @@ function UploadBlankSlate(props: {
|
|
|
518
668
|
gap={4}
|
|
519
669
|
alignItems="center"
|
|
520
670
|
>
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
671
|
+
{isFigmaEnabled ? (
|
|
672
|
+
<>
|
|
673
|
+
<Text>Generate slices from your designs</Text>
|
|
674
|
+
<Text variant="small" color="grey11">
|
|
675
|
+
Upload your design images or paste them directly from Figma.
|
|
676
|
+
</Text>
|
|
677
|
+
</>
|
|
678
|
+
) : (
|
|
679
|
+
<>
|
|
680
|
+
<Text>Upload your design images.</Text>
|
|
681
|
+
<Text variant="small" color="grey11">
|
|
682
|
+
Once uploaded, you can generate slices automatically using AI.
|
|
683
|
+
</Text>
|
|
684
|
+
</>
|
|
685
|
+
)}
|
|
525
686
|
</Box>
|
|
526
687
|
<Box display="flex" alignItems="center" gap={16}>
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
688
|
+
{isFigmaEnabled ? (
|
|
689
|
+
<>
|
|
690
|
+
<Button
|
|
691
|
+
size="small"
|
|
692
|
+
renderStartIcon={() => (
|
|
693
|
+
<FigmaIcon variant="original" height={16} />
|
|
694
|
+
)}
|
|
695
|
+
color="grey"
|
|
696
|
+
onClick={onPaste}
|
|
697
|
+
>
|
|
698
|
+
Paste from Figma
|
|
699
|
+
</Button>
|
|
700
|
+
<FileUploadButton
|
|
701
|
+
size="small"
|
|
702
|
+
onFilesSelected={onFilesSelected}
|
|
703
|
+
color="purple"
|
|
704
|
+
invisible
|
|
705
|
+
>
|
|
706
|
+
Add images
|
|
707
|
+
</FileUploadButton>
|
|
708
|
+
</>
|
|
709
|
+
) : (
|
|
710
|
+
<FileUploadButton
|
|
711
|
+
startIcon="attachFile"
|
|
712
|
+
onFilesSelected={onFilesSelected}
|
|
713
|
+
color="grey"
|
|
714
|
+
>
|
|
715
|
+
Add images
|
|
716
|
+
</FileUploadButton>
|
|
717
|
+
)}
|
|
545
718
|
</Box>
|
|
546
719
|
</Box>
|
|
547
720
|
</BlankSlate>
|
|
@@ -566,13 +739,6 @@ async function getImageUrl({ image }: { image: File }) {
|
|
|
566
739
|
return url;
|
|
567
740
|
}
|
|
568
741
|
|
|
569
|
-
type NewSlice = {
|
|
570
|
-
image: File;
|
|
571
|
-
model: SharedSlice;
|
|
572
|
-
langSmithUrl?: string;
|
|
573
|
-
source: "figma" | "upload";
|
|
574
|
-
};
|
|
575
|
-
|
|
576
742
|
/**
|
|
577
743
|
* Keeps track of the existing slices in the project.
|
|
578
744
|
* Re-fetches them when the modal is opened.
|
|
@@ -644,40 +810,42 @@ function sliceWithoutConflicts({
|
|
|
644
810
|
};
|
|
645
811
|
}
|
|
646
812
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
const { libraries = [] } =
|
|
650
|
-
await managerClient.project.getSliceMachineConfig();
|
|
651
|
-
const library = libraries[0];
|
|
652
|
-
if (!library) {
|
|
653
|
-
throw new Error("No library found in the config.");
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
for (const { model } of newSlices) {
|
|
657
|
-
const { errors } = await managerClient.slices.createSlice({
|
|
658
|
-
libraryID: library,
|
|
659
|
-
model,
|
|
660
|
-
});
|
|
661
|
-
if (errors.length) {
|
|
662
|
-
throw new Error(`Failed to create slice ${model.id}.`);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
813
|
+
function useLibraryID() {
|
|
814
|
+
const [libraryID, setLibraryID] = useState<string | undefined>();
|
|
665
815
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
libraryID
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
816
|
+
useEffect(() => {
|
|
817
|
+
managerClient.project
|
|
818
|
+
.getSliceMachineConfig()
|
|
819
|
+
.then((smConfig) => {
|
|
820
|
+
const libraryID = smConfig?.libraries?.[0];
|
|
821
|
+
if (libraryID === undefined) {
|
|
822
|
+
throw new Error("No library found in the config.");
|
|
823
|
+
}
|
|
824
|
+
setLibraryID(libraryID);
|
|
825
|
+
})
|
|
826
|
+
.catch(() => {
|
|
827
|
+
throw new Error("Could not get library ID from the config.");
|
|
674
828
|
});
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
}),
|
|
680
|
-
);
|
|
829
|
+
}, []);
|
|
830
|
+
|
|
831
|
+
return { libraryID, isLoading: libraryID === undefined };
|
|
832
|
+
}
|
|
681
833
|
|
|
682
|
-
|
|
834
|
+
function useIsFigmaEnabled() {
|
|
835
|
+
const experiment = useExperimentVariant("llm-proxy-access");
|
|
836
|
+
return experiment?.value === "on";
|
|
683
837
|
}
|
|
838
|
+
|
|
839
|
+
const getSubmitButtonLabel = (
|
|
840
|
+
location: "custom_type" | "page_type" | "slices",
|
|
841
|
+
completedSliceCount: number,
|
|
842
|
+
) => {
|
|
843
|
+
switch (location) {
|
|
844
|
+
case "custom_type":
|
|
845
|
+
return `Add to type (${completedSliceCount})`;
|
|
846
|
+
case "page_type":
|
|
847
|
+
return `Add to page (${completedSliceCount})`;
|
|
848
|
+
case "slices":
|
|
849
|
+
return "Done";
|
|
850
|
+
}
|
|
851
|
+
};
|