slice-machine-ui 2.19.2-beta.2 → 2.19.2-beta.3
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/chunks/{489-ce3053e1d81ade83.js → 489-32281540712d98bb.js} +1 -1
- package/out/_next/static/chunks/593-7ffd1197c3405ef8.js +1 -0
- package/out/_next/static/chunks/633-275b9968b5aaa920.js +1 -0
- package/out/_next/static/chunks/882-48d61b2fabee28d8.js +1 -0
- package/out/_next/static/chunks/pages/{_app-83c4f5b209504941.js → _app-9cc2da8ff60c3087.js} +1 -1
- package/out/_next/static/chunks/pages/{changes-7f24b37f5bf872ae.js → changes-e66094f57453cf9c.js} +1 -1
- package/out/_next/static/chunks/pages/custom-types/{[customTypeId]-1b47424a37b49dff.js → [customTypeId]-6d613b67e6967ae5.js} +1 -1
- package/out/_next/static/chunks/pages/page-types/{[pageTypeId]-385f933c203e8b16.js → [pageTypeId]-40207b66190e3fcd.js} +1 -1
- package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/{[variation]-08cdeefc96106c0c.js → [variation]-22ffa2561ac66557.js} +1 -1
- package/out/_next/static/chunks/pages/{slices-bedcb854fbdca8cd.js → slices-a9b4d6d022cfcd88.js} +1 -1
- package/out/_next/static/uC4K1_hgf7dK4FL02cbUH/_buildManifest.js +1 -0
- 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 +428 -240
- package/src/features/customTypes/customTypesBuilder/CreateSliceFromImageModal/SliceCard.tsx +49 -12
- package/src/icons/FigmaIcon.tsx +33 -34
- package/src/icons/FigmaIconSquare.tsx +45 -0
- package/src/legacy/components/ScreenshotChangesModal/index.tsx +2 -2
- package/src/legacy/lib/builders/CustomTypeBuilder/SliceZone/index.tsx +1 -1
- package/out/_next/static/4ZbwQOH1s2JwSCJTqy_83/_buildManifest.js +0 -1
- package/out/_next/static/chunks/34-28725deef8b874b1.js +0 -1
- package/out/_next/static/chunks/658-8231c0b729e0124a.js +0 -1
- package/out/_next/static/chunks/907-bf4215e6fc238ea0.js +0 -1
- /package/out/_next/static/{4ZbwQOH1s2JwSCJTqy_83 → uC4K1_hgf7dK4FL02cbUH}/_ssgManifest.js +0 -0
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BlankSlate,
|
|
3
|
-
BlankSlateActions,
|
|
4
|
-
BlankSlateDescription,
|
|
5
3
|
BlankSlateIcon,
|
|
6
|
-
BlankSlateTitle,
|
|
7
4
|
Box,
|
|
5
|
+
Button,
|
|
8
6
|
Dialog,
|
|
9
7
|
DialogActionButton,
|
|
10
8
|
DialogActions,
|
|
@@ -14,7 +12,9 @@ import {
|
|
|
14
12
|
DialogHeader,
|
|
15
13
|
FileDropZone,
|
|
16
14
|
FileUploadButton,
|
|
15
|
+
ProgressCircle,
|
|
17
16
|
ScrollArea,
|
|
17
|
+
Text,
|
|
18
18
|
} from "@prismicio/editor-ui";
|
|
19
19
|
import { SharedSlice } from "@prismicio/types-internal/lib/customtypes";
|
|
20
20
|
import { useEffect, useRef, useState } from "react";
|
|
@@ -27,6 +27,7 @@ import { addAiFeedback } from "@/features/aiFeedback";
|
|
|
27
27
|
import { useOnboarding } from "@/features/onboarding/useOnboarding";
|
|
28
28
|
import { useAutoSync } from "@/features/sync/AutoSyncProvider";
|
|
29
29
|
import { useExperimentVariant } from "@/hooks/useExperimentVariant";
|
|
30
|
+
import { FigmaIcon } from "@/icons/FigmaIcon";
|
|
30
31
|
import { managerClient } from "@/managerClient";
|
|
31
32
|
import useSliceMachineActions from "@/modules/useSliceMachineActions";
|
|
32
33
|
|
|
@@ -37,13 +38,7 @@ const IMAGE_UPLOAD_LIMIT = 10;
|
|
|
37
38
|
interface CreateSliceFromImageModalProps {
|
|
38
39
|
open: boolean;
|
|
39
40
|
location: "custom_type" | "page_type" | "slices";
|
|
40
|
-
onSuccess: (args: {
|
|
41
|
-
slices: {
|
|
42
|
-
model: SharedSlice;
|
|
43
|
-
langSmithUrl?: string;
|
|
44
|
-
}[];
|
|
45
|
-
library: string;
|
|
46
|
-
}) => void;
|
|
41
|
+
onSuccess: (args: { slices: SharedSlice[]; library: string }) => void;
|
|
47
42
|
onClose: () => void;
|
|
48
43
|
}
|
|
49
44
|
|
|
@@ -58,11 +53,14 @@ export function CreateSliceFromImageModal(
|
|
|
58
53
|
) {
|
|
59
54
|
const { open, location, onSuccess, onClose } = props;
|
|
60
55
|
const [slices, setSlices] = useState<Slice[]>([]);
|
|
61
|
-
const [
|
|
56
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
57
|
+
|
|
62
58
|
const { syncChanges } = useAutoSync();
|
|
63
59
|
const { createSliceSuccess } = useSliceMachineActions();
|
|
64
60
|
const { completeStep } = useOnboarding();
|
|
61
|
+
const existingSlices = useExistingSlices({ open });
|
|
65
62
|
const isFigmaEnabled = useIsFigmaEnabled();
|
|
63
|
+
const { libraryID, isLoading: isLoadingLibraryID } = useLibraryID();
|
|
66
64
|
|
|
67
65
|
/**
|
|
68
66
|
* Keeps track of the current instance id.
|
|
@@ -87,14 +85,9 @@ export function CreateSliceFromImageModal(
|
|
|
87
85
|
setSlices((slices) => slices.map((s, i) => (i === index ? slice(s) : s)));
|
|
88
86
|
};
|
|
89
87
|
|
|
90
|
-
const onOpenChange = (open: boolean) => {
|
|
91
|
-
if (open || isCreatingSlices) return;
|
|
92
|
-
onClose();
|
|
93
|
-
id.current = crypto.randomUUID();
|
|
94
|
-
setSlices([]);
|
|
95
|
-
};
|
|
96
|
-
|
|
97
88
|
const onImagesSelected = (images: File[]) => {
|
|
89
|
+
if (hasTriggeredGeneration) return;
|
|
90
|
+
|
|
98
91
|
if (images.length > IMAGE_UPLOAD_LIMIT) {
|
|
99
92
|
toast.error(
|
|
100
93
|
`You can only upload ${IMAGE_UPLOAD_LIMIT} images at a time.`,
|
|
@@ -102,23 +95,28 @@ export function CreateSliceFromImageModal(
|
|
|
102
95
|
return;
|
|
103
96
|
}
|
|
104
97
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
image
|
|
110
|
-
|
|
111
|
-
|
|
98
|
+
const startIndex = slices.length;
|
|
99
|
+
setSlices((prevSlices) => [
|
|
100
|
+
...prevSlices,
|
|
101
|
+
...images.map(
|
|
102
|
+
(image): Slice => ({
|
|
103
|
+
source: "upload",
|
|
104
|
+
status: "uploading",
|
|
105
|
+
image,
|
|
106
|
+
}),
|
|
107
|
+
),
|
|
108
|
+
]);
|
|
112
109
|
|
|
113
|
-
images.forEach((image,
|
|
114
|
-
|
|
115
|
-
|
|
110
|
+
images.forEach((image, relativeIndex) => {
|
|
111
|
+
const index = startIndex + relativeIndex;
|
|
112
|
+
void uploadImage({ index, image, source: "upload" });
|
|
113
|
+
});
|
|
116
114
|
};
|
|
117
115
|
|
|
118
|
-
const uploadImage = (args: {
|
|
116
|
+
const uploadImage = async (args: {
|
|
119
117
|
index: number;
|
|
120
118
|
image: File;
|
|
121
|
-
source: "
|
|
119
|
+
source: "figma" | "upload";
|
|
122
120
|
}) => {
|
|
123
121
|
const { index, image, source } = args;
|
|
124
122
|
const currentId = id.current;
|
|
@@ -128,40 +126,45 @@ export function CreateSliceFromImageModal(
|
|
|
128
126
|
slice: (prevSlice) => ({
|
|
129
127
|
...prevSlice,
|
|
130
128
|
status: "uploading",
|
|
129
|
+
image,
|
|
131
130
|
source,
|
|
132
131
|
}),
|
|
133
132
|
});
|
|
134
133
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
void inferSlice({ index, imageUrl, source });
|
|
139
|
-
},
|
|
140
|
-
() => {
|
|
141
|
-
if (currentId !== id.current) return;
|
|
142
|
-
setSlice({
|
|
143
|
-
index,
|
|
144
|
-
slice: (prevSlice) => ({
|
|
145
|
-
...prevSlice,
|
|
146
|
-
status: "uploadError",
|
|
147
|
-
onRetry: () => uploadImage({ index, image, source }),
|
|
148
|
-
}),
|
|
149
|
-
});
|
|
150
|
-
},
|
|
151
|
-
);
|
|
152
|
-
};
|
|
134
|
+
try {
|
|
135
|
+
const imageUrl = await getImageUrl({ image });
|
|
136
|
+
if (currentId !== id.current) return;
|
|
153
137
|
|
|
154
|
-
|
|
138
|
+
setSlice({
|
|
139
|
+
index,
|
|
140
|
+
slice: (prevSlice) => ({
|
|
141
|
+
...prevSlice,
|
|
142
|
+
status: "pending",
|
|
143
|
+
thumbnailUrl: imageUrl,
|
|
144
|
+
}),
|
|
145
|
+
});
|
|
146
|
+
} catch {
|
|
147
|
+
if (currentId !== id.current) return;
|
|
148
|
+
setSlice({
|
|
149
|
+
index,
|
|
150
|
+
slice: (prevSlice) => ({
|
|
151
|
+
...prevSlice,
|
|
152
|
+
status: "uploadError",
|
|
153
|
+
onRetry: () => void uploadImage({ index, image, source }),
|
|
154
|
+
}),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
};
|
|
155
158
|
|
|
156
159
|
const inferSlice = async (args: {
|
|
157
160
|
index: number;
|
|
158
161
|
imageUrl: string;
|
|
159
|
-
source: "
|
|
162
|
+
source: "figma" | "upload";
|
|
160
163
|
}) => {
|
|
161
|
-
|
|
162
|
-
const currentId = id.current;
|
|
164
|
+
if (libraryID === undefined) return;
|
|
163
165
|
|
|
164
|
-
const
|
|
166
|
+
const { index, imageUrl, source } = args;
|
|
167
|
+
let currentId = id.current;
|
|
165
168
|
|
|
166
169
|
setSlice({
|
|
167
170
|
index,
|
|
@@ -174,10 +177,11 @@ export function CreateSliceFromImageModal(
|
|
|
174
177
|
|
|
175
178
|
try {
|
|
176
179
|
const inferResult = await managerClient.customTypes.inferSlice({
|
|
177
|
-
imageUrl,
|
|
178
180
|
source,
|
|
179
181
|
libraryID,
|
|
182
|
+
imageUrl,
|
|
180
183
|
});
|
|
184
|
+
|
|
181
185
|
if (currentId !== id.current) return;
|
|
182
186
|
|
|
183
187
|
const model = sliceWithoutConflicts({
|
|
@@ -186,85 +190,143 @@ export function CreateSliceFromImageModal(
|
|
|
186
190
|
slice: inferResult.slice,
|
|
187
191
|
});
|
|
188
192
|
|
|
189
|
-
setSlices((prevSlices) =>
|
|
190
|
-
prevSlices.map((prevSlice, i) =>
|
|
191
|
-
i
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
193
|
+
setSlices((prevSlices) => {
|
|
194
|
+
return prevSlices.map((prevSlice, i) => {
|
|
195
|
+
if (i !== index) return prevSlice;
|
|
196
|
+
return {
|
|
197
|
+
...prevSlice,
|
|
198
|
+
status: "success",
|
|
199
|
+
thumbnailUrl: imageUrl,
|
|
200
|
+
model,
|
|
201
|
+
langSmithUrl: inferResult.langSmithUrl,
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
if (source === "upload") {
|
|
207
|
+
currentId = id.current;
|
|
208
|
+
const currentSlice = slices[index];
|
|
209
|
+
|
|
210
|
+
const { errors } = await managerClient.slices.createSlice({
|
|
211
|
+
libraryID,
|
|
212
|
+
model: model,
|
|
213
|
+
});
|
|
214
|
+
if (errors.length) {
|
|
215
|
+
throw new Error(`Failed to create slice ${model.id}.`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
await managerClient.slices.updateSliceScreenshot({
|
|
219
|
+
libraryID,
|
|
220
|
+
sliceID: model.id,
|
|
221
|
+
variationID: model.variations[0].id,
|
|
222
|
+
data: currentSlice.image,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
if (currentId !== id.current) return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
void completeStep("createSlice");
|
|
229
|
+
|
|
230
|
+
void telemetry.track({
|
|
231
|
+
event: "slice:created",
|
|
232
|
+
id: model.id,
|
|
233
|
+
name: model.name,
|
|
234
|
+
library: libraryID,
|
|
235
|
+
location,
|
|
236
|
+
mode: "ai",
|
|
237
|
+
langSmithUrl: inferResult.langSmithUrl,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
addAiFeedback({
|
|
241
|
+
type: "model",
|
|
242
|
+
library: libraryID,
|
|
243
|
+
sliceId: model.id,
|
|
244
|
+
variationId: model.variations[0].id,
|
|
245
|
+
langSmithUrl: inferResult.langSmithUrl,
|
|
246
|
+
});
|
|
247
|
+
} catch (error) {
|
|
203
248
|
if (currentId !== id.current) return;
|
|
249
|
+
|
|
204
250
|
setSlice({
|
|
205
251
|
index,
|
|
206
252
|
slice: (prevSlice) => ({
|
|
207
253
|
...prevSlice,
|
|
208
254
|
status: "generateError",
|
|
209
255
|
thumbnailUrl: imageUrl,
|
|
210
|
-
onRetry: () =>
|
|
256
|
+
onRetry: () => {
|
|
257
|
+
void inferSlice({ index, imageUrl, source });
|
|
258
|
+
},
|
|
211
259
|
}),
|
|
212
260
|
});
|
|
213
261
|
}
|
|
214
262
|
};
|
|
215
263
|
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
264
|
+
const generatePendingSlices = () => {
|
|
265
|
+
if (libraryID === undefined) return;
|
|
266
|
+
|
|
267
|
+
slices.forEach((slice, index) => {
|
|
268
|
+
if (slice.status === "pending") {
|
|
269
|
+
void inferSlice({
|
|
270
|
+
index,
|
|
271
|
+
imageUrl: slice.thumbnailUrl,
|
|
272
|
+
source: slice.source,
|
|
273
|
+
});
|
|
220
274
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (!newSlices.length) return;
|
|
275
|
+
});
|
|
276
|
+
};
|
|
224
277
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
.then(async ({ slices, library }) => {
|
|
229
|
-
if (currentId !== id.current) return;
|
|
278
|
+
const generatingSliceCount = slices.filter((slice) => {
|
|
279
|
+
return slice.status === "generating";
|
|
280
|
+
}).length;
|
|
230
281
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
282
|
+
const uploadingSliceCount = slices.filter((slice) => {
|
|
283
|
+
return slice.status === "uploading";
|
|
284
|
+
}).length;
|
|
285
|
+
|
|
286
|
+
const loadingSliceCount = generatingSliceCount + uploadingSliceCount;
|
|
287
|
+
|
|
288
|
+
const pendingSliceCount = slices.filter((slice) => {
|
|
289
|
+
return slice.status === "pending";
|
|
290
|
+
}).length;
|
|
291
|
+
|
|
292
|
+
const completedSliceCount = slices.filter((slice) => {
|
|
293
|
+
return slice.status === "success";
|
|
294
|
+
}).length;
|
|
295
|
+
|
|
296
|
+
const hasTriggeredGeneration = slices.some((slice) => {
|
|
297
|
+
return slice.status === "generating" || slice.status === "success";
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const generateSliceCount = loadingSliceCount + pendingSliceCount;
|
|
301
|
+
|
|
302
|
+
const closeModal = () => {
|
|
303
|
+
if (loadingSliceCount > 0) return;
|
|
304
|
+
onClose();
|
|
305
|
+
id.current = crypto.randomUUID();
|
|
306
|
+
setTimeout(() => setSlices([]), 250); // wait for the modal fade animation
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const onSubmit = async () => {
|
|
310
|
+
if (libraryID === undefined) return;
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
setIsSubmitting(true);
|
|
314
|
+
|
|
315
|
+
const serverState = await getState();
|
|
316
|
+
createSliceSuccess(serverState.libraries);
|
|
317
|
+
syncChanges();
|
|
318
|
+
|
|
319
|
+
onSuccess({
|
|
320
|
+
slices: slices.flatMap((slice) =>
|
|
321
|
+
slice.status === "success" ? slice.model : [],
|
|
322
|
+
),
|
|
323
|
+
library: libraryID,
|
|
267
324
|
});
|
|
325
|
+
|
|
326
|
+
closeModal();
|
|
327
|
+
} finally {
|
|
328
|
+
setIsSubmitting(false);
|
|
329
|
+
}
|
|
268
330
|
};
|
|
269
331
|
|
|
270
332
|
const handlePaste = async () => {
|
|
@@ -362,7 +424,7 @@ export function CreateSliceFromImageModal(
|
|
|
362
424
|
}
|
|
363
425
|
|
|
364
426
|
// Create File object from blob and append to existing slices
|
|
365
|
-
const
|
|
427
|
+
const image = new File([imageBlob], imageName, {
|
|
366
428
|
type: imageBlob.type,
|
|
367
429
|
});
|
|
368
430
|
const newIndex = currentSliceCount;
|
|
@@ -373,12 +435,12 @@ export function CreateSliceFromImageModal(
|
|
|
373
435
|
{
|
|
374
436
|
source: "figma",
|
|
375
437
|
status: "uploading",
|
|
376
|
-
image
|
|
438
|
+
image,
|
|
377
439
|
},
|
|
378
440
|
]);
|
|
379
441
|
|
|
380
442
|
// Start uploading the new image
|
|
381
|
-
void uploadImage({ index: newIndex, image
|
|
443
|
+
void uploadImage({ index: newIndex, image, source: "figma" });
|
|
382
444
|
|
|
383
445
|
toast.success(`Pasted ${imageName}${success ? " from Figma" : ""}`);
|
|
384
446
|
} catch (error) {
|
|
@@ -389,60 +451,173 @@ export function CreateSliceFromImageModal(
|
|
|
389
451
|
}
|
|
390
452
|
};
|
|
391
453
|
|
|
392
|
-
const areSlicesLoading = slices.some(
|
|
393
|
-
(slice) => slice.status === "uploading" || slice.status === "generating",
|
|
394
|
-
);
|
|
395
|
-
const readySlices = slices.filter((slice) => slice.status === "success");
|
|
396
|
-
const someSlicesReady = readySlices.length > 0;
|
|
397
|
-
|
|
398
454
|
return (
|
|
399
|
-
<Dialog open={open} onOpenChange={
|
|
400
|
-
<DialogHeader title="Generate
|
|
455
|
+
<Dialog open={open} onOpenChange={(open) => !open && closeModal()}>
|
|
456
|
+
<DialogHeader title="Generate with AI" />
|
|
401
457
|
<DialogContent gap={0}>
|
|
402
458
|
<DialogDescription hidden>
|
|
403
459
|
Upload images to generate slices with AI
|
|
404
460
|
</DialogDescription>
|
|
405
|
-
{
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
461
|
+
{!isLoadingLibraryID ? (
|
|
462
|
+
<>
|
|
463
|
+
{slices.length === 0 ? (
|
|
464
|
+
<Box
|
|
465
|
+
padding={16}
|
|
466
|
+
height="100%"
|
|
467
|
+
gap={16}
|
|
468
|
+
display="flex"
|
|
469
|
+
flexDirection="column"
|
|
470
|
+
>
|
|
471
|
+
{isFigmaEnabled && (
|
|
472
|
+
<Box
|
|
473
|
+
display="flex"
|
|
474
|
+
gap={16}
|
|
475
|
+
alignItems="center"
|
|
476
|
+
backgroundColor="grey2"
|
|
477
|
+
padding={16}
|
|
478
|
+
borderRadius={12}
|
|
479
|
+
>
|
|
480
|
+
<Box
|
|
481
|
+
display="flex"
|
|
482
|
+
gap={8}
|
|
483
|
+
alignItems="center"
|
|
484
|
+
flexGrow={1}
|
|
485
|
+
>
|
|
486
|
+
<Box
|
|
487
|
+
width={48}
|
|
488
|
+
height={48}
|
|
489
|
+
backgroundColor="grey12"
|
|
490
|
+
borderRadius="100%"
|
|
491
|
+
display="flex"
|
|
492
|
+
alignItems="center"
|
|
493
|
+
justifyContent="center"
|
|
494
|
+
>
|
|
495
|
+
<FigmaIcon height={25} />
|
|
496
|
+
</Box>
|
|
497
|
+
<Box display="flex" flexDirection="column" flexGrow={1}>
|
|
498
|
+
<Text variant="bold">Want to work faster?</Text>
|
|
499
|
+
<Text variant="small" color="grey11">
|
|
500
|
+
Copy frames from Figma with the Slice Machine plugin
|
|
501
|
+
and paste them here.
|
|
502
|
+
</Text>
|
|
503
|
+
</Box>
|
|
504
|
+
</Box>
|
|
505
|
+
<Button
|
|
506
|
+
endIcon="arrowForward"
|
|
507
|
+
color="indigo"
|
|
508
|
+
onClick={() =>
|
|
509
|
+
window.open(
|
|
510
|
+
"https://www.figma.com/community/plugin/TODO",
|
|
511
|
+
"_blank",
|
|
512
|
+
)
|
|
513
|
+
}
|
|
514
|
+
sx={{ marginRight: 8 }}
|
|
515
|
+
invisible
|
|
516
|
+
>
|
|
517
|
+
Install plugin
|
|
518
|
+
</Button>
|
|
519
|
+
</Box>
|
|
520
|
+
)}
|
|
521
|
+
<FileDropZone
|
|
413
522
|
onFilesSelected={onImagesSelected}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
523
|
+
assetType="image"
|
|
524
|
+
maxFiles={IMAGE_UPLOAD_LIMIT}
|
|
525
|
+
overlay={
|
|
526
|
+
<UploadBlankSlate
|
|
527
|
+
onFilesSelected={onImagesSelected}
|
|
528
|
+
onPaste={() => void handlePaste()}
|
|
529
|
+
droppingFiles
|
|
530
|
+
/>
|
|
531
|
+
}
|
|
532
|
+
>
|
|
533
|
+
<UploadBlankSlate
|
|
534
|
+
onFilesSelected={onImagesSelected}
|
|
535
|
+
onPaste={() => void handlePaste()}
|
|
536
|
+
/>
|
|
537
|
+
</FileDropZone>
|
|
538
|
+
</Box>
|
|
539
|
+
) : (
|
|
540
|
+
<>
|
|
541
|
+
<Box
|
|
542
|
+
display="flex"
|
|
543
|
+
alignItems="center"
|
|
544
|
+
justifyContent="space-between"
|
|
545
|
+
padding={16}
|
|
546
|
+
>
|
|
547
|
+
<Text variant="h3">Design</Text>
|
|
548
|
+
<FileUploadButton
|
|
549
|
+
size="medium"
|
|
550
|
+
color="grey"
|
|
551
|
+
onFilesSelected={onImagesSelected}
|
|
552
|
+
startIcon="attachFile"
|
|
553
|
+
disabled={hasTriggeredGeneration}
|
|
554
|
+
>
|
|
555
|
+
Add images
|
|
556
|
+
</FileUploadButton>
|
|
557
|
+
</Box>
|
|
558
|
+
<ScrollArea stableScrollbar={false}>
|
|
559
|
+
<Box
|
|
560
|
+
display="grid"
|
|
561
|
+
gridTemplateColumns="1fr 1fr"
|
|
562
|
+
gap={16}
|
|
563
|
+
padding={16}
|
|
564
|
+
>
|
|
565
|
+
{slices.map((slice, index) => (
|
|
566
|
+
<SliceCard slice={slice} key={`slice-${index}`} />
|
|
567
|
+
))}
|
|
568
|
+
</Box>
|
|
569
|
+
</ScrollArea>
|
|
570
|
+
</>
|
|
571
|
+
)}
|
|
572
|
+
<DialogActions>
|
|
573
|
+
<DialogCancelButton
|
|
574
|
+
onClick={() => closeModal()}
|
|
575
|
+
size="medium"
|
|
576
|
+
disabled={loadingSliceCount > 0}
|
|
577
|
+
sx={{ marginRight: 8 }}
|
|
578
|
+
invisible
|
|
579
|
+
>
|
|
580
|
+
Close
|
|
581
|
+
</DialogCancelButton>
|
|
582
|
+
{completedSliceCount === 0 || loadingSliceCount > 0 ? (
|
|
583
|
+
<DialogActionButton
|
|
584
|
+
color="purple"
|
|
585
|
+
startIcon="autoFixHigh"
|
|
586
|
+
onClick={() => void generatePendingSlices()}
|
|
587
|
+
disabled={
|
|
588
|
+
hasTriggeredGeneration ||
|
|
589
|
+
loadingSliceCount > 0 ||
|
|
590
|
+
pendingSliceCount === 0
|
|
591
|
+
}
|
|
592
|
+
loading={loadingSliceCount > 0}
|
|
593
|
+
size="medium"
|
|
594
|
+
>
|
|
595
|
+
Generate{" "}
|
|
596
|
+
{generateSliceCount > 0 ? `(${generateSliceCount}) ` : ""}
|
|
597
|
+
{generateSliceCount === 1 ? "Slice" : "Slices"}
|
|
598
|
+
</DialogActionButton>
|
|
599
|
+
) : (
|
|
600
|
+
<DialogActionButton
|
|
601
|
+
color="purple"
|
|
602
|
+
onClick={() => void onSubmit()}
|
|
603
|
+
loading={isSubmitting}
|
|
604
|
+
size="medium"
|
|
605
|
+
>
|
|
606
|
+
{getSubmitButtonLabel(location, completedSliceCount)}
|
|
607
|
+
</DialogActionButton>
|
|
608
|
+
)}
|
|
609
|
+
</DialogActions>
|
|
610
|
+
</>
|
|
421
611
|
) : (
|
|
422
|
-
<
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
padding={16}
|
|
428
|
-
>
|
|
429
|
-
{slices.map((slice, index) => (
|
|
430
|
-
<SliceCard slice={slice} key={`slice-${index}`} />
|
|
431
|
-
))}
|
|
432
|
-
</Box>
|
|
433
|
-
</ScrollArea>
|
|
434
|
-
)}
|
|
435
|
-
|
|
436
|
-
<DialogActions>
|
|
437
|
-
<DialogCancelButton disabled={isCreatingSlices} />
|
|
438
|
-
<DialogActionButton
|
|
439
|
-
disabled={!someSlicesReady || areSlicesLoading}
|
|
440
|
-
loading={isCreatingSlices}
|
|
441
|
-
onClick={onSubmit}
|
|
612
|
+
<Box
|
|
613
|
+
display="flex"
|
|
614
|
+
justifyContent="center"
|
|
615
|
+
alignItems="center"
|
|
616
|
+
height="100%"
|
|
442
617
|
>
|
|
443
|
-
|
|
444
|
-
</
|
|
445
|
-
|
|
618
|
+
<ProgressCircle color="purple9" />
|
|
619
|
+
</Box>
|
|
620
|
+
)}
|
|
446
621
|
</DialogContent>
|
|
447
622
|
</Dialog>
|
|
448
623
|
);
|
|
@@ -451,8 +626,10 @@ export function CreateSliceFromImageModal(
|
|
|
451
626
|
function UploadBlankSlate(props: {
|
|
452
627
|
droppingFiles?: boolean;
|
|
453
628
|
onFilesSelected: (files: File[]) => void;
|
|
629
|
+
onPaste: () => void;
|
|
454
630
|
}) {
|
|
455
|
-
const { droppingFiles = false, onFilesSelected } = props;
|
|
631
|
+
const { droppingFiles = false, onFilesSelected, onPaste } = props;
|
|
632
|
+
const isFigmaEnabled = useIsFigmaEnabled();
|
|
456
633
|
|
|
457
634
|
return (
|
|
458
635
|
<Box
|
|
@@ -463,27 +640,70 @@ function UploadBlankSlate(props: {
|
|
|
463
640
|
border
|
|
464
641
|
borderStyle="dashed"
|
|
465
642
|
borderColor={droppingFiles ? "purple9" : "grey6"}
|
|
643
|
+
borderRadius={12}
|
|
644
|
+
flexGrow={1}
|
|
466
645
|
>
|
|
467
646
|
<BlankSlate>
|
|
468
|
-
<
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
startIcon="attachFile"
|
|
481
|
-
onFilesSelected={onFilesSelected}
|
|
482
|
-
color="grey"
|
|
647
|
+
<Box display="flex" flexDirection="column" gap={16} alignItems="center">
|
|
648
|
+
<BlankSlateIcon
|
|
649
|
+
lineColor="purple11"
|
|
650
|
+
backgroundColor="purple5"
|
|
651
|
+
name="cloudUpload"
|
|
652
|
+
size="large"
|
|
653
|
+
/>
|
|
654
|
+
<Box
|
|
655
|
+
display="flex"
|
|
656
|
+
flexDirection="column"
|
|
657
|
+
gap={4}
|
|
658
|
+
alignItems="center"
|
|
483
659
|
>
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
660
|
+
{isFigmaEnabled ? (
|
|
661
|
+
<>
|
|
662
|
+
<Text>Generate slices from your designs</Text>
|
|
663
|
+
<Text variant="small" color="grey11">
|
|
664
|
+
Upload your design images or paste them directly from Figma.
|
|
665
|
+
</Text>
|
|
666
|
+
</>
|
|
667
|
+
) : (
|
|
668
|
+
<>
|
|
669
|
+
<Text>Upload your design images.</Text>
|
|
670
|
+
<Text variant="small" color="grey11">
|
|
671
|
+
Once uploaded, you can generate slices automatically using AI.
|
|
672
|
+
</Text>
|
|
673
|
+
</>
|
|
674
|
+
)}
|
|
675
|
+
</Box>
|
|
676
|
+
<Box display="flex" alignItems="center" gap={16}>
|
|
677
|
+
{isFigmaEnabled ? (
|
|
678
|
+
<>
|
|
679
|
+
<Button
|
|
680
|
+
size="small"
|
|
681
|
+
renderStartIcon={() => <FigmaIcon height={16} />}
|
|
682
|
+
color="grey"
|
|
683
|
+
onClick={onPaste}
|
|
684
|
+
>
|
|
685
|
+
Paste from Figma
|
|
686
|
+
</Button>
|
|
687
|
+
<FileUploadButton
|
|
688
|
+
size="small"
|
|
689
|
+
onFilesSelected={onFilesSelected}
|
|
690
|
+
color="purple"
|
|
691
|
+
invisible
|
|
692
|
+
>
|
|
693
|
+
Add images
|
|
694
|
+
</FileUploadButton>
|
|
695
|
+
</>
|
|
696
|
+
) : (
|
|
697
|
+
<FileUploadButton
|
|
698
|
+
startIcon="attachFile"
|
|
699
|
+
onFilesSelected={onFilesSelected}
|
|
700
|
+
color="grey"
|
|
701
|
+
>
|
|
702
|
+
Add images
|
|
703
|
+
</FileUploadButton>
|
|
704
|
+
)}
|
|
705
|
+
</Box>
|
|
706
|
+
</Box>
|
|
487
707
|
</BlankSlate>
|
|
488
708
|
</Box>
|
|
489
709
|
);
|
|
@@ -506,12 +726,6 @@ async function getImageUrl({ image }: { image: File }) {
|
|
|
506
726
|
return url;
|
|
507
727
|
}
|
|
508
728
|
|
|
509
|
-
type NewSlice = {
|
|
510
|
-
image: File;
|
|
511
|
-
model: SharedSlice;
|
|
512
|
-
langSmithUrl?: string;
|
|
513
|
-
};
|
|
514
|
-
|
|
515
729
|
/**
|
|
516
730
|
* Keeps track of the existing slices in the project.
|
|
517
731
|
* Re-fetches them when the modal is opened.
|
|
@@ -583,54 +797,17 @@ function sliceWithoutConflicts({
|
|
|
583
797
|
};
|
|
584
798
|
}
|
|
585
799
|
|
|
586
|
-
async function addSlices(newSlices: NewSlice[]) {
|
|
587
|
-
// use the first library
|
|
588
|
-
const { libraries = [] } =
|
|
589
|
-
await managerClient.project.getSliceMachineConfig();
|
|
590
|
-
const library = libraries[0];
|
|
591
|
-
if (!library) {
|
|
592
|
-
throw new Error("No library found in the config.");
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
for (const { model } of newSlices) {
|
|
596
|
-
const { errors } = await managerClient.slices.createSlice({
|
|
597
|
-
libraryID: library,
|
|
598
|
-
model,
|
|
599
|
-
});
|
|
600
|
-
if (errors.length) {
|
|
601
|
-
throw new Error(`Failed to create slice ${model.id}.`);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
// for each added slice, set the variation screenshot
|
|
606
|
-
const slices = await Promise.all(
|
|
607
|
-
newSlices.map(async ({ model, image, langSmithUrl }) => {
|
|
608
|
-
await managerClient.slices.updateSliceScreenshot({
|
|
609
|
-
libraryID: library,
|
|
610
|
-
sliceID: model.id,
|
|
611
|
-
variationID: model.variations[0].id,
|
|
612
|
-
data: image,
|
|
613
|
-
});
|
|
614
|
-
return {
|
|
615
|
-
model,
|
|
616
|
-
langSmithUrl,
|
|
617
|
-
};
|
|
618
|
-
}),
|
|
619
|
-
);
|
|
620
|
-
|
|
621
|
-
return { library, slices };
|
|
622
|
-
}
|
|
623
|
-
|
|
624
800
|
const getSubmitButtonLabel = (
|
|
625
801
|
location: "custom_type" | "page_type" | "slices",
|
|
802
|
+
completedSliceCount: number,
|
|
626
803
|
) => {
|
|
627
804
|
switch (location) {
|
|
628
805
|
case "custom_type":
|
|
629
|
-
return
|
|
806
|
+
return `Add to type (${completedSliceCount})`;
|
|
630
807
|
case "page_type":
|
|
631
|
-
return
|
|
808
|
+
return `Add to page (${completedSliceCount})`;
|
|
632
809
|
case "slices":
|
|
633
|
-
return "
|
|
810
|
+
return "Done";
|
|
634
811
|
}
|
|
635
812
|
};
|
|
636
813
|
|
|
@@ -639,12 +816,23 @@ function useIsFigmaEnabled() {
|
|
|
639
816
|
return experiment?.value === "on";
|
|
640
817
|
}
|
|
641
818
|
|
|
642
|
-
function
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
819
|
+
function useLibraryID() {
|
|
820
|
+
const [libraryID, setLibraryID] = useState<string | undefined>();
|
|
821
|
+
|
|
822
|
+
useEffect(() => {
|
|
823
|
+
managerClient.project
|
|
824
|
+
.getSliceMachineConfig()
|
|
825
|
+
.then((smConfig) => {
|
|
826
|
+
const libraryID = smConfig?.libraries?.[0];
|
|
827
|
+
if (libraryID === undefined) {
|
|
828
|
+
throw new Error("No library found in the config.");
|
|
829
|
+
}
|
|
830
|
+
setLibraryID(libraryID);
|
|
831
|
+
})
|
|
832
|
+
.catch(() => {
|
|
833
|
+
throw new Error("Could not get library ID from the config.");
|
|
834
|
+
});
|
|
835
|
+
}, []);
|
|
836
|
+
|
|
837
|
+
return { libraryID, isLoading: libraryID === undefined };
|
|
650
838
|
}
|