UNPKG

biometry-angular-components

Version:

Angular UI component library for capturing biometric data

114 lines 21 kB
import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; import { CommonModule } from '@angular/common'; import * as i0 from "@angular/core"; import * as i1 from "../../services/permissions.service"; import * as i2 from "@angular/common"; export class DocScanComponent { perms; zone; cdr; rectWidth = 640; rectHeight = 400; noShadow = false; capture = new EventEmitter(); isConfirming = false; capturedUrl = null; capturedFile = null; stream; QUALITY_MULTIPLIER = 3; constructor(perms, zone, cdr) { this.perms = perms; this.zone = zone; this.cdr = cdr; } ngOnInit() { this.initStream(); } ngOnDestroy() { this.stopStream(); this.revokeBlob(); } async initStream() { const { stream, granted } = await this.perms.requestCamera({ width: this.rectWidth, height: this.rectHeight, }); if (!granted || !stream) return; this.zone.run(() => { this.stream = stream; this.cdr.markForCheck(); }); } stopStream() { if (!this.stream) return; this.perms.stopStream(this.stream); this.stream = undefined; } handleCapture(videoEl) { if (!videoEl) return; const canvas = document.createElement('canvas'); canvas.width = this.rectWidth * this.QUALITY_MULTIPLIER; canvas.height = this.rectHeight * this.QUALITY_MULTIPLIER; const ctx = canvas.getContext('2d'); if (!ctx) return; const videoAspect = videoEl.videoWidth / videoEl.videoHeight; const targetAspect = this.rectWidth / this.rectHeight; let sx = 0, sy = 0, sw = videoEl.videoWidth, sh = videoEl.videoHeight; if (videoAspect > targetAspect) { sw = videoEl.videoHeight * targetAspect; sx = (videoEl.videoWidth - sw) / 2; } else { sh = videoEl.videoWidth / targetAspect; sy = (videoEl.videoHeight - sh) / 2; } ctx.drawImage(videoEl, sx, sy, sw, sh, 0, 0, canvas.width, canvas.height); canvas.toBlob(blob => { if (!blob) return; const file = new File([blob], 'document.jpg', { type: 'image/jpeg' }); this.zone.run(() => { this.capturedFile = file; this.capturedUrl = URL.createObjectURL(blob); this.isConfirming = true; this.cdr.markForCheck(); }); }, 'image/jpeg', 0.98); } handleConfirm() { if (!this.capturedFile) return; this.capture.emit(this.capturedFile); } handleDecline() { this.revokeBlob(); this.capturedFile = null; this.isConfirming = false; this.initStream(); } revokeBlob() { if (!this.capturedUrl) return; URL.revokeObjectURL(this.capturedUrl); this.capturedUrl = null; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DocScanComponent, deps: [{ token: i1.PermissionsService }, { token: i0.NgZone }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: DocScanComponent, isStandalone: true, selector: "bio-doc-scan", inputs: { rectWidth: "rectWidth", rectHeight: "rectHeight", noShadow: "noShadow" }, outputs: { capture: "capture" }, ngImport: i0, template: "<div class=\"docscan-wrapper\"\n [ngStyle]=\"{ width: rectWidth + 'px', height: rectHeight + 64 + 'px', boxShadow: noShadow ? 'none' : '0 4px 24px rgba(0,0,0,0.12)' }\">\n <ng-container *ngIf=\"!isConfirming; else confirmTpl\">\n <div class=\"docscan-camera-area\">\n <video #video [width]=\"rectWidth\" [height]=\"rectHeight\" autoplay muted playsinline\n [style.border-radius.px]=\"noShadow ? 0 : 8\" class=\"docscan-video\" [srcObject]=\"stream\">\n </video>\n <div class=\"docscan-dark-overlay\"></div>\n <div class=\"docscan-dashed-area\">\n <span class=\"docscan-guidance-text\">Place your document here</span>\n </div>\n </div>\n <button class=\"docscan-capture-btn\" (click)=\"handleCapture(video)\">Capture</button>\n </ng-container>\n\n <ng-template #confirmTpl>\n <div class=\"docscan-preview\">\n <img *ngIf=\"capturedUrl\" [src]=\"capturedUrl\" alt=\"Captured document\" />\n </div>\n <div class=\"docscan-confirm-text\">Do you want to use this photo?</div>\n <div class=\"docscan-confirm-btns\">\n <button class=\"docscan-icon-btn\" (click)=\"handleDecline()\">\n <!-- SVG Retake -->\n </button>\n <button class=\"docscan-icon-btn\" (click)=\"handleConfirm()\">\n <!-- SVG Confirm -->\n </button>\n </div>\n </ng-template>\n</div>", styles: [".docscan-wrapper{background:#fff;border-radius:10px;padding:20px;display:flex;flex-direction:column;align-items:center;justify-content:center;margin:0 auto}.docscan-wrapper .docscan-camera-area{position:relative;width:100%;height:100%;margin-bottom:20px}.docscan-wrapper .docscan-camera-area video{object-fit:cover;position:absolute;top:0;left:0;z-index:0;width:100%;height:100%}.docscan-wrapper .docscan-camera-area .docscan-dark-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:#0006;border-radius:8px;z-index:1}.docscan-wrapper .docscan-camera-area .docscan-dashed-area{position:absolute;top:10%;left:10%;width:80%;height:80%;border:2.5px dashed #fff;border-radius:16px;z-index:2;display:flex;align-items:center;justify-content:center}.docscan-wrapper .docscan-camera-area .docscan-dashed-area .docscan-guidance-text{color:#fff;font-size:12px;text-shadow:0 1px 4px rgba(0,0,0,.25)}.docscan-wrapper .docscan-capture-btn{padding:12px 32px;font-size:18px;border-radius:8px;border:none;background:#1976d2;color:#fff;cursor:pointer;box-shadow:0 2px 8px #0000001a}.docscan-wrapper .docscan-preview img{max-width:100%;max-height:100%;border-radius:8px}.docscan-wrapper .docscan-confirm-btns{display:flex;justify-content:center;gap:32px;margin-top:16px}.docscan-wrapper .docscan-icon-btn{background:none;border:none;cursor:pointer;padding:8px;border-radius:50%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DocScanComponent, decorators: [{ type: Component, args: [{ selector: 'bio-doc-scan', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"docscan-wrapper\"\n [ngStyle]=\"{ width: rectWidth + 'px', height: rectHeight + 64 + 'px', boxShadow: noShadow ? 'none' : '0 4px 24px rgba(0,0,0,0.12)' }\">\n <ng-container *ngIf=\"!isConfirming; else confirmTpl\">\n <div class=\"docscan-camera-area\">\n <video #video [width]=\"rectWidth\" [height]=\"rectHeight\" autoplay muted playsinline\n [style.border-radius.px]=\"noShadow ? 0 : 8\" class=\"docscan-video\" [srcObject]=\"stream\">\n </video>\n <div class=\"docscan-dark-overlay\"></div>\n <div class=\"docscan-dashed-area\">\n <span class=\"docscan-guidance-text\">Place your document here</span>\n </div>\n </div>\n <button class=\"docscan-capture-btn\" (click)=\"handleCapture(video)\">Capture</button>\n </ng-container>\n\n <ng-template #confirmTpl>\n <div class=\"docscan-preview\">\n <img *ngIf=\"capturedUrl\" [src]=\"capturedUrl\" alt=\"Captured document\" />\n </div>\n <div class=\"docscan-confirm-text\">Do you want to use this photo?</div>\n <div class=\"docscan-confirm-btns\">\n <button class=\"docscan-icon-btn\" (click)=\"handleDecline()\">\n <!-- SVG Retake -->\n </button>\n <button class=\"docscan-icon-btn\" (click)=\"handleConfirm()\">\n <!-- SVG Confirm -->\n </button>\n </div>\n </ng-template>\n</div>", styles: [".docscan-wrapper{background:#fff;border-radius:10px;padding:20px;display:flex;flex-direction:column;align-items:center;justify-content:center;margin:0 auto}.docscan-wrapper .docscan-camera-area{position:relative;width:100%;height:100%;margin-bottom:20px}.docscan-wrapper .docscan-camera-area video{object-fit:cover;position:absolute;top:0;left:0;z-index:0;width:100%;height:100%}.docscan-wrapper .docscan-camera-area .docscan-dark-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:#0006;border-radius:8px;z-index:1}.docscan-wrapper .docscan-camera-area .docscan-dashed-area{position:absolute;top:10%;left:10%;width:80%;height:80%;border:2.5px dashed #fff;border-radius:16px;z-index:2;display:flex;align-items:center;justify-content:center}.docscan-wrapper .docscan-camera-area .docscan-dashed-area .docscan-guidance-text{color:#fff;font-size:12px;text-shadow:0 1px 4px rgba(0,0,0,.25)}.docscan-wrapper .docscan-capture-btn{padding:12px 32px;font-size:18px;border-radius:8px;border:none;background:#1976d2;color:#fff;cursor:pointer;box-shadow:0 2px 8px #0000001a}.docscan-wrapper .docscan-preview img{max-width:100%;max-height:100%;border-radius:8px}.docscan-wrapper .docscan-confirm-btns{display:flex;justify-content:center;gap:32px;margin-top:16px}.docscan-wrapper .docscan-icon-btn{background:none;border:none;cursor:pointer;padding:8px;border-radius:50%}\n"] }] }], ctorParameters: () => [{ type: i1.PermissionsService }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }], propDecorators: { rectWidth: [{ type: Input }], rectHeight: [{ type: Input }], noShadow: [{ type: Input }], capture: [{ type: Output }] } }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZG9jLXNjYW4uY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vc3JjL2xpYi9jb21wb25lbnRzL2RvYy1zY2FuL2RvYy1zY2FuLmNvbXBvbmVudC50cyIsIi4uLy4uLy4uLy4uLy4uL3NyYy9saWIvY29tcG9uZW50cy9kb2Mtc2Nhbi9kb2Mtc2Nhbi5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsU0FBUyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsWUFBWSxFQUFxQix1QkFBdUIsRUFBcUIsTUFBTSxlQUFlLENBQUM7QUFDdEksT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLGlCQUFpQixDQUFDOzs7O0FBVy9DLE1BQU0sT0FBTyxnQkFBZ0I7SUFhUDtJQUFtQztJQUFzQjtJQVpwRSxTQUFTLEdBQUcsR0FBRyxDQUFDO0lBQ2hCLFVBQVUsR0FBRyxHQUFHLENBQUM7SUFDakIsUUFBUSxHQUFHLEtBQUssQ0FBQztJQUNoQixPQUFPLEdBQUcsSUFBSSxZQUFZLEVBQVEsQ0FBQztJQUU3QyxZQUFZLEdBQUcsS0FBSyxDQUFDO0lBQ3JCLFdBQVcsR0FBa0IsSUFBSSxDQUFDO0lBQ2xDLFlBQVksR0FBZ0IsSUFBSSxDQUFDO0lBRTFCLE1BQU0sQ0FBZTtJQUNYLGtCQUFrQixHQUFHLENBQUMsQ0FBQztJQUV4QyxZQUFvQixLQUF5QixFQUFVLElBQVksRUFBVSxHQUFzQjtRQUEvRSxVQUFLLEdBQUwsS0FBSyxDQUFvQjtRQUFVLFNBQUksR0FBSixJQUFJLENBQVE7UUFBVSxRQUFHLEdBQUgsR0FBRyxDQUFtQjtJQUFJLENBQUM7SUFFeEcsUUFBUTtRQUNOLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztJQUNwQixDQUFDO0lBRUQsV0FBVztRQUNULElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUNsQixJQUFJLENBQUMsVUFBVSxFQUFFLENBQUM7SUFDcEIsQ0FBQztJ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cHV0LCBPdXRwdXQsIEV2ZW50RW1pdHRlciwgT25EZXN0cm95LCBOZ1pvbmUsIENoYW5nZURldGVjdGlvblN0cmF0ZWd5LCBDaGFuZ2VEZXRlY3RvclJlZiB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgQ29tbW9uTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvY29tbW9uJztcbmltcG9ydCB7IFBlcm1pc3Npb25zU2VydmljZSB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL3Blcm1pc3Npb25zLnNlcnZpY2UnO1xuXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICdiaW8tZG9jLXNjYW4nLFxuICBzdGFuZGFsb25lOiB0cnVlLFxuICBpbXBvcnRzOiBbQ29tbW9uTW9kdWxlXSxcbiAgdGVtcGxhdGVVcmw6ICcuL2RvYy1zY2FuLmNvbXBvbmVudC5odG1sJyxcbiAgc3R5bGVVcmxzOiBbJy4vZG9jLXNjYW4uY29tcG9uZW50LnNjc3MnXSxcbiAgY2hhbmdlRGV0ZWN0aW9uOiBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneS5PblB1c2gsXG59KVxuZXhwb3J0IGNsYXNzIERvY1NjYW5Db21wb25lbnQgaW1wbGVtZW50cyBPbkRlc3Ryb3kge1xuICBASW5wdXQoKSByZWN0V2lkdGggPSA2NDA7XG4gIEBJbnB1dCgpIHJlY3RIZWlnaHQgPSA0MDA7XG4gIEBJbnB1dCgpIG5vU2hhZG93ID0gZmFsc2U7XG4gIEBPdXRwdXQoKSBjYXB0dXJlID0gbmV3IEV2ZW50RW1pdHRlcjxGaWxlPigpO1xuXG4gIGlzQ29uZmlybWluZyA9IGZhbHNlO1xuICBjYXB0dXJlZFVybDogc3RyaW5nIHwgbnVsbCA9IG51bGw7XG4gIGNhcHR1cmVkRmlsZTogRmlsZSB8IG51bGwgPSBudWxsO1xuXG4gIHB1YmxpYyBzdHJlYW0/OiBNZWRpYVN0cmVhbTtcbiAgcHJpdmF0ZSByZWFkb25seSBRVUFMSVRZX01VTFRJUExJRVIgPSAzO1xuXG4gIGNvbnN0cnVjdG9yKHByaXZhdGUgcGVybXM6IFBlcm1pc3Npb25zU2VydmljZSwgcHJpdmF0ZSB6b25lOiBOZ1pvbmUsIHByaXZhdGUgY2RyOiBDaGFuZ2VEZXRlY3RvclJlZikgeyB9XG5cbiAgbmdPbkluaXQoKSB7XG4gICAgdGhpcy5pbml0U3RyZWFtKCk7XG4gIH1cblxuICBuZ09uRGVzdHJveSgpIHtcbiAgICB0aGlzLnN0b3BTdHJlYW0oKTtcbiAgICB0aGlzLnJldm9rZUJsb2IoKTtcbiAgfVxuXG4gIGFzeW5jIGluaXRTdHJlYW0oKSB7XG4gICAgY29uc3QgeyBzdHJlYW0sIGdyYW50ZWQgfSA9IGF3YWl0IHRoaXMucGVybXMucmVxdWVzdENhbWVyYSh7XG4gICAgICB3aWR0aDogdGhpcy5yZWN0V2lkdGgsXG4gICAgICBoZWlnaHQ6IHRoaXMucmVjdEhlaWdodCxcbiAgICB9KTtcblxuICAgIGlmICghZ3JhbnRlZCB8fCAhc3RyZWFtKSByZXR1cm47XG5cbiAgICB0aGlzLnpvbmUucnVuKCgpID0+IHtcbiAgICAgIHRoaXMuc3RyZWFtID0gc3RyZWFtO1xuICAgICAgdGhpcy5jZHIubWFya0ZvckNoZWNrKCk7XG4gICAgfSk7XG4gIH1cblxuICBzdG9wU3RyZWFtKCkge1xuICAgIGlmICghdGhpcy5zdHJlYW0pIHJldHVybjtcbiAgICB0aGlzLnBlcm1zLnN0b3BTdHJlYW0odGhpcy5zdHJlYW0pO1xuICAgIHRoaXMuc3RyZWFtID0gdW5kZWZpbmVkO1xuICB9XG5cbiAgaGFuZGxlQ2FwdHVyZSh2aWRlb0VsOiBIVE1MVmlkZW9FbGVtZW50KSB7XG4gICAgaWYgKCF2aWRlb0VsKSByZXR1cm47XG5cbiAgICBjb25zdCBjYW52YXMgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdjYW52YXMnKTtcbiAgICBjYW52YXMud2lkdGggPSB0aGlzLnJlY3RXaWR0aCAqIHRoaXMuUVVBTElUWV9NVUxUSVBMSUVSO1xuICAgIGNhbnZhcy5oZWlnaHQgPSB0aGlzLnJlY3RIZWlnaHQgKiB0aGlzLlFVQUxJVFlfTVVMVElQTElFUjtcbiAgICBjb25zdCBjdHggPSBjYW52YXMuZ2V0Q29udGV4dCgnMmQnKTtcbiAgICBpZiAoIWN0eCkgcmV0dXJuO1xuXG4gICAgY29uc3QgdmlkZW9Bc3BlY3QgPSB2aWRlb0VsLnZpZGVvV2lkdGggLyB2aWRlb0VsLnZpZGVvSGVpZ2h0O1xuICAgIGNvbnN0IHRhcmdldEFzcGVjdCA9IHRoaXMucmVjdFdpZHRoIC8gdGhpcy5yZWN0SGVpZ2h0O1xuICAgIGxldCBzeCA9IDAsIHN5ID0gMCwgc3cgPSB2aWRlb0VsLnZpZGVvV2lkdGgsIHNoID0gdmlkZW9FbC52aWRlb0hlaWdodDtcbiAgICBpZiAodmlkZW9Bc3BlY3QgPiB0YXJnZXRBc3BlY3QpIHtcbiAgICAgIHN3ID0gdmlkZW9FbC52aWRlb0hlaWdodCAqIHRhcmdldEFzcGVjdDtcbiAgICAgIHN4ID0gKHZpZGVvRWwudmlkZW9XaWR0aCAtIHN3KSAvIDI7XG4gICAgfSBlbHNlIHtcbiAgICAgIHNoID0gdmlkZW9FbC52aWRlb1dpZHRoIC8gdGFyZ2V0QXNwZWN0O1xuICAgICAgc3kgPSAodmlkZW9FbC52aWRlb0hlaWdodCAtIHNoKSAvIDI7XG4gICAgfVxuXG4gICAgY3R4LmRyYXdJbWFnZSh2aWRlb0VsLCBzeCwgc3ksIHN3LCBzaCwgMCwgMCwgY2FudmFzLndpZHRoLCBjYW52YXMuaGVpZ2h0KTtcblxuICAgIGNhbnZhcy50b0Jsb2IoYmxvYiA9PiB7XG4gICAgICBpZiAoIWJsb2IpIHJldHVybjtcbiAgICAgIGNvbnN0IGZpbGUgPSBuZXcgRmlsZShbYmxvYl0sICdkb2N1bWVudC5qcGcnLCB7IHR5cGU6ICdpbWFnZS9qcGVnJyB9KTtcbiAgICAgIHRoaXMuem9uZS5ydW4oKCkgPT4ge1xuICAgICAgICB0aGlzLmNhcHR1cmVkRmlsZSA9IGZpbGU7XG4gICAgICAgIHRoaXMuY2FwdHVyZWRVcmwgPSBVUkwuY3JlYXRlT2JqZWN0VVJMKGJsb2IpO1xuICAgICAgICB0aGlzLmlzQ29uZmlybWluZyA9IHRydWU7XG4gICAgICAgIHRoaXMuY2RyLm1hcmtGb3JDaGVjaygpO1xuICAgICAgfSk7XG4gICAgfSwgJ2ltYWdlL2pwZWcnLCAwLjk4KTtcbiAgfVxuXG4gIGhhbmRsZUNvbmZpcm0oKSB7XG4gICAgaWYgKCF0aGlzLmNhcHR1cmVkRmlsZSkgcmV0dXJuO1xuICAgIHRoaXMuY2FwdHVyZS5lbWl0KHRoaXMuY2FwdHVyZWRGaWxlKTtcbiAgfVxuXG4gIGhhbmRsZURlY2xpbmUoKSB7XG4gICAgdGhpcy5yZXZva2VCbG9iKCk7XG4gICAgdGhpcy5jYXB0dXJlZEZpbGUgPSBudWxsO1xuICAgIHRoaXMuaXNDb25maXJtaW5nID0gZmFsc2U7XG4gICAgdGhpcy5pbml0U3RyZWFtKCk7XG4gIH1cblxuICBwcml2YXRlIHJldm9rZUJsb2IoKSB7XG4gICAgaWYgKCF0aGlzLmNhcHR1cmVkVXJsKSByZXR1cm47XG4gICAgVVJMLnJldm9rZU9iamVjdFVSTCh0aGlzLmNhcHR1cmVkVXJsKTtcbiAgICB0aGlzLmNhcHR1cmVkVXJsID0gbnVsbDtcbiAgfVxufSIsIjxkaXYgY2xhc3M9XCJkb2NzY2FuLXdyYXBwZXJcIlxuICBbbmdTdHlsZV09XCJ7IHdpZHRoOiByZWN0V2lkdGggKyAncHgnLCBoZWlnaHQ6IHJlY3RIZWlnaHQgKyA2NCArICdweCcsIGJveFNoYWRvdzogbm9TaGFkb3cgPyAnbm9uZScgOiAnMCA0cHggMjRweCByZ2JhKDAsMCwwLDAuMTIpJyB9XCI+XG4gIDxuZy1jb250YWluZXIgKm5nSWY9XCIhaXNDb25maXJtaW5nOyBlbHNlIGNvbmZpcm1UcGxcIj5cbiAgICA8ZGl2IGNsYXNzPVwiZG9jc2Nhbi1jYW1lcmEtYXJlYVwiPlxuICAgICAgPHZpZGVvICN2aWRlbyBbd2lkdGhdPVwicmVjdFdpZHRoXCIgW2hlaWdodF09XCJyZWN0SGVpZ2h0XCIgYXV0b3BsYXkgbXV0ZWQgcGxheXNpbmxpbmVcbiAgICAgICAgW3N0eWxlLmJvcmRlci1yYWRpdXMucHhdPVwibm9TaGFkb3cgPyAwIDogOFwiIGNsYXNzPVwiZG9jc2Nhbi12aWRlb1wiIFtzcmNPYmplY3RdPVwic3RyZWFtXCI+XG4gICAgICA8L3ZpZGVvPlxuICAgICAgPGRpdiBjbGFzcz1cImRvY3NjYW4tZGFyay1vdmVybGF5XCI+PC9kaXY+XG4gICAgICA8ZGl2IGNsYXNzPVwiZG9jc2Nhbi1kYXNoZWQtYXJlYVwiPlxuICAgICAgICA8c3BhbiBjbGFzcz1cImRvY3NjYW4tZ3VpZGFuY2UtdGV4dFwiPlBsYWNlIHlvdXIgZG9jdW1lbnQgaGVyZTwvc3Bhbj5cbiAgICAgIDwvZGl2PlxuICAgIDwvZGl2PlxuICAgIDxidXR0b24gY2xhc3M9XCJkb2NzY2FuLWNhcHR1cmUtYnRuXCIgKGNsaWNrKT1cImhhbmRsZUNhcHR1cmUodmlkZW8pXCI+Q2FwdHVyZTwvYnV0dG9uPlxuICA8L25nLWNvbnRhaW5lcj5cblxuICA8bmctdGVtcGxhdGUgI2NvbmZpcm1UcGw+XG4gICAgPGRpdiBjbGFzcz1cImRvY3NjYW4tcHJldmlld1wiPlxuICAgICAgPGltZyAqbmdJZj1cImNhcHR1cmVkVXJsXCIgW3NyY109XCJjYXB0dXJlZFVybFwiIGFsdD1cIkNhcHR1cmVkIGRvY3VtZW50XCIgLz5cbiAgICA8L2Rpdj5cbiAgICA8ZGl2IGNsYXNzPVwiZG9jc2Nhbi1jb25maXJtLXRleHRcIj5EbyB5b3Ugd2FudCB0byB1c2UgdGhpcyBwaG90bz88L2Rpdj5cbiAgICA8ZGl2IGNsYXNzPVwiZG9jc2Nhbi1jb25maXJtLWJ0bnNcIj5cbiAgICAgIDxidXR0b24gY2xhc3M9XCJkb2NzY2FuLWljb24tYnRuXCIgKGNsaWNrKT1cImhhbmRsZURlY2xpbmUoKVwiPlxuICAgICAgICA8IS0tIFNWRyBSZXRha2UgLS0+XG4gICAgICA8L2J1dHRvbj5cbiAgICAgIDxidXR0b24gY2xhc3M9XCJkb2NzY2FuLWljb24tYnRuXCIgKGNsaWNrKT1cImhhbmRsZUNvbmZpcm0oKVwiPlxuICAgICAgICA8IS0tIFNWRyBDb25maXJtIC0tPlxuICAgICAgPC9idXR0b24+XG4gICAgPC9kaXY+XG4gIDwvbmctdGVtcGxhdGU+XG48L2Rpdj4iXX0=