@masterteam/customization 0.0.14 → 0.0.16
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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, Injectable, computed, input, signal, effect, Component } from '@angular/core';
|
|
2
|
+
import { inject, Injectable, computed, input, signal, effect, Component, output } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/forms';
|
|
4
4
|
import { FormControl, ReactiveFormsModule, FormsModule } from '@angular/forms';
|
|
5
5
|
import { TranslocoDirective } from '@jsverse/transloco';
|
|
@@ -8,7 +8,7 @@ import { TextField } from '@masterteam/components/text-field';
|
|
|
8
8
|
import { ToggleField } from '@masterteam/components/toggle-field';
|
|
9
9
|
import { Button } from '@masterteam/components/button';
|
|
10
10
|
import { Tabs } from '@masterteam/components/tabs';
|
|
11
|
-
import { EntitiesManage } from '@masterteam/components/entities';
|
|
11
|
+
import { buildDisplayEntities, isLeafDetailsSyntheticKey, LEAF_DETAILS_KEY_SEPARATOR, EntitiesManage } from '@masterteam/components/entities';
|
|
12
12
|
import { ModalService } from '@masterteam/components/modal';
|
|
13
13
|
import { Icon } from '@masterteam/icons';
|
|
14
14
|
import { Skeleton } from 'primeng/skeleton';
|
|
@@ -25,6 +25,7 @@ const BORDER_VIEW_TYPES = [
|
|
|
25
25
|
'Currency',
|
|
26
26
|
'Date',
|
|
27
27
|
'DateTime',
|
|
28
|
+
'LeafDetails',
|
|
28
29
|
];
|
|
29
30
|
/** ViewTypes that support user-specific toggles */
|
|
30
31
|
const USER_VIEW_TYPES = ['User'];
|
|
@@ -38,6 +39,7 @@ const EDITABLE_VIEW_TYPES = [
|
|
|
38
39
|
'Currency',
|
|
39
40
|
'Date',
|
|
40
41
|
'DateTime',
|
|
42
|
+
'LeafDetails',
|
|
41
43
|
'Percentage',
|
|
42
44
|
'Status',
|
|
43
45
|
'Checkbox',
|
|
@@ -211,30 +213,23 @@ let CustomizationState = class CustomizationState extends CrudStateBase {
|
|
|
211
213
|
bulkReplaceConfigurations(ctx, action) {
|
|
212
214
|
const state = ctx.getState();
|
|
213
215
|
const contextKey = this.buildContextKey(state);
|
|
216
|
+
const previousConfigurations = state.configurations;
|
|
217
|
+
const nextConfigurations = this.toDisplayConfigurations(contextKey, action.items);
|
|
214
218
|
const req$ = this.http.put(`${this.displayConfigUrl}/bulk-replace`, {
|
|
215
219
|
contextKey,
|
|
216
220
|
items: action.items,
|
|
217
221
|
});
|
|
222
|
+
ctx.patchState({
|
|
223
|
+
configurations: nextConfigurations,
|
|
224
|
+
});
|
|
218
225
|
return handleApiRequest({
|
|
219
226
|
ctx,
|
|
220
227
|
key: CustomizationActionKey.BulkReplaceConfigurations,
|
|
221
228
|
request$: req$,
|
|
222
|
-
onSuccess: () =>
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
for (const da of item.displayAreas) {
|
|
227
|
-
newConfigs.push({
|
|
228
|
-
contextKey,
|
|
229
|
-
areaKey: da.areaKey,
|
|
230
|
-
propertyKey: item.propertyKey,
|
|
231
|
-
order: da.order,
|
|
232
|
-
configuration: da.configuration,
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return { configurations: newConfigs };
|
|
237
|
-
},
|
|
229
|
+
onSuccess: () => undefined,
|
|
230
|
+
onError: () => ({
|
|
231
|
+
configurations: previousConfigurations,
|
|
232
|
+
}),
|
|
238
233
|
});
|
|
239
234
|
}
|
|
240
235
|
// ============================================================================
|
|
@@ -250,6 +245,21 @@ let CustomizationState = class CustomizationState extends CrudStateBase {
|
|
|
250
245
|
}
|
|
251
246
|
return contextParts.join('/');
|
|
252
247
|
}
|
|
248
|
+
toDisplayConfigurations(contextKey, items) {
|
|
249
|
+
const configurations = [];
|
|
250
|
+
for (const item of items) {
|
|
251
|
+
for (const displayArea of item.displayAreas) {
|
|
252
|
+
configurations.push({
|
|
253
|
+
contextKey,
|
|
254
|
+
areaKey: displayArea.areaKey,
|
|
255
|
+
propertyKey: item.propertyKey,
|
|
256
|
+
order: displayArea.order,
|
|
257
|
+
configuration: displayArea.configuration,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return configurations;
|
|
262
|
+
}
|
|
253
263
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: CustomizationState, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
254
264
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: CustomizationState });
|
|
255
265
|
};
|
|
@@ -402,6 +412,7 @@ class PropertyConfigDrawer {
|
|
|
402
412
|
// ─── Computed visibility flags ───
|
|
403
413
|
isBorderViewType = computed(() => BORDER_VIEW_TYPES.includes(this.viewType()), ...(ngDevMode ? [{ debugName: "isBorderViewType" }] : /* istanbul ignore next */ []));
|
|
404
414
|
isUserViewType = computed(() => USER_VIEW_TYPES.includes(this.viewType()), ...(ngDevMode ? [{ debugName: "isUserViewType" }] : /* istanbul ignore next */ []));
|
|
415
|
+
isLeafDetailsViewType = computed(() => this.viewType() === 'LeafDetails', ...(ngDevMode ? [{ debugName: "isLeafDetailsViewType" }] : /* istanbul ignore next */ []));
|
|
405
416
|
/** True when the document direction is RTL */
|
|
406
417
|
isRtl = signal(this.doc.documentElement.getAttribute('dir') === 'rtl', ...(ngDevMode ? [{ debugName: "isRtl" }] : /* istanbul ignore next */ []));
|
|
407
418
|
/** Align-end icon: right in LTR, left in RTL */
|
|
@@ -425,19 +436,27 @@ class PropertyConfigDrawer {
|
|
|
425
436
|
.configurations()
|
|
426
437
|
.find((c) => c.propertyKey === this.propertyKey() && c.areaKey === this.areaKey());
|
|
427
438
|
const existingSize = existingConfig?.configuration?.['size'];
|
|
439
|
+
const existingLeafLevels = existingConfig?.configuration?.['leafLevels'];
|
|
428
440
|
const configuration = {};
|
|
429
441
|
// Carry over size so drag-resize value is not lost
|
|
430
442
|
if (existingSize != null) {
|
|
431
443
|
configuration['size'] = existingSize;
|
|
432
444
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
445
|
+
if (existingLeafLevels != null) {
|
|
446
|
+
configuration['leafLevels'] = existingLeafLevels;
|
|
447
|
+
}
|
|
448
|
+
if (!this.isLeafDetailsViewType()) {
|
|
449
|
+
// Keep both keys for backward compatibility while the shared entities
|
|
450
|
+
// layer now prefers hideLabel.
|
|
451
|
+
configuration['hideName'] = this.hideNameControl.value ?? false;
|
|
452
|
+
configuration['hideLabel'] = this.hideNameControl.value ?? false;
|
|
453
|
+
}
|
|
437
454
|
configuration['labelPosition'] = this.labelOnTopControl.value
|
|
438
455
|
? 'top'
|
|
439
456
|
: 'bottom';
|
|
440
|
-
|
|
457
|
+
if (!this.isLeafDetailsViewType()) {
|
|
458
|
+
configuration['alignEnd'] = this.alignEndControl.value ?? false;
|
|
459
|
+
}
|
|
441
460
|
if (this.isBorderViewType()) {
|
|
442
461
|
configuration['showBorder'] = this.showBorderControl.value ?? false;
|
|
443
462
|
}
|
|
@@ -487,13 +506,67 @@ class PropertyConfigDrawer {
|
|
|
487
506
|
this.ref.close(null);
|
|
488
507
|
}
|
|
489
508
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: PropertyConfigDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
490
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: PropertyConfigDrawer, isStandalone: true, selector: "mt-property-config-drawer", inputs: { propertyKey: { classPropertyName: "propertyKey", publicName: "propertyKey", isSignal: true, isRequired: false, transformFunction: null }, propertyName: { classPropertyName: "propertyName", publicName: "propertyName", isSignal: true, isRequired: false, transformFunction: null }, viewType: { classPropertyName: "viewType", publicName: "viewType", isSignal: true, isRequired: false, transformFunction: null }, areaKey: { classPropertyName: "areaKey", publicName: "areaKey", isSignal: true, isRequired: false, transformFunction: null }, currentOrder: { classPropertyName: "currentOrder", publicName: "currentOrder", isSignal: true, isRequired: false, transformFunction: null }, initialConfig: { classPropertyName: "initialConfig", publicName: "initialConfig", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-2 h-full overflow-y-auto pb-10 flex flex-col gap-5\">\r\n <!-- Hide Name Toggle
|
|
509
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: PropertyConfigDrawer, isStandalone: true, selector: "mt-property-config-drawer", inputs: { propertyKey: { classPropertyName: "propertyKey", publicName: "propertyKey", isSignal: true, isRequired: false, transformFunction: null }, propertyName: { classPropertyName: "propertyName", publicName: "propertyName", isSignal: true, isRequired: false, transformFunction: null }, viewType: { classPropertyName: "viewType", publicName: "viewType", isSignal: true, isRequired: false, transformFunction: null }, areaKey: { classPropertyName: "areaKey", publicName: "areaKey", isSignal: true, isRequired: false, transformFunction: null }, currentOrder: { classPropertyName: "currentOrder", publicName: "currentOrder", isSignal: true, isRequired: false, transformFunction: null }, initialConfig: { classPropertyName: "initialConfig", publicName: "initialConfig", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-2 h-full overflow-y-auto pb-10 flex flex-col gap-5\">\r\n @if (!isLeafDetailsViewType()) {\r\n <!-- Hide Name Toggle -->\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('hide-name')\"\r\n icon=\"general.eye-off\"\r\n [formControl]=\"hideNameControl\"\r\n ></mt-toggle-field>\r\n }\r\n\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('label-on-top')\"\r\n icon=\"layout.align-right-02\"\r\n [formControl]=\"labelOnTopControl\"\r\n ></mt-toggle-field>\r\n\r\n @if (!isLeafDetailsViewType()) {\r\n <!-- Align End Toggle -->\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('align-end')\"\r\n [icon]=\"alignEndIcon()\"\r\n [formControl]=\"alignEndControl\"\r\n ></mt-toggle-field>\r\n }\r\n\r\n <!-- Show Border Toggle (Text, Currency, Date, DateTime) -->\r\n @if (isBorderViewType()) {\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-border')\"\r\n icon=\"layout.layout-grid-01\"\r\n [formControl]=\"showBorderControl\"\r\n ></mt-toggle-field>\r\n }\r\n\r\n <!-- User-specific Toggles -->\r\n @if (isUserViewType()) {\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-display-name')\"\r\n icon=\"user.user-circle\"\r\n [formControl]=\"showDisplayNameControl\"\r\n ></mt-toggle-field>\r\n\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-phone-number')\"\r\n icon=\"communication.phone\"\r\n [formControl]=\"showPhoneNumberControl\"\r\n ></mt-toggle-field>\r\n\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-email')\"\r\n icon=\"communication.mail-01\"\r\n [formControl]=\"showEmailControl\"\r\n ></mt-toggle-field>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modalService.footerClass\">\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n severity=\"secondary\"\r\n [disabled]=\"submitting()\"\r\n (onClick)=\"onCancel()\"\r\n ></mt-button>\r\n <mt-button\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n (onClick)=\"onSave()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }] });
|
|
491
510
|
}
|
|
492
511
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: PropertyConfigDrawer, decorators: [{
|
|
493
512
|
type: Component,
|
|
494
|
-
args: [{ selector: 'mt-property-config-drawer', standalone: true, imports: [TranslocoDirective, ReactiveFormsModule, Button, ToggleField], template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-2 h-full overflow-y-auto pb-10 flex flex-col gap-5\">\r\n <!-- Hide Name Toggle
|
|
513
|
+
args: [{ selector: 'mt-property-config-drawer', standalone: true, imports: [TranslocoDirective, ReactiveFormsModule, Button, ToggleField], template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-2 h-full overflow-y-auto pb-10 flex flex-col gap-5\">\r\n @if (!isLeafDetailsViewType()) {\r\n <!-- Hide Name Toggle -->\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('hide-name')\"\r\n icon=\"general.eye-off\"\r\n [formControl]=\"hideNameControl\"\r\n ></mt-toggle-field>\r\n }\r\n\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('label-on-top')\"\r\n icon=\"layout.align-right-02\"\r\n [formControl]=\"labelOnTopControl\"\r\n ></mt-toggle-field>\r\n\r\n @if (!isLeafDetailsViewType()) {\r\n <!-- Align End Toggle -->\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('align-end')\"\r\n [icon]=\"alignEndIcon()\"\r\n [formControl]=\"alignEndControl\"\r\n ></mt-toggle-field>\r\n }\r\n\r\n <!-- Show Border Toggle (Text, Currency, Date, DateTime) -->\r\n @if (isBorderViewType()) {\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-border')\"\r\n icon=\"layout.layout-grid-01\"\r\n [formControl]=\"showBorderControl\"\r\n ></mt-toggle-field>\r\n }\r\n\r\n <!-- User-specific Toggles -->\r\n @if (isUserViewType()) {\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-display-name')\"\r\n icon=\"user.user-circle\"\r\n [formControl]=\"showDisplayNameControl\"\r\n ></mt-toggle-field>\r\n\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-phone-number')\"\r\n icon=\"communication.phone\"\r\n [formControl]=\"showPhoneNumberControl\"\r\n ></mt-toggle-field>\r\n\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-email')\"\r\n icon=\"communication.mail-01\"\r\n [formControl]=\"showEmailControl\"\r\n ></mt-toggle-field>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modalService.footerClass\">\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n severity=\"secondary\"\r\n [disabled]=\"submitting()\"\r\n (onClick)=\"onCancel()\"\r\n ></mt-button>\r\n <mt-button\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n (onClick)=\"onSave()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n" }]
|
|
495
514
|
}], ctorParameters: () => [], propDecorators: { propertyKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "propertyKey", required: false }] }], propertyName: [{ type: i0.Input, args: [{ isSignal: true, alias: "propertyName", required: false }] }], viewType: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewType", required: false }] }], areaKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "areaKey", required: false }] }], currentOrder: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentOrder", required: false }] }], initialConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialConfig", required: false }] }] } });
|
|
496
515
|
|
|
516
|
+
class LeafDetailsSelector {
|
|
517
|
+
property = input.required(...(ngDevMode ? [{ debugName: "property" }] : /* istanbul ignore next */ []));
|
|
518
|
+
leafLevels = input({}, ...(ngDevMode ? [{ debugName: "leafLevels" }] : /* istanbul ignore next */ []));
|
|
519
|
+
showEditAction = input(false, ...(ngDevMode ? [{ debugName: "showEditAction" }] : /* istanbul ignore next */ []));
|
|
520
|
+
configChange = output();
|
|
521
|
+
editRequested = output();
|
|
522
|
+
levels = computed(() => this.property().configuration ?? [], ...(ngDevMode ? [{ debugName: "levels" }] : /* istanbul ignore next */ []));
|
|
523
|
+
isLevelEnabled(levelId) {
|
|
524
|
+
return this.leafLevels()[String(levelId)] != null;
|
|
525
|
+
}
|
|
526
|
+
isStatusSelected(levelId, statusKey) {
|
|
527
|
+
return (this.leafLevels()[String(levelId)]?.selectedStatuses.includes(statusKey) ?? false);
|
|
528
|
+
}
|
|
529
|
+
/** Section toggle — show or hide the entire level in the preview */
|
|
530
|
+
toggleLevel(levelId, enabled) {
|
|
531
|
+
const current = { ...this.leafLevels() };
|
|
532
|
+
if (enabled) {
|
|
533
|
+
const level = this.levels().find((l) => l.levelId === levelId);
|
|
534
|
+
current[String(levelId)] = {
|
|
535
|
+
order: Object.keys(current).length + 1,
|
|
536
|
+
selectedStatuses: level?.statuses.map((s) => s.key) ?? [],
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
delete current[String(levelId)];
|
|
541
|
+
}
|
|
542
|
+
this.configChange.emit(current);
|
|
543
|
+
}
|
|
544
|
+
/** Status toggle — show or hide a single status badge in the level preview */
|
|
545
|
+
toggleStatus(levelId, statusKey) {
|
|
546
|
+
const current = { ...this.leafLevels() };
|
|
547
|
+
const saved = current[String(levelId)];
|
|
548
|
+
if (!saved)
|
|
549
|
+
return;
|
|
550
|
+
const idx = saved.selectedStatuses.indexOf(statusKey);
|
|
551
|
+
current[String(levelId)] = {
|
|
552
|
+
...saved,
|
|
553
|
+
selectedStatuses: idx >= 0
|
|
554
|
+
? saved.selectedStatuses.filter((k) => k !== statusKey)
|
|
555
|
+
: [...saved.selectedStatuses, statusKey],
|
|
556
|
+
};
|
|
557
|
+
this.configChange.emit(current);
|
|
558
|
+
}
|
|
559
|
+
onEditClick() {
|
|
560
|
+
this.editRequested.emit();
|
|
561
|
+
}
|
|
562
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: LeafDetailsSelector, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
563
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: LeafDetailsSelector, isStandalone: true, selector: "mt-leaf-details-selector", inputs: { property: { classPropertyName: "property", publicName: "property", isSignal: true, isRequired: true, transformFunction: null }, leafLevels: { classPropertyName: "leafLevels", publicName: "leafLevels", isSignal: true, isRequired: false, transformFunction: null }, showEditAction: { classPropertyName: "showEditAction", publicName: "showEditAction", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { configChange: "configChange", editRequested: "editRequested" }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div class=\"space-y-2 pt-1\">\r\n <div class=\"flex items-center gap-1 px-1\">\r\n <p class=\"text-sm font-semibold text-color\">\r\n {{ t(\"leaf-details-target-statuses\") }}\r\n </p>\r\n @if (showEditAction()) {\r\n <mt-button\r\n icon=\"editor.pencil-01\"\r\n variant=\"text\"\r\n severity=\"primary\"\r\n (onClick)=\"onEditClick()\"\r\n />\r\n }\r\n </div>\r\n\r\n @for (level of levels(); track $index) {\r\n <div class=\"rounded-lg border border-surface-200 overflow-hidden\">\r\n <!-- Level header: icon + name | section toggle (show/hide this level) -->\r\n <div class=\"flex items-center gap-2 px-3 py-2.5\">\r\n <mt-icon\r\n [icon]=\"level.levelIcon\"\r\n size=\"xs\"\r\n class=\"text-color-secondary shrink-0\"\r\n />\r\n <span class=\"flex-1 text-sm font-medium truncate\">{{\r\n level.levelName\r\n }}</span>\r\n <mt-toggle-field\r\n [ngModel]=\"isLevelEnabled(level.levelId)\"\r\n (ngModelChange)=\"toggleLevel(level.levelId, $event)\"\r\n size=\"small\"\r\n class=\"shrink-0\"\r\n />\r\n </div>\r\n\r\n <!-- Status rows: each toggle shows/hides that status badge in the preview -->\r\n @if (isLevelEnabled(level.levelId)) {\r\n <div class=\"border-t border-surface-200 divide-y divide-surface-100\">\r\n @for (status of level.statuses; track $index) {\r\n <div class=\"flex items-center gap-2.5 px-3 py-2\">\r\n <span\r\n class=\"w-2.5 h-2.5 rounded-full shrink-0 border border-black/10\"\r\n [style.background-color]=\"status.color ?? '#9CA3AF'\"\r\n ></span>\r\n <span class=\"text-sm flex-1 truncate\">{{\r\n status.display\r\n }}</span>\r\n <mt-toggle-field\r\n [ngModel]=\"isStatusSelected(level.levelId, status.key)\"\r\n (ngModelChange)=\"toggleStatus(level.levelId, status.key)\"\r\n size=\"small\"\r\n class=\"shrink-0\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }] });
|
|
564
|
+
}
|
|
565
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: LeafDetailsSelector, decorators: [{
|
|
566
|
+
type: Component,
|
|
567
|
+
args: [{ selector: 'mt-leaf-details-selector', standalone: true, imports: [FormsModule, TranslocoDirective, Icon, ToggleField, Button], template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div class=\"space-y-2 pt-1\">\r\n <div class=\"flex items-center gap-1 px-1\">\r\n <p class=\"text-sm font-semibold text-color\">\r\n {{ t(\"leaf-details-target-statuses\") }}\r\n </p>\r\n @if (showEditAction()) {\r\n <mt-button\r\n icon=\"editor.pencil-01\"\r\n variant=\"text\"\r\n severity=\"primary\"\r\n (onClick)=\"onEditClick()\"\r\n />\r\n }\r\n </div>\r\n\r\n @for (level of levels(); track $index) {\r\n <div class=\"rounded-lg border border-surface-200 overflow-hidden\">\r\n <!-- Level header: icon + name | section toggle (show/hide this level) -->\r\n <div class=\"flex items-center gap-2 px-3 py-2.5\">\r\n <mt-icon\r\n [icon]=\"level.levelIcon\"\r\n size=\"xs\"\r\n class=\"text-color-secondary shrink-0\"\r\n />\r\n <span class=\"flex-1 text-sm font-medium truncate\">{{\r\n level.levelName\r\n }}</span>\r\n <mt-toggle-field\r\n [ngModel]=\"isLevelEnabled(level.levelId)\"\r\n (ngModelChange)=\"toggleLevel(level.levelId, $event)\"\r\n size=\"small\"\r\n class=\"shrink-0\"\r\n />\r\n </div>\r\n\r\n <!-- Status rows: each toggle shows/hides that status badge in the preview -->\r\n @if (isLevelEnabled(level.levelId)) {\r\n <div class=\"border-t border-surface-200 divide-y divide-surface-100\">\r\n @for (status of level.statuses; track $index) {\r\n <div class=\"flex items-center gap-2.5 px-3 py-2\">\r\n <span\r\n class=\"w-2.5 h-2.5 rounded-full shrink-0 border border-black/10\"\r\n [style.background-color]=\"status.color ?? '#9CA3AF'\"\r\n ></span>\r\n <span class=\"text-sm flex-1 truncate\">{{\r\n status.display\r\n }}</span>\r\n <mt-toggle-field\r\n [ngModel]=\"isStatusSelected(level.levelId, status.key)\"\r\n (ngModelChange)=\"toggleStatus(level.levelId, status.key)\"\r\n size=\"small\"\r\n class=\"shrink-0\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-container>\r\n" }]
|
|
568
|
+
}], propDecorators: { property: [{ type: i0.Input, args: [{ isSignal: true, alias: "property", required: true }] }], leafLevels: [{ type: i0.Input, args: [{ isSignal: true, alias: "leafLevels", required: false }] }], showEditAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEditAction", required: false }] }], configChange: [{ type: i0.Output, args: ["configChange"] }], editRequested: [{ type: i0.Output, args: ["editRequested"] }] } });
|
|
569
|
+
|
|
497
570
|
class Customization {
|
|
498
571
|
facade = inject(CustomizationFacade);
|
|
499
572
|
modalService = inject(ModalService);
|
|
@@ -534,15 +607,41 @@ class Customization {
|
|
|
534
607
|
filteredProperties = computed(() => {
|
|
535
608
|
const query = this.searchQuery().toLowerCase().trim();
|
|
536
609
|
const list = this.propertiesWithEnabled();
|
|
610
|
+
// LeafDetails always first in the list regardless of active state
|
|
611
|
+
const leaf = list.filter((p) => p.viewType === 'LeafDetails');
|
|
612
|
+
const regular = list.filter((p) => p.viewType !== 'LeafDetails');
|
|
613
|
+
const ordered = [...leaf, ...regular];
|
|
537
614
|
if (!query)
|
|
538
|
-
return
|
|
539
|
-
return
|
|
615
|
+
return ordered;
|
|
616
|
+
return ordered.filter((prop) => {
|
|
540
617
|
const name = typeof prop.name === 'string'
|
|
541
618
|
? prop.name
|
|
542
619
|
: Object.values(prop.name).join(' ');
|
|
543
620
|
return name.toLowerCase().includes(query);
|
|
544
621
|
});
|
|
545
622
|
}, ...(ngDevMode ? [{ debugName: "filteredProperties" }] : /* istanbul ignore next */ []));
|
|
623
|
+
/**
|
|
624
|
+
* LeafDetails saved configs indexed by propertyKey for the active area.
|
|
625
|
+
* Used by the inline selector in the property list.
|
|
626
|
+
*/
|
|
627
|
+
leafConfigsByProp = computed(() => {
|
|
628
|
+
const configs = this.validConfigurations();
|
|
629
|
+
const area = this.activeArea();
|
|
630
|
+
if (!area)
|
|
631
|
+
return {};
|
|
632
|
+
const result = {};
|
|
633
|
+
for (const config of configs) {
|
|
634
|
+
if (config.areaKey !== area)
|
|
635
|
+
continue;
|
|
636
|
+
const leafLevels = config.configuration?.['leafLevels'];
|
|
637
|
+
if (leafLevels &&
|
|
638
|
+
typeof leafLevels === 'object' &&
|
|
639
|
+
!Array.isArray(leafLevels)) {
|
|
640
|
+
result[config.propertyKey] = leafLevels;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return result;
|
|
644
|
+
}, ...(ngDevMode ? [{ debugName: "leafConfigsByProp" }] : /* istanbul ignore next */ []));
|
|
546
645
|
/** Preview entities built from configurations + properties for the active area */
|
|
547
646
|
previewEntities = signal([], ...(ngDevMode ? [{ debugName: "previewEntities" }] : /* istanbul ignore next */ []));
|
|
548
647
|
constructor() {
|
|
@@ -563,25 +662,23 @@ class Customization {
|
|
|
563
662
|
return;
|
|
564
663
|
}
|
|
565
664
|
const areaConfigs = configs.filter((c) => c.areaKey === area);
|
|
566
|
-
const entities = areaConfigs
|
|
567
|
-
.map((config) => this.buildEntityFromConfig(config, props))
|
|
568
|
-
.filter((e) => e !== null)
|
|
569
|
-
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
665
|
+
const entities = buildDisplayEntities(areaConfigs.flatMap((config) => this.buildEntitiesFromConfig(config, props)));
|
|
570
666
|
this.previewEntities.set(entities);
|
|
571
667
|
});
|
|
572
668
|
}
|
|
573
669
|
ngOnInit() {
|
|
574
|
-
// Load display areas
|
|
575
670
|
this.facade.loadAreas();
|
|
576
671
|
this.facade.loadProperties();
|
|
577
672
|
this.facade.loadConfigurations();
|
|
578
673
|
}
|
|
579
674
|
onPropertyToggle(prop, enabled) {
|
|
675
|
+
// LeafDetails is managed entirely via the inline status selector — skip
|
|
676
|
+
if (prop.viewType === 'LeafDetails')
|
|
677
|
+
return;
|
|
580
678
|
if (enabled) {
|
|
581
679
|
this.activateProperty(prop, this.editableViewTypes.has(prop.viewType));
|
|
582
680
|
return;
|
|
583
681
|
}
|
|
584
|
-
// Remove this property and bulk-replace with remaining checked props
|
|
585
682
|
this.bulkSaveWithout(prop.key);
|
|
586
683
|
}
|
|
587
684
|
onEditProperty(prop) {
|
|
@@ -596,16 +693,20 @@ class Customization {
|
|
|
596
693
|
});
|
|
597
694
|
}
|
|
598
695
|
onEntityClicked(entity) {
|
|
696
|
+
// LeafDetails sub-entities are configured inline — not via drawer
|
|
599
697
|
const area = this.activeArea();
|
|
600
698
|
if (!area || !entity.key)
|
|
601
699
|
return;
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
700
|
+
const propertyKey = isLeafDetailsSyntheticKey(entity.key)
|
|
701
|
+
? entity.key.split(LEAF_DETAILS_KEY_SEPARATOR)[0]
|
|
702
|
+
: entity.key;
|
|
703
|
+
const prop = this.properties().find((p) => p.key === propertyKey);
|
|
605
704
|
if (!prop)
|
|
606
705
|
return;
|
|
706
|
+
if (!this.editableViewTypes.has(prop.viewType))
|
|
707
|
+
return;
|
|
607
708
|
const configs = this.validConfigurations();
|
|
608
|
-
const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey ===
|
|
709
|
+
const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === propertyKey);
|
|
609
710
|
this.openDrawer(prop, {
|
|
610
711
|
currentOrder: existingConfig?.order,
|
|
611
712
|
configuration: existingConfig?.configuration,
|
|
@@ -616,44 +717,183 @@ class Customization {
|
|
|
616
717
|
if (!area)
|
|
617
718
|
return;
|
|
618
719
|
const configs = this.validConfigurations();
|
|
619
|
-
//
|
|
720
|
+
// ── LeafDetails sub-entity resize ──
|
|
721
|
+
if (isLeafDetailsSyntheticKey(event.entity.key)) {
|
|
722
|
+
const [propKey, levelId] = event.entity.key.split(LEAF_DETAILS_KEY_SEPARATOR);
|
|
723
|
+
const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === propKey);
|
|
724
|
+
const leafLevels = {
|
|
725
|
+
...(existingConfig?.configuration?.['leafLevels'] ?? {}),
|
|
726
|
+
};
|
|
727
|
+
if (leafLevels[levelId]) {
|
|
728
|
+
leafLevels[levelId] = { ...leafLevels[levelId], size: event.newSize };
|
|
729
|
+
}
|
|
730
|
+
const updatedConfigs = configs.map((c) => c.areaKey === area && c.propertyKey === propKey
|
|
731
|
+
? { ...c, configuration: { ...c.configuration, leafLevels } }
|
|
732
|
+
: c);
|
|
733
|
+
this.facade
|
|
734
|
+
.bulkReplaceConfigurations(this.groupConfigsByProperty(updatedConfigs))
|
|
735
|
+
.subscribe();
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
// ── Regular entity resize ──
|
|
620
739
|
const updatedConfigs = configs.map((c) => c.areaKey === area && c.propertyKey === event.entity.key
|
|
621
740
|
? { ...c, configuration: { ...c.configuration, size: event.newSize } }
|
|
622
741
|
: c);
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
742
|
+
this.facade
|
|
743
|
+
.bulkReplaceConfigurations(this.groupConfigsByProperty(updatedConfigs))
|
|
744
|
+
.subscribe();
|
|
626
745
|
}
|
|
627
746
|
onEntitiesReordered(entities) {
|
|
628
747
|
const area = this.activeArea();
|
|
629
748
|
if (!area)
|
|
630
749
|
return;
|
|
631
750
|
const configs = this.validConfigurations();
|
|
632
|
-
|
|
751
|
+
// Separate regular entities from LeafDetails sub-entities
|
|
752
|
+
const regularEntities = entities.filter((e) => !isLeafDetailsSyntheticKey(e.key));
|
|
753
|
+
const leafEntities = entities.filter((e) => isLeafDetailsSyntheticKey(e.key));
|
|
754
|
+
// Regular entities → BulkReplaceItems
|
|
755
|
+
const regularItems = regularEntities.map((entity) => {
|
|
633
756
|
const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === entity.key);
|
|
634
757
|
return {
|
|
635
758
|
propertyKey: entity.key,
|
|
636
759
|
displayAreas: [
|
|
637
760
|
{
|
|
638
761
|
areaKey: area,
|
|
639
|
-
order:
|
|
762
|
+
order: entity.order,
|
|
640
763
|
configuration: existingConfig?.configuration ?? {},
|
|
641
764
|
},
|
|
642
765
|
],
|
|
643
766
|
};
|
|
644
767
|
});
|
|
645
|
-
//
|
|
646
|
-
const
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
768
|
+
// Leaf entities → group by parent propertyKey, update per-level order
|
|
769
|
+
const leafByProp = new Map();
|
|
770
|
+
for (const entity of leafEntities) {
|
|
771
|
+
const [propKey, levelId] = entity.key.split(LEAF_DETAILS_KEY_SEPARATOR);
|
|
772
|
+
if (!leafByProp.has(propKey))
|
|
773
|
+
leafByProp.set(propKey, []);
|
|
774
|
+
leafByProp.get(propKey).push({ levelId, order: entity.order });
|
|
775
|
+
}
|
|
776
|
+
const leafItems = [];
|
|
777
|
+
for (const [propKey, levelUpdates] of leafByProp) {
|
|
778
|
+
const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === propKey);
|
|
779
|
+
const leafLevels = {
|
|
780
|
+
...(existingConfig?.configuration?.['leafLevels'] ?? {}),
|
|
781
|
+
};
|
|
782
|
+
for (const { levelId, order } of levelUpdates) {
|
|
783
|
+
if (leafLevels[levelId]) {
|
|
784
|
+
leafLevels[levelId] = { ...leafLevels[levelId], order };
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
const minOrder = Math.min(...levelUpdates.map((l) => l.order));
|
|
788
|
+
leafItems.push({
|
|
789
|
+
propertyKey: propKey,
|
|
790
|
+
displayAreas: [
|
|
791
|
+
{
|
|
792
|
+
areaKey: area,
|
|
793
|
+
order: existingConfig?.order ?? minOrder,
|
|
794
|
+
configuration: { ...existingConfig?.configuration, leafLevels },
|
|
795
|
+
},
|
|
796
|
+
],
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
// Configs from other areas (preserve as-is)
|
|
800
|
+
const otherAreaItems = this.groupConfigsByProperty(configs.filter((c) => c.areaKey !== area));
|
|
801
|
+
this.facade
|
|
802
|
+
.bulkReplaceConfigurations(this.mergeItems([...regularItems, ...leafItems], otherAreaItems))
|
|
803
|
+
.subscribe();
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Called by the inline LeafDetailsSelector when the user changes status selections.
|
|
807
|
+
* Immediately persists the updated leafLevels configuration.
|
|
808
|
+
*/
|
|
809
|
+
onLeafConfigChange(prop, leafLevels) {
|
|
810
|
+
const area = this.activeArea();
|
|
811
|
+
if (!area)
|
|
812
|
+
return;
|
|
813
|
+
const configs = this.validConfigurations();
|
|
814
|
+
const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === prop.key);
|
|
815
|
+
// Ensure every level has an order assigned
|
|
816
|
+
let nextOrder = configs.filter((c) => c.areaKey === area && c.propertyKey !== prop.key)
|
|
817
|
+
.length + 1;
|
|
818
|
+
const normalizedLevels = {};
|
|
819
|
+
for (const [levelId, levelCfg] of Object.entries(leafLevels)) {
|
|
820
|
+
normalizedLevels[levelId] = levelCfg.order
|
|
821
|
+
? levelCfg
|
|
822
|
+
: { ...levelCfg, order: nextOrder++ };
|
|
823
|
+
}
|
|
824
|
+
// Other configs excluding this leaf property in the current area
|
|
825
|
+
const otherItems = this.groupConfigsByProperty(configs.filter((c) => !(c.areaKey === area && c.propertyKey === prop.key)));
|
|
826
|
+
if (Object.keys(normalizedLevels).length === 0) {
|
|
827
|
+
// All levels deselected — remove property from display config
|
|
828
|
+
this.facade.bulkReplaceConfigurations(otherItems).subscribe();
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
const minOrder = Math.min(...Object.values(normalizedLevels).map((l) => l.order));
|
|
832
|
+
const newItem = {
|
|
833
|
+
propertyKey: prop.key,
|
|
834
|
+
displayAreas: [
|
|
835
|
+
{
|
|
836
|
+
areaKey: area,
|
|
837
|
+
order: existingConfig?.order ?? minOrder,
|
|
838
|
+
configuration: {
|
|
839
|
+
...existingConfig?.configuration,
|
|
840
|
+
leafLevels: normalizedLevels,
|
|
841
|
+
},
|
|
842
|
+
},
|
|
843
|
+
],
|
|
844
|
+
};
|
|
845
|
+
this.facade
|
|
846
|
+
.bulkReplaceConfigurations(this.mergeItems([newItem], otherItems))
|
|
847
|
+
.subscribe();
|
|
651
848
|
}
|
|
652
849
|
// ── Private helpers ──
|
|
653
850
|
/**
|
|
654
|
-
*
|
|
655
|
-
*
|
|
851
|
+
* Builds zero or more EntityData entries from a single DisplayConfiguration.
|
|
852
|
+
* Regular properties → one entity. LeafDetails → one entity per enabled level.
|
|
656
853
|
*/
|
|
854
|
+
buildEntitiesFromConfig(config, props) {
|
|
855
|
+
const prop = props.find((p) => p.key === config.propertyKey);
|
|
856
|
+
if (!prop)
|
|
857
|
+
return [];
|
|
858
|
+
if (prop.viewType === 'LeafDetails') {
|
|
859
|
+
return this.buildLeafDetailsEntities(config, prop);
|
|
860
|
+
}
|
|
861
|
+
const entity = this.buildEntityFromConfig(config, props);
|
|
862
|
+
return entity ? [entity] : [];
|
|
863
|
+
}
|
|
864
|
+
/** Builds the shared LeafDetails entity that preview/manage will expand. */
|
|
865
|
+
buildLeafDetailsEntities(config, prop) {
|
|
866
|
+
const levels = prop.configuration ?? [];
|
|
867
|
+
const leafLevels = config.configuration?.['leafLevels'];
|
|
868
|
+
if (!leafLevels)
|
|
869
|
+
return [];
|
|
870
|
+
return [
|
|
871
|
+
{
|
|
872
|
+
id: prop.id,
|
|
873
|
+
propertyId: prop.id,
|
|
874
|
+
key: prop.key,
|
|
875
|
+
name: this.getPropertyDisplayName(prop),
|
|
876
|
+
propertyConfiguration: prop.configuration,
|
|
877
|
+
value: levels.map((level) => ({
|
|
878
|
+
levelId: level.levelId,
|
|
879
|
+
levelName: level.levelName,
|
|
880
|
+
levelIcon: level.levelIcon,
|
|
881
|
+
statuses: level.statuses.map((status) => ({
|
|
882
|
+
key: status.key,
|
|
883
|
+
display: status.display,
|
|
884
|
+
color: status.color,
|
|
885
|
+
count: 0,
|
|
886
|
+
})),
|
|
887
|
+
})),
|
|
888
|
+
viewType: 'LeafDetails',
|
|
889
|
+
order: config.order,
|
|
890
|
+
configuration: {
|
|
891
|
+
...config.configuration,
|
|
892
|
+
leafLevels,
|
|
893
|
+
},
|
|
894
|
+
},
|
|
895
|
+
];
|
|
896
|
+
}
|
|
657
897
|
activateProperty(prop, openConfigAfterSave = false) {
|
|
658
898
|
const area = this.activeArea();
|
|
659
899
|
if (!area)
|
|
@@ -672,60 +912,40 @@ class Customization {
|
|
|
672
912
|
const areaConfigs = configs.filter((c) => c.areaKey === area);
|
|
673
913
|
const nextOrder = areaConfigs.length + 1;
|
|
674
914
|
const configuration = {};
|
|
675
|
-
const newDisplayArea = {
|
|
676
|
-
areaKey: area,
|
|
677
|
-
order: nextOrder,
|
|
678
|
-
configuration,
|
|
679
|
-
};
|
|
680
|
-
// Build bulk items from existing configs + new property
|
|
681
915
|
const itemsMap = new Map();
|
|
682
916
|
for (const c of configs) {
|
|
683
|
-
if (!itemsMap.has(c.propertyKey))
|
|
917
|
+
if (!itemsMap.has(c.propertyKey))
|
|
684
918
|
itemsMap.set(c.propertyKey, []);
|
|
685
|
-
}
|
|
686
919
|
itemsMap.get(c.propertyKey).push({
|
|
687
920
|
areaKey: c.areaKey,
|
|
688
921
|
order: c.order,
|
|
689
922
|
configuration: c.configuration,
|
|
690
923
|
});
|
|
691
924
|
}
|
|
692
|
-
|
|
693
|
-
if (!itemsMap.has(prop.key)) {
|
|
925
|
+
if (!itemsMap.has(prop.key))
|
|
694
926
|
itemsMap.set(prop.key, []);
|
|
695
|
-
|
|
696
|
-
|
|
927
|
+
itemsMap
|
|
928
|
+
.get(prop.key)
|
|
929
|
+
.push({ areaKey: area, order: nextOrder, configuration });
|
|
697
930
|
const items = Array.from(itemsMap.entries()).map(([propertyKey, displayAreas]) => ({ propertyKey, displayAreas }));
|
|
698
931
|
this.facade.bulkReplaceConfigurations(items).subscribe({
|
|
699
932
|
next: () => {
|
|
700
|
-
if (
|
|
701
|
-
|
|
933
|
+
if (openConfigAfterSave) {
|
|
934
|
+
this.openDrawer(prop, { currentOrder: nextOrder, configuration });
|
|
702
935
|
}
|
|
703
|
-
this.openDrawer(prop, {
|
|
704
|
-
currentOrder: nextOrder,
|
|
705
|
-
configuration,
|
|
706
|
-
});
|
|
707
936
|
},
|
|
708
937
|
});
|
|
709
938
|
}
|
|
710
|
-
/**
|
|
711
|
-
* Build bulk items from all current configurations, excluding a specific property.
|
|
712
|
-
* Then call bulk-replace so the backend replaces the entire context atomically.
|
|
713
|
-
*/
|
|
714
939
|
bulkSaveWithout(excludePropertyKey) {
|
|
715
940
|
const configs = this.validConfigurations();
|
|
716
|
-
const
|
|
717
|
-
const items = this.groupConfigsByProperty(remaining);
|
|
941
|
+
const items = this.groupConfigsByProperty(configs.filter((c) => c.propertyKey !== excludePropertyKey));
|
|
718
942
|
this.facade.bulkReplaceConfigurations(items).subscribe();
|
|
719
943
|
}
|
|
720
|
-
/**
|
|
721
|
-
* Group flat DisplayConfiguration[] into bulk-replace items grouped by propertyKey.
|
|
722
|
-
*/
|
|
723
944
|
groupConfigsByProperty(configs) {
|
|
724
945
|
const map = new Map();
|
|
725
946
|
for (const c of configs) {
|
|
726
|
-
if (!map.has(c.propertyKey))
|
|
947
|
+
if (!map.has(c.propertyKey))
|
|
727
948
|
map.set(c.propertyKey, []);
|
|
728
|
-
}
|
|
729
949
|
map.get(c.propertyKey).push({
|
|
730
950
|
areaKey: c.areaKey,
|
|
731
951
|
order: c.order,
|
|
@@ -737,23 +957,16 @@ class Customization {
|
|
|
737
957
|
displayAreas,
|
|
738
958
|
}));
|
|
739
959
|
}
|
|
740
|
-
/**
|
|
741
|
-
* Merge two sets of bulk items. If a propertyKey exists in both,
|
|
742
|
-
* combine their displayAreas arrays.
|
|
743
|
-
*/
|
|
744
960
|
mergeItems(primary, secondary) {
|
|
745
961
|
const map = new Map();
|
|
746
|
-
for (const item of primary)
|
|
962
|
+
for (const item of primary)
|
|
747
963
|
map.set(item.propertyKey, [...item.displayAreas]);
|
|
748
|
-
}
|
|
749
964
|
for (const item of secondary) {
|
|
750
965
|
const existing = map.get(item.propertyKey);
|
|
751
|
-
if (existing)
|
|
966
|
+
if (existing)
|
|
752
967
|
existing.push(...item.displayAreas);
|
|
753
|
-
|
|
754
|
-
else {
|
|
968
|
+
else
|
|
755
969
|
map.set(item.propertyKey, [...item.displayAreas]);
|
|
756
|
-
}
|
|
757
970
|
}
|
|
758
971
|
return Array.from(map.entries()).map(([propertyKey, displayAreas]) => ({
|
|
759
972
|
propertyKey,
|
|
@@ -807,7 +1020,7 @@ class Customization {
|
|
|
807
1020
|
return null;
|
|
808
1021
|
const displayName = this.getPropertyDisplayName(prop);
|
|
809
1022
|
const viewType = prop.viewType;
|
|
810
|
-
|
|
1023
|
+
return {
|
|
811
1024
|
id: prop.id,
|
|
812
1025
|
propertyId: prop.id,
|
|
813
1026
|
key: prop.key,
|
|
@@ -816,9 +1029,9 @@ class Customization {
|
|
|
816
1029
|
rawValue: this.getPlaceholderRawValue(viewType, displayName),
|
|
817
1030
|
viewType,
|
|
818
1031
|
order: config.order,
|
|
1032
|
+
propertyConfiguration: prop.configuration,
|
|
819
1033
|
configuration: config.configuration,
|
|
820
1034
|
};
|
|
821
|
-
return entity;
|
|
822
1035
|
}
|
|
823
1036
|
getPropertyDisplayName(prop) {
|
|
824
1037
|
if (typeof prop.name === 'string')
|
|
@@ -877,7 +1090,7 @@ class Customization {
|
|
|
877
1090
|
}
|
|
878
1091
|
}
|
|
879
1092
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: Customization, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
880
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: Customization, isStandalone: true, selector: "mt-customization", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div\r\n class=\"flex gap-6 h-full w-full overflow-hidden max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:flex-col max-[1025px]:gap-4 max-[1025px]:overflow-x-hidden max-[1025px]:overflow-y-auto\"\r\n >\r\n <!-- \u2500\u2500\u2500 Left Sidebar: Attributes Panel \u2500\u2500\u2500 -->\r\n <mt-card\r\n class=\"w-1/5 h-full flex flex-col overflow-hidden pb-3 max-[1025px]:h-auto max-[1025px]:max-h-[42vh] max-[1025px]:min-h-0 max-[1025px]:w-full\"\r\n >\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <h3 class=\"text-xl font-semibold px-4 pt-5 pb-3\">\r\n {{ t(\"attributes\") }}\r\n </h3>\r\n\r\n <!-- Properties List -->\r\n <div class=\"flex-1 overflow-y-auto px-4 pb-4 space-y-3\">\r\n <!-- Search Field -->\r\n <div class=\"sticky top-0 bg-surface-0 mb-0 py-3 pb-2 mb-1 z-10\">\r\n <mt-text-field\r\n [placeholder]=\"t('search-properties')\"\r\n [(ngModel)]=\"searchQuery\"\r\n icon=\"general.search-lg\"\r\n />\r\n </div>\r\n @if (isLoading()) {\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <div class=\"flex items-center gap-1 p-1 rounded-lg\">\r\n <p-skeleton height=\"4rem\" />\r\n </div>\r\n }\r\n } @else {\r\n @for (prop of filteredProperties(); track prop.id) {\r\n <div\r\n class=\"flex items-center gap-3 px-4 rounded-lg border border-gray-300 border-dashed hover:bg-emphasis transition-colors\"\r\n >\r\n <!-- Property Name -->\r\n <div class=\"flex-1 min-w-0 py-4\">\r\n <span\r\n class=\"text-sm font-medium truncate block\"\r\n [class.text-primary]=\"prop.enabled\"\r\n >\r\n {{ prop.name?.display }}\r\n </span>\r\n </div>\r\n\r\n <!-- Edit Button (visible when enabled and viewType has configurable fields) -->\r\n @if (prop.enabled && editableViewTypes.has(prop.viewType)) {\r\n <mt-button\r\n icon=\"editor.pencil-01\"\r\n variant=\"text\"\r\n severity=\"primary\"\r\n (onClick)=\"onEditProperty(prop)\"\r\n />\r\n }\r\n\r\n <!-- Toggle -->\r\n <mt-toggle-field\r\n [ngModel]=\"prop.enabled\"\r\n (ngModelChange)=\"onPropertyToggle(prop, $event)\"\r\n class=\"shrink-0\"\r\n size=\"small\"\r\n ></mt-toggle-field>\r\n </div>\r\n }\r\n\r\n @if (filteredProperties().length === 0) {\r\n <div class=\"py-8 text-center text-muted-color\">\r\n <p class=\"text-sm\">{{ t(\"no-properties\") }}</p>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n\r\n <!-- \u2500\u2500\u2500 Main Area: Preview \u2500\u2500\u2500 -->\r\n <div\r\n class=\"w-3/5 flex flex-col gap-4 h-full overflow-hidden items-center max-[1025px]:h-auto max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:w-full max-[1025px]:flex-1 max-[1025px]:items-stretch max-[1025px]:overflow-visible max-[1025px]:pb-4\"\r\n >\r\n <!-- Area Tabs -->\r\n @if (isLoadingAreas()) {\r\n <div class=\"flex gap-2 py-1\">\r\n @for (i of [1, 2, 3]; track i) {\r\n <p-skeleton width=\"6rem\" height=\"2.25rem\" borderRadius=\"8px\" />\r\n }\r\n </div>\r\n } @else if (areasTabs().length > 0) {\r\n <div class=\"max-[1025px]:w-full\">\r\n <div\r\n class=\"max-[1025px]:w-full max-[1025px]:min-w-0 max-[1025px]:overflow-x-auto max-[1025px]:pb-1\"\r\n >\r\n <mt-tabs\r\n class=\"max-[1025px]:min-w-max\"\r\n [options]=\"areasTabs()\"\r\n [(active)]=\"activeArea\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Entities Preview -->\r\n <mt-card\r\n class=\"w-130 flex-1 overflow-hidden max-[1025px]:w-full max-[1025px]:min-h-[24rem]\"\r\n >\r\n <div>\r\n <ng-template #headless>\r\n <div class=\"overflow-auto h-full my-2 py-3 px-4\">\r\n @if (isLoadingPreview()) {\r\n <div\r\n class=\"grid grid-cols-24 gap-x-4 gap-y-5 p-4 max-[640px]:grid-cols-1 max-[640px]:gap-4\"\r\n >\r\n <div\r\n class=\"col-span-20 flex items-center gap-2.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton size=\"4rem\" shape=\"circle\" />\r\n <div class=\"flex flex-col gap-1 flex-1\">\r\n <p-skeleton height=\"2.5rem\" borderRadius=\"4px\" />\r\n </div>\r\n </div>\r\n @for (i of [1, 2, 3, 4]; track i) {\r\n <div\r\n class=\"col-span-8 flex flex-col gap-1.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton height=\"4rem\" borderRadius=\"6px\" />\r\n </div>\r\n }\r\n </div>\r\n } @else if (previewEntities().length > 0) {\r\n <mt-entities-manage\r\n [(entities)]=\"previewEntities\"\r\n (entitiesReordered)=\"onEntitiesReordered($event)\"\r\n (entityResized)=\"onEntityResized($event)\"\r\n (entityClicked)=\"onEntityClicked($event)\"\r\n />\r\n } @else {\r\n <div\r\n class=\"flex flex-col items-center justify-center h-full text-muted-color gap-3 text-center px-4\"\r\n >\r\n <mt-icon\r\n name=\"general.eye-off\"\r\n size=\"xl\"\r\n class=\"opacity-40\"\r\n />\r\n <p class=\"text-sm font-medium\">{{ t(\"no-preview-items\") }}</p>\r\n <p class=\"text-xs\">{{ t(\"no-preview-items-hint\") }}</p>\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n </div>\r\n </mt-card>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%;width:100%;min-height:0;min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "icon", "iconPosition"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "size", "fluid", "disabled"], outputs: ["activeChange", "onChange"] }, { kind: "component", type: Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: EntitiesManage, selector: "mt-entities-manage", inputs: ["entities"], outputs: ["entitiesChange", "entitiesReordered", "entityClicked"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }] });
|
|
1093
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: Customization, isStandalone: true, selector: "mt-customization", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div\r\n class=\"flex gap-6 h-full w-full overflow-hidden max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:flex-col max-[1025px]:gap-4 max-[1025px]:overflow-x-hidden max-[1025px]:overflow-y-auto\"\r\n >\r\n <!-- \u2500\u2500\u2500 Left Sidebar: Attributes Panel \u2500\u2500\u2500 -->\r\n <mt-card\r\n class=\"w-1/5 h-full flex flex-col overflow-hidden pb-3 max-[1025px]:h-auto max-[1025px]:max-h-[42vh] max-[1025px]:min-h-0 max-[1025px]:w-full\"\r\n >\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <h3 class=\"text-xl font-semibold px-4 pt-5 pb-3\">\r\n {{ t(\"attributes\") }}\r\n </h3>\r\n\r\n <!-- Properties List -->\r\n <div class=\"flex-1 overflow-y-auto px-4 pb-4 space-y-3\">\r\n <!-- Search Field -->\r\n <div class=\"sticky top-0 bg-surface-0 mb-0 py-3 pb-2 mb-1 z-10\">\r\n <mt-text-field\r\n [placeholder]=\"t('search-properties')\"\r\n [(ngModel)]=\"searchQuery\"\r\n icon=\"general.search-lg\"\r\n />\r\n </div>\r\n @if (isLoading()) {\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <div class=\"flex items-center gap-1 p-1 rounded-lg\">\r\n <p-skeleton height=\"4rem\" />\r\n </div>\r\n }\r\n } @else {\r\n @for (prop of filteredProperties(); track prop.id) {\r\n @if (prop.viewType === \"LeafDetails\") {\r\n <!-- \u2500\u2500 LeafDetails: always first, title is \"Target Statuses\" inside the selector \u2500\u2500 -->\r\n <div\r\n class=\"rounded-lg border border-gray-300 border-dashed px-4 py-3\"\r\n >\r\n <mt-leaf-details-selector\r\n [property]=\"prop\"\r\n [leafLevels]=\"leafConfigsByProp()[prop.key] ?? {}\"\r\n [showEditAction]=\"prop.enabled && editableViewTypes.has(prop.viewType)\"\r\n (configChange)=\"onLeafConfigChange(prop, $event)\"\r\n (editRequested)=\"onEditProperty(prop)\"\r\n />\r\n </div>\r\n } @else {\r\n <!-- \u2500\u2500 Normal property toggle row \u2500\u2500 -->\r\n <div\r\n class=\"flex items-center gap-3 px-4 rounded-lg border border-gray-300 border-dashed hover:bg-emphasis transition-colors\"\r\n >\r\n <!-- Property Name -->\r\n <div class=\"flex-1 min-w-0 py-4\">\r\n <span\r\n class=\"text-sm font-medium truncate block\"\r\n [class.text-primary]=\"prop.enabled\"\r\n >\r\n {{ prop.name?.display }}\r\n </span>\r\n </div>\r\n\r\n <!-- Edit Button (visible when enabled and viewType has configurable fields) -->\r\n @if (prop.enabled && editableViewTypes.has(prop.viewType)) {\r\n <mt-button\r\n icon=\"editor.pencil-01\"\r\n variant=\"text\"\r\n severity=\"primary\"\r\n (onClick)=\"onEditProperty(prop)\"\r\n />\r\n }\r\n\r\n <!-- Toggle -->\r\n <mt-toggle-field\r\n [ngModel]=\"prop.enabled\"\r\n (ngModelChange)=\"onPropertyToggle(prop, $event)\"\r\n class=\"shrink-0\"\r\n size=\"small\"\r\n ></mt-toggle-field>\r\n </div>\r\n }\r\n }\r\n\r\n @if (filteredProperties().length === 0) {\r\n <div class=\"py-8 text-center text-muted-color\">\r\n <p class=\"text-sm\">{{ t(\"no-properties\") }}</p>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n\r\n <!-- \u2500\u2500\u2500 Main Area: Preview \u2500\u2500\u2500 -->\r\n <div\r\n class=\"w-3/5 flex flex-col gap-4 h-full overflow-hidden items-center max-[1025px]:h-auto max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:w-full max-[1025px]:flex-1 max-[1025px]:items-stretch max-[1025px]:overflow-visible max-[1025px]:pb-4\"\r\n >\r\n <!-- Area Tabs -->\r\n @if (isLoadingAreas()) {\r\n <div class=\"flex gap-2 py-1\">\r\n @for (i of [1, 2, 3]; track i) {\r\n <p-skeleton width=\"6rem\" height=\"2.25rem\" borderRadius=\"8px\" />\r\n }\r\n </div>\r\n } @else if (areasTabs().length > 0) {\r\n <div class=\"max-[1025px]:w-full\">\r\n <div\r\n class=\"max-[1025px]:w-full max-[1025px]:min-w-0 max-[1025px]:overflow-x-auto max-[1025px]:pb-1\"\r\n >\r\n <mt-tabs\r\n class=\"max-[1025px]:min-w-max\"\r\n [options]=\"areasTabs()\"\r\n [(active)]=\"activeArea\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Entities Preview -->\r\n <mt-card\r\n class=\"w-130 flex-1 overflow-hidden max-[1025px]:w-full max-[1025px]:min-h-[24rem]\"\r\n >\r\n <div>\r\n <ng-template #headless>\r\n <div class=\"overflow-auto h-full my-2 py-3 px-4\">\r\n @if (isLoadingPreview()) {\r\n <div\r\n class=\"grid grid-cols-24 gap-x-4 gap-y-5 p-4 max-[640px]:grid-cols-1 max-[640px]:gap-4\"\r\n >\r\n <div\r\n class=\"col-span-20 flex items-center gap-2.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton size=\"4rem\" shape=\"circle\" />\r\n <div class=\"flex flex-col gap-1 flex-1\">\r\n <p-skeleton height=\"2.5rem\" borderRadius=\"4px\" />\r\n </div>\r\n </div>\r\n @for (i of [1, 2, 3, 4]; track i) {\r\n <div\r\n class=\"col-span-8 flex flex-col gap-1.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton height=\"4rem\" borderRadius=\"6px\" />\r\n </div>\r\n }\r\n </div>\r\n } @else if (previewEntities().length > 0) {\r\n <mt-entities-manage\r\n [(entities)]=\"previewEntities\"\r\n (entitiesReordered)=\"onEntitiesReordered($event)\"\r\n (entityResized)=\"onEntityResized($event)\"\r\n (entityClicked)=\"onEntityClicked($event)\"\r\n />\r\n } @else {\r\n <div\r\n class=\"flex flex-col items-center justify-center h-full text-muted-color gap-3 text-center px-4\"\r\n >\r\n <mt-icon\r\n name=\"general.eye-off\"\r\n size=\"xl\"\r\n class=\"opacity-40\"\r\n />\r\n <p class=\"text-sm font-medium\">{{ t(\"no-preview-items\") }}</p>\r\n <p class=\"text-xs\">{{ t(\"no-preview-items-hint\") }}</p>\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n </div>\r\n </mt-card>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%;width:100%;min-height:0;min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "icon", "iconPosition"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "size", "fluid", "disabled"], outputs: ["activeChange", "onChange"] }, { kind: "component", type: Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: EntitiesManage, selector: "mt-entities-manage", inputs: ["entities"], outputs: ["entitiesChange", "entitiesReordered", "entityClicked"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: LeafDetailsSelector, selector: "mt-leaf-details-selector", inputs: ["property", "leafLevels", "showEditAction"], outputs: ["configChange", "editRequested"] }] });
|
|
881
1094
|
}
|
|
882
1095
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: Customization, decorators: [{
|
|
883
1096
|
type: Component,
|
|
@@ -892,7 +1105,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
892
1105
|
Skeleton,
|
|
893
1106
|
EntitiesManage,
|
|
894
1107
|
Icon,
|
|
895
|
-
|
|
1108
|
+
LeafDetailsSelector,
|
|
1109
|
+
], template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\r\n <div\r\n class=\"flex gap-6 h-full w-full overflow-hidden max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:flex-col max-[1025px]:gap-4 max-[1025px]:overflow-x-hidden max-[1025px]:overflow-y-auto\"\r\n >\r\n <!-- \u2500\u2500\u2500 Left Sidebar: Attributes Panel \u2500\u2500\u2500 -->\r\n <mt-card\r\n class=\"w-1/5 h-full flex flex-col overflow-hidden pb-3 max-[1025px]:h-auto max-[1025px]:max-h-[42vh] max-[1025px]:min-h-0 max-[1025px]:w-full\"\r\n >\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <h3 class=\"text-xl font-semibold px-4 pt-5 pb-3\">\r\n {{ t(\"attributes\") }}\r\n </h3>\r\n\r\n <!-- Properties List -->\r\n <div class=\"flex-1 overflow-y-auto px-4 pb-4 space-y-3\">\r\n <!-- Search Field -->\r\n <div class=\"sticky top-0 bg-surface-0 mb-0 py-3 pb-2 mb-1 z-10\">\r\n <mt-text-field\r\n [placeholder]=\"t('search-properties')\"\r\n [(ngModel)]=\"searchQuery\"\r\n icon=\"general.search-lg\"\r\n />\r\n </div>\r\n @if (isLoading()) {\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <div class=\"flex items-center gap-1 p-1 rounded-lg\">\r\n <p-skeleton height=\"4rem\" />\r\n </div>\r\n }\r\n } @else {\r\n @for (prop of filteredProperties(); track prop.id) {\r\n @if (prop.viewType === \"LeafDetails\") {\r\n <!-- \u2500\u2500 LeafDetails: always first, title is \"Target Statuses\" inside the selector \u2500\u2500 -->\r\n <div\r\n class=\"rounded-lg border border-gray-300 border-dashed px-4 py-3\"\r\n >\r\n <mt-leaf-details-selector\r\n [property]=\"prop\"\r\n [leafLevels]=\"leafConfigsByProp()[prop.key] ?? {}\"\r\n [showEditAction]=\"prop.enabled && editableViewTypes.has(prop.viewType)\"\r\n (configChange)=\"onLeafConfigChange(prop, $event)\"\r\n (editRequested)=\"onEditProperty(prop)\"\r\n />\r\n </div>\r\n } @else {\r\n <!-- \u2500\u2500 Normal property toggle row \u2500\u2500 -->\r\n <div\r\n class=\"flex items-center gap-3 px-4 rounded-lg border border-gray-300 border-dashed hover:bg-emphasis transition-colors\"\r\n >\r\n <!-- Property Name -->\r\n <div class=\"flex-1 min-w-0 py-4\">\r\n <span\r\n class=\"text-sm font-medium truncate block\"\r\n [class.text-primary]=\"prop.enabled\"\r\n >\r\n {{ prop.name?.display }}\r\n </span>\r\n </div>\r\n\r\n <!-- Edit Button (visible when enabled and viewType has configurable fields) -->\r\n @if (prop.enabled && editableViewTypes.has(prop.viewType)) {\r\n <mt-button\r\n icon=\"editor.pencil-01\"\r\n variant=\"text\"\r\n severity=\"primary\"\r\n (onClick)=\"onEditProperty(prop)\"\r\n />\r\n }\r\n\r\n <!-- Toggle -->\r\n <mt-toggle-field\r\n [ngModel]=\"prop.enabled\"\r\n (ngModelChange)=\"onPropertyToggle(prop, $event)\"\r\n class=\"shrink-0\"\r\n size=\"small\"\r\n ></mt-toggle-field>\r\n </div>\r\n }\r\n }\r\n\r\n @if (filteredProperties().length === 0) {\r\n <div class=\"py-8 text-center text-muted-color\">\r\n <p class=\"text-sm\">{{ t(\"no-properties\") }}</p>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n\r\n <!-- \u2500\u2500\u2500 Main Area: Preview \u2500\u2500\u2500 -->\r\n <div\r\n class=\"w-3/5 flex flex-col gap-4 h-full overflow-hidden items-center max-[1025px]:h-auto max-[1025px]:min-h-0 max-[1025px]:min-w-0 max-[1025px]:w-full max-[1025px]:flex-1 max-[1025px]:items-stretch max-[1025px]:overflow-visible max-[1025px]:pb-4\"\r\n >\r\n <!-- Area Tabs -->\r\n @if (isLoadingAreas()) {\r\n <div class=\"flex gap-2 py-1\">\r\n @for (i of [1, 2, 3]; track i) {\r\n <p-skeleton width=\"6rem\" height=\"2.25rem\" borderRadius=\"8px\" />\r\n }\r\n </div>\r\n } @else if (areasTabs().length > 0) {\r\n <div class=\"max-[1025px]:w-full\">\r\n <div\r\n class=\"max-[1025px]:w-full max-[1025px]:min-w-0 max-[1025px]:overflow-x-auto max-[1025px]:pb-1\"\r\n >\r\n <mt-tabs\r\n class=\"max-[1025px]:min-w-max\"\r\n [options]=\"areasTabs()\"\r\n [(active)]=\"activeArea\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Entities Preview -->\r\n <mt-card\r\n class=\"w-130 flex-1 overflow-hidden max-[1025px]:w-full max-[1025px]:min-h-[24rem]\"\r\n >\r\n <div>\r\n <ng-template #headless>\r\n <div class=\"overflow-auto h-full my-2 py-3 px-4\">\r\n @if (isLoadingPreview()) {\r\n <div\r\n class=\"grid grid-cols-24 gap-x-4 gap-y-5 p-4 max-[640px]:grid-cols-1 max-[640px]:gap-4\"\r\n >\r\n <div\r\n class=\"col-span-20 flex items-center gap-2.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton size=\"4rem\" shape=\"circle\" />\r\n <div class=\"flex flex-col gap-1 flex-1\">\r\n <p-skeleton height=\"2.5rem\" borderRadius=\"4px\" />\r\n </div>\r\n </div>\r\n @for (i of [1, 2, 3, 4]; track i) {\r\n <div\r\n class=\"col-span-8 flex flex-col gap-1.5 p-3 max-[640px]:col-span-1\"\r\n >\r\n <p-skeleton height=\"4rem\" borderRadius=\"6px\" />\r\n </div>\r\n }\r\n </div>\r\n } @else if (previewEntities().length > 0) {\r\n <mt-entities-manage\r\n [(entities)]=\"previewEntities\"\r\n (entitiesReordered)=\"onEntitiesReordered($event)\"\r\n (entityResized)=\"onEntityResized($event)\"\r\n (entityClicked)=\"onEntityClicked($event)\"\r\n />\r\n } @else {\r\n <div\r\n class=\"flex flex-col items-center justify-center h-full text-muted-color gap-3 text-center px-4\"\r\n >\r\n <mt-icon\r\n name=\"general.eye-off\"\r\n size=\"xl\"\r\n class=\"opacity-40\"\r\n />\r\n <p class=\"text-sm font-medium\">{{ t(\"no-preview-items\") }}</p>\r\n <p class=\"text-xs\">{{ t(\"no-preview-items-hint\") }}</p>\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n </div>\r\n </mt-card>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%;width:100%;min-height:0;min-width:0}\n"] }]
|
|
896
1110
|
}], ctorParameters: () => [] });
|
|
897
1111
|
|
|
898
1112
|
/**
|