slice-machine-ui 2.19.1 → 2.19.2-alpha.jp-figma-to-prismic.2
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/125-00b909bdbab2ca15.js +1 -0
- package/out/_next/static/chunks/20-4cb8941c8aafa019.js +1 -0
- package/out/_next/static/chunks/{344-fdb3008f4bb3b0c1.js → 344-b64f09e670634ed1.js} +1 -1
- package/out/_next/static/chunks/{500-d3989390f5e8da53.js → 444-d39213143f782fec.js} +1 -1
- package/out/_next/static/chunks/593-97393b59cba3d429.js +1 -0
- package/out/_next/static/chunks/66-d9d3bcb5d041cb6d.js +1 -0
- package/out/_next/static/chunks/907-88dafe5c1e80dead.js +1 -0
- package/out/_next/static/chunks/pages/{_app-664e26e8e0083aaa.js → _app-b73cf0344465689d.js} +1 -1
- package/out/_next/static/chunks/pages/{changes-8af4acbb8f974cb2.js → changes-b3f45dfeb5dc08f0.js} +1 -1
- package/out/_next/static/chunks/pages/custom-types/{[customTypeId]-af9376721beb489e.js → [customTypeId]-02278526092bcf5c.js} +1 -1
- package/out/_next/static/chunks/pages/page-types/{[pageTypeId]-a24665e91b882169.js → [pageTypeId]-4d99de1b52de7c9b.js} +1 -1
- package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/{[variation]-e973a443d8b8a75d.js → [variation]-330e5d545d9f6269.js} +1 -1
- package/out/_next/static/chunks/pages/{slices-81c1c3f1bcad60f4.js → slices-de5f8cf4719e88c4.js} +1 -1
- package/out/_next/static/j0_D1z-ZN75hJO-JvwC0X/_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 +5 -4
- package/src/features/customTypes/customTypesBuilder/CreateSliceFromImageModal/CreateSliceFromImageModal.tsx +364 -155
- package/src/features/customTypes/customTypesBuilder/CreateSliceFromImageModal/SliceCard.tsx +26 -9
- package/src/icons/FigmaIcon.tsx +78 -34
- package/src/legacy/components/ScreenshotChangesModal/index.tsx +1 -1
- package/test/src/modules/__fixtures__/serverState.ts +1 -0
- 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-180eb33eefccc237.js +0 -1
- package/out/_next/static/chunks/918-fa4f2563cb5fd014.js +0 -1
- package/out/_next/static/mWW0JPKbrqF9bfSpOlAsb/_buildManifest.js +0 -1
- /package/out/_next/static/{mWW0JPKbrqF9bfSpOlAsb → j0_D1z-ZN75hJO-JvwC0X}/_ssgManifest.js +0 -0
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BlankSlate,
|
|
3
|
-
BlankSlateActions,
|
|
4
|
-
BlankSlateDescription,
|
|
5
3
|
BlankSlateIcon,
|
|
6
|
-
BlankSlateTitle,
|
|
7
4
|
Box,
|
|
5
|
+
Button,
|
|
8
6
|
Dialog,
|
|
9
|
-
DialogActionButton,
|
|
10
7
|
DialogActions,
|
|
11
8
|
DialogCancelButton,
|
|
12
9
|
DialogContent,
|
|
@@ -15,20 +12,30 @@ import {
|
|
|
15
12
|
FileDropZone,
|
|
16
13
|
FileUploadButton,
|
|
17
14
|
ScrollArea,
|
|
15
|
+
Text,
|
|
18
16
|
} from "@prismicio/editor-ui";
|
|
19
17
|
import { SharedSlice } from "@prismicio/types-internal/lib/customtypes";
|
|
20
18
|
import { useEffect, useRef, useState } from "react";
|
|
19
|
+
import { useHotkeys } from "react-hotkeys-hook";
|
|
21
20
|
import { toast } from "react-toastify";
|
|
21
|
+
import { z } from "zod";
|
|
22
22
|
|
|
23
23
|
import { getState, telemetry } from "@/apiClient";
|
|
24
24
|
import { addAiFeedback } from "@/features/aiFeedback";
|
|
25
25
|
import { useOnboarding } from "@/features/onboarding/useOnboarding";
|
|
26
26
|
import { useAutoSync } from "@/features/sync/AutoSyncProvider";
|
|
27
|
+
import { FigmaIcon } from "@/icons/FigmaIcon";
|
|
27
28
|
import { managerClient } from "@/managerClient";
|
|
28
29
|
import useSliceMachineActions from "@/modules/useSliceMachineActions";
|
|
29
30
|
|
|
30
31
|
import { Slice, SliceCard } from "./SliceCard";
|
|
31
32
|
|
|
33
|
+
const clipboardDataSchema = z.object({
|
|
34
|
+
__type: z.literal("figma-to-prismic/clipboard-data"),
|
|
35
|
+
name: z.string(),
|
|
36
|
+
image: z.string().startsWith("data:image/"),
|
|
37
|
+
});
|
|
38
|
+
|
|
32
39
|
const IMAGE_UPLOAD_LIMIT = 10;
|
|
33
40
|
|
|
34
41
|
interface CreateSliceFromImageModalProps {
|
|
@@ -47,12 +54,12 @@ interface CreateSliceFromImageModalProps {
|
|
|
47
54
|
export function CreateSliceFromImageModal(
|
|
48
55
|
props: CreateSliceFromImageModalProps,
|
|
49
56
|
) {
|
|
50
|
-
const { open, location,
|
|
57
|
+
const { open, location, onClose } = props;
|
|
51
58
|
const [slices, setSlices] = useState<Slice[]>([]);
|
|
52
|
-
const [isCreatingSlices, setIsCreatingSlices] = useState(false);
|
|
53
59
|
const { syncChanges } = useAutoSync();
|
|
54
60
|
const { createSliceSuccess } = useSliceMachineActions();
|
|
55
61
|
const { completeStep } = useOnboarding();
|
|
62
|
+
const existingSlices = useExistingSlices({ open });
|
|
56
63
|
|
|
57
64
|
/**
|
|
58
65
|
* Keeps track of the current instance id.
|
|
@@ -60,6 +67,21 @@ export function CreateSliceFromImageModal(
|
|
|
60
67
|
*/
|
|
61
68
|
const id = useRef(crypto.randomUUID());
|
|
62
69
|
|
|
70
|
+
useHotkeys(
|
|
71
|
+
["meta+v", "ctrl+v"],
|
|
72
|
+
(event) => {
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
void handlePaste();
|
|
75
|
+
},
|
|
76
|
+
{ enabled: open },
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (slices.every((slice) => slice.status === "success")) {
|
|
81
|
+
void onAllComplete();
|
|
82
|
+
}
|
|
83
|
+
}, [slices]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
84
|
+
|
|
63
85
|
const setSlice = (args: {
|
|
64
86
|
index: number;
|
|
65
87
|
slice: (prevSlice: Slice) => Slice;
|
|
@@ -69,7 +91,7 @@ export function CreateSliceFromImageModal(
|
|
|
69
91
|
};
|
|
70
92
|
|
|
71
93
|
const onOpenChange = (open: boolean) => {
|
|
72
|
-
if (open
|
|
94
|
+
if (open) return;
|
|
73
95
|
onClose();
|
|
74
96
|
id.current = crypto.randomUUID();
|
|
75
97
|
setSlices([]);
|
|
@@ -86,48 +108,170 @@ export function CreateSliceFromImageModal(
|
|
|
86
108
|
setSlices(
|
|
87
109
|
images.map((image) => ({
|
|
88
110
|
status: "uploading",
|
|
111
|
+
source: "upload",
|
|
89
112
|
image,
|
|
90
113
|
})),
|
|
91
114
|
);
|
|
92
115
|
|
|
93
|
-
images.forEach((
|
|
116
|
+
images.forEach((imageData, index) => {
|
|
117
|
+
void generateSlice({ index, imageData, source: "upload" });
|
|
118
|
+
});
|
|
94
119
|
};
|
|
95
120
|
|
|
96
|
-
const
|
|
97
|
-
|
|
121
|
+
const handlePaste = async () => {
|
|
122
|
+
// Limit just to one paste at a time for now
|
|
123
|
+
if (!open || slices.length > 0) return;
|
|
124
|
+
|
|
125
|
+
const supportsClipboardRead =
|
|
126
|
+
typeof navigator.clipboard?.read === "function";
|
|
127
|
+
|
|
128
|
+
if (!supportsClipboardRead) {
|
|
129
|
+
toast.error("Clipboard paste is not supported in this browser.");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const clipboardItems = await navigator.clipboard.read();
|
|
135
|
+
if (clipboardItems.length === 0) {
|
|
136
|
+
toast.error("No data found in clipboard.");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let imageName = "pasted-image.png";
|
|
141
|
+
let imageBlob: Blob | null = null;
|
|
142
|
+
let success = false;
|
|
143
|
+
|
|
144
|
+
// Method 1: Try to extract image from clipboard image/png blob (preferred)
|
|
145
|
+
for (const item of clipboardItems) {
|
|
146
|
+
const imageType = item.types.find((type) => type.startsWith("image/"));
|
|
147
|
+
if (imageType !== undefined) {
|
|
148
|
+
imageBlob = await item.getType(imageType);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Method 2: Read JSON from text/plain to get metadata and base64 image as fallback
|
|
154
|
+
for (const item of clipboardItems) {
|
|
155
|
+
if (item.types.includes("text/plain")) {
|
|
156
|
+
try {
|
|
157
|
+
const textBlob = await item.getType("text/plain");
|
|
158
|
+
const text = await textBlob.text();
|
|
159
|
+
|
|
160
|
+
const result = clipboardDataSchema.safeParse(JSON.parse(text));
|
|
161
|
+
if (result.success) {
|
|
162
|
+
success = true;
|
|
163
|
+
const data = result.data;
|
|
164
|
+
imageName = `${data.name}.png`;
|
|
165
|
+
|
|
166
|
+
// Use base64 image as fallback if no blob was found
|
|
167
|
+
if (!imageBlob) {
|
|
168
|
+
const response = await fetch(data.image);
|
|
169
|
+
imageBlob = await response.blob();
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
console.warn("Clipboard data validation failed:", result.error);
|
|
173
|
+
}
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.warn("Failed to parse JSON from clipboard:", error);
|
|
176
|
+
// Continue - we may still have imageBlob from Method 1
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!imageBlob) {
|
|
182
|
+
if (success) {
|
|
183
|
+
toast.error(
|
|
184
|
+
"Could not extract Figma data from clipboard. Please try copying again using the Prismic Figma plugin.",
|
|
185
|
+
);
|
|
186
|
+
} else {
|
|
187
|
+
toast.error(
|
|
188
|
+
"No Figma data found in clipboard. Make sure you've copied a design using the Prismic Figma plugin.",
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Check if we're at the limit
|
|
195
|
+
const currentSliceCount = slices.length;
|
|
196
|
+
if (currentSliceCount >= IMAGE_UPLOAD_LIMIT) {
|
|
197
|
+
toast.error(
|
|
198
|
+
`You can only upload ${IMAGE_UPLOAD_LIMIT} images at a time.`,
|
|
199
|
+
);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Create File object from blob and append to existing slices
|
|
204
|
+
const imageData = new File([imageBlob], imageName, {
|
|
205
|
+
type: imageBlob.type,
|
|
206
|
+
});
|
|
207
|
+
const newIndex = currentSliceCount;
|
|
208
|
+
|
|
209
|
+
// Append new slice to existing ones
|
|
210
|
+
setSlices((prevSlices) => [
|
|
211
|
+
...prevSlices,
|
|
212
|
+
{
|
|
213
|
+
source: "figma",
|
|
214
|
+
status: "uploading",
|
|
215
|
+
image: imageData,
|
|
216
|
+
},
|
|
217
|
+
]);
|
|
218
|
+
|
|
219
|
+
// Start uploading the new image
|
|
220
|
+
void generateSlice({ index: newIndex, imageData, source: "figma" });
|
|
221
|
+
|
|
222
|
+
toast.success(`Pasted ${imageName}${success ? " from Figma" : ""}`);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error("Failed to paste from clipboard:", error);
|
|
225
|
+
toast.error(
|
|
226
|
+
"Failed to paste from clipboard. Please check browser permissions and try again.",
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const generateSlice = async (args: {
|
|
232
|
+
index: number;
|
|
233
|
+
imageData: File;
|
|
234
|
+
source: "figma" | "upload";
|
|
235
|
+
}) => {
|
|
236
|
+
const { index, imageData, source } = args;
|
|
98
237
|
const currentId = id.current;
|
|
99
238
|
|
|
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
|
+
|
|
100
245
|
setSlice({
|
|
101
246
|
index,
|
|
102
|
-
slice: (prevSlice) => ({
|
|
103
|
-
...prevSlice,
|
|
104
|
-
status: "uploading",
|
|
105
|
-
}),
|
|
247
|
+
slice: (prevSlice) => ({ ...prevSlice, status: "uploading" }),
|
|
106
248
|
});
|
|
107
249
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
);
|
|
250
|
+
try {
|
|
251
|
+
const imageUrl = await getImageUrl({ image: imageData });
|
|
252
|
+
if (currentId !== id.current) return;
|
|
253
|
+
|
|
254
|
+
void inferSlice({ index, imageUrl, libraryID, source });
|
|
255
|
+
} catch {
|
|
256
|
+
if (currentId !== id.current) return;
|
|
257
|
+
setSlice({
|
|
258
|
+
index,
|
|
259
|
+
slice: (prevSlice) => ({
|
|
260
|
+
...prevSlice,
|
|
261
|
+
status: "uploadError",
|
|
262
|
+
onRetry: () => void generateSlice({ index, imageData, source }),
|
|
263
|
+
}),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
125
266
|
};
|
|
126
267
|
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
268
|
+
const inferSlice = async (args: {
|
|
269
|
+
index: number;
|
|
270
|
+
imageUrl: string;
|
|
271
|
+
libraryID: string;
|
|
272
|
+
source: "figma" | "upload";
|
|
273
|
+
}) => {
|
|
274
|
+
const { index, imageUrl, libraryID, source } = args;
|
|
131
275
|
const currentId = id.current;
|
|
132
276
|
|
|
133
277
|
setSlice({
|
|
@@ -139,100 +283,106 @@ export function CreateSliceFromImageModal(
|
|
|
139
283
|
}),
|
|
140
284
|
});
|
|
141
285
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
newSlices: prevSlices,
|
|
156
|
-
slice,
|
|
157
|
-
}),
|
|
158
|
-
langSmithUrl,
|
|
159
|
-
}
|
|
160
|
-
: prevSlice,
|
|
161
|
-
),
|
|
162
|
-
);
|
|
163
|
-
},
|
|
164
|
-
() => {
|
|
165
|
-
if (currentId !== id.current) return;
|
|
166
|
-
setSlice({
|
|
167
|
-
index,
|
|
168
|
-
slice: (prevSlice) => ({
|
|
286
|
+
try {
|
|
287
|
+
const inferResult = await managerClient.customTypes.inferSlice({
|
|
288
|
+
source,
|
|
289
|
+
libraryID,
|
|
290
|
+
imageUrl,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
if (currentId !== id.current) return;
|
|
294
|
+
|
|
295
|
+
setSlices((prevSlices) => {
|
|
296
|
+
return prevSlices.map((prevSlice, i) => {
|
|
297
|
+
if (i !== index) return prevSlice;
|
|
298
|
+
return {
|
|
169
299
|
...prevSlice,
|
|
170
|
-
status: "
|
|
300
|
+
status: "success",
|
|
171
301
|
thumbnailUrl: imageUrl,
|
|
172
|
-
|
|
173
|
-
|
|
302
|
+
model: sliceWithoutConflicts({
|
|
303
|
+
existingSlices: existingSlices.current,
|
|
304
|
+
newSlices: slices,
|
|
305
|
+
slice: inferResult.slice,
|
|
306
|
+
}),
|
|
307
|
+
};
|
|
174
308
|
});
|
|
175
|
-
}
|
|
176
|
-
|
|
309
|
+
});
|
|
310
|
+
} catch {
|
|
311
|
+
if (currentId !== id.current) return;
|
|
312
|
+
setSlice({
|
|
313
|
+
index,
|
|
314
|
+
slice: (prevSlice) => ({
|
|
315
|
+
...prevSlice,
|
|
316
|
+
status: "generateError",
|
|
317
|
+
thumbnailUrl: imageUrl,
|
|
318
|
+
onRetry: () => {
|
|
319
|
+
void inferSlice({ index, imageUrl, libraryID, source });
|
|
320
|
+
},
|
|
321
|
+
}),
|
|
322
|
+
});
|
|
323
|
+
}
|
|
177
324
|
};
|
|
178
325
|
|
|
179
|
-
const
|
|
326
|
+
const onAllComplete = async () => {
|
|
180
327
|
const newSlices = slices.reduce<NewSlice[]>((acc, slice) => {
|
|
181
|
-
if (slice.status === "success")
|
|
328
|
+
if (slice.status === "success") {
|
|
329
|
+
acc.push(slice);
|
|
330
|
+
}
|
|
182
331
|
return acc;
|
|
183
332
|
}, []);
|
|
333
|
+
|
|
184
334
|
if (!newSlices.length) return;
|
|
185
335
|
|
|
186
336
|
const currentId = id.current;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
337
|
+
try {
|
|
338
|
+
// Only the slices generated from uploaded images need this step
|
|
339
|
+
const { slices, library } = await addSlices(
|
|
340
|
+
newSlices.filter((slice) => slice.source === "upload"),
|
|
341
|
+
);
|
|
342
|
+
if (currentId !== id.current) return;
|
|
343
|
+
|
|
344
|
+
id.current = crypto.randomUUID();
|
|
345
|
+
|
|
346
|
+
const serverState = await getState();
|
|
347
|
+
createSliceSuccess(serverState.libraries);
|
|
348
|
+
syncChanges();
|
|
349
|
+
|
|
350
|
+
void completeStep("createSlice");
|
|
351
|
+
|
|
352
|
+
for (const { model, langSmithUrl } of slices) {
|
|
353
|
+
void telemetry.track({
|
|
354
|
+
event: "slice:created",
|
|
355
|
+
id: model.id,
|
|
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
|
+
}
|
|
371
|
+
|
|
372
|
+
toast.success(
|
|
373
|
+
`${newSlices.length} new slice${
|
|
374
|
+
newSlices.length > 1 ? "s" : ""
|
|
375
|
+
} successfully generated.`,
|
|
376
|
+
);
|
|
377
|
+
} catch {
|
|
378
|
+
if (currentId !== id.current) return;
|
|
379
|
+
toast.error("An unexpected error happened while adding slices.");
|
|
380
|
+
}
|
|
229
381
|
};
|
|
230
382
|
|
|
231
383
|
const areSlicesLoading = slices.some(
|
|
232
384
|
(slice) => slice.status === "uploading" || slice.status === "generating",
|
|
233
385
|
);
|
|
234
|
-
const readySlices = slices.filter((slice) => slice.status === "success");
|
|
235
|
-
const someSlicesReady = readySlices.length > 0;
|
|
236
386
|
|
|
237
387
|
return (
|
|
238
388
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
@@ -242,7 +392,56 @@ export function CreateSliceFromImageModal(
|
|
|
242
392
|
Upload images to generate slices with AI
|
|
243
393
|
</DialogDescription>
|
|
244
394
|
{slices.length === 0 ? (
|
|
245
|
-
<Box
|
|
395
|
+
<Box
|
|
396
|
+
padding={16}
|
|
397
|
+
height="100%"
|
|
398
|
+
gap={16}
|
|
399
|
+
display="flex"
|
|
400
|
+
flexDirection="column"
|
|
401
|
+
>
|
|
402
|
+
<Box
|
|
403
|
+
display="flex"
|
|
404
|
+
gap={16}
|
|
405
|
+
alignItems="center"
|
|
406
|
+
backgroundColor="grey2"
|
|
407
|
+
padding={16}
|
|
408
|
+
borderRadius={12}
|
|
409
|
+
>
|
|
410
|
+
<Box display="flex" gap={8} alignItems="center" flexGrow={1}>
|
|
411
|
+
<Box
|
|
412
|
+
width={48}
|
|
413
|
+
height={48}
|
|
414
|
+
backgroundColor="grey12"
|
|
415
|
+
borderRadius="100%"
|
|
416
|
+
display="flex"
|
|
417
|
+
alignItems="center"
|
|
418
|
+
justifyContent="center"
|
|
419
|
+
>
|
|
420
|
+
<FigmaIcon variant="original" height={25} />
|
|
421
|
+
</Box>
|
|
422
|
+
<Box display="flex" flexDirection="column" flexGrow={1}>
|
|
423
|
+
<Text variant="bold">Want to work faster?</Text>
|
|
424
|
+
<Text variant="small" color="grey11">
|
|
425
|
+
Copy frames from Figma with the Slice Machine plugin and
|
|
426
|
+
paste them here.
|
|
427
|
+
</Text>
|
|
428
|
+
</Box>
|
|
429
|
+
</Box>
|
|
430
|
+
<Button
|
|
431
|
+
endIcon="arrowForward"
|
|
432
|
+
color="indigo"
|
|
433
|
+
onClick={() =>
|
|
434
|
+
window.open(
|
|
435
|
+
"https://www.figma.com/community/plugin/TODO",
|
|
436
|
+
"_blank",
|
|
437
|
+
)
|
|
438
|
+
}
|
|
439
|
+
sx={{ marginRight: 8 }}
|
|
440
|
+
invisible
|
|
441
|
+
>
|
|
442
|
+
Install plugin
|
|
443
|
+
</Button>
|
|
444
|
+
</Box>
|
|
246
445
|
<FileDropZone
|
|
247
446
|
onFilesSelected={onImagesSelected}
|
|
248
447
|
assetType="image"
|
|
@@ -250,11 +449,15 @@ export function CreateSliceFromImageModal(
|
|
|
250
449
|
overlay={
|
|
251
450
|
<UploadBlankSlate
|
|
252
451
|
onFilesSelected={onImagesSelected}
|
|
452
|
+
onPaste={() => void handlePaste()}
|
|
253
453
|
droppingFiles
|
|
254
454
|
/>
|
|
255
455
|
}
|
|
256
456
|
>
|
|
257
|
-
<UploadBlankSlate
|
|
457
|
+
<UploadBlankSlate
|
|
458
|
+
onFilesSelected={onImagesSelected}
|
|
459
|
+
onPaste={() => void handlePaste()}
|
|
460
|
+
/>
|
|
258
461
|
</FileDropZone>
|
|
259
462
|
</Box>
|
|
260
463
|
) : (
|
|
@@ -273,14 +476,9 @@ export function CreateSliceFromImageModal(
|
|
|
273
476
|
)}
|
|
274
477
|
|
|
275
478
|
<DialogActions>
|
|
276
|
-
<DialogCancelButton disabled={
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
loading={isCreatingSlices}
|
|
280
|
-
onClick={onSubmit}
|
|
281
|
-
>
|
|
282
|
-
{getSubmitButtonLabel(location)} ({readySlices.length})
|
|
283
|
-
</DialogActionButton>
|
|
479
|
+
<DialogCancelButton disabled={areSlicesLoading}>
|
|
480
|
+
Close
|
|
481
|
+
</DialogCancelButton>
|
|
284
482
|
</DialogActions>
|
|
285
483
|
</DialogContent>
|
|
286
484
|
</Dialog>
|
|
@@ -290,8 +488,9 @@ export function CreateSliceFromImageModal(
|
|
|
290
488
|
function UploadBlankSlate(props: {
|
|
291
489
|
droppingFiles?: boolean;
|
|
292
490
|
onFilesSelected: (files: File[]) => void;
|
|
491
|
+
onPaste: () => void;
|
|
293
492
|
}) {
|
|
294
|
-
const { droppingFiles = false, onFilesSelected } = props;
|
|
493
|
+
const { droppingFiles = false, onFilesSelected, onPaste } = props;
|
|
295
494
|
|
|
296
495
|
return (
|
|
297
496
|
<Box
|
|
@@ -302,27 +501,49 @@ function UploadBlankSlate(props: {
|
|
|
302
501
|
border
|
|
303
502
|
borderStyle="dashed"
|
|
304
503
|
borderColor={droppingFiles ? "purple9" : "grey6"}
|
|
504
|
+
borderRadius={12}
|
|
505
|
+
flexGrow={1}
|
|
305
506
|
>
|
|
306
507
|
<BlankSlate>
|
|
307
|
-
<
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
startIcon="attachFile"
|
|
320
|
-
onFilesSelected={onFilesSelected}
|
|
321
|
-
color="grey"
|
|
508
|
+
<Box display="flex" flexDirection="column" gap={16} alignItems="center">
|
|
509
|
+
<BlankSlateIcon
|
|
510
|
+
lineColor="purple11"
|
|
511
|
+
backgroundColor="purple5"
|
|
512
|
+
name="cloudUpload"
|
|
513
|
+
size="large"
|
|
514
|
+
/>
|
|
515
|
+
<Box
|
|
516
|
+
display="flex"
|
|
517
|
+
flexDirection="column"
|
|
518
|
+
gap={4}
|
|
519
|
+
alignItems="center"
|
|
322
520
|
>
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
521
|
+
<Text>Generate slices from your designs</Text>
|
|
522
|
+
<Text variant="small" color="grey11">
|
|
523
|
+
Upload your design images or paste them directly from Figma.
|
|
524
|
+
</Text>
|
|
525
|
+
</Box>
|
|
526
|
+
<Box display="flex" alignItems="center" gap={16}>
|
|
527
|
+
<Button
|
|
528
|
+
size="small"
|
|
529
|
+
renderStartIcon={() => (
|
|
530
|
+
<FigmaIcon variant="original" height={16} />
|
|
531
|
+
)}
|
|
532
|
+
color="grey"
|
|
533
|
+
onClick={onPaste}
|
|
534
|
+
>
|
|
535
|
+
Paste from Figma
|
|
536
|
+
</Button>
|
|
537
|
+
<FileUploadButton
|
|
538
|
+
size="small"
|
|
539
|
+
onFilesSelected={onFilesSelected}
|
|
540
|
+
color="purple"
|
|
541
|
+
invisible
|
|
542
|
+
>
|
|
543
|
+
Add images
|
|
544
|
+
</FileUploadButton>
|
|
545
|
+
</Box>
|
|
546
|
+
</Box>
|
|
326
547
|
</BlankSlate>
|
|
327
548
|
</Box>
|
|
328
549
|
);
|
|
@@ -349,6 +570,7 @@ type NewSlice = {
|
|
|
349
570
|
image: File;
|
|
350
571
|
model: SharedSlice;
|
|
351
572
|
langSmithUrl?: string;
|
|
573
|
+
source: "figma" | "upload";
|
|
352
574
|
};
|
|
353
575
|
|
|
354
576
|
/**
|
|
@@ -459,16 +681,3 @@ async function addSlices(newSlices: NewSlice[]) {
|
|
|
459
681
|
|
|
460
682
|
return { library, slices };
|
|
461
683
|
}
|
|
462
|
-
|
|
463
|
-
const getSubmitButtonLabel = (
|
|
464
|
-
location: "custom_type" | "page_type" | "slices",
|
|
465
|
-
) => {
|
|
466
|
-
switch (location) {
|
|
467
|
-
case "custom_type":
|
|
468
|
-
return "Add to type";
|
|
469
|
-
case "page_type":
|
|
470
|
-
return "Add to page";
|
|
471
|
-
case "slices":
|
|
472
|
-
return "Add to slices";
|
|
473
|
-
}
|
|
474
|
-
};
|