@luomus/laji-form 15.1.69 → 15.1.71

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 (39) hide show
  1. package/dist/laji-form.js +1 -1
  2. package/dist/styles.css +2 -2
  3. package/lib/ApiClient.d.ts +66 -21
  4. package/lib/ApiClient.js +174 -68
  5. package/lib/components/LajiForm.d.ts +10 -2
  6. package/lib/components/LajiForm.js +1 -1
  7. package/lib/components/fields/AnnotationField.js +26 -22
  8. package/lib/components/fields/AudioArrayField.js +18 -5
  9. package/lib/components/fields/AutosuggestField.d.ts +2 -2
  10. package/lib/components/fields/AutosuggestField.js +4 -4
  11. package/lib/components/fields/CondensedObjectField.js +3 -5
  12. package/lib/components/fields/EnumRangeArrayField.js +1 -1
  13. package/lib/components/fields/GeocoderField.js +67 -79
  14. package/lib/components/fields/ImageArrayField.d.ts +9 -5
  15. package/lib/components/fields/ImageArrayField.js +103 -122
  16. package/lib/components/fields/MapArrayField.js +13 -5
  17. package/lib/components/fields/MapField.d.ts +1 -1
  18. package/lib/components/fields/MapField.js +13 -5
  19. package/lib/components/fields/NamedPlaceChooserField/NamedPlaceChooser.d.ts +2 -1
  20. package/lib/components/fields/NamedPlaceChooserField/NamedPlaceChooserField.d.ts +9 -4
  21. package/lib/components/fields/NamedPlaceChooserField/NamedPlaceChooserField.js +51 -51
  22. package/lib/components/fields/NamedPlaceChooserField/Popup.d.ts +2 -1
  23. package/lib/components/fields/NamedPlaceSaverField.js +22 -18
  24. package/lib/components/fields/PrefixArrayField.js +3 -3
  25. package/lib/components/fields/ScopeField.js +1 -1
  26. package/lib/components/fields/SectionArrayField.js +1 -1
  27. package/lib/components/fields/SingleActiveArrayField.js +7 -7
  28. package/lib/components/fields/SortArrayField.js +1 -1
  29. package/lib/components/fields/TableField.js +2 -2
  30. package/lib/components/fields/UnitCountShorthandField.js +1 -1
  31. package/lib/components/fields/UnitListShorthandArrayField.js +2 -2
  32. package/lib/components/fields/UnitShorthandField.js +14 -18
  33. package/lib/components/templates/ArrayFieldTemplate.js +2 -2
  34. package/lib/components/widgets/AutosuggestWidget.js +77 -66
  35. package/lib/components/widgets/InformalTaxonGroupChooserWidget.js +1 -1
  36. package/lib/components/widgets/InputWithDefaultValueButtonWidget.js +1 -2
  37. package/lib/themes/bs5.js +10 -2
  38. package/lib/validation.js +1 -1
  39. package/package.json +5 -3
package/dist/styles.css CHANGED
@@ -2149,7 +2149,7 @@ body .laji-form {
2149
2149
  margin-bottom: 6px;
2150
2150
  }
2151
2151
 
2152
- .laji-form button {
2152
+ .laji-form button, .laji-form .page-link {
2153
2153
  line-height: 14px;
2154
2154
  white-space: normal;
2155
2155
  }
@@ -2685,7 +2685,7 @@ body .laji-form {
2685
2685
  padding: 10px 15px;
2686
2686
  color: rgb(51, 51, 51);
2687
2687
  }
2688
- .laji-form .laji-form-panel .panel-heading, .laji-form .pager {
2688
+ .laji-form .laji-form-panel .panel-heading, .laji-form .pager, .laji-form .pagination {
2689
2689
  padding: 0;
2690
2690
  margin: 0;
2691
2691
  }
@@ -1,47 +1,92 @@
1
1
  import { Translations } from "./components/LajiForm";
2
2
  import { Lang } from "./types";
3
+ import type { paths } from "generated/api";
4
+ export declare class LajiApiError extends Error {
5
+ statusCode: number;
6
+ constructor(message: string, statusCode: number);
7
+ }
3
8
  interface Query {
4
9
  [param: string]: any;
5
10
  }
6
- interface Options {
7
- failSilently?: boolean;
8
- [option: string]: any;
9
- }
10
11
  export interface ApiClientImplementation {
11
- fetch: (path: string, query: Query, options: any) => Promise<any>;
12
+ fetch: (path: string, query: Query, options: any) => Promise<Response>;
12
13
  }
14
+ type RelaxQuery<P, K extends string> = P extends {
15
+ query: infer Q;
16
+ } ? Omit<P, "query"> & (Record<string, unknown> extends Omit<Q, Extract<keyof Q, K>> ? {
17
+ query?: Omit<Q, Extract<keyof Q, K>> & Partial<Pick<Q, Extract<keyof Q, K>>>;
18
+ } : {
19
+ query: Omit<Q, Extract<keyof Q, K>> & Partial<Pick<Q, Extract<keyof Q, K>>>;
20
+ }) : P;
21
+ type MiddlewareInjectedKeys = "collectionID" | "formID" | "personToken";
22
+ type Parameters<T> = "parameters" extends keyof T ? T["parameters"] : never;
23
+ type ExtractContentIfExists<R> = R extends {
24
+ content: infer C;
25
+ } ? C[keyof C] : null;
26
+ type ExtractRequestBodyIfExists<R> = R extends {
27
+ requestBody: {
28
+ content: infer C;
29
+ };
30
+ } | {
31
+ requestBody?: {
32
+ content: infer C;
33
+ };
34
+ } ? C[keyof C] : never;
35
+ type HttpSuccessCodes = 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226;
36
+ type IntersectUnionTypes<A, B> = A extends B ? A : never;
37
+ type WithResponses<T> = T & {
38
+ responses: unknown;
39
+ };
40
+ type Path = keyof paths & string;
41
+ type PathWithMethod<M extends string> = keyof {
42
+ [P in keyof paths as paths[P] extends Record<M, any> ? P : never]: paths[P];
43
+ };
44
+ type Method<P extends Path> = Exclude<keyof {
45
+ [K in keyof paths[P] as paths[P][K] extends undefined | never ? never : K]: paths[P][K];
46
+ }, "parameters">;
47
+ type Responses<P extends Path, M extends Method<P>> = WithResponses<paths[P][M]>["responses"];
48
+ type CacheNode = {
49
+ branches: Map<string, CacheNode>;
50
+ queries: Map<string, Promise<any>>;
51
+ path: string;
52
+ };
13
53
  /**
14
- * ApiClient "interface". Wraps the given apiClient as a singleton object.
15
- * Given apiClient must implement fetch().
54
+ * ApiClient with automatically generated typings for laji-api. An `ApiClientImplementation` must be provided, that will
55
+ * perform the actual requests. The implementation acts as a middleware for injecting access token and person token.
56
+ *
57
+ * Get requests are automatically cached, and they will flush if the resource receives a POST/PUT/DELETE request.
16
58
  */
17
59
  export default class ApiClient {
18
60
  apiClient: ApiClientImplementation;
19
61
  lang: Lang;
20
62
  translations: Translations;
21
- cache: {
22
- [path: string]: {
23
- [cacheKey: string]: Promise<any>;
24
- };
25
- };
63
+ cacheTree: CacheNode;
26
64
  on: {
27
65
  [path: string]: (() => void)[];
28
66
  };
29
67
  constructor(apiClient: ApiClientImplementation, lang: Lang | undefined, translations: Translations);
68
+ private flush;
69
+ private getCacheNode;
70
+ /** The response is cached until the resource receives a POST/PUT/DELETE request. */
71
+ get<P extends PathWithMethod<"get">, R extends Responses<P, "get" extends Method<P> ? "get" : never>>(path: P, params?: RelaxQuery<Parameters<paths[P]["get"]>, MiddlewareInjectedKeys>,
72
+ /** Defaults to true */
73
+ useCache?: boolean): Promise<ExtractContentIfExists<R[IntersectUnionTypes<keyof R, HttpSuccessCodes>]>>;
74
+ /** Subscribe to a GET request. It re-emits when the resource receives a POST/PUT/DELETE request. */
75
+ subscribe<P extends PathWithMethod<"get">, R extends Responses<P, "get" extends Method<P> ? "get" : never>>(path: P, params: RelaxQuery<Parameters<paths[P]["get"]>, MiddlewareInjectedKeys> | undefined, onFulfilled: (response: ExtractContentIfExists<R[IntersectUnionTypes<keyof R, HttpSuccessCodes>]>) => void, onError?: () => void,
76
+ /**
77
+ * Store doesn't sync it's search indices right away after data is changed, so delaying the request can be useful
78
+ */
79
+ delay?: number): () => void;
80
+ put<P extends PathWithMethod<"put">, R extends Responses<P, "put" extends Method<P> ? "put" : never>>(path: P, params?: RelaxQuery<Parameters<paths[P]["put"]>, MiddlewareInjectedKeys>, body?: ExtractRequestBodyIfExists<paths[P]["put"]>): Promise<ExtractContentIfExists<R[IntersectUnionTypes<keyof R, HttpSuccessCodes>]>>;
81
+ post<P extends PathWithMethod<"post">, R extends Responses<P, "post" extends Method<P> ? "post" : never>>(path: P, params?: RelaxQuery<Parameters<paths[P]["post"]>, MiddlewareInjectedKeys>, body?: ExtractRequestBodyIfExists<paths[P]["post"]>): Promise<ExtractContentIfExists<R[IntersectUnionTypes<keyof R, HttpSuccessCodes>]>>;
82
+ delete<P extends PathWithMethod<"delete">, R extends Responses<P, "delete" extends Method<P> ? "delete" : never>>(path: P, params?: RelaxQuery<Parameters<paths[P]["delete"]>, MiddlewareInjectedKeys>): Promise<ExtractContentIfExists<R[IntersectUnionTypes<keyof R, HttpSuccessCodes>]>>;
30
83
  /**
31
84
  * Implementing apiClient must return a promise that passes the raw response as 1st arg.
32
85
  * @param path URL for GET.
33
86
  * @param query Object, where keys are param names and values are param values.
34
87
  * @returns a Promise.
35
88
  */
36
- fetchRaw(path: string, query?: Query, options?: any): Promise<any>;
37
- fetch<T>(path: string, query?: Query, options?: Options): Promise<T>;
38
- getCacheKey(query?: Query, options?: Options): string;
39
- fetchCached<T>(path: string, query?: Query, options?: Options): Promise<T>;
40
- invalidateCachePath(path: string): void;
41
- invalidateCachePathQuery(path: string, query: string): void;
42
- flushCache: () => void;
43
- onCachePathInvalidation(path: string, callback: () => void): void;
44
- removeOnCachePathInvalidation(path: string, callback: () => void): void;
89
+ fetch<P extends Path, M extends Method<P>, R extends Responses<P, M>>(path: P, method: M, params?: Parameters<paths[P][M]>, body?: ExtractRequestBodyIfExists<paths[P][M]>, options?: any): Promise<ExtractContentIfExists<R[IntersectUnionTypes<keyof R, HttpSuccessCodes>]>>;
45
90
  setLang(lang: Lang): void;
46
91
  }
47
92
  export {};
package/lib/ApiClient.js CHANGED
@@ -1,96 +1,202 @@
1
1
  "use strict";
2
- var __rest = (this && this.__rest) || function (s, e) {
3
- var t = {};
4
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
- t[p] = s[p];
6
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
- t[p[i]] = s[p[i]];
10
- }
11
- return t;
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
12
10
  };
13
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.LajiApiError = void 0;
13
+ class LajiApiError extends Error {
14
+ constructor(message, statusCode) {
15
+ super(message);
16
+ this.statusCode = statusCode;
17
+ }
18
+ }
19
+ exports.LajiApiError = LajiApiError;
20
+ const splitAndResolvePath = (path, params) => {
21
+ // parse path into segments based on path parameters
22
+ // eg. "/person/{personToken}/profile" -> ["/person", "/{personToken}", "/profile"]
23
+ const segments = path.split(/(\/\{[^}]+\})/).filter(Boolean);
24
+ if (!params) {
25
+ return segments;
26
+ }
27
+ // resolve path parameters (eg. replace '/{personToken}' with params.path.personToken)
28
+ for (let i = 1; i < segments.length; i++) {
29
+ const segment = segments[i];
30
+ segments[i] = segment.replace(/\{([^}]+)\}/, (match, p1) => { var _a; return (_a = params["path"][p1]) !== null && _a !== void 0 ? _a : ""; });
31
+ }
32
+ return segments;
33
+ };
14
34
  /**
15
- * ApiClient "interface". Wraps the given apiClient as a singleton object.
16
- * Given apiClient must implement fetch().
35
+ * ApiClient with automatically generated typings for laji-api. An `ApiClientImplementation` must be provided, that will
36
+ * perform the actual requests. The implementation acts as a middleware for injecting access token and person token.
37
+ *
38
+ * Get requests are automatically cached, and they will flush if the resource receives a POST/PUT/DELETE request.
17
39
  */
18
40
  class ApiClient {
19
41
  constructor(apiClient, lang = "en", translations) {
20
- this.cache = {};
42
+ this.lang = "en";
43
+ this.cacheTree = { branches: new Map(), queries: new Map(), path: "" };
21
44
  this.on = {};
22
- this.flushCache = () => {
23
- this.cache = {};
24
- };
25
45
  this.apiClient = apiClient;
26
46
  this.lang = lang;
27
47
  this.translations = translations;
28
48
  }
29
- /**
30
- * Implementing apiClient must return a promise that passes the raw response as 1st arg.
31
- * @param path URL for GET.
32
- * @param query Object, where keys are param names and values are param values.
33
- * @returns a Promise.
34
- */
35
- fetchRaw(path, query, options) {
36
- const _query = Object.assign({ lang: this.lang }, (query || {}));
37
- if (!_query.lang) {
38
- delete _query.lang;
49
+ flush(path, params) {
50
+ var _a;
51
+ const segments = splitAndResolvePath(path, params);
52
+ let node = this.cacheTree;
53
+ const nodesToFlush = [];
54
+ for (let i = 0; i < segments.length; i++) {
55
+ const segment = segments[i];
56
+ if (node.branches.has(segment)) {
57
+ node = node.branches.get(segment);
58
+ nodesToFlush.push(node);
59
+ }
60
+ else {
61
+ break;
62
+ }
63
+ }
64
+ for (let i = 0; i < nodesToFlush.length; i++) {
65
+ const node = nodesToFlush[i];
66
+ node.queries = new Map();
67
+ (_a = this.on[node.path]) === null || _a === void 0 ? void 0 : _a.forEach(fn => fn());
39
68
  }
40
- return this.apiClient.fetch(path, _query, options).catch(() => {
41
- throw new Error(this.translations[this.lang].RequestFailed);
42
- });
43
69
  }
44
- fetch(path, query, options = {}) {
45
- const { failSilently = false } = options, fetchOptions = __rest(options, ["failSilently"]);
46
- return this.fetchRaw(path, query, fetchOptions).then(response => {
47
- if (!failSilently && response.status >= 400) {
48
- throw new Error("Request failed");
70
+ getCacheNode(path, params) {
71
+ const segments = splitAndResolvePath(path, params);
72
+ const serializedQuery = hashRecord((params === null || params === void 0 ? void 0 : params.query) || {});
73
+ let node = this.cacheTree;
74
+ for (let i = 0; i < segments.length; i++) {
75
+ const segment = segments[i];
76
+ if (node.branches.has(segment)) {
77
+ node = node.branches.get(segment);
49
78
  }
50
- if (response.status === 204) {
51
- return undefined;
79
+ else {
80
+ const newNode = { branches: new Map(), queries: new Map(), path: segments.join("") };
81
+ node.branches.set(segment, newNode);
82
+ node = newNode;
83
+ }
84
+ if (i === segments.length - 1) {
85
+ continue;
86
+ }
87
+ if (node.queries.has(serializedQuery)) {
88
+ return node;
52
89
  }
53
- return response.json();
54
- }).catch(() => {
55
- if (this.cache[path])
56
- delete this.cache[path][this.getCacheKey(query, options)];
57
- throw new Error(this.translations[this.lang].RequestFailed);
58
- });
59
- }
60
- getCacheKey(query, options) {
61
- return JSON.stringify(query) + JSON.stringify(options);
62
- }
63
- fetchCached(path, query, options) {
64
- const cacheKey = this.getCacheKey(query, options);
65
- if (!this.cache[path])
66
- this.cache[path] = {};
67
- this.cache[path][cacheKey] = cacheKey in this.cache[path] ? this.cache[path][cacheKey] : this.fetch(path, query, options);
68
- return this.cache[path][cacheKey];
69
- }
70
- invalidateCachePath(path) {
71
- delete this.cache[path];
72
- if (this.on[path]) {
73
- this.on[path].forEach(callback => callback());
74
90
  }
91
+ return node;
75
92
  }
76
- //TODO on invalidation callbacks
77
- invalidateCachePathQuery(path, query) {
78
- if (this.cache[path])
79
- delete this.cache[path][query];
93
+ ;
94
+ /** The response is cached until the resource receives a POST/PUT/DELETE request. */
95
+ get(path_1, params_1) {
96
+ return __awaiter(this, arguments, void 0, function* (path, params,
97
+ /** Defaults to true */
98
+ useCache = true) {
99
+ let cacheNode;
100
+ let serializedQuery;
101
+ if (useCache) {
102
+ serializedQuery = hashRecord(params === null || params === void 0 ? void 0 : params.query);
103
+ cacheNode = this.getCacheNode(path, params);
104
+ if (cacheNode.queries.has(serializedQuery)) {
105
+ return cacheNode.queries.get(serializedQuery);
106
+ }
107
+ }
108
+ const result = this.fetch(path, "get", params);
109
+ if (useCache) {
110
+ cacheNode.queries = cacheNode.queries;
111
+ cacheNode.queries.set(serializedQuery, result);
112
+ }
113
+ return result;
114
+ });
80
115
  }
81
- onCachePathInvalidation(path, callback) {
116
+ /** Subscribe to a GET request. It re-emits when the resource receives a POST/PUT/DELETE request. */
117
+ subscribe(path, params, onFulfilled, onError,
118
+ /**
119
+ * Store doesn't sync it's search indices right away after data is changed, so delaying the request can be useful
120
+ */
121
+ delay) {
82
122
  if (!this.on[path])
83
123
  this.on[path] = [];
84
- this.on[path].push(callback);
124
+ const fn = delay
125
+ ? () => { setTimeout(() => this.get(path, params).then(onFulfilled, onError), delay); }
126
+ : () => { this.get(path, params).then(onFulfilled, onError); };
127
+ this.on[path].push(fn);
128
+ this.get(path, params).then(onFulfilled, onError);
129
+ return () => {
130
+ this.on[path] = this.on[path].filter(_fn => _fn !== fn);
131
+ };
85
132
  }
86
- removeOnCachePathInvalidation(path, callback) {
87
- if (this.on[path]) {
88
- this.on[path] = this.on[path].filter(fn => fn !== callback);
89
- }
133
+ put(path, params, body) {
134
+ return __awaiter(this, void 0, void 0, function* () {
135
+ const result = yield this.fetch(path, "put", params, body);
136
+ this.flush(path, params);
137
+ return result;
138
+ });
139
+ }
140
+ post(path, params, body) {
141
+ return __awaiter(this, void 0, void 0, function* () {
142
+ const result = this.fetch(path, "post", params, body);
143
+ this.flush(path, params);
144
+ return result;
145
+ });
146
+ }
147
+ delete(path, params) {
148
+ return __awaiter(this, void 0, void 0, function* () {
149
+ const result = this.fetch(path, "delete", params);
150
+ this.flush(path, params);
151
+ return result;
152
+ });
153
+ }
154
+ /**
155
+ * Implementing apiClient must return a promise that passes the raw response as 1st arg.
156
+ * @param path URL for GET.
157
+ * @param query Object, where keys are param names and values are param values.
158
+ * @returns a Promise.
159
+ */
160
+ fetch(path_1, method_1, params_1, body_1) {
161
+ return __awaiter(this, arguments, void 0, function* (path, method, params, body, options = {}) {
162
+ const _query = Object.assign({ lang: this.lang }, ((params === null || params === void 0 ? void 0 : params.query) || {}));
163
+ if (!_query.lang) {
164
+ delete _query.lang;
165
+ }
166
+ const pathSegments = splitAndResolvePath(path, params);
167
+ options = Object.assign(Object.assign({}, options), { headers: Object.assign(Object.assign({}, (options.headers || {})), { "API-Version": 1, "Accept-Language": this.lang }) });
168
+ if (body && !otherThanJSON(body)) {
169
+ options = Object.assign(Object.assign({}, options), { headers: Object.assign(Object.assign({}, (options.headers || {})), { "Content-Type": "application/json", "Accept": "application/json" }) });
170
+ body = JSON.stringify(body);
171
+ }
172
+ const response = yield this.apiClient.fetch(pathSegments.join(""), _query, Object.assign({ method, body }, options));
173
+ if (response.status >= 400) {
174
+ const error = yield response.json();
175
+ throw new LajiApiError(error === null || error === void 0 ? void 0 : error.message, response.status);
176
+ }
177
+ return (response.headers.get("Content-Type") || "").includes("application/json")
178
+ ? response.json()
179
+ : response.text();
180
+ });
90
181
  }
91
182
  setLang(lang) {
92
183
  this.lang = lang;
93
- this.flushCache();
184
+ this.cacheTree = { branches: new Map(), queries: new Map(), path: "" };
94
185
  }
95
186
  }
96
187
  exports.default = ApiClient;
188
+ const otherThanJSON = (body) => body instanceof FormData ||
189
+ body instanceof Blob ||
190
+ body instanceof ArrayBuffer ||
191
+ body instanceof URLSearchParams ||
192
+ body instanceof ReadableStream;
193
+ function isRecord(value) {
194
+ return typeof value === "object" && value !== null && !Array.isArray(value);
195
+ }
196
+ const sortRecordRecursively = (record) => (Object.entries(record)
197
+ .sort(([aKey, aVal], [bKey, bVal]) => aKey > bKey ? 1 : aKey < bKey ? -1 : 0)
198
+ .reduce((acc, [key, value]) => {
199
+ acc[key] = isRecord(value) ? sortRecordRecursively(value) : value;
200
+ return acc;
201
+ }, Object.create(null)));
202
+ const hashRecord = (record) => JSON.stringify(sortRecordRecursively(record || {}));
@@ -28,7 +28,7 @@ export interface LajiFormProps extends HasMaybeChildren {
28
28
  topOffset?: number;
29
29
  bottomOffset?: number;
30
30
  formContext?: any;
31
- uiSchemaContext?: any;
31
+ uiSchemaContext?: UiSchemaContext;
32
32
  settings?: any;
33
33
  id?: string;
34
34
  googleApiKey?: string;
@@ -70,7 +70,7 @@ export interface LajiFormState {
70
70
  runningSubmitHooks?: boolean;
71
71
  }
72
72
  export interface MediaMetadata {
73
- capturerVerbatim: string;
73
+ capturerVerbatim?: string | string[];
74
74
  intellectualOwner: string;
75
75
  intellectualRights: string;
76
76
  }
@@ -118,6 +118,14 @@ export interface FormContext {
118
118
  multiActiveArray: MultiActiveArrayService;
119
119
  };
120
120
  }
121
+ export interface UiSchemaContext {
122
+ isAdmin?: boolean;
123
+ isEdit?: boolean;
124
+ creator?: string;
125
+ confirmDelete?: boolean;
126
+ additionalClassNames?: Record<string, string>;
127
+ [key: string]: any;
128
+ }
121
129
  export type NotifyMessager = (msg: string) => void;
122
130
  export interface Notifier {
123
131
  success: NotifyMessager;
@@ -510,7 +510,7 @@ class LajiForm extends React.Component {
510
510
  this.state = this.getStateFromProps(props);
511
511
  }
512
512
  UNSAFE_componentWillReceiveProps(props) {
513
- if (props.apiClient && props.apiClient !== this.apiClient) {
513
+ if (props.apiClient && props.apiClient !== this.apiClient.apiClient) {
514
514
  this.apiClient = new ApiClient_1.default(props.apiClient, props.lang, this.translations);
515
515
  }
516
516
  if (this.apiClient && "lang" in props && this.props.lang !== props.lang) {
@@ -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 __importDefault = (this && this.__importDefault) || function (mod) {
42
51
  return (mod && mod.__esModule) ? mod : { "default": mod };
43
52
  };
@@ -134,29 +143,24 @@ exports.default = AnnotationField;
134
143
  class AnnotationBox extends React.Component {
135
144
  constructor(props) {
136
145
  super(props);
137
- this.onAnnotationSubmit = ({ formData }) => {
146
+ this.onAnnotationSubmit = (_a) => __awaiter(this, [_a], void 0, function* ({ formData }) {
138
147
  const { type } = this.getAddOptions();
139
148
  const rootID = this.props.formContext.services.rootInstance.getFormData().id;
140
149
  this.props.formContext.services.blocker.push();
141
- this.props.formContext.apiClient.fetchRaw("/annotations", undefined, {
142
- method: "POST",
143
- body: JSON.stringify(Object.assign(Object.assign({}, formData), { targetID: this.props.id, rootID, type, byRole: "MMAN.formAdmin" }))
144
- }).then(response => {
145
- if (response.status >= 400) {
146
- throw new Error("Request failed");
147
- }
148
- return response.json();
149
- }).then(annotation => {
150
+ const body = Object.assign(Object.assign({}, formData), { targetID: this.props.id, rootID, type, byRole: "MMAN.formAdmin" });
151
+ try {
152
+ const annotation = yield this.props.formContext.apiClient.post("/annotations", undefined, body);
150
153
  this.props.formContext.services.blocker.pop();
151
154
  const annotationContext = (0, Context_1.default)(`${this.props.formContext.contextId}_ANNOTATIONS`);
152
155
  const annotations = [annotation];
153
156
  annotationContext[this.props.id] = annotations;
154
157
  this.setState({ annotations: annotations, fail: false });
155
- }).catch(() => {
158
+ }
159
+ catch (e) {
156
160
  this.props.formContext.services.blocker.pop();
157
161
  this.setState({ fail: true });
158
- });
159
- };
162
+ }
163
+ });
160
164
  this.onAnnotationChange = (formData) => {
161
165
  let state = {};
162
166
  if (this.state.fail !== undefined) {
@@ -213,30 +217,30 @@ class AnnotationBox extends React.Component {
213
217
  return add && addSchema ? (React.createElement(LajiForm_1.default, Object.assign({}, metadataForm, { schema: addSchema, uiSchema: addUiSchema || _uiSchema, onSubmit: this.onAnnotationSubmit, onChange: this.onAnnotationChange, renderSubmit: renderSubmit, formData: addFormData, lang: lang, apiClient: this.props.formContext.apiClient.apiClient, uiSchemaContext: this.props.formContext.uiSchemaContext }), React.createElement("div", null,
214
218
  this.state.fail !== undefined &&
215
219
  React.createElement(Alert, { variant: this.state.fail ? "danger" : "success" }, translations[this.state.fail ? "SaveFail" : "SaveSuccess"]),
216
- renderSubmit && React.createElement(components_1.Button, { id: "submit", type: "submit" }, translations.Submit)))) : null;
220
+ renderSubmit && React.createElement(components_1.Button, { id: "submit", type: "submit", onClick: this.onAnnotationSubmit }, translations.Submit)))) : null;
217
221
  };
218
222
  this.getUiSchema = () => {
219
223
  const { uiSchema } = this.props;
220
224
  const { metadataForm = {} } = this.state;
221
225
  return uiSchema || Object.assign(Object.assign({}, metadataForm.uiSchema), { "ui:shortcuts": Object.assign(Object.assign({}, ((metadataForm.uiSchema || {})["ui:shorcuts"] || {})), (this.props.formContext.services.keyHandler.shortcuts)), "ui:showShortcutsButton": false });
222
226
  };
223
- this.onDelete = (id) => () => {
224
- this.props.formContext.apiClient.fetchRaw(`/annotations/${id}`, undefined, {
225
- method: "DELETE"
226
- }).then(() => {
227
+ this.onDelete = (id) => () => __awaiter(this, void 0, void 0, function* () {
228
+ try {
229
+ yield this.props.formContext.apiClient.delete("/annotations/{id}", { path: { id } });
227
230
  const annotationContext = (0, Context_1.default)(`${this.props.formContext.contextId}_ANNOTATIONS`);
228
231
  const annotations = this.state.annotations.filter(({ id: _id }) => _id !== id);
229
232
  annotationContext[this.props.id] = annotations;
230
233
  this.setState({ deleteFail: false, annotations });
231
- }).catch(() => {
234
+ }
235
+ catch (e) {
232
236
  this.setState({ deleteFail: true });
233
- });
234
- };
237
+ }
238
+ });
235
239
  this.state = { annotations: props.annotations || [] };
236
240
  }
237
241
  componentDidMount() {
238
242
  this.mounted = true;
239
- this.props.formContext.apiClient.fetchCached(`/forms/${this.props.formId}`, { lang: this.props.lang, format: "schema" })
243
+ this.props.formContext.apiClient.get(`/forms/${this.props.formId}`, { query: { format: "schema" } })
240
244
  .then(metadataForm => {
241
245
  if (!this.mounted)
242
246
  return;
@@ -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 __importDefault = (this && this.__importDefault) || function (mod) {
42
51
  return (mod && mod.__esModule) ? mod : { "default": mod };
43
52
  };
@@ -118,14 +127,18 @@ const LajiAudio = React.forwardRef((props, ref) => {
118
127
  const [mp3Url, setMp3Url] = useState(null);
119
128
  const [wavUrl, setWavUrl] = useState(null);
120
129
  const [flacUrl, setFlacUrl] = useState(null);
121
- useEffect(() => {
122
- props.apiClient.fetchCached(`/audio/${props.id}`, undefined, { failSilently: true }).then(response => {
130
+ useEffect(() => __awaiter(void 0, void 0, void 0, function* () {
131
+ let response;
132
+ try {
133
+ response = yield props.apiClient.get(`/audio/${props.id}`);
123
134
  setMp3Url(response.mp3URL);
124
135
  setWavUrl(response.wavURL);
125
136
  setFlacUrl(response.flacURL);
126
- props.onLoaded && props.onLoaded(true);
127
- });
128
- });
137
+ }
138
+ catch (e) {
139
+ }
140
+ props.onLoaded && props.onLoaded(true);
141
+ }));
129
142
  return !mp3Url ? React.createElement(react_spinner_1.default, { style: props.style }) : (React.createElement("div", null,
130
143
  React.createElement("audio", { controls: true, style: props.style, ref: ref, onPause: props.onStop },
131
144
  React.createElement("source", { src: mp3Url })),
@@ -7,8 +7,8 @@
7
7
  * suggestionValueField: <fieldName> (the field which the value for autosuggest is pulled from)
8
8
  * suggestionReceivers: {
9
9
  * <fieldName>: <suggestion path>, (when an autosuggestion is selected, these fields receive the autosuggestions value defined by suggestion path.
10
- * <fieldName2>: <suggestion path 2>, Example: autosuggestion = {key: "MLV.2", value: "kalalokki", payload: {informalGroups: ["linnut"]}}
11
- * } suggestionReceivers: {someFieldName: "key", someFieldName2: "/payload/informalgroups/0"}
10
+ * <fieldName2>: <suggestion path 2>, Example: autosuggestion = {key: "MLV.2", value: "kalalokki", informalGroups: ["linnut"]}
11
+ * } suggestionReceivers: {someFieldName: "key", someFieldName2: "/informalgroups/0"}
12
12
  * If fieldName start with '$', then a function from autosuggestFieldSettings parses the suggestion. Example: $taxonGroup
13
13
  * If fieldName start with '/', it is handled as a JSON pointer.
14
14
  * uiSchema: <uiSchema> (uiSchema which is passed to inner SchemaField)
@@ -43,7 +43,7 @@ const Context_1 = __importDefault(require("../../Context"));
43
43
  const deepmerge_1 = __importDefault(require("deepmerge"));
44
44
  const suggestionParsers = {
45
45
  taxonGroup: suggestion => {
46
- return suggestion.payload ? suggestion.payload.informalTaxonGroups.map(item => typeof item === "string" ? item : item.id) : [];
46
+ return (suggestion.informalGroups || []).map(item => typeof item === "string" ? item : item.id);
47
47
  }
48
48
  };
49
49
  const parseQuery = (query, props, taxonGroups) => {
@@ -70,8 +70,8 @@ const parseQuery = (query, props, taxonGroups) => {
70
70
  * suggestionValueField: <fieldName> (the field which the value for autosuggest is pulled from)
71
71
  * suggestionReceivers: {
72
72
  * <fieldName>: <suggestion path>, (when an autosuggestion is selected, these fields receive the autosuggestions value defined by suggestion path.
73
- * <fieldName2>: <suggestion path 2>, Example: autosuggestion = {key: "MLV.2", value: "kalalokki", payload: {informalGroups: ["linnut"]}}
74
- * } suggestionReceivers: {someFieldName: "key", someFieldName2: "/payload/informalgroups/0"}
73
+ * <fieldName2>: <suggestion path 2>, Example: autosuggestion = {key: "MLV.2", value: "kalalokki", informalGroups: ["linnut"]}
74
+ * } suggestionReceivers: {someFieldName: "key", someFieldName2: "/informalgroups/0"}
75
75
  * If fieldName start with '$', then a function from autosuggestFieldSettings parses the suggestion. Example: $taxonGroup
76
76
  * If fieldName start with '/', it is handled as a JSON pointer.
77
77
  * uiSchema: <uiSchema> (uiSchema which is passed to inner SchemaField)
@@ -168,7 +168,7 @@ class AutosuggestField extends React.Component {
168
168
  return formData;
169
169
  };
170
170
  if (autosuggestField === "unit") {
171
- let { unit } = suggestion.payload;
171
+ let { unit } = suggestion;
172
172
  if (unit.unitType) {
173
173
  unit.informalTaxonGroups = unit.unitType;
174
174
  delete unit.unitType;