@memberjunction/ng-entity-viewer 6.2.0-edge.0 → 6.2.0-edge.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.d.ts +22 -0
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.d.ts.map +1 -1
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.js +57 -13
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.js.map +1 -1
- package/dist/lib/entity-cards/entity-cards.component.d.ts +67 -5
- package/dist/lib/entity-cards/entity-cards.component.d.ts.map +1 -1
- package/dist/lib/entity-cards/entity-cards.component.js +202 -71
- package/dist/lib/entity-cards/entity-cards.component.js.map +1 -1
- package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts +159 -20
- package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts.map +1 -1
- package/dist/lib/entity-data-grid/entity-data-grid.component.js +446 -167
- package/dist/lib/entity-data-grid/entity-data-grid.component.js.map +1 -1
- package/dist/lib/entity-data-grid/events/grid-events.js +67 -67
- package/dist/lib/entity-data-grid/events/grid-events.js.map +1 -1
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.d.ts +112 -8
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.d.ts.map +1 -1
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js +245 -61
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js.map +1 -1
- package/dist/lib/entity-viewer/entity-viewer.component.d.ts +13 -0
- package/dist/lib/entity-viewer/entity-viewer.component.d.ts.map +1 -1
- package/dist/lib/entity-viewer/entity-viewer.component.js +53 -10
- package/dist/lib/entity-viewer/entity-viewer.component.js.map +1 -1
- package/dist/lib/pill/pill.component.d.ts +13 -3
- package/dist/lib/pill/pill.component.d.ts.map +1 -1
- package/dist/lib/pill/pill.component.js +34 -10
- package/dist/lib/pill/pill.component.js.map +1 -1
- package/dist/lib/quick-save-dialog/quick-save-dialog.component.d.ts +29 -3
- package/dist/lib/quick-save-dialog/quick-save-dialog.component.d.ts.map +1 -1
- package/dist/lib/quick-save-dialog/quick-save-dialog.component.js +173 -114
- package/dist/lib/quick-save-dialog/quick-save-dialog.component.js.map +1 -1
- package/dist/lib/recycle-bin/recycle-bin.component.d.ts +4 -0
- package/dist/lib/recycle-bin/recycle-bin.component.d.ts.map +1 -1
- package/dist/lib/recycle-bin/recycle-bin.component.js +10 -2
- package/dist/lib/recycle-bin/recycle-bin.component.js.map +1 -1
- package/dist/lib/utils/export-column.util.d.ts +14 -0
- package/dist/lib/utils/export-column.util.d.ts.map +1 -0
- package/dist/lib/utils/export-column.util.js +34 -0
- package/dist/lib/utils/export-column.util.js.map +1 -0
- package/dist/lib/utils/highlight.util.d.ts +6 -0
- package/dist/lib/utils/highlight.util.d.ts.map +1 -1
- package/dist/lib/utils/highlight.util.js +23 -11
- package/dist/lib/utils/highlight.util.js.map +1 -1
- package/dist/lib/utils/record.util.d.ts +10 -0
- package/dist/lib/utils/record.util.d.ts.map +1 -1
- package/dist/lib/utils/record.util.js +20 -4
- package/dist/lib/utils/record.util.js.map +1 -1
- package/dist/lib/view-config-panel/view-config-panel.component.d.ts +17 -1
- package/dist/lib/view-config-panel/view-config-panel.component.d.ts.map +1 -1
- package/dist/lib/view-config-panel/view-config-panel.component.js +39 -6
- package/dist/lib/view-config-panel/view-config-panel.component.js.map +1 -1
- package/dist/lib/view-selector/view-selector.component.js +2 -2
- package/dist/lib/view-types/renderers/cards-view-renderer.component.d.ts +15 -3
- package/dist/lib/view-types/renderers/cards-view-renderer.component.d.ts.map +1 -1
- package/dist/lib/view-types/renderers/cards-view-renderer.component.js +33 -5
- package/dist/lib/view-types/renderers/cards-view-renderer.component.js.map +1 -1
- package/dist/lib/view-types/renderers/grid-view-renderer.component.d.ts +70 -2
- package/dist/lib/view-types/renderers/grid-view-renderer.component.d.ts.map +1 -1
- package/dist/lib/view-types/renderers/grid-view-renderer.component.js +155 -33
- package/dist/lib/view-types/renderers/grid-view-renderer.component.js.map +1 -1
- package/dist/lib/view-types/renderers/map-view-renderer.component.d.ts +16 -2
- package/dist/lib/view-types/renderers/map-view-renderer.component.d.ts.map +1 -1
- package/dist/lib/view-types/renderers/map-view-renderer.component.js +37 -9
- package/dist/lib/view-types/renderers/map-view-renderer.component.js.map +1 -1
- package/dist/lib/view-types/renderers/timeline-view-renderer.component.d.ts +2 -0
- package/dist/lib/view-types/renderers/timeline-view-renderer.component.d.ts.map +1 -1
- package/dist/lib/view-types/renderers/timeline-view-renderer.component.js +11 -2
- package/dist/lib/view-types/renderers/timeline-view-renderer.component.js.map +1 -1
- package/dist/lib/view-types/view-type.contracts.d.ts +8 -0
- package/dist/lib/view-types/view-type.contracts.d.ts.map +1 -1
- package/dist/lib/view-types/view-type.contracts.js.map +1 -1
- package/dist/lib/view-workspace/view-workspace.component.d.ts +244 -25
- package/dist/lib/view-workspace/view-workspace.component.d.ts.map +1 -1
- package/dist/lib/view-workspace/view-workspace.component.js +639 -182
- package/dist/lib/view-workspace/view-workspace.component.js.map +1 -1
- package/dist/public-api.d.ts +1 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +1 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +21 -20
|
@@ -3,6 +3,7 @@ import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
|
3
3
|
import { LogError } from '@memberjunction/core';
|
|
4
4
|
import { UUIDsEqual } from '@memberjunction/global';
|
|
5
5
|
import { UserInfoEngine } from '@memberjunction/core-entities';
|
|
6
|
+
import { ExportColumnTypeForSQLType } from '../utils/export-column.util';
|
|
6
7
|
import { MJNotificationService } from '@memberjunction/ng-notifications';
|
|
7
8
|
import { EntityViewerComponent } from '../entity-viewer/entity-viewer.component';
|
|
8
9
|
import { ViewSelectorComponent } from '../view-selector/view-selector.component';
|
|
@@ -161,12 +162,12 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
161
162
|
* the workspace emits {@link SelectedViewChange} whenever the selection changes internally.
|
|
162
163
|
*/
|
|
163
164
|
get SelectedView() {
|
|
164
|
-
return this.
|
|
165
|
+
return this.CurrentViewEntity;
|
|
165
166
|
}
|
|
166
167
|
set SelectedView(value) {
|
|
167
|
-
this.
|
|
168
|
-
this.
|
|
169
|
-
this.
|
|
168
|
+
this.CurrentViewEntity = value;
|
|
169
|
+
this.CurrentGridState = value ? this.parseViewGridState(value) : this.loadUserDefaultGridState();
|
|
170
|
+
this.ViewModified = false;
|
|
170
171
|
}
|
|
171
172
|
// ----- Host-driven passthrough inputs (forwarded to the inner entity-viewer) -----
|
|
172
173
|
//
|
|
@@ -299,63 +300,257 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
299
300
|
// INTERNAL STATE (plain fields — no state service)
|
|
300
301
|
// ========================================
|
|
301
302
|
/** The currently selected view entity (null = the default/unsaved view). */
|
|
302
|
-
|
|
303
|
+
CurrentViewEntity = null;
|
|
304
|
+
/** @deprecated Use {@link CurrentViewEntity}. */
|
|
305
|
+
get currentViewEntity() {
|
|
306
|
+
return this.CurrentViewEntity;
|
|
307
|
+
}
|
|
308
|
+
/** @deprecated Use {@link CurrentViewEntity}. */
|
|
309
|
+
set currentViewEntity(value) {
|
|
310
|
+
this.CurrentViewEntity = value;
|
|
311
|
+
}
|
|
303
312
|
/** The currently selected view ID (null = default view). */
|
|
304
|
-
|
|
313
|
+
SelectedViewId = null;
|
|
314
|
+
/** @deprecated Use {@link SelectedViewId}. */
|
|
315
|
+
get selectedViewId() {
|
|
316
|
+
return this.SelectedViewId;
|
|
317
|
+
}
|
|
318
|
+
/** @deprecated Use {@link SelectedViewId}. */
|
|
319
|
+
set selectedViewId(value) {
|
|
320
|
+
this.SelectedViewId = value;
|
|
321
|
+
}
|
|
305
322
|
/** Live grid state (column widths / order / sort / aggregates) reflecting user interaction. */
|
|
306
|
-
|
|
323
|
+
CurrentGridState = null;
|
|
324
|
+
/** @deprecated Use {@link CurrentGridState}. */
|
|
325
|
+
get currentGridState() {
|
|
326
|
+
return this.CurrentGridState;
|
|
327
|
+
}
|
|
328
|
+
/** @deprecated Use {@link CurrentGridState}. */
|
|
329
|
+
set currentGridState(value) {
|
|
330
|
+
this.CurrentGridState = value;
|
|
331
|
+
}
|
|
307
332
|
/** Whether the current view has unsaved modifications. */
|
|
308
|
-
|
|
333
|
+
ViewModified = false;
|
|
334
|
+
/** @deprecated Use {@link ViewModified}. */
|
|
335
|
+
get viewModified() {
|
|
336
|
+
return this.ViewModified;
|
|
337
|
+
}
|
|
338
|
+
/** @deprecated Use {@link ViewModified}. */
|
|
339
|
+
set viewModified(value) {
|
|
340
|
+
this.ViewModified = value;
|
|
341
|
+
}
|
|
309
342
|
/** The current free-text filter. */
|
|
310
343
|
filterText = '';
|
|
311
344
|
/** The currently selected record's composite-key string (for grid highlight). */
|
|
312
345
|
selectedRecordId = null;
|
|
313
346
|
/** Records loaded by the viewer, kept for config-panel sample data. */
|
|
314
|
-
|
|
347
|
+
LoadedRecords = [];
|
|
348
|
+
/** @deprecated Use {@link LoadedRecords}. */
|
|
349
|
+
get loadedRecords() {
|
|
350
|
+
return this.LoadedRecords;
|
|
351
|
+
}
|
|
352
|
+
/** @deprecated Use {@link LoadedRecords}. */
|
|
353
|
+
set loadedRecords(value) {
|
|
354
|
+
this.LoadedRecords = value;
|
|
355
|
+
}
|
|
315
356
|
/** Whether a save operation is in progress (drives the saving spinners). */
|
|
316
|
-
|
|
357
|
+
IsSavingView = false;
|
|
358
|
+
/** @deprecated Use {@link IsSavingView}. */
|
|
359
|
+
get isSavingView() {
|
|
360
|
+
return this.IsSavingView;
|
|
361
|
+
}
|
|
362
|
+
/** @deprecated Use {@link IsSavingView}. */
|
|
363
|
+
set isSavingView(value) {
|
|
364
|
+
this.IsSavingView = value;
|
|
365
|
+
}
|
|
317
366
|
// ----- Dialog / panel open flags -----
|
|
318
367
|
/** Whether the slide-in config panel is open. */
|
|
319
|
-
|
|
368
|
+
IsConfigPanelOpen = false;
|
|
369
|
+
/** @deprecated Use {@link IsConfigPanelOpen}. */
|
|
370
|
+
get isConfigPanelOpen() {
|
|
371
|
+
return this.IsConfigPanelOpen;
|
|
372
|
+
}
|
|
373
|
+
/** @deprecated Use {@link IsConfigPanelOpen}. */
|
|
374
|
+
set isConfigPanelOpen(value) {
|
|
375
|
+
this.IsConfigPanelOpen = value;
|
|
376
|
+
}
|
|
320
377
|
/** Whether the quick-save dialog is open. */
|
|
321
|
-
|
|
378
|
+
ShowQuickSaveDialog = false;
|
|
379
|
+
/** @deprecated Use {@link ShowQuickSaveDialog}. */
|
|
380
|
+
get showQuickSaveDialog() {
|
|
381
|
+
return this.ShowQuickSaveDialog;
|
|
382
|
+
}
|
|
383
|
+
/** @deprecated Use {@link ShowQuickSaveDialog}. */
|
|
384
|
+
set showQuickSaveDialog(value) {
|
|
385
|
+
this.ShowQuickSaveDialog = value;
|
|
386
|
+
}
|
|
322
387
|
/** Whether the duplicate-view dialog is open. */
|
|
323
|
-
|
|
388
|
+
ShowDuplicateDialog = false;
|
|
389
|
+
/** @deprecated Use {@link ShowDuplicateDialog}. */
|
|
390
|
+
get showDuplicateDialog() {
|
|
391
|
+
return this.ShowDuplicateDialog;
|
|
392
|
+
}
|
|
393
|
+
/** @deprecated Use {@link ShowDuplicateDialog}. */
|
|
394
|
+
set showDuplicateDialog(value) {
|
|
395
|
+
this.ShowDuplicateDialog = value;
|
|
396
|
+
}
|
|
324
397
|
/** Whether the shared-view warning dialog is open. */
|
|
325
|
-
|
|
398
|
+
ShowSharedViewWarning = false;
|
|
399
|
+
/** @deprecated Use {@link ShowSharedViewWarning}. */
|
|
400
|
+
get showSharedViewWarning() {
|
|
401
|
+
return this.ShowSharedViewWarning;
|
|
402
|
+
}
|
|
403
|
+
/** @deprecated Use {@link ShowSharedViewWarning}. */
|
|
404
|
+
set showSharedViewWarning(value) {
|
|
405
|
+
this.ShowSharedViewWarning = value;
|
|
406
|
+
}
|
|
326
407
|
/** Whether the config panel opens in "save as new" mode. */
|
|
327
|
-
|
|
408
|
+
DefaultSaveAsNew = false;
|
|
409
|
+
/** @deprecated Use {@link DefaultSaveAsNew}. */
|
|
410
|
+
get defaultSaveAsNew() {
|
|
411
|
+
return this.DefaultSaveAsNew;
|
|
412
|
+
}
|
|
413
|
+
/** @deprecated Use {@link DefaultSaveAsNew}. */
|
|
414
|
+
set defaultSaveAsNew(value) {
|
|
415
|
+
this.DefaultSaveAsNew = value;
|
|
416
|
+
}
|
|
328
417
|
// ----- Pending state carried across dialogs -----
|
|
329
418
|
/** Summary shown in the quick-save dialog. */
|
|
330
|
-
|
|
419
|
+
QuickSaveSummary = null;
|
|
420
|
+
/** @deprecated Use {@link QuickSaveSummary}. */
|
|
421
|
+
get quickSaveSummary() {
|
|
422
|
+
return this.QuickSaveSummary;
|
|
423
|
+
}
|
|
424
|
+
/** @deprecated Use {@link QuickSaveSummary}. */
|
|
425
|
+
set quickSaveSummary(value) {
|
|
426
|
+
this.QuickSaveSummary = value;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Name pre-filled in the quick-save dialog when it opens as a name prompt for a default-view
|
|
430
|
+
* Save that carries a filter (see {@link OnSaveDefaultViewSettings}). Empty for every other
|
|
431
|
+
* quick-save, which keeps the field blank as before.
|
|
432
|
+
*/
|
|
433
|
+
QuickSaveSuggestedName = '';
|
|
434
|
+
/**
|
|
435
|
+
* The config-panel save staged while the user names the view it is being promoted into.
|
|
436
|
+
* Set only when a Save on the default (unsaved) view carries a filter; consumed by
|
|
437
|
+
* {@link executeQuickSave}, which merges it with the name the dialog collects.
|
|
438
|
+
*/
|
|
439
|
+
pendingDefaultViewSave = null;
|
|
331
440
|
/** Summary shown in the duplicate-view dialog. */
|
|
332
|
-
|
|
441
|
+
DuplicateSummary = null;
|
|
442
|
+
/** @deprecated Use {@link DuplicateSummary}. */
|
|
443
|
+
get duplicateSummary() {
|
|
444
|
+
return this.DuplicateSummary;
|
|
445
|
+
}
|
|
446
|
+
/** @deprecated Use {@link DuplicateSummary}. */
|
|
447
|
+
set duplicateSummary(value) {
|
|
448
|
+
this.DuplicateSummary = value;
|
|
449
|
+
}
|
|
333
450
|
/** Source view name shown in the duplicate-view dialog. */
|
|
334
|
-
|
|
451
|
+
DuplicateSourceViewName = '';
|
|
452
|
+
/** @deprecated Use {@link DuplicateSourceViewName}. */
|
|
453
|
+
get duplicateSourceViewName() {
|
|
454
|
+
return this.DuplicateSourceViewName;
|
|
455
|
+
}
|
|
456
|
+
/** @deprecated Use {@link DuplicateSourceViewName}. */
|
|
457
|
+
set duplicateSourceViewName(value) {
|
|
458
|
+
this.DuplicateSourceViewName = value;
|
|
459
|
+
}
|
|
335
460
|
/** The view ID being duplicated. */
|
|
336
461
|
duplicateTargetViewId = null;
|
|
337
462
|
/** A quick-save event held while the shared-view warning is shown. */
|
|
338
463
|
pendingQuickSaveEvent = null;
|
|
339
464
|
/** Pre-populated name carried from quick-save dialog into the config panel. */
|
|
340
|
-
|
|
465
|
+
PendingNewViewName = '';
|
|
466
|
+
/** @deprecated Use {@link PendingNewViewName}. */
|
|
467
|
+
get pendingNewViewName() {
|
|
468
|
+
return this.PendingNewViewName;
|
|
469
|
+
}
|
|
470
|
+
/** @deprecated Use {@link PendingNewViewName}. */
|
|
471
|
+
set pendingNewViewName(value) {
|
|
472
|
+
this.PendingNewViewName = value;
|
|
473
|
+
}
|
|
341
474
|
/** Pre-populated description carried from quick-save dialog into the config panel. */
|
|
342
|
-
|
|
475
|
+
PendingNewViewDescription = '';
|
|
476
|
+
/** @deprecated Use {@link PendingNewViewDescription}. */
|
|
477
|
+
get pendingNewViewDescription() {
|
|
478
|
+
return this.PendingNewViewDescription;
|
|
479
|
+
}
|
|
480
|
+
/** @deprecated Use {@link PendingNewViewDescription}. */
|
|
481
|
+
set pendingNewViewDescription(value) {
|
|
482
|
+
this.PendingNewViewDescription = value;
|
|
483
|
+
}
|
|
343
484
|
/** Pre-populated sharing preference carried from quick-save dialog into the config panel. */
|
|
344
|
-
|
|
485
|
+
PendingNewViewIsShared = false;
|
|
486
|
+
/** @deprecated Use {@link PendingNewViewIsShared}. */
|
|
487
|
+
get pendingNewViewIsShared() {
|
|
488
|
+
return this.PendingNewViewIsShared;
|
|
489
|
+
}
|
|
490
|
+
/** @deprecated Use {@link PendingNewViewIsShared}. */
|
|
491
|
+
set pendingNewViewIsShared(value) {
|
|
492
|
+
this.PendingNewViewIsShared = value;
|
|
493
|
+
}
|
|
494
|
+
/** Traditional filter carried from quick-save dialog back into the config panel. */
|
|
495
|
+
PendingNewViewFilterState = null;
|
|
496
|
+
/** Smart-filter toggle carried from quick-save dialog back into the config panel. */
|
|
497
|
+
PendingNewViewSmartFilterEnabled = false;
|
|
498
|
+
/** Smart-filter prompt carried from quick-save dialog back into the config panel. */
|
|
499
|
+
PendingNewViewSmartFilterPrompt = '';
|
|
345
500
|
// ----- Filter dialog (rendered at workspace level for full width) -----
|
|
346
501
|
/** Whether the full-width filter dialog is open. */
|
|
347
|
-
|
|
502
|
+
IsFilterDialogOpen = false;
|
|
503
|
+
/** @deprecated Use {@link IsFilterDialogOpen}. */
|
|
504
|
+
get isFilterDialogOpen() {
|
|
505
|
+
return this.IsFilterDialogOpen;
|
|
506
|
+
}
|
|
507
|
+
/** @deprecated Use {@link IsFilterDialogOpen}. */
|
|
508
|
+
set isFilterDialogOpen(value) {
|
|
509
|
+
this.IsFilterDialogOpen = value;
|
|
510
|
+
}
|
|
348
511
|
/** The filter state currently being edited in the filter dialog. */
|
|
349
|
-
|
|
512
|
+
FilterDialogState = null;
|
|
513
|
+
/** @deprecated Use {@link FilterDialogState}. */
|
|
514
|
+
get filterDialogState() {
|
|
515
|
+
return this.FilterDialogState;
|
|
516
|
+
}
|
|
517
|
+
/** @deprecated Use {@link FilterDialogState}. */
|
|
518
|
+
set filterDialogState(value) {
|
|
519
|
+
this.FilterDialogState = value;
|
|
520
|
+
}
|
|
350
521
|
/** The filter fields available in the filter dialog. */
|
|
351
|
-
|
|
522
|
+
FilterDialogFields = [];
|
|
523
|
+
/** @deprecated Use {@link FilterDialogFields}. */
|
|
524
|
+
get filterDialogFields() {
|
|
525
|
+
return this.FilterDialogFields;
|
|
526
|
+
}
|
|
527
|
+
/** @deprecated Use {@link FilterDialogFields}. */
|
|
528
|
+
set filterDialogFields(value) {
|
|
529
|
+
this.FilterDialogFields = value;
|
|
530
|
+
}
|
|
352
531
|
constructor(cdr) {
|
|
353
532
|
super();
|
|
354
533
|
this.cdr = cdr;
|
|
355
534
|
}
|
|
356
535
|
/** Controls the workspace-level export dialog (the same generic dialog the grid uses). */
|
|
357
|
-
|
|
358
|
-
|
|
536
|
+
ShowExportDialog = false;
|
|
537
|
+
/** @deprecated Use {@link ShowExportDialog}. */
|
|
538
|
+
get showExportDialog() {
|
|
539
|
+
return this.ShowExportDialog;
|
|
540
|
+
}
|
|
541
|
+
/** @deprecated Use {@link ShowExportDialog}. */
|
|
542
|
+
set showExportDialog(value) {
|
|
543
|
+
this.ShowExportDialog = value;
|
|
544
|
+
}
|
|
545
|
+
ExportDialogConfig = null;
|
|
546
|
+
/** @deprecated Use {@link ExportDialogConfig}. */
|
|
547
|
+
get exportDialogConfig() {
|
|
548
|
+
return this.ExportDialogConfig;
|
|
549
|
+
}
|
|
550
|
+
/** @deprecated Use {@link ExportDialogConfig}. */
|
|
551
|
+
set exportDialogConfig(value) {
|
|
552
|
+
this.ExportDialogConfig = value;
|
|
553
|
+
}
|
|
359
554
|
/**
|
|
360
555
|
* Handles the toolbar's Export button (the `<mj-view-selector>` `ExportRequested` output). This is
|
|
361
556
|
* the always-visible, view-type-agnostic export for the workspace — previously the event wasn't
|
|
@@ -365,7 +560,7 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
365
560
|
* options. Because the dialog only needs data + columns (it has no view-type dependency), this works
|
|
366
561
|
* identically for Grid, Cards, Map and Timeline. Never fails silently — every path notifies.
|
|
367
562
|
*/
|
|
368
|
-
async
|
|
563
|
+
async OnExportRequested() {
|
|
369
564
|
const viewer = this.entityViewerRef;
|
|
370
565
|
if (!viewer || !this._entity) {
|
|
371
566
|
console.error('[ViewWorkspace] Export: viewer not ready', { hasViewer: !!viewer, hasEntity: !!this._entity });
|
|
@@ -379,7 +574,7 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
379
574
|
MJNotificationService.Instance.CreateSimpleNotification('Nothing to export — the view returned no records.', 'warning', 5000);
|
|
380
575
|
return;
|
|
381
576
|
}
|
|
382
|
-
this.
|
|
577
|
+
this.ExportDialogConfig = {
|
|
383
578
|
data: rows,
|
|
384
579
|
columns: this.buildExportColumns(),
|
|
385
580
|
defaultFileName: this.buildExportFileName(),
|
|
@@ -389,7 +584,7 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
389
584
|
defaultSamplingMode: 'all',
|
|
390
585
|
dialogTitle: `Export ${this._entity.Name}`
|
|
391
586
|
};
|
|
392
|
-
this.
|
|
587
|
+
this.ShowExportDialog = true;
|
|
393
588
|
this.cdr.detectChanges();
|
|
394
589
|
}
|
|
395
590
|
catch (e) {
|
|
@@ -397,29 +592,67 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
397
592
|
console.error('[ViewWorkspace] Export error:', e);
|
|
398
593
|
}
|
|
399
594
|
}
|
|
595
|
+
/** @deprecated Use {@link OnExportRequested}. */
|
|
596
|
+
async onExportRequested() {
|
|
597
|
+
return this.OnExportRequested();
|
|
598
|
+
}
|
|
400
599
|
/** Closes the workspace export dialog (the dialog performs the export + download itself). */
|
|
401
|
-
|
|
402
|
-
this.
|
|
403
|
-
this.
|
|
600
|
+
OnExportDialogClosed(_result) {
|
|
601
|
+
this.ShowExportDialog = false;
|
|
602
|
+
this.ExportDialogConfig = null;
|
|
404
603
|
this.cdr.detectChanges();
|
|
405
604
|
}
|
|
406
|
-
/**
|
|
605
|
+
/** @deprecated Use {@link OnExportDialogClosed}. */
|
|
606
|
+
onExportDialogClosed(_result) {
|
|
607
|
+
return this.OnExportDialogClosed(_result);
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Columns to export: what the active renderer shows on screen (the grid), else the view's saved
|
|
611
|
+
* columns, else the entity fields. The fallbacks apply the grid's own rules, so Cards, Map and
|
|
612
|
+
* Timeline export the columns a grid of this view would show: saved settings are sorted by
|
|
613
|
+
* `orderIndex`, headed by the user's rename, resolved to the entity's field spelling (the export
|
|
614
|
+
* engine reads each row by that key), and dropped when the field no longer exists; fields the
|
|
615
|
+
* user is denied read access to are left out. Each column carries the type the grid gives it, so a
|
|
616
|
+
* date-only field exports as its day (`YYYY-MM-DD`) here as it does from the grid.
|
|
617
|
+
*/
|
|
407
618
|
buildExportColumns() {
|
|
408
|
-
|
|
619
|
+
const entity = this._entity;
|
|
620
|
+
if (!entity) {
|
|
409
621
|
return [];
|
|
410
622
|
}
|
|
411
|
-
const
|
|
623
|
+
const onScreen = this.entityViewerRef?.GetExportColumns() ?? [];
|
|
624
|
+
if (onScreen.length > 0) {
|
|
625
|
+
return onScreen;
|
|
626
|
+
}
|
|
627
|
+
const user = this.ProviderToUse?.CurrentUser;
|
|
628
|
+
const denied = user ? entity.GetDeniedReadFields(user) : new Set();
|
|
629
|
+
const readable = (f) => !denied.has(f.Name.trim().toLowerCase());
|
|
630
|
+
const gridCols = this.CurrentGridState?.columnSettings;
|
|
412
631
|
if (gridCols && gridCols.length > 0) {
|
|
413
|
-
|
|
632
|
+
const columns = [];
|
|
633
|
+
const sorted = [...gridCols]
|
|
414
634
|
.filter(c => c.hidden !== true)
|
|
415
|
-
.
|
|
635
|
+
.sort((a, b) => (a.orderIndex ?? 0) - (b.orderIndex ?? 0));
|
|
636
|
+
for (const c of sorted) {
|
|
637
|
+
const field = entity.Fields.find(f => f.Name.toLowerCase() === c.Name.toLowerCase());
|
|
638
|
+
if (field && readable(field)) {
|
|
639
|
+
columns.push({
|
|
640
|
+
name: field.Name,
|
|
641
|
+
displayName: c.userDisplayName || c.DisplayName || field.DisplayNameOrName,
|
|
642
|
+
dataType: ExportColumnTypeForSQLType(field.Type),
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
if (columns.length > 0) {
|
|
647
|
+
return columns;
|
|
648
|
+
}
|
|
416
649
|
}
|
|
417
|
-
return
|
|
418
|
-
.filter(f => !f.IsVirtual)
|
|
419
|
-
.map(f => ({ name: f.Name, displayName: f.DisplayNameOrName }));
|
|
650
|
+
return entity.Fields
|
|
651
|
+
.filter(f => !f.IsVirtual && readable(f))
|
|
652
|
+
.map(f => ({ name: f.Name, displayName: f.DisplayNameOrName, dataType: ExportColumnTypeForSQLType(f.Type) }));
|
|
420
653
|
}
|
|
421
654
|
buildExportFileName() {
|
|
422
|
-
const viewName = this.
|
|
655
|
+
const viewName = this.CurrentViewEntity?.Name || 'Data';
|
|
423
656
|
return `${this._entity.Name}_${viewName}_${new Date().toISOString().split('T')[0]}`;
|
|
424
657
|
}
|
|
425
658
|
// ========================================
|
|
@@ -428,27 +661,30 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
428
661
|
/** Initializes the workspace and loads the per-user default grid state for the entity. */
|
|
429
662
|
ngOnInit() {
|
|
430
663
|
this._initialized = true;
|
|
431
|
-
if (this._entity && !this.
|
|
432
|
-
this.
|
|
664
|
+
if (this._entity && !this.CurrentViewEntity) {
|
|
665
|
+
this.CurrentGridState = this.loadUserDefaultGridState();
|
|
433
666
|
}
|
|
434
667
|
}
|
|
435
668
|
/** Resets all view/selection state when the bound entity changes to a different entity. */
|
|
436
669
|
resetForEntityChange() {
|
|
437
|
-
this.
|
|
438
|
-
this.
|
|
439
|
-
this.
|
|
670
|
+
this.CurrentViewEntity = null;
|
|
671
|
+
this.SelectedViewId = null;
|
|
672
|
+
this.ViewModified = false;
|
|
440
673
|
this.selectedRecordId = null;
|
|
441
|
-
this.
|
|
674
|
+
this.CurrentGridState = this.loadUserDefaultGridState();
|
|
442
675
|
this.closeAllDialogs();
|
|
443
676
|
this.cdr.detectChanges();
|
|
444
677
|
}
|
|
445
678
|
/** Closes every dialog/panel — used on entity change. */
|
|
446
679
|
closeAllDialogs() {
|
|
447
|
-
this.
|
|
448
|
-
this.
|
|
449
|
-
this.
|
|
450
|
-
this.
|
|
451
|
-
this.
|
|
680
|
+
this.IsConfigPanelOpen = false;
|
|
681
|
+
this.ShowQuickSaveDialog = false;
|
|
682
|
+
this.ShowDuplicateDialog = false;
|
|
683
|
+
this.ShowSharedViewWarning = false;
|
|
684
|
+
this.IsFilterDialogOpen = false;
|
|
685
|
+
// A save staged for the previous entity references that entity's fields — it must not
|
|
686
|
+
// surface on the next quick-save against the new one.
|
|
687
|
+
this.clearPendingNewViewState();
|
|
452
688
|
}
|
|
453
689
|
// ========================================
|
|
454
690
|
// VIEW-TYPE SWITCHER (lifted into the toolbar)
|
|
@@ -460,26 +696,38 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
460
696
|
* + persistence). The viewer's own header switcher is suppressed via Config, so this is the
|
|
461
697
|
* single switcher in the workspace.
|
|
462
698
|
*/
|
|
463
|
-
|
|
699
|
+
OnToolbarViewTypeSelected(event) {
|
|
464
700
|
this.entityViewerRef?.SelectViewTypeById(event.viewTypeId);
|
|
465
701
|
this.cdr.detectChanges();
|
|
466
702
|
}
|
|
703
|
+
/** @deprecated Use {@link OnToolbarViewTypeSelected}. */
|
|
704
|
+
onToolbarViewTypeSelected(event) {
|
|
705
|
+
return this.OnToolbarViewTypeSelected(event);
|
|
706
|
+
}
|
|
467
707
|
/**
|
|
468
708
|
* The active view type's `MJ: View Types` row ID, read from the inner viewer for the toolbar
|
|
469
709
|
* switcher's active highlight. Safe to read in the template — it reflects the viewer's own
|
|
470
710
|
* resolved state rather than driving it.
|
|
471
711
|
*/
|
|
472
|
-
get
|
|
712
|
+
get ActiveViewTypeId() {
|
|
473
713
|
return this.entityViewerRef?.ActiveViewTypeId ?? null;
|
|
474
714
|
}
|
|
715
|
+
/** @deprecated Use {@link ActiveViewTypeId}. */
|
|
716
|
+
get activeViewTypeId() {
|
|
717
|
+
return this.ActiveViewTypeId;
|
|
718
|
+
}
|
|
475
719
|
/**
|
|
476
720
|
* The Config forwarded to the inner viewer. Merges the host-supplied {@link ViewerConfig} but
|
|
477
721
|
* always forces `showViewModeToggle: false` so the view-type switcher appears only once — in the
|
|
478
722
|
* workspace toolbar, not duplicated in the viewer's own header.
|
|
479
723
|
*/
|
|
480
|
-
get
|
|
724
|
+
get InnerViewerConfig() {
|
|
481
725
|
return { ...(this.ViewerConfig ?? {}), showViewModeToggle: false };
|
|
482
726
|
}
|
|
727
|
+
/** @deprecated Use {@link InnerViewerConfig}. */
|
|
728
|
+
get innerViewerConfig() {
|
|
729
|
+
return this.InnerViewerConfig;
|
|
730
|
+
}
|
|
483
731
|
// ========================================
|
|
484
732
|
// VIEW SELECTION
|
|
485
733
|
// ========================================
|
|
@@ -487,82 +735,126 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
487
735
|
* Handle a view selection from the selector dropdown. Applies the view's grid state and filter,
|
|
488
736
|
* resets the modified flag, and notifies the host. (Generalized from DataExplorer.onViewSelected.)
|
|
489
737
|
*/
|
|
490
|
-
|
|
738
|
+
OnViewSelected(event) {
|
|
491
739
|
this.entityViewerRef?.EnsurePendingChangesSaved();
|
|
492
|
-
this.
|
|
493
|
-
this.
|
|
494
|
-
this.
|
|
740
|
+
this.CurrentViewEntity = event.View;
|
|
741
|
+
this.SelectedViewId = event.ViewID;
|
|
742
|
+
this.ViewModified = false;
|
|
495
743
|
this.filterText = '';
|
|
496
|
-
this.
|
|
744
|
+
this.CurrentGridState = event.View
|
|
497
745
|
? this.parseViewGridState(event.View)
|
|
498
746
|
: this.loadUserDefaultGridState();
|
|
499
747
|
this.ViewSelected.emit(event.View);
|
|
500
748
|
this.SelectedViewChange.emit(event.View);
|
|
501
749
|
this.cdr.detectChanges();
|
|
502
750
|
}
|
|
751
|
+
/** @deprecated Use {@link OnViewSelected}. */
|
|
752
|
+
onViewSelected(event) {
|
|
753
|
+
return this.OnViewSelected(event);
|
|
754
|
+
}
|
|
503
755
|
/** Handle filter text change from the viewer — re-emit so the host can sync its filter box / URL. */
|
|
504
|
-
|
|
756
|
+
OnFilterTextChanged(filterText) {
|
|
505
757
|
this.filterText = filterText;
|
|
506
758
|
this.FilterTextChanged.emit(filterText);
|
|
507
759
|
}
|
|
760
|
+
/** @deprecated Use {@link OnFilterTextChanged}. */
|
|
761
|
+
onFilterTextChanged(filterText) {
|
|
762
|
+
return this.OnFilterTextChanged(filterText);
|
|
763
|
+
}
|
|
508
764
|
/** Track loaded records so the config panel has sample data, and re-emit the full event. */
|
|
509
|
-
|
|
510
|
-
this.
|
|
765
|
+
OnDataLoaded(event) {
|
|
766
|
+
this.LoadedRecords = event.records;
|
|
511
767
|
this.DataLoaded.emit(event);
|
|
512
768
|
}
|
|
769
|
+
/** @deprecated Use {@link OnDataLoaded}. */
|
|
770
|
+
onDataLoaded(event) {
|
|
771
|
+
return this.OnDataLoaded(event);
|
|
772
|
+
}
|
|
513
773
|
/** Re-emit the inner viewer's filtered-count change for the host. */
|
|
514
|
-
|
|
774
|
+
OnFilteredCountChanged(event) {
|
|
515
775
|
this.FilteredCountChanged.emit(event);
|
|
516
776
|
}
|
|
777
|
+
/** @deprecated Use {@link OnFilteredCountChanged}. */
|
|
778
|
+
onFilteredCountChanged(event) {
|
|
779
|
+
return this.OnFilteredCountChanged(event);
|
|
780
|
+
}
|
|
517
781
|
// ========================================
|
|
518
782
|
// RECORD SELECTION / OPENING / NAVIGATION (emit for host)
|
|
519
783
|
// ========================================
|
|
520
784
|
/** Handle a record single-click — track selection and re-emit for the host. */
|
|
521
|
-
|
|
785
|
+
OnRecordSelected(event) {
|
|
522
786
|
this.selectedRecordId = event.compositeKey.ToConcatenatedString();
|
|
523
787
|
this.RecordSelected.emit(event);
|
|
524
788
|
}
|
|
789
|
+
/** @deprecated Use {@link OnRecordSelected}. */
|
|
790
|
+
onRecordSelected(event) {
|
|
791
|
+
return this.OnRecordSelected(event);
|
|
792
|
+
}
|
|
525
793
|
/** Handle a record open (double-click) — emit for the host to route to the record. */
|
|
526
|
-
|
|
794
|
+
OnRecordOpened(event) {
|
|
527
795
|
if (event.record) {
|
|
528
796
|
this.OpenRecordRequested.emit({ entity: event.entity, record: event.record });
|
|
529
797
|
}
|
|
530
798
|
}
|
|
799
|
+
/** @deprecated Use {@link OnRecordOpened}. */
|
|
800
|
+
onRecordOpened(event) {
|
|
801
|
+
return this.OnRecordOpened(event);
|
|
802
|
+
}
|
|
531
803
|
/**
|
|
532
804
|
* Handle a plug-in renderer's request (via the inner viewer) to open a related record on a
|
|
533
805
|
* (possibly different) entity — re-emit for the host to route.
|
|
534
806
|
*/
|
|
535
|
-
|
|
807
|
+
OnOpenRelatedRecordRequested(nav) {
|
|
536
808
|
this.OpenRelatedRecordRequested.emit(nav);
|
|
537
809
|
}
|
|
810
|
+
/** @deprecated Use {@link OnOpenRelatedRecordRequested}. */
|
|
811
|
+
onOpenRelatedRecordRequested(nav) {
|
|
812
|
+
return this.OnOpenRelatedRecordRequested(nav);
|
|
813
|
+
}
|
|
538
814
|
/**
|
|
539
815
|
* Handle a plug-in renderer's request (via the inner viewer) to create a new record of the
|
|
540
816
|
* current entity (e.g. a grid's "New" button) — re-emit for the host to open the form.
|
|
541
817
|
*/
|
|
542
|
-
|
|
818
|
+
OnCreateRecordRequested() {
|
|
543
819
|
if (this._entity) {
|
|
544
820
|
this.CreateNewRecordRequested.emit(this._entity);
|
|
545
821
|
}
|
|
546
822
|
}
|
|
823
|
+
/** @deprecated Use {@link OnCreateRecordRequested}. */
|
|
824
|
+
onCreateRecordRequested() {
|
|
825
|
+
return this.OnCreateRecordRequested();
|
|
826
|
+
}
|
|
547
827
|
// ========================================
|
|
548
828
|
// VIEW SELECTOR ACTIONS
|
|
549
829
|
// ========================================
|
|
550
830
|
/** Handle the selector's "open in tab" request — emit for the host to route. */
|
|
551
|
-
|
|
831
|
+
OnOpenInTabRequested(viewId) {
|
|
552
832
|
this.OpenViewInTabRequested.emit(viewId);
|
|
553
833
|
}
|
|
834
|
+
/** @deprecated Use {@link OnOpenInTabRequested}. */
|
|
835
|
+
onOpenInTabRequested(viewId) {
|
|
836
|
+
return this.OnOpenInTabRequested(viewId);
|
|
837
|
+
}
|
|
554
838
|
/** Handle the selector's "create new record" request — emit for the host. */
|
|
555
|
-
|
|
839
|
+
OnCreateNewRecordRequested() {
|
|
556
840
|
if (this._entity) {
|
|
557
841
|
this.CreateNewRecordRequested.emit(this._entity);
|
|
558
842
|
}
|
|
559
843
|
}
|
|
844
|
+
/** @deprecated Use {@link OnCreateNewRecordRequested}. */
|
|
845
|
+
onCreateNewRecordRequested() {
|
|
846
|
+
return this.OnCreateNewRecordRequested();
|
|
847
|
+
}
|
|
560
848
|
/** Handle the selector's "configure view" request — open the config panel. */
|
|
561
|
-
|
|
562
|
-
this.
|
|
563
|
-
this.
|
|
849
|
+
OnConfigureViewRequested() {
|
|
850
|
+
this.DefaultSaveAsNew = false;
|
|
851
|
+
this.IsConfigPanelOpen = true;
|
|
564
852
|
this.cdr.detectChanges();
|
|
565
853
|
}
|
|
854
|
+
/** @deprecated Use {@link OnConfigureViewRequested}. */
|
|
855
|
+
onConfigureViewRequested() {
|
|
856
|
+
return this.OnConfigureViewRequested();
|
|
857
|
+
}
|
|
566
858
|
// ========================================
|
|
567
859
|
// PROGRAMMATIC GRID CONTROL (passthrough to the inner entity viewer)
|
|
568
860
|
// ========================================
|
|
@@ -622,46 +914,71 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
622
914
|
return this.entityViewerRef ? this.entityViewerRef.ExportRecords(format) : false;
|
|
623
915
|
}
|
|
624
916
|
/** Handle the selector's "save view" request — open the config panel in the requested mode. */
|
|
625
|
-
|
|
626
|
-
this.
|
|
627
|
-
this.
|
|
917
|
+
OnSaveViewRequested(event) {
|
|
918
|
+
this.DefaultSaveAsNew = event.SaveAsNew || false;
|
|
919
|
+
this.IsConfigPanelOpen = true;
|
|
628
920
|
this.cdr.detectChanges();
|
|
629
921
|
}
|
|
922
|
+
/** @deprecated Use {@link OnSaveViewRequested}. */
|
|
923
|
+
onSaveViewRequested(event) {
|
|
924
|
+
return this.OnSaveViewRequested(event);
|
|
925
|
+
}
|
|
630
926
|
// ========================================
|
|
631
927
|
// CONFIG PANEL
|
|
632
928
|
// ========================================
|
|
633
929
|
/** Close the config panel and clear any pending new-view carry-over state. */
|
|
634
|
-
|
|
635
|
-
this.
|
|
930
|
+
OnCloseConfigPanel() {
|
|
931
|
+
this.IsConfigPanelOpen = false;
|
|
636
932
|
this.clearPendingNewViewState();
|
|
637
933
|
this.cdr.detectChanges();
|
|
638
934
|
}
|
|
935
|
+
/** @deprecated Use {@link OnCloseConfigPanel}. */
|
|
936
|
+
onCloseConfigPanel() {
|
|
937
|
+
return this.OnCloseConfigPanel();
|
|
938
|
+
}
|
|
639
939
|
/** Reset the carry-over state used when continuing a new-view flow into the config panel. */
|
|
640
940
|
clearPendingNewViewState() {
|
|
641
|
-
this.
|
|
642
|
-
this.
|
|
643
|
-
this.
|
|
644
|
-
this.
|
|
941
|
+
this.PendingNewViewName = '';
|
|
942
|
+
this.PendingNewViewDescription = '';
|
|
943
|
+
this.PendingNewViewIsShared = false;
|
|
944
|
+
this.PendingNewViewFilterState = null;
|
|
945
|
+
this.PendingNewViewSmartFilterEnabled = false;
|
|
946
|
+
this.PendingNewViewSmartFilterPrompt = '';
|
|
947
|
+
this.DefaultSaveAsNew = false;
|
|
948
|
+
this.pendingDefaultViewSave = null;
|
|
949
|
+
this.QuickSaveSuggestedName = '';
|
|
645
950
|
}
|
|
646
951
|
// ----- Filter dialog -----
|
|
647
952
|
/** Open the full-width filter dialog from the config panel's request. */
|
|
648
|
-
|
|
649
|
-
this.
|
|
650
|
-
this.
|
|
651
|
-
this.
|
|
953
|
+
OnOpenFilterDialogRequest(event) {
|
|
954
|
+
this.FilterDialogState = event.filterState;
|
|
955
|
+
this.FilterDialogFields = event.filterFields;
|
|
956
|
+
this.IsFilterDialogOpen = true;
|
|
652
957
|
this.cdr.detectChanges();
|
|
653
958
|
}
|
|
959
|
+
/** @deprecated Use {@link OnOpenFilterDialogRequest}. */
|
|
960
|
+
onOpenFilterDialogRequest(event) {
|
|
961
|
+
return this.OnOpenFilterDialogRequest(event);
|
|
962
|
+
}
|
|
654
963
|
/** Close the filter dialog. */
|
|
655
|
-
|
|
656
|
-
this.
|
|
964
|
+
OnCloseFilterDialog() {
|
|
965
|
+
this.IsFilterDialogOpen = false;
|
|
657
966
|
this.cdr.detectChanges();
|
|
658
967
|
}
|
|
968
|
+
/** @deprecated Use {@link OnCloseFilterDialog}. */
|
|
969
|
+
onCloseFilterDialog() {
|
|
970
|
+
return this.OnCloseFilterDialog();
|
|
971
|
+
}
|
|
659
972
|
/** Apply a filter from the dialog — the config panel picks it up via `externalFilterState`. */
|
|
660
|
-
|
|
661
|
-
this.
|
|
662
|
-
this.
|
|
973
|
+
OnFilterApplied(filter) {
|
|
974
|
+
this.FilterDialogState = filter;
|
|
975
|
+
this.IsFilterDialogOpen = false;
|
|
663
976
|
this.cdr.detectChanges();
|
|
664
977
|
}
|
|
978
|
+
/** @deprecated Use {@link OnFilterApplied}. */
|
|
979
|
+
onFilterApplied(filter) {
|
|
980
|
+
return this.OnFilterApplied(filter);
|
|
981
|
+
}
|
|
665
982
|
// ========================================
|
|
666
983
|
// SAVE VIEW
|
|
667
984
|
// ========================================
|
|
@@ -669,33 +986,39 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
669
986
|
* Handle a save from the config panel. When {@link AutoSaveView}, persists the view itself
|
|
670
987
|
* (create-new or update) via the BaseEntity; otherwise emits {@link SaveViewRequested} for the host.
|
|
671
988
|
* (Faithful generalization of DataExplorer.onSaveView, minus routing/state-service/notifications.)
|
|
989
|
+
* Resolves `true` once the view is saved (or handed to the host), `false` if it was not.
|
|
672
990
|
*/
|
|
673
|
-
async
|
|
991
|
+
async OnSaveView(event) {
|
|
674
992
|
if (!this._entity) {
|
|
675
|
-
return;
|
|
993
|
+
return false;
|
|
676
994
|
}
|
|
677
995
|
if (!this.AutoSaveView) {
|
|
678
996
|
this.SaveViewRequested.emit(event);
|
|
679
|
-
this.
|
|
997
|
+
this.IsConfigPanelOpen = false;
|
|
680
998
|
this.clearPendingNewViewState();
|
|
681
999
|
this.cdr.detectChanges();
|
|
682
|
-
return;
|
|
1000
|
+
return true;
|
|
683
1001
|
}
|
|
684
|
-
this.
|
|
1002
|
+
this.IsSavingView = true;
|
|
685
1003
|
this.cdr.detectChanges();
|
|
686
|
-
const isNew = event.SaveAsNew || !this.
|
|
1004
|
+
const isNew = event.SaveAsNew || !this.CurrentViewEntity;
|
|
687
1005
|
const success = isNew
|
|
688
1006
|
? await this.persistNewView(event)
|
|
689
1007
|
: await this.persistExistingView(event);
|
|
690
1008
|
if (success) {
|
|
691
|
-
this.
|
|
1009
|
+
this.IsConfigPanelOpen = false;
|
|
692
1010
|
this.clearPendingNewViewState();
|
|
693
1011
|
// The view selector re-derives its lists reactively from UserViewEngine's cache
|
|
694
1012
|
// (BaseEntity save auto-invalidates the engine); we only need to reload the grid data.
|
|
695
1013
|
await this.entityViewerRef?.LoadData();
|
|
696
1014
|
}
|
|
697
|
-
this.
|
|
1015
|
+
this.IsSavingView = false;
|
|
698
1016
|
this.cdr.detectChanges();
|
|
1017
|
+
return success;
|
|
1018
|
+
}
|
|
1019
|
+
/** @deprecated Use {@link OnSaveView}. */
|
|
1020
|
+
async onSaveView(event) {
|
|
1021
|
+
return this.OnSaveView(event);
|
|
699
1022
|
}
|
|
700
1023
|
/**
|
|
701
1024
|
* Create and persist a brand-new view from the save event. Fires the cancelable
|
|
@@ -731,10 +1054,10 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
731
1054
|
LogError(`[ViewWorkspace] Failed to create view: ${newView.LatestResult?.CompleteMessage ?? 'unknown error'}`);
|
|
732
1055
|
return false;
|
|
733
1056
|
}
|
|
734
|
-
this.
|
|
735
|
-
this.
|
|
736
|
-
this.
|
|
737
|
-
this.
|
|
1057
|
+
this.CurrentViewEntity = newView;
|
|
1058
|
+
this.SelectedViewId = newView.ID;
|
|
1059
|
+
this.ViewModified = false;
|
|
1060
|
+
this.CurrentGridState = this.parseViewGridState(newView);
|
|
738
1061
|
this.ViewSelected.emit(newView);
|
|
739
1062
|
this.SelectedViewChange.emit(newView);
|
|
740
1063
|
this.AfterViewSave.emit({ View: newView, IsNew: true });
|
|
@@ -745,7 +1068,7 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
745
1068
|
* {@link BeforeViewSave} before saving and {@link AfterViewSave} after success.
|
|
746
1069
|
*/
|
|
747
1070
|
async persistExistingView(event) {
|
|
748
|
-
const view = this.
|
|
1071
|
+
const view = this.CurrentViewEntity;
|
|
749
1072
|
view.Name = event.Name;
|
|
750
1073
|
view.Description = event.Description;
|
|
751
1074
|
view.IsShared = event.IsShared;
|
|
@@ -770,8 +1093,8 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
770
1093
|
LogError(`[ViewWorkspace] Failed to update view: ${view.LatestResult?.CompleteMessage ?? 'unknown error'}`);
|
|
771
1094
|
return false;
|
|
772
1095
|
}
|
|
773
|
-
this.
|
|
774
|
-
this.
|
|
1096
|
+
this.ViewModified = false;
|
|
1097
|
+
this.CurrentGridState = this.parseViewGridState(view);
|
|
775
1098
|
this.AfterViewSave.emit({ View: view, IsNew: false });
|
|
776
1099
|
return true;
|
|
777
1100
|
}
|
|
@@ -780,17 +1103,32 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
780
1103
|
* persists to `UserInfoEngine`; otherwise emits {@link SaveDefaultsRequested}.
|
|
781
1104
|
* (Generalized from DataExplorer.onSaveDefaultViewSettings.)
|
|
782
1105
|
*/
|
|
783
|
-
async
|
|
1106
|
+
async OnSaveDefaultViewSettings(event) {
|
|
784
1107
|
if (!this._entity) {
|
|
785
1108
|
return;
|
|
786
1109
|
}
|
|
1110
|
+
// A default-view Save that carries a filter is not a preferences save at all — the
|
|
1111
|
+
// `default-view-setting/<Entity>` payload is an IGridState with nowhere to put a filter, so
|
|
1112
|
+
// persisting here would silently drop it (#4220). It is really a view creation missing a
|
|
1113
|
+
// name, so stage it and open the quick-save dialog to collect one. Confirming there routes
|
|
1114
|
+
// through `OnSaveView`, which honours `AutoSaveView` for both host modes.
|
|
1115
|
+
if (this.eventHasFilter(event)) {
|
|
1116
|
+
this.pendingDefaultViewSave = event;
|
|
1117
|
+
this.QuickSaveSuggestedName = this.buildSuggestedViewName(event);
|
|
1118
|
+
this.QuickSaveSummary = this.buildConfigSummary(event);
|
|
1119
|
+
this.DefaultSaveAsNew = true;
|
|
1120
|
+
this.IsConfigPanelOpen = false;
|
|
1121
|
+
this.ShowQuickSaveDialog = true;
|
|
1122
|
+
this.cdr.detectChanges();
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
787
1125
|
if (!this.AutoSaveView) {
|
|
788
1126
|
this.SaveDefaultsRequested.emit(event);
|
|
789
|
-
this.
|
|
1127
|
+
this.IsConfigPanelOpen = false;
|
|
790
1128
|
this.cdr.detectChanges();
|
|
791
1129
|
return;
|
|
792
1130
|
}
|
|
793
|
-
this.
|
|
1131
|
+
this.IsSavingView = true;
|
|
794
1132
|
this.cdr.detectChanges();
|
|
795
1133
|
const gridState = this.buildGridState(event);
|
|
796
1134
|
if (gridState) {
|
|
@@ -798,7 +1136,7 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
798
1136
|
const settingKey = `default-view-setting/${this._entity.Name}`;
|
|
799
1137
|
const saved = await UserInfoEngine.Instance.SetSetting(settingKey, JSON.stringify(gridState));
|
|
800
1138
|
if (saved) {
|
|
801
|
-
this.
|
|
1139
|
+
this.CurrentGridState = {
|
|
802
1140
|
columnSettings: gridState.columnSettings,
|
|
803
1141
|
sortSettings: gridState.sortSettings,
|
|
804
1142
|
aggregates: gridState.aggregates
|
|
@@ -810,10 +1148,14 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
810
1148
|
LogError('[ViewWorkspace] Failed to save default view settings');
|
|
811
1149
|
}
|
|
812
1150
|
}
|
|
813
|
-
this.
|
|
814
|
-
this.
|
|
1151
|
+
this.IsConfigPanelOpen = false;
|
|
1152
|
+
this.IsSavingView = false;
|
|
815
1153
|
this.cdr.detectChanges();
|
|
816
1154
|
}
|
|
1155
|
+
/** @deprecated Use {@link OnSaveDefaultViewSettings}. */
|
|
1156
|
+
async onSaveDefaultViewSettings(event) {
|
|
1157
|
+
return this.OnSaveDefaultViewSettings(event);
|
|
1158
|
+
}
|
|
817
1159
|
// ========================================
|
|
818
1160
|
// DELETE VIEW
|
|
819
1161
|
// ========================================
|
|
@@ -822,17 +1164,17 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
822
1164
|
* (firing cancelable {@link BeforeViewDelete} / notification {@link AfterViewDelete}); otherwise
|
|
823
1165
|
* emits {@link DeleteViewRequested}. (Generalized from DataExplorer.onDeleteView.)
|
|
824
1166
|
*/
|
|
825
|
-
async
|
|
826
|
-
if (!this.
|
|
1167
|
+
async OnDeleteView() {
|
|
1168
|
+
if (!this.CurrentViewEntity) {
|
|
827
1169
|
return;
|
|
828
1170
|
}
|
|
829
1171
|
if (!this.AutoSaveView) {
|
|
830
|
-
this.DeleteViewRequested.emit(this.
|
|
831
|
-
this.
|
|
1172
|
+
this.DeleteViewRequested.emit(this.CurrentViewEntity);
|
|
1173
|
+
this.IsConfigPanelOpen = false;
|
|
832
1174
|
this.cdr.detectChanges();
|
|
833
1175
|
return;
|
|
834
1176
|
}
|
|
835
|
-
const view = this.
|
|
1177
|
+
const view = this.CurrentViewEntity;
|
|
836
1178
|
const viewId = view.ID;
|
|
837
1179
|
const viewName = view.Name;
|
|
838
1180
|
const before = { Data: view, Cancel: false };
|
|
@@ -845,11 +1187,11 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
845
1187
|
LogError(`[ViewWorkspace] Failed to delete view: ${view.LatestResult?.CompleteMessage ?? 'unknown error'}`);
|
|
846
1188
|
return;
|
|
847
1189
|
}
|
|
848
|
-
this.
|
|
849
|
-
this.
|
|
850
|
-
this.
|
|
851
|
-
this.
|
|
852
|
-
this.
|
|
1190
|
+
this.CurrentViewEntity = null;
|
|
1191
|
+
this.SelectedViewId = null;
|
|
1192
|
+
this.ViewModified = false;
|
|
1193
|
+
this.IsConfigPanelOpen = false;
|
|
1194
|
+
this.CurrentGridState = this.loadUserDefaultGridState();
|
|
853
1195
|
// The view selector re-derives its lists reactively from UserViewEngine's cache
|
|
854
1196
|
// (BaseEntity delete auto-invalidates the engine), so no explicit reload is needed here.
|
|
855
1197
|
this.ViewSelected.emit(null);
|
|
@@ -857,6 +1199,10 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
857
1199
|
this.AfterViewDelete.emit({ ViewID: viewId, ViewName: viewName });
|
|
858
1200
|
this.cdr.detectChanges();
|
|
859
1201
|
}
|
|
1202
|
+
/** @deprecated Use {@link OnDeleteView}. */
|
|
1203
|
+
async onDeleteView() {
|
|
1204
|
+
return this.OnDeleteView();
|
|
1205
|
+
}
|
|
860
1206
|
// ========================================
|
|
861
1207
|
// QUICK SAVE
|
|
862
1208
|
// ========================================
|
|
@@ -864,47 +1210,73 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
864
1210
|
* Handle a quick-save request from the selector (F-001). Builds a summary from the config panel
|
|
865
1211
|
* and opens the quick-save dialog. (Generalized from DataExplorer.onQuickSaveRequested.)
|
|
866
1212
|
*/
|
|
867
|
-
|
|
868
|
-
this.
|
|
869
|
-
this.
|
|
870
|
-
this.
|
|
1213
|
+
OnQuickSaveRequested(saveAsNew) {
|
|
1214
|
+
this.DefaultSaveAsNew = saveAsNew;
|
|
1215
|
+
this.QuickSaveSummary = this.viewConfigPanelRef?.BuildSummary() ?? null;
|
|
1216
|
+
this.ShowQuickSaveDialog = true;
|
|
871
1217
|
this.cdr.detectChanges();
|
|
872
1218
|
}
|
|
1219
|
+
/** @deprecated Use {@link OnQuickSaveRequested}. */
|
|
1220
|
+
onQuickSaveRequested(saveAsNew) {
|
|
1221
|
+
return this.OnQuickSaveRequested(saveAsNew);
|
|
1222
|
+
}
|
|
873
1223
|
/**
|
|
874
1224
|
* Handle the quick-save dialog's save. Intercepts updates to a shared view with the shared-view
|
|
875
1225
|
* warning; otherwise executes the save. (Generalized from DataExplorer.onQuickSave.)
|
|
876
1226
|
*/
|
|
877
|
-
async
|
|
878
|
-
this.
|
|
879
|
-
if (!event.SaveAsNew && this.
|
|
1227
|
+
async OnQuickSave(event) {
|
|
1228
|
+
this.ShowQuickSaveDialog = false;
|
|
1229
|
+
if (!event.SaveAsNew && this.CurrentViewEntity?.IsShared) {
|
|
880
1230
|
this.pendingQuickSaveEvent = event;
|
|
881
|
-
this.
|
|
1231
|
+
this.ShowSharedViewWarning = true;
|
|
882
1232
|
this.cdr.detectChanges();
|
|
883
1233
|
return;
|
|
884
1234
|
}
|
|
885
1235
|
await this.executeQuickSave(event);
|
|
886
1236
|
}
|
|
887
|
-
/**
|
|
1237
|
+
/** @deprecated Use {@link OnQuickSave}. */
|
|
1238
|
+
async onQuickSave(event) {
|
|
1239
|
+
return this.OnQuickSave(event);
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Build a `ViewSaveEvent` from a `QuickSaveEvent` and delegate to {@link OnSaveView}.
|
|
1243
|
+
*
|
|
1244
|
+
* When a config-panel save is staged — a default-view Save carrying a filter, waiting only on a
|
|
1245
|
+
* name (see {@link OnSaveDefaultViewSettings}) — its columns, sort, filter and aggregates are
|
|
1246
|
+
* merged in and the dialog supplies just the name/description/sharing. The smart-filter fields
|
|
1247
|
+
* used to be hard-coded to `false`/`''` here, which dropped a smart filter on this path too
|
|
1248
|
+
* (#4220).
|
|
1249
|
+
*
|
|
1250
|
+
* The staged save is left in place until the save succeeds ({@link OnSaveView} clears it). If
|
|
1251
|
+
* the save fails or a host cancels it, the name prompt reopens so the user can retry or cancel
|
|
1252
|
+
* rather than losing the filter with nothing on screen.
|
|
1253
|
+
*/
|
|
888
1254
|
async executeQuickSave(event) {
|
|
1255
|
+
const staged = this.pendingDefaultViewSave;
|
|
889
1256
|
const viewSaveEvent = {
|
|
890
1257
|
Name: event.Name,
|
|
891
1258
|
Description: event.Description,
|
|
892
1259
|
IsShared: event.IsShared,
|
|
893
1260
|
SaveAsNew: event.SaveAsNew,
|
|
894
|
-
Columns: [],
|
|
895
|
-
SortField: null,
|
|
896
|
-
SortDirection: 'asc',
|
|
897
|
-
SortItems: [],
|
|
898
|
-
SmartFilterEnabled: false,
|
|
899
|
-
SmartFilterPrompt: '',
|
|
900
|
-
FilterState: this.
|
|
901
|
-
AggregatesConfig: null
|
|
1261
|
+
Columns: staged?.Columns ?? [],
|
|
1262
|
+
SortField: staged?.SortField ?? null,
|
|
1263
|
+
SortDirection: staged?.SortDirection ?? 'asc',
|
|
1264
|
+
SortItems: staged?.SortItems ?? [],
|
|
1265
|
+
SmartFilterEnabled: staged?.SmartFilterEnabled ?? false,
|
|
1266
|
+
SmartFilterPrompt: staged?.SmartFilterPrompt ?? '',
|
|
1267
|
+
FilterState: staged?.FilterState ?? this.FilterDialogState ?? null,
|
|
1268
|
+
AggregatesConfig: staged?.AggregatesConfig ?? null
|
|
902
1269
|
};
|
|
903
|
-
await this.
|
|
1270
|
+
const saved = await this.OnSaveView(viewSaveEvent);
|
|
1271
|
+
if (!saved && staged) {
|
|
1272
|
+
this.QuickSaveSuggestedName = event.Name;
|
|
1273
|
+
this.ShowQuickSaveDialog = true;
|
|
1274
|
+
this.cdr.detectChanges();
|
|
1275
|
+
}
|
|
904
1276
|
}
|
|
905
1277
|
/** Handle the shared-view warning action (update / save-as-copy / cancel). */
|
|
906
|
-
async
|
|
907
|
-
this.
|
|
1278
|
+
async OnSharedViewAction(action) {
|
|
1279
|
+
this.ShowSharedViewWarning = false;
|
|
908
1280
|
const event = this.pendingQuickSaveEvent;
|
|
909
1281
|
this.pendingQuickSaveEvent = null;
|
|
910
1282
|
if (!event) {
|
|
@@ -918,27 +1290,54 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
918
1290
|
}
|
|
919
1291
|
this.cdr.detectChanges();
|
|
920
1292
|
}
|
|
1293
|
+
/** @deprecated Use {@link OnSharedViewAction}. */
|
|
1294
|
+
async onSharedViewAction(action) {
|
|
1295
|
+
return this.OnSharedViewAction(action);
|
|
1296
|
+
}
|
|
921
1297
|
/** Handle the shared-view warning cancel. */
|
|
922
|
-
|
|
923
|
-
this.
|
|
1298
|
+
OnSharedViewWarningCancel() {
|
|
1299
|
+
this.ShowSharedViewWarning = false;
|
|
924
1300
|
this.pendingQuickSaveEvent = null;
|
|
925
1301
|
this.cdr.detectChanges();
|
|
926
1302
|
}
|
|
1303
|
+
/** @deprecated Use {@link OnSharedViewWarningCancel}. */
|
|
1304
|
+
onSharedViewWarningCancel() {
|
|
1305
|
+
return this.OnSharedViewWarningCancel();
|
|
1306
|
+
}
|
|
927
1307
|
/** Handle the quick-save dialog close. */
|
|
928
|
-
|
|
929
|
-
this.
|
|
1308
|
+
OnQuickSaveClose() {
|
|
1309
|
+
this.ShowQuickSaveDialog = false;
|
|
1310
|
+
// Abandoning the name prompt abandons the promoted save with it — otherwise the staged
|
|
1311
|
+
// filter would surface on whatever the user saved next.
|
|
1312
|
+
this.clearPendingNewViewState();
|
|
930
1313
|
this.cdr.detectChanges();
|
|
931
1314
|
}
|
|
1315
|
+
/** @deprecated Use {@link OnQuickSaveClose}. */
|
|
1316
|
+
onQuickSaveClose() {
|
|
1317
|
+
return this.OnQuickSaveClose();
|
|
1318
|
+
}
|
|
932
1319
|
/** Handle "open advanced" from the quick-save dialog — carry data into the config panel. */
|
|
933
|
-
|
|
934
|
-
this.
|
|
935
|
-
this.
|
|
936
|
-
this.
|
|
937
|
-
|
|
938
|
-
this
|
|
939
|
-
|
|
1320
|
+
OnQuickSaveOpenAdvanced(event) {
|
|
1321
|
+
this.PendingNewViewName = event.Name;
|
|
1322
|
+
this.PendingNewViewDescription = event.Description;
|
|
1323
|
+
this.PendingNewViewIsShared = event.IsShared;
|
|
1324
|
+
// Hand any staged filter back to the panel, which resets its own filter state on open.
|
|
1325
|
+
// Without this the escape hatch out of the name prompt would silently drop the filter.
|
|
1326
|
+
const staged = this.pendingDefaultViewSave;
|
|
1327
|
+
if (staged) {
|
|
1328
|
+
this.PendingNewViewFilterState = staged.FilterState;
|
|
1329
|
+
this.PendingNewViewSmartFilterEnabled = staged.SmartFilterEnabled;
|
|
1330
|
+
this.PendingNewViewSmartFilterPrompt = staged.SmartFilterPrompt;
|
|
1331
|
+
}
|
|
1332
|
+
this.DefaultSaveAsNew = true;
|
|
1333
|
+
this.ShowQuickSaveDialog = false;
|
|
1334
|
+
this.IsConfigPanelOpen = true;
|
|
940
1335
|
this.cdr.detectChanges();
|
|
941
1336
|
}
|
|
1337
|
+
/** @deprecated Use {@link OnQuickSaveOpenAdvanced}. */
|
|
1338
|
+
onQuickSaveOpenAdvanced(event) {
|
|
1339
|
+
return this.OnQuickSaveOpenAdvanced(event);
|
|
1340
|
+
}
|
|
942
1341
|
// ========================================
|
|
943
1342
|
// DUPLICATE VIEW
|
|
944
1343
|
// ========================================
|
|
@@ -946,19 +1345,23 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
946
1345
|
* Handle a duplicate request (F-005). Opens the duplicate dialog so the user names the copy.
|
|
947
1346
|
* (Generalized from DataExplorer.onDuplicateView.)
|
|
948
1347
|
*/
|
|
949
|
-
|
|
950
|
-
const targetId = viewId || this.
|
|
1348
|
+
OnDuplicateViewRequested(viewId) {
|
|
1349
|
+
const targetId = viewId || this.CurrentViewEntity?.ID;
|
|
951
1350
|
if (!targetId || !this._entity) {
|
|
952
1351
|
return;
|
|
953
1352
|
}
|
|
954
1353
|
const allViews = [...(this.viewSelectorRef?.MyViews ?? []), ...(this.viewSelectorRef?.SharedViews ?? [])];
|
|
955
1354
|
const viewItem = allViews.find(v => v.id === targetId);
|
|
956
1355
|
this.duplicateTargetViewId = targetId;
|
|
957
|
-
this.
|
|
958
|
-
this.
|
|
959
|
-
this.
|
|
1356
|
+
this.DuplicateSourceViewName = viewItem?.name || this.CurrentViewEntity?.Name || 'View';
|
|
1357
|
+
this.DuplicateSummary = this.buildDuplicateSummary(viewItem?.entity ?? this.CurrentViewEntity);
|
|
1358
|
+
this.ShowDuplicateDialog = true;
|
|
960
1359
|
this.cdr.detectChanges();
|
|
961
1360
|
}
|
|
1361
|
+
/** @deprecated Use {@link OnDuplicateViewRequested}. */
|
|
1362
|
+
onDuplicateViewRequested(viewId) {
|
|
1363
|
+
return this.OnDuplicateViewRequested(viewId);
|
|
1364
|
+
}
|
|
962
1365
|
/** Build a {@link ViewConfigSummary} from a view entity for the duplicate dialog. */
|
|
963
1366
|
buildDuplicateSummary(view) {
|
|
964
1367
|
if (!view) {
|
|
@@ -1008,8 +1411,8 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
1008
1411
|
* copies its config into a new personal view, and persists it; otherwise emits
|
|
1009
1412
|
* {@link DuplicateViewRequested}. (Generalized from DataExplorer.onDuplicateConfirmed.)
|
|
1010
1413
|
*/
|
|
1011
|
-
async
|
|
1012
|
-
this.
|
|
1414
|
+
async OnDuplicateConfirmed(event) {
|
|
1415
|
+
this.ShowDuplicateDialog = false;
|
|
1013
1416
|
const targetId = this.duplicateTargetViewId;
|
|
1014
1417
|
this.duplicateTargetViewId = null;
|
|
1015
1418
|
if (!targetId || !this._entity) {
|
|
@@ -1021,6 +1424,10 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
1021
1424
|
}
|
|
1022
1425
|
await this.persistDuplicate(targetId, event.Name);
|
|
1023
1426
|
}
|
|
1427
|
+
/** @deprecated Use {@link OnDuplicateConfirmed}. */
|
|
1428
|
+
async onDuplicateConfirmed(event) {
|
|
1429
|
+
return this.OnDuplicateConfirmed(event);
|
|
1430
|
+
}
|
|
1024
1431
|
/** Load the source view, copy its config into a new personal view, and persist it. */
|
|
1025
1432
|
async persistDuplicate(sourceViewId, newName) {
|
|
1026
1433
|
const provider = this.ProviderToUse;
|
|
@@ -1052,18 +1459,26 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
1052
1459
|
this.AfterViewSave.emit({ View: newView, IsNew: true });
|
|
1053
1460
|
}
|
|
1054
1461
|
/** Handle the duplicate dialog cancel. */
|
|
1055
|
-
|
|
1056
|
-
this.
|
|
1462
|
+
OnDuplicateCancel() {
|
|
1463
|
+
this.ShowDuplicateDialog = false;
|
|
1057
1464
|
this.duplicateTargetViewId = null;
|
|
1058
1465
|
this.cdr.detectChanges();
|
|
1059
1466
|
}
|
|
1467
|
+
/** @deprecated Use {@link OnDuplicateCancel}. */
|
|
1468
|
+
onDuplicateCancel() {
|
|
1469
|
+
return this.OnDuplicateCancel();
|
|
1470
|
+
}
|
|
1060
1471
|
/** Handle duplicate triggered from the config panel — duplicate the selected view. */
|
|
1061
|
-
|
|
1062
|
-
if (this.
|
|
1063
|
-
this.
|
|
1064
|
-
this.
|
|
1472
|
+
OnDuplicateFromPanel() {
|
|
1473
|
+
if (this.CurrentViewEntity?.ID) {
|
|
1474
|
+
this.IsConfigPanelOpen = false;
|
|
1475
|
+
this.OnDuplicateViewRequested(this.CurrentViewEntity.ID);
|
|
1065
1476
|
}
|
|
1066
1477
|
}
|
|
1478
|
+
/** @deprecated Use {@link OnDuplicateFromPanel}. */
|
|
1479
|
+
onDuplicateFromPanel() {
|
|
1480
|
+
return this.OnDuplicateFromPanel();
|
|
1481
|
+
}
|
|
1067
1482
|
// ========================================
|
|
1068
1483
|
// REVERT
|
|
1069
1484
|
// ========================================
|
|
@@ -1071,18 +1486,22 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
1071
1486
|
* Handle a revert request (F-007). Re-parses the saved view's grid state, clears the modified
|
|
1072
1487
|
* flag, and reloads. (Generalized from DataExplorer.onRevertView.)
|
|
1073
1488
|
*/
|
|
1074
|
-
async
|
|
1075
|
-
if (!this.
|
|
1489
|
+
async OnRevertView() {
|
|
1490
|
+
if (!this.CurrentViewEntity) {
|
|
1076
1491
|
return;
|
|
1077
1492
|
}
|
|
1078
|
-
const gridState = this.parseViewGridState(this.
|
|
1493
|
+
const gridState = this.parseViewGridState(this.CurrentViewEntity);
|
|
1079
1494
|
if (gridState) {
|
|
1080
|
-
this.
|
|
1495
|
+
this.CurrentGridState = gridState;
|
|
1081
1496
|
}
|
|
1082
|
-
this.
|
|
1497
|
+
this.ViewModified = false;
|
|
1083
1498
|
await this.entityViewerRef?.LoadData();
|
|
1084
1499
|
this.cdr.detectChanges();
|
|
1085
1500
|
}
|
|
1501
|
+
/** @deprecated Use {@link OnRevertView}. */
|
|
1502
|
+
async onRevertView() {
|
|
1503
|
+
return this.OnRevertView();
|
|
1504
|
+
}
|
|
1086
1505
|
// ========================================
|
|
1087
1506
|
// GRID/SORT/FILTER STATE BUILDERS
|
|
1088
1507
|
// (Generalized from DataExplorer — no state-service / selectedEntity coupling.)
|
|
@@ -1105,8 +1524,8 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
1105
1524
|
format: col.format
|
|
1106
1525
|
}));
|
|
1107
1526
|
}
|
|
1108
|
-
else if (this.
|
|
1109
|
-
columnSettings = this.
|
|
1527
|
+
else if (this.CurrentGridState?.columnSettings && this.CurrentGridState.columnSettings.length > 0) {
|
|
1528
|
+
columnSettings = this.CurrentGridState.columnSettings;
|
|
1110
1529
|
}
|
|
1111
1530
|
else if (this._entity) {
|
|
1112
1531
|
columnSettings = this._entity.Fields
|
|
@@ -1128,8 +1547,8 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
1128
1547
|
}
|
|
1129
1548
|
const sortSettings = this.buildGridSortSettings(event);
|
|
1130
1549
|
let aggregates = event.AggregatesConfig ?? undefined;
|
|
1131
|
-
if (!aggregates && this.
|
|
1132
|
-
aggregates = this.
|
|
1550
|
+
if (!aggregates && this.CurrentGridState?.aggregates) {
|
|
1551
|
+
aggregates = this.CurrentGridState.aggregates;
|
|
1133
1552
|
}
|
|
1134
1553
|
return { columnSettings, sortSettings, aggregates };
|
|
1135
1554
|
}
|
|
@@ -1141,8 +1560,8 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
1141
1560
|
if (event.SortField) {
|
|
1142
1561
|
return [{ field: event.SortField, dir: event.SortDirection }];
|
|
1143
1562
|
}
|
|
1144
|
-
if (this.
|
|
1145
|
-
return this.
|
|
1563
|
+
if (this.CurrentGridState?.sortSettings && this.CurrentGridState.sortSettings.length > 0) {
|
|
1564
|
+
return this.CurrentGridState.sortSettings;
|
|
1146
1565
|
}
|
|
1147
1566
|
return undefined;
|
|
1148
1567
|
}
|
|
@@ -1162,6 +1581,44 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
1162
1581
|
? JSON.stringify(event.FilterState)
|
|
1163
1582
|
: JSON.stringify({ logic: 'and', filters: [] });
|
|
1164
1583
|
}
|
|
1584
|
+
/**
|
|
1585
|
+
* Whether a save event carries a filter the user would expect to be kept — a smart prompt with
|
|
1586
|
+
* actual text, or at least one traditional filter. Deliberately stricter than the config panel's
|
|
1587
|
+
* own `hasActiveFilters()`, which only looks at the traditional list.
|
|
1588
|
+
*/
|
|
1589
|
+
eventHasFilter(event) {
|
|
1590
|
+
const hasSmartFilter = event.SmartFilterEnabled && !!event.SmartFilterPrompt?.trim();
|
|
1591
|
+
const hasTraditionalFilter = (event.FilterState?.filters?.length ?? 0) > 0;
|
|
1592
|
+
return hasSmartFilter || hasTraditionalFilter;
|
|
1593
|
+
}
|
|
1594
|
+
/** Longest suggested name we will build before eliding the smart prompt. */
|
|
1595
|
+
static SUGGESTED_NAME_MAX_LENGTH = 60;
|
|
1596
|
+
/**
|
|
1597
|
+
* Propose a name for a view being promoted out of the default view, so the user can accept it
|
|
1598
|
+
* with a single click. A smart prompt describes the view better than anything we could invent,
|
|
1599
|
+
* so it becomes the name (elided if long); a traditional filter gets a plain marker.
|
|
1600
|
+
*/
|
|
1601
|
+
buildSuggestedViewName(event) {
|
|
1602
|
+
const entityName = this._entity?.DisplayNameOrName ?? this._entity?.Name ?? 'View';
|
|
1603
|
+
const prompt = event.SmartFilterEnabled ? event.SmartFilterPrompt?.trim() : '';
|
|
1604
|
+
if (!prompt) {
|
|
1605
|
+
return `${entityName} — Filtered`;
|
|
1606
|
+
}
|
|
1607
|
+
const budget = ViewWorkspaceComponent.SUGGESTED_NAME_MAX_LENGTH - entityName.length - ' — '.length;
|
|
1608
|
+
const suffix = prompt.length > budget ? `${prompt.slice(0, Math.max(budget - 1, 1)).trimEnd()}…` : prompt;
|
|
1609
|
+
return `${entityName} — ${suffix}`;
|
|
1610
|
+
}
|
|
1611
|
+
/** Build a {@link ViewConfigSummary} from a pending save event, for the quick-save dialog. */
|
|
1612
|
+
buildConfigSummary(event) {
|
|
1613
|
+
return {
|
|
1614
|
+
ColumnCount: event.Columns.length,
|
|
1615
|
+
FilterCount: event.FilterState?.filters?.length ?? 0,
|
|
1616
|
+
SortCount: event.SortItems.length,
|
|
1617
|
+
SmartFilterActive: event.SmartFilterEnabled && !!event.SmartFilterPrompt?.trim(),
|
|
1618
|
+
SmartFilterPrompt: event.SmartFilterPrompt,
|
|
1619
|
+
AggregateCount: event.AggregatesConfig?.expressions?.filter(a => a.enabled !== false).length ?? 0
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1165
1622
|
// ========================================
|
|
1166
1623
|
// GRID STATE PARSE / DEFAULTS
|
|
1167
1624
|
// ========================================
|
|
@@ -1232,7 +1689,7 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
1232
1689
|
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.viewSelectorRef = _t.first);
|
|
1233
1690
|
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.entityViewerRef = _t.first);
|
|
1234
1691
|
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.viewConfigPanelRef = _t.first);
|
|
1235
|
-
} }, inputs: { Entity: "Entity", EntityName: "EntityName", EntityID: "EntityID", AutoSaveView: "AutoSaveView", SelectedView: "SelectedView", FilterText: "FilterText", SelectedRecordId: "SelectedRecordId", ViewerConfig: "ViewerConfig" }, outputs: { OpenRecordRequested: "OpenRecordRequested", OpenViewInTabRequested: "OpenViewInTabRequested", CreateNewRecordRequested: "CreateNewRecordRequested", OpenRelatedRecordRequested: "OpenRelatedRecordRequested", RecordSelected: "RecordSelected", ViewSelected: "ViewSelected", SelectedViewChange: "SelectedViewChange", FilterTextChanged: "FilterTextChanged", DataLoaded: "DataLoaded", FilteredCountChanged: "FilteredCountChanged", BeforeViewSave: "BeforeViewSave", AfterViewSave: "AfterViewSave", BeforeViewDelete: "BeforeViewDelete", AfterViewDelete: "AfterViewDelete", SaveViewRequested: "SaveViewRequested", DeleteViewRequested: "DeleteViewRequested", DuplicateViewRequested: "DuplicateViewRequested", SaveDefaultsRequested: "SaveDefaultsRequested" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 13, vars:
|
|
1692
|
+
} }, inputs: { Entity: "Entity", EntityName: "EntityName", EntityID: "EntityID", AutoSaveView: "AutoSaveView", SelectedView: "SelectedView", FilterText: "FilterText", SelectedRecordId: "SelectedRecordId", ViewerConfig: "ViewerConfig" }, outputs: { OpenRecordRequested: "OpenRecordRequested", OpenViewInTabRequested: "OpenViewInTabRequested", CreateNewRecordRequested: "CreateNewRecordRequested", OpenRelatedRecordRequested: "OpenRelatedRecordRequested", RecordSelected: "RecordSelected", ViewSelected: "ViewSelected", SelectedViewChange: "SelectedViewChange", FilterTextChanged: "FilterTextChanged", DataLoaded: "DataLoaded", FilteredCountChanged: "FilteredCountChanged", BeforeViewSave: "BeforeViewSave", AfterViewSave: "AfterViewSave", BeforeViewDelete: "BeforeViewDelete", AfterViewDelete: "AfterViewDelete", SaveViewRequested: "SaveViewRequested", DeleteViewRequested: "DeleteViewRequested", DuplicateViewRequested: "DuplicateViewRequested", SaveDefaultsRequested: "SaveDefaultsRequested" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 13, vars: 38, consts: [[1, "view-workspace"], [1, "workspace-toolbar"], [3, "ViewSelected", "SaveViewRequested", "OpenInTabRequested", "ConfigureViewRequested", "CreateNewRecordRequested", "ExportRequested", "DuplicateViewRequested", "QuickSaveRequested", "RevertRequested", "Provider", "Entity", "SelectedViewID", "ViewModified"], [1, "workspace-toolbar-spacer"], [3, "ViewTypeSelected", "Provider", "Entity", "ActiveViewTypeID"], [1, "workspace-body"], [3, "Provider", "Entity", "ViewEntity", "FilterText", "SelectedRecordID", "GridState", "AutoSaveView", "Config"], [3, "Close", "Save", "SaveDefaults", "Delete", "Duplicate", "OpenFilterDialogRequest", "Provider", "Entity", "ViewEntity", "IsOpen", "CurrentGridState", "SampleData", "ExternalFilterState", "IsSaving", "DefaultSaveAsNew", "PendingNewViewName", "PendingNewViewDescription", "PendingNewViewIsShared", "PendingNewViewFilterState", "PendingNewViewSmartFilterEnabled", "PendingNewViewSmartFilterPrompt"], [3, "Save", "Close", "OpenAdvanced", "IsOpen", "ViewEntity", "EntityName", "Summary", "IsSaving", "DefaultSaveAsNew", "SuggestedName"], [3, "Duplicate", "Cancel", "IsOpen", "SourceViewName", "Summary"], [3, "Action", "Cancel", "IsOpen", "ViewName"], [3, "closed", "visible", "config"], [3, "FilterTextChange", "RecordSelected", "RecordOpened", "DataLoaded", "FilteredCountChanged", "OpenRelatedRecordRequested", "CreateRecordRequested", "ConfigureRequested", "Provider", "Entity", "ViewEntity", "FilterText", "SelectedRecordID", "GridState", "AutoSaveView", "Config"], [1, "filter-dialog-backdrop", 3, "click"], ["role", "dialog", "aria-modal", "true", 1, "filter-dialog"], [1, "filter-dialog-header"], [1, "filter-dialog-title"], [1, "fa-solid", "fa-filter"], ["type", "button", "title", "Close", 1, "filter-dialog-close", 3, "click"], [1, "fa-solid", "fa-times"], [1, "filter-dialog-content"], [3, "apply", "filterChange", "fields", "filter", "showSummary"], [1, "filter-dialog-footer"], ["mjButton", "", "variant", "primary", "size", "sm", "type", "button", 3, "click"], [1, "fa-solid", "fa-check"], ["mjButton", "", "variant", "flat", "size", "sm", "type", "button", 3, "click"]], template: function ViewWorkspaceComponent_Template(rf, ctx) { if (rf & 1) {
|
|
1236
1693
|
i0.ɵɵelementStart(0, "div", 0)(1, "div", 1)(2, "mj-view-selector", 2);
|
|
1237
1694
|
i0.ɵɵlistener("ViewSelected", function ViewWorkspaceComponent_Template_mj_view_selector_ViewSelected_2_listener($event) { return ctx.onViewSelected($event); })("SaveViewRequested", function ViewWorkspaceComponent_Template_mj_view_selector_SaveViewRequested_2_listener($event) { return ctx.onSaveViewRequested($event); })("OpenInTabRequested", function ViewWorkspaceComponent_Template_mj_view_selector_OpenInTabRequested_2_listener($event) { return ctx.onOpenInTabRequested($event); })("ConfigureViewRequested", function ViewWorkspaceComponent_Template_mj_view_selector_ConfigureViewRequested_2_listener() { return ctx.onConfigureViewRequested(); })("CreateNewRecordRequested", function ViewWorkspaceComponent_Template_mj_view_selector_CreateNewRecordRequested_2_listener() { return ctx.onCreateNewRecordRequested(); })("ExportRequested", function ViewWorkspaceComponent_Template_mj_view_selector_ExportRequested_2_listener() { return ctx.onExportRequested(); })("DuplicateViewRequested", function ViewWorkspaceComponent_Template_mj_view_selector_DuplicateViewRequested_2_listener($event) { return ctx.onDuplicateViewRequested($event); })("QuickSaveRequested", function ViewWorkspaceComponent_Template_mj_view_selector_QuickSaveRequested_2_listener($event) { return ctx.onQuickSaveRequested($event); })("RevertRequested", function ViewWorkspaceComponent_Template_mj_view_selector_RevertRequested_2_listener() { return ctx.onRevertView(); });
|
|
1238
1695
|
i0.ɵɵelementEnd();
|
|
@@ -1267,11 +1724,11 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
1267
1724
|
i0.ɵɵadvance(2);
|
|
1268
1725
|
i0.ɵɵconditional(ctx.Entity ? 6 : -1);
|
|
1269
1726
|
i0.ɵɵadvance();
|
|
1270
|
-
i0.ɵɵproperty("Provider", ctx.Provider)("Entity", ctx.Entity)("ViewEntity", ctx.currentViewEntity)("IsOpen", ctx.isConfigPanelOpen)("CurrentGridState", ctx.currentGridState)("SampleData", ctx.loadedRecords)("ExternalFilterState", ctx.filterDialogState)("IsSaving", ctx.isSavingView)("DefaultSaveAsNew", ctx.defaultSaveAsNew)("PendingNewViewName", ctx.pendingNewViewName)("PendingNewViewDescription", ctx.pendingNewViewDescription)("PendingNewViewIsShared", ctx.pendingNewViewIsShared);
|
|
1727
|
+
i0.ɵɵproperty("Provider", ctx.Provider)("Entity", ctx.Entity)("ViewEntity", ctx.currentViewEntity)("IsOpen", ctx.isConfigPanelOpen)("CurrentGridState", ctx.currentGridState)("SampleData", ctx.loadedRecords)("ExternalFilterState", ctx.filterDialogState)("IsSaving", ctx.isSavingView)("DefaultSaveAsNew", ctx.defaultSaveAsNew)("PendingNewViewName", ctx.pendingNewViewName)("PendingNewViewDescription", ctx.pendingNewViewDescription)("PendingNewViewIsShared", ctx.pendingNewViewIsShared)("PendingNewViewFilterState", ctx.PendingNewViewFilterState)("PendingNewViewSmartFilterEnabled", ctx.PendingNewViewSmartFilterEnabled)("PendingNewViewSmartFilterPrompt", ctx.PendingNewViewSmartFilterPrompt);
|
|
1271
1728
|
i0.ɵɵadvance();
|
|
1272
1729
|
i0.ɵɵconditional(ctx.isFilterDialogOpen ? 8 : -1);
|
|
1273
1730
|
i0.ɵɵadvance();
|
|
1274
|
-
i0.ɵɵproperty("IsOpen", ctx.showQuickSaveDialog)("ViewEntity", ctx.currentViewEntity)("EntityName", (ctx.Entity == null ? null : ctx.Entity.DisplayNameOrName) ?? "")("Summary", ctx.quickSaveSummary)("IsSaving", ctx.isSavingView)("DefaultSaveAsNew", ctx.defaultSaveAsNew);
|
|
1731
|
+
i0.ɵɵproperty("IsOpen", ctx.showQuickSaveDialog)("ViewEntity", ctx.currentViewEntity)("EntityName", (ctx.Entity == null ? null : ctx.Entity.DisplayNameOrName) ?? "")("Summary", ctx.quickSaveSummary)("IsSaving", ctx.isSavingView)("DefaultSaveAsNew", ctx.defaultSaveAsNew)("SuggestedName", ctx.QuickSaveSuggestedName);
|
|
1275
1732
|
i0.ɵɵadvance();
|
|
1276
1733
|
i0.ɵɵproperty("IsOpen", ctx.showDuplicateDialog)("SourceViewName", ctx.duplicateSourceViewName)("Summary", ctx.duplicateSummary);
|
|
1277
1734
|
i0.ɵɵadvance();
|
|
@@ -1282,7 +1739,7 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
1282
1739
|
}
|
|
1283
1740
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ViewWorkspaceComponent, [{
|
|
1284
1741
|
type: Component,
|
|
1285
|
-
args: [{ standalone: false, selector: 'mj-view-workspace', template: "<div class=\"view-workspace\">\n <!-- Toolbar: view selector + view-type switcher (single row) -->\n <div class=\"workspace-toolbar\">\n <mj-view-selector\n [Provider]=\"Provider\"\n [Entity]=\"Entity\"\n [SelectedViewID]=\"selectedViewId\"\n [ViewModified]=\"viewModified\"\n (ViewSelected)=\"onViewSelected($event)\"\n (SaveViewRequested)=\"onSaveViewRequested($event)\"\n (OpenInTabRequested)=\"onOpenInTabRequested($event)\"\n (ConfigureViewRequested)=\"onConfigureViewRequested()\"\n (CreateNewRecordRequested)=\"onCreateNewRecordRequested()\"\n (ExportRequested)=\"onExportRequested()\"\n (DuplicateViewRequested)=\"onDuplicateViewRequested($event)\"\n (QuickSaveRequested)=\"onQuickSaveRequested($event)\"\n (RevertRequested)=\"onRevertView()\">\n </mj-view-selector>\n\n <span class=\"workspace-toolbar-spacer\"></span>\n\n <!-- View-type switcher lifted into the top toolbar (same row as the selector). The inner\n entity-viewer's own header switcher is suppressed via innerViewerConfig so it appears\n exactly once. Switching routes to the viewer via SelectViewTypeById. -->\n <mj-view-type-switcher\n [Provider]=\"Provider\"\n [Entity]=\"Entity\"\n [ActiveViewTypeID]=\"activeViewTypeId\"\n (ViewTypeSelected)=\"onToolbarViewTypeSelected($event)\">\n </mj-view-type-switcher>\n </div>\n\n <!-- Data renderer -->\n <div class=\"workspace-body\">\n @if (Entity) {\n <mj-entity-viewer\n [Provider]=\"Provider\"\n [Entity]=\"Entity\"\n [ViewEntity]=\"currentViewEntity\"\n [FilterText]=\"filterText\"\n [SelectedRecordID]=\"selectedRecordId\"\n [GridState]=\"currentGridState\"\n [AutoSaveView]=\"AutoSaveView\"\n [Config]=\"innerViewerConfig\"\n (FilterTextChange)=\"onFilterTextChanged($event)\"\n (RecordSelected)=\"onRecordSelected($event)\"\n (RecordOpened)=\"onRecordOpened($event)\"\n (DataLoaded)=\"onDataLoaded($event)\"\n (FilteredCountChanged)=\"onFilteredCountChanged($event)\"\n (OpenRelatedRecordRequested)=\"onOpenRelatedRecordRequested($event)\"\n (CreateRecordRequested)=\"onCreateRecordRequested()\"\n (ConfigureRequested)=\"onConfigureViewRequested()\">\n </mj-entity-viewer>\n }\n </div>\n\n <!-- View configuration panel (slide-in) -->\n <mj-view-config-panel\n [Provider]=\"Provider\"\n [Entity]=\"Entity\"\n [ViewEntity]=\"currentViewEntity\"\n [IsOpen]=\"isConfigPanelOpen\"\n [CurrentGridState]=\"currentGridState\"\n [SampleData]=\"loadedRecords\"\n [ExternalFilterState]=\"filterDialogState\"\n [IsSaving]=\"isSavingView\"\n [DefaultSaveAsNew]=\"defaultSaveAsNew\"\n [PendingNewViewName]=\"pendingNewViewName\"\n [PendingNewViewDescription]=\"pendingNewViewDescription\"\n [PendingNewViewIsShared]=\"pendingNewViewIsShared\"\n (Close)=\"onCloseConfigPanel()\"\n (Save)=\"onSaveView($event)\"\n (SaveDefaults)=\"onSaveDefaultViewSettings($event)\"\n (Delete)=\"onDeleteView()\"\n (Duplicate)=\"onDuplicateFromPanel()\"\n (OpenFilterDialogRequest)=\"onOpenFilterDialogRequest($event)\">\n </mj-view-config-panel>\n\n <!-- Full-width filter dialog -->\n @if (isFilterDialogOpen) {\n <div class=\"filter-dialog-backdrop\" (click)=\"onCloseFilterDialog()\"></div>\n <div class=\"filter-dialog\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"filter-dialog-header\">\n <div class=\"filter-dialog-title\">\n <i class=\"fa-solid fa-filter\"></i>\n <span>Edit Filters</span>\n </div>\n <button class=\"filter-dialog-close\" type=\"button\" (click)=\"onCloseFilterDialog()\" title=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"filter-dialog-content\">\n <mj-filter-builder\n [fields]=\"filterDialogFields\"\n [filter]=\"filterDialogState\"\n [showSummary]=\"true\"\n (apply)=\"onFilterApplied($event)\"\n (filterChange)=\"filterDialogState = $event\">\n </mj-filter-builder>\n </div>\n <div class=\"filter-dialog-footer\">\n <button mjButton variant=\"primary\" size=\"sm\" type=\"button\"\n (click)=\"filterDialogState && onFilterApplied(filterDialogState)\">\n <i class=\"fa-solid fa-check\"></i> Apply Filters\n </button>\n <button mjButton variant=\"flat\" size=\"sm\" type=\"button\" (click)=\"onCloseFilterDialog()\">\n Cancel\n </button>\n </div>\n </div>\n }\n\n <!-- Quick save dialog -->\n <mj-quick-save-dialog\n [IsOpen]=\"showQuickSaveDialog\"\n [ViewEntity]=\"currentViewEntity\"\n [EntityName]=\"Entity?.DisplayNameOrName ?? ''\"\n [Summary]=\"quickSaveSummary\"\n [IsSaving]=\"isSavingView\"\n [DefaultSaveAsNew]=\"defaultSaveAsNew\"\n (Save)=\"onQuickSave($event)\"\n (Close)=\"onQuickSaveClose()\"\n (OpenAdvanced)=\"onQuickSaveOpenAdvanced($event)\">\n </mj-quick-save-dialog>\n\n <!-- Duplicate view dialog -->\n <mj-duplicate-view-dialog\n [IsOpen]=\"showDuplicateDialog\"\n [SourceViewName]=\"duplicateSourceViewName\"\n [Summary]=\"duplicateSummary\"\n (Duplicate)=\"onDuplicateConfirmed($event)\"\n (Cancel)=\"onDuplicateCancel()\">\n </mj-duplicate-view-dialog>\n\n <!-- Shared view warning dialog -->\n <mj-shared-view-warning-dialog\n [IsOpen]=\"showSharedViewWarning\"\n [ViewName]=\"currentViewEntity?.Name ?? ''\"\n (Action)=\"onSharedViewAction($event)\"\n (Cancel)=\"onSharedViewWarningCancel()\">\n </mj-shared-view-warning-dialog>\n\n <!-- Export dialog \u2014 opened by the toolbar Export button for ANY view type. The dialog is generic\n (data + columns only), so Excel/CSV/JSON + sampling work identically on Grid/Cards/Map/Timeline. -->\n <mj-export-dialog\n [visible]=\"showExportDialog\"\n [config]=\"exportDialogConfig\"\n (closed)=\"onExportDialogClosed($event)\">\n </mj-export-dialog>\n</div>\n", styles: [":host {\n display: flex; /* keep block if you prefer; width:100% still works */\n flex: 1 1 auto; /* grow to fill the new flex row in .content-body */\n width: 100%;\n height: 100%;\n min-width: 0; /* lets it shrink too, prevents overflow when narrow */\n}\n\n.view-workspace {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n flex: 1 1 auto; /* add: grow to fill the host's flex row */\n width: 100%; /* add */\n min-width: 0; /* add: mirror min-height so it can shrink too */\n background: var(--mj-bg-page);\n}\n\n/* Toolbar row: view selector + view-type switcher */\n.workspace-toolbar {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n padding: 10px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n flex-shrink: 0;\n}\n\n/* Pushes the view-type switcher to the right end of the toolbar row */\n.workspace-toolbar-spacer {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/* Data renderer fills remaining space */\n.workspace-body {\n flex: 1 1 auto;\n min-height: 0;\n position: relative;\n overflow: hidden;\n}\n\n.workspace-body mj-entity-viewer {\n display: block;\n height: 100%;\n}\n\n/* ============================\n Full-width filter dialog\n ============================ */\n.filter-dialog-backdrop {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n z-index: 2000;\n animation: vw-fade-in 0.15s ease;\n}\n\n.filter-dialog {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 90%;\n max-width: 800px;\n max-height: 85vh;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n box-shadow: 0 20px 60px color-mix(in srgb, var(--mj-text-primary) 25%, transparent);\n z-index: 2001;\n animation: vw-slide-in 0.2s ease;\n}\n\n.filter-dialog-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.filter-dialog-title {\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 18px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.filter-dialog-title i {\n color: var(--mj-brand-primary);\n}\n\n.filter-dialog-close {\n width: 36px;\n height: 36px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n color: var(--mj-text-muted);\n font-size: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.15s ease, color 0.15s ease;\n}\n\n.filter-dialog-close:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.filter-dialog-content {\n flex: 1;\n overflow-y: auto;\n padding: 20px;\n min-height: 200px;\n}\n\n.filter-dialog-footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 16px 20px;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n border-radius: 0 0 12px 12px;\n flex-shrink: 0;\n}\n\n@keyframes vw-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes vw-slide-in {\n from {\n opacity: 0;\n transform: translate(-50%, -48%);\n }\n to {\n opacity: 1;\n transform: translate(-50%, -50%);\n }\n}\n\n@media (max-width: 600px) {\n .filter-dialog {\n width: 95%;\n max-height: 90vh;\n border-radius: 8px;\n }\n\n .filter-dialog-footer {\n flex-direction: column;\n align-items: stretch;\n }\n}\n"] }]
|
|
1742
|
+
args: [{ standalone: false, selector: 'mj-view-workspace', template: "<div class=\"view-workspace\">\n <!-- Toolbar: view selector + view-type switcher (single row) -->\n <div class=\"workspace-toolbar\">\n <mj-view-selector\n [Provider]=\"Provider\"\n [Entity]=\"Entity\"\n [SelectedViewID]=\"selectedViewId\"\n [ViewModified]=\"viewModified\"\n (ViewSelected)=\"onViewSelected($event)\"\n (SaveViewRequested)=\"onSaveViewRequested($event)\"\n (OpenInTabRequested)=\"onOpenInTabRequested($event)\"\n (ConfigureViewRequested)=\"onConfigureViewRequested()\"\n (CreateNewRecordRequested)=\"onCreateNewRecordRequested()\"\n (ExportRequested)=\"onExportRequested()\"\n (DuplicateViewRequested)=\"onDuplicateViewRequested($event)\"\n (QuickSaveRequested)=\"onQuickSaveRequested($event)\"\n (RevertRequested)=\"onRevertView()\">\n </mj-view-selector>\n\n <span class=\"workspace-toolbar-spacer\"></span>\n\n <!-- View-type switcher lifted into the top toolbar (same row as the selector). The inner\n entity-viewer's own header switcher is suppressed via innerViewerConfig so it appears\n exactly once. Switching routes to the viewer via SelectViewTypeById. -->\n <mj-view-type-switcher\n [Provider]=\"Provider\"\n [Entity]=\"Entity\"\n [ActiveViewTypeID]=\"activeViewTypeId\"\n (ViewTypeSelected)=\"onToolbarViewTypeSelected($event)\">\n </mj-view-type-switcher>\n </div>\n\n <!-- Data renderer -->\n <div class=\"workspace-body\">\n @if (Entity) {\n <mj-entity-viewer\n [Provider]=\"Provider\"\n [Entity]=\"Entity\"\n [ViewEntity]=\"currentViewEntity\"\n [FilterText]=\"filterText\"\n [SelectedRecordID]=\"selectedRecordId\"\n [GridState]=\"currentGridState\"\n [AutoSaveView]=\"AutoSaveView\"\n [Config]=\"innerViewerConfig\"\n (FilterTextChange)=\"onFilterTextChanged($event)\"\n (RecordSelected)=\"onRecordSelected($event)\"\n (RecordOpened)=\"onRecordOpened($event)\"\n (DataLoaded)=\"onDataLoaded($event)\"\n (FilteredCountChanged)=\"onFilteredCountChanged($event)\"\n (OpenRelatedRecordRequested)=\"onOpenRelatedRecordRequested($event)\"\n (CreateRecordRequested)=\"onCreateRecordRequested()\"\n (ConfigureRequested)=\"onConfigureViewRequested()\">\n </mj-entity-viewer>\n }\n </div>\n\n <!-- View configuration panel (slide-in) -->\n <mj-view-config-panel\n [Provider]=\"Provider\"\n [Entity]=\"Entity\"\n [ViewEntity]=\"currentViewEntity\"\n [IsOpen]=\"isConfigPanelOpen\"\n [CurrentGridState]=\"currentGridState\"\n [SampleData]=\"loadedRecords\"\n [ExternalFilterState]=\"filterDialogState\"\n [IsSaving]=\"isSavingView\"\n [DefaultSaveAsNew]=\"defaultSaveAsNew\"\n [PendingNewViewName]=\"pendingNewViewName\"\n [PendingNewViewDescription]=\"pendingNewViewDescription\"\n [PendingNewViewIsShared]=\"pendingNewViewIsShared\"\n [PendingNewViewFilterState]=\"PendingNewViewFilterState\"\n [PendingNewViewSmartFilterEnabled]=\"PendingNewViewSmartFilterEnabled\"\n [PendingNewViewSmartFilterPrompt]=\"PendingNewViewSmartFilterPrompt\"\n (Close)=\"onCloseConfigPanel()\"\n (Save)=\"onSaveView($event)\"\n (SaveDefaults)=\"onSaveDefaultViewSettings($event)\"\n (Delete)=\"onDeleteView()\"\n (Duplicate)=\"onDuplicateFromPanel()\"\n (OpenFilterDialogRequest)=\"onOpenFilterDialogRequest($event)\">\n </mj-view-config-panel>\n\n <!-- Full-width filter dialog -->\n @if (isFilterDialogOpen) {\n <div class=\"filter-dialog-backdrop\" (click)=\"onCloseFilterDialog()\"></div>\n <div class=\"filter-dialog\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"filter-dialog-header\">\n <div class=\"filter-dialog-title\">\n <i class=\"fa-solid fa-filter\"></i>\n <span>Edit Filters</span>\n </div>\n <button class=\"filter-dialog-close\" type=\"button\" (click)=\"onCloseFilterDialog()\" title=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"filter-dialog-content\">\n <mj-filter-builder\n [fields]=\"filterDialogFields\"\n [filter]=\"filterDialogState\"\n [showSummary]=\"true\"\n (apply)=\"onFilterApplied($event)\"\n (filterChange)=\"filterDialogState = $event\">\n </mj-filter-builder>\n </div>\n <div class=\"filter-dialog-footer\">\n <button mjButton variant=\"primary\" size=\"sm\" type=\"button\"\n (click)=\"filterDialogState && onFilterApplied(filterDialogState)\">\n <i class=\"fa-solid fa-check\"></i> Apply Filters\n </button>\n <button mjButton variant=\"flat\" size=\"sm\" type=\"button\" (click)=\"onCloseFilterDialog()\">\n Cancel\n </button>\n </div>\n </div>\n }\n\n <!-- Quick save dialog -->\n <mj-quick-save-dialog\n [IsOpen]=\"showQuickSaveDialog\"\n [ViewEntity]=\"currentViewEntity\"\n [EntityName]=\"Entity?.DisplayNameOrName ?? ''\"\n [Summary]=\"quickSaveSummary\"\n [IsSaving]=\"isSavingView\"\n [DefaultSaveAsNew]=\"defaultSaveAsNew\"\n [SuggestedName]=\"QuickSaveSuggestedName\"\n (Save)=\"onQuickSave($event)\"\n (Close)=\"onQuickSaveClose()\"\n (OpenAdvanced)=\"onQuickSaveOpenAdvanced($event)\">\n </mj-quick-save-dialog>\n\n <!-- Duplicate view dialog -->\n <mj-duplicate-view-dialog\n [IsOpen]=\"showDuplicateDialog\"\n [SourceViewName]=\"duplicateSourceViewName\"\n [Summary]=\"duplicateSummary\"\n (Duplicate)=\"onDuplicateConfirmed($event)\"\n (Cancel)=\"onDuplicateCancel()\">\n </mj-duplicate-view-dialog>\n\n <!-- Shared view warning dialog -->\n <mj-shared-view-warning-dialog\n [IsOpen]=\"showSharedViewWarning\"\n [ViewName]=\"currentViewEntity?.Name ?? ''\"\n (Action)=\"onSharedViewAction($event)\"\n (Cancel)=\"onSharedViewWarningCancel()\">\n </mj-shared-view-warning-dialog>\n\n <!-- Export dialog \u2014 opened by the toolbar Export button for ANY view type. The dialog is generic\n (data + columns only), so Excel/CSV/JSON + sampling work identically on Grid/Cards/Map/Timeline. -->\n <mj-export-dialog\n [visible]=\"showExportDialog\"\n [config]=\"exportDialogConfig\"\n (closed)=\"onExportDialogClosed($event)\">\n </mj-export-dialog>\n</div>\n", styles: [":host {\n display: flex; /* keep block if you prefer; width:100% still works */\n flex: 1 1 auto; /* grow to fill the new flex row in .content-body */\n width: 100%;\n height: 100%;\n min-width: 0; /* lets it shrink too, prevents overflow when narrow */\n}\n\n.view-workspace {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n flex: 1 1 auto; /* add: grow to fill the host's flex row */\n width: 100%; /* add */\n min-width: 0; /* add: mirror min-height so it can shrink too */\n background: var(--mj-bg-page);\n}\n\n/* Toolbar row: view selector + view-type switcher */\n.workspace-toolbar {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n padding: 10px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n flex-shrink: 0;\n}\n\n/* Pushes the view-type switcher to the right end of the toolbar row */\n.workspace-toolbar-spacer {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/* Data renderer fills remaining space */\n.workspace-body {\n flex: 1 1 auto;\n min-height: 0;\n position: relative;\n overflow: hidden;\n}\n\n.workspace-body mj-entity-viewer {\n display: block;\n height: 100%;\n}\n\n/* ============================\n Full-width filter dialog\n ============================ */\n.filter-dialog-backdrop {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n z-index: 2000;\n animation: vw-fade-in 0.15s ease;\n}\n\n.filter-dialog {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 90%;\n max-width: 800px;\n max-height: 85vh;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n box-shadow: 0 20px 60px color-mix(in srgb, var(--mj-text-primary) 25%, transparent);\n z-index: 2001;\n animation: vw-slide-in 0.2s ease;\n}\n\n.filter-dialog-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.filter-dialog-title {\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 18px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.filter-dialog-title i {\n color: var(--mj-brand-primary);\n}\n\n.filter-dialog-close {\n width: 36px;\n height: 36px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n color: var(--mj-text-muted);\n font-size: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.15s ease, color 0.15s ease;\n}\n\n.filter-dialog-close:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.filter-dialog-content {\n flex: 1;\n overflow-y: auto;\n padding: 20px;\n min-height: 200px;\n}\n\n.filter-dialog-footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 16px 20px;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n border-radius: 0 0 12px 12px;\n flex-shrink: 0;\n}\n\n@keyframes vw-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes vw-slide-in {\n from {\n opacity: 0;\n transform: translate(-50%, -48%);\n }\n to {\n opacity: 1;\n transform: translate(-50%, -50%);\n }\n}\n\n@media (max-width: 600px) {\n .filter-dialog {\n width: 95%;\n max-height: 90vh;\n border-radius: 8px;\n }\n\n .filter-dialog-footer {\n flex-direction: column;\n align-items: stretch;\n }\n}\n"] }]
|
|
1286
1743
|
}], () => [{ type: i0.ChangeDetectorRef }], { Entity: [{
|
|
1287
1744
|
type: Input
|
|
1288
1745
|
}], EntityName: [{
|
|
@@ -1345,5 +1802,5 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
|
|
|
1345
1802
|
type: ViewChild,
|
|
1346
1803
|
args: [ViewConfigPanelComponent]
|
|
1347
1804
|
}] }); })();
|
|
1348
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ViewWorkspaceComponent, { className: "ViewWorkspaceComponent", filePath: "src/lib/view-workspace/view-workspace.component.ts", lineNumber:
|
|
1805
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ViewWorkspaceComponent, { className: "ViewWorkspaceComponent", filePath: "src/lib/view-workspace/view-workspace.component.ts", lineNumber: 133 }); })();
|
|
1349
1806
|
//# sourceMappingURL=view-workspace.component.js.map
|