Merge branch 'main' into documentation-update

This commit is contained in:
lkauria
2025-06-05 10:06:45 +03:00
committed by GitHub
8 changed files with 146 additions and 2 deletions
+11
View File
@@ -12,6 +12,7 @@
"@emotion/styled": "^11.13.5",
"@mui/material": "^6.1.9",
"@reduxjs/toolkit": "^2.2.3",
"@svgdotjs/svg.js": "^3.2.4",
"export-to-csv": "^1.3.0",
"leaflet": "^1.9.4",
"lodash-es": "^4.17.21",
@@ -2938,6 +2939,16 @@
"@sinonjs/commons": "^3.0.0"
}
},
"node_modules/@svgdotjs/svg.js": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.js/-/svg.js-3.2.4.tgz",
"integrity": "sha512-BjJ/7vWNowlX3Z8O4ywT58DqbNRyYlkk6Yz/D13aB7hGmfQTvGX4Tkgtm/ApYlu9M7lCQi15xUEidqMUmdMYwg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Fuzzyma"
}
},
"node_modules/@tanstack/match-sorter-utils": {
"version": "8.19.4",
"resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.19.4.tgz",
+2 -1
View File
@@ -16,6 +16,7 @@
"@emotion/styled": "^11.13.5",
"@mui/material": "^6.1.9",
"@reduxjs/toolkit": "^2.2.3",
"@svgdotjs/svg.js": "^3.2.4",
"export-to-csv": "^1.3.0",
"leaflet": "^1.9.4",
"lodash-es": "^4.17.21",
@@ -60,4 +61,4 @@
"vite": "^5.2.5",
"vite-plugin-istanbul": "^6.0.2"
}
}
}
@@ -6,6 +6,7 @@ import { TableView } from '../TableView/TableView'
import { useNotify } from '@/hooks/notification'
import { LocalitiesMap } from '../Map/LocalitiesMap'
import { generateKml } from '@/util/kml'
import { generateSvg } from '../Map/generateSvg'
const decimalCount = (num: number) => {
const numAsString = num.toString()
@@ -397,6 +398,17 @@ export const LocalityTable = ({ selectorFn }: { selectorFn?: (newObject: Localit
a.click()
}
const svgExport = <T extends MRT_RowData>(table: MRT_TableInstance<T>) => {
const rowData: Locality[] = table.getPrePaginationRowModel().rows.map(row => row.original as unknown as Locality)
const dataString = generateSvg(rowData)
const blob = new Blob([dataString], { type: 'image/svg+xml' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'map.svg'
a.click()
}
const checkRowRestriction = (row: Locality) => {
return !!row.loc_status
}
@@ -416,6 +428,7 @@ export const LocalityTable = ({ selectorFn }: { selectorFn?: (newObject: Localit
url="locality"
combinedExport={combinedExport}
kmlExport={kmlExport}
svgExport={svgExport}
exportIsLoading={isLoading}
enableColumnFilterModes={true}
/>
@@ -146,7 +146,7 @@ export const LocalitiesMap = ({ localitiesQueryData, localitiesQueryIsFetching }
<div ref={mapRef}></div>
</div>
{!localitiesQueryIsFetching && (
{!localitiesQueryIsFetching && localitiesQueryData && (
<div className="button-row">
<Button variant="contained" startIcon={<MapIcon />} onClick={() => setIsOpen(v => !v)}>
{isOpen ? 'Close' : 'Open'} map
@@ -0,0 +1,60 @@
import { SVG } from '@svgdotjs/svg.js'
import { borders } from './country_borders_WGS84'
import { Locality } from '@/shared/types'
interface PixelCoordinates {
x: number
y: number
}
// Converts decimal latitude longitude (degrees) coordinates to pixel coordinates,
// using width as the total map width.
const webMercatorConvert = (lat: number, long: number, width: number): PixelCoordinates => {
const pi = 3.141592653589793
const latRadians = lat * (pi / 180)
const longRadians = long * (pi / 180)
return {
x: (1 / (pi * 2)) * (pi + longRadians) * width,
y: (1 / (pi * 2)) * (pi - Math.log(Math.tan(pi / 4 + latRadians / 2))) * width,
}
}
// Generates and returns an SVG image of localities on a map.
export const generateSvg = (
localities: Locality[],
mapBorderColor = '#000000',
mapFillColor = '#ffffff',
markerBorderColor = '#ff0000',
markerFillColor = '#ffffff',
transparent = true
) => {
const draw = SVG()
const width = 2048
draw.viewbox(0, 0, width, width)
if (!transparent) draw.polygon(`0,0 ${width},0 ${width},${width} 0,${width}`).fill('#ffffff')
borders.forEach(country_border => {
const points: string[] = []
country_border.forEach(coordinate => {
const pixelCoords = webMercatorConvert(coordinate[0], coordinate[1], width)
points.push(`${pixelCoords.x},${pixelCoords.y}`)
})
const polygon = points.join(' ')
draw.polygon(polygon).stroke({ width: 1, color: mapBorderColor }).fill(mapFillColor)
})
localities.forEach(locality => {
const pixelCoords = webMercatorConvert(locality.dec_lat, locality.dec_long, width)
const radius = 3
const topLeftX = pixelCoords.x - radius
const topLeftY = pixelCoords.y - radius
draw.circle(radius).move(topLeftX, topLeftY).stroke({ width: 1, color: markerBorderColor }).fill(markerFillColor)
})
return draw.svg()
}
@@ -13,6 +13,7 @@ export const TableToolBar = <T extends MRT_RowData>({
table,
combinedExport,
kmlExport,
svgExport,
exportIsLoading,
isCrossSearchTable,
selectorFn,
@@ -21,6 +22,7 @@ export const TableToolBar = <T extends MRT_RowData>({
table: MRT_TableInstance<T>
combinedExport?: (lids: number[]) => Promise<void>
kmlExport?: (table: MRT_TableInstance<T>) => void
svgExport?: (table: MRT_TableInstance<T>) => void
exportIsLoading?: boolean
isCrossSearchTable?: boolean
selectorFn?: (id: T) => void
@@ -93,6 +95,18 @@ export const TableToolBar = <T extends MRT_RowData>({
Export KML
</MenuItem>
)}
{svgExport && (
<MenuItem
onClick={() => {
svgExport(table)
handleClose()
}}
>
Export SVG map
</MenuItem>
)}
{combinedExport && (
<Box>
<Tooltip
@@ -39,6 +39,7 @@ export const TableView = <T extends MRT_RowData>({
title,
combinedExport,
kmlExport,
svgExport,
exportIsLoading,
isCrossSearchTable,
enableColumnFilterModes,
@@ -55,6 +56,7 @@ export const TableView = <T extends MRT_RowData>({
url?: string
combinedExport?: (lids: number[]) => Promise<void>
kmlExport?: (table: MRT_TableInstance<T>) => void
svgExport?: (table: MRT_TableInstance<T>) => void
exportIsLoading?: boolean
isCrossSearchTable?: boolean
enableColumnFilterModes?: boolean
@@ -207,6 +209,7 @@ export const TableView = <T extends MRT_RowData>({
table={table}
combinedExport={combinedExport}
kmlExport={kmlExport}
svgExport={svgExport}
exportIsLoading={exportIsLoading}
isCrossSearchTable={isCrossSearchTable}
showNewButton={editRights.new && title != 'Locality-Species-Cross-Search'}
+42
View File
@@ -0,0 +1,42 @@
.modal {
position: fixed;
display: flex;
justify-content: center;
align-items: center;
border: none;
}
.modal > .modal-content {
border: none;
box-sizing: border-box;
background-color: white;
padding: 2em 2em;
width: calc(100% - 4em);
height: calc(100% - 2em);
max-width: 50em;
max-height: fit-content;
border-radius: 0.25rem;
overflow-y: auto;
}
.modal.big > .modal-content {
width: calc(100% - 4em);
height: calc(100% - 2em);
max-width: 70em;
max-height: fit-content;
}
.modal .map {
width: 100%;
height: 50vh;
}
.modal .MuiFormControl-root {
max-width: 100%;
}
@media screen and (max-width: 750px) {
.modal > .modal-content {
width: calc(100% - 2em) !important;
}
}