@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.
- package/esm2022/lib/dictionaries.mjs +27 -0
- package/esm2022/lib/filter-preset.mjs +1 -1
- package/esm2022/lib/pages/photo-editor.page.mjs +187 -0
- package/esm2022/lib/pages/photo-viewer.page.mjs +101 -0
- package/esm2022/lib/photoEditorErrors.mjs +7 -0
- package/esm2022/lib/service/helper.service.mjs +44 -0
- package/esm2022/lib/service/photo-file.service.mjs +137 -0
- package/esm2022/lib/types.mjs +1 -1
- package/esm2022/public-api.mjs +4 -2
- package/fesm2022/rdlabo-ionic-angular-photo-editor.mjs +268 -24
- package/fesm2022/rdlabo-ionic-angular-photo-editor.mjs.map +1 -1
- package/lib/dictionaries.d.ts +4 -0
- package/lib/filter-preset.d.ts +2 -2
- package/lib/{page → pages}/photo-editor.page.d.ts +4 -4
- package/lib/pages/photo-viewer.page.d.ts +26 -0
- package/lib/photoEditorErrors.d.ts +5 -0
- package/lib/service/helper.service.d.ts +9 -0
- package/lib/service/photo-file.service.d.ts +16 -0
- package/lib/types.d.ts +15 -1
- package/package.json +4 -1
- package/public-api.d.ts +3 -1
- package/esm2022/lib/dictionary.mjs +0 -17
- package/esm2022/lib/page/photo-editor.page.mjs +0 -187
- package/esm2022/lib/service/photo-editor.service.mjs +0 -41
- package/lib/dictionary.d.ts +0 -2
- package/lib/service/photo-editor.service.d.ts +0 -8
|
@@ -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
|
|
59
|
+
class HelperService {
|
|
56
60
|
constructor() { }
|
|
57
|
-
|
|
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.
|
|
83
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.
|
|
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.
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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.
|
|
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.
|
|
267
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.0.
|
|
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.
|
|
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
|
-
}],
|
|
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
|