@rdlabo/ionic-angular-photo-editor 21.0.1 → 21.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,523 @@
1
+ import { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, signal, input, effect, viewChild, ElementRef, computed, ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, Injectable } from '@angular/core';
4
+ import { CommonModule } from '@angular/common';
5
+ import { FormsModule } from '@angular/forms';
6
+ import * as i1 from '@ionic/angular/standalone';
7
+ import { IonHeader, IonToolbar, IonButtons, IonButton, IonIcon, IonContent, IonFooter, IonText, IonRange, ModalController, IonicSlides, ActionSheetController, Platform } from '@ionic/angular/standalone';
8
+ import ImageEditor from 'tui-image-editor';
9
+ import { toObservable } from '@angular/core/rxjs-interop';
10
+ import { addIcons } from 'ionicons';
11
+ import { removeOutline, closeOutline, checkmarkOutline, refreshOutline, squareOutline, tabletLandscapeOutline, expandOutline, sunnyOutline, colorFilterOutline, cropOutline, send } from 'ionicons/icons';
12
+ import { Navigation, Zoom } from 'swiper/modules';
13
+ import { Subscription, fromEvent, zipWith, withLatestFrom, throttleTime } from 'rxjs';
14
+ import { register } from 'swiper/element/bundle';
15
+ import { CameraSource, CameraResultType, Camera } from '@capacitor/camera';
16
+
17
+ const filterPreset = (dictionary) => [
18
+ {
19
+ name: dictionary.original,
20
+ type: 'Default',
21
+ option: null,
22
+ },
23
+ // {
24
+ // name: dictionary.invert,
25
+ // type: 'Invert',
26
+ // option: null,
27
+ // },
28
+ {
29
+ name: dictionary.sepia,
30
+ type: 'Sepia',
31
+ option: null,
32
+ },
33
+ {
34
+ name: dictionary.vintage,
35
+ type: 'vintage',
36
+ option: null,
37
+ },
38
+ {
39
+ name: 'ぼかし',
40
+ type: 'Blur',
41
+ option: { blur: 0.1 },
42
+ },
43
+ {
44
+ name: dictionary.grayscale,
45
+ type: 'Grayscale',
46
+ option: null,
47
+ },
48
+ // {
49
+ // name: dictionary.sharpen,
50
+ // type: 'Sharpen',
51
+ // option: null,
52
+ // },
53
+ // {
54
+ // name: dictionary.emboss,
55
+ // type: 'Emboss',
56
+ // option: null,
57
+ // },
58
+ ];
59
+
60
+ const ionComponents = [IonHeader, IonToolbar, IonButtons, IonButton, IonIcon, IonContent, IonFooter, IonText, IonRange];
61
+
62
+ const dictionaryForEditor = () => ({
63
+ // UI labels
64
+ save: '保存',
65
+ crop: '切り抜き・回転',
66
+ filter: 'フィルター',
67
+ brightness: '明るさ',
68
+ // Filter labels
69
+ original: 'オリジナル',
70
+ invert: '反転',
71
+ sepia: 'セピア',
72
+ vintage: 'ヴィンテージ',
73
+ blur: 'ぼかし',
74
+ grayscale: 'グレースケール',
75
+ sharpen: '輪郭',
76
+ emboss: 'エンボス',
77
+ });
78
+ const dictionaryForViewer = () => ({
79
+ // UI labels
80
+ delete: '削除',
81
+ });
82
+ const dictionaryForService = () => ({
83
+ // UI labels
84
+ camera: 'カメラ撮影',
85
+ album: 'アルバムから選択',
86
+ cancel: 'キャンセル',
87
+ });
88
+
89
+ const initializeViewerIcons = () => {
90
+ addIcons({
91
+ closeOutline,
92
+ removeOutline,
93
+ });
94
+ };
95
+ const initializeEditorIcons = () => {
96
+ addIcons({
97
+ closeOutline,
98
+ send,
99
+ cropOutline,
100
+ colorFilterOutline,
101
+ sunnyOutline,
102
+ expandOutline,
103
+ tabletLandscapeOutline,
104
+ squareOutline,
105
+ refreshOutline,
106
+ checkmarkOutline,
107
+ });
108
+ };
109
+ const waitToFindDom = (nativeElement, selector) => {
110
+ return new Promise((resolve) => {
111
+ const interval = setInterval(() => {
112
+ const find = nativeElement.querySelector(selector);
113
+ if (find) {
114
+ clearInterval(interval);
115
+ resolve();
116
+ }
117
+ });
118
+ });
119
+ };
120
+
121
+ class PhotoEditorPage {
122
+ constructor() {
123
+ this.modalCtrl = inject(ModalController);
124
+ this.dictionary = signal(dictionaryForEditor(), ...(ngDevMode ? [{ debugName: "dictionary" }] : []));
125
+ this.requireSquare = input(false, { ...(ngDevMode ? { debugName: "requireSquare" } : {}), transform: coerceBooleanProperty });
126
+ this.labels = input(undefined, ...(ngDevMode ? [{ debugName: "labels" }] : []));
127
+ this.setLabels = effect(() => {
128
+ if (this.labels()) {
129
+ this.dictionary.update((value) => ({ ...value, ...this.labels() }));
130
+ }
131
+ }, ...(ngDevMode ? [{ debugName: "setLabels" }] : []));
132
+ this.value = input.required(...(ngDevMode ? [{ debugName: "value" }] : []));
133
+ this.editorRef = viewChild.required('imageEditor');
134
+ this.ionContent = viewChild.required(IonContent, { read: ElementRef });
135
+ this.filters = signal([], ...(ngDevMode ? [{ debugName: "filters" }] : []));
136
+ this.footerMenu = signal('menu', ...(ngDevMode ? [{ debugName: "footerMenu" }] : []));
137
+ this.currentCrop = signal('cover', ...(ngDevMode ? [{ debugName: "currentCrop" }] : []));
138
+ this.currentRotate = signal(0, ...(ngDevMode ? [{ debugName: "currentRotate" }] : []));
139
+ this.photoCrop = signal({
140
+ width: 0,
141
+ height: 0,
142
+ }, ...(ngDevMode ? [{ debugName: "photoCrop" }] : []));
143
+ this.isCropped = signal(false, ...(ngDevMode ? [{ debugName: "isCropped" }] : []));
144
+ this.adoptFilter = signal(undefined, ...(ngDevMode ? [{ debugName: "adoptFilter" }] : []));
145
+ this.filterPreset = computed(() => filterPreset(this.dictionary()), ...(ngDevMode ? [{ debugName: "filterPreset" }] : []));
146
+ this.footerMenu$ = toObservable(this.footerMenu);
147
+ this.initSubscription$ = [];
148
+ this.filterImageSize = 240;
149
+ this.canvasContainerObserver = new MutationObserver((mutationsList) => {
150
+ if (mutationsList.find((mutation) => mutation.type === 'attributes' && mutation.attributeName === 'style')) {
151
+ // Cover the image editor with the parent element
152
+ const editorRef = this.editorRef();
153
+ editorRef.nativeElement.style.minWidth = mutationsList[0].target.parentElement.style.maxWidth;
154
+ editorRef.nativeElement.style.minHeight = mutationsList[0].target.parentElement.style.maxHeight;
155
+ this.photoCrop.set({
156
+ width: mutationsList[0].target.parentElement.querySelector('canvas').width,
157
+ height: mutationsList[0].target.parentElement.querySelector('canvas').height,
158
+ });
159
+ }
160
+ });
161
+ initializeEditorIcons();
162
+ }
163
+ ngOnInit() {
164
+ this.initSubscription$.push(this.footerMenu$.subscribe((value) => {
165
+ if (value === 'filter') {
166
+ this.initializeFilterMenu().then();
167
+ }
168
+ if (value === 'crop') {
169
+ this.editorInstance.startDrawingMode('CROPPER');
170
+ this.changeCrop(this.requireSquare() ? '1' : 'cover');
171
+ }
172
+ }));
173
+ }
174
+ ngOnDestroy() {
175
+ this.initSubscription$.forEach((subscription) => subscription.unsubscribe());
176
+ }
177
+ async ionViewDidEnter() {
178
+ this.editorInstance = new ImageEditor(this.editorRef().nativeElement, {
179
+ cssMaxWidth: this.ionContent().nativeElement.clientWidth - 32,
180
+ cssMaxHeight: this.ionContent().nativeElement.clientHeight - 32,
181
+ });
182
+ waitToFindDom(this.editorRef().nativeElement, '.tui-image-editor-canvas-container').then(() => {
183
+ this.canvasContainerObserver.observe(this.editorRef().nativeElement.querySelector('.tui-image-editor-canvas-container'), {
184
+ attributes: true,
185
+ childList: false,
186
+ subtree: true,
187
+ });
188
+ });
189
+ const blob = await fetch(this.value()).then((res) => res.blob());
190
+ await this.editorInstance.loadImageFromFile(new File([blob], 'data.png', { type: blob.type }));
191
+ this.footerMenu.set(this.requireSquare() ? 'crop' : 'menu');
192
+ }
193
+ ionViewDidLeave() {
194
+ this.editorInstance.destroy();
195
+ this.canvasContainerObserver.disconnect();
196
+ }
197
+ changeCrop(crop) {
198
+ const rect = crop === 'cover' ? this.photoCrop().width / this.photoCrop().height : crop === '16/9' ? 16 / 9 : 1;
199
+ this.editorInstance.setCropzoneRect(crop !== 'auto' ? rect : undefined);
200
+ this.currentCrop.set(crop);
201
+ }
202
+ async rotate() {
203
+ this.editorInstance.stopDrawingMode();
204
+ await this.editorInstance.rotate(90);
205
+ this.currentRotate.update((value) => value + 90);
206
+ this.editorInstance.startDrawingMode('CROPPER');
207
+ requestAnimationFrame(() => this.changeCrop(this.currentCrop()));
208
+ }
209
+ async closeCrop(type) {
210
+ if (this.footerMenu() === 'crop') {
211
+ if (type === 'cancel') {
212
+ await this.editorInstance.rotate(this.currentRotate() * -1);
213
+ }
214
+ else {
215
+ await this.editorInstance.crop(this.editorInstance.getCropzoneRect());
216
+ this.isCropped.set(true);
217
+ }
218
+ this.currentRotate.set(0);
219
+ this.currentCrop.set('cover');
220
+ this.editorInstance.stopDrawingMode();
221
+ }
222
+ else if (this.footerMenu() === 'brightness') {
223
+ if (type === 'cancel' && this.editorInstance.hasFilter('brightness')) {
224
+ await this.editorInstance.removeFilter('brightness');
225
+ }
226
+ }
227
+ this.footerMenu.set('menu');
228
+ }
229
+ async changeRange(event) {
230
+ if (this.editorInstance.hasFilter('brightness')) {
231
+ await this.editorInstance.removeFilter('brightness');
232
+ }
233
+ await this.editorInstance.applyFilter('brightness', {
234
+ brightness: Number(event.detail.value) / 255,
235
+ });
236
+ }
237
+ imageSave() {
238
+ const value = this.editorInstance.toDataURL();
239
+ this.modalCtrl.dismiss({ value });
240
+ }
241
+ async initializeFilterMenu() {
242
+ const filters = [];
243
+ const defaultInstance = new ImageEditor(document.createElement('div'), {
244
+ cssMaxWidth: this.filterImageSize,
245
+ cssMaxHeight: (this.photoCrop().height * this.filterImageSize) / this.photoCrop().width,
246
+ });
247
+ const blob = await fetch(this.editorInstance.toDataURL({
248
+ multiplier: this.filterImageSize / this.photoCrop().width,
249
+ })).then((res) => res.blob());
250
+ await defaultInstance.loadImageFromFile(new File([blob], 'defaultInstance.png', { type: blob.type }));
251
+ for (const filter of this.filterPreset()) {
252
+ if (filter.type !== 'Default') {
253
+ await defaultInstance.applyFilter(filter.type, filter.option);
254
+ }
255
+ filters.push({
256
+ name: filter.name,
257
+ type: filter.type,
258
+ option: filter.option,
259
+ data: defaultInstance.toDataURL(),
260
+ width: this.filterImageSize,
261
+ height: (this.photoCrop().height * this.filterImageSize) / this.photoCrop().width,
262
+ });
263
+ if (filter.type !== 'Default') {
264
+ await defaultInstance.removeFilter(filter.type);
265
+ }
266
+ }
267
+ this.filters.set(filters);
268
+ defaultInstance.destroy();
269
+ }
270
+ async filterImage(filter) {
271
+ if (this.adoptFilter()) {
272
+ await this.editorInstance.removeFilter(this.adoptFilter().type);
273
+ }
274
+ if (filter.type === 'Default') {
275
+ this.adoptFilter.set(undefined);
276
+ return;
277
+ }
278
+ await this.editorInstance.applyFilter(filter.type, filter.option);
279
+ this.adoptFilter.set(filter);
280
+ }
281
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: PhotoEditorPage, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
282
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.5", type: PhotoEditorPage, isStandalone: true, selector: "app-editor-image", inputs: { requireSquare: { classPropertyName: "requireSquare", publicName: "requireSquare", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["imageEditor"], descendants: true, isSignal: true }, { propertyName: "ionContent", first: true, predicate: IonContent, descendants: true, read: ElementRef, isSignal: 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=\"photo-editor-primary\" fill=\"solid\" type=\"submit\" [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\" color=\"photo-editor-success\" slot=\"icon-only\"></ion-icon></ion-button\n ></ion-buttons>\n }\n </ion-toolbar>\n }\n</ion-footer>\n", styles: [":host{--editor-background: var(--ion-photo-editor-background, --ion-color-step-100, #2a2a2a);--editor-background-tint: var(--ion-photo-editor-background-tint, --ion-color-step-200, #414141);--editor-color: var(--ion-photo-editor-color, --ion-color-step-950, #f0f0f0);--editor-color-tint: var(--ion-photo-editor-color-tint, --ion-color-step-850, #dbdbdb);--ion-color-photo-editor-primary: var(--ion-photo-editor-primary, --ion-color-primary, #4d8dff);--ion-color-photo-editor-danger: var(--ion-photo-editor-danger, #f24c58);--ion-color-photo-editor-success: var(--ion-photo-editor-success, #2dd55b)}.ion-color-photo-editor-primary{--ion-color-base: var(--ion-color-photo-editor-primary)}.ion-color-photo-editor-danger{--ion-color-base: var(--ion-color-photo-editor-danger)}.ion-color-photo-editor-success{--ion-color-base: var(--ion-color-photo-editor-success)}ion-content,ion-toolbar,ion-header,ion-footer{background:var(--editor-background)!important;--background: var(--editor-background) !important}\n", "ion-content{--background: var(--editor-background)}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(--editor-color);border:1px solid var(--editor-background-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(--editor-color);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(--editor-color) !important}ion-footer ion-toolbar ion-text.footer-title{color:var(--editor-color)!important;display:block;text-align:center}ion-footer ion-toolbar ion-range{--bar-background: var(--editor-color);--bar-background-active: var(--editor-color);--bar-height: 2px;--bar-border-radius: 8px;--knob-background: var(--editor-color);--knob-size: 20px;--pin-background: none;--pin-color: var(--editor-color)}ion-footer ion-toolbar ion-buttons[slot=start] ion-button,ion-footer ion-toolbar ion-buttons[slot=end] ion-button{--background: var(--editor-background-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", "fixedSlotPlacement", "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", "max", "min", "mode", "name", "pin", "pinFormatter", "snaps", "step", "ticks", "value"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
283
+ }
284
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: PhotoEditorPage, decorators: [{
285
+ type: Component,
286
+ args: [{ selector: 'app-editor-image', imports: [CommonModule, FormsModule, ...ionComponents], changeDetection: ChangeDetectionStrategy.OnPush, 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=\"photo-editor-primary\" fill=\"solid\" type=\"submit\" [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\" color=\"photo-editor-success\" slot=\"icon-only\"></ion-icon></ion-button\n ></ion-buttons>\n }\n </ion-toolbar>\n }\n</ion-footer>\n", styles: [":host{--editor-background: var(--ion-photo-editor-background, --ion-color-step-100, #2a2a2a);--editor-background-tint: var(--ion-photo-editor-background-tint, --ion-color-step-200, #414141);--editor-color: var(--ion-photo-editor-color, --ion-color-step-950, #f0f0f0);--editor-color-tint: var(--ion-photo-editor-color-tint, --ion-color-step-850, #dbdbdb);--ion-color-photo-editor-primary: var(--ion-photo-editor-primary, --ion-color-primary, #4d8dff);--ion-color-photo-editor-danger: var(--ion-photo-editor-danger, #f24c58);--ion-color-photo-editor-success: var(--ion-photo-editor-success, #2dd55b)}.ion-color-photo-editor-primary{--ion-color-base: var(--ion-color-photo-editor-primary)}.ion-color-photo-editor-danger{--ion-color-base: var(--ion-color-photo-editor-danger)}.ion-color-photo-editor-success{--ion-color-base: var(--ion-color-photo-editor-success)}ion-content,ion-toolbar,ion-header,ion-footer{background:var(--editor-background)!important;--background: var(--editor-background) !important}\n", "ion-content{--background: var(--editor-background)}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(--editor-color);border:1px solid var(--editor-background-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(--editor-color);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(--editor-color) !important}ion-footer ion-toolbar ion-text.footer-title{color:var(--editor-color)!important;display:block;text-align:center}ion-footer ion-toolbar ion-range{--bar-background: var(--editor-color);--bar-background-active: var(--editor-color);--bar-height: 2px;--bar-border-radius: 8px;--knob-background: var(--editor-color);--knob-size: 20px;--pin-background: none;--pin-color: var(--editor-color)}ion-footer ion-toolbar ion-buttons[slot=start] ion-button,ion-footer ion-toolbar ion-buttons[slot=end] ion-button{--background: var(--editor-background-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"] }]
287
+ }], ctorParameters: () => [], propDecorators: { requireSquare: [{ type: i0.Input, args: [{ isSignal: true, alias: "requireSquare", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], editorRef: [{ type: i0.ViewChild, args: ['imageEditor', { isSignal: true }] }], ionContent: [{ type: i0.ViewChild, args: [i0.forwardRef(() => IonContent), { ...{ read: ElementRef }, isSignal: true }] }] } });
288
+
289
+ class PhotoViewerPage {
290
+ constructor() {
291
+ this.imageUrls = input.required(...(ngDevMode ? [{ debugName: "imageUrls" }] : []));
292
+ this.index = input(0, { ...(ngDevMode ? { debugName: "index" } : {}), transform: coerceNumberProperty });
293
+ this.isCircle = input(false, { ...(ngDevMode ? { debugName: "isCircle" } : {}), transform: coerceBooleanProperty });
294
+ this.enableDelete = input(false, { ...(ngDevMode ? { debugName: "enableDelete" } : {}), transform: coerceBooleanProperty });
295
+ this.enableFooterSafeArea = input(false, { ...(ngDevMode ? { debugName: "enableFooterSafeArea" } : {}), transform: coerceBooleanProperty });
296
+ this.labels = input(...(ngDevMode ? [undefined, { debugName: "labels" }] : []));
297
+ this.setLabels = effect(() => {
298
+ if (this.labels()) {
299
+ this.dictionary.update((value) => ({ ...value, ...this.labels() }));
300
+ }
301
+ }, ...(ngDevMode ? [{ debugName: "setLabels" }] : []));
302
+ this.swiper = viewChild.required('swiper');
303
+ this.dictionary = signal(dictionaryForViewer(), ...(ngDevMode ? [{ debugName: "dictionary" }] : []));
304
+ this.watchSwipe$ = new Subscription();
305
+ this.modalCtrl = inject(ModalController);
306
+ this.el = inject(ElementRef);
307
+ register();
308
+ initializeViewerIcons();
309
+ }
310
+ async ngOnInit() {
311
+ waitToFindDom(this.el.nativeElement, 'swiper-container').then(() => {
312
+ const index = this.index();
313
+ const swiper = this.swiper();
314
+ Object.assign(swiper.nativeElement, {
315
+ modules: [Navigation, Zoom, IonicSlides],
316
+ initialSlide: index,
317
+ slidesPerView: 1,
318
+ pagination: {
319
+ enabled: true,
320
+ clickable: true,
321
+ },
322
+ zoom: true,
323
+ });
324
+ swiper.nativeElement.initialize();
325
+ swiper.nativeElement.swiper.zoom.enable();
326
+ swiper.nativeElement.swiper.activeIndex = index;
327
+ swiper.nativeElement.swiper.update();
328
+ });
329
+ this.watchSwipe$.add(fromEvent(this.el.nativeElement, 'touchstart')
330
+ .pipe(zipWith(fromEvent(this.el.nativeElement, 'touchend').pipe(withLatestFrom(fromEvent(this.el.nativeElement, 'touchmove')))), throttleTime(1))
331
+ .subscribe(([touchstart, [_, touchmove]]) => {
332
+ const touchstartClientX = touchstart.touches ? touchstart.touches[0].clientX : touchstart.detail[1].clientX;
333
+ const touchmoveClientX = touchmove.touches ? touchmove.touches[0].clientX : touchmove.detail[1].clientX;
334
+ const touchstartClientY = touchstart.touches ? touchstart.touches[0].clientY : touchstart.detail[1].clientY;
335
+ const touchmoveClientY = touchmove.touches ? touchmove.touches[0].clientY : touchmove.detail[1].clientY;
336
+ const xDiff = touchstartClientX - touchmoveClientX;
337
+ const yDiff = touchstartClientY - touchmoveClientY;
338
+ const slides = this.swiper().nativeElement.querySelectorAll('swiper-slide');
339
+ const isZoomed = Array.from(slides).find((slide) => {
340
+ return ['swiper-slide-zoomed', 'swiper-slide-active'].every((c) => slide.classList.contains(c));
341
+ });
342
+ const threshold = touchmove.touches ? -50 : -5;
343
+ if (!isZoomed && Math.abs(xDiff) < Math.abs(threshold) && yDiff < threshold && touchstart.timeStamp <= touchmove.timeStamp) {
344
+ this.watchSwipe$.unsubscribe();
345
+ this.modalCtrl.dismiss();
346
+ }
347
+ }));
348
+ }
349
+ ngOnDestroy() {
350
+ this.watchSwipe$.unsubscribe();
351
+ }
352
+ remove() {
353
+ this.modalCtrl.dismiss({
354
+ delete: {
355
+ index: this.swiper().nativeElement.swiper.activeIndex,
356
+ value: this.imageUrls()[this.swiper().nativeElement.swiper.activeIndex],
357
+ },
358
+ });
359
+ }
360
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: PhotoViewerPage, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
361
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.5", type: PhotoViewerPage, isStandalone: true, selector: "app-photo-image", inputs: { imageUrls: { classPropertyName: "imageUrls", publicName: "imageUrls", isSignal: true, isRequired: true, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: false, transformFunction: null }, isCircle: { classPropertyName: "isCircle", publicName: "isCircle", isSignal: true, isRequired: false, transformFunction: null }, enableDelete: { classPropertyName: "enableDelete", publicName: "enableDelete", isSignal: true, isRequired: false, transformFunction: null }, enableFooterSafeArea: { classPropertyName: "enableFooterSafeArea", publicName: "enableFooterSafeArea", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "swiper", first: true, predicate: ["swiper"], descendants: true, isSignal: true }], ngImport: i0, template: "<ion-header [translucent]=\"true\">\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-button (click)=\"modalCtrl.dismiss()\" class=\"viewer-close-button\">\n <ion-icon name=\"close-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n </ion-buttons>\n @if (enableDelete()) {\n <ion-buttons slot=\"end\">\n <ion-button color=\"photo-editor-danger\" fill=\"solid\" type=\"submit\" (click)=\"remove()\">{{ dictionary().delete }}</ion-button>\n </ion-buttons>\n }\n </ion-toolbar>\n</ion-header>\n\n<ion-content [fullscreen]=\"true\">\n <swiper-container #swiper>\n @for (url of imageUrls(); track url) {\n <swiper-slide>\n <div class=\"swiper-zoom-container\"><img [src]=\"url\" alt=\"\" [class.circle]=\"isCircle()\" /></div>\n </swiper-slide>\n }\n </swiper-container>\n</ion-content>\n\n@if (enableFooterSafeArea()) {\n <!-- use for safe area-->\n <ion-footer>\n <ion-toolbar style=\"--border-width: 0; --min-height: 0\"></ion-toolbar>\n </ion-footer>\n}\n", styles: [":host{--editor-background: var(--ion-photo-editor-background, --ion-color-step-100, #2a2a2a);--editor-background-tint: var(--ion-photo-editor-background-tint, --ion-color-step-200, #414141);--editor-color: var(--ion-photo-editor-color, --ion-color-step-950, #f0f0f0);--editor-color-tint: var(--ion-photo-editor-color-tint, --ion-color-step-850, #dbdbdb);--ion-color-photo-editor-primary: var(--ion-photo-editor-primary, --ion-color-primary, #4d8dff);--ion-color-photo-editor-danger: var(--ion-photo-editor-danger, #f24c58);--ion-color-photo-editor-success: var(--ion-photo-editor-success, #2dd55b)}.ion-color-photo-editor-primary{--ion-color-base: var(--ion-color-photo-editor-primary)}.ion-color-photo-editor-danger{--ion-color-base: var(--ion-color-photo-editor-danger)}.ion-color-photo-editor-success{--ion-color-base: var(--ion-color-photo-editor-success)}ion-content,ion-toolbar,ion-header,ion-footer{background:var(--editor-background)!important;--background: var(--editor-background) !important}\n", "ion-button.viewer-close-button{--color: var(--editor-color) !important}img.circle{border-radius:50%;display:block;padding:16px}swiper-container{height:100%;max-height:100%}swiper-slide{height:100%;max-height:100%;display:flex;justify-content:center;align-items:center}swiper-slide img{display:block;object-fit:contain;max-width:100%;max-height:100%}\n"], dependencies: [{ 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", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
362
+ }
363
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: PhotoViewerPage, decorators: [{
364
+ type: Component,
365
+ args: [{ selector: 'app-photo-image', imports: [...ionComponents], schemas: [CUSTOM_ELEMENTS_SCHEMA], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-header [translucent]=\"true\">\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-button (click)=\"modalCtrl.dismiss()\" class=\"viewer-close-button\">\n <ion-icon name=\"close-outline\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n </ion-buttons>\n @if (enableDelete()) {\n <ion-buttons slot=\"end\">\n <ion-button color=\"photo-editor-danger\" fill=\"solid\" type=\"submit\" (click)=\"remove()\">{{ dictionary().delete }}</ion-button>\n </ion-buttons>\n }\n </ion-toolbar>\n</ion-header>\n\n<ion-content [fullscreen]=\"true\">\n <swiper-container #swiper>\n @for (url of imageUrls(); track url) {\n <swiper-slide>\n <div class=\"swiper-zoom-container\"><img [src]=\"url\" alt=\"\" [class.circle]=\"isCircle()\" /></div>\n </swiper-slide>\n }\n </swiper-container>\n</ion-content>\n\n@if (enableFooterSafeArea()) {\n <!-- use for safe area-->\n <ion-footer>\n <ion-toolbar style=\"--border-width: 0; --min-height: 0\"></ion-toolbar>\n </ion-footer>\n}\n", styles: [":host{--editor-background: var(--ion-photo-editor-background, --ion-color-step-100, #2a2a2a);--editor-background-tint: var(--ion-photo-editor-background-tint, --ion-color-step-200, #414141);--editor-color: var(--ion-photo-editor-color, --ion-color-step-950, #f0f0f0);--editor-color-tint: var(--ion-photo-editor-color-tint, --ion-color-step-850, #dbdbdb);--ion-color-photo-editor-primary: var(--ion-photo-editor-primary, --ion-color-primary, #4d8dff);--ion-color-photo-editor-danger: var(--ion-photo-editor-danger, #f24c58);--ion-color-photo-editor-success: var(--ion-photo-editor-success, #2dd55b)}.ion-color-photo-editor-primary{--ion-color-base: var(--ion-color-photo-editor-primary)}.ion-color-photo-editor-danger{--ion-color-base: var(--ion-color-photo-editor-danger)}.ion-color-photo-editor-success{--ion-color-base: var(--ion-color-photo-editor-success)}ion-content,ion-toolbar,ion-header,ion-footer{background:var(--editor-background)!important;--background: var(--editor-background) !important}\n", "ion-button.viewer-close-button{--color: var(--editor-color) !important}img.circle{border-radius:50%;display:block;padding:16px}swiper-container{height:100%;max-height:100%}swiper-slide{height:100%;max-height:100%;display:flex;justify-content:center;align-items:center}swiper-slide img{display:block;object-fit:contain;max-width:100%;max-height:100%}\n"] }]
366
+ }], ctorParameters: () => [], propDecorators: { imageUrls: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageUrls", required: true }] }], index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: false }] }], isCircle: [{ type: i0.Input, args: [{ isSignal: true, alias: "isCircle", required: false }] }], enableDelete: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableDelete", required: false }] }], enableFooterSafeArea: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableFooterSafeArea", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], swiper: [{ type: i0.ViewChild, args: ['swiper', { isSignal: true }] }] } });
367
+
368
+ var PhotoEditorErrors;
369
+ (function (PhotoEditorErrors) {
370
+ PhotoEditorErrors["initialize"] = "input dom is not found";
371
+ PhotoEditorErrors["cancel"] = "user canceled";
372
+ PhotoEditorErrors["type"] = "upload file is not image.";
373
+ })(PhotoEditorErrors || (PhotoEditorErrors = {}));
374
+
375
+ class PhotoFileService {
376
+ constructor() {
377
+ this.$photoMaxSize = signal(1000, ...(ngDevMode ? [{ debugName: "$photoMaxSize" }] : []));
378
+ this.actionSheetCtrl = inject(ActionSheetController);
379
+ this.platform = inject(Platform);
380
+ this.dictionary = signal(dictionaryForService(), ...(ngDevMode ? [{ debugName: "dictionary" }] : []));
381
+ }
382
+ set photoMaxSize(value) {
383
+ this.$photoMaxSize.set(value);
384
+ }
385
+ set labels(d) {
386
+ this.dictionary.update((value) => ({ ...value, ...d }));
387
+ }
388
+ async loadPhoto(limit) {
389
+ /**
390
+ * Using Input for browser
391
+ */
392
+ if (!this.platform.is('capacitor')) {
393
+ return this.getPictureFromBrowser();
394
+ }
395
+ const actionSheet = await this.actionSheetCtrl.create({
396
+ buttons: [
397
+ {
398
+ text: this.dictionary().camera,
399
+ handler: () => {
400
+ actionSheet.dismiss('camera');
401
+ },
402
+ },
403
+ {
404
+ text: this.dictionary().album,
405
+ handler: () => {
406
+ actionSheet.dismiss('album');
407
+ },
408
+ },
409
+ {
410
+ text: this.dictionary().cancel,
411
+ role: 'cancel',
412
+ },
413
+ ],
414
+ });
415
+ await actionSheet.present();
416
+ const { data } = await actionSheet.onDidDismiss();
417
+ if (!data) {
418
+ return Promise.reject(PhotoEditorErrors.cancel);
419
+ }
420
+ if (data === 'camera') {
421
+ const defaultCamera = {
422
+ quality: 100,
423
+ width: this.$photoMaxSize(),
424
+ allowEditing: false,
425
+ resultType: CameraResultType.DataUrl,
426
+ source: CameraSource.Camera,
427
+ presentationStyle: 'popover',
428
+ };
429
+ const image = await Camera.getPhoto(defaultCamera).catch(() => undefined);
430
+ if (!image?.dataUrl) {
431
+ return Promise.reject(PhotoEditorErrors.cancel);
432
+ }
433
+ if (!image.dataUrl.includes('capacitor://localhost')) {
434
+ return [image.dataUrl];
435
+ }
436
+ return Promise.all([image.dataUrl].map(async (image) => await this.loadPhotoFromFilePath(image)));
437
+ }
438
+ if (data === 'album') {
439
+ const images = await Camera.pickImages({
440
+ quality: 100,
441
+ width: this.$photoMaxSize(),
442
+ limit,
443
+ presentationStyle: 'popover',
444
+ }).catch(() => undefined);
445
+ if (!images) {
446
+ return Promise.reject(PhotoEditorErrors.cancel);
447
+ }
448
+ return Promise.all(images.photos.map(async (image) => await this.loadPhotoFromFilePath(image.webPath)));
449
+ }
450
+ // Not run on this line. This is for lint
451
+ return [];
452
+ }
453
+ getPictureFromBrowser() {
454
+ const inputFile = document.querySelector('input#browserPhotoUploader');
455
+ if (!inputFile) {
456
+ return Promise.reject(PhotoEditorErrors.initialize);
457
+ }
458
+ return new Promise((resolve, reject) => {
459
+ const cancelMethod = () => {
460
+ inputFile.removeEventListener('cancel', cancelMethod, false);
461
+ inputFile.removeEventListener('change', changeMethod, false);
462
+ reject(PhotoEditorErrors.cancel);
463
+ };
464
+ const changeMethod = (e) => {
465
+ inputFile.removeEventListener('cancel', cancelMethod, false);
466
+ inputFile.removeEventListener('change', changeMethod, false);
467
+ if (!e.target.files || !e.target.files[0]) {
468
+ reject(PhotoEditorErrors.cancel);
469
+ }
470
+ const file = e.target.files[0];
471
+ const reader = new FileReader();
472
+ reader.onload = (() => {
473
+ if (file.type.indexOf('image') < 0) {
474
+ reject(PhotoEditorErrors.type);
475
+ }
476
+ return async (event) => {
477
+ inputFile.value = '';
478
+ const result = event.target.result;
479
+ const data = await this.loadPhotoFromFilePath(result);
480
+ resolve([data]);
481
+ };
482
+ })();
483
+ reader.readAsDataURL(file);
484
+ };
485
+ inputFile.addEventListener('cancel', cancelMethod, false);
486
+ inputFile.addEventListener('change', changeMethod, false);
487
+ inputFile.click();
488
+ });
489
+ }
490
+ async loadPhotoFromFilePath(filePath) {
491
+ const defaultInstance = new ImageEditor(document.createElement('div'), {
492
+ cssMaxWidth: this.$photoMaxSize(),
493
+ cssMaxHeight: this.$photoMaxSize(),
494
+ });
495
+ const blob = await fetch(filePath).then((res) => res.blob());
496
+ const loaded = await defaultInstance.loadImageFromFile(new File([blob], 'data.png', { type: blob.type }));
497
+ const maxSize = Math.max(loaded.newWidth, loaded.newHeight);
498
+ const dataUrl = defaultInstance.toDataURL({
499
+ multiplier: this.$photoMaxSize() / maxSize,
500
+ });
501
+ defaultInstance.destroy();
502
+ return dataUrl;
503
+ }
504
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: PhotoFileService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
505
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: PhotoFileService, providedIn: 'root' }); }
506
+ }
507
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: PhotoFileService, decorators: [{
508
+ type: Injectable,
509
+ args: [{
510
+ providedIn: 'root',
511
+ }]
512
+ }] });
513
+
514
+ /*
515
+ * Public API Surface of photo-editor
516
+ */
517
+
518
+ /**
519
+ * Generated bundle index. Do not edit.
520
+ */
521
+
522
+ export { PhotoEditorPage, PhotoFileService, PhotoViewerPage };
523
+ //# sourceMappingURL=rdlabo-ionic-angular-photo-editor.mjs.map