@finos/legend-data-cube 0.1.11 → 0.1.13
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/lib/components/DataCubePlaceholder.d.ts.map +1 -1
- package/lib/components/DataCubePlaceholder.js +2 -1
- package/lib/components/DataCubePlaceholder.js.map +1 -1
- package/lib/components/core/DataCubePureCodeEditorUtils.d.ts +2 -1
- package/lib/components/core/DataCubePureCodeEditorUtils.d.ts.map +1 -1
- package/lib/components/core/DataCubePureCodeEditorUtils.js.map +1 -1
- package/lib/components/view/editor/DataCubeEditor.d.ts.map +1 -1
- package/lib/components/view/editor/DataCubeEditor.js +4 -2
- package/lib/components/view/editor/DataCubeEditor.js.map +1 -1
- package/lib/components/view/editor/DataCubeEditorColumnsPanel.d.ts.map +1 -1
- package/lib/components/view/editor/DataCubeEditorColumnsPanel.js +3 -3
- package/lib/components/view/editor/DataCubeEditorColumnsPanel.js.map +1 -1
- package/lib/components/view/editor/DataCubeEditorColumnsSelector.d.ts +47 -0
- package/lib/components/view/editor/DataCubeEditorColumnsSelector.d.ts.map +1 -0
- package/lib/components/view/editor/{DataCubeEditorColumnSelector.js → DataCubeEditorColumnsSelector.js} +70 -55
- package/lib/components/view/editor/DataCubeEditorColumnsSelector.js.map +1 -0
- package/lib/components/view/editor/DataCubeEditorDimensionsPanel.d.ts +3 -13
- package/lib/components/view/editor/DataCubeEditorDimensionsPanel.d.ts.map +1 -1
- package/lib/components/view/editor/DataCubeEditorDimensionsPanel.js +707 -264
- package/lib/components/view/editor/DataCubeEditorDimensionsPanel.js.map +1 -1
- package/lib/components/view/editor/DataCubeEditorHorizontalPivotsPanel.js +2 -2
- package/lib/components/view/editor/DataCubeEditorHorizontalPivotsPanel.js.map +1 -1
- package/lib/components/view/editor/DataCubeEditorSortsPanel.js +2 -2
- package/lib/components/view/editor/DataCubeEditorSortsPanel.js.map +1 -1
- package/lib/components/view/editor/DataCubeEditorVerticalPivotsPanel.js +2 -2
- package/lib/components/view/editor/DataCubeEditorVerticalPivotsPanel.js.map +1 -1
- package/lib/components/view/{grid/DataCubeGridShared.d.ts → extend/DataCubeCodeEditor.d.ts} +7 -3
- package/lib/components/view/extend/DataCubeCodeEditor.d.ts.map +1 -0
- package/lib/components/view/extend/DataCubeCodeEditor.js +114 -0
- package/lib/components/view/extend/DataCubeCodeEditor.js.map +1 -0
- package/lib/components/view/extend/DataCubeCodeEditorState.d.ts +43 -0
- package/lib/components/view/extend/DataCubeCodeEditorState.d.ts.map +1 -0
- package/lib/components/view/extend/DataCubeCodeEditorState.js +37 -0
- package/lib/components/view/extend/DataCubeCodeEditorState.js.map +1 -0
- package/lib/components/view/extend/DataCubeColumnEditor.d.ts.map +1 -1
- package/lib/components/view/extend/DataCubeColumnEditor.js +5 -84
- package/lib/components/view/extend/DataCubeColumnEditor.js.map +1 -1
- package/lib/components/view/grid/DataCubeGrid.d.ts +2 -1
- package/lib/components/view/grid/DataCubeGrid.d.ts.map +1 -1
- package/lib/components/view/grid/DataCubeGrid.js +29 -19
- package/lib/components/view/grid/DataCubeGrid.js.map +1 -1
- package/lib/components/view/grid/DataCubeMultidimensionalGrid.d.ts +1 -1
- package/lib/components/view/grid/DataCubeMultidimensionalGrid.d.ts.map +1 -1
- package/lib/components/view/grid/DataCubeMultidimensionalGrid.js +2 -1
- package/lib/components/view/grid/DataCubeMultidimensionalGrid.js.map +1 -1
- package/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -0
- package/lib/index.js.map +1 -1
- package/lib/package.json +1 -1
- package/lib/stores/DataCubeAPI.d.ts.map +1 -1
- package/lib/stores/DataCubeAPI.js +3 -1
- package/lib/stores/DataCubeAPI.js.map +1 -1
- package/lib/stores/core/DataCubeEngine.d.ts +1 -1
- package/lib/stores/core/DataCubeEngine.d.ts.map +1 -1
- package/lib/stores/core/DataCubeQueryEngine.d.ts +5 -0
- package/lib/stores/core/DataCubeQueryEngine.d.ts.map +1 -1
- package/lib/stores/core/DataCubeQueryEngine.js +6 -0
- package/lib/stores/core/DataCubeQueryEngine.js.map +1 -1
- package/lib/stores/core/DataCubeSnapshot.d.ts +1 -1
- package/lib/stores/core/DataCubeSnapshot.d.ts.map +1 -1
- package/lib/stores/core/model/AdhocQueryDataCubeSource.d.ts +2 -0
- package/lib/stores/core/model/AdhocQueryDataCubeSource.d.ts.map +1 -1
- package/lib/stores/core/model/AdhocQueryDataCubeSource.js +3 -0
- package/lib/stores/core/model/AdhocQueryDataCubeSource.js.map +1 -1
- package/lib/stores/core/model/DataCubeConfiguration.d.ts +11 -0
- package/lib/stores/core/model/DataCubeConfiguration.d.ts.map +1 -1
- package/lib/stores/core/model/DataCubeConfiguration.js +19 -1
- package/lib/stores/core/model/DataCubeConfiguration.js.map +1 -1
- package/lib/stores/services/DataCubeSettingService.d.ts.map +1 -1
- package/lib/stores/services/DataCubeSettingService.js +10 -6
- package/lib/stores/services/DataCubeSettingService.js.map +1 -1
- package/lib/stores/view/DataCubeInfoState.d.ts +2 -0
- package/lib/stores/view/DataCubeInfoState.d.ts.map +1 -1
- package/lib/stores/view/DataCubeInfoState.js +3 -0
- package/lib/stores/view/DataCubeInfoState.js.map +1 -1
- package/lib/stores/view/editor/DataCubeEditorColumnsPanelState.d.ts +8 -8
- package/lib/stores/view/editor/DataCubeEditorColumnsPanelState.d.ts.map +1 -1
- package/lib/stores/view/editor/DataCubeEditorColumnsPanelState.js +6 -6
- package/lib/stores/view/editor/DataCubeEditorColumnsPanelState.js.map +1 -1
- package/lib/stores/view/editor/{DataCubeEditorColumnSelectorState.d.ts → DataCubeEditorColumnsSelectorState.d.ts} +6 -6
- package/lib/stores/view/editor/DataCubeEditorColumnsSelectorState.d.ts.map +1 -0
- package/lib/stores/view/editor/{DataCubeEditorColumnSelectorState.js → DataCubeEditorColumnsSelectorState.js} +4 -4
- package/lib/stores/view/editor/DataCubeEditorColumnsSelectorState.js.map +1 -0
- package/lib/stores/view/editor/DataCubeEditorDimensionsPanelState.d.ts +29 -6
- package/lib/stores/view/editor/DataCubeEditorDimensionsPanelState.d.ts.map +1 -1
- package/lib/stores/view/editor/DataCubeEditorDimensionsPanelState.js +102 -39
- package/lib/stores/view/editor/DataCubeEditorDimensionsPanelState.js.map +1 -1
- package/lib/stores/view/editor/DataCubeEditorHorizontalPivotsPanelState.d.ts +5 -5
- package/lib/stores/view/editor/DataCubeEditorHorizontalPivotsPanelState.d.ts.map +1 -1
- package/lib/stores/view/editor/DataCubeEditorHorizontalPivotsPanelState.js +7 -6
- package/lib/stores/view/editor/DataCubeEditorHorizontalPivotsPanelState.js.map +1 -1
- package/lib/stores/view/editor/DataCubeEditorMutableConfiguration.d.ts +2 -1
- package/lib/stores/view/editor/DataCubeEditorMutableConfiguration.d.ts.map +1 -1
- package/lib/stores/view/editor/DataCubeEditorMutableConfiguration.js +5 -0
- package/lib/stores/view/editor/DataCubeEditorMutableConfiguration.js.map +1 -1
- package/lib/stores/view/editor/DataCubeEditorSortsPanelState.d.ts +5 -5
- package/lib/stores/view/editor/DataCubeEditorSortsPanelState.d.ts.map +1 -1
- package/lib/stores/view/editor/DataCubeEditorSortsPanelState.js +6 -6
- package/lib/stores/view/editor/DataCubeEditorSortsPanelState.js.map +1 -1
- package/lib/stores/view/editor/DataCubeEditorState.d.ts +3 -0
- package/lib/stores/view/editor/DataCubeEditorState.d.ts.map +1 -1
- package/lib/stores/view/editor/DataCubeEditorState.js +6 -0
- package/lib/stores/view/editor/DataCubeEditorState.js.map +1 -1
- package/lib/stores/view/editor/DataCubeEditorVerticalPivotsPanelState.d.ts +5 -5
- package/lib/stores/view/editor/DataCubeEditorVerticalPivotsPanelState.d.ts.map +1 -1
- package/lib/stores/view/editor/DataCubeEditorVerticalPivotsPanelState.js +6 -6
- package/lib/stores/view/editor/DataCubeEditorVerticalPivotsPanelState.js.map +1 -1
- package/lib/stores/view/extend/DataCubeColumnEditorState.d.ts +7 -14
- package/lib/stores/view/extend/DataCubeColumnEditorState.d.ts.map +1 -1
- package/lib/stores/view/extend/DataCubeColumnEditorState.js +14 -22
- package/lib/stores/view/extend/DataCubeColumnEditorState.js.map +1 -1
- package/lib/stores/view/grid/DataCubeGridClientEngine.d.ts +2 -1
- package/lib/stores/view/grid/DataCubeGridClientEngine.d.ts.map +1 -1
- package/lib/stores/view/grid/DataCubeGridClientEngine.js +7 -0
- package/lib/stores/view/grid/DataCubeGridClientEngine.js.map +1 -1
- package/lib/stores/view/grid/DataCubeGridClientExportEngine.js +1 -1
- package/lib/stores/view/grid/DataCubeGridClientExportEngine.js.map +1 -1
- package/lib/stores/view/grid/DataCubeGridMenuBuilder.d.ts +2 -0
- package/lib/stores/view/grid/DataCubeGridMenuBuilder.d.ts.map +1 -1
- package/lib/stores/view/grid/DataCubeGridMenuBuilder.js +9 -16
- package/lib/stores/view/grid/DataCubeGridMenuBuilder.js.map +1 -1
- package/lib/stores/view/grid/DataCubeGridState.d.ts +2 -2
- package/lib/stores/view/grid/DataCubeGridState.d.ts.map +1 -1
- package/lib/stores/view/grid/DataCubeGridState.js +19 -6
- package/lib/stores/view/grid/DataCubeGridState.js.map +1 -1
- package/package.json +5 -5
- package/src/components/DataCubePlaceholder.tsx +2 -0
- package/src/components/core/DataCubePureCodeEditorUtils.tsx +2 -1
- package/src/components/view/editor/DataCubeEditor.tsx +8 -3
- package/src/components/view/editor/DataCubeEditorColumnsPanel.tsx +6 -8
- package/src/components/view/editor/{DataCubeEditorColumnSelector.tsx → DataCubeEditorColumnsSelector.tsx} +108 -83
- package/src/components/view/editor/DataCubeEditorDimensionsPanel.tsx +1259 -503
- package/src/components/view/editor/DataCubeEditorHorizontalPivotsPanel.tsx +5 -5
- package/src/components/view/editor/DataCubeEditorSortsPanel.tsx +10 -10
- package/src/components/view/editor/DataCubeEditorVerticalPivotsPanel.tsx +2 -2
- package/src/components/view/extend/DataCubeCodeEditor.tsx +175 -0
- package/src/components/view/extend/DataCubeCodeEditorState.tsx +72 -0
- package/src/components/view/extend/DataCubeColumnEditor.tsx +5 -133
- package/src/components/view/grid/DataCubeGrid.tsx +70 -53
- package/src/components/view/grid/DataCubeMultidimensionalGrid.tsx +3 -1
- package/src/index.tsx +3 -0
- package/src/stores/DataCubeAPI.ts +3 -1
- package/src/stores/core/DataCubeEngine.tsx +1 -1
- package/src/stores/core/DataCubeQueryEngine.ts +6 -0
- package/src/stores/core/DataCubeSnapshot.ts +1 -1
- package/src/stores/core/model/AdhocQueryDataCubeSource.ts +3 -0
- package/src/stores/core/model/DataCubeConfiguration.ts +31 -0
- package/src/stores/services/DataCubeSettingService.tsx +10 -6
- package/src/stores/view/DataCubeInfoState.ts +4 -0
- package/src/stores/view/editor/DataCubeEditorColumnsPanelState.ts +13 -13
- package/src/stores/view/editor/{DataCubeEditorColumnSelectorState.ts → DataCubeEditorColumnsSelectorState.ts} +6 -6
- package/src/stores/view/editor/DataCubeEditorDimensionsPanelState.ts +162 -58
- package/src/stores/view/editor/DataCubeEditorHorizontalPivotsPanelState.ts +12 -11
- package/src/stores/view/editor/DataCubeEditorMutableConfiguration.ts +8 -0
- package/src/stores/view/editor/DataCubeEditorSortsPanelState.ts +10 -10
- package/src/stores/view/editor/DataCubeEditorState.tsx +6 -0
- package/src/stores/view/editor/DataCubeEditorVerticalPivotsPanelState.ts +11 -11
- package/src/stores/view/extend/DataCubeColumnEditorState.tsx +16 -26
- package/src/stores/view/grid/DataCubeGridClientEngine.ts +8 -0
- package/src/stores/view/grid/DataCubeGridClientExportEngine.ts +1 -1
- package/src/stores/view/grid/DataCubeGridMenuBuilder.tsx +26 -1
- package/src/stores/view/grid/DataCubeGridState.ts +23 -6
- package/tsconfig.json +4 -3
- package/lib/components/view/editor/DataCubeEditorColumnSelector.d.ts +0 -38
- package/lib/components/view/editor/DataCubeEditorColumnSelector.d.ts.map +0 -1
- package/lib/components/view/editor/DataCubeEditorColumnSelector.js.map +0 -1
- package/lib/components/view/grid/DataCubeGridShared.d.ts.map +0 -1
- package/lib/components/view/grid/DataCubeGridShared.js +0 -25
- package/lib/components/view/grid/DataCubeGridShared.js.map +0 -1
- package/lib/stores/view/editor/DataCubeEditorColumnSelectorState.d.ts.map +0 -1
- package/lib/stores/view/editor/DataCubeEditorColumnSelectorState.js.map +0 -1
- package/src/components/view/grid/DataCubeGridShared.tsx +0 -42
|
@@ -15,590 +15,1346 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { observer } from 'mobx-react-lite';
|
|
18
|
-
import { DataCubeIcon } from '@finos/legend-art';
|
|
18
|
+
import { cn, DataCubeIcon } from '@finos/legend-art';
|
|
19
19
|
import {
|
|
20
|
-
type ColDef,
|
|
21
|
-
type ColDefField,
|
|
22
20
|
type GridApi,
|
|
23
|
-
type
|
|
24
|
-
type
|
|
21
|
+
type IRowNode,
|
|
22
|
+
type RowDragEvent,
|
|
25
23
|
type SelectionChangedEvent,
|
|
26
24
|
AllCommunityModule,
|
|
27
25
|
} from 'ag-grid-community';
|
|
28
|
-
import
|
|
26
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
29
27
|
import {
|
|
30
28
|
AgGridReact,
|
|
31
|
-
type AgGridReactProps,
|
|
32
29
|
type CustomCellRendererProps,
|
|
33
30
|
type CustomNoRowsOverlayProps,
|
|
34
31
|
} from 'ag-grid-react';
|
|
32
|
+
import { filterByType, isNonNullable } from '@finos/legend-shared';
|
|
35
33
|
import {
|
|
36
|
-
|
|
37
|
-
type DataCubeEditorColumnSelectorState,
|
|
38
|
-
} from '../../../stores/view/editor/DataCubeEditorColumnSelectorState.js';
|
|
39
|
-
import { isNonNullable } from '@finos/legend-shared';
|
|
40
|
-
import {
|
|
34
|
+
getAllNodes,
|
|
41
35
|
getDataForAllFilteredNodes,
|
|
42
|
-
getDataForAllNodes,
|
|
43
36
|
} from '../../../stores/view/grid/DataCubeGridClientEngine.js';
|
|
37
|
+
import type { DataCubeViewState } from '../../../stores/view/DataCubeViewState.js';
|
|
38
|
+
import {
|
|
39
|
+
FormAlert,
|
|
40
|
+
FormBadge_WIP,
|
|
41
|
+
FormCheckbox,
|
|
42
|
+
} from '../../core/DataCubeFormUtils.js';
|
|
43
|
+
import { DataCubeGridMode } from '../../../stores/core/DataCubeQueryEngine.js';
|
|
44
|
+
import { DataCubeEditorColumnsSelectorColumnState } from '../../../stores/view/editor/DataCubeEditorColumnsSelectorState.js';
|
|
45
|
+
import {
|
|
46
|
+
getColumnsSelectorBaseGridProps,
|
|
47
|
+
INTERNAL__EDITOR_COLUMNS_SELECTOR_ROW_HEIGHT,
|
|
48
|
+
} from './DataCubeEditorColumnsSelector.js';
|
|
49
|
+
import {
|
|
50
|
+
DataCubeEditorDimensionState,
|
|
51
|
+
type DataCubeEditorDimensionsPanelState,
|
|
52
|
+
type DataCubeEditorDimensionsTreeNode,
|
|
53
|
+
} from '../../../stores/view/editor/DataCubeEditorDimensionsPanelState.js';
|
|
54
|
+
import { TreeDataModule } from 'ag-grid-enterprise';
|
|
55
|
+
import { _findCol } from '../../../stores/core/model/DataCubeColumn.js';
|
|
56
|
+
import { AlertType } from '../../../stores/services/DataCubeAlertService.js';
|
|
57
|
+
import { DEFAULT_ALERT_WINDOW_CONFIG } from '../../../stores/services/DataCubeLayoutService.js';
|
|
44
58
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
className: 'ag-theme-quartz',
|
|
51
|
-
animateRows: false,
|
|
52
|
-
getRowId: (params) => params.data.name,
|
|
53
|
-
editType: 'fullRow',
|
|
54
|
-
rowDragMultiRow: true,
|
|
55
|
-
rowDragEntireRow: true,
|
|
56
|
-
rowSelection: {
|
|
57
|
-
mode: 'multiRow',
|
|
58
|
-
checkboxes: true,
|
|
59
|
-
headerCheckbox: true,
|
|
60
|
-
enableClickSelection: true,
|
|
61
|
-
},
|
|
62
|
-
selectionColumnDef: {
|
|
63
|
-
width: 40,
|
|
64
|
-
headerClass: '!pl-[23px]',
|
|
65
|
-
cellClass: '!pl-1.5',
|
|
66
|
-
rowDrag: true,
|
|
67
|
-
rowDragText: (params, dragItemCount) => {
|
|
68
|
-
if (dragItemCount > 1) {
|
|
69
|
-
return `${dragItemCount} columns`;
|
|
70
|
-
}
|
|
71
|
-
return (params.rowNode?.data as T).name;
|
|
72
|
-
},
|
|
73
|
-
sortable: false,
|
|
74
|
-
resizable: false,
|
|
75
|
-
suppressHeaderMenuButton: true,
|
|
76
|
-
},
|
|
77
|
-
suppressMoveWhenRowDragging: true,
|
|
78
|
-
rowHeight: 20,
|
|
79
|
-
headerHeight: 20,
|
|
80
|
-
suppressRowHoverHighlight: false,
|
|
81
|
-
noRowsOverlayComponent: (
|
|
82
|
-
params: CustomNoRowsOverlayProps<T> & {
|
|
83
|
-
noColumnsSelectedRenderer?: (() => React.ReactNode) | undefined;
|
|
84
|
-
},
|
|
85
|
-
) => {
|
|
86
|
-
if (params.api.getQuickFilter()) {
|
|
87
|
-
return (
|
|
88
|
-
<div className="flex items-center border-[1.5px] border-neutral-300 p-2 font-semibold text-neutral-400">
|
|
89
|
-
<div>
|
|
90
|
-
<DataCubeIcon.WarningCircle className="mr-1 text-lg" />
|
|
91
|
-
</div>
|
|
92
|
-
No match found
|
|
93
|
-
</div>
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
if (params.noColumnsSelectedRenderer) {
|
|
97
|
-
return params.noColumnsSelectedRenderer();
|
|
98
|
-
}
|
|
99
|
-
return <div />;
|
|
100
|
-
},
|
|
101
|
-
// Show no rows overlay when there are no search results
|
|
102
|
-
// See https://stackoverflow.com/a/72637410
|
|
103
|
-
onModelUpdated: (event: ModelUpdatedEvent<T>) => {
|
|
104
|
-
if (event.api.getDisplayedRowCount() === 0) {
|
|
105
|
-
event.api.showNoRowsOverlay();
|
|
106
|
-
} else {
|
|
107
|
-
event.api.hideOverlay();
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
}
|
|
59
|
+
// NOTE: This is a workaround to prevent ag-grid license key check from flooding the console screen
|
|
60
|
+
// with its stack trace in Chrome.
|
|
61
|
+
// We MUST NEVER completely surpress this warning in production, else it's a violation of the ag-grid license!
|
|
62
|
+
// See https://www.ag-grid.com/react-data-grid/licensing/
|
|
63
|
+
const __INTERNAL__original_console_error = console.error; // eslint-disable-line no-console
|
|
112
64
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Move this display to a separate component to avoid re-rendering the header too frequently
|
|
67
|
+
*/
|
|
68
|
+
const AvailableColumnsSearchResultCountBadge = observer(
|
|
69
|
+
(props: { panel: DataCubeEditorDimensionsPanelState; gridApi: GridApi }) => {
|
|
70
|
+
const { panel, gridApi } = props;
|
|
71
|
+
return (
|
|
72
|
+
<div className="flex items-center justify-center rounded-lg bg-neutral-500 px-1 py-0.5 font-mono text-xs font-bold text-white">
|
|
73
|
+
{`${getDataForAllFilteredNodes(gridApi).length}/${panel.availableColumnsForDisplay.length}`}
|
|
74
|
+
<span className="hidden">
|
|
75
|
+
{/* subscribing to the search text to trigger re-render as it changes */}
|
|
76
|
+
{panel.availableColumnsSearchText}
|
|
77
|
+
</span>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
},
|
|
81
|
+
);
|
|
128
82
|
|
|
129
83
|
/**
|
|
130
84
|
* Move this display to a separate component to avoid re-rendering the header too frequently
|
|
131
85
|
*/
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
>(props: {
|
|
136
|
-
selector: DataCubeEditorColumnSelectorState<T>;
|
|
137
|
-
gridApi: GridApi<T>;
|
|
138
|
-
scope: 'available' | 'selected';
|
|
139
|
-
}) {
|
|
140
|
-
const { selector, gridApi, scope } = props;
|
|
86
|
+
const DimensionsTreeSearchResultCountBadge = observer(
|
|
87
|
+
(props: { panel: DataCubeEditorDimensionsPanelState; gridApi: GridApi }) => {
|
|
88
|
+
const { panel, gridApi } = props;
|
|
141
89
|
return (
|
|
142
90
|
<div className="flex items-center justify-center rounded-lg bg-neutral-500 px-1 py-0.5 font-mono text-xs font-bold text-white">
|
|
143
|
-
{`${
|
|
91
|
+
{`${
|
|
92
|
+
getDataForAllFilteredNodes(gridApi)
|
|
93
|
+
.map((node) => node.data)
|
|
94
|
+
.filter((node) => node instanceof DataCubeEditorDimensionState)
|
|
95
|
+
.length
|
|
96
|
+
}/${panel.dimensions.length}`}
|
|
144
97
|
<span className="hidden">
|
|
145
|
-
{
|
|
146
|
-
|
|
147
|
-
selector.availableColumnsSearchText
|
|
148
|
-
: selector.selectedColumnsSearchText}
|
|
98
|
+
{/* subscribing to the search text to trigger re-render as it changes */}
|
|
99
|
+
{panel.dimensionsTreeSearchText}
|
|
149
100
|
</span>
|
|
150
101
|
</div>
|
|
151
102
|
);
|
|
152
103
|
},
|
|
153
104
|
);
|
|
154
105
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Move this display to a separate component to avoid re-rendering too frequently
|
|
108
|
+
*/
|
|
109
|
+
const DimensionLabel = observer(
|
|
110
|
+
(props: {
|
|
111
|
+
panel: DataCubeEditorDimensionsPanelState;
|
|
112
|
+
dimension: DataCubeEditorDimensionState;
|
|
113
|
+
gridApi: GridApi;
|
|
114
|
+
}) => {
|
|
115
|
+
const { panel, dimension, gridApi } = props;
|
|
116
|
+
const nameInputRef = useRef<HTMLInputElement>(null);
|
|
117
|
+
const [name, setName] = useState(dimension.name);
|
|
118
|
+
const validationError =
|
|
119
|
+
name.trim() === ''
|
|
120
|
+
? `Name is required`
|
|
121
|
+
: Boolean(
|
|
122
|
+
panel.dimensions
|
|
123
|
+
.filter((dim) => dim.name !== dimension.name)
|
|
124
|
+
.find((dim) => dim.name === name),
|
|
125
|
+
)
|
|
126
|
+
? `Name must be unique`
|
|
127
|
+
: undefined;
|
|
128
|
+
const isNameValid = !validationError;
|
|
129
|
+
|
|
130
|
+
const updateName = () => {
|
|
131
|
+
if (!isNameValid) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
dimension.setName(name);
|
|
135
|
+
dimension.setIsRenaming(false);
|
|
136
|
+
gridApi.clearFocusedCell();
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
setName(dimension.name);
|
|
141
|
+
}, [dimension.name]);
|
|
142
|
+
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
if (dimension.isRenaming) {
|
|
145
|
+
nameInputRef.current?.focus();
|
|
146
|
+
}
|
|
147
|
+
}, [dimension.isRenaming]);
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<div
|
|
151
|
+
className="flex h-full w-full items-center pl-1"
|
|
152
|
+
title={`[Dimension: ${dimension.name}]\nDouble-click to remove all columns`}
|
|
153
|
+
>
|
|
154
|
+
<div className="text-2xs flex h-3 flex-shrink-0 items-center justify-center rounded-sm bg-neutral-500 px-1 font-bold text-white">
|
|
155
|
+
DIM
|
|
156
|
+
</div>
|
|
157
|
+
{!dimension.isRenaming && (
|
|
158
|
+
<div
|
|
159
|
+
className="h-full flex-1 cursor-pointer items-center overflow-hidden overflow-ellipsis whitespace-nowrap pl-1"
|
|
160
|
+
title="Double-click to remove dimension"
|
|
161
|
+
/**
|
|
162
|
+
* ag-grid row select event listener is at a deeper layer so we need to stop
|
|
163
|
+
* the propagation as event capturing is happening, not when it's bubbling.
|
|
164
|
+
*/
|
|
165
|
+
onDoubleClickCapture={(event) => {
|
|
166
|
+
event.stopPropagation();
|
|
167
|
+
dimension.setIsRenaming(true);
|
|
168
|
+
gridApi.clearFocusedCell();
|
|
169
|
+
}}
|
|
170
|
+
>
|
|
171
|
+
{dimension.name}
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
{dimension.isRenaming && (
|
|
175
|
+
<div
|
|
176
|
+
className="relative flex items-center"
|
|
177
|
+
/**
|
|
178
|
+
* ag-grid row select event listener is at a deeper layer so we need to stop
|
|
179
|
+
* the propagation as event capturing is happening, not when it's bubbling.
|
|
180
|
+
*/
|
|
181
|
+
onDoubleClickCapture={(event) => {
|
|
182
|
+
event.stopPropagation();
|
|
183
|
+
gridApi.clearFocusedCell();
|
|
184
|
+
}}
|
|
185
|
+
>
|
|
186
|
+
<input
|
|
187
|
+
ref={nameInputRef}
|
|
188
|
+
className={cn(
|
|
189
|
+
'ml-0.5 h-4 border border-neutral-300 pl-[1px] pr-6 focus:border-sky-600 focus:!outline-none',
|
|
190
|
+
{
|
|
191
|
+
'border-red-600 focus:border-red-600': !isNameValid,
|
|
192
|
+
},
|
|
193
|
+
)}
|
|
194
|
+
value={name}
|
|
195
|
+
onChange={(event) => {
|
|
196
|
+
setName(event.target.value);
|
|
197
|
+
}}
|
|
198
|
+
onKeyDown={(event) => {
|
|
199
|
+
if (event.code === 'Escape') {
|
|
200
|
+
nameInputRef.current?.focus();
|
|
201
|
+
nameInputRef.current?.select();
|
|
202
|
+
}
|
|
203
|
+
}}
|
|
204
|
+
onKeyDownCapture={(event) => {
|
|
205
|
+
if (event.code === 'Enter') {
|
|
206
|
+
event.stopPropagation();
|
|
207
|
+
updateName();
|
|
208
|
+
}
|
|
209
|
+
}}
|
|
210
|
+
onBlur={() => {
|
|
211
|
+
dimension.setIsRenaming(false);
|
|
212
|
+
setName(dimension.name);
|
|
213
|
+
}}
|
|
214
|
+
/>
|
|
215
|
+
{isNameValid && (
|
|
216
|
+
<button
|
|
217
|
+
className="text-2xs absolute right-0.5 flex h-3 w-5 items-center justify-center rounded-sm bg-sky-600 text-white"
|
|
218
|
+
onClick={(event) => {
|
|
219
|
+
event.stopPropagation();
|
|
220
|
+
updateName();
|
|
221
|
+
}}
|
|
222
|
+
>
|
|
223
|
+
Save
|
|
224
|
+
</button>
|
|
225
|
+
)}
|
|
226
|
+
</div>
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
},
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
function confirmPopulateStubDimensionWhenAddingColumns(
|
|
234
|
+
view: DataCubeViewState,
|
|
235
|
+
handler: () => void,
|
|
236
|
+
) {
|
|
237
|
+
view.alertService.alert({
|
|
238
|
+
message: `No target dimensions to add columns to`,
|
|
239
|
+
text: `Columns need to be added to a dimension, but no dimension has been specified.\nDo you want to proceed with creating a new dimension and add the columns to it?`,
|
|
240
|
+
type: AlertType.WARNING,
|
|
241
|
+
actions: [
|
|
242
|
+
{
|
|
243
|
+
label: 'Proceed',
|
|
244
|
+
handler,
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
label: 'Cancel',
|
|
248
|
+
handler: () => {},
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
windowConfig: {
|
|
252
|
+
...DEFAULT_ALERT_WINDOW_CONFIG,
|
|
253
|
+
width: 550,
|
|
254
|
+
height: 180,
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function computeDragOverNode<T = DataCubeEditorDimensionsTreeNode>(
|
|
260
|
+
event: RowDragEvent,
|
|
261
|
+
) {
|
|
262
|
+
let overIndex = event.overIndex === -1 ? undefined : event.overIndex;
|
|
263
|
+
|
|
264
|
+
// if the drag point passes the middle of the row, we consider
|
|
265
|
+
// the spot to be the one before the row
|
|
266
|
+
if (
|
|
267
|
+
overIndex !== undefined &&
|
|
268
|
+
event.y % INTERNAL__EDITOR_COLUMNS_SELECTOR_ROW_HEIGHT <
|
|
269
|
+
INTERNAL__EDITOR_COLUMNS_SELECTOR_ROW_HEIGHT / 2
|
|
270
|
+
) {
|
|
271
|
+
overIndex -= 1;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
overIndex,
|
|
276
|
+
overNode: (overIndex !== undefined
|
|
277
|
+
? getAllNodes(event.api).at(overIndex === -1 ? 0 : overIndex)
|
|
278
|
+
: undefined) as IRowNode<T> | undefined,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export const DataCubeEditorDimensionsPanel = observer(
|
|
283
|
+
(props: { view: DataCubeViewState }) => {
|
|
284
|
+
const { view } = props;
|
|
285
|
+
const editor = view.editor;
|
|
286
|
+
const panel = editor.dimensions;
|
|
287
|
+
|
|
288
|
+
const searchAvailableColumnsInputRef = useRef<HTMLInputElement | null>(
|
|
289
|
+
null,
|
|
290
|
+
);
|
|
291
|
+
const searchDimensionsTreeInputRef = useRef<HTMLInputElement | null>(null);
|
|
180
292
|
const [selectedAvailableColumns, setSelectedAvailableColumns] = useState<
|
|
181
|
-
|
|
293
|
+
DataCubeEditorColumnsSelectorColumnState[]
|
|
294
|
+
>([]);
|
|
295
|
+
const [selectedSelectedColumns, setSelectedSelectedColumns] = useState<
|
|
296
|
+
DataCubeEditorColumnsSelectorColumnState[]
|
|
297
|
+
>([]);
|
|
298
|
+
const [selectedDimensions, setSelectedDimensions] = useState<
|
|
299
|
+
DataCubeEditorDimensionState[]
|
|
182
300
|
>([]);
|
|
183
|
-
const [selectedSelectedColumns, setSelectedSelectedColumns] = useState<T[]>(
|
|
184
|
-
[],
|
|
185
|
-
);
|
|
186
301
|
const [availableColumnsGridApi, setAvailableColumnsGridApi] =
|
|
187
302
|
useState<GridApi | null>(null);
|
|
188
|
-
|
|
303
|
+
|
|
304
|
+
const [dimensionsTreeGridApi, setDimensionsTreeGridApi] =
|
|
189
305
|
useState<GridApi | null>(null);
|
|
190
|
-
const searchAvailableColumnsInputRef = useRef<HTMLInputElement | null>(
|
|
191
|
-
null,
|
|
192
|
-
);
|
|
193
|
-
const searchSelectedColumnsInputRef = useRef<HTMLInputElement | null>(null);
|
|
194
306
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
307
|
+
const onAvailableColumnsExternalDragStop = useCallback(
|
|
308
|
+
(params: RowDragEvent<DataCubeEditorDimensionsTreeNode>) => {
|
|
309
|
+
// NOTE: here, we do not scope the columns being moved by the search
|
|
310
|
+
// this is a complicated behavior to implement and it's not clear if
|
|
311
|
+
// it's necessary, the current behavior is sensible in its own way.
|
|
312
|
+
const columnsToMove = params.nodes
|
|
313
|
+
.map((node) => node.data?.data)
|
|
314
|
+
.filter(filterByType(DataCubeEditorColumnsSelectorColumnState));
|
|
315
|
+
panel.deselectColumns(columnsToMove);
|
|
316
|
+
panel.refreshDimensionsTreeData();
|
|
204
317
|
},
|
|
205
|
-
[
|
|
318
|
+
[panel],
|
|
206
319
|
);
|
|
207
320
|
|
|
208
|
-
const
|
|
209
|
-
(
|
|
210
|
-
const
|
|
211
|
-
|
|
321
|
+
const onDimensionsTreeExternalDragStop = useCallback(
|
|
322
|
+
(event: RowDragEvent<DataCubeEditorColumnsSelectorColumnState>) => {
|
|
323
|
+
const { overNode } = computeDragOverNode(event);
|
|
324
|
+
|
|
325
|
+
// NOTE: here, we do not scope the columns being moved by the search
|
|
326
|
+
// this is a complicated behavior to implement and it's not clear if
|
|
327
|
+
// it's necessary, the current behavior is sensible in its own way.
|
|
328
|
+
const columnsToMove = event.nodes
|
|
212
329
|
.map((node) => node.data)
|
|
213
330
|
.filter(isNonNullable);
|
|
214
|
-
selector.setSelectedColumns(
|
|
215
|
-
selector.selectedColumns.filter(
|
|
216
|
-
(column) => !columnsToMove.includes(column),
|
|
217
|
-
),
|
|
218
|
-
);
|
|
219
|
-
},
|
|
220
|
-
[selector],
|
|
221
|
-
);
|
|
222
331
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
332
|
+
const dimension =
|
|
333
|
+
(!overNode?.data
|
|
334
|
+
? undefined
|
|
335
|
+
: overNode.data.data instanceof DataCubeEditorDimensionState
|
|
336
|
+
? overNode.data.data
|
|
337
|
+
: overNode.parent?.data?.data instanceof
|
|
338
|
+
DataCubeEditorDimensionState
|
|
339
|
+
? overNode.parent.data.data
|
|
340
|
+
: undefined) ?? panel.dimensions.at(-1);
|
|
341
|
+
if (dimension) {
|
|
342
|
+
// dropping position will be honored and affect columns
|
|
343
|
+
// ordering within the dimension accordingly
|
|
344
|
+
const colIdx =
|
|
345
|
+
overNode?.data?.data instanceof
|
|
346
|
+
DataCubeEditorColumnsSelectorColumnState
|
|
347
|
+
? dimension.columns.indexOf(overNode.data.data)
|
|
348
|
+
: overNode?.data?.data instanceof DataCubeEditorDimensionState
|
|
349
|
+
? -1
|
|
350
|
+
: undefined;
|
|
351
|
+
dimension.setColumns(
|
|
352
|
+
colIdx !== undefined
|
|
353
|
+
? [
|
|
354
|
+
...dimension.columns.slice(0, colIdx + 1),
|
|
355
|
+
...columnsToMove,
|
|
356
|
+
...dimension.columns.slice(colIdx + 1),
|
|
357
|
+
]
|
|
358
|
+
: [...dimension.columns, ...columnsToMove],
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
panel.refreshDimensionsTreeData();
|
|
362
|
+
} else {
|
|
363
|
+
confirmPopulateStubDimensionWhenAddingColumns(view, () => {
|
|
364
|
+
const newDimension = panel.newDimension();
|
|
365
|
+
newDimension.setColumns([...columnsToMove]);
|
|
366
|
+
panel.refreshDimensionsTreeData();
|
|
367
|
+
});
|
|
233
368
|
}
|
|
234
|
-
selector.setSelectedColumns(getDataForAllNodes(event.api));
|
|
235
369
|
},
|
|
236
|
-
[
|
|
370
|
+
[panel, view],
|
|
237
371
|
);
|
|
238
372
|
|
|
239
373
|
/**
|
|
240
|
-
* Setup
|
|
374
|
+
* Setup drop zones for each grid to allow moving columns between them
|
|
241
375
|
* See https://www.ag-grid.com/react-data-grid/row-dragging-to-grid/
|
|
242
376
|
*/
|
|
243
377
|
useEffect(() => {
|
|
244
|
-
if (!availableColumnsGridApi || !
|
|
378
|
+
if (!availableColumnsGridApi || !dimensionsTreeGridApi) {
|
|
245
379
|
return;
|
|
246
380
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
availableColumnsGridApi.
|
|
381
|
+
|
|
382
|
+
const dimensionstreeDropZoneParams = !dimensionsTreeGridApi.isDestroyed()
|
|
383
|
+
? dimensionsTreeGridApi.getRowDropZoneParams({
|
|
384
|
+
onDragStop: (event) => {
|
|
385
|
+
onDimensionsTreeExternalDragStop(event);
|
|
386
|
+
availableColumnsGridApi.clearFocusedCell();
|
|
387
|
+
},
|
|
388
|
+
})
|
|
389
|
+
: undefined;
|
|
390
|
+
if (
|
|
391
|
+
dimensionstreeDropZoneParams &&
|
|
392
|
+
!availableColumnsGridApi.isDestroyed()
|
|
393
|
+
) {
|
|
394
|
+
availableColumnsGridApi.addRowDropZone(dimensionstreeDropZoneParams);
|
|
259
395
|
}
|
|
260
396
|
|
|
261
397
|
const availableColumnsDropZoneParams =
|
|
262
|
-
availableColumnsGridApi.
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
398
|
+
!availableColumnsGridApi.isDestroyed()
|
|
399
|
+
? availableColumnsGridApi.getRowDropZoneParams({
|
|
400
|
+
onDragStop: (event) => {
|
|
401
|
+
onAvailableColumnsExternalDragStop(event);
|
|
402
|
+
dimensionsTreeGridApi.clearFocusedCell();
|
|
403
|
+
},
|
|
404
|
+
})
|
|
405
|
+
: undefined;
|
|
406
|
+
if (
|
|
407
|
+
availableColumnsDropZoneParams &&
|
|
408
|
+
!dimensionsTreeGridApi.isDestroyed()
|
|
409
|
+
) {
|
|
410
|
+
dimensionsTreeGridApi.addRowDropZone(availableColumnsDropZoneParams);
|
|
273
411
|
}
|
|
274
412
|
}, [
|
|
275
413
|
availableColumnsGridApi,
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
414
|
+
dimensionsTreeGridApi,
|
|
415
|
+
onDimensionsTreeExternalDragStop,
|
|
416
|
+
onAvailableColumnsExternalDragStop,
|
|
279
417
|
]);
|
|
280
418
|
|
|
419
|
+
const [dimensionsTreeOnHoverIndex, setDimensionsTreeOnHoverIndex] =
|
|
420
|
+
useState<number | undefined>(undefined);
|
|
421
|
+
|
|
422
|
+
// This event will be triggered when user drops any node on the dimensions tree,
|
|
423
|
+
// regardless of the source, i.e. this includes columns from available columns grid
|
|
424
|
+
// so we have to guard against that case because the logic should already been handled
|
|
425
|
+
// by the drag stop hook in the external drop zone config for dimensions tree.
|
|
426
|
+
const isDimensionsTreeRowDragEnabled =
|
|
427
|
+
selectedDimensions.length === 0 || selectedSelectedColumns.length === 0;
|
|
428
|
+
const onDimensionsTreeRowDragEnd = useCallback(
|
|
429
|
+
(event: RowDragEvent<DataCubeEditorDimensionsTreeNode>) => {
|
|
430
|
+
const { overIndex, overNode } = computeDragOverNode(event);
|
|
431
|
+
|
|
432
|
+
setDimensionsTreeOnHoverIndex(undefined);
|
|
433
|
+
|
|
434
|
+
// NOTE: here, we do not scope the columns being moved by the search
|
|
435
|
+
// this is a complicated behavior to implement and it's not clear if
|
|
436
|
+
// it's necessary, the current behavior is sensible in its own way.
|
|
437
|
+
const dragEntities = event.nodes
|
|
438
|
+
.map(
|
|
439
|
+
(node) =>
|
|
440
|
+
// this assertion is valid because we also need to handle columns being dragged from external sources
|
|
441
|
+
node.data as
|
|
442
|
+
| DataCubeEditorDimensionsTreeNode
|
|
443
|
+
| DataCubeEditorColumnsSelectorColumnState,
|
|
444
|
+
)
|
|
445
|
+
.filter(
|
|
446
|
+
(data): data is DataCubeEditorDimensionsTreeNode =>
|
|
447
|
+
!(data instanceof DataCubeEditorDimensionState),
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
// we guard against DnD from the available columns grid to the dimensions tree
|
|
451
|
+
if (dragEntities.length === 0) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const dragColumns: DataCubeEditorColumnsSelectorColumnState[] = [];
|
|
456
|
+
const dragDimensions: DataCubeEditorDimensionState[] = [];
|
|
457
|
+
dragEntities.forEach((node) => {
|
|
458
|
+
if (node.data instanceof DataCubeEditorDimensionState) {
|
|
459
|
+
dragDimensions.push(node.data);
|
|
460
|
+
} else if (
|
|
461
|
+
node.data instanceof DataCubeEditorColumnsSelectorColumnState
|
|
462
|
+
) {
|
|
463
|
+
dragColumns.push(node.data);
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
if (dragColumns.length > 0 && dragDimensions.length > 0) {
|
|
468
|
+
// we don't support DnD for a mixture of columns and dimensions
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// moving dimensions
|
|
473
|
+
if (dragDimensions.length > 0) {
|
|
474
|
+
// dropping position will be honored and affect dimensions ordering
|
|
475
|
+
let dimensionIdx: number | undefined = undefined;
|
|
476
|
+
if (overIndex === -1) {
|
|
477
|
+
dimensionIdx = -1;
|
|
478
|
+
} else if (overNode !== undefined) {
|
|
479
|
+
const dimension = !overNode.data
|
|
480
|
+
? undefined
|
|
481
|
+
: overNode.data.data instanceof DataCubeEditorDimensionState
|
|
482
|
+
? overNode.data.data
|
|
483
|
+
: overNode.parent?.data?.data instanceof
|
|
484
|
+
DataCubeEditorDimensionState
|
|
485
|
+
? overNode.parent.data.data
|
|
486
|
+
: undefined;
|
|
487
|
+
if (dimension) {
|
|
488
|
+
const _idx = panel.dimensions.indexOf(dimension);
|
|
489
|
+
// when compute the move position, account for dimensions which will be moved out
|
|
490
|
+
// these would shift the move position up.
|
|
491
|
+
dimensionIdx =
|
|
492
|
+
_idx === -1
|
|
493
|
+
? -1
|
|
494
|
+
: _idx -
|
|
495
|
+
panel.dimensions
|
|
496
|
+
.slice(0, _idx + 1)
|
|
497
|
+
.filter((dim) =>
|
|
498
|
+
dragDimensions.find((_dim) => dim.name === _dim.name),
|
|
499
|
+
).length;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const dimensions = panel.dimensions.filter(
|
|
504
|
+
(dim) => !dragDimensions.find((_dim) => dim.name === _dim.name),
|
|
505
|
+
);
|
|
506
|
+
panel.setDimensions(
|
|
507
|
+
dimensionIdx !== undefined
|
|
508
|
+
? [
|
|
509
|
+
...dimensions.slice(0, dimensionIdx + 1),
|
|
510
|
+
...dragDimensions,
|
|
511
|
+
...dimensions.slice(dimensionIdx + 1),
|
|
512
|
+
]
|
|
513
|
+
: [...dimensions, ...dragDimensions],
|
|
514
|
+
);
|
|
515
|
+
panel.refreshDimensionsTreeData();
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// moving columns
|
|
520
|
+
if (dragColumns.length > 0) {
|
|
521
|
+
const dimension =
|
|
522
|
+
(!overNode?.data
|
|
523
|
+
? undefined
|
|
524
|
+
: overNode.data.data instanceof DataCubeEditorDimensionState
|
|
525
|
+
? overNode.data.data
|
|
526
|
+
: overNode.parent?.data?.data instanceof
|
|
527
|
+
DataCubeEditorDimensionState
|
|
528
|
+
? overNode.parent.data.data
|
|
529
|
+
: undefined) ?? panel.dimensions.at(-1);
|
|
530
|
+
if (dimension) {
|
|
531
|
+
// dropping position will be honored and affect columns
|
|
532
|
+
// ordering within the dimension accordingly
|
|
533
|
+
let colIdx: number | undefined = undefined;
|
|
534
|
+
if (overNode?.data?.data instanceof DataCubeEditorDimensionState) {
|
|
535
|
+
colIdx = -1;
|
|
536
|
+
} else if (
|
|
537
|
+
overNode?.data?.data instanceof
|
|
538
|
+
DataCubeEditorColumnsSelectorColumnState
|
|
539
|
+
) {
|
|
540
|
+
const _idx = dimension.columns.indexOf(overNode.data.data);
|
|
541
|
+
// when compute the move position, account for columns which will be moved out
|
|
542
|
+
// these would shift the move position up.
|
|
543
|
+
colIdx =
|
|
544
|
+
_idx === -1
|
|
545
|
+
? -1
|
|
546
|
+
: _idx -
|
|
547
|
+
dimension.columns
|
|
548
|
+
.slice(0, _idx + 1)
|
|
549
|
+
.filter((col) => _findCol(dragColumns, col.name)).length;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
panel.deselectColumns(dragColumns);
|
|
553
|
+
dimension.setColumns(
|
|
554
|
+
colIdx !== undefined
|
|
555
|
+
? [
|
|
556
|
+
...dimension.columns.slice(0, colIdx + 1),
|
|
557
|
+
...dragColumns,
|
|
558
|
+
...dimension.columns.slice(colIdx + 1),
|
|
559
|
+
]
|
|
560
|
+
: [...dimension.columns, ...dragColumns],
|
|
561
|
+
);
|
|
562
|
+
panel.refreshDimensionsTreeData();
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
[panel],
|
|
569
|
+
);
|
|
570
|
+
const onDimensionsTreeRowDragMove = useCallback(
|
|
571
|
+
(event: RowDragEvent<DataCubeEditorDimensionsTreeNode>) => {
|
|
572
|
+
const { overIndex, overNode } = computeDragOverNode(event);
|
|
573
|
+
|
|
574
|
+
setDimensionsTreeOnHoverIndex(overIndex);
|
|
575
|
+
event.api.refreshCells({
|
|
576
|
+
rowNodes: overNode ? [overNode] : [],
|
|
577
|
+
force: true, // since no data change is happening, we need to force refresh
|
|
578
|
+
});
|
|
579
|
+
},
|
|
580
|
+
[],
|
|
581
|
+
);
|
|
582
|
+
const onDimensionsTreeRowDragCancel = useCallback(
|
|
583
|
+
(event: RowDragEvent<DataCubeEditorDimensionsTreeNode>) => {
|
|
584
|
+
setDimensionsTreeOnHoverIndex(undefined);
|
|
585
|
+
},
|
|
586
|
+
[],
|
|
587
|
+
);
|
|
588
|
+
const onDimensionsTreeRowDragLeave = useCallback(
|
|
589
|
+
(event: RowDragEvent<DataCubeEditorDimensionsTreeNode>) => {
|
|
590
|
+
setDimensionsTreeOnHoverIndex(undefined);
|
|
591
|
+
},
|
|
592
|
+
[],
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
useEffect(() => {
|
|
596
|
+
// on load, reset all renaming state for all dimensions
|
|
597
|
+
panel.dimensions.forEach((dimension) => dimension.setIsRenaming(false));
|
|
598
|
+
|
|
599
|
+
// reset transient grid states
|
|
600
|
+
setDimensionsTreeOnHoverIndex(undefined);
|
|
601
|
+
}, [panel]);
|
|
602
|
+
|
|
603
|
+
// eslint-disable-next-line no-process-env
|
|
604
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
605
|
+
// eslint-disable-next-line no-console
|
|
606
|
+
console.error = (message?: unknown, ...agrs: unknown[]) => {
|
|
607
|
+
console.debug(`%c ${message}`, 'color: silver'); // eslint-disable-line no-console
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
|
|
281
611
|
return (
|
|
282
|
-
<div className="
|
|
283
|
-
<div className="h-
|
|
284
|
-
<div className="flex h-
|
|
285
|
-
|
|
612
|
+
<div className="h-full w-full select-none p-2">
|
|
613
|
+
<div className="flex h-6">
|
|
614
|
+
<div className="flex h-6 items-center text-xl font-medium">
|
|
615
|
+
<DataCubeIcon.TableDimension />
|
|
616
|
+
</div>
|
|
617
|
+
<div className="ml-1 flex h-6 items-center text-xl font-medium">
|
|
618
|
+
Dimensions
|
|
619
|
+
<FormBadge_WIP />
|
|
286
620
|
</div>
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
621
|
+
</div>
|
|
622
|
+
<div className="h-[calc(100%_-_24px)] w-full">
|
|
623
|
+
<div className="h-10 py-2">
|
|
624
|
+
<div className="flex h-6 w-full items-center">
|
|
625
|
+
<FormCheckbox
|
|
626
|
+
label="Enable multidimensional grid mode"
|
|
627
|
+
checked={
|
|
628
|
+
editor.generalProperties.configuration.gridMode ===
|
|
629
|
+
DataCubeGridMode.MULTIDIMENSIONAL
|
|
296
630
|
}
|
|
297
|
-
|
|
298
|
-
if (
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
631
|
+
onChange={() => {
|
|
632
|
+
if (
|
|
633
|
+
editor.generalProperties.configuration.gridMode ===
|
|
634
|
+
DataCubeGridMode.MULTIDIMENSIONAL
|
|
635
|
+
) {
|
|
636
|
+
editor.generalProperties.configuration.setGridMode(
|
|
637
|
+
DataCubeGridMode.STANDARD,
|
|
638
|
+
);
|
|
639
|
+
} else {
|
|
640
|
+
if (
|
|
641
|
+
// only suggest converting vertical pivots if no dimensions have been specified
|
|
642
|
+
editor.verticalPivots.selector.selectedColumns.length !==
|
|
643
|
+
0 &&
|
|
644
|
+
panel.dimensions.length === 0
|
|
645
|
+
) {
|
|
646
|
+
view.alertService.alert({
|
|
647
|
+
message: `Specified vertical pivots will be ignored in multidimensional grid mode.`,
|
|
648
|
+
text: `Multidimensional grid mode will ignore any specified vertical pivots, essentially, vertical pivot columns can be considered as one dimension.\nFor convenience, these vertical pivots can be converted into a dimension, do you want this conversion to happen?`,
|
|
649
|
+
type: AlertType.WARNING,
|
|
650
|
+
actions: [
|
|
651
|
+
{
|
|
652
|
+
label: 'Yes',
|
|
653
|
+
handler: () => {
|
|
654
|
+
const groupByColumns =
|
|
655
|
+
editor.verticalPivots.selector.selectedColumns;
|
|
656
|
+
const newDimension = panel.newDimension();
|
|
657
|
+
newDimension.setColumns(
|
|
658
|
+
panel.availableColumns.filter((column) =>
|
|
659
|
+
_findCol(groupByColumns, column.name),
|
|
660
|
+
),
|
|
661
|
+
);
|
|
662
|
+
editor.verticalPivots.selector.setSelectedColumns(
|
|
663
|
+
[],
|
|
664
|
+
);
|
|
665
|
+
panel.refreshDimensionsTreeData();
|
|
666
|
+
|
|
667
|
+
editor.generalProperties.configuration.setGridMode(
|
|
668
|
+
DataCubeGridMode.MULTIDIMENSIONAL,
|
|
669
|
+
);
|
|
670
|
+
},
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
label: 'No',
|
|
674
|
+
handler: () => {
|
|
675
|
+
editor.generalProperties.configuration.setGridMode(
|
|
676
|
+
DataCubeGridMode.MULTIDIMENSIONAL,
|
|
677
|
+
);
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
],
|
|
681
|
+
windowConfig: {
|
|
682
|
+
...DEFAULT_ALERT_WINDOW_CONFIG,
|
|
683
|
+
width: 550,
|
|
684
|
+
height: 220,
|
|
685
|
+
},
|
|
686
|
+
});
|
|
687
|
+
} else {
|
|
688
|
+
editor.generalProperties.configuration.setGridMode(
|
|
689
|
+
DataCubeGridMode.MULTIDIMENSIONAL,
|
|
690
|
+
);
|
|
691
|
+
}
|
|
302
692
|
}
|
|
303
693
|
}}
|
|
304
694
|
/>
|
|
305
|
-
<div className="absolute left-0 top-0 flex h-6 w-10 items-center justify-center">
|
|
306
|
-
<DataCubeIcon.Search className="stroke-[3px] text-lg text-neutral-500" />
|
|
307
|
-
</div>
|
|
308
|
-
<button
|
|
309
|
-
className="absolute right-0 top-0 flex h-6 w-6 items-center justify-center text-neutral-500 disabled:text-neutral-300"
|
|
310
|
-
disabled={!selector.availableColumnsSearchText}
|
|
311
|
-
title="Clear search [Esc]"
|
|
312
|
-
onClick={() => {
|
|
313
|
-
selector.setAvailableColumnsSearchText('');
|
|
314
|
-
searchAvailableColumnsInputRef.current?.focus();
|
|
315
|
-
}}
|
|
316
|
-
>
|
|
317
|
-
<DataCubeIcon.X className="text-lg" />
|
|
318
|
-
</button>
|
|
319
695
|
</div>
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
696
|
+
</div>
|
|
697
|
+
|
|
698
|
+
{editor.generalProperties.configuration.gridMode !==
|
|
699
|
+
DataCubeGridMode.MULTIDIMENSIONAL && (
|
|
700
|
+
<FormAlert
|
|
701
|
+
message="Multidimensional grid mode is experimental"
|
|
702
|
+
type={AlertType.WARNING}
|
|
703
|
+
text={`This is an alternative grid view for DataCube with a different mode of interaction: dimensions (ordered list of columns to group by) are defined and will be used to explore data "top-down". Initially, the grid will show the root aggregation, then user can drill down into each dimension. This grid mode comes with the following limitations:
|
|
704
|
+
- Specified vertical pivots will be ignored: essentially, vertical pivot can be considered as one dimension in this mode
|
|
705
|
+
- No support for pagination
|
|
706
|
+
- Initial view can take longer to materialize if drilldown is deep`}
|
|
707
|
+
/>
|
|
708
|
+
)}
|
|
709
|
+
{editor.generalProperties.configuration.gridMode ===
|
|
710
|
+
DataCubeGridMode.MULTIDIMENSIONAL && (
|
|
711
|
+
<div className="flex h-[calc(100%_-_40px)] w-full">
|
|
712
|
+
<div className="data-cube-column-selector flex h-full w-full">
|
|
713
|
+
<div className="h-full w-[calc(50%_-_20px)]">
|
|
714
|
+
<div className="flex h-5 items-center text-sm">
|
|
715
|
+
Available columns:
|
|
716
|
+
</div>
|
|
717
|
+
<div className="h-[calc(100%_-_20px)] rounded-sm border border-neutral-200">
|
|
718
|
+
<div className="relative h-6 border-b border-neutral-200">
|
|
719
|
+
<input
|
|
720
|
+
className="h-full w-full pl-10 pr-6 placeholder-neutral-400"
|
|
721
|
+
ref={searchAvailableColumnsInputRef}
|
|
722
|
+
placeholder="Search columns..."
|
|
723
|
+
value={panel.availableColumnsSearchText}
|
|
724
|
+
onChange={(event) =>
|
|
725
|
+
panel.setAvailableColumnsSearchText(
|
|
726
|
+
event.target.value,
|
|
727
|
+
)
|
|
728
|
+
}
|
|
729
|
+
onKeyDown={(event) => {
|
|
730
|
+
if (event.code === 'Escape') {
|
|
731
|
+
event.stopPropagation();
|
|
732
|
+
searchAvailableColumnsInputRef.current?.select();
|
|
733
|
+
panel.setAvailableColumnsSearchText('');
|
|
734
|
+
}
|
|
735
|
+
}}
|
|
736
|
+
/>
|
|
737
|
+
<div className="absolute left-0 top-0 flex h-6 w-10 items-center justify-center">
|
|
738
|
+
<DataCubeIcon.Search className="stroke-[3px] text-lg text-neutral-500" />
|
|
739
|
+
</div>
|
|
346
740
|
<button
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
);
|
|
354
|
-
selector.setSelectedColumns([
|
|
355
|
-
...selector.selectedColumns,
|
|
356
|
-
...filteredData,
|
|
357
|
-
]);
|
|
358
|
-
params.api.clearFocusedCell();
|
|
741
|
+
className="absolute right-0 top-0 flex h-6 w-6 items-center justify-center text-neutral-500 disabled:text-neutral-300"
|
|
742
|
+
disabled={!panel.availableColumnsSearchText}
|
|
743
|
+
title="Clear search [Esc]"
|
|
744
|
+
onClick={() => {
|
|
745
|
+
panel.setAvailableColumnsSearchText('');
|
|
746
|
+
searchAvailableColumnsInputRef.current?.focus();
|
|
359
747
|
}}
|
|
360
748
|
>
|
|
361
|
-
<
|
|
362
|
-
<ColumnsSearchResultCountBadge
|
|
363
|
-
selector={selector}
|
|
364
|
-
gridApi={params.api}
|
|
365
|
-
scope="available"
|
|
366
|
-
/>
|
|
749
|
+
<DataCubeIcon.X className="text-lg" />
|
|
367
750
|
</button>
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
751
|
+
</div>
|
|
752
|
+
<div className="h-[calc(100%_-_24px)]">
|
|
753
|
+
<AgGridReact
|
|
754
|
+
{...getColumnsSelectorBaseGridProps()}
|
|
755
|
+
// Disable managed row-dragging to disallow changing the order of columns
|
|
756
|
+
// and to make sure the row data and the available columns state are in sync
|
|
757
|
+
rowDragManaged={false}
|
|
758
|
+
onGridReady={(params) =>
|
|
759
|
+
setAvailableColumnsGridApi(params.api)
|
|
760
|
+
}
|
|
761
|
+
onSelectionChanged={(event) => {
|
|
762
|
+
setSelectedAvailableColumns(
|
|
763
|
+
event.api
|
|
764
|
+
.getSelectedNodes()
|
|
765
|
+
.map((node) => node.data)
|
|
766
|
+
.filter(isNonNullable),
|
|
767
|
+
);
|
|
768
|
+
}}
|
|
769
|
+
// Using ag-grid quick filter is a cheap way to implement search
|
|
770
|
+
quickFilterText={panel.availableColumnsSearchText}
|
|
771
|
+
rowData={panel.availableColumnsForDisplay}
|
|
772
|
+
rowClass="border-t border-b border-1 border-transparent"
|
|
773
|
+
columnDefs={[
|
|
774
|
+
{
|
|
775
|
+
field: 'name',
|
|
776
|
+
colId: 'name',
|
|
777
|
+
flex: 1,
|
|
778
|
+
minWidth: 100,
|
|
779
|
+
filter: true,
|
|
780
|
+
sortable: false,
|
|
781
|
+
resizable: false,
|
|
782
|
+
suppressHeaderMenuButton: true,
|
|
783
|
+
getQuickFilterText: (params) => params.value,
|
|
784
|
+
/**
|
|
785
|
+
* Support double-click to add all (filtered by search) columns
|
|
786
|
+
*/
|
|
787
|
+
headerComponent: (
|
|
788
|
+
params: CustomCellRendererProps<DataCubeEditorColumnsSelectorColumnState>,
|
|
789
|
+
) => (
|
|
790
|
+
<button
|
|
791
|
+
title="Double-click to add all columns"
|
|
792
|
+
className="flex h-full w-full items-center justify-between pl-0.5"
|
|
793
|
+
onDoubleClick={() => {
|
|
794
|
+
// The columns being moved are scoped by the current search
|
|
795
|
+
const filteredNodes =
|
|
796
|
+
getDataForAllFilteredNodes(params.api);
|
|
797
|
+
|
|
798
|
+
const dimension = panel.dimensions.at(-1);
|
|
799
|
+
if (dimension) {
|
|
800
|
+
dimension.setColumns([
|
|
801
|
+
...dimension.columns,
|
|
802
|
+
...filteredNodes,
|
|
803
|
+
]);
|
|
804
|
+
panel.refreshDimensionsTreeData();
|
|
805
|
+
} else {
|
|
806
|
+
confirmPopulateStubDimensionWhenAddingColumns(
|
|
807
|
+
view,
|
|
808
|
+
() => {
|
|
809
|
+
const newDimension =
|
|
810
|
+
panel.newDimension();
|
|
811
|
+
newDimension.setColumns([
|
|
812
|
+
...filteredNodes,
|
|
813
|
+
]);
|
|
814
|
+
panel.refreshDimensionsTreeData();
|
|
815
|
+
},
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
params.api.clearFocusedCell();
|
|
820
|
+
}}
|
|
821
|
+
>
|
|
822
|
+
<div>{`[All Columns]`}</div>
|
|
823
|
+
<AvailableColumnsSearchResultCountBadge
|
|
824
|
+
panel={panel}
|
|
825
|
+
gridApi={params.api}
|
|
826
|
+
/>
|
|
827
|
+
</button>
|
|
828
|
+
),
|
|
829
|
+
cellRenderer: (
|
|
830
|
+
params: CustomCellRendererProps<DataCubeEditorColumnsSelectorColumnState>,
|
|
831
|
+
) => {
|
|
832
|
+
const data = params.data;
|
|
833
|
+
if (!data) {
|
|
834
|
+
return null;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
return (
|
|
838
|
+
<div
|
|
839
|
+
className="flex h-full w-full cursor-pointer items-center"
|
|
840
|
+
title={`[${data.name}]\nDouble-click to add column`}
|
|
841
|
+
onDoubleClick={() => {
|
|
842
|
+
const dimension = panel.dimensions.at(-1);
|
|
843
|
+
if (dimension) {
|
|
844
|
+
dimension.setColumns([
|
|
845
|
+
...dimension.columns,
|
|
846
|
+
data,
|
|
847
|
+
]);
|
|
848
|
+
panel.refreshDimensionsTreeData();
|
|
849
|
+
} else {
|
|
850
|
+
confirmPopulateStubDimensionWhenAddingColumns(
|
|
851
|
+
view,
|
|
852
|
+
() => {
|
|
853
|
+
const newDimension =
|
|
854
|
+
panel.newDimension();
|
|
855
|
+
newDimension.setColumns([data]);
|
|
856
|
+
panel.refreshDimensionsTreeData();
|
|
857
|
+
},
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
params.api.clearFocusedCell();
|
|
862
|
+
}}
|
|
863
|
+
>
|
|
864
|
+
<div className="h-full flex-1 items-center overflow-hidden overflow-ellipsis whitespace-nowrap pl-2">
|
|
865
|
+
{data.name}
|
|
866
|
+
</div>
|
|
867
|
+
</div>
|
|
868
|
+
);
|
|
869
|
+
},
|
|
870
|
+
},
|
|
871
|
+
]}
|
|
872
|
+
/>
|
|
873
|
+
</div>
|
|
874
|
+
</div>
|
|
875
|
+
</div>
|
|
876
|
+
<div className="flex h-full w-10 items-center justify-center">
|
|
877
|
+
<div className="flex flex-col">
|
|
878
|
+
<button
|
|
879
|
+
className="flex items-center justify-center rounded-sm border border-neutral-300 bg-neutral-100 text-neutral-500 hover:bg-neutral-200 disabled:bg-neutral-200 disabled:text-neutral-400"
|
|
880
|
+
title="Add selected column(s)"
|
|
881
|
+
/**
|
|
882
|
+
* Support add selected (filtered by search) columns
|
|
883
|
+
* We reset the selection after this operation
|
|
884
|
+
*/
|
|
885
|
+
onClick={() => {
|
|
886
|
+
if (
|
|
887
|
+
!availableColumnsGridApi ||
|
|
888
|
+
selectedAvailableColumns.length === 0
|
|
889
|
+
) {
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// The columns being moved are scoped by the current search
|
|
894
|
+
const filteredNodes = getDataForAllFilteredNodes(
|
|
895
|
+
availableColumnsGridApi,
|
|
896
|
+
);
|
|
897
|
+
const columnsToMove = selectedAvailableColumns.filter(
|
|
898
|
+
(column) => _findCol(filteredNodes, column.name),
|
|
899
|
+
);
|
|
900
|
+
|
|
901
|
+
const dimension = panel.dimensions.at(-1);
|
|
902
|
+
if (dimension) {
|
|
903
|
+
dimension.setColumns([
|
|
904
|
+
...dimension.columns,
|
|
905
|
+
...columnsToMove,
|
|
906
|
+
]);
|
|
907
|
+
panel.refreshDimensionsTreeData();
|
|
908
|
+
} else {
|
|
909
|
+
confirmPopulateStubDimensionWhenAddingColumns(
|
|
910
|
+
view,
|
|
911
|
+
() => {
|
|
912
|
+
const newDimension = panel.newDimension();
|
|
913
|
+
newDimension.setColumns([...columnsToMove]);
|
|
914
|
+
panel.refreshDimensionsTreeData();
|
|
915
|
+
},
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
availableColumnsGridApi.clearFocusedCell();
|
|
920
|
+
}}
|
|
921
|
+
disabled={
|
|
922
|
+
!availableColumnsGridApi ||
|
|
923
|
+
selectedAvailableColumns.length === 0
|
|
373
924
|
}
|
|
925
|
+
>
|
|
926
|
+
<DataCubeIcon.ChevronRight className="text-2xl" />
|
|
927
|
+
</button>
|
|
928
|
+
<button
|
|
929
|
+
className="mt-2 flex items-center justify-center rounded-sm border border-neutral-300 bg-neutral-100 text-neutral-500 hover:bg-neutral-200 disabled:bg-neutral-200 disabled:text-neutral-400"
|
|
930
|
+
title="Remove selected column(s)"
|
|
931
|
+
/**
|
|
932
|
+
* Support remove selected (filtered by search) columns
|
|
933
|
+
* We reset the selection after this operation
|
|
934
|
+
*/
|
|
935
|
+
onClick={() => {
|
|
936
|
+
if (
|
|
937
|
+
!dimensionsTreeGridApi ||
|
|
938
|
+
selectedSelectedColumns.length === 0
|
|
939
|
+
) {
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
374
942
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
943
|
+
// The columns being moved are scoped by the current search
|
|
944
|
+
const filteredNodes = getDataForAllFilteredNodes(
|
|
945
|
+
dimensionsTreeGridApi,
|
|
946
|
+
);
|
|
947
|
+
const columnsToMove = selectedSelectedColumns.filter(
|
|
948
|
+
(column) => _findCol(filteredNodes, column.name),
|
|
949
|
+
);
|
|
950
|
+
|
|
951
|
+
panel.deselectColumns(columnsToMove);
|
|
952
|
+
panel.refreshDimensionsTreeData();
|
|
953
|
+
dimensionsTreeGridApi.clearFocusedCell();
|
|
954
|
+
}}
|
|
955
|
+
disabled={
|
|
956
|
+
!dimensionsTreeGridApi ||
|
|
957
|
+
selectedSelectedColumns.length === 0
|
|
958
|
+
}
|
|
959
|
+
>
|
|
960
|
+
<DataCubeIcon.ChevronLeft className="text-2xl" />
|
|
961
|
+
</button>
|
|
962
|
+
</div>
|
|
963
|
+
</div>
|
|
964
|
+
<div className="data-cube-tree-selector h-full w-[calc(50%_-_20px)]">
|
|
965
|
+
<div className="flex h-5 items-center text-sm">
|
|
966
|
+
Dimensions:
|
|
967
|
+
</div>
|
|
968
|
+
<div className="h-[calc(100%_-_20px)] rounded-sm border border-neutral-200">
|
|
969
|
+
<div className="flex h-6">
|
|
970
|
+
<div className="relative h-6 w-[calc(100%_-_124px)] border-b border-r border-neutral-200">
|
|
971
|
+
<input
|
|
972
|
+
className="h-full w-full pl-10 pr-6 placeholder-neutral-400"
|
|
973
|
+
ref={searchDimensionsTreeInputRef}
|
|
974
|
+
placeholder="Search..."
|
|
975
|
+
value={panel.dimensionsTreeSearchText}
|
|
976
|
+
onChange={(event) =>
|
|
977
|
+
panel.setDimensionsTreeSearchText(
|
|
978
|
+
event.target.value,
|
|
979
|
+
)
|
|
980
|
+
}
|
|
981
|
+
onKeyDown={(event) => {
|
|
982
|
+
if (event.code === 'Escape') {
|
|
983
|
+
event.stopPropagation();
|
|
984
|
+
searchDimensionsTreeInputRef.current?.select();
|
|
985
|
+
panel.setDimensionsTreeSearchText('');
|
|
986
|
+
}
|
|
987
|
+
}}
|
|
988
|
+
/>
|
|
989
|
+
<div className="absolute left-0 top-0 flex h-6 w-10 items-center justify-center">
|
|
990
|
+
<DataCubeIcon.Search className="stroke-[3px] text-lg text-neutral-500" />
|
|
991
|
+
</div>
|
|
992
|
+
<button
|
|
993
|
+
className="absolute right-0 top-0 flex h-6 w-6 items-center justify-center text-neutral-500 disabled:text-neutral-300"
|
|
994
|
+
disabled={!panel.dimensionsTreeSearchText}
|
|
995
|
+
title="Clear search [Esc]"
|
|
996
|
+
onClick={() => {
|
|
997
|
+
panel.setDimensionsTreeSearchText('');
|
|
998
|
+
searchDimensionsTreeInputRef.current?.focus();
|
|
999
|
+
}}
|
|
1000
|
+
>
|
|
1001
|
+
<DataCubeIcon.X className="text-lg" />
|
|
1002
|
+
</button>
|
|
1003
|
+
</div>
|
|
1004
|
+
<div className="flex h-6 items-center border-b border-neutral-200">
|
|
1005
|
+
<button
|
|
1006
|
+
className="flex h-6 w-6 items-center justify-center text-neutral-500 disabled:text-neutral-300"
|
|
1007
|
+
title="Add new dimension"
|
|
1008
|
+
onClick={() => {
|
|
1009
|
+
panel.newDimension();
|
|
1010
|
+
panel.refreshDimensionsTreeData();
|
|
1011
|
+
}}
|
|
1012
|
+
>
|
|
1013
|
+
<DataCubeIcon.Plus className="text-lg" />
|
|
1014
|
+
</button>
|
|
1015
|
+
<div className="h-3 w-[1px] bg-neutral-200" />
|
|
1016
|
+
<button
|
|
1017
|
+
className="flex h-6 w-6 items-center justify-center text-neutral-500 disabled:text-neutral-300"
|
|
1018
|
+
title="Move dimension(s) up"
|
|
1019
|
+
disabled={selectedDimensions.length === 0}
|
|
1020
|
+
onClick={() => {
|
|
1021
|
+
if (selectedDimensions.length === 0) {
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
const minIdx = Math.min(
|
|
1026
|
+
...selectedDimensions.map((dimension) =>
|
|
1027
|
+
panel.dimensions.indexOf(dimension),
|
|
1028
|
+
),
|
|
1029
|
+
);
|
|
1030
|
+
const precedings = [
|
|
1031
|
+
...panel.dimensions.slice(
|
|
1032
|
+
0,
|
|
1033
|
+
Math.max(minIdx - 1, 0),
|
|
1034
|
+
),
|
|
1035
|
+
// ensure moving dimensions maintain their relative ordering
|
|
1036
|
+
...panel.dimensions
|
|
1037
|
+
.map((dimension) =>
|
|
1038
|
+
selectedDimensions.find(
|
|
1039
|
+
(dim) => dim.name === dimension.name,
|
|
1040
|
+
),
|
|
1041
|
+
)
|
|
1042
|
+
.filter(isNonNullable),
|
|
1043
|
+
];
|
|
1044
|
+
|
|
1045
|
+
panel.setDimensions([
|
|
1046
|
+
...precedings,
|
|
1047
|
+
...panel.dimensions.filter(
|
|
1048
|
+
(dimension) =>
|
|
1049
|
+
!precedings.find(
|
|
1050
|
+
(dim) => dim.name === dimension.name,
|
|
1051
|
+
),
|
|
1052
|
+
),
|
|
383
1053
|
]);
|
|
384
|
-
|
|
1054
|
+
|
|
1055
|
+
panel.refreshDimensionsTreeData();
|
|
385
1056
|
}}
|
|
386
1057
|
>
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
)
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
column: data,
|
|
399
|
-
}) ?? null}
|
|
400
|
-
</div>
|
|
401
|
-
</div>
|
|
402
|
-
);
|
|
403
|
-
},
|
|
404
|
-
},
|
|
405
|
-
]}
|
|
406
|
-
/>
|
|
407
|
-
</div>
|
|
408
|
-
</div>
|
|
409
|
-
</div>
|
|
410
|
-
<div className="flex h-full w-10 items-center justify-center">
|
|
411
|
-
<div className="flex flex-col">
|
|
412
|
-
<button
|
|
413
|
-
className="flex items-center justify-center rounded-sm border border-neutral-300 bg-neutral-100 text-neutral-500 hover:bg-neutral-200 disabled:bg-neutral-200 disabled:text-neutral-400"
|
|
414
|
-
title="Add selected columns"
|
|
415
|
-
/**
|
|
416
|
-
* Support add selected (filtered by search) columns
|
|
417
|
-
* We reset the selection after this operation
|
|
418
|
-
*/
|
|
419
|
-
onClick={() => {
|
|
420
|
-
if (!availableColumnsGridApi) {
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
// The columns being moved are scoped by the current search
|
|
424
|
-
const filteredData = getDataForAllFilteredNodes(
|
|
425
|
-
availableColumnsGridApi,
|
|
426
|
-
);
|
|
427
|
-
const columnsToMove = selectedAvailableColumns.filter(
|
|
428
|
-
(column) => filteredData.includes(column),
|
|
429
|
-
);
|
|
430
|
-
selector.setSelectedColumns([
|
|
431
|
-
...selector.selectedColumns,
|
|
432
|
-
...columnsToMove,
|
|
433
|
-
]);
|
|
434
|
-
availableColumnsGridApi.clearFocusedCell();
|
|
435
|
-
}}
|
|
436
|
-
disabled={selectedAvailableColumns.length === 0}
|
|
437
|
-
>
|
|
438
|
-
<DataCubeIcon.ChevronRight className="text-2xl" />
|
|
439
|
-
</button>
|
|
440
|
-
<button
|
|
441
|
-
className="mt-2 flex items-center justify-center rounded-sm border border-neutral-300 bg-neutral-100 text-neutral-500 hover:bg-neutral-200 disabled:bg-neutral-200 disabled:text-neutral-400"
|
|
442
|
-
title="Remove selected columns"
|
|
443
|
-
/**
|
|
444
|
-
* Support remove selected (filtered by search) columns
|
|
445
|
-
* We reset the selection after this operation
|
|
446
|
-
*/
|
|
447
|
-
onClick={() => {
|
|
448
|
-
if (!selectedColumnsGridApi) {
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
// The columns being moved are scoped by the current search
|
|
452
|
-
const filteredData = getDataForAllFilteredNodes(
|
|
453
|
-
selectedColumnsGridApi,
|
|
454
|
-
);
|
|
455
|
-
const columnsToMove = selectedSelectedColumns.filter((column) =>
|
|
456
|
-
filteredData.includes(column),
|
|
457
|
-
);
|
|
458
|
-
selector.setSelectedColumns(
|
|
459
|
-
selector.selectedColumns.filter(
|
|
460
|
-
(column) => !columnsToMove.includes(column),
|
|
461
|
-
),
|
|
462
|
-
);
|
|
463
|
-
selectedColumnsGridApi.clearFocusedCell();
|
|
464
|
-
}}
|
|
465
|
-
disabled={selectedSelectedColumns.length === 0}
|
|
466
|
-
>
|
|
467
|
-
<DataCubeIcon.ChevronLeft className="text-2xl" />
|
|
468
|
-
</button>
|
|
469
|
-
</div>
|
|
470
|
-
</div>
|
|
471
|
-
<div className="h-full w-[calc(50%_-_20px)]">
|
|
472
|
-
<div className="flex h-5 items-center text-sm">Selected columns:</div>
|
|
473
|
-
<div className="h-[calc(100%_-_20px)] rounded-sm border border-neutral-200">
|
|
474
|
-
<div className="relative h-6 border-b border-neutral-200">
|
|
475
|
-
<input
|
|
476
|
-
className="h-full w-full pl-10 placeholder-neutral-400"
|
|
477
|
-
ref={searchSelectedColumnsInputRef}
|
|
478
|
-
placeholder="Click here to search..."
|
|
479
|
-
value={selector.selectedColumnsSearchText}
|
|
480
|
-
onChange={(event) =>
|
|
481
|
-
selector.setSelectedColumnsSearchText(event.target.value)
|
|
482
|
-
}
|
|
483
|
-
onKeyDown={(event) => {
|
|
484
|
-
if (event.code === 'Escape') {
|
|
485
|
-
event.stopPropagation();
|
|
486
|
-
selector.setSelectedColumnsSearchText('');
|
|
487
|
-
}
|
|
488
|
-
}}
|
|
489
|
-
/>
|
|
490
|
-
<div className="absolute left-0 top-0 flex h-6 w-10 items-center justify-center">
|
|
491
|
-
<DataCubeIcon.Search className="stroke-[3px] text-lg text-neutral-500" />
|
|
492
|
-
</div>
|
|
493
|
-
<button
|
|
494
|
-
className="absolute right-0 top-0 flex h-6 w-6 items-center justify-center text-neutral-500 disabled:text-neutral-300"
|
|
495
|
-
disabled={!selector.selectedColumnsSearchText}
|
|
496
|
-
title="Clear search [Esc]"
|
|
497
|
-
onClick={() => {
|
|
498
|
-
selector.setSelectedColumnsSearchText('');
|
|
499
|
-
searchSelectedColumnsInputRef.current?.focus();
|
|
500
|
-
}}
|
|
501
|
-
>
|
|
502
|
-
<DataCubeIcon.X className="text-lg" />
|
|
503
|
-
</button>
|
|
504
|
-
</div>
|
|
505
|
-
<div className="h-[calc(100%_-_24px)]">
|
|
506
|
-
<AgGridReact
|
|
507
|
-
theme="legacy"
|
|
508
|
-
{...getBaseGridProps<T>()}
|
|
509
|
-
// NOTE: technically, we don't want to enable managed row-dragging here
|
|
510
|
-
// but enabling this gives us free row moving management and interaction
|
|
511
|
-
// comes out of the box from ag-grid, we will just sync the state with
|
|
512
|
-
// grid row data afterwards to ensure consistency
|
|
513
|
-
rowDragManaged={true}
|
|
514
|
-
onRowDragEnd={onSelectedColumnsDragEnd}
|
|
515
|
-
onGridReady={(params) => setSelectedColumnsGridApi(params.api)}
|
|
516
|
-
onSelectionChanged={(event: SelectionChangedEvent<T>) => {
|
|
517
|
-
setSelectedSelectedColumns(
|
|
518
|
-
event.api
|
|
519
|
-
.getSelectedNodes()
|
|
520
|
-
.map((node) => node.data)
|
|
521
|
-
.filter(isNonNullable),
|
|
522
|
-
);
|
|
523
|
-
}}
|
|
524
|
-
// Using ag-grid quick filter is a cheap way to implement search
|
|
525
|
-
quickFilterText={selector.selectedColumnsSearchText}
|
|
526
|
-
noRowsOverlayComponentParams={{
|
|
527
|
-
noColumnsSelectedRenderer,
|
|
528
|
-
}}
|
|
529
|
-
rowData={selector.selectedColumnsForDisplay}
|
|
530
|
-
columnDefs={[
|
|
531
|
-
{
|
|
532
|
-
...getBaseColumnDef<T>(),
|
|
533
|
-
/**
|
|
534
|
-
* Support double-click to remove all (filtered by search) columns
|
|
535
|
-
*/
|
|
536
|
-
headerComponent: (params: CustomCellRendererProps<T>) => (
|
|
537
|
-
<button
|
|
538
|
-
title="Double-click to remove all columns"
|
|
539
|
-
className="flex h-full w-full items-center justify-between pl-0.5"
|
|
540
|
-
onDoubleClick={() => {
|
|
541
|
-
// The columns being moved are scoped by the current search
|
|
542
|
-
const filteredData = getDataForAllFilteredNodes(
|
|
543
|
-
params.api,
|
|
544
|
-
);
|
|
545
|
-
selector.setSelectedColumns(
|
|
546
|
-
selector.selectedColumns.filter(
|
|
547
|
-
(column) => !filteredData.includes(column),
|
|
548
|
-
),
|
|
549
|
-
);
|
|
550
|
-
params.api.clearFocusedCell();
|
|
551
|
-
}}
|
|
552
|
-
>
|
|
553
|
-
<div>{`[All Columns]`}</div>
|
|
554
|
-
<ColumnsSearchResultCountBadge
|
|
555
|
-
selector={selector}
|
|
556
|
-
gridApi={params.api}
|
|
557
|
-
scope="selected"
|
|
558
|
-
/>
|
|
559
|
-
</button>
|
|
560
|
-
),
|
|
561
|
-
cellRenderer: (params: CustomCellRendererProps<T>) => {
|
|
562
|
-
const data = params.data;
|
|
563
|
-
if (!data) {
|
|
564
|
-
return null;
|
|
565
|
-
}
|
|
1058
|
+
<DataCubeIcon.ChevronUp className="text-lg" />
|
|
1059
|
+
</button>
|
|
1060
|
+
<div className="h-3 w-[1px] bg-neutral-200" />
|
|
1061
|
+
<button
|
|
1062
|
+
className="flex h-6 w-6 items-center justify-center text-neutral-500 disabled:text-neutral-300"
|
|
1063
|
+
title="Move dimension(s) down"
|
|
1064
|
+
disabled={selectedDimensions.length === 0}
|
|
1065
|
+
onClick={() => {
|
|
1066
|
+
if (selectedDimensions.length === 0) {
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
566
1069
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
title={`[${data.name}]\nDouble-click to add column`}
|
|
571
|
-
onDoubleClick={() => {
|
|
572
|
-
selector.setSelectedColumns(
|
|
573
|
-
selector.selectedColumns.filter(
|
|
574
|
-
(column) => column !== data,
|
|
1070
|
+
const maxIdx = Math.max(
|
|
1071
|
+
...selectedDimensions.map((dimension) =>
|
|
1072
|
+
panel.dimensions.indexOf(dimension),
|
|
575
1073
|
),
|
|
576
1074
|
);
|
|
577
|
-
|
|
1075
|
+
const followings = [
|
|
1076
|
+
// ensure moving dimensions maintain their relative ordering
|
|
1077
|
+
...panel.dimensions
|
|
1078
|
+
.map((dimension) =>
|
|
1079
|
+
selectedDimensions.find(
|
|
1080
|
+
(dim) => dim.name === dimension.name,
|
|
1081
|
+
),
|
|
1082
|
+
)
|
|
1083
|
+
.filter(isNonNullable),
|
|
1084
|
+
...panel.dimensions.slice(
|
|
1085
|
+
Math.min(maxIdx + 1, panel.dimensions.length) +
|
|
1086
|
+
1,
|
|
1087
|
+
),
|
|
1088
|
+
];
|
|
1089
|
+
|
|
1090
|
+
panel.setDimensions([
|
|
1091
|
+
...panel.dimensions.filter(
|
|
1092
|
+
(dimension) =>
|
|
1093
|
+
!followings.find(
|
|
1094
|
+
(dim) => dim.name === dimension.name,
|
|
1095
|
+
),
|
|
1096
|
+
),
|
|
1097
|
+
...followings,
|
|
1098
|
+
]);
|
|
1099
|
+
|
|
1100
|
+
panel.refreshDimensionsTreeData();
|
|
578
1101
|
}}
|
|
579
1102
|
>
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
1103
|
+
<DataCubeIcon.ChevronDown className="text-lg" />
|
|
1104
|
+
</button>
|
|
1105
|
+
<div className="h-3 w-[1px] bg-neutral-200" />
|
|
1106
|
+
<button
|
|
1107
|
+
className="flex h-6 w-6 items-center justify-center text-neutral-500 disabled:text-neutral-300"
|
|
1108
|
+
title="Remove dimension(s)"
|
|
1109
|
+
disabled={selectedDimensions.length === 0}
|
|
1110
|
+
onClick={() => {
|
|
1111
|
+
if (selectedDimensions.length === 0) {
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
selectedDimensions.at(0)?.setIsRenaming(true);
|
|
1116
|
+
}}
|
|
1117
|
+
>
|
|
1118
|
+
<DataCubeIcon.Pencil className="text-lg" />
|
|
1119
|
+
</button>
|
|
1120
|
+
<div className="h-3 w-[1px] bg-neutral-200" />
|
|
1121
|
+
<button
|
|
1122
|
+
className="flex h-6 w-6 items-center justify-center text-neutral-500 disabled:text-neutral-300"
|
|
1123
|
+
title="Rename dimension"
|
|
1124
|
+
disabled={selectedDimensions.length === 0}
|
|
1125
|
+
onClick={() => {
|
|
1126
|
+
if (selectedDimensions.length === 0) {
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
panel.setDimensions(
|
|
1131
|
+
panel.dimensions.filter(
|
|
1132
|
+
(dimension) =>
|
|
1133
|
+
!selectedDimensions.find(
|
|
1134
|
+
(dim) => dim.name === dimension.name,
|
|
1135
|
+
),
|
|
1136
|
+
),
|
|
1137
|
+
);
|
|
1138
|
+
panel.refreshDimensionsTreeData();
|
|
1139
|
+
}}
|
|
1140
|
+
>
|
|
1141
|
+
<DataCubeIcon.Delete className="text-lg" />
|
|
1142
|
+
</button>
|
|
1143
|
+
</div>
|
|
1144
|
+
</div>
|
|
1145
|
+
<div className="h-[calc(100%_-_24px)]">
|
|
1146
|
+
<AgGridReact
|
|
1147
|
+
{...getColumnsSelectorBaseGridProps<DataCubeEditorDimensionsTreeNode>()}
|
|
1148
|
+
modules={[AllCommunityModule, TreeDataModule]}
|
|
1149
|
+
className="ag-theme-quartz"
|
|
1150
|
+
treeData={true}
|
|
1151
|
+
treeDataChildrenField="children"
|
|
1152
|
+
groupDefaultExpanded={-1} // expand all groups by default
|
|
1153
|
+
// Disable managed row-dragging to disallow changing the order of columns
|
|
1154
|
+
// and to make sure the row data and the available columns state are in sync
|
|
1155
|
+
onGridReady={(params) => {
|
|
1156
|
+
setDimensionsTreeGridApi(params.api);
|
|
1157
|
+
|
|
1158
|
+
// eslint-disable-next-line no-process-env
|
|
1159
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1160
|
+
// restore original error logging
|
|
1161
|
+
console.error = __INTERNAL__original_console_error; // eslint-disable-line no-console
|
|
1162
|
+
}
|
|
1163
|
+
}}
|
|
1164
|
+
onSelectionChanged={(
|
|
1165
|
+
event: SelectionChangedEvent<DataCubeEditorDimensionsTreeNode>,
|
|
1166
|
+
) => {
|
|
1167
|
+
const selectedNodes = event.api
|
|
1168
|
+
.getSelectedNodes()
|
|
1169
|
+
.map((node) => node.data)
|
|
1170
|
+
.filter(isNonNullable);
|
|
1171
|
+
const _selectedColumns: DataCubeEditorColumnsSelectorColumnState[] =
|
|
1172
|
+
[];
|
|
1173
|
+
const _selectedDimensions: DataCubeEditorDimensionState[] =
|
|
1174
|
+
[];
|
|
1175
|
+
selectedNodes.forEach((node) => {
|
|
1176
|
+
if (
|
|
1177
|
+
node.data instanceof DataCubeEditorDimensionState
|
|
1178
|
+
) {
|
|
1179
|
+
_selectedDimensions.push(node.data);
|
|
1180
|
+
} else if (
|
|
1181
|
+
node.data instanceof
|
|
1182
|
+
DataCubeEditorColumnsSelectorColumnState
|
|
1183
|
+
) {
|
|
1184
|
+
_selectedColumns.push(node.data);
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
setSelectedDimensions(_selectedDimensions);
|
|
1188
|
+
setSelectedSelectedColumns(_selectedColumns);
|
|
1189
|
+
}}
|
|
1190
|
+
rowSelection={{
|
|
1191
|
+
mode: 'multiRow',
|
|
1192
|
+
groupSelects: 'self',
|
|
1193
|
+
}}
|
|
1194
|
+
columnDefs={[]}
|
|
1195
|
+
// Using ag-grid quick filter is a cheap way to implement search
|
|
1196
|
+
quickFilterText={panel.dimensionsTreeSearchText}
|
|
1197
|
+
rowData={panel.dimensionsTreeData.nodes}
|
|
1198
|
+
rowClassRules={{
|
|
1199
|
+
'border-t border-b border-1 border-transparent': () =>
|
|
1200
|
+
true,
|
|
1201
|
+
'border-b-sky-600': (params) => {
|
|
1202
|
+
return (
|
|
1203
|
+
dimensionsTreeOnHoverIndex !== undefined &&
|
|
1204
|
+
params.rowIndex === dimensionsTreeOnHoverIndex
|
|
1205
|
+
);
|
|
1206
|
+
},
|
|
1207
|
+
'border-t-sky-600': (params) => {
|
|
1208
|
+
return (
|
|
1209
|
+
dimensionsTreeOnHoverIndex === -1 &&
|
|
1210
|
+
params.rowIndex === 0
|
|
1211
|
+
);
|
|
1212
|
+
},
|
|
1213
|
+
}}
|
|
1214
|
+
autoGroupColumnDef={{
|
|
1215
|
+
headerName: 'Name',
|
|
1216
|
+
field: 'name',
|
|
1217
|
+
colId: 'name',
|
|
1218
|
+
flex: 1,
|
|
1219
|
+
minWidth: 100,
|
|
1220
|
+
filter: true,
|
|
1221
|
+
sortable: false,
|
|
1222
|
+
resizable: false,
|
|
1223
|
+
suppressHeaderMenuButton: true,
|
|
1224
|
+
suppressKeyboardEvent: (event) => {
|
|
1225
|
+
if (
|
|
1226
|
+
panel.dimensions.some(
|
|
1227
|
+
(dimension) => dimension.isRenaming,
|
|
1228
|
+
)
|
|
1229
|
+
) {
|
|
1230
|
+
// only allow tabbing through the grid when renaming
|
|
1231
|
+
if (event.event.code === 'Tab') {
|
|
1232
|
+
return false;
|
|
1233
|
+
}
|
|
1234
|
+
return true;
|
|
1235
|
+
}
|
|
1236
|
+
return false;
|
|
1237
|
+
},
|
|
1238
|
+
getQuickFilterText: (params) => params.value,
|
|
1239
|
+
/**
|
|
1240
|
+
* Support double-click to remove all (filtered by search) columns
|
|
1241
|
+
*/
|
|
1242
|
+
headerComponent: (
|
|
1243
|
+
params: CustomCellRendererProps,
|
|
1244
|
+
) => (
|
|
1245
|
+
<button
|
|
1246
|
+
title="Double-click to remove all columns"
|
|
1247
|
+
className="flex h-full w-full items-center justify-between pl-0.5"
|
|
1248
|
+
onDoubleClick={() => {
|
|
1249
|
+
// The columns being moved are scoped by the current search
|
|
1250
|
+
const filteredNodes =
|
|
1251
|
+
getDataForAllFilteredNodes(params.api);
|
|
1252
|
+
|
|
1253
|
+
panel.deselectColumns(
|
|
1254
|
+
panel.selectedColumns.filter((column) =>
|
|
1255
|
+
_findCol(filteredNodes, column.name),
|
|
1256
|
+
),
|
|
1257
|
+
);
|
|
1258
|
+
panel.refreshDimensionsTreeData();
|
|
1259
|
+
params.api.clearFocusedCell();
|
|
1260
|
+
}}
|
|
1261
|
+
>
|
|
1262
|
+
<div>{`[All Dimensions]`}</div>
|
|
1263
|
+
<DimensionsTreeSearchResultCountBadge
|
|
1264
|
+
panel={panel}
|
|
1265
|
+
gridApi={params.api}
|
|
1266
|
+
/>
|
|
1267
|
+
</button>
|
|
1268
|
+
),
|
|
1269
|
+
onCellDoubleClicked: (params) => {
|
|
1270
|
+
if (
|
|
1271
|
+
params.data?.data instanceof
|
|
1272
|
+
DataCubeEditorDimensionState
|
|
1273
|
+
) {
|
|
1274
|
+
params.data.data.setColumns([]);
|
|
1275
|
+
panel.refreshDimensionsTreeData();
|
|
1276
|
+
params.api.clearFocusedCell();
|
|
1277
|
+
}
|
|
1278
|
+
},
|
|
1279
|
+
cellRendererParams: {
|
|
1280
|
+
innerRenderer: (
|
|
1281
|
+
params: CustomCellRendererProps<DataCubeEditorDimensionsTreeNode>,
|
|
1282
|
+
) => {
|
|
1283
|
+
const data = params.data;
|
|
1284
|
+
|
|
1285
|
+
if (!data) {
|
|
1286
|
+
return null;
|
|
1287
|
+
}
|
|
1288
|
+
if (
|
|
1289
|
+
data.data instanceof
|
|
1290
|
+
DataCubeEditorDimensionState
|
|
1291
|
+
) {
|
|
1292
|
+
return (
|
|
1293
|
+
<DimensionLabel
|
|
1294
|
+
panel={panel}
|
|
1295
|
+
dimension={data.data}
|
|
1296
|
+
gridApi={params.api}
|
|
1297
|
+
/>
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
return (
|
|
1301
|
+
<div
|
|
1302
|
+
className="flex h-full w-full cursor-pointer items-center"
|
|
1303
|
+
title={`[${data.name}]\nDouble-click to remove column`}
|
|
1304
|
+
onDoubleClick={() => {
|
|
1305
|
+
if (
|
|
1306
|
+
data.data instanceof
|
|
1307
|
+
DataCubeEditorColumnsSelectorColumnState
|
|
1308
|
+
) {
|
|
1309
|
+
panel.deselectColumns([data.data]);
|
|
1310
|
+
panel.refreshDimensionsTreeData();
|
|
1311
|
+
}
|
|
1312
|
+
params.api.clearFocusedCell();
|
|
1313
|
+
}}
|
|
1314
|
+
>
|
|
1315
|
+
<div className="h-full flex-1 items-center overflow-hidden overflow-ellipsis whitespace-nowrap pl-1">
|
|
1316
|
+
{data.name}
|
|
1317
|
+
</div>
|
|
1318
|
+
</div>
|
|
1319
|
+
);
|
|
1320
|
+
},
|
|
1321
|
+
},
|
|
1322
|
+
}}
|
|
1323
|
+
noRowsOverlayComponent={(
|
|
1324
|
+
params: CustomNoRowsOverlayProps,
|
|
1325
|
+
) => {
|
|
1326
|
+
if (params.api.getQuickFilter()) {
|
|
1327
|
+
return (
|
|
1328
|
+
<div className="flex items-center border-[1.5px] border-neutral-300 p-2 font-semibold text-neutral-400">
|
|
1329
|
+
<div>
|
|
1330
|
+
<DataCubeIcon.WarningCircle className="mr-1 text-lg" />
|
|
1331
|
+
</div>
|
|
1332
|
+
No match found
|
|
1333
|
+
</div>
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
return (
|
|
1337
|
+
<div className="flex items-center border-[1.5px] border-neutral-200 p-2 font-semibold text-neutral-400">
|
|
1338
|
+
<div>
|
|
1339
|
+
<DataCubeIcon.Warning className="mr-1 text-lg" />
|
|
1340
|
+
</div>
|
|
1341
|
+
No dimensions specified
|
|
586
1342
|
</div>
|
|
587
|
-
)
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
1343
|
+
);
|
|
1344
|
+
}}
|
|
1345
|
+
suppressRowDrag={!isDimensionsTreeRowDragEnabled}
|
|
1346
|
+
rowDragEntireRow={isDimensionsTreeRowDragEnabled}
|
|
1347
|
+
onRowDragEnd={onDimensionsTreeRowDragEnd}
|
|
1348
|
+
onRowDragMove={onDimensionsTreeRowDragMove}
|
|
1349
|
+
onRowDragCancel={onDimensionsTreeRowDragCancel}
|
|
1350
|
+
onRowDragLeave={onDimensionsTreeRowDragLeave}
|
|
1351
|
+
/>
|
|
1352
|
+
</div>
|
|
1353
|
+
</div>
|
|
1354
|
+
</div>
|
|
1355
|
+
</div>
|
|
600
1356
|
</div>
|
|
601
|
-
|
|
1357
|
+
)}
|
|
602
1358
|
</div>
|
|
603
1359
|
</div>
|
|
604
1360
|
);
|