@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,3 @@
1
+ export default {
2
+ gatewayUrl: 'http://localhost:8180/gateway/publicart',
3
+ };
@@ -0,0 +1,26 @@
1
+ export const PREFS_LOADED = 'PREFS_LOADED';
2
+ export const EXPAND_PANEL = 'EXPAND_PANEL';
3
+ export const TOGGLE_PANEL = 'TOGGLE_PANEL';
4
+
5
+ export const SET_MEDIA = 'SET_MEDIA';
6
+
7
+ export const OPEN_SEARCH = 'OPEN_SEARCH';
8
+ export const SET_SEARCH_PAGE_SIZE = 'SET_SEARCH_PAGE_SIZE';
9
+ export const SET_SEARCH_PARAMS = 'SET_SEARCH_PARAMS';
10
+ export const SEARCH_STARTED = 'SEARCH_STARTED';
11
+ export const SEARCH_FULFILLED = 'SEARCH_FULFILLED';
12
+ export const SEARCH_REJECTED = 'SEARCH_REJECTED';
13
+
14
+ export const SET_SEARCH_ENTRY_FORM_PARAM = 'SET_SEARCH_ENTRY_FORM_PARAM';
15
+
16
+ export const SET_FILTER_SEARCH_VALUE = 'SET_FILTER_SEARCH_VALUE';
17
+
18
+ export const CLEAR_DETAIL = 'CLEAR_DETAIL';
19
+ export const SET_DETAIL_PARAMS = 'SET_DETAIL_PARAMS';
20
+ export const DETAIL_READ_STARTED = 'DETAIL_READ_STARTED';
21
+ export const DETAIL_READ_FULFILLED = 'DETAIL_READ_FULFILLED';
22
+ export const DETAIL_READ_REJECTED = 'DETAIL_READ_REJECTED';
23
+
24
+ export const INST_SEARCH_STARTED = 'INST_SEARCH_STARTED';
25
+ export const INST_SEARCH_FULFILLED = 'INST_SEARCH_FULFILLED';
26
+ export const INST_SEARCH_REJECTED = 'INST_SEARCH_REJECTED';
@@ -0,0 +1,3 @@
1
+ export const FILTER_PANEL_ID = 'filter';
2
+ export const SEARCH_QUERY_ID = 'search';
3
+ export const SORT_ID = 'sort';
@@ -0,0 +1,5 @@
1
+ export default (className) => {
2
+ const name = className.split('--', 1)[0];
3
+
4
+ return `has-${name}`;
5
+ };
@@ -0,0 +1,224 @@
1
+ import config from '../config';
2
+ import { SEARCH_QUERY_ID, SORT_ID } from '../constants/ids';
3
+
4
+ export const fulltextParamToQuery = (value) => {
5
+ if (!value) {
6
+ return undefined;
7
+ }
8
+
9
+ const fulltextSearchFields = config.get('fulltextSearchFields');
10
+
11
+ return {
12
+ bool: {
13
+ should: [
14
+ {
15
+ multi_match: {
16
+ query: value,
17
+ fields: fulltextSearchFields,
18
+ type: 'cross_fields',
19
+ operator: 'and',
20
+ },
21
+ },
22
+ {
23
+ multi_match: {
24
+ query: value,
25
+ fields: fulltextSearchFields,
26
+ type: 'phrase_prefix',
27
+ operator: 'and',
28
+ },
29
+ },
30
+ ],
31
+ minimum_should_match: '1',
32
+ },
33
+ };
34
+ };
35
+
36
+ export const termFilterParamToQuery = (field, value) => ({
37
+ terms: {
38
+ [field]: value.toJS(),
39
+ },
40
+ });
41
+
42
+ export const histogramFilterParamToQuery = (field, value, interval) => {
43
+ const clauses = value.map((v) => ({
44
+ range: {
45
+ [field]: {
46
+ gte: v,
47
+ lt: v + interval,
48
+ },
49
+ },
50
+ })).toJS();
51
+
52
+ if (clauses.length === 1) {
53
+ return clauses[0];
54
+ }
55
+
56
+ return {
57
+ bool: {
58
+ should: clauses,
59
+ },
60
+ };
61
+ };
62
+
63
+ export const filterParamToQuery = (id, value) => {
64
+ const filterFieldConfig = config.getFilterFieldConfig(id);
65
+
66
+ if (!filterFieldConfig) {
67
+ return undefined;
68
+ }
69
+
70
+ const {
71
+ field,
72
+ type,
73
+ } = filterFieldConfig;
74
+
75
+ if (type === 'histogram') {
76
+ return histogramFilterParamToQuery(field, value, filterFieldConfig.interval);
77
+ }
78
+
79
+ return termFilterParamToQuery(field, value);
80
+ };
81
+
82
+ export const getSearchQuery = (params) => fulltextParamToQuery(params.get(SEARCH_QUERY_ID));
83
+
84
+ export const getFilterQueries = (params) => params
85
+ .delete(SEARCH_QUERY_ID)
86
+ .entrySeq()
87
+ .map(([id, value]) => filterParamToQuery(id, value))
88
+ .toJS();
89
+
90
+ export const getQuery = (params) => {
91
+ const clauses = [
92
+ config.get('defaultQuery'),
93
+ getSearchQuery(params),
94
+ ...getFilterQueries(params),
95
+ ]
96
+ .filter((clause) => !!clause);
97
+
98
+ if (clauses.length > 1) {
99
+ return {
100
+ bool: {
101
+ must: clauses,
102
+ },
103
+ };
104
+ }
105
+
106
+ if (clauses.length > 0) {
107
+ return clauses[0];
108
+ }
109
+
110
+ return {
111
+ match_all: {},
112
+ };
113
+ };
114
+
115
+ export const getHistogramAgg = (field, interval = 1) => ({
116
+ histogram: {
117
+ field,
118
+ interval,
119
+ min_doc_count: 1,
120
+ },
121
+ });
122
+
123
+ export const getTermsAgg = (field, order = { _term: 'asc' }) => ({
124
+ terms: {
125
+ field,
126
+ order,
127
+ size: 300,
128
+ },
129
+ });
130
+
131
+ export const getFilterAgg = (filterFieldConfig) => {
132
+ const {
133
+ field,
134
+ order,
135
+ type,
136
+ } = filterFieldConfig;
137
+
138
+ if (type === 'histogram') {
139
+ return getHistogramAgg(field, filterFieldConfig.interval);
140
+ }
141
+
142
+ return getTermsAgg(field, order);
143
+ };
144
+
145
+ let aggs;
146
+
147
+ export const getAggs = (params) => {
148
+ if (!aggs) {
149
+ aggs = {};
150
+
151
+ const filterFieldsConfig = config.get('filters').fields;
152
+
153
+ Object.entries(filterFieldsConfig).forEach(([id, filterFieldConfig]) => {
154
+ const param = params.get(id);
155
+ const hasValue = param && param.size > 0;
156
+
157
+ if (!hasValue) {
158
+ aggs[id] = getFilterAgg(filterFieldConfig);
159
+ }
160
+ });
161
+ }
162
+
163
+ return aggs;
164
+ };
165
+
166
+ export const getSort = (params) => {
167
+ const sortOrder = params.get(SORT_ID) || config.get('defaultSortOrder');
168
+
169
+ const effectiveSortOrder = (sortOrder === 'bestmatch' && params.delete(SORT_ID).isEmpty())
170
+ ? 'newest'
171
+ : sortOrder;
172
+
173
+ switch (effectiveSortOrder) {
174
+ case 'bestmatch':
175
+ return [
176
+ {
177
+ _score: {
178
+ order: 'desc',
179
+ },
180
+ },
181
+ {
182
+ [config.get('sortField')]: {
183
+ order: 'asc',
184
+ },
185
+ },
186
+ ];
187
+ case 'atoz':
188
+ return {
189
+ [config.get('sortField')]: {
190
+ order: 'asc',
191
+ },
192
+ };
193
+ case 'ztoa':
194
+ return {
195
+ [config.get('sortField')]: {
196
+ order: 'desc',
197
+ },
198
+ };
199
+ case 'newest':
200
+ return {
201
+ 'collectionspace_core:createdAt': {
202
+ order: 'desc',
203
+ },
204
+ };
205
+ case 'oldest':
206
+ return {
207
+ 'collectionspace_core:createdAt': {
208
+ order: 'asc',
209
+ },
210
+ };
211
+ default:
212
+ return {};
213
+ }
214
+ };
215
+
216
+ export const getSearchResultPayload = (params, pageSize, offset) => ({
217
+ query: getQuery(params.delete(SORT_ID)),
218
+ from: offset,
219
+ size: pageSize,
220
+ _source: {
221
+ includes: config.get('includeFields'),
222
+ },
223
+ sort: getSort(params),
224
+ });
@@ -0,0 +1,361 @@
1
+ import React from 'react';
2
+ import get from 'lodash/get';
3
+ import qs from 'qs';
4
+ import { Link } from 'react-router-dom';
5
+ import { getDisplayName } from 'cspace-refname';
6
+ import FieldValueList from '../components/detail/FieldValueList';
7
+ import linkStyles from '../../styles/cspace/Link.css';
8
+
9
+ export const renderLink = (url, text, type) => {
10
+ if (!url) {
11
+ return text;
12
+ }
13
+
14
+ if (type === 'external') {
15
+ const fullUrl = !url.startsWith('http') ? `http://${url}` : url;
16
+ const content = text || url;
17
+
18
+ // Make sure urls with no spaces are able to wrap anywhere.
19
+
20
+ const inlineStyle = content.startsWith('http')
21
+ ? { overflowWrap: 'anywhere' }
22
+ : undefined;
23
+
24
+ return (
25
+ <a
26
+ className={linkStyles[type]}
27
+ href={fullUrl}
28
+ rel="noopener noreferrer"
29
+ style={inlineStyle}
30
+ target="_blank"
31
+ >
32
+ {content}
33
+ </a>
34
+ );
35
+ }
36
+
37
+ return <Link className={type && linkStyles[type]} to={url}>{text || url}</Link>;
38
+ };
39
+
40
+ export const renderFilterLink = (filterId, filterValue, linkText) => {
41
+ if (!filterValue) {
42
+ return null;
43
+ }
44
+
45
+ const values = [filterValue];
46
+ const query = qs.stringify({ [filterId]: JSON.stringify(values) });
47
+
48
+ return renderLink(
49
+ `/search?${query}`,
50
+ typeof linkText === 'undefined' ? filterValue : linkText,
51
+ );
52
+ };
53
+
54
+ export const renderJoined = (parts, separator = '') => {
55
+ const nonEmptyParts = parts.filter(
56
+ (part) => (typeof part !== 'undefined' && part !== null && part !== ''),
57
+ );
58
+
59
+ if (nonEmptyParts.length === 0) {
60
+ return null;
61
+ }
62
+
63
+ const separatorElement = (separator === '\n') ? <br /> : separator;
64
+
65
+ return nonEmptyParts.reduce((joinedParts, nextPart) => (
66
+ // eslint-disable-next-line react/jsx-one-expression-per-line
67
+ <span>{joinedParts}{separatorElement}{nextPart}</span>
68
+ ));
69
+ };
70
+
71
+ const renderList = (values, inline = false) => {
72
+ if (Array.isArray(values)) {
73
+ if (values.length > 1) {
74
+ return (
75
+ <FieldValueList inline={inline}>
76
+ {/* eslint-disable-next-line react/no-array-index-key */}
77
+ {values.map((value, index) => <li key={index}>{value}</li>)}
78
+ </FieldValueList>
79
+ );
80
+ }
81
+
82
+ return values[0];
83
+ }
84
+
85
+ return values;
86
+ };
87
+
88
+ export const unformatted = (data) => data;
89
+
90
+ export const boolean = (value) => {
91
+ switch (value) {
92
+ case 0:
93
+ return 'No';
94
+ case 1:
95
+ return 'Yes';
96
+ default:
97
+ return value;
98
+ }
99
+ };
100
+
101
+ export const literal = (value) => () => value;
102
+
103
+ export const collectionValue = (value) => value.replace('_', '-');
104
+
105
+ export const objectTypeValue = (value) => value.replace('_', '-');
106
+
107
+ export const lines = (values) => values && values.join('\n');
108
+
109
+ export const list = (values) => renderList(values);
110
+
111
+ export const inlineList = (values) => renderJoined(values, ', ');
112
+
113
+ export const listOf = (format) => (array, fieldName) => (
114
+ renderList(array.map((value) => format(value, fieldName)))
115
+ );
116
+
117
+ export const inlineListOf = (format) => (array, fieldName) => (
118
+ renderJoined(array.map((value) => format(value, fieldName)), ', ')
119
+ );
120
+
121
+ export const displayName = (value) => (getDisplayName(value) || value);
122
+
123
+ export const displayNameFrom = (name) => (data) => displayName(data[name]);
124
+
125
+ export const unqualifiedFieldName = (data, fieldName) => {
126
+ const parts = fieldName.split(':');
127
+
128
+ return (parts.length > 1 ? parts[1] : parts[0]);
129
+ };
130
+
131
+ export const filterLink = (config) => (data, fieldName) => {
132
+ const {
133
+ filterIdFormat = unqualifiedFieldName,
134
+ filterValueFormat = unformatted,
135
+ linkTextFormat,
136
+ } = config;
137
+
138
+ const formattedFilterId = filterIdFormat(data, fieldName);
139
+ const formattedFilterValue = filterValueFormat(data, fieldName);
140
+
141
+ const formattedLinkText = linkTextFormat
142
+ ? linkTextFormat(data, fieldName)
143
+ : formattedFilterValue;
144
+
145
+ return renderFilterLink(
146
+ formattedFilterId,
147
+ formattedFilterValue,
148
+ formattedLinkText,
149
+ );
150
+ };
151
+
152
+ export const linkNote = (urlField, noteField, separator = ' - ') => (object) => {
153
+ const {
154
+ [noteField]: note,
155
+ [urlField]: url,
156
+ } = object;
157
+
158
+ const link = renderLink(url);
159
+
160
+ return (
161
+ <>
162
+ {/* eslint-disable-next-line react/jsx-one-expression-per-line */}
163
+ {link}{link && note && separator}{note}
164
+ </>
165
+ );
166
+ };
167
+
168
+ export const linkedDisplayName = (filterId) => (data) => (
169
+ renderFilterLink(filterId, displayName(data))
170
+ );
171
+
172
+ export const linkText = (config) => (data) => {
173
+ const {
174
+ urlFieldName,
175
+ textFieldName,
176
+ type,
177
+ } = config;
178
+
179
+ return renderLink(data[urlFieldName], data[textFieldName], type);
180
+ };
181
+
182
+ export const nameValue = (config) => (data, fieldName) => {
183
+ const {
184
+ nameFormat,
185
+ valueFormat,
186
+ separator = ': ',
187
+ } = config;
188
+
189
+ const formattedName = nameFormat(data, fieldName);
190
+ const formattedValue = valueFormat(data, fieldName);
191
+
192
+ const parts = [
193
+ formattedName,
194
+ formattedValue,
195
+ ];
196
+
197
+ return renderJoined(parts, separator);
198
+ };
199
+
200
+ export const property = (config) => (data) => {
201
+ const {
202
+ nameFieldName,
203
+ valueFieldName,
204
+ separator = ': ',
205
+ } = config;
206
+
207
+ const parts = [
208
+ renderFilterLink(nameFieldName, displayName(data[nameFieldName])),
209
+ data[valueFieldName],
210
+ ];
211
+
212
+ return renderJoined(parts, separator);
213
+ };
214
+
215
+ export const nameRole = (config) => (data) => {
216
+ const {
217
+ nameFieldName,
218
+ roleFieldName,
219
+ linkName = true,
220
+ separator = ' ',
221
+ } = config;
222
+
223
+ const name = displayName(data[nameFieldName]);
224
+
225
+ let role = displayName(data[roleFieldName]);
226
+
227
+ if (role) {
228
+ role = `(${role})`;
229
+ }
230
+
231
+ const parts = [
232
+ linkName ? renderFilterLink(nameFieldName, name) : name,
233
+ role,
234
+ ];
235
+
236
+ return renderJoined(parts, separator);
237
+ };
238
+
239
+ export const valueWithNote = (config) => (data) => {
240
+ const {
241
+ valueFieldName,
242
+ noteFieldName,
243
+ linkValue = true,
244
+ separator = '\n',
245
+ noteLabel = 'Note: ',
246
+ } = config;
247
+
248
+ const value = displayName(data[valueFieldName]);
249
+
250
+ let note = data[noteFieldName];
251
+
252
+ if (note) {
253
+ note = `${noteLabel}${note}`;
254
+ }
255
+
256
+ const parts = [
257
+ linkValue ? renderFilterLink(valueFieldName, value) : value,
258
+ note,
259
+ ];
260
+
261
+ return renderJoined(parts, separator);
262
+ };
263
+
264
+ export const numericRange = (config) => (data) => {
265
+ const {
266
+ linkQualifier,
267
+ lowFieldName,
268
+ highFieldName,
269
+ unitFieldName,
270
+ qualifierFieldName,
271
+ qualifierSeparator = ' ',
272
+ } = config;
273
+
274
+ let {
275
+ unit,
276
+ } = config;
277
+
278
+ const low = data[lowFieldName];
279
+ const high = data[highFieldName];
280
+ const range = [low, high].filter((part) => !!part).join('-');
281
+
282
+ if (range) {
283
+ unit = unit || displayName(data[unitFieldName]);
284
+ } else {
285
+ unit = null;
286
+ }
287
+
288
+ const qualifier = displayName(data[qualifierFieldName]);
289
+ const rangeUnit = renderJoined([range, unit], ' ');
290
+
291
+ return renderJoined([
292
+ rangeUnit,
293
+ linkQualifier ? renderFilterLink(qualifierFieldName, qualifier) : qualifier,
294
+ ], qualifierSeparator);
295
+ };
296
+
297
+ export const paragraphs = (array) => (
298
+ // eslint-disable-next-line react/no-array-index-key
299
+ array && array.length > 0 && array.map((value, index) => <p key={index}>{value}</p>)
300
+ );
301
+
302
+ export const valueAt = (config) => (data) => {
303
+ const {
304
+ path,
305
+ format = unformatted,
306
+ } = config;
307
+
308
+ const value = get(data, path);
309
+ const fieldName = Array.isArray(path) ? path[path.length - 1] : path;
310
+
311
+ return format(value, fieldName);
312
+ };
313
+
314
+ export const pickFromList = (config) => (array) => {
315
+ const {
316
+ condition,
317
+ format,
318
+ } = config;
319
+
320
+ const {
321
+ path,
322
+ value: targetValue,
323
+ } = condition;
324
+
325
+ const value = array.find((candidateItem) => {
326
+ const candidateValue = path ? get(candidateItem, path) : candidateItem;
327
+
328
+ return (candidateValue === targetValue);
329
+ });
330
+
331
+ return (format ? format(value) : value);
332
+ };
333
+
334
+ export const pickAllFromList = (config) => (array) => {
335
+ const {
336
+ condition,
337
+ format,
338
+ } = config;
339
+
340
+ const {
341
+ path,
342
+ value: targetValue,
343
+ } = condition;
344
+
345
+ const values = array.filter((candidateItem) => {
346
+ const candidateValue = path ? get(candidateItem, path) : candidateItem;
347
+
348
+ return (candidateValue === targetValue);
349
+ });
350
+
351
+ return renderJoined((format ? values.map(format) : values), '\n');
352
+ };
353
+
354
+ export const decade = (startYear) => {
355
+ const endYear = startYear + 9;
356
+
357
+ return `${startYear}–${endYear}`;
358
+ };
359
+
360
+ // eslint-disable-next-line react/no-danger
361
+ export const html = (value) => <div dangerouslySetInnerHTML={{ __html: value }} />;
@@ -0,0 +1,21 @@
1
+ /* global window */
2
+
3
+ import cssDimensions from '../../styles/dimensions.css';
4
+
5
+ const {
6
+ searchResultTileWidth: cssTileWidth,
7
+ searchResultTileBodyHeight: cssTileBodyHeight,
8
+ } = cssDimensions;
9
+
10
+ export const tileWidth = parseInt(cssTileWidth, 10);
11
+ export const tileBodyHeight = parseInt(cssTileBodyHeight, 10);
12
+ export const tileHeight = tileWidth + tileBodyHeight;
13
+
14
+ export function calculateSearchPageSize() {
15
+ const width = window.innerWidth;
16
+ const height = window.innerHeight;
17
+ const ratio = window.devicePixelRatio || 1;
18
+ const pageSize = ((width / tileWidth) * (height / tileHeight + 2)) / ratio;
19
+
20
+ return Math.max(Math.ceil(pageSize), 12);
21
+ }
@@ -0,0 +1,49 @@
1
+ import qs from 'qs';
2
+ import Immutable from 'immutable';
3
+
4
+ export const blobUrl = (gatewayUrl, mediaCsid, derivative) => (
5
+ `${gatewayUrl}/cspace-services/media/${mediaCsid}/blob/derivatives/${derivative}/content`
6
+ );
7
+
8
+ export const searchParamsToQueryString = (params) => qs.stringify(
9
+ params.map((value) => (value && JSON.stringify(value)))
10
+ .filter((value) => !!value)
11
+ .toJS(),
12
+ { format: 'RFC1738' },
13
+ );
14
+
15
+ export const locationToSearchParams = (location) => (
16
+ Immutable.Map(qs.parse(location.search, { ignoreQueryPrefix: true }))
17
+ .filter((value) => !!value)
18
+ .map((value) => Immutable.fromJS(JSON.parse(value)))
19
+ );
20
+
21
+ export const locationToDetailParams = (location, match) => {
22
+ let params = Immutable.fromJS(match.params);
23
+
24
+ const {
25
+ hash,
26
+ state,
27
+ } = location;
28
+
29
+ if (hash) {
30
+ params = params.set('#', hash.substring(1));
31
+ }
32
+
33
+ if (state) {
34
+ const {
35
+ index,
36
+ searchParams,
37
+ } = state;
38
+
39
+ if (typeof index !== 'undefined') {
40
+ params = params.set('index', index);
41
+ }
42
+
43
+ if (typeof searchParams !== 'undefined') {
44
+ params = params.set('searchParams', Immutable.fromJS(searchParams));
45
+ }
46
+ }
47
+
48
+ return params;
49
+ };