@rdlabo/ionic-angular-photo-editor 20.0.3 → 21.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/eslint.config.js +7 -0
- package/ng-package.json +7 -0
- package/package.json +6 -17
- package/src/lib/dictionaries.ts +31 -0
- package/src/lib/filter-preset.ts +44 -0
- package/src/lib/ion-components.ts +3 -0
- package/src/lib/pages/core.scss +63 -0
- package/src/lib/pages/photo-editor/photo-editor.page.html +95 -0
- package/src/lib/pages/photo-editor/photo-editor.page.scss +95 -0
- package/src/lib/pages/photo-editor/photo-editor.page.spec.ts +21 -0
- package/src/lib/pages/photo-editor/photo-editor.page.ts +221 -0
- package/src/lib/pages/photo-viewer/photo-viewer.page.html +31 -0
- package/src/lib/pages/photo-viewer/photo-viewer.page.scss +29 -0
- package/src/lib/pages/photo-viewer/photo-viewer.page.spec.ts +25 -0
- package/src/lib/pages/photo-viewer/photo-viewer.page.ts +134 -0
- package/src/lib/pages/util.ts +48 -0
- package/src/lib/photoEditorErrors.ts +5 -0
- package/src/lib/services/photo-file.service.spec.ts +19 -0
- package/src/lib/services/photo-file.service.ts +161 -0
- package/src/lib/types.ts +55 -0
- package/src/public-api.ts +8 -0
- package/tsconfig.lib.json +14 -0
- package/tsconfig.lib.prod.json +10 -0
- package/tsconfig.spec.json +14 -0
- package/fesm2022/rdlabo-ionic-angular-photo-editor.mjs +0 -533
- package/fesm2022/rdlabo-ionic-angular-photo-editor.mjs.map +0 -1
- package/index.d.ts +0 -132
|
@@ -1,533 +0,0 @@
|
|
|
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());
|
|
125
|
-
this.requireSquare = input(false, {
|
|
126
|
-
transform: coerceBooleanProperty,
|
|
127
|
-
});
|
|
128
|
-
this.labels = input(undefined);
|
|
129
|
-
this.setLabels = effect(() => {
|
|
130
|
-
if (this.labels()) {
|
|
131
|
-
this.dictionary.update((value) => ({ ...value, ...this.labels() }));
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
this.value = input.required();
|
|
135
|
-
this.editorRef = viewChild.required('imageEditor');
|
|
136
|
-
this.ionContent = viewChild.required(IonContent, { read: ElementRef });
|
|
137
|
-
this.filters = signal([]);
|
|
138
|
-
this.footerMenu = signal('menu');
|
|
139
|
-
this.currentCrop = signal('cover');
|
|
140
|
-
this.currentRotate = signal(0);
|
|
141
|
-
this.photoCrop = signal({
|
|
142
|
-
width: 0,
|
|
143
|
-
height: 0,
|
|
144
|
-
});
|
|
145
|
-
this.isCropped = signal(false);
|
|
146
|
-
this.adoptFilter = signal(undefined);
|
|
147
|
-
this.filterPreset = computed(() => filterPreset(this.dictionary()));
|
|
148
|
-
this.footerMenu$ = toObservable(this.footerMenu);
|
|
149
|
-
this.initSubscription$ = [];
|
|
150
|
-
this.filterImageSize = 240;
|
|
151
|
-
this.canvasContainerObserver = new MutationObserver((mutationsList) => {
|
|
152
|
-
if (mutationsList.find((mutation) => mutation.type === 'attributes' && mutation.attributeName === 'style')) {
|
|
153
|
-
// Cover the image editor with the parent element
|
|
154
|
-
const editorRef = this.editorRef();
|
|
155
|
-
editorRef.nativeElement.style.minWidth = mutationsList[0].target.parentElement.style.maxWidth;
|
|
156
|
-
editorRef.nativeElement.style.minHeight = mutationsList[0].target.parentElement.style.maxHeight;
|
|
157
|
-
this.photoCrop.set({
|
|
158
|
-
width: mutationsList[0].target.parentElement.querySelector('canvas').width,
|
|
159
|
-
height: mutationsList[0].target.parentElement.querySelector('canvas').height,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
initializeEditorIcons();
|
|
164
|
-
}
|
|
165
|
-
ngOnInit() {
|
|
166
|
-
this.initSubscription$.push(this.footerMenu$.subscribe((value) => {
|
|
167
|
-
if (value === 'filter') {
|
|
168
|
-
this.initializeFilterMenu().then();
|
|
169
|
-
}
|
|
170
|
-
if (value === 'crop') {
|
|
171
|
-
this.editorInstance.startDrawingMode('CROPPER');
|
|
172
|
-
this.changeCrop(this.requireSquare() ? '1' : 'cover');
|
|
173
|
-
}
|
|
174
|
-
}));
|
|
175
|
-
}
|
|
176
|
-
ngOnDestroy() {
|
|
177
|
-
this.initSubscription$.forEach((subscription) => subscription.unsubscribe());
|
|
178
|
-
}
|
|
179
|
-
async ionViewDidEnter() {
|
|
180
|
-
this.editorInstance = new ImageEditor(this.editorRef().nativeElement, {
|
|
181
|
-
cssMaxWidth: this.ionContent().nativeElement.clientWidth - 32,
|
|
182
|
-
cssMaxHeight: this.ionContent().nativeElement.clientHeight - 32,
|
|
183
|
-
});
|
|
184
|
-
waitToFindDom(this.editorRef().nativeElement, '.tui-image-editor-canvas-container').then(() => {
|
|
185
|
-
this.canvasContainerObserver.observe(this.editorRef().nativeElement.querySelector('.tui-image-editor-canvas-container'), {
|
|
186
|
-
attributes: true,
|
|
187
|
-
childList: false,
|
|
188
|
-
subtree: true,
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
const blob = await fetch(this.value()).then((res) => res.blob());
|
|
192
|
-
await this.editorInstance.loadImageFromFile(new File([blob], 'data.png', { type: blob.type }));
|
|
193
|
-
this.footerMenu.set(this.requireSquare() ? 'crop' : 'menu');
|
|
194
|
-
}
|
|
195
|
-
ionViewDidLeave() {
|
|
196
|
-
this.editorInstance.destroy();
|
|
197
|
-
this.canvasContainerObserver.disconnect();
|
|
198
|
-
}
|
|
199
|
-
changeCrop(crop) {
|
|
200
|
-
const rect = crop === 'cover' ? this.photoCrop().width / this.photoCrop().height : crop === '16/9' ? 16 / 9 : 1;
|
|
201
|
-
this.editorInstance.setCropzoneRect(crop !== 'auto' ? rect : undefined);
|
|
202
|
-
this.currentCrop.set(crop);
|
|
203
|
-
}
|
|
204
|
-
async rotate() {
|
|
205
|
-
this.editorInstance.stopDrawingMode();
|
|
206
|
-
await this.editorInstance.rotate(90);
|
|
207
|
-
this.currentRotate.update((value) => value + 90);
|
|
208
|
-
this.editorInstance.startDrawingMode('CROPPER');
|
|
209
|
-
requestAnimationFrame(() => this.changeCrop(this.currentCrop()));
|
|
210
|
-
}
|
|
211
|
-
async closeCrop(type) {
|
|
212
|
-
if (this.footerMenu() === 'crop') {
|
|
213
|
-
if (type === 'cancel') {
|
|
214
|
-
await this.editorInstance.rotate(this.currentRotate() * -1);
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
await this.editorInstance.crop(this.editorInstance.getCropzoneRect());
|
|
218
|
-
this.isCropped.set(true);
|
|
219
|
-
}
|
|
220
|
-
this.currentRotate.set(0);
|
|
221
|
-
this.currentCrop.set('cover');
|
|
222
|
-
this.editorInstance.stopDrawingMode();
|
|
223
|
-
}
|
|
224
|
-
else if (this.footerMenu() === 'brightness') {
|
|
225
|
-
if (type === 'cancel' && this.editorInstance.hasFilter('brightness')) {
|
|
226
|
-
await this.editorInstance.removeFilter('brightness');
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
this.footerMenu.set('menu');
|
|
230
|
-
}
|
|
231
|
-
async changeRange(event) {
|
|
232
|
-
if (this.editorInstance.hasFilter('brightness')) {
|
|
233
|
-
await this.editorInstance.removeFilter('brightness');
|
|
234
|
-
}
|
|
235
|
-
await this.editorInstance.applyFilter('brightness', {
|
|
236
|
-
brightness: Number(event.detail.value) / 255,
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
imageSave() {
|
|
240
|
-
const value = this.editorInstance.toDataURL();
|
|
241
|
-
this.modalCtrl.dismiss({ value });
|
|
242
|
-
}
|
|
243
|
-
async initializeFilterMenu() {
|
|
244
|
-
const filters = [];
|
|
245
|
-
const defaultInstance = new ImageEditor(document.createElement('div'), {
|
|
246
|
-
cssMaxWidth: this.filterImageSize,
|
|
247
|
-
cssMaxHeight: (this.photoCrop().height * this.filterImageSize) / this.photoCrop().width,
|
|
248
|
-
});
|
|
249
|
-
const blob = await fetch(this.editorInstance.toDataURL({
|
|
250
|
-
multiplier: this.filterImageSize / this.photoCrop().width,
|
|
251
|
-
})).then((res) => res.blob());
|
|
252
|
-
await defaultInstance.loadImageFromFile(new File([blob], 'defaultInstance.png', { type: blob.type }));
|
|
253
|
-
for (const filter of this.filterPreset()) {
|
|
254
|
-
if (filter.type !== 'Default') {
|
|
255
|
-
await defaultInstance.applyFilter(filter.type, filter.option);
|
|
256
|
-
}
|
|
257
|
-
filters.push({
|
|
258
|
-
name: filter.name,
|
|
259
|
-
type: filter.type,
|
|
260
|
-
option: filter.option,
|
|
261
|
-
data: defaultInstance.toDataURL(),
|
|
262
|
-
width: this.filterImageSize,
|
|
263
|
-
height: (this.photoCrop().height * this.filterImageSize) / this.photoCrop().width,
|
|
264
|
-
});
|
|
265
|
-
if (filter.type !== 'Default') {
|
|
266
|
-
await defaultInstance.removeFilter(filter.type);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
this.filters.set(filters);
|
|
270
|
-
defaultInstance.destroy();
|
|
271
|
-
}
|
|
272
|
-
async filterImage(filter) {
|
|
273
|
-
if (this.adoptFilter()) {
|
|
274
|
-
await this.editorInstance.removeFilter(this.adoptFilter().type);
|
|
275
|
-
}
|
|
276
|
-
if (filter.type === 'Default') {
|
|
277
|
-
this.adoptFilter.set(undefined);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
await this.editorInstance.applyFilter(filter.type, filter.option);
|
|
281
|
-
this.adoptFilter.set(filter);
|
|
282
|
-
}
|
|
283
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: PhotoEditorPage, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
284
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.0", 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=\"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\" 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 }); }
|
|
285
|
-
}
|
|
286
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: PhotoEditorPage, decorators: [{
|
|
287
|
-
type: Component,
|
|
288
|
-
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=\"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\" 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"] }]
|
|
289
|
-
}], ctorParameters: () => [] });
|
|
290
|
-
|
|
291
|
-
class PhotoViewerPage {
|
|
292
|
-
constructor() {
|
|
293
|
-
this.imageUrls = input.required();
|
|
294
|
-
this.index = input(0, {
|
|
295
|
-
transform: coerceNumberProperty,
|
|
296
|
-
});
|
|
297
|
-
this.isCircle = input(false, {
|
|
298
|
-
transform: coerceBooleanProperty,
|
|
299
|
-
});
|
|
300
|
-
this.enableDelete = input(false, {
|
|
301
|
-
transform: coerceBooleanProperty,
|
|
302
|
-
});
|
|
303
|
-
this.enableFooterSafeArea = input(false, {
|
|
304
|
-
transform: coerceBooleanProperty,
|
|
305
|
-
});
|
|
306
|
-
this.labels = input();
|
|
307
|
-
this.setLabels = effect(() => {
|
|
308
|
-
if (this.labels()) {
|
|
309
|
-
this.dictionary.update((value) => ({ ...value, ...this.labels() }));
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
this.swiper = viewChild.required('swiper');
|
|
313
|
-
this.dictionary = signal(dictionaryForViewer());
|
|
314
|
-
this.watchSwipe$ = new Subscription();
|
|
315
|
-
this.modalCtrl = inject(ModalController);
|
|
316
|
-
this.el = inject(ElementRef);
|
|
317
|
-
register();
|
|
318
|
-
initializeViewerIcons();
|
|
319
|
-
}
|
|
320
|
-
async ngOnInit() {
|
|
321
|
-
waitToFindDom(this.el.nativeElement, 'swiper-container').then(() => {
|
|
322
|
-
const index = this.index();
|
|
323
|
-
const swiper = this.swiper();
|
|
324
|
-
Object.assign(swiper.nativeElement, {
|
|
325
|
-
modules: [Navigation, Zoom, IonicSlides],
|
|
326
|
-
initialSlide: index,
|
|
327
|
-
slidesPerView: 1,
|
|
328
|
-
pagination: {
|
|
329
|
-
enabled: true,
|
|
330
|
-
clickable: true,
|
|
331
|
-
},
|
|
332
|
-
zoom: true,
|
|
333
|
-
});
|
|
334
|
-
swiper.nativeElement.initialize();
|
|
335
|
-
swiper.nativeElement.swiper.zoom.enable();
|
|
336
|
-
swiper.nativeElement.swiper.activeIndex = index;
|
|
337
|
-
swiper.nativeElement.swiper.update();
|
|
338
|
-
});
|
|
339
|
-
this.watchSwipe$.add(fromEvent(this.el.nativeElement, 'touchstart')
|
|
340
|
-
.pipe(zipWith(fromEvent(this.el.nativeElement, 'touchend').pipe(withLatestFrom(fromEvent(this.el.nativeElement, 'touchmove')))), throttleTime(1))
|
|
341
|
-
.subscribe(([touchstart, [_, touchmove]]) => {
|
|
342
|
-
const touchstartClientX = touchstart.touches ? touchstart.touches[0].clientX : touchstart.detail[1].clientX;
|
|
343
|
-
const touchmoveClientX = touchmove.touches ? touchmove.touches[0].clientX : touchmove.detail[1].clientX;
|
|
344
|
-
const touchstartClientY = touchstart.touches ? touchstart.touches[0].clientY : touchstart.detail[1].clientY;
|
|
345
|
-
const touchmoveClientY = touchmove.touches ? touchmove.touches[0].clientY : touchmove.detail[1].clientY;
|
|
346
|
-
const xDiff = touchstartClientX - touchmoveClientX;
|
|
347
|
-
const yDiff = touchstartClientY - touchmoveClientY;
|
|
348
|
-
const slides = this.swiper().nativeElement.querySelectorAll('swiper-slide');
|
|
349
|
-
const isZoomed = Array.from(slides).find((slide) => {
|
|
350
|
-
return ['swiper-slide-zoomed', 'swiper-slide-active'].every((c) => slide.classList.contains(c));
|
|
351
|
-
});
|
|
352
|
-
const threshold = touchmove.touches ? -50 : -5;
|
|
353
|
-
if (!isZoomed && Math.abs(xDiff) < Math.abs(threshold) && yDiff < threshold && touchstart.timeStamp <= touchmove.timeStamp) {
|
|
354
|
-
this.watchSwipe$.unsubscribe();
|
|
355
|
-
this.modalCtrl.dismiss();
|
|
356
|
-
}
|
|
357
|
-
}));
|
|
358
|
-
}
|
|
359
|
-
ngOnDestroy() {
|
|
360
|
-
this.watchSwipe$.unsubscribe();
|
|
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: "20.0.0", ngImport: i0, type: PhotoViewerPage, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
371
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.0", 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=\"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: [":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 }); }
|
|
372
|
-
}
|
|
373
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: PhotoViewerPage, decorators: [{
|
|
374
|
-
type: Component,
|
|
375
|
-
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=\"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: [":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"] }]
|
|
376
|
-
}], ctorParameters: () => [] });
|
|
377
|
-
|
|
378
|
-
var PhotoEditorErrors;
|
|
379
|
-
(function (PhotoEditorErrors) {
|
|
380
|
-
PhotoEditorErrors["initialize"] = "input dom is not found";
|
|
381
|
-
PhotoEditorErrors["cancel"] = "user canceled";
|
|
382
|
-
PhotoEditorErrors["type"] = "upload file is not image.";
|
|
383
|
-
})(PhotoEditorErrors || (PhotoEditorErrors = {}));
|
|
384
|
-
|
|
385
|
-
class PhotoFileService {
|
|
386
|
-
constructor() {
|
|
387
|
-
this.$photoMaxSize = signal(1000);
|
|
388
|
-
this.actionSheetCtrl = inject(ActionSheetController);
|
|
389
|
-
this.platform = inject(Platform);
|
|
390
|
-
this.dictionary = signal(dictionaryForService());
|
|
391
|
-
}
|
|
392
|
-
set photoMaxSize(value) {
|
|
393
|
-
this.$photoMaxSize.set(value);
|
|
394
|
-
}
|
|
395
|
-
set labels(d) {
|
|
396
|
-
this.dictionary.update((value) => ({ ...value, ...d }));
|
|
397
|
-
}
|
|
398
|
-
async loadPhoto(limit) {
|
|
399
|
-
/**
|
|
400
|
-
* Using Input for browser
|
|
401
|
-
*/
|
|
402
|
-
if (!this.platform.is('capacitor')) {
|
|
403
|
-
return this.getPictureFromBrowser();
|
|
404
|
-
}
|
|
405
|
-
const actionSheet = await this.actionSheetCtrl.create({
|
|
406
|
-
buttons: [
|
|
407
|
-
{
|
|
408
|
-
text: this.dictionary().camera,
|
|
409
|
-
handler: () => {
|
|
410
|
-
actionSheet.dismiss('camera');
|
|
411
|
-
},
|
|
412
|
-
},
|
|
413
|
-
{
|
|
414
|
-
text: this.dictionary().album,
|
|
415
|
-
handler: () => {
|
|
416
|
-
actionSheet.dismiss('album');
|
|
417
|
-
},
|
|
418
|
-
},
|
|
419
|
-
{
|
|
420
|
-
text: this.dictionary().cancel,
|
|
421
|
-
role: 'cancel',
|
|
422
|
-
},
|
|
423
|
-
],
|
|
424
|
-
});
|
|
425
|
-
await actionSheet.present();
|
|
426
|
-
const { data } = await actionSheet.onDidDismiss();
|
|
427
|
-
if (!data) {
|
|
428
|
-
return Promise.reject(PhotoEditorErrors.cancel);
|
|
429
|
-
}
|
|
430
|
-
if (data === 'camera') {
|
|
431
|
-
const defaultCamera = {
|
|
432
|
-
quality: 100,
|
|
433
|
-
width: this.$photoMaxSize(),
|
|
434
|
-
allowEditing: false,
|
|
435
|
-
resultType: CameraResultType.DataUrl,
|
|
436
|
-
source: CameraSource.Camera,
|
|
437
|
-
presentationStyle: 'popover',
|
|
438
|
-
};
|
|
439
|
-
const image = await Camera.getPhoto(defaultCamera).catch(() => undefined);
|
|
440
|
-
if (!image?.dataUrl) {
|
|
441
|
-
return Promise.reject(PhotoEditorErrors.cancel);
|
|
442
|
-
}
|
|
443
|
-
if (!image.dataUrl.includes('capacitor://localhost')) {
|
|
444
|
-
return [image.dataUrl];
|
|
445
|
-
}
|
|
446
|
-
return Promise.all([image.dataUrl].map(async (image) => await this.loadPhotoFromFilePath(image)));
|
|
447
|
-
}
|
|
448
|
-
if (data === 'album') {
|
|
449
|
-
const images = await Camera.pickImages({
|
|
450
|
-
quality: 100,
|
|
451
|
-
width: this.$photoMaxSize(),
|
|
452
|
-
limit,
|
|
453
|
-
presentationStyle: 'popover',
|
|
454
|
-
}).catch(() => undefined);
|
|
455
|
-
if (!images) {
|
|
456
|
-
return Promise.reject(PhotoEditorErrors.cancel);
|
|
457
|
-
}
|
|
458
|
-
return Promise.all(images.photos.map(async (image) => await this.loadPhotoFromFilePath(image.webPath)));
|
|
459
|
-
}
|
|
460
|
-
// Not run on this line. This is for lint
|
|
461
|
-
return [];
|
|
462
|
-
}
|
|
463
|
-
getPictureFromBrowser() {
|
|
464
|
-
const inputFile = document.querySelector('input#browserPhotoUploader');
|
|
465
|
-
if (!inputFile) {
|
|
466
|
-
return Promise.reject(PhotoEditorErrors.initialize);
|
|
467
|
-
}
|
|
468
|
-
return new Promise((resolve, reject) => {
|
|
469
|
-
const cancelMethod = () => {
|
|
470
|
-
inputFile.removeEventListener('cancel', cancelMethod, false);
|
|
471
|
-
inputFile.removeEventListener('change', changeMethod, false);
|
|
472
|
-
reject(PhotoEditorErrors.cancel);
|
|
473
|
-
};
|
|
474
|
-
const changeMethod = (e) => {
|
|
475
|
-
inputFile.removeEventListener('cancel', cancelMethod, false);
|
|
476
|
-
inputFile.removeEventListener('change', changeMethod, false);
|
|
477
|
-
if (!e.target.files || !e.target.files[0]) {
|
|
478
|
-
reject(PhotoEditorErrors.cancel);
|
|
479
|
-
}
|
|
480
|
-
const file = e.target.files[0];
|
|
481
|
-
const reader = new FileReader();
|
|
482
|
-
reader.onload = (() => {
|
|
483
|
-
if (file.type.indexOf('image') < 0) {
|
|
484
|
-
reject(PhotoEditorErrors.type);
|
|
485
|
-
}
|
|
486
|
-
return async (event) => {
|
|
487
|
-
inputFile.value = '';
|
|
488
|
-
const result = event.target.result;
|
|
489
|
-
const data = await this.loadPhotoFromFilePath(result);
|
|
490
|
-
resolve([data]);
|
|
491
|
-
};
|
|
492
|
-
})();
|
|
493
|
-
reader.readAsDataURL(file);
|
|
494
|
-
};
|
|
495
|
-
inputFile.addEventListener('cancel', cancelMethod, false);
|
|
496
|
-
inputFile.addEventListener('change', changeMethod, false);
|
|
497
|
-
inputFile.click();
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
async loadPhotoFromFilePath(filePath) {
|
|
501
|
-
const defaultInstance = new ImageEditor(document.createElement('div'), {
|
|
502
|
-
cssMaxWidth: this.$photoMaxSize(),
|
|
503
|
-
cssMaxHeight: this.$photoMaxSize(),
|
|
504
|
-
});
|
|
505
|
-
const blob = await fetch(filePath).then((res) => res.blob());
|
|
506
|
-
const loaded = await defaultInstance.loadImageFromFile(new File([blob], 'data.png', { type: blob.type }));
|
|
507
|
-
const maxSize = Math.max(loaded.newWidth, loaded.newHeight);
|
|
508
|
-
const dataUrl = defaultInstance.toDataURL({
|
|
509
|
-
multiplier: this.$photoMaxSize() / maxSize,
|
|
510
|
-
});
|
|
511
|
-
defaultInstance.destroy();
|
|
512
|
-
return dataUrl;
|
|
513
|
-
}
|
|
514
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: PhotoFileService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
515
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: PhotoFileService, providedIn: 'root' }); }
|
|
516
|
-
}
|
|
517
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: PhotoFileService, decorators: [{
|
|
518
|
-
type: Injectable,
|
|
519
|
-
args: [{
|
|
520
|
-
providedIn: 'root',
|
|
521
|
-
}]
|
|
522
|
-
}] });
|
|
523
|
-
|
|
524
|
-
/*
|
|
525
|
-
* Public API Surface of photo-editor
|
|
526
|
-
*/
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Generated bundle index. Do not edit.
|
|
530
|
-
*/
|
|
531
|
-
|
|
532
|
-
export { PhotoEditorPage, PhotoFileService, PhotoViewerPage };
|
|
533
|
-
//# sourceMappingURL=rdlabo-ionic-angular-photo-editor.mjs.map
|