@@ -17,6 +17,8 @@ import { useAnnotationShapes } from "../annotation/use-shapes"
1717import { CloseIcon } from "../annotation/icons"
1818import { PrimaryButton , SecondaryButton , StepIndicator } from "./controls"
1919import { theme } from "./theme"
20+ import { pickFiles } from "../capture/file-picker"
21+ import { DEFAULT_ATTACHMENT_LIMITS , validateAttachments , type Attachment } from "@reprojs/sdk-utils"
2022
2123export interface WizardArgs {
2224 initialTitle ?: string
@@ -27,6 +29,7 @@ export interface WizardArgs {
2729 description : string
2830 annotatedUri : string | null
2931 rawUri : string | null
32+ attachments : Attachment [ ]
3033 } ) => Promise < void >
3134 onClose : ( ) => void
3235}
@@ -48,6 +51,8 @@ export function WizardSheet({
4851 const [ submitting , setSubmitting ] = useState ( false )
4952 const [ error , setError ] = useState < string | null > ( null )
5053 const [ annotateSize , setAnnotateSize ] = useState ( { w : 0 , h : 0 } )
54+ const [ attachments , setAttachments ] = useState < Attachment [ ] > ( [ ] )
55+ const [ attachmentErrors , setAttachmentErrors ] = useState < string [ ] > ( [ ] )
5156 const store = useRef ( createAnnotationStore ( ) ) . current
5257 const flattenRef = useRef < FlattenHandle | null > ( null )
5358
@@ -58,6 +63,35 @@ export function WizardSheet({
5863 setStep ( "form" )
5964 } , [ screenshot ] )
6065
66+ async function handleAttachmentsAdd ( ) {
67+ const picked = await pickFiles ( { multiple : true } )
68+ if ( picked . length === 0 ) return
69+ // Convert picker output → File[] for validateAttachments. Each picked
70+ // Attachment already has size/mime/filename; we mirror them onto a stub
71+ // File so the validator's File contract type-checks. Override size since
72+ // the picker already told us the real value.
73+ const asFiles : File [ ] = picked . map ( ( a ) => {
74+ const f = new File ( [ new Uint8Array ( 0 ) ] , a . filename , { type : a . mime } )
75+ Object . defineProperty ( f , "size" , { value : a . size , configurable : true } )
76+ return f
77+ } )
78+ const result = validateAttachments ( asFiles , attachments , DEFAULT_ATTACHMENT_LIMITS )
79+ // Reattach picker uri/blob to the validated Attachments (validateAttachments
80+ // wraps the File reference in `blob`, which on RN is a stub — replace with
81+ // the picker's previewUrl + blob).
82+ const accepted = result . accepted . map ( ( a ) => {
83+ const original = picked . find ( ( p ) => p . filename === a . filename )
84+ if ( ! original ) return a
85+ return { ...a , blob : original . blob , previewUrl : original . previewUrl }
86+ } )
87+ setAttachments ( ( prev ) => [ ...prev , ...accepted ] )
88+ setAttachmentErrors ( result . rejected . map ( ( r ) => `${ r . filename } : ${ r . reason . replace ( "-" , " " ) } ` ) )
89+ }
90+
91+ function handleAttachmentRemove ( id : string ) {
92+ setAttachments ( ( prev ) => prev . filter ( ( a ) => a . id !== id ) )
93+ }
94+
6195 async function handleSubmit ( ) {
6296 setSubmitting ( true )
6397 setError ( null )
@@ -82,6 +116,7 @@ export function WizardSheet({
82116 description,
83117 annotatedUri : annotated ,
84118 rawUri : screenshot ?. uri ?? null ,
119+ attachments,
85120 } )
86121 } catch ( e ) {
87122 setError ( ( e as Error ) . message )
@@ -122,8 +157,11 @@ export function WizardSheet({
122157 }
123158 lines . push ( { label : "Console, network & breadcrumbs" } )
124159 lines . push ( { label : "Device & environment info" } )
160+ if ( attachments . length > 0 ) {
161+ lines . push ( { label : "Additional attachments" , hint : String ( attachments . length ) } )
162+ }
125163 return lines
126- } , [ title , screenshot , shapes . length ] )
164+ } , [ title , screenshot , shapes . length , attachments . length ] )
127165
128166 return (
129167 < Modal visible animationType = "slide" onRequestClose = { onClose } presentationStyle = "pageSheet" >
@@ -191,8 +229,12 @@ export function WizardSheet({
191229 < StepForm
192230 title = { title }
193231 description = { description }
232+ attachments = { attachments }
233+ attachmentErrors = { attachmentErrors }
194234 onTitleChange = { setTitle }
195235 onDescriptionChange = { setDescription }
236+ onAttachmentsAdd = { handleAttachmentsAdd }
237+ onAttachmentRemove = { handleAttachmentRemove }
196238 />
197239 ) }
198240 { step === "annotate" && (
0 commit comments