@dataclouder/ngx-cloud-storage 0.0.21 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,27 +1,26 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, EventEmitter, Component, Input, Output, ViewChild } from '@angular/core';
2
+ import { inject, Injectable, input, output, ViewChild, Component, ChangeDetectorRef, Input, signal } from '@angular/core';
3
3
  import { ImageCropperComponent } from 'ngx-image-cropper';
4
- import { NgIf } from '@angular/common';
5
- import * as i2 from '@angular/forms';
4
+ import * as i1 from '@angular/forms';
6
5
  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';
6
+ import { AngularFireStorage } from '@angular/fire/compat/storage';
7
+ import { getStorage, listAll, deleteObject, getDownloadURL, Storage, ref as ref$1 } from '@angular/fire/storage';
8
+ import { ref, getStorage as getStorage$1, listAll as listAll$1, getDownloadURL as getDownloadURL$1 } from 'firebase/storage';
9
+ import { lastValueFrom, BehaviorSubject } from 'rxjs';
10
+ import * as i4 from 'primeng/dialog';
13
11
  import { DialogModule } from 'primeng/dialog';
14
- import * as i6 from 'primeng/tooltip';
12
+ import * as i5 from 'primeng/tooltip';
15
13
  import { TooltipModule } from 'primeng/tooltip';
16
- import * as i3 from 'primeng/button';
14
+ import * as i2 from 'primeng/button';
17
15
  import { ButtonModule } from 'primeng/button';
18
- import * as i7 from 'primeng/message';
16
+ import * as i6 from 'primeng/message';
19
17
  import { MessageModule } from 'primeng/message';
20
- import * as i8 from 'primeng/select';
18
+ import * as i7 from 'primeng/select';
21
19
  import { SelectModule } from 'primeng/select';
22
- import * as i9 from 'primeng/inputtext';
20
+ import * as i8 from 'primeng/inputtext';
23
21
  import { InputTextModule } from 'primeng/inputtext';
24
- import * as i4 from 'primeng/api';
22
+ import * as i3 from 'primeng/api';
23
+ import { AsyncPipe, CommonModule } from '@angular/common';
25
24
 
26
25
  const AspectRatioOptions = [
27
26
  { value: '1:1', label: 'square', description: 'Square (1:1)', valueRatio: 1 / 1 },
@@ -82,18 +81,79 @@ const DEFAULT_SETTINGS = {
82
81
  resizeToWidth: 450,
83
82
  },
84
83
  };
84
+ const FIREBASE_STORAGE_URL_REGEX = /https:\/\/firebasestorage\.googleapis\.com\/v0\/b\/([^/]+)\/o\/([^?]+)/;
85
+ /**
86
+ * Extracts the bucket name from a Firebase Storage URL.
87
+ * The URL is expected to follow the pattern:
88
+ * 'https://firebasestorage.googleapis.com/v0/b/<bucket>/<path>?<query_params>'
89
+ *
90
+ * @param data The CloudStorageData object containing the 'url' property.
91
+ * @returns The bucket name (e.g., 'appingles-pro.appspot.com') or undefined if not found or URL is invalid.
92
+ */
93
+ function extractBucket(data) {
94
+ if (!data.url) {
95
+ return undefined;
96
+ }
97
+ const match = data.url.match(FIREBASE_STORAGE_URL_REGEX);
98
+ // match[0] is the full matched string
99
+ // match[1] is the first capturing group (bucket)
100
+ // match[2] is the second capturing group (path)
101
+ return match?.[1];
102
+ }
103
+ /**
104
+ * Extracts the file path from a Firebase Storage URL.
105
+ * The URL is expected to follow the pattern:
106
+ * 'https://firebasestorage.googleapis.com/v0/b/<bucket>/<path>?<query_params>'
107
+ * The extracted path is the part after the bucket and before any query parameters.
108
+ *
109
+ * @param data The CloudStorageData object containing the 'url' property.
110
+ * @returns The file path (e.g., 'scenarios/666506c3b9b5443f4bfab5e0/images/hairdresser.webp')
111
+ * or undefined if not found or URL is invalid.
112
+ * Note: This function does not perform URL decoding on the path. If URL-decoded paths
113
+ * (e.g., converting %2F to /) are needed, `decodeURIComponent` should be applied to the result.
114
+ */
115
+ function extractPath(data) {
116
+ if (data && data?.url) {
117
+ const match = data.url.match(FIREBASE_STORAGE_URL_REGEX);
118
+ // match[0] is the full matched string
119
+ // match[1] is the first capturing group (bucket)
120
+ // match[2] is the second capturing group (path after 'o/')
121
+ if (match && match[2]) {
122
+ try {
123
+ // Decode URI components, e.g., %2F to /
124
+ return decodeURIComponent(match[2]);
125
+ }
126
+ catch (e) {
127
+ // Log error if decoding fails.
128
+ // Returning the raw path component as a fallback.
129
+ // Consider if undefined or throwing an error would be more appropriate for your use case.
130
+ console.error(`Failed to decode URI component for path: '${match[2]}'`, e);
131
+ return match[2]; // Fallback to raw path component
132
+ }
133
+ }
134
+ return undefined; // URL did not match expected pattern or path component missing
135
+ }
136
+ else {
137
+ return undefined;
138
+ }
139
+ }
85
140
 
86
141
  class MultiImagesStorageService {
87
- constructor(storage) {
88
- this.storage = storage;
142
+ constructor() {
143
+ this.storage = inject(AngularFireStorage);
89
144
  }
90
145
  async uploadImage(image, path) {
91
146
  try {
92
147
  const refStorage = this.storage.ref(path);
93
148
  const task = await refStorage.put(image);
94
- const { fullPath, bucket } = task.metadata;
149
+ const { bucket, name: metadataName, contentType, size } = task.metadata;
95
150
  const url = await lastValueFrom(refStorage.getDownloadURL());
96
- const imageStorage = { url, path: fullPath, bucket };
151
+ const imageStorage = {
152
+ url,
153
+ name: metadataName,
154
+ type: contentType,
155
+ size,
156
+ };
97
157
  return imageStorage;
98
158
  }
99
159
  catch (error) {
@@ -101,43 +161,6 @@ class MultiImagesStorageService {
101
161
  return null;
102
162
  }
103
163
  }
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
164
  async delete_directory(imagePath) {
142
165
  // WARNING!! user very carefully could delete whatever folder
143
166
  const storage = getStorage();
@@ -161,41 +184,62 @@ class MultiImagesStorageService {
161
184
  }
162
185
  async uploadAndGetUrl(task, resolution) {
163
186
  const snap = await task;
164
- const { fullPath, bucket, name } = snap.metadata;
187
+ const { fullPath, bucket, name, contentType, size } = snap.metadata;
165
188
  const storage = getStorage();
166
189
  const storageRef = ref(storage, fullPath);
167
190
  const url = await getDownloadURL(storageRef);
168
191
  const meta = {
169
192
  url,
170
- fullPath,
171
- bucket,
172
193
  name,
173
194
  resolution,
174
- path: '',
175
- resolutions: {},
195
+ type: contentType,
196
+ size,
197
+ resolutions: {}, // Default empty resolutions
176
198
  };
177
199
  return meta;
178
200
  }
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' }); }
201
+ async uploadGenericFile(file, storagePath) {
202
+ try {
203
+ // Ensure storagePath is a directory path, and append file.name
204
+ const fullFilePath = `${storagePath.replace(/\/$/, '')}/${file.name}`;
205
+ const refStorage = this.storage.ref(fullFilePath);
206
+ const task = await refStorage.put(file);
207
+ const url = await lastValueFrom(refStorage.getDownloadURL());
208
+ const fileData = {
209
+ url,
210
+ name: file.name,
211
+ type: file.type,
212
+ size: file.size,
213
+ metadata: task.metadata,
214
+ };
215
+ return fileData;
216
+ }
217
+ catch (error) {
218
+ console.error(`Error uploading file ${file.name} to ${storagePath}:`, error);
219
+ return null;
220
+ }
221
+ }
222
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MultiImagesStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
223
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MultiImagesStorageService, providedIn: 'root' }); }
181
224
  }
182
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: MultiImagesStorageService, decorators: [{
225
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MultiImagesStorageService, decorators: [{
183
226
  type: Injectable,
184
227
  args: [{
185
228
  providedIn: 'root',
186
229
  }]
187
- }], ctorParameters: () => [{ type: i1.AngularFireStorage }] });
230
+ }] });
188
231
 
189
232
  class CropperComponent {
190
- constructor(multiImagesStorageService) {
191
- this.multiImagesStorageService = multiImagesStorageService;
233
+ constructor() {
234
+ this.multiImagesStorageService = inject(MultiImagesStorageService);
192
235
  // 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();
236
+ this.imageSettings = input({});
237
+ this.ratioType = input(AspectType.Square);
238
+ this.resolutions = input([ResolutionType.MediumLarge]);
239
+ // Outputs
240
+ this.imageUploaded = output();
241
+ this.onImageCropped = output();
242
+ this.onFileSelected = output();
199
243
  this.fileMetadata = null;
200
244
  this.aspectRatio = 1;
201
245
  this.croppedImage = '';
@@ -206,13 +250,14 @@ class CropperComponent {
206
250
  this.showModal = false;
207
251
  }
208
252
  ngOnInit() {
209
- this.aspectRatio = AspectRatio[this.ratioType];
210
- if (this.imageSettings.path) {
211
- this.storagePath = `${this.imageSettings.path}/${this.imageSettings.fileName}.webp`;
253
+ this.aspectRatio = AspectRatio[this.ratioType()];
254
+ const imageSettings = this.imageSettings();
255
+ if (imageSettings.path) {
256
+ this.storagePath = `${imageSettings.path}/${imageSettings.fileName}.webp`;
212
257
  }
213
258
  }
214
259
  reloadPath() {
215
- this.storagePath = `${this.imageSettings.path}/${this.renameFile}.webp`;
260
+ this.storagePath = `${this.imageSettings().path}/${this.renameFile}.webp`;
216
261
  }
217
262
  async fileChangeEvent(event) {
218
263
  this.imageChangedEvent = event;
@@ -223,7 +268,7 @@ class CropperComponent {
223
268
  this.showModal = true; // Show modal when file is selected
224
269
  this.renameFile = this.fileMetadata?.name?.split('.')[0];
225
270
  console.log(this.renameFile);
226
- if (!this.imageSettings.fileName) {
271
+ if (!this.imageSettings().fileName) {
227
272
  this.reloadPath();
228
273
  }
229
274
  }
@@ -243,65 +288,31 @@ class CropperComponent {
243
288
  this.imageUploaded.emit(imgStorage);
244
289
  this.closeModal();
245
290
  }
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
291
  closeModal() {
269
292
  this.showModal = false;
270
293
  }
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"] }] }); }
294
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: CropperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
295
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: CropperComponent, isStandalone: true, selector: "app-cropper", inputs: { imageSettings: { classPropertyName: "imageSettings", publicName: "imageSettings", isSignal: true, isRequired: false, transformFunction: null }, ratioType: { classPropertyName: "ratioType", publicName: "ratioType", isSignal: true, isRequired: false, transformFunction: null }, resolutions: { classPropertyName: "resolutions", publicName: "resolutions", isSignal: true, isRequired: false, transformFunction: null } }, 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 @if (!isUploaded) {\n <div>\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 @if (!fileMetadata) {\n <em>Carga una imagen para comenzar</em>\n }\n </div>\n }\n\n @if (fileMetadata) {\n <span>\n <span style=\"margin: 1px 20px\"> tipo: {{ fileMetadata.type }} </span>\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 }\n</div>\n\n@if (fileMetadata && !isUploaded) {\n <div class=\"modal\" [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 <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 <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\n@if (croppedImage && !isUploaded) {\n <button [disabled]=\"isLoading\" nbButton status=\"info\"> upload </button>\n}\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: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.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
296
  }
274
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: CropperComponent, decorators: [{
297
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: CropperComponent, decorators: [{
275
298
  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: [{
299
+ args: [{ selector: 'app-cropper', standalone: true, imports: [FormsModule, ImageCropperComponent], template: "<div> path: {{ storagePath }} </div>\n\n<div class=\"options\">\n @if (!isUploaded) {\n <div>\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 @if (!fileMetadata) {\n <em>Carga una imagen para comenzar</em>\n }\n </div>\n }\n\n @if (fileMetadata) {\n <span>\n <span style=\"margin: 1px 20px\"> tipo: {{ fileMetadata.type }} </span>\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 }\n</div>\n\n@if (fileMetadata && !isUploaded) {\n <div class=\"modal\" [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 <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 <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\n@if (croppedImage && !isUploaded) {\n <button [disabled]=\"isLoading\" nbButton status=\"info\"> upload </button>\n}\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"] }]
300
+ }], propDecorators: { imageCropper: [{
290
301
  type: ViewChild,
291
302
  args: [ImageCropperComponent]
292
303
  }] } });
293
304
 
294
305
  class CropperComponentModal {
295
- constructor(multiImagesStorageService, changeDetectorRef) {
296
- this.multiImagesStorageService = multiImagesStorageService;
297
- this.changeDetectorRef = changeDetectorRef;
306
+ constructor() {
307
+ this.multiImagesStorageService = inject(MultiImagesStorageService);
308
+ this.changeDetectorRef = inject(ChangeDetectorRef);
298
309
  // overrides name, path and resizeToWidth
299
- this.imgStorageSettings = DEFAULT_SETTINGS;
300
- this.buttonLabel = 'Seleccionar archivo';
310
+ this.imgStorageSettings = input(DEFAULT_SETTINGS);
311
+ this.buttonLabel = input('Seleccionar archivo');
301
312
  this.currentStorage = {};
302
- this.imageUploaded = new EventEmitter();
303
- this.onImageCropped = new EventEmitter();
304
- this.onFileSelected = new EventEmitter();
313
+ this.imageUploaded = output();
314
+ this.onImageCropped = output();
315
+ this.onFileSelected = output();
305
316
  this.aspectRatioOptions = AspectRatioOptions;
306
317
  this.fileMetadata = null;
307
318
  this.displayDialog = false;
@@ -315,38 +326,40 @@ class CropperComponentModal {
315
326
  this.fileInputId = `file-upload-${Math.random().toString(36).substring(2, 11)}`;
316
327
  }
317
328
  ngOnInit() {
318
- if (!this.imgStorageSettings.path) {
329
+ if (!this.imgStorageSettings().path) {
319
330
  console.warn('⚠️ Remember to set imgStorageSettings, path and fileName are required , path example: /collection/id/subcollection ');
320
331
  }
321
332
  this.reloadPath();
322
333
  }
323
334
  reloadPath() {
324
335
  const randomCharacters = Math.random().toString(36).substring(2, 15);
325
- this.storagePath = `${this.imgStorageSettings.path}/${this.imgStorageSettings.fileName}-${randomCharacters}.webp`;
336
+ const fileName = this.imgStorageSettings().fileName || 'img';
337
+ this.storagePath = `${this.imgStorageSettings().path}/${fileName}-${randomCharacters}.webp`;
326
338
  }
327
339
  setSettingsForComponent() {
328
- console.log('setSettingsForComponent', this.imgStorageSettings);
340
+ const imgStorageSettings = this.imgStorageSettings();
341
+ console.log('setSettingsForComponent', imgStorageSettings);
329
342
  // TODO: remove all the imageSettings and keep only imgStorageSettings
330
- this.aspectRatioValue = this.imgStorageSettings?.cropSettings?.aspectRatio
331
- ? AspectRatio[this.imgStorageSettings?.cropSettings?.aspectRatio]
343
+ this.aspectRatioValue = imgStorageSettings?.cropSettings?.aspectRatio
344
+ ? AspectRatio[imgStorageSettings?.cropSettings?.aspectRatio]
332
345
  : AspectRatio[AspectType.Square];
333
- if (this.imgStorageSettings?.path) {
334
- this.storagePath = `${this.imgStorageSettings.path}/${this.imgStorageSettings.fileName}.webp`;
346
+ if (imgStorageSettings?.path) {
347
+ this.storagePath = `${imgStorageSettings.path}/${imgStorageSettings.fileName}.webp`;
335
348
  }
336
- else if (this.imgStorageSettings.path) {
337
- this.storagePath = `${this.imgStorageSettings.path}/${this.imgStorageSettings.fileName}.webp`;
349
+ else if (imgStorageSettings.path) {
350
+ this.storagePath = `${imgStorageSettings.path}/${imgStorageSettings.fileName}.webp`;
338
351
  }
339
- if (this.imgStorageSettings?.cropSettings?.resizeToWidth) {
340
- this.resizeToWidth = this.imgStorageSettings.cropSettings.resizeToWidth;
352
+ if (imgStorageSettings?.cropSettings?.resizeToWidth) {
353
+ this.resizeToWidth = imgStorageSettings.cropSettings.resizeToWidth;
341
354
  }
342
- else if (this.imgStorageSettings.cropSettings.resizeToWidth) {
343
- this.resizeToWidth = this.imgStorageSettings.cropSettings.resizeToWidth;
355
+ else if (imgStorageSettings.cropSettings.resizeToWidth) {
356
+ this.resizeToWidth = imgStorageSettings.cropSettings.resizeToWidth;
344
357
  }
345
- if (this.imgStorageSettings?.fileName) {
346
- this.renameFile = this.imgStorageSettings.fileName;
358
+ if (imgStorageSettings?.fileName) {
359
+ this.renameFile = imgStorageSettings.fileName;
347
360
  }
348
- else if (this.imgStorageSettings.fileName) {
349
- this.renameFile = this.imgStorageSettings.fileName;
361
+ else if (imgStorageSettings.fileName) {
362
+ this.renameFile = imgStorageSettings.fileName;
350
363
  }
351
364
  }
352
365
  async fileChangeEvent(event) {
@@ -362,7 +375,7 @@ class CropperComponentModal {
362
375
  .replace(/[^a-zA-Z0-9]/g, '')
363
376
  .slice(0, 80);
364
377
  console.log(this.renameFile);
365
- if (!this.imgStorageSettings.fileName) {
378
+ if (!this.imgStorageSettings().fileName) {
366
379
  this.reloadPath();
367
380
  }
368
381
  this.displayDialog = true;
@@ -383,12 +396,15 @@ class CropperComponentModal {
383
396
  }
384
397
  async simpleCropAndUpload() {
385
398
  console.log(this.fileInputId);
399
+ debugger;
386
400
  console.log('simpleCropAndUpload');
387
401
  const imageCropped = await this.imageCropper.crop();
388
402
  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);
403
+ const path = extractPath(this.currentStorage);
404
+ if (path) {
405
+ console.warn('deleting current Image', path);
406
+ debugger;
407
+ this.multiImagesStorageService.deleteImage(path);
392
408
  }
393
409
  console.log('imgStorage', imgStorage);
394
410
  this.imageUploaded.emit(imgStorage);
@@ -401,32 +417,152 @@ class CropperComponentModal {
401
417
  this.aspectRatioValue = event.valueRatio;
402
418
  this.changeDetectorRef.detectChanges();
403
419
  }
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"] }] }); }
420
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: CropperComponentModal, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
421
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: CropperComponentModal, isStandalone: true, selector: "dc-cropper-modal", inputs: { imgStorageSettings: { classPropertyName: "imgStorageSettings", publicName: "imgStorageSettings", isSignal: true, isRequired: false, transformFunction: null }, buttonLabel: { classPropertyName: "buttonLabel", publicName: "buttonLabel", isSignal: true, isRequired: false, transformFunction: null }, currentStorage: { classPropertyName: "currentStorage", publicName: "currentStorage", isSignal: false, isRequired: false, transformFunction: null } }, 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 @if (fileMetadata) {\n <div class=\"metadata-section\">\n <span class=\"metadata-item\">tipo: {{ fileMetadata.type }}</span>\n <span class=\"metadata-item\">tama\u00F1o: {{ fileMetadata.size }}</span>\n </div>\n }\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: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.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: i2.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "directive", type: i3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i4.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: i5.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: i6.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: i7.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: i8.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }] }); }
406
422
  }
407
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: CropperComponentModal, decorators: [{
423
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: CropperComponentModal, decorators: [{
408
424
  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: [{
425
+ args: [{ selector: 'dc-cropper-modal', standalone: true, imports: [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 @if (fileMetadata) {\n <div class=\"metadata-section\">\n <span class=\"metadata-item\">tipo: {{ fileMetadata.type }}</span>\n <span class=\"metadata-item\">tama\u00F1o: {{ fileMetadata.size }}</span>\n </div>\n }\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"] }]
426
+ }], ctorParameters: () => [], propDecorators: { currentStorage: [{
411
427
  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
428
  }], imageCropper: [{
423
429
  type: ViewChild,
424
430
  args: [ImageCropperComponent]
425
431
  }] } });
426
432
 
433
+ class ImageStoragePreviewComponent {
434
+ constructor() {
435
+ this.storage = inject(AngularFireStorage);
436
+ this.imageSelected = output();
437
+ this.images$ = new BehaviorSubject([]);
438
+ this.loading$ = new BehaviorSubject(false);
439
+ this.error$ = new BehaviorSubject(null);
440
+ this.storagePath = '/images/resources';
441
+ this.subscriptions = [];
442
+ this.imgStorageSettings = {
443
+ path: this.storagePath,
444
+ cropSettings: {
445
+ aspectRatio: AspectType.Square,
446
+ resolutions: [ResolutionType.Small],
447
+ },
448
+ };
449
+ }
450
+ ngOnInit() {
451
+ this.loadImagesFromStorage();
452
+ }
453
+ ngOnDestroy() {
454
+ this.subscriptions.forEach((sub) => sub.unsubscribe());
455
+ }
456
+ /**
457
+ * Loads images from Firebase Storage at the specified path
458
+ */
459
+ async loadImagesFromStorage() {
460
+ try {
461
+ this.loading$.next(true);
462
+ this.error$.next(null);
463
+ const storage = getStorage$1();
464
+ const storageRef = ref(storage, this.storagePath);
465
+ const result = await listAll$1(storageRef);
466
+ if (result.items.length === 0) {
467
+ this.images$.next([]);
468
+ this.loading$.next(false);
469
+ return;
470
+ }
471
+ const imagePromises = result.items.map(async (itemRef) => {
472
+ try {
473
+ const url = await getDownloadURL$1(itemRef);
474
+ const image = {
475
+ url,
476
+ // fullPath: itemRef.fullPath,
477
+ name: itemRef.name,
478
+ };
479
+ return image;
480
+ }
481
+ catch (error) {
482
+ console.error(`Error getting download URL for ${itemRef.fullPath}:`, error);
483
+ return null;
484
+ }
485
+ });
486
+ const images = (await Promise.all(imagePromises)).filter((img) => img !== null);
487
+ this.images$.next(images);
488
+ }
489
+ catch (error) {
490
+ console.error('Error loading images from storage:', error);
491
+ this.error$.next('Failed to load images from storage. Please try again later.');
492
+ }
493
+ finally {
494
+ this.loading$.next(false);
495
+ }
496
+ }
497
+ /**
498
+ * Refreshes the image list
499
+ */
500
+ refreshImages() {
501
+ this.loadImagesFromStorage();
502
+ }
503
+ selectImage(image) {
504
+ this.imageSelected.emit(image);
505
+ }
506
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ImageStoragePreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
507
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: ImageStoragePreviewComponent, isStandalone: true, selector: "dc-image-storage-preview", outputs: { imageSelected: "imageSelected" }, ngImport: i0, template: "<div class=\"image-storage-preview-container\">\n <div class=\"header\">\n <h2>Storage Images</h2>\n <button class=\"refresh-btn\" (click)=\"refreshImages()\" [disabled]=\"loading$ | async\">\n @if (!(loading$ | async)) {\n <span>Refresh</span>\n }\n @if (loading$ | async) {\n <span>Loading...</span>\n }\n </button>\n </div>\n\n @if (loading$ | async) {\n <div class=\"loading-container\">\n <div class=\"spinner\"></div>\n <p>Loading images...</p>\n </div>\n }\n\n @if (error$ | async) {\n <div class=\"error-container\">\n <p class=\"error-message\">{{ error$ | async }}</p>\n <button (click)=\"refreshImages()\">Try Again</button>\n </div>\n }\n\n @if (!(loading$ | async) && !(error$ | async)) {\n <div class=\"images-grid\">\n @if ((images$ | async)?.length) {\n @for (image of images$ | async; track image) {\n <div class=\"image-card\">\n <div class=\"image-container\">\n <img [src]=\"image.url\" [alt]=\"image.name || 'Storage image'\" loading=\"lazy\" />\n </div>\n <div class=\"image-info\">\n <p class=\"image-name\" [title]=\"image.name\">{{ image.name }}</p>\n <div class=\"image-actions\">\n <a [href]=\"image.url\" target=\"_blank\" class=\"action-btn\">View</a>\n <button (click)=\"selectImage(image)\" class=\"action-btn\">Select</button>\n </div>\n </div>\n </div>\n }\n } @else {\n <div class=\"no-images\">\n <p>No images found in the storage path: {{ storagePath }}</p>\n </div>\n }\n <dc-cropper-modal [imgStorageSettings]=\"imgStorageSettings\"></dc-cropper-modal>\n </div>\n }\n</div>\n", styles: [".image-storage-preview-container{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif;padding:20px;max-width:1200px;margin:0 auto}.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.header h2{margin:0;color:#333}.refresh-btn{background-color:#4285f4;color:#fff;border:none;border-radius:4px;padding:8px 16px;cursor:pointer;font-size:14px;transition:background-color .2s}.refresh-btn:hover{background-color:#3367d6}.refresh-btn:disabled{background-color:#a9a9a9;cursor:not-allowed}.loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 0}.spinner{border:4px solid rgba(0,0,0,.1);border-radius:50%;border-top:4px solid #4285f4;width:30px;height:30px;animation:spin 1s linear infinite;margin-bottom:15px}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.error-container{background-color:#ffebee;border:1px solid #ffcdd2;border-radius:4px;padding:16px;margin:20px 0;text-align:center}.error-message{color:#d32f2f;margin-bottom:10px}.error-container button{background-color:#d32f2f;color:#fff;border:none;border-radius:4px;padding:8px 16px;cursor:pointer;font-size:14px}.images-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:20px}.image-card{border-radius:8px;overflow:hidden;box-shadow:0 2px 10px #0000001a;transition:transform .2s,box-shadow .2s;background-color:#fff}.image-card:hover{transform:translateY(-5px);box-shadow:0 5px 15px #00000026}.image-container{height:200px;overflow:hidden;position:relative;background-color:#f5f5f5}.image-container img{width:100%;height:100%;object-fit:cover;transition:transform .3s}.image-card:hover .image-container img{transform:scale(1.05)}.image-info{padding:15px}.image-name{margin:0 0 10px;font-size:14px;font-weight:500;color:#333;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.image-actions{display:flex;justify-content:flex-end}.action-btn{background-color:#4285f4;color:#fff;text-decoration:none;border-radius:4px;padding:6px 12px;font-size:12px;transition:background-color .2s}.action-btn:hover{background-color:#3367d6}.no-images{grid-column:1 / -1;text-align:center;padding:40px 0;color:#757575;background-color:#f5f5f5;border-radius:8px}\n"], dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "component", type: CropperComponentModal, selector: "dc-cropper-modal", inputs: ["imgStorageSettings", "buttonLabel", "currentStorage"], outputs: ["imageUploaded", "onImageCropped", "onFileSelected"] }] }); }
508
+ }
509
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ImageStoragePreviewComponent, decorators: [{
510
+ type: Component,
511
+ args: [{ selector: 'dc-image-storage-preview', standalone: true, imports: [AsyncPipe, CropperComponentModal], template: "<div class=\"image-storage-preview-container\">\n <div class=\"header\">\n <h2>Storage Images</h2>\n <button class=\"refresh-btn\" (click)=\"refreshImages()\" [disabled]=\"loading$ | async\">\n @if (!(loading$ | async)) {\n <span>Refresh</span>\n }\n @if (loading$ | async) {\n <span>Loading...</span>\n }\n </button>\n </div>\n\n @if (loading$ | async) {\n <div class=\"loading-container\">\n <div class=\"spinner\"></div>\n <p>Loading images...</p>\n </div>\n }\n\n @if (error$ | async) {\n <div class=\"error-container\">\n <p class=\"error-message\">{{ error$ | async }}</p>\n <button (click)=\"refreshImages()\">Try Again</button>\n </div>\n }\n\n @if (!(loading$ | async) && !(error$ | async)) {\n <div class=\"images-grid\">\n @if ((images$ | async)?.length) {\n @for (image of images$ | async; track image) {\n <div class=\"image-card\">\n <div class=\"image-container\">\n <img [src]=\"image.url\" [alt]=\"image.name || 'Storage image'\" loading=\"lazy\" />\n </div>\n <div class=\"image-info\">\n <p class=\"image-name\" [title]=\"image.name\">{{ image.name }}</p>\n <div class=\"image-actions\">\n <a [href]=\"image.url\" target=\"_blank\" class=\"action-btn\">View</a>\n <button (click)=\"selectImage(image)\" class=\"action-btn\">Select</button>\n </div>\n </div>\n </div>\n }\n } @else {\n <div class=\"no-images\">\n <p>No images found in the storage path: {{ storagePath }}</p>\n </div>\n }\n <dc-cropper-modal [imgStorageSettings]=\"imgStorageSettings\"></dc-cropper-modal>\n </div>\n }\n</div>\n", styles: [".image-storage-preview-container{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif;padding:20px;max-width:1200px;margin:0 auto}.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.header h2{margin:0;color:#333}.refresh-btn{background-color:#4285f4;color:#fff;border:none;border-radius:4px;padding:8px 16px;cursor:pointer;font-size:14px;transition:background-color .2s}.refresh-btn:hover{background-color:#3367d6}.refresh-btn:disabled{background-color:#a9a9a9;cursor:not-allowed}.loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 0}.spinner{border:4px solid rgba(0,0,0,.1);border-radius:50%;border-top:4px solid #4285f4;width:30px;height:30px;animation:spin 1s linear infinite;margin-bottom:15px}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.error-container{background-color:#ffebee;border:1px solid #ffcdd2;border-radius:4px;padding:16px;margin:20px 0;text-align:center}.error-message{color:#d32f2f;margin-bottom:10px}.error-container button{background-color:#d32f2f;color:#fff;border:none;border-radius:4px;padding:8px 16px;cursor:pointer;font-size:14px}.images-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:20px}.image-card{border-radius:8px;overflow:hidden;box-shadow:0 2px 10px #0000001a;transition:transform .2s,box-shadow .2s;background-color:#fff}.image-card:hover{transform:translateY(-5px);box-shadow:0 5px 15px #00000026}.image-container{height:200px;overflow:hidden;position:relative;background-color:#f5f5f5}.image-container img{width:100%;height:100%;object-fit:cover;transition:transform .3s}.image-card:hover .image-container img{transform:scale(1.05)}.image-info{padding:15px}.image-name{margin:0 0 10px;font-size:14px;font-weight:500;color:#333;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.image-actions{display:flex;justify-content:flex-end}.action-btn{background-color:#4285f4;color:#fff;text-decoration:none;border-radius:4px;padding:6px 12px;font-size:12px;transition:background-color .2s}.action-btn:hover{background-color:#3367d6}.no-images{grid-column:1 / -1;text-align:center;padding:40px 0;color:#757575;background-color:#f5f5f5;border-radius:8px}\n"] }]
512
+ }], ctorParameters: () => [] });
513
+
514
+ class SimpleUploaderComponent {
515
+ constructor() {
516
+ this.multiImagesStorageService = inject(MultiImagesStorageService);
517
+ this.storagePath = input.required();
518
+ this.buttonLabel = input('Upload File');
519
+ this.accept = input('*/*');
520
+ this.disabled = input(false);
521
+ this.fileUploaded = output();
522
+ this.uploadError = output();
523
+ this.isLoading = signal(false);
524
+ this.fileInputId = `file-upload-${Math.random().toString(36).substring(2, 11)}`;
525
+ }
526
+ triggerFileInputClick(fileInput) {
527
+ fileInput.click();
528
+ }
529
+ async onFileSelected(event) {
530
+ const fileInput = event.target;
531
+ const file = fileInput.files?.[0];
532
+ if (file) {
533
+ this.isLoading.set(true);
534
+ try {
535
+ const result = await this.multiImagesStorageService.uploadGenericFile(file, this.storagePath());
536
+ if (result) {
537
+ this.fileUploaded.emit(result);
538
+ }
539
+ else {
540
+ // This case might occur if the service's uploadGenericFile returns null without throwing an error
541
+ this.uploadError.emit({ error: 'Upload failed and no specific error was provided by the service.' });
542
+ }
543
+ }
544
+ catch (error) {
545
+ console.error('Error during file upload in SimpleUploaderComponent:', error);
546
+ this.uploadError.emit(error);
547
+ }
548
+ finally {
549
+ this.isLoading.set(false);
550
+ // Reset file input to allow uploading the same file again if needed
551
+ fileInput.value = '';
552
+ }
553
+ }
554
+ }
555
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SimpleUploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
556
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.4", type: SimpleUploaderComponent, isStandalone: true, selector: "dc-simple-uploader", inputs: { storagePath: { classPropertyName: "storagePath", publicName: "storagePath", isSignal: true, isRequired: true, transformFunction: null }, buttonLabel: { classPropertyName: "buttonLabel", publicName: "buttonLabel", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileUploaded: "fileUploaded", uploadError: "uploadError" }, ngImport: i0, template: "<input #fileInput type=\"file\" [id]=\"fileInputId\" (change)=\"onFileSelected($event)\" [accept]=\"accept()\" style=\"display: none\" />\n<p-button\n [label]=\"buttonLabel()\"\n (click)=\"triggerFileInputClick(fileInput)\"\n [loading]=\"isLoading()\"\n [disabled]=\"disabled() || isLoading()\"\n icon=\"pi pi-upload\"\n iconPos=\"left\"></p-button>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }] }); }
557
+ }
558
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SimpleUploaderComponent, decorators: [{
559
+ type: Component,
560
+ args: [{ selector: 'dc-simple-uploader', standalone: true, imports: [CommonModule, FormsModule, ButtonModule], template: "<input #fileInput type=\"file\" [id]=\"fileInputId\" (change)=\"onFileSelected($event)\" [accept]=\"accept()\" style=\"display: none\" />\n<p-button\n [label]=\"buttonLabel()\"\n (click)=\"triggerFileInputClick(fileInput)\"\n [loading]=\"isLoading()\"\n [disabled]=\"disabled() || isLoading()\"\n icon=\"pi pi-upload\"\n iconPos=\"left\"></p-button>\n" }]
561
+ }], ctorParameters: () => [] });
562
+
427
563
  class DCFilesCacheService {
428
- constructor(storage) {
429
- this.storage = storage;
564
+ constructor() {
565
+ this.storage = inject(Storage);
430
566
  this.files = {};
431
567
  }
432
568
  async getURLSrcFile(path) {
@@ -460,15 +596,113 @@ class DCFilesCacheService {
460
596
  const blob = await this.getBlob(url);
461
597
  return URL.createObjectURL(blob);
462
598
  }
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' }); }
599
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCFilesCacheService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
600
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCFilesCacheService, providedIn: 'root' }); }
601
+ }
602
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCFilesCacheService, decorators: [{
603
+ type: Injectable,
604
+ args: [{
605
+ providedIn: 'root',
606
+ }]
607
+ }], ctorParameters: () => [] });
608
+
609
+ class MultiObjectStorageService {
610
+ constructor() {
611
+ this.storage = inject(AngularFireStorage);
612
+ }
613
+ /**
614
+ * Uploads a Blob or File object to a specified path in Firebase Storage.
615
+ * @param objectToUpload The Blob or File to upload.
616
+ * @param path The desired storage path (e.g., 'documents/report.pdf').
617
+ * @returns A promise that resolves with the storage metadata of the uploaded object.
618
+ * @throws Throws an error if the upload fails.
619
+ */
620
+ async uploadObject(objectToUpload, path) {
621
+ try {
622
+ const refStorage = this.storage.ref(path);
623
+ const task = await refStorage.put(objectToUpload);
624
+ const { fullPath, bucket, name } = task.metadata;
625
+ const url = await lastValueFrom(refStorage.getDownloadURL());
626
+ const storageData = { url, path: fullPath, bucket, name };
627
+ return storageData;
628
+ }
629
+ catch (error) {
630
+ console.error(`Error uploading object to path "${path}": `, error);
631
+ // Re-throw the error to allow calling code to handle it
632
+ throw error;
633
+ }
634
+ }
635
+ /**
636
+ * Deletes all objects within a specified directory path in Firebase Storage.
637
+ * WARNING: Use with extreme caution as this will permanently delete all files in the directory.
638
+ * @param directoryPath The path to the directory to delete (e.g., 'users/userId/files/').
639
+ * @returns A promise that resolves when the deletion attempt is complete.
640
+ */
641
+ async deleteDirectory(directoryPath) {
642
+ const storage = getStorage();
643
+ const directoryRef = ref(storage, directoryPath);
644
+ try {
645
+ const res = await listAll(directoryRef);
646
+ const deletePromises = [];
647
+ res.items.forEach((itemRef) => {
648
+ console.log(`Deleting object: ${itemRef.fullPath}`);
649
+ deletePromises.push(deleteObject(itemRef));
650
+ });
651
+ // You might want to handle potential errors during individual deletions if needed
652
+ await Promise.all(deletePromises);
653
+ console.log(`Successfully deleted objects in directory: ${directoryPath}`);
654
+ }
655
+ catch (error) {
656
+ console.error(`Error deleting objects in directory "${directoryPath}":`, error);
657
+ // Optionally re-throw or handle the error appropriately
658
+ throw error;
659
+ }
660
+ }
661
+ /**
662
+ * Deletes a single object from Firebase Storage based on its full path.
663
+ * @param objectPath The full path of the object to delete (e.g., 'images/profile.jpg').
664
+ * @returns A promise that resolves when the deletion is complete.
665
+ */
666
+ async deleteObjectByPath(objectPath) {
667
+ const storageRef = this.storage.ref(objectPath);
668
+ try {
669
+ await lastValueFrom(storageRef.delete());
670
+ console.log(`Object deleted successfully: ${objectPath}`);
671
+ }
672
+ catch (error) {
673
+ console.error(`Error deleting object at path "${objectPath}":`, error);
674
+ // Optionally re-throw or handle the error appropriately
675
+ throw error;
676
+ }
677
+ }
678
+ /**
679
+ * Private helper to get metadata after an upload task completes.
680
+ * @param task The AngularFireUploadTask.
681
+ * @returns A promise resolving with the object's storage metadata.
682
+ */
683
+ async uploadAndGetObjectMetadata(task) {
684
+ const snap = await task;
685
+ const { fullPath, bucket, name } = snap.metadata;
686
+ const storage = getStorage();
687
+ const storageRef = ref(storage, fullPath);
688
+ const url = await getDownloadURL(storageRef);
689
+ const meta = {
690
+ url,
691
+ path: fullPath, // Use fullPath for the 'path' property
692
+ bucket,
693
+ name,
694
+ };
695
+ return meta;
696
+ }
697
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MultiObjectStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
698
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MultiObjectStorageService, providedIn: 'root' }); }
465
699
  }
466
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCFilesCacheService, decorators: [{
700
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MultiObjectStorageService, decorators: [{
467
701
  type: Injectable,
468
702
  args: [{
469
703
  providedIn: 'root',
470
704
  }]
471
- }], ctorParameters: () => [{ type: i1$1.Storage }] });
705
+ }] });
472
706
 
473
707
  /*
474
708
  * Public API Surface of storage-uploader
@@ -478,5 +712,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImpor
478
712
  * Generated bundle index. Do not edit.
479
713
  */
480
714
 
481
- export { AspectRatio, AspectRatio2, AspectRatioOptions, AspectType, CropperComponent, CropperComponentModal, DCFilesCacheService, DEFAULT_SETTINGS, MultiImagesStorageService, ResolutionType };
715
+ export { AspectRatio, AspectRatio2, AspectRatioOptions, AspectType, CropperComponent, CropperComponentModal, DCFilesCacheService, DEFAULT_SETTINGS, ImageStoragePreviewComponent, MultiImagesStorageService, MultiObjectStorageService, ResolutionType, SimpleUploaderComponent, extractBucket, extractPath };
482
716
  //# sourceMappingURL=dataclouder-ngx-cloud-storage.mjs.map