valtech-components 4.0.1039 → 4.0.1041
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/components/molecules/select-input/select-input.component.mjs +9 -4
- package/esm2022/lib/services/firebase/storage.service.mjs +49 -1
- package/esm2022/lib/services/firebase/types.mjs +1 -1
- package/esm2022/lib/version.mjs +2 -2
- package/fesm2022/valtech-components.mjs +308 -258
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/atoms/rights-footer/rights-footer.component.d.ts +1 -1
- package/lib/components/molecules/features-list/features-list.component.d.ts +1 -1
- package/lib/components/molecules/load-more/load-more.component.d.ts +1 -1
- package/lib/components/molecules/operation-reference/operation-reference.component.d.ts +1 -1
- package/lib/components/molecules/phone-display/phone-display.component.d.ts +1 -1
- package/lib/components/molecules/select-input/select-input.component.d.ts +1 -0
- package/lib/components/molecules/username-input/username-input.component.d.ts +1 -1
- package/lib/components/organisms/article/article.component.d.ts +4 -4
- package/lib/components/organisms/landing-steps/landing-steps.component.d.ts +1 -1
- package/lib/components/organisms/member-import-modal/member-import-modal.component.d.ts +1 -1
- package/lib/components/organisms/notification-preferences-view/notification-preferences-view.component.d.ts +1 -1
- package/lib/services/firebase/storage.service.d.ts +11 -1
- package/lib/services/firebase/types.d.ts +34 -0
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -70,7 +70,7 @@ import fixWebmDuration from 'fix-webm-duration';
|
|
|
70
70
|
* Current version of valtech-components.
|
|
71
71
|
* This is automatically updated during the publish process.
|
|
72
72
|
*/
|
|
73
|
-
const VERSION = '4.0.
|
|
73
|
+
const VERSION = '4.0.1041';
|
|
74
74
|
|
|
75
75
|
function evaluateValtechAccess(rule, context, features = {}, visitedFeatures = new Set()) {
|
|
76
76
|
if (rule == null)
|
|
@@ -5371,6 +5371,260 @@ function query() {
|
|
|
5371
5371
|
return new QueryBuilder();
|
|
5372
5372
|
}
|
|
5373
5373
|
|
|
5374
|
+
/**
|
|
5375
|
+
* Default values for image processing
|
|
5376
|
+
*/
|
|
5377
|
+
const IMAGE_DEFAULTS = {
|
|
5378
|
+
maxWidth: 800,
|
|
5379
|
+
maxHeight: 800,
|
|
5380
|
+
quality: 0.8,
|
|
5381
|
+
mimeType: 'image/jpeg',
|
|
5382
|
+
maxSize: 10 * 1024 * 1024, // 10MB
|
|
5383
|
+
allowedTypes: ['image/jpeg', 'image/png', 'image/webp', 'image/gif'],
|
|
5384
|
+
thumbnailSize: 150,
|
|
5385
|
+
};
|
|
5386
|
+
|
|
5387
|
+
/**
|
|
5388
|
+
* ImageService
|
|
5389
|
+
*
|
|
5390
|
+
* Service for image processing including compression, thumbnails, cropping and validation.
|
|
5391
|
+
* Uses HTML Canvas for all operations - no external dependencies.
|
|
5392
|
+
*
|
|
5393
|
+
* @example
|
|
5394
|
+
* ```typescript
|
|
5395
|
+
* const imageService = inject(ImageService);
|
|
5396
|
+
*
|
|
5397
|
+
* // Compress an image
|
|
5398
|
+
* const compressed = await imageService.compress(file, { maxWidth: 800, quality: 0.8 });
|
|
5399
|
+
*
|
|
5400
|
+
* // Generate thumbnail
|
|
5401
|
+
* const thumb = await imageService.thumbnail(file, 150);
|
|
5402
|
+
*
|
|
5403
|
+
* // Validate before processing
|
|
5404
|
+
* const validation = imageService.validate(file, { maxSize: 5 * 1024 * 1024 });
|
|
5405
|
+
* if (!validation.valid) {
|
|
5406
|
+
* console.error(validation.message);
|
|
5407
|
+
* }
|
|
5408
|
+
* ```
|
|
5409
|
+
*/
|
|
5410
|
+
class ImageService {
|
|
5411
|
+
/**
|
|
5412
|
+
* Compress an image maintaining aspect ratio
|
|
5413
|
+
* @param file - File or Blob to compress
|
|
5414
|
+
* @param options - Compression options
|
|
5415
|
+
* @returns Promise with processed image data
|
|
5416
|
+
*/
|
|
5417
|
+
async compress(file, options) {
|
|
5418
|
+
const opts = {
|
|
5419
|
+
maxWidth: options?.maxWidth ?? IMAGE_DEFAULTS.maxWidth,
|
|
5420
|
+
maxHeight: options?.maxHeight ?? IMAGE_DEFAULTS.maxHeight,
|
|
5421
|
+
quality: options?.quality ?? IMAGE_DEFAULTS.quality,
|
|
5422
|
+
mimeType: options?.mimeType ?? IMAGE_DEFAULTS.mimeType,
|
|
5423
|
+
};
|
|
5424
|
+
const img = await this.loadImage(file);
|
|
5425
|
+
const { width, height } = this.calculateDimensions(img.width, img.height, opts.maxWidth, opts.maxHeight);
|
|
5426
|
+
const canvas = document.createElement('canvas');
|
|
5427
|
+
canvas.width = width;
|
|
5428
|
+
canvas.height = height;
|
|
5429
|
+
const ctx = canvas.getContext('2d');
|
|
5430
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
5431
|
+
const blob = await this.canvasToBlob(canvas, opts.mimeType, opts.quality);
|
|
5432
|
+
const dataUrl = canvas.toDataURL(opts.mimeType, opts.quality);
|
|
5433
|
+
return {
|
|
5434
|
+
blob,
|
|
5435
|
+
dataUrl,
|
|
5436
|
+
width,
|
|
5437
|
+
height,
|
|
5438
|
+
size: blob.size,
|
|
5439
|
+
};
|
|
5440
|
+
}
|
|
5441
|
+
/**
|
|
5442
|
+
* Generate a square thumbnail from an image
|
|
5443
|
+
* @param file - File or Blob to process
|
|
5444
|
+
* @param size - Thumbnail size in pixels (default: 150)
|
|
5445
|
+
* @returns Promise with processed thumbnail
|
|
5446
|
+
*/
|
|
5447
|
+
async thumbnail(file, size) {
|
|
5448
|
+
const thumbSize = size ?? IMAGE_DEFAULTS.thumbnailSize;
|
|
5449
|
+
const img = await this.loadImage(file);
|
|
5450
|
+
// Calculate square crop from center
|
|
5451
|
+
const minDim = Math.min(img.width, img.height);
|
|
5452
|
+
const cropX = (img.width - minDim) / 2;
|
|
5453
|
+
const cropY = (img.height - minDim) / 2;
|
|
5454
|
+
const canvas = document.createElement('canvas');
|
|
5455
|
+
canvas.width = thumbSize;
|
|
5456
|
+
canvas.height = thumbSize;
|
|
5457
|
+
const ctx = canvas.getContext('2d');
|
|
5458
|
+
ctx.drawImage(img, cropX, cropY, minDim, minDim, 0, 0, thumbSize, thumbSize);
|
|
5459
|
+
const blob = await this.canvasToBlob(canvas, IMAGE_DEFAULTS.mimeType, 0.7 // Lower quality for thumbnails
|
|
5460
|
+
);
|
|
5461
|
+
const dataUrl = canvas.toDataURL(IMAGE_DEFAULTS.mimeType, 0.7);
|
|
5462
|
+
return {
|
|
5463
|
+
blob,
|
|
5464
|
+
dataUrl,
|
|
5465
|
+
width: thumbSize,
|
|
5466
|
+
height: thumbSize,
|
|
5467
|
+
size: blob.size,
|
|
5468
|
+
};
|
|
5469
|
+
}
|
|
5470
|
+
/**
|
|
5471
|
+
* Crop an image with specific coordinates
|
|
5472
|
+
* @param file - File or Blob to crop
|
|
5473
|
+
* @param cropData - Crop coordinates and dimensions
|
|
5474
|
+
* @param options - Optional compression options for output
|
|
5475
|
+
* @returns Promise with cropped image
|
|
5476
|
+
*/
|
|
5477
|
+
async crop(file, cropData, options) {
|
|
5478
|
+
const img = await this.loadImage(file);
|
|
5479
|
+
const opts = {
|
|
5480
|
+
quality: options?.quality ?? IMAGE_DEFAULTS.quality,
|
|
5481
|
+
mimeType: options?.mimeType ?? IMAGE_DEFAULTS.mimeType,
|
|
5482
|
+
};
|
|
5483
|
+
const canvas = document.createElement('canvas');
|
|
5484
|
+
canvas.width = cropData.width;
|
|
5485
|
+
canvas.height = cropData.height;
|
|
5486
|
+
const ctx = canvas.getContext('2d');
|
|
5487
|
+
ctx.drawImage(img, cropData.x, cropData.y, cropData.width, cropData.height, 0, 0, cropData.width, cropData.height);
|
|
5488
|
+
// Apply max dimensions if specified
|
|
5489
|
+
if (options?.maxWidth || options?.maxHeight) {
|
|
5490
|
+
return this.compress(await this.canvasToBlob(canvas, opts.mimeType, 1), options);
|
|
5491
|
+
}
|
|
5492
|
+
const blob = await this.canvasToBlob(canvas, opts.mimeType, opts.quality);
|
|
5493
|
+
const dataUrl = canvas.toDataURL(opts.mimeType, opts.quality);
|
|
5494
|
+
return {
|
|
5495
|
+
blob,
|
|
5496
|
+
dataUrl,
|
|
5497
|
+
width: cropData.width,
|
|
5498
|
+
height: cropData.height,
|
|
5499
|
+
size: blob.size,
|
|
5500
|
+
};
|
|
5501
|
+
}
|
|
5502
|
+
/**
|
|
5503
|
+
* Validate an image file before processing
|
|
5504
|
+
* @param file - File to validate
|
|
5505
|
+
* @param options - Validation options
|
|
5506
|
+
* @returns Validation result with error details if invalid
|
|
5507
|
+
*/
|
|
5508
|
+
validate(file, options) {
|
|
5509
|
+
const opts = {
|
|
5510
|
+
maxSize: options?.maxSize ?? IMAGE_DEFAULTS.maxSize,
|
|
5511
|
+
allowedTypes: options?.allowedTypes ?? IMAGE_DEFAULTS.allowedTypes,
|
|
5512
|
+
};
|
|
5513
|
+
// Check file type
|
|
5514
|
+
if (!opts.allowedTypes.includes(file.type)) {
|
|
5515
|
+
return {
|
|
5516
|
+
valid: false,
|
|
5517
|
+
error: 'invalidType',
|
|
5518
|
+
message: `Formato no válido. Usa: ${opts.allowedTypes.map(t => t.split('/')[1].toUpperCase()).join(', ')}`,
|
|
5519
|
+
};
|
|
5520
|
+
}
|
|
5521
|
+
// Check file size
|
|
5522
|
+
if (file.size > opts.maxSize) {
|
|
5523
|
+
const maxMB = Math.round(opts.maxSize / (1024 * 1024));
|
|
5524
|
+
return {
|
|
5525
|
+
valid: false,
|
|
5526
|
+
error: 'fileTooLarge',
|
|
5527
|
+
message: `La imagen es muy grande. Máximo ${maxMB}MB`,
|
|
5528
|
+
};
|
|
5529
|
+
}
|
|
5530
|
+
return { valid: true };
|
|
5531
|
+
}
|
|
5532
|
+
/**
|
|
5533
|
+
* Validate image dimensions (async - requires loading image)
|
|
5534
|
+
* @param file - File to validate
|
|
5535
|
+
* @param options - Validation options with minWidth/minHeight
|
|
5536
|
+
* @returns Promise with validation result
|
|
5537
|
+
*/
|
|
5538
|
+
async validateDimensions(file, options) {
|
|
5539
|
+
const img = await this.loadImage(file);
|
|
5540
|
+
if (options.minWidth && img.width < options.minWidth) {
|
|
5541
|
+
return {
|
|
5542
|
+
valid: false,
|
|
5543
|
+
error: 'imageTooSmall',
|
|
5544
|
+
message: `La imagen debe tener al menos ${options.minWidth}px de ancho`,
|
|
5545
|
+
};
|
|
5546
|
+
}
|
|
5547
|
+
if (options.minHeight && img.height < options.minHeight) {
|
|
5548
|
+
return {
|
|
5549
|
+
valid: false,
|
|
5550
|
+
error: 'imageTooSmall',
|
|
5551
|
+
message: `La imagen debe tener al menos ${options.minHeight}px de alto`,
|
|
5552
|
+
};
|
|
5553
|
+
}
|
|
5554
|
+
return { valid: true };
|
|
5555
|
+
}
|
|
5556
|
+
/**
|
|
5557
|
+
* Convert a Blob/File to a data URL
|
|
5558
|
+
*/
|
|
5559
|
+
async toDataUrl(file) {
|
|
5560
|
+
return new Promise((resolve, reject) => {
|
|
5561
|
+
const reader = new FileReader();
|
|
5562
|
+
reader.onload = () => resolve(reader.result);
|
|
5563
|
+
reader.onerror = reject;
|
|
5564
|
+
reader.readAsDataURL(file);
|
|
5565
|
+
});
|
|
5566
|
+
}
|
|
5567
|
+
/**
|
|
5568
|
+
* Convert a data URL to a Blob
|
|
5569
|
+
*/
|
|
5570
|
+
dataUrlToBlob(dataUrl) {
|
|
5571
|
+
const arr = dataUrl.split(',');
|
|
5572
|
+
const mime = arr[0].match(/:(.*?);/)[1];
|
|
5573
|
+
const bstr = atob(arr[1]);
|
|
5574
|
+
let n = bstr.length;
|
|
5575
|
+
const u8arr = new Uint8Array(n);
|
|
5576
|
+
while (n--) {
|
|
5577
|
+
u8arr[n] = bstr.charCodeAt(n);
|
|
5578
|
+
}
|
|
5579
|
+
return new Blob([u8arr], { type: mime });
|
|
5580
|
+
}
|
|
5581
|
+
// ============== Private Helpers ==============
|
|
5582
|
+
loadImage(file) {
|
|
5583
|
+
return new Promise((resolve, reject) => {
|
|
5584
|
+
const img = new Image();
|
|
5585
|
+
img.onload = () => {
|
|
5586
|
+
URL.revokeObjectURL(img.src);
|
|
5587
|
+
resolve(img);
|
|
5588
|
+
};
|
|
5589
|
+
img.onerror = reject;
|
|
5590
|
+
img.src = URL.createObjectURL(file);
|
|
5591
|
+
});
|
|
5592
|
+
}
|
|
5593
|
+
calculateDimensions(originalWidth, originalHeight, maxWidth, maxHeight) {
|
|
5594
|
+
let width = originalWidth;
|
|
5595
|
+
let height = originalHeight;
|
|
5596
|
+
// Scale down if necessary, maintaining aspect ratio
|
|
5597
|
+
if (width > maxWidth) {
|
|
5598
|
+
height = (height * maxWidth) / width;
|
|
5599
|
+
width = maxWidth;
|
|
5600
|
+
}
|
|
5601
|
+
if (height > maxHeight) {
|
|
5602
|
+
width = (width * maxHeight) / height;
|
|
5603
|
+
height = maxHeight;
|
|
5604
|
+
}
|
|
5605
|
+
return {
|
|
5606
|
+
width: Math.round(width),
|
|
5607
|
+
height: Math.round(height),
|
|
5608
|
+
};
|
|
5609
|
+
}
|
|
5610
|
+
canvasToBlob(canvas, mimeType, quality) {
|
|
5611
|
+
return new Promise((resolve, reject) => {
|
|
5612
|
+
canvas.toBlob((blob) => {
|
|
5613
|
+
if (blob)
|
|
5614
|
+
resolve(blob);
|
|
5615
|
+
else
|
|
5616
|
+
reject(new Error('Failed to create blob from canvas'));
|
|
5617
|
+
}, mimeType, quality);
|
|
5618
|
+
});
|
|
5619
|
+
}
|
|
5620
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5621
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageService, providedIn: 'root' }); }
|
|
5622
|
+
}
|
|
5623
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageService, decorators: [{
|
|
5624
|
+
type: Injectable,
|
|
5625
|
+
args: [{ providedIn: 'root' }]
|
|
5626
|
+
}] });
|
|
5627
|
+
|
|
5374
5628
|
/**
|
|
5375
5629
|
* Storage Service
|
|
5376
5630
|
*
|
|
@@ -5409,6 +5663,7 @@ class StorageService {
|
|
|
5409
5663
|
constructor(storage) {
|
|
5410
5664
|
this.storage = storage;
|
|
5411
5665
|
this.config = inject(VALTECH_FIREBASE_CONFIG, { optional: true });
|
|
5666
|
+
this.imageService = inject(ImageService);
|
|
5412
5667
|
}
|
|
5413
5668
|
/**
|
|
5414
5669
|
* Prefija el path de storage con el appId si está configurado.
|
|
@@ -5654,6 +5909,52 @@ class StorageService {
|
|
|
5654
5909
|
}
|
|
5655
5910
|
return sdkResult;
|
|
5656
5911
|
}
|
|
5912
|
+
/**
|
|
5913
|
+
* Procesa y sube una imagen.
|
|
5914
|
+
*
|
|
5915
|
+
* A diferencia de `uploadAndGetUrl`, este método es específico para imágenes:
|
|
5916
|
+
* comprime por defecto con `ImageService` y puede generar un thumbnail en un
|
|
5917
|
+
* path separado. Para archivos que deben conservarse byte a byte, usar
|
|
5918
|
+
* `uploadAndGetUrl`.
|
|
5919
|
+
*/
|
|
5920
|
+
async uploadImageAndGetUrl(path, file, options) {
|
|
5921
|
+
const { compress = true, compression, thumbnailPath, thumbnailSize, thumbnailMetadata, ...metadata } = options ?? {};
|
|
5922
|
+
const processed = compress
|
|
5923
|
+
? await this.imageService.compress(file, compression)
|
|
5924
|
+
: {
|
|
5925
|
+
blob: await this.materialize(file),
|
|
5926
|
+
width: 0,
|
|
5927
|
+
height: 0,
|
|
5928
|
+
size: file.size,
|
|
5929
|
+
};
|
|
5930
|
+
const imageContentType = compress
|
|
5931
|
+
? (compression?.mimeType ?? IMAGE_DEFAULTS.mimeType)
|
|
5932
|
+
: (metadata.contentType ?? file.type ?? 'application/octet-stream');
|
|
5933
|
+
const result = await this.uploadAndGetUrl(path, processed.blob, {
|
|
5934
|
+
...metadata,
|
|
5935
|
+
contentType: imageContentType,
|
|
5936
|
+
});
|
|
5937
|
+
let thumbnail;
|
|
5938
|
+
if (thumbnailPath) {
|
|
5939
|
+
const thumb = await this.imageService.thumbnail(processed.blob, thumbnailSize);
|
|
5940
|
+
thumbnail = await this.uploadAndGetUrl(thumbnailPath, thumb.blob, {
|
|
5941
|
+
...metadata,
|
|
5942
|
+
...thumbnailMetadata,
|
|
5943
|
+
contentType: thumbnailMetadata?.contentType ?? IMAGE_DEFAULTS.mimeType,
|
|
5944
|
+
});
|
|
5945
|
+
}
|
|
5946
|
+
return {
|
|
5947
|
+
...result,
|
|
5948
|
+
thumbnail,
|
|
5949
|
+
processed: {
|
|
5950
|
+
originalSize: file.size,
|
|
5951
|
+
size: processed.blob.size,
|
|
5952
|
+
width: processed.width,
|
|
5953
|
+
height: processed.height,
|
|
5954
|
+
compressed: compress,
|
|
5955
|
+
},
|
|
5956
|
+
};
|
|
5957
|
+
}
|
|
5657
5958
|
/**
|
|
5658
5959
|
* Sube un archivo desde una Data URL (base64).
|
|
5659
5960
|
*
|
|
@@ -22698,6 +22999,7 @@ class SearchSelectorComponent {
|
|
|
22698
22999
|
* Resolved props after merging preset + explicit props.
|
|
22699
23000
|
*/
|
|
22700
23001
|
this.resolvedProps = {};
|
|
23002
|
+
addIcons({ chevronDownOutline });
|
|
22701
23003
|
}
|
|
22702
23004
|
ngOnInit() {
|
|
22703
23005
|
this.resolveProps();
|
|
@@ -22766,6 +23068,7 @@ class SearchSelectorComponent {
|
|
|
22766
23068
|
[interfaceOptions]="customPopoverOptions"
|
|
22767
23069
|
[interface]="resolvedProps.selectInterface || 'popover'"
|
|
22768
23070
|
[placeholder]="resolvedProps.placeholder"
|
|
23071
|
+
toggleIcon="chevron-down-outline"
|
|
22769
23072
|
[cancelText]="cancelText"
|
|
22770
23073
|
[okText]="okText"
|
|
22771
23074
|
>
|
|
@@ -22791,7 +23094,7 @@ class SearchSelectorComponent {
|
|
|
22791
23094
|
</ion-select-option>
|
|
22792
23095
|
}
|
|
22793
23096
|
</ion-select>
|
|
22794
|
-
`, isInline: true, styles: ["@charset \"UTF-8\";:root{--val-container-sm: 540px;--val-container-md: 720px;--val-container-lg: 880px;--val-container-xl: 1100px;--val-container-xl-wide: 1280px;--val-container-md-wide: 900px;--val-container-aside: 0px;--val-container-padding: 16px;--val-radius-xs: 10px;--val-radius-sm: 16px;--val-radius-md: 20px;--val-radius-lg: 28px;--val-radius-xl: 36px;--val-radius-full: 999px;--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #737478;--ion-color-medium-rgb: 115,116,120;--ion-color-medium-contrast: #ffffff;--ion-color-medium-contrast-rgb: 255,255,255;--ion-color-medium-shade: #65666a;--ion-color-medium-tint: #818286;--ion-color-warning: #ffde38;--ion-color-warning-rgb: 255, 222, 56;--ion-color-warning-contrast: #000000;--ion-color-warning-contrast-rgb: 0, 0, 0;--ion-color-warning-shade: #e0c331;--ion-color-warning-tint: #ffe14c;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-dark)}body.dark,html.ion-palette-dark,body[data-theme=dark]{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}ion-select{--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;min-height:auto;border:.0625rem solid var(--ion-color-medium);border-radius:1.5rem;margin-top:.375rem;padding-top:.5rem!important;padding-bottom:.5rem!important;padding-inline-start:1rem!important;padding-inline-end:.
|
|
23097
|
+
`, isInline: true, styles: ["@charset \"UTF-8\";:root{--val-container-sm: 540px;--val-container-md: 720px;--val-container-lg: 880px;--val-container-xl: 1100px;--val-container-xl-wide: 1280px;--val-container-md-wide: 900px;--val-container-aside: 0px;--val-container-padding: 16px;--val-radius-xs: 10px;--val-radius-sm: 16px;--val-radius-md: 20px;--val-radius-lg: 28px;--val-radius-xl: 36px;--val-radius-full: 999px;--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #737478;--ion-color-medium-rgb: 115,116,120;--ion-color-medium-contrast: #ffffff;--ion-color-medium-contrast-rgb: 255,255,255;--ion-color-medium-shade: #65666a;--ion-color-medium-tint: #818286;--ion-color-warning: #ffde38;--ion-color-warning-rgb: 255, 222, 56;--ion-color-warning-contrast: #000000;--ion-color-warning-contrast-rgb: 0, 0, 0;--ion-color-warning-shade: #e0c331;--ion-color-warning-tint: #ffe14c;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-dark)}body.dark,html.ion-palette-dark,body[data-theme=dark]{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}ion-select{--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;min-height:auto;border:.0625rem solid var(--ion-color-medium);border-radius:1.5rem;margin-top:.375rem;padding-top:.5rem!important;padding-bottom:.5rem!important;padding-inline-start:1rem!important;padding-inline-end:.875rem!important}.select-option-content{display:inline-flex;align-items:center;gap:.625rem}.select-option-icon{flex:0 0 auto;font-size:1.125rem}.select-option-icon-mask{width:1.125rem;height:1.125rem;background:currentColor;mask:var(--val-select-option-icon-src) center/contain no-repeat;-webkit-mask:var(--val-select-option-icon-src) center/contain no-repeat}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$8.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonSelect, selector: "ion-select", inputs: ["cancelText", "color", "compareWith", "disabled", "errorText", "expandedIcon", "fill", "helperText", "interface", "interfaceOptions", "justify", "label", "labelPlacement", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "shape", "toggleIcon", "value"] }, { kind: "component", type: IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }] }); }
|
|
22795
23098
|
}
|
|
22796
23099
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SearchSelectorComponent, decorators: [{
|
|
22797
23100
|
type: Component,
|
|
@@ -22802,6 +23105,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
22802
23105
|
[interfaceOptions]="customPopoverOptions"
|
|
22803
23106
|
[interface]="resolvedProps.selectInterface || 'popover'"
|
|
22804
23107
|
[placeholder]="resolvedProps.placeholder"
|
|
23108
|
+
toggleIcon="chevron-down-outline"
|
|
22805
23109
|
[cancelText]="cancelText"
|
|
22806
23110
|
[okText]="okText"
|
|
22807
23111
|
>
|
|
@@ -22827,8 +23131,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
22827
23131
|
</ion-select-option>
|
|
22828
23132
|
}
|
|
22829
23133
|
</ion-select>
|
|
22830
|
-
`, styles: ["@charset \"UTF-8\";:root{--val-container-sm: 540px;--val-container-md: 720px;--val-container-lg: 880px;--val-container-xl: 1100px;--val-container-xl-wide: 1280px;--val-container-md-wide: 900px;--val-container-aside: 0px;--val-container-padding: 16px;--val-radius-xs: 10px;--val-radius-sm: 16px;--val-radius-md: 20px;--val-radius-lg: 28px;--val-radius-xl: 36px;--val-radius-full: 999px;--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #737478;--ion-color-medium-rgb: 115,116,120;--ion-color-medium-contrast: #ffffff;--ion-color-medium-contrast-rgb: 255,255,255;--ion-color-medium-shade: #65666a;--ion-color-medium-tint: #818286;--ion-color-warning: #ffde38;--ion-color-warning-rgb: 255, 222, 56;--ion-color-warning-contrast: #000000;--ion-color-warning-contrast-rgb: 0, 0, 0;--ion-color-warning-shade: #e0c331;--ion-color-warning-tint: #ffe14c;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-dark)}body.dark,html.ion-palette-dark,body[data-theme=dark]{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}ion-select{--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;min-height:auto;border:.0625rem solid var(--ion-color-medium);border-radius:1.5rem;margin-top:.375rem;padding-top:.5rem!important;padding-bottom:.5rem!important;padding-inline-start:1rem!important;padding-inline-end:.
|
|
22831
|
-
}], propDecorators: { preset: [{
|
|
23134
|
+
`, styles: ["@charset \"UTF-8\";:root{--val-container-sm: 540px;--val-container-md: 720px;--val-container-lg: 880px;--val-container-xl: 1100px;--val-container-xl-wide: 1280px;--val-container-md-wide: 900px;--val-container-aside: 0px;--val-container-padding: 16px;--val-radius-xs: 10px;--val-radius-sm: 16px;--val-radius-md: 20px;--val-radius-lg: 28px;--val-radius-xl: 36px;--val-radius-full: 999px;--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #737478;--ion-color-medium-rgb: 115,116,120;--ion-color-medium-contrast: #ffffff;--ion-color-medium-contrast-rgb: 255,255,255;--ion-color-medium-shade: #65666a;--ion-color-medium-tint: #818286;--ion-color-warning: #ffde38;--ion-color-warning-rgb: 255, 222, 56;--ion-color-warning-contrast: #000000;--ion-color-warning-contrast-rgb: 0, 0, 0;--ion-color-warning-shade: #e0c331;--ion-color-warning-tint: #ffe14c;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-dark)}body.dark,html.ion-palette-dark,body[data-theme=dark]{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}ion-select{--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;min-height:auto;border:.0625rem solid var(--ion-color-medium);border-radius:1.5rem;margin-top:.375rem;padding-top:.5rem!important;padding-bottom:.5rem!important;padding-inline-start:1rem!important;padding-inline-end:.875rem!important}.select-option-content{display:inline-flex;align-items:center;gap:.625rem}.select-option-icon{flex:0 0 auto;font-size:1.125rem}.select-option-icon-mask{width:1.125rem;height:1.125rem;background:currentColor;mask:var(--val-select-option-icon-src) center/contain no-repeat;-webkit-mask:var(--val-select-option-icon-src) center/contain no-repeat}\n"] }]
|
|
23135
|
+
}], ctorParameters: () => [], propDecorators: { preset: [{
|
|
22832
23136
|
type: Input
|
|
22833
23137
|
}], props: [{
|
|
22834
23138
|
type: Input
|
|
@@ -37939,260 +38243,6 @@ const DEFAULT_FEEDBACK_TYPE_OPTIONS = [
|
|
|
37939
38243
|
*/
|
|
37940
38244
|
// Configuration
|
|
37941
38245
|
|
|
37942
|
-
/**
|
|
37943
|
-
* Default values for image processing
|
|
37944
|
-
*/
|
|
37945
|
-
const IMAGE_DEFAULTS = {
|
|
37946
|
-
maxWidth: 800,
|
|
37947
|
-
maxHeight: 800,
|
|
37948
|
-
quality: 0.8,
|
|
37949
|
-
mimeType: 'image/jpeg',
|
|
37950
|
-
maxSize: 10 * 1024 * 1024, // 10MB
|
|
37951
|
-
allowedTypes: ['image/jpeg', 'image/png', 'image/webp', 'image/gif'],
|
|
37952
|
-
thumbnailSize: 150,
|
|
37953
|
-
};
|
|
37954
|
-
|
|
37955
|
-
/**
|
|
37956
|
-
* ImageService
|
|
37957
|
-
*
|
|
37958
|
-
* Service for image processing including compression, thumbnails, cropping and validation.
|
|
37959
|
-
* Uses HTML Canvas for all operations - no external dependencies.
|
|
37960
|
-
*
|
|
37961
|
-
* @example
|
|
37962
|
-
* ```typescript
|
|
37963
|
-
* const imageService = inject(ImageService);
|
|
37964
|
-
*
|
|
37965
|
-
* // Compress an image
|
|
37966
|
-
* const compressed = await imageService.compress(file, { maxWidth: 800, quality: 0.8 });
|
|
37967
|
-
*
|
|
37968
|
-
* // Generate thumbnail
|
|
37969
|
-
* const thumb = await imageService.thumbnail(file, 150);
|
|
37970
|
-
*
|
|
37971
|
-
* // Validate before processing
|
|
37972
|
-
* const validation = imageService.validate(file, { maxSize: 5 * 1024 * 1024 });
|
|
37973
|
-
* if (!validation.valid) {
|
|
37974
|
-
* console.error(validation.message);
|
|
37975
|
-
* }
|
|
37976
|
-
* ```
|
|
37977
|
-
*/
|
|
37978
|
-
class ImageService {
|
|
37979
|
-
/**
|
|
37980
|
-
* Compress an image maintaining aspect ratio
|
|
37981
|
-
* @param file - File or Blob to compress
|
|
37982
|
-
* @param options - Compression options
|
|
37983
|
-
* @returns Promise with processed image data
|
|
37984
|
-
*/
|
|
37985
|
-
async compress(file, options) {
|
|
37986
|
-
const opts = {
|
|
37987
|
-
maxWidth: options?.maxWidth ?? IMAGE_DEFAULTS.maxWidth,
|
|
37988
|
-
maxHeight: options?.maxHeight ?? IMAGE_DEFAULTS.maxHeight,
|
|
37989
|
-
quality: options?.quality ?? IMAGE_DEFAULTS.quality,
|
|
37990
|
-
mimeType: options?.mimeType ?? IMAGE_DEFAULTS.mimeType,
|
|
37991
|
-
};
|
|
37992
|
-
const img = await this.loadImage(file);
|
|
37993
|
-
const { width, height } = this.calculateDimensions(img.width, img.height, opts.maxWidth, opts.maxHeight);
|
|
37994
|
-
const canvas = document.createElement('canvas');
|
|
37995
|
-
canvas.width = width;
|
|
37996
|
-
canvas.height = height;
|
|
37997
|
-
const ctx = canvas.getContext('2d');
|
|
37998
|
-
ctx.drawImage(img, 0, 0, width, height);
|
|
37999
|
-
const blob = await this.canvasToBlob(canvas, opts.mimeType, opts.quality);
|
|
38000
|
-
const dataUrl = canvas.toDataURL(opts.mimeType, opts.quality);
|
|
38001
|
-
return {
|
|
38002
|
-
blob,
|
|
38003
|
-
dataUrl,
|
|
38004
|
-
width,
|
|
38005
|
-
height,
|
|
38006
|
-
size: blob.size,
|
|
38007
|
-
};
|
|
38008
|
-
}
|
|
38009
|
-
/**
|
|
38010
|
-
* Generate a square thumbnail from an image
|
|
38011
|
-
* @param file - File or Blob to process
|
|
38012
|
-
* @param size - Thumbnail size in pixels (default: 150)
|
|
38013
|
-
* @returns Promise with processed thumbnail
|
|
38014
|
-
*/
|
|
38015
|
-
async thumbnail(file, size) {
|
|
38016
|
-
const thumbSize = size ?? IMAGE_DEFAULTS.thumbnailSize;
|
|
38017
|
-
const img = await this.loadImage(file);
|
|
38018
|
-
// Calculate square crop from center
|
|
38019
|
-
const minDim = Math.min(img.width, img.height);
|
|
38020
|
-
const cropX = (img.width - minDim) / 2;
|
|
38021
|
-
const cropY = (img.height - minDim) / 2;
|
|
38022
|
-
const canvas = document.createElement('canvas');
|
|
38023
|
-
canvas.width = thumbSize;
|
|
38024
|
-
canvas.height = thumbSize;
|
|
38025
|
-
const ctx = canvas.getContext('2d');
|
|
38026
|
-
ctx.drawImage(img, cropX, cropY, minDim, minDim, 0, 0, thumbSize, thumbSize);
|
|
38027
|
-
const blob = await this.canvasToBlob(canvas, IMAGE_DEFAULTS.mimeType, 0.7 // Lower quality for thumbnails
|
|
38028
|
-
);
|
|
38029
|
-
const dataUrl = canvas.toDataURL(IMAGE_DEFAULTS.mimeType, 0.7);
|
|
38030
|
-
return {
|
|
38031
|
-
blob,
|
|
38032
|
-
dataUrl,
|
|
38033
|
-
width: thumbSize,
|
|
38034
|
-
height: thumbSize,
|
|
38035
|
-
size: blob.size,
|
|
38036
|
-
};
|
|
38037
|
-
}
|
|
38038
|
-
/**
|
|
38039
|
-
* Crop an image with specific coordinates
|
|
38040
|
-
* @param file - File or Blob to crop
|
|
38041
|
-
* @param cropData - Crop coordinates and dimensions
|
|
38042
|
-
* @param options - Optional compression options for output
|
|
38043
|
-
* @returns Promise with cropped image
|
|
38044
|
-
*/
|
|
38045
|
-
async crop(file, cropData, options) {
|
|
38046
|
-
const img = await this.loadImage(file);
|
|
38047
|
-
const opts = {
|
|
38048
|
-
quality: options?.quality ?? IMAGE_DEFAULTS.quality,
|
|
38049
|
-
mimeType: options?.mimeType ?? IMAGE_DEFAULTS.mimeType,
|
|
38050
|
-
};
|
|
38051
|
-
const canvas = document.createElement('canvas');
|
|
38052
|
-
canvas.width = cropData.width;
|
|
38053
|
-
canvas.height = cropData.height;
|
|
38054
|
-
const ctx = canvas.getContext('2d');
|
|
38055
|
-
ctx.drawImage(img, cropData.x, cropData.y, cropData.width, cropData.height, 0, 0, cropData.width, cropData.height);
|
|
38056
|
-
// Apply max dimensions if specified
|
|
38057
|
-
if (options?.maxWidth || options?.maxHeight) {
|
|
38058
|
-
return this.compress(await this.canvasToBlob(canvas, opts.mimeType, 1), options);
|
|
38059
|
-
}
|
|
38060
|
-
const blob = await this.canvasToBlob(canvas, opts.mimeType, opts.quality);
|
|
38061
|
-
const dataUrl = canvas.toDataURL(opts.mimeType, opts.quality);
|
|
38062
|
-
return {
|
|
38063
|
-
blob,
|
|
38064
|
-
dataUrl,
|
|
38065
|
-
width: cropData.width,
|
|
38066
|
-
height: cropData.height,
|
|
38067
|
-
size: blob.size,
|
|
38068
|
-
};
|
|
38069
|
-
}
|
|
38070
|
-
/**
|
|
38071
|
-
* Validate an image file before processing
|
|
38072
|
-
* @param file - File to validate
|
|
38073
|
-
* @param options - Validation options
|
|
38074
|
-
* @returns Validation result with error details if invalid
|
|
38075
|
-
*/
|
|
38076
|
-
validate(file, options) {
|
|
38077
|
-
const opts = {
|
|
38078
|
-
maxSize: options?.maxSize ?? IMAGE_DEFAULTS.maxSize,
|
|
38079
|
-
allowedTypes: options?.allowedTypes ?? IMAGE_DEFAULTS.allowedTypes,
|
|
38080
|
-
};
|
|
38081
|
-
// Check file type
|
|
38082
|
-
if (!opts.allowedTypes.includes(file.type)) {
|
|
38083
|
-
return {
|
|
38084
|
-
valid: false,
|
|
38085
|
-
error: 'invalidType',
|
|
38086
|
-
message: `Formato no válido. Usa: ${opts.allowedTypes.map(t => t.split('/')[1].toUpperCase()).join(', ')}`,
|
|
38087
|
-
};
|
|
38088
|
-
}
|
|
38089
|
-
// Check file size
|
|
38090
|
-
if (file.size > opts.maxSize) {
|
|
38091
|
-
const maxMB = Math.round(opts.maxSize / (1024 * 1024));
|
|
38092
|
-
return {
|
|
38093
|
-
valid: false,
|
|
38094
|
-
error: 'fileTooLarge',
|
|
38095
|
-
message: `La imagen es muy grande. Máximo ${maxMB}MB`,
|
|
38096
|
-
};
|
|
38097
|
-
}
|
|
38098
|
-
return { valid: true };
|
|
38099
|
-
}
|
|
38100
|
-
/**
|
|
38101
|
-
* Validate image dimensions (async - requires loading image)
|
|
38102
|
-
* @param file - File to validate
|
|
38103
|
-
* @param options - Validation options with minWidth/minHeight
|
|
38104
|
-
* @returns Promise with validation result
|
|
38105
|
-
*/
|
|
38106
|
-
async validateDimensions(file, options) {
|
|
38107
|
-
const img = await this.loadImage(file);
|
|
38108
|
-
if (options.minWidth && img.width < options.minWidth) {
|
|
38109
|
-
return {
|
|
38110
|
-
valid: false,
|
|
38111
|
-
error: 'imageTooSmall',
|
|
38112
|
-
message: `La imagen debe tener al menos ${options.minWidth}px de ancho`,
|
|
38113
|
-
};
|
|
38114
|
-
}
|
|
38115
|
-
if (options.minHeight && img.height < options.minHeight) {
|
|
38116
|
-
return {
|
|
38117
|
-
valid: false,
|
|
38118
|
-
error: 'imageTooSmall',
|
|
38119
|
-
message: `La imagen debe tener al menos ${options.minHeight}px de alto`,
|
|
38120
|
-
};
|
|
38121
|
-
}
|
|
38122
|
-
return { valid: true };
|
|
38123
|
-
}
|
|
38124
|
-
/**
|
|
38125
|
-
* Convert a Blob/File to a data URL
|
|
38126
|
-
*/
|
|
38127
|
-
async toDataUrl(file) {
|
|
38128
|
-
return new Promise((resolve, reject) => {
|
|
38129
|
-
const reader = new FileReader();
|
|
38130
|
-
reader.onload = () => resolve(reader.result);
|
|
38131
|
-
reader.onerror = reject;
|
|
38132
|
-
reader.readAsDataURL(file);
|
|
38133
|
-
});
|
|
38134
|
-
}
|
|
38135
|
-
/**
|
|
38136
|
-
* Convert a data URL to a Blob
|
|
38137
|
-
*/
|
|
38138
|
-
dataUrlToBlob(dataUrl) {
|
|
38139
|
-
const arr = dataUrl.split(',');
|
|
38140
|
-
const mime = arr[0].match(/:(.*?);/)[1];
|
|
38141
|
-
const bstr = atob(arr[1]);
|
|
38142
|
-
let n = bstr.length;
|
|
38143
|
-
const u8arr = new Uint8Array(n);
|
|
38144
|
-
while (n--) {
|
|
38145
|
-
u8arr[n] = bstr.charCodeAt(n);
|
|
38146
|
-
}
|
|
38147
|
-
return new Blob([u8arr], { type: mime });
|
|
38148
|
-
}
|
|
38149
|
-
// ============== Private Helpers ==============
|
|
38150
|
-
loadImage(file) {
|
|
38151
|
-
return new Promise((resolve, reject) => {
|
|
38152
|
-
const img = new Image();
|
|
38153
|
-
img.onload = () => {
|
|
38154
|
-
URL.revokeObjectURL(img.src);
|
|
38155
|
-
resolve(img);
|
|
38156
|
-
};
|
|
38157
|
-
img.onerror = reject;
|
|
38158
|
-
img.src = URL.createObjectURL(file);
|
|
38159
|
-
});
|
|
38160
|
-
}
|
|
38161
|
-
calculateDimensions(originalWidth, originalHeight, maxWidth, maxHeight) {
|
|
38162
|
-
let width = originalWidth;
|
|
38163
|
-
let height = originalHeight;
|
|
38164
|
-
// Scale down if necessary, maintaining aspect ratio
|
|
38165
|
-
if (width > maxWidth) {
|
|
38166
|
-
height = (height * maxWidth) / width;
|
|
38167
|
-
width = maxWidth;
|
|
38168
|
-
}
|
|
38169
|
-
if (height > maxHeight) {
|
|
38170
|
-
width = (width * maxHeight) / height;
|
|
38171
|
-
height = maxHeight;
|
|
38172
|
-
}
|
|
38173
|
-
return {
|
|
38174
|
-
width: Math.round(width),
|
|
38175
|
-
height: Math.round(height),
|
|
38176
|
-
};
|
|
38177
|
-
}
|
|
38178
|
-
canvasToBlob(canvas, mimeType, quality) {
|
|
38179
|
-
return new Promise((resolve, reject) => {
|
|
38180
|
-
canvas.toBlob((blob) => {
|
|
38181
|
-
if (blob)
|
|
38182
|
-
resolve(blob);
|
|
38183
|
-
else
|
|
38184
|
-
reject(new Error('Failed to create blob from canvas'));
|
|
38185
|
-
}, mimeType, quality);
|
|
38186
|
-
});
|
|
38187
|
-
}
|
|
38188
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
38189
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageService, providedIn: 'root' }); }
|
|
38190
|
-
}
|
|
38191
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageService, decorators: [{
|
|
38192
|
-
type: Injectable,
|
|
38193
|
-
args: [{ providedIn: 'root' }]
|
|
38194
|
-
}] });
|
|
38195
|
-
|
|
38196
38246
|
class AttachmentUploaderComponent {
|
|
38197
38247
|
get readyUrls() {
|
|
38198
38248
|
return this.attachments()
|