@nyris/nyris-webapp 0.3.9 → 0.3.12

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 (36) hide show
  1. package/build/asset-manifest.json +11 -11
  2. package/build/index.html +1 -1
  3. package/build/{precache-manifest.a97813497ab8d37548141e5e2618d0dc.js → precache-manifest.1b00dd5c15aa0815244681503d6fa9da.js} +9 -9
  4. package/build/service-worker.js +1 -1
  5. package/build/static/css/main.0c9239ba.chunk.css +2 -0
  6. package/build/static/css/main.0c9239ba.chunk.css.map +1 -0
  7. package/build/static/js/2.520bb6d6.chunk.js +3 -0
  8. package/build/static/js/{2.6e13adbe.chunk.js.LICENSE.txt → 2.520bb6d6.chunk.js.LICENSE.txt} +0 -0
  9. package/build/static/js/2.520bb6d6.chunk.js.map +1 -0
  10. package/build/static/js/main.ef6a9744.chunk.js +2 -0
  11. package/build/static/js/main.ef6a9744.chunk.js.map +1 -0
  12. package/package.json +2 -2
  13. package/src/App.tsx +333 -188
  14. package/src/actions/nyrisAppActions.ts +69 -65
  15. package/src/actions/searchActions.ts +301 -195
  16. package/src/components/CategoryFilter.tsx +16 -13
  17. package/src/components/Codes.tsx +20 -16
  18. package/src/components/ExampleImages.tsx +27 -17
  19. package/src/components/Feedback.tsx +78 -48
  20. package/src/components/FiltersList.tsx +113 -58
  21. package/src/components/Header.tsx +29 -17
  22. package/src/components/PredictedCategories.tsx +15 -12
  23. package/src/components/Result.tsx +186 -113
  24. package/src/components/SelectedFiltersSummary.tsx +85 -0
  25. package/src/components/Sidebar.tsx +44 -34
  26. package/src/epics/index.ts +173 -104
  27. package/src/epics/search.ts +214 -139
  28. package/src/index.css +95 -6
  29. package/src/index.tsx +147 -145
  30. package/src/utils.ts +5 -0
  31. package/build/static/css/main.0481043c.chunk.css +0 -2
  32. package/build/static/css/main.0481043c.chunk.css.map +0 -1
  33. package/build/static/js/2.6e13adbe.chunk.js +0 -3
  34. package/build/static/js/2.6e13adbe.chunk.js.map +0 -1
  35. package/build/static/js/main.f5da7aa4.chunk.js +0 -2
  36. package/build/static/js/main.f5da7aa4.chunk.js.map +0 -1
package/src/App.tsx CHANGED
@@ -1,11 +1,8 @@
1
1
  import React, { useEffect, useState } from "react";
2
2
  import { useDispatch } from "react-redux";
3
3
  import { Animate, NodeGroup } from "react-move";
4
- import { useDropzone} from "react-dropzone";
5
- import {
6
- Box,
7
- Snackbar
8
- } from "@material-ui/core";
4
+ import { useDropzone } from "react-dropzone";
5
+ import { Box, Snackbar } from "@material-ui/core";
9
6
  import { Alert } from "@material-ui/lab";
10
7
  import classNames from "classnames";
11
8
  import { loadFilters } from "./actions/searchActions";
@@ -17,228 +14,376 @@ import CategoryFilter from "./components/CategoryFilter";
17
14
  import Codes from "./components/Codes";
18
15
  import PredictedCategories from "./components/PredictedCategories";
19
16
  import ExampleImages from "./components/ExampleImages";
20
- import {makeFileHandler, Capture, Preview} from "@nyris/nyris-react-components";
21
- import { RectCoords, cadExtensions, CategoryPrediction, Code, Region, Filter} from "@nyris/nyris-api";
17
+ import {
18
+ makeFileHandler,
19
+ Capture,
20
+ Preview,
21
+ } from "@nyris/nyris-react-components";
22
+ import {
23
+ RectCoords,
24
+ cadExtensions,
25
+ CategoryPrediction,
26
+ Code,
27
+ Region,
28
+ Filter,
29
+ } from "@nyris/nyris-api";
22
30
  import { AppSettings, CanvasWithId, MDSettings } from "./types";
23
31
  import { NyrisAppPart, NyrisFeedbackState } from "./actions/nyrisAppActions";
32
+ import SelectedFiltersSummary from "./components/SelectedFiltersSummary";
24
33
 
25
34
  export interface AppHandlers {
26
- onExampleImageClick: (url: string) => void,
27
- onImageClick: (position: number, url: string) => void,
28
- onLinkClick: (position: number, url: string) => void,
29
- onFileDropped: (file: File) => void,
30
- onCaptureComplete: (image: HTMLCanvasElement) => void,
31
- onCaptureCanceled: () => void,
32
- onSelectFile: (f: File) => void,
33
- onCameraClick: () => void,
34
- onShowStart: () => void,
35
- onSelectionChange: (r: RectCoords) => void,
36
- onPositiveFeedback: () => void,
37
- onNegativeFeedback: () => void,
38
- onCloseFeedback: () => void
35
+ onExampleImageClick: (url: string) => void;
36
+ onImageClick: (position: number, url: string) => void;
37
+ onLinkClick: (position: number, url: string) => void;
38
+ onFileDropped: (file: File) => void;
39
+ onCaptureComplete: (image: HTMLCanvasElement) => void;
40
+ onCaptureCanceled: () => void;
41
+ onSelectFile: (f: File) => void;
42
+ onCameraClick: () => void;
43
+ onShowStart: () => void;
44
+ onSelectionChange: (r: RectCoords) => void;
45
+ onPositiveFeedback: () => void;
46
+ onNegativeFeedback: () => void;
47
+ onCloseFeedback: () => void;
39
48
  }
40
49
  export interface AppProps {
41
- search: {
42
- results: any[],
43
- requestId?: string,
44
- duration?: number,
45
- categoryPredictions: CategoryPrediction[],
46
- codes: Code[],
47
- filterOptions: string[],
48
- errorMessage?: string,
49
- regions: Region[],
50
- previewSelection: RectCoords,
51
- toastErrorMessage?: string,
52
- filters: Filter[],
53
- selectedFilters: Map<string,string[]>
54
- },
55
- previewImage?: CanvasWithId,
56
- settings: AppSettings,
57
- loading: boolean,
58
- showPart: NyrisAppPart,
59
- feedbackState: NyrisFeedbackState,
60
- handlers: AppHandlers,
61
- mdSettings: MDSettings
50
+ search: {
51
+ results: any[];
52
+ requestId?: string;
53
+ duration?: number;
54
+ categoryPredictions: CategoryPrediction[];
55
+ codes: Code[];
56
+ filterOptions: string[];
57
+ errorMessage?: string;
58
+ regions: Region[];
59
+ previewSelection: RectCoords;
60
+ toastErrorMessage?: string;
61
+ filters: Filter[];
62
+ selectedFilters: Map<string, string[]>;
63
+ };
64
+ previewImage?: CanvasWithId;
65
+ settings: AppSettings;
66
+ loading: boolean;
67
+ showPart: NyrisAppPart;
68
+ feedbackState: NyrisFeedbackState;
69
+ handlers: AppHandlers;
70
+ mdSettings: MDSettings;
62
71
  }
63
72
 
64
- const Demo2: React.FC<AppProps> = ({
65
- search: {results, regions, previewSelection, requestId, duration, errorMessage, filterOptions, categoryPredictions, codes, toastErrorMessage, filters, selectedFilters},
66
- showPart, settings, handlers, loading, previewImage, feedbackState
73
+ const App: React.FC<AppProps> = ({
74
+ search: {
75
+ results,
76
+ regions,
77
+ previewSelection,
78
+ requestId,
79
+ duration,
80
+ errorMessage,
81
+ filterOptions,
82
+ categoryPredictions,
83
+ codes,
84
+ toastErrorMessage,
85
+ filters,
86
+ selectedFilters,
87
+ },
88
+ showPart,
89
+ settings,
90
+ handlers,
91
+ loading,
92
+ previewImage,
93
+ feedbackState,
67
94
  }) => {
68
-
69
- const {getRootProps, getInputProps, isDragActive} = useDropzone({onDrop: (fs: File[]) => handlers.onFileDropped(fs[0])});
70
-
71
- const minPreviewHeight = 400;
72
- const halfOfTheScreenHeight = Math.floor(window.innerHeight * 0.45);
73
- const maxPreviewHeight = Math.max(minPreviewHeight, halfOfTheScreenHeight);
74
- const [toastOpen, setToastOpen] = useState(false);
75
- const dispatch = useDispatch();
76
-
77
- const acceptTypes =[ 'image/*' ].concat( settings.cadSearch ? cadExtensions : [] ).join(',');
78
-
79
-
80
- useEffect(() => {
81
- if (toastErrorMessage !== '') {
82
- setToastOpen(true);
83
- }
84
- }, [toastErrorMessage])
85
-
86
- useEffect(() =>{
95
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({
96
+ onDrop: (fs: File[]) => handlers.onFileDropped(fs[0]),
97
+ });
98
+
99
+ const minPreviewHeight = 400;
100
+ const halfOfTheScreenHeight = Math.floor(window.innerHeight * 0.45);
101
+ const maxPreviewHeight = Math.max(minPreviewHeight, halfOfTheScreenHeight);
102
+ const [toastOpen, setToastOpen] = useState(false);
103
+ const dispatch = useDispatch();
104
+
105
+ const acceptTypes = ["image/*"]
106
+ .concat(settings.cadSearch ? cadExtensions : [])
107
+ .join(",");
108
+
109
+ useEffect(() => {
110
+ if (toastErrorMessage !== "") {
111
+ setToastOpen(true);
112
+ }
113
+ }, [toastErrorMessage]);
114
+
115
+ useEffect(() => {
87
116
  dispatch(loadFilters());
88
- }, [showPart, dispatch])
89
-
117
+ }, [showPart, dispatch]);
118
+
90
119
  return (
91
120
  <React.Fragment>
92
- <Header/>
93
- <div className="wrapperBody">
94
- <Sidebar filters={filters} selectedFilters={selectedFilters}/>
95
-
96
- <div className="mainContent">
97
- {showPart === 'camera' &&
98
- <Capture onCaptureComplete={handlers.onCaptureComplete} onCaptureCanceled={handlers.onCaptureCanceled}
99
- useAppText='Use default camera app'/>}
100
-
101
-
102
- <div className={classNames('headSection', {hidden: showPart === 'results'})} id="headSection">
103
- <div {...getRootProps({
104
- onClick: e => {
105
- e.stopPropagation();
106
- }
107
- })} className={classNames('wrapper', 'dragAndDropActionArea', {'fileIsHover': isDragActive})}>
108
-
109
- <div className={classNames('contentWrap')}>
110
- <Box display="flex" flexDirection="column" justifyContent="center" alignItems="center" minHeight="100vh">
111
- <section className="uploadImage">
112
- <input type="button" name="file" id="capture" className="inputfile" accept="image/*"
113
- capture="environment" onClick={handlers.onCameraClick}/>
114
- <input type="file" name="file" id="capture_file" className="inputfile" accept={acceptTypes}
115
- capture="environment"/>
116
- <input {...getInputProps()} type="file" name="file" id="select_file" className="inputfile"
117
- accept={acceptTypes}
118
- onChange={makeFileHandler(handlers.onSelectFile)}
119
- />
120
- <div className="onDesktop">
121
- Drop an image
122
- <div className="smallText">or</div>
123
- </div>
124
- <div className="onMobile camIcon">
125
- <img src="./images/ic_cam_large.svg" alt="Camera"/>
126
- </div>
127
- <label htmlFor="capture" className="btn primary onMobile"
128
- style={{marginBottom: '2em', width: '12em'}}>
129
- <span className="onMobile">Take a picture</span>
130
- </label>
131
- <br/>
132
- <label htmlFor="select_file" className="btn primary" style={{width: '12em'}}>
133
- <span>Select a file</span>
134
- </label>
135
- <label htmlFor="capture" className="mobileUploadHandler onMobile"/>
136
- </section>
137
- <ExampleImages images={settings.exampleImages} onExampleImageClicked={handlers.onExampleImageClick}/>
138
-
121
+ <Header />
122
+ <div className="wrapperBody">
123
+ <Sidebar filters={filters} selectedFilters={selectedFilters} />
124
+
125
+ <div className="mainContent">
126
+ {showPart === "camera" && (
127
+ <Capture
128
+ onCaptureComplete={handlers.onCaptureComplete}
129
+ onCaptureCanceled={handlers.onCaptureCanceled}
130
+ useAppText="Use default camera app"
131
+ />
132
+ )}
133
+
134
+ <SelectedFiltersSummary />
135
+ <div
136
+ className={classNames("headSection", {
137
+ hidden: showPart === "results",
138
+ })}
139
+ id="headSection"
140
+ >
141
+ <div
142
+ {...getRootProps({
143
+ onClick: (e) => {
144
+ e.stopPropagation();
145
+ },
146
+ })}
147
+ className={classNames("wrapper", "dragAndDropActionArea", {
148
+ fileIsHover: isDragActive,
149
+ })}
150
+ >
151
+ <div className={classNames("contentWrap")}>
152
+ <Box
153
+ display="flex"
154
+ flexDirection="column"
155
+ justifyContent="center"
156
+ alignItems="center"
157
+ minHeight="100vh"
158
+ >
159
+ <section className="uploadImage">
160
+ <input
161
+ type="button"
162
+ name="file"
163
+ id="capture"
164
+ className="inputfile"
165
+ accept="image/*"
166
+ capture="environment"
167
+ onClick={handlers.onCameraClick}
168
+ />
169
+ <input
170
+ type="file"
171
+ name="file"
172
+ id="capture_file"
173
+ className="inputfile"
174
+ accept={acceptTypes}
175
+ capture="environment"
176
+ />
177
+ <input
178
+ {...getInputProps()}
179
+ type="file"
180
+ name="file"
181
+ id="select_file"
182
+ className="inputfile"
183
+ accept={acceptTypes}
184
+ onChange={makeFileHandler(handlers.onSelectFile)}
185
+ />
186
+ <div className="onDesktop">
187
+ Drop an image
188
+ <div className="smallText">or</div>
189
+ </div>
190
+ <div className="onMobile camIcon">
191
+ <img src="./images/ic_cam_large.svg" alt="Camera" />
192
+ </div>
193
+ <label
194
+ htmlFor="capture"
195
+ className="btn primary onMobile"
196
+ style={{ marginBottom: "2em", width: "12em" }}
197
+ >
198
+ <span className="onMobile">Take a picture</span>
199
+ </label>
200
+ <br />
201
+ <label
202
+ htmlFor="select_file"
203
+ className="btn primary"
204
+ style={{ width: "12em" }}
205
+ >
206
+ <span>Select a file</span>
207
+ </label>
208
+ <label
209
+ htmlFor="capture"
210
+ className="mobileUploadHandler onMobile"
211
+ />
212
+ </section>
213
+ <ExampleImages
214
+ images={settings.exampleImages}
215
+ onExampleImageClicked={handlers.onExampleImageClick}
216
+ />
139
217
  </Box>
140
- </div>
218
+ </div>
141
219
  </div>
142
-
143
- <div className="headerSeparatorTop"/>
144
- <div className="headerSeparatorBack"/>
145
- <div className={classNames('tryDifferent', {hidden: showPart !== 'results'})}
146
- onClick={handlers.onShowStart}>
147
- <div className="icIcon">
148
- </div>
149
- <div className="textDesc"> Try a different image</div>
150
- <br style={{clear: 'both'}}/>
220
+
221
+ <div className="headerSeparatorTop" />
222
+ <div className="headerSeparatorBack" />
223
+ <div
224
+ className={classNames("tryDifferent", {
225
+ hidden: showPart !== "results",
226
+ })}
227
+ onClick={handlers.onShowStart}
228
+ >
229
+ <div className="icIcon"></div>
230
+ <div className="textDesc"> Try a different image</div>
231
+ <br style={{ clear: "both" }} />
151
232
  </div>
152
- </div>
153
-
154
- <section className={classNames({hideResults: showPart !== 'results'},'results', {resultsActive: showPart === 'results'}, (results.length === 1 ? 'singleProduct' : 'multipleProducts'))}>
155
- {errorMessage &&
156
- <div className="errorMsg">
233
+ </div>
234
+
235
+ <section
236
+ className={classNames(
237
+ { hideResults: showPart !== "results" },
238
+ "results",
239
+ { resultsActive: showPart === "results" },
240
+ results.length === 1 ? "singleProduct" : "multipleProducts"
241
+ )}
242
+ >
243
+ {errorMessage && (
244
+ <div className="errorMsg">
157
245
  {errorMessage}
158
- <div style={{textAlign: 'center', fontSize: '0ß.7em', paddingTop: '0.8em'}}><span>Make sure to include the request ID when reporting a problem: {requestId}</span>
246
+ <div
247
+ style={{
248
+ textAlign: "center",
249
+ fontSize: "0ß.7em",
250
+ paddingTop: "0.8em",
251
+ }}
252
+ >
253
+ <span>
254
+ Make sure to include the request ID when reporting a
255
+ problem: {requestId}
256
+ </span>
159
257
  </div>
160
- </div>
161
- }
162
- <Animate show={loading} start={{opacity: 0.0}} enter={{opacity: [1.0], timing: {duration: 300}}}
163
- leave={{opacity: [0.0], timing: {duration: 300}}}>
164
- {s =>
165
- <div className="loadingOverlay" style={{...s}}>
166
- <div className="loading"/>
167
- </div>
168
- }
258
+ </div>
259
+ )}
260
+ <Animate
261
+ show={loading}
262
+ start={{ opacity: 0.0 }}
263
+ enter={{ opacity: [1.0], timing: { duration: 300 } }}
264
+ leave={{ opacity: [0.0], timing: { duration: 300 } }}
265
+ >
266
+ {(s) => (
267
+ <div className="loadingOverlay" style={{ ...s }}>
268
+ <div className="loading" />
269
+ </div>
270
+ )}
169
271
  </Animate>
170
- {settings.preview && previewImage &&
171
- <div className="preview">
172
- <Preview key={previewImage.id}
173
- maxWidth={document.body.clientWidth} maxHeight={maxPreviewHeight}
174
- dotColor="#4C8F9F"
175
- onSelectionChange={handlers.onSelectionChange} regions={regions}
176
- selection={previewSelection} image={previewImage.canvas}/>
177
- </div>
178
- }
272
+ {settings.preview && previewImage && (
273
+ <div className="preview">
274
+ <Preview
275
+ key={previewImage.id}
276
+ maxWidth={document.body.clientWidth}
277
+ maxHeight={maxPreviewHeight}
278
+ dotColor="#4C8F9F"
279
+ onSelectionChange={handlers.onSelectionChange}
280
+ regions={regions}
281
+ selection={previewSelection}
282
+ image={previewImage.canvas}
283
+ />
284
+ </div>
285
+ )}
179
286
  <div className="predicted-categories">
180
- <PredictedCategories cs={categoryPredictions}/>
287
+ <PredictedCategories cs={categoryPredictions} />
181
288
  </div>
182
289
  <div className="predicted-categories">
183
- <Codes codes={codes}/>
290
+ <Codes codes={codes} />
184
291
  </div>
185
- <CategoryFilter cats={filterOptions}/>
292
+ <CategoryFilter cats={filterOptions} />
186
293
 
187
294
  <div className="wrapper">
188
- <NodeGroup data={results}
189
- keyAccessor={r => r.sku}
190
- start={(r, i) => ({opacity: 0, translateX: -100})}
191
- enter={(r, i) => ({
192
- opacity: [1],
193
- translateX: [0],
194
- timing: {delay: i * 100, duration: 300}
195
- })}
196
- >
197
- {rs => <>{rs.map(({key, data, state}) => <Result
295
+ <NodeGroup
296
+ data={results}
297
+ keyAccessor={(r) => r.sku}
298
+ start={(r, i) => ({ opacity: 0, translateX: -100 })}
299
+ enter={(r, i) => ({
300
+ opacity: [1],
301
+ translateX: [0],
302
+ timing: { delay: i * 100, duration: 300 },
303
+ })}
304
+ >
305
+ {(rs) => (
306
+ <>
307
+ {rs.map(({ key, data, state }) => (
308
+ <Result
198
309
  key={key}
199
310
  noImageUrl={settings.noImageUrl}
200
311
  template={settings.resultTemplate}
201
312
  onImageClick={handlers.onImageClick}
202
313
  onLinkClick={handlers.onLinkClick}
203
314
  result={data}
204
- style={{opacity: state.opacity, transform: `translateX(${state.translateX}%)`}}/>)}</>}
205
- </NodeGroup>
206
-
207
- {results.length === 0 && showPart === 'results' && !loading && (
208
-
209
- <div className="noResults">We did not find anything <span role="img"
210
- aria-label="sad face">😕</span></div>
315
+ style={{
316
+ opacity: state.opacity,
317
+ transform: `translateX(${state.translateX}%)`,
318
+ }}
319
+ />
320
+ ))}
321
+ </>
211
322
  )}
323
+ </NodeGroup>
324
+
325
+ {results.length === 0 && showPart === "results" && !loading && (
326
+ <div className="noResults">
327
+ We did not find anything{" "}
328
+ <span role="img" aria-label="sad face">
329
+ 😕
330
+ </span>
331
+ </div>
332
+ )}
212
333
 
213
- <br style={{clear: 'both'}}/>
334
+ <br style={{ clear: "both" }} />
214
335
 
215
- {duration && showPart === 'results' && (<div style={{textAlign: 'center', fontSize: '0.7em', paddingTop: '0.8em'}}>Search
216
- took {duration.toFixed(2)} seconds</div>)}
336
+ {duration && showPart === "results" && (
337
+ <div
338
+ style={{
339
+ textAlign: "center",
340
+ fontSize: "0.7em",
341
+ paddingTop: "0.8em",
342
+ }}
343
+ >
344
+ Search took {duration.toFixed(2)} seconds
345
+ </div>
346
+ )}
217
347
 
218
- {requestId && showPart === 'results' && <div style={{textAlign: 'center', fontSize: '0.7em', paddingTop: '0.8em'}}>Request
219
- identifier {requestId}</div>}
348
+ {requestId && showPart === "results" && (
349
+ <div
350
+ style={{
351
+ textAlign: "center",
352
+ fontSize: "0.7em",
353
+ paddingTop: "0.8em",
354
+ }}
355
+ >
356
+ Request identifier {requestId}
357
+ </div>
358
+ )}
220
359
  </div>
221
- </section>
360
+ </section>
222
361
 
223
- <Snackbar open={toastOpen} autoHideDuration={3000} onClose={() => setToastOpen(false)}>
362
+ <Snackbar
363
+ open={toastOpen}
364
+ autoHideDuration={3000}
365
+ onClose={() => setToastOpen(false)}
366
+ >
224
367
  <Alert onClose={() => setToastOpen(false)} severity="error">
225
- {toastErrorMessage}
368
+ {toastErrorMessage}
226
369
  </Alert>
227
- </Snackbar>
228
-
370
+ </Snackbar>
371
+ </div>
229
372
  </div>
230
- </div>
231
- <section className="footnote">
232
- <div className="wrapper">
233
- © 2017 - 2019 <a href="https://nyris.io">nyris GmbH</a> - All rights reserved - <a
234
- href="https://nyris.io/imprint/">Imprint</a>
235
- </div>
236
- </section>
237
- <Feedback feedbackState={feedbackState} onPositiveFeedback={handlers.onPositiveFeedback}
238
- onNegativeFeedback={handlers.onNegativeFeedback} onClose={handlers.onCloseFeedback}/>
239
-
373
+ <section className="footnote">
374
+ <div className="wrapper">
375
+ © 2017 - 2019 <a href="https://nyris.io">nyris GmbH</a> - All rights
376
+ reserved - <a href="https://nyris.io/imprint/">Imprint</a>
377
+ </div>
378
+ </section>
379
+ <Feedback
380
+ feedbackState={feedbackState}
381
+ onPositiveFeedback={handlers.onPositiveFeedback}
382
+ onNegativeFeedback={handlers.onNegativeFeedback}
383
+ onClose={handlers.onCloseFeedback}
384
+ />
240
385
  </React.Fragment>
241
386
  );
242
- }
387
+ };
243
388
 
244
- export default Demo2;
389
+ export default App;