@nyris/nyris-webapp 0.3.6 → 0.3.13

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 (37) hide show
  1. package/build/asset-manifest.json +11 -11
  2. package/build/index.html +1 -1
  3. package/build/{precache-manifest.bffed513ca17d8ac16af1cc3aaa7d908.js → precache-manifest.793f0a4375602ec8cd0fba83bf0e3e67.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.4e9a4ce1.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.8405239a.chunk.js +2 -0
  11. package/build/static/js/main.8405239a.chunk.js.map +1 -0
  12. package/package.json +2 -2
  13. package/src/App.tsx +346 -213
  14. package/src/actions/nyrisAppActions.ts +69 -65
  15. package/src/actions/searchActions.ts +301 -196
  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 +106 -59
  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 +84 -0
  25. package/src/components/Sidebar.tsx +41 -32
  26. package/src/epics/index.ts +173 -104
  27. package/src/epics/search.ts +209 -177
  28. package/src/index.css +98 -9
  29. package/src/index.tsx +148 -144
  30. package/src/utils.ts +5 -0
  31. package/build/static/css/main.2a76dc8a.chunk.css +0 -2
  32. package/build/static/css/main.2a76dc8a.chunk.css.map +0 -1
  33. package/build/static/js/2.4e9a4ce1.chunk.js +0 -3
  34. package/build/static/js/2.4e9a4ce1.chunk.js.map +0 -1
  35. package/build/static/js/main.ec93aa4d.chunk.js +0 -2
  36. package/build/static/js/main.ec93aa4d.chunk.js.map +0 -1
  37. package/src/Demo2.tsx +0 -220
package/src/App.tsx CHANGED
@@ -1,256 +1,389 @@
1
- import './App.css';
2
- import React, {useEffect, useState} from 'react';
3
- import Result from './components/Result';
4
- import ExampleImages from './components/ExampleImages';
5
- import Feedback from './components/Feedback';
1
+ import React, { useEffect, useState } from "react";
2
+ import { useDispatch } from "react-redux";
3
+ import { Animate, NodeGroup } from "react-move";
4
+ import { useDropzone } from "react-dropzone";
5
+ import { Box, Snackbar } from "@material-ui/core";
6
+ import { Alert } from "@material-ui/lab";
7
+ import classNames from "classnames";
8
+ import { loadFilters } from "./actions/searchActions";
9
+ import Result from "./components/Result";
10
+ import Sidebar from "./components/Sidebar";
11
+ import Header from "./components/Header";
12
+ import Feedback from "./components/Feedback";
6
13
  import CategoryFilter from "./components/CategoryFilter";
7
- import PredictedCategories from "./components/PredictedCategories";
8
14
  import Codes from "./components/Codes";
9
- import {Code, CategoryPrediction, RectCoords, Region, cadExtensions, Filter} from "@nyris/nyris-api";
10
- import { useDropzone} from "react-dropzone";
11
- import classNames from 'classnames';
12
- import {Animate, NodeGroup} from "react-move";
13
- import {AppSettings, MDSettings, CanvasWithId} from "./types";
14
- import {NyrisAppPart, NyrisFeedbackState} from "./actions/nyrisAppActions";
15
- import {makeFileHandler, Capture, Preview} from "@nyris/nyris-react-components";
16
- import {Drawer, Snackbar, Toolbar} from "@material-ui/core";
17
- import MuiAlert, { AlertProps } from '@material-ui/lab/Alert';
18
- import { dispatch } from 'rxjs/internal/observable/pairs';
19
- import { useDispatch } from 'react-redux';
20
- import { loadFilters } from './actions/searchActions';
21
- import Sidebar from './components/Sidebar';
15
+ import PredictedCategories from "./components/PredictedCategories";
16
+ import ExampleImages from "./components/ExampleImages";
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";
30
+ import { AppSettings, CanvasWithId, MDSettings } from "./types";
31
+ import { NyrisAppPart, NyrisFeedbackState } from "./actions/nyrisAppActions";
32
+ import SelectedFiltersSummary from "./components/SelectedFiltersSummary";
22
33
 
23
34
  export interface AppHandlers {
24
- onExampleImageClick: (url: string) => void,
25
- onImageClick: (position: number, url: string) => void,
26
- onLinkClick: (position: number, url: string) => void,
27
- onFileDropped: (file: File) => void,
28
- onCaptureComplete: (image: HTMLCanvasElement) => void,
29
- onCaptureCanceled: () => void,
30
- onSelectFile: (f: File) => void,
31
- onCameraClick: () => void,
32
- onShowStart: () => void,
33
- onSelectionChange: (r: RectCoords) => void,
34
- onPositiveFeedback: () => void,
35
- onNegativeFeedback: () => void,
36
- 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;
37
48
  }
38
-
39
49
  export interface AppProps {
40
- search: {
41
- results: any[],
42
- requestId?: string,
43
- duration?: number,
44
- categoryPredictions: CategoryPrediction[],
45
- codes: Code[],
46
- filterOptions: string[],
47
- errorMessage?: string,
48
- regions: Region[],
49
- previewSelection: RectCoords,
50
- toastErrorMessage?: string,
51
- filters: Filter[]
52
- },
53
- previewImage?: CanvasWithId,
54
- settings: AppSettings,
55
- loading: boolean,
56
- showPart: NyrisAppPart,
57
- feedbackState: NyrisFeedbackState,
58
- handlers: AppHandlers,
59
- mdSettings: MDSettings
60
- }
61
-
62
- function Alert(props: AlertProps) {
63
- return <MuiAlert elevation={6} variant="filled" {...props} />;
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;
64
71
  }
65
72
 
66
73
  const App: React.FC<AppProps> = ({
67
- search: {results, regions, previewSelection, requestId, duration, errorMessage, filterOptions, categoryPredictions, codes, toastErrorMessage, filters},
68
- showPart, settings, handlers, loading, previewImage, feedbackState
69
- }) => {
70
- const {getRootProps, getInputProps, isDragActive} = useDropzone({onDrop: (fs: File[]) => handlers.onFileDropped(fs[0])});
71
- const minPreviewHeight = 400;
72
- const halfOfTheScreenHeight = Math.floor(window.innerHeight * 0.45);
73
- const maxPreviewHeight = Math.max(minPreviewHeight, halfOfTheScreenHeight);
74
- const acceptTypes =
75
- [ 'image/*' ].concat(
76
- settings.cadSearch ? cadExtensions : []
77
- ).join(',');
78
- const [toastOpen, setToastOpen] = useState(false);
79
- const dispatch = useDispatch();
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,
94
+ }) => {
95
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({
96
+ onDrop: (fs: File[]) => handlers.onFileDropped(fs[0]),
97
+ });
80
98
 
81
- useEffect(() => {
82
- if (toastErrorMessage !== '') {
83
- setToastOpen(true);
84
- }
85
- }, [toastErrorMessage])
86
-
87
- useEffect(() =>{
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(() => {
88
116
  dispatch(loadFilters());
89
- }, [])
117
+ }, [showPart, dispatch]);
118
+
119
+ return (
120
+ <React.Fragment>
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
+ )}
90
133
 
91
- return (
92
- <div>
93
- {showPart === 'camera' &&
94
- <Capture onCaptureComplete={handlers.onCaptureComplete} onCaptureCanceled={handlers.onCaptureCanceled}
95
- useAppText='Use default camera app'/>}
96
- <div className={classNames('headSection', {hidden: showPart === 'results'})} id="headSection">
97
- <div className="navWrap">
98
- <div>
99
- <section id="branding" style={{paddingLeft: '50px',marginLeft: '70px'}}/>
100
- <div id="menu" className="menuWrap" role="navigation">
101
- <ul>
102
- <li><a href="https://nyris.io/imprint/#privacy" target="_blank"
103
- rel="noopener noreferrer">Privacy Policy</a></li>
104
- <li><a href="https://nyris.io/" target="_blank" rel="noopener noreferrer">Visit our
105
- Website</a></li>
106
- </ul>
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>
107
189
  </div>
108
- </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
+ />
217
+ </Box>
218
+ </div>
109
219
  </div>
110
- <div {...getRootProps({
111
- onClick: e => {
112
- e.stopPropagation();
113
- }
114
- })} className={classNames('wrapper', 'dragAndDropActionArea', {'fileIsHover': isDragActive})}>
115
- <section style={{width:'350px', height: '100%' , marginTop: '100px', borderRight:'4px solid rgba(255, 255, 255, .12)'}}>
116
- <Toolbar>
117
220
 
118
- <div> Select Filters </div>
119
- </Toolbar>
120
- { filters && filters.length > 0 ? <Sidebar filters={filters}/> : <div></div> }
121
-
122
-
123
- </section>
124
- <div className="contentWrap" style={{marginLeft: '50px', marginTop:'50px'}}>
125
- <section className="uploadImage">
126
- <input type="button" name="file" id="capture" className="inputfile" accept="image/*"
127
- capture="environment" onClick={handlers.onCameraClick}/>
128
- <input type="file" name="file" id="capture_file" className="inputfile" accept={acceptTypes}
129
- capture="environment"/>
130
- <input {...getInputProps()} type="file" name="file" id="select_file" className="inputfile"
131
- accept={acceptTypes}
132
- onChange={makeFileHandler(handlers.onSelectFile)}
133
- />
134
- <div className="onDesktop">
135
- Drop an image
136
- <div className="smallText">or</div>
137
- </div>
138
- <div className="onMobile camIcon">
139
- <img src="./images/ic_cam_large.svg" alt="Camera"/>
140
- </div>
141
- <label htmlFor="capture" className="btn primary onMobile"
142
- style={{marginBottom: '2em', width: '22em'}}>
143
- <span className="onMobile">Take a picture</span>
144
- </label>
145
- <br/>
146
- <label htmlFor="select_file" className="btn primary" style={{width: '22em'}}>
147
- <span>Select a file</span>
148
- </label>
149
- <label htmlFor="capture" className="mobileUploadHandler onMobile"/>
150
- </section>
151
- <ExampleImages images={settings.exampleImages} onExampleImageClicked={handlers.onExampleImageClick}/>
152
- </div>
153
- </div>
154
- <div className={classNames('tryDifferent', {hidden: showPart !== 'results'})}
155
- onClick={handlers.onShowStart}>
156
- <div className="icIcon">
157
- </div>
158
- <div className="textDesc"> Try a different image</div>
159
- <br style={{clear: 'both'}}/>
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" }} />
160
232
  </div>
161
- <div className="headerSeparatorTop"/>
162
- <div className="headerSeparatorBack"/>
163
- </div>
233
+ </div>
164
234
 
165
- <section
166
- className={classNames('results', {resultsActive: showPart === 'results'}, (results.length === 1 ? 'singleProduct' : 'multipleProducts'))}>
167
- {errorMessage &&
168
- <div className="errorMsg">
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">
169
245
  {errorMessage}
170
- <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>
171
257
  </div>
172
- </div>
173
- }
174
- <Animate show={loading} start={{opacity: 0.0}} enter={{opacity: [1.0], timing: {duration: 300}}}
175
- leave={{opacity: [0.0], timing: {duration: 300}}}>
176
- {s =>
177
- <div className="loadingOverlay" style={{...s}}>
178
- <div className="loading"/>
179
- </div>
180
- }
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
+ )}
181
271
  </Animate>
182
- {settings.preview && previewImage &&
183
- <div className="preview">
184
- <Preview key={previewImage.id}
185
- maxWidth={document.body.clientWidth} maxHeight={maxPreviewHeight}
186
- dotColor="#4C8F9F"
187
- onSelectionChange={handlers.onSelectionChange} regions={regions}
188
- selection={previewSelection} image={previewImage.canvas}/>
189
- </div>
190
- }
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
+ )}
191
286
  <div className="predicted-categories">
192
- <PredictedCategories cs={categoryPredictions}/>
287
+ <PredictedCategories cs={categoryPredictions} />
193
288
  </div>
194
289
  <div className="predicted-categories">
195
- <Codes codes={codes}/>
290
+ <Codes codes={codes} />
196
291
  </div>
197
- <CategoryFilter cats={filterOptions}/>
292
+ <CategoryFilter cats={filterOptions} />
198
293
 
199
294
  <div className="wrapper">
200
- <NodeGroup data={results}
201
- keyAccessor={r => r.sku}
202
- start={(r, i) => ({opacity: 0, translateX: -100})}
203
- enter={(r, i) => ({
204
- opacity: [1],
205
- translateX: [0],
206
- timing: {delay: i * 100, duration: 300}
207
- })}
208
- >
209
- {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
210
309
  key={key}
211
310
  noImageUrl={settings.noImageUrl}
212
311
  template={settings.resultTemplate}
213
312
  onImageClick={handlers.onImageClick}
214
313
  onLinkClick={handlers.onLinkClick}
215
314
  result={data}
216
- style={{opacity: state.opacity, transform: `translateX(${state.translateX}%)`}}/>)}</>}
217
- </NodeGroup>
218
-
219
- {results.length === 0 && showPart === 'results' && !loading && (
220
-
221
- <div className="noResults">We did not find anything <span role="img"
222
- aria-label="sad face">😕</span></div>
315
+ style={{
316
+ opacity: state.opacity,
317
+ transform: `translateX(${state.translateX}%)`,
318
+ }}
319
+ />
320
+ ))}
321
+ </>
223
322
  )}
323
+ </NodeGroup>
224
324
 
225
- <br style={{clear: 'both'}}/>
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
+ )}
226
333
 
227
- {duration && showPart === 'results' && (<div style={{textAlign: 'center', fontSize: '0.7em', paddingTop: '0.8em'}}>Search
228
- took {duration.toFixed(2)} seconds</div>)}
334
+ <br style={{ clear: "both" }} />
229
335
 
230
- {requestId && showPart === 'results' && <div style={{textAlign: 'center', fontSize: '0.7em', paddingTop: '0.8em'}}>Request
231
- identifier {requestId}</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
+ )}
347
+
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
+ )}
232
359
  </div>
233
- </section>
360
+ </section>
234
361
 
235
- <Snackbar open={toastOpen} autoHideDuration={3000} onClose={() => setToastOpen(false)}>
362
+ <Snackbar
363
+ open={toastOpen}
364
+ autoHideDuration={3000}
365
+ onClose={() => setToastOpen(false)}
366
+ >
236
367
  <Alert onClose={() => setToastOpen(false)} severity="error">
237
- {toastErrorMessage}
368
+ {toastErrorMessage}
238
369
  </Alert>
239
- </Snackbar>
240
-
241
- <section className="footnote">
242
- <div className="wrapper">
243
- © 2017 - 2019 <a href="https://nyris.io">nyris GmbH</a> - All rights reserved - <a
244
- href="https://nyris.io/imprint/">Imprint</a>
245
- </div>
246
- </section>
247
- <Feedback feedbackState={feedbackState} onPositiveFeedback={handlers.onPositiveFeedback}
248
- onNegativeFeedback={handlers.onNegativeFeedback} onClose={handlers.onCloseFeedback}/>
249
- </div>
250
- );
251
-
370
+ </Snackbar>
371
+ </div>
372
+ </div>
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
+ />
385
+ </React.Fragment>
386
+ );
252
387
  };
253
388
 
254
-
255
-
256
389
  export default App;