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