@rdlabo/ionic-angular-photo-editor 0.0.2 → 0.0.4

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.
@@ -1,13 +1,17 @@
1
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';
2
+ import { Injectable, signal, inject, Component, Input, ViewChild, ElementRef, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
3
+ import { CommonModule, NgOptimizedImage, NgFor, NgIf } from '@angular/common';
4
4
  import { FormsModule } from '@angular/forms';
5
5
  import * as i1 from '@ionic/angular/standalone';
6
- import { IonHeader, IonToolbar, IonButtons, IonButton, IonIcon, IonContent, IonFooter, IonText, IonRange, ModalController } from '@ionic/angular/standalone';
6
+ import { IonHeader, IonToolbar, IonButtons, IonButton, IonIcon, IonContent, IonFooter, IonText, IonRange, ModalController, IonicSlides, ActionSheetController, Platform } from '@ionic/angular/standalone';
7
7
  import ImageEditor from 'tui-image-editor';
8
8
  import { toObservable } from '@angular/core/rxjs-interop';
9
9
  import { addIcons } from 'ionicons';
10
- import { closeOutline, send, cropOutline, colorFilterOutline, sunnyOutline, expandOutline, tabletLandscapeOutline, squareOutline, refreshOutline, checkmarkOutline } from 'ionicons/icons';
10
+ import { closeOutline, removeOutline, send, cropOutline, colorFilterOutline, sunnyOutline, expandOutline, tabletLandscapeOutline, squareOutline, refreshOutline, checkmarkOutline } from 'ionicons/icons';
11
+ import { Navigation, Zoom } from 'swiper/modules';
12
+ import { fromEvent, withLatestFrom, zipWith, throttleTime } from 'rxjs';
13
+ import { register } from 'swiper/element/bundle';
14
+ import { CameraResultType, CameraSource, Camera } from '@capacitor/camera';
11
15
 
12
16
  const filterPreset = (dictionary) => [
13
17
  {
@@ -52,9 +56,15 @@ const filterPreset = (dictionary) => [
52
56
  // },
53
57
  ];
54
58
 
55
- class PhotoEditorService {
59
+ class HelperService {
56
60
  constructor() { }
57
- initializeIcons() {
61
+ initializeViewerIcons() {
62
+ addIcons({
63
+ closeOutline,
64
+ removeOutline,
65
+ });
66
+ }
67
+ initializeEditorIcons() {
58
68
  addIcons({
59
69
  closeOutline,
60
70
  send,
@@ -79,19 +89,16 @@ class PhotoEditorService {
79
89
  });
80
90
  });
81
91
  }
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' }); }
92
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: HelperService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
93
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: HelperService }); }
84
94
  }
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
- }]
95
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: HelperService, decorators: [{
96
+ type: Injectable
90
97
  }], ctorParameters: () => [] });
91
98
 
92
99
  const ionComponents = [IonHeader, IonToolbar, IonButtons, IonButton, IonIcon, IonContent, IonFooter, IonText, IonRange];
93
100
 
94
- const dictionary = () => ({
101
+ const dictionaryForEditor = () => ({
95
102
  // UI labels
96
103
  save: '保存',
97
104
  crop: '切り抜き・回転',
@@ -107,14 +114,24 @@ const dictionary = () => ({
107
114
  sharpen: '輪郭',
108
115
  emboss: 'エンボス',
109
116
  });
117
+ const dictionaryForViewer = () => ({
118
+ // UI labels
119
+ delete: '削除',
120
+ });
121
+ const dictionaryForService = () => ({
122
+ // UI labels
123
+ camera: 'カメラ撮影',
124
+ album: 'アルバムから選択',
125
+ cancel: 'キャンセル',
126
+ });
110
127
 
111
128
  class PhotoEditorPage {
112
- set label(d) {
129
+ set labels(d) {
113
130
  this.dictionary = Object.assign(this.dictionary, d);
114
131
  this.filterPreset = filterPreset(this.dictionary);
115
132
  }
116
133
  constructor() {
117
- this.dictionary = dictionary();
134
+ this.dictionary = dictionaryForEditor();
118
135
  this.filterPreset = filterPreset(this.dictionary);
119
136
  this.requireSquare = false;
120
137
  this.$filters = signal([]);
@@ -130,7 +147,7 @@ class PhotoEditorPage {
130
147
  this.$adoptFilter = signal(undefined);
131
148
  this.initSubscription$ = [];
132
149
  this.filterImageSize = 240;
133
- this.service = inject(PhotoEditorService);
150
+ this.service = inject(HelperService);
134
151
  this.modalCtrl = inject(ModalController);
135
152
  this.canvasContainerObserver = new MutationObserver((mutationsList) => {
136
153
  if (mutationsList.find((mutation) => mutation.type === 'attributes' && mutation.attributeName === 'style')) {
@@ -143,7 +160,7 @@ class PhotoEditorPage {
143
160
  });
144
161
  }
145
162
  });
146
- this.service.initializeIcons();
163
+ this.service.initializeEditorIcons();
147
164
  }
148
165
  ngOnInit() {
149
166
  this.initSubscription$.push(this.footerMenu$.subscribe((value) => {
@@ -263,17 +280,17 @@ class PhotoEditorPage {
263
280
  await this.editorInstance.applyFilter(filter.type, filter.option);
264
281
  this.$adoptFilter.set(filter);
265
282
  }
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"] }] }); }
283
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: PhotoEditorPage, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
284
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.0.7", type: PhotoEditorPage, isStandalone: true, selector: "app-editor-image", inputs: { requireSquare: "requireSquare", value: "value", labels: "labels" }, providers: [HelperService], 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)}\n", "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
285
  }
269
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.6", ngImport: i0, type: PhotoEditorPage, decorators: [{
286
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: PhotoEditorPage, decorators: [{
270
287
  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"] }]
288
+ args: [{ selector: 'app-editor-image', standalone: true, imports: [CommonModule, FormsModule, NgOptimizedImage, ...ionComponents], providers: [HelperService], 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)}\n", "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
289
  }], ctorParameters: () => [], propDecorators: { requireSquare: [{
273
290
  type: Input
274
291
  }], value: [{
275
292
  type: Input
276
- }], label: [{
293
+ }], labels: [{
277
294
  type: Input
278
295
  }], editorRef: [{
279
296
  type: ViewChild,
@@ -283,6 +300,233 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.6", ngImpor
283
300
  args: [IonContent]
284
301
  }] } });
285
302
 
303
+ class PhotoViewerPage {
304
+ set labels(d) {
305
+ this.dictionary = Object.assign(this.dictionary, d);
306
+ }
307
+ constructor() {
308
+ this.dictionary = dictionaryForViewer();
309
+ this.imageUrls = [];
310
+ this.index = 0;
311
+ this.isCircle = false;
312
+ this.enableDelete = false;
313
+ this.enableFooterSafeArea = false;
314
+ this.modalCtrl = inject(ModalController);
315
+ this.el = inject(ElementRef);
316
+ this.service = inject(HelperService);
317
+ register();
318
+ this.service.initializeViewerIcons();
319
+ }
320
+ async ngOnInit() {
321
+ this.service.waitToFindDom(this.el.nativeElement, 'swiper-container').then(() => {
322
+ Object.assign(this.swiper.nativeElement, {
323
+ modules: [Navigation, Zoom, IonicSlides],
324
+ initialSlide: this.index,
325
+ slidesPerView: 1,
326
+ pagination: {
327
+ enabled: true,
328
+ clickable: true,
329
+ },
330
+ zoom: true,
331
+ });
332
+ this.swiper.nativeElement.initialize();
333
+ this.swiper.nativeElement.swiper.zoom.enable();
334
+ this.swiper.nativeElement.swiper.activeIndex = this.index;
335
+ this.swiper.nativeElement.swiper.update();
336
+ });
337
+ this.watchSwipe$ = fromEvent(this.el.nativeElement, 'touchstart')
338
+ .pipe(zipWith(fromEvent(this.el.nativeElement, 'touchend').pipe(withLatestFrom(fromEvent(this.el.nativeElement, 'touchmove')))), throttleTime(1))
339
+ .subscribe(([touchstart, [_, touchmove]]) => {
340
+ const touchstartClientX = touchstart.touches ? touchstart.touches[0].clientX : touchstart.detail[1].clientX;
341
+ const touchmoveClientX = touchmove.touches ? touchmove.touches[0].clientX : touchmove.detail[1].clientX;
342
+ const touchstartClientY = touchstart.touches ? touchstart.touches[0].clientY : touchstart.detail[1].clientY;
343
+ const touchmoveClientY = touchmove.touches ? touchmove.touches[0].clientY : touchmove.detail[1].clientY;
344
+ const xDiff = touchstartClientX - touchmoveClientX;
345
+ const yDiff = touchstartClientY - touchmoveClientY;
346
+ const slides = this.swiper.nativeElement.querySelectorAll('swiper-slide');
347
+ const isZoomed = Array.from(slides).find((slide) => {
348
+ return ['swiper-slide-zoomed', 'swiper-slide-active'].every((c) => slide.classList.contains(c));
349
+ });
350
+ const threshold = touchmove.touches ? -50 : -5;
351
+ if (!isZoomed && Math.abs(xDiff) < Math.abs(threshold) && yDiff < threshold && touchstart.timeStamp <= touchmove.timeStamp) {
352
+ this.watchSwipe$.unsubscribe();
353
+ this.modalCtrl.dismiss();
354
+ }
355
+ });
356
+ }
357
+ ngOnDestroy() {
358
+ if (this.watchSwipe$ && !this.watchSwipe$.closed) {
359
+ this.watchSwipe$.unsubscribe();
360
+ }
361
+ }
362
+ remove() {
363
+ this.modalCtrl.dismiss({
364
+ delete: {
365
+ index: this.swiper.nativeElement.swiper.activeIndex,
366
+ value: this.imageUrls[this.swiper.nativeElement.swiper.activeIndex],
367
+ },
368
+ });
369
+ }
370
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: PhotoViewerPage, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
371
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.0.7", type: PhotoViewerPage, isStandalone: true, selector: "app-photo-image", inputs: { imageUrls: "imageUrls", index: "index", isCircle: "isCircle", enableDelete: "enableDelete", enableFooterSafeArea: "enableFooterSafeArea", labels: "labels" }, providers: [HelperService], viewQueries: [{ propertyName: "swiper", first: true, predicate: ["swiper"], descendants: 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=\"danger\" fill=\"outline\" (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: ["@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)}\n", "ion-button.viewer-close-button{--color: var(--alway-white) !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", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }] }); }
372
+ }
373
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: PhotoViewerPage, decorators: [{
374
+ type: Component,
375
+ args: [{ selector: 'app-photo-image', standalone: true, imports: [NgFor, NgIf, ...ionComponents], schemas: [CUSTOM_ELEMENTS_SCHEMA], providers: [HelperService], 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=\"danger\" fill=\"outline\" (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: ["@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)}\n", "ion-button.viewer-close-button{--color: var(--alway-white) !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"] }]
376
+ }], ctorParameters: () => [], propDecorators: { imageUrls: [{
377
+ type: Input
378
+ }], index: [{
379
+ type: Input
380
+ }], isCircle: [{
381
+ type: Input
382
+ }], enableDelete: [{
383
+ type: Input
384
+ }], enableFooterSafeArea: [{
385
+ type: Input
386
+ }], labels: [{
387
+ type: Input
388
+ }], swiper: [{
389
+ type: ViewChild,
390
+ args: ['swiper']
391
+ }] } });
392
+
393
+ var PhotoEditorErrors;
394
+ (function (PhotoEditorErrors) {
395
+ PhotoEditorErrors["initialize"] = "input dom is not found";
396
+ PhotoEditorErrors["cancel"] = "user canceled";
397
+ PhotoEditorErrors["type"] = "upload file is not image.";
398
+ })(PhotoEditorErrors || (PhotoEditorErrors = {}));
399
+
400
+ class PhotoFileService {
401
+ constructor() {
402
+ this.$photoMaxSize = signal(1000);
403
+ this.actionSheetCtrl = inject(ActionSheetController);
404
+ this.platform = inject(Platform);
405
+ this.dictionary = dictionaryForService();
406
+ }
407
+ set photoMaxSize(value) {
408
+ this.$photoMaxSize.set(value);
409
+ }
410
+ set labels(d) {
411
+ this.dictionary = Object.assign(this.dictionary, d);
412
+ }
413
+ async loadPhoto(limit) {
414
+ /**
415
+ * Using Input for browser
416
+ */
417
+ if (!this.platform.is('capacitor')) {
418
+ return this.getPictureFromBrowser();
419
+ }
420
+ const actionSheet = await this.actionSheetCtrl.create({
421
+ buttons: [
422
+ {
423
+ text: this.dictionary.camera,
424
+ handler: () => {
425
+ actionSheet.dismiss('camera');
426
+ },
427
+ },
428
+ {
429
+ text: this.dictionary.album,
430
+ handler: () => {
431
+ actionSheet.dismiss('album');
432
+ },
433
+ },
434
+ {
435
+ text: this.dictionary.cancel,
436
+ role: 'cancel',
437
+ },
438
+ ],
439
+ });
440
+ await actionSheet.present();
441
+ const { data } = await actionSheet.onDidDismiss();
442
+ if (!data) {
443
+ return Promise.reject(PhotoEditorErrors.cancel);
444
+ }
445
+ if (data === 'camera') {
446
+ const defaultCamera = {
447
+ quality: 100,
448
+ width: this.$photoMaxSize(),
449
+ allowEditing: false,
450
+ resultType: CameraResultType.DataUrl,
451
+ source: CameraSource.Camera,
452
+ presentationStyle: 'popover',
453
+ };
454
+ const image = await Camera.getPhoto(defaultCamera).catch(() => undefined);
455
+ if (!image?.dataUrl) {
456
+ return Promise.reject(PhotoEditorErrors.cancel);
457
+ }
458
+ if (!image.dataUrl.includes('capacitor://localhost')) {
459
+ return [image.dataUrl];
460
+ }
461
+ return Promise.all([image.dataUrl].map(async (image) => await this.loadPhotoFromFilePath(image)));
462
+ }
463
+ if (data === 'album') {
464
+ const images = await Camera.pickImages({
465
+ quality: 100,
466
+ width: this.$photoMaxSize(),
467
+ limit,
468
+ presentationStyle: 'popover',
469
+ }).catch(() => undefined);
470
+ if (!images) {
471
+ return Promise.reject(PhotoEditorErrors.cancel);
472
+ }
473
+ return Promise.all(images.photos.map(async (image) => await this.loadPhotoFromFilePath(image.webPath)));
474
+ }
475
+ // Not run on this line. This is for lint
476
+ return [];
477
+ }
478
+ getPictureFromBrowser() {
479
+ const inputFile = document.querySelector('input#browserPhotoUploader');
480
+ if (!inputFile) {
481
+ return Promise.reject(PhotoEditorErrors.initialize);
482
+ }
483
+ return new Promise((resolve, reject) => {
484
+ inputFile.addEventListener('change', (e) => {
485
+ if (!e.target.files || !e.target.files[0]) {
486
+ reject(PhotoEditorErrors.cancel);
487
+ }
488
+ const file = e.target.files[0];
489
+ const reader = new FileReader();
490
+ reader.onload = (() => {
491
+ if (file.type.indexOf('image') < 0) {
492
+ reject(PhotoEditorErrors.type);
493
+ }
494
+ return async (event) => {
495
+ inputFile.value = '';
496
+ const result = event.target.result;
497
+ const data = await this.loadPhotoFromFilePath(result);
498
+ resolve([data]);
499
+ };
500
+ })();
501
+ reader.readAsDataURL(file);
502
+ }, false);
503
+ inputFile.click();
504
+ });
505
+ }
506
+ async loadPhotoFromFilePath(filePath) {
507
+ const defaultInstance = new ImageEditor(document.createElement('div'), {
508
+ cssMaxWidth: this.$photoMaxSize(),
509
+ cssMaxHeight: this.$photoMaxSize(),
510
+ });
511
+ const blob = await fetch(filePath).then((res) => res.blob());
512
+ const loaded = await defaultInstance.loadImageFromFile(new File([blob], 'data.png', { type: blob.type }));
513
+ const maxSize = Math.max(loaded.newWidth, loaded.newHeight);
514
+ const dataUrl = defaultInstance.toDataURL({
515
+ multiplier: this.$photoMaxSize() / maxSize,
516
+ });
517
+ defaultInstance.destroy();
518
+ return dataUrl;
519
+ }
520
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: PhotoFileService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
521
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: PhotoFileService, providedIn: 'root' }); }
522
+ }
523
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: PhotoFileService, decorators: [{
524
+ type: Injectable,
525
+ args: [{
526
+ providedIn: 'root',
527
+ }]
528
+ }], ctorParameters: () => [] });
529
+
286
530
  /*
287
531
  * Public API Surface of photo-editor
288
532
  */
@@ -291,5 +535,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.6", ngImpor
291
535
  * Generated bundle index. Do not edit.
292
536
  */
293
537
 
294
- export { PhotoEditorPage };
538
+ export { PhotoEditorPage, PhotoFileService, PhotoViewerPage };
295
539
  //# sourceMappingURL=rdlabo-ionic-angular-photo-editor.mjs.map