@meshmakers/octo-ui 3.4.280 → 3.4.290
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/meshmakers-octo-ui-tree-navigation-settings.mjs +347 -0
- package/fesm2022/meshmakers-octo-ui-tree-navigation-settings.mjs.map +1 -0
- package/fesm2022/meshmakers-octo-ui.mjs +685 -194
- package/fesm2022/meshmakers-octo-ui.mjs.map +1 -1
- package/lib/runtime-browser/styles/_styles.scss +5 -23
- package/package.json +5 -1
- package/types/meshmakers-octo-ui-tree-navigation-settings.d.ts +132 -0
- package/types/meshmakers-octo-ui.d.ts +161 -6
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { input, inject, signal, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
4
|
+
import * as i1 from '@angular/forms';
|
|
5
|
+
import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
6
|
+
import { ActivatedRoute } from '@angular/router';
|
|
7
|
+
import { TreeNavigationConfigService, TREE_NAVIGATION_CONFIG_CONSTANTS } from '@meshmakers/octo-ui';
|
|
8
|
+
import { CkTypeSelectorService, AssetRepoService, JobManagementService } from '@meshmakers/octo-services';
|
|
9
|
+
import { ImportStrategyDialogService } from '@meshmakers/shared-ui';
|
|
10
|
+
import { MessageService } from '@meshmakers/shared-services';
|
|
11
|
+
import * as i2 from '@progress/kendo-angular-buttons';
|
|
12
|
+
import { ButtonsModule } from '@progress/kendo-angular-buttons';
|
|
13
|
+
import * as i4 from '@progress/kendo-angular-dropdowns';
|
|
14
|
+
import { DropDownsModule } from '@progress/kendo-angular-dropdowns';
|
|
15
|
+
import * as i3 from '@progress/kendo-angular-inputs';
|
|
16
|
+
import { InputsModule } from '@progress/kendo-angular-inputs';
|
|
17
|
+
import { LabelModule } from '@progress/kendo-angular-label';
|
|
18
|
+
import { saveIcon, plusIcon, arrowRotateCwIcon, xIcon, downloadIcon, uploadIcon } from '@progress/kendo-svg-icons';
|
|
19
|
+
import { firstValueFrom } from 'rxjs';
|
|
20
|
+
|
|
21
|
+
/** English defaults; hosts may override via the `messages` input. */
|
|
22
|
+
const DEFAULT_TREE_NAVIGATION_SETTINGS_MESSAGES = {
|
|
23
|
+
title: 'Tree Navigation',
|
|
24
|
+
description: 'Per-tenant overrides for the entity trees (repository browser and data-mappings). Rules are applied on top of auto-discovered associations. Without any rule a role is shown with its default name, grouped (except System/ParentChild which is flattened).',
|
|
25
|
+
notInstalled: 'The System.UI construction kit model (>= 2.2.0) is not installed on this tenant, so tree navigation cannot be configured yet.',
|
|
26
|
+
columnSourceType: 'Source type',
|
|
27
|
+
columnRole: 'Role id',
|
|
28
|
+
columnDisplayName: 'Label',
|
|
29
|
+
columnSortIndex: 'Order',
|
|
30
|
+
columnVisible: 'Visibility',
|
|
31
|
+
columnGrouped: 'Grouping',
|
|
32
|
+
columnIcon: 'Icon',
|
|
33
|
+
columnActions: '',
|
|
34
|
+
visibleAuto: 'Auto',
|
|
35
|
+
visibleShow: 'Show',
|
|
36
|
+
visibleHide: 'Hide',
|
|
37
|
+
groupedAuto: 'Auto',
|
|
38
|
+
groupedGroup: 'Group',
|
|
39
|
+
groupedFlatten: 'Flatten',
|
|
40
|
+
sourceTypeHint: '* matches every type',
|
|
41
|
+
roleHint: 'e.g. EnergyIQ/SpaceSensors',
|
|
42
|
+
addRule: 'Add rule',
|
|
43
|
+
removeRule: 'Remove',
|
|
44
|
+
save: 'Save',
|
|
45
|
+
reload: 'Reload',
|
|
46
|
+
export: 'Export',
|
|
47
|
+
import: 'Import',
|
|
48
|
+
empty: 'No rules yet — every association uses its defaults.',
|
|
49
|
+
saveSuccess: 'Tree navigation configuration saved.',
|
|
50
|
+
saveError: 'Failed to save the tree navigation configuration.',
|
|
51
|
+
loadError: 'Failed to load the tree navigation configuration.',
|
|
52
|
+
exportNothing: 'Save the configuration before exporting it.',
|
|
53
|
+
exportError: 'Failed to export the tree navigation configuration.',
|
|
54
|
+
importSuccess: 'Import completed successfully.',
|
|
55
|
+
importError: 'Failed to import the tree navigation configuration.',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** Matches every source CK type. */
|
|
59
|
+
const WILDCARD = '*';
|
|
60
|
+
/**
|
|
61
|
+
* Admin editor for the per-tenant `System.UI/TreeNavigationConfiguration`.
|
|
62
|
+
* Each row is one override rule matched by (source type, role id); `*` as the
|
|
63
|
+
* source type matches every type. Empty dropdown value = auto (default
|
|
64
|
+
* behavior). Source type and role id offer autocomplete suggestions but also
|
|
65
|
+
* accept custom values (so orphan roles remain configurable).
|
|
66
|
+
*/
|
|
67
|
+
class TreeNavigationSettingsComponent {
|
|
68
|
+
messages = input(DEFAULT_TREE_NAVIGATION_SETTINGS_MESSAGES, /* @ts-ignore */
|
|
69
|
+
...(ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []));
|
|
70
|
+
fb = inject(FormBuilder);
|
|
71
|
+
config = inject(TreeNavigationConfigService);
|
|
72
|
+
ckTypeSelector = inject(CkTypeSelectorService);
|
|
73
|
+
assetRepo = inject(AssetRepoService);
|
|
74
|
+
jobs = inject(JobManagementService);
|
|
75
|
+
importStrategyDialog = inject(ImportStrategyDialogService);
|
|
76
|
+
route = inject(ActivatedRoute);
|
|
77
|
+
messageService = inject(MessageService);
|
|
78
|
+
loading = signal(true, /* @ts-ignore */
|
|
79
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
80
|
+
saving = signal(false, /* @ts-ignore */
|
|
81
|
+
...(ngDevMode ? [{ debugName: "saving" }] : /* istanbul ignore next */ []));
|
|
82
|
+
typePresent = signal(true, /* @ts-ignore */
|
|
83
|
+
...(ngDevMode ? [{ debugName: "typePresent" }] : /* istanbul ignore next */ []));
|
|
84
|
+
rtId = null;
|
|
85
|
+
rules = this.fb.array([]);
|
|
86
|
+
// Shared suggestion pools — only one combobox dropdown is open at a time, so a
|
|
87
|
+
// single signal per kind is enough (loaded on open / filter).
|
|
88
|
+
ckTypeSuggestions = signal([WILDCARD], /* @ts-ignore */
|
|
89
|
+
...(ngDevMode ? [{ debugName: "ckTypeSuggestions" }] : /* istanbul ignore next */ []));
|
|
90
|
+
roleSuggestions = signal([], /* @ts-ignore */
|
|
91
|
+
...(ngDevMode ? [{ debugName: "roleSuggestions" }] : /* istanbul ignore next */ []));
|
|
92
|
+
saveIcon = saveIcon;
|
|
93
|
+
plusIcon = plusIcon;
|
|
94
|
+
reloadIcon = arrowRotateCwIcon;
|
|
95
|
+
removeIcon = xIcon;
|
|
96
|
+
exportIcon = downloadIcon;
|
|
97
|
+
importIcon = uploadIcon;
|
|
98
|
+
visibleOptions = computed(() => [
|
|
99
|
+
{ text: this.messages().visibleAuto, value: 'auto' },
|
|
100
|
+
{ text: this.messages().visibleShow, value: 'show' },
|
|
101
|
+
{ text: this.messages().visibleHide, value: 'hide' },
|
|
102
|
+
], /* @ts-ignore */
|
|
103
|
+
...(ngDevMode ? [{ debugName: "visibleOptions" }] : /* istanbul ignore next */ []));
|
|
104
|
+
groupedOptions = computed(() => [
|
|
105
|
+
{ text: this.messages().groupedAuto, value: 'auto' },
|
|
106
|
+
{ text: this.messages().groupedGroup, value: 'group' },
|
|
107
|
+
{ text: this.messages().groupedFlatten, value: 'flatten' },
|
|
108
|
+
], /* @ts-ignore */
|
|
109
|
+
...(ngDevMode ? [{ debugName: "groupedOptions" }] : /* istanbul ignore next */ []));
|
|
110
|
+
async ngOnInit() {
|
|
111
|
+
await this.reload();
|
|
112
|
+
}
|
|
113
|
+
async reload() {
|
|
114
|
+
this.loading.set(true);
|
|
115
|
+
try {
|
|
116
|
+
const config = await this.config.loadConfig();
|
|
117
|
+
this.typePresent.set(config.typePresent);
|
|
118
|
+
this.rtId = config.rtId;
|
|
119
|
+
this.rules.clear();
|
|
120
|
+
for (const role of config.roles) {
|
|
121
|
+
this.rules.push(this.createRow(role));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.error('[TreeNavigationSettingsComponent] Failed to load config', error);
|
|
126
|
+
this.messageService.showError(this.messages().loadError);
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
this.loading.set(false);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
addRule() {
|
|
133
|
+
this.rules.push(this.createRow());
|
|
134
|
+
this.rules.markAsDirty();
|
|
135
|
+
}
|
|
136
|
+
removeRule(index) {
|
|
137
|
+
this.rules.removeAt(index);
|
|
138
|
+
this.rules.markAsDirty();
|
|
139
|
+
}
|
|
140
|
+
async onSave() {
|
|
141
|
+
if (this.rules.invalid) {
|
|
142
|
+
this.rules.markAllAsTouched();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
this.saving.set(true);
|
|
146
|
+
try {
|
|
147
|
+
this.rtId = await this.config.saveConfig(this.rtId, this.collectRoles());
|
|
148
|
+
this.messageService.showInformation(this.messages().saveSuccess);
|
|
149
|
+
this.rules.markAsPristine();
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
console.error('[TreeNavigationSettingsComponent] Failed to save config', error);
|
|
153
|
+
this.messageService.showError(this.messages().saveError);
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
this.saving.set(false);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/** Loads CK type suggestions for the source-type combobox (server filter). */
|
|
160
|
+
async searchCkTypes(term) {
|
|
161
|
+
try {
|
|
162
|
+
const result = await firstValueFrom(this.ckTypeSelector.getCkTypes({
|
|
163
|
+
searchText: term?.trim() || undefined,
|
|
164
|
+
first: 30,
|
|
165
|
+
}));
|
|
166
|
+
const ids = result.items
|
|
167
|
+
.map((i) => i.rtCkTypeId)
|
|
168
|
+
.filter((id) => !!id && id !== WILDCARD);
|
|
169
|
+
this.ckTypeSuggestions.set([WILDCARD, ...ids]);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
console.error('Error loading CK type suggestions', error);
|
|
173
|
+
this.ckTypeSuggestions.set([WILDCARD]);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/** Loads role suggestions for the source type of the opened row's combobox. */
|
|
177
|
+
async loadRoleSuggestions(row) {
|
|
178
|
+
const ckTypeId = row.get('sourceCkTypeId')?.value ?? '';
|
|
179
|
+
this.roleSuggestions.set(await this.config.getRoleSuggestions(ckTypeId));
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Exports the saved configuration singleton as a deep-graph runtime model ZIP,
|
|
183
|
+
* via the standard asset-repo export job (same mechanism as pools / adapters /
|
|
184
|
+
* data flows). Requires the config to have been saved first.
|
|
185
|
+
*/
|
|
186
|
+
async export() {
|
|
187
|
+
if (!this.rtId) {
|
|
188
|
+
this.messageService.showInformation(this.messages().exportNothing);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const tenantId = this.getTenantId();
|
|
192
|
+
if (!tenantId) {
|
|
193
|
+
this.messageService.showError(this.messages().exportError);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const jobId = await this.assetRepo.exportRtModelDeepGraph(tenantId, [this.rtId], TREE_NAVIGATION_CONFIG_CONSTANTS.CONFIG_CK_TYPE_ID);
|
|
198
|
+
if (!jobId) {
|
|
199
|
+
throw new Error('export job not started');
|
|
200
|
+
}
|
|
201
|
+
const ok = await this.jobs.waitForJob(jobId, this.messages().export, this.messages().title);
|
|
202
|
+
if (ok) {
|
|
203
|
+
await this.jobs.downloadJobResult(tenantId, jobId, 'tree-navigation-config.zip');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
console.error('[TreeNavigationSettingsComponent] Export failed', error);
|
|
208
|
+
this.messageService.showError(this.messages().exportError);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Imports a configuration from a deep-graph runtime model ZIP via the standard
|
|
213
|
+
* import-strategy dialog + asset-repo import job, then reloads the editor.
|
|
214
|
+
*/
|
|
215
|
+
async onFileSelected(event) {
|
|
216
|
+
const input = event.target;
|
|
217
|
+
const file = input.files?.[0];
|
|
218
|
+
input.value = '';
|
|
219
|
+
if (!file) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const tenantId = this.getTenantId();
|
|
223
|
+
if (!tenantId) {
|
|
224
|
+
this.messageService.showError(this.messages().importError);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const strategy = await this.importStrategyDialog.showImportStrategyDialog(this.messages().import);
|
|
228
|
+
if (strategy === null) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
const jobId = await this.assetRepo.importRtModel(tenantId, file, strategy);
|
|
233
|
+
if (!jobId) {
|
|
234
|
+
throw new Error('import job not started');
|
|
235
|
+
}
|
|
236
|
+
const ok = await this.jobs.waitForJob(jobId, this.messages().import, file.name);
|
|
237
|
+
if (ok) {
|
|
238
|
+
this.messageService.showInformation(this.messages().importSuccess);
|
|
239
|
+
await this.reload();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
console.error('[TreeNavigationSettingsComponent] Import failed', error);
|
|
244
|
+
this.messageService.showError(this.messages().importError);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/** Resolves the tenant id from the route hierarchy (route is /:tenantId/...). */
|
|
248
|
+
getTenantId() {
|
|
249
|
+
let route = this.route;
|
|
250
|
+
while (route) {
|
|
251
|
+
const tenantId = route.snapshot.paramMap.get('tenantId');
|
|
252
|
+
if (tenantId) {
|
|
253
|
+
return tenantId;
|
|
254
|
+
}
|
|
255
|
+
route = route.parent;
|
|
256
|
+
}
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
collectRoles() {
|
|
260
|
+
return this.rules.controls
|
|
261
|
+
.map((group) => this.toRoleConfig(group))
|
|
262
|
+
.filter((r) => r.sourceCkTypeId && r.roleId);
|
|
263
|
+
}
|
|
264
|
+
createRow(role) {
|
|
265
|
+
return this.fb.group({
|
|
266
|
+
sourceCkTypeId: [role?.sourceCkTypeId ?? '*', [Validators.required]],
|
|
267
|
+
roleId: [role?.roleId ?? '', [Validators.required]],
|
|
268
|
+
displayName: [role?.displayName ?? ''],
|
|
269
|
+
sortIndex: [role?.sortIndex ?? null],
|
|
270
|
+
visible: [this.toVisibleValue(role?.visible)],
|
|
271
|
+
grouped: [this.toGroupedValue(role?.grouped)],
|
|
272
|
+
icon: [role?.icon ?? ''],
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
toRoleConfig(group) {
|
|
276
|
+
const value = group.getRawValue();
|
|
277
|
+
return {
|
|
278
|
+
sourceCkTypeId: (value.sourceCkTypeId ?? '').trim(),
|
|
279
|
+
roleId: (value.roleId ?? '').trim(),
|
|
280
|
+
displayName: value.displayName?.trim() || undefined,
|
|
281
|
+
sortIndex: value.sortIndex ?? undefined,
|
|
282
|
+
visible: value.visible === 'auto' ? undefined : value.visible === 'show',
|
|
283
|
+
grouped: value.grouped === 'auto' ? undefined : value.grouped === 'group',
|
|
284
|
+
icon: value.icon?.trim() || undefined,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
toVisibleValue(visible) {
|
|
288
|
+
if (visible === undefined)
|
|
289
|
+
return 'auto';
|
|
290
|
+
return visible ? 'show' : 'hide';
|
|
291
|
+
}
|
|
292
|
+
toGroupedValue(grouped) {
|
|
293
|
+
if (grouped === undefined)
|
|
294
|
+
return 'auto';
|
|
295
|
+
return grouped ? 'group' : 'flatten';
|
|
296
|
+
}
|
|
297
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: TreeNavigationSettingsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
298
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: TreeNavigationSettingsComponent, isStandalone: true, selector: "mm-tree-navigation-settings", inputs: { messages: { classPropertyName: "messages", publicName: "messages", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"tree-nav-settings\">\n <header class=\"header\">\n <h2 class=\"title\">{{ messages().title }}</h2>\n <p class=\"description\">{{ messages().description }}</p>\n </header>\n\n @if (loading()) {\n <div class=\"state\">\u2026</div>\n } @else if (!typePresent()) {\n <div class=\"hint not-installed\">{{ messages().notInstalled }}</div>\n } @else {\n <div class=\"toolbar mm-toolbar-actions\">\n <button kendoButton [svgIcon]=\"plusIcon\" (click)=\"addRule()\">\n {{ messages().addRule }}\n </button>\n <button\n kendoButton\n [svgIcon]=\"exportIcon\"\n fillMode=\"flat\"\n (click)=\"export()\"\n >\n {{ messages().export }}\n </button>\n <button\n kendoButton\n [svgIcon]=\"importIcon\"\n fillMode=\"flat\"\n (click)=\"fileInput.click()\"\n >\n {{ messages().import }}\n </button>\n <input\n #fileInput\n type=\"file\"\n accept=\".zip\"\n hidden\n (change)=\"onFileSelected($event)\"\n />\n <span class=\"toolbar-spacer\"></span>\n <button\n kendoButton\n [svgIcon]=\"reloadIcon\"\n fillMode=\"flat\"\n [disabled]=\"saving()\"\n (click)=\"reload()\"\n >\n {{ messages().reload }}\n </button>\n <button\n kendoButton\n themeColor=\"primary\"\n [svgIcon]=\"saveIcon\"\n [disabled]=\"saving()\"\n (click)=\"onSave()\"\n >\n {{ messages().save }}\n </button>\n </div>\n\n @if (rules.length === 0) {\n <div class=\"empty\">{{ messages().empty }}</div>\n } @else {\n <div class=\"table-wrap\">\n <table class=\"rules\">\n <thead>\n <tr>\n <th>{{ messages().columnSourceType }}</th>\n <th>{{ messages().columnRole }}</th>\n <th>{{ messages().columnDisplayName }}</th>\n <th class=\"narrow\">{{ messages().columnSortIndex }}</th>\n <th class=\"narrow\">{{ messages().columnVisible }}</th>\n <th class=\"narrow\">{{ messages().columnGrouped }}</th>\n <th>{{ messages().columnIcon }}</th>\n <th class=\"actions\">{{ messages().columnActions }}</th>\n </tr>\n </thead>\n <tbody>\n @for (row of rules.controls; track $index) {\n <tr [formGroup]=\"row\">\n <td>\n <kendo-combobox\n [data]=\"ckTypeSuggestions()\"\n [allowCustom]=\"true\"\n [filterable]=\"true\"\n [valuePrimitive]=\"true\"\n [placeholder]=\"messages().sourceTypeHint\"\n formControlName=\"sourceCkTypeId\"\n (open)=\"searchCkTypes('')\"\n (filterChange)=\"searchCkTypes($event)\"\n ></kendo-combobox>\n </td>\n <td>\n <kendo-combobox\n [data]=\"roleSuggestions()\"\n textField=\"label\"\n valueField=\"roleId\"\n [allowCustom]=\"true\"\n [filterable]=\"true\"\n [valuePrimitive]=\"true\"\n [placeholder]=\"messages().roleHint\"\n formControlName=\"roleId\"\n (open)=\"loadRoleSuggestions(row)\"\n ></kendo-combobox>\n </td>\n <td><input kendoTextBox formControlName=\"displayName\" /></td>\n <td class=\"narrow\">\n <kendo-numerictextbox\n formControlName=\"sortIndex\"\n [decimals]=\"0\"\n format=\"n0\"\n [spinners]=\"false\"\n ></kendo-numerictextbox>\n </td>\n <td class=\"narrow\">\n <kendo-dropdownlist\n [data]=\"visibleOptions()\"\n textField=\"text\"\n valueField=\"value\"\n [valuePrimitive]=\"true\"\n formControlName=\"visible\"\n ></kendo-dropdownlist>\n </td>\n <td class=\"narrow\">\n <kendo-dropdownlist\n [data]=\"groupedOptions()\"\n textField=\"text\"\n valueField=\"value\"\n [valuePrimitive]=\"true\"\n formControlName=\"grouped\"\n ></kendo-dropdownlist>\n </td>\n <td><input kendoTextBox formControlName=\"icon\" /></td>\n <td class=\"actions\">\n <button\n kendoButton\n fillMode=\"flat\"\n [svgIcon]=\"removeIcon\"\n [title]=\"messages().removeRule\"\n (click)=\"removeRule($index)\"\n ></button>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n }\n</div>\n", styles: [":host{display:block;padding:16px 20px}.header{margin-bottom:16px}.header .title{margin:0 0 4px;font-size:1.1rem;font-weight:600}.header .description{margin:0;max-width:70ch;color:var(--kendo-color-subtle, #6c757d);font-size:.85rem}.toolbar{display:flex;align-items:center;gap:8px;margin-bottom:12px}.toolbar .toolbar-spacer{flex:1}.hint,.empty{padding:12px 14px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;background:var(--kendo-color-surface-alt, #f8f9fa);color:var(--kendo-color-subtle, #6c757d);font-size:.9rem}.table-wrap{overflow-x:auto}.rules{width:100%;border-collapse:collapse}.rules th,.rules td{padding:6px 8px;text-align:left;vertical-align:middle;border-bottom:1px solid var(--kendo-color-border, #dee2e6)}.rules th{font-size:.75rem;text-transform:uppercase;letter-spacing:.03em;color:var(--kendo-color-subtle, #6c757d);font-weight:600}.rules td input,.rules td kendo-numerictextbox,.rules td kendo-dropdownlist{width:100%}.rules .narrow{width:120px}.rules .actions{width:48px;text-align:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "directive", type: i3.TextBoxDirective, selector: "input[kendoTextBox]", inputs: ["value"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "ngmodule", type: LabelModule }, { kind: "ngmodule", type: DropDownsModule }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
299
|
+
}
|
|
300
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: TreeNavigationSettingsComponent, decorators: [{
|
|
301
|
+
type: Component,
|
|
302
|
+
args: [{ selector: 'mm-tree-navigation-settings', standalone: true, imports: [
|
|
303
|
+
CommonModule,
|
|
304
|
+
ReactiveFormsModule,
|
|
305
|
+
ButtonsModule,
|
|
306
|
+
InputsModule,
|
|
307
|
+
LabelModule,
|
|
308
|
+
DropDownsModule,
|
|
309
|
+
], changeDetection: ChangeDetectionStrategy.Eager, template: "<div class=\"tree-nav-settings\">\n <header class=\"header\">\n <h2 class=\"title\">{{ messages().title }}</h2>\n <p class=\"description\">{{ messages().description }}</p>\n </header>\n\n @if (loading()) {\n <div class=\"state\">\u2026</div>\n } @else if (!typePresent()) {\n <div class=\"hint not-installed\">{{ messages().notInstalled }}</div>\n } @else {\n <div class=\"toolbar mm-toolbar-actions\">\n <button kendoButton [svgIcon]=\"plusIcon\" (click)=\"addRule()\">\n {{ messages().addRule }}\n </button>\n <button\n kendoButton\n [svgIcon]=\"exportIcon\"\n fillMode=\"flat\"\n (click)=\"export()\"\n >\n {{ messages().export }}\n </button>\n <button\n kendoButton\n [svgIcon]=\"importIcon\"\n fillMode=\"flat\"\n (click)=\"fileInput.click()\"\n >\n {{ messages().import }}\n </button>\n <input\n #fileInput\n type=\"file\"\n accept=\".zip\"\n hidden\n (change)=\"onFileSelected($event)\"\n />\n <span class=\"toolbar-spacer\"></span>\n <button\n kendoButton\n [svgIcon]=\"reloadIcon\"\n fillMode=\"flat\"\n [disabled]=\"saving()\"\n (click)=\"reload()\"\n >\n {{ messages().reload }}\n </button>\n <button\n kendoButton\n themeColor=\"primary\"\n [svgIcon]=\"saveIcon\"\n [disabled]=\"saving()\"\n (click)=\"onSave()\"\n >\n {{ messages().save }}\n </button>\n </div>\n\n @if (rules.length === 0) {\n <div class=\"empty\">{{ messages().empty }}</div>\n } @else {\n <div class=\"table-wrap\">\n <table class=\"rules\">\n <thead>\n <tr>\n <th>{{ messages().columnSourceType }}</th>\n <th>{{ messages().columnRole }}</th>\n <th>{{ messages().columnDisplayName }}</th>\n <th class=\"narrow\">{{ messages().columnSortIndex }}</th>\n <th class=\"narrow\">{{ messages().columnVisible }}</th>\n <th class=\"narrow\">{{ messages().columnGrouped }}</th>\n <th>{{ messages().columnIcon }}</th>\n <th class=\"actions\">{{ messages().columnActions }}</th>\n </tr>\n </thead>\n <tbody>\n @for (row of rules.controls; track $index) {\n <tr [formGroup]=\"row\">\n <td>\n <kendo-combobox\n [data]=\"ckTypeSuggestions()\"\n [allowCustom]=\"true\"\n [filterable]=\"true\"\n [valuePrimitive]=\"true\"\n [placeholder]=\"messages().sourceTypeHint\"\n formControlName=\"sourceCkTypeId\"\n (open)=\"searchCkTypes('')\"\n (filterChange)=\"searchCkTypes($event)\"\n ></kendo-combobox>\n </td>\n <td>\n <kendo-combobox\n [data]=\"roleSuggestions()\"\n textField=\"label\"\n valueField=\"roleId\"\n [allowCustom]=\"true\"\n [filterable]=\"true\"\n [valuePrimitive]=\"true\"\n [placeholder]=\"messages().roleHint\"\n formControlName=\"roleId\"\n (open)=\"loadRoleSuggestions(row)\"\n ></kendo-combobox>\n </td>\n <td><input kendoTextBox formControlName=\"displayName\" /></td>\n <td class=\"narrow\">\n <kendo-numerictextbox\n formControlName=\"sortIndex\"\n [decimals]=\"0\"\n format=\"n0\"\n [spinners]=\"false\"\n ></kendo-numerictextbox>\n </td>\n <td class=\"narrow\">\n <kendo-dropdownlist\n [data]=\"visibleOptions()\"\n textField=\"text\"\n valueField=\"value\"\n [valuePrimitive]=\"true\"\n formControlName=\"visible\"\n ></kendo-dropdownlist>\n </td>\n <td class=\"narrow\">\n <kendo-dropdownlist\n [data]=\"groupedOptions()\"\n textField=\"text\"\n valueField=\"value\"\n [valuePrimitive]=\"true\"\n formControlName=\"grouped\"\n ></kendo-dropdownlist>\n </td>\n <td><input kendoTextBox formControlName=\"icon\" /></td>\n <td class=\"actions\">\n <button\n kendoButton\n fillMode=\"flat\"\n [svgIcon]=\"removeIcon\"\n [title]=\"messages().removeRule\"\n (click)=\"removeRule($index)\"\n ></button>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n }\n</div>\n", styles: [":host{display:block;padding:16px 20px}.header{margin-bottom:16px}.header .title{margin:0 0 4px;font-size:1.1rem;font-weight:600}.header .description{margin:0;max-width:70ch;color:var(--kendo-color-subtle, #6c757d);font-size:.85rem}.toolbar{display:flex;align-items:center;gap:8px;margin-bottom:12px}.toolbar .toolbar-spacer{flex:1}.hint,.empty{padding:12px 14px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;background:var(--kendo-color-surface-alt, #f8f9fa);color:var(--kendo-color-subtle, #6c757d);font-size:.9rem}.table-wrap{overflow-x:auto}.rules{width:100%;border-collapse:collapse}.rules th,.rules td{padding:6px 8px;text-align:left;vertical-align:middle;border-bottom:1px solid var(--kendo-color-border, #dee2e6)}.rules th{font-size:.75rem;text-transform:uppercase;letter-spacing:.03em;color:var(--kendo-color-subtle, #6c757d);font-weight:600}.rules td input,.rules td kendo-numerictextbox,.rules td kendo-dropdownlist{width:100%}.rules .narrow{width:120px}.rules .actions{width:48px;text-align:center}\n"] }]
|
|
310
|
+
}], propDecorators: { messages: [{ type: i0.Input, args: [{ isSignal: true, alias: "messages", required: false }] }] } });
|
|
311
|
+
|
|
312
|
+
var treeNavigationSettings_component = /*#__PURE__*/Object.freeze({
|
|
313
|
+
__proto__: null,
|
|
314
|
+
TreeNavigationSettingsComponent: TreeNavigationSettingsComponent
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Routes for the tree navigation settings UI. Mount under any path, e.g.:
|
|
319
|
+
*
|
|
320
|
+
* ```ts
|
|
321
|
+
* { path: 'tree-navigation', canActivate: [adminGuard], children: TREE_NAVIGATION_SETTINGS_ROUTES }
|
|
322
|
+
* ```
|
|
323
|
+
*
|
|
324
|
+
* The component is lazy-loaded on first navigation (`loadComponent`).
|
|
325
|
+
*/
|
|
326
|
+
const TREE_NAVIGATION_SETTINGS_ROUTES = [
|
|
327
|
+
{
|
|
328
|
+
path: '',
|
|
329
|
+
loadComponent: () => Promise.resolve().then(function () { return treeNavigationSettings_component; }).then((m) => m.TreeNavigationSettingsComponent),
|
|
330
|
+
},
|
|
331
|
+
];
|
|
332
|
+
|
|
333
|
+
/*
|
|
334
|
+
* Public API Surface of @meshmakers/octo-ui/tree-navigation-settings
|
|
335
|
+
*
|
|
336
|
+
* Admin-only editor for the per-tenant System.UI/TreeNavigationConfiguration.
|
|
337
|
+
* Lives in its own secondary entry point so host applications that only need
|
|
338
|
+
* the runtime browser / data-mappings trees (primary `@meshmakers/octo-ui`
|
|
339
|
+
* entry) do not pay for the reactive-form / Kendo modules this editor pulls in.
|
|
340
|
+
*/
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Generated bundle index. Do not edit.
|
|
344
|
+
*/
|
|
345
|
+
|
|
346
|
+
export { DEFAULT_TREE_NAVIGATION_SETTINGS_MESSAGES, TREE_NAVIGATION_SETTINGS_ROUTES, TreeNavigationSettingsComponent };
|
|
347
|
+
//# sourceMappingURL=meshmakers-octo-ui-tree-navigation-settings.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meshmakers-octo-ui-tree-navigation-settings.mjs","sources":["../../../../projects/meshmakers/octo-ui/tree-navigation-settings/src/tree-navigation-settings.messages.ts","../../../../projects/meshmakers/octo-ui/tree-navigation-settings/src/tree-navigation-settings.component.ts","../../../../projects/meshmakers/octo-ui/tree-navigation-settings/src/tree-navigation-settings.component.html","../../../../projects/meshmakers/octo-ui/tree-navigation-settings/src/tree-navigation-settings.routes.ts","../../../../projects/meshmakers/octo-ui/tree-navigation-settings/src/public-api.ts","../../../../projects/meshmakers/octo-ui/tree-navigation-settings/src/meshmakers-octo-ui-tree-navigation-settings.ts"],"sourcesContent":["/** UI strings for the tree navigation settings editor. */\nexport interface TreeNavigationSettingsMessages {\n title: string;\n description: string;\n /** Shown when System.UI < 2.2.0 (the CK type is not installed on the tenant). */\n notInstalled: string;\n columnSourceType: string;\n columnRole: string;\n columnDisplayName: string;\n columnSortIndex: string;\n columnVisible: string;\n columnGrouped: string;\n columnIcon: string;\n columnActions: string;\n visibleAuto: string;\n visibleShow: string;\n visibleHide: string;\n groupedAuto: string;\n groupedGroup: string;\n groupedFlatten: string;\n sourceTypeHint: string;\n roleHint: string;\n addRule: string;\n removeRule: string;\n save: string;\n reload: string;\n export: string;\n import: string;\n empty: string;\n saveSuccess: string;\n saveError: string;\n loadError: string;\n exportNothing: string;\n exportError: string;\n importSuccess: string;\n importError: string;\n}\n\n/** English defaults; hosts may override via the `messages` input. */\nexport const DEFAULT_TREE_NAVIGATION_SETTINGS_MESSAGES: TreeNavigationSettingsMessages =\n {\n title: 'Tree Navigation',\n description:\n 'Per-tenant overrides for the entity trees (repository browser and data-mappings). Rules are applied on top of auto-discovered associations. Without any rule a role is shown with its default name, grouped (except System/ParentChild which is flattened).',\n notInstalled:\n 'The System.UI construction kit model (>= 2.2.0) is not installed on this tenant, so tree navigation cannot be configured yet.',\n columnSourceType: 'Source type',\n columnRole: 'Role id',\n columnDisplayName: 'Label',\n columnSortIndex: 'Order',\n columnVisible: 'Visibility',\n columnGrouped: 'Grouping',\n columnIcon: 'Icon',\n columnActions: '',\n visibleAuto: 'Auto',\n visibleShow: 'Show',\n visibleHide: 'Hide',\n groupedAuto: 'Auto',\n groupedGroup: 'Group',\n groupedFlatten: 'Flatten',\n sourceTypeHint: '* matches every type',\n roleHint: 'e.g. EnergyIQ/SpaceSensors',\n addRule: 'Add rule',\n removeRule: 'Remove',\n save: 'Save',\n reload: 'Reload',\n export: 'Export',\n import: 'Import',\n empty: 'No rules yet — every association uses its defaults.',\n saveSuccess: 'Tree navigation configuration saved.',\n saveError: 'Failed to save the tree navigation configuration.',\n loadError: 'Failed to load the tree navigation configuration.',\n exportNothing: 'Save the configuration before exporting it.',\n exportError: 'Failed to export the tree navigation configuration.',\n importSuccess: 'Import completed successfully.',\n importError: 'Failed to import the tree navigation configuration.',\n };\n","import { CommonModule } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n inject,\n input,\n OnInit,\n signal,\n} from '@angular/core';\nimport {\n FormBuilder,\n FormGroup,\n ReactiveFormsModule,\n Validators,\n} from '@angular/forms';\nimport { ActivatedRoute } from '@angular/router';\nimport {\n TreeNavigationConfigService,\n TreeNavigationRoleConfig,\n TREE_NAVIGATION_CONFIG_CONSTANTS,\n} from '@meshmakers/octo-ui';\nimport {\n AssetRepoService,\n CkTypeSelectorService,\n ImportStrategyDto,\n JobManagementService,\n} from '@meshmakers/octo-services';\nimport { ImportStrategyDialogService } from '@meshmakers/shared-ui';\nimport { MessageService } from '@meshmakers/shared-services';\nimport { ButtonsModule } from '@progress/kendo-angular-buttons';\nimport { DropDownsModule } from '@progress/kendo-angular-dropdowns';\nimport { InputsModule } from '@progress/kendo-angular-inputs';\nimport { LabelModule } from '@progress/kendo-angular-label';\nimport {\n plusIcon,\n saveIcon,\n arrowRotateCwIcon,\n xIcon,\n downloadIcon,\n uploadIcon,\n} from '@progress/kendo-svg-icons';\nimport { firstValueFrom } from 'rxjs';\nimport {\n DEFAULT_TREE_NAVIGATION_SETTINGS_MESSAGES,\n TreeNavigationSettingsMessages,\n} from './tree-navigation-settings.messages';\n\n/** Tri-state form value for the visibility/grouping dropdowns. */\ntype TriState = 'auto' | string;\n\n/** Matches every source CK type. */\nconst WILDCARD = '*';\n\n/**\n * Admin editor for the per-tenant `System.UI/TreeNavigationConfiguration`.\n * Each row is one override rule matched by (source type, role id); `*` as the\n * source type matches every type. Empty dropdown value = auto (default\n * behavior). Source type and role id offer autocomplete suggestions but also\n * accept custom values (so orphan roles remain configurable).\n */\n@Component({\n selector: 'mm-tree-navigation-settings',\n standalone: true,\n imports: [\n CommonModule,\n ReactiveFormsModule,\n ButtonsModule,\n InputsModule,\n LabelModule,\n DropDownsModule,\n ],\n templateUrl: './tree-navigation-settings.component.html',\n styleUrl: './tree-navigation-settings.component.scss',\n changeDetection: ChangeDetectionStrategy.Eager,\n})\nexport class TreeNavigationSettingsComponent implements OnInit {\n readonly messages = input<TreeNavigationSettingsMessages>(\n DEFAULT_TREE_NAVIGATION_SETTINGS_MESSAGES,\n );\n\n private readonly fb = inject(FormBuilder);\n private readonly config = inject(TreeNavigationConfigService);\n private readonly ckTypeSelector = inject(CkTypeSelectorService);\n private readonly assetRepo = inject(AssetRepoService);\n private readonly jobs = inject(JobManagementService);\n private readonly importStrategyDialog = inject(ImportStrategyDialogService);\n private readonly route = inject(ActivatedRoute);\n private readonly messageService = inject(MessageService);\n\n protected readonly loading = signal(true);\n protected readonly saving = signal(false);\n protected readonly typePresent = signal(true);\n private rtId: string | null = null;\n\n protected readonly rules = this.fb.array<FormGroup>([]);\n\n // Shared suggestion pools — only one combobox dropdown is open at a time, so a\n // single signal per kind is enough (loaded on open / filter).\n protected readonly ckTypeSuggestions = signal<string[]>([WILDCARD]);\n protected readonly roleSuggestions = signal<{ roleId: string; label: string }[]>(\n [],\n );\n\n protected readonly saveIcon = saveIcon;\n protected readonly plusIcon = plusIcon;\n protected readonly reloadIcon = arrowRotateCwIcon;\n protected readonly removeIcon = xIcon;\n protected readonly exportIcon = downloadIcon;\n protected readonly importIcon = uploadIcon;\n\n protected readonly visibleOptions = computed(() => [\n { text: this.messages().visibleAuto, value: 'auto' },\n { text: this.messages().visibleShow, value: 'show' },\n { text: this.messages().visibleHide, value: 'hide' },\n ]);\n protected readonly groupedOptions = computed(() => [\n { text: this.messages().groupedAuto, value: 'auto' },\n { text: this.messages().groupedGroup, value: 'group' },\n { text: this.messages().groupedFlatten, value: 'flatten' },\n ]);\n\n async ngOnInit(): Promise<void> {\n await this.reload();\n }\n\n protected async reload(): Promise<void> {\n this.loading.set(true);\n try {\n const config = await this.config.loadConfig();\n this.typePresent.set(config.typePresent);\n this.rtId = config.rtId;\n this.rules.clear();\n for (const role of config.roles) {\n this.rules.push(this.createRow(role));\n }\n } catch (error) {\n console.error(\n '[TreeNavigationSettingsComponent] Failed to load config',\n error,\n );\n this.messageService.showError(this.messages().loadError);\n } finally {\n this.loading.set(false);\n }\n }\n\n protected addRule(): void {\n this.rules.push(this.createRow());\n this.rules.markAsDirty();\n }\n\n protected removeRule(index: number): void {\n this.rules.removeAt(index);\n this.rules.markAsDirty();\n }\n\n protected async onSave(): Promise<void> {\n if (this.rules.invalid) {\n this.rules.markAllAsTouched();\n return;\n }\n this.saving.set(true);\n try {\n this.rtId = await this.config.saveConfig(this.rtId, this.collectRoles());\n this.messageService.showInformation(this.messages().saveSuccess);\n this.rules.markAsPristine();\n } catch (error) {\n console.error(\n '[TreeNavigationSettingsComponent] Failed to save config',\n error,\n );\n this.messageService.showError(this.messages().saveError);\n } finally {\n this.saving.set(false);\n }\n }\n\n /** Loads CK type suggestions for the source-type combobox (server filter). */\n protected async searchCkTypes(term: string): Promise<void> {\n try {\n const result = await firstValueFrom(\n this.ckTypeSelector.getCkTypes({\n searchText: term?.trim() || undefined,\n first: 30,\n }),\n );\n const ids = result.items\n .map((i) => i.rtCkTypeId)\n .filter((id): id is string => !!id && id !== WILDCARD);\n this.ckTypeSuggestions.set([WILDCARD, ...ids]);\n } catch (error) {\n console.error('Error loading CK type suggestions', error);\n this.ckTypeSuggestions.set([WILDCARD]);\n }\n }\n\n /** Loads role suggestions for the source type of the opened row's combobox. */\n protected async loadRoleSuggestions(row: FormGroup): Promise<void> {\n const ckTypeId = (row.get('sourceCkTypeId')?.value as string) ?? '';\n this.roleSuggestions.set(await this.config.getRoleSuggestions(ckTypeId));\n }\n\n /**\n * Exports the saved configuration singleton as a deep-graph runtime model ZIP,\n * via the standard asset-repo export job (same mechanism as pools / adapters /\n * data flows). Requires the config to have been saved first.\n */\n protected async export(): Promise<void> {\n if (!this.rtId) {\n this.messageService.showInformation(this.messages().exportNothing);\n return;\n }\n const tenantId = this.getTenantId();\n if (!tenantId) {\n this.messageService.showError(this.messages().exportError);\n return;\n }\n try {\n const jobId = await this.assetRepo.exportRtModelDeepGraph(\n tenantId,\n [this.rtId],\n TREE_NAVIGATION_CONFIG_CONSTANTS.CONFIG_CK_TYPE_ID,\n );\n if (!jobId) {\n throw new Error('export job not started');\n }\n const ok = await this.jobs.waitForJob(\n jobId,\n this.messages().export,\n this.messages().title,\n );\n if (ok) {\n await this.jobs.downloadJobResult(\n tenantId,\n jobId,\n 'tree-navigation-config.zip',\n );\n }\n } catch (error) {\n console.error('[TreeNavigationSettingsComponent] Export failed', error);\n this.messageService.showError(this.messages().exportError);\n }\n }\n\n /**\n * Imports a configuration from a deep-graph runtime model ZIP via the standard\n * import-strategy dialog + asset-repo import job, then reloads the editor.\n */\n protected async onFileSelected(event: Event): Promise<void> {\n const input = event.target as HTMLInputElement;\n const file = input.files?.[0];\n input.value = '';\n if (!file) {\n return;\n }\n const tenantId = this.getTenantId();\n if (!tenantId) {\n this.messageService.showError(this.messages().importError);\n return;\n }\n const strategy = await this.importStrategyDialog.showImportStrategyDialog(\n this.messages().import,\n );\n if (strategy === null) {\n return;\n }\n try {\n const jobId = await this.assetRepo.importRtModel(\n tenantId,\n file,\n strategy as ImportStrategyDto,\n );\n if (!jobId) {\n throw new Error('import job not started');\n }\n const ok = await this.jobs.waitForJob(\n jobId,\n this.messages().import,\n file.name,\n );\n if (ok) {\n this.messageService.showInformation(this.messages().importSuccess);\n await this.reload();\n }\n } catch (error) {\n console.error('[TreeNavigationSettingsComponent] Import failed', error);\n this.messageService.showError(this.messages().importError);\n }\n }\n\n /** Resolves the tenant id from the route hierarchy (route is /:tenantId/...). */\n private getTenantId(): string | null {\n let route: ActivatedRoute | null = this.route;\n while (route) {\n const tenantId = route.snapshot.paramMap.get('tenantId');\n if (tenantId) {\n return tenantId;\n }\n route = route.parent;\n }\n return null;\n }\n\n private collectRoles(): TreeNavigationRoleConfig[] {\n return this.rules.controls\n .map((group) => this.toRoleConfig(group as FormGroup))\n .filter((r) => r.sourceCkTypeId && r.roleId);\n }\n\n private createRow(role?: TreeNavigationRoleConfig): FormGroup {\n return this.fb.group({\n sourceCkTypeId: [role?.sourceCkTypeId ?? '*', [Validators.required]],\n roleId: [role?.roleId ?? '', [Validators.required]],\n displayName: [role?.displayName ?? ''],\n sortIndex: [role?.sortIndex ?? null],\n visible: [this.toVisibleValue(role?.visible)],\n grouped: [this.toGroupedValue(role?.grouped)],\n icon: [role?.icon ?? ''],\n });\n }\n\n private toRoleConfig(group: FormGroup): TreeNavigationRoleConfig {\n const value = group.getRawValue() as {\n sourceCkTypeId: string;\n roleId: string;\n displayName: string;\n sortIndex: number | null;\n visible: TriState;\n grouped: TriState;\n icon: string;\n };\n return {\n sourceCkTypeId: (value.sourceCkTypeId ?? '').trim(),\n roleId: (value.roleId ?? '').trim(),\n displayName: value.displayName?.trim() || undefined,\n sortIndex: value.sortIndex ?? undefined,\n visible: value.visible === 'auto' ? undefined : value.visible === 'show',\n grouped: value.grouped === 'auto' ? undefined : value.grouped === 'group',\n icon: value.icon?.trim() || undefined,\n };\n }\n\n private toVisibleValue(visible: boolean | undefined): TriState {\n if (visible === undefined) return 'auto';\n return visible ? 'show' : 'hide';\n }\n\n private toGroupedValue(grouped: boolean | undefined): TriState {\n if (grouped === undefined) return 'auto';\n return grouped ? 'group' : 'flatten';\n }\n}\n","<div class=\"tree-nav-settings\">\n <header class=\"header\">\n <h2 class=\"title\">{{ messages().title }}</h2>\n <p class=\"description\">{{ messages().description }}</p>\n </header>\n\n @if (loading()) {\n <div class=\"state\">…</div>\n } @else if (!typePresent()) {\n <div class=\"hint not-installed\">{{ messages().notInstalled }}</div>\n } @else {\n <div class=\"toolbar mm-toolbar-actions\">\n <button kendoButton [svgIcon]=\"plusIcon\" (click)=\"addRule()\">\n {{ messages().addRule }}\n </button>\n <button\n kendoButton\n [svgIcon]=\"exportIcon\"\n fillMode=\"flat\"\n (click)=\"export()\"\n >\n {{ messages().export }}\n </button>\n <button\n kendoButton\n [svgIcon]=\"importIcon\"\n fillMode=\"flat\"\n (click)=\"fileInput.click()\"\n >\n {{ messages().import }}\n </button>\n <input\n #fileInput\n type=\"file\"\n accept=\".zip\"\n hidden\n (change)=\"onFileSelected($event)\"\n />\n <span class=\"toolbar-spacer\"></span>\n <button\n kendoButton\n [svgIcon]=\"reloadIcon\"\n fillMode=\"flat\"\n [disabled]=\"saving()\"\n (click)=\"reload()\"\n >\n {{ messages().reload }}\n </button>\n <button\n kendoButton\n themeColor=\"primary\"\n [svgIcon]=\"saveIcon\"\n [disabled]=\"saving()\"\n (click)=\"onSave()\"\n >\n {{ messages().save }}\n </button>\n </div>\n\n @if (rules.length === 0) {\n <div class=\"empty\">{{ messages().empty }}</div>\n } @else {\n <div class=\"table-wrap\">\n <table class=\"rules\">\n <thead>\n <tr>\n <th>{{ messages().columnSourceType }}</th>\n <th>{{ messages().columnRole }}</th>\n <th>{{ messages().columnDisplayName }}</th>\n <th class=\"narrow\">{{ messages().columnSortIndex }}</th>\n <th class=\"narrow\">{{ messages().columnVisible }}</th>\n <th class=\"narrow\">{{ messages().columnGrouped }}</th>\n <th>{{ messages().columnIcon }}</th>\n <th class=\"actions\">{{ messages().columnActions }}</th>\n </tr>\n </thead>\n <tbody>\n @for (row of rules.controls; track $index) {\n <tr [formGroup]=\"row\">\n <td>\n <kendo-combobox\n [data]=\"ckTypeSuggestions()\"\n [allowCustom]=\"true\"\n [filterable]=\"true\"\n [valuePrimitive]=\"true\"\n [placeholder]=\"messages().sourceTypeHint\"\n formControlName=\"sourceCkTypeId\"\n (open)=\"searchCkTypes('')\"\n (filterChange)=\"searchCkTypes($event)\"\n ></kendo-combobox>\n </td>\n <td>\n <kendo-combobox\n [data]=\"roleSuggestions()\"\n textField=\"label\"\n valueField=\"roleId\"\n [allowCustom]=\"true\"\n [filterable]=\"true\"\n [valuePrimitive]=\"true\"\n [placeholder]=\"messages().roleHint\"\n formControlName=\"roleId\"\n (open)=\"loadRoleSuggestions(row)\"\n ></kendo-combobox>\n </td>\n <td><input kendoTextBox formControlName=\"displayName\" /></td>\n <td class=\"narrow\">\n <kendo-numerictextbox\n formControlName=\"sortIndex\"\n [decimals]=\"0\"\n format=\"n0\"\n [spinners]=\"false\"\n ></kendo-numerictextbox>\n </td>\n <td class=\"narrow\">\n <kendo-dropdownlist\n [data]=\"visibleOptions()\"\n textField=\"text\"\n valueField=\"value\"\n [valuePrimitive]=\"true\"\n formControlName=\"visible\"\n ></kendo-dropdownlist>\n </td>\n <td class=\"narrow\">\n <kendo-dropdownlist\n [data]=\"groupedOptions()\"\n textField=\"text\"\n valueField=\"value\"\n [valuePrimitive]=\"true\"\n formControlName=\"grouped\"\n ></kendo-dropdownlist>\n </td>\n <td><input kendoTextBox formControlName=\"icon\" /></td>\n <td class=\"actions\">\n <button\n kendoButton\n fillMode=\"flat\"\n [svgIcon]=\"removeIcon\"\n [title]=\"messages().removeRule\"\n (click)=\"removeRule($index)\"\n ></button>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n }\n</div>\n","import { Routes } from '@angular/router';\n\n/**\n * Routes for the tree navigation settings UI. Mount under any path, e.g.:\n *\n * ```ts\n * { path: 'tree-navigation', canActivate: [adminGuard], children: TREE_NAVIGATION_SETTINGS_ROUTES }\n * ```\n *\n * The component is lazy-loaded on first navigation (`loadComponent`).\n */\nexport const TREE_NAVIGATION_SETTINGS_ROUTES: Routes = [\n {\n path: '',\n loadComponent: () =>\n import('./tree-navigation-settings.component').then(\n (m) => m.TreeNavigationSettingsComponent,\n ),\n },\n];\n","/*\n * Public API Surface of @meshmakers/octo-ui/tree-navigation-settings\n *\n * Admin-only editor for the per-tenant System.UI/TreeNavigationConfiguration.\n * Lives in its own secondary entry point so host applications that only need\n * the runtime browser / data-mappings trees (primary `@meshmakers/octo-ui`\n * entry) do not pay for the reactive-form / Kendo modules this editor pulls in.\n */\nexport * from './tree-navigation-settings.component';\nexport * from './tree-navigation-settings.messages';\nexport * from './tree-navigation-settings.routes';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|