@sqlrooms/kepler 0.29.0-rc.2 → 0.29.0-rc.4
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/README.md +12 -2
- package/dist/KeplerSlice.d.ts +7 -5
- package/dist/KeplerSlice.d.ts.map +1 -1
- package/dist/KeplerSlice.js +48 -141
- package/dist/KeplerSlice.js.map +1 -1
- package/dist/components/CustomDndContext.d.ts.map +1 -1
- package/dist/components/CustomDndContext.js +5 -2
- package/dist/components/CustomDndContext.js.map +1 -1
- package/dist/components/CustomFilterPanelHeader.d.ts.map +1 -1
- package/dist/components/CustomFilterPanelHeader.js +5 -5
- package/dist/components/CustomFilterPanelHeader.js.map +1 -1
- package/dist/components/CustomLayerManager.d.ts.map +1 -1
- package/dist/components/CustomLayerManager.js.map +1 -1
- package/dist/components/CustomMapLegend.d.ts +4 -1
- package/dist/components/CustomMapLegend.d.ts.map +1 -1
- package/dist/components/CustomMapLegend.js +19 -8
- package/dist/components/CustomMapLegend.js.map +1 -1
- package/dist/components/CustomMapLegendPanel.d.ts +3 -3
- package/dist/components/CustomMapLegendPanel.d.ts.map +1 -1
- package/dist/components/CustomMapLegendPanel.js +19 -4
- package/dist/components/CustomMapLegendPanel.js.map +1 -1
- package/dist/components/KeplerMapContainer.d.ts.map +1 -1
- package/dist/components/KeplerMapContainer.js +36 -4
- package/dist/components/KeplerMapContainer.js.map +1 -1
- package/dist/components/KeplerPlotContainer.d.ts +1 -1
- package/dist/components/KeplerPlotContainer.d.ts.map +1 -1
- package/dist/components/KeplerPlotContainer.js +3 -1
- package/dist/components/KeplerPlotContainer.js.map +1 -1
- package/dist/components/SplitMapIndexContext.d.ts +2 -0
- package/dist/components/SplitMapIndexContext.d.ts.map +1 -0
- package/dist/components/SplitMapIndexContext.js +3 -0
- package/dist/components/SplitMapIndexContext.js.map +1 -0
- package/dist/hooks/useDndEffects.d.ts +1 -1
- package/dist/hooks/useDndEffects.d.ts.map +1 -1
- package/dist/hooks/useDndEffects.js +4 -0
- package/dist/hooks/useDndEffects.js.map +1 -1
- package/dist/hooks/useDndLayers.d.ts +1 -1
- package/dist/hooks/useDndLayers.d.ts.map +1 -1
- package/dist/hooks/useDndLayers.js +4 -0
- package/dist/hooks/useDndLayers.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +23 -23
package/README.md
CHANGED
|
@@ -7,7 +7,16 @@ Use this package when you want a **map-first analytics experience** in a SQLRoom
|
|
|
7
7
|
- `createKeplerSlice()` to add Kepler state/actions to your Room store
|
|
8
8
|
- `KeplerMapContainer` and `KeplerPlotContainer` for rendering maps/overlays
|
|
9
9
|
- `KeplerSidePanels` for layer/filter/interaction UI
|
|
10
|
-
- utilities for map config persistence
|
|
10
|
+
- utilities for map config persistence, dataset synchronization, and migration
|
|
11
|
+
from legacy Kepler-owned tabs to artifact-backed tabs
|
|
12
|
+
|
|
13
|
+
## Selection model
|
|
14
|
+
|
|
15
|
+
- `createKeplerSlice()` manages Kepler map documents and runtime state keyed by
|
|
16
|
+
map id, but it does not own host-level map selection.
|
|
17
|
+
- Render maps with explicit ids, for example `<KeplerMapContainer mapId={id} />`.
|
|
18
|
+
- Use `@sqlrooms/artifacts` when an app needs multiple user-managed map tabs;
|
|
19
|
+
artifact state should own the selected map artifact.
|
|
11
20
|
|
|
12
21
|
## Installation
|
|
13
22
|
|
|
@@ -51,7 +60,7 @@ export const {roomStore, useRoomStore} = createRoomStore<RoomState>(
|
|
|
51
60
|
);
|
|
52
61
|
|
|
53
62
|
function MapPanel() {
|
|
54
|
-
const mapId = useRoomStore((state) => state.kepler.config.
|
|
63
|
+
const mapId = useRoomStore((state) => state.kepler.config.maps[0]?.id);
|
|
55
64
|
const addTableToMap = useRoomStore((state) => state.kepler.addTableToMap);
|
|
56
65
|
const isTableReady = useRoomStore((state) =>
|
|
57
66
|
Boolean(state.db.findTableByName('earthquakes')),
|
|
@@ -94,6 +103,7 @@ createKeplerSlice({
|
|
|
94
103
|
|
|
95
104
|
## Related packages
|
|
96
105
|
|
|
106
|
+
- `@sqlrooms/artifacts` for artifact-backed map tabs
|
|
97
107
|
- `@sqlrooms/kepler-config` for Zod schemas used by persisted Kepler config
|
|
98
108
|
- `@sqlrooms/room-shell` for Room store composition and UI shell
|
|
99
109
|
- `@sqlrooms/duckdb` for DuckDB-backed table loading/querying
|
package/dist/KeplerSlice.d.ts
CHANGED
|
@@ -66,7 +66,10 @@ export type KeplerSliceState = {
|
|
|
66
66
|
setFilter: (mapId: string, filterIdx: number, prop: string | string[], value: any, valueIndex?: number) => void;
|
|
67
67
|
setFilterAnimationTime: (mapId: string, filterIdx: number, prop: string, value: any) => void;
|
|
68
68
|
setFilterAnimationWindow: (mapId: string, filterId: string, animationWindow: string) => void;
|
|
69
|
+
setFilterView: (mapId: string, filterIdx: number, view: 'side' | 'enlarged' | 'minified') => void;
|
|
69
70
|
updateTooltipFields: (mapId: string, datasetId: string, fieldNames: string[]) => void;
|
|
71
|
+
toggleSplitMap: (mapId: string, index?: number) => void;
|
|
72
|
+
toggleLayerForMap: (mapId: string, mapIndex: number, layerId: string) => void;
|
|
70
73
|
addTableToMap: (mapId: string, tableName: string, options?: AddDataToMapPayload['options'], config?: AddDataToMapPayload['config']) => Promise<void>;
|
|
71
74
|
addTileSetToMap: (mapId: string, tableName: string, tileset: {
|
|
72
75
|
name: string;
|
|
@@ -76,13 +79,15 @@ export type KeplerSliceState = {
|
|
|
76
79
|
addConfigToMap: (mapId: string, config: KeplerMapSchema) => void;
|
|
77
80
|
removeDatasetFromMaps: (datasetId: string) => void;
|
|
78
81
|
dispatchAction: (mapId: string, action: KeplerAction) => void;
|
|
79
|
-
|
|
82
|
+
ensureMap: (mapId: string, name?: string) => void;
|
|
80
83
|
/**
|
|
81
84
|
* Create a new map and return the map id
|
|
82
85
|
* @param name - The name of the map
|
|
83
86
|
* @returns The map id
|
|
84
87
|
*/
|
|
85
|
-
createMap: (name?: string
|
|
88
|
+
createMap: (name?: string, options?: {
|
|
89
|
+
id?: string;
|
|
90
|
+
}) => string;
|
|
86
91
|
deleteMap: (mapId: string) => void;
|
|
87
92
|
duplicateMap: (mapId: string) => Promise<{
|
|
88
93
|
success: boolean;
|
|
@@ -90,9 +95,6 @@ export type KeplerSliceState = {
|
|
|
90
95
|
code?: string;
|
|
91
96
|
}>;
|
|
92
97
|
renameMap: (mapId: string, name: string) => void;
|
|
93
|
-
closeMap: (mapId: string) => void;
|
|
94
|
-
setOpenTabs: (tabIds: string[]) => void;
|
|
95
|
-
getCurrentMap: () => KeplerMapSchema | undefined;
|
|
96
98
|
registerKeplerMapIfNotExists: (mapId: string) => void;
|
|
97
99
|
__reduxProviderStore: ReduxStore<KeplerGlReduxState, AnyAction> | undefined;
|
|
98
100
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KeplerSlice.d.ts","sourceRoot":"","sources":["../src/KeplerSlice.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"KeplerSlice.d.ts","sourceRoot":"","sources":["../src/KeplerSlice.ts"],"names":[],"mappings":"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"}
|
package/dist/KeplerSlice.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { addDataToMap, addFilter as addFilterAction, addLayer as addLayerAction, deleteEntry, interactionConfigChange, ActionTypes as KeplerActionTypes, registerEntry, removeDataset, requestMapStyles, setFilter as setFilterAction, setFilterAnimationTime as setFilterAnimationTimeAction, setFilterAnimationWindow as setFilterAnimationWindowAction, wrapTo, } from '@kepler.gl/actions';
|
|
1
|
+
import { addDataToMap, addFilter as addFilterAction, addLayer as addLayerAction, deleteEntry, interactionConfigChange, ActionTypes as KeplerActionTypes, registerEntry, removeDataset, requestMapStyles, setFilter as setFilterAction, setFilterAnimationTime as setFilterAnimationTimeAction, setFilterAnimationWindow as setFilterAnimationWindowAction, setFilterView as setFilterViewAction, toggleLayerForMap as toggleLayerForMapAction, toggleSplitMap as toggleSplitMapAction, wrapTo, } from '@kepler.gl/actions';
|
|
2
2
|
import { ALL_FIELD_TYPES } from '@kepler.gl/constants';
|
|
3
3
|
import { castDuckDBTypesForKepler, getDuckDBColumnTypes, getDuckDBColumnTypesMap, restoreGeoarrowMetadata, setGeoArrowWKBExtension, } from '@kepler.gl/duckdb';
|
|
4
4
|
import { arrowSchemaToFields } from '@kepler.gl/processors';
|
|
@@ -7,7 +7,8 @@ import { KeplerGLSchemaClass } from '@kepler.gl/schemas';
|
|
|
7
7
|
import { KeplerTable } from '@kepler.gl/table';
|
|
8
8
|
import { initApplicationConfig, } from '@kepler.gl/utils';
|
|
9
9
|
import { createId } from '@paralleldrive/cuid2';
|
|
10
|
-
import {
|
|
10
|
+
import { KeplerSliceConfig } from '@sqlrooms/kepler-config';
|
|
11
|
+
import { createSlice, useBaseRoomShellStore, } from '@sqlrooms/room-shell';
|
|
11
12
|
import { getTheme } from '@sqlrooms/ui';
|
|
12
13
|
import { produce, setAutoFreeze } from 'immer';
|
|
13
14
|
import { taskMiddleware } from 'react-palm/tasks';
|
|
@@ -15,7 +16,6 @@ import { compose } from 'redux';
|
|
|
15
16
|
import { createLogger } from 'redux-logger';
|
|
16
17
|
setAutoFreeze(false); // Kepler attempts to mutate redux state, so we need to disable immer's auto freeze to avoid errors
|
|
17
18
|
const KeplerGLSchemaManager = new KeplerGLSchemaClass();
|
|
18
|
-
const KEPLER_COMMAND_OWNER = '@sqlrooms/kepler';
|
|
19
19
|
class DesktopKeplerTable extends KeplerTable {
|
|
20
20
|
static getInputDataValidator = function () {
|
|
21
21
|
// Default validator accepts only string timestamps
|
|
@@ -32,6 +32,10 @@ function createDefaultMapKeplerState(resolvedTheme) {
|
|
|
32
32
|
currentModal: null,
|
|
33
33
|
mapControls: {
|
|
34
34
|
visibleLayers: INITIAL_UI_STATE.mapControls.visibleLayers,
|
|
35
|
+
splitMap: {
|
|
36
|
+
show: true,
|
|
37
|
+
active: false,
|
|
38
|
+
},
|
|
35
39
|
mapLegend: {
|
|
36
40
|
show: true,
|
|
37
41
|
active: false,
|
|
@@ -50,7 +54,7 @@ function createDefaultMapKeplerState(resolvedTheme) {
|
|
|
50
54
|
}
|
|
51
55
|
export function createDefaultKeplerConfig(props) {
|
|
52
56
|
const mapId = createId();
|
|
53
|
-
|
|
57
|
+
const config = {
|
|
54
58
|
maps: [
|
|
55
59
|
{
|
|
56
60
|
id: mapId,
|
|
@@ -59,70 +63,9 @@ export function createDefaultKeplerConfig(props) {
|
|
|
59
63
|
lastOpenedAt: Date.now(),
|
|
60
64
|
},
|
|
61
65
|
],
|
|
62
|
-
currentMapId: mapId,
|
|
63
|
-
openTabs: [mapId],
|
|
64
66
|
...props,
|
|
65
67
|
};
|
|
66
|
-
|
|
67
|
-
function createKeplerCommands() {
|
|
68
|
-
const DUPLICATE_MAP_COMMAND_ID = 'kepler.duplicate-tab';
|
|
69
|
-
// Error codes for internal tracking/logging (kebab-case for consistency)
|
|
70
|
-
const ERROR_CODES = {
|
|
71
|
-
MAP_NOT_FOUND: 'map-not-found',
|
|
72
|
-
MAP_STATE_NOT_INITIALIZED: 'map-state-not-initialized',
|
|
73
|
-
};
|
|
74
|
-
return [
|
|
75
|
-
{
|
|
76
|
-
id: DUPLICATE_MAP_COMMAND_ID,
|
|
77
|
-
name: 'Duplicate Tab',
|
|
78
|
-
description: 'Duplicate the current map tab',
|
|
79
|
-
group: 'Kepler',
|
|
80
|
-
keywords: ['kepler', 'map', 'duplicate', 'tab', 'copy'],
|
|
81
|
-
metadata: {
|
|
82
|
-
readOnly: false,
|
|
83
|
-
idempotent: false,
|
|
84
|
-
riskLevel: 'low',
|
|
85
|
-
},
|
|
86
|
-
execute: async ({ getState }) => {
|
|
87
|
-
const currentMapId = getState().kepler.config.currentMapId;
|
|
88
|
-
const sourceMap = getState().kepler.config.maps.find((m) => m.id === currentMapId);
|
|
89
|
-
if (!sourceMap) {
|
|
90
|
-
return {
|
|
91
|
-
success: false,
|
|
92
|
-
commandId: DUPLICATE_MAP_COMMAND_ID,
|
|
93
|
-
message: 'Unable to duplicate map: current map not found',
|
|
94
|
-
code: ERROR_CODES.MAP_NOT_FOUND,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
// Ensure the map's redux state is registered before attempting to duplicate
|
|
98
|
-
getState().kepler.registerKeplerMapIfNotExists(currentMapId);
|
|
99
|
-
// Re-read state after registration to avoid stale state
|
|
100
|
-
const sourceMapState = getState().kepler.map[currentMapId];
|
|
101
|
-
if (!sourceMapState) {
|
|
102
|
-
return {
|
|
103
|
-
success: false,
|
|
104
|
-
commandId: DUPLICATE_MAP_COMMAND_ID,
|
|
105
|
-
message: 'Unable to duplicate map: map state not initialized',
|
|
106
|
-
code: ERROR_CODES.MAP_STATE_NOT_INITIALIZED,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
const duplicateResult = await getState().kepler.duplicateMap(currentMapId);
|
|
110
|
-
if (!duplicateResult.success) {
|
|
111
|
-
return {
|
|
112
|
-
success: false,
|
|
113
|
-
commandId: DUPLICATE_MAP_COMMAND_ID,
|
|
114
|
-
message: duplicateResult.message,
|
|
115
|
-
code: duplicateResult.code,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
return {
|
|
119
|
-
success: true,
|
|
120
|
-
commandId: DUPLICATE_MAP_COMMAND_ID,
|
|
121
|
-
message: 'Duplicated map tab',
|
|
122
|
-
};
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
];
|
|
68
|
+
return KeplerSliceConfig.parse(config);
|
|
126
69
|
}
|
|
127
70
|
export function hasMapId(action) {
|
|
128
71
|
return (typeof action.payload === 'object' &&
|
|
@@ -158,7 +101,7 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
158
101
|
map.lastOpenedAt = now;
|
|
159
102
|
}
|
|
160
103
|
};
|
|
161
|
-
return createSlice((set, get,
|
|
104
|
+
return createSlice((set, get, _store) => {
|
|
162
105
|
function resolveInitialMapKeplerState(context) {
|
|
163
106
|
const resolvedTheme = getTheme();
|
|
164
107
|
const defaultInitialMapKeplerState = createDefaultMapKeplerState(resolvedTheme);
|
|
@@ -205,8 +148,9 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
205
148
|
__reduxProviderStore: undefined,
|
|
206
149
|
forwardDispatch: {},
|
|
207
150
|
setConfig: (config) => {
|
|
151
|
+
const nextConfig = KeplerSliceConfig.parse(config);
|
|
208
152
|
set((state) => produce(state, (draft) => {
|
|
209
|
-
draft.kepler.config =
|
|
153
|
+
draft.kepler.config = nextConfig;
|
|
210
154
|
}));
|
|
211
155
|
updateMapReduxStates();
|
|
212
156
|
updateForwardDispatch();
|
|
@@ -215,11 +159,11 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
215
159
|
},
|
|
216
160
|
async initialize() {
|
|
217
161
|
const config = get().kepler.config;
|
|
218
|
-
const
|
|
219
|
-
const keplerInitialState = createKeplerReducer({
|
|
162
|
+
const keplerInitialState = config.maps.reduce((mapState, map) => createKeplerReducer({
|
|
220
163
|
reason: 'initialize',
|
|
221
|
-
mapId:
|
|
222
|
-
|
|
164
|
+
mapId: map.id,
|
|
165
|
+
name: map.name,
|
|
166
|
+
})(mapState, registerEntry({ id: map.id })), {});
|
|
223
167
|
set({
|
|
224
168
|
kepler: {
|
|
225
169
|
...get().kepler,
|
|
@@ -247,13 +191,12 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
247
191
|
updateForwardDispatch();
|
|
248
192
|
await get().kepler.syncKeplerDatasets();
|
|
249
193
|
updateMapConfigs();
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
registerCommandsForOwner(store, KEPLER_COMMAND_OWNER, keplerCommands);
|
|
194
|
+
for (const mapId of Object.keys(get().kepler.map)) {
|
|
195
|
+
requestMapStyle(mapId);
|
|
196
|
+
}
|
|
254
197
|
},
|
|
255
198
|
async destroy() {
|
|
256
|
-
|
|
199
|
+
// no-op
|
|
257
200
|
},
|
|
258
201
|
addLayer: (mapId, layer, datasetId) => {
|
|
259
202
|
get().kepler.registerKeplerMapIfNotExists(mapId);
|
|
@@ -275,6 +218,10 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
275
218
|
get().kepler.registerKeplerMapIfNotExists(mapId);
|
|
276
219
|
get().kepler.dispatchAction(mapId, setFilterAnimationWindowAction({ id: filterId, animationWindow }));
|
|
277
220
|
},
|
|
221
|
+
setFilterView: (mapId, filterIdx, view) => {
|
|
222
|
+
get().kepler.registerKeplerMapIfNotExists(mapId);
|
|
223
|
+
get().kepler.dispatchAction(mapId, setFilterViewAction(filterIdx, view));
|
|
224
|
+
},
|
|
278
225
|
updateTooltipFields: (mapId, datasetId, fieldNames) => {
|
|
279
226
|
get().kepler.registerKeplerMapIfNotExists(mapId);
|
|
280
227
|
const mapState = get().kepler.map[mapId];
|
|
@@ -304,6 +251,14 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
304
251
|
},
|
|
305
252
|
}));
|
|
306
253
|
},
|
|
254
|
+
toggleSplitMap: (mapId, index) => {
|
|
255
|
+
get().kepler.registerKeplerMapIfNotExists(mapId);
|
|
256
|
+
get().kepler.dispatchAction(mapId, toggleSplitMapAction(index));
|
|
257
|
+
},
|
|
258
|
+
toggleLayerForMap: (mapId, mapIndex, layerId) => {
|
|
259
|
+
get().kepler.registerKeplerMapIfNotExists(mapId);
|
|
260
|
+
get().kepler.dispatchAction(mapId, toggleLayerForMapAction(mapIndex, layerId));
|
|
261
|
+
},
|
|
307
262
|
addTableToMap: async (mapId, tableName, options = {}, config = {}) => {
|
|
308
263
|
const connector = await get().db.getConnector();
|
|
309
264
|
const duckDbColumns = await getDuckDBColumnTypes({
|
|
@@ -326,26 +281,21 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
326
281
|
get().kepler.dispatchAction(mapId, addDataToMap({ datasets, options, config }));
|
|
327
282
|
}
|
|
328
283
|
},
|
|
329
|
-
|
|
330
|
-
return get().kepler.config.maps.find((map) => map.id === get().kepler.config.currentMapId);
|
|
331
|
-
},
|
|
332
|
-
setCurrentMapId: (mapId) => {
|
|
333
|
-
return set((state) => produce(state, (draft) => {
|
|
334
|
-
const now = Date.now();
|
|
335
|
-
draft.kepler.config.currentMapId = mapId;
|
|
336
|
-
updateMapLastOpenedAt(draft.kepler.config.maps, mapId, now);
|
|
337
|
-
}));
|
|
338
|
-
},
|
|
339
|
-
createMap: (name) => {
|
|
340
|
-
const mapId = createId();
|
|
284
|
+
ensureMap: (mapId, name) => {
|
|
341
285
|
const now = Date.now();
|
|
342
286
|
set((state) => produce(state, (draft) => {
|
|
287
|
+
const existing = draft.kepler.config.maps.find((map) => map.id === mapId);
|
|
288
|
+
if (existing) {
|
|
289
|
+
if (name && existing.name !== name) {
|
|
290
|
+
existing.name = name;
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
343
294
|
draft.kepler.config.maps.push({
|
|
344
295
|
id: mapId,
|
|
345
296
|
name: name ?? 'Untitled Map',
|
|
346
297
|
lastOpenedAt: now,
|
|
347
298
|
});
|
|
348
|
-
draft.kepler.config.openTabs.push(mapId);
|
|
349
299
|
draft.kepler.map = createKeplerReducer({
|
|
350
300
|
reason: 'create-map',
|
|
351
301
|
mapId,
|
|
@@ -353,8 +303,15 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
353
303
|
})(draft.kepler.map, registerEntry({ id: mapId }));
|
|
354
304
|
draft.kepler.forwardDispatch[mapId] = getForwardDispatch(mapId);
|
|
355
305
|
}));
|
|
306
|
+
if (!get().kepler.map[mapId]) {
|
|
307
|
+
get().kepler.registerKeplerMapIfNotExists(mapId);
|
|
308
|
+
}
|
|
356
309
|
requestMapStyle(mapId);
|
|
357
310
|
get().kepler.syncKeplerDatasets();
|
|
311
|
+
},
|
|
312
|
+
createMap: (name, options) => {
|
|
313
|
+
const mapId = options?.id ?? createId();
|
|
314
|
+
get().kepler.ensureMap(mapId, name);
|
|
358
315
|
return mapId;
|
|
359
316
|
},
|
|
360
317
|
async syncKeplerDatasets() {
|
|
@@ -409,38 +366,8 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
409
366
|
},
|
|
410
367
|
deleteMap: (mapId) => {
|
|
411
368
|
set((state) => produce(state, (draft) => {
|
|
412
|
-
const openTabs = draft.kepler.config.openTabs;
|
|
413
369
|
const maps = draft.kepler.config.maps;
|
|
414
|
-
const wasCurrentMap = draft.kepler.config.currentMapId === mapId;
|
|
415
|
-
const deletingIndex = openTabs.indexOf(mapId);
|
|
416
|
-
// Remove from maps and openTabs
|
|
417
370
|
draft.kepler.config.maps = maps.filter((map) => map.id !== mapId);
|
|
418
|
-
draft.kepler.config.openTabs = openTabs.filter((id) => id !== mapId);
|
|
419
|
-
// If we deleted the current map, select another one
|
|
420
|
-
if (wasCurrentMap) {
|
|
421
|
-
const newOpenTabs = draft.kepler.config.openTabs;
|
|
422
|
-
const remainingMaps = draft.kepler.config.maps;
|
|
423
|
-
if (newOpenTabs.length > 0) {
|
|
424
|
-
// Select from remaining open tabs
|
|
425
|
-
const newIndex = deletingIndex === 0
|
|
426
|
-
? 0
|
|
427
|
-
: Math.min(deletingIndex - 1, newOpenTabs.length - 1);
|
|
428
|
-
const newSelectedId = newOpenTabs[newIndex];
|
|
429
|
-
if (newSelectedId) {
|
|
430
|
-
draft.kepler.config.currentMapId = newSelectedId;
|
|
431
|
-
updateMapLastOpenedAt(draft.kepler.config.maps, newSelectedId, Date.now());
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
else if (remainingMaps.length > 0) {
|
|
435
|
-
// No open tabs left, open a closed map
|
|
436
|
-
const mapToOpen = remainingMaps[0];
|
|
437
|
-
if (mapToOpen) {
|
|
438
|
-
draft.kepler.config.openTabs.push(mapToOpen.id);
|
|
439
|
-
draft.kepler.config.currentMapId = mapToOpen.id;
|
|
440
|
-
updateMapLastOpenedAt(draft.kepler.config.maps, mapToOpen.id, Date.now());
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
371
|
draft.kepler.map = keplerReducer(draft.kepler.map, deleteEntry(mapId));
|
|
445
372
|
delete draft.kepler.forwardDispatch[mapId];
|
|
446
373
|
}));
|
|
@@ -468,8 +395,6 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
468
395
|
config: savedConfig,
|
|
469
396
|
lastOpenedAt: now,
|
|
470
397
|
});
|
|
471
|
-
draft.kepler.config.openTabs.push(newMapId);
|
|
472
|
-
draft.kepler.config.currentMapId = newMapId;
|
|
473
398
|
// Register the new map with empty state, then load the config
|
|
474
399
|
draft.kepler.map = createKeplerReducer({
|
|
475
400
|
reason: 'duplicate-map',
|
|
@@ -496,21 +421,6 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
496
421
|
}
|
|
497
422
|
}));
|
|
498
423
|
},
|
|
499
|
-
closeMap: (mapId) => {
|
|
500
|
-
set((state) => produce(state, (draft) => {
|
|
501
|
-
const openTabs = draft.kepler.config.openTabs;
|
|
502
|
-
// Don't close if it's the last open tab (defensive check, TabStrip also prevents this)
|
|
503
|
-
if (openTabs.length <= 1)
|
|
504
|
-
return;
|
|
505
|
-
// Just remove from openTabs - TabStrip handles selection via onSelect before calling onClose
|
|
506
|
-
draft.kepler.config.openTabs = openTabs.filter((id) => id !== mapId);
|
|
507
|
-
}));
|
|
508
|
-
},
|
|
509
|
-
setOpenTabs: (tabIds) => {
|
|
510
|
-
set((state) => produce(state, (draft) => {
|
|
511
|
-
draft.kepler.config.openTabs = tabIds;
|
|
512
|
-
}));
|
|
513
|
-
},
|
|
514
424
|
addDataToMap: (mapId, data) => {
|
|
515
425
|
get().kepler.registerKeplerMapIfNotExists(mapId);
|
|
516
426
|
get().kepler.dispatchAction(mapId, addDataToMap(data));
|
|
@@ -646,10 +556,7 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
646
556
|
}
|
|
647
557
|
function updateForwardDispatch() {
|
|
648
558
|
const config = get().kepler.config;
|
|
649
|
-
const
|
|
650
|
-
const forwardDispatch = {
|
|
651
|
-
[currentMapId]: getForwardDispatch(currentMapId),
|
|
652
|
-
};
|
|
559
|
+
const forwardDispatch = {};
|
|
653
560
|
if (config) {
|
|
654
561
|
for (const { id } of config.maps) {
|
|
655
562
|
forwardDispatch[id] = getForwardDispatch(id);
|