@smartbit4all/ng-client 7.3.41 → 7.3.43
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/WIDGETS.md +6 -2
- package/fesm2022/smartbit4all-ng-client.mjs +1007 -415
- package/fesm2022/smartbit4all-ng-client.mjs.map +1 -1
- package/package.json +1 -1
- package/smartbit4all-ng-client-7.3.43.tgz +0 -0
- package/types/smartbit4all-ng-client.d.ts +115 -3
- package/smartbit4all-ng-client-7.3.41.tgz +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, reflectComponentType, Injectable, inject, DOCUMENT, PLATFORM_ID, Optional, Inject, SkipSelf, NgModule, RendererStyleFlags2, input, computed, Component, HostListener, HostBinding, Directive, ViewContainerRef, ElementRef, Renderer2, ChangeDetectorRef, Input, output, Injector, signal, afterNextRender, CSP_NONCE, ViewChild, ViewChildren, effect, untracked, NgZone, DestroyRef, forwardRef, createComponent, EnvironmentInjector, ApplicationRef, ChangeDetectionStrategy, ViewEncapsulation, Pipe,
|
|
2
|
+
import { InjectionToken, reflectComponentType, Injectable, inject, DOCUMENT, PLATFORM_ID, Optional, Inject, SkipSelf, NgModule, RendererStyleFlags2, input, computed, Component, HostListener, HostBinding, Directive, ViewContainerRef, ElementRef, Renderer2, ChangeDetectorRef, Input, output, Injector, signal, afterNextRender, CSP_NONCE, ViewChild, ViewChildren, effect, untracked, viewChild, NgZone, DestroyRef, forwardRef, createComponent, EnvironmentInjector, ApplicationRef, ChangeDetectionStrategy, ViewEncapsulation, Pipe, runInInjectionContext, EventEmitter, Output, makeEnvironmentProviders, importProvidersFrom, provideEnvironmentInitializer } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common/http';
|
|
4
4
|
import { HttpHeaders, HttpContext, HttpErrorResponse, HttpParams, provideHttpClient, withInterceptors, withInterceptorsFromDi } from '@angular/common/http';
|
|
5
5
|
import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';
|
|
@@ -3481,7 +3481,13 @@ class SmartTooltipDirective {
|
|
|
3481
3481
|
...(ngDevMode ? [undefined, { debugName: "smartTooltipClass" }] : /* istanbul ignore next */ []));
|
|
3482
3482
|
this.cursor = 'pointer';
|
|
3483
3483
|
}
|
|
3484
|
-
// A keyboard user reaches the element by focus, never by hover
|
|
3484
|
+
// A keyboard user reaches the element by focus, never by hover; a focus a script gives
|
|
3485
|
+
// (a dialog restoring it, the focus restore after a push) is no reason to show.
|
|
3486
|
+
focusin(event) {
|
|
3487
|
+
if (event.target.matches(':focus-visible')) {
|
|
3488
|
+
this.mouseover();
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3485
3491
|
mouseover() {
|
|
3486
3492
|
const smartTooltip = this.smartTooltip();
|
|
3487
3493
|
if (!smartTooltip)
|
|
@@ -3515,7 +3521,7 @@ class SmartTooltipDirective {
|
|
|
3515
3521
|
return delay ? delay : 2000;
|
|
3516
3522
|
}
|
|
3517
3523
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartTooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
3518
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: SmartTooltipDirective, isStandalone: true, selector: "[smartTooltip]", inputs: { smartTooltip: { classPropertyName: "smartTooltip", publicName: "smartTooltip", isSignal: true, isRequired: false, transformFunction: null }, smartTooltipClass: { classPropertyName: "smartTooltipClass", publicName: "smartTooltipClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "focusin": "
|
|
3524
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: SmartTooltipDirective, isStandalone: true, selector: "[smartTooltip]", inputs: { smartTooltip: { classPropertyName: "smartTooltip", publicName: "smartTooltip", isSignal: true, isRequired: false, transformFunction: null }, smartTooltipClass: { classPropertyName: "smartTooltipClass", publicName: "smartTooltipClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "focusin": "focusin($event)", "mouseover": "mouseover()", "focusout": "mouseleave()", "mouseleave": "mouseleave()" }, properties: { "style.cursor": "this.cursor" } }, providers: [MatTooltip], ngImport: i0 }); }
|
|
3519
3525
|
}
|
|
3520
3526
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartTooltipDirective, decorators: [{
|
|
3521
3527
|
type: Directive,
|
|
@@ -3526,10 +3532,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
3526
3532
|
}], propDecorators: { smartTooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "smartTooltip", required: false }] }], smartTooltipClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "smartTooltipClass", required: false }] }], cursor: [{
|
|
3527
3533
|
type: HostBinding,
|
|
3528
3534
|
args: ['style.cursor']
|
|
3529
|
-
}],
|
|
3535
|
+
}], focusin: [{
|
|
3530
3536
|
type: HostListener,
|
|
3531
|
-
args: ['focusin']
|
|
3532
|
-
}, {
|
|
3537
|
+
args: ['focusin', ['$event']]
|
|
3538
|
+
}], mouseover: [{
|
|
3533
3539
|
type: HostListener,
|
|
3534
3540
|
args: ['mouseover']
|
|
3535
3541
|
}], mouseleave: [{
|
|
@@ -7341,8 +7347,8 @@ class SmartdialogService {
|
|
|
7341
7347
|
});
|
|
7342
7348
|
return this.dialogRef;
|
|
7343
7349
|
}
|
|
7344
|
-
createDialogRef(smartDialog, component) {
|
|
7345
|
-
const dialogRef = this.dialog.open(component, { data: smartDialog });
|
|
7350
|
+
createDialogRef(smartDialog, component, config) {
|
|
7351
|
+
const dialogRef = this.dialog.open(component, { ...config, data: smartDialog });
|
|
7346
7352
|
dialogRef.afterClosed().subscribe((result) => {
|
|
7347
7353
|
this.handleAfterClosed(result);
|
|
7348
7354
|
dialogRef.close();
|
|
@@ -7773,6 +7779,86 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
7773
7779
|
|
|
7774
7780
|
const APIS$3 = [DefaultService];
|
|
7775
7781
|
|
|
7782
|
+
/**
|
|
7783
|
+
* Form layout definition
|
|
7784
|
+
* Contains form layout definition objects.
|
|
7785
|
+
*
|
|
7786
|
+
* The version of the OpenAPI document: 1.0.0
|
|
7787
|
+
* Contact: info@it4all.hu
|
|
7788
|
+
*
|
|
7789
|
+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
|
7790
|
+
* https://openapi-generator.tech
|
|
7791
|
+
* Do not edit the class manually.
|
|
7792
|
+
*/
|
|
7793
|
+
/**
|
|
7794
|
+
* When the camera or the microphone starts: AUTO the moment the capture UI opens (the recorder starts recording), MANUAL on a switch-on button.
|
|
7795
|
+
*/
|
|
7796
|
+
var CaptureDeviceStart;
|
|
7797
|
+
(function (CaptureDeviceStart) {
|
|
7798
|
+
CaptureDeviceStart["AUTO"] = "AUTO";
|
|
7799
|
+
CaptureDeviceStart["MANUAL"] = "MANUAL";
|
|
7800
|
+
})(CaptureDeviceStart || (CaptureDeviceStart = {}));
|
|
7801
|
+
|
|
7802
|
+
/**
|
|
7803
|
+
* Form layout definition
|
|
7804
|
+
* Contains form layout definition objects.
|
|
7805
|
+
*
|
|
7806
|
+
* The version of the OpenAPI document: 1.0.0
|
|
7807
|
+
* Contact: info@it4all.hu
|
|
7808
|
+
*
|
|
7809
|
+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
|
7810
|
+
* https://openapi-generator.tech
|
|
7811
|
+
* Do not edit the class manually.
|
|
7812
|
+
*/
|
|
7813
|
+
/**
|
|
7814
|
+
* What happens to a taken photo, a stopped video or a stopped recording: CONFIRM shows it for confirmation (retake / use, playback / save), IMMEDIATE hands it to the widget at once.
|
|
7815
|
+
*/
|
|
7816
|
+
var CaptureHandover;
|
|
7817
|
+
(function (CaptureHandover) {
|
|
7818
|
+
CaptureHandover["CONFIRM"] = "CONFIRM";
|
|
7819
|
+
CaptureHandover["IMMEDIATE"] = "IMMEDIATE";
|
|
7820
|
+
})(CaptureHandover || (CaptureHandover = {}));
|
|
7821
|
+
|
|
7822
|
+
/**
|
|
7823
|
+
* Form layout definition
|
|
7824
|
+
* Contains form layout definition objects.
|
|
7825
|
+
*
|
|
7826
|
+
* The version of the OpenAPI document: 1.0.0
|
|
7827
|
+
* Contact: info@it4all.hu
|
|
7828
|
+
*
|
|
7829
|
+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
|
7830
|
+
* https://openapi-generator.tech
|
|
7831
|
+
* Do not edit the class manually.
|
|
7832
|
+
*/
|
|
7833
|
+
/**
|
|
7834
|
+
* Where the capture UI of a file widget opens: INLINE under the capture buttons, DIALOG in a dialog of its own.
|
|
7835
|
+
*/
|
|
7836
|
+
var CaptureMode;
|
|
7837
|
+
(function (CaptureMode) {
|
|
7838
|
+
CaptureMode["INLINE"] = "INLINE";
|
|
7839
|
+
CaptureMode["DIALOG"] = "DIALOG";
|
|
7840
|
+
})(CaptureMode || (CaptureMode = {}));
|
|
7841
|
+
|
|
7842
|
+
/**
|
|
7843
|
+
* Form layout definition
|
|
7844
|
+
* Contains form layout definition objects.
|
|
7845
|
+
*
|
|
7846
|
+
* The version of the OpenAPI document: 1.0.0
|
|
7847
|
+
* Contact: info@it4all.hu
|
|
7848
|
+
*
|
|
7849
|
+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
|
7850
|
+
* https://openapi-generator.tech
|
|
7851
|
+
* Do not edit the class manually.
|
|
7852
|
+
*/
|
|
7853
|
+
/**
|
|
7854
|
+
* The side the inline recorder\'s waveform strip grows to from its microphone button: LEFT to the left with the button at the right edge, RIGHT to the right with the button at the left edge.
|
|
7855
|
+
*/
|
|
7856
|
+
var CaptureOpenDirection;
|
|
7857
|
+
(function (CaptureOpenDirection) {
|
|
7858
|
+
CaptureOpenDirection["LEFT"] = "LEFT";
|
|
7859
|
+
CaptureOpenDirection["RIGHT"] = "RIGHT";
|
|
7860
|
+
})(CaptureOpenDirection || (CaptureOpenDirection = {}));
|
|
7861
|
+
|
|
7776
7862
|
/**
|
|
7777
7863
|
* Form layout definition
|
|
7778
7864
|
* Contains form layout definition objects.
|
|
@@ -9099,6 +9185,18 @@ class UploadWidgetUtils {
|
|
|
9099
9185
|
const value = parseFloat((bytes / Math.pow(k, i)).toFixed(1));
|
|
9100
9186
|
return `${value} ${sizes[i]}`;
|
|
9101
9187
|
}
|
|
9188
|
+
/** The size of a capture for the Capture Dialog's captions: a decimal point in English, a
|
|
9189
|
+
* decimal comma in Hungarian and without a known locale. */
|
|
9190
|
+
static formatCapturedSize(bytes, locale) {
|
|
9191
|
+
const size = UploadWidgetUtils.formatSize(bytes);
|
|
9192
|
+
return locale?.startsWith('en') ? size : size.replace('.', ',');
|
|
9193
|
+
}
|
|
9194
|
+
/** A length of time as mm:ss. */
|
|
9195
|
+
static formatDuration(milliseconds) {
|
|
9196
|
+
return [Math.floor((milliseconds % 3600000) / 60000), Math.floor((milliseconds % 60000) / 1000)]
|
|
9197
|
+
.map((v) => (v < 10 ? '0' + v : v))
|
|
9198
|
+
.join(':');
|
|
9199
|
+
}
|
|
9102
9200
|
/** The recording mime of a media kind: webm where the browser records it, else mp4 (Safari). */
|
|
9103
9201
|
static chooseRecorderMimeType(kind) {
|
|
9104
9202
|
return MediaRecorder.isTypeSupported(`${kind}/webm`) ? `${kind}/webm` : `${kind}/mp4`;
|
|
@@ -9110,6 +9208,89 @@ class UploadWidgetUtils {
|
|
|
9110
9208
|
const ext = mimeType.includes('webm') ? 'webm' : 'mp4';
|
|
9111
9209
|
return new File([blob], `${prefix}_${formattedDate}.${ext}`, { type: mimeType });
|
|
9112
9210
|
}
|
|
9211
|
+
/** An image re-encoded as a jpeg, smaller step by step until it fits; the file itself when it
|
|
9212
|
+
* does not fit at the floor of the quality and the size, or is no image. */
|
|
9213
|
+
static async compressImageFile(file, maxFileSize) {
|
|
9214
|
+
if (!file.type.startsWith('image/'))
|
|
9215
|
+
return file;
|
|
9216
|
+
const img = await new Promise((resolve, reject) => {
|
|
9217
|
+
const url = URL.createObjectURL(file);
|
|
9218
|
+
const image = new Image();
|
|
9219
|
+
image.onload = () => {
|
|
9220
|
+
URL.revokeObjectURL(url);
|
|
9221
|
+
resolve(image);
|
|
9222
|
+
};
|
|
9223
|
+
image.onerror = (e) => {
|
|
9224
|
+
URL.revokeObjectURL(url);
|
|
9225
|
+
reject(e);
|
|
9226
|
+
};
|
|
9227
|
+
image.src = url;
|
|
9228
|
+
});
|
|
9229
|
+
const canvas = document.createElement('canvas');
|
|
9230
|
+
const ctx = canvas.getContext('2d');
|
|
9231
|
+
if (!ctx)
|
|
9232
|
+
return file;
|
|
9233
|
+
let width = img.naturalWidth;
|
|
9234
|
+
let height = img.naturalHeight;
|
|
9235
|
+
let quality = 0.9;
|
|
9236
|
+
const minQuality = 0.4;
|
|
9237
|
+
const minDimension = 640;
|
|
9238
|
+
const getBlob = (q) => new Promise((resolve) => {
|
|
9239
|
+
canvas.width = width;
|
|
9240
|
+
canvas.height = height;
|
|
9241
|
+
ctx.clearRect(0, 0, width, height);
|
|
9242
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
9243
|
+
canvas.toBlob((blob) => {
|
|
9244
|
+
if (blob)
|
|
9245
|
+
resolve(blob);
|
|
9246
|
+
}, 'image/jpeg', q);
|
|
9247
|
+
});
|
|
9248
|
+
let blob = await getBlob(quality);
|
|
9249
|
+
while (blob.size > maxFileSize) {
|
|
9250
|
+
if (quality > minQuality) {
|
|
9251
|
+
quality -= 0.1;
|
|
9252
|
+
}
|
|
9253
|
+
else if (width > minDimension && height > minDimension) {
|
|
9254
|
+
width = Math.floor(width * 0.8);
|
|
9255
|
+
height = Math.floor(height * 0.8);
|
|
9256
|
+
quality = 0.9;
|
|
9257
|
+
}
|
|
9258
|
+
else {
|
|
9259
|
+
break;
|
|
9260
|
+
}
|
|
9261
|
+
blob = await getBlob(quality);
|
|
9262
|
+
}
|
|
9263
|
+
if (blob.size <= maxFileSize) {
|
|
9264
|
+
return new File([blob], file.name, { type: 'image/jpeg' });
|
|
9265
|
+
}
|
|
9266
|
+
else {
|
|
9267
|
+
return file;
|
|
9268
|
+
}
|
|
9269
|
+
}
|
|
9270
|
+
/** The descriptor of a capture button: raised, in the primary colour unless told otherwise. */
|
|
9271
|
+
static createCaptureButton(title, icon, color = 'primary', type = UiActionButtonType.RAISED) {
|
|
9272
|
+
return { type, title, icon, iconPosition: IconPosition.PRE, color };
|
|
9273
|
+
}
|
|
9274
|
+
/** The button that takes a picture. */
|
|
9275
|
+
static createTakePictureButton(texts) {
|
|
9276
|
+
return UploadWidgetUtils.createCaptureButton(texts.takePicture, 'camera');
|
|
9277
|
+
}
|
|
9278
|
+
/** The button that starts a video recording. */
|
|
9279
|
+
static createStartRecordingButton(texts) {
|
|
9280
|
+
return UploadWidgetUtils.createCaptureButton(texts.startRecording, 'fiber_manual_record');
|
|
9281
|
+
}
|
|
9282
|
+
/** The button that stops a recording, in the colour of its host. */
|
|
9283
|
+
static createStopRecordingButton(texts, color) {
|
|
9284
|
+
return UploadWidgetUtils.createCaptureButton(texts.stopRecording, 'stop', color);
|
|
9285
|
+
}
|
|
9286
|
+
/** The button that discards the kept capture for a new one. */
|
|
9287
|
+
static createRetakeButton(texts, type = UiActionButtonType.RAISED) {
|
|
9288
|
+
return UploadWidgetUtils.createCaptureButton(texts.retake, 'replay', 'primary', type);
|
|
9289
|
+
}
|
|
9290
|
+
/** The button that accepts the kept capture. */
|
|
9291
|
+
static createUseButton(texts) {
|
|
9292
|
+
return UploadWidgetUtils.createCaptureButton(texts.use, 'check');
|
|
9293
|
+
}
|
|
9113
9294
|
/** The zone's texts: a descriptor's text per key, the client's default for a missing one. */
|
|
9114
9295
|
static resolveTexts(texts) {
|
|
9115
9296
|
return { ...UPLOAD_ZONE_DEFAULT_TEXTS, ...texts };
|
|
@@ -9147,11 +9328,23 @@ const UPLOAD_ZONE_DEFAULT_TEXTS = {
|
|
|
9147
9328
|
cameraError: 'A kamera nem érhető el.',
|
|
9148
9329
|
microphoneError: 'A mikrofon nem érhető el.',
|
|
9149
9330
|
play: 'Lejátszás',
|
|
9331
|
+
photoDialogTitle: 'Fénykép készítése',
|
|
9332
|
+
videoDialogTitle: 'Videó felvétele',
|
|
9333
|
+
audioDialogTitle: 'Hangfelvétel',
|
|
9334
|
+
cancel: 'Mégse',
|
|
9335
|
+
retake: 'Újra',
|
|
9336
|
+
use: 'Használ',
|
|
9337
|
+
save: 'Mentés',
|
|
9338
|
+
capturedPhoto: 'Elkészült kép',
|
|
9339
|
+
capturedVideo: 'Felvétel',
|
|
9340
|
+
recordingHint: 'A lejátszás ikon visszajátssza, a mikrofon gomb új felvételt indít (felülírja ezt).',
|
|
9341
|
+
microphoneRetryHint: 'A mikrofon gomb újra kéri az engedélyt.',
|
|
9150
9342
|
};
|
|
9151
9343
|
|
|
9152
9344
|
class SmartfileuploaderComponent {
|
|
9153
9345
|
constructor() {
|
|
9154
9346
|
this._snackBar = inject(MatSnackBar);
|
|
9347
|
+
this.changeDetectorRef = inject(ChangeDetectorRef);
|
|
9155
9348
|
this.files = [];
|
|
9156
9349
|
this.uploadCallback = input.required(/* @ts-ignore */
|
|
9157
9350
|
...(ngDevMode ? [{ debugName: "uploadCallback" }] : /* istanbul ignore next */ []));
|
|
@@ -9225,6 +9418,8 @@ class SmartfileuploaderComponent {
|
|
|
9225
9418
|
if (this.autoUpload() && this.errors.length == 0) {
|
|
9226
9419
|
this.uploadFile();
|
|
9227
9420
|
}
|
|
9421
|
+
// A Capture Dialog hands its file over outside any template event of this view.
|
|
9422
|
+
this.changeDetectorRef.markForCheck();
|
|
9228
9423
|
}
|
|
9229
9424
|
getFile(event) {
|
|
9230
9425
|
if (event.target.files && event.target.files.length) {
|
|
@@ -9334,11 +9529,11 @@ class SmartfileuploaderComponent {
|
|
|
9334
9529
|
};
|
|
9335
9530
|
}
|
|
9336
9531
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartfileuploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9337
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SmartfileuploaderComponent, isStandalone: true, selector: "smartfileuploader", inputs: { uploadCallback: { classPropertyName: "uploadCallback", publicName: "uploadCallback", isSignal: true, isRequired: true, transformFunction: null }, fileFormats: { classPropertyName: "fileFormats", publicName: "fileFormats", isSignal: true, isRequired: false, transformFunction: null }, maxSizeMb: { classPropertyName: "maxSizeMb", publicName: "maxSizeMb", isSignal: true, isRequired: false, transformFunction: null }, i18n: { classPropertyName: "i18n", publicName: "i18n", isSignal: false, isRequired: false, transformFunction: null }, useIconButton: { classPropertyName: "useIconButton", publicName: "useIconButton", isSignal: true, isRequired: false, transformFunction: null }, isMultiple: { classPropertyName: "isMultiple", publicName: "isMultiple", isSignal: true, isRequired: false, transformFunction: null }, autoUpload: { classPropertyName: "autoUpload", publicName: "autoUpload", isSignal: true, isRequired: false, transformFunction: null }, isDisabled: { classPropertyName: "isDisabled", publicName: "isDisabled", isSignal: true, isRequired: false, transformFunction: null }, pickerShown: { classPropertyName: "pickerShown", publicName: "pickerShown", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "<div\n class=\"container\"\n [ngClass]=\"{ disabledWidget: isDisabled() }\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [class.dragover]=\"isDragOver\"\n [class.captureOnly]=\"!pickerShown()\"\n>\n @if (pickerShown()) {\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n <input\n #fileInput\n id=\"addFile\"\n placeholder=\"fileInput\"\n type=\"file\"\n (change)=\"getFile($event)\"\n class=\"file\"\n accept=\"{{ fileFormats()?.join(', ') }}\"\n [size]=\"maxSizeMb()\"\n [multiple]=\"isMultiple()\"\n [disabled]=\"isDisabled()\"\n style=\"display: none\"\n />\n <div class=\"fileUploadContentContainer\">\n <div class=\"icon\">\n @if (useIconButton()) {\n <mat-label class=\"addFileButton\">\n <mat-icon fontSet=\"material-symbols-outlined\" color=\"primary\" class=\"addCircle\"\n >add_circle</mat-icon\n >\n </mat-label>\n }\n @if (!useIconButton()) {\n <button mat-raised-button color=\"primary\" class=\"addFileButton\">\n {{ i18n!.addFile }}\n </button>\n }\n </div>\n <div class=\"labels\">\n @if (useIconButton()) {\n <mat-label class=\"label primary title\">\n {{ i18n!.addFile }}\n </mat-label>\n }\n <mat-label class=\"label secondary\"> {{ i18n!.browseOrDrag }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.maxSize }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.formats }} </mat-label>\n </div>\n @if (files.length) {\n <div class=\"uploadButton\">\n <ui-action-button\n (actionClick)=\"uploadFile($event)\"\n [code]=\"'default-upload'\"\n [descriptor]=\"uploadButton\"\n >\n </ui-action-button>\n </div>\n }\n </div>\n </div>\n }\n\n @if (errors.length) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"'error'\" [color]=\"'warn'\"></smart-icon>\n <div class=\"errors\">\n @for (error of errors; track error) {\n <span>{{ error }}</span>\n }\n </div>\n </div>\n }\n\n @if (files.length) {\n <div class=\"uploadedFilesContainer\">\n @for (file of files; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @if (filePreviewUrl(file)) {\n <img [src]=\"filePreviewUrl(file)\" [alt]=\"file.name\" width=\"50\" height=\"50\" />\n } @else {\n <smart-icon [icon]=\"'insert_drive_file'\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file.name }}</span>\n <span class=\"fileSize\">{{ formatSize(file.size) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file)\"\n [attr.data-testid]=\"'default_download'\"\n ></smart-icon>\n <smart-icon\n class=\"removeIcon\"\n icon=\"delete\"\n [attr.data-testid]=\"'default_remove'\"\n (click)=\"remove(i)\"\n ></smart-icon>\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem}.container{display:flex;flex-direction:column;border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.container.captureOnly{border:none}.fileContainer{cursor:pointer;padding:1rem;display:flex;flex-direction:column}.fileContainer:hover{background-color:rgb(from var(--border-color) r g b / .1)}.disabledWidget ::ng-deep .fileContainer:hover{background-color:unset;cursor:unset}.disabledWidget ::ng-deep *{opacity:.85}.dragover{background-color:rgb(from var(--border-color) r g b / .1)}.fileUploadContentContainer{display:flex;flex-direction:row;gap:1rem}.icon mat-icon{font-size:3rem;width:unset;height:unset}.icon{align-content:center}.labels{flex:1;display:flex;flex-direction:column;justify-content:center;width:fit-content}.uploadButton{align-content:center}.primary{color:var(--primary-color)}.secondary{color:var(--gray)}.label{margin:unset}.subLabel{font-size:smaller}.title{padding-bottom:.3rem}.errorMessage,.uploadedFilesContainer{border-top:2px solid var(--border-color)}.errorMessage{display:flex;flex-direction:row;align-items:center;color:var(--warn-color);padding:.5rem 1rem;gap:1rem}.errorMessage ::ng-deep mat-icon{color:var(--warn-color)}.errors{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0;gap:1rem}.uploadedFile:last-child{border-bottom:none}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }] }); }
|
|
9532
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SmartfileuploaderComponent, isStandalone: true, selector: "smartfileuploader", inputs: { uploadCallback: { classPropertyName: "uploadCallback", publicName: "uploadCallback", isSignal: true, isRequired: true, transformFunction: null }, fileFormats: { classPropertyName: "fileFormats", publicName: "fileFormats", isSignal: true, isRequired: false, transformFunction: null }, maxSizeMb: { classPropertyName: "maxSizeMb", publicName: "maxSizeMb", isSignal: true, isRequired: false, transformFunction: null }, i18n: { classPropertyName: "i18n", publicName: "i18n", isSignal: false, isRequired: false, transformFunction: null }, useIconButton: { classPropertyName: "useIconButton", publicName: "useIconButton", isSignal: true, isRequired: false, transformFunction: null }, isMultiple: { classPropertyName: "isMultiple", publicName: "isMultiple", isSignal: true, isRequired: false, transformFunction: null }, autoUpload: { classPropertyName: "autoUpload", publicName: "autoUpload", isSignal: true, isRequired: false, transformFunction: null }, isDisabled: { classPropertyName: "isDisabled", publicName: "isDisabled", isSignal: true, isRequired: false, transformFunction: null }, pickerShown: { classPropertyName: "pickerShown", publicName: "pickerShown", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "<div\n class=\"container\"\n [ngClass]=\"{ disabledWidget: isDisabled() }\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [class.dragover]=\"isDragOver\"\n [class.captureOnly]=\"!pickerShown()\"\n>\n @if (pickerShown()) {\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n <input\n #fileInput\n id=\"addFile\"\n placeholder=\"fileInput\"\n type=\"file\"\n (change)=\"getFile($event)\"\n class=\"file\"\n accept=\"{{ fileFormats()?.join(', ') }}\"\n [size]=\"maxSizeMb()\"\n [multiple]=\"isMultiple()\"\n [disabled]=\"isDisabled()\"\n style=\"display: none\"\n />\n <div class=\"fileUploadContentContainer\">\n <div class=\"icon\">\n @if (useIconButton()) {\n <mat-label class=\"addFileButton\">\n <mat-icon fontSet=\"material-symbols-outlined\" color=\"primary\" class=\"addCircle\"\n >add_circle</mat-icon\n >\n </mat-label>\n }\n @if (!useIconButton()) {\n <button mat-raised-button color=\"primary\" class=\"addFileButton\">\n {{ i18n!.addFile }}\n </button>\n }\n </div>\n <div class=\"labels\">\n @if (useIconButton()) {\n <mat-label class=\"label primary title\">\n {{ i18n!.addFile }}\n </mat-label>\n }\n <mat-label class=\"label secondary\"> {{ i18n!.browseOrDrag }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.maxSize }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.formats }} </mat-label>\n </div>\n @if (files.length) {\n <div class=\"uploadButton\">\n <ui-action-button\n (actionClick)=\"uploadFile($event)\"\n [code]=\"'default-upload'\"\n [descriptor]=\"uploadButton\"\n >\n </ui-action-button>\n </div>\n }\n </div>\n </div>\n }\n\n @if (errors.length) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"'error'\" [color]=\"'warn'\"></smart-icon>\n <div class=\"errors\">\n @for (error of errors; track error) {\n <span>{{ error }}</span>\n }\n </div>\n </div>\n }\n\n @if (files.length) {\n <div class=\"uploadedFilesContainer\">\n @for (file of files; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @if (filePreviewUrl(file)) {\n <img [src]=\"filePreviewUrl(file)\" [alt]=\"file.name\" width=\"50\" height=\"50\" />\n } @else {\n <smart-icon [icon]=\"'insert_drive_file'\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file.name }}</span>\n <span class=\"fileSize\">{{ formatSize(file.size) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file)\"\n [attr.data-testid]=\"'default_download'\"\n ></smart-icon>\n <smart-icon\n class=\"removeIcon\"\n icon=\"delete\"\n [attr.data-testid]=\"'default_remove'\"\n (click)=\"remove(i)\"\n ></smart-icon>\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem}.container{display:flex;flex-direction:column;border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.container.captureOnly{border:none}.fileContainer{cursor:pointer;padding:1rem;display:flex;flex-direction:column}.fileContainer:hover{background-color:rgb(from var(--border-color) r g b / .1)}.disabledWidget ::ng-deep .fileContainer:hover{background-color:unset;cursor:unset}.disabledWidget ::ng-deep *{opacity:.85}.dragover{background-color:rgb(from var(--border-color) r g b / .1)}.fileUploadContentContainer{display:flex;flex-direction:row;gap:1rem}.icon mat-icon{font-size:3rem;width:unset;height:unset}.icon .addCircle{color:var(--primary-color)}.icon{align-content:center}.labels{flex:1;display:flex;flex-direction:column;justify-content:center;width:fit-content}.uploadButton{align-content:center}.primary{color:var(--primary-color)}.secondary{color:var(--gray)}.label{margin:unset}.subLabel{font-size:smaller}.title{padding-bottom:.3rem}.errorMessage,.uploadedFilesContainer{border-top:2px solid var(--border-color)}.errorMessage{display:flex;flex-direction:row;align-items:center;color:var(--warn-color);padding:.5rem 1rem;gap:1rem}.errorMessage ::ng-deep mat-icon{color:var(--warn-color)}.errors{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0;gap:1rem}.uploadedFile:last-child{border-bottom:none}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }] }); }
|
|
9338
9533
|
}
|
|
9339
9534
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartfileuploaderComponent, decorators: [{
|
|
9340
9535
|
type: Component,
|
|
9341
|
-
args: [{ selector: 'smartfileuploader', imports: [NgClass, MatLabel, MatIcon, MatButton, UiActionButtonComponent, SmartIconComponent], template: "<div\n class=\"container\"\n [ngClass]=\"{ disabledWidget: isDisabled() }\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [class.dragover]=\"isDragOver\"\n [class.captureOnly]=\"!pickerShown()\"\n>\n @if (pickerShown()) {\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n <input\n #fileInput\n id=\"addFile\"\n placeholder=\"fileInput\"\n type=\"file\"\n (change)=\"getFile($event)\"\n class=\"file\"\n accept=\"{{ fileFormats()?.join(', ') }}\"\n [size]=\"maxSizeMb()\"\n [multiple]=\"isMultiple()\"\n [disabled]=\"isDisabled()\"\n style=\"display: none\"\n />\n <div class=\"fileUploadContentContainer\">\n <div class=\"icon\">\n @if (useIconButton()) {\n <mat-label class=\"addFileButton\">\n <mat-icon fontSet=\"material-symbols-outlined\" color=\"primary\" class=\"addCircle\"\n >add_circle</mat-icon\n >\n </mat-label>\n }\n @if (!useIconButton()) {\n <button mat-raised-button color=\"primary\" class=\"addFileButton\">\n {{ i18n!.addFile }}\n </button>\n }\n </div>\n <div class=\"labels\">\n @if (useIconButton()) {\n <mat-label class=\"label primary title\">\n {{ i18n!.addFile }}\n </mat-label>\n }\n <mat-label class=\"label secondary\"> {{ i18n!.browseOrDrag }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.maxSize }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.formats }} </mat-label>\n </div>\n @if (files.length) {\n <div class=\"uploadButton\">\n <ui-action-button\n (actionClick)=\"uploadFile($event)\"\n [code]=\"'default-upload'\"\n [descriptor]=\"uploadButton\"\n >\n </ui-action-button>\n </div>\n }\n </div>\n </div>\n }\n\n @if (errors.length) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"'error'\" [color]=\"'warn'\"></smart-icon>\n <div class=\"errors\">\n @for (error of errors; track error) {\n <span>{{ error }}</span>\n }\n </div>\n </div>\n }\n\n @if (files.length) {\n <div class=\"uploadedFilesContainer\">\n @for (file of files; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @if (filePreviewUrl(file)) {\n <img [src]=\"filePreviewUrl(file)\" [alt]=\"file.name\" width=\"50\" height=\"50\" />\n } @else {\n <smart-icon [icon]=\"'insert_drive_file'\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file.name }}</span>\n <span class=\"fileSize\">{{ formatSize(file.size) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file)\"\n [attr.data-testid]=\"'default_download'\"\n ></smart-icon>\n <smart-icon\n class=\"removeIcon\"\n icon=\"delete\"\n [attr.data-testid]=\"'default_remove'\"\n (click)=\"remove(i)\"\n ></smart-icon>\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem}.container{display:flex;flex-direction:column;border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.container.captureOnly{border:none}.fileContainer{cursor:pointer;padding:1rem;display:flex;flex-direction:column}.fileContainer:hover{background-color:rgb(from var(--border-color) r g b / .1)}.disabledWidget ::ng-deep .fileContainer:hover{background-color:unset;cursor:unset}.disabledWidget ::ng-deep *{opacity:.85}.dragover{background-color:rgb(from var(--border-color) r g b / .1)}.fileUploadContentContainer{display:flex;flex-direction:row;gap:1rem}.icon mat-icon{font-size:3rem;width:unset;height:unset}.icon{align-content:center}.labels{flex:1;display:flex;flex-direction:column;justify-content:center;width:fit-content}.uploadButton{align-content:center}.primary{color:var(--primary-color)}.secondary{color:var(--gray)}.label{margin:unset}.subLabel{font-size:smaller}.title{padding-bottom:.3rem}.errorMessage,.uploadedFilesContainer{border-top:2px solid var(--border-color)}.errorMessage{display:flex;flex-direction:row;align-items:center;color:var(--warn-color);padding:.5rem 1rem;gap:1rem}.errorMessage ::ng-deep mat-icon{color:var(--warn-color)}.errors{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0;gap:1rem}.uploadedFile:last-child{border-bottom:none}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}\n"] }]
|
|
9536
|
+
args: [{ selector: 'smartfileuploader', imports: [NgClass, MatLabel, MatIcon, MatButton, UiActionButtonComponent, SmartIconComponent], template: "<div\n class=\"container\"\n [ngClass]=\"{ disabledWidget: isDisabled() }\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [class.dragover]=\"isDragOver\"\n [class.captureOnly]=\"!pickerShown()\"\n>\n @if (pickerShown()) {\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n <input\n #fileInput\n id=\"addFile\"\n placeholder=\"fileInput\"\n type=\"file\"\n (change)=\"getFile($event)\"\n class=\"file\"\n accept=\"{{ fileFormats()?.join(', ') }}\"\n [size]=\"maxSizeMb()\"\n [multiple]=\"isMultiple()\"\n [disabled]=\"isDisabled()\"\n style=\"display: none\"\n />\n <div class=\"fileUploadContentContainer\">\n <div class=\"icon\">\n @if (useIconButton()) {\n <mat-label class=\"addFileButton\">\n <mat-icon fontSet=\"material-symbols-outlined\" color=\"primary\" class=\"addCircle\"\n >add_circle</mat-icon\n >\n </mat-label>\n }\n @if (!useIconButton()) {\n <button mat-raised-button color=\"primary\" class=\"addFileButton\">\n {{ i18n!.addFile }}\n </button>\n }\n </div>\n <div class=\"labels\">\n @if (useIconButton()) {\n <mat-label class=\"label primary title\">\n {{ i18n!.addFile }}\n </mat-label>\n }\n <mat-label class=\"label secondary\"> {{ i18n!.browseOrDrag }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.maxSize }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.formats }} </mat-label>\n </div>\n @if (files.length) {\n <div class=\"uploadButton\">\n <ui-action-button\n (actionClick)=\"uploadFile($event)\"\n [code]=\"'default-upload'\"\n [descriptor]=\"uploadButton\"\n >\n </ui-action-button>\n </div>\n }\n </div>\n </div>\n }\n\n @if (errors.length) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"'error'\" [color]=\"'warn'\"></smart-icon>\n <div class=\"errors\">\n @for (error of errors; track error) {\n <span>{{ error }}</span>\n }\n </div>\n </div>\n }\n\n @if (files.length) {\n <div class=\"uploadedFilesContainer\">\n @for (file of files; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @if (filePreviewUrl(file)) {\n <img [src]=\"filePreviewUrl(file)\" [alt]=\"file.name\" width=\"50\" height=\"50\" />\n } @else {\n <smart-icon [icon]=\"'insert_drive_file'\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file.name }}</span>\n <span class=\"fileSize\">{{ formatSize(file.size) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file)\"\n [attr.data-testid]=\"'default_download'\"\n ></smart-icon>\n <smart-icon\n class=\"removeIcon\"\n icon=\"delete\"\n [attr.data-testid]=\"'default_remove'\"\n (click)=\"remove(i)\"\n ></smart-icon>\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem}.container{display:flex;flex-direction:column;border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.container.captureOnly{border:none}.fileContainer{cursor:pointer;padding:1rem;display:flex;flex-direction:column}.fileContainer:hover{background-color:rgb(from var(--border-color) r g b / .1)}.disabledWidget ::ng-deep .fileContainer:hover{background-color:unset;cursor:unset}.disabledWidget ::ng-deep *{opacity:.85}.dragover{background-color:rgb(from var(--border-color) r g b / .1)}.fileUploadContentContainer{display:flex;flex-direction:row;gap:1rem}.icon mat-icon{font-size:3rem;width:unset;height:unset}.icon .addCircle{color:var(--primary-color)}.icon{align-content:center}.labels{flex:1;display:flex;flex-direction:column;justify-content:center;width:fit-content}.uploadButton{align-content:center}.primary{color:var(--primary-color)}.secondary{color:var(--gray)}.label{margin:unset}.subLabel{font-size:smaller}.title{padding-bottom:.3rem}.errorMessage,.uploadedFilesContainer{border-top:2px solid var(--border-color)}.errorMessage{display:flex;flex-direction:row;align-items:center;color:var(--warn-color);padding:.5rem 1rem;gap:1rem}.errorMessage ::ng-deep mat-icon{color:var(--warn-color)}.errors{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0;gap:1rem}.uploadedFile:last-child{border-bottom:none}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}\n"] }]
|
|
9342
9537
|
}], propDecorators: { fileInput: [{
|
|
9343
9538
|
type: ViewChild,
|
|
9344
9539
|
args: ['fileInput']
|
|
@@ -9346,8 +9541,658 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
9346
9541
|
type: Input
|
|
9347
9542
|
}], useIconButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "useIconButton", required: false }] }], isMultiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMultiple", required: false }] }], autoUpload: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoUpload", required: false }] }], isDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDisabled", required: false }] }], pickerShown: [{ type: i0.Input, args: [{ isSignal: true, alias: "pickerShown", required: false }] }] } });
|
|
9348
9543
|
|
|
9544
|
+
/** The camera of one capture view: the stream, the recorder and the capture kept for a decision.
|
|
9545
|
+
* The view calls release() when it closes. */
|
|
9546
|
+
class CameraDevice {
|
|
9547
|
+
constructor(options) {
|
|
9548
|
+
this.options = options;
|
|
9549
|
+
/** A signal, because it is set after an `await`, outside the change detection of the click. */
|
|
9550
|
+
this.isCameraActive = signal(false, /* @ts-ignore */
|
|
9551
|
+
...(ngDevMode ? [{ debugName: "isCameraActive" }] : /* istanbul ignore next */ []));
|
|
9552
|
+
/** Set when the browser gives no camera. */
|
|
9553
|
+
this.cameraError = signal(false, /* @ts-ignore */
|
|
9554
|
+
...(ngDevMode ? [{ debugName: "cameraError" }] : /* istanbul ignore next */ []));
|
|
9555
|
+
/** True while a camera request runs. */
|
|
9556
|
+
this.isCameraStarting = signal(false, /* @ts-ignore */
|
|
9557
|
+
...(ngDevMode ? [{ debugName: "isCameraStarting" }] : /* istanbul ignore next */ []));
|
|
9558
|
+
this.isRecording = signal(false, /* @ts-ignore */
|
|
9559
|
+
...(ngDevMode ? [{ debugName: "isRecording" }] : /* istanbul ignore next */ []));
|
|
9560
|
+
this.isCaptured = signal(false, /* @ts-ignore */
|
|
9561
|
+
...(ngDevMode ? [{ debugName: "isCaptured" }] : /* istanbul ignore next */ []));
|
|
9562
|
+
/** A capture kept for retake or use under the CONFIRM handover. */
|
|
9563
|
+
this.keptFiles = signal(undefined, /* @ts-ignore */
|
|
9564
|
+
...(ngDevMode ? [{ debugName: "keptFiles" }] : /* istanbul ignore next */ []));
|
|
9565
|
+
/** The playback address of a kept video. */
|
|
9566
|
+
this.keptVideoUrl = signal(undefined, /* @ts-ignore */
|
|
9567
|
+
...(ngDevMode ? [{ debugName: "keptVideoUrl" }] : /* istanbul ignore next */ []));
|
|
9568
|
+
/** The milliseconds of the running video, kept as the length of the stopped one. */
|
|
9569
|
+
this.recordingTime = signal(0, /* @ts-ignore */
|
|
9570
|
+
...(ngDevMode ? [{ debugName: "recordingTime" }] : /* istanbul ignore next */ []));
|
|
9571
|
+
/** True while a host may not lose the capture by accident: a device is asked for or runs, or
|
|
9572
|
+
* a capture waits for confirmation. */
|
|
9573
|
+
this.isCaptureUnderWay = computed(() => this.isCameraStarting() ||
|
|
9574
|
+
this.isCameraActive() ||
|
|
9575
|
+
this.isRecording() ||
|
|
9576
|
+
!!this.keptFiles(), /* @ts-ignore */
|
|
9577
|
+
...(ngDevMode ? [{ debugName: "isCaptureUnderWay" }] : /* istanbul ignore next */ []));
|
|
9578
|
+
this.recordingStartTime = 0;
|
|
9579
|
+
/** Advanced by stopCamera(), so a stream the browser gives after a close is stopped at once. */
|
|
9580
|
+
this.cameraRequest = 0;
|
|
9581
|
+
}
|
|
9582
|
+
async startCamera() {
|
|
9583
|
+
this.cameraError.set(false);
|
|
9584
|
+
this.isCameraStarting.set(true);
|
|
9585
|
+
const request = ++this.cameraRequest;
|
|
9586
|
+
let stream;
|
|
9587
|
+
try {
|
|
9588
|
+
stream = await this.openCameraStream();
|
|
9589
|
+
}
|
|
9590
|
+
catch {
|
|
9591
|
+
if (request === this.cameraRequest) {
|
|
9592
|
+
this.isCameraStarting.set(false);
|
|
9593
|
+
this.cameraError.set(true);
|
|
9594
|
+
}
|
|
9595
|
+
return;
|
|
9596
|
+
}
|
|
9597
|
+
if (request !== this.cameraRequest) {
|
|
9598
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
9599
|
+
return;
|
|
9600
|
+
}
|
|
9601
|
+
this.stream = stream;
|
|
9602
|
+
const video = this.options.getVideoElement();
|
|
9603
|
+
video.srcObject = this.stream;
|
|
9604
|
+
video.play();
|
|
9605
|
+
this.isCameraStarting.set(false);
|
|
9606
|
+
this.isCameraActive.set(true);
|
|
9607
|
+
this.isCaptured.set(false);
|
|
9608
|
+
}
|
|
9609
|
+
/** A video asks for the sound too; a desktop without a microphone records a silent one. */
|
|
9610
|
+
async openCameraStream() {
|
|
9611
|
+
if (this.options.mode() === 'VIDEO') {
|
|
9612
|
+
try {
|
|
9613
|
+
return await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
|
|
9614
|
+
}
|
|
9615
|
+
catch {
|
|
9616
|
+
// No microphone answers: the camera alone is asked below.
|
|
9617
|
+
}
|
|
9618
|
+
}
|
|
9619
|
+
return navigator.mediaDevices.getUserMedia({ video: true });
|
|
9620
|
+
}
|
|
9621
|
+
/** Closing the camera mid-recording discards the recording. */
|
|
9622
|
+
stopCamera() {
|
|
9623
|
+
this.cameraRequest++;
|
|
9624
|
+
this.isCameraStarting.set(false);
|
|
9625
|
+
clearInterval(this.recordingClock);
|
|
9626
|
+
if (this.recorder) {
|
|
9627
|
+
this.recorder.onstop = null;
|
|
9628
|
+
this.recorder.stop();
|
|
9629
|
+
this.recorder = undefined;
|
|
9630
|
+
}
|
|
9631
|
+
this.isRecording.set(false);
|
|
9632
|
+
this.stream?.getTracks().forEach((track) => track.stop());
|
|
9633
|
+
this.stream = undefined;
|
|
9634
|
+
this.isCameraActive.set(false);
|
|
9635
|
+
}
|
|
9636
|
+
toggleCamera() {
|
|
9637
|
+
if (this.isCameraActive()) {
|
|
9638
|
+
this.stopCamera();
|
|
9639
|
+
}
|
|
9640
|
+
else {
|
|
9641
|
+
this.startCamera();
|
|
9642
|
+
}
|
|
9643
|
+
}
|
|
9644
|
+
async capturePhoto() {
|
|
9645
|
+
const video = this.options.getVideoElement();
|
|
9646
|
+
const canvas = this.options.getCanvasElement();
|
|
9647
|
+
const context = canvas.getContext('2d');
|
|
9648
|
+
if (!context)
|
|
9649
|
+
return;
|
|
9650
|
+
canvas.width = video.videoWidth;
|
|
9651
|
+
canvas.height = video.videoHeight;
|
|
9652
|
+
context.drawImage(video, 0, 0);
|
|
9653
|
+
const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/jpeg', 0.9));
|
|
9654
|
+
if (!blob)
|
|
9655
|
+
return;
|
|
9656
|
+
let file = new File([blob], `photo_${Date.now()}.jpg`, { type: 'image/jpeg' });
|
|
9657
|
+
const maxFileSize = this.options.maxFileSize();
|
|
9658
|
+
if (maxFileSize && file.size > maxFileSize) {
|
|
9659
|
+
file = await UploadWidgetUtils.compressImageFile(file, maxFileSize);
|
|
9660
|
+
}
|
|
9661
|
+
this.handOverCapture([file]);
|
|
9662
|
+
this.isCaptured.set(true);
|
|
9663
|
+
this.stopCamera();
|
|
9664
|
+
}
|
|
9665
|
+
/** A browser that records neither webm nor mp4 refuses the recorder: the camera error shows. */
|
|
9666
|
+
startRecording() {
|
|
9667
|
+
let recorder;
|
|
9668
|
+
try {
|
|
9669
|
+
recorder = new MediaRecorder(this.stream, {
|
|
9670
|
+
mimeType: UploadWidgetUtils.chooseRecorderMimeType('video'),
|
|
9671
|
+
});
|
|
9672
|
+
}
|
|
9673
|
+
catch {
|
|
9674
|
+
this.cameraError.set(true);
|
|
9675
|
+
return;
|
|
9676
|
+
}
|
|
9677
|
+
const chunks = [];
|
|
9678
|
+
recorder.ondataavailable = (event) => chunks.push(event.data);
|
|
9679
|
+
recorder.onstop = () => {
|
|
9680
|
+
const blob = new Blob(chunks, { type: recorder.mimeType });
|
|
9681
|
+
this.recorder = undefined;
|
|
9682
|
+
this.updateRecordingTime();
|
|
9683
|
+
this.handOverCapture([UploadWidgetUtils.createRecordingFile(blob, 'video', 'video/mp4')]);
|
|
9684
|
+
this.stopCamera();
|
|
9685
|
+
};
|
|
9686
|
+
recorder.start();
|
|
9687
|
+
this.recorder = recorder;
|
|
9688
|
+
this.isRecording.set(true);
|
|
9689
|
+
this.recordingStartTime = Date.now();
|
|
9690
|
+
this.recordingTime.set(0);
|
|
9691
|
+
this.recordingClock = setInterval(() => this.updateRecordingTime(), 1000);
|
|
9692
|
+
}
|
|
9693
|
+
updateRecordingTime() {
|
|
9694
|
+
this.recordingTime.set(Date.now() - this.recordingStartTime);
|
|
9695
|
+
}
|
|
9696
|
+
/** The recording state holds until onstop hands the video over and stops the camera. */
|
|
9697
|
+
stopRecording() {
|
|
9698
|
+
this.recorder?.stop();
|
|
9699
|
+
}
|
|
9700
|
+
handOverCapture(files) {
|
|
9701
|
+
if (this.options.handover() === CaptureHandover.IMMEDIATE) {
|
|
9702
|
+
this.options.handOver(files);
|
|
9703
|
+
return;
|
|
9704
|
+
}
|
|
9705
|
+
this.keptFiles.set(files);
|
|
9706
|
+
if (this.options.mode() === 'VIDEO') {
|
|
9707
|
+
this.keptVideoUrl.set(URL.createObjectURL(files[0]));
|
|
9708
|
+
}
|
|
9709
|
+
}
|
|
9710
|
+
retakeCapture() {
|
|
9711
|
+
this.discardKeptCapture();
|
|
9712
|
+
if (this.options.deviceStart() === CaptureDeviceStart.AUTO) {
|
|
9713
|
+
this.startCamera();
|
|
9714
|
+
}
|
|
9715
|
+
}
|
|
9716
|
+
useCapture() {
|
|
9717
|
+
this.options.handOver(this.keptFiles());
|
|
9718
|
+
this.discardKeptCapture();
|
|
9719
|
+
}
|
|
9720
|
+
/** Stops the camera and drops what it kept: the close of the view. */
|
|
9721
|
+
release() {
|
|
9722
|
+
this.stopCamera();
|
|
9723
|
+
this.discardKeptCapture();
|
|
9724
|
+
}
|
|
9725
|
+
discardKeptCapture() {
|
|
9726
|
+
const videoUrl = this.keptVideoUrl();
|
|
9727
|
+
if (videoUrl) {
|
|
9728
|
+
URL.revokeObjectURL(videoUrl);
|
|
9729
|
+
}
|
|
9730
|
+
this.keptVideoUrl.set(undefined);
|
|
9731
|
+
this.keptFiles.set(undefined);
|
|
9732
|
+
this.isCaptured.set(false);
|
|
9733
|
+
}
|
|
9734
|
+
}
|
|
9735
|
+
|
|
9736
|
+
/** The camera view of the Capture Dialog: a fixed stage with the switch and the captions inside
|
|
9737
|
+
* it, and the buttons in actionButtonsTemplate for the dialog's footer. */
|
|
9738
|
+
class CaptureCameraViewComponent {
|
|
9739
|
+
constructor() {
|
|
9740
|
+
this.injector = inject(Injector);
|
|
9741
|
+
this.session = inject(SmartSessionService);
|
|
9742
|
+
this.video = viewChild.required('video', /* @ts-ignore */
|
|
9743
|
+
...(ngDevMode ? [{ debugName: "video" }] : /* istanbul ignore next */ []));
|
|
9744
|
+
this.canvas = viewChild.required('canvas', /* @ts-ignore */
|
|
9745
|
+
...(ngDevMode ? [{ debugName: "canvas" }] : /* istanbul ignore next */ []));
|
|
9746
|
+
this.actionButtonsTemplate = viewChild('actionButtons', /* @ts-ignore */
|
|
9747
|
+
...(ngDevMode ? [{ debugName: "actionButtonsTemplate" }] : /* istanbul ignore next */ []));
|
|
9748
|
+
this.mode = input.required(/* @ts-ignore */
|
|
9749
|
+
...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
9750
|
+
this.deviceStart = input.required(/* @ts-ignore */
|
|
9751
|
+
...(ngDevMode ? [{ debugName: "deviceStart" }] : /* istanbul ignore next */ []));
|
|
9752
|
+
this.handover = input.required(/* @ts-ignore */
|
|
9753
|
+
...(ngDevMode ? [{ debugName: "handover" }] : /* istanbul ignore next */ []));
|
|
9754
|
+
this.texts = input.required(/* @ts-ignore */
|
|
9755
|
+
...(ngDevMode ? [{ debugName: "texts" }] : /* istanbul ignore next */ []));
|
|
9756
|
+
this.maxFileSize = input(/* @ts-ignore */
|
|
9757
|
+
...(ngDevMode ? [undefined, { debugName: "maxFileSize" }] : /* istanbul ignore next */ []));
|
|
9758
|
+
this.captured = output();
|
|
9759
|
+
this.device = new CameraDevice({
|
|
9760
|
+
mode: this.mode,
|
|
9761
|
+
deviceStart: this.deviceStart,
|
|
9762
|
+
handover: this.handover,
|
|
9763
|
+
maxFileSize: this.maxFileSize,
|
|
9764
|
+
getVideoElement: () => this.video().nativeElement,
|
|
9765
|
+
getCanvasElement: () => this.canvas().nativeElement,
|
|
9766
|
+
handOver: (files) => this.captured.emit(files),
|
|
9767
|
+
});
|
|
9768
|
+
/** The timer on the stage of a running video. */
|
|
9769
|
+
this.recordingCaption = computed(() => UploadWidgetUtils.formatDuration(this.device.recordingTime()), /* @ts-ignore */
|
|
9770
|
+
...(ngDevMode ? [{ debugName: "recordingCaption" }] : /* istanbul ignore next */ []));
|
|
9771
|
+
/** The kept capture's line on the stage. */
|
|
9772
|
+
this.capturedCaption = computed(() => {
|
|
9773
|
+
const files = this.device.keptFiles();
|
|
9774
|
+
if (!files) {
|
|
9775
|
+
return undefined;
|
|
9776
|
+
}
|
|
9777
|
+
const size = UploadWidgetUtils.formatCapturedSize(files[0].size, this.session.sessionInfoData?.locale);
|
|
9778
|
+
if (this.mode() === 'PHOTO') {
|
|
9779
|
+
return `${this.texts().capturedPhoto} · ${size}`;
|
|
9780
|
+
}
|
|
9781
|
+
const duration = UploadWidgetUtils.formatDuration(this.device.recordingTime());
|
|
9782
|
+
return `${this.texts().capturedVideo} · ${duration} · ${size}`;
|
|
9783
|
+
}, /* @ts-ignore */
|
|
9784
|
+
...(ngDevMode ? [{ debugName: "capturedCaption" }] : /* istanbul ignore next */ []));
|
|
9785
|
+
/** The switch shows while the camera is off, so a second shot stays possible. */
|
|
9786
|
+
this.isCameraSwitchShown = computed(() => !this.device.keptFiles() &&
|
|
9787
|
+
!(this.device.isCameraActive() || this.device.isCameraStarting()), /* @ts-ignore */
|
|
9788
|
+
...(ngDevMode ? [{ debugName: "isCameraSwitchShown" }] : /* istanbul ignore next */ []));
|
|
9789
|
+
this.isSwitchOnHintShown = computed(() => this.isCameraSwitchShown() && !this.device.isCaptured(), /* @ts-ignore */
|
|
9790
|
+
...(ngDevMode ? [{ debugName: "isSwitchOnHintShown" }] : /* istanbul ignore next */ []));
|
|
9791
|
+
this.switchOnButton = computed(() => UploadWidgetUtils.createCaptureButton(this.texts().cameraOn, 'power_settings_new', 'primary', UiActionButtonType.NORMAL), /* @ts-ignore */
|
|
9792
|
+
...(ngDevMode ? [{ debugName: "switchOnButton" }] : /* istanbul ignore next */ []));
|
|
9793
|
+
this.captureButton = computed(() => UploadWidgetUtils.createTakePictureButton(this.texts()), /* @ts-ignore */
|
|
9794
|
+
...(ngDevMode ? [{ debugName: "captureButton" }] : /* istanbul ignore next */ []));
|
|
9795
|
+
this.startRecordingButton = computed(() => UploadWidgetUtils.createStartRecordingButton(this.texts()), /* @ts-ignore */
|
|
9796
|
+
...(ngDevMode ? [{ debugName: "startRecordingButton" }] : /* istanbul ignore next */ []));
|
|
9797
|
+
this.stopRecordingButton = computed(() => UploadWidgetUtils.createStopRecordingButton(this.texts(), 'warn'), /* @ts-ignore */
|
|
9798
|
+
...(ngDevMode ? [{ debugName: "stopRecordingButton" }] : /* istanbul ignore next */ []));
|
|
9799
|
+
this.retakeButton = computed(() => UploadWidgetUtils.createRetakeButton(this.texts(), UiActionButtonType.NORMAL), /* @ts-ignore */
|
|
9800
|
+
...(ngDevMode ? [{ debugName: "retakeButton" }] : /* istanbul ignore next */ []));
|
|
9801
|
+
this.useButton = computed(() => UploadWidgetUtils.createUseButton(this.texts()), /* @ts-ignore */
|
|
9802
|
+
...(ngDevMode ? [{ debugName: "useButton" }] : /* istanbul ignore next */ []));
|
|
9803
|
+
}
|
|
9804
|
+
ngAfterViewInit() {
|
|
9805
|
+
if (this.deviceStart() === CaptureDeviceStart.AUTO) {
|
|
9806
|
+
afterNextRender(() => this.device.startCamera(), { injector: this.injector });
|
|
9807
|
+
}
|
|
9808
|
+
}
|
|
9809
|
+
ngOnDestroy() {
|
|
9810
|
+
this.device.release();
|
|
9811
|
+
}
|
|
9812
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureCameraViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9813
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: CaptureCameraViewComponent, isStandalone: true, selector: "smart-capture-camera-view", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: true, transformFunction: null }, deviceStart: { classPropertyName: "deviceStart", publicName: "deviceStart", isSignal: true, isRequired: true, transformFunction: null }, handover: { classPropertyName: "handover", publicName: "handover", isSignal: true, isRequired: true, transformFunction: null }, texts: { classPropertyName: "texts", publicName: "texts", isSignal: true, isRequired: true, transformFunction: null }, maxFileSize: { classPropertyName: "maxFileSize", publicName: "maxFileSize", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { captured: "captured" }, viewQueries: [{ propertyName: "video", first: true, predicate: ["video"], descendants: true, isSignal: true }, { propertyName: "canvas", first: true, predicate: ["canvas"], descendants: true, isSignal: true }, { propertyName: "actionButtonsTemplate", first: true, predicate: ["actionButtons"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"cameraStage\">\n <video #video autoplay [muted]=\"true\" [hidden]=\"device.isCaptured() || !device.isCameraActive()\"></video>\n <canvas #canvas [hidden]=\"!device.isCaptured() || device.isCameraActive()\"></canvas>\n @if (device.keptVideoUrl(); as videoUrl) {\n <video controls [src]=\"videoUrl\"></video>\n }\n @if (device.cameraError()) {\n <p class=\"message cameraErrorMessage\">{{ texts().cameraError }}</p>\n } @else if (capturedCaption(); as caption) {\n <p class=\"message capturedCaption\">{{ caption }}</p>\n } @else if (device.isRecording()) {\n <p class=\"message recordingMessage\">{{ recordingCaption() }}</p>\n } @else if (isSwitchOnHintShown()) {\n @if (mode() === 'VIDEO') {\n <p class=\"message\">{{ texts().switchOnForVideo }}</p>\n } @else {\n <p class=\"message\">{{ texts().switchOnCameraHint }}</p>\n }\n }\n @if (isCameraSwitchShown()) {\n <ui-action-button\n class=\"switchOnButton\"\n [descriptor]=\"switchOnButton()\"\n (actionClick)=\"device.startCamera()\"\n ></ui-action-button>\n }\n</div>\n\n<ng-template #actionButtons>\n @if (device.keptFiles()) {\n <ui-action-button\n class=\"retakeButton\"\n [descriptor]=\"retakeButton()\"\n (actionClick)=\"device.retakeCapture()\"\n ></ui-action-button>\n <ui-action-button\n class=\"useButton\"\n [descriptor]=\"useButton()\"\n (actionClick)=\"device.useCapture()\"\n ></ui-action-button>\n } @else if (mode() === 'VIDEO') {\n @if (device.isRecording()) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"device.stopRecording()\"\n ></ui-action-button>\n } @else {\n <ui-action-button\n class=\"startRecordingButton\"\n [disabled]=\"!device.isCameraActive()\"\n [descriptor]=\"startRecordingButton()\"\n (actionClick)=\"device.startRecording()\"\n ></ui-action-button>\n }\n } @else {\n <ui-action-button\n class=\"capturePhotoButton\"\n [disabled]=\"!device.isCameraActive()\"\n [descriptor]=\"captureButton()\"\n (actionClick)=\"device.capturePhoto()\"\n ></ui-action-button>\n }\n</ng-template>\n", styles: [":host{display:block;width:100%}.cameraStage{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;aspect-ratio:16 / 9;overflow:hidden;border-radius:4px;--stage-background: var( --capture-stage-background, color-mix(in srgb, var(--primary-color) 30%, black) );background:var(--stage-background);color:var(--capture-stage-text, color-mix(in srgb, var(--primary-color) 10%, white))}.cameraStage video,.cameraStage canvas{position:absolute;inset:0;width:100%;height:100%;object-fit:contain}.cameraStage .switchOnButton ::ng-deep button{background:var(--color-surface)}.cameraStage .message{margin:0;text-align:center;color:inherit}.cameraStage .cameraErrorMessage{position:relative;color:color-mix(in srgb,var(--warn-color) 65%,white)}.cameraStage .recordingMessage,.cameraStage .capturedCaption{position:absolute;left:.5rem;top:.5rem;padding:.125rem .5rem;border-radius:.5rem;font-size:.75rem;font-weight:500}.cameraStage .recordingMessage{background:color-mix(in srgb,var(--stage-background) 67%,transparent);font-family:monospace}.cameraStage .capturedCaption{background:color-mix(in srgb,var(--success-color) 50%,black)}.cameraStage .recordingMessage:before{content:\"\";display:inline-block;width:.5rem;height:.5rem;margin-right:.3125rem;border-radius:50%;background:var(--warn-color)}\n"], dependencies: [{ kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
|
|
9814
|
+
}
|
|
9815
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureCameraViewComponent, decorators: [{
|
|
9816
|
+
type: Component,
|
|
9817
|
+
args: [{ selector: 'smart-capture-camera-view', imports: [UiActionButtonComponent], template: "<div class=\"cameraStage\">\n <video #video autoplay [muted]=\"true\" [hidden]=\"device.isCaptured() || !device.isCameraActive()\"></video>\n <canvas #canvas [hidden]=\"!device.isCaptured() || device.isCameraActive()\"></canvas>\n @if (device.keptVideoUrl(); as videoUrl) {\n <video controls [src]=\"videoUrl\"></video>\n }\n @if (device.cameraError()) {\n <p class=\"message cameraErrorMessage\">{{ texts().cameraError }}</p>\n } @else if (capturedCaption(); as caption) {\n <p class=\"message capturedCaption\">{{ caption }}</p>\n } @else if (device.isRecording()) {\n <p class=\"message recordingMessage\">{{ recordingCaption() }}</p>\n } @else if (isSwitchOnHintShown()) {\n @if (mode() === 'VIDEO') {\n <p class=\"message\">{{ texts().switchOnForVideo }}</p>\n } @else {\n <p class=\"message\">{{ texts().switchOnCameraHint }}</p>\n }\n }\n @if (isCameraSwitchShown()) {\n <ui-action-button\n class=\"switchOnButton\"\n [descriptor]=\"switchOnButton()\"\n (actionClick)=\"device.startCamera()\"\n ></ui-action-button>\n }\n</div>\n\n<ng-template #actionButtons>\n @if (device.keptFiles()) {\n <ui-action-button\n class=\"retakeButton\"\n [descriptor]=\"retakeButton()\"\n (actionClick)=\"device.retakeCapture()\"\n ></ui-action-button>\n <ui-action-button\n class=\"useButton\"\n [descriptor]=\"useButton()\"\n (actionClick)=\"device.useCapture()\"\n ></ui-action-button>\n } @else if (mode() === 'VIDEO') {\n @if (device.isRecording()) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"device.stopRecording()\"\n ></ui-action-button>\n } @else {\n <ui-action-button\n class=\"startRecordingButton\"\n [disabled]=\"!device.isCameraActive()\"\n [descriptor]=\"startRecordingButton()\"\n (actionClick)=\"device.startRecording()\"\n ></ui-action-button>\n }\n } @else {\n <ui-action-button\n class=\"capturePhotoButton\"\n [disabled]=\"!device.isCameraActive()\"\n [descriptor]=\"captureButton()\"\n (actionClick)=\"device.capturePhoto()\"\n ></ui-action-button>\n }\n</ng-template>\n", styles: [":host{display:block;width:100%}.cameraStage{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;aspect-ratio:16 / 9;overflow:hidden;border-radius:4px;--stage-background: var( --capture-stage-background, color-mix(in srgb, var(--primary-color) 30%, black) );background:var(--stage-background);color:var(--capture-stage-text, color-mix(in srgb, var(--primary-color) 10%, white))}.cameraStage video,.cameraStage canvas{position:absolute;inset:0;width:100%;height:100%;object-fit:contain}.cameraStage .switchOnButton ::ng-deep button{background:var(--color-surface)}.cameraStage .message{margin:0;text-align:center;color:inherit}.cameraStage .cameraErrorMessage{position:relative;color:color-mix(in srgb,var(--warn-color) 65%,white)}.cameraStage .recordingMessage,.cameraStage .capturedCaption{position:absolute;left:.5rem;top:.5rem;padding:.125rem .5rem;border-radius:.5rem;font-size:.75rem;font-weight:500}.cameraStage .recordingMessage{background:color-mix(in srgb,var(--stage-background) 67%,transparent);font-family:monospace}.cameraStage .capturedCaption{background:color-mix(in srgb,var(--success-color) 50%,black)}.cameraStage .recordingMessage:before{content:\"\";display:inline-block;width:.5rem;height:.5rem;margin-right:.3125rem;border-radius:50%;background:var(--warn-color)}\n"] }]
|
|
9818
|
+
}], propDecorators: { video: [{ type: i0.ViewChild, args: ['video', { isSignal: true }] }], canvas: [{ type: i0.ViewChild, args: ['canvas', { isSignal: true }] }], actionButtonsTemplate: [{ type: i0.ViewChild, args: ['actionButtons', { isSignal: true }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: true }] }], deviceStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "deviceStart", required: true }] }], handover: [{ type: i0.Input, args: [{ isSignal: true, alias: "handover", required: true }] }], texts: [{ type: i0.Input, args: [{ isSignal: true, alias: "texts", required: true }] }], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], captured: [{ type: i0.Output, args: ["captured"] }] } });
|
|
9819
|
+
|
|
9820
|
+
/** The microphone of one capture view: the record plugin, its start and the stopped recording.
|
|
9821
|
+
* The view calls release() when it closes. */
|
|
9822
|
+
class MicrophoneDevice {
|
|
9823
|
+
constructor(options) {
|
|
9824
|
+
this.options = options;
|
|
9825
|
+
this.isRecording = signal(false, /* @ts-ignore */
|
|
9826
|
+
...(ngDevMode ? [{ debugName: "isRecording" }] : /* istanbul ignore next */ []));
|
|
9827
|
+
this.recordedBlob = signal(null, /* @ts-ignore */
|
|
9828
|
+
...(ngDevMode ? [{ debugName: "recordedBlob" }] : /* istanbul ignore next */ []));
|
|
9829
|
+
this.time = signal('00:00', /* @ts-ignore */
|
|
9830
|
+
...(ngDevMode ? [{ debugName: "time" }] : /* istanbul ignore next */ []));
|
|
9831
|
+
this.isPlaying = signal(false, /* @ts-ignore */
|
|
9832
|
+
...(ngDevMode ? [{ debugName: "isPlaying" }] : /* istanbul ignore next */ []));
|
|
9833
|
+
/** Set when the browser gives no microphone, until the next start. */
|
|
9834
|
+
this.microphoneError = signal(false, /* @ts-ignore */
|
|
9835
|
+
...(ngDevMode ? [{ debugName: "microphoneError" }] : /* istanbul ignore next */ []));
|
|
9836
|
+
this.isWaveformOpen = signal(false, /* @ts-ignore */
|
|
9837
|
+
...(ngDevMode ? [{ debugName: "isWaveformOpen" }] : /* istanbul ignore next */ []));
|
|
9838
|
+
/** True while a host may not lose the capture by accident: the recording runs, its microphone
|
|
9839
|
+
* request included, or waits for the save. */
|
|
9840
|
+
this.isCaptureUnderWay = computed(() => this.isRecording() || !!this.recordedBlob(), /* @ts-ignore */
|
|
9841
|
+
...(ngDevMode ? [{ debugName: "isCaptureUnderWay" }] : /* istanbul ignore next */ []));
|
|
9842
|
+
/** True while the view shows the waveform band. */
|
|
9843
|
+
this.isWaveformShown = computed(() => this.isWaveformOpen() || this.isRecording() || !!this.recordedBlob(), /* @ts-ignore */
|
|
9844
|
+
...(ngDevMode ? [{ debugName: "isWaveformShown" }] : /* istanbul ignore next */ []));
|
|
9845
|
+
}
|
|
9846
|
+
async initializeWaveSurferIfNeeded() {
|
|
9847
|
+
const waveform = this.options.getWaveformElement();
|
|
9848
|
+
if (this.wavesurfer || !waveform) {
|
|
9849
|
+
return;
|
|
9850
|
+
}
|
|
9851
|
+
await new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
9852
|
+
if (!this.pendingStart) {
|
|
9853
|
+
return;
|
|
9854
|
+
}
|
|
9855
|
+
waveform.style.padding = '0.1rem';
|
|
9856
|
+
this.initWaveSurfer(waveform);
|
|
9857
|
+
}
|
|
9858
|
+
initWaveSurfer(waveform) {
|
|
9859
|
+
const primaryColor = getComputedStyle(this.options.getThemeElement())
|
|
9860
|
+
.getPropertyValue('--primary-color')
|
|
9861
|
+
.trim();
|
|
9862
|
+
const el = this.options.getButtonElement().querySelector('.sb4-primary');
|
|
9863
|
+
const remInPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
9864
|
+
const height = (el?.getBoundingClientRect().height ?? 0) - remInPx * 0.1;
|
|
9865
|
+
this.wavesurfer = WaveSurfer.create({
|
|
9866
|
+
container: waveform,
|
|
9867
|
+
waveColor: primaryColor,
|
|
9868
|
+
progressColor: this.addAlphaToColor(primaryColor, 0.5),
|
|
9869
|
+
height: height,
|
|
9870
|
+
barWidth: 5,
|
|
9871
|
+
barGap: 1,
|
|
9872
|
+
barRadius: 5,
|
|
9873
|
+
});
|
|
9874
|
+
this.wavesurfer.setVolume(1);
|
|
9875
|
+
const mimeType = UploadWidgetUtils.chooseRecorderMimeType('audio');
|
|
9876
|
+
const record = this.wavesurfer.registerPlugin(RecordPlugin.create({
|
|
9877
|
+
renderRecordedAudio: true,
|
|
9878
|
+
scrollingWaveform: true,
|
|
9879
|
+
continuousWaveform: false,
|
|
9880
|
+
continuousWaveformDuration: 15,
|
|
9881
|
+
mimeType,
|
|
9882
|
+
}));
|
|
9883
|
+
this.record = record;
|
|
9884
|
+
this.wavesurfer.on('dblclick', () => {
|
|
9885
|
+
this.options.ngZone.run(() => {
|
|
9886
|
+
this.wavesurfer?.play();
|
|
9887
|
+
this.isPlaying.set(true);
|
|
9888
|
+
});
|
|
9889
|
+
});
|
|
9890
|
+
this.record.on('record-end', (blob) => {
|
|
9891
|
+
if (record !== this.record) {
|
|
9892
|
+
return;
|
|
9893
|
+
}
|
|
9894
|
+
this.options.ngZone.run(() => {
|
|
9895
|
+
this.recordedBlob.set(blob);
|
|
9896
|
+
this.isRecording.set(false);
|
|
9897
|
+
});
|
|
9898
|
+
if (this.options.handover() === CaptureHandover.IMMEDIATE) {
|
|
9899
|
+
this.saveRecording();
|
|
9900
|
+
}
|
|
9901
|
+
});
|
|
9902
|
+
this.record.on('record-progress', (time) => {
|
|
9903
|
+
this.options.ngZone.run(() => {
|
|
9904
|
+
this.time.set(UploadWidgetUtils.formatDuration(time));
|
|
9905
|
+
});
|
|
9906
|
+
});
|
|
9907
|
+
this.wavesurfer.on('finish', () => {
|
|
9908
|
+
this.options.ngZone.run(() => {
|
|
9909
|
+
this.isPlaying.set(false);
|
|
9910
|
+
});
|
|
9911
|
+
});
|
|
9912
|
+
}
|
|
9913
|
+
/** Starts from a click or on open: a refused microphone shows its error line, no rejection. */
|
|
9914
|
+
requestRecording() {
|
|
9915
|
+
this.startRecording().catch(() => { });
|
|
9916
|
+
}
|
|
9917
|
+
async startRecording() {
|
|
9918
|
+
if (this.isRecording())
|
|
9919
|
+
return;
|
|
9920
|
+
const start = {};
|
|
9921
|
+
this.pendingStart = start;
|
|
9922
|
+
// Initialize first (still within the gesture tick on most browsers)
|
|
9923
|
+
await this.initializeWaveSurferIfNeeded();
|
|
9924
|
+
if (this.pendingStart !== start) {
|
|
9925
|
+
return;
|
|
9926
|
+
}
|
|
9927
|
+
const record = this.record;
|
|
9928
|
+
if (!record)
|
|
9929
|
+
throw new Error('Voice recorder plugin is not initialized.');
|
|
9930
|
+
this.options.ngZone.run(() => {
|
|
9931
|
+
this.microphoneError.set(false);
|
|
9932
|
+
this.isWaveformOpen.set(true);
|
|
9933
|
+
this.isRecording.set(true);
|
|
9934
|
+
this.isPlaying.set(false);
|
|
9935
|
+
this.recordedBlob.set(null);
|
|
9936
|
+
this.time.set('00:00');
|
|
9937
|
+
});
|
|
9938
|
+
try {
|
|
9939
|
+
// Force native mic permission first so it works with WKWebView
|
|
9940
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
9941
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
9942
|
+
if (this.pendingStart !== start) {
|
|
9943
|
+
return;
|
|
9944
|
+
}
|
|
9945
|
+
await record.startRecording();
|
|
9946
|
+
if (this.pendingStart !== start) {
|
|
9947
|
+
record.stopRecording();
|
|
9948
|
+
record.stopMic();
|
|
9949
|
+
return;
|
|
9950
|
+
}
|
|
9951
|
+
this.pendingStart = undefined;
|
|
9952
|
+
}
|
|
9953
|
+
catch (error) {
|
|
9954
|
+
if (this.pendingStart !== start) {
|
|
9955
|
+
return;
|
|
9956
|
+
}
|
|
9957
|
+
console.error('Mic failed', error);
|
|
9958
|
+
this.pendingStart = undefined;
|
|
9959
|
+
this.options.ngZone.run(() => {
|
|
9960
|
+
this.isRecording.set(false);
|
|
9961
|
+
this.isWaveformOpen.set(!!this.recordedBlob());
|
|
9962
|
+
this.microphoneError.set(true);
|
|
9963
|
+
});
|
|
9964
|
+
throw error;
|
|
9965
|
+
}
|
|
9966
|
+
}
|
|
9967
|
+
stopRecording() {
|
|
9968
|
+
if (this.pendingStart) {
|
|
9969
|
+
this.cancelPendingStart();
|
|
9970
|
+
}
|
|
9971
|
+
else if (this.record && this.isRecording()) {
|
|
9972
|
+
this.record.stopRecording();
|
|
9973
|
+
this.options.ngZone.run(() => {
|
|
9974
|
+
this.isRecording.set(false);
|
|
9975
|
+
});
|
|
9976
|
+
}
|
|
9977
|
+
}
|
|
9978
|
+
/** A stop before the recording runs has nothing to keep: the strip goes back to idle. */
|
|
9979
|
+
cancelPendingStart() {
|
|
9980
|
+
this.pendingStart = undefined;
|
|
9981
|
+
this.destroyRecorder();
|
|
9982
|
+
this.options.ngZone.run(() => {
|
|
9983
|
+
this.isRecording.set(false);
|
|
9984
|
+
this.isWaveformOpen.set(false);
|
|
9985
|
+
});
|
|
9986
|
+
}
|
|
9987
|
+
/** The plugin's destroy alone closes a running microphone's AudioContext twice, which rejects;
|
|
9988
|
+
* after the stop and the stopMic it has nothing left to close. */
|
|
9989
|
+
destroyRecorder() {
|
|
9990
|
+
const wavesurfer = this.wavesurfer;
|
|
9991
|
+
const record = this.record;
|
|
9992
|
+
this.wavesurfer = undefined;
|
|
9993
|
+
this.record = undefined;
|
|
9994
|
+
record?.stopRecording();
|
|
9995
|
+
record?.stopMic();
|
|
9996
|
+
wavesurfer?.destroy();
|
|
9997
|
+
}
|
|
9998
|
+
saveRecording() {
|
|
9999
|
+
const recordedBlob = this.recordedBlob();
|
|
10000
|
+
if (recordedBlob) {
|
|
10001
|
+
const file = UploadWidgetUtils.createRecordingFile(recordedBlob, 'recording', 'audio/mp4');
|
|
10002
|
+
this.options.handOver(file);
|
|
10003
|
+
// Destroy completely so next recording initializes fresh
|
|
10004
|
+
this.destroyRecorder();
|
|
10005
|
+
this.options.ngZone.run(() => {
|
|
10006
|
+
this.recordedBlob.set(null);
|
|
10007
|
+
this.time.set('00:00');
|
|
10008
|
+
this.isPlaying.set(false);
|
|
10009
|
+
this.isWaveformOpen.set(false);
|
|
10010
|
+
});
|
|
10011
|
+
}
|
|
10012
|
+
}
|
|
10013
|
+
replayRecording() {
|
|
10014
|
+
if (this.recordedBlob()) {
|
|
10015
|
+
this.options.ngZone.run(() => {
|
|
10016
|
+
this.isPlaying.update((isPlaying) => !isPlaying);
|
|
10017
|
+
this.wavesurfer?.playPause();
|
|
10018
|
+
});
|
|
10019
|
+
}
|
|
10020
|
+
}
|
|
10021
|
+
/** Cancels a start under way and closes the microphone: the close of the view. */
|
|
10022
|
+
release() {
|
|
10023
|
+
this.pendingStart = undefined;
|
|
10024
|
+
this.destroyRecorder();
|
|
10025
|
+
}
|
|
10026
|
+
addAlphaToColor(hexColor, alpha) {
|
|
10027
|
+
if (/^#([a-f\d])([a-f\d])([a-f\d])$/i.test(hexColor)) {
|
|
10028
|
+
hexColor = hexColor.replace(/^#([a-f\d])([a-f\d])([a-f\d])$/i, (_, r, g, b) => `#${r}${r}${g}${g}${b}${b}`);
|
|
10029
|
+
}
|
|
10030
|
+
const hexMatch = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexColor);
|
|
10031
|
+
if (hexMatch) {
|
|
10032
|
+
const [, r, g, b] = hexMatch;
|
|
10033
|
+
return `rgba(${parseInt(r, 16)}, ${parseInt(g, 16)}, ${parseInt(b, 16)}, ${alpha})`;
|
|
10034
|
+
}
|
|
10035
|
+
const ctx = document.createElement('canvas').getContext('2d');
|
|
10036
|
+
if (ctx) {
|
|
10037
|
+
ctx.fillStyle = hexColor;
|
|
10038
|
+
const parsed = ctx.fillStyle;
|
|
10039
|
+
const rgbMatch = /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/.exec(parsed);
|
|
10040
|
+
if (rgbMatch) {
|
|
10041
|
+
const [, r, g, b] = rgbMatch;
|
|
10042
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
10043
|
+
}
|
|
10044
|
+
}
|
|
10045
|
+
return hexColor;
|
|
10046
|
+
}
|
|
10047
|
+
}
|
|
10048
|
+
|
|
10049
|
+
/** The recorder view of the Capture Dialog: the strip in the theme's colours, with its hints, and
|
|
10050
|
+
* the stop and save buttons in actionButtonsTemplate for the dialog's footer. */
|
|
10051
|
+
class CaptureRecorderViewComponent {
|
|
10052
|
+
constructor() {
|
|
10053
|
+
this.actionButtonsTemplate = viewChild('actionButtons', /* @ts-ignore */
|
|
10054
|
+
...(ngDevMode ? [{ debugName: "actionButtonsTemplate" }] : /* istanbul ignore next */ []));
|
|
10055
|
+
this.deviceStart = input.required(/* @ts-ignore */
|
|
10056
|
+
...(ngDevMode ? [{ debugName: "deviceStart" }] : /* istanbul ignore next */ []));
|
|
10057
|
+
this.handover = input.required(/* @ts-ignore */
|
|
10058
|
+
...(ngDevMode ? [{ debugName: "handover" }] : /* istanbul ignore next */ []));
|
|
10059
|
+
this.texts = input.required(/* @ts-ignore */
|
|
10060
|
+
...(ngDevMode ? [{ debugName: "texts" }] : /* istanbul ignore next */ []));
|
|
10061
|
+
this.recordingSaved = output();
|
|
10062
|
+
this.device = new MicrophoneDevice({
|
|
10063
|
+
handover: this.handover,
|
|
10064
|
+
ngZone: inject(NgZone),
|
|
10065
|
+
getWaveformElement: () => this.waveformRef?.nativeElement,
|
|
10066
|
+
getButtonElement: () => this.mainButton.nativeElement,
|
|
10067
|
+
// A host theme sets the token on the body, which the element inherits and html does not see.
|
|
10068
|
+
getThemeElement: () => this.waveformRef.nativeElement,
|
|
10069
|
+
handOver: (file) => this.recordingSaved.emit(file),
|
|
10070
|
+
});
|
|
10071
|
+
this.micButton = this.createIconButton('microphone', 'primary');
|
|
10072
|
+
/** Only a theme colour's name paints the icon. */
|
|
10073
|
+
this.stopButton = {
|
|
10074
|
+
...this.createIconButton('stop-circle', 'warn'),
|
|
10075
|
+
iconColor: 'warn',
|
|
10076
|
+
};
|
|
10077
|
+
this.playButton = this.createIconButton('play_circle', 'primary');
|
|
10078
|
+
this.stopPlaybackButton = this.createIconButton('stop_circle', 'warn');
|
|
10079
|
+
this.saveButton = computed(() => UploadWidgetUtils.createCaptureButton(this.texts().save, 'save'), /* @ts-ignore */
|
|
10080
|
+
...(ngDevMode ? [{ debugName: "saveButton" }] : /* istanbul ignore next */ []));
|
|
10081
|
+
this.stopRecordingButton = computed(() => UploadWidgetUtils.createStopRecordingButton(this.texts(), 'warn'), /* @ts-ignore */
|
|
10082
|
+
...(ngDevMode ? [{ debugName: "stopRecordingButton" }] : /* istanbul ignore next */ []));
|
|
10083
|
+
}
|
|
10084
|
+
ngAfterViewInit() {
|
|
10085
|
+
if (this.deviceStart() === CaptureDeviceStart.AUTO) {
|
|
10086
|
+
this.device.requestRecording();
|
|
10087
|
+
}
|
|
10088
|
+
}
|
|
10089
|
+
ngOnDestroy() {
|
|
10090
|
+
this.device.release();
|
|
10091
|
+
}
|
|
10092
|
+
createIconButton(icon, color) {
|
|
10093
|
+
return UploadWidgetUtils.createCaptureButton('', icon, color, UiActionButtonType.ICON);
|
|
10094
|
+
}
|
|
10095
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureRecorderViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10096
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: CaptureRecorderViewComponent, isStandalone: true, selector: "smart-capture-recorder-view", inputs: { deviceStart: { classPropertyName: "deviceStart", publicName: "deviceStart", isSignal: true, isRequired: true, transformFunction: null }, handover: { classPropertyName: "handover", publicName: "handover", isSignal: true, isRequired: true, transformFunction: null }, texts: { classPropertyName: "texts", publicName: "texts", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { recordingSaved: "recordingSaved" }, viewQueries: [{ propertyName: "actionButtonsTemplate", first: true, predicate: ["actionButtons"], descendants: true, isSignal: true }, { propertyName: "waveformRef", first: true, predicate: ["waveform"], descendants: true }, { propertyName: "mainButton", first: true, predicate: ["mainButton"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div\n class=\"recorderContainer direction-right\"\n [class.active]=\"device.isWaveformShown()\"\n [class.isRecording]=\"device.isRecording()\"\n [class.hasRecording]=\"device.recordedBlob()\"\n>\n <div class=\"recorderMain\">\n <div class=\"recorderToolbar\">\n @if (device.isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"stopButton\"\n (actionClick)=\"device.stopRecording()\"\n ></ui-action-button>\n <div class=\"progress\">{{ device.time() }}</div>\n } @else {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"micButton\"\n (actionClick)=\"device.requestRecording()\"\n ></ui-action-button>\n }\n\n @if (device.recordedBlob()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"device.isPlaying() ? stopPlaybackButton : playButton\"\n (actionClick)=\"device.replayRecording()\"\n ></ui-action-button>\n }\n </div>\n\n <div class=\"waveformBand\" [class.open]=\"device.isWaveformShown()\">\n <div class=\"waveformGrid\"></div>\n <div class=\"waveform\" #waveform></div>\n </div>\n </div>\n</div>\n@if (device.microphoneError()) {\n <p class=\"message\">{{ texts().microphoneError }}</p>\n <p class=\"hint\">{{ texts().microphoneRetryHint }}</p>\n}\n@if (device.recordedBlob()) {\n <p class=\"hint\">{{ texts().recordingHint }}</p>\n}\n\n<ng-template #actionButtons>\n @if (device.isRecording()) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"device.stopRecording()\"\n ></ui-action-button>\n }\n @if (device.recordedBlob()) {\n <ui-action-button\n class=\"saveRecordingButton\"\n [descriptor]=\"saveButton()\"\n (actionClick)=\"device.saveRecording()\"\n ></ui-action-button>\n }\n</ng-template>\n", styles: [":host{display:block;width:100%;min-width:0}.message{text-align:center;color:#6b7280}:host ::ng-deep .waveform{height:unset!important}.recorderContainer{display:flex;flex-direction:row;justify-content:space-between;width:100%;min-width:0;align-items:center;gap:.5rem}.active ::ng-deep .recorderToolbar{border:1px solid #e5e7eb;border-radius:.5rem}.isRecording ::ng-deep .recorderToolbar{padding-right:1rem}.recorderToolbar ::ng-deep .sb4-red,.recorderToolbar ::ng-deep .sb4-primary{border:1px solid #e5e7eb;box-shadow:unset}.active ::ng-deep .recorderToolbar ::ng-deep .sb4-red,.active ::ng-deep .recorderToolbar ::ng-deep .sb4-primary{border:unset}.mainButton ::ng-deep .sb4-red,.mainButton ::ng-deep .sb4-primary{padding:.5rem .75rem}.recorderMain{display:flex;align-items:center;min-width:0;flex:1}.direction-right .recorderMain{flex-direction:row}.direction-left .recorderMain{flex-direction:row-reverse}.waveform{width:100%;height:45px}.waveformBand{position:relative;display:grid;align-items:center;width:0;max-width:0;min-width:0;opacity:0;overflow:hidden;border-radius:5px;flex:1 1 0;transform:scaleX(.96);will-change:opacity,transform,margin;transition:max-width .22s ease,opacity .18s ease,margin .22s ease,transform .22s ease}.waveformBand.open{min-width:35px;max-width:100%;opacity:1;transform:scaleX(1)}.direction-right .waveformBand{margin-left:0}.direction-left .waveformBand{margin-right:0}.direction-right .waveformBand.open{margin-left:.45rem}.direction-left .waveformBand.open{margin-right:.45rem}.waveformGrid{position:absolute;inset:0;border:1px solid #e5e7eb;border-radius:.5rem;background:repeating-linear-gradient(to right,rgba(148,163,184,.35) 0,rgba(148,163,184,.35) 2px,transparent 2px,transparent 8px),linear-gradient(to bottom,#94a3b814,#94a3b805);pointer-events:none}.recorderToolbar{background-color:transparent;display:flex;flex-direction:row;align-items:center}.progress{align-content:center;font-size:1rem;height:100%;color:#6b7280}.hasRecording ::ng-deep .recorderToolbar ui-action-button{border-right:1px solid #e5e7eb}.hasRecording ::ng-deep .recorderToolbar ui-action-button:last-child{border-right:unset}\n", ":host .message{color:var(--warn-color)}:host .hint{margin:.5rem 0 0;text-align:center;font-size:.75rem;color:var(--color-text-muted)}:host .progress{color:var(--color-text-muted)}:host .isRecording .progress:before{content:\"\";display:inline-block;width:.5rem;height:.5rem;margin-right:.3125rem;border-radius:50%;background:var(--warn-color)}:host .active ::ng-deep .recorderToolbar{border-color:var(--color-border)}:host .waveformGrid{border-color:var(--color-border);background:repeating-linear-gradient(to right,color-mix(in srgb,var(--color-text-muted) 35%,transparent) 0,color-mix(in srgb,var(--color-text-muted) 35%,transparent) 2px,transparent 2px,transparent 8px),linear-gradient(to bottom,color-mix(in srgb,var(--color-text-muted) 8%,transparent),color-mix(in srgb,var(--color-text-muted) 2%,transparent))}:host .recorderToolbar ::ng-deep .sb4-warn,:host .recorderToolbar ::ng-deep .sb4-primary,:host .recorderToolbar ::ng-deep ui-action-button{border:unset}:host .mainButton ::ng-deep .sb4-warn{padding:.5rem .75rem}\n"], dependencies: [{ kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
|
|
10097
|
+
}
|
|
10098
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureRecorderViewComponent, decorators: [{
|
|
10099
|
+
type: Component,
|
|
10100
|
+
args: [{ selector: 'smart-capture-recorder-view', imports: [UiActionButtonComponent], template: "<div\n class=\"recorderContainer direction-right\"\n [class.active]=\"device.isWaveformShown()\"\n [class.isRecording]=\"device.isRecording()\"\n [class.hasRecording]=\"device.recordedBlob()\"\n>\n <div class=\"recorderMain\">\n <div class=\"recorderToolbar\">\n @if (device.isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"stopButton\"\n (actionClick)=\"device.stopRecording()\"\n ></ui-action-button>\n <div class=\"progress\">{{ device.time() }}</div>\n } @else {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"micButton\"\n (actionClick)=\"device.requestRecording()\"\n ></ui-action-button>\n }\n\n @if (device.recordedBlob()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"device.isPlaying() ? stopPlaybackButton : playButton\"\n (actionClick)=\"device.replayRecording()\"\n ></ui-action-button>\n }\n </div>\n\n <div class=\"waveformBand\" [class.open]=\"device.isWaveformShown()\">\n <div class=\"waveformGrid\"></div>\n <div class=\"waveform\" #waveform></div>\n </div>\n </div>\n</div>\n@if (device.microphoneError()) {\n <p class=\"message\">{{ texts().microphoneError }}</p>\n <p class=\"hint\">{{ texts().microphoneRetryHint }}</p>\n}\n@if (device.recordedBlob()) {\n <p class=\"hint\">{{ texts().recordingHint }}</p>\n}\n\n<ng-template #actionButtons>\n @if (device.isRecording()) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"device.stopRecording()\"\n ></ui-action-button>\n }\n @if (device.recordedBlob()) {\n <ui-action-button\n class=\"saveRecordingButton\"\n [descriptor]=\"saveButton()\"\n (actionClick)=\"device.saveRecording()\"\n ></ui-action-button>\n }\n</ng-template>\n", styles: [":host{display:block;width:100%;min-width:0}.message{text-align:center;color:#6b7280}:host ::ng-deep .waveform{height:unset!important}.recorderContainer{display:flex;flex-direction:row;justify-content:space-between;width:100%;min-width:0;align-items:center;gap:.5rem}.active ::ng-deep .recorderToolbar{border:1px solid #e5e7eb;border-radius:.5rem}.isRecording ::ng-deep .recorderToolbar{padding-right:1rem}.recorderToolbar ::ng-deep .sb4-red,.recorderToolbar ::ng-deep .sb4-primary{border:1px solid #e5e7eb;box-shadow:unset}.active ::ng-deep .recorderToolbar ::ng-deep .sb4-red,.active ::ng-deep .recorderToolbar ::ng-deep .sb4-primary{border:unset}.mainButton ::ng-deep .sb4-red,.mainButton ::ng-deep .sb4-primary{padding:.5rem .75rem}.recorderMain{display:flex;align-items:center;min-width:0;flex:1}.direction-right .recorderMain{flex-direction:row}.direction-left .recorderMain{flex-direction:row-reverse}.waveform{width:100%;height:45px}.waveformBand{position:relative;display:grid;align-items:center;width:0;max-width:0;min-width:0;opacity:0;overflow:hidden;border-radius:5px;flex:1 1 0;transform:scaleX(.96);will-change:opacity,transform,margin;transition:max-width .22s ease,opacity .18s ease,margin .22s ease,transform .22s ease}.waveformBand.open{min-width:35px;max-width:100%;opacity:1;transform:scaleX(1)}.direction-right .waveformBand{margin-left:0}.direction-left .waveformBand{margin-right:0}.direction-right .waveformBand.open{margin-left:.45rem}.direction-left .waveformBand.open{margin-right:.45rem}.waveformGrid{position:absolute;inset:0;border:1px solid #e5e7eb;border-radius:.5rem;background:repeating-linear-gradient(to right,rgba(148,163,184,.35) 0,rgba(148,163,184,.35) 2px,transparent 2px,transparent 8px),linear-gradient(to bottom,#94a3b814,#94a3b805);pointer-events:none}.recorderToolbar{background-color:transparent;display:flex;flex-direction:row;align-items:center}.progress{align-content:center;font-size:1rem;height:100%;color:#6b7280}.hasRecording ::ng-deep .recorderToolbar ui-action-button{border-right:1px solid #e5e7eb}.hasRecording ::ng-deep .recorderToolbar ui-action-button:last-child{border-right:unset}\n", ":host .message{color:var(--warn-color)}:host .hint{margin:.5rem 0 0;text-align:center;font-size:.75rem;color:var(--color-text-muted)}:host .progress{color:var(--color-text-muted)}:host .isRecording .progress:before{content:\"\";display:inline-block;width:.5rem;height:.5rem;margin-right:.3125rem;border-radius:50%;background:var(--warn-color)}:host .active ::ng-deep .recorderToolbar{border-color:var(--color-border)}:host .waveformGrid{border-color:var(--color-border);background:repeating-linear-gradient(to right,color-mix(in srgb,var(--color-text-muted) 35%,transparent) 0,color-mix(in srgb,var(--color-text-muted) 35%,transparent) 2px,transparent 2px,transparent 8px),linear-gradient(to bottom,color-mix(in srgb,var(--color-text-muted) 8%,transparent),color-mix(in srgb,var(--color-text-muted) 2%,transparent))}:host .recorderToolbar ::ng-deep .sb4-warn,:host .recorderToolbar ::ng-deep .sb4-primary,:host .recorderToolbar ::ng-deep ui-action-button{border:unset}:host .mainButton ::ng-deep .sb4-warn{padding:.5rem .75rem}\n"] }]
|
|
10101
|
+
}], propDecorators: { waveformRef: [{
|
|
10102
|
+
type: ViewChild,
|
|
10103
|
+
args: ['waveform']
|
|
10104
|
+
}], mainButton: [{
|
|
10105
|
+
type: ViewChild,
|
|
10106
|
+
args: ['mainButton', { read: ElementRef }]
|
|
10107
|
+
}], actionButtonsTemplate: [{ type: i0.ViewChild, args: ['actionButtons', { isSignal: true }] }], deviceStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "deviceStart", required: true }] }], handover: [{ type: i0.Input, args: [{ isSignal: true, alias: "handover", required: true }] }], texts: [{ type: i0.Input, args: [{ isSignal: true, alias: "texts", required: true }] }], recordingSaved: [{ type: i0.Output, args: ["recordingSaved"] }] } });
|
|
10108
|
+
|
|
10109
|
+
/** The Capture Dialog of a File Field: closes with the files of a use or a save, else empty. */
|
|
10110
|
+
class CaptureDialogComponent {
|
|
10111
|
+
constructor() {
|
|
10112
|
+
this.dialogRef = inject(MatDialogRef);
|
|
10113
|
+
this.data = inject(MAT_DIALOG_DATA);
|
|
10114
|
+
this.isMicrophone = this.data.source === FileUploaderSource.MICROPHONE;
|
|
10115
|
+
this.cameraMode = this.data.source === FileUploaderSource.CAMERA_VIDEO ? 'VIDEO' : 'PHOTO';
|
|
10116
|
+
this.cameraView = viewChild(CaptureCameraViewComponent, /* @ts-ignore */
|
|
10117
|
+
...(ngDevMode ? [{ debugName: "cameraView" }] : /* istanbul ignore next */ []));
|
|
10118
|
+
this.recorderView = viewChild(CaptureRecorderViewComponent, /* @ts-ignore */
|
|
10119
|
+
...(ngDevMode ? [{ debugName: "recorderView" }] : /* istanbul ignore next */ []));
|
|
10120
|
+
this.captureView = computed(() => this.cameraView() ?? this.recorderView(), /* @ts-ignore */
|
|
10121
|
+
...(ngDevMode ? [{ debugName: "captureView" }] : /* istanbul ignore next */ []));
|
|
10122
|
+
/** The view's buttons go to the footer of the dialog. */
|
|
10123
|
+
this.actionButtons = computed(() => this.captureView()?.actionButtonsTemplate() ?? null, /* @ts-ignore */
|
|
10124
|
+
...(ngDevMode ? [{ debugName: "actionButtons" }] : /* istanbul ignore next */ []));
|
|
10125
|
+
/** The recorder waits for the open: it reads the waveform's height from its rendered button. */
|
|
10126
|
+
this.isOpened = signal(false, /* @ts-ignore */
|
|
10127
|
+
...(ngDevMode ? [{ debugName: "isOpened" }] : /* istanbul ignore next */ []));
|
|
10128
|
+
this.closeButton = {
|
|
10129
|
+
title: '',
|
|
10130
|
+
type: UiActionButtonType.ICON,
|
|
10131
|
+
icon: 'close',
|
|
10132
|
+
iconPosition: IconPosition.PRE,
|
|
10133
|
+
color: 'primary',
|
|
10134
|
+
};
|
|
10135
|
+
this.cancelButton = {
|
|
10136
|
+
title: this.data.texts.cancel,
|
|
10137
|
+
type: UiActionButtonType.FLAT,
|
|
10138
|
+
color: 'primary',
|
|
10139
|
+
};
|
|
10140
|
+
effect(() => {
|
|
10141
|
+
this.dialogRef.disableClose = this.captureView()?.device.isCaptureUnderWay() ?? false;
|
|
10142
|
+
});
|
|
10143
|
+
this.dialogRef.afterOpened().subscribe(() => this.isOpened.set(true));
|
|
10144
|
+
// disableClose stops the keyboard too, but only the backdrop is meant to wait for the capture.
|
|
10145
|
+
this.dialogRef.keydownEvents().subscribe((event) => {
|
|
10146
|
+
if (event.key === 'Escape' && this.dialogRef.disableClose) {
|
|
10147
|
+
this.discardCapture();
|
|
10148
|
+
}
|
|
10149
|
+
});
|
|
10150
|
+
}
|
|
10151
|
+
/** The close destroys the capture view, which stops the device. */
|
|
10152
|
+
discardCapture() {
|
|
10153
|
+
this.dialogRef.close();
|
|
10154
|
+
}
|
|
10155
|
+
handOverCapture(files) {
|
|
10156
|
+
this.dialogRef.close(files);
|
|
10157
|
+
}
|
|
10158
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10159
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: CaptureDialogComponent, isStandalone: true, selector: "smart-capture-dialog", viewQueries: [{ propertyName: "cameraView", first: true, predicate: CaptureCameraViewComponent, descendants: true, isSignal: true }, { propertyName: "recorderView", first: true, predicate: CaptureRecorderViewComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"dialog-container captureDialog\">\n <div class=\"dialog-header\">\n <h2 class=\"dialog-title\">{{ data.content.title }}</h2>\n <ui-action-button\n class=\"closeCaptureButton\"\n [descriptor]=\"closeButton\"\n (actionClick)=\"discardCapture()\"\n ></ui-action-button>\n </div>\n\n <mat-dialog-content class=\"dialog-content\">\n @if (isMicrophone) {\n <div class=\"recorderSlot\">\n @if (isOpened()) {\n <smart-capture-recorder-view\n [deviceStart]=\"data.settings.deviceStart\"\n [handover]=\"data.settings.handover\"\n [texts]=\"data.texts\"\n (recordingSaved)=\"handOverCapture([$event])\"\n ></smart-capture-recorder-view>\n }\n </div>\n } @else {\n <smart-capture-camera-view\n [mode]=\"cameraMode\"\n [deviceStart]=\"data.settings.deviceStart\"\n [handover]=\"data.settings.handover\"\n [texts]=\"data.texts\"\n [maxFileSize]=\"data.maxFileSize\"\n (captured)=\"handOverCapture($event)\"\n ></smart-capture-camera-view>\n }\n </mat-dialog-content>\n\n <div class=\"dialog-footer\">\n <ui-action-button\n class=\"cancelCaptureButton\"\n [descriptor]=\"cancelButton\"\n (actionClick)=\"discardCapture()\"\n ></ui-action-button>\n <ng-container [ngTemplateOutlet]=\"actionButtons()\" />\n </div>\n</div>\n", styles: [".dialog-container{display:flex;flex-direction:column;--sb4-dialog-default-padding: .5rem;--mat-dialog-content-padding: var(--sb4-dialog-default-padding)}.dialog-header{display:flex;align-items:center;gap:var(--sb4-dialog-default-padding);padding:var(--sb4-dialog-default-padding)}.mat-mdc-dialog-content{max-height:var(--sb4-dialog-default-max-height, 80vh);max-width:var(--sb4-dialog-default-max-width, unset);min-width:var(--sb4-dialog-default-min-width, unset)}.dialog-title{flex:1;margin:0}.dialog-footer{padding:var(--sb4-dialog-default-padding)}\n", ".captureDialog{--sb4-dialog-default-padding: 1rem}.captureDialog .dialog-footer{display:flex;flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:.5rem}.closeCaptureButton ::ng-deep button{width:40px;min-width:40px;height:40px;min-height:40px;padding:0;border-radius:50%}.captureDialog ::ng-deep .stopRecordingButton{--text-color: color-mix(in srgb, var(--warn-color) 5%, white)}.recorderSlot{min-height:3.25rem}\n"], dependencies: [{ kind: "component", type: CaptureCameraViewComponent, selector: "smart-capture-camera-view", inputs: ["mode", "deviceStart", "handover", "texts", "maxFileSize"], outputs: ["captured"] }, { kind: "component", type: CaptureRecorderViewComponent, selector: "smart-capture-recorder-view", inputs: ["deviceStart", "handover", "texts"], outputs: ["recordingSaved"] }, { kind: "directive", type: MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
|
|
10160
|
+
}
|
|
10161
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogComponent, decorators: [{
|
|
10162
|
+
type: Component,
|
|
10163
|
+
args: [{ selector: 'smart-capture-dialog', imports: [
|
|
10164
|
+
CaptureCameraViewComponent,
|
|
10165
|
+
CaptureRecorderViewComponent,
|
|
10166
|
+
MatDialogContent,
|
|
10167
|
+
NgTemplateOutlet,
|
|
10168
|
+
UiActionButtonComponent,
|
|
10169
|
+
], template: "<div class=\"dialog-container captureDialog\">\n <div class=\"dialog-header\">\n <h2 class=\"dialog-title\">{{ data.content.title }}</h2>\n <ui-action-button\n class=\"closeCaptureButton\"\n [descriptor]=\"closeButton\"\n (actionClick)=\"discardCapture()\"\n ></ui-action-button>\n </div>\n\n <mat-dialog-content class=\"dialog-content\">\n @if (isMicrophone) {\n <div class=\"recorderSlot\">\n @if (isOpened()) {\n <smart-capture-recorder-view\n [deviceStart]=\"data.settings.deviceStart\"\n [handover]=\"data.settings.handover\"\n [texts]=\"data.texts\"\n (recordingSaved)=\"handOverCapture([$event])\"\n ></smart-capture-recorder-view>\n }\n </div>\n } @else {\n <smart-capture-camera-view\n [mode]=\"cameraMode\"\n [deviceStart]=\"data.settings.deviceStart\"\n [handover]=\"data.settings.handover\"\n [texts]=\"data.texts\"\n [maxFileSize]=\"data.maxFileSize\"\n (captured)=\"handOverCapture($event)\"\n ></smart-capture-camera-view>\n }\n </mat-dialog-content>\n\n <div class=\"dialog-footer\">\n <ui-action-button\n class=\"cancelCaptureButton\"\n [descriptor]=\"cancelButton\"\n (actionClick)=\"discardCapture()\"\n ></ui-action-button>\n <ng-container [ngTemplateOutlet]=\"actionButtons()\" />\n </div>\n</div>\n", styles: [".dialog-container{display:flex;flex-direction:column;--sb4-dialog-default-padding: .5rem;--mat-dialog-content-padding: var(--sb4-dialog-default-padding)}.dialog-header{display:flex;align-items:center;gap:var(--sb4-dialog-default-padding);padding:var(--sb4-dialog-default-padding)}.mat-mdc-dialog-content{max-height:var(--sb4-dialog-default-max-height, 80vh);max-width:var(--sb4-dialog-default-max-width, unset);min-width:var(--sb4-dialog-default-min-width, unset)}.dialog-title{flex:1;margin:0}.dialog-footer{padding:var(--sb4-dialog-default-padding)}\n", ".captureDialog{--sb4-dialog-default-padding: 1rem}.captureDialog .dialog-footer{display:flex;flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:.5rem}.closeCaptureButton ::ng-deep button{width:40px;min-width:40px;height:40px;min-height:40px;padding:0;border-radius:50%}.captureDialog ::ng-deep .stopRecordingButton{--text-color: color-mix(in srgb, var(--warn-color) 5%, white)}.recorderSlot{min-height:3.25rem}\n"] }]
|
|
10170
|
+
}], ctorParameters: () => [], propDecorators: { cameraView: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CaptureCameraViewComponent), { isSignal: true }] }], recorderView: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CaptureRecorderViewComponent), { isSignal: true }] }] } });
|
|
10171
|
+
|
|
10172
|
+
class CaptureDialogService extends SmartdialogService {
|
|
10173
|
+
constructor() {
|
|
10174
|
+
super(inject(MatDialog), inject(Injector));
|
|
10175
|
+
}
|
|
10176
|
+
/** The returned dialog closes with the handed over files, or empty at a discard. */
|
|
10177
|
+
openCaptureDialog(data) {
|
|
10178
|
+
return this.createDialogRef(data, CaptureDialogComponent, {
|
|
10179
|
+
width: '560px',
|
|
10180
|
+
maxWidth: '90vw',
|
|
10181
|
+
autoFocus: 'dialog',
|
|
10182
|
+
});
|
|
10183
|
+
}
|
|
10184
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
10185
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogService, providedIn: 'root' }); }
|
|
10186
|
+
}
|
|
10187
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogService, decorators: [{
|
|
10188
|
+
type: Injectable,
|
|
10189
|
+
args: [{ providedIn: 'root' }]
|
|
10190
|
+
}], ctorParameters: () => [] });
|
|
10191
|
+
|
|
10192
|
+
/** The inline camera panel of the upload zone; a phone opens its own camera instead. */
|
|
9349
10193
|
class PhotoCaptureWidgetComponent {
|
|
9350
10194
|
constructor() {
|
|
10195
|
+
this.injector = inject(Injector);
|
|
9351
10196
|
this.maxFileSize = input(/* @ts-ignore */
|
|
9352
10197
|
...(ngDevMode ? [undefined, { debugName: "maxFileSize" }] : /* istanbul ignore next */ []));
|
|
9353
10198
|
/** PHOTO takes a picture; VIDEO records a video with the webcam or picks one on a desktop. */
|
|
@@ -9360,53 +10205,46 @@ class PhotoCaptureWidgetComponent {
|
|
|
9360
10205
|
/** The zone's texts; the buttons follow them. */
|
|
9361
10206
|
this.texts = input(UPLOAD_ZONE_DEFAULT_TEXTS, /* @ts-ignore */
|
|
9362
10207
|
...(ngDevMode ? [{ debugName: "texts" }] : /* istanbul ignore next */ []));
|
|
10208
|
+
/** AUTO asks for the camera when the widget opens, MANUAL at the switch-on button. */
|
|
10209
|
+
this.deviceStart = input(CaptureDeviceStart.MANUAL, /* @ts-ignore */
|
|
10210
|
+
...(ngDevMode ? [{ debugName: "deviceStart" }] : /* istanbul ignore next */ []));
|
|
10211
|
+
/** CONFIRM keeps a capture for retake or use, IMMEDIATE hands it over at the take or the stop. */
|
|
10212
|
+
this.handover = input(CaptureHandover.IMMEDIATE, /* @ts-ignore */
|
|
10213
|
+
...(ngDevMode ? [{ debugName: "handover" }] : /* istanbul ignore next */ []));
|
|
9363
10214
|
this.photoCaptured = output();
|
|
10215
|
+
this.device = new CameraDevice({
|
|
10216
|
+
mode: this.mode,
|
|
10217
|
+
deviceStart: this.deviceStart,
|
|
10218
|
+
handover: this.handover,
|
|
10219
|
+
maxFileSize: this.maxFileSize,
|
|
10220
|
+
getVideoElement: () => this.videoRef.nativeElement,
|
|
10221
|
+
getCanvasElement: () => this.canvasRef.nativeElement,
|
|
10222
|
+
handOver: (files) => this.photoCaptured.emit(files),
|
|
10223
|
+
});
|
|
9364
10224
|
/** The desktop webcam panel of an externally triggered photo widget, opened by openCapture(). */
|
|
9365
10225
|
this.isPanelOpen = signal(false, /* @ts-ignore */
|
|
9366
10226
|
...(ngDevMode ? [{ debugName: "isPanelOpen" }] : /* istanbul ignore next */ []));
|
|
9367
|
-
/**
|
|
9368
|
-
|
|
9369
|
-
|
|
9370
|
-
|
|
9371
|
-
|
|
9372
|
-
|
|
9373
|
-
|
|
9374
|
-
this.
|
|
9375
|
-
...(ngDevMode ? [{ debugName: "cameraError" }] : /* istanbul ignore next */ []));
|
|
9376
|
-
this.isRecordingState = signal(false, /* @ts-ignore */
|
|
9377
|
-
...(ngDevMode ? [{ debugName: "isRecordingState" }] : /* istanbul ignore next */ []));
|
|
9378
|
-
this.isCapturedState = signal(false, /* @ts-ignore */
|
|
9379
|
-
...(ngDevMode ? [{ debugName: "isCapturedState" }] : /* istanbul ignore next */ []));
|
|
9380
|
-
/** Advanced by stopCamera(), so a stream the browser gives after a close is stopped at once. */
|
|
9381
|
-
this.cameraRequest = 0;
|
|
9382
|
-
this.toggleCameraButton = computed(() => this.createButton(this.cameraActive ? this.texts().cameraOff : this.texts().cameraOn, 'power_settings_new', this.cameraActive ? 'red' : 'primary'), /* @ts-ignore */
|
|
10227
|
+
/** Under AUTO the switch returns when the camera is off, so a second shot stays possible. */
|
|
10228
|
+
this.isCameraSwitchShown = computed(() => !this.device.keptFiles() &&
|
|
10229
|
+
(this.deviceStart() === CaptureDeviceStart.MANUAL ||
|
|
10230
|
+
!(this.device.isCameraActive() || this.device.isCameraStarting())), /* @ts-ignore */
|
|
10231
|
+
...(ngDevMode ? [{ debugName: "isCameraSwitchShown" }] : /* istanbul ignore next */ []));
|
|
10232
|
+
this.isSwitchOnHintShown = computed(() => this.isCameraSwitchShown() && !this.device.isCameraActive() && !this.device.isCaptured(), /* @ts-ignore */
|
|
10233
|
+
...(ngDevMode ? [{ debugName: "isSwitchOnHintShown" }] : /* istanbul ignore next */ []));
|
|
10234
|
+
this.toggleCameraButton = computed(() => UploadWidgetUtils.createCaptureButton(this.device.isCameraActive() ? this.texts().cameraOff : this.texts().cameraOn, 'power_settings_new', this.device.isCameraActive() ? 'red' : 'primary'), /* @ts-ignore */
|
|
9383
10235
|
...(ngDevMode ? [{ debugName: "toggleCameraButton" }] : /* istanbul ignore next */ []));
|
|
9384
|
-
this.captureButton = computed(() =>
|
|
10236
|
+
this.captureButton = computed(() => UploadWidgetUtils.createTakePictureButton(this.texts()), /* @ts-ignore */
|
|
9385
10237
|
...(ngDevMode ? [{ debugName: "captureButton" }] : /* istanbul ignore next */ []));
|
|
9386
|
-
this.startRecordingButton = computed(() =>
|
|
10238
|
+
this.startRecordingButton = computed(() => UploadWidgetUtils.createStartRecordingButton(this.texts()), /* @ts-ignore */
|
|
9387
10239
|
...(ngDevMode ? [{ debugName: "startRecordingButton" }] : /* istanbul ignore next */ []));
|
|
9388
|
-
this.stopRecordingButton = computed(() =>
|
|
10240
|
+
this.stopRecordingButton = computed(() => UploadWidgetUtils.createStopRecordingButton(this.texts(), 'red'), /* @ts-ignore */
|
|
9389
10241
|
...(ngDevMode ? [{ debugName: "stopRecordingButton" }] : /* istanbul ignore next */ []));
|
|
9390
|
-
this.pickVideoButton = computed(() =>
|
|
10242
|
+
this.pickVideoButton = computed(() => UploadWidgetUtils.createCaptureButton(this.texts().pickVideo, 'video_library'), /* @ts-ignore */
|
|
9391
10243
|
...(ngDevMode ? [{ debugName: "pickVideoButton" }] : /* istanbul ignore next */ []));
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
set cameraActive(active) {
|
|
9397
|
-
this.cameraActiveState.set(active);
|
|
9398
|
-
}
|
|
9399
|
-
get isRecording() {
|
|
9400
|
-
return this.isRecordingState();
|
|
9401
|
-
}
|
|
9402
|
-
set isRecording(recording) {
|
|
9403
|
-
this.isRecordingState.set(recording);
|
|
9404
|
-
}
|
|
9405
|
-
get isCaptured() {
|
|
9406
|
-
return this.isCapturedState();
|
|
9407
|
-
}
|
|
9408
|
-
set isCaptured(captured) {
|
|
9409
|
-
this.isCapturedState.set(captured);
|
|
10244
|
+
this.retakeButton = computed(() => UploadWidgetUtils.createRetakeButton(this.texts()), /* @ts-ignore */
|
|
10245
|
+
...(ngDevMode ? [{ debugName: "retakeButton" }] : /* istanbul ignore next */ []));
|
|
10246
|
+
this.useButton = computed(() => UploadWidgetUtils.createUseButton(this.texts()), /* @ts-ignore */
|
|
10247
|
+
...(ngDevMode ? [{ debugName: "useButton" }] : /* istanbul ignore next */ []));
|
|
9410
10248
|
}
|
|
9411
10249
|
isMobile() {
|
|
9412
10250
|
return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
|
@@ -9417,110 +10255,32 @@ class PhotoCaptureWidgetComponent {
|
|
|
9417
10255
|
this.fileInputRef.nativeElement.click();
|
|
9418
10256
|
return;
|
|
9419
10257
|
}
|
|
9420
|
-
if (this.isPanelOpen()) {
|
|
9421
|
-
this.
|
|
9422
|
-
}
|
|
9423
|
-
this.isPanelOpen.update((open) => !open);
|
|
9424
|
-
}
|
|
9425
|
-
ngOnDestroy() {
|
|
9426
|
-
this.stopCamera();
|
|
9427
|
-
}
|
|
9428
|
-
async startCamera() {
|
|
9429
|
-
this.cameraError.set(false);
|
|
9430
|
-
const request = ++this.cameraRequest;
|
|
9431
|
-
let stream;
|
|
9432
|
-
try {
|
|
9433
|
-
stream = await this.openCameraStream();
|
|
9434
|
-
}
|
|
9435
|
-
catch {
|
|
9436
|
-
if (request === this.cameraRequest) {
|
|
9437
|
-
this.cameraError.set(true);
|
|
9438
|
-
}
|
|
9439
|
-
return;
|
|
9440
|
-
}
|
|
9441
|
-
if (request !== this.cameraRequest) {
|
|
9442
|
-
stream.getTracks().forEach((track) => track.stop());
|
|
9443
|
-
return;
|
|
9444
|
-
}
|
|
9445
|
-
this.stream = stream;
|
|
9446
|
-
this.videoRef.nativeElement.srcObject = this.stream;
|
|
9447
|
-
this.videoRef.nativeElement.play();
|
|
9448
|
-
this.cameraActive = true;
|
|
9449
|
-
this.isCaptured = false;
|
|
9450
|
-
}
|
|
9451
|
-
/** A video asks for the sound too; a desktop without a microphone records a silent one. */
|
|
9452
|
-
async openCameraStream() {
|
|
9453
|
-
if (this.mode() === 'VIDEO') {
|
|
9454
|
-
try {
|
|
9455
|
-
return await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
|
|
9456
|
-
}
|
|
9457
|
-
catch {
|
|
9458
|
-
// No microphone answers: the camera alone is asked below.
|
|
9459
|
-
}
|
|
10258
|
+
if (this.isPanelOpen()) {
|
|
10259
|
+
this.device.release();
|
|
9460
10260
|
}
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
/** Closing the camera mid-recording discards the recording. */
|
|
9464
|
-
stopCamera() {
|
|
9465
|
-
this.cameraRequest++;
|
|
9466
|
-
if (this.recorder) {
|
|
9467
|
-
this.recorder.onstop = null;
|
|
9468
|
-
this.recorder.stop();
|
|
9469
|
-
this.recorder = undefined;
|
|
10261
|
+
else {
|
|
10262
|
+
this.startCameraAfterRender();
|
|
9470
10263
|
}
|
|
9471
|
-
this.
|
|
9472
|
-
this.stream?.getTracks().forEach((track) => track.stop());
|
|
9473
|
-
this.stream = undefined;
|
|
9474
|
-
this.cameraActive = false;
|
|
10264
|
+
this.isPanelOpen.update((open) => !open);
|
|
9475
10265
|
}
|
|
9476
|
-
|
|
9477
|
-
|
|
9478
|
-
|
|
9479
|
-
const context = canvas.getContext('2d');
|
|
9480
|
-
if (!context)
|
|
9481
|
-
return;
|
|
9482
|
-
canvas.width = video.videoWidth;
|
|
9483
|
-
canvas.height = video.videoHeight;
|
|
9484
|
-
context.drawImage(video, 0, 0);
|
|
9485
|
-
const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/jpeg', 0.9));
|
|
9486
|
-
if (!blob)
|
|
9487
|
-
return;
|
|
9488
|
-
let file = new File([blob], `photo_${Date.now()}.jpg`, { type: 'image/jpeg' });
|
|
9489
|
-
const maxFileSize = this.maxFileSize();
|
|
9490
|
-
if (maxFileSize && file.size > maxFileSize) {
|
|
9491
|
-
file = await this.compressImageFile(file, maxFileSize);
|
|
10266
|
+
ngAfterViewInit() {
|
|
10267
|
+
if (!this.externalTrigger()) {
|
|
10268
|
+
this.startCameraAfterRender();
|
|
9492
10269
|
}
|
|
9493
|
-
this.photoCaptured.emit([file]);
|
|
9494
|
-
this.isCaptured = true;
|
|
9495
|
-
this.stopCamera();
|
|
9496
10270
|
}
|
|
9497
|
-
|
|
9498
|
-
|
|
9499
|
-
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
});
|
|
9504
|
-
}
|
|
9505
|
-
catch {
|
|
9506
|
-
this.cameraError.set(true);
|
|
10271
|
+
ngOnDestroy() {
|
|
10272
|
+
this.device.release();
|
|
10273
|
+
}
|
|
10274
|
+
/** AUTO waits for the render: the stream goes into the panel's video element. */
|
|
10275
|
+
startCameraAfterRender() {
|
|
10276
|
+
if (this.deviceStart() !== CaptureDeviceStart.AUTO || this.isMobile()) {
|
|
9507
10277
|
return;
|
|
9508
10278
|
}
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
this.photoCaptured.emit([UploadWidgetUtils.createRecordingFile(blob, 'video', 'video/mp4')]);
|
|
9515
|
-
this.stopCamera();
|
|
9516
|
-
};
|
|
9517
|
-
recorder.start();
|
|
9518
|
-
this.recorder = recorder;
|
|
9519
|
-
this.isRecording = true;
|
|
9520
|
-
}
|
|
9521
|
-
/** The recording state holds until onstop hands the video over and stops the camera. */
|
|
9522
|
-
stopRecording() {
|
|
9523
|
-
this.recorder?.stop();
|
|
10279
|
+
afterNextRender(() => {
|
|
10280
|
+
if (!this.externalTrigger() || this.isPanelOpen()) {
|
|
10281
|
+
this.device.startCamera();
|
|
10282
|
+
}
|
|
10283
|
+
}, { injector: this.injector });
|
|
9524
10284
|
}
|
|
9525
10285
|
getPhotoDataURL() {
|
|
9526
10286
|
return this.canvasRef.nativeElement.toDataURL('image/png');
|
|
@@ -9535,7 +10295,7 @@ class PhotoCaptureWidgetComponent {
|
|
|
9535
10295
|
for (const file of filesArray) {
|
|
9536
10296
|
if (maxSize && file.size > maxSize && file.type.startsWith('image/')) {
|
|
9537
10297
|
try {
|
|
9538
|
-
const compressed = await
|
|
10298
|
+
const compressed = await UploadWidgetUtils.compressImageFile(file, maxSize);
|
|
9539
10299
|
processedFiles.push(compressed);
|
|
9540
10300
|
}
|
|
9541
10301
|
catch {
|
|
@@ -9548,80 +10308,12 @@ class PhotoCaptureWidgetComponent {
|
|
|
9548
10308
|
}
|
|
9549
10309
|
this.photoCaptured.emit(processedFiles);
|
|
9550
10310
|
}
|
|
9551
|
-
toggleCamera() {
|
|
9552
|
-
if (this.cameraActive) {
|
|
9553
|
-
this.stopCamera();
|
|
9554
|
-
}
|
|
9555
|
-
else {
|
|
9556
|
-
this.startCamera();
|
|
9557
|
-
}
|
|
9558
|
-
}
|
|
9559
|
-
async compressImageFile(file, maxFileSize) {
|
|
9560
|
-
if (!file.type.startsWith('image/'))
|
|
9561
|
-
return file;
|
|
9562
|
-
const img = await new Promise((resolve, reject) => {
|
|
9563
|
-
const url = URL.createObjectURL(file);
|
|
9564
|
-
const image = new Image();
|
|
9565
|
-
image.onload = () => {
|
|
9566
|
-
URL.revokeObjectURL(url);
|
|
9567
|
-
resolve(image);
|
|
9568
|
-
};
|
|
9569
|
-
image.onerror = (e) => {
|
|
9570
|
-
URL.revokeObjectURL(url);
|
|
9571
|
-
reject(e);
|
|
9572
|
-
};
|
|
9573
|
-
image.src = url;
|
|
9574
|
-
});
|
|
9575
|
-
const canvas = document.createElement('canvas');
|
|
9576
|
-
const ctx = canvas.getContext('2d');
|
|
9577
|
-
if (!ctx)
|
|
9578
|
-
return file;
|
|
9579
|
-
let width = img.naturalWidth;
|
|
9580
|
-
let height = img.naturalHeight;
|
|
9581
|
-
let quality = 0.9;
|
|
9582
|
-
const minQuality = 0.4;
|
|
9583
|
-
const minDimension = 640;
|
|
9584
|
-
const getBlob = (q) => new Promise((resolve) => {
|
|
9585
|
-
canvas.width = width;
|
|
9586
|
-
canvas.height = height;
|
|
9587
|
-
ctx.clearRect(0, 0, width, height);
|
|
9588
|
-
ctx.drawImage(img, 0, 0, width, height);
|
|
9589
|
-
canvas.toBlob((blob) => {
|
|
9590
|
-
if (blob)
|
|
9591
|
-
resolve(blob);
|
|
9592
|
-
}, 'image/jpeg', q);
|
|
9593
|
-
});
|
|
9594
|
-
let blob = await getBlob(quality);
|
|
9595
|
-
while (blob.size > maxFileSize) {
|
|
9596
|
-
if (quality > minQuality) {
|
|
9597
|
-
quality -= 0.1;
|
|
9598
|
-
}
|
|
9599
|
-
else if (width > minDimension && height > minDimension) {
|
|
9600
|
-
width = Math.floor(width * 0.8);
|
|
9601
|
-
height = Math.floor(height * 0.8);
|
|
9602
|
-
quality = 0.9;
|
|
9603
|
-
}
|
|
9604
|
-
else {
|
|
9605
|
-
break;
|
|
9606
|
-
}
|
|
9607
|
-
blob = await getBlob(quality);
|
|
9608
|
-
}
|
|
9609
|
-
if (blob.size <= maxFileSize) {
|
|
9610
|
-
return new File([blob], file.name, { type: 'image/jpeg' });
|
|
9611
|
-
}
|
|
9612
|
-
else {
|
|
9613
|
-
return file;
|
|
9614
|
-
}
|
|
9615
|
-
}
|
|
9616
|
-
createButton(title, icon, color = 'primary') {
|
|
9617
|
-
return { type: UiActionButtonType.RAISED, title, icon, iconPosition: IconPosition.PRE, color };
|
|
9618
|
-
}
|
|
9619
10311
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PhotoCaptureWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9620
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: PhotoCaptureWidgetComponent, isStandalone: true, selector: "photo-capture-widget", inputs: { maxFileSize: { classPropertyName: "maxFileSize", publicName: "maxFileSize", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, externalTrigger: { classPropertyName: "externalTrigger", publicName: "externalTrigger", isSignal: true, isRequired: false, transformFunction: null }, texts: { classPropertyName: "texts", publicName: "texts", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { photoCaptured: "photoCaptured" }, viewQueries: [{ propertyName: "videoRef", first: true, predicate: ["video"], descendants: true }, { propertyName: "canvasRef", first: true, predicate: ["canvas"], descendants: true }, { propertyName: "fileInputRef", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "@if (isMobile()) {\n <input\n #fileInput\n type=\"file\"\n [accept]=\"mode() === 'VIDEO' ? 'video/*' : 'image/*'\"\n capture=\"environment\"\n (change)=\"onFileSelected($event)\"\n style=\"display: none\"\n />\n @if (!externalTrigger()) {\n <div class=\"photoWidgetContainer\">\n <div class=\"controls\">\n <ui-action-button [descriptor]=\"captureButton()\" (actionClick)=\"fileInput.click()\">\n </ui-action-button>\n </div>\n </div>\n }\n} @else if (!externalTrigger() || isPanelOpen()) {\n <div class=\"photoWidgetContainer\">\n <video #video autoplay [muted]=\"true\" [hidden]=\"isCaptured || !
|
|
10312
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: PhotoCaptureWidgetComponent, isStandalone: true, selector: "photo-capture-widget", inputs: { maxFileSize: { classPropertyName: "maxFileSize", publicName: "maxFileSize", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, externalTrigger: { classPropertyName: "externalTrigger", publicName: "externalTrigger", isSignal: true, isRequired: false, transformFunction: null }, texts: { classPropertyName: "texts", publicName: "texts", isSignal: true, isRequired: false, transformFunction: null }, deviceStart: { classPropertyName: "deviceStart", publicName: "deviceStart", isSignal: true, isRequired: false, transformFunction: null }, handover: { classPropertyName: "handover", publicName: "handover", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { photoCaptured: "photoCaptured" }, viewQueries: [{ propertyName: "videoRef", first: true, predicate: ["video"], descendants: true }, { propertyName: "canvasRef", first: true, predicate: ["canvas"], descendants: true }, { propertyName: "fileInputRef", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "@if (isMobile()) {\n <input\n #fileInput\n type=\"file\"\n [accept]=\"mode() === 'VIDEO' ? 'video/*' : 'image/*'\"\n capture=\"environment\"\n (change)=\"onFileSelected($event)\"\n style=\"display: none\"\n />\n @if (!externalTrigger()) {\n <div class=\"photoWidgetContainer\">\n <div class=\"controls\">\n <ui-action-button [descriptor]=\"captureButton()\" (actionClick)=\"fileInput.click()\">\n </ui-action-button>\n </div>\n </div>\n }\n} @else if (!externalTrigger() || isPanelOpen()) {\n <div class=\"photoWidgetContainer\">\n <video #video autoplay [muted]=\"true\" [hidden]=\"device.isCaptured() || !device.isCameraActive()\"></video>\n <canvas #canvas [hidden]=\"!device.isCaptured() || device.isCameraActive()\"></canvas>\n @if (device.keptVideoUrl(); as videoUrl) {\n <video controls [src]=\"videoUrl\"></video>\n }\n @if (device.cameraError()) {\n <p class=\"message\">{{ texts().cameraError }}</p>\n } @else if (device.isRecording()) {\n <p class=\"message\">{{ texts().recording }}</p>\n } @else if (isSwitchOnHintShown()) {\n @if (mode() === 'VIDEO') {\n <p class=\"message\">{{ texts().switchOnForVideo }}</p>\n } @else {\n <p class=\"message\">{{ texts().switchOnCameraHint }}</p>\n }\n }\n <div class=\"controls\">\n @if (isCameraSwitchShown()) {\n <!-- START CAMERA -->\n <ui-action-button\n class=\"toggleCameraButton\"\n [disabled]=\"device.isRecording()\"\n [descriptor]=\"toggleCameraButton()\"\n (actionClick)=\"device.toggleCamera()\"\n >\n </ui-action-button>\n }\n @if (device.keptFiles()) {\n <ui-action-button\n class=\"retakeButton\"\n [descriptor]=\"retakeButton()\"\n (actionClick)=\"device.retakeCapture()\"\n >\n </ui-action-button>\n <ui-action-button\n class=\"useButton\"\n [descriptor]=\"useButton()\"\n (actionClick)=\"device.useCapture()\"\n >\n </ui-action-button>\n } @else if (mode() === 'VIDEO') {\n <!-- RECORD / STOP VIDEO -->\n @if (device.isRecording()) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"device.stopRecording()\"\n >\n </ui-action-button>\n } @else {\n <ui-action-button\n class=\"startRecordingButton\"\n [disabled]=\"!device.isCameraActive()\"\n [descriptor]=\"startRecordingButton()\"\n (actionClick)=\"device.startRecording()\"\n >\n </ui-action-button>\n }\n } @else {\n <!-- CAPTURE CAMERA -->\n <ui-action-button\n class=\"capturePhotoButton\"\n [disabled]=\"!device.isCameraActive()\"\n [descriptor]=\"captureButton()\"\n (actionClick)=\"device.capturePhoto()\"\n >\n </ui-action-button>\n }\n @if (mode() === 'VIDEO' && !device.keptFiles()) {\n <!-- PICK VIDEO FILE -->\n <input\n #fileInput\n type=\"file\"\n accept=\"video/*\"\n (change)=\"onFileSelected($event)\"\n style=\"display: none\"\n />\n <ui-action-button\n class=\"pickVideoButton\"\n [disabled]=\"device.isRecording()\"\n [descriptor]=\"pickVideoButton()\"\n (actionClick)=\"fileInput.click()\"\n >\n </ui-action-button>\n }\n </div>\n </div>\n}\n", styles: [".photoWidgetContainer{border-radius:5px;width:100%;max-width:100%;display:flex;flex-direction:column;justify-content:center;border:1px solid #dee2e6}.message{text-align:center;color:#6b7280}video,canvas{border-top-right-radius:5px;border-top-left-radius:5px;width:100%}.controls{padding:.5rem;display:flex;gap:1rem;flex-direction:row;justify-content:center;background:#f8f9fa;border-top:1px solid #dee2e6;border-bottom-right-radius:5px;border-bottom-left-radius:5px}.controls ::ng-deep button{border-radius:3px}\n"], dependencies: [{ kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
|
|
9621
10313
|
}
|
|
9622
10314
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PhotoCaptureWidgetComponent, decorators: [{
|
|
9623
10315
|
type: Component,
|
|
9624
|
-
args: [{ selector: 'photo-capture-widget', imports: [UiActionButtonComponent], template: "@if (isMobile()) {\n <input\n #fileInput\n type=\"file\"\n [accept]=\"mode() === 'VIDEO' ? 'video/*' : 'image/*'\"\n capture=\"environment\"\n (change)=\"onFileSelected($event)\"\n style=\"display: none\"\n />\n @if (!externalTrigger()) {\n <div class=\"photoWidgetContainer\">\n <div class=\"controls\">\n <ui-action-button [descriptor]=\"captureButton()\" (actionClick)=\"fileInput.click()\">\n </ui-action-button>\n </div>\n </div>\n }\n} @else if (!externalTrigger() || isPanelOpen()) {\n <div class=\"photoWidgetContainer\">\n <video #video autoplay [muted]=\"true\" [hidden]=\"isCaptured || !
|
|
10316
|
+
args: [{ selector: 'photo-capture-widget', imports: [UiActionButtonComponent], template: "@if (isMobile()) {\n <input\n #fileInput\n type=\"file\"\n [accept]=\"mode() === 'VIDEO' ? 'video/*' : 'image/*'\"\n capture=\"environment\"\n (change)=\"onFileSelected($event)\"\n style=\"display: none\"\n />\n @if (!externalTrigger()) {\n <div class=\"photoWidgetContainer\">\n <div class=\"controls\">\n <ui-action-button [descriptor]=\"captureButton()\" (actionClick)=\"fileInput.click()\">\n </ui-action-button>\n </div>\n </div>\n }\n} @else if (!externalTrigger() || isPanelOpen()) {\n <div class=\"photoWidgetContainer\">\n <video #video autoplay [muted]=\"true\" [hidden]=\"device.isCaptured() || !device.isCameraActive()\"></video>\n <canvas #canvas [hidden]=\"!device.isCaptured() || device.isCameraActive()\"></canvas>\n @if (device.keptVideoUrl(); as videoUrl) {\n <video controls [src]=\"videoUrl\"></video>\n }\n @if (device.cameraError()) {\n <p class=\"message\">{{ texts().cameraError }}</p>\n } @else if (device.isRecording()) {\n <p class=\"message\">{{ texts().recording }}</p>\n } @else if (isSwitchOnHintShown()) {\n @if (mode() === 'VIDEO') {\n <p class=\"message\">{{ texts().switchOnForVideo }}</p>\n } @else {\n <p class=\"message\">{{ texts().switchOnCameraHint }}</p>\n }\n }\n <div class=\"controls\">\n @if (isCameraSwitchShown()) {\n <!-- START CAMERA -->\n <ui-action-button\n class=\"toggleCameraButton\"\n [disabled]=\"device.isRecording()\"\n [descriptor]=\"toggleCameraButton()\"\n (actionClick)=\"device.toggleCamera()\"\n >\n </ui-action-button>\n }\n @if (device.keptFiles()) {\n <ui-action-button\n class=\"retakeButton\"\n [descriptor]=\"retakeButton()\"\n (actionClick)=\"device.retakeCapture()\"\n >\n </ui-action-button>\n <ui-action-button\n class=\"useButton\"\n [descriptor]=\"useButton()\"\n (actionClick)=\"device.useCapture()\"\n >\n </ui-action-button>\n } @else if (mode() === 'VIDEO') {\n <!-- RECORD / STOP VIDEO -->\n @if (device.isRecording()) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"device.stopRecording()\"\n >\n </ui-action-button>\n } @else {\n <ui-action-button\n class=\"startRecordingButton\"\n [disabled]=\"!device.isCameraActive()\"\n [descriptor]=\"startRecordingButton()\"\n (actionClick)=\"device.startRecording()\"\n >\n </ui-action-button>\n }\n } @else {\n <!-- CAPTURE CAMERA -->\n <ui-action-button\n class=\"capturePhotoButton\"\n [disabled]=\"!device.isCameraActive()\"\n [descriptor]=\"captureButton()\"\n (actionClick)=\"device.capturePhoto()\"\n >\n </ui-action-button>\n }\n @if (mode() === 'VIDEO' && !device.keptFiles()) {\n <!-- PICK VIDEO FILE -->\n <input\n #fileInput\n type=\"file\"\n accept=\"video/*\"\n (change)=\"onFileSelected($event)\"\n style=\"display: none\"\n />\n <ui-action-button\n class=\"pickVideoButton\"\n [disabled]=\"device.isRecording()\"\n [descriptor]=\"pickVideoButton()\"\n (actionClick)=\"fileInput.click()\"\n >\n </ui-action-button>\n }\n </div>\n </div>\n}\n", styles: [".photoWidgetContainer{border-radius:5px;width:100%;max-width:100%;display:flex;flex-direction:column;justify-content:center;border:1px solid #dee2e6}.message{text-align:center;color:#6b7280}video,canvas{border-top-right-radius:5px;border-top-left-radius:5px;width:100%}.controls{padding:.5rem;display:flex;gap:1rem;flex-direction:row;justify-content:center;background:#f8f9fa;border-top:1px solid #dee2e6;border-bottom-right-radius:5px;border-bottom-left-radius:5px}.controls ::ng-deep button{border-radius:3px}\n"] }]
|
|
9625
10317
|
}], propDecorators: { videoRef: [{
|
|
9626
10318
|
type: ViewChild,
|
|
9627
10319
|
args: ['video']
|
|
@@ -9631,12 +10323,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
9631
10323
|
}], fileInputRef: [{
|
|
9632
10324
|
type: ViewChild,
|
|
9633
10325
|
args: ['fileInput']
|
|
9634
|
-
}], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], externalTrigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "externalTrigger", required: false }] }], texts: [{ type: i0.Input, args: [{ isSignal: true, alias: "texts", required: false }] }], photoCaptured: [{ type: i0.Output, args: ["photoCaptured"] }] } });
|
|
10326
|
+
}], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], externalTrigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "externalTrigger", required: false }] }], texts: [{ type: i0.Input, args: [{ isSignal: true, alias: "texts", required: false }] }], deviceStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "deviceStart", required: false }] }], handover: [{ type: i0.Input, args: [{ isSignal: true, alias: "handover", required: false }] }], photoCaptured: [{ type: i0.Output, args: ["photoCaptured"] }] } });
|
|
9635
10327
|
|
|
10328
|
+
/** The inline recorder strip of the upload zone and of the deprecated recordingUploader. */
|
|
9636
10329
|
class VoiceRecordWidgetComponent {
|
|
9637
10330
|
constructor() {
|
|
9638
|
-
this.ngZone = inject(NgZone);
|
|
9639
|
-
this.changeDetectorRef = inject(ChangeDetectorRef);
|
|
9640
10331
|
this.openDirection = input('RIGHT', /* @ts-ignore */
|
|
9641
10332
|
...(ngDevMode ? [{ debugName: "openDirection" }] : /* istanbul ignore next */ []));
|
|
9642
10333
|
/** The zone's texts, the microphone error line's among them. */
|
|
@@ -9645,15 +10336,24 @@ class VoiceRecordWidgetComponent {
|
|
|
9645
10336
|
/** True in the upload zone only; the deprecated recordingUploader shows no error line. */
|
|
9646
10337
|
this.showMicrophoneError = input(false, /* @ts-ignore */
|
|
9647
10338
|
...(ngDevMode ? [{ debugName: "showMicrophoneError" }] : /* istanbul ignore next */ []));
|
|
10339
|
+
/** AUTO starts the recording when the widget opens, MANUAL at the microphone button. */
|
|
10340
|
+
this.deviceStart = input(CaptureDeviceStart.MANUAL, /* @ts-ignore */
|
|
10341
|
+
...(ngDevMode ? [{ debugName: "deviceStart" }] : /* istanbul ignore next */ []));
|
|
10342
|
+
/** CONFIRM keeps a stopped recording for playback and save, IMMEDIATE hands it over. */
|
|
10343
|
+
this.handover = input(CaptureHandover.CONFIRM, /* @ts-ignore */
|
|
10344
|
+
...(ngDevMode ? [{ debugName: "handover" }] : /* istanbul ignore next */ []));
|
|
10345
|
+
/** True under the zone's Capture Settings; everywhere else the save stays an icon. */
|
|
10346
|
+
this.showSaveTitle = input(false, /* @ts-ignore */
|
|
10347
|
+
...(ngDevMode ? [{ debugName: "showSaveTitle" }] : /* istanbul ignore next */ []));
|
|
9648
10348
|
this.recordingSaved = output();
|
|
9649
|
-
this.
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
10349
|
+
this.device = new MicrophoneDevice({
|
|
10350
|
+
handover: this.handover,
|
|
10351
|
+
ngZone: inject(NgZone),
|
|
10352
|
+
getWaveformElement: () => this.waveformRef?.nativeElement,
|
|
10353
|
+
getButtonElement: () => this.mainButton.nativeElement,
|
|
10354
|
+
getThemeElement: () => document.documentElement,
|
|
10355
|
+
handOver: (file) => this.recordingSaved.emit(file),
|
|
10356
|
+
});
|
|
9657
10357
|
this.micButton = {
|
|
9658
10358
|
title: '',
|
|
9659
10359
|
type: UiActionButtonType.ICON,
|
|
@@ -9676,6 +10376,10 @@ class VoiceRecordWidgetComponent {
|
|
|
9676
10376
|
iconPosition: IconPosition.PRE,
|
|
9677
10377
|
color: 'primary',
|
|
9678
10378
|
};
|
|
10379
|
+
this.effectiveSaveButton = computed(() => this.showSaveTitle()
|
|
10380
|
+
? { ...this.saveButton, title: this.texts().save, type: UiActionButtonType.RAISED }
|
|
10381
|
+
: this.saveButton, /* @ts-ignore */
|
|
10382
|
+
...(ngDevMode ? [{ debugName: "effectiveSaveButton" }] : /* istanbul ignore next */ []));
|
|
9679
10383
|
}
|
|
9680
10384
|
ngOnInit() {
|
|
9681
10385
|
this.playButton = {
|
|
@@ -9693,195 +10397,34 @@ class VoiceRecordWidgetComponent {
|
|
|
9693
10397
|
color: 'red',
|
|
9694
10398
|
};
|
|
9695
10399
|
}
|
|
9696
|
-
ngAfterViewInit() {
|
|
9697
|
-
|
|
9698
|
-
|
|
9699
|
-
}
|
|
9700
|
-
async initializeWaveSurferIfNeeded() {
|
|
9701
|
-
if (this.wavesurfer || !this.waveformRef?.nativeElement) {
|
|
9702
|
-
return;
|
|
9703
|
-
}
|
|
9704
|
-
await new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
9705
|
-
this.waveformRef.nativeElement.style.padding = '0.1rem';
|
|
9706
|
-
this.initWaveSurfer();
|
|
9707
|
-
}
|
|
9708
|
-
initWaveSurfer() {
|
|
9709
|
-
let body = document.querySelector('body');
|
|
9710
|
-
let primaryColor = getComputedStyle(document.documentElement)
|
|
9711
|
-
.getPropertyValue('--primary-color')
|
|
9712
|
-
.trim();
|
|
9713
|
-
let el = this.mainButton.nativeElement.querySelector('.sb4-primary');
|
|
9714
|
-
let remInPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
9715
|
-
let height = (el?.getBoundingClientRect().height ?? 0) - remInPx * 0.1;
|
|
9716
|
-
this.wavesurfer = WaveSurfer.create({
|
|
9717
|
-
container: this.waveformRef.nativeElement,
|
|
9718
|
-
waveColor: primaryColor,
|
|
9719
|
-
progressColor: this.addAlphaToColor(primaryColor, 0.5),
|
|
9720
|
-
height: height,
|
|
9721
|
-
barWidth: 5,
|
|
9722
|
-
barGap: 1,
|
|
9723
|
-
barRadius: 5,
|
|
9724
|
-
});
|
|
9725
|
-
this.wavesurfer.setVolume(1);
|
|
9726
|
-
const mimeType = UploadWidgetUtils.chooseRecorderMimeType('audio');
|
|
9727
|
-
this.record = this.wavesurfer.registerPlugin(RecordPlugin.create({
|
|
9728
|
-
renderRecordedAudio: true,
|
|
9729
|
-
scrollingWaveform: true,
|
|
9730
|
-
continuousWaveform: false,
|
|
9731
|
-
continuousWaveformDuration: 15,
|
|
9732
|
-
mimeType,
|
|
9733
|
-
}));
|
|
9734
|
-
this.wavesurfer.on('dblclick', () => {
|
|
9735
|
-
this.updateViewState(() => {
|
|
9736
|
-
this.wavesurfer?.play();
|
|
9737
|
-
this.isPlaying = true;
|
|
9738
|
-
});
|
|
9739
|
-
});
|
|
9740
|
-
this.record.on('record-end', (blob) => {
|
|
9741
|
-
this.updateViewState(() => {
|
|
9742
|
-
this.recordedBlob = blob;
|
|
9743
|
-
this.isRecording = false;
|
|
9744
|
-
});
|
|
9745
|
-
});
|
|
9746
|
-
this.record.on('record-progress', (time) => {
|
|
9747
|
-
this.updateViewState(() => {
|
|
9748
|
-
this.updateProgress(time);
|
|
9749
|
-
});
|
|
9750
|
-
});
|
|
9751
|
-
this.wavesurfer.on('finish', () => {
|
|
9752
|
-
this.updateViewState(() => {
|
|
9753
|
-
this.isPlaying = false;
|
|
9754
|
-
});
|
|
9755
|
-
});
|
|
9756
|
-
}
|
|
9757
|
-
async startRecording() {
|
|
9758
|
-
const confirmed = this.checkHasRecorded();
|
|
9759
|
-
if (!confirmed || this.isRecording)
|
|
9760
|
-
return;
|
|
9761
|
-
// Initialize first (still within the gesture tick on most browsers)
|
|
9762
|
-
await this.initializeWaveSurferIfNeeded();
|
|
9763
|
-
if (!this.record)
|
|
9764
|
-
throw new Error('Voice recorder plugin is not initialized.');
|
|
9765
|
-
this.updateViewState(() => {
|
|
9766
|
-
this.microphoneError.set(false);
|
|
9767
|
-
this.isWaveformOpen = true;
|
|
9768
|
-
this.isRecording = true;
|
|
9769
|
-
this.isPlaying = false;
|
|
9770
|
-
this.recordedBlob = null;
|
|
9771
|
-
this.time = '00:00';
|
|
9772
|
-
});
|
|
9773
|
-
try {
|
|
9774
|
-
// Force native mic permission first so it works with WKWebView
|
|
9775
|
-
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
9776
|
-
stream.getTracks().forEach((track) => track.stop());
|
|
9777
|
-
await this.record.startRecording();
|
|
9778
|
-
}
|
|
9779
|
-
catch (error) {
|
|
9780
|
-
console.error('Mic failed', error);
|
|
9781
|
-
this.updateViewState(() => {
|
|
9782
|
-
this.isRecording = false;
|
|
9783
|
-
this.isWaveformOpen = !!this.recordedBlob;
|
|
9784
|
-
this.microphoneError.set(true);
|
|
9785
|
-
});
|
|
9786
|
-
throw error;
|
|
9787
|
-
}
|
|
9788
|
-
}
|
|
9789
|
-
/**
|
|
9790
|
-
* Whether recording may start. Always `true` today: the branch below is where a "you
|
|
9791
|
-
* already have a recording — overwrite it?" confirmation was meant to go, and never did.
|
|
9792
|
-
* Kept, rather than inlined away, so the place stays visible; see A37.
|
|
9793
|
-
*/
|
|
9794
|
-
checkHasRecorded() {
|
|
9795
|
-
if (this.recordedBlob) {
|
|
9796
|
-
}
|
|
9797
|
-
return true;
|
|
9798
|
-
}
|
|
9799
|
-
updateProgress(time) {
|
|
9800
|
-
let formattedTime = [Math.floor((time % 3600000) / 60000), Math.floor((time % 60000) / 1000)]
|
|
9801
|
-
.map((v) => (v < 10 ? '0' + v : v))
|
|
9802
|
-
.join(':');
|
|
9803
|
-
this.time = formattedTime;
|
|
9804
|
-
}
|
|
9805
|
-
stopRecording() {
|
|
9806
|
-
if (this.record && this.isRecording) {
|
|
9807
|
-
this.record.stopRecording();
|
|
9808
|
-
this.updateViewState(() => {
|
|
9809
|
-
this.isRecording = false;
|
|
9810
|
-
});
|
|
9811
|
-
}
|
|
9812
|
-
}
|
|
9813
|
-
saveRecording() {
|
|
9814
|
-
if (this.recordedBlob) {
|
|
9815
|
-
const file = UploadWidgetUtils.createRecordingFile(this.recordedBlob, 'recording', 'audio/mp4');
|
|
9816
|
-
this.recordingSaved.emit(file);
|
|
9817
|
-
// Destroy completely so next recording initializes fresh
|
|
9818
|
-
this.wavesurfer?.destroy();
|
|
9819
|
-
this.wavesurfer = undefined;
|
|
9820
|
-
this.record = undefined;
|
|
9821
|
-
this.updateViewState(() => {
|
|
9822
|
-
this.recordedBlob = null;
|
|
9823
|
-
this.time = '00:00';
|
|
9824
|
-
this.isPlaying = false;
|
|
9825
|
-
this.isWaveformOpen = false;
|
|
9826
|
-
});
|
|
10400
|
+
ngAfterViewInit() {
|
|
10401
|
+
if (this.deviceStart() === CaptureDeviceStart.AUTO) {
|
|
10402
|
+
this.device.requestRecording();
|
|
9827
10403
|
}
|
|
9828
10404
|
}
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
this.updateViewState(() => {
|
|
9832
|
-
this.isPlaying = !this.isPlaying;
|
|
9833
|
-
this.wavesurfer?.playPause();
|
|
9834
|
-
});
|
|
9835
|
-
}
|
|
10405
|
+
get isLeftDirection() {
|
|
10406
|
+
return this.openDirection() === 'LEFT';
|
|
9836
10407
|
}
|
|
9837
10408
|
ngOnDestroy() {
|
|
9838
|
-
|
|
9839
|
-
this.wavesurfer.destroy();
|
|
9840
|
-
}
|
|
9841
|
-
}
|
|
9842
|
-
addAlphaToColor(hexColor, alpha) {
|
|
9843
|
-
if (/^#([a-f\d])([a-f\d])([a-f\d])$/i.test(hexColor)) {
|
|
9844
|
-
hexColor = hexColor.replace(/^#([a-f\d])([a-f\d])([a-f\d])$/i, (_, r, g, b) => `#${r}${r}${g}${g}${b}${b}`);
|
|
9845
|
-
}
|
|
9846
|
-
const hexMatch = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexColor);
|
|
9847
|
-
if (hexMatch) {
|
|
9848
|
-
const [, r, g, b] = hexMatch;
|
|
9849
|
-
return `rgba(${parseInt(r, 16)}, ${parseInt(g, 16)}, ${parseInt(b, 16)}, ${alpha})`;
|
|
9850
|
-
}
|
|
9851
|
-
const ctx = document.createElement('canvas').getContext('2d');
|
|
9852
|
-
if (ctx) {
|
|
9853
|
-
ctx.fillStyle = hexColor;
|
|
9854
|
-
const parsed = ctx.fillStyle;
|
|
9855
|
-
const rgbMatch = /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/.exec(parsed);
|
|
9856
|
-
if (rgbMatch) {
|
|
9857
|
-
const [, r, g, b] = rgbMatch;
|
|
9858
|
-
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
9859
|
-
}
|
|
9860
|
-
}
|
|
9861
|
-
return hexColor;
|
|
9862
|
-
}
|
|
9863
|
-
updateViewState(update) {
|
|
9864
|
-
this.ngZone.run(() => {
|
|
9865
|
-
update();
|
|
9866
|
-
this.changeDetectorRef.markForCheck();
|
|
9867
|
-
});
|
|
10409
|
+
this.device.release();
|
|
9868
10410
|
}
|
|
9869
10411
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: VoiceRecordWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9870
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: VoiceRecordWidgetComponent, isStandalone: true, selector: "voice-record-widget", inputs: { openDirection: { classPropertyName: "openDirection", publicName: "openDirection", isSignal: true, isRequired: false, transformFunction: null }, texts: { classPropertyName: "texts", publicName: "texts", isSignal: true, isRequired: false, transformFunction: null }, showMicrophoneError: { classPropertyName: "showMicrophoneError", publicName: "showMicrophoneError", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { recordingSaved: "recordingSaved" }, viewQueries: [{ propertyName: "waveformRef", first: true, predicate: ["waveform"], descendants: true }, { propertyName: "mainButton", first: true, predicate: ["mainButton"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div\n class=\"recorderContainer\"\n [class.direction-left]=\"isLeftDirection\"\n [class.direction-right]=\"!isLeftDirection\"\n [class.active]=\"
|
|
10412
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: VoiceRecordWidgetComponent, isStandalone: true, selector: "voice-record-widget", inputs: { openDirection: { classPropertyName: "openDirection", publicName: "openDirection", isSignal: true, isRequired: false, transformFunction: null }, texts: { classPropertyName: "texts", publicName: "texts", isSignal: true, isRequired: false, transformFunction: null }, showMicrophoneError: { classPropertyName: "showMicrophoneError", publicName: "showMicrophoneError", isSignal: true, isRequired: false, transformFunction: null }, deviceStart: { classPropertyName: "deviceStart", publicName: "deviceStart", isSignal: true, isRequired: false, transformFunction: null }, handover: { classPropertyName: "handover", publicName: "handover", isSignal: true, isRequired: false, transformFunction: null }, showSaveTitle: { classPropertyName: "showSaveTitle", publicName: "showSaveTitle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { recordingSaved: "recordingSaved" }, viewQueries: [{ propertyName: "waveformRef", first: true, predicate: ["waveform"], descendants: true }, { propertyName: "mainButton", first: true, predicate: ["mainButton"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div\n class=\"recorderContainer\"\n [class.direction-left]=\"isLeftDirection\"\n [class.direction-right]=\"!isLeftDirection\"\n [class.active]=\"device.isWaveformShown()\"\n [class.isRecording]=\"device.isRecording()\"\n [class.hasRecording]=\"device.recordedBlob()\"\n >\n <div class=\"recorderMain\">\n <div class=\"recorderToolbar\">\n <!-- Start Recording -->\n @if (!device.isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"micButton\"\n (actionClick)=\"device.requestRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Stop Recording -->\n @if (device.isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"stopButton\"\n (actionClick)=\"device.stopRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Save Recording -->\n @if (device.recordedBlob()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"effectiveSaveButton()\"\n (actionClick)=\"device.saveRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Start Play -->\n @if (device.recordedBlob() && !device.isPlaying()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"playButton\"\n (actionClick)=\"device.replayRecording()\"\n ></ui-action-button>\n }\n\n <!-- Stop Play -->\n @if (device.recordedBlob() && device.isPlaying()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"stopPlaybackButton\"\n (actionClick)=\"device.replayRecording()\"\n ></ui-action-button>\n }\n\n @if (device.isRecording()) {\n <div class=\"progress\">{{ device.time() }}</div>\n }\n </div>\n\n <div class=\"waveformBand\" [class.open]=\"device.isWaveformShown()\">\n <div class=\"waveformGrid\"></div>\n <div class=\"waveform\" #waveform></div>\n </div>\n </div>\n</div>\n@if (showMicrophoneError() && device.microphoneError()) {\n <p class=\"message\">{{ texts().microphoneError }}</p>\n}\n", styles: [":host{display:block;width:100%;min-width:0}.message{text-align:center;color:#6b7280}:host ::ng-deep .waveform{height:unset!important}.recorderContainer{display:flex;flex-direction:row;justify-content:space-between;width:100%;min-width:0;align-items:center;gap:.5rem}.active ::ng-deep .recorderToolbar{border:1px solid #e5e7eb;border-radius:.5rem}.isRecording ::ng-deep .recorderToolbar{padding-right:1rem}.recorderToolbar ::ng-deep .sb4-red,.recorderToolbar ::ng-deep .sb4-primary{border:1px solid #e5e7eb;box-shadow:unset}.active ::ng-deep .recorderToolbar ::ng-deep .sb4-red,.active ::ng-deep .recorderToolbar ::ng-deep .sb4-primary{border:unset}.mainButton ::ng-deep .sb4-red,.mainButton ::ng-deep .sb4-primary{padding:.5rem .75rem}.recorderMain{display:flex;align-items:center;min-width:0;flex:1}.direction-right .recorderMain{flex-direction:row}.direction-left .recorderMain{flex-direction:row-reverse}.waveform{width:100%;height:45px}.waveformBand{position:relative;display:grid;align-items:center;width:0;max-width:0;min-width:0;opacity:0;overflow:hidden;border-radius:5px;flex:1 1 0;transform:scaleX(.96);will-change:opacity,transform,margin;transition:max-width .22s ease,opacity .18s ease,margin .22s ease,transform .22s ease}.waveformBand.open{min-width:35px;max-width:100%;opacity:1;transform:scaleX(1)}.direction-right .waveformBand{margin-left:0}.direction-left .waveformBand{margin-right:0}.direction-right .waveformBand.open{margin-left:.45rem}.direction-left .waveformBand.open{margin-right:.45rem}.waveformGrid{position:absolute;inset:0;border:1px solid #e5e7eb;border-radius:.5rem;background:repeating-linear-gradient(to right,rgba(148,163,184,.35) 0,rgba(148,163,184,.35) 2px,transparent 2px,transparent 8px),linear-gradient(to bottom,#94a3b814,#94a3b805);pointer-events:none}.recorderToolbar{background-color:transparent;display:flex;flex-direction:row;align-items:center}.progress{align-content:center;font-size:1rem;height:100%;color:#6b7280}.hasRecording ::ng-deep .recorderToolbar ui-action-button{border-right:1px solid #e5e7eb}.hasRecording ::ng-deep .recorderToolbar ui-action-button:last-child{border-right:unset}\n"], dependencies: [{ kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
|
|
9871
10413
|
}
|
|
9872
10414
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: VoiceRecordWidgetComponent, decorators: [{
|
|
9873
10415
|
type: Component,
|
|
9874
|
-
args: [{ selector: 'voice-record-widget', imports: [UiActionButtonComponent], template: "<div\n class=\"recorderContainer\"\n [class.direction-left]=\"isLeftDirection\"\n [class.direction-right]=\"!isLeftDirection\"\n [class.active]=\"
|
|
10416
|
+
args: [{ selector: 'voice-record-widget', imports: [UiActionButtonComponent], template: "<div\n class=\"recorderContainer\"\n [class.direction-left]=\"isLeftDirection\"\n [class.direction-right]=\"!isLeftDirection\"\n [class.active]=\"device.isWaveformShown()\"\n [class.isRecording]=\"device.isRecording()\"\n [class.hasRecording]=\"device.recordedBlob()\"\n >\n <div class=\"recorderMain\">\n <div class=\"recorderToolbar\">\n <!-- Start Recording -->\n @if (!device.isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"micButton\"\n (actionClick)=\"device.requestRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Stop Recording -->\n @if (device.isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"stopButton\"\n (actionClick)=\"device.stopRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Save Recording -->\n @if (device.recordedBlob()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"effectiveSaveButton()\"\n (actionClick)=\"device.saveRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Start Play -->\n @if (device.recordedBlob() && !device.isPlaying()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"playButton\"\n (actionClick)=\"device.replayRecording()\"\n ></ui-action-button>\n }\n\n <!-- Stop Play -->\n @if (device.recordedBlob() && device.isPlaying()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"stopPlaybackButton\"\n (actionClick)=\"device.replayRecording()\"\n ></ui-action-button>\n }\n\n @if (device.isRecording()) {\n <div class=\"progress\">{{ device.time() }}</div>\n }\n </div>\n\n <div class=\"waveformBand\" [class.open]=\"device.isWaveformShown()\">\n <div class=\"waveformGrid\"></div>\n <div class=\"waveform\" #waveform></div>\n </div>\n </div>\n</div>\n@if (showMicrophoneError() && device.microphoneError()) {\n <p class=\"message\">{{ texts().microphoneError }}</p>\n}\n", styles: [":host{display:block;width:100%;min-width:0}.message{text-align:center;color:#6b7280}:host ::ng-deep .waveform{height:unset!important}.recorderContainer{display:flex;flex-direction:row;justify-content:space-between;width:100%;min-width:0;align-items:center;gap:.5rem}.active ::ng-deep .recorderToolbar{border:1px solid #e5e7eb;border-radius:.5rem}.isRecording ::ng-deep .recorderToolbar{padding-right:1rem}.recorderToolbar ::ng-deep .sb4-red,.recorderToolbar ::ng-deep .sb4-primary{border:1px solid #e5e7eb;box-shadow:unset}.active ::ng-deep .recorderToolbar ::ng-deep .sb4-red,.active ::ng-deep .recorderToolbar ::ng-deep .sb4-primary{border:unset}.mainButton ::ng-deep .sb4-red,.mainButton ::ng-deep .sb4-primary{padding:.5rem .75rem}.recorderMain{display:flex;align-items:center;min-width:0;flex:1}.direction-right .recorderMain{flex-direction:row}.direction-left .recorderMain{flex-direction:row-reverse}.waveform{width:100%;height:45px}.waveformBand{position:relative;display:grid;align-items:center;width:0;max-width:0;min-width:0;opacity:0;overflow:hidden;border-radius:5px;flex:1 1 0;transform:scaleX(.96);will-change:opacity,transform,margin;transition:max-width .22s ease,opacity .18s ease,margin .22s ease,transform .22s ease}.waveformBand.open{min-width:35px;max-width:100%;opacity:1;transform:scaleX(1)}.direction-right .waveformBand{margin-left:0}.direction-left .waveformBand{margin-right:0}.direction-right .waveformBand.open{margin-left:.45rem}.direction-left .waveformBand.open{margin-right:.45rem}.waveformGrid{position:absolute;inset:0;border:1px solid #e5e7eb;border-radius:.5rem;background:repeating-linear-gradient(to right,rgba(148,163,184,.35) 0,rgba(148,163,184,.35) 2px,transparent 2px,transparent 8px),linear-gradient(to bottom,#94a3b814,#94a3b805);pointer-events:none}.recorderToolbar{background-color:transparent;display:flex;flex-direction:row;align-items:center}.progress{align-content:center;font-size:1rem;height:100%;color:#6b7280}.hasRecording ::ng-deep .recorderToolbar ui-action-button{border-right:1px solid #e5e7eb}.hasRecording ::ng-deep .recorderToolbar ui-action-button:last-child{border-right:unset}\n"] }]
|
|
9875
10417
|
}], propDecorators: { waveformRef: [{
|
|
9876
10418
|
type: ViewChild,
|
|
9877
10419
|
args: ['waveform']
|
|
9878
10420
|
}], mainButton: [{
|
|
9879
10421
|
type: ViewChild,
|
|
9880
10422
|
args: ['mainButton', { read: ElementRef }]
|
|
9881
|
-
}], openDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "openDirection", required: false }] }], texts: [{ type: i0.Input, args: [{ isSignal: true, alias: "texts", required: false }] }], showMicrophoneError: [{ type: i0.Input, args: [{ isSignal: true, alias: "showMicrophoneError", required: false }] }], recordingSaved: [{ type: i0.Output, args: ["recordingSaved"] }] } });
|
|
10423
|
+
}], openDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "openDirection", required: false }] }], texts: [{ type: i0.Input, args: [{ isSignal: true, alias: "texts", required: false }] }], showMicrophoneError: [{ type: i0.Input, args: [{ isSignal: true, alias: "showMicrophoneError", required: false }] }], deviceStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "deviceStart", required: false }] }], handover: [{ type: i0.Input, args: [{ isSignal: true, alias: "handover", required: false }] }], showSaveTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSaveTitle", required: false }] }], recordingSaved: [{ type: i0.Output, args: ["recordingSaved"] }] } });
|
|
9882
10424
|
|
|
9883
10425
|
class UploadWidgetComponent {
|
|
9884
10426
|
constructor() {
|
|
10427
|
+
this.captureDialogService = inject(CaptureDialogService);
|
|
9885
10428
|
this.uploadDescriptor = input(this.defaultUploadDescriptor, /* @ts-ignore */
|
|
9886
10429
|
...(ngDevMode ? [{ debugName: "uploadDescriptor" }] : /* istanbul ignore next */ []));
|
|
9887
10430
|
this.isMultiple = input.required(/* @ts-ignore */
|
|
@@ -9890,7 +10433,20 @@ class UploadWidgetComponent {
|
|
|
9890
10433
|
...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
9891
10434
|
this.sources = input(undefined, /* @ts-ignore */
|
|
9892
10435
|
...(ngDevMode ? [{ debugName: "sources" }] : /* istanbul ignore next */ []));
|
|
10436
|
+
this.capture = input(undefined, /* @ts-ignore */
|
|
10437
|
+
...(ngDevMode ? [{ debugName: "capture" }] : /* istanbul ignore next */ []));
|
|
9893
10438
|
this.uploadFilesEvent = output();
|
|
10439
|
+
/** The Capture Settings in force: a missing bean or field takes its default here only. */
|
|
10440
|
+
this.captureSettings = computed(() => {
|
|
10441
|
+
const capture = this.capture();
|
|
10442
|
+
return {
|
|
10443
|
+
mode: capture?.mode ?? CaptureMode.DIALOG,
|
|
10444
|
+
openDirection: capture?.openDirection ?? CaptureOpenDirection.RIGHT,
|
|
10445
|
+
deviceStart: capture?.deviceStart ?? CaptureDeviceStart.AUTO,
|
|
10446
|
+
handover: capture?.handover ?? CaptureHandover.CONFIRM,
|
|
10447
|
+
};
|
|
10448
|
+
}, /* @ts-ignore */
|
|
10449
|
+
...(ngDevMode ? [{ debugName: "captureSettings" }] : /* istanbul ignore next */ []));
|
|
9894
10450
|
this.uploadWidgetType = UploadWidgetType;
|
|
9895
10451
|
this.fileUploaderSource = FileUploaderSource;
|
|
9896
10452
|
/** The recorder opens below the capture row at a click on the microphone button. */
|
|
@@ -9965,17 +10521,45 @@ class UploadWidgetComponent {
|
|
|
9965
10521
|
getCaptureSources() {
|
|
9966
10522
|
return (this.sources() ?? []).filter((source) => source !== FileUploaderSource.FILE);
|
|
9967
10523
|
}
|
|
10524
|
+
/** On a phone a camera button stays the device's own camera, whatever the Capture Mode says. */
|
|
9968
10525
|
startCapture(source) {
|
|
9969
|
-
|
|
9970
|
-
|
|
10526
|
+
const camera = this.getCameraWidget(source);
|
|
10527
|
+
if (this.captureSettings().mode === CaptureMode.DIALOG && !camera?.isMobile()) {
|
|
10528
|
+
this.openCaptureDialog(source);
|
|
9971
10529
|
}
|
|
9972
|
-
else if (
|
|
9973
|
-
|
|
10530
|
+
else if (camera) {
|
|
10531
|
+
camera.openCapture();
|
|
9974
10532
|
}
|
|
9975
10533
|
else if (source === FileUploaderSource.MICROPHONE) {
|
|
9976
10534
|
this.isRecorderOpen.update((open) => !open);
|
|
9977
10535
|
}
|
|
9978
10536
|
}
|
|
10537
|
+
getCameraWidget(source) {
|
|
10538
|
+
if (source === FileUploaderSource.MICROPHONE) {
|
|
10539
|
+
return undefined;
|
|
10540
|
+
}
|
|
10541
|
+
return source === FileUploaderSource.CAMERA_PHOTO ? this.photoCapture : this.videoCapture;
|
|
10542
|
+
}
|
|
10543
|
+
openCaptureDialog(source) {
|
|
10544
|
+
this.captureDialogService
|
|
10545
|
+
.openCaptureDialog({
|
|
10546
|
+
size: {},
|
|
10547
|
+
content: { title: this.texts[CAPTURE_BUTTONS[source].dialogTitle] },
|
|
10548
|
+
source,
|
|
10549
|
+
settings: this.captureSettings(),
|
|
10550
|
+
texts: this.texts,
|
|
10551
|
+
maxFileSize: source === FileUploaderSource.CAMERA_PHOTO ? this.getMaxFileSize() : undefined,
|
|
10552
|
+
})
|
|
10553
|
+
.afterClosed()
|
|
10554
|
+
.subscribe((files) => {
|
|
10555
|
+
if (files) {
|
|
10556
|
+
this.uploadImage(files);
|
|
10557
|
+
}
|
|
10558
|
+
});
|
|
10559
|
+
}
|
|
10560
|
+
getMaxFileSize() {
|
|
10561
|
+
return this.maxSizeMb * 1024 * 1024;
|
|
10562
|
+
}
|
|
9979
10563
|
getCaptureButtonClass(source) {
|
|
9980
10564
|
return CAPTURE_BUTTONS[source].cssClass;
|
|
9981
10565
|
}
|
|
@@ -10019,7 +10603,7 @@ class UploadWidgetComponent {
|
|
|
10019
10603
|
};
|
|
10020
10604
|
}
|
|
10021
10605
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UploadWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10022
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: UploadWidgetComponent, isStandalone: true, selector: "smart-upload-widget", inputs: { uploadDescriptor: { classPropertyName: "uploadDescriptor", publicName: "uploadDescriptor", isSignal: true, isRequired: false, transformFunction: null }, isMultiple: { classPropertyName: "isMultiple", publicName: "isMultiple", isSignal: true, isRequired: true, transformFunction: null }, isDisabled: { classPropertyName: "isDisabled", publicName: "isDisabled", isSignal: true, isRequired: false, transformFunction: null }, sources: { classPropertyName: "sources", publicName: "sources", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { uploadFilesEvent: "uploadFilesEvent" }, viewQueries: [{ propertyName: "fileUploadMaterial", first: true, predicate: ["fileUploadMaterial"], descendants: true }, { propertyName: "photoCapture", first: true, predicate: ["photoCapture"], descendants: true }, { propertyName: "videoCapture", first: true, predicate: ["videoCapture"], descendants: true }], ngImport: i0, template: "<div class=\"sb4UploadWidget\">\n @if (hasSources()) {\n <!-- CAPTURE ROW OF THE SOURCES, hidden in view mode -->\n @if (getCaptureSources().length && !isDisabled()) {\n <div class=\"captureRow\" [class.captureOnly]=\"isCaptureOnly()\">\n <div class=\"captureButtons\">\n @for (source of getCaptureSources(); track source) {\n <ui-action-button\n class=\"{{ getCaptureButtonClass(source) }}\"\n [descriptor]=\"getCaptureButton(source)\"\n (actionClick)=\"startCapture(source)\"\n ></ui-action-button>\n }\n </div>\n @if (isCaptureOnly()) {\n <div class=\"captureHint\">{{ getCaptureOnlyHint() }}</div>\n }\n </div>\n }\n @if (isSourceNeeded(fileUploaderSource.CAMERA_PHOTO)) {\n <photo-capture-widget\n #photoCapture\n [mode]=\"'PHOTO'\"\n [externalTrigger]=\"true\"\n [texts]=\"texts\"\n [maxFileSize]=\"
|
|
10606
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: UploadWidgetComponent, isStandalone: true, selector: "smart-upload-widget", inputs: { uploadDescriptor: { classPropertyName: "uploadDescriptor", publicName: "uploadDescriptor", isSignal: true, isRequired: false, transformFunction: null }, isMultiple: { classPropertyName: "isMultiple", publicName: "isMultiple", isSignal: true, isRequired: true, transformFunction: null }, isDisabled: { classPropertyName: "isDisabled", publicName: "isDisabled", isSignal: true, isRequired: false, transformFunction: null }, sources: { classPropertyName: "sources", publicName: "sources", isSignal: true, isRequired: false, transformFunction: null }, capture: { classPropertyName: "capture", publicName: "capture", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { uploadFilesEvent: "uploadFilesEvent" }, viewQueries: [{ propertyName: "fileUploadMaterial", first: true, predicate: ["fileUploadMaterial"], descendants: true }, { propertyName: "photoCapture", first: true, predicate: ["photoCapture"], descendants: true }, { propertyName: "videoCapture", first: true, predicate: ["videoCapture"], descendants: true }], ngImport: i0, template: "<div class=\"sb4UploadWidget\">\n @if (hasSources()) {\n <!-- CAPTURE ROW OF THE SOURCES, hidden in view mode -->\n @if (getCaptureSources().length && !isDisabled()) {\n <div class=\"captureRow\" [class.captureOnly]=\"isCaptureOnly()\">\n <div class=\"captureButtons\">\n @for (source of getCaptureSources(); track source) {\n <ui-action-button\n class=\"{{ getCaptureButtonClass(source) }}\"\n [descriptor]=\"getCaptureButton(source)\"\n (actionClick)=\"startCapture(source)\"\n ></ui-action-button>\n }\n </div>\n @if (isCaptureOnly()) {\n <div class=\"captureHint\">{{ getCaptureOnlyHint() }}</div>\n }\n </div>\n }\n @if (isSourceNeeded(fileUploaderSource.CAMERA_PHOTO)) {\n <photo-capture-widget\n #photoCapture\n [mode]=\"'PHOTO'\"\n [externalTrigger]=\"true\"\n [deviceStart]=\"captureSettings().deviceStart\"\n [handover]=\"captureSettings().handover\"\n [texts]=\"texts\"\n [maxFileSize]=\"getMaxFileSize()\"\n (photoCaptured)=\"uploadImage($event)\"\n ></photo-capture-widget>\n }\n @if (isSourceNeeded(fileUploaderSource.CAMERA_VIDEO)) {\n <photo-capture-widget\n #videoCapture\n [mode]=\"'VIDEO'\"\n [externalTrigger]=\"true\"\n [deviceStart]=\"captureSettings().deviceStart\"\n [handover]=\"captureSettings().handover\"\n [texts]=\"texts\"\n (photoCaptured)=\"uploadImage($event)\"\n ></photo-capture-widget>\n }\n @if (isRecorderOpen()) {\n <voice-record-widget\n [openDirection]=\"captureSettings().openDirection\"\n [deviceStart]=\"captureSettings().deviceStart\"\n [handover]=\"captureSettings().handover\"\n [showSaveTitle]=\"true\"\n [texts]=\"texts\"\n [showMicrophoneError]=\"true\"\n (recordingSaved)=\"uploadRecording($event)\"\n ></voice-record-widget>\n }\n } @else {\n <!-- IMAGE TYPE WIDGET -->\n @if (widgetNeeded(uploadWidgetType.IMAGE) || widgetNeeded(uploadWidgetType.ALL)) {\n <photo-capture-widget\n [maxFileSize]=\"getMaxFileSize()\"\n [texts]=\"texts\"\n (photoCaptured)=\"uploadImage($event)\"\n >\n </photo-capture-widget>\n }\n\n <!-- SOUND TYPE WIDGET -->\n @if (widgetNeeded(uploadWidgetType.SOUND) || widgetNeeded(uploadWidgetType.ALL)) {\n <voice-record-widget\n [texts]=\"texts\"\n [showMicrophoneError]=\"true\"\n (recordingSaved)=\"uploadRecording($event)\"\n ></voice-record-widget>\n }\n }\n <smartfileuploader\n #fileUploadMaterial\n [i18n]=\"i18n\"\n [fileFormats]=\"fileFormats\"\n [maxSizeMb]=\"maxSizeMb\"\n [uploadCallback]=\"upload.bind(this)\"\n [isMultiple]=\"isMultiple()\"\n [autoUpload]=\"isUploadImmediate()\"\n [isDisabled]=\"isDisabled()\"\n [pickerShown]=\"isPickerShown()\"\n ></smartfileuploader>\n</div>\n", styles: ["photo-capture-widget{width:100%;max-width:100%;overflow:hidden;display:flex;flex-direction:column;align-items:center}voice-record-widget{box-sizing:border-box;padding:.5rem;border-bottom:1px solid #e5e7eb}voice-record-widget ::ng-deep .sb4-red,voice-record-widget ::ng-deep .sb4-primary,voice-record-widget ::ng-deep ui-action-button{border:unset!important}@media(max-width:900px){:host{width:100%}}.captureRow{display:flex;justify-content:center;padding:.5rem;border-bottom:1px solid #e5e7eb}.captureRow.captureOnly{border-bottom:unset;flex-direction:column;align-items:center;padding:.875rem .5rem;gap:.375rem}.captureButtons{display:flex;gap:.5rem}.captureButtons ::ng-deep button{border:unset}.captureHint{font-size:12px;color:#6b7280}\n"], dependencies: [{ kind: "component", type: PhotoCaptureWidgetComponent, selector: "photo-capture-widget", inputs: ["maxFileSize", "mode", "externalTrigger", "texts", "deviceStart", "handover"], outputs: ["photoCaptured"] }, { kind: "component", type: VoiceRecordWidgetComponent, selector: "voice-record-widget", inputs: ["openDirection", "texts", "showMicrophoneError", "deviceStart", "handover", "showSaveTitle"], outputs: ["recordingSaved"] }, { kind: "component", type: SmartfileuploaderComponent, selector: "smartfileuploader", inputs: ["uploadCallback", "fileFormats", "maxSizeMb", "i18n", "useIconButton", "isMultiple", "autoUpload", "isDisabled", "pickerShown"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
|
|
10023
10607
|
}
|
|
10024
10608
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UploadWidgetComponent, decorators: [{
|
|
10025
10609
|
type: Component,
|
|
@@ -10028,7 +10612,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
10028
10612
|
VoiceRecordWidgetComponent,
|
|
10029
10613
|
SmartfileuploaderComponent,
|
|
10030
10614
|
UiActionButtonComponent,
|
|
10031
|
-
], template: "<div class=\"sb4UploadWidget\">\n @if (hasSources()) {\n <!-- CAPTURE ROW OF THE SOURCES, hidden in view mode -->\n @if (getCaptureSources().length && !isDisabled()) {\n <div class=\"captureRow\" [class.captureOnly]=\"isCaptureOnly()\">\n <div class=\"captureButtons\">\n @for (source of getCaptureSources(); track source) {\n <ui-action-button\n class=\"{{ getCaptureButtonClass(source) }}\"\n [descriptor]=\"getCaptureButton(source)\"\n (actionClick)=\"startCapture(source)\"\n ></ui-action-button>\n }\n </div>\n @if (isCaptureOnly()) {\n <div class=\"captureHint\">{{ getCaptureOnlyHint() }}</div>\n }\n </div>\n }\n @if (isSourceNeeded(fileUploaderSource.CAMERA_PHOTO)) {\n <photo-capture-widget\n #photoCapture\n [mode]=\"'PHOTO'\"\n [externalTrigger]=\"true\"\n [texts]=\"texts\"\n [maxFileSize]=\"
|
|
10615
|
+
], template: "<div class=\"sb4UploadWidget\">\n @if (hasSources()) {\n <!-- CAPTURE ROW OF THE SOURCES, hidden in view mode -->\n @if (getCaptureSources().length && !isDisabled()) {\n <div class=\"captureRow\" [class.captureOnly]=\"isCaptureOnly()\">\n <div class=\"captureButtons\">\n @for (source of getCaptureSources(); track source) {\n <ui-action-button\n class=\"{{ getCaptureButtonClass(source) }}\"\n [descriptor]=\"getCaptureButton(source)\"\n (actionClick)=\"startCapture(source)\"\n ></ui-action-button>\n }\n </div>\n @if (isCaptureOnly()) {\n <div class=\"captureHint\">{{ getCaptureOnlyHint() }}</div>\n }\n </div>\n }\n @if (isSourceNeeded(fileUploaderSource.CAMERA_PHOTO)) {\n <photo-capture-widget\n #photoCapture\n [mode]=\"'PHOTO'\"\n [externalTrigger]=\"true\"\n [deviceStart]=\"captureSettings().deviceStart\"\n [handover]=\"captureSettings().handover\"\n [texts]=\"texts\"\n [maxFileSize]=\"getMaxFileSize()\"\n (photoCaptured)=\"uploadImage($event)\"\n ></photo-capture-widget>\n }\n @if (isSourceNeeded(fileUploaderSource.CAMERA_VIDEO)) {\n <photo-capture-widget\n #videoCapture\n [mode]=\"'VIDEO'\"\n [externalTrigger]=\"true\"\n [deviceStart]=\"captureSettings().deviceStart\"\n [handover]=\"captureSettings().handover\"\n [texts]=\"texts\"\n (photoCaptured)=\"uploadImage($event)\"\n ></photo-capture-widget>\n }\n @if (isRecorderOpen()) {\n <voice-record-widget\n [openDirection]=\"captureSettings().openDirection\"\n [deviceStart]=\"captureSettings().deviceStart\"\n [handover]=\"captureSettings().handover\"\n [showSaveTitle]=\"true\"\n [texts]=\"texts\"\n [showMicrophoneError]=\"true\"\n (recordingSaved)=\"uploadRecording($event)\"\n ></voice-record-widget>\n }\n } @else {\n <!-- IMAGE TYPE WIDGET -->\n @if (widgetNeeded(uploadWidgetType.IMAGE) || widgetNeeded(uploadWidgetType.ALL)) {\n <photo-capture-widget\n [maxFileSize]=\"getMaxFileSize()\"\n [texts]=\"texts\"\n (photoCaptured)=\"uploadImage($event)\"\n >\n </photo-capture-widget>\n }\n\n <!-- SOUND TYPE WIDGET -->\n @if (widgetNeeded(uploadWidgetType.SOUND) || widgetNeeded(uploadWidgetType.ALL)) {\n <voice-record-widget\n [texts]=\"texts\"\n [showMicrophoneError]=\"true\"\n (recordingSaved)=\"uploadRecording($event)\"\n ></voice-record-widget>\n }\n }\n <smartfileuploader\n #fileUploadMaterial\n [i18n]=\"i18n\"\n [fileFormats]=\"fileFormats\"\n [maxSizeMb]=\"maxSizeMb\"\n [uploadCallback]=\"upload.bind(this)\"\n [isMultiple]=\"isMultiple()\"\n [autoUpload]=\"isUploadImmediate()\"\n [isDisabled]=\"isDisabled()\"\n [pickerShown]=\"isPickerShown()\"\n ></smartfileuploader>\n</div>\n", styles: ["photo-capture-widget{width:100%;max-width:100%;overflow:hidden;display:flex;flex-direction:column;align-items:center}voice-record-widget{box-sizing:border-box;padding:.5rem;border-bottom:1px solid #e5e7eb}voice-record-widget ::ng-deep .sb4-red,voice-record-widget ::ng-deep .sb4-primary,voice-record-widget ::ng-deep ui-action-button{border:unset!important}@media(max-width:900px){:host{width:100%}}.captureRow{display:flex;justify-content:center;padding:.5rem;border-bottom:1px solid #e5e7eb}.captureRow.captureOnly{border-bottom:unset;flex-direction:column;align-items:center;padding:.875rem .5rem;gap:.375rem}.captureButtons{display:flex;gap:.5rem}.captureButtons ::ng-deep button{border:unset}.captureHint{font-size:12px;color:#6b7280}\n"] }]
|
|
10032
10616
|
}], propDecorators: { fileUploadMaterial: [{
|
|
10033
10617
|
type: ViewChild,
|
|
10034
10618
|
args: ['fileUploadMaterial']
|
|
@@ -10038,25 +10622,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
10038
10622
|
}], videoCapture: [{
|
|
10039
10623
|
type: ViewChild,
|
|
10040
10624
|
args: ['videoCapture']
|
|
10041
|
-
}], uploadDescriptor: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadDescriptor", required: false }] }], isMultiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMultiple", required: true }] }], isDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDisabled", required: false }] }], sources: [{ type: i0.Input, args: [{ isSignal: true, alias: "sources", required: false }] }], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }] } });
|
|
10625
|
+
}], uploadDescriptor: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadDescriptor", required: false }] }], isMultiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMultiple", required: true }] }], isDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDisabled", required: false }] }], sources: [{ type: i0.Input, args: [{ isSignal: true, alias: "sources", required: false }] }], capture: [{ type: i0.Input, args: [{ isSignal: true, alias: "capture", required: false }] }], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }] } });
|
|
10042
10626
|
const CAPTURE_BUTTONS = {
|
|
10043
10627
|
[FileUploaderSource.CAMERA_PHOTO]: {
|
|
10044
10628
|
icon: 'photo_camera',
|
|
10045
10629
|
cssClass: 'capturePhotoButton',
|
|
10046
10630
|
tooltip: 'capturePhoto',
|
|
10047
10631
|
noun: 'photo',
|
|
10632
|
+
dialogTitle: 'photoDialogTitle',
|
|
10048
10633
|
},
|
|
10049
10634
|
[FileUploaderSource.CAMERA_VIDEO]: {
|
|
10050
10635
|
icon: 'videocam',
|
|
10051
10636
|
cssClass: 'captureVideoButton',
|
|
10052
10637
|
tooltip: 'captureVideo',
|
|
10053
10638
|
noun: 'video',
|
|
10639
|
+
dialogTitle: 'videoDialogTitle',
|
|
10054
10640
|
},
|
|
10055
10641
|
[FileUploaderSource.MICROPHONE]: {
|
|
10056
10642
|
icon: 'mic',
|
|
10057
10643
|
cssClass: 'recordAudioButton',
|
|
10058
10644
|
tooltip: 'recordAudio',
|
|
10059
10645
|
noun: 'audio',
|
|
10646
|
+
dialogTitle: 'audioDialogTitle',
|
|
10060
10647
|
},
|
|
10061
10648
|
};
|
|
10062
10649
|
|
|
@@ -10376,7 +10963,7 @@ class SmartFileEditorComponent {
|
|
|
10376
10963
|
useExisting: forwardRef(() => SmartFileEditorComponent),
|
|
10377
10964
|
multi: true,
|
|
10378
10965
|
},
|
|
10379
|
-
], ngImport: i0, template: "@if (widgetInstance && widgetInstance.fileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else if (!widgetInstance.value || fileToChange) {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.fileUploaderProperties.uploadDescriptor!\"\n [sources]=\"widgetInstance.fileUploaderProperties.sources\"\n [isMultiple]=\"false\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n @if (clearedFile && !isDisabled) {\n <div class=\"undoClearContainer\">\n <ui-action-button\n class=\"removeIcon undoClearButton\"\n (actionClick)=\"undoClear()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n </div>\n }\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n @if (preview; as filePreview) {\n <div class=\"filePreview\">\n @switch (filePreview.kind) {\n @case ('image') {\n <img [src]=\"filePreview.url\" [alt]=\"widgetInstance.value?.fileName\" />\n }\n @case ('video') {\n <video\n controls\n [src]=\"filePreview.url\"\n (loadedmetadata)=\"onPreviewMetadata($event)\"\n ></video>\n }\n @case ('audio') {\n <audio controls [src]=\"filePreview.url\"></audio>\n }\n }\n </div>\n }\n <div class=\"uploadedFile\">\n @if (!preview) {\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n }\n\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ widgetInstance.value?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(widgetInstance.value?.size)) }}</span>\n </div>\n </div>\n\n <div class=\"fileActions\">\n <smart-ui-action-toolbar [id]=\"widgetInstance.toolbarId\"></smart-ui-action-toolbar>\n\n @if (!fileToChange) {\n <ui-action-button\n class=\"changeButton\"\n (actionClick)=\"change(widgetInstance.value)\"\n [disabled]=\"isDisabled\"\n [descriptor]=\"changeButton\"\n >\n </ui-action-button>\n @if (!isDisabled && isClearEnabled) {\n <ui-action-button\n class=\"deleteButton\"\n (actionClick)=\"clear()\"\n [descriptor]=\"deleteButton\"\n ></ui-action-button>\n }\n } @else {\n <ui-action-button\n class=\"removeIcon\"\n (actionClick)=\"cancelChange()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n }\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.fileEditorComponent:has(::ng-deep smartfileuploader) ::ng-deep .uploadedFile{border-top:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button,.undoClearContainer ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover,.undoClearContainer ::ng-deep button:hover{border:unset}.undoClearContainer{display:flex;justify-content:flex-end;padding:0 1rem .5rem}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.filePreview{padding:.75rem 1rem 0}.filePreview img,.filePreview video{display:block;width:100%;max-height:200px;object-fit:contain;background
|
|
10966
|
+
], ngImport: i0, template: "@if (widgetInstance && widgetInstance.fileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else if (!widgetInstance.value || fileToChange) {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.fileUploaderProperties.uploadDescriptor!\"\n [sources]=\"widgetInstance.fileUploaderProperties.sources\"\n [capture]=\"widgetInstance.fileUploaderProperties.capture\"\n [isMultiple]=\"false\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n @if (clearedFile && !isDisabled) {\n <div class=\"undoClearContainer\">\n <ui-action-button\n class=\"removeIcon undoClearButton\"\n (actionClick)=\"undoClear()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n </div>\n }\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n @if (preview; as filePreview) {\n <div class=\"filePreview\">\n @switch (filePreview.kind) {\n @case ('image') {\n <img [src]=\"filePreview.url\" [alt]=\"widgetInstance.value?.fileName\" />\n }\n @case ('video') {\n <video\n controls\n [src]=\"filePreview.url\"\n (loadedmetadata)=\"onPreviewMetadata($event)\"\n ></video>\n }\n @case ('audio') {\n <audio controls [src]=\"filePreview.url\"></audio>\n }\n }\n </div>\n }\n <div class=\"uploadedFile\">\n @if (!preview) {\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n }\n\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ widgetInstance.value?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(widgetInstance.value?.size)) }}</span>\n </div>\n </div>\n\n <div class=\"fileActions\">\n <smart-ui-action-toolbar [id]=\"widgetInstance.toolbarId\"></smart-ui-action-toolbar>\n\n @if (!fileToChange) {\n <ui-action-button\n class=\"changeButton\"\n (actionClick)=\"change(widgetInstance.value)\"\n [disabled]=\"isDisabled\"\n [descriptor]=\"changeButton\"\n >\n </ui-action-button>\n @if (!isDisabled && isClearEnabled) {\n <ui-action-button\n class=\"deleteButton\"\n (actionClick)=\"clear()\"\n [descriptor]=\"deleteButton\"\n ></ui-action-button>\n }\n } @else {\n <ui-action-button\n class=\"removeIcon\"\n (actionClick)=\"cancelChange()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n }\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.fileEditorComponent:has(::ng-deep smartfileuploader) ::ng-deep .uploadedFile{border-top:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button,.undoClearContainer ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover,.undoClearContainer ::ng-deep button:hover{border:unset}.undoClearContainer{display:flex;justify-content:flex-end;padding:0 1rem .5rem}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.filePreview{padding:.75rem 1rem 0}.filePreview img,.filePreview video{display:block;width:100%;max-height:200px;object-fit:contain;background:var(--color-surface-alt);border-radius:4px}.filePreview audio{display:block;width:100%}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .changeButton *,:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 12px,#f1f1f1 12px 24px)}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }, { kind: "component", type: UploadWidgetComponent, selector: "smart-upload-widget", inputs: ["uploadDescriptor", "isMultiple", "isDisabled", "sources", "capture"], outputs: ["uploadFilesEvent"] }, { kind: "component", type: UiActionToolbarComponent, selector: "smart-ui-action-toolbar", inputs: ["uiActionModels", "uiActionDescriptorService", "id", "executor", "widgetId", "nodeId", "actionParams", "scrollOnWrap", "toolbarPropertes"], outputs: ["actionFired"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
|
|
10380
10967
|
}
|
|
10381
10968
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartFileEditorComponent, decorators: [{
|
|
10382
10969
|
type: Component,
|
|
@@ -10386,7 +10973,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
10386
10973
|
useExisting: forwardRef(() => SmartFileEditorComponent),
|
|
10387
10974
|
multi: true,
|
|
10388
10975
|
},
|
|
10389
|
-
], imports: [NgClass, NgStyle, SmartIconComponent, UploadWidgetComponent, UiActionToolbarComponent, UiActionButtonComponent], template: "@if (widgetInstance && widgetInstance.fileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else if (!widgetInstance.value || fileToChange) {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.fileUploaderProperties.uploadDescriptor!\"\n [sources]=\"widgetInstance.fileUploaderProperties.sources\"\n [isMultiple]=\"false\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n @if (clearedFile && !isDisabled) {\n <div class=\"undoClearContainer\">\n <ui-action-button\n class=\"removeIcon undoClearButton\"\n (actionClick)=\"undoClear()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n </div>\n }\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n @if (preview; as filePreview) {\n <div class=\"filePreview\">\n @switch (filePreview.kind) {\n @case ('image') {\n <img [src]=\"filePreview.url\" [alt]=\"widgetInstance.value?.fileName\" />\n }\n @case ('video') {\n <video\n controls\n [src]=\"filePreview.url\"\n (loadedmetadata)=\"onPreviewMetadata($event)\"\n ></video>\n }\n @case ('audio') {\n <audio controls [src]=\"filePreview.url\"></audio>\n }\n }\n </div>\n }\n <div class=\"uploadedFile\">\n @if (!preview) {\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n }\n\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ widgetInstance.value?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(widgetInstance.value?.size)) }}</span>\n </div>\n </div>\n\n <div class=\"fileActions\">\n <smart-ui-action-toolbar [id]=\"widgetInstance.toolbarId\"></smart-ui-action-toolbar>\n\n @if (!fileToChange) {\n <ui-action-button\n class=\"changeButton\"\n (actionClick)=\"change(widgetInstance.value)\"\n [disabled]=\"isDisabled\"\n [descriptor]=\"changeButton\"\n >\n </ui-action-button>\n @if (!isDisabled && isClearEnabled) {\n <ui-action-button\n class=\"deleteButton\"\n (actionClick)=\"clear()\"\n [descriptor]=\"deleteButton\"\n ></ui-action-button>\n }\n } @else {\n <ui-action-button\n class=\"removeIcon\"\n (actionClick)=\"cancelChange()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n }\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.fileEditorComponent:has(::ng-deep smartfileuploader) ::ng-deep .uploadedFile{border-top:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button,.undoClearContainer ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover,.undoClearContainer ::ng-deep button:hover{border:unset}.undoClearContainer{display:flex;justify-content:flex-end;padding:0 1rem .5rem}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.filePreview{padding:.75rem 1rem 0}.filePreview img,.filePreview video{display:block;width:100%;max-height:200px;object-fit:contain;background
|
|
10976
|
+
], imports: [NgClass, NgStyle, SmartIconComponent, UploadWidgetComponent, UiActionToolbarComponent, UiActionButtonComponent], template: "@if (widgetInstance && widgetInstance.fileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else if (!widgetInstance.value || fileToChange) {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.fileUploaderProperties.uploadDescriptor!\"\n [sources]=\"widgetInstance.fileUploaderProperties.sources\"\n [capture]=\"widgetInstance.fileUploaderProperties.capture\"\n [isMultiple]=\"false\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n @if (clearedFile && !isDisabled) {\n <div class=\"undoClearContainer\">\n <ui-action-button\n class=\"removeIcon undoClearButton\"\n (actionClick)=\"undoClear()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n </div>\n }\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n @if (preview; as filePreview) {\n <div class=\"filePreview\">\n @switch (filePreview.kind) {\n @case ('image') {\n <img [src]=\"filePreview.url\" [alt]=\"widgetInstance.value?.fileName\" />\n }\n @case ('video') {\n <video\n controls\n [src]=\"filePreview.url\"\n (loadedmetadata)=\"onPreviewMetadata($event)\"\n ></video>\n }\n @case ('audio') {\n <audio controls [src]=\"filePreview.url\"></audio>\n }\n }\n </div>\n }\n <div class=\"uploadedFile\">\n @if (!preview) {\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n }\n\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ widgetInstance.value?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(widgetInstance.value?.size)) }}</span>\n </div>\n </div>\n\n <div class=\"fileActions\">\n <smart-ui-action-toolbar [id]=\"widgetInstance.toolbarId\"></smart-ui-action-toolbar>\n\n @if (!fileToChange) {\n <ui-action-button\n class=\"changeButton\"\n (actionClick)=\"change(widgetInstance.value)\"\n [disabled]=\"isDisabled\"\n [descriptor]=\"changeButton\"\n >\n </ui-action-button>\n @if (!isDisabled && isClearEnabled) {\n <ui-action-button\n class=\"deleteButton\"\n (actionClick)=\"clear()\"\n [descriptor]=\"deleteButton\"\n ></ui-action-button>\n }\n } @else {\n <ui-action-button\n class=\"removeIcon\"\n (actionClick)=\"cancelChange()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n }\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.fileEditorComponent:has(::ng-deep smartfileuploader) ::ng-deep .uploadedFile{border-top:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button,.undoClearContainer ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover,.undoClearContainer ::ng-deep button:hover{border:unset}.undoClearContainer{display:flex;justify-content:flex-end;padding:0 1rem .5rem}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.filePreview{padding:.75rem 1rem 0}.filePreview img,.filePreview video{display:block;width:100%;max-height:200px;object-fit:contain;background:var(--color-surface-alt);border-radius:4px}.filePreview audio{display:block;width:100%}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .changeButton *,:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 12px,#f1f1f1 12px 24px)}\n"] }]
|
|
10390
10977
|
}], propDecorators: { widgetInstance: [{
|
|
10391
10978
|
type: Input
|
|
10392
10979
|
}], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }] } });
|
|
@@ -11457,7 +12044,7 @@ class SmartMultiFileEditorComponent {
|
|
|
11457
12044
|
useExisting: forwardRef(() => SmartMultiFileEditorComponent),
|
|
11458
12045
|
multi: true,
|
|
11459
12046
|
},
|
|
11460
|
-
], ngImport: i0, template: "@if (widgetInstance && widgetInstance.multiFileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.multiFileUploaderProperties.uploadDescriptor!\"\n [sources]=\"widgetInstance.multiFileUploaderProperties.sources\"\n [isMultiple]=\"true\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"scrollable\">\n @for (file of widgetInstance.value; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @let previewKind = getPreviewKind(file);\n @let thumbnail = previewKind === 'image' ? getPreview(file.dataUri) : undefined;\n @if (thumbnail) {\n <img class=\"fileThumbnail\" [src]=\"thumbnail.url\" [alt]=\"file.fileName\" />\n } @else if (previewKind === 'video' || previewKind === 'audio') {\n <button\n type=\"button\"\n class=\"playTile\"\n [class.audioTile]=\"previewKind === 'audio'\"\n [class.open]=\"expandedDataUri === file.dataUri\"\n [smartTooltip]=\"playTooltip\"\n (click)=\"togglePlayer(file.dataUri)\"\n >\n <smart-icon\n [icon]=\"previewKind === 'audio' ? 'graphic_eq' : 'play_arrow'\"\n ></smart-icon>\n </button>\n } @else {\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(file?.size)) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <!--\n One toolbar per file, each resolving the widget's actions for itself and\n carrying its own file. The hidden sibling whose list these used to read is\n gone with the collection machinery (ADR-0006).\n -->\n <smart-ui-action-toolbar\n [id]=\"widgetInstance.toolbarId\"\n [actionParams]=\"{ _dataUri: file.dataUri }\"\n ></smart-ui-action-toolbar>\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file.dataUri)\"\n ></smart-icon>\n @if (!file.readOnly && !isDisabled) {\n <smart-icon\n class=\"removeIcon\"\n [icon]=\"removeIcon\"\n (click)=\"file.readOnly === false && deleteFile(file.dataUri)\"\n ></smart-icon>\n }\n </div>\n </div>\n @if (expandedDataUri === file.dataUri) {\n @if (getPreview(file.dataUri); as player) {\n <div class=\"filePlayer\">\n @if (player.kind === 'audio') {\n <audio controls [src]=\"player.url\"></audio>\n } @else {\n <video\n controls\n [src]=\"player.url\"\n (loadedmetadata)=\"onPreviewMetadata(file.dataUri, $event)\"\n ></video>\n }\n </div>\n }\n }\n }\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius);overflow:hidden}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column;border-top:1px solid #e0e0e0}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.scrollable{max-height:30vh;overflow-y:auto}.scrollable>div:not(:last-child){border-bottom:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover{border:unset}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileThumbnail,.playTile{width:3rem;height:3rem;flex:none;border-radius:4px}.fileThumbnail{object-fit:cover;background
|
|
12047
|
+
], ngImport: i0, template: "@if (widgetInstance && widgetInstance.multiFileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.multiFileUploaderProperties.uploadDescriptor!\"\n [sources]=\"widgetInstance.multiFileUploaderProperties.sources\"\n [capture]=\"widgetInstance.multiFileUploaderProperties.capture\"\n [isMultiple]=\"true\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"scrollable\">\n @for (file of widgetInstance.value; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @let previewKind = getPreviewKind(file);\n @let thumbnail = previewKind === 'image' ? getPreview(file.dataUri) : undefined;\n @if (thumbnail) {\n <img class=\"fileThumbnail\" [src]=\"thumbnail.url\" [alt]=\"file.fileName\" />\n } @else if (previewKind === 'video' || previewKind === 'audio') {\n <button\n type=\"button\"\n class=\"playTile\"\n [class.audioTile]=\"previewKind === 'audio'\"\n [class.open]=\"expandedDataUri === file.dataUri\"\n [smartTooltip]=\"playTooltip\"\n (click)=\"togglePlayer(file.dataUri)\"\n >\n <smart-icon\n [icon]=\"previewKind === 'audio' ? 'graphic_eq' : 'play_arrow'\"\n ></smart-icon>\n </button>\n } @else {\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(file?.size)) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <!--\n One toolbar per file, each resolving the widget's actions for itself and\n carrying its own file. The hidden sibling whose list these used to read is\n gone with the collection machinery (ADR-0006).\n -->\n <smart-ui-action-toolbar\n [id]=\"widgetInstance.toolbarId\"\n [actionParams]=\"{ _dataUri: file.dataUri }\"\n ></smart-ui-action-toolbar>\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file.dataUri)\"\n ></smart-icon>\n @if (!file.readOnly && !isDisabled) {\n <smart-icon\n class=\"removeIcon\"\n [icon]=\"removeIcon\"\n (click)=\"file.readOnly === false && deleteFile(file.dataUri)\"\n ></smart-icon>\n }\n </div>\n </div>\n @if (expandedDataUri === file.dataUri) {\n @if (getPreview(file.dataUri); as player) {\n <div class=\"filePlayer\">\n @if (player.kind === 'audio') {\n <audio controls [src]=\"player.url\"></audio>\n } @else {\n <video\n controls\n [src]=\"player.url\"\n (loadedmetadata)=\"onPreviewMetadata(file.dataUri, $event)\"\n ></video>\n }\n </div>\n }\n }\n }\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius);overflow:hidden}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column;border-top:1px solid #e0e0e0}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.scrollable{max-height:30vh;overflow-y:auto}.scrollable>div:not(:last-child){border-bottom:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover{border:unset}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileThumbnail,.playTile{width:3rem;height:3rem;flex:none;border-radius:4px}.fileThumbnail{object-fit:cover;background:var(--color-surface-alt)}.playTile{display:flex;align-items:center;justify-content:center;padding:0;border:none;background:#2f3550;color:#fff;cursor:pointer}.playTile.audioTile{background:var(--color-surface-alt);color:var(--primary-color)}.playTile.open{outline:2px solid var(--primary-color);outline-offset:1px}.playTile smart-icon,.playTile smart-icon ::ng-deep mat-icon{font-size:2rem;color:inherit}.scrollable>.uploadedFile:has(+.filePlayer){border-bottom:none}.filePlayer{padding:0 1rem .75rem 5rem}.filePlayer video{display:block;width:100%;max-height:160px;background:#000;border-radius:4px}.filePlayer audio{display:block;width:100%}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 6px,#f1f1f1 6px 12px)}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: SmartTooltipDirective, selector: "[smartTooltip]", inputs: ["smartTooltip", "smartTooltipClass"] }, { kind: "component", type: UiActionToolbarComponent, selector: "smart-ui-action-toolbar", inputs: ["uiActionModels", "uiActionDescriptorService", "id", "executor", "widgetId", "nodeId", "actionParams", "scrollOnWrap", "toolbarPropertes"], outputs: ["actionFired"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }, { kind: "component", type: UploadWidgetComponent, selector: "smart-upload-widget", inputs: ["uploadDescriptor", "isMultiple", "isDisabled", "sources", "capture"], outputs: ["uploadFilesEvent"] }] }); }
|
|
11461
12048
|
}
|
|
11462
12049
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartMultiFileEditorComponent, decorators: [{
|
|
11463
12050
|
type: Component,
|
|
@@ -11474,7 +12061,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
11474
12061
|
UiActionToolbarComponent,
|
|
11475
12062
|
SmartIconComponent,
|
|
11476
12063
|
UploadWidgetComponent,
|
|
11477
|
-
], template: "@if (widgetInstance && widgetInstance.multiFileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.multiFileUploaderProperties.uploadDescriptor!\"\n [sources]=\"widgetInstance.multiFileUploaderProperties.sources\"\n [isMultiple]=\"true\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"scrollable\">\n @for (file of widgetInstance.value; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @let previewKind = getPreviewKind(file);\n @let thumbnail = previewKind === 'image' ? getPreview(file.dataUri) : undefined;\n @if (thumbnail) {\n <img class=\"fileThumbnail\" [src]=\"thumbnail.url\" [alt]=\"file.fileName\" />\n } @else if (previewKind === 'video' || previewKind === 'audio') {\n <button\n type=\"button\"\n class=\"playTile\"\n [class.audioTile]=\"previewKind === 'audio'\"\n [class.open]=\"expandedDataUri === file.dataUri\"\n [smartTooltip]=\"playTooltip\"\n (click)=\"togglePlayer(file.dataUri)\"\n >\n <smart-icon\n [icon]=\"previewKind === 'audio' ? 'graphic_eq' : 'play_arrow'\"\n ></smart-icon>\n </button>\n } @else {\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(file?.size)) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <!--\n One toolbar per file, each resolving the widget's actions for itself and\n carrying its own file. The hidden sibling whose list these used to read is\n gone with the collection machinery (ADR-0006).\n -->\n <smart-ui-action-toolbar\n [id]=\"widgetInstance.toolbarId\"\n [actionParams]=\"{ _dataUri: file.dataUri }\"\n ></smart-ui-action-toolbar>\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file.dataUri)\"\n ></smart-icon>\n @if (!file.readOnly && !isDisabled) {\n <smart-icon\n class=\"removeIcon\"\n [icon]=\"removeIcon\"\n (click)=\"file.readOnly === false && deleteFile(file.dataUri)\"\n ></smart-icon>\n }\n </div>\n </div>\n @if (expandedDataUri === file.dataUri) {\n @if (getPreview(file.dataUri); as player) {\n <div class=\"filePlayer\">\n @if (player.kind === 'audio') {\n <audio controls [src]=\"player.url\"></audio>\n } @else {\n <video\n controls\n [src]=\"player.url\"\n (loadedmetadata)=\"onPreviewMetadata(file.dataUri, $event)\"\n ></video>\n }\n </div>\n }\n }\n }\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius);overflow:hidden}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column;border-top:1px solid #e0e0e0}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.scrollable{max-height:30vh;overflow-y:auto}.scrollable>div:not(:last-child){border-bottom:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover{border:unset}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileThumbnail,.playTile{width:3rem;height:3rem;flex:none;border-radius:4px}.fileThumbnail{object-fit:cover;background
|
|
12064
|
+
], template: "@if (widgetInstance && widgetInstance.multiFileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.multiFileUploaderProperties.uploadDescriptor!\"\n [sources]=\"widgetInstance.multiFileUploaderProperties.sources\"\n [capture]=\"widgetInstance.multiFileUploaderProperties.capture\"\n [isMultiple]=\"true\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"scrollable\">\n @for (file of widgetInstance.value; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @let previewKind = getPreviewKind(file);\n @let thumbnail = previewKind === 'image' ? getPreview(file.dataUri) : undefined;\n @if (thumbnail) {\n <img class=\"fileThumbnail\" [src]=\"thumbnail.url\" [alt]=\"file.fileName\" />\n } @else if (previewKind === 'video' || previewKind === 'audio') {\n <button\n type=\"button\"\n class=\"playTile\"\n [class.audioTile]=\"previewKind === 'audio'\"\n [class.open]=\"expandedDataUri === file.dataUri\"\n [smartTooltip]=\"playTooltip\"\n (click)=\"togglePlayer(file.dataUri)\"\n >\n <smart-icon\n [icon]=\"previewKind === 'audio' ? 'graphic_eq' : 'play_arrow'\"\n ></smart-icon>\n </button>\n } @else {\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(file?.size)) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <!--\n One toolbar per file, each resolving the widget's actions for itself and\n carrying its own file. The hidden sibling whose list these used to read is\n gone with the collection machinery (ADR-0006).\n -->\n <smart-ui-action-toolbar\n [id]=\"widgetInstance.toolbarId\"\n [actionParams]=\"{ _dataUri: file.dataUri }\"\n ></smart-ui-action-toolbar>\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file.dataUri)\"\n ></smart-icon>\n @if (!file.readOnly && !isDisabled) {\n <smart-icon\n class=\"removeIcon\"\n [icon]=\"removeIcon\"\n (click)=\"file.readOnly === false && deleteFile(file.dataUri)\"\n ></smart-icon>\n }\n </div>\n </div>\n @if (expandedDataUri === file.dataUri) {\n @if (getPreview(file.dataUri); as player) {\n <div class=\"filePlayer\">\n @if (player.kind === 'audio') {\n <audio controls [src]=\"player.url\"></audio>\n } @else {\n <video\n controls\n [src]=\"player.url\"\n (loadedmetadata)=\"onPreviewMetadata(file.dataUri, $event)\"\n ></video>\n }\n </div>\n }\n }\n }\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius);overflow:hidden}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column;border-top:1px solid #e0e0e0}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.scrollable{max-height:30vh;overflow-y:auto}.scrollable>div:not(:last-child){border-bottom:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover{border:unset}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileThumbnail,.playTile{width:3rem;height:3rem;flex:none;border-radius:4px}.fileThumbnail{object-fit:cover;background:var(--color-surface-alt)}.playTile{display:flex;align-items:center;justify-content:center;padding:0;border:none;background:#2f3550;color:#fff;cursor:pointer}.playTile.audioTile{background:var(--color-surface-alt);color:var(--primary-color)}.playTile.open{outline:2px solid var(--primary-color);outline-offset:1px}.playTile smart-icon,.playTile smart-icon ::ng-deep mat-icon{font-size:2rem;color:inherit}.scrollable>.uploadedFile:has(+.filePlayer){border-bottom:none}.filePlayer{padding:0 1rem .75rem 5rem}.filePlayer video{display:block;width:100%;max-height:160px;background:#000;border-radius:4px}.filePlayer audio{display:block;width:100%}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 6px,#f1f1f1 6px 12px)}\n"] }]
|
|
11478
12065
|
}], propDecorators: { widgetInstance: [{
|
|
11479
12066
|
type: Input
|
|
11480
12067
|
}], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }], fileEvent: [{ type: i0.Output, args: ["fileEvent"] }] } });
|
|
@@ -11704,7 +12291,7 @@ class SmartVoiceRecorderComponent {
|
|
|
11704
12291
|
useExisting: forwardRef(() => SmartVoiceRecorderComponent),
|
|
11705
12292
|
multi: true,
|
|
11706
12293
|
},
|
|
11707
|
-
], ngImport: i0, template: "<voice-record-widget [openDirection]=\"openDirection\" (recordingSaved)=\"uploadRecording($event)\">\n</voice-record-widget>\n", styles: [":host{display:block;width:100%;min-width:0}\n"], dependencies: [{ kind: "component", type: VoiceRecordWidgetComponent, selector: "voice-record-widget", inputs: ["openDirection", "texts", "showMicrophoneError"], outputs: ["recordingSaved"] }] }); }
|
|
12294
|
+
], ngImport: i0, template: "<voice-record-widget [openDirection]=\"openDirection\" (recordingSaved)=\"uploadRecording($event)\">\n</voice-record-widget>\n", styles: [":host{display:block;width:100%;min-width:0}\n"], dependencies: [{ kind: "component", type: VoiceRecordWidgetComponent, selector: "voice-record-widget", inputs: ["openDirection", "texts", "showMicrophoneError", "deviceStart", "handover", "showSaveTitle"], outputs: ["recordingSaved"] }] }); }
|
|
11708
12295
|
}
|
|
11709
12296
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartVoiceRecorderComponent, decorators: [{
|
|
11710
12297
|
type: Component,
|
|
@@ -16425,7 +17012,8 @@ class Table {
|
|
|
16425
17012
|
renderRows() {
|
|
16426
17013
|
this.createCellToActionMap();
|
|
16427
17014
|
this.rowMenuActionModelArrayCache.clear();
|
|
16428
|
-
|
|
17015
|
+
// The mobile template renders from its own loop and has no material table.
|
|
17016
|
+
this.myTableChild?.renderRows();
|
|
16429
17017
|
}
|
|
16430
17018
|
createCellToActionMap() {
|
|
16431
17019
|
this.cellToActionMap = SmartGridToolbarActionsUtil.calculateCellToActionMap(this.smartTable.tableRows);
|
|
@@ -16782,6 +17370,10 @@ class Table {
|
|
|
16782
17370
|
}
|
|
16783
17371
|
}
|
|
16784
17372
|
async onToggle(element, event) {
|
|
17373
|
+
// The mobile table has no material table, so no detail row to open.
|
|
17374
|
+
if (!this.myTableChild) {
|
|
17375
|
+
return;
|
|
17376
|
+
}
|
|
16785
17377
|
if (!this.smartTable.rowExpander) {
|
|
16786
17378
|
throw new Error('SmartTableRowExpander not found!. Please provide a SmartTableRowExpander implementation.');
|
|
16787
17379
|
}
|
|
@@ -17259,11 +17851,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
17259
17851
|
|
|
17260
17852
|
class MobileTableComponent extends Table {
|
|
17261
17853
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MobileTableComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
17262
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: MobileTableComponent, isStandalone: true, selector: "lib-mobile-table", usesInheritance: true, ngImport: i0, template: "<div class=\"mobileTable\">\n @for (row of smartTable.tableRows; track trackRow($index, row)) {\n <div\n class=\"mobileTableRow\"\n [ngClass]=\"getRowClasses(row)\"\n [ngStyle]=\"getRowStyles(row)\"\n [attr.data-testid]=\"row?.id ?? null\"\n >\n @if (showRowStateColumn) {\n <div class=\"mobileTableCell sm-grid-row-state-cell\" [attr.data-testid]=\"rowStateColumn\">\n @if (rowStateIcon(row); as icon) {\n <smart-icon\n class=\"sm-grid-row-state-icon\"\n [ngClass]=\"rowStateIconClass(row) ?? ''\"\n [icon]=\"icon\"\n color=\"\"\n role=\"img\"\n [attr.aria-label]=\"rowStateLabel(row)\"\n [matTooltip]=\"rowStateLabel(row) ?? ''\"\n ></smart-icon>\n }\n </div>\n }\n @for (header of smartTable.customSmartTableHeaders; track header; let i = $index) {\n <div\n class=\"mobileTableCell\"\n (click)=\"handleOnRowClick($event, row)\"\n (dblclick)=\"handleOnRowDoubleClick($event, row)\"\n >\n <div class=\"mobileTableCellHeader\" [attr.data-testid]=\"header.propertyName\">\n {{ header.label }}\n </div>\n @if (\n smartTable.customSmartTableHeaders &&\n smartTable.customSmartTableHeaders[i].propertyName === 'select'\n ) {\n <mat-checkbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"\n $event\n ? setSelection(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n : null\n \"\n [disabled]=\"isDisabled(row)\"\n [checked]=\"\n smartTable.selection!.isSelected(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n \"\n [aria-label]=\"\n checkboxLabel(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n \"\n >\n </mat-checkbox>\n }\n @if (\n smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].properties &&\n !columnTemplate(i)\n ) {\n <div\n >\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().DATETIME) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartDateTime: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().DATE) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartDate: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().TIME) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartTime: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().CHECKBOX) {\n <div>\n <mat-checkbox\n [disabled]=\"true\"\n [checked]=\"getValue(row, header.propertyName)\"\n ></mat-checkbox>\n </div>\n }\n @if (\n smartTable.customSmartTableHeaders[i].properties?.type === type().ICON &&\n smartTable.customSmartTableHeaders[i].properties?.icons?.length\n ) {\n <div\n >\n <smart-icon\n [smartTooltip]=\"getToolTip(row, i)\"\n [icon]=\"getIcon(getValue(row, header.propertyName), i)!\"\n [color]=\"getColor(getValue(row, header.propertyName), i)\"\n >\n </smart-icon>\n </div>\n }\n </div>\n }\n @if (isImageResource(row, header.propertyName)) {\n <div>\n <smart-icon\n [icon]=\"getImageResourceIcon(row, header.propertyName)\"\n [color]=\"getImageResourceColor(row, header.propertyName)\"\n >\n </smart-icon>\n </div>\n }\n @if (smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].buttons) {\n <div\n class=\"smart-table-buttons-col\"\n >\n @for (button of smartTable.customSmartTableHeaders[i].buttons; track button) {\n <div>\n @if (showButton(button, row)) {\n <div>\n @switch (button.type) {\n @case (smartTableButtonType.ICON) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-icon-button\n color=\"{{ button.color }}\"\n >\n <smart-icon title=\"{{ button.label }}\" [icon]=\"button.icon!\"></smart-icon>\n </button>\n }\n @case (smartTableButtonType.NORMAL) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-button\n color=\"{{ button.color }}\"\n >\n @if (button.icon) {\n <smart-icon [icon]=\"button.icon\"></smart-icon>\n }\n {{ button.label }}\n </button>\n }\n @case (smartTableButtonType.RAISED) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-raised-button\n color=\"{{ button.color }}\"\n >\n @if (button.icon) {\n <smart-icon [icon]=\"button.icon\"></smart-icon>\n }\n {{ button.label }}\n </button>\n }\n <!------ MENU ------>\n @case (smartTableButtonType.MENU) {\n <div class=\"menu-button\">\n <!------ DEFAULT_ACTION_COLUMN ID TOOLBAR ------>\n <smart-ui-action-toolbar\n [uiActionModels]=\"getRowColumnAction(element, defaultActionToolbarId)\"\n [widgetId]=\"smartTable.getGridId()\"\n [nodeId]=\"element.id\"\n [actionParams]=\"{ model: element }\"\n (actionFired)=\"onRowActionFired(element, $event)\"\n ></smart-ui-action-toolbar>\n <!------ GENERIC HAMBURGER ------>\n @if (shouldShowMenuButton(element)) {\n <smart-ui-action-toolbar\n [uiActionModels]=\"getMenuActions(element)\"\n [widgetId]=\"smartTable.getGridId()\"\n [nodeId]=\"element.id\"\n [actionParams]=\"{ model: element }\"\n (actionFired)=\"onRowActionFired(element, $event)\"\n ></smart-ui-action-toolbar>\n }\n <!------ MENU ------>\n </div>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n @if (smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].icon) {\n <div>\n @if (smartTable.customSmartTableHeaders[i].icon?.icon) {\n <smart-icon\n [ngClass]=\"smartTable.customSmartTableHeaders[i].icon?.cssClass ?? ''\"\n [color]=\"$safeNavigationMigration(smartTable.customSmartTableHeaders[i].icon?.color)\"\n [icon]=\"smartTable.customSmartTableHeaders[i].icon!.icon\"\n >\n </smart-icon>\n }\n </div>\n }\n @if (\n smartTable.customSmartTableHeaders &&\n smartTable.customSmartTableHeaders[i].translator !== undefined\n ) {\n <div\n class=\"smart-table-cell-text\"\n >\n {{ smartTable.customSmartTableHeaders[i].translator!(getValue(row, header.propertyName)) }}\n </div>\n }\n @if (\n !smartTable.customSmartTableHeaders ||\n (smartTable.customSmartTableHeaders &&\n (!smartTable.customSmartTableHeaders[i].properties || columnTemplate(i)) &&\n !smartTable.customSmartTableHeaders[i].icon &&\n !smartTable.customSmartTableHeaders[i].buttons &&\n !smartTable.customSmartTableHeaders[i].translator &&\n !(smartTable.customSmartTableHeaders[i].propertyName === 'select') &&\n !(smartTable.customSmartTableHeaders[i].propertyName === 'expand'))\n ) {\n <div\n >\n @if (header.propertyName === 'icon') {\n <smart-icon\n [icon]=\"getValue(row, header.propertyName)!\"\n >\n </smart-icon>\n }\n @if (header.propertyName === 'img') {\n <img\n [src]=\"getValue(row, header.propertyName)\"\n alt=\"\"\n class=\"smarttableImg\"\n />\n }\n @if (\n header.propertyName !== 'icon' &&\n header.propertyName !== 'img' &&\n header.propertyName !== 'option' &&\n header.propertyName !== 'button'\n ) {\n @if (columnTemplate(i); as template) {\n <smart-html-cell\n [row]=\"row\"\n [template]=\"template\"\n [gridIdentifier]=\"smartTable.getGridId()\"\n ></smart-html-cell>\n } @else if (isTextContent(i)) {\n <div class=\"smart-table-cell-text\">{{ getValue(row, header.propertyName) }}</div>\n } @else {\n <div\n class=\"smart-table-cell-text\"\n [innerHtml]=\"getValue(row, header.propertyName)\"\n ></div>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [".mobileTable{display:flex;flex-direction:column}.mobileTableRow{display:flex;flex-direction:column;border-bottom:1px solid var(--color-border, #e4eaf2);padding:.5rem;gap:.5rem}.mobileTableRow.sm-grid-row-new{color:var(--success-color, #16a34a);font-style:italic}.mobileTableRow.sm-grid-row-modified{color:var(--accent-color, #20b6a8);font-weight:600}.mobileTableRow.sm-grid-row-deleted{color:var(--color-text-muted, #68778e)}.mobileTableRow.sm-grid-row-deleted .smart-table-cell-text{text-decoration:line-through}.mobileTableRow.sm-grid-row-moved{font-style:italic}.mobileTableRow.sm-grid-row-contains-change{color:var(--accent-color, #20b6a8)}.sm-grid-row-state-icon ::ng-deep .mat-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--accent-color, #20b6a8)}.sm-grid-row-state-icon-new ::ng-deep .mat-icon{color:var(--success-color, #16a34a)}.sm-grid-row-state-icon-deleted ::ng-deep .mat-icon{color:var(--color-text-muted, #68778e)}.sm-grid-row-state-icon-contains-change ::ng-deep .mat-icon{font-size:10px;width:18px;text-align:center;color:var(--color-text-muted, #68778e)}.mobileTableRow.selected{color:inherit}.mobileTableRow.sm-grid-row-open{--sm-grid-open-row-tint: var( --grid-open-row-background, color-mix(in srgb, var(--accent-color, #20b6a8) 10%, transparent) );background-image:linear-gradient(var(--sm-grid-open-row-tint),var(--sm-grid-open-row-tint));box-shadow:inset 3px 0 0 var(--grid-open-row-bar, var(--accent-color, #20b6a8))}.mobileTableCell{display:flex;flex-direction:column;gap:.25rem}.mobileTableCellHeader{font-weight:700;color:var(--primary-color, #384372)}.selected{background-color:var(--primary-light-color, var(--color-surface-alt, #f4f6fb))}.mat-mdc-menu-item[disabled]{cursor:default!important}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }, { kind: "directive", type: SmartTooltipDirective, selector: "[smartTooltip]", inputs: ["smartTooltip", "smartTooltipClass"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: UiActionToolbarComponent, selector: "smart-ui-action-toolbar", inputs: ["uiActionModels", "uiActionDescriptorService", "id", "executor", "widgetId", "nodeId", "actionParams", "scrollOnWrap", "toolbarPropertes"], outputs: ["actionFired"] }, { kind: "component", type: SmartHtmlCellComponent, selector: "smart-html-cell", inputs: ["row", "template", "gridIdentifier"] }, { kind: "pipe", type: SmartDateTimePipe, name: "smartDateTime" }, { kind: "pipe", type: SmartDatePipe, name: "smartDate" }, { kind: "pipe", type: SmartTimePipe, name: "smartTime" }] }); }
|
|
17854
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: MobileTableComponent, isStandalone: true, selector: "lib-mobile-table", usesInheritance: true, ngImport: i0, template: "<div class=\"mobileTable\">\n @for (row of smartTable.tableRows; track trackRow($index, row)) {\n <div\n class=\"mobileTableRow\"\n [ngClass]=\"getRowClasses(row)\"\n [ngStyle]=\"getRowStyles(row)\"\n [attr.data-testid]=\"row?.id ?? null\"\n >\n @if (showRowStateColumn) {\n <div class=\"mobileTableCell sm-grid-row-state-cell\" [attr.data-testid]=\"rowStateColumn\">\n @if (rowStateIcon(row); as icon) {\n <smart-icon\n class=\"sm-grid-row-state-icon\"\n [ngClass]=\"rowStateIconClass(row) ?? ''\"\n [icon]=\"icon\"\n color=\"\"\n role=\"img\"\n [attr.aria-label]=\"rowStateLabel(row)\"\n [matTooltip]=\"rowStateLabel(row) ?? ''\"\n ></smart-icon>\n }\n </div>\n }\n @for (header of smartTable.customSmartTableHeaders; track header; let i = $index) {\n <div\n class=\"mobileTableCell\"\n (click)=\"handleOnRowClick($event, row)\"\n (dblclick)=\"handleOnRowDoubleClick($event, row)\"\n >\n <div class=\"mobileTableCellHeader\" [attr.data-testid]=\"header.propertyName\">\n {{ header.label }}\n </div>\n @if (\n smartTable.customSmartTableHeaders &&\n smartTable.customSmartTableHeaders[i].propertyName === 'select'\n ) {\n <mat-checkbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"\n $event\n ? setSelection(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n : null\n \"\n [disabled]=\"isDisabled(row)\"\n [checked]=\"\n smartTable.selection!.isSelected(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n \"\n [aria-label]=\"\n checkboxLabel(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n \"\n >\n </mat-checkbox>\n }\n @if (\n smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].properties &&\n !columnTemplate(i)\n ) {\n <div\n >\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().DATETIME) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartDateTime: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().DATE) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartDate: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().TIME) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartTime: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().CHECKBOX) {\n <div>\n <mat-checkbox\n [disabled]=\"true\"\n [checked]=\"getValue(row, header.propertyName)\"\n ></mat-checkbox>\n </div>\n }\n @if (\n smartTable.customSmartTableHeaders[i].properties?.type === type().ICON &&\n smartTable.customSmartTableHeaders[i].properties?.icons?.length\n ) {\n <div\n >\n <smart-icon\n [smartTooltip]=\"getToolTip(row, i)\"\n [icon]=\"getIcon(getValue(row, header.propertyName), i)!\"\n [color]=\"getColor(getValue(row, header.propertyName), i)\"\n >\n </smart-icon>\n </div>\n }\n </div>\n }\n @if (isImageResource(row, header.propertyName)) {\n <div>\n <smart-icon\n [icon]=\"getImageResourceIcon(row, header.propertyName)\"\n [color]=\"getImageResourceColor(row, header.propertyName)\"\n >\n </smart-icon>\n </div>\n }\n @if (smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].buttons) {\n <div\n class=\"smart-table-buttons-col\"\n >\n @for (button of smartTable.customSmartTableHeaders[i].buttons; track button) {\n <div>\n @if (showButton(button, row)) {\n <div>\n @switch (button.type) {\n @case (smartTableButtonType.ICON) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-icon-button\n color=\"{{ button.color }}\"\n >\n <smart-icon title=\"{{ button.label }}\" [icon]=\"button.icon!\"></smart-icon>\n </button>\n }\n @case (smartTableButtonType.NORMAL) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-button\n color=\"{{ button.color }}\"\n >\n @if (button.icon) {\n <smart-icon [icon]=\"button.icon\"></smart-icon>\n }\n {{ button.label }}\n </button>\n }\n @case (smartTableButtonType.RAISED) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-raised-button\n color=\"{{ button.color }}\"\n >\n @if (button.icon) {\n <smart-icon [icon]=\"button.icon\"></smart-icon>\n }\n {{ button.label }}\n </button>\n }\n <!------ MENU ------>\n @case (smartTableButtonType.MENU) {\n <div class=\"menu-button\">\n <!------ DEFAULT_ACTION_COLUMN ID TOOLBAR ------>\n <smart-ui-action-toolbar\n [uiActionModels]=\"getRowColumnAction(row, defaultActionToolbarId)\"\n [widgetId]=\"smartTable.getGridId()\"\n [nodeId]=\"row.id\"\n [actionParams]=\"{ model: row }\"\n (actionFired)=\"onRowActionFired(row, $event)\"\n ></smart-ui-action-toolbar>\n <!------ GENERIC HAMBURGER ------>\n @if (shouldShowMenuButton(row)) {\n <smart-ui-action-toolbar\n [uiActionModels]=\"getMenuActions(row)\"\n [widgetId]=\"smartTable.getGridId()\"\n [nodeId]=\"row.id\"\n [actionParams]=\"{ model: row }\"\n (actionFired)=\"onRowActionFired(row, $event)\"\n ></smart-ui-action-toolbar>\n }\n <!------ MENU ------>\n </div>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n @if (smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].icon) {\n <div>\n @if (smartTable.customSmartTableHeaders[i].icon?.icon) {\n <smart-icon\n [ngClass]=\"smartTable.customSmartTableHeaders[i].icon?.cssClass ?? ''\"\n [color]=\"$safeNavigationMigration(smartTable.customSmartTableHeaders[i].icon?.color)\"\n [icon]=\"smartTable.customSmartTableHeaders[i].icon!.icon\"\n >\n </smart-icon>\n }\n </div>\n }\n @if (\n smartTable.customSmartTableHeaders &&\n smartTable.customSmartTableHeaders[i].translator !== undefined\n ) {\n <div\n class=\"smart-table-cell-text\"\n >\n {{ smartTable.customSmartTableHeaders[i].translator!(getValue(row, header.propertyName)) }}\n </div>\n }\n @if (\n !smartTable.customSmartTableHeaders ||\n (smartTable.customSmartTableHeaders &&\n (!smartTable.customSmartTableHeaders[i].properties || columnTemplate(i)) &&\n !smartTable.customSmartTableHeaders[i].icon &&\n !smartTable.customSmartTableHeaders[i].buttons &&\n !smartTable.customSmartTableHeaders[i].translator &&\n !(smartTable.customSmartTableHeaders[i].propertyName === 'select') &&\n !(smartTable.customSmartTableHeaders[i].propertyName === 'expand'))\n ) {\n <div\n >\n @if (header.propertyName === 'icon') {\n <smart-icon\n [icon]=\"getValue(row, header.propertyName)!\"\n >\n </smart-icon>\n }\n @if (header.propertyName === 'img') {\n <img\n [src]=\"getValue(row, header.propertyName)\"\n alt=\"\"\n class=\"smarttableImg\"\n />\n }\n @if (\n header.propertyName !== 'icon' &&\n header.propertyName !== 'img' &&\n header.propertyName !== 'option' &&\n header.propertyName !== 'button'\n ) {\n @if (columnTemplate(i); as template) {\n <smart-html-cell\n [row]=\"row\"\n [template]=\"template\"\n [gridIdentifier]=\"smartTable.getGridId()\"\n ></smart-html-cell>\n } @else if (isTextContent(i)) {\n <div class=\"smart-table-cell-text\">{{ getValue(row, header.propertyName) }}</div>\n } @else {\n <div\n class=\"smart-table-cell-text\"\n [innerHtml]=\"getValue(row, header.propertyName)\"\n ></div>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [".mobileTable{display:flex;flex-direction:column}.mobileTableRow{display:flex;flex-direction:column;border-bottom:1px solid var(--color-border, #e4eaf2);padding:.5rem;gap:.5rem}.mobileTableRow.sm-grid-row-new{color:var(--success-color, #16a34a);font-style:italic}.mobileTableRow.sm-grid-row-modified{color:var(--accent-color, #20b6a8);font-weight:600}.mobileTableRow.sm-grid-row-deleted{color:var(--color-text-muted, #68778e)}.mobileTableRow.sm-grid-row-deleted .smart-table-cell-text{text-decoration:line-through}.mobileTableRow.sm-grid-row-moved{font-style:italic}.mobileTableRow.sm-grid-row-contains-change{color:var(--accent-color, #20b6a8)}.sm-grid-row-state-icon ::ng-deep .mat-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--accent-color, #20b6a8)}.sm-grid-row-state-icon-new ::ng-deep .mat-icon{color:var(--success-color, #16a34a)}.sm-grid-row-state-icon-deleted ::ng-deep .mat-icon{color:var(--color-text-muted, #68778e)}.sm-grid-row-state-icon-contains-change ::ng-deep .mat-icon{font-size:10px;width:18px;text-align:center;color:var(--color-text-muted, #68778e)}.mobileTableRow.selected{color:inherit}.mobileTableRow.sm-grid-row-open{--sm-grid-open-row-tint: var( --grid-open-row-background, color-mix(in srgb, var(--accent-color, #20b6a8) 10%, transparent) );background-image:linear-gradient(var(--sm-grid-open-row-tint),var(--sm-grid-open-row-tint));box-shadow:inset 3px 0 0 var(--grid-open-row-bar, var(--accent-color, #20b6a8))}.mobileTableCell{display:flex;flex-direction:column;gap:.25rem}.mobileTableCellHeader{font-weight:700;color:var(--primary-color, #384372)}.selected{background-color:var(--primary-light-color, var(--color-surface-alt, #f4f6fb))}.mat-mdc-menu-item[disabled]{cursor:default!important}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }, { kind: "directive", type: SmartTooltipDirective, selector: "[smartTooltip]", inputs: ["smartTooltip", "smartTooltipClass"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: UiActionToolbarComponent, selector: "smart-ui-action-toolbar", inputs: ["uiActionModels", "uiActionDescriptorService", "id", "executor", "widgetId", "nodeId", "actionParams", "scrollOnWrap", "toolbarPropertes"], outputs: ["actionFired"] }, { kind: "component", type: SmartHtmlCellComponent, selector: "smart-html-cell", inputs: ["row", "template", "gridIdentifier"] }, { kind: "pipe", type: SmartDateTimePipe, name: "smartDateTime" }, { kind: "pipe", type: SmartDatePipe, name: "smartDate" }, { kind: "pipe", type: SmartTimePipe, name: "smartTime" }] }); }
|
|
17263
17855
|
}
|
|
17264
17856
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MobileTableComponent, decorators: [{
|
|
17265
17857
|
type: Component,
|
|
17266
|
-
args: [{ selector: 'lib-mobile-table', imports: [NgClass, NgStyle, MatCheckbox, SmartIconComponent, SmartTooltipDirective, MatTooltip, MatIconButton, MatButton, UiActionToolbarComponent, SmartDateTimePipe, SmartDatePipe, SmartTimePipe, SmartHtmlCellComponent], template: "<div class=\"mobileTable\">\n @for (row of smartTable.tableRows; track trackRow($index, row)) {\n <div\n class=\"mobileTableRow\"\n [ngClass]=\"getRowClasses(row)\"\n [ngStyle]=\"getRowStyles(row)\"\n [attr.data-testid]=\"row?.id ?? null\"\n >\n @if (showRowStateColumn) {\n <div class=\"mobileTableCell sm-grid-row-state-cell\" [attr.data-testid]=\"rowStateColumn\">\n @if (rowStateIcon(row); as icon) {\n <smart-icon\n class=\"sm-grid-row-state-icon\"\n [ngClass]=\"rowStateIconClass(row) ?? ''\"\n [icon]=\"icon\"\n color=\"\"\n role=\"img\"\n [attr.aria-label]=\"rowStateLabel(row)\"\n [matTooltip]=\"rowStateLabel(row) ?? ''\"\n ></smart-icon>\n }\n </div>\n }\n @for (header of smartTable.customSmartTableHeaders; track header; let i = $index) {\n <div\n class=\"mobileTableCell\"\n (click)=\"handleOnRowClick($event, row)\"\n (dblclick)=\"handleOnRowDoubleClick($event, row)\"\n >\n <div class=\"mobileTableCellHeader\" [attr.data-testid]=\"header.propertyName\">\n {{ header.label }}\n </div>\n @if (\n smartTable.customSmartTableHeaders &&\n smartTable.customSmartTableHeaders[i].propertyName === 'select'\n ) {\n <mat-checkbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"\n $event\n ? setSelection(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n : null\n \"\n [disabled]=\"isDisabled(row)\"\n [checked]=\"\n smartTable.selection!.isSelected(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n \"\n [aria-label]=\"\n checkboxLabel(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n \"\n >\n </mat-checkbox>\n }\n @if (\n smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].properties &&\n !columnTemplate(i)\n ) {\n <div\n >\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().DATETIME) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartDateTime: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().DATE) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartDate: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().TIME) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartTime: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().CHECKBOX) {\n <div>\n <mat-checkbox\n [disabled]=\"true\"\n [checked]=\"getValue(row, header.propertyName)\"\n ></mat-checkbox>\n </div>\n }\n @if (\n smartTable.customSmartTableHeaders[i].properties?.type === type().ICON &&\n smartTable.customSmartTableHeaders[i].properties?.icons?.length\n ) {\n <div\n >\n <smart-icon\n [smartTooltip]=\"getToolTip(row, i)\"\n [icon]=\"getIcon(getValue(row, header.propertyName), i)!\"\n [color]=\"getColor(getValue(row, header.propertyName), i)\"\n >\n </smart-icon>\n </div>\n }\n </div>\n }\n @if (isImageResource(row, header.propertyName)) {\n <div>\n <smart-icon\n [icon]=\"getImageResourceIcon(row, header.propertyName)\"\n [color]=\"getImageResourceColor(row, header.propertyName)\"\n >\n </smart-icon>\n </div>\n }\n @if (smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].buttons) {\n <div\n class=\"smart-table-buttons-col\"\n >\n @for (button of smartTable.customSmartTableHeaders[i].buttons; track button) {\n <div>\n @if (showButton(button, row)) {\n <div>\n @switch (button.type) {\n @case (smartTableButtonType.ICON) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-icon-button\n color=\"{{ button.color }}\"\n >\n <smart-icon title=\"{{ button.label }}\" [icon]=\"button.icon!\"></smart-icon>\n </button>\n }\n @case (smartTableButtonType.NORMAL) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-button\n color=\"{{ button.color }}\"\n >\n @if (button.icon) {\n <smart-icon [icon]=\"button.icon\"></smart-icon>\n }\n {{ button.label }}\n </button>\n }\n @case (smartTableButtonType.RAISED) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-raised-button\n color=\"{{ button.color }}\"\n >\n @if (button.icon) {\n <smart-icon [icon]=\"button.icon\"></smart-icon>\n }\n {{ button.label }}\n </button>\n }\n <!------ MENU ------>\n @case (smartTableButtonType.MENU) {\n <div class=\"menu-button\">\n <!------ DEFAULT_ACTION_COLUMN ID TOOLBAR ------>\n <smart-ui-action-toolbar\n [uiActionModels]=\"getRowColumnAction(element, defaultActionToolbarId)\"\n [widgetId]=\"smartTable.getGridId()\"\n [nodeId]=\"element.id\"\n [actionParams]=\"{ model: element }\"\n (actionFired)=\"onRowActionFired(element, $event)\"\n ></smart-ui-action-toolbar>\n <!------ GENERIC HAMBURGER ------>\n @if (shouldShowMenuButton(element)) {\n <smart-ui-action-toolbar\n [uiActionModels]=\"getMenuActions(element)\"\n [widgetId]=\"smartTable.getGridId()\"\n [nodeId]=\"element.id\"\n [actionParams]=\"{ model: element }\"\n (actionFired)=\"onRowActionFired(element, $event)\"\n ></smart-ui-action-toolbar>\n }\n <!------ MENU ------>\n </div>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n @if (smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].icon) {\n <div>\n @if (smartTable.customSmartTableHeaders[i].icon?.icon) {\n <smart-icon\n [ngClass]=\"smartTable.customSmartTableHeaders[i].icon?.cssClass ?? ''\"\n [color]=\"$safeNavigationMigration(smartTable.customSmartTableHeaders[i].icon?.color)\"\n [icon]=\"smartTable.customSmartTableHeaders[i].icon!.icon\"\n >\n </smart-icon>\n }\n </div>\n }\n @if (\n smartTable.customSmartTableHeaders &&\n smartTable.customSmartTableHeaders[i].translator !== undefined\n ) {\n <div\n class=\"smart-table-cell-text\"\n >\n {{ smartTable.customSmartTableHeaders[i].translator!(getValue(row, header.propertyName)) }}\n </div>\n }\n @if (\n !smartTable.customSmartTableHeaders ||\n (smartTable.customSmartTableHeaders &&\n (!smartTable.customSmartTableHeaders[i].properties || columnTemplate(i)) &&\n !smartTable.customSmartTableHeaders[i].icon &&\n !smartTable.customSmartTableHeaders[i].buttons &&\n !smartTable.customSmartTableHeaders[i].translator &&\n !(smartTable.customSmartTableHeaders[i].propertyName === 'select') &&\n !(smartTable.customSmartTableHeaders[i].propertyName === 'expand'))\n ) {\n <div\n >\n @if (header.propertyName === 'icon') {\n <smart-icon\n [icon]=\"getValue(row, header.propertyName)!\"\n >\n </smart-icon>\n }\n @if (header.propertyName === 'img') {\n <img\n [src]=\"getValue(row, header.propertyName)\"\n alt=\"\"\n class=\"smarttableImg\"\n />\n }\n @if (\n header.propertyName !== 'icon' &&\n header.propertyName !== 'img' &&\n header.propertyName !== 'option' &&\n header.propertyName !== 'button'\n ) {\n @if (columnTemplate(i); as template) {\n <smart-html-cell\n [row]=\"row\"\n [template]=\"template\"\n [gridIdentifier]=\"smartTable.getGridId()\"\n ></smart-html-cell>\n } @else if (isTextContent(i)) {\n <div class=\"smart-table-cell-text\">{{ getValue(row, header.propertyName) }}</div>\n } @else {\n <div\n class=\"smart-table-cell-text\"\n [innerHtml]=\"getValue(row, header.propertyName)\"\n ></div>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [".mobileTable{display:flex;flex-direction:column}.mobileTableRow{display:flex;flex-direction:column;border-bottom:1px solid var(--color-border, #e4eaf2);padding:.5rem;gap:.5rem}.mobileTableRow.sm-grid-row-new{color:var(--success-color, #16a34a);font-style:italic}.mobileTableRow.sm-grid-row-modified{color:var(--accent-color, #20b6a8);font-weight:600}.mobileTableRow.sm-grid-row-deleted{color:var(--color-text-muted, #68778e)}.mobileTableRow.sm-grid-row-deleted .smart-table-cell-text{text-decoration:line-through}.mobileTableRow.sm-grid-row-moved{font-style:italic}.mobileTableRow.sm-grid-row-contains-change{color:var(--accent-color, #20b6a8)}.sm-grid-row-state-icon ::ng-deep .mat-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--accent-color, #20b6a8)}.sm-grid-row-state-icon-new ::ng-deep .mat-icon{color:var(--success-color, #16a34a)}.sm-grid-row-state-icon-deleted ::ng-deep .mat-icon{color:var(--color-text-muted, #68778e)}.sm-grid-row-state-icon-contains-change ::ng-deep .mat-icon{font-size:10px;width:18px;text-align:center;color:var(--color-text-muted, #68778e)}.mobileTableRow.selected{color:inherit}.mobileTableRow.sm-grid-row-open{--sm-grid-open-row-tint: var( --grid-open-row-background, color-mix(in srgb, var(--accent-color, #20b6a8) 10%, transparent) );background-image:linear-gradient(var(--sm-grid-open-row-tint),var(--sm-grid-open-row-tint));box-shadow:inset 3px 0 0 var(--grid-open-row-bar, var(--accent-color, #20b6a8))}.mobileTableCell{display:flex;flex-direction:column;gap:.25rem}.mobileTableCellHeader{font-weight:700;color:var(--primary-color, #384372)}.selected{background-color:var(--primary-light-color, var(--color-surface-alt, #f4f6fb))}.mat-mdc-menu-item[disabled]{cursor:default!important}\n"] }]
|
|
17858
|
+
args: [{ selector: 'lib-mobile-table', imports: [NgClass, NgStyle, MatCheckbox, SmartIconComponent, SmartTooltipDirective, MatTooltip, MatIconButton, MatButton, UiActionToolbarComponent, SmartDateTimePipe, SmartDatePipe, SmartTimePipe, SmartHtmlCellComponent], template: "<div class=\"mobileTable\">\n @for (row of smartTable.tableRows; track trackRow($index, row)) {\n <div\n class=\"mobileTableRow\"\n [ngClass]=\"getRowClasses(row)\"\n [ngStyle]=\"getRowStyles(row)\"\n [attr.data-testid]=\"row?.id ?? null\"\n >\n @if (showRowStateColumn) {\n <div class=\"mobileTableCell sm-grid-row-state-cell\" [attr.data-testid]=\"rowStateColumn\">\n @if (rowStateIcon(row); as icon) {\n <smart-icon\n class=\"sm-grid-row-state-icon\"\n [ngClass]=\"rowStateIconClass(row) ?? ''\"\n [icon]=\"icon\"\n color=\"\"\n role=\"img\"\n [attr.aria-label]=\"rowStateLabel(row)\"\n [matTooltip]=\"rowStateLabel(row) ?? ''\"\n ></smart-icon>\n }\n </div>\n }\n @for (header of smartTable.customSmartTableHeaders; track header; let i = $index) {\n <div\n class=\"mobileTableCell\"\n (click)=\"handleOnRowClick($event, row)\"\n (dblclick)=\"handleOnRowDoubleClick($event, row)\"\n >\n <div class=\"mobileTableCellHeader\" [attr.data-testid]=\"header.propertyName\">\n {{ header.label }}\n </div>\n @if (\n smartTable.customSmartTableHeaders &&\n smartTable.customSmartTableHeaders[i].propertyName === 'select'\n ) {\n <mat-checkbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"\n $event\n ? setSelection(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n : null\n \"\n [disabled]=\"isDisabled(row)\"\n [checked]=\"\n smartTable.selection!.isSelected(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n \"\n [aria-label]=\"\n checkboxLabel(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n \"\n >\n </mat-checkbox>\n }\n @if (\n smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].properties &&\n !columnTemplate(i)\n ) {\n <div\n >\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().DATETIME) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartDateTime: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().DATE) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartDate: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().TIME) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartTime: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().CHECKBOX) {\n <div>\n <mat-checkbox\n [disabled]=\"true\"\n [checked]=\"getValue(row, header.propertyName)\"\n ></mat-checkbox>\n </div>\n }\n @if (\n smartTable.customSmartTableHeaders[i].properties?.type === type().ICON &&\n smartTable.customSmartTableHeaders[i].properties?.icons?.length\n ) {\n <div\n >\n <smart-icon\n [smartTooltip]=\"getToolTip(row, i)\"\n [icon]=\"getIcon(getValue(row, header.propertyName), i)!\"\n [color]=\"getColor(getValue(row, header.propertyName), i)\"\n >\n </smart-icon>\n </div>\n }\n </div>\n }\n @if (isImageResource(row, header.propertyName)) {\n <div>\n <smart-icon\n [icon]=\"getImageResourceIcon(row, header.propertyName)\"\n [color]=\"getImageResourceColor(row, header.propertyName)\"\n >\n </smart-icon>\n </div>\n }\n @if (smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].buttons) {\n <div\n class=\"smart-table-buttons-col\"\n >\n @for (button of smartTable.customSmartTableHeaders[i].buttons; track button) {\n <div>\n @if (showButton(button, row)) {\n <div>\n @switch (button.type) {\n @case (smartTableButtonType.ICON) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-icon-button\n color=\"{{ button.color }}\"\n >\n <smart-icon title=\"{{ button.label }}\" [icon]=\"button.icon!\"></smart-icon>\n </button>\n }\n @case (smartTableButtonType.NORMAL) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-button\n color=\"{{ button.color }}\"\n >\n @if (button.icon) {\n <smart-icon [icon]=\"button.icon\"></smart-icon>\n }\n {{ button.label }}\n </button>\n }\n @case (smartTableButtonType.RAISED) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-raised-button\n color=\"{{ button.color }}\"\n >\n @if (button.icon) {\n <smart-icon [icon]=\"button.icon\"></smart-icon>\n }\n {{ button.label }}\n </button>\n }\n <!------ MENU ------>\n @case (smartTableButtonType.MENU) {\n <div class=\"menu-button\">\n <!------ DEFAULT_ACTION_COLUMN ID TOOLBAR ------>\n <smart-ui-action-toolbar\n [uiActionModels]=\"getRowColumnAction(row, defaultActionToolbarId)\"\n [widgetId]=\"smartTable.getGridId()\"\n [nodeId]=\"row.id\"\n [actionParams]=\"{ model: row }\"\n (actionFired)=\"onRowActionFired(row, $event)\"\n ></smart-ui-action-toolbar>\n <!------ GENERIC HAMBURGER ------>\n @if (shouldShowMenuButton(row)) {\n <smart-ui-action-toolbar\n [uiActionModels]=\"getMenuActions(row)\"\n [widgetId]=\"smartTable.getGridId()\"\n [nodeId]=\"row.id\"\n [actionParams]=\"{ model: row }\"\n (actionFired)=\"onRowActionFired(row, $event)\"\n ></smart-ui-action-toolbar>\n }\n <!------ MENU ------>\n </div>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n @if (smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].icon) {\n <div>\n @if (smartTable.customSmartTableHeaders[i].icon?.icon) {\n <smart-icon\n [ngClass]=\"smartTable.customSmartTableHeaders[i].icon?.cssClass ?? ''\"\n [color]=\"$safeNavigationMigration(smartTable.customSmartTableHeaders[i].icon?.color)\"\n [icon]=\"smartTable.customSmartTableHeaders[i].icon!.icon\"\n >\n </smart-icon>\n }\n </div>\n }\n @if (\n smartTable.customSmartTableHeaders &&\n smartTable.customSmartTableHeaders[i].translator !== undefined\n ) {\n <div\n class=\"smart-table-cell-text\"\n >\n {{ smartTable.customSmartTableHeaders[i].translator!(getValue(row, header.propertyName)) }}\n </div>\n }\n @if (\n !smartTable.customSmartTableHeaders ||\n (smartTable.customSmartTableHeaders &&\n (!smartTable.customSmartTableHeaders[i].properties || columnTemplate(i)) &&\n !smartTable.customSmartTableHeaders[i].icon &&\n !smartTable.customSmartTableHeaders[i].buttons &&\n !smartTable.customSmartTableHeaders[i].translator &&\n !(smartTable.customSmartTableHeaders[i].propertyName === 'select') &&\n !(smartTable.customSmartTableHeaders[i].propertyName === 'expand'))\n ) {\n <div\n >\n @if (header.propertyName === 'icon') {\n <smart-icon\n [icon]=\"getValue(row, header.propertyName)!\"\n >\n </smart-icon>\n }\n @if (header.propertyName === 'img') {\n <img\n [src]=\"getValue(row, header.propertyName)\"\n alt=\"\"\n class=\"smarttableImg\"\n />\n }\n @if (\n header.propertyName !== 'icon' &&\n header.propertyName !== 'img' &&\n header.propertyName !== 'option' &&\n header.propertyName !== 'button'\n ) {\n @if (columnTemplate(i); as template) {\n <smart-html-cell\n [row]=\"row\"\n [template]=\"template\"\n [gridIdentifier]=\"smartTable.getGridId()\"\n ></smart-html-cell>\n } @else if (isTextContent(i)) {\n <div class=\"smart-table-cell-text\">{{ getValue(row, header.propertyName) }}</div>\n } @else {\n <div\n class=\"smart-table-cell-text\"\n [innerHtml]=\"getValue(row, header.propertyName)\"\n ></div>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [".mobileTable{display:flex;flex-direction:column}.mobileTableRow{display:flex;flex-direction:column;border-bottom:1px solid var(--color-border, #e4eaf2);padding:.5rem;gap:.5rem}.mobileTableRow.sm-grid-row-new{color:var(--success-color, #16a34a);font-style:italic}.mobileTableRow.sm-grid-row-modified{color:var(--accent-color, #20b6a8);font-weight:600}.mobileTableRow.sm-grid-row-deleted{color:var(--color-text-muted, #68778e)}.mobileTableRow.sm-grid-row-deleted .smart-table-cell-text{text-decoration:line-through}.mobileTableRow.sm-grid-row-moved{font-style:italic}.mobileTableRow.sm-grid-row-contains-change{color:var(--accent-color, #20b6a8)}.sm-grid-row-state-icon ::ng-deep .mat-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--accent-color, #20b6a8)}.sm-grid-row-state-icon-new ::ng-deep .mat-icon{color:var(--success-color, #16a34a)}.sm-grid-row-state-icon-deleted ::ng-deep .mat-icon{color:var(--color-text-muted, #68778e)}.sm-grid-row-state-icon-contains-change ::ng-deep .mat-icon{font-size:10px;width:18px;text-align:center;color:var(--color-text-muted, #68778e)}.mobileTableRow.selected{color:inherit}.mobileTableRow.sm-grid-row-open{--sm-grid-open-row-tint: var( --grid-open-row-background, color-mix(in srgb, var(--accent-color, #20b6a8) 10%, transparent) );background-image:linear-gradient(var(--sm-grid-open-row-tint),var(--sm-grid-open-row-tint));box-shadow:inset 3px 0 0 var(--grid-open-row-bar, var(--accent-color, #20b6a8))}.mobileTableCell{display:flex;flex-direction:column;gap:.25rem}.mobileTableCellHeader{font-weight:700;color:var(--primary-color, #384372)}.selected{background-color:var(--primary-light-color, var(--color-surface-alt, #f4f6fb))}.mat-mdc-menu-item[disabled]{cursor:default!important}\n"] }]
|
|
17267
17859
|
}] });
|
|
17268
17860
|
|
|
17269
17861
|
class SmarttableComponent {
|
|
@@ -28437,7 +29029,7 @@ class UiActionFileUploadDialogComponent {
|
|
|
28437
29029
|
this.service.cancel();
|
|
28438
29030
|
}
|
|
28439
29031
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UiActionFileUploadDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
28440
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: UiActionFileUploadDialogComponent, isStandalone: true, selector: "lib-ui-action-file-upload-dialog", ngImport: i0, template: "<div class=\"folderNameDialogContainer\">\n <div class=\"headerContainer\">\n <h3 class=\"color-accent-700\">\n {{ getTitle() }}\n </h3>\n </div>\n\n @if(getText()){\n <p>\n {{ getText() }}\n </p>\n } @if(descriptor){\n <smart-upload-widget\n [uploadDescriptor]=\"descriptor.upload\"\n [isMultiple]=\"isMultiple ?? true\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div class=\"folderNameDialogButtonsContainer\">\n <ui-action-button [descriptor]=\"cancelButton\" (actionClick)=\"cancel()\"></ui-action-button>\n </div>\n</div>\n", styles: [".folderNameDialogContainer{display:flex;flex-direction:column;justify-content:center;gap:1rem;min-width:30vw}photo-capture-widget{width:100%;max-width:100%;overflow:hidden;display:flex;flex-direction:column;align-items:center}.headerContainer{display:flex;flex-direction:row;justify-content:space-between;align-items:center;padding-top:.5rem}.headerContainer ::ng-deep button{color:var(--primary-color);border:unset}.headerContainer h3{margin:0;font-size:1.5rem}.folderNameDialogButtonsContainer{display:flex;flex-direction:row;justify-content:flex-start;gap:.5rem}.message{text-align:center;color:#6b7280}.uploadField{display:flex;flex-direction:row;justify-content:center;align-items:center;gap:1rem;padding:1rem}.folderNameDialogButtonsContainer ::ng-deep button:hover{background-color:rgba(from var(--primary-color) r g b/.8)}.headerContainer ::ng-deep button:hover{background-color:rgba(from var(--primary-color) r g b/.1)}@media(max-width:900px){.folderNameDialogContainer{width:100%}}.fileSize{padding-left:1rem;display:flex;justify-content:flex-end}.fileData{display:flex;flex-direction:column;justify-content:flex-end;padding-right:1rem}\n"], dependencies: [{ kind: "component", type: UploadWidgetComponent, selector: "smart-upload-widget", inputs: ["uploadDescriptor", "isMultiple", "isDisabled", "sources"], outputs: ["uploadFilesEvent"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
|
|
29032
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: UiActionFileUploadDialogComponent, isStandalone: true, selector: "lib-ui-action-file-upload-dialog", ngImport: i0, template: "<div class=\"folderNameDialogContainer\">\n <div class=\"headerContainer\">\n <h3 class=\"color-accent-700\">\n {{ getTitle() }}\n </h3>\n </div>\n\n @if(getText()){\n <p>\n {{ getText() }}\n </p>\n } @if(descriptor){\n <smart-upload-widget\n [uploadDescriptor]=\"descriptor.upload\"\n [isMultiple]=\"isMultiple ?? true\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div class=\"folderNameDialogButtonsContainer\">\n <ui-action-button [descriptor]=\"cancelButton\" (actionClick)=\"cancel()\"></ui-action-button>\n </div>\n</div>\n", styles: [".folderNameDialogContainer{display:flex;flex-direction:column;justify-content:center;gap:1rem;min-width:30vw}photo-capture-widget{width:100%;max-width:100%;overflow:hidden;display:flex;flex-direction:column;align-items:center}.headerContainer{display:flex;flex-direction:row;justify-content:space-between;align-items:center;padding-top:.5rem}.headerContainer ::ng-deep button{color:var(--primary-color);border:unset}.headerContainer h3{margin:0;font-size:1.5rem}.folderNameDialogButtonsContainer{display:flex;flex-direction:row;justify-content:flex-start;gap:.5rem}.message{text-align:center;color:#6b7280}.uploadField{display:flex;flex-direction:row;justify-content:center;align-items:center;gap:1rem;padding:1rem}.folderNameDialogButtonsContainer ::ng-deep button:hover{background-color:rgba(from var(--primary-color) r g b/.8)}.headerContainer ::ng-deep button:hover{background-color:rgba(from var(--primary-color) r g b/.1)}@media(max-width:900px){.folderNameDialogContainer{width:100%}}.fileSize{padding-left:1rem;display:flex;justify-content:flex-end}.fileData{display:flex;flex-direction:column;justify-content:flex-end;padding-right:1rem}\n"], dependencies: [{ kind: "component", type: UploadWidgetComponent, selector: "smart-upload-widget", inputs: ["uploadDescriptor", "isMultiple", "isDisabled", "sources", "capture"], outputs: ["uploadFilesEvent"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
|
|
28441
29033
|
}
|
|
28442
29034
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UiActionFileUploadDialogComponent, decorators: [{
|
|
28443
29035
|
type: Component,
|
|
@@ -29312,5 +29904,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
29312
29904
|
* Generated bundle index. Do not edit.
|
|
29313
29905
|
*/
|
|
29314
29906
|
|
|
29315
|
-
export { ACLEntry, APIS$5 as APIS, AclEditingViewComponent, ApiModule$5 as ApiModule, ApiQueueService, BASE_PATH$7 as BASE_PATH, COLLECTION_FORMATS$7 as COLLECTION_FORMATS, CUSTOM_DIAGRAM_OPTIONS, CloseResult, CodeEditorLang, ComponentFactoryService, ComponentType, ComponentWidgetType, Configuration$7 as Configuration, DEFAULT_ROW_LAYOUT, DIALOG_DISABLE_CLOSE, DataChangeKind, DefaultChartOptionsProvider, DefaultUiActionCode_CLOSE, DrawTime, EMPTY_PARAMS, ExpandableSectionButtonIconPosition, ExpandableSectionButtonType, ExpandableSectionComponent, FileUploaderSource, FileUploaderThumbnail, FilterExpressionBoolOperator$1 as FilterExpressionBoolOperator, FilterExpressionBuilderGroupBuilderGroupKindEnum, FilterExpressionBuilderUiModelTypeEnum, FilterExpressionDataType, FilterExpressionFieldWidgetType, FilterExpressionOperation, FilterExpressionOrderByOrderEnum, GenericPageComponent, GeoMapDataLoadingMode, GeoMapDataSourceType, GeoMapItemKind, GeoMapOperationMode, GeoMapSelectionMode, GeoMapTextType, GridColumnContentType, GridDataAccessConfigKindEnum, GridDefaultRowActionTrigger, GridRowStateDisplay, GridSelectionMode, GridSelectionType, GridUiActionType, GridViewDescriptorKindEnum, HighlightPipe, IS_ASYNC_PARAM_NAME, IconPosition, ImageResourceKindEnum, InvalidSmartlinkComponent, LayoutDirection, LinkTargetEnum, MAP_ENGINE, MapEngine, MenuOpenDirection, MenuTriggerType, MessageOptionType, MessageTextType, MessageType, NAMED_VALIDATOR, NamedValidatorOperationEnum, NamedValidatorService, NoPermissionPageComponent, PageExportFieldScope, PageExportSelectionKind, PageExportValueType, PageExportValuesSource, REUSE_ROUTER_SLOT_COMPONENT, ROOT_CONTAINER_UUID, ROUTER_SLOT_ID, RecordingUploaderPropertiesOpenDirectionEnum, SMART_DATE_FORMATS, SMART_DEFAULT_VIEW_COMPONENTS, SMART_MESSAGE_DEFAULTS, SMART_NG_CLIENT_CONFIG, SMART_ROW_LAYOUT_COMPONENT, SearchPageComponent, SelectAllMode, SelectionDefinitionTypeEnum, SelectionDefinitionValueSetValueTypeEnum, ServerRequestType, SessionAPIS, SessionErrorBehaviour, SessionService, SmartActionHost, SmartActionType, SmartBackendBootstrapService, SmartComponent, SmartComponentApiClient, SmartComponentDetachedDirective, SmartComponentHost, SmartComponentLayoutComponent, SmartDateFnsAdapter, SmartDatePipe, SmartDateTimePipe, SmartDefaultErrorUiService, SmartDiagramComponent, SmartDialog, SmartEmbeddedSlotDirective, SmartExpandableSectionService, SmartFileEditorComponent, SmartFilterComponent, SmartFilterEditorContentComponent, SmartFilterEditorService, SmartFilterExpressionFieldComponent, SmartFilterPosition, SmartFilterType, SmartFilterWidgetComponent, SmartFormInputMode, SmartFormTextFieldButtonIconPosition, SmartFormWidgetDirection, SmartFormWidgetType, SmartFormWidgetWidth, SmartGridButtonType, SmartGridComponent, SmartGridDataLayout, SmartGridService, SmartGridType, SmartIconComponent, SmartIconService, SmartKeybindingService, SmartLayoutDef, SmartLinkChannelVariableInPath, SmartLinkMigrationStatusStatusEnum, SmartLinkUuidVariableInPath, SmartMapComponent, SmartMultiFileEditorComponent, SmartNavbarComponent, SmartNavbarService, SmartNavigationService, SmartNgClientFeatureKind, SmartService, SmartSessionService, SmartSessionTimerComponent, SmartTable, SmartTableButtonType, SmartTableHeaderPropertyType, SmartTableInterfaceTypeEnum, SmartTableOptionButtonDirection, SmartTableOrder, SmartTableType, SmartTimePipe, SmartTooltipDirective, SmartTreeComponent, SmartTreeNodeButtonType, SmartUserSettinsIconPosition, SmartValidatorName, SmartValueSetStore, SmartViewContextService, SmartViewRedirect, SmartVoiceRecorderComponent, SmartWidgetHintPosition, SmartWidgetHintPositionEnum, SmartdialogService, SmartfileuploaderComponent, SmartformComponent, SmartformLayoutDefinitionService, SmarttreeGenericService, SmarttreeService, SubjectSelectorComponent, ThemeService, ToggleLabelPosition, ToolbarAlignment, ToolbarAppearance, ToolbarDirection, TreeArrowPosition, TreeGridControl, USE_ADVANCED_FILTER, UiActionButtonComponent, UiActionButtonType, UiActionConfirmDialogService, UiActionDescriptorService, UiActionDialogButtonComponent, UiActionDialogType, UiActionFeedbackType, UiActionInputDialogService, UiActionInputType, UiActionService, UiActionToolbarComponent, UiActionTooltipTooltipPositionEnum, UiBadgeComponent, UiBadgeDirective, UploadWidgetType, ValidationResultPageComponent, ValueChangeMode, ValueSetDependencyWhenParentEmptyEnum, ViewContextRebuiltError, ViewEventHandlerViewEventTypeEnum, ViewService, ViewState, ViewType, barChart, bubbleChart, createBasicOptions, doughnutChart, formatChord, formatMessage, hasGroupEditActions, horizontalBarChart, horizontalStackedBarChart, injectSmartComponent, isMacPlatform, isMergedExtra, matches, multiAxisLineChart, parseChord, pieChart, polarAreaChart, provideSmartNgClient, radarChart, registerSmartWidget, resolveDateLocale, resolveSlot, resolveSmartNgClientConfig, resolveViewHandler, scatterChart, stackedBarChart, transformDataToBarLikeDataSet, transformDataToBasicDataSets, transformDataToBubble, transformDataToScatter, withSmartDiagram, withSmartMap };
|
|
29907
|
+
export { ACLEntry, APIS$5 as APIS, AclEditingViewComponent, ApiModule$5 as ApiModule, ApiQueueService, BASE_PATH$7 as BASE_PATH, COLLECTION_FORMATS$7 as COLLECTION_FORMATS, CUSTOM_DIAGRAM_OPTIONS, CaptureDeviceStart, CaptureHandover, CaptureMode, CaptureOpenDirection, CloseResult, CodeEditorLang, ComponentFactoryService, ComponentType, ComponentWidgetType, Configuration$7 as Configuration, DEFAULT_ROW_LAYOUT, DIALOG_DISABLE_CLOSE, DataChangeKind, DefaultChartOptionsProvider, DefaultUiActionCode_CLOSE, DrawTime, EMPTY_PARAMS, ExpandableSectionButtonIconPosition, ExpandableSectionButtonType, ExpandableSectionComponent, FileUploaderSource, FileUploaderThumbnail, FilterExpressionBoolOperator$1 as FilterExpressionBoolOperator, FilterExpressionBuilderGroupBuilderGroupKindEnum, FilterExpressionBuilderUiModelTypeEnum, FilterExpressionDataType, FilterExpressionFieldWidgetType, FilterExpressionOperation, FilterExpressionOrderByOrderEnum, GenericPageComponent, GeoMapDataLoadingMode, GeoMapDataSourceType, GeoMapItemKind, GeoMapOperationMode, GeoMapSelectionMode, GeoMapTextType, GridColumnContentType, GridDataAccessConfigKindEnum, GridDefaultRowActionTrigger, GridRowStateDisplay, GridSelectionMode, GridSelectionType, GridUiActionType, GridViewDescriptorKindEnum, HighlightPipe, IS_ASYNC_PARAM_NAME, IconPosition, ImageResourceKindEnum, InvalidSmartlinkComponent, LayoutDirection, LinkTargetEnum, MAP_ENGINE, MapEngine, MenuOpenDirection, MenuTriggerType, MessageOptionType, MessageTextType, MessageType, NAMED_VALIDATOR, NamedValidatorOperationEnum, NamedValidatorService, NoPermissionPageComponent, PageExportFieldScope, PageExportSelectionKind, PageExportValueType, PageExportValuesSource, REUSE_ROUTER_SLOT_COMPONENT, ROOT_CONTAINER_UUID, ROUTER_SLOT_ID, RecordingUploaderPropertiesOpenDirectionEnum, SMART_DATE_FORMATS, SMART_DEFAULT_VIEW_COMPONENTS, SMART_MESSAGE_DEFAULTS, SMART_NG_CLIENT_CONFIG, SMART_ROW_LAYOUT_COMPONENT, SearchPageComponent, SelectAllMode, SelectionDefinitionTypeEnum, SelectionDefinitionValueSetValueTypeEnum, ServerRequestType, SessionAPIS, SessionErrorBehaviour, SessionService, SmartActionHost, SmartActionType, SmartBackendBootstrapService, SmartComponent, SmartComponentApiClient, SmartComponentDetachedDirective, SmartComponentHost, SmartComponentLayoutComponent, SmartDateFnsAdapter, SmartDatePipe, SmartDateTimePipe, SmartDefaultErrorUiService, SmartDiagramComponent, SmartDialog, SmartEmbeddedSlotDirective, SmartExpandableSectionService, SmartFileEditorComponent, SmartFilterComponent, SmartFilterEditorContentComponent, SmartFilterEditorService, SmartFilterExpressionFieldComponent, SmartFilterPosition, SmartFilterType, SmartFilterWidgetComponent, SmartFormInputMode, SmartFormTextFieldButtonIconPosition, SmartFormWidgetDirection, SmartFormWidgetType, SmartFormWidgetWidth, SmartGridButtonType, SmartGridComponent, SmartGridDataLayout, SmartGridService, SmartGridType, SmartIconComponent, SmartIconService, SmartKeybindingService, SmartLayoutDef, SmartLinkChannelVariableInPath, SmartLinkMigrationStatusStatusEnum, SmartLinkUuidVariableInPath, SmartMapComponent, SmartMultiFileEditorComponent, SmartNavbarComponent, SmartNavbarService, SmartNavigationService, SmartNgClientFeatureKind, SmartService, SmartSessionService, SmartSessionTimerComponent, SmartTable, SmartTableButtonType, SmartTableHeaderPropertyType, SmartTableInterfaceTypeEnum, SmartTableOptionButtonDirection, SmartTableOrder, SmartTableType, SmartTimePipe, SmartTooltipDirective, SmartTreeComponent, SmartTreeNodeButtonType, SmartUserSettinsIconPosition, SmartValidatorName, SmartValueSetStore, SmartViewContextService, SmartViewRedirect, SmartVoiceRecorderComponent, SmartWidgetHintPosition, SmartWidgetHintPositionEnum, SmartdialogService, SmartfileuploaderComponent, SmartformComponent, SmartformLayoutDefinitionService, SmarttreeGenericService, SmarttreeService, SubjectSelectorComponent, ThemeService, ToggleLabelPosition, ToolbarAlignment, ToolbarAppearance, ToolbarDirection, TreeArrowPosition, TreeGridControl, USE_ADVANCED_FILTER, UiActionButtonComponent, UiActionButtonType, UiActionConfirmDialogService, UiActionDescriptorService, UiActionDialogButtonComponent, UiActionDialogType, UiActionFeedbackType, UiActionInputDialogService, UiActionInputType, UiActionService, UiActionToolbarComponent, UiActionTooltipTooltipPositionEnum, UiBadgeComponent, UiBadgeDirective, UploadWidgetType, ValidationResultPageComponent, ValueChangeMode, ValueSetDependencyWhenParentEmptyEnum, ViewContextRebuiltError, ViewEventHandlerViewEventTypeEnum, ViewService, ViewState, ViewType, barChart, bubbleChart, createBasicOptions, doughnutChart, formatChord, formatMessage, hasGroupEditActions, horizontalBarChart, horizontalStackedBarChart, injectSmartComponent, isMacPlatform, isMergedExtra, matches, multiAxisLineChart, parseChord, pieChart, polarAreaChart, provideSmartNgClient, radarChart, registerSmartWidget, resolveDateLocale, resolveSlot, resolveSmartNgClientConfig, resolveViewHandler, scatterChart, stackedBarChart, transformDataToBarLikeDataSet, transformDataToBasicDataSets, transformDataToBubble, transformDataToScatter, withSmartDiagram, withSmartMap };
|
|
29316
29908
|
//# sourceMappingURL=smartbit4all-ng-client.mjs.map
|