mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
Merge branch 'main' into documentation-update
This commit is contained in:
Generated
+11
@@ -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",
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user