@uxland/primary-shell 5.0.0 → 5.1.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uxland/primary-shell",
3
- "version": "5.0.0",
3
+ "version": "5.1.0",
4
4
  "description": "Primaria Shell",
5
5
  "author": "UXLand <dev@uxland.es>",
6
6
  "homepage": "https://github.com/uxland/harmonix/tree/app#readme",
@@ -44,7 +44,7 @@
44
44
  "react-dom": "^19.0.0",
45
45
  "@types/react": "^19.0.12",
46
46
  "inversify-inject-decorators": "^3.1.0",
47
- "@salut/design-system-salut": "../../design-system-salut-2.2.1.tgz",
47
+ "@salut/design-system-salut": "../../design-system-salut-2.3.0.tgz",
48
48
  "jwt-decode": "^4.0.0",
49
49
  "lit": "^3.1.0",
50
50
  "mediatr-ts": "^1.2.1",
@@ -9,15 +9,17 @@
9
9
  min-height: 1px;
10
10
  height: 100%;
11
11
  display: flex;
12
- flex-direction: row;
12
+ flex-direction: row;
13
+
13
14
  #widgets-sidebar-region {
15
+ z-index: 1;
14
16
  width: 25%;
15
17
  border-left: 1px solid rgb(189, 189, 189);
16
18
  }
17
19
  .content {
18
20
  flex: 1;
19
21
  display: flex;
20
- flex-direction: column;
22
+ flex-direction: column;
21
23
  #header-widgets-region {
22
24
  display: grid;
23
25
  grid-template-columns: repeat(3, 1fr);
@@ -67,6 +67,9 @@ export class PrimariaShell extends PrimariaRegionHost(LitElement) {
67
67
  @state()
68
68
  error: { message: string };
69
69
 
70
+ @state()
71
+ quickActionBusy = false;
72
+
70
73
  _toggleSidebar() {
71
74
  this.sidebarExpanded = !this.sidebarExpanded;
72
75
  }
@@ -91,6 +94,12 @@ export class PrimariaShell extends PrimariaRegionHost(LitElement) {
91
94
  this._handleError({ message: translate("errors.invalidPatient") });
92
95
  }),
93
96
  );
97
+
98
+ this.subscriptions.push(
99
+ shellApi.broker.subscribe(shellEvents.quickActionBusyChanged, (detail: { busy: boolean }) => {
100
+ this.quickActionBusy = detail.busy;
101
+ }),
102
+ );
94
103
  }
95
104
 
96
105
  _handleError(error: { message: string }) {
@@ -29,15 +29,28 @@
29
29
  width: 40px;
30
30
  height: 100%;
31
31
  color: white;
32
- .top-content {
32
+ .quick-actions-content {
33
33
  padding-top: 24px;
34
- padding-bottom: 24px;
34
+ padding-bottom: 22px;
35
35
  border-radius: 8px;
36
+ .create-button-icon-badge{
37
+ position: relative;
38
+ dss-notification-badge {
39
+ position: absolute;
40
+ top: -7px;
41
+ right: -6px;
42
+ z-index: 1;
43
+ }
44
+ dss-icon {
45
+ position: relative;
46
+ }
47
+ }
36
48
  }
37
49
  .icon {
38
50
  margin-left: 8px;
39
51
  }
40
52
  #menu-region-container {
53
+ padding-top: 24px;
41
54
  min-height: 1px;
42
55
  height: 100%;
43
56
  width: 100%;
@@ -13,10 +13,15 @@ export const template = (props: PrimariaShell) => html`
13
13
  <shell-header></shell-header>
14
14
  <div class="main-container">
15
15
  <div class="sidebar" ?expanded=${props.sidebarExpanded}>
16
- <div class="top-content">
16
+ <div id="menu-region-container"></div>
17
+ <div class="quick-actions-content">
17
18
  <quick-actions-menu>
18
19
  <div class="${props.sidebarExpanded ? "create-button-opened" : "create-button-closed"} " slot="anchor" id="usage-anchor">
19
- <dss-icon class="${props.sidebarExpanded ? "icon" : ""}" icon="add_circle_outline" size="md"></dss-icon>${props.sidebarExpanded ? translate('actions.create') : nothing}
20
+ <div class="${props.quickActionBusy ? "create-button-icon-badge" : ""}">
21
+ ${props.quickActionBusy ? html`<dss-notification-badge value="" state="error" borderwhite="" type="default" dot=""></dss-notification-badge>`: nothing}
22
+ <dss-icon class="${props.sidebarExpanded ? "icon" : ""}" icon="add_circle_outline" size="md"></dss-icon>
23
+ </div>
24
+ ${props.sidebarExpanded ? translate('actions.create') : nothing}
20
25
  ${!props.sidebarExpanded ? html`<dss-tooltip position="right">
21
26
  ${translate('actions.create')}
22
27
  </dss-tooltip>` : nothing}
@@ -24,7 +29,6 @@ export const template = (props: PrimariaShell) => html`
24
29
  <dss-action-menu id="quick-actions-region-container" slot="content" id="content" anchor="usage-anchor"></dss-action-menu>
25
30
  </quick-actions-menu>
26
31
  </div>
27
- <div id="menu-region-container"></div>
28
32
  <div class="bottom-content">
29
33
  <div class="bottom-content-first">
30
34
  <hr class="dss-divider--bold ${props.sidebarExpanded ? "divider-opened" : "divider-closed"}" />
@@ -21,7 +21,7 @@ export const template = (props: ShellHeader) => {
21
21
  <div id="header-actions-region-container"></div>
22
22
  ${when(
23
23
  props.professional,
24
- () => html`<dss-header-menu-professional @onLogout=${props.logout} slot="professional-menu" name="${props.professional.firstName} ${props.professional?.familyName} ${props.professional?.lastName}" center="${props.professional.workCenter}" collegiate="${props.professional.registrationNumber}">
24
+ () => html`<dss-header-menu-professional @onExit=${props.logout} slot="professional-menu" name="${props.professional.firstName} ${props.professional?.familyName} ${props.professional?.lastName}" center="${props.professional.workCenter}" collegiate="${props.professional.registrationNumber}">
25
25
  <dss-avatar size="xl" name="${props.professional.firstName}" surname="${props.professional?.familyName}" slot="avatar"></dss-avatar>
26
26
  <dss-input-dropdown icon="maps_home_work" type="default" .elements=${workCenterElements} selectedvalue="[&quot;1&quot;]">
27
27
  <label slot="label" for="preferences1">${translate("header.workCenter")}</label>
package/src/api/api.ts CHANGED
@@ -40,7 +40,7 @@ const regionManager: IRegionManager = createRegionManager("primaria");
40
40
  export const PrimariaRegionHost: any = createRegionHost(regionManager as any);
41
41
  const tokenManager = createTokenManager();
42
42
  const globalStateManager: PrimariaGlobalStateManager = createGlobalStateManager(broker);
43
- const pluginBusyManager = new PluginBusyManagerImpl();
43
+ const pluginBusyManager = new PluginBusyManagerImpl(broker);
44
44
  const interactionService = new ParimariaInteractionServiceImpl();
45
45
  const notificationService = new PrimariaNotificationServiceImpl();
46
46
 
@@ -9,6 +9,7 @@ import * as React from "react";
9
9
  import { useEffect, useState, useRef } from "react";
10
10
  import { createRoot } from "react-dom/client";
11
11
  import { ConfirmationMessage } from "./confirmation-message.tsx";
12
+ import modalStyles from './modal-styles.css?raw';
12
13
 
13
14
  const defaultOptions: ConfirmationOptions = {
14
15
  title: "Confirmació",
@@ -17,8 +18,8 @@ const defaultOptions: ConfirmationOptions = {
17
18
  confirmButtonText: "Acceptar",
18
19
  cancelButtonText: "Cancel·lar",
19
20
  showCloseButton: true,
20
- fullWidth: false,
21
21
  fullCustomization: false,
22
+ closeOnOutsideClick: false,
22
23
  };
23
24
 
24
25
  export class ParimariaInteractionServiceImpl extends PrimariaInteractionService {
@@ -32,32 +33,39 @@ export class ParimariaInteractionServiceImpl extends PrimariaInteractionService
32
33
  const finalOptions: ConfirmationOptions = { ...defaultOptions, ...(options || {}) };
33
34
  return new Promise((resolve) => {
34
35
  const div = document.createElement("div");
35
- const shadowRoot = div.attachShadow({ mode: "open" });
36
-
37
36
  document.body.appendChild(div);
37
+ const style = document.createElement("style");
38
+ style.textContent = modalStyles;
39
+
40
+ document.head.appendChild(style);
38
41
 
39
42
  const DialogWrapper = () => {
40
- const [open, setOpen] = useState(true);
41
43
  const [result, setResult] = useState<ConfirmationResult<TResult | undefined>>();
42
44
  const [isValid, setIsValid] = useState(true);
43
- const dssModalComponentRef = useRef(null);
44
45
  const dialogComponentRef = useRef(null);
46
+ const dialogRef = useRef<HTMLDivElement>(null);
47
+ const modalRef = useRef<HTMLDivElement>(null);
45
48
 
46
49
  const isWebComponent =
47
50
  typeof DialogComponent === "function" && DialogComponent.prototype instanceof HTMLElement;
48
51
 
49
52
  useEffect(() => {
50
- const dssModalComponent = dssModalComponentRef.current;
51
- const handleCloseDialog = (e: Event) => {
52
- e.preventDefault();
53
- handleClose(false);
54
- };
55
- dssModalComponent?.addEventListener("onModalClosed", handleCloseDialog);
56
- return () => {
57
- document.body.removeChild(div);
58
- };
53
+ window.addEventListener('keydown', handleKeydown);
59
54
  }, []);
60
55
 
56
+ const handleKeydown = (event: KeyboardEvent) => {
57
+ if (event.key === 'Escape') {
58
+ handleClose(false);
59
+ }
60
+ };
61
+ const handleOutsideClick = (event: Event) => {
62
+ if (dialogRef.current) {
63
+ if (event.target === modalRef.current && finalOptions.closeOnOutsideClick) {
64
+ handleClose(false);
65
+ }
66
+ }
67
+ };
68
+
61
69
  useEffect(() => {
62
70
  if (isWebComponent && dialogComponentRef.current) {
63
71
  const parent = dialogComponentRef.current;
@@ -91,7 +99,6 @@ export class ParimariaInteractionServiceImpl extends PrimariaInteractionService
91
99
  };
92
100
 
93
101
  const resolvePromise = (finalResult: TResult, confirmed: boolean) => {
94
- setOpen(false);
95
102
  setTimeout(() => {
96
103
  const confirmationResult = {
97
104
  result: confirmed ? finalResult : undefined,
@@ -99,6 +106,8 @@ export class ParimariaInteractionServiceImpl extends PrimariaInteractionService
99
106
  };
100
107
  resolve(confirmationResult);
101
108
  document.body.removeChild(div);
109
+ document.head.removeChild(style);
110
+ window.removeEventListener('keydown', handleKeydown);
102
111
  }, 300);
103
112
  };
104
113
 
@@ -130,88 +139,57 @@ export class ParimariaInteractionServiceImpl extends PrimariaInteractionService
130
139
  />
131
140
  );
132
141
  };
133
-
134
- if (finalOptions.fullCustomization) {
135
- return (
136
- <div className="modal" hidden={!open}>
137
- <div className="dialog">{_renderContent()}</div>
138
- </div>
139
- );
140
- }
141
142
  return (
142
- <dss-modal
143
- modalTitle={finalOptions.title}
144
- open={open}
145
- fullWidth={finalOptions.fullWidth}
146
- ref={dssModalComponentRef}
147
- hideCloseIcon={!finalOptions.showCloseButton}
148
- modalStyle="padding:0px;"
149
- >
150
- <div slot="body">{_renderContent()}</div>
151
- <div
152
- className="dss-modal-footer"
153
- slot="footer"
154
- hidden={!finalOptions.showCancelButton && !finalOptions.showConfirmButton}
155
- >
156
- {finalOptions.showCancelButton && (
157
- <dss-button
158
- label={finalOptions.cancelButtonText}
159
- onClick={() => handleClose(false)}
160
- size="md"
161
- variant={getCancelButtonVariant()}
162
- />
143
+ <div className="modal" ref={modalRef} onClick={handleOutsideClick} >
144
+ <div className="dialog" ref={dialogRef}>
145
+ {!finalOptions.fullCustomization && (
146
+ <div className="dss-dialog-header">
147
+ <div className="dss-dialog-header-title">{finalOptions.title}</div>
148
+ {finalOptions.showCloseButton && (
149
+ <dss-icon-button
150
+ class="dss-dialog-header-close"
151
+ size="md"
152
+ variant="neutral"
153
+ icon="close"
154
+ onClick={() => handleClose(false)}
155
+ ></dss-icon-button>
156
+ )}
157
+ </div>
163
158
  )}
164
- {finalOptions.showConfirmButton && (
165
- <dss-button
166
- onClick={() => isValid && handleClose(true)}
167
- label={finalOptions.confirmButtonText}
168
- disabled={!isValid}
169
- size="md"
170
- variant={getConfirmButtonVariant()}
171
- />
159
+ <div className={`modal-content ${finalOptions.fullCustomization ? 'full-customization' : ''}`}>
160
+ {_renderContent()}
161
+ </div>
162
+ {!finalOptions.fullCustomization && (
163
+ <div
164
+ className="dss-modal-footer"
165
+ hidden={!finalOptions.showCancelButton && !finalOptions.showConfirmButton}
166
+ >
167
+ {finalOptions.showCancelButton && (
168
+ <dss-button
169
+ label={finalOptions.cancelButtonText}
170
+ onClick={() => handleClose(false)}
171
+ size="md"
172
+ variant={getCancelButtonVariant()}
173
+ />
174
+ )}
175
+ {finalOptions.showConfirmButton && (
176
+ <dss-button
177
+ onClick={() => isValid && handleClose(true)}
178
+ label={finalOptions.confirmButtonText}
179
+ disabled={!isValid}
180
+ size="md"
181
+ variant={getConfirmButtonVariant()}
182
+ />
183
+ )}
184
+ </div>
172
185
  )}
173
186
  </div>
174
- </dss-modal>
187
+ </div>
175
188
  );
176
189
  };
177
190
 
178
- // Agregar estilos al Shadow DOM
179
- const style = document.createElement("style");
180
- style.textContent = `
181
- .modal {
182
- font-family: "Open Sans";
183
- display: block;
184
- position: fixed;
185
- z-index: 400;
186
- left: 0;
187
- top: 0;
188
- width: 100%;
189
- height: 100%;
190
- background-color: rgba(0, 0, 0, 0.65);
191
-
192
- .dialog {
193
- display: flex;
194
- flex-direction: column;
195
- position: absolute;
196
- top: 50%;
197
- left: 50%;
198
- transform: translate(-50%, -50%);
199
- z-index: 21;
200
- background: #fff;
201
- border-radius: 16px;
202
- width: auto;
203
- height: auto;
204
- max-height: 95%;
205
- min-width: 400px;
206
- box-shadow:
207
- 0px 0px 14px rgb(0 0 0 / 25%),
208
- 0px 1px 10px rgb(0 0 0 / 22%);
209
- }
210
- }
211
- `;
212
-
213
- shadowRoot.appendChild(style);
214
- const root = createRoot(shadowRoot);
191
+
192
+ const root = createRoot(div);
215
193
  root.render(<DialogWrapper />);
216
194
  });
217
195
  }
@@ -26,8 +26,8 @@ export interface ConfirmationOptions {
26
26
  confirmButtonText?: string | undefined;
27
27
  cancelButtonText?: string | undefined;
28
28
  showCloseButton?: boolean | undefined;
29
- fullWidth?: boolean | undefined;
30
29
  fullCustomization?: boolean | undefined;
30
+ closeOnOutsideClick?: boolean | undefined;
31
31
  state?: "success" | "info" | "alert" | "error";
32
32
  }
33
33
 
@@ -0,0 +1,69 @@
1
+ .modal {
2
+ font-family: "Open Sans";
3
+ display: block;
4
+ position: fixed;
5
+ z-index: 400;
6
+ left: 0;
7
+ top: 0;
8
+ width: 100%;
9
+ height: 100%;
10
+ background-color: rgba(0, 0, 0, 0.65);
11
+ }
12
+ .dialog {
13
+ display: flex;
14
+ flex-direction: column;
15
+ position: absolute;
16
+ top: 50%;
17
+ left: 50%;
18
+ transform: translate(-50%, -50%);
19
+ z-index: 21;
20
+ background: #fff;
21
+ border-radius: 16px;
22
+ width: auto;
23
+ height: auto;
24
+ max-height: 95%;
25
+ min-width: 400px;
26
+ box-shadow: 0 8px 12px 6px rgba(0 0 0 / 5%), 0 4px 4px rgba(0 0 0 / 10%);
27
+ }
28
+ .dss-dialog-header {
29
+ position: relative;
30
+ display: flex;
31
+ flex-direction: row;
32
+ justify-content: center;
33
+ align-items: center;
34
+ padding: 24px;
35
+ border-top-left-radius: 16px;
36
+ border-top-right-radius: 16px;
37
+ }
38
+ .dss-dialog-header-title {
39
+ display: flex;
40
+ flex-direction: column;
41
+ justify-content: center;
42
+ align-items: center;
43
+ gap: var(--dss-spacing-xs);
44
+ font-size: 20px;
45
+ line-height: 30px;
46
+ font-weight: var(--font-bold);
47
+ color: var(--color-neutral-900);
48
+ }
49
+ .dss-dialog-header-close {
50
+ position: absolute;
51
+ top: var(--dss-spacing-lg);
52
+ right: calc(var(--dss-spacing-lg) - 4px);
53
+ }
54
+ .modal-content {
55
+ flex: 1;
56
+ padding: 0 24px;
57
+ }
58
+ .modal-content.full-customization {
59
+ padding: 0;
60
+ }
61
+ .dss-modal-footer {
62
+ display: flex;
63
+ justify-content: center;
64
+ align-items: center;
65
+ gap: 16px;
66
+ padding: 24px;
67
+ border-bottom-left-radius: 16px;
68
+ border-bottom-right-radius: 16px;
69
+ }
@@ -1,49 +1,111 @@
1
- import { describe, it, expect, beforeEach } from "vitest";
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
  import { PluginBusyManagerImpl } from "./plugin-busy-manager";
3
+ import { shellEvents } from "../../events";
3
4
 
4
5
  describe("PluginBusyManagerImpl", () => {
6
+ let brokerMock: any;
5
7
  let manager: PluginBusyManagerImpl;
6
8
 
7
9
  beforeEach(() => {
8
- manager = new PluginBusyManagerImpl();
10
+ brokerMock = {
11
+ publish: vi.fn(),
12
+ };
13
+ manager = new PluginBusyManagerImpl(brokerMock);
9
14
  });
10
15
 
11
- it("should start with no busy plugins", () => {
12
- expect(manager.getBusyPluginTasks()).toEqual([]);
13
- expect(manager.isAnyPluginBusy()).toBe(false);
16
+ describe("addBusyPluginTask", () => {
17
+ it("should add a plugin busy task", () => {
18
+ const task = { taskId: "1", taskDescription: "Loading plugin" };
19
+ manager.addBusyPluginTask(task);
20
+ expect(manager.getBusyPluginTasks()).toContain(task);
21
+ });
14
22
  });
15
23
 
16
- it("should add a busy plugin", () => {
17
- const task = { taskId: "plugin1", taskDescription: "Description for plugin1" };
18
- manager.addBusyPluginTask(task);
19
- expect(manager.getBusyPluginTasks()).toContainEqual(task);
20
- expect(manager.isAnyPluginBusy()).toBe(true);
24
+ describe("removeBusyPluginTask", () => {
25
+ it("should remove a plugin busy task if it exists", () => {
26
+ const task = { taskId: "1", taskDescription: "Loading plugin" };
27
+ manager.addBusyPluginTask(task);
28
+ manager.removeBusyPluginTask("1");
29
+ expect(manager.getBusyPluginTasks()).not.toContain(task);
30
+ });
31
+
32
+ it("should do nothing if task does not exist", () => {
33
+ const task = { taskId: "1", taskDescription: "Loading plugin" };
34
+ manager.addBusyPluginTask(task);
35
+ manager.removeBusyPluginTask("non-existent");
36
+ expect(manager.getBusyPluginTasks()).toContain(task);
37
+ });
38
+ });
39
+
40
+ describe("addBusyQuickActionTask", () => {
41
+ it("should add a quick action busy task and emit event", () => {
42
+ const task = { taskId: "qa1" };
43
+ manager.addBusyQuickActionTask(task);
44
+ expect(brokerMock.publish).toHaveBeenCalledWith(shellEvents.quickActionBusyChanged, {
45
+ busy: true,
46
+ });
47
+ });
48
+ });
49
+
50
+ describe("removeBusyQuickActionTask", () => {
51
+ it("should remove a quick action task and emit event", () => {
52
+ const task = { taskId: "qa1" };
53
+ manager.addBusyQuickActionTask(task);
54
+ manager.removeBusyQuickActionTask("qa1");
55
+ expect(brokerMock.publish).toHaveBeenCalledWith(shellEvents.quickActionBusyChanged, {
56
+ busy: false,
57
+ });
58
+ });
59
+
60
+ it("should do nothing and not emit event if task does not exist", () => {
61
+ manager.removeBusyQuickActionTask("non-existent");
62
+ expect(brokerMock.publish).not.toHaveBeenCalled();
63
+ });
64
+ });
65
+
66
+ describe("isAnyPluginBusy", () => {
67
+ it("should return false when no plugin tasks exist", () => {
68
+ expect(manager.isAnyPluginBusy()).toBe(false);
69
+ });
70
+
71
+ it("should return true when plugin tasks exist", () => {
72
+ manager.addBusyPluginTask({ taskId: "1", taskDescription: "Busy" });
73
+ expect(manager.isAnyPluginBusy()).toBe(true);
74
+ });
75
+ });
76
+
77
+ describe("isAnyQuickActionBusy", () => {
78
+ it("should return false when no quick action tasks exist", () => {
79
+ expect(manager.isAnyQuickActionBusy()).toBe(false);
80
+ });
81
+
82
+ it("should return true when quick action tasks exist", () => {
83
+ manager.addBusyQuickActionTask({ taskId: "qa1" });
84
+ expect(manager.isAnyQuickActionBusy()).toBe(true);
85
+ });
21
86
  });
22
87
 
23
- it("should remove a busy plugin", () => {
24
- const task1 = { taskId: "plugin1", taskDescription: "Description for plugin1" };
25
- const task2 = { taskId: "plugin2", taskDescription: "Description for plugin2" };
26
- manager.addBusyPluginTask(task1);
27
- manager.addBusyPluginTask(task2);
28
- manager.removeBusyPluginTask("plugin1");
29
- expect(manager.getBusyPluginTasks()).not.toContainEqual(task1);
30
- expect(manager.getBusyPluginTasks()).toContainEqual(task2);
88
+ describe("clearAllBusyPlugins", () => {
89
+ it("should clear all plugin busy tasks", () => {
90
+ manager.addBusyPluginTask({ taskId: "1", taskDescription: "Busy" });
91
+ manager.clearAllBusyPlugins();
92
+ expect(manager.getBusyPluginTasks()).toHaveLength(0);
93
+ });
31
94
  });
32
95
 
33
- it("should do nothing if removing a non-existent plugin", () => {
34
- const task = { taskId: "plugin1", taskDescription: "Description for plugin1" };
35
- manager.addBusyPluginTask(task);
36
- manager.removeBusyPluginTask("nonexistent");
37
- expect(manager.getBusyPluginTasks()).toEqual([task]);
96
+ describe("clearAllBusyQuickActions", () => {
97
+ it("should clear all quick action busy tasks", () => {
98
+ manager.addBusyQuickActionTask({ taskId: "qa1" });
99
+ manager.clearAllBusyQuickActions();
100
+ expect(manager.isAnyQuickActionBusy()).toBe(false);
101
+ });
38
102
  });
39
103
 
40
- it("should clear all busy plugins", () => {
41
- const task1 = { taskId: "plugin1", taskDescription: "Description for plugin1" };
42
- const task2 = { taskId: "plugin2", taskDescription: "Description for plugin2" };
43
- manager.addBusyPluginTask(task1);
44
- manager.addBusyPluginTask(task2);
45
- manager.clearAllBusyPlugins();
46
- expect(manager.getBusyPluginTasks()).toEqual([]);
47
- expect(manager.isAnyPluginBusy()).toBe(false);
104
+ describe("getBusyPluginTasks", () => {
105
+ it("should return current plugin tasks", () => {
106
+ const task = { taskId: "1", taskDescription: "Loading plugin" };
107
+ manager.addBusyPluginTask(task);
108
+ expect(manager.getBusyPluginTasks()).toEqual([task]);
109
+ });
48
110
  });
49
111
  });
@@ -1,22 +1,44 @@
1
+ import { shellEvents } from "../../events";
2
+ import { PrimariaBroker } from "../broker/primaria-broker";
3
+
1
4
  export interface PluginBusyTask {
2
5
  taskId: string;
3
6
  taskDescription: string;
4
7
  }
5
8
 
9
+ export interface QuickActionBusyTask {
10
+ taskId: string;
11
+ }
12
+
6
13
  export abstract class PluginBusyManager {
7
14
  abstract addBusyPluginTask(busyTask: PluginBusyTask): void;
8
- abstract removeBusyPluginTask(taskId: string): any;
15
+ abstract removeBusyPluginTask(taskId: string): void;
16
+ abstract addBusyQuickActionTask(busyTask: QuickActionBusyTask): void;
17
+ abstract removeBusyQuickActionTask(taskId: string): void;
9
18
  abstract clearAllBusyPlugins(): void;
10
19
  abstract isAnyPluginBusy(): boolean;
20
+ abstract isAnyQuickActionBusy(): boolean;
11
21
  abstract getBusyPluginTasks(): PluginBusyTask[];
12
22
  }
13
23
  export class PluginBusyManagerImpl implements PluginBusyManager {
24
+ constructor(public broker: PrimariaBroker) {}
14
25
  private busyPluginTasks: PluginBusyTask[] = [];
26
+ private busyQuickActionTasks: QuickActionBusyTask[] = [];
15
27
 
16
28
  public addBusyPluginTask(busyTask: PluginBusyTask): void {
17
29
  this.busyPluginTasks.push(busyTask);
18
30
  }
19
31
 
32
+ public addBusyQuickActionTask(busyTask: QuickActionBusyTask): void {
33
+ const index = this.busyQuickActionTasks.findIndex((item) => item.taskId === busyTask.taskId);
34
+ if (index > -1) {
35
+ return;
36
+ }
37
+
38
+ this.busyQuickActionTasks.push(busyTask);
39
+ this.emitQuickActionBusyChanged();
40
+ }
41
+
20
42
  public removeBusyPluginTask(taskId: string): any {
21
43
  const index = this.busyPluginTasks.findIndex((item) => item.taskId === taskId);
22
44
  if (index > -1) {
@@ -24,15 +46,36 @@ export class PluginBusyManagerImpl implements PluginBusyManager {
24
46
  }
25
47
  }
26
48
 
49
+ public removeBusyQuickActionTask(taskId: string): any {
50
+ const index = this.busyQuickActionTasks.findIndex((item) => item.taskId === taskId);
51
+ if (index > -1) {
52
+ this.busyQuickActionTasks.splice(index, 1);
53
+ this.emitQuickActionBusyChanged();
54
+ }
55
+ }
56
+
27
57
  public isAnyPluginBusy(): boolean {
28
58
  return this.busyPluginTasks.length > 0;
29
59
  }
30
60
 
61
+ public isAnyQuickActionBusy(): boolean {
62
+ return this.busyQuickActionTasks.length > 0;
63
+ }
64
+
31
65
  public clearAllBusyPlugins(): void {
32
66
  this.busyPluginTasks = [];
33
67
  }
34
68
 
69
+ public clearAllBusyQuickActions(): void {
70
+ this.busyQuickActionTasks = [];
71
+ }
72
+
35
73
  public getBusyPluginTasks(): PluginBusyTask[] {
36
74
  return this.busyPluginTasks;
37
75
  }
76
+
77
+ private emitQuickActionBusyChanged() {
78
+ const busy = this.isAnyQuickActionBusy();
79
+ this.broker.publish(shellEvents.quickActionBusyChanged, { busy });
80
+ }
38
81
  }