@uxland/primary-shell 8.0.0-rc.0 → 8.0.0-rc.1
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/{component-Bt_gBs5e.js → component-BY2raxSF.js} +2 -2
- package/dist/{component-Bt_gBs5e.js.map → component-BY2raxSF.js.map} +1 -1
- package/dist/{index-8n9_rIYI.js → index-BZNo-mYO.js} +15427 -15933
- package/dist/index-BZNo-mYO.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.umd.cjs +2623 -2668
- package/dist/index.umd.cjs.map +1 -1
- package/dist/{plugin-B5E6LZ4X.js → plugin-IG9tBP6I.js} +5909 -5888
- package/dist/plugin-IG9tBP6I.js.map +1 -0
- package/dist/primary/shell/src/UI/components/clinical-pathways-action-menu/clinical-pathways-action-menu.d.ts +4 -12
- package/dist/primary/shell/src/UI/components/communication-action-menu/communication-action-menu.d.ts +4 -12
- package/dist/primary/shell/src/UI/components/petitioner-action-menu/petitioner-action-menu.d.ts +4 -12
- package/dist/primary/shell/src/UI/components/sidenav-action-menu/sidenav-action-menu.d.ts +27 -0
- package/dist/primary/shell/src/UI/components/sidenav-action-menu/template.d.ts +2 -0
- package/dist/primary/shell/src/UI/shared-components/primaria-text-editor/primaria-rich-text-editor.d.ts +7 -1
- package/dist/primary/shell/src/api/import-data-manager/component/component.d.ts +1 -0
- package/dist/primary/shell/src/api/region-manager/region-manager.d.ts +4 -3
- package/dist/primary/shell/src/api/region-manager/remove-view-idempotency.test.d.ts +1 -0
- package/dist/primary/shell/src/disposer.d.ts +1 -1
- package/dist/primary/shell/src/features/bootstrapper.d.ts +1 -1
- package/dist/primary/shell/src/features/exit/handler.d.ts +12 -0
- package/dist/primary/shell/src/features/get-user-info/backend-adapter.d.ts +4 -2
- package/dist/primary/shell/src/features/get-user-info/pds-token.d.ts +26 -0
- package/dist/primary/shell/src/features/get-visi-id/action.d.ts +2 -0
- package/package.json +3 -3
- package/src/UI/components/clinical-pathways-action-menu/clinical-pathways-action-menu.ts +6 -41
- package/src/UI/components/communication-action-menu/communication-action-menu.ts +6 -41
- package/src/UI/components/petitioner-action-menu/petitioner-action-menu.ts +6 -41
- package/src/UI/components/primaria-shell/primaria-shell.ts +3 -1
- package/src/UI/components/sidenav-action-menu/sidenav-action-menu.ts +62 -0
- package/src/UI/components/{petitioner-action-menu → sidenav-action-menu}/template.ts +3 -3
- package/src/UI/internal-views/doctor-nav-menu.ts +9 -2
- package/src/UI/shared-components/bootstrapper.ts +3 -1
- package/src/UI/shared-components/primaria-nav-item/primaria-nav-item.ts +3 -1
- package/src/UI/shared-components/primaria-text-editor/primaria-rich-text-editor.ts +34 -21
- package/src/api/activity-monitor/activity-monitor.test.ts +1 -1
- package/src/api/ecap-event-manager/ecap-event-manager.test.ts +12 -4
- package/src/api/http-client/http-client.test.ts +12 -31
- package/src/api/import-data-manager/component/component.ts +3 -0
- package/src/api/import-data-manager/import-data-manager-impl.test.ts +2 -0
- package/src/api/interaction-service/interaction-service-impl.tsx +19 -8
- package/src/api/localization/localization.ts +44 -40
- package/src/api/notification-service/notification-service-impl.test.ts +2 -1
- package/src/api/region-manager/region-manager.test.ts +59 -3
- package/src/api/region-manager/region-manager.ts +52 -10
- package/src/api/region-manager/remove-view-idempotency.test.ts +98 -0
- package/src/api/token-manager/token-manager.test.ts +3 -6
- package/src/disposer.ts +2 -2
- package/src/features/bootstrapper.ts +2 -2
- package/src/features/exit/handler.test.ts +60 -0
- package/src/features/exit/handler.ts +18 -2
- package/src/features/get-user-info/backend-adapter.ts +30 -18
- package/src/features/get-user-info/handler.test.ts +8 -2
- package/src/features/get-user-info/pds-token.ts +28 -0
- package/src/features/get-visi-id/action.ts +11 -0
- package/src/types/custom.d.ts +30 -0
- package/dist/index-8n9_rIYI.js.map +0 -1
- package/dist/plugin-B5E6LZ4X.js.map +0 -1
- package/dist/primary/shell/src/UI/components/clinical-pathways-action-menu/template.d.ts +0 -2
- package/dist/primary/shell/src/UI/components/communication-action-menu/template.d.ts +0 -2
- package/dist/primary/shell/src/UI/components/petitioner-action-menu/template.d.ts +0 -2
- package/src/UI/components/clinical-pathways-action-menu/template.ts +0 -24
- package/src/UI/components/communication-action-menu/styles.css +0 -29
- package/src/UI/components/communication-action-menu/template.ts +0 -24
- package/src/UI/components/petitioner-action-menu/styles.css +0 -29
- /package/src/UI/components/{clinical-pathways-action-menu → sidenav-action-menu}/styles.css +0 -0
|
@@ -7,6 +7,31 @@ import { fixSpellCheckerIssue } from "./utils";
|
|
|
7
7
|
|
|
8
8
|
fixSpellCheckerIssue(Quill);
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Una opció de la barra d'eines de Quill: el nom solt (`"bold"`), un objecte de configuració
|
|
12
|
+
* (`{ header: 1 }`) o un grup d'opcions, que és com Quill separa els blocs de la barra.
|
|
13
|
+
*/
|
|
14
|
+
type ToolbarOption = string | Record<string, unknown> | (string | Record<string, unknown>)[];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Opcions de la barra d'eines que Quill espera com a objecte de configuració en lloc del nom
|
|
18
|
+
* solt. Abans era una cadena d'`else if`.
|
|
19
|
+
*/
|
|
20
|
+
const TOOLBAR_OPTION_ALIASES: Record<string, ToolbarOption> = {
|
|
21
|
+
color: { color: [] },
|
|
22
|
+
ol: { list: "ordered" },
|
|
23
|
+
ul: { list: "bullet" },
|
|
24
|
+
subindex: { script: "sub" },
|
|
25
|
+
superindex: { script: "super" },
|
|
26
|
+
// outdent: { indent: "-1" },
|
|
27
|
+
// indent: { indent: "+1" },
|
|
28
|
+
size: { size: ["small", false, "large", "huge"] },
|
|
29
|
+
header: { header: [1, 2, 3, 4, 5, 6, false] },
|
|
30
|
+
background: { background: [] },
|
|
31
|
+
font: { font: [] },
|
|
32
|
+
align: { align: [] },
|
|
33
|
+
};
|
|
34
|
+
|
|
10
35
|
export class PrimariaRichTextEditor extends LitElement {
|
|
11
36
|
private textChangeTimeout: number | null = null;
|
|
12
37
|
|
|
@@ -124,7 +149,8 @@ export class PrimariaRichTextEditor extends LitElement {
|
|
|
124
149
|
formats: string;
|
|
125
150
|
|
|
126
151
|
_getOptions() {
|
|
127
|
-
|
|
152
|
+
// Sense anotar, `[]` s'infereix com a `never[]` i totes les assignacions posteriors fallen.
|
|
153
|
+
let toolbarOptions: ToolbarOption[] = [];
|
|
128
154
|
const availableOpts = this.availableOptions;
|
|
129
155
|
if (this.options === undefined || this.options.length === 0) {
|
|
130
156
|
toolbarOptions = [
|
|
@@ -145,26 +171,13 @@ export class PrimariaRichTextEditor extends LitElement {
|
|
|
145
171
|
// ["clean"],
|
|
146
172
|
];
|
|
147
173
|
} else {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
else if (option == "ul") return { list: "bullet" };
|
|
156
|
-
else if (option == "subindex") return { script: "sub" };
|
|
157
|
-
else if (option == "superindex") return { script: "super" };
|
|
158
|
-
// else if (option == 'outdent') return {indent: '-1'};
|
|
159
|
-
// else if (option == 'indent') return {indent: '+1'};
|
|
160
|
-
else if (option == "size") return { size: ["small", false, "large", "huge"] };
|
|
161
|
-
else if (option == "header") return { header: [1, 2, 3, 4, 5, 6, false] };
|
|
162
|
-
else if (option == "background") return { background: [] };
|
|
163
|
-
else if (option == "font") return { font: [] };
|
|
164
|
-
else if (option == "align") return { align: [] };
|
|
165
|
-
/* else if (option == 'undo') return {undo: undo_icon};
|
|
166
|
-
else if (option == 'redo') return {redo: redo_icon}; */ else return option;
|
|
167
|
-
});
|
|
174
|
+
// Abans això filtrava amb un `splice` dins d'un `forEach`, que salta elements quan n'hi ha
|
|
175
|
+
// dos d'invàlids seguits. Amb `filter` el resultat és el que la intenció demanava.
|
|
176
|
+
toolbarOptions = this.options
|
|
177
|
+
.split(",")
|
|
178
|
+
.filter((option) => availableOpts.indexOf(option) > -1)
|
|
179
|
+
// Les opcions amb configuració es tradueixen; la resta passen tal com són.
|
|
180
|
+
.map((option) => TOOLBAR_OPTION_ALIASES[option] ?? option);
|
|
168
181
|
// if (toolbarOptions.indexOf("clean") < 0) {
|
|
169
182
|
// toolbarOptions.push("clean");
|
|
170
183
|
// }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
1
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
3
|
import { ActivityMonitorImpl } from "./activity-monitor";
|
|
3
4
|
|
|
@@ -63,5 +64,4 @@ describe("ActivityMonitor", () => {
|
|
|
63
64
|
|
|
64
65
|
expect(monitor.getLastActivityTimestamp()).toBe(timestampBeforeStop);
|
|
65
66
|
});
|
|
66
|
-
|
|
67
67
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
1
2
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
3
|
import { createEcapEventManager, LAST_WORKED_DIAGNOSTICS_KEY } from "./ecap-event-manager";
|
|
3
4
|
|
|
@@ -8,6 +9,13 @@ const createMockGlobalStateManager = (data: Record<string, unknown> = {}) =>
|
|
|
8
9
|
clearData: vi.fn(),
|
|
9
10
|
}) as any;
|
|
10
11
|
|
|
12
|
+
/** `mock.calls[0][0]` és `unknown | undefined` amb `noUncheckedIndexedAccess`. */
|
|
13
|
+
const firstPostedMessage = (spy: { mock: { calls: unknown[][] } }): string => {
|
|
14
|
+
const payload = spy.mock.calls[0]?.[0];
|
|
15
|
+
if (typeof payload !== "string") throw new Error("s'esperava un missatge publicat");
|
|
16
|
+
return payload;
|
|
17
|
+
};
|
|
18
|
+
|
|
11
19
|
describe("EcapEventManagerImpl", () => {
|
|
12
20
|
const mockPostMessage = vi.fn();
|
|
13
21
|
const mockConsoleLog = vi.fn();
|
|
@@ -31,7 +39,7 @@ describe("EcapEventManagerImpl", () => {
|
|
|
31
39
|
|
|
32
40
|
eventManager.publish("click", "navigate", payload);
|
|
33
41
|
|
|
34
|
-
const postedEvent = JSON.parse(mockPostMessage
|
|
42
|
+
const postedEvent = JSON.parse(firstPostedMessage(mockPostMessage));
|
|
35
43
|
expect(postedEvent.DIAGNOSTICS).toBeUndefined();
|
|
36
44
|
expect(globalStateManager.getData).not.toHaveBeenCalled();
|
|
37
45
|
});
|
|
@@ -43,7 +51,7 @@ describe("EcapEventManagerImpl", () => {
|
|
|
43
51
|
|
|
44
52
|
eventManager.publish("PRESCRIPCIO_ACTIVA", "NO_TANCAR", {});
|
|
45
53
|
|
|
46
|
-
const postedEvent = JSON.parse(mockPostMessage
|
|
54
|
+
const postedEvent = JSON.parse(firstPostedMessage(mockPostMessage));
|
|
47
55
|
expect(postedEvent.DIAGNOSTICS).toEqual(diagnostics);
|
|
48
56
|
expect(globalStateManager.getData).toHaveBeenCalledWith(LAST_WORKED_DIAGNOSTICS_KEY);
|
|
49
57
|
});
|
|
@@ -63,7 +71,7 @@ describe("EcapEventManagerImpl", () => {
|
|
|
63
71
|
|
|
64
72
|
eventManager.publish(eventType, "NO_TANCAR", {});
|
|
65
73
|
|
|
66
|
-
const postedEvent = JSON.parse(mockPostMessage
|
|
74
|
+
const postedEvent = JSON.parse(firstPostedMessage(mockPostMessage));
|
|
67
75
|
expect(postedEvent.DIAGNOSTICS).toEqual(diagnostics);
|
|
68
76
|
});
|
|
69
77
|
|
|
@@ -73,7 +81,7 @@ describe("EcapEventManagerImpl", () => {
|
|
|
73
81
|
|
|
74
82
|
eventManager.publish("ORDRE_TRACTAMENT", "NO_TANCAR", {});
|
|
75
83
|
|
|
76
|
-
const postedEvent = JSON.parse(mockPostMessage
|
|
84
|
+
const postedEvent = JSON.parse(firstPostedMessage(mockPostMessage));
|
|
77
85
|
expect(postedEvent.DIAGNOSTICS).toEqual([]);
|
|
78
86
|
});
|
|
79
87
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
1
2
|
import axios, { AxiosInstance } from "axios";
|
|
2
3
|
import AxiosMockAdapter from "axios-mock-adapter";
|
|
3
4
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
@@ -14,7 +15,7 @@ const newAccessToken =
|
|
|
14
15
|
"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJidFZxNnRMWGpmcXdzbm5MR2FXMXdhdU9McDNiTmY4bWZ3Rm1SZ0lBS2VJIn0.eyJleHAiOjE3NDI5MTAzNzgsImlhdCI6MTc0MjkwOTQ3OCwianRpIjoiMTg4OTE3ZDYtNmM0OS00YWY0LTlhYjItZjEzYTM4MGY5OTgyIiwiaXNzIjoiaHR0cHM6Ly9wcmVwcm9kdWNjaW8ucGRzLmhlcy5jYXRzYWx1dC5nZW5jYXQuY2F0L2F1dGgvcmVhbG1zL0hFUyIsInN1YiI6Ijk1OGUyZWQ5LTJkNmUtNDZjOC1hOTE5LTU5MDQ0ZTYwMzVjMiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImV0Yy1jY2YtcHJlIiwic2Vzc2lvbl9zdGF0ZSI6IjY5ODdhMzExLWM1NzQtNDc1Ni1hMzY0LWY1ZjQzZmViZjNhYiIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLWhlcyIsIlJPTEVfSEVTX0VUQyIsIlJPTEVfSEVTX0xBTkRJTkciXX0sInNjb3BlIjoiIiwic2lkIjoiNjk4N2EzMTEtYzU3NC00NzU2LWEzNjQtZjVmNDNmZWJmM2FiIiwiY2xpZW50SG9zdCI6IjEwLjUzLjI1NC4xNTAiLCJhY2Nlc3NfcnVsZXMiOnt9LCJhY2Nlc3NfaW5mbyI6eyJtb2R1bGVfY29kZSI6IkEwMDEiLCJyb2xlX3R5cGUiOiJOT1JNIiwidHJhY2VfdXNlcl9pZCI6IlVzZXJfSUQiLCJjZW50ZXJfdHlwZSI6IkUiLCJtcGlfcGF0aWVudF9pZCI6IjVlYzc0YzdiLTVhYTUtNGE4NC1iM2I0LWYwMmVkZGZkZjZkYyIsInVwX2NvZGUiOiIwNzczMyIsInRyYWNlX3VzZXJfZ2l2ZW5fbmFtZSI6IkdpdmVuIE5hbWUiLCJ0cmFjZV91c2VyX2ZhbWlseV9uYW1lIjoiRmFtaWx5IE5hbWUiLCJ1c2VyX3R5cGUiOiJBRE0iLCJjZW50ZXJfY29kZSI6IkUwODU4Njk2MyIsInByb2Zlc3Npb25hbF9jYXRlZ29yeSI6IjMwOTM0MzAwNiIsInNlcnZpY2VfY29kZSI6IjVTMDg5IiwiZXBfY29kZSI6IjAyMDgiLCJpZGVudGlmaWVyIjpbeyJ0eXBlIjoiRE5JIiwidmFsdWUiOiI3MzI4ODIxOUEifSx7InR5cGUiOiJOVU1DT0wiLCJ2YWx1ZSI6IjIifV0sImFsdF9pZGVudGlmaWVyIjpbeyJ0eXBlIjoiTVBJIiwidmFsdWUiOiIwNjIxY2Y3ZC03ZDYzLTQ5ZWMtODAwNi04YzA1NjkyZWQzNzcifV19LCJjbGllbnRBZGRyZXNzIjoiMTAuNTMuMjU0LjE1MCIsImNsaWVudF9pZCI6ImV0Yy1jY2YtcHJlIn0.zPxoN5d8_uxdbxeH7ly9sQdlnDxNp8_eWDzNSzjzkUPsRVkLxgjQw_WcjZfns-LNgGr0FToS6uSDIC7uj2hPpKqD7HPWbI6UPa7g-43Jf9rh-KSOXEHJWhlASi2w0n3xQHFSxHDYBwhyXlvWQ_Tr1Vh37MroWmEL84fdwqhcfZHmD9Ek52EuRRrB-XkymCmN1ZvO2GPU04LbBvQxLVzdAaKRzr1cUqHTnsEdgRduyKShPcyth01L_X7m128SIwjJsa1lYxFE_DbSDKz8dPTsxoS1DgwRxeArrkMlIdTHyxmDRezZUGhamyLuWpXLDXJCFfrTUB8-j_JnVlILHeR6O";
|
|
15
16
|
|
|
16
17
|
describe("HTTP Client", () => {
|
|
17
|
-
|
|
18
|
+
const broker = createBroker();
|
|
18
19
|
let tokenManager: TokenManager;
|
|
19
20
|
let axiosInstance: AxiosInstance;
|
|
20
21
|
let axiosMockInstance: AxiosMockAdapter;
|
|
@@ -43,9 +44,7 @@ describe("HTTP Client", () => {
|
|
|
43
44
|
expect(response.data).toBe("success");
|
|
44
45
|
expect(axiosMockInstance.history.get?.length).toBe(1);
|
|
45
46
|
// @ts-ignore
|
|
46
|
-
expect(axiosMockInstance.history.get?.[0]?.headers.Authorization).toBe(
|
|
47
|
-
`Bearer ${access_token}`,
|
|
48
|
-
);
|
|
47
|
+
expect(axiosMockInstance.history.get?.[0]?.headers.Authorization).toBe(`Bearer ${access_token}`);
|
|
49
48
|
});
|
|
50
49
|
|
|
51
50
|
it("should retry request with new token if failed with 401 after token refresh, and requests after that have new token", async () => {
|
|
@@ -71,12 +70,8 @@ describe("HTTP Client", () => {
|
|
|
71
70
|
expect(response.status).toBe(200);
|
|
72
71
|
expect(response.data).toBe("success");
|
|
73
72
|
expect(axiosMockInstance.history.get?.length).toBe(3); // 3 Calls 2 for the first request (request and retry), 1 for the second request
|
|
74
|
-
expect(axiosMockInstance.history.get?.[1]?.headers?.Authorization).toBe(
|
|
75
|
-
|
|
76
|
-
);
|
|
77
|
-
expect(axiosMockInstance.history.get?.[2]?.headers?.Authorization).toBe(
|
|
78
|
-
"Bearer " + newAccessToken,
|
|
79
|
-
);
|
|
73
|
+
expect(axiosMockInstance.history.get?.[1]?.headers?.Authorization).toBe(`Bearer ${newAccessToken}`);
|
|
74
|
+
expect(axiosMockInstance.history.get?.[2]?.headers?.Authorization).toBe(`Bearer ${newAccessToken}`);
|
|
80
75
|
});
|
|
81
76
|
|
|
82
77
|
it("should fail request if token refresh fails and publish an event", async () => {
|
|
@@ -86,9 +81,7 @@ describe("HTTP Client", () => {
|
|
|
86
81
|
const brokerSpy = vi.spyOn(broker, "publish");
|
|
87
82
|
axiosMockInstance.onGet("/api/clinical-course").replyOnce(401, "Unauthorized");
|
|
88
83
|
|
|
89
|
-
await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow(
|
|
90
|
-
"Request failed with status code 401",
|
|
91
|
-
);
|
|
84
|
+
await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow("Request failed with status code 401");
|
|
92
85
|
|
|
93
86
|
expect(axiosMockInstance.history.get?.length).toBe(1); // No retry happened
|
|
94
87
|
expect(brokerSpy).toHaveBeenCalledWith(BROKER_EVENTS.shell.refreshTokenFailed, {});
|
|
@@ -121,9 +114,7 @@ describe("HTTP Client", () => {
|
|
|
121
114
|
const brokerSpy = vi.spyOn(broker, "publish");
|
|
122
115
|
|
|
123
116
|
// Perform the request
|
|
124
|
-
await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow(
|
|
125
|
-
"Mpid header is missing",
|
|
126
|
-
);
|
|
117
|
+
await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow("Mpid header is missing");
|
|
127
118
|
expect(brokerSpy).not.toHaveBeenCalled();
|
|
128
119
|
});
|
|
129
120
|
|
|
@@ -133,9 +124,7 @@ describe("HTTP Client", () => {
|
|
|
133
124
|
});
|
|
134
125
|
const brokerSpy = vi.spyOn(broker, "publish");
|
|
135
126
|
// Perform the request
|
|
136
|
-
await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow(
|
|
137
|
-
"Mpid header value is invalid",
|
|
138
|
-
);
|
|
127
|
+
await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow("Mpid header value is invalid");
|
|
139
128
|
expect(brokerSpy).toHaveBeenCalledWith(BROKER_EVENTS.shell.mpidHeaderInvalid, expect.any(Object));
|
|
140
129
|
});
|
|
141
130
|
|
|
@@ -148,9 +137,7 @@ describe("HTTP Client", () => {
|
|
|
148
137
|
expect(response.data).toBe("success");
|
|
149
138
|
expect(axiosMockInstance.history.get?.length).toBe(1);
|
|
150
139
|
// @ts-ignore
|
|
151
|
-
expect(axiosMockInstance.history.get?.[0]?.headers.Authorization).toBe(
|
|
152
|
-
`Bearer ${access_token}`,
|
|
153
|
-
);
|
|
140
|
+
expect(axiosMockInstance.history.get?.[0]?.headers.Authorization).toBe(`Bearer ${access_token}`);
|
|
154
141
|
});
|
|
155
142
|
|
|
156
143
|
it("should retry request with new token if failed with 401 after token refresh, and requests after that have new token", async () => {
|
|
@@ -177,12 +164,8 @@ describe("HTTP Client", () => {
|
|
|
177
164
|
expect(response.status).toBe(200);
|
|
178
165
|
expect(response.data).toBe("success");
|
|
179
166
|
expect(axiosMockInstance.history.get?.length).toBe(3); // 3 Calls 2 for the first request (request and retry), 1 for the second request
|
|
180
|
-
expect(axiosMockInstance.history.get?.[1]?.headers?.Authorization).toBe(
|
|
181
|
-
|
|
182
|
-
);
|
|
183
|
-
expect(axiosMockInstance.history.get?.[2]?.headers?.Authorization).toBe(
|
|
184
|
-
"Bearer " + newAccessToken,
|
|
185
|
-
);
|
|
167
|
+
expect(axiosMockInstance.history.get?.[1]?.headers?.Authorization).toBe(`Bearer ${newAccessToken}`);
|
|
168
|
+
expect(axiosMockInstance.history.get?.[2]?.headers?.Authorization).toBe(`Bearer ${newAccessToken}`);
|
|
186
169
|
});
|
|
187
170
|
|
|
188
171
|
it("should fail request if token refresh fails and publish an event", async () => {
|
|
@@ -192,9 +175,7 @@ describe("HTTP Client", () => {
|
|
|
192
175
|
const brokerSpy = vi.spyOn(broker, "publish");
|
|
193
176
|
axiosMockInstance.onGet("/api/clinical-course").replyOnce(401, "Unauthorized");
|
|
194
177
|
|
|
195
|
-
await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow(
|
|
196
|
-
"Request failed with status code 401",
|
|
197
|
-
);
|
|
178
|
+
await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow("Request failed with status code 401");
|
|
198
179
|
|
|
199
180
|
expect(axiosMockInstance.history.get?.length).toBe(1); // No retry happened
|
|
200
181
|
expect(brokerSpy).toHaveBeenCalledWith(BROKER_EVENTS.shell.refreshTokenFailed, {});
|
|
@@ -10,6 +10,9 @@ export class ImportDataManagerModal extends LitElement implements ConfirmationCo
|
|
|
10
10
|
setResult: (result: undefined) => void;
|
|
11
11
|
setIsValid: (isValid: boolean) => void;
|
|
12
12
|
confirm: () => void;
|
|
13
|
+
// Injectat pel servei d'interacció igual que la resta; faltava des que es va afegir a la
|
|
14
|
+
// interfície.
|
|
15
|
+
confirmResult: (result: undefined) => void;
|
|
13
16
|
cancel: () => void;
|
|
14
17
|
|
|
15
18
|
render() {
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
class MockInteractionService extends PrimariaInteractionService {
|
|
12
12
|
private mockConfirmResult: { confirmed: boolean; result?: unknown } = { confirmed: true };
|
|
13
13
|
|
|
14
|
+
dispose(): void {}
|
|
15
|
+
|
|
14
16
|
// Mètode per configurar el resultat del mock
|
|
15
17
|
setMockConfirmResult(result: { confirmed: boolean; result?: unknown }) {
|
|
16
18
|
this.mockConfirmResult = result;
|
|
@@ -17,6 +17,14 @@ const defaultOptions: ConfirmationOptions = {
|
|
|
17
17
|
hideIcon: false,
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Un diàleg pot ser un component de React o un web component. Només el primer es pot renderitzar
|
|
22
|
+
* com a JSX, i cal un predicat de tipus perquè TypeScript ho estrenyi al punt d'ús: guardar el
|
|
23
|
+
* resultat en un booleà no li serveix.
|
|
24
|
+
*/
|
|
25
|
+
const isWebComponentConstructor = (component: unknown): component is new () => HTMLElement =>
|
|
26
|
+
typeof component === "function" && component.prototype instanceof HTMLElement;
|
|
27
|
+
|
|
20
28
|
export class PrimariaInteractionServiceImpl extends PrimariaInteractionService {
|
|
21
29
|
private readonly openDialogCancellers = new Set<() => void>();
|
|
22
30
|
|
|
@@ -44,13 +52,13 @@ export class PrimariaInteractionServiceImpl extends PrimariaInteractionService {
|
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
const DialogWrapper = () => {
|
|
47
|
-
const [result, setResult] = useState<
|
|
55
|
+
const [result, setResult] = useState<TResult>();
|
|
48
56
|
const [isValid, setIsValid] = useState(true);
|
|
49
|
-
const dialogComponentRef = useRef(null);
|
|
57
|
+
const dialogComponentRef = useRef<HTMLDivElement>(null);
|
|
50
58
|
const dialogRef = useRef<HTMLDivElement>(null);
|
|
51
59
|
const modalRef = useRef<HTMLDivElement>(null);
|
|
52
60
|
|
|
53
|
-
const isWebComponent =
|
|
61
|
+
const isWebComponent = isWebComponentConstructor(DialogComponent);
|
|
54
62
|
|
|
55
63
|
useEffect(() => {
|
|
56
64
|
window.addEventListener("keydown", handleKeydown);
|
|
@@ -73,7 +81,7 @@ export class PrimariaInteractionServiceImpl extends PrimariaInteractionService {
|
|
|
73
81
|
handleClose(false);
|
|
74
82
|
}
|
|
75
83
|
};
|
|
76
|
-
const handleOutsideClick = (event:
|
|
84
|
+
const handleOutsideClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
|
77
85
|
if (dialogRef.current) {
|
|
78
86
|
if (event.target === modalRef.current && finalOptions.closeOnOutsideClick) {
|
|
79
87
|
handleClose(false);
|
|
@@ -81,6 +89,9 @@ export class PrimariaInteractionServiceImpl extends PrimariaInteractionService {
|
|
|
81
89
|
}
|
|
82
90
|
};
|
|
83
91
|
|
|
92
|
+
// L'efecte només ha de crear el web component quan canvia el component o les dades.
|
|
93
|
+
// Afegir-hi els handlers i `isValid` el faria recrear a cada render, perdent l'estat.
|
|
94
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: recrear-lo perdria l'estat
|
|
84
95
|
useEffect(() => {
|
|
85
96
|
if (isWebComponent && dialogComponentRef.current) {
|
|
86
97
|
const parent = dialogComponentRef.current;
|
|
@@ -113,7 +124,7 @@ export class PrimariaInteractionServiceImpl extends PrimariaInteractionService {
|
|
|
113
124
|
resolvePromise(finalResult, true);
|
|
114
125
|
};
|
|
115
126
|
|
|
116
|
-
const resolvePromise = (finalResult: TResult, confirmed: boolean) => {
|
|
127
|
+
const resolvePromise = (finalResult: TResult | undefined, confirmed: boolean) => {
|
|
117
128
|
if (isClosing) return;
|
|
118
129
|
|
|
119
130
|
isClosing = true;
|
|
@@ -153,7 +164,7 @@ export class PrimariaInteractionServiceImpl extends PrimariaInteractionService {
|
|
|
153
164
|
};
|
|
154
165
|
|
|
155
166
|
const _renderContent = () => {
|
|
156
|
-
return
|
|
167
|
+
return isWebComponentConstructor(DialogComponent) ? (
|
|
157
168
|
<div ref={dialogComponentRef} />
|
|
158
169
|
) : (
|
|
159
170
|
<DialogComponent
|
|
@@ -181,7 +192,7 @@ export class PrimariaInteractionServiceImpl extends PrimariaInteractionService {
|
|
|
181
192
|
fill
|
|
182
193
|
class={`dss-dialog-header-state--${getHeadIconVariantClass()}`}
|
|
183
194
|
icon={getHeadIcon()}
|
|
184
|
-
|
|
195
|
+
/>
|
|
185
196
|
)}
|
|
186
197
|
{finalOptions.title}
|
|
187
198
|
</div>
|
|
@@ -193,7 +204,7 @@ export class PrimariaInteractionServiceImpl extends PrimariaInteractionService {
|
|
|
193
204
|
variant="neutral"
|
|
194
205
|
icon="close"
|
|
195
206
|
onClick={() => handleClose(false)}
|
|
196
|
-
|
|
207
|
+
/>
|
|
197
208
|
)}
|
|
198
209
|
</div>
|
|
199
210
|
)}
|
|
@@ -11,44 +11,48 @@ import { getLanguage, getLocales, localizerFactory, setLocales } from "@uxland/l
|
|
|
11
11
|
* getCurrentLanguage: () => string,
|
|
12
12
|
* }>} A promise that resolves to an object with methods for translating strings and getting translations.
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return Promise.resolve({
|
|
19
|
-
/**
|
|
20
|
-
* Translates the given path using the provided variables and the plugin's ID.
|
|
21
|
-
*
|
|
22
|
-
* @param {string} path - The path to be translated.
|
|
23
|
-
* @param {Record<string, string>} [variables] - Optional variables to be used in the translation.
|
|
24
|
-
* @return {string} The translated string, or the original path if an error occurs.
|
|
25
|
-
*/
|
|
26
|
-
translate: (path: string, variables?: Record<string, string>) => {
|
|
27
|
-
try {
|
|
28
|
-
return localizer(`${pluginId}.${path}`, variables);
|
|
29
|
-
} catch (e) {
|
|
30
|
-
console.error(e);
|
|
31
|
-
return path;
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
/**
|
|
35
|
-
* Retrieves the translations for the current language and plugin.
|
|
36
|
-
*
|
|
37
|
-
* @return {Record<string, string>} The translations for the current language and plugin.
|
|
38
|
-
*/
|
|
39
|
-
getTranslations: () => {
|
|
40
|
-
const translations = getLocales();
|
|
41
|
-
const language = getLanguage();
|
|
42
|
-
return translations[language][pluginId] || {};
|
|
43
|
-
},
|
|
14
|
+
/** Arbre de traduccions: claus imbricades fins a arribar al text. */
|
|
15
|
+
export interface LocalizationMessages {
|
|
16
|
+
[key: string]: string | LocalizationMessages;
|
|
17
|
+
}
|
|
44
18
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
19
|
+
export const createLocaleManager = (pluginId: string) => (translations: Record<string, LocalizationMessages>) => {
|
|
20
|
+
const localizer = localizerFactory(getLanguage(), translations, "", true);
|
|
21
|
+
setLocales(translations);
|
|
22
|
+
return Promise.resolve({
|
|
23
|
+
/**
|
|
24
|
+
* Translates the given path using the provided variables and the plugin's ID.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} path - The path to be translated.
|
|
27
|
+
* @param {Record<string, string>} [variables] - Optional variables to be used in the translation.
|
|
28
|
+
* @return {string} The translated string, or the original path if an error occurs.
|
|
29
|
+
*/
|
|
30
|
+
translate: (path: string, variables?: Record<string, string>) => {
|
|
31
|
+
try {
|
|
32
|
+
return localizer(`${pluginId}.${path}`, variables);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
console.error(e);
|
|
35
|
+
return path;
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
/**
|
|
39
|
+
* Retrieves the translations for the current language and plugin.
|
|
40
|
+
*
|
|
41
|
+
* @return {Record<string, string>} The translations for the current language and plugin.
|
|
42
|
+
*/
|
|
43
|
+
getTranslations: () => {
|
|
44
|
+
const translations = getLocales();
|
|
45
|
+
const language = getLanguage();
|
|
46
|
+
return translations[language][pluginId] || {};
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Retrieves the current language.
|
|
51
|
+
*
|
|
52
|
+
* @return {string} The current language.
|
|
53
|
+
*/
|
|
54
|
+
getCurrentLanguage: () => {
|
|
55
|
+
return getLanguage();
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
1
2
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
3
|
import { PrimariaNotificationServiceImpl } from "./notification.service-impl";
|
|
3
4
|
|
|
@@ -15,7 +16,7 @@ describe("PrimariaNotificationServiceImpl", () => {
|
|
|
15
16
|
vi.useRealTimers();
|
|
16
17
|
});
|
|
17
18
|
|
|
18
|
-
const assertToast = (expectedState: string, message: string, duration
|
|
19
|
+
const assertToast = (expectedState: string, message: string, duration = 5000) => {
|
|
19
20
|
const toast = document.querySelector("dss-toast") as HTMLElement;
|
|
20
21
|
expect(toast).not.toBeNull();
|
|
21
22
|
expect(toast.getAttribute("isshow")).toBe("true");
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
3
|
import { BROKER_EVENTS } from "../broker/broker-events";
|
|
4
|
+
import { createRegionManagerProxy } from "./region-manager";
|
|
4
5
|
|
|
5
6
|
const pluginId = "test-plugin";
|
|
6
|
-
const mockView = { id: "view1" };
|
|
7
|
+
const mockView = { id: "view1", factory: () => Promise.resolve(document.createElement("div")) };
|
|
7
8
|
|
|
8
9
|
const createMockRegion = () => ({
|
|
9
10
|
activate: vi.fn(),
|
|
@@ -26,6 +27,7 @@ describe("RegionManagerProxy", () => {
|
|
|
26
27
|
registerViewWithRegion: vi.fn(),
|
|
27
28
|
unregisterViewFromRegion: vi.fn(),
|
|
28
29
|
getRegion: vi.fn().mockReturnValue(regionMock),
|
|
30
|
+
getRegisteredViews: vi.fn().mockReturnValue([]),
|
|
29
31
|
destroy: vi.fn(),
|
|
30
32
|
};
|
|
31
33
|
|
|
@@ -127,4 +129,58 @@ describe("RegionManagerProxy", () => {
|
|
|
127
129
|
proxy._destroy();
|
|
128
130
|
expect(regionManager.destroy).toHaveBeenCalled();
|
|
129
131
|
});
|
|
132
|
+
|
|
133
|
+
describe("_removeAllViews", () => {
|
|
134
|
+
/**
|
|
135
|
+
* Es crida en tancar, abans del `dispose` dels plugins, perque els cleanups dels `useEffect`
|
|
136
|
+
* trobin els handlers del broker encara registrats. Ha d'esperar cada desmuntatge: el
|
|
137
|
+
* `clear()`/`destroy()` del regionManager fan `forEach` amb crides asincrones sense
|
|
138
|
+
* esperar-les, i per aixo no serveixen.
|
|
139
|
+
*/
|
|
140
|
+
it("desregistra totes les vistes de totes les regions que tenen", async () => {
|
|
141
|
+
regionManager.getRegisteredViews = vi.fn((regionName: string) =>
|
|
142
|
+
regionName === "main-region"
|
|
143
|
+
? [
|
|
144
|
+
{ key: "a::v1", view: mockView },
|
|
145
|
+
{ key: "b::v2", view: mockView },
|
|
146
|
+
]
|
|
147
|
+
: [],
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
await proxy._removeAllViews();
|
|
151
|
+
|
|
152
|
+
expect(regionManager.unregisterViewFromRegion).toHaveBeenCalledWith("main-region", "a::v1");
|
|
153
|
+
expect(regionManager.unregisterViewFromRegion).toHaveBeenCalledWith("main-region", "b::v2");
|
|
154
|
+
expect(regionManager.unregisterViewFromRegion).toHaveBeenCalledTimes(2);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("espera cada desmuntatge abans de continuar amb el seguent", async () => {
|
|
158
|
+
const order: string[] = [];
|
|
159
|
+
regionManager.getRegisteredViews = vi.fn((regionName: string) =>
|
|
160
|
+
regionName === "main-region"
|
|
161
|
+
? [
|
|
162
|
+
{ key: "a::v1", view: mockView },
|
|
163
|
+
{ key: "b::v2", view: mockView },
|
|
164
|
+
]
|
|
165
|
+
: [],
|
|
166
|
+
);
|
|
167
|
+
regionManager.unregisterViewFromRegion = vi.fn(async (_region: string, key: string) => {
|
|
168
|
+
order.push(`start:${key}`);
|
|
169
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
170
|
+
order.push(`done:${key}`);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
await proxy._removeAllViews();
|
|
174
|
+
|
|
175
|
+
expect(order).toEqual(["start:a::v1", "done:a::v1", "start:b::v2", "done:b::v2"]);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("no fa res si cap regio te vistes", async () => {
|
|
179
|
+
regionManager.getRegisteredViews = vi.fn().mockReturnValue([]);
|
|
180
|
+
|
|
181
|
+
await proxy._removeAllViews();
|
|
182
|
+
|
|
183
|
+
expect(regionManager.unregisterViewFromRegion).not.toHaveBeenCalled();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
130
186
|
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { HarmonixRegionManager,
|
|
2
|
-
import { PluginInfo } from "../../handle-plugins";
|
|
1
|
+
import { HarmonixRegionManager, HarmonixViewDefinition, IRegionManager } from "@uxland/harmonix";
|
|
3
2
|
import { IRegion } from "@uxland/regions";
|
|
4
|
-
import {
|
|
5
|
-
import { PrimariaBroker } from "../broker/primaria-broker";
|
|
3
|
+
import { PluginInfo } from "../../handle-plugins";
|
|
6
4
|
import { BROKER_EVENTS } from "../broker/broker-events";
|
|
5
|
+
import { PrimariaBroker } from "../broker/primaria-broker";
|
|
6
|
+
import { activityHistoryRegions, administrativeManagementRegions, clinicalMonitoringRegions, shellRegions } from "./regions";
|
|
7
7
|
|
|
8
8
|
export interface PrimariaRegionManager extends HarmonixRegionManager {
|
|
9
9
|
regions: {
|
|
@@ -18,9 +18,18 @@ export interface PrimariaRegionManager extends HarmonixRegionManager {
|
|
|
18
18
|
activateMainView(viewId: string): Promise<void>;
|
|
19
19
|
activatePluginView(regionName: string, pluginId: string, viewId: string): Promise<void>;
|
|
20
20
|
getCurrentMainViewActive(): string | undefined;
|
|
21
|
-
|
|
21
|
+
_removeAllViews(): Promise<void>;
|
|
22
|
+
_destroy(): Promise<void>;
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
/** Totes les regions que coneix el shell, per poder-les recórrer al tancar. */
|
|
26
|
+
const ALL_REGION_NAMES: string[] = [
|
|
27
|
+
...Object.values(shellRegions),
|
|
28
|
+
...Object.values(clinicalMonitoringRegions),
|
|
29
|
+
...Object.values(activityHistoryRegions),
|
|
30
|
+
...Object.values(administrativeManagementRegions),
|
|
31
|
+
];
|
|
32
|
+
|
|
24
33
|
class RegionManagerProxy implements PrimariaRegionManager {
|
|
25
34
|
regions = {
|
|
26
35
|
shell: shellRegions,
|
|
@@ -109,14 +118,24 @@ class RegionManagerProxy implements PrimariaRegionManager {
|
|
|
109
118
|
*/
|
|
110
119
|
isViewActive(regionName: string, viewId: string) {
|
|
111
120
|
const region = this.regionManager.getRegion(regionName);
|
|
112
|
-
|
|
121
|
+
// `ViewDefinition` de @uxland/regions no declara `id`, però el shell només hi registra
|
|
122
|
+
// `HarmonixViewDefinition`, que sí que en porta.
|
|
123
|
+
const isViewActive = region.currentActiveViews.some((view) => (view as HarmonixViewDefinition).id === viewId);
|
|
113
124
|
return Promise.resolve(isViewActive);
|
|
114
125
|
}
|
|
115
126
|
async registerQuickAction(view: HarmonixViewDefinition): Promise<void> {
|
|
116
|
-
await this.regionManager.registerViewWithRegion(
|
|
127
|
+
await this.regionManager.registerViewWithRegion(
|
|
128
|
+
this.regions.shell.quickActions,
|
|
129
|
+
`${this.pluginInfo.pluginId}::${view.id}`,
|
|
130
|
+
view,
|
|
131
|
+
);
|
|
117
132
|
}
|
|
118
133
|
async registerNavigationMenu(view: HarmonixViewDefinition): Promise<void> {
|
|
119
|
-
await this.regionManager.registerViewWithRegion(
|
|
134
|
+
await this.regionManager.registerViewWithRegion(
|
|
135
|
+
this.regions.shell.navigationMenu,
|
|
136
|
+
`${this.pluginInfo.pluginId}::${view.id}`,
|
|
137
|
+
view,
|
|
138
|
+
);
|
|
120
139
|
}
|
|
121
140
|
async registerMainView(view: HarmonixViewDefinition): Promise<void> {
|
|
122
141
|
await this.regionManager.registerViewWithRegion(this.regions.shell.main, `${this.pluginInfo.pluginId}::${view.id}`, view);
|
|
@@ -173,8 +192,31 @@ class RegionManagerProxy implements PrimariaRegionManager {
|
|
|
173
192
|
this.broker.publish(BROKER_EVENTS.shell.mainViewChanged, { viewId });
|
|
174
193
|
}
|
|
175
194
|
|
|
176
|
-
|
|
177
|
-
|
|
195
|
+
/**
|
|
196
|
+
* Desmunta totes les vistes de totes les regions, i espera que acabin.
|
|
197
|
+
*
|
|
198
|
+
* Es crida en tancar, ABANS de fer el `dispose` dels plugins: els cleanups dels `useEffect` que
|
|
199
|
+
* s'executen en desmuntar-se poden enviar comandes pel broker, i han de trobar el handler encara
|
|
200
|
+
* registrat. Cada plugin ja ho fa pel seu compte al `dispose`, però això només cobreix els
|
|
201
|
+
* plugins d'aquest repositori; els de tercers (econsulta, allergies…) no els controlem.
|
|
202
|
+
*
|
|
203
|
+
* Fa servir `unregisterViewFromRegion` i no `destroy()`/`clear()` del regionManager perquè
|
|
204
|
+
* aquests fan `forEach` amb crides asíncrones sense esperar-les, així que retornen abans que les
|
|
205
|
+
* vistes s'hagin desmuntat de debò.
|
|
206
|
+
*
|
|
207
|
+
* És idempotent: desregistrar una vista que ja no hi és no fa res.
|
|
208
|
+
*/
|
|
209
|
+
async _removeAllViews(): Promise<void> {
|
|
210
|
+
for (const regionName of ALL_REGION_NAMES) {
|
|
211
|
+
const registered = this.regionManager.getRegisteredViews(regionName) ?? [];
|
|
212
|
+
for (const { key } of [...registered]) {
|
|
213
|
+
await this.regionManager.unregisterViewFromRegion(regionName, key);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async _destroy() {
|
|
219
|
+
await this.regionManager.destroy();
|
|
178
220
|
}
|
|
179
221
|
}
|
|
180
222
|
|