@masterteam/customization 0.0.14 → 0.0.15
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';
|
|
@@ -494,6 +494,57 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
494
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 (all entity types) -->\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 <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 <!-- Align End Toggle (all entity types) -->\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 <!-- 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
495
|
}], 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
496
|
|
|
497
|
+
class LeafDetailsSelector {
|
|
498
|
+
property = input.required(...(ngDevMode ? [{ debugName: "property" }] : /* istanbul ignore next */ []));
|
|
499
|
+
leafLevels = input({}, ...(ngDevMode ? [{ debugName: "leafLevels" }] : /* istanbul ignore next */ []));
|
|
500
|
+
configChange = output();
|
|
501
|
+
levels = computed(() => this.property().configuration ?? [], ...(ngDevMode ? [{ debugName: "levels" }] : /* istanbul ignore next */ []));
|
|
502
|
+
isLevelEnabled(levelId) {
|
|
503
|
+
return this.leafLevels()[String(levelId)] != null;
|
|
504
|
+
}
|
|
505
|
+
isStatusSelected(levelId, statusKey) {
|
|
506
|
+
return this.leafLevels()[String(levelId)]?.selectedStatuses.includes(statusKey) ?? false;
|
|
507
|
+
}
|
|
508
|
+
/** Section toggle — show or hide the entire level in the preview */
|
|
509
|
+
toggleLevel(levelId, enabled) {
|
|
510
|
+
const current = { ...this.leafLevels() };
|
|
511
|
+
if (enabled) {
|
|
512
|
+
const level = this.levels().find((l) => l.levelId === levelId);
|
|
513
|
+
current[String(levelId)] = {
|
|
514
|
+
order: Object.keys(current).length + 1,
|
|
515
|
+
selectedStatuses: level?.statuses.map((s) => s.key) ?? [],
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
delete current[String(levelId)];
|
|
520
|
+
}
|
|
521
|
+
this.configChange.emit(current);
|
|
522
|
+
}
|
|
523
|
+
/** Status toggle — show or hide a single status badge in the level preview */
|
|
524
|
+
toggleStatus(levelId, statusKey) {
|
|
525
|
+
const current = { ...this.leafLevels() };
|
|
526
|
+
const saved = current[String(levelId)];
|
|
527
|
+
if (!saved)
|
|
528
|
+
return;
|
|
529
|
+
const idx = saved.selectedStatuses.indexOf(statusKey);
|
|
530
|
+
current[String(levelId)] = {
|
|
531
|
+
...saved,
|
|
532
|
+
selectedStatuses: idx >= 0
|
|
533
|
+
? saved.selectedStatuses.filter((k) => k !== statusKey)
|
|
534
|
+
: [...saved.selectedStatuses, statusKey],
|
|
535
|
+
};
|
|
536
|
+
this.configChange.emit(current);
|
|
537
|
+
}
|
|
538
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: LeafDetailsSelector, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
539
|
+
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 } }, outputs: { configChange: "configChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\n <div class=\"space-y-2 pt-1\">\n\n <p class=\"text-sm font-semibold text-color px-1\">\n {{ t('leaf-details-target-statuses') }}\n </p>\n\n @for (level of levels(); track $index) {\n <div class=\"rounded-lg border border-surface-200 overflow-hidden\">\n\n <!-- Level header: icon + name | section toggle (show/hide this level) -->\n <div class=\"flex items-center gap-2 px-3 py-2.5\">\n <mt-icon [name]=\"level.levelIcon\" size=\"xs\" class=\"text-color-secondary shrink-0\" />\n <span class=\"flex-1 text-sm font-medium truncate\">{{ level.levelName }}</span>\n <mt-toggle-field\n [ngModel]=\"isLevelEnabled(level.levelId)\"\n (ngModelChange)=\"toggleLevel(level.levelId, $event)\"\n size=\"small\"\n class=\"shrink-0\"\n />\n </div>\n\n <!-- Status rows: each toggle shows/hides that status badge in the preview -->\n @if (isLevelEnabled(level.levelId)) {\n <div class=\"border-t border-surface-200 divide-y divide-surface-100\">\n @for (status of level.statuses; track $index) {\n <div class=\"flex items-center gap-2.5 px-3 py-2\">\n <span\n class=\"w-2.5 h-2.5 rounded-full shrink-0 border border-black/10\"\n [style.background-color]=\"status.color ?? '#9CA3AF'\"\n ></span>\n <span class=\"text-sm flex-1 truncate\">{{ status.display }}</span>\n <mt-toggle-field\n [ngModel]=\"isStatusSelected(level.levelId, status.key)\"\n (ngModelChange)=\"toggleStatus(level.levelId, status.key)\"\n size=\"small\"\n class=\"shrink-0\"\n />\n </div>\n }\n </div>\n }\n\n </div>\n }\n </div>\n</ng-container>\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"] }] });
|
|
540
|
+
}
|
|
541
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: LeafDetailsSelector, decorators: [{
|
|
542
|
+
type: Component,
|
|
543
|
+
args: [{ selector: 'mt-leaf-details-selector', standalone: true, imports: [FormsModule, TranslocoDirective, Icon, ToggleField], template: "<ng-container *transloco=\"let t; prefix: 'customization'\">\n <div class=\"space-y-2 pt-1\">\n\n <p class=\"text-sm font-semibold text-color px-1\">\n {{ t('leaf-details-target-statuses') }}\n </p>\n\n @for (level of levels(); track $index) {\n <div class=\"rounded-lg border border-surface-200 overflow-hidden\">\n\n <!-- Level header: icon + name | section toggle (show/hide this level) -->\n <div class=\"flex items-center gap-2 px-3 py-2.5\">\n <mt-icon [name]=\"level.levelIcon\" size=\"xs\" class=\"text-color-secondary shrink-0\" />\n <span class=\"flex-1 text-sm font-medium truncate\">{{ level.levelName }}</span>\n <mt-toggle-field\n [ngModel]=\"isLevelEnabled(level.levelId)\"\n (ngModelChange)=\"toggleLevel(level.levelId, $event)\"\n size=\"small\"\n class=\"shrink-0\"\n />\n </div>\n\n <!-- Status rows: each toggle shows/hides that status badge in the preview -->\n @if (isLevelEnabled(level.levelId)) {\n <div class=\"border-t border-surface-200 divide-y divide-surface-100\">\n @for (status of level.statuses; track $index) {\n <div class=\"flex items-center gap-2.5 px-3 py-2\">\n <span\n class=\"w-2.5 h-2.5 rounded-full shrink-0 border border-black/10\"\n [style.background-color]=\"status.color ?? '#9CA3AF'\"\n ></span>\n <span class=\"text-sm flex-1 truncate\">{{ status.display }}</span>\n <mt-toggle-field\n [ngModel]=\"isStatusSelected(level.levelId, status.key)\"\n (ngModelChange)=\"toggleStatus(level.levelId, status.key)\"\n size=\"small\"\n class=\"shrink-0\"\n />\n </div>\n }\n </div>\n }\n\n </div>\n }\n </div>\n</ng-container>\n" }]
|
|
544
|
+
}], propDecorators: { property: [{ type: i0.Input, args: [{ isSignal: true, alias: "property", required: true }] }], leafLevels: [{ type: i0.Input, args: [{ isSignal: true, alias: "leafLevels", required: false }] }], configChange: [{ type: i0.Output, args: ["configChange"] }] } });
|
|
545
|
+
|
|
546
|
+
/** Separator used to build synthetic entity keys for LeafDetails sub-entities */
|
|
547
|
+
const LEAF_DETAILS_SEP = '::leaflevel::';
|
|
497
548
|
class Customization {
|
|
498
549
|
facade = inject(CustomizationFacade);
|
|
499
550
|
modalService = inject(ModalService);
|
|
@@ -534,15 +585,39 @@ class Customization {
|
|
|
534
585
|
filteredProperties = computed(() => {
|
|
535
586
|
const query = this.searchQuery().toLowerCase().trim();
|
|
536
587
|
const list = this.propertiesWithEnabled();
|
|
588
|
+
// LeafDetails always first in the list regardless of active state
|
|
589
|
+
const leaf = list.filter((p) => p.viewType === 'LeafDetails');
|
|
590
|
+
const regular = list.filter((p) => p.viewType !== 'LeafDetails');
|
|
591
|
+
const ordered = [...leaf, ...regular];
|
|
537
592
|
if (!query)
|
|
538
|
-
return
|
|
539
|
-
return
|
|
593
|
+
return ordered;
|
|
594
|
+
return ordered.filter((prop) => {
|
|
540
595
|
const name = typeof prop.name === 'string'
|
|
541
596
|
? prop.name
|
|
542
597
|
: Object.values(prop.name).join(' ');
|
|
543
598
|
return name.toLowerCase().includes(query);
|
|
544
599
|
});
|
|
545
600
|
}, ...(ngDevMode ? [{ debugName: "filteredProperties" }] : /* istanbul ignore next */ []));
|
|
601
|
+
/**
|
|
602
|
+
* LeafDetails saved configs indexed by propertyKey for the active area.
|
|
603
|
+
* Used by the inline selector in the property list.
|
|
604
|
+
*/
|
|
605
|
+
leafConfigsByProp = computed(() => {
|
|
606
|
+
const configs = this.validConfigurations();
|
|
607
|
+
const area = this.activeArea();
|
|
608
|
+
if (!area)
|
|
609
|
+
return {};
|
|
610
|
+
const result = {};
|
|
611
|
+
for (const config of configs) {
|
|
612
|
+
if (config.areaKey !== area)
|
|
613
|
+
continue;
|
|
614
|
+
const leafLevels = config.configuration?.['leafLevels'];
|
|
615
|
+
if (leafLevels && typeof leafLevels === 'object' && !Array.isArray(leafLevels)) {
|
|
616
|
+
result[config.propertyKey] = leafLevels;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return result;
|
|
620
|
+
}, ...(ngDevMode ? [{ debugName: "leafConfigsByProp" }] : /* istanbul ignore next */ []));
|
|
546
621
|
/** Preview entities built from configurations + properties for the active area */
|
|
547
622
|
previewEntities = signal([], ...(ngDevMode ? [{ debugName: "previewEntities" }] : /* istanbul ignore next */ []));
|
|
548
623
|
constructor() {
|
|
@@ -563,25 +638,35 @@ class Customization {
|
|
|
563
638
|
return;
|
|
564
639
|
}
|
|
565
640
|
const areaConfigs = configs.filter((c) => c.areaKey === area);
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
641
|
+
const all = areaConfigs.flatMap((config) => this.buildEntitiesFromConfig(config, props));
|
|
642
|
+
// Regular entities come first, LeafDetails always last.
|
|
643
|
+
// Orders are reassigned sequentially so EntitiesManage's internal sort stays consistent.
|
|
644
|
+
const regular = all
|
|
645
|
+
.filter((e) => !e.key?.includes(LEAF_DETAILS_SEP))
|
|
646
|
+
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
647
|
+
const leafEntities = all
|
|
648
|
+
.filter((e) => e.key?.includes(LEAF_DETAILS_SEP))
|
|
569
649
|
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
650
|
+
const entities = [
|
|
651
|
+
...regular.map((e, i) => ({ ...e, order: i + 1 })),
|
|
652
|
+
...leafEntities.map((e, i) => ({ ...e, order: regular.length + i + 1 })),
|
|
653
|
+
];
|
|
570
654
|
this.previewEntities.set(entities);
|
|
571
655
|
});
|
|
572
656
|
}
|
|
573
657
|
ngOnInit() {
|
|
574
|
-
// Load display areas
|
|
575
658
|
this.facade.loadAreas();
|
|
576
659
|
this.facade.loadProperties();
|
|
577
660
|
this.facade.loadConfigurations();
|
|
578
661
|
}
|
|
579
662
|
onPropertyToggle(prop, enabled) {
|
|
663
|
+
// LeafDetails is managed entirely via the inline status selector — skip
|
|
664
|
+
if (prop.viewType === 'LeafDetails')
|
|
665
|
+
return;
|
|
580
666
|
if (enabled) {
|
|
581
667
|
this.activateProperty(prop, this.editableViewTypes.has(prop.viewType));
|
|
582
668
|
return;
|
|
583
669
|
}
|
|
584
|
-
// Remove this property and bulk-replace with remaining checked props
|
|
585
670
|
this.bulkSaveWithout(prop.key);
|
|
586
671
|
}
|
|
587
672
|
onEditProperty(prop) {
|
|
@@ -596,6 +681,9 @@ class Customization {
|
|
|
596
681
|
});
|
|
597
682
|
}
|
|
598
683
|
onEntityClicked(entity) {
|
|
684
|
+
// LeafDetails sub-entities are configured inline — not via drawer
|
|
685
|
+
if (entity.key?.includes(LEAF_DETAILS_SEP))
|
|
686
|
+
return;
|
|
599
687
|
const area = this.activeArea();
|
|
600
688
|
if (!area || !entity.key)
|
|
601
689
|
return;
|
|
@@ -616,44 +704,174 @@ class Customization {
|
|
|
616
704
|
if (!area)
|
|
617
705
|
return;
|
|
618
706
|
const configs = this.validConfigurations();
|
|
619
|
-
//
|
|
707
|
+
// ── LeafDetails sub-entity resize ──
|
|
708
|
+
if (event.entity.key?.includes(LEAF_DETAILS_SEP)) {
|
|
709
|
+
const [propKey, levelId] = event.entity.key.split(LEAF_DETAILS_SEP);
|
|
710
|
+
const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === propKey);
|
|
711
|
+
const leafLevels = {
|
|
712
|
+
...(existingConfig?.configuration?.['leafLevels'] ?? {}),
|
|
713
|
+
};
|
|
714
|
+
if (leafLevels[levelId]) {
|
|
715
|
+
leafLevels[levelId] = { ...leafLevels[levelId], size: event.newSize };
|
|
716
|
+
}
|
|
717
|
+
const updatedConfigs = configs.map((c) => c.areaKey === area && c.propertyKey === propKey
|
|
718
|
+
? { ...c, configuration: { ...c.configuration, leafLevels } }
|
|
719
|
+
: c);
|
|
720
|
+
this.facade.bulkReplaceConfigurations(this.groupConfigsByProperty(updatedConfigs)).subscribe();
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
// ── Regular entity resize ──
|
|
620
724
|
const updatedConfigs = configs.map((c) => c.areaKey === area && c.propertyKey === event.entity.key
|
|
621
725
|
? { ...c, configuration: { ...c.configuration, size: event.newSize } }
|
|
622
726
|
: c);
|
|
623
|
-
|
|
624
|
-
const items = this.groupConfigsByProperty(updatedConfigs);
|
|
625
|
-
this.facade.bulkReplaceConfigurations(items).subscribe();
|
|
727
|
+
this.facade.bulkReplaceConfigurations(this.groupConfigsByProperty(updatedConfigs)).subscribe();
|
|
626
728
|
}
|
|
627
729
|
onEntitiesReordered(entities) {
|
|
628
730
|
const area = this.activeArea();
|
|
629
731
|
if (!area)
|
|
630
732
|
return;
|
|
631
733
|
const configs = this.validConfigurations();
|
|
632
|
-
|
|
734
|
+
// Separate regular entities from LeafDetails sub-entities
|
|
735
|
+
const regularEntities = entities.filter((e) => !e.key?.includes(LEAF_DETAILS_SEP));
|
|
736
|
+
const leafEntities = entities.filter((e) => e.key?.includes(LEAF_DETAILS_SEP));
|
|
737
|
+
// Regular entities → BulkReplaceItems
|
|
738
|
+
const regularItems = regularEntities.map((entity) => {
|
|
633
739
|
const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === entity.key);
|
|
634
740
|
return {
|
|
635
741
|
propertyKey: entity.key,
|
|
636
742
|
displayAreas: [
|
|
637
743
|
{
|
|
638
744
|
areaKey: area,
|
|
639
|
-
order:
|
|
745
|
+
order: entity.order,
|
|
640
746
|
configuration: existingConfig?.configuration ?? {},
|
|
641
747
|
},
|
|
642
748
|
],
|
|
643
749
|
};
|
|
644
750
|
});
|
|
645
|
-
//
|
|
646
|
-
const
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
751
|
+
// Leaf entities → group by parent propertyKey, update per-level order
|
|
752
|
+
const leafByProp = new Map();
|
|
753
|
+
for (const entity of leafEntities) {
|
|
754
|
+
const [propKey, levelId] = entity.key.split(LEAF_DETAILS_SEP);
|
|
755
|
+
if (!leafByProp.has(propKey))
|
|
756
|
+
leafByProp.set(propKey, []);
|
|
757
|
+
leafByProp.get(propKey).push({ levelId, order: entity.order });
|
|
758
|
+
}
|
|
759
|
+
const leafItems = [];
|
|
760
|
+
for (const [propKey, levelUpdates] of leafByProp) {
|
|
761
|
+
const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === propKey);
|
|
762
|
+
const leafLevels = {
|
|
763
|
+
...(existingConfig?.configuration?.['leafLevels'] ?? {}),
|
|
764
|
+
};
|
|
765
|
+
for (const { levelId, order } of levelUpdates) {
|
|
766
|
+
if (leafLevels[levelId]) {
|
|
767
|
+
leafLevels[levelId] = { ...leafLevels[levelId], order };
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
const minOrder = Math.min(...levelUpdates.map((l) => l.order));
|
|
771
|
+
leafItems.push({
|
|
772
|
+
propertyKey: propKey,
|
|
773
|
+
displayAreas: [
|
|
774
|
+
{
|
|
775
|
+
areaKey: area,
|
|
776
|
+
order: existingConfig?.order ?? minOrder,
|
|
777
|
+
configuration: { ...existingConfig?.configuration, leafLevels },
|
|
778
|
+
},
|
|
779
|
+
],
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
// Configs from other areas (preserve as-is)
|
|
783
|
+
const otherAreaItems = this.groupConfigsByProperty(configs.filter((c) => c.areaKey !== area));
|
|
784
|
+
this.facade
|
|
785
|
+
.bulkReplaceConfigurations(this.mergeItems([...regularItems, ...leafItems], otherAreaItems))
|
|
786
|
+
.subscribe();
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Called by the inline LeafDetailsSelector when the user changes status selections.
|
|
790
|
+
* Immediately persists the updated leafLevels configuration.
|
|
791
|
+
*/
|
|
792
|
+
onLeafConfigChange(prop, leafLevels) {
|
|
793
|
+
const area = this.activeArea();
|
|
794
|
+
if (!area)
|
|
795
|
+
return;
|
|
796
|
+
const configs = this.validConfigurations();
|
|
797
|
+
const existingConfig = configs.find((c) => c.areaKey === area && c.propertyKey === prop.key);
|
|
798
|
+
// Ensure every level has an order assigned
|
|
799
|
+
let nextOrder = configs.filter((c) => c.areaKey === area && c.propertyKey !== prop.key).length + 1;
|
|
800
|
+
const normalizedLevels = {};
|
|
801
|
+
for (const [levelId, levelCfg] of Object.entries(leafLevels)) {
|
|
802
|
+
normalizedLevels[levelId] = levelCfg.order
|
|
803
|
+
? levelCfg
|
|
804
|
+
: { ...levelCfg, order: nextOrder++ };
|
|
805
|
+
}
|
|
806
|
+
// Other configs excluding this leaf property in the current area
|
|
807
|
+
const otherItems = this.groupConfigsByProperty(configs.filter((c) => !(c.areaKey === area && c.propertyKey === prop.key)));
|
|
808
|
+
if (Object.keys(normalizedLevels).length === 0) {
|
|
809
|
+
// All levels deselected — remove property from display config
|
|
810
|
+
this.facade.bulkReplaceConfigurations(otherItems).subscribe();
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
const minOrder = Math.min(...Object.values(normalizedLevels).map((l) => l.order));
|
|
814
|
+
const newItem = {
|
|
815
|
+
propertyKey: prop.key,
|
|
816
|
+
displayAreas: [
|
|
817
|
+
{
|
|
818
|
+
areaKey: area,
|
|
819
|
+
order: existingConfig?.order ?? minOrder,
|
|
820
|
+
configuration: { leafLevels: normalizedLevels },
|
|
821
|
+
},
|
|
822
|
+
],
|
|
823
|
+
};
|
|
824
|
+
this.facade.bulkReplaceConfigurations(this.mergeItems([newItem], otherItems)).subscribe();
|
|
651
825
|
}
|
|
652
826
|
// ── Private helpers ──
|
|
653
827
|
/**
|
|
654
|
-
*
|
|
655
|
-
*
|
|
828
|
+
* Builds zero or more EntityData entries from a single DisplayConfiguration.
|
|
829
|
+
* Regular properties → one entity. LeafDetails → one entity per enabled level.
|
|
656
830
|
*/
|
|
831
|
+
buildEntitiesFromConfig(config, props) {
|
|
832
|
+
const prop = props.find((p) => p.key === config.propertyKey);
|
|
833
|
+
if (!prop)
|
|
834
|
+
return [];
|
|
835
|
+
if (prop.viewType === 'LeafDetails') {
|
|
836
|
+
return this.buildLeafDetailsEntities(config, prop);
|
|
837
|
+
}
|
|
838
|
+
const entity = this.buildEntityFromConfig(config, props);
|
|
839
|
+
return entity ? [entity] : [];
|
|
840
|
+
}
|
|
841
|
+
/** Builds one EntityData per enabled level from a LeafDetails display config. */
|
|
842
|
+
buildLeafDetailsEntities(config, prop) {
|
|
843
|
+
const levels = prop.configuration ?? [];
|
|
844
|
+
const leafLevels = config.configuration?.['leafLevels'];
|
|
845
|
+
if (!leafLevels)
|
|
846
|
+
return [];
|
|
847
|
+
const result = [];
|
|
848
|
+
for (const level of levels) {
|
|
849
|
+
const levelCfg = leafLevels[String(level.levelId)];
|
|
850
|
+
if (!levelCfg)
|
|
851
|
+
continue;
|
|
852
|
+
const selectedKeys = new Set(levelCfg.selectedStatuses);
|
|
853
|
+
const value = {
|
|
854
|
+
levelId: level.levelId,
|
|
855
|
+
levelName: level.levelName,
|
|
856
|
+
levelIcon: level.levelIcon,
|
|
857
|
+
statuses: level.statuses
|
|
858
|
+
.filter((s) => selectedKeys.has(s.key))
|
|
859
|
+
.map((s) => ({ key: s.key, display: s.display, color: s.color, count: 0 })),
|
|
860
|
+
};
|
|
861
|
+
result.push({
|
|
862
|
+
id: prop.id,
|
|
863
|
+
propertyId: prop.id,
|
|
864
|
+
key: `${prop.key}${LEAF_DETAILS_SEP}${level.levelId}`,
|
|
865
|
+
name: level.levelName,
|
|
866
|
+
// Cast needed until entities package is rebuilt with LeafDetailsEntityValue in the union
|
|
867
|
+
value: value,
|
|
868
|
+
viewType: 'LeafDetails',
|
|
869
|
+
order: levelCfg.order,
|
|
870
|
+
configuration: levelCfg.size ? { size: levelCfg.size } : undefined,
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
return result;
|
|
874
|
+
}
|
|
657
875
|
activateProperty(prop, openConfigAfterSave = false) {
|
|
658
876
|
const area = this.activeArea();
|
|
659
877
|
if (!area)
|
|
@@ -672,60 +890,38 @@ class Customization {
|
|
|
672
890
|
const areaConfigs = configs.filter((c) => c.areaKey === area);
|
|
673
891
|
const nextOrder = areaConfigs.length + 1;
|
|
674
892
|
const configuration = {};
|
|
675
|
-
const newDisplayArea = {
|
|
676
|
-
areaKey: area,
|
|
677
|
-
order: nextOrder,
|
|
678
|
-
configuration,
|
|
679
|
-
};
|
|
680
|
-
// Build bulk items from existing configs + new property
|
|
681
893
|
const itemsMap = new Map();
|
|
682
894
|
for (const c of configs) {
|
|
683
|
-
if (!itemsMap.has(c.propertyKey))
|
|
895
|
+
if (!itemsMap.has(c.propertyKey))
|
|
684
896
|
itemsMap.set(c.propertyKey, []);
|
|
685
|
-
}
|
|
686
897
|
itemsMap.get(c.propertyKey).push({
|
|
687
898
|
areaKey: c.areaKey,
|
|
688
899
|
order: c.order,
|
|
689
900
|
configuration: c.configuration,
|
|
690
901
|
});
|
|
691
902
|
}
|
|
692
|
-
|
|
693
|
-
if (!itemsMap.has(prop.key)) {
|
|
903
|
+
if (!itemsMap.has(prop.key))
|
|
694
904
|
itemsMap.set(prop.key, []);
|
|
695
|
-
}
|
|
696
|
-
itemsMap.get(prop.key).push(newDisplayArea);
|
|
905
|
+
itemsMap.get(prop.key).push({ areaKey: area, order: nextOrder, configuration });
|
|
697
906
|
const items = Array.from(itemsMap.entries()).map(([propertyKey, displayAreas]) => ({ propertyKey, displayAreas }));
|
|
698
907
|
this.facade.bulkReplaceConfigurations(items).subscribe({
|
|
699
908
|
next: () => {
|
|
700
|
-
if (
|
|
701
|
-
|
|
909
|
+
if (openConfigAfterSave) {
|
|
910
|
+
this.openDrawer(prop, { currentOrder: nextOrder, configuration });
|
|
702
911
|
}
|
|
703
|
-
this.openDrawer(prop, {
|
|
704
|
-
currentOrder: nextOrder,
|
|
705
|
-
configuration,
|
|
706
|
-
});
|
|
707
912
|
},
|
|
708
913
|
});
|
|
709
914
|
}
|
|
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
915
|
bulkSaveWithout(excludePropertyKey) {
|
|
715
916
|
const configs = this.validConfigurations();
|
|
716
|
-
const
|
|
717
|
-
const items = this.groupConfigsByProperty(remaining);
|
|
917
|
+
const items = this.groupConfigsByProperty(configs.filter((c) => c.propertyKey !== excludePropertyKey));
|
|
718
918
|
this.facade.bulkReplaceConfigurations(items).subscribe();
|
|
719
919
|
}
|
|
720
|
-
/**
|
|
721
|
-
* Group flat DisplayConfiguration[] into bulk-replace items grouped by propertyKey.
|
|
722
|
-
*/
|
|
723
920
|
groupConfigsByProperty(configs) {
|
|
724
921
|
const map = new Map();
|
|
725
922
|
for (const c of configs) {
|
|
726
|
-
if (!map.has(c.propertyKey))
|
|
923
|
+
if (!map.has(c.propertyKey))
|
|
727
924
|
map.set(c.propertyKey, []);
|
|
728
|
-
}
|
|
729
925
|
map.get(c.propertyKey).push({
|
|
730
926
|
areaKey: c.areaKey,
|
|
731
927
|
order: c.order,
|
|
@@ -737,23 +933,16 @@ class Customization {
|
|
|
737
933
|
displayAreas,
|
|
738
934
|
}));
|
|
739
935
|
}
|
|
740
|
-
/**
|
|
741
|
-
* Merge two sets of bulk items. If a propertyKey exists in both,
|
|
742
|
-
* combine their displayAreas arrays.
|
|
743
|
-
*/
|
|
744
936
|
mergeItems(primary, secondary) {
|
|
745
937
|
const map = new Map();
|
|
746
|
-
for (const item of primary)
|
|
938
|
+
for (const item of primary)
|
|
747
939
|
map.set(item.propertyKey, [...item.displayAreas]);
|
|
748
|
-
}
|
|
749
940
|
for (const item of secondary) {
|
|
750
941
|
const existing = map.get(item.propertyKey);
|
|
751
|
-
if (existing)
|
|
942
|
+
if (existing)
|
|
752
943
|
existing.push(...item.displayAreas);
|
|
753
|
-
|
|
754
|
-
else {
|
|
944
|
+
else
|
|
755
945
|
map.set(item.propertyKey, [...item.displayAreas]);
|
|
756
|
-
}
|
|
757
946
|
}
|
|
758
947
|
return Array.from(map.entries()).map(([propertyKey, displayAreas]) => ({
|
|
759
948
|
propertyKey,
|
|
@@ -807,7 +996,7 @@ class Customization {
|
|
|
807
996
|
return null;
|
|
808
997
|
const displayName = this.getPropertyDisplayName(prop);
|
|
809
998
|
const viewType = prop.viewType;
|
|
810
|
-
|
|
999
|
+
return {
|
|
811
1000
|
id: prop.id,
|
|
812
1001
|
propertyId: prop.id,
|
|
813
1002
|
key: prop.key,
|
|
@@ -818,7 +1007,6 @@ class Customization {
|
|
|
818
1007
|
order: config.order,
|
|
819
1008
|
configuration: config.configuration,
|
|
820
1009
|
};
|
|
821
|
-
return entity;
|
|
822
1010
|
}
|
|
823
1011
|
getPropertyDisplayName(prop) {
|
|
824
1012
|
if (typeof prop.name === 'string')
|
|
@@ -877,7 +1065,7 @@ class Customization {
|
|
|
877
1065
|
}
|
|
878
1066
|
}
|
|
879
1067
|
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
|
|
1068
|
+
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 class=\"rounded-lg border border-gray-300 border-dashed px-4 py-3\">\r\n <mt-leaf-details-selector\r\n [property]=\"prop\"\r\n [leafLevels]=\"leafConfigsByProp()[prop.key] ?? {}\"\r\n (configChange)=\"onLeafConfigChange(prop, $event)\"\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"], outputs: ["configChange"] }] });
|
|
881
1069
|
}
|
|
882
1070
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: Customization, decorators: [{
|
|
883
1071
|
type: Component,
|
|
@@ -892,7 +1080,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
892
1080
|
Skeleton,
|
|
893
1081
|
EntitiesManage,
|
|
894
1082
|
Icon,
|
|
895
|
-
|
|
1083
|
+
LeafDetailsSelector,
|
|
1084
|
+
], 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 class=\"rounded-lg border border-gray-300 border-dashed px-4 py-3\">\r\n <mt-leaf-details-selector\r\n [property]=\"prop\"\r\n [leafLevels]=\"leafConfigsByProp()[prop.key] ?? {}\"\r\n (configChange)=\"onLeafConfigChange(prop, $event)\"\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
1085
|
}], ctorParameters: () => [] });
|
|
897
1086
|
|
|
898
1087
|
/**
|