feat: add leaflet visitor map if user not wanna register any token
This commit is contained in:
parent
4ed941b013
commit
3baa1ab55b
@ -110,6 +110,9 @@ importers:
|
|||||||
filesize:
|
filesize:
|
||||||
specifier: ^10.0.12
|
specifier: ^10.0.12
|
||||||
version: 10.0.12
|
version: 10.0.12
|
||||||
|
leaflet:
|
||||||
|
specifier: ^1.9.4
|
||||||
|
version: 1.9.4
|
||||||
lodash-es:
|
lodash-es:
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
@ -134,6 +137,9 @@ importers:
|
|||||||
react-icons:
|
react-icons:
|
||||||
specifier: ^4.12.0
|
specifier: ^4.12.0
|
||||||
version: 4.12.0(react@18.2.0)
|
version: 4.12.0(react@18.2.0)
|
||||||
|
react-leaflet:
|
||||||
|
specifier: ^4.2.1
|
||||||
|
version: 4.2.1(leaflet@1.9.4)(react-dom@18.2.0)(react@18.2.0)
|
||||||
react-resizable:
|
react-resizable:
|
||||||
specifier: ^3.0.5
|
specifier: ^3.0.5
|
||||||
version: 3.0.5(react-dom@18.2.0)(react@18.2.0)
|
version: 3.0.5(react-dom@18.2.0)(react@18.2.0)
|
||||||
@ -159,6 +165,9 @@ importers:
|
|||||||
specifier: ^4.4.1
|
specifier: ^4.4.1
|
||||||
version: 4.4.1(@types/react@18.2.21)(react@18.2.0)
|
version: 4.4.1(@types/react@18.2.21)(react@18.2.0)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@types/leaflet':
|
||||||
|
specifier: ^1.9.8
|
||||||
|
version: 1.9.8
|
||||||
'@types/loadable__component':
|
'@types/loadable__component':
|
||||||
specifier: ^5.13.8
|
specifier: ^5.13.8
|
||||||
version: 5.13.8
|
version: 5.13.8
|
||||||
@ -6498,6 +6507,18 @@ packages:
|
|||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@react-leaflet/core@2.1.0(leaflet@1.9.4)(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==}
|
||||||
|
peerDependencies:
|
||||||
|
leaflet: ^1.9.0
|
||||||
|
react: ^18.0.0
|
||||||
|
react-dom: ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
leaflet: 1.9.4
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@reduxjs/toolkit@1.9.7(react-redux@7.2.9)(react@17.0.2):
|
/@reduxjs/toolkit@1.9.7(react-redux@7.2.9)(react@17.0.2):
|
||||||
resolution: {integrity: sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==}
|
resolution: {integrity: sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -8108,7 +8129,6 @@ packages:
|
|||||||
|
|
||||||
/@types/geojson@7946.0.13:
|
/@types/geojson@7946.0.13:
|
||||||
resolution: {integrity: sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==}
|
resolution: {integrity: sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==}
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/geojson@7946.0.8:
|
/@types/geojson@7946.0.8:
|
||||||
resolution: {integrity: sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==}
|
resolution: {integrity: sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==}
|
||||||
@ -8189,6 +8209,12 @@ packages:
|
|||||||
'@types/node': 18.17.12
|
'@types/node': 18.17.12
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@types/leaflet@1.9.8:
|
||||||
|
resolution: {integrity: sha512-EXdsL4EhoUtGm2GC2ZYtXn+Fzc6pluVgagvo2VC1RHWToLGlTRwVYoDpqS/7QXa01rmDyBjJk3Catpf60VMkwg==}
|
||||||
|
dependencies:
|
||||||
|
'@types/geojson': 7946.0.13
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/loadable__component@5.13.8:
|
/@types/loadable__component@5.13.8:
|
||||||
resolution: {integrity: sha512-0FF/WihuPkR5IFOHiBzC95bSACvgQNUQCuNy1WF8F/lCBBHgS2SxarIk4CTjWM10A72ovpmXZDRcuAXZNS+/kQ==}
|
resolution: {integrity: sha512-0FF/WihuPkR5IFOHiBzC95bSACvgQNUQCuNy1WF8F/lCBBHgS2SxarIk4CTjWM10A72ovpmXZDRcuAXZNS+/kQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -14335,6 +14361,10 @@ packages:
|
|||||||
readable-stream: 2.3.8
|
readable-stream: 2.3.8
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/leaflet@1.9.4:
|
||||||
|
resolution: {integrity: sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/less@4.2.0:
|
/less@4.2.0:
|
||||||
resolution: {integrity: sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==}
|
resolution: {integrity: sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@ -18895,6 +18925,19 @@ packages:
|
|||||||
- encoding
|
- encoding
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-leaflet@4.2.1(leaflet@1.9.4)(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==}
|
||||||
|
peerDependencies:
|
||||||
|
leaflet: ^1.9.0
|
||||||
|
react: ^18.0.0
|
||||||
|
react-dom: ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
'@react-leaflet/core': 2.1.0(leaflet@1.9.4)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
leaflet: 1.9.4
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-lifecycles-compat@3.0.4:
|
/react-lifecycles-compat@3.0.4:
|
||||||
resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
|
resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -1,95 +0,0 @@
|
|||||||
import type { LarkMapProps, PointLayerProps } from '@antv/larkmap';
|
|
||||||
import { FullscreenControl, LarkMap, PointLayer } from '@antv/larkmap';
|
|
||||||
import React from 'react';
|
|
||||||
import { useSettingsStore } from '../../store/settings';
|
|
||||||
import { useGlobalConfig } from '../../hooks/useConfig';
|
|
||||||
import { trpc } from '../../api/trpc';
|
|
||||||
import { useCurrentWorkspaceId } from '../../store/user';
|
|
||||||
import { useGlobalRangeDate } from '../../hooks/useGlobalRangeDate';
|
|
||||||
|
|
||||||
const layerOptions: Omit<PointLayerProps, 'source'> = {
|
|
||||||
autoFit: true,
|
|
||||||
shape: 'circle',
|
|
||||||
size: 5,
|
|
||||||
blend: 'additive',
|
|
||||||
color: {
|
|
||||||
field: 'count',
|
|
||||||
value: [
|
|
||||||
'rgb(102,37,6)',
|
|
||||||
'rgb(153,52,4)',
|
|
||||||
'rgb(204,76,2)',
|
|
||||||
'rgb(236,112,20)',
|
|
||||||
'rgb(254,153,41)',
|
|
||||||
'rgb(254,196,79)',
|
|
||||||
'rgb(254,227,145)',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function useMapConfig(mapType: 'Mapbox' | 'Gaode' = 'Mapbox'): LarkMapProps {
|
|
||||||
const { amapToken, mapboxToken } = useGlobalConfig();
|
|
||||||
const colorScheme = useSettingsStore((state) => state.colorScheme);
|
|
||||||
|
|
||||||
const baseOption: LarkMapProps['mapOptions'] = {
|
|
||||||
center: [120.210792, 30.246026],
|
|
||||||
zoom: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (mapType === 'Gaode') {
|
|
||||||
return {
|
|
||||||
mapType: 'Gaode',
|
|
||||||
mapOptions: {
|
|
||||||
...baseOption,
|
|
||||||
style:
|
|
||||||
colorScheme === 'light'
|
|
||||||
? 'amap://styles/light'
|
|
||||||
: 'amap://styles/dark',
|
|
||||||
token: amapToken,
|
|
||||||
},
|
|
||||||
logoVisible: false,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
mapType: 'Mapbox',
|
|
||||||
mapOptions: {
|
|
||||||
...baseOption,
|
|
||||||
style: colorScheme === 'light' ? 'light' : 'dark',
|
|
||||||
token: mapboxToken,
|
|
||||||
},
|
|
||||||
logoVisible: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WebsiteVisitorMapProps {
|
|
||||||
websiteId: string;
|
|
||||||
}
|
|
||||||
export const WebsiteVisitorMap: React.FC<WebsiteVisitorMapProps> = React.memo(
|
|
||||||
(props) => {
|
|
||||||
const config = useMapConfig();
|
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
|
||||||
const { startDate, endDate } = useGlobalRangeDate();
|
|
||||||
const startAt = startDate.valueOf();
|
|
||||||
const endAt = endDate.valueOf();
|
|
||||||
|
|
||||||
const { data } = trpc.website.geoStats.useQuery({
|
|
||||||
workspaceId,
|
|
||||||
websiteId: props.websiteId,
|
|
||||||
startAt,
|
|
||||||
endAt,
|
|
||||||
});
|
|
||||||
|
|
||||||
const source: PointLayerProps['source'] = {
|
|
||||||
data: data ?? [],
|
|
||||||
parser: { type: 'json', x: 'longitude', y: 'latitude' },
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LarkMap {...config} style={{ height: '60vh' }}>
|
|
||||||
<FullscreenControl />
|
|
||||||
<PointLayer {...layerOptions} source={source} />
|
|
||||||
</LarkMap>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
WebsiteVisitorMap.displayName = 'WebsiteVisitorMap';
|
|
@ -0,0 +1,86 @@
|
|||||||
|
import {
|
||||||
|
FullscreenControl,
|
||||||
|
LarkMap,
|
||||||
|
LarkMapProps,
|
||||||
|
PointLayer,
|
||||||
|
PointLayerProps,
|
||||||
|
} from '@antv/larkmap';
|
||||||
|
import React from 'react';
|
||||||
|
import { AppRouterOutput } from '../../../api/trpc';
|
||||||
|
import { useGlobalConfig } from '../../../hooks/useConfig';
|
||||||
|
import { useSettingsStore } from '../../../store/settings';
|
||||||
|
import { mapCenter } from './utils';
|
||||||
|
|
||||||
|
const layerOptions: Omit<PointLayerProps, 'source'> = {
|
||||||
|
autoFit: true,
|
||||||
|
shape: 'circle',
|
||||||
|
size: 5,
|
||||||
|
blend: 'additive',
|
||||||
|
color: {
|
||||||
|
field: 'count',
|
||||||
|
value: [
|
||||||
|
'rgb(102,37,6)',
|
||||||
|
'rgb(153,52,4)',
|
||||||
|
'rgb(204,76,2)',
|
||||||
|
'rgb(236,112,20)',
|
||||||
|
'rgb(254,153,41)',
|
||||||
|
'rgb(254,196,79)',
|
||||||
|
'rgb(254,227,145)',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function useMapConfig(mapType: 'Mapbox' | 'Gaode' = 'Mapbox'): LarkMapProps {
|
||||||
|
const { amapToken, mapboxToken } = useGlobalConfig();
|
||||||
|
const colorScheme = useSettingsStore((state) => state.colorScheme);
|
||||||
|
|
||||||
|
const baseOption: LarkMapProps['mapOptions'] = {
|
||||||
|
center: [mapCenter.lng, mapCenter.lat],
|
||||||
|
zoom: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (mapType === 'Gaode') {
|
||||||
|
return {
|
||||||
|
mapType: 'Gaode',
|
||||||
|
mapOptions: {
|
||||||
|
...baseOption,
|
||||||
|
style:
|
||||||
|
colorScheme === 'light'
|
||||||
|
? 'amap://styles/light'
|
||||||
|
: 'amap://styles/dark',
|
||||||
|
token: amapToken,
|
||||||
|
},
|
||||||
|
logoVisible: false,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
mapType: 'Mapbox',
|
||||||
|
mapOptions: {
|
||||||
|
...baseOption,
|
||||||
|
style: colorScheme === 'light' ? 'light' : 'dark',
|
||||||
|
token: mapboxToken,
|
||||||
|
},
|
||||||
|
logoVisible: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VisitorLarkMap: React.FC<{
|
||||||
|
data: AppRouterOutput['website']['geoStats'];
|
||||||
|
mapType: 'Mapbox' | 'Gaode';
|
||||||
|
}> = React.memo((props) => {
|
||||||
|
const config = useMapConfig(props.mapType);
|
||||||
|
|
||||||
|
const source: PointLayerProps['source'] = {
|
||||||
|
data: props.data ?? [],
|
||||||
|
parser: { type: 'json', x: 'longitude', y: 'latitude' },
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LarkMap {...config} style={{ height: '60vh' }}>
|
||||||
|
<FullscreenControl />
|
||||||
|
<PointLayer {...layerOptions} source={source} />
|
||||||
|
</LarkMap>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
VisitorLarkMap.displayName = 'VisitorLarkMap';
|
@ -0,0 +1,3 @@
|
|||||||
|
.leaflet-control-attribution.leaflet-control {
|
||||||
|
display: none;
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { AppRouterOutput } from '../../../api/trpc';
|
||||||
|
import {
|
||||||
|
MapContainer,
|
||||||
|
CircleMarker,
|
||||||
|
Popup,
|
||||||
|
TileLayer,
|
||||||
|
useMap,
|
||||||
|
} from 'react-leaflet';
|
||||||
|
import { mapCenter } from './utils';
|
||||||
|
import 'leaflet/dist/leaflet.css';
|
||||||
|
import './VisitorLeafletMap.css';
|
||||||
|
|
||||||
|
export const UserDataPoint: React.FC<{
|
||||||
|
longitude: number;
|
||||||
|
latitude: number;
|
||||||
|
count: number;
|
||||||
|
}> = React.memo((props) => {
|
||||||
|
const map = useMap();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CircleMarker
|
||||||
|
center={{
|
||||||
|
lat: props.latitude,
|
||||||
|
lng: props.longitude,
|
||||||
|
}}
|
||||||
|
radius={5}
|
||||||
|
stroke={false}
|
||||||
|
fill={true}
|
||||||
|
fillColor="rgb(236,112,20)"
|
||||||
|
fillOpacity={0.8}
|
||||||
|
>
|
||||||
|
<Popup>{props.count} users</Popup>
|
||||||
|
</CircleMarker>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
UserDataPoint.displayName = 'UserDataPoint';
|
||||||
|
|
||||||
|
export const VisitorLeafletMap: React.FC<{
|
||||||
|
data: AppRouterOutput['website']['geoStats'];
|
||||||
|
}> = React.memo((props) => {
|
||||||
|
return (
|
||||||
|
<MapContainer
|
||||||
|
className="w-full h-[60vh]"
|
||||||
|
center={mapCenter}
|
||||||
|
zoom={2}
|
||||||
|
minZoom={2}
|
||||||
|
maxZoom={10}
|
||||||
|
scrollWheelZoom={true}
|
||||||
|
>
|
||||||
|
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
|
||||||
|
|
||||||
|
{props.data.map((item) => (
|
||||||
|
<UserDataPoint key={`${item.longitude},${item.latitude}`} {...item} />
|
||||||
|
))}
|
||||||
|
</MapContainer>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
VisitorLeafletMap.displayName = 'VisitorLeafletMap';
|
58
src/client/components/website/WebsiteVisitorMap/index.tsx
Normal file
58
src/client/components/website/WebsiteVisitorMap/index.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { trpc } from '../../../api/trpc';
|
||||||
|
import { useCurrentWorkspaceId } from '../../../store/user';
|
||||||
|
import { useGlobalRangeDate } from '../../../hooks/useGlobalRangeDate';
|
||||||
|
import loadable from '@loadable/component';
|
||||||
|
import { Loading } from '../../Loading';
|
||||||
|
import { useGlobalConfig } from '../../../hooks/useConfig';
|
||||||
|
|
||||||
|
const VisitorLeafletMap = loadable(() =>
|
||||||
|
import('./VisitorLeafletMap').then((m) => m.VisitorLeafletMap)
|
||||||
|
);
|
||||||
|
|
||||||
|
const VisitorLarkMap = loadable(() =>
|
||||||
|
import('./VisitorLarkMap').then((m) => m.VisitorLarkMap)
|
||||||
|
);
|
||||||
|
|
||||||
|
function useMapType() {
|
||||||
|
const { amapToken, mapboxToken } = useGlobalConfig();
|
||||||
|
|
||||||
|
if (mapboxToken) {
|
||||||
|
return 'Mapbox';
|
||||||
|
} else if (amapToken) {
|
||||||
|
return 'Gaode';
|
||||||
|
} else {
|
||||||
|
return 'Leaflet';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WebsiteVisitorMapProps {
|
||||||
|
websiteId: string;
|
||||||
|
}
|
||||||
|
export const WebsiteVisitorMap: React.FC<WebsiteVisitorMapProps> = React.memo(
|
||||||
|
(props) => {
|
||||||
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
|
const { startDate, endDate } = useGlobalRangeDate();
|
||||||
|
const startAt = startDate.valueOf();
|
||||||
|
const endAt = endDate.valueOf();
|
||||||
|
const { data } = trpc.website.geoStats.useQuery({
|
||||||
|
workspaceId,
|
||||||
|
websiteId: props.websiteId,
|
||||||
|
startAt,
|
||||||
|
endAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapType = useMapType();
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapType === 'Leaflet') {
|
||||||
|
return <VisitorLeafletMap data={data} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <VisitorLarkMap mapType={mapType} data={data} />;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
WebsiteVisitorMap.displayName = 'WebsiteVisitorMap';
|
4
src/client/components/website/WebsiteVisitorMap/utils.ts
Normal file
4
src/client/components/website/WebsiteVisitorMap/utils.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export const mapCenter = {
|
||||||
|
lat: 30.246026,
|
||||||
|
lng: 120.210792,
|
||||||
|
};
|
@ -32,6 +32,7 @@
|
|||||||
"dayjs": "^1.11.9",
|
"dayjs": "^1.11.9",
|
||||||
"eventemitter-strict": "^1.0.1",
|
"eventemitter-strict": "^1.0.1",
|
||||||
"filesize": "^10.0.12",
|
"filesize": "^10.0.12",
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"millify": "^6.1.0",
|
"millify": "^6.1.0",
|
||||||
"pretty-ms": "^9.0.0",
|
"pretty-ms": "^9.0.0",
|
||||||
@ -40,6 +41,7 @@
|
|||||||
"react-easy-sort": "^1.5.3",
|
"react-easy-sort": "^1.5.3",
|
||||||
"react-grid-layout": "1.4.2",
|
"react-grid-layout": "1.4.2",
|
||||||
"react-icons": "^4.12.0",
|
"react-icons": "^4.12.0",
|
||||||
|
"react-leaflet": "^4.2.1",
|
||||||
"react-resizable": "^3.0.5",
|
"react-resizable": "^3.0.5",
|
||||||
"react-router": "^6.15.0",
|
"react-router": "^6.15.0",
|
||||||
"react-router-dom": "^6.15.0",
|
"react-router-dom": "^6.15.0",
|
||||||
@ -50,6 +52,7 @@
|
|||||||
"zustand": "^4.4.1"
|
"zustand": "^4.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/leaflet": "^1.9.8",
|
||||||
"@types/loadable__component": "^5.13.8",
|
"@types/loadable__component": "^5.13.8",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/react": "^18.2.21",
|
"@types/react": "^18.2.21",
|
||||||
|
Loading…
Reference in New Issue
Block a user