@libs-ui/components-image-editor 0.1.1-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.
@@ -0,0 +1,1160 @@
1
+ import { NgTemplateOutlet } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { EventEmitter, Output, Input, Component, signal, input, model, viewChild, output, inject, DestroyRef, Optional, Inject, ChangeDetectionStrategy, ViewChild } from '@angular/core';
4
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
+ import { LibsUiComponentsButtonsButtonComponent } from '@libs-ui/components-buttons-button';
6
+ import { LibsUiComponentsInputsValidComponent } from '@libs-ui/components-inputs-valid';
7
+ import { LibsUiComponentsModalComponent } from '@libs-ui/components-modal';
8
+ import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
9
+ import { LibsUiComponentsSpinnerComponent } from '@libs-ui/components-spinner';
10
+ import { LibsUiDynamicComponentService } from '@libs-ui/services-dynamic-component';
11
+ import { get, convertBase64ToBlob, getLabelBySizeFile, convertBlobToFile, isNil, set, LINK_IMAGE_ERROR_TOKEN_INJECT } from '@libs-ui/utils';
12
+ import * as i1 from '@ngx-translate/core';
13
+ import { TranslateModule } from '@ngx-translate/core';
14
+ import { fromEvent } from 'rxjs';
15
+ import { takeUntil, tap, mergeMap } from 'rxjs/operators';
16
+
17
+ const cropRationItems = () => {
18
+ return [
19
+ {
20
+ key: 'free',
21
+ icon: 'libs-ui-icon-customize-image-outline',
22
+ },
23
+ {
24
+ key: '1:1',
25
+ value: 1,
26
+ icon: 'libs-ui-icon-ratio-1-1',
27
+ },
28
+ {
29
+ key: '2:3',
30
+ value: 2 / 3,
31
+ icon: 'libs-ui-icon-ratio-2-3',
32
+ },
33
+ {
34
+ key: '3:2',
35
+ value: 3 / 2,
36
+ icon: 'libs-ui-icon-ratio-3-2',
37
+ },
38
+ {
39
+ key: '3:4',
40
+ value: 3 / 4,
41
+ icon: 'libs-ui-icon-ratio-3-4',
42
+ },
43
+ {
44
+ key: '4:3',
45
+ value: 4 / 3,
46
+ icon: 'libs-ui-icon-ratio-4-3',
47
+ },
48
+ {
49
+ key: '9:16',
50
+ value: 9 / 16,
51
+ icon: 'libs-ui-icon-ratio-9-16',
52
+ },
53
+ {
54
+ key: '16:9',
55
+ value: 16 / 9,
56
+ icon: 'libs-ui-icon-ratio-16-9',
57
+ },
58
+ ];
59
+ };
60
+ const getMimeTypeFromSrc = (src) => {
61
+ if (!src) {
62
+ return undefined;
63
+ }
64
+ const match = /^data:(.*?);/.exec(src);
65
+ if (match) {
66
+ return match[1];
67
+ }
68
+ const srcSplit = src.split('.');
69
+ const mineType = srcSplit[srcSplit.length - 1];
70
+ return `image/${mineType.toLowerCase() === 'jpg' ? 'jpeg' : mineType}`;
71
+ };
72
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
+ const getDataUrl = (canvas, mimetype, src) => {
74
+ const mimeTypeBySrc = getMimeTypeFromSrc(src);
75
+ if (mimetype || mimeTypeBySrc) {
76
+ return canvas.toDataURL(mimetype || mimeTypeBySrc);
77
+ }
78
+ return canvas.toDataURL();
79
+ };
80
+ const getWidthHeightResizeCropFollow = (ratioValue, width, height, maxWidth, maxHeight, minWidth, minHeight) => {
81
+ height = Math.min(height, maxHeight);
82
+ height = Math.max(minHeight, height);
83
+ if (ratioValue) {
84
+ width = height * ratioValue;
85
+ width = Math.min(maxWidth, width);
86
+ width = Math.max(minWidth, width);
87
+ height = width / ratioValue;
88
+ }
89
+ width = Math.min(maxWidth, width);
90
+ width = Math.max(minWidth, width);
91
+ return [width, height];
92
+ };
93
+ const getCropRectImage = (rectClip, imgWidth, imgHeight, scale) => {
94
+ return {
95
+ left: Math.max(rectClip.left * scale, 0),
96
+ top: Math.max(rectClip.top * scale, 0),
97
+ width: rectClip.width * scale,
98
+ height: rectClip.height * scale,
99
+ };
100
+ };
101
+ const getStylesOfElement = (element, fields) => {
102
+ return fields?.map((field) => parseFloat(get(element, field) || '0'));
103
+ };
104
+
105
+ class LibsUiComponentsImageEditorResizeComponent {
106
+ buttonsFooter;
107
+ image = new Image();
108
+ originFileSize;
109
+ currentFileSize;
110
+ inputValidFunctionControl;
111
+ resizeData;
112
+ originWidth;
113
+ originHeight;
114
+ src;
115
+ zIndex;
116
+ mimetype;
117
+ outClose = new EventEmitter();
118
+ outSave = new EventEmitter();
119
+ constructor() {
120
+ this.buttonsFooter = [
121
+ {
122
+ type: 'button-third',
123
+ label: 'i18n_cancel',
124
+ action: async () => this.outClose.emit(),
125
+ },
126
+ {
127
+ label: 'i18n_save',
128
+ action: async () => {
129
+ this.outSave.emit(this.resizeData);
130
+ this.outClose.emit();
131
+ },
132
+ },
133
+ ];
134
+ this.inputValidFunctionControl = [];
135
+ }
136
+ async ngOnInit() {
137
+ const fileSize = await this.getFileSize();
138
+ this.currentFileSize = fileSize;
139
+ this.originFileSize = fileSize;
140
+ }
141
+ async handlerResizeRatio() {
142
+ let ratio = parseInt(`${this.resizeData.ratio}`);
143
+ if (!ratio || ratio < 0) {
144
+ return;
145
+ }
146
+ if (ratio > 100) {
147
+ ratio = 100;
148
+ this.resizeData.ratio = ratio;
149
+ }
150
+ this.resizeData.width = Math.max(parseInt(`${(this.originWidth * ratio) / 100}`), 1);
151
+ this.resizeData.height = Math.max(parseInt(`${(this.originHeight * ratio) / 100}`), 1);
152
+ // this.inputValidFunctionControl.forEach(item => item.detectChanges());
153
+ this.currentFileSize = await this.getFileSize();
154
+ }
155
+ getFileSize() {
156
+ return new Promise((resolve) => {
157
+ this.image.onload = () => {
158
+ const canvas = document.createElement('canvas');
159
+ canvas.width = this.resizeData.width;
160
+ canvas.height = this.resizeData.height;
161
+ const ctx = canvas.getContext('2d');
162
+ if (ctx) {
163
+ ctx.drawImage(this.image, 0, 0, canvas.width, canvas.height);
164
+ }
165
+ const src = getDataUrl(canvas, this.mimetype, this.src);
166
+ const file = convertBase64ToBlob(src);
167
+ resolve(getLabelBySizeFile(convertBlobToFile(file, Date.now().toLocaleString()).size));
168
+ };
169
+ this.image.src = this.src;
170
+ });
171
+ }
172
+ handlerResizeWidth() {
173
+ this.processResizeWidthOrHeight('width', 'height', this.originWidth, this.originHeight);
174
+ }
175
+ handlerResizeHeight() {
176
+ this.processResizeWidthOrHeight('height', 'width', this.originHeight, this.originWidth);
177
+ }
178
+ async processResizeWidthOrHeight(resizeBy, fieldResize, originByValue, originOtherValue) {
179
+ const value = parseInt(`${this.resizeData[resizeBy]}`);
180
+ if (!value || value > originByValue) {
181
+ return;
182
+ }
183
+ this.resizeData[fieldResize] = Math.max(parseInt(`${(originOtherValue * value) / originByValue}`), 1);
184
+ this.resizeData.ratio = Math.max(parseInt(`${(100 * value) / originByValue}`), 1);
185
+ this.currentFileSize = await this.getFileSize();
186
+ }
187
+ handlerInputValidFunctionControl(event) {
188
+ this.inputValidFunctionControl.push(event);
189
+ }
190
+ handlerEvent(event) {
191
+ if (event === 'close') {
192
+ this.outClose.emit();
193
+ }
194
+ }
195
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsImageEditorResizeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
196
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: LibsUiComponentsImageEditorResizeComponent, isStandalone: true, selector: "libs-ui-components-image_editor-resize", inputs: { resizeData: "resizeData", originWidth: "originWidth", originHeight: "originHeight", src: "src", zIndex: "zIndex", mimetype: "mimetype" }, outputs: { outClose: "outClose", outSave: "outSave" }, ngImport: i0, template: "<libs_ui-components-modal\n [headerConfig]=\"{ ignoreHeaderTheme: true }\"\n [mode]=\"'center'\"\n [width]=\"'500px'\"\n [height]=\"'auto'\"\n [title]=\"'i18n_resize' | translate\"\n [zIndex]=\"zIndex\"\n [buttonsFooter]=\"buttonsFooter\"\n (outEvent)=\"handlerEvent($event)\">\n <div class=\"libs-ui-modal-body-custom w-full h-full\">\n <div class=\"flex items-center\">\n <div class=\"libs-ui-font-h7r mr-[4px] text-[#6a7383] w-[100px] shrink-0\">{{ 'i18n_ratio_size' | translate }} (%)</div>\n <libs_ui-components-inputs-valid\n [ignoreWidthInput100]=\"true\"\n [classIncludeInput]=\"'w-[132px]'\"\n [dataType]=\"'int'\"\n [fieldNameBind]=\"'ratio'\"\n [(item)]=\"resizeData\"\n [minValueNumber]=\"1\"\n [maxValueNumber]=\"100\"\n [valueUpDownNumber]=\"1\"\n [validRequired]=\"{ isRequired: true }\"\n [ignoreShowError]=\"true\"\n (outValueChange)=\"handlerResizeRatio()\"\n (outFunctionsControl)=\"handlerInputValidFunctionControl($event)\" />\n </div>\n <div class=\"mt-[16px] relative\">\n <div class=\"flex items-center\">\n <div class=\"libs-ui-font-h7r mr-[4px] text-[#6a7383] w-[100px] shrink-0\">\n {{ 'i18n_width' | translate }}\n </div>\n <libs_ui-components-inputs-valid\n [ignoreWidthInput100]=\"true\"\n [classIncludeInput]=\"'w-[102px]'\"\n [dataType]=\"'int'\"\n [maxValueNumber]=\"originWidth\"\n [minValueNumber]=\"1\"\n [fieldNameBind]=\"'width'\"\n [(item)]=\"resizeData\"\n [unitsRight]=\"[{ id: 'px', label: 'px' }]\"\n [keySelectedUnitRight]=\"'px'\"\n [valueUpDownNumber]=\"1\"\n (outValueChange)=\"handlerResizeWidth()\"\n (outFunctionsControl)=\"handlerInputValidFunctionControl($event)\" />\n </div>\n <div class=\"flex items-center mt-[8px]\">\n <div class=\"libs-ui-font-h7r mr-[4px] text-[#6a7383] w-[100px] shrink-0\">\n {{ 'i18n_length' | translate }}\n </div>\n <libs_ui-components-inputs-valid\n [ignoreWidthInput100]=\"true\"\n [classIncludeInput]=\"'w-[102px]'\"\n [dataType]=\"'int'\"\n [minValueNumber]=\"1\"\n [maxValueNumber]=\"originHeight\"\n [fieldNameBind]=\"'height'\"\n [(item)]=\"resizeData\"\n [unitsRight]=\"[{ id: 'px', label: 'px' }]\"\n [keySelectedUnitRight]=\"'px'\"\n [valueUpDownNumber]=\"1\"\n (outValueChange)=\"handlerResizeHeight()\"\n (outFunctionsControl)=\"handlerInputValidFunctionControl($event)\" />\n </div>\n <div class=\"absolute flex items-center left-[236px] top-[14px]\">\n <div class=\"w-[20px] h-[44px] libs-ui-border-top-general libs-ui-border-right-general libs-ui-border-bottom-general\"></div>\n <div class=\"ml-[6px] text-[#9ca2ad] libs-ui-icon-lock\"></div>\n </div>\n </div>\n <div class=\"mt-[16px]\">\n <div class=\"libs-ui-font-h6m text-[#6a7383] mb-[8px]\">{{ 'i18n_size_photo' | translate }}</div>\n <div class=\"libs-ui-font-h5r rounded-[4px] py-[8px] px-[16px] bg-[#f8f9fa] text-[#9ca2ad]\">\n {{ 'i18n_file_size_compare_when_resize' | translate: { current_size: currentFileSize, original_size: originFileSize } }}\n </div>\n </div>\n </div>\n</libs_ui-components-modal>\n", dependencies: [{ kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }, { kind: "component", type: LibsUiComponentsModalComponent, selector: "libs_ui-components-modal", inputs: ["show", "mode", "isBackdropTransparent", "isBackgroundTransparentModal", "isSizeBackdropByWidthHeightInput", "hasShadowBoxWhenHiddenBackDropTransparent", "classIncludeModalWrapper", "zIndex", "width", "height", "maxWidth", "maxHeight", "minWidth", "isFullScreen", "disable", "ignoreCommunicateMicroEvent", "headerConfig", "bodyConfig", "footerConfig", "buttonsFooter", "title", "titleUseXssFilter", "titleUseTooltip", "titleUseInnerText"], outputs: ["showChange", "widthChange", "heightChange", "maxWidthChange", "maxHeightChange", "minWidthChange", "disableChange", "buttonsFooterChange", "outScrollContent", "outEvent", "outFunctionControl"] }, { kind: "component", type: LibsUiComponentsInputsValidComponent, selector: "libs_ui-components-inputs-valid", inputs: ["item", "labelConfig", "emitEmptyInDataTypeNumber", "ignoreBlockInputMaxValue", "fieldNameBind", "showCount", "typeComponentSelectItem", "valueComponentSelectItem", "disableComponentSelectItem", "tagInput", "dataType", "typeInput", "modeInput", "resetAutoCompletePassword", "textAreaEnterNotNewLine", "fixedFloat", "acceptNegativeValue", "valueUpDownNumber", "ignoreWidthInput100", "classIncludeInput", "classContainerInput", "readonly", "disable", "noBorder", "backgroundNone", "useColorModeExist", "placeholder", "keepPlaceholderOnly", "classContainerBottomInput", "autoRemoveEmoji", "defaultHeight", "maxHeightTextArea", "minHeightTextArea", "ignoreShowError", "borderError", "iconLeftClass", "popoverContentIconLeft", "iconRightClass", "popoverContentIconRight", "zIndexPopoverContent", "unitsLeft", "configUnitLeft", "keySelectedUnitLeft", "unitsRight", "configUnitRight", "keySelectedUnitRight", "maxValueNumber", "minValueNumber", "ignoreContentLeft", "ignoreContentRight", "isBaselineStyle", "valuePatternShowError", "validPattern", "validRequired", "validMinLength", "validMinValue", "validMaxValue", "validMaxLength", "functionValid", "maxLength", "positionMessageErrorStartInput", "classInclude", "resize", "templateLeftBottomInput", "templateRightBottomInput", "onlyAcceptNegativeValue", "autoAddZeroLessThan10InTypeInt", "maxLengthNumberCount", "classMessageErrorInclude", "ignoreStopPropagationEvent", "ignoreUnitRightClassReadOnly", "paddingRightCustomSpecific", "focusTimeOut", "debounceTimeValidate"], outputs: ["itemChange", "outValueChange", "outSelect", "outIconLeft", "outIconRight", "outClickButtonLabel", "outSwitchEventLabel", "outLabelRightClick", "outEnterInputEvent", "outHeightAreaChange", "outFunctionsControl", "outFocusAndBlur", "outChangeValueByButtonUpDown"] }] });
197
+ }
198
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsImageEditorResizeComponent, decorators: [{
199
+ type: Component,
200
+ args: [{ selector: 'libs-ui-components-image_editor-resize', standalone: true, imports: [TranslateModule, LibsUiComponentsModalComponent, LibsUiComponentsInputsValidComponent], template: "<libs_ui-components-modal\n [headerConfig]=\"{ ignoreHeaderTheme: true }\"\n [mode]=\"'center'\"\n [width]=\"'500px'\"\n [height]=\"'auto'\"\n [title]=\"'i18n_resize' | translate\"\n [zIndex]=\"zIndex\"\n [buttonsFooter]=\"buttonsFooter\"\n (outEvent)=\"handlerEvent($event)\">\n <div class=\"libs-ui-modal-body-custom w-full h-full\">\n <div class=\"flex items-center\">\n <div class=\"libs-ui-font-h7r mr-[4px] text-[#6a7383] w-[100px] shrink-0\">{{ 'i18n_ratio_size' | translate }} (%)</div>\n <libs_ui-components-inputs-valid\n [ignoreWidthInput100]=\"true\"\n [classIncludeInput]=\"'w-[132px]'\"\n [dataType]=\"'int'\"\n [fieldNameBind]=\"'ratio'\"\n [(item)]=\"resizeData\"\n [minValueNumber]=\"1\"\n [maxValueNumber]=\"100\"\n [valueUpDownNumber]=\"1\"\n [validRequired]=\"{ isRequired: true }\"\n [ignoreShowError]=\"true\"\n (outValueChange)=\"handlerResizeRatio()\"\n (outFunctionsControl)=\"handlerInputValidFunctionControl($event)\" />\n </div>\n <div class=\"mt-[16px] relative\">\n <div class=\"flex items-center\">\n <div class=\"libs-ui-font-h7r mr-[4px] text-[#6a7383] w-[100px] shrink-0\">\n {{ 'i18n_width' | translate }}\n </div>\n <libs_ui-components-inputs-valid\n [ignoreWidthInput100]=\"true\"\n [classIncludeInput]=\"'w-[102px]'\"\n [dataType]=\"'int'\"\n [maxValueNumber]=\"originWidth\"\n [minValueNumber]=\"1\"\n [fieldNameBind]=\"'width'\"\n [(item)]=\"resizeData\"\n [unitsRight]=\"[{ id: 'px', label: 'px' }]\"\n [keySelectedUnitRight]=\"'px'\"\n [valueUpDownNumber]=\"1\"\n (outValueChange)=\"handlerResizeWidth()\"\n (outFunctionsControl)=\"handlerInputValidFunctionControl($event)\" />\n </div>\n <div class=\"flex items-center mt-[8px]\">\n <div class=\"libs-ui-font-h7r mr-[4px] text-[#6a7383] w-[100px] shrink-0\">\n {{ 'i18n_length' | translate }}\n </div>\n <libs_ui-components-inputs-valid\n [ignoreWidthInput100]=\"true\"\n [classIncludeInput]=\"'w-[102px]'\"\n [dataType]=\"'int'\"\n [minValueNumber]=\"1\"\n [maxValueNumber]=\"originHeight\"\n [fieldNameBind]=\"'height'\"\n [(item)]=\"resizeData\"\n [unitsRight]=\"[{ id: 'px', label: 'px' }]\"\n [keySelectedUnitRight]=\"'px'\"\n [valueUpDownNumber]=\"1\"\n (outValueChange)=\"handlerResizeHeight()\"\n (outFunctionsControl)=\"handlerInputValidFunctionControl($event)\" />\n </div>\n <div class=\"absolute flex items-center left-[236px] top-[14px]\">\n <div class=\"w-[20px] h-[44px] libs-ui-border-top-general libs-ui-border-right-general libs-ui-border-bottom-general\"></div>\n <div class=\"ml-[6px] text-[#9ca2ad] libs-ui-icon-lock\"></div>\n </div>\n </div>\n <div class=\"mt-[16px]\">\n <div class=\"libs-ui-font-h6m text-[#6a7383] mb-[8px]\">{{ 'i18n_size_photo' | translate }}</div>\n <div class=\"libs-ui-font-h5r rounded-[4px] py-[8px] px-[16px] bg-[#f8f9fa] text-[#9ca2ad]\">\n {{ 'i18n_file_size_compare_when_resize' | translate: { current_size: currentFileSize, original_size: originFileSize } }}\n </div>\n </div>\n </div>\n</libs_ui-components-modal>\n" }]
201
+ }], ctorParameters: () => [], propDecorators: { resizeData: [{
202
+ type: Input,
203
+ args: [{ required: true }]
204
+ }], originWidth: [{
205
+ type: Input,
206
+ args: [{ required: true }]
207
+ }], originHeight: [{
208
+ type: Input,
209
+ args: [{ required: true }]
210
+ }], src: [{
211
+ type: Input,
212
+ args: [{ required: true }]
213
+ }], zIndex: [{
214
+ type: Input
215
+ }], mimetype: [{
216
+ type: Input
217
+ }], outClose: [{
218
+ type: Output
219
+ }], outSave: [{
220
+ type: Output
221
+ }] } });
222
+
223
+ /* eslint-disable @typescript-eslint/no-explicit-any */
224
+ class LibsUiComponentsImageEditorComponent {
225
+ linkImageError;
226
+ loading = signal(false);
227
+ loadingImage = signal(true);
228
+ changed = signal(false);
229
+ originWidth = signal(0);
230
+ originHeight = signal(0);
231
+ cropRatioItems = signal(cropRationItems());
232
+ cropRatioItemSelected = signal('');
233
+ cropSize = signal({ width: 20, height: 20 });
234
+ resizeData = signal({ ratio: 100, width: 600, height: 800 });
235
+ dragGridCrop = signal(false);
236
+ image = new Image();
237
+ canvas = document.createElement('canvas');
238
+ containerHeight = signal(800);
239
+ containerWidth = signal(1200);
240
+ imgContainerRect = signal({ top: 0, left: 0, width: 0, height: 0 });
241
+ rectClip = signal({ top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0 });
242
+ resizeState = signal('none');
243
+ moveState = signal('none');
244
+ startMouseDim = signal({ clientX: 0, clientY: 0, width: 0, height: 0, top: 0, left: 0, imageTop: 0, imageLeft: 0, imageWidth: 0, imageHeight: 0 });
245
+ dataUrlOrigin = signal('');
246
+ ratioValue = signal(0);
247
+ minHeight = 20;
248
+ minWidth = 20;
249
+ resizeComponentRef;
250
+ modeShowButton = input('save-file');
251
+ mimetype = input();
252
+ zIndex = input(1200);
253
+ imgSrc = model.required();
254
+ originUrl = input();
255
+ nameFile = input();
256
+ hasZoom = input(false);
257
+ aspectRatio = input();
258
+ requiredCropFollowRatio = input(false);
259
+ imageOrigin = viewChild.required('imageOrigin');
260
+ imageClip = viewChild.required('imageClip');
261
+ imageEditorContainer = viewChild.required('imageEditorContainer');
262
+ imageContainer = viewChild.required('imageContainer');
263
+ cropArea = viewChild.required('cropArea');
264
+ cropTL = viewChild.required('cropTL');
265
+ cropBL = viewChild.required('cropBL');
266
+ cropBR = viewChild.required('cropBR');
267
+ cropTR = viewChild.required('cropTR');
268
+ cropLineVL = viewChild.required('cropLineVL');
269
+ cropLineVR = viewChild.required('cropLineVR');
270
+ cropLineHT = viewChild.required('cropLineHT');
271
+ cropLineHB = viewChild.required('cropLineHB');
272
+ circleTL = viewChild.required('circleTL');
273
+ circleTR = viewChild.required('circleTR');
274
+ circleBL = viewChild.required('circleBL');
275
+ circleBR = viewChild.required('circleBR');
276
+ outClose = output();
277
+ outSaveFile = output();
278
+ outFunctionsControl = output();
279
+ destroyRef = inject(DestroyRef);
280
+ dynamicComponentService = inject(LibsUiDynamicComponentService);
281
+ constructor(linkImageError) {
282
+ this.linkImageError = linkImageError;
283
+ }
284
+ ngAfterViewInit() {
285
+ const aspectRatioFound = this.aspectRatio()?.key ? this.cropRatioItems().find((item) => item.key === this.aspectRatio()?.key) : this.cropRatioItems()[0];
286
+ if (aspectRatioFound) {
287
+ this.dragGridCrop.set(true);
288
+ this.cropRatioItemSelected.set(aspectRatioFound?.key);
289
+ this.updateOriginImageSize();
290
+ this.changedImage(true);
291
+ }
292
+ this.updateModalSize();
293
+ this.initData();
294
+ this.initMouseEvent();
295
+ this.outFunctionsControl.emit(this.FunctionsControl);
296
+ }
297
+ get FunctionsControl() {
298
+ return {
299
+ cropImage: this.cropImage.bind(this),
300
+ setLoadingState: (loading) => this.loading.set(loading),
301
+ };
302
+ }
303
+ handlerImageLoaded() {
304
+ setTimeout(() => {
305
+ this.updateModalSize();
306
+ this.loadingImage.set(false);
307
+ }, 500);
308
+ }
309
+ handlerImageError(e) {
310
+ console.log('e', e);
311
+ e.target.src = this.linkImageError;
312
+ }
313
+ updateModalSize() {
314
+ const maxHeight = window.innerHeight - 100;
315
+ const maxWidth = window.innerWidth - 400;
316
+ this.containerHeight.set(maxHeight - 124);
317
+ this.containerWidth.set(maxWidth - 384);
318
+ this.updateOriginImageSize();
319
+ }
320
+ initData() {
321
+ this.imgContainerRect.set(this.imageContainer().nativeElement.getBoundingClientRect());
322
+ this.image.setAttribute('crossorigin', 'anonymous');
323
+ this.image.onload = () => {
324
+ this.imgContainerRect.set(this.imageContainer().nativeElement.getBoundingClientRect());
325
+ this.canvas.height = this.image.height;
326
+ this.canvas.width = this.image.width;
327
+ this.originHeight.set(this.image.height);
328
+ this.originWidth.set(this.image.width);
329
+ this.updateOriginImageSize();
330
+ this.canvas.getContext('2d')?.drawImage(this.image, 0, 0, this.canvas.width, this.canvas.height);
331
+ this.dataUrlOrigin.set(getDataUrl(this.canvas, this.mimetype(), this.image.src));
332
+ this.imgSrc.set(this.dataUrlOrigin());
333
+ };
334
+ this.image.src = this.imgSrc();
335
+ }
336
+ initMouseEvent() {
337
+ const mouseUp = this.initEvent({ nativeElement: document }, 'mouseup', { callStopPropagation: true, callPreventDefault: true }, undefined, () => this.handlerMouseup());
338
+ const mouseMove = this.initEvent({ nativeElement: document }, 'mousemove', { callStopPropagation: true, callPreventDefault: true }).pipe(takeUntil(mouseUp));
339
+ this.initEvent(this.imageEditorContainer(), 'wheel', { callStopPropagation: true }, undefined, (e) => this.handlerMousewheel(e)).subscribe();
340
+ this.initEvent({ nativeElement: window }, 'resize', {}, undefined, () => this.updateModalSize()).subscribe();
341
+ this.initEvent(this.imageContainer(), 'mousedown', { callStopPropagation: true, callPreventDefault: true }, mouseMove, (e) => this.handlerImageContainerMousedown(e)).subscribe((e) => this.handlerMousemove(e));
342
+ this.initEvent(this.cropTL(), 'mousedown', { callStopPropagation: true, callPreventDefault: true }, mouseMove, (e) => {
343
+ this.handlerCropResize(e, 'TL');
344
+ }).subscribe((e) => this.handlerMousemove(e));
345
+ this.initEvent(this.cropBL(), 'mousedown', { callStopPropagation: true, callPreventDefault: true }, mouseMove, (e) => {
346
+ this.handlerCropResize(e, 'BL');
347
+ }).subscribe((e) => this.handlerMousemove(e));
348
+ this.initEvent(this.cropBR(), 'mousedown', { callStopPropagation: true, callPreventDefault: true }, mouseMove, (e) => {
349
+ this.handlerCropResize(e, 'BR');
350
+ }).subscribe((e) => this.handlerMousemove(e));
351
+ this.initEvent(this.cropTR(), 'mousedown', { callStopPropagation: true, callPreventDefault: true }, mouseMove, (e) => {
352
+ this.handlerCropResize(e, 'TR');
353
+ }).subscribe((e) => this.handlerMousemove(e));
354
+ this.initEvent(this.cropLineVL(), 'mousedown', { callStopPropagation: true, callPreventDefault: true }, mouseMove, (e) => {
355
+ this.handlerCropResize(e, 'VL');
356
+ }).subscribe((e) => this.handlerMousemove(e));
357
+ this.initEvent(this.cropLineVR(), 'mousedown', { callStopPropagation: true, callPreventDefault: true }, mouseMove, (e) => {
358
+ this.handlerCropResize(e, 'VR');
359
+ }).subscribe((e) => this.handlerMousemove(e));
360
+ this.initEvent(this.cropLineHT(), 'mousedown', { callStopPropagation: true, callPreventDefault: true }, mouseMove, (e) => {
361
+ this.handlerCropResize(e, 'HT');
362
+ }).subscribe((e) => this.handlerMousemove(e));
363
+ this.initEvent(this.cropLineHB(), 'mousedown', { callStopPropagation: true, callPreventDefault: true }, mouseMove, (e) => {
364
+ this.handlerCropResize(e, 'HB');
365
+ }).subscribe((e) => this.handlerMousemove(e));
366
+ this.initEvent(this.cropArea(), 'mousedown', {}, undefined, (e) => this.handlerMousedownCropArea(e)).subscribe((e) => this.handlerMousemove(e));
367
+ }
368
+ initEvent(element, eventName, flag, obsMerge, callback) {
369
+ const obs = fromEvent(element.nativeElement, eventName).pipe(tap((e) => {
370
+ if (flag.callStopPropagation && e?.stopPropagation) {
371
+ e.stopPropagation();
372
+ }
373
+ if (flag.callPreventDefault && e?.preventDefault) {
374
+ e.preventDefault();
375
+ }
376
+ if (callback) {
377
+ callback(e);
378
+ }
379
+ }), takeUntilDestroyed(this.destroyRef));
380
+ if (obsMerge) {
381
+ return obs.pipe(mergeMap(() => obsMerge), takeUntilDestroyed(this.destroyRef));
382
+ }
383
+ return obs;
384
+ }
385
+ updateOriginImageSize() {
386
+ let width = this.originWidth();
387
+ let height = this.originHeight();
388
+ const ratio = width / height;
389
+ if (width >= height) {
390
+ width = Math.min(this.containerWidth(), width);
391
+ height = Math.min(width / ratio, this.containerHeight());
392
+ width = height * ratio;
393
+ }
394
+ else {
395
+ height = Math.min(this.containerHeight(), height);
396
+ width = height * ratio;
397
+ }
398
+ this.setImagePosition(width, height);
399
+ this.updateCropAreaPos();
400
+ this.updateRectClipPos();
401
+ }
402
+ setImagePosition(width, height) {
403
+ const left = this.containerWidth() / 2 - width / 2;
404
+ const top = this.containerHeight() / 2 - height / 2;
405
+ const styles = { width, height, top, left, display: 'block' };
406
+ this.setStylesElements(this.imageOrigin(), styles);
407
+ this.setStylesElements(this.imageClip(), styles);
408
+ this.setCirclePosition(styles);
409
+ }
410
+ setCirclePosition(styles) {
411
+ this.setStylesElements(this.circleTL(), { top: (styles.top || 0) - 5, left: (styles.left || 0) - 5 });
412
+ this.setStylesElements(this.circleTR(), { top: (styles.top || 0) - 5, left: (styles.left || 0) + (styles.width || 0) - 10 });
413
+ this.setStylesElements(this.circleBL(), { top: (styles.top || 0) + (styles.height || 0) - 10, left: (styles.left || 0) - 5 });
414
+ this.setStylesElements(this.circleBR(), { top: (styles.top || 0) + (styles.height || 0) - 10, left: (styles.left || 0) + (styles.width || 0) - 10 });
415
+ }
416
+ updateCropAreaPos() {
417
+ const [imgTop, imgLeft, imgWidth, imgHeight] = getStylesOfElement(this.imageClip().nativeElement, ['style.top', 'style.left', 'offsetWidth', 'offsetHeight']);
418
+ const rectImg = {
419
+ top: Math.max(0, imgTop),
420
+ left: Math.max(0, imgLeft),
421
+ width: imgWidth,
422
+ height: imgHeight,
423
+ };
424
+ rectImg.width = Math.min(imgWidth, this.containerWidth() - imgLeft);
425
+ if (imgLeft < 0) {
426
+ rectImg.width = Math.min(imgWidth + imgLeft, this.containerWidth());
427
+ }
428
+ rectImg.height = Math.min(imgHeight, this.containerHeight() - imgTop);
429
+ if (imgTop < 0) {
430
+ rectImg.height = Math.min(imgTop + imgHeight, this.containerHeight());
431
+ }
432
+ this.ratioValue.set(this.getCropRatioValue());
433
+ if (!this.ratioValue()) {
434
+ this.setStylesElements(this.cropArea(), rectImg);
435
+ return;
436
+ }
437
+ let width = rectImg.width;
438
+ let height = width / this.ratioValue();
439
+ width = Math.min(rectImg.width, width);
440
+ height = width / this.ratioValue();
441
+ height = Math.min(rectImg.height, height);
442
+ width = height * this.ratioValue();
443
+ const top = rectImg.top + (rectImg.height - height) / 2;
444
+ const left = rectImg.left + (rectImg.width - width) / 2;
445
+ this.setStylesElements(this.cropArea(), { width, height, top, left });
446
+ }
447
+ getCropRatioValue() {
448
+ const cropRatio = this.cropRatioItems().find((item) => item.key === this.cropRatioItemSelected());
449
+ if (!cropRatio?.value) {
450
+ return this.aspectRatio()?.value || this.originWidth() / this.originHeight();
451
+ }
452
+ return cropRatio.value;
453
+ }
454
+ updateRectClipPos() {
455
+ const reactClip = this.getRectClipImage();
456
+ this.imageClip().nativeElement.style.clip = `rect(${reactClip.top}px, ${reactClip.right}px, ${reactClip.bottom}px, ${reactClip.left}px)`;
457
+ }
458
+ getRectClipImage() {
459
+ const imageRect = this.imageClip().nativeElement.getBoundingClientRect();
460
+ if (!imageRect.width) {
461
+ return this.rectClip();
462
+ }
463
+ const cropRect = this.cropArea().nativeElement.getBoundingClientRect();
464
+ const width = Math.round(cropRect.width);
465
+ const height = Math.round(cropRect.height);
466
+ const left = cropRect.left - imageRect.left;
467
+ const top = cropRect.top - imageRect.top;
468
+ const right = Math.round(left + width);
469
+ const bottom = Math.round(top + height);
470
+ const ratio = this.originWidth() / imageRect.width;
471
+ this.rectClip.set({ left, top, right, bottom, width, height });
472
+ this.cropSize.set({
473
+ height: top >= 0 ? Math.round((bottom - top) * ratio) : Math.floor((bottom - top) * ratio),
474
+ width: top >= 0 ? Math.round((right - left) * (this.ratioValue() ?? 1) * ratio) : Math.floor((right - left) * (this.ratioValue() ?? 1) * ratio),
475
+ });
476
+ return this.rectClip();
477
+ }
478
+ handlerMousemove(e) {
479
+ const typeState = ['BL', 'TL', 'TR', 'BR', 'VL', 'VR', 'HT', 'HB'];
480
+ const methodName = `resizeCropFollow${this.resizeState()}Dir`; // resizeCropFollowBLDir, resizeCropFollowTLDir, resizeCropFollowTRDir, resizeCropFollowBRDir, resizeCropFollowVLDir, resizeCropFollowVRDir, resizeCropFollowHTDir, resizeCropFollowHBDir
481
+ if (typeState.includes(this.resizeState())) {
482
+ this[methodName](e);
483
+ }
484
+ switch (this.moveState()) {
485
+ case 'clip':
486
+ this.moveClipArea(e);
487
+ break;
488
+ case 'image':
489
+ this.moveImage(e);
490
+ break;
491
+ }
492
+ this.changedImage(true);
493
+ }
494
+ resizeCropFollowBLDir = (e) => {
495
+ const momentY = e.clientY - this.startMouseDim().clientY;
496
+ const momentX = this.ratioValue() ? -momentY * this.ratioValue() : e.clientX - this.startMouseDim().clientX;
497
+ const maxHeight = this.startMouseDim().imageTop + this.startMouseDim().imageHeight - this.startMouseDim().top;
498
+ const maxWidth = this.startMouseDim().left + this.startMouseDim().width - this.startMouseDim().imageLeft;
499
+ let { width, height, left } = this.getRectResizeCropFollow({ width: -1, height: 1, left: 1, top: 0 }, momentX, momentY);
500
+ [width, height] = getWidthHeightResizeCropFollow(this.ratioValue(), width, height, maxWidth, maxHeight, this.minWidth, this.minHeight);
501
+ left = this.startMouseDim().left + this.startMouseDim().width - width;
502
+ this.setStylesElements(this.cropArea(), { left, height, width }, true);
503
+ };
504
+ resizeCropFollowTLDir = (e) => {
505
+ const momentY = e.clientY - this.startMouseDim().clientY;
506
+ const momentX = this.ratioValue() ? this.ratioValue() * momentY : e.clientX - this.startMouseDim().clientX;
507
+ const maxHeight = this.startMouseDim().top + this.startMouseDim().height - this.startMouseDim().imageTop;
508
+ const maxWidth = this.startMouseDim().left + this.startMouseDim().width - this.startMouseDim().imageLeft;
509
+ let { width, height, top, left } = this.getRectResizeCropFollow({ width: -1, height: -1, left: 1, top: 1 }, momentX, momentY);
510
+ [width, height] = getWidthHeightResizeCropFollow(this.ratioValue(), width, height, maxWidth, maxHeight, this.minWidth, this.minHeight);
511
+ left = this.startMouseDim().left + this.startMouseDim().width - width;
512
+ top = this.startMouseDim().top + this.startMouseDim().height - height;
513
+ this.setStylesElements(this.cropArea(), { top, left, height, width }, true);
514
+ };
515
+ resizeCropFollowTRDir = (e) => {
516
+ const momentY = e.clientY - this.startMouseDim().clientY;
517
+ const momentX = this.ratioValue() ? -momentY * this.ratioValue() : e.clientX - this.startMouseDim().clientX;
518
+ const maxHeight = this.startMouseDim().top + this.startMouseDim().height - this.startMouseDim().imageTop;
519
+ const maxWidth = this.startMouseDim().imageLeft + this.startMouseDim().imageWidth - this.startMouseDim().left;
520
+ let { width, height, top } = this.getRectResizeCropFollow({ width: 1, height: -1, left: 0, top: 1 }, momentX, momentY);
521
+ [width, height] = getWidthHeightResizeCropFollow(this.ratioValue(), width, height, maxWidth, maxHeight, this.minWidth, this.minHeight);
522
+ top = this.startMouseDim().top + this.startMouseDim().height - height;
523
+ this.setStylesElements(this.cropArea(), { top, height, width }, true);
524
+ };
525
+ resizeCropFollowBRDir(e) {
526
+ const momentY = e.clientY - this.startMouseDim().clientY;
527
+ const momentX = this.ratioValue() ? momentY * this.ratioValue() : e.clientX - this.startMouseDim().clientX;
528
+ const maxHeight = this.startMouseDim().imageTop + this.startMouseDim().imageHeight - this.startMouseDim().top;
529
+ const maxWidth = this.startMouseDim().imageLeft + this.startMouseDim().imageWidth - this.startMouseDim().left;
530
+ let { width, height } = this.getRectResizeCropFollow({ width: 1, height: 1, left: 0, top: 0 }, momentX, momentY);
531
+ [width, height] = getWidthHeightResizeCropFollow(this.ratioValue(), width, height, maxWidth, maxHeight, this.minWidth, this.minHeight);
532
+ this.setStylesElements(this.cropArea(), { height, width }, true);
533
+ }
534
+ resizeCropFollowVLDir = (e) => {
535
+ const momentX = e.clientX - this.startMouseDim().clientX;
536
+ const momentY = this.ratioValue() ? momentX / this.ratioValue() : 0;
537
+ const maxHeight = this.startMouseDim().top + this.startMouseDim().height - this.startMouseDim().imageTop;
538
+ const maxWidth = this.startMouseDim().left + this.startMouseDim().width - this.startMouseDim().imageLeft;
539
+ let { width, height, top, left } = this.getRectResizeCropFollow({ width: -1, height: -1, left: 1, top: 1 }, momentX, momentY);
540
+ [width, height] = getWidthHeightResizeCropFollow(this.ratioValue(), width, height, maxWidth, maxHeight, this.minWidth, this.minHeight);
541
+ left = this.startMouseDim().left + this.startMouseDim().width - width;
542
+ top = this.startMouseDim().top + this.startMouseDim().height - height;
543
+ this.setStylesElements(this.cropArea(), { top, left, height, width }, true);
544
+ };
545
+ resizeCropFollowVRDir = (e) => {
546
+ const momentX = e.clientX - this.startMouseDim().clientX;
547
+ const momentY = this.ratioValue() ? -momentX / this.ratioValue() : 0;
548
+ const maxHeight = this.startMouseDim().top + this.startMouseDim().height - this.startMouseDim().imageTop;
549
+ const maxWidth = this.startMouseDim().imageLeft + this.startMouseDim().imageWidth - this.startMouseDim().left;
550
+ let { width, height, top } = this.getRectResizeCropFollow({ width: 1, height: -1, left: 0, top: 1 }, momentX, momentY);
551
+ [width, height] = getWidthHeightResizeCropFollow(this.ratioValue(), width, height, maxWidth, maxHeight, this.minWidth, this.minHeight);
552
+ top = this.startMouseDim().top + this.startMouseDim().height - height;
553
+ this.setStylesElements(this.cropArea(), { top, height, width }, true);
554
+ };
555
+ resizeCropFollowHTDir = (e) => {
556
+ const momentY = e.clientY - this.startMouseDim().clientY;
557
+ const momentX = this.ratioValue() ? momentY * this.ratioValue() : 0;
558
+ const maxWidth = this.startMouseDim().width + this.startMouseDim().left - this.startMouseDim().imageLeft;
559
+ const maxHeight = this.startMouseDim().top + this.startMouseDim().height - this.startMouseDim().imageTop;
560
+ let { width, height, top, left } = this.getRectResizeCropFollow({ width: -1, height: -1, left: 1, top: 1 }, momentX, momentY);
561
+ [width, height] = getWidthHeightResizeCropFollow(this.ratioValue(), width, height, maxWidth, maxHeight, this.minWidth, this.minHeight);
562
+ top = this.startMouseDim().top + this.startMouseDim().height - height;
563
+ left = this.startMouseDim().left + this.startMouseDim().width - width;
564
+ this.setStylesElements(this.cropArea(), { top, left, height, width }, true);
565
+ };
566
+ resizeCropFollowHBDir = (e) => {
567
+ const momentY = e.clientY - this.startMouseDim().clientY;
568
+ const momentX = this.ratioValue() ? momentY * this.ratioValue() : 0;
569
+ const maxHeight = this.startMouseDim().imageTop + this.startMouseDim().imageHeight - this.startMouseDim().top;
570
+ const maxWidth = this.startMouseDim().width + this.startMouseDim().left - this.startMouseDim().imageLeft;
571
+ let { width, height, left } = this.getRectResizeCropFollow({ width: 1, height: 1, left: -1, top: 0 }, momentX, momentY);
572
+ [width, height] = getWidthHeightResizeCropFollow(this.ratioValue(), width, height, maxWidth, maxHeight, this.minWidth, this.minHeight);
573
+ left = this.startMouseDim().left + this.startMouseDim().width - width;
574
+ this.setStylesElements(this.cropArea(), { height, left, width }, true);
575
+ };
576
+ getRectResizeCropFollow(ratio, momentX, momentY) {
577
+ const width = this.startMouseDim().width + momentX * ratio.width;
578
+ const height = this.startMouseDim().height + momentY * ratio.height;
579
+ const top = this.startMouseDim().top + momentY * ratio.top;
580
+ const left = this.startMouseDim().left + momentX * ratio.left;
581
+ return { width, height, left, top };
582
+ }
583
+ handlerMouseup() {
584
+ this.imageContainer().nativeElement.classList.remove('cursor-grabbing');
585
+ this.cropArea().nativeElement.classList.remove('cursor-grabbing');
586
+ this.resizeState.set('none');
587
+ this.moveState.set('none');
588
+ }
589
+ handlerMousewheel(event) {
590
+ if (!this.hasZoom()) {
591
+ return;
592
+ }
593
+ if (event.deltaY < 0) {
594
+ this.zoomImage(1 / 1.1);
595
+ return;
596
+ }
597
+ this.zoomImage(1.1);
598
+ }
599
+ handlerImageContainerMousedown(e) {
600
+ if (this.resizeState() === 'none' && this.moveState() === 'none') {
601
+ this.moveState.set('image');
602
+ this.handlerMousedown(e);
603
+ }
604
+ return false;
605
+ }
606
+ handlerCropResize(e, resizeType) {
607
+ this.resizeState.set(resizeType);
608
+ this.dragGridCrop.set(true);
609
+ this.handlerMousedown(e);
610
+ }
611
+ handlerMousedownCropArea(e) {
612
+ this.moveState.set('clip');
613
+ this.handlerMousedown(e);
614
+ }
615
+ handlerMousedown(e) {
616
+ const [width, height, top, left] = getStylesOfElement(this.cropArea().nativeElement, ['offsetWidth', 'offsetHeight', 'style.top', 'style.left']);
617
+ const [imageWidth, imageHeight, imageTop, imageLeft] = getStylesOfElement(this.imageOrigin().nativeElement, ['offsetWidth', 'offsetHeight', 'style.top', 'style.left']);
618
+ switch (this.moveState()) {
619
+ case 'image':
620
+ this.imageContainer().nativeElement.classList.add('cursor-grabbing');
621
+ break;
622
+ case 'clip':
623
+ if (width === imageWidth && height === imageHeight) {
624
+ return;
625
+ }
626
+ this.cropArea().nativeElement.classList.add('cursor-grabbing');
627
+ break;
628
+ default:
629
+ break;
630
+ }
631
+ this.startMouseDim.set({
632
+ clientX: e.clientX,
633
+ clientY: e.clientY,
634
+ width: width,
635
+ height: height,
636
+ top: top,
637
+ left: left,
638
+ imageTop: imageTop,
639
+ imageLeft: imageLeft,
640
+ imageWidth: imageWidth,
641
+ imageHeight: imageHeight,
642
+ });
643
+ this.ratioValue.set(this.getCropRatioValue());
644
+ }
645
+ moveImage(e) {
646
+ const momentY = e.clientY - this.startMouseDim().clientY;
647
+ const momentX = e.clientX - this.startMouseDim().clientX;
648
+ const imgTop = this.startMouseDim().imageTop + momentY;
649
+ const imgLeft = this.startMouseDim().imageLeft + momentX;
650
+ const top = this.startMouseDim().top + momentY;
651
+ const left = this.startMouseDim().left + momentX;
652
+ this.setStylesElements(this.imageOrigin(), { top: imgTop, left: imgLeft });
653
+ this.setStylesElements(this.imageClip(), { top: imgTop, left: imgLeft });
654
+ this.setStylesElements(this.cropArea(), { top, left });
655
+ this.setCirclePosition({ top: imgTop, left: imgLeft, width: this.startMouseDim().imageWidth, height: this.startMouseDim().imageHeight });
656
+ }
657
+ moveClipArea(e) {
658
+ const [imgWidth, imgHeight, imgTop, imgLeft] = getStylesOfElement(this.imageClip().nativeElement, ['offsetWidth', 'offsetHeight', 'style.top', 'style.left']);
659
+ const [cropWidth, cropHeight] = getStylesOfElement(this.cropArea().nativeElement, ['offsetWidth', 'offsetHeight']);
660
+ const momentY = e.clientY - this.startMouseDim().clientY;
661
+ const momentX = e.clientX - this.startMouseDim().clientX;
662
+ let top = Math.max(imgTop, this.startMouseDim().top + momentY);
663
+ let left = Math.max(imgLeft, this.startMouseDim().left + momentX);
664
+ top = Math.min(top, imgTop + imgHeight - cropHeight);
665
+ left = Math.min(left, imgLeft + imgWidth - cropWidth);
666
+ this.setStylesElements(this.cropArea(), { top, left }, true);
667
+ }
668
+ zoomImage(scale) {
669
+ const [currWidth, currHeight, currTop, currLeft] = getStylesOfElement(this.imageOrigin().nativeElement, ['offsetWidth', 'offsetHeight', 'style.top', 'style.left']);
670
+ const width = Math.max(currWidth * scale, 50);
671
+ const height = (this.imageOrigin().nativeElement.offsetHeight * width) / this.imageOrigin().nativeElement.offsetWidth;
672
+ // Calculate the center point of the current view
673
+ const centerX = currLeft + currWidth / 2;
674
+ const centerY = currTop + currHeight / 2;
675
+ // Calculate new position to maintain center point
676
+ const top = centerY - height / 2;
677
+ const left = centerX - width / 2;
678
+ // Get current crop area dimensions and position
679
+ let [cropWidth, cropHeight] = getStylesOfElement(this.cropArea().nativeElement, ['offsetWidth', 'offsetHeight']);
680
+ const [cropTop, cropLeft] = getStylesOfElement(this.cropArea().nativeElement, ['style.top', 'style.left']);
681
+ // Scale crop area dimensions proportionally
682
+ cropWidth = cropWidth * scale;
683
+ cropHeight = cropHeight * scale;
684
+ // Calculate new crop area position relative to the image
685
+ const cropTopNew = top + (cropTop - currTop) * scale;
686
+ const cropLeftNew = left + (cropLeft - currLeft) * scale;
687
+ // Ensure crop area stays within bounds
688
+ const maxCropWidth = width;
689
+ const maxCropHeight = height;
690
+ const minWidth = 20;
691
+ const minHeight = 20;
692
+ cropWidth = Math.min(cropWidth, maxCropWidth);
693
+ cropHeight = Math.min(cropHeight, maxCropHeight);
694
+ cropWidth = Math.max(cropWidth, minWidth);
695
+ cropHeight = Math.max(cropHeight, minHeight);
696
+ this.ratioValue.set(this.getCropRatioValue());
697
+ if (this.ratioValue()) {
698
+ cropHeight = cropWidth / this.ratioValue();
699
+ cropHeight = Math.min(cropHeight, maxCropHeight);
700
+ cropHeight = Math.max(cropHeight, minHeight);
701
+ cropWidth = cropHeight * this.ratioValue();
702
+ }
703
+ // Update clipping rectangle
704
+ this.rectClip.update((rect) => {
705
+ const rectTop = cropTopNew - top;
706
+ const rectLeft = cropLeftNew - left;
707
+ return {
708
+ ...rect,
709
+ top: rectTop,
710
+ left: rectLeft,
711
+ right: rectLeft + cropWidth,
712
+ bottom: rectTop + cropHeight,
713
+ };
714
+ });
715
+ const clip = `rect(${this.rectClip().top}px, ${this.rectClip().right}px, ${this.rectClip().bottom}px, ${this.rectClip().left}px)`;
716
+ // Update all elements with new positions and dimensions
717
+ this.setStylesElements(this.cropArea(), { top: cropTopNew, left: cropLeftNew, width: cropWidth, height: cropHeight });
718
+ this.setStylesElements(this.imageOrigin(), { top, left, width, height });
719
+ this.setCirclePosition({ top, left, width, height });
720
+ this.setStylesElements(this.imageClip(), { clip, top, left, width, height });
721
+ this.getRectClipImage();
722
+ }
723
+ handlerRotateImage(event) {
724
+ event.stopPropagation();
725
+ this.image.src = this.imgSrc();
726
+ this.image.onload = () => {
727
+ const canvas = document.createElement('canvas');
728
+ const ctx = canvas.getContext('2d');
729
+ const [width, height] = getStylesOfElement(this.imageOrigin().nativeElement, ['offsetWidth', 'offsetHeight']);
730
+ const originWidth = this.image.width;
731
+ const originHeight = this.image.height;
732
+ let top = parseInt(this.imageOrigin().nativeElement.style.top);
733
+ let left = parseInt(this.imageOrigin().nativeElement.style.left);
734
+ let newHeight = width;
735
+ let newWidth = height;
736
+ const ratio = newWidth / newHeight;
737
+ canvas.height = originWidth;
738
+ canvas.width = originHeight;
739
+ ctx?.translate(originHeight / 2, originWidth / 2);
740
+ ctx?.rotate(Math.PI / 2);
741
+ ctx?.drawImage(this.image, -originWidth / 2, -originHeight / 2);
742
+ const src = getDataUrl(canvas, this.mimetype(), this.image.src);
743
+ this.imgSrc.set(src);
744
+ newWidth = Math.max(newWidth, originHeight);
745
+ newWidth = Math.min(newWidth, this.containerWidth());
746
+ newHeight = newWidth / ratio;
747
+ newHeight = Math.max(newHeight, originWidth);
748
+ newHeight = Math.min(newHeight, this.containerHeight());
749
+ newWidth = newHeight * ratio;
750
+ top = top - (newHeight / 2 - height / 2);
751
+ left = left - (newWidth / 2 - width / 2);
752
+ this.setStylesElements(this.imageOrigin(), { src, top, left, width: newWidth, height: newHeight });
753
+ this.setCirclePosition({ top, left, width: newWidth, height: newHeight });
754
+ this.setStylesElements(this.imageClip(), { src, top, left, width: newWidth, height: newHeight });
755
+ this.getOriginalImageSize(() => {
756
+ this.cropSize.set({ width: this.originWidth(), height: this.originHeight() });
757
+ this.updateOriginImageSize();
758
+ });
759
+ this.changedImage(true);
760
+ };
761
+ }
762
+ getOriginalImageSize(callback) {
763
+ this.image.src = this.imgSrc();
764
+ this.image.onload = () => {
765
+ this.originWidth.set(this.image.width);
766
+ this.originHeight.set(this.image.height);
767
+ if (callback) {
768
+ return callback();
769
+ }
770
+ };
771
+ }
772
+ changedImage(status) {
773
+ this.changed.set(status);
774
+ }
775
+ handlerFlipImage(event, mode) {
776
+ event.stopPropagation();
777
+ this.image.src = this.imgSrc();
778
+ this.image.onload = () => {
779
+ const canvas = document.createElement('canvas');
780
+ const ctx = canvas.getContext('2d');
781
+ const width = this.image.width;
782
+ const height = this.image.height;
783
+ canvas.height = height;
784
+ canvas.width = width;
785
+ if (ctx) {
786
+ ctx.drawImage(this.image, 0, 0, width, height);
787
+ if (mode === 'horizontal') {
788
+ ctx.translate(width, 0);
789
+ ctx.scale(-1, 1);
790
+ }
791
+ if (mode === 'vertical') {
792
+ ctx.translate(0, height);
793
+ ctx.scale(1, -1);
794
+ }
795
+ ctx.clearRect(0, 0, width, height);
796
+ ctx.drawImage(this.image, 0, 0);
797
+ }
798
+ const dataUrl = getDataUrl(canvas, this.mimetype(), this.image.src);
799
+ this.imgSrc.set(dataUrl);
800
+ this.imageOrigin().nativeElement.src = dataUrl;
801
+ this.imageClip().nativeElement.src = dataUrl;
802
+ this.updateCropAreaPos();
803
+ this.updateRectClipPos();
804
+ this.changedImage(true);
805
+ };
806
+ }
807
+ handlerRestoreImage(event) {
808
+ event.stopPropagation();
809
+ this.imgSrc.set(this.dataUrlOrigin());
810
+ this.dragGridCrop.set(false);
811
+ this.getOriginalImageSize(() => {
812
+ this.updateOriginImageSize();
813
+ this.changedImage(false);
814
+ this.cropRatioItemSelected.set('');
815
+ this.updateCropAreaPos();
816
+ this.updateRectClipPos();
817
+ });
818
+ }
819
+ cropImage() {
820
+ return new Promise((resolve) => {
821
+ this.image.src = this.imgSrc();
822
+ this.image.onload = () => {
823
+ const canvas = document.createElement('canvas');
824
+ const originWidth = this.image.width;
825
+ const width = parseInt(this.imageClip().nativeElement.offsetWidth);
826
+ const height = parseInt(this.imageClip().nativeElement.offsetHeight);
827
+ const scale = originWidth / width;
828
+ const rectClip = this.getRectClipImage();
829
+ const cropRect = getCropRectImage(rectClip, width, height, scale);
830
+ canvas.height = cropRect.height;
831
+ canvas.width = cropRect.width;
832
+ this.originHeight.set(canvas.height);
833
+ this.originWidth.set(canvas.width);
834
+ const ctx = canvas.getContext('2d');
835
+ if (ctx) {
836
+ ctx.drawImage(this.image, cropRect.left, cropRect.top, cropRect.width, cropRect.height, 0, 0, cropRect.width, cropRect.height);
837
+ }
838
+ const dataUrl = getDataUrl(canvas, this.mimetype(), this.image.src);
839
+ this.changedImage(true);
840
+ resolve(dataUrl);
841
+ };
842
+ });
843
+ }
844
+ async handlerSaveFile(event, modeSave) {
845
+ event.stopPropagation();
846
+ const dataUrl = await this.cropImage();
847
+ const file = convertBase64ToBlob(dataUrl);
848
+ this.outSaveFile.emit({ file: file, url: dataUrl, mode: modeSave });
849
+ }
850
+ async handlerClose(event) {
851
+ event.stopPropagation();
852
+ this.outClose.emit({ isClickButtonClose: true });
853
+ }
854
+ handlerSelectCropRatioItem(event, key) {
855
+ event.stopPropagation();
856
+ this.cropRatioItemSelected.set(key);
857
+ this.changed.set(true);
858
+ this.dragGridCrop.set(true);
859
+ this.updateOriginImageSize();
860
+ }
861
+ handlerResize(event) {
862
+ event.stopPropagation();
863
+ if (this.resizeComponentRef) {
864
+ return;
865
+ }
866
+ this.resizeComponentRef = this.dynamicComponentService.resolveComponentFactory(LibsUiComponentsImageEditorResizeComponent);
867
+ const instance = this.resizeComponentRef.instance;
868
+ const subs = instance.outClose.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
869
+ this.dynamicComponentService.remove(this.resizeComponentRef);
870
+ this.resizeComponentRef = undefined;
871
+ subs.unsubscribe();
872
+ });
873
+ instance.src = this.imgSrc();
874
+ instance.mimetype = this.mimetype();
875
+ instance.zIndex = this.zIndex();
876
+ subs.add(instance.outSave.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
877
+ this.resizeData.set(event);
878
+ this.saveResize();
879
+ }));
880
+ instance.resizeData = { ratio: 100, width: this.originWidth(), height: this.originHeight() };
881
+ instance.originWidth = this.originWidth();
882
+ instance.originHeight = this.originHeight();
883
+ this.dynamicComponentService.addToBody(this.resizeComponentRef);
884
+ }
885
+ saveResize() {
886
+ this.image.src = this.imgSrc();
887
+ this.image.onload = () => {
888
+ const canvas = document.createElement('canvas');
889
+ canvas.width = this.resizeData().width;
890
+ canvas.height = this.resizeData().height;
891
+ const ctx = canvas.getContext('2d');
892
+ if (ctx) {
893
+ ctx.drawImage(this.image, 0, 0, canvas.width, canvas.height);
894
+ }
895
+ const src = getDataUrl(canvas, this.mimetype(), this.image.src);
896
+ const [currWidth, currHeight, currTop, currLeft] = getStylesOfElement(this.imageOrigin().nativeElement, ['offsetWidth', 'offsetHeight', 'style.top', 'style.left']);
897
+ const ratio = currWidth / currHeight;
898
+ let newWidth = Math.min(this.resizeData().width, this.containerWidth());
899
+ let newHeight = newWidth / ratio;
900
+ newHeight = Math.min(this.resizeData().height, this.containerHeight());
901
+ newWidth = newHeight * ratio;
902
+ Math.min(this.resizeData().height, this.containerHeight());
903
+ const top = currTop + (currHeight - newHeight) / 2;
904
+ const left = currLeft + (currWidth - newWidth) / 2;
905
+ const movementY = currTop - top;
906
+ const movementX = currLeft - left;
907
+ this.rectClip.update((rect) => {
908
+ const rectTop = rect.top + movementY;
909
+ const rectLeft = rect.left + movementX;
910
+ return {
911
+ ...rect,
912
+ top: rectTop,
913
+ left: rectLeft,
914
+ right: rectLeft + newWidth,
915
+ bottom: rectTop + newHeight,
916
+ };
917
+ });
918
+ const clip = `rect(${this.rectClip().top}px, ${this.rectClip().right}px, ${this.rectClip().bottom}px, ${this.rectClip().left}px)`;
919
+ this.setStylesElements(this.imageOrigin(), { src, top, left, width: newWidth, height: newHeight });
920
+ this.setCirclePosition({ top, left, width: newWidth, height: newHeight });
921
+ this.setStylesElements(this.imageClip(), { src, clip, top, left, width: newWidth, height: newHeight });
922
+ this.imgSrc.set(src);
923
+ this.originWidth.set(this.resizeData().width);
924
+ this.originHeight.set(this.resizeData().height);
925
+ this.updateCropAreaPos();
926
+ this.updateRectClipPos();
927
+ this.changedImage(true);
928
+ };
929
+ }
930
+ handlerCropWidth() {
931
+ this.processCropWidthOrHeight('width');
932
+ }
933
+ handlerCropHeight() {
934
+ this.processCropWidthOrHeight('height');
935
+ }
936
+ processCropWidthOrHeight(cropBy) {
937
+ this.ratioValue.set(this.getCropRatioValue());
938
+ const [currImgWidth, currImgHeight, currImgTop, currImgLeft] = getStylesOfElement(this.imageOrigin().nativeElement, ['offsetWidth', 'offsetHeight', 'style.top', 'style.left']);
939
+ let [cropWidth, cropHeight] = getStylesOfElement(this.cropArea().nativeElement, ['offsetWidth', 'offsetHeight']);
940
+ const [cropTop, cropLeft] = getStylesOfElement(this.cropArea().nativeElement, ['style.top', 'style.left']);
941
+ const maxCropWidth = currImgLeft + currImgWidth - cropLeft;
942
+ const maxCropHeight = currImgHeight + currImgTop - cropTop;
943
+ const ratio = this.originWidth() / currImgWidth;
944
+ let cropValue = get(this.cropSize, cropBy) / ratio;
945
+ cropValue = Math.min(cropValue, cropBy === 'width' ? maxCropWidth : maxCropHeight);
946
+ cropValue = Math.max(1, cropValue);
947
+ if (this.ratioValue()) {
948
+ if (cropBy === 'width') {
949
+ cropHeight = cropValue / this.ratioValue();
950
+ cropHeight = Math.min(cropHeight, maxCropHeight);
951
+ cropValue = cropHeight * this.ratioValue();
952
+ this.cropSize.update((cropSize) => ({ ...cropSize, height: Math.round(cropHeight * ratio) }));
953
+ this.cropArea().nativeElement.style.height = `${cropHeight}px`;
954
+ }
955
+ if (cropBy === 'height') {
956
+ cropWidth = cropValue * this.ratioValue();
957
+ cropWidth = Math.min(cropWidth, maxCropWidth);
958
+ cropValue = cropWidth / this.ratioValue();
959
+ this.cropSize.update((cropSize) => ({ ...cropSize, width: Math.round(cropWidth * ratio) }));
960
+ this.cropArea().nativeElement.style.width = `${cropWidth}px`;
961
+ }
962
+ }
963
+ this.cropSize.update((cropSize) => ({ ...cropSize, [cropBy]: Math.round(cropValue * ratio) }));
964
+ this.cropArea().nativeElement.style[cropBy] = `${cropValue}px`;
965
+ const imageRect = this.imageClip().nativeElement.getBoundingClientRect();
966
+ const cropRect = this.cropArea().nativeElement.getBoundingClientRect();
967
+ const { width, height } = cropRect;
968
+ const left = cropRect.left - imageRect.left;
969
+ const top = cropRect.top - imageRect.top;
970
+ const right = left + width;
971
+ const bottom = top + height;
972
+ this.imageClip().nativeElement.style.clip = `rect(${top}px, ${right}px, ${bottom}px, ${left}px)`;
973
+ }
974
+ setStylesElements(element, styles, isUpdateRectClipPos) {
975
+ Object.keys(styles).forEach((key) => {
976
+ const value = get(styles, key);
977
+ if (isNil(value)) {
978
+ return;
979
+ }
980
+ set(element.nativeElement.style, key, typeof value === 'number' ? `${value}px` : value);
981
+ });
982
+ if (isUpdateRectClipPos) {
983
+ this.updateRectClipPos();
984
+ }
985
+ }
986
+ ngOnDestroy() {
987
+ this.dynamicComponentService.remove(this.resizeComponentRef);
988
+ }
989
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsImageEditorComponent, deps: [{ token: LINK_IMAGE_ERROR_TOKEN_INJECT, optional: true }], target: i0.ɵɵFactoryTarget.Component });
990
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: LibsUiComponentsImageEditorComponent, isStandalone: true, selector: "libs_ui-components-image_editor", inputs: { modeShowButton: { classPropertyName: "modeShowButton", publicName: "modeShowButton", isSignal: true, isRequired: false, transformFunction: null }, mimetype: { classPropertyName: "mimetype", publicName: "mimetype", isSignal: true, isRequired: false, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, imgSrc: { classPropertyName: "imgSrc", publicName: "imgSrc", isSignal: true, isRequired: true, transformFunction: null }, originUrl: { classPropertyName: "originUrl", publicName: "originUrl", isSignal: true, isRequired: false, transformFunction: null }, nameFile: { classPropertyName: "nameFile", publicName: "nameFile", isSignal: true, isRequired: false, transformFunction: null }, hasZoom: { classPropertyName: "hasZoom", publicName: "hasZoom", isSignal: true, isRequired: false, transformFunction: null }, aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, requiredCropFollowRatio: { classPropertyName: "requiredCropFollowRatio", publicName: "requiredCropFollowRatio", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { imgSrc: "imgSrcChange", outClose: "outClose", outSaveFile: "outSaveFile", outFunctionsControl: "outFunctionsControl" }, viewQueries: [{ propertyName: "imageOrigin", first: true, predicate: ["imageOrigin"], descendants: true, isSignal: true }, { propertyName: "imageClip", first: true, predicate: ["imageClip"], descendants: true, isSignal: true }, { propertyName: "imageEditorContainer", first: true, predicate: ["imageEditorContainer"], descendants: true, isSignal: true }, { propertyName: "imageContainer", first: true, predicate: ["imageContainer"], descendants: true, isSignal: true }, { propertyName: "cropArea", first: true, predicate: ["cropArea"], descendants: true, isSignal: true }, { propertyName: "cropTL", first: true, predicate: ["cropTL"], descendants: true, isSignal: true }, { propertyName: "cropBL", first: true, predicate: ["cropBL"], descendants: true, isSignal: true }, { propertyName: "cropBR", first: true, predicate: ["cropBR"], descendants: true, isSignal: true }, { propertyName: "cropTR", first: true, predicate: ["cropTR"], descendants: true, isSignal: true }, { propertyName: "cropLineVL", first: true, predicate: ["cropLineVL"], descendants: true, isSignal: true }, { propertyName: "cropLineVR", first: true, predicate: ["cropLineVR"], descendants: true, isSignal: true }, { propertyName: "cropLineHT", first: true, predicate: ["cropLineHT"], descendants: true, isSignal: true }, { propertyName: "cropLineHB", first: true, predicate: ["cropLineHB"], descendants: true, isSignal: true }, { propertyName: "circleTL", first: true, predicate: ["circleTL"], descendants: true, isSignal: true }, { propertyName: "circleTR", first: true, predicate: ["circleTR"], descendants: true, isSignal: true }, { propertyName: "circleBL", first: true, predicate: ["circleBL"], descendants: true, isSignal: true }, { propertyName: "circleBR", first: true, predicate: ["circleBR"], descendants: true, isSignal: true }], ngImport: i0, template: "<libs_ui-components-modal\n [width]=\"'calc(100vw - 400px)'\"\n [height]=\"'calc(100vh - 100px)'\"\n [minWidth]=\"'1200px'\"\n [headerConfig]=\"{ ignoreHeaderTheme: true, removeButtonClose: true }\"\n [bodyConfig]=\"{ classInclude: '!p-0', scrollOverlayOptions: { scrollX: 'hidden', scrollY: 'hidden' } }\"\n [footerConfig]=\"{ hidden: true }\"\n [zIndex]=\"zIndex()\"\n [buttonsFooter]=\"[]\"\n [mode]=\"'center'\">\n <div class=\"libs-ui-modal-header-custom w-full flex items-center justify-between\">\n <div class=\"w-full relative mx-[24px] h-[16px]\">\n <div class=\"flex w-full libs-ui-font-h4s absolute\">\n <libs_ui-components-popover\n [type]=\"'text'\"\n [config]=\"{ zIndex: zIndex() }\">\n {{ 'i18n_edit_file_name' | translate: { file_name: nameFile() } }}\n </libs_ui-components-popover>\n </div>\n </div>\n <div class=\"flex items-center\">\n @if (modeShowButton() === 'save-file') {\n <libs_ui-components-buttons-button\n [type]=\"'button-third'\"\n [label]=\"'i18n_cancel'\"\n [disable]=\"loading() || loadingImage()\"\n [classInclude]=\"'py-[6px] px-[12px] mr-[16px]'\"\n (outClick)=\"handlerClose($event)\" />\n\n <libs_ui-components-buttons-button\n class=\"mr-[24px]\"\n [label]=\"'i18n_save'\"\n [disable]=\"loading() || loadingImage()\"\n [classInclude]=\"'py-[6px] px-[12px]'\"\n (outClick)=\"handlerSaveFile($event, 'save-file')\" />\n } @else {\n <libs_ui-components-buttons-button\n [label]=\"'i18n_save_to_new_file'\"\n [type]=\"'button-outline'\"\n [classInclude]=\"'mx-[16px] py-[6px] px-[12px]'\"\n [disable]=\"loading() || loadingImage() || !changed()\"\n (outClick)=\"handlerSaveFile($event, 'save-api-as-new-file')\" />\n <libs_ui-components-buttons-button\n [type]=\"'button-secondary'\"\n [label]=\"'i18n_save'\"\n [classInclude]=\"'py-[6px] px-[12px]'\"\n [disable]=\"loading() || loadingImage() || !changed()\"\n (outClick)=\"handlerSaveFile($event, 'save-api')\" />\n <div class=\"h-[12px] libs-ui-border-left-general mx-[16px]\"></div>\n <libs_ui-components-buttons-button\n class=\"mr-[24px]\"\n [classInclude]=\"'p-[6px]'\"\n [type]=\"'button-third-hover-danger'\"\n [iconOnlyType]=\"true\"\n [disable]=\"loading()\"\n [popover]=\"{ config: { content: 'i18n_exit_edit_mode', zIndex: zIndex() } }\"\n [classIconLeft]=\"'libs-ui-icon-close'\"\n (outClick)=\"handlerClose($event)\" />\n }\n </div>\n </div>\n <div class=\"libs-ui-modal-body-custom h-full w-full flex\">\n <div\n #imageEditorContainer\n class=\"relative w-full h-full bg-[#e6e7ea] border-radius-bottom-left-8px p-[32px] libs-ui-input-image-editor-container-left\">\n @if (loadingImage()) {\n <div class=\"absolute h-full w-full bg-[#e6e7ea] top-0 left-0 z-[5]\">\n <libs_ui-components-spinner [size]=\"'medium'\" />\n </div>\n }\n <div class=\"absolute h-[32px] w-[calc(100%-64px)] bg-[#e6e7eab8] top-0 left-[32px] z-[1]\"></div>\n <div class=\"absolute w-[32px] h-full bg-[#e6e7eab8] left-0 top-0 z-[1] rounded-bl-[8px]\"></div>\n <div class=\"absolute w-[32px] h-full bg-[#e6e7eab8] right-0 top-0 z-[1]\"></div>\n <div class=\"absolute h-[32px] w-[calc(100%-64px)] bg-[#e6e7eab8] bottom-0 left-[32px] z-[1]\"></div>\n <div\n #imageContainer\n class=\"relative w-full h-full rounded-bl-[8px] min-h-0\">\n <div\n #circleTL\n class=\"absolute z-[1]\"\n [class.hidden]=\"!dragGridCrop()\"\n [class.flex]=\"dragGridCrop()\">\n <ng-template *ngTemplateOutlet=\"svgCircle\" />\n </div>\n <div\n #circleTR\n class=\"absolute z-[1]\"\n [class.hidden]=\"!dragGridCrop()\"\n [class.flex]=\"dragGridCrop()\">\n <ng-template *ngTemplateOutlet=\"svgCircle\" />\n </div>\n <div\n #circleBL\n class=\"absolute z-[1]\"\n [class.hidden]=\"!dragGridCrop()\"\n [class.flex]=\"dragGridCrop()\">\n <ng-template *ngTemplateOutlet=\"svgCircle\" />\n </div>\n <div\n #circleBR\n class=\"absolute z-[1]\"\n [class.hidden]=\"!dragGridCrop()\"\n [class.flex]=\"dragGridCrop()\">\n <ng-template *ngTemplateOutlet=\"svgCircle\" />\n </div>\n <img\n #imageOrigin\n class=\"absolute border-[4px] border-[#8e61ee] rounded-[4px] top-[-1000px] left-[-1000px]\"\n loading=\"lazy\"\n [src]=\"imgSrc()\"\n (load)=\"handlerImageLoaded()\"\n (error)=\"handlerImageError($event)\" />\n <img\n #imageClip\n class=\"absolute z-[1] top-[-1000px] left-[-1000px]\"\n loading=\"lazy\"\n [src]=\"imgSrc()\"\n (error)=\"handlerImageError($event)\" />\n <div class=\"absolute top-0 left-0 right-0 bottom-0 bg-[#e6e7eab8]\"></div>\n <div\n #cropArea\n class=\"absolute z-[2] border-[4px] border-[#8e61ee] rounded-[4px]\">\n <div class=\"relative w-full h-full\">\n <div\n #cropTL\n class=\"absolute z-[2] left-[-18px] top-[-17px] cursor-nwse-resize\">\n <ng-template *ngTemplateOutlet=\"svgArrow\" />\n </div>\n <div\n #cropBL\n class=\"absolute z-[2] left-[-17px] bottom-[-18px] cursor-nesw-resize rotate-[270deg]\">\n <ng-template *ngTemplateOutlet=\"svgArrow\" />\n </div>\n <div\n #cropTR\n class=\"absolute z-[2] right-[-17px] top-[-17px] cursor-nesw-resize rotate-[90deg]\">\n <ng-template *ngTemplateOutlet=\"svgArrow\" />\n </div>\n <div\n #cropBR\n class=\"absolute z-[2] right-[-18px] bottom-[-17px] cursor-nwse-resize rotate-[180deg]\">\n <ng-template *ngTemplateOutlet=\"svgArrow\" />\n </div>\n <div\n #cropLineVL\n class=\"absolute z-[1] w-[12px] left-[-6px] top-0 bottom-0 cursor-ew-resize\"></div>\n <div\n #cropLineVR\n class=\"absolute z-[1] w-[12px] right-[-6px] top-0 bottom-0 cursor-ew-resize\"></div>\n <div\n #cropLineHT\n class=\"absolute z-[1] h-[12px] left-0 top-[-6px] right-0 cursor-ns-resize\"></div>\n <div\n #cropLineHB\n class=\"absolute z-[1] h-[12px] left-0 right-0 bottom-[-6px] cursor-ns-resize\"></div>\n\n <div class=\"bg-white absolute h-[1px] top-0 left-0 right-0\"></div>\n <div class=\"bg-white absolute h-[1px] bottom-0 left-0 right-0\"></div>\n <div class=\"bg-white absolute w-[1px] top-0 left-0 bottom-0\"></div>\n <div class=\"bg-white absolute w-[1px] top-0 bottom-0 right-0\"></div>\n <div class=\"bg-white absolute h-[2px] top-[33%] left-0 right-0\"></div>\n <div class=\"bg-white absolute h-[2px] top-[66%] left-0 right-0\"></div>\n <div class=\"bg-white absolute w-[2px] top-0 bottom-0 left-[33%]\"></div>\n <div class=\"bg-white absolute w-[2px] top-0 bottom-0 left-[66%]\"></div>\n </div>\n </div>\n </div>\n @if (loading()) {\n <libs_ui-components-spinner [size]=\"'medium'\" />\n }\n </div>\n <div class=\"w-[320px] shrink-0 p-[16px] bg-[#ffffff] rounded-br-[8px] z-[2] libs-ui-input-image-editor-container-right\">\n <div class=\"flex items-center justify-between mb-[16px]\">\n <div class=\"libs-ui-font-h4m\">{{ 'i18n_cutting_ratio' | translate }}</div>\n <libs_ui-components-buttons-button\n [iconOnlyType]=\"true\"\n [type]=\"'button-third'\"\n [classIconLeft]=\"'libs-ui-icon-refresh'\"\n [classInclude]=\"'p-[6px] mr-[8px]'\"\n [popover]=\"{ config: { content: 'i18n_restore_the_original_state', zIndex: zIndex() } }\"\n [disable]=\"loading() || loadingImage() || !changed()\"\n (outClick)=\"handlerRestoreImage($event)\" />\n </div>\n <div class=\"mb-[16px] libs-ui-image-editor-edit-display-grid-gap-16\">\n @for (item of cropRatioItems(); track item.key) {\n <div class=\"flex flex-col items-center\">\n <libs_ui-components-buttons-button\n [iconOnlyType]=\"true\"\n [sizeButton]=\"'large'\"\n [disable]=\"loading() || loadingImage() || requiredCropFollowRatio()\"\n [type]=\"'button-outline'\"\n [classIconLeft]=\"item.icon + ' !text-[20px]'\"\n [isActive]=\"item.key === cropRatioItemSelected()\"\n (outClick)=\"handlerSelectCropRatioItem($event, item.key)\" />\n <div class=\"libs-ui-font-h6r text-[#6a7383] mt-[4px]\">\n {{ (item.key === 'free' ? 'i18n_custom' : item.key) | translate }}\n </div>\n </div>\n }\n </div>\n <div class=\"mb-[16px] flex\">\n <libs_ui-components-inputs-valid\n class=\"mr-[16px]\"\n [dataType]=\"'int'\"\n [labelConfig]=\"{ labelLeft: 'i18n_width', classInclude: 'mb-[4px]', labelLeftClass: 'libs-ui-font-h7r' }\"\n [fieldNameBind]=\"'width'\"\n [(item)]=\"cropSize\"\n [unitsRight]=\"[{ id: 'px', label: 'px' }]\"\n [keySelectedUnitRight]=\"'px'\"\n [valueUpDownNumber]=\"1\"\n [maxValueNumber]=\"originWidth()\"\n [disable]=\"loading() || loadingImage()\"\n (outValueChange)=\"handlerCropWidth()\" />\n <libs_ui-components-inputs-valid\n [dataType]=\"'int'\"\n [labelConfig]=\"{ labelLeft: 'i18n_length', classInclude: 'mb-[4px]', labelLeftClass: 'libs-ui-font-h7r' }\"\n [fieldNameBind]=\"'height'\"\n [(item)]=\"cropSize\"\n [unitsRight]=\"[{ id: 'px', label: 'px' }]\"\n [keySelectedUnitRight]=\"'px'\"\n [valueUpDownNumber]=\"1\"\n [maxValueNumber]=\"originHeight()\"\n [disable]=\"loading() || loadingImage()\"\n (outValueChange)=\"handlerCropHeight()\" />\n </div>\n <div class=\"libs-ui-font-h4m mb-[16px]\">{{ 'i18n_rotate_photos' | translate }}</div>\n <div class=\"flex libs-ui-image-editor-edit-display-grid-gap-16\">\n <div class=\"flex flex-col items-center\">\n <libs_ui-components-buttons-button\n [iconOnlyType]=\"true\"\n [sizeButton]=\"'large'\"\n [disable]=\"loading() || loadingImage()\"\n [type]=\"'button-outline'\"\n [classIconLeft]=\"'libs-ui-icon-rotate-image-outline !text-[20px]'\"\n (outClick)=\"handlerRotateImage($event)\" />\n <div class=\"libs-ui-font-h6r text-[#6a7383] mt-[4px]\">\n {{ 'i18n_rotate_ninety_degrees' | translate }}\n </div>\n </div>\n\n <div class=\"flex flex-col items-center\">\n <libs_ui-components-buttons-button\n [iconOnlyType]=\"true\"\n [sizeButton]=\"'large'\"\n [disable]=\"loading() || loadingImage()\"\n [type]=\"'button-outline'\"\n [classIconLeft]=\"'libs-ui-icon-flip-vertical !text-[20px]'\"\n (outClick)=\"handlerFlipImage($event, 'vertical')\" />\n <div class=\"libs-ui-font-h6r text-[#6a7383] mt-[4px]\">\n {{ 'i18n_vertical_flip' | translate }}\n </div>\n </div>\n\n <div class=\"flex flex-col items-center\">\n <libs_ui-components-buttons-button\n [iconOnlyType]=\"true\"\n [sizeButton]=\"'large'\"\n [disable]=\"loading() || loadingImage()\"\n [type]=\"'button-outline'\"\n [classIconLeft]=\"'libs-ui-icon-flip-horizontal !text-[20px]'\"\n (outClick)=\"handlerFlipImage($event, 'horizontal')\" />\n <div class=\"libs-ui-font-h6r text-[#6a7383] mt-[4px]\">\n {{ 'i18n_horizontal_flip' | translate }}\n </div>\n </div>\n\n <div class=\"flex flex-col items-center\">\n <libs_ui-components-buttons-button\n [iconOnlyType]=\"true\"\n [sizeButton]=\"'large'\"\n [disable]=\"loading() || loadingImage()\"\n [type]=\"'button-outline'\"\n [classIconLeft]=\"'libs-ui-icon-scale !text-[20px]'\"\n (outClick)=\"handlerResize($event)\" />\n <div class=\"libs-ui-font-h6r text-[#6a7383] mt-[4px]\">\n {{ 'i18n_resize' | translate }}\n </div>\n </div>\n </div>\n </div>\n </div>\n</libs_ui-components-modal>\n\n<ng-template #svgArrow>\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"40\"\n height=\"40\"\n viewBox=\"0 0 40 40\"\n fill=\"none\">\n <path\n d=\"M33.6987 14.6969L18.3309 14.6969C16.516 14.6969 15.0311 16.1818 15.0311 17.9967L15.0311 33.3645\"\n stroke=\"white\"\n stroke-width=\"5.83333\"\n stroke-miterlimit=\"10\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\" />\n </svg>\n</ng-template>\n\n<ng-template #svgCircle>\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"15\"\n height=\"15\"\n viewBox=\"0 0 15 15\"\n fill=\"none\">\n <circle\n cx=\"7.5\"\n cy=\"7.5\"\n r=\"7.5\"\n fill=\"white\" />\n </svg>\n</ng-template>\n", styles: [".libs-ui-image-editor-edit-display-grid-gap-16{display:grid;gap:16px;grid-template-columns:1fr 1fr 1fr 1fr}.libs-ui-image-editor-edit-top-33{top:33.33%}.libs-ui-image-editor-edit-top-66{top:66.66%}.libs-ui-image-editor-edit-left-33{left:33.33%}.libs-ui-image-editor-edit-left-66{left:66.66%}.libs-ui-image-editor-edit-action-item{display:flex;flex-direction:column;align-items:center;width:60px;cursor:pointer}.libs-ui-image-editor-edit-action-item-icon{padding:12px;border-radius:8px;width:max-content;border:1px solid #e6e7ea}.libs-ui-image-editor-edit-action-item-icon:before{font-size:20px}.libs-ui-image-editor-edit-action-item:hover .libs-ui-image-editor-edit-action-item-icon{border:1px solid var(--mo-global-color-primary-border-hover)}.libs-ui-image-editor-edit-action-item:hover .libs-ui-image-editor-edit-action-item-icon:before{color:var(--mo-global-color-primary-text-bright-20)!important}.libs-ui-image-editor-edit-action-item[active=true] .libs-ui-image-editor-edit-action-item-icon{border:1px solid var(--mo-global-color-primary-border-active)!important;background-color:var(--mo-global-color-primary-background-95)!important}.libs-ui-image-editor-edit-action-item[active=true] .libs-ui-image-editor-edit-action-item-icon:before{color:var(--mo-global-color-primary-text-active)!important}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }, { kind: "component", type: LibsUiComponentsPopoverComponent, selector: "libs_ui-components-popover,[LibsUiComponentsPopoverDirective]", inputs: ["debugId", "flagMouse", "type", "mode", "config", "ignoreShowPopover", "elementRefCustom", "initEventInElementRefCustom", "classInclude", "ignoreHiddenPopoverContentWhenMouseLeave", "ignoreStopPropagationEvent", "ignoreCursorPointerModeLikeClick", "isAddContentToParentDocument", "ignoreClickOutside"], outputs: ["outEvent", "outChangStageFlagMouse", "outEventPopoverContent", "outFunctionsControl"] }, { kind: "component", type: LibsUiComponentsModalComponent, selector: "libs_ui-components-modal", inputs: ["show", "mode", "isBackdropTransparent", "isBackgroundTransparentModal", "isSizeBackdropByWidthHeightInput", "hasShadowBoxWhenHiddenBackDropTransparent", "classIncludeModalWrapper", "zIndex", "width", "height", "maxWidth", "maxHeight", "minWidth", "isFullScreen", "disable", "ignoreCommunicateMicroEvent", "headerConfig", "bodyConfig", "footerConfig", "buttonsFooter", "title", "titleUseXssFilter", "titleUseTooltip", "titleUseInnerText"], outputs: ["showChange", "widthChange", "heightChange", "maxWidthChange", "maxHeightChange", "minWidthChange", "disableChange", "buttonsFooterChange", "outScrollContent", "outEvent", "outFunctionControl"] }, { kind: "component", type: LibsUiComponentsInputsValidComponent, selector: "libs_ui-components-inputs-valid", inputs: ["item", "labelConfig", "emitEmptyInDataTypeNumber", "ignoreBlockInputMaxValue", "fieldNameBind", "showCount", "typeComponentSelectItem", "valueComponentSelectItem", "disableComponentSelectItem", "tagInput", "dataType", "typeInput", "modeInput", "resetAutoCompletePassword", "textAreaEnterNotNewLine", "fixedFloat", "acceptNegativeValue", "valueUpDownNumber", "ignoreWidthInput100", "classIncludeInput", "classContainerInput", "readonly", "disable", "noBorder", "backgroundNone", "useColorModeExist", "placeholder", "keepPlaceholderOnly", "classContainerBottomInput", "autoRemoveEmoji", "defaultHeight", "maxHeightTextArea", "minHeightTextArea", "ignoreShowError", "borderError", "iconLeftClass", "popoverContentIconLeft", "iconRightClass", "popoverContentIconRight", "zIndexPopoverContent", "unitsLeft", "configUnitLeft", "keySelectedUnitLeft", "unitsRight", "configUnitRight", "keySelectedUnitRight", "maxValueNumber", "minValueNumber", "ignoreContentLeft", "ignoreContentRight", "isBaselineStyle", "valuePatternShowError", "validPattern", "validRequired", "validMinLength", "validMinValue", "validMaxValue", "validMaxLength", "functionValid", "maxLength", "positionMessageErrorStartInput", "classInclude", "resize", "templateLeftBottomInput", "templateRightBottomInput", "onlyAcceptNegativeValue", "autoAddZeroLessThan10InTypeInt", "maxLengthNumberCount", "classMessageErrorInclude", "ignoreStopPropagationEvent", "ignoreUnitRightClassReadOnly", "paddingRightCustomSpecific", "focusTimeOut", "debounceTimeValidate"], outputs: ["itemChange", "outValueChange", "outSelect", "outIconLeft", "outIconRight", "outClickButtonLabel", "outSwitchEventLabel", "outLabelRightClick", "outEnterInputEvent", "outHeightAreaChange", "outFunctionsControl", "outFocusAndBlur", "outChangeValueByButtonUpDown"] }, { kind: "component", type: LibsUiComponentsButtonsButtonComponent, selector: "libs_ui-components-buttons-button", inputs: ["flagMouse", "type", "buttonCustom", "sizeButton", "label", "disable", "isPending", "imageLeft", "classInclude", "classIconLeft", "classIconRight", "classLabel", "iconOnlyType", "popover", "ignoreStopPropagationEvent", "zIndex", "widthLabelPopover", "styleIconLeft", "styleButton", "ignoreFocusWhenInputTab", "ignoreSetClickWhenShowPopover", "ignorePointerEvent", "isActive", "isHandlerEnterDocumentClickButton"], outputs: ["outClick", "outPopoverEvent", "outFunctionsControl"] }, { kind: "component", type: LibsUiComponentsSpinnerComponent, selector: "libs_ui-components-spinner", inputs: ["type", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
991
+ }
992
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsImageEditorComponent, decorators: [{
993
+ type: Component,
994
+ args: [{ selector: 'libs_ui-components-image_editor', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [NgTemplateOutlet, TranslateModule, LibsUiComponentsPopoverComponent, LibsUiComponentsModalComponent, LibsUiComponentsInputsValidComponent, LibsUiComponentsButtonsButtonComponent, LibsUiComponentsSpinnerComponent], template: "<libs_ui-components-modal\n [width]=\"'calc(100vw - 400px)'\"\n [height]=\"'calc(100vh - 100px)'\"\n [minWidth]=\"'1200px'\"\n [headerConfig]=\"{ ignoreHeaderTheme: true, removeButtonClose: true }\"\n [bodyConfig]=\"{ classInclude: '!p-0', scrollOverlayOptions: { scrollX: 'hidden', scrollY: 'hidden' } }\"\n [footerConfig]=\"{ hidden: true }\"\n [zIndex]=\"zIndex()\"\n [buttonsFooter]=\"[]\"\n [mode]=\"'center'\">\n <div class=\"libs-ui-modal-header-custom w-full flex items-center justify-between\">\n <div class=\"w-full relative mx-[24px] h-[16px]\">\n <div class=\"flex w-full libs-ui-font-h4s absolute\">\n <libs_ui-components-popover\n [type]=\"'text'\"\n [config]=\"{ zIndex: zIndex() }\">\n {{ 'i18n_edit_file_name' | translate: { file_name: nameFile() } }}\n </libs_ui-components-popover>\n </div>\n </div>\n <div class=\"flex items-center\">\n @if (modeShowButton() === 'save-file') {\n <libs_ui-components-buttons-button\n [type]=\"'button-third'\"\n [label]=\"'i18n_cancel'\"\n [disable]=\"loading() || loadingImage()\"\n [classInclude]=\"'py-[6px] px-[12px] mr-[16px]'\"\n (outClick)=\"handlerClose($event)\" />\n\n <libs_ui-components-buttons-button\n class=\"mr-[24px]\"\n [label]=\"'i18n_save'\"\n [disable]=\"loading() || loadingImage()\"\n [classInclude]=\"'py-[6px] px-[12px]'\"\n (outClick)=\"handlerSaveFile($event, 'save-file')\" />\n } @else {\n <libs_ui-components-buttons-button\n [label]=\"'i18n_save_to_new_file'\"\n [type]=\"'button-outline'\"\n [classInclude]=\"'mx-[16px] py-[6px] px-[12px]'\"\n [disable]=\"loading() || loadingImage() || !changed()\"\n (outClick)=\"handlerSaveFile($event, 'save-api-as-new-file')\" />\n <libs_ui-components-buttons-button\n [type]=\"'button-secondary'\"\n [label]=\"'i18n_save'\"\n [classInclude]=\"'py-[6px] px-[12px]'\"\n [disable]=\"loading() || loadingImage() || !changed()\"\n (outClick)=\"handlerSaveFile($event, 'save-api')\" />\n <div class=\"h-[12px] libs-ui-border-left-general mx-[16px]\"></div>\n <libs_ui-components-buttons-button\n class=\"mr-[24px]\"\n [classInclude]=\"'p-[6px]'\"\n [type]=\"'button-third-hover-danger'\"\n [iconOnlyType]=\"true\"\n [disable]=\"loading()\"\n [popover]=\"{ config: { content: 'i18n_exit_edit_mode', zIndex: zIndex() } }\"\n [classIconLeft]=\"'libs-ui-icon-close'\"\n (outClick)=\"handlerClose($event)\" />\n }\n </div>\n </div>\n <div class=\"libs-ui-modal-body-custom h-full w-full flex\">\n <div\n #imageEditorContainer\n class=\"relative w-full h-full bg-[#e6e7ea] border-radius-bottom-left-8px p-[32px] libs-ui-input-image-editor-container-left\">\n @if (loadingImage()) {\n <div class=\"absolute h-full w-full bg-[#e6e7ea] top-0 left-0 z-[5]\">\n <libs_ui-components-spinner [size]=\"'medium'\" />\n </div>\n }\n <div class=\"absolute h-[32px] w-[calc(100%-64px)] bg-[#e6e7eab8] top-0 left-[32px] z-[1]\"></div>\n <div class=\"absolute w-[32px] h-full bg-[#e6e7eab8] left-0 top-0 z-[1] rounded-bl-[8px]\"></div>\n <div class=\"absolute w-[32px] h-full bg-[#e6e7eab8] right-0 top-0 z-[1]\"></div>\n <div class=\"absolute h-[32px] w-[calc(100%-64px)] bg-[#e6e7eab8] bottom-0 left-[32px] z-[1]\"></div>\n <div\n #imageContainer\n class=\"relative w-full h-full rounded-bl-[8px] min-h-0\">\n <div\n #circleTL\n class=\"absolute z-[1]\"\n [class.hidden]=\"!dragGridCrop()\"\n [class.flex]=\"dragGridCrop()\">\n <ng-template *ngTemplateOutlet=\"svgCircle\" />\n </div>\n <div\n #circleTR\n class=\"absolute z-[1]\"\n [class.hidden]=\"!dragGridCrop()\"\n [class.flex]=\"dragGridCrop()\">\n <ng-template *ngTemplateOutlet=\"svgCircle\" />\n </div>\n <div\n #circleBL\n class=\"absolute z-[1]\"\n [class.hidden]=\"!dragGridCrop()\"\n [class.flex]=\"dragGridCrop()\">\n <ng-template *ngTemplateOutlet=\"svgCircle\" />\n </div>\n <div\n #circleBR\n class=\"absolute z-[1]\"\n [class.hidden]=\"!dragGridCrop()\"\n [class.flex]=\"dragGridCrop()\">\n <ng-template *ngTemplateOutlet=\"svgCircle\" />\n </div>\n <img\n #imageOrigin\n class=\"absolute border-[4px] border-[#8e61ee] rounded-[4px] top-[-1000px] left-[-1000px]\"\n loading=\"lazy\"\n [src]=\"imgSrc()\"\n (load)=\"handlerImageLoaded()\"\n (error)=\"handlerImageError($event)\" />\n <img\n #imageClip\n class=\"absolute z-[1] top-[-1000px] left-[-1000px]\"\n loading=\"lazy\"\n [src]=\"imgSrc()\"\n (error)=\"handlerImageError($event)\" />\n <div class=\"absolute top-0 left-0 right-0 bottom-0 bg-[#e6e7eab8]\"></div>\n <div\n #cropArea\n class=\"absolute z-[2] border-[4px] border-[#8e61ee] rounded-[4px]\">\n <div class=\"relative w-full h-full\">\n <div\n #cropTL\n class=\"absolute z-[2] left-[-18px] top-[-17px] cursor-nwse-resize\">\n <ng-template *ngTemplateOutlet=\"svgArrow\" />\n </div>\n <div\n #cropBL\n class=\"absolute z-[2] left-[-17px] bottom-[-18px] cursor-nesw-resize rotate-[270deg]\">\n <ng-template *ngTemplateOutlet=\"svgArrow\" />\n </div>\n <div\n #cropTR\n class=\"absolute z-[2] right-[-17px] top-[-17px] cursor-nesw-resize rotate-[90deg]\">\n <ng-template *ngTemplateOutlet=\"svgArrow\" />\n </div>\n <div\n #cropBR\n class=\"absolute z-[2] right-[-18px] bottom-[-17px] cursor-nwse-resize rotate-[180deg]\">\n <ng-template *ngTemplateOutlet=\"svgArrow\" />\n </div>\n <div\n #cropLineVL\n class=\"absolute z-[1] w-[12px] left-[-6px] top-0 bottom-0 cursor-ew-resize\"></div>\n <div\n #cropLineVR\n class=\"absolute z-[1] w-[12px] right-[-6px] top-0 bottom-0 cursor-ew-resize\"></div>\n <div\n #cropLineHT\n class=\"absolute z-[1] h-[12px] left-0 top-[-6px] right-0 cursor-ns-resize\"></div>\n <div\n #cropLineHB\n class=\"absolute z-[1] h-[12px] left-0 right-0 bottom-[-6px] cursor-ns-resize\"></div>\n\n <div class=\"bg-white absolute h-[1px] top-0 left-0 right-0\"></div>\n <div class=\"bg-white absolute h-[1px] bottom-0 left-0 right-0\"></div>\n <div class=\"bg-white absolute w-[1px] top-0 left-0 bottom-0\"></div>\n <div class=\"bg-white absolute w-[1px] top-0 bottom-0 right-0\"></div>\n <div class=\"bg-white absolute h-[2px] top-[33%] left-0 right-0\"></div>\n <div class=\"bg-white absolute h-[2px] top-[66%] left-0 right-0\"></div>\n <div class=\"bg-white absolute w-[2px] top-0 bottom-0 left-[33%]\"></div>\n <div class=\"bg-white absolute w-[2px] top-0 bottom-0 left-[66%]\"></div>\n </div>\n </div>\n </div>\n @if (loading()) {\n <libs_ui-components-spinner [size]=\"'medium'\" />\n }\n </div>\n <div class=\"w-[320px] shrink-0 p-[16px] bg-[#ffffff] rounded-br-[8px] z-[2] libs-ui-input-image-editor-container-right\">\n <div class=\"flex items-center justify-between mb-[16px]\">\n <div class=\"libs-ui-font-h4m\">{{ 'i18n_cutting_ratio' | translate }}</div>\n <libs_ui-components-buttons-button\n [iconOnlyType]=\"true\"\n [type]=\"'button-third'\"\n [classIconLeft]=\"'libs-ui-icon-refresh'\"\n [classInclude]=\"'p-[6px] mr-[8px]'\"\n [popover]=\"{ config: { content: 'i18n_restore_the_original_state', zIndex: zIndex() } }\"\n [disable]=\"loading() || loadingImage() || !changed()\"\n (outClick)=\"handlerRestoreImage($event)\" />\n </div>\n <div class=\"mb-[16px] libs-ui-image-editor-edit-display-grid-gap-16\">\n @for (item of cropRatioItems(); track item.key) {\n <div class=\"flex flex-col items-center\">\n <libs_ui-components-buttons-button\n [iconOnlyType]=\"true\"\n [sizeButton]=\"'large'\"\n [disable]=\"loading() || loadingImage() || requiredCropFollowRatio()\"\n [type]=\"'button-outline'\"\n [classIconLeft]=\"item.icon + ' !text-[20px]'\"\n [isActive]=\"item.key === cropRatioItemSelected()\"\n (outClick)=\"handlerSelectCropRatioItem($event, item.key)\" />\n <div class=\"libs-ui-font-h6r text-[#6a7383] mt-[4px]\">\n {{ (item.key === 'free' ? 'i18n_custom' : item.key) | translate }}\n </div>\n </div>\n }\n </div>\n <div class=\"mb-[16px] flex\">\n <libs_ui-components-inputs-valid\n class=\"mr-[16px]\"\n [dataType]=\"'int'\"\n [labelConfig]=\"{ labelLeft: 'i18n_width', classInclude: 'mb-[4px]', labelLeftClass: 'libs-ui-font-h7r' }\"\n [fieldNameBind]=\"'width'\"\n [(item)]=\"cropSize\"\n [unitsRight]=\"[{ id: 'px', label: 'px' }]\"\n [keySelectedUnitRight]=\"'px'\"\n [valueUpDownNumber]=\"1\"\n [maxValueNumber]=\"originWidth()\"\n [disable]=\"loading() || loadingImage()\"\n (outValueChange)=\"handlerCropWidth()\" />\n <libs_ui-components-inputs-valid\n [dataType]=\"'int'\"\n [labelConfig]=\"{ labelLeft: 'i18n_length', classInclude: 'mb-[4px]', labelLeftClass: 'libs-ui-font-h7r' }\"\n [fieldNameBind]=\"'height'\"\n [(item)]=\"cropSize\"\n [unitsRight]=\"[{ id: 'px', label: 'px' }]\"\n [keySelectedUnitRight]=\"'px'\"\n [valueUpDownNumber]=\"1\"\n [maxValueNumber]=\"originHeight()\"\n [disable]=\"loading() || loadingImage()\"\n (outValueChange)=\"handlerCropHeight()\" />\n </div>\n <div class=\"libs-ui-font-h4m mb-[16px]\">{{ 'i18n_rotate_photos' | translate }}</div>\n <div class=\"flex libs-ui-image-editor-edit-display-grid-gap-16\">\n <div class=\"flex flex-col items-center\">\n <libs_ui-components-buttons-button\n [iconOnlyType]=\"true\"\n [sizeButton]=\"'large'\"\n [disable]=\"loading() || loadingImage()\"\n [type]=\"'button-outline'\"\n [classIconLeft]=\"'libs-ui-icon-rotate-image-outline !text-[20px]'\"\n (outClick)=\"handlerRotateImage($event)\" />\n <div class=\"libs-ui-font-h6r text-[#6a7383] mt-[4px]\">\n {{ 'i18n_rotate_ninety_degrees' | translate }}\n </div>\n </div>\n\n <div class=\"flex flex-col items-center\">\n <libs_ui-components-buttons-button\n [iconOnlyType]=\"true\"\n [sizeButton]=\"'large'\"\n [disable]=\"loading() || loadingImage()\"\n [type]=\"'button-outline'\"\n [classIconLeft]=\"'libs-ui-icon-flip-vertical !text-[20px]'\"\n (outClick)=\"handlerFlipImage($event, 'vertical')\" />\n <div class=\"libs-ui-font-h6r text-[#6a7383] mt-[4px]\">\n {{ 'i18n_vertical_flip' | translate }}\n </div>\n </div>\n\n <div class=\"flex flex-col items-center\">\n <libs_ui-components-buttons-button\n [iconOnlyType]=\"true\"\n [sizeButton]=\"'large'\"\n [disable]=\"loading() || loadingImage()\"\n [type]=\"'button-outline'\"\n [classIconLeft]=\"'libs-ui-icon-flip-horizontal !text-[20px]'\"\n (outClick)=\"handlerFlipImage($event, 'horizontal')\" />\n <div class=\"libs-ui-font-h6r text-[#6a7383] mt-[4px]\">\n {{ 'i18n_horizontal_flip' | translate }}\n </div>\n </div>\n\n <div class=\"flex flex-col items-center\">\n <libs_ui-components-buttons-button\n [iconOnlyType]=\"true\"\n [sizeButton]=\"'large'\"\n [disable]=\"loading() || loadingImage()\"\n [type]=\"'button-outline'\"\n [classIconLeft]=\"'libs-ui-icon-scale !text-[20px]'\"\n (outClick)=\"handlerResize($event)\" />\n <div class=\"libs-ui-font-h6r text-[#6a7383] mt-[4px]\">\n {{ 'i18n_resize' | translate }}\n </div>\n </div>\n </div>\n </div>\n </div>\n</libs_ui-components-modal>\n\n<ng-template #svgArrow>\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"40\"\n height=\"40\"\n viewBox=\"0 0 40 40\"\n fill=\"none\">\n <path\n d=\"M33.6987 14.6969L18.3309 14.6969C16.516 14.6969 15.0311 16.1818 15.0311 17.9967L15.0311 33.3645\"\n stroke=\"white\"\n stroke-width=\"5.83333\"\n stroke-miterlimit=\"10\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\" />\n </svg>\n</ng-template>\n\n<ng-template #svgCircle>\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"15\"\n height=\"15\"\n viewBox=\"0 0 15 15\"\n fill=\"none\">\n <circle\n cx=\"7.5\"\n cy=\"7.5\"\n r=\"7.5\"\n fill=\"white\" />\n </svg>\n</ng-template>\n", styles: [".libs-ui-image-editor-edit-display-grid-gap-16{display:grid;gap:16px;grid-template-columns:1fr 1fr 1fr 1fr}.libs-ui-image-editor-edit-top-33{top:33.33%}.libs-ui-image-editor-edit-top-66{top:66.66%}.libs-ui-image-editor-edit-left-33{left:33.33%}.libs-ui-image-editor-edit-left-66{left:66.66%}.libs-ui-image-editor-edit-action-item{display:flex;flex-direction:column;align-items:center;width:60px;cursor:pointer}.libs-ui-image-editor-edit-action-item-icon{padding:12px;border-radius:8px;width:max-content;border:1px solid #e6e7ea}.libs-ui-image-editor-edit-action-item-icon:before{font-size:20px}.libs-ui-image-editor-edit-action-item:hover .libs-ui-image-editor-edit-action-item-icon{border:1px solid var(--mo-global-color-primary-border-hover)}.libs-ui-image-editor-edit-action-item:hover .libs-ui-image-editor-edit-action-item-icon:before{color:var(--mo-global-color-primary-text-bright-20)!important}.libs-ui-image-editor-edit-action-item[active=true] .libs-ui-image-editor-edit-action-item-icon{border:1px solid var(--mo-global-color-primary-border-active)!important;background-color:var(--mo-global-color-primary-background-95)!important}.libs-ui-image-editor-edit-action-item[active=true] .libs-ui-image-editor-edit-action-item-icon:before{color:var(--mo-global-color-primary-text-active)!important}\n"] }]
995
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
996
+ type: Optional
997
+ }, {
998
+ type: Inject,
999
+ args: [LINK_IMAGE_ERROR_TOKEN_INJECT]
1000
+ }] }] });
1001
+
1002
+ class LibsUiComponentsImageEditorDemoComponent {
1003
+ imageFileInput;
1004
+ imageSource = '';
1005
+ showEditor = false;
1006
+ editedImageUrl = '';
1007
+ selectedFile = null;
1008
+ imageName = '';
1009
+ // Documentation data as regular properties
1010
+ inputsDoc = [
1011
+ { name: 'imgSrc', type: 'string', default: 'Bắt buộc', description: 'Đường dẫn hoặc base64 của hình ảnh cần chỉnh sửa' },
1012
+ { name: 'originUrl', type: 'string', default: '-', description: 'URL gốc của hình ảnh' },
1013
+ { name: 'nameFile', type: 'string', default: '-', description: 'Tên file khi lưu' },
1014
+ { name: 'modeShowButton', type: "'save-file' | 'save-api'", default: "'save-file'", description: 'Chế độ nút lưu' },
1015
+ { name: 'mimetype', type: 'string', default: '-', description: 'Định dạng của hình ảnh' },
1016
+ { name: 'zIndex', type: 'number', default: '1200', description: 'z-index của component' },
1017
+ { name: 'hasZoom', type: 'boolean', default: 'false', description: 'Cho phép phóng to/thu nhỏ hình ảnh' },
1018
+ { name: 'aspectRatio', type: 'IAspectRatio', default: '-', description: 'Tỷ lệ khung hình mặc định' },
1019
+ { name: 'requiredCropFollowRatio', type: 'boolean', default: 'false', description: 'Bắt buộc cắt theo tỷ lệ đã chọn' },
1020
+ ];
1021
+ outputsDoc = [
1022
+ { name: 'outClose', type: '{isClickButtonClose: boolean}', description: 'Sự kiện khi đóng trình chỉnh sửa' },
1023
+ { name: 'outSaveFile', type: 'ISaveFile', description: 'Sự kiện khi lưu file' },
1024
+ { name: 'outFunctionsControl', type: 'IImageEditorFunctionControlEvent', description: 'Sự kiện để kiểm soát các chức năng' },
1025
+ ];
1026
+ interfacesDoc = [
1027
+ {
1028
+ name: 'ISaveFile',
1029
+ code: "export interface ISaveFile {\n file: Blob;\n url: string;\n mode: 'save-file' | 'save-api' | 'save-api-as-new-file';\n}",
1030
+ description: 'Interface định nghĩa dữ liệu khi lưu file.',
1031
+ },
1032
+ {
1033
+ name: 'IImageEditorFunctionControlEvent',
1034
+ code: 'export interface IImageEditorFunctionControlEvent {\n cropImage: () => Promise<string>;\n setLoadingState: (loading: boolean) => void;\n}',
1035
+ description: 'Interface định nghĩa các hàm điều khiển có thể được gọi từ bên ngoài component.',
1036
+ },
1037
+ {
1038
+ name: 'IAspectRatio',
1039
+ code: 'export interface IAspectRatio {\n key: string;\n value: number;\n}',
1040
+ description: 'Interface định nghĩa tỷ lệ khung hình.',
1041
+ },
1042
+ ];
1043
+ features = [
1044
+ {
1045
+ id: 1,
1046
+ icon: '✂️',
1047
+ title: 'Cắt ảnh',
1048
+ description: 'Cắt hình ảnh theo khu vực tùy chỉnh hoặc dựa trên tỷ lệ định trước (1:1, 4:3, 16:9, v.v.).',
1049
+ },
1050
+ {
1051
+ id: 2,
1052
+ icon: '🔄',
1053
+ title: 'Xoay và lật',
1054
+ description: 'Xoay hình ảnh theo các góc khác nhau hoặc lật theo chiều ngang/dọc.',
1055
+ },
1056
+ {
1057
+ id: 3,
1058
+ icon: '📏',
1059
+ title: 'Thay đổi kích thước',
1060
+ description: 'Thay đổi kích thước hình ảnh theo tỷ lệ phần trăm hoặc kích thước cụ thể.',
1061
+ },
1062
+ {
1063
+ id: 4,
1064
+ icon: '💾',
1065
+ title: 'Nhiều chế độ lưu',
1066
+ description: 'Hỗ trợ nhiều chế độ lưu: tải xuống trực tiếp hoặc gửi đến API.',
1067
+ },
1068
+ {
1069
+ id: 5,
1070
+ icon: '🔍',
1071
+ title: 'Phóng to/thu nhỏ',
1072
+ description: 'Tính năng phóng to/thu nhỏ để xem chi tiết hình ảnh khi cần thiết.',
1073
+ },
1074
+ ];
1075
+ codeExamples = [
1076
+ {
1077
+ id: 1,
1078
+ title: 'Sử dụng cơ bản',
1079
+ code: `import { Component } from '@angular/core';\nimport { LibsUiComponentsImageEditorComponent } from '@libs-ui/components-image-editor';\n\n@Component({\n selector: 'app-example',\n standalone: true,\n imports: [LibsUiComponentsImageEditorComponent],\n template: \`\n <libs_ui-components-image_editor\n [(imgSrc)]="imageSource"\n [modeShowButton]="'save-file'"\n [nameFile]="'image.jpg'"\n (outSaveFile)="onSaveFile($event)"\n (outClose)="onClose($event)">\n </libs_ui-components-image_editor>\n \`\n})\nexport class ExampleComponent {\n imageSource = 'https://example.com/path/to/image.jpg';\n\n onSaveFile(data: {file: Blob, url: string, mode: string}) {\n console.log('File đã được lưu:', data);\n }\n\n onClose(data: {isClickButtonClose: boolean}) {\n console.log('Đã đóng trình chỉnh sửa ảnh');\n }\n}`,
1080
+ },
1081
+ {
1082
+ id: 2,
1083
+ title: 'Sử dụng với tỷ lệ cố định',
1084
+ code: `import { Component } from '@angular/core';\nimport { LibsUiComponentsImageEditorComponent } from '@libs-ui/components-image-editor';\nimport { IAspectRatio } from '@libs-ui/interfaces-types';\n\n@Component({\n selector: 'app-example',\n standalone: true,\n imports: [LibsUiComponentsImageEditorComponent],\n template: \`\n <libs_ui-components-image_editor\n [(imgSrc)]="imageSource"\n [aspectRatio]="aspectRatio"\n [requiredCropFollowRatio]="true"\n (outSaveFile)="onSaveFile($event)">\n </libs_ui-components-image_editor>\n \`\n})\nexport class ExampleComponent {\n imageSource = 'https://example.com/path/to/image.jpg';\n aspectRatio: IAspectRatio = {\n key: '1:1',\n value: 1\n };\n\n onSaveFile(data: any) {\n console.log('Đã lưu hình ảnh với tỷ lệ 1:1');\n }\n}`,
1085
+ },
1086
+ {
1087
+ id: 3,
1088
+ title: 'Sử dụng với phần điều khiển bên ngoài',
1089
+ code: `import { Component, ViewChild } from '@angular/core';\nimport { LibsUiComponentsImageEditorComponent, IImageEditorFunctionControlEvent } from '@libs-ui/components-image-editor';\n\n@Component({\n selector: 'app-example',\n standalone: true,\n imports: [LibsUiComponentsImageEditorComponent],\n template: \`\n <button (click)="cropAndSave()">Cắt và lưu ngay</button>\n <libs_ui-components-image_editor\n [(imgSrc)]="imageSource"\n (outFunctionsControl)="onFunctionsControl($event)">\n </libs_ui-components-image_editor>\n \`\n})\nexport class ExampleComponent {\n imageSource = 'https://example.com/path/to/image.jpg';\n editorFunctions!: IImageEditorFunctionControlEvent;\n\n onFunctionsControl(event: IImageEditorFunctionControlEvent) {\n this.editorFunctions = event;\n }\n\n async cropAndSave() {\n if (this.editorFunctions) {\n this.editorFunctions.setLoadingState(true);\n try {\n const dataUrl = await this.editorFunctions.cropImage();\n console.log('Hình ảnh đã được cắt:', dataUrl);\n } finally {\n this.editorFunctions.setLoadingState(false);\n }\n }\n }\n}`,
1090
+ },
1091
+ ];
1092
+ copyToClipboard(text) {
1093
+ navigator.clipboard
1094
+ .writeText(text)
1095
+ .then(() => {
1096
+ alert('Đã sao chép vào clipboard');
1097
+ })
1098
+ .catch((err) => {
1099
+ console.error('Không thể sao chép: ', err);
1100
+ });
1101
+ }
1102
+ onFileSelected(event) {
1103
+ const input = event.target;
1104
+ if (input.files && input.files.length > 0) {
1105
+ this.selectedFile = input.files[0];
1106
+ this.imageName = this.selectedFile.name;
1107
+ console.log('File selected:', this.selectedFile.name);
1108
+ }
1109
+ }
1110
+ uploadAndEdit() {
1111
+ if (!this.selectedFile) {
1112
+ alert('Vui lòng chọn một file hình ảnh trước');
1113
+ return;
1114
+ }
1115
+ const reader = new FileReader();
1116
+ reader.onload = (e) => {
1117
+ if (e.target?.result) {
1118
+ this.imageSource = e.target.result;
1119
+ this.showEditor = true;
1120
+ }
1121
+ };
1122
+ reader.readAsDataURL(this.selectedFile);
1123
+ }
1124
+ onSaveImage(data) {
1125
+ console.log('Image saved:', data);
1126
+ this.editedImageUrl = data.url;
1127
+ this.showEditor = false;
1128
+ }
1129
+ onCloseEditor(data) {
1130
+ console.log('Editor closed:', data);
1131
+ this.showEditor = false;
1132
+ }
1133
+ downloadImage() {
1134
+ if (this.editedImageUrl) {
1135
+ const link = document.createElement('a');
1136
+ link.href = this.editedImageUrl;
1137
+ link.download = this.imageName || 'edited-image.jpg';
1138
+ link.click();
1139
+ }
1140
+ }
1141
+ handleFunctionControl(event) {
1142
+ console.log(event);
1143
+ }
1144
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsImageEditorDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1145
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: LibsUiComponentsImageEditorDemoComponent, isStandalone: true, selector: "lib-image-editor-demo", viewQueries: [{ propertyName: "imageFileInput", first: true, predicate: ["imageFileInput"], descendants: true }], ngImport: i0, template: "<div class=\"max-w-6xl mx-auto p-5 font-sans text-gray-800\">\n <header class=\"text-center py-10 bg-white rounded-lg mb-8 shadow-sm\">\n <h1 class=\"text-4xl font-bold text-gray-800 mb-2\">Demo Tr\u00ECnh Ch\u1EC9nh S\u1EEDa H\u00ECnh \u1EA2nh</h1>\n <p class=\"text-xl text-gray-500\">Th\u01B0 vi\u1EC7n component cho Angular \u0111\u1EC3 ch\u1EC9nh s\u1EEDa h\u00ECnh \u1EA3nh</p>\n </header>\n\n <main>\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold text-gray-800 mb-5 pb-3 border-b border-gray-200\">Gi\u1EDBi thi\u1EC7u</h2>\n <p>\n <code class=\"text-sm bg-gray-100 px-1.5 py-0.5 rounded\">&#64;libs-ui/components-image-editor</code>\n l\u00E0 m\u1ED9t component Angular m\u1EA1nh m\u1EBD cho ph\u00E9p ng\u01B0\u1EDDi d\u00F9ng th\u1EF1c hi\u1EC7n c\u00E1c thao t\u00E1c ch\u1EC9nh s\u1EEDa h\u00ECnh \u1EA3nh nh\u01B0 c\u1EAFt, thay \u0111\u1ED5i k\u00EDch th\u01B0\u1EDBc, xoay, l\u1EADt v\u00E0 \u00E1p d\u1EE5ng t\u1EF7 l\u1EC7 c\u1ED1 \u0111\u1ECBnh.\n </p>\n </section>\n\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold text-gray-800 mb-5 pb-3 border-b border-gray-200\">C\u00E0i \u0111\u1EB7t</h2>\n\n <div class=\"mb-6\">\n <h3 class=\"text-xl font-semibold text-gray-700 mb-3\">Y\u00EAu c\u1EA7u</h3>\n <ul class=\"list-disc pl-5 space-y-2 text-gray-600\">\n <li>\n <span class=\"font-semibold\">Angular</span>\n : 18.0.0 tr\u1EDF l\u00EAn\n </li>\n <li>\n <span class=\"font-semibold\">Tailwind CSS</span>\n : 3.3.0 tr\u1EDF l\u00EAn\n </li>\n </ul>\n </div>\n\n <p class=\"mb-4\">\u0110\u1EC3 c\u00E0i \u0111\u1EB7t th\u01B0 vi\u1EC7n, s\u1EED d\u1EE5ng npm ho\u1EB7c yarn:</p>\n\n <div class=\"flex items-center bg-gray-100 p-4 rounded-lg mb-6\">\n <pre class=\"flex-1 text-sm overflow-x-auto\"><code>npm install &#64;libs-ui/components-image-editor</code></pre>\n <button\n class=\"ml-4 px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors\"\n (click)=\"copyToClipboard('npm install @libs-ui/components-image-editor')\">\n Sao ch\u00E9p\n </button>\n </div>\n\n <p class=\"mb-4\">Ho\u1EB7c v\u1EDBi yarn:</p>\n\n <div class=\"flex items-center bg-gray-100 p-4 rounded-lg mb-6\">\n <pre class=\"flex-1 text-sm overflow-x-auto\"><code>yarn add &#64;libs-ui/components-image-editor</code></pre>\n <button\n class=\"ml-4 px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors\"\n (click)=\"copyToClipboard('yarn add @libs-ui/components-image-editor')\">\n Sao ch\u00E9p\n </button>\n </div>\n </section>\n\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold text-gray-800 mb-5 pb-3 border-b border-gray-200\">Demo tr\u1EF1c ti\u1EBFp</h2>\n <div>\n <p class=\"mb-4\">T\u1EA3i l\u00EAn h\u00ECnh \u1EA3nh v\u00E0 ch\u1EC9nh s\u1EEDa ngay tr\u00EAn tr\u00ECnh duy\u1EC7t:</p>\n <div class=\"flex flex-wrap items-center gap-4 mb-8\">\n <input\n type=\"file\"\n #imageFileInput\n accept=\"image/*\"\n class=\"border border-gray-300 rounded px-3 py-2\"\n (change)=\"onFileSelected($event)\" />\n <libs_ui-components-buttons-button\n label=\"T\u1EA3i l\u00EAn v\u00E0 ch\u1EC9nh s\u1EEDa\"\n (click)=\"uploadAndEdit()\"></libs_ui-components-buttons-button>\n </div>\n\n @if (editedImageUrl) {\n <div class=\"mt-8 p-6 bg-gray-50 rounded-lg\">\n <h3 class=\"text-xl font-semibold text-gray-700 mb-4\">K\u1EBFt qu\u1EA3</h3>\n <div class=\"max-w-xl mx-auto mb-4 border border-gray-200 rounded-lg overflow-hidden shadow-md\">\n <img\n [src]=\"editedImageUrl\"\n alt=\"H\u00ECnh \u1EA3nh \u0111\u00E3 ch\u1EC9nh s\u1EEDa\"\n class=\"w-full h-auto\" />\n </div>\n <div class=\"flex justify-center\">\n <libs_ui-components-buttons-button\n label=\"T\u1EA3i xu\u1ED1ng h\u00ECnh \u1EA3nh\"\n (click)=\"downloadImage()\"></libs_ui-components-buttons-button>\n </div>\n </div>\n }\n </div>\n </section>\n\n @if (showEditor) {\n <div class=\"fixed inset-0 z-50\">\n <libs_ui-components-image_editor\n [(imgSrc)]=\"imageSource\"\n [nameFile]=\"imageName || 'demo-image.jpg'\"\n [modeShowButton]=\"'save-file'\"\n [hasZoom]=\"true\"\n (outSaveFile)=\"onSaveImage($event)\"\n (outClose)=\"onCloseEditor($event)\"\n (outFunctionsControl)=\"handleFunctionControl($event)\" />\n </div>\n }\n\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold text-gray-800 mb-5 pb-3 border-b border-gray-200\">C\u00E1ch s\u1EED d\u1EE5ng</h2>\n\n <div class=\"mb-8\">\n <h3 class=\"text-xl font-semibold text-gray-700 mb-4\">C\u00E1ch 1: S\u1EED d\u1EE5ng file HTML ri\u00EAng bi\u1EC7t</h3>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6 mb-4\">\n <div>\n <h4 class=\"font-semibold mb-2 text-gray-700\">HTML (example.component.html)</h4>\n <pre class=\"bg-gray-100 p-4 rounded-lg overflow-x-auto text-sm\"><code>&lt;libs_ui-components-image_editor\n [(imgSrc)]=\"imageSource\"\n [nameFile]=\"'image.jpg'\"\n [modeShowButton]=\"'save-file'\"\n (outSaveFile)=\"onSaveFile($event)\"\n (outClose)=\"onClose($event)\"&gt;\n&lt;/libs_ui-components-image_editor&gt;</code></pre>\n </div>\n\n <div>\n <h4 class=\"font-semibold mb-2 text-gray-700\">TypeScript (example.component.ts)</h4>\n <pre class=\"bg-gray-100 p-4 rounded-lg overflow-x-auto text-sm\"><code>import &#123; Component &#125; from '&#64;angular/core';\nimport &#123; LibsUiComponentsImageEditorComponent &#125; from '&#64;libs-ui/components-image-editor';\n\n&#64;Component(&#123;\n selector: 'app-example',\n standalone: true,\n imports: [LibsUiComponentsImageEditorComponent],\n templateUrl: './example.component.html'\n&#125;)\nexport class ExampleComponent &#123;\n imageSource = 'https://example.com/path/to/image.jpg';\n\n onSaveFile(data: &#123;file: Blob, url: string, mode: string&#125;) &#123;\n console.log('File \u0111\u00E3 \u0111\u01B0\u1EE3c l\u01B0u:', data);\n &#125;\n\n onClose(data: &#123;isClickButtonClose: boolean&#125;) &#123;\n console.log('\u0110\u00E3 \u0111\u00F3ng tr\u00ECnh ch\u1EC9nh s\u1EEDa \u1EA3nh');\n &#125;\n&#125;</code></pre>\n </div>\n </div>\n </div>\n\n <div class=\"mb-8\">\n <h3 class=\"text-xl font-semibold text-gray-700 mb-4\">C\u00E1ch 2: V\u1EDBi tr\u00ECnh ch\u1EC9nh s\u1EEDa c\u00F3 th\u1EC3 \u0111\u00F3ng/m\u1EDF</h3>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6 mb-4\">\n <div>\n <h4 class=\"font-semibold mb-2 text-gray-700\">HTML (toggle-editor.component.html)</h4>\n <pre class=\"bg-gray-100 p-4 rounded-lg overflow-x-auto text-sm\"><code>&lt;button (click)=\"openEditor()\" class=\"px-4 py-2 bg-blue-500 text-white rounded\"&gt;\n M\u1EDF tr\u00ECnh ch\u1EC9nh s\u1EEDa \u1EA3nh\n&lt;/button&gt;\n\n&#64;if (showEditor) &#123;\n &lt;libs_ui-components-image_editor\n [(imgSrc)]=\"imageSource\"\n [nameFile]=\"'my-image.jpg'\"\n (outSaveFile)=\"onSaveFile($event)\"\n (outClose)=\"onCloseEditor($event)\"&gt;\n &lt;/libs_ui-components-image_editor&gt;\n&#125;\n\n&#64;if (editedImageUrl) &#123;\n &lt;div class=\"mt-4\"&gt;\n &lt;h3 class=\"text-lg font-semibold\"&gt;H\u00ECnh \u1EA3nh \u0111\u00E3 ch\u1EC9nh s\u1EEDa:&lt;/h3&gt;\n &lt;img [src]=\"editedImageUrl\" alt=\"H\u00ECnh \u1EA3nh \u0111\u00E3 ch\u1EC9nh s\u1EEDa\" class=\"mt-2 max-w-full h-auto\" /&gt;\n &lt;/div&gt;\n&#125;</code></pre>\n </div>\n\n <div>\n <h4 class=\"font-semibold mb-2 text-gray-700\">TypeScript (toggle-editor.component.ts)</h4>\n <pre class=\"bg-gray-100 p-4 rounded-lg overflow-x-auto text-sm\"><code>import &#123; Component &#125; from '&#64;angular/core';\nimport &#123; LibsUiComponentsImageEditorComponent &#125; from '&#64;libs-ui/components-image-editor';\nimport &#123; ISaveFile &#125; from '&#64;libs-ui/components-image-editor';\n\n&#64;Component(&#123;\n selector: 'app-toggle-editor',\n standalone: true,\n imports: [LibsUiComponentsImageEditorComponent],\n templateUrl: './toggle-editor.component.html'\n&#125;)\nexport class ToggleEditorComponent &#123;\n imageSource = 'https://example.com/path/to/image.jpg';\n showEditor = false;\n editedImageUrl = '';\n\n openEditor() &#123;\n this.showEditor = true;\n &#125;\n\n onSaveFile(data: ISaveFile) &#123;\n this.editedImageUrl = data.url;\n this.showEditor = false;\n console.log('\u0110\u00E3 l\u01B0u file:', data.file);\n &#125;\n\n onCloseEditor(data: &#123;isClickButtonClose: boolean&#125;) &#123;\n this.showEditor = false;\n &#125;\n&#125;</code></pre>\n </div>\n </div>\n </div>\n\n <div class=\"mb-8\">\n <h3 class=\"text-xl font-semibold text-gray-700 mb-4\">C\u00E1ch 3: V\u1EDBi t\u1EF7 l\u1EC7 khung h\u00ECnh c\u1ED1 \u0111\u1ECBnh</h3>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6 mb-4\">\n <div>\n <h4 class=\"font-semibold mb-2 text-gray-700\">HTML (fixed-ratio.component.html)</h4>\n <pre class=\"bg-gray-100 p-4 rounded-lg overflow-x-auto text-sm\"><code>&lt;libs_ui-components-image_editor\n [(imgSrc)]=\"imageSource\"\n [aspectRatio]=\"aspectRatio\"\n [requiredCropFollowRatio]=\"true\"\n (outSaveFile)=\"onSaveFile($event)\"&gt;\n&lt;/libs_ui-components-image_editor&gt;</code></pre>\n </div>\n\n <div>\n <h4 class=\"font-semibold mb-2 text-gray-700\">TypeScript (fixed-ratio.component.ts)</h4>\n <pre class=\"bg-gray-100 p-4 rounded-lg overflow-x-auto text-sm\"><code>import &#123; Component &#125; from '&#64;angular/core';\nimport &#123; LibsUiComponentsImageEditorComponent &#125; from '&#64;libs-ui/components-image-editor';\nimport &#123; IAspectRatio &#125; from '&#64;libs-ui/interfaces-types';\n\n&#64;Component(&#123;\n selector: 'app-fixed-ratio',\n standalone: true,\n imports: [LibsUiComponentsImageEditorComponent],\n templateUrl: './fixed-ratio.component.html'\n&#125;)\nexport class FixedRatioComponent &#123;\n imageSource = 'https://example.com/path/to/image.jpg';\n aspectRatio: IAspectRatio = &#123;\n key: '1:1',\n value: 1\n &#125;;\n\n onSaveFile(data: any) &#123;\n console.log('\u0110\u00E3 l\u01B0u h\u00ECnh \u1EA3nh v\u1EDBi t\u1EF7 l\u1EC7 1:1');\n &#125;\n&#125;</code></pre>\n </div>\n </div>\n </div>\n </section>\n\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold text-gray-800 mb-5 pb-3 border-b border-gray-200\">T\u00E0i li\u1EC7u API</h2>\n\n <h3 class=\"text-xl font-semibold text-gray-700 mb-4\">Inputs</h3>\n <div class=\"overflow-x-auto mb-8\">\n <table class=\"min-w-full bg-white border border-gray-200\">\n <thead>\n <tr>\n <th class=\"py-3 px-4 border-b border-gray-200 bg-gray-100 text-left font-semibold text-gray-700\">T\u00EAn</th>\n <th class=\"py-3 px-4 border-b border-gray-200 bg-gray-100 text-left font-semibold text-gray-700\">Ki\u1EC3u d\u1EEF li\u1EC7u</th>\n <th class=\"py-3 px-4 border-b border-gray-200 bg-gray-100 text-left font-semibold text-gray-700\">M\u1EB7c \u0111\u1ECBnh</th>\n <th class=\"py-3 px-4 border-b border-gray-200 bg-gray-100 text-left font-semibold text-gray-700\">M\u00F4 t\u1EA3</th>\n </tr>\n </thead>\n <tbody>\n @for (input of inputsDoc; track input.name) {\n <tr>\n <td class=\"py-2 px-4 border-b border-gray-200\">\n <code class=\"text-sm bg-gray-100 px-1.5 py-0.5 rounded\">{{ input.name }}</code>\n </td>\n <td class=\"py-2 px-4 border-b border-gray-200\">\n <code class=\"text-sm bg-gray-100 px-1.5 py-0.5 rounded\">{{ input.type }}</code>\n </td>\n <td class=\"py-2 px-4 border-b border-gray-200\">{{ input.default }}</td>\n <td class=\"py-2 px-4 border-b border-gray-200\">{{ input.description }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <h3 class=\"text-xl font-semibold text-gray-700 mb-4\">Outputs</h3>\n <div class=\"overflow-x-auto mb-8\">\n <table class=\"min-w-full bg-white border border-gray-200\">\n <thead>\n <tr>\n <th class=\"py-3 px-4 border-b border-gray-200 bg-gray-100 text-left font-semibold text-gray-700\">T\u00EAn</th>\n <th class=\"py-3 px-4 border-b border-gray-200 bg-gray-100 text-left font-semibold text-gray-700\">Ki\u1EC3u d\u1EEF li\u1EC7u</th>\n <th class=\"py-3 px-4 border-b border-gray-200 bg-gray-100 text-left font-semibold text-gray-700\">M\u00F4 t\u1EA3</th>\n </tr>\n </thead>\n <tbody>\n @for (output of outputsDoc; track output.name) {\n <tr>\n <td class=\"py-2 px-4 border-b border-gray-200\">\n <code class=\"text-sm bg-gray-100 px-1.5 py-0.5 rounded\">{{ output.name }}</code>\n </td>\n <td class=\"py-2 px-4 border-b border-gray-200\">\n <code class=\"text-sm bg-gray-100 px-1.5 py-0.5 rounded\">{{ output.type }}</code>\n </td>\n <td class=\"py-2 px-4 border-b border-gray-200\">{{ output.description }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <h3 class=\"text-xl font-semibold text-gray-700 mb-4\">Interfaces</h3>\n <div class=\"space-y-6\">\n @for (interfaceItem of interfacesDoc; track interfaceItem.name) {\n <div class=\"bg-gray-50 p-6 rounded-lg\">\n <h4 class=\"text-lg font-semibold text-gray-700 mb-3\">{{ interfaceItem.name }}</h4>\n <pre class=\"bg-gray-100 p-4 rounded-lg overflow-x-auto text-sm mb-3\"><code>{{ interfaceItem.code }}</code></pre>\n <p class=\"text-gray-600\">{{ interfaceItem.description }}</p>\n </div>\n }\n </div>\n </section>\n\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold text-gray-800 mb-5 pb-3 border-b border-gray-200\">T\u00EDnh n\u0103ng</h2>\n <ul class=\"space-y-6\">\n @for (feature of features; track feature.id) {\n <li class=\"flex items-start\">\n <span class=\"text-2xl text-blue-500\">{{ feature.icon }}</span>\n <div>\n <h3 class=\"text-lg font-semibold text-gray-700 mb-1\">{{ feature.title }}</h3>\n <p class=\"text-gray-600\">{{ feature.description }}</p>\n </div>\n </li>\n }\n </ul>\n </section>\n </main>\n</div>\n", styles: ["pre code{@apply font-mono;}.hover\\:bg-blue-600{transition:background-color .2s ease-in-out}.overflow-x-auto{-webkit-overflow-scrolling:touch}pre::-webkit-scrollbar{@apply h-1.5 bg-gray-100;}pre::-webkit-scrollbar-thumb{@apply bg-gray-300 rounded;}button:focus,input:focus{@apply outline-none ring-2 ring-blue-400 ring-opacity-50;}input[type=file]{@apply text-sm text-gray-600;}\n"], dependencies: [{ kind: "component", type: LibsUiComponentsButtonsButtonComponent, selector: "libs_ui-components-buttons-button", inputs: ["flagMouse", "type", "buttonCustom", "sizeButton", "label", "disable", "isPending", "imageLeft", "classInclude", "classIconLeft", "classIconRight", "classLabel", "iconOnlyType", "popover", "ignoreStopPropagationEvent", "zIndex", "widthLabelPopover", "styleIconLeft", "styleButton", "ignoreFocusWhenInputTab", "ignoreSetClickWhenShowPopover", "ignorePointerEvent", "isActive", "isHandlerEnterDocumentClickButton"], outputs: ["outClick", "outPopoverEvent", "outFunctionsControl"] }, { kind: "component", type: LibsUiComponentsImageEditorComponent, selector: "libs_ui-components-image_editor", inputs: ["modeShowButton", "mimetype", "zIndex", "imgSrc", "originUrl", "nameFile", "hasZoom", "aspectRatio", "requiredCropFollowRatio"], outputs: ["imgSrcChange", "outClose", "outSaveFile", "outFunctionsControl"] }] });
1146
+ }
1147
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsImageEditorDemoComponent, decorators: [{
1148
+ type: Component,
1149
+ args: [{ selector: 'lib-image-editor-demo', standalone: true, imports: [LibsUiComponentsButtonsButtonComponent, LibsUiComponentsImageEditorComponent], template: "<div class=\"max-w-6xl mx-auto p-5 font-sans text-gray-800\">\n <header class=\"text-center py-10 bg-white rounded-lg mb-8 shadow-sm\">\n <h1 class=\"text-4xl font-bold text-gray-800 mb-2\">Demo Tr\u00ECnh Ch\u1EC9nh S\u1EEDa H\u00ECnh \u1EA2nh</h1>\n <p class=\"text-xl text-gray-500\">Th\u01B0 vi\u1EC7n component cho Angular \u0111\u1EC3 ch\u1EC9nh s\u1EEDa h\u00ECnh \u1EA3nh</p>\n </header>\n\n <main>\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold text-gray-800 mb-5 pb-3 border-b border-gray-200\">Gi\u1EDBi thi\u1EC7u</h2>\n <p>\n <code class=\"text-sm bg-gray-100 px-1.5 py-0.5 rounded\">&#64;libs-ui/components-image-editor</code>\n l\u00E0 m\u1ED9t component Angular m\u1EA1nh m\u1EBD cho ph\u00E9p ng\u01B0\u1EDDi d\u00F9ng th\u1EF1c hi\u1EC7n c\u00E1c thao t\u00E1c ch\u1EC9nh s\u1EEDa h\u00ECnh \u1EA3nh nh\u01B0 c\u1EAFt, thay \u0111\u1ED5i k\u00EDch th\u01B0\u1EDBc, xoay, l\u1EADt v\u00E0 \u00E1p d\u1EE5ng t\u1EF7 l\u1EC7 c\u1ED1 \u0111\u1ECBnh.\n </p>\n </section>\n\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold text-gray-800 mb-5 pb-3 border-b border-gray-200\">C\u00E0i \u0111\u1EB7t</h2>\n\n <div class=\"mb-6\">\n <h3 class=\"text-xl font-semibold text-gray-700 mb-3\">Y\u00EAu c\u1EA7u</h3>\n <ul class=\"list-disc pl-5 space-y-2 text-gray-600\">\n <li>\n <span class=\"font-semibold\">Angular</span>\n : 18.0.0 tr\u1EDF l\u00EAn\n </li>\n <li>\n <span class=\"font-semibold\">Tailwind CSS</span>\n : 3.3.0 tr\u1EDF l\u00EAn\n </li>\n </ul>\n </div>\n\n <p class=\"mb-4\">\u0110\u1EC3 c\u00E0i \u0111\u1EB7t th\u01B0 vi\u1EC7n, s\u1EED d\u1EE5ng npm ho\u1EB7c yarn:</p>\n\n <div class=\"flex items-center bg-gray-100 p-4 rounded-lg mb-6\">\n <pre class=\"flex-1 text-sm overflow-x-auto\"><code>npm install &#64;libs-ui/components-image-editor</code></pre>\n <button\n class=\"ml-4 px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors\"\n (click)=\"copyToClipboard('npm install @libs-ui/components-image-editor')\">\n Sao ch\u00E9p\n </button>\n </div>\n\n <p class=\"mb-4\">Ho\u1EB7c v\u1EDBi yarn:</p>\n\n <div class=\"flex items-center bg-gray-100 p-4 rounded-lg mb-6\">\n <pre class=\"flex-1 text-sm overflow-x-auto\"><code>yarn add &#64;libs-ui/components-image-editor</code></pre>\n <button\n class=\"ml-4 px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors\"\n (click)=\"copyToClipboard('yarn add @libs-ui/components-image-editor')\">\n Sao ch\u00E9p\n </button>\n </div>\n </section>\n\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold text-gray-800 mb-5 pb-3 border-b border-gray-200\">Demo tr\u1EF1c ti\u1EBFp</h2>\n <div>\n <p class=\"mb-4\">T\u1EA3i l\u00EAn h\u00ECnh \u1EA3nh v\u00E0 ch\u1EC9nh s\u1EEDa ngay tr\u00EAn tr\u00ECnh duy\u1EC7t:</p>\n <div class=\"flex flex-wrap items-center gap-4 mb-8\">\n <input\n type=\"file\"\n #imageFileInput\n accept=\"image/*\"\n class=\"border border-gray-300 rounded px-3 py-2\"\n (change)=\"onFileSelected($event)\" />\n <libs_ui-components-buttons-button\n label=\"T\u1EA3i l\u00EAn v\u00E0 ch\u1EC9nh s\u1EEDa\"\n (click)=\"uploadAndEdit()\"></libs_ui-components-buttons-button>\n </div>\n\n @if (editedImageUrl) {\n <div class=\"mt-8 p-6 bg-gray-50 rounded-lg\">\n <h3 class=\"text-xl font-semibold text-gray-700 mb-4\">K\u1EBFt qu\u1EA3</h3>\n <div class=\"max-w-xl mx-auto mb-4 border border-gray-200 rounded-lg overflow-hidden shadow-md\">\n <img\n [src]=\"editedImageUrl\"\n alt=\"H\u00ECnh \u1EA3nh \u0111\u00E3 ch\u1EC9nh s\u1EEDa\"\n class=\"w-full h-auto\" />\n </div>\n <div class=\"flex justify-center\">\n <libs_ui-components-buttons-button\n label=\"T\u1EA3i xu\u1ED1ng h\u00ECnh \u1EA3nh\"\n (click)=\"downloadImage()\"></libs_ui-components-buttons-button>\n </div>\n </div>\n }\n </div>\n </section>\n\n @if (showEditor) {\n <div class=\"fixed inset-0 z-50\">\n <libs_ui-components-image_editor\n [(imgSrc)]=\"imageSource\"\n [nameFile]=\"imageName || 'demo-image.jpg'\"\n [modeShowButton]=\"'save-file'\"\n [hasZoom]=\"true\"\n (outSaveFile)=\"onSaveImage($event)\"\n (outClose)=\"onCloseEditor($event)\"\n (outFunctionsControl)=\"handleFunctionControl($event)\" />\n </div>\n }\n\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold text-gray-800 mb-5 pb-3 border-b border-gray-200\">C\u00E1ch s\u1EED d\u1EE5ng</h2>\n\n <div class=\"mb-8\">\n <h3 class=\"text-xl font-semibold text-gray-700 mb-4\">C\u00E1ch 1: S\u1EED d\u1EE5ng file HTML ri\u00EAng bi\u1EC7t</h3>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6 mb-4\">\n <div>\n <h4 class=\"font-semibold mb-2 text-gray-700\">HTML (example.component.html)</h4>\n <pre class=\"bg-gray-100 p-4 rounded-lg overflow-x-auto text-sm\"><code>&lt;libs_ui-components-image_editor\n [(imgSrc)]=\"imageSource\"\n [nameFile]=\"'image.jpg'\"\n [modeShowButton]=\"'save-file'\"\n (outSaveFile)=\"onSaveFile($event)\"\n (outClose)=\"onClose($event)\"&gt;\n&lt;/libs_ui-components-image_editor&gt;</code></pre>\n </div>\n\n <div>\n <h4 class=\"font-semibold mb-2 text-gray-700\">TypeScript (example.component.ts)</h4>\n <pre class=\"bg-gray-100 p-4 rounded-lg overflow-x-auto text-sm\"><code>import &#123; Component &#125; from '&#64;angular/core';\nimport &#123; LibsUiComponentsImageEditorComponent &#125; from '&#64;libs-ui/components-image-editor';\n\n&#64;Component(&#123;\n selector: 'app-example',\n standalone: true,\n imports: [LibsUiComponentsImageEditorComponent],\n templateUrl: './example.component.html'\n&#125;)\nexport class ExampleComponent &#123;\n imageSource = 'https://example.com/path/to/image.jpg';\n\n onSaveFile(data: &#123;file: Blob, url: string, mode: string&#125;) &#123;\n console.log('File \u0111\u00E3 \u0111\u01B0\u1EE3c l\u01B0u:', data);\n &#125;\n\n onClose(data: &#123;isClickButtonClose: boolean&#125;) &#123;\n console.log('\u0110\u00E3 \u0111\u00F3ng tr\u00ECnh ch\u1EC9nh s\u1EEDa \u1EA3nh');\n &#125;\n&#125;</code></pre>\n </div>\n </div>\n </div>\n\n <div class=\"mb-8\">\n <h3 class=\"text-xl font-semibold text-gray-700 mb-4\">C\u00E1ch 2: V\u1EDBi tr\u00ECnh ch\u1EC9nh s\u1EEDa c\u00F3 th\u1EC3 \u0111\u00F3ng/m\u1EDF</h3>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6 mb-4\">\n <div>\n <h4 class=\"font-semibold mb-2 text-gray-700\">HTML (toggle-editor.component.html)</h4>\n <pre class=\"bg-gray-100 p-4 rounded-lg overflow-x-auto text-sm\"><code>&lt;button (click)=\"openEditor()\" class=\"px-4 py-2 bg-blue-500 text-white rounded\"&gt;\n M\u1EDF tr\u00ECnh ch\u1EC9nh s\u1EEDa \u1EA3nh\n&lt;/button&gt;\n\n&#64;if (showEditor) &#123;\n &lt;libs_ui-components-image_editor\n [(imgSrc)]=\"imageSource\"\n [nameFile]=\"'my-image.jpg'\"\n (outSaveFile)=\"onSaveFile($event)\"\n (outClose)=\"onCloseEditor($event)\"&gt;\n &lt;/libs_ui-components-image_editor&gt;\n&#125;\n\n&#64;if (editedImageUrl) &#123;\n &lt;div class=\"mt-4\"&gt;\n &lt;h3 class=\"text-lg font-semibold\"&gt;H\u00ECnh \u1EA3nh \u0111\u00E3 ch\u1EC9nh s\u1EEDa:&lt;/h3&gt;\n &lt;img [src]=\"editedImageUrl\" alt=\"H\u00ECnh \u1EA3nh \u0111\u00E3 ch\u1EC9nh s\u1EEDa\" class=\"mt-2 max-w-full h-auto\" /&gt;\n &lt;/div&gt;\n&#125;</code></pre>\n </div>\n\n <div>\n <h4 class=\"font-semibold mb-2 text-gray-700\">TypeScript (toggle-editor.component.ts)</h4>\n <pre class=\"bg-gray-100 p-4 rounded-lg overflow-x-auto text-sm\"><code>import &#123; Component &#125; from '&#64;angular/core';\nimport &#123; LibsUiComponentsImageEditorComponent &#125; from '&#64;libs-ui/components-image-editor';\nimport &#123; ISaveFile &#125; from '&#64;libs-ui/components-image-editor';\n\n&#64;Component(&#123;\n selector: 'app-toggle-editor',\n standalone: true,\n imports: [LibsUiComponentsImageEditorComponent],\n templateUrl: './toggle-editor.component.html'\n&#125;)\nexport class ToggleEditorComponent &#123;\n imageSource = 'https://example.com/path/to/image.jpg';\n showEditor = false;\n editedImageUrl = '';\n\n openEditor() &#123;\n this.showEditor = true;\n &#125;\n\n onSaveFile(data: ISaveFile) &#123;\n this.editedImageUrl = data.url;\n this.showEditor = false;\n console.log('\u0110\u00E3 l\u01B0u file:', data.file);\n &#125;\n\n onCloseEditor(data: &#123;isClickButtonClose: boolean&#125;) &#123;\n this.showEditor = false;\n &#125;\n&#125;</code></pre>\n </div>\n </div>\n </div>\n\n <div class=\"mb-8\">\n <h3 class=\"text-xl font-semibold text-gray-700 mb-4\">C\u00E1ch 3: V\u1EDBi t\u1EF7 l\u1EC7 khung h\u00ECnh c\u1ED1 \u0111\u1ECBnh</h3>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6 mb-4\">\n <div>\n <h4 class=\"font-semibold mb-2 text-gray-700\">HTML (fixed-ratio.component.html)</h4>\n <pre class=\"bg-gray-100 p-4 rounded-lg overflow-x-auto text-sm\"><code>&lt;libs_ui-components-image_editor\n [(imgSrc)]=\"imageSource\"\n [aspectRatio]=\"aspectRatio\"\n [requiredCropFollowRatio]=\"true\"\n (outSaveFile)=\"onSaveFile($event)\"&gt;\n&lt;/libs_ui-components-image_editor&gt;</code></pre>\n </div>\n\n <div>\n <h4 class=\"font-semibold mb-2 text-gray-700\">TypeScript (fixed-ratio.component.ts)</h4>\n <pre class=\"bg-gray-100 p-4 rounded-lg overflow-x-auto text-sm\"><code>import &#123; Component &#125; from '&#64;angular/core';\nimport &#123; LibsUiComponentsImageEditorComponent &#125; from '&#64;libs-ui/components-image-editor';\nimport &#123; IAspectRatio &#125; from '&#64;libs-ui/interfaces-types';\n\n&#64;Component(&#123;\n selector: 'app-fixed-ratio',\n standalone: true,\n imports: [LibsUiComponentsImageEditorComponent],\n templateUrl: './fixed-ratio.component.html'\n&#125;)\nexport class FixedRatioComponent &#123;\n imageSource = 'https://example.com/path/to/image.jpg';\n aspectRatio: IAspectRatio = &#123;\n key: '1:1',\n value: 1\n &#125;;\n\n onSaveFile(data: any) &#123;\n console.log('\u0110\u00E3 l\u01B0u h\u00ECnh \u1EA3nh v\u1EDBi t\u1EF7 l\u1EC7 1:1');\n &#125;\n&#125;</code></pre>\n </div>\n </div>\n </div>\n </section>\n\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold text-gray-800 mb-5 pb-3 border-b border-gray-200\">T\u00E0i li\u1EC7u API</h2>\n\n <h3 class=\"text-xl font-semibold text-gray-700 mb-4\">Inputs</h3>\n <div class=\"overflow-x-auto mb-8\">\n <table class=\"min-w-full bg-white border border-gray-200\">\n <thead>\n <tr>\n <th class=\"py-3 px-4 border-b border-gray-200 bg-gray-100 text-left font-semibold text-gray-700\">T\u00EAn</th>\n <th class=\"py-3 px-4 border-b border-gray-200 bg-gray-100 text-left font-semibold text-gray-700\">Ki\u1EC3u d\u1EEF li\u1EC7u</th>\n <th class=\"py-3 px-4 border-b border-gray-200 bg-gray-100 text-left font-semibold text-gray-700\">M\u1EB7c \u0111\u1ECBnh</th>\n <th class=\"py-3 px-4 border-b border-gray-200 bg-gray-100 text-left font-semibold text-gray-700\">M\u00F4 t\u1EA3</th>\n </tr>\n </thead>\n <tbody>\n @for (input of inputsDoc; track input.name) {\n <tr>\n <td class=\"py-2 px-4 border-b border-gray-200\">\n <code class=\"text-sm bg-gray-100 px-1.5 py-0.5 rounded\">{{ input.name }}</code>\n </td>\n <td class=\"py-2 px-4 border-b border-gray-200\">\n <code class=\"text-sm bg-gray-100 px-1.5 py-0.5 rounded\">{{ input.type }}</code>\n </td>\n <td class=\"py-2 px-4 border-b border-gray-200\">{{ input.default }}</td>\n <td class=\"py-2 px-4 border-b border-gray-200\">{{ input.description }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <h3 class=\"text-xl font-semibold text-gray-700 mb-4\">Outputs</h3>\n <div class=\"overflow-x-auto mb-8\">\n <table class=\"min-w-full bg-white border border-gray-200\">\n <thead>\n <tr>\n <th class=\"py-3 px-4 border-b border-gray-200 bg-gray-100 text-left font-semibold text-gray-700\">T\u00EAn</th>\n <th class=\"py-3 px-4 border-b border-gray-200 bg-gray-100 text-left font-semibold text-gray-700\">Ki\u1EC3u d\u1EEF li\u1EC7u</th>\n <th class=\"py-3 px-4 border-b border-gray-200 bg-gray-100 text-left font-semibold text-gray-700\">M\u00F4 t\u1EA3</th>\n </tr>\n </thead>\n <tbody>\n @for (output of outputsDoc; track output.name) {\n <tr>\n <td class=\"py-2 px-4 border-b border-gray-200\">\n <code class=\"text-sm bg-gray-100 px-1.5 py-0.5 rounded\">{{ output.name }}</code>\n </td>\n <td class=\"py-2 px-4 border-b border-gray-200\">\n <code class=\"text-sm bg-gray-100 px-1.5 py-0.5 rounded\">{{ output.type }}</code>\n </td>\n <td class=\"py-2 px-4 border-b border-gray-200\">{{ output.description }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <h3 class=\"text-xl font-semibold text-gray-700 mb-4\">Interfaces</h3>\n <div class=\"space-y-6\">\n @for (interfaceItem of interfacesDoc; track interfaceItem.name) {\n <div class=\"bg-gray-50 p-6 rounded-lg\">\n <h4 class=\"text-lg font-semibold text-gray-700 mb-3\">{{ interfaceItem.name }}</h4>\n <pre class=\"bg-gray-100 p-4 rounded-lg overflow-x-auto text-sm mb-3\"><code>{{ interfaceItem.code }}</code></pre>\n <p class=\"text-gray-600\">{{ interfaceItem.description }}</p>\n </div>\n }\n </div>\n </section>\n\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold text-gray-800 mb-5 pb-3 border-b border-gray-200\">T\u00EDnh n\u0103ng</h2>\n <ul class=\"space-y-6\">\n @for (feature of features; track feature.id) {\n <li class=\"flex items-start\">\n <span class=\"text-2xl text-blue-500\">{{ feature.icon }}</span>\n <div>\n <h3 class=\"text-lg font-semibold text-gray-700 mb-1\">{{ feature.title }}</h3>\n <p class=\"text-gray-600\">{{ feature.description }}</p>\n </div>\n </li>\n }\n </ul>\n </section>\n </main>\n</div>\n", styles: ["pre code{@apply font-mono;}.hover\\:bg-blue-600{transition:background-color .2s ease-in-out}.overflow-x-auto{-webkit-overflow-scrolling:touch}pre::-webkit-scrollbar{@apply h-1.5 bg-gray-100;}pre::-webkit-scrollbar-thumb{@apply bg-gray-300 rounded;}button:focus,input:focus{@apply outline-none ring-2 ring-blue-400 ring-opacity-50;}input[type=file]{@apply text-sm text-gray-600;}\n"] }]
1150
+ }], propDecorators: { imageFileInput: [{
1151
+ type: ViewChild,
1152
+ args: ['imageFileInput']
1153
+ }] } });
1154
+
1155
+ /**
1156
+ * Generated bundle index. Do not edit.
1157
+ */
1158
+
1159
+ export { LibsUiComponentsImageEditorComponent, LibsUiComponentsImageEditorDemoComponent, LibsUiComponentsImageEditorResizeComponent };
1160
+ //# sourceMappingURL=libs-ui-components-image-editor.mjs.map