@oceanum/eidos 0.9.0 → 0.9.1
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 +262 -8
- package/package.json +3 -3
- package/src/index.ts +7 -3
- package/src/lib/react.tsx +65 -3
- package/src/lib/render.ts +120 -30
- package/src/schema/interfaces.ts +58 -44
package/README.md
CHANGED
|
@@ -19,13 +19,63 @@ A lightweight, reactive JavaScript library for embedding and controlling EIDOS v
|
|
|
19
19
|
npm install @oceanum/eidos
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
###
|
|
22
|
+
### React Usage (Recommended)
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { EidosProvider, useEidosSpec } from "@oceanum/eidos";
|
|
26
|
+
|
|
27
|
+
// Define your EIDOS specification
|
|
28
|
+
const initialSpec = {
|
|
29
|
+
version: "0.9",
|
|
30
|
+
id: "my-app",
|
|
31
|
+
name: "My Visualization",
|
|
32
|
+
root: {
|
|
33
|
+
id: "root",
|
|
34
|
+
nodeType: "world",
|
|
35
|
+
children: [],
|
|
36
|
+
},
|
|
37
|
+
data: [],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function App() {
|
|
41
|
+
return (
|
|
42
|
+
<EidosProvider
|
|
43
|
+
initialSpec={initialSpec}
|
|
44
|
+
options={{
|
|
45
|
+
renderer: "https://render.eidos.oceanum.io",
|
|
46
|
+
eventListener: (event) => console.log("Event:", event),
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
<YourComponents />
|
|
50
|
+
</EidosProvider>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// In any child component
|
|
55
|
+
function YourComponent() {
|
|
56
|
+
const spec = useEidosSpec();
|
|
57
|
+
|
|
58
|
+
const addLayer = () => {
|
|
59
|
+
// Mutate spec directly - changes propagate automatically to iframe
|
|
60
|
+
spec.root.children.push({
|
|
61
|
+
id: "new-layer",
|
|
62
|
+
nodeType: "worldlayer",
|
|
63
|
+
layerType: "track",
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return <button onClick={addLayer}>Add Layer</button>;
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Vanilla JavaScript Usage
|
|
23
72
|
|
|
24
73
|
```javascript
|
|
25
|
-
import {
|
|
74
|
+
import { render } from "@oceanum/eidos";
|
|
26
75
|
|
|
27
76
|
// Define your EIDOS specification
|
|
28
77
|
const spec = {
|
|
78
|
+
version: "0.9",
|
|
29
79
|
id: "my-app",
|
|
30
80
|
name: "My Visualization",
|
|
31
81
|
root: {
|
|
@@ -34,24 +84,134 @@ const spec = {
|
|
|
34
84
|
children: [],
|
|
35
85
|
},
|
|
36
86
|
data: [],
|
|
37
|
-
transforms: [],
|
|
38
87
|
};
|
|
39
88
|
|
|
40
|
-
//
|
|
89
|
+
// Render in a container element
|
|
41
90
|
const container = document.getElementById("eidos-container");
|
|
42
|
-
const
|
|
43
|
-
|
|
91
|
+
const result = await render(container, spec, {
|
|
92
|
+
renderer: "https://render.eidos.oceanum.io",
|
|
93
|
+
eventListener: (event) => {
|
|
94
|
+
console.log("Received event:", event);
|
|
95
|
+
},
|
|
44
96
|
});
|
|
45
97
|
|
|
46
98
|
// Mutate the spec naturally - changes propagate automatically
|
|
47
|
-
|
|
48
|
-
|
|
99
|
+
result.spec.name = "Updated Visualization";
|
|
100
|
+
result.spec.root.children.push({
|
|
49
101
|
id: "layer-1",
|
|
50
102
|
nodeType: "worldlayer",
|
|
51
103
|
layerType: "track",
|
|
52
104
|
});
|
|
105
|
+
|
|
106
|
+
// Clean up when done
|
|
107
|
+
result.destroy();
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Advanced Usage
|
|
111
|
+
|
|
112
|
+
### Managing Multiple EIDOS Instances
|
|
113
|
+
|
|
114
|
+
The React Context API allows you to easily manage multiple EIDOS instances in the same application. Each `EidosProvider` creates its own isolated context with its own iframe and spec.
|
|
115
|
+
|
|
116
|
+
**Option 1: Multiple Providers (Recommended)**
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
import { EidosProvider, useEidosSpec } from "@oceanum/eidos";
|
|
120
|
+
|
|
121
|
+
function App() {
|
|
122
|
+
return (
|
|
123
|
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }}>
|
|
124
|
+
{/* Map view */}
|
|
125
|
+
<EidosProvider
|
|
126
|
+
initialSpec={mapSpec}
|
|
127
|
+
options={{ renderer: EIDOS_URL }}
|
|
128
|
+
>
|
|
129
|
+
<MapControls />
|
|
130
|
+
</EidosProvider>
|
|
131
|
+
|
|
132
|
+
{/* Chart view */}
|
|
133
|
+
<EidosProvider
|
|
134
|
+
initialSpec={chartSpec}
|
|
135
|
+
options={{ renderer: EIDOS_URL }}
|
|
136
|
+
>
|
|
137
|
+
<ChartControls />
|
|
138
|
+
</EidosProvider>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function MapControls() {
|
|
144
|
+
const spec = useEidosSpec(); // Gets mapSpec from nearest provider
|
|
145
|
+
// Mutations only affect this instance
|
|
146
|
+
const zoomIn = () => {
|
|
147
|
+
spec.root.viewState.zoom += 1;
|
|
148
|
+
};
|
|
149
|
+
return <button onClick={zoomIn}>Zoom In</button>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function ChartControls() {
|
|
153
|
+
const spec = useEidosSpec(); // Gets chartSpec from nearest provider
|
|
154
|
+
// Completely isolated from MapControls
|
|
155
|
+
const updateData = () => {
|
|
156
|
+
spec.data[0].dataSpec = newData;
|
|
157
|
+
};
|
|
158
|
+
return <button onClick={updateData}>Update Data</button>;
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Option 2: Low-Level Render API**
|
|
163
|
+
|
|
164
|
+
For more control, use the `render()` function directly:
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
import { render } from "@oceanum/eidos";
|
|
168
|
+
import { useEffect, useRef, useState } from "react";
|
|
169
|
+
|
|
170
|
+
function MultiInstance() {
|
|
171
|
+
const container1 = useRef(null);
|
|
172
|
+
const container2 = useRef(null);
|
|
173
|
+
const [specs, setSpecs] = useState({ map: null, chart: null });
|
|
174
|
+
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
const init = async () => {
|
|
177
|
+
const map = await render(container1.current, mapSpec, { renderer: EIDOS_URL });
|
|
178
|
+
const chart = await render(container2.current, chartSpec, { renderer: EIDOS_URL });
|
|
179
|
+
|
|
180
|
+
setSpecs({ map: map.spec, chart: chart.spec });
|
|
181
|
+
|
|
182
|
+
return () => {
|
|
183
|
+
map.destroy();
|
|
184
|
+
chart.destroy();
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
init();
|
|
189
|
+
}, []);
|
|
190
|
+
|
|
191
|
+
const updateMap = () => {
|
|
192
|
+
if (specs.map) {
|
|
193
|
+
specs.map.root.viewState.zoom += 1;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<div>
|
|
199
|
+
<div ref={container1} style={{ width: '50%', height: '100%' }} />
|
|
200
|
+
<div ref={container2} style={{ width: '50%', height: '100%' }} />
|
|
201
|
+
<button onClick={updateMap}>Update Map</button>
|
|
202
|
+
</div>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
53
205
|
```
|
|
54
206
|
|
|
207
|
+
**Key Points for Multiple Instances:**
|
|
208
|
+
|
|
209
|
+
- ✅ Each instance has its own iframe and spec proxy
|
|
210
|
+
- ✅ Changes to one instance don't affect others
|
|
211
|
+
- ✅ `useEidosSpec()` always returns the spec from the nearest `EidosProvider` ancestor
|
|
212
|
+
- ✅ Use unique `id` values in your specs to avoid conflicts
|
|
213
|
+
- ✅ Each instance can connect to the same or different renderer URLs
|
|
214
|
+
|
|
55
215
|
## Framework Integration
|
|
56
216
|
|
|
57
217
|
- [React Integration](./docs/eidos/react.md) - Hooks, components, and patterns
|
|
@@ -61,6 +221,100 @@ eidos.root.children.push({
|
|
|
61
221
|
|
|
62
222
|
## API Reference
|
|
63
223
|
|
|
224
|
+
### React Components and Hooks
|
|
225
|
+
|
|
226
|
+
#### `<EidosProvider>`
|
|
227
|
+
|
|
228
|
+
Renders an EIDOS iframe and provides the spec proxy to all child components via React Context.
|
|
229
|
+
|
|
230
|
+
**Props:**
|
|
231
|
+
- `initialSpec` (required): The EIDOS specification object
|
|
232
|
+
- `options` (optional): Render options
|
|
233
|
+
- `renderer` (string): URL of the EIDOS renderer (default: `https://render.eidos.oceanum.io`)
|
|
234
|
+
- `eventListener` (function): Callback for events from the renderer
|
|
235
|
+
- `authToken` (string | function): Authentication token for data fetching
|
|
236
|
+
- `containerStyle` (optional): CSS styles for the iframe container (default: `{ width: '100%', height: '100%', position: 'absolute' }`)
|
|
237
|
+
- `onInitialized` (optional): Callback called with the spec proxy after initialization
|
|
238
|
+
|
|
239
|
+
**Example:**
|
|
240
|
+
```tsx
|
|
241
|
+
<EidosProvider
|
|
242
|
+
initialSpec={mySpec}
|
|
243
|
+
options={{
|
|
244
|
+
renderer: "https://render.eidos.oceanum.io",
|
|
245
|
+
eventListener: (event) => console.log(event),
|
|
246
|
+
}}
|
|
247
|
+
containerStyle={{ width: '100vw', height: '100vh' }}
|
|
248
|
+
onInitialized={(spec) => console.log('Ready!', spec)}
|
|
249
|
+
>
|
|
250
|
+
<YourComponents />
|
|
251
|
+
</EidosProvider>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### `useEidosSpec()`
|
|
255
|
+
|
|
256
|
+
Hook that returns the EIDOS spec proxy from the nearest `EidosProvider` ancestor.
|
|
257
|
+
|
|
258
|
+
**Returns:** `Proxy<EidosSpec>` - The spec proxy (read and mutate directly)
|
|
259
|
+
|
|
260
|
+
**Throws:** Error if not used within an `EidosProvider`
|
|
261
|
+
|
|
262
|
+
**Example:**
|
|
263
|
+
```tsx
|
|
264
|
+
function MyComponent() {
|
|
265
|
+
const spec = useEidosSpec();
|
|
266
|
+
|
|
267
|
+
// Read from spec
|
|
268
|
+
const layerCount = spec.root.children.length;
|
|
269
|
+
|
|
270
|
+
// Mutate spec - changes sync to iframe automatically
|
|
271
|
+
const addLayer = () => {
|
|
272
|
+
spec.root.children.push({
|
|
273
|
+
id: 'new-layer',
|
|
274
|
+
nodeType: 'worldlayer',
|
|
275
|
+
});
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
return <button onClick={addLayer}>Add Layer ({layerCount})</button>;
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Core Functions
|
|
283
|
+
|
|
284
|
+
#### `render(element, spec, options)`
|
|
285
|
+
|
|
286
|
+
Low-level function to render EIDOS in a DOM element. Use this for vanilla JavaScript or when you need more control than `EidosProvider` offers.
|
|
287
|
+
|
|
288
|
+
**Parameters:**
|
|
289
|
+
- `element` (HTMLElement): Container element for the iframe
|
|
290
|
+
- `spec` (EidosSpec): The EIDOS specification
|
|
291
|
+
- `options` (RenderOptions): Configuration options
|
|
292
|
+
- `renderer` (string): Renderer URL
|
|
293
|
+
- `eventListener` (function): Event callback
|
|
294
|
+
- `authToken` (string | function): Auth token
|
|
295
|
+
- `id` (string): Optional override for spec ID
|
|
296
|
+
|
|
297
|
+
**Returns:** `Promise<RenderResult>`
|
|
298
|
+
- `spec`: The reactive spec proxy
|
|
299
|
+
- `iframe`: The iframe element
|
|
300
|
+
- `updateAuth(token)`: Function to update auth token
|
|
301
|
+
- `destroy()`: Cleanup function
|
|
302
|
+
|
|
303
|
+
**Example:**
|
|
304
|
+
```javascript
|
|
305
|
+
const result = await render(container, spec, {
|
|
306
|
+
renderer: EIDOS_URL,
|
|
307
|
+
eventListener: (e) => console.log(e),
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
result.spec.data.push(newData);
|
|
311
|
+
|
|
312
|
+
// Clean up
|
|
313
|
+
result.destroy();
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Additional Documentation
|
|
317
|
+
|
|
64
318
|
- [Core API](./docs/eidos/api.md) - Complete API documentation
|
|
65
319
|
- [Events](./docs/eidos/events.md) - Event handling and communication
|
|
66
320
|
- [Validation](./docs/eidos/validation.md) - Schema validation details
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oceanum/eidos",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"scripts": {
|
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"ajv": "^8.17.1",
|
|
12
|
-
"valtio": "^2.
|
|
12
|
+
"valtio": "^2.3.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"json-schema-to-typescript": "^15.0.4"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
|
-
"react": "^19.
|
|
18
|
+
"react": "^19.2.3"
|
|
19
19
|
}
|
|
20
20
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export
|
|
3
|
-
export * from
|
|
1
|
+
export * from './lib/render';
|
|
2
|
+
export type { RenderOptions, RenderResult } from './lib/render';
|
|
3
|
+
export * from './schema/interfaces';
|
|
4
|
+
export {
|
|
5
|
+
useEidosSpec,
|
|
6
|
+
EidosProvider
|
|
7
|
+
} from './lib/react';
|
package/src/lib/react.tsx
CHANGED
|
@@ -1,8 +1,70 @@
|
|
|
1
|
+
import { createContext, useContext, useEffect, useRef, useState, ReactNode } from 'react';
|
|
1
2
|
import { useSnapshot } from 'valtio';
|
|
2
3
|
import { Proxy } from 'valtio/vanilla';
|
|
3
4
|
import { EidosSpec } from '../schema/interfaces';
|
|
5
|
+
import { render, RenderOptions } from './render';
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
// Context to hold the EIDOS spec proxy
|
|
8
|
+
const EidosSpecContext = createContext<Proxy<EidosSpec> | null>(null);
|
|
9
|
+
|
|
10
|
+
// Hook to get the spec proxy from context
|
|
11
|
+
// Can be used for both reading and mutations - valtio tracks reads automatically
|
|
12
|
+
export const useEidosSpec = () => {
|
|
13
|
+
const spec = useContext(EidosSpecContext);
|
|
14
|
+
if (!spec) {
|
|
15
|
+
throw new Error('useEidosSpec must be used within an EidosProvider');
|
|
16
|
+
}
|
|
17
|
+
return spec;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Props for the EidosProvider component
|
|
21
|
+
interface EidosProviderProps {
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
initialSpec: EidosSpec;
|
|
24
|
+
options?: Omit<RenderOptions, 'id'>;
|
|
25
|
+
containerStyle?: React.CSSProperties;
|
|
26
|
+
onInitialized?: (spec: Proxy<EidosSpec>) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Provider component that renders EIDOS and provides spec to children
|
|
30
|
+
export const EidosProvider = ({
|
|
31
|
+
children,
|
|
32
|
+
initialSpec,
|
|
33
|
+
options = {},
|
|
34
|
+
containerStyle = { width: '100%', height: '100%', position: 'absolute' },
|
|
35
|
+
onInitialized,
|
|
36
|
+
}: EidosProviderProps) => {
|
|
37
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
38
|
+
const [spec, setSpec] = useState<Proxy<EidosSpec> | null>(null);
|
|
39
|
+
const initializedRef = useRef(false);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!containerRef.current || initializedRef.current) return;
|
|
43
|
+
|
|
44
|
+
initializedRef.current = true;
|
|
45
|
+
|
|
46
|
+
const initEidos = async () => {
|
|
47
|
+
try {
|
|
48
|
+
const result = await render(containerRef.current!, initialSpec, options);
|
|
49
|
+
setSpec(result.spec);
|
|
50
|
+
onInitialized?.(result.spec);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error('Failed to initialize EIDOS:', error);
|
|
53
|
+
initializedRef.current = false;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
initEidos();
|
|
58
|
+
}, [initialSpec, options, onInitialized]);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
|
|
62
|
+
<div ref={containerRef} style={containerStyle} />
|
|
63
|
+
{spec && (
|
|
64
|
+
<EidosSpecContext.Provider value={spec}>
|
|
65
|
+
{children}
|
|
66
|
+
</EidosSpecContext.Provider>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
8
70
|
};
|
package/src/lib/render.ts
CHANGED
|
@@ -1,25 +1,66 @@
|
|
|
1
|
-
import { proxy, subscribe, snapshot } from
|
|
2
|
-
import { validateSchema } from
|
|
3
|
-
import { EidosSpec } from
|
|
1
|
+
import { proxy, subscribe, snapshot, Proxy } from 'valtio/vanilla';
|
|
2
|
+
import { validateSchema } from './eidosmodel';
|
|
3
|
+
import { EidosSpec } from '../schema/interfaces';
|
|
4
4
|
|
|
5
|
-
const DEFAULT_RENDERER =
|
|
5
|
+
const DEFAULT_RENDERER = 'https://render.eidos.oceanum.io';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Options for rendering an EIDOS spec
|
|
9
|
+
*/
|
|
10
|
+
export interface RenderOptions {
|
|
11
|
+
/** The unique identifier for the EIDOS spec (optional, defaults to spec.id) */
|
|
12
|
+
id?: string;
|
|
13
|
+
/** Optional callback for handling events from the renderer */
|
|
14
|
+
eventListener?: (payload: unknown) => void;
|
|
15
|
+
/** URL of the EIDOS renderer */
|
|
16
|
+
renderer?: string;
|
|
17
|
+
/** Authentication token to pass to the renderer for data fetching */
|
|
18
|
+
authToken?: string | (() => string | Promise<string>);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Result from the render function
|
|
23
|
+
*/
|
|
24
|
+
export interface RenderResult {
|
|
25
|
+
/** The reactive EIDOS spec proxy */
|
|
26
|
+
spec: Proxy<EidosSpec>;
|
|
27
|
+
/** Update the auth token sent to the renderer */
|
|
28
|
+
updateAuth: (
|
|
29
|
+
token: string | (() => string | Promise<string>),
|
|
30
|
+
) => Promise<void>;
|
|
31
|
+
/** The iframe element */
|
|
32
|
+
iframe: HTMLIFrameElement;
|
|
33
|
+
/** Destroy the renderer and clean up resources */
|
|
34
|
+
destroy: () => void;
|
|
35
|
+
}
|
|
6
36
|
|
|
7
37
|
/**
|
|
8
38
|
* Embed the EIDOS iframe and set up message passing
|
|
9
39
|
* @param element HTML element to render the EIDOS spec into
|
|
10
40
|
* @param spec The EIDOS specification object
|
|
11
|
-
* @param
|
|
12
|
-
* @
|
|
13
|
-
* @param renderer URL of the EIDOS renderer
|
|
14
|
-
* @returns A proxy object that can be used with useEidosSpec
|
|
41
|
+
* @param options Render options including id, eventListener, renderer URL, and authToken
|
|
42
|
+
* @returns A RenderResult object containing the spec proxy and control methods
|
|
15
43
|
*/
|
|
16
44
|
const render = async (
|
|
17
45
|
element: HTMLElement,
|
|
18
46
|
spec: EidosSpec,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
renderer = DEFAULT_RENDERER
|
|
22
|
-
|
|
47
|
+
options: RenderOptions = {},
|
|
48
|
+
): Promise<RenderResult> => {
|
|
49
|
+
const { id, eventListener, renderer = DEFAULT_RENDERER, authToken } = options;
|
|
50
|
+
|
|
51
|
+
// Check if this container is already initialized
|
|
52
|
+
// We check both for an existing iframe and a marker on the container itself
|
|
53
|
+
// This prevents double-mounting even if destroy() was called but the container is being reused
|
|
54
|
+
const existingIframe = element.querySelector('iframe[data-eidos-renderer]');
|
|
55
|
+
const isInitialized = element.getAttribute('data-eidos-initialized') === 'true';
|
|
56
|
+
|
|
57
|
+
if (existingIframe || isInitialized) {
|
|
58
|
+
throw new Error('EIDOS renderer already mounted in this container. Call destroy() before re-rendering.');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Mark container as initialized
|
|
62
|
+
element.setAttribute('data-eidos-initialized', 'true');
|
|
63
|
+
|
|
23
64
|
// Validate the spec before creating proxy
|
|
24
65
|
try {
|
|
25
66
|
await validateSchema(spec);
|
|
@@ -32,55 +73,104 @@ const render = async (
|
|
|
32
73
|
const _id = id || spec.id;
|
|
33
74
|
|
|
34
75
|
return new Promise((resolve, reject) => {
|
|
35
|
-
const iframe = document.createElement(
|
|
76
|
+
const iframe = document.createElement('iframe');
|
|
36
77
|
iframe.src = `${renderer}?id=${_id}`;
|
|
37
|
-
iframe.width =
|
|
38
|
-
iframe.height =
|
|
39
|
-
iframe.frameBorder =
|
|
78
|
+
iframe.width = '100%';
|
|
79
|
+
iframe.height = '100%';
|
|
80
|
+
iframe.frameBorder = '0';
|
|
81
|
+
// Mark this as an EIDOS iframe for duplicate detection
|
|
82
|
+
iframe.setAttribute('data-eidos-renderer', 'true');
|
|
40
83
|
element.appendChild(iframe);
|
|
41
84
|
|
|
85
|
+
let messageHandler: ((event: MessageEvent) => void) | null = null;
|
|
86
|
+
let unsubscribe: (() => void) | null = null;
|
|
87
|
+
|
|
42
88
|
iframe.onload = () => {
|
|
43
89
|
const win = iframe.contentWindow;
|
|
44
90
|
|
|
45
|
-
|
|
91
|
+
// Helper to send auth token to iframe
|
|
92
|
+
const sendAuth = async (
|
|
93
|
+
token: string | (() => string | Promise<string>),
|
|
94
|
+
) => {
|
|
95
|
+
const resolvedToken =
|
|
96
|
+
typeof token === 'function' ? await token() : token;
|
|
97
|
+
win?.postMessage(
|
|
98
|
+
{
|
|
99
|
+
id: _id,
|
|
100
|
+
type: 'auth',
|
|
101
|
+
payload: resolvedToken,
|
|
102
|
+
},
|
|
103
|
+
'*',
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
messageHandler = (event: MessageEvent) => {
|
|
46
108
|
if (event.source !== win) return;
|
|
47
|
-
if (event.data.id !== id) return;
|
|
48
109
|
|
|
49
|
-
if
|
|
110
|
+
// Check ID only if both message and expected ID exist
|
|
111
|
+
// Some EIDOS renderers may not include ID in event messages
|
|
112
|
+
if (event.data.id && event.data.id !== _id) return;
|
|
113
|
+
|
|
114
|
+
if (event.data.type === 'init') {
|
|
50
115
|
// Send initial spec
|
|
51
116
|
win?.postMessage(
|
|
52
117
|
{
|
|
53
118
|
id: _id,
|
|
54
|
-
type:
|
|
119
|
+
type: 'spec',
|
|
55
120
|
payload: structuredClone(eidos),
|
|
56
121
|
},
|
|
57
|
-
|
|
122
|
+
'*',
|
|
58
123
|
);
|
|
59
|
-
|
|
60
|
-
|
|
124
|
+
// Send auth token on init if provided
|
|
125
|
+
if (authToken) {
|
|
126
|
+
sendAuth(authToken);
|
|
127
|
+
}
|
|
128
|
+
} else if (event.data.type || event.data.action || event.data.control) {
|
|
129
|
+
// Process as event - EIDOS events may not have 'type' but have 'action' and 'control'
|
|
130
|
+
eventListener?.(event.data);
|
|
61
131
|
}
|
|
62
|
-
}
|
|
132
|
+
};
|
|
133
|
+
window.addEventListener('message', messageHandler);
|
|
63
134
|
|
|
64
135
|
// Subscribe to changes and send patches to renderer
|
|
65
|
-
subscribe(eidos, () => {
|
|
136
|
+
unsubscribe = subscribe(eidos, () => {
|
|
66
137
|
win?.postMessage(
|
|
67
138
|
{
|
|
68
139
|
id: _id,
|
|
69
|
-
type:
|
|
140
|
+
type: 'spec',
|
|
70
141
|
payload: snapshot(eidos),
|
|
71
142
|
},
|
|
72
|
-
|
|
143
|
+
'*',
|
|
73
144
|
);
|
|
74
145
|
});
|
|
75
146
|
|
|
76
|
-
|
|
147
|
+
// Send initial auth token if provided (in case init already happened)
|
|
148
|
+
if (authToken) {
|
|
149
|
+
sendAuth(authToken);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
resolve({
|
|
153
|
+
spec: eidos,
|
|
154
|
+
updateAuth: sendAuth,
|
|
155
|
+
iframe,
|
|
156
|
+
destroy: () => {
|
|
157
|
+
if (messageHandler) {
|
|
158
|
+
window.removeEventListener('message', messageHandler);
|
|
159
|
+
}
|
|
160
|
+
if (unsubscribe) {
|
|
161
|
+
unsubscribe();
|
|
162
|
+
}
|
|
163
|
+
iframe.remove();
|
|
164
|
+
// Clear the initialization marker when explicitly destroyed
|
|
165
|
+
element.removeAttribute('data-eidos-initialized');
|
|
166
|
+
},
|
|
167
|
+
});
|
|
77
168
|
};
|
|
78
169
|
|
|
79
170
|
iframe.onerror = () => {
|
|
80
|
-
reject(new Error(
|
|
171
|
+
reject(new Error('Failed to load EIDOS renderer'));
|
|
81
172
|
};
|
|
82
173
|
});
|
|
83
|
-
return eidos;
|
|
84
174
|
};
|
|
85
175
|
|
|
86
176
|
export { render };
|
package/src/schema/interfaces.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated TypeScript interfaces for EIDOS schemas
|
|
3
3
|
* Generated from: https://schemas.oceanum.io/eidos/root.json
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* Each interface corresponds to a definition in the EIDOS schema bundle.
|
|
6
6
|
* These interfaces can be used for type validation and IDE support.
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* Do not modify this file directly - regenerate using:
|
|
9
9
|
* npx nx run eidos:generate-types
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
const WorldNodeType =
|
|
14
|
-
const PlotNodeType =
|
|
15
|
-
const DocumentNodeType =
|
|
16
|
-
const GridNodeType =
|
|
17
|
-
const MenuNodeType =
|
|
18
|
-
const WorldlayerNodeType =
|
|
19
|
-
const ControlgroupNodeType =
|
|
12
|
+
const Constant = '0.9' as const;
|
|
13
|
+
const WorldNodeType = 'world' as const;
|
|
14
|
+
const PlotNodeType = 'plot' as const;
|
|
15
|
+
const DocumentNodeType = 'document' as const;
|
|
16
|
+
const GridNodeType = 'grid' as const;
|
|
17
|
+
const MenuNodeType = 'menu' as const;
|
|
18
|
+
const WorldlayerNodeType = 'worldlayer' as const;
|
|
19
|
+
const ControlgroupNodeType = 'controlgroup' as const;
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* EIDOS specification
|
|
@@ -26,10 +26,10 @@ export interface EidosSpec {
|
|
|
26
26
|
/**
|
|
27
27
|
* Version of EIDOS
|
|
28
28
|
*/
|
|
29
|
-
version?:
|
|
29
|
+
version?: Constant;
|
|
30
30
|
/**
|
|
31
31
|
* Unique identifier for this specification. Must be URL-safe using only lowercase letters, numbers, hyphens, and underscores. This ID is used for referencing the specification in URLs, file systems, and databases. It should be descriptive but concise.
|
|
32
|
-
*
|
|
32
|
+
*
|
|
33
33
|
* @example
|
|
34
34
|
* "seastate-demo"
|
|
35
35
|
* "plot-demo"
|
|
@@ -38,7 +38,7 @@ export interface EidosSpec {
|
|
|
38
38
|
id: string;
|
|
39
39
|
/**
|
|
40
40
|
* Human-readable display name for this specification. This is the name shown to users in lists, menus, and interfaces. It can contain spaces, special characters, and mixed case. Should be descriptive and meaningful to end users.
|
|
41
|
-
*
|
|
41
|
+
*
|
|
42
42
|
* @example
|
|
43
43
|
* "DemoOverlay"
|
|
44
44
|
* "DemoPlot"
|
|
@@ -47,7 +47,7 @@ export interface EidosSpec {
|
|
|
47
47
|
name: string;
|
|
48
48
|
/**
|
|
49
49
|
* Detailed description of what this specification does and its purpose. This is primarily for documentation and metadata purposes - it's not typically displayed in the main interface but may be shown in tooltips, help text, or specification listings. Useful for developers and content creators.
|
|
50
|
-
*
|
|
50
|
+
*
|
|
51
51
|
* @example
|
|
52
52
|
* "Demonstration overlay"
|
|
53
53
|
* "Demonstration grid layout"
|
|
@@ -66,7 +66,7 @@ export interface EidosSpec {
|
|
|
66
66
|
/**
|
|
67
67
|
* Data Sources
|
|
68
68
|
* Array of data source definitions that provide data to the visualization components. Each data source can be a static dataset, OceanQL query, Oceanum Datamesh source, or other supported data provider. Data sources are referenced by ID throughout the specification.
|
|
69
|
-
*
|
|
69
|
+
*
|
|
70
70
|
* @example
|
|
71
71
|
* [{"id":"sig-wave-height-trki","dataType":"oceanumDatamesh","dataSpec":{"datasource":"oceanum_wave_trki_era5_v1_grid","variables":["hs","tps"],"geofilter":{"type":"feature","geom":{"type":"Feature","geometry":{"type":"Point","coordinates":[174.3,-38.5]}}},"timefilter":{"times":["2018-01-01 00:00:00Z","2019-01-01 00:00:00Z"]}}}]
|
|
72
72
|
*/
|
|
@@ -110,7 +110,7 @@ export interface EidosTheme {
|
|
|
110
110
|
/**
|
|
111
111
|
* Color scheme for the view
|
|
112
112
|
*/
|
|
113
|
-
preset?:
|
|
113
|
+
preset?: 'default' | 'dark';
|
|
114
114
|
style?: EidosStyle;
|
|
115
115
|
}
|
|
116
116
|
|
|
@@ -121,7 +121,7 @@ export interface EidosTheme {
|
|
|
121
121
|
export interface EidosData {
|
|
122
122
|
/**
|
|
123
123
|
* Unique identifier for this data source within the specification. Must be alphanumeric with hyphens and underscores only. This ID is used to reference the data source from visualization layers and components.
|
|
124
|
-
*
|
|
124
|
+
*
|
|
125
125
|
* @example
|
|
126
126
|
* "sig-wave-height-trki"
|
|
127
127
|
* "ship-positions"
|
|
@@ -130,13 +130,13 @@ export interface EidosData {
|
|
|
130
130
|
id: string;
|
|
131
131
|
/**
|
|
132
132
|
* Type of data source that determines how the data is accessed and processed. Each type requires different configuration in the dataSpec property.
|
|
133
|
-
*
|
|
133
|
+
*
|
|
134
134
|
* @example
|
|
135
135
|
* "dataset"
|
|
136
136
|
* "geojson"
|
|
137
137
|
* "transform"
|
|
138
138
|
*/
|
|
139
|
-
dataType:
|
|
139
|
+
dataType: 'oceanql' | 'zarr' | 'dataset' | 'geojson' | 'transform';
|
|
140
140
|
dataSpec: Dataset | Transform | Geojson | Oceanquery | Zarr;
|
|
141
141
|
}
|
|
142
142
|
|
|
@@ -147,7 +147,7 @@ export interface EidosData {
|
|
|
147
147
|
export interface World {
|
|
148
148
|
/**
|
|
149
149
|
* Unique identifier for this world node within the specification. Used for referencing this node in events, interactions, and programmatic access.
|
|
150
|
-
*
|
|
150
|
+
*
|
|
151
151
|
* @example
|
|
152
152
|
* "map-1"
|
|
153
153
|
* "world-view"
|
|
@@ -206,7 +206,7 @@ export interface Plot {
|
|
|
206
206
|
*/
|
|
207
207
|
title?: string;
|
|
208
208
|
nodeType?: PlotNodeType;
|
|
209
|
-
plotType?:
|
|
209
|
+
plotType?: 'vega' | 'vega-lite';
|
|
210
210
|
plotSpec: any & PlotSpec;
|
|
211
211
|
/**
|
|
212
212
|
* Actions to enable for the plot
|
|
@@ -245,7 +245,7 @@ export interface Document {
|
|
|
245
245
|
export interface Grid {
|
|
246
246
|
/**
|
|
247
247
|
* Unique identifier for this grid node within the specification. Used for referencing this node in events, interactions, and programmatic access.
|
|
248
|
-
*
|
|
248
|
+
*
|
|
249
249
|
* @example
|
|
250
250
|
* "main-grid"
|
|
251
251
|
* "dashboard-layout"
|
|
@@ -283,7 +283,7 @@ export interface Menu {
|
|
|
283
283
|
/**
|
|
284
284
|
* Location of menu relative to content
|
|
285
285
|
*/
|
|
286
|
-
position?:
|
|
286
|
+
position?: 'top' | 'left' | 'bottom' | 'right';
|
|
287
287
|
/**
|
|
288
288
|
* Whether menu is open
|
|
289
289
|
*/
|
|
@@ -356,7 +356,7 @@ export interface Dataset {
|
|
|
356
356
|
|
|
357
357
|
/**
|
|
358
358
|
* Transform
|
|
359
|
-
* Specification for data transform.
|
|
359
|
+
* Specification for data transform.
|
|
360
360
|
*/
|
|
361
361
|
export interface Transform {
|
|
362
362
|
/**
|
|
@@ -374,7 +374,7 @@ export interface Transform {
|
|
|
374
374
|
/**
|
|
375
375
|
* Output data type
|
|
376
376
|
*/
|
|
377
|
-
outputType?:
|
|
377
|
+
outputType?: 'dataset' | 'geojson';
|
|
378
378
|
/**
|
|
379
379
|
* Transform code
|
|
380
380
|
* Transform code as body of function
|
|
@@ -460,7 +460,7 @@ export interface Zarr {
|
|
|
460
460
|
export interface Worldlayer {
|
|
461
461
|
/**
|
|
462
462
|
* Unique identifier for this layer within the world node. Used for layer management, visibility control, and programmatic access.
|
|
463
|
-
*
|
|
463
|
+
*
|
|
464
464
|
* @example
|
|
465
465
|
* "wave-height"
|
|
466
466
|
* "ship-tracks"
|
|
@@ -469,7 +469,7 @@ export interface Worldlayer {
|
|
|
469
469
|
id: string;
|
|
470
470
|
/**
|
|
471
471
|
* Display name for this layer shown in the layer selector and legend. Should be descriptive and user-friendly.
|
|
472
|
-
*
|
|
472
|
+
*
|
|
473
473
|
* @example
|
|
474
474
|
* "Significant Wave Height"
|
|
475
475
|
* "Ship Positions"
|
|
@@ -479,7 +479,7 @@ export interface Worldlayer {
|
|
|
479
479
|
nodeType: WorldlayerNodeType;
|
|
480
480
|
/**
|
|
481
481
|
* Reference to a data source defined in the root 'data' array. This connects the layer to its data source for visualization.
|
|
482
|
-
*
|
|
482
|
+
*
|
|
483
483
|
* @example
|
|
484
484
|
* "hs-1"
|
|
485
485
|
* "sig-wave-height-trki"
|
|
@@ -530,7 +530,7 @@ export interface ControlGroup {
|
|
|
530
530
|
*/
|
|
531
531
|
id: string;
|
|
532
532
|
nodeType?: ControlgroupNodeType;
|
|
533
|
-
orientation?:
|
|
533
|
+
orientation?: 'horizontal' | 'vertical';
|
|
534
534
|
/**
|
|
535
535
|
* Control list
|
|
536
536
|
*/
|
|
@@ -550,10 +550,10 @@ export interface View {
|
|
|
550
550
|
/**
|
|
551
551
|
* Type of world view
|
|
552
552
|
*/
|
|
553
|
-
viewType?:
|
|
553
|
+
viewType?: 'map' | 'globe';
|
|
554
554
|
/**
|
|
555
555
|
* Longitude coordinate of the map center in decimal degrees (-180 to 180). Positive values are East, negative values are West.
|
|
556
|
-
*
|
|
556
|
+
*
|
|
557
557
|
* @example
|
|
558
558
|
* 174.3
|
|
559
559
|
* -122.4
|
|
@@ -562,7 +562,7 @@ export interface View {
|
|
|
562
562
|
longitude: number;
|
|
563
563
|
/**
|
|
564
564
|
* Latitude coordinate of the map center in decimal degrees (-90 to 90). Positive values are North, negative values are South.
|
|
565
|
-
*
|
|
565
|
+
*
|
|
566
566
|
* @example
|
|
567
567
|
* -38.5
|
|
568
568
|
* 37.7
|
|
@@ -583,7 +583,7 @@ export interface View {
|
|
|
583
583
|
maxZoom?: number;
|
|
584
584
|
/**
|
|
585
585
|
* Initial zoom level of the map. Higher values show more detail. Typically ranges from 0 (world view) to 20+ (street level).
|
|
586
|
-
*
|
|
586
|
+
*
|
|
587
587
|
* @example
|
|
588
588
|
* 2
|
|
589
589
|
* 8
|
|
@@ -642,7 +642,7 @@ export interface DocumentStyle {
|
|
|
642
642
|
/**
|
|
643
643
|
* Justify content
|
|
644
644
|
*/
|
|
645
|
-
justifyContent?:
|
|
645
|
+
justifyContent?: 'left' | 'center' | 'right';
|
|
646
646
|
}
|
|
647
647
|
|
|
648
648
|
/**
|
|
@@ -933,10 +933,17 @@ export interface MapHoverInfo {
|
|
|
933
933
|
/**
|
|
934
934
|
* Layer specification
|
|
935
935
|
*/
|
|
936
|
-
export type Layerspec =
|
|
936
|
+
export type Layerspec =
|
|
937
|
+
| Feature
|
|
938
|
+
| Gridded
|
|
939
|
+
| Label
|
|
940
|
+
| Scenegraph
|
|
941
|
+
| Seasurface
|
|
942
|
+
| Track
|
|
943
|
+
| Wmts;
|
|
937
944
|
|
|
938
945
|
export interface Timeselect {
|
|
939
|
-
mode:
|
|
946
|
+
mode: 'nearest' | 'exact' | 'range';
|
|
940
947
|
/**
|
|
941
948
|
* Time tolerance duration for nearest time select
|
|
942
949
|
*/
|
|
@@ -945,7 +952,7 @@ export interface Timeselect {
|
|
|
945
952
|
* Time aggregation
|
|
946
953
|
* Aggregation method for time range
|
|
947
954
|
*/
|
|
948
|
-
aggregate?:
|
|
955
|
+
aggregate?: 'last' | 'first' | 'sum' | 'mean' | 'max' | 'min';
|
|
949
956
|
/**
|
|
950
957
|
* Data field to group by
|
|
951
958
|
*/
|
|
@@ -959,7 +966,7 @@ export interface Levelselect {
|
|
|
959
966
|
/**
|
|
960
967
|
* Level selection mode
|
|
961
968
|
*/
|
|
962
|
-
mode?:
|
|
969
|
+
mode?: 'nearest' | 'exact' | 'range';
|
|
963
970
|
/**
|
|
964
971
|
* Level tolerance for nearest level select
|
|
965
972
|
*/
|
|
@@ -978,7 +985,7 @@ export interface Control {
|
|
|
978
985
|
/**
|
|
979
986
|
* Control type
|
|
980
987
|
*/
|
|
981
|
-
nodeType:
|
|
988
|
+
nodeType: 'points' | 'polygon' | 'bbox' | 'radius' | 'drop' | 'measure';
|
|
982
989
|
/**
|
|
983
990
|
* Control id
|
|
984
991
|
*/
|
|
@@ -1009,17 +1016,17 @@ export interface Control {
|
|
|
1009
1016
|
/**
|
|
1010
1017
|
* Base layer type
|
|
1011
1018
|
*/
|
|
1012
|
-
export type Baselayerpreset =
|
|
1019
|
+
export type Baselayerpreset = 'oceanum' | 'terrain';
|
|
1013
1020
|
|
|
1014
1021
|
/**
|
|
1015
1022
|
* ResampleType
|
|
1016
1023
|
*/
|
|
1017
|
-
export type Resampletype =
|
|
1024
|
+
export type Resampletype = 'mean' | 'nearest';
|
|
1018
1025
|
|
|
1019
1026
|
/**
|
|
1020
1027
|
* Aggregate Ops
|
|
1021
1028
|
*/
|
|
1022
|
-
export type AggregateOps =
|
|
1029
|
+
export type AggregateOps = 'mean' | 'min' | 'max' | 'std' | 'sum';
|
|
1023
1030
|
|
|
1024
1031
|
/**
|
|
1025
1032
|
* Feature
|
|
@@ -1029,7 +1036,7 @@ export interface Feature {
|
|
|
1029
1036
|
/**
|
|
1030
1037
|
* Type
|
|
1031
1038
|
*/
|
|
1032
|
-
type:
|
|
1039
|
+
type: 'Feature';
|
|
1033
1040
|
geometry: Geometry;
|
|
1034
1041
|
/**
|
|
1035
1042
|
* Properties
|
|
@@ -1049,7 +1056,14 @@ export interface Feature {
|
|
|
1049
1056
|
* Geometry
|
|
1050
1057
|
* Geometry Model
|
|
1051
1058
|
*/
|
|
1052
|
-
export type Geometry =
|
|
1059
|
+
export type Geometry =
|
|
1060
|
+
| Point
|
|
1061
|
+
| Multipoint
|
|
1062
|
+
| Linestring
|
|
1063
|
+
| Multilinestring
|
|
1064
|
+
| Polygon
|
|
1065
|
+
| Multipolygon
|
|
1066
|
+
| Geometrycollection;
|
|
1053
1067
|
|
|
1054
1068
|
/**
|
|
1055
1069
|
* Point
|