@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,231 @@
1
+ /* global fetch */
2
+
3
+ import config from '../config';
4
+ import { locationToDetailParams } from '../helpers/urlHelpers';
5
+
6
+ import {
7
+ getDetailData,
8
+ getDetailParams,
9
+ isDetailPending,
10
+ isDetailInstSearchPending,
11
+ } from '../reducers';
12
+
13
+ import {
14
+ CLEAR_DETAIL,
15
+ DETAIL_READ_FULFILLED,
16
+ DETAIL_READ_REJECTED,
17
+ DETAIL_READ_STARTED,
18
+ INST_SEARCH_FULFILLED,
19
+ INST_SEARCH_REJECTED,
20
+ INST_SEARCH_STARTED,
21
+ SET_DETAIL_PARAMS,
22
+ } from '../constants/actionCodes';
23
+
24
+ import {
25
+ getSearchResultPayload,
26
+ } from '../helpers/esQueryHelpers';
27
+
28
+ export const clearDetail = () => ({
29
+ type: CLEAR_DETAIL,
30
+ });
31
+
32
+ export const setDetailParams = (location, match) => {
33
+ const params = locationToDetailParams(location, match);
34
+
35
+ return {
36
+ type: SET_DETAIL_PARAMS,
37
+ payload: params,
38
+ };
39
+ };
40
+
41
+ export const findInstitutionHoldings = (institutionId) => (dispatch, getState) => {
42
+ const params = getDetailParams(getState());
43
+ const data = getDetailData(getState());
44
+
45
+ if (
46
+ !params
47
+ || !data
48
+ || isDetailPending(getState())
49
+ || isDetailInstSearchPending(getState(), institutionId)
50
+ ) {
51
+ return Promise.resolve();
52
+ }
53
+
54
+ const holdingsConfig = config.get('institutionHoldings') || {};
55
+
56
+ const {
57
+ sortField,
58
+ sortOrder,
59
+ query: queryBuilder,
60
+ } = holdingsConfig;
61
+
62
+ const institutionsConfig = config.get('institutions') || {};
63
+ const institutionConfig = institutionsConfig[institutionId];
64
+
65
+ if (!queryBuilder || !institutionConfig) {
66
+ return Promise.resolve();
67
+ }
68
+
69
+ const {
70
+ gatewayUrl,
71
+ } = institutionConfig;
72
+
73
+ const url = `${gatewayUrl}/es/doc/_search`;
74
+ const query = queryBuilder(data);
75
+
76
+ const payload = {
77
+ query,
78
+ from: 0,
79
+ size: 500,
80
+ };
81
+
82
+ if (sortField) {
83
+ payload.sort = {
84
+ [sortField]: {
85
+ order: sortOrder || 'asc',
86
+ },
87
+ };
88
+ }
89
+
90
+ dispatch({
91
+ type: INST_SEARCH_STARTED,
92
+ meta: {
93
+ institutionId,
94
+ params,
95
+ },
96
+ });
97
+
98
+ return fetch(url, {
99
+ method: 'POST',
100
+ headers: { 'Content-Type': 'application/json' },
101
+ body: JSON.stringify(payload),
102
+ })
103
+ .then((response) => {
104
+ if (!response.ok) {
105
+ const error = new Error();
106
+
107
+ error.response = response;
108
+
109
+ return Promise.reject(error);
110
+ }
111
+
112
+ return response.json();
113
+ })
114
+ .then((responseData) => {
115
+ dispatch({
116
+ type: INST_SEARCH_FULFILLED,
117
+ payload: responseData,
118
+ meta: {
119
+ institutionId,
120
+ params,
121
+ },
122
+ });
123
+ })
124
+ .catch((error) => {
125
+ dispatch({
126
+ type: INST_SEARCH_REJECTED,
127
+ payload: error,
128
+ meta: {
129
+ institutionId,
130
+ params,
131
+ },
132
+ });
133
+ });
134
+ };
135
+
136
+ export const findAllInstitutionHoldings = () => (dispatch) => {
137
+ const institutionsConfig = config.get('institutions');
138
+
139
+ if (!institutionsConfig) {
140
+ return Promise.resolve();
141
+ }
142
+
143
+ return Promise.all(Object.keys(institutionsConfig).map((institutionId) => (
144
+ dispatch(findInstitutionHoldings(institutionId))
145
+ )));
146
+ };
147
+
148
+ export const readDetail = () => (dispatch, getState) => {
149
+ const params = getDetailParams(getState());
150
+
151
+ if (!params || isDetailPending(getState())) {
152
+ return Promise.resolve();
153
+ }
154
+
155
+ const gatewayUrl = config.get('gatewayUrl');
156
+ const url = `${gatewayUrl}/es/doc/_msearch`;
157
+
158
+ const csid = params.get('csid');
159
+ const index = params.get('index');
160
+ const searchParams = params.get('searchParams');
161
+
162
+ const detailPayload = {
163
+ query: {
164
+ term: {
165
+ 'ecm:name': csid,
166
+ },
167
+ },
168
+ from: 0,
169
+ size: 1,
170
+ };
171
+
172
+ const bodyParts = [
173
+ JSON.stringify({ preference: 'detail' }),
174
+ JSON.stringify(detailPayload),
175
+ ];
176
+
177
+ if (searchParams && typeof index !== 'undefined') {
178
+ const adjacentResultsPayload = getSearchResultPayload(
179
+ searchParams,
180
+ 3,
181
+ Math.max(0, index - 1),
182
+ );
183
+
184
+ bodyParts.push(JSON.stringify({ preference: 'adjacent' }));
185
+ bodyParts.push(JSON.stringify(adjacentResultsPayload));
186
+ }
187
+
188
+ dispatch({
189
+ type: DETAIL_READ_STARTED,
190
+ meta: {
191
+ params,
192
+ },
193
+ });
194
+
195
+ return fetch(url, {
196
+ method: 'POST',
197
+ headers: { 'Content-Type': 'application/x-ndjson' },
198
+ body: bodyParts.join('\n'),
199
+ })
200
+ .then((response) => {
201
+ if (!response.ok) {
202
+ const error = new Error();
203
+
204
+ error.response = response;
205
+
206
+ return Promise.reject(error);
207
+ }
208
+
209
+ return response.json();
210
+ })
211
+ .then((data) => {
212
+ dispatch({
213
+ type: DETAIL_READ_FULFILLED,
214
+ payload: data,
215
+ meta: {
216
+ params,
217
+ },
218
+ });
219
+
220
+ dispatch(findAllInstitutionHoldings());
221
+ })
222
+ .catch((error) => {
223
+ dispatch({
224
+ type: DETAIL_READ_REJECTED,
225
+ payload: error,
226
+ meta: {
227
+ params,
228
+ },
229
+ });
230
+ });
231
+ };
@@ -0,0 +1,10 @@
1
+ import { SET_FILTER_SEARCH_VALUE } from '../constants/actionCodes';
2
+
3
+ // eslint-disable-next-line import/prefer-default-export
4
+ export const setFilterSearchValue = (id, value) => ({
5
+ type: SET_FILTER_SEARCH_VALUE,
6
+ payload: value,
7
+ meta: {
8
+ id,
9
+ },
10
+ });
@@ -0,0 +1,65 @@
1
+ /* global fetch */
2
+
3
+ import get from 'lodash/get';
4
+ import config from '../config';
5
+ import { getMedia } from '../reducers';
6
+
7
+ import {
8
+ SET_MEDIA,
9
+ } from '../constants/actionCodes';
10
+
11
+ export const setMedia = (referenceValue, institutionId, mediaCsids, mediaAltTexts, title) => ({
12
+ type: SET_MEDIA,
13
+ payload: {
14
+ title,
15
+ csids: mediaCsids,
16
+ altTexts: mediaAltTexts,
17
+ },
18
+ meta: {
19
+ institutionId,
20
+ referenceValue,
21
+ },
22
+ });
23
+
24
+ export const findMedia = (referenceValue, institutionId) => (dispatch, getState) => {
25
+ if (getMedia(getState(), referenceValue, institutionId)) {
26
+ return Promise.resolve();
27
+ }
28
+
29
+ let gatewayUrl;
30
+
31
+ if (institutionId === null) {
32
+ gatewayUrl = config.get('gatewayUrl');
33
+ } else {
34
+ gatewayUrl = config.get(['institutions', institutionId, 'gatewayUrl']);
35
+ }
36
+
37
+ const url = `${gatewayUrl}/es/doc/_search`;
38
+ const referenceField = config.get('referenceField');
39
+
40
+ const query = {
41
+ _source: ['collectionspace_denorm:mediaCsid', 'collectionspace_denorm:mediaAltText', 'collectionspace_denorm:title'],
42
+ query: {
43
+ term: {
44
+ [referenceField]: referenceValue,
45
+ },
46
+ },
47
+ size: 1,
48
+ terminate_after: 1,
49
+ };
50
+
51
+ return fetch(url, {
52
+ method: 'POST',
53
+ headers: { 'Content-Type': 'application/json' },
54
+ body: JSON.stringify(query),
55
+ })
56
+ .then((response) => response.json())
57
+ .then((data) => {
58
+ const source = get(data, ['hits', 'hits', 0, '_source']);
59
+ const title = get(source, 'collectionspace_denorm:title');
60
+ const mediaCsids = get(source, 'collectionspace_denorm:mediaCsid') || [];
61
+ const mediaAltTexts = get(source, 'collectionspace_denorm:mediaAltText') || [];
62
+
63
+ return dispatch(setMedia(referenceValue, institutionId, mediaCsids, mediaAltTexts, title));
64
+ });
65
+ };
@@ -0,0 +1,95 @@
1
+ /* global window */
2
+
3
+ import Immutable from 'immutable';
4
+
5
+ import {
6
+ EXPAND_PANEL,
7
+ PREFS_LOADED,
8
+ TOGGLE_PANEL,
9
+ } from '../constants/actionCodes';
10
+
11
+ import config from '../config';
12
+ import { getPrefs } from '../reducers';
13
+
14
+ // Username to use for pref storage when user is not logged in
15
+ const anonymousUsername = '*';
16
+
17
+ export const loadPrefs = () => (dispatch) => {
18
+ const username = anonymousUsername;
19
+ const storageKey = config.get('storageKey');
20
+
21
+ let userPrefs = null;
22
+
23
+ if (username) {
24
+ const serializedPrefs = window.localStorage.getItem(storageKey);
25
+
26
+ if (serializedPrefs) {
27
+ try {
28
+ userPrefs = Immutable.fromJS((JSON.parse(serializedPrefs))[username]);
29
+ } catch (error) {
30
+ userPrefs = null;
31
+ }
32
+ }
33
+ }
34
+
35
+ const defaultUserPrefs = config.get('defaultUserPrefs');
36
+
37
+ if (defaultUserPrefs) {
38
+ userPrefs = (Immutable.fromJS(defaultUserPrefs)).mergeDeep(userPrefs);
39
+ }
40
+
41
+ dispatch({
42
+ type: PREFS_LOADED,
43
+ payload: userPrefs,
44
+ });
45
+ };
46
+
47
+ export const savePrefs = () => (dispatch, getState) => {
48
+ const username = anonymousUsername;
49
+ const storageKey = config.get('storageKey');
50
+
51
+ let prefs;
52
+
53
+ if (username) {
54
+ const serializedPrefs = window.localStorage.getItem(storageKey);
55
+
56
+ if (serializedPrefs) {
57
+ try {
58
+ prefs = JSON.parse(serializedPrefs);
59
+ } catch (error) {
60
+ prefs = null;
61
+ }
62
+ }
63
+
64
+ if (!prefs) {
65
+ prefs = {};
66
+ }
67
+
68
+ prefs[username] = getPrefs(getState()).toJS();
69
+
70
+ window.localStorage.setItem(storageKey, JSON.stringify(prefs));
71
+ }
72
+ };
73
+
74
+ export const expandPanel = (id, expanded = true) => (dispatch) => {
75
+ dispatch({
76
+ type: EXPAND_PANEL,
77
+ payload: expanded,
78
+ meta: {
79
+ id,
80
+ },
81
+ });
82
+
83
+ dispatch(savePrefs());
84
+ };
85
+
86
+ export const togglePanel = (id) => (dispatch) => {
87
+ dispatch({
88
+ type: TOGGLE_PANEL,
89
+ meta: {
90
+ id,
91
+ },
92
+ });
93
+
94
+ dispatch(savePrefs());
95
+ };
@@ -0,0 +1,188 @@
1
+ /* global fetch, window */
2
+
3
+ import Immutable from 'immutable';
4
+ import config from '../config';
5
+ import { SEARCH_QUERY_ID, SORT_ID } from '../constants/ids';
6
+ import { locationToSearchParams, searchParamsToQueryString } from '../helpers/urlHelpers';
7
+
8
+ import {
9
+ getSearchNextOffset,
10
+ getSearchPageSize,
11
+ getSearchParams,
12
+ isSearchPending,
13
+ searchHasMore,
14
+ } from '../reducers';
15
+
16
+ import {
17
+ OPEN_SEARCH,
18
+ SEARCH_STARTED,
19
+ SEARCH_FULFILLED,
20
+ SEARCH_REJECTED,
21
+ SET_SEARCH_PAGE_SIZE,
22
+ SET_SEARCH_PARAMS,
23
+ } from '../constants/actionCodes';
24
+
25
+ import {
26
+ getAggs,
27
+ getSearchResultPayload,
28
+ getQuery,
29
+ getFilterAgg,
30
+ } from '../helpers/esQueryHelpers';
31
+
32
+ export const openSearch = (history, params = Immutable.Map()) => {
33
+ const queryString = searchParamsToQueryString(params);
34
+
35
+ history.push({
36
+ search: `?${queryString}`,
37
+ });
38
+
39
+ return {
40
+ type: OPEN_SEARCH,
41
+ payload: queryString,
42
+ };
43
+ };
44
+
45
+ export const search = (fetchDelay = 0) => (dispatch, getState) => {
46
+ const params = getSearchParams(getState());
47
+
48
+ if (!params || isSearchPending(getState()) || !searchHasMore(getState())) {
49
+ return Promise.resolve();
50
+ }
51
+
52
+ const gatewayUrl = config.get('gatewayUrl');
53
+ const url = `${gatewayUrl}/es/doc/_msearch`;
54
+
55
+ const offset = getSearchNextOffset(getState()) || 0;
56
+ const pageSize = getSearchPageSize(getState()) || 15;
57
+ const resultPayload = getSearchResultPayload(params, pageSize, offset);
58
+
59
+ let filterAggPayloads = [];
60
+
61
+ if (offset === 0) {
62
+ // When loading the first page, get aggregations.
63
+
64
+ resultPayload.aggs = getAggs(params);
65
+
66
+ filterAggPayloads = params
67
+ .delete(SORT_ID)
68
+ .delete(SEARCH_QUERY_ID)
69
+ .keySeq()
70
+ .flatMap((id) => {
71
+ const filterFieldConfig = config.getFilterFieldConfig(id);
72
+
73
+ return [
74
+ {
75
+ preference: id,
76
+ },
77
+ {
78
+ query: getQuery(params.delete(id)),
79
+ size: 0,
80
+ aggs: {
81
+ [id]: getFilterAgg(filterFieldConfig),
82
+ },
83
+ },
84
+ ];
85
+ })
86
+ .toJS();
87
+ }
88
+
89
+ dispatch({
90
+ type: SEARCH_STARTED,
91
+ });
92
+
93
+ const delay = new Promise((resolve) => {
94
+ window.setTimeout(() => {
95
+ resolve();
96
+ }, fetchDelay);
97
+ });
98
+
99
+ return delay
100
+ .then(() => fetch(url, {
101
+ method: 'POST',
102
+ headers: { 'Content-Type': 'application/x-ndjson' },
103
+ body: [
104
+ JSON.stringify({ preference: 'result' }),
105
+ JSON.stringify(resultPayload),
106
+ ...filterAggPayloads.map((payload) => JSON.stringify(payload)),
107
+ ].join('\n'),
108
+ }))
109
+ .then((response) => {
110
+ if (!response.ok) {
111
+ const error = new Error();
112
+
113
+ error.response = response;
114
+
115
+ return Promise.reject(error);
116
+ }
117
+
118
+ return response.json();
119
+ })
120
+ .then((data) => {
121
+ dispatch({
122
+ type: SEARCH_FULFILLED,
123
+ payload: data,
124
+ meta: {
125
+ offset,
126
+ pageSize,
127
+ params,
128
+ },
129
+ });
130
+ })
131
+ .catch((error) => {
132
+ dispatch({
133
+ type: SEARCH_REJECTED,
134
+ payload: error,
135
+ meta: {
136
+ offset,
137
+ pageSize,
138
+ params,
139
+ },
140
+ });
141
+ });
142
+ };
143
+
144
+ export const setSearchPageSize = (pageSize) => ({
145
+ type: SET_SEARCH_PAGE_SIZE,
146
+ payload: pageSize,
147
+ });
148
+
149
+ export const setSearchParams = (location) => {
150
+ const params = locationToSearchParams(location);
151
+
152
+ return {
153
+ type: SET_SEARCH_PARAMS,
154
+ payload: params,
155
+ };
156
+ };
157
+
158
+ export const applyFilter = (history, id, value, isSelected) => (dispatch, getState) => {
159
+ const params = getSearchParams(getState());
160
+
161
+ let selectedValues = params.get(id) || Immutable.List();
162
+
163
+ if (!Immutable.List.isList(selectedValues)) {
164
+ selectedValues = Immutable.List.of(selectedValues);
165
+ }
166
+
167
+ let nextParams;
168
+
169
+ if (isSelected) {
170
+ nextParams = params.set(id, selectedValues.push(value));
171
+ } else {
172
+ const selectedIndex = selectedValues.indexOf(value);
173
+
174
+ if (selectedIndex >= 0) {
175
+ nextParams = (selectedValues.size > 1)
176
+ ? params.set(id, selectedValues.delete(selectedIndex))
177
+ : params.delete(id);
178
+ } else {
179
+ nextParams = params;
180
+ }
181
+ }
182
+
183
+ return dispatch(openSearch(history, nextParams));
184
+ };
185
+
186
+ export const applySortOrder = (history, sortOrder) => (dispatch, getState) => (
187
+ dispatch(openSearch(history, getSearchParams(getState()).set(SORT_ID, sortOrder)))
188
+ );
@@ -0,0 +1,15 @@
1
+ import { openSearch } from './searchActions';
2
+ import { getSearchEntryFormParams } from '../reducers';
3
+ import { SET_SEARCH_ENTRY_FORM_PARAM } from '../constants/actionCodes';
4
+
5
+ export const setSearchEntryFormParam = (id, value) => ({
6
+ type: SET_SEARCH_ENTRY_FORM_PARAM,
7
+ payload: value,
8
+ meta: {
9
+ id,
10
+ },
11
+ });
12
+
13
+ export const applySearchEntryForm = (history) => (dispatch, getState) => (
14
+ dispatch(openSearch(history, getSearchEntryFormParams(getState())))
15
+ );
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { Redirect, Route, Switch } from 'react-router';
3
+ import { BrowserRouter as Router } from 'react-router-dom';
4
+ import config from '../config';
5
+ import RootPage from './pages/RootPage';
6
+
7
+ export default function App() {
8
+ const basename = config.get('basename');
9
+
10
+ return (
11
+ <Router basename={basename}>
12
+ <Switch>
13
+ <Redirect exact path="/" to="/search" />
14
+ <Route component={RootPage} />
15
+ </Switch>
16
+ </Router>
17
+ );
18
+ }