From 3c5fc3b28764e933660f0b9fa9fc8c239efeabc3 Mon Sep 17 00:00:00 2001 From: Julius Henke Date: Sun, 5 May 2024 00:48:54 +0200 Subject: [PATCH 1/2] feat: display a nickname tooltip on hover over relay markers --- .../controller/RelayDetailsController.kt | 4 + .../repository/RelayDetailsRepository.kt | 1 + frontend/src/util/layer-construction.ts | 131 +++++++++++++----- 3 files changed, 99 insertions(+), 37 deletions(-) diff --git a/backend/src/main/kotlin/org/tormap/adapter/controller/RelayDetailsController.kt b/backend/src/main/kotlin/org/tormap/adapter/controller/RelayDetailsController.kt index 37d388f6..d714a7e0 100644 --- a/backend/src/main/kotlin/org/tormap/adapter/controller/RelayDetailsController.kt +++ b/backend/src/main/kotlin/org/tormap/adapter/controller/RelayDetailsController.kt @@ -30,4 +30,8 @@ class RelayDetailsController( @PostMapping("family/identifiers") fun getFamilyIdentifiers(@RequestBody familyIds: List) = relayDetailsRepositoryImpl.findFamilyIdentifiers(familyIds) + + @Operation(summary = "Return the nicknames of the relays that are associated with a list of relay details IDs.") + @PostMapping("relay/nicknames") + fun getRelayNicknames(@RequestBody ids: List) = relayDetailsRepositoryImpl.findAllByIdIn(ids).map { it.nickname } } diff --git a/backend/src/main/kotlin/org/tormap/database/repository/RelayDetailsRepository.kt b/backend/src/main/kotlin/org/tormap/database/repository/RelayDetailsRepository.kt index d3293338..d1d7084b 100644 --- a/backend/src/main/kotlin/org/tormap/database/repository/RelayDetailsRepository.kt +++ b/backend/src/main/kotlin/org/tormap/database/repository/RelayDetailsRepository.kt @@ -8,4 +8,5 @@ interface RelayDetailsRepository : JpaRepository { fun findAllByMonthEqualsAndFamilyEntriesNotNull(month: String): List fun findAllByMonthEqualsAndAutonomousSystemNumberNull(month: String): List fun findAllByFamilyId(familyId: Long): List + fun findAllByIdIn(ids: List): List } diff --git a/frontend/src/util/layer-construction.ts b/frontend/src/util/layer-construction.ts index 88d71660..41a44f45 100644 --- a/frontend/src/util/layer-construction.ts +++ b/frontend/src/util/layer-construction.ts @@ -1,5 +1,14 @@ import {Feature, GeoJsonObject, GeometryObject} from "geojson"; -import L, {circleMarker, GeoJSON, HeatLatLngTuple, Layer, LayerGroup, LeafletMouseEvent, PathOptions} from "leaflet"; +import L, { + CircleMarker, + circleMarker, + GeoJSON, + HeatLatLngTuple, + Layer, + LayerGroup, + LeafletMouseEvent, + PathOptions +} from "leaflet"; import {Colors} from "../config"; import {RelayLocationDto} from "../dto/relay"; @@ -14,6 +23,7 @@ import { sortFamilyCoordinatesMap } from "./aggregate-relays"; import {getUniqueCountryColor} from "./geojson"; +import {backend} from "./util"; /** * Returns a Layer with markers with size relative to number of relays on a coordinate. @@ -46,51 +56,98 @@ export const buildAggregatedCoordinatesLayer = ( /** * Returns a Layer with markers for each relay. - * @param latLonMap - The LatLonMap + * @param relayCoordinatesMap - The LatLonMap * @param singleColor - Whether all markers should have the same color * @param onMarkerClick - Event handler for clicking on a marker */ export const buildRelayLayer = ( - latLonMap: Map, + relayCoordinatesMap: Map, singleColor: boolean, onMarkerClick: (e: LeafletMouseEvent) => void, ): LayerGroup => { - const defaultLayer = new LayerGroup() - const exitLayer = new LayerGroup() - const guardLayer = new LayerGroup() - const defaultMarkerLayer = new LayerGroup([defaultLayer, guardLayer, exitLayer]) - latLonMap.forEach((coordinate, key) => { - coordinate.forEach(relay => { - let color = Colors.Default - let layer = defaultLayer - switch (getRelayType(relay)) { - case RelayType.Exit: { - color = Colors.Exit - layer = exitLayer - break - } - case RelayType.Guard: { - color = Colors.Guard - layer = guardLayer - break - } - } - if (singleColor) color = "#989898" + const layer = new LayerGroup() + relayCoordinatesMap.forEach((relaysAtCoordinates, coordinatesKey) => { + if (relaysAtCoordinates.length == 0) return - circleMarker( - [relay.lat, relay.long], - { - radius: 1, - className: key, - color: color, - weight: 3, - }, - ) - .on("click", onMarkerClick) - .addTo(layer) - }) + const { + mostImportantRelay, + marker + } = addRelayMarker(relaysAtCoordinates, layer, singleColor, coordinatesKey, onMarkerClick) + + addRelayNicknameTooltip(relaysAtCoordinates, mostImportantRelay, marker, layer); }) - return defaultMarkerLayer + return layer +} + +function addRelayMarker(relaysAtCoordinates: RelayLocationDto[], targetLayer: LayerGroup, singleColor: boolean, coordinatesKey: string, onMarkerClick: (e: LeafletMouseEvent) => void) { + let mostImportantRelay = relaysAtCoordinates[0] + for (const relay of relaysAtCoordinates) { + if (getRelayType(relay) === RelayType.Exit) { + mostImportantRelay = relay + break + } else if (getRelayType(relay) === RelayType.Guard) { + mostImportantRelay = relay + } + } + let color = Colors.Default + switch (getRelayType(mostImportantRelay)) { + case RelayType.Exit: { + color = Colors.Exit + break + } + case RelayType.Guard: { + color = Colors.Guard + break + } + } + if (singleColor) color = "#989898" + + const marker = circleMarker( + [mostImportantRelay.lat, mostImportantRelay.long], + { + radius: 1, + className: coordinatesKey, + color: color, + weight: 3, + }, + ) + .on("click", onMarkerClick) + .addTo(targetLayer) + return {mostImportantRelay, marker}; +} + +function addRelayNicknameTooltip(relays: RelayLocationDto[], mostImportantRelay: RelayLocationDto, marker: CircleMarker, targetLayer: LayerGroup) { + const knownRelayDetailIds: number[] = relays.map(relay => relay.detailsId) + .filter(id => id !== undefined && id !== null) as number[] + + // Create an invisible larger circle around the marker + const hoverRadius = 15; + const hoverMarker = circleMarker( + [mostImportantRelay.lat, mostImportantRelay.long], + { + radius: hoverRadius, + fillOpacity: 0, + fill: false, + stroke: false, + }, + ) + .on('mouseover', async function (this: L.Marker, e) { + let tooltip = `${relays.length} relays` + const maxNicknamesShownFully = 3 + if (knownRelayDetailIds.length <= maxNicknamesShownFully) { + await backend.post('/relay/details/relay/nicknames', knownRelayDetailIds).then(response => { + tooltip = response.data.join(", ") + }) + } + marker.bindTooltip(tooltip, {permanent: true, direction: 'top'}).openTooltip(); + }) + .on('mouseout', function (this: L.Marker, e) { + marker.unbindTooltip(); + }) + .on("click", function (this: L.Marker, e) { + marker.fire('click'); // Trigger the click event of the actual marker + }) + .addTo(targetLayer) } /** From e69e6441da5ff214e78210e93cf552346423320f Mon Sep 17 00:00:00 2001 From: Julius Henke Date: Sun, 5 May 2024 11:30:03 +0200 Subject: [PATCH 2/2] feat: add donation section to about information --- frontend/.env | 4 +- frontend/package.json | 1 + frontend/src/components/card/BitcoinCard.tsx | 51 +++++++++++++++++++ .../components/dialogs/AboutInformation.tsx | 40 ++++++++++++--- 4 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 frontend/src/components/card/BitcoinCard.tsx diff --git a/frontend/.env b/frontend/.env index b7ce867d..96c430a7 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1,2 +1,4 @@ # The URL pointing to the backend instance (e.g. http://localhost:8080/) -VITE_BACKEND_API_URL=https://api.tormap.org \ No newline at end of file +VITE_BACKEND_API_URL=https://api.tormap.org +VITE_NOWPAYMENTS_API_KEY=BZ1BNEC-VDS47WP-PQB7G6F-9WTFHP8 +VITE_DONATION_BITCOIN_ADDRESS=bc1qkpwf376p75uyhqna87p22lt2t9zm99qflz39y3 diff --git a/frontend/package.json b/frontend/package.json index 77557bd3..6366eb2e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -2,6 +2,7 @@ "name": "tormap", "version": "2.2.0", "private": true, + "type": "module", "scripts": { "start": "vite --port 3000", "build": "tsc && vite build", diff --git a/frontend/src/components/card/BitcoinCard.tsx b/frontend/src/components/card/BitcoinCard.tsx new file mode 100644 index 00000000..f569182d --- /dev/null +++ b/frontend/src/components/card/BitcoinCard.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import Card from '@mui/material/Card'; +import CardActions from '@mui/material/CardActions'; +import CardContent from '@mui/material/CardContent'; +import Button from '@mui/material/Button'; +import Typography from '@mui/material/Typography'; +import IconButton from '@mui/material/IconButton'; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; +import LightningIcon from '@mui/icons-material/FlashOn'; +import WalletIcon from '@mui/icons-material/Wallet'; +import {Stack} from '@mui/material'; + +export default function BitcoinCard() { + const bitcoinAddress = import.meta.env.VITE_DONATION_BITCOIN_ADDRESS + + const handleCopyAddress = () => { + navigator.clipboard.writeText(bitcoinAddress); + }; + + const openInWallet = () => { + window.open(`bitcoin:${bitcoinAddress}`, '_blank'); + }; + + return ( + + + + Bitcoin + + + + {bitcoinAddress} + + + + + + + + + + + + + + ); +} diff --git a/frontend/src/components/dialogs/AboutInformation.tsx b/frontend/src/components/dialogs/AboutInformation.tsx index 85361783..c1dc84cc 100644 --- a/frontend/src/components/dialogs/AboutInformation.tsx +++ b/frontend/src/components/dialogs/AboutInformation.tsx @@ -22,8 +22,11 @@ import { import React, {useState} from "react"; import {ContributorCard} from "../card/ContributorCard"; -import {BitcoinIcon} from "../icons/BitcoinIcon"; import {ExternalLink} from "../link/ExternalLink"; +import BitcoinCard from "../card/BitcoinCard"; +import CardContent from "@mui/material/CardContent"; +import LaunchIcon from "@mui/icons-material/Launch"; +import Card from "@mui/material/Card"; /** * A component for displaying information about TorMap @@ -35,6 +38,8 @@ export const AboutInformation: React.FunctionComponent = () => { // App context const theme = useTheme() const isLargeScreen = useMediaQuery(theme.breakpoints.up("lg")) + const nowpaymentsApiKey = import.meta.env.VITE_NOWPAYMENTS_API_KEY + const nowpaymentsDonationUrl = `https://nowpayments.io/donation?api_key=${nowpaymentsApiKey}&source=lk_donation&medium=referral` return ( <> @@ -70,10 +75,6 @@ export const AboutInformation: React.FunctionComponent = () => { }/> - - }/> - { TorMap is a world map displaying approximate locations where Tor relays are being hosted. You can group, filter and analyze thousands of Tor relays, which route anonymous internet traffic daily. The historic state of the network can be viewed for any day between October 2007 - and today.
We developed TorMap for our practical P4-Project at the Initially TorMap was developed by us at the as part of - the . It was + the . The project was supervised by from { roles={["Server Admin"]} /> +

Donations

+ + If you like our project and want to support us, you can donate to us via Bitcoin or other + cryptocurrencies. Your donation helps us to cover server costs and keep the project running. + + + + + + + Other cryptocurrencies + + + + + + + We use IP geolocation data by