22 which stores the image (mediaUrl) and returns issue-type suggestions for step 2. Optional;
33 Skip/Next both advance. -->
44<script setup lang="ts">
5- import { computed , ref } from ' vue'
5+ import { computed , ref , useId } from ' vue'
66import { processForClassify } from ' @/utils/photo'
77import { useApi } from ' @/composables/useApi'
88import { useReportSubmissionStore } from ' @/stores/reportSubmission'
@@ -16,8 +16,10 @@ interface ClassifyResponse {
1616const store = useReportSubmissionStore ()
1717useWizardValidity (computed (() => true )) // the step is optional
1818
19+ const waitingImageUpload = ref (true )
1920const classifying = ref (false )
2021const errorMessage = ref (' ' )
22+ const imageId = useId ()
2123
2224// classifyBody.imgB64 is mutated before each fetchData() call; useApi reads
2325// opts.body lazily so the latest value is always sent.
@@ -28,25 +30,23 @@ const classify = useApi<ClassifyResponse>({
2830 body: classifyBody ,
2931})
3032
31- // createObjectURL produces a short-lived preview URL; the browser holds
32- // the underlying blob alive until revokeObjectURL is called or page unloads.
33- function makePreview(file : File ): string | undefined {
34- return typeof URL .createObjectURL === ' function' ? URL .createObjectURL (file ) : undefined
35- }
36-
3733async function onFile(e : Event ) {
3834 if (classifying .value ) return
39- const file = (e .target as HTMLInputElement ).files ?.[0 ]
35+ const target = e .target as HTMLInputElement
36+ const file = target .files ?.[0 ]
4037 if (! file ) return
38+ waitingImageUpload .value = false
4139 classifying .value = true
4240 errorMessage .value = ' '
43- const previewUrl = makePreview (file )
41+ const previewUrl = URL .createObjectURL (file )
42+ const imgElement = document .getElementById (imageId ) as HTMLImageElement
43+ imgElement .src = previewUrl
4444 try {
4545 classifyBody .imgB64 = await processForClassify (file )
4646 const result = await classify .fetchData ()
4747 if (! result || classify .error .value ) {
4848 errorMessage .value = classify .error .value ?.message || ' Classification failed.'
49- if (previewUrl ) URL .revokeObjectURL ?. (previewUrl )
49+ if (previewUrl ) URL .revokeObjectURL (previewUrl )
5050 return
5151 }
5252 store .setPhoto ({ mediaUrl: result .imageUrl , previewUrl })
@@ -55,10 +55,10 @@ async function onFile(e: Event) {
5555 )
5656 } catch (err ) {
5757 errorMessage .value = (err as Error ).message || ' Photo processing failed.'
58- if (previewUrl ) URL .revokeObjectURL ?. (previewUrl )
58+ if (previewUrl ) URL .revokeObjectURL (previewUrl )
5959 } finally {
6060 classifying .value = false
61- ;( e . target as HTMLInputElement ) .value = ' '
61+ target .value = ' '
6262 }
6363}
6464 </script >
@@ -71,52 +71,47 @@ async function onFile(e: Event) {
7171 type to report. Do not upload any images with personal or sensitive information.
7272 </p >
7373 <p class =" image-step__count" >{{ store.photo ? '1/1' : '0/1' }}</p >
74-
7574 <div class =" image-step__zones" >
7675 <label class =" image-step__zone" >
77- <span class =" image-step__zone-label" >Upload</span >
78- <input type =" file" accept =" image/*" :disabled =" classifying" @change =" onFile" />
79- </label >
80- <label class =" image-step__zone" >
81- <span class =" image-step__zone-label" >Camera</span >
82- <input
83- type =" file"
84- accept =" image/*"
85- capture =" environment"
86- :disabled =" classifying"
87- @change =" onFile"
88- />
76+ <span v-if =" waitingImageUpload" class =" image-step__zone-label" >Upload</span >
77+ <img :id =" imageId" alt =" Upload" style =" display : none " onload =" this.style.display = ''" />
78+ <input type =" file" accept =" image/*" @change =" onFile" />
8979 </label >
9080 </div >
9181
9282 <div role =" status" >
9383 <p v-if =" classifying" class =" image-step__status" >Analyzing your photo…</p >
94- <p v-if =" store.photo" class =" image-step__status" >Photo added.</p >
84+ <p v-else-if =" store.photo" class =" image-step__status" >Photo added.</p >
85+ <p v-else-if =" errorMessage" role =" alert" class =" image-step__error" >{{ errorMessage }}</p >
9586 </div >
96- <p v-if =" errorMessage" role =" alert" class =" image-step__error" >{{ errorMessage }}</p >
9787 </div >
9888</template >
9989
10090<style scoped>
10191.image-step {
10292 max-width : 640px ;
10393}
94+
10495.image-step__title {
10596 font-size : 1.25rem ;
10697 font-weight : 700 ;
10798 margin : 0 0 var (--spacing-s , 0.5rem );
10899}
100+
109101.image-step__note {
110102 color : var (--Schemes-On-Surface-Variant , #4a4a4a );
111103 margin : 0 0 var (--spacing-s , 0.5rem );
112104}
105+
113106.image-step__count {
114107 margin : 0 0 var (--spacing-s , 0.5rem );
115108}
109+
116110.image-step__zones {
117111 display : flex ;
118112 gap : var (--spacing-m , 1rem );
119113}
114+
120115.image-step__zone {
121116 flex : 1 ;
122117 min-height : 180px ;
@@ -127,16 +122,19 @@ async function onFile(e: Event) {
127122 justify-content : center ;
128123 cursor : pointer ;
129124}
125+
130126.image-step__zone input {
131127 position : absolute ;
132128 width : 1px ;
133129 height : 1px ;
134130 opacity : 0 ;
135131}
132+
136133.image-step__zone :focus-within {
137134 outline : 2px solid var (--Schemes-Primary , #0f4d90 );
138135 outline-offset : 2px ;
139136}
137+
140138.image-step__error {
141139 color : var (--Schemes-Error , #c0392b );
142140}
0 commit comments