@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 +254 -0
- package/defines/image-editor.define.d.ts +16 -0
- package/demo/image-editor-demo.component.d.ts +48 -0
- package/esm2022/defines/image-editor.define.mjs +89 -0
- package/esm2022/demo/image-editor-demo.component.mjs +154 -0
- package/esm2022/image-editor.component.mjs +781 -0
- package/esm2022/index.mjs +6 -0
- package/esm2022/interfaces/function-control-event.interface.mjs +2 -0
- package/esm2022/interfaces/image-editor.interface.mjs +2 -0
- package/esm2022/libs-ui-components-image-editor.mjs +5 -0
- package/esm2022/resize/resize.component.mjs +125 -0
- package/fesm2022/libs-ui-components-image-editor.mjs +1141 -0
- package/fesm2022/libs-ui-components-image-editor.mjs.map +1 -0
- package/image-editor.component.d.ts +117 -0
- package/index.d.ts +5 -0
- package/interfaces/function-control-event.interface.d.ts +4 -0
- package/interfaces/image-editor.interface.d.ts +52 -0
- package/package.json +35 -0
- package/resize/resize.component.d.ts +32 -0
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=
|