@rdlabo/ionic-angular-photo-editor 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # PhotoEditor
2
+
3
+ This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.0.0.
4
+
5
+ ## Code scaffolding
6
+
7
+ Run `ng generate component component-name --project photo-editor` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project photo-editor`.
8
+ > Note: Don't forget to add `--project photo-editor` or else it will be added to the default project in your `angular.json` file.
9
+
10
+ ## Build
11
+
12
+ Run `ng build photo-editor` to build the project. The build artifacts will be stored in the `dist/` directory.
13
+
14
+ ## Publishing
15
+
16
+ After building your library with `ng build photo-editor`, go to the dist folder `cd dist/photo-editor` and run `npm publish`.
17
+
18
+ ## Running unit tests
19
+
20
+ Run `ng test photo-editor` to execute the unit tests via [Karma](https://karma-runner.github.io).
21
+
22
+ ## Further help
23
+
24
+ To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
@@ -0,0 +1,17 @@
1
+ export const dictionary = () => ({
2
+ // UI labels
3
+ save: '保存',
4
+ crop: '切り抜き・回転',
5
+ filter: 'フィルター',
6
+ brightness: '明るさ',
7
+ // Filter labels
8
+ original: 'オリジナル',
9
+ invert: '反転',
10
+ sepia: 'セピア',
11
+ vintage: 'ヴィンテージ',
12
+ blur: 'ぼかし',
13
+ grayscale: 'グレースケール',
14
+ sharpen: '輪郭',
15
+ emboss: 'エンボス',
16
+ });
17
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGljdGlvbmFyeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3Byb2plY3RzL3Bob3RvLWVkaXRvci9zcmMvbGliL2RpY3Rpb25hcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHLEdBQWdCLEVBQUUsQ0FBQyxDQUFDO0lBQzVDLFlBQVk7SUFDWixJQUFJLEVBQUUsSUFBSTtJQUNWLElBQUksRUFBRSxTQUFTO0lBQ2YsTUFBTSxFQUFFLE9BQU87SUFDZixVQUFVLEVBQUUsS0FBSztJQUVqQixnQkFBZ0I7SUFDaEIsUUFBUSxFQUFFLE9BQU87SUFDakIsTUFBTSxFQUFFLElBQUk7SUFDWixLQUFLLEVBQUUsS0FBSztJQUNaLE9BQU8sRUFBRSxRQUFRO0lBQ2pCLElBQUksRUFBRSxLQUFLO0lBQ1gsU0FBUyxFQUFFLFNBQVM7SUFDcEIsT0FBTyxFQUFFLElBQUk7SUFDYixNQUFNLEVBQUUsTUFBTTtDQUNmLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IElEaWN0aW9uYXJ5IH0gZnJvbSAnLi90eXBlcyc7XG5cbmV4cG9ydCBjb25zdCBkaWN0aW9uYXJ5ID0gKCk6IElEaWN0aW9uYXJ5ID0+ICh7XG4gIC8vIFVJIGxhYmVsc1xuICBzYXZlOiAn5L+d5a2YJyxcbiAgY3JvcDogJ+WIh+OCiuaKnOOBjeODu+Wbnui7oicsXG4gIGZpbHRlcjogJ+ODleOCo+ODq+OCv+ODvCcsXG4gIGJyaWdodG5lc3M6ICfmmI7jgovjgZUnLFxuXG4gIC8vIEZpbHRlciBsYWJlbHNcbiAgb3JpZ2luYWw6ICfjgqrjg6rjgrjjg4rjg6snLFxuICBpbnZlcnQ6ICflj43ou6InLFxuICBzZXBpYTogJ+OCu+ODlOOCoicsXG4gIHZpbnRhZ2U6ICfjg7TjgqPjg7Pjg4bjg7zjgrgnLFxuICBibHVyOiAn44G844GL44GXJyxcbiAgZ3JheXNjYWxlOiAn44Kw44Os44O844K544Kx44O844OrJyxcbiAgc2hhcnBlbjogJ+i8qumDrScsXG4gIGVtYm9zczogJ+OCqOODs+ODnOOCuScsXG59KTtcbiJdfQ==
@@ -0,0 +1,43 @@
1
+ export const filterPreset = (dictionary) => [
2
+ {
3
+ name: dictionary.original,
4
+ type: 'Default',
5
+ option: null,
6
+ },
7
+ // {
8
+ // name: dictionary.invert,
9
+ // type: 'Invert',
10
+ // option: null,
11
+ // },
12
+ {
13
+ name: dictionary.sepia,
14
+ type: 'Sepia',
15
+ option: null,
16
+ },
17
+ {
18
+ name: dictionary.vintage,
19
+ type: 'vintage',
20
+ option: null,
21
+ },
22
+ {
23
+ name: 'ぼかし',
24
+ type: 'Blur',
25
+ option: { blur: 0.1 },
26
+ },
27
+ {
28
+ name: dictionary.grayscale,
29
+ type: 'Grayscale',
30
+ option: null,
31
+ },
32
+ // {
33
+ // name: dictionary.sharpen,
34
+ // type: 'Sharpen',
35
+ // option: null,
36
+ // },
37
+ // {
38
+ // name: dictionary.emboss,
39
+ // type: 'Emboss',
40
+ // option: null,
41
+ // },
42
+ ];
43
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmlsdGVyLXByZXNldC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3Byb2plY3RzL3Bob3RvLWVkaXRvci9zcmMvbGliL2ZpbHRlci1wcmVzZXQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsTUFBTSxDQUFDLE1BQU0sWUFBWSxHQUFHLENBQUMsVUFBdUIsRUFBbUIsRUFBRSxDQUFDO0lBQ3hFO1FBQ0UsSUFBSSxFQUFFLFVBQVUsQ0FBQyxRQUFRO1FBQ3pCLElBQUksRUFBRSxTQUFTO1FBQ2YsTUFBTSxFQUFFLElBQUk7S0FDYjtJQUNELElBQUk7SUFDSiw2QkFBNkI7SUFDN0Isb0JBQW9CO0lBQ3BCLGtCQUFrQjtJQUNsQixLQUFLO0lBQ0w7UUFDRSxJQUFJLEVBQUUsVUFBVSxDQUFDLEtBQUs7UUFDdEIsSUFBSSxFQUFFLE9BQU87UUFDYixNQUFNLEVBQUUsSUFBSTtLQUNiO0lBQ0Q7UUFDRSxJQUFJLEVBQUUsVUFBVSxDQUFDLE9BQU87UUFDeEIsSUFBSSxFQUFFLFNBQVM7UUFDZixNQUFNLEVBQUUsSUFBSTtLQUNiO0lBQ0Q7UUFDRSxJQUFJLEVBQUUsS0FBSztRQUNYLElBQUksRUFBRSxNQUFNO1FBQ1osTUFBTSxFQUFFLEVBQUUsSUFBSSxFQUFFLEdBQUcsRUFBRTtLQUN0QjtJQUNEO1FBQ0UsSUFBSSxFQUFFLFVBQVUsQ0FBQyxTQUFTO1FBQzFCLElBQUksRUFBRSxXQUFXO1FBQ2pCLE1BQU0sRUFBRSxJQUFJO0tBQ2I7SUFDRCxJQUFJO0lBQ0osOEJBQThCO0lBQzlCLHFCQUFxQjtJQUNyQixrQkFBa0I7SUFDbEIsS0FBSztJQUNMLElBQUk7SUFDSiw2QkFBNkI7SUFDN0Isb0JBQW9CO0lBQ3BCLGtCQUFrQjtJQUNsQixLQUFLO0NBQ04sQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IElEaWN0aW9uYXJ5LCBJRmlsdGVyUHJlc2V0IH0gZnJvbSAnLi90eXBlcyc7XG5cbmV4cG9ydCBjb25zdCBmaWx0ZXJQcmVzZXQgPSAoZGljdGlvbmFyeTogSURpY3Rpb25hcnkpOiBJRmlsdGVyUHJlc2V0W10gPT4gW1xuICB7XG4gICAgbmFtZTogZGljdGlvbmFyeS5vcmlnaW5hbCxcbiAgICB0eXBlOiAnRGVmYXVsdCcsXG4gICAgb3B0aW9uOiBudWxsLFxuICB9LFxuICAvLyB7XG4gIC8vICAgbmFtZTogZGljdGlvbmFyeS5pbnZlcnQsXG4gIC8vICAgdHlwZTogJ0ludmVydCcsXG4gIC8vICAgb3B0aW9uOiBudWxsLFxuICAvLyB9LFxuICB7XG4gICAgbmFtZTogZGljdGlvbmFyeS5zZXBpYSxcbiAgICB0eXBlOiAnU2VwaWEnLFxuICAgIG9wdGlvbjogbnVsbCxcbiAgfSxcbiAge1xuICAgIG5hbWU6IGRpY3Rpb25hcnkudmludGFnZSxcbiAgICB0eXBlOiAndmludGFnZScsXG4gICAgb3B0aW9uOiBudWxsLFxuICB9LFxuICB7XG4gICAgbmFtZTogJ+OBvOOBi+OBlycsXG4gICAgdHlwZTogJ0JsdXInLFxuICAgIG9wdGlvbjogeyBibHVyOiAwLjEgfSxcbiAgfSxcbiAge1xuICAgIG5hbWU6IGRpY3Rpb25hcnkuZ3JheXNjYWxlLFxuICAgIHR5cGU6ICdHcmF5c2NhbGUnLFxuICAgIG9wdGlvbjogbnVsbCxcbiAgfSxcbiAgLy8ge1xuICAvLyAgIG5hbWU6IGRpY3Rpb25hcnkuc2hhcnBlbixcbiAgLy8gICB0eXBlOiAnU2hhcnBlbicsXG4gIC8vICAgb3B0aW9uOiBudWxsLFxuICAvLyB9LFxuICAvLyB7XG4gIC8vICAgbmFtZTogZGljdGlvbmFyeS5lbWJvc3MsXG4gIC8vICAgdHlwZTogJ0VtYm9zcycsXG4gIC8vICAgb3B0aW9uOiBudWxsLFxuICAvLyB9LFxuXTtcbiJdfQ==
@@ -0,0 +1,3 @@
1
+ import { IonButton, IonButtons, IonContent, IonFooter, IonHeader, IonIcon, IonRange, IonText, IonToolbar } from '@ionic/angular/standalone';
2
+ export const ionComponents = [IonHeader, IonToolbar, IonButtons, IonButton, IonIcon, IonContent, IonFooter, IonText, IonRange];
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW9uLWNvbXBvbmVudHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wcm9qZWN0cy9waG90by1lZGl0b3Ivc3JjL2xpYi9pb24tY29tcG9uZW50cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsU0FBUyxFQUFFLFVBQVUsRUFBRSxVQUFVLEVBQUUsU0FBUyxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUU1SSxNQUFNLENBQUMsTUFBTSxhQUFhLEdBQUcsQ0FBQyxTQUFTLEVBQUUsVUFBVSxFQUFFLFVBQVUsRUFBRSxTQUFTLEVBQUUsT0FBTyxFQUFFLFVBQVUsRUFBRSxTQUFTLEVBQUUsT0FBTyxFQUFFLFFBQVEsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW9uQnV0dG9uLCBJb25CdXR0b25zLCBJb25Db250ZW50LCBJb25Gb290ZXIsIElvbkhlYWRlciwgSW9uSWNvbiwgSW9uUmFuZ2UsIElvblRleHQsIElvblRvb2xiYXIgfSBmcm9tICdAaW9uaWMvYW5ndWxhci9zdGFuZGFsb25lJztcblxuZXhwb3J0IGNvbnN0IGlvbkNvbXBvbmVudHMgPSBbSW9uSGVhZGVyLCBJb25Ub29sYmFyLCBJb25CdXR0b25zLCBJb25CdXR0b24sIElvbkljb24sIElvbkNvbnRlbnQsIElvbkZvb3RlciwgSW9uVGV4dCwgSW9uUmFuZ2VdO1xuIl19
@@ -0,0 +1,187 @@
1
+ import { Component, inject, Input, signal, ViewChild } from '@angular/core';
2
+ import { CommonModule, NgOptimizedImage } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+ import { IonContent, ModalController } from '@ionic/angular/standalone';
5
+ import ImageEditor from 'tui-image-editor';
6
+ import { filterPreset } from '../filter-preset';
7
+ import { toObservable } from '@angular/core/rxjs-interop';
8
+ import { PhotoEditorService } from '../service/photo-editor.service';
9
+ import { ionComponents } from '../ion-components';
10
+ import { dictionary } from '../dictionary';
11
+ import * as i0 from "@angular/core";
12
+ import * as i1 from "@ionic/angular/standalone";
13
+ export class PhotoEditorPage {
14
+ set label(d) {
15
+ this.dictionary = Object.assign(this.dictionary, d);
16
+ this.filterPreset = filterPreset(this.dictionary);
17
+ }
18
+ constructor() {
19
+ this.dictionary = dictionary();
20
+ this.filterPreset = filterPreset(this.dictionary);
21
+ this.requireSquare = false;
22
+ this.$filters = signal([]);
23
+ this.$footerMenu = signal('menu');
24
+ this.$currentCrop = signal('cover');
25
+ this.$currentRotate = signal(0);
26
+ this.$photoCrop = signal({
27
+ width: 0,
28
+ height: 0,
29
+ });
30
+ this.$isCropped = signal(false);
31
+ this.footerMenu$ = toObservable(this.$footerMenu);
32
+ this.$adoptFilter = signal(undefined);
33
+ this.initSubscription$ = [];
34
+ this.filterImageSize = 240;
35
+ this.service = inject(PhotoEditorService);
36
+ this.modalCtrl = inject(ModalController);
37
+ this.canvasContainerObserver = new MutationObserver((mutationsList) => {
38
+ if (mutationsList.find((mutation) => mutation.type === 'attributes' && mutation.attributeName === 'style')) {
39
+ // Cover the image editor with the parent element
40
+ this.editorRef.nativeElement.style.minWidth = mutationsList[0].target.parentElement.style.maxWidth;
41
+ this.editorRef.nativeElement.style.minHeight = mutationsList[0].target.parentElement.style.maxHeight;
42
+ this.$photoCrop.set({
43
+ width: mutationsList[0].target.parentElement.querySelector('canvas').width,
44
+ height: mutationsList[0].target.parentElement.querySelector('canvas').height,
45
+ });
46
+ }
47
+ });
48
+ this.service.initializeIcons();
49
+ }
50
+ ngOnInit() {
51
+ this.initSubscription$.push(this.footerMenu$.subscribe((value) => {
52
+ if (value === 'filter') {
53
+ this.initializeFilterMenu().then();
54
+ }
55
+ if (value === 'crop') {
56
+ this.editorInstance.startDrawingMode('CROPPER');
57
+ this.changeCrop(this.requireSquare ? '1' : 'cover');
58
+ }
59
+ }));
60
+ }
61
+ ngOnDestroy() {
62
+ this.initSubscription$.forEach((subscription) => subscription.unsubscribe());
63
+ }
64
+ async ionViewDidEnter() {
65
+ this.editorInstance = new ImageEditor(this.editorRef.nativeElement, {
66
+ cssMaxWidth: this.ionContent.el.clientWidth - 32,
67
+ cssMaxHeight: this.ionContent.el.clientHeight - 32,
68
+ });
69
+ this.service.waitToFindDom(this.editorRef.nativeElement, '.tui-image-editor-canvas-container').then(() => {
70
+ this.canvasContainerObserver.observe(this.editorRef.nativeElement.querySelector('.tui-image-editor-canvas-container'), {
71
+ attributes: true,
72
+ childList: false,
73
+ subtree: true,
74
+ });
75
+ });
76
+ const blob = await fetch(this.value).then((res) => res.blob());
77
+ await this.editorInstance.loadImageFromFile(new File([blob], 'data.png', { type: blob.type }));
78
+ this.$footerMenu.set(this.requireSquare ? 'crop' : 'menu');
79
+ }
80
+ ionViewDidLeave() {
81
+ this.editorInstance.destroy();
82
+ this.canvasContainerObserver.disconnect();
83
+ }
84
+ changeCrop(crop) {
85
+ const rect = crop === 'cover' ? this.$photoCrop().width / this.$photoCrop().height : crop === '16/9' ? 16 / 9 : 1;
86
+ this.editorInstance.setCropzoneRect(crop !== 'auto' ? rect : undefined);
87
+ this.$currentCrop.set(crop);
88
+ }
89
+ async rotate() {
90
+ this.editorInstance.stopDrawingMode();
91
+ await this.editorInstance.rotate(90);
92
+ this.$currentRotate.update((value) => value + 90);
93
+ this.editorInstance.startDrawingMode('CROPPER');
94
+ requestAnimationFrame(() => this.changeCrop(this.$currentCrop()));
95
+ }
96
+ async closeCrop(type) {
97
+ if (this.$footerMenu() === 'crop') {
98
+ if (type === 'cancel') {
99
+ await this.editorInstance.rotate(this.$currentRotate() * -1);
100
+ }
101
+ else {
102
+ await this.editorInstance.crop(this.editorInstance.getCropzoneRect());
103
+ this.$isCropped.set(true);
104
+ }
105
+ this.$currentRotate.set(0);
106
+ this.$currentCrop.set('cover');
107
+ this.editorInstance.stopDrawingMode();
108
+ }
109
+ else if (this.$footerMenu() === 'brightness') {
110
+ if (type === 'cancel' && this.editorInstance.hasFilter('brightness')) {
111
+ await this.editorInstance.removeFilter('brightness');
112
+ }
113
+ }
114
+ this.$footerMenu.set('menu');
115
+ }
116
+ async changeRange(event) {
117
+ if (this.editorInstance.hasFilter('brightness')) {
118
+ await this.editorInstance.removeFilter('brightness');
119
+ }
120
+ this.editorInstance.applyFilter('brightness', {
121
+ brightness: Number(event.detail.value) / 255,
122
+ });
123
+ }
124
+ imageSave() {
125
+ const value = this.editorInstance.toDataURL();
126
+ this.modalCtrl.dismiss({ value });
127
+ }
128
+ async initializeFilterMenu() {
129
+ const filters = [];
130
+ const defaultInstance = new ImageEditor(document.createElement('div'), {
131
+ cssMaxWidth: this.filterImageSize,
132
+ cssMaxHeight: (this.$photoCrop().height * this.filterImageSize) / this.$photoCrop().width,
133
+ });
134
+ const blob = await fetch(this.editorInstance.toDataURL({
135
+ multiplier: this.filterImageSize / this.$photoCrop().width,
136
+ })).then((res) => res.blob());
137
+ await defaultInstance.loadImageFromFile(new File([blob], 'defaultInstance.png', { type: blob.type }));
138
+ for (const filter of this.filterPreset) {
139
+ if (filter.type !== 'Default') {
140
+ await defaultInstance.applyFilter(filter.type, filter.option);
141
+ }
142
+ filters.push({
143
+ name: filter.name,
144
+ type: filter.type,
145
+ option: filter.option,
146
+ data: defaultInstance.toDataURL(),
147
+ width: this.filterImageSize,
148
+ height: (this.$photoCrop().height * this.filterImageSize) / this.$photoCrop().width,
149
+ });
150
+ if (filter.type !== 'Default') {
151
+ await defaultInstance.removeFilter(filter.type);
152
+ }
153
+ }
154
+ this.$filters.set(filters);
155
+ defaultInstance.destroy();
156
+ }
157
+ async filterImage(filter) {
158
+ if (this.$adoptFilter()) {
159
+ await this.editorInstance.removeFilter(this.$adoptFilter().type);
160
+ }
161
+ if (filter.type === 'Default') {
162
+ this.$adoptFilter.set(undefined);
163
+ return;
164
+ }
165
+ await this.editorInstance.applyFilter(filter.type, filter.option);
166
+ this.$adoptFilter.set(filter);
167
+ }
168
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.6", ngImport: i0, type: PhotoEditorPage, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
169
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.0.6", type: PhotoEditorPage, isStandalone: true, selector: "app-editor-image", inputs: { requireSquare: "requireSquare", value: "value", label: "label" }, viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["imageEditor"], descendants: true, static: true }, { propertyName: "ionContent", first: true, predicate: IonContent, descendants: true }], ngImport: i0, template: "<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\"\n ><ion-button (click)=\"modalCtrl.dismiss()\"><ion-icon name=\"close-outline\" slot=\"icon-only\"></ion-icon></ion-button\n ></ion-buttons>\n <ion-buttons slot=\"end\">\n <ion-button color=\"primary\" fill=\"outline\" [disabled]=\"!['menu', 'filter'].includes($footerMenu())\" (click)=\"imageSave()\">\n {{ dictionary.save }}\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\" [scrollY]=\"false\">\n <div #imageEditor></div>\n</ion-content>\n<ion-footer>\n <ion-toolbar>\n @if (this.$footerMenu() === 'menu') {\n <aside class=\"ion-justify-content-center\">\n <button (click)=\"$footerMenu.set('crop')\">\n <ion-text>{{ dictionary.crop }}</ion-text>\n <ion-icon name=\"crop-outline\" size=\"large\"></ion-icon>\n </button>\n <button (click)=\"$footerMenu.set('filter')\">\n <ion-text>{{ dictionary.filter }}</ion-text>\n <ion-icon name=\"color-filter-outline\" size=\"large\"></ion-icon>\n </button>\n <button (click)=\"$footerMenu.set('brightness')\">\n <ion-text>{{ dictionary.brightness }}</ion-text>\n <ion-icon name=\"sunny-outline\" size=\"large\"></ion-icon>\n </button>\n </aside>\n }\n\n @if (this.$footerMenu() === 'filter') {\n <aside>\n @for (item of $filters(); track item) {\n <button (click)=\"filterImage(item)\">\n <ion-text>{{ item.name }}</ion-text>\n <span class=\"image-filter-box\" [style.background-image]=\"'url(' + item.data + ')'\"></span>\n </button>\n }\n </aside>\n }\n\n @if (this.$footerMenu() === 'crop' && !requireSquare) {\n <ion-buttons class=\"ion-justify-content-center submenu-icon-buttons\">\n <ion-button (click)=\"changeCrop('cover')\" [color]=\"$currentCrop() === 'cover' ? 'success' : undefined\">\n <ion-icon name=\"expand-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"changeCrop('16/9')\" [color]=\"$currentCrop() === '16/9' ? 'success' : undefined\">\n <ion-icon name=\"tablet-landscape-outline\" slot=\"icon-only\" style=\"transform: scale(1, 0.8)\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"changeCrop('1')\" [color]=\"$currentCrop() === '1' ? 'success' : undefined\">\n <ion-icon name=\"square-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"changeCrop('auto')\" [color]=\"$currentCrop() === 'auto' ? 'success' : undefined\">\n <ion-icon name=\"crop-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"rotate()\"><ion-icon name=\"refresh-outline\" slot=\"icon-only\"></ion-icon></ion-button>\n </ion-buttons>\n }\n\n @if (['brightness'].includes(this.$footerMenu())) {\n <div class=\"ion-padding ion-margin\">\n <ion-range [pin]=\"true\" [min]=\"-100\" [max]=\"100\" (ionChange)=\"changeRange($any($event))\"></ion-range>\n </div>\n }\n </ion-toolbar>\n @if (this.$footerMenu() !== 'menu') {\n <ion-toolbar mode=\"md\">\n @if (this.$footerMenu() === 'filter') {\n <ion-buttons class=\"ion-justify-content-center\" style=\"margin: 0 8px\">\n <ion-button fill=\"outline\" shape=\"round\" (click)=\"this.$footerMenu.set('menu')\">\u623B\u308B</ion-button>\n </ion-buttons>\n } @else {\n <ion-buttons slot=\"start\" style=\"min-width: 60px\">\n @if (!requireSquare || $isCropped()) {\n <ion-button (click)=\"closeCrop('cancel')\"><ion-icon name=\"close-outline\" slot=\"icon-only\"></ion-icon></ion-button>\n }</ion-buttons\n ><ion-text class=\"footer-title\">\n @if ($footerMenu() === 'crop') {\n {{ dictionary.crop }}\n } @else if ($footerMenu() === 'brightness') {\n {{ dictionary.brightness }}\n }</ion-text\n ><ion-buttons slot=\"end\"\n ><ion-button (click)=\"closeCrop('apply')\"\n ><ion-icon name=\"checkmark-outline\" slot=\"icon-only\" color=\"success\"></ion-icon></ion-button\n ></ion-buttons>\n }\n </ion-toolbar>\n }\n</ion-footer>\n", styles: ["@charset \"UTF-8\";:host{--alway-dark: var(--ion-color-step-100, #2a2a2a);--alway-dark-tint: var(--ion-color-step-200, #414141);--alway-white: var(--ion-color-step-950, #f0f0f0);--alway-white-tint: var(--ion-color-step-850, #dbdbdb)}ion-content,ion-toolbar,ion-header,ion-footer{background:var(--alway-dark);--background: var(--alway-dark)}ion-content{--background: var(--alway-dark)}ion-content>div{width:100%;height:100%;max-width:100%;max-height:100%;display:grid;align-items:center;justify-items:center}aside{display:flex;overflow-x:scroll;margin-top:16px;min-height:120px}aside ion-icon{color:var(--alway-white);border:1px solid var(--alway-dark-tint);border-radius:50%;padding:16px}aside button{width:120px;display:block;flex:1;background:transparent;margin:0 8px 16px}aside button ion-text{display:block;color:var(--alway-white);margin-bottom:8px;font-size:.8rem}aside button span.image-filter-box{width:96px;height:64px;border-radius:8px;overflow:hidden;display:block;background-position:center,center;background-size:cover}ion-header ion-button,ion-footer ion-button{--color: var(--alway-white) !important}ion-footer ion-toolbar ion-text.footer-title{color:var(--alway-white)!important;display:block;text-align:center}ion-footer ion-toolbar ion-range{--bar-background: var(--alway-white);--bar-background-active: var(--alway-white);--bar-height: 2px;--bar-border-radius: 8px;--knob-background: var(--alway-white);--knob-size: 20px;--pin-background: none;--pin-color: var(--alway-white)}ion-footer ion-toolbar ion-buttons[slot=start] ion-button,ion-footer ion-toolbar ion-buttons[slot=end] ion-button{--background: var(--alway-dark-tint) !important;--border-radius: 50%;margin:0 8px;min-width:32px;min-height:32px}ion-footer ion-toolbar ion-buttons.submenu-icon-buttons ion-button{margin:16px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: i1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i1.IonRange, selector: "ion-range", inputs: ["activeBarStart", "color", "debounce", "disabled", "dualKnobs", "label", "labelPlacement", "legacy", "max", "min", "mode", "name", "pin", "pinFormatter", "snaps", "step", "ticks", "value"] }] }); }
170
+ }
171
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.6", ngImport: i0, type: PhotoEditorPage, decorators: [{
172
+ type: Component,
173
+ args: [{ selector: 'app-editor-image', standalone: true, imports: [CommonModule, FormsModule, NgOptimizedImage, ...ionComponents], template: "<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\"\n ><ion-button (click)=\"modalCtrl.dismiss()\"><ion-icon name=\"close-outline\" slot=\"icon-only\"></ion-icon></ion-button\n ></ion-buttons>\n <ion-buttons slot=\"end\">\n <ion-button color=\"primary\" fill=\"outline\" [disabled]=\"!['menu', 'filter'].includes($footerMenu())\" (click)=\"imageSave()\">\n {{ dictionary.save }}\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\" [scrollY]=\"false\">\n <div #imageEditor></div>\n</ion-content>\n<ion-footer>\n <ion-toolbar>\n @if (this.$footerMenu() === 'menu') {\n <aside class=\"ion-justify-content-center\">\n <button (click)=\"$footerMenu.set('crop')\">\n <ion-text>{{ dictionary.crop }}</ion-text>\n <ion-icon name=\"crop-outline\" size=\"large\"></ion-icon>\n </button>\n <button (click)=\"$footerMenu.set('filter')\">\n <ion-text>{{ dictionary.filter }}</ion-text>\n <ion-icon name=\"color-filter-outline\" size=\"large\"></ion-icon>\n </button>\n <button (click)=\"$footerMenu.set('brightness')\">\n <ion-text>{{ dictionary.brightness }}</ion-text>\n <ion-icon name=\"sunny-outline\" size=\"large\"></ion-icon>\n </button>\n </aside>\n }\n\n @if (this.$footerMenu() === 'filter') {\n <aside>\n @for (item of $filters(); track item) {\n <button (click)=\"filterImage(item)\">\n <ion-text>{{ item.name }}</ion-text>\n <span class=\"image-filter-box\" [style.background-image]=\"'url(' + item.data + ')'\"></span>\n </button>\n }\n </aside>\n }\n\n @if (this.$footerMenu() === 'crop' && !requireSquare) {\n <ion-buttons class=\"ion-justify-content-center submenu-icon-buttons\">\n <ion-button (click)=\"changeCrop('cover')\" [color]=\"$currentCrop() === 'cover' ? 'success' : undefined\">\n <ion-icon name=\"expand-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"changeCrop('16/9')\" [color]=\"$currentCrop() === '16/9' ? 'success' : undefined\">\n <ion-icon name=\"tablet-landscape-outline\" slot=\"icon-only\" style=\"transform: scale(1, 0.8)\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"changeCrop('1')\" [color]=\"$currentCrop() === '1' ? 'success' : undefined\">\n <ion-icon name=\"square-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"changeCrop('auto')\" [color]=\"$currentCrop() === 'auto' ? 'success' : undefined\">\n <ion-icon name=\"crop-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"rotate()\"><ion-icon name=\"refresh-outline\" slot=\"icon-only\"></ion-icon></ion-button>\n </ion-buttons>\n }\n\n @if (['brightness'].includes(this.$footerMenu())) {\n <div class=\"ion-padding ion-margin\">\n <ion-range [pin]=\"true\" [min]=\"-100\" [max]=\"100\" (ionChange)=\"changeRange($any($event))\"></ion-range>\n </div>\n }\n </ion-toolbar>\n @if (this.$footerMenu() !== 'menu') {\n <ion-toolbar mode=\"md\">\n @if (this.$footerMenu() === 'filter') {\n <ion-buttons class=\"ion-justify-content-center\" style=\"margin: 0 8px\">\n <ion-button fill=\"outline\" shape=\"round\" (click)=\"this.$footerMenu.set('menu')\">\u623B\u308B</ion-button>\n </ion-buttons>\n } @else {\n <ion-buttons slot=\"start\" style=\"min-width: 60px\">\n @if (!requireSquare || $isCropped()) {\n <ion-button (click)=\"closeCrop('cancel')\"><ion-icon name=\"close-outline\" slot=\"icon-only\"></ion-icon></ion-button>\n }</ion-buttons\n ><ion-text class=\"footer-title\">\n @if ($footerMenu() === 'crop') {\n {{ dictionary.crop }}\n } @else if ($footerMenu() === 'brightness') {\n {{ dictionary.brightness }}\n }</ion-text\n ><ion-buttons slot=\"end\"\n ><ion-button (click)=\"closeCrop('apply')\"\n ><ion-icon name=\"checkmark-outline\" slot=\"icon-only\" color=\"success\"></ion-icon></ion-button\n ></ion-buttons>\n }\n </ion-toolbar>\n }\n</ion-footer>\n", styles: ["@charset \"UTF-8\";:host{--alway-dark: var(--ion-color-step-100, #2a2a2a);--alway-dark-tint: var(--ion-color-step-200, #414141);--alway-white: var(--ion-color-step-950, #f0f0f0);--alway-white-tint: var(--ion-color-step-850, #dbdbdb)}ion-content,ion-toolbar,ion-header,ion-footer{background:var(--alway-dark);--background: var(--alway-dark)}ion-content{--background: var(--alway-dark)}ion-content>div{width:100%;height:100%;max-width:100%;max-height:100%;display:grid;align-items:center;justify-items:center}aside{display:flex;overflow-x:scroll;margin-top:16px;min-height:120px}aside ion-icon{color:var(--alway-white);border:1px solid var(--alway-dark-tint);border-radius:50%;padding:16px}aside button{width:120px;display:block;flex:1;background:transparent;margin:0 8px 16px}aside button ion-text{display:block;color:var(--alway-white);margin-bottom:8px;font-size:.8rem}aside button span.image-filter-box{width:96px;height:64px;border-radius:8px;overflow:hidden;display:block;background-position:center,center;background-size:cover}ion-header ion-button,ion-footer ion-button{--color: var(--alway-white) !important}ion-footer ion-toolbar ion-text.footer-title{color:var(--alway-white)!important;display:block;text-align:center}ion-footer ion-toolbar ion-range{--bar-background: var(--alway-white);--bar-background-active: var(--alway-white);--bar-height: 2px;--bar-border-radius: 8px;--knob-background: var(--alway-white);--knob-size: 20px;--pin-background: none;--pin-color: var(--alway-white)}ion-footer ion-toolbar ion-buttons[slot=start] ion-button,ion-footer ion-toolbar ion-buttons[slot=end] ion-button{--background: var(--alway-dark-tint) !important;--border-radius: 50%;margin:0 8px;min-width:32px;min-height:32px}ion-footer ion-toolbar ion-buttons.submenu-icon-buttons ion-button{margin:16px}\n"] }]
174
+ }], ctorParameters: () => [], propDecorators: { requireSquare: [{
175
+ type: Input
176
+ }], value: [{
177
+ type: Input
178
+ }], label: [{
179
+ type: Input
180
+ }], editorRef: [{
181
+ type: ViewChild,
182
+ args: ['imageEditor', { static: true }]
183
+ }], ionContent: [{
184
+ type: ViewChild,
185
+ args: [IonContent]
186
+ }] } });
187
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGhvdG8tZWRpdG9yLnBhZ2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy9waG90by1lZGl0b3Ivc3JjL2xpYi9wYWdlL3Bob3RvLWVkaXRvci5wYWdlLnRzIiwiLi4vLi4vLi4vLi4vLi4vcHJvamVjdHMvcGhvdG8tZWRpdG9yL3NyYy9saWIvcGFnZS9waG90by1lZGl0b3IucGFnZS5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQWMsTUFBTSxFQUFFLEtBQUssRUFBcUIsTUFBTSxFQUFFLFNBQVMsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUMzRyxPQUFPLEVBQUUsWUFBWSxFQUFFLGdCQUFnQixFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDakUsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzdDLE9BQU8sRUFBRSxVQUFVLEVBQUUsZUFBZSxFQUFnRCxNQUFNLDJCQUEyQixDQUFDO0FBQ3RILE9BQU8sV0FBVyxNQUFNLGtCQUFrQixDQUFDO0FBQzNDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUVoRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFFMUQsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDckUsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQ2xELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxlQUFlLENBQUM7OztBQVMzQyxNQUFNLE9BQU8sZUFBZTtJQU0xQixJQUFhLEtBQUssQ0FBQyxDQUFjO1FBQy9CLElBQUksQ0FBQyxVQUFVLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUMsQ0FBQyxDQUFDO1FBQ3BELElBQUksQ0FBQyxZQUFZLEdBQUcsWUFBWSxDQUFDLElBQUksQ0FBQyxV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QUFDLENBQUM7U0FDbkU7UUFDRCxJQUFJLE1BQU0sQ0FBQyxJQUFJLEtBQUssU0FBUyxFQUFFO1lBQzdCLElBQUksQ0FBQyxZQUFZLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxDQUFDO1lBQ2pDLE9BQU87U0FDUjtRQUNELE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxXQUFXLENBQUMsTUFBTSxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDbEUsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDaEMsQ0FBQzs4R0F2TFUsZUFBZTtrR0FBZixlQUFlLDZTQVlmLFVBQVUsZ0RDaEN2Qix3dklBK0ZBLG8wREQ3RVksWUFBWSw4QkFBRSxXQUFXOzsyRkFFeEIsZUFBZTtrQkFQM0IsU0FBUzsrQkFDRSxrQkFBa0IsY0FHaEIsSUFBSSxXQUNQLENBQUMsWUFBWSxFQUFFLFdBQVcsRUFBRSxnQkFBZ0IsRUFBRSxHQUFHLGFBQWEsQ0FBQzt3REFNL0QsYUFBYTtzQkFBckIsS0FBSztnQkFDRyxLQUFLO3NCQUFiLEtBQUs7Z0JBQ08sS0FBSztzQkFBakIsS0FBSztnQkFLc0MsU0FBUztzQkFBcEQsU0FBUzt1QkFBQyxhQUFhLEVBQUUsRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFO2dCQUNuQixVQUFVO3NCQUFoQyxTQUFTO3VCQUFDLFVBQVUiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb21wb25lbnQsIEVsZW1lbnRSZWYsIGluamVjdCwgSW5wdXQsIE9uRGVzdHJveSwgT25Jbml0LCBzaWduYWwsIFZpZXdDaGlsZCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgQ29tbW9uTW9kdWxlLCBOZ09wdGltaXplZEltYWdlIH0gZnJvbSAnQGFuZ3VsYXIvY29tbW9uJztcbmltcG9ydCB7IEZvcm1zTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvZm9ybXMnO1xuaW1wb3J0IHsgSW9uQ29udGVudCwgTW9kYWxDb250cm9sbGVyLCBSYW5nZUN1c3RvbUV2ZW50LCBWaWV3RGlkRW50ZXIsIFZpZXdEaWRMZWF2ZSB9IGZyb20gJ0Bpb25pYy9hbmd1bGFyL3N0YW5kYWxvbmUnO1xuaW1wb3J0IEltYWdlRWRpdG9yIGZyb20gJ3R1aS1pbWFnZS1lZGl0b3InO1xuaW1wb3J0IHsgZmlsdGVyUHJlc2V0IH0gZnJvbSAnLi4vZmlsdGVyLXByZXNldCc7XG5pbXBvcnQgeyBTdWJzY3JpcHRpb24gfSBmcm9tICdyeGpzJztcbmltcG9ydCB7IHRvT2JzZXJ2YWJsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUvcnhqcy1pbnRlcm9wJztcbmltcG9ydCB7IElEaWN0aW9uYXJ5LCBJUGhvdG9FZGl0b3JEaXNtaXNzLCBJRmlsdGVyLCBJU2l6ZSB9IGZyb20gJy4uL3R5cGVzJztcbmltcG9ydCB7IFBob3RvRWRpdG9yU2VydmljZSB9IGZyb20gJy4uL3NlcnZpY2UvcGhvdG8tZWRpdG9yLnNlcnZpY2UnO1xuaW1wb3J0IHsgaW9uQ29tcG9uZW50cyB9IGZyb20gJy4uL2lvbi1jb21wb25lbnRzJztcbmltcG9ydCB7IGRpY3Rpb25hcnkgfSBmcm9tICcuLi9kaWN0aW9uYXJ5JztcblxuQENvbXBvbmVudCh7XG4gIHNlbGVjdG9yOiAnYXBwLWVkaXRvci1pbWFnZScsXG4gIHRlbXBsYXRlVXJsOiAnLi9waG90by1lZGl0b3IucGFnZS5odG1sJyxcbiAgc3R5bGVVcmxzOiBbJy4vcGhvdG8tZWRpdG9yLnBhZ2Uuc2NzcyddLFxuICBzdGFuZGFsb25lOiB0cnVlLFxuICBpbXBvcnRzOiBbQ29tbW9uTW9kdWxlLCBGb3Jtc01vZHVsZSwgTmdPcHRpbWl6ZWRJbWFnZSwgLi4uaW9uQ29tcG9uZW50c10sXG59KVxuZXhwb3J0IGNsYXNzIFBob3RvRWRpdG9yUGFnZSBpbXBsZW1lbnRzIE9uSW5pdCwgT25EZXN0cm95LCBWaWV3RGlkRW50ZXIsIFZpZXdEaWRMZWF2ZSB7XG4gIHByb3RlY3RlZCBkaWN0aW9uYXJ5OiBJRGljdGlvbmFyeSA9IGRpY3Rpb25hcnkoKTtcbiAgcHJvdGVjdGVkIGZpbHRlclByZXNldCA9IGZpbHRlclByZXNldCh0aGlzLmRpY3Rpb25hcnkpO1xuXG4gIEBJbnB1dCgpIHJlcXVpcmVTcXVhcmU6IGJvb2xlYW4gPSBmYWxzZTtcbiAgQElucHV0KCkgdmFsdWUhOiBzdHJpbmc7XG4gIEBJbnB1dCgpIHNldCBsYWJlbChkOiBJRGljdGlvbmFyeSkge1xuICAgIHRoaXMuZGljdGlvbmFyeSA9IE9iamVjdC5hc3NpZ24odGhpcy5kaWN0aW9uYXJ5LCBkKTtcbiAgICB0aGlzLmZpbHRlclByZXNldCA9IGZpbHRlclByZXNldCh0aGlzLmRpY3Rpb25hcnkpO1xuICB9XG5cbiAgQFZpZXdDaGlsZCgnaW1hZ2VFZGl0b3InLCB7IHN0YXRpYzogdHJ1ZSB9KSBlZGl0b3JSZWYhOiBFbGVtZW50UmVmO1xuICBAVmlld0NoaWxkKElvbkNvbnRlbnQpIGlvbkNvbnRlbnQhOiB7XG4gICAgZWw6IElvbkNvbnRlbnQgJiBIVE1MRWxlbWVudDtcbiAgfTtcblxuICAkZmlsdGVycyA9IHNpZ25hbDxJRmlsdGVyW10+KFtdKTtcbiAgJGZvb3Rlck1lbnUgPSBzaWduYWw8J2ZpbHRlcicgfCAnbWVudScgfCAnY3JvcCcgfCAnYnJpZ2h0bmVzcyc+KCdtZW51Jyk7XG4gICRjdXJyZW50Q3JvcCA9IHNpZ25hbDwnY292ZXInIHwgJzE2LzknIHwgJzEnIHwgJ2F1dG8nPignY292ZXInKTtcbiAgJGN1cnJlbnRSb3RhdGUgPSBzaWduYWw8bnVtYmVyPigwKTtcbiAgJHBob3RvQ3JvcCA9IHNpZ25hbDxJU2l6ZT4oe1xuICAgIHdpZHRoOiAwLFxuICAgIGhlaWdodDogMCxcbiAgfSk7XG4gICRpc0Nyb3BwZWQgPSBzaWduYWw8Ym9vbGVhbj4oZmFsc2UpO1xuXG4gIHByaXZhdGUgZm9vdGVyTWVudSQgPSB0b09ic2VydmFibGUodGhpcy4kZm9vdGVyTWVudSk7XG4gIHByaXZhdGUgJGFkb3B0RmlsdGVyID0gc2lnbmFsPElGaWx0ZXIgfCB1bmRlZmluZWQ+KHVuZGVmaW5lZCk7XG4gIHByaXZhdGUgZWRpdG9ySW5zdGFuY2UhOiBJbWFnZUVkaXRvcjtcbiAgcHJpdmF0ZSBpbml0U3Vic2NyaXB0aW9uJDogU3Vic2NyaXB0aW9uW10gPSBbXTtcbiAgcHJpdmF0ZSByZWFkb25seSBmaWx0ZXJJbWFnZVNpemUgPSAyNDA7XG4gIHByaXZhdGUgcmVhZG9ubHkgc2VydmljZSA9IGluamVjdChQaG90b0VkaXRvclNlcnZpY2UpO1xuXG4gIG1vZGFsQ3RybCA9IGluamVjdChNb2RhbENvbnRyb2xsZXIpO1xuXG4gIHByaXZhdGUgY2FudmFzQ29udGFpbmVyT2JzZXJ2ZXI6IE11dGF0aW9uT2JzZXJ2ZXIgPSBuZXcgTXV0YXRpb25PYnNlcnZlcigobXV0YXRpb25zTGlzdDogTXV0YXRpb25SZWNvcmRbXSkgPT4ge1xuICAgIGlmIChtdXRhdGlvbnNMaXN0LmZpbmQoKG11dGF0aW9uKSA9PiBtdXRhdGlvbi50eXBlID09PSAnYXR0cmlidXRlcycgJiYgbXV0YXRpb24uYXR0cmlidXRlTmFtZSA9PT0gJ3N0eWxlJykpIHtcbiAgICAgIC8vIENvdmVyIHRoZSBpbWFnZSBlZGl0b3Igd2l0aCB0aGUgcGFyZW50IGVsZW1lbnRcbiAgICAgIHRoaXMuZWRpdG9yUmVmLm5hdGl2ZUVsZW1lbnQuc3R5bGUubWluV2lkdGggPSBtdXRhdGlvbnNMaXN0WzBdLnRhcmdldC5wYXJlbnRFbGVtZW50IS5zdHlsZS5tYXhXaWR0aDtcbiAgICAgIHRoaXMuZWRpdG9yUmVmLm5hdGl2ZUVsZW1lbnQuc3R5bGUubWluSGVpZ2h0ID0gbXV0YXRpb25zTGlzdFswXS50YXJnZXQucGFyZW50RWxlbWVudCEuc3R5bGUubWF4SGVpZ2h0O1xuXG4gICAgICB0aGlzLiRwaG90b0Nyb3Auc2V0KHtcbiAgICAgICAgd2lkdGg6IG11dGF0aW9uc0xpc3RbMF0udGFyZ2V0LnBhcmVudEVsZW1lbnQhLnF1ZXJ5U2VsZWN0b3IoJ2NhbnZhcycpIS53aWR0aCxcbiAgICAgICAgaGVpZ2h0OiBtdXRhdGlvbnNMaXN0WzBdLnRhcmdldC5wYXJlbnRFbGVtZW50IS5xdWVyeVNlbGVjdG9yKCdjYW52YXMnKSEuaGVpZ2h0LFxuICAgICAgfSk7XG4gICAgfVxuICB9KTtcblxuICBjb25zdHJ1Y3RvcigpIHtcbiAgICB0aGlzLnNlcnZpY2UuaW5pdGlhbGl6ZUljb25zKCk7XG4gIH1cblxuICBuZ09uSW5pdCgpIHtcbiAgICB0aGlzLmluaXRTdWJzY3JpcHRpb24kLnB1c2goXG4gICAgICB0aGlzLmZvb3Rlck1lbnUkLnN1YnNjcmliZSgodmFsdWUpID0+IHtcbiAgICAgICAgaWYgKHZhbHVlID09PSAnZmlsdGVyJykge1xuICAgICAgICAgIHRoaXMuaW5pdGlhbGl6ZUZpbHRlck1lbnUoKS50aGVuKCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHZhbHVlID09PSAnY3JvcCcpIHtcbiAgICAgICAgICB0aGlzLmVkaXRvckluc3RhbmNlLnN0YXJ0RHJhd2luZ01vZGUoJ0NST1BQRVInKTtcbiAgICAgICAgICB0aGlzLmNoYW5nZUNyb3AodGhpcy5yZXF1aXJlU3F1YXJlID8gJzEnIDogJ2NvdmVyJyk7XG4gICAgICAgIH1cbiAgICAgIH0pLFxuICAgICk7XG4gIH1cblxuICBuZ09uRGVzdHJveSgpIHtcbiAgICB0aGlzLmluaXRTdWJzY3JpcHRpb24kLmZvckVhY2goKHN1YnNjcmlwdGlvbikgPT4gc3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlKCkpO1xuICB9XG5cbiAgYXN5bmMgaW9uVmlld0RpZEVudGVyKCkge1xuICAgIHRoaXMuZWRpdG9ySW5zdGFuY2UgPSBuZXcgSW1hZ2VFZGl0b3IodGhpcy5lZGl0b3JSZWYubmF0aXZlRWxlbWVudCwge1xuICAgICAgY3NzTWF4V2lkdGg6IHRoaXMuaW9uQ29udGVudC5lbC5jbGllbnRXaWR0aCAtIDMyLFxuICAgICAgY3NzTWF4SGVpZ2h0OiB0aGlzLmlvbkNvbnRlbnQuZWwuY2xpZW50SGVpZ2h0IC0gMzIsXG4gICAgfSk7XG4gICAgdGhpcy5zZXJ2aWNlLndhaXRUb0ZpbmREb20odGhpcy5lZGl0b3JSZWYubmF0aXZlRWxlbWVudCwgJy50dWktaW1hZ2UtZWRpdG9yLWNhbnZhcy1jb250YWluZXInKS50aGVuKCgpID0+IHtcbiAgICAgIHRoaXMuY2FudmFzQ29udGFpbmVyT2JzZXJ2ZXIub2JzZXJ2ZSh0aGlzLmVkaXRvclJlZi5uYXRpdmVFbGVtZW50LnF1ZXJ5U2VsZWN0b3IoJy50dWktaW1hZ2UtZWRpdG9yLWNhbnZhcy1jb250YWluZXInKSwge1xuICAgICAgICBhdHRyaWJ1dGVzOiB0cnVlLFxuICAgICAgICBjaGlsZExpc3Q6IGZhbHNlLFxuICAgICAgICBzdWJ0cmVlOiB0cnVlLFxuICAgICAgfSk7XG4gICAgfSk7XG4gICAgY29uc3QgYmxvYiA9IGF3YWl0IGZldGNoKHRoaXMudmFsdWUpLnRoZW4oKHJlcykgPT4gcmVzLmJsb2IoKSk7XG4gICAgYXdhaXQgdGhpcy5lZGl0b3JJbnN0YW5jZS5sb2FkSW1hZ2VGcm9tRmlsZShuZXcgRmlsZShbYmxvYl0sICdkYXRhLnBuZycsIHsgdHlwZTogYmxvYi50eXBlIH0pKTtcbiAgICB0aGlzLiRmb290ZXJNZW51LnNldCh0aGlzLnJlcXVpcmVTcXVhcmUgPyAnY3JvcCcgOiAnbWVudScpO1xuICB9XG5cbiAgaW9uVmlld0RpZExlYXZlKCkge1xuICAgIHRoaXMuZWRpdG9ySW5zdGFuY2UuZGVzdHJveSgpO1xuICAgIHRoaXMuY2FudmFzQ29udGFpbmVyT2JzZXJ2ZXIuZGlzY29ubmVjdCgpO1xuICB9XG5cbiAgY2hhbmdlQ3JvcChjcm9wOiAnY292ZXInIHwgJzE2LzknIHwgJzEnIHwgJ2F1dG8nKSB7XG4gICAgY29uc3QgcmVjdCA9IGNyb3AgPT09ICdjb3ZlcicgPyB0aGlzLiRwaG90b0Nyb3AoKS53aWR0aCAvIHRoaXMuJHBob3RvQ3JvcCgpLmhlaWdodCA6IGNyb3AgPT09ICcxNi85JyA/IDE2IC8gOSA6IDE7XG4gICAgdGhpcy5lZGl0b3JJbnN0YW5jZS5zZXRDcm9wem9uZVJlY3QoY3JvcCAhPT0gJ2F1dG8nID8gcmVjdCA6IHVuZGVmaW5lZCk7XG4gICAgdGhpcy4kY3VycmVudENyb3Auc2V0KGNyb3ApO1xuICB9XG5cbiAgYXN5bmMgcm90YXRlKCkge1xuICAgIHRoaXMuZWRpdG9ySW5zdGFuY2Uuc3RvcERyYXdpbmdNb2RlKCk7XG4gICAgYXdhaXQgdGhpcy5lZGl0b3JJbnN0YW5jZS5yb3RhdGUoOTApO1xuICAgIHRoaXMuJGN1cnJlbnRSb3RhdGUudXBkYXRlKCh2YWx1ZSkgPT4gdmFsdWUgKyA5MCk7XG4gICAgdGhpcy5lZGl0b3JJbnN0YW5jZS5zdGFydERyYXdpbmdNb2RlKCdDUk9QUEVSJyk7XG4gICAgcmVxdWVzdEFuaW1hdGlvbkZyYW1lKCgpID0+IHRoaXMuY2hhbmdlQ3JvcCh0aGlzLiRjdXJyZW50Q3JvcCgpKSk7XG4gIH1cblxuICBhc3luYyBjbG9zZUNyb3AodHlwZTogJ2NhbmNlbCcgfCAnYXBwbHknKSB7XG4gICAgaWYgKHRoaXMuJGZvb3Rlck1lbnUoKSA9PT0gJ2Nyb3AnKSB7XG4gICAgICBpZiAodHlwZSA9PT0gJ2NhbmNlbCcpIHtcbiAgICAgICAgYXdhaXQgdGhpcy5lZGl0b3JJbnN0YW5jZS5yb3RhdGUodGhpcy4kY3VycmVudFJvdGF0ZSgpICogLTEpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgYXdhaXQgdGhpcy5lZGl0b3JJbnN0YW5jZS5jcm9wKHRoaXMuZWRpdG9ySW5zdGFuY2UuZ2V0Q3JvcHpvbmVSZWN0KCkpO1xuICAgICAgICB0aGlzLiRpc0Nyb3BwZWQuc2V0KHRydWUpO1xuICAgICAgfVxuICAgICAgdGhpcy4kY3VycmVudFJvdGF0ZS5zZXQoMCk7XG4gICAgICB0aGlzLiRjdXJyZW50Q3JvcC5zZXQoJ2NvdmVyJyk7XG4gICAgICB0aGlzLmVkaXRvckluc3RhbmNlLnN0b3BEcmF3aW5nTW9kZSgpO1xuICAgIH0gZWxzZSBpZiAodGhpcy4kZm9vdGVyTWVudSgpID09PSAnYnJpZ2h0bmVzcycpIHtcbiAgICAgIGlmICh0eXBlID09PSAnY2FuY2VsJyAmJiB0aGlzLmVkaXRvckluc3RhbmNlLmhhc0ZpbHRlcignYnJpZ2h0bmVzcycpKSB7XG4gICAgICAgIGF3YWl0IHRoaXMuZWRpdG9ySW5zdGFuY2UucmVtb3ZlRmlsdGVyKCdicmlnaHRuZXNzJyk7XG4gICAgICB9XG4gICAgfVxuICAgIHRoaXMuJGZvb3Rlck1lbnUuc2V0KCdtZW51Jyk7XG4gIH1cblxuICBhc3luYyBjaGFuZ2VSYW5nZShldmVudDogUmFuZ2VDdXN0b21FdmVudCkge1xuICAgIGlmICh0aGlzLmVkaXRvckluc3RhbmNlLmhhc0ZpbHRlcignYnJpZ2h0bmVzcycpKSB7XG4gICAgICBhd2FpdCB0aGlzLmVkaXRvckluc3RhbmNlLnJlbW92ZUZpbHRlcignYnJpZ2h0bmVzcycpO1xuICAgIH1cbiAgICB0aGlzLmVkaXRvckluc3RhbmNlLmFwcGx5RmlsdGVyKCdicmlnaHRuZXNzJywge1xuICAgICAgYnJpZ2h0bmVzczogTnVtYmVyKGV2ZW50LmRldGFpbC52YWx1ZSkgLyAyNTUsXG4gICAgfSk7XG4gIH1cblxuICBpbWFnZVNhdmUoKSB7XG4gICAgY29uc3QgdmFsdWUgPSB0aGlzLmVkaXRvckluc3RhbmNlLnRvRGF0YVVSTCgpO1xuICAgIHRoaXMubW9kYWxDdHJsLmRpc21pc3MoeyB2YWx1ZSB9IGFzIElQaG90b0VkaXRvckRpc21pc3MpO1xuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyBpbml0aWFsaXplRmlsdGVyTWVudSgpIHtcbiAgICBjb25zdCBmaWx0ZXJzOiBJRmlsdGVyW10gPSBbXTtcblxuICAgIGNvbnN0IGRlZmF1bHRJbnN0YW5jZSA9IG5ldyBJbWFnZUVkaXRvcihkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdkaXYnKSwge1xuICAgICAgY3NzTWF4V2lkdGg6IHRoaXMuZmlsdGVySW1hZ2VTaXplLFxuICAgICAgY3NzTWF4SGVpZ2h0OiAodGhpcy4kcGhvdG9Dcm9wKCkuaGVpZ2h0ICogdGhpcy5maWx0ZXJJbWFnZVNpemUpIC8gdGhpcy4kcGhvdG9Dcm9wKCkud2lkdGgsXG4gICAgfSk7XG4gICAgY29uc3QgYmxvYiA9IGF3YWl0IGZldGNoKFxuICAgICAgdGhpcy5lZGl0b3JJbnN0YW5jZS50b0RhdGFVUkwoe1xuICAgICAgICBtdWx0aXBsaWVyOiB0aGlzLmZpbHRlckltYWdlU2l6ZSAvIHRoaXMuJHBob3RvQ3JvcCgpLndpZHRoLFxuICAgICAgfSksXG4gICAgKS50aGVuKChyZXMpID0+IHJlcy5ibG9iKCkpO1xuICAgIGF3YWl0IGRlZmF1bHRJbnN0YW5jZS5sb2FkSW1hZ2VGcm9tRmlsZShuZXcgRmlsZShbYmxvYl0sICdkZWZhdWx0SW5zdGFuY2UucG5nJywgeyB0eXBlOiBibG9iLnR5cGUgfSkpO1xuXG4gICAgZm9yIChjb25zdCBmaWx0ZXIgb2YgdGhpcy5maWx0ZXJQcmVzZXQpIHtcbiAgICAgIGlmIChmaWx0ZXIudHlwZSAhPT0gJ0RlZmF1bHQnKSB7XG4gICAgICAgIGF3YWl0IGRlZmF1bHRJbnN0YW5jZS5hcHBseUZpbHRlcihmaWx0ZXIudHlwZSwgZmlsdGVyLm9wdGlvbik7XG4gICAgICB9XG4gICAgICBmaWx0ZXJzLnB1c2goe1xuICAgICAgICBuYW1lOiBmaWx0ZXIubmFtZSxcbiAgICAgICAgdHlwZTogZmlsdGVyLnR5cGUsXG4gICAgICAgIG9wdGlvbjogZmlsdGVyLm9wdGlvbixcbiAgICAgICAgZGF0YTogZGVmYXVsdEluc3RhbmNlLnRvRGF0YVVSTCgpLFxuICAgICAgICB3aWR0aDogdGhpcy5maWx0ZXJJbWFnZVNpemUsXG4gICAgICAgIGhlaWdodDogKHRoaXMuJHBob3RvQ3JvcCgpLmhlaWdodCAqIHRoaXMuZmlsdGVySW1hZ2VTaXplKSAvIHRoaXMuJHBob3RvQ3JvcCgpLndpZHRoLFxuICAgICAgfSk7XG4gICAgICBpZiAoZmlsdGVyLnR5cGUgIT09ICdEZWZhdWx0Jykge1xuICAgICAgICBhd2FpdCBkZWZhdWx0SW5zdGFuY2UucmVtb3ZlRmlsdGVyKGZpbHRlci50eXBlKTtcbiAgICAgIH1cbiAgICB9XG4gICAgdGhpcy4kZmlsdGVycy5zZXQoZmlsdGVycyk7XG4gICAgZGVmYXVsdEluc3RhbmNlLmRlc3Ryb3koKTtcbiAgfVxuXG4gIGFzeW5jIGZpbHRlckltYWdlKGZpbHRlcjogSUZpbHRlcikge1xuICAgIGlmICh0aGlzLiRhZG9wdEZpbHRlcigpKSB7XG4gICAgICBhd2FpdCB0aGlzLmVkaXRvckluc3RhbmNlLnJlbW92ZUZpbHRlcih0aGlzLiRhZG9wdEZpbHRlcigpIS50eXBlKTtcbiAgICB9XG4gICAgaWYgKGZpbHRlci50eXBlID09PSAnRGVmYXVsdCcpIHtcbiAgICAgIHRoaXMuJGFkb3B0RmlsdGVyLnNldCh1bmRlZmluZWQpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBhd2FpdCB0aGlzLmVkaXRvckluc3RhbmNlLmFwcGx5RmlsdGVyKGZpbHRlci50eXBlLCBmaWx0ZXIub3B0aW9uKTtcbiAgICB0aGlzLiRhZG9wdEZpbHRlci5zZXQoZmlsdGVyKTtcbiAgfVxufVxuIiwiPGlvbi1oZWFkZXI+XG4gIDxpb24tdG9vbGJhcj5cbiAgICA8aW9uLWJ1dHRvbnMgc2xvdD1cInN0YXJ0XCJcbiAgICAgID48aW9uLWJ1dHRvbiAoY2xpY2spPVwibW9kYWxDdHJsLmRpc21pc3MoKVwiPjxpb24taWNvbiBuYW1lPVwiY2xvc2Utb3V0bGluZVwiIHNsb3Q9XCJpY29uLW9ubHlcIj48L2lvbi1pY29uPjwvaW9uLWJ1dHRvblxuICAgID48L2lvbi1idXR0b25zPlxuICAgIDxpb24tYnV0dG9ucyBzbG90PVwiZW5kXCI+XG4gICAgICA8aW9uLWJ1dHRvbiBjb2xvcj1cInByaW1hcnlcIiBmaWxsPVwib3V0bGluZVwiIFtkaXNhYmxlZF09XCIhWydtZW51JywgJ2ZpbHRlciddLmluY2x1ZGVzKCRmb290ZXJNZW51KCkpXCIgKGNsaWNrKT1cImltYWdlU2F2ZSgpXCI+XG4gICAgICAgIHt7IGRpY3Rpb25hcnkuc2F2ZSB9fVxuICAgICAgPC9pb24tYnV0dG9uPlxuICAgIDwvaW9uLWJ1dHRvbnM+XG4gIDwvaW9uLXRvb2xiYXI+XG48L2lvbi1oZWFkZXI+XG5cbjxpb24tY29udGVudCBjbGFzcz1cImlvbi1wYWRkaW5nXCIgW3Njcm9sbFldPVwiZmFsc2VcIj5cbiAgPGRpdiAjaW1hZ2VFZGl0b3I+PC9kaXY+XG48L2lvbi1jb250ZW50PlxuPGlvbi1mb290ZXI+XG4gIDxpb24tdG9vbGJhcj5cbiAgICBAaWYgKHRoaXMuJGZvb3Rlck1lbnUoKSA9PT0gJ21lbnUnKSB7XG4gICAgICA8YXNpZGUgY2xhc3M9XCJpb24tanVzdGlmeS1jb250ZW50LWNlbnRlclwiPlxuICAgICAgICA8YnV0dG9uIChjbGljayk9XCIkZm9vdGVyTWVudS5zZXQoJ2Nyb3AnKVwiPlxuICAgICAgICAgIDxpb24tdGV4dD57eyBkaWN0aW9uYXJ5LmNyb3AgfX08L2lvbi10ZXh0PlxuICAgICAgICAgIDxpb24taWNvbiBuYW1lPVwiY3JvcC1vdXRsaW5lXCIgc2l6ZT1cImxhcmdlXCI+PC9pb24taWNvbj5cbiAgICAgICAgPC9idXR0b24+XG4gICAgICAgIDxidXR0b24gKGNsaWNrKT1cIiRmb290ZXJNZW51LnNldCgnZmlsdGVyJylcIj5cbiAgICAgICAgICA8aW9uLXRleHQ+e3sgZGljdGlvbmFyeS5maWx0ZXIgfX08L2lvbi10ZXh0PlxuICAgICAgICAgIDxpb24taWNvbiBuYW1lPVwiY29sb3ItZmlsdGVyLW91dGxpbmVcIiBzaXplPVwibGFyZ2VcIj48L2lvbi1pY29uPlxuICAgICAgICA8L2J1dHRvbj5cbiAgICAgICAgPGJ1dHRvbiAoY2xpY2spPVwiJGZvb3Rlck1lbnUuc2V0KCdicmlnaHRuZXNzJylcIj5cbiAgICAgICAgICA8aW9uLXRleHQ+e3sgZGljdGlvbmFyeS5icmlnaHRuZXNzIH19PC9pb24tdGV4dD5cbiAgICAgICAgICA8aW9uLWljb24gbmFtZT1cInN1bm55LW91dGxpbmVcIiBzaXplPVwibGFyZ2VcIj48L2lvbi1pY29uPlxuICAgICAgICA8L2J1dHRvbj5cbiAgICAgIDwvYXNpZGU+XG4gICAgfVxuXG4gICAgQGlmICh0aGlzLiRmb290ZXJNZW51KCkgPT09ICdmaWx0ZXInKSB7XG4gICAgICA8YXNpZGU+XG4gICAgICAgIEBmb3IgKGl0ZW0gb2YgJGZpbHRlcnMoKTsgdHJhY2sgaXRlbSkge1xuICAgICAgICAgIDxidXR0b24gKGNsaWNrKT1cImZpbHRlckltYWdlKGl0ZW0pXCI+XG4gICAgICAgICAgICA8aW9uLXRleHQ+e3sgaXRlbS5uYW1lIH19PC9pb24tdGV4dD5cbiAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwiaW1hZ2UtZmlsdGVyLWJveFwiIFtzdHlsZS5iYWNrZ3JvdW5kLWltYWdlXT1cIid1cmwoJyArIGl0ZW0uZGF0YSArICcpJ1wiPjwvc3Bhbj5cbiAgICAgICAgICA8L2J1dHRvbj5cbiAgICAgICAgfVxuICAgICAgPC9hc2lkZT5cbiAgICB9XG5cbiAgICBAaWYgKHRoaXMuJGZvb3Rlck1lbnUoKSA9PT0gJ2Nyb3AnICYmICFyZXF1aXJlU3F1YXJlKSB7XG4gICAgICA8aW9uLWJ1dHRvbnMgY2xhc3M9XCJpb24tanVzdGlmeS1jb250ZW50LWNlbnRlciBzdWJtZW51LWljb24tYnV0dG9uc1wiPlxuICAgICAgICA8aW9uLWJ1dHRvbiAoY2xpY2spPVwiY2hhbmdlQ3JvcCgnY292ZXInKVwiIFtjb2xvcl09XCIkY3VycmVudENyb3AoKSA9PT0gJ2NvdmVyJyA/ICdzdWNjZXNzJyA6IHVuZGVmaW5lZFwiPlxuICAgICAgICAgIDxpb24taWNvbiBuYW1lPVwiZXhwYW5kLW91dGxpbmVcIiBzbG90PVwiaWNvbi1vbmx5XCI+PC9pb24taWNvbj5cbiAgICAgICAgPC9pb24tYnV0dG9uPlxuICAgICAgICA8aW9uLWJ1dHRvbiAoY2xpY2spPVwiY2hhbmdlQ3JvcCgnMTYvOScpXCIgW2NvbG9yXT1cIiRjdXJyZW50Q3JvcCgpID09PSAnMTYvOScgPyAnc3VjY2VzcycgOiB1bmRlZmluZWRcIj5cbiAgICAgICAgICA8aW9uLWljb24gbmFtZT1cInRhYmxldC1sYW5kc2NhcGUtb3V0bGluZVwiIHNsb3Q9XCJpY29uLW9ubHlcIiBzdHlsZT1cInRyYW5zZm9ybTogc2NhbGUoMSwgMC44KVwiPjwvaW9uLWljb24+XG4gICAgICAgIDwvaW9uLWJ1dHRvbj5cbiAgICAgICAgPGlvbi1idXR0b24gKGNsaWNrKT1cImNoYW5nZUNyb3AoJzEnKVwiIFtjb2xvcl09XCIkY3VycmVudENyb3AoKSA9PT0gJzEnID8gJ3N1Y2Nlc3MnIDogdW5kZWZpbmVkXCI+XG4gICAgICAgICAgPGlvbi1pY29uIG5hbWU9XCJzcXVhcmUtb3V0bGluZVwiIHNsb3Q9XCJpY29uLW9ubHlcIj48L2lvbi1pY29uPlxuICAgICAgICA8L2lvbi1idXR0b24+XG4gICAgICAgIDxpb24tYnV0dG9uIChjbGljayk9XCJjaGFuZ2VDcm9wKCdhdXRvJylcIiBbY29sb3JdPVwiJGN1cnJlbnRDcm9wKCkgPT09ICdhdXRvJyA/ICdzdWNjZXNzJyA6IHVuZGVmaW5lZFwiPlxuICAgICAgICAgIDxpb24taWNvbiBuYW1lPVwiY3JvcC1vdXRsaW5lXCIgc2xvdD1cImljb24tb25seVwiPjwvaW9uLWljb24+XG4gICAgICAgIDwvaW9uLWJ1dHRvbj5cbiAgICAgICAgPGlvbi1idXR0b24gKGNsaWNrKT1cInJvdGF0ZSgpXCI+PGlvbi1pY29uIG5hbWU9XCJyZWZyZXNoLW91dGxpbmVcIiBzbG90PVwiaWNvbi1vbmx5XCI+PC9pb24taWNvbj48L2lvbi1idXR0b24+XG4gICAgICA8L2lvbi1idXR0b25zPlxuICAgIH1cblxuICAgIEBpZiAoWydicmlnaHRuZXNzJ10uaW5jbHVkZXModGhpcy4kZm9vdGVyTWVudSgpKSkge1xuICAgICAgPGRpdiBjbGFzcz1cImlvbi1wYWRkaW5nIGlvbi1tYXJnaW5cIj5cbiAgICAgICAgPGlvbi1yYW5nZSBbcGluXT1cInRydWVcIiBbbWluXT1cIi0xMDBcIiBbbWF4XT1cIjEwMFwiIChpb25DaGFuZ2UpPVwiY2hhbmdlUmFuZ2UoJGFueSgkZXZlbnQpKVwiPjwvaW9uLXJhbmdlPlxuICAgICAgPC9kaXY+XG4gICAgfVxuICA8L2lvbi10b29sYmFyPlxuICBAaWYgKHRoaXMuJGZvb3Rlck1lbnUoKSAhPT0gJ21lbnUnKSB7XG4gICAgPGlvbi10b29sYmFyIG1vZGU9XCJtZFwiPlxuICAgICAgQGlmICh0aGlzLiRmb290ZXJNZW51KCkgPT09ICdmaWx0ZXInKSB7XG4gICAgICAgIDxpb24tYnV0dG9ucyBjbGFzcz1cImlvbi1qdXN0aWZ5LWNvbnRlbnQtY2VudGVyXCIgc3R5bGU9XCJtYXJnaW46IDAgOHB4XCI+XG4gICAgICAgICAgPGlvbi1idXR0b24gZmlsbD1cIm91dGxpbmVcIiBzaGFwZT1cInJvdW5kXCIgKGNsaWNrKT1cInRoaXMuJGZvb3Rlck1lbnUuc2V0KCdtZW51JylcIj7miLvjgos8L2lvbi1idXR0b24+XG4gICAgICAgIDwvaW9uLWJ1dHRvbnM+XG4gICAgICB9IEBlbHNlIHtcbiAgICAgICAgPGlvbi1idXR0b25zIHNsb3Q9XCJzdGFydFwiIHN0eWxlPVwibWluLXdpZHRoOiA2MHB4XCI+XG4gICAgICAgICAgQGlmICghcmVxdWlyZVNxdWFyZSB8fCAkaXNDcm9wcGVkKCkpIHtcbiAgICAgICAgICAgIDxpb24tYnV0dG9uIChjbGljayk9XCJjbG9zZUNyb3AoJ2NhbmNlbCcpXCI+PGlvbi1pY29uIG5hbWU9XCJjbG9zZS1vdXRsaW5lXCIgc2xvdD1cImljb24tb25seVwiPjwvaW9uLWljb24+PC9pb24tYnV0dG9uPlxuICAgICAgICAgIH08L2lvbi1idXR0b25zXG4gICAgICAgID48aW9uLXRleHQgY2xhc3M9XCJmb290ZXItdGl0bGVcIj5cbiAgICAgICAgICBAaWYgKCRmb290ZXJNZW51KCkgPT09ICdjcm9wJykge1xuICAgICAgICAgICAge3sgZGljdGlvbmFyeS5jcm9wIH19XG4gICAgICAgICAgfSBAZWxzZSBpZiAoJGZvb3Rlck1lbnUoKSA9PT0gJ2JyaWdodG5lc3MnKSB7XG4gICAgICAgICAgICB7eyBkaWN0aW9uYXJ5LmJyaWdodG5lc3MgfX1cbiAgICAgICAgICB9PC9pb24tdGV4dFxuICAgICAgICA+PGlvbi1idXR0b25zIHNsb3Q9XCJlbmRcIlxuICAgICAgICAgID48aW9uLWJ1dHRvbiAoY2xpY2spPVwiY2xvc2VDcm9wKCdhcHBseScpXCJcbiAgICAgICAgICAgID48aW9uLWljb24gbmFtZT1cImNoZWNrbWFyay1vdXRsaW5lXCIgc2xvdD1cImljb24tb25seVwiIGNvbG9yPVwic3VjY2Vzc1wiPjwvaW9uLWljb24+PC9pb24tYnV0dG9uXG4gICAgICAgID48L2lvbi1idXR0b25zPlxuICAgICAgfVxuICAgIDwvaW9uLXRvb2xiYXI+XG4gIH1cbjwvaW9uLWZvb3Rlcj5cbiJdfQ==
@@ -0,0 +1,41 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { addIcons } from 'ionicons';
3
+ import { checkmarkOutline, closeOutline, colorFilterOutline, cropOutline, expandOutline, refreshOutline, send, squareOutline, sunnyOutline, tabletLandscapeOutline, } from 'ionicons/icons';
4
+ import * as i0 from "@angular/core";
5
+ export class PhotoEditorService {
6
+ constructor() { }
7
+ initializeIcons() {
8
+ addIcons({
9
+ closeOutline,
10
+ send,
11
+ cropOutline,
12
+ colorFilterOutline,
13
+ sunnyOutline,
14
+ expandOutline,
15
+ tabletLandscapeOutline,
16
+ squareOutline,
17
+ refreshOutline,
18
+ checkmarkOutline,
19
+ });
20
+ }
21
+ waitToFindDom(nativeElement, selector) {
22
+ return new Promise((resolve) => {
23
+ const interval = setInterval(() => {
24
+ const find = nativeElement.querySelector(selector);
25
+ if (find) {
26
+ clearInterval(interval);
27
+ resolve();
28
+ }
29
+ });
30
+ });
31
+ }
32
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.6", ngImport: i0, type: PhotoEditorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
33
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.6", ngImport: i0, type: PhotoEditorService, providedIn: 'root' }); }
34
+ }
35
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.6", ngImport: i0, type: PhotoEditorService, decorators: [{
36
+ type: Injectable,
37
+ args: [{
38
+ providedIn: 'root',
39
+ }]
40
+ }], ctorParameters: () => [] });
41
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGhvdG8tZWRpdG9yLnNlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy9waG90by1lZGl0b3Ivc3JjL2xpYi9zZXJ2aWNlL3Bob3RvLWVkaXRvci5zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDM0MsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLFVBQVUsQ0FBQztBQUNwQyxPQUFPLEVBQ0wsZ0JBQWdCLEVBQ2hCLFlBQVksRUFDWixrQkFBa0IsRUFDbEIsV0FBVyxFQUNYLGFBQWEsRUFDYixjQUFjLEVBQ2QsSUFBSSxFQUNKLGFBQWEsRUFDYixZQUFZLEVBQ1osc0JBQXNCLEdBQ3ZCLE1BQU0sZ0JBQWdCLENBQUM7O0FBS3hCLE1BQU0sT0FBTyxrQkFBa0I7SUFDN0IsZ0JBQWUsQ0FBQztJQUVoQixlQUFlO1FBQ2IsUUFBUSxDQUFDO1lBQ1AsWUFBWTtZQUNaLElBQUk7WUFDSixXQUFXO1lBQ1gsa0JBQWtCO1lBQ2xCLFlBQVk7WUFDWixhQUFhO1lBQ2Isc0JBQXNCO1lBQ3RCLGFBQWE7WUFDYixjQUFjO1lBQ2QsZ0JBQWdCO1NBQ2pCLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRCxhQUFhLENBQUMsYUFBMEIsRUFBRSxRQUFnQjtRQUN4RCxPQUFPLElBQUksT0FBTyxDQUFPLENBQUMsT0FBTyxFQUFFLEVBQUU7WUFDbkMsTUFBTSxRQUFRLEdBQUcsV0FBVyxDQUFDLEdBQUcsRUFBRTtnQkFDaEMsTUFBTSxJQUFJLEdBQUcsYUFBYSxDQUFDLGFBQWEsQ0FBQyxRQUFRLENBQUMsQ0FBQztnQkFDbkQsSUFBSSxJQUFJLEVBQUU7b0JBQ1IsYUFBYSxDQUFDLFFBQVEsQ0FBQyxDQUFDO29CQUN4QixPQUFPLEVBQUUsQ0FBQztpQkFDWDtZQUNILENBQUMsQ0FBQyxDQUFDO1FBQ0wsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDOzhHQTVCVSxrQkFBa0I7a0hBQWxCLGtCQUFrQixjQUZqQixNQUFNOzsyRkFFUCxrQkFBa0I7a0JBSDlCLFVBQVU7bUJBQUM7b0JBQ1YsVUFBVSxFQUFFLE1BQU07aUJBQ25CIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW5qZWN0YWJsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgYWRkSWNvbnMgfSBmcm9tICdpb25pY29ucyc7XG5pbXBvcnQge1xuICBjaGVja21hcmtPdXRsaW5lLFxuICBjbG9zZU91dGxpbmUsXG4gIGNvbG9yRmlsdGVyT3V0bGluZSxcbiAgY3JvcE91dGxpbmUsXG4gIGV4cGFuZE91dGxpbmUsXG4gIHJlZnJlc2hPdXRsaW5lLFxuICBzZW5kLFxuICBzcXVhcmVPdXRsaW5lLFxuICBzdW5ueU91dGxpbmUsXG4gIHRhYmxldExhbmRzY2FwZU91dGxpbmUsXG59IGZyb20gJ2lvbmljb25zL2ljb25zJztcblxuQEluamVjdGFibGUoe1xuICBwcm92aWRlZEluOiAncm9vdCcsXG59KVxuZXhwb3J0IGNsYXNzIFBob3RvRWRpdG9yU2VydmljZSB7XG4gIGNvbnN0cnVjdG9yKCkge31cblxuICBpbml0aWFsaXplSWNvbnMoKTogdm9pZCB7XG4gICAgYWRkSWNvbnMoe1xuICAgICAgY2xvc2VPdXRsaW5lLFxuICAgICAgc2VuZCxcbiAgICAgIGNyb3BPdXRsaW5lLFxuICAgICAgY29sb3JGaWx0ZXJPdXRsaW5lLFxuICAgICAgc3VubnlPdXRsaW5lLFxuICAgICAgZXhwYW5kT3V0bGluZSxcbiAgICAgIHRhYmxldExhbmRzY2FwZU91dGxpbmUsXG4gICAgICBzcXVhcmVPdXRsaW5lLFxuICAgICAgcmVmcmVzaE91dGxpbmUsXG4gICAgICBjaGVja21hcmtPdXRsaW5lLFxuICAgIH0pO1xuICB9XG5cbiAgd2FpdFRvRmluZERvbShuYXRpdmVFbGVtZW50OiBIVE1MRWxlbWVudCwgc2VsZWN0b3I6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIHJldHVybiBuZXcgUHJvbWlzZTx2b2lkPigocmVzb2x2ZSkgPT4ge1xuICAgICAgY29uc3QgaW50ZXJ2YWwgPSBzZXRJbnRlcnZhbCgoKSA9PiB7XG4gICAgICAgIGNvbnN0IGZpbmQgPSBuYXRpdmVFbGVtZW50LnF1ZXJ5U2VsZWN0b3Ioc2VsZWN0b3IpO1xuICAgICAgICBpZiAoZmluZCkge1xuICAgICAgICAgIGNsZWFySW50ZXJ2YWwoaW50ZXJ2YWwpO1xuICAgICAgICAgIHJlc29sdmUoKTtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgfSk7XG4gIH1cbn1cbiJdfQ==
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wcm9qZWN0cy9waG90by1lZGl0b3Ivc3JjL2xpYi90eXBlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGludGVyZmFjZSBJRmlsdGVyIHtcbiAgbmFtZTogc3RyaW5nO1xuICB0eXBlOiBzdHJpbmc7XG4gIG9wdGlvbjogYW55O1xuICBkYXRhOiBzdHJpbmc7XG4gIHdpZHRoOiBudW1iZXI7XG4gIGhlaWdodDogbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIElTaXplIHtcbiAgd2lkdGg6IG51bWJlcjtcbiAgaGVpZ2h0OiBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgSURpY3Rpb25hcnkge1xuICBzYXZlOiBzdHJpbmc7XG4gIGNyb3A6IHN0cmluZztcbiAgZmlsdGVyOiBzdHJpbmc7XG4gIGJyaWdodG5lc3M6IHN0cmluZztcbiAgb3JpZ2luYWw6IHN0cmluZztcbiAgaW52ZXJ0OiBzdHJpbmc7XG4gIHNlcGlhOiBzdHJpbmc7XG4gIHZpbnRhZ2U6IHN0cmluZztcbiAgYmx1cjogc3RyaW5nO1xuICBncmF5c2NhbGU6IHN0cmluZztcbiAgc2hhcnBlbjogc3RyaW5nO1xuICBlbWJvc3M6IHN0cmluZztcbn1cblxuZXhwb3J0IGludGVyZmFjZSBJRmlsdGVyUHJlc2V0IHtcbiAgbmFtZTogc3RyaW5nO1xuICB0eXBlOiBzdHJpbmc7XG4gIG9wdGlvbjogYW55O1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIElQaG90b0VkaXRvckRpc21pc3Mge1xuICB2YWx1ZTogc3RyaW5nO1xufVxuIl19
@@ -0,0 +1,6 @@
1
+ /*
2
+ * Public API Surface of photo-editor
3
+ */
4
+ export * from './lib/page/photo-editor.page';
5
+ export * from './lib/types';
6
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3Byb2plY3RzL3Bob3RvLWVkaXRvci9zcmMvcHVibGljLWFwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILGNBQWMsOEJBQThCLENBQUM7QUFDN0MsY0FBYyxhQUFhLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuICogUHVibGljIEFQSSBTdXJmYWNlIG9mIHBob3RvLWVkaXRvclxuICovXG5cbmV4cG9ydCAqIGZyb20gJy4vbGliL3BhZ2UvcGhvdG8tZWRpdG9yLnBhZ2UnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvdHlwZXMnO1xuIl19
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ export * from './public-api';
5
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmRsYWJvLWlvbmljLWFuZ3VsYXItcGhvdG8tZWRpdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vcHJvamVjdHMvcGhvdG8tZWRpdG9yL3NyYy9yZGxhYm8taW9uaWMtYW5ndWxhci1waG90by1lZGl0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLGNBQWMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2VuZXJhdGVkIGJ1bmRsZSBpbmRleC4gRG8gbm90IGVkaXQuXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9wdWJsaWMtYXBpJztcbiJdfQ==
@@ -0,0 +1,295 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, signal, inject, Component, Input, ViewChild } from '@angular/core';
3
+ import { CommonModule, NgOptimizedImage } from '@angular/common';
4
+ import { FormsModule } from '@angular/forms';
5
+ import * as i1 from '@ionic/angular/standalone';
6
+ import { IonHeader, IonToolbar, IonButtons, IonButton, IonIcon, IonContent, IonFooter, IonText, IonRange, ModalController } from '@ionic/angular/standalone';
7
+ import ImageEditor from 'tui-image-editor';
8
+ import { toObservable } from '@angular/core/rxjs-interop';
9
+ import { addIcons } from 'ionicons';
10
+ import { closeOutline, send, cropOutline, colorFilterOutline, sunnyOutline, expandOutline, tabletLandscapeOutline, squareOutline, refreshOutline, checkmarkOutline } from 'ionicons/icons';
11
+
12
+ const filterPreset = (dictionary) => [
13
+ {
14
+ name: dictionary.original,
15
+ type: 'Default',
16
+ option: null,
17
+ },
18
+ // {
19
+ // name: dictionary.invert,
20
+ // type: 'Invert',
21
+ // option: null,
22
+ // },
23
+ {
24
+ name: dictionary.sepia,
25
+ type: 'Sepia',
26
+ option: null,
27
+ },
28
+ {
29
+ name: dictionary.vintage,
30
+ type: 'vintage',
31
+ option: null,
32
+ },
33
+ {
34
+ name: 'ぼかし',
35
+ type: 'Blur',
36
+ option: { blur: 0.1 },
37
+ },
38
+ {
39
+ name: dictionary.grayscale,
40
+ type: 'Grayscale',
41
+ option: null,
42
+ },
43
+ // {
44
+ // name: dictionary.sharpen,
45
+ // type: 'Sharpen',
46
+ // option: null,
47
+ // },
48
+ // {
49
+ // name: dictionary.emboss,
50
+ // type: 'Emboss',
51
+ // option: null,
52
+ // },
53
+ ];
54
+
55
+ class PhotoEditorService {
56
+ constructor() { }
57
+ initializeIcons() {
58
+ addIcons({
59
+ closeOutline,
60
+ send,
61
+ cropOutline,
62
+ colorFilterOutline,
63
+ sunnyOutline,
64
+ expandOutline,
65
+ tabletLandscapeOutline,
66
+ squareOutline,
67
+ refreshOutline,
68
+ checkmarkOutline,
69
+ });
70
+ }
71
+ waitToFindDom(nativeElement, selector) {
72
+ return new Promise((resolve) => {
73
+ const interval = setInterval(() => {
74
+ const find = nativeElement.querySelector(selector);
75
+ if (find) {
76
+ clearInterval(interval);
77
+ resolve();
78
+ }
79
+ });
80
+ });
81
+ }
82
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.6", ngImport: i0, type: PhotoEditorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
83
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.6", ngImport: i0, type: PhotoEditorService, providedIn: 'root' }); }
84
+ }
85
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.6", ngImport: i0, type: PhotoEditorService, decorators: [{
86
+ type: Injectable,
87
+ args: [{
88
+ providedIn: 'root',
89
+ }]
90
+ }], ctorParameters: () => [] });
91
+
92
+ const ionComponents = [IonHeader, IonToolbar, IonButtons, IonButton, IonIcon, IonContent, IonFooter, IonText, IonRange];
93
+
94
+ const dictionary = () => ({
95
+ // UI labels
96
+ save: '保存',
97
+ crop: '切り抜き・回転',
98
+ filter: 'フィルター',
99
+ brightness: '明るさ',
100
+ // Filter labels
101
+ original: 'オリジナル',
102
+ invert: '反転',
103
+ sepia: 'セピア',
104
+ vintage: 'ヴィンテージ',
105
+ blur: 'ぼかし',
106
+ grayscale: 'グレースケール',
107
+ sharpen: '輪郭',
108
+ emboss: 'エンボス',
109
+ });
110
+
111
+ class PhotoEditorPage {
112
+ set label(d) {
113
+ this.dictionary = Object.assign(this.dictionary, d);
114
+ this.filterPreset = filterPreset(this.dictionary);
115
+ }
116
+ constructor() {
117
+ this.dictionary = dictionary();
118
+ this.filterPreset = filterPreset(this.dictionary);
119
+ this.requireSquare = false;
120
+ this.$filters = signal([]);
121
+ this.$footerMenu = signal('menu');
122
+ this.$currentCrop = signal('cover');
123
+ this.$currentRotate = signal(0);
124
+ this.$photoCrop = signal({
125
+ width: 0,
126
+ height: 0,
127
+ });
128
+ this.$isCropped = signal(false);
129
+ this.footerMenu$ = toObservable(this.$footerMenu);
130
+ this.$adoptFilter = signal(undefined);
131
+ this.initSubscription$ = [];
132
+ this.filterImageSize = 240;
133
+ this.service = inject(PhotoEditorService);
134
+ this.modalCtrl = inject(ModalController);
135
+ this.canvasContainerObserver = new MutationObserver((mutationsList) => {
136
+ if (mutationsList.find((mutation) => mutation.type === 'attributes' && mutation.attributeName === 'style')) {
137
+ // Cover the image editor with the parent element
138
+ this.editorRef.nativeElement.style.minWidth = mutationsList[0].target.parentElement.style.maxWidth;
139
+ this.editorRef.nativeElement.style.minHeight = mutationsList[0].target.parentElement.style.maxHeight;
140
+ this.$photoCrop.set({
141
+ width: mutationsList[0].target.parentElement.querySelector('canvas').width,
142
+ height: mutationsList[0].target.parentElement.querySelector('canvas').height,
143
+ });
144
+ }
145
+ });
146
+ this.service.initializeIcons();
147
+ }
148
+ ngOnInit() {
149
+ this.initSubscription$.push(this.footerMenu$.subscribe((value) => {
150
+ if (value === 'filter') {
151
+ this.initializeFilterMenu().then();
152
+ }
153
+ if (value === 'crop') {
154
+ this.editorInstance.startDrawingMode('CROPPER');
155
+ this.changeCrop(this.requireSquare ? '1' : 'cover');
156
+ }
157
+ }));
158
+ }
159
+ ngOnDestroy() {
160
+ this.initSubscription$.forEach((subscription) => subscription.unsubscribe());
161
+ }
162
+ async ionViewDidEnter() {
163
+ this.editorInstance = new ImageEditor(this.editorRef.nativeElement, {
164
+ cssMaxWidth: this.ionContent.el.clientWidth - 32,
165
+ cssMaxHeight: this.ionContent.el.clientHeight - 32,
166
+ });
167
+ this.service.waitToFindDom(this.editorRef.nativeElement, '.tui-image-editor-canvas-container').then(() => {
168
+ this.canvasContainerObserver.observe(this.editorRef.nativeElement.querySelector('.tui-image-editor-canvas-container'), {
169
+ attributes: true,
170
+ childList: false,
171
+ subtree: true,
172
+ });
173
+ });
174
+ const blob = await fetch(this.value).then((res) => res.blob());
175
+ await this.editorInstance.loadImageFromFile(new File([blob], 'data.png', { type: blob.type }));
176
+ this.$footerMenu.set(this.requireSquare ? 'crop' : 'menu');
177
+ }
178
+ ionViewDidLeave() {
179
+ this.editorInstance.destroy();
180
+ this.canvasContainerObserver.disconnect();
181
+ }
182
+ changeCrop(crop) {
183
+ const rect = crop === 'cover' ? this.$photoCrop().width / this.$photoCrop().height : crop === '16/9' ? 16 / 9 : 1;
184
+ this.editorInstance.setCropzoneRect(crop !== 'auto' ? rect : undefined);
185
+ this.$currentCrop.set(crop);
186
+ }
187
+ async rotate() {
188
+ this.editorInstance.stopDrawingMode();
189
+ await this.editorInstance.rotate(90);
190
+ this.$currentRotate.update((value) => value + 90);
191
+ this.editorInstance.startDrawingMode('CROPPER');
192
+ requestAnimationFrame(() => this.changeCrop(this.$currentCrop()));
193
+ }
194
+ async closeCrop(type) {
195
+ if (this.$footerMenu() === 'crop') {
196
+ if (type === 'cancel') {
197
+ await this.editorInstance.rotate(this.$currentRotate() * -1);
198
+ }
199
+ else {
200
+ await this.editorInstance.crop(this.editorInstance.getCropzoneRect());
201
+ this.$isCropped.set(true);
202
+ }
203
+ this.$currentRotate.set(0);
204
+ this.$currentCrop.set('cover');
205
+ this.editorInstance.stopDrawingMode();
206
+ }
207
+ else if (this.$footerMenu() === 'brightness') {
208
+ if (type === 'cancel' && this.editorInstance.hasFilter('brightness')) {
209
+ await this.editorInstance.removeFilter('brightness');
210
+ }
211
+ }
212
+ this.$footerMenu.set('menu');
213
+ }
214
+ async changeRange(event) {
215
+ if (this.editorInstance.hasFilter('brightness')) {
216
+ await this.editorInstance.removeFilter('brightness');
217
+ }
218
+ this.editorInstance.applyFilter('brightness', {
219
+ brightness: Number(event.detail.value) / 255,
220
+ });
221
+ }
222
+ imageSave() {
223
+ const value = this.editorInstance.toDataURL();
224
+ this.modalCtrl.dismiss({ value });
225
+ }
226
+ async initializeFilterMenu() {
227
+ const filters = [];
228
+ const defaultInstance = new ImageEditor(document.createElement('div'), {
229
+ cssMaxWidth: this.filterImageSize,
230
+ cssMaxHeight: (this.$photoCrop().height * this.filterImageSize) / this.$photoCrop().width,
231
+ });
232
+ const blob = await fetch(this.editorInstance.toDataURL({
233
+ multiplier: this.filterImageSize / this.$photoCrop().width,
234
+ })).then((res) => res.blob());
235
+ await defaultInstance.loadImageFromFile(new File([blob], 'defaultInstance.png', { type: blob.type }));
236
+ for (const filter of this.filterPreset) {
237
+ if (filter.type !== 'Default') {
238
+ await defaultInstance.applyFilter(filter.type, filter.option);
239
+ }
240
+ filters.push({
241
+ name: filter.name,
242
+ type: filter.type,
243
+ option: filter.option,
244
+ data: defaultInstance.toDataURL(),
245
+ width: this.filterImageSize,
246
+ height: (this.$photoCrop().height * this.filterImageSize) / this.$photoCrop().width,
247
+ });
248
+ if (filter.type !== 'Default') {
249
+ await defaultInstance.removeFilter(filter.type);
250
+ }
251
+ }
252
+ this.$filters.set(filters);
253
+ defaultInstance.destroy();
254
+ }
255
+ async filterImage(filter) {
256
+ if (this.$adoptFilter()) {
257
+ await this.editorInstance.removeFilter(this.$adoptFilter().type);
258
+ }
259
+ if (filter.type === 'Default') {
260
+ this.$adoptFilter.set(undefined);
261
+ return;
262
+ }
263
+ await this.editorInstance.applyFilter(filter.type, filter.option);
264
+ this.$adoptFilter.set(filter);
265
+ }
266
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.6", ngImport: i0, type: PhotoEditorPage, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
267
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.0.6", type: PhotoEditorPage, isStandalone: true, selector: "app-editor-image", inputs: { requireSquare: "requireSquare", value: "value", label: "label" }, viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["imageEditor"], descendants: true, static: true }, { propertyName: "ionContent", first: true, predicate: IonContent, descendants: true }], ngImport: i0, template: "<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\"\n ><ion-button (click)=\"modalCtrl.dismiss()\"><ion-icon name=\"close-outline\" slot=\"icon-only\"></ion-icon></ion-button\n ></ion-buttons>\n <ion-buttons slot=\"end\">\n <ion-button color=\"primary\" fill=\"outline\" [disabled]=\"!['menu', 'filter'].includes($footerMenu())\" (click)=\"imageSave()\">\n {{ dictionary.save }}\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\" [scrollY]=\"false\">\n <div #imageEditor></div>\n</ion-content>\n<ion-footer>\n <ion-toolbar>\n @if (this.$footerMenu() === 'menu') {\n <aside class=\"ion-justify-content-center\">\n <button (click)=\"$footerMenu.set('crop')\">\n <ion-text>{{ dictionary.crop }}</ion-text>\n <ion-icon name=\"crop-outline\" size=\"large\"></ion-icon>\n </button>\n <button (click)=\"$footerMenu.set('filter')\">\n <ion-text>{{ dictionary.filter }}</ion-text>\n <ion-icon name=\"color-filter-outline\" size=\"large\"></ion-icon>\n </button>\n <button (click)=\"$footerMenu.set('brightness')\">\n <ion-text>{{ dictionary.brightness }}</ion-text>\n <ion-icon name=\"sunny-outline\" size=\"large\"></ion-icon>\n </button>\n </aside>\n }\n\n @if (this.$footerMenu() === 'filter') {\n <aside>\n @for (item of $filters(); track item) {\n <button (click)=\"filterImage(item)\">\n <ion-text>{{ item.name }}</ion-text>\n <span class=\"image-filter-box\" [style.background-image]=\"'url(' + item.data + ')'\"></span>\n </button>\n }\n </aside>\n }\n\n @if (this.$footerMenu() === 'crop' && !requireSquare) {\n <ion-buttons class=\"ion-justify-content-center submenu-icon-buttons\">\n <ion-button (click)=\"changeCrop('cover')\" [color]=\"$currentCrop() === 'cover' ? 'success' : undefined\">\n <ion-icon name=\"expand-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"changeCrop('16/9')\" [color]=\"$currentCrop() === '16/9' ? 'success' : undefined\">\n <ion-icon name=\"tablet-landscape-outline\" slot=\"icon-only\" style=\"transform: scale(1, 0.8)\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"changeCrop('1')\" [color]=\"$currentCrop() === '1' ? 'success' : undefined\">\n <ion-icon name=\"square-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"changeCrop('auto')\" [color]=\"$currentCrop() === 'auto' ? 'success' : undefined\">\n <ion-icon name=\"crop-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"rotate()\"><ion-icon name=\"refresh-outline\" slot=\"icon-only\"></ion-icon></ion-button>\n </ion-buttons>\n }\n\n @if (['brightness'].includes(this.$footerMenu())) {\n <div class=\"ion-padding ion-margin\">\n <ion-range [pin]=\"true\" [min]=\"-100\" [max]=\"100\" (ionChange)=\"changeRange($any($event))\"></ion-range>\n </div>\n }\n </ion-toolbar>\n @if (this.$footerMenu() !== 'menu') {\n <ion-toolbar mode=\"md\">\n @if (this.$footerMenu() === 'filter') {\n <ion-buttons class=\"ion-justify-content-center\" style=\"margin: 0 8px\">\n <ion-button fill=\"outline\" shape=\"round\" (click)=\"this.$footerMenu.set('menu')\">\u623B\u308B</ion-button>\n </ion-buttons>\n } @else {\n <ion-buttons slot=\"start\" style=\"min-width: 60px\">\n @if (!requireSquare || $isCropped()) {\n <ion-button (click)=\"closeCrop('cancel')\"><ion-icon name=\"close-outline\" slot=\"icon-only\"></ion-icon></ion-button>\n }</ion-buttons\n ><ion-text class=\"footer-title\">\n @if ($footerMenu() === 'crop') {\n {{ dictionary.crop }}\n } @else if ($footerMenu() === 'brightness') {\n {{ dictionary.brightness }}\n }</ion-text\n ><ion-buttons slot=\"end\"\n ><ion-button (click)=\"closeCrop('apply')\"\n ><ion-icon name=\"checkmark-outline\" slot=\"icon-only\" color=\"success\"></ion-icon></ion-button\n ></ion-buttons>\n }\n </ion-toolbar>\n }\n</ion-footer>\n", styles: ["@charset \"UTF-8\";:host{--alway-dark: var(--ion-color-step-100, #2a2a2a);--alway-dark-tint: var(--ion-color-step-200, #414141);--alway-white: var(--ion-color-step-950, #f0f0f0);--alway-white-tint: var(--ion-color-step-850, #dbdbdb)}ion-content,ion-toolbar,ion-header,ion-footer{background:var(--alway-dark);--background: var(--alway-dark)}ion-content{--background: var(--alway-dark)}ion-content>div{width:100%;height:100%;max-width:100%;max-height:100%;display:grid;align-items:center;justify-items:center}aside{display:flex;overflow-x:scroll;margin-top:16px;min-height:120px}aside ion-icon{color:var(--alway-white);border:1px solid var(--alway-dark-tint);border-radius:50%;padding:16px}aside button{width:120px;display:block;flex:1;background:transparent;margin:0 8px 16px}aside button ion-text{display:block;color:var(--alway-white);margin-bottom:8px;font-size:.8rem}aside button span.image-filter-box{width:96px;height:64px;border-radius:8px;overflow:hidden;display:block;background-position:center,center;background-size:cover}ion-header ion-button,ion-footer ion-button{--color: var(--alway-white) !important}ion-footer ion-toolbar ion-text.footer-title{color:var(--alway-white)!important;display:block;text-align:center}ion-footer ion-toolbar ion-range{--bar-background: var(--alway-white);--bar-background-active: var(--alway-white);--bar-height: 2px;--bar-border-radius: 8px;--knob-background: var(--alway-white);--knob-size: 20px;--pin-background: none;--pin-color: var(--alway-white)}ion-footer ion-toolbar ion-buttons[slot=start] ion-button,ion-footer ion-toolbar ion-buttons[slot=end] ion-button{--background: var(--alway-dark-tint) !important;--border-radius: 50%;margin:0 8px;min-width:32px;min-height:32px}ion-footer ion-toolbar ion-buttons.submenu-icon-buttons ion-button{margin:16px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: i1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i1.IonRange, selector: "ion-range", inputs: ["activeBarStart", "color", "debounce", "disabled", "dualKnobs", "label", "labelPlacement", "legacy", "max", "min", "mode", "name", "pin", "pinFormatter", "snaps", "step", "ticks", "value"] }] }); }
268
+ }
269
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.6", ngImport: i0, type: PhotoEditorPage, decorators: [{
270
+ type: Component,
271
+ args: [{ selector: 'app-editor-image', standalone: true, imports: [CommonModule, FormsModule, NgOptimizedImage, ...ionComponents], template: "<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\"\n ><ion-button (click)=\"modalCtrl.dismiss()\"><ion-icon name=\"close-outline\" slot=\"icon-only\"></ion-icon></ion-button\n ></ion-buttons>\n <ion-buttons slot=\"end\">\n <ion-button color=\"primary\" fill=\"outline\" [disabled]=\"!['menu', 'filter'].includes($footerMenu())\" (click)=\"imageSave()\">\n {{ dictionary.save }}\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\" [scrollY]=\"false\">\n <div #imageEditor></div>\n</ion-content>\n<ion-footer>\n <ion-toolbar>\n @if (this.$footerMenu() === 'menu') {\n <aside class=\"ion-justify-content-center\">\n <button (click)=\"$footerMenu.set('crop')\">\n <ion-text>{{ dictionary.crop }}</ion-text>\n <ion-icon name=\"crop-outline\" size=\"large\"></ion-icon>\n </button>\n <button (click)=\"$footerMenu.set('filter')\">\n <ion-text>{{ dictionary.filter }}</ion-text>\n <ion-icon name=\"color-filter-outline\" size=\"large\"></ion-icon>\n </button>\n <button (click)=\"$footerMenu.set('brightness')\">\n <ion-text>{{ dictionary.brightness }}</ion-text>\n <ion-icon name=\"sunny-outline\" size=\"large\"></ion-icon>\n </button>\n </aside>\n }\n\n @if (this.$footerMenu() === 'filter') {\n <aside>\n @for (item of $filters(); track item) {\n <button (click)=\"filterImage(item)\">\n <ion-text>{{ item.name }}</ion-text>\n <span class=\"image-filter-box\" [style.background-image]=\"'url(' + item.data + ')'\"></span>\n </button>\n }\n </aside>\n }\n\n @if (this.$footerMenu() === 'crop' && !requireSquare) {\n <ion-buttons class=\"ion-justify-content-center submenu-icon-buttons\">\n <ion-button (click)=\"changeCrop('cover')\" [color]=\"$currentCrop() === 'cover' ? 'success' : undefined\">\n <ion-icon name=\"expand-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"changeCrop('16/9')\" [color]=\"$currentCrop() === '16/9' ? 'success' : undefined\">\n <ion-icon name=\"tablet-landscape-outline\" slot=\"icon-only\" style=\"transform: scale(1, 0.8)\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"changeCrop('1')\" [color]=\"$currentCrop() === '1' ? 'success' : undefined\">\n <ion-icon name=\"square-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"changeCrop('auto')\" [color]=\"$currentCrop() === 'auto' ? 'success' : undefined\">\n <ion-icon name=\"crop-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"rotate()\"><ion-icon name=\"refresh-outline\" slot=\"icon-only\"></ion-icon></ion-button>\n </ion-buttons>\n }\n\n @if (['brightness'].includes(this.$footerMenu())) {\n <div class=\"ion-padding ion-margin\">\n <ion-range [pin]=\"true\" [min]=\"-100\" [max]=\"100\" (ionChange)=\"changeRange($any($event))\"></ion-range>\n </div>\n }\n </ion-toolbar>\n @if (this.$footerMenu() !== 'menu') {\n <ion-toolbar mode=\"md\">\n @if (this.$footerMenu() === 'filter') {\n <ion-buttons class=\"ion-justify-content-center\" style=\"margin: 0 8px\">\n <ion-button fill=\"outline\" shape=\"round\" (click)=\"this.$footerMenu.set('menu')\">\u623B\u308B</ion-button>\n </ion-buttons>\n } @else {\n <ion-buttons slot=\"start\" style=\"min-width: 60px\">\n @if (!requireSquare || $isCropped()) {\n <ion-button (click)=\"closeCrop('cancel')\"><ion-icon name=\"close-outline\" slot=\"icon-only\"></ion-icon></ion-button>\n }</ion-buttons\n ><ion-text class=\"footer-title\">\n @if ($footerMenu() === 'crop') {\n {{ dictionary.crop }}\n } @else if ($footerMenu() === 'brightness') {\n {{ dictionary.brightness }}\n }</ion-text\n ><ion-buttons slot=\"end\"\n ><ion-button (click)=\"closeCrop('apply')\"\n ><ion-icon name=\"checkmark-outline\" slot=\"icon-only\" color=\"success\"></ion-icon></ion-button\n ></ion-buttons>\n }\n </ion-toolbar>\n }\n</ion-footer>\n", styles: ["@charset \"UTF-8\";:host{--alway-dark: var(--ion-color-step-100, #2a2a2a);--alway-dark-tint: var(--ion-color-step-200, #414141);--alway-white: var(--ion-color-step-950, #f0f0f0);--alway-white-tint: var(--ion-color-step-850, #dbdbdb)}ion-content,ion-toolbar,ion-header,ion-footer{background:var(--alway-dark);--background: var(--alway-dark)}ion-content{--background: var(--alway-dark)}ion-content>div{width:100%;height:100%;max-width:100%;max-height:100%;display:grid;align-items:center;justify-items:center}aside{display:flex;overflow-x:scroll;margin-top:16px;min-height:120px}aside ion-icon{color:var(--alway-white);border:1px solid var(--alway-dark-tint);border-radius:50%;padding:16px}aside button{width:120px;display:block;flex:1;background:transparent;margin:0 8px 16px}aside button ion-text{display:block;color:var(--alway-white);margin-bottom:8px;font-size:.8rem}aside button span.image-filter-box{width:96px;height:64px;border-radius:8px;overflow:hidden;display:block;background-position:center,center;background-size:cover}ion-header ion-button,ion-footer ion-button{--color: var(--alway-white) !important}ion-footer ion-toolbar ion-text.footer-title{color:var(--alway-white)!important;display:block;text-align:center}ion-footer ion-toolbar ion-range{--bar-background: var(--alway-white);--bar-background-active: var(--alway-white);--bar-height: 2px;--bar-border-radius: 8px;--knob-background: var(--alway-white);--knob-size: 20px;--pin-background: none;--pin-color: var(--alway-white)}ion-footer ion-toolbar ion-buttons[slot=start] ion-button,ion-footer ion-toolbar ion-buttons[slot=end] ion-button{--background: var(--alway-dark-tint) !important;--border-radius: 50%;margin:0 8px;min-width:32px;min-height:32px}ion-footer ion-toolbar ion-buttons.submenu-icon-buttons ion-button{margin:16px}\n"] }]
272
+ }], ctorParameters: () => [], propDecorators: { requireSquare: [{
273
+ type: Input
274
+ }], value: [{
275
+ type: Input
276
+ }], label: [{
277
+ type: Input
278
+ }], editorRef: [{
279
+ type: ViewChild,
280
+ args: ['imageEditor', { static: true }]
281
+ }], ionContent: [{
282
+ type: ViewChild,
283
+ args: [IonContent]
284
+ }] } });
285
+
286
+ /*
287
+ * Public API Surface of photo-editor
288
+ */
289
+
290
+ /**
291
+ * Generated bundle index. Do not edit.
292
+ */
293
+
294
+ export { PhotoEditorPage };
295
+ //# sourceMappingURL=rdlabo-ionic-angular-photo-editor.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rdlabo-ionic-angular-photo-editor.mjs","sources":["../../../projects/photo-editor/src/lib/filter-preset.ts","../../../projects/photo-editor/src/lib/service/photo-editor.service.ts","../../../projects/photo-editor/src/lib/ion-components.ts","../../../projects/photo-editor/src/lib/dictionary.ts","../../../projects/photo-editor/src/lib/page/photo-editor.page.ts","../../../projects/photo-editor/src/lib/page/photo-editor.page.html","../../../projects/photo-editor/src/public-api.ts","../../../projects/photo-editor/src/rdlabo-ionic-angular-photo-editor.ts"],"sourcesContent":["import { IDictionary, IFilterPreset } from './types';\n\nexport const filterPreset = (dictionary: IDictionary): IFilterPreset[] => [\n {\n name: dictionary.original,\n type: 'Default',\n option: null,\n },\n // {\n // name: dictionary.invert,\n // type: 'Invert',\n // option: null,\n // },\n {\n name: dictionary.sepia,\n type: 'Sepia',\n option: null,\n },\n {\n name: dictionary.vintage,\n type: 'vintage',\n option: null,\n },\n {\n name: 'ぼかし',\n type: 'Blur',\n option: { blur: 0.1 },\n },\n {\n name: dictionary.grayscale,\n type: 'Grayscale',\n option: null,\n },\n // {\n // name: dictionary.sharpen,\n // type: 'Sharpen',\n // option: null,\n // },\n // {\n // name: dictionary.emboss,\n // type: 'Emboss',\n // option: null,\n // },\n];\n","import { Injectable } from '@angular/core';\nimport { addIcons } from 'ionicons';\nimport {\n checkmarkOutline,\n closeOutline,\n colorFilterOutline,\n cropOutline,\n expandOutline,\n refreshOutline,\n send,\n squareOutline,\n sunnyOutline,\n tabletLandscapeOutline,\n} from 'ionicons/icons';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class PhotoEditorService {\n constructor() {}\n\n initializeIcons(): void {\n addIcons({\n closeOutline,\n send,\n cropOutline,\n colorFilterOutline,\n sunnyOutline,\n expandOutline,\n tabletLandscapeOutline,\n squareOutline,\n refreshOutline,\n checkmarkOutline,\n });\n }\n\n waitToFindDom(nativeElement: HTMLElement, selector: string): Promise<void> {\n return new Promise<void>((resolve) => {\n const interval = setInterval(() => {\n const find = nativeElement.querySelector(selector);\n if (find) {\n clearInterval(interval);\n resolve();\n }\n });\n });\n }\n}\n","import { IonButton, IonButtons, IonContent, IonFooter, IonHeader, IonIcon, IonRange, IonText, IonToolbar } from '@ionic/angular/standalone';\n\nexport const ionComponents = [IonHeader, IonToolbar, IonButtons, IonButton, IonIcon, IonContent, IonFooter, IonText, IonRange];\n","import { IDictionary } from './types';\n\nexport const dictionary = (): IDictionary => ({\n // UI labels\n save: '保存',\n crop: '切り抜き・回転',\n filter: 'フィルター',\n brightness: '明るさ',\n\n // Filter labels\n original: 'オリジナル',\n invert: '反転',\n sepia: 'セピア',\n vintage: 'ヴィンテージ',\n blur: 'ぼかし',\n grayscale: 'グレースケール',\n sharpen: '輪郭',\n emboss: 'エンボス',\n});\n","import { Component, ElementRef, inject, Input, OnDestroy, OnInit, signal, ViewChild } from '@angular/core';\nimport { CommonModule, NgOptimizedImage } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { IonContent, ModalController, RangeCustomEvent, ViewDidEnter, ViewDidLeave } from '@ionic/angular/standalone';\nimport ImageEditor from 'tui-image-editor';\nimport { filterPreset } from '../filter-preset';\nimport { Subscription } from 'rxjs';\nimport { toObservable } from '@angular/core/rxjs-interop';\nimport { IDictionary, IPhotoEditorDismiss, IFilter, ISize } from '../types';\nimport { PhotoEditorService } from '../service/photo-editor.service';\nimport { ionComponents } from '../ion-components';\nimport { dictionary } from '../dictionary';\n\n@Component({\n selector: 'app-editor-image',\n templateUrl: './photo-editor.page.html',\n styleUrls: ['./photo-editor.page.scss'],\n standalone: true,\n imports: [CommonModule, FormsModule, NgOptimizedImage, ...ionComponents],\n})\nexport class PhotoEditorPage implements OnInit, OnDestroy, ViewDidEnter, ViewDidLeave {\n protected dictionary: IDictionary = dictionary();\n protected filterPreset = filterPreset(this.dictionary);\n\n @Input() requireSquare: boolean = false;\n @Input() value!: string;\n @Input() set label(d: IDictionary) {\n this.dictionary = Object.assign(this.dictionary, d);\n this.filterPreset = filterPreset(this.dictionary);\n }\n\n @ViewChild('imageEditor', { static: true }) editorRef!: ElementRef;\n @ViewChild(IonContent) ionContent!: {\n el: IonContent & HTMLElement;\n };\n\n $filters = signal<IFilter[]>([]);\n $footerMenu = signal<'filter' | 'menu' | 'crop' | 'brightness'>('menu');\n $currentCrop = signal<'cover' | '16/9' | '1' | 'auto'>('cover');\n $currentRotate = signal<number>(0);\n $photoCrop = signal<ISize>({\n width: 0,\n height: 0,\n });\n $isCropped = signal<boolean>(false);\n\n private footerMenu$ = toObservable(this.$footerMenu);\n private $adoptFilter = signal<IFilter | undefined>(undefined);\n private editorInstance!: ImageEditor;\n private initSubscription$: Subscription[] = [];\n private readonly filterImageSize = 240;\n private readonly service = inject(PhotoEditorService);\n\n modalCtrl = inject(ModalController);\n\n private canvasContainerObserver: MutationObserver = new MutationObserver((mutationsList: MutationRecord[]) => {\n if (mutationsList.find((mutation) => mutation.type === 'attributes' && mutation.attributeName === 'style')) {\n // Cover the image editor with the parent element\n this.editorRef.nativeElement.style.minWidth = mutationsList[0].target.parentElement!.style.maxWidth;\n this.editorRef.nativeElement.style.minHeight = mutationsList[0].target.parentElement!.style.maxHeight;\n\n this.$photoCrop.set({\n width: mutationsList[0].target.parentElement!.querySelector('canvas')!.width,\n height: mutationsList[0].target.parentElement!.querySelector('canvas')!.height,\n });\n }\n });\n\n constructor() {\n this.service.initializeIcons();\n }\n\n ngOnInit() {\n this.initSubscription$.push(\n this.footerMenu$.subscribe((value) => {\n if (value === 'filter') {\n this.initializeFilterMenu().then();\n }\n if (value === 'crop') {\n this.editorInstance.startDrawingMode('CROPPER');\n this.changeCrop(this.requireSquare ? '1' : 'cover');\n }\n }),\n );\n }\n\n ngOnDestroy() {\n this.initSubscription$.forEach((subscription) => subscription.unsubscribe());\n }\n\n async ionViewDidEnter() {\n this.editorInstance = new ImageEditor(this.editorRef.nativeElement, {\n cssMaxWidth: this.ionContent.el.clientWidth - 32,\n cssMaxHeight: this.ionContent.el.clientHeight - 32,\n });\n this.service.waitToFindDom(this.editorRef.nativeElement, '.tui-image-editor-canvas-container').then(() => {\n this.canvasContainerObserver.observe(this.editorRef.nativeElement.querySelector('.tui-image-editor-canvas-container'), {\n attributes: true,\n childList: false,\n subtree: true,\n });\n });\n const blob = await fetch(this.value).then((res) => res.blob());\n await this.editorInstance.loadImageFromFile(new File([blob], 'data.png', { type: blob.type }));\n this.$footerMenu.set(this.requireSquare ? 'crop' : 'menu');\n }\n\n ionViewDidLeave() {\n this.editorInstance.destroy();\n this.canvasContainerObserver.disconnect();\n }\n\n changeCrop(crop: 'cover' | '16/9' | '1' | 'auto') {\n const rect = crop === 'cover' ? this.$photoCrop().width / this.$photoCrop().height : crop === '16/9' ? 16 / 9 : 1;\n this.editorInstance.setCropzoneRect(crop !== 'auto' ? rect : undefined);\n this.$currentCrop.set(crop);\n }\n\n async rotate() {\n this.editorInstance.stopDrawingMode();\n await this.editorInstance.rotate(90);\n this.$currentRotate.update((value) => value + 90);\n this.editorInstance.startDrawingMode('CROPPER');\n requestAnimationFrame(() => this.changeCrop(this.$currentCrop()));\n }\n\n async closeCrop(type: 'cancel' | 'apply') {\n if (this.$footerMenu() === 'crop') {\n if (type === 'cancel') {\n await this.editorInstance.rotate(this.$currentRotate() * -1);\n } else {\n await this.editorInstance.crop(this.editorInstance.getCropzoneRect());\n this.$isCropped.set(true);\n }\n this.$currentRotate.set(0);\n this.$currentCrop.set('cover');\n this.editorInstance.stopDrawingMode();\n } else if (this.$footerMenu() === 'brightness') {\n if (type === 'cancel' && this.editorInstance.hasFilter('brightness')) {\n await this.editorInstance.removeFilter('brightness');\n }\n }\n this.$footerMenu.set('menu');\n }\n\n async changeRange(event: RangeCustomEvent) {\n if (this.editorInstance.hasFilter('brightness')) {\n await this.editorInstance.removeFilter('brightness');\n }\n this.editorInstance.applyFilter('brightness', {\n brightness: Number(event.detail.value) / 255,\n });\n }\n\n imageSave() {\n const value = this.editorInstance.toDataURL();\n this.modalCtrl.dismiss({ value } as IPhotoEditorDismiss);\n }\n\n private async initializeFilterMenu() {\n const filters: IFilter[] = [];\n\n const defaultInstance = new ImageEditor(document.createElement('div'), {\n cssMaxWidth: this.filterImageSize,\n cssMaxHeight: (this.$photoCrop().height * this.filterImageSize) / this.$photoCrop().width,\n });\n const blob = await fetch(\n this.editorInstance.toDataURL({\n multiplier: this.filterImageSize / this.$photoCrop().width,\n }),\n ).then((res) => res.blob());\n await defaultInstance.loadImageFromFile(new File([blob], 'defaultInstance.png', { type: blob.type }));\n\n for (const filter of this.filterPreset) {\n if (filter.type !== 'Default') {\n await defaultInstance.applyFilter(filter.type, filter.option);\n }\n filters.push({\n name: filter.name,\n type: filter.type,\n option: filter.option,\n data: defaultInstance.toDataURL(),\n width: this.filterImageSize,\n height: (this.$photoCrop().height * this.filterImageSize) / this.$photoCrop().width,\n });\n if (filter.type !== 'Default') {\n await defaultInstance.removeFilter(filter.type);\n }\n }\n this.$filters.set(filters);\n defaultInstance.destroy();\n }\n\n async filterImage(filter: IFilter) {\n if (this.$adoptFilter()) {\n await this.editorInstance.removeFilter(this.$adoptFilter()!.type);\n }\n if (filter.type === 'Default') {\n this.$adoptFilter.set(undefined);\n return;\n }\n await this.editorInstance.applyFilter(filter.type, filter.option);\n this.$adoptFilter.set(filter);\n }\n}\n","<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\"\n ><ion-button (click)=\"modalCtrl.dismiss()\"><ion-icon name=\"close-outline\" slot=\"icon-only\"></ion-icon></ion-button\n ></ion-buttons>\n <ion-buttons slot=\"end\">\n <ion-button color=\"primary\" fill=\"outline\" [disabled]=\"!['menu', 'filter'].includes($footerMenu())\" (click)=\"imageSave()\">\n {{ dictionary.save }}\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\" [scrollY]=\"false\">\n <div #imageEditor></div>\n</ion-content>\n<ion-footer>\n <ion-toolbar>\n @if (this.$footerMenu() === 'menu') {\n <aside class=\"ion-justify-content-center\">\n <button (click)=\"$footerMenu.set('crop')\">\n <ion-text>{{ dictionary.crop }}</ion-text>\n <ion-icon name=\"crop-outline\" size=\"large\"></ion-icon>\n </button>\n <button (click)=\"$footerMenu.set('filter')\">\n <ion-text>{{ dictionary.filter }}</ion-text>\n <ion-icon name=\"color-filter-outline\" size=\"large\"></ion-icon>\n </button>\n <button (click)=\"$footerMenu.set('brightness')\">\n <ion-text>{{ dictionary.brightness }}</ion-text>\n <ion-icon name=\"sunny-outline\" size=\"large\"></ion-icon>\n </button>\n </aside>\n }\n\n @if (this.$footerMenu() === 'filter') {\n <aside>\n @for (item of $filters(); track item) {\n <button (click)=\"filterImage(item)\">\n <ion-text>{{ item.name }}</ion-text>\n <span class=\"image-filter-box\" [style.background-image]=\"'url(' + item.data + ')'\"></span>\n </button>\n }\n </aside>\n }\n\n @if (this.$footerMenu() === 'crop' && !requireSquare) {\n <ion-buttons class=\"ion-justify-content-center submenu-icon-buttons\">\n <ion-button (click)=\"changeCrop('cover')\" [color]=\"$currentCrop() === 'cover' ? 'success' : undefined\">\n <ion-icon name=\"expand-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"changeCrop('16/9')\" [color]=\"$currentCrop() === '16/9' ? 'success' : undefined\">\n <ion-icon name=\"tablet-landscape-outline\" slot=\"icon-only\" style=\"transform: scale(1, 0.8)\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"changeCrop('1')\" [color]=\"$currentCrop() === '1' ? 'success' : undefined\">\n <ion-icon name=\"square-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"changeCrop('auto')\" [color]=\"$currentCrop() === 'auto' ? 'success' : undefined\">\n <ion-icon name=\"crop-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n <ion-button (click)=\"rotate()\"><ion-icon name=\"refresh-outline\" slot=\"icon-only\"></ion-icon></ion-button>\n </ion-buttons>\n }\n\n @if (['brightness'].includes(this.$footerMenu())) {\n <div class=\"ion-padding ion-margin\">\n <ion-range [pin]=\"true\" [min]=\"-100\" [max]=\"100\" (ionChange)=\"changeRange($any($event))\"></ion-range>\n </div>\n }\n </ion-toolbar>\n @if (this.$footerMenu() !== 'menu') {\n <ion-toolbar mode=\"md\">\n @if (this.$footerMenu() === 'filter') {\n <ion-buttons class=\"ion-justify-content-center\" style=\"margin: 0 8px\">\n <ion-button fill=\"outline\" shape=\"round\" (click)=\"this.$footerMenu.set('menu')\">戻る</ion-button>\n </ion-buttons>\n } @else {\n <ion-buttons slot=\"start\" style=\"min-width: 60px\">\n @if (!requireSquare || $isCropped()) {\n <ion-button (click)=\"closeCrop('cancel')\"><ion-icon name=\"close-outline\" slot=\"icon-only\"></ion-icon></ion-button>\n }</ion-buttons\n ><ion-text class=\"footer-title\">\n @if ($footerMenu() === 'crop') {\n {{ dictionary.crop }}\n } @else if ($footerMenu() === 'brightness') {\n {{ dictionary.brightness }}\n }</ion-text\n ><ion-buttons slot=\"end\"\n ><ion-button (click)=\"closeCrop('apply')\"\n ><ion-icon name=\"checkmark-outline\" slot=\"icon-only\" color=\"success\"></ion-icon></ion-button\n ></ion-buttons>\n }\n </ion-toolbar>\n }\n</ion-footer>\n","/*\n * Public API Surface of photo-editor\n */\n\nexport * from './lib/page/photo-editor.page';\nexport * from './lib/types';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ /// <amd-module name="@rdlabo/ionic-angular-photo-editor" />
5
+ export * from './public-api';
@@ -0,0 +1,2 @@
1
+ import { IDictionary } from './types';
2
+ export declare const dictionary: () => IDictionary;
@@ -0,0 +1,2 @@
1
+ import { IDictionary, IFilterPreset } from './types';
2
+ export declare const filterPreset: (dictionary: IDictionary) => IFilterPreset[];
@@ -0,0 +1,2 @@
1
+ import { IonButton, IonButtons, IonContent, IonFooter, IonHeader, IonIcon, IonRange, IonText, IonToolbar } from '@ionic/angular/standalone';
2
+ export declare const ionComponents: (typeof IonHeader | typeof IonToolbar | typeof IonButtons | typeof IonButton | typeof IonIcon | typeof IonContent | typeof IonFooter | typeof IonText | typeof IonRange)[];
@@ -0,0 +1,43 @@
1
+ import { ElementRef, OnDestroy, OnInit } from '@angular/core';
2
+ import { IonContent, ModalController, RangeCustomEvent, ViewDidEnter, ViewDidLeave } from '@ionic/angular/standalone';
3
+ import { IDictionary, IFilter, ISize } from '../types';
4
+ import * as i0 from "@angular/core";
5
+ export declare class PhotoEditorPage implements OnInit, OnDestroy, ViewDidEnter, ViewDidLeave {
6
+ protected dictionary: IDictionary;
7
+ protected filterPreset: import("../types").IFilterPreset[];
8
+ requireSquare: boolean;
9
+ value: string;
10
+ set label(d: IDictionary);
11
+ editorRef: ElementRef;
12
+ ionContent: {
13
+ el: IonContent & HTMLElement;
14
+ };
15
+ $filters: import("@angular/core").WritableSignal<IFilter[]>;
16
+ $footerMenu: import("@angular/core").WritableSignal<"menu" | "filter" | "crop" | "brightness">;
17
+ $currentCrop: import("@angular/core").WritableSignal<"1" | "cover" | "16/9" | "auto">;
18
+ $currentRotate: import("@angular/core").WritableSignal<number>;
19
+ $photoCrop: import("@angular/core").WritableSignal<ISize>;
20
+ $isCropped: import("@angular/core").WritableSignal<boolean>;
21
+ private footerMenu$;
22
+ private $adoptFilter;
23
+ private editorInstance;
24
+ private initSubscription$;
25
+ private readonly filterImageSize;
26
+ private readonly service;
27
+ modalCtrl: ModalController;
28
+ private canvasContainerObserver;
29
+ constructor();
30
+ ngOnInit(): void;
31
+ ngOnDestroy(): void;
32
+ ionViewDidEnter(): Promise<void>;
33
+ ionViewDidLeave(): void;
34
+ changeCrop(crop: 'cover' | '16/9' | '1' | 'auto'): void;
35
+ rotate(): Promise<void>;
36
+ closeCrop(type: 'cancel' | 'apply'): Promise<void>;
37
+ changeRange(event: RangeCustomEvent): Promise<void>;
38
+ imageSave(): void;
39
+ private initializeFilterMenu;
40
+ filterImage(filter: IFilter): Promise<void>;
41
+ static ɵfac: i0.ɵɵFactoryDeclaration<PhotoEditorPage, never>;
42
+ static ɵcmp: i0.ɵɵComponentDeclaration<PhotoEditorPage, "app-editor-image", never, { "requireSquare": { "alias": "requireSquare"; "required": false; }; "value": { "alias": "value"; "required": false; }; "label": { "alias": "label"; "required": false; }; }, {}, never, never, true, never>;
43
+ }
@@ -0,0 +1,8 @@
1
+ import * as i0 from "@angular/core";
2
+ export declare class PhotoEditorService {
3
+ constructor();
4
+ initializeIcons(): void;
5
+ waitToFindDom(nativeElement: HTMLElement, selector: string): Promise<void>;
6
+ static ɵfac: i0.ɵɵFactoryDeclaration<PhotoEditorService, never>;
7
+ static ɵprov: i0.ɵɵInjectableDeclaration<PhotoEditorService>;
8
+ }
package/lib/types.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ export interface IFilter {
2
+ name: string;
3
+ type: string;
4
+ option: any;
5
+ data: string;
6
+ width: number;
7
+ height: number;
8
+ }
9
+ export interface ISize {
10
+ width: number;
11
+ height: number;
12
+ }
13
+ export interface IDictionary {
14
+ save: string;
15
+ crop: string;
16
+ filter: string;
17
+ brightness: string;
18
+ original: string;
19
+ invert: string;
20
+ sepia: string;
21
+ vintage: string;
22
+ blur: string;
23
+ grayscale: string;
24
+ sharpen: string;
25
+ emboss: string;
26
+ }
27
+ export interface IFilterPreset {
28
+ name: string;
29
+ type: string;
30
+ option: any;
31
+ }
32
+ export interface IPhotoEditorDismiss {
33
+ value: string;
34
+ }
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@rdlabo/ionic-angular-photo-editor",
3
+ "version": "0.0.1",
4
+ "peerDependencies": {
5
+ "@angular/common": "^17.0.0",
6
+ "@angular/core": "^17.0.0",
7
+ "@ionic/angular": "^7.6.0",
8
+ "tui-image-editor": "^3.15.3"
9
+ },
10
+ "dependencies": {
11
+ "tslib": "^2.3.0"
12
+ },
13
+ "sideEffects": false,
14
+ "module": "fesm2022/rdlabo-ionic-angular-photo-editor.mjs",
15
+ "typings": "index.d.ts",
16
+ "exports": {
17
+ "./package.json": {
18
+ "default": "./package.json"
19
+ },
20
+ ".": {
21
+ "types": "./index.d.ts",
22
+ "esm2022": "./esm2022/rdlabo-ionic-angular-photo-editor.mjs",
23
+ "esm": "./esm2022/rdlabo-ionic-angular-photo-editor.mjs",
24
+ "default": "./fesm2022/rdlabo-ionic-angular-photo-editor.mjs"
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,2 @@
1
+ export * from './lib/page/photo-editor.page';
2
+ export * from './lib/types';