• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

keplergl / kepler.gl / 25969480752

16 May 2026 06:25PM UTC coverage: 57.665% (+0.01%) from 57.652%
25969480752

Pull #3441

github

web-flow
Merge 06aa82672 into 259667648
Pull Request #3441: chore: Convert class components to functional components

7181 of 14909 branches covered (48.17%)

Branch coverage included in aggregate %.

49 of 110 new or added lines in 6 files covered. (44.55%)

1 existing line in 1 file now uncovered.

14568 of 22807 relevant lines covered (63.88%)

77.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.81
/src/components/src/modals/data-table-modal.tsx
1
// SPDX-License-Identifier: MIT
2
// Copyright contributors to the kepler.gl project
3

4
import React, {useState, useRef, useMemo, useCallback} from 'react';
5
import styled, {withTheme, IStyledComponent} from 'styled-components';
6
import DatasetLabel from '../common/dataset-label';
7
import DataTableFactory from '../common/data-table';
8
import {renderedSize} from '../common/data-table/cell-size';
9
import CanvasHack from '../common/data-table/canvas';
10
import KeplerTable, {Datasets} from '@kepler.gl/table';
11
import {UIStateActions} from '@kepler.gl/actions';
12
import {UiState} from '@kepler.gl/types';
13
import {Gear} from '../common/icons';
14
import Portaled from '../common/portaled';
15
import DataTableConfigFactory from '../common/data-table/display-format';
16
import {BaseComponentProps} from '../types';
17

18
const MIN_STATS_CELL_SIZE = 122;
7✔
19
const DEFAULT_SORT_COLUMN = {};
7✔
20

21
// sidePadding changes from 38 to 68, 30px for configuration button
22
const dgSettings = {
7✔
23
  sidePadding: '68px',
24
  verticalPadding: '16px',
25
  height: '36px'
26
};
27

28
const StyledModal = styled.div`
7✔
29
  min-height: 85vh;
30
  overflow: hidden;
31
  display: flex;
32
`;
33

34
const DatasetCatalog = styled.div`
7✔
35
  display: flex;
36
  padding: ${dgSettings.verticalPadding} ${dgSettings.sidePadding} 0 0;
37

38
  .overflow-horizontal {
39
    display: flex;
40
    overflow-x: auto;
41
    overflow-y: hidden;
42
    flex-direction: row;
43
    ${props => props.theme.modalScrollBar}
7✔
44
  }
45
`;
46

47
export type DatasetModalTabProps = BaseComponentProps & {
48
  active?: boolean;
49
};
50

51
export const DatasetModalTab: IStyledComponent<
52
  'web',
53
  DatasetModalTabProps
54
> = styled.div<DatasetModalTabProps>`
7✔
55
  align-items: center;
56
  border-bottom: 3px solid ${props => (props.active ? 'black' : 'transparent')};
12✔
57
  cursor: pointer;
58
  display: flex;
59
  height: 35px;
60
  margin: 0 3px;
61
  padding: 0 5px;
62

63
  &:hover {
64
    border-bottom: 3px solid black;
65
  }
66
`;
67

68
const StyledConfigureButton = styled.div`
7✔
69
  display: flex;
70
  justify-content: flex-end;
71
  position: absolute;
72
  top: 24px;
73
  right: 48px;
74
  svg {
75
    stroke: black;
76
  }
77
  cursor: pointer;
78
`;
79

80
interface DatasetTabsUnmemoizedProps {
81
  activeDataset: KeplerTable;
82
  datasets: Datasets;
83
  showDatasetTable: (id: string) => void;
84
}
85

86
const DatasetTabsUnmemoized: React.FC<DatasetTabsUnmemoizedProps> = ({
7✔
87
  activeDataset,
88
  datasets,
89
  showDatasetTable
90
}) => (
91
  <DatasetCatalog className="dataset-modal-catalog">
7✔
92
    <div className="overflow-horizontal">
93
      {Object.values(datasets).map((dataset: KeplerTable) => (
94
        <DatasetModalTab
12✔
95
          className="dataset-modal-tab"
96
          active={dataset === activeDataset}
97
          key={dataset.id}
98
          onClick={() => showDatasetTable(dataset.id)}
1✔
99
        >
100
          <DatasetLabel dataset={dataset} />
101
        </DatasetModalTab>
102
      ))}
103
    </div>
104
  </DatasetCatalog>
105
);
106

107
export const DatasetTabs = React.memo(DatasetTabsUnmemoized);
7✔
108

109
DatasetTabs.displayName = 'DatasetTabs';
7✔
110

111
DataTableModalFactory.deps = [DataTableFactory, DataTableConfigFactory];
7✔
112

113
const TableContainer = styled.div`
7✔
114
  display: flex;
115
  flex-direction: column;
116
  flex-grow: 1;
117
  min-height: 100%;
118
  max-height: 100%;
119
  max-width: 100%;
120
`;
121

122
interface DataTableModalProps {
123
  theme: any;
124
  dataId?: string;
125
  sortTableColumn: (id: string, column: string, mode?: string) => void;
126
  pinTableColumn: (id: string, column: string) => void;
127
  copyTableColumn: (id: string, column: string) => void;
128
  datasets: Datasets;
129
  showDatasetTable: (id: string) => void;
130
  showTab?: boolean;
131
  setColumnDisplayFormat: (
132
    dataId: string,
133
    formats: {
134
      column: string;
135
      displayFormat: string;
136
    }
137
  ) => void;
138
  uiStateActions: typeof UIStateActions;
139
  uiState: UiState;
140
}
141

142
function DataTableModalFactory(
143
  DataTable: ReturnType<typeof DataTableFactory>,
144
  DataTableConfig: ReturnType<typeof DataTableConfigFactory>
145
): React.ComponentType<Omit<DataTableModalProps, 'theme'>> {
146
  const DataTableModal: React.FC<DataTableModalProps> = ({
14✔
147
    theme,
148
    dataId = '',
1✔
149
    sortTableColumn,
150
    pinTableColumn,
151
    copyTableColumn: copyTableColumnProp,
152
    datasets,
153
    showDatasetTable,
154
    showTab = true,
8✔
155
    setColumnDisplayFormat: setColumnDisplayFormatProp,
156
    uiStateActions,
157
    uiState
158
  }) => {
159
    const [showConfig, setShowConfig] = useState(false);
8✔
160
    const datasetCellSizeCache = useRef<Record<string, any>>({});
8✔
161

162
    const fields = useMemo(
8✔
163
      () => (datasets && dataId ? (datasets[dataId] || {}).fields : undefined),
8!
164
      [datasets, dataId]
165
    );
166

167
    const columns = useMemo(() => fields?.map(f => f.name) || [], [fields]);
64✔
168

169
    const colMeta = useMemo(
8✔
170
      () =>
171
        fields?.reduce(
8✔
172
          (acc, {name, displayName, type, filterProps, format, displayFormat}) => ({
64✔
173
            ...acc,
174
            [name]: {
175
              name: displayName || name,
64!
176
              type,
177
              ...(format ? {format} : {}),
64✔
178
              ...(displayFormat ? {displayFormat} : {}),
64✔
179
              ...(filterProps?.columnStats ? {columnStats: filterProps.columnStats} : {})
64!
180
            }
181
          }),
182
          {}
183
        ) || {},
184
      [fields]
185
    );
186

187
    const cellSizeCache = useMemo(() => {
8✔
188
      if (!datasets || !dataId || !datasets[dataId]) {
8✔
189
        return {};
1✔
190
      }
191
      const {fields, dataContainer} = datasets[dataId];
7✔
192

193
      let showCalculate: boolean | null = null;
7✔
194
      if (!datasetCellSizeCache.current[dataId]) {
7!
195
        showCalculate = true;
7✔
196
      } else if (
×
197
        datasetCellSizeCache.current[dataId].fields !== fields ||
×
198
        datasetCellSizeCache.current[dataId].dataContainer !== dataContainer
199
      ) {
200
        showCalculate = true;
×
201
      }
202

203
      if (!showCalculate) {
7!
NEW
204
        return datasetCellSizeCache.current[dataId].cellSizeCache;
×
205
      }
206

207
      const cellSizeCache = fields.reduce(
7✔
208
        (acc, field, colIdx) => ({
64✔
209
          ...acc,
210
          [field.name]: renderedSize({
211
            text: {
212
              dataContainer,
213
              column: field.displayName
214
            },
215
            colIdx,
216
            type: field.type,
217
            fontSize: theme.cellFontSize,
218
            font: theme.fontFamily,
219
            minCellSize: MIN_STATS_CELL_SIZE
220
          })
221
        }),
222
        {}
223
      );
224

225
      datasetCellSizeCache.current[dataId] = {
7✔
226
        cellSizeCache,
227
        fields,
228
        dataContainer
229
      };
230
      return cellSizeCache;
7✔
231
    }, [dataId, datasets, theme]);
232

233
    const handleCopyTableColumn = useCallback(
8✔
234
      (column: string) => {
235
        copyTableColumnProp(dataId, column);
1✔
236
      },
237
      [copyTableColumnProp, dataId]
238
    );
239

240
    const handlePinTableColumn = useCallback(
8✔
241
      (column: string) => {
242
        pinTableColumn(dataId, column);
1✔
243
      },
244
      [pinTableColumn, dataId]
245
    );
246

247
    const handleSortTableColumn = useCallback(
8✔
248
      (column: string, mode?: string) => {
249
        sortTableColumn(dataId, column, mode);
1✔
250
      },
251
      [sortTableColumn, dataId]
252
    );
253

254
    const handleSetColumnDisplayFormat = useCallback(
8✔
255
      formats => {
256
        if (dataId) setColumnDisplayFormatProp(dataId, formats);
1!
257
      },
258
      [setColumnDisplayFormatProp, dataId]
259
    );
260

261
    const onOpenConfig = useCallback(() => {
8✔
NEW
262
      setShowConfig(true);
×
263
    }, []);
264

265
    const onCloseConfig = useCallback(() => {
8✔
NEW
266
      setShowConfig(false);
×
267
    }, []);
268

269
    if (!datasets || !dataId) {
8✔
270
      return null;
1✔
271
    }
272

273
    const activeDataset = datasets[dataId];
7✔
274

275
    return (
7✔
276
      <StyledModal className="dataset-modal" id="dataset-modal">
277
        <CanvasHack />
278
        <TableContainer>
279
          {showTab ? (
7!
280
            <DatasetTabs
281
              activeDataset={activeDataset}
282
              datasets={datasets}
283
              showDatasetTable={showDatasetTable}
284
            />
285
          ) : null}
286
          <StyledConfigureButton className="display-config-button">
287
            <Gear onClick={onOpenConfig} />
288
            <Portaled right={240} top={20} isOpened={showConfig} onClose={onCloseConfig}>
289
              <DataTableConfig
290
                columns={columns}
291
                colMeta={colMeta}
292
                setColumnDisplayFormat={handleSetColumnDisplayFormat}
293
                onClose={onCloseConfig}
294
              />
295
            </Portaled>
296
          </StyledConfigureButton>
297
          {datasets[dataId] ? (
7!
298
            <DataTable
299
              key={dataId}
300
              dataId={dataId}
301
              columns={columns}
302
              colMeta={colMeta}
303
              cellSizeCache={cellSizeCache}
304
              dataContainer={activeDataset.dataContainer}
305
              pinnedColumns={activeDataset.pinnedColumns}
306
              sortOrder={activeDataset.sortOrder}
307
              sortColumn={activeDataset.sortColumn || DEFAULT_SORT_COLUMN}
13✔
308
              copyTableColumn={handleCopyTableColumn}
309
              pinTableColumn={handlePinTableColumn}
310
              sortTableColumn={handleSortTableColumn}
311
              setColumnDisplayFormat={handleSetColumnDisplayFormat}
312
              hasStats={false}
313
            />
314
          ) : null}
315
        </TableContainer>
316
      </StyledModal>
317
    );
318
  };
319

320
  return withTheme(DataTableModal) as React.ComponentType<Omit<DataTableModalProps, 'theme'>>;
14✔
321
}
322

323
export default DataTableModalFactory;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc