1+ <template >
2+   <!--  Popup Overlay --> 
3+   <div  class =" fixed inset-0 z-40 flex items-center justify-center bg-black/50" @click.self =" closePopup" 
4+     <div  class =" image-compare-container max-w-4xl max-h-[90vh] overflow-y-auto" 
5+       <!--  Close Button --> 
6+       <div  class =" flex justify-end mb-4" 
7+         <button  type =" button" 
8+             @click =" closePopup" 
9+             class =" text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white" 
10+             <svg  class =" w-3 h-3" aria-hidden =" true" xmlns =" http://www.w3.org/2000/svg" fill =" none" viewBox =" 0 0 14 14" 
11+                 <path  stroke =" currentColor" stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" 
12+             </svg >
13+             <span  class =" sr-only" span >
14+         </button >
15+       </div >
16+       <div  class =" flex gap-4 items-start justify-between" 
17+         <h3  class =" text-sm font-medium text-gray-700 mb-2" h3 >
18+         <h3  class =" text-sm font-medium text-gray-700 mb-2" h3 >
19+       </div >
20+       <div  class =" flex gap-4 items-center" 
21+         <!--  Old Image --> 
22+         <div  class =" flex-1" 
23+           <div  class =" relative" 
24+             <img 
25+               v-if =" isValidUrl(compiledOldImage)" 
26+               ref =" oldImg" 
27+               :src =" compiledOldImage" 
28+               alt =" Old image" 
29+               class =" w-full max-w-sm h-auto object-cover rounded-lg cursor-pointer border hover:border-blue-500 transition-colors duration-200" 
30+             />
31+             <div  v-else  class =" w-full max-w-sm h-48 bg-gray-100 rounded-lg flex items-center justify-center" 
32+               <p  class =" text-gray-500" p >
33+             </div >
34+           </div >
35+         </div >
36+ 
37+         <!--  Comparison Arrow --> 
38+         <div  class =" flex items-center justify-center" 
39+           <div  class =" w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center" 
40+             <svg  class =" w-4 h-4 text-blue-600" fill =" none" stroke =" currentColor" viewBox =" 0 0 24 24" 
41+               <path  stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" M9 5l7 7-7 7" path >
42+             </svg >
43+           </div >
44+         </div >
45+ 
46+         <!--  New Image --> 
47+         <div  class =" flex-1" 
48+           <div  class =" relative" 
49+             <img 
50+               v-if =" isValidUrl(newImage)" 
51+               ref =" newImg" 
52+               :src =" newImage" 
53+               alt =" New image" 
54+               class =" w-full max-w-sm h-auto object-cover rounded-lg cursor-pointer border hover:border-blue-500 transition-colors duration-200" 
55+             />
56+             <div  v-else  class =" w-full max-w-sm h-48 bg-gray-100 rounded-lg flex items-center justify-center" 
57+               <p  class =" text-gray-500" p >
58+             </div >
59+           </div >
60+         </div >
61+       </div >
62+     </div >
63+   </div >
64+ 
65+ 
66+ </template >
67+ 
68+ <script  setup lang="ts">
69+ import  { ref , onMounted , watch , nextTick  } from  ' vue' 
70+ import  mediumZoom  from  ' medium-zoom' 
71+ import  { callAdminForthApi  } from  ' @/utils' 
72+ 
73+ const =  defineProps <{
74+   oldImage:  string  
75+   newImage:  string  
76+   meta:  any  
77+   columnName:  string  
78+ }>() 
79+ 
80+ const =  defineEmits <{
81+   close:  [] 
82+ }>() 
83+ 
84+ const =  ref <HTMLImageElement  |  null >(null )
85+ const =  ref <HTMLImageElement  |  null >(null )
86+ const =  ref <any >(null )
87+ const =  ref <any >(null )
88+ const =  ref <string >(' ' 
89+ 
90+ async  function  compileOldImage() {
91+   try  { 
92+     const =  await  callAdminForthApi ({ 
93+       path: ` /plugin/${props .meta .pluginInstanceId }/compile_old_image_link ` , 
94+       method: ' POST'  
95+       body: { 
96+         image: props .oldImage , 
97+         columnName: props .columnName , 
98+       }, 
99+     }); 
100+     compiledOldImage .value  =  res .previewUrl ; 
101+   } catch  (e ) { 
102+     console .error (" Error compiling old image:" e ) 
103+     return ; 
104+   } 
105+ } 
106+ 
107+ function  closePopup() {
108+   emit (' close'  
109+ } 
110+ 
111+ function  isValidUrl(str :  string ):  boolean  {
112+   if  (! str ) return  false  
113+   try  { 
114+     new  URL (str ) 
115+     return  true  
116+   } catch  { 
117+     return  false  
118+   } 
119+ } 
120+ 
121+ function  initializeZoom() {
122+   //  Clean up existing zoom instances 
123+   if  (oldZoom .value ) { 
124+     oldZoom .value .detach () 
125+   } 
126+   if  (newZoom .value ) { 
127+     newZoom .value .detach () 
128+   } 
129+ 
130+   //  Initialize zoom for old image 
131+   if  (oldImg .value  &&  isValidUrl (compiledOldImage .value )) { 
132+     oldZoom .value  =  mediumZoom (oldImg .value , { 
133+       margin: 24 , 
134+       background: ' rgba(0, 0, 0, 0.8)'  
135+       scrollOffset: 150  
136+     }) 
137+   } 
138+ 
139+   //  Initialize zoom for new image 
140+   if  (newImg .value  &&  isValidUrl (props .newImage )) { 
141+     newZoom .value  =  mediumZoom (newImg .value , { 
142+       margin: 24 , 
143+       background: ' rgba(0, 0, 0, 0.8)'  
144+       scrollOffset: 150  
145+     }) 
146+   } 
147+ } 
148+ 
149+ onMounted (async  () =>  {
150+   await  compileOldImage () 
151+   await  nextTick () 
152+   initializeZoom () 
153+ }) 
154+ 
155+ //  Re-initialize zoom when images change
156+ watch ([() =>  props .oldImage , () =>  props .newImage , () =>  compiledOldImage .value ], async  () =>  {
157+   await  nextTick () 
158+   initializeZoom () 
159+ }) 
160+ script >
161+ 
162+ <style >
163+ .medium-zoom-image  {
164+   z-index 999999  !important ; 
165+   background rgba (0 , 0 , 0 , 0.8 ); 
166+   border none  !important ; 
167+   border-radius 0  !important ; 
168+ } 
169+ .medium-zoom-overlay  {
170+   z-index 99999  !important ; 
171+   background rgba (0 , 0 , 0 , 0.8 ) !important ; 
172+ } 
173+ html .dark  .medium-zoom-overlay  {
174+   background rgba (17 , 24 , 39 , 0.8 ) !important ; 
175+ } 
176+ body .medium-zoom--opened  aside  {
177+   filter grayscale (1 ); 
178+ } 
179+ style >
180+ 
181+ <style  scoped>
182+ .image-compare-container  {
183+   padding 1rem  ; 
184+   background-color white ; 
185+   box-shadow 0  1px   3px   0  rgb (0  0  0  / 0.1 ), 0  1px   2px   -1px   rgb (0  0  0  / 0.1 ); 
186+   border 1px   solid  #e5e7eb ; 
187+ } 
188+ 
189+ .fade-enter-active , .fade-leave-active  {
190+   transition 0.3s   ease ; 
191+ } 
192+ 
193+ .fade-enter-from , .fade-leave-to  {
194+   opacity 0 ; 
195+ } 
196+ style >
0 commit comments