@luomus/laji-form 15.1.69 → 15.1.70

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 (31) hide show
  1. package/dist/laji-form.js +1 -1
  2. package/lib/ApiClient.d.ts +66 -21
  3. package/lib/ApiClient.js +174 -68
  4. package/lib/components/LajiForm.js +1 -1
  5. package/lib/components/fields/AnnotationField.js +26 -22
  6. package/lib/components/fields/AudioArrayField.js +18 -5
  7. package/lib/components/fields/AutosuggestField.d.ts +2 -2
  8. package/lib/components/fields/AutosuggestField.js +4 -4
  9. package/lib/components/fields/EnumRangeArrayField.js +1 -1
  10. package/lib/components/fields/GeocoderField.js +67 -79
  11. package/lib/components/fields/ImageArrayField.d.ts +2 -2
  12. package/lib/components/fields/ImageArrayField.js +82 -112
  13. package/lib/components/fields/MapArrayField.js +13 -5
  14. package/lib/components/fields/MapField.d.ts +1 -1
  15. package/lib/components/fields/MapField.js +13 -5
  16. package/lib/components/fields/NamedPlaceChooserField/NamedPlaceChooser.d.ts +2 -1
  17. package/lib/components/fields/NamedPlaceChooserField/NamedPlaceChooserField.d.ts +9 -4
  18. package/lib/components/fields/NamedPlaceChooserField/NamedPlaceChooserField.js +51 -51
  19. package/lib/components/fields/NamedPlaceChooserField/Popup.d.ts +2 -1
  20. package/lib/components/fields/NamedPlaceSaverField.js +22 -18
  21. package/lib/components/fields/ScopeField.js +1 -1
  22. package/lib/components/fields/SingleActiveArrayField.js +2 -2
  23. package/lib/components/fields/SortArrayField.js +1 -1
  24. package/lib/components/fields/UnitCountShorthandField.js +1 -1
  25. package/lib/components/fields/UnitListShorthandArrayField.js +2 -2
  26. package/lib/components/fields/UnitShorthandField.js +14 -18
  27. package/lib/components/widgets/AutosuggestWidget.js +77 -66
  28. package/lib/components/widgets/InformalTaxonGroupChooserWidget.js +1 -1
  29. package/lib/components/widgets/InputWithDefaultValueButtonWidget.js +1 -2
  30. package/lib/validation.js +1 -1
  31. package/package.json +5 -3
@@ -38,6 +38,15 @@ var __importStar = (this && this.__importStar) || (function () {
38
38
  return result;
39
39
  };
40
40
  })();
41
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
42
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
43
+ return new (P || (P = Promise))(function (resolve, reject) {
44
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
45
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
46
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
47
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
48
+ });
49
+ };
41
50
  var __rest = (this && this.__rest) || function (s, e) {
42
51
  var t = {};
43
52
  for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
@@ -63,7 +72,7 @@ const components_1 = require("../components");
63
72
  const ReactContext_1 = __importDefault(require("../../ReactContext"));
64
73
  const InformalTaxonGroupChooserWidget_1 = require("./InformalTaxonGroupChooserWidget");
65
74
  function renderFlag(suggestion, prepend) {
66
- return (suggestion && suggestion.payload || {}).finnish
75
+ return (suggestion || {}).finnish
67
76
  ? React.createElement(React.Fragment, null,
68
77
  prepend || null,
69
78
  React.createElement("img", { src: "https://cdn.laji.fi/images/icons/flag_fi_small.png", width: "16" }))
@@ -73,10 +82,11 @@ class _AutosuggestWidget extends React.Component {
73
82
  render() {
74
83
  if (this.props.options) {
75
84
  switch (this.props.options.autosuggestField) {
85
+ case "taxa":
76
86
  case "taxon":
77
- return React.createElement(TaxonAutosuggestWidget, Object.assign({}, this.props));
87
+ return React.createElement(TaxonAutosuggestWidget, Object.assign({}, this.props, { options: Object.assign(Object.assign({}, this.props.options), { autosuggestField: "taxa" }) }));
78
88
  case "unit":
79
- return React.createElement(UnitAutosuggestWidget, Object.assign({}, this.props));
89
+ return React.createElement(UnitAutosuggestWidget, Object.assign({}, this.props, { basePath: "/shorthand/unit/trip-report" }));
80
90
  case "friends":
81
91
  case "person":
82
92
  return React.createElement(FriendsAutosuggestWidget, Object.assign({}, this.props));
@@ -97,6 +107,7 @@ class _AutosuggestWidget extends React.Component {
97
107
  let component = undefined;
98
108
  switch (options.autosuggestField) {
99
109
  case "taxon":
110
+ case "taxa":
100
111
  component = TaxonAutosuggestWidget;
101
112
  break;
102
113
  case "unit":
@@ -176,11 +187,7 @@ function TaxonAutosuggest(ComposedComponent) {
176
187
  orWriteSpeciesNameLabel || this.props.formContext.translations.orWriteSpeciesName))));
177
188
  };
178
189
  this.onTaxonImgSelected = (taxonID, taxon) => {
179
- this.autosuggestRef.selectSuggestion({
180
- key: taxonID,
181
- value: taxon.vernacularName,
182
- payload: taxon
183
- });
190
+ this.autosuggestRef.selectSuggestion(Object.assign({ key: taxonID, value: taxon.vernacularName }, taxon));
184
191
  };
185
192
  this.setAutosuggestRef = (elem) => {
186
193
  this.autosuggestRef = elem;
@@ -212,19 +219,20 @@ function TaxonAutosuggest(ComposedComponent) {
212
219
  }
213
220
  }
214
221
  getSuggestionFromValue(value) {
215
- if (this.isValueSuggested(value)) {
216
- return this.props.formContext.apiClient.fetchCached(`/taxa/${value}`).then(({ vernacularName, scientificName }) => {
222
+ return __awaiter(this, void 0, void 0, function* () {
223
+ if (this.isValueSuggested(value)) {
224
+ const { vernacularName, scientificName } = yield this.props.formContext.apiClient.get(`/taxa/${id}`);
217
225
  if (vernacularName !== undefined) {
218
226
  return { value: vernacularName, key: value };
219
227
  }
220
228
  if (scientificName !== undefined) {
221
229
  return { value: scientificName, key: value };
222
230
  }
223
- });
224
- }
225
- else {
226
- return Promise.reject();
227
- }
231
+ }
232
+ else {
233
+ throw new Error("Unknown taxon");
234
+ }
235
+ });
228
236
  }
229
237
  isValueSuggested(value) {
230
238
  return !(0, utils_1.isEmptyString)(value) && !!value.match(/MX\.\d+/);
@@ -272,10 +280,10 @@ let UnitAutosuggestWidget = class UnitAutosuggestWidget extends React.Component
272
280
  this.renderSuggestion = this.renderSuggestion.bind(this);
273
281
  }
274
282
  renderSuggestion(suggestion) {
275
- const { count, maleIndividualCount, femaleIndividualCount } = suggestion.payload.interpretedFrom;
283
+ const { count, maleIndividualCount, femaleIndividualCount } = suggestion.interpretedFrom;
276
284
  const [countElem, maleElem, femaleElem] = [count, maleIndividualCount, femaleIndividualCount].map(val => val && React.createElement("span", { className: "text-muted" }, val));
277
- const taxonName = suggestion.payload.unit.identifications[0].taxon;
278
- const name = suggestion.payload.isNonMatching
285
+ const taxonName = suggestion.unit.identifications[0].taxon;
286
+ const name = suggestion.isNonMatching
279
287
  ? React.createElement("span", { className: "text-muted" },
280
288
  taxonName,
281
289
  " ",
@@ -318,10 +326,11 @@ class FriendsAutosuggestWidget extends React.Component {
318
326
  this.isValueSuggested = this.isValueSuggested.bind(this);
319
327
  }
320
328
  getSuggestionFromValue(value) {
321
- const { showID } = (0, utils_1.getUiOptions)(this.props);
322
- const { isAdmin } = this.props.formContext.uiSchemaContext;
323
- if (this.isValueSuggested(value)) {
324
- return this.props.formContext.apiClient.fetchCached(`/person/by-id/${value}`).then(({ fullName, group, id }) => {
329
+ return __awaiter(this, void 0, void 0, function* () {
330
+ const { showID } = (0, utils_1.getUiOptions)(this.props);
331
+ const { isAdmin } = this.props.formContext.uiSchemaContext;
332
+ if (this.isValueSuggested(value)) {
333
+ const { fullName, group, id } = yield this.props.formContext.apiClient.get(`/person/by-id/${value}`);
325
334
  if (fullName) {
326
335
  const addGroup = str => group ? `${str} (${group})` : str;
327
336
  const addID = str => isAdmin && showID ? `${str} (${id})` : str;
@@ -330,11 +339,11 @@ class FriendsAutosuggestWidget extends React.Component {
330
339
  key: value
331
340
  };
332
341
  }
333
- });
334
- }
335
- else {
336
- return Promise.reject();
337
- }
342
+ }
343
+ else {
344
+ throw new Error("Unknown friend");
345
+ }
346
+ });
338
347
  }
339
348
  isValueSuggested(value) {
340
349
  return !(0, utils_1.isEmptyString)(value) && value.match(/MA\.\d+/);
@@ -382,22 +391,21 @@ class BasicAutosuggestWidget extends React.Component {
382
391
  this.isValueSuggested = this.isValueSuggested.bind(this);
383
392
  }
384
393
  getSuggestionFromValue(value) {
385
- const { autosuggestField, nameField } = this.props;
386
- if (this.isValueSuggested(value)) {
387
- const apiClient = this.props.formContext.apiClient;
388
- const fetch = (this.props.cache ? apiClient.fetchCached : apiClient.fetch).bind(apiClient);
389
- return fetch(`/${autosuggestField}/by-id/${value}`).then(result => {
394
+ return __awaiter(this, void 0, void 0, function* () {
395
+ const { autosuggestField, nameField } = this.props;
396
+ if (this.isValueSuggested(value)) {
397
+ const result = yield this.props.formContext.apiClient.get(`/${autosuggestField}/by-id/${value}`, undefined, this.props.cache);
390
398
  if (result[nameField]) {
391
399
  return {
392
400
  value: result[nameField],
393
401
  key: value
394
402
  };
395
403
  }
396
- });
397
- }
398
- else {
399
- return Promise.reject();
400
- }
404
+ }
405
+ else {
406
+ throw new Error("Not autosuggested value");
407
+ }
408
+ });
401
409
  }
402
410
  isValueSuggested(value) {
403
411
  const regexp = new RegExp(this.props.validValueRegexp);
@@ -536,12 +544,12 @@ class Autosuggest extends React.Component {
536
544
  const { findExactMatch } = this.props;
537
545
  return findExactMatch
538
546
  ? findExactMatch(suggestions, inputValue)
539
- : suggestions.find(suggestion => (suggestion && suggestion.value.toLowerCase() === inputValue.trim().toLowerCase() && (!suggestion.payload || !suggestion.payload.isNonMatching)));
547
+ : suggestions.find(suggestion => (suggestion && suggestion.value.toLowerCase() === inputValue.trim().toLowerCase() && !suggestion.isNonMatching));
540
548
  };
541
549
  this.findTheOnlyOneMatch = (suggestions) => {
542
550
  if (!Array.isArray(suggestions))
543
551
  suggestions = [suggestions];
544
- const filtered = suggestions.filter(suggestion => !suggestion || !suggestion.payload || !suggestion.payload.isNonMatching);
552
+ const filtered = suggestions.filter(suggestion => !suggestion || !suggestion.isNonMatching);
545
553
  if (filtered.length === 1) {
546
554
  return filtered[0];
547
555
  }
@@ -549,7 +557,7 @@ class Autosuggest extends React.Component {
549
557
  this.findNonMatching = (suggestions) => {
550
558
  if (!Array.isArray(suggestions))
551
559
  suggestions = [suggestions];
552
- const filtered = suggestions.filter(suggestion => suggestion && suggestion.payload && suggestion.payload.isNonMatching);
560
+ const filtered = suggestions.filter(suggestion => suggestion && suggestion.isNonMatching);
553
561
  if (filtered.length === 1) {
554
562
  return filtered[0];
555
563
  }
@@ -572,11 +580,13 @@ class Autosuggest extends React.Component {
572
580
  }
573
581
  const { autosuggestField, query = {} } = this.props;
574
582
  this.setState({ isLoading: true });
575
- const request = () => {
583
+ const request = () => __awaiter(this, void 0, void 0, function* () {
576
584
  let timestamp = Date.now();
577
585
  this.promiseTimestamp = timestamp;
578
- const fetch = (this.props.cache ? this.apiClient.fetchCached : this.apiClient.fetch).bind(this.apiClient);
579
- fetch("/autocomplete/" + autosuggestField, Object.assign({ q: value, includePayload: true, matchType: "exact,partial", includeHidden: false }, query)).then(suggestions => {
586
+ try {
587
+ let suggestionsResponse = yield this.apiClient.get(this.props.basePath || "/autocomplete" + "/" + autosuggestField, { query: Object.assign({ query: value, matchType: "exact,partial", includeHidden: false }, query) }, this.props.cache);
588
+ // Hack for laji-form in Kotka, since it uses API that isn't laji-api and might have different response signature.
589
+ let suggestions = Array.isArray(suggestionsResponse) ? suggestionsResponse : suggestionsResponse.results;
580
590
  if (this.props.prepareSuggestion) {
581
591
  suggestions = suggestions.map(s => this.props.prepareSuggestion(s));
582
592
  }
@@ -586,12 +596,13 @@ class Autosuggest extends React.Component {
586
596
  this.mounted
587
597
  ? this.setState({ isLoading: false, suggestions }, () => this.afterBlurAndFetch(suggestions))
588
598
  : this.afterBlurAndFetch(suggestions);
589
- }).catch(() => {
599
+ }
600
+ catch (e) {
590
601
  this.mounted
591
602
  ? this.setState({ isLoading: false }, this.afterBlurAndFetch)
592
603
  : this.afterBlurAndFetch();
593
- });
594
- };
604
+ }
605
+ });
595
606
  if (this.timeout) {
596
607
  clearTimeout(this.timeout);
597
608
  }
@@ -802,10 +813,10 @@ class Autosuggest extends React.Component {
802
813
  ? this.props.highlightFirstSuggestion
803
814
  : !this.props.allowNonsuggestedValue;
804
815
  const { renderExtra } = props;
805
- return (React.createElement(React.Fragment, null,
816
+ return React.createElement(React.Fragment, null,
806
817
  renderExtra && renderExtra(),
807
818
  React.createElement("div", { className: (0, utils_1.classNames)("autosuggest-wrapper", props.wrapperClassName) },
808
- React.createElement(ReactAutosuggest, { id: `${this.props.id}-autosuggest`, inputProps: inputProps, renderInputComponent: this.renderInput, suggestions: suggestions, renderSuggestion: this.renderSuggestion, onSuggestionsFetchRequested: this.onSuggestionsFetchRequested, onSuggestionsClearRequested: this.onSuggestionsClearRequested, onSuggestionSelected: this.onSuggestionSelected, onUnsuggestedSelected: this.onUnsuggestedSelected, highlightFirstSuggestion: highlightFirstSuggestion, suggestionsOpenOnFocus: !this.isSuggested(), theme: cssClasses, formContext: this.props.formContext, ref: this.setRef }))));
819
+ React.createElement(ReactAutosuggest, { id: `${this.props.id}-autosuggest`, inputProps: inputProps, renderInputComponent: this.renderInput, suggestions: suggestions, renderSuggestion: this.renderSuggestion, onSuggestionsFetchRequested: this.onSuggestionsFetchRequested, onSuggestionsClearRequested: this.onSuggestionsClearRequested, onSuggestionSelected: this.onSuggestionSelected, onUnsuggestedSelected: this.onUnsuggestedSelected, highlightFirstSuggestion: highlightFirstSuggestion, suggestionsOpenOnFocus: !this.isSuggested(), theme: cssClasses, formContext: this.props.formContext, ref: this.setRef })));
809
820
  }
810
821
  }
811
822
  exports.Autosuggest = Autosuggest;
@@ -842,21 +853,22 @@ class _TaxonWrapper extends React.Component {
842
853
  this.fetch(value);
843
854
  }
844
855
  fetch(value) {
845
- if (!value) {
846
- this.setState({ scientificName: "", cursiveName: false });
847
- }
848
- else {
849
- this.props.formContext.apiClient.fetchCached(`/taxa/${value}`).then(({ scientificName, cursiveName, vernacularName, taxonRank, informalTaxonGroups, finnish }) => {
850
- if (!this.mounted)
851
- return;
852
- this.setState({ value, taxonRank, informalTaxonGroups, taxon: { scientificName, vernacularName, cursiveName, finnish } });
853
- (0, InformalTaxonGroupChooserWidget_1.getInformalGroups)(this.props.formContext.apiClient).then(({ informalTaxonGroupsById }) => {
856
+ return __awaiter(this, void 0, void 0, function* () {
857
+ if (!value) {
858
+ this.setState({ scientificName: "", cursiveName: false });
859
+ }
860
+ else {
861
+ this.props.formContext.apiClient.get(`/taxa/${value}`).then(({ scientificName, cursiveName, vernacularName, taxonRank, informalGroups, finnish }) => {
854
862
  if (!this.mounted)
855
863
  return;
856
- this.setState({ informalTaxonGroupsById });
864
+ this.setState({ value, taxonRank, informalTaxonGroups: informalGroups, taxon: { scientificName, vernacularName, cursiveName, finnish } });
865
+ (0, InformalTaxonGroupChooserWidget_1.getInformalGroups)(this.props.formContext.apiClient).then(({ informalTaxonGroupsById }) => {
866
+ if (!this.mounted)
867
+ return;
868
+ this.setState({ informalTaxonGroupsById });
869
+ });
857
870
  });
858
- });
859
- this.props.formContext.apiClient.fetchCached(`/taxa/${value}/parents`).then(parents => {
871
+ const parents = (yield this.props.formContext.apiClient.get(`/taxa/${value}/parents`)).results;
860
872
  if (!this.mounted)
861
873
  return;
862
874
  const state = { order: undefined, family: undefined };
@@ -874,13 +886,12 @@ class _TaxonWrapper extends React.Component {
874
886
  }
875
887
  }
876
888
  this.setState(Object.assign(Object.assign({}, state), { higherThanOrder: !state.order && !state.family }));
877
- });
878
- this.props.formContext.apiClient.fetchCached("/metadata/ranges/MX.taxonRankEnum").then(taxonRanks => {
889
+ const taxonRanks = yield this.props.formContext.apiClient.get("/metadata/ranges/MX.taxonRankEnum");
879
890
  if (!this.mounted)
880
891
  return;
881
892
  this.setState({ taxonRanks: (0, utils_1.dictionarify)(taxonRanks, function getKey(rank) { return rank.id; }, function getValue(rank) { return rank.value; }) });
882
- });
883
- }
893
+ }
894
+ });
884
895
  }
885
896
  render() {
886
897
  const { id, formContext, value, children, placement, inputValue, isSuggested } = this.props;
@@ -995,7 +1006,7 @@ class TaxonImgChooser extends React.Component {
995
1006
  }
996
1007
  componentDidMount() {
997
1008
  this.mounted = true;
998
- this.props.formContext.apiClient.fetchCached(`/taxa/${this.props.id}`, { includeMedia: true }).then(taxon => {
1009
+ this.props.formContext.apiClient.get(`/taxa/${this.props.id}`, { query: { includeMedia: true } }).then(taxon => {
999
1010
  if (!this.mounted)
1000
1011
  return;
1001
1012
  if (taxon.multimedia && taxon.multimedia.length) {
@@ -1042,7 +1053,7 @@ const TaxonName = ({ scientificName, vernacularName = "", cursiveName, finnish }
1042
1053
  return (React.createElement(React.Fragment, null,
1043
1054
  `${vernacularName}${vernacularName ? " " : ""}`,
1044
1055
  cursiveName ? React.createElement("i", null, _scientificName) : _scientificName,
1045
- renderFlag({ payload: { finnish } }, " ")));
1056
+ renderFlag({ finnish }, " ")));
1046
1057
  };
1047
1058
  class ReactAutosuggest extends React.Component {
1048
1059
  constructor(props) {
@@ -161,7 +161,7 @@ function getInformalGroups(apiClient) {
161
161
  if (informalTaxonGroups) {
162
162
  return Promise.resolve({ informalTaxonGroups, informalTaxonGroupsById });
163
163
  }
164
- return apiClient.fetchCached("/informal-taxon-groups/tree").then(response => {
164
+ return apiClient.get("/informal-taxon-groups/tree").then(response => {
165
165
  // Contains the current taxon group.
166
166
  const informalTaxonGroups = mapGroupsById(response.results);
167
167
  // Contains all groups in flat object.
@@ -54,9 +54,8 @@ class InputWithDefaultValueButtonWidget extends React.Component {
54
54
  else if (apiQueryForDefaultValue) {
55
55
  const { path, query = {}, resultKey, cache = false } = apiQueryForDefaultValue;
56
56
  const apiClient = this.props.formContext.apiClient;
57
- const fetch = (cache ? apiClient.fetchCached : apiClient.fetch).bind(apiClient);
58
57
  this.setState({ fetching: true });
59
- return fetch(path, query).then(result => {
58
+ return apiClient.get(path, { query }, cache).then(result => {
60
59
  if (result[resultKey]) {
61
60
  this.props.onChange(result[resultKey]);
62
61
  }
package/lib/validation.js CHANGED
@@ -39,7 +39,7 @@ exports.toErrorSchema = toErrorSchema;
39
39
  const lajiValidate = __importStar(require("@luomus/laji-validate"));
40
40
  function initializeValidation(apiClient) {
41
41
  lajiValidate.extend(lajiValidate.validators.remote, {
42
- fetch: (...params) => apiClient.fetchRaw(...params)
42
+ fetch: (...params) => apiClient.apiClient.fetch(...params)
43
43
  });
44
44
  }
45
45
  exports.default = ({ errors: error, warnings: warning }, data) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luomus/laji-form",
3
- "version": "15.1.69",
3
+ "version": "15.1.70",
4
4
  "description": "React module capable of building dynamic forms from Laji form json schemas",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -28,7 +28,8 @@
28
28
  "test:docker": "npm run test:docker:build && npm run test:docker:run --",
29
29
  "test:lightweight": "npx playwright test --project chromium",
30
30
  "test:docker:build": "docker build -t laji-form-test -f test.Dockerfile .",
31
- "test:docker:run": "docker run laji-form-test"
31
+ "test:docker:run": "docker run laji-form-test",
32
+ "generate:api-client": "openapi-typescript https://apitest.laji.fi/explorer-json -o ./generated/api.d.ts --properties-required-by-default"
32
33
  },
33
34
  "keywords": [
34
35
  "react-jsonschema-form",
@@ -71,7 +72,7 @@
71
72
  "@playwright/test": "^1.40.1",
72
73
  "@stylistic/eslint-plugin": "^5.4.0",
73
74
  "@types/jasmine": "^3.7.7",
74
- "@types/node": "^20.17.0",
75
+ "@types/node": "^24.5.2",
75
76
  "@typescript-eslint/eslint-plugin": "^8.44.1",
76
77
  "@typescript-eslint/parser": "^8.44.1",
77
78
  "copy-webpack-plugin": "^9.0.1",
@@ -82,6 +83,7 @@
82
83
  "eslint-plugin-react-hooks": "^5.2.0",
83
84
  "mini-css-extract-plugin": "^2.1.0",
84
85
  "notus": "^0.3.2",
86
+ "openapi-typescript": "^7.9.1",
85
87
  "querystring": "^0.2.1",
86
88
  "rimraf": "^2.5.4",
87
89
  "style-loader": "^3.0.0",