@sqlrooms/kepler 0.29.0-rc.1 → 0.29.0-rc.3
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 +29 -11
- package/dist/KeplerSlice.d.ts.map +1 -1
- package/dist/KeplerSlice.js +247 -112
- 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/CustomFilterManager.d.ts.map +1 -1
- package/dist/components/CustomFilterManager.js +4 -1
- package/dist/components/CustomFilterManager.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/CustomInteractionManager.d.ts.map +1 -1
- package/dist/components/CustomInteractionManager.js +4 -1
- package/dist/components/CustomInteractionManager.js.map +1 -1
- package/dist/components/CustomLayerManager.d.ts.map +1 -1
- package/dist/components/CustomLayerManager.js +138 -17
- package/dist/components/CustomLayerManager.js.map +1 -1
- package/dist/components/CustomMapDrawPanel.d.ts +8 -0
- package/dist/components/CustomMapDrawPanel.d.ts.map +1 -0
- package/dist/components/CustomMapDrawPanel.js +28 -0
- package/dist/components/CustomMapDrawPanel.js.map +1 -0
- package/dist/components/CustomMapLegend.d.ts +4 -1
- package/dist/components/CustomMapLegend.d.ts.map +1 -1
- package/dist/components/CustomMapLegend.js +15 -5
- 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/KeplerAddDataDialog.d.ts +2 -3
- package/dist/components/KeplerAddDataDialog.d.ts.map +1 -1
- package/dist/components/KeplerAddDataDialog.js +18 -8
- package/dist/components/KeplerAddDataDialog.js.map +1 -1
- package/dist/components/KeplerImageExport.d.ts.map +1 -1
- package/dist/components/KeplerImageExport.js +26 -8
- package/dist/components/KeplerImageExport.js.map +1 -1
- package/dist/components/KeplerInjector.d.ts.map +1 -1
- package/dist/components/KeplerInjector.js +3 -1
- package/dist/components/KeplerInjector.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/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/hooks/useKeplerStateActions.d.ts +7 -6
- package/dist/hooks/useKeplerStateActions.d.ts.map +1 -1
- package/dist/hooks/useKeplerStateActions.js +1 -1
- package/dist/hooks/useKeplerStateActions.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/package.json +20 -20
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
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { VectorTileDatasetMetadata } from '@kepler.gl/constants';
|
|
2
|
-
import { Layer } from '@kepler.gl/layers';
|
|
3
2
|
import { KeplerGlState } from '@kepler.gl/reducers';
|
|
4
|
-
import { AddDataToMapPayload } from '@kepler.gl/types';
|
|
3
|
+
import { AddDataToMapPayload, MinSavedLayer, ParsedLayer } from '@kepler.gl/types';
|
|
5
4
|
import { KeplerApplicationConfig } from '@kepler.gl/utils';
|
|
6
5
|
import { KeplerMapSchema, KeplerSliceConfig } from '@sqlrooms/kepler-config';
|
|
7
6
|
import { RoomShellSliceState, type StateCreator } from '@sqlrooms/room-shell';
|
|
@@ -11,9 +10,15 @@ import { ReduxLoggerOptions } from 'redux-logger';
|
|
|
11
10
|
export type KeplerGLBasicProps = {
|
|
12
11
|
mapboxApiAccessToken?: string;
|
|
13
12
|
};
|
|
13
|
+
export type CreateInitialMapKeplerStateContext = {
|
|
14
|
+
reason: 'initialize' | 'create-map' | 'duplicate-map' | 'register-map' | 'sync-config';
|
|
15
|
+
defaultInitialMapKeplerState: Partial<KeplerGlState>;
|
|
16
|
+
mapId?: string;
|
|
17
|
+
name?: string;
|
|
18
|
+
};
|
|
14
19
|
export type CreateKeplerSliceOptions = {
|
|
15
20
|
config?: Partial<KeplerSliceConfig>;
|
|
16
|
-
|
|
21
|
+
createInitialMapKeplerState?: (context: CreateInitialMapKeplerStateContext) => Partial<KeplerGlState>;
|
|
17
22
|
basicKeplerProps?: Partial<KeplerGLBasicProps>;
|
|
18
23
|
actionLogging?: boolean | ReduxLoggerOptions;
|
|
19
24
|
middlewares?: Middleware[];
|
|
@@ -49,14 +54,23 @@ export type KeplerSliceState = {
|
|
|
49
54
|
[mapId: string]: Dispatch;
|
|
50
55
|
};
|
|
51
56
|
initialize: () => Promise<void>;
|
|
57
|
+
destroy: () => Promise<void>;
|
|
52
58
|
setConfig: (config: KeplerSliceConfig) => void;
|
|
53
59
|
/**
|
|
54
60
|
* Update the datasets in all the kepler map so that they correspond to
|
|
55
61
|
* the latest table schemas in the database
|
|
56
62
|
*/
|
|
57
63
|
syncKeplerDatasets: () => Promise<void>;
|
|
58
|
-
addLayer: (mapId: string, layer:
|
|
59
|
-
|
|
64
|
+
addLayer: (mapId: string, layer: ParsedLayer | MinSavedLayer | undefined, datasetId: string) => void;
|
|
65
|
+
addFilter: (mapId: string, datasetId: string) => void;
|
|
66
|
+
setFilter: (mapId: string, filterIdx: number, prop: string | string[], value: any, valueIndex?: number) => void;
|
|
67
|
+
setFilterAnimationTime: (mapId: string, filterIdx: number, prop: string, value: any) => void;
|
|
68
|
+
setFilterAnimationWindow: (mapId: string, filterId: string, animationWindow: string) => void;
|
|
69
|
+
setFilterView: (mapId: string, filterIdx: number, view: 'side' | 'enlarged' | 'minified') => void;
|
|
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;
|
|
73
|
+
addTableToMap: (mapId: string, tableName: string, options?: AddDataToMapPayload['options'], config?: AddDataToMapPayload['config']) => Promise<void>;
|
|
60
74
|
addTileSetToMap: (mapId: string, tableName: string, tileset: {
|
|
61
75
|
name: string;
|
|
62
76
|
type: string;
|
|
@@ -65,23 +79,27 @@ export type KeplerSliceState = {
|
|
|
65
79
|
addConfigToMap: (mapId: string, config: KeplerMapSchema) => void;
|
|
66
80
|
removeDatasetFromMaps: (datasetId: string) => void;
|
|
67
81
|
dispatchAction: (mapId: string, action: KeplerAction) => void;
|
|
68
|
-
|
|
82
|
+
ensureMap: (mapId: string, name?: string) => void;
|
|
69
83
|
/**
|
|
70
84
|
* Create a new map and return the map id
|
|
71
85
|
* @param name - The name of the map
|
|
72
86
|
* @returns The map id
|
|
73
87
|
*/
|
|
74
|
-
createMap: (name?: string
|
|
88
|
+
createMap: (name?: string, options?: {
|
|
89
|
+
id?: string;
|
|
90
|
+
}) => string;
|
|
75
91
|
deleteMap: (mapId: string) => void;
|
|
92
|
+
duplicateMap: (mapId: string) => Promise<{
|
|
93
|
+
success: boolean;
|
|
94
|
+
message?: string;
|
|
95
|
+
code?: string;
|
|
96
|
+
}>;
|
|
76
97
|
renameMap: (mapId: string, name: string) => void;
|
|
77
|
-
closeMap: (mapId: string) => void;
|
|
78
|
-
setOpenTabs: (tabIds: string[]) => void;
|
|
79
|
-
getCurrentMap: () => KeplerMapSchema | undefined;
|
|
80
98
|
registerKeplerMapIfNotExists: (mapId: string) => void;
|
|
81
99
|
__reduxProviderStore: ReduxStore<KeplerGlReduxState, AnyAction> | undefined;
|
|
82
100
|
};
|
|
83
101
|
};
|
|
84
|
-
export declare function createKeplerSlice({ basicKeplerProps, config: initialConfigProps,
|
|
102
|
+
export declare function createKeplerSlice({ basicKeplerProps, config: initialConfigProps, createInitialMapKeplerState, actionLogging, middlewares: additionalMiddlewares, applicationConfig, onAction, }?: CreateKeplerSliceOptions): StateCreator<KeplerSliceState>;
|
|
85
103
|
type RoomStateWithDiscuss = RoomShellSliceState & KeplerSliceState;
|
|
86
104
|
export declare function useStoreWithKepler<T>(selector: (state: RoomStateWithDiscuss) => T): T;
|
|
87
105
|
export {};
|
|
@@ -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, addLayer as addLayerAction, deleteEntry, ActionTypes as KeplerActionTypes, registerEntry, removeDataset, requestMapStyles, 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,9 @@ 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 { KeplerSliceConfig } from '@sqlrooms/kepler-config';
|
|
10
11
|
import { createSlice, useBaseRoomShellStore, } from '@sqlrooms/room-shell';
|
|
12
|
+
import { getTheme } from '@sqlrooms/ui';
|
|
11
13
|
import { produce, setAutoFreeze } from 'immer';
|
|
12
14
|
import { taskMiddleware } from 'react-palm/tasks';
|
|
13
15
|
import { compose } from 'redux';
|
|
@@ -20,9 +22,39 @@ class DesktopKeplerTable extends KeplerTable {
|
|
|
20
22
|
return (d) => d;
|
|
21
23
|
};
|
|
22
24
|
}
|
|
25
|
+
function createDefaultMapKeplerState(resolvedTheme) {
|
|
26
|
+
return {
|
|
27
|
+
mapStyle: {
|
|
28
|
+
styleType: resolvedTheme === 'dark' ? 'dark-matter' : 'positron',
|
|
29
|
+
},
|
|
30
|
+
uiState: {
|
|
31
|
+
...INITIAL_UI_STATE,
|
|
32
|
+
currentModal: null,
|
|
33
|
+
mapControls: {
|
|
34
|
+
visibleLayers: INITIAL_UI_STATE.mapControls.visibleLayers,
|
|
35
|
+
splitMap: {
|
|
36
|
+
show: true,
|
|
37
|
+
active: false,
|
|
38
|
+
},
|
|
39
|
+
mapLegend: {
|
|
40
|
+
show: true,
|
|
41
|
+
active: false,
|
|
42
|
+
},
|
|
43
|
+
toggle3d: {
|
|
44
|
+
show: true,
|
|
45
|
+
active: false,
|
|
46
|
+
},
|
|
47
|
+
mapDraw: {
|
|
48
|
+
show: true,
|
|
49
|
+
active: false,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
23
55
|
export function createDefaultKeplerConfig(props) {
|
|
24
56
|
const mapId = createId();
|
|
25
|
-
|
|
57
|
+
const config = {
|
|
26
58
|
maps: [
|
|
27
59
|
{
|
|
28
60
|
id: mapId,
|
|
@@ -31,10 +63,9 @@ export function createDefaultKeplerConfig(props) {
|
|
|
31
63
|
lastOpenedAt: Date.now(),
|
|
32
64
|
},
|
|
33
65
|
],
|
|
34
|
-
currentMapId: mapId,
|
|
35
|
-
openTabs: [mapId],
|
|
36
66
|
...props,
|
|
37
67
|
};
|
|
68
|
+
return KeplerSliceConfig.parse(config);
|
|
38
69
|
}
|
|
39
70
|
export function hasMapId(action) {
|
|
40
71
|
return (typeof action.payload === 'object' &&
|
|
@@ -51,37 +82,38 @@ const SKIP_AUTO_SAVE_ACTIONS = [
|
|
|
51
82
|
KeplerActionTypes.UPDATE_MAP,
|
|
52
83
|
KeplerActionTypes.MOUSE_MOVE,
|
|
53
84
|
];
|
|
54
|
-
export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfigProps,
|
|
55
|
-
mapStyle: { styleType: 'positron' },
|
|
56
|
-
uiState: {
|
|
57
|
-
...INITIAL_UI_STATE,
|
|
58
|
-
currentModal: null,
|
|
59
|
-
mapControls: {
|
|
60
|
-
visibleLayers: INITIAL_UI_STATE.mapControls.visibleLayers,
|
|
61
|
-
mapLegend: {
|
|
62
|
-
show: true,
|
|
63
|
-
active: false,
|
|
64
|
-
},
|
|
65
|
-
toggle3d: {
|
|
66
|
-
show: true,
|
|
67
|
-
active: false,
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
}, actionLogging = false, middlewares: additionalMiddlewares = [], applicationConfig, onAction, } = {}) {
|
|
85
|
+
export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfigProps, createInitialMapKeplerState, actionLogging = false, middlewares: additionalMiddlewares = [], applicationConfig, onAction, } = {}) {
|
|
72
86
|
const initialConfig = createDefaultKeplerConfig(initialConfigProps);
|
|
73
87
|
initApplicationConfig({
|
|
74
88
|
table: DesktopKeplerTable,
|
|
75
89
|
...applicationConfig,
|
|
76
90
|
});
|
|
91
|
+
let syncKeplerPromise = null;
|
|
92
|
+
// When a caller arrives while a sync is already in-flight, the in-flight run
|
|
93
|
+
// may have captured a stale snapshot of db.tables/kepler.map (missing maps or
|
|
94
|
+
// tables added by createMap, duplicateMap, etc.). Setting this flag tells the
|
|
95
|
+
// active run to loop once more with a fresh snapshot after it finishes, so
|
|
96
|
+
// late mutations are never silently dropped.
|
|
97
|
+
let pendingKeplerSync = false;
|
|
77
98
|
const updateMapLastOpenedAt = (maps, mapId, now = Date.now()) => {
|
|
78
99
|
const map = maps.find((item) => item.id === mapId);
|
|
79
100
|
if (map) {
|
|
80
101
|
map.lastOpenedAt = now;
|
|
81
102
|
}
|
|
82
103
|
};
|
|
83
|
-
return createSlice((set, get) => {
|
|
84
|
-
|
|
104
|
+
return createSlice((set, get, _store) => {
|
|
105
|
+
function resolveInitialMapKeplerState(context) {
|
|
106
|
+
const resolvedTheme = getTheme();
|
|
107
|
+
const defaultInitialMapKeplerState = createDefaultMapKeplerState(resolvedTheme);
|
|
108
|
+
return (createInitialMapKeplerState?.({
|
|
109
|
+
...context,
|
|
110
|
+
defaultInitialMapKeplerState,
|
|
111
|
+
}) ?? defaultInitialMapKeplerState);
|
|
112
|
+
}
|
|
113
|
+
function createKeplerReducer(context) {
|
|
114
|
+
return keplerGlReducer.initialState(resolveInitialMapKeplerState(context));
|
|
115
|
+
}
|
|
116
|
+
const keplerReducer = createKeplerReducer({ reason: 'initialize' });
|
|
85
117
|
const middlewares = [
|
|
86
118
|
taskMiddleware,
|
|
87
119
|
saveKeplerConfigMiddleware,
|
|
@@ -116,18 +148,22 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
116
148
|
__reduxProviderStore: undefined,
|
|
117
149
|
forwardDispatch: {},
|
|
118
150
|
setConfig: (config) => {
|
|
151
|
+
const nextConfig = KeplerSliceConfig.parse(config);
|
|
119
152
|
set((state) => produce(state, (draft) => {
|
|
120
|
-
draft.kepler.config =
|
|
153
|
+
draft.kepler.config = nextConfig;
|
|
121
154
|
}));
|
|
122
155
|
updateMapReduxStates();
|
|
123
156
|
updateForwardDispatch();
|
|
124
|
-
updateMapConfigs();
|
|
125
157
|
get().kepler.syncKeplerDatasets();
|
|
158
|
+
updateMapConfigs();
|
|
126
159
|
},
|
|
127
160
|
async initialize() {
|
|
128
161
|
const config = get().kepler.config;
|
|
129
|
-
const
|
|
130
|
-
|
|
162
|
+
const keplerInitialState = config.maps.reduce((mapState, map) => createKeplerReducer({
|
|
163
|
+
reason: 'initialize',
|
|
164
|
+
mapId: map.id,
|
|
165
|
+
name: map.name,
|
|
166
|
+
})(mapState, registerEntry({ id: map.id })), {});
|
|
131
167
|
set({
|
|
132
168
|
kepler: {
|
|
133
169
|
...get().kepler,
|
|
@@ -153,15 +189,77 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
153
189
|
},
|
|
154
190
|
});
|
|
155
191
|
updateForwardDispatch();
|
|
156
|
-
updateMapConfigs();
|
|
157
192
|
await get().kepler.syncKeplerDatasets();
|
|
158
|
-
|
|
193
|
+
updateMapConfigs();
|
|
194
|
+
for (const mapId of Object.keys(get().kepler.map)) {
|
|
195
|
+
requestMapStyle(mapId);
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
async destroy() {
|
|
199
|
+
// no-op
|
|
159
200
|
},
|
|
160
201
|
addLayer: (mapId, layer, datasetId) => {
|
|
161
202
|
get().kepler.registerKeplerMapIfNotExists(mapId);
|
|
162
203
|
get().kepler.dispatchAction(mapId, addLayerAction(layer, datasetId));
|
|
163
204
|
},
|
|
164
|
-
|
|
205
|
+
addFilter: (mapId, datasetId) => {
|
|
206
|
+
get().kepler.registerKeplerMapIfNotExists(mapId);
|
|
207
|
+
get().kepler.dispatchAction(mapId, addFilterAction(datasetId));
|
|
208
|
+
},
|
|
209
|
+
setFilter: (mapId, filterIdx, prop, value, valueIndex) => {
|
|
210
|
+
get().kepler.registerKeplerMapIfNotExists(mapId);
|
|
211
|
+
get().kepler.dispatchAction(mapId, setFilterAction(filterIdx, prop, value, valueIndex));
|
|
212
|
+
},
|
|
213
|
+
setFilterAnimationTime: (mapId, filterIdx, prop, value) => {
|
|
214
|
+
get().kepler.registerKeplerMapIfNotExists(mapId);
|
|
215
|
+
get().kepler.dispatchAction(mapId, setFilterAnimationTimeAction(filterIdx, prop, value));
|
|
216
|
+
},
|
|
217
|
+
setFilterAnimationWindow: (mapId, filterId, animationWindow) => {
|
|
218
|
+
get().kepler.registerKeplerMapIfNotExists(mapId);
|
|
219
|
+
get().kepler.dispatchAction(mapId, setFilterAnimationWindowAction({ id: filterId, animationWindow }));
|
|
220
|
+
},
|
|
221
|
+
setFilterView: (mapId, filterIdx, view) => {
|
|
222
|
+
get().kepler.registerKeplerMapIfNotExists(mapId);
|
|
223
|
+
get().kepler.dispatchAction(mapId, setFilterViewAction(filterIdx, view));
|
|
224
|
+
},
|
|
225
|
+
updateTooltipFields: (mapId, datasetId, fieldNames) => {
|
|
226
|
+
get().kepler.registerKeplerMapIfNotExists(mapId);
|
|
227
|
+
const mapState = get().kepler.map[mapId];
|
|
228
|
+
const tooltipConfig = mapState?.visState?.interactionConfig?.tooltip;
|
|
229
|
+
if (!tooltipConfig)
|
|
230
|
+
return;
|
|
231
|
+
const currentFieldsToShow = tooltipConfig.config?.fieldsToShow || {};
|
|
232
|
+
const existingFields = currentFieldsToShow[datasetId] || [];
|
|
233
|
+
const existingByName = new Map(existingFields.map((field) => [
|
|
234
|
+
field.name,
|
|
235
|
+
field,
|
|
236
|
+
]));
|
|
237
|
+
const nextFields = Array.from(new Set(fieldNames)).map((name) => existingByName.get(name) ?? { name, format: null });
|
|
238
|
+
if (nextFields.length === existingFields.length &&
|
|
239
|
+
nextFields.every((field, index) => field.name === existingFields[index]?.name &&
|
|
240
|
+
field.format === existingFields[index]?.format)) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
get().kepler.dispatchAction(mapId, interactionConfigChange({
|
|
244
|
+
...tooltipConfig,
|
|
245
|
+
config: {
|
|
246
|
+
...tooltipConfig.config,
|
|
247
|
+
fieldsToShow: {
|
|
248
|
+
...currentFieldsToShow,
|
|
249
|
+
[datasetId]: nextFields,
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
}));
|
|
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
|
+
},
|
|
262
|
+
addTableToMap: async (mapId, tableName, options = {}, config = {}) => {
|
|
165
263
|
const connector = await get().db.getConnector();
|
|
166
264
|
const duckDbColumns = await getDuckDBColumnTypes({
|
|
167
265
|
query: (query) => connector.query(query).result,
|
|
@@ -180,93 +278,141 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
180
278
|
info: { label: tableName, id: tableName },
|
|
181
279
|
metadata: { tableName },
|
|
182
280
|
};
|
|
183
|
-
get().kepler.dispatchAction(mapId, addDataToMap({ datasets, options }));
|
|
281
|
+
get().kepler.dispatchAction(mapId, addDataToMap({ datasets, options, config }));
|
|
184
282
|
}
|
|
185
283
|
},
|
|
186
|
-
|
|
187
|
-
return get().kepler.config.maps.find((map) => map.id === get().kepler.config.currentMapId);
|
|
188
|
-
},
|
|
189
|
-
setCurrentMapId: (mapId) => {
|
|
190
|
-
return set((state) => produce(state, (draft) => {
|
|
191
|
-
const now = Date.now();
|
|
192
|
-
draft.kepler.config.currentMapId = mapId;
|
|
193
|
-
updateMapLastOpenedAt(draft.kepler.config.maps, mapId, now);
|
|
194
|
-
}));
|
|
195
|
-
},
|
|
196
|
-
createMap: (name) => {
|
|
197
|
-
const mapId = createId();
|
|
284
|
+
ensureMap: (mapId, name) => {
|
|
198
285
|
const now = Date.now();
|
|
199
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
|
+
}
|
|
200
294
|
draft.kepler.config.maps.push({
|
|
201
295
|
id: mapId,
|
|
202
296
|
name: name ?? 'Untitled Map',
|
|
203
297
|
lastOpenedAt: now,
|
|
204
298
|
});
|
|
205
|
-
draft.kepler.
|
|
206
|
-
|
|
299
|
+
draft.kepler.map = createKeplerReducer({
|
|
300
|
+
reason: 'create-map',
|
|
301
|
+
mapId,
|
|
302
|
+
name,
|
|
303
|
+
})(draft.kepler.map, registerEntry({ id: mapId }));
|
|
207
304
|
draft.kepler.forwardDispatch[mapId] = getForwardDispatch(mapId);
|
|
208
305
|
}));
|
|
306
|
+
if (!get().kepler.map[mapId]) {
|
|
307
|
+
get().kepler.registerKeplerMapIfNotExists(mapId);
|
|
308
|
+
}
|
|
209
309
|
requestMapStyle(mapId);
|
|
210
310
|
get().kepler.syncKeplerDatasets();
|
|
311
|
+
},
|
|
312
|
+
createMap: (name, options) => {
|
|
313
|
+
const mapId = options?.id ?? createId();
|
|
314
|
+
get().kepler.ensureMap(mapId, name);
|
|
211
315
|
return mapId;
|
|
212
316
|
},
|
|
213
317
|
async syncKeplerDatasets() {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
318
|
+
if (syncKeplerPromise) {
|
|
319
|
+
pendingKeplerSync = true;
|
|
320
|
+
return syncKeplerPromise;
|
|
321
|
+
}
|
|
322
|
+
syncKeplerPromise = (async () => {
|
|
323
|
+
// Loop until no new sync was requested during the current pass.
|
|
324
|
+
// Each iteration reads a fresh snapshot of db.tables and kepler.map,
|
|
325
|
+
// so tables/maps added concurrently are picked up on the next pass.
|
|
326
|
+
do {
|
|
327
|
+
pendingKeplerSync = false;
|
|
328
|
+
for (const mapId of Object.keys(get().kepler.map)) {
|
|
329
|
+
const mapState = get().kepler.map[mapId];
|
|
330
|
+
if (!mapState)
|
|
331
|
+
continue;
|
|
332
|
+
const keplerDatasets = mapState.visState.datasets;
|
|
333
|
+
// Only sync tables that are referenced by existing layers or filters
|
|
334
|
+
const referencedDataIds = new Set();
|
|
335
|
+
for (const layer of mapState.visState.layers ?? []) {
|
|
336
|
+
if (layer.config.dataId) {
|
|
337
|
+
referencedDataIds.add(layer.config.dataId);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
for (const filter of mapState.visState.filters ?? []) {
|
|
341
|
+
for (const dataId of filter.dataId ?? []) {
|
|
342
|
+
referencedDataIds.add(dataId);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const availableTables = new Set(get()
|
|
346
|
+
.db.tables.filter((t) => t.table.schema === 'main')
|
|
347
|
+
.map((t) => t.table.table));
|
|
348
|
+
for (const dataId of referencedDataIds) {
|
|
349
|
+
if (!keplerDatasets?.[dataId] &&
|
|
350
|
+
availableTables.has(dataId)) {
|
|
351
|
+
await get().kepler.addTableToMap(mapId, dataId, {
|
|
352
|
+
autoCreateLayers: false,
|
|
353
|
+
centerMap: false,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
228
357
|
}
|
|
229
|
-
}
|
|
358
|
+
} while (pendingKeplerSync);
|
|
359
|
+
})();
|
|
360
|
+
try {
|
|
361
|
+
await syncKeplerPromise;
|
|
362
|
+
}
|
|
363
|
+
finally {
|
|
364
|
+
syncKeplerPromise = null;
|
|
230
365
|
}
|
|
231
366
|
},
|
|
232
367
|
deleteMap: (mapId) => {
|
|
233
368
|
set((state) => produce(state, (draft) => {
|
|
234
|
-
const openTabs = draft.kepler.config.openTabs;
|
|
235
369
|
const maps = draft.kepler.config.maps;
|
|
236
|
-
const wasCurrentMap = draft.kepler.config.currentMapId === mapId;
|
|
237
|
-
const deletingIndex = openTabs.indexOf(mapId);
|
|
238
|
-
// Remove from maps and openTabs
|
|
239
370
|
draft.kepler.config.maps = maps.filter((map) => map.id !== mapId);
|
|
240
|
-
draft.kepler.config.openTabs = openTabs.filter((id) => id !== mapId);
|
|
241
|
-
// If we deleted the current map, select another one
|
|
242
|
-
if (wasCurrentMap) {
|
|
243
|
-
const newOpenTabs = draft.kepler.config.openTabs;
|
|
244
|
-
const remainingMaps = draft.kepler.config.maps;
|
|
245
|
-
if (newOpenTabs.length > 0) {
|
|
246
|
-
// Select from remaining open tabs
|
|
247
|
-
const newIndex = deletingIndex === 0
|
|
248
|
-
? 0
|
|
249
|
-
: Math.min(deletingIndex - 1, newOpenTabs.length - 1);
|
|
250
|
-
const newSelectedId = newOpenTabs[newIndex];
|
|
251
|
-
if (newSelectedId) {
|
|
252
|
-
draft.kepler.config.currentMapId = newSelectedId;
|
|
253
|
-
updateMapLastOpenedAt(draft.kepler.config.maps, newSelectedId, Date.now());
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
else if (remainingMaps.length > 0) {
|
|
257
|
-
// No open tabs left, open a closed map
|
|
258
|
-
const mapToOpen = remainingMaps[0];
|
|
259
|
-
if (mapToOpen) {
|
|
260
|
-
draft.kepler.config.openTabs.push(mapToOpen.id);
|
|
261
|
-
draft.kepler.config.currentMapId = mapToOpen.id;
|
|
262
|
-
updateMapLastOpenedAt(draft.kepler.config.maps, mapToOpen.id, Date.now());
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
371
|
draft.kepler.map = keplerReducer(draft.kepler.map, deleteEntry(mapId));
|
|
267
372
|
delete draft.kepler.forwardDispatch[mapId];
|
|
268
373
|
}));
|
|
269
374
|
},
|
|
375
|
+
duplicateMap: async (mapId) => {
|
|
376
|
+
// Ensure the map's redux state is registered, consistent with other kepler actions
|
|
377
|
+
get().kepler.registerKeplerMapIfNotExists(mapId);
|
|
378
|
+
const sourceMap = get().kepler.config.maps.find((m) => m.id === mapId);
|
|
379
|
+
const sourceMapState = get().kepler.map[mapId];
|
|
380
|
+
if (!sourceMap || !sourceMapState) {
|
|
381
|
+
return {
|
|
382
|
+
success: false,
|
|
383
|
+
message: 'Unable to duplicate map: source map or state not found',
|
|
384
|
+
code: 'source-map-not-found',
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
const newMapId = createId();
|
|
388
|
+
const now = Date.now();
|
|
389
|
+
// Save the source map state using Kepler's schema manager
|
|
390
|
+
const savedConfig = KeplerGLSchemaManager.getConfigToSave(sourceMapState);
|
|
391
|
+
set((state) => produce(state, (draft) => {
|
|
392
|
+
draft.kepler.config.maps.push({
|
|
393
|
+
id: newMapId,
|
|
394
|
+
name: `Copy of ${sourceMap.name}`,
|
|
395
|
+
config: savedConfig,
|
|
396
|
+
lastOpenedAt: now,
|
|
397
|
+
});
|
|
398
|
+
// Register the new map with empty state, then load the config
|
|
399
|
+
draft.kepler.map = createKeplerReducer({
|
|
400
|
+
reason: 'duplicate-map',
|
|
401
|
+
mapId: newMapId,
|
|
402
|
+
name: `Copy of ${sourceMap.name}`,
|
|
403
|
+
})(draft.kepler.map, registerEntry({ id: newMapId }));
|
|
404
|
+
draft.kepler.forwardDispatch[newMapId] =
|
|
405
|
+
getForwardDispatch(newMapId);
|
|
406
|
+
}));
|
|
407
|
+
// Load the saved config into the new map
|
|
408
|
+
get().kepler.addConfigToMap(newMapId, savedConfig);
|
|
409
|
+
requestMapStyle(newMapId);
|
|
410
|
+
get().kepler.syncKeplerDatasets();
|
|
411
|
+
return {
|
|
412
|
+
success: true,
|
|
413
|
+
message: 'Map duplicated successfully',
|
|
414
|
+
};
|
|
415
|
+
},
|
|
270
416
|
renameMap: (mapId, name) => {
|
|
271
417
|
set((state) => produce(state, (draft) => {
|
|
272
418
|
const map = draft.kepler.config.maps.find((map) => map.id === mapId);
|
|
@@ -275,21 +421,6 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
275
421
|
}
|
|
276
422
|
}));
|
|
277
423
|
},
|
|
278
|
-
closeMap: (mapId) => {
|
|
279
|
-
set((state) => produce(state, (draft) => {
|
|
280
|
-
const openTabs = draft.kepler.config.openTabs;
|
|
281
|
-
// Don't close if it's the last open tab (defensive check, TabStrip also prevents this)
|
|
282
|
-
if (openTabs.length <= 1)
|
|
283
|
-
return;
|
|
284
|
-
// Just remove from openTabs - TabStrip handles selection via onSelect before calling onClose
|
|
285
|
-
draft.kepler.config.openTabs = openTabs.filter((id) => id !== mapId);
|
|
286
|
-
}));
|
|
287
|
-
},
|
|
288
|
-
setOpenTabs: (tabIds) => {
|
|
289
|
-
set((state) => produce(state, (draft) => {
|
|
290
|
-
draft.kepler.config.openTabs = tabIds;
|
|
291
|
-
}));
|
|
292
|
-
},
|
|
293
424
|
addDataToMap: (mapId, data) => {
|
|
294
425
|
get().kepler.registerKeplerMapIfNotExists(mapId);
|
|
295
426
|
get().kepler.dispatchAction(mapId, addDataToMap(data));
|
|
@@ -343,7 +474,10 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
343
474
|
set({
|
|
344
475
|
kepler: {
|
|
345
476
|
...get().kepler,
|
|
346
|
-
map:
|
|
477
|
+
map: createKeplerReducer({
|
|
478
|
+
reason: 'register-map',
|
|
479
|
+
mapId,
|
|
480
|
+
})(get().kepler.map, registerEntry({ id: mapId })),
|
|
347
481
|
forwardDispatch: {
|
|
348
482
|
...get().kepler.forwardDispatch,
|
|
349
483
|
[mapId]: getForwardDispatch(mapId),
|
|
@@ -410,7 +544,11 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
410
544
|
// Register redux state of maps that are not in the config
|
|
411
545
|
for (const map of draft.kepler.config.maps) {
|
|
412
546
|
if (!draft.kepler.map[map.id]) {
|
|
413
|
-
draft.kepler.map =
|
|
547
|
+
draft.kepler.map = createKeplerReducer({
|
|
548
|
+
reason: 'sync-config',
|
|
549
|
+
mapId: map.id,
|
|
550
|
+
name: map.name,
|
|
551
|
+
})(draft.kepler.map, registerEntry({ id: map.id }));
|
|
414
552
|
draft.kepler.forwardDispatch[map.id] = getForwardDispatch(map.id);
|
|
415
553
|
}
|
|
416
554
|
}
|
|
@@ -418,10 +556,7 @@ export function createKeplerSlice({ basicKeplerProps = {}, config: initialConfig
|
|
|
418
556
|
}
|
|
419
557
|
function updateForwardDispatch() {
|
|
420
558
|
const config = get().kepler.config;
|
|
421
|
-
const
|
|
422
|
-
const forwardDispatch = {
|
|
423
|
-
[currentMapId]: getForwardDispatch(currentMapId),
|
|
424
|
-
};
|
|
559
|
+
const forwardDispatch = {};
|
|
425
560
|
if (config) {
|
|
426
561
|
for (const { id } of config.maps) {
|
|
427
562
|
forwardDispatch[id] = getForwardDispatch(id);
|