@meshmakers/octo-ui 3.3.560 → 3.3.570

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, Component, Injectable, EventEmitter, Output, Input, ElementRef, forwardRef, ViewChild, makeEnvironmentProviders } from '@angular/core';
3
- import { AttributeSelectorService, AttributeValueTypeDto as AttributeValueTypeDto$1, CkTypeAttributeService, CkTypeSelectorService, SearchFilterTypesDto, SortOrdersDto, FieldFilterOperatorsDto, provideOctoServices } from '@meshmakers/octo-services';
4
- import { DataSourceTyped, HierarchyDataSourceBase, NotificationDisplayService, provideMmSharedUi } from '@meshmakers/shared-ui';
2
+ import { inject, Component, Injectable, EventEmitter, Output, Input, ElementRef, forwardRef, ViewChild, signal, computed, makeEnvironmentProviders } from '@angular/core';
3
+ import { AttributeSelectorService, AttributeValueTypeDto as AttributeValueTypeDto$1, CkTypeAttributeService, CkTypeSelectorService, GetEntitiesByCkTypeDtoGQL, RuntimeEntitySelectDataSource, RuntimeEntityDialogDataSource, SearchFilterTypesDto, SortOrdersDto, FieldFilterOperatorsDto, provideOctoServices } from '@meshmakers/octo-services';
4
+ import { WindowStateService, EntitySelectInputComponent, DataSourceTyped, HierarchyDataSourceBase, NotificationDisplayService, provideMmSharedUi } from '@meshmakers/shared-ui';
5
5
  import { provideMmSharedAuth } from '@meshmakers/shared-auth';
6
6
  import * as i1 from '@angular/common';
7
7
  import { CommonModule } from '@angular/common';
@@ -17,7 +17,7 @@ import * as i3$1 from '@progress/kendo-angular-dropdowns';
17
17
  import { DropDownListModule, DropDownsModule, AutoCompleteModule } from '@progress/kendo-angular-dropdowns';
18
18
  import * as i5$1 from '@progress/kendo-angular-icons';
19
19
  import { IconsModule, SVGIconModule } from '@progress/kendo-angular-icons';
20
- import { searchIcon, sortAscSmallIcon, sortDescSmallIcon, chevronRightIcon, chevronDownIcon, downloadIcon, fileIcon, folderIcon, calendarIcon, checkboxCheckedIcon, listUnorderedIcon, filterClearIcon, arrowRightIcon, arrowLeftIcon, chevronDoubleRightIcon, chevronDoubleLeftIcon, arrowUpIcon, arrowDownIcon, plusIcon, minusIcon, trashIcon, dollarIcon, copyIcon } from '@progress/kendo-svg-icons';
20
+ import { searchIcon, sortAscSmallIcon, sortDescSmallIcon, chevronRightIcon, chevronDownIcon, downloadIcon, fileIcon, folderIcon, calendarIcon, checkboxCheckedIcon, listUnorderedIcon, filterClearIcon, arrowRightIcon, arrowLeftIcon, chevronDoubleRightIcon, chevronDoubleLeftIcon, arrowUpIcon, arrowDownIcon, pencilIcon, trashIcon, plusIcon, minusIcon, dollarIcon, copyIcon } from '@progress/kendo-svg-icons';
21
21
  import { WindowRef, WindowModule, WindowService, WindowCloseResult } from '@progress/kendo-angular-dialog';
22
22
  import { Subject, firstValueFrom, of, forkJoin, Subscription } from 'rxjs';
23
23
  import { debounceTime, distinctUntilChanged, switchMap, map, tap, catchError } from 'rxjs/operators';
@@ -493,6 +493,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
493
493
 
494
494
  class AttributeSortSelectorDialogService {
495
495
  windowService = inject(WindowService);
496
+ windowStateService = inject(WindowStateService);
496
497
  /**
497
498
  * Opens the attribute sort selector dialog
498
499
  * @param ckTypeId The CkType ID to fetch attributes for
@@ -506,15 +507,17 @@ class AttributeSortSelectorDialogService {
506
507
  selectedAttributes,
507
508
  dialogTitle
508
509
  };
510
+ const size = this.windowStateService.resolveWindowSize('attribute-sort-selector', { width: 1200, height: 750 });
509
511
  const windowRef = this.windowService.open({
510
512
  content: AttributeSortSelectorDialogComponent,
511
- width: 1200,
512
- height: 750,
513
+ width: size.width,
514
+ height: size.height,
513
515
  minWidth: 1050,
514
516
  minHeight: 700,
515
517
  resizable: true,
516
518
  title: dialogTitle || 'Select Attributes with Sort Order'
517
519
  });
520
+ this.windowStateService.applyModalBehavior('attribute-sort-selector', windowRef);
518
521
  // Pass data to the component
519
522
  const contentRef = windowRef.content;
520
523
  if (contentRef?.instance) {
@@ -523,14 +526,12 @@ class AttributeSortSelectorDialogService {
523
526
  try {
524
527
  const result = await firstValueFrom(windowRef.result);
525
528
  if (result instanceof WindowCloseResult) {
526
- // User closed the window via X button
527
529
  return {
528
530
  confirmed: false,
529
531
  selectedAttributes: []
530
532
  };
531
533
  }
532
534
  if (result && typeof result === 'object' && 'selectedAttributes' in result) {
533
- // User clicked OK
534
535
  const dialogResult = result;
535
536
  return {
536
537
  confirmed: true,
@@ -538,7 +539,6 @@ class AttributeSortSelectorDialogService {
538
539
  };
539
540
  }
540
541
  else {
541
- // User clicked Cancel or closed dialog (result is undefined)
542
542
  return {
543
543
  confirmed: false,
544
544
  selectedAttributes: []
@@ -546,7 +546,6 @@ class AttributeSortSelectorDialogService {
546
546
  }
547
547
  }
548
548
  catch {
549
- // Dialog was closed without result (e.g., ESC key, X button)
550
549
  return {
551
550
  confirmed: false,
552
551
  selectedAttributes: []
@@ -1898,7 +1897,7 @@ class CkTypeSelectorDialogComponent {
1898
1897
  searchIcon = searchIcon;
1899
1898
  filterClearIcon = filterClearIcon;
1900
1899
  dialogTitle = 'Select Construction Kit Type';
1901
- allowAbstract = false;
1900
+ allowAbstract = true;
1902
1901
  searchText = '';
1903
1902
  selectedModel = null;
1904
1903
  availableModels = [];
@@ -1919,7 +1918,7 @@ class CkTypeSelectorDialogComponent {
1919
1918
  ngOnInit() {
1920
1919
  if (this.data) {
1921
1920
  this.dialogTitle = this.data.dialogTitle || 'Select Construction Kit Type';
1922
- this.allowAbstract = this.data.allowAbstract ?? false;
1921
+ this.allowAbstract = this.data.allowAbstract ?? true;
1923
1922
  this.initialCkModelIds = this.data.ckModelIds;
1924
1923
  this.derivedFromRtCkTypeId = this.data.derivedFromRtCkTypeId;
1925
1924
  if (this.data.selectedCkTypeId) {
@@ -2220,6 +2219,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
2220
2219
 
2221
2220
  class CkTypeSelectorDialogService {
2222
2221
  windowService = inject(WindowService);
2222
+ windowStateService = inject(WindowStateService);
2223
2223
  /**
2224
2224
  * Opens the CkType selector dialog
2225
2225
  * @param options Dialog options
@@ -2233,15 +2233,17 @@ class CkTypeSelectorDialogService {
2233
2233
  allowAbstract: options.allowAbstract,
2234
2234
  derivedFromRtCkTypeId: options.derivedFromRtCkTypeId
2235
2235
  };
2236
+ const size = this.windowStateService.resolveWindowSize('ck-type-selector', { width: 900, height: 650 });
2236
2237
  const windowRef = this.windowService.open({
2237
2238
  content: CkTypeSelectorDialogComponent,
2238
- width: 900,
2239
- height: 650,
2239
+ width: size.width,
2240
+ height: size.height,
2240
2241
  minWidth: 750,
2241
2242
  minHeight: 550,
2242
2243
  resizable: true,
2243
2244
  title: options.dialogTitle || 'Select Construction Kit Type'
2244
2245
  });
2246
+ this.windowStateService.applyModalBehavior('ck-type-selector', windowRef);
2245
2247
  // Pass data to the component
2246
2248
  const contentRef = windowRef.content;
2247
2249
  if (contentRef?.instance) {
@@ -2256,7 +2258,6 @@ class CkTypeSelectorDialogService {
2256
2258
  };
2257
2259
  }
2258
2260
  if (result && typeof result === 'object' && 'selectedCkType' in result) {
2259
- // User clicked OK and we have a result
2260
2261
  const dialogResult = result;
2261
2262
  return {
2262
2263
  confirmed: true,
@@ -2264,7 +2265,6 @@ class CkTypeSelectorDialogService {
2264
2265
  };
2265
2266
  }
2266
2267
  else {
2267
- // User clicked Cancel or closed dialog
2268
2268
  return {
2269
2269
  confirmed: false,
2270
2270
  selectedCkType: null
@@ -2272,7 +2272,6 @@ class CkTypeSelectorDialogService {
2272
2272
  }
2273
2273
  }
2274
2274
  catch {
2275
- // Dialog was closed without result (e.g., ESC key, X button)
2276
2275
  return {
2277
2276
  confirmed: false,
2278
2277
  selectedCkType: null
@@ -2286,363 +2285,652 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
2286
2285
  type: Injectable
2287
2286
  }] });
2288
2287
 
2289
- // noinspection JSUnusedGlobalSymbols
2290
- class OctoGraphQlDataSource extends DataSourceTyped {
2291
- _searchFilterAttributePaths = [];
2292
- // noinspection JSUnusedGlobalSymbols
2293
- constructor(listViewComponent) {
2294
- super(listViewComponent);
2295
- }
2296
- get searchFilterAttributePaths() {
2297
- return this._searchFilterAttributePaths;
2298
- }
2299
- set searchFilterAttributePaths(value) {
2300
- this._searchFilterAttributePaths = value;
2288
+ class CkTypeSelectorInputComponent {
2289
+ autocomplete;
2290
+ placeholder = 'Select a CK type...';
2291
+ minSearchLength = 2;
2292
+ maxResults = 50;
2293
+ debounceMs = 300;
2294
+ ckModelIds;
2295
+ allowAbstract = true;
2296
+ dialogTitle = 'Select Construction Kit Type';
2297
+ advancedSearchLabel = 'Advanced Search...';
2298
+ derivedFromRtCkTypeId;
2299
+ _disabled = false;
2300
+ get disabled() {
2301
+ return this._disabled;
2301
2302
  }
2302
- // noinspection JSUnusedGlobalSymbols
2303
- getFieldFilterDefinitions(state) {
2304
- let fieldFilters = null;
2305
- if (state.filter?.filters) {
2306
- fieldFilters = state.filter.filters.map((f) => {
2307
- if (isCompositeFilterDescriptor(f)) {
2308
- throw new Error('Composite filter descriptor not supported');
2309
- }
2310
- const { operator, value } = OctoGraphQlDataSource.getOperatorAndValue(f.operator, f.value);
2311
- return {
2312
- attributePath: f.field,
2313
- operator: operator,
2314
- comparisonValue: value
2315
- };
2316
- });
2303
+ set disabled(value) {
2304
+ this._disabled = !!value;
2305
+ if (this._disabled) {
2306
+ this.searchFormControl.disable();
2317
2307
  }
2318
- return fieldFilters;
2319
- }
2320
- // noinspection JSUnusedGlobalSymbols
2321
- getSearchFilterDefinitions(textSearchValue) {
2322
- let searchFilterDto = null;
2323
- if (textSearchValue) {
2324
- searchFilterDto = {
2325
- type: SearchFilterTypesDto.AttributeFilterDto,
2326
- attributePaths: this.searchFilterAttributePaths,
2327
- searchTerm: textSearchValue,
2328
- };
2308
+ else {
2309
+ this.searchFormControl.enable();
2329
2310
  }
2330
- return searchFilterDto;
2331
2311
  }
2332
- // noinspection JSUnusedGlobalSymbols
2333
- getSortDefinitions(state) {
2334
- let sort = null;
2335
- if (state.sort) {
2336
- sort = new Array();
2337
- state.sort.forEach((s) => {
2338
- switch (s.dir) {
2339
- case 'asc':
2340
- sort?.push({
2341
- attributePath: s.field,
2342
- sortOrder: SortOrdersDto.AscendingDto
2343
- });
2344
- break;
2345
- case 'desc':
2346
- sort?.push({
2347
- attributePath: s.field,
2348
- sortOrder: SortOrdersDto.DescendingDto,
2349
- });
2350
- break;
2351
- default:
2352
- sort?.push({
2353
- attributePath: s.field,
2354
- sortOrder: SortOrdersDto.DefaultDto,
2355
- });
2356
- break;
2357
- }
2358
- });
2359
- }
2360
- return sort;
2312
+ _required = false;
2313
+ get required() {
2314
+ return this._required;
2361
2315
  }
2362
- static getOperatorAndValue(operator, value) {
2363
- switch (operator) {
2364
- case 'eq':
2365
- return { operator: FieldFilterOperatorsDto.EqualsDto, value: value };
2366
- case 'neq':
2367
- return { operator: FieldFilterOperatorsDto.NotEqualsDto, value: value };
2368
- case 'lt':
2369
- return { operator: FieldFilterOperatorsDto.LessThanDto, value: value };
2370
- case 'lte':
2371
- return { operator: FieldFilterOperatorsDto.LessEqualThanDto, value: value };
2372
- case 'gt':
2373
- return { operator: FieldFilterOperatorsDto.GreaterThanDto, value: value };
2374
- case 'gte':
2375
- return { operator: FieldFilterOperatorsDto.GreaterEqualThanDto, value: value };
2376
- case 'contains':
2377
- return { operator: FieldFilterOperatorsDto.LikeDto, value: value };
2378
- case 'doesnotcontain':
2379
- return { operator: FieldFilterOperatorsDto.MatchRegExDto, value: `^(?!.*${value}).*$` };
2380
- case 'startswith':
2381
- return { operator: FieldFilterOperatorsDto.MatchRegExDto, value: value + '.*' };
2382
- case 'endswith':
2383
- return { operator: FieldFilterOperatorsDto.MatchRegExDto, value: '.*' + value };
2384
- case 'isnull':
2385
- return { operator: FieldFilterOperatorsDto.EqualsDto, value: null };
2386
- case 'isnotnull':
2387
- return { operator: FieldFilterOperatorsDto.NotEqualsDto, value: null };
2388
- case 'isempty':
2389
- return { operator: FieldFilterOperatorsDto.EqualsDto, value: "" };
2390
- case 'isnotempty':
2391
- return { operator: FieldFilterOperatorsDto.NotEqualsDto, value: "" };
2392
- default:
2393
- throw new Error('The filter operator is not supported');
2394
- }
2316
+ set required(value) {
2317
+ this._required = !!value;
2395
2318
  }
2396
- }
2397
-
2398
- class OctoGraphQlHierarchyDataSource extends HierarchyDataSourceBase {
2399
- }
2400
-
2401
- class AttributeSelectorDialogComponent {
2402
- windowRef = inject(WindowRef);
2403
- attributeService = inject(AttributeSelectorService);
2319
+ ckTypeSelected = new EventEmitter();
2320
+ ckTypeCleared = new EventEmitter();
2321
+ searchFormControl = new FormControl();
2322
+ filteredTypes = [];
2323
+ selectedCkType = null;
2324
+ isLoading = false;
2325
+ typeMap = new Map();
2404
2326
  searchSubject = new Subject();
2405
- arrowRightIcon = arrowRightIcon;
2406
- arrowLeftIcon = arrowLeftIcon;
2407
- chevronDoubleRightIcon = chevronDoubleRightIcon;
2408
- chevronDoubleLeftIcon = chevronDoubleLeftIcon;
2327
+ subscriptions = new Subscription();
2328
+ onChange = () => { };
2329
+ onTouched = () => { };
2409
2330
  searchIcon = searchIcon;
2410
- arrowUpIcon = arrowUpIcon;
2411
- arrowDownIcon = arrowDownIcon;
2412
- data;
2413
- dialogTitle = 'Select Attributes';
2414
- rtCkTypeId;
2415
- singleSelect = false;
2416
- searchText = '';
2417
- selectedSingleKey = [];
2418
- selectedValueTypeFilter = null;
2419
- includeNavigationProperties = true;
2420
- maxDepth = null;
2421
- availableAttributes = [];
2422
- selectedAttributes = [];
2423
- availableGridData = { data: [], total: 0 };
2424
- selectedGridData = { data: [], total: 0 };
2425
- selectedAvailableKeys = [];
2426
- selectedChosenKeys = [];
2427
- valueTypeOptions = [
2428
- { text: 'All Types', value: null },
2429
- { text: 'String', value: AttributeValueTypeDto$1.StringDto },
2430
- { text: 'Integer', value: AttributeValueTypeDto$1.IntegerDto },
2431
- { text: 'Double', value: AttributeValueTypeDto$1.DoubleDto },
2432
- { text: 'Boolean', value: AttributeValueTypeDto$1.BooleanDto },
2433
- { text: 'DateTime', value: AttributeValueTypeDto$1.DateTimeDto },
2434
- { text: 'DateTimeOffset', value: AttributeValueTypeDto$1.DateTimeOffsetDto },
2435
- { text: 'Enum', value: AttributeValueTypeDto$1.EnumDto },
2436
- { text: 'TimeSpan', value: AttributeValueTypeDto$1.TimeSpanDto }
2437
- ];
2438
- // Double-click tracking
2439
- lastClickTime = 0;
2440
- lastClickedItem = null;
2441
- doubleClickDelay = 300; // milliseconds
2331
+ popupSettings = { appendTo: 'root', popupClass: 'mm-ck-type-popup' };
2332
+ static popupStyleInjected = false;
2333
+ ckTypeSelectorService = inject(CkTypeSelectorService);
2334
+ dialogService = inject(CkTypeSelectorDialogService, { optional: true });
2335
+ elementRef = inject(ElementRef);
2442
2336
  ngOnInit() {
2443
- if (this.data) {
2444
- this.rtCkTypeId = this.data.rtCkTypeId;
2445
- this.dialogTitle = this.data.dialogTitle || 'Select Attributes';
2446
- this.singleSelect = this.data.singleSelect ?? false;
2447
- this.includeNavigationProperties = this.data.includeNavigationProperties ?? true;
2448
- this.maxDepth = this.data.maxDepth ?? null;
2449
- if (this.data.selectedAttributes && this.data.selectedAttributes.length > 0) {
2450
- if (this.singleSelect) {
2451
- this.selectedSingleKey = [this.data.selectedAttributes[0]];
2452
- }
2453
- else {
2454
- // Pre-populate selected attributes if provided
2455
- this.loadInitialSelectedAttributes(this.data.selectedAttributes);
2337
+ this.setupSearch();
2338
+ this.injectPopupStyles();
2339
+ }
2340
+ ngOnDestroy() {
2341
+ this.subscriptions.unsubscribe();
2342
+ this.searchSubject.complete();
2343
+ }
2344
+ // ControlValueAccessor implementation
2345
+ writeValue(value) {
2346
+ if (value !== this.selectedCkType) {
2347
+ this.selectedCkType = value;
2348
+ if (value) {
2349
+ // Ensure the value is in the data list so the autocomplete can display it
2350
+ if (!this.typeMap.has(value.rtCkTypeId)) {
2351
+ this.typeMap.set(value.rtCkTypeId, value);
2352
+ this.filteredTypes = [value.rtCkTypeId];
2456
2353
  }
2354
+ this.searchFormControl.setValue(value.rtCkTypeId, { emitEvent: false });
2355
+ }
2356
+ else {
2357
+ this.searchFormControl.setValue('', { emitEvent: false });
2457
2358
  }
2458
2359
  }
2459
- // Set up search debouncing
2460
- this.searchSubject.pipe(debounceTime(300), distinctUntilChanged()).subscribe(searchText => {
2461
- this.loadAvailableAttributes(searchText);
2462
- });
2463
- // Load initial attributes
2464
- this.loadAvailableAttributes();
2465
- }
2466
- loadAvailableAttributes(searchTerm) {
2467
- this.attributeService.getAvailableAttributes(this.rtCkTypeId, undefined, undefined, undefined, this.selectedValueTypeFilter || undefined, searchTerm || undefined, this.includeNavigationProperties, this.maxDepth ?? undefined).subscribe(result => {
2468
- // Filter out already selected attributes
2469
- const selectedPaths = new Set(this.selectedAttributes.map(a => a.attributePath));
2470
- this.availableAttributes = result.items.filter(item => !selectedPaths.has(item.attributePath));
2471
- this.updateAvailableGrid();
2472
- });
2473
2360
  }
2474
- loadInitialSelectedAttributes(attributePaths) {
2475
- // Load all attributes to get the details for selected ones
2476
- this.attributeService.getAvailableAttributes(this.rtCkTypeId).subscribe(result => {
2477
- // Create a map for quick lookup
2478
- const attributeMap = new Map(result.items.map(item => [item.attributePath, item]));
2479
- // Preserve the order from attributePaths
2480
- this.selectedAttributes = attributePaths
2481
- .map(path => attributeMap.get(path))
2482
- .filter((item) => item !== undefined);
2483
- this.updateSelectedGrid();
2484
- // Filter out selected from available
2485
- const selectedPaths = new Set(this.selectedAttributes.map(a => a.attributePath));
2486
- this.availableAttributes = result.items.filter(item => !selectedPaths.has(item.attributePath));
2487
- this.updateAvailableGrid();
2488
- });
2361
+ registerOnChange(fn) {
2362
+ this.onChange = fn;
2489
2363
  }
2490
- onSearchChange(value) {
2491
- this.searchSubject.next(value);
2364
+ registerOnTouched(fn) {
2365
+ this.onTouched = fn;
2492
2366
  }
2493
- onValueTypeFilterChange(_value) {
2494
- this.loadAvailableAttributes(this.searchText || undefined);
2367
+ setDisabledState(isDisabled) {
2368
+ this.disabled = isDisabled;
2495
2369
  }
2496
- onNavigationPropertiesChange() {
2497
- if (!this.includeNavigationProperties) {
2498
- this.maxDepth = null;
2499
- }
2500
- this.loadAvailableAttributes(this.searchText || undefined);
2501
- }
2502
- onMaxDepthChange(value) {
2503
- this.maxDepth = value;
2504
- this.loadAvailableAttributes(this.searchText || undefined);
2370
+ // Validator implementation
2371
+ validate(control) {
2372
+ if (this.required && !this.selectedCkType) {
2373
+ return { required: true };
2374
+ }
2375
+ const value = control.value;
2376
+ if (value && typeof value === 'string') {
2377
+ return { invalidCkType: true };
2378
+ }
2379
+ return null;
2505
2380
  }
2506
- addSelected() {
2507
- const itemsToAdd = this.availableAttributes.filter(item => this.selectedAvailableKeys.includes(item.attributePath));
2508
- this.selectedAttributes.push(...itemsToAdd);
2509
- this.availableAttributes = this.availableAttributes.filter(item => !this.selectedAvailableKeys.includes(item.attributePath));
2510
- this.selectedAvailableKeys = [];
2511
- this.updateGrids();
2381
+ // Event handlers
2382
+ onFilterChange(filter) {
2383
+ if (!filter || filter.length < this.minSearchLength) {
2384
+ this.filteredTypes = [];
2385
+ return;
2386
+ }
2387
+ this.searchSubject.next(filter);
2512
2388
  }
2513
- removeSelected() {
2514
- const itemsToRemove = this.selectedAttributes.filter(item => this.selectedChosenKeys.includes(item.attributePath));
2515
- this.availableAttributes.push(...itemsToRemove);
2516
- this.selectedAttributes = this.selectedAttributes.filter(item => !this.selectedChosenKeys.includes(item.attributePath));
2517
- this.selectedChosenKeys = [];
2518
- this.sortAvailableAttributes();
2519
- this.updateGrids();
2389
+ onSelectionChange(value) {
2390
+ if (value && typeof value === 'string') {
2391
+ const ckType = this.typeMap.get(value);
2392
+ if (ckType) {
2393
+ this.selectCkType(ckType);
2394
+ }
2395
+ }
2520
2396
  }
2521
- addAll() {
2522
- this.selectedAttributes.push(...this.availableAttributes);
2523
- this.availableAttributes = [];
2524
- this.selectedAvailableKeys = [];
2525
- this.updateGrids();
2397
+ onFocus() {
2398
+ const currentValue = this.searchFormControl.value;
2399
+ if (currentValue && currentValue.length >= this.minSearchLength && this.filteredTypes.length === 0) {
2400
+ this.searchSubject.next(currentValue);
2401
+ }
2526
2402
  }
2527
- removeAll() {
2528
- this.availableAttributes.push(...this.selectedAttributes);
2529
- this.selectedAttributes = [];
2530
- this.selectedChosenKeys = [];
2531
- this.sortAvailableAttributes();
2532
- this.updateGrids();
2403
+ onBlur() {
2404
+ this.onTouched();
2405
+ if (this.filteredTypes.length === 1 && !this.selectedCkType) {
2406
+ const displayText = this.filteredTypes[0];
2407
+ const ckType = this.typeMap.get(displayText);
2408
+ if (ckType) {
2409
+ this.selectCkType(ckType);
2410
+ }
2411
+ }
2533
2412
  }
2534
- sortAvailableAttributes() {
2535
- this.availableAttributes.sort((a, b) => a.attributePath.localeCompare(b.attributePath));
2413
+ // Public methods
2414
+ clear() {
2415
+ this.selectedCkType = null;
2416
+ this.filteredTypes = [];
2417
+ this.typeMap.clear();
2418
+ this.searchFormControl.setValue('', { emitEvent: false });
2419
+ this.onChange(null);
2420
+ this.ckTypeCleared.emit();
2421
+ this.autocomplete.focus();
2536
2422
  }
2537
- canMoveUp() {
2538
- if (this.selectedChosenKeys.length !== 1)
2539
- return false;
2540
- const index = this.selectedAttributes.findIndex(a => a.attributePath === this.selectedChosenKeys[0]);
2541
- return index > 0;
2423
+ focus() {
2424
+ if (this.autocomplete) {
2425
+ this.autocomplete.focus();
2426
+ }
2542
2427
  }
2543
- canMoveDown() {
2544
- if (this.selectedChosenKeys.length !== 1)
2545
- return false;
2546
- const index = this.selectedAttributes.findIndex(a => a.attributePath === this.selectedChosenKeys[0]);
2547
- return index >= 0 && index < this.selectedAttributes.length - 1;
2428
+ reset() {
2429
+ this.clear();
2548
2430
  }
2549
- moveUp() {
2550
- if (!this.canMoveUp())
2551
- return;
2552
- const index = this.selectedAttributes.findIndex(a => a.attributePath === this.selectedChosenKeys[0]);
2553
- [this.selectedAttributes[index - 1], this.selectedAttributes[index]] =
2554
- [this.selectedAttributes[index], this.selectedAttributes[index - 1]];
2555
- this.updateSelectedGrid();
2431
+ // Private methods
2432
+ setupSearch() {
2433
+ this.subscriptions.add(this.searchSubject.pipe(debounceTime(this.debounceMs), distinctUntilChanged(), tap(() => {
2434
+ this.isLoading = true;
2435
+ this.filteredTypes = [];
2436
+ }), switchMap(filter => {
2437
+ const source$ = this.derivedFromRtCkTypeId
2438
+ ? this.ckTypeSelectorService.getDerivedCkTypes(this.derivedFromRtCkTypeId, {
2439
+ searchText: filter
2440
+ })
2441
+ : this.ckTypeSelectorService.getCkTypes({
2442
+ ckModelIds: this.ckModelIds,
2443
+ searchText: filter,
2444
+ first: this.maxResults
2445
+ });
2446
+ return source$.pipe(catchError(error => {
2447
+ console.error('CK type search error:', error);
2448
+ return of({ items: [], totalCount: 0 });
2449
+ }));
2450
+ })).subscribe(result => {
2451
+ this.isLoading = false;
2452
+ // Filter out abstract types if not allowed
2453
+ let items = result.items;
2454
+ if (!this.allowAbstract) {
2455
+ items = items.filter(item => !item.isAbstract);
2456
+ }
2457
+ this.filteredTypes = items.map(item => item.rtCkTypeId);
2458
+ this.typeMap = new Map(items.map(item => [item.rtCkTypeId, item]));
2459
+ }));
2556
2460
  }
2557
- moveDown() {
2558
- if (!this.canMoveDown())
2461
+ injectPopupStyles() {
2462
+ if (CkTypeSelectorInputComponent.popupStyleInjected)
2559
2463
  return;
2560
- const index = this.selectedAttributes.findIndex(a => a.attributePath === this.selectedChosenKeys[0]);
2561
- [this.selectedAttributes[index], this.selectedAttributes[index + 1]] =
2562
- [this.selectedAttributes[index + 1], this.selectedAttributes[index]];
2563
- this.updateSelectedGrid();
2564
- }
2565
- updateGrids() {
2566
- this.updateAvailableGrid();
2567
- this.updateSelectedGrid();
2568
- }
2569
- updateAvailableGrid() {
2570
- this.availableGridData = {
2571
- data: this.availableAttributes,
2572
- total: this.availableAttributes.length
2573
- };
2574
- }
2575
- updateSelectedGrid() {
2576
- this.selectedGridData = {
2577
- data: this.selectedAttributes,
2578
- total: this.selectedAttributes.length
2579
- };
2580
- }
2581
- onCancel() {
2582
- this.windowRef.close();
2464
+ const style = document.createElement('style');
2465
+ style.setAttribute('data-mm-ck-type-popup', '');
2466
+ style.textContent = `
2467
+ .mm-ck-type-popup {
2468
+ max-width: 500px !important;
2469
+ min-width: 0 !important;
2470
+ }
2471
+ .mm-ck-type-popup .k-child-animation-container {
2472
+ max-width: 500px !important;
2473
+ }
2474
+ .mm-ck-type-popup .k-list-item {
2475
+ padding: 4px 12px !important;
2476
+ min-height: 0 !important;
2477
+ font-size: 13px !important;
2478
+ line-height: 20px !important;
2479
+ }
2480
+ `;
2481
+ document.head.appendChild(style);
2482
+ CkTypeSelectorInputComponent.popupStyleInjected = true;
2583
2483
  }
2584
- onConfirm() {
2585
- if (this.singleSelect) {
2586
- const selected = this.availableAttributes.find(a => a.attributePath === this.selectedSingleKey[0]);
2587
- const result = {
2588
- selectedAttributes: selected ? [selected] : []
2589
- };
2590
- this.windowRef.close(result);
2591
- }
2592
- else {
2593
- const result = {
2594
- selectedAttributes: this.selectedAttributes
2595
- };
2596
- this.windowRef.close(result);
2597
- }
2484
+ selectCkType(ckType) {
2485
+ this.selectedCkType = ckType;
2486
+ this.searchFormControl.setValue(ckType.rtCkTypeId, { emitEvent: false });
2487
+ this.filteredTypes = [];
2488
+ this.onChange(ckType);
2489
+ this.ckTypeSelected.emit(ckType);
2490
+ this.autocomplete.toggle(false);
2598
2491
  }
2599
- /**
2600
- * Handle cell click on available attributes grid to detect double-click
2601
- */
2602
- onAvailableCellClick(event) {
2603
- const dataItem = event.dataItem;
2604
- if (!dataItem)
2605
- return;
2606
- const currentTime = Date.now();
2607
- const attributePath = dataItem.attributePath;
2608
- if (this.lastClickedItem === attributePath &&
2609
- currentTime - this.lastClickTime <= this.doubleClickDelay) {
2610
- // Double-click detected - move attribute to selected
2611
- this.moveAttributeToSelected(dataItem);
2612
- // Reset to prevent triple-click
2613
- this.lastClickTime = 0;
2614
- this.lastClickedItem = null;
2615
- }
2616
- else {
2617
- // Single click - just update tracking
2618
- this.lastClickTime = currentTime;
2619
- this.lastClickedItem = attributePath;
2492
+ // Dialog
2493
+ async openDialog(event) {
2494
+ if (event) {
2495
+ event.preventDefault();
2496
+ event.stopPropagation();
2620
2497
  }
2621
- }
2622
- /**
2623
- * Handle cell click on selected attributes grid to detect double-click
2624
- */
2625
- onSelectedCellClick(event) {
2626
- const dataItem = event.dataItem;
2627
- if (!dataItem)
2498
+ if (!this.dialogService) {
2499
+ console.warn('CkTypeSelectorDialogService not available');
2628
2500
  return;
2629
- const currentTime = Date.now();
2630
- const attributePath = dataItem.attributePath;
2631
- if (this.lastClickedItem === attributePath &&
2632
- currentTime - this.lastClickTime <= this.doubleClickDelay) {
2633
- // Double-click detected - move attribute to available
2634
- this.moveAttributeToAvailable(dataItem);
2635
- // Reset to prevent triple-click
2636
- this.lastClickTime = 0;
2637
- this.lastClickedItem = null;
2638
2501
  }
2639
- else {
2640
- // Single click - just update tracking
2641
- this.lastClickTime = currentTime;
2642
- this.lastClickedItem = attributePath;
2502
+ this.autocomplete.toggle(false);
2503
+ const result = await this.dialogService.openCkTypeSelector({
2504
+ selectedCkTypeId: this.selectedCkType?.fullName,
2505
+ ckModelIds: this.ckModelIds,
2506
+ dialogTitle: this.dialogTitle,
2507
+ allowAbstract: this.allowAbstract,
2508
+ derivedFromRtCkTypeId: this.derivedFromRtCkTypeId
2509
+ });
2510
+ if (result.confirmed && result.selectedCkType) {
2511
+ this.selectCkType(result.selectedCkType);
2643
2512
  }
2644
2513
  }
2645
- /**
2514
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CkTypeSelectorInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2515
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.0", 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", derivedFromRtCkTypeId: "derivedFromRtCkTypeId", disabled: "disabled", required: "required" }, outputs: { ckTypeSelected: "ckTypeSelected", ckTypeCleared: "ckTypeCleared" }, providers: [
2516
+ {
2517
+ provide: NG_VALUE_ACCESSOR,
2518
+ useExisting: forwardRef(() => CkTypeSelectorInputComponent),
2519
+ multi: true
2520
+ },
2521
+ {
2522
+ provide: NG_VALIDATORS,
2523
+ useExisting: forwardRef(() => CkTypeSelectorInputComponent),
2524
+ multi: true
2525
+ }
2526
+ ], viewQueries: [{ propertyName: "autocomplete", first: true, predicate: ["autocomplete"], descendants: true, static: true }], ngImport: i0, template: `
2527
+ <div class="ck-type-select-wrapper" [class.disabled]="disabled">
2528
+ <kendo-autocomplete
2529
+ #autocomplete
2530
+ [formControl]="searchFormControl"
2531
+ [data]="filteredTypes"
2532
+ [loading]="isLoading"
2533
+ [placeholder]="placeholder"
2534
+ [suggest]="true"
2535
+ [clearButton]="true"
2536
+ [filterable]="true"
2537
+ [popupSettings]="popupSettings"
2538
+ (filterChange)="onFilterChange($event)"
2539
+ (valueChange)="onSelectionChange($event)"
2540
+ (blur)="onBlur()"
2541
+ (focus)="onFocus()"
2542
+ class="ck-type-autocomplete">
2543
+
2544
+ <ng-template kendoAutoCompleteItemTemplate let-dataItem>
2545
+ <div class="ck-type-item">
2546
+ {{ dataItem }}
2547
+ </div>
2548
+ </ng-template>
2549
+
2550
+ <ng-template kendoAutoCompleteNoDataTemplate>
2551
+ <div class="no-data-message">
2552
+ <span *ngIf="!isLoading && searchFormControl.value && searchFormControl.value.length >= minSearchLength">
2553
+ No types found for "{{ searchFormControl.value }}"
2554
+ </span>
2555
+ <span *ngIf="!isLoading && (!searchFormControl.value || searchFormControl.value.length < minSearchLength)">
2556
+ Type at least {{ minSearchLength }} characters to search...
2557
+ </span>
2558
+ </div>
2559
+ </ng-template>
2560
+
2561
+ <ng-template kendoAutoCompleteFooterTemplate>
2562
+ <div class="advanced-search-footer" (click)="openDialog($event)">
2563
+ <kendo-svg-icon [icon]="searchIcon" size="small"></kendo-svg-icon>
2564
+ <span>{{ advancedSearchLabel }}</span>
2565
+ </div>
2566
+ </ng-template>
2567
+
2568
+ </kendo-autocomplete>
2569
+
2570
+ <button
2571
+ kendoButton
2572
+ type="button"
2573
+ [svgIcon]="searchIcon"
2574
+ [disabled]="disabled"
2575
+ [title]="advancedSearchLabel"
2576
+ class="dialog-button"
2577
+ (click)="openDialog()">
2578
+ </button>
2579
+ </div>
2580
+ `, 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: i4.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 }] });
2581
+ }
2582
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CkTypeSelectorInputComponent, decorators: [{
2583
+ type: Component,
2584
+ args: [{ selector: 'mm-ck-type-selector-input', standalone: true, imports: [
2585
+ CommonModule,
2586
+ ReactiveFormsModule,
2587
+ AutoCompleteModule,
2588
+ LoaderModule,
2589
+ ButtonsModule,
2590
+ IconsModule,
2591
+ SVGIconModule
2592
+ ], providers: [
2593
+ {
2594
+ provide: NG_VALUE_ACCESSOR,
2595
+ useExisting: forwardRef(() => CkTypeSelectorInputComponent),
2596
+ multi: true
2597
+ },
2598
+ {
2599
+ provide: NG_VALIDATORS,
2600
+ useExisting: forwardRef(() => CkTypeSelectorInputComponent),
2601
+ multi: true
2602
+ }
2603
+ ], template: `
2604
+ <div class="ck-type-select-wrapper" [class.disabled]="disabled">
2605
+ <kendo-autocomplete
2606
+ #autocomplete
2607
+ [formControl]="searchFormControl"
2608
+ [data]="filteredTypes"
2609
+ [loading]="isLoading"
2610
+ [placeholder]="placeholder"
2611
+ [suggest]="true"
2612
+ [clearButton]="true"
2613
+ [filterable]="true"
2614
+ [popupSettings]="popupSettings"
2615
+ (filterChange)="onFilterChange($event)"
2616
+ (valueChange)="onSelectionChange($event)"
2617
+ (blur)="onBlur()"
2618
+ (focus)="onFocus()"
2619
+ class="ck-type-autocomplete">
2620
+
2621
+ <ng-template kendoAutoCompleteItemTemplate let-dataItem>
2622
+ <div class="ck-type-item">
2623
+ {{ dataItem }}
2624
+ </div>
2625
+ </ng-template>
2626
+
2627
+ <ng-template kendoAutoCompleteNoDataTemplate>
2628
+ <div class="no-data-message">
2629
+ <span *ngIf="!isLoading && searchFormControl.value && searchFormControl.value.length >= minSearchLength">
2630
+ No types found for "{{ searchFormControl.value }}"
2631
+ </span>
2632
+ <span *ngIf="!isLoading && (!searchFormControl.value || searchFormControl.value.length < minSearchLength)">
2633
+ Type at least {{ minSearchLength }} characters to search...
2634
+ </span>
2635
+ </div>
2636
+ </ng-template>
2637
+
2638
+ <ng-template kendoAutoCompleteFooterTemplate>
2639
+ <div class="advanced-search-footer" (click)="openDialog($event)">
2640
+ <kendo-svg-icon [icon]="searchIcon" size="small"></kendo-svg-icon>
2641
+ <span>{{ advancedSearchLabel }}</span>
2642
+ </div>
2643
+ </ng-template>
2644
+
2645
+ </kendo-autocomplete>
2646
+
2647
+ <button
2648
+ kendoButton
2649
+ type="button"
2650
+ [svgIcon]="searchIcon"
2651
+ [disabled]="disabled"
2652
+ [title]="advancedSearchLabel"
2653
+ class="dialog-button"
2654
+ (click)="openDialog()">
2655
+ </button>
2656
+ </div>
2657
+ `, 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"] }]
2658
+ }], propDecorators: { autocomplete: [{
2659
+ type: ViewChild,
2660
+ args: ['autocomplete', { static: true }]
2661
+ }], placeholder: [{
2662
+ type: Input
2663
+ }], minSearchLength: [{
2664
+ type: Input
2665
+ }], maxResults: [{
2666
+ type: Input
2667
+ }], debounceMs: [{
2668
+ type: Input
2669
+ }], ckModelIds: [{
2670
+ type: Input
2671
+ }], allowAbstract: [{
2672
+ type: Input
2673
+ }], dialogTitle: [{
2674
+ type: Input
2675
+ }], advancedSearchLabel: [{
2676
+ type: Input
2677
+ }], derivedFromRtCkTypeId: [{
2678
+ type: Input
2679
+ }], disabled: [{
2680
+ type: Input
2681
+ }], required: [{
2682
+ type: Input
2683
+ }], ckTypeSelected: [{
2684
+ type: Output
2685
+ }], ckTypeCleared: [{
2686
+ type: Output
2687
+ }] } });
2688
+
2689
+ class AttributeSelectorDialogComponent {
2690
+ windowRef = inject(WindowRef);
2691
+ attributeService = inject(AttributeSelectorService);
2692
+ searchSubject = new Subject();
2693
+ arrowRightIcon = arrowRightIcon;
2694
+ arrowLeftIcon = arrowLeftIcon;
2695
+ chevronDoubleRightIcon = chevronDoubleRightIcon;
2696
+ chevronDoubleLeftIcon = chevronDoubleLeftIcon;
2697
+ searchIcon = searchIcon;
2698
+ arrowUpIcon = arrowUpIcon;
2699
+ arrowDownIcon = arrowDownIcon;
2700
+ data;
2701
+ dialogTitle = 'Select Attributes';
2702
+ rtCkTypeId;
2703
+ singleSelect = false;
2704
+ searchText = '';
2705
+ selectedSingleKey = [];
2706
+ selectedValueTypeFilter = null;
2707
+ includeNavigationProperties = true;
2708
+ maxDepth = null;
2709
+ availableAttributes = [];
2710
+ selectedAttributes = [];
2711
+ availableGridData = { data: [], total: 0 };
2712
+ selectedGridData = { data: [], total: 0 };
2713
+ selectedAvailableKeys = [];
2714
+ selectedChosenKeys = [];
2715
+ valueTypeOptions = [
2716
+ { text: 'All Types', value: null },
2717
+ { text: 'String', value: AttributeValueTypeDto$1.StringDto },
2718
+ { text: 'Integer', value: AttributeValueTypeDto$1.IntegerDto },
2719
+ { text: 'Double', value: AttributeValueTypeDto$1.DoubleDto },
2720
+ { text: 'Boolean', value: AttributeValueTypeDto$1.BooleanDto },
2721
+ { text: 'DateTime', value: AttributeValueTypeDto$1.DateTimeDto },
2722
+ { text: 'DateTimeOffset', value: AttributeValueTypeDto$1.DateTimeOffsetDto },
2723
+ { text: 'Enum', value: AttributeValueTypeDto$1.EnumDto },
2724
+ { text: 'TimeSpan', value: AttributeValueTypeDto$1.TimeSpanDto }
2725
+ ];
2726
+ // Double-click tracking
2727
+ lastClickTime = 0;
2728
+ lastClickedItem = null;
2729
+ doubleClickDelay = 300; // milliseconds
2730
+ ngOnInit() {
2731
+ if (this.data) {
2732
+ this.rtCkTypeId = this.data.rtCkTypeId;
2733
+ this.dialogTitle = this.data.dialogTitle || 'Select Attributes';
2734
+ this.singleSelect = this.data.singleSelect ?? false;
2735
+ this.includeNavigationProperties = this.data.includeNavigationProperties ?? true;
2736
+ this.maxDepth = this.data.maxDepth ?? null;
2737
+ if (this.data.selectedAttributes && this.data.selectedAttributes.length > 0) {
2738
+ if (this.singleSelect) {
2739
+ this.selectedSingleKey = [this.data.selectedAttributes[0]];
2740
+ }
2741
+ else {
2742
+ // Pre-populate selected attributes if provided
2743
+ this.loadInitialSelectedAttributes(this.data.selectedAttributes);
2744
+ }
2745
+ }
2746
+ }
2747
+ // Set up search debouncing
2748
+ this.searchSubject.pipe(debounceTime(300), distinctUntilChanged()).subscribe(searchText => {
2749
+ this.loadAvailableAttributes(searchText);
2750
+ });
2751
+ // Load initial attributes
2752
+ this.loadAvailableAttributes();
2753
+ }
2754
+ loadAvailableAttributes(searchTerm) {
2755
+ this.attributeService.getAvailableAttributes(this.rtCkTypeId, undefined, undefined, undefined, this.selectedValueTypeFilter || undefined, searchTerm || undefined, this.includeNavigationProperties, this.maxDepth ?? undefined).subscribe(result => {
2756
+ // Filter out already selected attributes
2757
+ const selectedPaths = new Set(this.selectedAttributes.map(a => a.attributePath));
2758
+ this.availableAttributes = result.items.filter(item => !selectedPaths.has(item.attributePath));
2759
+ this.updateAvailableGrid();
2760
+ });
2761
+ }
2762
+ loadInitialSelectedAttributes(attributePaths) {
2763
+ // Load all attributes to get the details for selected ones
2764
+ this.attributeService.getAvailableAttributes(this.rtCkTypeId).subscribe(result => {
2765
+ // Create a map for quick lookup
2766
+ const attributeMap = new Map(result.items.map(item => [item.attributePath, item]));
2767
+ // Preserve the order from attributePaths
2768
+ this.selectedAttributes = attributePaths
2769
+ .map(path => attributeMap.get(path))
2770
+ .filter((item) => item !== undefined);
2771
+ this.updateSelectedGrid();
2772
+ // Filter out selected from available
2773
+ const selectedPaths = new Set(this.selectedAttributes.map(a => a.attributePath));
2774
+ this.availableAttributes = result.items.filter(item => !selectedPaths.has(item.attributePath));
2775
+ this.updateAvailableGrid();
2776
+ });
2777
+ }
2778
+ onSearchChange(value) {
2779
+ this.searchSubject.next(value);
2780
+ }
2781
+ onValueTypeFilterChange(_value) {
2782
+ this.loadAvailableAttributes(this.searchText || undefined);
2783
+ }
2784
+ onNavigationPropertiesChange() {
2785
+ if (!this.includeNavigationProperties) {
2786
+ this.maxDepth = null;
2787
+ }
2788
+ this.loadAvailableAttributes(this.searchText || undefined);
2789
+ }
2790
+ onMaxDepthChange(value) {
2791
+ this.maxDepth = value;
2792
+ this.loadAvailableAttributes(this.searchText || undefined);
2793
+ }
2794
+ addSelected() {
2795
+ const itemsToAdd = this.availableAttributes.filter(item => this.selectedAvailableKeys.includes(item.attributePath));
2796
+ this.selectedAttributes.push(...itemsToAdd);
2797
+ this.availableAttributes = this.availableAttributes.filter(item => !this.selectedAvailableKeys.includes(item.attributePath));
2798
+ this.selectedAvailableKeys = [];
2799
+ this.updateGrids();
2800
+ }
2801
+ removeSelected() {
2802
+ const itemsToRemove = this.selectedAttributes.filter(item => this.selectedChosenKeys.includes(item.attributePath));
2803
+ this.availableAttributes.push(...itemsToRemove);
2804
+ this.selectedAttributes = this.selectedAttributes.filter(item => !this.selectedChosenKeys.includes(item.attributePath));
2805
+ this.selectedChosenKeys = [];
2806
+ this.sortAvailableAttributes();
2807
+ this.updateGrids();
2808
+ }
2809
+ addAll() {
2810
+ this.selectedAttributes.push(...this.availableAttributes);
2811
+ this.availableAttributes = [];
2812
+ this.selectedAvailableKeys = [];
2813
+ this.updateGrids();
2814
+ }
2815
+ removeAll() {
2816
+ this.availableAttributes.push(...this.selectedAttributes);
2817
+ this.selectedAttributes = [];
2818
+ this.selectedChosenKeys = [];
2819
+ this.sortAvailableAttributes();
2820
+ this.updateGrids();
2821
+ }
2822
+ sortAvailableAttributes() {
2823
+ this.availableAttributes.sort((a, b) => a.attributePath.localeCompare(b.attributePath));
2824
+ }
2825
+ canMoveUp() {
2826
+ if (this.selectedChosenKeys.length !== 1)
2827
+ return false;
2828
+ const index = this.selectedAttributes.findIndex(a => a.attributePath === this.selectedChosenKeys[0]);
2829
+ return index > 0;
2830
+ }
2831
+ canMoveDown() {
2832
+ if (this.selectedChosenKeys.length !== 1)
2833
+ return false;
2834
+ const index = this.selectedAttributes.findIndex(a => a.attributePath === this.selectedChosenKeys[0]);
2835
+ return index >= 0 && index < this.selectedAttributes.length - 1;
2836
+ }
2837
+ moveUp() {
2838
+ if (!this.canMoveUp())
2839
+ return;
2840
+ const index = this.selectedAttributes.findIndex(a => a.attributePath === this.selectedChosenKeys[0]);
2841
+ [this.selectedAttributes[index - 1], this.selectedAttributes[index]] =
2842
+ [this.selectedAttributes[index], this.selectedAttributes[index - 1]];
2843
+ this.updateSelectedGrid();
2844
+ }
2845
+ moveDown() {
2846
+ if (!this.canMoveDown())
2847
+ return;
2848
+ const index = this.selectedAttributes.findIndex(a => a.attributePath === this.selectedChosenKeys[0]);
2849
+ [this.selectedAttributes[index], this.selectedAttributes[index + 1]] =
2850
+ [this.selectedAttributes[index + 1], this.selectedAttributes[index]];
2851
+ this.updateSelectedGrid();
2852
+ }
2853
+ updateGrids() {
2854
+ this.updateAvailableGrid();
2855
+ this.updateSelectedGrid();
2856
+ }
2857
+ updateAvailableGrid() {
2858
+ this.availableGridData = {
2859
+ data: this.availableAttributes,
2860
+ total: this.availableAttributes.length
2861
+ };
2862
+ }
2863
+ updateSelectedGrid() {
2864
+ this.selectedGridData = {
2865
+ data: this.selectedAttributes,
2866
+ total: this.selectedAttributes.length
2867
+ };
2868
+ }
2869
+ onCancel() {
2870
+ this.windowRef.close();
2871
+ }
2872
+ onConfirm() {
2873
+ if (this.singleSelect) {
2874
+ const selected = this.availableAttributes.find(a => a.attributePath === this.selectedSingleKey[0]);
2875
+ const result = {
2876
+ selectedAttributes: selected ? [selected] : []
2877
+ };
2878
+ this.windowRef.close(result);
2879
+ }
2880
+ else {
2881
+ const result = {
2882
+ selectedAttributes: this.selectedAttributes
2883
+ };
2884
+ this.windowRef.close(result);
2885
+ }
2886
+ }
2887
+ /**
2888
+ * Handle cell click on available attributes grid to detect double-click
2889
+ */
2890
+ onAvailableCellClick(event) {
2891
+ const dataItem = event.dataItem;
2892
+ if (!dataItem)
2893
+ return;
2894
+ const currentTime = Date.now();
2895
+ const attributePath = dataItem.attributePath;
2896
+ if (this.lastClickedItem === attributePath &&
2897
+ currentTime - this.lastClickTime <= this.doubleClickDelay) {
2898
+ // Double-click detected - move attribute to selected
2899
+ this.moveAttributeToSelected(dataItem);
2900
+ // Reset to prevent triple-click
2901
+ this.lastClickTime = 0;
2902
+ this.lastClickedItem = null;
2903
+ }
2904
+ else {
2905
+ // Single click - just update tracking
2906
+ this.lastClickTime = currentTime;
2907
+ this.lastClickedItem = attributePath;
2908
+ }
2909
+ }
2910
+ /**
2911
+ * Handle cell click on selected attributes grid to detect double-click
2912
+ */
2913
+ onSelectedCellClick(event) {
2914
+ const dataItem = event.dataItem;
2915
+ if (!dataItem)
2916
+ return;
2917
+ const currentTime = Date.now();
2918
+ const attributePath = dataItem.attributePath;
2919
+ if (this.lastClickedItem === attributePath &&
2920
+ currentTime - this.lastClickTime <= this.doubleClickDelay) {
2921
+ // Double-click detected - move attribute to available
2922
+ this.moveAttributeToAvailable(dataItem);
2923
+ // Reset to prevent triple-click
2924
+ this.lastClickTime = 0;
2925
+ this.lastClickedItem = null;
2926
+ }
2927
+ else {
2928
+ // Single click - just update tracking
2929
+ this.lastClickTime = currentTime;
2930
+ this.lastClickedItem = attributePath;
2931
+ }
2932
+ }
2933
+ /**
2646
2934
  * Handle cell click on single-select grid to detect double-click (confirm immediately)
2647
2935
  */
2648
2936
  onSingleSelectCellClick(event) {
@@ -3011,6 +3299,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
3011
3299
 
3012
3300
  class AttributeSelectorDialogService {
3013
3301
  windowService = inject(WindowService);
3302
+ windowStateService = inject(WindowStateService);
3014
3303
  /**
3015
3304
  * Opens the attribute selector dialog
3016
3305
  * @param rtCkTypeId The RtCkType ID to fetch attributes for
@@ -3026,455 +3315,540 @@ class AttributeSelectorDialogService {
3026
3315
  dialogTitle,
3027
3316
  singleSelect
3028
3317
  };
3318
+ const dialogKey = singleSelect ? 'attribute-selector-single' : 'attribute-selector';
3319
+ const defaultWidth = singleSelect ? 550 : 1000;
3320
+ const defaultHeight = singleSelect ? 650 : 700;
3321
+ const size = this.windowStateService.resolveWindowSize(dialogKey, { width: defaultWidth, height: defaultHeight });
3029
3322
  const windowRef = this.windowService.open({
3030
3323
  content: AttributeSelectorDialogComponent,
3031
- width: singleSelect ? 550 : 1000,
3032
- height: singleSelect ? 650 : 700,
3324
+ width: size.width,
3325
+ height: size.height,
3033
3326
  minWidth: singleSelect ? 450 : 850,
3034
3327
  minHeight: singleSelect ? 550 : 650,
3035
3328
  resizable: true,
3036
3329
  title: dialogTitle || 'Select Attributes'
3037
3330
  });
3038
- // Pass data to the component
3039
- const contentRef = windowRef.content;
3040
- if (contentRef?.instance) {
3041
- contentRef.instance.data = data;
3042
- }
3043
- try {
3044
- const result = await firstValueFrom(windowRef.result);
3045
- if (result instanceof WindowCloseResult) {
3046
- // User closed the window via X button
3047
- return {
3048
- confirmed: false,
3049
- selectedAttributes: []
3050
- };
3051
- }
3052
- if (result && typeof result === 'object' && 'selectedAttributes' in result) {
3053
- // User clicked OK and we have a result
3054
- const dialogResult = result;
3055
- return {
3056
- confirmed: true,
3057
- selectedAttributes: dialogResult.selectedAttributes
3058
- };
3059
- }
3060
- else {
3061
- // User clicked Cancel or closed dialog
3062
- return {
3063
- confirmed: false,
3064
- selectedAttributes: []
3065
- };
3066
- }
3067
- }
3068
- catch {
3069
- // Dialog was closed without result (e.g., ESC key, X button)
3070
- return {
3071
- confirmed: false,
3072
- selectedAttributes: []
3073
- };
3074
- }
3075
- }
3076
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AttributeSelectorDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3077
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AttributeSelectorDialogService });
3078
- }
3079
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AttributeSelectorDialogService, decorators: [{
3080
- type: Injectable
3081
- }] });
3082
-
3083
- class CkTypeSelectorInputComponent {
3084
- autocomplete;
3085
- placeholder = 'Select a CK type...';
3086
- minSearchLength = 2;
3087
- maxResults = 50;
3088
- debounceMs = 300;
3089
- ckModelIds;
3090
- allowAbstract = false;
3091
- dialogTitle = 'Select Construction Kit Type';
3092
- advancedSearchLabel = 'Advanced Search...';
3093
- derivedFromRtCkTypeId;
3094
- _disabled = false;
3095
- get disabled() {
3096
- return this._disabled;
3097
- }
3098
- set disabled(value) {
3099
- this._disabled = !!value;
3100
- if (this._disabled) {
3101
- this.searchFormControl.disable();
3102
- }
3103
- else {
3104
- this.searchFormControl.enable();
3105
- }
3106
- }
3107
- _required = false;
3108
- get required() {
3109
- return this._required;
3110
- }
3111
- set required(value) {
3112
- this._required = !!value;
3113
- }
3114
- ckTypeSelected = new EventEmitter();
3115
- ckTypeCleared = new EventEmitter();
3116
- searchFormControl = new FormControl();
3117
- filteredTypes = [];
3118
- selectedCkType = null;
3119
- isLoading = false;
3120
- typeMap = new Map();
3121
- searchSubject = new Subject();
3122
- subscriptions = new Subscription();
3123
- onChange = () => { };
3124
- onTouched = () => { };
3125
- searchIcon = searchIcon;
3126
- popupSettings = { appendTo: 'root', popupClass: 'mm-ck-type-popup' };
3127
- static popupStyleInjected = false;
3128
- ckTypeSelectorService = inject(CkTypeSelectorService);
3129
- dialogService = inject(CkTypeSelectorDialogService, { optional: true });
3130
- elementRef = inject(ElementRef);
3131
- ngOnInit() {
3132
- this.setupSearch();
3133
- this.injectPopupStyles();
3134
- }
3135
- ngOnDestroy() {
3136
- this.subscriptions.unsubscribe();
3137
- this.searchSubject.complete();
3138
- }
3139
- // ControlValueAccessor implementation
3140
- writeValue(value) {
3141
- if (value !== this.selectedCkType) {
3142
- this.selectedCkType = value;
3143
- if (value) {
3144
- this.searchFormControl.setValue(value.rtCkTypeId, { emitEvent: false });
3145
- }
3146
- else {
3147
- this.searchFormControl.setValue('', { emitEvent: false });
3148
- }
3149
- }
3150
- }
3151
- registerOnChange(fn) {
3152
- this.onChange = fn;
3153
- }
3154
- registerOnTouched(fn) {
3155
- this.onTouched = fn;
3156
- }
3157
- setDisabledState(isDisabled) {
3158
- this.disabled = isDisabled;
3159
- }
3160
- // Validator implementation
3161
- validate(control) {
3162
- if (this.required && !this.selectedCkType) {
3163
- return { required: true };
3164
- }
3165
- const value = control.value;
3166
- if (value && typeof value === 'string') {
3167
- return { invalidCkType: true };
3168
- }
3169
- return null;
3170
- }
3171
- // Event handlers
3172
- onFilterChange(filter) {
3173
- if (!filter || filter.length < this.minSearchLength) {
3174
- this.filteredTypes = [];
3175
- return;
3331
+ this.windowStateService.applyModalBehavior(dialogKey, windowRef);
3332
+ // Pass data to the component
3333
+ const contentRef = windowRef.content;
3334
+ if (contentRef?.instance) {
3335
+ contentRef.instance.data = data;
3176
3336
  }
3177
- this.searchSubject.next(filter);
3178
- }
3179
- onSelectionChange(value) {
3180
- if (value && typeof value === 'string') {
3181
- const ckType = this.typeMap.get(value);
3182
- if (ckType) {
3183
- this.selectCkType(ckType);
3337
+ try {
3338
+ const result = await firstValueFrom(windowRef.result);
3339
+ if (result instanceof WindowCloseResult) {
3340
+ return {
3341
+ confirmed: false,
3342
+ selectedAttributes: []
3343
+ };
3184
3344
  }
3185
- }
3186
- }
3187
- onFocus() {
3188
- const currentValue = this.searchFormControl.value;
3189
- if (currentValue && currentValue.length >= this.minSearchLength && this.filteredTypes.length === 0) {
3190
- this.searchSubject.next(currentValue);
3191
- }
3192
- }
3193
- onBlur() {
3194
- this.onTouched();
3195
- if (this.filteredTypes.length === 1 && !this.selectedCkType) {
3196
- const displayText = this.filteredTypes[0];
3197
- const ckType = this.typeMap.get(displayText);
3198
- if (ckType) {
3199
- this.selectCkType(ckType);
3345
+ if (result && typeof result === 'object' && 'selectedAttributes' in result) {
3346
+ const dialogResult = result;
3347
+ return {
3348
+ confirmed: true,
3349
+ selectedAttributes: dialogResult.selectedAttributes
3350
+ };
3351
+ }
3352
+ else {
3353
+ return {
3354
+ confirmed: false,
3355
+ selectedAttributes: []
3356
+ };
3200
3357
  }
3201
3358
  }
3202
- }
3203
- // Public methods
3204
- clear() {
3205
- this.selectedCkType = null;
3206
- this.filteredTypes = [];
3207
- this.typeMap.clear();
3208
- this.searchFormControl.setValue('', { emitEvent: false });
3209
- this.onChange(null);
3210
- this.ckTypeCleared.emit();
3211
- this.autocomplete.focus();
3212
- }
3213
- focus() {
3214
- if (this.autocomplete) {
3215
- this.autocomplete.focus();
3359
+ catch {
3360
+ return {
3361
+ confirmed: false,
3362
+ selectedAttributes: []
3363
+ };
3216
3364
  }
3217
3365
  }
3218
- reset() {
3219
- this.clear();
3220
- }
3221
- // Private methods
3222
- setupSearch() {
3223
- this.subscriptions.add(this.searchSubject.pipe(debounceTime(this.debounceMs), distinctUntilChanged(), tap(() => {
3224
- this.isLoading = true;
3225
- this.filteredTypes = [];
3226
- }), switchMap(filter => {
3227
- const source$ = this.derivedFromRtCkTypeId
3228
- ? this.ckTypeSelectorService.getDerivedCkTypes(this.derivedFromRtCkTypeId, {
3229
- searchText: filter
3230
- })
3231
- : this.ckTypeSelectorService.getCkTypes({
3232
- ckModelIds: this.ckModelIds,
3233
- searchText: filter,
3234
- first: this.maxResults
3235
- });
3236
- return source$.pipe(catchError(error => {
3237
- console.error('CK type search error:', error);
3238
- return of({ items: [], totalCount: 0 });
3239
- }));
3240
- })).subscribe(result => {
3241
- this.isLoading = false;
3242
- // Filter out abstract types if not allowed
3243
- let items = result.items;
3244
- if (!this.allowAbstract) {
3245
- items = items.filter(item => !item.isAbstract);
3246
- }
3247
- this.filteredTypes = items.map(item => item.rtCkTypeId);
3248
- this.typeMap = new Map(items.map(item => [item.rtCkTypeId, item]));
3249
- }));
3250
- }
3251
- injectPopupStyles() {
3252
- if (CkTypeSelectorInputComponent.popupStyleInjected)
3366
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AttributeSelectorDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3367
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AttributeSelectorDialogService, providedIn: 'root' });
3368
+ }
3369
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AttributeSelectorDialogService, decorators: [{
3370
+ type: Injectable,
3371
+ args: [{
3372
+ providedIn: 'root'
3373
+ }]
3374
+ }] });
3375
+
3376
+ class RuntimeEntityVariableDialogComponent {
3377
+ windowRef = inject(WindowRef);
3378
+ getEntitiesByCkTypeGQL = inject(GetEntitiesByCkTypeDtoGQL);
3379
+ attributeSelectorDialogService = inject(AttributeSelectorDialogService);
3380
+ pencilIcon = pencilIcon;
3381
+ trashIcon = trashIcon;
3382
+ /** Data passed to the dialog (set by service) */
3383
+ data = {};
3384
+ // Signals
3385
+ selectedCkType = signal(null, ...(ngDevMode ? [{ debugName: "selectedCkType" }] : []));
3386
+ selectedEntity = signal(null, ...(ngDevMode ? [{ debugName: "selectedEntity" }] : []));
3387
+ variableMappings = signal([], ...(ngDevMode ? [{ debugName: "variableMappings" }] : []));
3388
+ entityDataSource = computed(() => {
3389
+ const ckType = this.selectedCkType();
3390
+ if (!ckType)
3391
+ return null;
3392
+ return new RuntimeEntitySelectDataSource(this.getEntitiesByCkTypeGQL, ckType.rtCkTypeId);
3393
+ }, ...(ngDevMode ? [{ debugName: "entityDataSource" }] : []));
3394
+ entityDialogDataSource = computed(() => {
3395
+ const ckType = this.selectedCkType();
3396
+ if (!ckType)
3397
+ return null;
3398
+ return new RuntimeEntityDialogDataSource(this.getEntitiesByCkTypeGQL, ckType.rtCkTypeId);
3399
+ }, ...(ngDevMode ? [{ debugName: "entityDialogDataSource" }] : []));
3400
+ VALID_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
3401
+ onCkTypeSelected(ckType) {
3402
+ this.selectedCkType.set(ckType);
3403
+ this.selectedEntity.set(null);
3404
+ // Don't clear mappings — keep them if user is just switching type
3405
+ }
3406
+ onCkTypeCleared() {
3407
+ this.selectedCkType.set(null);
3408
+ this.selectedEntity.set(null);
3409
+ }
3410
+ onEntitySelected(entity) {
3411
+ this.selectedEntity.set(entity);
3412
+ }
3413
+ onEntityCleared() {
3414
+ this.selectedEntity.set(null);
3415
+ }
3416
+ async openAttributeSelector() {
3417
+ const ckType = this.selectedCkType();
3418
+ if (!ckType)
3253
3419
  return;
3254
- const style = document.createElement('style');
3255
- style.setAttribute('data-mm-ck-type-popup', '');
3256
- style.textContent = `
3257
- .mm-ck-type-popup {
3258
- max-width: 500px !important;
3259
- min-width: 0 !important;
3260
- }
3261
- .mm-ck-type-popup .k-child-animation-container {
3262
- max-width: 500px !important;
3263
- }
3264
- .mm-ck-type-popup .k-list-item {
3265
- padding: 4px 12px !important;
3266
- min-height: 0 !important;
3267
- font-size: 13px !important;
3268
- line-height: 20px !important;
3269
- }
3270
- `;
3271
- document.head.appendChild(style);
3272
- CkTypeSelectorInputComponent.popupStyleInjected = true;
3420
+ const existingPaths = this.variableMappings().map(m => m.attributePath);
3421
+ const result = await this.attributeSelectorDialogService.openAttributeSelector(ckType.rtCkTypeId, existingPaths, 'Select Attributes');
3422
+ if (result.confirmed && result.selectedAttributes.length > 0) {
3423
+ this.addAttributeMappings(result.selectedAttributes);
3424
+ }
3425
+ }
3426
+ onNameChange(index, name) {
3427
+ const sanitized = name.replace(/[^a-zA-Z0-9_]/g, '');
3428
+ const mappings = [...this.variableMappings()];
3429
+ mappings[index] = { ...mappings[index], name: sanitized };
3430
+ this.variableMappings.set(mappings);
3431
+ }
3432
+ removeMapping(index) {
3433
+ const mappings = this.variableMappings().filter((_, i) => i !== index);
3434
+ this.variableMappings.set(mappings);
3435
+ }
3436
+ getNameError(name, index) {
3437
+ if (!name)
3438
+ return 'Name required';
3439
+ if (!this.VALID_NAME_REGEX.test(name))
3440
+ return 'Invalid name';
3441
+ if (this.isDuplicateName(name, index))
3442
+ return 'Duplicate';
3443
+ return null;
3273
3444
  }
3274
- selectCkType(ckType) {
3275
- this.selectedCkType = ckType;
3276
- this.searchFormControl.setValue(ckType.rtCkTypeId, { emitEvent: false });
3277
- this.filteredTypes = [];
3278
- this.onChange(ckType);
3279
- this.ckTypeSelected.emit(ckType);
3280
- this.autocomplete.toggle(false);
3445
+ isValid() {
3446
+ const entity = this.selectedEntity();
3447
+ const mappings = this.variableMappings();
3448
+ if (!entity || mappings.length === 0)
3449
+ return false;
3450
+ return mappings.every((m, i) => m.name &&
3451
+ this.VALID_NAME_REGEX.test(m.name) &&
3452
+ !this.isDuplicateName(m.name, i));
3281
3453
  }
3282
- // Dialog
3283
- async openDialog(event) {
3284
- if (event) {
3285
- event.preventDefault();
3286
- event.stopPropagation();
3287
- }
3288
- if (!this.dialogService) {
3289
- console.warn('CkTypeSelectorDialogService not available');
3454
+ onOk() {
3455
+ const ckType = this.selectedCkType();
3456
+ const entity = this.selectedEntity();
3457
+ if (!ckType || !entity)
3290
3458
  return;
3291
- }
3292
- this.autocomplete.toggle(false);
3293
- const result = await this.dialogService.openCkTypeSelector({
3294
- selectedCkTypeId: this.selectedCkType?.fullName,
3295
- ckModelIds: this.ckModelIds,
3296
- dialogTitle: this.dialogTitle,
3297
- allowAbstract: this.allowAbstract,
3298
- derivedFromRtCkTypeId: this.derivedFromRtCkTypeId
3299
- });
3300
- if (result.confirmed && result.selectedCkType) {
3301
- this.selectCkType(result.selectedCkType);
3302
- }
3459
+ const result = {
3460
+ entityCkTypeId: ckType.rtCkTypeId,
3461
+ entityRtId: entity.rtId,
3462
+ entityDisplayName: entity.displayName,
3463
+ variables: this.variableMappings()
3464
+ };
3465
+ this.windowRef.close(result);
3303
3466
  }
3304
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CkTypeSelectorInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3305
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.0", 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", derivedFromRtCkTypeId: "derivedFromRtCkTypeId", disabled: "disabled", required: "required" }, outputs: { ckTypeSelected: "ckTypeSelected", ckTypeCleared: "ckTypeCleared" }, providers: [
3306
- {
3307
- provide: NG_VALUE_ACCESSOR,
3308
- useExisting: forwardRef(() => CkTypeSelectorInputComponent),
3309
- multi: true
3310
- },
3311
- {
3312
- provide: NG_VALIDATORS,
3313
- useExisting: forwardRef(() => CkTypeSelectorInputComponent),
3314
- multi: true
3467
+ onCancel() {
3468
+ this.windowRef.close();
3469
+ }
3470
+ addAttributeMappings(attributes) {
3471
+ const existingMappings = this.variableMappings();
3472
+ const existingPaths = new Set(existingMappings.map(m => m.attributePath));
3473
+ const allNames = new Set([
3474
+ ...existingMappings.map(m => m.name),
3475
+ ...(this.data.existingVariableNames ?? [])
3476
+ ]);
3477
+ const newMappings = [];
3478
+ for (const attr of attributes) {
3479
+ if (existingPaths.has(attr.attributePath))
3480
+ continue;
3481
+ let name = this.attributePathToVariableName(attr.attributePath);
3482
+ let counter = 1;
3483
+ while (allNames.has(name)) {
3484
+ name = `${this.attributePathToVariableName(attr.attributePath)}${counter}`;
3485
+ counter++;
3315
3486
  }
3316
- ], viewQueries: [{ propertyName: "autocomplete", first: true, predicate: ["autocomplete"], descendants: true, static: true }], ngImport: i0, template: `
3317
- <div class="ck-type-select-wrapper" [class.disabled]="disabled">
3318
- <kendo-autocomplete
3319
- #autocomplete
3320
- [formControl]="searchFormControl"
3321
- [data]="filteredTypes"
3322
- [loading]="isLoading"
3323
- [placeholder]="placeholder"
3324
- [suggest]="true"
3325
- [clearButton]="true"
3326
- [filterable]="true"
3327
- [popupSettings]="popupSettings"
3328
- (filterChange)="onFilterChange($event)"
3329
- (valueChange)="onSelectionChange($event)"
3330
- (blur)="onBlur()"
3331
- (focus)="onFocus()"
3332
- class="ck-type-autocomplete">
3487
+ allNames.add(name);
3488
+ newMappings.push({
3489
+ name,
3490
+ attributePath: attr.attributePath,
3491
+ attributeValueType: attr.attributeValueType
3492
+ });
3493
+ }
3494
+ this.variableMappings.set([...existingMappings, ...newMappings]);
3495
+ }
3496
+ attributePathToVariableName(attributePath) {
3497
+ const parts = attributePath.split('.');
3498
+ return parts
3499
+ .map((part, index) => index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1))
3500
+ .join('');
3501
+ }
3502
+ isDuplicateName(name, currentIndex) {
3503
+ const mappings = this.variableMappings();
3504
+ const existingNames = this.data.existingVariableNames ?? [];
3505
+ // Check within dialog mappings
3506
+ if (mappings.some((m, i) => i !== currentIndex && m.name === name))
3507
+ return true;
3508
+ // Check against existing external variable names
3509
+ if (existingNames.includes(name))
3510
+ return true;
3511
+ return false;
3512
+ }
3513
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: RuntimeEntityVariableDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3514
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: RuntimeEntityVariableDialogComponent, isStandalone: true, selector: "mm-runtime-entity-variable-dialog", ngImport: i0, template: `
3515
+ <div class="dialog-content">
3516
+ <div class="form-row">
3517
+ <label class="form-label">CK Type</label>
3518
+ <mm-ck-type-selector-input
3519
+ [placeholder]="'Select CK Type...'"
3520
+ [minSearchLength]="2"
3521
+ (ckTypeSelected)="onCkTypeSelected($event)"
3522
+ (ckTypeCleared)="onCkTypeCleared()">
3523
+ </mm-ck-type-selector-input>
3524
+ </div>
3333
3525
 
3334
- <ng-template kendoAutoCompleteItemTemplate let-dataItem>
3335
- <div class="ck-type-item">
3336
- {{ dataItem }}
3337
- </div>
3338
- </ng-template>
3526
+ <div class="form-row">
3527
+ <label class="form-label">Entity</label>
3528
+ <mm-entity-select-input
3529
+ [dataSource]="entityDataSource()!"
3530
+ [dialogDataSource]="entityDialogDataSource()!"
3531
+ [placeholder]="'Select Entity...'"
3532
+ [disabled]="!selectedCkType()"
3533
+ [minSearchLength]="1"
3534
+ dialogTitle="Select Entity"
3535
+ (entitySelected)="onEntitySelected($event)"
3536
+ (entityCleared)="onEntityCleared()">
3537
+ </mm-entity-select-input>
3538
+ </div>
3339
3539
 
3340
- <ng-template kendoAutoCompleteNoDataTemplate>
3341
- <div class="no-data-message">
3342
- <span *ngIf="!isLoading && searchFormControl.value && searchFormControl.value.length >= minSearchLength">
3343
- No types found for "{{ searchFormControl.value }}"
3344
- </span>
3345
- <span *ngIf="!isLoading && (!searchFormControl.value || searchFormControl.value.length < minSearchLength)">
3346
- Type at least {{ minSearchLength }} characters to search...
3347
- </span>
3348
- </div>
3349
- </ng-template>
3540
+ <div class="form-row">
3541
+ <label class="form-label">Attributes</label>
3542
+ <button
3543
+ kendoButton
3544
+ (click)="openAttributeSelector()"
3545
+ [disabled]="!selectedCkType()"
3546
+ themeColor="primary"
3547
+ fillMode="outline">
3548
+ Select Attributes...
3549
+ </button>
3550
+ </div>
3350
3551
 
3351
- <ng-template kendoAutoCompleteFooterTemplate>
3352
- <div class="advanced-search-footer" (click)="openDialog($event)">
3353
- <kendo-svg-icon [icon]="searchIcon" size="small"></kendo-svg-icon>
3354
- <span>{{ advancedSearchLabel }}</span>
3552
+ @if (variableMappings().length > 0) {
3553
+ <div class="mappings-grid">
3554
+ <div class="grid-header">
3555
+ <span class="col-name">Variable Name</span>
3556
+ <span class="col-path">Attribute Path</span>
3557
+ <span class="col-type">Type</span>
3558
+ <span class="col-actions"></span>
3355
3559
  </div>
3356
- </ng-template>
3357
-
3358
- </kendo-autocomplete>
3560
+ @for (mapping of variableMappings(); track mapping.attributePath; let i = $index) {
3561
+ <div class="grid-row">
3562
+ <div class="col-name">
3563
+ <kendo-textbox
3564
+ [value]="mapping.name"
3565
+ (valueChange)="onNameChange(i, $event)"
3566
+ [style.width.%]="100">
3567
+ </kendo-textbox>
3568
+ @if (getNameError(mapping.name, i); as error) {
3569
+ <span class="error-text">{{ error }}</span>
3570
+ }
3571
+ </div>
3572
+ <div class="col-path">{{ mapping.attributePath }}</div>
3573
+ <div class="col-type">{{ mapping.attributeValueType }}</div>
3574
+ <div class="col-actions">
3575
+ <button
3576
+ kendoButton
3577
+ (click)="removeMapping(i)"
3578
+ [svgIcon]="trashIcon"
3579
+ fillMode="flat"
3580
+ themeColor="error"
3581
+ title="Remove">
3582
+ </button>
3583
+ </div>
3584
+ </div>
3585
+ }
3586
+ </div>
3587
+ }
3359
3588
 
3360
- <button
3361
- kendoButton
3362
- type="button"
3363
- [svgIcon]="searchIcon"
3364
- [disabled]="disabled"
3365
- [title]="advancedSearchLabel"
3366
- class="dialog-button"
3367
- (click)="openDialog()">
3368
- </button>
3589
+ <div class="dialog-actions">
3590
+ <button kendoButton (click)="onCancel()" fillMode="outline">Cancel</button>
3591
+ <button kendoButton (click)="onOk()" themeColor="primary" [disabled]="!isValid()">OK</button>
3592
+ </div>
3369
3593
  </div>
3370
- `, 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: i4.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 }] });
3594
+ `, isInline: true, styles: [".dialog-content{display:flex;flex-direction:column;gap:16px;padding:16px 20px}.form-row{display:flex;flex-direction:column;gap:4px}.form-label{font-size:12px;font-weight:600;color:var(--kendo-color-subtle, #666)}.mappings-grid{border:1px solid var(--kendo-color-border, #e0e0e0);border-radius:4px;background:var(--kendo-color-surface-alt, #fafafa)}.grid-header{display:grid;grid-template-columns:1fr 1fr 100px 40px;gap:.5rem;padding:.5rem .75rem;background:var(--kendo-color-surface, #f5f5f5);border-bottom:1px solid var(--kendo-color-border, #e0e0e0);font-size:12px;font-weight:600;color:var(--kendo-color-subtle, #666)}.grid-row{display:grid;grid-template-columns:1fr 1fr 100px 40px;gap:.5rem;padding:.5rem .75rem;align-items:start;border-bottom:1px solid var(--kendo-color-border, #e0e0e0)}.grid-row:last-child{border-bottom:none}.col-name{display:flex;flex-direction:column;gap:2px}.col-path,.col-type{font-size:13px;padding-top:6px;color:var(--kendo-color-on-app-surface, #424242)}.col-actions{display:flex;justify-content:center;padding-top:2px}.error-text{font-size:11px;color:var(--kendo-color-error, #f44336)}.dialog-actions{display:flex;justify-content:flex-end;gap:8px;padding-top:8px;border-top:1px solid var(--kendo-color-border, #e0e0e0)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i4.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: i5.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: GridModule }, { kind: "ngmodule", type: IconsModule }, { kind: "component", type: CkTypeSelectorInputComponent, selector: "mm-ck-type-selector-input", inputs: ["placeholder", "minSearchLength", "maxResults", "debounceMs", "ckModelIds", "allowAbstract", "dialogTitle", "advancedSearchLabel", "derivedFromRtCkTypeId", "disabled", "required"], outputs: ["ckTypeSelected", "ckTypeCleared"] }, { kind: "component", type: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "initialDisplayValue", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "dialogMessages", "messages", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }] });
3371
3595
  }
3372
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CkTypeSelectorInputComponent, decorators: [{
3373
- type: Component,
3374
- args: [{ selector: 'mm-ck-type-selector-input', standalone: true, imports: [
3375
- CommonModule,
3376
- ReactiveFormsModule,
3377
- AutoCompleteModule,
3378
- LoaderModule,
3596
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: RuntimeEntityVariableDialogComponent, decorators: [{
3597
+ type: Component,
3598
+ args: [{ selector: 'mm-runtime-entity-variable-dialog', standalone: true, imports: [
3599
+ CommonModule,
3600
+ FormsModule,
3379
3601
  ButtonsModule,
3602
+ InputsModule,
3603
+ GridModule,
3380
3604
  IconsModule,
3381
- SVGIconModule
3382
- ], providers: [
3383
- {
3384
- provide: NG_VALUE_ACCESSOR,
3385
- useExisting: forwardRef(() => CkTypeSelectorInputComponent),
3386
- multi: true
3387
- },
3388
- {
3389
- provide: NG_VALIDATORS,
3390
- useExisting: forwardRef(() => CkTypeSelectorInputComponent),
3391
- multi: true
3392
- }
3605
+ CkTypeSelectorInputComponent,
3606
+ EntitySelectInputComponent
3393
3607
  ], template: `
3394
- <div class="ck-type-select-wrapper" [class.disabled]="disabled">
3395
- <kendo-autocomplete
3396
- #autocomplete
3397
- [formControl]="searchFormControl"
3398
- [data]="filteredTypes"
3399
- [loading]="isLoading"
3400
- [placeholder]="placeholder"
3401
- [suggest]="true"
3402
- [clearButton]="true"
3403
- [filterable]="true"
3404
- [popupSettings]="popupSettings"
3405
- (filterChange)="onFilterChange($event)"
3406
- (valueChange)="onSelectionChange($event)"
3407
- (blur)="onBlur()"
3408
- (focus)="onFocus()"
3409
- class="ck-type-autocomplete">
3608
+ <div class="dialog-content">
3609
+ <div class="form-row">
3610
+ <label class="form-label">CK Type</label>
3611
+ <mm-ck-type-selector-input
3612
+ [placeholder]="'Select CK Type...'"
3613
+ [minSearchLength]="2"
3614
+ (ckTypeSelected)="onCkTypeSelected($event)"
3615
+ (ckTypeCleared)="onCkTypeCleared()">
3616
+ </mm-ck-type-selector-input>
3617
+ </div>
3410
3618
 
3411
- <ng-template kendoAutoCompleteItemTemplate let-dataItem>
3412
- <div class="ck-type-item">
3413
- {{ dataItem }}
3414
- </div>
3415
- </ng-template>
3619
+ <div class="form-row">
3620
+ <label class="form-label">Entity</label>
3621
+ <mm-entity-select-input
3622
+ [dataSource]="entityDataSource()!"
3623
+ [dialogDataSource]="entityDialogDataSource()!"
3624
+ [placeholder]="'Select Entity...'"
3625
+ [disabled]="!selectedCkType()"
3626
+ [minSearchLength]="1"
3627
+ dialogTitle="Select Entity"
3628
+ (entitySelected)="onEntitySelected($event)"
3629
+ (entityCleared)="onEntityCleared()">
3630
+ </mm-entity-select-input>
3631
+ </div>
3416
3632
 
3417
- <ng-template kendoAutoCompleteNoDataTemplate>
3418
- <div class="no-data-message">
3419
- <span *ngIf="!isLoading && searchFormControl.value && searchFormControl.value.length >= minSearchLength">
3420
- No types found for "{{ searchFormControl.value }}"
3421
- </span>
3422
- <span *ngIf="!isLoading && (!searchFormControl.value || searchFormControl.value.length < minSearchLength)">
3423
- Type at least {{ minSearchLength }} characters to search...
3424
- </span>
3425
- </div>
3426
- </ng-template>
3633
+ <div class="form-row">
3634
+ <label class="form-label">Attributes</label>
3635
+ <button
3636
+ kendoButton
3637
+ (click)="openAttributeSelector()"
3638
+ [disabled]="!selectedCkType()"
3639
+ themeColor="primary"
3640
+ fillMode="outline">
3641
+ Select Attributes...
3642
+ </button>
3643
+ </div>
3427
3644
 
3428
- <ng-template kendoAutoCompleteFooterTemplate>
3429
- <div class="advanced-search-footer" (click)="openDialog($event)">
3430
- <kendo-svg-icon [icon]="searchIcon" size="small"></kendo-svg-icon>
3431
- <span>{{ advancedSearchLabel }}</span>
3645
+ @if (variableMappings().length > 0) {
3646
+ <div class="mappings-grid">
3647
+ <div class="grid-header">
3648
+ <span class="col-name">Variable Name</span>
3649
+ <span class="col-path">Attribute Path</span>
3650
+ <span class="col-type">Type</span>
3651
+ <span class="col-actions"></span>
3432
3652
  </div>
3433
- </ng-template>
3434
-
3435
- </kendo-autocomplete>
3653
+ @for (mapping of variableMappings(); track mapping.attributePath; let i = $index) {
3654
+ <div class="grid-row">
3655
+ <div class="col-name">
3656
+ <kendo-textbox
3657
+ [value]="mapping.name"
3658
+ (valueChange)="onNameChange(i, $event)"
3659
+ [style.width.%]="100">
3660
+ </kendo-textbox>
3661
+ @if (getNameError(mapping.name, i); as error) {
3662
+ <span class="error-text">{{ error }}</span>
3663
+ }
3664
+ </div>
3665
+ <div class="col-path">{{ mapping.attributePath }}</div>
3666
+ <div class="col-type">{{ mapping.attributeValueType }}</div>
3667
+ <div class="col-actions">
3668
+ <button
3669
+ kendoButton
3670
+ (click)="removeMapping(i)"
3671
+ [svgIcon]="trashIcon"
3672
+ fillMode="flat"
3673
+ themeColor="error"
3674
+ title="Remove">
3675
+ </button>
3676
+ </div>
3677
+ </div>
3678
+ }
3679
+ </div>
3680
+ }
3436
3681
 
3437
- <button
3438
- kendoButton
3439
- type="button"
3440
- [svgIcon]="searchIcon"
3441
- [disabled]="disabled"
3442
- [title]="advancedSearchLabel"
3443
- class="dialog-button"
3444
- (click)="openDialog()">
3445
- </button>
3682
+ <div class="dialog-actions">
3683
+ <button kendoButton (click)="onCancel()" fillMode="outline">Cancel</button>
3684
+ <button kendoButton (click)="onOk()" themeColor="primary" [disabled]="!isValid()">OK</button>
3685
+ </div>
3446
3686
  </div>
3447
- `, 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"] }]
3448
- }], propDecorators: { autocomplete: [{
3449
- type: ViewChild,
3450
- args: ['autocomplete', { static: true }]
3451
- }], placeholder: [{
3452
- type: Input
3453
- }], minSearchLength: [{
3454
- type: Input
3455
- }], maxResults: [{
3456
- type: Input
3457
- }], debounceMs: [{
3458
- type: Input
3459
- }], ckModelIds: [{
3460
- type: Input
3461
- }], allowAbstract: [{
3462
- type: Input
3463
- }], dialogTitle: [{
3464
- type: Input
3465
- }], advancedSearchLabel: [{
3466
- type: Input
3467
- }], derivedFromRtCkTypeId: [{
3468
- type: Input
3469
- }], disabled: [{
3470
- type: Input
3471
- }], required: [{
3472
- type: Input
3473
- }], ckTypeSelected: [{
3474
- type: Output
3475
- }], ckTypeCleared: [{
3476
- type: Output
3477
- }] } });
3687
+ `, styles: [".dialog-content{display:flex;flex-direction:column;gap:16px;padding:16px 20px}.form-row{display:flex;flex-direction:column;gap:4px}.form-label{font-size:12px;font-weight:600;color:var(--kendo-color-subtle, #666)}.mappings-grid{border:1px solid var(--kendo-color-border, #e0e0e0);border-radius:4px;background:var(--kendo-color-surface-alt, #fafafa)}.grid-header{display:grid;grid-template-columns:1fr 1fr 100px 40px;gap:.5rem;padding:.5rem .75rem;background:var(--kendo-color-surface, #f5f5f5);border-bottom:1px solid var(--kendo-color-border, #e0e0e0);font-size:12px;font-weight:600;color:var(--kendo-color-subtle, #666)}.grid-row{display:grid;grid-template-columns:1fr 1fr 100px 40px;gap:.5rem;padding:.5rem .75rem;align-items:start;border-bottom:1px solid var(--kendo-color-border, #e0e0e0)}.grid-row:last-child{border-bottom:none}.col-name{display:flex;flex-direction:column;gap:2px}.col-path,.col-type{font-size:13px;padding-top:6px;color:var(--kendo-color-on-app-surface, #424242)}.col-actions{display:flex;justify-content:center;padding-top:2px}.error-text{font-size:11px;color:var(--kendo-color-error, #f44336)}.dialog-actions{display:flex;justify-content:flex-end;gap:8px;padding-top:8px;border-top:1px solid var(--kendo-color-border, #e0e0e0)}\n"] }]
3688
+ }] });
3689
+
3690
+ class RuntimeEntityVariableDialogService {
3691
+ windowService = inject(WindowService);
3692
+ windowStateService = inject(WindowStateService);
3693
+ /**
3694
+ * Opens the runtime entity variable dialog.
3695
+ * @param data Optional pre-populated data for editing
3696
+ * @returns Promise with confirmation status and dialog result
3697
+ */
3698
+ async openRuntimeEntityVariableDialog(data) {
3699
+ const size = this.windowStateService.resolveWindowSize('runtime-entity-variable', { width: 800, height: 600 });
3700
+ const windowRef = this.windowService.open({
3701
+ content: RuntimeEntityVariableDialogComponent,
3702
+ width: size.width,
3703
+ height: size.height,
3704
+ minWidth: 650,
3705
+ minHeight: 500,
3706
+ resizable: true,
3707
+ title: 'Runtime Entity Variables'
3708
+ });
3709
+ this.windowStateService.applyModalBehavior('runtime-entity-variable', windowRef);
3710
+ const contentRef = windowRef.content;
3711
+ if (contentRef?.instance && data) {
3712
+ contentRef.instance.data = data;
3713
+ }
3714
+ try {
3715
+ const result = await firstValueFrom(windowRef.result);
3716
+ if (result instanceof WindowCloseResult) {
3717
+ return { confirmed: false };
3718
+ }
3719
+ if (result && typeof result === 'object' && 'variables' in result) {
3720
+ return {
3721
+ confirmed: true,
3722
+ result: result
3723
+ };
3724
+ }
3725
+ return { confirmed: false };
3726
+ }
3727
+ catch {
3728
+ return { confirmed: false };
3729
+ }
3730
+ }
3731
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: RuntimeEntityVariableDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3732
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: RuntimeEntityVariableDialogService, providedIn: 'root' });
3733
+ }
3734
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: RuntimeEntityVariableDialogService, decorators: [{
3735
+ type: Injectable,
3736
+ args: [{
3737
+ providedIn: 'root'
3738
+ }]
3739
+ }] });
3740
+
3741
+ // noinspection JSUnusedGlobalSymbols
3742
+ class OctoGraphQlDataSource extends DataSourceTyped {
3743
+ _searchFilterAttributePaths = [];
3744
+ // noinspection JSUnusedGlobalSymbols
3745
+ constructor(listViewComponent) {
3746
+ super(listViewComponent);
3747
+ }
3748
+ get searchFilterAttributePaths() {
3749
+ return this._searchFilterAttributePaths;
3750
+ }
3751
+ set searchFilterAttributePaths(value) {
3752
+ this._searchFilterAttributePaths = value;
3753
+ }
3754
+ // noinspection JSUnusedGlobalSymbols
3755
+ getFieldFilterDefinitions(state) {
3756
+ let fieldFilters = null;
3757
+ if (state.filter?.filters) {
3758
+ fieldFilters = state.filter.filters.map((f) => {
3759
+ if (isCompositeFilterDescriptor(f)) {
3760
+ throw new Error('Composite filter descriptor not supported');
3761
+ }
3762
+ const { operator, value } = OctoGraphQlDataSource.getOperatorAndValue(f.operator, f.value);
3763
+ return {
3764
+ attributePath: f.field,
3765
+ operator: operator,
3766
+ comparisonValue: value
3767
+ };
3768
+ });
3769
+ }
3770
+ return fieldFilters;
3771
+ }
3772
+ // noinspection JSUnusedGlobalSymbols
3773
+ getSearchFilterDefinitions(textSearchValue) {
3774
+ let searchFilterDto = null;
3775
+ if (textSearchValue) {
3776
+ searchFilterDto = {
3777
+ type: SearchFilterTypesDto.AttributeFilterDto,
3778
+ attributePaths: this.searchFilterAttributePaths,
3779
+ searchTerm: textSearchValue,
3780
+ };
3781
+ }
3782
+ return searchFilterDto;
3783
+ }
3784
+ // noinspection JSUnusedGlobalSymbols
3785
+ getSortDefinitions(state) {
3786
+ let sort = null;
3787
+ if (state.sort) {
3788
+ sort = new Array();
3789
+ state.sort.forEach((s) => {
3790
+ switch (s.dir) {
3791
+ case 'asc':
3792
+ sort?.push({
3793
+ attributePath: s.field,
3794
+ sortOrder: SortOrdersDto.AscendingDto
3795
+ });
3796
+ break;
3797
+ case 'desc':
3798
+ sort?.push({
3799
+ attributePath: s.field,
3800
+ sortOrder: SortOrdersDto.DescendingDto,
3801
+ });
3802
+ break;
3803
+ default:
3804
+ sort?.push({
3805
+ attributePath: s.field,
3806
+ sortOrder: SortOrdersDto.DefaultDto,
3807
+ });
3808
+ break;
3809
+ }
3810
+ });
3811
+ }
3812
+ return sort;
3813
+ }
3814
+ static getOperatorAndValue(operator, value) {
3815
+ switch (operator) {
3816
+ case 'eq':
3817
+ return { operator: FieldFilterOperatorsDto.EqualsDto, value: value };
3818
+ case 'neq':
3819
+ return { operator: FieldFilterOperatorsDto.NotEqualsDto, value: value };
3820
+ case 'lt':
3821
+ return { operator: FieldFilterOperatorsDto.LessThanDto, value: value };
3822
+ case 'lte':
3823
+ return { operator: FieldFilterOperatorsDto.LessEqualThanDto, value: value };
3824
+ case 'gt':
3825
+ return { operator: FieldFilterOperatorsDto.GreaterThanDto, value: value };
3826
+ case 'gte':
3827
+ return { operator: FieldFilterOperatorsDto.GreaterEqualThanDto, value: value };
3828
+ case 'contains':
3829
+ return { operator: FieldFilterOperatorsDto.LikeDto, value: value };
3830
+ case 'doesnotcontain':
3831
+ return { operator: FieldFilterOperatorsDto.MatchRegExDto, value: `^(?!.*${value}).*$` };
3832
+ case 'startswith':
3833
+ return { operator: FieldFilterOperatorsDto.MatchRegExDto, value: value + '.*' };
3834
+ case 'endswith':
3835
+ return { operator: FieldFilterOperatorsDto.MatchRegExDto, value: '.*' + value };
3836
+ case 'isnull':
3837
+ return { operator: FieldFilterOperatorsDto.EqualsDto, value: null };
3838
+ case 'isnotnull':
3839
+ return { operator: FieldFilterOperatorsDto.NotEqualsDto, value: null };
3840
+ case 'isempty':
3841
+ return { operator: FieldFilterOperatorsDto.EqualsDto, value: "" };
3842
+ case 'isnotempty':
3843
+ return { operator: FieldFilterOperatorsDto.NotEqualsDto, value: "" };
3844
+ default:
3845
+ throw new Error('The filter operator is not supported');
3846
+ }
3847
+ }
3848
+ }
3849
+
3850
+ class OctoGraphQlHierarchyDataSource extends HierarchyDataSourceBase {
3851
+ }
3478
3852
 
3479
3853
  class FieldFilterEditorComponent {
3480
3854
  attributeService = inject(AttributeSelectorService, { optional: true });
@@ -4795,7 +5169,8 @@ function provideOctoUi() {
4795
5169
  provideMmSharedAuth(),
4796
5170
  AttributeSortSelectorDialogService,
4797
5171
  PropertyConverterService,
4798
- CkTypeSelectorDialogService
5172
+ CkTypeSelectorDialogService,
5173
+ RuntimeEntityVariableDialogService
4799
5174
  ]);
4800
5175
  }
4801
5176
 
@@ -4803,5 +5178,5 @@ function provideOctoUi() {
4803
5178
  * Generated bundle index. Do not edit.
4804
5179
  */
4805
5180
 
4806
- export { AttributeSelectorDialogComponent, AttributeSelectorDialogService, AttributeSortSelectorDialogComponent, AttributeSortSelectorDialogService, AttributeValueTypeDto, CkTypeSelectorDialogComponent, CkTypeSelectorDialogService, CkTypeSelectorInputComponent, DefaultPropertyCategory, EntityIdInfoComponent, FieldFilterEditorComponent, OctoGraphQlDataSource, OctoGraphQlHierarchyDataSource, OctoLoaderComponent, PropertyConverterService, PropertyDisplayMode, PropertyGridComponent, PropertyValueDisplayComponent, provideOctoUi };
5181
+ export { AttributeSelectorDialogComponent, AttributeSelectorDialogService, AttributeSortSelectorDialogComponent, AttributeSortSelectorDialogService, AttributeValueTypeDto, CkTypeSelectorDialogComponent, CkTypeSelectorDialogService, CkTypeSelectorInputComponent, DefaultPropertyCategory, EntityIdInfoComponent, FieldFilterEditorComponent, OctoGraphQlDataSource, OctoGraphQlHierarchyDataSource, OctoLoaderComponent, PropertyConverterService, PropertyDisplayMode, PropertyGridComponent, PropertyValueDisplayComponent, RuntimeEntityVariableDialogComponent, RuntimeEntityVariableDialogService, provideOctoUi };
4807
5182
  //# sourceMappingURL=meshmakers-octo-ui.mjs.map