@meshmakers/octo-ui 3.3.34 → 3.3.390

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,481 +1,4228 @@
1
1
  import * as i0 from '@angular/core';
2
- import { NgModule, EventEmitter, TemplateRef, ViewChild, Input, Output, ContentChildren, Component } from '@angular/core';
3
- import { CommonModule, NgIf, NgTemplateOutlet, AsyncPipe } from '@angular/common';
4
- import { fromEvent, merge, BehaviorSubject } from 'rxjs';
5
- import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
6
- import { SortOrdersDto, SearchFilterTypesDto } from '@meshmakers/octo-services';
7
- import { MatPaginator } from '@angular/material/paginator';
8
- import { MatSort, MatSortHeader } from '@angular/material/sort';
9
- import { MatButton, MatIconButton } from '@angular/material/button';
10
- import { MatCell, MatCellDef, MatColumnDef, MatHeaderCell, MatRow, MatRowDef, MatTable, MatHeaderCellDef, MatHeaderRow, MatHeaderRowDef } from '@angular/material/table';
11
- import { MatFormField, MatLabel } from '@angular/material/form-field';
12
- import { MatIcon } from '@angular/material/icon';
13
- import { MatInput } from '@angular/material/input';
14
- import { MatProgressBar } from '@angular/material/progress-bar';
15
- import { MatToolbar } from '@angular/material/toolbar';
16
- import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
17
- import { MatListItemIcon } from '@angular/material/list';
18
- import { RouterLink } from '@angular/router';
19
-
20
- class ListElementModule {
21
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: ListElementModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
22
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.12", ngImport: i0, type: ListElementModule, imports: [CommonModule] });
23
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: ListElementModule, imports: [CommonModule] });
2
+ import { inject, Component, Injectable, EventEmitter, Output, Input, forwardRef, ViewChild, makeEnvironmentProviders } from '@angular/core';
3
+ import { AttributeSelectorService, CkTypeAttributeService, CkTypeSelectorService, SearchFilterTypesDto, SortOrdersDto, FieldFilterOperatorsDto, AttributeValueTypeDto as AttributeValueTypeDto$1, provideOctoServices } from '@meshmakers/octo-services';
4
+ import { DataSourceTyped, HierarchyDataSourceBase, NotificationDisplayService, provideMmSharedUi } from '@meshmakers/shared-ui';
5
+ import { provideMmSharedAuth } from '@meshmakers/shared-auth';
6
+ import * as i1 from '@angular/common';
7
+ import { CommonModule } from '@angular/common';
8
+ import * as i2 from '@angular/forms';
9
+ import { FormsModule, FormControl, ReactiveFormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
10
+ import * as i2$1 from '@progress/kendo-angular-grid';
11
+ import { GridModule } from '@progress/kendo-angular-grid';
12
+ import * as i3 from '@progress/kendo-angular-buttons';
13
+ import { ButtonsModule, ButtonModule } from '@progress/kendo-angular-buttons';
14
+ import * as i4 from '@progress/kendo-angular-inputs';
15
+ import { InputsModule } from '@progress/kendo-angular-inputs';
16
+ import * as i3$1 from '@progress/kendo-angular-dropdowns';
17
+ import { DropDownListModule, DropDownsModule, AutoCompleteModule } from '@progress/kendo-angular-dropdowns';
18
+ import * as i5$1 from '@progress/kendo-angular-icons';
19
+ import { IconsModule, SVGIconModule } from '@progress/kendo-angular-icons';
20
+ import { searchIcon, sortAscSmallIcon, sortDescSmallIcon, filterClearIcon, chevronRightIcon, chevronDownIcon, downloadIcon, fileIcon, folderIcon, calendarIcon, checkboxCheckedIcon, listUnorderedIcon, arrowRightIcon, arrowLeftIcon, chevronDoubleRightIcon, chevronDoubleLeftIcon, arrowUpIcon, arrowDownIcon, plusIcon, minusIcon, trashIcon, dollarIcon, copyIcon } from '@progress/kendo-svg-icons';
21
+ import * as i5 from '@progress/kendo-angular-dialog';
22
+ import { DialogContentBase, DialogRef, DialogModule, DialogService } from '@progress/kendo-angular-dialog';
23
+ import { Subject, firstValueFrom, of, forkJoin, Subscription } from 'rxjs';
24
+ import { debounceTime, distinctUntilChanged, switchMap, map, tap, catchError } from 'rxjs/operators';
25
+ import { LoaderModule } from '@progress/kendo-angular-indicators';
26
+ import { isCompositeFilterDescriptor } from '@progress/kendo-data-query';
27
+ import * as i6 from '@progress/kendo-angular-dateinputs';
28
+ import { DateInputsModule } from '@progress/kendo-angular-dateinputs';
29
+ import { PopupModule } from '@progress/kendo-angular-popup';
30
+ import { IntlModule } from '@progress/kendo-angular-intl';
31
+
32
+ class AttributeSortSelectorDialogComponent extends DialogContentBase {
33
+ attributeService = inject(AttributeSelectorService);
34
+ searchSubject = new Subject();
35
+ constructor() {
36
+ super(inject(DialogRef));
37
+ }
38
+ // Dialog data
39
+ ckTypeId;
40
+ searchText = '';
41
+ currentSortOrder = 'standard';
42
+ // Grid data
43
+ availableAttributes = [];
44
+ selectedAttributes = [];
45
+ availableGridData = { data: [], total: 0 };
46
+ selectedGridData = { data: [], total: 0 };
47
+ // Selection tracking
48
+ selectedAvailableKeys = [];
49
+ selectedChosenKeys = [];
50
+ // Double-click tracking
51
+ lastClickTime = 0;
52
+ lastClickedItem = null;
53
+ doubleClickDelay = 300;
54
+ // UI configuration
55
+ dialogTitle = 'Select Attributes with Sort Order';
56
+ sortOptions = [
57
+ { text: 'Standard', value: 'standard' },
58
+ { text: 'Ascending', value: 'ascending' },
59
+ { text: 'Descending', value: 'descending' }
60
+ ];
61
+ // Icons
62
+ searchIcon = searchIcon;
63
+ sortAscIcon = sortAscSmallIcon;
64
+ sortDescIcon = sortDescSmallIcon;
65
+ ngOnInit() {
66
+ // Get dialog data
67
+ const data = this.dialog.content?.instance?.data;
68
+ console.log('ngOnInit - received data:', data);
69
+ if (data) {
70
+ this.ckTypeId = data.ckTypeId;
71
+ this.dialogTitle = data.dialogTitle || 'Select Attributes with Sort Order';
72
+ console.log('Set ckTypeId:', this.ckTypeId);
73
+ if (data.selectedAttributes && data.selectedAttributes.length > 0) {
74
+ this.selectedAttributes = [...data.selectedAttributes];
75
+ this.updateSelectedGrid();
76
+ console.log('Pre-populated selectedAttributes:', this.selectedAttributes);
77
+ }
78
+ }
79
+ else {
80
+ console.warn('No dialog data received');
81
+ }
82
+ // Set up search debouncing
83
+ this.searchSubject.pipe(debounceTime(300), distinctUntilChanged()).subscribe(searchText => {
84
+ this.loadAvailableAttributes(searchText);
85
+ });
86
+ // Load initial data
87
+ this.loadAvailableAttributes();
88
+ }
89
+ loadAvailableAttributes(filter) {
90
+ this.attributeService.getAvailableAttributes(this.ckTypeId, filter).subscribe(result => {
91
+ // Filter out already selected attributes
92
+ const selectedPaths = new Set(this.selectedAttributes.map(a => a.attributePath));
93
+ this.availableAttributes = result.items.filter(item => !selectedPaths.has(item.attributePath));
94
+ this.updateAvailableGrid();
95
+ });
96
+ }
97
+ onSearchChange(value) {
98
+ this.searchSubject.next(value);
99
+ }
100
+ setSortOrder(order) {
101
+ this.currentSortOrder = order;
102
+ }
103
+ addAttributeWithSort() {
104
+ if (this.selectedAvailableKeys.length === 0)
105
+ return;
106
+ const attributePath = this.selectedAvailableKeys[0];
107
+ const attribute = this.availableAttributes.find(a => a.attributePath === attributePath);
108
+ if (attribute) {
109
+ this.addAttributeToSelected(attribute);
110
+ }
111
+ }
112
+ onAvailableCellClick(event) {
113
+ const dataItem = event.dataItem;
114
+ if (!dataItem)
115
+ return;
116
+ const currentTime = Date.now();
117
+ const attributePath = dataItem.attributePath;
118
+ if (this.lastClickedItem === attributePath &&
119
+ currentTime - this.lastClickTime <= this.doubleClickDelay) {
120
+ // Double-click detected - add attribute with current sort
121
+ this.addAttributeToSelected(dataItem);
122
+ this.lastClickTime = 0;
123
+ this.lastClickedItem = null;
124
+ }
125
+ else {
126
+ this.lastClickTime = currentTime;
127
+ this.lastClickedItem = attributePath;
128
+ }
129
+ }
130
+ onSelectedCellClick(event) {
131
+ const dataItem = event.dataItem;
132
+ if (!dataItem)
133
+ return;
134
+ const currentTime = Date.now();
135
+ const attributePath = dataItem.attributePath;
136
+ if (this.lastClickedItem === attributePath &&
137
+ currentTime - this.lastClickTime <= this.doubleClickDelay) {
138
+ // Double-click detected - remove attribute
139
+ this.removeAttribute(dataItem);
140
+ this.lastClickTime = 0;
141
+ this.lastClickedItem = null;
142
+ }
143
+ else {
144
+ this.lastClickTime = currentTime;
145
+ this.lastClickedItem = attributePath;
146
+ }
147
+ }
148
+ addAttributeToSelected(attribute) {
149
+ console.log('Adding attribute to selected:', attribute, 'with sort order:', this.currentSortOrder);
150
+ const sortItem = {
151
+ attributePath: attribute.attributePath,
152
+ attributeValueType: attribute.attributeValueType,
153
+ sortOrder: this.currentSortOrder
154
+ };
155
+ this.selectedAttributes.push(sortItem);
156
+ console.log('Selected attributes after add:', this.selectedAttributes);
157
+ // Remove from available
158
+ this.availableAttributes = this.availableAttributes.filter(item => item.attributePath !== attribute.attributePath);
159
+ // Clear selections
160
+ this.selectedAvailableKeys = [];
161
+ this.updateGrids();
162
+ }
163
+ removeAttribute(attribute) {
164
+ // Remove from selected
165
+ this.selectedAttributes = this.selectedAttributes.filter(item => item.attributePath !== attribute.attributePath);
166
+ // Add back to available (convert back to AttributeItem)
167
+ const availableItem = {
168
+ attributePath: attribute.attributePath,
169
+ attributeValueType: attribute.attributeValueType
170
+ };
171
+ this.availableAttributes.push(availableItem);
172
+ this.availableAttributes.sort((a, b) => a.attributePath.localeCompare(b.attributePath));
173
+ // Clear selections
174
+ this.selectedChosenKeys = this.selectedChosenKeys.filter(key => key !== attribute.attributePath);
175
+ this.updateGrids();
176
+ }
177
+ getSortIndicator(sortOrder) {
178
+ switch (sortOrder) {
179
+ case 'ascending': return '↑';
180
+ case 'descending': return '↓';
181
+ default: return '';
182
+ }
183
+ }
184
+ getSortText(sortOrder) {
185
+ const option = this.sortOptions.find(opt => opt.value === sortOrder);
186
+ return option?.text || sortOrder;
187
+ }
188
+ updateGrids() {
189
+ this.updateAvailableGrid();
190
+ this.updateSelectedGrid();
191
+ }
192
+ updateAvailableGrid() {
193
+ this.availableGridData = {
194
+ data: this.availableAttributes,
195
+ total: this.availableAttributes.length
196
+ };
197
+ }
198
+ updateSelectedGrid() {
199
+ this.selectedGridData = {
200
+ data: this.selectedAttributes,
201
+ total: this.selectedAttributes.length
202
+ };
203
+ }
204
+ onOk() {
205
+ console.log('onOk called, selectedAttributes:', this.selectedAttributes);
206
+ const result = {
207
+ selectedAttributes: this.selectedAttributes
208
+ };
209
+ console.log('Closing dialog with result:', result);
210
+ this.dialog.close(result);
211
+ }
212
+ onCancel() {
213
+ console.log('onCancel called');
214
+ this.dialog.close();
215
+ }
216
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AttributeSortSelectorDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
217
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.5", type: AttributeSortSelectorDialogComponent, isStandalone: true, selector: "mm-attribute-sort-selector-dialog", host: { attributes: { "data-component": "attribute-sort-selector" } }, usesInheritance: true, ngImport: i0, template: `
218
+ <div class="attribute-sort-selector-container">
219
+ <!-- Search Section -->
220
+ <div class="search-container">
221
+ <kendo-textbox
222
+ [(ngModel)]="searchText"
223
+ (ngModelChange)="onSearchChange($event)"
224
+ placeholder="Search attributes..."
225
+ class="search-input">
226
+ <ng-template kendoTextBoxSuffixTemplate>
227
+ <button kendoButton [svgIcon]="searchIcon" fillMode="clear"></button>
228
+ </ng-template>
229
+ </kendo-textbox>
230
+ </div>
231
+
232
+ <!-- Main Content Area - All side by side -->
233
+ <div class="lists-container">
234
+ <!-- Available Attributes -->
235
+ <div class="list-section">
236
+ <h4>Available Attributes</h4>
237
+ <kendo-grid
238
+ [data]="availableGridData"
239
+ [height]="350"
240
+ [scrollable]="'scrollable'"
241
+ [selectable]="{ mode: 'single', enabled: true }"
242
+ [kendoGridSelectBy]="'attributePath'"
243
+ [(selectedKeys)]="selectedAvailableKeys"
244
+ (cellClick)="onAvailableCellClick($event)"
245
+ class="attribute-grid">
246
+ <kendo-grid-column field="attributePath" title="Attribute Path" [width]="200"></kendo-grid-column>
247
+ <kendo-grid-column field="attributeValueType" title="Type" [width]="140"></kendo-grid-column>
248
+ </kendo-grid>
249
+ </div>
250
+
251
+ <!-- Sort Options in center -->
252
+ <div class="sort-options-section">
253
+ <h4>Sort Order</h4>
254
+ <div class="sort-buttons">
255
+ <button
256
+ kendoButton
257
+ [fillMode]="currentSortOrder === 'standard' ? 'solid' : 'outline'"
258
+ [themeColor]="currentSortOrder === 'standard' ? 'primary' : 'base'"
259
+ (click)="setSortOrder('standard')"
260
+ class="sort-button"
261
+ title="Standard (no sort)">
262
+
263
+ </button>
264
+ <button
265
+ kendoButton
266
+ [svgIcon]="sortAscIcon"
267
+ [fillMode]="currentSortOrder === 'ascending' ? 'solid' : 'outline'"
268
+ [themeColor]="currentSortOrder === 'ascending' ? 'primary' : 'base'"
269
+ (click)="setSortOrder('ascending')"
270
+ class="sort-button"
271
+ title="Ascending">
272
+ </button>
273
+ <button
274
+ kendoButton
275
+ [svgIcon]="sortDescIcon"
276
+ [fillMode]="currentSortOrder === 'descending' ? 'solid' : 'outline'"
277
+ [themeColor]="currentSortOrder === 'descending' ? 'primary' : 'base'"
278
+ (click)="setSortOrder('descending')"
279
+ class="sort-button"
280
+ title="Descending">
281
+ </button>
282
+ </div>
283
+
284
+ <button
285
+ kendoButton
286
+ themeColor="primary"
287
+ [disabled]="selectedAvailableKeys.length === 0"
288
+ (click)="addAttributeWithSort()"
289
+ class="add-button">
290
+ Add →
291
+ </button>
292
+ </div>
293
+
294
+ <!-- Selected Attributes -->
295
+ <div class="list-section">
296
+ <h4>Selected ({{ selectedAttributes.length }})</h4>
297
+ <kendo-grid
298
+ [data]="selectedGridData"
299
+ [height]="350"
300
+ [scrollable]="'scrollable'"
301
+ [selectable]="{ mode: 'single', enabled: true }"
302
+ [kendoGridSelectBy]="'attributePath'"
303
+ [(selectedKeys)]="selectedChosenKeys"
304
+ (cellClick)="onSelectedCellClick($event)"
305
+ class="attribute-grid">
306
+ <kendo-grid-column field="attributePath" title="Attribute Path" [width]="180"></kendo-grid-column>
307
+ <kendo-grid-column field="sortOrder" title="Sort" [width]="100">
308
+ <ng-template kendoGridCellTemplate let-dataItem>
309
+ <div class="sort-display">
310
+ <span class="sort-indicator">{{ getSortIndicator(dataItem.sortOrder) }}</span>
311
+ <span>{{ getSortText(dataItem.sortOrder) }}</span>
312
+ </div>
313
+ </ng-template>
314
+ </kendo-grid-column>
315
+ <kendo-grid-column title="" [width]="50">
316
+ <ng-template kendoGridCellTemplate let-dataItem>
317
+ <button
318
+ kendoButton
319
+ size="small"
320
+ fillMode="flat"
321
+ (click)="removeAttribute(dataItem)"
322
+ title="Remove">
323
+
324
+ </button>
325
+ </ng-template>
326
+ </kendo-grid-column>
327
+ </kendo-grid>
328
+ </div>
329
+ </div>
330
+ </div>
331
+
332
+ <kendo-dialog-actions>
333
+ <button kendoButton (click)="onCancel()">Cancel</button>
334
+ <button kendoButton themeColor="primary" (click)="onOk()">Apply</button>
335
+ </kendo-dialog-actions>
336
+ `, isInline: true, styles: [":host{display:block}.attribute-sort-selector-container{display:flex;flex-direction:column;padding:16px 20px;min-width:1000px;box-sizing:border-box;gap:16px}.search-container{flex-shrink:0}.search-input{width:100%}.lists-container{display:flex;gap:16px;align-items:flex-start}.list-section{flex:1;display:flex;flex-direction:column}.list-section h4,.sort-options-section h4{margin:0 0 10px;font-size:.85rem;font-weight:600}.attribute-grid{border-radius:4px}.attribute-grid ::ng-deep .k-grid-table tbody tr{cursor:pointer}.sort-options-section{width:120px;flex-shrink:0;display:flex;flex-direction:column;padding-top:32px}.sort-buttons{display:flex;flex-direction:row;gap:4px}.sort-button{flex:1;min-width:36px;height:36px;padding:0;display:flex;align-items:center;justify-content:center}.add-button{width:100%;margin-top:16px}.sort-display{display:flex;align-items:center;gap:6px}.sort-indicator{font-size:14px;font-weight:700;color:var(--kendo-color-primary, #ff6358)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: GridModule }, { kind: "component", type: i2$1.GridComponent, selector: "kendo-grid", inputs: ["data", "pageSize", "height", "rowHeight", "adaptiveMode", "detailRowHeight", "skip", "scrollable", "selectable", "sort", "size", "trackBy", "filter", "group", "virtualColumns", "filterable", "sortable", "pageable", "groupable", "gridResizable", "rowReorderable", "navigable", "autoSize", "rowClass", "rowSticky", "rowSelected", "isRowSelectable", "cellSelected", "resizable", "reorderable", "loading", "columnMenu", "hideHeader", "showInactiveTools", "isDetailExpanded", "isGroupExpanded", "dataLayoutMode"], outputs: ["filterChange", "pageChange", "groupChange", "sortChange", "selectionChange", "rowReorder", "dataStateChange", "gridStateChange", "groupExpand", "groupCollapse", "detailExpand", "detailCollapse", "edit", "cancel", "save", "remove", "add", "cellClose", "cellClick", "pdfExport", "excelExport", "columnResize", "columnReorder", "columnVisibilityChange", "columnLockedChange", "columnStickyChange", "scrollBottom", "contentScroll"], exportAs: ["kendoGrid"] }, { kind: "directive", type: i2$1.SelectionDirective, selector: "[kendoGridSelectBy]" }, { kind: "component", type: i2$1.ColumnComponent, selector: "kendo-grid-column", inputs: ["field", "format", "sortable", "groupable", "editor", "filter", "filterVariant", "filterable", "editable"] }, { kind: "directive", type: i2$1.CellTemplateDirective, selector: "[kendoGridCellTemplate]" }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i3.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i4.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "directive", type: i4.TextBoxSuffixTemplateDirective, selector: "[kendoTextBoxSuffixTemplate]", inputs: ["showSeparator"] }, { kind: "ngmodule", type: DropDownListModule }, { kind: "ngmodule", type: IconsModule }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i5.DialogActionsComponent, selector: "kendo-dialog-actions", inputs: ["actions", "layout"], outputs: ["action"] }] });
337
+ }
338
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AttributeSortSelectorDialogComponent, decorators: [{
339
+ type: Component,
340
+ args: [{ selector: 'mm-attribute-sort-selector-dialog', standalone: true, host: { 'data-component': 'attribute-sort-selector' }, imports: [
341
+ CommonModule,
342
+ FormsModule,
343
+ GridModule,
344
+ ButtonsModule,
345
+ InputsModule,
346
+ DropDownListModule,
347
+ IconsModule,
348
+ DialogModule
349
+ ], template: `
350
+ <div class="attribute-sort-selector-container">
351
+ <!-- Search Section -->
352
+ <div class="search-container">
353
+ <kendo-textbox
354
+ [(ngModel)]="searchText"
355
+ (ngModelChange)="onSearchChange($event)"
356
+ placeholder="Search attributes..."
357
+ class="search-input">
358
+ <ng-template kendoTextBoxSuffixTemplate>
359
+ <button kendoButton [svgIcon]="searchIcon" fillMode="clear"></button>
360
+ </ng-template>
361
+ </kendo-textbox>
362
+ </div>
363
+
364
+ <!-- Main Content Area - All side by side -->
365
+ <div class="lists-container">
366
+ <!-- Available Attributes -->
367
+ <div class="list-section">
368
+ <h4>Available Attributes</h4>
369
+ <kendo-grid
370
+ [data]="availableGridData"
371
+ [height]="350"
372
+ [scrollable]="'scrollable'"
373
+ [selectable]="{ mode: 'single', enabled: true }"
374
+ [kendoGridSelectBy]="'attributePath'"
375
+ [(selectedKeys)]="selectedAvailableKeys"
376
+ (cellClick)="onAvailableCellClick($event)"
377
+ class="attribute-grid">
378
+ <kendo-grid-column field="attributePath" title="Attribute Path" [width]="200"></kendo-grid-column>
379
+ <kendo-grid-column field="attributeValueType" title="Type" [width]="140"></kendo-grid-column>
380
+ </kendo-grid>
381
+ </div>
382
+
383
+ <!-- Sort Options in center -->
384
+ <div class="sort-options-section">
385
+ <h4>Sort Order</h4>
386
+ <div class="sort-buttons">
387
+ <button
388
+ kendoButton
389
+ [fillMode]="currentSortOrder === 'standard' ? 'solid' : 'outline'"
390
+ [themeColor]="currentSortOrder === 'standard' ? 'primary' : 'base'"
391
+ (click)="setSortOrder('standard')"
392
+ class="sort-button"
393
+ title="Standard (no sort)">
394
+
395
+ </button>
396
+ <button
397
+ kendoButton
398
+ [svgIcon]="sortAscIcon"
399
+ [fillMode]="currentSortOrder === 'ascending' ? 'solid' : 'outline'"
400
+ [themeColor]="currentSortOrder === 'ascending' ? 'primary' : 'base'"
401
+ (click)="setSortOrder('ascending')"
402
+ class="sort-button"
403
+ title="Ascending">
404
+ </button>
405
+ <button
406
+ kendoButton
407
+ [svgIcon]="sortDescIcon"
408
+ [fillMode]="currentSortOrder === 'descending' ? 'solid' : 'outline'"
409
+ [themeColor]="currentSortOrder === 'descending' ? 'primary' : 'base'"
410
+ (click)="setSortOrder('descending')"
411
+ class="sort-button"
412
+ title="Descending">
413
+ </button>
414
+ </div>
415
+
416
+ <button
417
+ kendoButton
418
+ themeColor="primary"
419
+ [disabled]="selectedAvailableKeys.length === 0"
420
+ (click)="addAttributeWithSort()"
421
+ class="add-button">
422
+ Add →
423
+ </button>
424
+ </div>
425
+
426
+ <!-- Selected Attributes -->
427
+ <div class="list-section">
428
+ <h4>Selected ({{ selectedAttributes.length }})</h4>
429
+ <kendo-grid
430
+ [data]="selectedGridData"
431
+ [height]="350"
432
+ [scrollable]="'scrollable'"
433
+ [selectable]="{ mode: 'single', enabled: true }"
434
+ [kendoGridSelectBy]="'attributePath'"
435
+ [(selectedKeys)]="selectedChosenKeys"
436
+ (cellClick)="onSelectedCellClick($event)"
437
+ class="attribute-grid">
438
+ <kendo-grid-column field="attributePath" title="Attribute Path" [width]="180"></kendo-grid-column>
439
+ <kendo-grid-column field="sortOrder" title="Sort" [width]="100">
440
+ <ng-template kendoGridCellTemplate let-dataItem>
441
+ <div class="sort-display">
442
+ <span class="sort-indicator">{{ getSortIndicator(dataItem.sortOrder) }}</span>
443
+ <span>{{ getSortText(dataItem.sortOrder) }}</span>
444
+ </div>
445
+ </ng-template>
446
+ </kendo-grid-column>
447
+ <kendo-grid-column title="" [width]="50">
448
+ <ng-template kendoGridCellTemplate let-dataItem>
449
+ <button
450
+ kendoButton
451
+ size="small"
452
+ fillMode="flat"
453
+ (click)="removeAttribute(dataItem)"
454
+ title="Remove">
455
+
456
+ </button>
457
+ </ng-template>
458
+ </kendo-grid-column>
459
+ </kendo-grid>
460
+ </div>
461
+ </div>
462
+ </div>
463
+
464
+ <kendo-dialog-actions>
465
+ <button kendoButton (click)="onCancel()">Cancel</button>
466
+ <button kendoButton themeColor="primary" (click)="onOk()">Apply</button>
467
+ </kendo-dialog-actions>
468
+ `, styles: [":host{display:block}.attribute-sort-selector-container{display:flex;flex-direction:column;padding:16px 20px;min-width:1000px;box-sizing:border-box;gap:16px}.search-container{flex-shrink:0}.search-input{width:100%}.lists-container{display:flex;gap:16px;align-items:flex-start}.list-section{flex:1;display:flex;flex-direction:column}.list-section h4,.sort-options-section h4{margin:0 0 10px;font-size:.85rem;font-weight:600}.attribute-grid{border-radius:4px}.attribute-grid ::ng-deep .k-grid-table tbody tr{cursor:pointer}.sort-options-section{width:120px;flex-shrink:0;display:flex;flex-direction:column;padding-top:32px}.sort-buttons{display:flex;flex-direction:row;gap:4px}.sort-button{flex:1;min-width:36px;height:36px;padding:0;display:flex;align-items:center;justify-content:center}.add-button{width:100%;margin-top:16px}.sort-display{display:flex;align-items:center;gap:6px}.sort-indicator{font-size:14px;font-weight:700;color:var(--kendo-color-primary, #ff6358)}\n"] }]
469
+ }], ctorParameters: () => [] });
470
+
471
+ class AttributeSortSelectorDialogService {
472
+ dialogService = inject(DialogService);
473
+ /**
474
+ * Opens the attribute sort selector dialog
475
+ * @param ckTypeId The CkType ID to fetch attributes for
476
+ * @param selectedAttributes Optional array of pre-selected attributes with sort orders
477
+ * @param dialogTitle Optional custom dialog title
478
+ * @returns Promise that resolves with the result containing selected attributes with sort orders and confirmation status
479
+ */
480
+ async openAttributeSortSelector(ckTypeId, selectedAttributes, dialogTitle) {
481
+ const data = {
482
+ ckTypeId,
483
+ selectedAttributes,
484
+ dialogTitle
485
+ };
486
+ const dialogRef = this.dialogService.open({
487
+ content: AttributeSortSelectorDialogComponent,
488
+ width: 1100,
489
+ height: 750,
490
+ minWidth: 1050,
491
+ minHeight: 700,
492
+ title: dialogTitle || 'Select Attributes with Sort Order'
493
+ });
494
+ // Pass data to the component
495
+ if (dialogRef.content?.instance) {
496
+ dialogRef.content.instance.data = data;
497
+ }
498
+ try {
499
+ const result = await firstValueFrom(dialogRef.result);
500
+ if (result && typeof result === 'object' && 'selectedAttributes' in result) {
501
+ // User clicked OK
502
+ const dialogResult = result;
503
+ return {
504
+ confirmed: true,
505
+ selectedAttributes: dialogResult.selectedAttributes || []
506
+ };
507
+ }
508
+ else {
509
+ // User clicked Cancel or closed dialog (result is undefined)
510
+ return {
511
+ confirmed: false,
512
+ selectedAttributes: []
513
+ };
514
+ }
515
+ }
516
+ catch {
517
+ // Dialog was closed without result (e.g., ESC key, X button)
518
+ return {
519
+ confirmed: false,
520
+ selectedAttributes: []
521
+ };
522
+ }
523
+ }
524
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AttributeSortSelectorDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
525
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AttributeSortSelectorDialogService });
526
+ }
527
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AttributeSortSelectorDialogService, decorators: [{
528
+ type: Injectable
529
+ }] });
530
+
531
+ // Note: This should eventually be imported from a shared location
532
+ // For now, we define our own enum that matches the GraphQL generated types
533
+ var AttributeValueTypeDto;
534
+ (function (AttributeValueTypeDto) {
535
+ AttributeValueTypeDto["BinaryDto"] = "BINARY";
536
+ AttributeValueTypeDto["BinaryLinkedDto"] = "BINARY_LINKED";
537
+ AttributeValueTypeDto["BooleanDto"] = "BOOLEAN";
538
+ AttributeValueTypeDto["DateTimeDto"] = "DATE_TIME";
539
+ AttributeValueTypeDto["DateTimeOffsetDto"] = "DATE_TIME_OFFSET";
540
+ AttributeValueTypeDto["DoubleDto"] = "DOUBLE";
541
+ AttributeValueTypeDto["EnumDto"] = "ENUM";
542
+ AttributeValueTypeDto["GeospatialPointDto"] = "GEOSPATIAL_POINT";
543
+ AttributeValueTypeDto["IntDto"] = "INT";
544
+ AttributeValueTypeDto["IntegerDto"] = "INTEGER";
545
+ AttributeValueTypeDto["Integer_64Dto"] = "INTEGER_64";
546
+ AttributeValueTypeDto["IntegerArrayDto"] = "INTEGER_ARRAY";
547
+ AttributeValueTypeDto["Int_64Dto"] = "INT_64";
548
+ AttributeValueTypeDto["IntArrayDto"] = "INT_ARRAY";
549
+ AttributeValueTypeDto["RecordDto"] = "RECORD";
550
+ AttributeValueTypeDto["RecordArrayDto"] = "RECORD_ARRAY";
551
+ AttributeValueTypeDto["StringDto"] = "STRING";
552
+ AttributeValueTypeDto["StringArrayDto"] = "STRING_ARRAY";
553
+ AttributeValueTypeDto["TimeSpanDto"] = "TIME_SPAN";
554
+ })(AttributeValueTypeDto || (AttributeValueTypeDto = {}));
555
+ /**
556
+ * Supported display modes for complex values
557
+ */
558
+ var PropertyDisplayMode;
559
+ (function (PropertyDisplayMode) {
560
+ /** Simple text representation */
561
+ PropertyDisplayMode["Text"] = "text";
562
+ /** JSON formatted display */
563
+ PropertyDisplayMode["Json"] = "json";
564
+ /** Custom template display */
565
+ PropertyDisplayMode["Custom"] = "custom";
566
+ })(PropertyDisplayMode || (PropertyDisplayMode = {}));
567
+ /**
568
+ * Categories for default property grouping
569
+ */
570
+ var DefaultPropertyCategory;
571
+ (function (DefaultPropertyCategory) {
572
+ DefaultPropertyCategory["General"] = "General";
573
+ DefaultPropertyCategory["System"] = "System";
574
+ DefaultPropertyCategory["Attributes"] = "Attributes";
575
+ DefaultPropertyCategory["Associations"] = "Associations";
576
+ DefaultPropertyCategory["Metadata"] = "Metadata";
577
+ })(DefaultPropertyCategory || (DefaultPropertyCategory = {}));
578
+
579
+ /**
580
+ * Service for converting various data structures to PropertyGridItem format.
581
+ * Uses Construction Kit (CK) type definitions for accurate attribute type resolution.
582
+ */
583
+ class PropertyConverterService {
584
+ ckTypeAttributeService = inject(CkTypeAttributeService);
585
+ /**
586
+ * Convert OctoMesh RtEntity attributes to property grid items using CK type definitions.
587
+ * @param attributes The attributes array from RtEntity
588
+ * @param ckTypeId The fullName of the CK type to look up attribute types
589
+ * @returns Observable of PropertyGridItem array
590
+ */
591
+ convertRtEntityAttributes(attributes, ckTypeId) {
592
+ if (!attributes || attributes.length === 0) {
593
+ return of([]);
594
+ }
595
+ return this.ckTypeAttributeService.getCkTypeAttributes(ckTypeId).pipe(switchMap(ckAttributes => {
596
+ const ckAttributeMap = this.buildAttributeTypeMap(ckAttributes);
597
+ return this.convertAttributesWithCkTypes(attributes, ckAttributeMap);
598
+ }));
599
+ }
600
+ /**
601
+ * Convert any JavaScript object to property grid items using reflection.
602
+ * This method remains synchronous as there is no CK type for generic JS objects.
603
+ */
604
+ convertObjectToProperties(obj, category) {
605
+ if (!obj || typeof obj !== 'object') {
606
+ return [];
607
+ }
608
+ return Object.keys(obj).map(key => ({
609
+ id: `obj_${key}`,
610
+ name: key,
611
+ displayName: key,
612
+ value: obj[key],
613
+ type: this.inferAttributeType(obj[key]),
614
+ category: category || DefaultPropertyCategory.General,
615
+ readOnly: false,
616
+ description: `Property: ${key}`
617
+ }));
618
+ }
619
+ /**
620
+ * Convert RtRecord to property grid items using CK type definitions.
621
+ * @param record The record object containing ckRecordId and attributes
622
+ * @returns Observable of PropertyGridItem array
623
+ */
624
+ convertRtRecordToProperties(record) {
625
+ if (!record || typeof record !== 'object' || !record.attributes) {
626
+ return of([]);
627
+ }
628
+ const ckRecordId = record.ckRecordId;
629
+ if (!ckRecordId) {
630
+ return of([]);
631
+ }
632
+ return this.ckTypeAttributeService.getCkRecordAttributes(ckRecordId).pipe(switchMap(ckAttributes => {
633
+ const ckAttributeMap = this.buildAttributeTypeMap(ckAttributes);
634
+ const properties = [];
635
+ // Add ckRecordId as system property
636
+ properties.push({
637
+ id: 'ckRecordId',
638
+ name: 'ckRecordId',
639
+ displayName: 'Record ID',
640
+ value: ckRecordId,
641
+ type: AttributeValueTypeDto.StringDto,
642
+ category: DefaultPropertyCategory.System,
643
+ readOnly: true,
644
+ description: 'Construction kit record identifier'
645
+ });
646
+ // Convert attributes with CK types
647
+ return this.convertAttributesWithCkTypes(record.attributes, ckAttributeMap).pipe(map(attributeProperties => {
648
+ properties.push(...attributeProperties);
649
+ return properties;
650
+ }));
651
+ }));
652
+ }
653
+ /**
654
+ * Convert OctoMesh RtEntity to comprehensive property list using CK type definitions.
655
+ * @param entity The RtEntity object
656
+ * @returns Observable of PropertyGridItem array
657
+ */
658
+ convertRtEntityToProperties(entity) {
659
+ const properties = [];
660
+ // System properties
661
+ if (entity.rtId) {
662
+ properties.push({
663
+ id: 'rtId',
664
+ name: 'rtId',
665
+ displayName: 'Runtime ID',
666
+ value: entity.rtId,
667
+ type: AttributeValueTypeDto.StringDto,
668
+ category: DefaultPropertyCategory.System,
669
+ readOnly: true,
670
+ description: 'Unique runtime identifier'
671
+ });
672
+ }
673
+ if (entity.ckTypeId) {
674
+ properties.push({
675
+ id: 'ckTypeId',
676
+ name: 'ckTypeId',
677
+ displayName: 'Type ID',
678
+ value: entity.ckTypeId,
679
+ type: AttributeValueTypeDto.StringDto,
680
+ category: DefaultPropertyCategory.System,
681
+ readOnly: true,
682
+ description: 'Construction kit type identifier'
683
+ });
684
+ }
685
+ if (entity.rtCreationDateTime) {
686
+ properties.push({
687
+ id: 'rtCreationDateTime',
688
+ name: 'rtCreationDateTime',
689
+ displayName: 'Created',
690
+ value: entity.rtCreationDateTime,
691
+ type: AttributeValueTypeDto.DateTimeDto,
692
+ category: DefaultPropertyCategory.System,
693
+ readOnly: true,
694
+ description: 'Creation date and time'
695
+ });
696
+ }
697
+ if (entity.rtChangedDateTime) {
698
+ properties.push({
699
+ id: 'rtChangedDateTime',
700
+ name: 'rtChangedDateTime',
701
+ displayName: 'Modified',
702
+ value: entity.rtChangedDateTime,
703
+ type: AttributeValueTypeDto.DateTimeDto,
704
+ category: DefaultPropertyCategory.System,
705
+ readOnly: true,
706
+ description: 'Last modification date and time'
707
+ });
708
+ }
709
+ if (entity.rtWellKnownName) {
710
+ properties.push({
711
+ id: 'rtWellKnownName',
712
+ name: 'rtWellKnownName',
713
+ displayName: 'Well Known Name',
714
+ value: entity.rtWellKnownName,
715
+ type: AttributeValueTypeDto.StringDto,
716
+ category: DefaultPropertyCategory.General,
717
+ readOnly: false,
718
+ description: 'Well-known name for the entity'
719
+ });
720
+ }
721
+ // If no ckTypeId or no attributes, return system properties only
722
+ if (!entity.ckTypeId || !entity.attributes?.items) {
723
+ return of(properties);
724
+ }
725
+ // Load CK type attributes and convert entity attributes
726
+ return this.convertRtEntityAttributes(entity.attributes.items, entity.ckTypeId).pipe(map(attributeProperties => {
727
+ properties.push(...attributeProperties);
728
+ return properties;
729
+ }));
730
+ }
731
+ /**
732
+ * Build a map from attribute name to attribute value type from CK definitions
733
+ */
734
+ buildAttributeTypeMap(ckAttributes) {
735
+ const map = new Map();
736
+ for (const attr of ckAttributes) {
737
+ map.set(attr.attributeName, attr.attributeValueType);
738
+ }
739
+ return map;
740
+ }
741
+ /**
742
+ * Convert attributes using CK type map for type resolution
743
+ */
744
+ convertAttributesWithCkTypes(attributes, ckAttributeMap) {
745
+ if (!attributes || attributes.length === 0) {
746
+ return of([]);
747
+ }
748
+ // Check if any attributes have nested records that need async conversion
749
+ const hasNestedRecords = attributes.some(attr => attr?.value && typeof attr.value === 'object' && attr.value.attributes !== undefined);
750
+ if (!hasNestedRecords) {
751
+ // No nested records - convert synchronously
752
+ const items = attributes.map((attr, index) => this.convertSingleAttribute(attr, index, ckAttributeMap));
753
+ return of(items);
754
+ }
755
+ // Has nested records - need to handle async conversion
756
+ const conversionObservables = attributes.map((attr, index) => {
757
+ if (attr?.value && typeof attr.value === 'object' && attr.value.attributes !== undefined) {
758
+ // Nested record - convert async
759
+ return this.convertRtRecordToProperties(attr.value).pipe(map(nestedProperties => ({
760
+ id: `attr_${index}_${attr?.attributeName || 'unknown'}`,
761
+ name: attr?.attributeName || `attribute_${index}`,
762
+ displayName: attr?.attributeName || `attribute_${index}`,
763
+ value: nestedProperties,
764
+ type: this.getAttributeType(attr?.attributeName, ckAttributeMap),
765
+ category: DefaultPropertyCategory.Attributes,
766
+ readOnly: false,
767
+ description: `Attribute: ${attr?.attributeName}`
768
+ })));
769
+ }
770
+ else {
771
+ // Regular attribute - return as observable
772
+ return of(this.convertSingleAttribute(attr, index, ckAttributeMap));
773
+ }
774
+ });
775
+ return forkJoin(conversionObservables);
776
+ }
777
+ /**
778
+ * Convert a single attribute to PropertyGridItem
779
+ */
780
+ convertSingleAttribute(attr, index, ckAttributeMap) {
781
+ const value = this.convertRtEntityAttributeValueSync(attr?.value);
782
+ return {
783
+ id: `attr_${index}_${attr?.attributeName || 'unknown'}`,
784
+ name: attr?.attributeName || `attribute_${index}`,
785
+ displayName: attr?.attributeName || `attribute_${index}`,
786
+ value: value,
787
+ type: this.getAttributeType(attr?.attributeName, ckAttributeMap),
788
+ category: DefaultPropertyCategory.Attributes,
789
+ readOnly: false,
790
+ description: `Attribute: ${attr?.attributeName}`
791
+ };
792
+ }
793
+ /**
794
+ * Get attribute type from CK map
795
+ */
796
+ getAttributeType(attributeName, ckAttributeMap) {
797
+ if (!attributeName) {
798
+ return AttributeValueTypeDto.StringDto;
799
+ }
800
+ const ckType = ckAttributeMap.get(attributeName);
801
+ if (ckType) {
802
+ return this.mapCkTypeToAttributeValueType(ckType);
803
+ }
804
+ // Fallback for attributes not found in CK (should not happen in normal cases)
805
+ return AttributeValueTypeDto.StringDto;
806
+ }
807
+ /**
808
+ * Map CK type string to AttributeValueTypeDto enum
809
+ */
810
+ mapCkTypeToAttributeValueType(ckType) {
811
+ // The ckType from GraphQL should match the enum values
812
+ const typeMap = {
813
+ 'BINARY': AttributeValueTypeDto.BinaryDto,
814
+ 'BINARY_LINKED': AttributeValueTypeDto.BinaryLinkedDto,
815
+ 'BOOLEAN': AttributeValueTypeDto.BooleanDto,
816
+ 'DATE_TIME': AttributeValueTypeDto.DateTimeDto,
817
+ 'DATE_TIME_OFFSET': AttributeValueTypeDto.DateTimeOffsetDto,
818
+ 'DOUBLE': AttributeValueTypeDto.DoubleDto,
819
+ 'ENUM': AttributeValueTypeDto.EnumDto,
820
+ 'GEOSPATIAL_POINT': AttributeValueTypeDto.GeospatialPointDto,
821
+ 'INT': AttributeValueTypeDto.IntDto,
822
+ 'INTEGER': AttributeValueTypeDto.IntegerDto,
823
+ 'INTEGER_64': AttributeValueTypeDto.Integer_64Dto,
824
+ 'INTEGER_ARRAY': AttributeValueTypeDto.IntegerArrayDto,
825
+ 'INT_64': AttributeValueTypeDto.Int_64Dto,
826
+ 'INT_ARRAY': AttributeValueTypeDto.IntArrayDto,
827
+ 'RECORD': AttributeValueTypeDto.RecordDto,
828
+ 'RECORD_ARRAY': AttributeValueTypeDto.RecordArrayDto,
829
+ 'STRING': AttributeValueTypeDto.StringDto,
830
+ 'STRING_ARRAY': AttributeValueTypeDto.StringArrayDto,
831
+ 'TIME_SPAN': AttributeValueTypeDto.TimeSpanDto
832
+ };
833
+ return typeMap[ckType] || AttributeValueTypeDto.StringDto;
834
+ }
835
+ /**
836
+ * Convert attribute value synchronously (for non-record values)
837
+ */
838
+ convertRtEntityAttributeValueSync(value) {
839
+ if (value === null || value === undefined) {
840
+ return null;
841
+ }
842
+ if (Array.isArray(value)) {
843
+ return value.map(v => this.convertRtEntityAttributeValueSync(v));
844
+ }
845
+ // For nested records, return as-is (will be handled by async conversion if needed)
846
+ if (typeof value === 'object' && value.attributes !== undefined) {
847
+ return value;
848
+ }
849
+ return value;
850
+ }
851
+ /**
852
+ * Infer attribute type from JavaScript value.
853
+ * Used only for convertObjectToProperties where no CK type is available.
854
+ */
855
+ inferAttributeType(value) {
856
+ if (value === null || value === undefined) {
857
+ return AttributeValueTypeDto.StringDto;
858
+ }
859
+ if (typeof value === 'boolean') {
860
+ return AttributeValueTypeDto.BooleanDto;
861
+ }
862
+ if (typeof value === 'number') {
863
+ return Number.isInteger(value) ? AttributeValueTypeDto.IntDto : AttributeValueTypeDto.DoubleDto;
864
+ }
865
+ if (typeof value === 'string') {
866
+ if (this.isIsoDateString(value)) {
867
+ return AttributeValueTypeDto.DateTimeDto;
868
+ }
869
+ return AttributeValueTypeDto.StringDto;
870
+ }
871
+ if (value instanceof Date) {
872
+ return AttributeValueTypeDto.DateTimeDto;
873
+ }
874
+ if (Array.isArray(value)) {
875
+ if (value.length > 0) {
876
+ const firstType = this.inferAttributeType(value[0]);
877
+ switch (firstType) {
878
+ case AttributeValueTypeDto.StringDto:
879
+ return AttributeValueTypeDto.StringArrayDto;
880
+ case AttributeValueTypeDto.IntDto:
881
+ case AttributeValueTypeDto.DoubleDto:
882
+ return AttributeValueTypeDto.IntegerArrayDto;
883
+ case AttributeValueTypeDto.RecordDto:
884
+ return AttributeValueTypeDto.RecordArrayDto;
885
+ default:
886
+ return AttributeValueTypeDto.StringArrayDto;
887
+ }
888
+ }
889
+ return AttributeValueTypeDto.StringArrayDto;
890
+ }
891
+ if (typeof value === 'object') {
892
+ return AttributeValueTypeDto.RecordDto;
893
+ }
894
+ return AttributeValueTypeDto.StringDto;
895
+ }
896
+ /**
897
+ * Check if string is ISO date format
898
+ */
899
+ isIsoDateString(value) {
900
+ const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/;
901
+ return isoDateRegex.test(value) && !isNaN(Date.parse(value));
902
+ }
903
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: PropertyConverterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
904
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: PropertyConverterService, providedIn: 'root' });
24
905
  }
25
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: ListElementModule, decorators: [{
26
- type: NgModule,
906
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: PropertyConverterService, decorators: [{
907
+ type: Injectable,
27
908
  args: [{
28
- declarations: [],
29
- imports: [CommonModule]
909
+ providedIn: 'root'
30
910
  }]
31
911
  }] });
32
912
 
33
- class OctoListNavigationDataInfo {
34
- skip;
35
- take;
36
- searchFilter;
37
- sort;
913
+ class CkTypeSelectorDialogComponent extends DialogContentBase {
914
+ ckTypeSelectorService = inject(CkTypeSelectorService);
915
+ searchSubject = new Subject();
916
+ subscriptions = new Subscription();
917
+ searchIcon = searchIcon;
918
+ filterClearIcon = filterClearIcon;
919
+ dialogTitle = 'Select Construction Kit Type';
920
+ allowAbstract = false;
921
+ searchText = '';
922
+ selectedModel = null;
923
+ availableModels = [];
924
+ gridData = { data: [], total: 0 };
925
+ isLoading = false;
926
+ pageSize = 50;
927
+ skip = 0;
928
+ selectedKeys = [];
929
+ selectedType = null;
930
+ // Selection key function - returns fullName as unique identifier
931
+ selectItemBy = (context) => context.dataItem.fullName;
932
+ initialCkModelIds;
38
933
  constructor() {
934
+ super(inject(DialogRef));
935
+ }
936
+ ngOnInit() {
937
+ const data = this.dialog.content?.instance?.data;
938
+ if (data) {
939
+ this.dialogTitle = data.dialogTitle || 'Select Construction Kit Type';
940
+ this.allowAbstract = data.allowAbstract ?? false;
941
+ this.initialCkModelIds = data.ckModelIds;
942
+ if (data.selectedCkTypeId) {
943
+ this.selectedKeys = [data.selectedCkTypeId];
944
+ }
945
+ }
946
+ // Set up search debouncing
947
+ this.subscriptions.add(this.searchSubject.pipe(debounceTime(300), distinctUntilChanged()).subscribe(() => {
948
+ this.skip = 0;
949
+ this.loadTypes();
950
+ }));
951
+ // Load initial types and extract available models
952
+ this.loadTypes();
953
+ this.loadAvailableModels();
954
+ }
955
+ ngOnDestroy() {
956
+ this.subscriptions.unsubscribe();
957
+ }
958
+ loadTypes() {
959
+ this.isLoading = true;
960
+ const ckModelIds = this.selectedModel
961
+ ? [this.selectedModel]
962
+ : this.initialCkModelIds;
963
+ this.subscriptions.add(this.ckTypeSelectorService.getCkTypes({
964
+ ckModelIds,
965
+ searchText: this.searchText || undefined,
966
+ first: this.pageSize,
967
+ skip: this.skip
968
+ }).subscribe({
969
+ next: result => {
970
+ this.gridData = {
971
+ data: result.items,
972
+ total: result.totalCount
973
+ };
974
+ // Restore selection if exists
975
+ if (this.selectedKeys.length > 0) {
976
+ const selectedItem = result.items.find(item => item.fullName === this.selectedKeys[0]);
977
+ if (selectedItem) {
978
+ this.selectedType = selectedItem;
979
+ }
980
+ }
981
+ this.isLoading = false;
982
+ },
983
+ error: () => {
984
+ this.isLoading = false;
985
+ this.gridData = { data: [], total: 0 };
986
+ }
987
+ }));
988
+ }
989
+ loadAvailableModels() {
990
+ // Load all types to extract unique models
991
+ this.subscriptions.add(this.ckTypeSelectorService.getCkTypes({
992
+ first: 1000
993
+ }).subscribe(result => {
994
+ const modelSet = new Set();
995
+ result.items.forEach(item => {
996
+ // Extract model from fullName (format: "ModelName-Version/TypeName-Version")
997
+ const modelMatch = item.fullName.match(/^([^/]+)\//);
998
+ if (modelMatch) {
999
+ modelSet.add(modelMatch[1]);
1000
+ }
1001
+ });
1002
+ this.availableModels = Array.from(modelSet).sort();
1003
+ }));
1004
+ }
1005
+ onSearchChange(value) {
1006
+ this.searchSubject.next(value);
1007
+ }
1008
+ onModelFilterChange(_value) {
1009
+ this.skip = 0;
1010
+ this.loadTypes();
1011
+ }
1012
+ clearFilters() {
1013
+ this.searchText = '';
1014
+ this.selectedModel = null;
39
1015
  this.skip = 0;
40
- this.take = 10;
1016
+ this.loadTypes();
1017
+ }
1018
+ onPageChange(event) {
1019
+ this.skip = event.skip;
1020
+ this.pageSize = event.take;
1021
+ this.loadTypes();
1022
+ }
1023
+ onSelectionChange(event) {
1024
+ const selectedItems = event.selectedRows;
1025
+ if (selectedItems && selectedItems.length > 0) {
1026
+ this.selectedType = selectedItems[0].dataItem;
1027
+ }
1028
+ else {
1029
+ this.selectedType = null;
1030
+ }
1031
+ }
1032
+ onCancel() {
1033
+ this.dialog.close();
1034
+ }
1035
+ onConfirm() {
1036
+ if (this.selectedType) {
1037
+ const result = {
1038
+ selectedCkType: this.selectedType
1039
+ };
1040
+ this.dialog.close(result);
1041
+ }
1042
+ }
1043
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CkTypeSelectorDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1044
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.5", type: CkTypeSelectorDialogComponent, isStandalone: true, selector: "mm-ck-type-selector-dialog", usesInheritance: true, ngImport: i0, template: `
1045
+ <div class="ck-type-selector-container">
1046
+ <div class="filter-container">
1047
+ <div class="filter-row">
1048
+ <div class="filter-item">
1049
+ <label>Model Filter</label>
1050
+ <kendo-combobox
1051
+ [data]="availableModels"
1052
+ [(ngModel)]="selectedModel"
1053
+ (valueChange)="onModelFilterChange($event)"
1054
+ [allowCustom]="false"
1055
+ [clearButton]="true"
1056
+ placeholder="All Models"
1057
+ class="filter-input">
1058
+ </kendo-combobox>
1059
+ </div>
1060
+ <div class="filter-item flex-grow">
1061
+ <label>Type Search</label>
1062
+ <kendo-textbox
1063
+ [(ngModel)]="searchText"
1064
+ (ngModelChange)="onSearchChange($event)"
1065
+ placeholder="Search types..."
1066
+ class="filter-input">
1067
+ <ng-template kendoTextBoxSuffixTemplate>
1068
+ <button kendoButton [svgIcon]="searchIcon" fillMode="clear" size="small"></button>
1069
+ </ng-template>
1070
+ </kendo-textbox>
1071
+ </div>
1072
+ <div class="filter-item filter-actions">
1073
+ <label>&nbsp;</label>
1074
+ <button kendoButton [svgIcon]="filterClearIcon" (click)="clearFilters()" title="Clear filters"></button>
1075
+ </div>
1076
+ </div>
1077
+ </div>
1078
+
1079
+ <div class="grid-container">
1080
+ <kendo-grid
1081
+ [data]="gridData"
1082
+ [loading]="isLoading"
1083
+ [height]="400"
1084
+ [selectable]="{ mode: 'single' }"
1085
+ [pageable]="{ pageSizes: [25, 50, 100] }"
1086
+ [pageSize]="pageSize"
1087
+ [skip]="skip"
1088
+ (pageChange)="onPageChange($event)"
1089
+ (selectionChange)="onSelectionChange($event)"
1090
+ [kendoGridSelectBy]="selectItemBy"
1091
+ [(selectedKeys)]="selectedKeys"
1092
+ class="type-grid">
1093
+ <kendo-grid-column field="rtCkTypeId" title="Type" [width]="300">
1094
+ <ng-template kendoGridCellTemplate let-dataItem>
1095
+ <span [class.abstract-type]="dataItem.isAbstract" [class.final-type]="dataItem.isFinal">
1096
+ {{ dataItem.rtCkTypeId }}
1097
+ </span>
1098
+ <span *ngIf="dataItem.isAbstract" class="type-badge abstract">abstract</span>
1099
+ <span *ngIf="dataItem.isFinal" class="type-badge final">final</span>
1100
+ </ng-template>
1101
+ </kendo-grid-column>
1102
+ <kendo-grid-column field="baseTypeRtCkTypeId" title="Base Type" [width]="200">
1103
+ <ng-template kendoGridCellTemplate let-dataItem>
1104
+ {{ dataItem.baseTypeRtCkTypeId || '-' }}
1105
+ </ng-template>
1106
+ </kendo-grid-column>
1107
+ <kendo-grid-column field="description" title="Description">
1108
+ <ng-template kendoGridCellTemplate let-dataItem>
1109
+ {{ dataItem.description || '-' }}
1110
+ </ng-template>
1111
+ </kendo-grid-column>
1112
+ </kendo-grid>
1113
+ </div>
1114
+
1115
+ <div class="selection-info" *ngIf="selectedType">
1116
+ <strong>Selected:</strong> {{ selectedType.rtCkTypeId }}
1117
+ </div>
1118
+ </div>
1119
+
1120
+ <kendo-dialog-actions>
1121
+ <button kendoButton (click)="onCancel()">Cancel</button>
1122
+ <button kendoButton themeColor="primary" [disabled]="!selectedType || (selectedType.isAbstract && !allowAbstract)" (click)="onConfirm()">OK</button>
1123
+ </kendo-dialog-actions>
1124
+ `, isInline: true, styles: [".ck-type-selector-container{display:flex;flex-direction:column;height:100%;padding:20px;min-width:700px;box-sizing:border-box}.filter-container{margin-bottom:16px;flex-shrink:0}.filter-row{display:flex;gap:16px;align-items:flex-end}.filter-item{display:flex;flex-direction:column;gap:4px}.filter-item label{font-size:12px;font-weight:500}.filter-item.flex-grow{flex:1}.filter-item.filter-actions{flex-shrink:0}.filter-input{min-width:180px}.grid-container{flex:1;min-height:0}.type-grid{border-radius:4px}.type-grid ::ng-deep .k-grid-table tbody tr{cursor:pointer}.abstract-type{font-style:italic;opacity:.7}.final-type{font-weight:600}.type-badge{display:inline-block;font-size:10px;padding:1px 6px;border-radius:10px;margin-left:8px;text-transform:uppercase}.type-badge.abstract{background-color:var(--kendo-color-warning, #ffc107);color:var(--kendo-color-on-warning, #000);opacity:.8}.type-badge.final{background-color:var(--kendo-color-success, #28a745);color:var(--kendo-color-on-success, #fff);opacity:.8}.selection-info{margin-top:12px;padding:8px 12px;background:var(--kendo-color-success-subtle, #d4edda);border:1px solid var(--kendo-color-success, #28a745);border-radius:4px;font-size:14px;flex-shrink:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: GridModule }, { kind: "component", type: i2$1.GridComponent, selector: "kendo-grid", inputs: ["data", "pageSize", "height", "rowHeight", "adaptiveMode", "detailRowHeight", "skip", "scrollable", "selectable", "sort", "size", "trackBy", "filter", "group", "virtualColumns", "filterable", "sortable", "pageable", "groupable", "gridResizable", "rowReorderable", "navigable", "autoSize", "rowClass", "rowSticky", "rowSelected", "isRowSelectable", "cellSelected", "resizable", "reorderable", "loading", "columnMenu", "hideHeader", "showInactiveTools", "isDetailExpanded", "isGroupExpanded", "dataLayoutMode"], outputs: ["filterChange", "pageChange", "groupChange", "sortChange", "selectionChange", "rowReorder", "dataStateChange", "gridStateChange", "groupExpand", "groupCollapse", "detailExpand", "detailCollapse", "edit", "cancel", "save", "remove", "add", "cellClose", "cellClick", "pdfExport", "excelExport", "columnResize", "columnReorder", "columnVisibilityChange", "columnLockedChange", "columnStickyChange", "scrollBottom", "contentScroll"], exportAs: ["kendoGrid"] }, { kind: "directive", type: i2$1.SelectionDirective, selector: "[kendoGridSelectBy]" }, { kind: "component", type: i2$1.ColumnComponent, selector: "kendo-grid-column", inputs: ["field", "format", "sortable", "groupable", "editor", "filter", "filterVariant", "filterable", "editable"] }, { kind: "directive", type: i2$1.CellTemplateDirective, selector: "[kendoGridCellTemplate]" }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i3.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i4.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "directive", type: i4.TextBoxSuffixTemplateDirective, selector: "[kendoTextBoxSuffixTemplate]", inputs: ["showSeparator"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "component", type: i3$1.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: "ngmodule", type: IconsModule }, { kind: "ngmodule", type: LoaderModule }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i5.DialogActionsComponent, selector: "kendo-dialog-actions", inputs: ["actions", "layout"], outputs: ["action"] }] });
1125
+ }
1126
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CkTypeSelectorDialogComponent, decorators: [{
1127
+ type: Component,
1128
+ args: [{ selector: 'mm-ck-type-selector-dialog', standalone: true, imports: [
1129
+ CommonModule,
1130
+ FormsModule,
1131
+ GridModule,
1132
+ ButtonsModule,
1133
+ InputsModule,
1134
+ DropDownsModule,
1135
+ IconsModule,
1136
+ LoaderModule,
1137
+ DialogModule
1138
+ ], template: `
1139
+ <div class="ck-type-selector-container">
1140
+ <div class="filter-container">
1141
+ <div class="filter-row">
1142
+ <div class="filter-item">
1143
+ <label>Model Filter</label>
1144
+ <kendo-combobox
1145
+ [data]="availableModels"
1146
+ [(ngModel)]="selectedModel"
1147
+ (valueChange)="onModelFilterChange($event)"
1148
+ [allowCustom]="false"
1149
+ [clearButton]="true"
1150
+ placeholder="All Models"
1151
+ class="filter-input">
1152
+ </kendo-combobox>
1153
+ </div>
1154
+ <div class="filter-item flex-grow">
1155
+ <label>Type Search</label>
1156
+ <kendo-textbox
1157
+ [(ngModel)]="searchText"
1158
+ (ngModelChange)="onSearchChange($event)"
1159
+ placeholder="Search types..."
1160
+ class="filter-input">
1161
+ <ng-template kendoTextBoxSuffixTemplate>
1162
+ <button kendoButton [svgIcon]="searchIcon" fillMode="clear" size="small"></button>
1163
+ </ng-template>
1164
+ </kendo-textbox>
1165
+ </div>
1166
+ <div class="filter-item filter-actions">
1167
+ <label>&nbsp;</label>
1168
+ <button kendoButton [svgIcon]="filterClearIcon" (click)="clearFilters()" title="Clear filters"></button>
1169
+ </div>
1170
+ </div>
1171
+ </div>
1172
+
1173
+ <div class="grid-container">
1174
+ <kendo-grid
1175
+ [data]="gridData"
1176
+ [loading]="isLoading"
1177
+ [height]="400"
1178
+ [selectable]="{ mode: 'single' }"
1179
+ [pageable]="{ pageSizes: [25, 50, 100] }"
1180
+ [pageSize]="pageSize"
1181
+ [skip]="skip"
1182
+ (pageChange)="onPageChange($event)"
1183
+ (selectionChange)="onSelectionChange($event)"
1184
+ [kendoGridSelectBy]="selectItemBy"
1185
+ [(selectedKeys)]="selectedKeys"
1186
+ class="type-grid">
1187
+ <kendo-grid-column field="rtCkTypeId" title="Type" [width]="300">
1188
+ <ng-template kendoGridCellTemplate let-dataItem>
1189
+ <span [class.abstract-type]="dataItem.isAbstract" [class.final-type]="dataItem.isFinal">
1190
+ {{ dataItem.rtCkTypeId }}
1191
+ </span>
1192
+ <span *ngIf="dataItem.isAbstract" class="type-badge abstract">abstract</span>
1193
+ <span *ngIf="dataItem.isFinal" class="type-badge final">final</span>
1194
+ </ng-template>
1195
+ </kendo-grid-column>
1196
+ <kendo-grid-column field="baseTypeRtCkTypeId" title="Base Type" [width]="200">
1197
+ <ng-template kendoGridCellTemplate let-dataItem>
1198
+ {{ dataItem.baseTypeRtCkTypeId || '-' }}
1199
+ </ng-template>
1200
+ </kendo-grid-column>
1201
+ <kendo-grid-column field="description" title="Description">
1202
+ <ng-template kendoGridCellTemplate let-dataItem>
1203
+ {{ dataItem.description || '-' }}
1204
+ </ng-template>
1205
+ </kendo-grid-column>
1206
+ </kendo-grid>
1207
+ </div>
1208
+
1209
+ <div class="selection-info" *ngIf="selectedType">
1210
+ <strong>Selected:</strong> {{ selectedType.rtCkTypeId }}
1211
+ </div>
1212
+ </div>
1213
+
1214
+ <kendo-dialog-actions>
1215
+ <button kendoButton (click)="onCancel()">Cancel</button>
1216
+ <button kendoButton themeColor="primary" [disabled]="!selectedType || (selectedType.isAbstract && !allowAbstract)" (click)="onConfirm()">OK</button>
1217
+ </kendo-dialog-actions>
1218
+ `, styles: [".ck-type-selector-container{display:flex;flex-direction:column;height:100%;padding:20px;min-width:700px;box-sizing:border-box}.filter-container{margin-bottom:16px;flex-shrink:0}.filter-row{display:flex;gap:16px;align-items:flex-end}.filter-item{display:flex;flex-direction:column;gap:4px}.filter-item label{font-size:12px;font-weight:500}.filter-item.flex-grow{flex:1}.filter-item.filter-actions{flex-shrink:0}.filter-input{min-width:180px}.grid-container{flex:1;min-height:0}.type-grid{border-radius:4px}.type-grid ::ng-deep .k-grid-table tbody tr{cursor:pointer}.abstract-type{font-style:italic;opacity:.7}.final-type{font-weight:600}.type-badge{display:inline-block;font-size:10px;padding:1px 6px;border-radius:10px;margin-left:8px;text-transform:uppercase}.type-badge.abstract{background-color:var(--kendo-color-warning, #ffc107);color:var(--kendo-color-on-warning, #000);opacity:.8}.type-badge.final{background-color:var(--kendo-color-success, #28a745);color:var(--kendo-color-on-success, #fff);opacity:.8}.selection-info{margin-top:12px;padding:8px 12px;background:var(--kendo-color-success-subtle, #d4edda);border:1px solid var(--kendo-color-success, #28a745);border-radius:4px;font-size:14px;flex-shrink:0}\n"] }]
1219
+ }], ctorParameters: () => [] });
1220
+
1221
+ class CkTypeSelectorDialogService {
1222
+ dialogService = inject(DialogService);
1223
+ /**
1224
+ * Opens the CkType selector dialog
1225
+ * @param options Dialog options
1226
+ * @returns Promise that resolves with the result containing selected CkType and confirmation status
1227
+ */
1228
+ async openCkTypeSelector(options = {}) {
1229
+ const data = {
1230
+ selectedCkTypeId: options.selectedCkTypeId,
1231
+ ckModelIds: options.ckModelIds,
1232
+ dialogTitle: options.dialogTitle,
1233
+ allowAbstract: options.allowAbstract
1234
+ };
1235
+ const dialogRef = this.dialogService.open({
1236
+ content: CkTypeSelectorDialogComponent,
1237
+ width: 900,
1238
+ height: 650,
1239
+ minWidth: 750,
1240
+ minHeight: 550,
1241
+ title: options.dialogTitle || 'Select Construction Kit Type'
1242
+ });
1243
+ // Pass data to the component
1244
+ if (dialogRef.content?.instance) {
1245
+ dialogRef.content.instance.data = data;
1246
+ }
1247
+ try {
1248
+ const result = await firstValueFrom(dialogRef.result);
1249
+ if (result && typeof result === 'object' && 'selectedCkType' in result) {
1250
+ // User clicked OK and we have a result
1251
+ const dialogResult = result;
1252
+ return {
1253
+ confirmed: true,
1254
+ selectedCkType: dialogResult.selectedCkType
1255
+ };
1256
+ }
1257
+ else {
1258
+ // User clicked Cancel or closed dialog
1259
+ return {
1260
+ confirmed: false,
1261
+ selectedCkType: null
1262
+ };
1263
+ }
1264
+ }
1265
+ catch {
1266
+ // Dialog was closed without result (e.g., ESC key, X button)
1267
+ return {
1268
+ confirmed: false,
1269
+ selectedCkType: null
1270
+ };
1271
+ }
1272
+ }
1273
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CkTypeSelectorDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1274
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CkTypeSelectorDialogService });
1275
+ }
1276
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CkTypeSelectorDialogService, decorators: [{
1277
+ type: Injectable
1278
+ }] });
1279
+
1280
+ // noinspection JSUnusedGlobalSymbols
1281
+ class OctoGraphQlDataSource extends DataSourceTyped {
1282
+ _searchFilterAttributePaths = [];
1283
+ // noinspection JSUnusedGlobalSymbols
1284
+ constructor(listViewComponent) {
1285
+ super(listViewComponent);
41
1286
  }
1287
+ get searchFilterAttributePaths() {
1288
+ return this._searchFilterAttributePaths;
1289
+ }
1290
+ set searchFilterAttributePaths(value) {
1291
+ this._searchFilterAttributePaths = value;
1292
+ }
1293
+ // noinspection JSUnusedGlobalSymbols
1294
+ getFieldFilterDefinitions(state) {
1295
+ let fieldFilters = null;
1296
+ if (state.filter?.filters) {
1297
+ fieldFilters = state.filter.filters.map((f) => {
1298
+ if (isCompositeFilterDescriptor(f)) {
1299
+ throw new Error('Composite filter descriptor not supported');
1300
+ }
1301
+ const { operator, value } = OctoGraphQlDataSource.getOperatorAndValue(f.operator, f.value);
1302
+ return {
1303
+ attributePath: f.field,
1304
+ operator: operator,
1305
+ comparisonValue: value
1306
+ };
1307
+ });
1308
+ }
1309
+ return fieldFilters;
1310
+ }
1311
+ // noinspection JSUnusedGlobalSymbols
1312
+ getSearchFilterDefinitions(textSearchValue) {
1313
+ let searchFilterDto = null;
1314
+ if (textSearchValue) {
1315
+ searchFilterDto = {
1316
+ type: SearchFilterTypesDto.AttributeFilterDto,
1317
+ attributePaths: this.searchFilterAttributePaths,
1318
+ searchTerm: textSearchValue,
1319
+ };
1320
+ }
1321
+ return searchFilterDto;
1322
+ }
1323
+ // noinspection JSUnusedGlobalSymbols
1324
+ getSortDefinitions(state) {
1325
+ let sort = null;
1326
+ if (state.sort) {
1327
+ sort = new Array();
1328
+ state.sort.forEach((s) => {
1329
+ switch (s.dir) {
1330
+ case 'asc':
1331
+ sort?.push({
1332
+ attributePath: s.field,
1333
+ sortOrder: SortOrdersDto.AscendingDto
1334
+ });
1335
+ break;
1336
+ case 'desc':
1337
+ sort?.push({
1338
+ attributePath: s.field,
1339
+ sortOrder: SortOrdersDto.DescendingDto,
1340
+ });
1341
+ break;
1342
+ default:
1343
+ sort?.push({
1344
+ attributePath: s.field,
1345
+ sortOrder: SortOrdersDto.DefaultDto,
1346
+ });
1347
+ break;
1348
+ }
1349
+ });
1350
+ }
1351
+ return sort;
1352
+ }
1353
+ static getOperatorAndValue(operator, value) {
1354
+ switch (operator) {
1355
+ case 'eq':
1356
+ return { operator: FieldFilterOperatorsDto.EqualsDto, value: value };
1357
+ case 'neq':
1358
+ return { operator: FieldFilterOperatorsDto.NotEqualsDto, value: value };
1359
+ case 'lt':
1360
+ return { operator: FieldFilterOperatorsDto.LessThanDto, value: value };
1361
+ case 'lte':
1362
+ return { operator: FieldFilterOperatorsDto.LessEqualThanDto, value: value };
1363
+ case 'gt':
1364
+ return { operator: FieldFilterOperatorsDto.GreaterThanDto, value: value };
1365
+ case 'gte':
1366
+ return { operator: FieldFilterOperatorsDto.GreaterEqualThanDto, value: value };
1367
+ case 'contains':
1368
+ return { operator: FieldFilterOperatorsDto.LikeDto, value: value };
1369
+ case 'doesnotcontain':
1370
+ return { operator: FieldFilterOperatorsDto.MatchRegExDto, value: `^(?!.*${value}).*$` };
1371
+ case 'startswith':
1372
+ return { operator: FieldFilterOperatorsDto.MatchRegExDto, value: value + '.*' };
1373
+ case 'endswith':
1374
+ return { operator: FieldFilterOperatorsDto.MatchRegExDto, value: '.*' + value };
1375
+ case 'isnull':
1376
+ return { operator: FieldFilterOperatorsDto.EqualsDto, value: null };
1377
+ case 'isnotnull':
1378
+ return { operator: FieldFilterOperatorsDto.NotEqualsDto, value: null };
1379
+ case 'isempty':
1380
+ return { operator: FieldFilterOperatorsDto.EqualsDto, value: "" };
1381
+ case 'isnotempty':
1382
+ return { operator: FieldFilterOperatorsDto.NotEqualsDto, value: "" };
1383
+ default:
1384
+ throw new Error('The filter operator is not supported');
1385
+ }
1386
+ }
1387
+ }
1388
+
1389
+ class OctoGraphQlHierarchyDataSource extends HierarchyDataSourceBase {
1390
+ }
1391
+
1392
+ /**
1393
+ * Component for displaying property values with appropriate formatting
1394
+ */
1395
+ class PropertyValueDisplayComponent {
1396
+ value;
1397
+ type = AttributeValueTypeDto.StringDto;
1398
+ displayMode = PropertyDisplayMode.Text;
1399
+ /** Emitted when a binary download is requested */
1400
+ binaryDownload = new EventEmitter();
1401
+ // Expansion state
1402
+ isExpanded = false;
1403
+ PropertyDisplayMode = PropertyDisplayMode;
1404
+ AttributeValueTypeDto = AttributeValueTypeDto;
1405
+ Array = Array;
1406
+ chevronRightIcon = chevronRightIcon;
1407
+ chevronDownIcon = chevronDownIcon;
1408
+ downloadIcon = downloadIcon;
1409
+ ngOnInit() {
1410
+ // Debug every property value display component creation
1411
+ console.log('PropertyValueDisplayComponent ngOnInit:', {
1412
+ type: this.type,
1413
+ value: this.value,
1414
+ valueType: typeof this.value,
1415
+ valueStringified: JSON.stringify(this.value),
1416
+ isExpandableRecord: this.isExpandableRecord()
1417
+ });
1418
+ }
1419
+ /**
1420
+ * Get formatted display value
1421
+ */
1422
+ getFormattedValue() {
1423
+ if (this.value === null) {
1424
+ return '<null>';
1425
+ }
1426
+ if (this.value === undefined) {
1427
+ return '<undefined>';
1428
+ }
1429
+ if (this.value === '') {
1430
+ return '<empty>';
1431
+ }
1432
+ switch (this.type) {
1433
+ case AttributeValueTypeDto.BooleanDto:
1434
+ return this.value ? '✓ True' : '✗ False';
1435
+ case AttributeValueTypeDto.DateTimeDto:
1436
+ case AttributeValueTypeDto.DateTimeOffsetDto:
1437
+ return this.formatDateTime(this.value);
1438
+ case AttributeValueTypeDto.StringArrayDto:
1439
+ case AttributeValueTypeDto.IntegerArrayDto:
1440
+ case AttributeValueTypeDto.IntArrayDto:
1441
+ return this.formatArray(this.value);
1442
+ case AttributeValueTypeDto.RecordDto:
1443
+ case AttributeValueTypeDto.RecordArrayDto:
1444
+ return this.displayMode === PropertyDisplayMode.Json
1445
+ ? JSON.stringify(this.value, null, 2)
1446
+ : this.formatObject(this.value);
1447
+ case AttributeValueTypeDto.BinaryDto:
1448
+ case AttributeValueTypeDto.BinaryLinkedDto:
1449
+ return this.formatBinary(this.value);
1450
+ case AttributeValueTypeDto.IntDto:
1451
+ case AttributeValueTypeDto.IntegerDto:
1452
+ case AttributeValueTypeDto.Integer_64Dto:
1453
+ case AttributeValueTypeDto.Int_64Dto:
1454
+ return this.formatNumber(this.value, 0);
1455
+ case AttributeValueTypeDto.DoubleDto:
1456
+ return this.formatNumber(this.value, 2);
1457
+ default:
1458
+ return String(this.value);
1459
+ }
1460
+ }
1461
+ /**
1462
+ * Check if this is an expandable record type
1463
+ */
1464
+ isExpandableRecord() {
1465
+ // Comprehensive debug logging
1466
+ console.log('PropertyValueDisplay: isExpandableRecord check:', {
1467
+ type: this.type,
1468
+ typeString: String(this.type),
1469
+ typeOf: typeof this.type,
1470
+ AttributeValueTypeDtoRecordDto: AttributeValueTypeDto.RecordDto,
1471
+ AttributeValueTypeDtoRecordArrayDto: AttributeValueTypeDto.RecordArrayDto,
1472
+ typeIsRecordDto: this.type === AttributeValueTypeDto.RecordDto,
1473
+ typeIsRecordDtoString: this.type === 'RECORD',
1474
+ typeIsRecordArrayDto: this.type === AttributeValueTypeDto.RecordArrayDto,
1475
+ value: this.value,
1476
+ valueType: typeof this.value,
1477
+ valueIsNull: this.value == null,
1478
+ valueKeys: this.value && typeof this.value === 'object' ? Object.keys(this.value) : 'not object'
1479
+ });
1480
+ // Check for record types
1481
+ const isRecordDto = this.type === AttributeValueTypeDto.RecordDto;
1482
+ const isRecordArrayDto = this.type === AttributeValueTypeDto.RecordArrayDto;
1483
+ const isRecordType = isRecordDto || isRecordArrayDto;
1484
+ // Simple check: does it have a value?
1485
+ const hasValue = this.value != null;
1486
+ // Simple check: is it an object or array?
1487
+ const isObjectLike = typeof this.value === 'object' && this.value != null;
1488
+ const result = isRecordType && hasValue && isObjectLike;
1489
+ console.log('PropertyValueDisplay: Expandable result:', {
1490
+ isRecordDto,
1491
+ isRecordArrayDto,
1492
+ isRecordType,
1493
+ hasValue,
1494
+ isObjectLike,
1495
+ finalResult: result
1496
+ });
1497
+ return result;
1498
+ }
1499
+ /**
1500
+ * Check if the type is complex (object/array)
1501
+ */
1502
+ isComplexType() {
1503
+ return [
1504
+ AttributeValueTypeDto.RecordDto,
1505
+ AttributeValueTypeDto.RecordArrayDto,
1506
+ AttributeValueTypeDto.StringArrayDto,
1507
+ AttributeValueTypeDto.IntegerArrayDto,
1508
+ AttributeValueTypeDto.IntArrayDto,
1509
+ AttributeValueTypeDto.BinaryDto,
1510
+ AttributeValueTypeDto.BinaryLinkedDto
1511
+ ].includes(this.type);
1512
+ }
1513
+ /**
1514
+ * Get type indicator text
1515
+ */
1516
+ getTypeIndicator() {
1517
+ switch (this.type) {
1518
+ case AttributeValueTypeDto.RecordDto:
1519
+ return 'RECORD';
1520
+ case AttributeValueTypeDto.RecordArrayDto:
1521
+ return 'ARR[RECORD]';
1522
+ case AttributeValueTypeDto.StringArrayDto:
1523
+ return 'ARR[STR]';
1524
+ case AttributeValueTypeDto.IntegerArrayDto:
1525
+ case AttributeValueTypeDto.IntArrayDto:
1526
+ return 'ARR[INT]';
1527
+ case AttributeValueTypeDto.BinaryDto:
1528
+ case AttributeValueTypeDto.BinaryLinkedDto:
1529
+ return 'BIN';
1530
+ default:
1531
+ return this.type.replace('_DTO', '').replace('DTO', '');
1532
+ }
1533
+ }
1534
+ /**
1535
+ * Get type description for tooltip
1536
+ */
1537
+ getTypeDescription() {
1538
+ switch (this.type) {
1539
+ case AttributeValueTypeDto.RecordDto:
1540
+ return 'Complex object';
1541
+ case AttributeValueTypeDto.RecordArrayDto:
1542
+ return 'Array of complex objects';
1543
+ case AttributeValueTypeDto.StringArrayDto:
1544
+ return 'Array of strings';
1545
+ case AttributeValueTypeDto.IntegerArrayDto:
1546
+ case AttributeValueTypeDto.IntArrayDto:
1547
+ return 'Array of numbers';
1548
+ case AttributeValueTypeDto.BinaryDto:
1549
+ case AttributeValueTypeDto.BinaryLinkedDto:
1550
+ return 'Binary data';
1551
+ default:
1552
+ return `Type: ${this.type}`;
1553
+ }
1554
+ }
1555
+ /**
1556
+ * Format date/time values
1557
+ */
1558
+ formatDateTime(value) {
1559
+ try {
1560
+ const date = value instanceof Date ? value : new Date(value);
1561
+ if (isNaN(date.getTime())) {
1562
+ return String(value);
1563
+ }
1564
+ return date.toLocaleString();
1565
+ }
1566
+ catch {
1567
+ return String(value);
1568
+ }
1569
+ }
1570
+ /**
1571
+ * Format array values
1572
+ */
1573
+ formatArray(value) {
1574
+ if (!Array.isArray(value)) {
1575
+ return String(value);
1576
+ }
1577
+ if (value.length === 0) {
1578
+ return '[]';
1579
+ }
1580
+ if (value.length <= 3) {
1581
+ return `[${value.join(', ')}]`;
1582
+ }
1583
+ return `[${value.slice(0, 3).join(', ')}, ... +${value.length - 3} more]`;
1584
+ }
1585
+ /**
1586
+ * Format object values
1587
+ */
1588
+ formatObject(value) {
1589
+ if (typeof value !== 'object' || value === null) {
1590
+ return String(value);
1591
+ }
1592
+ const keys = Object.keys(value);
1593
+ if (keys.length === 0) {
1594
+ return '{}';
1595
+ }
1596
+ if (keys.length <= 2) {
1597
+ const preview = keys.map(key => `${key}: ${this.truncateValue(value[key])}`).join(', ');
1598
+ return `{${preview}}`;
1599
+ }
1600
+ return `{${keys.slice(0, 2).map(key => `${key}: ${this.truncateValue(value[key])}`).join(', ')}, ... +${keys.length - 2} more}`;
1601
+ }
1602
+ /**
1603
+ * Format binary data
1604
+ */
1605
+ formatBinary(value) {
1606
+ if (value instanceof ArrayBuffer) {
1607
+ return `<Binary: ${value.byteLength} bytes>`;
1608
+ }
1609
+ if (typeof value === 'string' && value.startsWith('data:')) {
1610
+ return '<Base64 Data>';
1611
+ }
1612
+ return '<Binary Data>';
1613
+ }
1614
+ /**
1615
+ * Format numeric values
1616
+ */
1617
+ formatNumber(value, decimals) {
1618
+ const num = Number(value);
1619
+ if (isNaN(num)) {
1620
+ return String(value);
1621
+ }
1622
+ return decimals > 0 ? num.toFixed(decimals) : num.toString();
1623
+ }
1624
+ /**
1625
+ * Truncate long values for preview
1626
+ */
1627
+ truncateValue(value) {
1628
+ const str = String(value);
1629
+ return str.length > 20 ? str.substring(0, 20) + '...' : str;
1630
+ }
1631
+ /**
1632
+ * Toggle expansion state
1633
+ */
1634
+ toggleExpansion() {
1635
+ this.isExpanded = !this.isExpanded;
1636
+ }
1637
+ /**
1638
+ * Get summary text for record header
1639
+ */
1640
+ getRecordSummary() {
1641
+ if (this.type === AttributeValueTypeDto.RecordArrayDto && Array.isArray(this.value)) {
1642
+ return `Array with ${this.value.length} item${this.value.length !== 1 ? 's' : ''}`;
1643
+ }
1644
+ if (typeof this.value === 'object' && this.value !== null) {
1645
+ // Handle regular objects
1646
+ const keys = Object.keys(this.value);
1647
+ return `Object with ${keys.length} propert${keys.length !== 1 ? 'ies' : 'y'}`;
1648
+ }
1649
+ return 'Complex object';
1650
+ }
1651
+ /**
1652
+ * Get properties of an object for display
1653
+ */
1654
+ getObjectProperties(obj) {
1655
+ if (typeof obj !== 'object' || obj === null) {
1656
+ return [];
1657
+ }
1658
+ if (Array.isArray(obj) && obj.length > 0 && obj[0].id === 'ckRecordId') {
1659
+ // Handle array of records
1660
+ return obj.map((item, _) => ({
1661
+ key: item.name,
1662
+ value: item.value
1663
+ }));
1664
+ }
1665
+ // Handle regular objects
1666
+ return Object.keys(obj).map(key => ({
1667
+ key,
1668
+ value: obj[key]
1669
+ }));
1670
+ }
1671
+ /**
1672
+ * Determine the appropriate type for a nested property value
1673
+ */
1674
+ getPropertyType(value) {
1675
+ if (value === null || value === undefined) {
1676
+ return AttributeValueTypeDto.StringDto;
1677
+ }
1678
+ if (typeof value === 'boolean') {
1679
+ return AttributeValueTypeDto.BooleanDto;
1680
+ }
1681
+ if (typeof value === 'number') {
1682
+ return Number.isInteger(value) ? AttributeValueTypeDto.IntegerDto : AttributeValueTypeDto.DoubleDto;
1683
+ }
1684
+ if (typeof value === 'string') {
1685
+ // Check if it looks like a date
1686
+ if (value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)) {
1687
+ return AttributeValueTypeDto.DateTimeDto;
1688
+ }
1689
+ return AttributeValueTypeDto.StringDto;
1690
+ }
1691
+ if (Array.isArray(value)) {
1692
+ if (value.length > 0 && typeof value[0] === 'object' && value[0].id === 'ckRecordId') {
1693
+ return AttributeValueTypeDto.RecordDto;
1694
+ }
1695
+ if (value.length > 0 && typeof value[0] === 'object') {
1696
+ return AttributeValueTypeDto.RecordArrayDto;
1697
+ }
1698
+ if (value.length > 0 && typeof value[0] === 'string') {
1699
+ return AttributeValueTypeDto.StringArrayDto;
1700
+ }
1701
+ if (value.length > 0 && typeof value[0] === 'number') {
1702
+ return AttributeValueTypeDto.IntegerArrayDto;
1703
+ }
1704
+ return AttributeValueTypeDto.StringArrayDto; // fallback
1705
+ }
1706
+ return AttributeValueTypeDto.StringDto;
1707
+ }
1708
+ // ========== Binary Linked Methods ==========
1709
+ /**
1710
+ * Check if this is a BINARY_LINKED type with download capability
1711
+ */
1712
+ isBinaryLinkedWithDownload() {
1713
+ if (this.type !== AttributeValueTypeDto.BinaryLinkedDto) {
1714
+ return false;
1715
+ }
1716
+ // Check if value has binaryId or downloadUri (LargeBinaryInfo structure)
1717
+ return this.value && typeof this.value === 'object' && ('binaryId' in this.value || 'downloadUri' in this.value);
1718
+ }
1719
+ /**
1720
+ * Get the filename from binary info
1721
+ */
1722
+ getBinaryFilename() {
1723
+ if (!this.value || typeof this.value !== 'object') {
1724
+ return null;
1725
+ }
1726
+ return this.value.filename || null;
1727
+ }
1728
+ /**
1729
+ * Get the file size from binary info
1730
+ */
1731
+ getBinarySize() {
1732
+ if (!this.value || typeof this.value !== 'object') {
1733
+ return null;
1734
+ }
1735
+ return this.value.size || null;
1736
+ }
1737
+ /**
1738
+ * Get the content type from binary info
1739
+ */
1740
+ getBinaryContentType() {
1741
+ if (!this.value || typeof this.value !== 'object') {
1742
+ return null;
1743
+ }
1744
+ return this.value.contentType || null;
1745
+ }
1746
+ /**
1747
+ * Format file size to human readable format
1748
+ */
1749
+ formatFileSize(bytes) {
1750
+ if (bytes === null || bytes === undefined) {
1751
+ return '';
1752
+ }
1753
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
1754
+ let unitIndex = 0;
1755
+ let size = bytes;
1756
+ while (size >= 1024 && unitIndex < units.length - 1) {
1757
+ size /= 1024;
1758
+ unitIndex++;
1759
+ }
1760
+ return `${size.toFixed(unitIndex > 0 ? 1 : 0)} ${units[unitIndex]}`;
1761
+ }
1762
+ /**
1763
+ * Handle download button click
1764
+ */
1765
+ onDownload() {
1766
+ if (!this.value || typeof this.value !== 'object') {
1767
+ console.warn('No binary value available');
1768
+ return;
1769
+ }
1770
+ // If downloadUri is available, open directly
1771
+ if (this.value.downloadUri) {
1772
+ window.open(this.value.downloadUri, '_blank', 'noopener,noreferrer');
1773
+ return;
1774
+ }
1775
+ // Otherwise, emit event for parent to handle (needs to load downloadUri)
1776
+ if (this.value.binaryId) {
1777
+ const event = {
1778
+ binaryId: this.value.binaryId,
1779
+ filename: this.value.filename,
1780
+ contentType: this.value.contentType
1781
+ };
1782
+ this.binaryDownload.emit(event);
1783
+ }
1784
+ else {
1785
+ console.warn('No binaryId or downloadUri available');
1786
+ }
1787
+ }
1788
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: PropertyValueDisplayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1789
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: PropertyValueDisplayComponent, isStandalone: true, selector: "mm-property-value-display", inputs: { value: "value", type: "type", displayMode: "displayMode" }, outputs: { binaryDownload: "binaryDownload" }, ngImport: i0, template: `
1790
+ <div class="property-value-display" [ngClass]="'type-' + type.toLowerCase()">
1791
+
1792
+ @if (isExpandableRecord()) {
1793
+ <div class="expandable-record">
1794
+ <!-- Expand/Collapse Button and Summary -->
1795
+ <div class="record-header" (click)="toggleExpansion()">
1796
+ <kendo-svgicon
1797
+ [icon]="isExpanded ? chevronDownIcon : chevronRightIcon"
1798
+ class="expand-icon">
1799
+ </kendo-svgicon>
1800
+ <span class="record-summary">{{ getRecordSummary() }}</span>
1801
+ <span class="type-indicator" [title]="getTypeDescription()">
1802
+ {{ getTypeIndicator() }}
1803
+ </span>
1804
+ </div>
1805
+
1806
+ <!-- Expanded Content -->
1807
+ @if (isExpanded) {
1808
+ <div class="record-content">
1809
+ @if (type === AttributeValueTypeDto.RecordArrayDto && Array.isArray(value)) {
1810
+ @for (item of value; track $index) {
1811
+ <div class="array-item">
1812
+ <span class="array-index">[{{ $index }}]</span>
1813
+ <div class="nested-properties">
1814
+ @for (prop of getObjectProperties(item); track prop.key) {
1815
+ <div class="nested-property">
1816
+ <span class="property-key">{{ prop.key }}:</span>
1817
+ <mm-property-value-display
1818
+ [value]="prop.value"
1819
+ [type]="getPropertyType(prop.value)">
1820
+ </mm-property-value-display>
1821
+ </div>
1822
+ }
1823
+ </div>
1824
+ </div>
1825
+ }
1826
+ } @else {
1827
+ <div class="nested-properties">
1828
+ @for (prop of getObjectProperties(value); track prop.key) {
1829
+ <div class="nested-property">
1830
+ <span class="property-key">{{ prop.key }}:</span>
1831
+ <mm-property-value-display
1832
+ [value]="prop.value"
1833
+ [type]="getPropertyType(prop.value)">
1834
+ </mm-property-value-display>
1835
+ </div>
1836
+ }
1837
+ </div>
1838
+ }
1839
+ </div>
1840
+ }
1841
+ </div>
1842
+ } @else if (isBinaryLinkedWithDownload()) {
1843
+ <!-- Binary Linked with download capability -->
1844
+ <div class="binary-linked-display">
1845
+ <div class="binary-info">
1846
+ @if (getBinaryFilename()) {
1847
+ <span class="filename">{{ getBinaryFilename() }}</span>
1848
+ }
1849
+ @if (getBinarySize()) {
1850
+ <span class="file-size">({{ formatFileSize(getBinarySize()) }})</span>
1851
+ }
1852
+ @if (getBinaryContentType()) {
1853
+ <span class="content-type">{{ getBinaryContentType() }}</span>
1854
+ }
1855
+ </div>
1856
+ <button
1857
+ kendoButton
1858
+ size="small"
1859
+ fillMode="flat"
1860
+ [svgIcon]="downloadIcon"
1861
+ title="Download file"
1862
+ (click)="onDownload()">
1863
+ Download
1864
+ </button>
1865
+ </div>
1866
+ } @else {
1867
+ <!-- Non-expandable value display -->
1868
+ @switch (displayMode) {
1869
+ @case (PropertyDisplayMode.Json) {
1870
+ <pre class="json-display">{{ getFormattedValue() }}</pre>
1871
+ }
1872
+ @case (PropertyDisplayMode.Text) {
1873
+ <span class="text-display" [innerHTML]="getFormattedValue()"></span>
1874
+ }
1875
+ @default {
1876
+ <span class="default-display">{{ getFormattedValue() }}</span>
1877
+ }
1878
+ }
1879
+
1880
+ @if (isComplexType() && !isExpandableRecord()) {
1881
+ <span class="type-indicator" [title]="getTypeDescription()">
1882
+ {{ getTypeIndicator() }}
1883
+ </span>
1884
+ }
1885
+ }
1886
+ </div>
1887
+ `, isInline: true, styles: [".property-value-display{display:flex;align-items:flex-start;gap:8px;min-height:20px;font-family:inherit;width:100%}.expandable-record{width:100%}.record-header{display:flex;align-items:center;gap:6px;cursor:pointer;padding:4px;border-radius:4px;transition:background-color .2s}.record-header:hover{background:var(--kendo-color-base-subtle)}.expand-icon{width:16px;height:16px;flex-shrink:0;transition:transform .2s}.record-summary{flex:1;font-size:.9em}.record-content{margin-left:22px;margin-top:8px;border-left:2px solid var(--kendo-color-border);padding-left:12px;background:var(--kendo-color-base-subtle);border-radius:0 4px 4px 0}.nested-properties{display:flex;flex-direction:column;gap:6px;padding:8px}.nested-property{display:flex;align-items:flex-start;gap:8px;min-height:24px}.property-key{font-weight:500;color:var(--kendo-color-primary);min-width:100px;flex-shrink:0;font-size:.9em}.array-item{border:1px solid var(--kendo-color-border);border-radius:4px;margin-bottom:8px;background:transparent}.array-index{display:inline-block;background:var(--kendo-color-primary);color:#fff;padding:2px 8px;font-size:.8em;font-weight:700;border-radius:4px 0;margin-bottom:4px}.json-display{font-family:Courier New,monospace;font-size:12px;margin:0;padding:4px 8px;background:var(--kendo-color-base-subtle);border-radius:4px;max-width:300px;overflow:auto}.text-display,.default-display{flex:1;word-break:break-word;overflow-wrap:anywhere}.type-boolean .default-display{font-weight:500}.type-boolean .default-display.value-true{color:#28a745}.type-boolean .default-display.value-false{color:#dc3545}:is(.type-datetime,.type-datetimeoffset) .default-display{font-family:monospace;font-size:.9em}:is(.type-int,.type-integer,.type-double) .default-display{font-family:monospace;text-align:right}.type-indicator{font-size:.75em;padding:2px 6px;background:var(--kendo-color-primary);color:#fff;border-radius:3px;text-transform:uppercase;font-weight:500;flex-shrink:0}.empty-value{color:var(--kendo-color-subtle);font-style:italic}.null-value{color:var(--kendo-color-error);font-style:italic}.binary-linked-display{display:flex;align-items:center;gap:12px;width:100%}.binary-info{display:flex;align-items:center;gap:8px;flex:1;min-width:0}.filename{font-weight:500;color:var(--kendo-color-on-app-surface);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-size{font-size:.85em;color:var(--kendo-color-subtle);flex-shrink:0}.content-type{font-size:.75em;padding:2px 6px;background:var(--kendo-color-base-subtle);border-radius:3px;color:var(--kendo-color-subtle);flex-shrink:0}\n"], dependencies: [{ kind: "component", type: PropertyValueDisplayComponent, selector: "mm-property-value-display", inputs: ["value", "type", "displayMode"], outputs: ["binaryDownload"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i5$1.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i3.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }] });
42
1888
  }
43
- class OctoListNavigationOptions {
44
- language;
45
- searchFilterType;
46
- searchFilterAttributePaths;
1889
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: PropertyValueDisplayComponent, decorators: [{
1890
+ type: Component,
1891
+ args: [{ selector: 'mm-property-value-display', standalone: true, imports: [CommonModule, SVGIconModule, ButtonModule], template: `
1892
+ <div class="property-value-display" [ngClass]="'type-' + type.toLowerCase()">
1893
+
1894
+ @if (isExpandableRecord()) {
1895
+ <div class="expandable-record">
1896
+ <!-- Expand/Collapse Button and Summary -->
1897
+ <div class="record-header" (click)="toggleExpansion()">
1898
+ <kendo-svgicon
1899
+ [icon]="isExpanded ? chevronDownIcon : chevronRightIcon"
1900
+ class="expand-icon">
1901
+ </kendo-svgicon>
1902
+ <span class="record-summary">{{ getRecordSummary() }}</span>
1903
+ <span class="type-indicator" [title]="getTypeDescription()">
1904
+ {{ getTypeIndicator() }}
1905
+ </span>
1906
+ </div>
1907
+
1908
+ <!-- Expanded Content -->
1909
+ @if (isExpanded) {
1910
+ <div class="record-content">
1911
+ @if (type === AttributeValueTypeDto.RecordArrayDto && Array.isArray(value)) {
1912
+ @for (item of value; track $index) {
1913
+ <div class="array-item">
1914
+ <span class="array-index">[{{ $index }}]</span>
1915
+ <div class="nested-properties">
1916
+ @for (prop of getObjectProperties(item); track prop.key) {
1917
+ <div class="nested-property">
1918
+ <span class="property-key">{{ prop.key }}:</span>
1919
+ <mm-property-value-display
1920
+ [value]="prop.value"
1921
+ [type]="getPropertyType(prop.value)">
1922
+ </mm-property-value-display>
1923
+ </div>
1924
+ }
1925
+ </div>
1926
+ </div>
1927
+ }
1928
+ } @else {
1929
+ <div class="nested-properties">
1930
+ @for (prop of getObjectProperties(value); track prop.key) {
1931
+ <div class="nested-property">
1932
+ <span class="property-key">{{ prop.key }}:</span>
1933
+ <mm-property-value-display
1934
+ [value]="prop.value"
1935
+ [type]="getPropertyType(prop.value)">
1936
+ </mm-property-value-display>
1937
+ </div>
1938
+ }
1939
+ </div>
1940
+ }
1941
+ </div>
1942
+ }
1943
+ </div>
1944
+ } @else if (isBinaryLinkedWithDownload()) {
1945
+ <!-- Binary Linked with download capability -->
1946
+ <div class="binary-linked-display">
1947
+ <div class="binary-info">
1948
+ @if (getBinaryFilename()) {
1949
+ <span class="filename">{{ getBinaryFilename() }}</span>
1950
+ }
1951
+ @if (getBinarySize()) {
1952
+ <span class="file-size">({{ formatFileSize(getBinarySize()) }})</span>
1953
+ }
1954
+ @if (getBinaryContentType()) {
1955
+ <span class="content-type">{{ getBinaryContentType() }}</span>
1956
+ }
1957
+ </div>
1958
+ <button
1959
+ kendoButton
1960
+ size="small"
1961
+ fillMode="flat"
1962
+ [svgIcon]="downloadIcon"
1963
+ title="Download file"
1964
+ (click)="onDownload()">
1965
+ Download
1966
+ </button>
1967
+ </div>
1968
+ } @else {
1969
+ <!-- Non-expandable value display -->
1970
+ @switch (displayMode) {
1971
+ @case (PropertyDisplayMode.Json) {
1972
+ <pre class="json-display">{{ getFormattedValue() }}</pre>
1973
+ }
1974
+ @case (PropertyDisplayMode.Text) {
1975
+ <span class="text-display" [innerHTML]="getFormattedValue()"></span>
1976
+ }
1977
+ @default {
1978
+ <span class="default-display">{{ getFormattedValue() }}</span>
1979
+ }
1980
+ }
1981
+
1982
+ @if (isComplexType() && !isExpandableRecord()) {
1983
+ <span class="type-indicator" [title]="getTypeDescription()">
1984
+ {{ getTypeIndicator() }}
1985
+ </span>
1986
+ }
1987
+ }
1988
+ </div>
1989
+ `, styles: [".property-value-display{display:flex;align-items:flex-start;gap:8px;min-height:20px;font-family:inherit;width:100%}.expandable-record{width:100%}.record-header{display:flex;align-items:center;gap:6px;cursor:pointer;padding:4px;border-radius:4px;transition:background-color .2s}.record-header:hover{background:var(--kendo-color-base-subtle)}.expand-icon{width:16px;height:16px;flex-shrink:0;transition:transform .2s}.record-summary{flex:1;font-size:.9em}.record-content{margin-left:22px;margin-top:8px;border-left:2px solid var(--kendo-color-border);padding-left:12px;background:var(--kendo-color-base-subtle);border-radius:0 4px 4px 0}.nested-properties{display:flex;flex-direction:column;gap:6px;padding:8px}.nested-property{display:flex;align-items:flex-start;gap:8px;min-height:24px}.property-key{font-weight:500;color:var(--kendo-color-primary);min-width:100px;flex-shrink:0;font-size:.9em}.array-item{border:1px solid var(--kendo-color-border);border-radius:4px;margin-bottom:8px;background:transparent}.array-index{display:inline-block;background:var(--kendo-color-primary);color:#fff;padding:2px 8px;font-size:.8em;font-weight:700;border-radius:4px 0;margin-bottom:4px}.json-display{font-family:Courier New,monospace;font-size:12px;margin:0;padding:4px 8px;background:var(--kendo-color-base-subtle);border-radius:4px;max-width:300px;overflow:auto}.text-display,.default-display{flex:1;word-break:break-word;overflow-wrap:anywhere}.type-boolean .default-display{font-weight:500}.type-boolean .default-display.value-true{color:#28a745}.type-boolean .default-display.value-false{color:#dc3545}:is(.type-datetime,.type-datetimeoffset) .default-display{font-family:monospace;font-size:.9em}:is(.type-int,.type-integer,.type-double) .default-display{font-family:monospace;text-align:right}.type-indicator{font-size:.75em;padding:2px 6px;background:var(--kendo-color-primary);color:#fff;border-radius:3px;text-transform:uppercase;font-weight:500;flex-shrink:0}.empty-value{color:var(--kendo-color-subtle);font-style:italic}.null-value{color:var(--kendo-color-error);font-style:italic}.binary-linked-display{display:flex;align-items:center;gap:12px;width:100%}.binary-info{display:flex;align-items:center;gap:8px;flex:1;min-width:0}.filename{font-weight:500;color:var(--kendo-color-on-app-surface);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-size{font-size:.85em;color:var(--kendo-color-subtle);flex-shrink:0}.content-type{font-size:.75em;padding:2px 6px;background:var(--kendo-color-base-subtle);border-radius:3px;color:var(--kendo-color-subtle);flex-shrink:0}\n"] }]
1990
+ }], propDecorators: { value: [{
1991
+ type: Input
1992
+ }], type: [{
1993
+ type: Input
1994
+ }], displayMode: [{
1995
+ type: Input
1996
+ }], binaryDownload: [{
1997
+ type: Output
1998
+ }] } });
1999
+
2000
+ /**
2001
+ * Generic property grid component for OctoMesh
2002
+ */
2003
+ class PropertyGridComponent {
2004
+ data = [];
2005
+ config = {};
2006
+ showTypeColumn = false;
2007
+ propertyChange = new EventEmitter();
2008
+ saveRequested = new EventEmitter();
2009
+ binaryDownload = new EventEmitter();
2010
+ // Component state
2011
+ filteredData = [];
2012
+ searchTerm = '';
2013
+ hasChanges = false;
2014
+ pendingChanges = [];
2015
+ // Icons
2016
+ fileIcon = fileIcon;
2017
+ folderIcon = folderIcon;
2018
+ calendarIcon = calendarIcon;
2019
+ checkboxCheckedIcon = checkboxCheckedIcon;
2020
+ listUnorderedIcon = listUnorderedIcon;
2021
+ // Enum reference
2022
+ AttributeValueTypeDto = AttributeValueTypeDto;
2023
+ ngOnInit() {
2024
+ this.updateFilteredData();
2025
+ }
2026
+ ngOnChanges(changes) {
2027
+ if (changes['data']) {
2028
+ this.updateFilteredData();
2029
+ this.hasChanges = false;
2030
+ this.pendingChanges = [];
2031
+ }
2032
+ }
2033
+ get gridHeight() {
2034
+ const baseHeight = this.config.height || '400px';
2035
+ const searchHeight = this.config.showSearch ? 40 : 0;
2036
+ const toolbarHeight = !this.config.readOnlyMode ? 50 : 0;
2037
+ const totalOffset = searchHeight + toolbarHeight;
2038
+ if (baseHeight.includes('px')) {
2039
+ const px = parseInt(baseHeight) - totalOffset;
2040
+ return `${Math.max(px, 200)}px`;
2041
+ }
2042
+ return baseHeight;
2043
+ }
2044
+ /**
2045
+ * Update filtered data based on search term
2046
+ */
2047
+ updateFilteredData() {
2048
+ if (!this.searchTerm.trim()) {
2049
+ this.filteredData = [...this.data];
2050
+ return;
2051
+ }
2052
+ const term = this.searchTerm.toLowerCase();
2053
+ this.filteredData = this.data.filter(item => item.name.toLowerCase().includes(term) ||
2054
+ (item.displayName?.toLowerCase().includes(term)) ||
2055
+ (item.description?.toLowerCase().includes(term)) ||
2056
+ String(item.value).toLowerCase().includes(term));
2057
+ }
2058
+ /**
2059
+ * Handle search input
2060
+ */
2061
+ onSearch() {
2062
+ this.updateFilteredData();
2063
+ }
2064
+ /**
2065
+ * Get appropriate icon for property type
2066
+ */
2067
+ getTypeIcon(type) {
2068
+ switch (type) {
2069
+ case AttributeValueTypeDto.BooleanDto:
2070
+ return checkboxCheckedIcon;
2071
+ case AttributeValueTypeDto.IntDto:
2072
+ case AttributeValueTypeDto.IntegerDto:
2073
+ case AttributeValueTypeDto.DoubleDto:
2074
+ return fileIcon;
2075
+ case AttributeValueTypeDto.DateTimeDto:
2076
+ case AttributeValueTypeDto.DateTimeOffsetDto:
2077
+ return calendarIcon;
2078
+ case AttributeValueTypeDto.StringArrayDto:
2079
+ case AttributeValueTypeDto.IntegerArrayDto:
2080
+ return listUnorderedIcon;
2081
+ case AttributeValueTypeDto.RecordDto:
2082
+ case AttributeValueTypeDto.RecordArrayDto:
2083
+ return folderIcon;
2084
+ default:
2085
+ return fileIcon;
2086
+ }
2087
+ }
2088
+ /**
2089
+ * Format type name for display
2090
+ */
2091
+ formatTypeName(type) {
2092
+ return type.replace('_DTO', '').replace('DTO', '').replace('_', ' ');
2093
+ }
2094
+ /**
2095
+ * Save pending changes
2096
+ */
2097
+ saveChanges() {
2098
+ if (this.pendingChanges.length > 0) {
2099
+ const updatedData = this.data.map(item => {
2100
+ const change = this.pendingChanges.find(c => c.property.id === item.id);
2101
+ return change ? { ...item, value: change.newValue } : item;
2102
+ });
2103
+ this.saveRequested.emit(updatedData);
2104
+ this.hasChanges = false;
2105
+ this.pendingChanges = [];
2106
+ }
2107
+ }
2108
+ /**
2109
+ * Discard pending changes
2110
+ */
2111
+ discardChanges() {
2112
+ this.hasChanges = false;
2113
+ this.pendingChanges = [];
2114
+ this.updateFilteredData();
2115
+ }
2116
+ /**
2117
+ * Handle property value change
2118
+ */
2119
+ onPropertyChange(property, oldValue, newValue) {
2120
+ const changeEvent = {
2121
+ property,
2122
+ oldValue,
2123
+ newValue
2124
+ };
2125
+ // Update or add to pending changes
2126
+ const existingIndex = this.pendingChanges.findIndex(c => c.property.id === property.id);
2127
+ if (existingIndex >= 0) {
2128
+ this.pendingChanges[existingIndex] = changeEvent;
2129
+ }
2130
+ else {
2131
+ this.pendingChanges.push(changeEvent);
2132
+ }
2133
+ this.hasChanges = this.pendingChanges.length > 0;
2134
+ this.propertyChange.emit(changeEvent);
2135
+ }
2136
+ /**
2137
+ * Handle binary download request from property value display
2138
+ */
2139
+ onBinaryDownload(event) {
2140
+ this.binaryDownload.emit(event);
2141
+ }
2142
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: PropertyGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2143
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: PropertyGridComponent, isStandalone: true, selector: "mm-property-grid", inputs: { data: "data", config: "config", showTypeColumn: "showTypeColumn" }, outputs: { propertyChange: "propertyChange", saveRequested: "saveRequested", binaryDownload: "binaryDownload" }, usesOnChanges: true, ngImport: i0, template: `
2144
+ <div class="mm-property-grid" [style.height]="config.height || '400px'">
2145
+
2146
+ @if (config.showSearch) {
2147
+ <div class="search-toolbar">
2148
+ <kendo-textbox
2149
+ [(ngModel)]="searchTerm"
2150
+ placeholder="Search attributes..."
2151
+ (input)="onSearch()"
2152
+ [clearButton]="true">
2153
+ </kendo-textbox>
2154
+ </div>
2155
+ }
2156
+
2157
+ <kendo-grid
2158
+ [data]="filteredData"
2159
+ [sortable]="true"
2160
+ [resizable]="true"
2161
+ [style.height]="gridHeight"
2162
+ [selectable]="{mode: 'single'}"
2163
+ class="property-grid">
2164
+
2165
+ <!-- Property Name Column -->
2166
+ <kendo-grid-column
2167
+ field="displayName"
2168
+ title="Property"
2169
+ [width]="200"
2170
+ [sortable]="true">
2171
+ <ng-template kendoGridCellTemplate let-dataItem="dataItem">
2172
+ <div class="property-name-cell">
2173
+ @if (config.showTypeIcons !== false) {
2174
+ <kendo-svgicon
2175
+ [icon]="getTypeIcon(dataItem.type)"
2176
+ class="type-icon">
2177
+ </kendo-svgicon>
2178
+ }
2179
+ <div class="property-info">
2180
+ <span class="property-name" [title]="dataItem.description">
2181
+ {{ dataItem.displayName || dataItem.name }}
2182
+ </span>
2183
+ @if (dataItem.required) {
2184
+ <span class="required-indicator">*</span>
2185
+ }
2186
+ @if (dataItem.readOnly) {
2187
+ <span class="readonly-indicator" title="Read-only">🔒</span>
2188
+ }
2189
+ </div>
2190
+ </div>
2191
+ </ng-template>
2192
+ </kendo-grid-column>
2193
+
2194
+ <!-- Property Value Column -->
2195
+ <kendo-grid-column
2196
+ field="value"
2197
+ title="Value"
2198
+ [sortable]="false">
2199
+ <ng-template kendoGridCellTemplate let-dataItem="dataItem">
2200
+ <mm-property-value-display
2201
+ [value]="dataItem.value"
2202
+ [type]="dataItem.type"
2203
+ (binaryDownload)="onBinaryDownload($event)">
2204
+ </mm-property-value-display>
2205
+ </ng-template>
2206
+ </kendo-grid-column>
2207
+
2208
+ <!-- Type Column (optional) -->
2209
+ @if (showTypeColumn) {
2210
+ <kendo-grid-column
2211
+ field="type"
2212
+ title="Type"
2213
+ [width]="120"
2214
+ [sortable]="true">
2215
+ <ng-template kendoGridCellTemplate let-dataItem="dataItem">
2216
+ <span class="type-badge" [ngClass]="'type-' + dataItem.type.toLowerCase()">
2217
+ {{ formatTypeName(dataItem.type) }}
2218
+ </span>
2219
+ </ng-template>
2220
+ </kendo-grid-column>
2221
+ }
2222
+
2223
+ </kendo-grid>
2224
+ </div>
2225
+ `, isInline: true, styles: [".mm-property-grid{display:flex;flex-direction:column;border:1px solid var(--kendo-color-border);border-radius:4px;overflow:hidden}.search-toolbar{padding:8px;border-bottom:1px solid var(--kendo-color-border);background:var(--kendo-color-base-subtle)}.property-grid{flex:1;border:none}.property-name-cell{display:flex;align-items:center;gap:8px;min-height:24px}.type-icon{width:16px;height:16px;opacity:.7}.property-info{display:flex;align-items:center;gap:4px;flex:1}.property-name{font-weight:500}.required-indicator{color:var(--kendo-color-error);font-weight:700}.readonly-indicator{font-size:12px;opacity:.7}.property-editor{width:100%;min-width:200px}.validation-error{color:var(--kendo-color-error);font-size:.8em;margin-top:4px}.type-badge{font-size:.75em;padding:2px 6px;border-radius:3px;text-transform:uppercase;font-weight:500;background:var(--kendo-color-base-subtle);color:var(--kendo-color-on-base)}.grid-toolbar{padding:8px;border-top:1px solid var(--kendo-color-border);background:var(--kendo-color-base-subtle);display:flex;align-items:center;gap:8px}.changes-indicator{margin-left:auto;font-size:.9em;color:var(--kendo-color-primary);font-style:italic}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: GridModule }, { kind: "component", type: i2$1.GridComponent, selector: "kendo-grid", inputs: ["data", "pageSize", "height", "rowHeight", "adaptiveMode", "detailRowHeight", "skip", "scrollable", "selectable", "sort", "size", "trackBy", "filter", "group", "virtualColumns", "filterable", "sortable", "pageable", "groupable", "gridResizable", "rowReorderable", "navigable", "autoSize", "rowClass", "rowSticky", "rowSelected", "isRowSelectable", "cellSelected", "resizable", "reorderable", "loading", "columnMenu", "hideHeader", "showInactiveTools", "isDetailExpanded", "isGroupExpanded", "dataLayoutMode"], outputs: ["filterChange", "pageChange", "groupChange", "sortChange", "selectionChange", "rowReorder", "dataStateChange", "gridStateChange", "groupExpand", "groupCollapse", "detailExpand", "detailCollapse", "edit", "cancel", "save", "remove", "add", "cellClose", "cellClick", "pdfExport", "excelExport", "columnResize", "columnReorder", "columnVisibilityChange", "columnLockedChange", "columnStickyChange", "scrollBottom", "contentScroll"], exportAs: ["kendoGrid"] }, { kind: "component", type: i2$1.ColumnComponent, selector: "kendo-grid-column", inputs: ["field", "format", "sortable", "groupable", "editor", "filter", "filterVariant", "filterable", "editable"] }, { kind: "directive", type: i2$1.CellTemplateDirective, selector: "[kendoGridCellTemplate]" }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i4.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "ngmodule", type: ButtonsModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i5$1.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "component", type: PropertyValueDisplayComponent, selector: "mm-property-value-display", inputs: ["value", "type", "displayMode"], outputs: ["binaryDownload"] }] });
2226
+ }
2227
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: PropertyGridComponent, decorators: [{
2228
+ type: Component,
2229
+ args: [{ selector: 'mm-property-grid', standalone: true, imports: [
2230
+ CommonModule,
2231
+ FormsModule,
2232
+ GridModule,
2233
+ InputsModule,
2234
+ DropDownsModule,
2235
+ ButtonsModule,
2236
+ SVGIconModule,
2237
+ PropertyValueDisplayComponent
2238
+ ], template: `
2239
+ <div class="mm-property-grid" [style.height]="config.height || '400px'">
2240
+
2241
+ @if (config.showSearch) {
2242
+ <div class="search-toolbar">
2243
+ <kendo-textbox
2244
+ [(ngModel)]="searchTerm"
2245
+ placeholder="Search attributes..."
2246
+ (input)="onSearch()"
2247
+ [clearButton]="true">
2248
+ </kendo-textbox>
2249
+ </div>
2250
+ }
2251
+
2252
+ <kendo-grid
2253
+ [data]="filteredData"
2254
+ [sortable]="true"
2255
+ [resizable]="true"
2256
+ [style.height]="gridHeight"
2257
+ [selectable]="{mode: 'single'}"
2258
+ class="property-grid">
2259
+
2260
+ <!-- Property Name Column -->
2261
+ <kendo-grid-column
2262
+ field="displayName"
2263
+ title="Property"
2264
+ [width]="200"
2265
+ [sortable]="true">
2266
+ <ng-template kendoGridCellTemplate let-dataItem="dataItem">
2267
+ <div class="property-name-cell">
2268
+ @if (config.showTypeIcons !== false) {
2269
+ <kendo-svgicon
2270
+ [icon]="getTypeIcon(dataItem.type)"
2271
+ class="type-icon">
2272
+ </kendo-svgicon>
2273
+ }
2274
+ <div class="property-info">
2275
+ <span class="property-name" [title]="dataItem.description">
2276
+ {{ dataItem.displayName || dataItem.name }}
2277
+ </span>
2278
+ @if (dataItem.required) {
2279
+ <span class="required-indicator">*</span>
2280
+ }
2281
+ @if (dataItem.readOnly) {
2282
+ <span class="readonly-indicator" title="Read-only">🔒</span>
2283
+ }
2284
+ </div>
2285
+ </div>
2286
+ </ng-template>
2287
+ </kendo-grid-column>
2288
+
2289
+ <!-- Property Value Column -->
2290
+ <kendo-grid-column
2291
+ field="value"
2292
+ title="Value"
2293
+ [sortable]="false">
2294
+ <ng-template kendoGridCellTemplate let-dataItem="dataItem">
2295
+ <mm-property-value-display
2296
+ [value]="dataItem.value"
2297
+ [type]="dataItem.type"
2298
+ (binaryDownload)="onBinaryDownload($event)">
2299
+ </mm-property-value-display>
2300
+ </ng-template>
2301
+ </kendo-grid-column>
2302
+
2303
+ <!-- Type Column (optional) -->
2304
+ @if (showTypeColumn) {
2305
+ <kendo-grid-column
2306
+ field="type"
2307
+ title="Type"
2308
+ [width]="120"
2309
+ [sortable]="true">
2310
+ <ng-template kendoGridCellTemplate let-dataItem="dataItem">
2311
+ <span class="type-badge" [ngClass]="'type-' + dataItem.type.toLowerCase()">
2312
+ {{ formatTypeName(dataItem.type) }}
2313
+ </span>
2314
+ </ng-template>
2315
+ </kendo-grid-column>
2316
+ }
2317
+
2318
+ </kendo-grid>
2319
+ </div>
2320
+ `, styles: [".mm-property-grid{display:flex;flex-direction:column;border:1px solid var(--kendo-color-border);border-radius:4px;overflow:hidden}.search-toolbar{padding:8px;border-bottom:1px solid var(--kendo-color-border);background:var(--kendo-color-base-subtle)}.property-grid{flex:1;border:none}.property-name-cell{display:flex;align-items:center;gap:8px;min-height:24px}.type-icon{width:16px;height:16px;opacity:.7}.property-info{display:flex;align-items:center;gap:4px;flex:1}.property-name{font-weight:500}.required-indicator{color:var(--kendo-color-error);font-weight:700}.readonly-indicator{font-size:12px;opacity:.7}.property-editor{width:100%;min-width:200px}.validation-error{color:var(--kendo-color-error);font-size:.8em;margin-top:4px}.type-badge{font-size:.75em;padding:2px 6px;border-radius:3px;text-transform:uppercase;font-weight:500;background:var(--kendo-color-base-subtle);color:var(--kendo-color-on-base)}.grid-toolbar{padding:8px;border-top:1px solid var(--kendo-color-border);background:var(--kendo-color-base-subtle);display:flex;align-items:center;gap:8px}.changes-indicator{margin-left:auto;font-size:.9em;color:var(--kendo-color-primary);font-style:italic}\n"] }]
2321
+ }], propDecorators: { data: [{
2322
+ type: Input
2323
+ }], config: [{
2324
+ type: Input
2325
+ }], showTypeColumn: [{
2326
+ type: Input
2327
+ }], propertyChange: [{
2328
+ type: Output
2329
+ }], saveRequested: [{
2330
+ type: Output
2331
+ }], binaryDownload: [{
2332
+ type: Output
2333
+ }] } });
2334
+
2335
+ // Models
2336
+
2337
+ class AttributeSelectorDialogComponent extends DialogContentBase {
2338
+ attributeService = inject(AttributeSelectorService);
2339
+ searchSubject = new Subject();
47
2340
  constructor() {
48
- this.language = null;
2341
+ super(inject(DialogRef));
49
2342
  }
50
- }
51
- class OctoListNavigation {
52
- paginator;
53
- sort;
54
- searchBox;
55
- octoOptions;
56
- loadDataRequest = new EventEmitter();
57
- lastSortDirection;
58
- lastSortField;
59
- lastSearchText;
60
- constructor(paginator, sort, searchBox, octoOptions) {
61
- this.paginator = paginator;
62
- this.sort = sort;
63
- this.searchBox = searchBox;
64
- this.octoOptions = octoOptions;
65
- this.lastSortDirection = null;
66
- this.lastSortField = null;
67
- this.lastSearchText = null;
68
- }
69
- get loadDataInfo() {
70
- const filterString = this.searchBox?.nativeElement.value;
71
- const sortField = this.sort.active;
72
- const sortDirection = this.sort.direction;
73
- let filter = null;
74
- if (filterString && this.octoOptions) {
75
- filter = {
76
- language: this.octoOptions.language,
77
- searchTerm: filterString,
78
- type: this.octoOptions.searchFilterType,
79
- attributePaths: this.octoOptions.searchFilterAttributePaths
80
- };
81
- }
82
- const sort = [];
83
- if (sortField && sortDirection) {
84
- sort.push({
85
- attributePath: sortField,
86
- sortOrder: sortDirection === 'asc' ? SortOrdersDto.AscendingDto : SortOrdersDto.DescendingDto
87
- });
2343
+ arrowRightIcon = arrowRightIcon;
2344
+ arrowLeftIcon = arrowLeftIcon;
2345
+ chevronDoubleRightIcon = chevronDoubleRightIcon;
2346
+ chevronDoubleLeftIcon = chevronDoubleLeftIcon;
2347
+ searchIcon = searchIcon;
2348
+ arrowUpIcon = arrowUpIcon;
2349
+ arrowDownIcon = arrowDownIcon;
2350
+ dialogTitle = 'Select Attributes';
2351
+ rtCkTypeId;
2352
+ searchText = '';
2353
+ availableAttributes = [];
2354
+ selectedAttributes = [];
2355
+ availableGridData = { data: [], total: 0 };
2356
+ selectedGridData = { data: [], total: 0 };
2357
+ selectedAvailableKeys = [];
2358
+ selectedChosenKeys = [];
2359
+ // Double-click tracking
2360
+ lastClickTime = 0;
2361
+ lastClickedItem = null;
2362
+ doubleClickDelay = 300; // milliseconds
2363
+ ngOnInit() {
2364
+ const data = this.dialog.content?.instance?.data;
2365
+ if (data) {
2366
+ this.rtCkTypeId = data.rtCkTypeId;
2367
+ this.dialogTitle = data.dialogTitle || 'Select Attributes';
2368
+ if (data.selectedAttributes && data.selectedAttributes.length > 0) {
2369
+ // Pre-populate selected attributes if provided
2370
+ this.loadInitialSelectedAttributes(data.selectedAttributes);
2371
+ }
88
2372
  }
89
- return {
90
- skip: this.paginator.pageIndex * this.paginator.pageSize,
91
- take: this.paginator.pageSize,
92
- searchFilter: filter,
93
- sort
2373
+ // Set up search debouncing
2374
+ this.searchSubject.pipe(debounceTime(300), distinctUntilChanged()).subscribe(searchText => {
2375
+ this.loadAvailableAttributes(searchText);
2376
+ });
2377
+ // Load initial attributes
2378
+ this.loadAvailableAttributes();
2379
+ }
2380
+ loadAvailableAttributes(filter) {
2381
+ this.attributeService.getAvailableAttributes(this.rtCkTypeId, filter).subscribe(result => {
2382
+ // Filter out already selected attributes
2383
+ const selectedPaths = new Set(this.selectedAttributes.map(a => a.attributePath));
2384
+ this.availableAttributes = result.items.filter(item => !selectedPaths.has(item.attributePath));
2385
+ this.updateAvailableGrid();
2386
+ });
2387
+ }
2388
+ loadInitialSelectedAttributes(attributePaths) {
2389
+ // Load all attributes to get the details for selected ones
2390
+ this.attributeService.getAvailableAttributes(this.rtCkTypeId).subscribe(result => {
2391
+ // Create a map for quick lookup
2392
+ const attributeMap = new Map(result.items.map(item => [item.attributePath, item]));
2393
+ // Preserve the order from attributePaths
2394
+ this.selectedAttributes = attributePaths
2395
+ .map(path => attributeMap.get(path))
2396
+ .filter((item) => item !== undefined);
2397
+ this.updateSelectedGrid();
2398
+ // Filter out selected from available
2399
+ const selectedPaths = new Set(this.selectedAttributes.map(a => a.attributePath));
2400
+ this.availableAttributes = result.items.filter(item => !selectedPaths.has(item.attributePath));
2401
+ this.updateAvailableGrid();
2402
+ });
2403
+ }
2404
+ onSearchChange(value) {
2405
+ this.searchSubject.next(value);
2406
+ }
2407
+ addSelected() {
2408
+ const itemsToAdd = this.availableAttributes.filter(item => this.selectedAvailableKeys.includes(item.attributePath));
2409
+ this.selectedAttributes.push(...itemsToAdd);
2410
+ this.availableAttributes = this.availableAttributes.filter(item => !this.selectedAvailableKeys.includes(item.attributePath));
2411
+ this.selectedAvailableKeys = [];
2412
+ this.updateGrids();
2413
+ }
2414
+ removeSelected() {
2415
+ const itemsToRemove = this.selectedAttributes.filter(item => this.selectedChosenKeys.includes(item.attributePath));
2416
+ this.availableAttributes.push(...itemsToRemove);
2417
+ this.selectedAttributes = this.selectedAttributes.filter(item => !this.selectedChosenKeys.includes(item.attributePath));
2418
+ this.selectedChosenKeys = [];
2419
+ this.sortAvailableAttributes();
2420
+ this.updateGrids();
2421
+ }
2422
+ addAll() {
2423
+ this.selectedAttributes.push(...this.availableAttributes);
2424
+ this.availableAttributes = [];
2425
+ this.selectedAvailableKeys = [];
2426
+ this.updateGrids();
2427
+ }
2428
+ removeAll() {
2429
+ this.availableAttributes.push(...this.selectedAttributes);
2430
+ this.selectedAttributes = [];
2431
+ this.selectedChosenKeys = [];
2432
+ this.sortAvailableAttributes();
2433
+ this.updateGrids();
2434
+ }
2435
+ sortAvailableAttributes() {
2436
+ this.availableAttributes.sort((a, b) => a.attributePath.localeCompare(b.attributePath));
2437
+ }
2438
+ canMoveUp() {
2439
+ if (this.selectedChosenKeys.length !== 1)
2440
+ return false;
2441
+ const index = this.selectedAttributes.findIndex(a => a.attributePath === this.selectedChosenKeys[0]);
2442
+ return index > 0;
2443
+ }
2444
+ canMoveDown() {
2445
+ if (this.selectedChosenKeys.length !== 1)
2446
+ return false;
2447
+ const index = this.selectedAttributes.findIndex(a => a.attributePath === this.selectedChosenKeys[0]);
2448
+ return index >= 0 && index < this.selectedAttributes.length - 1;
2449
+ }
2450
+ moveUp() {
2451
+ if (!this.canMoveUp())
2452
+ return;
2453
+ const index = this.selectedAttributes.findIndex(a => a.attributePath === this.selectedChosenKeys[0]);
2454
+ [this.selectedAttributes[index - 1], this.selectedAttributes[index]] =
2455
+ [this.selectedAttributes[index], this.selectedAttributes[index - 1]];
2456
+ this.updateSelectedGrid();
2457
+ }
2458
+ moveDown() {
2459
+ if (!this.canMoveDown())
2460
+ return;
2461
+ const index = this.selectedAttributes.findIndex(a => a.attributePath === this.selectedChosenKeys[0]);
2462
+ [this.selectedAttributes[index], this.selectedAttributes[index + 1]] =
2463
+ [this.selectedAttributes[index + 1], this.selectedAttributes[index]];
2464
+ this.updateSelectedGrid();
2465
+ }
2466
+ updateGrids() {
2467
+ this.updateAvailableGrid();
2468
+ this.updateSelectedGrid();
2469
+ }
2470
+ updateAvailableGrid() {
2471
+ this.availableGridData = {
2472
+ data: this.availableAttributes,
2473
+ total: this.availableAttributes.length
94
2474
  };
95
2475
  }
96
- init() {
97
- if (this.searchBox) {
98
- // server-side search
99
- fromEvent(this.searchBox.nativeElement, 'keyup')
100
- .pipe(debounceTime(500), distinctUntilChanged(), tap(() => {
101
- this.paginator.pageIndex = 0;
102
- if (!this.searchBox) {
103
- return;
104
- }
105
- const searchText = this.searchBox.nativeElement.value;
106
- if (!this.lastSearchText && searchText) {
107
- this.lastSortDirection = this.sort.direction;
108
- this.lastSortField = this.sort.active;
109
- // Reset sorting to see the score rating (default sorting returned from server)
110
- this.sort.sort({ id: '', start: 'asc', disableClear: false });
111
- }
112
- this.lastSearchText = searchText;
113
- if (!searchText && this.lastSortField) {
114
- if (this.lastSortDirection === 'asc') {
115
- this.sort.sort({
116
- id: this.lastSortField,
117
- start: 'asc',
118
- disableClear: true
119
- });
120
- }
121
- else if (this.lastSortDirection === 'desc') {
122
- this.sort.sort({
123
- id: this.lastSortField,
124
- start: 'desc',
125
- disableClear: true
126
- });
127
- }
128
- }
129
- this.loadData();
130
- }))
131
- .subscribe();
2476
+ updateSelectedGrid() {
2477
+ this.selectedGridData = {
2478
+ data: this.selectedAttributes,
2479
+ total: this.selectedAttributes.length
2480
+ };
2481
+ }
2482
+ onCancel() {
2483
+ this.dialog.close();
2484
+ }
2485
+ onConfirm() {
2486
+ const result = {
2487
+ selectedAttributes: this.selectedAttributes
2488
+ };
2489
+ this.dialog.close(result);
2490
+ }
2491
+ /**
2492
+ * Handle cell click on available attributes grid to detect double-click
2493
+ */
2494
+ onAvailableCellClick(event) {
2495
+ const dataItem = event.dataItem;
2496
+ if (!dataItem)
2497
+ return;
2498
+ const currentTime = Date.now();
2499
+ const attributePath = dataItem.attributePath;
2500
+ if (this.lastClickedItem === attributePath &&
2501
+ currentTime - this.lastClickTime <= this.doubleClickDelay) {
2502
+ // Double-click detected - move attribute to selected
2503
+ this.moveAttributeToSelected(dataItem);
2504
+ // Reset to prevent triple-click
2505
+ this.lastClickTime = 0;
2506
+ this.lastClickedItem = null;
2507
+ }
2508
+ else {
2509
+ // Single click - just update tracking
2510
+ this.lastClickTime = currentTime;
2511
+ this.lastClickedItem = attributePath;
132
2512
  }
133
- // reset the paginator after sorting
134
- this.sort.sortChange.subscribe(() => (this.paginator.pageIndex = 0));
135
- merge(this.sort.sortChange, this.paginator.page)
136
- .pipe(tap(() => {
137
- this.loadData();
138
- }))
139
- .subscribe();
140
2513
  }
141
- loadData() {
142
- this.loadDataRequest.emit(this.loadDataInfo);
2514
+ /**
2515
+ * Handle cell click on selected attributes grid to detect double-click
2516
+ */
2517
+ onSelectedCellClick(event) {
2518
+ const dataItem = event.dataItem;
2519
+ if (!dataItem)
2520
+ return;
2521
+ const currentTime = Date.now();
2522
+ const attributePath = dataItem.attributePath;
2523
+ if (this.lastClickedItem === attributePath &&
2524
+ currentTime - this.lastClickTime <= this.doubleClickDelay) {
2525
+ // Double-click detected - move attribute to available
2526
+ this.moveAttributeToAvailable(dataItem);
2527
+ // Reset to prevent triple-click
2528
+ this.lastClickTime = 0;
2529
+ this.lastClickedItem = null;
2530
+ }
2531
+ else {
2532
+ // Single click - just update tracking
2533
+ this.lastClickTime = currentTime;
2534
+ this.lastClickedItem = attributePath;
2535
+ }
143
2536
  }
144
- }
2537
+ /**
2538
+ * Move a single attribute from available to selected
2539
+ */
2540
+ moveAttributeToSelected(attribute) {
2541
+ // Remove from available
2542
+ this.availableAttributes = this.availableAttributes.filter(item => item.attributePath !== attribute.attributePath);
2543
+ // Add to selected
2544
+ this.selectedAttributes.push(attribute);
2545
+ // Clear selections
2546
+ this.selectedAvailableKeys = this.selectedAvailableKeys.filter(key => key !== attribute.attributePath);
2547
+ this.updateGrids();
2548
+ }
2549
+ /**
2550
+ * Move a single attribute from selected to available
2551
+ */
2552
+ moveAttributeToAvailable(attribute) {
2553
+ // Remove from selected
2554
+ this.selectedAttributes = this.selectedAttributes.filter(item => item.attributePath !== attribute.attributePath);
2555
+ // Add to available
2556
+ this.availableAttributes.push(attribute);
2557
+ // Clear selections
2558
+ this.selectedChosenKeys = this.selectedChosenKeys.filter(key => key !== attribute.attributePath);
2559
+ // Sort available attributes
2560
+ this.sortAvailableAttributes();
2561
+ this.updateGrids();
2562
+ }
2563
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AttributeSelectorDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2564
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.5", type: AttributeSelectorDialogComponent, isStandalone: true, selector: "mm-attribute-selector-dialog", usesInheritance: true, ngImport: i0, template: `
2565
+ <div class="attribute-selector-container">
2566
+ <div class="search-container">
2567
+ <kendo-textbox
2568
+ [(ngModel)]="searchText"
2569
+ (ngModelChange)="onSearchChange($event)"
2570
+ placeholder="Search attributes..."
2571
+ class="search-input">
2572
+ <ng-template kendoTextBoxSuffixTemplate>
2573
+ <button kendoButton [svgIcon]="searchIcon" fillMode="clear"></button>
2574
+ </ng-template>
2575
+ </kendo-textbox>
2576
+ </div>
145
2577
 
146
- function getDisplayName(column) {
147
- return column.displayName ?? column.dataKey;
148
- }
149
- function getDataKey(column) {
150
- return column.dataKey;
2578
+ <div class="lists-container">
2579
+ <div class="list-section">
2580
+ <h4>Available Attributes</h4>
2581
+ <kendo-grid
2582
+ [data]="availableGridData"
2583
+ [height]="350"
2584
+ [scrollable]="'scrollable'"
2585
+ [selectable]="{ mode: 'multiple', enabled: true }"
2586
+ [kendoGridSelectBy]="'attributePath'"
2587
+ [(selectedKeys)]="selectedAvailableKeys"
2588
+ (cellClick)="onAvailableCellClick($event)"
2589
+ class="attribute-grid">
2590
+ <kendo-grid-column field="attributePath" title="Attribute Path" [width]="200"></kendo-grid-column>
2591
+ <kendo-grid-column field="attributeValueType" title="Type" [width]="100"></kendo-grid-column>
2592
+ </kendo-grid>
2593
+ </div>
2594
+
2595
+ <div class="action-buttons">
2596
+ <button
2597
+ kendoButton
2598
+ [svgIcon]="arrowRightIcon"
2599
+ [disabled]="selectedAvailableKeys.length === 0"
2600
+ (click)="addSelected()"
2601
+ title="Add selected">
2602
+ </button>
2603
+ <button
2604
+ kendoButton
2605
+ [svgIcon]="arrowLeftIcon"
2606
+ [disabled]="selectedChosenKeys.length === 0"
2607
+ (click)="removeSelected()"
2608
+ title="Remove selected">
2609
+ </button>
2610
+ <div class="separator"></div>
2611
+ <button
2612
+ kendoButton
2613
+ [svgIcon]="chevronDoubleRightIcon"
2614
+ [disabled]="availableAttributes.length === 0"
2615
+ (click)="addAll()"
2616
+ title="Add all">
2617
+ </button>
2618
+ <button
2619
+ kendoButton
2620
+ [svgIcon]="chevronDoubleLeftIcon"
2621
+ [disabled]="selectedAttributes.length === 0"
2622
+ (click)="removeAll()"
2623
+ title="Remove all">
2624
+ </button>
2625
+ </div>
2626
+
2627
+ <div class="list-section">
2628
+ <h4>Selected Attributes ({{ selectedAttributes.length }})</h4>
2629
+ <kendo-grid
2630
+ [data]="selectedGridData"
2631
+ [height]="350"
2632
+ [scrollable]="'scrollable'"
2633
+ [selectable]="{ mode: 'single', enabled: true }"
2634
+ [kendoGridSelectBy]="'attributePath'"
2635
+ [(selectedKeys)]="selectedChosenKeys"
2636
+ (cellClick)="onSelectedCellClick($event)"
2637
+ class="attribute-grid">
2638
+ <kendo-grid-column field="attributePath" title="Attribute Path" [width]="200">
2639
+ <ng-template kendoGridCellTemplate let-dataItem let-rowIndex="rowIndex">
2640
+ <span class="order-number">{{ rowIndex + 1 }}.</span>
2641
+ {{ dataItem.attributePath }}
2642
+ </ng-template>
2643
+ </kendo-grid-column>
2644
+ <kendo-grid-column field="attributeValueType" title="Type" [width]="100"></kendo-grid-column>
2645
+ </kendo-grid>
2646
+ </div>
2647
+
2648
+ <div class="order-buttons">
2649
+ <button
2650
+ kendoButton
2651
+ [svgIcon]="arrowUpIcon"
2652
+ [disabled]="!canMoveUp()"
2653
+ (click)="moveUp()"
2654
+ title="Move up">
2655
+ </button>
2656
+ <button
2657
+ kendoButton
2658
+ [svgIcon]="arrowDownIcon"
2659
+ [disabled]="!canMoveDown()"
2660
+ (click)="moveDown()"
2661
+ title="Move down">
2662
+ </button>
2663
+ </div>
2664
+ </div>
2665
+ </div>
2666
+
2667
+ <kendo-dialog-actions>
2668
+ <button kendoButton (click)="onCancel()">Cancel</button>
2669
+ <button kendoButton themeColor="primary" (click)="onConfirm()">OK</button>
2670
+ </kendo-dialog-actions>
2671
+ `, isInline: true, styles: [":host{display:block}.attribute-selector-container{display:flex;flex-direction:column;padding:16px 20px;min-width:800px;box-sizing:border-box;gap:16px}.search-container{flex-shrink:0}.search-input{width:100%}.lists-container{display:flex;gap:16px;align-items:flex-start}.list-section{flex:1;display:flex;flex-direction:column}.list-section h4{margin:0 0 10px;font-size:.85rem;font-weight:600}.attribute-grid{border-radius:4px}.attribute-grid ::ng-deep .k-grid-table tbody tr{cursor:pointer}.action-buttons{display:flex;flex-direction:column;gap:8px;justify-content:center;padding:32px 8px 0}.separator{height:1px;background-color:var(--kendo-color-border, #dee2e6);margin:8px 0}.order-buttons{display:flex;flex-direction:column;gap:8px;justify-content:center;padding:32px 8px 0}.order-number{color:var(--kendo-color-primary, #ff6358);font-weight:600;margin-right:8px;min-width:24px;display:inline-block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: GridModule }, { kind: "component", type: i2$1.GridComponent, selector: "kendo-grid", inputs: ["data", "pageSize", "height", "rowHeight", "adaptiveMode", "detailRowHeight", "skip", "scrollable", "selectable", "sort", "size", "trackBy", "filter", "group", "virtualColumns", "filterable", "sortable", "pageable", "groupable", "gridResizable", "rowReorderable", "navigable", "autoSize", "rowClass", "rowSticky", "rowSelected", "isRowSelectable", "cellSelected", "resizable", "reorderable", "loading", "columnMenu", "hideHeader", "showInactiveTools", "isDetailExpanded", "isGroupExpanded", "dataLayoutMode"], outputs: ["filterChange", "pageChange", "groupChange", "sortChange", "selectionChange", "rowReorder", "dataStateChange", "gridStateChange", "groupExpand", "groupCollapse", "detailExpand", "detailCollapse", "edit", "cancel", "save", "remove", "add", "cellClose", "cellClick", "pdfExport", "excelExport", "columnResize", "columnReorder", "columnVisibilityChange", "columnLockedChange", "columnStickyChange", "scrollBottom", "contentScroll"], exportAs: ["kendoGrid"] }, { kind: "directive", type: i2$1.SelectionDirective, selector: "[kendoGridSelectBy]" }, { kind: "component", type: i2$1.ColumnComponent, selector: "kendo-grid-column", inputs: ["field", "format", "sortable", "groupable", "editor", "filter", "filterVariant", "filterable", "editable"] }, { kind: "directive", type: i2$1.CellTemplateDirective, selector: "[kendoGridCellTemplate]" }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i3.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i4.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "directive", type: i4.TextBoxSuffixTemplateDirective, selector: "[kendoTextBoxSuffixTemplate]", inputs: ["showSeparator"] }, { kind: "ngmodule", type: IconsModule }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i5.DialogActionsComponent, selector: "kendo-dialog-actions", inputs: ["actions", "layout"], outputs: ["action"] }] });
151
2672
  }
2673
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AttributeSelectorDialogComponent, decorators: [{
2674
+ type: Component,
2675
+ args: [{ selector: 'mm-attribute-selector-dialog', standalone: true, imports: [
2676
+ CommonModule,
2677
+ FormsModule,
2678
+ GridModule,
2679
+ ButtonsModule,
2680
+ InputsModule,
2681
+ IconsModule,
2682
+ DialogModule
2683
+ ], template: `
2684
+ <div class="attribute-selector-container">
2685
+ <div class="search-container">
2686
+ <kendo-textbox
2687
+ [(ngModel)]="searchText"
2688
+ (ngModelChange)="onSearchChange($event)"
2689
+ placeholder="Search attributes..."
2690
+ class="search-input">
2691
+ <ng-template kendoTextBoxSuffixTemplate>
2692
+ <button kendoButton [svgIcon]="searchIcon" fillMode="clear"></button>
2693
+ </ng-template>
2694
+ </kendo-textbox>
2695
+ </div>
152
2696
 
153
- class MmOctoTableComponent {
154
- dataSource;
155
- cellTemplates;
156
- templateMap = new Map();
157
- actionColumns = [];
158
- leftToolbarActions = [];
159
- optionActions = [];
160
- searchFilterColumns = [];
161
- currentId = '';
162
- defaultSortColumn = '';
163
- rowIsClickable = true;
164
- pageSizeOptions = [5, 10, 20, 50];
165
- selectedPageSize = 5;
166
- rowClicked = new EventEmitter();
167
- searchFilterStringUpdated = new EventEmitter();
168
- selectedRowId = '';
169
- actionColumnClick = new EventEmitter();
170
- set columns(cols) {
171
- if (cols === null || cols === undefined || cols.length === 0) {
172
- this._columns = [];
173
- return;
2697
+ <div class="lists-container">
2698
+ <div class="list-section">
2699
+ <h4>Available Attributes</h4>
2700
+ <kendo-grid
2701
+ [data]="availableGridData"
2702
+ [height]="350"
2703
+ [scrollable]="'scrollable'"
2704
+ [selectable]="{ mode: 'multiple', enabled: true }"
2705
+ [kendoGridSelectBy]="'attributePath'"
2706
+ [(selectedKeys)]="selectedAvailableKeys"
2707
+ (cellClick)="onAvailableCellClick($event)"
2708
+ class="attribute-grid">
2709
+ <kendo-grid-column field="attributePath" title="Attribute Path" [width]="200"></kendo-grid-column>
2710
+ <kendo-grid-column field="attributeValueType" title="Type" [width]="100"></kendo-grid-column>
2711
+ </kendo-grid>
2712
+ </div>
2713
+
2714
+ <div class="action-buttons">
2715
+ <button
2716
+ kendoButton
2717
+ [svgIcon]="arrowRightIcon"
2718
+ [disabled]="selectedAvailableKeys.length === 0"
2719
+ (click)="addSelected()"
2720
+ title="Add selected">
2721
+ </button>
2722
+ <button
2723
+ kendoButton
2724
+ [svgIcon]="arrowLeftIcon"
2725
+ [disabled]="selectedChosenKeys.length === 0"
2726
+ (click)="removeSelected()"
2727
+ title="Remove selected">
2728
+ </button>
2729
+ <div class="separator"></div>
2730
+ <button
2731
+ kendoButton
2732
+ [svgIcon]="chevronDoubleRightIcon"
2733
+ [disabled]="availableAttributes.length === 0"
2734
+ (click)="addAll()"
2735
+ title="Add all">
2736
+ </button>
2737
+ <button
2738
+ kendoButton
2739
+ [svgIcon]="chevronDoubleLeftIcon"
2740
+ [disabled]="selectedAttributes.length === 0"
2741
+ (click)="removeAll()"
2742
+ title="Remove all">
2743
+ </button>
2744
+ </div>
2745
+
2746
+ <div class="list-section">
2747
+ <h4>Selected Attributes ({{ selectedAttributes.length }})</h4>
2748
+ <kendo-grid
2749
+ [data]="selectedGridData"
2750
+ [height]="350"
2751
+ [scrollable]="'scrollable'"
2752
+ [selectable]="{ mode: 'single', enabled: true }"
2753
+ [kendoGridSelectBy]="'attributePath'"
2754
+ [(selectedKeys)]="selectedChosenKeys"
2755
+ (cellClick)="onSelectedCellClick($event)"
2756
+ class="attribute-grid">
2757
+ <kendo-grid-column field="attributePath" title="Attribute Path" [width]="200">
2758
+ <ng-template kendoGridCellTemplate let-dataItem let-rowIndex="rowIndex">
2759
+ <span class="order-number">{{ rowIndex + 1 }}.</span>
2760
+ {{ dataItem.attributePath }}
2761
+ </ng-template>
2762
+ </kendo-grid-column>
2763
+ <kendo-grid-column field="attributeValueType" title="Type" [width]="100"></kendo-grid-column>
2764
+ </kendo-grid>
2765
+ </div>
2766
+
2767
+ <div class="order-buttons">
2768
+ <button
2769
+ kendoButton
2770
+ [svgIcon]="arrowUpIcon"
2771
+ [disabled]="!canMoveUp()"
2772
+ (click)="moveUp()"
2773
+ title="Move up">
2774
+ </button>
2775
+ <button
2776
+ kendoButton
2777
+ [svgIcon]="arrowDownIcon"
2778
+ [disabled]="!canMoveDown()"
2779
+ (click)="moveDown()"
2780
+ title="Move down">
2781
+ </button>
2782
+ </div>
2783
+ </div>
2784
+ </div>
2785
+
2786
+ <kendo-dialog-actions>
2787
+ <button kendoButton (click)="onCancel()">Cancel</button>
2788
+ <button kendoButton themeColor="primary" (click)="onConfirm()">OK</button>
2789
+ </kendo-dialog-actions>
2790
+ `, styles: [":host{display:block}.attribute-selector-container{display:flex;flex-direction:column;padding:16px 20px;min-width:800px;box-sizing:border-box;gap:16px}.search-container{flex-shrink:0}.search-input{width:100%}.lists-container{display:flex;gap:16px;align-items:flex-start}.list-section{flex:1;display:flex;flex-direction:column}.list-section h4{margin:0 0 10px;font-size:.85rem;font-weight:600}.attribute-grid{border-radius:4px}.attribute-grid ::ng-deep .k-grid-table tbody tr{cursor:pointer}.action-buttons{display:flex;flex-direction:column;gap:8px;justify-content:center;padding:32px 8px 0}.separator{height:1px;background-color:var(--kendo-color-border, #dee2e6);margin:8px 0}.order-buttons{display:flex;flex-direction:column;gap:8px;justify-content:center;padding:32px 8px 0}.order-number{color:var(--kendo-color-primary, #ff6358);font-weight:600;margin-right:8px;min-width:24px;display:inline-block}\n"] }]
2791
+ }], ctorParameters: () => [] });
2792
+
2793
+ class AttributeSelectorDialogService {
2794
+ dialogService = inject(DialogService);
2795
+ /**
2796
+ * Opens the attribute selector dialog
2797
+ * @param rtCkTypeId The RtCkType ID to fetch attributes for
2798
+ * @param selectedAttributes Optional array of pre-selected attribute paths
2799
+ * @param dialogTitle Optional custom dialog title
2800
+ * @returns Promise that resolves with the result containing selected attributes and confirmation status
2801
+ */
2802
+ async openAttributeSelector(rtCkTypeId, selectedAttributes, dialogTitle) {
2803
+ const data = {
2804
+ rtCkTypeId,
2805
+ selectedAttributes,
2806
+ dialogTitle
2807
+ };
2808
+ const dialogRef = this.dialogService.open({
2809
+ content: AttributeSelectorDialogComponent,
2810
+ width: 900,
2811
+ height: 700,
2812
+ minWidth: 800,
2813
+ minHeight: 650,
2814
+ title: dialogTitle || 'Select Attributes'
2815
+ });
2816
+ // Pass data to the component
2817
+ if (dialogRef.content?.instance) {
2818
+ dialogRef.content.instance.data = data;
174
2819
  }
175
- this._columns = [];
176
- for (const column of cols) {
177
- if (typeof column === 'string') {
178
- this._columns.push({ dataKey: column });
2820
+ try {
2821
+ const result = await firstValueFrom(dialogRef.result);
2822
+ if (result && typeof result === 'object' && 'selectedAttributes' in result) {
2823
+ // User clicked OK and we have a result
2824
+ const dialogResult = result;
2825
+ return {
2826
+ confirmed: true,
2827
+ selectedAttributes: dialogResult.selectedAttributes
2828
+ };
179
2829
  }
180
2830
  else {
181
- this._columns.push(column);
2831
+ // User clicked Cancel or closed dialog
2832
+ return {
2833
+ confirmed: false,
2834
+ selectedAttributes: []
2835
+ };
182
2836
  }
183
2837
  }
2838
+ catch {
2839
+ // Dialog was closed without result (e.g., ESC key, X button)
2840
+ return {
2841
+ confirmed: false,
2842
+ selectedAttributes: []
2843
+ };
2844
+ }
2845
+ }
2846
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AttributeSelectorDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2847
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AttributeSelectorDialogService });
2848
+ }
2849
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AttributeSelectorDialogService, decorators: [{
2850
+ type: Injectable
2851
+ }] });
2852
+
2853
+ class CkTypeSelectorInputComponent {
2854
+ autocomplete;
2855
+ placeholder = 'Select a CK type...';
2856
+ minSearchLength = 2;
2857
+ maxResults = 50;
2858
+ debounceMs = 300;
2859
+ ckModelIds;
2860
+ allowAbstract = false;
2861
+ dialogTitle = 'Select Construction Kit Type';
2862
+ advancedSearchLabel = 'Advanced Search...';
2863
+ _disabled = false;
2864
+ get disabled() {
2865
+ return this._disabled;
184
2866
  }
185
- get columns() {
186
- return this._columns;
2867
+ set disabled(value) {
2868
+ this._disabled = !!value;
2869
+ if (this._disabled) {
2870
+ this.searchFormControl.disable();
2871
+ }
2872
+ else {
2873
+ this.searchFormControl.enable();
2874
+ }
187
2875
  }
188
- _columns = [];
189
- selectedRow = null; // Track the selected row
190
- selectedPageSizeSubject = new BehaviorSubject(this.selectedPageSize);
191
- paginator;
192
- sort;
193
- input;
194
- loadingSubscription;
195
- wasLoadingPreviously = false;
196
- hadFocusBeforeLoading = false;
197
- get columnDataKeys() {
198
- return this._columns.map((c) => getDataKey(c));
2876
+ _required = false;
2877
+ get required() {
2878
+ return this._required;
199
2879
  }
200
- get columnDisplayNames() {
201
- return this._columns.map((c) => getDisplayName(c));
2880
+ set required(value) {
2881
+ this._required = !!value;
202
2882
  }
2883
+ ckTypeSelected = new EventEmitter();
2884
+ ckTypeCleared = new EventEmitter();
2885
+ searchFormControl = new FormControl();
2886
+ filteredTypes = [];
2887
+ selectedCkType = null;
2888
+ isLoading = false;
2889
+ typeMap = new Map();
2890
+ searchSubject = new Subject();
2891
+ subscriptions = new Subscription();
2892
+ onChange = () => { };
2893
+ onTouched = () => { };
2894
+ searchIcon = searchIcon;
2895
+ ckTypeSelectorService = inject(CkTypeSelectorService);
2896
+ dialogService = inject(CkTypeSelectorDialogService, { optional: true });
203
2897
  ngOnInit() {
204
- this.selectedPageSizeSubject.next(this.selectedPageSize);
205
- if (!this.dataSource) {
206
- throw new Error('No dataSource provided');
207
- }
208
- this.checkSelectedRow();
209
- }
210
- ngAfterContentInit() {
211
- // Build a map from template reference name to TemplateRef
212
- this.templateMap.clear();
213
- if (this.cellTemplates) {
214
- this.cellTemplates.forEach((template) => {
215
- // Try to get the reference name from the template
216
- const refName = template._declarationTContainer?.localNames?.[0];
217
- if (refName) {
218
- this.templateMap.set(refName, template);
219
- }
220
- });
2898
+ this.setupSearch();
2899
+ }
2900
+ ngOnDestroy() {
2901
+ this.subscriptions.unsubscribe();
2902
+ this.searchSubject.complete();
2903
+ }
2904
+ // ControlValueAccessor implementation
2905
+ writeValue(value) {
2906
+ if (value !== this.selectedCkType) {
2907
+ this.selectedCkType = value;
2908
+ if (value) {
2909
+ this.searchFormControl.setValue(value.rtCkTypeId, { emitEvent: false });
2910
+ }
2911
+ else {
2912
+ this.searchFormControl.setValue('', { emitEvent: false });
2913
+ }
221
2914
  }
222
2915
  }
223
- ngAfterViewInit() {
224
- // Subscribe to loading state changes to restore focus
225
- if (this.dataSource?.loading$) {
226
- this.loadingSubscription = this.dataSource.loading$.subscribe((isLoading) => {
227
- // Check if we're transitioning from loading to not loading
228
- if (this.wasLoadingPreviously && !isLoading && this.hadFocusBeforeLoading && this.input) {
229
- // Use setTimeout to ensure focus happens after Angular's change detection
230
- setTimeout(() => {
231
- this.input?.nativeElement?.focus();
232
- this.hadFocusBeforeLoading = false;
233
- }, 0);
234
- }
235
- // Track if input has focus before it gets disabled
236
- if (!this.wasLoadingPreviously && isLoading && this.input) {
237
- this.hadFocusBeforeLoading = document.activeElement === this.input.nativeElement;
238
- }
239
- this.wasLoadingPreviously = isLoading;
240
- });
2916
+ registerOnChange(fn) {
2917
+ this.onChange = fn;
2918
+ }
2919
+ registerOnTouched(fn) {
2920
+ this.onTouched = fn;
2921
+ }
2922
+ setDisabledState(isDisabled) {
2923
+ this.disabled = isDisabled;
2924
+ }
2925
+ // Validator implementation
2926
+ validate(control) {
2927
+ if (this.required && !this.selectedCkType) {
2928
+ return { required: true };
241
2929
  }
242
- if (this.sort && this.input && this.paginator) {
243
- fromEvent(this.input.nativeElement, 'keyup')
244
- .pipe(debounceTime(500), distinctUntilChanged(), tap(() => {
245
- // server-side search
246
- if (this.paginator) {
247
- this.paginator.pageIndex = 0;
248
- }
249
- this.searchFilterStringUpdated.emit(this.input?.nativeElement?.value ?? '');
250
- this.loadData();
251
- }))
252
- .subscribe();
253
- // reset the paginator after sorting
254
- this.sort.sortChange.subscribe(() => {
255
- if (this.paginator)
256
- this.paginator.pageIndex = 0;
257
- });
258
- merge(this.sort.sortChange, this.paginator.page)
259
- .pipe(tap(() => {
260
- this.loadData();
261
- }))
262
- .subscribe();
2930
+ const value = control.value;
2931
+ if (value && typeof value === 'string') {
2932
+ return { invalidCkType: true };
263
2933
  }
264
- this.loadData();
2934
+ return null;
265
2935
  }
266
- loadData() {
267
- if (!this.input || !this.sort) {
2936
+ // Event handlers
2937
+ onFilterChange(filter) {
2938
+ if (!filter || filter.length < this.minSearchLength) {
2939
+ this.filteredTypes = [];
268
2940
  return;
269
2941
  }
270
- const filterString = this.input.nativeElement.value;
271
- const field = this.sort.active;
272
- const direction = this.sort.direction;
273
- let filter = null;
274
- if (filterString) {
275
- filter = {
276
- type: SearchFilterTypesDto.AttributeFilterDto,
277
- attributePaths: this.searchFilterColumns,
278
- searchTerm: filterString
279
- };
280
- }
281
- const sort = [];
282
- if (field) {
283
- sort.push({
284
- attributePath: field,
285
- sortOrder: direction === 'asc' ? SortOrdersDto.AscendingDto : SortOrdersDto.DescendingDto
286
- });
287
- }
288
- if (this.paginator) {
289
- this.dataSource?.loadData(this.paginator.pageIndex * this.paginator.pageSize, this.paginator.pageSize, filter, null, sort);
290
- }
2942
+ this.searchSubject.next(filter);
291
2943
  }
292
- encodeURIComponent = encodeURIComponent;
293
- accessElement(element, column) {
294
- if (column.templateName) {
295
- return element;
296
- }
297
- if (column.dataKey.indexOf('.') === -1) {
298
- return element[column.dataKey];
2944
+ onSelectionChange(value) {
2945
+ if (value && typeof value === 'string') {
2946
+ const ckType = this.typeMap.get(value);
2947
+ if (ckType) {
2948
+ this.selectCkType(ckType);
2949
+ }
299
2950
  }
300
- const keys = column.dataKey.split('.');
301
- let value = element;
302
- for (const key of keys) {
303
- value = value[key];
2951
+ }
2952
+ onFocus() {
2953
+ const currentValue = this.searchFormControl.value;
2954
+ if (currentValue && currentValue.length >= this.minSearchLength && this.filteredTypes.length === 0) {
2955
+ this.searchSubject.next(currentValue);
304
2956
  }
305
- return value;
306
2957
  }
307
- selectedPageSizeChanged($event) {
308
- console.log($event.pageSize);
309
- this.selectedPageSizeSubject.next($event.pageSize);
2958
+ onBlur() {
2959
+ this.onTouched();
2960
+ if (this.filteredTypes.length === 1 && !this.selectedCkType) {
2961
+ const displayText = this.filteredTypes[0];
2962
+ const ckType = this.typeMap.get(displayText);
2963
+ if (ckType) {
2964
+ this.selectCkType(ckType);
2965
+ }
2966
+ }
310
2967
  }
311
- getActionDisplayTexts() {
312
- return this.actionColumns.map((ac) => ac.displayText);
2968
+ // Public methods
2969
+ clear() {
2970
+ this.selectedCkType = null;
2971
+ this.filteredTypes = [];
2972
+ this.typeMap.clear();
2973
+ this.searchFormControl.setValue('', { emitEvent: false });
2974
+ this.onChange(null);
2975
+ this.ckTypeCleared.emit();
2976
+ this.autocomplete.focus();
313
2977
  }
314
- onRowClick(row) {
315
- if (this.rowIsClickable) {
316
- this.selectedRow = row; // Set the clicked row as the selected one
317
- this.rowClicked.emit(row); // Emit the clicked row data
2978
+ focus() {
2979
+ if (this.autocomplete) {
2980
+ this.autocomplete.focus();
318
2981
  }
319
2982
  }
320
- isRowSelected(row) {
321
- return this.selectedRow === row; // Check if the row is selected
2983
+ reset() {
2984
+ this.clear();
322
2985
  }
323
- checkSelectedRow() {
324
- // @ts-expect-error jnu
325
- this.dataSource.connect(null).subscribe((data) => {
326
- for (const entry of data) {
327
- if (entry.rtId === this.selectedRowId) {
328
- this.onRowClick(entry);
329
- }
2986
+ // Private methods
2987
+ setupSearch() {
2988
+ this.subscriptions.add(this.searchSubject.pipe(debounceTime(this.debounceMs), distinctUntilChanged(), tap(() => {
2989
+ this.isLoading = true;
2990
+ this.filteredTypes = [];
2991
+ }), switchMap(filter => this.ckTypeSelectorService.getCkTypes({
2992
+ ckModelIds: this.ckModelIds,
2993
+ searchText: filter,
2994
+ first: this.maxResults
2995
+ }).pipe(catchError(error => {
2996
+ console.error('CK type search error:', error);
2997
+ return of({ items: [], totalCount: 0 });
2998
+ })))).subscribe(result => {
2999
+ this.isLoading = false;
3000
+ // Filter out abstract types if not allowed
3001
+ let items = result.items;
3002
+ if (!this.allowAbstract) {
3003
+ items = items.filter(item => !item.isAbstract);
330
3004
  }
331
- });
3005
+ this.filteredTypes = items.map(item => item.rtCkTypeId);
3006
+ this.typeMap = new Map(items.map(item => [item.rtCkTypeId, item]));
3007
+ }));
332
3008
  }
333
- emitRowData(data) {
334
- this.actionColumnClick.emit(data);
335
- }
336
- // Predicate for rows with optionActions
337
- hasOptionActions = (_row) => {
338
- return this.optionActions.length > 0;
339
- };
340
- // Predicate for rows with actionColumns but no optionActions
341
- hasActionColumns = () => {
342
- return this.actionColumns.length > 0 || this.optionActions.length > 0;
343
- };
344
- getDisplayName = getDisplayName;
345
- getDataKey = getDataKey;
346
- isSortable(column) {
347
- // If sortingDisabled is explicitly set, use that setting
348
- if (column.sortingDisabled !== undefined) {
349
- return !column.sortingDisabled;
350
- }
351
- return true;
352
- }
353
- getTemplate(templateName) {
354
- return this.templateMap.get(templateName);
3009
+ selectCkType(ckType) {
3010
+ this.selectedCkType = ckType;
3011
+ this.searchFormControl.setValue(ckType.rtCkTypeId, { emitEvent: false });
3012
+ this.filteredTypes = [];
3013
+ this.onChange(ckType);
3014
+ this.ckTypeSelected.emit(ckType);
3015
+ this.autocomplete.toggle(false);
355
3016
  }
356
- ngOnDestroy() {
357
- // Clean up the loading subscription to prevent memory leaks
358
- if (this.loadingSubscription) {
359
- this.loadingSubscription.unsubscribe();
3017
+ // Dialog
3018
+ async openDialog(event) {
3019
+ if (event) {
3020
+ event.preventDefault();
3021
+ event.stopPropagation();
3022
+ }
3023
+ if (!this.dialogService) {
3024
+ console.warn('CkTypeSelectorDialogService not available');
3025
+ return;
3026
+ }
3027
+ this.autocomplete.toggle(false);
3028
+ const result = await this.dialogService.openCkTypeSelector({
3029
+ selectedCkTypeId: this.selectedCkType?.fullName,
3030
+ ckModelIds: this.ckModelIds,
3031
+ dialogTitle: this.dialogTitle,
3032
+ allowAbstract: this.allowAbstract
3033
+ });
3034
+ if (result.confirmed && result.selectedCkType) {
3035
+ this.selectCkType(result.selectedCkType);
360
3036
  }
361
3037
  }
362
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: MmOctoTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
363
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.12", type: MmOctoTableComponent, isStandalone: true, selector: "mm-octo-table", inputs: { dataSource: "dataSource", actionColumns: "actionColumns", leftToolbarActions: "leftToolbarActions", optionActions: "optionActions", searchFilterColumns: "searchFilterColumns", currentId: "currentId", defaultSortColumn: "defaultSortColumn", rowIsClickable: "rowIsClickable", pageSizeOptions: "pageSizeOptions", selectedPageSize: "selectedPageSize", selectedRowId: "selectedRowId", columns: "columns" }, outputs: { rowClicked: "rowClicked", searchFilterStringUpdated: "searchFilterStringUpdated", actionColumnClick: "actionColumnClick" }, queries: [{ propertyName: "cellTemplates", predicate: TemplateRef }], viewQueries: [{ propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }, { propertyName: "input", first: true, predicate: ["input"], descendants: true }], ngImport: i0, template: "<div>\n @if (dataSource?.loading$ | async) {\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n }\n</div>\n\n<mat-toolbar class=\"octo-toolbar octo-detail-toolbar\">\n @for (toolbarAction of leftToolbarActions; track toolbarAction) {\n @if (toolbarAction.route) {\n <button\n mat-flat-button\n [routerLink]=\"toolbarAction.route\"\n type=\"button\"\n [hidden]=\"!toolbarAction.isHidden ? false : (toolbarAction.isHidden | async)\"\n [disabled]=\"!toolbarAction.isDisabled ? false : (toolbarAction.isDisabled | async)\"\n >\n @if (toolbarAction?.svgIconName) {\n <mat-icon class=\"svg-icon\" svgIcon=\"{{ toolbarAction.svgIconName }}\" matListItemIcon></mat-icon>\n }\n @if (toolbarAction?.iconName) {\n <mat-icon class=\"material-symbols-outlined\">{{ toolbarAction.iconName }}</mat-icon>\n }\n {{ toolbarAction.actionText }}\n </button>\n } @else {\n <button\n mat-flat-button\n type=\"button\"\n (click)=\"toolbarAction.clickHandler?.()\"\n [hidden]=\"!toolbarAction.isHidden ? false : (toolbarAction.isHidden | async)\"\n [disabled]=\"!toolbarAction.isDisabled ? false : (toolbarAction.isDisabled | async)\"\n >\n @if (toolbarAction?.svgIconName) {\n <mat-icon class=\"svg-icon\" svgIcon=\"{{ toolbarAction.svgIconName }}\" matListItemIcon></mat-icon>\n }\n @if (toolbarAction?.iconName) {\n <mat-icon class=\"material-symbols-outlined\">{{ toolbarAction.iconName }}</mat-icon>\n }\n {{ toolbarAction.actionText }}\n </button>\n }\n }\n\n <div class=\"octo-spacer\"></div>\n\n <div class=\"octo-toolbar-search\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>\n <mat-icon class=\"material-symbols-outlined\">search</mat-icon>\n Search\n </mat-label>\n <input #input matInput [disabled]=\"dataSource?.loading$ | async\"/>\n </mat-form-field>\n </div>\n</mat-toolbar>\n\n<mat-table\n [dataSource]=\"dataSource\"\n class=\"mat-elevation-z8 table-container\"\n mat-table\n matSort\n matSortActive=\"{{ defaultSortColumn }}\"\n matSortDirection=\"asc\"\n matSortDisableClear=\"true\"\n>\n @for (column of columns; track column) {\n <ng-container [matColumnDef]=\"getDataKey(column)\">\n <mat-header-cell *matHeaderCellDef>\n <span *ngIf=\"isSortable(column); else nonSortableHeader\" mat-sort-header>\n {{ getDisplayName(column) }}\n </span>\n <ng-template #nonSortableHeader>\n {{ getDisplayName(column) }}\n </ng-template>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let element\">\n <span class=\"mobile-label\">{{ getDisplayName(column)}}</span>\n @if (column.templateName) {\n <ng-container\n *ngTemplateOutlet=\"getTemplate(column.templateName); context: { $implicit: element }\"></ng-container>\n } @else {\n {{ accessElement(element, column) }}\n }\n </mat-cell>\n </ng-container>\n }\n\n <!-- Consolidated Action Column with multiple buttons -->\n <ng-container matColumnDef=\"actions\">\n <mat-header-cell *matHeaderCellDef>Actions</mat-header-cell>\n <mat-cell *matCellDef=\"let element\">\n @for (column of actionColumns; track column) {\n <ng-container>\n <button mat-button\n (click)=\"emitRowData({ action: column.actionId, id: encodeURIComponent(element[currentId]), entry: element })\">\n <mat-icon *ngIf=\"column?.svgIconName\" class=\"svg-icon\" svgIcon=\"{{ column.svgIconName }}\"\n matListItemIcon></mat-icon>\n <mat-icon *ngIf=\"column?.iconName\" class=\"material-symbols-outlined\">{{ column.iconName }}</mat-icon>\n </button>\n </ng-container>\n }\n\n <!-- Options menu with additional actions -->\n @if (optionActions.length > 0) {\n <button mat-icon-button [matMenuTriggerFor]=\"menu\" aria-label=\"Options\">\n <mat-icon class=\"material-symbols-outlined\">more_vert</mat-icon>\n </button>\n }\n\n <mat-menu #menu=\"matMenu\">\n @for (optionAction of optionActions; track optionAction) {\n <ng-container>\n <button\n mat-menu-item\n (click)=\"emitRowData({ action: optionAction.actionId, id: encodeURIComponent(element[currentId]), entry: element })\"\n >\n @if (optionAction?.svgIconName) {\n <mat-icon class=\"svg-icon\" svgIcon=\"{{ optionAction.svgIconName }}\" matListItemIcon></mat-icon>\n }\n @if (optionAction?.iconName) {\n <mat-icon class=\"material-symbols-outlined\">{{ optionAction.iconName }}</mat-icon>\n }\n {{ optionAction.displayText }}\n </button>\n </ng-container>\n }\n </mat-menu>\n </mat-cell>\n </ng-container>\n\n @if (hasActionColumns()) {\n <mat-header-row *matHeaderRowDef=\"columnDataKeys.concat(['actions'])\"></mat-header-row>\n <mat-row *matRowDef=\"let row; columns: columnDataKeys.concat(['actions'])\"></mat-row>\n } @else {\n <mat-header-row *matHeaderRowDef=\"columnDataKeys\"></mat-header-row>\n <mat-row *matRowDef=\"let row; columns: columnDataKeys\"></mat-row>\n }\n</mat-table>\n\n<mat-paginator\n [length]=\"dataSource?.totalCount$ | async\"\n (page)=\"selectedPageSizeChanged($event)\"\n [pageSizeOptions]=\"pageSizeOptions\"\n [pageSize]=\"selectedPageSizeSubject.getValue()\"\n></mat-paginator>\n", styles: [".table-container{display:block;width:100%;max-height:70vh;overflow-x:auto;overflow-y:auto}.mat-mdc-header-cell{white-space:unset!important;word-wrap:break-word!important;overflow-wrap:break-word;word-break:break-word;-ms-hyphens:auto;-moz-hyphens:auto;-webkit-hyphens:auto;hyphens:auto}.octo-toolbar .octo-toolbar-search .mat-mdc-form-field{--mat-form-field-container-height: 48px;--mat-form-field-filled-label-display: none;--mat-form-field-container-vertical-padding: 12px;--mat-form-field-filled-with-label-container-padding-top: 12px;--mat-form-field-filled-with-label-container-padding-bottom: 12px}.octo-toolbar .octo-toolbar-search .mat-mdc-form-field .mat-mdc-form-field-subscript-wrapper{height:0;min-height:0}.octo-toolbar .octo-spacer{flex:1 1 auto}.octo-toolbar button{margin-right:5px}.mobile-label{display:none}.mobileCellHidden{display:flex}@media (max-width: 959px){.mat-mdc-table{max-height:800px}.mat-mdc-table mat-row,.mat-table mat-header-row,.mat-table mat-footer-row{padding-left:24px;padding-right:24px}.mat-mdc-table mat-cell:first-child{padding-left:16px}.mat-mdc-table .mat-column-actions{display:flex;justify-content:flex-end;border-bottom-width:2px}.mobile-label{width:140px;display:inline-block;font-weight:700}.mat-mdc-footer-row:after,.mat-mdc-header-row:after,.mat-mdc-row:after{min-height:0}.mat-mdc-header-row{display:none}.mat-mdc-row{flex-direction:column;align-items:start;padding:8px 24px}.mat-mdc-cell{min-height:auto;width:100%;display:inline-block}.mobileCellHidden{display:none}}\n"], dependencies: [{ kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "directive", type: MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "component", type: MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "component", type: MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "component", type: MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "directive", type: MatListItemIcon, selector: "[matListItemIcon]" }, { kind: "component", type: MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "directive", type: MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: AsyncPipe, name: "async" }] });
3038
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CkTypeSelectorInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3039
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.5", type: CkTypeSelectorInputComponent, isStandalone: true, selector: "mm-ck-type-selector-input", inputs: { placeholder: "placeholder", minSearchLength: "minSearchLength", maxResults: "maxResults", debounceMs: "debounceMs", ckModelIds: "ckModelIds", allowAbstract: "allowAbstract", dialogTitle: "dialogTitle", advancedSearchLabel: "advancedSearchLabel", disabled: "disabled", required: "required" }, outputs: { ckTypeSelected: "ckTypeSelected", ckTypeCleared: "ckTypeCleared" }, providers: [
3040
+ {
3041
+ provide: NG_VALUE_ACCESSOR,
3042
+ useExisting: forwardRef(() => CkTypeSelectorInputComponent),
3043
+ multi: true
3044
+ },
3045
+ {
3046
+ provide: NG_VALIDATORS,
3047
+ useExisting: forwardRef(() => CkTypeSelectorInputComponent),
3048
+ multi: true
3049
+ }
3050
+ ], viewQueries: [{ propertyName: "autocomplete", first: true, predicate: ["autocomplete"], descendants: true, static: true }], ngImport: i0, template: `
3051
+ <div class="ck-type-select-wrapper" [class.disabled]="disabled">
3052
+ <kendo-autocomplete
3053
+ #autocomplete
3054
+ [formControl]="searchFormControl"
3055
+ [data]="filteredTypes"
3056
+ [loading]="isLoading"
3057
+ [placeholder]="placeholder"
3058
+ [suggest]="true"
3059
+ [clearButton]="true"
3060
+ [filterable]="true"
3061
+ (filterChange)="onFilterChange($event)"
3062
+ (valueChange)="onSelectionChange($event)"
3063
+ (blur)="onBlur()"
3064
+ (focus)="onFocus()"
3065
+ class="ck-type-autocomplete">
3066
+
3067
+ <ng-template kendoAutoCompleteItemTemplate let-dataItem>
3068
+ <div class="ck-type-item">
3069
+ {{ dataItem }}
3070
+ </div>
3071
+ </ng-template>
3072
+
3073
+ <ng-template kendoAutoCompleteNoDataTemplate>
3074
+ <div class="no-data-message">
3075
+ <span *ngIf="!isLoading && searchFormControl.value && searchFormControl.value.length >= minSearchLength">
3076
+ No types found for "{{ searchFormControl.value }}"
3077
+ </span>
3078
+ <span *ngIf="!isLoading && (!searchFormControl.value || searchFormControl.value.length < minSearchLength)">
3079
+ Type at least {{ minSearchLength }} characters to search...
3080
+ </span>
3081
+ </div>
3082
+ </ng-template>
3083
+
3084
+ <ng-template kendoAutoCompleteFooterTemplate>
3085
+ <div class="advanced-search-footer" (click)="openDialog($event)">
3086
+ <kendo-svg-icon [icon]="searchIcon" size="small"></kendo-svg-icon>
3087
+ <span>{{ advancedSearchLabel }}</span>
3088
+ </div>
3089
+ </ng-template>
3090
+
3091
+ </kendo-autocomplete>
3092
+
3093
+ <button
3094
+ kendoButton
3095
+ type="button"
3096
+ [svgIcon]="searchIcon"
3097
+ [disabled]="disabled"
3098
+ [title]="advancedSearchLabel"
3099
+ class="dialog-button"
3100
+ (click)="openDialog()">
3101
+ </button>
3102
+ </div>
3103
+ `, isInline: true, styles: [":host{display:block;width:100%}.ck-type-select-wrapper{position:relative;display:flex;align-items:center;width:100%;gap:4px}.ck-type-select-wrapper.disabled{opacity:.6;pointer-events:none}.ck-type-autocomplete{flex:1;min-width:0}.dialog-button{flex-shrink:0;height:30px;width:30px;padding:0;display:flex;align-items:center;justify-content:center}.ck-type-item{padding:4px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-data-message{padding:8px 12px;color:var(--kendo-color-subtle);font-style:italic;text-align:center}.advanced-search-footer{display:flex;align-items:center;gap:8px;padding:8px 12px;cursor:pointer;color:var(--kendo-color-primary);border-top:1px solid var(--kendo-color-border);background:var(--kendo-color-surface-alt);transition:background-color .2s}.advanced-search-footer:hover{background:var(--kendo-color-base-hover)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i3$1.AutoCompleteComponent, selector: "kendo-autocomplete", inputs: ["highlightFirst", "showStickyHeader", "focusableId", "data", "value", "valueField", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "clearButton", "suggest", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoAutoComplete"] }, { kind: "directive", type: i3$1.FooterTemplateDirective, selector: "[kendoDropDownListFooterTemplate],[kendoComboBoxFooterTemplate],[kendoDropDownTreeFooterTemplate],[kendoMultiColumnComboBoxFooterTemplate],[kendoAutoCompleteFooterTemplate],[kendoMultiSelectFooterTemplate],[kendoMultiSelectTreeFooterTemplate]" }, { kind: "directive", type: i3$1.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "directive", type: i3$1.NoDataTemplateDirective, selector: "[kendoDropDownListNoDataTemplate],[kendoDropDownTreeNoDataTemplate],[kendoComboBoxNoDataTemplate],[kendoMultiColumnComboBoxNoDataTemplate],[kendoAutoCompleteNoDataTemplate],[kendoMultiSelectNoDataTemplate],[kendoMultiSelectTreeNoDataTemplate]" }, { kind: "ngmodule", type: LoaderModule }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i3.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: IconsModule }, { kind: "component", type: i5$1.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "ngmodule", type: SVGIconModule }] });
364
3104
  }
365
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: MmOctoTableComponent, decorators: [{
3105
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CkTypeSelectorInputComponent, decorators: [{
366
3106
  type: Component,
367
- args: [{ selector: 'mm-octo-table', standalone: true, imports: [
368
- AsyncPipe,
369
- MatButton,
370
- MatCell,
371
- MatCellDef,
372
- MatColumnDef,
373
- MatFormField,
374
- MatHeaderCell,
375
- MatIcon,
376
- MatInput,
377
- MatLabel,
378
- MatPaginator,
379
- MatProgressBar,
380
- MatRow,
381
- MatRowDef,
382
- MatSort,
383
- MatSortHeader,
384
- MatTable,
385
- MatToolbar,
386
- NgIf,
387
- MatHeaderCellDef,
388
- MatIcon,
389
- MatIconButton,
390
- MatMenu,
391
- MatMenuItem,
392
- MatMenuTrigger,
393
- MatIcon,
394
- MatIcon,
395
- MatListItemIcon,
396
- MatHeaderRow,
397
- MatHeaderRowDef,
398
- MatHeaderRow,
399
- MatHeaderRowDef,
400
- MatButton,
401
- MatIcon,
402
- RouterLink,
403
- MatIcon,
404
- MatHeaderCellDef,
405
- MatCellDef,
406
- MatHeaderRowDef,
407
- MatRowDef,
408
- NgTemplateOutlet
409
- ], template: "<div>\n @if (dataSource?.loading$ | async) {\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n }\n</div>\n\n<mat-toolbar class=\"octo-toolbar octo-detail-toolbar\">\n @for (toolbarAction of leftToolbarActions; track toolbarAction) {\n @if (toolbarAction.route) {\n <button\n mat-flat-button\n [routerLink]=\"toolbarAction.route\"\n type=\"button\"\n [hidden]=\"!toolbarAction.isHidden ? false : (toolbarAction.isHidden | async)\"\n [disabled]=\"!toolbarAction.isDisabled ? false : (toolbarAction.isDisabled | async)\"\n >\n @if (toolbarAction?.svgIconName) {\n <mat-icon class=\"svg-icon\" svgIcon=\"{{ toolbarAction.svgIconName }}\" matListItemIcon></mat-icon>\n }\n @if (toolbarAction?.iconName) {\n <mat-icon class=\"material-symbols-outlined\">{{ toolbarAction.iconName }}</mat-icon>\n }\n {{ toolbarAction.actionText }}\n </button>\n } @else {\n <button\n mat-flat-button\n type=\"button\"\n (click)=\"toolbarAction.clickHandler?.()\"\n [hidden]=\"!toolbarAction.isHidden ? false : (toolbarAction.isHidden | async)\"\n [disabled]=\"!toolbarAction.isDisabled ? false : (toolbarAction.isDisabled | async)\"\n >\n @if (toolbarAction?.svgIconName) {\n <mat-icon class=\"svg-icon\" svgIcon=\"{{ toolbarAction.svgIconName }}\" matListItemIcon></mat-icon>\n }\n @if (toolbarAction?.iconName) {\n <mat-icon class=\"material-symbols-outlined\">{{ toolbarAction.iconName }}</mat-icon>\n }\n {{ toolbarAction.actionText }}\n </button>\n }\n }\n\n <div class=\"octo-spacer\"></div>\n\n <div class=\"octo-toolbar-search\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>\n <mat-icon class=\"material-symbols-outlined\">search</mat-icon>\n Search\n </mat-label>\n <input #input matInput [disabled]=\"dataSource?.loading$ | async\"/>\n </mat-form-field>\n </div>\n</mat-toolbar>\n\n<mat-table\n [dataSource]=\"dataSource\"\n class=\"mat-elevation-z8 table-container\"\n mat-table\n matSort\n matSortActive=\"{{ defaultSortColumn }}\"\n matSortDirection=\"asc\"\n matSortDisableClear=\"true\"\n>\n @for (column of columns; track column) {\n <ng-container [matColumnDef]=\"getDataKey(column)\">\n <mat-header-cell *matHeaderCellDef>\n <span *ngIf=\"isSortable(column); else nonSortableHeader\" mat-sort-header>\n {{ getDisplayName(column) }}\n </span>\n <ng-template #nonSortableHeader>\n {{ getDisplayName(column) }}\n </ng-template>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let element\">\n <span class=\"mobile-label\">{{ getDisplayName(column)}}</span>\n @if (column.templateName) {\n <ng-container\n *ngTemplateOutlet=\"getTemplate(column.templateName); context: { $implicit: element }\"></ng-container>\n } @else {\n {{ accessElement(element, column) }}\n }\n </mat-cell>\n </ng-container>\n }\n\n <!-- Consolidated Action Column with multiple buttons -->\n <ng-container matColumnDef=\"actions\">\n <mat-header-cell *matHeaderCellDef>Actions</mat-header-cell>\n <mat-cell *matCellDef=\"let element\">\n @for (column of actionColumns; track column) {\n <ng-container>\n <button mat-button\n (click)=\"emitRowData({ action: column.actionId, id: encodeURIComponent(element[currentId]), entry: element })\">\n <mat-icon *ngIf=\"column?.svgIconName\" class=\"svg-icon\" svgIcon=\"{{ column.svgIconName }}\"\n matListItemIcon></mat-icon>\n <mat-icon *ngIf=\"column?.iconName\" class=\"material-symbols-outlined\">{{ column.iconName }}</mat-icon>\n </button>\n </ng-container>\n }\n\n <!-- Options menu with additional actions -->\n @if (optionActions.length > 0) {\n <button mat-icon-button [matMenuTriggerFor]=\"menu\" aria-label=\"Options\">\n <mat-icon class=\"material-symbols-outlined\">more_vert</mat-icon>\n </button>\n }\n\n <mat-menu #menu=\"matMenu\">\n @for (optionAction of optionActions; track optionAction) {\n <ng-container>\n <button\n mat-menu-item\n (click)=\"emitRowData({ action: optionAction.actionId, id: encodeURIComponent(element[currentId]), entry: element })\"\n >\n @if (optionAction?.svgIconName) {\n <mat-icon class=\"svg-icon\" svgIcon=\"{{ optionAction.svgIconName }}\" matListItemIcon></mat-icon>\n }\n @if (optionAction?.iconName) {\n <mat-icon class=\"material-symbols-outlined\">{{ optionAction.iconName }}</mat-icon>\n }\n {{ optionAction.displayText }}\n </button>\n </ng-container>\n }\n </mat-menu>\n </mat-cell>\n </ng-container>\n\n @if (hasActionColumns()) {\n <mat-header-row *matHeaderRowDef=\"columnDataKeys.concat(['actions'])\"></mat-header-row>\n <mat-row *matRowDef=\"let row; columns: columnDataKeys.concat(['actions'])\"></mat-row>\n } @else {\n <mat-header-row *matHeaderRowDef=\"columnDataKeys\"></mat-header-row>\n <mat-row *matRowDef=\"let row; columns: columnDataKeys\"></mat-row>\n }\n</mat-table>\n\n<mat-paginator\n [length]=\"dataSource?.totalCount$ | async\"\n (page)=\"selectedPageSizeChanged($event)\"\n [pageSizeOptions]=\"pageSizeOptions\"\n [pageSize]=\"selectedPageSizeSubject.getValue()\"\n></mat-paginator>\n", styles: [".table-container{display:block;width:100%;max-height:70vh;overflow-x:auto;overflow-y:auto}.mat-mdc-header-cell{white-space:unset!important;word-wrap:break-word!important;overflow-wrap:break-word;word-break:break-word;-ms-hyphens:auto;-moz-hyphens:auto;-webkit-hyphens:auto;hyphens:auto}.octo-toolbar .octo-toolbar-search .mat-mdc-form-field{--mat-form-field-container-height: 48px;--mat-form-field-filled-label-display: none;--mat-form-field-container-vertical-padding: 12px;--mat-form-field-filled-with-label-container-padding-top: 12px;--mat-form-field-filled-with-label-container-padding-bottom: 12px}.octo-toolbar .octo-toolbar-search .mat-mdc-form-field .mat-mdc-form-field-subscript-wrapper{height:0;min-height:0}.octo-toolbar .octo-spacer{flex:1 1 auto}.octo-toolbar button{margin-right:5px}.mobile-label{display:none}.mobileCellHidden{display:flex}@media (max-width: 959px){.mat-mdc-table{max-height:800px}.mat-mdc-table mat-row,.mat-table mat-header-row,.mat-table mat-footer-row{padding-left:24px;padding-right:24px}.mat-mdc-table mat-cell:first-child{padding-left:16px}.mat-mdc-table .mat-column-actions{display:flex;justify-content:flex-end;border-bottom-width:2px}.mobile-label{width:140px;display:inline-block;font-weight:700}.mat-mdc-footer-row:after,.mat-mdc-header-row:after,.mat-mdc-row:after{min-height:0}.mat-mdc-header-row{display:none}.mat-mdc-row{flex-direction:column;align-items:start;padding:8px 24px}.mat-mdc-cell{min-height:auto;width:100%;display:inline-block}.mobileCellHidden{display:none}}\n"] }]
410
- }], propDecorators: { dataSource: [{
3107
+ args: [{ selector: 'mm-ck-type-selector-input', standalone: true, imports: [
3108
+ CommonModule,
3109
+ ReactiveFormsModule,
3110
+ AutoCompleteModule,
3111
+ LoaderModule,
3112
+ ButtonsModule,
3113
+ IconsModule,
3114
+ SVGIconModule
3115
+ ], providers: [
3116
+ {
3117
+ provide: NG_VALUE_ACCESSOR,
3118
+ useExisting: forwardRef(() => CkTypeSelectorInputComponent),
3119
+ multi: true
3120
+ },
3121
+ {
3122
+ provide: NG_VALIDATORS,
3123
+ useExisting: forwardRef(() => CkTypeSelectorInputComponent),
3124
+ multi: true
3125
+ }
3126
+ ], template: `
3127
+ <div class="ck-type-select-wrapper" [class.disabled]="disabled">
3128
+ <kendo-autocomplete
3129
+ #autocomplete
3130
+ [formControl]="searchFormControl"
3131
+ [data]="filteredTypes"
3132
+ [loading]="isLoading"
3133
+ [placeholder]="placeholder"
3134
+ [suggest]="true"
3135
+ [clearButton]="true"
3136
+ [filterable]="true"
3137
+ (filterChange)="onFilterChange($event)"
3138
+ (valueChange)="onSelectionChange($event)"
3139
+ (blur)="onBlur()"
3140
+ (focus)="onFocus()"
3141
+ class="ck-type-autocomplete">
3142
+
3143
+ <ng-template kendoAutoCompleteItemTemplate let-dataItem>
3144
+ <div class="ck-type-item">
3145
+ {{ dataItem }}
3146
+ </div>
3147
+ </ng-template>
3148
+
3149
+ <ng-template kendoAutoCompleteNoDataTemplate>
3150
+ <div class="no-data-message">
3151
+ <span *ngIf="!isLoading && searchFormControl.value && searchFormControl.value.length >= minSearchLength">
3152
+ No types found for "{{ searchFormControl.value }}"
3153
+ </span>
3154
+ <span *ngIf="!isLoading && (!searchFormControl.value || searchFormControl.value.length < minSearchLength)">
3155
+ Type at least {{ minSearchLength }} characters to search...
3156
+ </span>
3157
+ </div>
3158
+ </ng-template>
3159
+
3160
+ <ng-template kendoAutoCompleteFooterTemplate>
3161
+ <div class="advanced-search-footer" (click)="openDialog($event)">
3162
+ <kendo-svg-icon [icon]="searchIcon" size="small"></kendo-svg-icon>
3163
+ <span>{{ advancedSearchLabel }}</span>
3164
+ </div>
3165
+ </ng-template>
3166
+
3167
+ </kendo-autocomplete>
3168
+
3169
+ <button
3170
+ kendoButton
3171
+ type="button"
3172
+ [svgIcon]="searchIcon"
3173
+ [disabled]="disabled"
3174
+ [title]="advancedSearchLabel"
3175
+ class="dialog-button"
3176
+ (click)="openDialog()">
3177
+ </button>
3178
+ </div>
3179
+ `, styles: [":host{display:block;width:100%}.ck-type-select-wrapper{position:relative;display:flex;align-items:center;width:100%;gap:4px}.ck-type-select-wrapper.disabled{opacity:.6;pointer-events:none}.ck-type-autocomplete{flex:1;min-width:0}.dialog-button{flex-shrink:0;height:30px;width:30px;padding:0;display:flex;align-items:center;justify-content:center}.ck-type-item{padding:4px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-data-message{padding:8px 12px;color:var(--kendo-color-subtle);font-style:italic;text-align:center}.advanced-search-footer{display:flex;align-items:center;gap:8px;padding:8px 12px;cursor:pointer;color:var(--kendo-color-primary);border-top:1px solid var(--kendo-color-border);background:var(--kendo-color-surface-alt);transition:background-color .2s}.advanced-search-footer:hover{background:var(--kendo-color-base-hover)}\n"] }]
3180
+ }], propDecorators: { autocomplete: [{
3181
+ type: ViewChild,
3182
+ args: ['autocomplete', { static: true }]
3183
+ }], placeholder: [{
411
3184
  type: Input
412
- }], cellTemplates: [{
413
- type: ContentChildren,
414
- args: [TemplateRef]
415
- }], actionColumns: [{
3185
+ }], minSearchLength: [{
416
3186
  type: Input
417
- }], leftToolbarActions: [{
3187
+ }], maxResults: [{
418
3188
  type: Input
419
- }], optionActions: [{
3189
+ }], debounceMs: [{
420
3190
  type: Input
421
- }], searchFilterColumns: [{
3191
+ }], ckModelIds: [{
422
3192
  type: Input
423
- }], currentId: [{
3193
+ }], allowAbstract: [{
424
3194
  type: Input
425
- }], defaultSortColumn: [{
3195
+ }], dialogTitle: [{
426
3196
  type: Input
427
- }], rowIsClickable: [{
3197
+ }], advancedSearchLabel: [{
428
3198
  type: Input
429
- }], pageSizeOptions: [{
3199
+ }], disabled: [{
430
3200
  type: Input
431
- }], selectedPageSize: [{
3201
+ }], required: [{
432
3202
  type: Input
433
- }], rowClicked: [{
3203
+ }], ckTypeSelected: [{
434
3204
  type: Output
435
- }], searchFilterStringUpdated: [{
3205
+ }], ckTypeCleared: [{
436
3206
  type: Output
437
- }], selectedRowId: [{
3207
+ }] } });
3208
+
3209
+ class FieldFilterEditorComponent {
3210
+ plusIcon = plusIcon;
3211
+ minusIcon = minusIcon;
3212
+ trashIcon = trashIcon;
3213
+ dollarIcon = dollarIcon;
3214
+ arrayOperators = [
3215
+ FieldFilterOperatorsDto.InDto,
3216
+ FieldFilterOperatorsDto.NotInDto
3217
+ ];
3218
+ operators = [
3219
+ { label: 'Equals', value: FieldFilterOperatorsDto.EqualsDto },
3220
+ { label: 'Not Equals', value: FieldFilterOperatorsDto.NotEqualsDto },
3221
+ { label: 'Greater Than', value: FieldFilterOperatorsDto.GreaterThanDto },
3222
+ { label: 'Greater Equal', value: FieldFilterOperatorsDto.GreaterEqualThanDto },
3223
+ { label: 'Less Than', value: FieldFilterOperatorsDto.LessThanDto },
3224
+ { label: 'Less Equal', value: FieldFilterOperatorsDto.LessEqualThanDto },
3225
+ { label: 'Like', value: FieldFilterOperatorsDto.LikeDto },
3226
+ { label: 'In', value: FieldFilterOperatorsDto.InDto },
3227
+ { label: 'Not In', value: FieldFilterOperatorsDto.NotInDto },
3228
+ { label: 'Any Equals', value: FieldFilterOperatorsDto.AnyEqDto },
3229
+ { label: 'Any Like', value: FieldFilterOperatorsDto.AnyLikeDto },
3230
+ { label: 'Match RegEx', value: FieldFilterOperatorsDto.MatchRegExDto }
3231
+ ];
3232
+ booleanOptions = ['true', 'false'];
3233
+ availableAttributes = [];
3234
+ /** Enable variable mode - allows using variables instead of literal values */
3235
+ enableVariables = false;
3236
+ /** Available variables for selection when enableVariables is true */
3237
+ availableVariables = [];
3238
+ _filters = [];
3239
+ nextId = 1;
3240
+ filteredAttributeList = [];
3241
+ attributeTypeMap = new Map();
3242
+ get filters() {
3243
+ return this._filters;
3244
+ }
3245
+ set filters(value) {
3246
+ this._filters = value;
3247
+ this.filtersChange.emit(this._filters);
3248
+ }
3249
+ filtersChange = new EventEmitter();
3250
+ selectedKeys = [];
3251
+ ngOnChanges() {
3252
+ this.filteredAttributeList = [...this.availableAttributes];
3253
+ this.buildAttributeTypeMap();
3254
+ // Ensure useVariable flag is set correctly for filters with variable values
3255
+ this.detectVariableFilters();
3256
+ }
3257
+ /**
3258
+ * Detects filters that contain variable references and sets useVariable flag.
3259
+ * This ensures proper display when filters are loaded from saved configuration.
3260
+ */
3261
+ detectVariableFilters() {
3262
+ for (const filter of this._filters) {
3263
+ // Always check if the value is a variable reference and update the flag
3264
+ const isVariable = this.isVariableValue(filter.comparisonValue);
3265
+ if (isVariable && !filter.useVariable) {
3266
+ filter.useVariable = true;
3267
+ }
3268
+ }
3269
+ }
3270
+ buildAttributeTypeMap() {
3271
+ this.attributeTypeMap.clear();
3272
+ for (const attr of this.availableAttributes) {
3273
+ this.attributeTypeMap.set(attr.attributePath, attr.attributeValueType);
3274
+ }
3275
+ }
3276
+ addFilter() {
3277
+ const newFilter = {
3278
+ id: this.nextId++,
3279
+ attributePath: '',
3280
+ operator: FieldFilterOperatorsDto.EqualsDto,
3281
+ comparisonValue: ''
3282
+ };
3283
+ this._filters = [...this._filters, newFilter];
3284
+ this.filtersChange.emit(this._filters);
3285
+ }
3286
+ removeFilter(filter) {
3287
+ this._filters = this._filters.filter(f => f.id !== filter.id);
3288
+ this.selectedKeys = this.selectedKeys.filter(k => k !== filter.id);
3289
+ this.filtersChange.emit(this._filters);
3290
+ }
3291
+ removeSelected() {
3292
+ this._filters = this._filters.filter(f => !this.selectedKeys.includes(f.id));
3293
+ this.selectedKeys = [];
3294
+ this.filtersChange.emit(this._filters);
3295
+ }
3296
+ onFilterChange() {
3297
+ this.filtersChange.emit(this._filters);
3298
+ }
3299
+ onAttributeChange(filter, attributePath) {
3300
+ filter.attributePath = attributePath;
3301
+ // Reset comparison value when attribute changes
3302
+ filter.comparisonValue = '';
3303
+ this.onFilterChange();
3304
+ }
3305
+ onOperatorChange(filter) {
3306
+ if (!filter.comparisonValue) {
3307
+ this.onFilterChange();
3308
+ return;
3309
+ }
3310
+ const currentValue = String(filter.comparisonValue);
3311
+ if (this.isArrayOperator(filter.operator)) {
3312
+ const valuesToProcess = this.extractArrayContent(currentValue);
3313
+ const cleanedValues = this.parseAndCleanArrayValues(valuesToProcess);
3314
+ filter.comparisonValue = `[${cleanedValues.join(', ')}]`;
3315
+ }
3316
+ else {
3317
+ const scalarValue = this.extractArrayContent(currentValue);
3318
+ if (scalarValue.includes(',')) {
3319
+ filter.comparisonValue = scalarValue.split(',')[0].trim();
3320
+ }
3321
+ else {
3322
+ filter.comparisonValue = scalarValue.trim();
3323
+ }
3324
+ }
3325
+ this.onFilterChange();
3326
+ }
3327
+ onComparisonValueChange(filter, inputValue) {
3328
+ if (inputValue === null) {
3329
+ filter.comparisonValue = '';
3330
+ return;
3331
+ }
3332
+ if (this.isArrayOperator(filter.operator)) {
3333
+ const cleanedValues = this.parseAndCleanArrayValues(inputValue);
3334
+ filter.comparisonValue = `[${cleanedValues.join(', ')}]`;
3335
+ }
3336
+ else {
3337
+ filter.comparisonValue = this.extractArrayContent(inputValue);
3338
+ }
3339
+ this.onFilterChange();
3340
+ }
3341
+ onComparisonValueBlur(filter) {
3342
+ if (!filter.comparisonValue)
3343
+ return;
3344
+ const value = String(filter.comparisonValue);
3345
+ if (this.isArrayOperator(filter.operator)) {
3346
+ const cleanedValues = this.parseAndCleanArrayValues(value);
3347
+ filter.comparisonValue = `[${cleanedValues.join(', ')}]`;
3348
+ }
3349
+ else {
3350
+ filter.comparisonValue = this.extractArrayContent(value).trim();
3351
+ }
3352
+ this.onFilterChange();
3353
+ }
3354
+ onBooleanValueChange(filter, value) {
3355
+ filter.comparisonValue = value;
3356
+ this.onFilterChange();
3357
+ }
3358
+ onNumericValueChange(filter, value) {
3359
+ filter.comparisonValue = value !== null ? String(value) : '';
3360
+ this.onFilterChange();
3361
+ }
3362
+ onDateTimeValueChange(filter, value) {
3363
+ filter.comparisonValue = value !== null ? value.toISOString() : '';
3364
+ this.onFilterChange();
3365
+ }
3366
+ getDisplayValue(filter) {
3367
+ if (!filter.comparisonValue) {
3368
+ return '';
3369
+ }
3370
+ const value = String(filter.comparisonValue);
3371
+ if (this.isArrayOperator(filter.operator) && this.isArrayValue(value)) {
3372
+ return this.extractArrayContent(value);
3373
+ }
3374
+ return value;
3375
+ }
3376
+ getBooleanValue(filter) {
3377
+ const value = filter.comparisonValue;
3378
+ if (value === 'true' || value === true)
3379
+ return 'true';
3380
+ if (value === 'false' || value === false)
3381
+ return 'false';
3382
+ return '';
3383
+ }
3384
+ getNumericValue(filter) {
3385
+ const value = filter.comparisonValue;
3386
+ if (value === null || value === undefined || value === '')
3387
+ return 0;
3388
+ const num = Number(value);
3389
+ return isNaN(num) ? 0 : num;
3390
+ }
3391
+ getDateTimeValue(filter) {
3392
+ const value = filter.comparisonValue;
3393
+ if (!value)
3394
+ return null;
3395
+ const date = new Date(String(value));
3396
+ return isNaN(date.getTime()) ? null : date;
3397
+ }
3398
+ getInputType(filter) {
3399
+ const attrType = this.attributeTypeMap.get(filter.attributePath);
3400
+ if (!attrType)
3401
+ return 'text';
3402
+ // Normalize to uppercase for case-insensitive comparison
3403
+ const normalizedType = String(attrType).toUpperCase();
3404
+ // Boolean types
3405
+ if (normalizedType === 'BOOLEAN') {
3406
+ return 'boolean';
3407
+ }
3408
+ // Numeric types
3409
+ if (normalizedType === 'INT' || normalizedType === 'INTEGER' ||
3410
+ normalizedType === 'INT_64' || normalizedType === 'INTEGER_64' ||
3411
+ normalizedType === 'DOUBLE') {
3412
+ return 'number';
3413
+ }
3414
+ // DateTime types
3415
+ if (normalizedType === 'DATE_TIME' || normalizedType === 'DATE_TIME_OFFSET' ||
3416
+ normalizedType === 'DATETIME' || normalizedType === 'DATETIMEOFFSET') {
3417
+ return 'datetime';
3418
+ }
3419
+ return 'text';
3420
+ }
3421
+ getDecimals(filter) {
3422
+ const attrType = this.attributeTypeMap.get(filter.attributePath);
3423
+ return attrType === AttributeValueTypeDto$1.DoubleDto ? 4 : 0;
3424
+ }
3425
+ getNumberFormat(filter) {
3426
+ const attrType = this.attributeTypeMap.get(filter.attributePath);
3427
+ return attrType === AttributeValueTypeDto$1.DoubleDto ? 'n4' : 'n0';
3428
+ }
3429
+ getValuePlaceholder(filter) {
3430
+ if (this.isArrayOperator(filter.operator)) {
3431
+ return 'Values: A,B,C or "value,with,comma"';
3432
+ }
3433
+ return 'Enter value';
3434
+ }
3435
+ isArrayOperator(operator) {
3436
+ return this.arrayOperators.includes(operator);
3437
+ }
3438
+ isSelected(filter) {
3439
+ return this.selectedKeys.includes(filter.id);
3440
+ }
3441
+ toggleSelection(filter) {
3442
+ if (this.isSelected(filter)) {
3443
+ this.selectedKeys = this.selectedKeys.filter(k => k !== filter.id);
3444
+ }
3445
+ else {
3446
+ this.selectedKeys = [...this.selectedKeys, filter.id];
3447
+ }
3448
+ }
3449
+ isAllSelected() {
3450
+ return this._filters.length > 0 && this.selectedKeys.length === this._filters.length;
3451
+ }
3452
+ isIndeterminate() {
3453
+ return this.selectedKeys.length > 0 && this.selectedKeys.length < this._filters.length;
3454
+ }
3455
+ toggleSelectAll(event) {
3456
+ const checkbox = event.target;
3457
+ if (checkbox.checked) {
3458
+ this.selectedKeys = this._filters.map(f => f.id);
3459
+ }
3460
+ else {
3461
+ this.selectedKeys = [];
3462
+ }
3463
+ }
3464
+ onAttributeFilterChange(filter) {
3465
+ this.filteredAttributeList = this.availableAttributes.filter(attr => attr.attributePath.toLowerCase().includes(filter.toLowerCase()));
3466
+ }
3467
+ // ============================================================================
3468
+ // Variable Mode Methods
3469
+ // ============================================================================
3470
+ /**
3471
+ * Toggles between literal value and variable mode for a filter.
3472
+ */
3473
+ toggleVariableMode(filter) {
3474
+ filter.useVariable = !filter.useVariable;
3475
+ // Clear the value when switching modes
3476
+ filter.comparisonValue = '';
3477
+ this.onFilterChange();
3478
+ }
3479
+ /**
3480
+ * Gets the selected variable object for a filter that uses variable mode.
3481
+ */
3482
+ getSelectedVariable(filter) {
3483
+ if (!filter.comparisonValue)
3484
+ return null;
3485
+ const varName = this.extractVariableName(String(filter.comparisonValue));
3486
+ if (!varName)
3487
+ return null;
3488
+ return this.availableVariables.find(v => v.name === varName) || null;
3489
+ }
3490
+ /**
3491
+ * Handles variable selection from the dropdown.
3492
+ */
3493
+ onVariableSelected(filter, variable) {
3494
+ if (variable) {
3495
+ // Store as ${variableName} format
3496
+ filter.comparisonValue = `\${${variable.name}}`;
3497
+ }
3498
+ else {
3499
+ filter.comparisonValue = '';
3500
+ }
3501
+ this.onFilterChange();
3502
+ }
3503
+ /**
3504
+ * Extracts variable name from ${variableName} or $variableName format.
3505
+ */
3506
+ extractVariableName(value) {
3507
+ // Match ${varName} format
3508
+ const bracketMatch = value.match(/^\$\{([^}]+)\}$/);
3509
+ if (bracketMatch) {
3510
+ return bracketMatch[1];
3511
+ }
3512
+ // Match $varName format (without braces)
3513
+ const simpleMatch = value.match(/^\$([a-zA-Z_][a-zA-Z0-9_]*)$/);
3514
+ if (simpleMatch) {
3515
+ return simpleMatch[1];
3516
+ }
3517
+ return null;
3518
+ }
3519
+ /**
3520
+ * Checks if a filter value is a variable reference.
3521
+ */
3522
+ isVariableValue(value) {
3523
+ if (!value || typeof value !== 'string')
3524
+ return false;
3525
+ return /^\$\{[^}]+\}$/.test(value) || /^\$[a-zA-Z_][a-zA-Z0-9_]*$/.test(value);
3526
+ }
3527
+ /**
3528
+ * Formats a variable name for display (e.g., "myVar" -> "${myVar}").
3529
+ */
3530
+ formatVariableDisplay(name) {
3531
+ return '$' + '{' + name + '}';
3532
+ }
3533
+ getFieldFilters() {
3534
+ return this._filters
3535
+ .filter(f => f.attributePath && f.attributePath.trim() !== '')
3536
+ .map(f => ({
3537
+ attributePath: f.attributePath,
3538
+ operator: f.operator,
3539
+ comparisonValue: f.comparisonValue
3540
+ }));
3541
+ }
3542
+ setFieldFilters(filters) {
3543
+ this._filters = filters.map(f => ({
3544
+ ...f,
3545
+ id: this.nextId++,
3546
+ // Detect if the value is a variable reference
3547
+ useVariable: this.isVariableValue(f.comparisonValue)
3548
+ }));
3549
+ this.filtersChange.emit(this._filters);
3550
+ }
3551
+ clear() {
3552
+ this._filters = [];
3553
+ this.selectedKeys = [];
3554
+ this.filtersChange.emit(this._filters);
3555
+ }
3556
+ isArrayValue(value) {
3557
+ return value.trim().startsWith('[') && value.trim().endsWith(']');
3558
+ }
3559
+ extractArrayContent(value) {
3560
+ const trimmed = value.trim();
3561
+ if (this.isArrayValue(trimmed)) {
3562
+ return trimmed.slice(1, -1);
3563
+ }
3564
+ return trimmed;
3565
+ }
3566
+ parseAndCleanArrayValues(inputValue) {
3567
+ const values = [];
3568
+ let current = '';
3569
+ let inQuotes = false;
3570
+ let quoteChar = '';
3571
+ for (const char of inputValue) {
3572
+ if (!inQuotes && (char === '"' || char === "'")) {
3573
+ inQuotes = true;
3574
+ quoteChar = char;
3575
+ current += char;
3576
+ }
3577
+ else if (inQuotes && char === quoteChar) {
3578
+ inQuotes = false;
3579
+ current += char;
3580
+ quoteChar = '';
3581
+ }
3582
+ else if (!inQuotes && char === ',') {
3583
+ const trimmed = current.trim();
3584
+ if (trimmed.length > 0) {
3585
+ values.push(trimmed);
3586
+ }
3587
+ current = '';
3588
+ }
3589
+ else {
3590
+ current += char;
3591
+ }
3592
+ }
3593
+ const trimmed = current.trim();
3594
+ if (trimmed.length > 0) {
3595
+ values.push(trimmed);
3596
+ }
3597
+ return values;
3598
+ }
3599
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: FieldFilterEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3600
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: FieldFilterEditorComponent, isStandalone: true, selector: "mm-field-filter-editor", inputs: { availableAttributes: "availableAttributes", enableVariables: "enableVariables", availableVariables: "availableVariables", filters: "filters" }, outputs: { filtersChange: "filtersChange" }, usesOnChanges: true, ngImport: i0, template: `
3601
+ <div class="field-filter-editor">
3602
+ <div class="toolbar">
3603
+ <button
3604
+ kendoButton
3605
+ [svgIcon]="plusIcon"
3606
+ themeColor="primary"
3607
+ (click)="addFilter()"
3608
+ title="Add filter">
3609
+ Add Filter
3610
+ </button>
3611
+ <button
3612
+ kendoButton
3613
+ [svgIcon]="trashIcon"
3614
+ themeColor="error"
3615
+ (click)="removeSelected()"
3616
+ [disabled]="selectedKeys.length === 0"
3617
+ title="Remove selected filters">
3618
+ Remove Selected
3619
+ </button>
3620
+ </div>
3621
+
3622
+ <kendo-grid
3623
+ [data]="filters"
3624
+ [selectable]="{ mode: 'multiple', enabled: true }"
3625
+ [kendoGridSelectBy]="'id'"
3626
+ [(selectedKeys)]="selectedKeys"
3627
+ class="filter-grid">
3628
+
3629
+ <kendo-grid-column title="" [width]="50" [class]="'checkbox-column'">
3630
+ <ng-template kendoGridHeaderTemplate>
3631
+ <input
3632
+ type="checkbox"
3633
+ [checked]="isAllSelected()"
3634
+ [indeterminate]="isIndeterminate()"
3635
+ (change)="toggleSelectAll($event)" />
3636
+ </ng-template>
3637
+ <ng-template kendoGridCellTemplate let-dataItem>
3638
+ <input
3639
+ type="checkbox"
3640
+ [checked]="isSelected(dataItem)"
3641
+ (change)="toggleSelection(dataItem)" />
3642
+ </ng-template>
3643
+ </kendo-grid-column>
3644
+
3645
+ <kendo-grid-column field="attributePath" title="Attribute Path" [width]="280">
3646
+ <ng-template kendoGridCellTemplate let-dataItem>
3647
+ @if (availableAttributes.length > 0) {
3648
+ <kendo-dropdownlist
3649
+ [data]="filteredAttributeList"
3650
+ [(ngModel)]="dataItem.attributePath"
3651
+ (valueChange)="onAttributeChange(dataItem, $event)"
3652
+ [textField]="'attributePath'"
3653
+ [valueField]="'attributePath'"
3654
+ [valuePrimitive]="true"
3655
+ [filterable]="true"
3656
+ (filterChange)="onAttributeFilterChange($event)"
3657
+ [listHeight]="300"
3658
+ [popupSettings]="{ appendTo: 'root', animate: true }"
3659
+ class="attribute-dropdown">
3660
+ <ng-template kendoDropDownListItemTemplate let-item>
3661
+ <div class="attribute-item">
3662
+ <span class="attribute-path">{{ item.attributePath }}</span>
3663
+ <span class="attribute-type">{{ item.attributeValueType }}</span>
3664
+ </div>
3665
+ </ng-template>
3666
+ </kendo-dropdownlist>
3667
+ } @else {
3668
+ <kendo-textbox
3669
+ [(ngModel)]="dataItem.attributePath"
3670
+ (valueChange)="onFilterChange()"
3671
+ placeholder="Enter attribute path"
3672
+ class="attribute-input">
3673
+ </kendo-textbox>
3674
+ }
3675
+ </ng-template>
3676
+ </kendo-grid-column>
3677
+
3678
+ <kendo-grid-column field="operator" title="Operator" [width]="180">
3679
+ <ng-template kendoGridCellTemplate let-dataItem>
3680
+ <kendo-dropdownlist
3681
+ [data]="operators"
3682
+ [(ngModel)]="dataItem.operator"
3683
+ (valueChange)="onOperatorChange(dataItem)"
3684
+ [textField]="'label'"
3685
+ [valueField]="'value'"
3686
+ [valuePrimitive]="true"
3687
+ [listHeight]="300"
3688
+ [popupSettings]="{ appendTo: 'root', animate: true }"
3689
+ class="operator-dropdown">
3690
+ </kendo-dropdownlist>
3691
+ </ng-template>
3692
+ </kendo-grid-column>
3693
+
3694
+ <kendo-grid-column field="comparisonValue" title="Value" [width]="280">
3695
+ <ng-template kendoGridCellTemplate let-dataItem>
3696
+ <div class="value-cell">
3697
+ @if (dataItem.useVariable && enableVariables) {
3698
+ <!-- Variable mode: show variable dropdown -->
3699
+ <kendo-dropdownlist
3700
+ [data]="availableVariables"
3701
+ [value]="getSelectedVariable(dataItem)"
3702
+ (valueChange)="onVariableSelected(dataItem, $event)"
3703
+ [textField]="'label'"
3704
+ [valueField]="'name'"
3705
+ [valuePrimitive]="false"
3706
+ [popupSettings]="{ appendTo: 'root', animate: true }"
3707
+ placeholder="Select variable..."
3708
+ class="value-input variable-dropdown">
3709
+ <ng-template kendoDropDownListItemTemplate let-item>
3710
+ <div class="variable-item">
3711
+ <span class="variable-name">{{ formatVariableDisplay(item.name) }}</span>
3712
+ @if (item.label && item.label !== item.name) {
3713
+ <span class="variable-label">{{ item.label }}</span>
3714
+ }
3715
+ </div>
3716
+ </ng-template>
3717
+ <ng-template kendoDropDownListValueTemplate let-item>
3718
+ @if (item) {
3719
+ <span class="variable-value">{{ formatVariableDisplay(item.name) }}</span>
3720
+ } @else {
3721
+ <span class="variable-placeholder">Select variable...</span>
3722
+ }
3723
+ </ng-template>
3724
+ </kendo-dropdownlist>
3725
+ } @else {
3726
+ <!-- Literal value mode: show type-specific input -->
3727
+ @switch (getInputType(dataItem)) {
3728
+ @case ('boolean') {
3729
+ @if (isArrayOperator(dataItem.operator)) {
3730
+ <kendo-textbox
3731
+ [value]="getDisplayValue(dataItem)"
3732
+ (valueChange)="onComparisonValueChange(dataItem, $event)"
3733
+ placeholder="true, false"
3734
+ class="value-input">
3735
+ </kendo-textbox>
3736
+ } @else {
3737
+ <kendo-dropdownlist
3738
+ [data]="booleanOptions"
3739
+ [value]="getBooleanValue(dataItem)"
3740
+ (valueChange)="onBooleanValueChange(dataItem, $event)"
3741
+ [popupSettings]="{ appendTo: 'root', animate: true }"
3742
+ class="value-input">
3743
+ </kendo-dropdownlist>
3744
+ }
3745
+ }
3746
+ @case ('number') {
3747
+ @if (isArrayOperator(dataItem.operator)) {
3748
+ <kendo-textbox
3749
+ [value]="getDisplayValue(dataItem)"
3750
+ (valueChange)="onComparisonValueChange(dataItem, $event)"
3751
+ placeholder="1, 2, 3"
3752
+ class="value-input">
3753
+ </kendo-textbox>
3754
+ } @else {
3755
+ <kendo-numerictextbox
3756
+ [value]="getNumericValue(dataItem)"
3757
+ (valueChange)="onNumericValueChange(dataItem, $event)"
3758
+ [decimals]="getDecimals(dataItem)"
3759
+ [format]="getNumberFormat(dataItem)"
3760
+ class="value-input">
3761
+ </kendo-numerictextbox>
3762
+ }
3763
+ }
3764
+ @case ('datetime') {
3765
+ @if (isArrayOperator(dataItem.operator)) {
3766
+ <kendo-textbox
3767
+ [value]="getDisplayValue(dataItem)"
3768
+ (valueChange)="onComparisonValueChange(dataItem, $event)"
3769
+ placeholder="2024-01-15, 2024-02-20"
3770
+ class="value-input">
3771
+ </kendo-textbox>
3772
+ } @else {
3773
+ <kendo-datetimepicker
3774
+ [value]="getDateTimeValue(dataItem)"
3775
+ (valueChange)="onDateTimeValueChange(dataItem, $event)"
3776
+ [popupSettings]="{ appendTo: 'root', animate: true }"
3777
+ class="value-input">
3778
+ </kendo-datetimepicker>
3779
+ }
3780
+ }
3781
+ @default {
3782
+ <kendo-textbox
3783
+ [value]="getDisplayValue(dataItem)"
3784
+ (valueChange)="onComparisonValueChange(dataItem, $event)"
3785
+ (blur)="onComparisonValueBlur(dataItem)"
3786
+ [placeholder]="getValuePlaceholder(dataItem)"
3787
+ class="value-input">
3788
+ </kendo-textbox>
3789
+ }
3790
+ }
3791
+ }
3792
+ <!-- Variable toggle button -->
3793
+ @if (enableVariables && availableVariables.length > 0) {
3794
+ <button
3795
+ kendoButton
3796
+ [svgIcon]="dollarIcon"
3797
+ fillMode="flat"
3798
+ [themeColor]="dataItem.useVariable ? 'primary' : 'base'"
3799
+ (click)="toggleVariableMode(dataItem)"
3800
+ [title]="dataItem.useVariable ? 'Switch to literal value' : 'Use variable'"
3801
+ class="variable-toggle">
3802
+ </button>
3803
+ }
3804
+ </div>
3805
+ </ng-template>
3806
+ </kendo-grid-column>
3807
+
3808
+ <kendo-grid-column title="" [width]="60">
3809
+ <ng-template kendoGridCellTemplate let-dataItem>
3810
+ <button
3811
+ kendoButton
3812
+ [svgIcon]="minusIcon"
3813
+ fillMode="flat"
3814
+ themeColor="error"
3815
+ (click)="removeFilter(dataItem)"
3816
+ title="Remove this filter">
3817
+ </button>
3818
+ </ng-template>
3819
+ </kendo-grid-column>
3820
+
3821
+ </kendo-grid>
3822
+
3823
+ @if (filters.length === 0) {
3824
+ <div class="empty-state">
3825
+ <p>No filters defined. Click "Add Filter" to create a new filter.</p>
3826
+ </div>
3827
+ }
3828
+ </div>
3829
+ `, isInline: true, styles: [".field-filter-editor{display:flex;flex-direction:column;gap:10px}.toolbar{display:flex;gap:10px;margin-bottom:5px}.filter-grid{border:1px solid #d5d5d5}.filter-grid ::ng-deep .k-grid-header .k-header{font-weight:600}.checkbox-column{text-align:center}.attribute-dropdown,.operator-dropdown,.attribute-input,.value-input{width:100%}.attribute-item{display:flex;justify-content:space-between;align-items:center;width:100%}.attribute-path{flex:1}.attribute-type{font-size:11px;color:#888;margin-left:8px;padding:2px 6px;background:#f0f0f0;border-radius:3px}.empty-state{padding:40px;text-align:center;border:1px dashed;border-radius:8px;font-family:Montserrat,sans-serif;font-size:.9rem}.empty-state p{margin:0}.value-cell{display:flex;gap:4px;align-items:center}.value-cell .value-input{flex:1}.variable-toggle{flex-shrink:0}.variable-item{display:flex;flex-direction:column;gap:2px}.variable-name{font-family:monospace;font-weight:500}.variable-label{font-size:11px;color:var(--kendo-color-subtle, #888)}.variable-value{font-family:monospace;color:var(--kendo-color-primary, #0d6efd)}.variable-placeholder{color:var(--kendo-color-subtle, #888)}.variable-dropdown ::ng-deep .k-input-value-text{font-family:monospace}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: GridModule }, { kind: "component", type: i2$1.GridComponent, selector: "kendo-grid", inputs: ["data", "pageSize", "height", "rowHeight", "adaptiveMode", "detailRowHeight", "skip", "scrollable", "selectable", "sort", "size", "trackBy", "filter", "group", "virtualColumns", "filterable", "sortable", "pageable", "groupable", "gridResizable", "rowReorderable", "navigable", "autoSize", "rowClass", "rowSticky", "rowSelected", "isRowSelectable", "cellSelected", "resizable", "reorderable", "loading", "columnMenu", "hideHeader", "showInactiveTools", "isDetailExpanded", "isGroupExpanded", "dataLayoutMode"], outputs: ["filterChange", "pageChange", "groupChange", "sortChange", "selectionChange", "rowReorder", "dataStateChange", "gridStateChange", "groupExpand", "groupCollapse", "detailExpand", "detailCollapse", "edit", "cancel", "save", "remove", "add", "cellClose", "cellClick", "pdfExport", "excelExport", "columnResize", "columnReorder", "columnVisibilityChange", "columnLockedChange", "columnStickyChange", "scrollBottom", "contentScroll"], exportAs: ["kendoGrid"] }, { kind: "directive", type: i2$1.SelectionDirective, selector: "[kendoGridSelectBy]" }, { kind: "component", type: i2$1.ColumnComponent, selector: "kendo-grid-column", inputs: ["field", "format", "sortable", "groupable", "editor", "filter", "filterVariant", "filterable", "editable"] }, { kind: "directive", type: i2$1.CellTemplateDirective, selector: "[kendoGridCellTemplate]" }, { kind: "directive", type: i2$1.HeaderTemplateDirective, selector: "[kendoGridHeaderTemplate]" }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i3.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i3$1.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i3$1.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"] }, { kind: "directive", type: i3$1.ValueTemplateDirective, selector: "[kendoDropDownListValueTemplate],[kendoDropDownTreeValueTemplate]" }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i4.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i4.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: DateInputsModule }, { kind: "component", type: i6.DateTimePickerComponent, selector: "kendo-datetimepicker", inputs: ["focusableId", "weekDaysFormat", "showOtherMonthDays", "value", "format", "twoDigitYearMax", "tabindex", "disabledDates", "popupSettings", "adaptiveTitle", "adaptiveSubtitle", "disabled", "readonly", "readOnlyInput", "cancelButton", "formatPlaceholder", "placeholder", "steps", "focusedDate", "calendarType", "animateCalendarNavigation", "weekNumber", "min", "max", "rangeValidation", "disabledDatesValidation", "incompleteDateValidation", "autoCorrectParts", "autoSwitchParts", "autoSwitchKeys", "enableMouseWheel", "allowCaretMode", "clearButton", "autoFill", "adaptiveMode", "inputAttributes", "defaultTab", "size", "rounded", "fillMode", "headerTemplate", "footerTemplate", "footer"], outputs: ["valueChange", "open", "close", "focus", "blur", "escape"], exportAs: ["kendo-datetimepicker"] }, { kind: "ngmodule", type: IconsModule }, { kind: "ngmodule", type: PopupModule }, { kind: "ngmodule", type: IntlModule }] });
3830
+ }
3831
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: FieldFilterEditorComponent, decorators: [{
3832
+ type: Component,
3833
+ args: [{ selector: 'mm-field-filter-editor', standalone: true, imports: [
3834
+ CommonModule,
3835
+ FormsModule,
3836
+ GridModule,
3837
+ ButtonsModule,
3838
+ DropDownsModule,
3839
+ InputsModule,
3840
+ DateInputsModule,
3841
+ IconsModule,
3842
+ PopupModule,
3843
+ IntlModule
3844
+ ], template: `
3845
+ <div class="field-filter-editor">
3846
+ <div class="toolbar">
3847
+ <button
3848
+ kendoButton
3849
+ [svgIcon]="plusIcon"
3850
+ themeColor="primary"
3851
+ (click)="addFilter()"
3852
+ title="Add filter">
3853
+ Add Filter
3854
+ </button>
3855
+ <button
3856
+ kendoButton
3857
+ [svgIcon]="trashIcon"
3858
+ themeColor="error"
3859
+ (click)="removeSelected()"
3860
+ [disabled]="selectedKeys.length === 0"
3861
+ title="Remove selected filters">
3862
+ Remove Selected
3863
+ </button>
3864
+ </div>
3865
+
3866
+ <kendo-grid
3867
+ [data]="filters"
3868
+ [selectable]="{ mode: 'multiple', enabled: true }"
3869
+ [kendoGridSelectBy]="'id'"
3870
+ [(selectedKeys)]="selectedKeys"
3871
+ class="filter-grid">
3872
+
3873
+ <kendo-grid-column title="" [width]="50" [class]="'checkbox-column'">
3874
+ <ng-template kendoGridHeaderTemplate>
3875
+ <input
3876
+ type="checkbox"
3877
+ [checked]="isAllSelected()"
3878
+ [indeterminate]="isIndeterminate()"
3879
+ (change)="toggleSelectAll($event)" />
3880
+ </ng-template>
3881
+ <ng-template kendoGridCellTemplate let-dataItem>
3882
+ <input
3883
+ type="checkbox"
3884
+ [checked]="isSelected(dataItem)"
3885
+ (change)="toggleSelection(dataItem)" />
3886
+ </ng-template>
3887
+ </kendo-grid-column>
3888
+
3889
+ <kendo-grid-column field="attributePath" title="Attribute Path" [width]="280">
3890
+ <ng-template kendoGridCellTemplate let-dataItem>
3891
+ @if (availableAttributes.length > 0) {
3892
+ <kendo-dropdownlist
3893
+ [data]="filteredAttributeList"
3894
+ [(ngModel)]="dataItem.attributePath"
3895
+ (valueChange)="onAttributeChange(dataItem, $event)"
3896
+ [textField]="'attributePath'"
3897
+ [valueField]="'attributePath'"
3898
+ [valuePrimitive]="true"
3899
+ [filterable]="true"
3900
+ (filterChange)="onAttributeFilterChange($event)"
3901
+ [listHeight]="300"
3902
+ [popupSettings]="{ appendTo: 'root', animate: true }"
3903
+ class="attribute-dropdown">
3904
+ <ng-template kendoDropDownListItemTemplate let-item>
3905
+ <div class="attribute-item">
3906
+ <span class="attribute-path">{{ item.attributePath }}</span>
3907
+ <span class="attribute-type">{{ item.attributeValueType }}</span>
3908
+ </div>
3909
+ </ng-template>
3910
+ </kendo-dropdownlist>
3911
+ } @else {
3912
+ <kendo-textbox
3913
+ [(ngModel)]="dataItem.attributePath"
3914
+ (valueChange)="onFilterChange()"
3915
+ placeholder="Enter attribute path"
3916
+ class="attribute-input">
3917
+ </kendo-textbox>
3918
+ }
3919
+ </ng-template>
3920
+ </kendo-grid-column>
3921
+
3922
+ <kendo-grid-column field="operator" title="Operator" [width]="180">
3923
+ <ng-template kendoGridCellTemplate let-dataItem>
3924
+ <kendo-dropdownlist
3925
+ [data]="operators"
3926
+ [(ngModel)]="dataItem.operator"
3927
+ (valueChange)="onOperatorChange(dataItem)"
3928
+ [textField]="'label'"
3929
+ [valueField]="'value'"
3930
+ [valuePrimitive]="true"
3931
+ [listHeight]="300"
3932
+ [popupSettings]="{ appendTo: 'root', animate: true }"
3933
+ class="operator-dropdown">
3934
+ </kendo-dropdownlist>
3935
+ </ng-template>
3936
+ </kendo-grid-column>
3937
+
3938
+ <kendo-grid-column field="comparisonValue" title="Value" [width]="280">
3939
+ <ng-template kendoGridCellTemplate let-dataItem>
3940
+ <div class="value-cell">
3941
+ @if (dataItem.useVariable && enableVariables) {
3942
+ <!-- Variable mode: show variable dropdown -->
3943
+ <kendo-dropdownlist
3944
+ [data]="availableVariables"
3945
+ [value]="getSelectedVariable(dataItem)"
3946
+ (valueChange)="onVariableSelected(dataItem, $event)"
3947
+ [textField]="'label'"
3948
+ [valueField]="'name'"
3949
+ [valuePrimitive]="false"
3950
+ [popupSettings]="{ appendTo: 'root', animate: true }"
3951
+ placeholder="Select variable..."
3952
+ class="value-input variable-dropdown">
3953
+ <ng-template kendoDropDownListItemTemplate let-item>
3954
+ <div class="variable-item">
3955
+ <span class="variable-name">{{ formatVariableDisplay(item.name) }}</span>
3956
+ @if (item.label && item.label !== item.name) {
3957
+ <span class="variable-label">{{ item.label }}</span>
3958
+ }
3959
+ </div>
3960
+ </ng-template>
3961
+ <ng-template kendoDropDownListValueTemplate let-item>
3962
+ @if (item) {
3963
+ <span class="variable-value">{{ formatVariableDisplay(item.name) }}</span>
3964
+ } @else {
3965
+ <span class="variable-placeholder">Select variable...</span>
3966
+ }
3967
+ </ng-template>
3968
+ </kendo-dropdownlist>
3969
+ } @else {
3970
+ <!-- Literal value mode: show type-specific input -->
3971
+ @switch (getInputType(dataItem)) {
3972
+ @case ('boolean') {
3973
+ @if (isArrayOperator(dataItem.operator)) {
3974
+ <kendo-textbox
3975
+ [value]="getDisplayValue(dataItem)"
3976
+ (valueChange)="onComparisonValueChange(dataItem, $event)"
3977
+ placeholder="true, false"
3978
+ class="value-input">
3979
+ </kendo-textbox>
3980
+ } @else {
3981
+ <kendo-dropdownlist
3982
+ [data]="booleanOptions"
3983
+ [value]="getBooleanValue(dataItem)"
3984
+ (valueChange)="onBooleanValueChange(dataItem, $event)"
3985
+ [popupSettings]="{ appendTo: 'root', animate: true }"
3986
+ class="value-input">
3987
+ </kendo-dropdownlist>
3988
+ }
3989
+ }
3990
+ @case ('number') {
3991
+ @if (isArrayOperator(dataItem.operator)) {
3992
+ <kendo-textbox
3993
+ [value]="getDisplayValue(dataItem)"
3994
+ (valueChange)="onComparisonValueChange(dataItem, $event)"
3995
+ placeholder="1, 2, 3"
3996
+ class="value-input">
3997
+ </kendo-textbox>
3998
+ } @else {
3999
+ <kendo-numerictextbox
4000
+ [value]="getNumericValue(dataItem)"
4001
+ (valueChange)="onNumericValueChange(dataItem, $event)"
4002
+ [decimals]="getDecimals(dataItem)"
4003
+ [format]="getNumberFormat(dataItem)"
4004
+ class="value-input">
4005
+ </kendo-numerictextbox>
4006
+ }
4007
+ }
4008
+ @case ('datetime') {
4009
+ @if (isArrayOperator(dataItem.operator)) {
4010
+ <kendo-textbox
4011
+ [value]="getDisplayValue(dataItem)"
4012
+ (valueChange)="onComparisonValueChange(dataItem, $event)"
4013
+ placeholder="2024-01-15, 2024-02-20"
4014
+ class="value-input">
4015
+ </kendo-textbox>
4016
+ } @else {
4017
+ <kendo-datetimepicker
4018
+ [value]="getDateTimeValue(dataItem)"
4019
+ (valueChange)="onDateTimeValueChange(dataItem, $event)"
4020
+ [popupSettings]="{ appendTo: 'root', animate: true }"
4021
+ class="value-input">
4022
+ </kendo-datetimepicker>
4023
+ }
4024
+ }
4025
+ @default {
4026
+ <kendo-textbox
4027
+ [value]="getDisplayValue(dataItem)"
4028
+ (valueChange)="onComparisonValueChange(dataItem, $event)"
4029
+ (blur)="onComparisonValueBlur(dataItem)"
4030
+ [placeholder]="getValuePlaceholder(dataItem)"
4031
+ class="value-input">
4032
+ </kendo-textbox>
4033
+ }
4034
+ }
4035
+ }
4036
+ <!-- Variable toggle button -->
4037
+ @if (enableVariables && availableVariables.length > 0) {
4038
+ <button
4039
+ kendoButton
4040
+ [svgIcon]="dollarIcon"
4041
+ fillMode="flat"
4042
+ [themeColor]="dataItem.useVariable ? 'primary' : 'base'"
4043
+ (click)="toggleVariableMode(dataItem)"
4044
+ [title]="dataItem.useVariable ? 'Switch to literal value' : 'Use variable'"
4045
+ class="variable-toggle">
4046
+ </button>
4047
+ }
4048
+ </div>
4049
+ </ng-template>
4050
+ </kendo-grid-column>
4051
+
4052
+ <kendo-grid-column title="" [width]="60">
4053
+ <ng-template kendoGridCellTemplate let-dataItem>
4054
+ <button
4055
+ kendoButton
4056
+ [svgIcon]="minusIcon"
4057
+ fillMode="flat"
4058
+ themeColor="error"
4059
+ (click)="removeFilter(dataItem)"
4060
+ title="Remove this filter">
4061
+ </button>
4062
+ </ng-template>
4063
+ </kendo-grid-column>
4064
+
4065
+ </kendo-grid>
4066
+
4067
+ @if (filters.length === 0) {
4068
+ <div class="empty-state">
4069
+ <p>No filters defined. Click "Add Filter" to create a new filter.</p>
4070
+ </div>
4071
+ }
4072
+ </div>
4073
+ `, styles: [".field-filter-editor{display:flex;flex-direction:column;gap:10px}.toolbar{display:flex;gap:10px;margin-bottom:5px}.filter-grid{border:1px solid #d5d5d5}.filter-grid ::ng-deep .k-grid-header .k-header{font-weight:600}.checkbox-column{text-align:center}.attribute-dropdown,.operator-dropdown,.attribute-input,.value-input{width:100%}.attribute-item{display:flex;justify-content:space-between;align-items:center;width:100%}.attribute-path{flex:1}.attribute-type{font-size:11px;color:#888;margin-left:8px;padding:2px 6px;background:#f0f0f0;border-radius:3px}.empty-state{padding:40px;text-align:center;border:1px dashed;border-radius:8px;font-family:Montserrat,sans-serif;font-size:.9rem}.empty-state p{margin:0}.value-cell{display:flex;gap:4px;align-items:center}.value-cell .value-input{flex:1}.variable-toggle{flex-shrink:0}.variable-item{display:flex;flex-direction:column;gap:2px}.variable-name{font-family:monospace;font-weight:500}.variable-label{font-size:11px;color:var(--kendo-color-subtle, #888)}.variable-value{font-family:monospace;color:var(--kendo-color-primary, #0d6efd)}.variable-placeholder{color:var(--kendo-color-subtle, #888)}.variable-dropdown ::ng-deep .k-input-value-text{font-family:monospace}\n"] }]
4074
+ }], propDecorators: { availableAttributes: [{
438
4075
  type: Input
439
- }], actionColumnClick: [{
440
- type: Output
441
- }], columns: [{
4076
+ }], enableVariables: [{
442
4077
  type: Input
443
- }], paginator: [{
444
- type: ViewChild,
445
- args: [MatPaginator, { static: false }]
446
- }], sort: [{
447
- type: ViewChild,
448
- args: [MatSort, { static: false }]
449
- }], input: [{
450
- type: ViewChild,
451
- args: ['input', { static: false }]
4078
+ }], availableVariables: [{
4079
+ type: Input
4080
+ }], filters: [{
4081
+ type: Input
4082
+ }], filtersChange: [{
4083
+ type: Output
452
4084
  }] } });
453
4085
 
454
- class MmOctoUiModule {
455
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: MmOctoUiModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
456
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.12", ngImport: i0, type: MmOctoUiModule, imports: [MmOctoTableComponent], exports: [MmOctoTableComponent] });
457
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: MmOctoUiModule, imports: [MmOctoTableComponent] });
4086
+ /**
4087
+ * A reusable component for displaying and copying entity identifiers.
4088
+ * Provides a dropdown button with options to copy:
4089
+ * - RtId: The runtime ObjectId
4090
+ * - CkTypeId: The full versioned Construction Kit type ID
4091
+ * - RtCkTypeId: The runtime CK type ID (unversioned)
4092
+ * - RtEntityId: Combined format {RtCkTypeId}@{RtId}
4093
+ *
4094
+ * @example
4095
+ * ```html
4096
+ * <mm-entity-id-info
4097
+ * [rtId]="entity.rtId"
4098
+ * [rtCkTypeId]="entity.ckTypeId"
4099
+ * [ckTypeId]="entity.constructionKitType?.ckTypeId?.fullName">
4100
+ * </mm-entity-id-info>
4101
+ * ```
4102
+ */
4103
+ class EntityIdInfoComponent {
4104
+ notificationService = inject(NotificationDisplayService);
4105
+ copyIcon = copyIcon;
4106
+ /** The RtId (ObjectId) of the entity */
4107
+ rtId;
4108
+ /** The RtCkTypeId - runtime CK type ID (e.g., System.Communication/MeshAdapter) */
4109
+ rtCkTypeId;
4110
+ /** The CkTypeId - full versioned Construction Kit type ID (e.g., System.Communication-2.0.3/MeshAdapter-1) */
4111
+ ckTypeId;
4112
+ get copyOptions() {
4113
+ const fullCkTypeId = this.ckTypeId || this.rtCkTypeId;
4114
+ const rtEntityId = `${this.rtCkTypeId}@${this.rtId}`;
4115
+ return [
4116
+ {
4117
+ label: 'RtId',
4118
+ value: this.rtId,
4119
+ displayText: this.truncateValue(this.rtId, 24)
4120
+ },
4121
+ {
4122
+ label: 'CkTypeId',
4123
+ value: fullCkTypeId,
4124
+ displayText: this.truncateValue(fullCkTypeId, 40)
4125
+ },
4126
+ {
4127
+ label: 'RtCkTypeId',
4128
+ value: this.rtCkTypeId,
4129
+ displayText: this.truncateValue(this.rtCkTypeId, 40)
4130
+ },
4131
+ {
4132
+ label: 'RtEntityId',
4133
+ value: rtEntityId,
4134
+ displayText: this.truncateValue(rtEntityId, 40)
4135
+ }
4136
+ ];
4137
+ }
4138
+ truncateValue(value, maxLength) {
4139
+ if (value.length <= maxLength)
4140
+ return value;
4141
+ return value.substring(0, maxLength - 3) + '...';
4142
+ }
4143
+ async copyToClipboard(option) {
4144
+ try {
4145
+ await navigator.clipboard.writeText(option.value);
4146
+ this.notificationService.showSuccess(`${option.label} copied`, 2000);
4147
+ }
4148
+ catch (error) {
4149
+ console.error('Failed to copy to clipboard:', error);
4150
+ this.notificationService.showError('Failed to copy to clipboard');
4151
+ }
4152
+ }
4153
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: EntityIdInfoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4154
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.5", type: EntityIdInfoComponent, isStandalone: true, selector: "mm-entity-id-info", inputs: { rtId: "rtId", rtCkTypeId: "rtCkTypeId", ckTypeId: "ckTypeId" }, ngImport: i0, template: `
4155
+ <kendo-dropdownbutton
4156
+ [data]="copyOptions"
4157
+ [svgIcon]="copyIcon"
4158
+ look="flat"
4159
+ size="small"
4160
+ title="Copy Entity ID to clipboard"
4161
+ (itemClick)="copyToClipboard($event)">
4162
+ <ng-template kendoDropDownButtonItemTemplate let-item>
4163
+ <div class="copy-item">
4164
+ <span class="copy-label">{{ item.label }}</span>
4165
+ <span class="copy-value">{{ item.displayText }}</span>
4166
+ </div>
4167
+ </ng-template>
4168
+ Copy ID
4169
+ </kendo-dropdownbutton>
4170
+ `, isInline: true, styles: [":host{display:inline-block}.copy-item{display:flex;flex-direction:column;gap:2px;padding:8px 12px;width:100%}.copy-label{font-size:.7rem;font-weight:600;color:var(--kendo-color-primary, #64ceb9);text-transform:uppercase;letter-spacing:.5px}.copy-value{font-family:Roboto Mono,monospace;font-size:.75rem;color:var(--kendo-color-subtle, #9292a6);word-break:break-all}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i3.DropDownButtonComponent, selector: "kendo-dropdownbutton", inputs: ["arrowIcon", "icon", "svgIcon", "iconClass", "imageUrl", "textField", "data", "size", "rounded", "fillMode", "themeColor", "buttonAttributes"], outputs: ["itemClick", "focus", "blur"], exportAs: ["kendoDropDownButton"] }, { kind: "directive", type: i3.ButtonItemTemplateDirective, selector: "[kendoDropDownButtonItemTemplate],[kendoSplitButtonItemTemplate]" }, { kind: "ngmodule", type: SVGIconModule }] });
458
4171
  }
459
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: MmOctoUiModule, decorators: [{
460
- type: NgModule,
461
- args: [{
462
- declarations: [],
463
- imports: [
464
- MmOctoTableComponent
465
- ],
466
- exports: [
467
- MmOctoTableComponent
468
- ]
469
- }]
470
- }] });
4172
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: EntityIdInfoComponent, decorators: [{
4173
+ type: Component,
4174
+ args: [{ selector: 'mm-entity-id-info', standalone: true, imports: [
4175
+ CommonModule,
4176
+ ButtonsModule,
4177
+ SVGIconModule
4178
+ ], template: `
4179
+ <kendo-dropdownbutton
4180
+ [data]="copyOptions"
4181
+ [svgIcon]="copyIcon"
4182
+ look="flat"
4183
+ size="small"
4184
+ title="Copy Entity ID to clipboard"
4185
+ (itemClick)="copyToClipboard($event)">
4186
+ <ng-template kendoDropDownButtonItemTemplate let-item>
4187
+ <div class="copy-item">
4188
+ <span class="copy-label">{{ item.label }}</span>
4189
+ <span class="copy-value">{{ item.displayText }}</span>
4190
+ </div>
4191
+ </ng-template>
4192
+ Copy ID
4193
+ </kendo-dropdownbutton>
4194
+ `, styles: [":host{display:inline-block}.copy-item{display:flex;flex-direction:column;gap:2px;padding:8px 12px;width:100%}.copy-label{font-size:.7rem;font-weight:600;color:var(--kendo-color-primary, #64ceb9);text-transform:uppercase;letter-spacing:.5px}.copy-value{font-family:Roboto Mono,monospace;font-size:.75rem;color:var(--kendo-color-subtle, #9292a6);word-break:break-all}\n"] }]
4195
+ }], propDecorators: { rtId: [{
4196
+ type: Input,
4197
+ args: [{ required: true }]
4198
+ }], rtCkTypeId: [{
4199
+ type: Input,
4200
+ args: [{ required: true }]
4201
+ }], ckTypeId: [{
4202
+ type: Input
4203
+ }] } });
471
4204
 
472
4205
  /*
473
4206
  * Public API Surface of octo-ui
474
4207
  */
4208
+ /**
4209
+ * Provides OctoUi services using modern Angular provider functions.
4210
+ * This is the recommended approach for library providers.
4211
+ */
4212
+ function provideOctoUi() {
4213
+ return makeEnvironmentProviders([
4214
+ provideOctoServices(),
4215
+ provideMmSharedUi(),
4216
+ provideMmSharedAuth(),
4217
+ AttributeSortSelectorDialogService,
4218
+ PropertyConverterService,
4219
+ CkTypeSelectorDialogService
4220
+ ]);
4221
+ }
475
4222
 
476
4223
  /**
477
4224
  * Generated bundle index. Do not edit.
478
4225
  */
479
4226
 
480
- export { ListElementModule, MmOctoTableComponent, MmOctoUiModule, OctoListNavigation, OctoListNavigationDataInfo, OctoListNavigationOptions, getDataKey, getDisplayName };
4227
+ export { AttributeSelectorDialogComponent, AttributeSelectorDialogService, AttributeSortSelectorDialogComponent, AttributeSortSelectorDialogService, AttributeValueTypeDto, CkTypeSelectorDialogComponent, CkTypeSelectorDialogService, CkTypeSelectorInputComponent, DefaultPropertyCategory, EntityIdInfoComponent, FieldFilterEditorComponent, OctoGraphQlDataSource, OctoGraphQlHierarchyDataSource, PropertyConverterService, PropertyDisplayMode, PropertyGridComponent, PropertyValueDisplayComponent, provideOctoUi };
481
4228
  //# sourceMappingURL=meshmakers-octo-ui.mjs.map