@dataclouder/ngx-cloud-storage 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # StorageUploader
2
+
3
+ This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.0.
4
+
5
+ ## Code scaffolding
6
+
7
+ Run `ng generate component component-name --project storage-uploader` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project storage-uploader`.
8
+ > Note: Don't forget to add `--project storage-uploader` or else it will be added to the default project in your `angular.json` file.
9
+
10
+ ## Build
11
+
12
+ Run `ng build storage-uploader` to build the project. The build artifacts will be stored in the `dist/` directory.
13
+
14
+ ## Publishing
15
+
16
+ After building your library with `ng build storage-uploader`, go to the dist folder `cd dist/storage-uploader` and run `npm publish`.
17
+
18
+ ## Running unit tests
19
+
20
+ Run `ng test storage-uploader` to execute the unit tests via [Karma](https://karma-runner.github.io).
21
+
22
+ ## Further help
23
+
24
+ To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
@@ -0,0 +1,482 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, EventEmitter, Component, Input, Output, ViewChild } from '@angular/core';
3
+ import { ImageCropperComponent } from 'ngx-image-cropper';
4
+ import { NgIf } from '@angular/common';
5
+ import * as i2 from '@angular/forms';
6
+ import { FormsModule } from '@angular/forms';
7
+ import * as i1$1 from '@angular/fire/storage';
8
+ import { getStorage, listAll, deleteObject, getDownloadURL, ref as ref$1 } from '@angular/fire/storage';
9
+ import { ref } from 'firebase/storage';
10
+ import { lastValueFrom } from 'rxjs';
11
+ import * as i1 from '@angular/fire/compat/storage';
12
+ import * as i5 from 'primeng/dialog';
13
+ import { DialogModule } from 'primeng/dialog';
14
+ import * as i6 from 'primeng/tooltip';
15
+ import { TooltipModule } from 'primeng/tooltip';
16
+ import * as i3 from 'primeng/button';
17
+ import { ButtonModule } from 'primeng/button';
18
+ import * as i7 from 'primeng/message';
19
+ import { MessageModule } from 'primeng/message';
20
+ import * as i8 from 'primeng/select';
21
+ import { SelectModule } from 'primeng/select';
22
+ import * as i9 from 'primeng/inputtext';
23
+ import { InputTextModule } from 'primeng/inputtext';
24
+ import * as i4 from 'primeng/api';
25
+
26
+ const AspectRatioOptions = [
27
+ { value: '1:1', label: 'square', description: 'Square (1:1)', valueRatio: 1 / 1 },
28
+ { value: '1:2', label: 'vertical_1_2', description: 'Vertical (1:2)', valueRatio: 1 / 2 },
29
+ { value: '2:3', label: 'vertical_2_3', description: 'Vertical (2:3)', valueRatio: 2 / 3 },
30
+ { value: '3:4', label: 'vertical_3_4', description: 'Vertical (3:4)', valueRatio: 3 / 4 },
31
+ { value: '4:5', label: 'vertical_4_5', description: 'Vertical (4:5)', valueRatio: 4 / 5 },
32
+ { value: '9:16', label: 'vertical_9_16', description: 'Vertical (9:16)', valueRatio: 9 / 16 },
33
+ { value: '2:1', label: 'horizontal_2_1', description: 'Horizontal (2:1)', valueRatio: 2 / 1 },
34
+ { value: '3:2', label: 'horizontal_3_2', description: 'Horizontal (3:2)', valueRatio: 3 / 2 },
35
+ { value: '4:3', label: 'horizontal_4_3', description: 'Horizontal (4:3)', valueRatio: 4 / 3 },
36
+ { value: '5:4', label: 'horizontal_5_4', description: 'Horizontal (5:4)', valueRatio: 5 / 4 },
37
+ { value: '16:9', label: 'horizontal_16_9', description: 'Horizontal (16:9)', valueRatio: 16 / 9 },
38
+ ];
39
+ var AspectType;
40
+ (function (AspectType) {
41
+ AspectType["Square"] = "square";
42
+ AspectType["Rectangle"] = "rectangle";
43
+ AspectType["Banner"] = "banner";
44
+ AspectType["RectangleLarge"] = "rectangleLarge";
45
+ AspectType["Vertical_9_16"] = "vertical_9_16";
46
+ AspectType["Vertical_3_5"] = "vertical_3_5";
47
+ AspectType["Vertical_2_3"] = "vertical_2_3";
48
+ })(AspectType || (AspectType = {}));
49
+ // Creo que seria bueno refactorizar para solo enviar aspect ratio asi
50
+ var AspectRatio2;
51
+ (function (AspectRatio2) {
52
+ // Vertical
53
+ AspectRatio2[AspectRatio2["9:16"] = 1.7777777777777777] = "9:16";
54
+ AspectRatio2[AspectRatio2["3:5"] = 0.6] = "3:5";
55
+ AspectRatio2[AspectRatio2["5:8"] = 0.625] = "5:8";
56
+ AspectRatio2[AspectRatio2["2:3"] = 0.6666666666666666] = "2:3";
57
+ AspectRatio2[AspectRatio2["1:1"] = 1] = "1:1";
58
+ })(AspectRatio2 || (AspectRatio2 = {}));
59
+ var ResolutionType;
60
+ (function (ResolutionType) {
61
+ ResolutionType[ResolutionType["Small"] = 200] = "Small";
62
+ ResolutionType[ResolutionType["Medium"] = 400] = "Medium";
63
+ ResolutionType[ResolutionType["MediumLarge"] = 800] = "MediumLarge";
64
+ ResolutionType[ResolutionType["Large"] = 1200] = "Large";
65
+ ResolutionType[ResolutionType["VeryLarge"] = 1600] = "VeryLarge";
66
+ })(ResolutionType || (ResolutionType = {}));
67
+ const AspectRatio = {
68
+ [AspectType.Square]: 1 / 1,
69
+ [AspectType.Rectangle]: 16 / 9,
70
+ [AspectType.Vertical_9_16]: 9 / 16,
71
+ [AspectType.RectangleLarge]: 16 / 8,
72
+ [AspectType.Banner]: 16 / 7,
73
+ [AspectType.Vertical_3_5]: 3 / 5,
74
+ [AspectType.Vertical_2_3]: 2 / 3,
75
+ };
76
+ const DEFAULT_SETTINGS = {
77
+ path: '/default-collection/id/please-change-this-subcollection',
78
+ fileName: 'image',
79
+ cropSettings: {
80
+ aspectRatio: AspectType.Square,
81
+ resolutions: [ResolutionType.Small, ResolutionType.MediumLarge],
82
+ resizeToWidth: 450,
83
+ },
84
+ };
85
+
86
+ class MultiImagesStorageService {
87
+ constructor(storage) {
88
+ this.storage = storage;
89
+ }
90
+ async uploadImage(image, path) {
91
+ try {
92
+ const refStorage = this.storage.ref(path);
93
+ const task = await refStorage.put(image);
94
+ const { fullPath, bucket } = task.metadata;
95
+ const url = await lastValueFrom(refStorage.getDownloadURL());
96
+ const imageStorage = { url, path: fullPath, bucket };
97
+ return imageStorage;
98
+ }
99
+ catch (error) {
100
+ console.error('uploading image error: ', error);
101
+ return null;
102
+ }
103
+ }
104
+ uploadImage2(imageMulticrops, path) {
105
+ // path is usually [products/id] will create a new folder with the name and upload the images
106
+ const blobs = imageMulticrops.imagesBlobs;
107
+ const tasks = [];
108
+ let fileName = imageMulticrops.settings?.renameFile ?? imageMulticrops.file.name;
109
+ fileName = fileName.split('.')[0];
110
+ // load the default image jpeg
111
+ path = `${path}/${fileName}`;
112
+ const defaultImagePath = `${path}/${fileName}-400W.jpeg`;
113
+ const refStorage = this.storage.ref(defaultImagePath);
114
+ const task = refStorage.put(imageMulticrops.defaultImageBlob);
115
+ tasks.push(this.uploadAndGetUrl(task, 'default'));
116
+ // Upload webp images
117
+ for (const resolutionStr of Object.keys(blobs)) {
118
+ const fullName = `${fileName}-${resolutionStr}W.webp`;
119
+ const filePath = `${path}/${fullName}`;
120
+ const refStorage = this.storage.ref(filePath);
121
+ const resolution = Number(resolutionStr);
122
+ const task = refStorage.put(blobs[resolution]);
123
+ tasks.push(this.uploadAndGetUrl(task, resolution + 'W'));
124
+ }
125
+ return Promise.all(tasks).then(async (upladedImages) => {
126
+ let defaultImage = {};
127
+ let resolutionsUrls = {};
128
+ for (const image of upladedImages) {
129
+ if (image.resolution === 'default') {
130
+ defaultImage = image;
131
+ }
132
+ else {
133
+ resolutionsUrls[image.resolution] = image.url;
134
+ }
135
+ }
136
+ defaultImage['resolutions'] = resolutionsUrls;
137
+ defaultImage['path'] = path;
138
+ return defaultImage;
139
+ });
140
+ }
141
+ async delete_directory(imagePath) {
142
+ // WARNING!! user very carefully could delete whatever folder
143
+ const storage = getStorage();
144
+ const directoryRef = ref(storage, imagePath);
145
+ listAll(directoryRef)
146
+ .then((res) => {
147
+ res.items.forEach((itemRef) => {
148
+ console.log(itemRef);
149
+ deleteObject(itemRef);
150
+ });
151
+ })
152
+ .catch((error) => {
153
+ console.error('error al eliminar imagenes de cloud storage', error);
154
+ });
155
+ }
156
+ async deleteImage(imagePath) {
157
+ const storageRef = this.storage.ref(imagePath);
158
+ storageRef.delete().subscribe((res) => {
159
+ console.log('image deleted', res);
160
+ });
161
+ }
162
+ async uploadAndGetUrl(task, resolution) {
163
+ const snap = await task;
164
+ const { fullPath, bucket, name } = snap.metadata;
165
+ const storage = getStorage();
166
+ const storageRef = ref(storage, fullPath);
167
+ const url = await getDownloadURL(storageRef);
168
+ const meta = {
169
+ url,
170
+ fullPath,
171
+ bucket,
172
+ name,
173
+ resolution,
174
+ path: '',
175
+ resolutions: {},
176
+ };
177
+ return meta;
178
+ }
179
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: MultiImagesStorageService, deps: [{ token: i1.AngularFireStorage }], target: i0.ɵɵFactoryTarget.Injectable }); }
180
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: MultiImagesStorageService, providedIn: 'root' }); }
181
+ }
182
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: MultiImagesStorageService, decorators: [{
183
+ type: Injectable,
184
+ args: [{
185
+ providedIn: 'root',
186
+ }]
187
+ }], ctorParameters: () => [{ type: i1.AngularFireStorage }] });
188
+
189
+ class CropperComponent {
190
+ constructor(multiImagesStorageService) {
191
+ this.multiImagesStorageService = multiImagesStorageService;
192
+ // overrides name, path and resizeToWidth
193
+ this.imageSettings = {};
194
+ this.ratioType = AspectType.Square;
195
+ this.resolutions = [ResolutionType.MediumLarge];
196
+ this.imageUploaded = new EventEmitter();
197
+ this.onImageCropped = new EventEmitter();
198
+ this.onFileSelected = new EventEmitter();
199
+ this.fileMetadata = null;
200
+ this.aspectRatio = 1;
201
+ this.croppedImage = '';
202
+ this.isLoading = false;
203
+ this.isUploaded = false;
204
+ this.renameFile = '';
205
+ this.storagePath = '';
206
+ this.showModal = false;
207
+ }
208
+ ngOnInit() {
209
+ this.aspectRatio = AspectRatio[this.ratioType];
210
+ if (this.imageSettings.path) {
211
+ this.storagePath = `${this.imageSettings.path}/${this.imageSettings.fileName}.webp`;
212
+ }
213
+ }
214
+ reloadPath() {
215
+ this.storagePath = `${this.imageSettings.path}/${this.renameFile}.webp`;
216
+ }
217
+ async fileChangeEvent(event) {
218
+ this.imageChangedEvent = event;
219
+ const file = event?.target?.files[0];
220
+ if (file) {
221
+ this.fileMetadata = file;
222
+ this.onFileSelected.emit(file);
223
+ this.showModal = true; // Show modal when file is selected
224
+ this.renameFile = this.fileMetadata?.name?.split('.')[0];
225
+ console.log(this.renameFile);
226
+ if (!this.imageSettings.fileName) {
227
+ this.reloadPath();
228
+ }
229
+ }
230
+ }
231
+ onInnerImageCropped(event) {
232
+ this.croppedImage = event.base64;
233
+ }
234
+ imageLoaded(image) {
235
+ // show cropper
236
+ }
237
+ loadImageFailed() {
238
+ console.error('fallo al cargar la imagen');
239
+ }
240
+ async simpleCropAndUpload() {
241
+ const imageCropped = await this.imageCropper.crop();
242
+ const imgStorage = await this.multiImagesStorageService.uploadImage(imageCropped?.blob, this.storagePath);
243
+ this.imageUploaded.emit(imgStorage);
244
+ this.closeModal();
245
+ }
246
+ async uploadToStorage(imageMulticrops) {
247
+ // TODO: Nota si algo falla aquí puede causar inconsistencias en el sistema, ver como manejar errores
248
+ const path = this.imageSettings.path;
249
+ // const imageUploaded = await this.multiImagesStorageService.uploadImage(imageMulticrops, path);
250
+ // this.modalRef.close();
251
+ // console.log(imageUploaded);
252
+ // const image = { type: 'cover', ...imageUploaded };
253
+ // TODO: creo que esta parte va en el componente que llama a este componente
254
+ // if (!this.lesson.media) {
255
+ // this.lesson.media = {};
256
+ // this.lesson.media.images = [image];
257
+ // } else {
258
+ // // solo sustituir el cover si ya existe
259
+ // const currentCover = this.lesson.media.images.find((img) => img.type === 'cover');
260
+ // this.multiImagesStorageService.delete_directory(currentCover.path);
261
+ // this.lesson.media.images = this.lesson.media.images.filter((img) => img.type !== 'cover');
262
+ // this.lesson.media.images.push(image);
263
+ // }
264
+ // await this.saveLesson();
265
+ // this.updateCover();
266
+ // this.imageUploaded.emit(image);
267
+ }
268
+ closeModal() {
269
+ this.showModal = false;
270
+ }
271
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: CropperComponent, deps: [{ token: MultiImagesStorageService }], target: i0.ɵɵFactoryTarget.Component }); }
272
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: CropperComponent, isStandalone: true, selector: "app-cropper", inputs: { imageSettings: "imageSettings", ratioType: "ratioType", resolutions: "resolutions" }, outputs: { imageUploaded: "imageUploaded", onImageCropped: "onImageCropped", onFileSelected: "onFileSelected" }, viewQueries: [{ propertyName: "imageCropper", first: true, predicate: ImageCropperComponent, descendants: true }], ngImport: i0, template: "<div> path: {{ storagePath }} </div>\n\n<div class=\"options\">\n <div *ngIf=\"!isUploaded\">\n <input type=\"file\" id=\"file-upload\" class=\"file-input\" (change)=\"fileChangeEvent($event)\" />\n <label for=\"file-upload\" class=\"btn-upload\">Seleccionar archivo</label>\n <em *ngIf=\"!fileMetadata\">Carga una imagen para comenzar</em>\n </div>\n\n <span *ngIf=\"fileMetadata\">\n <span style=\"margin: 1px 20px\"> tipo: {{ fileMetadata.type }} </span>\n\n <span style=\"margin: 1px 20px\"> tama\u00F1o {{ fileMetadata.size }} </span>\n <br />\n <input\n [disabled]=\"imageSettings?.fileName\"\n style=\"margin: 1px 20px; width: 400px\"\n [(ngModel)]=\"renameFile\"\n type=\"text\"\n placeholder=\"Rename File\"\n (ngModelChange)=\"reloadPath()\" />\n <button class=\"btn-crop\" (click)=\"closeModal()\"> Recortar y Subir </button>\n </span>\n</div>\n\n<div class=\"modal\" *ngIf=\"fileMetadata && !isUploaded\" [class.show-modal]=\"showModal\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <h3>Recortar imagen</h3>\n <button class=\"close-button\" (click)=\"closeModal()\">\u00D7</button>\n </div>\n <div class=\"modal-body\">\n <h1>Hlloa</h1>\n\n <image-cropper\n [imageChangedEvent]=\"imageChangedEvent\"\n [maintainAspectRatio]=\"true\"\n [aspectRatio]=\"aspectRatio\"\n format=\"webp\"\n [resizeToWidth]=\"450\"\n (imageCropped)=\"onInnerImageCropped($event)\"\n (loadImageFailed)=\"loadImageFailed()\"\n (imageLoaded)=\"imageLoaded($event)\"\n [autoCrop]=\"false\"></image-cropper>\n\n <div class=\"modal-footer\">\n <button class=\"btn-crop\" (click)=\"simpleCropAndUpload()\">Recortar y Subir</button>\n </div>\n </div>\n </div>\n</div>\n\n<button *ngIf=\"croppedImage && !isUploaded\" [disabled]=\"isLoading\" nbButton status=\"info\"> upload </button>\n", styles: [".options{display:flex}.btn-crop{cursor:pointer;outline:0;display:inline-block;font-weight:400;line-height:1.5;text-align:center;background-color:transparent;border:1px solid transparent;padding:6px 12px;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;color:#0d6efd;border-color:#0d6efd}.btn-crop :hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.file-input{display:none}.btn-upload{cursor:pointer;outline:0;display:inline-block;font-weight:400;line-height:1.5;text-align:center;background-color:transparent;border:1px solid #0d6efd;padding:6px 12px;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;color:#0d6efd}.btn-upload:hover{color:#fff;background-color:#0d6efd}.modal{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background-color:#00000080;z-index:1000}.modal.show-modal{display:flex;align-items:center;justify-content:center}.modal .modal-content{background-color:#fff;border-radius:8px;width:90%;max-width:800px;max-height:90vh;overflow-y:auto;position:relative}.modal .modal-header{padding:1rem;border-bottom:1px solid #dee2e6;display:flex;justify-content:space-between;align-items:center}.modal .modal-header h3{margin:0}.modal .modal-header .close-button{background:none;border:none;font-size:1.5rem;cursor:pointer;padding:0;color:#6c757d}.modal .modal-header .close-button:hover{color:#343a40}.modal .modal-body{padding:1rem}.modal .modal-footer{padding:1rem;border-top:1px solid #dee2e6;display:flex;justify-content:flex-end}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ImageCropperComponent, selector: "image-cropper", inputs: ["imageChangedEvent", "imageURL", "imageBase64", "imageFile", "imageAltText", "options", "cropperFrameAriaLabel", "output", "format", "autoCrop", "cropper", "transform", "maintainAspectRatio", "aspectRatio", "resetCropOnAspectRatioChange", "resizeToWidth", "resizeToHeight", "cropperMinWidth", "cropperMinHeight", "cropperMaxHeight", "cropperMaxWidth", "cropperStaticWidth", "cropperStaticHeight", "canvasRotation", "initialStepSize", "roundCropper", "onlyScaleDown", "imageQuality", "backgroundColor", "containWithinAspectRatio", "hideResizeSquares", "allowMoveImage", "checkImageType", "alignImage", "disabled", "hidden"], outputs: ["imageCropped", "startCropImage", "imageLoaded", "cropperReady", "loadImageFailed", "transformChange", "cropperChange"] }] }); }
273
+ }
274
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: CropperComponent, decorators: [{
275
+ type: Component,
276
+ args: [{ selector: 'app-cropper', standalone: true, imports: [NgIf, FormsModule, ImageCropperComponent], template: "<div> path: {{ storagePath }} </div>\n\n<div class=\"options\">\n <div *ngIf=\"!isUploaded\">\n <input type=\"file\" id=\"file-upload\" class=\"file-input\" (change)=\"fileChangeEvent($event)\" />\n <label for=\"file-upload\" class=\"btn-upload\">Seleccionar archivo</label>\n <em *ngIf=\"!fileMetadata\">Carga una imagen para comenzar</em>\n </div>\n\n <span *ngIf=\"fileMetadata\">\n <span style=\"margin: 1px 20px\"> tipo: {{ fileMetadata.type }} </span>\n\n <span style=\"margin: 1px 20px\"> tama\u00F1o {{ fileMetadata.size }} </span>\n <br />\n <input\n [disabled]=\"imageSettings?.fileName\"\n style=\"margin: 1px 20px; width: 400px\"\n [(ngModel)]=\"renameFile\"\n type=\"text\"\n placeholder=\"Rename File\"\n (ngModelChange)=\"reloadPath()\" />\n <button class=\"btn-crop\" (click)=\"closeModal()\"> Recortar y Subir </button>\n </span>\n</div>\n\n<div class=\"modal\" *ngIf=\"fileMetadata && !isUploaded\" [class.show-modal]=\"showModal\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <h3>Recortar imagen</h3>\n <button class=\"close-button\" (click)=\"closeModal()\">\u00D7</button>\n </div>\n <div class=\"modal-body\">\n <h1>Hlloa</h1>\n\n <image-cropper\n [imageChangedEvent]=\"imageChangedEvent\"\n [maintainAspectRatio]=\"true\"\n [aspectRatio]=\"aspectRatio\"\n format=\"webp\"\n [resizeToWidth]=\"450\"\n (imageCropped)=\"onInnerImageCropped($event)\"\n (loadImageFailed)=\"loadImageFailed()\"\n (imageLoaded)=\"imageLoaded($event)\"\n [autoCrop]=\"false\"></image-cropper>\n\n <div class=\"modal-footer\">\n <button class=\"btn-crop\" (click)=\"simpleCropAndUpload()\">Recortar y Subir</button>\n </div>\n </div>\n </div>\n</div>\n\n<button *ngIf=\"croppedImage && !isUploaded\" [disabled]=\"isLoading\" nbButton status=\"info\"> upload </button>\n", styles: [".options{display:flex}.btn-crop{cursor:pointer;outline:0;display:inline-block;font-weight:400;line-height:1.5;text-align:center;background-color:transparent;border:1px solid transparent;padding:6px 12px;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;color:#0d6efd;border-color:#0d6efd}.btn-crop :hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.file-input{display:none}.btn-upload{cursor:pointer;outline:0;display:inline-block;font-weight:400;line-height:1.5;text-align:center;background-color:transparent;border:1px solid #0d6efd;padding:6px 12px;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;color:#0d6efd}.btn-upload:hover{color:#fff;background-color:#0d6efd}.modal{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background-color:#00000080;z-index:1000}.modal.show-modal{display:flex;align-items:center;justify-content:center}.modal .modal-content{background-color:#fff;border-radius:8px;width:90%;max-width:800px;max-height:90vh;overflow-y:auto;position:relative}.modal .modal-header{padding:1rem;border-bottom:1px solid #dee2e6;display:flex;justify-content:space-between;align-items:center}.modal .modal-header h3{margin:0}.modal .modal-header .close-button{background:none;border:none;font-size:1.5rem;cursor:pointer;padding:0;color:#6c757d}.modal .modal-header .close-button:hover{color:#343a40}.modal .modal-body{padding:1rem}.modal .modal-footer{padding:1rem;border-top:1px solid #dee2e6;display:flex;justify-content:flex-end}\n"] }]
277
+ }], ctorParameters: () => [{ type: MultiImagesStorageService }], propDecorators: { imageSettings: [{
278
+ type: Input
279
+ }], ratioType: [{
280
+ type: Input
281
+ }], resolutions: [{
282
+ type: Input
283
+ }], imageUploaded: [{
284
+ type: Output
285
+ }], onImageCropped: [{
286
+ type: Output
287
+ }], onFileSelected: [{
288
+ type: Output
289
+ }], imageCropper: [{
290
+ type: ViewChild,
291
+ args: [ImageCropperComponent]
292
+ }] } });
293
+
294
+ class CropperComponentModal {
295
+ constructor(multiImagesStorageService, changeDetectorRef) {
296
+ this.multiImagesStorageService = multiImagesStorageService;
297
+ this.changeDetectorRef = changeDetectorRef;
298
+ // overrides name, path and resizeToWidth
299
+ this.imgStorageSettings = DEFAULT_SETTINGS;
300
+ this.buttonLabel = 'Seleccionar archivo';
301
+ this.currentStorage = {};
302
+ this.imageUploaded = new EventEmitter();
303
+ this.onImageCropped = new EventEmitter();
304
+ this.onFileSelected = new EventEmitter();
305
+ this.aspectRatioOptions = AspectRatioOptions;
306
+ this.fileMetadata = null;
307
+ this.displayDialog = false;
308
+ this.aspectRatioValue = 1;
309
+ this.croppedImage = '';
310
+ this.renameFile = '';
311
+ this.storagePath = '';
312
+ this.resizeToWidth = 450;
313
+ this.ratioSelected = null;
314
+ // Generate random ID for file input
315
+ this.fileInputId = `file-upload-${Math.random().toString(36).substring(2, 11)}`;
316
+ }
317
+ ngOnInit() {
318
+ if (!this.imgStorageSettings.path) {
319
+ console.warn('⚠️ Remember to set imgStorageSettings, path and fileName are required , path example: /collection/id/subcollection ');
320
+ }
321
+ this.reloadPath();
322
+ }
323
+ reloadPath() {
324
+ const randomCharacters = Math.random().toString(36).substring(2, 15);
325
+ this.storagePath = `${this.imgStorageSettings.path}/${this.imgStorageSettings.fileName}-${randomCharacters}.webp`;
326
+ }
327
+ setSettingsForComponent() {
328
+ console.log('setSettingsForComponent', this.imgStorageSettings);
329
+ // TODO: remove all the imageSettings and keep only imgStorageSettings
330
+ this.aspectRatioValue = this.imgStorageSettings?.cropSettings?.aspectRatio
331
+ ? AspectRatio[this.imgStorageSettings?.cropSettings?.aspectRatio]
332
+ : AspectRatio[AspectType.Square];
333
+ if (this.imgStorageSettings?.path) {
334
+ this.storagePath = `${this.imgStorageSettings.path}/${this.imgStorageSettings.fileName}.webp`;
335
+ }
336
+ else if (this.imgStorageSettings.path) {
337
+ this.storagePath = `${this.imgStorageSettings.path}/${this.imgStorageSettings.fileName}.webp`;
338
+ }
339
+ if (this.imgStorageSettings?.cropSettings?.resizeToWidth) {
340
+ this.resizeToWidth = this.imgStorageSettings.cropSettings.resizeToWidth;
341
+ }
342
+ else if (this.imgStorageSettings.cropSettings.resizeToWidth) {
343
+ this.resizeToWidth = this.imgStorageSettings.cropSettings.resizeToWidth;
344
+ }
345
+ if (this.imgStorageSettings?.fileName) {
346
+ this.renameFile = this.imgStorageSettings.fileName;
347
+ }
348
+ else if (this.imgStorageSettings.fileName) {
349
+ this.renameFile = this.imgStorageSettings.fileName;
350
+ }
351
+ }
352
+ async fileChangeEvent(event) {
353
+ this.setSettingsForComponent();
354
+ console.log(this.fileInputId);
355
+ this.imageChangedEvent = event;
356
+ const file = event?.target?.files[0];
357
+ if (file) {
358
+ this.fileMetadata = file;
359
+ this.onFileSelected.emit(file);
360
+ this.renameFile = this.fileMetadata?.name
361
+ ?.split('.')[0]
362
+ .replace(/[^a-zA-Z0-9]/g, '')
363
+ .slice(0, 80);
364
+ console.log(this.renameFile);
365
+ if (!this.imgStorageSettings.fileName) {
366
+ this.reloadPath();
367
+ }
368
+ this.displayDialog = true;
369
+ this.changeDetectorRef.detectChanges();
370
+ }
371
+ }
372
+ onInnerImageCropped(event) {
373
+ this.croppedImage = event.base64;
374
+ }
375
+ imageLoaded(image) {
376
+ this.changeDetectorRef.detectChanges();
377
+ }
378
+ cropperReady() {
379
+ this.changeDetectorRef.detectChanges();
380
+ }
381
+ loadImageFailed() {
382
+ console.error('fallo al cargar la imagen');
383
+ }
384
+ async simpleCropAndUpload() {
385
+ console.log(this.fileInputId);
386
+ console.log('simpleCropAndUpload');
387
+ const imageCropped = await this.imageCropper.crop();
388
+ const imgStorage = await this.multiImagesStorageService.uploadImage(imageCropped?.blob, this.storagePath);
389
+ if (this.currentStorage?.path) {
390
+ console.warn('deleting current Image', this.currentStorage?.path);
391
+ this.multiImagesStorageService.deleteImage(this.currentStorage.path);
392
+ }
393
+ console.log('imgStorage', imgStorage);
394
+ this.imageUploaded.emit(imgStorage);
395
+ this.displayDialog = false;
396
+ this.changeDetectorRef.detectChanges();
397
+ }
398
+ changeRatio(event) {
399
+ console.log('changeRatio', event);
400
+ // this.imgStorageSettings.cropSettings.aspectRatio = event.valueRatio;
401
+ this.aspectRatioValue = event.valueRatio;
402
+ this.changeDetectorRef.detectChanges();
403
+ }
404
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: CropperComponentModal, deps: [{ token: MultiImagesStorageService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
405
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: CropperComponentModal, isStandalone: true, selector: "dc-cropper-modal", inputs: { imgStorageSettings: "imgStorageSettings", buttonLabel: "buttonLabel", currentStorage: "currentStorage" }, outputs: { imageUploaded: "imageUploaded", onImageCropped: "onImageCropped", onFileSelected: "onFileSelected" }, viewQueries: [{ propertyName: "imageCropper", first: true, predicate: ImageCropperComponent, descendants: true }], ngImport: i0, template: "<div class=\"upload-section\">\n <input type=\"file\" [id]=\"fileInputId\" class=\"file-input\" (change)=\"fileChangeEvent($event)\" />\n <label pButton [for]=\"fileInputId\" [pTooltip]=\"storagePath\" class=\"upload-button\">\n {{ buttonLabel }}\n </label>\n</div>\n@if(displayDialog) {\n<!-- Cropper Dialog -->\n\n<p-dialog header=\"Recortar imagen\" [(visible)]=\"displayDialog\" [modal]=\"true\" [draggable]=\"false\" [resizable]=\"false\" styleClass=\"cropper-dialog\">\n <!-- Image Settings Section -->\n <div class=\"settings-section\">\n @if(!imgStorageSettings.path) {\n <p-message severity=\"warn\">Developer Note: make sure you have a path to save the image pass object imgStorageSettings</p-message>\n } @if(currentStorage.url) {\n\n <p-message severity=\"warn\" variant=\"outlined\">\n <div>\n <span class=\"setting-label\">Image will be replaced:</span>\n <img width=\"100\" height=\"Auto\" [src]=\"currentStorage?.url\" />\n </div>\n </p-message>\n\n }\n\n <p-message>\n <b>Estas opciones estan preconfiguradas y no se pueden cambiar</b>\n\n <ul>\n <li> <b>Path to save:</b> {{ storagePath }}< </li>\n <li>\n <b>Resoluciones:</b>\n <span>{{ imgStorageSettings?.cropSettings?.resolutions }}</span>\n </li>\n </ul>\n </p-message>\n\n <div class=\"setting-item\">\n <span class=\"setting-label\">Aspecto:</span>\n <p class=\"setting-value\">{{ imgStorageSettings?.cropSettings?.aspectRatio }}</p>\n </div>\n\n <p-select\n [options]=\"aspectRatioOptions\"\n [ngModel]=\"ratioSelected\"\n (ngModelChange)=\"changeRatio($event)\"\n optionLabel=\"description\"\n placeholder=\"Select a ratio\" />\n\n <!-- File Metadata Section -->\n <div class=\"metadata-section\" *ngIf=\"fileMetadata\">\n <span class=\"metadata-item\">tipo: {{ fileMetadata.type }}</span>\n <span class=\"metadata-item\">tama\u00F1o: {{ fileMetadata.size }}</span>\n </div>\n\n <!-- Rename Input -->\n <input\n pInputText\n [disabled]=\"imgStorageSettings?.fileName\"\n [(ngModel)]=\"renameFile\"\n type=\"text\"\n placeholder=\"Rename File\"\n (ngModelChange)=\"reloadPath()\"\n class=\"rename-input\" />\n </div>\n\n <!-- Image Cropper -->\n\n <div class=\"cropper-container-father\">\n <image-cropper\n [imageChangedEvent]=\"imageChangedEvent\"\n [maintainAspectRatio]=\"true\"\n [aspectRatio]=\"aspectRatioValue\"\n format=\"webp\"\n [resizeToWidth]=\"resizeToWidth\"\n (imageCropped)=\"onInnerImageCropped($event)\"\n (loadImageFailed)=\"loadImageFailed()\"\n (cropperReady)=\"cropperReady()\"\n [autoCrop]=\"false\">\n </image-cropper>\n </div>\n <!-- Dialog Footer -->\n <ng-template pTemplate=\"footer\">\n <div class=\"dialog-footer\">\n <button pButton class=\"p-button-primary\" (click)=\"simpleCropAndUpload()\"> Recortar y Subir </button>\n </div>\n </ng-template>\n</p-dialog>\n}\n", styles: [":host{display:block}:host ::ng-deep .ngx-ic-overlay{width:100%!important}.upload-section{margin-bottom:1rem}.upload-section .file-input{display:none}.upload-section .upload-button{cursor:pointer}::ng-deep .cropper-dialog{max-width:90vw;width:800px}::ng-deep .cropper-dialog .p-dialog-content{padding:1.5rem}.settings-section{margin-bottom:1.5rem}.settings-section .settings-header{color:var(--text-color-secondary);margin-bottom:1rem}.settings-section .settings-grid{display:grid;gap:1rem;margin-bottom:1.5rem}.settings-section .settings-grid .setting-item .setting-label{font-weight:600;color:var(--text-color);display:block;margin-bottom:.5rem}.settings-section .settings-grid .setting-item .setting-value{color:var(--text-color-secondary);margin:0}.metadata-section{display:flex;gap:1.5rem;margin-bottom:1rem}.metadata-section .metadata-item{color:var(--text-color-secondary)}.rename-section{margin-bottom:5px}.rename-section .rename-input{width:100%;padding:.5rem;border:1px solid var(--surface-border);border-radius:4px}.rename-section .rename-input:disabled{background-color:var(--surface-200);cursor:not-allowed}.cropper-container-father{display:flex;justify-content:center;align-items:center;height:65vh}.dialog-footer{display:flex;justify-content:flex-end;gap:1rem}.btn-crop{cursor:pointer;outline:0;display:inline-block;font-weight:400;line-height:1.5;text-align:center;background-color:transparent;border:1px solid transparent;padding:6px 12px;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;color:#0d6efd;border-color:#0d6efd}.btn-crop :hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.file-input{display:none}.btn-upload{cursor:pointer;outline:0;display:inline-block;font-weight:400;line-height:1.5;text-align:center;background-color:transparent;border:1px solid #0d6efd;padding:6px 12px;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;color:#0d6efd}.btn-upload:hover{color:#fff;background-color:#0d6efd}.modal{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background-color:#00000080;z-index:1000}.modal.show-modal{display:flex;align-items:center;justify-content:center}.modal .modal-content{background-color:#fff;border-radius:8px;width:90%;max-width:800px;max-height:90vh;overflow-y:auto;position:relative}.modal .modal-header{padding:1rem;border-bottom:1px solid #dee2e6;display:flex;justify-content:space-between;align-items:center}.modal .modal-header h3{margin:0}.modal .modal-header .close-button{background:none;border:none;font-size:1.5rem;cursor:pointer;padding:0;color:#6c757d}.modal .modal-header .close-button:hover{color:#343a40}.modal .modal-body{padding:1rem;height:100vh;display:flex;flex-direction:column}.modal .modal-footer{padding:1rem;border-top:1px solid #dee2e6;display:flex;justify-content:flex-end}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ImageCropperComponent, selector: "image-cropper", inputs: ["imageChangedEvent", "imageURL", "imageBase64", "imageFile", "imageAltText", "options", "cropperFrameAriaLabel", "output", "format", "autoCrop", "cropper", "transform", "maintainAspectRatio", "aspectRatio", "resetCropOnAspectRatioChange", "resizeToWidth", "resizeToHeight", "cropperMinWidth", "cropperMinHeight", "cropperMaxHeight", "cropperMaxWidth", "cropperStaticWidth", "cropperStaticHeight", "canvasRotation", "initialStepSize", "roundCropper", "onlyScaleDown", "imageQuality", "backgroundColor", "containWithinAspectRatio", "hideResizeSquares", "allowMoveImage", "checkImageType", "alignImage", "disabled", "hidden"], outputs: ["imageCropped", "startCropImage", "imageLoaded", "cropperReady", "loadImageFailed", "transformChange", "cropperChange"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i3.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "directive", type: i4.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i5.Dialog, selector: "p-dialog", inputs: ["header", "draggable", "resizable", "positionLeft", "positionTop", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "responsive", "appendTo", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "breakpoint", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: i7.Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant"], outputs: ["onClose"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i8.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "variant", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "size", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "fluid", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i9.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }] }); }
406
+ }
407
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: CropperComponentModal, decorators: [{
408
+ type: Component,
409
+ args: [{ selector: 'dc-cropper-modal', standalone: true, imports: [NgIf, FormsModule, ImageCropperComponent, ButtonModule, DialogModule, TooltipModule, MessageModule, SelectModule, InputTextModule], template: "<div class=\"upload-section\">\n <input type=\"file\" [id]=\"fileInputId\" class=\"file-input\" (change)=\"fileChangeEvent($event)\" />\n <label pButton [for]=\"fileInputId\" [pTooltip]=\"storagePath\" class=\"upload-button\">\n {{ buttonLabel }}\n </label>\n</div>\n@if(displayDialog) {\n<!-- Cropper Dialog -->\n\n<p-dialog header=\"Recortar imagen\" [(visible)]=\"displayDialog\" [modal]=\"true\" [draggable]=\"false\" [resizable]=\"false\" styleClass=\"cropper-dialog\">\n <!-- Image Settings Section -->\n <div class=\"settings-section\">\n @if(!imgStorageSettings.path) {\n <p-message severity=\"warn\">Developer Note: make sure you have a path to save the image pass object imgStorageSettings</p-message>\n } @if(currentStorage.url) {\n\n <p-message severity=\"warn\" variant=\"outlined\">\n <div>\n <span class=\"setting-label\">Image will be replaced:</span>\n <img width=\"100\" height=\"Auto\" [src]=\"currentStorage?.url\" />\n </div>\n </p-message>\n\n }\n\n <p-message>\n <b>Estas opciones estan preconfiguradas y no se pueden cambiar</b>\n\n <ul>\n <li> <b>Path to save:</b> {{ storagePath }}< </li>\n <li>\n <b>Resoluciones:</b>\n <span>{{ imgStorageSettings?.cropSettings?.resolutions }}</span>\n </li>\n </ul>\n </p-message>\n\n <div class=\"setting-item\">\n <span class=\"setting-label\">Aspecto:</span>\n <p class=\"setting-value\">{{ imgStorageSettings?.cropSettings?.aspectRatio }}</p>\n </div>\n\n <p-select\n [options]=\"aspectRatioOptions\"\n [ngModel]=\"ratioSelected\"\n (ngModelChange)=\"changeRatio($event)\"\n optionLabel=\"description\"\n placeholder=\"Select a ratio\" />\n\n <!-- File Metadata Section -->\n <div class=\"metadata-section\" *ngIf=\"fileMetadata\">\n <span class=\"metadata-item\">tipo: {{ fileMetadata.type }}</span>\n <span class=\"metadata-item\">tama\u00F1o: {{ fileMetadata.size }}</span>\n </div>\n\n <!-- Rename Input -->\n <input\n pInputText\n [disabled]=\"imgStorageSettings?.fileName\"\n [(ngModel)]=\"renameFile\"\n type=\"text\"\n placeholder=\"Rename File\"\n (ngModelChange)=\"reloadPath()\"\n class=\"rename-input\" />\n </div>\n\n <!-- Image Cropper -->\n\n <div class=\"cropper-container-father\">\n <image-cropper\n [imageChangedEvent]=\"imageChangedEvent\"\n [maintainAspectRatio]=\"true\"\n [aspectRatio]=\"aspectRatioValue\"\n format=\"webp\"\n [resizeToWidth]=\"resizeToWidth\"\n (imageCropped)=\"onInnerImageCropped($event)\"\n (loadImageFailed)=\"loadImageFailed()\"\n (cropperReady)=\"cropperReady()\"\n [autoCrop]=\"false\">\n </image-cropper>\n </div>\n <!-- Dialog Footer -->\n <ng-template pTemplate=\"footer\">\n <div class=\"dialog-footer\">\n <button pButton class=\"p-button-primary\" (click)=\"simpleCropAndUpload()\"> Recortar y Subir </button>\n </div>\n </ng-template>\n</p-dialog>\n}\n", styles: [":host{display:block}:host ::ng-deep .ngx-ic-overlay{width:100%!important}.upload-section{margin-bottom:1rem}.upload-section .file-input{display:none}.upload-section .upload-button{cursor:pointer}::ng-deep .cropper-dialog{max-width:90vw;width:800px}::ng-deep .cropper-dialog .p-dialog-content{padding:1.5rem}.settings-section{margin-bottom:1.5rem}.settings-section .settings-header{color:var(--text-color-secondary);margin-bottom:1rem}.settings-section .settings-grid{display:grid;gap:1rem;margin-bottom:1.5rem}.settings-section .settings-grid .setting-item .setting-label{font-weight:600;color:var(--text-color);display:block;margin-bottom:.5rem}.settings-section .settings-grid .setting-item .setting-value{color:var(--text-color-secondary);margin:0}.metadata-section{display:flex;gap:1.5rem;margin-bottom:1rem}.metadata-section .metadata-item{color:var(--text-color-secondary)}.rename-section{margin-bottom:5px}.rename-section .rename-input{width:100%;padding:.5rem;border:1px solid var(--surface-border);border-radius:4px}.rename-section .rename-input:disabled{background-color:var(--surface-200);cursor:not-allowed}.cropper-container-father{display:flex;justify-content:center;align-items:center;height:65vh}.dialog-footer{display:flex;justify-content:flex-end;gap:1rem}.btn-crop{cursor:pointer;outline:0;display:inline-block;font-weight:400;line-height:1.5;text-align:center;background-color:transparent;border:1px solid transparent;padding:6px 12px;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;color:#0d6efd;border-color:#0d6efd}.btn-crop :hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.file-input{display:none}.btn-upload{cursor:pointer;outline:0;display:inline-block;font-weight:400;line-height:1.5;text-align:center;background-color:transparent;border:1px solid #0d6efd;padding:6px 12px;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;color:#0d6efd}.btn-upload:hover{color:#fff;background-color:#0d6efd}.modal{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background-color:#00000080;z-index:1000}.modal.show-modal{display:flex;align-items:center;justify-content:center}.modal .modal-content{background-color:#fff;border-radius:8px;width:90%;max-width:800px;max-height:90vh;overflow-y:auto;position:relative}.modal .modal-header{padding:1rem;border-bottom:1px solid #dee2e6;display:flex;justify-content:space-between;align-items:center}.modal .modal-header h3{margin:0}.modal .modal-header .close-button{background:none;border:none;font-size:1.5rem;cursor:pointer;padding:0;color:#6c757d}.modal .modal-header .close-button:hover{color:#343a40}.modal .modal-body{padding:1rem;height:100vh;display:flex;flex-direction:column}.modal .modal-footer{padding:1rem;border-top:1px solid #dee2e6;display:flex;justify-content:flex-end}\n"] }]
410
+ }], ctorParameters: () => [{ type: MultiImagesStorageService }, { type: i0.ChangeDetectorRef }], propDecorators: { imgStorageSettings: [{
411
+ type: Input
412
+ }], buttonLabel: [{
413
+ type: Input
414
+ }], currentStorage: [{
415
+ type: Input
416
+ }], imageUploaded: [{
417
+ type: Output
418
+ }], onImageCropped: [{
419
+ type: Output
420
+ }], onFileSelected: [{
421
+ type: Output
422
+ }], imageCropper: [{
423
+ type: ViewChild,
424
+ args: [ImageCropperComponent]
425
+ }] } });
426
+
427
+ class DCFilesCacheService {
428
+ constructor(storage) {
429
+ this.storage = storage;
430
+ this.files = {};
431
+ }
432
+ async getURLSrcFile(path) {
433
+ if (path in this.files) {
434
+ return this.files[path];
435
+ }
436
+ else {
437
+ const url = await getDownloadURL(ref$1(this.storage, path));
438
+ const localUrl = await this.donwloadFileAndGetLocalURL(url);
439
+ this.files[path] = localUrl;
440
+ return localUrl;
441
+ }
442
+ }
443
+ getBlob(url) {
444
+ return new Promise((resolve, reject) => {
445
+ const xhr = new XMLHttpRequest();
446
+ xhr.responseType = 'blob';
447
+ xhr.overrideMimeType('audio/mp3');
448
+ xhr.onload = (event) => {
449
+ var blob = xhr.response;
450
+ resolve(blob);
451
+ };
452
+ xhr.onerror = (event) => {
453
+ reject(event);
454
+ };
455
+ xhr.open('GET', url);
456
+ xhr.send();
457
+ });
458
+ }
459
+ async donwloadFileAndGetLocalURL(url) {
460
+ const blob = await this.getBlob(url);
461
+ return URL.createObjectURL(blob);
462
+ }
463
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCFilesCacheService, deps: [{ token: i1$1.Storage }], target: i0.ɵɵFactoryTarget.Injectable }); }
464
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCFilesCacheService, providedIn: 'root' }); }
465
+ }
466
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCFilesCacheService, decorators: [{
467
+ type: Injectable,
468
+ args: [{
469
+ providedIn: 'root',
470
+ }]
471
+ }], ctorParameters: () => [{ type: i1$1.Storage }] });
472
+
473
+ /*
474
+ * Public API Surface of storage-uploader
475
+ */
476
+
477
+ /**
478
+ * Generated bundle index. Do not edit.
479
+ */
480
+
481
+ export { AspectRatio, AspectRatio2, AspectRatioOptions, AspectType, CropperComponent, CropperComponentModal, DCFilesCacheService, DEFAULT_SETTINGS, MultiImagesStorageService, ResolutionType };
482
+ //# sourceMappingURL=dataclouder-ngx-cloud-storage.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataclouder-ngx-cloud-storage.mjs","sources":["../../../../projects/dataclouder/ngx-cloud-storage/src/lib/classes/cropper.classes.ts","../../../../projects/dataclouder/ngx-cloud-storage/src/lib/services/multi-images-storage.service.ts","../../../../projects/dataclouder/ngx-cloud-storage/src/lib/components/cropper/cropper.component.ts","../../../../projects/dataclouder/ngx-cloud-storage/src/lib/components/cropper/cropper.component.html","../../../../projects/dataclouder/ngx-cloud-storage/src/lib/components/cropper-modal/cropper-modal.component.ts","../../../../projects/dataclouder/ngx-cloud-storage/src/lib/components/cropper-modal/cropper-modal.component.html","../../../../projects/dataclouder/ngx-cloud-storage/src/lib/services/dc-files-cache.service.ts","../../../../projects/dataclouder/ngx-cloud-storage/src/public-api.ts","../../../../projects/dataclouder/ngx-cloud-storage/src/dataclouder-ngx-cloud-storage.ts"],"sourcesContent":["export interface StorageImageSettings {\n path?: string;\n fileName?: string;\n cropSettings?: ImageCropSettings;\n}\n\nexport interface ImageCropSettings {\n resizeToWidth?: number;\n resolutions: Array<number>; // 400, 800, 1200, 1600\n aspectRatio: AspectType;\n}\n\nexport interface CropImageSettings {\n // Resolutions should be here.\n path: string;\n fileName?: string;\n resizeToWidth?: number;\n}\n\nexport interface ImageMultipleCrops {\n file: File;\n defaultImageBlob?: Blob;\n imagesBlobs: { [resolution: number]: Blob };\n settings: { renameFile: string; width?: number };\n}\n\nexport const AspectRatioOptions: AspectRatioOption[] = [\n { value: '1:1', label: 'square', description: 'Square (1:1)', valueRatio: 1 / 1 },\n { value: '1:2', label: 'vertical_1_2', description: 'Vertical (1:2)', valueRatio: 1 / 2 },\n { value: '2:3', label: 'vertical_2_3', description: 'Vertical (2:3)', valueRatio: 2 / 3 },\n { value: '3:4', label: 'vertical_3_4', description: 'Vertical (3:4)', valueRatio: 3 / 4 },\n { value: '4:5', label: 'vertical_4_5', description: 'Vertical (4:5)', valueRatio: 4 / 5 },\n { value: '9:16', label: 'vertical_9_16', description: 'Vertical (9:16)', valueRatio: 9 / 16 },\n { value: '2:1', label: 'horizontal_2_1', description: 'Horizontal (2:1)', valueRatio: 2 / 1 },\n { value: '3:2', label: 'horizontal_3_2', description: 'Horizontal (3:2)', valueRatio: 3 / 2 },\n { value: '4:3', label: 'horizontal_4_3', description: 'Horizontal (4:3)', valueRatio: 4 / 3 },\n { value: '5:4', label: 'horizontal_5_4', description: 'Horizontal (5:4)', valueRatio: 5 / 4 },\n { value: '16:9', label: 'horizontal_16_9', description: 'Horizontal (16:9)', valueRatio: 16 / 9 },\n];\n\nexport interface AspectRatioOption {\n label: string;\n description: string;\n value: string;\n valueRatio: number;\n}\n\nexport enum AspectType {\n Square = 'square',\n Rectangle = 'rectangle',\n Banner = 'banner',\n RectangleLarge = 'rectangleLarge',\n Vertical_9_16 = 'vertical_9_16',\n Vertical_3_5 = 'vertical_3_5',\n Vertical_2_3 = 'vertical_2_3',\n}\n\n// Creo que seria bueno refactorizar para solo enviar aspect ratio asi\nexport enum AspectRatio2 {\n // Vertical\n '9:16' = 16 / 9,\n '3:5' = 3 / 5,\n '5:8' = 5 / 8,\n '2:3' = 2 / 3,\n '1:1' = 1 / 1,\n}\n\nexport enum ResolutionType {\n Small = 200,\n Medium = 400,\n MediumLarge = 800,\n Large = 1200,\n VeryLarge = 1600,\n}\n\nexport const AspectRatio = {\n [AspectType.Square]: 1 / 1,\n [AspectType.Rectangle]: 16 / 9,\n [AspectType.Vertical_9_16]: 9 / 16,\n [AspectType.RectangleLarge]: 16 / 8,\n [AspectType.Banner]: 16 / 7,\n [AspectType.Vertical_3_5]: 3 / 5,\n [AspectType.Vertical_2_3]: 2 / 3,\n};\n\nexport interface ImgStorageData extends CloudStorageData {\n name?: string;\n bucket?: string;\n url?: string;\n path?: string; // path where the file is in the storage\n fullPath?: string; // path + name\n resolution?: string;\n resolutions?: any;\n}\n\nexport interface CloudStorageData {\n bucket?: string;\n url?: string;\n path?: string; // path where the file is in the storage\n}\n\nexport const DEFAULT_SETTINGS: StorageImageSettings = {\n path: '/default-collection/id/please-change-this-subcollection',\n fileName: 'image',\n cropSettings: {\n aspectRatio: AspectType.Square,\n resolutions: [ResolutionType.Small, ResolutionType.MediumLarge],\n resizeToWidth: 450,\n },\n};\n","import { Injectable } from '@angular/core';\nimport { AngularFireStorage, AngularFireUploadTask } from '@angular/fire/compat/storage';\nimport { getDownloadURL, getStorage, deleteObject, listAll } from '@angular/fire/storage';\n\nimport { ref } from 'firebase/storage';\nimport { lastValueFrom } from 'rxjs';\nimport { ImageMultipleCrops, ImgStorageData } from '../classes/cropper.classes';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class MultiImagesStorageService {\n constructor(private storage: AngularFireStorage) {}\n\n public async uploadImage(image: Blob, path: string): Promise<ImgStorageData> {\n try {\n const refStorage = this.storage.ref(path);\n const task = await refStorage.put(image);\n const { fullPath, bucket } = task.metadata;\n const url = await lastValueFrom(refStorage.getDownloadURL());\n const imageStorage = { url, path: fullPath, bucket };\n return imageStorage;\n } catch (error) {\n console.error('uploading image error: ', error);\n return null;\n }\n }\n\n public uploadImage2(imageMulticrops: ImageMultipleCrops, path: string): Promise<ImgStorageData> {\n // path is usually [products/id] will create a new folder with the name and upload the images\n\n const blobs = imageMulticrops.imagesBlobs;\n\n const tasks: Array<Promise<any>> = [];\n let fileName = imageMulticrops.settings?.renameFile ?? imageMulticrops.file.name;\n fileName = fileName.split('.')[0];\n\n // load the default image jpeg\n path = `${path}/${fileName}`;\n const defaultImagePath = `${path}/${fileName}-400W.jpeg`;\n const refStorage = this.storage.ref(defaultImagePath);\n const task = refStorage.put(imageMulticrops.defaultImageBlob);\n\n tasks.push(this.uploadAndGetUrl(task, 'default'));\n\n // Upload webp images\n for (const resolutionStr of Object.keys(blobs)) {\n const fullName = `${fileName}-${resolutionStr}W.webp`;\n const filePath = `${path}/${fullName}`;\n\n const refStorage = this.storage.ref(filePath);\n\n const resolution = Number(resolutionStr);\n const task = refStorage.put(blobs[resolution]);\n tasks.push(this.uploadAndGetUrl(task, resolution + 'W'));\n }\n\n return Promise.all(tasks).then(async (upladedImages) => {\n let defaultImage: any = {};\n let resolutionsUrls: any = {};\n\n for (const image of upladedImages) {\n if (image.resolution === 'default') {\n defaultImage = image;\n } else {\n resolutionsUrls[image.resolution] = image.url;\n }\n }\n defaultImage['resolutions'] = resolutionsUrls;\n defaultImage['path'] = path;\n\n return defaultImage;\n });\n }\n\n public async delete_directory(imagePath: string): Promise<void> {\n // WARNING!! user very carefully could delete whatever folder\n const storage = getStorage();\n const directoryRef = ref(storage, imagePath);\n\n listAll(directoryRef)\n .then((res) => {\n res.items.forEach((itemRef) => {\n console.log(itemRef);\n deleteObject(itemRef);\n });\n })\n .catch((error) => {\n console.error('error al eliminar imagenes de cloud storage', error);\n });\n }\n\n public async deleteImage(imagePath: string): Promise<void> {\n const storageRef = this.storage.ref(imagePath);\n storageRef.delete().subscribe((res) => {\n console.log('image deleted', res);\n });\n }\n\n private async uploadAndGetUrl(task: AngularFireUploadTask, resolution: string): Promise<ImgStorageData> {\n const snap = await task;\n const { fullPath, bucket, name } = snap.metadata;\n const storage = getStorage();\n const storageRef = ref(storage, fullPath);\n const url = await getDownloadURL(storageRef);\n const meta: ImgStorageData = {\n url,\n fullPath,\n bucket,\n name,\n resolution,\n path: '',\n resolutions: {},\n };\n\n return meta;\n }\n}\n","import { Component, Input, OnInit, Output, EventEmitter, ViewChild } from '@angular/core';\nimport { ImageCroppedEvent, ImageCropperComponent, LoadedImage, base64ToFile } from 'ngx-image-cropper';\nimport { Observable } from 'rxjs';\nimport { AspectRatio, AspectType, ImageMultipleCrops, CropImageSettings, ResolutionType } from '../../classes/cropper.classes';\nimport { NgIf } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { MultiImagesStorageService } from '../../services/multi-images-storage.service';\n\n@Component({\n selector: 'app-cropper',\n templateUrl: './cropper.component.html',\n styleUrls: ['./cropper.component.scss'],\n standalone: true,\n imports: [NgIf, FormsModule, ImageCropperComponent],\n})\nexport class CropperComponent implements OnInit {\n // overrides name, path and resizeToWidth\n @Input() imageSettings: CropImageSettings = {} as any;\n\n @Input() ratioType: AspectType | string = AspectType.Square;\n\n @Input() resolutions: Array<number> = [ResolutionType.MediumLarge];\n\n @Output() imageUploaded = new EventEmitter<any>();\n\n @Output() onImageCropped = new EventEmitter<ImageMultipleCrops>();\n\n @Output() onFileSelected = new EventEmitter<any>();\n\n @ViewChild(ImageCropperComponent) imageCropper!: ImageCropperComponent;\n\n public fileMetadata: File | null = null;\n public imageChangedEvent!: Event;\n\n public aspectRatio: number = 1;\n\n public croppedImage: any = '';\n\n public isLoading = false;\n public isUploaded = false;\n public renameFile: any = '';\n public storagePath: string = '';\n public showModal = false;\n\n constructor(private multiImagesStorageService: MultiImagesStorageService) {}\n\n ngOnInit(): void {\n this.aspectRatio = AspectRatio[this.ratioType];\n if (this.imageSettings.path) {\n this.storagePath = `${this.imageSettings.path}/${this.imageSettings.fileName}.webp`;\n }\n }\n\n public reloadPath(): void {\n this.storagePath = `${this.imageSettings.path}/${this.renameFile}.webp`;\n }\n\n async fileChangeEvent(event: any) {\n this.imageChangedEvent = event;\n const file = event?.target?.files[0];\n if (file) {\n this.fileMetadata = file;\n this.onFileSelected.emit(file);\n this.showModal = true; // Show modal when file is selected\n this.renameFile = this.fileMetadata?.name?.split('.')[0];\n console.log(this.renameFile);\n\n if (!this.imageSettings.fileName) {\n this.reloadPath();\n }\n }\n }\n\n onInnerImageCropped(event: ImageCroppedEvent) {\n this.croppedImage = event.base64;\n }\n\n imageLoaded(image: LoadedImage) {\n // show cropper\n }\n\n loadImageFailed() {\n console.error('fallo al cargar la imagen');\n }\n\n public downloadURL!: Observable<string>;\n\n public async simpleCropAndUpload() {\n const imageCropped: any = await this.imageCropper.crop();\n const imgStorage = await this.multiImagesStorageService.uploadImage(imageCropped?.blob, this.storagePath);\n this.imageUploaded.emit(imgStorage);\n this.closeModal();\n }\n\n public async uploadToStorage(imageMulticrops: ImageMultipleCrops): Promise<void> {\n // TODO: Nota si algo falla aquí puede causar inconsistencias en el sistema, ver como manejar errores\n const path = this.imageSettings.path;\n // const imageUploaded = await this.multiImagesStorageService.uploadImage(imageMulticrops, path);\n // this.modalRef.close();\n // console.log(imageUploaded);\n // const image = { type: 'cover', ...imageUploaded };\n\n // TODO: creo que esta parte va en el componente que llama a este componente\n // if (!this.lesson.media) {\n // this.lesson.media = {};\n // this.lesson.media.images = [image];\n // } else {\n // // solo sustituir el cover si ya existe\n // const currentCover = this.lesson.media.images.find((img) => img.type === 'cover');\n // this.multiImagesStorageService.delete_directory(currentCover.path);\n\n // this.lesson.media.images = this.lesson.media.images.filter((img) => img.type !== 'cover');\n\n // this.lesson.media.images.push(image);\n // }\n\n // await this.saveLesson();\n // this.updateCover();\n // this.imageUploaded.emit(image);\n }\n\n closeModal(): void {\n this.showModal = false;\n }\n}\n","<div> path: {{ storagePath }} </div>\n\n<div class=\"options\">\n <div *ngIf=\"!isUploaded\">\n <input type=\"file\" id=\"file-upload\" class=\"file-input\" (change)=\"fileChangeEvent($event)\" />\n <label for=\"file-upload\" class=\"btn-upload\">Seleccionar archivo</label>\n <em *ngIf=\"!fileMetadata\">Carga una imagen para comenzar</em>\n </div>\n\n <span *ngIf=\"fileMetadata\">\n <span style=\"margin: 1px 20px\"> tipo: {{ fileMetadata.type }} </span>\n\n <span style=\"margin: 1px 20px\"> tamaño {{ fileMetadata.size }} </span>\n <br />\n <input\n [disabled]=\"imageSettings?.fileName\"\n style=\"margin: 1px 20px; width: 400px\"\n [(ngModel)]=\"renameFile\"\n type=\"text\"\n placeholder=\"Rename File\"\n (ngModelChange)=\"reloadPath()\" />\n <button class=\"btn-crop\" (click)=\"closeModal()\"> Recortar y Subir </button>\n </span>\n</div>\n\n<div class=\"modal\" *ngIf=\"fileMetadata && !isUploaded\" [class.show-modal]=\"showModal\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <h3>Recortar imagen</h3>\n <button class=\"close-button\" (click)=\"closeModal()\">×</button>\n </div>\n <div class=\"modal-body\">\n <h1>Hlloa</h1>\n\n <image-cropper\n [imageChangedEvent]=\"imageChangedEvent\"\n [maintainAspectRatio]=\"true\"\n [aspectRatio]=\"aspectRatio\"\n format=\"webp\"\n [resizeToWidth]=\"450\"\n (imageCropped)=\"onInnerImageCropped($event)\"\n (loadImageFailed)=\"loadImageFailed()\"\n (imageLoaded)=\"imageLoaded($event)\"\n [autoCrop]=\"false\"></image-cropper>\n\n <div class=\"modal-footer\">\n <button class=\"btn-crop\" (click)=\"simpleCropAndUpload()\">Recortar y Subir</button>\n </div>\n </div>\n </div>\n</div>\n\n<button *ngIf=\"croppedImage && !isUploaded\" [disabled]=\"isLoading\" nbButton status=\"info\"> upload </button>\n","import { Component, Input, OnInit, Output, EventEmitter, ViewChild, ChangeDetectorRef, Attribute } from '@angular/core';\nimport { NgIf } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\n\nimport { DialogModule } from 'primeng/dialog';\n\nimport { ImageCroppedEvent, ImageCropperComponent, LoadedImage } from 'ngx-image-cropper';\nimport { Observable } from 'rxjs';\n\nimport { MultiImagesStorageService } from '../../services/multi-images-storage.service';\nimport {\n AspectRatio,\n AspectType,\n ImageMultipleCrops,\n StorageImageSettings,\n ImgStorageData,\n AspectRatioOptions,\n AspectRatioOption,\n DEFAULT_SETTINGS,\n} from '../../classes/cropper.classes';\nimport { TooltipModule } from 'primeng/tooltip';\n\nimport { ButtonModule } from 'primeng/button';\nimport { MessageModule } from 'primeng/message';\nimport { SelectModule } from 'primeng/select';\nimport { InputTextModule } from 'primeng/inputtext';\n\n@Component({\n selector: 'dc-cropper-modal',\n templateUrl: './cropper-modal.component.html',\n styleUrls: ['./cropper-modal.component.scss'],\n standalone: true,\n imports: [NgIf, FormsModule, ImageCropperComponent, ButtonModule, DialogModule, TooltipModule, MessageModule, SelectModule, InputTextModule],\n})\nexport class CropperComponentModal implements OnInit {\n // overrides name, path and resizeToWidth\n\n @Input() imgStorageSettings: StorageImageSettings = DEFAULT_SETTINGS;\n @Input() buttonLabel: string = 'Seleccionar archivo';\n @Input() currentStorage: ImgStorageData = {} as any;\n\n @Output() imageUploaded = new EventEmitter<ImgStorageData>();\n @Output() onImageCropped = new EventEmitter<ImageMultipleCrops>();\n @Output() onFileSelected = new EventEmitter<any>();\n\n @ViewChild(ImageCropperComponent) imageCropper!: ImageCropperComponent;\n\n public aspectRatioOptions = AspectRatioOptions;\n public fileMetadata: File | null = null;\n public imageChangedEvent!: Event;\n public displayDialog = false;\n public aspectRatioValue: number = 1;\n public croppedImage: any = '';\n public renameFile: any = '';\n public storagePath: string = '';\n public downloadURL!: Observable<string>;\n public resizeToWidth: number = 450;\n public ratioSelected: any = null;\n\n // Add a unique identifier for the file input\n public fileInputId: string;\n\n constructor(private multiImagesStorageService: MultiImagesStorageService, private changeDetectorRef: ChangeDetectorRef) {\n // Generate random ID for file input\n this.fileInputId = `file-upload-${Math.random().toString(36).substring(2, 11)}`;\n }\n\n ngOnInit(): void {\n if (!this.imgStorageSettings.path) {\n console.warn('⚠️ Remember to set imgStorageSettings, path and fileName are required , path example: /collection/id/subcollection ');\n }\n this.reloadPath();\n }\n\n public reloadPath(): void {\n const randomCharacters = Math.random().toString(36).substring(2, 15);\n this.storagePath = `${this.imgStorageSettings.path}/${this.imgStorageSettings.fileName}-${randomCharacters}.webp`;\n }\n\n private setSettingsForComponent(): void {\n console.log('setSettingsForComponent', this.imgStorageSettings);\n // TODO: remove all the imageSettings and keep only imgStorageSettings\n\n this.aspectRatioValue = this.imgStorageSettings?.cropSettings?.aspectRatio\n ? AspectRatio[this.imgStorageSettings?.cropSettings?.aspectRatio]\n : AspectRatio[AspectType.Square];\n\n if (this.imgStorageSettings?.path) {\n this.storagePath = `${this.imgStorageSettings.path}/${this.imgStorageSettings.fileName}.webp`;\n } else if (this.imgStorageSettings.path) {\n this.storagePath = `${this.imgStorageSettings.path}/${this.imgStorageSettings.fileName}.webp`;\n }\n if (this.imgStorageSettings?.cropSettings?.resizeToWidth) {\n this.resizeToWidth = this.imgStorageSettings.cropSettings.resizeToWidth;\n } else if (this.imgStorageSettings.cropSettings.resizeToWidth) {\n this.resizeToWidth = this.imgStorageSettings.cropSettings.resizeToWidth;\n }\n if (this.imgStorageSettings?.fileName) {\n this.renameFile = this.imgStorageSettings.fileName;\n } else if (this.imgStorageSettings.fileName) {\n this.renameFile = this.imgStorageSettings.fileName;\n }\n }\n\n async fileChangeEvent(event: any) {\n this.setSettingsForComponent();\n\n console.log(this.fileInputId);\n\n this.imageChangedEvent = event;\n const file = event?.target?.files[0];\n if (file) {\n this.fileMetadata = file;\n this.onFileSelected.emit(file);\n this.renameFile = this.fileMetadata?.name\n ?.split('.')[0]\n .replace(/[^a-zA-Z0-9]/g, '')\n .slice(0, 80);\n\n console.log(this.renameFile);\n\n if (!this.imgStorageSettings.fileName) {\n this.reloadPath();\n }\n this.displayDialog = true;\n this.changeDetectorRef.detectChanges();\n }\n }\n\n onInnerImageCropped(event: ImageCroppedEvent) {\n this.croppedImage = event.base64;\n }\n\n imageLoaded(image: LoadedImage) {\n this.changeDetectorRef.detectChanges();\n }\n\n cropperReady() {\n this.changeDetectorRef.detectChanges();\n }\n\n loadImageFailed() {\n console.error('fallo al cargar la imagen');\n }\n\n public async simpleCropAndUpload() {\n console.log(this.fileInputId);\n\n console.log('simpleCropAndUpload');\n const imageCropped: any = await this.imageCropper.crop();\n const imgStorage = await this.multiImagesStorageService.uploadImage(imageCropped?.blob, this.storagePath);\n if (this.currentStorage?.path) {\n console.warn('deleting current Image', this.currentStorage?.path);\n this.multiImagesStorageService.deleteImage(this.currentStorage.path);\n }\n console.log('imgStorage', imgStorage);\n this.imageUploaded.emit(imgStorage);\n this.displayDialog = false;\n this.changeDetectorRef.detectChanges();\n }\n\n public changeRatio(event: AspectRatioOption) {\n console.log('changeRatio', event);\n // this.imgStorageSettings.cropSettings.aspectRatio = event.valueRatio;\n this.aspectRatioValue = event.valueRatio;\n this.changeDetectorRef.detectChanges();\n }\n}\n","<div class=\"upload-section\">\n <input type=\"file\" [id]=\"fileInputId\" class=\"file-input\" (change)=\"fileChangeEvent($event)\" />\n <label pButton [for]=\"fileInputId\" [pTooltip]=\"storagePath\" class=\"upload-button\">\n {{ buttonLabel }}\n </label>\n</div>\n@if(displayDialog) {\n<!-- Cropper Dialog -->\n\n<p-dialog header=\"Recortar imagen\" [(visible)]=\"displayDialog\" [modal]=\"true\" [draggable]=\"false\" [resizable]=\"false\" styleClass=\"cropper-dialog\">\n <!-- Image Settings Section -->\n <div class=\"settings-section\">\n @if(!imgStorageSettings.path) {\n <p-message severity=\"warn\">Developer Note: make sure you have a path to save the image pass object imgStorageSettings</p-message>\n } @if(currentStorage.url) {\n\n <p-message severity=\"warn\" variant=\"outlined\">\n <div>\n <span class=\"setting-label\">Image will be replaced:</span>\n <img width=\"100\" height=\"Auto\" [src]=\"currentStorage?.url\" />\n </div>\n </p-message>\n\n }\n\n <p-message>\n <b>Estas opciones estan preconfiguradas y no se pueden cambiar</b>\n\n <ul>\n <li> <b>Path to save:</b> {{ storagePath }}< </li>\n <li>\n <b>Resoluciones:</b>\n <span>{{ imgStorageSettings?.cropSettings?.resolutions }}</span>\n </li>\n </ul>\n </p-message>\n\n <div class=\"setting-item\">\n <span class=\"setting-label\">Aspecto:</span>\n <p class=\"setting-value\">{{ imgStorageSettings?.cropSettings?.aspectRatio }}</p>\n </div>\n\n <p-select\n [options]=\"aspectRatioOptions\"\n [ngModel]=\"ratioSelected\"\n (ngModelChange)=\"changeRatio($event)\"\n optionLabel=\"description\"\n placeholder=\"Select a ratio\" />\n\n <!-- File Metadata Section -->\n <div class=\"metadata-section\" *ngIf=\"fileMetadata\">\n <span class=\"metadata-item\">tipo: {{ fileMetadata.type }}</span>\n <span class=\"metadata-item\">tamaño: {{ fileMetadata.size }}</span>\n </div>\n\n <!-- Rename Input -->\n <input\n pInputText\n [disabled]=\"imgStorageSettings?.fileName\"\n [(ngModel)]=\"renameFile\"\n type=\"text\"\n placeholder=\"Rename File\"\n (ngModelChange)=\"reloadPath()\"\n class=\"rename-input\" />\n </div>\n\n <!-- Image Cropper -->\n\n <div class=\"cropper-container-father\">\n <image-cropper\n [imageChangedEvent]=\"imageChangedEvent\"\n [maintainAspectRatio]=\"true\"\n [aspectRatio]=\"aspectRatioValue\"\n format=\"webp\"\n [resizeToWidth]=\"resizeToWidth\"\n (imageCropped)=\"onInnerImageCropped($event)\"\n (loadImageFailed)=\"loadImageFailed()\"\n (cropperReady)=\"cropperReady()\"\n [autoCrop]=\"false\">\n </image-cropper>\n </div>\n <!-- Dialog Footer -->\n <ng-template pTemplate=\"footer\">\n <div class=\"dialog-footer\">\n <button pButton class=\"p-button-primary\" (click)=\"simpleCropAndUpload()\"> Recortar y Subir </button>\n </div>\n </ng-template>\n</p-dialog>\n}\n","import { Injectable } from '@angular/core';\nimport { getDownloadURL, ref, Storage } from '@angular/fire/storage';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class DCFilesCacheService {\n constructor(private storage: Storage) {}\n\n public files: { [key: string]: string } = {};\n\n public async getURLSrcFile(path: string): Promise<string> {\n if (path in this.files) {\n return this.files[path];\n } else {\n const url = await getDownloadURL(ref(this.storage, path));\n const localUrl = await this.donwloadFileAndGetLocalURL(url);\n this.files[path] = localUrl;\n return localUrl;\n }\n }\n\n public getBlob(url: string): Promise<Blob> {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.responseType = 'blob';\n xhr.overrideMimeType('audio/mp3');\n\n xhr.onload = (event) => {\n var blob = xhr.response;\n resolve(blob);\n };\n xhr.onerror = (event) => {\n reject(event);\n };\n\n xhr.open('GET', url);\n xhr.send();\n });\n }\n\n public async donwloadFileAndGetLocalURL(url: string) {\n const blob = await this.getBlob(url);\n\n return URL.createObjectURL(blob);\n }\n}\n","/*\n * Public API Surface of storage-uploader\n */\n\nexport * from './lib/components/cropper/cropper.component';\nexport * from './lib/components/cropper-modal/cropper-modal.component';\nexport * from './lib/classes/cropper.classes';\n\nexport * from './lib/services/multi-images-storage.service';\nexport * from './lib/services/dc-files-cache.service';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1.MultiImagesStorageService","ref","i1"],"mappings":"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"}
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ /// <amd-module name="@dataclouder/ngx-cloud-storage" />
5
+ export * from './public-api';
@@ -0,0 +1,80 @@
1
+ export interface StorageImageSettings {
2
+ path?: string;
3
+ fileName?: string;
4
+ cropSettings?: ImageCropSettings;
5
+ }
6
+ export interface ImageCropSettings {
7
+ resizeToWidth?: number;
8
+ resolutions: Array<number>;
9
+ aspectRatio: AspectType;
10
+ }
11
+ export interface CropImageSettings {
12
+ path: string;
13
+ fileName?: string;
14
+ resizeToWidth?: number;
15
+ }
16
+ export interface ImageMultipleCrops {
17
+ file: File;
18
+ defaultImageBlob?: Blob;
19
+ imagesBlobs: {
20
+ [resolution: number]: Blob;
21
+ };
22
+ settings: {
23
+ renameFile: string;
24
+ width?: number;
25
+ };
26
+ }
27
+ export declare const AspectRatioOptions: AspectRatioOption[];
28
+ export interface AspectRatioOption {
29
+ label: string;
30
+ description: string;
31
+ value: string;
32
+ valueRatio: number;
33
+ }
34
+ export declare enum AspectType {
35
+ Square = "square",
36
+ Rectangle = "rectangle",
37
+ Banner = "banner",
38
+ RectangleLarge = "rectangleLarge",
39
+ Vertical_9_16 = "vertical_9_16",
40
+ Vertical_3_5 = "vertical_3_5",
41
+ Vertical_2_3 = "vertical_2_3"
42
+ }
43
+ export declare enum AspectRatio2 {
44
+ '9:16' = 1.7777777777777777,
45
+ '3:5' = 0.6,
46
+ '5:8' = 0.625,
47
+ '2:3' = 0.6666666666666666,
48
+ '1:1' = 1
49
+ }
50
+ export declare enum ResolutionType {
51
+ Small = 200,
52
+ Medium = 400,
53
+ MediumLarge = 800,
54
+ Large = 1200,
55
+ VeryLarge = 1600
56
+ }
57
+ export declare const AspectRatio: {
58
+ square: number;
59
+ rectangle: number;
60
+ vertical_9_16: number;
61
+ rectangleLarge: number;
62
+ banner: number;
63
+ vertical_3_5: number;
64
+ vertical_2_3: number;
65
+ };
66
+ export interface ImgStorageData extends CloudStorageData {
67
+ name?: string;
68
+ bucket?: string;
69
+ url?: string;
70
+ path?: string;
71
+ fullPath?: string;
72
+ resolution?: string;
73
+ resolutions?: any;
74
+ }
75
+ export interface CloudStorageData {
76
+ bucket?: string;
77
+ url?: string;
78
+ path?: string;
79
+ }
80
+ export declare const DEFAULT_SETTINGS: StorageImageSettings;
@@ -0,0 +1,38 @@
1
+ import { OnInit, EventEmitter } from '@angular/core';
2
+ import { ImageCroppedEvent, ImageCropperComponent, LoadedImage } from 'ngx-image-cropper';
3
+ import { Observable } from 'rxjs';
4
+ import { AspectType, ImageMultipleCrops, CropImageSettings } from '../../classes/cropper.classes';
5
+ import { MultiImagesStorageService } from '../../services/multi-images-storage.service';
6
+ import * as i0 from "@angular/core";
7
+ export declare class CropperComponent implements OnInit {
8
+ private multiImagesStorageService;
9
+ imageSettings: CropImageSettings;
10
+ ratioType: AspectType | string;
11
+ resolutions: Array<number>;
12
+ imageUploaded: EventEmitter<any>;
13
+ onImageCropped: EventEmitter<ImageMultipleCrops>;
14
+ onFileSelected: EventEmitter<any>;
15
+ imageCropper: ImageCropperComponent;
16
+ fileMetadata: File | null;
17
+ imageChangedEvent: Event;
18
+ aspectRatio: number;
19
+ croppedImage: any;
20
+ isLoading: boolean;
21
+ isUploaded: boolean;
22
+ renameFile: any;
23
+ storagePath: string;
24
+ showModal: boolean;
25
+ constructor(multiImagesStorageService: MultiImagesStorageService);
26
+ ngOnInit(): void;
27
+ reloadPath(): void;
28
+ fileChangeEvent(event: any): Promise<void>;
29
+ onInnerImageCropped(event: ImageCroppedEvent): void;
30
+ imageLoaded(image: LoadedImage): void;
31
+ loadImageFailed(): void;
32
+ downloadURL: Observable<string>;
33
+ simpleCropAndUpload(): Promise<void>;
34
+ uploadToStorage(imageMulticrops: ImageMultipleCrops): Promise<void>;
35
+ closeModal(): void;
36
+ static ɵfac: i0.ɵɵFactoryDeclaration<CropperComponent, never>;
37
+ static ɵcmp: i0.ɵɵComponentDeclaration<CropperComponent, "app-cropper", never, { "imageSettings": { "alias": "imageSettings"; "required": false; }; "ratioType": { "alias": "ratioType"; "required": false; }; "resolutions": { "alias": "resolutions"; "required": false; }; }, { "imageUploaded": "imageUploaded"; "onImageCropped": "onImageCropped"; "onFileSelected": "onFileSelected"; }, never, never, true, never>;
38
+ }
@@ -0,0 +1,42 @@
1
+ import { OnInit, EventEmitter, ChangeDetectorRef } from '@angular/core';
2
+ import { ImageCroppedEvent, ImageCropperComponent, LoadedImage } from 'ngx-image-cropper';
3
+ import { Observable } from 'rxjs';
4
+ import { MultiImagesStorageService } from '../../services/multi-images-storage.service';
5
+ import { ImageMultipleCrops, StorageImageSettings, ImgStorageData, AspectRatioOption } from '../../classes/cropper.classes';
6
+ import * as i0 from "@angular/core";
7
+ export declare class CropperComponentModal implements OnInit {
8
+ private multiImagesStorageService;
9
+ private changeDetectorRef;
10
+ imgStorageSettings: StorageImageSettings;
11
+ buttonLabel: string;
12
+ currentStorage: ImgStorageData;
13
+ imageUploaded: EventEmitter<ImgStorageData>;
14
+ onImageCropped: EventEmitter<ImageMultipleCrops>;
15
+ onFileSelected: EventEmitter<any>;
16
+ imageCropper: ImageCropperComponent;
17
+ aspectRatioOptions: AspectRatioOption[];
18
+ fileMetadata: File | null;
19
+ imageChangedEvent: Event;
20
+ displayDialog: boolean;
21
+ aspectRatioValue: number;
22
+ croppedImage: any;
23
+ renameFile: any;
24
+ storagePath: string;
25
+ downloadURL: Observable<string>;
26
+ resizeToWidth: number;
27
+ ratioSelected: any;
28
+ fileInputId: string;
29
+ constructor(multiImagesStorageService: MultiImagesStorageService, changeDetectorRef: ChangeDetectorRef);
30
+ ngOnInit(): void;
31
+ reloadPath(): void;
32
+ private setSettingsForComponent;
33
+ fileChangeEvent(event: any): Promise<void>;
34
+ onInnerImageCropped(event: ImageCroppedEvent): void;
35
+ imageLoaded(image: LoadedImage): void;
36
+ cropperReady(): void;
37
+ loadImageFailed(): void;
38
+ simpleCropAndUpload(): Promise<void>;
39
+ changeRatio(event: AspectRatioOption): void;
40
+ static ɵfac: i0.ɵɵFactoryDeclaration<CropperComponentModal, never>;
41
+ static ɵcmp: i0.ɵɵComponentDeclaration<CropperComponentModal, "dc-cropper-modal", never, { "imgStorageSettings": { "alias": "imgStorageSettings"; "required": false; }; "buttonLabel": { "alias": "buttonLabel"; "required": false; }; "currentStorage": { "alias": "currentStorage"; "required": false; }; }, { "imageUploaded": "imageUploaded"; "onImageCropped": "onImageCropped"; "onFileSelected": "onFileSelected"; }, never, never, true, never>;
42
+ }
@@ -0,0 +1,14 @@
1
+ import { Storage } from '@angular/fire/storage';
2
+ import * as i0 from "@angular/core";
3
+ export declare class DCFilesCacheService {
4
+ private storage;
5
+ constructor(storage: Storage);
6
+ files: {
7
+ [key: string]: string;
8
+ };
9
+ getURLSrcFile(path: string): Promise<string>;
10
+ getBlob(url: string): Promise<Blob>;
11
+ donwloadFileAndGetLocalURL(url: string): Promise<string>;
12
+ static ɵfac: i0.ɵɵFactoryDeclaration<DCFilesCacheService, never>;
13
+ static ɵprov: i0.ɵɵInjectableDeclaration<DCFilesCacheService>;
14
+ }
@@ -0,0 +1,14 @@
1
+ import { AngularFireStorage } from '@angular/fire/compat/storage';
2
+ import { ImageMultipleCrops, ImgStorageData } from '../classes/cropper.classes';
3
+ import * as i0 from "@angular/core";
4
+ export declare class MultiImagesStorageService {
5
+ private storage;
6
+ constructor(storage: AngularFireStorage);
7
+ uploadImage(image: Blob, path: string): Promise<ImgStorageData>;
8
+ uploadImage2(imageMulticrops: ImageMultipleCrops, path: string): Promise<ImgStorageData>;
9
+ delete_directory(imagePath: string): Promise<void>;
10
+ deleteImage(imagePath: string): Promise<void>;
11
+ private uploadAndGetUrl;
12
+ static ɵfac: i0.ɵɵFactoryDeclaration<MultiImagesStorageService, never>;
13
+ static ɵprov: i0.ɵɵInjectableDeclaration<MultiImagesStorageService>;
14
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@dataclouder/ngx-cloud-storage",
3
+ "version": "0.0.21",
4
+ "description": "Angular component library for handling cloud storage uploads with image cropping capabilities",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "peerDependencies": {
9
+ "@angular/common": ">=18.0.0",
10
+ "@angular/core": ">=18.0.0",
11
+ "@angular/fire": ">=18.0.0",
12
+ "ngx-image-cropper": "^9.0.0",
13
+ "primeng": ">=19.0.0"
14
+ },
15
+ "dependencies": {
16
+ "tslib": "^2.3.0"
17
+ },
18
+ "sideEffects": false,
19
+ "module": "fesm2022/dataclouder-ngx-cloud-storage.mjs",
20
+ "typings": "index.d.ts",
21
+ "exports": {
22
+ "./package.json": {
23
+ "default": "./package.json"
24
+ },
25
+ ".": {
26
+ "types": "./index.d.ts",
27
+ "default": "./fesm2022/dataclouder-ngx-cloud-storage.mjs"
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,5 @@
1
+ export * from './lib/components/cropper/cropper.component';
2
+ export * from './lib/components/cropper-modal/cropper-modal.component';
3
+ export * from './lib/classes/cropper.classes';
4
+ export * from './lib/services/multi-images-storage.service';
5
+ export * from './lib/services/dc-files-cache.service';