@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.
- package/README.md +161 -13
- package/fesm2022/meshmakers-octo-ui.mjs +4147 -400
- package/fesm2022/meshmakers-octo-ui.mjs.map +1 -1
- package/package.json +19 -5
- package/types/meshmakers-octo-ui.d.ts +806 -0
- package/index.d.ts +0 -136
|
@@ -1,481 +1,4228 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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: "
|
|
26
|
-
type:
|
|
906
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: PropertyConverterService, decorators: [{
|
|
907
|
+
type: Injectable,
|
|
27
908
|
args: [{
|
|
28
|
-
|
|
29
|
-
imports: [CommonModule]
|
|
909
|
+
providedIn: 'root'
|
|
30
910
|
}]
|
|
31
911
|
}] });
|
|
32
912
|
|
|
33
|
-
class
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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.
|
|
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> </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> </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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
2341
|
+
super(inject(DialogRef));
|
|
49
2342
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
if (typeof
|
|
178
|
-
|
|
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
|
-
|
|
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
|
-
|
|
186
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
201
|
-
|
|
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.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
this.
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
this.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
2934
|
+
return null;
|
|
265
2935
|
}
|
|
266
|
-
|
|
267
|
-
|
|
2936
|
+
// Event handlers
|
|
2937
|
+
onFilterChange(filter) {
|
|
2938
|
+
if (!filter || filter.length < this.minSearchLength) {
|
|
2939
|
+
this.filteredTypes = [];
|
|
268
2940
|
return;
|
|
269
2941
|
}
|
|
270
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
this.
|
|
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
|
-
|
|
312
|
-
|
|
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
|
-
|
|
315
|
-
if (this.
|
|
316
|
-
this.
|
|
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
|
-
|
|
321
|
-
|
|
2983
|
+
reset() {
|
|
2984
|
+
this.clear();
|
|
322
2985
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
this.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
334
|
-
this.
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
if (
|
|
359
|
-
|
|
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: "
|
|
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: "
|
|
3105
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CkTypeSelectorInputComponent, decorators: [{
|
|
366
3106
|
type: Component,
|
|
367
|
-
args: [{ selector: 'mm-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
}],
|
|
413
|
-
type: ContentChildren,
|
|
414
|
-
args: [TemplateRef]
|
|
415
|
-
}], actionColumns: [{
|
|
3185
|
+
}], minSearchLength: [{
|
|
416
3186
|
type: Input
|
|
417
|
-
}],
|
|
3187
|
+
}], maxResults: [{
|
|
418
3188
|
type: Input
|
|
419
|
-
}],
|
|
3189
|
+
}], debounceMs: [{
|
|
420
3190
|
type: Input
|
|
421
|
-
}],
|
|
3191
|
+
}], ckModelIds: [{
|
|
422
3192
|
type: Input
|
|
423
|
-
}],
|
|
3193
|
+
}], allowAbstract: [{
|
|
424
3194
|
type: Input
|
|
425
|
-
}],
|
|
3195
|
+
}], dialogTitle: [{
|
|
426
3196
|
type: Input
|
|
427
|
-
}],
|
|
3197
|
+
}], advancedSearchLabel: [{
|
|
428
3198
|
type: Input
|
|
429
|
-
}],
|
|
3199
|
+
}], disabled: [{
|
|
430
3200
|
type: Input
|
|
431
|
-
}],
|
|
3201
|
+
}], required: [{
|
|
432
3202
|
type: Input
|
|
433
|
-
}],
|
|
3203
|
+
}], ckTypeSelected: [{
|
|
434
3204
|
type: Output
|
|
435
|
-
}],
|
|
3205
|
+
}], ckTypeCleared: [{
|
|
436
3206
|
type: Output
|
|
437
|
-
}]
|
|
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
|
-
}],
|
|
440
|
-
type: Output
|
|
441
|
-
}], columns: [{
|
|
4076
|
+
}], enableVariables: [{
|
|
442
4077
|
type: Input
|
|
443
|
-
}],
|
|
444
|
-
type:
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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: "
|
|
460
|
-
type:
|
|
461
|
-
args: [{
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
],
|
|
466
|
-
|
|
467
|
-
|
|
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 {
|
|
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
|