@luomus/laji-form 15.1.66 → 15.1.68
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 +66 -21
- package/lib/ApiClient.js +174 -68
- package/lib/components/LajiForm.js +1 -1
- package/lib/components/fields/AnnotationField.js +26 -22
- package/lib/components/fields/AudioArrayField.js +18 -5
- 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 +2 -9
- package/lib/components/fields/ImageArrayField.d.ts +2 -2
- package/lib/components/fields/ImageArrayField.js +82 -112
- package/lib/components/fields/MapArrayField.js +13 -5
- package/lib/components/fields/MapField.d.ts +1 -1
- package/lib/components/fields/MapField.js +13 -5
- package/lib/components/fields/NamedPlaceChooserField/NamedPlaceChooser.d.ts +2 -1
- package/lib/components/fields/NamedPlaceChooserField/NamedPlaceChooserField.d.ts +9 -4
- package/lib/components/fields/NamedPlaceChooserField/NamedPlaceChooserField.js +51 -51
- package/lib/components/fields/NamedPlaceChooserField/Popup.d.ts +2 -1
- package/lib/components/fields/NamedPlaceSaverField.js +22 -18
- 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 +14 -18
- package/lib/components/widgets/AutosuggestWidget.js +79 -66
- package/lib/components/widgets/InformalTaxonGroupChooserWidget.js +1 -1
- package/lib/components/widgets/InputWithDefaultValueButtonWidget.js +1 -2
- package/lib/validation.js +1 -1
- package/package.json +5 -3
package/lib/ApiClient.d.ts
CHANGED
|
@@ -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<
|
|
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
|
|
15
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
16
|
-
*
|
|
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.
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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.header || {})), { "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.header || {})), { "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.
|
|
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 || {}));
|
|
@@ -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.
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
225
|
-
|
|
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
|
-
}
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
11
|
-
* } suggestionReceivers: {someFieldName: "key", someFieldName2: "/
|
|
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.
|
|
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",
|
|
74
|
-
* } suggestionReceivers: {someFieldName: "key", someFieldName2: "/
|
|
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
|
|
171
|
+
let { unit } = suggestion;
|
|
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.get(`/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,15 +292,8 @@ 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
|
-
|
|
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);
|
|
295
|
+
: this.props.formContext.apiClient.post("/coordinates/location", undefined, geometry).then().then(handleResponse(props.formContext.translations.Finland, "municipality", "biologicalProvince", "biogeographicalProvince")).catch((e) => {
|
|
296
|
+
fail(typeof e === "string" ? e : e.message);
|
|
304
297
|
});
|
|
305
298
|
}));
|
|
306
299
|
};
|
|
@@ -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
|
-
}) => void
|
|
102
|
+
}) => Promise<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) => void
|
|
173
|
+
updateURL: ({ id, apiClient, apiEndpoint }: ThumbnailProps) => Promise<void>;
|
|
174
174
|
render(): JSX.Element | null;
|
|
175
175
|
}
|
|
176
176
|
export {};
|