@ttoss/geovis-workspace 0.1.0
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/LICENSE +21 -0
- package/README.md +154 -0
- package/dist/index.cjs +691 -0
- package/dist/index.d.cts +164 -0
- package/dist/index.d.mts +164 -0
- package/dist/index.mjs +674 -0
- package/package.json +62 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
|
+
import { GeoVisCanvas, GeoVisProvider } from "@ttoss/geovis";
|
|
3
|
+
import { Box, Flex, Heading, IconButton, Link, Text } from "@ttoss/ui";
|
|
4
|
+
import { defineMessages, useI18n } from "@ttoss/react-i18n";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
|
|
8
|
+
//#region src/context/GeovisWorkspaceContext.ts
|
|
9
|
+
var GeovisWorkspaceContext = React.createContext(void 0);
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/hooks/useGeovisWorkspace.ts
|
|
13
|
+
/**
|
|
14
|
+
* Consumes the GeovisWorkspace context.
|
|
15
|
+
* Must be used inside a GeovisWorkspaceProvider.
|
|
16
|
+
*/
|
|
17
|
+
var useGeovisWorkspace = () => {
|
|
18
|
+
const context = React.useContext(GeovisWorkspaceContext);
|
|
19
|
+
if (!context) throw new Error("useGeovisWorkspace must be used within a GeovisWorkspaceProvider");
|
|
20
|
+
return context;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/messages.ts
|
|
25
|
+
var messages = defineMessages({
|
|
26
|
+
detailsTitle: {
|
|
27
|
+
id: "35Dt1p",
|
|
28
|
+
defaultMessage: [{
|
|
29
|
+
"type": 0,
|
|
30
|
+
"value": "Details"
|
|
31
|
+
}]
|
|
32
|
+
},
|
|
33
|
+
noSelection: {
|
|
34
|
+
id: "cculTE",
|
|
35
|
+
defaultMessage: [{
|
|
36
|
+
"type": 0,
|
|
37
|
+
"value": "Select an item to view details."
|
|
38
|
+
}]
|
|
39
|
+
},
|
|
40
|
+
openMenu: {
|
|
41
|
+
id: "ebJRt+",
|
|
42
|
+
defaultMessage: [{
|
|
43
|
+
"type": 0,
|
|
44
|
+
"value": "Open menu"
|
|
45
|
+
}]
|
|
46
|
+
},
|
|
47
|
+
closeMenu: {
|
|
48
|
+
id: "ncOMIu",
|
|
49
|
+
defaultMessage: [{
|
|
50
|
+
"type": 0,
|
|
51
|
+
"value": "Close menu"
|
|
52
|
+
}]
|
|
53
|
+
},
|
|
54
|
+
openDetails: {
|
|
55
|
+
id: "IU+fm9",
|
|
56
|
+
defaultMessage: [{
|
|
57
|
+
"type": 0,
|
|
58
|
+
"value": "Open details"
|
|
59
|
+
}]
|
|
60
|
+
},
|
|
61
|
+
closeDetails: {
|
|
62
|
+
id: "j3dtho",
|
|
63
|
+
defaultMessage: [{
|
|
64
|
+
"type": 0,
|
|
65
|
+
"value": "Close details"
|
|
66
|
+
}]
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/components/MenuButton.tsx
|
|
72
|
+
/**
|
|
73
|
+
* Internal full-width menu item used inside the left sidebar groups.
|
|
74
|
+
* Highlights itself when `active` is true.
|
|
75
|
+
*/
|
|
76
|
+
var MenuButton = ({
|
|
77
|
+
label,
|
|
78
|
+
active,
|
|
79
|
+
onClick
|
|
80
|
+
}) => {
|
|
81
|
+
return /* @__PURE__ */jsx(Box, {
|
|
82
|
+
as: "button",
|
|
83
|
+
type: "button",
|
|
84
|
+
"aria-pressed": active,
|
|
85
|
+
"data-active": active ? "" : void 0,
|
|
86
|
+
onClick,
|
|
87
|
+
sx: {
|
|
88
|
+
display: "block",
|
|
89
|
+
width: "100%",
|
|
90
|
+
marginBottom: "2px",
|
|
91
|
+
paddingBlock: "7px",
|
|
92
|
+
paddingInline: "10px",
|
|
93
|
+
border: "none",
|
|
94
|
+
borderRadius: "6px",
|
|
95
|
+
cursor: "pointer",
|
|
96
|
+
textAlign: "left",
|
|
97
|
+
fontFamily: "body",
|
|
98
|
+
fontSize: "14px",
|
|
99
|
+
lineHeight: "1.4",
|
|
100
|
+
letterSpacing: "0.01em",
|
|
101
|
+
fontWeight: active ? 600 : 500,
|
|
102
|
+
color: active ? "#4338ca" : "#374151",
|
|
103
|
+
backgroundColor: active ? "#eef2ff" : "transparent",
|
|
104
|
+
transition: "background-color 0.15s ease, color 0.15s ease",
|
|
105
|
+
"&:hover": {
|
|
106
|
+
backgroundColor: active ? "#e0e7ff" : "#f3f4f6",
|
|
107
|
+
color: "#4338ca"
|
|
108
|
+
},
|
|
109
|
+
"&:focus-visible": {
|
|
110
|
+
outline: "2px solid #6366f1",
|
|
111
|
+
outlineOffset: "1px"
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
children: label
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/components/LeftSidebar.tsx
|
|
120
|
+
/**
|
|
121
|
+
* Internal left sidebar that renders the menu groups defined in the config.
|
|
122
|
+
* Reads and writes the per-group selection via GeovisWorkspaceContext.
|
|
123
|
+
*/
|
|
124
|
+
var LeftSidebar = () => {
|
|
125
|
+
const {
|
|
126
|
+
intl: {
|
|
127
|
+
formatMessage
|
|
128
|
+
}
|
|
129
|
+
} = useI18n();
|
|
130
|
+
const {
|
|
131
|
+
config,
|
|
132
|
+
selection,
|
|
133
|
+
setSelection,
|
|
134
|
+
setLeftSidebarOpen
|
|
135
|
+
} = useGeovisWorkspace();
|
|
136
|
+
const menus = config.leftSidebar?.menus ?? [];
|
|
137
|
+
return /* @__PURE__ */jsxs(Flex, {
|
|
138
|
+
sx: {
|
|
139
|
+
position: "relative",
|
|
140
|
+
flexDirection: "column",
|
|
141
|
+
gap: "5",
|
|
142
|
+
width: "256px",
|
|
143
|
+
height: "100%",
|
|
144
|
+
flexShrink: 0,
|
|
145
|
+
paddingX: "4",
|
|
146
|
+
paddingTop: "5",
|
|
147
|
+
paddingBottom: "4",
|
|
148
|
+
backgroundColor: "#ffffff",
|
|
149
|
+
borderRight: "1px solid #e5e7eb",
|
|
150
|
+
overflowY: "auto"
|
|
151
|
+
},
|
|
152
|
+
children: [/* @__PURE__ */jsx(IconButton, {
|
|
153
|
+
icon: "lucide:chevron-left",
|
|
154
|
+
"aria-label": formatMessage(messages.closeMenu),
|
|
155
|
+
onClick: event => {
|
|
156
|
+
event.currentTarget.blur();
|
|
157
|
+
setLeftSidebarOpen({
|
|
158
|
+
open: false
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
sx: {
|
|
162
|
+
position: "absolute",
|
|
163
|
+
top: "3",
|
|
164
|
+
right: "3",
|
|
165
|
+
color: "#6b7280",
|
|
166
|
+
backgroundColor: "transparent",
|
|
167
|
+
borderRadius: "md",
|
|
168
|
+
"&:hover": {
|
|
169
|
+
color: "#4338ca"
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}), menus.map(menu => {
|
|
173
|
+
return /* @__PURE__ */jsxs(Box, {
|
|
174
|
+
sx: {
|
|
175
|
+
display: "flex",
|
|
176
|
+
flexDirection: "column"
|
|
177
|
+
},
|
|
178
|
+
children: [/* @__PURE__ */jsx(Text, {
|
|
179
|
+
sx: {
|
|
180
|
+
fontSize: "xs",
|
|
181
|
+
fontWeight: "semibold",
|
|
182
|
+
color: "#6b7280",
|
|
183
|
+
textTransform: "uppercase",
|
|
184
|
+
letterSpacing: "0.08em",
|
|
185
|
+
marginBottom: "2"
|
|
186
|
+
},
|
|
187
|
+
children: menu.title
|
|
188
|
+
}), menu.items.map(item => {
|
|
189
|
+
return /* @__PURE__ */jsx(MenuButton, {
|
|
190
|
+
label: item.label,
|
|
191
|
+
active: selection[menu.id] === item.value,
|
|
192
|
+
onClick: () => {
|
|
193
|
+
setSelection({
|
|
194
|
+
menuId: menu.id,
|
|
195
|
+
value: item.value
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}, item.value);
|
|
199
|
+
})]
|
|
200
|
+
}, menu.id);
|
|
201
|
+
})]
|
|
202
|
+
});
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region src/components/RightSidebar.tsx
|
|
207
|
+
/** Renders one data-source entry, as an external link when `href` is set. */
|
|
208
|
+
var SourceItem = ({
|
|
209
|
+
label,
|
|
210
|
+
href
|
|
211
|
+
}) => {
|
|
212
|
+
return /* @__PURE__ */jsx(Box, {
|
|
213
|
+
as: "li",
|
|
214
|
+
sx: {
|
|
215
|
+
fontSize: "xs",
|
|
216
|
+
color: "#6b7280",
|
|
217
|
+
lineHeight: "base"
|
|
218
|
+
},
|
|
219
|
+
children: href ? /* @__PURE__ */jsx(Link, {
|
|
220
|
+
href,
|
|
221
|
+
target: "_blank",
|
|
222
|
+
rel: "noopener noreferrer",
|
|
223
|
+
sx: {
|
|
224
|
+
color: "#4338ca",
|
|
225
|
+
textDecoration: "underline"
|
|
226
|
+
},
|
|
227
|
+
children: label
|
|
228
|
+
}) : label
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
/**
|
|
232
|
+
* Color legend panel driven by `rightSidebar.legendWithColor`: an optional
|
|
233
|
+
* description, a swatch-per-class legend and a list of data sources. Each block
|
|
234
|
+
* renders only when present in the spec.
|
|
235
|
+
*/
|
|
236
|
+
var LegendWithColorPanel = ({
|
|
237
|
+
description,
|
|
238
|
+
legend,
|
|
239
|
+
sources
|
|
240
|
+
}) => {
|
|
241
|
+
return /* @__PURE__ */jsxs(Flex, {
|
|
242
|
+
sx: {
|
|
243
|
+
flexDirection: "column",
|
|
244
|
+
gap: "4"
|
|
245
|
+
},
|
|
246
|
+
children: [description && /* @__PURE__ */jsx(Text, {
|
|
247
|
+
sx: {
|
|
248
|
+
fontSize: "sm",
|
|
249
|
+
color: "#374151",
|
|
250
|
+
lineHeight: "base"
|
|
251
|
+
},
|
|
252
|
+
children: description
|
|
253
|
+
}), legend && /* @__PURE__ */jsxs(Flex, {
|
|
254
|
+
sx: {
|
|
255
|
+
flexDirection: "column",
|
|
256
|
+
gap: "2"
|
|
257
|
+
},
|
|
258
|
+
children: [legend.title && /* @__PURE__ */jsx(Text, {
|
|
259
|
+
sx: {
|
|
260
|
+
fontSize: "xs",
|
|
261
|
+
fontWeight: "semibold",
|
|
262
|
+
textTransform: "uppercase",
|
|
263
|
+
letterSpacing: "0.08em",
|
|
264
|
+
color: "#6b7280"
|
|
265
|
+
},
|
|
266
|
+
children: legend.title
|
|
267
|
+
}), /* @__PURE__ */jsx(Flex, {
|
|
268
|
+
sx: {
|
|
269
|
+
flexDirection: "column",
|
|
270
|
+
gap: "1"
|
|
271
|
+
},
|
|
272
|
+
children: legend.items.map(item => {
|
|
273
|
+
return /* @__PURE__ */jsxs(Flex, {
|
|
274
|
+
sx: {
|
|
275
|
+
alignItems: "center",
|
|
276
|
+
gap: "2"
|
|
277
|
+
},
|
|
278
|
+
children: [/* @__PURE__ */jsx(Box, {
|
|
279
|
+
sx: {
|
|
280
|
+
width: "20px",
|
|
281
|
+
height: "14px",
|
|
282
|
+
borderRadius: "2px",
|
|
283
|
+
flexShrink: 0,
|
|
284
|
+
backgroundColor: item.color
|
|
285
|
+
}
|
|
286
|
+
}), /* @__PURE__ */jsx(Text, {
|
|
287
|
+
sx: {
|
|
288
|
+
fontSize: "xs",
|
|
289
|
+
color: "#374151"
|
|
290
|
+
},
|
|
291
|
+
children: item.label
|
|
292
|
+
})]
|
|
293
|
+
}, item.label);
|
|
294
|
+
})
|
|
295
|
+
})]
|
|
296
|
+
}), sources && /* @__PURE__ */jsxs(Box, {
|
|
297
|
+
children: [sources.title && /* @__PURE__ */jsx(Text, {
|
|
298
|
+
sx: {
|
|
299
|
+
fontSize: "sm",
|
|
300
|
+
fontWeight: "semibold",
|
|
301
|
+
color: "#6b7280"
|
|
302
|
+
},
|
|
303
|
+
children: sources.title
|
|
304
|
+
}), /* @__PURE__ */jsx(Box, {
|
|
305
|
+
as: "ul",
|
|
306
|
+
sx: {
|
|
307
|
+
paddingLeft: "4",
|
|
308
|
+
marginTop: "1",
|
|
309
|
+
display: "flex",
|
|
310
|
+
flexDirection: "column",
|
|
311
|
+
gap: "1"
|
|
312
|
+
},
|
|
313
|
+
children: sources.items.map(source => {
|
|
314
|
+
return /* @__PURE__ */jsx(SourceItem, {
|
|
315
|
+
...source
|
|
316
|
+
}, source.label);
|
|
317
|
+
})
|
|
318
|
+
})]
|
|
319
|
+
})]
|
|
320
|
+
});
|
|
321
|
+
};
|
|
322
|
+
/**
|
|
323
|
+
* Internal right sidebar. Shows the config-defined title and an optional color
|
|
324
|
+
* legend panel. Rendered only when the config defines a rightSidebar.
|
|
325
|
+
*/
|
|
326
|
+
var RightSidebar = () => {
|
|
327
|
+
const {
|
|
328
|
+
intl: {
|
|
329
|
+
formatMessage
|
|
330
|
+
}
|
|
331
|
+
} = useI18n();
|
|
332
|
+
const {
|
|
333
|
+
config,
|
|
334
|
+
setRightSidebarOpen
|
|
335
|
+
} = useGeovisWorkspace();
|
|
336
|
+
const legendWithColor = config.rightSidebar?.legendWithColor;
|
|
337
|
+
return /* @__PURE__ */jsxs(Flex, {
|
|
338
|
+
sx: {
|
|
339
|
+
position: "relative",
|
|
340
|
+
flexDirection: "column",
|
|
341
|
+
gap: "4",
|
|
342
|
+
width: "256px",
|
|
343
|
+
height: "100%",
|
|
344
|
+
flexShrink: 0,
|
|
345
|
+
paddingX: "4",
|
|
346
|
+
paddingTop: "5",
|
|
347
|
+
paddingBottom: "4",
|
|
348
|
+
backgroundColor: "#ffffff",
|
|
349
|
+
borderLeft: "1px solid #e5e7eb",
|
|
350
|
+
overflowY: "auto"
|
|
351
|
+
},
|
|
352
|
+
children: [/* @__PURE__ */jsx(IconButton, {
|
|
353
|
+
icon: "lucide:chevron-right",
|
|
354
|
+
"aria-label": formatMessage(messages.closeDetails),
|
|
355
|
+
onClick: event => {
|
|
356
|
+
event.currentTarget.blur();
|
|
357
|
+
setRightSidebarOpen({
|
|
358
|
+
open: false
|
|
359
|
+
});
|
|
360
|
+
},
|
|
361
|
+
sx: {
|
|
362
|
+
position: "absolute",
|
|
363
|
+
top: "3",
|
|
364
|
+
right: "3",
|
|
365
|
+
color: "#6b7280",
|
|
366
|
+
backgroundColor: "transparent",
|
|
367
|
+
borderRadius: "md",
|
|
368
|
+
"&:hover": {
|
|
369
|
+
color: "#4338ca"
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}), /* @__PURE__ */jsx(Heading, {
|
|
373
|
+
as: "h3",
|
|
374
|
+
sx: {
|
|
375
|
+
margin: 0,
|
|
376
|
+
fontSize: "xs",
|
|
377
|
+
fontWeight: "semibold",
|
|
378
|
+
textTransform: "uppercase",
|
|
379
|
+
letterSpacing: "0.08em",
|
|
380
|
+
color: "#6b7280"
|
|
381
|
+
},
|
|
382
|
+
children: config.rightSidebar?.title ?? formatMessage(messages.detailsTitle)
|
|
383
|
+
}), legendWithColor && /* @__PURE__ */jsx(LegendWithColorPanel, {
|
|
384
|
+
...legendWithColor
|
|
385
|
+
})]
|
|
386
|
+
});
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
//#endregion
|
|
390
|
+
//#region src/components/Layout.tsx
|
|
391
|
+
/**
|
|
392
|
+
* Slide-in overlay that hosts a sidebar on the given side. The sidebar fills
|
|
393
|
+
* the height and is positioned absolutely so it does not push the children.
|
|
394
|
+
*/
|
|
395
|
+
var SidebarOverlay = ({
|
|
396
|
+
side,
|
|
397
|
+
open,
|
|
398
|
+
children
|
|
399
|
+
}) => {
|
|
400
|
+
const hiddenTransform = side === "left" ? "translateX(-100%)" : "translateX(100%)";
|
|
401
|
+
return /* @__PURE__ */jsx(Box, {
|
|
402
|
+
"aria-hidden": !open,
|
|
403
|
+
sx: {
|
|
404
|
+
position: "absolute",
|
|
405
|
+
top: 0,
|
|
406
|
+
bottom: 0,
|
|
407
|
+
[side]: 0,
|
|
408
|
+
zIndex: 2,
|
|
409
|
+
transform: open ? "translateX(0)" : hiddenTransform,
|
|
410
|
+
opacity: open ? 1 : 0,
|
|
411
|
+
visibility: open ? "visible" : "hidden",
|
|
412
|
+
boxShadow: open ? "lg" : "none",
|
|
413
|
+
transition: "transform 0.25s ease-in-out, opacity 0.2s ease-in-out, visibility 0.25s ease-in-out, box-shadow 0.25s ease-in-out"
|
|
414
|
+
},
|
|
415
|
+
children
|
|
416
|
+
});
|
|
417
|
+
};
|
|
418
|
+
/**
|
|
419
|
+
* Floating button that opens the left sidebar. Hidden while it is open.
|
|
420
|
+
*/
|
|
421
|
+
var OpenLeftSidebarButton = () => {
|
|
422
|
+
const {
|
|
423
|
+
intl: {
|
|
424
|
+
formatMessage
|
|
425
|
+
}
|
|
426
|
+
} = useI18n();
|
|
427
|
+
const {
|
|
428
|
+
isLeftSidebarOpen,
|
|
429
|
+
setLeftSidebarOpen
|
|
430
|
+
} = useGeovisWorkspace();
|
|
431
|
+
return /* @__PURE__ */jsx(IconButton, {
|
|
432
|
+
icon: "lucide:sliders-horizontal",
|
|
433
|
+
"aria-label": formatMessage(messages.openMenu),
|
|
434
|
+
onClick: event => {
|
|
435
|
+
event.currentTarget.blur();
|
|
436
|
+
setLeftSidebarOpen({
|
|
437
|
+
open: true
|
|
438
|
+
});
|
|
439
|
+
},
|
|
440
|
+
"aria-hidden": isLeftSidebarOpen,
|
|
441
|
+
tabIndex: isLeftSidebarOpen ? -1 : 0,
|
|
442
|
+
sx: {
|
|
443
|
+
position: "absolute",
|
|
444
|
+
top: "4",
|
|
445
|
+
left: "4",
|
|
446
|
+
zIndex: 1,
|
|
447
|
+
color: "display.text.primary.default",
|
|
448
|
+
backgroundColor: "display.background.primary.default",
|
|
449
|
+
border: "sm",
|
|
450
|
+
borderColor: "display.border.muted.default",
|
|
451
|
+
boxShadow: "md",
|
|
452
|
+
opacity: isLeftSidebarOpen ? 0 : 1,
|
|
453
|
+
visibility: isLeftSidebarOpen ? "hidden" : "visible",
|
|
454
|
+
pointerEvents: isLeftSidebarOpen ? "none" : "auto",
|
|
455
|
+
transition: "opacity 0.2s ease-in-out, visibility 0.2s ease-in-out",
|
|
456
|
+
"&:hover": {
|
|
457
|
+
backgroundColor: "display.background.secondary.default"
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
};
|
|
462
|
+
/**
|
|
463
|
+
* Floating button that opens the right sidebar. Sits vertically centered on
|
|
464
|
+
* the right edge and is hidden while the sidebar is open.
|
|
465
|
+
*/
|
|
466
|
+
var OpenRightSidebarButton = () => {
|
|
467
|
+
const {
|
|
468
|
+
intl: {
|
|
469
|
+
formatMessage
|
|
470
|
+
}
|
|
471
|
+
} = useI18n();
|
|
472
|
+
const {
|
|
473
|
+
isRightSidebarOpen,
|
|
474
|
+
setRightSidebarOpen
|
|
475
|
+
} = useGeovisWorkspace();
|
|
476
|
+
return /* @__PURE__ */jsx(IconButton, {
|
|
477
|
+
icon: "lucide:chevrons-left",
|
|
478
|
+
"aria-label": formatMessage(messages.openDetails),
|
|
479
|
+
onClick: event => {
|
|
480
|
+
event.currentTarget.blur();
|
|
481
|
+
setRightSidebarOpen({
|
|
482
|
+
open: true
|
|
483
|
+
});
|
|
484
|
+
},
|
|
485
|
+
"aria-hidden": isRightSidebarOpen,
|
|
486
|
+
tabIndex: isRightSidebarOpen ? -1 : 0,
|
|
487
|
+
sx: {
|
|
488
|
+
position: "absolute",
|
|
489
|
+
top: "50%",
|
|
490
|
+
right: 0,
|
|
491
|
+
transform: "translateY(-50%)",
|
|
492
|
+
zIndex: 1,
|
|
493
|
+
borderTopLeftRadius: "md",
|
|
494
|
+
borderBottomLeftRadius: "md",
|
|
495
|
+
borderTopRightRadius: 0,
|
|
496
|
+
borderBottomRightRadius: 0,
|
|
497
|
+
color: "display.text.primary.default",
|
|
498
|
+
backgroundColor: "display.background.primary.default",
|
|
499
|
+
border: "sm",
|
|
500
|
+
borderColor: "display.border.muted.default",
|
|
501
|
+
boxShadow: "md",
|
|
502
|
+
opacity: isRightSidebarOpen ? 0 : 1,
|
|
503
|
+
visibility: isRightSidebarOpen ? "hidden" : "visible",
|
|
504
|
+
pointerEvents: isRightSidebarOpen ? "none" : "auto",
|
|
505
|
+
transition: "opacity 0.2s ease-in-out, visibility 0.2s ease-in-out",
|
|
506
|
+
"&:hover": {
|
|
507
|
+
backgroundColor: "display.background.secondary.default"
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
};
|
|
512
|
+
/**
|
|
513
|
+
* Internal layout shell. The children fill the whole area, and each sidebar
|
|
514
|
+
* (and its floating reopen button) is rendered only when the corresponding
|
|
515
|
+
* section is defined in the spec.
|
|
516
|
+
*/
|
|
517
|
+
var Layout = ({
|
|
518
|
+
children
|
|
519
|
+
}) => {
|
|
520
|
+
const {
|
|
521
|
+
config,
|
|
522
|
+
isLeftSidebarOpen,
|
|
523
|
+
isRightSidebarOpen
|
|
524
|
+
} = useGeovisWorkspace();
|
|
525
|
+
const hasLeftSidebar = config.leftSidebar !== void 0;
|
|
526
|
+
const hasRightSidebar = config.rightSidebar !== void 0;
|
|
527
|
+
return /* @__PURE__ */jsxs(Flex, {
|
|
528
|
+
sx: {
|
|
529
|
+
position: "relative",
|
|
530
|
+
overflow: "hidden",
|
|
531
|
+
minHeight: "440px",
|
|
532
|
+
border: "sm",
|
|
533
|
+
borderColor: "display.border.muted.default",
|
|
534
|
+
borderRadius: "lg",
|
|
535
|
+
backgroundColor: "display.background.primary.default"
|
|
536
|
+
},
|
|
537
|
+
children: [/* @__PURE__ */jsx(Flex, {
|
|
538
|
+
sx: {
|
|
539
|
+
flex: 1
|
|
540
|
+
},
|
|
541
|
+
children
|
|
542
|
+
}), hasLeftSidebar && /* @__PURE__ */jsx(SidebarOverlay, {
|
|
543
|
+
side: "left",
|
|
544
|
+
open: isLeftSidebarOpen,
|
|
545
|
+
children: /* @__PURE__ */jsx(LeftSidebar, {})
|
|
546
|
+
}), hasRightSidebar && /* @__PURE__ */jsx(SidebarOverlay, {
|
|
547
|
+
side: "right",
|
|
548
|
+
open: isRightSidebarOpen,
|
|
549
|
+
children: /* @__PURE__ */jsx(RightSidebar, {})
|
|
550
|
+
}), hasLeftSidebar && /* @__PURE__ */jsx(OpenLeftSidebarButton, {}), hasRightSidebar && /* @__PURE__ */jsx(OpenRightSidebarButton, {})]
|
|
551
|
+
});
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
//#endregion
|
|
555
|
+
//#region src/GeovisWorkspaceProvider.tsx
|
|
556
|
+
/**
|
|
557
|
+
* Builds the initial selection by reading the `defaultValue` of every menu
|
|
558
|
+
* group in the config. Use it to seed the parent's selection state when
|
|
559
|
+
* controlling the workspace.
|
|
560
|
+
*/
|
|
561
|
+
var getInitialSelection = ({
|
|
562
|
+
config
|
|
563
|
+
}) => {
|
|
564
|
+
const selection = {};
|
|
565
|
+
const menus = config.leftSidebar?.menus ?? [];
|
|
566
|
+
for (const menu of menus) selection[menu.id] = menu.defaultValue;
|
|
567
|
+
return selection;
|
|
568
|
+
};
|
|
569
|
+
/**
|
|
570
|
+
* Provides shared state for GeovisWorkspace and all internal components.
|
|
571
|
+
* Manages the per-group selection (controlled or uncontrolled) and the sidebar
|
|
572
|
+
* open state, exposing them via useGeovisWorkspace.
|
|
573
|
+
*/
|
|
574
|
+
var GeovisWorkspaceProvider = ({
|
|
575
|
+
children,
|
|
576
|
+
config,
|
|
577
|
+
selection,
|
|
578
|
+
onSelectionChange
|
|
579
|
+
}) => {
|
|
580
|
+
const isControlled = selection !== void 0;
|
|
581
|
+
const [internalSelection, setInternalSelection] = React.useState(() => {
|
|
582
|
+
return getInitialSelection({
|
|
583
|
+
config
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
const currentSelection = isControlled ? selection : internalSelection;
|
|
587
|
+
const [isLeftSidebarOpen, setIsLeftSidebarOpen] = React.useState(false);
|
|
588
|
+
const [isRightSidebarOpen, setIsRightSidebarOpen] = React.useState(false);
|
|
589
|
+
const setSelection = React.useCallback(({
|
|
590
|
+
menuId,
|
|
591
|
+
value
|
|
592
|
+
}) => {
|
|
593
|
+
const next = {
|
|
594
|
+
...currentSelection,
|
|
595
|
+
[menuId]: value
|
|
596
|
+
};
|
|
597
|
+
if (!isControlled) setInternalSelection(next);
|
|
598
|
+
onSelectionChange?.(next);
|
|
599
|
+
}, [currentSelection, isControlled, onSelectionChange]);
|
|
600
|
+
const setLeftSidebarOpen = React.useCallback(({
|
|
601
|
+
open
|
|
602
|
+
}) => {
|
|
603
|
+
setIsLeftSidebarOpen(open);
|
|
604
|
+
}, []);
|
|
605
|
+
const setRightSidebarOpen = React.useCallback(({
|
|
606
|
+
open
|
|
607
|
+
}) => {
|
|
608
|
+
setIsRightSidebarOpen(open);
|
|
609
|
+
}, []);
|
|
610
|
+
const value = React.useMemo(() => {
|
|
611
|
+
return {
|
|
612
|
+
config,
|
|
613
|
+
selection: currentSelection,
|
|
614
|
+
setSelection,
|
|
615
|
+
isLeftSidebarOpen,
|
|
616
|
+
setLeftSidebarOpen,
|
|
617
|
+
isRightSidebarOpen,
|
|
618
|
+
setRightSidebarOpen
|
|
619
|
+
};
|
|
620
|
+
}, [config, currentSelection, setSelection, isLeftSidebarOpen, setLeftSidebarOpen, isRightSidebarOpen, setRightSidebarOpen]);
|
|
621
|
+
return /* @__PURE__ */jsx(GeovisWorkspaceContext.Provider, {
|
|
622
|
+
value,
|
|
623
|
+
children
|
|
624
|
+
});
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
//#endregion
|
|
628
|
+
//#region src/GeovisWorkspace.tsx
|
|
629
|
+
/**
|
|
630
|
+
* Renders the GeoVis map for the workspace inside the main content area. Kept
|
|
631
|
+
* as an internal component so the map fills the layout's main slot and mounts
|
|
632
|
+
* inside the provider tree.
|
|
633
|
+
*/
|
|
634
|
+
var GeovisWorkspaceMap = ({
|
|
635
|
+
visualizationSpec
|
|
636
|
+
}) => {
|
|
637
|
+
return /* @__PURE__ */jsx(Box, {
|
|
638
|
+
sx: {
|
|
639
|
+
position: "relative",
|
|
640
|
+
flex: 1,
|
|
641
|
+
display: "flex",
|
|
642
|
+
minHeight: "440px"
|
|
643
|
+
},
|
|
644
|
+
children: /* @__PURE__ */jsx(GeoVisProvider, {
|
|
645
|
+
spec: visualizationSpec,
|
|
646
|
+
children: /* @__PURE__ */jsx(GeoVisCanvas, {
|
|
647
|
+
style: {
|
|
648
|
+
width: "100%",
|
|
649
|
+
height: "100%"
|
|
650
|
+
}
|
|
651
|
+
})
|
|
652
|
+
})
|
|
653
|
+
});
|
|
654
|
+
};
|
|
655
|
+
var GeovisWorkspace = ({
|
|
656
|
+
config,
|
|
657
|
+
visualizationSpec,
|
|
658
|
+
variables,
|
|
659
|
+
onVariableChange
|
|
660
|
+
}) => {
|
|
661
|
+
return /* @__PURE__ */jsx(GeovisWorkspaceProvider, {
|
|
662
|
+
config,
|
|
663
|
+
selection: variables,
|
|
664
|
+
onSelectionChange: onVariableChange,
|
|
665
|
+
children: /* @__PURE__ */jsx(Layout, {
|
|
666
|
+
children: /* @__PURE__ */jsx(GeovisWorkspaceMap, {
|
|
667
|
+
visualizationSpec
|
|
668
|
+
})
|
|
669
|
+
})
|
|
670
|
+
});
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
//#endregion
|
|
674
|
+
export { GeovisWorkspace, GeovisWorkspaceProvider, getInitialSelection, useGeovisWorkspace };
|