slice-machine-ui 2.19.2-alpha.jp-figma-to-prismic.3 → 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/{8UM-Fh6SBL5e0B_msyLxt → 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-a9ca8c9f371bd423.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 +335 -267
- package/src/features/customTypes/customTypesBuilder/CreateSliceFromImageModal/SliceCard.tsx +6 -2
- 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 -2
- package/out/_next/static/chunks/20-4cb8941c8aafa019.js +0 -1
- package/out/_next/static/chunks/907-fbd4308471792876.js +0 -1
- /package/out/_next/static/{8UM-Fh6SBL5e0B_msyLxt → J2YngfgTVf7j_xF1hSI57}/_ssgManifest.js +0 -0
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
DialogHeader,
|
|
13
13
|
FileDropZone,
|
|
14
14
|
FileUploadButton,
|
|
15
|
+
ProgressCircle,
|
|
15
16
|
ScrollArea,
|
|
16
17
|
Text,
|
|
17
18
|
} from "@prismicio/editor-ui";
|
|
@@ -25,6 +26,7 @@ import { getState, telemetry } from "@/apiClient";
|
|
|
25
26
|
import { addAiFeedback } from "@/features/aiFeedback";
|
|
26
27
|
import { useOnboarding } from "@/features/onboarding/useOnboarding";
|
|
27
28
|
import { useAutoSync } from "@/features/sync/AutoSyncProvider";
|
|
29
|
+
import { useExperimentVariant } from "@/hooks/useExperimentVariant";
|
|
28
30
|
import { FigmaIcon } from "@/icons/FigmaIcon";
|
|
29
31
|
import { managerClient } from "@/managerClient";
|
|
30
32
|
import useSliceMachineActions from "@/modules/useSliceMachineActions";
|
|
@@ -42,13 +44,7 @@ const IMAGE_UPLOAD_LIMIT = 10;
|
|
|
42
44
|
interface CreateSliceFromImageModalProps {
|
|
43
45
|
open: boolean;
|
|
44
46
|
location: "custom_type" | "page_type" | "slices";
|
|
45
|
-
onSuccess: (args: {
|
|
46
|
-
slices: {
|
|
47
|
-
model: SharedSlice;
|
|
48
|
-
langSmithUrl?: string;
|
|
49
|
-
}[];
|
|
50
|
-
library: string;
|
|
51
|
-
}) => void;
|
|
47
|
+
onSuccess: (args: { slices: SharedSlice[]; library: string }) => void;
|
|
52
48
|
onClose: () => void;
|
|
53
49
|
}
|
|
54
50
|
|
|
@@ -57,16 +53,18 @@ export function CreateSliceFromImageModal(
|
|
|
57
53
|
) {
|
|
58
54
|
const { open, location, onClose, onSuccess } = props;
|
|
59
55
|
const [slices, setSlices] = useState<Slice[]>([]);
|
|
56
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
60
57
|
const { syncChanges } = useAutoSync();
|
|
61
58
|
const { createSliceSuccess } = useSliceMachineActions();
|
|
62
59
|
const { completeStep } = useOnboarding();
|
|
63
60
|
const existingSlices = useExistingSlices({ open });
|
|
64
|
-
|
|
61
|
+
const { libraryID, isLoading: isLoadingLibraryID } = useLibraryID();
|
|
65
62
|
/**
|
|
66
63
|
* Keeps track of the current instance id.
|
|
67
64
|
* When the modal is closed, the id is reset.
|
|
68
65
|
*/
|
|
69
66
|
const id = useRef(crypto.randomUUID());
|
|
67
|
+
const isFigmaEnabled = useIsFigmaEnabled();
|
|
70
68
|
|
|
71
69
|
useHotkeys(
|
|
72
70
|
["meta+v", "ctrl+v"],
|
|
@@ -74,15 +72,9 @@ export function CreateSliceFromImageModal(
|
|
|
74
72
|
event.preventDefault();
|
|
75
73
|
void handlePaste();
|
|
76
74
|
},
|
|
77
|
-
{ enabled: open },
|
|
75
|
+
{ enabled: open && isFigmaEnabled },
|
|
78
76
|
);
|
|
79
77
|
|
|
80
|
-
useEffect(() => {
|
|
81
|
-
if (slices.every((slice) => slice.status === "success")) {
|
|
82
|
-
void onAllComplete();
|
|
83
|
-
}
|
|
84
|
-
}, [slices]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
85
|
-
|
|
86
78
|
const setSlice = (args: {
|
|
87
79
|
index: number;
|
|
88
80
|
slice: (prevSlice: Slice) => Slice;
|
|
@@ -99,6 +91,8 @@ export function CreateSliceFromImageModal(
|
|
|
99
91
|
};
|
|
100
92
|
|
|
101
93
|
const onImagesSelected = (images: File[]) => {
|
|
94
|
+
if (hasTriggeredGeneration) return;
|
|
95
|
+
|
|
102
96
|
if (images.length > IMAGE_UPLOAD_LIMIT) {
|
|
103
97
|
toast.error(
|
|
104
98
|
`You can only upload ${IMAGE_UPLOAD_LIMIT} images at a time.`,
|
|
@@ -111,8 +105,8 @@ export function CreateSliceFromImageModal(
|
|
|
111
105
|
...prevSlices,
|
|
112
106
|
...images.map(
|
|
113
107
|
(image): Slice => ({
|
|
114
|
-
status: "uploading",
|
|
115
108
|
source: "upload",
|
|
109
|
+
status: "uploading",
|
|
116
110
|
image,
|
|
117
111
|
}),
|
|
118
112
|
),
|
|
@@ -125,7 +119,14 @@ export function CreateSliceFromImageModal(
|
|
|
125
119
|
};
|
|
126
120
|
|
|
127
121
|
const handlePaste = async () => {
|
|
128
|
-
if (
|
|
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
|
+
}
|
|
129
130
|
|
|
130
131
|
// Don't allow pasting while uploads or generation are in progress
|
|
131
132
|
const isLoading = slices.some(
|
|
@@ -282,20 +283,16 @@ export function CreateSliceFromImageModal(
|
|
|
282
283
|
}
|
|
283
284
|
};
|
|
284
285
|
|
|
285
|
-
const generateAllPendingSlices =
|
|
286
|
-
|
|
287
|
-
const libraryID = smConfig?.libraries?.[0];
|
|
288
|
-
if (libraryID === undefined) {
|
|
289
|
-
throw new Error("No library found in the config.");
|
|
290
|
-
}
|
|
286
|
+
const generateAllPendingSlices = () => {
|
|
287
|
+
if (libraryID === undefined) return;
|
|
291
288
|
|
|
292
289
|
// Generate all pending slices simultaneously
|
|
293
290
|
slices.forEach((slice, index) => {
|
|
294
291
|
if (slice.status === "pending") {
|
|
295
292
|
void inferSlice({
|
|
296
293
|
index,
|
|
297
|
-
imageUrl: slice.thumbnailUrl,
|
|
298
294
|
libraryID,
|
|
295
|
+
imageUrl: slice.thumbnailUrl,
|
|
299
296
|
source: slice.source,
|
|
300
297
|
});
|
|
301
298
|
}
|
|
@@ -309,7 +306,7 @@ export function CreateSliceFromImageModal(
|
|
|
309
306
|
source: "figma" | "upload";
|
|
310
307
|
}) => {
|
|
311
308
|
const { index, imageUrl, libraryID, source } = args;
|
|
312
|
-
|
|
309
|
+
let currentId = id.current;
|
|
313
310
|
|
|
314
311
|
setSlice({
|
|
315
312
|
index,
|
|
@@ -329,6 +326,13 @@ export function CreateSliceFromImageModal(
|
|
|
329
326
|
|
|
330
327
|
if (currentId !== id.current) return;
|
|
331
328
|
|
|
329
|
+
const resolvedModel = sliceWithoutConflicts({
|
|
330
|
+
existingSlices: existingSlices.current,
|
|
331
|
+
newSlices: slices,
|
|
332
|
+
slice: inferResult.slice,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Update slice state with success
|
|
332
336
|
setSlices((prevSlices) => {
|
|
333
337
|
return prevSlices.map((prevSlice, i) => {
|
|
334
338
|
if (i !== index) return prevSlice;
|
|
@@ -336,16 +340,57 @@ export function CreateSliceFromImageModal(
|
|
|
336
340
|
...prevSlice,
|
|
337
341
|
status: "success",
|
|
338
342
|
thumbnailUrl: imageUrl,
|
|
339
|
-
model:
|
|
340
|
-
|
|
341
|
-
newSlices: slices,
|
|
342
|
-
slice: inferResult.slice,
|
|
343
|
-
}),
|
|
343
|
+
model: resolvedModel,
|
|
344
|
+
langSmithUrl: inferResult.langSmithUrl,
|
|
344
345
|
};
|
|
345
346
|
});
|
|
346
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
|
+
});
|
|
347
391
|
} catch {
|
|
348
392
|
if (currentId !== id.current) return;
|
|
393
|
+
|
|
349
394
|
setSlice({
|
|
350
395
|
index,
|
|
351
396
|
slice: (prevSlice) => ({
|
|
@@ -360,75 +405,38 @@ export function CreateSliceFromImageModal(
|
|
|
360
405
|
}
|
|
361
406
|
};
|
|
362
407
|
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (slice.source === "upload") {
|
|
368
|
-
acc.upload.push(slice);
|
|
369
|
-
} else {
|
|
370
|
-
acc.figma.push(slice);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
return acc;
|
|
374
|
-
},
|
|
375
|
-
{ upload: [], figma: [] },
|
|
376
|
-
);
|
|
408
|
+
const resetState = () => {
|
|
409
|
+
id.current = crypto.randomUUID();
|
|
410
|
+
setSlices([]);
|
|
411
|
+
};
|
|
377
412
|
|
|
378
|
-
|
|
413
|
+
const handleClose = () => {
|
|
414
|
+
resetState();
|
|
415
|
+
onClose();
|
|
416
|
+
};
|
|
379
417
|
|
|
380
|
-
|
|
418
|
+
const onSubmit = async () => {
|
|
381
419
|
try {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if (currentId !== id.current) return;
|
|
420
|
+
setIsSubmitting(true);
|
|
421
|
+
if (libraryID === undefined) return;
|
|
385
422
|
|
|
386
423
|
const serverState = await getState();
|
|
387
424
|
createSliceSuccess(serverState.libraries);
|
|
388
425
|
syncChanges();
|
|
389
426
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
id.current = crypto.randomUUID();
|
|
397
|
-
setSlices([]);
|
|
398
|
-
|
|
399
|
-
void completeStep("createSlice");
|
|
400
|
-
|
|
401
|
-
for (const { model, langSmithUrl } of slices) {
|
|
402
|
-
void telemetry.track({
|
|
403
|
-
event: "slice:created",
|
|
404
|
-
id: model.id,
|
|
405
|
-
name: model.name,
|
|
406
|
-
library,
|
|
407
|
-
location,
|
|
408
|
-
mode: "ai",
|
|
409
|
-
langSmithUrl,
|
|
410
|
-
});
|
|
427
|
+
onSuccess({
|
|
428
|
+
slices: slices.flatMap((slice) =>
|
|
429
|
+
slice.status === "success" ? slice.model : [],
|
|
430
|
+
),
|
|
431
|
+
library: libraryID,
|
|
432
|
+
});
|
|
411
433
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
sliceId: model.id,
|
|
416
|
-
variationId: model.variations[0].id,
|
|
417
|
-
langSmithUrl,
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
} catch {
|
|
421
|
-
if (currentId !== id.current) return;
|
|
422
|
-
toast.error("An unexpected error happened while adding slices.");
|
|
434
|
+
resetState();
|
|
435
|
+
} finally {
|
|
436
|
+
setIsSubmitting(false);
|
|
423
437
|
}
|
|
424
438
|
};
|
|
425
439
|
|
|
426
|
-
const handleClose = () => {
|
|
427
|
-
id.current = crypto.randomUUID();
|
|
428
|
-
setSlices([]);
|
|
429
|
-
onClose();
|
|
430
|
-
};
|
|
431
|
-
|
|
432
440
|
const loadingSliceCount = slices.filter((slice) => {
|
|
433
441
|
return slice.status === "uploading" || slice.status === "generating";
|
|
434
442
|
}).length;
|
|
@@ -441,8 +449,18 @@ export function CreateSliceFromImageModal(
|
|
|
441
449
|
return slice.status === "generating" || slice.status === "success";
|
|
442
450
|
});
|
|
443
451
|
|
|
452
|
+
const completedSliceCount = slices.filter((slice) => {
|
|
453
|
+
return slice.status === "success";
|
|
454
|
+
}).length;
|
|
455
|
+
|
|
444
456
|
const generateSliceCount = loadingSliceCount + pendingSliceCount;
|
|
445
457
|
|
|
458
|
+
console.log({
|
|
459
|
+
slices,
|
|
460
|
+
generateSliceCount,
|
|
461
|
+
loadingSliceCount,
|
|
462
|
+
pendingSliceCount,
|
|
463
|
+
});
|
|
446
464
|
return (
|
|
447
465
|
<Dialog
|
|
448
466
|
open={open}
|
|
@@ -453,133 +471,164 @@ export function CreateSliceFromImageModal(
|
|
|
453
471
|
<DialogDescription hidden>
|
|
454
472
|
Upload images to generate slices with AI
|
|
455
473
|
</DialogDescription>
|
|
456
|
-
{
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
+
<>
|
|
473
554
|
<Box
|
|
474
|
-
width={48}
|
|
475
|
-
height={48}
|
|
476
|
-
backgroundColor="grey12"
|
|
477
|
-
borderRadius="100%"
|
|
478
555
|
display="flex"
|
|
479
556
|
alignItems="center"
|
|
480
|
-
justifyContent="
|
|
557
|
+
justifyContent="space-between"
|
|
558
|
+
padding={16}
|
|
481
559
|
>
|
|
482
|
-
<
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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>
|
|
490
570
|
</Box>
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
<FileDropZone
|
|
508
|
-
onFilesSelected={onImagesSelected}
|
|
509
|
-
assetType="image"
|
|
510
|
-
maxFiles={IMAGE_UPLOAD_LIMIT}
|
|
511
|
-
overlay={
|
|
512
|
-
<UploadBlankSlate
|
|
513
|
-
onFilesSelected={onImagesSelected}
|
|
514
|
-
onPaste={() => void handlePaste()}
|
|
515
|
-
droppingFiles
|
|
516
|
-
/>
|
|
517
|
-
}
|
|
518
|
-
>
|
|
519
|
-
<UploadBlankSlate
|
|
520
|
-
onFilesSelected={onImagesSelected}
|
|
521
|
-
onPaste={() => void handlePaste()}
|
|
522
|
-
/>
|
|
523
|
-
</FileDropZone>
|
|
524
|
-
</Box>
|
|
525
|
-
) : (
|
|
526
|
-
<>
|
|
527
|
-
<Box
|
|
528
|
-
display="flex"
|
|
529
|
-
alignItems="center"
|
|
530
|
-
justifyContent="space-between"
|
|
531
|
-
padding={16}
|
|
532
|
-
>
|
|
533
|
-
<Text variant="h3">Design</Text>
|
|
534
|
-
<FileUploadButton
|
|
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
|
|
535
587
|
size="medium"
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
startIcon="attachFile"
|
|
539
|
-
disabled={hasTriggeredGeneration}
|
|
588
|
+
onClick={handleClose}
|
|
589
|
+
disabled={loadingSliceCount > 0}
|
|
540
590
|
>
|
|
541
|
-
|
|
542
|
-
</
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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>
|
|
556
621
|
</>
|
|
557
|
-
)
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
disabled={loadingSliceCount > 0}
|
|
564
|
-
>
|
|
565
|
-
Close
|
|
566
|
-
</DialogCancelButton>
|
|
567
|
-
<DialogActionButton
|
|
568
|
-
color="purple"
|
|
569
|
-
startIcon="autoFixHigh"
|
|
570
|
-
onClick={() => void generateAllPendingSlices()}
|
|
571
|
-
disabled={
|
|
572
|
-
hasTriggeredGeneration ||
|
|
573
|
-
loadingSliceCount > 0 ||
|
|
574
|
-
pendingSliceCount === 0
|
|
575
|
-
}
|
|
576
|
-
loading={hasTriggeredGeneration}
|
|
577
|
-
size="medium"
|
|
622
|
+
) : (
|
|
623
|
+
<Box
|
|
624
|
+
display="flex"
|
|
625
|
+
justifyContent="center"
|
|
626
|
+
alignItems="center"
|
|
627
|
+
height="100%"
|
|
578
628
|
>
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
</DialogActions>
|
|
629
|
+
<ProgressCircle color="purple9" />
|
|
630
|
+
</Box>
|
|
631
|
+
)}
|
|
583
632
|
</DialogContent>
|
|
584
633
|
</Dialog>
|
|
585
634
|
);
|
|
@@ -591,6 +640,7 @@ function UploadBlankSlate(props: {
|
|
|
591
640
|
onPaste: () => void;
|
|
592
641
|
}) {
|
|
593
642
|
const { droppingFiles = false, onFilesSelected, onPaste } = props;
|
|
643
|
+
const isFigmaEnabled = useIsFigmaEnabled();
|
|
594
644
|
|
|
595
645
|
return (
|
|
596
646
|
<Box
|
|
@@ -618,30 +668,53 @@ function UploadBlankSlate(props: {
|
|
|
618
668
|
gap={4}
|
|
619
669
|
alignItems="center"
|
|
620
670
|
>
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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
|
+
)}
|
|
625
686
|
</Box>
|
|
626
687
|
<Box display="flex" alignItems="center" gap={16}>
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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
|
+
)}
|
|
645
718
|
</Box>
|
|
646
719
|
</Box>
|
|
647
720
|
</BlankSlate>
|
|
@@ -666,13 +739,6 @@ async function getImageUrl({ image }: { image: File }) {
|
|
|
666
739
|
return url;
|
|
667
740
|
}
|
|
668
741
|
|
|
669
|
-
type NewSlice = {
|
|
670
|
-
image: File;
|
|
671
|
-
model: SharedSlice;
|
|
672
|
-
langSmithUrl?: string;
|
|
673
|
-
source: "figma" | "upload";
|
|
674
|
-
};
|
|
675
|
-
|
|
676
742
|
/**
|
|
677
743
|
* Keeps track of the existing slices in the project.
|
|
678
744
|
* Re-fetches them when the modal is opened.
|
|
@@ -744,40 +810,42 @@ function sliceWithoutConflicts({
|
|
|
744
810
|
};
|
|
745
811
|
}
|
|
746
812
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
const { libraries = [] } =
|
|
750
|
-
await managerClient.project.getSliceMachineConfig();
|
|
751
|
-
const library = libraries[0];
|
|
752
|
-
if (!library) {
|
|
753
|
-
throw new Error("No library found in the config.");
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
for (const { model } of newSlices) {
|
|
757
|
-
const { errors } = await managerClient.slices.createSlice({
|
|
758
|
-
libraryID: library,
|
|
759
|
-
model,
|
|
760
|
-
});
|
|
761
|
-
if (errors.length) {
|
|
762
|
-
throw new Error(`Failed to create slice ${model.id}.`);
|
|
763
|
-
}
|
|
764
|
-
}
|
|
813
|
+
function useLibraryID() {
|
|
814
|
+
const [libraryID, setLibraryID] = useState<string | undefined>();
|
|
765
815
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
libraryID
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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.");
|
|
774
828
|
});
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
}),
|
|
780
|
-
);
|
|
829
|
+
}, []);
|
|
830
|
+
|
|
831
|
+
return { libraryID, isLoading: libraryID === undefined };
|
|
832
|
+
}
|
|
781
833
|
|
|
782
|
-
|
|
834
|
+
function useIsFigmaEnabled() {
|
|
835
|
+
const experiment = useExperimentVariant("llm-proxy-access");
|
|
836
|
+
return experiment?.value === "on";
|
|
783
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
|
+
};
|