Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update PlacesDetailPanel with more flexible styling #264

Closed
wants to merge 9 commits into from
27,103 changes: 27,103 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion packages/core-data/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-dialog": "^1.0.5",
"@samvera/clover-iiif": "^2.3.2",
"@turf/turf": "^6.5.0",
"i18next": "^23.8.2",
"lucide-react": "^0.321.0",
"react-instantsearch": "^7.5.4",
Expand All @@ -44,4 +45,4 @@
"tailwindcss": "^3.4.1",
"vite": "^5.1.4"
}
}
}
2 changes: 1 addition & 1 deletion packages/core-data/src/components/PlaceDetailsPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const PlaceDetailsPanel = (props: Props) => {

return (
<aside
className='flex flex-col absolute z-10 h-full w-[280px] bg-white/80 backdrop-blur shadow overflow-y-auto'
className='flex flex-col relative z-10 h-full min-w-[260px] bg-white/80 backdrop-blur shadow overflow-y-auto'
ref={el}
>
<button
Expand Down
59 changes: 0 additions & 59 deletions packages/core-data/src/components/PlaceMarker.js

This file was deleted.

77 changes: 77 additions & 0 deletions packages/core-data/src/components/PlaceMarkers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// @flow

import { LocationMarkers } from '@performant-software/geospatial';
import { feature, featureCollection } from '@turf/turf';
import React, {
useCallback,
useEffect,
useMemo,
useState
} from 'react';
import _ from 'underscore';

type Props = {
animate?: boolean,

/**
* The URL of the Core Data place record.
*/
urls: Array<string>
};

/**
* This component renders a map marker for a given Core Data Place record.
*/
const PlaceMarkers = (props: Props) => {
const [places, setPlaces] = useState([]);

/**
* Converts the set of places into a FeatureCollection.
*
* @type {FeatureCollection<Geometry, Properties>}
*/
const data = useMemo(() => featureCollection(places), [places]);

/**
* Fetches the passed URLs and converts the response to JSON.
*
* @type {function(): *}
*/
const onFetch = useCallback(() => (
_.map(props.urls, (url) => fetch(url).then((res) => res.json()))
), [props.urls]);

/**
* Converts the passed list of records to Features and sets them on the state.
*
* @type {function(*): *}
*/
const onLoad = useCallback((records) => (
_.map(records, (record) => setPlaces((prevPlaces) => [
...prevPlaces,
feature(record.geometry, record.properties)
]))
), []);

/**
* Fetch the place record from the passed URL.
*/
useEffect(() => {
Promise
.all(onFetch())
.then(onLoad);
}, [onFetch, onLoad]);

if (_.isEmpty(data?.features)) {
return null;
}

return (
<LocationMarkers
animate={props.animate}
data={data}
/>
);
};

export default PlaceMarkers;
2 changes: 1 addition & 1 deletion packages/core-data/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import './index.css';
export { default as LoadAnimation } from './components/LoadAnimation';
export { default as MediaGallery } from './components/MediaGallery';
export { default as PlaceDetailsPanel } from './components/PlaceDetailsPanel';
export { default as PlaceMarker } from './components/PlaceMarker';
export { default as PlaceMarkers } from './components/PlaceMarkers';
export { default as PlaceResultsList } from './components/PlaceResultsList';
export { default as RelatedItemsList } from './components/RelatedItemsList';
export { default as RelatedList } from './components/RelatedList';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import { DEFAULT_FILL_STYLE, DEFAULT_POINT_STYLE, DEFAULT_STROKE_STYLE } from '.
import MapUtils from '../utils/Map';

type Props = {
/**
* If `true`, the point marker will display with a pulsing animation.
*/
animate?: boolean,

/**
* The number of miles to buffer the GeoJSON data.
*/
Expand Down Expand Up @@ -37,7 +42,7 @@ const DEFAULT_BUFFER = 2;
/**
* This component renders a location marker to be used in a Peripleo context.
*/
const LocationMarker = (props: Props) => {
const LocationMarkers = (props: Props) => {
const map = useMap();

/**
Expand All @@ -52,12 +57,12 @@ const LocationMarker = (props: Props) => {

return (
<>
<PulsingMarkerLayer
id='current'
data={props.data}
/>
{ props.animate && (
<PulsingMarkerLayer
data={props.data}
/>
)}
<MixedGeoJSONLayer
id='current'
data={props.data}
fillStyle={props.fillStyle}
strokeStyle={props.strokeStyle}
Expand All @@ -67,11 +72,11 @@ const LocationMarker = (props: Props) => {
);
};

LocationMarker.defaultProps = {
LocationMarkers.defaultProps = {
buffer: DEFAULT_BUFFER,
fillStyle: DEFAULT_FILL_STYLE,
pointStyle: DEFAULT_POINT_STYLE,
strokeStyle: DEFAULT_STROKE_STYLE
};

export default LocationMarker;
export default LocationMarkers;
2 changes: 1 addition & 1 deletion packages/geospatial/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
export { default as DrawControl } from './components/DrawControl';
export { default as GeoJsonLayer } from './components/GeoJsonLayer';
export { default as LayerMenu } from './components/LayerMenu';
export { default as LocationMarker } from './components/LocationMarker';
export { default as LocationMarkers } from './components/LocationMarkers';
export { default as MapControl } from './components/MapControl';
export { default as MapDraw } from './components/MapDraw';
export { default as RasterLayer } from './components/RasterLayer';
Expand Down
14 changes: 8 additions & 6 deletions packages/storybook/.storybook/middleware.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// @flow

const bodyParser = require('body-parser');
const ControlledVocabulary = require('./routes/ControlledVocabulary');
const dotenv = require('dotenv');
const UserDefinedFields = require('./routes/UserDefinedFields');
const ZoteroTranslate = require('./routes/ZoteroTranslate');
import bodyParser from 'body-parser';
import dotenv from 'dotenv';
import ControlledVocabulary from './routes/ControlledVocabulary';
import CoreData from './routes/CoreData';
import UserDefinedFields from './routes/UserDefinedFields';
import ZoteroTranslate from './routes/ZoteroTranslate';

// Configure environment variables
dotenv.config();
Expand All @@ -14,8 +15,9 @@ const expressMiddleWare = (router) => {
router.use(bodyParser.json());

ControlledVocabulary.addRoutes(router);
CoreData.addRoutes(router);
UserDefinedFields.addRoutes(router);
ZoteroTranslate.addRoutes(router);
};

module.exports = expressMiddleWare;
export default expressMiddleWare;
132 changes: 132 additions & 0 deletions packages/storybook/.storybook/routes/CoreData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// @flow

import { faker } from '@faker-js/faker';
import _ from 'underscore';
import StateBoundaries from '../../src/data/StateBoundaries.json';

/**
* Returns the min/max latitude/longitude for the passed state. If no state is provided, we'll compute
* the boundary of all states.
*
* @param state
*
* @returns {{max_lat, min_lng, min_lat, max_lng}|*}
*/
const getBoundary = (state = null) => {
if (state) {
return StateBoundaries[state];
}

let minLatitude;
let maxLatitude;
let minLongitude;
let maxLongitude;

const keys = _.keys(StateBoundaries);
_.each(keys, (key) => {
const data = StateBoundaries[key];

if (!minLatitude || data.min_lat < minLatitude) {
minLatitude = data.min_lat;
}

if (!maxLatitude || data.max_lat > maxLatitude) {
maxLatitude = data.max_lat;
}

if (!minLongitude || data.min_lng < minLongitude) {
minLongitude = data.min_lng;
}

if (!maxLongitude || data.max_lng > maxLongitude) {
maxLongitude = data.max_lng;
}
});

return {
min_lat: minLatitude,
max_lat: maxLatitude,
min_lng: minLongitude,
max_lng: maxLongitude
};
};

/**
* Creates a sample place record in the Linked Places format.
*
* @returns {{
* names: [{
* toponym: string
* }],
* geometry: {
* coordinates: number[],
* type: string
* },
* '@id': string,
* type: string,
* properties: {
* ccode: *[],
* record_id: string,
* title: string,
* uuid: string
* }
* }}
*/
const createPlace = () => {
const uuid = faker.string.uuid();
const title = faker.location.city();

const boundary = getBoundary();
const latitude = faker.location.latitude({ min: boundary.min_lat, max: boundary.max_lat });
const longitude = faker.location.longitude({ min: boundary.min_lng, max: boundary.max_lng });

return {
'@id': `https://example.com/${uuid}`,
type: 'Place',
properties: {
ccode: [],
record_id: faker.string.numeric(7),
title,
uuid
},
geometry: {
type: 'Point',
coordinates: [longitude, latitude]
},
names: [{
toponym: title
}]
};
};

/**
* Adds the `/core_data/places` and `/core_data/places/:id` routes.
*
* @param router
*/
const addRoutes = (router) => {
router.get('/core_data/places', (request, response) => {
const { query } = request;

let number = 1;
if (query.number) {
number = query.number;
}

const places = [];

_.times(number, () => places.push(createPlace()));

response.send(places);
response.end();
});

router.get('/core_data/places/:id', (request, response) => {
response.send(createPlace());
response.end();
});
};

export default {
addRoutes
};
Loading