@collectionspace/cspace-public-browser 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/LICENSE.md +71 -0
  2. package/README.md +38 -0
  3. package/dist/cspacePublicBrowser.js +4680 -0
  4. package/dist/cspacePublicBrowser.min.js +2 -0
  5. package/dist/cspacePublicBrowser.min.js.LICENSE.txt +56 -0
  6. package/images/check.svg +3 -0
  7. package/images/close.svg +3 -0
  8. package/images/collapse.svg +3 -0
  9. package/images/collapseActive.svg +3 -0
  10. package/images/expand.svg +3 -0
  11. package/images/expandActive.svg +3 -0
  12. package/images/filter.svg +4 -0
  13. package/images/hideLeft.svg +4 -0
  14. package/images/linkBack.svg +4 -0
  15. package/images/linkDown.svg +4 -0
  16. package/images/linkNext.svg +4 -0
  17. package/images/linkPrev.svg +4 -0
  18. package/images/openNew.svg +4 -0
  19. package/images/search.svg +4 -0
  20. package/images/select.svg +4 -0
  21. package/images/top.svg +1 -0
  22. package/lib/actions/detailActions.js +177 -0
  23. package/lib/actions/filterActions.js +16 -0
  24. package/lib/actions/mediaActions.js +63 -0
  25. package/lib/actions/prefsActions.js +84 -0
  26. package/lib/actions/searchActions.js +140 -0
  27. package/lib/actions/searchEntryFormActions.js +19 -0
  28. package/lib/components/App.js +24 -0
  29. package/lib/components/detail/DetailNavBar.js +98 -0
  30. package/lib/components/detail/DetailPanel.js +171 -0
  31. package/lib/components/detail/DetailPanelContainer.js +22 -0
  32. package/lib/components/detail/ExhibitionSection.js +54 -0
  33. package/lib/components/detail/FieldList.js +95 -0
  34. package/lib/components/detail/FieldValueList.js +30 -0
  35. package/lib/components/detail/ImageGallery.js +137 -0
  36. package/lib/components/detail/ImageGalleryContainer.js +19 -0
  37. package/lib/components/detail/InstitutionHoldingList.js +155 -0
  38. package/lib/components/detail/InstitutionHoldingListContainer.js +22 -0
  39. package/lib/components/detail/InstitutionIndex.js +53 -0
  40. package/lib/components/detail/InstitutionIndexContainer.js +15 -0
  41. package/lib/components/detail/InstitutionSection.js +44 -0
  42. package/lib/components/detail/InstitutionSectionContainer.js +15 -0
  43. package/lib/components/layout/Fixed.js +26 -0
  44. package/lib/components/layout/IconButton.js +41 -0
  45. package/lib/components/layout/Panel.js +56 -0
  46. package/lib/components/layout/PanelContainer.js +19 -0
  47. package/lib/components/layout/PanelTitle.js +38 -0
  48. package/lib/components/layout/ScrollTopButton.js +70 -0
  49. package/lib/components/layout/ToggleFilterPanelButton.js +42 -0
  50. package/lib/components/pages/DetailPage.js +93 -0
  51. package/lib/components/pages/DetailPageContainer.js +20 -0
  52. package/lib/components/pages/RootPage.js +41 -0
  53. package/lib/components/pages/SearchPage.js +130 -0
  54. package/lib/components/pages/SearchPageContainer.js +23 -0
  55. package/lib/components/search/entry/SearchEntryForm.js +74 -0
  56. package/lib/components/search/entry/SearchEntryFormContainer.js +20 -0
  57. package/lib/components/search/entry/SearchEntryPanel.js +30 -0
  58. package/lib/components/search/entry/SearchQueryInput.js +89 -0
  59. package/lib/components/search/entry/SearchSubmitButton.js +22 -0
  60. package/lib/components/search/entry/SortSelect.js +89 -0
  61. package/lib/components/search/entry/SortSelectContainer.js +15 -0
  62. package/lib/components/search/result/ClearSearchParamsLink.js +42 -0
  63. package/lib/components/search/result/Filter.js +186 -0
  64. package/lib/components/search/result/FilterContainer.js +22 -0
  65. package/lib/components/search/result/FilterGroup.js +72 -0
  66. package/lib/components/search/result/FilterList.js +48 -0
  67. package/lib/components/search/result/FilterPanel.js +115 -0
  68. package/lib/components/search/result/FilterPanelContainer.js +16 -0
  69. package/lib/components/search/result/FilterSearchInput.js +63 -0
  70. package/lib/components/search/result/RemoveSearchParamLink.js +66 -0
  71. package/lib/components/search/result/SearchError.js +29 -0
  72. package/lib/components/search/result/SearchLoadMore.js +36 -0
  73. package/lib/components/search/result/SearchParamList.js +41 -0
  74. package/lib/components/search/result/SearchPending.js +21 -0
  75. package/lib/components/search/result/SearchResultImage.js +227 -0
  76. package/lib/components/search/result/SearchResultList.js +119 -0
  77. package/lib/components/search/result/SearchResultPanel.js +139 -0
  78. package/lib/components/search/result/SearchResultPanelContainer.js +23 -0
  79. package/lib/components/search/result/SearchResultStats.js +39 -0
  80. package/lib/components/search/result/SearchResultTile.js +61 -0
  81. package/lib/config/anthro.js +123 -0
  82. package/lib/config/bonsai.js +50 -0
  83. package/lib/config/botgarden.js +10 -0
  84. package/lib/config/default.js +530 -0
  85. package/lib/config/fcart.js +36 -0
  86. package/lib/config/herbarium.js +10 -0
  87. package/lib/config/index.js +53 -0
  88. package/lib/config/lhmc.js +10 -0
  89. package/lib/config/materials.js +982 -0
  90. package/lib/config/publicart.js +10 -0
  91. package/lib/constants/actionCodes.js +46 -0
  92. package/lib/constants/ids.js +12 -0
  93. package/lib/helpers/bodyClassName.js +11 -0
  94. package/lib/helpers/esQueryHelpers.js +206 -0
  95. package/lib/helpers/formatHelpers.js +293 -0
  96. package/lib/helpers/searchDimensions.js +28 -0
  97. package/lib/helpers/urlHelpers.js +43 -0
  98. package/lib/index.js +53 -0
  99. package/lib/intl/index.js +16 -0
  100. package/lib/reducers/detailReducer.js +145 -0
  101. package/lib/reducers/filterReducer.js +22 -0
  102. package/lib/reducers/index.js +66 -0
  103. package/lib/reducers/mediaReducer.js +43 -0
  104. package/lib/reducers/prefsReducer.js +27 -0
  105. package/lib/reducers/searchEntryFormReducer.js +24 -0
  106. package/lib/reducers/searchReducer.js +88 -0
  107. package/package.json +118 -0
  108. package/src/actions/detailActions.js +231 -0
  109. package/src/actions/filterActions.js +10 -0
  110. package/src/actions/mediaActions.js +65 -0
  111. package/src/actions/prefsActions.js +95 -0
  112. package/src/actions/searchActions.js +188 -0
  113. package/src/actions/searchEntryFormActions.js +15 -0
  114. package/src/components/App.jsx +18 -0
  115. package/src/components/detail/DetailNavBar.jsx +132 -0
  116. package/src/components/detail/DetailPanel.jsx +215 -0
  117. package/src/components/detail/DetailPanelContainer.js +29 -0
  118. package/src/components/detail/ExhibitionSection.jsx +71 -0
  119. package/src/components/detail/FieldList.jsx +122 -0
  120. package/src/components/detail/FieldValueList.jsx +31 -0
  121. package/src/components/detail/ImageGallery.jsx +153 -0
  122. package/src/components/detail/ImageGalleryContainer.js +17 -0
  123. package/src/components/detail/InstitutionHoldingList.jsx +188 -0
  124. package/src/components/detail/InstitutionHoldingListContainer.js +29 -0
  125. package/src/components/detail/InstitutionIndex.jsx +57 -0
  126. package/src/components/detail/InstitutionIndexContainer.js +11 -0
  127. package/src/components/detail/InstitutionSection.jsx +48 -0
  128. package/src/components/detail/InstitutionSectionContainer.js +11 -0
  129. package/src/components/layout/Fixed.jsx +29 -0
  130. package/src/components/layout/IconButton.jsx +41 -0
  131. package/src/components/layout/Panel.jsx +60 -0
  132. package/src/components/layout/PanelContainer.js +20 -0
  133. package/src/components/layout/PanelTitle.jsx +43 -0
  134. package/src/components/layout/ScrollTopButton.jsx +76 -0
  135. package/src/components/layout/ToggleFilterPanelButton.jsx +43 -0
  136. package/src/components/pages/DetailPage.jsx +101 -0
  137. package/src/components/pages/DetailPageContainer.js +18 -0
  138. package/src/components/pages/RootPage.jsx +37 -0
  139. package/src/components/pages/SearchPage.jsx +160 -0
  140. package/src/components/pages/SearchPageContainer.js +21 -0
  141. package/src/components/search/entry/SearchEntryForm.jsx +82 -0
  142. package/src/components/search/entry/SearchEntryFormContainer.js +22 -0
  143. package/src/components/search/entry/SearchEntryPanel.jsx +28 -0
  144. package/src/components/search/entry/SearchQueryInput.jsx +95 -0
  145. package/src/components/search/entry/SearchSubmitButton.jsx +22 -0
  146. package/src/components/search/entry/SortSelect.jsx +104 -0
  147. package/src/components/search/entry/SortSelectContainer.js +12 -0
  148. package/src/components/search/result/ClearSearchParamsLink.jsx +43 -0
  149. package/src/components/search/result/Filter.jsx +226 -0
  150. package/src/components/search/result/FilterContainer.js +20 -0
  151. package/src/components/search/result/FilterGroup.jsx +83 -0
  152. package/src/components/search/result/FilterList.jsx +51 -0
  153. package/src/components/search/result/FilterPanel.jsx +143 -0
  154. package/src/components/search/result/FilterPanelContainer.js +16 -0
  155. package/src/components/search/result/FilterSearchInput.jsx +68 -0
  156. package/src/components/search/result/RemoveSearchParamLink.jsx +79 -0
  157. package/src/components/search/result/SearchError.jsx +30 -0
  158. package/src/components/search/result/SearchLoadMore.jsx +37 -0
  159. package/src/components/search/result/SearchParamList.jsx +47 -0
  160. package/src/components/search/result/SearchPending.jsx +19 -0
  161. package/src/components/search/result/SearchResultImage.jsx +275 -0
  162. package/src/components/search/result/SearchResultList.jsx +144 -0
  163. package/src/components/search/result/SearchResultPanel.jsx +169 -0
  164. package/src/components/search/result/SearchResultPanelContainer.js +31 -0
  165. package/src/components/search/result/SearchResultStats.jsx +38 -0
  166. package/src/components/search/result/SearchResultTile.jsx +70 -0
  167. package/src/config/anthro.js +153 -0
  168. package/src/config/bonsai.js +50 -0
  169. package/src/config/botgarden.js +3 -0
  170. package/src/config/default.js +604 -0
  171. package/src/config/fcart.js +38 -0
  172. package/src/config/herbarium.js +3 -0
  173. package/src/config/index.js +51 -0
  174. package/src/config/lhmc.js +3 -0
  175. package/src/config/materials.js +1173 -0
  176. package/src/config/publicart.js +3 -0
  177. package/src/constants/actionCodes.js +26 -0
  178. package/src/constants/ids.js +3 -0
  179. package/src/helpers/bodyClassName.js +5 -0
  180. package/src/helpers/esQueryHelpers.js +224 -0
  181. package/src/helpers/formatHelpers.jsx +361 -0
  182. package/src/helpers/searchDimensions.js +21 -0
  183. package/src/helpers/urlHelpers.js +49 -0
  184. package/src/index.jsx +59 -0
  185. package/src/intl/index.js +16 -0
  186. package/src/reducers/detailReducer.js +201 -0
  187. package/src/reducers/filterReducer.js +16 -0
  188. package/src/reducers/index.js +56 -0
  189. package/src/reducers/mediaReducer.js +44 -0
  190. package/src/reducers/prefsReducer.js +24 -0
  191. package/src/reducers/searchEntryFormReducer.js +19 -0
  192. package/src/reducers/searchReducer.js +118 -0
  193. package/styles/colors.css +7 -0
  194. package/styles/cspace/DetailNavBar.css +17 -0
  195. package/styles/cspace/DetailPage.css +3 -0
  196. package/styles/cspace/DetailPanel.css +69 -0
  197. package/styles/cspace/ExhibitionSection.css +9 -0
  198. package/styles/cspace/FieldList.css +15 -0
  199. package/styles/cspace/FieldListField.css +7 -0
  200. package/styles/cspace/FieldListGroup.css +27 -0
  201. package/styles/cspace/FieldValueList.css +19 -0
  202. package/styles/cspace/Filter.css +64 -0
  203. package/styles/cspace/FilterGroup.css +21 -0
  204. package/styles/cspace/FilterPanel.css +45 -0
  205. package/styles/cspace/FilterSearchInput.css +13 -0
  206. package/styles/cspace/Fixed.css +8 -0
  207. package/styles/cspace/IconButton.css +11 -0
  208. package/styles/cspace/ImageGallery.css +43 -0
  209. package/styles/cspace/InstitutionHoldingList.css +109 -0
  210. package/styles/cspace/InstitutionIndex.css +13 -0
  211. package/styles/cspace/InstitutionSection.css +4 -0
  212. package/styles/cspace/Link.css +39 -0
  213. package/styles/cspace/Panel.css +53 -0
  214. package/styles/cspace/PanelTitle.css +48 -0
  215. package/styles/cspace/RemoveSearchParamLink.css +15 -0
  216. package/styles/cspace/RootPage.css +60 -0
  217. package/styles/cspace/ScrollTopButton.css +32 -0
  218. package/styles/cspace/SearchEntryForm.css +4 -0
  219. package/styles/cspace/SearchEntryPanel.css +11 -0
  220. package/styles/cspace/SearchPage.css +2 -0
  221. package/styles/cspace/SearchParamLink.css +21 -0
  222. package/styles/cspace/SearchParamList.css +6 -0
  223. package/styles/cspace/SearchQueryInput.css +30 -0
  224. package/styles/cspace/SearchResultImage.css +29 -0
  225. package/styles/cspace/SearchResultList.css +10 -0
  226. package/styles/cspace/SearchResultPanel.css +31 -0
  227. package/styles/cspace/SearchResultStats.css +4 -0
  228. package/styles/cspace/SearchResultTile.css +67 -0
  229. package/styles/cspace/SearchStatus.css +12 -0
  230. package/styles/cspace/SearchSubmitButton.css +3 -0
  231. package/styles/cspace/SortSelect.css +19 -0
  232. package/styles/cspace/ToggleFilterPanelButton.css +44 -0
  233. package/styles/dimensions.css +9 -0
@@ -0,0 +1,153 @@
1
+ import React, { Component } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { defineMessages, injectIntl } from 'react-intl';
4
+ import Immutable from 'immutable';
5
+ import Gallery from 'react-image-gallery';
6
+ import 'react-image-gallery/styles/css/image-gallery.css';
7
+ import config from '../../config';
8
+ import { blobUrl } from '../../helpers/urlHelpers';
9
+ import styles from '../../../styles/cspace/ImageGallery.css';
10
+
11
+ const propTypes = {
12
+ findMedia: PropTypes.func,
13
+ institutionId: PropTypes.string,
14
+ intl: PropTypes.shape({
15
+ formatMessage: PropTypes.func.isRequired,
16
+ }).isRequired,
17
+ media: PropTypes.instanceOf(Immutable.Map),
18
+ referenceValue: PropTypes.string.isRequired,
19
+ };
20
+
21
+ const defaultProps = {
22
+ institutionId: undefined,
23
+ media: undefined,
24
+ findMedia: () => undefined,
25
+ };
26
+
27
+ const messages = defineMessages({
28
+ defaultAltText: {
29
+ id: 'imageGallery.defaultAltText',
30
+ defaultMessage: 'Image {num}',
31
+ },
32
+ titledAltText: {
33
+ id: 'imageGallery.titledAltText',
34
+ defaultMessage: '{title} Image {num}',
35
+ },
36
+ thumbnailAltText: {
37
+ id: 'imageGallery.thumbnailAltText',
38
+ defaultMessage: 'Thumbnail: {altText}',
39
+ },
40
+ });
41
+
42
+ const getInstitutionIds = () => {
43
+ const institutionsConfig = config.get('institutions');
44
+
45
+ return (institutionsConfig ? Object.keys(institutionsConfig) : []);
46
+ };
47
+
48
+ class ImageGallery extends Component {
49
+ componentDidMount() {
50
+ this.findMedia();
51
+ }
52
+
53
+ componentDidUpdate(prevProps) {
54
+ const {
55
+ referenceValue,
56
+ } = this.props;
57
+
58
+ const {
59
+ referenceValue: prevReferenceValue,
60
+ } = prevProps;
61
+
62
+ if (referenceValue !== prevReferenceValue) {
63
+ this.findMedia();
64
+ }
65
+ }
66
+
67
+ findMedia() {
68
+ const {
69
+ findMedia,
70
+ institutionId,
71
+ media,
72
+ referenceValue,
73
+ } = this.props;
74
+
75
+ const institutionIds = (typeof institutionId === 'undefined')
76
+ ? [null, ...getInstitutionIds()]
77
+ : [institutionId];
78
+
79
+ institutionIds.forEach((instId) => {
80
+ if (!media || !media.get(instId)) {
81
+ findMedia(referenceValue, instId);
82
+ }
83
+ });
84
+ }
85
+
86
+ render() {
87
+ const {
88
+ institutionId,
89
+ intl,
90
+ media,
91
+ } = this.props;
92
+
93
+ if (!media) {
94
+ return null;
95
+ }
96
+
97
+ const institutionIds = (typeof institutionId === 'undefined')
98
+ ? [null, ...getInstitutionIds()]
99
+ : [institutionId];
100
+
101
+ const items = [];
102
+
103
+ institutionIds.forEach((instId) => {
104
+ const mediaMap = media.get(instId) || Immutable.Map();
105
+ const title = mediaMap.get('title');
106
+ const mediaCsids = mediaMap.get('csids') || Immutable.List();
107
+ const mediaAltTexts = mediaMap.get('altTexts') || Immutable.List();
108
+
109
+ if (mediaCsids && mediaCsids.size > 0) {
110
+ const gatewayUrl = instId
111
+ ? config.get(['institutions', instId, 'gatewayUrl'])
112
+ : config.get('gatewayUrl');
113
+
114
+ mediaCsids.forEach((mediaCsid, index) => {
115
+ let altText = mediaAltTexts.get(index);
116
+ if (!altText) {
117
+ const num = index + 1;
118
+ altText = title ? intl.formatMessage(messages.titledAltText, { title, num })
119
+ : intl.formatMessage(messages.defaultAltText, { num });
120
+ }
121
+
122
+ items.push({
123
+ original: blobUrl(gatewayUrl, mediaCsid, config.get('detailImageDerivative')),
124
+ thumbnail: blobUrl(gatewayUrl, mediaCsid, 'Thumbnail'),
125
+ originalAlt: altText,
126
+ thumbnailAlt: intl.formatMessage(messages.thumbnailAltText, { altText }),
127
+ });
128
+ });
129
+ }
130
+ });
131
+
132
+ if (items.length > 0) {
133
+ return (
134
+ <div className={styles.common}>
135
+ <Gallery
136
+ disableArrowKeys
137
+ items={items}
138
+ showFullscreenButton={false}
139
+ showPlayButton={false}
140
+ showThumbnails={items.length > 1}
141
+ />
142
+ </div>
143
+ );
144
+ }
145
+
146
+ return <div />;
147
+ }
148
+ }
149
+
150
+ ImageGallery.propTypes = propTypes;
151
+ ImageGallery.defaultProps = defaultProps;
152
+
153
+ export default injectIntl(ImageGallery);
@@ -0,0 +1,17 @@
1
+ import { connect } from 'react-redux';
2
+ import ImageGallery from './ImageGallery';
3
+ import { findMedia } from '../../actions/mediaActions';
4
+ import { getMedia } from '../../reducers';
5
+
6
+ const mapStateToProps = (state, ownProps) => ({
7
+ media: getMedia(state, ownProps.referenceValue),
8
+ });
9
+
10
+ const mapDispatchToProps = {
11
+ findMedia,
12
+ };
13
+
14
+ export default connect(
15
+ mapStateToProps,
16
+ mapDispatchToProps,
17
+ )(ImageGallery);
@@ -0,0 +1,188 @@
1
+ /* global window */
2
+
3
+ import React, { Component } from 'react';
4
+ import PropTypes from 'prop-types';
5
+ import { defineMessages, FormattedMessage } from 'react-intl';
6
+ import FieldList from './FieldList';
7
+ import ImageGallery from './ImageGalleryContainer';
8
+ import PanelTitle from '../layout/PanelTitle';
9
+ import config from '../../config';
10
+ import styles from '../../../styles/cspace/InstitutionHoldingList.css';
11
+
12
+ const messages = defineMessages({
13
+ title: {
14
+ id: 'institutionHoldingList.title',
15
+ defaultMessage: 'Samples at {title}',
16
+ },
17
+ });
18
+
19
+ const propTypes = {
20
+ hits: PropTypes.arrayOf(PropTypes.shape({
21
+ _source: PropTypes.shape({
22
+ 'collectionspace_core:uri': PropTypes.string,
23
+ }),
24
+ })),
25
+ institutionConfig: PropTypes.shape({
26
+ title: PropTypes.string,
27
+ }).isRequired,
28
+ institutionId: PropTypes.string.isRequired,
29
+ isExpanded: PropTypes.bool,
30
+ isSelected: PropTypes.bool,
31
+ expandPanel: PropTypes.func,
32
+ referenceValue: PropTypes.string.isRequired,
33
+ togglePanel: PropTypes.func,
34
+ };
35
+
36
+ const defaultProps = {
37
+ hits: [],
38
+ isExpanded: false,
39
+ isSelected: false,
40
+ expandPanel: () => undefined,
41
+ togglePanel: () => undefined,
42
+ };
43
+
44
+ const renderResult = (data) => {
45
+ const {
46
+ 'collectionspace_core:uri': uri,
47
+ } = data;
48
+
49
+ return (
50
+ <li key={uri}>
51
+ <FieldList config={config.get('institutionHoldings').detailFields} data={data} />
52
+ </li>
53
+ );
54
+ };
55
+
56
+ export default class InstitutionHoldingList extends Component {
57
+ constructor() {
58
+ super();
59
+
60
+ this.ref = React.createRef();
61
+ }
62
+
63
+ componentDidMount() {
64
+ const {
65
+ isSelected,
66
+ } = this.props;
67
+
68
+ if (isSelected) {
69
+ this.focus();
70
+ }
71
+ }
72
+
73
+ componentDidUpdate(prevProps) {
74
+ const {
75
+ isSelected: prevIsSelected,
76
+ } = prevProps;
77
+
78
+ const {
79
+ isSelected,
80
+ } = this.props;
81
+
82
+ if (isSelected && !prevIsSelected) {
83
+ this.focus();
84
+ }
85
+ }
86
+
87
+ focus() {
88
+ const {
89
+ expandPanel,
90
+ } = this.props;
91
+
92
+ const domNode = this.ref.current;
93
+
94
+ if (domNode) {
95
+ window.setTimeout(() => {
96
+ expandPanel();
97
+
98
+ domNode.scrollIntoView();
99
+ }, 0);
100
+ }
101
+ }
102
+
103
+ renderContent() {
104
+ const {
105
+ hits,
106
+ institutionId,
107
+ isExpanded,
108
+ referenceValue,
109
+ } = this.props;
110
+
111
+ if (isExpanded) {
112
+ return (
113
+ <div>
114
+ <ImageGallery institutionId={institutionId} referenceValue={referenceValue} />
115
+
116
+ <ul>
117
+ {/* eslint-disable-next-line no-underscore-dangle */}
118
+ {hits.map((hit) => renderResult(hit._source))}
119
+ </ul>
120
+ </div>
121
+ );
122
+ }
123
+
124
+ return null;
125
+ }
126
+
127
+ renderTitle() {
128
+ const {
129
+ hits,
130
+ institutionConfig,
131
+ isExpanded,
132
+ togglePanel,
133
+ } = this.props;
134
+
135
+ const {
136
+ title,
137
+ } = institutionConfig;
138
+
139
+ const formattedTitle = (
140
+ <FormattedMessage
141
+ // eslint-disable-next-line react/jsx-props-no-spreading
142
+ {...messages.title}
143
+ tagName="h2"
144
+ values={{
145
+ title,
146
+ count: hits.length,
147
+ }}
148
+ />
149
+ );
150
+
151
+ return (
152
+ <PanelTitle
153
+ isExpanded={isExpanded}
154
+ title={formattedTitle}
155
+ onClick={togglePanel}
156
+ />
157
+ );
158
+ }
159
+
160
+ render() {
161
+ const {
162
+ hits,
163
+ } = this.props;
164
+
165
+ if (hits.length === 0) {
166
+ return null;
167
+ }
168
+
169
+ const {
170
+ institutionId,
171
+ isExpanded,
172
+ } = this.props;
173
+
174
+ return (
175
+ <section
176
+ className={isExpanded ? styles.expanded : styles.collapsed}
177
+ id={institutionId}
178
+ ref={this.ref}
179
+ >
180
+ {this.renderTitle()}
181
+ {this.renderContent()}
182
+ </section>
183
+ );
184
+ }
185
+ }
186
+
187
+ InstitutionHoldingList.propTypes = propTypes;
188
+ InstitutionHoldingList.defaultProps = defaultProps;
@@ -0,0 +1,29 @@
1
+ import { connect } from 'react-redux';
2
+ import InstitutionHoldingList from './InstitutionHoldingList';
3
+
4
+ import {
5
+ expandPanel,
6
+ togglePanel,
7
+ } from '../../actions/prefsActions';
8
+
9
+ import {
10
+ getDetailInstitutionHits,
11
+ isPanelExpanded,
12
+ } from '../../reducers';
13
+
14
+ const panelId = (institutionId) => `SampleList-${institutionId}`;
15
+
16
+ const mapStateToProps = (state, ownProps) => ({
17
+ hits: getDetailInstitutionHits(state, ownProps.institutionId),
18
+ isExpanded: isPanelExpanded(state, panelId(ownProps.institutionId)),
19
+ });
20
+
21
+ const mapDispatchToProps = (dispatch, ownProps) => ({
22
+ expandPanel: () => dispatch(expandPanel(panelId(ownProps.institutionId))),
23
+ togglePanel: () => dispatch(togglePanel(panelId(ownProps.institutionId))),
24
+ });
25
+
26
+ export default connect(
27
+ mapStateToProps,
28
+ mapDispatchToProps,
29
+ )(InstitutionHoldingList);
@@ -0,0 +1,57 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import Immutable from 'immutable';
4
+ import get from 'lodash/get';
5
+ import { defineMessages, FormattedMessage } from 'react-intl';
6
+ import config from '../../config';
7
+ import linkStyles from '../../../styles/cspace/Link.css';
8
+ import styles from '../../../styles/cspace/InstitutionIndex.css';
9
+
10
+ const messages = defineMessages({
11
+ label: {
12
+ id: 'institutionIndex.label',
13
+ defaultMessage: 'Holdings at {title}',
14
+ },
15
+ });
16
+
17
+ const propTypes = {
18
+ holdingInstitutions: PropTypes.instanceOf(Immutable.Set).isRequired,
19
+ };
20
+
21
+ export default function InstitutionIndex(props) {
22
+ const {
23
+ holdingInstitutions,
24
+ } = props;
25
+
26
+ const institutions = config.get('institutions');
27
+
28
+ const links = holdingInstitutions.map((institutionId) => {
29
+ const title = get(institutions, [institutionId, 'title']);
30
+
31
+ return (
32
+ <li key={institutionId}>
33
+ <a className={linkStyles.hash} href={`#${institutionId}`}>
34
+ <FormattedMessage
35
+ // eslint-disable-next-line react/jsx-props-no-spreading
36
+ {...messages.label}
37
+ values={{ title }}
38
+ />
39
+ </a>
40
+ </li>
41
+ );
42
+ });
43
+
44
+ if (links.size === 0) {
45
+ return null;
46
+ }
47
+
48
+ return (
49
+ <nav className={styles.common}>
50
+ <ul>
51
+ {links}
52
+ </ul>
53
+ </nav>
54
+ );
55
+ }
56
+
57
+ InstitutionIndex.propTypes = propTypes;
@@ -0,0 +1,11 @@
1
+ import { connect } from 'react-redux';
2
+ import InstitutionIndex from './InstitutionIndex';
3
+ import { getDetailHoldingInstitutions } from '../../reducers';
4
+
5
+ const mapStateToProps = (state) => ({
6
+ holdingInstitutions: getDetailHoldingInstitutions(state),
7
+ });
8
+
9
+ export default connect(
10
+ mapStateToProps,
11
+ )(InstitutionIndex);
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import Immutable from 'immutable';
4
+ import InstitutionHoldingList from './InstitutionHoldingListContainer';
5
+ import styles from '../../../styles/cspace/InstitutionSection.css';
6
+
7
+ const propTypes = {
8
+ config: PropTypes.objectOf(PropTypes.object).isRequired,
9
+ holdingInstitutions: PropTypes.instanceOf(Immutable.Set).isRequired,
10
+ referenceValue: PropTypes.string.isRequired,
11
+ selectedInstitutionId: PropTypes.string,
12
+ };
13
+
14
+ const defaultProps = {
15
+ selectedInstitutionId: undefined,
16
+ };
17
+
18
+ export default function InstitutionSection(props) {
19
+ const {
20
+ config,
21
+ holdingInstitutions,
22
+ referenceValue,
23
+ selectedInstitutionId,
24
+ } = props;
25
+
26
+ if (!holdingInstitutions || holdingInstitutions.size === 0) {
27
+ return null;
28
+ }
29
+
30
+ const institutions = holdingInstitutions.map((institutionId) => (
31
+ <InstitutionHoldingList
32
+ institutionConfig={config[institutionId]}
33
+ institutionId={institutionId}
34
+ isSelected={institutionId === selectedInstitutionId}
35
+ key={institutionId}
36
+ referenceValue={referenceValue}
37
+ />
38
+ ));
39
+
40
+ return (
41
+ <section className={styles.common}>
42
+ {institutions}
43
+ </section>
44
+ );
45
+ }
46
+
47
+ InstitutionSection.propTypes = propTypes;
48
+ InstitutionSection.defaultProps = defaultProps;
@@ -0,0 +1,11 @@
1
+ import { connect } from 'react-redux';
2
+ import InstitutionSection from './InstitutionSection';
3
+ import { getDetailHoldingInstitutions } from '../../reducers';
4
+
5
+ const mapStateToProps = (state) => ({
6
+ holdingInstitutions: getDetailHoldingInstitutions(state),
7
+ });
8
+
9
+ export default connect(
10
+ mapStateToProps,
11
+ )(InstitutionSection);
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styles from '../../../styles/cspace/Fixed.css';
4
+
5
+ const propTypes = {
6
+ children: PropTypes.oneOfType([
7
+ PropTypes.element,
8
+ PropTypes.arrayOf(PropTypes.element),
9
+ ]),
10
+ };
11
+
12
+ const defaultProps = {
13
+ children: undefined,
14
+ };
15
+
16
+ export default function Fixed(props) {
17
+ const {
18
+ children,
19
+ } = props;
20
+
21
+ return (
22
+ <div className={styles.common}>
23
+ {children}
24
+ </div>
25
+ );
26
+ }
27
+
28
+ Fixed.propTypes = propTypes;
29
+ Fixed.defaultProps = defaultProps;
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import classNames from 'classnames';
4
+ import { FormattedMessage } from 'react-intl';
5
+ import styles from '../../../styles/cspace/IconButton.css';
6
+
7
+ const propTypes = {
8
+ className: PropTypes.string,
9
+ labelMessage: PropTypes.shape({
10
+ id: PropTypes.string.isRequired,
11
+ defaultMessage: PropTypes.string.isRequired,
12
+ }).isRequired,
13
+ onClick: PropTypes.func,
14
+ };
15
+
16
+ const defaultProps = {
17
+ className: undefined,
18
+ onClick: undefined,
19
+ };
20
+
21
+ export default function IconButton(props) {
22
+ const {
23
+ className,
24
+ labelMessage,
25
+ onClick,
26
+ } = props;
27
+
28
+ return (
29
+ <button
30
+ className={classNames(className, styles.common)}
31
+ type="button"
32
+ onClick={onClick}
33
+ >
34
+ {/* eslint-disable-next-line react/jsx-props-no-spreading */}
35
+ <FormattedMessage {...labelMessage} tagName="span" />
36
+ </button>
37
+ );
38
+ }
39
+
40
+ IconButton.propTypes = propTypes;
41
+ IconButton.defaultProps = defaultProps;
@@ -0,0 +1,60 @@
1
+ import React, { Component } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styles from '../../../styles/cspace/Panel.css';
4
+
5
+ const propTypes = {
6
+ id: PropTypes.string.isRequired,
7
+ children: PropTypes.oneOfType([
8
+ PropTypes.element,
9
+ PropTypes.arrayOf(PropTypes.element),
10
+ ]),
11
+ title: PropTypes.element,
12
+ isExpanded: PropTypes.bool,
13
+ onHeaderClick: PropTypes.func,
14
+ };
15
+
16
+ const defaultProps = {
17
+ children: undefined,
18
+ isExpanded: false,
19
+ onHeaderClick: () => undefined,
20
+ title: undefined,
21
+ };
22
+
23
+ export default class Panel extends Component {
24
+ constructor() {
25
+ super();
26
+
27
+ this.handleHeaderButtonClick = this.handleHeaderButtonClick.bind(this);
28
+ }
29
+
30
+ handleHeaderButtonClick() {
31
+ const {
32
+ id,
33
+ onHeaderClick,
34
+ } = this.props;
35
+
36
+ onHeaderClick(id);
37
+ }
38
+
39
+ render() {
40
+ const {
41
+ children,
42
+ isExpanded,
43
+ title,
44
+ } = this.props;
45
+
46
+ const className = isExpanded ? styles.expanded : styles.collapsed;
47
+
48
+ return (
49
+ <div className={className}>
50
+ <header>
51
+ <button onClick={this.handleHeaderButtonClick} aria-expanded={isExpanded} type="button">{title}</button>
52
+ </header>
53
+ {isExpanded ? children : undefined}
54
+ </div>
55
+ );
56
+ }
57
+ }
58
+
59
+ Panel.propTypes = propTypes;
60
+ Panel.defaultProps = defaultProps;
@@ -0,0 +1,20 @@
1
+ import { connect } from 'react-redux';
2
+ import Panel from './Panel';
3
+ import { isPanelExpanded } from '../../reducers';
4
+
5
+ import {
6
+ togglePanel,
7
+ } from '../../actions/prefsActions';
8
+
9
+ const mapStateToProps = (state, ownProps) => ({
10
+ isExpanded: isPanelExpanded(state, ownProps.id),
11
+ });
12
+
13
+ const mapDispatchToProps = {
14
+ onHeaderClick: togglePanel,
15
+ };
16
+
17
+ export default connect(
18
+ mapStateToProps,
19
+ mapDispatchToProps,
20
+ )(Panel);