@libs-ui/components-image-editor 0.2.30-6.1

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 ADDED
@@ -0,0 +1,254 @@
1
+ # Image Editor
2
+
3
+ ## Giới thiệu
4
+
5
+ `image-editor` là một component mạnh mẽ dùng để chỉnh sửa hình ảnh trong ứng dụng Angular. Component này cho phép người dùng thực hiện nhiều thao tác chỉnh sửa hình ảnh như cắt, thay đổi kích thước, xoay, lật và áp dụng các tỷ lệ cố định.
6
+
7
+ ## Tính năng
8
+
9
+ - Cắt hình ảnh với nhiều lựa chọn tỷ lệ khác nhau
10
+ - Thay đổi kích thước hình ảnh
11
+ - Xoay và lật hình ảnh
12
+ - Phóng to/thu nhỏ hình ảnh
13
+ - Khôi phục hình ảnh gốc
14
+ - Lưu hình ảnh dưới dạng file hoặc gửi qua API
15
+
16
+ ## Cài đặt
17
+
18
+ ### Yêu cầu
19
+
20
+ - Angular 18.0.0 trở lên
21
+ - Tailwind CSS 3.3.0 trở lên
22
+
23
+ ### Hướng dẫn
24
+
25
+ Để cài đặt component `image-editor`, sử dụng npm hoặc yarn:
26
+
27
+ ```bash
28
+ npm install @libs-ui/components-image-editor
29
+ ```
30
+
31
+ hoặc
32
+
33
+ ```bash
34
+ yarn add @libs-ui/components-image-editor
35
+ ```
36
+
37
+ ## Sử dụng
38
+
39
+ ### Import component
40
+
41
+ ```typescript
42
+ // example.component.ts
43
+ import { Component } from '@angular/core';
44
+ import { LibsUiComponentsImageEditorComponent } from '@libs-ui/components-image-editor';
45
+
46
+ @Component({
47
+ selector: 'app-example',
48
+ standalone: true,
49
+ imports: [LibsUiComponentsImageEditorComponent],
50
+ template: `
51
+ <libs_ui-components-image_editor
52
+ [(imgSrc)]="imageSource"
53
+ [modeShowButton]="'save-file'"
54
+ [nameFile]="'image.jpg'"
55
+ (outSaveFile)="onSaveFile($event)"
56
+ (outClose)="onClose($event)">
57
+ </libs_ui-components-image_editor>
58
+ `
59
+ })
60
+ export class ExampleComponent {
61
+ imageSource = 'https://example.com/path/to/image.jpg';
62
+
63
+ onSaveFile(data: {file: Blob, url: string, mode: string}) {
64
+ console.log('File đã được lưu:', data);
65
+ }
66
+
67
+ onClose(data: {isClickButtonClose: boolean}) {
68
+ console.log('Đã đóng trình chỉnh sửa ảnh');
69
+ }
70
+ }
71
+ ```
72
+
73
+ #### Cách 2: Sử dụng file HTML riêng biệt
74
+
75
+ ```typescript
76
+ // example.component.ts
77
+ import { Component } from '@angular/core';
78
+ import { LibsUiComponentsImageEditorComponent } from '@libs-ui/components-image-editor';
79
+
80
+ @Component({
81
+ selector: 'app-example',
82
+ standalone: true,
83
+ imports: [LibsUiComponentsImageEditorComponent],
84
+ templateUrl: './example.component.html'
85
+ })
86
+ export class ExampleComponent {
87
+ imageSource = 'https://example.com/path/to/image.jpg';
88
+
89
+ onSaveFile(data: {file: Blob, url: string, mode: string}) {
90
+ console.log('File đã được lưu:', data);
91
+ }
92
+
93
+ onClose(data: {isClickButtonClose: boolean}) {
94
+ console.log('Đã đóng trình chỉnh sửa ảnh');
95
+ }
96
+ }
97
+ ```
98
+
99
+ ```html
100
+ <!-- example.component.html -->
101
+ <libs_ui-components-image_editor
102
+ [(imgSrc)]="imageSource"
103
+ [modeShowButton]="'save-file'"
104
+ [nameFile]="'image.jpg'"
105
+ (outSaveFile)="onSaveFile($event)"
106
+ (outClose)="onClose($event)">
107
+ </libs_ui-components-image_editor>
108
+ ```
109
+
110
+ ## Công nghệ sử dụng
111
+
112
+ - **Angular 18**: Sử dụng các tính năng mới nhất của Angular 18 như control flow (@if, @for), standalone components, và signals
113
+ - **Tailwind CSS**: Component được xây dựng với Tailwind CSS 3.3+ để quản lý style
114
+ - **File Structure**: HTML, CSS và TypeScript được tách riêng để dễ dàng bảo trì và cập nhật
115
+
116
+ ## API Reference
117
+
118
+ ### Inputs
119
+
120
+ | Tên | Kiểu dữ liệu | Mặc định | Mô tả |
121
+ |-----|--------------|----------|-------|
122
+ | imgSrc | `string` | Bắt buộc | Đường dẫn hoặc base64 của hình ảnh cần chỉnh sửa |
123
+ | originUrl | `string` | - | URL gốc của hình ảnh |
124
+ | nameFile | `string` | - | Tên file khi lưu |
125
+ | modeShowButton | `'save-file' \| 'save-api'` | `'save-file'` | Chế độ nút lưu |
126
+ | mimetype | `string` | - | Định dạng của hình ảnh |
127
+ | zIndex | `number` | 1200 | z-index của component |
128
+ | hasZoom | `boolean` | false | Cho phép phóng to/thu nhỏ hình ảnh |
129
+ | aspectRatio | `IAspectRatio` | - | Tỷ lệ khung hình mặc định |
130
+ | requiredCropFollowRatio | `boolean` | false | Bắt buộc cắt theo tỷ lệ đã chọn |
131
+
132
+ ### Outputs
133
+
134
+ | Tên | Kiểu dữ liệu | Mô tả |
135
+ |-----|--------------|-------|
136
+ | outClose | `{isClickButtonClose: boolean}` | Sự kiện khi đóng trình chỉnh sửa |
137
+ | outSaveFile | `ISaveFile` | Sự kiện khi lưu file |
138
+ | outFunctionsControl | `IImageEditorFunctionControlEvent` | Sự kiện để kiểm soát các chức năng |
139
+
140
+ ### Interfaces
141
+
142
+ #### ISaveFile
143
+ ```typescript
144
+ export interface ISaveFile {
145
+ file: Blob;
146
+ url: string;
147
+ mode: 'save-file' | 'save-api' | 'save-api-as-new-file';
148
+ }
149
+ ```
150
+
151
+ #### IImageEditorFunctionControlEvent
152
+ ```typescript
153
+ export interface IImageEditorFunctionControlEvent {
154
+ cropImage: () => Promise<string>;
155
+ setLoadingState: (loading: boolean) => void;
156
+ }
157
+ ```
158
+
159
+ ## Ví dụ
160
+
161
+ ### Chỉnh sửa và lưu hình ảnh
162
+
163
+ **TypeScript (edit-image.component.ts):**
164
+ ```typescript
165
+ import { Component } from '@angular/core';
166
+ import { ISaveFile, LibsUiComponentsImageEditorComponent } from '@libs-ui/components-image-editor';
167
+
168
+ @Component({
169
+ selector: 'app-edit-image',
170
+ standalone: true,
171
+ imports: [LibsUiComponentsImageEditorComponent],
172
+ templateUrl: './edit-image.component.html'
173
+ })
174
+ export class EditImageComponent {
175
+ imageSource = 'https://example.com/path/to/image.jpg';
176
+ showEditor = false;
177
+ editedImageUrl = '';
178
+
179
+ openEditor() {
180
+ this.showEditor = true;
181
+ }
182
+
183
+ onSaveFile(data: ISaveFile) {
184
+ this.editedImageUrl = data.url;
185
+ this.showEditor = false;
186
+ // Xử lý file đã lưu
187
+ console.log('Đã lưu file:', data.file);
188
+ }
189
+
190
+ onCloseEditor(data: {isClickButtonClose: boolean}) {
191
+ this.showEditor = false;
192
+ }
193
+ }
194
+ ```
195
+
196
+ **HTML (edit-image.component.html):**
197
+ ```html
198
+ <button (click)="openEditor()" class="px-4 py-2 bg-blue-500 text-white rounded">
199
+ Mở trình chỉnh sửa ảnh
200
+ </button>
201
+
202
+ @if (showEditor) {
203
+ <libs_ui-components-image_editor
204
+ [(imgSrc)]="imageSource"
205
+ [nameFile]="'my-image.jpg'"
206
+ (outSaveFile)="onSaveFile($event)"
207
+ (outClose)="onCloseEditor($event)">
208
+ </libs_ui-components-image_editor>
209
+ }
210
+
211
+ @if (editedImageUrl) {
212
+ <div class="mt-4">
213
+ <h3 class="text-lg font-semibold">Hình ảnh đã chỉnh sửa:</h3>
214
+ <img [src]="editedImageUrl" alt="Hình ảnh đã chỉnh sửa" class="mt-2 max-w-full h-auto border rounded" />
215
+ </div>
216
+ }
217
+ ```
218
+
219
+ ### Sử dụng với tỷ lệ cố định
220
+
221
+ **TypeScript (fixed-ratio.component.ts):**
222
+ ```typescript
223
+ import { Component } from '@angular/core';
224
+ import { LibsUiComponentsImageEditorComponent } from '@libs-ui/components-image-editor';
225
+ import { IAspectRatio } from '@libs-ui/interfaces-types';
226
+
227
+ @Component({
228
+ selector: 'app-fixed-ratio',
229
+ standalone: true,
230
+ imports: [LibsUiComponentsImageEditorComponent],
231
+ templateUrl: './fixed-ratio.component.html'
232
+ })
233
+ export class FixedRatioComponent {
234
+ imageSource = 'https://example.com/path/to/image.jpg';
235
+ aspectRatio: IAspectRatio = {
236
+ key: '1:1',
237
+ value: 1
238
+ };
239
+
240
+ onSaveFile(data: any) {
241
+ console.log('Đã lưu hình ảnh với tỷ lệ 1:1');
242
+ }
243
+ }
244
+ ```
245
+
246
+ **HTML (fixed-ratio.component.html):**
247
+ ```html
248
+ <libs_ui-components-image_editor
249
+ [(imgSrc)]="imageSource"
250
+ [aspectRatio]="aspectRatio"
251
+ [requiredCropFollowRatio]="true"
252
+ (outSaveFile)="onSaveFile($event)">
253
+ </libs_ui-components-image_editor>
254
+ ```
@@ -0,0 +1,16 @@
1
+ import { ICropRatioItem } from "../interfaces/image-editor.interface";
2
+ export declare const cropRationItems: () => Array<ICropRatioItem>;
3
+ export declare const getDataUrl: (canvas: any, mimetype?: string, src?: string) => any;
4
+ export declare const getWidthHeightResizeCropFollow: (ratioValue: number | undefined, width: number, height: number, maxWidth: number, maxHeight: number, minWidth: number, minHeight: number) => number[];
5
+ export declare const getCropRectImage: (rectClip: {
6
+ left: number;
7
+ top: number;
8
+ width: number;
9
+ height: number;
10
+ }, imgWidth: number, imgHeight: number, scale: number) => {
11
+ left: number;
12
+ top: number;
13
+ width: number;
14
+ height: number;
15
+ };
16
+ export declare const getStylesOfElement: <T>(element: HTMLElement, fields: Array<string>) => Array<T>;
@@ -0,0 +1,48 @@
1
+ import { ElementRef } from '@angular/core';
2
+ import { ISaveFile } from '../interfaces/image-editor.interface';
3
+ import * as i0 from "@angular/core";
4
+ export declare class LibsUiComponentsImageEditorDemoComponent {
5
+ imageFileInput: ElementRef<HTMLInputElement>;
6
+ imageSource: string;
7
+ showEditor: boolean;
8
+ editedImageUrl: string;
9
+ selectedFile: File | null;
10
+ imageName: string;
11
+ inputsDoc: {
12
+ name: string;
13
+ type: string;
14
+ default: string;
15
+ description: string;
16
+ }[];
17
+ outputsDoc: {
18
+ name: string;
19
+ type: string;
20
+ description: string;
21
+ }[];
22
+ interfacesDoc: {
23
+ name: string;
24
+ code: string;
25
+ description: string;
26
+ }[];
27
+ features: {
28
+ id: number;
29
+ icon: string;
30
+ title: string;
31
+ description: string;
32
+ }[];
33
+ codeExamples: {
34
+ id: number;
35
+ title: string;
36
+ code: string;
37
+ }[];
38
+ copyToClipboard(text: string): void;
39
+ onFileSelected(event: Event): void;
40
+ uploadAndEdit(): void;
41
+ onSaveImage(data: ISaveFile): void;
42
+ onCloseEditor(data: {
43
+ isClickButtonClose: boolean;
44
+ }): void;
45
+ downloadImage(): void;
46
+ static ɵfac: i0.ɵɵFactoryDeclaration<LibsUiComponentsImageEditorDemoComponent, never>;
47
+ static ɵcmp: i0.ɵɵComponentDeclaration<LibsUiComponentsImageEditorDemoComponent, "lib-image-editor-demo", never, {}, {}, never, never, true, never>;
48
+ }
@@ -0,0 +1,89 @@
1
+ import { get } from "@libs-ui/utils";
2
+ export const cropRationItems = () => {
3
+ return [
4
+ {
5
+ key: 'free',
6
+ icon: 'libs-ui-icon-customize-image-outline'
7
+ },
8
+ {
9
+ key: '1:1',
10
+ value: 1,
11
+ icon: 'libs-ui-icon-ratio-1-1'
12
+ },
13
+ {
14
+ key: '2:3',
15
+ value: 2 / 3,
16
+ icon: 'libs-ui-icon-ratio-2-3'
17
+ },
18
+ {
19
+ key: '3:2',
20
+ value: 3 / 2,
21
+ icon: 'libs-ui-icon-ratio-3-2'
22
+ },
23
+ {
24
+ key: '3:4',
25
+ value: 3 / 4,
26
+ icon: 'libs-ui-icon-ratio-3-4'
27
+ },
28
+ {
29
+ key: '4:3',
30
+ value: 4 / 3,
31
+ icon: 'libs-ui-icon-ratio-4-3'
32
+ },
33
+ {
34
+ key: '9:16',
35
+ value: 9 / 16,
36
+ icon: 'libs-ui-icon-ratio-9-16'
37
+ },
38
+ {
39
+ key: '16:9',
40
+ value: 16 / 9,
41
+ icon: 'libs-ui-icon-ratio-16-9'
42
+ }
43
+ ];
44
+ };
45
+ const getMimeTypeFromSrc = (src) => {
46
+ if (!src) {
47
+ return undefined;
48
+ }
49
+ const match = /^data:(.*?);/.exec(src);
50
+ if (match) {
51
+ return match[1];
52
+ }
53
+ const srcSplit = src.split('.');
54
+ const mineType = srcSplit[srcSplit.length - 1];
55
+ return `image/${(mineType.toLowerCase()) === 'jpg' ? 'jpeg' : mineType}`;
56
+ };
57
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
+ export const getDataUrl = (canvas, mimetype, src) => {
59
+ const mimeTypeBySrc = getMimeTypeFromSrc(src);
60
+ if (mimetype || mimeTypeBySrc) {
61
+ return canvas.toDataURL(mimetype || mimeTypeBySrc);
62
+ }
63
+ return canvas.toDataURL();
64
+ };
65
+ export const getWidthHeightResizeCropFollow = (ratioValue, width, height, maxWidth, maxHeight, minWidth, minHeight) => {
66
+ height = Math.min(height, maxHeight);
67
+ height = Math.max(minHeight, height);
68
+ if (ratioValue) {
69
+ width = height * ratioValue;
70
+ width = Math.min(maxWidth, width);
71
+ width = Math.max(minWidth, width);
72
+ height = width / ratioValue;
73
+ }
74
+ width = Math.min(maxWidth, width);
75
+ width = Math.max(minWidth, width);
76
+ return [width, height];
77
+ };
78
+ export const getCropRectImage = (rectClip, imgWidth, imgHeight, scale) => {
79
+ return {
80
+ left: Math.max(rectClip.left * scale, 0),
81
+ top: Math.max(rectClip.top * scale, 0),
82
+ width: rectClip.width * scale,
83
+ height: rectClip.height * scale
84
+ };
85
+ };
86
+ export const getStylesOfElement = (element, fields) => {
87
+ return fields?.map(field => parseFloat(get(element, field) || '0'));
88
+ };
89
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1hZ2UtZWRpdG9yLmRlZmluZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uL2xpYnMtdWkvY29tcG9uZW50cy9pbWFnZS1lZGl0b3Ivc3JjL2RlZmluZXMvaW1hZ2UtZWRpdG9yLmRlZmluZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFHckMsTUFBTSxDQUFDLE1BQU0sZUFBZSxHQUFHLEdBQTBCLEVBQUU7SUFDekQsT0FBTztRQUNMO1lBQ0UsR0FBRyxFQUFFLE1BQU07WUFDWCxJQUFJLEVBQUUsc0NBQXNDO1NBQzdDO1FBQ0Q7WUFDRSxHQUFHLEVBQUUsS0FBSztZQUNWLEtBQUssRUFBRSxDQUFDO1lBQ1IsSUFBSSxFQUFFLHdCQUF3QjtTQUMvQjtRQUNEO1lBQ0UsR0FBRyxFQUFFLEtBQUs7WUFDVixLQUFLLEVBQUUsQ0FBQyxHQUFHLENBQUM7WUFDWixJQUFJLEVBQUUsd0JBQXdCO1NBQy9CO1FBQ0Q7WUFDRSxHQUFHLEVBQUUsS0FBSztZQUNWLEtBQUssRUFBRSxDQUFDLEdBQUcsQ0FBQztZQUNaLElBQUksRUFBRSx3QkFBd0I7U0FDL0I7UUFDRDtZQUNFLEdBQUcsRUFBRSxLQUFLO1lBQ1YsS0FBSyxFQUFFLENBQUMsR0FBRyxDQUFDO1lBQ1osSUFBSSxFQUFFLHdCQUF3QjtTQUMvQjtRQUNEO1lBQ0UsR0FBRyxFQUFFLEtBQUs7WUFDVixLQUFLLEVBQUUsQ0FBQyxHQUFHLENBQUM7WUFDWixJQUFJLEVBQUUsd0JBQXdCO1NBQy9CO1FBQ0Q7WUFDRSxHQUFHLEVBQUUsTUFBTTtZQUNYLEtBQUssRUFBRSxDQUFDLEdBQUcsRUFBRTtZQUNiLElBQUksRUFBRSx5QkFBeUI7U0FDaEM7UUFDRDtZQUNFLEdBQUcsRUFBRSxNQUFNO1lBQ1gsS0FBSyxFQUFFLEVBQUUsR0FBRyxDQUFDO1lBQ2IsSUFBSSxFQUFFLHlCQUF5QjtTQUNo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QUFDLE1BQU0sa0JBQWtCLEdBQUcsQ0FBSSxPQUFvQixFQUFFLE1BQXFCLEVBQVksRUFBRTtJQUM3RixPQUFPLE1BQU0sRUFBRSxHQUFHLENBQUksS0FBSyxDQUFDLEVBQUUsQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUMsSUFBSSxHQUFHLENBQU0sQ0FBQyxDQUFDO0FBQzlFLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGdldCB9IGZyb20gXCJAbGlicy11aS91dGlsc1wiO1xuaW1wb3J0IHsgSUNyb3BSYXRpb0l0ZW0gfSBmcm9tIFwiLi4vaW50ZXJmYWNlcy9pbWFnZS1lZGl0b3IuaW50ZXJmYWNlXCI7XG5cbmV4cG9ydCBjb25zdCBjcm9wUmF0aW9uSXRlbXMgPSAoKTogQXJyYXk8SUNyb3BSYXRpb0l0ZW0+ID0+IHtcbiAgcmV0dXJuIFtcbiAgICB7XG4gICAgICBrZXk6ICdmcmVlJyxcbiAgICAgIGljb246ICdsaWJzLXVpLWljb24tY3VzdG9taXplLWltYWdlLW91dGxpbmUnXG4gICAgfSxcbiAgICB7XG4gICAgICBrZXk6ICcxOjEnLFxuICAgICAgdmFsdWU6IDEsXG4gICAgICBpY29uOiAnbGlicy11aS1pY29uLXJhdGlvLTEtMSdcbiAgICB9LFxuICAgIHtcbiAgICAgIGtleTogJzI6MycsXG4gICAgICB2YWx1ZTogMiAvIDMsXG4gICAgICBpY29uOiAnbGlicy11aS1pY29uLXJhdGlvLTItMydcbiAgICB9LFxuICAgIHtcbiAgICAgIGtleTogJzM6MicsXG4gICAgICB2YWx1ZTogMyAvIDIsXG4gICAgICBpY29uOiAnbGlicy11aS1pY29uLXJhdGlvLTMtMidcbiAgICB9LFxuICAgIHtcbiAgICAgIGtleTogJzM6NCcsXG4gICAgICB2YWx1ZTogMyAvIDQsXG4gICAgICBpY29uOiAnbGlicy11aS1pY29uLXJhdGlvLTMtNCdcbiAgICB9LFxuICAgIHtcbiAgICAgIGtleTogJzQ6MycsXG4gICAgICB2YWx1ZTogNCAvIDMsXG4gICAgICBpY29uOiAnbGlicy11aS1pY29uLXJhdGlvLTQtMydcbiAgICB9LFxuICAgIHtcbiAgICAgIGtleTogJzk6MTYnLFxuICAgICAgdmFsdWU6IDkgLyAxNixcbiAgICAgIGljb246ICdsaWJzLXVpLWljb24tcmF0aW8tOS0xNidcbiAgICB9LFxuICAgIHtcbiAgICAgIGtleTogJzE2OjknLFxuICAgICAgdmFsdWU6IDE2IC8gOSxcbiAgICAgIGljb246ICdsaWJzLXVpLWljb24tcmF0aW8tMTYtOSdcbiAgICB9XG4gIF07XG59O1xuXG5jb25zdCBnZXRNaW1lVHlwZUZyb21TcmMgPSAoc3JjPzogc3RyaW5nKSA9PiB7XG4gIGlmICghc3JjKSB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxuICBjb25zdCBtYXRjaCA9IC9eZGF0YTooLio/KTsvLmV4ZWMoc3JjKTtcblxuICBpZiAobWF0Y2gpIHtcbiAgICByZXR1cm4gbWF0Y2hbMV07XG4gIH1cbiAgY29uc3Qgc3JjU3BsaXQgPSBzcmMuc3BsaXQoJy4nKTtcbiAgY29uc3QgbWluZVR5cGUgPSBzcmNTcGxpdFtzcmNTcGxpdC5sZW5ndGggLSAxXTtcblxuICByZXR1cm4gYGltYWdlLyR7KG1pbmVUeXBlLnRvTG93ZXJDYXNlKCkpID09PSAnanBnJyA/ICdqcGVnJyA6IG1pbmVUeXBlfWA7XG59O1xuXG4vLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLWV4cGxpY2l0LWFueVxuZXhwb3J0IGNvbnN0IGdldERhdGFVcmwgPSAoY2FudmFzOiBhbnksIG1pbWV0eXBlPzogc3RyaW5nLCBzcmM/OiBzdHJpbmcpID0+IHtcbiAgY29uc3QgbWltZVR5cGVCeVNyYyA9IGdldE1pbWVUeXBlRnJvbVNyYyhzcmMpO1xuXG4gIGlmIChtaW1ldHlwZSB8fCBtaW1lVHlwZUJ5U3JjKSB7XG4gICAgcmV0dXJuIGNhbnZhcy50b0RhdGFVUkwobWltZXR5cGUgfHwgbWltZVR5cGVCeVNyYyk7XG4gIH1cblxuICByZXR1cm4gY2FudmFzLnRvRGF0YVVSTCgpO1xufTtcblxuZXhwb3J0IGNvbnN0IGdldFdpZHRoSGVpZ2h0UmVzaXplQ3JvcEZvbGxvdyA9IChyYXRpb1ZhbHVlOiBudW1iZXIgfCB1bmRlZmluZWQsIHdpZHRoOiBudW1iZXIsIGhlaWdodDogbnVtYmVyLCBtYXhXaWR0aDogbnVtYmVyLCBtYXhIZWlnaHQ6IG51bWJlciwgbWluV2lkdGg6IG51bWJlciwgbWluSGVpZ2h0OiBudW1iZXIpID0+IHtcbiAgaGVpZ2h0ID0gTWF0aC5taW4oaGVpZ2h0LCBtYXhIZWlnaHQpO1xuICBoZWlnaHQgPSBNYXRoLm1heChtaW5IZWlnaHQsIGhlaWdodCk7XG4gIGlmIChyYXRpb1ZhbHVlKSB7XG4gICAgd2lkdGggPSBoZWlnaHQgKiByYXRpb1ZhbHVlO1xuICAgIHdpZHRoID0gTWF0aC5taW4obWF4V2lkdGgsIHdpZHRoKTtcbiAgICB3aWR0aCA9IE1hdGgubWF4KG1pbldpZHRoLCB3aWR0aCk7XG4gICAgaGVpZ2h0ID0gd2lkdGggLyByYXRpb1ZhbHVlO1xuICB9XG4gIHdpZHRoID0gTWF0aC5taW4obWF4V2lkdGgsIHdpZHRoKTtcbiAgd2lkdGggPSBNYXRoLm1heChtaW5XaWR0aCwgd2lkdGgpO1xuXG4gIHJldHVybiBbd2lkdGgsIGhlaWdodF07XG59O1xuXG5leHBvcnQgY29uc3QgZ2V0Q3JvcFJlY3RJbWFnZSA9IChyZWN0Q2xpcDogeyBsZWZ0OiBudW1iZXI7IHRvcDogbnVtYmVyOyB3aWR0aDogbnVtYmVyOyBoZWlnaHQ6IG51bWJlcjsgfSwgaW1nV2lkdGg6IG51bWJlciwgaW1nSGVpZ2h0OiBudW1iZXIsIHNjYWxlOiBudW1iZXIpID0+IHtcbiAgcmV0dXJuIHtcbiAgICBsZWZ0OiBNYXRoLm1heChyZWN0Q2xpcC5sZWZ0ICogc2NhbGUsIDApLFxuICAgIHRvcDogTWF0aC5tYXgocmVjdENsaXAudG9wICogc2NhbGUsIDApLFxuICAgIHdpZHRoOiByZWN0Q2xpcC53aWR0aCAqIHNjYWxlLFxuICAgIGhlaWdodDogcmVjdENsaXAuaGVpZ2h0ICogc2NhbGVcbiAgfTtcbn07XG5cbmV4cG9ydCBjb25zdCBnZXRTdHlsZXNPZkVsZW1lbnQgPSA8VD4oZWxlbWVudDogSFRNTEVsZW1lbnQsIGZpZWxkczogQXJyYXk8c3RyaW5nPik6IEFycmF5PFQ+ID0+IHtcbiAgcmV0dXJuIGZpZWxkcz8ubWFwPFQ+KGZpZWxkID0+IHBhcnNlRmxvYXQoZ2V0KGVsZW1lbnQsIGZpZWxkKSB8fCAnMCcpIGFzIFQpO1xufTsiXX0=