@vidro/map-handler 1.2.12 → 1.2.19
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 +367 -48
- package/dist/map-handler.js +1 -1
- package/doc/confirmComponent.png +0 -0
- package/examples/full/apidemo.js +46 -43
- package/examples/full/cachedToken.dat +1 -1
- package/examples/full/cachedTokenData.dat +1 -1
- package/examples/full/docker/Docker_compose.yml +14 -0
- package/examples/full/docker/Dockerfile +27 -0
- package/examples/full/index.php +5 -4
- package/examples/full/tester.css +74 -0
- package/examples/full/tester.js +2 -2
- package/examples/react-next/README.md +282 -0
- package/examples/react-next/components/AuthComponent.js +88 -0
- package/examples/react-next/components/MapButtons.js +161 -0
- package/examples/react-next/components/MapFilters.js +120 -0
- package/examples/react-next/components/MapIframe.js +25 -0
- package/examples/react-next/components/MapInfo.js +36 -0
- package/examples/react-next/components/MapLayers.js +60 -0
- package/examples/react-next/components/MapList.js +43 -0
- package/examples/react-next/contexts/auth.js +101 -0
- package/examples/react-next/contexts/maps.js +158 -0
- package/examples/react-next/contexts/messages.js +340 -0
- package/examples/react-next/env.sample +3 -0
- package/examples/react-next/eslint.config.mjs +14 -0
- package/examples/react-next/hooks/useMapEvents.js +118 -0
- package/examples/react-next/jsconfig.json +7 -0
- package/examples/react-next/next.config.mjs +6 -0
- package/examples/react-next/package.json +24 -0
- package/examples/react-next/pages/_app.js +5 -0
- package/examples/react-next/pages/index.js +87 -0
- package/examples/react-next/postcss.config.mjs +8 -0
- package/examples/react-next/public/discord.svg +8 -0
- package/examples/react-next/public/favicon.ico +0 -0
- package/examples/react-next/public/file.svg +1 -0
- package/examples/react-next/public/logo.png +0 -0
- package/examples/react-next/public/next.svg +1 -0
- package/examples/react-next/shared/constants.js +47 -0
- package/examples/react-next/styles/globals.css +24 -0
- package/examples/react-next/tailwind.config.mjs +17 -0
- package/helpers.md +45 -0
- package/package.json +1 -1
- package/src/index.js +263 -23
@@ -0,0 +1,282 @@
|
|
1
|
+
# Map Component React Integration
|
2
|
+
|
3
|
+
#### Feb, 2025
|
4
|
+
|
5
|
+
## Getting Started
|
6
|
+
|
7
|
+
This example is a basic mapComponent integration based in React 19 and Next.js
|
8
|
+
|
9
|
+
Requirements:
|
10
|
+
|
11
|
+
Node & npm ^19. We recommend using [nvm](https://github.com/nvm-sh/nvm) to manage multiple Node versions on a single server.
|
12
|
+
|
13
|
+
|
14
|
+
>Install project
|
15
|
+
|
16
|
+
```bash
|
17
|
+
npm install
|
18
|
+
```
|
19
|
+
>Run
|
20
|
+
|
21
|
+
```bash
|
22
|
+
npm run dev
|
23
|
+
```
|
24
|
+
You can create an `.env` file with default values for the API URL, user, and password. Simply rename `.env.sample` to `.env`:
|
25
|
+
|
26
|
+
```
|
27
|
+
NEXT_PUBLIC_APIURL="https://your api url"
|
28
|
+
NEXT_PUBLIC_USER="your user"
|
29
|
+
NEXT_PUBLIC_PWD="your password"
|
30
|
+
```
|
31
|
+
|
32
|
+
This example uses [Tailwindcss](https://tailwindcss.com) for layout
|
33
|
+
|
34
|
+
|
35
|
+
## Example flow
|
36
|
+
|
37
|
+
1. **Signs in to the Maps API**
|
38
|
+
|
39
|
+
The API sign-in request returns an authentication token and a list of maps the user can access.
|
40
|
+
|
41
|
+
If the sign-in is successful, a dropdown with available maps will be populated.
|
42
|
+
|
43
|
+
2. **Select a map and click `Load map`**
|
44
|
+
|
45
|
+
The selected map will be loaded. 😉
|
46
|
+
|
47
|
+
## Project structure
|
48
|
+
|
49
|
+
- **pages**
|
50
|
+
- **index.js** is the main file
|
51
|
+
|
52
|
+
- **contexts**
|
53
|
+
- **auth.js** handles user's sign in and sign out
|
54
|
+
- **map.js** handles map info
|
55
|
+
- **messajes** handles communication with map component (mapHandler integration)
|
56
|
+
|
57
|
+
- **hooks**
|
58
|
+
- **useMapEvents** - custom hook for handling map events
|
59
|
+
|
60
|
+
|
61
|
+
- **components**
|
62
|
+
- **AuthComponent** component with auth
|
63
|
+
- **MapList** component with available maps
|
64
|
+
- **MapIframe** component with map iframe
|
65
|
+
- **MapButtons** component with map buttons
|
66
|
+
- **MapLayers** component with map layers, with toggle button
|
67
|
+
- **MapInfo** component for display map info
|
68
|
+
- **MapFilters** component for handle filters
|
69
|
+
|
70
|
+
- **shared**
|
71
|
+
- **constants.js** file with constants to minimize typos
|
72
|
+
|
73
|
+
|
74
|
+
## Step by step
|
75
|
+
* `components/SessionComponent` interacts with `contexts/auth.js` to handle sign-in and load the map list. Maps and the authentication token are stored in context state variables `projects` and `token`.
|
76
|
+
* If sign-in is successful, `components/SessionComponent` displays a dropdown with the map list and a `Load Map` button.
|
77
|
+
* When `Load Map` is clicked, it calls the `GetMap` method from `contexts/maps`. This sends a request to the maps API and retrieves the iframe URL along with the session token. The API response looks something like this:
|
78
|
+
|
79
|
+
|
80
|
+
```
|
81
|
+
{
|
82
|
+
"message": {
|
83
|
+
"sessionToken": "eyJ0eXAiOiJKV1QiLC.... very long",
|
84
|
+
"iframe": "https:\/\/your.domain.com\/yourcomponent\/"
|
85
|
+
}
|
86
|
+
}
|
87
|
+
```
|
88
|
+
|
89
|
+
* `contexts/maps` sets context vars `map` and `sessionToken`. `map` will contain the url required by the iframe:
|
90
|
+
|
91
|
+
```
|
92
|
+
setSessionToken(message.essionToken);
|
93
|
+
setMap(
|
94
|
+
`${message.iframe}?sessionToken=${message.sessionToken}`
|
95
|
+
);
|
96
|
+
```
|
97
|
+
|
98
|
+
* `components/MapIframe` will render the iframe and start the mapHandler library:
|
99
|
+
|
100
|
+
```
|
101
|
+
useEffect(() => {
|
102
|
+
if (!sessionToken) return;
|
103
|
+
start(sessionToken); //starts communicator
|
104
|
+
}, [sessionToken]);
|
105
|
+
```
|
106
|
+
|
107
|
+
* `hooks/useMapEvents` listens to map events. When the map is loaded, a `loaded` event is emitted, and a context variable `mapReady` is set to `true`. This ensures that all child components recognize when the map is ready for interaction.
|
108
|
+
|
109
|
+
|
110
|
+
```
|
111
|
+
useEffect(() => {
|
112
|
+
if (!message) return;
|
113
|
+
switch (message.type) {
|
114
|
+
case MAP_EVENTS.LOADED:
|
115
|
+
if (message.what === "map") {
|
116
|
+
console.log("useMapEvents Map loaded and ready");
|
117
|
+
setMapReady(true);
|
118
|
+
}
|
119
|
+
setMessage(null);
|
120
|
+
break;
|
121
|
+
}
|
122
|
+
}, [message]);
|
123
|
+
```
|
124
|
+
|
125
|
+
|
126
|
+
Map is ready and buttons active!
|
127
|
+
|
128
|
+
|
129
|
+
## Map Events
|
130
|
+
|
131
|
+
The map emits several events. To avoid race conditions caused by concurrent events, we use a message queue.
|
132
|
+
|
133
|
+
In `contexts/messages.js` we manage this queue.
|
134
|
+
|
135
|
+
We have to state vars:
|
136
|
+
|
137
|
+
```
|
138
|
+
const [message, setMessage] = useState(null);
|
139
|
+
const [messageQueue, setMessageQueue] = useState([]);
|
140
|
+
```
|
141
|
+
|
142
|
+
|
143
|
+
We set listeners for the events we need:
|
144
|
+
|
145
|
+
```
|
146
|
+
useEffect(() => {
|
147
|
+
if (!communicator) return;
|
148
|
+
communicator.on(MAP_EVENTS.ZOOM_CHANGE, onMapEvent);
|
149
|
+
communicator.on(MAP_EVENTS.LOADED, onMapEvent);
|
150
|
+
|
151
|
+
return () => {
|
152
|
+
if (!communicator) return;
|
153
|
+
communicator.off(MAP_EVENTS.ZOOM_CHANGE, onMapEvent);
|
154
|
+
communicator.off(MAP_EVENTS.LOADED, onMapEvent);
|
155
|
+
|
156
|
+
setCommunicator(null);
|
157
|
+
};
|
158
|
+
}, [communicator, events]);
|
159
|
+
|
160
|
+
```
|
161
|
+
Each time an event is received we add the event to the queue
|
162
|
+
|
163
|
+
```
|
164
|
+
const onMapEvent = (data) => {
|
165
|
+
console.log(`onMapEvent`, { type: data.type, data });
|
166
|
+
setMessageQueue((prevQueue) => [...prevQueue, data]);
|
167
|
+
};
|
168
|
+
```
|
169
|
+
When `messageQueue` or `message` changes to `null`, we pick the next message in the queue `messageQueue[0]`
|
170
|
+
|
171
|
+
```
|
172
|
+
|
173
|
+
useEffect(() => {
|
174
|
+
if (message) return;
|
175
|
+
if (messageQueue.length === 0) {
|
176
|
+
return;
|
177
|
+
}
|
178
|
+
setMessage(messageQueue[0]);
|
179
|
+
setMessageQueue((prevQueue) => {
|
180
|
+
return prevQueue.slice(1);
|
181
|
+
});
|
182
|
+
}, [messageQueue, message]);
|
183
|
+
```
|
184
|
+
|
185
|
+
In this project, all events are handled in `hooks/useMapEvents` , each event, when is handled sets `message` to `null` -> `setMessage(null)`, for example:
|
186
|
+
|
187
|
+
```
|
188
|
+
useEffect(() => {
|
189
|
+
if (!message) return;
|
190
|
+
switch (message.type) {
|
191
|
+
case MAP_EVENTS.LOADED:
|
192
|
+
if (message.what === "map") {
|
193
|
+
console.log("useMapEvents Map loaded and ready");
|
194
|
+
setMapReady(true);
|
195
|
+
}
|
196
|
+
setMessage(null);
|
197
|
+
break;
|
198
|
+
|
199
|
+
default:
|
200
|
+
setMessage(null);
|
201
|
+
break;
|
202
|
+
}
|
203
|
+
}, [message, displayedLayers]);
|
204
|
+
```
|
205
|
+
|
206
|
+
## Adding elements to map
|
207
|
+
|
208
|
+
There's an example of how to add a point to map.
|
209
|
+
|
210
|
+
Button `Add point` starts the map component add point flow.
|
211
|
+
|
212
|
+
```
|
213
|
+
const { drawPoint } = useMessages();
|
214
|
+
<button
|
215
|
+
onClick={(e) => {
|
216
|
+
console.log("Draw point");
|
217
|
+
drawPoint({});
|
218
|
+
}}
|
219
|
+
>
|
220
|
+
Add point
|
221
|
+
</button>
|
222
|
+
```
|
223
|
+
When point is added, in `hooks/useMapEvents` we receive the event and we draw the geometry:
|
224
|
+
|
225
|
+
```
|
226
|
+
useEffect(() => {
|
227
|
+
if (!message) return;
|
228
|
+
switch (message.type) {
|
229
|
+
case MAP_EVENTS.GEOM_ADDED:
|
230
|
+
console.log("GeomAdded", message);
|
231
|
+
setCurrentMapAction(null);
|
232
|
+
//Highlight the added geom
|
233
|
+
Highlight(
|
234
|
+
{
|
235
|
+
feature_type: "HIGHLIGHT",
|
236
|
+
geom: message?.geom_astext,
|
237
|
+
},
|
238
|
+
2,
|
239
|
+
{
|
240
|
+
duration: 1500,
|
241
|
+
repeat: true,
|
242
|
+
},
|
243
|
+
0,
|
244
|
+
null
|
245
|
+
);
|
246
|
+
if (message?.geom_astext === null) {
|
247
|
+
setMessage(null);
|
248
|
+
return;
|
249
|
+
}
|
250
|
+
}, [message, displayedLayers]);
|
251
|
+
```
|
252
|
+
|
253
|
+
Check [Highlight method documentation](https://github.com/Vidro-Software-SL/maphandler?tab=readme-ov-file#higlight) to understand method params
|
254
|
+
|
255
|
+
## Filtering layers
|
256
|
+
|
257
|
+
In `components/MapFilters` there's an example of how to apply filters
|
258
|
+
|
259
|
+
Method `applyFilter`:
|
260
|
+
|
261
|
+
- Checks if there is an active layer selected.
|
262
|
+
- Constructs a filter object based on the selected layer and filter criteria.
|
263
|
+
- Sends the filter to the mapComponent to be applied on the map.
|
264
|
+
|
265
|
+
## Next.js
|
266
|
+
|
267
|
+
To learn more about Next.js, take a look at the following resources:
|
268
|
+
|
269
|
+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
270
|
+
- [Learn Next.js](https://nextjs.org/learn-pages-router) - an interactive Next.js tutorial.
|
271
|
+
|
272
|
+
## Tailwindcss
|
273
|
+
|
274
|
+
o learn more about Tailwindcss, take a look [https://tailwindcss.com](https://tailwindcss.com)
|
275
|
+
|
276
|
+
|
277
|
+
## Support
|
278
|
+
|
279
|
+
Join us in [Discord](https://discord.com/channels/1305257097843179642/1305257098313072714)
|
280
|
+
|
281
|
+

|
282
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
import { useAuth } from "@/contexts/auth";
|
2
|
+
import { useState } from "react";
|
3
|
+
|
4
|
+
const AuthComponent = () => {
|
5
|
+
const { apiUrl, setApiUrl, login, logged, logout } = useAuth();
|
6
|
+
const [user, setUser] = useState(
|
7
|
+
process.env.NEXT_PUBLIC_USER ? process.env.NEXT_PUBLIC_USER : ""
|
8
|
+
);
|
9
|
+
const [pwd, setPwd] = useState(
|
10
|
+
process.env.NEXT_PUBLIC_PWD ? process.env.NEXT_PUBLIC_PWD : ""
|
11
|
+
);
|
12
|
+
const [selectedMap, setSelectedMap] = useState("-1");
|
13
|
+
|
14
|
+
// Handler for API URL input changes
|
15
|
+
const handleApiUrlChange = (event) => {
|
16
|
+
setApiUrl(event.target.value);
|
17
|
+
};
|
18
|
+
|
19
|
+
return (
|
20
|
+
<div>
|
21
|
+
{logged && (
|
22
|
+
<div className="bg-gray-200 p-2 text-left">
|
23
|
+
<div className="text-xs">
|
24
|
+
Logged as <b>{user}</b>
|
25
|
+
</div>
|
26
|
+
<div className="text-xs">
|
27
|
+
API: <b>{apiUrl}</b>
|
28
|
+
</div>
|
29
|
+
<button
|
30
|
+
onClick={(e) => {
|
31
|
+
logout();
|
32
|
+
}}
|
33
|
+
className="border border-gray-300 bg-black text-white rounded-md p-2 mt-1 text-xs"
|
34
|
+
>
|
35
|
+
Logout
|
36
|
+
</button>
|
37
|
+
</div>
|
38
|
+
)}
|
39
|
+
{!logged && (
|
40
|
+
<div>
|
41
|
+
<div className="mb-4 flex gap-1">
|
42
|
+
<label className="w-32">API URL</label>
|
43
|
+
<input
|
44
|
+
type="text"
|
45
|
+
value={apiUrl}
|
46
|
+
onChange={handleApiUrlChange}
|
47
|
+
placeholder="Enter API URL..."
|
48
|
+
className="border border-gray-300 rounded-md p-2 w-full"
|
49
|
+
/>
|
50
|
+
</div>
|
51
|
+
<div className="mb-4 flex gap-1">
|
52
|
+
<label className="w-32">User</label>
|
53
|
+
<input
|
54
|
+
type="text"
|
55
|
+
value={user}
|
56
|
+
onChange={(e) => setUser(e.target.value)}
|
57
|
+
placeholder="User"
|
58
|
+
disabled={logged}
|
59
|
+
className="border border-gray-300 rounded-md p-2 w-full"
|
60
|
+
/>
|
61
|
+
</div>
|
62
|
+
<div className="mb-4 flex gap-1">
|
63
|
+
<label className="w-32">Password</label>
|
64
|
+
<input
|
65
|
+
type="password"
|
66
|
+
value={pwd}
|
67
|
+
onChange={(e) => setPwd(e.target.value)}
|
68
|
+
disabled={logged}
|
69
|
+
placeholder="User"
|
70
|
+
className="border border-gray-300 rounded-md p-2 w-full"
|
71
|
+
/>
|
72
|
+
</div>
|
73
|
+
|
74
|
+
<button
|
75
|
+
onClick={(e) => {
|
76
|
+
login(user, pwd);
|
77
|
+
}}
|
78
|
+
className="border border-gray-300 bg-black text-white rounded-md p-2"
|
79
|
+
>
|
80
|
+
Login
|
81
|
+
</button>
|
82
|
+
</div>
|
83
|
+
)}
|
84
|
+
</div>
|
85
|
+
);
|
86
|
+
};
|
87
|
+
|
88
|
+
export default AuthComponent;
|
@@ -0,0 +1,161 @@
|
|
1
|
+
import { useAuth } from "@/contexts/auth";
|
2
|
+
import { useMaps } from "@/contexts/maps";
|
3
|
+
import { useMessages } from "@/contexts/messages";
|
4
|
+
import { useEffect, useState } from "react";
|
5
|
+
|
6
|
+
const MapButtons = () => {
|
7
|
+
const { logged } = useAuth();
|
8
|
+
const { map, mapReady, clickedCoordinates } = useMaps();
|
9
|
+
const {
|
10
|
+
ZoomIn,
|
11
|
+
ZoomOut,
|
12
|
+
zoomToExtent,
|
13
|
+
drawPoint,
|
14
|
+
Clear,
|
15
|
+
Highlight,
|
16
|
+
centerMap,
|
17
|
+
} = useMessages();
|
18
|
+
if (!logged) return null;
|
19
|
+
return (
|
20
|
+
<>
|
21
|
+
{!mapReady && <div>Waiting map...</div>}
|
22
|
+
{mapReady && (
|
23
|
+
<div className="flex gap-1 text-xs">
|
24
|
+
<button
|
25
|
+
onClick={(e) => {
|
26
|
+
console.log("Zoom +");
|
27
|
+
ZoomIn();
|
28
|
+
}}
|
29
|
+
disabled={!map}
|
30
|
+
className="border border-gray-300 bg-black text-white rounded-md p-2"
|
31
|
+
>
|
32
|
+
Zoom +
|
33
|
+
</button>
|
34
|
+
<button
|
35
|
+
onClick={(e) => {
|
36
|
+
console.log("Zoom -");
|
37
|
+
ZoomOut();
|
38
|
+
}}
|
39
|
+
className="border border-gray-300 bg-black text-white rounded-md p-2"
|
40
|
+
>
|
41
|
+
Zoom -
|
42
|
+
</button>
|
43
|
+
<button
|
44
|
+
onClick={(e) => {
|
45
|
+
console.log("Zoom to extent");
|
46
|
+
zoomToExtent();
|
47
|
+
}}
|
48
|
+
className="border border-gray-300 bg-black text-white rounded-md p-2"
|
49
|
+
>
|
50
|
+
Zoom to extent
|
51
|
+
</button>
|
52
|
+
<button
|
53
|
+
onClick={(e) => {
|
54
|
+
console.log("Draw point");
|
55
|
+
drawPoint({});
|
56
|
+
}}
|
57
|
+
className="border border-gray-300 bg-black text-white rounded-md p-2"
|
58
|
+
>
|
59
|
+
Add point
|
60
|
+
</button>
|
61
|
+
<button
|
62
|
+
onClick={(e) => {
|
63
|
+
console.log("Clear");
|
64
|
+
Clear();
|
65
|
+
}}
|
66
|
+
className="border border-gray-300 bg-black text-white rounded-md p-2"
|
67
|
+
>
|
68
|
+
Clear
|
69
|
+
</button>
|
70
|
+
<button
|
71
|
+
onClick={(e) => {
|
72
|
+
console.log("Highlight clickedCoordinates", clickedCoordinates);
|
73
|
+
Highlight(
|
74
|
+
{
|
75
|
+
feature_type: "HIGHLIGHT",
|
76
|
+
geom: `POINT(${clickedCoordinates[0]} ${clickedCoordinates[1]})`,
|
77
|
+
},
|
78
|
+
2,
|
79
|
+
{
|
80
|
+
duration: 1500,
|
81
|
+
repeat: true,
|
82
|
+
},
|
83
|
+
0,
|
84
|
+
null
|
85
|
+
);
|
86
|
+
}}
|
87
|
+
disabled={!clickedCoordinates}
|
88
|
+
className="border border-gray-300 bg-black text-white rounded-md p-2"
|
89
|
+
>
|
90
|
+
Highlight clicked coordinates
|
91
|
+
</button>
|
92
|
+
<button
|
93
|
+
onClick={(e) => {
|
94
|
+
console.log("Highlight clickedCoordinates", clickedCoordinates);
|
95
|
+
centerMap(clickedCoordinates);
|
96
|
+
}}
|
97
|
+
disabled={!clickedCoordinates}
|
98
|
+
className="border border-gray-300 bg-black text-white rounded-md p-2"
|
99
|
+
>
|
100
|
+
Center map to clicked coordinates
|
101
|
+
</button>
|
102
|
+
<ZoomToScaleButton />
|
103
|
+
</div>
|
104
|
+
)}
|
105
|
+
</>
|
106
|
+
);
|
107
|
+
};
|
108
|
+
export default MapButtons;
|
109
|
+
|
110
|
+
const ZoomToScaleButton = () => {
|
111
|
+
const { zoomToScale } = useMessages();
|
112
|
+
const { mapScale } = useMaps();
|
113
|
+
const scales = [
|
114
|
+
{ id: "1:100", val: "1:100", label: "1:100" },
|
115
|
+
{ id: "1:200", val: "1:200", label: "1:200" },
|
116
|
+
{ id: "1:500", val: "1:500", label: "1:500" },
|
117
|
+
{ id: "1:1000", val: "1:1000", label: "1:1000" },
|
118
|
+
{ id: "1:2000", val: "1:2000", label: "1:2000" },
|
119
|
+
{ id: "1:5000", val: "1:5000", label: "1:5000" },
|
120
|
+
{ id: "1:10000", val: "1:10000", label: "1:10000" },
|
121
|
+
{ id: "1:50000", val: "1:50000", label: "1:50000" },
|
122
|
+
];
|
123
|
+
|
124
|
+
const [actualValue, setActualValue] = useState(mapScale ? mapScale : "-1");
|
125
|
+
useEffect(() => {
|
126
|
+
if (!mapScale) return;
|
127
|
+
const scale = scales.find((s) => s.val === mapScale);
|
128
|
+
if (scale) {
|
129
|
+
setActualValue(scale.val);
|
130
|
+
} else {
|
131
|
+
setActualValue("-1");
|
132
|
+
}
|
133
|
+
console.log("mapScale", mapScale, scale);
|
134
|
+
}, [mapScale]);
|
135
|
+
return (
|
136
|
+
<div className="mx-2 pt-2">
|
137
|
+
<label>Zoom to scale:</label>
|
138
|
+
<select
|
139
|
+
value={actualValue}
|
140
|
+
onChange={(e) => {
|
141
|
+
const newVal = !isNaN(e.target.value)
|
142
|
+
? Number(e.target.value)
|
143
|
+
: e.target.value;
|
144
|
+
setActualValue(newVal);
|
145
|
+
zoomToScale(newVal);
|
146
|
+
}}
|
147
|
+
>
|
148
|
+
<option key={`opt_select_scale`} value="-1">
|
149
|
+
Select scale...
|
150
|
+
</option>
|
151
|
+
{scales.map((opt, index) => {
|
152
|
+
return (
|
153
|
+
<option key={`opt_${index}`} value={opt.val}>
|
154
|
+
{opt.label}
|
155
|
+
</option>
|
156
|
+
);
|
157
|
+
})}
|
158
|
+
</select>
|
159
|
+
</div>
|
160
|
+
);
|
161
|
+
};
|
@@ -0,0 +1,120 @@
|
|
1
|
+
import { useAuth } from "@/contexts/auth";
|
2
|
+
import { useMaps } from "@/contexts/maps";
|
3
|
+
import { useMessages } from "@/contexts/messages";
|
4
|
+
import { useEffect, useState } from "react";
|
5
|
+
|
6
|
+
const MapFilters = () => {
|
7
|
+
const {
|
8
|
+
configuredFilters,
|
9
|
+
mapId,
|
10
|
+
filters,
|
11
|
+
activeFilters,
|
12
|
+
|
13
|
+
activeLayer,
|
14
|
+
mapLayers,
|
15
|
+
} = useMaps();
|
16
|
+
const { Filters } = useMessages();
|
17
|
+
const { logged } = useAuth();
|
18
|
+
const [currentFilter, setCurrentFilter] = useState("");
|
19
|
+
const [filterValue, setFilterValue] = useState("");
|
20
|
+
useEffect(() => {
|
21
|
+
if (!configuredFilters) return;
|
22
|
+
console.log("MapFilters configuredFilters", configuredFilters);
|
23
|
+
}, [configuredFilters]);
|
24
|
+
|
25
|
+
useEffect(() => {
|
26
|
+
if (!activeFilters) return;
|
27
|
+
console.log("MapFilters activeFilters", activeFilters);
|
28
|
+
}, [activeFilters]);
|
29
|
+
|
30
|
+
useEffect(() => {
|
31
|
+
if (!filters) return;
|
32
|
+
console.log("MapFilters filters", filters);
|
33
|
+
}, [filters]);
|
34
|
+
|
35
|
+
const applyFilter = () => {
|
36
|
+
if (!activeLayer) {
|
37
|
+
console.log("No active layer");
|
38
|
+
return;
|
39
|
+
}
|
40
|
+
console.log("Apply filter", {
|
41
|
+
currentFilter,
|
42
|
+
filterValue,
|
43
|
+
activeLayer,
|
44
|
+
});
|
45
|
+
//find layer info
|
46
|
+
const lay = mapLayers.find((l) => l.qgis_name === activeLayer);
|
47
|
+
console.log("Layer info", lay);
|
48
|
+
//format filter for mapComponent digest
|
49
|
+
const filter = [
|
50
|
+
{
|
51
|
+
layer_id: lay.id,
|
52
|
+
layer_name: lay.qgis_name,
|
53
|
+
filters: [
|
54
|
+
[
|
55
|
+
{
|
56
|
+
name: currentFilter,
|
57
|
+
condition: "=", //default condition - you can get condition from configuredFilters
|
58
|
+
value: filterValue,
|
59
|
+
value2: null,
|
60
|
+
layer_id: lay.id,
|
61
|
+
mapId: mapId,
|
62
|
+
},
|
63
|
+
],
|
64
|
+
],
|
65
|
+
},
|
66
|
+
];
|
67
|
+
Filters(filter);
|
68
|
+
console.log("Filter", filter);
|
69
|
+
};
|
70
|
+
|
71
|
+
if (!logged) return null;
|
72
|
+
if (!mapId) return null;
|
73
|
+
return (
|
74
|
+
<>
|
75
|
+
<div>
|
76
|
+
{configuredFilters && configuredFilters.length === 0 && (
|
77
|
+
<div>No filters available</div>
|
78
|
+
)}
|
79
|
+
|
80
|
+
<div className="m-2 flex gap-2">
|
81
|
+
<label className="block text-sm font-medium text-gray-700 pt-2">
|
82
|
+
Filters
|
83
|
+
</label>
|
84
|
+
{configuredFilters && configuredFilters.length > 0 && (
|
85
|
+
<select
|
86
|
+
className="border border-gray-300 rounded-md p-2 w-60"
|
87
|
+
onChange={(e) => setCurrentFilter(e.target.value)}
|
88
|
+
value={currentFilter}
|
89
|
+
>
|
90
|
+
<option value="-1">Select filter...</option>
|
91
|
+
{configuredFilters.map((ele) => (
|
92
|
+
<option key={ele.id} value={ele.name}>
|
93
|
+
{ele.name}
|
94
|
+
</option>
|
95
|
+
))}
|
96
|
+
</select>
|
97
|
+
)}
|
98
|
+
<input
|
99
|
+
type="text"
|
100
|
+
value={filterValue}
|
101
|
+
onChange={(e) => setFilterValue(e.target.value)}
|
102
|
+
className="border border-gray-300 rounded-md p-2 w-60"
|
103
|
+
/>
|
104
|
+
<button
|
105
|
+
onClick={(e) => {
|
106
|
+
console.log("Apply filter");
|
107
|
+
applyFilter();
|
108
|
+
}}
|
109
|
+
disabled={filterValue === "" || currentFilter === "-1"}
|
110
|
+
className="border border-gray-300 bg-black text-white rounded-md p-2 mt-1 text-xs"
|
111
|
+
>
|
112
|
+
Apply filter
|
113
|
+
</button>
|
114
|
+
</div>
|
115
|
+
</div>
|
116
|
+
</>
|
117
|
+
);
|
118
|
+
};
|
119
|
+
|
120
|
+
export default MapFilters;
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import { useMaps } from "@/contexts/maps";
|
2
|
+
import { useMessages } from "@/contexts/messages";
|
3
|
+
import { useEffect } from "react";
|
4
|
+
|
5
|
+
const MapIframe = () => {
|
6
|
+
const { map, sessionToken } = useMaps();
|
7
|
+
const { start } = useMessages();
|
8
|
+
|
9
|
+
useEffect(() => {
|
10
|
+
if (!sessionToken) return;
|
11
|
+
start(sessionToken); //starts communicator
|
12
|
+
}, [sessionToken]);
|
13
|
+
|
14
|
+
if (!map && !sessionToken) return null;
|
15
|
+
return (
|
16
|
+
<iframe
|
17
|
+
id="map-frame"
|
18
|
+
name="map-frame"
|
19
|
+
src={map}
|
20
|
+
allow="geolocation"
|
21
|
+
className="h-screen w-full"
|
22
|
+
></iframe>
|
23
|
+
);
|
24
|
+
};
|
25
|
+
export default MapIframe;
|