@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.
- package/dist/laji-form.js +1 -1
- package/dist/styles.css +2 -2
- package/lib/ApiClient.d.ts +66 -21
- package/lib/ApiClient.js +174 -68
- package/lib/components/LajiForm.d.ts +10 -2
- 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/CondensedObjectField.js +3 -5
- package/lib/components/fields/EnumRangeArrayField.js +1 -1
- package/lib/components/fields/GeocoderField.js +67 -79
- package/lib/components/fields/ImageArrayField.d.ts +9 -5
- package/lib/components/fields/ImageArrayField.js +103 -122
- 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/PrefixArrayField.js +3 -3
- package/lib/components/fields/ScopeField.js +1 -1
- package/lib/components/fields/SectionArrayField.js +1 -1
- package/lib/components/fields/SingleActiveArrayField.js +7 -7
- package/lib/components/fields/SortArrayField.js +1 -1
- package/lib/components/fields/TableField.js +2 -2
- 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/templates/ArrayFieldTemplate.js +2 -2
- package/lib/components/widgets/AutosuggestWidget.js +77 -66
- package/lib/components/widgets/InformalTaxonGroupChooserWidget.js +1 -1
- package/lib/components/widgets/InputWithDefaultValueButtonWidget.js +1 -2
- package/lib/themes/bs5.js +10 -2
- package/lib/validation.js +1 -1
- 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
|
}
|
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.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.
|
|
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?:
|
|
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
|
|
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.
|
|
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;
|