@smartbit4all/ng-client 7.3.40 → 7.3.42
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 +18 -0
- package/fesm2022/smartbit4all-ng-client.mjs +1074 -136
- package/fesm2022/smartbit4all-ng-client.mjs.map +1 -1
- package/package.json +1 -1
- package/smartbit4all-ng-client-7.3.42.tgz +0 -0
- package/types/smartbit4all-ng-client.d.ts +209 -5
- package/smartbit4all-ng-client-7.3.40.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, forwardRef, createComponent, EnvironmentInjector, ApplicationRef,
|
|
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.
|
|
@@ -7805,6 +7891,48 @@ var CodeEditorLang;
|
|
|
7805
7891
|
* Do not edit the class manually.
|
|
7806
7892
|
*/
|
|
7807
7893
|
|
|
7894
|
+
/**
|
|
7895
|
+
* Form layout definition
|
|
7896
|
+
* Contains form layout definition objects.
|
|
7897
|
+
*
|
|
7898
|
+
* The version of the OpenAPI document: 1.0.0
|
|
7899
|
+
* Contact: info@it4all.hu
|
|
7900
|
+
*
|
|
7901
|
+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
|
7902
|
+
* https://openapi-generator.tech
|
|
7903
|
+
* Do not edit the class manually.
|
|
7904
|
+
*/
|
|
7905
|
+
/**
|
|
7906
|
+
* A place a file widget takes a file from: FILE the file picker, CAMERA_PHOTO the device camera for a photo, CAMERA_VIDEO the device camera for a video, MICROPHONE the microphone.
|
|
7907
|
+
*/
|
|
7908
|
+
var FileUploaderSource;
|
|
7909
|
+
(function (FileUploaderSource) {
|
|
7910
|
+
FileUploaderSource["FILE"] = "FILE";
|
|
7911
|
+
FileUploaderSource["CAMERA_PHOTO"] = "CAMERA_PHOTO";
|
|
7912
|
+
FileUploaderSource["CAMERA_VIDEO"] = "CAMERA_VIDEO";
|
|
7913
|
+
FileUploaderSource["MICROPHONE"] = "MICROPHONE";
|
|
7914
|
+
})(FileUploaderSource || (FileUploaderSource = {}));
|
|
7915
|
+
|
|
7916
|
+
/**
|
|
7917
|
+
* Form layout definition
|
|
7918
|
+
* Contains form layout definition objects.
|
|
7919
|
+
*
|
|
7920
|
+
* The version of the OpenAPI document: 1.0.0
|
|
7921
|
+
* Contact: info@it4all.hu
|
|
7922
|
+
*
|
|
7923
|
+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
|
7924
|
+
* https://openapi-generator.tech
|
|
7925
|
+
* Do not edit the class manually.
|
|
7926
|
+
*/
|
|
7927
|
+
/**
|
|
7928
|
+
* Whether the widget shows the stored file itself (image, video player, audio player) on the card: NONE shows the icon, the name and the size only, FULL shows the file above the name line.
|
|
7929
|
+
*/
|
|
7930
|
+
var FileUploaderThumbnail;
|
|
7931
|
+
(function (FileUploaderThumbnail) {
|
|
7932
|
+
FileUploaderThumbnail["NONE"] = "NONE";
|
|
7933
|
+
FileUploaderThumbnail["FULL"] = "FULL";
|
|
7934
|
+
})(FileUploaderThumbnail || (FileUploaderThumbnail = {}));
|
|
7935
|
+
|
|
7808
7936
|
/**
|
|
7809
7937
|
* Form layout definition
|
|
7810
7938
|
* Contains form layout definition objects.
|
|
@@ -9057,11 +9185,81 @@ class UploadWidgetUtils {
|
|
|
9057
9185
|
const value = parseFloat((bytes / Math.pow(k, i)).toFixed(1));
|
|
9058
9186
|
return `${value} ${sizes[i]}`;
|
|
9059
9187
|
}
|
|
9060
|
-
|
|
9188
|
+
/** The size of a capture with a decimal comma, as the Capture Dialog's captions show it. */
|
|
9189
|
+
static formatCapturedSize(bytes) {
|
|
9190
|
+
return UploadWidgetUtils.formatSize(bytes).replace('.', ',');
|
|
9191
|
+
}
|
|
9192
|
+
/** A length of time as mm:ss. */
|
|
9193
|
+
static formatDuration(milliseconds) {
|
|
9194
|
+
return [Math.floor((milliseconds % 3600000) / 60000), Math.floor((milliseconds % 60000) / 1000)]
|
|
9195
|
+
.map((v) => (v < 10 ? '0' + v : v))
|
|
9196
|
+
.join(':');
|
|
9197
|
+
}
|
|
9198
|
+
/** The recording mime of a media kind: webm where the browser records it, else mp4 (Safari). */
|
|
9199
|
+
static chooseRecorderMimeType(kind) {
|
|
9200
|
+
return MediaRecorder.isTypeSupported(`${kind}/webm`) ? `${kind}/webm` : `${kind}/mp4`;
|
|
9201
|
+
}
|
|
9202
|
+
/** A recording as a file named by the prefix and the local time, its extension by its mime. */
|
|
9203
|
+
static createRecordingFile(blob, prefix, fallbackMimeType) {
|
|
9204
|
+
const formattedDate = new Date().toLocaleString('sv-SE').replace(' ', '_').replace(/:/g, '-');
|
|
9205
|
+
const mimeType = blob.type || fallbackMimeType;
|
|
9206
|
+
const ext = mimeType.includes('webm') ? 'webm' : 'mp4';
|
|
9207
|
+
return new File([blob], `${prefix}_${formattedDate}.${ext}`, { type: mimeType });
|
|
9208
|
+
}
|
|
9209
|
+
/** The zone's texts: a descriptor's text per key, the client's default for a missing one. */
|
|
9210
|
+
static resolveTexts(texts) {
|
|
9211
|
+
return { ...UPLOAD_ZONE_DEFAULT_TEXTS, ...texts };
|
|
9212
|
+
}
|
|
9213
|
+
/** Fills the `{name}` placeholders of a text, which the server sends raw. */
|
|
9214
|
+
static fillPlaceholders(text, values) {
|
|
9215
|
+
return text.replace(/\{(\w+)\}/g, (placeholder, name) => name in values ? String(values[name]) : placeholder);
|
|
9216
|
+
}
|
|
9217
|
+
}
|
|
9218
|
+
/** The client's text of every key, shown where the descriptor's texts lack the key. */
|
|
9219
|
+
const UPLOAD_ZONE_DEFAULT_TEXTS = {
|
|
9220
|
+
addFile: 'Dokumentum hozzáadása',
|
|
9221
|
+
browseOrDrag: 'tallózás vagy behúzás',
|
|
9222
|
+
maxSize: 'max. {size} MB',
|
|
9223
|
+
upload: 'Feltöltés',
|
|
9224
|
+
unsupportedFormat: '"{fileName}" - nem támogatott formátum',
|
|
9225
|
+
fileTooLarge: '"{fileName}" - túl nagy fájl (max {size} MB)',
|
|
9226
|
+
capturePhoto: 'Fotó készítése',
|
|
9227
|
+
captureVideo: 'Videó készítése',
|
|
9228
|
+
recordAudio: 'Hangfelvétel',
|
|
9229
|
+
photo: 'fotó',
|
|
9230
|
+
video: 'videó',
|
|
9231
|
+
audio: 'hangfelvétel',
|
|
9232
|
+
or: 'vagy',
|
|
9233
|
+
captureOnlyHint: '{sources} készítése',
|
|
9234
|
+
takePicture: 'Kép készítése',
|
|
9235
|
+
cameraOn: 'Kamera bekapcsolása',
|
|
9236
|
+
cameraOff: 'Kamera kikapcsolása',
|
|
9237
|
+
switchOnCameraHint: 'Kapcsolja be a kamerát a kép készítéséhez!',
|
|
9238
|
+
switchOnForVideo: 'Kapcsolja be a kamerát a videó felvételéhez!',
|
|
9239
|
+
startRecording: 'Felvétel indítása',
|
|
9240
|
+
stopRecording: 'Felvétel leállítása',
|
|
9241
|
+
recording: 'Felvétel folyamatban…',
|
|
9242
|
+
pickVideo: 'Videó kiválasztása',
|
|
9243
|
+
cameraError: 'A kamera nem érhető el.',
|
|
9244
|
+
microphoneError: 'A mikrofon nem érhető el.',
|
|
9245
|
+
play: 'Lejátszás',
|
|
9246
|
+
photoDialogTitle: 'Fénykép készítése',
|
|
9247
|
+
videoDialogTitle: 'Videó felvétele',
|
|
9248
|
+
audioDialogTitle: 'Hangfelvétel',
|
|
9249
|
+
cancel: 'Mégse',
|
|
9250
|
+
retake: 'Újra',
|
|
9251
|
+
use: 'Használ',
|
|
9252
|
+
save: 'Mentés',
|
|
9253
|
+
capturedPhoto: 'Elkészült kép',
|
|
9254
|
+
capturedVideo: 'Felvétel',
|
|
9255
|
+
recordingHint: 'A lejátszás ikon visszajátssza, a mikrofon gomb új felvételt indít (felülírja ezt).',
|
|
9256
|
+
microphoneRetryHint: 'A mikrofon gomb újra kéri az engedélyt.',
|
|
9257
|
+
};
|
|
9061
9258
|
|
|
9062
9259
|
class SmartfileuploaderComponent {
|
|
9063
9260
|
constructor() {
|
|
9064
9261
|
this._snackBar = inject(MatSnackBar);
|
|
9262
|
+
this.changeDetectorRef = inject(ChangeDetectorRef);
|
|
9065
9263
|
this.files = [];
|
|
9066
9264
|
this.uploadCallback = input.required(/* @ts-ignore */
|
|
9067
9265
|
...(ngDevMode ? [{ debugName: "uploadCallback" }] : /* istanbul ignore next */ []));
|
|
@@ -9077,6 +9275,10 @@ class SmartfileuploaderComponent {
|
|
|
9077
9275
|
...(ngDevMode ? [{ debugName: "autoUpload" }] : /* istanbul ignore next */ []));
|
|
9078
9276
|
this.isDisabled = input(false, /* @ts-ignore */
|
|
9079
9277
|
...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
9278
|
+
/** False in a capture-only zone: no picker and drop zone, the list and the checks stay. The
|
|
9279
|
+
* upload button goes with the picker, so a host hiding it needs autoUpload. */
|
|
9280
|
+
this.pickerShown = input(true, /* @ts-ignore */
|
|
9281
|
+
...(ngDevMode ? [{ debugName: "pickerShown" }] : /* istanbul ignore next */ []));
|
|
9080
9282
|
this.isDragOver = false;
|
|
9081
9283
|
this.errors = [];
|
|
9082
9284
|
this.previewUrls = new Map();
|
|
@@ -9084,13 +9286,14 @@ class SmartfileuploaderComponent {
|
|
|
9084
9286
|
ngOnInit() {
|
|
9085
9287
|
const defaultI18n = {
|
|
9086
9288
|
addFile: 'dokumentum hozzáadása',
|
|
9087
|
-
browseOrDrag:
|
|
9289
|
+
browseOrDrag: UPLOAD_ZONE_DEFAULT_TEXTS.browseOrDrag,
|
|
9088
9290
|
maxSize: `max. ${this.maxSizeMb()} MB`,
|
|
9089
9291
|
formats: 'PDF, docx, xls formátum',
|
|
9090
|
-
upload:
|
|
9292
|
+
upload: UPLOAD_ZONE_DEFAULT_TEXTS.upload,
|
|
9091
9293
|
};
|
|
9092
9294
|
this.i18n = { ...defaultI18n, ...this.i18n };
|
|
9093
9295
|
}
|
|
9296
|
+
/** Checks the formats and the size of picked, dropped and captured files alike. */
|
|
9094
9297
|
processFiles(fileList) {
|
|
9095
9298
|
this.errors = [];
|
|
9096
9299
|
var validFiles = [];
|
|
@@ -9104,12 +9307,17 @@ class SmartfileuploaderComponent {
|
|
|
9104
9307
|
.map((f) => f.replace('.', '').trim().toLowerCase());
|
|
9105
9308
|
var isAllowed = trimmedFormats.includes(ext);
|
|
9106
9309
|
if (!isAllowed) {
|
|
9107
|
-
this.
|
|
9310
|
+
const message = this.i18n?.unsupportedFormat ?? UPLOAD_ZONE_DEFAULT_TEXTS.unsupportedFormat;
|
|
9311
|
+
this.errors.push(UploadWidgetUtils.fillPlaceholders(message, { fileName: file.name }));
|
|
9108
9312
|
hasError = true;
|
|
9109
9313
|
}
|
|
9110
9314
|
}
|
|
9111
9315
|
if (file.size > this.maxSizeMb() * 1024 * 1024) {
|
|
9112
|
-
|
|
9316
|
+
const message = this.i18n?.fileTooLarge ?? UPLOAD_ZONE_DEFAULT_TEXTS.fileTooLarge;
|
|
9317
|
+
this.errors.push(UploadWidgetUtils.fillPlaceholders(message, {
|
|
9318
|
+
fileName: file.name,
|
|
9319
|
+
size: this.maxSizeMb(),
|
|
9320
|
+
}));
|
|
9113
9321
|
hasError = true;
|
|
9114
9322
|
}
|
|
9115
9323
|
if (!hasError) {
|
|
@@ -9125,6 +9333,8 @@ class SmartfileuploaderComponent {
|
|
|
9125
9333
|
if (this.autoUpload() && this.errors.length == 0) {
|
|
9126
9334
|
this.uploadFile();
|
|
9127
9335
|
}
|
|
9336
|
+
// A Capture Dialog hands its file over outside any template event of this view.
|
|
9337
|
+
this.changeDetectorRef.markForCheck();
|
|
9128
9338
|
}
|
|
9129
9339
|
getFile(event) {
|
|
9130
9340
|
if (event.target.files && event.target.files.length) {
|
|
@@ -9134,6 +9344,9 @@ class SmartfileuploaderComponent {
|
|
|
9134
9344
|
}
|
|
9135
9345
|
}
|
|
9136
9346
|
onDrop(event) {
|
|
9347
|
+
if (!this.pickerShown()) {
|
|
9348
|
+
return;
|
|
9349
|
+
}
|
|
9137
9350
|
event.preventDefault();
|
|
9138
9351
|
this.isDragOver = false;
|
|
9139
9352
|
if (!event.dataTransfer?.files)
|
|
@@ -9142,6 +9355,9 @@ class SmartfileuploaderComponent {
|
|
|
9142
9355
|
this.processFiles(fileList);
|
|
9143
9356
|
}
|
|
9144
9357
|
onDragOver(event) {
|
|
9358
|
+
if (!this.pickerShown()) {
|
|
9359
|
+
return;
|
|
9360
|
+
}
|
|
9145
9361
|
event.preventDefault();
|
|
9146
9362
|
this.isDragOver = true;
|
|
9147
9363
|
}
|
|
@@ -9228,68 +9444,232 @@ class SmartfileuploaderComponent {
|
|
|
9228
9444
|
};
|
|
9229
9445
|
}
|
|
9230
9446
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartfileuploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9231
|
-
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 } }, 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>\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n
|
|
9447
|
+
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"] }] }); }
|
|
9232
9448
|
}
|
|
9233
9449
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartfileuploaderComponent, decorators: [{
|
|
9234
9450
|
type: Component,
|
|
9235
|
-
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>\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n
|
|
9451
|
+
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"] }]
|
|
9236
9452
|
}], propDecorators: { fileInput: [{
|
|
9237
9453
|
type: ViewChild,
|
|
9238
9454
|
args: ['fileInput']
|
|
9239
9455
|
}], uploadCallback: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadCallback", required: true }] }], fileFormats: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileFormats", required: false }] }], maxSizeMb: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSizeMb", required: false }] }], i18n: [{
|
|
9240
9456
|
type: Input
|
|
9241
|
-
}], 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 }] }] } });
|
|
9457
|
+
}], 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 }] }] } });
|
|
9242
9458
|
|
|
9243
9459
|
class PhotoCaptureWidgetComponent {
|
|
9244
|
-
get cameraActive() {
|
|
9245
|
-
return this.cameraActiveState();
|
|
9246
|
-
}
|
|
9247
|
-
set cameraActive(active) {
|
|
9248
|
-
this.cameraActiveState.set(active);
|
|
9249
|
-
}
|
|
9250
|
-
get isCaptured() {
|
|
9251
|
-
return this.isCapturedState();
|
|
9252
|
-
}
|
|
9253
|
-
set isCaptured(captured) {
|
|
9254
|
-
this.isCapturedState.set(captured);
|
|
9255
|
-
}
|
|
9256
9460
|
constructor() {
|
|
9461
|
+
this.injector = inject(Injector);
|
|
9462
|
+
/** The take, record, retake and use buttons, for a host that draws them itself. */
|
|
9463
|
+
this.actionButtonsTemplate = viewChild('actionButtons', /* @ts-ignore */
|
|
9464
|
+
...(ngDevMode ? [{ debugName: "actionButtonsTemplate" }] : /* istanbul ignore next */ []));
|
|
9257
9465
|
this.maxFileSize = input(/* @ts-ignore */
|
|
9258
9466
|
...(ngDevMode ? [undefined, { debugName: "maxFileSize" }] : /* istanbul ignore next */ []));
|
|
9467
|
+
/** PHOTO takes a picture; VIDEO records a video with the webcam or picks one on a desktop. */
|
|
9468
|
+
this.mode = input('PHOTO', /* @ts-ignore */
|
|
9469
|
+
...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
9470
|
+
/** True when the zone's capture row carries the button: the widget then opens only by
|
|
9471
|
+
* openCapture(). */
|
|
9472
|
+
this.externalTrigger = input(false, /* @ts-ignore */
|
|
9473
|
+
...(ngDevMode ? [{ debugName: "externalTrigger" }] : /* istanbul ignore next */ []));
|
|
9474
|
+
/** The zone's texts; the buttons follow them. */
|
|
9475
|
+
this.texts = input(UPLOAD_ZONE_DEFAULT_TEXTS, /* @ts-ignore */
|
|
9476
|
+
...(ngDevMode ? [{ debugName: "texts" }] : /* istanbul ignore next */ []));
|
|
9477
|
+
/** AUTO asks for the camera when the widget opens, MANUAL at the switch-on button. */
|
|
9478
|
+
this.deviceStart = input(CaptureDeviceStart.MANUAL, /* @ts-ignore */
|
|
9479
|
+
...(ngDevMode ? [{ debugName: "deviceStart" }] : /* istanbul ignore next */ []));
|
|
9480
|
+
/** CONFIRM keeps a capture for retake or use, IMMEDIATE hands it over at the take or the stop. */
|
|
9481
|
+
this.handover = input(CaptureHandover.IMMEDIATE, /* @ts-ignore */
|
|
9482
|
+
...(ngDevMode ? [{ debugName: "handover" }] : /* istanbul ignore next */ []));
|
|
9483
|
+
/** True in a host that draws actionButtonsTemplate: the widget then lays out as the Capture
|
|
9484
|
+
* Dialog's camera view, with the switch inside the view. */
|
|
9485
|
+
this.actionsInHost = input(false, /* @ts-ignore */
|
|
9486
|
+
...(ngDevMode ? [{ debugName: "actionsInHost" }] : /* istanbul ignore next */ []));
|
|
9259
9487
|
this.photoCaptured = output();
|
|
9488
|
+
/** The desktop webcam panel of an externally triggered photo widget, opened by openCapture(). */
|
|
9489
|
+
this.isPanelOpen = signal(false, /* @ts-ignore */
|
|
9490
|
+
...(ngDevMode ? [{ debugName: "isPanelOpen" }] : /* istanbul ignore next */ []));
|
|
9260
9491
|
/**
|
|
9261
9492
|
* Both are written after an `await` — the camera stream, the compression — so a signal
|
|
9262
9493
|
* each: the click that started it has long been checked by then.
|
|
9263
9494
|
*/
|
|
9264
9495
|
this.cameraActiveState = signal(false, /* @ts-ignore */
|
|
9265
9496
|
...(ngDevMode ? [{ debugName: "cameraActiveState" }] : /* istanbul ignore next */ []));
|
|
9497
|
+
/** Set when the browser gives no camera, shown in place of the switch-on hint. */
|
|
9498
|
+
this.cameraError = signal(false, /* @ts-ignore */
|
|
9499
|
+
...(ngDevMode ? [{ debugName: "cameraError" }] : /* istanbul ignore next */ []));
|
|
9500
|
+
/** True while a camera request runs: AUTO then shows neither the switch nor its hint. */
|
|
9501
|
+
this.isCameraStarting = signal(false, /* @ts-ignore */
|
|
9502
|
+
...(ngDevMode ? [{ debugName: "isCameraStarting" }] : /* istanbul ignore next */ []));
|
|
9503
|
+
this.isRecordingState = signal(false, /* @ts-ignore */
|
|
9504
|
+
...(ngDevMode ? [{ debugName: "isRecordingState" }] : /* istanbul ignore next */ []));
|
|
9266
9505
|
this.isCapturedState = signal(false, /* @ts-ignore */
|
|
9267
9506
|
...(ngDevMode ? [{ debugName: "isCapturedState" }] : /* istanbul ignore next */ []));
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
this.
|
|
9507
|
+
/** A capture kept for retake or use under the CONFIRM handover. */
|
|
9508
|
+
this.keptFiles = signal(undefined, /* @ts-ignore */
|
|
9509
|
+
...(ngDevMode ? [{ debugName: "keptFiles" }] : /* istanbul ignore next */ []));
|
|
9510
|
+
/** The playback address of a kept video. */
|
|
9511
|
+
this.keptVideoUrl = signal(undefined, /* @ts-ignore */
|
|
9512
|
+
...(ngDevMode ? [{ debugName: "keptVideoUrl" }] : /* istanbul ignore next */ []));
|
|
9513
|
+
/** The milliseconds of the running video, kept as the length of the stopped one. */
|
|
9514
|
+
this.recordingTime = signal(0, /* @ts-ignore */
|
|
9515
|
+
...(ngDevMode ? [{ debugName: "recordingTime" }] : /* istanbul ignore next */ []));
|
|
9516
|
+
/** The line on the stage of a running video: a timer in a host, the message on the panel. */
|
|
9517
|
+
this.recordingCaption = computed(() => this.actionsInHost()
|
|
9518
|
+
? UploadWidgetUtils.formatDuration(this.recordingTime())
|
|
9519
|
+
: this.texts().recording, /* @ts-ignore */
|
|
9520
|
+
...(ngDevMode ? [{ debugName: "recordingCaption" }] : /* istanbul ignore next */ []));
|
|
9521
|
+
/** The kept capture's line on the stage of a host; the panel has none. */
|
|
9522
|
+
this.capturedCaption = computed(() => {
|
|
9523
|
+
const files = this.keptFiles();
|
|
9524
|
+
if (!files || !this.actionsInHost()) {
|
|
9525
|
+
return undefined;
|
|
9526
|
+
}
|
|
9527
|
+
const size = UploadWidgetUtils.formatCapturedSize(files[0].size);
|
|
9528
|
+
if (this.mode() === 'PHOTO') {
|
|
9529
|
+
return `${this.texts().capturedPhoto} · ${size}`;
|
|
9530
|
+
}
|
|
9531
|
+
const duration = UploadWidgetUtils.formatDuration(this.recordingTime());
|
|
9532
|
+
return `${this.texts().capturedVideo} · ${duration} · ${size}`;
|
|
9533
|
+
}, /* @ts-ignore */
|
|
9534
|
+
...(ngDevMode ? [{ debugName: "capturedCaption" }] : /* istanbul ignore next */ []));
|
|
9535
|
+
/** True while a host may not lose the capture by accident: a device is asked for or runs, or
|
|
9536
|
+
* a capture waits for confirmation. */
|
|
9537
|
+
this.isCaptureUnderWay = computed(() => this.isCameraStarting() || this.cameraActive || this.isRecording || !!this.keptFiles(), /* @ts-ignore */
|
|
9538
|
+
...(ngDevMode ? [{ debugName: "isCaptureUnderWay" }] : /* istanbul ignore next */ []));
|
|
9539
|
+
/** Under AUTO and in a host the switch returns when the camera is off, so a second shot stays
|
|
9540
|
+
* possible. */
|
|
9541
|
+
this.isCameraSwitchShown = computed(() => !this.keptFiles() &&
|
|
9542
|
+
((this.deviceStart() === CaptureDeviceStart.MANUAL && !this.actionsInHost()) ||
|
|
9543
|
+
!(this.cameraActive || this.isCameraStarting())), /* @ts-ignore */
|
|
9544
|
+
...(ngDevMode ? [{ debugName: "isCameraSwitchShown" }] : /* istanbul ignore next */ []));
|
|
9545
|
+
this.isSwitchOnHintShown = computed(() => this.isCameraSwitchShown() && !this.cameraActive && !this.isCaptured, /* @ts-ignore */
|
|
9546
|
+
...(ngDevMode ? [{ debugName: "isSwitchOnHintShown" }] : /* istanbul ignore next */ []));
|
|
9547
|
+
this.recordingStartTime = 0;
|
|
9548
|
+
/** Advanced by stopCamera(), so a stream the browser gives after a close is stopped at once. */
|
|
9549
|
+
this.cameraRequest = 0;
|
|
9550
|
+
this.toggleCameraButton = computed(() => this.createButton(this.cameraActive ? this.texts().cameraOff : this.texts().cameraOn, 'power_settings_new', this.cameraActive ? 'red' : 'primary', this.getButtonType(UiActionButtonType.NORMAL)), /* @ts-ignore */
|
|
9551
|
+
...(ngDevMode ? [{ debugName: "toggleCameraButton" }] : /* istanbul ignore next */ []));
|
|
9552
|
+
this.captureButton = computed(() => this.createButton(this.texts().takePicture, 'camera'), /* @ts-ignore */
|
|
9553
|
+
...(ngDevMode ? [{ debugName: "captureButton" }] : /* istanbul ignore next */ []));
|
|
9554
|
+
this.startRecordingButton = computed(() => this.createButton(this.texts().startRecording, 'fiber_manual_record'), /* @ts-ignore */
|
|
9555
|
+
...(ngDevMode ? [{ debugName: "startRecordingButton" }] : /* istanbul ignore next */ []));
|
|
9556
|
+
this.stopRecordingButton = computed(() => this.createButton(this.texts().stopRecording, 'stop', this.actionsInHost() ? 'warn' : 'red'), /* @ts-ignore */
|
|
9557
|
+
...(ngDevMode ? [{ debugName: "stopRecordingButton" }] : /* istanbul ignore next */ []));
|
|
9558
|
+
this.pickVideoButton = computed(() => this.createButton(this.texts().pickVideo, 'video_library'), /* @ts-ignore */
|
|
9559
|
+
...(ngDevMode ? [{ debugName: "pickVideoButton" }] : /* istanbul ignore next */ []));
|
|
9560
|
+
this.retakeButton = computed(() => this.createButton(this.texts().retake, 'replay', 'primary', this.getButtonType(UiActionButtonType.NORMAL)), /* @ts-ignore */
|
|
9561
|
+
...(ngDevMode ? [{ debugName: "retakeButton" }] : /* istanbul ignore next */ []));
|
|
9562
|
+
this.useButton = computed(() => this.createButton(this.texts().use, 'check'), /* @ts-ignore */
|
|
9563
|
+
...(ngDevMode ? [{ debugName: "useButton" }] : /* istanbul ignore next */ []));
|
|
9564
|
+
}
|
|
9565
|
+
get cameraActive() {
|
|
9566
|
+
return this.cameraActiveState();
|
|
9567
|
+
}
|
|
9568
|
+
set cameraActive(active) {
|
|
9569
|
+
this.cameraActiveState.set(active);
|
|
9570
|
+
}
|
|
9571
|
+
get isRecording() {
|
|
9572
|
+
return this.isRecordingState();
|
|
9573
|
+
}
|
|
9574
|
+
set isRecording(recording) {
|
|
9575
|
+
this.isRecordingState.set(recording);
|
|
9576
|
+
}
|
|
9577
|
+
get isCaptured() {
|
|
9578
|
+
return this.isCapturedState();
|
|
9579
|
+
}
|
|
9580
|
+
set isCaptured(captured) {
|
|
9581
|
+
this.isCapturedState.set(captured);
|
|
9276
9582
|
}
|
|
9277
9583
|
isMobile() {
|
|
9278
9584
|
return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
|
9279
9585
|
}
|
|
9586
|
+
/** A phone opens its own camera, a desktop the webcam panel. */
|
|
9587
|
+
openCapture() {
|
|
9588
|
+
if (this.isMobile()) {
|
|
9589
|
+
this.fileInputRef.nativeElement.click();
|
|
9590
|
+
return;
|
|
9591
|
+
}
|
|
9592
|
+
if (this.isPanelOpen()) {
|
|
9593
|
+
this.stopCamera();
|
|
9594
|
+
this.discardKeptCapture();
|
|
9595
|
+
}
|
|
9596
|
+
else {
|
|
9597
|
+
this.startCameraAfterRender();
|
|
9598
|
+
}
|
|
9599
|
+
this.isPanelOpen.update((open) => !open);
|
|
9600
|
+
}
|
|
9601
|
+
ngAfterViewInit() {
|
|
9602
|
+
if (!this.externalTrigger()) {
|
|
9603
|
+
this.startCameraAfterRender();
|
|
9604
|
+
}
|
|
9605
|
+
}
|
|
9606
|
+
ngOnDestroy() {
|
|
9607
|
+
this.stopCamera();
|
|
9608
|
+
this.discardKeptCapture();
|
|
9609
|
+
}
|
|
9610
|
+
/** AUTO waits for the render: the stream goes into the panel's video element. */
|
|
9611
|
+
startCameraAfterRender() {
|
|
9612
|
+
if (this.deviceStart() !== CaptureDeviceStart.AUTO || this.isMobile()) {
|
|
9613
|
+
return;
|
|
9614
|
+
}
|
|
9615
|
+
afterNextRender(() => {
|
|
9616
|
+
if (!this.externalTrigger() || this.isPanelOpen()) {
|
|
9617
|
+
this.startCamera();
|
|
9618
|
+
}
|
|
9619
|
+
}, { injector: this.injector });
|
|
9620
|
+
}
|
|
9280
9621
|
async startCamera() {
|
|
9281
|
-
this.
|
|
9622
|
+
this.cameraError.set(false);
|
|
9623
|
+
this.isCameraStarting.set(true);
|
|
9624
|
+
const request = ++this.cameraRequest;
|
|
9625
|
+
let stream;
|
|
9626
|
+
try {
|
|
9627
|
+
stream = await this.openCameraStream();
|
|
9628
|
+
}
|
|
9629
|
+
catch {
|
|
9630
|
+
if (request === this.cameraRequest) {
|
|
9631
|
+
this.isCameraStarting.set(false);
|
|
9632
|
+
this.cameraError.set(true);
|
|
9633
|
+
}
|
|
9634
|
+
return;
|
|
9635
|
+
}
|
|
9636
|
+
if (request !== this.cameraRequest) {
|
|
9637
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
9638
|
+
return;
|
|
9639
|
+
}
|
|
9640
|
+
this.stream = stream;
|
|
9282
9641
|
this.videoRef.nativeElement.srcObject = this.stream;
|
|
9283
9642
|
this.videoRef.nativeElement.play();
|
|
9643
|
+
this.isCameraStarting.set(false);
|
|
9284
9644
|
this.cameraActive = true;
|
|
9285
9645
|
this.isCaptured = false;
|
|
9286
|
-
this.updateToggleButton();
|
|
9287
9646
|
}
|
|
9647
|
+
/** A video asks for the sound too; a desktop without a microphone records a silent one. */
|
|
9648
|
+
async openCameraStream() {
|
|
9649
|
+
if (this.mode() === 'VIDEO') {
|
|
9650
|
+
try {
|
|
9651
|
+
return await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
|
|
9652
|
+
}
|
|
9653
|
+
catch {
|
|
9654
|
+
// No microphone answers: the camera alone is asked below.
|
|
9655
|
+
}
|
|
9656
|
+
}
|
|
9657
|
+
return navigator.mediaDevices.getUserMedia({ video: true });
|
|
9658
|
+
}
|
|
9659
|
+
/** Closing the camera mid-recording discards the recording. */
|
|
9288
9660
|
stopCamera() {
|
|
9661
|
+
this.cameraRequest++;
|
|
9662
|
+
this.isCameraStarting.set(false);
|
|
9663
|
+
clearInterval(this.recordingClock);
|
|
9664
|
+
if (this.recorder) {
|
|
9665
|
+
this.recorder.onstop = null;
|
|
9666
|
+
this.recorder.stop();
|
|
9667
|
+
this.recorder = undefined;
|
|
9668
|
+
}
|
|
9669
|
+
this.isRecording = false;
|
|
9289
9670
|
this.stream?.getTracks().forEach((track) => track.stop());
|
|
9290
9671
|
this.stream = undefined;
|
|
9291
9672
|
this.cameraActive = false;
|
|
9292
|
-
this.updateToggleButton();
|
|
9293
9673
|
}
|
|
9294
9674
|
async capturePhoto() {
|
|
9295
9675
|
const video = this.videoRef.nativeElement;
|
|
@@ -9308,10 +9688,74 @@ class PhotoCaptureWidgetComponent {
|
|
|
9308
9688
|
if (maxFileSize && file.size > maxFileSize) {
|
|
9309
9689
|
file = await this.compressImageFile(file, maxFileSize);
|
|
9310
9690
|
}
|
|
9311
|
-
this.
|
|
9691
|
+
this.handOverCapture([file]);
|
|
9312
9692
|
this.isCaptured = true;
|
|
9313
9693
|
this.stopCamera();
|
|
9314
9694
|
}
|
|
9695
|
+
/** A browser that records neither webm nor mp4 refuses the recorder: the camera error shows. */
|
|
9696
|
+
startRecording() {
|
|
9697
|
+
let recorder;
|
|
9698
|
+
try {
|
|
9699
|
+
recorder = new MediaRecorder(this.stream, {
|
|
9700
|
+
mimeType: UploadWidgetUtils.chooseRecorderMimeType('video'),
|
|
9701
|
+
});
|
|
9702
|
+
}
|
|
9703
|
+
catch {
|
|
9704
|
+
this.cameraError.set(true);
|
|
9705
|
+
return;
|
|
9706
|
+
}
|
|
9707
|
+
const chunks = [];
|
|
9708
|
+
recorder.ondataavailable = (event) => chunks.push(event.data);
|
|
9709
|
+
recorder.onstop = () => {
|
|
9710
|
+
const blob = new Blob(chunks, { type: recorder.mimeType });
|
|
9711
|
+
this.recorder = undefined;
|
|
9712
|
+
this.updateRecordingTime();
|
|
9713
|
+
this.handOverCapture([UploadWidgetUtils.createRecordingFile(blob, 'video', 'video/mp4')]);
|
|
9714
|
+
this.stopCamera();
|
|
9715
|
+
};
|
|
9716
|
+
recorder.start();
|
|
9717
|
+
this.recorder = recorder;
|
|
9718
|
+
this.isRecording = true;
|
|
9719
|
+
this.recordingStartTime = Date.now();
|
|
9720
|
+
this.recordingTime.set(0);
|
|
9721
|
+
this.recordingClock = setInterval(() => this.updateRecordingTime(), 1000);
|
|
9722
|
+
}
|
|
9723
|
+
updateRecordingTime() {
|
|
9724
|
+
this.recordingTime.set(Date.now() - this.recordingStartTime);
|
|
9725
|
+
}
|
|
9726
|
+
/** The recording state holds until onstop hands the video over and stops the camera. */
|
|
9727
|
+
stopRecording() {
|
|
9728
|
+
this.recorder?.stop();
|
|
9729
|
+
}
|
|
9730
|
+
handOverCapture(files) {
|
|
9731
|
+
if (this.handover() === CaptureHandover.IMMEDIATE) {
|
|
9732
|
+
this.photoCaptured.emit(files);
|
|
9733
|
+
return;
|
|
9734
|
+
}
|
|
9735
|
+
this.keptFiles.set(files);
|
|
9736
|
+
if (this.mode() === 'VIDEO') {
|
|
9737
|
+
this.keptVideoUrl.set(URL.createObjectURL(files[0]));
|
|
9738
|
+
}
|
|
9739
|
+
}
|
|
9740
|
+
retakeCapture() {
|
|
9741
|
+
this.discardKeptCapture();
|
|
9742
|
+
if (this.deviceStart() === CaptureDeviceStart.AUTO) {
|
|
9743
|
+
this.startCamera();
|
|
9744
|
+
}
|
|
9745
|
+
}
|
|
9746
|
+
useCapture() {
|
|
9747
|
+
this.photoCaptured.emit(this.keptFiles());
|
|
9748
|
+
this.discardKeptCapture();
|
|
9749
|
+
}
|
|
9750
|
+
discardKeptCapture() {
|
|
9751
|
+
const videoUrl = this.keptVideoUrl();
|
|
9752
|
+
if (videoUrl) {
|
|
9753
|
+
URL.revokeObjectURL(videoUrl);
|
|
9754
|
+
}
|
|
9755
|
+
this.keptVideoUrl.set(undefined);
|
|
9756
|
+
this.keptFiles.set(undefined);
|
|
9757
|
+
this.isCaptured = false;
|
|
9758
|
+
}
|
|
9315
9759
|
getPhotoDataURL() {
|
|
9316
9760
|
return this.canvasRef.nativeElement.toDataURL('image/png');
|
|
9317
9761
|
}
|
|
@@ -9346,26 +9790,6 @@ class PhotoCaptureWidgetComponent {
|
|
|
9346
9790
|
this.startCamera();
|
|
9347
9791
|
}
|
|
9348
9792
|
}
|
|
9349
|
-
updateToggleButton() {
|
|
9350
|
-
let icon = 'power_settings_new';
|
|
9351
|
-
let title;
|
|
9352
|
-
let color;
|
|
9353
|
-
if (this.cameraActive) {
|
|
9354
|
-
title = 'Kamera kikapcsolása';
|
|
9355
|
-
color = 'red';
|
|
9356
|
-
}
|
|
9357
|
-
else {
|
|
9358
|
-
title = 'Kamera bekapcsolása';
|
|
9359
|
-
color = 'primary';
|
|
9360
|
-
}
|
|
9361
|
-
this.toggleCameraButton = {
|
|
9362
|
-
type: UiActionButtonType.RAISED,
|
|
9363
|
-
title: title,
|
|
9364
|
-
icon: icon,
|
|
9365
|
-
color,
|
|
9366
|
-
iconPosition: IconPosition.PRE,
|
|
9367
|
-
};
|
|
9368
|
-
}
|
|
9369
9793
|
async compressImageFile(file, maxFileSize) {
|
|
9370
9794
|
if (!file.type.startsWith('image/'))
|
|
9371
9795
|
return file;
|
|
@@ -9423,13 +9847,20 @@ class PhotoCaptureWidgetComponent {
|
|
|
9423
9847
|
return file;
|
|
9424
9848
|
}
|
|
9425
9849
|
}
|
|
9850
|
+
/** The inline bar's buttons are all raised; a host's view tells them apart by type. */
|
|
9851
|
+
getButtonType(typeInHost) {
|
|
9852
|
+
return this.actionsInHost() ? typeInHost : UiActionButtonType.RAISED;
|
|
9853
|
+
}
|
|
9854
|
+
createButton(title, icon, color = 'primary', type = UiActionButtonType.RAISED) {
|
|
9855
|
+
return { type, title, icon, iconPosition: IconPosition.PRE, color };
|
|
9856
|
+
}
|
|
9426
9857
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PhotoCaptureWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9427
|
-
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 } }, 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: "<div class=\"photoWidgetContainer\">\n @if (!
|
|
9858
|
+
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 }, actionsInHost: { classPropertyName: "actionsInHost", publicName: "actionsInHost", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { photoCaptured: "photoCaptured" }, viewQueries: [{ propertyName: "actionButtonsTemplate", first: true, predicate: ["actionButtons"], descendants: true, isSignal: true }, { 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\" [class.actionsInHost]=\"actionsInHost()\">\n <div class=\"cameraStage\">\n <video #video autoplay [muted]=\"true\" [hidden]=\"isCaptured || !cameraActive\"></video>\n <canvas #canvas [hidden]=\"!isCaptured || cameraActive\"></canvas>\n @if (keptVideoUrl(); as videoUrl) {\n <video controls [src]=\"videoUrl\"></video>\n }\n @if (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 (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 (actionsInHost()) {\n <ng-container [ngTemplateOutlet]=\"cameraSwitch\" />\n }\n </div>\n @if (!actionsInHost()) {\n <div class=\"controls\">\n <ng-container [ngTemplateOutlet]=\"cameraSwitch\" />\n <ng-container [ngTemplateOutlet]=\"actionButtons\" />\n @if (mode() === 'VIDEO' && !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]=\"isRecording\"\n [descriptor]=\"pickVideoButton()\"\n (actionClick)=\"fileInput.click()\"\n >\n </ui-action-button>\n }\n </div>\n }\n </div>\n}\n\n<ng-template #cameraSwitch>\n @if (isCameraSwitchShown()) {\n <!-- START CAMERA -->\n <ui-action-button\n class=\"toggleCameraButton\"\n [disabled]=\"isRecording\"\n [descriptor]=\"toggleCameraButton()\"\n (actionClick)=\"toggleCamera()\"\n >\n </ui-action-button>\n }\n</ng-template>\n\n<ng-template #actionButtons>\n @if (keptFiles()) {\n <ui-action-button\n class=\"retakeButton\"\n [descriptor]=\"retakeButton()\"\n (actionClick)=\"retakeCapture()\"\n >\n </ui-action-button>\n <ui-action-button\n class=\"useButton\"\n [descriptor]=\"useButton()\"\n (actionClick)=\"useCapture()\"\n >\n </ui-action-button>\n } @else if (mode() === 'VIDEO') {\n <!-- RECORD / STOP VIDEO -->\n @if (isRecording) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"stopRecording()\"\n >\n </ui-action-button>\n } @else {\n <ui-action-button\n class=\"startRecordingButton\"\n [disabled]=\"!cameraActive\"\n [descriptor]=\"startRecordingButton()\"\n (actionClick)=\"startRecording()\"\n >\n </ui-action-button>\n }\n } @else {\n <!-- CAPTURE CAMERA -->\n <ui-action-button\n class=\"capturePhotoButton\"\n [disabled]=\"!cameraActive\"\n [descriptor]=\"captureButton()\"\n (actionClick)=\"capturePhoto()\"\n >\n </ui-action-button>\n }\n</ng-template>\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}.cameraStage{display:contents}.actionsInHost{border:unset}.actionsInHost .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))}.actionsInHost .cameraStage video,.actionsInHost .cameraStage canvas{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;border-radius:unset}.actionsInHost .toggleCameraButton ::ng-deep button{background:var(--color-surface)}.actionsInHost .message{margin:0;color:inherit}.actionsInHost .cameraErrorMessage{position:relative;color:color-mix(in srgb,var(--warn-color) 65%,white)}.actionsInHost .recordingMessage,.actionsInHost .capturedCaption{position:absolute;left:.5rem;top:.5rem;padding:.125rem .5rem;border-radius:.5rem;font-size:.75rem;font-weight:500}.actionsInHost .recordingMessage{background:color-mix(in srgb,var(--stage-background) 67%,transparent);font-family:monospace}.actionsInHost .capturedCaption{background:color-mix(in srgb,var(--success-color) 50%,black)}.actionsInHost .recordingMessage:before{content:\"\";display:inline-block;width:.5rem;height:.5rem;margin-right:.3125rem;border-radius:50%;background:var(--warn-color)}\n"], dependencies: [{ 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"] }] }); }
|
|
9428
9859
|
}
|
|
9429
9860
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PhotoCaptureWidgetComponent, decorators: [{
|
|
9430
9861
|
type: Component,
|
|
9431
|
-
args: [{ selector: 'photo-capture-widget', imports: [UiActionButtonComponent], template: "<div class=\"photoWidgetContainer\">\n @if (!
|
|
9432
|
-
}],
|
|
9862
|
+
args: [{ selector: 'photo-capture-widget', imports: [NgTemplateOutlet, 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\" [class.actionsInHost]=\"actionsInHost()\">\n <div class=\"cameraStage\">\n <video #video autoplay [muted]=\"true\" [hidden]=\"isCaptured || !cameraActive\"></video>\n <canvas #canvas [hidden]=\"!isCaptured || cameraActive\"></canvas>\n @if (keptVideoUrl(); as videoUrl) {\n <video controls [src]=\"videoUrl\"></video>\n }\n @if (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 (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 (actionsInHost()) {\n <ng-container [ngTemplateOutlet]=\"cameraSwitch\" />\n }\n </div>\n @if (!actionsInHost()) {\n <div class=\"controls\">\n <ng-container [ngTemplateOutlet]=\"cameraSwitch\" />\n <ng-container [ngTemplateOutlet]=\"actionButtons\" />\n @if (mode() === 'VIDEO' && !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]=\"isRecording\"\n [descriptor]=\"pickVideoButton()\"\n (actionClick)=\"fileInput.click()\"\n >\n </ui-action-button>\n }\n </div>\n }\n </div>\n}\n\n<ng-template #cameraSwitch>\n @if (isCameraSwitchShown()) {\n <!-- START CAMERA -->\n <ui-action-button\n class=\"toggleCameraButton\"\n [disabled]=\"isRecording\"\n [descriptor]=\"toggleCameraButton()\"\n (actionClick)=\"toggleCamera()\"\n >\n </ui-action-button>\n }\n</ng-template>\n\n<ng-template #actionButtons>\n @if (keptFiles()) {\n <ui-action-button\n class=\"retakeButton\"\n [descriptor]=\"retakeButton()\"\n (actionClick)=\"retakeCapture()\"\n >\n </ui-action-button>\n <ui-action-button\n class=\"useButton\"\n [descriptor]=\"useButton()\"\n (actionClick)=\"useCapture()\"\n >\n </ui-action-button>\n } @else if (mode() === 'VIDEO') {\n <!-- RECORD / STOP VIDEO -->\n @if (isRecording) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"stopRecording()\"\n >\n </ui-action-button>\n } @else {\n <ui-action-button\n class=\"startRecordingButton\"\n [disabled]=\"!cameraActive\"\n [descriptor]=\"startRecordingButton()\"\n (actionClick)=\"startRecording()\"\n >\n </ui-action-button>\n }\n } @else {\n <!-- CAPTURE CAMERA -->\n <ui-action-button\n class=\"capturePhotoButton\"\n [disabled]=\"!cameraActive\"\n [descriptor]=\"captureButton()\"\n (actionClick)=\"capturePhoto()\"\n >\n </ui-action-button>\n }\n</ng-template>\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}.cameraStage{display:contents}.actionsInHost{border:unset}.actionsInHost .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))}.actionsInHost .cameraStage video,.actionsInHost .cameraStage canvas{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;border-radius:unset}.actionsInHost .toggleCameraButton ::ng-deep button{background:var(--color-surface)}.actionsInHost .message{margin:0;color:inherit}.actionsInHost .cameraErrorMessage{position:relative;color:color-mix(in srgb,var(--warn-color) 65%,white)}.actionsInHost .recordingMessage,.actionsInHost .capturedCaption{position:absolute;left:.5rem;top:.5rem;padding:.125rem .5rem;border-radius:.5rem;font-size:.75rem;font-weight:500}.actionsInHost .recordingMessage{background:color-mix(in srgb,var(--stage-background) 67%,transparent);font-family:monospace}.actionsInHost .capturedCaption{background:color-mix(in srgb,var(--success-color) 50%,black)}.actionsInHost .recordingMessage:before{content:\"\";display:inline-block;width:.5rem;height:.5rem;margin-right:.3125rem;border-radius:50%;background:var(--warn-color)}\n"] }]
|
|
9863
|
+
}], propDecorators: { videoRef: [{
|
|
9433
9864
|
type: ViewChild,
|
|
9434
9865
|
args: ['video']
|
|
9435
9866
|
}], canvasRef: [{
|
|
@@ -9438,20 +9869,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
9438
9869
|
}], fileInputRef: [{
|
|
9439
9870
|
type: ViewChild,
|
|
9440
9871
|
args: ['fileInput']
|
|
9441
|
-
}], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], photoCaptured: [{ type: i0.Output, args: ["photoCaptured"] }] } });
|
|
9872
|
+
}], actionButtonsTemplate: [{ type: i0.ViewChild, args: ['actionButtons', { isSignal: true }] }], 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 }] }], actionsInHost: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionsInHost", required: false }] }], photoCaptured: [{ type: i0.Output, args: ["photoCaptured"] }] } });
|
|
9442
9873
|
|
|
9443
9874
|
class VoiceRecordWidgetComponent {
|
|
9444
9875
|
constructor() {
|
|
9445
9876
|
this.ngZone = inject(NgZone);
|
|
9446
9877
|
this.changeDetectorRef = inject(ChangeDetectorRef);
|
|
9878
|
+
/** The stop and save buttons, for a host that draws them itself. */
|
|
9879
|
+
this.actionButtonsTemplate = viewChild('actionButtons', /* @ts-ignore */
|
|
9880
|
+
...(ngDevMode ? [{ debugName: "actionButtonsTemplate" }] : /* istanbul ignore next */ []));
|
|
9447
9881
|
this.openDirection = input('RIGHT', /* @ts-ignore */
|
|
9448
9882
|
...(ngDevMode ? [{ debugName: "openDirection" }] : /* istanbul ignore next */ []));
|
|
9883
|
+
/** The zone's texts, the microphone error line's among them. */
|
|
9884
|
+
this.texts = input(UPLOAD_ZONE_DEFAULT_TEXTS, /* @ts-ignore */
|
|
9885
|
+
...(ngDevMode ? [{ debugName: "texts" }] : /* istanbul ignore next */ []));
|
|
9886
|
+
/** True in the upload zone only; the deprecated recordingUploader shows no error line. */
|
|
9887
|
+
this.showMicrophoneError = input(false, /* @ts-ignore */
|
|
9888
|
+
...(ngDevMode ? [{ debugName: "showMicrophoneError" }] : /* istanbul ignore next */ []));
|
|
9889
|
+
/** AUTO starts the recording when the widget opens, MANUAL at the microphone button. */
|
|
9890
|
+
this.deviceStart = input(CaptureDeviceStart.MANUAL, /* @ts-ignore */
|
|
9891
|
+
...(ngDevMode ? [{ debugName: "deviceStart" }] : /* istanbul ignore next */ []));
|
|
9892
|
+
/** CONFIRM keeps a stopped recording for playback and save, IMMEDIATE hands it over. */
|
|
9893
|
+
this.handover = input(CaptureHandover.CONFIRM, /* @ts-ignore */
|
|
9894
|
+
...(ngDevMode ? [{ debugName: "handover" }] : /* istanbul ignore next */ []));
|
|
9895
|
+
/** True under the zone's Capture Settings; everywhere else the save stays an icon. */
|
|
9896
|
+
this.showSaveTitle = input(false, /* @ts-ignore */
|
|
9897
|
+
...(ngDevMode ? [{ debugName: "showSaveTitle" }] : /* istanbul ignore next */ []));
|
|
9898
|
+
/** True in a host that draws actionButtonsTemplate: the strip keeps no save of its own. */
|
|
9899
|
+
this.actionsInHost = input(false, /* @ts-ignore */
|
|
9900
|
+
...(ngDevMode ? [{ debugName: "actionsInHost" }] : /* istanbul ignore next */ []));
|
|
9449
9901
|
this.recordingSaved = output();
|
|
9450
|
-
|
|
9451
|
-
this.
|
|
9902
|
+
/** Signals, as the host reads them through isCaptureUnderWay and actionButtonsTemplate. */
|
|
9903
|
+
this.isRecording = signal(false, /* @ts-ignore */
|
|
9904
|
+
...(ngDevMode ? [{ debugName: "isRecording" }] : /* istanbul ignore next */ []));
|
|
9905
|
+
this.recordedBlob = signal(null, /* @ts-ignore */
|
|
9906
|
+
...(ngDevMode ? [{ debugName: "recordedBlob" }] : /* istanbul ignore next */ []));
|
|
9907
|
+
/** True while a host may not lose the capture by accident: the microphone is asked for, the
|
|
9908
|
+
* recording runs or waits for the save. */
|
|
9909
|
+
this.isCaptureUnderWay = computed(() => this.isRecording() || !!this.recordedBlob(), /* @ts-ignore */
|
|
9910
|
+
...(ngDevMode ? [{ debugName: "isCaptureUnderWay" }] : /* istanbul ignore next */ []));
|
|
9452
9911
|
this.time = '00:00';
|
|
9453
9912
|
this.isPlaying = false;
|
|
9454
9913
|
this.isWaveformOpen = false;
|
|
9914
|
+
/** Set when the browser gives no microphone, until the next start. */
|
|
9915
|
+
this.microphoneError = signal(false, /* @ts-ignore */
|
|
9916
|
+
...(ngDevMode ? [{ debugName: "microphoneError" }] : /* istanbul ignore next */ []));
|
|
9455
9917
|
this.micButton = {
|
|
9456
9918
|
title: '',
|
|
9457
9919
|
type: UiActionButtonType.ICON,
|
|
@@ -9467,6 +9929,9 @@ class VoiceRecordWidgetComponent {
|
|
|
9467
9929
|
iconColor: 'red',
|
|
9468
9930
|
color: 'primary',
|
|
9469
9931
|
};
|
|
9932
|
+
/** Only a theme colour's name paints the icon: the deprecated recorder keeps its look. */
|
|
9933
|
+
this.effectiveStopButton = computed(() => this.actionsInHost() ? { ...this.stopButton, color: 'warn', iconColor: 'warn' } : this.stopButton, /* @ts-ignore */
|
|
9934
|
+
...(ngDevMode ? [{ debugName: "effectiveStopButton" }] : /* istanbul ignore next */ []));
|
|
9470
9935
|
this.saveButton = {
|
|
9471
9936
|
title: '',
|
|
9472
9937
|
type: UiActionButtonType.ICON,
|
|
@@ -9474,6 +9939,18 @@ class VoiceRecordWidgetComponent {
|
|
|
9474
9939
|
iconPosition: IconPosition.PRE,
|
|
9475
9940
|
color: 'primary',
|
|
9476
9941
|
};
|
|
9942
|
+
this.effectiveSaveButton = computed(() => this.showSaveTitle()
|
|
9943
|
+
? { ...this.saveButton, title: this.texts().save, type: UiActionButtonType.RAISED }
|
|
9944
|
+
: this.saveButton, /* @ts-ignore */
|
|
9945
|
+
...(ngDevMode ? [{ debugName: "effectiveSaveButton" }] : /* istanbul ignore next */ []));
|
|
9946
|
+
this.stopRecordingButton = computed(() => ({
|
|
9947
|
+
title: this.texts().stopRecording,
|
|
9948
|
+
type: UiActionButtonType.RAISED,
|
|
9949
|
+
icon: 'stop',
|
|
9950
|
+
iconPosition: IconPosition.PRE,
|
|
9951
|
+
color: 'warn',
|
|
9952
|
+
}), /* @ts-ignore */
|
|
9953
|
+
...(ngDevMode ? [{ debugName: "stopRecordingButton" }] : /* istanbul ignore next */ []));
|
|
9477
9954
|
}
|
|
9478
9955
|
ngOnInit() {
|
|
9479
9956
|
this.playButton = {
|
|
@@ -9488,10 +9965,14 @@ class VoiceRecordWidgetComponent {
|
|
|
9488
9965
|
type: UiActionButtonType.ICON,
|
|
9489
9966
|
icon: 'stop_circle',
|
|
9490
9967
|
iconPosition: IconPosition.PRE,
|
|
9491
|
-
color: 'red',
|
|
9968
|
+
color: this.actionsInHost() ? 'warn' : 'red',
|
|
9492
9969
|
};
|
|
9493
9970
|
}
|
|
9494
|
-
ngAfterViewInit() {
|
|
9971
|
+
ngAfterViewInit() {
|
|
9972
|
+
if (this.deviceStart() === CaptureDeviceStart.AUTO) {
|
|
9973
|
+
this.requestRecording();
|
|
9974
|
+
}
|
|
9975
|
+
}
|
|
9495
9976
|
get isLeftDirection() {
|
|
9496
9977
|
return this.openDirection() === 'LEFT';
|
|
9497
9978
|
}
|
|
@@ -9500,12 +9981,16 @@ class VoiceRecordWidgetComponent {
|
|
|
9500
9981
|
return;
|
|
9501
9982
|
}
|
|
9502
9983
|
await new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
9984
|
+
if (!this.pendingStart) {
|
|
9985
|
+
return;
|
|
9986
|
+
}
|
|
9503
9987
|
this.waveformRef.nativeElement.style.padding = '0.1rem';
|
|
9504
9988
|
this.initWaveSurfer();
|
|
9505
9989
|
}
|
|
9506
9990
|
initWaveSurfer() {
|
|
9507
9991
|
let body = document.querySelector('body');
|
|
9508
|
-
|
|
9992
|
+
// A host theme sets the token on the body, which the element inherits and html does not see.
|
|
9993
|
+
let primaryColor = getComputedStyle(this.actionsInHost() ? this.waveformRef.nativeElement : document.documentElement)
|
|
9509
9994
|
.getPropertyValue('--primary-color')
|
|
9510
9995
|
.trim();
|
|
9511
9996
|
let el = this.mainButton.nativeElement.querySelector('.sb4-primary');
|
|
@@ -9521,15 +10006,15 @@ class VoiceRecordWidgetComponent {
|
|
|
9521
10006
|
barRadius: 5,
|
|
9522
10007
|
});
|
|
9523
10008
|
this.wavesurfer.setVolume(1);
|
|
9524
|
-
|
|
9525
|
-
const
|
|
9526
|
-
this.record = this.wavesurfer.registerPlugin(RecordPlugin.create({
|
|
10009
|
+
const mimeType = UploadWidgetUtils.chooseRecorderMimeType('audio');
|
|
10010
|
+
const record = this.wavesurfer.registerPlugin(RecordPlugin.create({
|
|
9527
10011
|
renderRecordedAudio: true,
|
|
9528
10012
|
scrollingWaveform: true,
|
|
9529
10013
|
continuousWaveform: false,
|
|
9530
10014
|
continuousWaveformDuration: 15,
|
|
9531
10015
|
mimeType,
|
|
9532
10016
|
}));
|
|
10017
|
+
this.record = record;
|
|
9533
10018
|
this.wavesurfer.on('dblclick', () => {
|
|
9534
10019
|
this.updateViewState(() => {
|
|
9535
10020
|
this.wavesurfer?.play();
|
|
@@ -9537,10 +10022,16 @@ class VoiceRecordWidgetComponent {
|
|
|
9537
10022
|
});
|
|
9538
10023
|
});
|
|
9539
10024
|
this.record.on('record-end', (blob) => {
|
|
10025
|
+
if (record !== this.record) {
|
|
10026
|
+
return;
|
|
10027
|
+
}
|
|
9540
10028
|
this.updateViewState(() => {
|
|
9541
|
-
this.recordedBlob
|
|
9542
|
-
this.isRecording
|
|
10029
|
+
this.recordedBlob.set(blob);
|
|
10030
|
+
this.isRecording.set(false);
|
|
9543
10031
|
});
|
|
10032
|
+
if (this.handover() === CaptureHandover.IMMEDIATE) {
|
|
10033
|
+
this.saveRecording();
|
|
10034
|
+
}
|
|
9544
10035
|
});
|
|
9545
10036
|
this.record.on('record-progress', (time) => {
|
|
9546
10037
|
this.updateViewState(() => {
|
|
@@ -9553,32 +10044,57 @@ class VoiceRecordWidgetComponent {
|
|
|
9553
10044
|
});
|
|
9554
10045
|
});
|
|
9555
10046
|
}
|
|
10047
|
+
/** Starts from a click or on open: a refused microphone shows its error line, no rejection. */
|
|
10048
|
+
requestRecording() {
|
|
10049
|
+
this.startRecording().catch(() => { });
|
|
10050
|
+
}
|
|
9556
10051
|
async startRecording() {
|
|
9557
10052
|
const confirmed = this.checkHasRecorded();
|
|
9558
|
-
if (!confirmed || this.isRecording)
|
|
10053
|
+
if (!confirmed || this.isRecording())
|
|
9559
10054
|
return;
|
|
10055
|
+
const start = {};
|
|
10056
|
+
this.pendingStart = start;
|
|
9560
10057
|
// Initialize first (still within the gesture tick on most browsers)
|
|
9561
10058
|
await this.initializeWaveSurferIfNeeded();
|
|
9562
|
-
if (
|
|
10059
|
+
if (this.pendingStart !== start) {
|
|
10060
|
+
return;
|
|
10061
|
+
}
|
|
10062
|
+
const record = this.record;
|
|
10063
|
+
if (!record)
|
|
9563
10064
|
throw new Error('Voice recorder plugin is not initialized.');
|
|
9564
10065
|
this.updateViewState(() => {
|
|
10066
|
+
this.microphoneError.set(false);
|
|
9565
10067
|
this.isWaveformOpen = true;
|
|
9566
|
-
this.isRecording
|
|
10068
|
+
this.isRecording.set(true);
|
|
9567
10069
|
this.isPlaying = false;
|
|
9568
|
-
this.recordedBlob
|
|
10070
|
+
this.recordedBlob.set(null);
|
|
9569
10071
|
this.time = '00:00';
|
|
9570
10072
|
});
|
|
9571
10073
|
try {
|
|
9572
10074
|
// Force native mic permission first so it works with WKWebView
|
|
9573
10075
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
9574
10076
|
stream.getTracks().forEach((track) => track.stop());
|
|
9575
|
-
|
|
10077
|
+
if (this.pendingStart !== start) {
|
|
10078
|
+
return;
|
|
10079
|
+
}
|
|
10080
|
+
await record.startRecording();
|
|
10081
|
+
if (this.pendingStart !== start) {
|
|
10082
|
+
record.stopRecording();
|
|
10083
|
+
record.stopMic();
|
|
10084
|
+
return;
|
|
10085
|
+
}
|
|
10086
|
+
this.pendingStart = undefined;
|
|
9576
10087
|
}
|
|
9577
10088
|
catch (error) {
|
|
10089
|
+
if (this.pendingStart !== start) {
|
|
10090
|
+
return;
|
|
10091
|
+
}
|
|
9578
10092
|
console.error('Mic failed', error);
|
|
10093
|
+
this.pendingStart = undefined;
|
|
9579
10094
|
this.updateViewState(() => {
|
|
9580
|
-
this.isRecording
|
|
9581
|
-
this.isWaveformOpen = !!this.recordedBlob;
|
|
10095
|
+
this.isRecording.set(false);
|
|
10096
|
+
this.isWaveformOpen = !!this.recordedBlob();
|
|
10097
|
+
this.microphoneError.set(true);
|
|
9582
10098
|
});
|
|
9583
10099
|
throw error;
|
|
9584
10100
|
}
|
|
@@ -9589,40 +10105,53 @@ class VoiceRecordWidgetComponent {
|
|
|
9589
10105
|
* Kept, rather than inlined away, so the place stays visible; see A37.
|
|
9590
10106
|
*/
|
|
9591
10107
|
checkHasRecorded() {
|
|
9592
|
-
if (this.recordedBlob) {
|
|
10108
|
+
if (this.recordedBlob()) {
|
|
9593
10109
|
}
|
|
9594
10110
|
return true;
|
|
9595
10111
|
}
|
|
9596
10112
|
updateProgress(time) {
|
|
9597
|
-
|
|
9598
|
-
.map((v) => (v < 10 ? '0' + v : v))
|
|
9599
|
-
.join(':');
|
|
9600
|
-
this.time = formattedTime;
|
|
10113
|
+
this.time = UploadWidgetUtils.formatDuration(time);
|
|
9601
10114
|
}
|
|
9602
10115
|
stopRecording() {
|
|
9603
|
-
if (this.
|
|
10116
|
+
if (this.pendingStart) {
|
|
10117
|
+
this.cancelPendingStart();
|
|
10118
|
+
}
|
|
10119
|
+
else if (this.record && this.isRecording()) {
|
|
9604
10120
|
this.record.stopRecording();
|
|
9605
10121
|
this.updateViewState(() => {
|
|
9606
|
-
this.isRecording
|
|
10122
|
+
this.isRecording.set(false);
|
|
9607
10123
|
});
|
|
9608
10124
|
}
|
|
9609
10125
|
}
|
|
10126
|
+
/** A stop before the recording runs has nothing to keep: the strip goes back to idle. */
|
|
10127
|
+
cancelPendingStart() {
|
|
10128
|
+
this.pendingStart = undefined;
|
|
10129
|
+
this.destroyRecorder();
|
|
10130
|
+
this.updateViewState(() => {
|
|
10131
|
+
this.isRecording.set(false);
|
|
10132
|
+
this.isWaveformOpen = false;
|
|
10133
|
+
});
|
|
10134
|
+
}
|
|
10135
|
+
/** The plugin's destroy alone closes a running microphone's AudioContext twice, which rejects;
|
|
10136
|
+
* after the stop and the stopMic it has nothing left to close. */
|
|
10137
|
+
destroyRecorder() {
|
|
10138
|
+
const wavesurfer = this.wavesurfer;
|
|
10139
|
+
const record = this.record;
|
|
10140
|
+
this.wavesurfer = undefined;
|
|
10141
|
+
this.record = undefined;
|
|
10142
|
+
record?.stopRecording();
|
|
10143
|
+
record?.stopMic();
|
|
10144
|
+
wavesurfer?.destroy();
|
|
10145
|
+
}
|
|
9610
10146
|
saveRecording() {
|
|
9611
|
-
|
|
9612
|
-
|
|
9613
|
-
|
|
9614
|
-
//Safari support
|
|
9615
|
-
let mimeType = this.recordedBlob.type || 'audio/mp4';
|
|
9616
|
-
let ext = mimeType.includes('webm') ? 'webm' : 'mp4';
|
|
9617
|
-
let fileName = `recording_${formattedDate}.${ext}`;
|
|
9618
|
-
let file = new File([this.recordedBlob], fileName, { type: mimeType });
|
|
10147
|
+
const recordedBlob = this.recordedBlob();
|
|
10148
|
+
if (recordedBlob) {
|
|
10149
|
+
const file = UploadWidgetUtils.createRecordingFile(recordedBlob, 'recording', 'audio/mp4');
|
|
9619
10150
|
this.recordingSaved.emit(file);
|
|
9620
10151
|
// Destroy completely so next recording initializes fresh
|
|
9621
|
-
this.
|
|
9622
|
-
this.wavesurfer = undefined;
|
|
9623
|
-
this.record = undefined;
|
|
10152
|
+
this.destroyRecorder();
|
|
9624
10153
|
this.updateViewState(() => {
|
|
9625
|
-
this.recordedBlob
|
|
10154
|
+
this.recordedBlob.set(null);
|
|
9626
10155
|
this.time = '00:00';
|
|
9627
10156
|
this.isPlaying = false;
|
|
9628
10157
|
this.isWaveformOpen = false;
|
|
@@ -9630,7 +10159,7 @@ class VoiceRecordWidgetComponent {
|
|
|
9630
10159
|
}
|
|
9631
10160
|
}
|
|
9632
10161
|
replayRecording() {
|
|
9633
|
-
if (this.recordedBlob) {
|
|
10162
|
+
if (this.recordedBlob()) {
|
|
9634
10163
|
this.updateViewState(() => {
|
|
9635
10164
|
this.isPlaying = !this.isPlaying;
|
|
9636
10165
|
this.wavesurfer?.playPause();
|
|
@@ -9638,9 +10167,8 @@ class VoiceRecordWidgetComponent {
|
|
|
9638
10167
|
}
|
|
9639
10168
|
}
|
|
9640
10169
|
ngOnDestroy() {
|
|
9641
|
-
|
|
9642
|
-
|
|
9643
|
-
}
|
|
10170
|
+
this.pendingStart = undefined;
|
|
10171
|
+
this.destroyRecorder();
|
|
9644
10172
|
}
|
|
9645
10173
|
addAlphaToColor(hexColor, alpha) {
|
|
9646
10174
|
if (/^#([a-f\d])([a-f\d])([a-f\d])$/i.test(hexColor)) {
|
|
@@ -9670,29 +10198,136 @@ class VoiceRecordWidgetComponent {
|
|
|
9670
10198
|
});
|
|
9671
10199
|
}
|
|
9672
10200
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: VoiceRecordWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9673
|
-
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 } }, 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]=\"isWaveformOpen || isRecording || !!recordedBlob\"\n [class.isRecording]=\"isRecording\"\n [class.hasRecording]=\"recordedBlob\"\n >\n <div class=\"recorderMain\">\n <div class=\"recorderToolbar\">\n <!-- Start Recording -->\n @if (!isRecording) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"micButton\"\n (actionClick)=\"
|
|
10201
|
+
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 }, actionsInHost: { classPropertyName: "actionsInHost", publicName: "actionsInHost", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { recordingSaved: "recordingSaved" }, host: { properties: { "class.actionsInHost": "actionsInHost()" } }, 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\"\n [class.direction-left]=\"isLeftDirection\"\n [class.direction-right]=\"!isLeftDirection\"\n [class.active]=\"isWaveformOpen || isRecording() || !!recordedBlob()\"\n [class.isRecording]=\"isRecording()\"\n [class.hasRecording]=\"recordedBlob()\"\n >\n <div class=\"recorderMain\">\n <div class=\"recorderToolbar\">\n <!-- Start Recording -->\n @if (!isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"micButton\"\n (actionClick)=\"requestRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Stop Recording -->\n @if (isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"effectiveStopButton()\"\n (actionClick)=\"stopRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Save Recording -->\n @if (recordedBlob() && !actionsInHost()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"effectiveSaveButton()\"\n (actionClick)=\"saveRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Start Play -->\n @if (recordedBlob() && !isPlaying) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"playButton\"\n (actionClick)=\"replayRecording()\"\n ></ui-action-button>\n }\n\n <!-- Stop Play -->\n @if (recordedBlob() && isPlaying) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"stopPlaybackButton\"\n (actionClick)=\"replayRecording()\"\n ></ui-action-button>\n }\n\n @if (isRecording()) {\n <div class=\"progress\">{{ time }}</div>\n }\n </div>\n\n <div class=\"waveformBand\" [class.open]=\"isWaveformOpen || isRecording() || !!recordedBlob()\">\n <div class=\"waveformGrid\"></div>\n <div class=\"waveform\" #waveform></div>\n </div>\n </div>\n</div>\n@if (showMicrophoneError() && microphoneError()) {\n <p class=\"message\">{{ texts().microphoneError }}</p>\n @if (actionsInHost()) {\n <p class=\"hint\">{{ texts().microphoneRetryHint }}</p>\n }\n}\n@if (actionsInHost() && recordedBlob()) {\n <p class=\"hint\">{{ texts().recordingHint }}</p>\n}\n\n<ng-template #actionButtons>\n @if (isRecording()) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"stopRecording()\"\n >\n </ui-action-button>\n }\n @if (recordedBlob()) {\n <ui-action-button\n class=\"saveRecordingButton\"\n [descriptor]=\"effectiveSaveButton()\"\n (actionClick)=\"saveRecording()\"\n >\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}:host(.actionsInHost) .message{color:var(--warn-color)}:host(.actionsInHost) .hint{margin:.5rem 0 0;text-align:center;font-size:.75rem;color:var(--color-text-muted)}:host(.actionsInHost) .progress{color:var(--color-text-muted)}:host(.actionsInHost) .isRecording .progress:before{content:\"\";display:inline-block;width:.5rem;height:.5rem;margin-right:.3125rem;border-radius:50%;background:var(--warn-color)}:host(.actionsInHost) .active ::ng-deep .recorderToolbar{border-color:var(--color-border)}:host(.actionsInHost) .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(.actionsInHost) .recorderToolbar ::ng-deep .sb4-warn,:host(.actionsInHost) .recorderToolbar ::ng-deep .sb4-primary,:host(.actionsInHost) .recorderToolbar ::ng-deep ui-action-button{border:unset}:host(.actionsInHost) .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"] }] }); }
|
|
9674
10202
|
}
|
|
9675
10203
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: VoiceRecordWidgetComponent, decorators: [{
|
|
9676
10204
|
type: Component,
|
|
9677
|
-
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]=\"isWaveformOpen || isRecording || !!recordedBlob\"\n [class.isRecording]=\"isRecording\"\n [class.hasRecording]=\"recordedBlob\"\n >\n <div class=\"recorderMain\">\n <div class=\"recorderToolbar\">\n <!-- Start Recording -->\n @if (!isRecording) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"micButton\"\n (actionClick)=\"
|
|
10205
|
+
args: [{ selector: 'voice-record-widget', imports: [UiActionButtonComponent], host: { '[class.actionsInHost]': 'actionsInHost()' }, template: "<div\n class=\"recorderContainer\"\n [class.direction-left]=\"isLeftDirection\"\n [class.direction-right]=\"!isLeftDirection\"\n [class.active]=\"isWaveformOpen || isRecording() || !!recordedBlob()\"\n [class.isRecording]=\"isRecording()\"\n [class.hasRecording]=\"recordedBlob()\"\n >\n <div class=\"recorderMain\">\n <div class=\"recorderToolbar\">\n <!-- Start Recording -->\n @if (!isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"micButton\"\n (actionClick)=\"requestRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Stop Recording -->\n @if (isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"effectiveStopButton()\"\n (actionClick)=\"stopRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Save Recording -->\n @if (recordedBlob() && !actionsInHost()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"effectiveSaveButton()\"\n (actionClick)=\"saveRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Start Play -->\n @if (recordedBlob() && !isPlaying) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"playButton\"\n (actionClick)=\"replayRecording()\"\n ></ui-action-button>\n }\n\n <!-- Stop Play -->\n @if (recordedBlob() && isPlaying) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"stopPlaybackButton\"\n (actionClick)=\"replayRecording()\"\n ></ui-action-button>\n }\n\n @if (isRecording()) {\n <div class=\"progress\">{{ time }}</div>\n }\n </div>\n\n <div class=\"waveformBand\" [class.open]=\"isWaveformOpen || isRecording() || !!recordedBlob()\">\n <div class=\"waveformGrid\"></div>\n <div class=\"waveform\" #waveform></div>\n </div>\n </div>\n</div>\n@if (showMicrophoneError() && microphoneError()) {\n <p class=\"message\">{{ texts().microphoneError }}</p>\n @if (actionsInHost()) {\n <p class=\"hint\">{{ texts().microphoneRetryHint }}</p>\n }\n}\n@if (actionsInHost() && recordedBlob()) {\n <p class=\"hint\">{{ texts().recordingHint }}</p>\n}\n\n<ng-template #actionButtons>\n @if (isRecording()) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"stopRecording()\"\n >\n </ui-action-button>\n }\n @if (recordedBlob()) {\n <ui-action-button\n class=\"saveRecordingButton\"\n [descriptor]=\"effectiveSaveButton()\"\n (actionClick)=\"saveRecording()\"\n >\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}:host(.actionsInHost) .message{color:var(--warn-color)}:host(.actionsInHost) .hint{margin:.5rem 0 0;text-align:center;font-size:.75rem;color:var(--color-text-muted)}:host(.actionsInHost) .progress{color:var(--color-text-muted)}:host(.actionsInHost) .isRecording .progress:before{content:\"\";display:inline-block;width:.5rem;height:.5rem;margin-right:.3125rem;border-radius:50%;background:var(--warn-color)}:host(.actionsInHost) .active ::ng-deep .recorderToolbar{border-color:var(--color-border)}:host(.actionsInHost) .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(.actionsInHost) .recorderToolbar ::ng-deep .sb4-warn,:host(.actionsInHost) .recorderToolbar ::ng-deep .sb4-primary,:host(.actionsInHost) .recorderToolbar ::ng-deep ui-action-button{border:unset}:host(.actionsInHost) .mainButton ::ng-deep .sb4-warn{padding:.5rem .75rem}\n"] }]
|
|
9678
10206
|
}], propDecorators: { waveformRef: [{
|
|
9679
10207
|
type: ViewChild,
|
|
9680
10208
|
args: ['waveform']
|
|
9681
10209
|
}], mainButton: [{
|
|
9682
10210
|
type: ViewChild,
|
|
9683
10211
|
args: ['mainButton', { read: ElementRef }]
|
|
9684
|
-
}], openDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "openDirection", required: false }] }], recordingSaved: [{ type: i0.Output, args: ["recordingSaved"] }] } });
|
|
10212
|
+
}], actionButtonsTemplate: [{ type: i0.ViewChild, args: ['actionButtons', { isSignal: true }] }], 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 }] }], actionsInHost: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionsInHost", required: false }] }], recordingSaved: [{ type: i0.Output, args: ["recordingSaved"] }] } });
|
|
10213
|
+
|
|
10214
|
+
/** The Capture Dialog of a File Field: closes with the files of a use or a save, else empty. */
|
|
10215
|
+
class CaptureDialogComponent {
|
|
10216
|
+
constructor() {
|
|
10217
|
+
this.dialogRef = inject(MatDialogRef);
|
|
10218
|
+
this.data = inject(MAT_DIALOG_DATA);
|
|
10219
|
+
this.isMicrophone = this.data.source === FileUploaderSource.MICROPHONE;
|
|
10220
|
+
this.cameraMode = this.data.source === FileUploaderSource.CAMERA_VIDEO ? 'VIDEO' : 'PHOTO';
|
|
10221
|
+
this.cameraWidget = viewChild(PhotoCaptureWidgetComponent, /* @ts-ignore */
|
|
10222
|
+
...(ngDevMode ? [{ debugName: "cameraWidget" }] : /* istanbul ignore next */ []));
|
|
10223
|
+
this.recorderWidget = viewChild(VoiceRecordWidgetComponent, /* @ts-ignore */
|
|
10224
|
+
...(ngDevMode ? [{ debugName: "recorderWidget" }] : /* istanbul ignore next */ []));
|
|
10225
|
+
this.captureWidget = computed(() => this.cameraWidget() ?? this.recorderWidget(), /* @ts-ignore */
|
|
10226
|
+
...(ngDevMode ? [{ debugName: "captureWidget" }] : /* istanbul ignore next */ []));
|
|
10227
|
+
/** The widget's buttons go to the footer of the dialog. */
|
|
10228
|
+
this.actionButtons = computed(() => this.captureWidget()?.actionButtonsTemplate() ?? null, /* @ts-ignore */
|
|
10229
|
+
...(ngDevMode ? [{ debugName: "actionButtons" }] : /* istanbul ignore next */ []));
|
|
10230
|
+
/** The recorder waits for the open: it reads the waveform's height from its rendered button. */
|
|
10231
|
+
this.isOpened = signal(false, /* @ts-ignore */
|
|
10232
|
+
...(ngDevMode ? [{ debugName: "isOpened" }] : /* istanbul ignore next */ []));
|
|
10233
|
+
this.closeButton = {
|
|
10234
|
+
title: '',
|
|
10235
|
+
type: UiActionButtonType.ICON,
|
|
10236
|
+
icon: 'close',
|
|
10237
|
+
iconPosition: IconPosition.PRE,
|
|
10238
|
+
color: 'primary',
|
|
10239
|
+
};
|
|
10240
|
+
this.cancelButton = {
|
|
10241
|
+
title: this.data.texts.cancel,
|
|
10242
|
+
type: UiActionButtonType.FLAT,
|
|
10243
|
+
color: 'primary',
|
|
10244
|
+
};
|
|
10245
|
+
effect(() => {
|
|
10246
|
+
this.dialogRef.disableClose = this.captureWidget()?.isCaptureUnderWay() ?? false;
|
|
10247
|
+
});
|
|
10248
|
+
this.dialogRef.afterOpened().subscribe(() => this.isOpened.set(true));
|
|
10249
|
+
// disableClose stops the keyboard too, but only the backdrop is meant to wait for the capture.
|
|
10250
|
+
this.dialogRef.keydownEvents().subscribe((event) => {
|
|
10251
|
+
if (event.key === 'Escape' && this.dialogRef.disableClose) {
|
|
10252
|
+
this.discardCapture();
|
|
10253
|
+
}
|
|
10254
|
+
});
|
|
10255
|
+
}
|
|
10256
|
+
/** The close destroys the capture widget, which stops the device. */
|
|
10257
|
+
discardCapture() {
|
|
10258
|
+
this.dialogRef.close();
|
|
10259
|
+
}
|
|
10260
|
+
handOverCapture(files) {
|
|
10261
|
+
this.dialogRef.close(files);
|
|
10262
|
+
}
|
|
10263
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10264
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: CaptureDialogComponent, isStandalone: true, selector: "smart-capture-dialog", viewQueries: [{ propertyName: "cameraWidget", first: true, predicate: PhotoCaptureWidgetComponent, descendants: true, isSignal: true }, { propertyName: "recorderWidget", first: true, predicate: VoiceRecordWidgetComponent, 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 <voice-record-widget\n [deviceStart]=\"data.settings.deviceStart\"\n [handover]=\"data.settings.handover\"\n [actionsInHost]=\"true\"\n [showSaveTitle]=\"true\"\n [texts]=\"data.texts\"\n [showMicrophoneError]=\"true\"\n (recordingSaved)=\"handOverCapture([$event])\"\n ></voice-record-widget>\n }\n </div>\n } @else {\n <photo-capture-widget\n [mode]=\"cameraMode\"\n [deviceStart]=\"data.settings.deviceStart\"\n [handover]=\"data.settings.handover\"\n [actionsInHost]=\"true\"\n [texts]=\"data.texts\"\n [maxFileSize]=\"data.maxFileSize\"\n (photoCaptured)=\"handOverCapture($event)\"\n ></photo-capture-widget>\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}.captureDialog ::ng-deep .stopRecordingButton{--text-color: color-mix(in srgb, var(--warn-color) 5%, white)}photo-capture-widget{display:block;width:100%}.recorderSlot{min-height:3.25rem}\n"], dependencies: [{ 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: PhotoCaptureWidgetComponent, selector: "photo-capture-widget", inputs: ["maxFileSize", "mode", "externalTrigger", "texts", "deviceStart", "handover", "actionsInHost"], outputs: ["photoCaptured"] }, { 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: VoiceRecordWidgetComponent, selector: "voice-record-widget", inputs: ["openDirection", "texts", "showMicrophoneError", "deviceStart", "handover", "showSaveTitle", "actionsInHost"], outputs: ["recordingSaved"] }] }); }
|
|
10265
|
+
}
|
|
10266
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogComponent, decorators: [{
|
|
10267
|
+
type: Component,
|
|
10268
|
+
args: [{ selector: 'smart-capture-dialog', imports: [
|
|
10269
|
+
MatDialogContent,
|
|
10270
|
+
NgTemplateOutlet,
|
|
10271
|
+
PhotoCaptureWidgetComponent,
|
|
10272
|
+
UiActionButtonComponent,
|
|
10273
|
+
VoiceRecordWidgetComponent,
|
|
10274
|
+
], 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 <voice-record-widget\n [deviceStart]=\"data.settings.deviceStart\"\n [handover]=\"data.settings.handover\"\n [actionsInHost]=\"true\"\n [showSaveTitle]=\"true\"\n [texts]=\"data.texts\"\n [showMicrophoneError]=\"true\"\n (recordingSaved)=\"handOverCapture([$event])\"\n ></voice-record-widget>\n }\n </div>\n } @else {\n <photo-capture-widget\n [mode]=\"cameraMode\"\n [deviceStart]=\"data.settings.deviceStart\"\n [handover]=\"data.settings.handover\"\n [actionsInHost]=\"true\"\n [texts]=\"data.texts\"\n [maxFileSize]=\"data.maxFileSize\"\n (photoCaptured)=\"handOverCapture($event)\"\n ></photo-capture-widget>\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}.captureDialog ::ng-deep .stopRecordingButton{--text-color: color-mix(in srgb, var(--warn-color) 5%, white)}photo-capture-widget{display:block;width:100%}.recorderSlot{min-height:3.25rem}\n"] }]
|
|
10275
|
+
}], ctorParameters: () => [], propDecorators: { cameraWidget: [{ type: i0.ViewChild, args: [i0.forwardRef(() => PhotoCaptureWidgetComponent), { isSignal: true }] }], recorderWidget: [{ type: i0.ViewChild, args: [i0.forwardRef(() => VoiceRecordWidgetComponent), { isSignal: true }] }] } });
|
|
10276
|
+
|
|
10277
|
+
class CaptureDialogService extends SmartdialogService {
|
|
10278
|
+
constructor() {
|
|
10279
|
+
super(inject(MatDialog), inject(Injector));
|
|
10280
|
+
}
|
|
10281
|
+
/** The returned dialog closes with the handed over files, or empty at a discard. */
|
|
10282
|
+
openCaptureDialog(data) {
|
|
10283
|
+
return this.createDialogRef(data, CaptureDialogComponent, {
|
|
10284
|
+
width: '560px',
|
|
10285
|
+
maxWidth: '90vw',
|
|
10286
|
+
autoFocus: 'dialog',
|
|
10287
|
+
});
|
|
10288
|
+
}
|
|
10289
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
10290
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogService, providedIn: 'root' }); }
|
|
10291
|
+
}
|
|
10292
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogService, decorators: [{
|
|
10293
|
+
type: Injectable,
|
|
10294
|
+
args: [{ providedIn: 'root' }]
|
|
10295
|
+
}], ctorParameters: () => [] });
|
|
9685
10296
|
|
|
9686
10297
|
class UploadWidgetComponent {
|
|
9687
10298
|
constructor() {
|
|
10299
|
+
this.captureDialogService = inject(CaptureDialogService);
|
|
9688
10300
|
this.uploadDescriptor = input(this.defaultUploadDescriptor, /* @ts-ignore */
|
|
9689
10301
|
...(ngDevMode ? [{ debugName: "uploadDescriptor" }] : /* istanbul ignore next */ []));
|
|
9690
10302
|
this.isMultiple = input.required(/* @ts-ignore */
|
|
9691
10303
|
...(ngDevMode ? [{ debugName: "isMultiple" }] : /* istanbul ignore next */ []));
|
|
9692
10304
|
this.isDisabled = input(false, /* @ts-ignore */
|
|
9693
10305
|
...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
10306
|
+
this.sources = input(undefined, /* @ts-ignore */
|
|
10307
|
+
...(ngDevMode ? [{ debugName: "sources" }] : /* istanbul ignore next */ []));
|
|
10308
|
+
this.capture = input(undefined, /* @ts-ignore */
|
|
10309
|
+
...(ngDevMode ? [{ debugName: "capture" }] : /* istanbul ignore next */ []));
|
|
9694
10310
|
this.uploadFilesEvent = output();
|
|
10311
|
+
/** The Capture Settings in force: a missing bean or field takes its default here only. */
|
|
10312
|
+
this.captureSettings = computed(() => {
|
|
10313
|
+
const capture = this.capture();
|
|
10314
|
+
return {
|
|
10315
|
+
mode: capture?.mode ?? CaptureMode.DIALOG,
|
|
10316
|
+
openDirection: capture?.openDirection ?? CaptureOpenDirection.RIGHT,
|
|
10317
|
+
deviceStart: capture?.deviceStart ?? CaptureDeviceStart.AUTO,
|
|
10318
|
+
handover: capture?.handover ?? CaptureHandover.CONFIRM,
|
|
10319
|
+
};
|
|
10320
|
+
}, /* @ts-ignore */
|
|
10321
|
+
...(ngDevMode ? [{ debugName: "captureSettings" }] : /* istanbul ignore next */ []));
|
|
9695
10322
|
this.uploadWidgetType = UploadWidgetType;
|
|
10323
|
+
this.fileUploaderSource = FileUploaderSource;
|
|
10324
|
+
/** The recorder opens below the capture row at a click on the microphone button. */
|
|
10325
|
+
this.isRecorderOpen = signal(false, /* @ts-ignore */
|
|
10326
|
+
...(ngDevMode ? [{ debugName: "isRecorderOpen" }] : /* istanbul ignore next */ []));
|
|
10327
|
+
/** The descriptor's texts over the client's defaults, handed down to the capture widgets. */
|
|
10328
|
+
this.texts = UPLOAD_ZONE_DEFAULT_TEXTS;
|
|
10329
|
+
/** Cached per text set, so change detection hands each button the same descriptor. */
|
|
10330
|
+
this.captureButtons = new Map();
|
|
9696
10331
|
this.maxSizeMb = 25;
|
|
9697
10332
|
}
|
|
9698
10333
|
ngOnInit() {
|
|
@@ -9709,13 +10344,17 @@ class UploadWidgetComponent {
|
|
|
9709
10344
|
this.fileFormats = [uploadDescriptor.formats];
|
|
9710
10345
|
}
|
|
9711
10346
|
// fileFormats = this.descriptor.upload?.formats;
|
|
10347
|
+
this.texts = UploadWidgetUtils.resolveTexts(uploadDescriptor?.texts);
|
|
9712
10348
|
this.i18n = {
|
|
9713
|
-
addFile: uploadDescriptor?.title ??
|
|
9714
|
-
browseOrDrag: uploadDescriptor?.description ??
|
|
10349
|
+
addFile: uploadDescriptor?.title ?? this.texts.addFile,
|
|
10350
|
+
browseOrDrag: uploadDescriptor?.description ?? this.texts.browseOrDrag,
|
|
9715
10351
|
formats: uploadDescriptor?.formats ?? '',
|
|
9716
|
-
maxSize:
|
|
9717
|
-
upload: uploadDescriptor?.uploadButtonTitle ??
|
|
10352
|
+
maxSize: UploadWidgetUtils.fillPlaceholders(this.texts.maxSize, { size: this.maxSizeMb }),
|
|
10353
|
+
upload: uploadDescriptor?.uploadButtonTitle ?? this.texts.upload,
|
|
10354
|
+
unsupportedFormat: this.texts.unsupportedFormat,
|
|
10355
|
+
fileTooLarge: this.texts.fileTooLarge,
|
|
9718
10356
|
};
|
|
10357
|
+
this.captureButtons.clear();
|
|
9719
10358
|
}
|
|
9720
10359
|
upload(files) {
|
|
9721
10360
|
this.uploadFilesEvent.emit({
|
|
@@ -9724,23 +10363,103 @@ class UploadWidgetComponent {
|
|
|
9724
10363
|
});
|
|
9725
10364
|
this.fileUploadMaterial.files = [];
|
|
9726
10365
|
}
|
|
10366
|
+
/** A captured file takes the picker's path: the same checks, the list or the upload. */
|
|
9727
10367
|
uploadRecording(file) {
|
|
9728
|
-
this.
|
|
10368
|
+
this.fileUploadMaterial.processFiles([file]);
|
|
9729
10369
|
}
|
|
9730
10370
|
uploadImage(files) {
|
|
9731
|
-
this.
|
|
10371
|
+
this.fileUploadMaterial.processFiles(files);
|
|
9732
10372
|
}
|
|
9733
|
-
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
10373
|
+
/** True when the sources decide the zone: they are set and not the default FILE alone beside
|
|
10374
|
+
* uploadWidgets. */
|
|
10375
|
+
hasSources() {
|
|
10376
|
+
const sources = this.sources() ?? [];
|
|
10377
|
+
const isDefaultOnly = sources.length === 1 && sources[0] === FileUploaderSource.FILE;
|
|
10378
|
+
return !!sources.length && !(isDefaultOnly && !!this.uploadDescriptor()?.uploadWidgets?.length);
|
|
10379
|
+
}
|
|
10380
|
+
isSourceNeeded(source) {
|
|
10381
|
+
return this.sources()?.includes(source) ?? false;
|
|
10382
|
+
}
|
|
10383
|
+
isPickerShown() {
|
|
10384
|
+
return !this.hasSources() || this.isSourceNeeded(FileUploaderSource.FILE);
|
|
10385
|
+
}
|
|
10386
|
+
isCaptureOnly() {
|
|
10387
|
+
return !this.isPickerShown();
|
|
10388
|
+
}
|
|
10389
|
+
/** Capture-only uploads at once whatever the descriptor says, as there is no upload button. */
|
|
10390
|
+
isUploadImmediate() {
|
|
10391
|
+
return !!this.autoUpload || this.isCaptureOnly();
|
|
10392
|
+
}
|
|
10393
|
+
getCaptureSources() {
|
|
10394
|
+
return (this.sources() ?? []).filter((source) => source !== FileUploaderSource.FILE);
|
|
10395
|
+
}
|
|
10396
|
+
/** On a phone a camera button stays the device's own camera, whatever the Capture Mode says. */
|
|
10397
|
+
startCapture(source) {
|
|
10398
|
+
const camera = this.getCameraWidget(source);
|
|
10399
|
+
if (this.captureSettings().mode === CaptureMode.DIALOG && !camera?.isMobile()) {
|
|
10400
|
+
this.openCaptureDialog(source);
|
|
9739
10401
|
}
|
|
9740
|
-
else {
|
|
9741
|
-
|
|
10402
|
+
else if (camera) {
|
|
10403
|
+
camera.openCapture();
|
|
10404
|
+
}
|
|
10405
|
+
else if (source === FileUploaderSource.MICROPHONE) {
|
|
10406
|
+
this.isRecorderOpen.update((open) => !open);
|
|
9742
10407
|
}
|
|
9743
10408
|
}
|
|
10409
|
+
getCameraWidget(source) {
|
|
10410
|
+
if (source === FileUploaderSource.MICROPHONE) {
|
|
10411
|
+
return undefined;
|
|
10412
|
+
}
|
|
10413
|
+
return source === FileUploaderSource.CAMERA_PHOTO ? this.photoCapture : this.videoCapture;
|
|
10414
|
+
}
|
|
10415
|
+
openCaptureDialog(source) {
|
|
10416
|
+
this.captureDialogService
|
|
10417
|
+
.openCaptureDialog({
|
|
10418
|
+
size: {},
|
|
10419
|
+
content: { title: this.texts[CAPTURE_BUTTONS[source].dialogTitle] },
|
|
10420
|
+
source,
|
|
10421
|
+
settings: this.captureSettings(),
|
|
10422
|
+
texts: this.texts,
|
|
10423
|
+
maxFileSize: source === FileUploaderSource.CAMERA_PHOTO ? this.getMaxFileSize() : undefined,
|
|
10424
|
+
})
|
|
10425
|
+
.afterClosed()
|
|
10426
|
+
.subscribe((files) => {
|
|
10427
|
+
if (files) {
|
|
10428
|
+
this.uploadImage(files);
|
|
10429
|
+
}
|
|
10430
|
+
});
|
|
10431
|
+
}
|
|
10432
|
+
getMaxFileSize() {
|
|
10433
|
+
return this.maxSizeMb * 1024 * 1024;
|
|
10434
|
+
}
|
|
10435
|
+
getCaptureButtonClass(source) {
|
|
10436
|
+
return CAPTURE_BUTTONS[source].cssClass;
|
|
10437
|
+
}
|
|
10438
|
+
getCaptureButton(source) {
|
|
10439
|
+
if (!this.captureButtons.has(source)) {
|
|
10440
|
+
const button = CAPTURE_BUTTONS[source];
|
|
10441
|
+
this.captureButtons.set(source, {
|
|
10442
|
+
title: '',
|
|
10443
|
+
type: UiActionButtonType.ICON,
|
|
10444
|
+
icon: button.icon,
|
|
10445
|
+
iconPosition: IconPosition.PRE,
|
|
10446
|
+
color: 'primary',
|
|
10447
|
+
tooltip: { tooltip: this.texts[button.tooltip] },
|
|
10448
|
+
});
|
|
10449
|
+
}
|
|
10450
|
+
return this.captureButtons.get(source);
|
|
10451
|
+
}
|
|
10452
|
+
/** The hint of a capture-only zone names its sources, as the picker's lines name the files.
|
|
10453
|
+
* Its grammar is owned by the captureOnlyHint and or texts. */
|
|
10454
|
+
getCaptureOnlyHint() {
|
|
10455
|
+
const nouns = this.getCaptureSources().map((source) => this.texts[CAPTURE_BUTTONS[source].noun]);
|
|
10456
|
+
const last = nouns.pop();
|
|
10457
|
+
const joined = nouns.length ? `${nouns.join(', ')} ${this.texts.or} ${last}` : last;
|
|
10458
|
+
const hint = UploadWidgetUtils.fillPlaceholders(this.texts.captureOnlyHint, {
|
|
10459
|
+
sources: joined,
|
|
10460
|
+
});
|
|
10461
|
+
return `${hint.charAt(0).toUpperCase()}${hint.slice(1)} · ${this.i18n?.maxSize}`;
|
|
10462
|
+
}
|
|
9744
10463
|
widgetNeeded(type) {
|
|
9745
10464
|
return this.uploadDescriptor()?.uploadWidgets?.includes(type) ?? false;
|
|
9746
10465
|
}
|
|
@@ -9749,26 +10468,175 @@ class UploadWidgetComponent {
|
|
|
9749
10468
|
}
|
|
9750
10469
|
get defaultUploadDescriptor() {
|
|
9751
10470
|
return {
|
|
9752
|
-
title:
|
|
9753
|
-
description:
|
|
10471
|
+
title: UPLOAD_ZONE_DEFAULT_TEXTS.addFile,
|
|
10472
|
+
description: UPLOAD_ZONE_DEFAULT_TEXTS.browseOrDrag,
|
|
9754
10473
|
maxSize: '25',
|
|
9755
10474
|
autoUpload: false,
|
|
9756
10475
|
};
|
|
9757
10476
|
}
|
|
9758
10477
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UploadWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9759
|
-
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 } }, outputs: { uploadFilesEvent: "uploadFilesEvent" }, viewQueries: [{ propertyName: "fileUploadMaterial", first: true, predicate: ["fileUploadMaterial"], descendants: true }], ngImport: i0, template: "<div class=\"sb4UploadWidget\">\n <!-- IMAGE TYPE WIDGET -->\n
|
|
10478
|
+
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", "actionsInHost"], outputs: ["photoCaptured"] }, { kind: "component", type: VoiceRecordWidgetComponent, selector: "voice-record-widget", inputs: ["openDirection", "texts", "showMicrophoneError", "deviceStart", "handover", "showSaveTitle", "actionsInHost"], 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"] }] }); }
|
|
9760
10479
|
}
|
|
9761
10480
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UploadWidgetComponent, decorators: [{
|
|
9762
10481
|
type: Component,
|
|
9763
|
-
args: [{ selector: 'smart-upload-widget', imports: [
|
|
10482
|
+
args: [{ selector: 'smart-upload-widget', imports: [
|
|
10483
|
+
PhotoCaptureWidgetComponent,
|
|
10484
|
+
VoiceRecordWidgetComponent,
|
|
10485
|
+
SmartfileuploaderComponent,
|
|
10486
|
+
UiActionButtonComponent,
|
|
10487
|
+
], 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"] }]
|
|
9764
10488
|
}], propDecorators: { fileUploadMaterial: [{
|
|
9765
10489
|
type: ViewChild,
|
|
9766
10490
|
args: ['fileUploadMaterial']
|
|
9767
|
-
}],
|
|
10491
|
+
}], photoCapture: [{
|
|
10492
|
+
type: ViewChild,
|
|
10493
|
+
args: ['photoCapture']
|
|
10494
|
+
}], videoCapture: [{
|
|
10495
|
+
type: ViewChild,
|
|
10496
|
+
args: ['videoCapture']
|
|
10497
|
+
}], 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"] }] } });
|
|
10498
|
+
const CAPTURE_BUTTONS = {
|
|
10499
|
+
[FileUploaderSource.CAMERA_PHOTO]: {
|
|
10500
|
+
icon: 'photo_camera',
|
|
10501
|
+
cssClass: 'capturePhotoButton',
|
|
10502
|
+
tooltip: 'capturePhoto',
|
|
10503
|
+
noun: 'photo',
|
|
10504
|
+
dialogTitle: 'photoDialogTitle',
|
|
10505
|
+
},
|
|
10506
|
+
[FileUploaderSource.CAMERA_VIDEO]: {
|
|
10507
|
+
icon: 'videocam',
|
|
10508
|
+
cssClass: 'captureVideoButton',
|
|
10509
|
+
tooltip: 'captureVideo',
|
|
10510
|
+
noun: 'video',
|
|
10511
|
+
dialogTitle: 'videoDialogTitle',
|
|
10512
|
+
},
|
|
10513
|
+
[FileUploaderSource.MICROPHONE]: {
|
|
10514
|
+
icon: 'mic',
|
|
10515
|
+
cssClass: 'recordAudioButton',
|
|
10516
|
+
tooltip: 'recordAudio',
|
|
10517
|
+
noun: 'audio',
|
|
10518
|
+
dialogTitle: 'audioDialogTitle',
|
|
10519
|
+
},
|
|
10520
|
+
};
|
|
10521
|
+
|
|
10522
|
+
const FILE_PREVIEW_KEY_PREFIX = 'filePreview.';
|
|
10523
|
+
/** The preview element of a mime type, or `undefined` when the file has no preview. */
|
|
10524
|
+
function getFilePreviewKind(mimeType) {
|
|
10525
|
+
const type = mimeType?.split('/')[0];
|
|
10526
|
+
return type === 'image' || type === 'video' || type === 'audio' ? type : undefined;
|
|
10527
|
+
}
|
|
10528
|
+
/** The server's preview key of a single-file widget (`FileWidgetHandlerApi.getPreviewItemKey`). */
|
|
10529
|
+
function createSingleFilePreviewKey(widgetKey) {
|
|
10530
|
+
return FILE_PREVIEW_KEY_PREFIX + widgetKey;
|
|
10531
|
+
}
|
|
10532
|
+
/** The server's preview key of a list row: the widget key and the object id of the dataUri. */
|
|
10533
|
+
function createListFilePreviewKey(widgetKey, dataUri) {
|
|
10534
|
+
const path = dataUri.split(/[?#]/)[0];
|
|
10535
|
+
const objectId = path.substring(path.lastIndexOf('/') + 1).split('.')[0];
|
|
10536
|
+
return `${FILE_PREVIEW_KEY_PREFIX}${widgetKey}.${objectId}`;
|
|
10537
|
+
}
|
|
10538
|
+
/**
|
|
10539
|
+
* Loads the previews of a file editor through the view's authenticated download and owns their
|
|
10540
|
+
* blob URLs: a URL is revoked when its file leaves the requests and when the editor is destroyed.
|
|
10541
|
+
* Created in the editor's injection context; the view uuid is the editor's client's.
|
|
10542
|
+
*/
|
|
10543
|
+
class FilePreviewLoader {
|
|
10544
|
+
constructor() {
|
|
10545
|
+
this.viewContext = inject(SmartViewContextService);
|
|
10546
|
+
this.smartComponent = injectSmartComponent();
|
|
10547
|
+
this.requests = signal([], /* @ts-ignore */
|
|
10548
|
+
...(ngDevMode ? [{ debugName: "requests" }] : /* istanbul ignore next */ []));
|
|
10549
|
+
this.previewsState = signal(new Map(), /* @ts-ignore */
|
|
10550
|
+
...(ngDevMode ? [{ debugName: "previewsState" }] : /* istanbul ignore next */ []));
|
|
10551
|
+
this.loadingDataUris = new Set();
|
|
10552
|
+
/** Not fetched again while it stays in the requests, as every model write sets them anew. */
|
|
10553
|
+
this.failedDataUris = new Set();
|
|
10554
|
+
this.destroyed = false;
|
|
10555
|
+
effect(() => this.loadPreviews(this.smartComponent?.widgetUuid(), this.requests()));
|
|
10556
|
+
inject(DestroyRef).onDestroy(() => {
|
|
10557
|
+
this.destroyed = true;
|
|
10558
|
+
this.previewsState().forEach((preview) => URL.revokeObjectURL(preview.url));
|
|
10559
|
+
});
|
|
10560
|
+
}
|
|
10561
|
+
/** Sets the files to preview; a file of no preview kind is ignored. */
|
|
10562
|
+
setRequests(requests) {
|
|
10563
|
+
this.requests.set(requests.filter((request) => getFilePreviewKind(request.mimeType)));
|
|
10564
|
+
}
|
|
10565
|
+
/** The loaded preview of a file, `undefined` while loading or without one. */
|
|
10566
|
+
getPreview(dataUri) {
|
|
10567
|
+
return dataUri ? this.previewsState().get(dataUri) : undefined;
|
|
10568
|
+
}
|
|
10569
|
+
/**
|
|
10570
|
+
* Turns a video preview whose media reports no video track into an audio preview of the same
|
|
10571
|
+
* blob URL, as a voice note may be stored with a video mime type; call it at `loadedmetadata`.
|
|
10572
|
+
*/
|
|
10573
|
+
applyVideoMetadata(dataUri, video) {
|
|
10574
|
+
const preview = this.previewsState().get(dataUri);
|
|
10575
|
+
if (preview?.kind === 'video' && video.videoWidth === 0 && video.videoHeight === 0) {
|
|
10576
|
+
const previews = new Map(this.previewsState());
|
|
10577
|
+
previews.set(dataUri, { ...preview, kind: 'audio' });
|
|
10578
|
+
this.previewsState.set(previews);
|
|
10579
|
+
}
|
|
10580
|
+
}
|
|
10581
|
+
loadPreviews(viewUuid, requests) {
|
|
10582
|
+
untracked(() => {
|
|
10583
|
+
const previews = new Map(this.previewsState());
|
|
10584
|
+
previews.forEach((preview, dataUri) => {
|
|
10585
|
+
if (!this.isRequested(dataUri)) {
|
|
10586
|
+
URL.revokeObjectURL(preview.url);
|
|
10587
|
+
previews.delete(dataUri);
|
|
10588
|
+
}
|
|
10589
|
+
});
|
|
10590
|
+
if (previews.size !== this.previewsState().size) {
|
|
10591
|
+
this.previewsState.set(previews);
|
|
10592
|
+
}
|
|
10593
|
+
this.failedDataUris.forEach((dataUri) => {
|
|
10594
|
+
if (!this.isRequested(dataUri)) {
|
|
10595
|
+
this.failedDataUris.delete(dataUri);
|
|
10596
|
+
}
|
|
10597
|
+
});
|
|
10598
|
+
if (viewUuid) {
|
|
10599
|
+
requests
|
|
10600
|
+
.filter((request) => !previews.has(request.dataUri))
|
|
10601
|
+
.filter((request) => !this.loadingDataUris.has(request.dataUri))
|
|
10602
|
+
.filter((request) => !this.failedDataUris.has(request.dataUri))
|
|
10603
|
+
.forEach((request) => this.loadPreview(viewUuid, request));
|
|
10604
|
+
}
|
|
10605
|
+
});
|
|
10606
|
+
}
|
|
10607
|
+
async loadPreview(viewUuid, request) {
|
|
10608
|
+
this.loadingDataUris.add(request.dataUri);
|
|
10609
|
+
let data;
|
|
10610
|
+
try {
|
|
10611
|
+
data = await this.viewContext.downloadResource(viewUuid, request.key);
|
|
10612
|
+
}
|
|
10613
|
+
catch {
|
|
10614
|
+
// A file the server has not registered keeps the card without its preview.
|
|
10615
|
+
if (this.isRequested(request.dataUri)) {
|
|
10616
|
+
this.failedDataUris.add(request.dataUri);
|
|
10617
|
+
}
|
|
10618
|
+
return;
|
|
10619
|
+
}
|
|
10620
|
+
finally {
|
|
10621
|
+
this.loadingDataUris.delete(request.dataUri);
|
|
10622
|
+
}
|
|
10623
|
+
if (this.destroyed || !this.isRequested(request.dataUri)) {
|
|
10624
|
+
return;
|
|
10625
|
+
}
|
|
10626
|
+
const url = URL.createObjectURL(new Blob([data], { type: request.mimeType }));
|
|
10627
|
+
const previews = new Map(this.previewsState());
|
|
10628
|
+
previews.set(request.dataUri, { kind: getFilePreviewKind(request.mimeType), url });
|
|
10629
|
+
this.previewsState.set(previews);
|
|
10630
|
+
}
|
|
10631
|
+
isRequested(dataUri) {
|
|
10632
|
+
return this.requests().some((request) => request.dataUri === dataUri);
|
|
10633
|
+
}
|
|
10634
|
+
}
|
|
9768
10635
|
|
|
9769
10636
|
class SmartFileEditorComponent {
|
|
9770
10637
|
constructor() {
|
|
9771
10638
|
this.cdr = inject(ChangeDetectorRef);
|
|
10639
|
+
this.previewLoader = new FilePreviewLoader();
|
|
9772
10640
|
this.uploadFilesEvent = output();
|
|
9773
10641
|
// ---- ControlValueAccessor implementation ----
|
|
9774
10642
|
this.onChange = (value) => { };
|
|
@@ -9796,8 +10664,23 @@ class SmartFileEditorComponent {
|
|
|
9796
10664
|
undoIcon: { identifier: 'undo' },
|
|
9797
10665
|
};
|
|
9798
10666
|
}
|
|
10667
|
+
this.requestPreview();
|
|
9799
10668
|
this.cdr.markForCheck();
|
|
9800
10669
|
}
|
|
10670
|
+
/** With thumbnail FULL the current value is previewed; any other value drops the preview. */
|
|
10671
|
+
requestPreview() {
|
|
10672
|
+
const value = this._widgetInstance.value;
|
|
10673
|
+
const isFull = this._widgetInstance.fileUploaderProperties?.thumbnail === FileUploaderThumbnail.FULL;
|
|
10674
|
+
this.previewLoader.setRequests(isFull && this.isTypeSupported(value)
|
|
10675
|
+
? [
|
|
10676
|
+
{
|
|
10677
|
+
key: createSingleFilePreviewKey(this._widgetInstance.key),
|
|
10678
|
+
dataUri: value.dataUri,
|
|
10679
|
+
mimeType: value.mimeType,
|
|
10680
|
+
},
|
|
10681
|
+
]
|
|
10682
|
+
: []);
|
|
10683
|
+
}
|
|
9801
10684
|
upload(event) {
|
|
9802
10685
|
if (!event.files || event.files.length == 0) {
|
|
9803
10686
|
return;
|
|
@@ -9818,10 +10701,12 @@ class SmartFileEditorComponent {
|
|
|
9818
10701
|
clear() {
|
|
9819
10702
|
this.clearedFile = this._widgetInstance.value;
|
|
9820
10703
|
this._widgetInstance.value = null;
|
|
10704
|
+
this.requestPreview();
|
|
9821
10705
|
this.onChange(null);
|
|
9822
10706
|
}
|
|
9823
10707
|
undoClear() {
|
|
9824
10708
|
this._widgetInstance.value = this.clearedFile;
|
|
10709
|
+
this.requestPreview();
|
|
9825
10710
|
this.onChange(this.clearedFile);
|
|
9826
10711
|
this.clearedFile = undefined;
|
|
9827
10712
|
}
|
|
@@ -9871,6 +10756,7 @@ class SmartFileEditorComponent {
|
|
|
9871
10756
|
if (value) {
|
|
9872
10757
|
this.clearedFile = undefined;
|
|
9873
10758
|
}
|
|
10759
|
+
this.requestPreview();
|
|
9874
10760
|
this.cdr.markForCheck();
|
|
9875
10761
|
}
|
|
9876
10762
|
}
|
|
@@ -9896,6 +10782,15 @@ class SmartFileEditorComponent {
|
|
|
9896
10782
|
get fileIcon() {
|
|
9897
10783
|
return 'insert_drive_file';
|
|
9898
10784
|
}
|
|
10785
|
+
get preview() {
|
|
10786
|
+
return this.previewLoader.getPreview(this._widgetInstance.value?.dataUri);
|
|
10787
|
+
}
|
|
10788
|
+
onPreviewMetadata(event) {
|
|
10789
|
+
const dataUri = this._widgetInstance.value?.dataUri;
|
|
10790
|
+
if (dataUri) {
|
|
10791
|
+
this.previewLoader.applyVideoMetadata(dataUri, event.target);
|
|
10792
|
+
}
|
|
10793
|
+
}
|
|
9899
10794
|
get changeButton() {
|
|
9900
10795
|
var props = this._widgetInstance.fileUploaderProperties;
|
|
9901
10796
|
var baseAction = props.changeAction ?? props.uploadAction;
|
|
@@ -9940,7 +10835,7 @@ class SmartFileEditorComponent {
|
|
|
9940
10835
|
useExisting: forwardRef(() => SmartFileEditorComponent),
|
|
9941
10836
|
multi: true,
|
|
9942
10837
|
},
|
|
9943
|
-
], 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 [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 <div class=\"uploadedFile\">\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\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}.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"], 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"] }] }); }
|
|
10838
|
+
], 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"] }] }); }
|
|
9944
10839
|
}
|
|
9945
10840
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartFileEditorComponent, decorators: [{
|
|
9946
10841
|
type: Component,
|
|
@@ -9950,7 +10845,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
9950
10845
|
useExisting: forwardRef(() => SmartFileEditorComponent),
|
|
9951
10846
|
multi: true,
|
|
9952
10847
|
},
|
|
9953
|
-
], 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 [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 <div class=\"uploadedFile\">\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\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}.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"] }]
|
|
10848
|
+
], 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"] }]
|
|
9954
10849
|
}], propDecorators: { widgetInstance: [{
|
|
9955
10850
|
type: Input
|
|
9956
10851
|
}], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }] } });
|
|
@@ -10859,6 +11754,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
10859
11754
|
class SmartMultiFileEditorComponent {
|
|
10860
11755
|
constructor() {
|
|
10861
11756
|
this.cdr = inject(ChangeDetectorRef);
|
|
11757
|
+
this.previewLoader = new FilePreviewLoader();
|
|
10862
11758
|
this.uploadFilesEvent = output();
|
|
10863
11759
|
this.fileEvent = output();
|
|
10864
11760
|
// ---- ControlValueAccessor implementation ----
|
|
@@ -10881,8 +11777,28 @@ class SmartMultiFileEditorComponent {
|
|
|
10881
11777
|
? structuredClone(value)
|
|
10882
11778
|
: JSON.parse(JSON.stringify(value));
|
|
10883
11779
|
this.isDisabled = !!value.isDisabled;
|
|
11780
|
+
const texts = value.multiFileUploaderProperties?.uploadDescriptor?.texts;
|
|
11781
|
+
this.playTooltip = { tooltip: UploadWidgetUtils.resolveTexts(texts).play };
|
|
11782
|
+
this.requestPreviews();
|
|
10884
11783
|
this.cdr.markForCheck();
|
|
10885
11784
|
}
|
|
11785
|
+
/** With thumbnail FULL the image rows and the expanded player's row are previewed. */
|
|
11786
|
+
requestPreviews() {
|
|
11787
|
+
const files = this.isFullThumbnail && Array.isArray(this._widgetInstance.value)
|
|
11788
|
+
? this._widgetInstance.value
|
|
11789
|
+
: [];
|
|
11790
|
+
this.previewLoader.setRequests(files
|
|
11791
|
+
.filter((file) => this.getPreviewKind(file) === 'image' || file.dataUri === this.expandedDataUri)
|
|
11792
|
+
.map((file) => ({
|
|
11793
|
+
key: createListFilePreviewKey(this._widgetInstance.key, file.dataUri),
|
|
11794
|
+
dataUri: file.dataUri,
|
|
11795
|
+
mimeType: file.mimeType,
|
|
11796
|
+
})));
|
|
11797
|
+
}
|
|
11798
|
+
togglePlayer(dataUri) {
|
|
11799
|
+
this.expandedDataUri = this.expandedDataUri === dataUri ? undefined : dataUri;
|
|
11800
|
+
this.requestPreviews();
|
|
11801
|
+
}
|
|
10886
11802
|
upload(event) {
|
|
10887
11803
|
if (!event.files || event.files.length == 0) {
|
|
10888
11804
|
return;
|
|
@@ -10956,6 +11872,7 @@ class SmartMultiFileEditorComponent {
|
|
|
10956
11872
|
writeValue(value) {
|
|
10957
11873
|
if (value !== undefined && this._widgetInstance) {
|
|
10958
11874
|
this._widgetInstance.value = value;
|
|
11875
|
+
this.requestPreviews();
|
|
10959
11876
|
this.cdr.markForCheck();
|
|
10960
11877
|
}
|
|
10961
11878
|
}
|
|
@@ -10976,6 +11893,19 @@ class SmartMultiFileEditorComponent {
|
|
|
10976
11893
|
get fileIcon() {
|
|
10977
11894
|
return 'insert_drive_file';
|
|
10978
11895
|
}
|
|
11896
|
+
get isFullThumbnail() {
|
|
11897
|
+
return (this._widgetInstance.multiFileUploaderProperties?.thumbnail === FileUploaderThumbnail.FULL);
|
|
11898
|
+
}
|
|
11899
|
+
/** The preview kind of a row, `undefined` without thumbnail FULL or for another file type. */
|
|
11900
|
+
getPreviewKind(file) {
|
|
11901
|
+
return this.isFullThumbnail ? getFilePreviewKind(file?.mimeType) : undefined;
|
|
11902
|
+
}
|
|
11903
|
+
getPreview(dataUri) {
|
|
11904
|
+
return this.previewLoader.getPreview(dataUri);
|
|
11905
|
+
}
|
|
11906
|
+
onPreviewMetadata(dataUri, event) {
|
|
11907
|
+
this.previewLoader.applyVideoMetadata(dataUri, event.target);
|
|
11908
|
+
}
|
|
10979
11909
|
get errorIcon() {
|
|
10980
11910
|
return 'error';
|
|
10981
11911
|
}
|
|
@@ -10986,7 +11916,7 @@ class SmartMultiFileEditorComponent {
|
|
|
10986
11916
|
useExisting: forwardRef(() => SmartMultiFileEditorComponent),
|
|
10987
11917
|
multi: true,
|
|
10988
11918
|
},
|
|
10989
|
-
], 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 [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 <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\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 }\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}.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: "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"], outputs: ["uploadFilesEvent"] }] }); }
|
|
11919
|
+
], 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"] }] }); }
|
|
10990
11920
|
}
|
|
10991
11921
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartMultiFileEditorComponent, decorators: [{
|
|
10992
11922
|
type: Component,
|
|
@@ -10996,7 +11926,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
10996
11926
|
useExisting: forwardRef(() => SmartMultiFileEditorComponent),
|
|
10997
11927
|
multi: true,
|
|
10998
11928
|
},
|
|
10999
|
-
], imports: [
|
|
11929
|
+
], imports: [
|
|
11930
|
+
NgClass,
|
|
11931
|
+
NgStyle,
|
|
11932
|
+
SmartTooltipDirective,
|
|
11933
|
+
UiActionToolbarComponent,
|
|
11934
|
+
SmartIconComponent,
|
|
11935
|
+
UploadWidgetComponent,
|
|
11936
|
+
], 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"] }]
|
|
11000
11937
|
}], propDecorators: { widgetInstance: [{
|
|
11001
11938
|
type: Input
|
|
11002
11939
|
}], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }], fileEvent: [{ type: i0.Output, args: ["fileEvent"] }] } });
|
|
@@ -11226,7 +12163,7 @@ class SmartVoiceRecorderComponent {
|
|
|
11226
12163
|
useExisting: forwardRef(() => SmartVoiceRecorderComponent),
|
|
11227
12164
|
multi: true,
|
|
11228
12165
|
},
|
|
11229
|
-
], 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"], outputs: ["recordingSaved"] }] }); }
|
|
12166
|
+
], 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", "actionsInHost"], outputs: ["recordingSaved"] }] }); }
|
|
11230
12167
|
}
|
|
11231
12168
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartVoiceRecorderComponent, decorators: [{
|
|
11232
12169
|
type: Component,
|
|
@@ -15947,7 +16884,8 @@ class Table {
|
|
|
15947
16884
|
renderRows() {
|
|
15948
16885
|
this.createCellToActionMap();
|
|
15949
16886
|
this.rowMenuActionModelArrayCache.clear();
|
|
15950
|
-
|
|
16887
|
+
// The mobile template renders from its own loop and has no material table.
|
|
16888
|
+
this.myTableChild?.renderRows();
|
|
15951
16889
|
}
|
|
15952
16890
|
createCellToActionMap() {
|
|
15953
16891
|
this.cellToActionMap = SmartGridToolbarActionsUtil.calculateCellToActionMap(this.smartTable.tableRows);
|
|
@@ -16781,11 +17719,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
16781
17719
|
|
|
16782
17720
|
class MobileTableComponent extends Table {
|
|
16783
17721
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MobileTableComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
16784
|
-
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" }] }); }
|
|
17722
|
+
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" }] }); }
|
|
16785
17723
|
}
|
|
16786
17724
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MobileTableComponent, decorators: [{
|
|
16787
17725
|
type: Component,
|
|
16788
|
-
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"] }]
|
|
17726
|
+
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"] }]
|
|
16789
17727
|
}] });
|
|
16790
17728
|
|
|
16791
17729
|
class SmarttableComponent {
|
|
@@ -27959,7 +28897,7 @@ class UiActionFileUploadDialogComponent {
|
|
|
27959
28897
|
this.service.cancel();
|
|
27960
28898
|
}
|
|
27961
28899
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UiActionFileUploadDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
27962
|
-
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"], 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"] }] }); }
|
|
28900
|
+
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"] }] }); }
|
|
27963
28901
|
}
|
|
27964
28902
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UiActionFileUploadDialogComponent, decorators: [{
|
|
27965
28903
|
type: Component,
|
|
@@ -28834,5 +29772,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
28834
29772
|
* Generated bundle index. Do not edit.
|
|
28835
29773
|
*/
|
|
28836
29774
|
|
|
28837
|
-
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, 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 };
|
|
29775
|
+
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 };
|
|
28838
29776
|
//# sourceMappingURL=smartbit4all-ng-client.mjs.map
|