| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  | import { | 
					
						
							|  |  |  |   customElement, | 
					
						
							|  |  |  |   html, | 
					
						
							|  |  |  |   property, | 
					
						
							|  |  |  |   css, | 
					
						
							|  |  |  |   cssManager, | 
					
						
							|  |  |  |   state, | 
					
						
							|  |  |  |   type TemplateResult, | 
					
						
							|  |  |  | } from '@design.estate/dees-element'; | 
					
						
							|  |  |  | import { DeesInputBase } from '../dees-input-base.js'; | 
					
						
							|  |  |  | import '../dees-icon.js'; | 
					
						
							|  |  |  | import '../dees-label.js'; | 
					
						
							|  |  |  | import { ProfilePictureModal } from './profilepicture.modal.js'; | 
					
						
							|  |  |  | import { demoFunc } from './dees-input-profilepicture.demo.js'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | declare global { | 
					
						
							|  |  |  |   interface HTMLElementTagNameMap { | 
					
						
							|  |  |  |     'dees-input-profilepicture': DeesInputProfilePicture; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export type ProfileShape = 'square' | 'round'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @customElement('dees-input-profilepicture') | 
					
						
							|  |  |  | export class DeesInputProfilePicture extends DeesInputBase<DeesInputProfilePicture> { | 
					
						
							|  |  |  |   public static demo = demoFunc; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property({ type: String }) | 
					
						
							|  |  |  |   public value: string = ''; // Base64 encoded image or URL
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property({ type: String }) | 
					
						
							|  |  |  |   public shape: ProfileShape = 'round'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property({ type: Number }) | 
					
						
							|  |  |  |   public size: number = 120; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property({ type: String }) | 
					
						
							|  |  |  |   public placeholder: string = ''; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property({ type: Boolean }) | 
					
						
							|  |  |  |   public allowUpload: boolean = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property({ type: Boolean }) | 
					
						
							|  |  |  |   public allowDelete: boolean = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property({ type: Number }) | 
					
						
							|  |  |  |   public maxFileSize: number = 5 * 1024 * 1024; // 5MB
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property({ type: Array }) | 
					
						
							|  |  |  |   public acceptedFormats: string[] = ['image/jpeg', 'image/png', 'image/webp']; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |   @property({ type: Number }) | 
					
						
							|  |  |  |   public outputSize: number = 800; // Output resolution in pixels
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property({ type: Number }) | 
					
						
							|  |  |  |   public outputQuality: number = 0.95; // 0-1 quality for JPEG
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |   @state() | 
					
						
							|  |  |  |   private isHovered: boolean = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @state() | 
					
						
							|  |  |  |   private isDragging: boolean = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |   @state() | 
					
						
							|  |  |  |   private isLoading: boolean = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |   private modalInstance: ProfilePictureModal | null = null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public static styles = [ | 
					
						
							|  |  |  |     ...DeesInputBase.baseStyles, | 
					
						
							|  |  |  |     cssManager.defaultStyles, | 
					
						
							|  |  |  |     css`
 | 
					
						
							|  |  |  |       :host { | 
					
						
							|  |  |  |         display: block; | 
					
						
							|  |  |  |         position: relative; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .input-wrapper { | 
					
						
							|  |  |  |         display: flex; | 
					
						
							|  |  |  |         flex-direction: column; | 
					
						
							|  |  |  |         gap: 16px; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .profile-container { | 
					
						
							|  |  |  |         position: relative; | 
					
						
							|  |  |  |         display: inline-block; | 
					
						
							|  |  |  |         cursor: pointer; | 
					
						
							|  |  |  |         transition: all 0.3s ease; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .profile-container:hover { | 
					
						
							|  |  |  |         transform: scale(1.02); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .profile-picture { | 
					
						
							|  |  |  |         width: var(--size, 120px); | 
					
						
							|  |  |  |         height: var(--size, 120px); | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |         background: ${cssManager.bdTheme('#f5f5f5', '#18181b')}; | 
					
						
							|  |  |  |         border: 3px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')}; | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |         display: flex; | 
					
						
							|  |  |  |         align-items: center; | 
					
						
							|  |  |  |         justify-content: center; | 
					
						
							|  |  |  |         overflow: hidden; | 
					
						
							|  |  |  |         position: relative; | 
					
						
							|  |  |  |         transition: all 0.3s ease; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .profile-picture.round { | 
					
						
							|  |  |  |         border-radius: 50%; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .profile-picture.square { | 
					
						
							|  |  |  |         border-radius: 12px; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .profile-picture.dragging { | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |         border-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')}; | 
					
						
							|  |  |  |         box-shadow: 0 0 0 4px ${cssManager.bdTheme('rgba(59, 130, 246, 0.15)', 'rgba(96, 165, 250, 0.15)')}; | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .profile-picture:hover { | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |         border-color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')}; | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .profile-picture:disabled { | 
					
						
							|  |  |  |         cursor: not-allowed; | 
					
						
							|  |  |  |         opacity: 0.5; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .profile-image { | 
					
						
							|  |  |  |         width: 100%; | 
					
						
							|  |  |  |         height: 100%; | 
					
						
							|  |  |  |         object-fit: cover; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .placeholder-icon { | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |         color: ${cssManager.bdTheme('#71717a', '#a1a1aa')}; | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .overlay { | 
					
						
							|  |  |  |         position: absolute; | 
					
						
							|  |  |  |         top: 0; | 
					
						
							|  |  |  |         left: 0; | 
					
						
							|  |  |  |         right: 0; | 
					
						
							|  |  |  |         bottom: 0; | 
					
						
							|  |  |  |         background: rgba(0, 0, 0, 0.6); | 
					
						
							|  |  |  |         display: flex; | 
					
						
							|  |  |  |         align-items: center; | 
					
						
							|  |  |  |         justify-content: center; | 
					
						
							|  |  |  |         opacity: 0; | 
					
						
							|  |  |  |         transition: opacity 0.3s ease; | 
					
						
							|  |  |  |         pointer-events: none; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .profile-container:hover .overlay { | 
					
						
							|  |  |  |         opacity: 1; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .overlay-content { | 
					
						
							|  |  |  |         display: flex; | 
					
						
							|  |  |  |         gap: 12px; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .overlay-button { | 
					
						
							|  |  |  |         width: 40px; | 
					
						
							|  |  |  |         height: 40px; | 
					
						
							|  |  |  |         border-radius: 50%; | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |         background: ${cssManager.bdTheme('rgba(255, 255, 255, 0.95)', 'rgba(39, 39, 42, 0.95)')}; | 
					
						
							|  |  |  |         border: 1px solid ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(255, 255, 255, 0.1)')}; | 
					
						
							|  |  |  |         color: ${cssManager.bdTheme('#09090b', '#fafafa')}; | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |         display: flex; | 
					
						
							|  |  |  |         align-items: center; | 
					
						
							|  |  |  |         justify-content: center; | 
					
						
							|  |  |  |         cursor: pointer; | 
					
						
							|  |  |  |         transition: all 0.2s ease; | 
					
						
							|  |  |  |         pointer-events: auto; | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |         box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .overlay-button:hover { | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |         background: ${cssManager.bdTheme('#ffffff', '#3f3f46')}; | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |         transform: scale(1.1); | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |         box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .overlay-button.delete { | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |         background: ${cssManager.bdTheme('rgba(239, 68, 68, 0.9)', 'rgba(220, 38, 38, 0.9)')}; | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |         color: white; | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |         border-color: transparent; | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .overlay-button.delete:hover { | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |         background: ${cssManager.bdTheme('#ef4444', '#dc2626')}; | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .drop-zone-text { | 
					
						
							|  |  |  |         position: absolute; | 
					
						
							|  |  |  |         top: 50%; | 
					
						
							|  |  |  |         left: 50%; | 
					
						
							|  |  |  |         transform: translate(-50%, -50%); | 
					
						
							|  |  |  |         text-align: center; | 
					
						
							|  |  |  |         color: white; | 
					
						
							|  |  |  |         font-weight: 500; | 
					
						
							|  |  |  |         pointer-events: none; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .hidden-input { | 
					
						
							|  |  |  |         display: none; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |       /* Loading animation */ | 
					
						
							|  |  |  |       .loading-overlay { | 
					
						
							|  |  |  |         position: absolute; | 
					
						
							|  |  |  |         top: 0; | 
					
						
							|  |  |  |         left: 0; | 
					
						
							|  |  |  |         right: 0; | 
					
						
							|  |  |  |         bottom: 0; | 
					
						
							|  |  |  |         background: ${cssManager.bdTheme('rgba(255, 255, 255, 0.8)', 'rgba(0, 0, 0, 0.8)')}; | 
					
						
							|  |  |  |         display: flex; | 
					
						
							|  |  |  |         align-items: center; | 
					
						
							|  |  |  |         justify-content: center; | 
					
						
							|  |  |  |         border-radius: inherit; | 
					
						
							|  |  |  |         opacity: 0; | 
					
						
							|  |  |  |         pointer-events: none; | 
					
						
							|  |  |  |         transition: opacity 0.2s ease; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .loading-overlay.show { | 
					
						
							|  |  |  |         opacity: 1; | 
					
						
							|  |  |  |         pointer-events: auto; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .loading-spinner { | 
					
						
							|  |  |  |         width: 40px; | 
					
						
							|  |  |  |         height: 40px; | 
					
						
							|  |  |  |         border: 3px solid ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(255, 255, 255, 0.1)')}; | 
					
						
							|  |  |  |         border-top-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')}; | 
					
						
							|  |  |  |         border-radius: 50%; | 
					
						
							|  |  |  |         animation: spin 0.6s linear infinite; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @keyframes spin { | 
					
						
							|  |  |  |         to { | 
					
						
							|  |  |  |           transform: rotate(360deg); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @keyframes pulse { | 
					
						
							|  |  |  |         0% { | 
					
						
							|  |  |  |           transform: scale(1); | 
					
						
							|  |  |  |           opacity: 1; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         50% { | 
					
						
							|  |  |  |           transform: scale(1.05); | 
					
						
							|  |  |  |           opacity: 0.8; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         100% { | 
					
						
							|  |  |  |           transform: scale(1); | 
					
						
							|  |  |  |           opacity: 1; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .profile-picture.clicking { | 
					
						
							|  |  |  |         animation: pulse 0.3s ease-out; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |     `,
 | 
					
						
							|  |  |  |   ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   render(): TemplateResult { | 
					
						
							|  |  |  |     return html`
 | 
					
						
							|  |  |  |       <div class="input-wrapper"> | 
					
						
							|  |  |  |         <dees-label .label=${this.label} .description=${this.description} .required=${this.required}></dees-label> | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         <div  | 
					
						
							|  |  |  |           class="profile-container" | 
					
						
							|  |  |  |           @click=${this.handleClick} | 
					
						
							|  |  |  |           @dragover=${this.handleDragOver} | 
					
						
							|  |  |  |           @dragleave=${this.handleDragLeave} | 
					
						
							|  |  |  |           @drop=${this.handleDrop} | 
					
						
							|  |  |  |           style="--size: ${this.size}px" | 
					
						
							|  |  |  |         > | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |           <div class="profile-picture ${this.shape} ${this.isDragging ? 'dragging' : ''} ${this.isLoading && !this.value ? 'clicking' : ''}"> | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |             ${this.value ? html`
 | 
					
						
							|  |  |  |               <img class="profile-image" src="${this.value}" alt="Profile picture" /> | 
					
						
							|  |  |  |             ` : html` | 
					
						
							|  |  |  |               <dees-icon class="placeholder-icon" icon="lucide:user" iconSize="${this.size * 0.5}"></dees-icon> | 
					
						
							|  |  |  |             `}
 | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             ${this.isDragging ? html`
 | 
					
						
							|  |  |  |               <div class="overlay" style="opacity: 1"> | 
					
						
							|  |  |  |                 <div class="drop-zone-text"> | 
					
						
							|  |  |  |                   Drop image here | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |               </div> | 
					
						
							|  |  |  |             ` : ''}
 | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             ${this.value && !this.disabled ? html`
 | 
					
						
							|  |  |  |               <div class="overlay"> | 
					
						
							|  |  |  |                 <div class="overlay-content"> | 
					
						
							|  |  |  |                   ${this.allowUpload ? html`
 | 
					
						
							|  |  |  |                     <button class="overlay-button" @click=${(e: Event) => { e.stopPropagation(); this.openModal(); }} title="Change picture"> | 
					
						
							|  |  |  |                       <dees-icon icon="lucide:pencil" iconSize="20"></dees-icon> | 
					
						
							|  |  |  |                     </button> | 
					
						
							|  |  |  |                   ` : ''}
 | 
					
						
							|  |  |  |                   ${this.allowDelete ? html`
 | 
					
						
							|  |  |  |                     <button class="overlay-button delete" @click=${(e: Event) => { e.stopPropagation(); this.deletePicture(); }} title="Delete picture"> | 
					
						
							|  |  |  |                       <dees-icon icon="lucide:trash2" iconSize="20"></dees-icon> | 
					
						
							|  |  |  |                     </button> | 
					
						
							|  |  |  |                   ` : ''}
 | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |               </div> | 
					
						
							|  |  |  |             ` : ''}
 | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |              | 
					
						
							|  |  |  |             ${this.isLoading && !this.value ? html`
 | 
					
						
							|  |  |  |               <div class="loading-overlay show"> | 
					
						
							|  |  |  |                 <div class="loading-spinner"></div> | 
					
						
							|  |  |  |               </div> | 
					
						
							|  |  |  |             ` : ''}
 | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |           </div> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         <input | 
					
						
							|  |  |  |           type="file" | 
					
						
							|  |  |  |           class="hidden-input" | 
					
						
							|  |  |  |           accept="${this.acceptedFormats.join(',')}" | 
					
						
							|  |  |  |           @change=${this.handleFileSelect} | 
					
						
							|  |  |  |         /> | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     `;
 | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private handleClick(): void { | 
					
						
							|  |  |  |     if (this.disabled || !this.allowUpload) return; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if (!this.value) { | 
					
						
							|  |  |  |       // If no image, open file picker
 | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |       this.isLoading = true; | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |       const input = this.shadowRoot!.querySelector('.hidden-input') as HTMLInputElement; | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |        | 
					
						
							|  |  |  |       // Set up a focus handler to detect when the dialog is closed without selection
 | 
					
						
							|  |  |  |       const handleFocus = () => { | 
					
						
							|  |  |  |         setTimeout(() => { | 
					
						
							|  |  |  |           // Check if no file was selected
 | 
					
						
							|  |  |  |           if (!input.files || input.files.length === 0) { | 
					
						
							|  |  |  |             this.isLoading = false; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           window.removeEventListener('focus', handleFocus); | 
					
						
							|  |  |  |         }, 300); | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       window.addEventListener('focus', handleFocus); | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |       input.click(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private handleFileSelect(event: Event): void { | 
					
						
							|  |  |  |     const input = event.target as HTMLInputElement; | 
					
						
							|  |  |  |     const file = input.files?.[0]; | 
					
						
							|  |  |  |      | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |     // Always reset loading state when file dialog interaction completes
 | 
					
						
							|  |  |  |     this.isLoading = false; | 
					
						
							|  |  |  |      | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |     if (file) { | 
					
						
							|  |  |  |       this.processFile(file); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Reset input to allow selecting the same file again
 | 
					
						
							|  |  |  |     input.value = ''; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private handleDragOver(event: DragEvent): void { | 
					
						
							|  |  |  |     event.preventDefault(); | 
					
						
							|  |  |  |     if (!this.disabled && this.allowUpload) { | 
					
						
							|  |  |  |       this.isDragging = true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private handleDragLeave(): void { | 
					
						
							|  |  |  |     this.isDragging = false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private handleDrop(event: DragEvent): void { | 
					
						
							|  |  |  |     event.preventDefault(); | 
					
						
							|  |  |  |     this.isDragging = false; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if (this.disabled || !this.allowUpload) return; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     const file = event.dataTransfer?.files[0]; | 
					
						
							|  |  |  |     if (file) { | 
					
						
							|  |  |  |       this.processFile(file); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async processFile(file: File): Promise<void> { | 
					
						
							|  |  |  |     // Validate file type
 | 
					
						
							|  |  |  |     if (!this.acceptedFormats.includes(file.type)) { | 
					
						
							|  |  |  |       console.error('Invalid file type:', file.type); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Validate file size
 | 
					
						
							|  |  |  |     if (file.size > this.maxFileSize) { | 
					
						
							|  |  |  |       console.error('File too large:', file.size); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Read file as base64
 | 
					
						
							|  |  |  |     const reader = new FileReader(); | 
					
						
							|  |  |  |     reader.onload = async (e) => { | 
					
						
							|  |  |  |       const base64 = e.target?.result as string; | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Open modal for cropping
 | 
					
						
							|  |  |  |       await this.openModal(base64); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     reader.readAsDataURL(file); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async openModal(initialImage?: string): Promise<void> { | 
					
						
							|  |  |  |     const imageToEdit = initialImage || this.value; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if (!imageToEdit) { | 
					
						
							|  |  |  |       // If no image provided, open file picker
 | 
					
						
							|  |  |  |       const input = this.shadowRoot!.querySelector('.hidden-input') as HTMLInputElement; | 
					
						
							|  |  |  |       input.click(); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Create and show modal
 | 
					
						
							|  |  |  |     this.modalInstance = new ProfilePictureModal(); | 
					
						
							|  |  |  |     this.modalInstance.shape = this.shape; | 
					
						
							|  |  |  |     this.modalInstance.initialImage = imageToEdit; | 
					
						
							| 
									
										
										
										
											2025-06-30 12:02:02 +00:00
										 |  |  |     this.modalInstance.outputSize = this.outputSize; | 
					
						
							|  |  |  |     this.modalInstance.outputQuality = this.outputQuality; | 
					
						
							| 
									
										
										
										
											2025-06-30 11:35:38 +00:00
										 |  |  |      | 
					
						
							|  |  |  |     this.modalInstance.addEventListener('save', (event: CustomEvent) => { | 
					
						
							|  |  |  |       this.value = event.detail.croppedImage; | 
					
						
							|  |  |  |       this.changeSubject.next(this); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     document.body.appendChild(this.modalInstance); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private deletePicture(): void { | 
					
						
							|  |  |  |     this.value = ''; | 
					
						
							|  |  |  |     this.changeSubject.next(this); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public getValue(): string { | 
					
						
							|  |  |  |     return this.value; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public setValue(value: string): void { | 
					
						
							|  |  |  |     this.value = value; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |