@@ -17,7 +17,7 @@ import {
1717 ScrollArea ,
1818} from "@prismicio/editor-ui" ;
1919import { SharedSlice } from "@prismicio/types-internal/lib/customtypes" ;
20- import { useRef , useState } from "react" ;
20+ import { useEffect , useRef , useState } from "react" ;
2121import { toast } from "react-toastify" ;
2222
2323import { getState , telemetry } from "@/apiClient" ;
@@ -70,8 +70,8 @@ export function CreateSliceFromImageModal(
7070
7171 const onOpenChange = ( open : boolean ) => {
7272 if ( open || isCreatingSlices ) return ;
73- id . current = crypto . randomUUID ( ) ;
7473 onClose ( ) ;
74+ id . current = crypto . randomUUID ( ) ;
7575 setSlices ( [ ] ) ;
7676 } ;
7777
@@ -124,6 +124,8 @@ export function CreateSliceFromImageModal(
124124 ) ;
125125 } ;
126126
127+ const existingSlices = useExistingSlices ( { open } ) ;
128+
127129 const inferSlice = ( args : { index : number ; imageUrl : string } ) => {
128130 const { index, imageUrl } = args ;
129131 const currentId = id . current ;
@@ -140,16 +142,24 @@ export function CreateSliceFromImageModal(
140142 managerClient . customTypes . inferSlice ( { imageUrl } ) . then (
141143 ( { slice, langSmithUrl } ) => {
142144 if ( currentId !== id . current ) return ;
143- setSlice ( {
144- index,
145- slice : ( prevSlice ) => ( {
146- ...prevSlice ,
147- status : "success" ,
148- thumbnailUrl : imageUrl ,
149- model : slice ,
150- langSmithUrl,
151- } ) ,
152- } ) ;
145+
146+ setSlices ( ( prevSlices ) =>
147+ prevSlices . map ( ( prevSlice , i ) =>
148+ i === index
149+ ? {
150+ ...prevSlice ,
151+ status : "success" ,
152+ thumbnailUrl : imageUrl ,
153+ model : sliceWithoutConflicts ( {
154+ existingSlices : existingSlices . current ,
155+ newSlices : prevSlices ,
156+ slice,
157+ } ) ,
158+ langSmithUrl,
159+ }
160+ : prevSlice ,
161+ ) ,
162+ ) ;
153163 } ,
154164 ( ) => {
155165 if ( currentId !== id . current ) return ;
@@ -178,15 +188,19 @@ export function CreateSliceFromImageModal(
178188 addSlices ( newSlices )
179189 . then ( async ( { slices, library } ) => {
180190 if ( currentId !== id . current ) return ;
181- id . current = crypto . randomUUID ( ) ;
191+
182192 const serverState = await getState ( ) ;
183193 createSliceSuccess ( serverState . libraries ) ;
184- onSuccess ( { slices, library } ) ;
185- void completeStep ( "createSlice" ) ;
186194 syncChanges ( ) ;
195+
196+ onSuccess ( { slices, library } ) ;
197+
187198 setIsCreatingSlices ( false ) ;
199+ id . current = crypto . randomUUID ( ) ;
188200 setSlices ( [ ] ) ;
189201
202+ void completeStep ( "createSlice" ) ;
203+
190204 for ( const { model, langSmithUrl } of slices ) {
191205 void telemetry . track ( {
192206 event : "slice:created" ,
@@ -337,6 +351,77 @@ type NewSlice = {
337351 langSmithUrl ?: string ;
338352} ;
339353
354+ /**
355+ * Keeps track of the existing slices in the project.
356+ * Re-fetches them when the modal is opened.
357+ */
358+ function useExistingSlices ( { open } : { open : boolean } ) {
359+ const ref = useRef < SharedSlice [ ] > ( [ ] ) ;
360+
361+ useEffect ( ( ) => {
362+ if ( ! open ) return ;
363+
364+ ref . current = [ ] ;
365+ managerClient . slices
366+ . readAllSlices ( )
367+ . then ( ( slices ) => {
368+ ref . current = slices . models . map ( ( { model } ) => model ) ;
369+ } )
370+ . catch ( ( ) => null ) ;
371+ } , [ open ] ) ;
372+
373+ return ref ;
374+ }
375+
376+ /**
377+ * If needed, assigns new ids and names to avoid conflicts with existing slices.
378+ * Names are compared case-insensitively to avoid conflicts
379+ * between folder names with different casing.
380+ */
381+ function sliceWithoutConflicts ( {
382+ existingSlices,
383+ newSlices,
384+ slice,
385+ } : {
386+ existingSlices : SharedSlice [ ] ;
387+ newSlices : Slice [ ] ;
388+ slice : SharedSlice ;
389+ } ) : SharedSlice {
390+ const existingIds = new Set < string > ( ) ;
391+ const existingNames = new Set < string > ( ) ;
392+
393+ for ( const { id, name } of existingSlices ) {
394+ existingIds . add ( id ) ;
395+ existingNames . add ( name . toLowerCase ( ) ) ;
396+ }
397+
398+ for ( const slice of newSlices ) {
399+ if ( slice . status !== "success" ) continue ;
400+ existingIds . add ( slice . model . id ) ;
401+ existingNames . add ( slice . model . name . toLowerCase ( ) ) ;
402+ }
403+
404+ let id = slice . id ;
405+ let counter = 2 ;
406+ while ( existingIds . has ( id ) ) {
407+ id = `${ slice . id } _${ counter } ` ;
408+ counter ++ ;
409+ }
410+
411+ let name = slice . name ;
412+ counter = 2 ;
413+ while ( existingNames . has ( name . toLowerCase ( ) ) ) {
414+ name = `${ slice . name } ${ counter } ` ;
415+ counter ++ ;
416+ }
417+
418+ return {
419+ ...slice ,
420+ id,
421+ name,
422+ } ;
423+ }
424+
340425async function addSlices ( newSlices : NewSlice [ ] ) {
341426 // use the first library
342427 const { libraries = [ ] } =
@@ -346,24 +431,28 @@ async function addSlices(newSlices: NewSlice[]) {
346431 throw new Error ( "No library found in the config." ) ;
347432 }
348433
349- // add the slices computing new ids/names if needed
350- const models = await managerClient . slices . addSlices ( {
351- library,
352- models : newSlices . map ( ( slice ) => slice . model ) ,
353- } ) ;
434+ for ( const { model } of newSlices ) {
435+ const { errors } = await managerClient . slices . createSlice ( {
436+ libraryID : library ,
437+ model,
438+ } ) ;
439+ if ( errors . length ) {
440+ throw new Error ( `Failed to create slice ${ model . id } .` ) ;
441+ }
442+ }
354443
355444 // for each added slice, set the variation screenshot
356445 const slices = await Promise . all (
357- models . map ( async ( model , index ) => {
446+ newSlices . map ( async ( { model, image , langSmithUrl } ) => {
358447 await managerClient . slices . updateSliceScreenshot ( {
359448 libraryID : library ,
360449 sliceID : model . id ,
361450 variationID : model . variations [ 0 ] . id ,
362- data : newSlices [ index ] . image ,
451+ data : image ,
363452 } ) ;
364453 return {
365454 model,
366- langSmithUrl : newSlices [ index ] . langSmithUrl ,
455+ langSmithUrl,
367456 } ;
368457 } ) ,
369458 ) ;
0 commit comments