@ruc-lib/screen-recorder 2.9.1 → 3.1.0
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.
- package/README.md +1 -150
- package/{fesm2020 → fesm2022}/ruc-lib-screen-recorder.mjs +622 -634
- package/fesm2022/ruc-lib-screen-recorder.mjs.map +1 -0
- package/index.d.ts +288 -4
- package/package.json +14 -22
- package/esm2020/index.mjs +0 -5
- package/esm2020/lib/models/screen-recorder.models.mjs +0 -20
- package/esm2020/lib/ruclib-screen-recorder.module.mjs +0 -20
- package/esm2020/lib/screen-recorder/screen-recorder-constant.mjs +0 -44
- package/esm2020/lib/screen-recorder/screen-recorder.component.mjs +0 -242
- package/esm2020/lib/services/screen-recorder.service.mjs +0 -333
- package/esm2020/ruc-lib-screen-recorder.mjs +0 -5
- package/fesm2015/ruc-lib-screen-recorder.mjs +0 -644
- package/fesm2015/ruc-lib-screen-recorder.mjs.map +0 -1
- package/fesm2020/ruc-lib-screen-recorder.mjs.map +0 -1
- package/lib/models/screen-recorder.models.d.ts +0 -51
- package/lib/ruclib-screen-recorder.module.d.ts +0 -8
- package/lib/screen-recorder/screen-recorder-constant.d.ts +0 -27
- package/lib/screen-recorder/screen-recorder.component.d.ts +0 -100
- package/lib/services/screen-recorder.service.d.ts +0 -111
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
import { Component, Input, ChangeDetectionStrategy, ChangeDetectorRef, ViewChild, ElementRef } from '@angular/core';
|
|
2
|
-
import { DomSanitizer } from '@angular/platform-browser';
|
|
3
|
-
import { Subscription } from 'rxjs';
|
|
4
|
-
import { ScreenRecorderPosition, DisplayFormat, RecordingState, } from '../models/screen-recorder.models';
|
|
5
|
-
import { ScreenRecorderService } from '../services/screen-recorder.service';
|
|
6
|
-
import { ScreenRecorderConstants } from './screen-recorder-constant';
|
|
7
|
-
import * as i0 from "@angular/core";
|
|
8
|
-
import * as i1 from "../services/screen-recorder.service";
|
|
9
|
-
import * as i2 from "@angular/platform-browser";
|
|
10
|
-
import * as i3 from "./screen-recorder-constant";
|
|
11
|
-
import * as i4 from "@angular/common";
|
|
12
|
-
export class ScreenRecorderComponent {
|
|
13
|
-
set rucInputData(value) {
|
|
14
|
-
this._options = {
|
|
15
|
-
position: ScreenRecorderPosition.Bottom,
|
|
16
|
-
displayFormat: DisplayFormat.Icon,
|
|
17
|
-
showAudioToggle: true,
|
|
18
|
-
defaultAudioState: true,
|
|
19
|
-
downloadFileName: 'screen-recording.webm',
|
|
20
|
-
icons: { ...this.screenRecordingConstant.DEFAULT_ICONS },
|
|
21
|
-
showTimer: true,
|
|
22
|
-
showPauseResume: true,
|
|
23
|
-
showPlaybackControls: true,
|
|
24
|
-
showDownloadButton: true,
|
|
25
|
-
useUTCForTimer: false,
|
|
26
|
-
allowSpecificAreaSelection: true,
|
|
27
|
-
accessibilityLabels: { ...this.screenRecordingConstant.DEFAULT_LABELS },
|
|
28
|
-
...value,
|
|
29
|
-
};
|
|
30
|
-
// Deep merge icons and labels
|
|
31
|
-
if (value.icons) {
|
|
32
|
-
this._options.icons = { ...this.screenRecordingConstant.DEFAULT_ICONS, ...value.icons };
|
|
33
|
-
}
|
|
34
|
-
if (value.accessibilityLabels) {
|
|
35
|
-
this._options.accessibilityLabels = { ...this.screenRecordingConstant.DEFAULT_LABELS, ...value.accessibilityLabels };
|
|
36
|
-
}
|
|
37
|
-
this.isAudioEnabled = this._options.defaultAudioState ?? true;
|
|
38
|
-
}
|
|
39
|
-
get rucInputData() {
|
|
40
|
-
return this._options;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Checks if recording is currently in progress
|
|
44
|
-
*
|
|
45
|
-
* @returns boolean - True if recording is in progress, false otherwise
|
|
46
|
-
*/
|
|
47
|
-
isRecordingInProgress() {
|
|
48
|
-
return this.recordingState === RecordingState.Recording;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Gets the appropriate icon for recording based on audio state and settings
|
|
52
|
-
* @returns string - The icon name to display
|
|
53
|
-
*/
|
|
54
|
-
getRecordingIcon() {
|
|
55
|
-
if (this.isAudioEnabled && this.rucInputData.showAudioToggle) {
|
|
56
|
-
return this.getIcon('recordWithAudio');
|
|
57
|
-
}
|
|
58
|
-
return this.getIcon('record');
|
|
59
|
-
}
|
|
60
|
-
constructor(screenRecorderService, sanitizer, cdr, screenRecordingConstant) {
|
|
61
|
-
this.screenRecorderService = screenRecorderService;
|
|
62
|
-
this.sanitizer = sanitizer;
|
|
63
|
-
this.cdr = cdr;
|
|
64
|
-
this.screenRecordingConstant = screenRecordingConstant;
|
|
65
|
-
this.recordingState = RecordingState.Idle;
|
|
66
|
-
this.recordedTime = 0;
|
|
67
|
-
this.formattedTime = '00:00:00';
|
|
68
|
-
this.safeRecordedUrl = null;
|
|
69
|
-
this.recordingTimestamp = '';
|
|
70
|
-
this.errorMessage = null;
|
|
71
|
-
this.isBrowserSupported = true;
|
|
72
|
-
this.isAudioEnabled = true;
|
|
73
|
-
// Expose enums to template
|
|
74
|
-
this.DisplayFormat = DisplayFormat;
|
|
75
|
-
this.RecordingState = RecordingState;
|
|
76
|
-
this.ScreenRecorderPosition = ScreenRecorderPosition;
|
|
77
|
-
this.subscriptions = new Subscription();
|
|
78
|
-
// Initialize with default options. The setter will override if an input is provided.
|
|
79
|
-
if (!this._options) {
|
|
80
|
-
this.rucInputData = {}; // Trigger setter with empty object to apply defaults
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
ngOnInit() {
|
|
84
|
-
this.subscriptions.add(this.screenRecorderService.recordingState$.subscribe((state) => {
|
|
85
|
-
this.recordingState = state;
|
|
86
|
-
if (state === RecordingState.Idle || state === RecordingState.Stopped) {
|
|
87
|
-
// Ensure audio toggle is reset to default if needed, or based on user action
|
|
88
|
-
}
|
|
89
|
-
this.cdr.detectChanges();
|
|
90
|
-
}));
|
|
91
|
-
this.subscriptions.add(this.screenRecorderService.recordingTimestamp$.subscribe((timestamp) => {
|
|
92
|
-
this.recordingTimestamp = timestamp;
|
|
93
|
-
this.cdr.detectChanges();
|
|
94
|
-
}));
|
|
95
|
-
this.subscriptions.add(this.screenRecorderService.recordedTime$.subscribe((time) => {
|
|
96
|
-
this.recordedTime = time;
|
|
97
|
-
this.formattedTime = this.formatTimeDisplay(time);
|
|
98
|
-
this.cdr.detectChanges();
|
|
99
|
-
}));
|
|
100
|
-
this.subscriptions.add(this.screenRecorderService.recordedUrl$.subscribe((url) => {
|
|
101
|
-
this.safeRecordedUrl = url ? this.sanitizer.bypassSecurityTrustUrl(url) : null;
|
|
102
|
-
this.cdr.detectChanges();
|
|
103
|
-
}));
|
|
104
|
-
this.subscriptions.add(this.screenRecorderService.error$.subscribe((error) => {
|
|
105
|
-
if (error != this.screenRecordingConstant.PERMISSION_DENIED) {
|
|
106
|
-
this.errorMessage = error;
|
|
107
|
-
}
|
|
108
|
-
this.cdr.detectChanges();
|
|
109
|
-
}));
|
|
110
|
-
}
|
|
111
|
-
ngOnDestroy() {
|
|
112
|
-
if (this.subscriptions) {
|
|
113
|
-
this.subscriptions.unsubscribe();
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Toggles the recording state between start and stop
|
|
118
|
-
*
|
|
119
|
-
* @returns Promise<void>
|
|
120
|
-
*/
|
|
121
|
-
async toggleRecord() {
|
|
122
|
-
this.errorMessage = null; // Clear previous errors
|
|
123
|
-
if (this.recordingState === RecordingState.Idle || this.recordingState === RecordingState.Stopped) {
|
|
124
|
-
// Set recording timestamp when starting recording
|
|
125
|
-
const now = new Date();
|
|
126
|
-
this.recordingTimestamp = now.toLocaleString('en-IN', {
|
|
127
|
-
year: 'numeric',
|
|
128
|
-
month: '2-digit',
|
|
129
|
-
day: '2-digit',
|
|
130
|
-
hour: '2-digit',
|
|
131
|
-
minute: '2-digit',
|
|
132
|
-
hour12: true
|
|
133
|
-
});
|
|
134
|
-
await this.screenRecorderService.startRecording({ audio: this.isAudioEnabled });
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
this.screenRecorderService.stopRecording();
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Toggles between pause and resume states during recording
|
|
142
|
-
*
|
|
143
|
-
* @returns void
|
|
144
|
-
*/
|
|
145
|
-
togglePauseResume() {
|
|
146
|
-
this.errorMessage = null;
|
|
147
|
-
if (this.recordingState === RecordingState.Recording) {
|
|
148
|
-
this.screenRecorderService.pauseRecording();
|
|
149
|
-
}
|
|
150
|
-
else if (this.recordingState === RecordingState.Paused) {
|
|
151
|
-
this.screenRecorderService.resumeRecording();
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Checks if recording can be resumed
|
|
156
|
-
*
|
|
157
|
-
* @returns boolean - True if recording can be resumed, false otherwise
|
|
158
|
-
*/
|
|
159
|
-
canResume() {
|
|
160
|
-
return this.recordingState === RecordingState.Paused;
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Downloads the recorded video file
|
|
164
|
-
*
|
|
165
|
-
* @returns void
|
|
166
|
-
*/
|
|
167
|
-
onDownload() {
|
|
168
|
-
this.errorMessage = null;
|
|
169
|
-
this.screenRecorderService.downloadRecording(this.rucInputData.downloadFileName);
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Checks if recording can be downloaded
|
|
173
|
-
*
|
|
174
|
-
* @returns boolean - True if recording can be downloaded, false otherwise
|
|
175
|
-
*/
|
|
176
|
-
canDownload() {
|
|
177
|
-
return this.recordingState === RecordingState.Stopped;
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Toggles audio recording on/off
|
|
181
|
-
*
|
|
182
|
-
* @returns void
|
|
183
|
-
*/
|
|
184
|
-
toggleAudio() {
|
|
185
|
-
this.isAudioEnabled = !this.isAudioEnabled;
|
|
186
|
-
this.cdr.detectChanges();
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
*
|
|
190
|
-
* @param iconName
|
|
191
|
-
* @returns
|
|
192
|
-
*/
|
|
193
|
-
getIcon(iconName) {
|
|
194
|
-
return this.rucInputData.icons?.[iconName] || this.screenRecordingConstant.DEFAULT_ICONS[iconName];
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
*
|
|
198
|
-
* @param labelName
|
|
199
|
-
* @returns
|
|
200
|
-
*/
|
|
201
|
-
getLabel(labelName) {
|
|
202
|
-
return this.rucInputData.accessibilityLabels?.[labelName] || this.screenRecordingConstant.DEFAULT_LABELS[labelName];
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
*
|
|
206
|
-
* @param time - The time in seconds to format
|
|
207
|
-
* @returns string - Formatted time string (HH:MM:SS)
|
|
208
|
-
*/
|
|
209
|
-
formatTimeDisplay(time) {
|
|
210
|
-
const hours = Math.floor(time / 3600);
|
|
211
|
-
const minutes = Math.floor((time % 3600) / 60);
|
|
212
|
-
const seconds = time % 60;
|
|
213
|
-
const pad = (num) => num.toString().padStart(2, '0');
|
|
214
|
-
if (this.rucInputData && this.rucInputData.useUTCForTimer) {
|
|
215
|
-
// This is a simple duration format, not a specific UTC time.
|
|
216
|
-
// For actual UTC time of day, you'd use new Date().toUTCString() or similar.
|
|
217
|
-
// Assuming timer should show elapsed time formatted as HH:MM:SS.
|
|
218
|
-
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
|
|
219
|
-
}
|
|
220
|
-
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
|
|
221
|
-
}
|
|
222
|
-
// Method to reset the component and service to initial state
|
|
223
|
-
reset() {
|
|
224
|
-
this.screenRecorderService.resetToIdle();
|
|
225
|
-
this.isAudioEnabled = this.rucInputData.defaultAudioState ?? true;
|
|
226
|
-
this.cdr.detectChanges();
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
ScreenRecorderComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: ScreenRecorderComponent, deps: [{ token: i1.ScreenRecorderService }, { token: i2.DomSanitizer }, { token: i0.ChangeDetectorRef }, { token: i3.ScreenRecorderConstants }], target: i0.ɵɵFactoryTarget.Component });
|
|
230
|
-
ScreenRecorderComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: ScreenRecorderComponent, selector: "uxp-screen-recorder", inputs: { customTheme: "customTheme", rucInputData: "rucInputData" }, viewQueries: [{ propertyName: "videoPlayer", first: true, predicate: ["videoPlayer"], descendants: true }], ngImport: i0, template: "<div class=\"screen-recorder-controls\" [ngClass]=\"customTheme + ' position-' + (rucInputData.position || ScreenRecorderPosition.Bottom)\"\r\n *ngIf=\"isBrowserSupported\">\r\n\r\n <!-- Error Message -->\r\n <div *ngIf=\"errorMessage\" class=\"recorder-error\">\r\n {{ errorMessage }}\r\n </div>\r\n\r\n <!-- Record/Stop Button -->\r\n <button\r\n *ngIf=\"recordingState === RecordingState.Idle || recordingState === RecordingState.Stopped\" (click)=\"toggleRecord()\"\r\n [attr.aria-label]=\"getLabel('recordButton')\" [attr.aria-pressed]=\"false\" class=\"recorder-button record-button\">\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Icon\">\r\n <span class=\"material-icons\">{{ getRecordingIcon() || getIcon('record') }}</span>\r\n </ng-container>\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Text\">{{ getLabel('recordButton')\r\n }}</ng-container>\r\n </button>\r\n\r\n <button\r\n *ngIf=\"recordingState === RecordingState.Recording || recordingState === RecordingState.Paused\"\r\n (click)=\"toggleRecord()\" [attr.aria-label]=\"getLabel('stopButton')\" [attr.aria-pressed]=\"true\"\r\n class=\"recorder-button stop-button\">\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Icon\">\r\n <span class=\"material-icons\">{{ getIcon('stop') }}</span>\r\n </ng-container>\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Text\">{{ getLabel('stopButton') }}</ng-container>\r\n </button>\r\n\r\n <!-- Pause/Resume Button -->\r\n <button *ngIf=\"rucInputData.showPauseResume && recordingState === RecordingState.Recording\"\r\n (click)=\"togglePauseResume()\" [attr.aria-label]=\"getLabel('pauseButton')\" [attr.aria-pressed]=\"false\"\r\n class=\"recorder-button pause-button\">\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Icon\">\r\n <span class=\"material-icons\">{{ getIcon('pause') }}</span>\r\n </ng-container>\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Text\">{{ getLabel('pauseButton') }}</ng-container>\r\n </button>\r\n <button *ngIf=\"rucInputData.showPauseResume && recordingState === RecordingState.Paused\"\r\n (click)=\"togglePauseResume()\" [attr.aria-label]=\"getLabel('resumeButton')\" [attr.aria-pressed]=\"true\"\r\n class=\"recorder-button resume-button\">\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Icon\">\r\n <span class=\"material-icons\">{{ getIcon('resume') }}</span>\r\n </ng-container>\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Text\">{{ getLabel('resumeButton')\r\n }}</ng-container>\r\n </button>\r\n\r\n <!-- Audio Toggle Button -->\r\n <button\r\n *ngIf=\"rucInputData.showAudioToggle && (recordingState === RecordingState.Idle || recordingState === RecordingState.Stopped)\"\r\n (click)=\"toggleAudio()\" [attr.aria-label]=\"getLabel('audioToggleButton')\" [attr.aria-pressed]=\"isAudioEnabled\"\r\n class=\"recorder-button audio-toggle-button\">\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Icon\">\r\n <span class=\"material-icons\">{{ isAudioEnabled ? getIcon('audioOn') : getIcon('audioOff') }}</span>\r\n </ng-container>\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Text\">\r\n {{ isAudioEnabled ? screenRecordingConstant.AUDIO_ON : screenRecordingConstant.AUDIO_OFF }}\r\n </ng-container>\r\n </button>\r\n\r\n <!-- Timer -->\r\n <div\r\n *ngIf=\"rucInputData.showTimer && (recordingState === RecordingState.Recording || recordingState === RecordingState.Paused)\"\r\n class=\"recorder-timer\" aria-live=\"polite\" aria-atomic=\"true\">\r\n {{ formattedTime }}\r\n </div>\r\n\r\n <!-- Download Button -->\r\n <button\r\n *ngIf=\"rucInputData.showDownloadButton && recordingState === RecordingState.Stopped && safeRecordedUrl\"\r\n (click)=\"onDownload()\" [attr.aria-label]=\"getLabel('downloadButton')\" class=\"recorder-button download-button\">\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Icon\">\r\n <span class=\"material-icons\">{{ getIcon('download') }}</span>\r\n </ng-container>\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Text\">{{ getLabel('downloadButton')\r\n }}</ng-container>\r\n </button>\r\n\r\n <!-- Playback Area -->\r\n <div *ngIf=\"rucInputData.showPlaybackControls && recordingState === RecordingState.Stopped && safeRecordedUrl\"\r\n class=\"playback-area\">\r\n <video #videoPlayer [src]=\"safeRecordedUrl\" controls [attr.aria-label]=\"getLabel('playbackVideo')\"></video>\r\n <div class=\"video-timer-overlay\">\r\n <span>Recorded on: {{ recordingTimestamp }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- Reset Button (optional, good for testing/clearing state) -->\r\n <button *ngIf=\"recordingState === RecordingState.Stopped && safeRecordedUrl\" (click)=\"reset()\"\r\n aria-label=\"New Recording\" class=\"recorder-button reset-button\">\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Icon\">\r\n <span class=\"material-icons\">refresh</span>\r\n </ng-container>\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Text\">New</ng-container>\r\n </button>\r\n\r\n</div>\r\n\r\n<div *ngIf=\"!isBrowserSupported\" class=\"recorder-notsupported\" [ngClass]=\"customTheme\">\r\n {{screenRecordingConstant.NO_SUPPORT}}\r\n</div>", styles: [":host{display:block;font-family:sans-serif}.screen-recorder-controls{display:flex;gap:8px;align-items:center;padding:8px;border-radius:4px;position:relative;z-index:1000}.screen-recorder-controls.position-top-left{margin-top:10px;margin-left:10px;flex-direction:row}.screen-recorder-controls.position-top-right{margin-top:10px;margin-right:10px;flex-direction:row}.screen-recorder-controls.position-bottom-left{margin-bottom:10px;margin-left:10px;flex-direction:row}.screen-recorder-controls.position-bottom-right{margin-bottom:10px;margin-right:10px;flex-direction:row}.screen-recorder-controls.position-left,.screen-recorder-controls.position-right{float:right}.recorder-button{padding:8px 12px;border:1px solid #ccc;border-radius:4px;background-color:#fff;cursor:pointer;display:inline-flex;align-items:center;gap:4px}.recorder-button:hover{background-color:#e9e9e9}.recorder-button:active{background-color:#d0d0d0}.recorder-button.record-button{color:red}.material-icons{font-family:Material Icons;font-weight:400;font-style:normal;font-size:20px;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:\"liga\"}.recorder-timer{padding:0 8px;font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums}.playback-area{width:100%;max-width:100%;margin-top:1rem;position:relative}.playback-area video{width:100%;height:auto;max-width:100%}.playback-area .video-timer-overlay{position:absolute;top:10px;right:10px;background:rgba(0,0,0,.7);color:#fff;padding:5px 10px;border-radius:4px;font-size:14px;z-index:10}.recorder-error{color:red;padding:8px;border:1px solid red;background-color:#ffe0e0;border-radius:4px;margin-bottom:8px}.recorder-notsupported{padding:10px;background-color:#fff3cd;border:1px solid #ffeeba;color:#856404;border-radius:4px}\n"], dependencies: [{ kind: "directive", type: i4.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
231
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: ScreenRecorderComponent, decorators: [{
|
|
232
|
-
type: Component,
|
|
233
|
-
args: [{ selector: 'uxp-screen-recorder', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"screen-recorder-controls\" [ngClass]=\"customTheme + ' position-' + (rucInputData.position || ScreenRecorderPosition.Bottom)\"\r\n *ngIf=\"isBrowserSupported\">\r\n\r\n <!-- Error Message -->\r\n <div *ngIf=\"errorMessage\" class=\"recorder-error\">\r\n {{ errorMessage }}\r\n </div>\r\n\r\n <!-- Record/Stop Button -->\r\n <button\r\n *ngIf=\"recordingState === RecordingState.Idle || recordingState === RecordingState.Stopped\" (click)=\"toggleRecord()\"\r\n [attr.aria-label]=\"getLabel('recordButton')\" [attr.aria-pressed]=\"false\" class=\"recorder-button record-button\">\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Icon\">\r\n <span class=\"material-icons\">{{ getRecordingIcon() || getIcon('record') }}</span>\r\n </ng-container>\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Text\">{{ getLabel('recordButton')\r\n }}</ng-container>\r\n </button>\r\n\r\n <button\r\n *ngIf=\"recordingState === RecordingState.Recording || recordingState === RecordingState.Paused\"\r\n (click)=\"toggleRecord()\" [attr.aria-label]=\"getLabel('stopButton')\" [attr.aria-pressed]=\"true\"\r\n class=\"recorder-button stop-button\">\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Icon\">\r\n <span class=\"material-icons\">{{ getIcon('stop') }}</span>\r\n </ng-container>\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Text\">{{ getLabel('stopButton') }}</ng-container>\r\n </button>\r\n\r\n <!-- Pause/Resume Button -->\r\n <button *ngIf=\"rucInputData.showPauseResume && recordingState === RecordingState.Recording\"\r\n (click)=\"togglePauseResume()\" [attr.aria-label]=\"getLabel('pauseButton')\" [attr.aria-pressed]=\"false\"\r\n class=\"recorder-button pause-button\">\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Icon\">\r\n <span class=\"material-icons\">{{ getIcon('pause') }}</span>\r\n </ng-container>\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Text\">{{ getLabel('pauseButton') }}</ng-container>\r\n </button>\r\n <button *ngIf=\"rucInputData.showPauseResume && recordingState === RecordingState.Paused\"\r\n (click)=\"togglePauseResume()\" [attr.aria-label]=\"getLabel('resumeButton')\" [attr.aria-pressed]=\"true\"\r\n class=\"recorder-button resume-button\">\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Icon\">\r\n <span class=\"material-icons\">{{ getIcon('resume') }}</span>\r\n </ng-container>\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Text\">{{ getLabel('resumeButton')\r\n }}</ng-container>\r\n </button>\r\n\r\n <!-- Audio Toggle Button -->\r\n <button\r\n *ngIf=\"rucInputData.showAudioToggle && (recordingState === RecordingState.Idle || recordingState === RecordingState.Stopped)\"\r\n (click)=\"toggleAudio()\" [attr.aria-label]=\"getLabel('audioToggleButton')\" [attr.aria-pressed]=\"isAudioEnabled\"\r\n class=\"recorder-button audio-toggle-button\">\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Icon\">\r\n <span class=\"material-icons\">{{ isAudioEnabled ? getIcon('audioOn') : getIcon('audioOff') }}</span>\r\n </ng-container>\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Text\">\r\n {{ isAudioEnabled ? screenRecordingConstant.AUDIO_ON : screenRecordingConstant.AUDIO_OFF }}\r\n </ng-container>\r\n </button>\r\n\r\n <!-- Timer -->\r\n <div\r\n *ngIf=\"rucInputData.showTimer && (recordingState === RecordingState.Recording || recordingState === RecordingState.Paused)\"\r\n class=\"recorder-timer\" aria-live=\"polite\" aria-atomic=\"true\">\r\n {{ formattedTime }}\r\n </div>\r\n\r\n <!-- Download Button -->\r\n <button\r\n *ngIf=\"rucInputData.showDownloadButton && recordingState === RecordingState.Stopped && safeRecordedUrl\"\r\n (click)=\"onDownload()\" [attr.aria-label]=\"getLabel('downloadButton')\" class=\"recorder-button download-button\">\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Icon\">\r\n <span class=\"material-icons\">{{ getIcon('download') }}</span>\r\n </ng-container>\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Text\">{{ getLabel('downloadButton')\r\n }}</ng-container>\r\n </button>\r\n\r\n <!-- Playback Area -->\r\n <div *ngIf=\"rucInputData.showPlaybackControls && recordingState === RecordingState.Stopped && safeRecordedUrl\"\r\n class=\"playback-area\">\r\n <video #videoPlayer [src]=\"safeRecordedUrl\" controls [attr.aria-label]=\"getLabel('playbackVideo')\"></video>\r\n <div class=\"video-timer-overlay\">\r\n <span>Recorded on: {{ recordingTimestamp }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- Reset Button (optional, good for testing/clearing state) -->\r\n <button *ngIf=\"recordingState === RecordingState.Stopped && safeRecordedUrl\" (click)=\"reset()\"\r\n aria-label=\"New Recording\" class=\"recorder-button reset-button\">\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Icon\">\r\n <span class=\"material-icons\">refresh</span>\r\n </ng-container>\r\n <ng-container *ngIf=\"rucInputData.displayFormat === DisplayFormat.Text\">New</ng-container>\r\n </button>\r\n\r\n</div>\r\n\r\n<div *ngIf=\"!isBrowserSupported\" class=\"recorder-notsupported\" [ngClass]=\"customTheme\">\r\n {{screenRecordingConstant.NO_SUPPORT}}\r\n</div>", styles: [":host{display:block;font-family:sans-serif}.screen-recorder-controls{display:flex;gap:8px;align-items:center;padding:8px;border-radius:4px;position:relative;z-index:1000}.screen-recorder-controls.position-top-left{margin-top:10px;margin-left:10px;flex-direction:row}.screen-recorder-controls.position-top-right{margin-top:10px;margin-right:10px;flex-direction:row}.screen-recorder-controls.position-bottom-left{margin-bottom:10px;margin-left:10px;flex-direction:row}.screen-recorder-controls.position-bottom-right{margin-bottom:10px;margin-right:10px;flex-direction:row}.screen-recorder-controls.position-left,.screen-recorder-controls.position-right{float:right}.recorder-button{padding:8px 12px;border:1px solid #ccc;border-radius:4px;background-color:#fff;cursor:pointer;display:inline-flex;align-items:center;gap:4px}.recorder-button:hover{background-color:#e9e9e9}.recorder-button:active{background-color:#d0d0d0}.recorder-button.record-button{color:red}.material-icons{font-family:Material Icons;font-weight:400;font-style:normal;font-size:20px;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:\"liga\"}.recorder-timer{padding:0 8px;font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums}.playback-area{width:100%;max-width:100%;margin-top:1rem;position:relative}.playback-area video{width:100%;height:auto;max-width:100%}.playback-area .video-timer-overlay{position:absolute;top:10px;right:10px;background:rgba(0,0,0,.7);color:#fff;padding:5px 10px;border-radius:4px;font-size:14px;z-index:10}.recorder-error{color:red;padding:8px;border:1px solid red;background-color:#ffe0e0;border-radius:4px;margin-bottom:8px}.recorder-notsupported{padding:10px;background-color:#fff3cd;border:1px solid #ffeeba;color:#856404;border-radius:4px}\n"] }]
|
|
234
|
-
}], ctorParameters: function () { return [{ type: i1.ScreenRecorderService }, { type: i2.DomSanitizer }, { type: i0.ChangeDetectorRef }, { type: i3.ScreenRecorderConstants }]; }, propDecorators: { customTheme: [{
|
|
235
|
-
type: Input
|
|
236
|
-
}], rucInputData: [{
|
|
237
|
-
type: Input
|
|
238
|
-
}], videoPlayer: [{
|
|
239
|
-
type: ViewChild,
|
|
240
|
-
args: ['videoPlayer']
|
|
241
|
-
}] } });
|
|
242
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2NyZWVuLXJlY29yZGVyLmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uL2xpYnMvcnVjbGliL3NjcmVlbi1yZWNvcmRlci9zcmMvbGliL3NjcmVlbi1yZWNvcmRlci9zY3JlZW4tcmVjb3JkZXIuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vLi4vLi4vbGlicy9ydWNsaWIvc2NyZWVuLXJlY29yZGVyL3NyYy9saWIvc2NyZWVuLXJlY29yZGVyL3NjcmVlbi1yZWNvcmRlci5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsU0FBUyxFQUFFLEtBQUssRUFBcUIsdUJBQXVCLEVBQUUsaUJBQWlCLEVBQUUsU0FBUyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUN2SSxPQUFPLEVBQUUsWUFBWSxFQUFXLE1BQU0sMkJBQTJCLENBQUM7QUFDbEUsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUNwQyxPQUFPLEVBRUwsc0JBQXNCLEVBQ3RCLGFBQWEsRUFDYixjQUFjLEdBRWYsTUFBTSxrQ0FBa0MsQ0FBQztBQUMxQyxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxxQ0FBcUMsQ0FBQztBQUM1RSxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQzs7Ozs7O0FBVXJFLE1BQU0sT0FBTyx1QkFBdUI7SUFJbEMsSUFDSSxZQUFZLENBQUMsS0FBNEI7UUFDM0MsSUFBSSxDQUFDLFFBQVEsR0FBRztZQUNkLFFBQVEsRUFBRSxzQkFBc0IsQ0FBQyxNQUFNO1lBQ3ZDLGFBQWEsRUFBRSxhQUFhLENBQUMsSUFBSTtZQUNqQyxlQUFlLEVBQUUsSUFBSTtZQUNyQixpQkFBaUIsRUFBRSxJQUFJO1lBQ3ZCLGdCQUFnQixFQUFFLHVCQUF1QjtZQUN6QyxLQUFLLEVBQUUsRUFBRSxHQUFHLElBQUksQ0FBQyx1QkFB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UUFDbEUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxhQUFhLEVBQUUsQ0FBQztJQUMzQixDQUFDOztxSEEvUFUsdUJBQXVCO3lHQUF2Qix1QkFBdUIsNk9DckJwQyxzOEtBcUdNOzRGRGhGTyx1QkFBdUI7a0JBTm5DLFNBQVM7K0JBQ0UscUJBQXFCLG1CQUdkLHVCQUF1QixDQUFDLE1BQU07Nk1BSS9DLFdBQVc7c0JBRFYsS0FBSztnQkFJRixZQUFZO3NCQURmLEtBQUs7Z0JBZ0NvQixXQUFXO3NCQUFwQyxTQUFTO3VCQUFDLGFBQWEiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb21wb25lbnQsIElucHV0LCBPbkluaXQsIE9uRGVzdHJveSwgQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3ksIENoYW5nZURldGVjdG9yUmVmLCBWaWV3Q2hpbGQsIEVsZW1lbnRSZWYgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcclxuaW1wb3J0IHsgRG9tU2FuaXRpemVyLCBTYWZlVXJsIH0gZnJvbSAnQGFuZ3VsYXIvcGxhdGZvcm0tYnJvd3Nlcic7XHJcbmltcG9ydCB7IFN1YnNjcmlwdGlvbiB9IGZyb20gJ3J4anMnO1xyXG5pbXBvcnQge1xyXG4gIFNjcmVlblJlY29yZGVyT3B0aW9ucyxcclxuICBTY3JlZW5SZWNvcmRlclBvc2l0aW9uLFxyXG4gIERpc3BsYXlGb3JtYXQsXHJcbiAgUmVjb3JkaW5nU3RhdGUsXHJcbiAgU2NyZWVuUmVjb3JkZXJJY29ucyxcclxufSBmcm9tICcuLi9tb2RlbHMvc2NyZWVuLXJlY29yZGVyLm1vZGVscyc7XHJcbmltcG9ydCB7IFNjcmVlblJlY29yZGVyU2VydmljZSB9IGZyb20gJy4uL3NlcnZpY2VzL3NjcmVlbi1yZWNvcmRlci5zZXJ2aWNlJztcclxuaW1wb3J0IHsgU2NyZWVuUmVjb3JkZXJDb25zdGFudHMgfSBmcm9tICcuL3NjcmVlbi1yZWNvcmRlci1jb25zdGFudCc7XHJcblxyXG5cclxuXHJcbkBDb21wb25lbnQoe1xyXG4gIHNlbGVjdG9yOiAndXhwLXNjcmVlbi1yZWNvcmRlcicsXHJcbiAgdGVtcGxhdGVVcmw6ICcuL3NjcmVlbi1yZWNvcmRlci5jb21wb25lbnQuaHRtbCcsXHJcbiAgc3R5bGVVcmxzOiBbJy4vc2NyZWVuLXJlY29yZGVyLmNvbXBvbmVudC5zY3NzJ10sXHJcbiAgY2hhbmdlRGV0ZWN0aW9uOiBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneS5PblB1c2gsXHJcbn0pXHJcbmV4cG9ydCBjbGFzcyBTY3JlZW5SZWNvcmRlckNvbXBvbmVudCBpbXBsZW1lbnRzIE9uSW5pdCwgT25EZXN0cm95IHtcclxuICBASW5wdXQoKVxyXG4gIGN1c3RvbVRoZW1lOiBhbnk7XHJcbiAgXHJcbiAgQElucHV0KClcclxuICBzZXQgcnVjSW5wdXREYXRhKHZhbHVlOiBTY3JlZW5SZWNvcmRlck9wdGlvbnMpIHtcclxuICAgIHRoaXMuX29wdGlvbnMgPSB7XHJcbiAgICAgIHBvc2l0aW9uOiBTY3JlZW5SZWNvcmRlclBvc2l0aW9uLkJvdHRvbSxcclxuICAgICAgZGlzcGxheUZvcm1hdDogRGlzcGxheUZvcm1hdC5JY29uLFxyXG4gICAgICBzaG93QXVkaW9Ub2dnbGU6IHRydWUsXHJcbiAgICAgIGRlZmF1bHRBdWRpb1N0YXRlOiB0cnVlLFxyXG4gICAgICBkb3dubG9hZEZpbGVOYW1lOiAnc2NyZWVuLXJlY29yZGluZy53ZWJtJyxcclxuICAgICAgaWNvbnM6IHsgLi4udGhpcy5zY3JlZW5SZWNvcmRpbmdDb25zdGFudC5ERUZBVUxUX0lDT05TIH0sXHJcbiAgICAgIHNob3dUaW1lcjogdHJ1ZSxcclxuICAgICAgc2hvd1BhdXNlUmVzdW1lOiB0cnVlLFxyXG4gICAgICBzaG93UGxheWJhY2tDb250cm9sczogdHJ1ZSxcclxuICAgICAgc2hvd0Rvd25sb2FkQnV0dG9uOiB0cnVlLFxyXG4gICAgICB1c2VVVENGb3JUaW1lcjogZmFsc2UsXHJcbiAgICAgIGFsbG93U3BlY2lmaWNBcmVhU2VsZWN0aW9uOiB0cnVlLCAvLyBEZWZhdWx0IHRvIGFsbG93aW5nIGJyb3dzZXIncyBuYXRpdmUgcGlja2VyXHJcbiAgICAgIGFjY2Vzc2liaWxpdHlMYWJlbHM6IHsgLi4udGhpcy5zY3JlZW5SZWNvcmRpbmdDb25zdGFudC5ERUZBVUxUX0xBQkVMUyB9LFxyXG4gICAgICAuLi52YWx1ZSxcclxuICAgIH07XHJcbiAgICAvLyBEZWVwIG1lcmdlIGljb25zIGFuZCBsYWJlbHNcclxuICAgIGlmICh2YWx1ZS5pY29ucykge1xyXG4gICAgICB0aGlzLl9vcHRpb25zLmljb25zID0geyAuLi50aGlzLnNjcmVlblJlY29yZGluZ0NvbnN0YW50LkRFRkFVTFRfSUNPTlMsIC4uLnZhbHVlLmljb25zIH07XHJcbiAgICB9XHJcbiAgICBpZiAodmFsdWUuYWNjZXNzaWJpbGl0eUxhYmVscykge1xyXG4gICAgICB0aGlzLl9vcHRpb25zLmFjY2Vzc2liaWxpdHlMYWJlbHMgPSB7IC4uLnRoaXMuc2NyZWVuUmVjb3JkaW5nQ29uc3RhbnQuREVGQVVMVF9MQUJFTFMsIC4uLnZhbHVlLmFjY2Vzc2liaWxpdHlMYWJlbHMgfTtcclxuICAgIH1cclxuICAgIHRoaXMuaXNBdWRpb0VuYWJsZWQgPSB0aGlzLl9vcHRpb25zLmRlZmF1bHRBdWRpb1N0YXRlID8/IHRydWU7XHJcbiAgfVxyXG4gIGdldCBydWNJbnB1dERhdGEoKTogU2NyZWVuUmVjb3JkZXJPcHRpb25zIHtcclxuICAgIHJldHVybiB0aGlzLl9vcHRpb25zO1xyXG4gIH1cclxuICBwcml2YXRlIF9vcHRpb25zITogU2NyZWVuUmVjb3JkZXJPcHRpb25zO1xyXG5cclxuICBAVmlld0NoaWxkKCd2aWRlb1BsYXllcicpIHZpZGVvUGxheWVyITogRWxlbWVudFJlZjxIVE1MVmlkZW9FbGVtZW50PjtcclxuXHJcbiAgcmVjb3JkaW5nU3RhdGU6IFJlY29yZGluZ1N0YXRlID0gUmVjb3JkaW5nU3RhdGUuSWRsZTtcclxuICByZWNvcmRlZFRpbWU6IG51bWJlciA9IDA7XHJcbiAgZm9ybWF0dGVkVGltZTogc3RyaW5nID0gJzAwOjAwOjAwJztcclxuICBzYWZlUmVjb3JkZWRVcmw6IFNhZmVVcmwgfCBudWxsID0gbnVsbDtcclxuICByZWNvcmRpbmdUaW1lc3RhbXA6IHN0cmluZyA9ICcnO1xyXG4gIC8qKlxyXG4gICAqIENoZWNrcyBpZiByZWNvcmRpbmcgaXMgY3VycmVudGx5IGluIHByb2dyZXNzXHJcbiAgICogXHJcbiAgICogQHJldHVybnMgYm9vbGVhbiAtIFRydWUgaWYgcmVjb3JkaW5nIGlzIGluIHByb2dyZXNzLCBmYWxzZSBvdGhlcndpc2VcclxuICAgKi9cclxuICBpc1JlY29yZGluZ0luUHJvZ3Jlc3MoKTogYm9vbGVhbiB7XHJcbiAgICByZXR1cm4gdGhpcy5yZWNvcmRpbmdTdGF0ZSA9PT0gUmVjb3JkaW5nU3RhdGUuUmVjb3JkaW5nO1xyXG4gIH1cclxuICBlcnJvck1lc3NhZ2U6IHN0cmluZyB8IG51bGwgPSBudWxsO1xyXG4gIGlzQnJvd3NlclN1cHBvcnRlZDogYm9vbGVhbiA9IHRydWU7XHJcbiAgaXNBdWRpb0VuYWJsZWQ6IGJvb2xlYW4gPSB0cnVlO1xyXG5cclxuICAvKipcclxuICAgKiBHZXRzIHRoZSBhcHByb3ByaWF0ZSBpY29uIGZvciByZWNvcmRpbmcgYmFzZWQgb24gYXVkaW8gc3RhdGUgYW5kIHNldHRpbmdzXHJcbiAgICogQHJldHVybnMgc3RyaW5nIC0gVGhlIGljb24gbmFtZSB0byBkaXNwbGF5XHJcbiAgICovXHJcbiAgZ2V0UmVjb3JkaW5nSWNvbigpOiBzdHJpbmcge1xyXG4gICAgaWYgKHRoaXMuaXNBdWRpb0VuYWJsZWQgJiYgdGhpcy5ydWNJbnB1dERhdGEuc2hvd0F1ZGlvVG9nZ2xlKSB7XHJcbiAgICAgIHJldHVybiB0aGlzLmdldEljb24oJ3JlY29yZFdpdGhBdWRpbycpO1xyXG4gICAgfVxyXG4gICAgcmV0dXJuIHRoaXMuZ2V0SWNvbigncmVjb3JkJyk7XHJcbiAgfVxyXG5cclxuICAvLyBFeHBvc2UgZW51bXMgdG8gdGVtcGxhdGVcclxuICBEaXNwbGF5Rm9ybWF0ID0gRGlzcGxheUZvcm1hdDtcclxuICBSZWNvcmRpbmdTdGF0ZSA9IFJlY29yZGluZ1N0YXRlO1xyXG4gIFNjcmVlblJlY29yZGVyUG9zaXRpb24gPSBTY3JlZW5SZWNvcmRlclBvc2l0aW9uO1xyXG5cclxuICBwcml2YXRlIHN1YnNjcmlwdGlvbnMgPSBuZXcgU3Vic2NyaXB0aW9uKCk7XHJcblxyXG4gIGNvbnN0cnVjdG9yKFxyXG4gICAgcHVibGljIHNjcmVlblJlY29yZGVyU2VydmljZTogU2NyZWVuUmVjb3JkZXJTZXJ2aWNlLFxyXG4gICAgcHJpdmF0ZSBzYW5pdGl6ZXI6IERvbVNhbml0aXplcixcclxuICAgIHByaXZhdGUgY2RyOiBDaGFuZ2VEZXRlY3RvclJlZixcclxuICAgIHB1YmxpYyBzY3JlZW5SZWNvcmRpbmdDb25zdGFudDogU2NyZWVuUmVjb3JkZXJDb25zdGFudHNcclxuICApIHtcclxuXHJcbiAgICAvLyBJbml0aWFsaXplIHdpdGggZGVmYXVsdCBvcHRpb25zLiBUaGUgc2V0dGVyIHdpbGwgb3ZlcnJpZGUgaWYgYW4gaW5wdXQgaXMgcHJvdmlkZWQuXHJcbiAgICBpZiAoIXRoaXMuX29wdGlvbnMpIHtcclxuICAgICAgICB0aGlzLnJ1Y0lucHV0RGF0YSA9IHt9OyAvLyBUcmlnZ2VyIHNldHRlciB3aXRoIGVtcHR5IG9iamVjdCB0byBhcHBseSBkZWZhdWx0c1xyXG4gICAgfVxyXG4gIH0gICBcclxuXHJcbiAgbmdPbkluaXQoKTogdm9pZCB7XHJcbiAgICB0aGlzLnN1YnNjcmlwdGlvbnMuYWRkKFxyXG4gICAgICB0aGlzLnNjcmVlblJlY29yZGVyU2VydmljZS5yZWNvcmRpbmdTdGF0ZSQuc3Vic2NyaWJlKChzdGF0ZSkgPT4ge1xyXG4gICAgICAgIHRoaXMucmVjb3JkaW5nU3RhdGUgPSBzdGF0ZTtcclxuICAgICAgICBpZiAoc3RhdGUgPT09IFJlY29yZGluZ1N0YXRlLklkbGUgfHwgc3RhdGUgPT09IFJlY29yZGluZ1N0YXRlLlN0b3BwZWQpIHtcclxuICAgICAgICAgICAgLy8gRW5zdXJlIGF1ZGlvIHRvZ2dsZSBpcyByZXNldCB0byBkZWZhdWx0IGlmIG5lZWRlZCwgb3IgYmFzZWQgb24gdXNlciBhY3Rpb25cclxuICAgICAgICB9XHJcbiAgICAgICAgdGhpcy5jZHIuZGV0ZWN0Q2hhbmdlcygpO1xyXG4gICAgICB9KVxyXG4gICAgKTtcclxuXHJcbiAgICB0aGlzLnN1YnNjcmlwdGlvbnMuYWRkKFxyXG4gICAgICB0aGlzLnNjcmVlblJlY29yZGVyU2VydmljZS5yZWNvcmRpbmdUaW1lc3RhbXAkLnN1YnNjcmliZSgodGltZXN0YW1wKSA9PiB7XHJcbiAgICAgICAgdGhpcy5yZWNvcmRpbmdUaW1lc3RhbXAgPSB0aW1lc3RhbXA7XHJcbiAgICAgICAgdGhpcy5jZHIuZGV0ZWN0Q2hhbmdlcygpO1xyXG4gICAgICB9KVxyXG4gICAgKTtcclxuXHJcbiAgICB0aGlzLnN1YnNjcmlwdGlvbnMuYWRkKFxyXG4gICAgICB0aGlzLnNjcmVlblJlY29yZGVyU2VydmljZS5yZWNvcmRlZFRpbWUkLnN1YnNjcmliZSgodGltZSkgPT4ge1xyXG4gICAgICAgIHRoaXMucmVjb3JkZWRUaW1lID0gdGltZTtcclxuICAgICAgICB0aGlzLmZvcm1hdHRlZFRpbWUgPSB0aGlzLmZvcm1hdFRpbWVEaXNwbGF5KHRpbWUpO1xyXG4gICAgICAgIHRoaXMuY2RyLmRldGVjdENoYW5nZXMoKTtcclxuICAgICAgfSlcclxuICAgICk7XHJcblxyXG4gICAgdGhpcy5zdWJzY3JpcHRpb25zLmFkZChcclxuICAgICAgdGhpcy5zY3JlZW5SZWNvcmRlclNlcnZpY2UucmVjb3JkZWRVcmwkLnN1YnNjcmliZSgodXJsKSA9PiB7XHJcbiAgICAgICAgdGhpcy5zYWZlUmVjb3JkZWRVcmwgPSB1cmwgPyB0aGlzLnNhbml0aXplci5ieXBhc3NTZWN1cml0eVRydXN0VXJsKHVybCkgOiBudWxsO1xyXG4gICAgICAgIHRoaXMuY2RyLmRldGVjdENoYW5nZXMoKTtcclxuICAgICAgfSlcclxuICAgICk7XHJcblxyXG4gICAgdGhpcy5zdWJzY3JpcHRpb25zLmFkZChcclxuICAgICAgdGhpcy5zY3JlZW5SZWNvcmRlclNlcnZpY2UuZXJyb3IkLnN1YnNjcmliZSgoZXJyb3IpID0+IHtcclxuICAgICAgICBpZihlcnJvciAhPSB0aGlzLnNjcmVlblJlY29yZGluZ0NvbnN0YW50LlBFUk1JU1NJT05fREVOSUVEKXtcclxuICAgICAgICAgIHRoaXMuZXJyb3JNZXNzYWdlID0gZXJyb3I7XHJcbiAgICAgICAgfVxyXG4gICAgICAgIHRoaXMuY2RyLmRldGVjdENoYW5nZXMoKTtcclxuICAgICAgfSlcclxuICAgICk7XHJcbiAgfVxyXG5cclxuICBuZ09uRGVzdHJveSgpIHtcclxuICAgIGlmICh0aGlzLnN1YnNjcmlwdGlvbnMpIHtcclxuICAgICAgdGhpcy5zdWJzY3JpcHRpb25zLnVuc3Vic2NyaWJlKCk7XHJcbiAgICB9XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBUb2dnbGVzIHRoZSByZWNvcmRpbmcgc3RhdGUgYmV0d2VlbiBzdGFydCBhbmQgc3RvcFxyXG4gICAqIFxyXG4gICAqIEByZXR1cm5zIFByb21pc2U8dm9pZD5cclxuICAgKi9cclxuICBhc3luYyB0b2dnbGVSZWNvcmQoKTogUHJvbWlzZTx2b2lkPiB7XHJcbiAgICB0aGlzLmVycm9yTWVzc2FnZSA9IG51bGw7IC8vIENsZWFyIHByZXZpb3VzIGVycm9yc1xyXG4gICAgaWYgKHRoaXMucmVjb3JkaW5nU3RhdGUgPT09IFJlY29yZGluZ1N0YXRlLklkbGUgfHwgdGhpcy5yZWNvcmRpbmdTdGF0ZSA9PT0gUmVjb3JkaW5nU3RhdGUuU3RvcHBlZCkge1xyXG4gICAgICAvLyBTZXQgcmVjb3JkaW5nIHRpbWVzdGFtcCB3aGVuIHN0YXJ0aW5nIHJlY29yZGluZ1xyXG4gICAgICBjb25zdCBub3cgPSBuZXcgRGF0ZSgpO1xyXG4gICAgICB0aGlzLnJlY29yZGluZ1RpbWVzdGFtcCA9IG5vdy50b0xvY2FsZVN0cmluZygnZW4tSU4nLCB7XHJcbiAgICAgICAgeWVhcjogJ251bWVyaWMnLFxyXG4gICAgICAgIG1vbnRoOiAnMi1kaWdpdCcsXHJcbiAgICAgICAgZGF5OiAnMi1kaWdpdCcsXHJcbiAgICAgICAgaG91cjogJzItZGlnaXQnLFxyXG4gICAgICAgIG1pbnV0ZTogJzItZGlnaXQnLFxyXG4gICAgICAgIGhvdXIxMjogdHJ1ZVxyXG4gICAgICB9KTtcclxuICAgICAgYXdhaXQgdGhpcy5zY3JlZW5SZWNvcmRlclNlcnZpY2Uuc3RhcnRSZWNvcmRpbmcoeyBhdWRpbzogdGhpcy5pc0F1ZGlvRW5hYmxlZCB9KTtcclxuICAgIH0gZWxzZSB7XHJcbiAgICAgIHRoaXMuc2NyZWVuUmVjb3JkZXJTZXJ2aWNlLnN0b3BSZWNvcmRpbmcoKTtcclxuICAgIH1cclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIFRvZ2dsZXMgYmV0d2VlbiBwYXVzZSBhbmQgcmVzdW1lIHN0YXRlcyBkdXJpbmcgcmVjb3JkaW5nXHJcbiAgICogXHJcbiAgICogQHJldHVybnMgdm9pZFxyXG4gICAqL1xyXG4gIHRvZ2dsZVBhdXNlUmVzdW1lKCk6IHZvaWQge1xyXG4gICAgdGhpcy5lcnJvck1lc3NhZ2UgPSBudWxsO1xyXG4gICAgaWYgKHRoaXMucmVjb3JkaW5nU3RhdGUgPT09IFJlY29yZGluZ1N0YXRlLlJlY29yZGluZykge1xyXG4gICAgICB0aGlzLnNjcmVlblJlY29yZGVyU2VydmljZS5wYXVzZVJlY29yZGluZygpO1xyXG4gICAgfSBlbHNlIGlmICh0aGlzLnJlY29yZGluZ1N0YXRlID09PSBSZWNvcmRpbmdTdGF0ZS5QYXVzZWQpIHtcclxuICAgICAgdGhpcy5zY3JlZW5SZWNvcmRlclNlcnZpY2UucmVzdW1lUmVjb3JkaW5nKCk7XHJcbiAgICB9XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBDaGVja3MgaWYgcmVjb3JkaW5nIGNhbiBiZSByZXN1bWVkXHJcbiAgICogXHJcbiAgICogQHJldHVybnMgYm9vbGVhbiAtIFRydWUgaWYgcmVjb3JkaW5nIGNhbiBiZSByZXN1bWVkLCBmYWxzZSBvdGhlcndpc2VcclxuICAgKi9cclxuICBjYW5SZXN1bWUoKTogYm9vbGVhbiB7XHJcbiAgICByZXR1cm4gdGhpcy5yZWNvcmRpbmdTdGF0ZSA9PT0gUmVjb3JkaW5nU3RhdGUuUGF1c2VkO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogRG93bmxvYWRzIHRoZSByZWNvcmRlZCB2aWRlbyBmaWxlXHJcbiAgICogXHJcbiAgICogQHJldHVybnMgdm9pZFxyXG4gICAqL1xyXG4gIG9uRG93bmxvYWQoKTogdm9pZCB7XHJcbiAgICB0aGlzLmVycm9yTWVzc2FnZSA9IG51bGw7XHJcbiAgICB0aGlzLnNjcmVlblJlY29yZGVyU2VydmljZS5kb3dubG9hZFJlY29yZGluZyh0aGlzLnJ1Y0lucHV0RGF0YS5kb3dubG9hZEZpbGVOYW1lKTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIENoZWNrcyBpZiByZWNvcmRpbmcgY2FuIGJlIGRvd25sb2FkZWRcclxuICAgKiBcclxuICAgKiBAcmV0dXJucyBib29sZWFuIC0gVHJ1ZSBpZiByZWNvcmRpbmcgY2FuIGJlIGRvd25sb2FkZWQsIGZhbHNlIG90aGVyd2lzZVxyXG4gICAqL1xyXG4gIGNhbkRvd25sb2FkKCk6IGJvb2xlYW4ge1xyXG4gICAgcmV0dXJuIHRoaXMucmVjb3JkaW5nU3RhdGUgPT09IFJlY29yZGluZ1N0YXRlLlN0b3BwZWQ7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBUb2dnbGVzIGF1ZGlvIHJlY29yZGluZyBvbi9vZmZcclxuICAgKiBcclxuICAgKiBAcmV0dXJucyB2b2lkXHJcbiAgICovXHJcbiAgdG9nZ2xlQXVkaW8oKTogdm9pZCB7XHJcbiAgICB0aGlzLmlzQXVkaW9FbmFibGVkID0gIXRoaXMuaXNBdWRpb0VuYWJsZWQ7XHJcbiAgICB0aGlzLmNkci5kZXRlY3RDaGFuZ2VzKCk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBcclxuICAgKiBAcGFyYW0gaWNvbk5hbWUgXHJcbiAgICogQHJldHVybnMgXHJcbiAgICovXHJcbiAgZ2V0SWNvbihpY29uTmFtZToga2V5b2YgU2NyZWVuUmVjb3JkZXJJY29ucyk6IHN0cmluZyB7XHJcbiAgICByZXR1cm4gdGhpcy5ydWNJbnB1dERhdGEuaWNvbnM/LltpY29uTmFtZV0gfHwgKHRoaXMuc2NyZWVuUmVjb3JkaW5nQ29uc3RhbnQuREVGQVVMVF9JQ09OUyBhcyBhbnkpW2ljb25OYW1lXTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIFxyXG4gICAqIEBwYXJhbSBsYWJlbE5hbWUgXHJcbiAgICogQHJldHVybnMgXHJcbiAgICovXHJcbiAgZ2V0TGFiZWwobGFiZWxOYW1lOiBrZXlvZiBOb25OdWxsYWJsZTxTY3JlZW5SZWNvcmRlck9wdGlvbnNbJ2FjY2Vzc2liaWxpdHlMYWJlbHMnXT4pOiBzdHJpbmcge1xyXG4gICAgcmV0dXJuIHRoaXMucnVjSW5wdXREYXRhLmFjY2Vzc2liaWxpdHlMYWJlbHM/LltsYWJlbE5hbWVdIHx8ICh0aGlzLnNjcmVlblJlY29yZGluZ0NvbnN0YW50LkRFRkFVTFRfTEFCRUxTIGFzIGFueSlbbGFiZWxOYW1lXTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIFxyXG4gICAqIEBwYXJhbSB0aW1lIC0gVGhlIHRpbWUgaW4gc2Vjb25kcyB0byBmb3JtYXRcclxuICAgKiBAcmV0dXJucyBzdHJpbmcgLSBGb3JtYXR0ZWQgdGltZSBzdHJpbmcgKEhIOk1NOlNTKVxyXG4gICAqL1xyXG4gIGZvcm1hdFRpbWVEaXNwbGF5KHRpbWU6IG51bWJlcik6IHN0cmluZyB7XHJcbiAgICBjb25zdCBob3VycyA9IE1hdGguZmxvb3IodGltZSAvIDM2MDApO1xyXG4gICAgY29uc3QgbWludXRlcyA9IE1hdGguZmxvb3IoKHRpbWUgJSAzNjAwKSAvIDYwKTtcclxuICAgIGNvbnN0IHNlY29uZHMgPSB0aW1lICUgNjA7XHJcblxyXG4gICAgY29uc3QgcGFkID0gKG51bTogbnVtYmVyKSA9PiBudW0udG9TdHJpbmcoKS5wYWRTdGFydCgyLCAnMCcpO1xyXG5cclxuICAgIGlmICh0aGlzLnJ1Y0lucHV0RGF0YSAmJiB0aGlzLnJ1Y0lucHV0RGF0YS51c2VVVENGb3JUaW1lcikge1xyXG4gICAgICAgIC8vIFRoaXMgaXMgYSBzaW1wbGUgZHVyYXRpb24gZm9ybWF0LCBub3QgYSBzcGVjaWZpYyBVVEMgdGltZS5cclxuICAgICAgICAvLyBGb3IgYWN0dWFsIFVUQyB0aW1lIG9mIGRheSwgeW91J2QgdXNlIG5ldyBEYXRlKCkudG9VVENTdHJpbmcoKSBvciBzaW1pbGFyLlxyXG4gICAgICAgIC8vIEFzc3VtaW5nIHRpbWVyIHNob3VsZCBzaG93IGVsYXBzZWQgdGltZSBmb3JtYXR0ZWQgYXMgSEg6TU06U1MuXHJcbiAgICAgICAgcmV0dXJuIGAke3BhZChob3Vycyl9OiR7cGFkKG1pbnV0ZXMpfToke3BhZChzZWNvbmRzKX1gO1xyXG4gICAgfVxyXG4gICAgcmV0dXJuIGAke3BhZChob3Vycyl9OiR7cGFkKG1pbnV0ZXMpfToke3BhZChzZWNvbmRzKX1gO1xyXG4gIH1cclxuXHJcbiAgLy8gTWV0aG9kIHRvIHJlc2V0IHRoZSBjb21wb25lbnQgYW5kIHNlcnZpY2UgdG8gaW5pdGlhbCBzdGF0ZVxyXG4gIHJlc2V0KCk6IHZvaWQge1xyXG4gICAgdGhpcy5zY3JlZW5SZWNvcmRlclNlcnZpY2UucmVzZXRUb0lkbGUoKTtcclxuICAgIHRoaXMuaXNBdWRpb0VuYWJsZWQgPSB0aGlzLnJ1Y0lucHV0RGF0YS5kZWZhdWx0QXVkaW9TdGF0ZSA/PyB0cnVlO1xyXG4gICAgdGhpcy5jZHIuZGV0ZWN0Q2hhbmdlcygpO1xyXG4gIH1cclxufVxyXG5cclxuIiwiPGRpdiBjbGFzcz1cInNjcmVlbi1yZWNvcmRlci1jb250cm9sc1wiIFtuZ0NsYXNzXT1cImN1c3RvbVRoZW1lICsgJyBwb3NpdGlvbi0nICsgKHJ1Y0lucHV0RGF0YS5wb3NpdGlvbiB8fCBTY3JlZW5SZWNvcmRlclBvc2l0aW9uLkJvdHRvbSlcIlxyXG4gICpuZ0lmPVwiaXNCcm93c2VyU3VwcG9ydGVkXCI+XHJcblxyXG4gIDwhLS0gRXJyb3IgTWVzc2FnZSAtLT5cclxuICA8ZGl2ICpuZ0lmPVwiZXJyb3JNZXNzYWdlXCIgY2xhc3M9XCJyZWNvcmRlci1lcnJvclwiPlxyXG4gICAge3sgZXJyb3JNZXNzYWdlIH19XHJcbiAgPC9kaXY+XHJcblxyXG4gIDwhLS0gUmVjb3JkL1N0b3AgQnV0dG9uIC0tPlxyXG4gIDxidXR0b25cclxuICAgICpuZ0lmPVwicmVjb3JkaW5nU3RhdGUgPT09IFJlY29yZGluZ1N0YXRlLklkbGUgfHwgcmVjb3JkaW5nU3RhdGUgPT09IFJlY29yZGluZ1N0YXRlLlN0b3BwZWRcIiAoY2xpY2spPVwidG9nZ2xlUmVjb3JkKClcIlxyXG4gICAgW2F0dHIuYXJpYS1sYWJlbF09XCJnZXRMYWJlbCgncmVjb3JkQnV0dG9uJylcIiBbYXR0ci5hcmlhLXByZXNzZWRdPVwiZmFsc2VcIiBjbGFzcz1cInJlY29yZGVyLWJ1dHRvbiByZWNvcmQtYnV0dG9uXCI+XHJcbiAgICA8bmctY29udGFpbmVyICpuZ0lmPVwicnVjSW5wdXREYXRhLmRpc3BsYXlGb3JtYXQgPT09IERpc3BsYXlGb3JtYXQuSWNvblwiPlxyXG4gICAgICA8c3BhbiBjbGFzcz1cIm1hdGVyaWFsLWljb25zXCI+e3sgZ2V0UmVjb3JkaW5nSWNvbigpIHx8IGdldEljb24oJ3JlY29yZCcpIH19PC9zcGFuPlxyXG4gICAgPC9uZy1jb250YWluZXI+XHJcbiAgICA8bmctY29udGFpbmVyICpuZ0lmPVwicnVjSW5wdXREYXRhLmRpc3BsYXlGb3JtYXQgPT09IERpc3BsYXlGb3JtYXQuVGV4dFwiPnt7IGdldExhYmVsKCdyZWNvcmRCdXR0b24nKVxyXG4gICAgICB9fTwvbmctY29udGFpbmVyPlxyXG4gIDwvYnV0dG9uPlxyXG5cclxuICA8YnV0dG9uXHJcbiAgICAqbmdJZj1cInJlY29yZGluZ1N0YXRlID09PSBSZWNvcmRpbmdTdGF0ZS5SZWNvcmRpbmcgfHwgcmVjb3JkaW5nU3RhdGUgPT09IFJlY29yZGluZ1N0YXRlLlBhdXNlZFwiXHJcbiAgICAoY2xpY2spPVwidG9nZ2xlUmVjb3JkKClcIiBbYXR0ci5hcmlhLWxhYmVsXT1cImdldExhYmVsKCdzdG9wQnV0dG9uJylcIiBbYXR0ci5hcmlhLXByZXNzZWRdPVwidHJ1ZVwiXHJcbiAgICBjbGFzcz1cInJlY29yZGVyLWJ1dHRvbiBzdG9wLWJ1dHRvblwiPlxyXG4gICAgPG5nLWNvbnRhaW5lciAqbmdJZj1cInJ1Y0lucHV0RGF0YS5kaXNwbGF5Rm9ybWF0ID09PSBEaXNwbGF5Rm9ybWF0Lkljb25cIj5cclxuICAgICAgPHNwYW4gY2xhc3M9XCJtYXRlcmlhbC1pY29uc1wiPnt7IGdldEljb24oJ3N0b3AnKSB9fTwvc3Bhbj5cclxuICAgIDwvbmctY29udGFpbmVyPlxyXG4gICAgPG5nLWNvbnRhaW5lciAqbmdJZj1cInJ1Y0lucHV0RGF0YS5kaXNwbGF5Rm9ybWF0ID09PSBEaXNwbGF5Rm9ybWF0LlRleHRcIj57eyBnZXRMYWJlbCgnc3RvcEJ1dHRvbicpIH19PC9uZy1jb250YWluZXI+XHJcbiAgPC9idXR0b24+XHJcblxyXG4gIDwhLS0gUGF1c2UvUmVzdW1lIEJ1dHRvbiAtLT5cclxuICA8YnV0dG9uICpuZ0lmPVwicnVjSW5wdXREYXRhLnNob3dQYXVzZVJlc3VtZSAmJiByZWNvcmRpbmdTdGF0ZSA9PT0gUmVjb3JkaW5nU3RhdGUuUmVjb3JkaW5nXCJcclxuICAgIChjbGljayk9XCJ0b2dnbGVQYXVzZVJlc3VtZSgpXCIgW2F0dHIuYXJpYS1sYWJlbF09XCJnZXRMYWJlbCgncGF1c2VCdXR0b24nKVwiIFthdHRyLmFyaWEtcHJlc3NlZF09XCJmYWxzZVwiXHJcbiAgICBjbGFzcz1cInJlY29yZGVyLWJ1dHRvbiBwYXVzZS1idXR0b25cIj5cclxuICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJydWNJbnB1dERhdGEuZGlzcGxheUZvcm1hdCA9PT0gRGlzcGxheUZvcm1hdC5JY29uXCI+XHJcbiAgICAgIDxzcGFuIGNsYXNzPVwibWF0ZXJpYWwtaWNvbnNcIj57eyBnZXRJY29uKCdwYXVzZScpIH19PC9zcGFuPlxyXG4gICAgPC9uZy1jb250YWluZXI+XHJcbiAgICA8bmctY29udGFpbmVyICpuZ0lmPVwicnVjSW5wdXREYXRhLmRpc3BsYXlGb3JtYXQgPT09IERpc3BsYXlGb3JtYXQuVGV4dFwiPnt7IGdldExhYmVsKCdwYXVzZUJ1dHRvbicpIH19PC9uZy1jb250YWluZXI+XHJcbiAgPC9idXR0b24+XHJcbiAgPGJ1dHRvbiAqbmdJZj1cInJ1Y0lucHV0RGF0YS5zaG93UGF1c2VSZXN1bWUgJiYgcmVjb3JkaW5nU3RhdGUgPT09IFJlY29yZGluZ1N0YXRlLlBhdXNlZFwiXHJcbiAgICAoY2xpY2spPVwidG9nZ2xlUGF1c2VSZXN1bWUoKVwiIFthdHRyLmFyaWEtbGFiZWxdPVwiZ2V0TGFiZWwoJ3Jlc3VtZUJ1dHRvbicpXCIgW2F0dHIuYXJpYS1wcmVzc2VkXT1cInRydWVcIlxyXG4gICAgY2xhc3M9XCJyZWNvcmRlci1idXR0b24gcmVzdW1lLWJ1dHRvblwiPlxyXG4gICAgPG5nLWNvbnRhaW5lciAqbmdJZj1cInJ1Y0lucHV0RGF0YS5kaXNwbGF5Rm9ybWF0ID09PSBEaXNwbGF5Rm9ybWF0Lkljb25cIj5cclxuICAgICAgPHNwYW4gY2xhc3M9XCJtYXRlcmlhbC1pY29uc1wiPnt7IGdldEljb24oJ3Jlc3VtZScpIH19PC9zcGFuPlxyXG4gICAgPC9uZy1jb250YWluZXI+XHJcbiAgICA8bmctY29udGFpbmVyICpuZ0lmPVwicnVjSW5wdXREYXRhLmRpc3BsYXlGb3JtYXQgPT09IERpc3BsYXlGb3JtYXQuVGV4dFwiPnt7IGdldExhYmVsKCdyZXN1bWVCdXR0b24nKVxyXG4gICAgICB9fTwvbmctY29udGFpbmVyPlxyXG4gIDwvYnV0dG9uPlxyXG5cclxuICA8IS0tIEF1ZGlvIFRvZ2dsZSBCdXR0b24gLS0+XHJcbiAgPGJ1dHRvblxyXG4gICAgKm5nSWY9XCJydWNJbnB1dERhdGEuc2hvd0F1ZGlvVG9nZ2xlICYmIChyZWNvcmRpbmdTdGF0ZSA9PT0gUmVjb3JkaW5nU3RhdGUuSWRsZSB8fCByZWNvcmRpbmdTdGF0ZSA9PT0gUmVjb3JkaW5nU3RhdGUuU3RvcHBlZClcIlxyXG4gICAgKGNsaWNrKT1cInRvZ2dsZUF1ZGlvKClcIiBbYXR0ci5hcmlhLWxhYmVsXT1cImdldExhYmVsKCdhdWRpb1RvZ2dsZUJ1dHRvbicpXCIgW2F0dHIuYXJpYS1wcmVzc2VkXT1cImlzQXVkaW9FbmFibGVkXCJcclxuICAgIGNsYXNzPVwicmVjb3JkZXItYnV0dG9uIGF1ZGlvLXRvZ2dsZS1idXR0b25cIj5cclxuICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJydWNJbnB1dERhdGEuZGlzcGxheUZvcm1hdCA9PT0gRGlzcGxheUZvcm1hdC5JY29uXCI+XHJcbiAgICAgIDxzcGFuIGNsYXNzPVwibWF0ZXJpYWwtaWNvbnNcIj57eyBpc0F1ZGlvRW5hYmxlZCA/IGdldEljb24oJ2F1ZGlvT24nKSA6IGdldEljb24oJ2F1ZGlvT2ZmJykgfX08L3NwYW4+XHJcbiAgICA8L25nLWNvbnRhaW5lcj5cclxuICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJydWNJbnB1dERhdGEuZGlzcGxheUZvcm1hdCA9PT0gRGlzcGxheUZvcm1hdC5UZXh0XCI+XHJcbiAgICAgIHt7IGlzQXVkaW9FbmFibGVkID8gc2NyZWVuUmVjb3JkaW5nQ29uc3RhbnQuQVVESU9fT04gOiBzY3JlZW5SZWNvcmRpbmdDb25zdGFudC5BVURJT19PRkYgfX1cclxuICAgIDwvbmctY29udGFpbmVyPlxyXG4gIDwvYnV0dG9uPlxyXG5cclxuICA8IS0tIFRpbWVyIC0tPlxyXG4gIDxkaXZcclxuICAgICpuZ0lmPVwicnVjSW5wdXREYXRhLnNob3dUaW1lciAmJiAocmVjb3JkaW5nU3RhdGUgPT09IFJlY29yZGluZ1N0YXRlLlJlY29yZGluZyB8fCByZWNvcmRpbmdTdGF0ZSA9PT0gUmVjb3JkaW5nU3RhdGUuUGF1c2VkKVwiXHJcbiAgICBjbGFzcz1cInJlY29yZGVyLXRpbWVyXCIgYXJpYS1saXZlPVwicG9saXRlXCIgYXJpYS1hdG9taWM9XCJ0cnVlXCI+XHJcbiAgICB7eyBmb3JtYXR0ZWRUaW1lIH19XHJcbiAgPC9kaXY+XHJcblxyXG4gIDwhLS0gRG93bmxvYWQgQnV0dG9uIC0tPlxyXG4gIDxidXR0b25cclxuICAgICpuZ0lmPVwicnVjSW5wdXREYXRhLnNob3dEb3dubG9hZEJ1dHRvbiAmJiByZWNvcmRpbmdTdGF0ZSA9PT0gUmVjb3JkaW5nU3RhdGUuU3RvcHBlZCAmJiBzYWZlUmVjb3JkZWRVcmxcIlxyXG4gICAgKGNsaWNrKT1cIm9uRG93bmxvYWQoKVwiIFthdHRyLmFyaWEtbGFiZWxdPVwiZ2V0TGFiZWwoJ2Rvd25sb2FkQnV0dG9uJylcIiBjbGFzcz1cInJlY29yZGVyLWJ1dHRvbiBkb3dubG9hZC1idXR0b25cIj5cclxuICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJydWNJbnB1dERhdGEuZGlzcGxheUZvcm1hdCA9PT0gRGlzcGxheUZvcm1hdC5JY29uXCI+XHJcbiAgICAgIDxzcGFuIGNsYXNzPVwibWF0ZXJpYWwtaWNvbnNcIj57eyBnZXRJY29uKCdkb3dubG9hZCcpIH19PC9zcGFuPlxyXG4gICAgPC9uZy1jb250YWluZXI+XHJcbiAgICA8bmctY29udGFpbmVyICpuZ0lmPVwicnVjSW5wdXREYXRhLmRpc3BsYXlGb3JtYXQgPT09IERpc3BsYXlGb3JtYXQuVGV4dFwiPnt7IGdldExhYmVsKCdkb3dubG9hZEJ1dHRvbicpXHJcbiAgICAgIH19PC9uZy1jb250YWluZXI+XHJcbiAgPC9idXR0b24+XHJcblxyXG4gIDwhLS0gUGxheWJhY2sgQXJlYSAtLT5cclxuICA8ZGl2ICpuZ0lmPVwicnVjSW5wdXREYXRhLnNob3dQbGF5YmFja0NvbnRyb2xzICYmIHJlY29yZGluZ1N0YXRlID09PSBSZWNvcmRpbmdTdGF0ZS5TdG9wcGVkICYmIHNhZmVSZWNvcmRlZFVybFwiXHJcbiAgICBjbGFzcz1cInBsYXliYWNrLWFyZWFcIj5cclxuICAgIDx2aWRlbyAjdmlkZW9QbGF5ZXIgW3NyY109XCJzYWZlUmVjb3JkZWRVcmxcIiBjb250cm9scyBbYXR0ci5hcmlhLWxhYmVsXT1cImdldExhYmVsKCdwbGF5YmFja1ZpZGVvJylcIj48L3ZpZGVvPlxyXG4gICAgPGRpdiBjbGFzcz1cInZpZGVvLXRpbWVyLW92ZXJsYXlcIj5cclxuICAgICAgPHNwYW4+UmVjb3JkZWQgb246IHt7IHJlY29yZGluZ1RpbWVzdGFtcCB9fTwvc3Bhbj5cclxuICAgIDwvZGl2PlxyXG4gIDwvZGl2PlxyXG5cclxuICA8IS0tIFJlc2V0IEJ1dHRvbiAob3B0aW9uYWwsIGdvb2QgZm9yIHRlc3RpbmcvY2xlYXJpbmcgc3RhdGUpIC0tPlxyXG4gIDxidXR0b24gKm5nSWY9XCJyZWNvcmRpbmdTdGF0ZSA9PT0gUmVjb3JkaW5nU3RhdGUuU3RvcHBlZCAmJiBzYWZlUmVjb3JkZWRVcmxcIiAoY2xpY2spPVwicmVzZXQoKVwiXHJcbiAgICBhcmlhLWxhYmVsPVwiTmV3IFJlY29yZGluZ1wiIGNsYXNzPVwicmVjb3JkZXItYnV0dG9uIHJlc2V0LWJ1dHRvblwiPlxyXG4gICAgPG5nLWNvbnRhaW5lciAqbmdJZj1cInJ1Y0lucHV0RGF0YS5kaXNwbGF5Rm9ybWF0ID09PSBEaXNwbGF5Rm9ybWF0Lkljb25cIj5cclxuICAgICAgPHNwYW4gY2xhc3M9XCJtYXRlcmlhbC1pY29uc1wiPnJlZnJlc2g8L3NwYW4+XHJcbiAgICA8L25nLWNvbnRhaW5lcj5cclxuICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJydWNJbnB1dERhdGEuZGlzcGxheUZvcm1hdCA9PT0gRGlzcGxheUZvcm1hdC5UZXh0XCI+TmV3PC9uZy1jb250YWluZXI+XHJcbiAgPC9idXR0b24+XHJcblxyXG48L2Rpdj5cclxuXHJcbjxkaXYgKm5nSWY9XCIhaXNCcm93c2VyU3VwcG9ydGVkXCIgY2xhc3M9XCJyZWNvcmRlci1ub3RzdXBwb3J0ZWRcIiBbbmdDbGFzc109XCJjdXN0b21UaGVtZVwiPlxyXG4gIHt7c2NyZWVuUmVjb3JkaW5nQ29uc3RhbnQuTk9fU1VQUE9SVH19XHJcbjwvZGl2PiJdfQ==
|