@woosmap/ui 4.116.0 → 4.117.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@woosmap/ui",
3
- "version": "4.116.0",
3
+ "version": "4.117.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/WebGeoServices/ui.git"
@@ -4,10 +4,10 @@ import cl from 'classnames';
4
4
  import React, { Component } from 'react';
5
5
  import PropTypes from 'prop-types';
6
6
  import Demo from './SkeletonDemo';
7
- import Input from '../Input/Input';
8
7
  import Select from '../Select/Select';
9
8
  import Button from '../Button/Button';
10
9
  import ButtonGroup from '../Button/ButtonGroup';
10
+ import LocalitiesAutocomplete from '../Woosmap/LocalitiesAutocomplete';
11
11
  import CountrySelect, { countriesOptions } from '../Select/CountrySelect';
12
12
  import Constants from '../../Constants';
13
13
  import { tr } from '../utils/locale';
@@ -50,9 +50,9 @@ export default class LocalitiesDemo extends Component {
50
50
  localities: null,
51
51
  selectedLocality: null,
52
52
  error: null,
53
- input: 'Milan',
54
53
  localitiesTypes: [],
55
54
  countries: [],
55
+ showDetails: false,
56
56
  language: (defaultLang || languages[1]).value,
57
57
  };
58
58
  this.types = types.map((item) => ({
@@ -64,7 +64,9 @@ export default class LocalitiesDemo extends Component {
64
64
  this.marker = null;
65
65
  this.viewport = null;
66
66
  this.requestUrl = 'https://api.woosmap.com/localities/autocomplete/';
67
+ this.requestUrlDetail = 'https://api.woosmap.com/localities/details/';
67
68
  this.demoRef = React.createRef();
69
+ this.inputRef = React.createRef();
68
70
  this.overlay = null;
69
71
  }
70
72
 
@@ -76,50 +78,44 @@ export default class LocalitiesDemo extends Component {
76
78
  this.mounted = false;
77
79
  }
78
80
 
79
- requestLocalities = () => {
80
- axios
81
- .get(this.requestUrl, {
82
- params: this.getRequestparams(),
83
- })
84
- .then((response) => {
85
- if (this.mounted) {
86
- const selectedLocality = response.data.localities.length > 0 ? response.data.localities[0] : null;
87
- this.setState(
88
- {
89
- localities: response.data,
90
- error: null,
91
- },
92
- () => this.selectLocality(selectedLocality)
93
- );
94
- }
95
- })
96
- .catch((error) => {
97
- const errorResult = (error && error.response && error.response.data) || 'Unhandled error';
98
- this.setState({ error: errorResult });
99
- });
100
- };
81
+ typesContainsAddress = (localitiesTypes) => localitiesTypes.find((item) => item.value === 'address');
101
82
 
102
83
  getRequestparams = () => {
103
- const { input, language, localitiesTypes, countries } = this.state;
104
- return {
105
- key: Constants.woosmapKey,
106
- input,
107
- language,
108
- types: localitiesTypes.map((item) => item.value).join('|'),
109
- components: countries.map((item) => `country:${item.value}`).join('|'),
110
- };
84
+ const { selectedLocality, language } = this.state;
85
+ return { public_id: selectedLocality?.public_id, key: Constants.woosmapKey, language };
111
86
  };
112
87
 
113
- typesContainsAddress = (localitiesTypes) => localitiesTypes.find((item) => item.value === 'address');
114
-
115
88
  selectLocality = (locality) => {
116
- this.setState({ selectedLocality: locality }, this.displayOnMap);
89
+ this.setState({ selectedLocality: locality }, () => {
90
+ if (locality) {
91
+ axios
92
+ .get(this.requestUrlDetail, {
93
+ params: this.getRequestparams(),
94
+ })
95
+ .then((response) => {
96
+ if (this.mounted) {
97
+ this.setState(
98
+ {
99
+ selectedLocality: response.data.result,
100
+ showDetails: true,
101
+ localities: response.data,
102
+ },
103
+ this.displayOnMap
104
+ );
105
+ }
106
+ })
107
+ .catch((error) => {
108
+ const errorResult = (error && error.response && error.response.data) || 'Unhandled error';
109
+ this.setState({ error: errorResult });
110
+ });
111
+ }
112
+ });
117
113
  };
118
114
 
119
115
  displayOnMap = () => {
120
116
  const { selectedLocality } = this.state;
121
117
 
122
- if (this.map && selectedLocality?.location) {
118
+ if (this.map && selectedLocality?.geometry?.location) {
123
119
  if (this.marker) {
124
120
  this.marker.setMap(null);
125
121
  this.marker = null;
@@ -132,18 +128,27 @@ export default class LocalitiesDemo extends Component {
132
128
 
133
129
  this.marker = new window.woosmap.map.Marker({
134
130
  icon: { url: markerIcon, scaledSize: { height: 46, width: 30 } },
135
- position: selectedLocality.location,
131
+ position: selectedLocality.geometry.location,
136
132
  map: this.map,
137
133
  });
138
134
 
139
- if (selectedLocality.viewpoint) {
140
- this.overlay = new WoosmapMapBoundingBox(selectedLocality.viewpoint.bounds);
135
+ if (selectedLocality.geometry.viewport) {
136
+ const vp = selectedLocality.geometry.viewport;
137
+ const bounds = {
138
+ north: vp.northeast.lat,
139
+ south: vp.southwest.lat,
140
+ east: vp.northeast.lng,
141
+ west: vp.southwest.lng,
142
+ };
143
+ this.overlay = new WoosmapMapBoundingBox(bounds);
141
144
  this.overlay.setMap(this.map);
142
145
 
143
- this.map.fitBounds(selectedLocality.viewpoint.bounds, { top: 70, bottom: 70, left: 70, right: 70 });
146
+ this.map.fitBounds(bounds, { top: 70, bottom: 70, left: 70, right: 70 });
144
147
  } else {
145
- this.map.setCenter(selectedLocality.location);
146
- this.map.setZoom(10);
148
+ this.map.setCenter(selectedLocality.geometry.location);
149
+ if (selectedLocality.type === 'address') {
150
+ this.map.setZoom(10);
151
+ }
147
152
  }
148
153
  }
149
154
  };
@@ -168,28 +173,75 @@ export default class LocalitiesDemo extends Component {
168
173
  return <div dangerouslySetInnerHTML={{ __html: description }} />;
169
174
  };
170
175
 
171
- renderAutocompleteList = () => {
172
- const { localities } = this.state;
173
- if (!localities || localities.localities.length === 0) {
176
+ renderResult = () => {
177
+ const { selectedLocality } = this.state;
178
+ if (!selectedLocality) {
174
179
  return <div className="demo__empty">{tr('No locality found')}</div>;
175
180
  }
176
- const result = localities.localities.map((locality) => {
177
- const { selectedLocality } = this.state;
178
- return (
179
- <li key={locality.public_id}>
180
- <button
181
- type="button"
182
- className={cl('btn', 'btn--link', {
183
- selected: selectedLocality === locality,
184
- })}
185
- onClick={() => this.selectLocality(locality)}
186
- >
187
- {this.getHighlightedLocalityDesc(locality)}
188
- </button>
189
- </li>
190
- );
191
- });
192
- return <ul>{result}</ul>;
181
+
182
+ return (
183
+ <>
184
+ {selectedLocality.public_id && (
185
+ <p className="demo--localities__result-line">
186
+ <span className="demo--localities__result-line__type">Public id:</span>
187
+ <span className="demo--localities__result-line__info">{selectedLocality.public_id}</span>
188
+ </p>
189
+ )}
190
+ {selectedLocality.formatted_address && (
191
+ <p className="demo--localities__result-line">
192
+ <span className="demo--localities__result-line__type">Description:</span>
193
+ <span className="demo--localities__result-line__info">
194
+ {selectedLocality.formatted_address}
195
+ </span>
196
+ </p>
197
+ )}
198
+ {selectedLocality.types && selectedLocality.types[0] && (
199
+ <p className="demo--localities__result-line">
200
+ <span className="demo--localities__result-line__type">Type:</span>
201
+ <span className="demo--localities__result-line__info">
202
+ {selectedLocality.types[0].replace('_', ' ')}
203
+ </span>
204
+ </p>
205
+ )}
206
+ {selectedLocality.geometry?.accuracy && (
207
+ <p className="demo--localities__result-line">
208
+ <span className="demo--localities__result-line__type">Location type:</span>
209
+ <span className="demo--localities__result-line__info">
210
+ {selectedLocality.geometry?.accuracy.replace('_', ' ').toLowerCase()}
211
+ </span>
212
+ </p>
213
+ )}
214
+ {selectedLocality.geometry?.location && (
215
+ <p className="demo--localities__result-line two-lines">
216
+ <div className="two-lines--line">
217
+ <span className="demo--localities__result-line__type">Latitude:</span>
218
+ <span className="demo--localities__result-line__info">
219
+ {selectedLocality.geometry.location.lat.toString()}
220
+ </span>
221
+ </div>
222
+ <div className="two-lines--line">
223
+ <span className="demo--localities__result-line__type">Longitude:</span>
224
+ <span className="demo--localities__result-line__info">
225
+ {selectedLocality.geometry.location.lng.toString()}
226
+ </span>
227
+ </div>
228
+ </p>
229
+ )}
230
+ {selectedLocality.address_components && (
231
+ <div className="demo--localities__result-section">
232
+ <div className="demo--localities__result">Address components</div>
233
+ {selectedLocality.address_components.map((comp) => (
234
+ <div className="demo--localities__result-line">
235
+ <span className="demo--localities__result-line__type">{comp.types[0]}:</span>
236
+ <span className="demo--localities__result-line__info">
237
+ {Array.isArray(comp.long_name) ? comp.long_name.join(', ') : comp.long_name}
238
+ </span>
239
+ </div>
240
+ ))}
241
+ </div>
242
+ )}
243
+ </>
244
+ );
193
245
  };
194
246
 
195
247
  displayMap = () => {
@@ -200,26 +252,32 @@ export default class LocalitiesDemo extends Component {
200
252
  if (window.woosmap && window.woosmap.map && this.demoRef.current) {
201
253
  window.woosmap.map.config.setApiKey(Constants.woosmapKey);
202
254
  this.map = createWoosmapMap(this.demoRef.current.getMap());
203
- this.requestLocalities();
204
255
  } else {
205
256
  this.timeoutMap = setTimeout(this.displayMap, 300);
206
257
  }
207
258
  };
208
259
 
209
260
  renderHeaderFilters = () => {
210
- const { input } = this.state;
211
261
  const { usecaseFilter } = this.props;
262
+ const { language, localitiesTypes, countries } = this.state;
212
263
  const inputFilter = {
213
264
  label: tr('Type in '),
214
265
  component: (
215
- <Input
216
- value={input}
217
- autocomplete={false}
218
- placeholder={tr('Type a locality name...')}
219
- onChange={(e) => this.setState({ input: e.target.value }, this.requestLocalities)}
266
+ <LocalitiesAutocomplete
267
+ ref={this.inputRef}
268
+ woosmapKey={Constants.woosmapKey}
269
+ inputChangeCb={() => this.setState({ showDetails: false })}
270
+ placeholder={tr('Type in a locality name...')}
271
+ defaultLocality={this.defaultOrigin}
272
+ language={language}
273
+ responseCb={(localities) => this.setState({ localities })}
274
+ components={countries.map((item) => `country:${item.value}`).join('|')}
275
+ types={localitiesTypes.map((item) => item.value).join('|')}
276
+ callback={this.selectLocality}
220
277
  />
221
278
  ),
222
279
  };
280
+
223
281
  return [usecaseFilter, inputFilter];
224
282
  };
225
283
 
@@ -237,27 +295,24 @@ export default class LocalitiesDemo extends Component {
237
295
  label: tr('Filter by'),
238
296
  component: (
239
297
  <>
240
- <CountrySelect
241
- isMulti
242
- placeholder={tr('Country')}
243
- filter={['fr', 'gb', 'sp', 'it', 'de', 'nl']}
244
- onChange={(items) => this.setState({ countries: items }, this.requestLocalities)}
245
- value={countries}
246
- />
247
298
  <Select
248
- placeholder={tr('Point of interest')}
299
+ placeholder={tr('Type of location')}
249
300
  options={this.types}
250
301
  isMulti
251
302
  onChange={(items) =>
252
- this.setState(
253
- {
254
- localitiesTypes: items,
255
- countries: this.typesContainsAddress(items) ? defaultCountry : countries,
256
- },
257
- this.requestLocalities
258
- )
303
+ this.setState({
304
+ localitiesTypes: items,
305
+ countries: this.typesContainsAddress(items) ? defaultCountry : countries,
306
+ })
259
307
  }
260
308
  />
309
+ <CountrySelect
310
+ isMulti
311
+ placeholder={tr('Country')}
312
+ filter={['fr', 'gb', 'sp', 'it', 'de', 'nl']}
313
+ onChange={(items) => this.setState({ countries: items })}
314
+ value={countries}
315
+ />
261
316
  </>
262
317
  ),
263
318
  };
@@ -272,7 +327,7 @@ export default class LocalitiesDemo extends Component {
272
327
  key={item.value}
273
328
  active={language === item.value}
274
329
  onClick={() => {
275
- this.setState({ language: item.value }, this.requestLocalities);
330
+ this.setState({ language: item.value });
276
331
  }}
277
332
  >
278
333
  <span className={cl('flag', `flag-${item.value}`)} title={item.label} />
@@ -285,7 +340,7 @@ export default class LocalitiesDemo extends Component {
285
340
  };
286
341
 
287
342
  render() {
288
- const { localities, error } = this.state;
343
+ const { localities, error, showDetails } = this.state;
289
344
  const { noheader, headerLabels, subHeader } = this.props;
290
345
  return (
291
346
  <Demo
@@ -296,12 +351,12 @@ export default class LocalitiesDemo extends Component {
296
351
  className="demo--localities"
297
352
  footerFilters={this.renderFooterFilters()}
298
353
  headerFilters={this.renderHeaderFilters()}
299
- request={this.requestUrl}
354
+ request={showDetails ? this.requestUrlDetail : this.requestUrl}
300
355
  noheader={noheader}
301
- params={this.getRequestparams()}
356
+ params={showDetails ? this.getRequestparams() : this.inputRef?.current?.getAutocompleteRequestparams()}
302
357
  referer="http://localhost/"
303
358
  response={error || localities}
304
- result={this.renderAutocompleteList()}
359
+ result={this.renderResult()}
305
360
  withMap
306
361
  ref={this.demoRef}
307
362
  />
@@ -293,25 +293,57 @@
293
293
  max-height 49rem
294
294
  .demo--localities--fr &
295
295
  max-height 33rem
296
+ &--localities--uk
297
+ &--localities--fr
296
298
  &--address
297
299
  &--localitiesaddress
298
300
  &__formcontainer
301
+ mbib(1.4)
299
302
  position relative
300
-
301
- &--address
302
- &__formcontainer
303
303
  .input
304
304
  &__label
305
+ font-weight 600
305
306
  margin-bottom .4rem
306
307
  &__line
307
308
  margin-bottom 1rem
308
- &--localities--uk
309
- &--localities--fr
310
- &__formcontainer
311
- mbib(1.6)
312
- position relative
313
- .input__label
314
- font-weight 600
309
+ &--search
310
+ .demo__results__content
311
+ li
312
+ list-style disc inside
313
+ &--localities
314
+ .demo
315
+ &__results__content
316
+ padding 0
317
+ &__empty
318
+ padding $demoPadding
319
+ &__result-line
320
+ padding 1.4rem $demoPadding 0 $demoPadding
321
+ display flex
322
+ align-items center
323
+ flex-shrink 0
324
+ flex-wrap wrap
325
+ &:not(.two-lines)
326
+ mbi(.6)
327
+ &__type
328
+ white-space nowrap
329
+ &__info
330
+ font-weight 700
331
+ &.two-lines
332
+ .two-lines--line
333
+ mbi(.6)
334
+ &__result
335
+ margin-bottom 1rem
336
+ color $dark50
337
+ font-size 1rem
338
+ text-transform uppercase
339
+ letter-spacing .1rem
340
+ &-section
341
+ margin-top $demoPadding
342
+ padding $demoPadding
343
+ background-color $dark2
344
+ .demo--localities__result-line
345
+ padding 0
346
+ margin-bottom .6rem
315
347
  .overlay
316
348
  position absolute
317
349
  top 0
@@ -16,6 +16,7 @@ export default class LocalitiesAutocomplete extends Component {
16
16
  }
17
17
  : null,
18
18
  menuIsOpen: false,
19
+ input: '',
19
20
  };
20
21
  this.apiUrl = 'https://api.woosmap.com/localities/autocomplete';
21
22
  }
@@ -27,6 +28,33 @@ export default class LocalitiesAutocomplete extends Component {
27
28
  };
28
29
  };
29
30
 
31
+ getAutocompleteRequestparams = () => {
32
+ const { language, components, types } = this.props;
33
+ const { input } = this.state;
34
+ const { woosmapKey } = this.props;
35
+ const inputValue = input;
36
+ const params = {
37
+ key: woosmapKey,
38
+ input: inputValue,
39
+ language,
40
+ };
41
+ if (components) {
42
+ params.components = components;
43
+ }
44
+ if (types) {
45
+ params.types = types;
46
+ }
47
+ return params;
48
+ };
49
+
50
+ onInputChange = (value) => {
51
+ const { inputChangeCb } = this.props;
52
+
53
+ if (inputChangeCb) {
54
+ inputChangeCb(value);
55
+ }
56
+ };
57
+
30
58
  onChange = (selected) => {
31
59
  const { callback } = this.props;
32
60
  const { localities } = this.state;
@@ -47,36 +75,30 @@ export default class LocalitiesAutocomplete extends Component {
47
75
  };
48
76
 
49
77
  loadLocalities = (newValue, callback) => {
50
- this.setState({ menuIsOpen: true });
51
- const { language, components, types } = this.props;
52
- const { woosmapKey } = this.props;
53
- const inputValue = newValue;
54
- const params = {
55
- key: woosmapKey,
56
- input: inputValue,
57
- language,
78
+ const xhr = () => {
79
+ const params = this.getAutocompleteRequestparams(newValue);
80
+ axios
81
+ .get(this.apiUrl, {
82
+ params,
83
+ })
84
+ .then((response) => {
85
+ const result = response.data.localities.map((item) => ({
86
+ value: item.public_id,
87
+ label: item.description,
88
+ }));
89
+ this.setState({ localities: response.data.localities }, () => {
90
+ const { responseCb } = this.props;
91
+ if (responseCb) {
92
+ responseCb(response.data.localities);
93
+ }
94
+ });
95
+ callback(result);
96
+ })
97
+ .catch(() => {
98
+ // do nothing
99
+ });
58
100
  };
59
- if (components) {
60
- params.components = components;
61
- }
62
- if (types) {
63
- params.types = types;
64
- }
65
- axios
66
- .get(this.apiUrl, {
67
- params,
68
- })
69
- .then((response) => {
70
- const result = response.data.localities.map((item) => ({
71
- value: item.public_id,
72
- label: item.description,
73
- }));
74
- this.setState({ localities: response.data.localities });
75
- callback(result);
76
- })
77
- .catch(() => {
78
- // do nothing
79
- });
101
+ this.setState({ menuIsOpen: true, input: newValue }, xhr);
80
102
  };
81
103
 
82
104
  render() {
@@ -89,6 +111,7 @@ export default class LocalitiesAutocomplete extends Component {
89
111
  isClearable
90
112
  loadOptions={this.loadLocalities}
91
113
  onChange={this.onChange}
114
+ onInputChange={this.onInputChange}
92
115
  placeholder={placeholder}
93
116
  onBlur={() => this.setState({ menuIsOpen: false })}
94
117
  menuIsOpen={menuIsOpen}
@@ -105,6 +128,8 @@ LocalitiesAutocomplete.defaultProps = {
105
128
  components: null,
106
129
  types: null,
107
130
  inputRef: null,
131
+ inputChangeCb: null,
132
+ responseCb: null,
108
133
  };
109
134
  LocalitiesAutocomplete.propTypes = {
110
135
  woosmapKey: PropTypes.string.isRequired,
@@ -115,4 +140,6 @@ LocalitiesAutocomplete.propTypes = {
115
140
  language: PropTypes.string,
116
141
  callback: PropTypes.func.isRequired,
117
142
  defaultLocality: PropTypes.object,
143
+ inputChangeCb: PropTypes.func,
144
+ responseCb: PropTypes.func,
118
145
  };
@@ -1,70 +0,0 @@
1
- /* eslint-disable prefer-destructuring */
2
- import React from 'react';
3
- import { render, fireEvent, screen } from '@testing-library/react';
4
- import '@testing-library/jest-dom/extend-expect';
5
-
6
- import LocalitiesDemo from './LocalitiesDemo';
7
-
8
- it('renders a LocalitiesDemo', () => {
9
- const { container } = render(<LocalitiesDemo />);
10
- expect(container.firstChild).toHaveClass('demo');
11
- });
12
-
13
- it('test Type in', () => {
14
- const { container } = render(<LocalitiesDemo />);
15
- const input = container.querySelectorAll('input')[0];
16
- fireEvent.focus(input);
17
- fireEvent.change(input, { target: { value: 'Paris' } });
18
- expect(screen.getByText('input=Paris&\\')).toBeVisible();
19
- });
20
-
21
- it('test Filter by', () => {
22
- const { container } = render(<LocalitiesDemo />);
23
- const input = container.querySelectorAll('input')[1];
24
- fireEvent.focus(input);
25
- fireEvent.keyDown(input, { key: 'ArrowDown', code: 40 });
26
- fireEvent.keyDown(input, { key: 'ArrowDown', code: 40 });
27
- fireEvent.keyDown(input, { key: 'ArrowDown', code: 40 });
28
- fireEvent.click(screen.getByText('France'));
29
- expect(screen.getByText('components=country:fr"')).toBeVisible();
30
- });
31
-
32
- it('test language button', () => {
33
- render(<LocalitiesDemo />);
34
- expect(screen.getByText('language=gb&\\')).toBeVisible();
35
- fireEvent.click(screen.getByTitle('Italian'));
36
- expect(screen.getByText('language=it&\\')).toBeVisible();
37
- });
38
-
39
- it('test point of interest list', () => {
40
- const { container } = render(<LocalitiesDemo />);
41
- const input = container.querySelectorAll('input')[2];
42
- fireEvent.focus(input);
43
- fireEvent.keyDown(input, { key: 'ArrowDown', code: 40 });
44
- fireEvent.click(screen.getByText('Airports'));
45
- expect(screen.getByText('types=airport&\\')).toBeVisible();
46
- fireEvent.focus(input);
47
- fireEvent.keyDown(input, { key: 'ArrowDown', code: 40 });
48
- fireEvent.click(screen.getByText('Postal codes'));
49
- expect(screen.getByText('types=airport|postal_code&\\')).toBeVisible();
50
- fireEvent.keyDown(input, { key: 'ArrowDown', code: 40 });
51
- fireEvent.click(screen.getByText('Address'));
52
- expect(screen.getByText('types=airport|postal_code|address&\\')).toBeVisible();
53
- });
54
-
55
- it('test locality not found', () => {
56
- const { container } = render(<LocalitiesDemo />);
57
- const input = container.querySelectorAll('input')[0];
58
- fireEvent.focus(input);
59
- fireEvent.change(input, { target: { value: 'kjiuhlihijo' } });
60
- expect(screen.getByText('input=kjiuhlihijo&\\')).toBeVisible();
61
- expect(screen.getByText('No locality found')).toBeVisible();
62
- });
63
- it('test type address selected then only all countries to be selected by default', () => {
64
- const { container } = render(<LocalitiesDemo />);
65
- const input = container.querySelectorAll('input')[2];
66
- fireEvent.keyDown(input, { key: 'ArrowDown', code: 40 });
67
- fireEvent.click(screen.getByText('Address'));
68
- expect(screen.getByText('types=address&\\')).toBeVisible();
69
- expect(screen.getByText('components=country:fr|country:de|country:it|country:nl|country:gb"')).toBeVisible();
70
- });