slice-machine-ui 2.20.4-beta.5 → 2.20.5-alpha.jp-import-slice-base.1
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/130-e1a16d2f94fb2b64.js +1 -0
- package/out/_next/static/chunks/422-c7a16d95b75c9e1c.js +1 -0
- package/out/_next/static/chunks/{429-aab52070cad2884b.js → 429-e5d7e39160de9f5e.js} +1 -1
- package/out/_next/static/chunks/489-c9535ef34da63d1a.js +1 -0
- package/out/_next/static/chunks/585-c89bb2471e85b9f8.js +1 -0
- package/out/_next/static/chunks/954-bedaaabf664584a0.js +1 -0
- package/out/_next/static/chunks/pages/{_app-0757e69fc5aa12d6.js → _app-058f06cc8ce33c8d.js} +1 -1
- package/out/_next/static/chunks/pages/custom-types/{[customTypeId]-6d613b67e6967ae5.js → [customTypeId]-273e9a82c085b596.js} +1 -1
- package/out/_next/static/chunks/pages/page-types/{[pageTypeId]-40207b66190e3fcd.js → [pageTypeId]-3fa7667de1a790d9.js} +1 -1
- package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]/{simulator-faeb6d2f77d97096.js → simulator-8c70298caf51bed0.js} +1 -1
- package/out/_next/static/chunks/pages/slices-76679cf064761d2b.js +1 -0
- package/out/_next/static/ycaxuLGaColncBF1Sgjjd/_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 +4 -4
- package/src/features/customTypes/customTypesBuilder/ImportSlicesFromLibraryModal/ImportSlicesFromLibraryModal.tsx +336 -0
- package/src/features/customTypes/customTypesBuilder/ImportSlicesFromLibraryModal/SliceCard.tsx +48 -0
- package/src/features/customTypes/customTypesBuilder/ImportSlicesFromLibraryModal/hooks/useImportSlicesFromGithub.ts +91 -0
- package/src/features/customTypes/customTypesBuilder/ImportSlicesFromLibraryModal/index.tsx +1 -0
- package/src/features/customTypes/customTypesBuilder/ImportSlicesFromLibraryModal/types.ts +28 -0
- package/src/features/customTypes/customTypesBuilder/ImportSlicesFromLibraryModal/utils/addSlices.ts +186 -0
- package/src/features/customTypes/customTypesBuilder/ImportSlicesFromLibraryModal/utils/github.ts +657 -0
- package/src/features/customTypes/customTypesBuilder/ImportSlicesFromLibraryModal/utils/mapWithConcurrency.ts +28 -0
- package/src/features/customTypes/customTypesBuilder/ImportSlicesFromLibraryModal/utils/sliceWithoutConflicts.ts +51 -0
- package/src/features/customTypes/customTypesBuilder/SliceZoneBlankSlate.tsx +11 -0
- package/src/features/customTypes/customTypesBuilder/shared/getSubmitButtonLabel.ts +12 -0
- package/src/features/customTypes/customTypesBuilder/shared/useExistingSlices.ts +26 -0
- package/src/features/customTypes/customTypesBuilder/sliceCreationOptions.tsx +14 -0
- package/src/legacy/lib/builders/CustomTypeBuilder/SliceZone/index.tsx +51 -0
- package/src/pages/slices.tsx +30 -0
- package/out/_next/static/9dRDwSkN862Z880iYSr4h/_buildManifest.js +0 -1
- package/out/_next/static/chunks/422-c9192a1dbdd2ae0e.js +0 -1
- package/out/_next/static/chunks/489-32281540712d98bb.js +0 -1
- package/out/_next/static/chunks/633-74a9ae6d5200cefc.js +0 -1
- package/out/_next/static/chunks/pages/slices-ec56b94b35794675.js +0 -1
- /package/out/_next/static/{9dRDwSkN862Z880iYSr4h → ycaxuLGaColncBF1Sgjjd}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Box,
|
|
3
|
+
Button,
|
|
4
|
+
Checkbox,
|
|
5
|
+
Dialog,
|
|
6
|
+
DialogActionButton,
|
|
7
|
+
DialogActions,
|
|
8
|
+
DialogCancelButton,
|
|
9
|
+
DialogContent,
|
|
10
|
+
DialogDescription,
|
|
11
|
+
DialogHeader,
|
|
12
|
+
InlineLabel,
|
|
13
|
+
ScrollArea,
|
|
14
|
+
Text,
|
|
15
|
+
TextInput,
|
|
16
|
+
} from "@prismicio/editor-ui";
|
|
17
|
+
import { SharedSlice } from "@prismicio/types-internal/lib/customtypes";
|
|
18
|
+
import { useEffect, useRef, useState } from "react";
|
|
19
|
+
import { toast } from "react-toastify";
|
|
20
|
+
|
|
21
|
+
import { getState, telemetry } from "@/apiClient";
|
|
22
|
+
import { useOnboarding } from "@/features/onboarding/useOnboarding";
|
|
23
|
+
import { useAutoSync } from "@/features/sync/AutoSyncProvider";
|
|
24
|
+
import useSliceMachineActions from "@/modules/useSliceMachineActions";
|
|
25
|
+
|
|
26
|
+
import { getSubmitButtonLabel } from "../shared/getSubmitButtonLabel";
|
|
27
|
+
import { useExistingSlices } from "../shared/useExistingSlices";
|
|
28
|
+
import { useImportSlicesFromGithub } from "./hooks/useImportSlicesFromGithub";
|
|
29
|
+
import { SliceCard } from "./SliceCard";
|
|
30
|
+
import { NewSlice } from "./types";
|
|
31
|
+
import { addSlices } from "./utils/addSlices";
|
|
32
|
+
import { sliceWithoutConflicts } from "./utils/sliceWithoutConflicts";
|
|
33
|
+
|
|
34
|
+
interface ImportSlicesFromLibraryModalProps {
|
|
35
|
+
open: boolean;
|
|
36
|
+
location: "custom_type" | "page_type" | "slices";
|
|
37
|
+
onSuccess: (args: {
|
|
38
|
+
slices: {
|
|
39
|
+
model: SharedSlice;
|
|
40
|
+
langSmithUrl?: string;
|
|
41
|
+
}[];
|
|
42
|
+
library: string;
|
|
43
|
+
}) => void;
|
|
44
|
+
onClose: () => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function ImportSlicesFromLibraryModal(
|
|
48
|
+
props: ImportSlicesFromLibraryModalProps,
|
|
49
|
+
) {
|
|
50
|
+
const { open, location, onSuccess, onClose } = props;
|
|
51
|
+
|
|
52
|
+
const [isCreatingSlices, setIsCreatingSlices] = useState(false);
|
|
53
|
+
const [githubUrl, setGithubUrl] = useState("");
|
|
54
|
+
const [selectedSliceIds, setSelectedSliceIds] = useState<Set<string>>(
|
|
55
|
+
new Set(),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const { syncChanges } = useAutoSync();
|
|
59
|
+
const { createSliceSuccess, updateSliceMockSuccess } =
|
|
60
|
+
useSliceMachineActions();
|
|
61
|
+
const { completeStep } = useOnboarding();
|
|
62
|
+
const existingSlices = useExistingSlices({ open });
|
|
63
|
+
const { isLoadingSlices, handleImportFromGithub, slices, resetSlices } =
|
|
64
|
+
useImportSlicesFromGithub();
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (slices.length === 0) return;
|
|
68
|
+
|
|
69
|
+
// Set all slices as selected by default
|
|
70
|
+
const allSliceIds = new Set<string>();
|
|
71
|
+
for (const slice of slices) {
|
|
72
|
+
allSliceIds.add(slice.model.id);
|
|
73
|
+
}
|
|
74
|
+
setSelectedSliceIds(allSliceIds);
|
|
75
|
+
}, [slices]);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Keeps track of the current instance id.
|
|
79
|
+
* When the modal is closed, the id is reset.
|
|
80
|
+
*/
|
|
81
|
+
const id = useRef(crypto.randomUUID());
|
|
82
|
+
|
|
83
|
+
const onOpenChange = (open: boolean) => {
|
|
84
|
+
if (open || isCreatingSlices) return;
|
|
85
|
+
onClose();
|
|
86
|
+
id.current = crypto.randomUUID();
|
|
87
|
+
setGithubUrl("");
|
|
88
|
+
setSelectedSliceIds(new Set());
|
|
89
|
+
resetSlices();
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const onSubmit = () => {
|
|
93
|
+
const newSlices = slices.reduce<NewSlice[]>((acc, slice) => {
|
|
94
|
+
if (selectedSliceIds.has(slice.model.id)) {
|
|
95
|
+
acc.push({
|
|
96
|
+
image: slice.image,
|
|
97
|
+
model: slice.model,
|
|
98
|
+
files: slice.files,
|
|
99
|
+
componentContents: slice.componentContents,
|
|
100
|
+
mocks: slice.mocks,
|
|
101
|
+
screenshots: slice.screenshots,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return acc;
|
|
105
|
+
}, []);
|
|
106
|
+
if (!newSlices.length) {
|
|
107
|
+
toast.error("Please select at least one slice to import");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Ensure ids and names are conflict-free against existing and newly-added slices
|
|
112
|
+
const conflictFreeSlices: NewSlice[] = [];
|
|
113
|
+
for (const s of newSlices) {
|
|
114
|
+
const adjustedModel = sliceWithoutConflicts({
|
|
115
|
+
existingSlices: existingSlices.current,
|
|
116
|
+
newSlices: conflictFreeSlices,
|
|
117
|
+
slice: s.model,
|
|
118
|
+
});
|
|
119
|
+
conflictFreeSlices.push({ ...s, model: adjustedModel });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const currentId = id.current;
|
|
123
|
+
setIsCreatingSlices(true);
|
|
124
|
+
addSlices(conflictFreeSlices)
|
|
125
|
+
.then(async ({ slices, library }) => {
|
|
126
|
+
if (currentId !== id.current) return;
|
|
127
|
+
|
|
128
|
+
// Wait a moment to ensure all file writes are complete
|
|
129
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
130
|
+
|
|
131
|
+
const serverState = await getState();
|
|
132
|
+
|
|
133
|
+
// Ensure mocks are included in the libraries data before updating store
|
|
134
|
+
const librariesWithMocks = serverState.libraries.map((lib) => {
|
|
135
|
+
if (lib.name !== library) return lib;
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
...lib,
|
|
139
|
+
components: lib.components.map((component) => {
|
|
140
|
+
// Find the corresponding slice from newSlices to get its mocks
|
|
141
|
+
const importedSlice = conflictFreeSlices.find(
|
|
142
|
+
(s) => s.model.id === component.model.id,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// If mocks are already in component, use them; otherwise use imported mocks
|
|
146
|
+
const mocks =
|
|
147
|
+
component.mocks && component.mocks.length > 0
|
|
148
|
+
? component.mocks
|
|
149
|
+
: importedSlice?.mocks;
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
...component,
|
|
153
|
+
mocks: mocks ?? component.mocks,
|
|
154
|
+
};
|
|
155
|
+
}),
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
createSliceSuccess(librariesWithMocks);
|
|
160
|
+
|
|
161
|
+
// Also update mocks individually to ensure they're in the store
|
|
162
|
+
for (const slice of newSlices) {
|
|
163
|
+
if (
|
|
164
|
+
slice.mocks &&
|
|
165
|
+
Array.isArray(slice.mocks) &&
|
|
166
|
+
slice.mocks.length > 0
|
|
167
|
+
) {
|
|
168
|
+
updateSliceMockSuccess({
|
|
169
|
+
libraryID: library,
|
|
170
|
+
sliceID: slice.model.id,
|
|
171
|
+
mocks: slice.mocks,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
syncChanges();
|
|
177
|
+
|
|
178
|
+
onSuccess({ slices, library });
|
|
179
|
+
|
|
180
|
+
setIsCreatingSlices(false);
|
|
181
|
+
id.current = crypto.randomUUID();
|
|
182
|
+
resetSlices();
|
|
183
|
+
|
|
184
|
+
void completeStep("createSlice");
|
|
185
|
+
|
|
186
|
+
for (const { model } of slices) {
|
|
187
|
+
void telemetry.track({
|
|
188
|
+
event: "slice:created",
|
|
189
|
+
id: model.id,
|
|
190
|
+
name: model.name,
|
|
191
|
+
library,
|
|
192
|
+
location,
|
|
193
|
+
mode: "import",
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
.catch(() => {
|
|
198
|
+
if (currentId !== id.current) return;
|
|
199
|
+
setIsCreatingSlices(false);
|
|
200
|
+
toast.error("An unexpected error happened while adding slices.");
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const selectedSlices = slices.filter((slice) =>
|
|
205
|
+
selectedSliceIds.has(slice.model.id),
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const allSelected =
|
|
209
|
+
slices.length > 0 && selectedSliceIds.size === slices.length;
|
|
210
|
+
const someSelected =
|
|
211
|
+
selectedSliceIds.size > 0 && selectedSliceIds.size < slices.length;
|
|
212
|
+
|
|
213
|
+
const handleSelectAll = (selected: boolean) => {
|
|
214
|
+
if (selected) {
|
|
215
|
+
const allSliceIds = new Set<string>();
|
|
216
|
+
for (const slice of slices) {
|
|
217
|
+
allSliceIds.add(slice.model.id);
|
|
218
|
+
}
|
|
219
|
+
setSelectedSliceIds(allSliceIds);
|
|
220
|
+
} else {
|
|
221
|
+
setSelectedSliceIds(new Set());
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
let selectAllLabel = "Select all slices";
|
|
226
|
+
if (allSelected) {
|
|
227
|
+
selectAllLabel = `Selected all slices (${selectedSliceIds.size})`;
|
|
228
|
+
} else if (someSelected) {
|
|
229
|
+
selectAllLabel = `${selectedSliceIds.size} of ${slices.length} selected`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
234
|
+
<DialogHeader title="Import slices from library" />
|
|
235
|
+
<DialogContent gap={0}>
|
|
236
|
+
<DialogDescription hidden>
|
|
237
|
+
Import slices from a github repository
|
|
238
|
+
</DialogDescription>
|
|
239
|
+
{slices.length === 0 ? (
|
|
240
|
+
<Box padding={16} height="100%" flexDirection="column" gap={16}>
|
|
241
|
+
<Box flexDirection="column" gap={8}>
|
|
242
|
+
<Box
|
|
243
|
+
display="flex"
|
|
244
|
+
flexDirection="column"
|
|
245
|
+
gap={8}
|
|
246
|
+
padding={16}
|
|
247
|
+
border
|
|
248
|
+
borderRadius={8}
|
|
249
|
+
>
|
|
250
|
+
<Text color="grey11">Import from GitHub</Text>
|
|
251
|
+
<TextInput
|
|
252
|
+
placeholder="https://github.com/username/repository"
|
|
253
|
+
value={githubUrl}
|
|
254
|
+
onValueChange={setGithubUrl}
|
|
255
|
+
/>
|
|
256
|
+
<Button
|
|
257
|
+
onClick={() => void handleImportFromGithub(githubUrl)}
|
|
258
|
+
disabled={!githubUrl.trim() || isLoadingSlices}
|
|
259
|
+
loading={isLoadingSlices}
|
|
260
|
+
color="purple"
|
|
261
|
+
>
|
|
262
|
+
{isLoadingSlices ? "Loading slices..." : "Import from GitHub"}
|
|
263
|
+
</Button>
|
|
264
|
+
</Box>
|
|
265
|
+
</Box>
|
|
266
|
+
</Box>
|
|
267
|
+
) : (
|
|
268
|
+
<>
|
|
269
|
+
<Box
|
|
270
|
+
display="flex"
|
|
271
|
+
alignItems="center"
|
|
272
|
+
justifyContent="space-between"
|
|
273
|
+
padding={16}
|
|
274
|
+
border={{ bottom: true }}
|
|
275
|
+
>
|
|
276
|
+
<Box display="flex" alignItems="center" gap={8}>
|
|
277
|
+
<InlineLabel value={selectAllLabel}>
|
|
278
|
+
<Checkbox
|
|
279
|
+
checked={allSelected}
|
|
280
|
+
indeterminate={someSelected}
|
|
281
|
+
onCheckedChange={handleSelectAll}
|
|
282
|
+
/>
|
|
283
|
+
</InlineLabel>
|
|
284
|
+
</Box>
|
|
285
|
+
</Box>
|
|
286
|
+
<ScrollArea stableScrollbar={false}>
|
|
287
|
+
<Box
|
|
288
|
+
display="grid"
|
|
289
|
+
gridTemplateColumns="1fr 1fr 1fr"
|
|
290
|
+
gap={16}
|
|
291
|
+
padding={16}
|
|
292
|
+
>
|
|
293
|
+
{slices.map((slice) => {
|
|
294
|
+
return (
|
|
295
|
+
<SliceCard
|
|
296
|
+
model={slice.model}
|
|
297
|
+
thumbnailUrl={slice.thumbnailUrl}
|
|
298
|
+
key={slice.model.id}
|
|
299
|
+
selected={selectedSliceIds.has(slice.model.id)}
|
|
300
|
+
onSelectedChange={(selected) => {
|
|
301
|
+
if (selected) {
|
|
302
|
+
setSelectedSliceIds((prev) => {
|
|
303
|
+
const next = new Set(prev);
|
|
304
|
+
next.add(slice.model.id);
|
|
305
|
+
return next;
|
|
306
|
+
});
|
|
307
|
+
} else {
|
|
308
|
+
setSelectedSliceIds((prev) => {
|
|
309
|
+
const next = new Set(prev);
|
|
310
|
+
next.delete(slice.model.id);
|
|
311
|
+
return next;
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}}
|
|
315
|
+
/>
|
|
316
|
+
);
|
|
317
|
+
})}
|
|
318
|
+
</Box>
|
|
319
|
+
</ScrollArea>
|
|
320
|
+
</>
|
|
321
|
+
)}
|
|
322
|
+
|
|
323
|
+
<DialogActions>
|
|
324
|
+
<DialogCancelButton disabled={isCreatingSlices} />
|
|
325
|
+
<DialogActionButton
|
|
326
|
+
disabled={selectedSlices.length === 0}
|
|
327
|
+
loading={isCreatingSlices}
|
|
328
|
+
onClick={onSubmit}
|
|
329
|
+
>
|
|
330
|
+
{getSubmitButtonLabel(location)} ({selectedSlices.length})
|
|
331
|
+
</DialogActionButton>
|
|
332
|
+
</DialogActions>
|
|
333
|
+
</DialogContent>
|
|
334
|
+
</Dialog>
|
|
335
|
+
);
|
|
336
|
+
}
|
package/src/features/customTypes/customTypesBuilder/ImportSlicesFromLibraryModal/SliceCard.tsx
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Checkbox } from "@prismicio/editor-ui";
|
|
2
|
+
import { SharedSlice } from "@prismicio/types-internal/lib/customtypes";
|
|
3
|
+
|
|
4
|
+
import { Card, CardFooter, CardMedia } from "@/components/Card";
|
|
5
|
+
|
|
6
|
+
interface SliceCardProps {
|
|
7
|
+
thumbnailUrl?: string;
|
|
8
|
+
model: SharedSlice;
|
|
9
|
+
selected: boolean;
|
|
10
|
+
onSelectedChange: (selected: boolean) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function SliceCard(props: SliceCardProps) {
|
|
14
|
+
const { thumbnailUrl, model, selected = true, onSelectedChange } = props;
|
|
15
|
+
|
|
16
|
+
const handleClick = () => {
|
|
17
|
+
onSelectedChange(!selected);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const cardContent = (
|
|
21
|
+
<>
|
|
22
|
+
{thumbnailUrl !== undefined && thumbnailUrl ? (
|
|
23
|
+
<CardMedia src={thumbnailUrl} />
|
|
24
|
+
) : (
|
|
25
|
+
<CardMedia component="div" />
|
|
26
|
+
)}
|
|
27
|
+
<CardFooter
|
|
28
|
+
title={model.name}
|
|
29
|
+
action={
|
|
30
|
+
<div onClick={(event) => event.stopPropagation()}>
|
|
31
|
+
<Checkbox checked={selected} onCheckedChange={onSelectedChange} />
|
|
32
|
+
</div>
|
|
33
|
+
}
|
|
34
|
+
/>
|
|
35
|
+
</>
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Card
|
|
40
|
+
interactive={true}
|
|
41
|
+
onClick={handleClick}
|
|
42
|
+
checked={selected}
|
|
43
|
+
size="small"
|
|
44
|
+
>
|
|
45
|
+
{cardContent}
|
|
46
|
+
</Card>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { toast } from "react-toastify";
|
|
3
|
+
|
|
4
|
+
import { SliceImport } from "../types";
|
|
5
|
+
import {
|
|
6
|
+
fetchSlicesFromLibraries,
|
|
7
|
+
getDefaultBranch,
|
|
8
|
+
getSliceLibraries,
|
|
9
|
+
parseGithubUrl,
|
|
10
|
+
} from "../utils/github";
|
|
11
|
+
|
|
12
|
+
export function useImportSlicesFromGithub() {
|
|
13
|
+
const [isLoadingSlices, setIsLoadingSlices] = useState(false);
|
|
14
|
+
const [slices, setSlices] = useState<SliceImport[]>([]);
|
|
15
|
+
|
|
16
|
+
const resetSlices = () => {
|
|
17
|
+
setSlices([]);
|
|
18
|
+
setIsLoadingSlices(false);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const handleImportFromGithub = async (githubUrl: string) => {
|
|
22
|
+
try {
|
|
23
|
+
setIsLoadingSlices(true);
|
|
24
|
+
|
|
25
|
+
const { owner, repo } = parseGithubUrl(githubUrl);
|
|
26
|
+
if (!owner || !repo) {
|
|
27
|
+
toast.error("Invalid GitHub URL format");
|
|
28
|
+
setIsLoadingSlices(false);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const branch = await getDefaultBranch({ owner, repo });
|
|
33
|
+
|
|
34
|
+
let libraries: string[] | undefined;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
libraries = await getSliceLibraries({ owner, repo, branch });
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error("Failed to fetch slicemachine.config.json:", error);
|
|
40
|
+
toast.error(
|
|
41
|
+
`Failed to fetch slicemachine.config.json: ${
|
|
42
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
43
|
+
}`,
|
|
44
|
+
);
|
|
45
|
+
setIsLoadingSlices(false);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (libraries.length === 0) {
|
|
50
|
+
console.warn("No libraries were found in the SM config.");
|
|
51
|
+
setIsLoadingSlices(false);
|
|
52
|
+
toast.error("No libraries were found in the SM config.");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const fetchedSlices = await fetchSlicesFromLibraries({
|
|
57
|
+
owner,
|
|
58
|
+
repo,
|
|
59
|
+
branch,
|
|
60
|
+
libraries,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (fetchedSlices.length === 0) {
|
|
64
|
+
toast.error("Error fetching slices from the repository");
|
|
65
|
+
setIsLoadingSlices(false);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
setSlices(fetchedSlices);
|
|
70
|
+
setIsLoadingSlices(false);
|
|
71
|
+
toast.success(
|
|
72
|
+
`Found ${fetchedSlices.length} slice(s) from ${libraries.length} library/libraries`,
|
|
73
|
+
);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error("Error importing from GitHub:", error);
|
|
76
|
+
toast.error(
|
|
77
|
+
`Error importing from GitHub: ${
|
|
78
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
79
|
+
}`,
|
|
80
|
+
);
|
|
81
|
+
setIsLoadingSlices(false);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
isLoadingSlices,
|
|
87
|
+
slices,
|
|
88
|
+
resetSlices,
|
|
89
|
+
handleImportFromGithub,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ImportSlicesFromLibraryModal } from "./ImportSlicesFromLibraryModal";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { SharedSliceContent } from "@prismicio/types-internal/lib/content";
|
|
2
|
+
import { SharedSlice } from "@prismicio/types-internal/lib/customtypes";
|
|
3
|
+
|
|
4
|
+
export type SliceImport = {
|
|
5
|
+
image: File;
|
|
6
|
+
model: SharedSlice;
|
|
7
|
+
thumbnailUrl?: string;
|
|
8
|
+
files?: SliceFile[];
|
|
9
|
+
componentContents?: string;
|
|
10
|
+
mocks?: SharedSliceContent[];
|
|
11
|
+
screenshots?: Record<string, File>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type SliceFile = {
|
|
15
|
+
path: string;
|
|
16
|
+
contents: string | File;
|
|
17
|
+
isBinary: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type NewSlice = {
|
|
21
|
+
image: File;
|
|
22
|
+
model: SharedSlice;
|
|
23
|
+
langSmithUrl?: string;
|
|
24
|
+
files?: SliceFile[];
|
|
25
|
+
componentContents?: string;
|
|
26
|
+
mocks?: SharedSliceContent[];
|
|
27
|
+
screenshots?: Record<string, File>;
|
|
28
|
+
};
|
package/src/features/customTypes/customTypesBuilder/ImportSlicesFromLibraryModal/utils/addSlices.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { managerClient } from "@/managerClient";
|
|
2
|
+
|
|
3
|
+
import { NewSlice, SliceFile } from "../types";
|
|
4
|
+
import { mapWithConcurrency } from "./mapWithConcurrency";
|
|
5
|
+
|
|
6
|
+
export async function addSlices(newSlices: NewSlice[]) {
|
|
7
|
+
// use the first library
|
|
8
|
+
const { libraries = [] } =
|
|
9
|
+
await managerClient.project.getSliceMachineConfig();
|
|
10
|
+
const library = libraries[0];
|
|
11
|
+
if (!library) {
|
|
12
|
+
throw new Error("No library found in the config.");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Create slices with bounded concurrency
|
|
16
|
+
await mapWithConcurrency(newSlices, 3, async (slice: NewSlice) => {
|
|
17
|
+
const { errors } = await managerClient.slices.createSlice({
|
|
18
|
+
libraryID: library,
|
|
19
|
+
model: slice.model,
|
|
20
|
+
componentContents: slice.componentContents,
|
|
21
|
+
});
|
|
22
|
+
if (errors.length) {
|
|
23
|
+
throw new Error(`Failed to create slice ${slice.model.id}.`);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Update mocks and screenshots, and write additional files
|
|
28
|
+
const slices = await mapWithConcurrency(
|
|
29
|
+
newSlices,
|
|
30
|
+
3,
|
|
31
|
+
async (slice: NewSlice) => {
|
|
32
|
+
const { model, image, langSmithUrl, mocks, files, screenshots } = slice;
|
|
33
|
+
|
|
34
|
+
// Update mocks if available
|
|
35
|
+
if (mocks && Array.isArray(mocks) && mocks.length > 0) {
|
|
36
|
+
const { errors: mocksErrors } =
|
|
37
|
+
await managerClient.slices.updateSliceMocks({
|
|
38
|
+
libraryID: library,
|
|
39
|
+
sliceID: model.id,
|
|
40
|
+
mocks,
|
|
41
|
+
});
|
|
42
|
+
if (mocksErrors.length) {
|
|
43
|
+
console.warn(
|
|
44
|
+
`Failed to update mocks for slice ${model.id}:`,
|
|
45
|
+
mocksErrors,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Update screenshots for all variations
|
|
51
|
+
if (screenshots && Object.keys(screenshots).length > 0) {
|
|
52
|
+
await Promise.all(
|
|
53
|
+
Object.entries(screenshots).map(
|
|
54
|
+
async ([variationID, screenshotFile]) => {
|
|
55
|
+
if (screenshotFile.size > 0) {
|
|
56
|
+
await managerClient.slices.updateSliceScreenshot({
|
|
57
|
+
libraryID: library,
|
|
58
|
+
sliceID: model.id,
|
|
59
|
+
variationID,
|
|
60
|
+
data: screenshotFile,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
),
|
|
65
|
+
);
|
|
66
|
+
} else if (
|
|
67
|
+
image.size > 0 &&
|
|
68
|
+
model.variations !== undefined &&
|
|
69
|
+
model.variations.length > 0
|
|
70
|
+
) {
|
|
71
|
+
// Fallback to using the first image if no screenshots were provided
|
|
72
|
+
await managerClient.slices.updateSliceScreenshot({
|
|
73
|
+
libraryID: library,
|
|
74
|
+
sliceID: model.id,
|
|
75
|
+
variationID: model.variations[0].id,
|
|
76
|
+
data: image,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Write additional files (CSS, other assets, etc.)
|
|
81
|
+
console.log(
|
|
82
|
+
`About to write files for slice ${model.id}:`,
|
|
83
|
+
files
|
|
84
|
+
? `${files.length} file(s) - ${files
|
|
85
|
+
.map((f: SliceFile) => f.path)
|
|
86
|
+
.join(", ")}`
|
|
87
|
+
: "no files",
|
|
88
|
+
);
|
|
89
|
+
if (files && files.length > 0) {
|
|
90
|
+
await writeSliceFiles({
|
|
91
|
+
libraryID: library,
|
|
92
|
+
sliceID: model.id,
|
|
93
|
+
files,
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
console.warn(`No files to write for slice ${model.id}. Files:`, files);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { model, langSmithUrl };
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return { library, slices };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Writes additional slice files to the filesystem using the manager's plugin runner
|
|
108
|
+
* Uses the manager client's RPC interface to write files
|
|
109
|
+
*/
|
|
110
|
+
async function writeSliceFiles(args: {
|
|
111
|
+
libraryID: string;
|
|
112
|
+
sliceID: string;
|
|
113
|
+
files: SliceFile[];
|
|
114
|
+
}): Promise<void> {
|
|
115
|
+
const { libraryID, sliceID, files } = args;
|
|
116
|
+
|
|
117
|
+
if (files.length === 0) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Filter out files that are already handled by other methods
|
|
122
|
+
// Note: We still write ALL files, but skip ones that are handled by createSlice/updateSliceMocks/updateSliceScreenshot
|
|
123
|
+
// This includes component files in subdirectories, CSS files, assets, etc.
|
|
124
|
+
const filesToWrite = files.filter(
|
|
125
|
+
(file) =>
|
|
126
|
+
// Skip mocks.json (handled by updateSliceMocks)
|
|
127
|
+
file.path !== "mocks.json" &&
|
|
128
|
+
// Skip screenshots (handled by updateSliceScreenshot)
|
|
129
|
+
!file.path.startsWith("screenshot-") &&
|
|
130
|
+
// Skip the main index component file (handled by createSlice with componentContents)
|
|
131
|
+
// But allow component files in subdirectories (e.g., components/Button.tsx)
|
|
132
|
+
!file.path.match(/^index\.(tsx?|jsx?|vue|svelte)$/),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
console.log(
|
|
136
|
+
`Writing ${filesToWrite.length} additional file(s) for slice ${sliceID}:`,
|
|
137
|
+
filesToWrite.map((f) => f.path),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (filesToWrite.length === 0) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const filesForRPC = filesToWrite.map((file) => {
|
|
146
|
+
if (file.isBinary) {
|
|
147
|
+
return {
|
|
148
|
+
path: file.path,
|
|
149
|
+
contents: file.contents,
|
|
150
|
+
isBinary: true,
|
|
151
|
+
};
|
|
152
|
+
} else if (typeof file.contents === "string") {
|
|
153
|
+
return {
|
|
154
|
+
path: file.path,
|
|
155
|
+
contents: file.contents,
|
|
156
|
+
isBinary: false,
|
|
157
|
+
};
|
|
158
|
+
} else {
|
|
159
|
+
throw new Error(`Unexpected file contents type for ${file.path}`);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const result = await managerClient.slices.writeSliceFiles({
|
|
164
|
+
libraryID,
|
|
165
|
+
sliceID,
|
|
166
|
+
files: filesForRPC,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
if (result.errors.length > 0) {
|
|
170
|
+
console.error(
|
|
171
|
+
`Errors writing files for slice ${sliceID}:`,
|
|
172
|
+
result.errors.map((e: { message?: string }) => e.message ?? String(e)),
|
|
173
|
+
);
|
|
174
|
+
} else {
|
|
175
|
+
console.log(
|
|
176
|
+
`Successfully wrote ${filesToWrite.length} file(s) for slice ${sliceID}`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error(
|
|
181
|
+
`Error writing files for slice ${sliceID}:`,
|
|
182
|
+
error instanceof Error ? error.message : String(error),
|
|
183
|
+
);
|
|
184
|
+
// Don't throw - allow slice creation to succeed even if some files fail
|
|
185
|
+
}
|
|
186
|
+
}
|