spiderly 19.8.2 → 19.8.4
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 +833 -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 +3 -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,23 @@ 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',
|
|
87
|
+
ExternalEmailMissing: 'external_email_missing',
|
|
83
88
|
};
|
|
84
89
|
|
|
85
90
|
class BaseControl {
|
|
86
91
|
constructor(translocoService) {
|
|
87
92
|
this.translocoService = translocoService;
|
|
88
93
|
this.disabled = false;
|
|
94
|
+
/** Whether the field's label is rendered. Defaults to `true`. */
|
|
89
95
|
this.showLabel = true;
|
|
96
|
+
/** Whether the required (*) indicator is shown next to the label. Defaults to `true`. */
|
|
90
97
|
this.showRequired = true;
|
|
91
98
|
this.label = null; // NgModel/Want custom translation
|
|
92
99
|
this.controlValid = true; // NgModel
|
|
93
100
|
this.placeholder = '';
|
|
101
|
+
/** Whether the info tooltip icon is shown next to the label. Defaults to `false`. */
|
|
94
102
|
this.showTooltip = false;
|
|
95
103
|
this.tooltipText = null;
|
|
96
104
|
this.tooltipIcon = 'pi pi-info-circle';
|
|
@@ -143,6 +151,7 @@ class BaseDropdownControl extends BaseControl {
|
|
|
143
151
|
constructor(translocoService) {
|
|
144
152
|
super(translocoService);
|
|
145
153
|
this.translocoService = translocoService;
|
|
154
|
+
/** Whether an addon button is shown next to the dropdown. Defaults to `false`. */
|
|
146
155
|
this.showAddon = false;
|
|
147
156
|
this.addonIcon = 'pi pi-ellipsis-h';
|
|
148
157
|
this.placeholder = this.translocoService.translate('SelectFromTheList');
|
|
@@ -390,6 +399,7 @@ class SpiderlyAutocompleteComponent extends BaseAutocompleteControl {
|
|
|
390
399
|
this.translocoService = translocoService;
|
|
391
400
|
this.validatorService = validatorService;
|
|
392
401
|
this.appendTo = 'body';
|
|
402
|
+
/** Whether a clear button is shown. Defaults to `true`. */
|
|
393
403
|
this.showClear = true;
|
|
394
404
|
this.helperFormControl = new SpiderlyFormControl(null, {
|
|
395
405
|
updateOn: 'change',
|
|
@@ -622,10 +632,11 @@ function exportListToExcel(exportListToExcelObservableMethod, filter) {
|
|
|
622
632
|
FileSaver.saveAs(res.body, decodeURIComponent(fileName));
|
|
623
633
|
});
|
|
624
634
|
}
|
|
635
|
+
function getPrimengNamebookOptions(namebookList) {
|
|
636
|
+
return namebookList.map((x) => ({ label: x.displayName, code: x.id }));
|
|
637
|
+
}
|
|
625
638
|
function getPrimengDropdownNamebookOptions(getDropdownListObservable, parentEntityId) {
|
|
626
|
-
return getDropdownListObservable(parentEntityId ?? 0).pipe(map((res) =>
|
|
627
|
-
return res.map((x) => ({ label: x.displayName, code: x.id }));
|
|
628
|
-
}));
|
|
639
|
+
return getDropdownListObservable(parentEntityId ?? 0).pipe(map((res) => getPrimengNamebookOptions(res)));
|
|
629
640
|
}
|
|
630
641
|
function getPrimengDropdownCodebookOptions(getDropdownListObservable) {
|
|
631
642
|
return getDropdownListObservable().pipe(map((res) => {
|
|
@@ -675,25 +686,6 @@ function kebabToTitleCase(input) {
|
|
|
675
686
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
676
687
|
.join(' ');
|
|
677
688
|
}
|
|
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
689
|
const PROPS_KEY = Symbol('props');
|
|
698
690
|
function ReflectProp(target, propertyKey) {
|
|
699
691
|
const props = Reflect.getMetadata(PROPS_KEY, target) || [];
|
|
@@ -720,6 +712,7 @@ class SpiderlyCalendarComponent extends BaseControl {
|
|
|
720
712
|
constructor(translocoService) {
|
|
721
713
|
super(translocoService);
|
|
722
714
|
this.translocoService = translocoService;
|
|
715
|
+
/** Whether the time picker is shown in addition to the date. Defaults to `false`. */
|
|
723
716
|
this.showTime = false;
|
|
724
717
|
this.dateOnly = false;
|
|
725
718
|
this.timeOnly = false;
|
|
@@ -880,6 +873,7 @@ class SpiderlyColorPickerComponent extends BaseControl {
|
|
|
880
873
|
constructor(translocoService) {
|
|
881
874
|
super(translocoService);
|
|
882
875
|
this.translocoService = translocoService;
|
|
876
|
+
/** Whether a hex text input is shown alongside the color swatch. Defaults to `true`. */
|
|
883
877
|
this.showInputTextField = true;
|
|
884
878
|
}
|
|
885
879
|
ngOnInit() {
|
|
@@ -937,6 +931,7 @@ class SpiderlyPasswordComponent extends BaseControl {
|
|
|
937
931
|
constructor(translocoService) {
|
|
938
932
|
super(translocoService);
|
|
939
933
|
this.translocoService = translocoService;
|
|
934
|
+
/** Whether a password-strength meter is shown below the field. Defaults to `false`. */
|
|
940
935
|
this.showPasswordStrength = false;
|
|
941
936
|
}
|
|
942
937
|
ngOnInit() {
|
|
@@ -962,6 +957,7 @@ class SpiderlyTextboxComponent extends BaseControl {
|
|
|
962
957
|
constructor(translocoService) {
|
|
963
958
|
super(translocoService);
|
|
964
959
|
this.translocoService = translocoService;
|
|
960
|
+
/** Whether an icon button is appended to the input. Defaults to `false`. */
|
|
965
961
|
this.showButton = false;
|
|
966
962
|
this.onButtonClick = new EventEmitter();
|
|
967
963
|
}
|
|
@@ -1039,6 +1035,7 @@ class SpiderlyNumberComponent extends BaseControl {
|
|
|
1039
1035
|
constructor(translocoService) {
|
|
1040
1036
|
super(translocoService);
|
|
1041
1037
|
this.translocoService = translocoService;
|
|
1038
|
+
/** Whether increment/decrement spinner buttons are shown. Defaults to `true`. */
|
|
1042
1039
|
this.showButtons = true;
|
|
1043
1040
|
this.maxFractionDigits = 0;
|
|
1044
1041
|
}
|
|
@@ -1171,6 +1168,114 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
|
|
|
1171
1168
|
type: Input
|
|
1172
1169
|
}] } });
|
|
1173
1170
|
|
|
1171
|
+
/**
|
|
1172
|
+
* Markdown form control: a plain textarea (Write) with a rendered live preview (Preview),
|
|
1173
|
+
* arranged as tabs. The stored value is raw Markdown text.
|
|
1174
|
+
*
|
|
1175
|
+
* The preview is rendered with ngx-markdown (marked) and is intentionally APPROXIMATE — a
|
|
1176
|
+
* consuming storefront may render the same Markdown with a different engine/flavor.
|
|
1177
|
+
*
|
|
1178
|
+
* The <textarea> DOM is the source of truth for the text (the form control mirrors it, like
|
|
1179
|
+
* spiderly-editor mirrors Quill). We never splice control.value, because SpiderlyFormControl
|
|
1180
|
+
* defaults to updateOn:'blur' and would be stale relative to the focused textarea.
|
|
1181
|
+
*
|
|
1182
|
+
* When {@link uploadImageMethod} is provided (wired by the generator for properties with an
|
|
1183
|
+
* S3 public-storage attribute), pasting an image uploads it and, on success, inserts a
|
|
1184
|
+
* standard `` link at the caret via execCommand (which preserves the native undo
|
|
1185
|
+
* stack). Upload progress is shown out-of-band, not as a token in the text.
|
|
1186
|
+
*/
|
|
1187
|
+
class SpiderlyMarkdownComponent extends BaseControl {
|
|
1188
|
+
constructor(translocoService) {
|
|
1189
|
+
super(translocoService);
|
|
1190
|
+
this.translocoService = translocoService;
|
|
1191
|
+
this.objectId = 0;
|
|
1192
|
+
this.pendingImageUploads = 0;
|
|
1193
|
+
this.imageUploadFailed = false;
|
|
1194
|
+
}
|
|
1195
|
+
ngOnInit() {
|
|
1196
|
+
super.ngOnInit();
|
|
1197
|
+
}
|
|
1198
|
+
onPaste(event) {
|
|
1199
|
+
// Only intercept when image upload is wired; otherwise let the default paste happen.
|
|
1200
|
+
if (!this.uploadImageMethod || this.control?.disabled)
|
|
1201
|
+
return;
|
|
1202
|
+
const imageFile = this.getPastedImage(event);
|
|
1203
|
+
if (!imageFile)
|
|
1204
|
+
return;
|
|
1205
|
+
event.preventDefault();
|
|
1206
|
+
const formData = new FormData();
|
|
1207
|
+
formData.append('file', imageFile, `${this.objectId}-${imageFile.name || 'pasted-image.png'}`);
|
|
1208
|
+
this.imageUploadFailed = false;
|
|
1209
|
+
this.pendingImageUploads++;
|
|
1210
|
+
this.uploadImageMethod(formData).subscribe({
|
|
1211
|
+
next: (result) => {
|
|
1212
|
+
this.pendingImageUploads--;
|
|
1213
|
+
this.insertImageMarkdown(result.url);
|
|
1214
|
+
},
|
|
1215
|
+
error: () => {
|
|
1216
|
+
this.pendingImageUploads--;
|
|
1217
|
+
this.imageUploadFailed = true;
|
|
1218
|
+
},
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
getPastedImage(event) {
|
|
1222
|
+
const items = event.clipboardData?.items;
|
|
1223
|
+
if (!items)
|
|
1224
|
+
return null;
|
|
1225
|
+
for (let i = 0; i < items.length; i++) {
|
|
1226
|
+
if (items[i].type.startsWith('image/')) {
|
|
1227
|
+
return items[i].getAsFile();
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
return null;
|
|
1231
|
+
}
|
|
1232
|
+
insertImageMarkdown(url) {
|
|
1233
|
+
const snippet = ``;
|
|
1234
|
+
const textarea = this.textareaRef?.nativeElement;
|
|
1235
|
+
// Preferred path: the textarea is focused, so insert at the caret while preserving the
|
|
1236
|
+
// native undo stack. The resulting 'input' event syncs the control on blur, exactly like
|
|
1237
|
+
// the user typing — no manual setValue, no stale-model read.
|
|
1238
|
+
if (textarea && document.activeElement === textarea && document.execCommand('insertText', false, snippet)) {
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
// Fallback (textarea blurred/absent, or execCommand unsupported): read the LIVE textarea
|
|
1242
|
+
// value — never control.value, which is stale while focused. When blurred, the control is
|
|
1243
|
+
// already current, so appending can't drop uncommitted text.
|
|
1244
|
+
if (textarea) {
|
|
1245
|
+
const sep = textarea.value.length ? '\n' : '';
|
|
1246
|
+
textarea.value = `${textarea.value}${sep}${snippet}`;
|
|
1247
|
+
this.control.setValue(textarea.value);
|
|
1248
|
+
}
|
|
1249
|
+
else {
|
|
1250
|
+
const current = this.control.value ?? '';
|
|
1251
|
+
this.control.setValue(current.length ? `${current}\n${snippet}` : snippet);
|
|
1252
|
+
}
|
|
1253
|
+
this.control.markAsDirty();
|
|
1254
|
+
}
|
|
1255
|
+
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 }); }
|
|
1256
|
+
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"] }] }); }
|
|
1257
|
+
}
|
|
1258
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyMarkdownComponent, decorators: [{
|
|
1259
|
+
type: Component,
|
|
1260
|
+
args: [{ selector: 'spiderly-markdown', imports: [
|
|
1261
|
+
CommonModule,
|
|
1262
|
+
ReactiveFormsModule,
|
|
1263
|
+
FormsModule,
|
|
1264
|
+
TextareaModule,
|
|
1265
|
+
TabsModule,
|
|
1266
|
+
MarkdownComponent,
|
|
1267
|
+
RequiredComponent,
|
|
1268
|
+
TranslocoDirective,
|
|
1269
|
+
], 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" }]
|
|
1270
|
+
}], ctorParameters: () => [{ type: i1.TranslocoService }], propDecorators: { textareaRef: [{
|
|
1271
|
+
type: ViewChild,
|
|
1272
|
+
args: ['textarea']
|
|
1273
|
+
}], uploadImageMethod: [{
|
|
1274
|
+
type: Input
|
|
1275
|
+
}], objectId: [{
|
|
1276
|
+
type: Input
|
|
1277
|
+
}] } });
|
|
1278
|
+
|
|
1174
1279
|
class SpiderlyButtonBaseComponent {
|
|
1175
1280
|
constructor(router) {
|
|
1176
1281
|
this.router = router;
|
|
@@ -1199,13 +1304,15 @@ class SpiderlyButtonBaseComponent {
|
|
|
1199
1304
|
this.subscription.unsubscribe();
|
|
1200
1305
|
}
|
|
1201
1306
|
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 }] }); }
|
|
1307
|
+
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
1308
|
}
|
|
1204
1309
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyButtonBaseComponent, decorators: [{
|
|
1205
1310
|
type: Component,
|
|
1206
1311
|
args: [{ selector: 'spiderly-button-base', template: ``, imports: [CommonModule, ButtonModule, SplitButtonModule] }]
|
|
1207
1312
|
}], ctorParameters: () => [{ type: i3$2.Router }], propDecorators: { icon: [{
|
|
1208
1313
|
type: Input
|
|
1314
|
+
}], iconUrl: [{
|
|
1315
|
+
type: Input
|
|
1209
1316
|
}], label: [{
|
|
1210
1317
|
type: Input
|
|
1211
1318
|
}], outlined: [{
|
|
@@ -1239,11 +1346,11 @@ class SpiderlyButtonComponent extends SpiderlyButtonBaseComponent {
|
|
|
1239
1346
|
this.type = 'button';
|
|
1240
1347
|
}
|
|
1241
1348
|
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 }] }); }
|
|
1349
|
+
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
1350
|
}
|
|
1244
1351
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyButtonComponent, decorators: [{
|
|
1245
1352
|
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" }]
|
|
1353
|
+
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
1354
|
}], propDecorators: { type: [{
|
|
1248
1355
|
type: Input
|
|
1249
1356
|
}] } });
|
|
@@ -1415,7 +1522,7 @@ class SpiderlyFileComponent extends BaseControl {
|
|
|
1415
1522
|
return isExcelFileType(mimeType);
|
|
1416
1523
|
}
|
|
1417
1524
|
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$
|
|
1525
|
+
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
1526
|
}
|
|
1420
1527
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyFileComponent, decorators: [{
|
|
1421
1528
|
type: Component,
|
|
@@ -1508,6 +1615,7 @@ class SpiderlyControlsModule {
|
|
|
1508
1615
|
SpiderlyNumberComponent,
|
|
1509
1616
|
SpiderlyDropdownComponent,
|
|
1510
1617
|
SpiderlyEditorComponent,
|
|
1618
|
+
SpiderlyMarkdownComponent,
|
|
1511
1619
|
SpiderlyColorPickerComponent,
|
|
1512
1620
|
SpiderlyFileComponent], exports: [SpiderlyTextboxComponent,
|
|
1513
1621
|
SpiderlyTextareaComponent,
|
|
@@ -1522,6 +1630,7 @@ class SpiderlyControlsModule {
|
|
|
1522
1630
|
SpiderlyNumberComponent,
|
|
1523
1631
|
SpiderlyDropdownComponent,
|
|
1524
1632
|
SpiderlyEditorComponent,
|
|
1633
|
+
SpiderlyMarkdownComponent,
|
|
1525
1634
|
SpiderlyColorPickerComponent,
|
|
1526
1635
|
SpiderlyFileComponent] }); }
|
|
1527
1636
|
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyControlsModule, imports: [SpiderlyTextboxComponent,
|
|
@@ -1537,6 +1646,7 @@ class SpiderlyControlsModule {
|
|
|
1537
1646
|
SpiderlyNumberComponent,
|
|
1538
1647
|
SpiderlyDropdownComponent,
|
|
1539
1648
|
SpiderlyEditorComponent,
|
|
1649
|
+
SpiderlyMarkdownComponent,
|
|
1540
1650
|
SpiderlyColorPickerComponent,
|
|
1541
1651
|
SpiderlyFileComponent] }); }
|
|
1542
1652
|
}
|
|
@@ -1557,6 +1667,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
|
|
|
1557
1667
|
SpiderlyNumberComponent,
|
|
1558
1668
|
SpiderlyDropdownComponent,
|
|
1559
1669
|
SpiderlyEditorComponent,
|
|
1670
|
+
SpiderlyMarkdownComponent,
|
|
1560
1671
|
SpiderlyColorPickerComponent,
|
|
1561
1672
|
SpiderlyFileComponent,
|
|
1562
1673
|
],
|
|
@@ -1574,6 +1685,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
|
|
|
1574
1685
|
SpiderlyNumberComponent,
|
|
1575
1686
|
SpiderlyDropdownComponent,
|
|
1576
1687
|
SpiderlyEditorComponent,
|
|
1688
|
+
SpiderlyMarkdownComponent,
|
|
1577
1689
|
SpiderlyColorPickerComponent,
|
|
1578
1690
|
SpiderlyFileComponent,
|
|
1579
1691
|
],
|
|
@@ -1582,95 +1694,494 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
|
|
|
1582
1694
|
}]
|
|
1583
1695
|
}] });
|
|
1584
1696
|
|
|
1585
|
-
class
|
|
1586
|
-
|
|
1587
|
-
constructor({ id, email, } = {}) {
|
|
1697
|
+
class InitCompanyAuthDialogDetails extends BaseEntity {
|
|
1698
|
+
constructor({ image, companyName, } = {}) {
|
|
1588
1699
|
super();
|
|
1589
|
-
this.
|
|
1590
|
-
this.
|
|
1700
|
+
this.image = image;
|
|
1701
|
+
this.companyName = companyName;
|
|
1591
1702
|
}
|
|
1592
|
-
static { this.
|
|
1593
|
-
id: {
|
|
1594
|
-
type: 'number',
|
|
1595
|
-
},
|
|
1596
|
-
email: {
|
|
1597
|
-
type: 'string',
|
|
1598
|
-
},
|
|
1599
|
-
}; }
|
|
1703
|
+
static { this.typeName = 'InitCompanyAuthDialogDetails'; }
|
|
1600
1704
|
}
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
constructor(
|
|
1604
|
-
|
|
1605
|
-
this.
|
|
1606
|
-
this.
|
|
1607
|
-
this.
|
|
1608
|
-
|
|
1705
|
+
|
|
1706
|
+
class ConfigServiceBase {
|
|
1707
|
+
constructor() {
|
|
1708
|
+
this.production = false;
|
|
1709
|
+
this.frontendUrl = 'http://localhost:4200';
|
|
1710
|
+
this.companyName = 'Company Name';
|
|
1711
|
+
this.primaryColor = '#111b2c';
|
|
1712
|
+
/* URLs */
|
|
1713
|
+
this.loginSlug = 'login';
|
|
1714
|
+
/* Local storage */
|
|
1715
|
+
this.accessTokenKey = 'access_token';
|
|
1716
|
+
this.refreshTokenKey = 'refresh_token';
|
|
1717
|
+
this.browserIdKey = 'browser_id';
|
|
1718
|
+
this.httpOptions = {};
|
|
1719
|
+
this.httpSkipSpinnerOptions = {
|
|
1720
|
+
headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
|
|
1721
|
+
params: new HttpParams().set('X-Skip-Spinner', 'true'),
|
|
1722
|
+
};
|
|
1723
|
+
this.logoPath = 'assets/images/logo/logo.svg';
|
|
1724
|
+
/* Pagination */
|
|
1725
|
+
this.defaultPageSize = 10;
|
|
1609
1726
|
}
|
|
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
|
-
}; }
|
|
1727
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ConfigServiceBase, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1728
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ConfigServiceBase, providedIn: 'root' }); }
|
|
1624
1729
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1730
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ConfigServiceBase, decorators: [{
|
|
1731
|
+
type: Injectable,
|
|
1732
|
+
args: [{
|
|
1733
|
+
providedIn: 'root',
|
|
1734
|
+
}]
|
|
1735
|
+
}], ctorParameters: () => [] });
|
|
1736
|
+
|
|
1737
|
+
class ApiSecurityService {
|
|
1738
|
+
constructor(http, config) {
|
|
1739
|
+
this.http = http;
|
|
1740
|
+
this.config = config;
|
|
1741
|
+
//#region Authentication
|
|
1742
|
+
this.login = (request) => {
|
|
1743
|
+
return this.http.post(`${this.config.apiUrl}/Security/Login`, request, this.config.httpOptions);
|
|
1744
|
+
};
|
|
1745
|
+
this.sendLoginVerificationEmail = (loginDTO) => {
|
|
1746
|
+
return this.http.post(`${this.config.apiUrl}/Security/SendLoginVerificationEmail`, loginDTO, this.config.httpOptions);
|
|
1747
|
+
};
|
|
1748
|
+
this.loginWithCookies = (request) => {
|
|
1749
|
+
return this.http.post(`${this.config.apiUrl}/Security/LoginWithCookies`, request, this.config.httpOptions);
|
|
1750
|
+
};
|
|
1751
|
+
this.getExternalProviders = () => {
|
|
1752
|
+
return this.http.get(`${this.config.apiUrl}/Security/GetExternalProviders`, this.config.httpSkipSpinnerOptions);
|
|
1753
|
+
};
|
|
1754
|
+
this.logout = (browserId) => {
|
|
1755
|
+
return this.http.get(`${this.config.apiUrl}/Security/Logout?browserId=${browserId}`);
|
|
1756
|
+
};
|
|
1757
|
+
this.logoutWithCookies = (browserId) => {
|
|
1758
|
+
return this.http.get(`${this.config.apiUrl}/Security/LogoutWithCookies?browserId=${browserId}`);
|
|
1759
|
+
};
|
|
1760
|
+
this.refreshTokenWithHeaders = (request) => {
|
|
1761
|
+
return this.http.post(`${this.config.apiUrl}/Security/RefreshTokenWithHeaders`, request, this.config.httpOptions);
|
|
1762
|
+
};
|
|
1763
|
+
this.refreshTokenWithCookies = (browserId) => {
|
|
1764
|
+
// POST, not GET: refresh rotates the single-use token (a state mutation), and a cacheable GET let
|
|
1765
|
+
// browsers replay a stale logged-in response on back/forward navigation (phantom dashboard after logout).
|
|
1766
|
+
return this.http.post(`${this.config.apiUrl}/Security/RefreshTokenWithCookies?browserId=${browserId}`, null);
|
|
1767
|
+
};
|
|
1768
|
+
//#endregion
|
|
1769
|
+
//#region User
|
|
1770
|
+
this.getCurrentUserBase = () => {
|
|
1771
|
+
return this.http.get(`${this.config.apiUrl}/Security/GetCurrentUserBase`, this.config.httpSkipSpinnerOptions);
|
|
1772
|
+
};
|
|
1773
|
+
this.getCurrentUserPermissionCodes = () => {
|
|
1774
|
+
return this.http.get(`${this.config.apiUrl}/Security/GetCurrentUserPermissionCodes`, this.config.httpSkipSpinnerOptions);
|
|
1775
|
+
};
|
|
1632
1776
|
}
|
|
1633
|
-
static { this
|
|
1634
|
-
|
|
1635
|
-
type: 'string',
|
|
1636
|
-
},
|
|
1637
|
-
browserId: {
|
|
1638
|
-
type: 'string',
|
|
1639
|
-
},
|
|
1640
|
-
email: {
|
|
1641
|
-
type: 'string',
|
|
1642
|
-
},
|
|
1643
|
-
}; }
|
|
1777
|
+
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 }); }
|
|
1778
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ApiSecurityService, providedIn: 'root' }); }
|
|
1644
1779
|
}
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1780
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ApiSecurityService, decorators: [{
|
|
1781
|
+
type: Injectable,
|
|
1782
|
+
args: [{
|
|
1783
|
+
providedIn: 'root',
|
|
1784
|
+
}]
|
|
1785
|
+
}], ctorParameters: () => [{ type: i1$2.HttpClient }, { type: ConfigServiceBase }] });
|
|
1786
|
+
|
|
1787
|
+
/**
|
|
1788
|
+
* Cookie-based session auth. The access/refresh JWTs live in HttpOnly cookies set by the backend
|
|
1789
|
+
* (so JS never holds them — XSS can't exfiltrate them); requests are authenticated via
|
|
1790
|
+
* `withCredentials` (see jwtInterceptor). The readable result only carries userId/email/expiry.
|
|
1791
|
+
*/
|
|
1792
|
+
class AuthServiceBase {
|
|
1793
|
+
constructor(router, http, apiService, config, platformId) {
|
|
1794
|
+
this.router = router;
|
|
1795
|
+
this.http = http;
|
|
1796
|
+
this.apiService = apiService;
|
|
1797
|
+
this.config = config;
|
|
1798
|
+
this.platformId = platformId;
|
|
1799
|
+
this.apiUrl = this.config.apiUrl;
|
|
1800
|
+
// External-login error code captured from the bootstrap URL (?externalAuthError=expired|failed) set by the
|
|
1801
|
+
// backend's OAuth callback on failure. Captured before routing can strip it, surfaced once by the login page.
|
|
1802
|
+
this.externalAuthErrorCode = null;
|
|
1803
|
+
this._currentUserPermissionCodes = new BehaviorSubject(undefined);
|
|
1804
|
+
// The subject seeds with `undefined` (not yet loaded) and emits `null` on logout. Consumers only ever care
|
|
1805
|
+
// about a real code list, so filter both out here — subscribers get a clean `string[]`, and `firstValueFrom`
|
|
1806
|
+
// waits for the first loaded value instead of grabbing the `undefined` seed in a load race.
|
|
1807
|
+
this.currentUserPermissionCodes$ = this._currentUserPermissionCodes
|
|
1808
|
+
.asObservable()
|
|
1809
|
+
.pipe(filter((codes) => codes != null));
|
|
1810
|
+
this._user = new BehaviorSubject(undefined);
|
|
1811
|
+
this.user$ = this._user.asObservable();
|
|
1812
|
+
// Cross-tab sync. We store only marker values here (never tokens — those are HttpOnly cookies).
|
|
1813
|
+
this.storageEventListener = (event) => {
|
|
1814
|
+
if (event.storageArea === localStorage) {
|
|
1815
|
+
if (event.key === 'logout-event') {
|
|
1816
|
+
this.stopTokenTimer();
|
|
1817
|
+
this._user.next(null);
|
|
1818
|
+
this._currentUserPermissionCodes.next(null);
|
|
1819
|
+
}
|
|
1820
|
+
if (event.key === 'login-event') {
|
|
1821
|
+
this.refreshToken().subscribe();
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
};
|
|
1825
|
+
this.onAfterLogout = () => {
|
|
1826
|
+
this._currentUserPermissionCodes.next(null);
|
|
1827
|
+
this.router.navigate([this.config.loginSlug]);
|
|
1828
|
+
};
|
|
1829
|
+
this.onAfterRefreshToken = () => {
|
|
1830
|
+
this.setCurrentUserPermissionCodes().subscribe(); // after the session is re-established
|
|
1831
|
+
};
|
|
1832
|
+
this.initCompanyAuthDialogDetails = () => {
|
|
1833
|
+
return of(new InitCompanyAuthDialogDetails({
|
|
1834
|
+
image: this.config.logoPath,
|
|
1835
|
+
companyName: this.config.companyName,
|
|
1836
|
+
}));
|
|
1837
|
+
};
|
|
1838
|
+
this.onAfterNgOnDestroy = () => { };
|
|
1839
|
+
if (isPlatformBrowser(platformId)) {
|
|
1840
|
+
window.addEventListener('storage', this.storageEventListener);
|
|
1841
|
+
}
|
|
1651
1842
|
}
|
|
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;
|
|
1843
|
+
sendLoginVerificationEmail(body) {
|
|
1844
|
+
body.browserId = this.getBrowserId();
|
|
1845
|
+
return this.apiService.sendLoginVerificationEmail(body);
|
|
1667
1846
|
}
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1847
|
+
login(body) {
|
|
1848
|
+
body.browserId = this.getBrowserId();
|
|
1849
|
+
return this.apiService.loginWithCookies(body).pipe(map$1((result) => {
|
|
1850
|
+
this.handleAuthResult(result);
|
|
1851
|
+
return result;
|
|
1852
|
+
}));
|
|
1853
|
+
}
|
|
1854
|
+
// Establishes the in-memory session from a cookie auth result (login or refresh). No tokens are stored
|
|
1855
|
+
// in JS — only the user identity + the access-token expiry the backend reports (to schedule refresh).
|
|
1856
|
+
handleAuthResult(result) {
|
|
1857
|
+
this._user.next({
|
|
1858
|
+
id: result.userId,
|
|
1859
|
+
email: result.email,
|
|
1860
|
+
});
|
|
1861
|
+
this.accessTokenExpiresAt = result.accessTokenExpiresAt
|
|
1862
|
+
? new Date(result.accessTokenExpiresAt)
|
|
1863
|
+
: undefined;
|
|
1864
|
+
localStorage.setItem('login-event', 'login' + Math.random());
|
|
1865
|
+
this.startTokenTimer();
|
|
1866
|
+
this.setCurrentUserPermissionCodes().subscribe();
|
|
1867
|
+
}
|
|
1868
|
+
logout() {
|
|
1869
|
+
const browserId = this.getBrowserId();
|
|
1870
|
+
this.apiService
|
|
1871
|
+
.logoutWithCookies(browserId)
|
|
1872
|
+
.pipe(finalize(() => {
|
|
1873
|
+
this._user.next(null);
|
|
1874
|
+
localStorage.setItem('logout-event', 'logout' + Math.random());
|
|
1875
|
+
this.onAfterLogout();
|
|
1876
|
+
this.stopTokenTimer();
|
|
1877
|
+
}))
|
|
1878
|
+
.subscribe();
|
|
1879
|
+
}
|
|
1880
|
+
// Clears in-memory session state without calling the backend — used when a request comes back 401
|
|
1881
|
+
// (the backend has already cleared the auth cookies in that case).
|
|
1882
|
+
clearSession() {
|
|
1883
|
+
this.stopTokenTimer();
|
|
1884
|
+
this._user.next(null);
|
|
1885
|
+
this._currentUserPermissionCodes.next(null);
|
|
1886
|
+
localStorage.setItem('logout-event', 'logout' + Math.random());
|
|
1887
|
+
}
|
|
1888
|
+
// Called on app init and by the proactive timer. The refresh token is an HttpOnly cookie; a 401 ("no valid
|
|
1889
|
+
// session" — not logged in / expired) propagates from the interceptor and is handled by catchError below,
|
|
1890
|
+
// resolving the session to anonymous (null). map runs only for a real result, so _user is never partial.
|
|
1891
|
+
refreshToken() {
|
|
1892
|
+
const browserId = this.getBrowserId();
|
|
1893
|
+
return this.apiService.refreshTokenWithCookies(browserId).pipe(map$1((result) => {
|
|
1894
|
+
if (result) {
|
|
1895
|
+
// A re-established session makes any pending external-login error moot — drop it so it can't
|
|
1896
|
+
// surface as a stale toast on a later /login visit (e.g. after a subsequent logout).
|
|
1897
|
+
this.externalAuthErrorCode = null;
|
|
1898
|
+
this._user.next({ id: result.userId, email: result.email });
|
|
1899
|
+
this.accessTokenExpiresAt = result.accessTokenExpiresAt
|
|
1900
|
+
? new Date(result.accessTokenExpiresAt)
|
|
1901
|
+
: undefined;
|
|
1902
|
+
this.startTokenTimer();
|
|
1903
|
+
this.onAfterRefreshToken();
|
|
1904
|
+
}
|
|
1905
|
+
return result;
|
|
1906
|
+
}), catchError(() => {
|
|
1907
|
+
this._user.next(null);
|
|
1908
|
+
return of(null);
|
|
1909
|
+
}));
|
|
1910
|
+
}
|
|
1911
|
+
// Reads ?externalAuthError= from the bootstrap URL (set by the backend OAuth callback on failure) and
|
|
1912
|
+
// strips it so a manual refresh won't re-trigger the message. Called from the app initializer, before the
|
|
1913
|
+
// router runs — otherwise an unauthenticated landing on "/" redirects to /login and drops the param.
|
|
1914
|
+
captureExternalAuthError() {
|
|
1915
|
+
if (isPlatformBrowser(this.platformId) === false) {
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
const params = new URLSearchParams(window.location.search);
|
|
1919
|
+
const code = params.get('externalAuthError');
|
|
1920
|
+
if (!code) {
|
|
1921
|
+
return;
|
|
1922
|
+
}
|
|
1923
|
+
this.externalAuthErrorCode = code;
|
|
1924
|
+
params.delete('externalAuthError');
|
|
1925
|
+
const query = params.toString();
|
|
1926
|
+
history.replaceState(history.state, '', window.location.pathname + (query ? `?${query}` : '') + window.location.hash);
|
|
1927
|
+
}
|
|
1928
|
+
getBrowserId() {
|
|
1929
|
+
let browserId = localStorage.getItem(this.config.browserIdKey); // not a token — a stable per-browser id
|
|
1930
|
+
if (!browserId) {
|
|
1931
|
+
browserId = crypto.randomUUID();
|
|
1932
|
+
localStorage.setItem(this.config.browserIdKey, browserId);
|
|
1933
|
+
}
|
|
1934
|
+
return browserId;
|
|
1935
|
+
}
|
|
1936
|
+
getTokenRemainingTime() {
|
|
1937
|
+
if (!this.accessTokenExpiresAt) {
|
|
1938
|
+
return 0;
|
|
1939
|
+
}
|
|
1940
|
+
return this.accessTokenExpiresAt.getTime() - Date.now();
|
|
1941
|
+
}
|
|
1942
|
+
startTokenTimer() {
|
|
1943
|
+
const timeout = this.getTokenRemainingTime();
|
|
1944
|
+
if (timeout <= 0) {
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
this.stopTokenTimer();
|
|
1948
|
+
this.timer = of(true)
|
|
1949
|
+
.pipe(delay(timeout), tap({
|
|
1950
|
+
next: () => this.refreshToken().subscribe(),
|
|
1951
|
+
}))
|
|
1952
|
+
.subscribe();
|
|
1953
|
+
}
|
|
1954
|
+
stopTokenTimer() {
|
|
1955
|
+
this.timer?.unsubscribe();
|
|
1956
|
+
}
|
|
1957
|
+
navigateToDashboard() {
|
|
1958
|
+
this.router.navigate(['/']);
|
|
1959
|
+
}
|
|
1960
|
+
setCurrentUserPermissionCodes() {
|
|
1961
|
+
return this.apiService.getCurrentUserPermissionCodes().pipe(map$1((permissionCodes) => {
|
|
1962
|
+
this._currentUserPermissionCodes.next(permissionCodes);
|
|
1963
|
+
return permissionCodes;
|
|
1964
|
+
}));
|
|
1965
|
+
}
|
|
1966
|
+
ngOnDestroy() {
|
|
1967
|
+
if (isPlatformBrowser(this.platformId)) {
|
|
1968
|
+
window.removeEventListener('storage', this.storageEventListener);
|
|
1969
|
+
}
|
|
1970
|
+
this.onAfterNgOnDestroy();
|
|
1971
|
+
}
|
|
1972
|
+
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 }); }
|
|
1973
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: AuthServiceBase, providedIn: 'root' }); }
|
|
1974
|
+
}
|
|
1975
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: AuthServiceBase, decorators: [{
|
|
1976
|
+
type: Injectable,
|
|
1977
|
+
args: [{
|
|
1978
|
+
providedIn: 'root',
|
|
1979
|
+
}]
|
|
1980
|
+
}], ctorParameters: () => [{ type: i3$2.Router }, { type: i1$2.HttpClient }, { type: ApiSecurityService }, { type: ConfigServiceBase }, { type: Object, decorators: [{
|
|
1981
|
+
type: Inject,
|
|
1982
|
+
args: [PLATFORM_ID]
|
|
1983
|
+
}] }] });
|
|
1984
|
+
|
|
1985
|
+
class AuthCardComponent {
|
|
1986
|
+
constructor(authService) {
|
|
1987
|
+
this.authService = authService;
|
|
1988
|
+
this.companyDetailsSubscription = null;
|
|
1989
|
+
}
|
|
1990
|
+
ngOnInit() {
|
|
1991
|
+
this.companyDetailsSubscription = this.authService
|
|
1992
|
+
.initCompanyAuthDialogDetails()
|
|
1993
|
+
.subscribe((details) => {
|
|
1994
|
+
if (details != null) {
|
|
1995
|
+
this.image = details.image;
|
|
1996
|
+
this.companyName = details.companyName;
|
|
1997
|
+
}
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
ngOnDestroy() {
|
|
2001
|
+
this.companyDetailsSubscription?.unsubscribe();
|
|
2002
|
+
}
|
|
2003
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: AuthCardComponent, deps: [{ token: AuthServiceBase }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2004
|
+
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"] }] }); }
|
|
2005
|
+
}
|
|
2006
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: AuthCardComponent, decorators: [{
|
|
2007
|
+
type: Component,
|
|
2008
|
+
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" }]
|
|
2009
|
+
}], ctorParameters: () => [{ type: AuthServiceBase }] });
|
|
2010
|
+
|
|
2011
|
+
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>`;
|
|
2012
|
+
/**
|
|
2013
|
+
* Built-in default icons for external auth providers, keyed by provider code.
|
|
2014
|
+
* Values are inline data URIs — no network request, CSP entry, or asset wiring,
|
|
2015
|
+
* and they render offline. Consumers override per code via the `providerIcons`
|
|
2016
|
+
* input on ExternalLoginComponent / SpiderlyLoginComponent.
|
|
2017
|
+
*/
|
|
2018
|
+
const DEFAULT_EXTERNAL_PROVIDER_ICONS = {
|
|
2019
|
+
google: `data:image/svg+xml,${encodeURIComponent(GOOGLE_G_SVG)}`,
|
|
2020
|
+
};
|
|
2021
|
+
|
|
2022
|
+
class ExternalLoginComponent {
|
|
2023
|
+
constructor(config, authService, apiService) {
|
|
2024
|
+
this.config = config;
|
|
2025
|
+
this.authService = authService;
|
|
2026
|
+
this.apiService = apiService;
|
|
2027
|
+
/** Per-code icon overrides; unset codes fall back to DEFAULT_EXTERNAL_PROVIDER_ICONS. */
|
|
2028
|
+
this.providerIcons = {};
|
|
2029
|
+
// Config-driven: populated from Security/GetExternalProviders (backend is the single source of truth for which providers are enabled).
|
|
2030
|
+
this.externalProviders = [];
|
|
2031
|
+
}
|
|
2032
|
+
ngOnInit() {
|
|
2033
|
+
this.apiService.getExternalProviders().subscribe({
|
|
2034
|
+
next: (providers) => {
|
|
2035
|
+
this.externalProviders = providers ?? [];
|
|
2036
|
+
},
|
|
2037
|
+
// The global unauthorized interceptor already surfaces the HTTP error to the user; here we just
|
|
2038
|
+
// leave the provider buttons hidden instead of letting the error reach the global error handler.
|
|
2039
|
+
error: () => {
|
|
2040
|
+
this.externalProviders = [];
|
|
2041
|
+
},
|
|
2042
|
+
});
|
|
2043
|
+
}
|
|
2044
|
+
iconFor(code) {
|
|
2045
|
+
return this.providerIcons[code] ?? DEFAULT_EXTERNAL_PROVIDER_ICONS[code];
|
|
2046
|
+
}
|
|
2047
|
+
loginWithExternalProvider(code) {
|
|
2048
|
+
// Server-side flow (B2): hand off to the backend challenge endpoint. The backend runs the OAuth
|
|
2049
|
+
// dance, sets the session cookies, and redirects back to returnUrl.
|
|
2050
|
+
const returnUrl = this.config.frontendUrl;
|
|
2051
|
+
const browserId = this.authService.getBrowserId();
|
|
2052
|
+
window.location.href =
|
|
2053
|
+
`${this.config.apiUrl}/Security/ExternalLoginChallenge` +
|
|
2054
|
+
`?provider=${encodeURIComponent(code)}` +
|
|
2055
|
+
`&returnUrl=${encodeURIComponent(returnUrl)}` +
|
|
2056
|
+
`&browserId=${encodeURIComponent(browserId)}`;
|
|
2057
|
+
}
|
|
2058
|
+
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 }); }
|
|
2059
|
+
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"] }] }); }
|
|
2060
|
+
}
|
|
2061
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ExternalLoginComponent, decorators: [{
|
|
2062
|
+
type: Component,
|
|
2063
|
+
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" }]
|
|
2064
|
+
}], ctorParameters: () => [{ type: ConfigServiceBase }, { type: AuthServiceBase }, { type: ApiSecurityService }], propDecorators: { providerIcons: [{
|
|
2065
|
+
type: Input
|
|
2066
|
+
}] } });
|
|
2067
|
+
|
|
2068
|
+
class UserBase extends BaseEntity {
|
|
2069
|
+
static { this.typeName = 'UserBase'; }
|
|
2070
|
+
constructor({ id, email, } = {}) {
|
|
2071
|
+
super();
|
|
2072
|
+
this.id = id;
|
|
2073
|
+
this.email = email;
|
|
2074
|
+
}
|
|
2075
|
+
static { this.schema = {
|
|
2076
|
+
id: {
|
|
2077
|
+
type: 'number',
|
|
2078
|
+
},
|
|
2079
|
+
email: {
|
|
2080
|
+
type: 'string',
|
|
2081
|
+
},
|
|
2082
|
+
}; }
|
|
2083
|
+
}
|
|
2084
|
+
class AuthResult extends BaseEntity {
|
|
2085
|
+
static { this.typeName = 'AuthResult'; }
|
|
2086
|
+
constructor({ userId, email, accessToken, refreshToken, } = {}) {
|
|
2087
|
+
super();
|
|
2088
|
+
this.userId = userId;
|
|
2089
|
+
this.email = email;
|
|
2090
|
+
this.accessToken = accessToken;
|
|
2091
|
+
this.refreshToken = refreshToken;
|
|
2092
|
+
}
|
|
2093
|
+
static { this.schema = {
|
|
2094
|
+
userId: {
|
|
2095
|
+
type: 'number',
|
|
2096
|
+
},
|
|
2097
|
+
email: {
|
|
2098
|
+
type: 'string',
|
|
2099
|
+
},
|
|
2100
|
+
accessToken: {
|
|
2101
|
+
type: 'string',
|
|
2102
|
+
},
|
|
2103
|
+
refreshToken: {
|
|
2104
|
+
type: 'string',
|
|
2105
|
+
},
|
|
2106
|
+
}; }
|
|
2107
|
+
}
|
|
2108
|
+
class VerificationTokenRequest extends BaseEntity {
|
|
2109
|
+
static { this.typeName = 'VerificationTokenRequest'; }
|
|
2110
|
+
constructor({ verificationCode, browserId, email, } = {}) {
|
|
2111
|
+
super();
|
|
2112
|
+
this.verificationCode = verificationCode;
|
|
2113
|
+
this.browserId = browserId;
|
|
2114
|
+
this.email = email;
|
|
2115
|
+
}
|
|
2116
|
+
static { this.schema = {
|
|
2117
|
+
verificationCode: {
|
|
2118
|
+
type: 'string',
|
|
2119
|
+
},
|
|
2120
|
+
browserId: {
|
|
2121
|
+
type: 'string',
|
|
2122
|
+
},
|
|
2123
|
+
email: {
|
|
2124
|
+
type: 'string',
|
|
2125
|
+
},
|
|
2126
|
+
}; }
|
|
2127
|
+
}
|
|
2128
|
+
class ExternalProvider extends BaseEntity {
|
|
2129
|
+
static { this.typeName = 'ExternalProvider'; }
|
|
2130
|
+
constructor({ provider, idToken, browserId, } = {}) {
|
|
2131
|
+
super();
|
|
2132
|
+
this.provider = provider;
|
|
2133
|
+
this.idToken = idToken;
|
|
2134
|
+
this.browserId = browserId;
|
|
2135
|
+
}
|
|
2136
|
+
static { this.schema = {
|
|
2137
|
+
provider: {
|
|
2138
|
+
type: 'string',
|
|
2139
|
+
},
|
|
2140
|
+
idToken: {
|
|
2141
|
+
type: 'string',
|
|
2142
|
+
},
|
|
2143
|
+
browserId: {
|
|
2144
|
+
type: 'string',
|
|
2145
|
+
},
|
|
2146
|
+
}; }
|
|
2147
|
+
}
|
|
2148
|
+
class ExternalProviderPublic extends BaseEntity {
|
|
2149
|
+
static { this.typeName = 'ExternalProviderPublic'; }
|
|
2150
|
+
constructor({ code, authority, clientId, label, } = {}) {
|
|
2151
|
+
super();
|
|
2152
|
+
this.code = code;
|
|
2153
|
+
this.authority = authority;
|
|
2154
|
+
this.clientId = clientId;
|
|
2155
|
+
this.label = label;
|
|
2156
|
+
}
|
|
2157
|
+
static { this.schema = {
|
|
2158
|
+
code: {
|
|
2159
|
+
type: 'string',
|
|
2160
|
+
},
|
|
2161
|
+
authority: {
|
|
2162
|
+
type: 'string',
|
|
2163
|
+
},
|
|
2164
|
+
clientId: {
|
|
2165
|
+
type: 'string',
|
|
2166
|
+
},
|
|
2167
|
+
label: {
|
|
2168
|
+
type: 'string',
|
|
2169
|
+
},
|
|
2170
|
+
}; }
|
|
2171
|
+
}
|
|
2172
|
+
class UserRole extends BaseEntity {
|
|
2173
|
+
static { this.typeName = 'UserRole'; }
|
|
2174
|
+
constructor({ roleId, userId, } = {}) {
|
|
2175
|
+
super();
|
|
2176
|
+
this.roleId = roleId;
|
|
2177
|
+
this.userId = userId;
|
|
2178
|
+
}
|
|
2179
|
+
static { this.schema = {
|
|
2180
|
+
roleId: {
|
|
2181
|
+
type: 'number',
|
|
2182
|
+
},
|
|
2183
|
+
userId: {
|
|
2184
|
+
type: 'number',
|
|
1674
2185
|
},
|
|
1675
2186
|
}; }
|
|
1676
2187
|
}
|
|
@@ -2049,501 +2560,142 @@ class BaseFormComponent {
|
|
|
2049
2560
|
if (!this.saveBodyClass)
|
|
2050
2561
|
throw new SpiderlyError('You did not initialize saveBodyClass');
|
|
2051
2562
|
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
|
-
}
|
|
2563
|
+
throw new SpiderlyError('You did not initialize mainUIFormClass');
|
|
2564
|
+
let saveBody = this.parentFormGroup.getRawValue();
|
|
2565
|
+
this.onBeforeSave(saveBody);
|
|
2566
|
+
const isValid = this.baseFormService.isControlValid(this.parentFormGroup);
|
|
2567
|
+
if (isValid) {
|
|
2568
|
+
this.parentFormGroup
|
|
2569
|
+
.saveObservableMethod(saveBody)
|
|
2570
|
+
.subscribe((res) => {
|
|
2571
|
+
this.messageService.successMessage(this.successfulSaveToastDescription);
|
|
2572
|
+
if (rerouteToParentSlugAfterSave) {
|
|
2573
|
+
this.rerouteToSavedObject(undefined);
|
|
2574
|
+
}
|
|
2575
|
+
else {
|
|
2576
|
+
saveBody = this.baseFormService.mapMainUIFormToSaveBody(this.mainUIFormClass, res);
|
|
2577
|
+
this.baseFormService.initFormGroup(this.parentFormGroup, this.saveBodyClass, saveBody);
|
|
2578
|
+
const saveBodyMainDTOKey = this.baseFormService.getSaveBodyMainDTOKey(this.saveBodyClass);
|
|
2579
|
+
const savedObjectId = saveBody[saveBodyMainDTOKey]?.id;
|
|
2580
|
+
this.rerouteToSavedObject(savedObjectId); // You always need to have id, because of id == 0 and version change
|
|
2581
|
+
}
|
|
2582
|
+
this.onAfterSave();
|
|
2583
|
+
});
|
|
2584
|
+
this.onAfterSaveRequest();
|
|
2585
|
+
}
|
|
2586
|
+
else {
|
|
2587
|
+
this.baseFormService.showInvalidFieldsMessage();
|
|
2335
2588
|
}
|
|
2336
2589
|
};
|
|
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();
|
|
2590
|
+
/**
|
|
2591
|
+
* Hook that runs **before** form validation and the save request.
|
|
2592
|
+
* Use this to modify the save body or perform any pre-save logic (e.g., transforming data, setting computed fields).
|
|
2593
|
+
*
|
|
2594
|
+
* @param saveBody - The current save body built from the form's raw value. Mutate it directly to change what gets sent to the server.
|
|
2595
|
+
*
|
|
2596
|
+
* @example
|
|
2597
|
+
* ```ts
|
|
2598
|
+
* onBeforeSave = (saveBody?: ProductSaveBody) => {
|
|
2599
|
+
* saveBody.productDTO.fullName = saveBody.productDTO.firstName + ' ' + saveBody.productDTO.lastName;
|
|
2600
|
+
* };
|
|
2601
|
+
* ```
|
|
2602
|
+
*/
|
|
2603
|
+
this.onBeforeSave = (saveBody) => { };
|
|
2604
|
+
/**
|
|
2605
|
+
* Hook that runs **after** a successful save response is received.
|
|
2606
|
+
* Use this for post-save side effects (e.g., refreshing related data, showing additional notifications).
|
|
2607
|
+
*
|
|
2608
|
+
* @example
|
|
2609
|
+
* ```ts
|
|
2610
|
+
* onAfterSave = () => {
|
|
2611
|
+
* this.loadRelatedProducts();
|
|
2612
|
+
* };
|
|
2613
|
+
* ```
|
|
2614
|
+
*/
|
|
2615
|
+
this.onAfterSave = () => { };
|
|
2616
|
+
/**
|
|
2617
|
+
* Hook that runs immediately **after** the save HTTP request is sent, but **before** the response arrives.
|
|
2618
|
+
* Use this for side effects that should happen as soon as the request is dispatched (e.g., disabling UI elements, starting a loading indicator).
|
|
2619
|
+
*
|
|
2620
|
+
* @example
|
|
2621
|
+
* ```ts
|
|
2622
|
+
* onAfterSaveRequest = () => {
|
|
2623
|
+
* this.isSaving = true;
|
|
2624
|
+
* };
|
|
2625
|
+
* ```
|
|
2626
|
+
*/
|
|
2627
|
+
this.onAfterSaveRequest = () => { };
|
|
2409
2628
|
}
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2629
|
+
ngOnInit() { }
|
|
2630
|
+
/**
|
|
2631
|
+
* Handles navigation after a successful save.
|
|
2632
|
+
* Override this to customize the post-save navigation behavior.
|
|
2633
|
+
* By default, navigates to the parent URL when `rerouteId` is not provided, or to the saved object's URL otherwise.
|
|
2634
|
+
*
|
|
2635
|
+
* @param rerouteId - The ID of the saved object, used to build the target URL. When not provided, navigates to the parent URL.
|
|
2636
|
+
*
|
|
2637
|
+
* @example
|
|
2638
|
+
* ```ts
|
|
2639
|
+
* // Override to navigate to a custom route after save
|
|
2640
|
+
* override rerouteToSavedObject(rerouteId: number | string): void {
|
|
2641
|
+
* this.router.navigateByUrl(`/products/${rerouteId}/details`);
|
|
2642
|
+
* }
|
|
2643
|
+
* ```
|
|
2644
|
+
*/
|
|
2645
|
+
rerouteToSavedObject(rerouteId) {
|
|
2646
|
+
if (rerouteId == null) {
|
|
2647
|
+
const currentUrl = this.router.url;
|
|
2648
|
+
const parentUrl = getParentUrl(currentUrl);
|
|
2649
|
+
this.router.navigateByUrl(parentUrl);
|
|
2650
|
+
return;
|
|
2415
2651
|
}
|
|
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());
|
|
2652
|
+
const segments = this.router.url.split('/');
|
|
2653
|
+
segments[segments.length - 1] = rerouteId.toString();
|
|
2654
|
+
const newUrl = segments.join('/');
|
|
2655
|
+
this.router.navigateByUrl(newUrl);
|
|
2442
2656
|
}
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2657
|
+
//#endregion
|
|
2658
|
+
//#region Model List
|
|
2659
|
+
getFormArrayControlByIndex(formControlName, formArray, index, filter) {
|
|
2660
|
+
// if(formArray.controlNamesFromHtml.findIndex(x => x === formControlName) === -1)
|
|
2661
|
+
// formArray.controlNamesFromHtml.push(formControlName);
|
|
2662
|
+
let filteredFormGroups;
|
|
2663
|
+
if (filter) {
|
|
2664
|
+
filteredFormGroups = filter(formArray.controls);
|
|
2448
2665
|
}
|
|
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;
|
|
2666
|
+
else {
|
|
2667
|
+
return formArray.controls[index].controls[formControlName];
|
|
2459
2668
|
}
|
|
2460
|
-
|
|
2461
|
-
const expires = new Date(jwtToken.exp * 1000);
|
|
2462
|
-
return expires.getTime() - Date.now();
|
|
2669
|
+
return filteredFormGroups[index]?.controls[formControlName]; // FT: Don't change this. It's always possible that change detection occurs before something.
|
|
2463
2670
|
}
|
|
2464
|
-
|
|
2465
|
-
if
|
|
2466
|
-
|
|
2671
|
+
getFormArrayControls(formControlName, formArray, filter) {
|
|
2672
|
+
// if(formArray.controlNamesFromHtml.findIndex(x => x === formControlName) === -1)
|
|
2673
|
+
// formArray.controlNamesFromHtml.push(formControlName);
|
|
2674
|
+
let filteredFormGroups;
|
|
2675
|
+
if (filter) {
|
|
2676
|
+
filteredFormGroups = filter(formArray.controls);
|
|
2467
2677
|
}
|
|
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);
|
|
2678
|
+
else {
|
|
2679
|
+
return formArray.controls.map((x) => x.controls[formControlName]);
|
|
2493
2680
|
}
|
|
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();
|
|
2681
|
+
return filteredFormGroups.map((x) => x.controls[formControlName]);
|
|
2518
2682
|
}
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
if (
|
|
2524
|
-
|
|
2525
|
-
this.companyName = initCompanyAuthDialogDetails.companyName;
|
|
2526
|
-
this.onCompanyNameChange.next(this.companyName);
|
|
2683
|
+
removeFormControlsFromTheFormArray(formArray, indexes) {
|
|
2684
|
+
// Sort indexes in descending order to avoid index shifts when removing controls
|
|
2685
|
+
const sortedIndexes = indexes.sort((a, b) => b - a);
|
|
2686
|
+
sortedIndexes.forEach((index) => {
|
|
2687
|
+
if (index >= 0 && index < formArray.length) {
|
|
2688
|
+
formArray.removeAt(index);
|
|
2527
2689
|
}
|
|
2528
2690
|
});
|
|
2529
2691
|
}
|
|
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"] }] }); }
|
|
2692
|
+
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 }); }
|
|
2693
|
+
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
2694
|
}
|
|
2541
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type:
|
|
2695
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: BaseFormComponent, decorators: [{
|
|
2542
2696
|
type: Component,
|
|
2543
|
-
args: [{ selector: '
|
|
2544
|
-
}], ctorParameters: () => [{ type:
|
|
2545
|
-
type: Output
|
|
2546
|
-
}] } });
|
|
2697
|
+
args: [{ selector: 'base-form', template: '', standalone: false }]
|
|
2698
|
+
}], 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
2699
|
|
|
2548
2700
|
class PanelBodyComponent {
|
|
2549
2701
|
constructor() {
|
|
@@ -2574,6 +2726,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
|
|
|
2574
2726
|
class PanelHeaderComponent {
|
|
2575
2727
|
constructor(translocoService) {
|
|
2576
2728
|
this.translocoService = translocoService;
|
|
2729
|
+
/** Whether the header icon is shown. Defaults to `true`. */
|
|
2577
2730
|
this.showIcon = true;
|
|
2578
2731
|
}
|
|
2579
2732
|
ngOnInit() {
|
|
@@ -2624,8 +2777,11 @@ class SpiderlyPanelComponent {
|
|
|
2624
2777
|
this.toggleable = false;
|
|
2625
2778
|
this.toggler = 'icon';
|
|
2626
2779
|
this.collapsed = false;
|
|
2780
|
+
/** Whether the CRUD context-menu icon is shown. Defaults to `true`. */
|
|
2627
2781
|
this.showCrudMenu = true;
|
|
2782
|
+
/** Whether a remove/delete icon is shown. Defaults to `false`. */
|
|
2628
2783
|
this.showRemoveIcon = false;
|
|
2784
|
+
/** Whether the panel header is rendered. Defaults to `true`. */
|
|
2629
2785
|
this.showPanelHeader = true;
|
|
2630
2786
|
this.onMenuIconClick = new EventEmitter();
|
|
2631
2787
|
this.onRemoveIconClick = new EventEmitter();
|
|
@@ -2793,8 +2949,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
|
|
|
2793
2949
|
type: Input
|
|
2794
2950
|
}] } });
|
|
2795
2951
|
|
|
2796
|
-
class
|
|
2797
|
-
constructor(differs, http, messageService, changeDetectorRef, router, route, translocoService, baseFormService, authService
|
|
2952
|
+
class SpiderlyLoginComponent extends BaseFormComponent {
|
|
2953
|
+
constructor(differs, http, messageService, changeDetectorRef, router, route, translocoService, baseFormService, authService) {
|
|
2798
2954
|
super(differs, http, messageService, changeDetectorRef, router, route, translocoService, baseFormService);
|
|
2799
2955
|
this.differs = differs;
|
|
2800
2956
|
this.http = http;
|
|
@@ -2805,21 +2961,31 @@ class LoginComponent extends BaseFormComponent {
|
|
|
2805
2961
|
this.translocoService = translocoService;
|
|
2806
2962
|
this.baseFormService = baseFormService;
|
|
2807
2963
|
this.authService = authService;
|
|
2808
|
-
this.config = config;
|
|
2809
2964
|
this.loginFormGroup = new SpiderlyFormGroup({});
|
|
2810
2965
|
this.showEmailSentDialog = false;
|
|
2966
|
+
/** Per-code provider icon overrides, forwarded to <spiderly-external-login>. */
|
|
2967
|
+
this.providerIcons = {};
|
|
2811
2968
|
}
|
|
2812
2969
|
ngOnInit() {
|
|
2813
2970
|
this.initLoginFormGroup(new Login({}));
|
|
2971
|
+
this.showExternalAuthErrorIfPresent();
|
|
2972
|
+
}
|
|
2973
|
+
// Surface a friendly message when the server-side external login bounced back with an error (captured from
|
|
2974
|
+
// the URL at bootstrap by AuthServiceBase). "expired" = the user lingered on the provider's account picker.
|
|
2975
|
+
showExternalAuthErrorIfPresent() {
|
|
2976
|
+
const code = this.authService.externalAuthErrorCode;
|
|
2977
|
+
if (!code) {
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
this.authService.externalAuthErrorCode = null; // show once
|
|
2981
|
+
const messageKey = code === 'expired' ? 'ExternalLoginExpiredDetails' : 'ExternalLoginFailedDetails';
|
|
2982
|
+
this.messageService.warningMessage(this.translocoService.translate(messageKey));
|
|
2814
2983
|
}
|
|
2815
2984
|
initLoginFormGroup(model) {
|
|
2816
2985
|
this.baseFormService.initFormGroup(this.loginFormGroup, Login, model, [
|
|
2817
2986
|
'email',
|
|
2818
2987
|
]);
|
|
2819
2988
|
}
|
|
2820
|
-
companyNameChange(companyName) {
|
|
2821
|
-
this.companyName = companyName;
|
|
2822
|
-
}
|
|
2823
2989
|
sendLoginVerificationEmail() {
|
|
2824
2990
|
let isFormGroupValid = this.baseFormService.isControlValid(this.loginFormGroup);
|
|
2825
2991
|
if (isFormGroupValid == false) {
|
|
@@ -2835,20 +3001,23 @@ class LoginComponent extends BaseFormComponent {
|
|
|
2835
3001
|
this.showEmailSentDialog = true;
|
|
2836
3002
|
});
|
|
2837
3003
|
}
|
|
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:
|
|
3004
|
+
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 }); }
|
|
3005
|
+
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 <!--\n General projection slot for consumer-supplied login extras (a bot-challenge widget, a notice,\n an extra field, \u2026). Rendered in BOTH the email-entry and code-verification states \u2014 outside the\n showEmailSentDialog toggle \u2014 so projected content survives the email \u2192 resend flow (e.g. a\n single-use challenge widget that must stay mounted to issue a fresh token on resend).\n Project with the `loginExtra` attribute: <spiderly-login><my-widget loginExtra/></spiderly-login>.\n -->\n <ng-content select=\"[loginExtra]\"></ng-content>\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
3006
|
}
|
|
2841
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type:
|
|
3007
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyLoginComponent, decorators: [{
|
|
2842
3008
|
type: Component,
|
|
2843
|
-
args: [{ selector: '
|
|
3009
|
+
args: [{ selector: 'spiderly-login', imports: [
|
|
2844
3010
|
CommonModule,
|
|
2845
3011
|
ReactiveFormsModule,
|
|
2846
|
-
|
|
3012
|
+
AuthCardComponent,
|
|
3013
|
+
ExternalLoginComponent,
|
|
2847
3014
|
SpiderlyControlsModule,
|
|
2848
3015
|
LoginVerificationComponent,
|
|
2849
3016
|
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 }, {
|
|
3017
|
+
], 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 <!--\n General projection slot for consumer-supplied login extras (a bot-challenge widget, a notice,\n an extra field, \u2026). Rendered in BOTH the email-entry and code-verification states \u2014 outside the\n showEmailSentDialog toggle \u2014 so projected content survives the email \u2192 resend flow (e.g. a\n single-use challenge widget that must stay mounted to issue a fresh token on resend).\n Project with the `loginExtra` attribute: <spiderly-login><my-widget loginExtra/></spiderly-login>.\n -->\n <ng-content select=\"[loginExtra]\"></ng-content>\n }\n</ng-container>\n" }]
|
|
3018
|
+
}], 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: [{
|
|
3019
|
+
type: Input
|
|
3020
|
+
}] } });
|
|
2852
3021
|
|
|
2853
3022
|
class CardSkeletonComponent {
|
|
2854
3023
|
constructor() {
|
|
@@ -2893,6 +3062,7 @@ class IndexCardComponent {
|
|
|
2893
3062
|
constructor(formBuilder) {
|
|
2894
3063
|
this.formBuilder = formBuilder;
|
|
2895
3064
|
this.header = '';
|
|
3065
|
+
/** Whether the CRUD context-menu icon is shown. Defaults to `true`. */
|
|
2896
3066
|
this.showCrudMenu = true;
|
|
2897
3067
|
this.onMenuIconClick = new EventEmitter();
|
|
2898
3068
|
this.onRemoveIconClick = new EventEmitter();
|
|
@@ -2934,6 +3104,7 @@ class InfoCardComponent {
|
|
|
2934
3104
|
constructor(formBuilder) {
|
|
2935
3105
|
this.formBuilder = formBuilder;
|
|
2936
3106
|
this.header = '';
|
|
3107
|
+
/** Whether the small icon variant is shown. Defaults to `true`. */
|
|
2937
3108
|
this.showSmallIcon = true;
|
|
2938
3109
|
this.icon = 'pi pi-info-circle';
|
|
2939
3110
|
this.textColor = '';
|
|
@@ -2976,22 +3147,16 @@ class LayoutServiceBase {
|
|
|
2976
3147
|
inputStyle: 'outlined',
|
|
2977
3148
|
menuMode: 'static',
|
|
2978
3149
|
colorScheme: 'light',
|
|
2979
|
-
theme: 'lara-light-indigo',
|
|
2980
|
-
scale: 14,
|
|
2981
|
-
color: `var(--p-primary-color)`,
|
|
2982
3150
|
};
|
|
2983
3151
|
this.state = {
|
|
2984
3152
|
staticMenuDesktopInactive: false,
|
|
2985
3153
|
overlayMenuActive: false,
|
|
2986
3154
|
profileSidebarVisible: false,
|
|
2987
3155
|
profileDropdownSidebarVisible: false,
|
|
2988
|
-
configSidebarVisible: false,
|
|
2989
3156
|
staticMenuMobileActive: false,
|
|
2990
3157
|
menuHoverActive: false,
|
|
2991
3158
|
};
|
|
2992
|
-
this.configUpdate = new Subject();
|
|
2993
3159
|
this.overlayOpen = new Subject();
|
|
2994
|
-
this.configUpdate$ = this.configUpdate.asObservable();
|
|
2995
3160
|
this.overlayOpen$ = this.overlayOpen.asObservable();
|
|
2996
3161
|
//#region Top Bar
|
|
2997
3162
|
this.initTopBarData = () => {
|
|
@@ -3042,9 +3207,6 @@ class LayoutServiceBase {
|
|
|
3042
3207
|
this.overlayOpen.next(null);
|
|
3043
3208
|
}
|
|
3044
3209
|
}
|
|
3045
|
-
showConfigSidebar() {
|
|
3046
|
-
this.state.configSidebarVisible = true;
|
|
3047
|
-
}
|
|
3048
3210
|
isOverlay() {
|
|
3049
3211
|
return this.layoutConfig.menuMode === 'overlay';
|
|
3050
3212
|
}
|
|
@@ -3054,9 +3216,6 @@ class LayoutServiceBase {
|
|
|
3054
3216
|
isMobile() {
|
|
3055
3217
|
return !this.isDesktop();
|
|
3056
3218
|
}
|
|
3057
|
-
onConfigUpdate() {
|
|
3058
|
-
this.configUpdate.next(this.layoutConfig);
|
|
3059
|
-
}
|
|
3060
3219
|
//#endregion
|
|
3061
3220
|
ngOnDestroy() {
|
|
3062
3221
|
if (this.userSubscription) {
|
|
@@ -3297,6 +3456,7 @@ class ProfileAvatarComponent {
|
|
|
3297
3456
|
this.config = config;
|
|
3298
3457
|
this.isSideMenuLayout = true;
|
|
3299
3458
|
this.routeOnLargeProfileAvatarClick = true;
|
|
3459
|
+
/** Whether the login button is shown when no user is signed in. Defaults to `true`. */
|
|
3300
3460
|
this.showLoginButton = true;
|
|
3301
3461
|
this.routeToLoginPage = true;
|
|
3302
3462
|
this.loginButtonOutlined = false;
|
|
@@ -3719,7 +3879,9 @@ class SpiderlyDataTableComponent {
|
|
|
3719
3879
|
this.locale = locale;
|
|
3720
3880
|
this.destroy$ = new Subject();
|
|
3721
3881
|
this.tableIcon = 'pi pi-list';
|
|
3722
|
-
|
|
3882
|
+
/** Whether the paginator is shown. Pass only when `hasLazyLoad === false`. Defaults to `true`. */
|
|
3883
|
+
this.showPaginator = true;
|
|
3884
|
+
/** Whether the table is wrapped in a card container. Defaults to `false`. */
|
|
3723
3885
|
this.showCardWrapper = false;
|
|
3724
3886
|
this.readonly = false;
|
|
3725
3887
|
this.idField = 'id';
|
|
@@ -3737,8 +3899,11 @@ class SpiderlyDataTableComponent {
|
|
|
3737
3899
|
this.onIsAllSelectedChange = new EventEmitter();
|
|
3738
3900
|
this.matchModeDateOptions = [];
|
|
3739
3901
|
this.matchModeNumberOptions = [];
|
|
3902
|
+
/** Whether the "Add" button is shown. Defaults to `true`. */
|
|
3740
3903
|
this.showAddButton = true;
|
|
3904
|
+
/** Whether the "Export to Excel" button is shown. Defaults to `true`. */
|
|
3741
3905
|
this.showExportToExcelButton = true;
|
|
3906
|
+
/** Whether the reload-table button is shown. Defaults to `false`. */
|
|
3742
3907
|
this.showReloadTableButton = false;
|
|
3743
3908
|
this.hasLazyLoad = true;
|
|
3744
3909
|
/** 'session' persists across refresh only; 'local' persists indefinitely. */
|
|
@@ -4388,12 +4553,14 @@ class SpiderlyDataViewComponent {
|
|
|
4388
4553
|
this.rows = 10;
|
|
4389
4554
|
this.filters = [];
|
|
4390
4555
|
this.onLazyLoad = new EventEmitter();
|
|
4556
|
+
/** Whether the data view is wrapped in a card container. Defaults to `true`. */
|
|
4391
4557
|
this.showCardWrapper = true;
|
|
4392
4558
|
/**
|
|
4393
4559
|
* Whether to display additional data on the right side of the paginator.
|
|
4394
4560
|
* Defaults to `false`.
|
|
4395
4561
|
*/
|
|
4396
4562
|
this.showPaginatorRightData = false;
|
|
4563
|
+
/** Whether the total records count is shown. Defaults to `false`. */
|
|
4397
4564
|
this.showTotalRecordsNumber = false;
|
|
4398
4565
|
this.applyFiltersIcon = 'pi pi-filter';
|
|
4399
4566
|
this.clearFiltersIcon = 'pi pi-filter-slash';
|
|
@@ -4762,18 +4929,13 @@ class AuthGuard {
|
|
|
4762
4929
|
return this.checkAuth();
|
|
4763
4930
|
}
|
|
4764
4931
|
checkAuth() {
|
|
4765
|
-
return this.authService.user$.pipe(
|
|
4932
|
+
return this.authService.user$.pipe(filter$1((user) => user !== undefined), // wait until the session is resolved (undefined = still loading)
|
|
4933
|
+
take(1), map((user) => {
|
|
4766
4934
|
if (user) {
|
|
4767
4935
|
return true;
|
|
4768
4936
|
}
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
// this.router.navigate(['login'], {
|
|
4772
|
-
// queryParams: { returnUrl },
|
|
4773
|
-
// });
|
|
4774
|
-
this.router.navigate([this.config.loginSlug]);
|
|
4775
|
-
return false;
|
|
4776
|
-
}
|
|
4937
|
+
this.router.navigate([this.config.loginSlug]);
|
|
4938
|
+
return false;
|
|
4777
4939
|
}));
|
|
4778
4940
|
}
|
|
4779
4941
|
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 +4956,13 @@ class NotAuthGuard {
|
|
|
4794
4956
|
return this.checkAuth();
|
|
4795
4957
|
}
|
|
4796
4958
|
checkAuth() {
|
|
4797
|
-
return this.authService.user$.pipe(
|
|
4959
|
+
return this.authService.user$.pipe(filter$1((user) => user !== undefined), // wait until the session is resolved (undefined = still loading)
|
|
4960
|
+
take(1), map((user) => {
|
|
4798
4961
|
if (user) {
|
|
4799
4962
|
this.authService.navigateToDashboard();
|
|
4800
4963
|
return false;
|
|
4801
4964
|
}
|
|
4802
|
-
|
|
4803
|
-
return true;
|
|
4804
|
-
}
|
|
4965
|
+
return true;
|
|
4805
4966
|
}));
|
|
4806
4967
|
}
|
|
4807
4968
|
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 +5070,90 @@ const convertToDate = (object, parent, key) => {
|
|
|
4909
5070
|
}
|
|
4910
5071
|
};
|
|
4911
5072
|
|
|
5073
|
+
// Cookie-based auth: the session JWTs are HttpOnly cookies, so we just send credentials on API calls.
|
|
5074
|
+
// The browser attaches/refreshes the cookies; JS never holds the tokens (XSS-safe).
|
|
5075
|
+
//
|
|
5076
|
+
// CSRF: state-changing requests (POST/PUT/DELETE/PATCH) authenticated via cookie must include the
|
|
5077
|
+
// X-CSRF header, otherwise Spiderly.Shared/Attributes/AuthGuardAttribute.cs returns 403 Forbidden
|
|
5078
|
+
// (the server-side check was added in commit 92f238d but the matching client-side header was never
|
|
5079
|
+
// emitted, so every cookie-authed write was failing in the admin). The check is presence-only —
|
|
5080
|
+
// any non-empty value works — and the protection comes from the fact that a cross-origin form
|
|
5081
|
+
// submission cannot set custom request headers without a CORS preflight.
|
|
5082
|
+
const SAFE_HTTP_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
|
|
4912
5083
|
const jwtInterceptor = (req, next) => {
|
|
4913
5084
|
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
5085
|
const isApiUrl = req.url.startsWith(config.apiUrl);
|
|
4920
|
-
if (
|
|
5086
|
+
if (isApiUrl) {
|
|
5087
|
+
const isStateChanging = !SAFE_HTTP_METHODS.has(req.method.toUpperCase());
|
|
4921
5088
|
req = req.clone({
|
|
4922
|
-
|
|
5089
|
+
withCredentials: true,
|
|
5090
|
+
...(isStateChanging && { setHeaders: { 'X-CSRF': '1' } }),
|
|
4923
5091
|
});
|
|
4924
5092
|
}
|
|
4925
5093
|
return next(req);
|
|
4926
5094
|
};
|
|
4927
5095
|
|
|
5096
|
+
/**
|
|
5097
|
+
* Owns cross-cutting HTTP-error UX: shows the right message and, on an expired session, clears auth — then
|
|
5098
|
+
* RETHROWS. Errors stay errors: callers run only their success path, and an unhandled HttpErrorResponse that
|
|
5099
|
+
* reaches the global ErrorHandler is intentionally ignored there (HTTP-error UX lives here). This interceptor
|
|
5100
|
+
* must never convert an error into a value — doing so makes callers treat failures as data.
|
|
5101
|
+
*/
|
|
4928
5102
|
const unauthorizedInterceptor = (req, next) => {
|
|
4929
5103
|
const messageService = inject(SpiderlyMessageService);
|
|
4930
5104
|
const translocoService = inject(TranslocoService);
|
|
4931
5105
|
const config = inject(ConfigServiceBase);
|
|
4932
5106
|
const authService = inject(AuthServiceBase);
|
|
4933
|
-
const
|
|
5107
|
+
const reactToError = (err, request) => {
|
|
4934
5108
|
if (!config.production) {
|
|
4935
5109
|
console.error(err);
|
|
4936
5110
|
}
|
|
4937
5111
|
let errorResponse = err.error;
|
|
4938
|
-
if (request.responseType
|
|
4939
|
-
|
|
4940
|
-
|
|
5112
|
+
if (request.responseType !== 'json' && typeof err.error === 'string') {
|
|
5113
|
+
try {
|
|
5114
|
+
errorResponse = JSON.parse(err.error);
|
|
5115
|
+
}
|
|
5116
|
+
catch {
|
|
5117
|
+
errorResponse = null;
|
|
5118
|
+
}
|
|
5119
|
+
}
|
|
5120
|
+
if (err.status === 0) {
|
|
5121
|
+
// Server unreachable; defer so the message isn't lost during a shutdown/refresh race.
|
|
4941
5122
|
setTimeout(() => {
|
|
4942
|
-
// Had problem when the server is shut down, and try to refresh token, warning message didn't appear
|
|
4943
5123
|
messageService.warningMessage(translocoService.translate('ServerLostConnectionDetails'), translocoService.translate('ServerLostConnectionTitle'));
|
|
4944
5124
|
}, 100);
|
|
4945
|
-
return of(err.message);
|
|
4946
5125
|
}
|
|
4947
|
-
else if (err.status
|
|
4948
|
-
messageService.warningMessage(errorResponse
|
|
4949
|
-
translocoService.translate('BadRequestDetails'), translocoService.translate('Warning'));
|
|
4950
|
-
return of(err.message);
|
|
5126
|
+
else if (err.status === 400) {
|
|
5127
|
+
messageService.warningMessage(errorResponse?.message ?? translocoService.translate('BadRequestDetails'), translocoService.translate('Warning'));
|
|
4951
5128
|
}
|
|
4952
|
-
else if (err.status
|
|
5129
|
+
else if (err.status === 401) {
|
|
4953
5130
|
if (errorResponse?.errorCode === ApiErrorCodes.InvalidToken) {
|
|
4954
|
-
authService.
|
|
4955
|
-
|
|
5131
|
+
authService.clearSession(); // expired/invalid session — drop it; guards send the user to login
|
|
5132
|
+
}
|
|
5133
|
+
else {
|
|
5134
|
+
messageService.warningMessage(errorResponse?.message ?? translocoService.translate('LoginRequired'), translocoService.translate('Warning'));
|
|
4956
5135
|
}
|
|
4957
|
-
messageService.warningMessage(errorResponse?.message ?? translocoService.translate('LoginRequired'), translocoService.translate('Warning'));
|
|
4958
|
-
return of(err.message);
|
|
4959
5136
|
}
|
|
4960
|
-
else if (err.status
|
|
5137
|
+
else if (err.status === 403) {
|
|
4961
5138
|
messageService.warningMessage(translocoService.translate('PermissionErrorDetails'), translocoService.translate('PermissionErrorTitle'));
|
|
4962
|
-
return of(err.message);
|
|
4963
5139
|
}
|
|
4964
|
-
else if (err.status
|
|
5140
|
+
else if (err.status === 404) {
|
|
4965
5141
|
messageService.warningMessage(translocoService.translate('NotFoundDetails'), translocoService.translate('NotFoundTitle'));
|
|
4966
|
-
return of(err.message);
|
|
4967
5142
|
}
|
|
4968
5143
|
else {
|
|
4969
5144
|
messageService.errorMessage(translocoService.translate('UnexpectedErrorDetails'), translocoService.translate('UnexpectedErrorTitle'));
|
|
4970
|
-
return of(err.message);
|
|
4971
5145
|
}
|
|
4972
5146
|
};
|
|
4973
5147
|
return next(req).pipe(catchError((err) => {
|
|
4974
|
-
|
|
5148
|
+
reactToError(err, req);
|
|
5149
|
+
return throwError(() => err);
|
|
4975
5150
|
}));
|
|
4976
5151
|
};
|
|
4977
5152
|
|
|
4978
5153
|
function authInitializer(authService, platformId) {
|
|
4979
5154
|
if (isPlatformBrowser(platformId)) {
|
|
4980
5155
|
return () => {
|
|
5156
|
+
authService.captureExternalAuthError(); // before the router can strip ?externalAuthError on the /login redirect
|
|
4981
5157
|
return authService.refreshToken();
|
|
4982
5158
|
};
|
|
4983
5159
|
}
|
|
@@ -5028,5 +5204,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
|
|
|
5028
5204
|
* Generated bundle index. Do not edit.
|
|
5029
5205
|
*/
|
|
5030
5206
|
|
|
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,
|
|
5207
|
+
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
5208
|
//# sourceMappingURL=spiderly.mjs.map
|