Skip to content

Commit

Permalink
feat: store data in URL
Browse files Browse the repository at this point in the history
  • Loading branch information
frederic-maury committed Mar 31, 2024
1 parent e34d81c commit a35c36c
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 23 deletions.
43 changes: 41 additions & 2 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"react-leaflet": "^4.2.1",
"react-leaflet-cluster": "^2.1.0",
"react-redux": "^9.1.0",
"react-router-dom": "^6.22.3",
"react-scripts": "^5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
Expand Down
15 changes: 9 additions & 6 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@ import { MantineProviders } from './providers/MantineProvider';
import { HotkeysProvider } from './providers/HotkeysProvider';
import InitStoreProvider from './providers/InitStoreProvider';
import './i18next/i18next';
import { QueryProvider } from './providers/QueryProvider';

function App() {
return (
<Provider store={store}>
<InitStoreProvider>
<MantineProviders>
<HotkeysProvider>
<MapWrapper />
<AppActions />
</HotkeysProvider>
</MantineProviders>
<QueryProvider>
<MantineProviders>
<HotkeysProvider>
<MapWrapper />
<AppActions />
</HotkeysProvider>
</MantineProviders>
</QueryProvider>
</InitStoreProvider>
</Provider>
);
Expand Down
50 changes: 40 additions & 10 deletions frontend/src/components/Map/MapMarkers.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { TFunction } from 'i18next';
import { DivIcon, MarkerClusterGroup, marker, Marker, markerClusterGroup, LeafletMouseEvent } from 'leaflet';
import { DivIcon, MarkerClusterGroup, marker, Marker, markerClusterGroup, LeafletMouseEvent, LeafletEvent } from 'leaflet';
import { useLeafletContext } from '@react-leaflet/core';
import 'leaflet.markercluster';
import { Wallet } from '../../types/wallet';
import { Property } from '../../types/property';
import { useAppDispatch, useAppSelector } from '../../hooks/useInitStore';
import { setSelected } from '../../store/marker/markerReducer';
import { setLatLng, setSelectedProperty, setZoom } from '../../store/urlQuery/urlQuery.reducer';
import { selectedProperty } from '../../store/urlQuery/urlQuery.selector';
import { Maybe } from '../../types/global';

export const OWNED_SELECTOR = '[data-marker-owned]';
export const CSSCLASSES = {
owned: 'stroke-white owned drop-shadow-lg',
notOwned: 'opacity-80',
}


function pinSvg(cssClasses: string) {
return `<svg width="50" height="50" viewBox="0 0 50 78" class="marker-svg ${cssClasses
}"><path class="at-176__pin" d="M24,0A24,24,0,0,0,0,24C0,37.25,20,72,24,72S48,37.25,48,24A24,24,0,0,0,24,0Zm0,33a9,9,0,1,1,9-9A9,9,0,0,1,24,33Z"/></svg>`;
}

// TODO - Map options for opacity
export function generateIcon(
property: Property,
differentiateOwned: boolean,
markerOpacity: number,
selected: boolean,
) {
const owned = property.ownedAmount > 0;
const ownedClass = differentiateOwned && owned ? CSSCLASSES.owned : CSSCLASSES.notOwned;
Expand All @@ -37,7 +38,7 @@ export function generateIcon(
${owned ? 'data-marker-owned' : ''}
${property.source ? `data-marker-${property.source}` : ''}
${property.ownerWallets.length ? `data-marker-wallet="${property.ownerWallets.join(' ')}"` : ''}>
${pinSvg(`${property.iconColorClass}-icon ${ownedClass}`)}
${pinSvg(`${property.iconColorClass}-icon ${ownedClass + (selected ? ' selected' : '')}`)}
<i class="text-3xl drop-shadow-sm mf-icon material-icons absolute top-0 left-[20%]">${property.icon}</i>
</div>`,
iconSize: [50, 50],
Expand All @@ -50,9 +51,13 @@ function filterProperties(
displayAll: boolean,
displayGnosis: boolean,
displayRmm: boolean,
selected: string | null,
) {
return properties
.filter((property) => {
if (selected && property.address === selected) {
return true;
}
let toInclude = !property.isOld && property.productType !== 'equity_token';
if (!displayAll && property.ownedAmount <= 0) {
toInclude = false;
Expand All @@ -71,10 +76,11 @@ function createMarker(
property: Property,
markerOpacity: number,
differentiateOwned: boolean,
selected: Maybe<string>,
t: TFunction<"common", undefined>,
): Marker {
return marker([property.coordinate.lat, property.coordinate.lng], {
icon: generateIcon(property, differentiateOwned, markerOpacity),
icon: generateIcon(property, differentiateOwned, markerOpacity, selected === property.address),
alt: property.propertyTypeName,
title: t('propertyType.' + property.propertyTypeName),
});
Expand Down Expand Up @@ -107,10 +113,8 @@ let markerCluster: MarkerClusterGroup;
let markers: Array<Marker> = [];
export function MapMarkers({
properties,
wallets,
}: {
properties: Property[];
wallets: Wallet[];
}) {
const { t } = useTranslation('common');
const dispatch = useAppDispatch();
Expand All @@ -122,6 +126,7 @@ export function MapMarkers({
differentiateOwned,
markerOpacity,
} = useAppSelector((state) => state.mapOptions);
const selectedUrlParam = useAppSelector(selectedProperty);

function onMarkerClicked(event: LeafletMouseEvent, property: Property) {
const currentZoom = map.getZoom();
Expand All @@ -142,6 +147,7 @@ export function MapMarkers({
lng: event.latlng.lng,
},
}));
dispatch(setSelectedProperty(property.address));
}

function clearMap() {
Expand All @@ -156,6 +162,8 @@ export function MapMarkers({
markerCluster.clearAllEventListeners();
markerCluster.clearLayers();
map.removeLayer(markerCluster);
map.removeEventListener('zoom');
map.removeEventListener('moveend');
}

function getCleanMarkerCluster() {
Expand All @@ -173,15 +181,37 @@ export function MapMarkers({
clearMap();
markerCluster = getCleanMarkerCluster();

filterProperties(properties, displayAll, displayGnosis, displayRmm)
filterProperties(properties, displayAll, displayGnosis, displayRmm, selectedUrlParam)
.forEach((property) => {
const marker = createMarker(property, markerOpacity, differentiateOwned, t)
const marker = createMarker(property, markerOpacity, differentiateOwned, selectedUrlParam, t)
.addEventListener('click', (event) => onMarkerClicked(event, property));
markers.push(marker);
markerCluster.addLayer(marker);
});
map.addLayer(markerCluster);
map.addEventListener('zoom', (event: LeafletEvent) => {
dispatch(setZoom(event.target.getZoom()));
});
map.addEventListener('moveend', (event: LeafletEvent) => {
dispatch(setLatLng([event.target.getCenter().lat, event.target.getCenter().lng]));
});

if (selectedUrlParam) {
const selectedProperty = properties.find((property) => property.address === selectedUrlParam);
if (selectedProperty) {
dispatch(setSelected({
property: selectedProperty,
latlng: {
lat: selectedProperty.coordinate.lat,
lng: selectedProperty.coordinate.lng,
},
}));
}
}

return () => {
clearMap();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties, displayAll, displayGnosis, displayRmm, differentiateOwned, markerOpacity]);

Expand Down
10 changes: 6 additions & 4 deletions frontend/src/components/Map/MapWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ import { Property } from '../../types/property';
import { PropertyPanel } from './PropertyPanel';
import { MapMarkers } from './MapMarkers';
import { MapEvents } from './MapEvents';
import { selectedLatLng, selectedZoom } from '../../store/urlQuery/urlQuery.selector';

export function MapWrapper() {
const realToken = useAppSelector(selectRealtokensList);
const wallets = useAppSelector(selectWalletsList);
const center = useAppSelector(selectedLatLng);
const zoom = useAppSelector(selectedZoom);

const [properties, setProperties] = useState<Property[]>([]);

Expand All @@ -25,12 +28,11 @@ export function MapWrapper() {
<>
<MapContainer
className="map"
center={[32, -83]}
zoom={4}
>
center={center}
zoom={zoom}>
<TileLayer url={`https://tile.openstreetmap.org/{z}/{x}/{y}.png`}/>
<MarkerClusterGroup>
<MapMarkers properties={properties} wallets={wallets} />
<MapMarkers properties={properties} />
<MapEvents />
</MarkerClusterGroup>
</MapContainer>
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/components/Map/PropertyPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import date from "../../utils/date";
import { selectedLanguage } from "../../store/settings/settingsSelector";
import { selectDifferentiateOwned } from "../../store/mapOptions/mapOptionsSelector";
import { useElementSize } from "@mantine/hooks";
import { setSelectedProperty } from "../../store/urlQuery/urlQuery.reducer";

function toFixedStr(value: number, precision: number = 2) {
return value.toFixed(precision).toLowerCase();
Expand All @@ -31,6 +32,7 @@ export function PropertyPanel() {
}, [property]);

function onClose() {
dispatch(setSelectedProperty(null));
dispatch(clearSelected());
}

Expand Down
5 changes: 4 additions & 1 deletion frontend/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import './index.css';
import '@mantine/core/styles.css';
import '@mantine/carousel/styles.css';
import App from './App';
import { BrowserRouter } from 'react-router-dom';

const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);

Expand Down
41 changes: 41 additions & 0 deletions frontend/src/providers/QueryProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useEffect } from "react";
import { useSearchParams } from "react-router-dom";
import { useAppSelector } from "../hooks/useInitStore";
import { selectedLatLng, selectedProperty, selectedZoom } from "../store/urlQuery/urlQuery.selector";
import { Maybe } from "../types/global";

function handleSearchParam(
searchParams: URLSearchParams,
key: string,
value: Maybe<string>,
) {
if (value) {
searchParams.set(key, value);
} else {
searchParams.delete(key);
}
return searchParams;
}

export function QueryProvider({
children,
}: {
children: React.ReactNode
}) {
let [_, setSearchParams] = useSearchParams();
const center = useAppSelector(selectedLatLng);
const zoom = useAppSelector(selectedZoom);
const selected = useAppSelector(selectedProperty);

useEffect(() => {
const newSearchParams = new URLSearchParams();
handleSearchParam(newSearchParams, "latlng", JSON.stringify(center));
handleSearchParam(newSearchParams, "zoom", JSON.stringify(zoom));
handleSearchParam(newSearchParams, "selected", selected);
setSearchParams(newSearchParams);
}, [center, zoom, selected, setSearchParams]);

return (
<>{children}</>
)
}
2 changes: 2 additions & 0 deletions frontend/src/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import realTokensReducer from './realtokens/realtokensReducer';
import settingsReducer from './settings/settingsReducer';
import markerReducer from './marker/markerReducer';
import currenciesReducer from './currencies/currenciesReducer';
import urlQueryReducer from './urlQuery/urlQuery.reducer';

const rootReducer = combineReducers({
settings: settingsReducer,
Expand All @@ -13,6 +14,7 @@ const rootReducer = combineReducers({
realtokens: realTokensReducer,
marker: markerReducer,
currencies: currenciesReducer,
urlQuery: urlQueryReducer,
});

const store = configureStore({
Expand Down
Loading

0 comments on commit a35c36c

Please sign in to comment.