@smartbit4all/ng-client 7.3.40 → 7.3.41
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 +14 -0
- package/fesm2022/smartbit4all-ng-client.mjs +572 -94
- package/fesm2022/smartbit4all-ng-client.mjs.map +1 -1
- package/package.json +1 -1
- package/smartbit4all-ng-client-7.3.41.tgz +0 -0
- package/types/smartbit4all-ng-client.d.ts +99 -3
- 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, NgZone, DestroyRef, forwardRef, createComponent, EnvironmentInjector, ApplicationRef, ChangeDetectionStrategy, ViewEncapsulation, Pipe, viewChild, 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';
|
|
@@ -7805,6 +7805,48 @@ var CodeEditorLang;
|
|
|
7805
7805
|
* Do not edit the class manually.
|
|
7806
7806
|
*/
|
|
7807
7807
|
|
|
7808
|
+
/**
|
|
7809
|
+
* Form layout definition
|
|
7810
|
+
* Contains form layout definition objects.
|
|
7811
|
+
*
|
|
7812
|
+
* The version of the OpenAPI document: 1.0.0
|
|
7813
|
+
* Contact: info@it4all.hu
|
|
7814
|
+
*
|
|
7815
|
+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
|
7816
|
+
* https://openapi-generator.tech
|
|
7817
|
+
* Do not edit the class manually.
|
|
7818
|
+
*/
|
|
7819
|
+
/**
|
|
7820
|
+
* 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.
|
|
7821
|
+
*/
|
|
7822
|
+
var FileUploaderSource;
|
|
7823
|
+
(function (FileUploaderSource) {
|
|
7824
|
+
FileUploaderSource["FILE"] = "FILE";
|
|
7825
|
+
FileUploaderSource["CAMERA_PHOTO"] = "CAMERA_PHOTO";
|
|
7826
|
+
FileUploaderSource["CAMERA_VIDEO"] = "CAMERA_VIDEO";
|
|
7827
|
+
FileUploaderSource["MICROPHONE"] = "MICROPHONE";
|
|
7828
|
+
})(FileUploaderSource || (FileUploaderSource = {}));
|
|
7829
|
+
|
|
7830
|
+
/**
|
|
7831
|
+
* Form layout definition
|
|
7832
|
+
* Contains form layout definition objects.
|
|
7833
|
+
*
|
|
7834
|
+
* The version of the OpenAPI document: 1.0.0
|
|
7835
|
+
* Contact: info@it4all.hu
|
|
7836
|
+
*
|
|
7837
|
+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
|
7838
|
+
* https://openapi-generator.tech
|
|
7839
|
+
* Do not edit the class manually.
|
|
7840
|
+
*/
|
|
7841
|
+
/**
|
|
7842
|
+
* 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.
|
|
7843
|
+
*/
|
|
7844
|
+
var FileUploaderThumbnail;
|
|
7845
|
+
(function (FileUploaderThumbnail) {
|
|
7846
|
+
FileUploaderThumbnail["NONE"] = "NONE";
|
|
7847
|
+
FileUploaderThumbnail["FULL"] = "FULL";
|
|
7848
|
+
})(FileUploaderThumbnail || (FileUploaderThumbnail = {}));
|
|
7849
|
+
|
|
7808
7850
|
/**
|
|
7809
7851
|
* Form layout definition
|
|
7810
7852
|
* Contains form layout definition objects.
|
|
@@ -9057,7 +9099,55 @@ class UploadWidgetUtils {
|
|
|
9057
9099
|
const value = parseFloat((bytes / Math.pow(k, i)).toFixed(1));
|
|
9058
9100
|
return `${value} ${sizes[i]}`;
|
|
9059
9101
|
}
|
|
9060
|
-
|
|
9102
|
+
/** The recording mime of a media kind: webm where the browser records it, else mp4 (Safari). */
|
|
9103
|
+
static chooseRecorderMimeType(kind) {
|
|
9104
|
+
return MediaRecorder.isTypeSupported(`${kind}/webm`) ? `${kind}/webm` : `${kind}/mp4`;
|
|
9105
|
+
}
|
|
9106
|
+
/** A recording as a file named by the prefix and the local time, its extension by its mime. */
|
|
9107
|
+
static createRecordingFile(blob, prefix, fallbackMimeType) {
|
|
9108
|
+
const formattedDate = new Date().toLocaleString('sv-SE').replace(' ', '_').replace(/:/g, '-');
|
|
9109
|
+
const mimeType = blob.type || fallbackMimeType;
|
|
9110
|
+
const ext = mimeType.includes('webm') ? 'webm' : 'mp4';
|
|
9111
|
+
return new File([blob], `${prefix}_${formattedDate}.${ext}`, { type: mimeType });
|
|
9112
|
+
}
|
|
9113
|
+
/** The zone's texts: a descriptor's text per key, the client's default for a missing one. */
|
|
9114
|
+
static resolveTexts(texts) {
|
|
9115
|
+
return { ...UPLOAD_ZONE_DEFAULT_TEXTS, ...texts };
|
|
9116
|
+
}
|
|
9117
|
+
/** Fills the `{name}` placeholders of a text, which the server sends raw. */
|
|
9118
|
+
static fillPlaceholders(text, values) {
|
|
9119
|
+
return text.replace(/\{(\w+)\}/g, (placeholder, name) => name in values ? String(values[name]) : placeholder);
|
|
9120
|
+
}
|
|
9121
|
+
}
|
|
9122
|
+
/** The client's text of every key, shown where the descriptor's texts lack the key. */
|
|
9123
|
+
const UPLOAD_ZONE_DEFAULT_TEXTS = {
|
|
9124
|
+
addFile: 'Dokumentum hozzáadása',
|
|
9125
|
+
browseOrDrag: 'tallózás vagy behúzás',
|
|
9126
|
+
maxSize: 'max. {size} MB',
|
|
9127
|
+
upload: 'Feltöltés',
|
|
9128
|
+
unsupportedFormat: '"{fileName}" - nem támogatott formátum',
|
|
9129
|
+
fileTooLarge: '"{fileName}" - túl nagy fájl (max {size} MB)',
|
|
9130
|
+
capturePhoto: 'Fotó készítése',
|
|
9131
|
+
captureVideo: 'Videó készítése',
|
|
9132
|
+
recordAudio: 'Hangfelvétel',
|
|
9133
|
+
photo: 'fotó',
|
|
9134
|
+
video: 'videó',
|
|
9135
|
+
audio: 'hangfelvétel',
|
|
9136
|
+
or: 'vagy',
|
|
9137
|
+
captureOnlyHint: '{sources} készítése',
|
|
9138
|
+
takePicture: 'Kép készítése',
|
|
9139
|
+
cameraOn: 'Kamera bekapcsolása',
|
|
9140
|
+
cameraOff: 'Kamera kikapcsolása',
|
|
9141
|
+
switchOnCameraHint: 'Kapcsolja be a kamerát a kép készítéséhez!',
|
|
9142
|
+
switchOnForVideo: 'Kapcsolja be a kamerát a videó felvételéhez!',
|
|
9143
|
+
startRecording: 'Felvétel indítása',
|
|
9144
|
+
stopRecording: 'Felvétel leállítása',
|
|
9145
|
+
recording: 'Felvétel folyamatban…',
|
|
9146
|
+
pickVideo: 'Videó kiválasztása',
|
|
9147
|
+
cameraError: 'A kamera nem érhető el.',
|
|
9148
|
+
microphoneError: 'A mikrofon nem érhető el.',
|
|
9149
|
+
play: 'Lejátszás',
|
|
9150
|
+
};
|
|
9061
9151
|
|
|
9062
9152
|
class SmartfileuploaderComponent {
|
|
9063
9153
|
constructor() {
|
|
@@ -9077,6 +9167,10 @@ class SmartfileuploaderComponent {
|
|
|
9077
9167
|
...(ngDevMode ? [{ debugName: "autoUpload" }] : /* istanbul ignore next */ []));
|
|
9078
9168
|
this.isDisabled = input(false, /* @ts-ignore */
|
|
9079
9169
|
...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
9170
|
+
/** False in a capture-only zone: no picker and drop zone, the list and the checks stay. The
|
|
9171
|
+
* upload button goes with the picker, so a host hiding it needs autoUpload. */
|
|
9172
|
+
this.pickerShown = input(true, /* @ts-ignore */
|
|
9173
|
+
...(ngDevMode ? [{ debugName: "pickerShown" }] : /* istanbul ignore next */ []));
|
|
9080
9174
|
this.isDragOver = false;
|
|
9081
9175
|
this.errors = [];
|
|
9082
9176
|
this.previewUrls = new Map();
|
|
@@ -9084,13 +9178,14 @@ class SmartfileuploaderComponent {
|
|
|
9084
9178
|
ngOnInit() {
|
|
9085
9179
|
const defaultI18n = {
|
|
9086
9180
|
addFile: 'dokumentum hozzáadása',
|
|
9087
|
-
browseOrDrag:
|
|
9181
|
+
browseOrDrag: UPLOAD_ZONE_DEFAULT_TEXTS.browseOrDrag,
|
|
9088
9182
|
maxSize: `max. ${this.maxSizeMb()} MB`,
|
|
9089
9183
|
formats: 'PDF, docx, xls formátum',
|
|
9090
|
-
upload:
|
|
9184
|
+
upload: UPLOAD_ZONE_DEFAULT_TEXTS.upload,
|
|
9091
9185
|
};
|
|
9092
9186
|
this.i18n = { ...defaultI18n, ...this.i18n };
|
|
9093
9187
|
}
|
|
9188
|
+
/** Checks the formats and the size of picked, dropped and captured files alike. */
|
|
9094
9189
|
processFiles(fileList) {
|
|
9095
9190
|
this.errors = [];
|
|
9096
9191
|
var validFiles = [];
|
|
@@ -9104,12 +9199,17 @@ class SmartfileuploaderComponent {
|
|
|
9104
9199
|
.map((f) => f.replace('.', '').trim().toLowerCase());
|
|
9105
9200
|
var isAllowed = trimmedFormats.includes(ext);
|
|
9106
9201
|
if (!isAllowed) {
|
|
9107
|
-
this.
|
|
9202
|
+
const message = this.i18n?.unsupportedFormat ?? UPLOAD_ZONE_DEFAULT_TEXTS.unsupportedFormat;
|
|
9203
|
+
this.errors.push(UploadWidgetUtils.fillPlaceholders(message, { fileName: file.name }));
|
|
9108
9204
|
hasError = true;
|
|
9109
9205
|
}
|
|
9110
9206
|
}
|
|
9111
9207
|
if (file.size > this.maxSizeMb() * 1024 * 1024) {
|
|
9112
|
-
|
|
9208
|
+
const message = this.i18n?.fileTooLarge ?? UPLOAD_ZONE_DEFAULT_TEXTS.fileTooLarge;
|
|
9209
|
+
this.errors.push(UploadWidgetUtils.fillPlaceholders(message, {
|
|
9210
|
+
fileName: file.name,
|
|
9211
|
+
size: this.maxSizeMb(),
|
|
9212
|
+
}));
|
|
9113
9213
|
hasError = true;
|
|
9114
9214
|
}
|
|
9115
9215
|
if (!hasError) {
|
|
@@ -9134,6 +9234,9 @@ class SmartfileuploaderComponent {
|
|
|
9134
9234
|
}
|
|
9135
9235
|
}
|
|
9136
9236
|
onDrop(event) {
|
|
9237
|
+
if (!this.pickerShown()) {
|
|
9238
|
+
return;
|
|
9239
|
+
}
|
|
9137
9240
|
event.preventDefault();
|
|
9138
9241
|
this.isDragOver = false;
|
|
9139
9242
|
if (!event.dataTransfer?.files)
|
|
@@ -9142,6 +9245,9 @@ class SmartfileuploaderComponent {
|
|
|
9142
9245
|
this.processFiles(fileList);
|
|
9143
9246
|
}
|
|
9144
9247
|
onDragOver(event) {
|
|
9248
|
+
if (!this.pickerShown()) {
|
|
9249
|
+
return;
|
|
9250
|
+
}
|
|
9145
9251
|
event.preventDefault();
|
|
9146
9252
|
this.isDragOver = true;
|
|
9147
9253
|
}
|
|
@@ -9228,68 +9334,144 @@ class SmartfileuploaderComponent {
|
|
|
9228
9334
|
};
|
|
9229
9335
|
}
|
|
9230
9336
|
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
|
|
9337
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SmartfileuploaderComponent, isStandalone: true, selector: "smartfileuploader", inputs: { uploadCallback: { classPropertyName: "uploadCallback", publicName: "uploadCallback", isSignal: true, isRequired: true, transformFunction: null }, fileFormats: { classPropertyName: "fileFormats", publicName: "fileFormats", isSignal: true, isRequired: false, transformFunction: null }, maxSizeMb: { classPropertyName: "maxSizeMb", publicName: "maxSizeMb", isSignal: true, isRequired: false, transformFunction: null }, i18n: { classPropertyName: "i18n", publicName: "i18n", isSignal: false, isRequired: false, transformFunction: null }, useIconButton: { classPropertyName: "useIconButton", publicName: "useIconButton", isSignal: true, isRequired: false, transformFunction: null }, isMultiple: { classPropertyName: "isMultiple", publicName: "isMultiple", isSignal: true, isRequired: false, transformFunction: null }, autoUpload: { classPropertyName: "autoUpload", publicName: "autoUpload", isSignal: true, isRequired: false, transformFunction: null }, isDisabled: { classPropertyName: "isDisabled", publicName: "isDisabled", isSignal: true, isRequired: false, transformFunction: null }, pickerShown: { classPropertyName: "pickerShown", publicName: "pickerShown", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "<div\n class=\"container\"\n [ngClass]=\"{ disabledWidget: isDisabled() }\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [class.dragover]=\"isDragOver\"\n [class.captureOnly]=\"!pickerShown()\"\n>\n @if (pickerShown()) {\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n <input\n #fileInput\n id=\"addFile\"\n placeholder=\"fileInput\"\n type=\"file\"\n (change)=\"getFile($event)\"\n class=\"file\"\n accept=\"{{ fileFormats()?.join(', ') }}\"\n [size]=\"maxSizeMb()\"\n [multiple]=\"isMultiple()\"\n [disabled]=\"isDisabled()\"\n style=\"display: none\"\n />\n <div class=\"fileUploadContentContainer\">\n <div class=\"icon\">\n @if (useIconButton()) {\n <mat-label class=\"addFileButton\">\n <mat-icon fontSet=\"material-symbols-outlined\" color=\"primary\" class=\"addCircle\"\n >add_circle</mat-icon\n >\n </mat-label>\n }\n @if (!useIconButton()) {\n <button mat-raised-button color=\"primary\" class=\"addFileButton\">\n {{ i18n!.addFile }}\n </button>\n }\n </div>\n <div class=\"labels\">\n @if (useIconButton()) {\n <mat-label class=\"label primary title\">\n {{ i18n!.addFile }}\n </mat-label>\n }\n <mat-label class=\"label secondary\"> {{ i18n!.browseOrDrag }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.maxSize }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.formats }} </mat-label>\n </div>\n @if (files.length) {\n <div class=\"uploadButton\">\n <ui-action-button\n (actionClick)=\"uploadFile($event)\"\n [code]=\"'default-upload'\"\n [descriptor]=\"uploadButton\"\n >\n </ui-action-button>\n </div>\n }\n </div>\n </div>\n }\n\n @if (errors.length) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"'error'\" [color]=\"'warn'\"></smart-icon>\n <div class=\"errors\">\n @for (error of errors; track error) {\n <span>{{ error }}</span>\n }\n </div>\n </div>\n }\n\n @if (files.length) {\n <div class=\"uploadedFilesContainer\">\n @for (file of files; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @if (filePreviewUrl(file)) {\n <img [src]=\"filePreviewUrl(file)\" [alt]=\"file.name\" width=\"50\" height=\"50\" />\n } @else {\n <smart-icon [icon]=\"'insert_drive_file'\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file.name }}</span>\n <span class=\"fileSize\">{{ formatSize(file.size) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file)\"\n [attr.data-testid]=\"'default_download'\"\n ></smart-icon>\n <smart-icon\n class=\"removeIcon\"\n icon=\"delete\"\n [attr.data-testid]=\"'default_remove'\"\n (click)=\"remove(i)\"\n ></smart-icon>\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem}.container{display:flex;flex-direction:column;border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.container.captureOnly{border:none}.fileContainer{cursor:pointer;padding:1rem;display:flex;flex-direction:column}.fileContainer:hover{background-color:rgb(from var(--border-color) r g b / .1)}.disabledWidget ::ng-deep .fileContainer:hover{background-color:unset;cursor:unset}.disabledWidget ::ng-deep *{opacity:.85}.dragover{background-color:rgb(from var(--border-color) r g b / .1)}.fileUploadContentContainer{display:flex;flex-direction:row;gap:1rem}.icon mat-icon{font-size:3rem;width:unset;height:unset}.icon{align-content:center}.labels{flex:1;display:flex;flex-direction:column;justify-content:center;width:fit-content}.uploadButton{align-content:center}.primary{color:var(--primary-color)}.secondary{color:var(--gray)}.label{margin:unset}.subLabel{font-size:smaller}.title{padding-bottom:.3rem}.errorMessage,.uploadedFilesContainer{border-top:2px solid var(--border-color)}.errorMessage{display:flex;flex-direction:row;align-items:center;color:var(--warn-color);padding:.5rem 1rem;gap:1rem}.errorMessage ::ng-deep mat-icon{color:var(--warn-color)}.errors{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0;gap:1rem}.uploadedFile:last-child{border-bottom:none}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }] }); }
|
|
9232
9338
|
}
|
|
9233
9339
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartfileuploaderComponent, decorators: [{
|
|
9234
9340
|
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
|
|
9341
|
+
args: [{ selector: 'smartfileuploader', imports: [NgClass, MatLabel, MatIcon, MatButton, UiActionButtonComponent, SmartIconComponent], template: "<div\n class=\"container\"\n [ngClass]=\"{ disabledWidget: isDisabled() }\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [class.dragover]=\"isDragOver\"\n [class.captureOnly]=\"!pickerShown()\"\n>\n @if (pickerShown()) {\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n <input\n #fileInput\n id=\"addFile\"\n placeholder=\"fileInput\"\n type=\"file\"\n (change)=\"getFile($event)\"\n class=\"file\"\n accept=\"{{ fileFormats()?.join(', ') }}\"\n [size]=\"maxSizeMb()\"\n [multiple]=\"isMultiple()\"\n [disabled]=\"isDisabled()\"\n style=\"display: none\"\n />\n <div class=\"fileUploadContentContainer\">\n <div class=\"icon\">\n @if (useIconButton()) {\n <mat-label class=\"addFileButton\">\n <mat-icon fontSet=\"material-symbols-outlined\" color=\"primary\" class=\"addCircle\"\n >add_circle</mat-icon\n >\n </mat-label>\n }\n @if (!useIconButton()) {\n <button mat-raised-button color=\"primary\" class=\"addFileButton\">\n {{ i18n!.addFile }}\n </button>\n }\n </div>\n <div class=\"labels\">\n @if (useIconButton()) {\n <mat-label class=\"label primary title\">\n {{ i18n!.addFile }}\n </mat-label>\n }\n <mat-label class=\"label secondary\"> {{ i18n!.browseOrDrag }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.maxSize }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.formats }} </mat-label>\n </div>\n @if (files.length) {\n <div class=\"uploadButton\">\n <ui-action-button\n (actionClick)=\"uploadFile($event)\"\n [code]=\"'default-upload'\"\n [descriptor]=\"uploadButton\"\n >\n </ui-action-button>\n </div>\n }\n </div>\n </div>\n }\n\n @if (errors.length) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"'error'\" [color]=\"'warn'\"></smart-icon>\n <div class=\"errors\">\n @for (error of errors; track error) {\n <span>{{ error }}</span>\n }\n </div>\n </div>\n }\n\n @if (files.length) {\n <div class=\"uploadedFilesContainer\">\n @for (file of files; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @if (filePreviewUrl(file)) {\n <img [src]=\"filePreviewUrl(file)\" [alt]=\"file.name\" width=\"50\" height=\"50\" />\n } @else {\n <smart-icon [icon]=\"'insert_drive_file'\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file.name }}</span>\n <span class=\"fileSize\">{{ formatSize(file.size) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file)\"\n [attr.data-testid]=\"'default_download'\"\n ></smart-icon>\n <smart-icon\n class=\"removeIcon\"\n icon=\"delete\"\n [attr.data-testid]=\"'default_remove'\"\n (click)=\"remove(i)\"\n ></smart-icon>\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem}.container{display:flex;flex-direction:column;border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.container.captureOnly{border:none}.fileContainer{cursor:pointer;padding:1rem;display:flex;flex-direction:column}.fileContainer:hover{background-color:rgb(from var(--border-color) r g b / .1)}.disabledWidget ::ng-deep .fileContainer:hover{background-color:unset;cursor:unset}.disabledWidget ::ng-deep *{opacity:.85}.dragover{background-color:rgb(from var(--border-color) r g b / .1)}.fileUploadContentContainer{display:flex;flex-direction:row;gap:1rem}.icon mat-icon{font-size:3rem;width:unset;height:unset}.icon{align-content:center}.labels{flex:1;display:flex;flex-direction:column;justify-content:center;width:fit-content}.uploadButton{align-content:center}.primary{color:var(--primary-color)}.secondary{color:var(--gray)}.label{margin:unset}.subLabel{font-size:smaller}.title{padding-bottom:.3rem}.errorMessage,.uploadedFilesContainer{border-top:2px solid var(--border-color)}.errorMessage{display:flex;flex-direction:row;align-items:center;color:var(--warn-color);padding:.5rem 1rem;gap:1rem}.errorMessage ::ng-deep mat-icon{color:var(--warn-color)}.errors{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0;gap:1rem}.uploadedFile:last-child{border-bottom:none}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}\n"] }]
|
|
9236
9342
|
}], propDecorators: { fileInput: [{
|
|
9237
9343
|
type: ViewChild,
|
|
9238
9344
|
args: ['fileInput']
|
|
9239
9345
|
}], 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
9346
|
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 }] }] } });
|
|
9347
|
+
}], 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
9348
|
|
|
9243
9349
|
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
9350
|
constructor() {
|
|
9257
9351
|
this.maxFileSize = input(/* @ts-ignore */
|
|
9258
9352
|
...(ngDevMode ? [undefined, { debugName: "maxFileSize" }] : /* istanbul ignore next */ []));
|
|
9353
|
+
/** PHOTO takes a picture; VIDEO records a video with the webcam or picks one on a desktop. */
|
|
9354
|
+
this.mode = input('PHOTO', /* @ts-ignore */
|
|
9355
|
+
...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
9356
|
+
/** True when the zone's capture row carries the button: the widget then opens only by
|
|
9357
|
+
* openCapture(). */
|
|
9358
|
+
this.externalTrigger = input(false, /* @ts-ignore */
|
|
9359
|
+
...(ngDevMode ? [{ debugName: "externalTrigger" }] : /* istanbul ignore next */ []));
|
|
9360
|
+
/** The zone's texts; the buttons follow them. */
|
|
9361
|
+
this.texts = input(UPLOAD_ZONE_DEFAULT_TEXTS, /* @ts-ignore */
|
|
9362
|
+
...(ngDevMode ? [{ debugName: "texts" }] : /* istanbul ignore next */ []));
|
|
9259
9363
|
this.photoCaptured = output();
|
|
9364
|
+
/** The desktop webcam panel of an externally triggered photo widget, opened by openCapture(). */
|
|
9365
|
+
this.isPanelOpen = signal(false, /* @ts-ignore */
|
|
9366
|
+
...(ngDevMode ? [{ debugName: "isPanelOpen" }] : /* istanbul ignore next */ []));
|
|
9260
9367
|
/**
|
|
9261
9368
|
* Both are written after an `await` — the camera stream, the compression — so a signal
|
|
9262
9369
|
* each: the click that started it has long been checked by then.
|
|
9263
9370
|
*/
|
|
9264
9371
|
this.cameraActiveState = signal(false, /* @ts-ignore */
|
|
9265
9372
|
...(ngDevMode ? [{ debugName: "cameraActiveState" }] : /* istanbul ignore next */ []));
|
|
9373
|
+
/** Set when the browser gives no camera, shown in place of the switch-on hint. */
|
|
9374
|
+
this.cameraError = signal(false, /* @ts-ignore */
|
|
9375
|
+
...(ngDevMode ? [{ debugName: "cameraError" }] : /* istanbul ignore next */ []));
|
|
9376
|
+
this.isRecordingState = signal(false, /* @ts-ignore */
|
|
9377
|
+
...(ngDevMode ? [{ debugName: "isRecordingState" }] : /* istanbul ignore next */ []));
|
|
9266
9378
|
this.isCapturedState = signal(false, /* @ts-ignore */
|
|
9267
9379
|
...(ngDevMode ? [{ debugName: "isCapturedState" }] : /* istanbul ignore next */ []));
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9380
|
+
/** Advanced by stopCamera(), so a stream the browser gives after a close is stopped at once. */
|
|
9381
|
+
this.cameraRequest = 0;
|
|
9382
|
+
this.toggleCameraButton = computed(() => this.createButton(this.cameraActive ? this.texts().cameraOff : this.texts().cameraOn, 'power_settings_new', this.cameraActive ? 'red' : 'primary'), /* @ts-ignore */
|
|
9383
|
+
...(ngDevMode ? [{ debugName: "toggleCameraButton" }] : /* istanbul ignore next */ []));
|
|
9384
|
+
this.captureButton = computed(() => this.createButton(this.texts().takePicture, 'camera'), /* @ts-ignore */
|
|
9385
|
+
...(ngDevMode ? [{ debugName: "captureButton" }] : /* istanbul ignore next */ []));
|
|
9386
|
+
this.startRecordingButton = computed(() => this.createButton(this.texts().startRecording, 'fiber_manual_record'), /* @ts-ignore */
|
|
9387
|
+
...(ngDevMode ? [{ debugName: "startRecordingButton" }] : /* istanbul ignore next */ []));
|
|
9388
|
+
this.stopRecordingButton = computed(() => this.createButton(this.texts().stopRecording, 'stop', 'red'), /* @ts-ignore */
|
|
9389
|
+
...(ngDevMode ? [{ debugName: "stopRecordingButton" }] : /* istanbul ignore next */ []));
|
|
9390
|
+
this.pickVideoButton = computed(() => this.createButton(this.texts().pickVideo, 'video_library'), /* @ts-ignore */
|
|
9391
|
+
...(ngDevMode ? [{ debugName: "pickVideoButton" }] : /* istanbul ignore next */ []));
|
|
9392
|
+
}
|
|
9393
|
+
get cameraActive() {
|
|
9394
|
+
return this.cameraActiveState();
|
|
9395
|
+
}
|
|
9396
|
+
set cameraActive(active) {
|
|
9397
|
+
this.cameraActiveState.set(active);
|
|
9398
|
+
}
|
|
9399
|
+
get isRecording() {
|
|
9400
|
+
return this.isRecordingState();
|
|
9401
|
+
}
|
|
9402
|
+
set isRecording(recording) {
|
|
9403
|
+
this.isRecordingState.set(recording);
|
|
9404
|
+
}
|
|
9405
|
+
get isCaptured() {
|
|
9406
|
+
return this.isCapturedState();
|
|
9407
|
+
}
|
|
9408
|
+
set isCaptured(captured) {
|
|
9409
|
+
this.isCapturedState.set(captured);
|
|
9276
9410
|
}
|
|
9277
9411
|
isMobile() {
|
|
9278
9412
|
return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
|
9279
9413
|
}
|
|
9414
|
+
/** A phone opens its own camera, a desktop the webcam panel. */
|
|
9415
|
+
openCapture() {
|
|
9416
|
+
if (this.isMobile()) {
|
|
9417
|
+
this.fileInputRef.nativeElement.click();
|
|
9418
|
+
return;
|
|
9419
|
+
}
|
|
9420
|
+
if (this.isPanelOpen()) {
|
|
9421
|
+
this.stopCamera();
|
|
9422
|
+
}
|
|
9423
|
+
this.isPanelOpen.update((open) => !open);
|
|
9424
|
+
}
|
|
9425
|
+
ngOnDestroy() {
|
|
9426
|
+
this.stopCamera();
|
|
9427
|
+
}
|
|
9280
9428
|
async startCamera() {
|
|
9281
|
-
this.
|
|
9429
|
+
this.cameraError.set(false);
|
|
9430
|
+
const request = ++this.cameraRequest;
|
|
9431
|
+
let stream;
|
|
9432
|
+
try {
|
|
9433
|
+
stream = await this.openCameraStream();
|
|
9434
|
+
}
|
|
9435
|
+
catch {
|
|
9436
|
+
if (request === this.cameraRequest) {
|
|
9437
|
+
this.cameraError.set(true);
|
|
9438
|
+
}
|
|
9439
|
+
return;
|
|
9440
|
+
}
|
|
9441
|
+
if (request !== this.cameraRequest) {
|
|
9442
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
9443
|
+
return;
|
|
9444
|
+
}
|
|
9445
|
+
this.stream = stream;
|
|
9282
9446
|
this.videoRef.nativeElement.srcObject = this.stream;
|
|
9283
9447
|
this.videoRef.nativeElement.play();
|
|
9284
9448
|
this.cameraActive = true;
|
|
9285
9449
|
this.isCaptured = false;
|
|
9286
|
-
this.updateToggleButton();
|
|
9287
9450
|
}
|
|
9451
|
+
/** A video asks for the sound too; a desktop without a microphone records a silent one. */
|
|
9452
|
+
async openCameraStream() {
|
|
9453
|
+
if (this.mode() === 'VIDEO') {
|
|
9454
|
+
try {
|
|
9455
|
+
return await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
|
|
9456
|
+
}
|
|
9457
|
+
catch {
|
|
9458
|
+
// No microphone answers: the camera alone is asked below.
|
|
9459
|
+
}
|
|
9460
|
+
}
|
|
9461
|
+
return navigator.mediaDevices.getUserMedia({ video: true });
|
|
9462
|
+
}
|
|
9463
|
+
/** Closing the camera mid-recording discards the recording. */
|
|
9288
9464
|
stopCamera() {
|
|
9465
|
+
this.cameraRequest++;
|
|
9466
|
+
if (this.recorder) {
|
|
9467
|
+
this.recorder.onstop = null;
|
|
9468
|
+
this.recorder.stop();
|
|
9469
|
+
this.recorder = undefined;
|
|
9470
|
+
}
|
|
9471
|
+
this.isRecording = false;
|
|
9289
9472
|
this.stream?.getTracks().forEach((track) => track.stop());
|
|
9290
9473
|
this.stream = undefined;
|
|
9291
9474
|
this.cameraActive = false;
|
|
9292
|
-
this.updateToggleButton();
|
|
9293
9475
|
}
|
|
9294
9476
|
async capturePhoto() {
|
|
9295
9477
|
const video = this.videoRef.nativeElement;
|
|
@@ -9312,6 +9494,34 @@ class PhotoCaptureWidgetComponent {
|
|
|
9312
9494
|
this.isCaptured = true;
|
|
9313
9495
|
this.stopCamera();
|
|
9314
9496
|
}
|
|
9497
|
+
/** A browser that records neither webm nor mp4 refuses the recorder: the camera error shows. */
|
|
9498
|
+
startRecording() {
|
|
9499
|
+
let recorder;
|
|
9500
|
+
try {
|
|
9501
|
+
recorder = new MediaRecorder(this.stream, {
|
|
9502
|
+
mimeType: UploadWidgetUtils.chooseRecorderMimeType('video'),
|
|
9503
|
+
});
|
|
9504
|
+
}
|
|
9505
|
+
catch {
|
|
9506
|
+
this.cameraError.set(true);
|
|
9507
|
+
return;
|
|
9508
|
+
}
|
|
9509
|
+
const chunks = [];
|
|
9510
|
+
recorder.ondataavailable = (event) => chunks.push(event.data);
|
|
9511
|
+
recorder.onstop = () => {
|
|
9512
|
+
const blob = new Blob(chunks, { type: recorder.mimeType });
|
|
9513
|
+
this.recorder = undefined;
|
|
9514
|
+
this.photoCaptured.emit([UploadWidgetUtils.createRecordingFile(blob, 'video', 'video/mp4')]);
|
|
9515
|
+
this.stopCamera();
|
|
9516
|
+
};
|
|
9517
|
+
recorder.start();
|
|
9518
|
+
this.recorder = recorder;
|
|
9519
|
+
this.isRecording = true;
|
|
9520
|
+
}
|
|
9521
|
+
/** The recording state holds until onstop hands the video over and stops the camera. */
|
|
9522
|
+
stopRecording() {
|
|
9523
|
+
this.recorder?.stop();
|
|
9524
|
+
}
|
|
9315
9525
|
getPhotoDataURL() {
|
|
9316
9526
|
return this.canvasRef.nativeElement.toDataURL('image/png');
|
|
9317
9527
|
}
|
|
@@ -9346,26 +9556,6 @@ class PhotoCaptureWidgetComponent {
|
|
|
9346
9556
|
this.startCamera();
|
|
9347
9557
|
}
|
|
9348
9558
|
}
|
|
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
9559
|
async compressImageFile(file, maxFileSize) {
|
|
9370
9560
|
if (!file.type.startsWith('image/'))
|
|
9371
9561
|
return file;
|
|
@@ -9423,13 +9613,16 @@ class PhotoCaptureWidgetComponent {
|
|
|
9423
9613
|
return file;
|
|
9424
9614
|
}
|
|
9425
9615
|
}
|
|
9616
|
+
createButton(title, icon, color = 'primary') {
|
|
9617
|
+
return { type: UiActionButtonType.RAISED, title, icon, iconPosition: IconPosition.PRE, color };
|
|
9618
|
+
}
|
|
9426
9619
|
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 (!
|
|
9620
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: PhotoCaptureWidgetComponent, isStandalone: true, selector: "photo-capture-widget", inputs: { maxFileSize: { classPropertyName: "maxFileSize", publicName: "maxFileSize", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, externalTrigger: { classPropertyName: "externalTrigger", publicName: "externalTrigger", isSignal: true, isRequired: false, transformFunction: null }, texts: { classPropertyName: "texts", publicName: "texts", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { photoCaptured: "photoCaptured" }, viewQueries: [{ propertyName: "videoRef", first: true, predicate: ["video"], descendants: true }, { propertyName: "canvasRef", first: true, predicate: ["canvas"], descendants: true }, { propertyName: "fileInputRef", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "@if (isMobile()) {\n <input\n #fileInput\n type=\"file\"\n [accept]=\"mode() === 'VIDEO' ? 'video/*' : 'image/*'\"\n capture=\"environment\"\n (change)=\"onFileSelected($event)\"\n style=\"display: none\"\n />\n @if (!externalTrigger()) {\n <div class=\"photoWidgetContainer\">\n <div class=\"controls\">\n <ui-action-button [descriptor]=\"captureButton()\" (actionClick)=\"fileInput.click()\">\n </ui-action-button>\n </div>\n </div>\n }\n} @else if (!externalTrigger() || isPanelOpen()) {\n <div class=\"photoWidgetContainer\">\n <video #video autoplay [muted]=\"true\" [hidden]=\"isCaptured || !cameraActive\"></video>\n <canvas #canvas [hidden]=\"!isCaptured || cameraActive\"></canvas>\n @if (cameraError()) {\n <p class=\"message\">{{ texts().cameraError }}</p>\n } @else if (isRecording) {\n <p class=\"message\">{{ texts().recording }}</p>\n } @else if (!cameraActive && !isCaptured) {\n @if (mode() === 'VIDEO') {\n <p class=\"message\">{{ texts().switchOnForVideo }}</p>\n } @else {\n <p class=\"message\">{{ texts().switchOnCameraHint }}</p>\n }\n }\n <div class=\"controls\">\n <!-- START CAMERA -->\n <ui-action-button\n class=\"toggleCameraButton\"\n [disabled]=\"isRecording\"\n [descriptor]=\"toggleCameraButton()\"\n (actionClick)=\"toggleCamera()\"\n >\n </ui-action-button>\n @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 <!-- 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 } @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 </div>\n </div>\n}\n", styles: [".photoWidgetContainer{border-radius:5px;width:100%;max-width:100%;display:flex;flex-direction:column;justify-content:center;border:1px solid #dee2e6}.message{text-align:center;color:#6b7280}video,canvas{border-top-right-radius:5px;border-top-left-radius:5px;width:100%}.controls{padding:.5rem;display:flex;gap:1rem;flex-direction:row;justify-content:center;background:#f8f9fa;border-top:1px solid #dee2e6;border-bottom-right-radius:5px;border-bottom-left-radius:5px}.controls ::ng-deep button{border-radius:3px}\n"], dependencies: [{ kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
|
|
9428
9621
|
}
|
|
9429
9622
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PhotoCaptureWidgetComponent, decorators: [{
|
|
9430
9623
|
type: Component,
|
|
9431
|
-
args: [{ selector: 'photo-capture-widget', imports: [UiActionButtonComponent], template: "<div class=\"photoWidgetContainer\">\n @if (!
|
|
9432
|
-
}],
|
|
9624
|
+
args: [{ selector: 'photo-capture-widget', imports: [UiActionButtonComponent], template: "@if (isMobile()) {\n <input\n #fileInput\n type=\"file\"\n [accept]=\"mode() === 'VIDEO' ? 'video/*' : 'image/*'\"\n capture=\"environment\"\n (change)=\"onFileSelected($event)\"\n style=\"display: none\"\n />\n @if (!externalTrigger()) {\n <div class=\"photoWidgetContainer\">\n <div class=\"controls\">\n <ui-action-button [descriptor]=\"captureButton()\" (actionClick)=\"fileInput.click()\">\n </ui-action-button>\n </div>\n </div>\n }\n} @else if (!externalTrigger() || isPanelOpen()) {\n <div class=\"photoWidgetContainer\">\n <video #video autoplay [muted]=\"true\" [hidden]=\"isCaptured || !cameraActive\"></video>\n <canvas #canvas [hidden]=\"!isCaptured || cameraActive\"></canvas>\n @if (cameraError()) {\n <p class=\"message\">{{ texts().cameraError }}</p>\n } @else if (isRecording) {\n <p class=\"message\">{{ texts().recording }}</p>\n } @else if (!cameraActive && !isCaptured) {\n @if (mode() === 'VIDEO') {\n <p class=\"message\">{{ texts().switchOnForVideo }}</p>\n } @else {\n <p class=\"message\">{{ texts().switchOnCameraHint }}</p>\n }\n }\n <div class=\"controls\">\n <!-- START CAMERA -->\n <ui-action-button\n class=\"toggleCameraButton\"\n [disabled]=\"isRecording\"\n [descriptor]=\"toggleCameraButton()\"\n (actionClick)=\"toggleCamera()\"\n >\n </ui-action-button>\n @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 <!-- 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 } @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 </div>\n </div>\n}\n", styles: [".photoWidgetContainer{border-radius:5px;width:100%;max-width:100%;display:flex;flex-direction:column;justify-content:center;border:1px solid #dee2e6}.message{text-align:center;color:#6b7280}video,canvas{border-top-right-radius:5px;border-top-left-radius:5px;width:100%}.controls{padding:.5rem;display:flex;gap:1rem;flex-direction:row;justify-content:center;background:#f8f9fa;border-top:1px solid #dee2e6;border-bottom-right-radius:5px;border-bottom-left-radius:5px}.controls ::ng-deep button{border-radius:3px}\n"] }]
|
|
9625
|
+
}], propDecorators: { videoRef: [{
|
|
9433
9626
|
type: ViewChild,
|
|
9434
9627
|
args: ['video']
|
|
9435
9628
|
}], canvasRef: [{
|
|
@@ -9438,7 +9631,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
9438
9631
|
}], fileInputRef: [{
|
|
9439
9632
|
type: ViewChild,
|
|
9440
9633
|
args: ['fileInput']
|
|
9441
|
-
}], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], photoCaptured: [{ type: i0.Output, args: ["photoCaptured"] }] } });
|
|
9634
|
+
}], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], externalTrigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "externalTrigger", required: false }] }], texts: [{ type: i0.Input, args: [{ isSignal: true, alias: "texts", required: false }] }], photoCaptured: [{ type: i0.Output, args: ["photoCaptured"] }] } });
|
|
9442
9635
|
|
|
9443
9636
|
class VoiceRecordWidgetComponent {
|
|
9444
9637
|
constructor() {
|
|
@@ -9446,12 +9639,21 @@ class VoiceRecordWidgetComponent {
|
|
|
9446
9639
|
this.changeDetectorRef = inject(ChangeDetectorRef);
|
|
9447
9640
|
this.openDirection = input('RIGHT', /* @ts-ignore */
|
|
9448
9641
|
...(ngDevMode ? [{ debugName: "openDirection" }] : /* istanbul ignore next */ []));
|
|
9642
|
+
/** The zone's texts, the microphone error line's among them. */
|
|
9643
|
+
this.texts = input(UPLOAD_ZONE_DEFAULT_TEXTS, /* @ts-ignore */
|
|
9644
|
+
...(ngDevMode ? [{ debugName: "texts" }] : /* istanbul ignore next */ []));
|
|
9645
|
+
/** True in the upload zone only; the deprecated recordingUploader shows no error line. */
|
|
9646
|
+
this.showMicrophoneError = input(false, /* @ts-ignore */
|
|
9647
|
+
...(ngDevMode ? [{ debugName: "showMicrophoneError" }] : /* istanbul ignore next */ []));
|
|
9449
9648
|
this.recordingSaved = output();
|
|
9450
9649
|
this.isRecording = false;
|
|
9451
9650
|
this.recordedBlob = null;
|
|
9452
9651
|
this.time = '00:00';
|
|
9453
9652
|
this.isPlaying = false;
|
|
9454
9653
|
this.isWaveformOpen = false;
|
|
9654
|
+
/** Set when the browser gives no microphone, until the next start. */
|
|
9655
|
+
this.microphoneError = signal(false, /* @ts-ignore */
|
|
9656
|
+
...(ngDevMode ? [{ debugName: "microphoneError" }] : /* istanbul ignore next */ []));
|
|
9455
9657
|
this.micButton = {
|
|
9456
9658
|
title: '',
|
|
9457
9659
|
type: UiActionButtonType.ICON,
|
|
@@ -9521,8 +9723,7 @@ class VoiceRecordWidgetComponent {
|
|
|
9521
9723
|
barRadius: 5,
|
|
9522
9724
|
});
|
|
9523
9725
|
this.wavesurfer.setVolume(1);
|
|
9524
|
-
|
|
9525
|
-
const mimeType = MediaRecorder.isTypeSupported('audio/webm') ? 'audio/webm' : 'audio/mp4';
|
|
9726
|
+
const mimeType = UploadWidgetUtils.chooseRecorderMimeType('audio');
|
|
9526
9727
|
this.record = this.wavesurfer.registerPlugin(RecordPlugin.create({
|
|
9527
9728
|
renderRecordedAudio: true,
|
|
9528
9729
|
scrollingWaveform: true,
|
|
@@ -9562,6 +9763,7 @@ class VoiceRecordWidgetComponent {
|
|
|
9562
9763
|
if (!this.record)
|
|
9563
9764
|
throw new Error('Voice recorder plugin is not initialized.');
|
|
9564
9765
|
this.updateViewState(() => {
|
|
9766
|
+
this.microphoneError.set(false);
|
|
9565
9767
|
this.isWaveformOpen = true;
|
|
9566
9768
|
this.isRecording = true;
|
|
9567
9769
|
this.isPlaying = false;
|
|
@@ -9579,6 +9781,7 @@ class VoiceRecordWidgetComponent {
|
|
|
9579
9781
|
this.updateViewState(() => {
|
|
9580
9782
|
this.isRecording = false;
|
|
9581
9783
|
this.isWaveformOpen = !!this.recordedBlob;
|
|
9784
|
+
this.microphoneError.set(true);
|
|
9582
9785
|
});
|
|
9583
9786
|
throw error;
|
|
9584
9787
|
}
|
|
@@ -9609,13 +9812,7 @@ class VoiceRecordWidgetComponent {
|
|
|
9609
9812
|
}
|
|
9610
9813
|
saveRecording() {
|
|
9611
9814
|
if (this.recordedBlob) {
|
|
9612
|
-
|
|
9613
|
-
let formattedDate = now.toLocaleString('sv-SE').replace(' ', '_').replace(/:/g, '-');
|
|
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 });
|
|
9815
|
+
const file = UploadWidgetUtils.createRecordingFile(this.recordedBlob, 'recording', 'audio/mp4');
|
|
9619
9816
|
this.recordingSaved.emit(file);
|
|
9620
9817
|
// Destroy completely so next recording initializes fresh
|
|
9621
9818
|
this.wavesurfer?.destroy();
|
|
@@ -9670,18 +9867,18 @@ class VoiceRecordWidgetComponent {
|
|
|
9670
9867
|
});
|
|
9671
9868
|
}
|
|
9672
9869
|
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)=\"startRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Stop Recording -->\n @if (isRecording) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"stopButton\"\n (actionClick)=\"stopRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Save Recording -->\n @if (recordedBlob) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"saveButton\"\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", styles: [":host{display:block;width:100%;min-width:0}:host ::ng-deep .waveform{height:unset!important}.recorderContainer{display:flex;flex-direction:row;justify-content:space-between;width:100%;min-width:0;align-items:center;gap:.5rem}.active ::ng-deep .recorderToolbar{border:1px solid #e5e7eb;border-radius:.5rem}.isRecording ::ng-deep .recorderToolbar{padding-right:1rem}.recorderToolbar ::ng-deep .sb4-red,.recorderToolbar ::ng-deep .sb4-primary{border:1px solid #e5e7eb;box-shadow:unset}.active ::ng-deep .recorderToolbar ::ng-deep .sb4-red,.active ::ng-deep .recorderToolbar ::ng-deep .sb4-primary{border:unset}.mainButton ::ng-deep .sb4-red,.mainButton ::ng-deep .sb4-primary{padding:.5rem .75rem}.recorderMain{display:flex;align-items:center;min-width:0;flex:1}.direction-right .recorderMain{flex-direction:row}.direction-left .recorderMain{flex-direction:row-reverse}.waveform{width:100%;height:45px}.waveformBand{position:relative;display:grid;align-items:center;width:0;max-width:0;min-width:0;opacity:0;overflow:hidden;border-radius:5px;flex:1 1 0;transform:scaleX(.96);will-change:opacity,transform,margin;transition:max-width .22s ease,opacity .18s ease,margin .22s ease,transform .22s ease}.waveformBand.open{min-width:35px;max-width:100%;opacity:1;transform:scaleX(1)}.direction-right .waveformBand{margin-left:0}.direction-left .waveformBand{margin-right:0}.direction-right .waveformBand.open{margin-left:.45rem}.direction-left .waveformBand.open{margin-right:.45rem}.waveformGrid{position:absolute;inset:0;border:1px solid #e5e7eb;border-radius:.5rem;background:repeating-linear-gradient(to right,rgba(148,163,184,.35) 0,rgba(148,163,184,.35) 2px,transparent 2px,transparent 8px),linear-gradient(to bottom,#94a3b814,#94a3b805);pointer-events:none}.recorderToolbar{background-color:transparent;display:flex;flex-direction:row;align-items:center}.progress{align-content:center;font-size:1rem;height:100%;color:#6b7280}.hasRecording ::ng-deep .recorderToolbar ui-action-button{border-right:1px solid #e5e7eb}.hasRecording ::ng-deep .recorderToolbar ui-action-button:last-child{border-right:unset}\n"], dependencies: [{ kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
|
|
9870
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: VoiceRecordWidgetComponent, isStandalone: true, selector: "voice-record-widget", inputs: { openDirection: { classPropertyName: "openDirection", publicName: "openDirection", isSignal: true, isRequired: false, transformFunction: null }, texts: { classPropertyName: "texts", publicName: "texts", isSignal: true, isRequired: false, transformFunction: null }, showMicrophoneError: { classPropertyName: "showMicrophoneError", publicName: "showMicrophoneError", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { recordingSaved: "recordingSaved" }, viewQueries: [{ propertyName: "waveformRef", first: true, predicate: ["waveform"], descendants: true }, { propertyName: "mainButton", first: true, predicate: ["mainButton"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div\n class=\"recorderContainer\"\n [class.direction-left]=\"isLeftDirection\"\n [class.direction-right]=\"!isLeftDirection\"\n [class.active]=\"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)=\"startRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Stop Recording -->\n @if (isRecording) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"stopButton\"\n (actionClick)=\"stopRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Save Recording -->\n @if (recordedBlob) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"saveButton\"\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}\n", styles: [":host{display:block;width:100%;min-width:0}.message{text-align:center;color:#6b7280}:host ::ng-deep .waveform{height:unset!important}.recorderContainer{display:flex;flex-direction:row;justify-content:space-between;width:100%;min-width:0;align-items:center;gap:.5rem}.active ::ng-deep .recorderToolbar{border:1px solid #e5e7eb;border-radius:.5rem}.isRecording ::ng-deep .recorderToolbar{padding-right:1rem}.recorderToolbar ::ng-deep .sb4-red,.recorderToolbar ::ng-deep .sb4-primary{border:1px solid #e5e7eb;box-shadow:unset}.active ::ng-deep .recorderToolbar ::ng-deep .sb4-red,.active ::ng-deep .recorderToolbar ::ng-deep .sb4-primary{border:unset}.mainButton ::ng-deep .sb4-red,.mainButton ::ng-deep .sb4-primary{padding:.5rem .75rem}.recorderMain{display:flex;align-items:center;min-width:0;flex:1}.direction-right .recorderMain{flex-direction:row}.direction-left .recorderMain{flex-direction:row-reverse}.waveform{width:100%;height:45px}.waveformBand{position:relative;display:grid;align-items:center;width:0;max-width:0;min-width:0;opacity:0;overflow:hidden;border-radius:5px;flex:1 1 0;transform:scaleX(.96);will-change:opacity,transform,margin;transition:max-width .22s ease,opacity .18s ease,margin .22s ease,transform .22s ease}.waveformBand.open{min-width:35px;max-width:100%;opacity:1;transform:scaleX(1)}.direction-right .waveformBand{margin-left:0}.direction-left .waveformBand{margin-right:0}.direction-right .waveformBand.open{margin-left:.45rem}.direction-left .waveformBand.open{margin-right:.45rem}.waveformGrid{position:absolute;inset:0;border:1px solid #e5e7eb;border-radius:.5rem;background:repeating-linear-gradient(to right,rgba(148,163,184,.35) 0,rgba(148,163,184,.35) 2px,transparent 2px,transparent 8px),linear-gradient(to bottom,#94a3b814,#94a3b805);pointer-events:none}.recorderToolbar{background-color:transparent;display:flex;flex-direction:row;align-items:center}.progress{align-content:center;font-size:1rem;height:100%;color:#6b7280}.hasRecording ::ng-deep .recorderToolbar ui-action-button{border-right:1px solid #e5e7eb}.hasRecording ::ng-deep .recorderToolbar ui-action-button:last-child{border-right:unset}\n"], dependencies: [{ kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
|
|
9674
9871
|
}
|
|
9675
9872
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: VoiceRecordWidgetComponent, decorators: [{
|
|
9676
9873
|
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)=\"startRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Stop Recording -->\n @if (isRecording) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"stopButton\"\n (actionClick)=\"stopRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Save Recording -->\n @if (recordedBlob) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"saveButton\"\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", styles: [":host{display:block;width:100%;min-width:0}:host ::ng-deep .waveform{height:unset!important}.recorderContainer{display:flex;flex-direction:row;justify-content:space-between;width:100%;min-width:0;align-items:center;gap:.5rem}.active ::ng-deep .recorderToolbar{border:1px solid #e5e7eb;border-radius:.5rem}.isRecording ::ng-deep .recorderToolbar{padding-right:1rem}.recorderToolbar ::ng-deep .sb4-red,.recorderToolbar ::ng-deep .sb4-primary{border:1px solid #e5e7eb;box-shadow:unset}.active ::ng-deep .recorderToolbar ::ng-deep .sb4-red,.active ::ng-deep .recorderToolbar ::ng-deep .sb4-primary{border:unset}.mainButton ::ng-deep .sb4-red,.mainButton ::ng-deep .sb4-primary{padding:.5rem .75rem}.recorderMain{display:flex;align-items:center;min-width:0;flex:1}.direction-right .recorderMain{flex-direction:row}.direction-left .recorderMain{flex-direction:row-reverse}.waveform{width:100%;height:45px}.waveformBand{position:relative;display:grid;align-items:center;width:0;max-width:0;min-width:0;opacity:0;overflow:hidden;border-radius:5px;flex:1 1 0;transform:scaleX(.96);will-change:opacity,transform,margin;transition:max-width .22s ease,opacity .18s ease,margin .22s ease,transform .22s ease}.waveformBand.open{min-width:35px;max-width:100%;opacity:1;transform:scaleX(1)}.direction-right .waveformBand{margin-left:0}.direction-left .waveformBand{margin-right:0}.direction-right .waveformBand.open{margin-left:.45rem}.direction-left .waveformBand.open{margin-right:.45rem}.waveformGrid{position:absolute;inset:0;border:1px solid #e5e7eb;border-radius:.5rem;background:repeating-linear-gradient(to right,rgba(148,163,184,.35) 0,rgba(148,163,184,.35) 2px,transparent 2px,transparent 8px),linear-gradient(to bottom,#94a3b814,#94a3b805);pointer-events:none}.recorderToolbar{background-color:transparent;display:flex;flex-direction:row;align-items:center}.progress{align-content:center;font-size:1rem;height:100%;color:#6b7280}.hasRecording ::ng-deep .recorderToolbar ui-action-button{border-right:1px solid #e5e7eb}.hasRecording ::ng-deep .recorderToolbar ui-action-button:last-child{border-right:unset}\n"] }]
|
|
9874
|
+
args: [{ selector: 'voice-record-widget', imports: [UiActionButtonComponent], template: "<div\n class=\"recorderContainer\"\n [class.direction-left]=\"isLeftDirection\"\n [class.direction-right]=\"!isLeftDirection\"\n [class.active]=\"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)=\"startRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Stop Recording -->\n @if (isRecording) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"stopButton\"\n (actionClick)=\"stopRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Save Recording -->\n @if (recordedBlob) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"saveButton\"\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}\n", styles: [":host{display:block;width:100%;min-width:0}.message{text-align:center;color:#6b7280}:host ::ng-deep .waveform{height:unset!important}.recorderContainer{display:flex;flex-direction:row;justify-content:space-between;width:100%;min-width:0;align-items:center;gap:.5rem}.active ::ng-deep .recorderToolbar{border:1px solid #e5e7eb;border-radius:.5rem}.isRecording ::ng-deep .recorderToolbar{padding-right:1rem}.recorderToolbar ::ng-deep .sb4-red,.recorderToolbar ::ng-deep .sb4-primary{border:1px solid #e5e7eb;box-shadow:unset}.active ::ng-deep .recorderToolbar ::ng-deep .sb4-red,.active ::ng-deep .recorderToolbar ::ng-deep .sb4-primary{border:unset}.mainButton ::ng-deep .sb4-red,.mainButton ::ng-deep .sb4-primary{padding:.5rem .75rem}.recorderMain{display:flex;align-items:center;min-width:0;flex:1}.direction-right .recorderMain{flex-direction:row}.direction-left .recorderMain{flex-direction:row-reverse}.waveform{width:100%;height:45px}.waveformBand{position:relative;display:grid;align-items:center;width:0;max-width:0;min-width:0;opacity:0;overflow:hidden;border-radius:5px;flex:1 1 0;transform:scaleX(.96);will-change:opacity,transform,margin;transition:max-width .22s ease,opacity .18s ease,margin .22s ease,transform .22s ease}.waveformBand.open{min-width:35px;max-width:100%;opacity:1;transform:scaleX(1)}.direction-right .waveformBand{margin-left:0}.direction-left .waveformBand{margin-right:0}.direction-right .waveformBand.open{margin-left:.45rem}.direction-left .waveformBand.open{margin-right:.45rem}.waveformGrid{position:absolute;inset:0;border:1px solid #e5e7eb;border-radius:.5rem;background:repeating-linear-gradient(to right,rgba(148,163,184,.35) 0,rgba(148,163,184,.35) 2px,transparent 2px,transparent 8px),linear-gradient(to bottom,#94a3b814,#94a3b805);pointer-events:none}.recorderToolbar{background-color:transparent;display:flex;flex-direction:row;align-items:center}.progress{align-content:center;font-size:1rem;height:100%;color:#6b7280}.hasRecording ::ng-deep .recorderToolbar ui-action-button{border-right:1px solid #e5e7eb}.hasRecording ::ng-deep .recorderToolbar ui-action-button:last-child{border-right:unset}\n"] }]
|
|
9678
9875
|
}], propDecorators: { waveformRef: [{
|
|
9679
9876
|
type: ViewChild,
|
|
9680
9877
|
args: ['waveform']
|
|
9681
9878
|
}], mainButton: [{
|
|
9682
9879
|
type: ViewChild,
|
|
9683
9880
|
args: ['mainButton', { read: ElementRef }]
|
|
9684
|
-
}], openDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "openDirection", required: false }] }], recordingSaved: [{ type: i0.Output, args: ["recordingSaved"] }] } });
|
|
9881
|
+
}], openDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "openDirection", required: false }] }], texts: [{ type: i0.Input, args: [{ isSignal: true, alias: "texts", required: false }] }], showMicrophoneError: [{ type: i0.Input, args: [{ isSignal: true, alias: "showMicrophoneError", required: false }] }], recordingSaved: [{ type: i0.Output, args: ["recordingSaved"] }] } });
|
|
9685
9882
|
|
|
9686
9883
|
class UploadWidgetComponent {
|
|
9687
9884
|
constructor() {
|
|
@@ -9691,8 +9888,18 @@ class UploadWidgetComponent {
|
|
|
9691
9888
|
...(ngDevMode ? [{ debugName: "isMultiple" }] : /* istanbul ignore next */ []));
|
|
9692
9889
|
this.isDisabled = input(false, /* @ts-ignore */
|
|
9693
9890
|
...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
9891
|
+
this.sources = input(undefined, /* @ts-ignore */
|
|
9892
|
+
...(ngDevMode ? [{ debugName: "sources" }] : /* istanbul ignore next */ []));
|
|
9694
9893
|
this.uploadFilesEvent = output();
|
|
9695
9894
|
this.uploadWidgetType = UploadWidgetType;
|
|
9895
|
+
this.fileUploaderSource = FileUploaderSource;
|
|
9896
|
+
/** The recorder opens below the capture row at a click on the microphone button. */
|
|
9897
|
+
this.isRecorderOpen = signal(false, /* @ts-ignore */
|
|
9898
|
+
...(ngDevMode ? [{ debugName: "isRecorderOpen" }] : /* istanbul ignore next */ []));
|
|
9899
|
+
/** The descriptor's texts over the client's defaults, handed down to the capture widgets. */
|
|
9900
|
+
this.texts = UPLOAD_ZONE_DEFAULT_TEXTS;
|
|
9901
|
+
/** Cached per text set, so change detection hands each button the same descriptor. */
|
|
9902
|
+
this.captureButtons = new Map();
|
|
9696
9903
|
this.maxSizeMb = 25;
|
|
9697
9904
|
}
|
|
9698
9905
|
ngOnInit() {
|
|
@@ -9709,13 +9916,17 @@ class UploadWidgetComponent {
|
|
|
9709
9916
|
this.fileFormats = [uploadDescriptor.formats];
|
|
9710
9917
|
}
|
|
9711
9918
|
// fileFormats = this.descriptor.upload?.formats;
|
|
9919
|
+
this.texts = UploadWidgetUtils.resolveTexts(uploadDescriptor?.texts);
|
|
9712
9920
|
this.i18n = {
|
|
9713
|
-
addFile: uploadDescriptor?.title ??
|
|
9714
|
-
browseOrDrag: uploadDescriptor?.description ??
|
|
9921
|
+
addFile: uploadDescriptor?.title ?? this.texts.addFile,
|
|
9922
|
+
browseOrDrag: uploadDescriptor?.description ?? this.texts.browseOrDrag,
|
|
9715
9923
|
formats: uploadDescriptor?.formats ?? '',
|
|
9716
|
-
maxSize:
|
|
9717
|
-
upload: uploadDescriptor?.uploadButtonTitle ??
|
|
9924
|
+
maxSize: UploadWidgetUtils.fillPlaceholders(this.texts.maxSize, { size: this.maxSizeMb }),
|
|
9925
|
+
upload: uploadDescriptor?.uploadButtonTitle ?? this.texts.upload,
|
|
9926
|
+
unsupportedFormat: this.texts.unsupportedFormat,
|
|
9927
|
+
fileTooLarge: this.texts.fileTooLarge,
|
|
9718
9928
|
};
|
|
9929
|
+
this.captureButtons.clear();
|
|
9719
9930
|
}
|
|
9720
9931
|
upload(files) {
|
|
9721
9932
|
this.uploadFilesEvent.emit({
|
|
@@ -9724,23 +9935,75 @@ class UploadWidgetComponent {
|
|
|
9724
9935
|
});
|
|
9725
9936
|
this.fileUploadMaterial.files = [];
|
|
9726
9937
|
}
|
|
9938
|
+
/** A captured file takes the picker's path: the same checks, the list or the upload. */
|
|
9727
9939
|
uploadRecording(file) {
|
|
9728
|
-
this.
|
|
9940
|
+
this.fileUploadMaterial.processFiles([file]);
|
|
9729
9941
|
}
|
|
9730
9942
|
uploadImage(files) {
|
|
9731
|
-
this.
|
|
9943
|
+
this.fileUploadMaterial.processFiles(files);
|
|
9732
9944
|
}
|
|
9733
|
-
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9945
|
+
/** True when the sources decide the zone: they are set and not the default FILE alone beside
|
|
9946
|
+
* uploadWidgets. */
|
|
9947
|
+
hasSources() {
|
|
9948
|
+
const sources = this.sources() ?? [];
|
|
9949
|
+
const isDefaultOnly = sources.length === 1 && sources[0] === FileUploaderSource.FILE;
|
|
9950
|
+
return !!sources.length && !(isDefaultOnly && !!this.uploadDescriptor()?.uploadWidgets?.length);
|
|
9951
|
+
}
|
|
9952
|
+
isSourceNeeded(source) {
|
|
9953
|
+
return this.sources()?.includes(source) ?? false;
|
|
9954
|
+
}
|
|
9955
|
+
isPickerShown() {
|
|
9956
|
+
return !this.hasSources() || this.isSourceNeeded(FileUploaderSource.FILE);
|
|
9957
|
+
}
|
|
9958
|
+
isCaptureOnly() {
|
|
9959
|
+
return !this.isPickerShown();
|
|
9960
|
+
}
|
|
9961
|
+
/** Capture-only uploads at once whatever the descriptor says, as there is no upload button. */
|
|
9962
|
+
isUploadImmediate() {
|
|
9963
|
+
return !!this.autoUpload || this.isCaptureOnly();
|
|
9964
|
+
}
|
|
9965
|
+
getCaptureSources() {
|
|
9966
|
+
return (this.sources() ?? []).filter((source) => source !== FileUploaderSource.FILE);
|
|
9967
|
+
}
|
|
9968
|
+
startCapture(source) {
|
|
9969
|
+
if (source === FileUploaderSource.CAMERA_PHOTO) {
|
|
9970
|
+
this.photoCapture?.openCapture();
|
|
9739
9971
|
}
|
|
9740
|
-
else {
|
|
9741
|
-
this.
|
|
9972
|
+
else if (source === FileUploaderSource.CAMERA_VIDEO) {
|
|
9973
|
+
this.videoCapture?.openCapture();
|
|
9974
|
+
}
|
|
9975
|
+
else if (source === FileUploaderSource.MICROPHONE) {
|
|
9976
|
+
this.isRecorderOpen.update((open) => !open);
|
|
9742
9977
|
}
|
|
9743
9978
|
}
|
|
9979
|
+
getCaptureButtonClass(source) {
|
|
9980
|
+
return CAPTURE_BUTTONS[source].cssClass;
|
|
9981
|
+
}
|
|
9982
|
+
getCaptureButton(source) {
|
|
9983
|
+
if (!this.captureButtons.has(source)) {
|
|
9984
|
+
const button = CAPTURE_BUTTONS[source];
|
|
9985
|
+
this.captureButtons.set(source, {
|
|
9986
|
+
title: '',
|
|
9987
|
+
type: UiActionButtonType.ICON,
|
|
9988
|
+
icon: button.icon,
|
|
9989
|
+
iconPosition: IconPosition.PRE,
|
|
9990
|
+
color: 'primary',
|
|
9991
|
+
tooltip: { tooltip: this.texts[button.tooltip] },
|
|
9992
|
+
});
|
|
9993
|
+
}
|
|
9994
|
+
return this.captureButtons.get(source);
|
|
9995
|
+
}
|
|
9996
|
+
/** The hint of a capture-only zone names its sources, as the picker's lines name the files.
|
|
9997
|
+
* Its grammar is owned by the captureOnlyHint and or texts. */
|
|
9998
|
+
getCaptureOnlyHint() {
|
|
9999
|
+
const nouns = this.getCaptureSources().map((source) => this.texts[CAPTURE_BUTTONS[source].noun]);
|
|
10000
|
+
const last = nouns.pop();
|
|
10001
|
+
const joined = nouns.length ? `${nouns.join(', ')} ${this.texts.or} ${last}` : last;
|
|
10002
|
+
const hint = UploadWidgetUtils.fillPlaceholders(this.texts.captureOnlyHint, {
|
|
10003
|
+
sources: joined,
|
|
10004
|
+
});
|
|
10005
|
+
return `${hint.charAt(0).toUpperCase()}${hint.slice(1)} · ${this.i18n?.maxSize}`;
|
|
10006
|
+
}
|
|
9744
10007
|
widgetNeeded(type) {
|
|
9745
10008
|
return this.uploadDescriptor()?.uploadWidgets?.includes(type) ?? false;
|
|
9746
10009
|
}
|
|
@@ -9749,26 +10012,172 @@ class UploadWidgetComponent {
|
|
|
9749
10012
|
}
|
|
9750
10013
|
get defaultUploadDescriptor() {
|
|
9751
10014
|
return {
|
|
9752
|
-
title:
|
|
9753
|
-
description:
|
|
10015
|
+
title: UPLOAD_ZONE_DEFAULT_TEXTS.addFile,
|
|
10016
|
+
description: UPLOAD_ZONE_DEFAULT_TEXTS.browseOrDrag,
|
|
9754
10017
|
maxSize: '25',
|
|
9755
10018
|
autoUpload: false,
|
|
9756
10019
|
};
|
|
9757
10020
|
}
|
|
9758
10021
|
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
|
|
10022
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: UploadWidgetComponent, isStandalone: true, selector: "smart-upload-widget", inputs: { uploadDescriptor: { classPropertyName: "uploadDescriptor", publicName: "uploadDescriptor", isSignal: true, isRequired: false, transformFunction: null }, isMultiple: { classPropertyName: "isMultiple", publicName: "isMultiple", isSignal: true, isRequired: true, transformFunction: null }, isDisabled: { classPropertyName: "isDisabled", publicName: "isDisabled", isSignal: true, isRequired: false, transformFunction: null }, sources: { classPropertyName: "sources", publicName: "sources", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { uploadFilesEvent: "uploadFilesEvent" }, viewQueries: [{ propertyName: "fileUploadMaterial", first: true, predicate: ["fileUploadMaterial"], descendants: true }, { propertyName: "photoCapture", first: true, predicate: ["photoCapture"], descendants: true }, { propertyName: "videoCapture", first: true, predicate: ["videoCapture"], descendants: true }], ngImport: i0, template: "<div class=\"sb4UploadWidget\">\n @if (hasSources()) {\n <!-- CAPTURE ROW OF THE SOURCES, hidden in view mode -->\n @if (getCaptureSources().length && !isDisabled()) {\n <div class=\"captureRow\" [class.captureOnly]=\"isCaptureOnly()\">\n <div class=\"captureButtons\">\n @for (source of getCaptureSources(); track source) {\n <ui-action-button\n class=\"{{ getCaptureButtonClass(source) }}\"\n [descriptor]=\"getCaptureButton(source)\"\n (actionClick)=\"startCapture(source)\"\n ></ui-action-button>\n }\n </div>\n @if (isCaptureOnly()) {\n <div class=\"captureHint\">{{ getCaptureOnlyHint() }}</div>\n }\n </div>\n }\n @if (isSourceNeeded(fileUploaderSource.CAMERA_PHOTO)) {\n <photo-capture-widget\n #photoCapture\n [mode]=\"'PHOTO'\"\n [externalTrigger]=\"true\"\n [texts]=\"texts\"\n [maxFileSize]=\"maxSizeMb * 1024 * 1024\"\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 [texts]=\"texts\"\n (photoCaptured)=\"uploadImage($event)\"\n ></photo-capture-widget>\n }\n @if (isRecorderOpen()) {\n <voice-record-widget\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]=\"maxSizeMb * 1024 * 1024\"\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{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"], outputs: ["photoCaptured"] }, { kind: "component", type: VoiceRecordWidgetComponent, selector: "voice-record-widget", inputs: ["openDirection", "texts", "showMicrophoneError"], 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
10023
|
}
|
|
9761
10024
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UploadWidgetComponent, decorators: [{
|
|
9762
10025
|
type: Component,
|
|
9763
|
-
args: [{ selector: 'smart-upload-widget', imports: [
|
|
10026
|
+
args: [{ selector: 'smart-upload-widget', imports: [
|
|
10027
|
+
PhotoCaptureWidgetComponent,
|
|
10028
|
+
VoiceRecordWidgetComponent,
|
|
10029
|
+
SmartfileuploaderComponent,
|
|
10030
|
+
UiActionButtonComponent,
|
|
10031
|
+
], template: "<div class=\"sb4UploadWidget\">\n @if (hasSources()) {\n <!-- CAPTURE ROW OF THE SOURCES, hidden in view mode -->\n @if (getCaptureSources().length && !isDisabled()) {\n <div class=\"captureRow\" [class.captureOnly]=\"isCaptureOnly()\">\n <div class=\"captureButtons\">\n @for (source of getCaptureSources(); track source) {\n <ui-action-button\n class=\"{{ getCaptureButtonClass(source) }}\"\n [descriptor]=\"getCaptureButton(source)\"\n (actionClick)=\"startCapture(source)\"\n ></ui-action-button>\n }\n </div>\n @if (isCaptureOnly()) {\n <div class=\"captureHint\">{{ getCaptureOnlyHint() }}</div>\n }\n </div>\n }\n @if (isSourceNeeded(fileUploaderSource.CAMERA_PHOTO)) {\n <photo-capture-widget\n #photoCapture\n [mode]=\"'PHOTO'\"\n [externalTrigger]=\"true\"\n [texts]=\"texts\"\n [maxFileSize]=\"maxSizeMb * 1024 * 1024\"\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 [texts]=\"texts\"\n (photoCaptured)=\"uploadImage($event)\"\n ></photo-capture-widget>\n }\n @if (isRecorderOpen()) {\n <voice-record-widget\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]=\"maxSizeMb * 1024 * 1024\"\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{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
10032
|
}], propDecorators: { fileUploadMaterial: [{
|
|
9765
10033
|
type: ViewChild,
|
|
9766
10034
|
args: ['fileUploadMaterial']
|
|
9767
|
-
}],
|
|
10035
|
+
}], photoCapture: [{
|
|
10036
|
+
type: ViewChild,
|
|
10037
|
+
args: ['photoCapture']
|
|
10038
|
+
}], videoCapture: [{
|
|
10039
|
+
type: ViewChild,
|
|
10040
|
+
args: ['videoCapture']
|
|
10041
|
+
}], uploadDescriptor: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadDescriptor", required: false }] }], isMultiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMultiple", required: true }] }], isDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDisabled", required: false }] }], sources: [{ type: i0.Input, args: [{ isSignal: true, alias: "sources", required: false }] }], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }] } });
|
|
10042
|
+
const CAPTURE_BUTTONS = {
|
|
10043
|
+
[FileUploaderSource.CAMERA_PHOTO]: {
|
|
10044
|
+
icon: 'photo_camera',
|
|
10045
|
+
cssClass: 'capturePhotoButton',
|
|
10046
|
+
tooltip: 'capturePhoto',
|
|
10047
|
+
noun: 'photo',
|
|
10048
|
+
},
|
|
10049
|
+
[FileUploaderSource.CAMERA_VIDEO]: {
|
|
10050
|
+
icon: 'videocam',
|
|
10051
|
+
cssClass: 'captureVideoButton',
|
|
10052
|
+
tooltip: 'captureVideo',
|
|
10053
|
+
noun: 'video',
|
|
10054
|
+
},
|
|
10055
|
+
[FileUploaderSource.MICROPHONE]: {
|
|
10056
|
+
icon: 'mic',
|
|
10057
|
+
cssClass: 'recordAudioButton',
|
|
10058
|
+
tooltip: 'recordAudio',
|
|
10059
|
+
noun: 'audio',
|
|
10060
|
+
},
|
|
10061
|
+
};
|
|
10062
|
+
|
|
10063
|
+
const FILE_PREVIEW_KEY_PREFIX = 'filePreview.';
|
|
10064
|
+
/** The preview element of a mime type, or `undefined` when the file has no preview. */
|
|
10065
|
+
function getFilePreviewKind(mimeType) {
|
|
10066
|
+
const type = mimeType?.split('/')[0];
|
|
10067
|
+
return type === 'image' || type === 'video' || type === 'audio' ? type : undefined;
|
|
10068
|
+
}
|
|
10069
|
+
/** The server's preview key of a single-file widget (`FileWidgetHandlerApi.getPreviewItemKey`). */
|
|
10070
|
+
function createSingleFilePreviewKey(widgetKey) {
|
|
10071
|
+
return FILE_PREVIEW_KEY_PREFIX + widgetKey;
|
|
10072
|
+
}
|
|
10073
|
+
/** The server's preview key of a list row: the widget key and the object id of the dataUri. */
|
|
10074
|
+
function createListFilePreviewKey(widgetKey, dataUri) {
|
|
10075
|
+
const path = dataUri.split(/[?#]/)[0];
|
|
10076
|
+
const objectId = path.substring(path.lastIndexOf('/') + 1).split('.')[0];
|
|
10077
|
+
return `${FILE_PREVIEW_KEY_PREFIX}${widgetKey}.${objectId}`;
|
|
10078
|
+
}
|
|
10079
|
+
/**
|
|
10080
|
+
* Loads the previews of a file editor through the view's authenticated download and owns their
|
|
10081
|
+
* blob URLs: a URL is revoked when its file leaves the requests and when the editor is destroyed.
|
|
10082
|
+
* Created in the editor's injection context; the view uuid is the editor's client's.
|
|
10083
|
+
*/
|
|
10084
|
+
class FilePreviewLoader {
|
|
10085
|
+
constructor() {
|
|
10086
|
+
this.viewContext = inject(SmartViewContextService);
|
|
10087
|
+
this.smartComponent = injectSmartComponent();
|
|
10088
|
+
this.requests = signal([], /* @ts-ignore */
|
|
10089
|
+
...(ngDevMode ? [{ debugName: "requests" }] : /* istanbul ignore next */ []));
|
|
10090
|
+
this.previewsState = signal(new Map(), /* @ts-ignore */
|
|
10091
|
+
...(ngDevMode ? [{ debugName: "previewsState" }] : /* istanbul ignore next */ []));
|
|
10092
|
+
this.loadingDataUris = new Set();
|
|
10093
|
+
/** Not fetched again while it stays in the requests, as every model write sets them anew. */
|
|
10094
|
+
this.failedDataUris = new Set();
|
|
10095
|
+
this.destroyed = false;
|
|
10096
|
+
effect(() => this.loadPreviews(this.smartComponent?.widgetUuid(), this.requests()));
|
|
10097
|
+
inject(DestroyRef).onDestroy(() => {
|
|
10098
|
+
this.destroyed = true;
|
|
10099
|
+
this.previewsState().forEach((preview) => URL.revokeObjectURL(preview.url));
|
|
10100
|
+
});
|
|
10101
|
+
}
|
|
10102
|
+
/** Sets the files to preview; a file of no preview kind is ignored. */
|
|
10103
|
+
setRequests(requests) {
|
|
10104
|
+
this.requests.set(requests.filter((request) => getFilePreviewKind(request.mimeType)));
|
|
10105
|
+
}
|
|
10106
|
+
/** The loaded preview of a file, `undefined` while loading or without one. */
|
|
10107
|
+
getPreview(dataUri) {
|
|
10108
|
+
return dataUri ? this.previewsState().get(dataUri) : undefined;
|
|
10109
|
+
}
|
|
10110
|
+
/**
|
|
10111
|
+
* Turns a video preview whose media reports no video track into an audio preview of the same
|
|
10112
|
+
* blob URL, as a voice note may be stored with a video mime type; call it at `loadedmetadata`.
|
|
10113
|
+
*/
|
|
10114
|
+
applyVideoMetadata(dataUri, video) {
|
|
10115
|
+
const preview = this.previewsState().get(dataUri);
|
|
10116
|
+
if (preview?.kind === 'video' && video.videoWidth === 0 && video.videoHeight === 0) {
|
|
10117
|
+
const previews = new Map(this.previewsState());
|
|
10118
|
+
previews.set(dataUri, { ...preview, kind: 'audio' });
|
|
10119
|
+
this.previewsState.set(previews);
|
|
10120
|
+
}
|
|
10121
|
+
}
|
|
10122
|
+
loadPreviews(viewUuid, requests) {
|
|
10123
|
+
untracked(() => {
|
|
10124
|
+
const previews = new Map(this.previewsState());
|
|
10125
|
+
previews.forEach((preview, dataUri) => {
|
|
10126
|
+
if (!this.isRequested(dataUri)) {
|
|
10127
|
+
URL.revokeObjectURL(preview.url);
|
|
10128
|
+
previews.delete(dataUri);
|
|
10129
|
+
}
|
|
10130
|
+
});
|
|
10131
|
+
if (previews.size !== this.previewsState().size) {
|
|
10132
|
+
this.previewsState.set(previews);
|
|
10133
|
+
}
|
|
10134
|
+
this.failedDataUris.forEach((dataUri) => {
|
|
10135
|
+
if (!this.isRequested(dataUri)) {
|
|
10136
|
+
this.failedDataUris.delete(dataUri);
|
|
10137
|
+
}
|
|
10138
|
+
});
|
|
10139
|
+
if (viewUuid) {
|
|
10140
|
+
requests
|
|
10141
|
+
.filter((request) => !previews.has(request.dataUri))
|
|
10142
|
+
.filter((request) => !this.loadingDataUris.has(request.dataUri))
|
|
10143
|
+
.filter((request) => !this.failedDataUris.has(request.dataUri))
|
|
10144
|
+
.forEach((request) => this.loadPreview(viewUuid, request));
|
|
10145
|
+
}
|
|
10146
|
+
});
|
|
10147
|
+
}
|
|
10148
|
+
async loadPreview(viewUuid, request) {
|
|
10149
|
+
this.loadingDataUris.add(request.dataUri);
|
|
10150
|
+
let data;
|
|
10151
|
+
try {
|
|
10152
|
+
data = await this.viewContext.downloadResource(viewUuid, request.key);
|
|
10153
|
+
}
|
|
10154
|
+
catch {
|
|
10155
|
+
// A file the server has not registered keeps the card without its preview.
|
|
10156
|
+
if (this.isRequested(request.dataUri)) {
|
|
10157
|
+
this.failedDataUris.add(request.dataUri);
|
|
10158
|
+
}
|
|
10159
|
+
return;
|
|
10160
|
+
}
|
|
10161
|
+
finally {
|
|
10162
|
+
this.loadingDataUris.delete(request.dataUri);
|
|
10163
|
+
}
|
|
10164
|
+
if (this.destroyed || !this.isRequested(request.dataUri)) {
|
|
10165
|
+
return;
|
|
10166
|
+
}
|
|
10167
|
+
const url = URL.createObjectURL(new Blob([data], { type: request.mimeType }));
|
|
10168
|
+
const previews = new Map(this.previewsState());
|
|
10169
|
+
previews.set(request.dataUri, { kind: getFilePreviewKind(request.mimeType), url });
|
|
10170
|
+
this.previewsState.set(previews);
|
|
10171
|
+
}
|
|
10172
|
+
isRequested(dataUri) {
|
|
10173
|
+
return this.requests().some((request) => request.dataUri === dataUri);
|
|
10174
|
+
}
|
|
10175
|
+
}
|
|
9768
10176
|
|
|
9769
10177
|
class SmartFileEditorComponent {
|
|
9770
10178
|
constructor() {
|
|
9771
10179
|
this.cdr = inject(ChangeDetectorRef);
|
|
10180
|
+
this.previewLoader = new FilePreviewLoader();
|
|
9772
10181
|
this.uploadFilesEvent = output();
|
|
9773
10182
|
// ---- ControlValueAccessor implementation ----
|
|
9774
10183
|
this.onChange = (value) => { };
|
|
@@ -9796,8 +10205,23 @@ class SmartFileEditorComponent {
|
|
|
9796
10205
|
undoIcon: { identifier: 'undo' },
|
|
9797
10206
|
};
|
|
9798
10207
|
}
|
|
10208
|
+
this.requestPreview();
|
|
9799
10209
|
this.cdr.markForCheck();
|
|
9800
10210
|
}
|
|
10211
|
+
/** With thumbnail FULL the current value is previewed; any other value drops the preview. */
|
|
10212
|
+
requestPreview() {
|
|
10213
|
+
const value = this._widgetInstance.value;
|
|
10214
|
+
const isFull = this._widgetInstance.fileUploaderProperties?.thumbnail === FileUploaderThumbnail.FULL;
|
|
10215
|
+
this.previewLoader.setRequests(isFull && this.isTypeSupported(value)
|
|
10216
|
+
? [
|
|
10217
|
+
{
|
|
10218
|
+
key: createSingleFilePreviewKey(this._widgetInstance.key),
|
|
10219
|
+
dataUri: value.dataUri,
|
|
10220
|
+
mimeType: value.mimeType,
|
|
10221
|
+
},
|
|
10222
|
+
]
|
|
10223
|
+
: []);
|
|
10224
|
+
}
|
|
9801
10225
|
upload(event) {
|
|
9802
10226
|
if (!event.files || event.files.length == 0) {
|
|
9803
10227
|
return;
|
|
@@ -9818,10 +10242,12 @@ class SmartFileEditorComponent {
|
|
|
9818
10242
|
clear() {
|
|
9819
10243
|
this.clearedFile = this._widgetInstance.value;
|
|
9820
10244
|
this._widgetInstance.value = null;
|
|
10245
|
+
this.requestPreview();
|
|
9821
10246
|
this.onChange(null);
|
|
9822
10247
|
}
|
|
9823
10248
|
undoClear() {
|
|
9824
10249
|
this._widgetInstance.value = this.clearedFile;
|
|
10250
|
+
this.requestPreview();
|
|
9825
10251
|
this.onChange(this.clearedFile);
|
|
9826
10252
|
this.clearedFile = undefined;
|
|
9827
10253
|
}
|
|
@@ -9871,6 +10297,7 @@ class SmartFileEditorComponent {
|
|
|
9871
10297
|
if (value) {
|
|
9872
10298
|
this.clearedFile = undefined;
|
|
9873
10299
|
}
|
|
10300
|
+
this.requestPreview();
|
|
9874
10301
|
this.cdr.markForCheck();
|
|
9875
10302
|
}
|
|
9876
10303
|
}
|
|
@@ -9896,6 +10323,15 @@ class SmartFileEditorComponent {
|
|
|
9896
10323
|
get fileIcon() {
|
|
9897
10324
|
return 'insert_drive_file';
|
|
9898
10325
|
}
|
|
10326
|
+
get preview() {
|
|
10327
|
+
return this.previewLoader.getPreview(this._widgetInstance.value?.dataUri);
|
|
10328
|
+
}
|
|
10329
|
+
onPreviewMetadata(event) {
|
|
10330
|
+
const dataUri = this._widgetInstance.value?.dataUri;
|
|
10331
|
+
if (dataUri) {
|
|
10332
|
+
this.previewLoader.applyVideoMetadata(dataUri, event.target);
|
|
10333
|
+
}
|
|
10334
|
+
}
|
|
9899
10335
|
get changeButton() {
|
|
9900
10336
|
var props = this._widgetInstance.fileUploaderProperties;
|
|
9901
10337
|
var baseAction = props.changeAction ?? props.uploadAction;
|
|
@@ -9940,7 +10376,7 @@ class SmartFileEditorComponent {
|
|
|
9940
10376
|
useExisting: forwardRef(() => SmartFileEditorComponent),
|
|
9941
10377
|
multi: true,
|
|
9942
10378
|
},
|
|
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"] }] }); }
|
|
10379
|
+
], ngImport: i0, template: "@if (widgetInstance && widgetInstance.fileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else if (!widgetInstance.value || fileToChange) {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.fileUploaderProperties.uploadDescriptor!\"\n [sources]=\"widgetInstance.fileUploaderProperties.sources\"\n [isMultiple]=\"false\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n @if (clearedFile && !isDisabled) {\n <div class=\"undoClearContainer\">\n <ui-action-button\n class=\"removeIcon undoClearButton\"\n (actionClick)=\"undoClear()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n </div>\n }\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n @if (preview; as filePreview) {\n <div class=\"filePreview\">\n @switch (filePreview.kind) {\n @case ('image') {\n <img [src]=\"filePreview.url\" [alt]=\"widgetInstance.value?.fileName\" />\n }\n @case ('video') {\n <video\n controls\n [src]=\"filePreview.url\"\n (loadedmetadata)=\"onPreviewMetadata($event)\"\n ></video>\n }\n @case ('audio') {\n <audio controls [src]=\"filePreview.url\"></audio>\n }\n }\n </div>\n }\n <div class=\"uploadedFile\">\n @if (!preview) {\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n }\n\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ widgetInstance.value?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(widgetInstance.value?.size)) }}</span>\n </div>\n </div>\n\n <div class=\"fileActions\">\n <smart-ui-action-toolbar [id]=\"widgetInstance.toolbarId\"></smart-ui-action-toolbar>\n\n @if (!fileToChange) {\n <ui-action-button\n class=\"changeButton\"\n (actionClick)=\"change(widgetInstance.value)\"\n [disabled]=\"isDisabled\"\n [descriptor]=\"changeButton\"\n >\n </ui-action-button>\n @if (!isDisabled && isClearEnabled) {\n <ui-action-button\n class=\"deleteButton\"\n (actionClick)=\"clear()\"\n [descriptor]=\"deleteButton\"\n ></ui-action-button>\n }\n } @else {\n <ui-action-button\n class=\"removeIcon\"\n (actionClick)=\"cancelChange()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n }\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.fileEditorComponent:has(::ng-deep smartfileuploader) ::ng-deep .uploadedFile{border-top:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button,.undoClearContainer ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover,.undoClearContainer ::ng-deep button:hover{border:unset}.undoClearContainer{display:flex;justify-content:flex-end;padding:0 1rem .5rem}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.filePreview{padding:.75rem 1rem 0}.filePreview img,.filePreview video{display:block;width:100%;max-height:200px;object-fit:contain;background:#f1f1f1;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"], 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
10380
|
}
|
|
9945
10381
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartFileEditorComponent, decorators: [{
|
|
9946
10382
|
type: Component,
|
|
@@ -9950,7 +10386,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
9950
10386
|
useExisting: forwardRef(() => SmartFileEditorComponent),
|
|
9951
10387
|
multi: true,
|
|
9952
10388
|
},
|
|
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"] }]
|
|
10389
|
+
], imports: [NgClass, NgStyle, SmartIconComponent, UploadWidgetComponent, UiActionToolbarComponent, UiActionButtonComponent], template: "@if (widgetInstance && widgetInstance.fileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else if (!widgetInstance.value || fileToChange) {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.fileUploaderProperties.uploadDescriptor!\"\n [sources]=\"widgetInstance.fileUploaderProperties.sources\"\n [isMultiple]=\"false\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n @if (clearedFile && !isDisabled) {\n <div class=\"undoClearContainer\">\n <ui-action-button\n class=\"removeIcon undoClearButton\"\n (actionClick)=\"undoClear()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n </div>\n }\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n @if (preview; as filePreview) {\n <div class=\"filePreview\">\n @switch (filePreview.kind) {\n @case ('image') {\n <img [src]=\"filePreview.url\" [alt]=\"widgetInstance.value?.fileName\" />\n }\n @case ('video') {\n <video\n controls\n [src]=\"filePreview.url\"\n (loadedmetadata)=\"onPreviewMetadata($event)\"\n ></video>\n }\n @case ('audio') {\n <audio controls [src]=\"filePreview.url\"></audio>\n }\n }\n </div>\n }\n <div class=\"uploadedFile\">\n @if (!preview) {\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n }\n\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ widgetInstance.value?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(widgetInstance.value?.size)) }}</span>\n </div>\n </div>\n\n <div class=\"fileActions\">\n <smart-ui-action-toolbar [id]=\"widgetInstance.toolbarId\"></smart-ui-action-toolbar>\n\n @if (!fileToChange) {\n <ui-action-button\n class=\"changeButton\"\n (actionClick)=\"change(widgetInstance.value)\"\n [disabled]=\"isDisabled\"\n [descriptor]=\"changeButton\"\n >\n </ui-action-button>\n @if (!isDisabled && isClearEnabled) {\n <ui-action-button\n class=\"deleteButton\"\n (actionClick)=\"clear()\"\n [descriptor]=\"deleteButton\"\n ></ui-action-button>\n }\n } @else {\n <ui-action-button\n class=\"removeIcon\"\n (actionClick)=\"cancelChange()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n }\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.fileEditorComponent:has(::ng-deep smartfileuploader) ::ng-deep .uploadedFile{border-top:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button,.undoClearContainer ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover,.undoClearContainer ::ng-deep button:hover{border:unset}.undoClearContainer{display:flex;justify-content:flex-end;padding:0 1rem .5rem}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.filePreview{padding:.75rem 1rem 0}.filePreview img,.filePreview video{display:block;width:100%;max-height:200px;object-fit:contain;background:#f1f1f1;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
10390
|
}], propDecorators: { widgetInstance: [{
|
|
9955
10391
|
type: Input
|
|
9956
10392
|
}], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }] } });
|
|
@@ -10859,6 +11295,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
10859
11295
|
class SmartMultiFileEditorComponent {
|
|
10860
11296
|
constructor() {
|
|
10861
11297
|
this.cdr = inject(ChangeDetectorRef);
|
|
11298
|
+
this.previewLoader = new FilePreviewLoader();
|
|
10862
11299
|
this.uploadFilesEvent = output();
|
|
10863
11300
|
this.fileEvent = output();
|
|
10864
11301
|
// ---- ControlValueAccessor implementation ----
|
|
@@ -10881,8 +11318,28 @@ class SmartMultiFileEditorComponent {
|
|
|
10881
11318
|
? structuredClone(value)
|
|
10882
11319
|
: JSON.parse(JSON.stringify(value));
|
|
10883
11320
|
this.isDisabled = !!value.isDisabled;
|
|
11321
|
+
const texts = value.multiFileUploaderProperties?.uploadDescriptor?.texts;
|
|
11322
|
+
this.playTooltip = { tooltip: UploadWidgetUtils.resolveTexts(texts).play };
|
|
11323
|
+
this.requestPreviews();
|
|
10884
11324
|
this.cdr.markForCheck();
|
|
10885
11325
|
}
|
|
11326
|
+
/** With thumbnail FULL the image rows and the expanded player's row are previewed. */
|
|
11327
|
+
requestPreviews() {
|
|
11328
|
+
const files = this.isFullThumbnail && Array.isArray(this._widgetInstance.value)
|
|
11329
|
+
? this._widgetInstance.value
|
|
11330
|
+
: [];
|
|
11331
|
+
this.previewLoader.setRequests(files
|
|
11332
|
+
.filter((file) => this.getPreviewKind(file) === 'image' || file.dataUri === this.expandedDataUri)
|
|
11333
|
+
.map((file) => ({
|
|
11334
|
+
key: createListFilePreviewKey(this._widgetInstance.key, file.dataUri),
|
|
11335
|
+
dataUri: file.dataUri,
|
|
11336
|
+
mimeType: file.mimeType,
|
|
11337
|
+
})));
|
|
11338
|
+
}
|
|
11339
|
+
togglePlayer(dataUri) {
|
|
11340
|
+
this.expandedDataUri = this.expandedDataUri === dataUri ? undefined : dataUri;
|
|
11341
|
+
this.requestPreviews();
|
|
11342
|
+
}
|
|
10886
11343
|
upload(event) {
|
|
10887
11344
|
if (!event.files || event.files.length == 0) {
|
|
10888
11345
|
return;
|
|
@@ -10956,6 +11413,7 @@ class SmartMultiFileEditorComponent {
|
|
|
10956
11413
|
writeValue(value) {
|
|
10957
11414
|
if (value !== undefined && this._widgetInstance) {
|
|
10958
11415
|
this._widgetInstance.value = value;
|
|
11416
|
+
this.requestPreviews();
|
|
10959
11417
|
this.cdr.markForCheck();
|
|
10960
11418
|
}
|
|
10961
11419
|
}
|
|
@@ -10976,6 +11434,19 @@ class SmartMultiFileEditorComponent {
|
|
|
10976
11434
|
get fileIcon() {
|
|
10977
11435
|
return 'insert_drive_file';
|
|
10978
11436
|
}
|
|
11437
|
+
get isFullThumbnail() {
|
|
11438
|
+
return (this._widgetInstance.multiFileUploaderProperties?.thumbnail === FileUploaderThumbnail.FULL);
|
|
11439
|
+
}
|
|
11440
|
+
/** The preview kind of a row, `undefined` without thumbnail FULL or for another file type. */
|
|
11441
|
+
getPreviewKind(file) {
|
|
11442
|
+
return this.isFullThumbnail ? getFilePreviewKind(file?.mimeType) : undefined;
|
|
11443
|
+
}
|
|
11444
|
+
getPreview(dataUri) {
|
|
11445
|
+
return this.previewLoader.getPreview(dataUri);
|
|
11446
|
+
}
|
|
11447
|
+
onPreviewMetadata(dataUri, event) {
|
|
11448
|
+
this.previewLoader.applyVideoMetadata(dataUri, event.target);
|
|
11449
|
+
}
|
|
10979
11450
|
get errorIcon() {
|
|
10980
11451
|
return 'error';
|
|
10981
11452
|
}
|
|
@@ -10986,7 +11457,7 @@ class SmartMultiFileEditorComponent {
|
|
|
10986
11457
|
useExisting: forwardRef(() => SmartMultiFileEditorComponent),
|
|
10987
11458
|
multi: true,
|
|
10988
11459
|
},
|
|
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"] }] }); }
|
|
11460
|
+
], ngImport: i0, template: "@if (widgetInstance && widgetInstance.multiFileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.multiFileUploaderProperties.uploadDescriptor!\"\n [sources]=\"widgetInstance.multiFileUploaderProperties.sources\"\n [isMultiple]=\"true\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"scrollable\">\n @for (file of widgetInstance.value; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @let previewKind = getPreviewKind(file);\n @let thumbnail = previewKind === 'image' ? getPreview(file.dataUri) : undefined;\n @if (thumbnail) {\n <img class=\"fileThumbnail\" [src]=\"thumbnail.url\" [alt]=\"file.fileName\" />\n } @else if (previewKind === 'video' || previewKind === 'audio') {\n <button\n type=\"button\"\n class=\"playTile\"\n [class.audioTile]=\"previewKind === 'audio'\"\n [class.open]=\"expandedDataUri === file.dataUri\"\n [smartTooltip]=\"playTooltip\"\n (click)=\"togglePlayer(file.dataUri)\"\n >\n <smart-icon\n [icon]=\"previewKind === 'audio' ? 'graphic_eq' : 'play_arrow'\"\n ></smart-icon>\n </button>\n } @else {\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(file?.size)) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <!--\n One toolbar per file, each resolving the widget's actions for itself and\n carrying its own file. The hidden sibling whose list these used to read is\n gone with the collection machinery (ADR-0006).\n -->\n <smart-ui-action-toolbar\n [id]=\"widgetInstance.toolbarId\"\n [actionParams]=\"{ _dataUri: file.dataUri }\"\n ></smart-ui-action-toolbar>\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file.dataUri)\"\n ></smart-icon>\n @if (!file.readOnly && !isDisabled) {\n <smart-icon\n class=\"removeIcon\"\n [icon]=\"removeIcon\"\n (click)=\"file.readOnly === false && deleteFile(file.dataUri)\"\n ></smart-icon>\n }\n </div>\n </div>\n @if (expandedDataUri === file.dataUri) {\n @if (getPreview(file.dataUri); as player) {\n <div class=\"filePlayer\">\n @if (player.kind === 'audio') {\n <audio controls [src]=\"player.url\"></audio>\n } @else {\n <video\n controls\n [src]=\"player.url\"\n (loadedmetadata)=\"onPreviewMetadata(file.dataUri, $event)\"\n ></video>\n }\n </div>\n }\n }\n }\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius);overflow:hidden}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column;border-top:1px solid #e0e0e0}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.scrollable{max-height:30vh;overflow-y:auto}.scrollable>div:not(:last-child){border-bottom:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover{border:unset}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileThumbnail,.playTile{width:3rem;height:3rem;flex:none;border-radius:4px}.fileThumbnail{object-fit:cover;background:#f1f1f1}.playTile{display:flex;align-items:center;justify-content:center;padding:0;border:none;background:#2f3550;color:#fff;cursor:pointer}.playTile.audioTile{background:#e8eaf6;color:var(--primary-color)}.playTile.open{outline:2px solid var(--primary-color);outline-offset:1px}.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"], outputs: ["uploadFilesEvent"] }] }); }
|
|
10990
11461
|
}
|
|
10991
11462
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartMultiFileEditorComponent, decorators: [{
|
|
10992
11463
|
type: Component,
|
|
@@ -10996,7 +11467,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
10996
11467
|
useExisting: forwardRef(() => SmartMultiFileEditorComponent),
|
|
10997
11468
|
multi: true,
|
|
10998
11469
|
},
|
|
10999
|
-
], imports: [
|
|
11470
|
+
], imports: [
|
|
11471
|
+
NgClass,
|
|
11472
|
+
NgStyle,
|
|
11473
|
+
SmartTooltipDirective,
|
|
11474
|
+
UiActionToolbarComponent,
|
|
11475
|
+
SmartIconComponent,
|
|
11476
|
+
UploadWidgetComponent,
|
|
11477
|
+
], template: "@if (widgetInstance && widgetInstance.multiFileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.multiFileUploaderProperties.uploadDescriptor!\"\n [sources]=\"widgetInstance.multiFileUploaderProperties.sources\"\n [isMultiple]=\"true\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"scrollable\">\n @for (file of widgetInstance.value; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @let previewKind = getPreviewKind(file);\n @let thumbnail = previewKind === 'image' ? getPreview(file.dataUri) : undefined;\n @if (thumbnail) {\n <img class=\"fileThumbnail\" [src]=\"thumbnail.url\" [alt]=\"file.fileName\" />\n } @else if (previewKind === 'video' || previewKind === 'audio') {\n <button\n type=\"button\"\n class=\"playTile\"\n [class.audioTile]=\"previewKind === 'audio'\"\n [class.open]=\"expandedDataUri === file.dataUri\"\n [smartTooltip]=\"playTooltip\"\n (click)=\"togglePlayer(file.dataUri)\"\n >\n <smart-icon\n [icon]=\"previewKind === 'audio' ? 'graphic_eq' : 'play_arrow'\"\n ></smart-icon>\n </button>\n } @else {\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(file?.size)) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <!--\n One toolbar per file, each resolving the widget's actions for itself and\n carrying its own file. The hidden sibling whose list these used to read is\n gone with the collection machinery (ADR-0006).\n -->\n <smart-ui-action-toolbar\n [id]=\"widgetInstance.toolbarId\"\n [actionParams]=\"{ _dataUri: file.dataUri }\"\n ></smart-ui-action-toolbar>\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file.dataUri)\"\n ></smart-icon>\n @if (!file.readOnly && !isDisabled) {\n <smart-icon\n class=\"removeIcon\"\n [icon]=\"removeIcon\"\n (click)=\"file.readOnly === false && deleteFile(file.dataUri)\"\n ></smart-icon>\n }\n </div>\n </div>\n @if (expandedDataUri === file.dataUri) {\n @if (getPreview(file.dataUri); as player) {\n <div class=\"filePlayer\">\n @if (player.kind === 'audio') {\n <audio controls [src]=\"player.url\"></audio>\n } @else {\n <video\n controls\n [src]=\"player.url\"\n (loadedmetadata)=\"onPreviewMetadata(file.dataUri, $event)\"\n ></video>\n }\n </div>\n }\n }\n }\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius);overflow:hidden}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column;border-top:1px solid #e0e0e0}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.scrollable{max-height:30vh;overflow-y:auto}.scrollable>div:not(:last-child){border-bottom:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover{border:unset}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileThumbnail,.playTile{width:3rem;height:3rem;flex:none;border-radius:4px}.fileThumbnail{object-fit:cover;background:#f1f1f1}.playTile{display:flex;align-items:center;justify-content:center;padding:0;border:none;background:#2f3550;color:#fff;cursor:pointer}.playTile.audioTile{background:#e8eaf6;color:var(--primary-color)}.playTile.open{outline:2px solid var(--primary-color);outline-offset:1px}.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
11478
|
}], propDecorators: { widgetInstance: [{
|
|
11001
11479
|
type: Input
|
|
11002
11480
|
}], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }], fileEvent: [{ type: i0.Output, args: ["fileEvent"] }] } });
|
|
@@ -11226,7 +11704,7 @@ class SmartVoiceRecorderComponent {
|
|
|
11226
11704
|
useExisting: forwardRef(() => SmartVoiceRecorderComponent),
|
|
11227
11705
|
multi: true,
|
|
11228
11706
|
},
|
|
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"] }] }); }
|
|
11707
|
+
], ngImport: i0, template: "<voice-record-widget [openDirection]=\"openDirection\" (recordingSaved)=\"uploadRecording($event)\">\n</voice-record-widget>\n", styles: [":host{display:block;width:100%;min-width:0}\n"], dependencies: [{ kind: "component", type: VoiceRecordWidgetComponent, selector: "voice-record-widget", inputs: ["openDirection", "texts", "showMicrophoneError"], outputs: ["recordingSaved"] }] }); }
|
|
11230
11708
|
}
|
|
11231
11709
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartVoiceRecorderComponent, decorators: [{
|
|
11232
11710
|
type: Component,
|
|
@@ -27959,7 +28437,7 @@ class UiActionFileUploadDialogComponent {
|
|
|
27959
28437
|
this.service.cancel();
|
|
27960
28438
|
}
|
|
27961
28439
|
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"] }] }); }
|
|
28440
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: UiActionFileUploadDialogComponent, isStandalone: true, selector: "lib-ui-action-file-upload-dialog", ngImport: i0, template: "<div class=\"folderNameDialogContainer\">\n <div class=\"headerContainer\">\n <h3 class=\"color-accent-700\">\n {{ getTitle() }}\n </h3>\n </div>\n\n @if(getText()){\n <p>\n {{ getText() }}\n </p>\n } @if(descriptor){\n <smart-upload-widget\n [uploadDescriptor]=\"descriptor.upload\"\n [isMultiple]=\"isMultiple ?? true\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div class=\"folderNameDialogButtonsContainer\">\n <ui-action-button [descriptor]=\"cancelButton\" (actionClick)=\"cancel()\"></ui-action-button>\n </div>\n</div>\n", styles: [".folderNameDialogContainer{display:flex;flex-direction:column;justify-content:center;gap:1rem;min-width:30vw}photo-capture-widget{width:100%;max-width:100%;overflow:hidden;display:flex;flex-direction:column;align-items:center}.headerContainer{display:flex;flex-direction:row;justify-content:space-between;align-items:center;padding-top:.5rem}.headerContainer ::ng-deep button{color:var(--primary-color);border:unset}.headerContainer h3{margin:0;font-size:1.5rem}.folderNameDialogButtonsContainer{display:flex;flex-direction:row;justify-content:flex-start;gap:.5rem}.message{text-align:center;color:#6b7280}.uploadField{display:flex;flex-direction:row;justify-content:center;align-items:center;gap:1rem;padding:1rem}.folderNameDialogButtonsContainer ::ng-deep button:hover{background-color:rgba(from var(--primary-color) r g b/.8)}.headerContainer ::ng-deep button:hover{background-color:rgba(from var(--primary-color) r g b/.1)}@media(max-width:900px){.folderNameDialogContainer{width:100%}}.fileSize{padding-left:1rem;display:flex;justify-content:flex-end}.fileData{display:flex;flex-direction:column;justify-content:flex-end;padding-right:1rem}\n"], dependencies: [{ kind: "component", type: UploadWidgetComponent, selector: "smart-upload-widget", inputs: ["uploadDescriptor", "isMultiple", "isDisabled", "sources"], outputs: ["uploadFilesEvent"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
|
|
27963
28441
|
}
|
|
27964
28442
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UiActionFileUploadDialogComponent, decorators: [{
|
|
27965
28443
|
type: Component,
|
|
@@ -28834,5 +29312,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
28834
29312
|
* Generated bundle index. Do not edit.
|
|
28835
29313
|
*/
|
|
28836
29314
|
|
|
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 };
|
|
29315
|
+
export { ACLEntry, APIS$5 as APIS, AclEditingViewComponent, ApiModule$5 as ApiModule, ApiQueueService, BASE_PATH$7 as BASE_PATH, COLLECTION_FORMATS$7 as COLLECTION_FORMATS, CUSTOM_DIAGRAM_OPTIONS, CloseResult, CodeEditorLang, ComponentFactoryService, ComponentType, ComponentWidgetType, Configuration$7 as Configuration, DEFAULT_ROW_LAYOUT, DIALOG_DISABLE_CLOSE, DataChangeKind, DefaultChartOptionsProvider, DefaultUiActionCode_CLOSE, DrawTime, EMPTY_PARAMS, ExpandableSectionButtonIconPosition, ExpandableSectionButtonType, ExpandableSectionComponent, FileUploaderSource, FileUploaderThumbnail, FilterExpressionBoolOperator$1 as FilterExpressionBoolOperator, FilterExpressionBuilderGroupBuilderGroupKindEnum, FilterExpressionBuilderUiModelTypeEnum, FilterExpressionDataType, FilterExpressionFieldWidgetType, FilterExpressionOperation, FilterExpressionOrderByOrderEnum, GenericPageComponent, GeoMapDataLoadingMode, GeoMapDataSourceType, GeoMapItemKind, GeoMapOperationMode, GeoMapSelectionMode, GeoMapTextType, GridColumnContentType, GridDataAccessConfigKindEnum, GridDefaultRowActionTrigger, GridRowStateDisplay, GridSelectionMode, GridSelectionType, GridUiActionType, GridViewDescriptorKindEnum, HighlightPipe, IS_ASYNC_PARAM_NAME, IconPosition, ImageResourceKindEnum, InvalidSmartlinkComponent, LayoutDirection, LinkTargetEnum, MAP_ENGINE, MapEngine, MenuOpenDirection, MenuTriggerType, MessageOptionType, MessageTextType, MessageType, NAMED_VALIDATOR, NamedValidatorOperationEnum, NamedValidatorService, NoPermissionPageComponent, PageExportFieldScope, PageExportSelectionKind, PageExportValueType, PageExportValuesSource, REUSE_ROUTER_SLOT_COMPONENT, ROOT_CONTAINER_UUID, ROUTER_SLOT_ID, RecordingUploaderPropertiesOpenDirectionEnum, SMART_DATE_FORMATS, SMART_DEFAULT_VIEW_COMPONENTS, SMART_MESSAGE_DEFAULTS, SMART_NG_CLIENT_CONFIG, SMART_ROW_LAYOUT_COMPONENT, SearchPageComponent, SelectAllMode, SelectionDefinitionTypeEnum, SelectionDefinitionValueSetValueTypeEnum, ServerRequestType, SessionAPIS, SessionErrorBehaviour, SessionService, SmartActionHost, SmartActionType, SmartBackendBootstrapService, SmartComponent, SmartComponentApiClient, SmartComponentDetachedDirective, SmartComponentHost, SmartComponentLayoutComponent, SmartDateFnsAdapter, SmartDatePipe, SmartDateTimePipe, SmartDefaultErrorUiService, SmartDiagramComponent, SmartDialog, SmartEmbeddedSlotDirective, SmartExpandableSectionService, SmartFileEditorComponent, SmartFilterComponent, SmartFilterEditorContentComponent, SmartFilterEditorService, SmartFilterExpressionFieldComponent, SmartFilterPosition, SmartFilterType, SmartFilterWidgetComponent, SmartFormInputMode, SmartFormTextFieldButtonIconPosition, SmartFormWidgetDirection, SmartFormWidgetType, SmartFormWidgetWidth, SmartGridButtonType, SmartGridComponent, SmartGridDataLayout, SmartGridService, SmartGridType, SmartIconComponent, SmartIconService, SmartKeybindingService, SmartLayoutDef, SmartLinkChannelVariableInPath, SmartLinkMigrationStatusStatusEnum, SmartLinkUuidVariableInPath, SmartMapComponent, SmartMultiFileEditorComponent, SmartNavbarComponent, SmartNavbarService, SmartNavigationService, SmartNgClientFeatureKind, SmartService, SmartSessionService, SmartSessionTimerComponent, SmartTable, SmartTableButtonType, SmartTableHeaderPropertyType, SmartTableInterfaceTypeEnum, SmartTableOptionButtonDirection, SmartTableOrder, SmartTableType, SmartTimePipe, SmartTooltipDirective, SmartTreeComponent, SmartTreeNodeButtonType, SmartUserSettinsIconPosition, SmartValidatorName, SmartValueSetStore, SmartViewContextService, SmartViewRedirect, SmartVoiceRecorderComponent, SmartWidgetHintPosition, SmartWidgetHintPositionEnum, SmartdialogService, SmartfileuploaderComponent, SmartformComponent, SmartformLayoutDefinitionService, SmarttreeGenericService, SmarttreeService, SubjectSelectorComponent, ThemeService, ToggleLabelPosition, ToolbarAlignment, ToolbarAppearance, ToolbarDirection, TreeArrowPosition, TreeGridControl, USE_ADVANCED_FILTER, UiActionButtonComponent, UiActionButtonType, UiActionConfirmDialogService, UiActionDescriptorService, UiActionDialogButtonComponent, UiActionDialogType, UiActionFeedbackType, UiActionInputDialogService, UiActionInputType, UiActionService, UiActionToolbarComponent, UiActionTooltipTooltipPositionEnum, UiBadgeComponent, UiBadgeDirective, UploadWidgetType, ValidationResultPageComponent, ValueChangeMode, ValueSetDependencyWhenParentEmptyEnum, ViewContextRebuiltError, ViewEventHandlerViewEventTypeEnum, ViewService, ViewState, ViewType, barChart, bubbleChart, createBasicOptions, doughnutChart, formatChord, formatMessage, hasGroupEditActions, horizontalBarChart, horizontalStackedBarChart, injectSmartComponent, isMacPlatform, isMergedExtra, matches, multiAxisLineChart, parseChord, pieChart, polarAreaChart, provideSmartNgClient, radarChart, registerSmartWidget, resolveDateLocale, resolveSlot, resolveSmartNgClientConfig, resolveViewHandler, scatterChart, stackedBarChart, transformDataToBarLikeDataSet, transformDataToBasicDataSets, transformDataToBubble, transformDataToScatter, withSmartDiagram, withSmartMap };
|
|
28838
29316
|
//# sourceMappingURL=smartbit4all-ng-client.mjs.map
|