@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.
Files changed (42) hide show
  1. package/README.md +367 -48
  2. package/dist/map-handler.js +1 -1
  3. package/doc/confirmComponent.png +0 -0
  4. package/examples/full/apidemo.js +46 -43
  5. package/examples/full/cachedToken.dat +1 -1
  6. package/examples/full/cachedTokenData.dat +1 -1
  7. package/examples/full/docker/Docker_compose.yml +14 -0
  8. package/examples/full/docker/Dockerfile +27 -0
  9. package/examples/full/index.php +5 -4
  10. package/examples/full/tester.css +74 -0
  11. package/examples/full/tester.js +2 -2
  12. package/examples/react-next/README.md +282 -0
  13. package/examples/react-next/components/AuthComponent.js +88 -0
  14. package/examples/react-next/components/MapButtons.js +161 -0
  15. package/examples/react-next/components/MapFilters.js +120 -0
  16. package/examples/react-next/components/MapIframe.js +25 -0
  17. package/examples/react-next/components/MapInfo.js +36 -0
  18. package/examples/react-next/components/MapLayers.js +60 -0
  19. package/examples/react-next/components/MapList.js +43 -0
  20. package/examples/react-next/contexts/auth.js +101 -0
  21. package/examples/react-next/contexts/maps.js +158 -0
  22. package/examples/react-next/contexts/messages.js +340 -0
  23. package/examples/react-next/env.sample +3 -0
  24. package/examples/react-next/eslint.config.mjs +14 -0
  25. package/examples/react-next/hooks/useMapEvents.js +118 -0
  26. package/examples/react-next/jsconfig.json +7 -0
  27. package/examples/react-next/next.config.mjs +6 -0
  28. package/examples/react-next/package.json +24 -0
  29. package/examples/react-next/pages/_app.js +5 -0
  30. package/examples/react-next/pages/index.js +87 -0
  31. package/examples/react-next/postcss.config.mjs +8 -0
  32. package/examples/react-next/public/discord.svg +8 -0
  33. package/examples/react-next/public/favicon.ico +0 -0
  34. package/examples/react-next/public/file.svg +1 -0
  35. package/examples/react-next/public/logo.png +0 -0
  36. package/examples/react-next/public/next.svg +1 -0
  37. package/examples/react-next/shared/constants.js +47 -0
  38. package/examples/react-next/styles/globals.css +24 -0
  39. package/examples/react-next/tailwind.config.mjs +17 -0
  40. package/helpers.md +45 -0
  41. package/package.json +1 -1
  42. package/src/index.js +263 -23
@@ -0,0 +1,340 @@
1
+ import { createContext, useContext, useEffect, useState } from "react";
2
+ import { MAP_EVENTS } from "@/shared/constants";
3
+
4
+ const MessageContext = createContext({});
5
+
6
+ export const MessageProvider = ({ children }) => {
7
+ const [events, setEvents] = useState(false);
8
+ const [communicator, setCommunicator] = useState(false);
9
+ const [message, setMessage] = useState(null);
10
+ const [messageQueue, setMessageQueue] = useState([]);
11
+
12
+ useEffect(() => {
13
+ if (!communicator) return;
14
+ console.log("MapHandler is ready for use");
15
+ }, [communicator]);
16
+
17
+ const start = async (sessionToken) => {
18
+ const { Communicator } = await import("@vidro/map-handler");
19
+ const _communicator = new Communicator({ sessionToken });
20
+ setCommunicator(_communicator);
21
+ };
22
+
23
+ const reloadLayers = () => {
24
+ if (!communicator) return;
25
+ communicator.reloadDisplayedLayers();
26
+ };
27
+
28
+ const ZoomIn = () => {
29
+ console.log("ZoomIn");
30
+ if (!communicator) return;
31
+ console.log("ZoomIn 2");
32
+ communicator.ZoomIn();
33
+ };
34
+
35
+ const zoomToExtent = () => {
36
+ if (!communicator) return;
37
+ communicator.zoomToExtent();
38
+ };
39
+
40
+ const ZoomOut = () => {
41
+ if (!communicator) return;
42
+ communicator.ZoomOut();
43
+ };
44
+
45
+ const Clear = () => {
46
+ if (!communicator) return;
47
+ communicator.clear();
48
+ };
49
+
50
+ const Info = (type = "geojson", layer, format = "json") => {
51
+ const hitTolerance = 5;
52
+ communicator.infoFromCoordinates(type, layer, hitTolerance, format);
53
+ };
54
+
55
+ const Geolocalize = (what) => {
56
+ if (!communicator) return;
57
+ communicator.Geolocalize(what);
58
+ };
59
+
60
+ const Filters = (filters) => {
61
+ if (!communicator) return;
62
+ console.log("messages - Filters", filters);
63
+ communicator.setFilters(filters);
64
+ };
65
+
66
+ const RemoveElementFromMap = (id, layer) => {
67
+ communicator.RemoveGeometry(id, layer);
68
+ };
69
+
70
+ const CancelAddGeom = () => {
71
+ if (!communicator) return;
72
+ console.log("CancelAddGeom");
73
+ communicator.CancelAddGeom();
74
+ };
75
+
76
+ const Highlight = (item, center, animate, id, style, zoom = null) => {
77
+ if (!communicator) return;
78
+ console.log("Highlight", { item, center, animate, id, style, zoom });
79
+ let options = {
80
+ geom: item.geom,
81
+ zoom,
82
+ center,
83
+ animate,
84
+ data: item,
85
+ style,
86
+ };
87
+ communicator.Highlight(options);
88
+ };
89
+
90
+ const RemoveGeometriesByProperty = (layer, property, value) => {
91
+ if (!communicator) return;
92
+ communicator.RemoveGeometriesByProperty(layer, property, value);
93
+ };
94
+
95
+ const UpdateGeometriesByProperty = (layer, property, value, style) => {
96
+ if (!communicator) return;
97
+ console.log("UpdateGeometriesByProperty", {
98
+ layer,
99
+ property,
100
+ value,
101
+ style,
102
+ });
103
+ communicator.UpdateGeometriesByProperty(layer, property, value, style);
104
+ };
105
+
106
+ const DrawGeometries = (geoms) => {
107
+ if (!communicator) return;
108
+ console.log("DrawGeometries", geoms);
109
+ communicator.DrawGeometries(geoms);
110
+ };
111
+
112
+ const DrawGeometry = (geom, featureId, style) => {
113
+ if (!communicator) return;
114
+ console.log("DrawGeometry", { geom, featureId, style });
115
+ communicator.DrawGeometries([{ geom, style, featureId, id: featureId }]);
116
+ };
117
+
118
+ const ToggleLayer = (layer, properties) => {
119
+ if (!communicator) return;
120
+ communicator.toggleLayer(layer, properties);
121
+ };
122
+
123
+ const removeLayer = (name) => {
124
+ if (!communicator) return;
125
+ console.log("removeLayer", { name });
126
+ communicator.removeLayer(name);
127
+ };
128
+
129
+ const startDrawPolygon = () => {
130
+ const options = {
131
+ texts: {
132
+ start: t({
133
+ id: "COMMON.COMMON.CLICK_TO_START_DRAWING",
134
+ }),
135
+ continue: t({
136
+ id: "COMMON.COMMON.CLICK_TO_CONTINUE_DRAWING_POLYGON",
137
+ }),
138
+ },
139
+ style: {
140
+ fill: "rgb(249, 34, 34,0.3)",
141
+ stroke: "rgb(249, 34, 34)",
142
+ },
143
+ drawOnEnd: false,
144
+ showConfirm: false,
145
+ };
146
+ communicator.AddGeom("Polygon", options);
147
+ };
148
+
149
+ const BBoxForClicks = (size) => {
150
+ if (!communicator) return;
151
+ communicator.setBboxSize(size);
152
+ };
153
+
154
+ const cancelMeasure = () => {
155
+ if (!communicator) return;
156
+ communicator.cancelMeasure();
157
+ };
158
+
159
+ const initMeasure = (type, startMsg, continueMsg) => {
160
+ if (!communicator) return;
161
+ communicator.cancelMeasure();
162
+ communicator.initMeasure(type, startMsg, continueMsg);
163
+ };
164
+
165
+ const zoomToCoordinates = (coordinates, zoomLevel) => {
166
+ if (!communicator) return;
167
+ communicator.zoomToCoordinates(coordinates[0], coordinates[1], zoomLevel);
168
+ };
169
+
170
+ const zoomToGeometry = (geom, limits) => {
171
+ if (!communicator) return;
172
+ communicator.zoomToGeometry(geom, limits);
173
+ };
174
+
175
+ const centerMap = (coordinates) => {
176
+ if (!communicator) return;
177
+ communicator.CenterMap(coordinates[0], coordinates[1]);
178
+ };
179
+
180
+ const drawPoint = ({ drawOnEnd = false, showConfirm = false }) => {
181
+ if (!communicator) return;
182
+ communicator.AddGeom("Point", { drawOnEnd, showConfirm: true });
183
+ };
184
+
185
+ const addIcon = (icon, coordinates) => {
186
+ communicator.addIcon({ icon, coordinates });
187
+ };
188
+
189
+ const zoomToScale = (scale) => {
190
+ const allowedScales = [
191
+ "1:100",
192
+ "1:200",
193
+ "1:400",
194
+ "1:500",
195
+ "1:1000",
196
+ "1:2000",
197
+ "1:5000",
198
+ "1:10000",
199
+ "1:50000",
200
+ ];
201
+ if (!allowedScales.includes(scale)) {
202
+ console.error(
203
+ `Invalid scale: ${scale}. Allowed values are: ${allowedScales.join(
204
+ ", "
205
+ )}`
206
+ );
207
+ return;
208
+ }
209
+ if (!communicator) return;
210
+ console.log("zoomToScale", { scale });
211
+ communicator.zoomToScale(scale);
212
+ };
213
+
214
+ const onMapEvent = (data) => {
215
+ console.log(`onMapEvent`, { type: data.type, data });
216
+ setMessageQueue((prevQueue) => [...prevQueue, data]);
217
+ };
218
+
219
+ useEffect(() => {
220
+ if (message) return;
221
+ if (messageQueue.length === 0) {
222
+ return;
223
+ }
224
+ setMessage(messageQueue[0]);
225
+ setMessageQueue((prevQueue) => {
226
+ return prevQueue.slice(1);
227
+ });
228
+ }, [messageQueue, message]);
229
+
230
+ useEffect(() => {
231
+ if (!communicator) return;
232
+ communicator.on(MAP_EVENTS.ZOOM_CHANGE, onMapEvent);
233
+ communicator.on(MAP_EVENTS.LOADED, onMapEvent);
234
+ communicator.on(MAP_EVENTS.CAPABILITIES, onMapEvent);
235
+ communicator.on(MAP_EVENTS.ERROR, onMapEvent);
236
+ communicator.on(MAP_EVENTS.GEOLOCATION, onMapEvent);
237
+ communicator.on(MAP_EVENTS.END_MEASURE, onMapEvent);
238
+ communicator.on(MAP_EVENTS.START_MEASURE, onMapEvent);
239
+ communicator.on(MAP_EVENTS.UNLOADED, onMapEvent);
240
+ communicator.on(MAP_EVENTS.COORDINATES, onMapEvent);
241
+ communicator.on(MAP_EVENTS.CENTER_CHANGE, onMapEvent);
242
+ communicator.on(MAP_EVENTS.ACTIVE_LAYER, onMapEvent);
243
+ communicator.on(MAP_EVENTS.WMS_LAYERS, onMapEvent);
244
+ communicator.on(MAP_EVENTS.STATUS, onMapEvent);
245
+ communicator.on(MAP_EVENTS.INFO, onMapEvent);
246
+ communicator.on(MAP_EVENTS.GEOM_ADDED, onMapEvent);
247
+ communicator.on(MAP_EVENTS.LAYERS, onMapEvent);
248
+ communicator.on(MAP_EVENTS.VERSION, onMapEvent);
249
+ communicator.on(MAP_EVENTS.SCREENSHOT, onMapEvent);
250
+ return () => {
251
+ if (!communicator) return;
252
+ communicator.off(MAP_EVENTS.ZOOM_CHANGE, onMapEvent);
253
+ communicator.off(MAP_EVENTS.LOADED, onMapEvent);
254
+ communicator.off(MAP_EVENTS.CAPABILITIES, onMapEvent);
255
+ communicator.off(MAP_EVENTS.ERROR, onMapEvent);
256
+ communicator.off(MAP_EVENTS.GEOLOCATION, onMapEvent);
257
+ communicator.off(MAP_EVENTS.END_MEASURE, onMapEvent);
258
+ communicator.off(MAP_EVENTS.START_MEASURE, onMapEvent);
259
+ communicator.off(MAP_EVENTS.UNLOADED, onMapEvent);
260
+ communicator.off(MAP_EVENTS.COORDINATES, onMapEvent);
261
+ communicator.off(MAP_EVENTS.CENTER_CHANGE, onMapEvent);
262
+ communicator.off(MAP_EVENTS.ACTIVE_LAYER, onMapEvent);
263
+ communicator.off(MAP_EVENTS.WMS_LAYERS, onMapEvent);
264
+ communicator.off(MAP_EVENTS.STATUS, onMapEvent);
265
+ communicator.off(MAP_EVENTS.INFO, onMapEvent);
266
+ communicator.off(MAP_EVENTS.LAYERS, onMapEvent);
267
+ communicator.off(MAP_EVENTS.VERSION, onMapEvent);
268
+ communicator.off(MAP_EVENTS.SCREENSHOT, onMapEvent);
269
+ setCommunicator(null);
270
+ };
271
+ }, [communicator, events]);
272
+
273
+ useEffect(() => {
274
+ // Clean up
275
+ return function cleanup() {
276
+ if (communicator) {
277
+ communicator.off(MAP_EVENTS.ZOOM_CHANGE, onMapEvent);
278
+ communicator.off(MAP_EVENTS.LOADED, onMapEvent);
279
+ communicator.off(MAP_EVENTS.CAPABILITIES, onMapEvent);
280
+ communicator.off(MAP_EVENTS.ERROR, onMapEvent);
281
+ communicator.off(MAP_EVENTS.GEOLOCATION, onMapEvent);
282
+ communicator.off(MAP_EVENTS.END_MEASURE, onMapEvent);
283
+ communicator.off(MAP_EVENTS.START_MEASURE, onMapEvent);
284
+ communicator.off(MAP_EVENTS.UNLOADED, onMapEvent);
285
+ communicator.off(MAP_EVENTS.COORDINATES, onMapEvent);
286
+ communicator.off(MAP_EVENTS.CENTER_CHANGE, onMapEvent);
287
+ communicator.off(MAP_EVENTS.ACTIVE_LAYER, onMapEvent);
288
+ communicator.off(MAP_EVENTS.WMS_LAYERS, onMapEvent);
289
+ communicator.off(MAP_EVENTS.STATUS, onMapEvent);
290
+ communicator.off(MAP_EVENTS.INFO, onMapEvent);
291
+ communicator.off(MAP_EVENTS.VERSION, onMapEvent);
292
+ }
293
+ setMessage(null);
294
+ setEvents(false);
295
+ };
296
+ }, []);
297
+
298
+ return (
299
+ <MessageContext.Provider
300
+ value={{
301
+ communicator,
302
+ start,
303
+ ZoomIn,
304
+ ZoomOut,
305
+ zoomToExtent,
306
+ DrawGeometry,
307
+ DrawGeometries,
308
+
309
+ startDrawPolygon,
310
+ CancelAddGeom,
311
+ Clear,
312
+ Info,
313
+ RemoveElementFromMap,
314
+ Highlight,
315
+ RemoveGeometriesByProperty,
316
+ UpdateGeometriesByProperty,
317
+ ToggleLayer,
318
+ message,
319
+ setMessage,
320
+ Filters,
321
+ reloadLayers,
322
+ BBoxForClicks,
323
+ cancelMeasure,
324
+ initMeasure,
325
+ removeLayer,
326
+ Geolocalize,
327
+ zoomToCoordinates,
328
+ zoomToGeometry,
329
+ centerMap,
330
+ drawPoint,
331
+ addIcon,
332
+ zoomToScale,
333
+ }}
334
+ >
335
+ {children}
336
+ </MessageContext.Provider>
337
+ );
338
+ };
339
+
340
+ export const useMessages = () => useContext(MessageContext);
@@ -0,0 +1,3 @@
1
+ NEXT_PUBLIC_APIURL="https://your api url"
2
+ NEXT_PUBLIC_USER="your user"
3
+ NEXT_PUBLIC_PWD="your password"
@@ -0,0 +1,14 @@
1
+ import { dirname } from "path";
2
+ import { fileURLToPath } from "url";
3
+ import { FlatCompat } from "@eslint/eslintrc";
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+
8
+ const compat = new FlatCompat({
9
+ baseDirectory: __dirname,
10
+ });
11
+
12
+ const eslintConfig = [...compat.extends("next/core-web-vitals")];
13
+
14
+ export default eslintConfig;
@@ -0,0 +1,118 @@
1
+ "use client";
2
+ import { useEffect } from "react";
3
+ import { MAP_EVENTS } from "@/shared/constants";
4
+ import { useMessages } from "@/contexts/messages";
5
+ import { useMaps } from "@/contexts/maps";
6
+ const useMapEvents = () => {
7
+ const {
8
+ setMapReady,
9
+ setDisplayedLayers,
10
+ setCurrentMapAction,
11
+ displayedLayers,
12
+ setZoomLevel,
13
+ mapReady,
14
+ setMapScale,
15
+ setClickedCoordinates,
16
+ setSrid,
17
+ setBboxcoordinates,
18
+ setMapResolution,
19
+ } = useMaps();
20
+ const { message, setMessage, Highlight } = useMessages();
21
+
22
+ useEffect(() => {
23
+ if (!message) return;
24
+ if (!mapReady) return;
25
+
26
+ switch (message.type) {
27
+ case MAP_EVENTS.ZOOM_CHANGE:
28
+ console.log("useMapEvents Zoom changed", message);
29
+ setZoomLevel(message.zoom);
30
+ setMapScale(message?.meta?.scale);
31
+ setMapResolution(message?.meta?.resolution);
32
+ setMessage(null);
33
+ break;
34
+ case MAP_EVENTS.CENTER_CHANGE:
35
+ setMessage(null);
36
+ break;
37
+ default:
38
+ setMessage(null);
39
+ break;
40
+ }
41
+ }, [message, mapReady]);
42
+
43
+ useEffect(() => {
44
+ if (!message) return;
45
+ switch (message.type) {
46
+ case MAP_EVENTS.LOADED:
47
+ if (message.what === "map") {
48
+ console.log("useMapEvents Map loaded and ready");
49
+ setMapReady(true);
50
+ } else if (message.what === "tiled") {
51
+ console.log("useMapEvents Tiled loaded");
52
+ }
53
+ if (message.what === "layer") {
54
+ }
55
+ setMessage(null);
56
+ break;
57
+ case MAP_EVENTS.UNLOADED:
58
+ if (message.what === "tiled") {
59
+ console.log("useMapEvents Tiled unloaded");
60
+ }
61
+ setMessage(null);
62
+ break;
63
+ case MAP_EVENTS.LAYERS:
64
+ console.log("useMapEvents displayed layers", message.layers);
65
+ if (!message.layers) returns;
66
+ //check if is already displayed
67
+ setDisplayedLayers(message.layers);
68
+ setMessage(null);
69
+ break;
70
+ case MAP_EVENTS.GEOM_ADDED:
71
+ console.log("GeomAdded", message);
72
+ setCurrentMapAction(null);
73
+ //Highlight the added geom
74
+ Highlight(
75
+ {
76
+ feature_type: "HIGHLIGHT",
77
+ geom: message?.geom_astext,
78
+ },
79
+ 2,
80
+ {
81
+ duration: 1500,
82
+ repeat: true,
83
+ },
84
+ 0,
85
+ null
86
+ );
87
+ if (message?.geom_astext === null) {
88
+ setMessage(null);
89
+ return;
90
+ }
91
+
92
+ /************************************************************************************
93
+ ***************** CLICKED COORDINATES *************************
94
+ ***********************************************************************************/
95
+
96
+ //***************** MAP CLICKED COORDINATES *************************
97
+ case MAP_EVENTS.COORDINATES:
98
+ console.log("clicked coordinates", message);
99
+
100
+ setClickedCoordinates(message.coordinates);
101
+ setSrid(message.srid);
102
+ setBboxcoordinates(message.bbox);
103
+ setMessage(null);
104
+ break;
105
+
106
+ /************************************************************************************
107
+ ***************** END CLICKED COORDINATES *************************
108
+ ***********************************************************************************/
109
+
110
+ default:
111
+ setMessage(null);
112
+ break;
113
+ }
114
+ }, [message, displayedLayers]);
115
+
116
+ return {};
117
+ };
118
+ export default useMapEvents;
@@ -0,0 +1,7 @@
1
+ {
2
+ "compilerOptions": {
3
+ "paths": {
4
+ "@/*": ["./*"]
5
+ }
6
+ }
7
+ }
@@ -0,0 +1,6 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ reactStrictMode: true,
4
+ };
5
+
6
+ export default nextConfig;
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "react-next",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev -p",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint"
10
+ },
11
+ "dependencies": {
12
+ "@vidro/map-handler": "^1.2.179",
13
+ "next": "15.1.6",
14
+ "react": "^19.0.0",
15
+ "react-dom": "^19.0.0"
16
+ },
17
+ "devDependencies": {
18
+ "@eslint/eslintrc": "^3",
19
+ "eslint": "^9",
20
+ "eslint-config-next": "15.1.6",
21
+ "postcss": "^8",
22
+ "tailwindcss": "^3.4.1"
23
+ }
24
+ }
@@ -0,0 +1,5 @@
1
+ import "@/styles/globals.css";
2
+
3
+ export default function App({ Component, pageProps }) {
4
+ return <Component {...pageProps} />;
5
+ }
@@ -0,0 +1,87 @@
1
+ import MapButtons from "@/components/MapButtons";
2
+ import MapIframe from "@/components/MapIframe";
3
+ import MapInfo from "@/components/MapInfo";
4
+ import AuthComponent from "@/components/AuthComponent";
5
+ import { AuthProvider } from "@/contexts/auth";
6
+ import { MapsProvider } from "@/contexts/maps";
7
+ import { MessageProvider } from "@/contexts/messages";
8
+ import Image from "next/image";
9
+ import MapList from "@/components/MapList";
10
+ import MapLayers from "@/components/MapLayers";
11
+ import MapFilters from "@/components/MapFilters";
12
+
13
+ export default function Home() {
14
+ return (
15
+ <div>
16
+ <AuthProvider>
17
+ <MessageProvider>
18
+ <MapsProvider>
19
+ <main className="text-center">
20
+ <h1 className="text-3xl">Map component REACT integration</h1>
21
+ <div className="my-5">
22
+ <AuthComponent />
23
+ </div>
24
+ <div className="my-5">
25
+ <MapList />
26
+ <MapLayers />
27
+ <MapFilters />
28
+ </div>
29
+
30
+ <div className="my-5 bg-gray-100 border border-gray-400 p-4">
31
+ <MapButtons />
32
+ <MapInfo />
33
+ <MapIframe />
34
+ </div>
35
+ </main>
36
+ </MapsProvider>
37
+ </MessageProvider>
38
+ </AuthProvider>
39
+ <footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
40
+ <a
41
+ className="flex items-center gap-2 hover:underline hover:underline-offset-4"
42
+ href="https://www.vidrosoftware.com"
43
+ target="_blank"
44
+ rel="noopener noreferrer"
45
+ >
46
+ <Image
47
+ aria-hidden
48
+ src="/logo.png"
49
+ alt="Vidrosoftware"
50
+ width={150}
51
+ height={120}
52
+ />
53
+ </a>
54
+ <a
55
+ className="flex items-center gap-2 hover:underline hover:underline-offset-4"
56
+ href="https://github.com/Vidro-Software-SL/maphandler"
57
+ target="_blank"
58
+ rel="noopener noreferrer"
59
+ >
60
+ <Image
61
+ aria-hidden
62
+ src="/file.svg"
63
+ alt="Window icon"
64
+ width={16}
65
+ height={16}
66
+ />
67
+ Documentation
68
+ </a>
69
+ <a
70
+ className="flex items-center gap-2 hover:underline hover:underline-offset-4"
71
+ href="https://discord.com/channels/1305257097843179642/1305257098313072714"
72
+ target="_blank"
73
+ rel="noopener noreferrer"
74
+ >
75
+ <Image
76
+ aria-hidden
77
+ src="/discord.svg"
78
+ alt="Discord icon"
79
+ width={16}
80
+ height={16}
81
+ />
82
+ Discord channel
83
+ </a>
84
+ </footer>
85
+ </div>
86
+ );
87
+ }
@@ -0,0 +1,8 @@
1
+ /** @type {import('postcss-load-config').Config} */
2
+ const config = {
3
+ plugins: {
4
+ tailwindcss: {},
5
+ },
6
+ };
7
+
8
+ export default config;
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
3
+ <svg width="800px" height="800px" viewBox="0 -28.5 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
4
+ <g>
5
+ <path d="M216.856339,16.5966031 C200.285002,8.84328665 182.566144,3.2084988 164.041564,0 C161.766523,4.11318106 159.108624,9.64549908 157.276099,14.0464379 C137.583995,11.0849896 118.072967,11.0849896 98.7430163,14.0464379 C96.9108417,9.64549908 94.1925838,4.11318106 91.8971895,0 C73.3526068,3.2084988 55.6133949,8.86399117 39.0420583,16.6376612 C5.61752293,67.146514 -3.4433191,116.400813 1.08711069,164.955721 C23.2560196,181.510915 44.7403634,191.567697 65.8621325,198.148576 C71.0772151,190.971126 75.7283628,183.341335 79.7352139,175.300261 C72.104019,172.400575 64.7949724,168.822202 57.8887866,164.667963 C59.7209612,163.310589 61.5131304,161.891452 63.2445898,160.431257 C105.36741,180.133187 151.134928,180.133187 192.754523,160.431257 C194.506336,161.891452 196.298154,163.310589 198.110326,164.667963 C191.183787,168.842556 183.854737,172.420929 176.223542,175.320965 C180.230393,183.341335 184.861538,190.991831 190.096624,198.16893 C211.238746,191.588051 232.743023,181.531619 254.911949,164.955721 C260.227747,108.668201 245.831087,59.8662432 216.856339,16.5966031 Z M85.4738752,135.09489 C72.8290281,135.09489 62.4592217,123.290155 62.4592217,108.914901 C62.4592217,94.5396472 72.607595,82.7145587 85.4738752,82.7145587 C98.3405064,82.7145587 108.709962,94.5189427 108.488529,108.914901 C108.508531,123.290155 98.3405064,135.09489 85.4738752,135.09489 Z M170.525237,135.09489 C157.88039,135.09489 147.510584,123.290155 147.510584,108.914901 C147.510584,94.5396472 157.658606,82.7145587 170.525237,82.7145587 C183.391518,82.7145587 193.761324,94.5189427 193.539891,108.914901 C193.539891,123.290155 183.391518,135.09489 170.525237,135.09489 Z" fill="#5865F2" fill-rule="nonzero">
6
+
7
+ </g>
8
+ </svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>