@servicemind.tis/tis-image-and-file-upload-and-view 1.2.23 → 1.2.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,6 +23,8 @@ import { HttpHeaders, HttpClientModule } from '@angular/common/http';
23
23
  import * as i1$3 from '@angular/material/snack-bar';
24
24
  import { MatSnackBarModule } from '@angular/material/snack-bar';
25
25
  import { takeUntil, take, timeout } from 'rxjs/operators';
26
+ import * as i4$1 from 'angularx-qrcode';
27
+ import { QRCodeComponent } from 'angularx-qrcode';
26
28
  import * as i10 from '@angular/cdk/drag-drop';
27
29
  import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
28
30
  import * as i6 from '@angular/forms';
@@ -545,7 +547,9 @@ class TisRemoteUploadService {
545
547
  // Step 1: Get link token from backend
546
548
  const endpoint = this.config?.apiEndpoints?.generateMobileLinkToken
547
549
  || `${this.apiUrl}/ease-of-access/mobile-upload-link-token`;
548
- const response = await this.callHttpApi(endpoint, { deviceId: this.deviceId, userId: this.userId });
550
+ const apiResponse = await this.callHttpApi(endpoint, { deviceId: this.deviceId, userId: this.userId });
551
+ // Unwrap the data from API response
552
+ const response = apiResponse.data || apiResponse;
549
553
  const expirySeconds = this.config?.qrCode?.expirySeconds || DEFAULT_QR_EXPIRY;
550
554
  const expiresAt = response.expiresAt || Date.now() + expirySeconds * 1000;
551
555
  // Step 2: Build QR URL
@@ -845,7 +849,6 @@ class TisQrCodeDialogComponent {
845
849
  dialogRef;
846
850
  data;
847
851
  remoteUploadService;
848
- qrCanvas;
849
852
  qrData = '';
850
853
  expiresAt = 0;
851
854
  remainingTime = '';
@@ -905,8 +908,6 @@ class TisQrCodeDialogComponent {
905
908
  this.expiresAt = result.expiresAt;
906
909
  this.isLoading = false;
907
910
  this.startCountdown();
908
- // Generate QR code after view is ready
909
- setTimeout(() => this.renderQrCode(), 100);
910
911
  }
911
912
  catch (error) {
912
913
  this.isLoading = false;
@@ -964,74 +965,6 @@ class TisQrCodeDialogComponent {
964
965
  }
965
966
  });
966
967
  }
967
- renderQrCode() {
968
- if (!this.qrCanvas || !this.qrData)
969
- return;
970
- const canvas = this.qrCanvas.nativeElement;
971
- const ctx = canvas.getContext('2d');
972
- if (!ctx)
973
- return;
974
- const size = this.data.qrSize || 200;
975
- canvas.width = size;
976
- canvas.height = size;
977
- this.generateQrCodeOnCanvas(ctx, this.qrData, size);
978
- }
979
- /**
980
- * Simple QR code generator using canvas
981
- * In production, you'd use a library like qrcode or qrcode-generator
982
- */
983
- generateQrCodeOnCanvas(ctx, data, size) {
984
- const moduleCount = 25;
985
- const moduleSize = size / moduleCount;
986
- ctx.fillStyle = '#ffffff';
987
- ctx.fillRect(0, 0, size, size);
988
- const pattern = this.generatePattern(data, moduleCount);
989
- ctx.fillStyle = '#000000';
990
- for (let row = 0; row < moduleCount; row++) {
991
- for (let col = 0; col < moduleCount; col++) {
992
- if (pattern[row][col]) {
993
- ctx.fillRect(col * moduleSize, row * moduleSize, moduleSize, moduleSize);
994
- }
995
- }
996
- }
997
- this.drawFinderPattern(ctx, 0, 0, moduleSize);
998
- this.drawFinderPattern(ctx, (moduleCount - 7) * moduleSize, 0, moduleSize);
999
- this.drawFinderPattern(ctx, 0, (moduleCount - 7) * moduleSize, moduleSize);
1000
- }
1001
- generatePattern(data, size) {
1002
- const pattern = [];
1003
- let hash = 0;
1004
- for (let i = 0; i < data.length; i++) {
1005
- hash = ((hash << 5) - hash) + data.charCodeAt(i);
1006
- hash = hash & hash;
1007
- }
1008
- const seed = Math.abs(hash);
1009
- let current = seed;
1010
- for (let row = 0; row < size; row++) {
1011
- pattern[row] = [];
1012
- for (let col = 0; col < size; col++) {
1013
- if ((row < 8 && col < 8) ||
1014
- (row < 8 && col >= size - 8) ||
1015
- (row >= size - 8 && col < 8)) {
1016
- pattern[row][col] = false;
1017
- }
1018
- else {
1019
- current = (current * 1103515245 + 12345) & 0x7fffffff;
1020
- pattern[row][col] = (current % 2) === 0;
1021
- }
1022
- }
1023
- }
1024
- return pattern;
1025
- }
1026
- drawFinderPattern(ctx, x, y, moduleSize) {
1027
- const s = moduleSize;
1028
- ctx.fillStyle = '#000000';
1029
- ctx.fillRect(x, y, 7 * s, 7 * s);
1030
- ctx.fillStyle = '#ffffff';
1031
- ctx.fillRect(x + s, y + s, 5 * s, 5 * s);
1032
- ctx.fillStyle = '#000000';
1033
- ctx.fillRect(x + 2 * s, y + 2 * s, 3 * s, 3 * s);
1034
- }
1035
968
  // ---------------------------------------------------------------------------
1036
969
  // Public Methods
1037
970
  // ---------------------------------------------------------------------------
@@ -1079,18 +1012,15 @@ class TisQrCodeDialogComponent {
1079
1012
  });
1080
1013
  }
1081
1014
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TisQrCodeDialogComponent, deps: [{ token: i1$2.MatDialogRef }, { token: MAT_DIALOG_DATA }, { token: TisRemoteUploadService }], target: i0.ɵɵFactoryTarget.Component });
1082
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: TisQrCodeDialogComponent, isStandalone: false, selector: "tis-qr-code-dialog", viewQueries: [{ propertyName: "qrCanvas", first: true, predicate: ["qrCanvas"], descendants: true }], ngImport: i0, template: "<div class=\"qr-dialog-container\">\n <!-- Header -->\n <div class=\"qr-dialog-header\">\n <h2 class=\"qr-dialog-title\">{{ data.title || 'Upload from Mobile' }}</h2>\n <p class=\"qr-dialog-subtitle\">{{ data.subtitle || 'Scan this QR code with your mobile device to upload images' }}</p>\n <button mat-icon-button class=\"close-btn\" (click)=\"close()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"qr-dialog-content\">\n <!-- Loading State -->\n <div *ngIf=\"isLoading\" class=\"loading-container\">\n <mat-spinner diameter=\"48\"></mat-spinner>\n <p>Generating QR Code...</p>\n </div>\n\n <!-- Error State -->\n <div *ngIf=\"errorMessage && !isLoading\" class=\"error-container\">\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <p class=\"error-message\">{{ errorMessage }}</p>\n <button mat-raised-button color=\"primary\" (click)=\"refreshQrCode()\">\n Try Again\n </button>\n </div>\n\n <!-- Main Content -->\n <div *ngIf=\"!isLoading && !errorMessage\" class=\"qr-content\">\n \n <!-- Device Info Card -->\n <div class=\"device-info-card\">\n <div class=\"device-row\">\n <mat-icon class=\"device-icon desktop\">computer</mat-icon>\n <div class=\"device-details\">\n <span class=\"device-label\">This Device</span>\n <span class=\"device-id\">{{ formatDeviceId(desktopDeviceId) }}</span>\n </div>\n </div>\n \n <div class=\"connection-line\" *ngIf=\"mobileDeviceId\">\n <mat-icon>sync_alt</mat-icon>\n </div>\n \n <div class=\"device-row\" *ngIf=\"mobileDeviceId\">\n <mat-icon class=\"device-icon mobile\">smartphone</mat-icon>\n <div class=\"device-details\">\n <span class=\"device-label\">Connected Mobile</span>\n <span class=\"device-id\">{{ formatDeviceId(mobileDeviceId) }}</span>\n </div>\n </div>\n </div>\n\n <!-- Connected State -->\n <div *ngIf=\"isConnected\" class=\"connected-state\">\n <div class=\"connected-indicator\">\n <mat-icon class=\"success-icon\">check_circle</mat-icon>\n <span>Mobile Connected!</span>\n </div>\n <p class=\"connected-message\">\n You can now upload images from your mobile device. \n Select a file/image upload field and the uploaded files will appear automatically.\n </p>\n \n <!-- Uploaded Files -->\n <div class=\"uploaded-files\" *ngIf=\"uploadedFiles.length > 0\">\n <h4>Uploaded Files ({{ uploadedFiles.length }})</h4>\n <div class=\"file-list\">\n <div class=\"file-item\" *ngFor=\"let upload of uploadedFiles\">\n <mat-icon>check_circle</mat-icon>\n <span>{{ upload.file.fileName }}</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- QR Code (only show when not connected) -->\n <div *ngIf=\"!isConnected\" class=\"qr-section\">\n <!-- Connection Status -->\n <div class=\"connection-status\" [ngClass]=\"connectionStatus\">\n <div class=\"status-indicator\"></div>\n <span *ngIf=\"connectionStatus === 'disconnected'\">Waiting for mobile...</span>\n <span *ngIf=\"connectionStatus === 'pending'\">Waiting for connection...</span>\n </div>\n\n <!-- QR Code -->\n <div class=\"qr-code-wrapper\" [class.expired]=\"isExpired\">\n <canvas #qrCanvas class=\"qr-canvas\"></canvas>\n \n <!-- Expired Overlay -->\n <div *ngIf=\"isExpired\" class=\"expired-overlay\">\n <mat-icon>refresh</mat-icon>\n <p>QR Code Expired</p>\n <button mat-raised-button color=\"primary\" (click)=\"refreshQrCode()\">\n Generate New Code\n </button>\n </div>\n </div>\n\n <!-- Countdown Timer -->\n <div class=\"countdown\" *ngIf=\"data.showCountdown !== false && !isExpired\">\n <mat-icon>timer</mat-icon>\n <span>Expires in {{ remainingTime }}</span>\n </div>\n </div>\n\n </div>\n </div>\n\n <!-- Footer -->\n <div class=\"qr-dialog-footer\">\n <button mat-button *ngIf=\"isConnected\" color=\"warn\" (click)=\"disconnect()\">\n <mat-icon>link_off</mat-icon>\n Disconnect\n </button>\n <button mat-button *ngIf=\"!isConnected && !isLoading\" (click)=\"connectToMobile()\">\n <mat-icon>qr_code</mat-icon>\n Connect & Upload via Mobile\n </button>\n <button mat-raised-button color=\"primary\" (click)=\"close()\">\n {{ uploadedFiles.length > 0 ? 'Done' : 'Close' }}\n </button>\n </div>\n</div>\n", styles: [".qr-dialog-container{display:flex;flex-direction:column;min-width:360px;max-width:440px;background:#fff;border-radius:16px;overflow:hidden}.qr-dialog-header{position:relative;padding:24px 24px 16px;text-align:center;border-bottom:1px solid #f0f0f0}.qr-dialog-title{margin:0 0 8px;font-size:20px;font-weight:600;color:#1a1a1a}.qr-dialog-subtitle{margin:0;font-size:14px;color:#666;line-height:1.4}.close-btn{position:absolute;top:12px;right:12px;color:#999}.qr-dialog-content{padding:24px;display:flex;flex-direction:column;align-items:center;min-height:300px}.loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;height:250px;color:#666}.error-container{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;height:250px;text-align:center}.error-icon{font-size:48px;width:48px;height:48px;color:#f44336}.error-message{color:#666;margin:0}.qr-content{display:flex;flex-direction:column;align-items:center;gap:20px;width:100%}.device-info-section{width:100%;display:flex;align-items:center;justify-content:center;gap:12px;padding:16px;background:linear-gradient(135deg,#f8f9fa,#e9ecef);border-radius:12px;margin-bottom:8px}.device-card{display:flex;flex-direction:column;align-items:center;gap:6px;padding:12px 16px;background:#fff;border-radius:10px;min-width:120px;box-shadow:0 2px 8px #0000000f;transition:all .3s ease}.device-card.this-device{border:2px solid #2196f3}.device-card.connected-device{border:2px dashed #9e9e9e}.device-card.connected-device.active{border:2px solid #4caf50}.device-label{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.device-card.this-device .device-label{color:#2196f3}.device-card.connected-device .device-label{color:#9e9e9e}.device-card.connected-device.active .device-label{color:#4caf50}.device-label mat-icon{font-size:14px;width:14px;height:14px}.device-id{font-family:SF Mono,Monaco,Courier New,monospace;font-size:13px;font-weight:600;color:#333;letter-spacing:.5px}.device-id.waiting{color:#9e9e9e;font-style:italic;font-weight:400}.connection-line{display:flex;flex-direction:column;align-items:center;gap:4px;padding:0 8px}.connection-line .line{width:40px;height:2px;background:#e0e0e0;border-radius:1px;position:relative;overflow:hidden}.connection-line.waiting .line:after{content:\"\";position:absolute;top:0;left:-100%;width:50%;height:100%;background:linear-gradient(90deg,transparent,#2196f3,transparent);animation:shimmer 1.5s infinite}.connection-line.connected .line{background:#4caf50}.connection-line mat-icon{font-size:16px;width:16px;height:16px;color:#9e9e9e}.connection-line.waiting mat-icon{color:#ff9800;animation:blink 1s infinite}.connection-line.connected mat-icon{color:#4caf50}@keyframes shimmer{0%{left:-100%}to{left:200%}}@keyframes blink{0%,to{opacity:1}50%{opacity:.3}}.connection-status{display:flex;align-items:center;gap:8px;padding:8px 16px;border-radius:20px;font-size:13px;font-weight:500}.connection-status .status-indicator{width:8px;height:8px;border-radius:50%;animation:pulse 2s infinite}.connection-status.disconnected{background:#f5f5f5;color:#666}.connection-status.disconnected .status-indicator{background:#999}.connection-status.pending{background:#fff3e0;color:#e65100}.connection-status.pending .status-indicator{background:#ff9800}.connection-status.connected{background:#e8f5e9;color:#2e7d32}.connection-status.connected .status-indicator{background:#4caf50;animation:none}@keyframes pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(1.2)}}.qr-code-wrapper{position:relative;padding:16px;background:#f8f9fa;border-radius:12px;border:2px solid #e0e0e0;transition:all .3s ease}.qr-code-wrapper.expired{opacity:.5}.qr-code-wrapper.connected{border-color:#4caf50;background:#f1f8f4}.qr-canvas{display:block;width:200px;height:200px}.expired-overlay,.connected-overlay{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;background:#fffffff2;border-radius:10px}.expired-overlay mat-icon{font-size:40px;width:40px;height:40px;color:#999}.expired-overlay p{margin:0;color:#666;font-weight:500}.connected-overlay .success-icon{font-size:56px;width:56px;height:56px;color:#4caf50}.connected-overlay p{margin:0;color:#2e7d32;font-size:16px;font-weight:600}.pairing-code-section{text-align:center}.pairing-label{margin:0 0 8px;font-size:13px;color:#666}.pairing-code{display:inline-flex;align-items:center;gap:8px;padding:10px 16px;background:#f5f5f5;border-radius:8px;cursor:pointer;transition:background .2s ease}.pairing-code:hover{background:#eee}.pairing-code .code{font-family:SF Mono,Monaco,Courier New,monospace;font-size:18px;font-weight:600;letter-spacing:2px;color:#1a1a1a}.pairing-code .copy-icon{font-size:18px;width:18px;height:18px;color:#666}.countdown{display:flex;align-items:center;gap:6px;font-size:13px;color:#666}.countdown mat-icon{font-size:18px;width:18px;height:18px}.uploaded-files{width:100%;margin-top:8px;padding-top:16px;border-top:1px solid #f0f0f0}.uploaded-files h4{margin:0 0 12px;font-size:14px;font-weight:600;color:#333}.file-list{display:flex;flex-direction:column;gap:8px;max-height:120px;overflow-y:auto}.file-item{display:flex;align-items:center;gap:8px;padding:8px 12px;background:#f1f8f4;border-radius:8px;font-size:13px;color:#333}.file-item mat-icon{font-size:18px;width:18px;height:18px;color:#4caf50}.file-item span{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.qr-dialog-footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid #f0f0f0;background:#fafafa}@media (max-width: 400px){.qr-dialog-container{min-width:100%;border-radius:0}.qr-canvas{width:180px;height:180px}.device-info-section{flex-direction:column;gap:8px}.connection-line{transform:rotate(90deg)}.device-card{min-width:100%}}\n"], dependencies: [{ kind: "directive", type: i2$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i2.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "component", type: i4.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }] });
1015
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: TisQrCodeDialogComponent, isStandalone: false, selector: "tis-qr-code-dialog", ngImport: i0, template: "<div class=\"qr-dialog-container\">\n <!-- Header -->\n <div class=\"qr-dialog-header\">\n <h2 class=\"qr-dialog-title\">{{ data.title || 'Upload from Mobile' }}</h2>\n <p class=\"qr-dialog-subtitle\">{{ data.subtitle || 'Scan this QR code with your mobile device to upload images' }}</p>\n <button mat-icon-button class=\"close-btn\" (click)=\"close()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"qr-dialog-content\">\n <!-- Loading State -->\n <div *ngIf=\"isLoading\" class=\"loading-container\">\n <mat-spinner diameter=\"48\"></mat-spinner>\n <p>Generating QR Code...</p>\n </div>\n\n <!-- Error State -->\n <div *ngIf=\"errorMessage && !isLoading\" class=\"error-container\">\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <p class=\"error-message\">{{ errorMessage }}</p>\n <button mat-raised-button color=\"primary\" (click)=\"refreshQrCode()\">\n Try Again\n </button>\n </div>\n\n <!-- Main Content -->\n <div *ngIf=\"!isLoading && !errorMessage\" class=\"qr-content\">\n \n <!-- Device Info Card -->\n <div class=\"device-info-card\">\n <div class=\"device-row\">\n <mat-icon class=\"device-icon desktop\">computer</mat-icon>\n <div class=\"device-details\">\n <span class=\"device-label\">THIS DEVICE</span>\n <span class=\"device-id\" [title]=\"desktopDeviceId\">{{ formatDeviceId(desktopDeviceId) }}</span>\n </div>\n </div>\n \n <div class=\"connection-line\" *ngIf=\"mobileDeviceId\">\n <mat-icon>sync_alt</mat-icon>\n </div>\n \n <div class=\"device-row\" *ngIf=\"mobileDeviceId\">\n <mat-icon class=\"device-icon mobile\">smartphone</mat-icon>\n <div class=\"device-details\">\n <span class=\"device-label\">CONNECTED MOBILE</span>\n <span class=\"device-id\" [title]=\"mobileDeviceId\">{{ formatDeviceId(mobileDeviceId) }}</span>\n </div>\n </div>\n </div>\n\n <!-- Connected State -->\n <div *ngIf=\"isConnected\" class=\"connected-state\">\n <div class=\"connected-indicator\">\n <mat-icon class=\"success-icon\">check_circle</mat-icon>\n <span>Mobile Connected!</span>\n </div>\n <p class=\"connected-message\">\n You can now upload images from your mobile device. \n Select a file/image upload field and the uploaded files will appear automatically.\n </p>\n \n <!-- Uploaded Files -->\n <div class=\"uploaded-files\" *ngIf=\"uploadedFiles.length > 0\">\n <h4>Uploaded Files ({{ uploadedFiles.length }})</h4>\n <div class=\"file-list\">\n <div class=\"file-item\" *ngFor=\"let upload of uploadedFiles\">\n <mat-icon>check_circle</mat-icon>\n <span>{{ upload.file.fileName }}</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- QR Code (only show when not connected) -->\n <div *ngIf=\"!isConnected\" class=\"qr-section\">\n <!-- Connection Status -->\n <div class=\"connection-status\" [ngClass]=\"connectionStatus\">\n <div class=\"status-indicator\"></div>\n <span *ngIf=\"connectionStatus === 'disconnected'\">Waiting for mobile...</span>\n <span *ngIf=\"connectionStatus === 'pending'\">Waiting for connection...</span>\n </div>\n\n <!-- QR Code -->\n <div class=\"qr-code-wrapper\" [class.expired]=\"isExpired\">\n <qrcode \n *ngIf=\"qrData\"\n [qrdata]=\"qrData\" \n [width]=\"data.qrSize || 200\" \n [errorCorrectionLevel]=\"'M'\"\n [elementType]=\"'canvas'\"\n [cssClass]=\"'qr-canvas'\"\n ></qrcode>\n \n <!-- Expired Overlay -->\n <div *ngIf=\"isExpired\" class=\"expired-overlay\">\n <mat-icon>refresh</mat-icon>\n <p>QR Code Expired</p>\n <button mat-raised-button color=\"primary\" (click)=\"refreshQrCode()\">\n Generate New Code\n </button>\n </div>\n </div>\n\n <!-- Countdown Timer -->\n <div class=\"countdown\" *ngIf=\"data.showCountdown !== false && !isExpired\">\n <mat-icon>timer</mat-icon>\n <span>Expires in {{ remainingTime }}</span>\n </div>\n </div>\n\n </div>\n </div>\n\n <!-- Footer -->\n <div class=\"qr-dialog-footer\">\n <button mat-button *ngIf=\"isConnected\" color=\"warn\" (click)=\"disconnect()\">\n <mat-icon>link_off</mat-icon>\n Disconnect\n </button>\n <button mat-button *ngIf=\"!isConnected && !isLoading\" (click)=\"connectToMobile()\">\n <mat-icon>qr_code</mat-icon>\n Connect & Upload via Mobile\n </button>\n <button mat-raised-button color=\"primary\" (click)=\"close()\">\n {{ uploadedFiles.length > 0 ? 'Done' : 'Close' }}\n </button>\n </div>\n</div>\n", styles: [".qr-dialog-container{display:flex;flex-direction:column;min-width:360px;max-width:440px;background:#fff;border-radius:16px;overflow:hidden}.qr-dialog-header{position:relative;padding:24px 24px 16px;text-align:center;border-bottom:1px solid #f0f0f0}.qr-dialog-title{margin:0 0 8px;font-size:20px;font-weight:600;color:#1a1a1a}.qr-dialog-subtitle{margin:0;font-size:14px;color:#666;line-height:1.4}.close-btn{position:absolute;top:12px;right:12px;color:#999}.qr-dialog-content{padding:24px;display:flex;flex-direction:column;align-items:center;min-height:300px}.loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;height:250px;color:#666}.error-container{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;height:250px;text-align:center}.error-icon{font-size:48px;width:48px;height:48px;color:#f44336}.error-message{color:#666;margin:0}.qr-content{display:flex;flex-direction:column;align-items:center;gap:20px;width:100%}.device-info-section{width:100%;display:flex;align-items:center;justify-content:center;gap:12px;padding:16px;background:linear-gradient(135deg,#f8f9fa,#e9ecef);border-radius:12px;margin-bottom:8px}.device-card{display:flex;flex-direction:column;align-items:center;gap:6px;padding:12px 16px;background:#fff;border-radius:10px;min-width:120px;box-shadow:0 2px 8px #0000000f;transition:all .3s ease}.device-card.this-device{border:2px solid #2196f3}.device-card.connected-device{border:2px dashed #9e9e9e}.device-card.connected-device.active{border:2px solid #4caf50}.device-label{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.device-card.this-device .device-label{color:#2196f3}.device-card.connected-device .device-label{color:#9e9e9e}.device-card.connected-device.active .device-label{color:#4caf50}.device-label mat-icon{font-size:14px;width:14px;height:14px}.device-id{font-family:SF Mono,Monaco,Courier New,monospace;font-size:13px;font-weight:600;color:#333;letter-spacing:.5px}.device-id.waiting{color:#9e9e9e;font-style:italic;font-weight:400}.connection-line{display:flex;flex-direction:column;align-items:center;gap:4px;padding:0 8px}.connection-line .line{width:40px;height:2px;background:#e0e0e0;border-radius:1px;position:relative;overflow:hidden}.connection-line.waiting .line:after{content:\"\";position:absolute;top:0;left:-100%;width:50%;height:100%;background:linear-gradient(90deg,transparent,#2196f3,transparent);animation:shimmer 1.5s infinite}.connection-line.connected .line{background:#4caf50}.connection-line mat-icon{font-size:16px;width:16px;height:16px;color:#9e9e9e}.connection-line.waiting mat-icon{color:#ff9800;animation:blink 1s infinite}.connection-line.connected mat-icon{color:#4caf50}@keyframes shimmer{0%{left:-100%}to{left:200%}}@keyframes blink{0%,to{opacity:1}50%{opacity:.3}}.connection-status{display:flex;align-items:center;gap:8px;padding:8px 16px;border-radius:20px;font-size:13px;font-weight:500}.connection-status .status-indicator{width:8px;height:8px;border-radius:50%;animation:pulse 2s infinite}.connection-status.disconnected{background:#f5f5f5;color:#666}.connection-status.disconnected .status-indicator{background:#999}.connection-status.pending{background:#fff3e0;color:#e65100}.connection-status.pending .status-indicator{background:#ff9800}.connection-status.connected{background:#e8f5e9;color:#2e7d32}.connection-status.connected .status-indicator{background:#4caf50;animation:none}@keyframes pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(1.2)}}.qr-code-wrapper{position:relative;padding:16px;background:#f8f9fa;border-radius:12px;border:2px solid #e0e0e0;transition:all .3s ease}.qr-code-wrapper.expired{opacity:.5}.qr-code-wrapper.connected{border-color:#4caf50;background:#f1f8f4}.qr-canvas{display:block;width:200px;height:200px}.expired-overlay,.connected-overlay{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;background:#fffffff2;border-radius:10px}.expired-overlay mat-icon{font-size:40px;width:40px;height:40px;color:#999}.expired-overlay p{margin:0;color:#666;font-weight:500}.connected-overlay .success-icon{font-size:56px;width:56px;height:56px;color:#4caf50}.connected-overlay p{margin:0;color:#2e7d32;font-size:16px;font-weight:600}.pairing-code-section{text-align:center}.pairing-label{margin:0 0 8px;font-size:13px;color:#666}.pairing-code{display:inline-flex;align-items:center;gap:8px;padding:10px 16px;background:#f5f5f5;border-radius:8px;cursor:pointer;transition:background .2s ease}.pairing-code:hover{background:#eee}.pairing-code .code{font-family:SF Mono,Monaco,Courier New,monospace;font-size:18px;font-weight:600;letter-spacing:2px;color:#1a1a1a}.pairing-code .copy-icon{font-size:18px;width:18px;height:18px;color:#666}.countdown{display:flex;align-items:center;gap:6px;font-size:13px;color:#666}.countdown mat-icon{font-size:18px;width:18px;height:18px}.uploaded-files{width:100%;margin-top:8px;padding-top:16px;border-top:1px solid #f0f0f0}.uploaded-files h4{margin:0 0 12px;font-size:14px;font-weight:600;color:#333}.file-list{display:flex;flex-direction:column;gap:8px;max-height:120px;overflow-y:auto}.file-item{display:flex;align-items:center;gap:8px;padding:8px 12px;background:#f1f8f4;border-radius:8px;font-size:13px;color:#333}.file-item mat-icon{font-size:18px;width:18px;height:18px;color:#4caf50}.file-item span{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.qr-dialog-footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid #f0f0f0;background:#fafafa}.device-info-card{width:100%;display:flex;flex-direction:row;align-items:center;justify-content:center;gap:12px;padding:12px 16px;background:linear-gradient(135deg,#f8f9fa,#e9ecef);border-radius:12px;margin-bottom:8px}.device-info-card .device-row{display:flex;align-items:center;gap:10px;flex:0 0 auto}.device-info-card .device-icon{font-size:24px;width:24px;height:24px}.device-info-card .device-icon.desktop{color:#1976d2}.device-info-card .device-icon.mobile{color:#7b1fa2}.device-info-card .device-details{display:flex;flex-direction:column;gap:2px}.device-info-card .device-label{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:#666}.device-info-card .device-id{font-family:SF Mono,Monaco,Courier New,monospace;font-size:12px;font-weight:500;color:#333;cursor:default}.device-info-card .connection-line{display:flex;align-items:center;padding:0 4px}.device-info-card .connection-line mat-icon{font-size:18px;width:18px;height:18px;color:#4caf50}@media (max-width: 400px){.qr-dialog-container{min-width:100%;border-radius:0}.qr-canvas{width:180px;height:180px}.device-info-section,.device-info-card{flex-direction:column;gap:8px}.device-info-card .connection-line,.connection-line{transform:rotate(90deg)}.device-card{min-width:100%}}\n"], dependencies: [{ kind: "directive", type: i2$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i4$1.QRCodeComponent, selector: "qrcode", inputs: ["allowEmptyString", "colorDark", "colorLight", "cssClass", "elementType", "errorCorrectionLevel", "imageSrc", "imageHeight", "imageWidth", "margin", "qrdata", "scale", "version", "width", "alt", "ariaLabel", "title"], outputs: ["qrCodeURL"] }, { kind: "component", type: i3$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i2.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "component", type: i4.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }] });
1083
1016
  }
1084
1017
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TisQrCodeDialogComponent, decorators: [{
1085
1018
  type: Component,
1086
- args: [{ selector: 'tis-qr-code-dialog', standalone: false, template: "<div class=\"qr-dialog-container\">\n <!-- Header -->\n <div class=\"qr-dialog-header\">\n <h2 class=\"qr-dialog-title\">{{ data.title || 'Upload from Mobile' }}</h2>\n <p class=\"qr-dialog-subtitle\">{{ data.subtitle || 'Scan this QR code with your mobile device to upload images' }}</p>\n <button mat-icon-button class=\"close-btn\" (click)=\"close()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"qr-dialog-content\">\n <!-- Loading State -->\n <div *ngIf=\"isLoading\" class=\"loading-container\">\n <mat-spinner diameter=\"48\"></mat-spinner>\n <p>Generating QR Code...</p>\n </div>\n\n <!-- Error State -->\n <div *ngIf=\"errorMessage && !isLoading\" class=\"error-container\">\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <p class=\"error-message\">{{ errorMessage }}</p>\n <button mat-raised-button color=\"primary\" (click)=\"refreshQrCode()\">\n Try Again\n </button>\n </div>\n\n <!-- Main Content -->\n <div *ngIf=\"!isLoading && !errorMessage\" class=\"qr-content\">\n \n <!-- Device Info Card -->\n <div class=\"device-info-card\">\n <div class=\"device-row\">\n <mat-icon class=\"device-icon desktop\">computer</mat-icon>\n <div class=\"device-details\">\n <span class=\"device-label\">This Device</span>\n <span class=\"device-id\">{{ formatDeviceId(desktopDeviceId) }}</span>\n </div>\n </div>\n \n <div class=\"connection-line\" *ngIf=\"mobileDeviceId\">\n <mat-icon>sync_alt</mat-icon>\n </div>\n \n <div class=\"device-row\" *ngIf=\"mobileDeviceId\">\n <mat-icon class=\"device-icon mobile\">smartphone</mat-icon>\n <div class=\"device-details\">\n <span class=\"device-label\">Connected Mobile</span>\n <span class=\"device-id\">{{ formatDeviceId(mobileDeviceId) }}</span>\n </div>\n </div>\n </div>\n\n <!-- Connected State -->\n <div *ngIf=\"isConnected\" class=\"connected-state\">\n <div class=\"connected-indicator\">\n <mat-icon class=\"success-icon\">check_circle</mat-icon>\n <span>Mobile Connected!</span>\n </div>\n <p class=\"connected-message\">\n You can now upload images from your mobile device. \n Select a file/image upload field and the uploaded files will appear automatically.\n </p>\n \n <!-- Uploaded Files -->\n <div class=\"uploaded-files\" *ngIf=\"uploadedFiles.length > 0\">\n <h4>Uploaded Files ({{ uploadedFiles.length }})</h4>\n <div class=\"file-list\">\n <div class=\"file-item\" *ngFor=\"let upload of uploadedFiles\">\n <mat-icon>check_circle</mat-icon>\n <span>{{ upload.file.fileName }}</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- QR Code (only show when not connected) -->\n <div *ngIf=\"!isConnected\" class=\"qr-section\">\n <!-- Connection Status -->\n <div class=\"connection-status\" [ngClass]=\"connectionStatus\">\n <div class=\"status-indicator\"></div>\n <span *ngIf=\"connectionStatus === 'disconnected'\">Waiting for mobile...</span>\n <span *ngIf=\"connectionStatus === 'pending'\">Waiting for connection...</span>\n </div>\n\n <!-- QR Code -->\n <div class=\"qr-code-wrapper\" [class.expired]=\"isExpired\">\n <canvas #qrCanvas class=\"qr-canvas\"></canvas>\n \n <!-- Expired Overlay -->\n <div *ngIf=\"isExpired\" class=\"expired-overlay\">\n <mat-icon>refresh</mat-icon>\n <p>QR Code Expired</p>\n <button mat-raised-button color=\"primary\" (click)=\"refreshQrCode()\">\n Generate New Code\n </button>\n </div>\n </div>\n\n <!-- Countdown Timer -->\n <div class=\"countdown\" *ngIf=\"data.showCountdown !== false && !isExpired\">\n <mat-icon>timer</mat-icon>\n <span>Expires in {{ remainingTime }}</span>\n </div>\n </div>\n\n </div>\n </div>\n\n <!-- Footer -->\n <div class=\"qr-dialog-footer\">\n <button mat-button *ngIf=\"isConnected\" color=\"warn\" (click)=\"disconnect()\">\n <mat-icon>link_off</mat-icon>\n Disconnect\n </button>\n <button mat-button *ngIf=\"!isConnected && !isLoading\" (click)=\"connectToMobile()\">\n <mat-icon>qr_code</mat-icon>\n Connect & Upload via Mobile\n </button>\n <button mat-raised-button color=\"primary\" (click)=\"close()\">\n {{ uploadedFiles.length > 0 ? 'Done' : 'Close' }}\n </button>\n </div>\n</div>\n", styles: [".qr-dialog-container{display:flex;flex-direction:column;min-width:360px;max-width:440px;background:#fff;border-radius:16px;overflow:hidden}.qr-dialog-header{position:relative;padding:24px 24px 16px;text-align:center;border-bottom:1px solid #f0f0f0}.qr-dialog-title{margin:0 0 8px;font-size:20px;font-weight:600;color:#1a1a1a}.qr-dialog-subtitle{margin:0;font-size:14px;color:#666;line-height:1.4}.close-btn{position:absolute;top:12px;right:12px;color:#999}.qr-dialog-content{padding:24px;display:flex;flex-direction:column;align-items:center;min-height:300px}.loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;height:250px;color:#666}.error-container{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;height:250px;text-align:center}.error-icon{font-size:48px;width:48px;height:48px;color:#f44336}.error-message{color:#666;margin:0}.qr-content{display:flex;flex-direction:column;align-items:center;gap:20px;width:100%}.device-info-section{width:100%;display:flex;align-items:center;justify-content:center;gap:12px;padding:16px;background:linear-gradient(135deg,#f8f9fa,#e9ecef);border-radius:12px;margin-bottom:8px}.device-card{display:flex;flex-direction:column;align-items:center;gap:6px;padding:12px 16px;background:#fff;border-radius:10px;min-width:120px;box-shadow:0 2px 8px #0000000f;transition:all .3s ease}.device-card.this-device{border:2px solid #2196f3}.device-card.connected-device{border:2px dashed #9e9e9e}.device-card.connected-device.active{border:2px solid #4caf50}.device-label{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.device-card.this-device .device-label{color:#2196f3}.device-card.connected-device .device-label{color:#9e9e9e}.device-card.connected-device.active .device-label{color:#4caf50}.device-label mat-icon{font-size:14px;width:14px;height:14px}.device-id{font-family:SF Mono,Monaco,Courier New,monospace;font-size:13px;font-weight:600;color:#333;letter-spacing:.5px}.device-id.waiting{color:#9e9e9e;font-style:italic;font-weight:400}.connection-line{display:flex;flex-direction:column;align-items:center;gap:4px;padding:0 8px}.connection-line .line{width:40px;height:2px;background:#e0e0e0;border-radius:1px;position:relative;overflow:hidden}.connection-line.waiting .line:after{content:\"\";position:absolute;top:0;left:-100%;width:50%;height:100%;background:linear-gradient(90deg,transparent,#2196f3,transparent);animation:shimmer 1.5s infinite}.connection-line.connected .line{background:#4caf50}.connection-line mat-icon{font-size:16px;width:16px;height:16px;color:#9e9e9e}.connection-line.waiting mat-icon{color:#ff9800;animation:blink 1s infinite}.connection-line.connected mat-icon{color:#4caf50}@keyframes shimmer{0%{left:-100%}to{left:200%}}@keyframes blink{0%,to{opacity:1}50%{opacity:.3}}.connection-status{display:flex;align-items:center;gap:8px;padding:8px 16px;border-radius:20px;font-size:13px;font-weight:500}.connection-status .status-indicator{width:8px;height:8px;border-radius:50%;animation:pulse 2s infinite}.connection-status.disconnected{background:#f5f5f5;color:#666}.connection-status.disconnected .status-indicator{background:#999}.connection-status.pending{background:#fff3e0;color:#e65100}.connection-status.pending .status-indicator{background:#ff9800}.connection-status.connected{background:#e8f5e9;color:#2e7d32}.connection-status.connected .status-indicator{background:#4caf50;animation:none}@keyframes pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(1.2)}}.qr-code-wrapper{position:relative;padding:16px;background:#f8f9fa;border-radius:12px;border:2px solid #e0e0e0;transition:all .3s ease}.qr-code-wrapper.expired{opacity:.5}.qr-code-wrapper.connected{border-color:#4caf50;background:#f1f8f4}.qr-canvas{display:block;width:200px;height:200px}.expired-overlay,.connected-overlay{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;background:#fffffff2;border-radius:10px}.expired-overlay mat-icon{font-size:40px;width:40px;height:40px;color:#999}.expired-overlay p{margin:0;color:#666;font-weight:500}.connected-overlay .success-icon{font-size:56px;width:56px;height:56px;color:#4caf50}.connected-overlay p{margin:0;color:#2e7d32;font-size:16px;font-weight:600}.pairing-code-section{text-align:center}.pairing-label{margin:0 0 8px;font-size:13px;color:#666}.pairing-code{display:inline-flex;align-items:center;gap:8px;padding:10px 16px;background:#f5f5f5;border-radius:8px;cursor:pointer;transition:background .2s ease}.pairing-code:hover{background:#eee}.pairing-code .code{font-family:SF Mono,Monaco,Courier New,monospace;font-size:18px;font-weight:600;letter-spacing:2px;color:#1a1a1a}.pairing-code .copy-icon{font-size:18px;width:18px;height:18px;color:#666}.countdown{display:flex;align-items:center;gap:6px;font-size:13px;color:#666}.countdown mat-icon{font-size:18px;width:18px;height:18px}.uploaded-files{width:100%;margin-top:8px;padding-top:16px;border-top:1px solid #f0f0f0}.uploaded-files h4{margin:0 0 12px;font-size:14px;font-weight:600;color:#333}.file-list{display:flex;flex-direction:column;gap:8px;max-height:120px;overflow-y:auto}.file-item{display:flex;align-items:center;gap:8px;padding:8px 12px;background:#f1f8f4;border-radius:8px;font-size:13px;color:#333}.file-item mat-icon{font-size:18px;width:18px;height:18px;color:#4caf50}.file-item span{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.qr-dialog-footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid #f0f0f0;background:#fafafa}@media (max-width: 400px){.qr-dialog-container{min-width:100%;border-radius:0}.qr-canvas{width:180px;height:180px}.device-info-section{flex-direction:column;gap:8px}.connection-line{transform:rotate(90deg)}.device-card{min-width:100%}}\n"] }]
1019
+ args: [{ selector: 'tis-qr-code-dialog', standalone: false, template: "<div class=\"qr-dialog-container\">\n <!-- Header -->\n <div class=\"qr-dialog-header\">\n <h2 class=\"qr-dialog-title\">{{ data.title || 'Upload from Mobile' }}</h2>\n <p class=\"qr-dialog-subtitle\">{{ data.subtitle || 'Scan this QR code with your mobile device to upload images' }}</p>\n <button mat-icon-button class=\"close-btn\" (click)=\"close()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"qr-dialog-content\">\n <!-- Loading State -->\n <div *ngIf=\"isLoading\" class=\"loading-container\">\n <mat-spinner diameter=\"48\"></mat-spinner>\n <p>Generating QR Code...</p>\n </div>\n\n <!-- Error State -->\n <div *ngIf=\"errorMessage && !isLoading\" class=\"error-container\">\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <p class=\"error-message\">{{ errorMessage }}</p>\n <button mat-raised-button color=\"primary\" (click)=\"refreshQrCode()\">\n Try Again\n </button>\n </div>\n\n <!-- Main Content -->\n <div *ngIf=\"!isLoading && !errorMessage\" class=\"qr-content\">\n \n <!-- Device Info Card -->\n <div class=\"device-info-card\">\n <div class=\"device-row\">\n <mat-icon class=\"device-icon desktop\">computer</mat-icon>\n <div class=\"device-details\">\n <span class=\"device-label\">THIS DEVICE</span>\n <span class=\"device-id\" [title]=\"desktopDeviceId\">{{ formatDeviceId(desktopDeviceId) }}</span>\n </div>\n </div>\n \n <div class=\"connection-line\" *ngIf=\"mobileDeviceId\">\n <mat-icon>sync_alt</mat-icon>\n </div>\n \n <div class=\"device-row\" *ngIf=\"mobileDeviceId\">\n <mat-icon class=\"device-icon mobile\">smartphone</mat-icon>\n <div class=\"device-details\">\n <span class=\"device-label\">CONNECTED MOBILE</span>\n <span class=\"device-id\" [title]=\"mobileDeviceId\">{{ formatDeviceId(mobileDeviceId) }}</span>\n </div>\n </div>\n </div>\n\n <!-- Connected State -->\n <div *ngIf=\"isConnected\" class=\"connected-state\">\n <div class=\"connected-indicator\">\n <mat-icon class=\"success-icon\">check_circle</mat-icon>\n <span>Mobile Connected!</span>\n </div>\n <p class=\"connected-message\">\n You can now upload images from your mobile device. \n Select a file/image upload field and the uploaded files will appear automatically.\n </p>\n \n <!-- Uploaded Files -->\n <div class=\"uploaded-files\" *ngIf=\"uploadedFiles.length > 0\">\n <h4>Uploaded Files ({{ uploadedFiles.length }})</h4>\n <div class=\"file-list\">\n <div class=\"file-item\" *ngFor=\"let upload of uploadedFiles\">\n <mat-icon>check_circle</mat-icon>\n <span>{{ upload.file.fileName }}</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- QR Code (only show when not connected) -->\n <div *ngIf=\"!isConnected\" class=\"qr-section\">\n <!-- Connection Status -->\n <div class=\"connection-status\" [ngClass]=\"connectionStatus\">\n <div class=\"status-indicator\"></div>\n <span *ngIf=\"connectionStatus === 'disconnected'\">Waiting for mobile...</span>\n <span *ngIf=\"connectionStatus === 'pending'\">Waiting for connection...</span>\n </div>\n\n <!-- QR Code -->\n <div class=\"qr-code-wrapper\" [class.expired]=\"isExpired\">\n <qrcode \n *ngIf=\"qrData\"\n [qrdata]=\"qrData\" \n [width]=\"data.qrSize || 200\" \n [errorCorrectionLevel]=\"'M'\"\n [elementType]=\"'canvas'\"\n [cssClass]=\"'qr-canvas'\"\n ></qrcode>\n \n <!-- Expired Overlay -->\n <div *ngIf=\"isExpired\" class=\"expired-overlay\">\n <mat-icon>refresh</mat-icon>\n <p>QR Code Expired</p>\n <button mat-raised-button color=\"primary\" (click)=\"refreshQrCode()\">\n Generate New Code\n </button>\n </div>\n </div>\n\n <!-- Countdown Timer -->\n <div class=\"countdown\" *ngIf=\"data.showCountdown !== false && !isExpired\">\n <mat-icon>timer</mat-icon>\n <span>Expires in {{ remainingTime }}</span>\n </div>\n </div>\n\n </div>\n </div>\n\n <!-- Footer -->\n <div class=\"qr-dialog-footer\">\n <button mat-button *ngIf=\"isConnected\" color=\"warn\" (click)=\"disconnect()\">\n <mat-icon>link_off</mat-icon>\n Disconnect\n </button>\n <button mat-button *ngIf=\"!isConnected && !isLoading\" (click)=\"connectToMobile()\">\n <mat-icon>qr_code</mat-icon>\n Connect & Upload via Mobile\n </button>\n <button mat-raised-button color=\"primary\" (click)=\"close()\">\n {{ uploadedFiles.length > 0 ? 'Done' : 'Close' }}\n </button>\n </div>\n</div>\n", styles: [".qr-dialog-container{display:flex;flex-direction:column;min-width:360px;max-width:440px;background:#fff;border-radius:16px;overflow:hidden}.qr-dialog-header{position:relative;padding:24px 24px 16px;text-align:center;border-bottom:1px solid #f0f0f0}.qr-dialog-title{margin:0 0 8px;font-size:20px;font-weight:600;color:#1a1a1a}.qr-dialog-subtitle{margin:0;font-size:14px;color:#666;line-height:1.4}.close-btn{position:absolute;top:12px;right:12px;color:#999}.qr-dialog-content{padding:24px;display:flex;flex-direction:column;align-items:center;min-height:300px}.loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;height:250px;color:#666}.error-container{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;height:250px;text-align:center}.error-icon{font-size:48px;width:48px;height:48px;color:#f44336}.error-message{color:#666;margin:0}.qr-content{display:flex;flex-direction:column;align-items:center;gap:20px;width:100%}.device-info-section{width:100%;display:flex;align-items:center;justify-content:center;gap:12px;padding:16px;background:linear-gradient(135deg,#f8f9fa,#e9ecef);border-radius:12px;margin-bottom:8px}.device-card{display:flex;flex-direction:column;align-items:center;gap:6px;padding:12px 16px;background:#fff;border-radius:10px;min-width:120px;box-shadow:0 2px 8px #0000000f;transition:all .3s ease}.device-card.this-device{border:2px solid #2196f3}.device-card.connected-device{border:2px dashed #9e9e9e}.device-card.connected-device.active{border:2px solid #4caf50}.device-label{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.device-card.this-device .device-label{color:#2196f3}.device-card.connected-device .device-label{color:#9e9e9e}.device-card.connected-device.active .device-label{color:#4caf50}.device-label mat-icon{font-size:14px;width:14px;height:14px}.device-id{font-family:SF Mono,Monaco,Courier New,monospace;font-size:13px;font-weight:600;color:#333;letter-spacing:.5px}.device-id.waiting{color:#9e9e9e;font-style:italic;font-weight:400}.connection-line{display:flex;flex-direction:column;align-items:center;gap:4px;padding:0 8px}.connection-line .line{width:40px;height:2px;background:#e0e0e0;border-radius:1px;position:relative;overflow:hidden}.connection-line.waiting .line:after{content:\"\";position:absolute;top:0;left:-100%;width:50%;height:100%;background:linear-gradient(90deg,transparent,#2196f3,transparent);animation:shimmer 1.5s infinite}.connection-line.connected .line{background:#4caf50}.connection-line mat-icon{font-size:16px;width:16px;height:16px;color:#9e9e9e}.connection-line.waiting mat-icon{color:#ff9800;animation:blink 1s infinite}.connection-line.connected mat-icon{color:#4caf50}@keyframes shimmer{0%{left:-100%}to{left:200%}}@keyframes blink{0%,to{opacity:1}50%{opacity:.3}}.connection-status{display:flex;align-items:center;gap:8px;padding:8px 16px;border-radius:20px;font-size:13px;font-weight:500}.connection-status .status-indicator{width:8px;height:8px;border-radius:50%;animation:pulse 2s infinite}.connection-status.disconnected{background:#f5f5f5;color:#666}.connection-status.disconnected .status-indicator{background:#999}.connection-status.pending{background:#fff3e0;color:#e65100}.connection-status.pending .status-indicator{background:#ff9800}.connection-status.connected{background:#e8f5e9;color:#2e7d32}.connection-status.connected .status-indicator{background:#4caf50;animation:none}@keyframes pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(1.2)}}.qr-code-wrapper{position:relative;padding:16px;background:#f8f9fa;border-radius:12px;border:2px solid #e0e0e0;transition:all .3s ease}.qr-code-wrapper.expired{opacity:.5}.qr-code-wrapper.connected{border-color:#4caf50;background:#f1f8f4}.qr-canvas{display:block;width:200px;height:200px}.expired-overlay,.connected-overlay{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;background:#fffffff2;border-radius:10px}.expired-overlay mat-icon{font-size:40px;width:40px;height:40px;color:#999}.expired-overlay p{margin:0;color:#666;font-weight:500}.connected-overlay .success-icon{font-size:56px;width:56px;height:56px;color:#4caf50}.connected-overlay p{margin:0;color:#2e7d32;font-size:16px;font-weight:600}.pairing-code-section{text-align:center}.pairing-label{margin:0 0 8px;font-size:13px;color:#666}.pairing-code{display:inline-flex;align-items:center;gap:8px;padding:10px 16px;background:#f5f5f5;border-radius:8px;cursor:pointer;transition:background .2s ease}.pairing-code:hover{background:#eee}.pairing-code .code{font-family:SF Mono,Monaco,Courier New,monospace;font-size:18px;font-weight:600;letter-spacing:2px;color:#1a1a1a}.pairing-code .copy-icon{font-size:18px;width:18px;height:18px;color:#666}.countdown{display:flex;align-items:center;gap:6px;font-size:13px;color:#666}.countdown mat-icon{font-size:18px;width:18px;height:18px}.uploaded-files{width:100%;margin-top:8px;padding-top:16px;border-top:1px solid #f0f0f0}.uploaded-files h4{margin:0 0 12px;font-size:14px;font-weight:600;color:#333}.file-list{display:flex;flex-direction:column;gap:8px;max-height:120px;overflow-y:auto}.file-item{display:flex;align-items:center;gap:8px;padding:8px 12px;background:#f1f8f4;border-radius:8px;font-size:13px;color:#333}.file-item mat-icon{font-size:18px;width:18px;height:18px;color:#4caf50}.file-item span{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.qr-dialog-footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid #f0f0f0;background:#fafafa}.device-info-card{width:100%;display:flex;flex-direction:row;align-items:center;justify-content:center;gap:12px;padding:12px 16px;background:linear-gradient(135deg,#f8f9fa,#e9ecef);border-radius:12px;margin-bottom:8px}.device-info-card .device-row{display:flex;align-items:center;gap:10px;flex:0 0 auto}.device-info-card .device-icon{font-size:24px;width:24px;height:24px}.device-info-card .device-icon.desktop{color:#1976d2}.device-info-card .device-icon.mobile{color:#7b1fa2}.device-info-card .device-details{display:flex;flex-direction:column;gap:2px}.device-info-card .device-label{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:#666}.device-info-card .device-id{font-family:SF Mono,Monaco,Courier New,monospace;font-size:12px;font-weight:500;color:#333;cursor:default}.device-info-card .connection-line{display:flex;align-items:center;padding:0 4px}.device-info-card .connection-line mat-icon{font-size:18px;width:18px;height:18px;color:#4caf50}@media (max-width: 400px){.qr-dialog-container{min-width:100%;border-radius:0}.qr-canvas{width:180px;height:180px}.device-info-section,.device-info-card{flex-direction:column;gap:8px}.device-info-card .connection-line,.connection-line{transform:rotate(90deg)}.device-card{min-width:100%}}\n"] }]
1087
1020
  }], ctorParameters: () => [{ type: i1$2.MatDialogRef }, { type: undefined, decorators: [{
1088
1021
  type: Inject,
1089
1022
  args: [MAT_DIALOG_DATA]
1090
- }] }, { type: TisRemoteUploadService }], propDecorators: { qrCanvas: [{
1091
- type: ViewChild,
1092
- args: ['qrCanvas', { static: false }]
1093
- }] } });
1023
+ }] }, { type: TisRemoteUploadService }] });
1094
1024
 
1095
1025
  const generateRandomString = (length) => {
1096
1026
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
@@ -2811,7 +2741,8 @@ class TisImageAndFileUploadAndViewModule {
2811
2741
  HttpClientModule,
2812
2742
  NgxExtendedPdfViewerModule,
2813
2743
  FormsModule,
2814
- ReactiveFormsModule, MatTooltipModule,
2744
+ ReactiveFormsModule,
2745
+ QRCodeComponent, MatTooltipModule,
2815
2746
  MatIconModule,
2816
2747
  MatSnackBarModule,
2817
2748
  MatProgressSpinnerModule,
@@ -2823,7 +2754,8 @@ class TisImageAndFileUploadAndViewModule {
2823
2754
  HttpClientModule,
2824
2755
  NgxExtendedPdfViewerModule,
2825
2756
  FormsModule,
2826
- ReactiveFormsModule, uiImports, DragDropModule] });
2757
+ ReactiveFormsModule,
2758
+ QRCodeComponent, uiImports, DragDropModule] });
2827
2759
  }
2828
2760
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TisImageAndFileUploadAndViewModule, decorators: [{
2829
2761
  type: NgModule,
@@ -2845,6 +2777,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImpor
2845
2777
  NgxExtendedPdfViewerModule,
2846
2778
  FormsModule,
2847
2779
  ReactiveFormsModule,
2780
+ QRCodeComponent,
2848
2781
  ...uiImports,
2849
2782
  DragDropModule
2850
2783
  ],