@@ -9,6 +9,42 @@ export type PendingLocalReferenceUpload = {
99} ;
1010
1111type UploadFileFromBrowserImpl = typeof uploadFileFromBrowser ;
12+ type FetchImpl = typeof fetch ;
13+
14+ export const isTransientCanvasUrl = ( url : string | null | undefined ) =>
15+ typeof url === 'string' &&
16+ ( url . startsWith ( 'blob:' ) || url . startsWith ( 'data:' ) ) ;
17+
18+ export const isLocalWorkspaceMediaUrl = ( url : string | null | undefined ) => {
19+ if ( ! url ) return false ;
20+ if ( isTransientCanvasUrl ( url ) ) return true ;
21+ if ( url . startsWith ( '/' ) && ! url . startsWith ( '//' ) ) return true ;
22+ try {
23+ const parsed = new URL ( url ) ;
24+ const origin =
25+ typeof window !== 'undefined' && window . location ?. origin
26+ ? window . location . origin
27+ : '' ;
28+ if ( origin ) return parsed . origin === origin ;
29+ return [ 'localhost' , '127.0.0.1' , '::1' ] . includes ( parsed . hostname ) ;
30+ } catch {
31+ return false ;
32+ }
33+ } ;
34+
35+ const needsJustInTimeUpload = ( {
36+ card,
37+ pendingUpload,
38+ } : {
39+ card : CanvasCard | undefined ;
40+ pendingUpload ?: PendingLocalReferenceUpload ;
41+ } ) => {
42+ if ( ! card || card . kind === 'generation' ) return false ;
43+ if ( pendingUpload && ( ! card . url || isTransientCanvasUrl ( card . url ) ) ) {
44+ return true ;
45+ }
46+ return Boolean ( card . url && isLocalWorkspaceMediaUrl ( card . url ) ) ;
47+ } ;
1248
1349export const getPendingDraftReferenceUploadCount = ( {
1450 draftCard,
@@ -24,22 +60,34 @@ export const getPendingDraftReferenceUploadCount = ({
2460 for ( const cardId of draftCard . referenceCardIds ) {
2561 if ( seenCardIds . has ( cardId ) ) continue ;
2662 seenCardIds . add ( cardId ) ;
27- const card = cardsById [ cardId ] ;
2863 if (
29- pendingUploadsByCardId [ cardId ] &&
30- card &&
31- card . kind !== 'generation' &&
32- ( ! card . url || isTransientCanvasUrl ( card . url ) )
64+ needsJustInTimeUpload ( {
65+ card : cardsById [ cardId ] ,
66+ pendingUpload : pendingUploadsByCardId [ cardId ] ,
67+ } )
3368 ) {
3469 count += 1 ;
3570 }
3671 }
3772 return count ;
3873} ;
3974
40- export const isTransientCanvasUrl = ( url : string | null | undefined ) =>
41- typeof url === 'string' &&
42- ( url . startsWith ( 'blob:' ) || url . startsWith ( 'data:' ) ) ;
75+ const fileFromWorkspaceUrl = async (
76+ url : string ,
77+ fetchImpl : FetchImpl
78+ ) : Promise < File > => {
79+ const response = await fetchImpl ( url ) ;
80+ if ( ! response . ok ) {
81+ throw new Error ( 'Failed to read a local canvas reference' ) ;
82+ }
83+ const blob = await response . blob ( ) ;
84+ const filename = decodeURIComponent (
85+ url . split ( '/' ) . pop ( ) ?. split ( '?' ) [ 0 ] || 'reference'
86+ ) ;
87+ return new File ( [ blob ] , filename , {
88+ type : blob . type || 'application/octet-stream' ,
89+ } ) ;
90+ } ;
4391
4492export const promotePendingDraftReferenceUploads = async ( {
4593 draftCard,
@@ -48,13 +96,15 @@ export const promotePendingDraftReferenceUploads = async ({
4896 projectId,
4997 generationIntentToken,
5098 uploadFileFromBrowserImpl = uploadFileFromBrowser ,
99+ fetchImpl = fetch ,
51100} : {
52101 draftCard : CanvasDraftCard ;
53102 cardsById : Record < string , CanvasCard > ;
54103 pendingUploadsByCardId : Record < string , PendingLocalReferenceUpload > ;
55104 projectId : string ;
56105 generationIntentToken : string ;
57106 uploadFileFromBrowserImpl ?: UploadFileFromBrowserImpl ;
107+ fetchImpl ?: FetchImpl ;
58108} ) : Promise <
59109 Array < {
60110 cardId : string ;
@@ -77,16 +127,22 @@ export const promotePendingDraftReferenceUploads = async ({
77127
78128 const pendingUpload = pendingUploadsByCardId [ cardId ] ;
79129 const card = cardsById [ cardId ] ;
80- if ( ! pendingUpload || ! card || card . kind === 'generation' ) {
130+ if (
131+ ! needsJustInTimeUpload ( {
132+ card,
133+ pendingUpload,
134+ } )
135+ ) {
81136 continue ;
82137 }
83138
84- if ( card . url && ! isTransientCanvasUrl ( card . url ) ) {
85- continue ;
86- }
139+ const file =
140+ pendingUpload && ( ! card . url || isTransientCanvasUrl ( card . url ) )
141+ ? pendingUpload . file
142+ : await fileFromWorkspaceUrl ( card . url as string , fetchImpl ) ;
87143
88144 const uploadResult = await uploadFileFromBrowserImpl (
89- pendingUpload . file ,
145+ file ,
90146 'beatcanvas/uploads' ,
91147 {
92148 projectId,
@@ -96,7 +152,7 @@ export const promotePendingDraftReferenceUploads = async ({
96152
97153 promotions . push ( {
98154 cardId,
99- objectUrl : pendingUpload . objectUrl ,
155+ objectUrl : pendingUpload ? .objectUrl || card . url || '' ,
100156 uploadResult,
101157 } ) ;
102158 }
0 commit comments