@luomus/laji-form 15.1.67 → 15.1.69
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/dist/laji-form.js +1 -1
- package/lib/ApiClient.d.ts +21 -64
- package/lib/ApiClient.js +68 -172
- package/lib/components/LajiForm.js +1 -1
- package/lib/components/fields/AnnotationField.js +22 -26
- package/lib/components/fields/AudioArrayField.js +5 -18
- package/lib/components/fields/AutosuggestField.d.ts +2 -2
- package/lib/components/fields/AutosuggestField.js +4 -4
- package/lib/components/fields/EnumRangeArrayField.js +1 -1
- package/lib/components/fields/GeocoderField.js +9 -2
- package/lib/components/fields/ImageArrayField.d.ts +2 -2
- package/lib/components/fields/ImageArrayField.js +112 -82
- package/lib/components/fields/MapArrayField.js +5 -13
- package/lib/components/fields/MapField.d.ts +1 -1
- package/lib/components/fields/MapField.js +5 -13
- package/lib/components/fields/NamedPlaceChooserField/NamedPlaceChooser.d.ts +1 -2
- package/lib/components/fields/NamedPlaceChooserField/NamedPlaceChooserField.d.ts +4 -9
- package/lib/components/fields/NamedPlaceChooserField/NamedPlaceChooserField.js +51 -52
- package/lib/components/fields/NamedPlaceChooserField/Popup.d.ts +1 -2
- package/lib/components/fields/NamedPlaceSaverField.js +18 -22
- package/lib/components/fields/ScopeField.js +1 -1
- package/lib/components/fields/SingleActiveArrayField.js +2 -2
- package/lib/components/fields/SortArrayField.js +1 -1
- package/lib/components/fields/UnitCountShorthandField.js +1 -1
- package/lib/components/fields/UnitListShorthandArrayField.js +2 -2
- package/lib/components/fields/UnitShorthandField.js +5 -5
- package/lib/components/widgets/AutosuggestWidget.js +66 -79
- package/lib/components/widgets/InformalTaxonGroupChooserWidget.js +1 -1
- package/lib/components/widgets/InputWithDefaultValueButtonWidget.js +2 -1
- package/lib/validation.js +1 -1
- package/package.json +3 -5
package/lib/ApiClient.d.ts
CHANGED
|
@@ -1,90 +1,47 @@
|
|
|
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
|
-
}
|
|
8
3
|
interface Query {
|
|
9
4
|
[param: string]: any;
|
|
10
5
|
}
|
|
6
|
+
interface Options {
|
|
7
|
+
failSilently?: boolean;
|
|
8
|
+
[option: string]: any;
|
|
9
|
+
}
|
|
11
10
|
export interface ApiClientImplementation {
|
|
12
|
-
fetch: (path: string, query: Query, options: any) => Promise<
|
|
11
|
+
fetch: (path: string, query: Query, options: any) => Promise<any>;
|
|
13
12
|
}
|
|
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
|
-
};
|
|
53
13
|
/**
|
|
54
|
-
* ApiClient
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* Get requests are automatically cached, and they will flush if the resource receives a POST/PUT/DELETE request.
|
|
14
|
+
* ApiClient "interface". Wraps the given apiClient as a singleton object.
|
|
15
|
+
* Given apiClient must implement fetch().
|
|
58
16
|
*/
|
|
59
17
|
export default class ApiClient {
|
|
60
18
|
apiClient: ApiClientImplementation;
|
|
61
19
|
lang: Lang;
|
|
62
20
|
translations: Translations;
|
|
63
|
-
|
|
21
|
+
cache: {
|
|
22
|
+
[path: string]: {
|
|
23
|
+
[cacheKey: string]: Promise<any>;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
64
26
|
on: {
|
|
65
27
|
[path: string]: (() => void)[];
|
|
66
28
|
};
|
|
67
29
|
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>, useCache?: boolean): Promise<ExtractContentIfExists<R[IntersectUnionTypes<keyof R, HttpSuccessCodes>]>>;
|
|
72
|
-
/** Subscribe to a GET request. It re-emits when the resource receives a POST/PUT/DELETE request. */
|
|
73
|
-
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,
|
|
74
|
-
/**
|
|
75
|
-
* Store doesn't sync it's search indices right away after data is changed, so delaying the request can be useful
|
|
76
|
-
*/
|
|
77
|
-
delay?: number): () => void;
|
|
78
|
-
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>]>>;
|
|
79
|
-
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>]>>;
|
|
80
|
-
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>]>>;
|
|
81
30
|
/**
|
|
82
31
|
* Implementing apiClient must return a promise that passes the raw response as 1st arg.
|
|
83
32
|
* @param path URL for GET.
|
|
84
33
|
* @param query Object, where keys are param names and values are param values.
|
|
85
34
|
* @returns a Promise.
|
|
86
35
|
*/
|
|
87
|
-
|
|
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;
|
|
88
45
|
setLang(lang: Lang): void;
|
|
89
46
|
}
|
|
90
47
|
export {};
|
package/lib/ApiClient.js
CHANGED
|
@@ -1,200 +1,96 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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;
|
|
10
12
|
};
|
|
11
13
|
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
|
-
};
|
|
34
14
|
/**
|
|
35
|
-
* ApiClient
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* Get requests are automatically cached, and they will flush if the resource receives a POST/PUT/DELETE request.
|
|
15
|
+
* ApiClient "interface". Wraps the given apiClient as a singleton object.
|
|
16
|
+
* Given apiClient must implement fetch().
|
|
39
17
|
*/
|
|
40
18
|
class ApiClient {
|
|
41
19
|
constructor(apiClient, lang = "en", translations) {
|
|
42
|
-
this.
|
|
43
|
-
this.cacheTree = { branches: new Map(), queries: new Map(), path: "" };
|
|
20
|
+
this.cache = {};
|
|
44
21
|
this.on = {};
|
|
22
|
+
this.flushCache = () => {
|
|
23
|
+
this.cache = {};
|
|
24
|
+
};
|
|
45
25
|
this.apiClient = apiClient;
|
|
46
26
|
this.lang = lang;
|
|
47
27
|
this.translations = translations;
|
|
48
28
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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());
|
|
68
|
-
}
|
|
69
|
-
}
|
|
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);
|
|
78
|
-
}
|
|
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;
|
|
89
|
-
}
|
|
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;
|
|
90
39
|
}
|
|
91
|
-
return
|
|
40
|
+
return this.apiClient.fetch(path, _query, options).catch(() => {
|
|
41
|
+
throw new Error(this.translations[this.lang].RequestFailed);
|
|
42
|
+
});
|
|
92
43
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
let serializedQuery;
|
|
99
|
-
if (useCache) {
|
|
100
|
-
serializedQuery = hashRecord(params === null || params === void 0 ? void 0 : params.query);
|
|
101
|
-
cacheNode = this.getCacheNode(path, params);
|
|
102
|
-
if (cacheNode.queries.has(serializedQuery)) {
|
|
103
|
-
return cacheNode.queries.get(serializedQuery);
|
|
104
|
-
}
|
|
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");
|
|
105
49
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
cacheNode.queries = cacheNode.queries;
|
|
109
|
-
cacheNode.queries.set(serializedQuery, result);
|
|
50
|
+
if (response.status === 204) {
|
|
51
|
+
return undefined;
|
|
110
52
|
}
|
|
111
|
-
return
|
|
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);
|
|
112
58
|
});
|
|
113
59
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Store doesn't sync it's search indices right away after data is changed, so delaying the request can be useful
|
|
118
|
-
*/
|
|
119
|
-
delay) {
|
|
120
|
-
if (!this.on[path])
|
|
121
|
-
this.on[path] = [];
|
|
122
|
-
const fn = delay
|
|
123
|
-
? () => { setTimeout(() => this.get(path, params).then(onFulfilled, onError), delay); }
|
|
124
|
-
: () => { this.get(path, params).then(onFulfilled, onError); };
|
|
125
|
-
this.on[path].push(fn);
|
|
126
|
-
this.get(path, params).then(onFulfilled, onError);
|
|
127
|
-
return () => {
|
|
128
|
-
this.on[path] = this.on[path].filter(_fn => _fn !== fn);
|
|
129
|
-
};
|
|
60
|
+
getCacheKey(query, options) {
|
|
61
|
+
return JSON.stringify(query) + JSON.stringify(options);
|
|
130
62
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
this.
|
|
135
|
-
|
|
136
|
-
|
|
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];
|
|
137
69
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
this.
|
|
142
|
-
|
|
143
|
-
});
|
|
70
|
+
invalidateCachePath(path) {
|
|
71
|
+
delete this.cache[path];
|
|
72
|
+
if (this.on[path]) {
|
|
73
|
+
this.on[path].forEach(callback => callback());
|
|
74
|
+
}
|
|
144
75
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
this.
|
|
149
|
-
return result;
|
|
150
|
-
});
|
|
76
|
+
//TODO on invalidation callbacks
|
|
77
|
+
invalidateCachePathQuery(path, query) {
|
|
78
|
+
if (this.cache[path])
|
|
79
|
+
delete this.cache[path][query];
|
|
151
80
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (!_query.lang) {
|
|
162
|
-
delete _query.lang;
|
|
163
|
-
}
|
|
164
|
-
const pathSegments = splitAndResolvePath(path, params);
|
|
165
|
-
options = Object.assign(Object.assign({}, options), { headers: Object.assign(Object.assign({}, (options.header || {})), { "API-Version": 1, "Accept-Language": this.lang }) });
|
|
166
|
-
if (body && !otherThanJSON(body)) {
|
|
167
|
-
options = Object.assign(Object.assign({}, options), { headers: Object.assign(Object.assign({}, (options.header || {})), { "Content-Type": "application/json", "Accept": "application/json" }) });
|
|
168
|
-
body = JSON.stringify(body);
|
|
169
|
-
}
|
|
170
|
-
const response = yield this.apiClient.fetch(pathSegments.join(""), _query, Object.assign({ method, body }, options));
|
|
171
|
-
if (response.status >= 400) {
|
|
172
|
-
const error = yield response.json();
|
|
173
|
-
throw new LajiApiError(error === null || error === void 0 ? void 0 : error.message, response.status);
|
|
174
|
-
}
|
|
175
|
-
return (response.headers.get("Content-Type") || "").includes("application/json")
|
|
176
|
-
? response.json()
|
|
177
|
-
: response.text();
|
|
178
|
-
});
|
|
81
|
+
onCachePathInvalidation(path, callback) {
|
|
82
|
+
if (!this.on[path])
|
|
83
|
+
this.on[path] = [];
|
|
84
|
+
this.on[path].push(callback);
|
|
85
|
+
}
|
|
86
|
+
removeOnCachePathInvalidation(path, callback) {
|
|
87
|
+
if (this.on[path]) {
|
|
88
|
+
this.on[path] = this.on[path].filter(fn => fn !== callback);
|
|
89
|
+
}
|
|
179
90
|
}
|
|
180
91
|
setLang(lang) {
|
|
181
92
|
this.lang = lang;
|
|
182
|
-
this.
|
|
93
|
+
this.flushCache();
|
|
183
94
|
}
|
|
184
95
|
}
|
|
185
96
|
exports.default = ApiClient;
|
|
186
|
-
const otherThanJSON = (body) => body instanceof FormData ||
|
|
187
|
-
body instanceof Blob ||
|
|
188
|
-
body instanceof ArrayBuffer ||
|
|
189
|
-
body instanceof URLSearchParams ||
|
|
190
|
-
body instanceof ReadableStream;
|
|
191
|
-
function isRecord(value) {
|
|
192
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
193
|
-
}
|
|
194
|
-
const sortRecordRecursively = (record) => (Object.entries(record)
|
|
195
|
-
.sort(([aKey, aVal], [bKey, bVal]) => aKey > bKey ? 1 : aKey < bKey ? -1 : 0)
|
|
196
|
-
.reduce((acc, [key, value]) => {
|
|
197
|
-
acc[key] = isRecord(value) ? sortRecordRecursively(value) : value;
|
|
198
|
-
return acc;
|
|
199
|
-
}, Object.create(null)));
|
|
200
|
-
const hashRecord = (record) => JSON.stringify(sortRecordRecursively(record || {}));
|
|
@@ -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) {
|
|
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,15 +38,6 @@ 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
|
-
};
|
|
50
41
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
51
42
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
52
43
|
};
|
|
@@ -143,24 +134,29 @@ exports.default = AnnotationField;
|
|
|
143
134
|
class AnnotationBox extends React.Component {
|
|
144
135
|
constructor(props) {
|
|
145
136
|
super(props);
|
|
146
|
-
this.onAnnotationSubmit = (
|
|
137
|
+
this.onAnnotationSubmit = ({ formData }) => {
|
|
147
138
|
const { type } = this.getAddOptions();
|
|
148
139
|
const rootID = this.props.formContext.services.rootInstance.getFormData().id;
|
|
149
140
|
this.props.formContext.services.blocker.push();
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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 => {
|
|
153
150
|
this.props.formContext.services.blocker.pop();
|
|
154
151
|
const annotationContext = (0, Context_1.default)(`${this.props.formContext.contextId}_ANNOTATIONS`);
|
|
155
152
|
const annotations = [annotation];
|
|
156
153
|
annotationContext[this.props.id] = annotations;
|
|
157
154
|
this.setState({ annotations: annotations, fail: false });
|
|
158
|
-
}
|
|
159
|
-
catch (e) {
|
|
155
|
+
}).catch(() => {
|
|
160
156
|
this.props.formContext.services.blocker.pop();
|
|
161
157
|
this.setState({ fail: true });
|
|
162
|
-
}
|
|
163
|
-
}
|
|
158
|
+
});
|
|
159
|
+
};
|
|
164
160
|
this.onAnnotationChange = (formData) => {
|
|
165
161
|
let state = {};
|
|
166
162
|
if (this.state.fail !== undefined) {
|
|
@@ -217,30 +213,30 @@ class AnnotationBox extends React.Component {
|
|
|
217
213
|
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,
|
|
218
214
|
this.state.fail !== undefined &&
|
|
219
215
|
React.createElement(Alert, { variant: this.state.fail ? "danger" : "success" }, translations[this.state.fail ? "SaveFail" : "SaveSuccess"]),
|
|
220
|
-
renderSubmit && React.createElement(components_1.Button, { id: "submit", type: "submit"
|
|
216
|
+
renderSubmit && React.createElement(components_1.Button, { id: "submit", type: "submit" }, translations.Submit)))) : null;
|
|
221
217
|
};
|
|
222
218
|
this.getUiSchema = () => {
|
|
223
219
|
const { uiSchema } = this.props;
|
|
224
220
|
const { metadataForm = {} } = this.state;
|
|
225
221
|
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 });
|
|
226
222
|
};
|
|
227
|
-
this.onDelete = (id) => () =>
|
|
228
|
-
|
|
229
|
-
|
|
223
|
+
this.onDelete = (id) => () => {
|
|
224
|
+
this.props.formContext.apiClient.fetchRaw(`/annotations/${id}`, undefined, {
|
|
225
|
+
method: "DELETE"
|
|
226
|
+
}).then(() => {
|
|
230
227
|
const annotationContext = (0, Context_1.default)(`${this.props.formContext.contextId}_ANNOTATIONS`);
|
|
231
228
|
const annotations = this.state.annotations.filter(({ id: _id }) => _id !== id);
|
|
232
229
|
annotationContext[this.props.id] = annotations;
|
|
233
230
|
this.setState({ deleteFail: false, annotations });
|
|
234
|
-
}
|
|
235
|
-
catch (e) {
|
|
231
|
+
}).catch(() => {
|
|
236
232
|
this.setState({ deleteFail: true });
|
|
237
|
-
}
|
|
238
|
-
}
|
|
233
|
+
});
|
|
234
|
+
};
|
|
239
235
|
this.state = { annotations: props.annotations || [] };
|
|
240
236
|
}
|
|
241
237
|
componentDidMount() {
|
|
242
238
|
this.mounted = true;
|
|
243
|
-
this.props.formContext.apiClient.
|
|
239
|
+
this.props.formContext.apiClient.fetchCached(`/forms/${this.props.formId}`, { lang: this.props.lang, format: "schema" })
|
|
244
240
|
.then(metadataForm => {
|
|
245
241
|
if (!this.mounted)
|
|
246
242
|
return;
|
|
@@ -38,15 +38,6 @@ 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
|
-
};
|
|
50
41
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
51
42
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
52
43
|
};
|
|
@@ -127,18 +118,14 @@ const LajiAudio = React.forwardRef((props, ref) => {
|
|
|
127
118
|
const [mp3Url, setMp3Url] = useState(null);
|
|
128
119
|
const [wavUrl, setWavUrl] = useState(null);
|
|
129
120
|
const [flacUrl, setFlacUrl] = useState(null);
|
|
130
|
-
useEffect(() =>
|
|
131
|
-
|
|
132
|
-
try {
|
|
133
|
-
response = yield props.apiClient.get(`/audio/${props.id}`);
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
props.apiClient.fetchCached(`/audio/${props.id}`, undefined, { failSilently: true }).then(response => {
|
|
134
123
|
setMp3Url(response.mp3URL);
|
|
135
124
|
setWavUrl(response.wavURL);
|
|
136
125
|
setFlacUrl(response.flacURL);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
props.onLoaded && props.onLoaded(true);
|
|
141
|
-
}));
|
|
126
|
+
props.onLoaded && props.onLoaded(true);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
142
129
|
return !mp3Url ? React.createElement(react_spinner_1.default, { style: props.style }) : (React.createElement("div", null,
|
|
143
130
|
React.createElement("audio", { controls: true, style: props.style, ref: ref, onPause: props.onStop },
|
|
144
131
|
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", informalGroups: ["linnut"]}
|
|
11
|
-
* } suggestionReceivers: {someFieldName: "key", someFieldName2: "/informalgroups/0"}
|
|
10
|
+
* <fieldName2>: <suggestion path 2>, Example: autosuggestion = {key: "MLV.2", value: "kalalokki", payload: {informalGroups: ["linnut"]}}
|
|
11
|
+
* } suggestionReceivers: {someFieldName: "key", someFieldName2: "/payload/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
|
|
46
|
+
return suggestion.payload ? suggestion.payload.informalTaxonGroups.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", informalGroups: ["linnut"]}
|
|
74
|
-
* } suggestionReceivers: {someFieldName: "key", someFieldName2: "/informalgroups/0"}
|
|
73
|
+
* <fieldName2>: <suggestion path 2>, Example: autosuggestion = {key: "MLV.2", value: "kalalokki", payload: {informalGroups: ["linnut"]}}
|
|
74
|
+
* } suggestionReceivers: {someFieldName: "key", someFieldName2: "/payload/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;
|
|
171
|
+
let { unit } = suggestion.payload;
|
|
172
172
|
if (unit.unitType) {
|
|
173
173
|
unit.informalTaxonGroups = unit.unitType;
|
|
174
174
|
delete unit.unitType;
|
|
@@ -60,7 +60,7 @@ function EnumRangeArrayField(props) {
|
|
|
60
60
|
propsOnChange(value);
|
|
61
61
|
}, [propsOnChange]);
|
|
62
62
|
const getEnumOptionsAsync = (0, react_1.useCallback)(() => __awaiter(this, void 0, void 0, function* () {
|
|
63
|
-
const enums = yield props.formContext.apiClient.
|
|
63
|
+
const enums = yield props.formContext.apiClient.fetchCached(`/metadata/ranges/${range}`);
|
|
64
64
|
return enums.map(({ value }) => ({ value, label: value }));
|
|
65
65
|
}), [props.formContext.apiClient, range]);
|
|
66
66
|
const { Label } = props.formContext;
|
|
@@ -292,8 +292,15 @@ let GeocoderField = class GeocoderField extends React.Component {
|
|
|
292
292
|
}, 30 * 1000);
|
|
293
293
|
!bounds.overlaps(globals_1.FINLAND_BOUNDS)
|
|
294
294
|
? fetchForeign()
|
|
295
|
-
: this.props.formContext.apiClient.
|
|
296
|
-
|
|
295
|
+
: this.props.formContext.apiClient.fetchRaw("/coordinates/location", undefined, {
|
|
296
|
+
method: "POST",
|
|
297
|
+
headers: {
|
|
298
|
+
"accept": "application/json",
|
|
299
|
+
"content-type": "application/json"
|
|
300
|
+
},
|
|
301
|
+
body: JSON.stringify(geometry)
|
|
302
|
+
}).then(response => response.json()).then(handleResponse(props.formContext.translations.Finland, "municipality", "biologicalProvince", "biogeographicalProvince")).catch((e) => {
|
|
303
|
+
fail(e.message);
|
|
297
304
|
});
|
|
298
305
|
}));
|
|
299
306
|
};
|
|
@@ -99,7 +99,7 @@ export declare function MediaArrayField<LFC extends Constructor<React.Component<
|
|
|
99
99
|
processFile: (file: File) => Promise<ProcessedFile>;
|
|
100
100
|
onMediaMetadataUpdate: ({ formData }: {
|
|
101
101
|
formData: any;
|
|
102
|
-
}) =>
|
|
102
|
+
}) => void;
|
|
103
103
|
getMetadataPromise: () => Promise<MediaMetadataSchema>;
|
|
104
104
|
getMaxFileSizeAsString: () => string;
|
|
105
105
|
getAllowedMediaFormatsTranslationKey: () => "AllowedFileFormat" | "AllowedFileFormats";
|
|
@@ -170,7 +170,7 @@ export declare class Thumbnail extends React.PureComponent<ThumbnailProps, Thumb
|
|
|
170
170
|
componentDidMount(): void;
|
|
171
171
|
componentWillUnmount(): void;
|
|
172
172
|
UNSAFE_componentWillReceiveProps(props: ThumbnailProps): void;
|
|
173
|
-
updateURL: ({ id, apiClient, apiEndpoint }: ThumbnailProps) =>
|
|
173
|
+
updateURL: ({ id, apiClient, apiEndpoint }: ThumbnailProps) => void;
|
|
174
174
|
render(): JSX.Element | null;
|
|
175
175
|
}
|
|
176
176
|
export {};
|