valtech-components 2.0.680 → 2.0.681
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/templates/docs-page/docs-page.component.mjs +4 -35
- package/esm2022/lib/components/templates/docs-page/types.mjs +1 -1
- package/esm2022/lib/services/auth/auth.service.mjs +2 -11
- package/esm2022/lib/services/auth/types.mjs +1 -1
- package/esm2022/lib/version.mjs +2 -2
- package/esm2022/public-api.mjs +1 -7
- package/fesm2022/valtech-components.mjs +7 -815
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/atoms/rights-footer/rights-footer.component.d.ts +1 -1
- package/lib/components/organisms/article/article.component.d.ts +1 -1
- package/lib/components/organisms/toolbar/toolbar.component.d.ts +1 -1
- package/lib/components/templates/docs-page/docs-page.component.d.ts +0 -3
- package/lib/components/templates/docs-page/types.d.ts +0 -39
- package/lib/services/auth/auth.service.d.ts +1 -6
- package/lib/services/auth/types.d.ts +0 -18
- package/lib/version.d.ts +1 -1
- package/package.json +2 -6
- package/public-api.d.ts +0 -4
- package/esm2022/lib/components/molecules/image-crop/image-crop.component.mjs +0 -174
- package/esm2022/lib/components/molecules/image-crop/index.mjs +0 -2
- package/esm2022/lib/components/organisms/avatar-upload/avatar-upload.component.mjs +0 -345
- package/esm2022/lib/components/organisms/avatar-upload/types.mjs +0 -15
- package/esm2022/lib/services/image/image.service.mjs +0 -244
- package/esm2022/lib/services/image/index.mjs +0 -3
- package/esm2022/lib/services/image/types.mjs +0 -13
- package/lib/components/molecules/image-crop/image-crop.component.d.ts +0 -59
- package/lib/components/molecules/image-crop/index.d.ts +0 -1
- package/lib/components/organisms/avatar-upload/avatar-upload.component.d.ts +0 -82
- package/lib/components/organisms/avatar-upload/types.d.ts +0 -62
- package/lib/services/image/image.service.d.ts +0 -76
- package/lib/services/image/index.d.ts +0 -2
- package/lib/services/image/types.d.ts +0 -74
- package/src/lib/services/firebase/firebase-messaging-sw.js +0 -134
|
@@ -5,7 +5,7 @@ import { IonAvatar, IonCard, IonIcon, IonButton, IonSpinner, IonText, IonModal,
|
|
|
5
5
|
import * as i1 from '@angular/common';
|
|
6
6
|
import { CommonModule, NgStyle, NgFor, isPlatformBrowser, NgClass } from '@angular/common';
|
|
7
7
|
import { addIcons } from 'ionicons';
|
|
8
|
-
import { addOutline, addCircleOutline, alertOutline, alertCircleOutline, arrowBackOutline, arrowForwardOutline, arrowDownOutline, settings, settingsOutline, checkmarkCircleOutline, ellipsisHorizontalOutline, notifications, notificationsOutline, openOutline, closeOutline, chatbubblesOutline, shareOutline, heart, heartOutline, home, homeOutline, eyeOffOutline, eyeOutline, scanOutline, chevronDownOutline, chevronForwardOutline, checkmarkOutline, clipboardOutline, copyOutline, filterOutline, locationOutline, calendarOutline, businessOutline, logoTwitter, logoInstagram, logoLinkedin, logoYoutube, logoTiktok, logoFacebook, logoGoogle, createOutline, trashOutline, playOutline, refreshOutline, documentTextOutline, lockClosedOutline, informationCircleOutline, logoNpm, removeOutline, add, close, share, create, trash, star, camera, mic, send, downloadOutline, chevronDown, language, globeOutline, checkmark, list, grid, apps, menu, search, person, helpCircle, informationCircle, documentText, mail, calendar, folder, chevronForward, ellipsisHorizontal, chevronBack, playBack, playForward, ellipse, starOutline, starHalf, heartHalf, checkmarkCircle, timeOutline, flag, trendingUp, trendingDown, remove, analytics, people, cash, cart, eye, chatbubbleOutline, thumbsUpOutline, thumbsUp, happyOutline, happy, sadOutline, sad, chevronUp, pin, pencil, callOutline, shuffleOutline, logoWhatsapp, paperPlaneOutline, mailOutline, trophyOutline, ticketOutline, giftOutline, personOutline, ellipsisVertical, closeCircle, alertCircle, logoApple, logoMicrosoft, linkOutline, unlinkOutline, chevronBackOutline, sendOutline, chatbubbleEllipsesOutline, swapVerticalOutline, chevronUpOutline, documentOutline, searchOutline, cartOutline, chatbubble, compass, compassOutline, gridOutline, listOutline, folderOutline, documents, documentsOutline, statsChart, statsChartOutline,
|
|
8
|
+
import { addOutline, addCircleOutline, alertOutline, alertCircleOutline, arrowBackOutline, arrowForwardOutline, arrowDownOutline, settings, settingsOutline, checkmarkCircleOutline, ellipsisHorizontalOutline, notifications, notificationsOutline, openOutline, closeOutline, chatbubblesOutline, shareOutline, heart, heartOutline, home, homeOutline, eyeOffOutline, eyeOutline, scanOutline, chevronDownOutline, chevronForwardOutline, checkmarkOutline, clipboardOutline, copyOutline, filterOutline, locationOutline, calendarOutline, businessOutline, logoTwitter, logoInstagram, logoLinkedin, logoYoutube, logoTiktok, logoFacebook, logoGoogle, createOutline, trashOutline, playOutline, refreshOutline, documentTextOutline, lockClosedOutline, informationCircleOutline, logoNpm, removeOutline, add, close, share, create, trash, star, camera, mic, send, downloadOutline, chevronDown, language, globeOutline, checkmark, list, grid, apps, menu, search, person, helpCircle, informationCircle, documentText, mail, calendar, folder, chevronForward, ellipsisHorizontal, chevronBack, playBack, playForward, ellipse, starOutline, starHalf, heartHalf, checkmarkCircle, timeOutline, flag, trendingUp, trendingDown, remove, analytics, people, cash, cart, eye, chatbubbleOutline, thumbsUpOutline, thumbsUp, happyOutline, happy, sadOutline, sad, chevronUp, pin, pencil, callOutline, shuffleOutline, logoWhatsapp, paperPlaneOutline, mailOutline, trophyOutline, ticketOutline, giftOutline, personOutline, ellipsisVertical, closeCircle, alertCircle, logoApple, logoMicrosoft, linkOutline, unlinkOutline, chevronBackOutline, sendOutline, chatbubbleEllipsesOutline, swapVerticalOutline, chevronUpOutline, documentOutline, searchOutline, cartOutline, chatbubble, compass, compassOutline, gridOutline, listOutline, folderOutline, documents, documentsOutline, statsChart, statsChartOutline, bugOutline, bulbOutline, closeCircleOutline, menuOutline } from 'ionicons/icons';
|
|
9
9
|
import * as i1$1 from '@angular/router';
|
|
10
10
|
import { Router, NavigationEnd, RouterLink, RouterOutlet, RouterModule } from '@angular/router';
|
|
11
11
|
import { Browser } from '@capacitor/browser';
|
|
@@ -39,7 +39,6 @@ import * as i1$7 from '@angular/fire/storage';
|
|
|
39
39
|
import { provideStorage, getStorage, connectStorageEmulator, ref, uploadBytesResumable, getDownloadURL, getMetadata, deleteObject, listAll } from '@angular/fire/storage';
|
|
40
40
|
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
|
41
41
|
import { filter, map as map$1, catchError as catchError$1, tap, switchMap as switchMap$1, finalize, take, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs/operators';
|
|
42
|
-
import { ImageCropperComponent } from 'ngx-image-cropper';
|
|
43
42
|
import * as i1$8 from '@angular/common/http';
|
|
44
43
|
import { provideHttpClient, withInterceptors, HttpClient } from '@angular/common/http';
|
|
45
44
|
import { Capacitor } from '@capacitor/core';
|
|
@@ -50,7 +49,7 @@ import 'prismjs/components/prism-json';
|
|
|
50
49
|
* Current version of valtech-components.
|
|
51
50
|
* This is automatically updated during the publish process.
|
|
52
51
|
*/
|
|
53
|
-
const VERSION = '2.0.
|
|
52
|
+
const VERSION = '2.0.681';
|
|
54
53
|
|
|
55
54
|
/**
|
|
56
55
|
* Servicio para gestionar presets de componentes.
|
|
@@ -21197,174 +21196,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
21197
21196
|
type: Input
|
|
21198
21197
|
}] } });
|
|
21199
21198
|
|
|
21200
|
-
/**
|
|
21201
|
-
* ImageCropComponent
|
|
21202
|
-
*
|
|
21203
|
-
* A modal-ready component for cropping images with a specified aspect ratio.
|
|
21204
|
-
* Uses ngx-image-cropper internally and provides a simple interface.
|
|
21205
|
-
*
|
|
21206
|
-
* @example Inside an ion-modal
|
|
21207
|
-
* ```html
|
|
21208
|
-
* <ion-modal [isOpen]="showCropModal">
|
|
21209
|
-
* <ng-template>
|
|
21210
|
-
* <val-image-crop
|
|
21211
|
-
* [image]="selectedFile"
|
|
21212
|
-
* [aspectRatio]="1"
|
|
21213
|
-
* [roundCropper]="true"
|
|
21214
|
-
* (cropComplete)="onCropComplete($event)"
|
|
21215
|
-
* (cancel)="showCropModal = false"
|
|
21216
|
-
* />
|
|
21217
|
-
* </ng-template>
|
|
21218
|
-
* </ion-modal>
|
|
21219
|
-
* ```
|
|
21220
|
-
*/
|
|
21221
|
-
class ImageCropComponent {
|
|
21222
|
-
constructor() {
|
|
21223
|
-
this.i18n = inject(I18nService);
|
|
21224
|
-
/** Image file to crop */
|
|
21225
|
-
this.image = input.required();
|
|
21226
|
-
/** Aspect ratio (1 for square, 16/9 for widescreen, etc.) */
|
|
21227
|
-
this.aspectRatio = input(1);
|
|
21228
|
-
/** Use round cropper (for avatars) */
|
|
21229
|
-
this.roundCropper = input(true);
|
|
21230
|
-
/** Resize output to specific width (0 = no resize) */
|
|
21231
|
-
this.resizeToWidth = input(0);
|
|
21232
|
-
/** i18n namespace for labels */
|
|
21233
|
-
this.i18nNamespace = input('ImageCrop');
|
|
21234
|
-
/** Emitted when crop is confirmed with the cropped blob */
|
|
21235
|
-
this.cropComplete = new EventEmitter();
|
|
21236
|
-
/** Emitted when user cancels the crop */
|
|
21237
|
-
this.cancel = new EventEmitter();
|
|
21238
|
-
/** Emitted when image fails to load */
|
|
21239
|
-
this.loadFailed = new EventEmitter();
|
|
21240
|
-
/** Internal signal for cropped blob */
|
|
21241
|
-
this.croppedBlob = signal(null);
|
|
21242
|
-
/** Computed text for cancel button */
|
|
21243
|
-
this.cancelText = computed(() => {
|
|
21244
|
-
this.i18n.lang(); // Track language changes
|
|
21245
|
-
return this.i18n.t('cancel', 'Common') || 'Cancelar';
|
|
21246
|
-
});
|
|
21247
|
-
/** Computed text for confirm button */
|
|
21248
|
-
this.confirmText = computed(() => {
|
|
21249
|
-
this.i18n.lang();
|
|
21250
|
-
return this.i18n.t('confirm', 'Common') || 'Confirmar';
|
|
21251
|
-
});
|
|
21252
|
-
/** Computed text for title */
|
|
21253
|
-
this.titleText = computed(() => {
|
|
21254
|
-
this.i18n.lang();
|
|
21255
|
-
return this.i18n.t('cropImage', this.i18nNamespace()) || 'Recortar imagen';
|
|
21256
|
-
});
|
|
21257
|
-
}
|
|
21258
|
-
/** Handle crop event from ngx-image-cropper */
|
|
21259
|
-
onImageCropped(event) {
|
|
21260
|
-
if (event.blob) {
|
|
21261
|
-
this.croppedBlob.set(event.blob);
|
|
21262
|
-
}
|
|
21263
|
-
}
|
|
21264
|
-
/** Confirm and emit the cropped blob */
|
|
21265
|
-
confirmCrop() {
|
|
21266
|
-
const blob = this.croppedBlob();
|
|
21267
|
-
if (blob) {
|
|
21268
|
-
this.cropComplete.emit(blob);
|
|
21269
|
-
}
|
|
21270
|
-
}
|
|
21271
|
-
/** Handle load failure */
|
|
21272
|
-
onLoadFailed() {
|
|
21273
|
-
this.loadFailed.emit();
|
|
21274
|
-
}
|
|
21275
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageCropComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
21276
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: ImageCropComponent, isStandalone: true, selector: "val-image-crop", inputs: { image: { classPropertyName: "image", publicName: "image", isSignal: true, isRequired: true, transformFunction: null }, aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, roundCropper: { classPropertyName: "roundCropper", publicName: "roundCropper", isSignal: true, isRequired: false, transformFunction: null }, resizeToWidth: { classPropertyName: "resizeToWidth", publicName: "resizeToWidth", isSignal: true, isRequired: false, transformFunction: null }, i18nNamespace: { classPropertyName: "i18nNamespace", publicName: "i18nNamespace", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cropComplete: "cropComplete", cancel: "cancel", loadFailed: "loadFailed" }, ngImport: i0, template: `
|
|
21277
|
-
<ion-header>
|
|
21278
|
-
<ion-toolbar>
|
|
21279
|
-
<ion-buttons slot="start">
|
|
21280
|
-
<ion-button (click)="cancel.emit()" color="medium">
|
|
21281
|
-
{{ cancelText() }}
|
|
21282
|
-
</ion-button>
|
|
21283
|
-
</ion-buttons>
|
|
21284
|
-
<ion-title>{{ titleText() }}</ion-title>
|
|
21285
|
-
<ion-buttons slot="end">
|
|
21286
|
-
<ion-button
|
|
21287
|
-
(click)="confirmCrop()"
|
|
21288
|
-
color="primary"
|
|
21289
|
-
[strong]="true"
|
|
21290
|
-
[disabled]="!croppedBlob()"
|
|
21291
|
-
>
|
|
21292
|
-
{{ confirmText() }}
|
|
21293
|
-
</ion-button>
|
|
21294
|
-
</ion-buttons>
|
|
21295
|
-
</ion-toolbar>
|
|
21296
|
-
</ion-header>
|
|
21297
|
-
|
|
21298
|
-
<ion-content class="image-crop-content">
|
|
21299
|
-
<image-cropper
|
|
21300
|
-
[imageFile]="image()"
|
|
21301
|
-
[aspectRatio]="aspectRatio()"
|
|
21302
|
-
[maintainAspectRatio]="true"
|
|
21303
|
-
[roundCropper]="roundCropper()"
|
|
21304
|
-
[resizeToWidth]="resizeToWidth()"
|
|
21305
|
-
format="jpeg"
|
|
21306
|
-
outputType="blob"
|
|
21307
|
-
(imageCropped)="onImageCropped($event)"
|
|
21308
|
-
(loadImageFailed)="onLoadFailed()"
|
|
21309
|
-
/>
|
|
21310
|
-
</ion-content>
|
|
21311
|
-
`, isInline: true, styles: [":host{display:flex;flex-direction:column;height:100%}.image-crop-content{--background: var(--ion-color-dark)}.image-crop-content::part(scroll){display:flex;flex-direction:column}image-cropper{--cropper-outline-color: rgba(255, 255, 255, .3);--cropper-background-color: var(--ion-color-dark);flex:1;height:100%;max-height:calc(100vh - 56px)}::ng-deep .ngx-ic-component{height:100%!important}::ng-deep .ngx-ic-source-image{max-height:100%!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { 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"] }] }); }
|
|
21312
|
-
}
|
|
21313
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageCropComponent, decorators: [{
|
|
21314
|
-
type: Component,
|
|
21315
|
-
args: [{ selector: 'val-image-crop', standalone: true, imports: [
|
|
21316
|
-
CommonModule,
|
|
21317
|
-
IonHeader,
|
|
21318
|
-
IonToolbar,
|
|
21319
|
-
IonTitle,
|
|
21320
|
-
IonButtons,
|
|
21321
|
-
IonButton,
|
|
21322
|
-
IonContent,
|
|
21323
|
-
ImageCropperComponent,
|
|
21324
|
-
], template: `
|
|
21325
|
-
<ion-header>
|
|
21326
|
-
<ion-toolbar>
|
|
21327
|
-
<ion-buttons slot="start">
|
|
21328
|
-
<ion-button (click)="cancel.emit()" color="medium">
|
|
21329
|
-
{{ cancelText() }}
|
|
21330
|
-
</ion-button>
|
|
21331
|
-
</ion-buttons>
|
|
21332
|
-
<ion-title>{{ titleText() }}</ion-title>
|
|
21333
|
-
<ion-buttons slot="end">
|
|
21334
|
-
<ion-button
|
|
21335
|
-
(click)="confirmCrop()"
|
|
21336
|
-
color="primary"
|
|
21337
|
-
[strong]="true"
|
|
21338
|
-
[disabled]="!croppedBlob()"
|
|
21339
|
-
>
|
|
21340
|
-
{{ confirmText() }}
|
|
21341
|
-
</ion-button>
|
|
21342
|
-
</ion-buttons>
|
|
21343
|
-
</ion-toolbar>
|
|
21344
|
-
</ion-header>
|
|
21345
|
-
|
|
21346
|
-
<ion-content class="image-crop-content">
|
|
21347
|
-
<image-cropper
|
|
21348
|
-
[imageFile]="image()"
|
|
21349
|
-
[aspectRatio]="aspectRatio()"
|
|
21350
|
-
[maintainAspectRatio]="true"
|
|
21351
|
-
[roundCropper]="roundCropper()"
|
|
21352
|
-
[resizeToWidth]="resizeToWidth()"
|
|
21353
|
-
format="jpeg"
|
|
21354
|
-
outputType="blob"
|
|
21355
|
-
(imageCropped)="onImageCropped($event)"
|
|
21356
|
-
(loadImageFailed)="onLoadFailed()"
|
|
21357
|
-
/>
|
|
21358
|
-
</ion-content>
|
|
21359
|
-
`, styles: [":host{display:flex;flex-direction:column;height:100%}.image-crop-content{--background: var(--ion-color-dark)}.image-crop-content::part(scroll){display:flex;flex-direction:column}image-cropper{--cropper-outline-color: rgba(255, 255, 255, .3);--cropper-background-color: var(--ion-color-dark);flex:1;height:100%;max-height:calc(100vh - 56px)}::ng-deep .ngx-ic-component{height:100%!important}::ng-deep .ngx-ic-source-image{max-height:100%!important}\n"] }]
|
|
21360
|
-
}], propDecorators: { cropComplete: [{
|
|
21361
|
-
type: Output
|
|
21362
|
-
}], cancel: [{
|
|
21363
|
-
type: Output
|
|
21364
|
-
}], loadFailed: [{
|
|
21365
|
-
type: Output
|
|
21366
|
-
}] } });
|
|
21367
|
-
|
|
21368
21199
|
/**
|
|
21369
21200
|
* Configuración de espaciado predefinida
|
|
21370
21201
|
*/
|
|
@@ -30754,15 +30585,6 @@ class AuthService {
|
|
|
30754
30585
|
.put(`${this.baseUrl}/profile`, request)
|
|
30755
30586
|
.pipe(catchError$1(error => this.handleAuthError(error)));
|
|
30756
30587
|
}
|
|
30757
|
-
/**
|
|
30758
|
-
* Actualiza el avatar del usuario en el backend.
|
|
30759
|
-
* Nota: El estado local del avatar se maneja a través de getProfile().
|
|
30760
|
-
*/
|
|
30761
|
-
updateAvatar(request) {
|
|
30762
|
-
return this.http
|
|
30763
|
-
.put(`${this.baseUrl}/profile/avatar`, request)
|
|
30764
|
-
.pipe(catchError$1(error => this.handleAuthError(error)));
|
|
30765
|
-
}
|
|
30766
30588
|
// =============================================
|
|
30767
30589
|
// RECUPERACIÓN DE CONTRASEÑA
|
|
30768
30590
|
// =============================================
|
|
@@ -30885,7 +30707,7 @@ class AuthService {
|
|
|
30885
30707
|
*/
|
|
30886
30708
|
updateHandle(handle) {
|
|
30887
30709
|
return this.http
|
|
30888
|
-
.put(`${this.baseUrl}/
|
|
30710
|
+
.put(`${this.baseUrl}/handle`, { handle })
|
|
30889
30711
|
.pipe(catchError$1(error => this.handleAuthError(error)));
|
|
30890
30712
|
}
|
|
30891
30713
|
/**
|
|
@@ -33679,607 +33501,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
33679
33501
|
type: Output
|
|
33680
33502
|
}] } });
|
|
33681
33503
|
|
|
33682
|
-
/**
|
|
33683
|
-
* Default values for image processing
|
|
33684
|
-
*/
|
|
33685
|
-
const IMAGE_DEFAULTS = {
|
|
33686
|
-
maxWidth: 800,
|
|
33687
|
-
maxHeight: 800,
|
|
33688
|
-
quality: 0.8,
|
|
33689
|
-
mimeType: 'image/jpeg',
|
|
33690
|
-
maxSize: 10 * 1024 * 1024, // 10MB
|
|
33691
|
-
allowedTypes: ['image/jpeg', 'image/png', 'image/webp', 'image/gif'],
|
|
33692
|
-
thumbnailSize: 150,
|
|
33693
|
-
};
|
|
33694
|
-
|
|
33695
|
-
/**
|
|
33696
|
-
* ImageService
|
|
33697
|
-
*
|
|
33698
|
-
* Service for image processing including compression, thumbnails, cropping and validation.
|
|
33699
|
-
* Uses HTML Canvas for all operations - no external dependencies.
|
|
33700
|
-
*
|
|
33701
|
-
* @example
|
|
33702
|
-
* ```typescript
|
|
33703
|
-
* const imageService = inject(ImageService);
|
|
33704
|
-
*
|
|
33705
|
-
* // Compress an image
|
|
33706
|
-
* const compressed = await imageService.compress(file, { maxWidth: 800, quality: 0.8 });
|
|
33707
|
-
*
|
|
33708
|
-
* // Generate thumbnail
|
|
33709
|
-
* const thumb = await imageService.thumbnail(file, 150);
|
|
33710
|
-
*
|
|
33711
|
-
* // Validate before processing
|
|
33712
|
-
* const validation = imageService.validate(file, { maxSize: 5 * 1024 * 1024 });
|
|
33713
|
-
* if (!validation.valid) {
|
|
33714
|
-
* console.error(validation.message);
|
|
33715
|
-
* }
|
|
33716
|
-
* ```
|
|
33717
|
-
*/
|
|
33718
|
-
class ImageService {
|
|
33719
|
-
/**
|
|
33720
|
-
* Compress an image maintaining aspect ratio
|
|
33721
|
-
* @param file - File or Blob to compress
|
|
33722
|
-
* @param options - Compression options
|
|
33723
|
-
* @returns Promise with processed image data
|
|
33724
|
-
*/
|
|
33725
|
-
async compress(file, options) {
|
|
33726
|
-
const opts = {
|
|
33727
|
-
maxWidth: options?.maxWidth ?? IMAGE_DEFAULTS.maxWidth,
|
|
33728
|
-
maxHeight: options?.maxHeight ?? IMAGE_DEFAULTS.maxHeight,
|
|
33729
|
-
quality: options?.quality ?? IMAGE_DEFAULTS.quality,
|
|
33730
|
-
mimeType: options?.mimeType ?? IMAGE_DEFAULTS.mimeType,
|
|
33731
|
-
};
|
|
33732
|
-
const img = await this.loadImage(file);
|
|
33733
|
-
const { width, height } = this.calculateDimensions(img.width, img.height, opts.maxWidth, opts.maxHeight);
|
|
33734
|
-
const canvas = document.createElement('canvas');
|
|
33735
|
-
canvas.width = width;
|
|
33736
|
-
canvas.height = height;
|
|
33737
|
-
const ctx = canvas.getContext('2d');
|
|
33738
|
-
ctx.drawImage(img, 0, 0, width, height);
|
|
33739
|
-
const blob = await this.canvasToBlob(canvas, opts.mimeType, opts.quality);
|
|
33740
|
-
const dataUrl = canvas.toDataURL(opts.mimeType, opts.quality);
|
|
33741
|
-
return {
|
|
33742
|
-
blob,
|
|
33743
|
-
dataUrl,
|
|
33744
|
-
width,
|
|
33745
|
-
height,
|
|
33746
|
-
size: blob.size,
|
|
33747
|
-
};
|
|
33748
|
-
}
|
|
33749
|
-
/**
|
|
33750
|
-
* Generate a square thumbnail from an image
|
|
33751
|
-
* @param file - File or Blob to process
|
|
33752
|
-
* @param size - Thumbnail size in pixels (default: 150)
|
|
33753
|
-
* @returns Promise with processed thumbnail
|
|
33754
|
-
*/
|
|
33755
|
-
async thumbnail(file, size) {
|
|
33756
|
-
const thumbSize = size ?? IMAGE_DEFAULTS.thumbnailSize;
|
|
33757
|
-
const img = await this.loadImage(file);
|
|
33758
|
-
// Calculate square crop from center
|
|
33759
|
-
const minDim = Math.min(img.width, img.height);
|
|
33760
|
-
const cropX = (img.width - minDim) / 2;
|
|
33761
|
-
const cropY = (img.height - minDim) / 2;
|
|
33762
|
-
const canvas = document.createElement('canvas');
|
|
33763
|
-
canvas.width = thumbSize;
|
|
33764
|
-
canvas.height = thumbSize;
|
|
33765
|
-
const ctx = canvas.getContext('2d');
|
|
33766
|
-
ctx.drawImage(img, cropX, cropY, minDim, minDim, 0, 0, thumbSize, thumbSize);
|
|
33767
|
-
const blob = await this.canvasToBlob(canvas, IMAGE_DEFAULTS.mimeType, 0.7 // Lower quality for thumbnails
|
|
33768
|
-
);
|
|
33769
|
-
const dataUrl = canvas.toDataURL(IMAGE_DEFAULTS.mimeType, 0.7);
|
|
33770
|
-
return {
|
|
33771
|
-
blob,
|
|
33772
|
-
dataUrl,
|
|
33773
|
-
width: thumbSize,
|
|
33774
|
-
height: thumbSize,
|
|
33775
|
-
size: blob.size,
|
|
33776
|
-
};
|
|
33777
|
-
}
|
|
33778
|
-
/**
|
|
33779
|
-
* Crop an image with specific coordinates
|
|
33780
|
-
* @param file - File or Blob to crop
|
|
33781
|
-
* @param cropData - Crop coordinates and dimensions
|
|
33782
|
-
* @param options - Optional compression options for output
|
|
33783
|
-
* @returns Promise with cropped image
|
|
33784
|
-
*/
|
|
33785
|
-
async crop(file, cropData, options) {
|
|
33786
|
-
const img = await this.loadImage(file);
|
|
33787
|
-
const opts = {
|
|
33788
|
-
quality: options?.quality ?? IMAGE_DEFAULTS.quality,
|
|
33789
|
-
mimeType: options?.mimeType ?? IMAGE_DEFAULTS.mimeType,
|
|
33790
|
-
};
|
|
33791
|
-
const canvas = document.createElement('canvas');
|
|
33792
|
-
canvas.width = cropData.width;
|
|
33793
|
-
canvas.height = cropData.height;
|
|
33794
|
-
const ctx = canvas.getContext('2d');
|
|
33795
|
-
ctx.drawImage(img, cropData.x, cropData.y, cropData.width, cropData.height, 0, 0, cropData.width, cropData.height);
|
|
33796
|
-
// Apply max dimensions if specified
|
|
33797
|
-
if (options?.maxWidth || options?.maxHeight) {
|
|
33798
|
-
return this.compress(await this.canvasToBlob(canvas, opts.mimeType, 1), options);
|
|
33799
|
-
}
|
|
33800
|
-
const blob = await this.canvasToBlob(canvas, opts.mimeType, opts.quality);
|
|
33801
|
-
const dataUrl = canvas.toDataURL(opts.mimeType, opts.quality);
|
|
33802
|
-
return {
|
|
33803
|
-
blob,
|
|
33804
|
-
dataUrl,
|
|
33805
|
-
width: cropData.width,
|
|
33806
|
-
height: cropData.height,
|
|
33807
|
-
size: blob.size,
|
|
33808
|
-
};
|
|
33809
|
-
}
|
|
33810
|
-
/**
|
|
33811
|
-
* Validate an image file before processing
|
|
33812
|
-
* @param file - File to validate
|
|
33813
|
-
* @param options - Validation options
|
|
33814
|
-
* @returns Validation result with error details if invalid
|
|
33815
|
-
*/
|
|
33816
|
-
validate(file, options) {
|
|
33817
|
-
const opts = {
|
|
33818
|
-
maxSize: options?.maxSize ?? IMAGE_DEFAULTS.maxSize,
|
|
33819
|
-
allowedTypes: options?.allowedTypes ?? IMAGE_DEFAULTS.allowedTypes,
|
|
33820
|
-
};
|
|
33821
|
-
// Check file type
|
|
33822
|
-
if (!opts.allowedTypes.includes(file.type)) {
|
|
33823
|
-
return {
|
|
33824
|
-
valid: false,
|
|
33825
|
-
error: 'invalidType',
|
|
33826
|
-
message: `Formato no válido. Usa: ${opts.allowedTypes.map(t => t.split('/')[1].toUpperCase()).join(', ')}`,
|
|
33827
|
-
};
|
|
33828
|
-
}
|
|
33829
|
-
// Check file size
|
|
33830
|
-
if (file.size > opts.maxSize) {
|
|
33831
|
-
const maxMB = Math.round(opts.maxSize / (1024 * 1024));
|
|
33832
|
-
return {
|
|
33833
|
-
valid: false,
|
|
33834
|
-
error: 'fileTooLarge',
|
|
33835
|
-
message: `La imagen es muy grande. Máximo ${maxMB}MB`,
|
|
33836
|
-
};
|
|
33837
|
-
}
|
|
33838
|
-
return { valid: true };
|
|
33839
|
-
}
|
|
33840
|
-
/**
|
|
33841
|
-
* Validate image dimensions (async - requires loading image)
|
|
33842
|
-
* @param file - File to validate
|
|
33843
|
-
* @param options - Validation options with minWidth/minHeight
|
|
33844
|
-
* @returns Promise with validation result
|
|
33845
|
-
*/
|
|
33846
|
-
async validateDimensions(file, options) {
|
|
33847
|
-
const img = await this.loadImage(file);
|
|
33848
|
-
if (options.minWidth && img.width < options.minWidth) {
|
|
33849
|
-
return {
|
|
33850
|
-
valid: false,
|
|
33851
|
-
error: 'imageTooSmall',
|
|
33852
|
-
message: `La imagen debe tener al menos ${options.minWidth}px de ancho`,
|
|
33853
|
-
};
|
|
33854
|
-
}
|
|
33855
|
-
if (options.minHeight && img.height < options.minHeight) {
|
|
33856
|
-
return {
|
|
33857
|
-
valid: false,
|
|
33858
|
-
error: 'imageTooSmall',
|
|
33859
|
-
message: `La imagen debe tener al menos ${options.minHeight}px de alto`,
|
|
33860
|
-
};
|
|
33861
|
-
}
|
|
33862
|
-
return { valid: true };
|
|
33863
|
-
}
|
|
33864
|
-
/**
|
|
33865
|
-
* Convert a Blob/File to a data URL
|
|
33866
|
-
*/
|
|
33867
|
-
async toDataUrl(file) {
|
|
33868
|
-
return new Promise((resolve, reject) => {
|
|
33869
|
-
const reader = new FileReader();
|
|
33870
|
-
reader.onload = () => resolve(reader.result);
|
|
33871
|
-
reader.onerror = reject;
|
|
33872
|
-
reader.readAsDataURL(file);
|
|
33873
|
-
});
|
|
33874
|
-
}
|
|
33875
|
-
/**
|
|
33876
|
-
* Convert a data URL to a Blob
|
|
33877
|
-
*/
|
|
33878
|
-
dataUrlToBlob(dataUrl) {
|
|
33879
|
-
const arr = dataUrl.split(',');
|
|
33880
|
-
const mime = arr[0].match(/:(.*?);/)[1];
|
|
33881
|
-
const bstr = atob(arr[1]);
|
|
33882
|
-
let n = bstr.length;
|
|
33883
|
-
const u8arr = new Uint8Array(n);
|
|
33884
|
-
while (n--) {
|
|
33885
|
-
u8arr[n] = bstr.charCodeAt(n);
|
|
33886
|
-
}
|
|
33887
|
-
return new Blob([u8arr], { type: mime });
|
|
33888
|
-
}
|
|
33889
|
-
// ============== Private Helpers ==============
|
|
33890
|
-
loadImage(file) {
|
|
33891
|
-
return new Promise((resolve, reject) => {
|
|
33892
|
-
const img = new Image();
|
|
33893
|
-
img.onload = () => {
|
|
33894
|
-
URL.revokeObjectURL(img.src);
|
|
33895
|
-
resolve(img);
|
|
33896
|
-
};
|
|
33897
|
-
img.onerror = reject;
|
|
33898
|
-
img.src = URL.createObjectURL(file);
|
|
33899
|
-
});
|
|
33900
|
-
}
|
|
33901
|
-
calculateDimensions(originalWidth, originalHeight, maxWidth, maxHeight) {
|
|
33902
|
-
let width = originalWidth;
|
|
33903
|
-
let height = originalHeight;
|
|
33904
|
-
// Scale down if necessary, maintaining aspect ratio
|
|
33905
|
-
if (width > maxWidth) {
|
|
33906
|
-
height = (height * maxWidth) / width;
|
|
33907
|
-
width = maxWidth;
|
|
33908
|
-
}
|
|
33909
|
-
if (height > maxHeight) {
|
|
33910
|
-
width = (width * maxHeight) / height;
|
|
33911
|
-
height = maxHeight;
|
|
33912
|
-
}
|
|
33913
|
-
return {
|
|
33914
|
-
width: Math.round(width),
|
|
33915
|
-
height: Math.round(height),
|
|
33916
|
-
};
|
|
33917
|
-
}
|
|
33918
|
-
canvasToBlob(canvas, mimeType, quality) {
|
|
33919
|
-
return new Promise((resolve, reject) => {
|
|
33920
|
-
canvas.toBlob((blob) => {
|
|
33921
|
-
if (blob)
|
|
33922
|
-
resolve(blob);
|
|
33923
|
-
else
|
|
33924
|
-
reject(new Error('Failed to create blob from canvas'));
|
|
33925
|
-
}, mimeType, quality);
|
|
33926
|
-
});
|
|
33927
|
-
}
|
|
33928
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
33929
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageService, providedIn: 'root' }); }
|
|
33930
|
-
}
|
|
33931
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageService, decorators: [{
|
|
33932
|
-
type: Injectable,
|
|
33933
|
-
args: [{ providedIn: 'root' }]
|
|
33934
|
-
}] });
|
|
33935
|
-
|
|
33936
|
-
/**
|
|
33937
|
-
* Default values
|
|
33938
|
-
*/
|
|
33939
|
-
const AVATAR_UPLOAD_DEFAULTS = {
|
|
33940
|
-
size: 100,
|
|
33941
|
-
editable: true,
|
|
33942
|
-
storagePath: 'avatars',
|
|
33943
|
-
i18nNamespace: 'AvatarUpload',
|
|
33944
|
-
maxFileSize: 10 * 1024 * 1024, // 10MB
|
|
33945
|
-
compressQuality: 0.8,
|
|
33946
|
-
maxWidth: 800,
|
|
33947
|
-
thumbnailSize: 150,
|
|
33948
|
-
backgroundColor: '#6366f1', // Indigo
|
|
33949
|
-
};
|
|
33950
|
-
|
|
33951
|
-
addIcons({ cameraOutline });
|
|
33952
|
-
/**
|
|
33953
|
-
* AvatarUploadComponent
|
|
33954
|
-
*
|
|
33955
|
-
* A complete avatar upload solution with:
|
|
33956
|
-
* - Image selection from device
|
|
33957
|
-
* - Crop modal with round preview
|
|
33958
|
-
* - Automatic compression and thumbnail generation
|
|
33959
|
-
* - Upload to Firebase Storage
|
|
33960
|
-
* - Backend sync via AuthService
|
|
33961
|
-
*
|
|
33962
|
-
* @example Basic usage
|
|
33963
|
-
* ```html
|
|
33964
|
-
* <val-avatar-upload
|
|
33965
|
-
* [props]="{
|
|
33966
|
-
* currentUrl: user()?.avatarUrl,
|
|
33967
|
-
* initials: 'JD',
|
|
33968
|
-
* size: 120
|
|
33969
|
-
* }"
|
|
33970
|
-
* (uploaded)="onAvatarUploaded($event)"
|
|
33971
|
-
* (error)="onError($event)"
|
|
33972
|
-
* />
|
|
33973
|
-
* ```
|
|
33974
|
-
*/
|
|
33975
|
-
class AvatarUploadComponent {
|
|
33976
|
-
constructor() {
|
|
33977
|
-
this.imageService = inject(ImageService);
|
|
33978
|
-
this.storageService = inject(StorageService);
|
|
33979
|
-
this.authService = inject(AuthService);
|
|
33980
|
-
this.i18n = inject(I18nService);
|
|
33981
|
-
/** Component configuration */
|
|
33982
|
-
this.props = input({});
|
|
33983
|
-
/** Emitted after successful upload and backend sync */
|
|
33984
|
-
this.uploaded = new EventEmitter();
|
|
33985
|
-
/** Emitted on any error during the process */
|
|
33986
|
-
this.error = new EventEmitter();
|
|
33987
|
-
/** Emitted when upload starts */
|
|
33988
|
-
this.uploadStart = new EventEmitter();
|
|
33989
|
-
// Internal state
|
|
33990
|
-
this.loading = signal(false);
|
|
33991
|
-
this.showCropModal = signal(false);
|
|
33992
|
-
this.selectedFile = signal(null);
|
|
33993
|
-
this.previewUrl = signal(null);
|
|
33994
|
-
this.imageLoadError = signal(false);
|
|
33995
|
-
/** Merged config with defaults */
|
|
33996
|
-
this.config = computed(() => ({
|
|
33997
|
-
...AVATAR_UPLOAD_DEFAULTS,
|
|
33998
|
-
...this.props(),
|
|
33999
|
-
}));
|
|
34000
|
-
/** URL to display (preview takes priority over current) */
|
|
34001
|
-
this.displayUrl = computed(() => {
|
|
34002
|
-
if (this.imageLoadError())
|
|
34003
|
-
return null;
|
|
34004
|
-
return this.previewUrl() || this.config().currentUrl || null;
|
|
34005
|
-
});
|
|
34006
|
-
/** Aria label for edit button */
|
|
34007
|
-
this.editButtonLabel = computed(() => {
|
|
34008
|
-
this.i18n.lang();
|
|
34009
|
-
return this.i18n.t('changePhoto', this.config().i18nNamespace) || 'Cambiar foto';
|
|
34010
|
-
});
|
|
34011
|
-
}
|
|
34012
|
-
/** Open file picker dialog */
|
|
34013
|
-
openFilePicker() {
|
|
34014
|
-
this.fileInput.nativeElement.click();
|
|
34015
|
-
}
|
|
34016
|
-
/** Handle file selection */
|
|
34017
|
-
onFileSelected(event) {
|
|
34018
|
-
const input = event.target;
|
|
34019
|
-
const file = input.files?.[0];
|
|
34020
|
-
if (!file)
|
|
34021
|
-
return;
|
|
34022
|
-
// Reset input for same file selection
|
|
34023
|
-
input.value = '';
|
|
34024
|
-
// Validate file
|
|
34025
|
-
const validation = this.imageService.validate(file, {
|
|
34026
|
-
maxSize: this.config().maxFileSize,
|
|
34027
|
-
allowedTypes: ['image/jpeg', 'image/png', 'image/webp'],
|
|
34028
|
-
});
|
|
34029
|
-
if (!validation.valid) {
|
|
34030
|
-
this.emitError(validation.error, validation.message);
|
|
34031
|
-
return;
|
|
34032
|
-
}
|
|
34033
|
-
// Open crop modal
|
|
34034
|
-
this.selectedFile.set(file);
|
|
34035
|
-
this.showCropModal.set(true);
|
|
34036
|
-
}
|
|
34037
|
-
/** Handle crop completion */
|
|
34038
|
-
async onCropComplete(croppedBlob) {
|
|
34039
|
-
this.showCropModal.set(false);
|
|
34040
|
-
this.selectedFile.set(null);
|
|
34041
|
-
await this.processAndUpload(croppedBlob);
|
|
34042
|
-
}
|
|
34043
|
-
/** Handle crop cancel */
|
|
34044
|
-
onCropCancel() {
|
|
34045
|
-
this.showCropModal.set(false);
|
|
34046
|
-
this.selectedFile.set(null);
|
|
34047
|
-
}
|
|
34048
|
-
/** Handle crop load failure */
|
|
34049
|
-
onCropLoadFailed() {
|
|
34050
|
-
this.showCropModal.set(false);
|
|
34051
|
-
this.selectedFile.set(null);
|
|
34052
|
-
this.emitError('invalidType', this.i18n.t('loadFailed', this.config().i18nNamespace) || 'No se pudo cargar la imagen');
|
|
34053
|
-
}
|
|
34054
|
-
/** Handle image load error */
|
|
34055
|
-
onImageError() {
|
|
34056
|
-
this.imageLoadError.set(true);
|
|
34057
|
-
}
|
|
34058
|
-
/** Process cropped image and upload */
|
|
34059
|
-
async processAndUpload(croppedBlob) {
|
|
34060
|
-
this.loading.set(true);
|
|
34061
|
-
this.uploadStart.emit();
|
|
34062
|
-
try {
|
|
34063
|
-
const config = this.config();
|
|
34064
|
-
// 1. Compress image
|
|
34065
|
-
const compressed = await this.imageService.compress(croppedBlob, {
|
|
34066
|
-
maxWidth: config.maxWidth,
|
|
34067
|
-
maxHeight: config.maxWidth,
|
|
34068
|
-
quality: config.compressQuality,
|
|
34069
|
-
});
|
|
34070
|
-
// 2. Generate thumbnail
|
|
34071
|
-
const thumbnail = await this.imageService.thumbnail(compressed.blob, config.thumbnailSize);
|
|
34072
|
-
// 3. Set preview immediately
|
|
34073
|
-
this.previewUrl.set(compressed.dataUrl);
|
|
34074
|
-
this.imageLoadError.set(false);
|
|
34075
|
-
// 4. Get user ID for storage path
|
|
34076
|
-
const userId = this.authService.user()?.userId;
|
|
34077
|
-
if (!userId) {
|
|
34078
|
-
throw new Error('User not authenticated');
|
|
34079
|
-
}
|
|
34080
|
-
// 5. Upload to Firebase Storage
|
|
34081
|
-
const timestamp = Date.now();
|
|
34082
|
-
const avatarPath = `${config.storagePath}/${userId}/avatar_${timestamp}.jpg`;
|
|
34083
|
-
const thumbPath = `${config.storagePath}/${userId}/thumb_${timestamp}.jpg`;
|
|
34084
|
-
const [avatarResult, thumbResult] = await Promise.all([
|
|
34085
|
-
this.storageService.uploadAndGetUrl(avatarPath, compressed.blob),
|
|
34086
|
-
this.storageService.uploadAndGetUrl(thumbPath, thumbnail.blob),
|
|
34087
|
-
]);
|
|
34088
|
-
// 6. Update backend
|
|
34089
|
-
await firstValueFrom(this.authService.updateAvatar({
|
|
34090
|
-
avatarUrl: avatarResult.downloadUrl,
|
|
34091
|
-
avatarThumbnail: thumbResult.downloadUrl,
|
|
34092
|
-
}));
|
|
34093
|
-
// 7. Emit success
|
|
34094
|
-
const result = {
|
|
34095
|
-
avatarUrl: avatarResult.downloadUrl,
|
|
34096
|
-
thumbnailUrl: thumbResult.downloadUrl,
|
|
34097
|
-
};
|
|
34098
|
-
this.uploaded.emit(result);
|
|
34099
|
-
}
|
|
34100
|
-
catch (err) {
|
|
34101
|
-
// Revert preview on error
|
|
34102
|
-
this.previewUrl.set(null);
|
|
34103
|
-
const message = err instanceof Error
|
|
34104
|
-
? err.message
|
|
34105
|
-
: this.i18n.t('uploadError', this.config().i18nNamespace) || 'Error al subir la imagen';
|
|
34106
|
-
this.emitError('uploadFailed', message, err);
|
|
34107
|
-
}
|
|
34108
|
-
finally {
|
|
34109
|
-
this.loading.set(false);
|
|
34110
|
-
}
|
|
34111
|
-
}
|
|
34112
|
-
/** Emit error event */
|
|
34113
|
-
emitError(type, message, originalError) {
|
|
34114
|
-
this.error.emit({ type, message, originalError });
|
|
34115
|
-
}
|
|
34116
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AvatarUploadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
34117
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AvatarUploadComponent, isStandalone: true, selector: "val-avatar-upload", inputs: { props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { uploaded: "uploaded", error: "error", uploadStart: "uploadStart" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: `
|
|
34118
|
-
<div
|
|
34119
|
-
class="avatar-upload"
|
|
34120
|
-
[style.--avatar-size.px]="config().size"
|
|
34121
|
-
[class.avatar-upload--loading]="loading()"
|
|
34122
|
-
>
|
|
34123
|
-
<div class="avatar-container">
|
|
34124
|
-
<!-- Avatar Image or Initials -->
|
|
34125
|
-
@if (displayUrl()) {
|
|
34126
|
-
<img
|
|
34127
|
-
class="avatar-image"
|
|
34128
|
-
[src]="displayUrl()"
|
|
34129
|
-
alt="Avatar"
|
|
34130
|
-
(error)="onImageError()"
|
|
34131
|
-
/>
|
|
34132
|
-
} @else {
|
|
34133
|
-
<div
|
|
34134
|
-
class="avatar-initials"
|
|
34135
|
-
[style.background-color]="config().backgroundColor"
|
|
34136
|
-
>
|
|
34137
|
-
{{ config().initials || '?' }}
|
|
34138
|
-
</div>
|
|
34139
|
-
}
|
|
34140
|
-
|
|
34141
|
-
<!-- Edit Button -->
|
|
34142
|
-
@if (config().editable && !loading()) {
|
|
34143
|
-
<button
|
|
34144
|
-
class="edit-button"
|
|
34145
|
-
type="button"
|
|
34146
|
-
(click)="openFilePicker()"
|
|
34147
|
-
[attr.aria-label]="editButtonLabel()"
|
|
34148
|
-
>
|
|
34149
|
-
<ion-icon name="camera-outline"></ion-icon>
|
|
34150
|
-
</button>
|
|
34151
|
-
}
|
|
34152
|
-
|
|
34153
|
-
<!-- Loading Overlay -->
|
|
34154
|
-
@if (loading()) {
|
|
34155
|
-
<div class="loading-overlay">
|
|
34156
|
-
<ion-spinner name="crescent"></ion-spinner>
|
|
34157
|
-
</div>
|
|
34158
|
-
}
|
|
34159
|
-
</div>
|
|
34160
|
-
|
|
34161
|
-
<!-- Hidden File Input -->
|
|
34162
|
-
<input
|
|
34163
|
-
#fileInput
|
|
34164
|
-
type="file"
|
|
34165
|
-
accept="image/jpeg,image/png,image/webp"
|
|
34166
|
-
(change)="onFileSelected($event)"
|
|
34167
|
-
hidden
|
|
34168
|
-
/>
|
|
34169
|
-
|
|
34170
|
-
<!-- Crop Modal -->
|
|
34171
|
-
<ion-modal
|
|
34172
|
-
[isOpen]="showCropModal()"
|
|
34173
|
-
(didDismiss)="onCropCancel()"
|
|
34174
|
-
[breakpoints]="[0, 1]"
|
|
34175
|
-
[initialBreakpoint]="1"
|
|
34176
|
-
>
|
|
34177
|
-
<ng-template>
|
|
34178
|
-
@if (selectedFile()) {
|
|
34179
|
-
<val-image-crop
|
|
34180
|
-
[image]="selectedFile()!"
|
|
34181
|
-
[aspectRatio]="1"
|
|
34182
|
-
[roundCropper]="true"
|
|
34183
|
-
[i18nNamespace]="config().i18nNamespace"
|
|
34184
|
-
(cropComplete)="onCropComplete($event)"
|
|
34185
|
-
(cancel)="onCropCancel()"
|
|
34186
|
-
(loadFailed)="onCropLoadFailed()"
|
|
34187
|
-
/>
|
|
34188
|
-
}
|
|
34189
|
-
</ng-template>
|
|
34190
|
-
</ion-modal>
|
|
34191
|
-
</div>
|
|
34192
|
-
`, isInline: true, styles: [".avatar-upload{--avatar-size: 100px;--edit-button-size: 32px;--edit-button-offset: 4px;display:inline-block}.avatar-container{position:relative;width:var(--avatar-size);height:var(--avatar-size);border-radius:50%;overflow:visible}.avatar-image{width:100%;height:100%;border-radius:50%;object-fit:cover;background-color:var(--ion-color-light)}.avatar-initials{width:100%;height:100%;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:calc(var(--avatar-size) * .4);font-weight:600;color:#fff;text-transform:uppercase;-webkit-user-select:none;user-select:none}.edit-button{position:absolute;bottom:var(--edit-button-offset);right:var(--edit-button-offset);width:var(--edit-button-size);height:var(--edit-button-size);border-radius:50%;border:2px solid white;background:var(--ion-color-primary);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:transform .2s ease,background-color .2s ease;box-shadow:0 2px 8px #00000026}.edit-button ion-icon{font-size:calc(var(--edit-button-size) * .5)}.edit-button:hover{transform:scale(1.1);background:var(--ion-color-primary-shade)}.edit-button:active{transform:scale(.95)}.edit-button:focus-visible{outline:2px solid var(--ion-color-primary);outline-offset:2px}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;border-radius:50%;background:#00000080;display:flex;align-items:center;justify-content:center}.loading-overlay ion-spinner{--color: white;width:calc(var(--avatar-size) * .4);height:calc(var(--avatar-size) * .4)}.avatar-upload--loading .edit-button{display:none}.avatar-upload--loading .avatar-image,.avatar-upload--loading .avatar-initials{filter:brightness(.7)}@container (max-width: 60px){.edit-button{--edit-button-size: 24px;--edit-button-offset: 0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonModal, selector: "ion-modal" }, { kind: "component", type: ImageCropComponent, selector: "val-image-crop", inputs: ["image", "aspectRatio", "roundCropper", "resizeToWidth", "i18nNamespace"], outputs: ["cropComplete", "cancel", "loadFailed"] }] }); }
|
|
34193
|
-
}
|
|
34194
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AvatarUploadComponent, decorators: [{
|
|
34195
|
-
type: Component,
|
|
34196
|
-
args: [{ selector: 'val-avatar-upload', standalone: true, imports: [CommonModule, IonIcon, IonSpinner, IonModal, ImageCropComponent], template: `
|
|
34197
|
-
<div
|
|
34198
|
-
class="avatar-upload"
|
|
34199
|
-
[style.--avatar-size.px]="config().size"
|
|
34200
|
-
[class.avatar-upload--loading]="loading()"
|
|
34201
|
-
>
|
|
34202
|
-
<div class="avatar-container">
|
|
34203
|
-
<!-- Avatar Image or Initials -->
|
|
34204
|
-
@if (displayUrl()) {
|
|
34205
|
-
<img
|
|
34206
|
-
class="avatar-image"
|
|
34207
|
-
[src]="displayUrl()"
|
|
34208
|
-
alt="Avatar"
|
|
34209
|
-
(error)="onImageError()"
|
|
34210
|
-
/>
|
|
34211
|
-
} @else {
|
|
34212
|
-
<div
|
|
34213
|
-
class="avatar-initials"
|
|
34214
|
-
[style.background-color]="config().backgroundColor"
|
|
34215
|
-
>
|
|
34216
|
-
{{ config().initials || '?' }}
|
|
34217
|
-
</div>
|
|
34218
|
-
}
|
|
34219
|
-
|
|
34220
|
-
<!-- Edit Button -->
|
|
34221
|
-
@if (config().editable && !loading()) {
|
|
34222
|
-
<button
|
|
34223
|
-
class="edit-button"
|
|
34224
|
-
type="button"
|
|
34225
|
-
(click)="openFilePicker()"
|
|
34226
|
-
[attr.aria-label]="editButtonLabel()"
|
|
34227
|
-
>
|
|
34228
|
-
<ion-icon name="camera-outline"></ion-icon>
|
|
34229
|
-
</button>
|
|
34230
|
-
}
|
|
34231
|
-
|
|
34232
|
-
<!-- Loading Overlay -->
|
|
34233
|
-
@if (loading()) {
|
|
34234
|
-
<div class="loading-overlay">
|
|
34235
|
-
<ion-spinner name="crescent"></ion-spinner>
|
|
34236
|
-
</div>
|
|
34237
|
-
}
|
|
34238
|
-
</div>
|
|
34239
|
-
|
|
34240
|
-
<!-- Hidden File Input -->
|
|
34241
|
-
<input
|
|
34242
|
-
#fileInput
|
|
34243
|
-
type="file"
|
|
34244
|
-
accept="image/jpeg,image/png,image/webp"
|
|
34245
|
-
(change)="onFileSelected($event)"
|
|
34246
|
-
hidden
|
|
34247
|
-
/>
|
|
34248
|
-
|
|
34249
|
-
<!-- Crop Modal -->
|
|
34250
|
-
<ion-modal
|
|
34251
|
-
[isOpen]="showCropModal()"
|
|
34252
|
-
(didDismiss)="onCropCancel()"
|
|
34253
|
-
[breakpoints]="[0, 1]"
|
|
34254
|
-
[initialBreakpoint]="1"
|
|
34255
|
-
>
|
|
34256
|
-
<ng-template>
|
|
34257
|
-
@if (selectedFile()) {
|
|
34258
|
-
<val-image-crop
|
|
34259
|
-
[image]="selectedFile()!"
|
|
34260
|
-
[aspectRatio]="1"
|
|
34261
|
-
[roundCropper]="true"
|
|
34262
|
-
[i18nNamespace]="config().i18nNamespace"
|
|
34263
|
-
(cropComplete)="onCropComplete($event)"
|
|
34264
|
-
(cancel)="onCropCancel()"
|
|
34265
|
-
(loadFailed)="onCropLoadFailed()"
|
|
34266
|
-
/>
|
|
34267
|
-
}
|
|
34268
|
-
</ng-template>
|
|
34269
|
-
</ion-modal>
|
|
34270
|
-
</div>
|
|
34271
|
-
`, styles: [".avatar-upload{--avatar-size: 100px;--edit-button-size: 32px;--edit-button-offset: 4px;display:inline-block}.avatar-container{position:relative;width:var(--avatar-size);height:var(--avatar-size);border-radius:50%;overflow:visible}.avatar-image{width:100%;height:100%;border-radius:50%;object-fit:cover;background-color:var(--ion-color-light)}.avatar-initials{width:100%;height:100%;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:calc(var(--avatar-size) * .4);font-weight:600;color:#fff;text-transform:uppercase;-webkit-user-select:none;user-select:none}.edit-button{position:absolute;bottom:var(--edit-button-offset);right:var(--edit-button-offset);width:var(--edit-button-size);height:var(--edit-button-size);border-radius:50%;border:2px solid white;background:var(--ion-color-primary);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:transform .2s ease,background-color .2s ease;box-shadow:0 2px 8px #00000026}.edit-button ion-icon{font-size:calc(var(--edit-button-size) * .5)}.edit-button:hover{transform:scale(1.1);background:var(--ion-color-primary-shade)}.edit-button:active{transform:scale(.95)}.edit-button:focus-visible{outline:2px solid var(--ion-color-primary);outline-offset:2px}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;border-radius:50%;background:#00000080;display:flex;align-items:center;justify-content:center}.loading-overlay ion-spinner{--color: white;width:calc(var(--avatar-size) * .4);height:calc(var(--avatar-size) * .4)}.avatar-upload--loading .edit-button{display:none}.avatar-upload--loading .avatar-image,.avatar-upload--loading .avatar-initials{filter:brightness(.7)}@container (max-width: 60px){.edit-button{--edit-button-size: 24px;--edit-button-offset: 0}}\n"] }]
|
|
34272
|
-
}], propDecorators: { fileInput: [{
|
|
34273
|
-
type: ViewChild,
|
|
34274
|
-
args: ['fileInput']
|
|
34275
|
-
}], uploaded: [{
|
|
34276
|
-
type: Output
|
|
34277
|
-
}], error: [{
|
|
34278
|
-
type: Output
|
|
34279
|
-
}], uploadStart: [{
|
|
34280
|
-
type: Output
|
|
34281
|
-
}] } });
|
|
34282
|
-
|
|
34283
33504
|
class LayoutComponent {
|
|
34284
33505
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
34285
33506
|
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: LayoutComponent, isStandalone: true, selector: "val-layout", ngImport: i0, template: `
|
|
@@ -41225,7 +40446,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
41225
40446
|
class DocsPageComponent {
|
|
41226
40447
|
constructor() {
|
|
41227
40448
|
this.elementRef = inject(ElementRef);
|
|
41228
|
-
this.router = inject(Router);
|
|
41229
40449
|
// Use signal internally so computed properties react to changes
|
|
41230
40450
|
this._props = signal({ title: '' });
|
|
41231
40451
|
this.tocItems = signal([]);
|
|
@@ -41271,22 +40491,6 @@ class DocsPageComponent {
|
|
|
41271
40491
|
homeRoute: props.breadcrumb.homeRoute ?? ['/'],
|
|
41272
40492
|
};
|
|
41273
40493
|
});
|
|
41274
|
-
this.feedbackProps = computed(() => {
|
|
41275
|
-
const props = this._props();
|
|
41276
|
-
const feedback = props.feedback;
|
|
41277
|
-
// Derive entityId from route if not provided
|
|
41278
|
-
const entityId = feedback?.entityId || this.router.url.replace(/^\//, '').replace(/\//g, '-') || 'unknown';
|
|
41279
|
-
return {
|
|
41280
|
-
entityRef: {
|
|
41281
|
-
entityType: feedback?.entityType || 'docs-page',
|
|
41282
|
-
entityId: entityId,
|
|
41283
|
-
},
|
|
41284
|
-
question: feedback?.question,
|
|
41285
|
-
showComment: feedback?.showComment ?? true,
|
|
41286
|
-
allowAnonymous: feedback?.allowAnonymous ?? true,
|
|
41287
|
-
showThankYou: true,
|
|
41288
|
-
};
|
|
41289
|
-
});
|
|
41290
40494
|
}
|
|
41291
40495
|
set props(value) {
|
|
41292
40496
|
this._props.set(value);
|
|
@@ -41358,12 +40562,6 @@ class DocsPageComponent {
|
|
|
41358
40562
|
<ng-content></ng-content>
|
|
41359
40563
|
</div>
|
|
41360
40564
|
|
|
41361
|
-
@if (props.feedback?.enabled) {
|
|
41362
|
-
<div class="docs-page__feedback">
|
|
41363
|
-
<val-content-reaction [props]="feedbackProps()"></val-content-reaction>
|
|
41364
|
-
</div>
|
|
41365
|
-
}
|
|
41366
|
-
|
|
41367
40565
|
@if (showNavLinks()) {
|
|
41368
40566
|
<val-docs-nav-links [props]="navLinksProps()"></val-docs-nav-links>
|
|
41369
40567
|
}
|
|
@@ -41375,11 +40573,11 @@ class DocsPageComponent {
|
|
|
41375
40573
|
</aside>
|
|
41376
40574
|
}
|
|
41377
40575
|
</div>
|
|
41378
|
-
`, isInline: true, styles: [".docs-page{display:grid;grid-template-columns:1fr;gap:2rem;max-width:1400px;margin:0 auto;padding:2rem 1.5rem}@media (min-width: 1200px){.docs-page{grid-template-columns:1fr 220px;padding:2rem}}.docs-page__content{min-width:0;max-width:900px}val-docs-breadcrumb{display:block;margin-bottom:1rem}.docs-page__header{margin-bottom:2rem}.docs-page__title-row{display:flex;align-items:center;gap:.75rem;flex-wrap:wrap}.docs-page__title{margin:0;font-size:2rem;font-weight:700;color:var(--ion-text-color, #1a1a1a);line-height:1.2}@media (min-width: 768px){.docs-page__title{font-size:2.5rem}}.docs-page__badge{display:inline-flex;align-items:center;padding:.25rem .5rem;font-size:.6875rem;font-weight:500;text-transform:uppercase;letter-spacing:.03em;border-radius:4px;background:#0000000a;color:#888}.docs-page__badge--success{background:#0000000a;color:#888}.docs-page__badge--warning{background:var(--ion-color-warning-tint, #fff3e0);color:var(--ion-color-warning-shade, #e65100)}.docs-page__badge--danger{background:var(--ion-color-danger-tint, #ffebee);color:var(--ion-color-danger-shade, #c62828)}.docs-page__lead{margin:1rem 0 0;font-size:1.125rem;line-height:1.7;color:var(--ion-color-medium, #666)}.docs-page__sections>*:last-child{margin-bottom:0}.docs-
|
|
40576
|
+
`, isInline: true, styles: [".docs-page{display:grid;grid-template-columns:1fr;gap:2rem;max-width:1400px;margin:0 auto;padding:2rem 1.5rem}@media (min-width: 1200px){.docs-page{grid-template-columns:1fr 220px;padding:2rem}}.docs-page__content{min-width:0;max-width:900px}val-docs-breadcrumb{display:block;margin-bottom:1rem}.docs-page__header{margin-bottom:2rem}.docs-page__title-row{display:flex;align-items:center;gap:.75rem;flex-wrap:wrap}.docs-page__title{margin:0;font-size:2rem;font-weight:700;color:var(--ion-text-color, #1a1a1a);line-height:1.2}@media (min-width: 768px){.docs-page__title{font-size:2.5rem}}.docs-page__badge{display:inline-flex;align-items:center;padding:.25rem .5rem;font-size:.6875rem;font-weight:500;text-transform:uppercase;letter-spacing:.03em;border-radius:4px;background:#0000000a;color:#888}.docs-page__badge--success{background:#0000000a;color:#888}.docs-page__badge--warning{background:var(--ion-color-warning-tint, #fff3e0);color:var(--ion-color-warning-shade, #e65100)}.docs-page__badge--danger{background:var(--ion-color-danger-tint, #ffebee);color:var(--ion-color-danger-shade, #c62828)}.docs-page__lead{margin:1rem 0 0;font-size:1.125rem;line-height:1.7;color:var(--ion-color-medium, #666)}.docs-page__sections>*:last-child{margin-bottom:0}.docs-page__toc{display:none}@media (min-width: 1200px){.docs-page__toc{display:block;position:sticky;top:2rem;height:fit-content;max-height:calc(100vh - 4rem);overflow-y:auto}}.docs-page__sections h2{font-size:1.5rem;font-weight:600;margin:0 0 1rem;color:var(--ion-text-color, #1a1a1a);scroll-margin-top:2rem}.docs-page__sections h3{font-size:1.125rem;font-weight:600;margin:1.5rem 0 1rem;color:var(--ion-text-color, #1a1a1a)}.docs-page__sections h4{font-size:1rem;font-weight:600;margin:1.25rem 0 .75rem;color:var(--ion-text-color, #1a1a1a)}.docs-page__sections p{line-height:1.7;color:var(--ion-text-color, #1a1a1a);margin:0 0 1rem}.docs-page__sections ul,.docs-page__sections ol{padding-left:1.5rem;margin:0 0 1rem}.docs-page__sections li{margin-bottom:.5rem;line-height:1.6;color:var(--ion-text-color, #1a1a1a)}.docs-page__sections a{color:var(--ion-color-primary, #3880ff);text-decoration:none}.docs-page__sections a:hover{text-decoration:underline}.docs-page__sections code:not([class*=language-]){background:#0000000f;padding:.125rem .375rem;border-radius:4px;font-family:SF Mono,Fira Code,Consolas,monospace;font-size:.875em}.docs-page__sections pre:not([class*=language-]){background:#0000000a;padding:1rem;border-radius:8px;overflow-x:auto;margin:0 0 1rem}.docs-page__sections pre:not([class*=language-]) code{background:none;padding:0}.docs-page__sections table{width:100%;border-collapse:collapse;margin:0 0 1rem;font-size:.875rem}.docs-page__sections th,.docs-page__sections td{padding:.75rem;text-align:left;border-bottom:1px solid rgba(0,0,0,.1)}.docs-page__sections th{font-weight:600;background:#00000005}.docs-page__sections blockquote{margin:0 0 1rem;padding:1rem 1.5rem;border-left:4px solid var(--ion-color-primary, #3880ff);background:#00000005;border-radius:0 8px 8px 0}.docs-page__sections blockquote p:last-child{margin-bottom:0}.docs-page__sections img{max-width:100%;height:auto;border-radius:8px}.docs-page__sections hr{border:none;border-top:1px solid rgba(0,0,0,.1);margin:2rem 0}.docs-page__sections strong{font-weight:600}.docs-page__sections section{margin-bottom:2rem}:host-context(.dark) .docs-page__badge,:host-context([color-scheme=\"dark\"]) .docs-page__badge{background:#ffffff0f;color:#999}:host-context(.dark) .docs-page__badge--success,:host-context([color-scheme=\"dark\"]) .docs-page__badge--success{background:#ffffff0f;color:#999}:host-context(.dark) .docs-page__badge--warning,:host-context([color-scheme=\"dark\"]) .docs-page__badge--warning{background:#e6510033;color:#ffb74d}:host-context(.dark) .docs-page__badge--danger,:host-context([color-scheme=\"dark\"]) .docs-page__badge--danger{background:#c6282833;color:#e57373}:host-context(.dark) .docs-page__sections code:not([class*=language-]),:host-context([color-scheme=\"dark\"]) .docs-page__sections code:not([class*=language-]){background:#ffffff1a}:host-context(.dark) .docs-page__sections pre:not([class*=language-]),:host-context([color-scheme=\"dark\"]) .docs-page__sections pre:not([class*=language-]){background:#ffffff0f}:host-context(.dark) .docs-page__sections th,:host-context([color-scheme=\"dark\"]) .docs-page__sections th{background:#ffffff0a}:host-context(.dark) .docs-page__sections th,:host-context(.dark) .docs-page__sections td,:host-context([color-scheme=\"dark\"]) .docs-page__sections th,:host-context([color-scheme=\"dark\"]) .docs-page__sections td{border-color:#ffffff1a}:host-context(.dark) .docs-page__sections blockquote,:host-context([color-scheme=\"dark\"]) .docs-page__sections blockquote{background:#ffffff0a}:host-context(.dark) .docs-page__sections hr,:host-context([color-scheme=\"dark\"]) .docs-page__sections hr{border-color:#ffffff1a}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DocsBreadcrumbComponent, selector: "val-docs-breadcrumb", inputs: ["props"], outputs: ["navigate"] }, { kind: "component", type: DocsNavLinksComponent, selector: "val-docs-nav-links", inputs: ["props"], outputs: ["navigate"] }, { kind: "component", type: DocsTocComponent, selector: "val-docs-toc", inputs: ["props"], outputs: ["sectionChange"] }] }); }
|
|
41379
40577
|
}
|
|
41380
40578
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DocsPageComponent, decorators: [{
|
|
41381
40579
|
type: Component,
|
|
41382
|
-
args: [{ selector: 'val-docs-page', standalone: true, imports: [CommonModule, DocsBreadcrumbComponent, DocsNavLinksComponent, DocsTocComponent
|
|
40580
|
+
args: [{ selector: 'val-docs-page', standalone: true, imports: [CommonModule, DocsBreadcrumbComponent, DocsNavLinksComponent, DocsTocComponent], template: `
|
|
41383
40581
|
<div class="docs-page" [class]="props.cssClass">
|
|
41384
40582
|
<div class="docs-page__content" #content>
|
|
41385
40583
|
@if (breadcrumbProps()) {
|
|
@@ -41408,12 +40606,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
41408
40606
|
<ng-content></ng-content>
|
|
41409
40607
|
</div>
|
|
41410
40608
|
|
|
41411
|
-
@if (props.feedback?.enabled) {
|
|
41412
|
-
<div class="docs-page__feedback">
|
|
41413
|
-
<val-content-reaction [props]="feedbackProps()"></val-content-reaction>
|
|
41414
|
-
</div>
|
|
41415
|
-
}
|
|
41416
|
-
|
|
41417
40609
|
@if (showNavLinks()) {
|
|
41418
40610
|
<val-docs-nav-links [props]="navLinksProps()"></val-docs-nav-links>
|
|
41419
40611
|
}
|
|
@@ -41425,7 +40617,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
41425
40617
|
</aside>
|
|
41426
40618
|
}
|
|
41427
40619
|
</div>
|
|
41428
|
-
`, styles: [".docs-page{display:grid;grid-template-columns:1fr;gap:2rem;max-width:1400px;margin:0 auto;padding:2rem 1.5rem}@media (min-width: 1200px){.docs-page{grid-template-columns:1fr 220px;padding:2rem}}.docs-page__content{min-width:0;max-width:900px}val-docs-breadcrumb{display:block;margin-bottom:1rem}.docs-page__header{margin-bottom:2rem}.docs-page__title-row{display:flex;align-items:center;gap:.75rem;flex-wrap:wrap}.docs-page__title{margin:0;font-size:2rem;font-weight:700;color:var(--ion-text-color, #1a1a1a);line-height:1.2}@media (min-width: 768px){.docs-page__title{font-size:2.5rem}}.docs-page__badge{display:inline-flex;align-items:center;padding:.25rem .5rem;font-size:.6875rem;font-weight:500;text-transform:uppercase;letter-spacing:.03em;border-radius:4px;background:#0000000a;color:#888}.docs-page__badge--success{background:#0000000a;color:#888}.docs-page__badge--warning{background:var(--ion-color-warning-tint, #fff3e0);color:var(--ion-color-warning-shade, #e65100)}.docs-page__badge--danger{background:var(--ion-color-danger-tint, #ffebee);color:var(--ion-color-danger-shade, #c62828)}.docs-page__lead{margin:1rem 0 0;font-size:1.125rem;line-height:1.7;color:var(--ion-color-medium, #666)}.docs-page__sections>*:last-child{margin-bottom:0}.docs-
|
|
40620
|
+
`, styles: [".docs-page{display:grid;grid-template-columns:1fr;gap:2rem;max-width:1400px;margin:0 auto;padding:2rem 1.5rem}@media (min-width: 1200px){.docs-page{grid-template-columns:1fr 220px;padding:2rem}}.docs-page__content{min-width:0;max-width:900px}val-docs-breadcrumb{display:block;margin-bottom:1rem}.docs-page__header{margin-bottom:2rem}.docs-page__title-row{display:flex;align-items:center;gap:.75rem;flex-wrap:wrap}.docs-page__title{margin:0;font-size:2rem;font-weight:700;color:var(--ion-text-color, #1a1a1a);line-height:1.2}@media (min-width: 768px){.docs-page__title{font-size:2.5rem}}.docs-page__badge{display:inline-flex;align-items:center;padding:.25rem .5rem;font-size:.6875rem;font-weight:500;text-transform:uppercase;letter-spacing:.03em;border-radius:4px;background:#0000000a;color:#888}.docs-page__badge--success{background:#0000000a;color:#888}.docs-page__badge--warning{background:var(--ion-color-warning-tint, #fff3e0);color:var(--ion-color-warning-shade, #e65100)}.docs-page__badge--danger{background:var(--ion-color-danger-tint, #ffebee);color:var(--ion-color-danger-shade, #c62828)}.docs-page__lead{margin:1rem 0 0;font-size:1.125rem;line-height:1.7;color:var(--ion-color-medium, #666)}.docs-page__sections>*:last-child{margin-bottom:0}.docs-page__toc{display:none}@media (min-width: 1200px){.docs-page__toc{display:block;position:sticky;top:2rem;height:fit-content;max-height:calc(100vh - 4rem);overflow-y:auto}}.docs-page__sections h2{font-size:1.5rem;font-weight:600;margin:0 0 1rem;color:var(--ion-text-color, #1a1a1a);scroll-margin-top:2rem}.docs-page__sections h3{font-size:1.125rem;font-weight:600;margin:1.5rem 0 1rem;color:var(--ion-text-color, #1a1a1a)}.docs-page__sections h4{font-size:1rem;font-weight:600;margin:1.25rem 0 .75rem;color:var(--ion-text-color, #1a1a1a)}.docs-page__sections p{line-height:1.7;color:var(--ion-text-color, #1a1a1a);margin:0 0 1rem}.docs-page__sections ul,.docs-page__sections ol{padding-left:1.5rem;margin:0 0 1rem}.docs-page__sections li{margin-bottom:.5rem;line-height:1.6;color:var(--ion-text-color, #1a1a1a)}.docs-page__sections a{color:var(--ion-color-primary, #3880ff);text-decoration:none}.docs-page__sections a:hover{text-decoration:underline}.docs-page__sections code:not([class*=language-]){background:#0000000f;padding:.125rem .375rem;border-radius:4px;font-family:SF Mono,Fira Code,Consolas,monospace;font-size:.875em}.docs-page__sections pre:not([class*=language-]){background:#0000000a;padding:1rem;border-radius:8px;overflow-x:auto;margin:0 0 1rem}.docs-page__sections pre:not([class*=language-]) code{background:none;padding:0}.docs-page__sections table{width:100%;border-collapse:collapse;margin:0 0 1rem;font-size:.875rem}.docs-page__sections th,.docs-page__sections td{padding:.75rem;text-align:left;border-bottom:1px solid rgba(0,0,0,.1)}.docs-page__sections th{font-weight:600;background:#00000005}.docs-page__sections blockquote{margin:0 0 1rem;padding:1rem 1.5rem;border-left:4px solid var(--ion-color-primary, #3880ff);background:#00000005;border-radius:0 8px 8px 0}.docs-page__sections blockquote p:last-child{margin-bottom:0}.docs-page__sections img{max-width:100%;height:auto;border-radius:8px}.docs-page__sections hr{border:none;border-top:1px solid rgba(0,0,0,.1);margin:2rem 0}.docs-page__sections strong{font-weight:600}.docs-page__sections section{margin-bottom:2rem}:host-context(.dark) .docs-page__badge,:host-context([color-scheme=\"dark\"]) .docs-page__badge{background:#ffffff0f;color:#999}:host-context(.dark) .docs-page__badge--success,:host-context([color-scheme=\"dark\"]) .docs-page__badge--success{background:#ffffff0f;color:#999}:host-context(.dark) .docs-page__badge--warning,:host-context([color-scheme=\"dark\"]) .docs-page__badge--warning{background:#e6510033;color:#ffb74d}:host-context(.dark) .docs-page__badge--danger,:host-context([color-scheme=\"dark\"]) .docs-page__badge--danger{background:#c6282833;color:#e57373}:host-context(.dark) .docs-page__sections code:not([class*=language-]),:host-context([color-scheme=\"dark\"]) .docs-page__sections code:not([class*=language-]){background:#ffffff1a}:host-context(.dark) .docs-page__sections pre:not([class*=language-]),:host-context([color-scheme=\"dark\"]) .docs-page__sections pre:not([class*=language-]){background:#ffffff0f}:host-context(.dark) .docs-page__sections th,:host-context([color-scheme=\"dark\"]) .docs-page__sections th{background:#ffffff0a}:host-context(.dark) .docs-page__sections th,:host-context(.dark) .docs-page__sections td,:host-context([color-scheme=\"dark\"]) .docs-page__sections th,:host-context([color-scheme=\"dark\"]) .docs-page__sections td{border-color:#ffffff1a}:host-context(.dark) .docs-page__sections blockquote,:host-context([color-scheme=\"dark\"]) .docs-page__sections blockquote{background:#ffffff0a}:host-context(.dark) .docs-page__sections hr,:host-context([color-scheme=\"dark\"]) .docs-page__sections hr{border-color:#ffffff1a}\n"] }]
|
|
41429
40621
|
}], propDecorators: { props: [{
|
|
41430
40622
|
type: Input
|
|
41431
40623
|
}] } });
|
|
@@ -41917,5 +41109,5 @@ function buildFooterLinks(links, t) {
|
|
|
41917
41109
|
* Generated bundle index. Do not edit.
|
|
41918
41110
|
*/
|
|
41919
41111
|
|
|
41920
|
-
export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_SPACING,
|
|
41112
|
+
export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_SPACING, AccordionComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AppConfigService, ArticleBuilder, ArticleComponent, AuthBackgroundComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, CheckInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContentLoaderComponent, ContentReactionComponent, CountdownComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_CANCEL_BUTTON, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_LEGEND_LABELS, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PAYMENT_STATUS_COLORS, DEFAULT_PAYMENT_STATUS_LABELS, DEFAULT_PLATFORMS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_STATUS_COLORS, DEFAULT_STATUS_LABELS, DEFAULT_WINNER_LABELS, DataTableComponent, DateInputComponent, DateRangeInputComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DownloadService, EmailInputComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FabComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FooterComponent, FooterLinksComponent, FormComponent, FormFooterComponent, FormSkeletonComponent, FunHeaderComponent, GlowCardComponent, GridSkeletonComponent, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, Icon, IconComponent, IconService, ImageComponent, InAppBrowserService, InfiniteListComponent, InfoComponent, InputI18nHelper, InputType, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LOGIN_DEFAULTS, LanguageSelectorComponent, LayeredCardComponent, LayoutComponent, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginComponent, MODAL_SIZES, MOTION, MaintenancePageComponent, MenuComponent, MessagingService, MetaService, ModalService, MultiSelectSearchComponent, NavigationService, NoContentComponent, NotesBoxComponent, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PLATFORM_CONFIGS, PageContentComponent, PageTemplateComponent, PageWrapperComponent, PaginationComponent, PaginationService, ParticipantCardComponent, PasswordInputComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, RadioInputComponent, RaffleStatusCardComponent, RangeInputComponent, RatingComponent, RecapCardComponent, RefresherComponent, RightsFooterComponent, RotatingTextComponent, SKELETON_PRESETS, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SegmentControlComponent, SelectSearchComponent, SessionService, ShareButtonsComponent, SimpleComponent, SkeletonComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TicketGridComponent, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TranslatePipe, TypedCollection, UpdateBannerComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_DEFAULT_CONTENT, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_SOCIAL_LINKS, VERSION, WinnerDisplayComponent, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, buildFooterLinks, buildPath, collections, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createTitleProps, extractPathParams, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, goToTop, guestGuard, hasEmulators, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, permissionGuard, permissionGuardFromRoute, provideValtechAds, provideValtechAppConfig, provideValtechAuth, provideValtechAuthInterceptor, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechPresets, provideValtechSkeleton, query, replaceSpecialChars, resolveColor, resolveInputDefaultValue, roleGuard, storagePaths, superAdminGuard };
|
|
41921
41113
|
//# sourceMappingURL=valtech-components.mjs.map
|