Add point selection v1

dev
Platon Yasev 3 years ago
parent 453d8fab9d
commit 225af8fef3

@ -17,7 +17,7 @@
"@watergis/maplibre-gl-export": "^1.3.7",
"antd": "^4.23.6",
"axios": "^1.1.3",
"immer": "^9.0.16",
"immer": "^9.0.19",
"lodash.debounce": "^4.0.8",
"mapbox-gl": "npm:empty-npm-package@1.0.0",
"maplibre-gl": "^2.4.0",
@ -29,6 +29,7 @@
"react-router-dom": "^6.8.1",
"tailwind-merge": "^1.7.0",
"vite-plugin-svgr": "^2.4.0",
"wellknown": "^0.5.0",
"zustand": "^4.1.3"
},
"devDependencies": {

@ -5,9 +5,12 @@ import { VerifyRegistrationPage } from "./pages/VerifyRegistration";
import { RegisterPage } from "./pages/Register";
import { MapPage } from "./pages/Map";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { enableMapSet } from "immer";
const queryClient = new QueryClient();
enableMapSet();
function App() {
return (
<QueryClientProvider client={queryClient}>

@ -1,30 +1,32 @@
import maplibregl from "maplibre-gl";
import Map from "react-map-gl";
import { useEffect, useRef, useState } from "react";
import Map, { MapProvider } from "react-map-gl";
import { useEffect, useRef } from "react";
import { Sidebar } from "../modules/Sidebar/Sidebar";
import { Layers } from "./Layers";
import { MapPopup } from "./Popup";
import { Basemap } from "./Basemap";
import { SignOut } from "../SignOut";
import debounce from "lodash.debounce";
import { Table } from "../modules/Table/Table";
import { usePopup } from "../stores/usePopup";
export const MapComponent = () => {
const mapRef = useRef(null);
const mapContainerRef = useRef(null);
const [clickedFeature, setClickedFeature] = useState(null);
const [popupCoordinates, setPopupCoordinates] = useState(null);
const { popup, setPopupPosition, setPopupFeature } = usePopup();
const { coordinates: popupCoords, feature: popupFeature } = popup;
const handleClick = (event) => {
if (!event.features) {
setPopupCoordinates(null);
setClickedFeature(null);
setPopupPosition(null);
setPopupFeature(null);
return;
}
const feature = event.features[0];
if (!feature) {
setPopupCoordinates(null);
setClickedFeature(null);
setPopupPosition(null);
setPopupFeature(null);
return;
}
@ -36,11 +38,11 @@ export const MapComponent = () => {
coordinates[0] += pointLng > coordinates[0] ? 360 : -360;
}
setPopupCoordinates(coordinates);
setPopupPosition(coordinates);
} else {
setPopupCoordinates([pointLng, pointLat]);
setPopupPosition([pointLng, pointLat]);
}
setClickedFeature(feature);
setPopupFeature(feature);
};
const handleMouseEnter = (event) => {
@ -74,43 +76,47 @@ export const MapComponent = () => {
}, [mapContainerRef.current]);
return (
<div ref={mapContainerRef} className="w-full flex-1">
<Map
mapLib={maplibregl}
// mapStyle={`https://api.maptiler.com/maps/voyager/style.json?key=${MAP_TILER_KEY}`}
// style={{ width: "100%", height: "100%" }}
initialViewState={{
latitude: 55.7558,
longitude: 37.6173,
zoom: 9,
}}
dragRotate={false}
ref={mapRef}
interactiveLayerIds={["points"]}
onClick={handleClick}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{clickedFeature && popupCoordinates && (
<MapPopup
lat={popupCoordinates[1]}
lng={popupCoordinates[0]}
feature={clickedFeature}
onClose={() => {
setPopupCoordinates(null);
setClickedFeature(null);
}}
/>
)}
<MapProvider>
<div ref={mapContainerRef} className="w-full flex-1">
<Map
mapLib={maplibregl}
// mapStyle={`https://api.maptiler.com/maps/voyager/style.json?key=${MAP_TILER_KEY}`}
// style={{ width: "100%", height: "100%" }}
initialViewState={{
latitude: 55.7558,
longitude: 37.6173,
zoom: 9,
}}
dragRotate={false}
ref={mapRef}
interactiveLayerIds={["points"]}
onClick={handleClick}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
id="map"
>
{popupFeature && popupCoords && (
<MapPopup
lat={popupCoords[1]}
lng={popupCoords[0]}
feature={popupFeature}
onClose={() => {
setPopupPosition(null);
setPopupFeature(null);
}}
/>
)}
<Basemap />
<Layers />
<Basemap />
<Layers />
<Sidebar />
<Sidebar />
{/*<Legend />*/}
<SignOut />
</Map>
</div>
{/*<Legend />*/}
<SignOut />
</Map>
</div>
<Table />
</MapProvider>
);
};

@ -1,10 +1,11 @@
import { Popup } from "react-map-gl";
import { Col, Row } from "antd";
import { Button, Col, Row } from "antd";
import { twMerge } from "tailwind-merge";
import { TYPE_MAPPER } from "../config";
import { useFactors } from "../stores/useFactors";
import { useMemo } from "react";
import { useModel } from "../stores/useModel";
import { usePointSelection } from "../stores/usePointSelection";
const useRateValue = (feature) => {
const { factors: weights } = useFactors();
@ -59,7 +60,6 @@ const gridConfig = [
];
export const MapPopup = ({ feature, lat, lng, onClose }) => {
console.log(feature);
const isPoint = feature.geometry.type === "Point";
const config = isPoint ? pointConfig : gridConfig;
const layout = isPoint
@ -67,6 +67,15 @@ export const MapPopup = ({ feature, lat, lng, onClose }) => {
: { keyCol: 20, valueCol: 4 };
const rate = useRateValue(feature);
const { include, selection, exclude } = usePointSelection();
const isSelected = selection.included.has(feature.properties.id);
const handleSelect = () =>
isSelected
? exclude(feature.properties.id)
: include(feature.properties.id);
return (
<Popup
longitude={lng}
@ -87,6 +96,9 @@ export const MapPopup = ({ feature, lat, lng, onClose }) => {
);
})}
</div>
<Button type="text" className="mt-2 mx-auto" block onClick={handleSelect}>
{isSelected ? "Исключить из выборки" : "Добавить в выборку"}
</Button>
</Popup>
);
};

@ -82,6 +82,7 @@
::-webkit-scrollbar {
width: 12px;
height: 12px;
}
::-webkit-scrollbar-thumb {
@ -96,20 +97,3 @@
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
}
/* Works on Chrome and Safari */
/**::-webkit-scrollbar {*/
/* @apply h-[2px] w-[4px];*/
/*}*/
/**::-webkit-scrollbar-thumb {*/
/* @apply rounded-xl border border-solid border-transparent bg-grey-light;*/
/*}*/
/**::-webkit-scrollbar-track {*/
/* @apply rounded-xl bg-transparent;*/
/*}*/
/**::-webkit-scrollbar-corner {*/
/* @apply hidden;*/
/*}*/

@ -31,7 +31,7 @@
}
.ant-table-pagination.ant-pagination {
margin: 0;
@apply !my-2 !mx-3;
}
.ant-table.ant-table-small .ant-table-title,

@ -1,10 +1,20 @@
import React, { useRef } from "react";
import React, { useCallback, useRef, useState } from "react";
import { Collapse, Empty, Table as AntdTable } from "antd";
import "./Table.css";
import { useQuery } from "@tanstack/react-query";
import { api } from "../../api";
import parse from "wellknown";
import { useMap } from "react-map-gl";
import { usePointSelection } from "../../stores/usePointSelection";
const columns = [
{
title: "Id",
dataIndex: "id",
key: "id",
width: "20px",
ellipsis: true,
},
{
title: "Статус",
dataIndex: "status",
@ -70,19 +80,53 @@ const columns = [
},
];
const PAGE_SIZE = 30;
export const Table = React.memo(({ height = 200 }) => {
const { map } = useMap();
const { include, selection, exclude } = usePointSelection();
const tableRef = useRef(null);
const { data } = useQuery(["table"], async () => {
const { data } = await api.get("/api/placement_points?page_size=20");
const [page, setPage] = useState(1);
const { data } = useQuery(
["table", page],
async () => {
const { data } = await api.get(
`/api/placement_points?page=${page}&page_size=${PAGE_SIZE}`
);
return data;
});
return data;
},
{ keepPreviousData: true }
);
const SCROLL = {
y: `${height}px`,
x: "max-content",
};
const handlePageChange = useCallback((page) => setPage(page), []);
const getSelectedRowKeys = () => {
const ids = data?.results.map((item) => item.id) ?? [];
return [
...ids.filter((id) => !selection.excluded.has(id)),
...selection.included,
];
};
const rowSelection = {
selectedRowKeys: getSelectedRowKeys(),
onSelect: (record, selected) => {
const { id } = record;
if (selected) {
include(id);
} else {
exclude(id);
}
},
};
return (
<div className="w-screen">
<Collapse>
@ -94,12 +138,37 @@ export const Table = React.memo(({ height = 200 }) => {
// rowSelection={rowSelection}
locale={{ emptyText: <Empty description="Нет данных" /> }}
// loading={isLoading}
pagination={false}
pagination={{
pageSize: PAGE_SIZE,
hideOnSinglePage: true,
current: page,
onChange: handlePageChange,
total: data?.count,
showSizeChanger: false,
position: "bottomCenter",
}}
dataSource={data?.results}
columns={columns}
rowKey="id"
scroll={SCROLL}
sticky={true}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
const geometry = parse(record.geometry);
// const feature = {
// properties: record,
// geometry,
// };
map.flyTo({
center: [geometry.coordinates[0], geometry.coordinates[1]],
zoom: 15,
speed: 5,
});
},
};
}}
rowSelection={rowSelection}
/>
</Collapse.Panel>
</Collapse>

@ -1,12 +1,10 @@
import { WithAuth } from "../WithAuth";
import { MapComponent } from "../Map/MapComponent";
import { Table } from "../modules/Table/Table";
export function MapPage() {
return (
<WithAuth>
<MapComponent />
<Table />
</WithAuth>
);
}

@ -0,0 +1,34 @@
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
const INITIAL = {
included: new Set([]),
excluded: new Set([]),
};
const store = (set) => ({
selection: INITIAL,
include: (id) => {
set((state) => {
const { excluded } = state.selection;
if (excluded.has(id)) {
state.selection.excluded.delete(id);
} else {
state.selection.included.add(id);
}
});
},
exclude: (id) => {
set((state) => {
const { included } = state.selection;
if (included.has(id)) {
state.selection.included.delete(id);
} else {
state.selection.excluded.add(id);
}
});
},
});
export const usePointSelection = create(immer(store));

@ -0,0 +1,24 @@
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
const INITIAL = {
coordinates: null,
feature: null,
};
const store = (set) => ({
popup: INITIAL,
setPopupPosition: (coords) => {
set((state) => {
state.popup.coordinates = coords;
});
},
setPopupFeature: (feature) => {
set((state) => {
state.popup.feature = feature;
});
},
});
export const usePopup = create(immer(store));

@ -914,6 +914,15 @@ compute-scroll-into-view@^1.0.20:
resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz#1768b5522d1172754f5d0c9b02de3af6be506a43"
integrity sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==
concat-stream@~1.5.0:
version "1.5.2"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266"
integrity sha512-H6xsIBfQ94aESBG8jGHXQ7i5AEpy5ZeVaLDOisDICiTCKpqEfr34/KmTrspKQNoLKNu9gTkovlpQcUi630AKiQ==
dependencies:
inherits "~2.0.1"
readable-stream "~2.0.0"
typedarray "~0.0.5"
convert-source-map@^1.7.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
@ -938,6 +947,11 @@ core-js@^3.6.0, core-js@^3.8.3:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.28.0.tgz#ed8b9e99c273879fdfff0edfc77ee709a5800e4a"
integrity sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw==
core-util-is@~1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
cosmiconfig@^7.0.1:
version "7.1.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6"
@ -1355,7 +1369,7 @@ image-size@~0.5.0:
resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c"
integrity sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==
immer@^9.0.16:
immer@^9.0.19:
version "9.0.19"
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.19.tgz#67fb97310555690b5f9cd8380d38fc0aabb6b38b"
integrity sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==
@ -1368,6 +1382,11 @@ import-fresh@^3.2.1:
parent-module "^1.0.0"
resolve-from "^4.0.0"
inherits@~2.0.1:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
ini@^1.3.5:
version "1.3.8"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
@ -1414,6 +1433,11 @@ is-what@^3.14.1:
resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1"
integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==
isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@ -1607,7 +1631,7 @@ mime@^1.4.1:
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
minimist@^1.2.6:
minimist@^1.2.6, minimist@~1.2.0:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
@ -1794,6 +1818,11 @@ potpack@^1.0.2:
resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.2.tgz#23b99e64eb74f5741ffe7656b5b5c4ddce8dfc14"
integrity sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==
process-nextick-args@~1.0.6:
version "1.0.7"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
integrity sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw==
protocol-buffers-schema@^3.3.1:
version "3.6.0"
resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz#77bc75a48b2ff142c1ad5b5b90c94cd0fa2efd03"
@ -2261,6 +2290,18 @@ read-cache@^1.0.0:
dependencies:
pify "^2.3.0"
readable-stream@~2.0.0:
version "2.0.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
integrity sha512-TXcFfb63BQe1+ySzsHZI/5v1aJPCShfqvWJ64ayNImXMsN1Cd0YGk/wm8KB7/OeessgPc9QvS9Zou8QTkFzsLw==
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.1"
isarray "~1.0.0"
process-nextick-args "~1.0.6"
string_decoder "~0.10.x"
util-deprecate "~1.0.1"
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
@ -2387,6 +2428,11 @@ string-convert@^0.2.0:
resolved "https://registry.yarnpkg.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97"
integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==
string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==
supercluster@^7.1.5:
version "7.1.5"
resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-7.1.5.tgz#65a6ce4a037a972767740614c19051b64b8be5a3"
@ -2484,6 +2530,11 @@ tslib@^2.3.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
typedarray@~0.0.5:
version "0.0.7"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.7.tgz#799207136a37f3b3efb8c66c40010d032714dc73"
integrity sha512-ueeb9YybpjhivjbHP2LdFDAjbS948fGEPj+ACAMs4xCMmh72OCOMQWBQKlaN4ZNQ04yfLSDLSx1tGRIoWimObQ==
update-browserslist-db@^1.0.10:
version "1.0.10"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3"
@ -2497,7 +2548,7 @@ use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0:
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
util-deprecate@^1.0.2:
util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
@ -2538,6 +2589,14 @@ vt-pbf@^3.1.3:
"@mapbox/vector-tile" "^1.3.1"
pbf "^3.2.1"
wellknown@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/wellknown/-/wellknown-0.5.0.tgz#09ae9871fa826cf0a6ec1537ef00c379d78d7101"
integrity sha512-za5vTLuPF9nmrVOovYQwNEWE/PwJCM+yHMAj4xN1WWUvtq9OElsvKiPL0CR9rO8xhrYqL7NpI7IknqR8r6eYOg==
dependencies:
concat-stream "~1.5.0"
minimist "~1.2.0"
which@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"

Loading…
Cancel
Save