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:
|
||||
specifier: ^10.0.12
|
||||
version: 10.0.12
|
||||
leaflet:
|
||||
specifier: ^1.9.4
|
||||
version: 1.9.4
|
||||
lodash-es:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
@ -134,6 +137,9 @@ importers:
|
||||
react-icons:
|
||||
specifier: ^4.12.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:
|
||||
specifier: ^3.0.5
|
||||
version: 3.0.5(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -159,6 +165,9 @@ importers:
|
||||
specifier: ^4.4.1
|
||||
version: 4.4.1(@types/react@18.2.21)(react@18.2.0)
|
||||
devDependencies:
|
||||
'@types/leaflet':
|
||||
specifier: ^1.9.8
|
||||
version: 1.9.8
|
||||
'@types/loadable__component':
|
||||
specifier: ^5.13.8
|
||||
version: 5.13.8
|
||||
@ -6498,6 +6507,18 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
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):
|
||||
resolution: {integrity: sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==}
|
||||
peerDependencies:
|
||||
@ -8108,7 +8129,6 @@ packages:
|
||||
|
||||
/@types/geojson@7946.0.13:
|
||||
resolution: {integrity: sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==}
|
||||
dev: false
|
||||
|
||||
/@types/geojson@7946.0.8:
|
||||
resolution: {integrity: sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==}
|
||||
@ -8189,6 +8209,12 @@ packages:
|
||||
'@types/node': 18.17.12
|
||||
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:
|
||||
resolution: {integrity: sha512-0FF/WihuPkR5IFOHiBzC95bSACvgQNUQCuNy1WF8F/lCBBHgS2SxarIk4CTjWM10A72ovpmXZDRcuAXZNS+/kQ==}
|
||||
dependencies:
|
||||
@ -14335,6 +14361,10 @@ packages:
|
||||
readable-stream: 2.3.8
|
||||
dev: true
|
||||
|
||||
/leaflet@1.9.4:
|
||||
resolution: {integrity: sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==}
|
||||
dev: false
|
||||
|
||||
/less@4.2.0:
|
||||
resolution: {integrity: sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==}
|
||||
engines: {node: '>=6'}
|
||||
@ -18895,6 +18925,19 @@ packages:
|
||||
- encoding
|
||||
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:
|
||||
resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
|
||||
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",
|
||||
"eventemitter-strict": "^1.0.1",
|
||||
"filesize": "^10.0.12",
|
||||
"leaflet": "^1.9.4",
|
||||
"lodash-es": "^4.17.21",
|
||||
"millify": "^6.1.0",
|
||||
"pretty-ms": "^9.0.0",
|
||||
@ -40,6 +41,7 @@
|
||||
"react-easy-sort": "^1.5.3",
|
||||
"react-grid-layout": "1.4.2",
|
||||
"react-icons": "^4.12.0",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-resizable": "^3.0.5",
|
||||
"react-router": "^6.15.0",
|
||||
"react-router-dom": "^6.15.0",
|
||||
@ -50,6 +52,7 @@
|
||||
"zustand": "^4.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/leaflet": "^1.9.8",
|
||||
"@types/loadable__component": "^5.13.8",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/react": "^18.2.21",
|
||||
|
Loading…
Reference in New Issue
Block a user