From 4b81f042005c32962bd62dc3fad5574011b4fb2e Mon Sep 17 00:00:00 2001 From: Camden Date: Tue, 21 May 2024 14:13:45 -0400 Subject: [PATCH] Add `RelatedSources` Core Data component (#286) * add relatedworks component * export RelatedWorks * refactor into RelatedSources * fix bad import path * add RelatedItems back in --- .../core-data/src/components/RelatedItems.js | 1 + .../src/components/RelatedSources.js | 110 ++++++++++++++++++ packages/core-data/src/hooks/CoreData.js | 2 - packages/core-data/src/index.js | 1 + .../core-data/src/services/BaseService.js | 26 +++++ packages/core-data/src/types/Instance.js | 9 ++ packages/core-data/src/types/Source.js | 7 ++ packages/core-data/src/types/Work.js | 9 ++ .../.storybook/api/core-data/Instances.js | 35 ++++++ .../.storybook/api/core-data/Works.js | 35 ++++++ .../storybook/.storybook/routes/CoreData.js | 14 ++- .../src/core-data/EventDetails.stories.js | 5 +- .../src/core-data/RelatedSources.stories.js | 48 ++++++++ 13 files changed, 297 insertions(+), 5 deletions(-) create mode 100644 packages/core-data/src/components/RelatedSources.js create mode 100644 packages/core-data/src/types/Instance.js create mode 100644 packages/core-data/src/types/Source.js create mode 100644 packages/core-data/src/types/Work.js create mode 100644 packages/storybook/.storybook/api/core-data/Instances.js create mode 100644 packages/storybook/.storybook/api/core-data/Works.js create mode 100644 packages/storybook/src/core-data/RelatedSources.stories.js diff --git a/packages/core-data/src/components/RelatedItems.js b/packages/core-data/src/components/RelatedItems.js index 45427828..a9a79719 100644 --- a/packages/core-data/src/components/RelatedItems.js +++ b/packages/core-data/src/components/RelatedItems.js @@ -40,6 +40,7 @@ type Props = { /** * This component render a list of related items. + * @deprecated */ const RelatedItems = (props: Props) => { const { data: { items } = {}, loading } = useLoader(props.onLoad, []); diff --git a/packages/core-data/src/components/RelatedSources.js b/packages/core-data/src/components/RelatedSources.js new file mode 100644 index 00000000..18015c28 --- /dev/null +++ b/packages/core-data/src/components/RelatedSources.js @@ -0,0 +1,110 @@ +// @flow + +import React from 'react'; +import _ from 'underscore'; +import type { Source as SourceType } from '../types/Source'; +import LoadAnimation from './LoadAnimation'; +import { useLoader } from '../hooks/CoreData'; + +type Props = { + /** + * Name of the class(es) to apply to the `ul` element. + */ + className?: string, + + /** + * Callback fired when a source in the list is clicked. + */ + onClick: (source: SourceType) => void, + + /** + * Callback fired on mount to load the list of items. + */ + onLoad: () => Promise, + + /** + * Function used to render the description element. + */ + renderDescription?: (source: SourceType) => JSX.Element, + + /** + * Function used to render the header element. + */ + renderHeader?: (source: SourceType) => JSX.Element, + + /** + * Function used to render the image element. + */ + renderImage?: (source: SourceType) => JSX.Element, + + /** + * Type of the source being fetched. + */ + sourceType: 'instances' | 'items' | 'works' +}; + +/** + * This component render a list of related items. + */ +const RelatedSources = (props: Props) => { + const { data = {}, loading } = useLoader(props.onLoad, []); + + if (loading) { + return ( + + ); + } + + return ( +
    + { _.map(data[props.sourceType], (item) => ( +
  • +
    + +
    +
  • + ))} +
+ ); +}; + +export default RelatedSources; diff --git a/packages/core-data/src/hooks/CoreData.js b/packages/core-data/src/hooks/CoreData.js index c32938b5..2c1850ac 100644 --- a/packages/core-data/src/hooks/CoreData.js +++ b/packages/core-data/src/hooks/CoreData.js @@ -35,8 +35,6 @@ export const useLoader = (onLoad, params = {}, deps = []) => { const [loading, setLoading] = useState(false); const [page, setPage] = useState(DEFAULT_PAGE); - const { baseUrl, projectIds } = useContext(CoreDataContext); - /** * Memo-izes the list metadata. * diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index 69f91980..5753b150 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -30,6 +30,7 @@ export { default as RelatedOrganizations } from './components/RelatedOrganizatio export { default as RelatedPeople } from './components/RelatedPeople'; export { default as RelatedPlaces } from './components/RelatedPlaces'; export { default as RelatedPlacesLayer } from './components/RelatedPlacesLayer'; +export { default as RelatedSources } from './components/RelatedSources'; export { default as RelatedTaxonomies } from './components/RelatedTaxonomies'; export { default as SearchResultsLayer } from './components/SearchResultsLayer'; diff --git a/packages/core-data/src/services/BaseService.js b/packages/core-data/src/services/BaseService.js index 5c3a491d..ad18467f 100644 --- a/packages/core-data/src/services/BaseService.js +++ b/packages/core-data/src/services/BaseService.js @@ -41,6 +41,19 @@ class BaseService { return fetch(url).then((response) => response.json()); } + /** + * Calls the GET /core_data/public///:id/instances API endpoint. + * + * @param id + * @param params + * + * @returns {Promise} + */ + fetchRelatedInstances(id, params = {}) { + const url = Api.buildNestedUrl(this.baseUrl, this.getRoute(), id, 'instances', this.getSearchParams(params)); + return fetch(url).then((response) => response.json()); + } + /** * Calls the GET /core_data/public///:id/items API endpoint. * @@ -131,6 +144,19 @@ class BaseService { return fetch(url).then((response) => response.json()); } + /** + * Calls the GET /core_data/public///:id/works API endpoint. + * + * @param id + * @param params + * + * @returns {Promise} + */ + fetchRelatedWorks(id, params = {}) { + const url = Api.buildNestedUrl(this.baseUrl, this.getRoute(), id, 'works', this.getSearchParams(params)); + return fetch(url).then((response) => response.json()); + } + // protected getRoute() { diff --git a/packages/core-data/src/types/Instance.js b/packages/core-data/src/types/Instance.js new file mode 100644 index 00000000..53a5e581 --- /dev/null +++ b/packages/core-data/src/types/Instance.js @@ -0,0 +1,9 @@ +// @flow + +import type { SourceTitle } from './SourceTitle'; + +export type Instance = { + uuid: string, + primary_name: SourceTitle, + source_titles: Array +}; diff --git a/packages/core-data/src/types/Source.js b/packages/core-data/src/types/Source.js new file mode 100644 index 00000000..7619d03d --- /dev/null +++ b/packages/core-data/src/types/Source.js @@ -0,0 +1,7 @@ +// @flow + +import type { Instance } from './Instance'; +import type { Item } from './Item'; +import type { Work } from './Work'; + +export type Source = Instance | Item | Work; diff --git a/packages/core-data/src/types/Work.js b/packages/core-data/src/types/Work.js new file mode 100644 index 00000000..8c85d09d --- /dev/null +++ b/packages/core-data/src/types/Work.js @@ -0,0 +1,9 @@ +// @flow + +import type { SourceTitle } from './SourceTitle'; + +export type Work = { + uuid: string, + primary_name: SourceTitle, + source_titles: Array +}; diff --git a/packages/storybook/.storybook/api/core-data/Instances.js b/packages/storybook/.storybook/api/core-data/Instances.js new file mode 100644 index 00000000..ee7aad0a --- /dev/null +++ b/packages/storybook/.storybook/api/core-data/Instances.js @@ -0,0 +1,35 @@ +// @flow + +import { faker } from '@faker-js/faker'; +import Base from './Base'; + +class Instances extends Base { + /** + * Returns a single instance. + */ + buildItem() { + return { + uuid: faker.string.uuid(), + primary_name: { + name: { + name: faker.lorem.words({ min: 1, max: 3 }) + } + } + }; + } + + /** + * Returns the instances parameter name. + * + * @returns {string} + */ + getIndexAttribute() { + return 'instances'; + } + + getShowAttribute() { + return 'instance'; + } +} + +export default new Instances(); diff --git a/packages/storybook/.storybook/api/core-data/Works.js b/packages/storybook/.storybook/api/core-data/Works.js new file mode 100644 index 00000000..3f481a13 --- /dev/null +++ b/packages/storybook/.storybook/api/core-data/Works.js @@ -0,0 +1,35 @@ +// @flow + +import { faker } from '@faker-js/faker'; +import Base from './Base'; + +class Works extends Base { + /** + * Returns a single work. + */ + buildItem() { + return { + uuid: faker.string.uuid(), + primary_name: { + name: { + name: faker.lorem.words({ min: 1, max: 3 }) + } + } + }; + } + + /** + * Returns the works parameter name. + * + * @returns {string} + */ + getIndexAttribute() { + return 'works'; + } + + getShowAttribute() { + return 'work'; + } +} + +export default new Works(); diff --git a/packages/storybook/.storybook/routes/CoreData.js b/packages/storybook/.storybook/routes/CoreData.js index 4dee9ffa..757db8bb 100644 --- a/packages/storybook/.storybook/routes/CoreData.js +++ b/packages/storybook/.storybook/routes/CoreData.js @@ -2,13 +2,15 @@ import { BASE_URL } from '../api/core-data/Base'; import Events from '../api/core-data/Events'; +import Instances from '../api/core-data/Instances'; import Items from '../api/core-data/Items'; import Places from '../api/core-data/Places'; import Organizations from '../api/core-data/Organizations'; import People from '../api/core-data/People'; -import Taxonomies from '../api/core-data/Taxonomies'; import Manifests from '../api/core-data/Manifests'; import MediaContents from '../api/core-data/MediaContents'; +import Taxonomies from '../api/core-data/Taxonomies'; +import Works from '../api/core-data/Works'; /** * Adds the Core Data dummy routes. @@ -25,6 +27,11 @@ const addRoutes = (router) => { response.end(); }); + router.get(`${BASE_URL}/events/:id/instances`, (request, response) => { + response.send(Instances.fetchItems(5)); + response.end(); + }); + router.get(`${BASE_URL}/events/:id/items`, (request, response) => { response.send(Items.fetchItems(5)); response.end(); @@ -45,6 +52,11 @@ const addRoutes = (router) => { response.end(); }); + router.get(`${BASE_URL}/events/:id/works`, (request, response) => { + response.send(Works.fetchItems(5)); + response.end(); + }); + router.get(`${BASE_URL}/places/:id`, (request, response) => { response.send(Places.fetchItem()); response.end(); diff --git a/packages/storybook/src/core-data/EventDetails.stories.js b/packages/storybook/src/core-data/EventDetails.stories.js index 094d5640..c3e22427 100644 --- a/packages/storybook/src/core-data/EventDetails.stories.js +++ b/packages/storybook/src/core-data/EventDetails.stories.js @@ -8,7 +8,7 @@ import React from 'react'; import _ from 'underscore'; import EventDetails from '../../../core-data/src/components/EventDetails'; import mapStyle from '../data/MapStyles.json'; -import RelatedItems from '../../../core-data/src/components/RelatedItems'; +import RelatedSources from '../../../core-data/src/components/RelatedSources'; import RelatedMedia from '../../../core-data/src/components/RelatedMedia'; import RelatedPeople from '../../../core-data/src/components/RelatedPeople'; import RelatedPlacesLayer from '../../../core-data/src/components/RelatedPlacesLayer'; @@ -75,7 +75,7 @@ export const RelatedRecords = withCoreDataContextProvider(() => { > Related Resources - action('click')(item)} onLoad={(params) => EventsService.fetchRelatedItems('1', params)} renderDescription={() => faker.date.anytime().toLocaleDateString()} @@ -86,6 +86,7 @@ export const RelatedRecords = withCoreDataContextProvider(() => { src={`https://picsum.photos/800/600?random=${item.uuid}`} /> )} + sourceType='items' />
{ + const EventsService = useEventsService(); + + return ( + EventsService.fetchRelatedItems('1', params)} + sourceType='items' + /> + ); +}); + +export const Instances = withCoreDataContextProvider(() => { + const EventsService = useEventsService(); + + return ( + EventsService.fetchRelatedInstances('1', params)} + sourceType='instances' + /> + ); +}); + +export const Works = withCoreDataContextProvider(() => { + const EventsService = useEventsService(); + + return ( + EventsService.fetchRelatedWorks('1', params)} + sourceType='works' + /> + ); +});