spiderly 19.8.2 → 19.8.3
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/fesm2022/spiderly.mjs +832 -657
- package/fesm2022/spiderly.mjs.map +1 -1
- package/lib/components/auth/auth-card/auth-card.component.d.ts +14 -0
- package/lib/components/auth/external-login/external-login.component.d.ts +20 -0
- package/lib/components/auth/external-provider-icons.d.ts +7 -0
- package/lib/components/auth/login/login.component.d.ts +7 -8
- package/lib/components/index-card/index-card.component.d.ts +2 -0
- package/lib/components/info-card/info-card.component.d.ts +1 -0
- package/lib/components/layout/profile-avatar/profile-avatar.component.d.ts +1 -0
- package/lib/components/spiderly-buttons/spiderly-button-base/spiderly-button-base.d.ts +3 -1
- package/lib/components/spiderly-data-table/spiderly-data-table.component.d.ts +6 -1
- package/lib/components/spiderly-data-view/spiderly-data-view.component.d.ts +2 -0
- package/lib/components/spiderly-panels/panel-header/panel-header.component.d.ts +2 -0
- package/lib/components/spiderly-panels/spiderly-panel/spiderly-panel.component.d.ts +3 -0
- package/lib/controls/base-control.d.ts +3 -0
- package/lib/controls/base-dropdown-control.d.ts +1 -0
- package/lib/controls/spiderly-autocomplete/spiderly-autocomplete.component.d.ts +1 -0
- package/lib/controls/spiderly-calendar/spiderly-calendar.component.d.ts +1 -0
- package/lib/controls/spiderly-colorpicker/spiderly-colorpicker.component.d.ts +1 -0
- package/lib/controls/spiderly-controls.module.d.ts +4 -3
- package/lib/controls/spiderly-markdown/spiderly-markdown.component.d.ts +37 -0
- package/lib/controls/spiderly-number/spiderly-number.component.d.ts +1 -0
- package/lib/controls/spiderly-password/spiderly-password.component.d.ts +1 -0
- package/lib/controls/spiderly-textbox/spiderly-textbox.component.d.ts +1 -0
- package/lib/entities/security-entities.d.ts +33 -1
- package/lib/errors/api-error-codes.d.ts +2 -0
- package/lib/interceptors/unauthorized.interceptor.d.ts +6 -0
- package/lib/services/api.service.security.d.ts +2 -3
- package/lib/services/app-layout.service.base.d.ts +0 -8
- package/lib/services/auth.service.base.d.ts +15 -18
- package/lib/services/config.service.base.d.ts +0 -2
- package/lib/services/helper-functions.d.ts +1 -6
- package/package.json +3 -2
- package/public-api.d.ts +4 -1
- package/lib/components/auth/partials/auth.component.d.ts +0 -19
- package/lib/components/spiderly-buttons/google-button/google-button.component.d.ts +0 -14
package/fesm2022/spiderly.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import { TooltipModule } from 'primeng/tooltip';
|
|
|
17
17
|
import * as FileSaver from 'file-saver';
|
|
18
18
|
import mime from 'mime';
|
|
19
19
|
import 'reflect-metadata';
|
|
20
|
-
import { map, Subject, throttleTime, BehaviorSubject, of, filter as filter$1, firstValueFrom, finalize as finalize$1, tap as tap$1 } from 'rxjs';
|
|
20
|
+
import { map, Subject, throttleTime, BehaviorSubject, of, filter as filter$1, firstValueFrom, take, finalize as finalize$1, tap as tap$1, throwError } from 'rxjs';
|
|
21
21
|
import * as i4$2 from 'primeng/checkbox';
|
|
22
22
|
import { CheckboxModule } from 'primeng/checkbox';
|
|
23
23
|
import * as i4$3 from 'primeng/colorpicker';
|
|
@@ -36,7 +36,10 @@ import * as i3$1 from 'primeng/select';
|
|
|
36
36
|
import { SelectModule } from 'primeng/select';
|
|
37
37
|
import * as i4$8 from 'primeng/editor';
|
|
38
38
|
import { EditorModule, Editor } from 'primeng/editor';
|
|
39
|
-
import * as i5$2 from 'primeng/
|
|
39
|
+
import * as i5$2 from 'primeng/tabs';
|
|
40
|
+
import { TabsModule } from 'primeng/tabs';
|
|
41
|
+
import { MarkdownComponent } from 'ngx-markdown';
|
|
42
|
+
import * as i5$3 from 'primeng/fileupload';
|
|
40
43
|
import { FileUploadModule } from 'primeng/fileupload';
|
|
41
44
|
import * as i12 from 'primeng/button';
|
|
42
45
|
import { ButtonModule } from 'primeng/button';
|
|
@@ -46,10 +49,9 @@ import * as i3$2 from '@angular/router';
|
|
|
46
49
|
import { NavigationEnd, RouterModule } from '@angular/router';
|
|
47
50
|
import * as i1$1 from 'primeng/api';
|
|
48
51
|
import { ConfirmationService } from 'primeng/api';
|
|
52
|
+
import { filter, map as map$1, finalize, catchError, delay, tap, takeUntil } from 'rxjs/operators';
|
|
49
53
|
import * as i1$2 from '@angular/common/http';
|
|
50
54
|
import { HttpParams, HttpHeaders, HttpErrorResponse, HttpResponse } from '@angular/common/http';
|
|
51
|
-
import { map as map$1, finalize, delay, tap, filter, takeUntil, catchError } from 'rxjs/operators';
|
|
52
|
-
import * as i3$3 from '@abacritt/angularx-social-login';
|
|
53
55
|
import { InputOtp } from 'primeng/inputotp';
|
|
54
56
|
import * as i2$1 from 'primeng/menu';
|
|
55
57
|
import { MenuModule } from 'primeng/menu';
|
|
@@ -80,17 +82,22 @@ const ApiErrorCodes = {
|
|
|
80
82
|
UniqueViolation: 'unique_violation',
|
|
81
83
|
ForeignKeyViolation: 'foreign_key_violation',
|
|
82
84
|
ConcurrencyConflict: 'concurrency_conflict',
|
|
85
|
+
EmailNotVerified: 'email_not_verified',
|
|
86
|
+
ExternalProviderNotConfigured: 'external_provider_not_configured',
|
|
83
87
|
};
|
|
84
88
|
|
|
85
89
|
class BaseControl {
|
|
86
90
|
constructor(translocoService) {
|
|
87
91
|
this.translocoService = translocoService;
|
|
88
92
|
this.disabled = false;
|
|
93
|
+
/** Whether the field's label is rendered. Defaults to `true`. */
|
|
89
94
|
this.showLabel = true;
|
|
95
|
+
/** Whether the required (*) indicator is shown next to the label. Defaults to `true`. */
|
|
90
96
|
this.showRequired = true;
|
|
91
97
|
this.label = null; // NgModel/Want custom translation
|
|
92
98
|
this.controlValid = true; // NgModel
|
|
93
99
|
this.placeholder = '';
|
|
100
|
+
/** Whether the info tooltip icon is shown next to the label. Defaults to `false`. */
|
|
94
101
|
this.showTooltip = false;
|
|
95
102
|
this.tooltipText = null;
|
|
96
103
|
this.tooltipIcon = 'pi pi-info-circle';
|
|
@@ -143,6 +150,7 @@ class BaseDropdownControl extends BaseControl {
|
|
|
143
150
|
constructor(translocoService) {
|
|
144
151
|
super(translocoService);
|
|
145
152
|
this.translocoService = translocoService;
|
|
153
|
+
/** Whether an addon button is shown next to the dropdown. Defaults to `false`. */
|
|
146
154
|
this.showAddon = false;
|
|
147
155
|
this.addonIcon = 'pi pi-ellipsis-h';
|
|
148
156
|
this.placeholder = this.translocoService.translate('SelectFromTheList');
|
|
@@ -390,6 +398,7 @@ class SpiderlyAutocompleteComponent extends BaseAutocompleteControl {
|
|
|
390
398
|
this.translocoService = translocoService;
|
|
391
399
|
this.validatorService = validatorService;
|
|
392
400
|
this.appendTo = 'body';
|
|
401
|
+
/** Whether a clear button is shown. Defaults to `true`. */
|
|
393
402
|
this.showClear = true;
|
|
394
403
|
this.helperFormControl = new SpiderlyFormControl(null, {
|
|
395
404
|
updateOn: 'change',
|
|
@@ -622,10 +631,11 @@ function exportListToExcel(exportListToExcelObservableMethod, filter) {
|
|
|
622
631
|
FileSaver.saveAs(res.body, decodeURIComponent(fileName));
|
|
623
632
|
});
|
|
624
633
|
}
|
|
634
|
+
function getPrimengNamebookOptions(namebookList) {
|
|
635
|
+
return namebookList.map((x) => ({ label: x.displayName, code: x.id }));
|
|
636
|
+
}
|
|
625
637
|
function getPrimengDropdownNamebookOptions(getDropdownListObservable, parentEntityId) {
|
|
626
|
-
return getDropdownListObservable(parentEntityId ?? 0).pipe(map((res) =>
|
|
627
|
-
return res.map((x) => ({ label: x.displayName, code: x.id }));
|
|
628
|
-
}));
|
|
638
|
+
return getDropdownListObservable(parentEntityId ?? 0).pipe(map((res) => getPrimengNamebookOptions(res)));
|
|
629
639
|
}
|
|
630
640
|
function getPrimengDropdownCodebookOptions(getDropdownListObservable) {
|
|
631
641
|
return getDropdownListObservable().pipe(map((res) => {
|
|
@@ -675,25 +685,6 @@ function kebabToTitleCase(input) {
|
|
|
675
685
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
676
686
|
.join(' ');
|
|
677
687
|
}
|
|
678
|
-
/**
|
|
679
|
-
* Custom styling of the google button - https://medium.com/simform-engineering/implement-custom-google-sign-in-using-angular-16-9c93aeff6252
|
|
680
|
-
*/
|
|
681
|
-
function createFakeGoogleWrapper() {
|
|
682
|
-
const googleLoginWrapper = document.createElement('div');
|
|
683
|
-
googleLoginWrapper.style.display = 'none';
|
|
684
|
-
googleLoginWrapper.classList.add('custom-google-button');
|
|
685
|
-
document.body.appendChild(googleLoginWrapper);
|
|
686
|
-
window.google.accounts.id.renderButton(googleLoginWrapper, {
|
|
687
|
-
type: 'icon',
|
|
688
|
-
width: '200',
|
|
689
|
-
});
|
|
690
|
-
const googleLoginWrapperButton = googleLoginWrapper.querySelector('div[role=button]');
|
|
691
|
-
return {
|
|
692
|
-
click: () => {
|
|
693
|
-
googleLoginWrapperButton?.click();
|
|
694
|
-
},
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
688
|
const PROPS_KEY = Symbol('props');
|
|
698
689
|
function ReflectProp(target, propertyKey) {
|
|
699
690
|
const props = Reflect.getMetadata(PROPS_KEY, target) || [];
|
|
@@ -720,6 +711,7 @@ class SpiderlyCalendarComponent extends BaseControl {
|
|
|
720
711
|
constructor(translocoService) {
|
|
721
712
|
super(translocoService);
|
|
722
713
|
this.translocoService = translocoService;
|
|
714
|
+
/** Whether the time picker is shown in addition to the date. Defaults to `false`. */
|
|
723
715
|
this.showTime = false;
|
|
724
716
|
this.dateOnly = false;
|
|
725
717
|
this.timeOnly = false;
|
|
@@ -880,6 +872,7 @@ class SpiderlyColorPickerComponent extends BaseControl {
|
|
|
880
872
|
constructor(translocoService) {
|
|
881
873
|
super(translocoService);
|
|
882
874
|
this.translocoService = translocoService;
|
|
875
|
+
/** Whether a hex text input is shown alongside the color swatch. Defaults to `true`. */
|
|
883
876
|
this.showInputTextField = true;
|
|
884
877
|
}
|
|
885
878
|
ngOnInit() {
|
|
@@ -937,6 +930,7 @@ class SpiderlyPasswordComponent extends BaseControl {
|
|
|
937
930
|
constructor(translocoService) {
|
|
938
931
|
super(translocoService);
|
|
939
932
|
this.translocoService = translocoService;
|
|
933
|
+
/** Whether a password-strength meter is shown below the field. Defaults to `false`. */
|
|
940
934
|
this.showPasswordStrength = false;
|
|
941
935
|
}
|
|
942
936
|
ngOnInit() {
|
|
@@ -962,6 +956,7 @@ class SpiderlyTextboxComponent extends BaseControl {
|
|
|
962
956
|
constructor(translocoService) {
|
|
963
957
|
super(translocoService);
|
|
964
958
|
this.translocoService = translocoService;
|
|
959
|
+
/** Whether an icon button is appended to the input. Defaults to `false`. */
|
|
965
960
|
this.showButton = false;
|
|
966
961
|
this.onButtonClick = new EventEmitter();
|
|
967
962
|
}
|
|
@@ -1039,6 +1034,7 @@ class SpiderlyNumberComponent extends BaseControl {
|
|
|
1039
1034
|
constructor(translocoService) {
|
|
1040
1035
|
super(translocoService);
|
|
1041
1036
|
this.translocoService = translocoService;
|
|
1037
|
+
/** Whether increment/decrement spinner buttons are shown. Defaults to `true`. */
|
|
1042
1038
|
this.showButtons = true;
|
|
1043
1039
|
this.maxFractionDigits = 0;
|
|
1044
1040
|
}
|
|
@@ -1171,6 +1167,114 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
|
|
|
1171
1167
|
type: Input
|
|
1172
1168
|
}] } });
|
|
1173
1169
|
|
|
1170
|
+
/**
|
|
1171
|
+
* Markdown form control: a plain textarea (Write) with a rendered live preview (Preview),
|
|
1172
|
+
* arranged as tabs. The stored value is raw Markdown text.
|
|
1173
|
+
*
|
|
1174
|
+
* The preview is rendered with ngx-markdown (marked) and is intentionally APPROXIMATE — a
|
|
1175
|
+
* consuming storefront may render the same Markdown with a different engine/flavor.
|
|
1176
|
+
*
|
|
1177
|
+
* The <textarea> DOM is the source of truth for the text (the form control mirrors it, like
|
|
1178
|
+
* spiderly-editor mirrors Quill). We never splice control.value, because SpiderlyFormControl
|
|
1179
|
+
* defaults to updateOn:'blur' and would be stale relative to the focused textarea.
|
|
1180
|
+
*
|
|
1181
|
+
* When {@link uploadImageMethod} is provided (wired by the generator for properties with an
|
|
1182
|
+
* S3 public-storage attribute), pasting an image uploads it and, on success, inserts a
|
|
1183
|
+
* standard `` link at the caret via execCommand (which preserves the native undo
|
|
1184
|
+
* stack). Upload progress is shown out-of-band, not as a token in the text.
|
|
1185
|
+
*/
|
|
1186
|
+
class SpiderlyMarkdownComponent extends BaseControl {
|
|
1187
|
+
constructor(translocoService) {
|
|
1188
|
+
super(translocoService);
|
|
1189
|
+
this.translocoService = translocoService;
|
|
1190
|
+
this.objectId = 0;
|
|
1191
|
+
this.pendingImageUploads = 0;
|
|
1192
|
+
this.imageUploadFailed = false;
|
|
1193
|
+
}
|
|
1194
|
+
ngOnInit() {
|
|
1195
|
+
super.ngOnInit();
|
|
1196
|
+
}
|
|
1197
|
+
onPaste(event) {
|
|
1198
|
+
// Only intercept when image upload is wired; otherwise let the default paste happen.
|
|
1199
|
+
if (!this.uploadImageMethod || this.control?.disabled)
|
|
1200
|
+
return;
|
|
1201
|
+
const imageFile = this.getPastedImage(event);
|
|
1202
|
+
if (!imageFile)
|
|
1203
|
+
return;
|
|
1204
|
+
event.preventDefault();
|
|
1205
|
+
const formData = new FormData();
|
|
1206
|
+
formData.append('file', imageFile, `${this.objectId}-${imageFile.name || 'pasted-image.png'}`);
|
|
1207
|
+
this.imageUploadFailed = false;
|
|
1208
|
+
this.pendingImageUploads++;
|
|
1209
|
+
this.uploadImageMethod(formData).subscribe({
|
|
1210
|
+
next: (result) => {
|
|
1211
|
+
this.pendingImageUploads--;
|
|
1212
|
+
this.insertImageMarkdown(result.url);
|
|
1213
|
+
},
|
|
1214
|
+
error: () => {
|
|
1215
|
+
this.pendingImageUploads--;
|
|
1216
|
+
this.imageUploadFailed = true;
|
|
1217
|
+
},
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
getPastedImage(event) {
|
|
1221
|
+
const items = event.clipboardData?.items;
|
|
1222
|
+
if (!items)
|
|
1223
|
+
return null;
|
|
1224
|
+
for (let i = 0; i < items.length; i++) {
|
|
1225
|
+
if (items[i].type.startsWith('image/')) {
|
|
1226
|
+
return items[i].getAsFile();
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
return null;
|
|
1230
|
+
}
|
|
1231
|
+
insertImageMarkdown(url) {
|
|
1232
|
+
const snippet = ``;
|
|
1233
|
+
const textarea = this.textareaRef?.nativeElement;
|
|
1234
|
+
// Preferred path: the textarea is focused, so insert at the caret while preserving the
|
|
1235
|
+
// native undo stack. The resulting 'input' event syncs the control on blur, exactly like
|
|
1236
|
+
// the user typing — no manual setValue, no stale-model read.
|
|
1237
|
+
if (textarea && document.activeElement === textarea && document.execCommand('insertText', false, snippet)) {
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
// Fallback (textarea blurred/absent, or execCommand unsupported): read the LIVE textarea
|
|
1241
|
+
// value — never control.value, which is stale while focused. When blurred, the control is
|
|
1242
|
+
// already current, so appending can't drop uncommitted text.
|
|
1243
|
+
if (textarea) {
|
|
1244
|
+
const sep = textarea.value.length ? '\n' : '';
|
|
1245
|
+
textarea.value = `${textarea.value}${sep}${snippet}`;
|
|
1246
|
+
this.control.setValue(textarea.value);
|
|
1247
|
+
}
|
|
1248
|
+
else {
|
|
1249
|
+
const current = this.control.value ?? '';
|
|
1250
|
+
this.control.setValue(current.length ? `${current}\n${snippet}` : snippet);
|
|
1251
|
+
}
|
|
1252
|
+
this.control.markAsDirty();
|
|
1253
|
+
}
|
|
1254
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyMarkdownComponent, deps: [{ token: i1.TranslocoService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1255
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: SpiderlyMarkdownComponent, isStandalone: true, selector: "spiderly-markdown", inputs: { uploadImageMethod: "uploadImageMethod", objectId: "objectId" }, viewQueries: [{ propertyName: "textareaRef", first: true, predicate: ["textarea"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<div style=\"display: flex; flex-direction: column; gap: 0.5rem\">\n <div *ngIf=\"getTranslatedLabel() != '' && getTranslatedLabel() != null\">\n <label>{{ getTranslatedLabel() }}</label>\n <required *ngIf=\"control?.required && showRequired\"></required>\n </div>\n\n <ng-container *transloco=\"let t\">\n <p-tabs *ngIf=\"control\" value=\"write\">\n <p-tablist>\n <p-tab value=\"write\">{{ t('Write') }}</p-tab>\n <p-tab value=\"preview\">{{ t('Preview') }}</p-tab>\n </p-tablist>\n <p-tabpanels>\n <p-tabpanel value=\"write\">\n <textarea\n #textarea\n pTextarea\n [formControl]=\"control\"\n [id]=\"control.label\"\n (blur)=\"control.markAsDirty()\"\n (paste)=\"onPaste($event)\"\n [autoResize]=\"true\"\n [class]=\"control.disabled ? 'disabled' : ''\"\n [style]=\"{ width: '100%', minHeight: '320px' }\"\n ></textarea>\n <small *ngIf=\"pendingImageUploads > 0\">{{ t('UploadingImage') }}</small>\n <small *ngIf=\"imageUploadFailed\" class=\"spiderly-error-message\">{{ t('ImageUploadFailed') }}</small>\n </p-tabpanel>\n <p-tabpanel value=\"preview\">\n <div class=\"spiderly-markdown-preview\" style=\"min-height: 320px\">\n <markdown [data]=\"control.value\"></markdown>\n </div>\n </p-tabpanel>\n </p-tabpanels>\n </p-tabs>\n </ng-container>\n\n <small *ngIf=\"control?.errors && control?.dirty\" class=\"spiderly-error-message\">\n {{ getValidationErrrorMessages() }}\n </small>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i4$6.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["autoResize", "variant", "fluid", "pSize"], outputs: ["onResize"] }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i5$2.Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i5$2.TabPanels, selector: "p-tabpanels" }, { kind: "component", type: i5$2.TabPanel, selector: "p-tabpanel", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: i5$2.TabList, selector: "p-tablist" }, { kind: "component", type: i5$2.Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "component", type: RequiredComponent, selector: "required" }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] }); }
|
|
1256
|
+
}
|
|
1257
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyMarkdownComponent, decorators: [{
|
|
1258
|
+
type: Component,
|
|
1259
|
+
args: [{ selector: 'spiderly-markdown', imports: [
|
|
1260
|
+
CommonModule,
|
|
1261
|
+
ReactiveFormsModule,
|
|
1262
|
+
FormsModule,
|
|
1263
|
+
TextareaModule,
|
|
1264
|
+
TabsModule,
|
|
1265
|
+
MarkdownComponent,
|
|
1266
|
+
RequiredComponent,
|
|
1267
|
+
TranslocoDirective,
|
|
1268
|
+
], template: "<div style=\"display: flex; flex-direction: column; gap: 0.5rem\">\n <div *ngIf=\"getTranslatedLabel() != '' && getTranslatedLabel() != null\">\n <label>{{ getTranslatedLabel() }}</label>\n <required *ngIf=\"control?.required && showRequired\"></required>\n </div>\n\n <ng-container *transloco=\"let t\">\n <p-tabs *ngIf=\"control\" value=\"write\">\n <p-tablist>\n <p-tab value=\"write\">{{ t('Write') }}</p-tab>\n <p-tab value=\"preview\">{{ t('Preview') }}</p-tab>\n </p-tablist>\n <p-tabpanels>\n <p-tabpanel value=\"write\">\n <textarea\n #textarea\n pTextarea\n [formControl]=\"control\"\n [id]=\"control.label\"\n (blur)=\"control.markAsDirty()\"\n (paste)=\"onPaste($event)\"\n [autoResize]=\"true\"\n [class]=\"control.disabled ? 'disabled' : ''\"\n [style]=\"{ width: '100%', minHeight: '320px' }\"\n ></textarea>\n <small *ngIf=\"pendingImageUploads > 0\">{{ t('UploadingImage') }}</small>\n <small *ngIf=\"imageUploadFailed\" class=\"spiderly-error-message\">{{ t('ImageUploadFailed') }}</small>\n </p-tabpanel>\n <p-tabpanel value=\"preview\">\n <div class=\"spiderly-markdown-preview\" style=\"min-height: 320px\">\n <markdown [data]=\"control.value\"></markdown>\n </div>\n </p-tabpanel>\n </p-tabpanels>\n </p-tabs>\n </ng-container>\n\n <small *ngIf=\"control?.errors && control?.dirty\" class=\"spiderly-error-message\">\n {{ getValidationErrrorMessages() }}\n </small>\n</div>\n" }]
|
|
1269
|
+
}], ctorParameters: () => [{ type: i1.TranslocoService }], propDecorators: { textareaRef: [{
|
|
1270
|
+
type: ViewChild,
|
|
1271
|
+
args: ['textarea']
|
|
1272
|
+
}], uploadImageMethod: [{
|
|
1273
|
+
type: Input
|
|
1274
|
+
}], objectId: [{
|
|
1275
|
+
type: Input
|
|
1276
|
+
}] } });
|
|
1277
|
+
|
|
1174
1278
|
class SpiderlyButtonBaseComponent {
|
|
1175
1279
|
constructor(router) {
|
|
1176
1280
|
this.router = router;
|
|
@@ -1199,13 +1303,15 @@ class SpiderlyButtonBaseComponent {
|
|
|
1199
1303
|
this.subscription.unsubscribe();
|
|
1200
1304
|
}
|
|
1201
1305
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyButtonBaseComponent, deps: [{ token: i3$2.Router }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1202
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: SpiderlyButtonBaseComponent, isStandalone: true, selector: "spiderly-button-base", inputs: { icon: "icon", label: "label", outlined: "outlined", rounded: "rounded", styleClass: "styleClass", routerLink: "routerLink", style: "style", class: "class", severity: "severity", size: "size", disabled: "disabled" }, outputs: { onClick: "onClick" }, ngImport: i0, template: ``, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: SplitButtonModule }] }); }
|
|
1306
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: SpiderlyButtonBaseComponent, isStandalone: true, selector: "spiderly-button-base", inputs: { icon: "icon", iconUrl: "iconUrl", label: "label", outlined: "outlined", rounded: "rounded", styleClass: "styleClass", routerLink: "routerLink", style: "style", class: "class", severity: "severity", size: "size", disabled: "disabled" }, outputs: { onClick: "onClick" }, ngImport: i0, template: ``, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: SplitButtonModule }] }); }
|
|
1203
1307
|
}
|
|
1204
1308
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyButtonBaseComponent, decorators: [{
|
|
1205
1309
|
type: Component,
|
|
1206
1310
|
args: [{ selector: 'spiderly-button-base', template: ``, imports: [CommonModule, ButtonModule, SplitButtonModule] }]
|
|
1207
1311
|
}], ctorParameters: () => [{ type: i3$2.Router }], propDecorators: { icon: [{
|
|
1208
1312
|
type: Input
|
|
1313
|
+
}], iconUrl: [{
|
|
1314
|
+
type: Input
|
|
1209
1315
|
}], label: [{
|
|
1210
1316
|
type: Input
|
|
1211
1317
|
}], outlined: [{
|
|
@@ -1239,11 +1345,11 @@ class SpiderlyButtonComponent extends SpiderlyButtonBaseComponent {
|
|
|
1239
1345
|
this.type = 'button';
|
|
1240
1346
|
}
|
|
1241
1347
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyButtonComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1242
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: SpiderlyButtonComponent, isStandalone: true, selector: "spiderly-button", inputs: { type: "type" }, usesInheritance: true, ngImport: i0, template: "<p-button\n (onClick)=\"handleClick($event)\"\n [label]=\"label\"\n [icon]=\"icon\"\n [outlined]=\"outlined\"\n [styleClass]=\"styleClass\"\n [severity]=\"severity\"\n [rounded]=\"rounded\"\n [style]=\"style\"\n [class]=\"class\"\n [disabled]=\"disabled\"\n [size]=\"size\"\n [type]=\"type\"\n>\n <ng-content></ng-content>\n</p-button>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i12.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SplitButtonModule }] }); }
|
|
1348
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: SpiderlyButtonComponent, isStandalone: true, selector: "spiderly-button", inputs: { type: "type" }, usesInheritance: true, ngImport: i0, template: "<p-button\n (onClick)=\"handleClick($event)\"\n [label]=\"iconUrl ? undefined : label\"\n [icon]=\"icon\"\n [outlined]=\"outlined\"\n [styleClass]=\"styleClass\"\n [severity]=\"severity\"\n [rounded]=\"rounded\"\n [style]=\"style\"\n [class]=\"class\"\n [disabled]=\"disabled\"\n [size]=\"size\"\n [type]=\"type\"\n>\n <ng-container *ngIf=\"iconUrl\">\n <img [src]=\"iconUrl\" [alt]=\"label\" style=\"height: 16px; width: 16px; margin-right: 8px\" />\n <span>{{ label }}</span>\n </ng-container>\n <ng-content></ng-content>\n</p-button>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i12.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SplitButtonModule }] }); }
|
|
1243
1349
|
}
|
|
1244
1350
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyButtonComponent, decorators: [{
|
|
1245
1351
|
type: Component,
|
|
1246
|
-
args: [{ selector: 'spiderly-button', imports: [CommonModule, ButtonModule, SplitButtonModule], template: "<p-button\n (onClick)=\"handleClick($event)\"\n [label]=\"label\"\n [icon]=\"icon\"\n [outlined]=\"outlined\"\n [styleClass]=\"styleClass\"\n [severity]=\"severity\"\n [rounded]=\"rounded\"\n [style]=\"style\"\n [class]=\"class\"\n [disabled]=\"disabled\"\n [size]=\"size\"\n [type]=\"type\"\n>\n <ng-content></ng-content>\n</p-button>\n" }]
|
|
1352
|
+
args: [{ selector: 'spiderly-button', imports: [CommonModule, ButtonModule, SplitButtonModule], template: "<p-button\n (onClick)=\"handleClick($event)\"\n [label]=\"iconUrl ? undefined : label\"\n [icon]=\"icon\"\n [outlined]=\"outlined\"\n [styleClass]=\"styleClass\"\n [severity]=\"severity\"\n [rounded]=\"rounded\"\n [style]=\"style\"\n [class]=\"class\"\n [disabled]=\"disabled\"\n [size]=\"size\"\n [type]=\"type\"\n>\n <ng-container *ngIf=\"iconUrl\">\n <img [src]=\"iconUrl\" [alt]=\"label\" style=\"height: 16px; width: 16px; margin-right: 8px\" />\n <span>{{ label }}</span>\n </ng-container>\n <ng-content></ng-content>\n</p-button>\n" }]
|
|
1247
1353
|
}], propDecorators: { type: [{
|
|
1248
1354
|
type: Input
|
|
1249
1355
|
}] } });
|
|
@@ -1415,7 +1521,7 @@ class SpiderlyFileComponent extends BaseControl {
|
|
|
1415
1521
|
return isExcelFileType(mimeType);
|
|
1416
1522
|
}
|
|
1417
1523
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyFileComponent, deps: [{ token: i1.TranslocoService }, { token: SpiderlyMessageService }, { token: ValidatorAbstractService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1418
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: SpiderlyFileComponent, isStandalone: true, selector: "spiderly-file", inputs: { objectId: "objectId", fileData: "fileData", acceptedFileTypes: "acceptedFileTypes", required: "required", multiple: "multiple", isUrlFileData: "isUrlFileData", imageWidth: "imageWidth", imageHeight: "imageHeight", maxFileSize: "maxFileSize", files: "files" }, outputs: { onFileSelected: "onFileSelected", onFileRemoved: "onFileRemoved" }, usesInheritance: true, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div style=\"display: flex; flex-direction: column; gap: 0.5rem; padding: 1px\">\n <div *ngIf=\"getTranslatedLabel() != '' && getTranslatedLabel() != null\">\n <label>{{ getTranslatedLabel() }}</label>\n <!-- It's okay for this control, because for the custom uploads where we are not initializing the control from the backend, there is no need for formControl. -->\n <required *ngIf=\"control?.required || required\"></required>\n </div>\n\n <p-fileUpload\n [files]=\"files\"\n [disabled]=\"disabled\"\n [name]=\"control?.label ?? label\"\n [multiple]=\"multiple\"\n [accept]=\"acceptedFileTypesCommaSeparated\"\n [maxFileSize]=\"maxFileSize\"\n (onSelect)=\"filesSelected($event)\"\n [class]=\"control?.invalid && control?.dirty ? 'control-error-border' : ''\"\n >\n <ng-template\n pTemplate=\"header\"\n let-files\n let-chooseCallback=\"chooseCallback\"\n let-clearCallback=\"clearCallback\"\n let-uploadCallback=\"uploadCallback\"\n >\n <div\n class=\"flex flex-wrap justify-between items-center flex-1 gap-2\"\n >\n <div class=\"flex gap-2\">\n <spiderly-button\n [disabled]=\"disabled\"\n (onClick)=\"choose($event, chooseCallback)\"\n icon=\"pi pi-upload\"\n [rounded]=\"true\"\n [outlined]=\"true\"\n />\n </div>\n </div>\n </ng-template>\n <ng-template\n pTemplate=\"content\"\n let-files\n let-removeFileCallback=\"removeFileCallback\"\n >\n <div *ngIf=\"existingFileUrl\">\n <div class=\"flex justify-center p-0 gap-8\">\n <div\n class=\"card m-0 px-4 py-4 flex flex-col items-center gap-4\"\n style=\"justify-content: center; overflow: hidden\"\n >\n <div *ngIf=\"existingFileIsImage\" class=\"image-container\">\n <img [src]=\"existingFileUrl\" />\n </div>\n <div *ngIf=\"!existingFileIsImage\">\n <i class=\"pi pi-file\" style=\"margin-right: 4px\"></i>\n <span>{{ existingFileName }}</span>\n </div>\n <spiderly-button\n [disabled]=\"disabled\"\n icon=\"pi pi-times\"\n (onClick)=\"removeExistingFile()\"\n [outlined]=\"true\"\n [rounded]=\"true\"\n severity=\"danger\"\n />\n </div>\n </div>\n </div>\n <div *ngIf=\"!existingFileUrl && files.length > 0\">\n <div class=\"flex justify-center p-0 gap-8\">\n <div\n *ngFor=\"let file of files; let index = index\"\n class=\"card m-0 px-4 py-4 flex flex-col items-center gap-4\"\n style=\"justify-content: center; overflow: hidden\"\n >\n <div *ngIf=\"isFileImageType(file.type)\" class=\"image-container\">\n <img role=\"presentation\" [src]=\"file.objectURL\" />\n </div>\n <div *ngIf=\"isExcelFileType(file.type)\" class=\"excel-container\">\n <div class=\"excel-details\">\n <i\n class=\"pi pi-file-excel\"\n style=\"color: green; margin-right: 4px\"\n ></i>\n <span class=\"file-name\">{{ file.name }}</span>\n </div>\n </div>\n <spiderly-button\n [disabled]=\"disabled\"\n icon=\"pi pi-times\"\n (onClick)=\"fileRemoved(removeFileCallback, index)\"\n [outlined]=\"true\"\n [rounded]=\"true\"\n severity=\"danger\"\n />\n </div>\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate=\"file\"> </ng-template>\n <ng-template pTemplate=\"empty\">\n <div *ngIf=\"!existingFileUrl\" class=\"flex items-center justify-center flex-col\">\n <i\n class=\"pi pi-cloud-upload border-2 rounded-full p-8 text-8xl text-gray-400 border-gray-400 mt-4\"\n ></i>\n <p class=\"mt-6 mb-0\">{{ t(\"DragAndDropFilesHereToUpload\") }}</p>\n </div>\n </ng-template>\n </p-fileUpload>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: FileUploadModule }, { kind: "component", type: i5$
|
|
1524
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: SpiderlyFileComponent, isStandalone: true, selector: "spiderly-file", inputs: { objectId: "objectId", fileData: "fileData", acceptedFileTypes: "acceptedFileTypes", required: "required", multiple: "multiple", isUrlFileData: "isUrlFileData", imageWidth: "imageWidth", imageHeight: "imageHeight", maxFileSize: "maxFileSize", files: "files" }, outputs: { onFileSelected: "onFileSelected", onFileRemoved: "onFileRemoved" }, usesInheritance: true, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div style=\"display: flex; flex-direction: column; gap: 0.5rem; padding: 1px\">\n <div *ngIf=\"getTranslatedLabel() != '' && getTranslatedLabel() != null\">\n <label>{{ getTranslatedLabel() }}</label>\n <!-- It's okay for this control, because for the custom uploads where we are not initializing the control from the backend, there is no need for formControl. -->\n <required *ngIf=\"control?.required || required\"></required>\n </div>\n\n <p-fileUpload\n [files]=\"files\"\n [disabled]=\"disabled\"\n [name]=\"control?.label ?? label\"\n [multiple]=\"multiple\"\n [accept]=\"acceptedFileTypesCommaSeparated\"\n [maxFileSize]=\"maxFileSize\"\n (onSelect)=\"filesSelected($event)\"\n [class]=\"control?.invalid && control?.dirty ? 'control-error-border' : ''\"\n >\n <ng-template\n pTemplate=\"header\"\n let-files\n let-chooseCallback=\"chooseCallback\"\n let-clearCallback=\"clearCallback\"\n let-uploadCallback=\"uploadCallback\"\n >\n <div\n class=\"flex flex-wrap justify-between items-center flex-1 gap-2\"\n >\n <div class=\"flex gap-2\">\n <spiderly-button\n [disabled]=\"disabled\"\n (onClick)=\"choose($event, chooseCallback)\"\n icon=\"pi pi-upload\"\n [rounded]=\"true\"\n [outlined]=\"true\"\n />\n </div>\n </div>\n </ng-template>\n <ng-template\n pTemplate=\"content\"\n let-files\n let-removeFileCallback=\"removeFileCallback\"\n >\n <div *ngIf=\"existingFileUrl\">\n <div class=\"flex justify-center p-0 gap-8\">\n <div\n class=\"card m-0 px-4 py-4 flex flex-col items-center gap-4\"\n style=\"justify-content: center; overflow: hidden\"\n >\n <div *ngIf=\"existingFileIsImage\" class=\"image-container\">\n <img [src]=\"existingFileUrl\" />\n </div>\n <div *ngIf=\"!existingFileIsImage\">\n <i class=\"pi pi-file\" style=\"margin-right: 4px\"></i>\n <span>{{ existingFileName }}</span>\n </div>\n <spiderly-button\n [disabled]=\"disabled\"\n icon=\"pi pi-times\"\n (onClick)=\"removeExistingFile()\"\n [outlined]=\"true\"\n [rounded]=\"true\"\n severity=\"danger\"\n />\n </div>\n </div>\n </div>\n <div *ngIf=\"!existingFileUrl && files.length > 0\">\n <div class=\"flex justify-center p-0 gap-8\">\n <div\n *ngFor=\"let file of files; let index = index\"\n class=\"card m-0 px-4 py-4 flex flex-col items-center gap-4\"\n style=\"justify-content: center; overflow: hidden\"\n >\n <div *ngIf=\"isFileImageType(file.type)\" class=\"image-container\">\n <img role=\"presentation\" [src]=\"file.objectURL\" />\n </div>\n <div *ngIf=\"isExcelFileType(file.type)\" class=\"excel-container\">\n <div class=\"excel-details\">\n <i\n class=\"pi pi-file-excel\"\n style=\"color: green; margin-right: 4px\"\n ></i>\n <span class=\"file-name\">{{ file.name }}</span>\n </div>\n </div>\n <spiderly-button\n [disabled]=\"disabled\"\n icon=\"pi pi-times\"\n (onClick)=\"fileRemoved(removeFileCallback, index)\"\n [outlined]=\"true\"\n [rounded]=\"true\"\n severity=\"danger\"\n />\n </div>\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate=\"file\"> </ng-template>\n <ng-template pTemplate=\"empty\">\n <div *ngIf=\"!existingFileUrl\" class=\"flex items-center justify-center flex-col\">\n <i\n class=\"pi pi-cloud-upload border-2 rounded-full p-8 text-8xl text-gray-400 border-gray-400 mt-4\"\n ></i>\n <p class=\"mt-6 mb-0\">{{ t(\"DragAndDropFilesHereToUpload\") }}</p>\n </div>\n </ng-template>\n </p-fileUpload>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: FileUploadModule }, { kind: "component", type: i5$3.FileUpload, selector: "p-fileupload, p-fileUpload", inputs: ["name", "url", "method", "multiple", "accept", "disabled", "auto", "withCredentials", "maxFileSize", "invalidFileSizeMessageSummary", "invalidFileSizeMessageDetail", "invalidFileTypeMessageSummary", "invalidFileTypeMessageDetail", "invalidFileLimitMessageDetail", "invalidFileLimitMessageSummary", "style", "styleClass", "previewWidth", "chooseLabel", "uploadLabel", "cancelLabel", "chooseIcon", "uploadIcon", "cancelIcon", "showUploadButton", "showCancelButton", "mode", "headers", "customUpload", "fileLimit", "uploadStyleClass", "cancelStyleClass", "removeStyleClass", "chooseStyleClass", "chooseButtonProps", "uploadButtonProps", "cancelButtonProps", "files"], outputs: ["onBeforeUpload", "onSend", "onUpload", "onError", "onClear", "onRemove", "onSelect", "onProgress", "uploadHandler", "onImageError", "onRemoveUploadedFile"] }, { kind: "directive", type: i1$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: RequiredComponent, selector: "required" }, { kind: "component", type: SpiderlyButtonComponent, selector: "spiderly-button", inputs: ["type"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] }); }
|
|
1419
1525
|
}
|
|
1420
1526
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyFileComponent, decorators: [{
|
|
1421
1527
|
type: Component,
|
|
@@ -1508,6 +1614,7 @@ class SpiderlyControlsModule {
|
|
|
1508
1614
|
SpiderlyNumberComponent,
|
|
1509
1615
|
SpiderlyDropdownComponent,
|
|
1510
1616
|
SpiderlyEditorComponent,
|
|
1617
|
+
SpiderlyMarkdownComponent,
|
|
1511
1618
|
SpiderlyColorPickerComponent,
|
|
1512
1619
|
SpiderlyFileComponent], exports: [SpiderlyTextboxComponent,
|
|
1513
1620
|
SpiderlyTextareaComponent,
|
|
@@ -1522,6 +1629,7 @@ class SpiderlyControlsModule {
|
|
|
1522
1629
|
SpiderlyNumberComponent,
|
|
1523
1630
|
SpiderlyDropdownComponent,
|
|
1524
1631
|
SpiderlyEditorComponent,
|
|
1632
|
+
SpiderlyMarkdownComponent,
|
|
1525
1633
|
SpiderlyColorPickerComponent,
|
|
1526
1634
|
SpiderlyFileComponent] }); }
|
|
1527
1635
|
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyControlsModule, imports: [SpiderlyTextboxComponent,
|
|
@@ -1537,6 +1645,7 @@ class SpiderlyControlsModule {
|
|
|
1537
1645
|
SpiderlyNumberComponent,
|
|
1538
1646
|
SpiderlyDropdownComponent,
|
|
1539
1647
|
SpiderlyEditorComponent,
|
|
1648
|
+
SpiderlyMarkdownComponent,
|
|
1540
1649
|
SpiderlyColorPickerComponent,
|
|
1541
1650
|
SpiderlyFileComponent] }); }
|
|
1542
1651
|
}
|
|
@@ -1557,6 +1666,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
|
|
|
1557
1666
|
SpiderlyNumberComponent,
|
|
1558
1667
|
SpiderlyDropdownComponent,
|
|
1559
1668
|
SpiderlyEditorComponent,
|
|
1669
|
+
SpiderlyMarkdownComponent,
|
|
1560
1670
|
SpiderlyColorPickerComponent,
|
|
1561
1671
|
SpiderlyFileComponent,
|
|
1562
1672
|
],
|
|
@@ -1574,6 +1684,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
|
|
|
1574
1684
|
SpiderlyNumberComponent,
|
|
1575
1685
|
SpiderlyDropdownComponent,
|
|
1576
1686
|
SpiderlyEditorComponent,
|
|
1687
|
+
SpiderlyMarkdownComponent,
|
|
1577
1688
|
SpiderlyColorPickerComponent,
|
|
1578
1689
|
SpiderlyFileComponent,
|
|
1579
1690
|
],
|
|
@@ -1582,95 +1693,494 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
|
|
|
1582
1693
|
}]
|
|
1583
1694
|
}] });
|
|
1584
1695
|
|
|
1585
|
-
class
|
|
1586
|
-
|
|
1587
|
-
constructor({ id, email, } = {}) {
|
|
1696
|
+
class InitCompanyAuthDialogDetails extends BaseEntity {
|
|
1697
|
+
constructor({ image, companyName, } = {}) {
|
|
1588
1698
|
super();
|
|
1589
|
-
this.
|
|
1590
|
-
this.
|
|
1699
|
+
this.image = image;
|
|
1700
|
+
this.companyName = companyName;
|
|
1591
1701
|
}
|
|
1592
|
-
static { this.
|
|
1593
|
-
id: {
|
|
1594
|
-
type: 'number',
|
|
1595
|
-
},
|
|
1596
|
-
email: {
|
|
1597
|
-
type: 'string',
|
|
1598
|
-
},
|
|
1599
|
-
}; }
|
|
1702
|
+
static { this.typeName = 'InitCompanyAuthDialogDetails'; }
|
|
1600
1703
|
}
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
constructor(
|
|
1604
|
-
|
|
1605
|
-
this.
|
|
1606
|
-
this.
|
|
1607
|
-
this.
|
|
1608
|
-
|
|
1704
|
+
|
|
1705
|
+
class ConfigServiceBase {
|
|
1706
|
+
constructor() {
|
|
1707
|
+
this.production = false;
|
|
1708
|
+
this.frontendUrl = 'http://localhost:4200';
|
|
1709
|
+
this.companyName = 'Company Name';
|
|
1710
|
+
this.primaryColor = '#111b2c';
|
|
1711
|
+
/* URLs */
|
|
1712
|
+
this.loginSlug = 'login';
|
|
1713
|
+
/* Local storage */
|
|
1714
|
+
this.accessTokenKey = 'access_token';
|
|
1715
|
+
this.refreshTokenKey = 'refresh_token';
|
|
1716
|
+
this.browserIdKey = 'browser_id';
|
|
1717
|
+
this.httpOptions = {};
|
|
1718
|
+
this.httpSkipSpinnerOptions = {
|
|
1719
|
+
headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
|
|
1720
|
+
params: new HttpParams().set('X-Skip-Spinner', 'true'),
|
|
1721
|
+
};
|
|
1722
|
+
this.logoPath = 'assets/images/logo/logo.svg';
|
|
1723
|
+
/* Pagination */
|
|
1724
|
+
this.defaultPageSize = 10;
|
|
1609
1725
|
}
|
|
1610
|
-
static { this
|
|
1611
|
-
|
|
1612
|
-
type: 'number',
|
|
1613
|
-
},
|
|
1614
|
-
email: {
|
|
1615
|
-
type: 'string',
|
|
1616
|
-
},
|
|
1617
|
-
accessToken: {
|
|
1618
|
-
type: 'string',
|
|
1619
|
-
},
|
|
1620
|
-
refreshToken: {
|
|
1621
|
-
type: 'string',
|
|
1622
|
-
},
|
|
1623
|
-
}; }
|
|
1726
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ConfigServiceBase, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1727
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ConfigServiceBase, providedIn: 'root' }); }
|
|
1624
1728
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1729
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ConfigServiceBase, decorators: [{
|
|
1730
|
+
type: Injectable,
|
|
1731
|
+
args: [{
|
|
1732
|
+
providedIn: 'root',
|
|
1733
|
+
}]
|
|
1734
|
+
}], ctorParameters: () => [] });
|
|
1735
|
+
|
|
1736
|
+
class ApiSecurityService {
|
|
1737
|
+
constructor(http, config) {
|
|
1738
|
+
this.http = http;
|
|
1739
|
+
this.config = config;
|
|
1740
|
+
//#region Authentication
|
|
1741
|
+
this.login = (request) => {
|
|
1742
|
+
return this.http.post(`${this.config.apiUrl}/Security/Login`, request, this.config.httpOptions);
|
|
1743
|
+
};
|
|
1744
|
+
this.sendLoginVerificationEmail = (loginDTO) => {
|
|
1745
|
+
return this.http.post(`${this.config.apiUrl}/Security/SendLoginVerificationEmail`, loginDTO, this.config.httpOptions);
|
|
1746
|
+
};
|
|
1747
|
+
this.loginWithCookies = (request) => {
|
|
1748
|
+
return this.http.post(`${this.config.apiUrl}/Security/LoginWithCookies`, request, this.config.httpOptions);
|
|
1749
|
+
};
|
|
1750
|
+
this.getExternalProviders = () => {
|
|
1751
|
+
return this.http.get(`${this.config.apiUrl}/Security/GetExternalProviders`, this.config.httpSkipSpinnerOptions);
|
|
1752
|
+
};
|
|
1753
|
+
this.logout = (browserId) => {
|
|
1754
|
+
return this.http.get(`${this.config.apiUrl}/Security/Logout?browserId=${browserId}`);
|
|
1755
|
+
};
|
|
1756
|
+
this.logoutWithCookies = (browserId) => {
|
|
1757
|
+
return this.http.get(`${this.config.apiUrl}/Security/LogoutWithCookies?browserId=${browserId}`);
|
|
1758
|
+
};
|
|
1759
|
+
this.refreshTokenWithHeaders = (request) => {
|
|
1760
|
+
return this.http.post(`${this.config.apiUrl}/Security/RefreshTokenWithHeaders`, request, this.config.httpOptions);
|
|
1761
|
+
};
|
|
1762
|
+
this.refreshTokenWithCookies = (browserId) => {
|
|
1763
|
+
// POST, not GET: refresh rotates the single-use token (a state mutation), and a cacheable GET let
|
|
1764
|
+
// browsers replay a stale logged-in response on back/forward navigation (phantom dashboard after logout).
|
|
1765
|
+
return this.http.post(`${this.config.apiUrl}/Security/RefreshTokenWithCookies?browserId=${browserId}`, null);
|
|
1766
|
+
};
|
|
1767
|
+
//#endregion
|
|
1768
|
+
//#region User
|
|
1769
|
+
this.getCurrentUserBase = () => {
|
|
1770
|
+
return this.http.get(`${this.config.apiUrl}/Security/GetCurrentUserBase`, this.config.httpSkipSpinnerOptions);
|
|
1771
|
+
};
|
|
1772
|
+
this.getCurrentUserPermissionCodes = () => {
|
|
1773
|
+
return this.http.get(`${this.config.apiUrl}/Security/GetCurrentUserPermissionCodes`, this.config.httpSkipSpinnerOptions);
|
|
1774
|
+
};
|
|
1632
1775
|
}
|
|
1633
|
-
static { this
|
|
1634
|
-
|
|
1635
|
-
type: 'string',
|
|
1636
|
-
},
|
|
1637
|
-
browserId: {
|
|
1638
|
-
type: 'string',
|
|
1639
|
-
},
|
|
1640
|
-
email: {
|
|
1641
|
-
type: 'string',
|
|
1642
|
-
},
|
|
1643
|
-
}; }
|
|
1776
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ApiSecurityService, deps: [{ token: i1$2.HttpClient }, { token: ConfigServiceBase }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1777
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ApiSecurityService, providedIn: 'root' }); }
|
|
1644
1778
|
}
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1779
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ApiSecurityService, decorators: [{
|
|
1780
|
+
type: Injectable,
|
|
1781
|
+
args: [{
|
|
1782
|
+
providedIn: 'root',
|
|
1783
|
+
}]
|
|
1784
|
+
}], ctorParameters: () => [{ type: i1$2.HttpClient }, { type: ConfigServiceBase }] });
|
|
1785
|
+
|
|
1786
|
+
/**
|
|
1787
|
+
* Cookie-based session auth. The access/refresh JWTs live in HttpOnly cookies set by the backend
|
|
1788
|
+
* (so JS never holds them — XSS can't exfiltrate them); requests are authenticated via
|
|
1789
|
+
* `withCredentials` (see jwtInterceptor). The readable result only carries userId/email/expiry.
|
|
1790
|
+
*/
|
|
1791
|
+
class AuthServiceBase {
|
|
1792
|
+
constructor(router, http, apiService, config, platformId) {
|
|
1793
|
+
this.router = router;
|
|
1794
|
+
this.http = http;
|
|
1795
|
+
this.apiService = apiService;
|
|
1796
|
+
this.config = config;
|
|
1797
|
+
this.platformId = platformId;
|
|
1798
|
+
this.apiUrl = this.config.apiUrl;
|
|
1799
|
+
// External-login error code captured from the bootstrap URL (?externalAuthError=expired|failed) set by the
|
|
1800
|
+
// backend's OAuth callback on failure. Captured before routing can strip it, surfaced once by the login page.
|
|
1801
|
+
this.externalAuthErrorCode = null;
|
|
1802
|
+
this._currentUserPermissionCodes = new BehaviorSubject(undefined);
|
|
1803
|
+
// The subject seeds with `undefined` (not yet loaded) and emits `null` on logout. Consumers only ever care
|
|
1804
|
+
// about a real code list, so filter both out here — subscribers get a clean `string[]`, and `firstValueFrom`
|
|
1805
|
+
// waits for the first loaded value instead of grabbing the `undefined` seed in a load race.
|
|
1806
|
+
this.currentUserPermissionCodes$ = this._currentUserPermissionCodes
|
|
1807
|
+
.asObservable()
|
|
1808
|
+
.pipe(filter((codes) => codes != null));
|
|
1809
|
+
this._user = new BehaviorSubject(undefined);
|
|
1810
|
+
this.user$ = this._user.asObservable();
|
|
1811
|
+
// Cross-tab sync. We store only marker values here (never tokens — those are HttpOnly cookies).
|
|
1812
|
+
this.storageEventListener = (event) => {
|
|
1813
|
+
if (event.storageArea === localStorage) {
|
|
1814
|
+
if (event.key === 'logout-event') {
|
|
1815
|
+
this.stopTokenTimer();
|
|
1816
|
+
this._user.next(null);
|
|
1817
|
+
this._currentUserPermissionCodes.next(null);
|
|
1818
|
+
}
|
|
1819
|
+
if (event.key === 'login-event') {
|
|
1820
|
+
this.refreshToken().subscribe();
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
};
|
|
1824
|
+
this.onAfterLogout = () => {
|
|
1825
|
+
this._currentUserPermissionCodes.next(null);
|
|
1826
|
+
this.router.navigate([this.config.loginSlug]);
|
|
1827
|
+
};
|
|
1828
|
+
this.onAfterRefreshToken = () => {
|
|
1829
|
+
this.setCurrentUserPermissionCodes().subscribe(); // after the session is re-established
|
|
1830
|
+
};
|
|
1831
|
+
this.initCompanyAuthDialogDetails = () => {
|
|
1832
|
+
return of(new InitCompanyAuthDialogDetails({
|
|
1833
|
+
image: this.config.logoPath,
|
|
1834
|
+
companyName: this.config.companyName,
|
|
1835
|
+
}));
|
|
1836
|
+
};
|
|
1837
|
+
this.onAfterNgOnDestroy = () => { };
|
|
1838
|
+
if (isPlatformBrowser(platformId)) {
|
|
1839
|
+
window.addEventListener('storage', this.storageEventListener);
|
|
1840
|
+
}
|
|
1651
1841
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
},
|
|
1656
|
-
browserId: {
|
|
1657
|
-
type: 'string',
|
|
1658
|
-
},
|
|
1659
|
-
}; }
|
|
1660
|
-
}
|
|
1661
|
-
class UserRole extends BaseEntity {
|
|
1662
|
-
static { this.typeName = 'UserRole'; }
|
|
1663
|
-
constructor({ roleId, userId, } = {}) {
|
|
1664
|
-
super();
|
|
1665
|
-
this.roleId = roleId;
|
|
1666
|
-
this.userId = userId;
|
|
1842
|
+
sendLoginVerificationEmail(body) {
|
|
1843
|
+
body.browserId = this.getBrowserId();
|
|
1844
|
+
return this.apiService.sendLoginVerificationEmail(body);
|
|
1667
1845
|
}
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1846
|
+
login(body) {
|
|
1847
|
+
body.browserId = this.getBrowserId();
|
|
1848
|
+
return this.apiService.loginWithCookies(body).pipe(map$1((result) => {
|
|
1849
|
+
this.handleAuthResult(result);
|
|
1850
|
+
return result;
|
|
1851
|
+
}));
|
|
1852
|
+
}
|
|
1853
|
+
// Establishes the in-memory session from a cookie auth result (login or refresh). No tokens are stored
|
|
1854
|
+
// in JS — only the user identity + the access-token expiry the backend reports (to schedule refresh).
|
|
1855
|
+
handleAuthResult(result) {
|
|
1856
|
+
this._user.next({
|
|
1857
|
+
id: result.userId,
|
|
1858
|
+
email: result.email,
|
|
1859
|
+
});
|
|
1860
|
+
this.accessTokenExpiresAt = result.accessTokenExpiresAt
|
|
1861
|
+
? new Date(result.accessTokenExpiresAt)
|
|
1862
|
+
: undefined;
|
|
1863
|
+
localStorage.setItem('login-event', 'login' + Math.random());
|
|
1864
|
+
this.startTokenTimer();
|
|
1865
|
+
this.setCurrentUserPermissionCodes().subscribe();
|
|
1866
|
+
}
|
|
1867
|
+
logout() {
|
|
1868
|
+
const browserId = this.getBrowserId();
|
|
1869
|
+
this.apiService
|
|
1870
|
+
.logoutWithCookies(browserId)
|
|
1871
|
+
.pipe(finalize(() => {
|
|
1872
|
+
this._user.next(null);
|
|
1873
|
+
localStorage.setItem('logout-event', 'logout' + Math.random());
|
|
1874
|
+
this.onAfterLogout();
|
|
1875
|
+
this.stopTokenTimer();
|
|
1876
|
+
}))
|
|
1877
|
+
.subscribe();
|
|
1878
|
+
}
|
|
1879
|
+
// Clears in-memory session state without calling the backend — used when a request comes back 401
|
|
1880
|
+
// (the backend has already cleared the auth cookies in that case).
|
|
1881
|
+
clearSession() {
|
|
1882
|
+
this.stopTokenTimer();
|
|
1883
|
+
this._user.next(null);
|
|
1884
|
+
this._currentUserPermissionCodes.next(null);
|
|
1885
|
+
localStorage.setItem('logout-event', 'logout' + Math.random());
|
|
1886
|
+
}
|
|
1887
|
+
// Called on app init and by the proactive timer. The refresh token is an HttpOnly cookie; a 401 ("no valid
|
|
1888
|
+
// session" — not logged in / expired) propagates from the interceptor and is handled by catchError below,
|
|
1889
|
+
// resolving the session to anonymous (null). map runs only for a real result, so _user is never partial.
|
|
1890
|
+
refreshToken() {
|
|
1891
|
+
const browserId = this.getBrowserId();
|
|
1892
|
+
return this.apiService.refreshTokenWithCookies(browserId).pipe(map$1((result) => {
|
|
1893
|
+
if (result) {
|
|
1894
|
+
// A re-established session makes any pending external-login error moot — drop it so it can't
|
|
1895
|
+
// surface as a stale toast on a later /login visit (e.g. after a subsequent logout).
|
|
1896
|
+
this.externalAuthErrorCode = null;
|
|
1897
|
+
this._user.next({ id: result.userId, email: result.email });
|
|
1898
|
+
this.accessTokenExpiresAt = result.accessTokenExpiresAt
|
|
1899
|
+
? new Date(result.accessTokenExpiresAt)
|
|
1900
|
+
: undefined;
|
|
1901
|
+
this.startTokenTimer();
|
|
1902
|
+
this.onAfterRefreshToken();
|
|
1903
|
+
}
|
|
1904
|
+
return result;
|
|
1905
|
+
}), catchError(() => {
|
|
1906
|
+
this._user.next(null);
|
|
1907
|
+
return of(null);
|
|
1908
|
+
}));
|
|
1909
|
+
}
|
|
1910
|
+
// Reads ?externalAuthError= from the bootstrap URL (set by the backend OAuth callback on failure) and
|
|
1911
|
+
// strips it so a manual refresh won't re-trigger the message. Called from the app initializer, before the
|
|
1912
|
+
// router runs — otherwise an unauthenticated landing on "/" redirects to /login and drops the param.
|
|
1913
|
+
captureExternalAuthError() {
|
|
1914
|
+
if (isPlatformBrowser(this.platformId) === false) {
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
const params = new URLSearchParams(window.location.search);
|
|
1918
|
+
const code = params.get('externalAuthError');
|
|
1919
|
+
if (!code) {
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
this.externalAuthErrorCode = code;
|
|
1923
|
+
params.delete('externalAuthError');
|
|
1924
|
+
const query = params.toString();
|
|
1925
|
+
history.replaceState(history.state, '', window.location.pathname + (query ? `?${query}` : '') + window.location.hash);
|
|
1926
|
+
}
|
|
1927
|
+
getBrowserId() {
|
|
1928
|
+
let browserId = localStorage.getItem(this.config.browserIdKey); // not a token — a stable per-browser id
|
|
1929
|
+
if (!browserId) {
|
|
1930
|
+
browserId = crypto.randomUUID();
|
|
1931
|
+
localStorage.setItem(this.config.browserIdKey, browserId);
|
|
1932
|
+
}
|
|
1933
|
+
return browserId;
|
|
1934
|
+
}
|
|
1935
|
+
getTokenRemainingTime() {
|
|
1936
|
+
if (!this.accessTokenExpiresAt) {
|
|
1937
|
+
return 0;
|
|
1938
|
+
}
|
|
1939
|
+
return this.accessTokenExpiresAt.getTime() - Date.now();
|
|
1940
|
+
}
|
|
1941
|
+
startTokenTimer() {
|
|
1942
|
+
const timeout = this.getTokenRemainingTime();
|
|
1943
|
+
if (timeout <= 0) {
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
this.stopTokenTimer();
|
|
1947
|
+
this.timer = of(true)
|
|
1948
|
+
.pipe(delay(timeout), tap({
|
|
1949
|
+
next: () => this.refreshToken().subscribe(),
|
|
1950
|
+
}))
|
|
1951
|
+
.subscribe();
|
|
1952
|
+
}
|
|
1953
|
+
stopTokenTimer() {
|
|
1954
|
+
this.timer?.unsubscribe();
|
|
1955
|
+
}
|
|
1956
|
+
navigateToDashboard() {
|
|
1957
|
+
this.router.navigate(['/']);
|
|
1958
|
+
}
|
|
1959
|
+
setCurrentUserPermissionCodes() {
|
|
1960
|
+
return this.apiService.getCurrentUserPermissionCodes().pipe(map$1((permissionCodes) => {
|
|
1961
|
+
this._currentUserPermissionCodes.next(permissionCodes);
|
|
1962
|
+
return permissionCodes;
|
|
1963
|
+
}));
|
|
1964
|
+
}
|
|
1965
|
+
ngOnDestroy() {
|
|
1966
|
+
if (isPlatformBrowser(this.platformId)) {
|
|
1967
|
+
window.removeEventListener('storage', this.storageEventListener);
|
|
1968
|
+
}
|
|
1969
|
+
this.onAfterNgOnDestroy();
|
|
1970
|
+
}
|
|
1971
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: AuthServiceBase, deps: [{ token: i3$2.Router }, { token: i1$2.HttpClient }, { token: ApiSecurityService }, { token: ConfigServiceBase }, { token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1972
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: AuthServiceBase, providedIn: 'root' }); }
|
|
1973
|
+
}
|
|
1974
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: AuthServiceBase, decorators: [{
|
|
1975
|
+
type: Injectable,
|
|
1976
|
+
args: [{
|
|
1977
|
+
providedIn: 'root',
|
|
1978
|
+
}]
|
|
1979
|
+
}], ctorParameters: () => [{ type: i3$2.Router }, { type: i1$2.HttpClient }, { type: ApiSecurityService }, { type: ConfigServiceBase }, { type: Object, decorators: [{
|
|
1980
|
+
type: Inject,
|
|
1981
|
+
args: [PLATFORM_ID]
|
|
1982
|
+
}] }] });
|
|
1983
|
+
|
|
1984
|
+
class AuthCardComponent {
|
|
1985
|
+
constructor(authService) {
|
|
1986
|
+
this.authService = authService;
|
|
1987
|
+
this.companyDetailsSubscription = null;
|
|
1988
|
+
}
|
|
1989
|
+
ngOnInit() {
|
|
1990
|
+
this.companyDetailsSubscription = this.authService
|
|
1991
|
+
.initCompanyAuthDialogDetails()
|
|
1992
|
+
.subscribe((details) => {
|
|
1993
|
+
if (details != null) {
|
|
1994
|
+
this.image = details.image;
|
|
1995
|
+
this.companyName = details.companyName;
|
|
1996
|
+
}
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
ngOnDestroy() {
|
|
2000
|
+
this.companyDetailsSubscription?.unsubscribe();
|
|
2001
|
+
}
|
|
2002
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: AuthCardComponent, deps: [{ token: AuthServiceBase }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2003
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: AuthCardComponent, isStandalone: true, selector: "spiderly-auth-card", ngImport: i0, template: "<div class=\"flex min-h-screen overflow-hidden p-5\">\n <div class=\"flex flex-col w-full\">\n <div\n class=\"w-full sm:w-120\"\n style=\"\n margin: auto;\n border-radius: 50px;\n padding: 0.3rem;\n background: linear-gradient(\n 180deg,\n var(--p-primary-color) 10%,\n rgba(33, 150, 243, 0) 30%\n );\n \"\n >\n <div class=\"surface-card py-12 px-8 sm:px-12\" style=\"border-radius: 45px\">\n <div class=\"flex justify-center\" style=\"margin-bottom: 38px\">\n <ng-content select=\"[auth-logo]\">\n <img\n *ngIf=\"image != null\"\n [src]=\"image\"\n alt=\"{{ companyName }} Logo\"\n title=\"{{ companyName }} Logo\"\n class=\"max-h-15\"\n />\n <i\n *ngIf=\"image == null\"\n class=\"pi pi-spin pi-spinner primary-color\"\n style=\"font-size: 2rem\"\n ></i>\n </ng-content>\n </div>\n\n <ng-content></ng-content>\n\n <ng-content select=\"[auth-footer]\"></ng-content>\n </div>\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); }
|
|
2004
|
+
}
|
|
2005
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: AuthCardComponent, decorators: [{
|
|
2006
|
+
type: Component,
|
|
2007
|
+
args: [{ selector: 'spiderly-auth-card', imports: [CommonModule], template: "<div class=\"flex min-h-screen overflow-hidden p-5\">\n <div class=\"flex flex-col w-full\">\n <div\n class=\"w-full sm:w-120\"\n style=\"\n margin: auto;\n border-radius: 50px;\n padding: 0.3rem;\n background: linear-gradient(\n 180deg,\n var(--p-primary-color) 10%,\n rgba(33, 150, 243, 0) 30%\n );\n \"\n >\n <div class=\"surface-card py-12 px-8 sm:px-12\" style=\"border-radius: 45px\">\n <div class=\"flex justify-center\" style=\"margin-bottom: 38px\">\n <ng-content select=\"[auth-logo]\">\n <img\n *ngIf=\"image != null\"\n [src]=\"image\"\n alt=\"{{ companyName }} Logo\"\n title=\"{{ companyName }} Logo\"\n class=\"max-h-15\"\n />\n <i\n *ngIf=\"image == null\"\n class=\"pi pi-spin pi-spinner primary-color\"\n style=\"font-size: 2rem\"\n ></i>\n </ng-content>\n </div>\n\n <ng-content></ng-content>\n\n <ng-content select=\"[auth-footer]\"></ng-content>\n </div>\n </div>\n </div>\n</div>\n" }]
|
|
2008
|
+
}], ctorParameters: () => [{ type: AuthServiceBase }] });
|
|
2009
|
+
|
|
2010
|
+
const GOOGLE_G_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="18" height="18"><path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/><path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"/><path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.55 10.78l7.98-6.19z"/><path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"/></svg>`;
|
|
2011
|
+
/**
|
|
2012
|
+
* Built-in default icons for external auth providers, keyed by provider code.
|
|
2013
|
+
* Values are inline data URIs — no network request, CSP entry, or asset wiring,
|
|
2014
|
+
* and they render offline. Consumers override per code via the `providerIcons`
|
|
2015
|
+
* input on ExternalLoginComponent / SpiderlyLoginComponent.
|
|
2016
|
+
*/
|
|
2017
|
+
const DEFAULT_EXTERNAL_PROVIDER_ICONS = {
|
|
2018
|
+
google: `data:image/svg+xml,${encodeURIComponent(GOOGLE_G_SVG)}`,
|
|
2019
|
+
};
|
|
2020
|
+
|
|
2021
|
+
class ExternalLoginComponent {
|
|
2022
|
+
constructor(config, authService, apiService) {
|
|
2023
|
+
this.config = config;
|
|
2024
|
+
this.authService = authService;
|
|
2025
|
+
this.apiService = apiService;
|
|
2026
|
+
/** Per-code icon overrides; unset codes fall back to DEFAULT_EXTERNAL_PROVIDER_ICONS. */
|
|
2027
|
+
this.providerIcons = {};
|
|
2028
|
+
// Config-driven: populated from Security/GetExternalProviders (backend is the single source of truth for which providers are enabled).
|
|
2029
|
+
this.externalProviders = [];
|
|
2030
|
+
}
|
|
2031
|
+
ngOnInit() {
|
|
2032
|
+
this.apiService.getExternalProviders().subscribe({
|
|
2033
|
+
next: (providers) => {
|
|
2034
|
+
this.externalProviders = providers ?? [];
|
|
2035
|
+
},
|
|
2036
|
+
// The global unauthorized interceptor already surfaces the HTTP error to the user; here we just
|
|
2037
|
+
// leave the provider buttons hidden instead of letting the error reach the global error handler.
|
|
2038
|
+
error: () => {
|
|
2039
|
+
this.externalProviders = [];
|
|
2040
|
+
},
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
2043
|
+
iconFor(code) {
|
|
2044
|
+
return this.providerIcons[code] ?? DEFAULT_EXTERNAL_PROVIDER_ICONS[code];
|
|
2045
|
+
}
|
|
2046
|
+
loginWithExternalProvider(code) {
|
|
2047
|
+
// Server-side flow (B2): hand off to the backend challenge endpoint. The backend runs the OAuth
|
|
2048
|
+
// dance, sets the session cookies, and redirects back to returnUrl.
|
|
2049
|
+
const returnUrl = this.config.frontendUrl;
|
|
2050
|
+
const browserId = this.authService.getBrowserId();
|
|
2051
|
+
window.location.href =
|
|
2052
|
+
`${this.config.apiUrl}/Security/ExternalLoginChallenge` +
|
|
2053
|
+
`?provider=${encodeURIComponent(code)}` +
|
|
2054
|
+
`&returnUrl=${encodeURIComponent(returnUrl)}` +
|
|
2055
|
+
`&browserId=${encodeURIComponent(browserId)}`;
|
|
2056
|
+
}
|
|
2057
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ExternalLoginComponent, deps: [{ token: ConfigServiceBase }, { token: AuthServiceBase }, { token: ApiSecurityService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2058
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: ExternalLoginComponent, isStandalone: true, selector: "spiderly-external-login", inputs: { providerIcons: "providerIcons" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div *ngIf=\"externalProviders.length > 0\">\n <div\n style=\"display: flex; align-items: center; gap: 7px; justify-content: center; margin-bottom: 16px;\"\n >\n <div class=\"separator\"></div>\n <div>{{ t(\"or\") }}</div>\n <div class=\"separator\"></div>\n </div>\n <div style=\"display: flex; flex-direction: column; gap: 10px\">\n <spiderly-button\n *ngFor=\"let provider of externalProviders\"\n (onClick)=\"loginWithExternalProvider(provider.code)\"\n [label]=\"provider.label || (provider.code | titlecase)\"\n [iconUrl]=\"iconFor(provider.code)\"\n [outlined]=\"true\"\n styleClass=\"w-full\"\n ></spiderly-button>\n </div>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: SpiderlyButtonComponent, selector: "spiderly-button", inputs: ["type"] }] }); }
|
|
2059
|
+
}
|
|
2060
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ExternalLoginComponent, decorators: [{
|
|
2061
|
+
type: Component,
|
|
2062
|
+
args: [{ selector: 'spiderly-external-login', imports: [CommonModule, TranslocoDirective, SpiderlyButtonComponent], template: "<ng-container *transloco=\"let t\">\n <div *ngIf=\"externalProviders.length > 0\">\n <div\n style=\"display: flex; align-items: center; gap: 7px; justify-content: center; margin-bottom: 16px;\"\n >\n <div class=\"separator\"></div>\n <div>{{ t(\"or\") }}</div>\n <div class=\"separator\"></div>\n </div>\n <div style=\"display: flex; flex-direction: column; gap: 10px\">\n <spiderly-button\n *ngFor=\"let provider of externalProviders\"\n (onClick)=\"loginWithExternalProvider(provider.code)\"\n [label]=\"provider.label || (provider.code | titlecase)\"\n [iconUrl]=\"iconFor(provider.code)\"\n [outlined]=\"true\"\n styleClass=\"w-full\"\n ></spiderly-button>\n </div>\n </div>\n</ng-container>\n" }]
|
|
2063
|
+
}], ctorParameters: () => [{ type: ConfigServiceBase }, { type: AuthServiceBase }, { type: ApiSecurityService }], propDecorators: { providerIcons: [{
|
|
2064
|
+
type: Input
|
|
2065
|
+
}] } });
|
|
2066
|
+
|
|
2067
|
+
class UserBase extends BaseEntity {
|
|
2068
|
+
static { this.typeName = 'UserBase'; }
|
|
2069
|
+
constructor({ id, email, } = {}) {
|
|
2070
|
+
super();
|
|
2071
|
+
this.id = id;
|
|
2072
|
+
this.email = email;
|
|
2073
|
+
}
|
|
2074
|
+
static { this.schema = {
|
|
2075
|
+
id: {
|
|
2076
|
+
type: 'number',
|
|
2077
|
+
},
|
|
2078
|
+
email: {
|
|
2079
|
+
type: 'string',
|
|
2080
|
+
},
|
|
2081
|
+
}; }
|
|
2082
|
+
}
|
|
2083
|
+
class AuthResult extends BaseEntity {
|
|
2084
|
+
static { this.typeName = 'AuthResult'; }
|
|
2085
|
+
constructor({ userId, email, accessToken, refreshToken, } = {}) {
|
|
2086
|
+
super();
|
|
2087
|
+
this.userId = userId;
|
|
2088
|
+
this.email = email;
|
|
2089
|
+
this.accessToken = accessToken;
|
|
2090
|
+
this.refreshToken = refreshToken;
|
|
2091
|
+
}
|
|
2092
|
+
static { this.schema = {
|
|
2093
|
+
userId: {
|
|
2094
|
+
type: 'number',
|
|
2095
|
+
},
|
|
2096
|
+
email: {
|
|
2097
|
+
type: 'string',
|
|
2098
|
+
},
|
|
2099
|
+
accessToken: {
|
|
2100
|
+
type: 'string',
|
|
2101
|
+
},
|
|
2102
|
+
refreshToken: {
|
|
2103
|
+
type: 'string',
|
|
2104
|
+
},
|
|
2105
|
+
}; }
|
|
2106
|
+
}
|
|
2107
|
+
class VerificationTokenRequest extends BaseEntity {
|
|
2108
|
+
static { this.typeName = 'VerificationTokenRequest'; }
|
|
2109
|
+
constructor({ verificationCode, browserId, email, } = {}) {
|
|
2110
|
+
super();
|
|
2111
|
+
this.verificationCode = verificationCode;
|
|
2112
|
+
this.browserId = browserId;
|
|
2113
|
+
this.email = email;
|
|
2114
|
+
}
|
|
2115
|
+
static { this.schema = {
|
|
2116
|
+
verificationCode: {
|
|
2117
|
+
type: 'string',
|
|
2118
|
+
},
|
|
2119
|
+
browserId: {
|
|
2120
|
+
type: 'string',
|
|
2121
|
+
},
|
|
2122
|
+
email: {
|
|
2123
|
+
type: 'string',
|
|
2124
|
+
},
|
|
2125
|
+
}; }
|
|
2126
|
+
}
|
|
2127
|
+
class ExternalProvider extends BaseEntity {
|
|
2128
|
+
static { this.typeName = 'ExternalProvider'; }
|
|
2129
|
+
constructor({ provider, idToken, browserId, } = {}) {
|
|
2130
|
+
super();
|
|
2131
|
+
this.provider = provider;
|
|
2132
|
+
this.idToken = idToken;
|
|
2133
|
+
this.browserId = browserId;
|
|
2134
|
+
}
|
|
2135
|
+
static { this.schema = {
|
|
2136
|
+
provider: {
|
|
2137
|
+
type: 'string',
|
|
2138
|
+
},
|
|
2139
|
+
idToken: {
|
|
2140
|
+
type: 'string',
|
|
2141
|
+
},
|
|
2142
|
+
browserId: {
|
|
2143
|
+
type: 'string',
|
|
2144
|
+
},
|
|
2145
|
+
}; }
|
|
2146
|
+
}
|
|
2147
|
+
class ExternalProviderPublic extends BaseEntity {
|
|
2148
|
+
static { this.typeName = 'ExternalProviderPublic'; }
|
|
2149
|
+
constructor({ code, authority, clientId, label, } = {}) {
|
|
2150
|
+
super();
|
|
2151
|
+
this.code = code;
|
|
2152
|
+
this.authority = authority;
|
|
2153
|
+
this.clientId = clientId;
|
|
2154
|
+
this.label = label;
|
|
2155
|
+
}
|
|
2156
|
+
static { this.schema = {
|
|
2157
|
+
code: {
|
|
2158
|
+
type: 'string',
|
|
2159
|
+
},
|
|
2160
|
+
authority: {
|
|
2161
|
+
type: 'string',
|
|
2162
|
+
},
|
|
2163
|
+
clientId: {
|
|
2164
|
+
type: 'string',
|
|
2165
|
+
},
|
|
2166
|
+
label: {
|
|
2167
|
+
type: 'string',
|
|
2168
|
+
},
|
|
2169
|
+
}; }
|
|
2170
|
+
}
|
|
2171
|
+
class UserRole extends BaseEntity {
|
|
2172
|
+
static { this.typeName = 'UserRole'; }
|
|
2173
|
+
constructor({ roleId, userId, } = {}) {
|
|
2174
|
+
super();
|
|
2175
|
+
this.roleId = roleId;
|
|
2176
|
+
this.userId = userId;
|
|
2177
|
+
}
|
|
2178
|
+
static { this.schema = {
|
|
2179
|
+
roleId: {
|
|
2180
|
+
type: 'number',
|
|
2181
|
+
},
|
|
2182
|
+
userId: {
|
|
2183
|
+
type: 'number',
|
|
1674
2184
|
},
|
|
1675
2185
|
}; }
|
|
1676
2186
|
}
|
|
@@ -2049,501 +2559,142 @@ class BaseFormComponent {
|
|
|
2049
2559
|
if (!this.saveBodyClass)
|
|
2050
2560
|
throw new SpiderlyError('You did not initialize saveBodyClass');
|
|
2051
2561
|
if (!this.mainUIFormClass)
|
|
2052
|
-
throw new SpiderlyError('You did not initialize mainUIFormClass');
|
|
2053
|
-
let saveBody = this.parentFormGroup.getRawValue();
|
|
2054
|
-
this.onBeforeSave(saveBody);
|
|
2055
|
-
const isValid = this.baseFormService.isControlValid(this.parentFormGroup);
|
|
2056
|
-
if (isValid) {
|
|
2057
|
-
this.parentFormGroup
|
|
2058
|
-
.saveObservableMethod(saveBody)
|
|
2059
|
-
.subscribe((res) => {
|
|
2060
|
-
this.messageService.successMessage(this.successfulSaveToastDescription);
|
|
2061
|
-
if (rerouteToParentSlugAfterSave) {
|
|
2062
|
-
this.rerouteToSavedObject(undefined);
|
|
2063
|
-
}
|
|
2064
|
-
else {
|
|
2065
|
-
saveBody = this.baseFormService.mapMainUIFormToSaveBody(this.mainUIFormClass, res);
|
|
2066
|
-
this.baseFormService.initFormGroup(this.parentFormGroup, this.saveBodyClass, saveBody);
|
|
2067
|
-
const saveBodyMainDTOKey = this.baseFormService.getSaveBodyMainDTOKey(this.saveBodyClass);
|
|
2068
|
-
const savedObjectId = saveBody[saveBodyMainDTOKey]?.id;
|
|
2069
|
-
this.rerouteToSavedObject(savedObjectId); // You always need to have id, because of id == 0 and version change
|
|
2070
|
-
}
|
|
2071
|
-
this.onAfterSave();
|
|
2072
|
-
});
|
|
2073
|
-
this.onAfterSaveRequest();
|
|
2074
|
-
}
|
|
2075
|
-
else {
|
|
2076
|
-
this.baseFormService.showInvalidFieldsMessage();
|
|
2077
|
-
}
|
|
2078
|
-
};
|
|
2079
|
-
/**
|
|
2080
|
-
* Hook that runs **before** form validation and the save request.
|
|
2081
|
-
* Use this to modify the save body or perform any pre-save logic (e.g., transforming data, setting computed fields).
|
|
2082
|
-
*
|
|
2083
|
-
* @param saveBody - The current save body built from the form's raw value. Mutate it directly to change what gets sent to the server.
|
|
2084
|
-
*
|
|
2085
|
-
* @example
|
|
2086
|
-
* ```ts
|
|
2087
|
-
* onBeforeSave = (saveBody?: ProductSaveBody) => {
|
|
2088
|
-
* saveBody.productDTO.fullName = saveBody.productDTO.firstName + ' ' + saveBody.productDTO.lastName;
|
|
2089
|
-
* };
|
|
2090
|
-
* ```
|
|
2091
|
-
*/
|
|
2092
|
-
this.onBeforeSave = (saveBody) => { };
|
|
2093
|
-
/**
|
|
2094
|
-
* Hook that runs **after** a successful save response is received.
|
|
2095
|
-
* Use this for post-save side effects (e.g., refreshing related data, showing additional notifications).
|
|
2096
|
-
*
|
|
2097
|
-
* @example
|
|
2098
|
-
* ```ts
|
|
2099
|
-
* onAfterSave = () => {
|
|
2100
|
-
* this.loadRelatedProducts();
|
|
2101
|
-
* };
|
|
2102
|
-
* ```
|
|
2103
|
-
*/
|
|
2104
|
-
this.onAfterSave = () => { };
|
|
2105
|
-
/**
|
|
2106
|
-
* Hook that runs immediately **after** the save HTTP request is sent, but **before** the response arrives.
|
|
2107
|
-
* Use this for side effects that should happen as soon as the request is dispatched (e.g., disabling UI elements, starting a loading indicator).
|
|
2108
|
-
*
|
|
2109
|
-
* @example
|
|
2110
|
-
* ```ts
|
|
2111
|
-
* onAfterSaveRequest = () => {
|
|
2112
|
-
* this.isSaving = true;
|
|
2113
|
-
* };
|
|
2114
|
-
* ```
|
|
2115
|
-
*/
|
|
2116
|
-
this.onAfterSaveRequest = () => { };
|
|
2117
|
-
}
|
|
2118
|
-
ngOnInit() { }
|
|
2119
|
-
/**
|
|
2120
|
-
* Handles navigation after a successful save.
|
|
2121
|
-
* Override this to customize the post-save navigation behavior.
|
|
2122
|
-
* By default, navigates to the parent URL when `rerouteId` is not provided, or to the saved object's URL otherwise.
|
|
2123
|
-
*
|
|
2124
|
-
* @param rerouteId - The ID of the saved object, used to build the target URL. When not provided, navigates to the parent URL.
|
|
2125
|
-
*
|
|
2126
|
-
* @example
|
|
2127
|
-
* ```ts
|
|
2128
|
-
* // Override to navigate to a custom route after save
|
|
2129
|
-
* override rerouteToSavedObject(rerouteId: number | string): void {
|
|
2130
|
-
* this.router.navigateByUrl(`/products/${rerouteId}/details`);
|
|
2131
|
-
* }
|
|
2132
|
-
* ```
|
|
2133
|
-
*/
|
|
2134
|
-
rerouteToSavedObject(rerouteId) {
|
|
2135
|
-
if (rerouteId == null) {
|
|
2136
|
-
const currentUrl = this.router.url;
|
|
2137
|
-
const parentUrl = getParentUrl(currentUrl);
|
|
2138
|
-
this.router.navigateByUrl(parentUrl);
|
|
2139
|
-
return;
|
|
2140
|
-
}
|
|
2141
|
-
const segments = this.router.url.split('/');
|
|
2142
|
-
segments[segments.length - 1] = rerouteId.toString();
|
|
2143
|
-
const newUrl = segments.join('/');
|
|
2144
|
-
this.router.navigateByUrl(newUrl);
|
|
2145
|
-
}
|
|
2146
|
-
//#endregion
|
|
2147
|
-
//#region Model List
|
|
2148
|
-
getFormArrayControlByIndex(formControlName, formArray, index, filter) {
|
|
2149
|
-
// if(formArray.controlNamesFromHtml.findIndex(x => x === formControlName) === -1)
|
|
2150
|
-
// formArray.controlNamesFromHtml.push(formControlName);
|
|
2151
|
-
let filteredFormGroups;
|
|
2152
|
-
if (filter) {
|
|
2153
|
-
filteredFormGroups = filter(formArray.controls);
|
|
2154
|
-
}
|
|
2155
|
-
else {
|
|
2156
|
-
return formArray.controls[index].controls[formControlName];
|
|
2157
|
-
}
|
|
2158
|
-
return filteredFormGroups[index]?.controls[formControlName]; // FT: Don't change this. It's always possible that change detection occurs before something.
|
|
2159
|
-
}
|
|
2160
|
-
getFormArrayControls(formControlName, formArray, filter) {
|
|
2161
|
-
// if(formArray.controlNamesFromHtml.findIndex(x => x === formControlName) === -1)
|
|
2162
|
-
// formArray.controlNamesFromHtml.push(formControlName);
|
|
2163
|
-
let filteredFormGroups;
|
|
2164
|
-
if (filter) {
|
|
2165
|
-
filteredFormGroups = filter(formArray.controls);
|
|
2166
|
-
}
|
|
2167
|
-
else {
|
|
2168
|
-
return formArray.controls.map((x) => x.controls[formControlName]);
|
|
2169
|
-
}
|
|
2170
|
-
return filteredFormGroups.map((x) => x.controls[formControlName]);
|
|
2171
|
-
}
|
|
2172
|
-
removeFormControlsFromTheFormArray(formArray, indexes) {
|
|
2173
|
-
// Sort indexes in descending order to avoid index shifts when removing controls
|
|
2174
|
-
const sortedIndexes = indexes.sort((a, b) => b - a);
|
|
2175
|
-
sortedIndexes.forEach((index) => {
|
|
2176
|
-
if (index >= 0 && index < formArray.length) {
|
|
2177
|
-
formArray.removeAt(index);
|
|
2178
|
-
}
|
|
2179
|
-
});
|
|
2180
|
-
}
|
|
2181
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: BaseFormComponent, deps: [{ token: i0.KeyValueDiffers }, { token: i1$2.HttpClient }, { token: SpiderlyMessageService }, { token: i0.ChangeDetectorRef }, { token: i3$2.Router }, { token: i3$2.ActivatedRoute }, { token: i1.TranslocoService }, { token: BaseFormService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2182
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: BaseFormComponent, isStandalone: false, selector: "base-form", ngImport: i0, template: '', isInline: true }); }
|
|
2183
|
-
}
|
|
2184
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: BaseFormComponent, decorators: [{
|
|
2185
|
-
type: Component,
|
|
2186
|
-
args: [{ selector: 'base-form', template: '', standalone: false }]
|
|
2187
|
-
}], ctorParameters: () => [{ type: i0.KeyValueDiffers }, { type: i1$2.HttpClient }, { type: SpiderlyMessageService }, { type: i0.ChangeDetectorRef }, { type: i3$2.Router }, { type: i3$2.ActivatedRoute }, { type: i1.TranslocoService }, { type: BaseFormService }] });
|
|
2188
|
-
|
|
2189
|
-
class GoogleButtonComponent {
|
|
2190
|
-
constructor() {
|
|
2191
|
-
this.loginWithGoogle = new EventEmitter();
|
|
2192
|
-
}
|
|
2193
|
-
handleGoogleLogin() {
|
|
2194
|
-
this.loginWithGoogle.emit(createFakeGoogleWrapper());
|
|
2195
|
-
}
|
|
2196
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: GoogleButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2197
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: GoogleButtonComponent, isStandalone: true, selector: "google-button", inputs: { label: "label" }, outputs: { loginWithGoogle: "loginWithGoogle" }, ngImport: i0, template: "<ng-container>\n <spiderly-button\n (onClick)=\"handleGoogleLogin()\"\n [label]=\"label\"\n [outlined]=\"true\"\n [style]=\"{ width: '100%' }\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"0.98em\"\n height=\"1em\"\n viewBox=\"0 0 256 262\"\n >\n <path\n fill=\"#4285f4\"\n d=\"M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027\"\n />\n <path\n fill=\"#34a853\"\n d=\"M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1\"\n />\n <path\n fill=\"#fbbc05\"\n d=\"M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z\"\n />\n <path\n fill=\"#eb4335\"\n d=\"M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251\"\n />\n </svg>\n </spiderly-button>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: SpiderlyButtonComponent, selector: "spiderly-button", inputs: ["type"] }] }); }
|
|
2198
|
-
}
|
|
2199
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: GoogleButtonComponent, decorators: [{
|
|
2200
|
-
type: Component,
|
|
2201
|
-
args: [{ selector: 'google-button', imports: [ButtonModule, SpiderlyButtonComponent], template: "<ng-container>\n <spiderly-button\n (onClick)=\"handleGoogleLogin()\"\n [label]=\"label\"\n [outlined]=\"true\"\n [style]=\"{ width: '100%' }\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"0.98em\"\n height=\"1em\"\n viewBox=\"0 0 256 262\"\n >\n <path\n fill=\"#4285f4\"\n d=\"M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027\"\n />\n <path\n fill=\"#34a853\"\n d=\"M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1\"\n />\n <path\n fill=\"#fbbc05\"\n d=\"M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z\"\n />\n <path\n fill=\"#eb4335\"\n d=\"M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251\"\n />\n </svg>\n </spiderly-button>\n</ng-container>\n" }]
|
|
2202
|
-
}], propDecorators: { label: [{
|
|
2203
|
-
type: Input
|
|
2204
|
-
}], loginWithGoogle: [{
|
|
2205
|
-
type: Output
|
|
2206
|
-
}] } });
|
|
2207
|
-
|
|
2208
|
-
class ConfigServiceBase {
|
|
2209
|
-
constructor() {
|
|
2210
|
-
this.production = false;
|
|
2211
|
-
this.frontendUrl = 'http://localhost:4200';
|
|
2212
|
-
this.showGoogleAuth = false;
|
|
2213
|
-
this.companyName = 'Company Name';
|
|
2214
|
-
this.primaryColor = '#111b2c';
|
|
2215
|
-
/* URLs */
|
|
2216
|
-
this.loginSlug = 'login';
|
|
2217
|
-
/* Local storage */
|
|
2218
|
-
this.accessTokenKey = 'access_token';
|
|
2219
|
-
this.refreshTokenKey = 'refresh_token';
|
|
2220
|
-
this.browserIdKey = 'browser_id';
|
|
2221
|
-
this.httpOptions = {};
|
|
2222
|
-
this.httpSkipSpinnerOptions = {
|
|
2223
|
-
headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
|
|
2224
|
-
params: new HttpParams().set('X-Skip-Spinner', 'true'),
|
|
2225
|
-
};
|
|
2226
|
-
this.logoPath = 'assets/images/logo/logo.svg';
|
|
2227
|
-
/* Pagination */
|
|
2228
|
-
this.defaultPageSize = 10;
|
|
2229
|
-
}
|
|
2230
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ConfigServiceBase, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2231
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ConfigServiceBase, providedIn: 'root' }); }
|
|
2232
|
-
}
|
|
2233
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ConfigServiceBase, decorators: [{
|
|
2234
|
-
type: Injectable,
|
|
2235
|
-
args: [{
|
|
2236
|
-
providedIn: 'root',
|
|
2237
|
-
}]
|
|
2238
|
-
}], ctorParameters: () => [] });
|
|
2239
|
-
|
|
2240
|
-
class InitCompanyAuthDialogDetails extends BaseEntity {
|
|
2241
|
-
constructor({ image, companyName, } = {}) {
|
|
2242
|
-
super();
|
|
2243
|
-
this.image = image;
|
|
2244
|
-
this.companyName = companyName;
|
|
2245
|
-
}
|
|
2246
|
-
static { this.typeName = 'InitCompanyAuthDialogDetails'; }
|
|
2247
|
-
}
|
|
2248
|
-
|
|
2249
|
-
class ApiSecurityService {
|
|
2250
|
-
constructor(http, config) {
|
|
2251
|
-
this.http = http;
|
|
2252
|
-
this.config = config;
|
|
2253
|
-
//#region Authentication
|
|
2254
|
-
this.login = (request) => {
|
|
2255
|
-
return this.http.post(`${this.config.apiUrl}/Security/Login`, request, this.config.httpOptions);
|
|
2256
|
-
};
|
|
2257
|
-
this.loginExternal = (externalProviderDTO) => {
|
|
2258
|
-
return this.http.post(`${this.config.apiUrl}/Security/LoginExternal`, externalProviderDTO, this.config.httpOptions);
|
|
2259
|
-
};
|
|
2260
|
-
this.sendLoginVerificationEmail = (loginDTO) => {
|
|
2261
|
-
return this.http.post(`${this.config.apiUrl}/Security/SendLoginVerificationEmail`, loginDTO, this.config.httpOptions);
|
|
2262
|
-
};
|
|
2263
|
-
this.loginWithCookies = (request) => {
|
|
2264
|
-
return this.http.post(`${this.config.apiUrl}/Security/LoginWithCookies`, request, this.config.httpOptions);
|
|
2265
|
-
};
|
|
2266
|
-
this.loginExternalWithCookies = (externalProviderDTO) => {
|
|
2267
|
-
return this.http.post(`${this.config.apiUrl}/Security/LoginExternalWithCookies`, externalProviderDTO, this.config.httpOptions);
|
|
2268
|
-
};
|
|
2269
|
-
this.logout = (browserId) => {
|
|
2270
|
-
return this.http.get(`${this.config.apiUrl}/Security/Logout?browserId=${browserId}`);
|
|
2271
|
-
};
|
|
2272
|
-
this.logoutWithCookies = (browserId) => {
|
|
2273
|
-
return this.http.get(`${this.config.apiUrl}/Security/LogoutWithCookies?browserId=${browserId}`);
|
|
2274
|
-
};
|
|
2275
|
-
this.refreshTokenWithHeaders = (request) => {
|
|
2276
|
-
return this.http.post(`${this.config.apiUrl}/Security/RefreshTokenWithHeaders`, request, this.config.httpOptions);
|
|
2277
|
-
};
|
|
2278
|
-
this.refreshTokenWithCookies = (browserId) => {
|
|
2279
|
-
return this.http.get(`${this.config.apiUrl}/Security/RefreshTokenWithCookies?browserId=${browserId}`);
|
|
2280
|
-
};
|
|
2281
|
-
//#endregion
|
|
2282
|
-
//#region User
|
|
2283
|
-
this.getCurrentUserBase = () => {
|
|
2284
|
-
return this.http.get(`${this.config.apiUrl}/Security/GetCurrentUserBase`, this.config.httpSkipSpinnerOptions);
|
|
2285
|
-
};
|
|
2286
|
-
this.getCurrentUserPermissionCodes = () => {
|
|
2287
|
-
return this.http.get(`${this.config.apiUrl}/Security/GetCurrentUserPermissionCodes`, this.config.httpSkipSpinnerOptions);
|
|
2288
|
-
};
|
|
2289
|
-
}
|
|
2290
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ApiSecurityService, deps: [{ token: i1$2.HttpClient }, { token: ConfigServiceBase }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2291
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ApiSecurityService, providedIn: 'root' }); }
|
|
2292
|
-
}
|
|
2293
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ApiSecurityService, decorators: [{
|
|
2294
|
-
type: Injectable,
|
|
2295
|
-
args: [{
|
|
2296
|
-
providedIn: 'root',
|
|
2297
|
-
}]
|
|
2298
|
-
}], ctorParameters: () => [{ type: i1$2.HttpClient }, { type: ConfigServiceBase }] });
|
|
2299
|
-
|
|
2300
|
-
class AuthServiceBase {
|
|
2301
|
-
constructor(router, http, externalAuthService, apiService, config, platformId) {
|
|
2302
|
-
this.router = router;
|
|
2303
|
-
this.http = http;
|
|
2304
|
-
this.externalAuthService = externalAuthService;
|
|
2305
|
-
this.apiService = apiService;
|
|
2306
|
-
this.config = config;
|
|
2307
|
-
this.platformId = platformId;
|
|
2308
|
-
this.apiUrl = this.config.apiUrl;
|
|
2309
|
-
this._currentUserPermissionCodes = new BehaviorSubject(undefined);
|
|
2310
|
-
this.currentUserPermissionCodes$ = this._currentUserPermissionCodes.asObservable();
|
|
2311
|
-
this._user = new BehaviorSubject(undefined);
|
|
2312
|
-
this.user$ = this._user.asObservable();
|
|
2313
|
-
// Google auth
|
|
2314
|
-
this.authChangeSub = new Subject();
|
|
2315
|
-
this.extAuthChangeSub = new Subject();
|
|
2316
|
-
this.authChanged = this.authChangeSub.asObservable();
|
|
2317
|
-
this.extAuthChanged = this.extAuthChangeSub.asObservable();
|
|
2318
|
-
this.storageEventListener = (event) => {
|
|
2319
|
-
if (event.storageArea === localStorage) {
|
|
2320
|
-
if (event.key === 'logout-event') {
|
|
2321
|
-
this.stopTokenTimer();
|
|
2322
|
-
this._user.next(null);
|
|
2323
|
-
this._currentUserPermissionCodes.next(null);
|
|
2324
|
-
}
|
|
2325
|
-
if (event.key === 'login-event') {
|
|
2326
|
-
this.stopTokenTimer();
|
|
2327
|
-
this.apiService.getCurrentUserBase().subscribe((user) => {
|
|
2328
|
-
this._user.next({
|
|
2329
|
-
id: user.id,
|
|
2330
|
-
email: user.email,
|
|
2331
|
-
});
|
|
2332
|
-
this.setCurrentUserPermissionCodes().subscribe();
|
|
2333
|
-
});
|
|
2334
|
-
}
|
|
2562
|
+
throw new SpiderlyError('You did not initialize mainUIFormClass');
|
|
2563
|
+
let saveBody = this.parentFormGroup.getRawValue();
|
|
2564
|
+
this.onBeforeSave(saveBody);
|
|
2565
|
+
const isValid = this.baseFormService.isControlValid(this.parentFormGroup);
|
|
2566
|
+
if (isValid) {
|
|
2567
|
+
this.parentFormGroup
|
|
2568
|
+
.saveObservableMethod(saveBody)
|
|
2569
|
+
.subscribe((res) => {
|
|
2570
|
+
this.messageService.successMessage(this.successfulSaveToastDescription);
|
|
2571
|
+
if (rerouteToParentSlugAfterSave) {
|
|
2572
|
+
this.rerouteToSavedObject(undefined);
|
|
2573
|
+
}
|
|
2574
|
+
else {
|
|
2575
|
+
saveBody = this.baseFormService.mapMainUIFormToSaveBody(this.mainUIFormClass, res);
|
|
2576
|
+
this.baseFormService.initFormGroup(this.parentFormGroup, this.saveBodyClass, saveBody);
|
|
2577
|
+
const saveBodyMainDTOKey = this.baseFormService.getSaveBodyMainDTOKey(this.saveBodyClass);
|
|
2578
|
+
const savedObjectId = saveBody[saveBodyMainDTOKey]?.id;
|
|
2579
|
+
this.rerouteToSavedObject(savedObjectId); // You always need to have id, because of id == 0 and version change
|
|
2580
|
+
}
|
|
2581
|
+
this.onAfterSave();
|
|
2582
|
+
});
|
|
2583
|
+
this.onAfterSaveRequest();
|
|
2584
|
+
}
|
|
2585
|
+
else {
|
|
2586
|
+
this.baseFormService.showInvalidFieldsMessage();
|
|
2335
2587
|
}
|
|
2336
2588
|
};
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
const browserId = this.getBrowserId();
|
|
2376
|
-
body.browserId = browserId;
|
|
2377
|
-
const loginResultObservable = this.http.post(`${this.apiUrl}/Security/Login`, body);
|
|
2378
|
-
return this.handleLoginResult(loginResultObservable);
|
|
2379
|
-
}
|
|
2380
|
-
loginExternal(body) {
|
|
2381
|
-
const browserId = this.getBrowserId();
|
|
2382
|
-
body.browserId = browserId;
|
|
2383
|
-
const loginResultObservable = this.http.post(`${this.apiUrl}/Security/LoginExternal`, body);
|
|
2384
|
-
return this.handleLoginResult(loginResultObservable);
|
|
2385
|
-
}
|
|
2386
|
-
handleLoginResult(loginResultObservable) {
|
|
2387
|
-
return loginResultObservable.pipe(map$1(async (loginResult) => {
|
|
2388
|
-
this.setLocalStorage(loginResult);
|
|
2389
|
-
this._user.next({
|
|
2390
|
-
id: loginResult.userId,
|
|
2391
|
-
email: loginResult.email,
|
|
2392
|
-
});
|
|
2393
|
-
this.startTokenTimer();
|
|
2394
|
-
this.setCurrentUserPermissionCodes().subscribe();
|
|
2395
|
-
return loginResult;
|
|
2396
|
-
}));
|
|
2397
|
-
}
|
|
2398
|
-
logout() {
|
|
2399
|
-
const browserId = this.getBrowserId();
|
|
2400
|
-
this.http
|
|
2401
|
-
.get(`${this.apiUrl}/Security/Logout?browserId=${browserId}`)
|
|
2402
|
-
.pipe(finalize(() => {
|
|
2403
|
-
this.clearLocalStorage();
|
|
2404
|
-
this._user.next(null);
|
|
2405
|
-
this.onAfterLogout();
|
|
2406
|
-
this.stopTokenTimer();
|
|
2407
|
-
}))
|
|
2408
|
-
.subscribe();
|
|
2589
|
+
/**
|
|
2590
|
+
* Hook that runs **before** form validation and the save request.
|
|
2591
|
+
* Use this to modify the save body or perform any pre-save logic (e.g., transforming data, setting computed fields).
|
|
2592
|
+
*
|
|
2593
|
+
* @param saveBody - The current save body built from the form's raw value. Mutate it directly to change what gets sent to the server.
|
|
2594
|
+
*
|
|
2595
|
+
* @example
|
|
2596
|
+
* ```ts
|
|
2597
|
+
* onBeforeSave = (saveBody?: ProductSaveBody) => {
|
|
2598
|
+
* saveBody.productDTO.fullName = saveBody.productDTO.firstName + ' ' + saveBody.productDTO.lastName;
|
|
2599
|
+
* };
|
|
2600
|
+
* ```
|
|
2601
|
+
*/
|
|
2602
|
+
this.onBeforeSave = (saveBody) => { };
|
|
2603
|
+
/**
|
|
2604
|
+
* Hook that runs **after** a successful save response is received.
|
|
2605
|
+
* Use this for post-save side effects (e.g., refreshing related data, showing additional notifications).
|
|
2606
|
+
*
|
|
2607
|
+
* @example
|
|
2608
|
+
* ```ts
|
|
2609
|
+
* onAfterSave = () => {
|
|
2610
|
+
* this.loadRelatedProducts();
|
|
2611
|
+
* };
|
|
2612
|
+
* ```
|
|
2613
|
+
*/
|
|
2614
|
+
this.onAfterSave = () => { };
|
|
2615
|
+
/**
|
|
2616
|
+
* Hook that runs immediately **after** the save HTTP request is sent, but **before** the response arrives.
|
|
2617
|
+
* Use this for side effects that should happen as soon as the request is dispatched (e.g., disabling UI elements, starting a loading indicator).
|
|
2618
|
+
*
|
|
2619
|
+
* @example
|
|
2620
|
+
* ```ts
|
|
2621
|
+
* onAfterSaveRequest = () => {
|
|
2622
|
+
* this.isSaving = true;
|
|
2623
|
+
* };
|
|
2624
|
+
* ```
|
|
2625
|
+
*/
|
|
2626
|
+
this.onAfterSaveRequest = () => { };
|
|
2409
2627
|
}
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2628
|
+
ngOnInit() { }
|
|
2629
|
+
/**
|
|
2630
|
+
* Handles navigation after a successful save.
|
|
2631
|
+
* Override this to customize the post-save navigation behavior.
|
|
2632
|
+
* By default, navigates to the parent URL when `rerouteId` is not provided, or to the saved object's URL otherwise.
|
|
2633
|
+
*
|
|
2634
|
+
* @param rerouteId - The ID of the saved object, used to build the target URL. When not provided, navigates to the parent URL.
|
|
2635
|
+
*
|
|
2636
|
+
* @example
|
|
2637
|
+
* ```ts
|
|
2638
|
+
* // Override to navigate to a custom route after save
|
|
2639
|
+
* override rerouteToSavedObject(rerouteId: number | string): void {
|
|
2640
|
+
* this.router.navigateByUrl(`/products/${rerouteId}/details`);
|
|
2641
|
+
* }
|
|
2642
|
+
* ```
|
|
2643
|
+
*/
|
|
2644
|
+
rerouteToSavedObject(rerouteId) {
|
|
2645
|
+
if (rerouteId == null) {
|
|
2646
|
+
const currentUrl = this.router.url;
|
|
2647
|
+
const parentUrl = getParentUrl(currentUrl);
|
|
2648
|
+
this.router.navigateByUrl(parentUrl);
|
|
2649
|
+
return;
|
|
2415
2650
|
}
|
|
2416
|
-
const
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
return this.http
|
|
2421
|
-
.post(`${this.apiUrl}/Security/RefreshTokenWithHeaders`, body, this.config.httpSkipSpinnerOptions)
|
|
2422
|
-
.pipe(map$1((loginResult) => {
|
|
2423
|
-
this._user.next({
|
|
2424
|
-
id: loginResult.userId,
|
|
2425
|
-
email: loginResult.email,
|
|
2426
|
-
});
|
|
2427
|
-
this.setLocalStorage(loginResult);
|
|
2428
|
-
this.startTokenTimer();
|
|
2429
|
-
this.onAfterRefreshToken();
|
|
2430
|
-
return loginResult;
|
|
2431
|
-
}));
|
|
2432
|
-
}
|
|
2433
|
-
setLocalStorage(loginResult) {
|
|
2434
|
-
localStorage.setItem(this.config.accessTokenKey, loginResult.accessToken);
|
|
2435
|
-
localStorage.setItem(this.config.refreshTokenKey, loginResult.refreshToken);
|
|
2436
|
-
localStorage.setItem('login-event', 'login' + Math.random());
|
|
2437
|
-
}
|
|
2438
|
-
clearLocalStorage() {
|
|
2439
|
-
localStorage.removeItem(this.config.accessTokenKey);
|
|
2440
|
-
localStorage.removeItem(this.config.refreshTokenKey);
|
|
2441
|
-
localStorage.setItem('logout-event', 'logout' + Math.random());
|
|
2651
|
+
const segments = this.router.url.split('/');
|
|
2652
|
+
segments[segments.length - 1] = rerouteId.toString();
|
|
2653
|
+
const newUrl = segments.join('/');
|
|
2654
|
+
this.router.navigateByUrl(newUrl);
|
|
2442
2655
|
}
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2656
|
+
//#endregion
|
|
2657
|
+
//#region Model List
|
|
2658
|
+
getFormArrayControlByIndex(formControlName, formArray, index, filter) {
|
|
2659
|
+
// if(formArray.controlNamesFromHtml.findIndex(x => x === formControlName) === -1)
|
|
2660
|
+
// formArray.controlNamesFromHtml.push(formControlName);
|
|
2661
|
+
let filteredFormGroups;
|
|
2662
|
+
if (filter) {
|
|
2663
|
+
filteredFormGroups = filter(formArray.controls);
|
|
2448
2664
|
}
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
isAccessTokenExpired() {
|
|
2452
|
-
const expired = this.getTokenRemainingTime() < 5000;
|
|
2453
|
-
return expired;
|
|
2454
|
-
}
|
|
2455
|
-
getTokenRemainingTime() {
|
|
2456
|
-
const accessToken = this.getAccessToken();
|
|
2457
|
-
if (!accessToken) {
|
|
2458
|
-
return 0;
|
|
2665
|
+
else {
|
|
2666
|
+
return formArray.controls[index].controls[formControlName];
|
|
2459
2667
|
}
|
|
2460
|
-
|
|
2461
|
-
const expires = new Date(jwtToken.exp * 1000);
|
|
2462
|
-
return expires.getTime() - Date.now();
|
|
2668
|
+
return filteredFormGroups[index]?.controls[formControlName]; // FT: Don't change this. It's always possible that change detection occurs before something.
|
|
2463
2669
|
}
|
|
2464
|
-
|
|
2465
|
-
if
|
|
2466
|
-
|
|
2670
|
+
getFormArrayControls(formControlName, formArray, filter) {
|
|
2671
|
+
// if(formArray.controlNamesFromHtml.findIndex(x => x === formControlName) === -1)
|
|
2672
|
+
// formArray.controlNamesFromHtml.push(formControlName);
|
|
2673
|
+
let filteredFormGroups;
|
|
2674
|
+
if (filter) {
|
|
2675
|
+
filteredFormGroups = filter(formArray.controls);
|
|
2467
2676
|
}
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
startTokenTimer() {
|
|
2471
|
-
const timeout = this.getTokenRemainingTime();
|
|
2472
|
-
this.timer = of(true)
|
|
2473
|
-
.pipe(delay(timeout), tap({
|
|
2474
|
-
next: () => this.refreshToken().subscribe(),
|
|
2475
|
-
}))
|
|
2476
|
-
.subscribe();
|
|
2477
|
-
}
|
|
2478
|
-
stopTokenTimer() {
|
|
2479
|
-
this.timer?.unsubscribe();
|
|
2480
|
-
}
|
|
2481
|
-
navigateToDashboard() {
|
|
2482
|
-
this.router.navigate(['/']);
|
|
2483
|
-
}
|
|
2484
|
-
setCurrentUserPermissionCodes() {
|
|
2485
|
-
return this.apiService.getCurrentUserPermissionCodes().pipe(map$1((permissionCodes) => {
|
|
2486
|
-
this._currentUserPermissionCodes.next(permissionCodes);
|
|
2487
|
-
return permissionCodes;
|
|
2488
|
-
}));
|
|
2489
|
-
}
|
|
2490
|
-
ngOnDestroy() {
|
|
2491
|
-
if (isPlatformBrowser(this.platformId)) {
|
|
2492
|
-
window.removeEventListener('storage', this.storageEventListener);
|
|
2677
|
+
else {
|
|
2678
|
+
return formArray.controls.map((x) => x.controls[formControlName]);
|
|
2493
2679
|
}
|
|
2494
|
-
|
|
2495
|
-
}
|
|
2496
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: AuthServiceBase, deps: [{ token: i3$2.Router }, { token: i1$2.HttpClient }, { token: i3$3.SocialAuthService }, { token: ApiSecurityService }, { token: ConfigServiceBase }, { token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2497
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: AuthServiceBase, providedIn: 'root' }); }
|
|
2498
|
-
}
|
|
2499
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: AuthServiceBase, decorators: [{
|
|
2500
|
-
type: Injectable,
|
|
2501
|
-
args: [{
|
|
2502
|
-
providedIn: 'root',
|
|
2503
|
-
}]
|
|
2504
|
-
}], ctorParameters: () => [{ type: i3$2.Router }, { type: i1$2.HttpClient }, { type: i3$3.SocialAuthService }, { type: ApiSecurityService }, { type: ConfigServiceBase }, { type: Object, decorators: [{
|
|
2505
|
-
type: Inject,
|
|
2506
|
-
args: [PLATFORM_ID]
|
|
2507
|
-
}] }] });
|
|
2508
|
-
|
|
2509
|
-
class AuthComponent {
|
|
2510
|
-
constructor(config, authService) {
|
|
2511
|
-
this.config = config;
|
|
2512
|
-
this.authService = authService;
|
|
2513
|
-
this.initCompanyAuthDialogDetailsSubscription = null;
|
|
2514
|
-
this.onCompanyNameChange = new EventEmitter();
|
|
2515
|
-
}
|
|
2516
|
-
ngOnInit() {
|
|
2517
|
-
this.initCompanyDetails();
|
|
2680
|
+
return filteredFormGroups.map((x) => x.controls[formControlName]);
|
|
2518
2681
|
}
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
if (
|
|
2524
|
-
|
|
2525
|
-
this.companyName = initCompanyAuthDialogDetails.companyName;
|
|
2526
|
-
this.onCompanyNameChange.next(this.companyName);
|
|
2682
|
+
removeFormControlsFromTheFormArray(formArray, indexes) {
|
|
2683
|
+
// Sort indexes in descending order to avoid index shifts when removing controls
|
|
2684
|
+
const sortedIndexes = indexes.sort((a, b) => b - a);
|
|
2685
|
+
sortedIndexes.forEach((index) => {
|
|
2686
|
+
if (index >= 0 && index < formArray.length) {
|
|
2687
|
+
formArray.removeAt(index);
|
|
2527
2688
|
}
|
|
2528
2689
|
});
|
|
2529
2690
|
}
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
}
|
|
2533
|
-
ngOnDestroy() {
|
|
2534
|
-
if (this.initCompanyAuthDialogDetailsSubscription) {
|
|
2535
|
-
this.initCompanyAuthDialogDetailsSubscription.unsubscribe();
|
|
2536
|
-
}
|
|
2537
|
-
}
|
|
2538
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: AuthComponent, deps: [{ token: ConfigServiceBase }, { token: AuthServiceBase }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2539
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: AuthComponent, isStandalone: true, selector: "auth", outputs: { onCompanyNameChange: "onCompanyNameChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex min-h-screen overflow-hidden p-5\">\n <div class=\"flex flex-col w-full\">\n <div\n class=\"w-full sm:w-120\"\n style=\"\n margin: auto;\n border-radius: 50px;\n padding: 0.3rem;\n background: linear-gradient(\n 180deg,\n var(--p-primary-color) 10%,\n rgba(33, 150, 243, 0) 30%\n );\n \"\n >\n <div\n class=\"surface-card py-12 px-8 sm:px-12\"\n style=\"border-radius: 45px\"\n >\n <div class=\"flex justify-center\" style=\"margin-bottom: 38px\">\n <img\n *ngIf=\"image != null\"\n [src]=\"image\"\n alt=\"{{ companyName }} Logo\"\n title=\"{{ companyName }} Logo\"\n class=\"max-h-15\"\n />\n <i\n *ngIf=\"image == null\"\n class=\"pi pi-spin pi-spinner primary-color\"\n style=\"font-size: 2rem\"\n ></i>\n </div>\n\n <ng-content></ng-content>\n\n <div *ngIf=\"config.showGoogleAuth\">\n <div\n style=\"\n display: flex;\n align-items: center;\n gap: 7px;\n justify-content: center;\n margin-bottom: 16px;\n \"\n >\n <div class=\"separator\"></div>\n <div>{{ t(\"or\") }}</div>\n <div class=\"separator\"></div>\n </div>\n <div>\n <!-- https://code-maze.com/how-to-sign-in-with-google-angular-aspnet-webapi/ -->\n <google-button\n (loginWithGoogle)=\"onGoogleSignIn($event)\"\n [label]=\"t('ContinueWithGoogle')\"\n ></google-button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: GoogleButtonComponent, selector: "google-button", inputs: ["label"], outputs: ["loginWithGoogle"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] }); }
|
|
2691
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: BaseFormComponent, deps: [{ token: i0.KeyValueDiffers }, { token: i1$2.HttpClient }, { token: SpiderlyMessageService }, { token: i0.ChangeDetectorRef }, { token: i3$2.Router }, { token: i3$2.ActivatedRoute }, { token: i1.TranslocoService }, { token: BaseFormService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2692
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: BaseFormComponent, isStandalone: false, selector: "base-form", ngImport: i0, template: '', isInline: true }); }
|
|
2540
2693
|
}
|
|
2541
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type:
|
|
2694
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: BaseFormComponent, decorators: [{
|
|
2542
2695
|
type: Component,
|
|
2543
|
-
args: [{ selector: '
|
|
2544
|
-
}], ctorParameters: () => [{ type:
|
|
2545
|
-
type: Output
|
|
2546
|
-
}] } });
|
|
2696
|
+
args: [{ selector: 'base-form', template: '', standalone: false }]
|
|
2697
|
+
}], ctorParameters: () => [{ type: i0.KeyValueDiffers }, { type: i1$2.HttpClient }, { type: SpiderlyMessageService }, { type: i0.ChangeDetectorRef }, { type: i3$2.Router }, { type: i3$2.ActivatedRoute }, { type: i1.TranslocoService }, { type: BaseFormService }] });
|
|
2547
2698
|
|
|
2548
2699
|
class PanelBodyComponent {
|
|
2549
2700
|
constructor() {
|
|
@@ -2574,6 +2725,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
|
|
|
2574
2725
|
class PanelHeaderComponent {
|
|
2575
2726
|
constructor(translocoService) {
|
|
2576
2727
|
this.translocoService = translocoService;
|
|
2728
|
+
/** Whether the header icon is shown. Defaults to `true`. */
|
|
2577
2729
|
this.showIcon = true;
|
|
2578
2730
|
}
|
|
2579
2731
|
ngOnInit() {
|
|
@@ -2624,8 +2776,11 @@ class SpiderlyPanelComponent {
|
|
|
2624
2776
|
this.toggleable = false;
|
|
2625
2777
|
this.toggler = 'icon';
|
|
2626
2778
|
this.collapsed = false;
|
|
2779
|
+
/** Whether the CRUD context-menu icon is shown. Defaults to `true`. */
|
|
2627
2780
|
this.showCrudMenu = true;
|
|
2781
|
+
/** Whether a remove/delete icon is shown. Defaults to `false`. */
|
|
2628
2782
|
this.showRemoveIcon = false;
|
|
2783
|
+
/** Whether the panel header is rendered. Defaults to `true`. */
|
|
2629
2784
|
this.showPanelHeader = true;
|
|
2630
2785
|
this.onMenuIconClick = new EventEmitter();
|
|
2631
2786
|
this.onRemoveIconClick = new EventEmitter();
|
|
@@ -2793,8 +2948,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
|
|
|
2793
2948
|
type: Input
|
|
2794
2949
|
}] } });
|
|
2795
2950
|
|
|
2796
|
-
class
|
|
2797
|
-
constructor(differs, http, messageService, changeDetectorRef, router, route, translocoService, baseFormService, authService
|
|
2951
|
+
class SpiderlyLoginComponent extends BaseFormComponent {
|
|
2952
|
+
constructor(differs, http, messageService, changeDetectorRef, router, route, translocoService, baseFormService, authService) {
|
|
2798
2953
|
super(differs, http, messageService, changeDetectorRef, router, route, translocoService, baseFormService);
|
|
2799
2954
|
this.differs = differs;
|
|
2800
2955
|
this.http = http;
|
|
@@ -2805,21 +2960,31 @@ class LoginComponent extends BaseFormComponent {
|
|
|
2805
2960
|
this.translocoService = translocoService;
|
|
2806
2961
|
this.baseFormService = baseFormService;
|
|
2807
2962
|
this.authService = authService;
|
|
2808
|
-
this.config = config;
|
|
2809
2963
|
this.loginFormGroup = new SpiderlyFormGroup({});
|
|
2810
2964
|
this.showEmailSentDialog = false;
|
|
2965
|
+
/** Per-code provider icon overrides, forwarded to <spiderly-external-login>. */
|
|
2966
|
+
this.providerIcons = {};
|
|
2811
2967
|
}
|
|
2812
2968
|
ngOnInit() {
|
|
2813
2969
|
this.initLoginFormGroup(new Login({}));
|
|
2970
|
+
this.showExternalAuthErrorIfPresent();
|
|
2971
|
+
}
|
|
2972
|
+
// Surface a friendly message when the server-side external login bounced back with an error (captured from
|
|
2973
|
+
// the URL at bootstrap by AuthServiceBase). "expired" = the user lingered on the provider's account picker.
|
|
2974
|
+
showExternalAuthErrorIfPresent() {
|
|
2975
|
+
const code = this.authService.externalAuthErrorCode;
|
|
2976
|
+
if (!code) {
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2979
|
+
this.authService.externalAuthErrorCode = null; // show once
|
|
2980
|
+
const messageKey = code === 'expired' ? 'ExternalLoginExpiredDetails' : 'ExternalLoginFailedDetails';
|
|
2981
|
+
this.messageService.warningMessage(this.translocoService.translate(messageKey));
|
|
2814
2982
|
}
|
|
2815
2983
|
initLoginFormGroup(model) {
|
|
2816
2984
|
this.baseFormService.initFormGroup(this.loginFormGroup, Login, model, [
|
|
2817
2985
|
'email',
|
|
2818
2986
|
]);
|
|
2819
2987
|
}
|
|
2820
|
-
companyNameChange(companyName) {
|
|
2821
|
-
this.companyName = companyName;
|
|
2822
|
-
}
|
|
2823
2988
|
sendLoginVerificationEmail() {
|
|
2824
2989
|
let isFormGroupValid = this.baseFormService.isControlValid(this.loginFormGroup);
|
|
2825
2990
|
if (isFormGroupValid == false) {
|
|
@@ -2835,20 +3000,23 @@ class LoginComponent extends BaseFormComponent {
|
|
|
2835
3000
|
this.showEmailSentDialog = true;
|
|
2836
3001
|
});
|
|
2837
3002
|
}
|
|
2838
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type:
|
|
2839
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.13", type:
|
|
3003
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyLoginComponent, deps: [{ token: i0.KeyValueDiffers }, { token: i1$2.HttpClient }, { token: SpiderlyMessageService }, { token: i0.ChangeDetectorRef }, { token: i3$2.Router }, { token: i3$2.ActivatedRoute }, { token: i1.TranslocoService }, { token: BaseFormService }, { token: AuthServiceBase }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3004
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.13", type: SpiderlyLoginComponent, isStandalone: true, selector: "spiderly-login", inputs: { providerIcons: "providerIcons" }, usesInheritance: true, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n @if (loginFormGroup != null) {\n @if (showEmailSentDialog == false) {\n <spiderly-auth-card>\n <form [formGroup]=\"loginFormGroup\" style=\"margin-bottom: 16px\">\n <div>\n <spiderly-textbox\n [control]=\"loginFormGroup.getControl('email')\"\n ></spiderly-textbox>\n </div>\n\n <div class=\"mt-4 mb-6\">\n <div class=\"text-center\" style=\"font-size: smaller\">\n {{ t(\"AgreementsOnRegister\") }}\n <b\n routerLink=\"/user-agreement\"\n class=\"primary-color cursor-pointer\"\n >{{ t(\"UserAgreement\") }}</b\n >\n {{ t(\"and\") }}\n <b\n routerLink=\"/privacy-policy\"\n class=\"primary-color cursor-pointer\"\n >{{ t(\"PrivacyPolicy\") }}</b\n >.\n </div>\n </div>\n\n <div style=\"display: flex; flex-direction: column; gap: 16px\">\n <spiderly-button\n [label]=\"t('Login')\"\n (onClick)=\"sendLoginVerificationEmail()\"\n [outlined]=\"true\"\n [style]=\"{ width: '100%' }\"\n type=\"submit\"\n ></spiderly-button>\n </div>\n </form>\n\n <spiderly-external-login\n [providerIcons]=\"providerIcons\"\n ></spiderly-external-login>\n </spiderly-auth-card>\n } @else {\n <login-verification\n [email]=\"loginFormGroup.controls.email.getRawValue()\"\n ></login-verification>\n }\n }\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: AuthCardComponent, selector: "spiderly-auth-card" }, { kind: "component", type: ExternalLoginComponent, selector: "spiderly-external-login", inputs: ["providerIcons"] }, { kind: "ngmodule", type: SpiderlyControlsModule }, { kind: "component", type: SpiderlyTextboxComponent, selector: "spiderly-textbox", inputs: ["showButton", "buttonIcon"], outputs: ["onButtonClick"] }, { kind: "component", type: SpiderlyButtonComponent, selector: "spiderly-button", inputs: ["type"] }, { kind: "component", type: LoginVerificationComponent, selector: "login-verification", inputs: ["email", "userId"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] }); }
|
|
2840
3005
|
}
|
|
2841
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type:
|
|
3006
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyLoginComponent, decorators: [{
|
|
2842
3007
|
type: Component,
|
|
2843
|
-
args: [{ selector: '
|
|
3008
|
+
args: [{ selector: 'spiderly-login', imports: [
|
|
2844
3009
|
CommonModule,
|
|
2845
3010
|
ReactiveFormsModule,
|
|
2846
|
-
|
|
3011
|
+
AuthCardComponent,
|
|
3012
|
+
ExternalLoginComponent,
|
|
2847
3013
|
SpiderlyControlsModule,
|
|
2848
3014
|
LoginVerificationComponent,
|
|
2849
3015
|
TranslocoDirective,
|
|
2850
|
-
], template: "<ng-container *transloco=\"let t\">\n @if (loginFormGroup != null) {\n @if (showEmailSentDialog == false) {\n <auth
|
|
2851
|
-
}], ctorParameters: () => [{ type: i0.KeyValueDiffers }, { type: i1$2.HttpClient }, { type: SpiderlyMessageService }, { type: i0.ChangeDetectorRef }, { type: i3$2.Router }, { type: i3$2.ActivatedRoute }, { type: i1.TranslocoService }, { type: BaseFormService }, { type: AuthServiceBase }, {
|
|
3016
|
+
], template: "<ng-container *transloco=\"let t\">\n @if (loginFormGroup != null) {\n @if (showEmailSentDialog == false) {\n <spiderly-auth-card>\n <form [formGroup]=\"loginFormGroup\" style=\"margin-bottom: 16px\">\n <div>\n <spiderly-textbox\n [control]=\"loginFormGroup.getControl('email')\"\n ></spiderly-textbox>\n </div>\n\n <div class=\"mt-4 mb-6\">\n <div class=\"text-center\" style=\"font-size: smaller\">\n {{ t(\"AgreementsOnRegister\") }}\n <b\n routerLink=\"/user-agreement\"\n class=\"primary-color cursor-pointer\"\n >{{ t(\"UserAgreement\") }}</b\n >\n {{ t(\"and\") }}\n <b\n routerLink=\"/privacy-policy\"\n class=\"primary-color cursor-pointer\"\n >{{ t(\"PrivacyPolicy\") }}</b\n >.\n </div>\n </div>\n\n <div style=\"display: flex; flex-direction: column; gap: 16px\">\n <spiderly-button\n [label]=\"t('Login')\"\n (onClick)=\"sendLoginVerificationEmail()\"\n [outlined]=\"true\"\n [style]=\"{ width: '100%' }\"\n type=\"submit\"\n ></spiderly-button>\n </div>\n </form>\n\n <spiderly-external-login\n [providerIcons]=\"providerIcons\"\n ></spiderly-external-login>\n </spiderly-auth-card>\n } @else {\n <login-verification\n [email]=\"loginFormGroup.controls.email.getRawValue()\"\n ></login-verification>\n }\n }\n</ng-container>\n" }]
|
|
3017
|
+
}], ctorParameters: () => [{ type: i0.KeyValueDiffers }, { type: i1$2.HttpClient }, { type: SpiderlyMessageService }, { type: i0.ChangeDetectorRef }, { type: i3$2.Router }, { type: i3$2.ActivatedRoute }, { type: i1.TranslocoService }, { type: BaseFormService }, { type: AuthServiceBase }], propDecorators: { providerIcons: [{
|
|
3018
|
+
type: Input
|
|
3019
|
+
}] } });
|
|
2852
3020
|
|
|
2853
3021
|
class CardSkeletonComponent {
|
|
2854
3022
|
constructor() {
|
|
@@ -2893,6 +3061,7 @@ class IndexCardComponent {
|
|
|
2893
3061
|
constructor(formBuilder) {
|
|
2894
3062
|
this.formBuilder = formBuilder;
|
|
2895
3063
|
this.header = '';
|
|
3064
|
+
/** Whether the CRUD context-menu icon is shown. Defaults to `true`. */
|
|
2896
3065
|
this.showCrudMenu = true;
|
|
2897
3066
|
this.onMenuIconClick = new EventEmitter();
|
|
2898
3067
|
this.onRemoveIconClick = new EventEmitter();
|
|
@@ -2934,6 +3103,7 @@ class InfoCardComponent {
|
|
|
2934
3103
|
constructor(formBuilder) {
|
|
2935
3104
|
this.formBuilder = formBuilder;
|
|
2936
3105
|
this.header = '';
|
|
3106
|
+
/** Whether the small icon variant is shown. Defaults to `true`. */
|
|
2937
3107
|
this.showSmallIcon = true;
|
|
2938
3108
|
this.icon = 'pi pi-info-circle';
|
|
2939
3109
|
this.textColor = '';
|
|
@@ -2976,22 +3146,16 @@ class LayoutServiceBase {
|
|
|
2976
3146
|
inputStyle: 'outlined',
|
|
2977
3147
|
menuMode: 'static',
|
|
2978
3148
|
colorScheme: 'light',
|
|
2979
|
-
theme: 'lara-light-indigo',
|
|
2980
|
-
scale: 14,
|
|
2981
|
-
color: `var(--p-primary-color)`,
|
|
2982
3149
|
};
|
|
2983
3150
|
this.state = {
|
|
2984
3151
|
staticMenuDesktopInactive: false,
|
|
2985
3152
|
overlayMenuActive: false,
|
|
2986
3153
|
profileSidebarVisible: false,
|
|
2987
3154
|
profileDropdownSidebarVisible: false,
|
|
2988
|
-
configSidebarVisible: false,
|
|
2989
3155
|
staticMenuMobileActive: false,
|
|
2990
3156
|
menuHoverActive: false,
|
|
2991
3157
|
};
|
|
2992
|
-
this.configUpdate = new Subject();
|
|
2993
3158
|
this.overlayOpen = new Subject();
|
|
2994
|
-
this.configUpdate$ = this.configUpdate.asObservable();
|
|
2995
3159
|
this.overlayOpen$ = this.overlayOpen.asObservable();
|
|
2996
3160
|
//#region Top Bar
|
|
2997
3161
|
this.initTopBarData = () => {
|
|
@@ -3042,9 +3206,6 @@ class LayoutServiceBase {
|
|
|
3042
3206
|
this.overlayOpen.next(null);
|
|
3043
3207
|
}
|
|
3044
3208
|
}
|
|
3045
|
-
showConfigSidebar() {
|
|
3046
|
-
this.state.configSidebarVisible = true;
|
|
3047
|
-
}
|
|
3048
3209
|
isOverlay() {
|
|
3049
3210
|
return this.layoutConfig.menuMode === 'overlay';
|
|
3050
3211
|
}
|
|
@@ -3054,9 +3215,6 @@ class LayoutServiceBase {
|
|
|
3054
3215
|
isMobile() {
|
|
3055
3216
|
return !this.isDesktop();
|
|
3056
3217
|
}
|
|
3057
|
-
onConfigUpdate() {
|
|
3058
|
-
this.configUpdate.next(this.layoutConfig);
|
|
3059
|
-
}
|
|
3060
3218
|
//#endregion
|
|
3061
3219
|
ngOnDestroy() {
|
|
3062
3220
|
if (this.userSubscription) {
|
|
@@ -3297,6 +3455,7 @@ class ProfileAvatarComponent {
|
|
|
3297
3455
|
this.config = config;
|
|
3298
3456
|
this.isSideMenuLayout = true;
|
|
3299
3457
|
this.routeOnLargeProfileAvatarClick = true;
|
|
3458
|
+
/** Whether the login button is shown when no user is signed in. Defaults to `true`. */
|
|
3300
3459
|
this.showLoginButton = true;
|
|
3301
3460
|
this.routeToLoginPage = true;
|
|
3302
3461
|
this.loginButtonOutlined = false;
|
|
@@ -3719,7 +3878,9 @@ class SpiderlyDataTableComponent {
|
|
|
3719
3878
|
this.locale = locale;
|
|
3720
3879
|
this.destroy$ = new Subject();
|
|
3721
3880
|
this.tableIcon = 'pi pi-list';
|
|
3722
|
-
|
|
3881
|
+
/** Whether the paginator is shown. Pass only when `hasLazyLoad === false`. Defaults to `true`. */
|
|
3882
|
+
this.showPaginator = true;
|
|
3883
|
+
/** Whether the table is wrapped in a card container. Defaults to `false`. */
|
|
3723
3884
|
this.showCardWrapper = false;
|
|
3724
3885
|
this.readonly = false;
|
|
3725
3886
|
this.idField = 'id';
|
|
@@ -3737,8 +3898,11 @@ class SpiderlyDataTableComponent {
|
|
|
3737
3898
|
this.onIsAllSelectedChange = new EventEmitter();
|
|
3738
3899
|
this.matchModeDateOptions = [];
|
|
3739
3900
|
this.matchModeNumberOptions = [];
|
|
3901
|
+
/** Whether the "Add" button is shown. Defaults to `true`. */
|
|
3740
3902
|
this.showAddButton = true;
|
|
3903
|
+
/** Whether the "Export to Excel" button is shown. Defaults to `true`. */
|
|
3741
3904
|
this.showExportToExcelButton = true;
|
|
3905
|
+
/** Whether the reload-table button is shown. Defaults to `false`. */
|
|
3742
3906
|
this.showReloadTableButton = false;
|
|
3743
3907
|
this.hasLazyLoad = true;
|
|
3744
3908
|
/** 'session' persists across refresh only; 'local' persists indefinitely. */
|
|
@@ -4388,12 +4552,14 @@ class SpiderlyDataViewComponent {
|
|
|
4388
4552
|
this.rows = 10;
|
|
4389
4553
|
this.filters = [];
|
|
4390
4554
|
this.onLazyLoad = new EventEmitter();
|
|
4555
|
+
/** Whether the data view is wrapped in a card container. Defaults to `true`. */
|
|
4391
4556
|
this.showCardWrapper = true;
|
|
4392
4557
|
/**
|
|
4393
4558
|
* Whether to display additional data on the right side of the paginator.
|
|
4394
4559
|
* Defaults to `false`.
|
|
4395
4560
|
*/
|
|
4396
4561
|
this.showPaginatorRightData = false;
|
|
4562
|
+
/** Whether the total records count is shown. Defaults to `false`. */
|
|
4397
4563
|
this.showTotalRecordsNumber = false;
|
|
4398
4564
|
this.applyFiltersIcon = 'pi pi-filter';
|
|
4399
4565
|
this.clearFiltersIcon = 'pi pi-filter-slash';
|
|
@@ -4762,18 +4928,13 @@ class AuthGuard {
|
|
|
4762
4928
|
return this.checkAuth();
|
|
4763
4929
|
}
|
|
4764
4930
|
checkAuth() {
|
|
4765
|
-
return this.authService.user$.pipe(
|
|
4931
|
+
return this.authService.user$.pipe(filter$1((user) => user !== undefined), // wait until the session is resolved (undefined = still loading)
|
|
4932
|
+
take(1), map((user) => {
|
|
4766
4933
|
if (user) {
|
|
4767
4934
|
return true;
|
|
4768
4935
|
}
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
// this.router.navigate(['login'], {
|
|
4772
|
-
// queryParams: { returnUrl },
|
|
4773
|
-
// });
|
|
4774
|
-
this.router.navigate([this.config.loginSlug]);
|
|
4775
|
-
return false;
|
|
4776
|
-
}
|
|
4936
|
+
this.router.navigate([this.config.loginSlug]);
|
|
4937
|
+
return false;
|
|
4777
4938
|
}));
|
|
4778
4939
|
}
|
|
4779
4940
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: AuthGuard, deps: [{ token: AuthServiceBase }, { token: i3$2.Router }, { token: ConfigServiceBase }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
@@ -4794,14 +4955,13 @@ class NotAuthGuard {
|
|
|
4794
4955
|
return this.checkAuth();
|
|
4795
4956
|
}
|
|
4796
4957
|
checkAuth() {
|
|
4797
|
-
return this.authService.user$.pipe(
|
|
4958
|
+
return this.authService.user$.pipe(filter$1((user) => user !== undefined), // wait until the session is resolved (undefined = still loading)
|
|
4959
|
+
take(1), map((user) => {
|
|
4798
4960
|
if (user) {
|
|
4799
4961
|
this.authService.navigateToDashboard();
|
|
4800
4962
|
return false;
|
|
4801
4963
|
}
|
|
4802
|
-
|
|
4803
|
-
return true;
|
|
4804
|
-
}
|
|
4964
|
+
return true;
|
|
4805
4965
|
}));
|
|
4806
4966
|
}
|
|
4807
4967
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: NotAuthGuard, deps: [{ token: AuthServiceBase }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
@@ -4909,75 +5069,90 @@ const convertToDate = (object, parent, key) => {
|
|
|
4909
5069
|
}
|
|
4910
5070
|
};
|
|
4911
5071
|
|
|
5072
|
+
// Cookie-based auth: the session JWTs are HttpOnly cookies, so we just send credentials on API calls.
|
|
5073
|
+
// The browser attaches/refreshes the cookies; JS never holds the tokens (XSS-safe).
|
|
5074
|
+
//
|
|
5075
|
+
// CSRF: state-changing requests (POST/PUT/DELETE/PATCH) authenticated via cookie must include the
|
|
5076
|
+
// X-CSRF header, otherwise Spiderly.Shared/Attributes/AuthGuardAttribute.cs returns 403 Forbidden
|
|
5077
|
+
// (the server-side check was added in commit 92f238d but the matching client-side header was never
|
|
5078
|
+
// emitted, so every cookie-authed write was failing in the admin). The check is presence-only —
|
|
5079
|
+
// any non-empty value works — and the protection comes from the fact that a cross-origin form
|
|
5080
|
+
// submission cannot set custom request headers without a CORS preflight.
|
|
5081
|
+
const SAFE_HTTP_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
|
|
4912
5082
|
const jwtInterceptor = (req, next) => {
|
|
4913
5083
|
const config = inject(ConfigServiceBase);
|
|
4914
|
-
const platformId = inject(PLATFORM_ID);
|
|
4915
|
-
let accessToken = null;
|
|
4916
|
-
if (isPlatformBrowser(platformId)) {
|
|
4917
|
-
accessToken = localStorage.getItem(config.accessTokenKey);
|
|
4918
|
-
}
|
|
4919
5084
|
const isApiUrl = req.url.startsWith(config.apiUrl);
|
|
4920
|
-
if (
|
|
5085
|
+
if (isApiUrl) {
|
|
5086
|
+
const isStateChanging = !SAFE_HTTP_METHODS.has(req.method.toUpperCase());
|
|
4921
5087
|
req = req.clone({
|
|
4922
|
-
|
|
5088
|
+
withCredentials: true,
|
|
5089
|
+
...(isStateChanging && { setHeaders: { 'X-CSRF': '1' } }),
|
|
4923
5090
|
});
|
|
4924
5091
|
}
|
|
4925
5092
|
return next(req);
|
|
4926
5093
|
};
|
|
4927
5094
|
|
|
5095
|
+
/**
|
|
5096
|
+
* Owns cross-cutting HTTP-error UX: shows the right message and, on an expired session, clears auth — then
|
|
5097
|
+
* RETHROWS. Errors stay errors: callers run only their success path, and an unhandled HttpErrorResponse that
|
|
5098
|
+
* reaches the global ErrorHandler is intentionally ignored there (HTTP-error UX lives here). This interceptor
|
|
5099
|
+
* must never convert an error into a value — doing so makes callers treat failures as data.
|
|
5100
|
+
*/
|
|
4928
5101
|
const unauthorizedInterceptor = (req, next) => {
|
|
4929
5102
|
const messageService = inject(SpiderlyMessageService);
|
|
4930
5103
|
const translocoService = inject(TranslocoService);
|
|
4931
5104
|
const config = inject(ConfigServiceBase);
|
|
4932
5105
|
const authService = inject(AuthServiceBase);
|
|
4933
|
-
const
|
|
5106
|
+
const reactToError = (err, request) => {
|
|
4934
5107
|
if (!config.production) {
|
|
4935
5108
|
console.error(err);
|
|
4936
5109
|
}
|
|
4937
5110
|
let errorResponse = err.error;
|
|
4938
|
-
if (request.responseType
|
|
4939
|
-
|
|
4940
|
-
|
|
5111
|
+
if (request.responseType !== 'json' && typeof err.error === 'string') {
|
|
5112
|
+
try {
|
|
5113
|
+
errorResponse = JSON.parse(err.error);
|
|
5114
|
+
}
|
|
5115
|
+
catch {
|
|
5116
|
+
errorResponse = null;
|
|
5117
|
+
}
|
|
5118
|
+
}
|
|
5119
|
+
if (err.status === 0) {
|
|
5120
|
+
// Server unreachable; defer so the message isn't lost during a shutdown/refresh race.
|
|
4941
5121
|
setTimeout(() => {
|
|
4942
|
-
// Had problem when the server is shut down, and try to refresh token, warning message didn't appear
|
|
4943
5122
|
messageService.warningMessage(translocoService.translate('ServerLostConnectionDetails'), translocoService.translate('ServerLostConnectionTitle'));
|
|
4944
5123
|
}, 100);
|
|
4945
|
-
return of(err.message);
|
|
4946
5124
|
}
|
|
4947
|
-
else if (err.status
|
|
4948
|
-
messageService.warningMessage(errorResponse
|
|
4949
|
-
translocoService.translate('BadRequestDetails'), translocoService.translate('Warning'));
|
|
4950
|
-
return of(err.message);
|
|
5125
|
+
else if (err.status === 400) {
|
|
5126
|
+
messageService.warningMessage(errorResponse?.message ?? translocoService.translate('BadRequestDetails'), translocoService.translate('Warning'));
|
|
4951
5127
|
}
|
|
4952
|
-
else if (err.status
|
|
5128
|
+
else if (err.status === 401) {
|
|
4953
5129
|
if (errorResponse?.errorCode === ApiErrorCodes.InvalidToken) {
|
|
4954
|
-
authService.
|
|
4955
|
-
|
|
5130
|
+
authService.clearSession(); // expired/invalid session — drop it; guards send the user to login
|
|
5131
|
+
}
|
|
5132
|
+
else {
|
|
5133
|
+
messageService.warningMessage(errorResponse?.message ?? translocoService.translate('LoginRequired'), translocoService.translate('Warning'));
|
|
4956
5134
|
}
|
|
4957
|
-
messageService.warningMessage(errorResponse?.message ?? translocoService.translate('LoginRequired'), translocoService.translate('Warning'));
|
|
4958
|
-
return of(err.message);
|
|
4959
5135
|
}
|
|
4960
|
-
else if (err.status
|
|
5136
|
+
else if (err.status === 403) {
|
|
4961
5137
|
messageService.warningMessage(translocoService.translate('PermissionErrorDetails'), translocoService.translate('PermissionErrorTitle'));
|
|
4962
|
-
return of(err.message);
|
|
4963
5138
|
}
|
|
4964
|
-
else if (err.status
|
|
5139
|
+
else if (err.status === 404) {
|
|
4965
5140
|
messageService.warningMessage(translocoService.translate('NotFoundDetails'), translocoService.translate('NotFoundTitle'));
|
|
4966
|
-
return of(err.message);
|
|
4967
5141
|
}
|
|
4968
5142
|
else {
|
|
4969
5143
|
messageService.errorMessage(translocoService.translate('UnexpectedErrorDetails'), translocoService.translate('UnexpectedErrorTitle'));
|
|
4970
|
-
return of(err.message);
|
|
4971
5144
|
}
|
|
4972
5145
|
};
|
|
4973
5146
|
return next(req).pipe(catchError((err) => {
|
|
4974
|
-
|
|
5147
|
+
reactToError(err, req);
|
|
5148
|
+
return throwError(() => err);
|
|
4975
5149
|
}));
|
|
4976
5150
|
};
|
|
4977
5151
|
|
|
4978
5152
|
function authInitializer(authService, platformId) {
|
|
4979
5153
|
if (isPlatformBrowser(platformId)) {
|
|
4980
5154
|
return () => {
|
|
5155
|
+
authService.captureExternalAuthError(); // before the router can strip ?externalAuthError on the /login redirect
|
|
4981
5156
|
return authService.refreshToken();
|
|
4982
5157
|
};
|
|
4983
5158
|
}
|
|
@@ -5028,5 +5203,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
|
|
|
5028
5203
|
* Generated bundle index. Do not edit.
|
|
5029
5204
|
*/
|
|
5030
5205
|
|
|
5031
|
-
export { Action, AllClickEvent, ApiErrorCodes, ApiSecurityService, AppSidebarComponent, AuthGuard, AuthResult, AuthResultWithCookies, AuthServiceBase, BaseAutocompleteControl, BaseControl, BaseDropdownControl, BaseEntity, BaseFormComponent, BaseFormService, CardSkeletonComponent, Codebook, Column, ConfigServiceBase, ExternalProvider, Filter, FilterRule, FilterSortMeta, FooterComponent,
|
|
5206
|
+
export { Action, AllClickEvent, ApiErrorCodes, ApiSecurityService, AppSidebarComponent, AuthCardComponent, AuthGuard, AuthResult, AuthResultWithCookies, AuthServiceBase, BaseAutocompleteControl, BaseControl, BaseDropdownControl, BaseEntity, BaseFormComponent, BaseFormService, CardSkeletonComponent, Codebook, Column, ConfigServiceBase, DEFAULT_EXTERNAL_PROVIDER_ICONS, ExternalLoginComponent, ExternalProvider, ExternalProviderPublic, Filter, FilterRule, FilterSortMeta, FooterComponent, IndexCardComponent, InfoCardComponent, InitCompanyAuthDialogDetails, InitTopBarData, IsAuthorizedForSaveEvent, LastMenuIconIndexClicked, LayoutServiceBase, LazyLoadSelectedIdsResult, Login, LoginVerificationComponent, LoginVerificationToken, MatchModeCodes, MenuChangeEvent, MenuitemComponent, Namebook, NotAuthGuard, NotFoundComponent, PROPS_KEY, PaginatedResult, PanelBodyComponent, PanelFooterComponent, PanelHeaderComponent, PrimengOption, ProfileAvatarComponent, ReflectProp, RefreshTokenRequest, RequiredComponent, RowClickEvent, SecurityPermissionCodes, SendLoginVerificationEmailResult, SideMenuTopBarComponent, SidebarMenuComponent, SimpleSaveResult, SpiderlyAutocompleteComponent, SpiderlyButtonBaseComponent, SpiderlyButtonComponent, SpiderlyCalendarComponent, SpiderlyCardComponent, SpiderlyCheckboxComponent, SpiderlyColorPickerComponent, SpiderlyControlsModule, SpiderlyDataTableComponent, SpiderlyDataViewComponent, SpiderlyDeleteConfirmationComponent, SpiderlyDropdownComponent, SpiderlyEditorComponent, SpiderlyErrorHandler, SpiderlyFileComponent, SpiderlyFileSelectEvent, SpiderlyFormArray, SpiderlyFormControl, SpiderlyFormGroup, SpiderlyLayoutComponent, SpiderlyLoginComponent, SpiderlyMarkdownComponent, SpiderlyMessageService, SpiderlyMultiAutocompleteComponent, SpiderlyMultiSelectComponent, SpiderlyNumberComponent, SpiderlyPanelComponent, SpiderlyPanelsModule, SpiderlyPasswordComponent, SpiderlyReturnButtonComponent, SpiderlySplitButtonComponent, SpiderlyTab, SpiderlyTemplateTypeDirective, SpiderlyTextareaComponent, SpiderlyTextboxComponent, SpiderlyTranslocoLoader, TopBarComponent, UserBase, UserRole, ValidatorAbstractService, VerificationTokenRequest, VerificationTypeCodes, VerificationWrapperComponent, adjustColor, authInitializer, capitalizeFirstChar, deleteAction, exportListToExcel, firstCharToUpper, getFileNameFromContentDisposition, getHtmlImgDisplayString64, getImageDimensions, getMimeTypeForFileName, getMonth, getParentUrl, getPrimengAutocompleteCodebookOptions, getPrimengAutocompleteNamebookOptions, getPrimengDropdownCodebookOptions, getPrimengDropdownNamebookOptions, getPrimengNamebookOptions, httpLoadingInterceptor, isExcelFileType, isFileImageType, isNullOrEmpty, jsonHttpInterceptor, jwtInterceptor, kebabToTitleCase, nameOf, nameof, parseDateOnlyLocal, primitiveArrayTypes, pushAction, selectedTab, singleOrDefault, splitPascalCase, toCommaSeparatedString, unauthorizedInterceptor, validatePrecisionScale };
|
|
5032
5207
|
//# sourceMappingURL=spiderly.mjs.map
|