@nyris/nyris-webapp 0.3.9 → 0.3.14

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 (32) hide show
  1. package/build/asset-manifest.json +11 -11
  2. package/build/index.html +1 -1
  3. package/build/js/settings.example.js +30 -0
  4. package/build/{precache-manifest.a97813497ab8d37548141e5e2618d0dc.js → precache-manifest.fa9930c6ba4e9fb0ebad3869127ef308.js} +9 -9
  5. package/build/service-worker.js +1 -1
  6. package/build/static/css/main.6633770c.chunk.css +2 -0
  7. package/build/static/css/main.6633770c.chunk.css.map +1 -0
  8. package/build/static/js/2.0798bb8b.chunk.js +3 -0
  9. package/build/static/js/{2.6e13adbe.chunk.js.LICENSE.txt → 2.0798bb8b.chunk.js.LICENSE.txt} +0 -0
  10. package/build/static/js/2.0798bb8b.chunk.js.map +1 -0
  11. package/build/static/js/main.31212715.chunk.js +2 -0
  12. package/build/static/js/main.31212715.chunk.js.map +1 -0
  13. package/package.json +2 -2
  14. package/public/js/settings.example.js +30 -0
  15. package/src/App.tsx +221 -203
  16. package/src/actions/nyrisAppActions.ts +69 -65
  17. package/src/actions/searchActions.ts +288 -196
  18. package/src/components/FiltersList.tsx +106 -57
  19. package/src/components/Header.tsx +29 -17
  20. package/src/components/SelectedFiltersSummary.tsx +84 -0
  21. package/src/components/Sidebar.tsx +41 -34
  22. package/src/epics/index.ts +128 -108
  23. package/src/epics/search.ts +114 -82
  24. package/src/index.css +95 -6
  25. package/src/index.tsx +89 -95
  26. package/src/utils.ts +5 -0
  27. package/build/static/css/main.0481043c.chunk.css +0 -2
  28. package/build/static/css/main.0481043c.chunk.css.map +0 -1
  29. package/build/static/js/2.6e13adbe.chunk.js +0 -3
  30. package/build/static/js/2.6e13adbe.chunk.js.map +0 -1
  31. package/build/static/js/main.f5da7aa4.chunk.js +0 -2
  32. package/build/static/js/main.f5da7aa4.chunk.js.map +0 -1
@@ -2,75 +2,78 @@ import {EpicConf} from "./types";
2
2
  import {combineEpics, ofType} from "redux-observable";
3
3
  import {switchMap, withLatestFrom} from "rxjs/operators";
4
4
  import {AppAction} from "../types";
5
- import {ImageSearchOptions, urlOrBlobToCanvas, isCadFile, isImageFile, Filter } from "@nyris/nyris-api";
5
+ import {ImageSearchOptions, urlOrBlobToCanvas, isCadFile, isImageFile, Filter} from "@nyris/nyris-api";
6
6
  import {imageLoaded, cadFileLoaded} from "../actions/searchActions";
7
7
 
8
- const imageSearch: EpicConf = (action$, state$, {api}) => action$.pipe(
9
- ofType('SEARCH_REQUEST_START'),
8
+
9
+ const imageSearch: EpicConf = (action$, state$, { api }) =>
10
+ action$.pipe(
11
+ ofType("SEARCH_REQUEST_START"),
10
12
  withLatestFrom(state$),
11
- switchMap(async ([action, state]) : Promise<AppAction> => {
12
- if (action.type !== 'SEARCH_REQUEST_START') {
13
- throw new Error(`Wrong action type ${action.type}`);
14
- }
15
- console.log("state search ");
16
- console.log(state.search.selectedFilters);
17
-
18
- if ('image' in action) {
19
- let { image, normalizedRect} = action;
20
- console.log("Selected Filters");
21
- let selectedFilters = new Array<Filter>();
22
- if(state.search.selectedFilters.size > 0 ){
23
- state.search.selectedFilters.forEach((values, key) => {
24
- let filterObj: Filter ={
25
- key : key,
26
- values : values
27
- }
28
- selectedFilters.push(filterObj);
29
- });
30
- }
31
-
32
- let options : ImageSearchOptions = {
33
- cropRect: normalizedRect
34
- };
35
-
36
- try {
37
- if(selectedFilters && selectedFilters.length> 0)
38
- {
39
- console.log("With Filters");
40
- const {results, duration, requestId, categoryPredictions, codes} = await api.findByImageWithFilters(image, options, selectedFilters);
41
- return ({ type: 'SEARCH_REQUEST_SUCCEED', results, requestId, duration, categoryPredictions, codes });
42
-
43
- }
44
- else{
45
- console.log("Without Filters");
46
- const {results, duration, requestId, categoryPredictions, codes} = await api.findByImage(image, options);
47
- return ({ type: 'SEARCH_REQUEST_SUCCEED', results, requestId, duration, categoryPredictions, codes });
48
- }
49
- } catch (e) {
50
- console.warn('search failed', e);
51
- return ({ type: 'SEARCH_REQUEST_FAIL', reason: e.message, exception: e });
13
+ switchMap(async ([action, state]): Promise<AppAction> => {
14
+ if (action.type !== "SEARCH_REQUEST_START") {
15
+ throw new Error(`Wrong action type ${action.type}`);
16
+ }
17
+ if ("image" in action) {
18
+ let { image, normalizedRect } = action;
19
+
20
+ let selectedFilters = new Array<Filter>();
21
+ if (state.search.selectedFilters.size > 0) {
22
+ state.search.selectedFilters.forEach((values, key) => {
23
+ if (values && values.length > 0) {
24
+ let filterObj: Filter = {
25
+ key: key,
26
+ values: values,
27
+ };
28
+ selectedFilters.push(filterObj);
52
29
  }
30
+ });
53
31
  }
54
32
 
55
- if ('file' in action) {
56
- console.log('file');
57
- let { file } = action;
33
+ let options: ImageSearchOptions = {
34
+ cropRect: normalizedRect,
35
+ };
58
36
 
59
- let options : ImageSearchOptions = { };
37
+ try {
38
+ const { results, duration, requestId, categoryPredictions, codes } =
39
+ await api.findByImage(image, options, selectedFilters);
40
+ return {
41
+ type: "SEARCH_REQUEST_SUCCEED",
42
+ results,
43
+ requestId,
44
+ duration,
45
+ categoryPredictions,
46
+ codes,
47
+ };
48
+ } catch (e) {
49
+ console.warn("search failed", e);
50
+ return {
51
+ type: "SEARCH_REQUEST_FAIL",
52
+ reason: e.message,
53
+ exception: e,
54
+ };
55
+ }
56
+ }
60
57
 
61
- try {
62
- const {results, duration, requestId, categoryPredictions, codes} = await api.findByCad(file, options);
63
- return ({ type: 'SEARCH_REQUEST_SUCCEED', results, requestId, duration, categoryPredictions, codes });
64
- } catch (e) {
65
- console.warn('search failed', e);
66
- return ({ type: 'SEARCH_REQUEST_FAIL', reason: e.message, exception: e });
67
- }
58
+ if ('file' in action) {
59
+ let { file } = action;
60
+
61
+ let options : ImageSearchOptions = { };
62
+
63
+ try {
64
+ const {results, duration, requestId, categoryPredictions, codes} = await api.findByCad(file, options);
65
+ return ({ type: 'SEARCH_REQUEST_SUCCEED', results, requestId, duration, categoryPredictions, codes });
66
+ } catch (e) {
67
+ console.warn('search failed', e);
68
+ return ({ type: 'SEARCH_REQUEST_FAIL', reason: e.message, exception: e });
68
69
  }
69
- throw new Error(`Wrong action content ${action}`);
70
+ }
71
+ throw new Error(`Wrong action content ${action}`);
72
+
70
73
  })
71
- );
74
+ );
72
75
 
73
- const regionSearch: EpicConf = (action$, state$, {api}) => action$.pipe(
76
+ const regionSearch: EpicConf = (action$, state$, {api}) => action$.pipe(
74
77
  ofType('REGION_REQUEST_START'),
75
78
  withLatestFrom(state$),
76
79
  switchMap(async ([action, {settings}]) : Promise<AppAction> => {
@@ -132,33 +135,62 @@ const loadImage: EpicConf = (action$) => action$.pipe(
132
135
  );
133
136
 
134
137
 
135
- const loadFilters: EpicConf = (action$, state$, {api}) => action$.pipe(
136
- ofType('LOAD_FILTERS'),
138
+ const loadFilters: EpicConf = (action$, state$, { api }) =>
139
+ action$.pipe(
140
+ ofType("LOAD_FILTERS"),
137
141
  withLatestFrom(state$),
138
- switchMap(async ([action, state]) : Promise<AppAction> => {
139
- if(action.type !== 'LOAD_FILTERS'){
140
- throw new Error(`Wrong action type ${action.type}`);
141
- }
142
- try {
143
- // use find filters
144
-
145
- let filters = await api.getFilters();
146
-
147
- filters= filters.slice(0, 3);
148
- console.log(filters);
149
- return {type: 'LOAD_FILTERS_SUCCESS', filters };
142
+ switchMap(async ([action, state]): Promise<AppAction> => {
143
+ if (action.type !== "LOAD_FILTERS") {
144
+ throw new Error(`Wrong action type ${action.type}`);
145
+ }
146
+ try {
147
+ // use find filters
148
+ let filters = await api.getFilters();
149
+ return { type: "LOAD_FILTERS_SUCCESS", filters };
150
+ } catch (e) {
151
+ console.error(e);
152
+ return { type: "LOAD_FILTERS_FAIL", reason: e.message, exception: e };
153
+ }
154
+ })
155
+ );
150
156
 
151
- } catch (e) {
152
- console.error(e);
153
- return {type: 'LOAD_FILTERS_FAIL', reason: e.message, exception: e};
157
+ const searchFilters: EpicConf = (action$, state$, { api }) =>
158
+ action$.pipe(
159
+ ofType("SEARCH_FILTERS"),
160
+ withLatestFrom(state$),
161
+ switchMap(async ([action, state]): Promise<AppAction> => {
162
+ if (action.type !== "SEARCH_FILTERS") {
163
+ throw new Error(`Wrong action type ${action.type}`);
164
+ }
165
+ try {
166
+ let { key, value } = action;
167
+ let values: string[] = [];
168
+ if (value) {
169
+ let response = await api.searchFilters(key, value);
170
+ if (response && response.length > 0) {
171
+ values = values.concat(await api.searchFilters(key, value));
172
+ }
173
+ } else {
174
+ // if no value load default values in search
175
+ let defaultfilters = await api.getFilters();
176
+ let filter = defaultfilters.find((x) => x.key === key);
177
+ if (filter && filter.values && filter.values.length > 0) {
178
+ values = filter.values;
179
+ }
154
180
  }
181
+ return { type: "UPDATE_FILTERS", key, values };
182
+ } catch (e) {
183
+ console.error(e);
184
+ return { type: "LOAD_FILTERS_FAIL", reason: e.message, exception: e };
185
+ }
155
186
  })
156
- );
157
-
187
+ );
158
188
 
159
189
  export default combineEpics(
160
- imageSearch,
161
- regionSearch,
162
- loadFile,
163
- loadImage,
164
- loadFilters);
190
+ imageSearch,
191
+ regionSearch,
192
+ loadFile,
193
+ loadImage,
194
+ loadFilters,
195
+ searchFilters
196
+ );
package/src/index.css CHANGED
@@ -315,7 +315,7 @@ h1, h2 {
315
315
 
316
316
  h1 {
317
317
  font-size: 25px;
318
- margin: 0 auto 25px
318
+ margin: 0 auto
319
319
  }
320
320
 
321
321
  h1.entry-title a {
@@ -1455,7 +1455,7 @@ a img.aligncenter {
1455
1455
  background: #031F2B;
1456
1456
 
1457
1457
  -ms-flex-pack: justify;
1458
- height: calc( 100vh - 60px);
1458
+ height: 100%;
1459
1459
  }
1460
1460
  .sidebar {
1461
1461
  order: 0;
@@ -1468,14 +1468,15 @@ a img.aligncenter {
1468
1468
  -webkit-flex: 0 0 329px;
1469
1469
  -ms-flex: 0 0 329px;
1470
1470
  flex: 0 0 240px;
1471
- border-right: 1px solid #eee;
1472
1471
  width: 25%;
1472
+ height:100%
1473
1473
  }
1474
1474
  .mainContent{
1475
1475
  width: calc(100vh-230);
1476
1476
  flex: 0 0 80%;
1477
1477
  flex-grow: 1;
1478
1478
  position: inherit;
1479
+ border-left: 1px solid #eee;
1479
1480
  }
1480
1481
  .sidebarContent {
1481
1482
  height: 100%;
@@ -1487,7 +1488,7 @@ a img.aligncenter {
1487
1488
  .sidebarHeader{
1488
1489
  display: flex;
1489
1490
  align-items: center;
1490
- padding: 16px;
1491
+ padding: 3px;
1491
1492
  }
1492
1493
  .sidebar-icon{
1493
1494
  width: 1.25em;
@@ -1502,8 +1503,11 @@ a img.aligncenter {
1502
1503
  font-size: 16px;
1503
1504
  float: left;
1504
1505
  color: white;
1505
- margin-top: 27px;
1506
+ margin-top: 7px;
1506
1507
  }
1508
+ .sidebarContent{
1509
+ overflow-y: auto;
1510
+ }
1507
1511
  .Sidebar-items{
1508
1512
  display: flex;
1509
1513
  flex-direction: column;
@@ -1535,11 +1539,96 @@ a img.aligncenter {
1535
1539
  }
1536
1540
  .sidebar-text{
1537
1541
  color: white;
1542
+ font-size: 16px;
1543
+ }
1544
+ .overflowHidden{
1545
+ overflow-y: hidden;
1538
1546
  }
1539
-
1540
1547
  .itemLabel{
1541
1548
  padding-left: 10px;
1549
+ font-size: 14px;
1550
+ max-width: 3ch;
1551
+ overflow: hidden;
1552
+ text-overflow: ellipsis;
1553
+ }
1554
+ .itemLabel:hover::before {
1555
+ content: attr(data-title);
1556
+ position: absolute;
1557
+ bottom: -46px;
1558
+ padding: 10px;
1559
+ background: #000;
1560
+ color: #fff;
1561
+ font-size: 14px;
1562
+ white-space: nowrap;
1563
+ }
1564
+ .itemLabel:hover::after {
1565
+ content: '';
1566
+ position: absolute;
1567
+ bottom: -12px;
1568
+ left: 8px;
1569
+ border: 8px solid transparent;
1570
+ border-bottom: 8px solid #000;
1571
+ }
1572
+
1573
+ #searchQueryInput {
1574
+ width: 100%;
1575
+ height: 1.8rem;
1576
+ background: #f5f5f5;
1577
+ outline: none;
1578
+ border: none;
1579
+ border-radius: 1.625rem;
1580
+ padding: 0 3.5rem 0 1.5rem;
1581
+ font-size: 1rem;
1582
+ }
1583
+
1584
+ #searchQuerySubmit {
1585
+ width: 3.5rem;
1586
+ height: 2.8rem;
1587
+ margin-left: -3.5rem;
1588
+ background: none;
1589
+ border: none;
1590
+ outline: none;
1591
+ }
1592
+
1593
+ .searchBar {
1594
+ width: 100%;
1595
+ display: flex;
1596
+ flex-direction: row;
1597
+ align-items: center;
1542
1598
  }
1599
+
1600
+ .wrap-box-refinements{
1601
+ width: 100%;
1602
+ margin-top: 16px;
1603
+ }
1604
+ .wrap-box-refinements ul{
1605
+ display: flex;
1606
+ flex-wrap: wrap;
1607
+ width: 100%;
1608
+ grid-gap: 8px;
1609
+ gap: 8px;
1610
+ align-items: center;
1611
+ }
1612
+ .summary-label{
1613
+ width: 100%;
1614
+ display: inherit;
1615
+ align-items: inherit;
1616
+ justify-content: inherit;
1617
+ }
1618
+
1619
+ .summary-label-key-text{
1620
+ font-size: 13px;
1621
+ }
1622
+
1623
+ .clear-all-filters-hidden{
1624
+ display: none;
1625
+ }
1626
+ .clear-all-filters{
1627
+ border: 0;
1628
+ background: initial;
1629
+ color: red;
1630
+ font-size: 13px;
1631
+ }
1543
1632
  #catlist a { display: inline-block; padding: 10px; border-radius: 10px; }
1544
1633
  #catlist a:hover { background-color: white; }
1545
1634
  #catlist a.selected { background-color: #444; color: #ddd; }
package/src/index.tsx CHANGED
@@ -5,6 +5,7 @@ import 'react-app-polyfill/stable';
5
5
  import React from 'react';
6
6
  import ReactDOM from 'react-dom';
7
7
  import './index.css';
8
+ import App from './App';
8
9
  import AppMD from './AppMD';
9
10
  import * as serviceWorker from './serviceWorker';
10
11
 
@@ -14,9 +15,8 @@ import {
14
15
  loadCanvas,
15
16
  loadFile,
16
17
  loadUrl,
17
- loadFilters,
18
18
  reducer as searchReducer,
19
- selectionChanged, submitNegativeFeedback, submitPositiveFeedback
19
+ selectionChanged, submitNegativeFeedback, submitPositiveFeedback, clearRequestImage
20
20
  } from './actions/searchActions';
21
21
  import {
22
22
  hideFeedback,
@@ -34,7 +34,6 @@ import 'typeface-roboto';
34
34
  import {defaultMdSettings, defaultSettings} from "./defaults";
35
35
  import rootEpic from "./epics";
36
36
  import { createHashHistory } from 'history';
37
- import App from './App';
38
37
 
39
38
 
40
39
  declare var settings: AppSettings;
@@ -84,122 +83,117 @@ epicMiddleware.run(rootEpic);
84
83
 
85
84
 
86
85
  history.listen((location, action) => {
87
- console.log('history', location, action)
88
- if (action === 'PUSH') {
89
- return;
90
- }
91
- switch (location.pathname) {
92
- case '/results':
93
- store.dispatch({type: 'SHOW_RESULTS'});
94
- store.dispatch({type: 'LOAD_FILTERS'});
95
- break;
96
- case '/':
97
- store.dispatch({type: 'SHOW_START'});
98
- break;
99
- }
86
+ if (action === "PUSH") {
87
+ return;
88
+ }
89
+ switch (location.pathname) {
90
+ case "/results":
91
+ store.dispatch({ type: "SHOW_RESULTS" });
92
+ store.dispatch({ type: "LOAD_FILTERS" });
93
+ break;
94
+ case "/":
95
+ store.dispatch({ type: "SHOW_START" });
96
+ break;
97
+ }
100
98
  });
101
99
 
102
-
103
100
  // Here comes the really dirty code of the composition-root
104
101
 
105
102
  const mapStateToProps = (state: AppState) => ({
106
- showPart: state.nyrisDesign.showPart,
107
- search: {
108
- results: state.search.results,
109
- categoryPredictions: state.search.categoryPredictions,
110
- codes: state.search.codes,
111
- filterOptions: state.search.filterOptions,
112
- previewSelection: state.search.selectedRegion,
113
- regions: state.search.regions,
114
- duration: state.search.duration,
115
- requestId: state.search.requestId,
116
- toastErrorMessage: state.search.errorMessage,
117
- filters: state.search.filters,
118
- selectedFilters: state.search.selectedFilters
119
- },
120
- settings: state.settings,
121
- previewImage: state.search.requestImage,
122
- loading: state.search.fetchingRegions || state.search.fetchingResults,
123
- feedbackState: state.nyrisDesign.feedbackState,
124
- mdSettings: state.settings.materialDesign || defaultMdSettings,
103
+ showPart: state.nyrisDesign.showPart,
104
+ search: {
105
+ results: state.search.results,
106
+ categoryPredictions: state.search.categoryPredictions,
107
+ codes: state.search.codes,
108
+ filterOptions: state.search.filterOptions,
109
+ previewSelection: state.search.selectedRegion,
110
+ regions: state.search.regions,
111
+ duration: state.search.duration,
112
+ requestId: state.search.requestId,
113
+ toastErrorMessage: state.search.errorMessage,
114
+ filters: state.search.filters,
115
+ selectedFilters: state.search.selectedFilters,
116
+ },
117
+ settings: state.settings,
118
+ previewImage: state.search.requestImage,
119
+ loading: state.search.fetchingRegions || state.search.fetchingResults,
120
+ feedbackState: state.nyrisDesign.feedbackState,
121
+ mdSettings: state.settings.materialDesign || defaultMdSettings,
125
122
  });
126
123
 
127
-
128
124
  const mapDispatchToProps = (dispatch: Dispatch<AppAction>) => {
129
- return {
130
- handlers: {
131
- ...bindActionCreators({
132
- onPositiveFeedback: submitPositiveFeedback,
133
- onNegativeFeedback: submitNegativeFeedback,
134
- onCameraClick: showCamera,
135
- onCaptureCanceled: showStart,
136
- onCaptureComplete: loadCanvas,
137
- onSelectFile: loadFile,
138
- onExampleImageClick: loadUrl,
139
- onFileDropped: loadFile,
140
- onSelectionChange: selectionChanged,
141
- onCloseFeedback: hideFeedback
142
- }, dispatch),
143
- onImageClick: (position: number, url: string) => {
144
- dispatch({ type: "RESULT_IMAGE_CLICKED", position, url});
145
- dispatch(loadUrl(url));
146
- },
147
- onLinkClick: (position: number, url: string) => {
148
- dispatch({type: 'RESULT_LINK_CLICKED', position, url});
149
- if (url) {
150
- window.open(url);
151
- }
152
- },
153
- onShowStart: () => {
154
- dispatch(showStart());
155
- dispatch({type: "CLEAR_SELECTED_FILTERS"});
156
- dispatch(loadFilters());
157
- scrollTop();
158
- },
125
+ return {
126
+ handlers: {
127
+ ...bindActionCreators(
128
+ {
129
+ onPositiveFeedback: submitPositiveFeedback,
130
+ onNegativeFeedback: submitNegativeFeedback,
131
+ onCameraClick: showCamera,
132
+ onCaptureCanceled: showStart,
133
+ onCaptureComplete: loadCanvas,
134
+ onSelectFile: loadFile,
135
+ onExampleImageClick: loadUrl,
136
+ onFileDropped: loadFile,
137
+ onSelectionChange: selectionChanged,
138
+ onCloseFeedback: hideFeedback,
139
+ }, dispatch),
140
+ onImageClick: (position: number, url: string) => {
141
+ dispatch({ type: "RESULT_IMAGE_CLICKED", position, url });
142
+ dispatch(loadUrl(url));
143
+ },
144
+ onLinkClick: (position: number, url: string) => {
145
+ dispatch({ type: "RESULT_LINK_CLICKED", position, url });
146
+ if (url) {
147
+ window.open(url);
159
148
  }
160
- };
149
+ },
150
+ onShowStart: () => {
151
+ dispatch(showStart());
152
+ dispatch(clearRequestImage());
153
+ scrollTop();
154
+ },
155
+ },
156
+ };
161
157
  };
162
158
 
163
-
164
159
  // chrome plugin communication
165
160
  function onMessage(evt: MessageEvent) {
166
- let msg = evt.data;
167
- if (msg.type === "image") {
168
- store.dispatch(loadUrl(msg.image));
169
- }
161
+ let msg = evt.data;
162
+ if (msg.type === "image") {
163
+ store.dispatch(loadUrl(msg.image));
164
+ }
170
165
  }
171
- window.addEventListener('message', onMessage);
172
-
173
-
174
-
166
+ window.addEventListener("message", onMessage);
175
167
 
176
168
  let useMd = settings.materialDesign !== undefined;
177
169
  let md: MDSettings = {
178
- ...defaultMdSettings,
179
- ...settings.materialDesign
170
+ ...defaultMdSettings,
171
+ ...settings.materialDesign,
180
172
  };
181
173
  let theme = createMuiTheme({
182
- typography: {
183
- fontFamily: md.customFontFamily,
174
+ typography: {
175
+ fontFamily: md.customFontFamily,
176
+ },
177
+ palette: {
178
+ primary: {
179
+ main: md.primaryColor,
184
180
  },
185
- palette: {
186
- primary: {
187
- main: md.primaryColor,
188
- },
189
- secondary: {
190
- main: md.secondaryColor
191
- }
192
- }
181
+ secondary: {
182
+ main: md.secondaryColor,
183
+ },
184
+ },
193
185
  });
194
186
  const SelectedApp = useMd ? AppMD : App;
195
187
  const ConnectedApp = connect(mapStateToProps, mapDispatchToProps)(SelectedApp);
196
188
 
197
- ReactDOM.render(<Provider store={store}><MuiThemeProvider
198
- theme={theme}><ConnectedApp/></MuiThemeProvider></Provider>, document.getElementById('root'));
199
-
200
-
201
-
202
-
189
+ ReactDOM.render(
190
+ <Provider store={store}>
191
+ <MuiThemeProvider theme={theme}>
192
+ <ConnectedApp />
193
+ </MuiThemeProvider>
194
+ </Provider>,
195
+ document.getElementById("root")
196
+ );
203
197
 
204
198
  // If you want your app to work offline and load faster, you can change
205
199
  // unregister() to register() below. Note this comes with some pitfalls.
package/src/utils.ts CHANGED
@@ -31,3 +31,8 @@ export function getThumbSizeLongestEdge(maxW: number, maxH: number, iW: number,
31
31
  }
32
32
  }
33
33
 
34
+ export function capitalizeFirstLetter(val: string) {
35
+ return val.charAt(0).toUpperCase() + val.slice(1);
36
+ }
37
+
38
+