@agent-native/dispatch 0.41.1-nightly-20261104110058 → 0.42.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/CHANGELOG.md +26 -55
- package/dist/actions/get-hosted-apps-settings.d.ts +5 -0
- package/dist/actions/get-hosted-apps-settings.d.ts.map +1 -0
- package/dist/actions/get-hosted-apps-settings.js +25 -0
- package/dist/actions/get-hosted-apps-settings.js.map +1 -0
- package/dist/actions/index.d.ts.map +1 -1
- package/dist/actions/index.js +4 -0
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/list-connected-agents.d.ts +1 -0
- package/dist/actions/list-connected-agents.js +11 -10
- package/dist/actions/list-connected-agents.js.map +1 -1
- package/dist/actions/list_apps.js +3 -2
- package/dist/actions/list_apps.js.map +1 -1
- package/dist/actions/provider-api-register.d.ts +10 -10
- package/dist/actions/set-hosted-apps-settings.d.ts +7 -0
- package/dist/actions/set-hosted-apps-settings.d.ts.map +1 -0
- package/dist/actions/set-hosted-apps-settings.js +35 -0
- package/dist/actions/set-hosted-apps-settings.js.map +1 -0
- package/dist/components/app-card-tiers.d.ts +13 -0
- package/dist/components/app-card-tiers.d.ts.map +1 -0
- package/dist/components/app-card-tiers.js +22 -0
- package/dist/components/app-card-tiers.js.map +1 -0
- package/dist/components/app-list-row.d.ts +12 -4
- package/dist/components/app-list-row.d.ts.map +1 -1
- package/dist/components/app-list-row.js +19 -4
- package/dist/components/app-list-row.js.map +1 -1
- package/dist/components/app-open-actions.d.ts +4 -0
- package/dist/components/app-open-actions.d.ts.map +1 -1
- package/dist/components/app-open-actions.js +9 -0
- package/dist/components/app-open-actions.js.map +1 -1
- package/dist/components/app-pin-button.d.ts +9 -0
- package/dist/components/app-pin-button.d.ts.map +1 -0
- package/dist/components/app-pin-button.js +29 -0
- package/dist/components/app-pin-button.js.map +1 -0
- package/dist/components/app-tier-sections.d.ts +27 -0
- package/dist/components/app-tier-sections.d.ts.map +1 -0
- package/dist/components/app-tier-sections.js +44 -0
- package/dist/components/app-tier-sections.js.map +1 -0
- package/dist/components/chat-home-apps.d.ts.map +1 -1
- package/dist/components/chat-home-apps.js +19 -26
- package/dist/components/chat-home-apps.js.map +1 -1
- package/dist/components/connected-app-card.d.ts +3 -1
- package/dist/components/connected-app-card.d.ts.map +1 -1
- package/dist/components/connected-app-card.js +6 -3
- package/dist/components/connected-app-card.js.map +1 -1
- package/dist/components/create-app-popover.d.ts +4 -0
- package/dist/components/create-app-popover.d.ts.map +1 -1
- package/dist/components/create-app-popover.js +6 -0
- package/dist/components/create-app-popover.js.map +1 -1
- package/dist/components/dispatch-control-plane.d.ts.map +1 -1
- package/dist/components/dispatch-control-plane.js +25 -35
- package/dist/components/dispatch-control-plane.js.map +1 -1
- package/dist/components/hosted-apps-settings-group.d.ts +6 -0
- package/dist/components/hosted-apps-settings-group.d.ts.map +1 -0
- package/dist/components/hosted-apps-settings-group.js +76 -0
- package/dist/components/hosted-apps-settings-group.js.map +1 -0
- package/dist/components/layout/Layout.d.ts +6 -4
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +97 -80
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +9 -20
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/components/workspace-app-layout-notice.d.ts +9 -0
- package/dist/components/workspace-app-layout-notice.d.ts.map +1 -0
- package/dist/components/workspace-app-layout-notice.js +27 -0
- package/dist/components/workspace-app-layout-notice.js.map +1 -0
- package/dist/components/workspace-app-search.d.ts.map +1 -1
- package/dist/components/workspace-app-search.js +11 -5
- package/dist/components/workspace-app-search.js.map +1 -1
- package/dist/components/workspace-template-card.d.ts.map +1 -1
- package/dist/components/workspace-template-card.js +7 -2
- package/dist/components/workspace-template-card.js.map +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -0
- package/dist/db/index.js.map +1 -1
- package/dist/lib/other-apps.d.ts +10 -1
- package/dist/lib/other-apps.d.ts.map +1 -1
- package/dist/lib/other-apps.js +7 -1
- package/dist/lib/other-apps.js.map +1 -1
- package/dist/lib/workspace-app-layout.d.ts +62 -6
- package/dist/lib/workspace-app-layout.d.ts.map +1 -1
- package/dist/lib/workspace-app-layout.js +196 -41
- package/dist/lib/workspace-app-layout.js.map +1 -1
- package/dist/lib/workspace-app-lists.d.ts +17 -0
- package/dist/lib/workspace-app-lists.d.ts.map +1 -0
- package/dist/lib/workspace-app-lists.js +21 -0
- package/dist/lib/workspace-app-lists.js.map +1 -0
- package/dist/lib/workspace-apps.d.ts +3 -1
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js +5 -1
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +23 -35
- package/dist/routes/pages/apps.js.map +1 -1
- package/dist/routes/pages/settings-page.d.ts.map +1 -1
- package/dist/routes/pages/settings-page.js +24 -3
- package/dist/routes/pages/settings-page.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +13 -18
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/app-roles.d.ts +2 -0
- package/dist/server/lib/app-roles.d.ts.map +1 -1
- package/dist/server/lib/app-roles.js +26 -7
- package/dist/server/lib/app-roles.js.map +1 -1
- package/dist/server/lib/hosted-apps-settings-schema.d.ts +6 -0
- package/dist/server/lib/hosted-apps-settings-schema.d.ts.map +1 -0
- package/dist/server/lib/hosted-apps-settings-schema.js +6 -0
- package/dist/server/lib/hosted-apps-settings-schema.js.map +1 -0
- package/dist/server/lib/hosted-apps-settings.d.ts +27 -0
- package/dist/server/lib/hosted-apps-settings.d.ts.map +1 -0
- package/dist/server/lib/hosted-apps-settings.js +72 -0
- package/dist/server/lib/hosted-apps-settings.js.map +1 -0
- package/dist/server/plugins/agent-chat.js +6 -3
- package/dist/server/plugins/agent-chat.js.map +1 -1
- package/dist/server/plugins/integrations.js +3 -3
- package/dist/server/plugins/integrations.js.map +1 -1
- package/dist/shared/hosted-apps-settings.d.ts +3 -0
- package/dist/shared/hosted-apps-settings.d.ts.map +1 -0
- package/dist/shared/hosted-apps-settings.js +3 -0
- package/dist/shared/hosted-apps-settings.js.map +1 -0
- package/package.json +4 -4
- package/src/actions/get-hosted-apps-settings.ts +29 -0
- package/src/actions/hosted-apps-settings.spec.ts +100 -0
- package/src/actions/index.ts +4 -0
- package/src/actions/list-connected-agents.spec.ts +67 -0
- package/src/actions/list-connected-agents.ts +14 -15
- package/src/actions/list_apps.spec.ts +16 -0
- package/src/actions/list_apps.ts +5 -2
- package/src/actions/set-hosted-apps-settings.ts +42 -0
- package/src/components/app-card-tiers.tsx +85 -0
- package/src/components/app-list-row.spec.tsx +54 -0
- package/src/components/app-list-row.tsx +50 -8
- package/src/components/app-open-actions.tsx +16 -0
- package/src/components/app-pin-button.spec.tsx +68 -0
- package/src/components/app-pin-button.tsx +66 -0
- package/src/components/app-tier-sections.spec.tsx +155 -0
- package/src/components/app-tier-sections.tsx +158 -0
- package/src/components/chat-home-apps.spec.tsx +135 -29
- package/src/components/chat-home-apps.tsx +115 -111
- package/src/components/connected-app-card.spec.tsx +18 -2
- package/src/components/connected-app-card.tsx +18 -1
- package/src/components/create-app-popover.tsx +22 -0
- package/src/components/dispatch-control-plane.spec.tsx +89 -20
- package/src/components/dispatch-control-plane.tsx +91 -125
- package/src/components/hosted-apps-settings-group.spec.tsx +218 -0
- package/src/components/hosted-apps-settings-group.tsx +189 -0
- package/src/components/layout/Layout.spec.tsx +356 -49
- package/src/components/layout/Layout.tsx +189 -142
- package/src/components/workspace-app-card.spec.tsx +33 -29
- package/src/components/workspace-app-card.tsx +13 -31
- package/src/components/workspace-app-layout-notice.spec.tsx +106 -0
- package/src/components/workspace-app-layout-notice.tsx +65 -0
- package/src/components/workspace-app-search.spec.tsx +53 -0
- package/src/components/workspace-app-search.tsx +39 -32
- package/src/components/workspace-template-card.spec.tsx +10 -0
- package/src/components/workspace-template-card.tsx +7 -2
- package/src/db/index.spec.ts +9 -0
- package/src/db/index.ts +1 -0
- package/src/lib/other-apps.spec.ts +25 -0
- package/src/lib/other-apps.ts +9 -0
- package/src/lib/workspace-app-layout.spec.ts +61 -3
- package/src/lib/workspace-app-layout.store.spec.tsx +261 -0
- package/src/lib/workspace-app-layout.ts +265 -48
- package/src/lib/workspace-app-lists.ts +32 -0
- package/src/lib/workspace-apps.ts +10 -2
- package/src/routes/pages/apps.spec.tsx +133 -5
- package/src/routes/pages/apps.tsx +79 -140
- package/src/routes/pages/chat.spec.tsx +66 -49
- package/src/routes/pages/settings-page.tsx +31 -3
- package/src/routes/pages/settings.spec.tsx +37 -0
- package/src/server/lib/app-creation-store.spec.ts +14 -0
- package/src/server/lib/app-creation-store.ts +16 -29
- package/src/server/lib/app-creation-store.write-observation.spec.ts +216 -0
- package/src/server/lib/app-roles.spec.ts +48 -1
- package/src/server/lib/app-roles.ts +44 -12
- package/src/server/lib/hosted-apps-settings-schema.ts +6 -0
- package/src/server/lib/hosted-apps-settings.spec.ts +154 -0
- package/src/server/lib/hosted-apps-settings.ts +108 -0
- package/src/server/plugins/agent-chat.spec.ts +16 -0
- package/src/server/plugins/agent-chat.ts +7 -2
- package/src/server/plugins/integrations.ts +3 -3
- package/src/shared/hosted-apps-settings.ts +3 -0
- package/src/styles/dispatch.css +40 -11
- package/dist/components/available-apps-section.d.ts +0 -9
- package/dist/components/available-apps-section.d.ts.map +0 -1
- package/dist/components/available-apps-section.js +0 -97
- package/dist/components/available-apps-section.js.map +0 -1
- package/dist/components/other-apps-section.d.ts +0 -35
- package/dist/components/other-apps-section.d.ts.map +0 -1
- package/dist/components/other-apps-section.js +0 -79
- package/dist/components/other-apps-section.js.map +0 -1
- package/src/components/available-apps-section.tsx +0 -239
- package/src/components/other-apps-section.spec.ts +0 -50
- package/src/components/other-apps-section.tsx +0 -254
|
@@ -5,7 +5,7 @@ import { MemoryRouter } from "react-router";
|
|
|
5
5
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
6
|
|
|
7
7
|
const queryState = vi.hoisted(() => ({
|
|
8
|
-
workspaceApps: [] as Array<Record<string, unknown
|
|
8
|
+
workspaceApps: [] as Array<Record<string, unknown>> | undefined,
|
|
9
9
|
workspaceAppsError: null as Error | null,
|
|
10
10
|
workspaceAppsLoading: false,
|
|
11
11
|
connectedApps: [] as Array<Record<string, unknown>>,
|
|
@@ -16,6 +16,8 @@ const queryState = vi.hoisted(() => ({
|
|
|
16
16
|
installCommunityApp: vi.fn(),
|
|
17
17
|
installCommunityAppResult: null as Record<string, unknown> | null,
|
|
18
18
|
sendToAgentChat: vi.fn(),
|
|
19
|
+
pinnedIds: [] as string[],
|
|
20
|
+
canManageOrg: false,
|
|
19
21
|
}));
|
|
20
22
|
|
|
21
23
|
vi.mock("@agent-native/core/client/hooks", () => ({
|
|
@@ -93,8 +95,19 @@ vi.mock("../../components/action-query-error", () => ({
|
|
|
93
95
|
),
|
|
94
96
|
}));
|
|
95
97
|
|
|
98
|
+
vi.mock("@agent-native/core/client/org", async (importOriginal) => ({
|
|
99
|
+
...(await importOriginal<typeof import("@agent-native/core/client/org")>()),
|
|
100
|
+
useOrgRole: () => ({
|
|
101
|
+
org: { orgId: "org-1" },
|
|
102
|
+
canManageOrg: queryState.canManageOrg,
|
|
103
|
+
isLoading: false,
|
|
104
|
+
role: queryState.canManageOrg ? "admin" : "member",
|
|
105
|
+
}),
|
|
106
|
+
}));
|
|
107
|
+
|
|
96
108
|
vi.mock("../../components/create-app-popover", () => ({
|
|
97
|
-
CreateAppPopover: () =>
|
|
109
|
+
CreateAppPopover: () => <span data-create-app />,
|
|
110
|
+
NewAppButton: () => <span data-create-app />,
|
|
98
111
|
}));
|
|
99
112
|
|
|
100
113
|
vi.mock("../../components/dispatch-shell", () => ({
|
|
@@ -108,16 +121,18 @@ vi.mock("../../components/workspace-app-search", () => ({
|
|
|
108
121
|
WorkspaceAppSearchEmpty: () => <div>No search results</div>,
|
|
109
122
|
}));
|
|
110
123
|
|
|
111
|
-
vi.mock("../../lib/workspace-app-layout", () => ({
|
|
124
|
+
vi.mock("../../lib/workspace-app-layout", async (importOriginal) => ({
|
|
125
|
+
...(await importOriginal<typeof import("../../lib/workspace-app-layout")>()),
|
|
112
126
|
orderWorkspaceApps: (apps: unknown[]) => apps,
|
|
113
127
|
useWorkspaceAppLayout: () => ({
|
|
114
|
-
layout: { pinnedIds:
|
|
128
|
+
layout: { pinnedIds: queryState.pinnedIds, orderedIds: [] },
|
|
115
129
|
persistenceError: null,
|
|
116
130
|
togglePinned: vi.fn(),
|
|
117
131
|
}),
|
|
118
132
|
workspaceAppMatchesQuery: () => true,
|
|
119
133
|
}));
|
|
120
134
|
|
|
135
|
+
import { TooltipProvider } from "../../components/ui/tooltip";
|
|
121
136
|
import AppsRouteEntry from "./apps";
|
|
122
137
|
|
|
123
138
|
describe("Dispatch apps route", () => {
|
|
@@ -137,6 +152,8 @@ describe("Dispatch apps route", () => {
|
|
|
137
152
|
queryState.installCommunityApp.mockReset();
|
|
138
153
|
queryState.installCommunityAppResult = null;
|
|
139
154
|
queryState.sendToAgentChat.mockReset();
|
|
155
|
+
queryState.pinnedIds = [];
|
|
156
|
+
queryState.canManageOrg = false;
|
|
140
157
|
container = document.createElement("div");
|
|
141
158
|
document.body.appendChild(container);
|
|
142
159
|
root = createRoot(container);
|
|
@@ -152,7 +169,9 @@ describe("Dispatch apps route", () => {
|
|
|
152
169
|
await act(async () => {
|
|
153
170
|
root.render(
|
|
154
171
|
<MemoryRouter initialEntries={["/apps"]}>
|
|
155
|
-
<
|
|
172
|
+
<TooltipProvider>
|
|
173
|
+
<AppsRouteEntry />
|
|
174
|
+
</TooltipProvider>
|
|
156
175
|
</MemoryRouter>,
|
|
157
176
|
);
|
|
158
177
|
});
|
|
@@ -169,12 +188,121 @@ describe("Dispatch apps route", () => {
|
|
|
169
188
|
expect(container.textContent).not.toContain("No workspace apps");
|
|
170
189
|
});
|
|
171
190
|
|
|
191
|
+
it.each([
|
|
192
|
+
[true, "link"],
|
|
193
|
+
[false, "disabled"],
|
|
194
|
+
] as const)(
|
|
195
|
+
"puts pinned hosted apps first and hosted apps last (admin %s)",
|
|
196
|
+
async (canManageOrg, manage) => {
|
|
197
|
+
queryState.canManageOrg = canManageOrg;
|
|
198
|
+
queryState.pinnedIds = ["clips"];
|
|
199
|
+
queryState.workspaceApps = [
|
|
200
|
+
{ id: "notes", name: "Notes", path: "/notes", status: "ready" },
|
|
201
|
+
];
|
|
202
|
+
queryState.connectedApps = [
|
|
203
|
+
{
|
|
204
|
+
id: "mail",
|
|
205
|
+
name: "Mail",
|
|
206
|
+
url: "https://mail.agent-native.com",
|
|
207
|
+
source: "builtin",
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
id: "clips",
|
|
211
|
+
name: "Clips",
|
|
212
|
+
url: "https://clips.agent-native.com",
|
|
213
|
+
source: "builtin",
|
|
214
|
+
},
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
await renderAppsRoute();
|
|
218
|
+
|
|
219
|
+
const sections = [...container.querySelectorAll("section[aria-label]")];
|
|
220
|
+
expect(
|
|
221
|
+
sections.map((section) => section.getAttribute("aria-label")),
|
|
222
|
+
).toEqual([
|
|
223
|
+
"dispatch.pages.pinnedApps",
|
|
224
|
+
"dispatch.pages.workspaceApps",
|
|
225
|
+
"dispatch.pages.hostedApps",
|
|
226
|
+
]);
|
|
227
|
+
expect(sections[0]?.textContent).toContain("Clips");
|
|
228
|
+
expect(sections[1]?.textContent).toContain("Notes");
|
|
229
|
+
expect(sections[2]?.textContent).toContain("Mail");
|
|
230
|
+
expect(sections[2]?.textContent).not.toContain("Clips");
|
|
231
|
+
const mailRow = sections[2]!;
|
|
232
|
+
expect(
|
|
233
|
+
mailRow.querySelector('a[href^="https://mail.agent-native.com"] svg'),
|
|
234
|
+
).not.toBeNull();
|
|
235
|
+
expect(
|
|
236
|
+
mailRow
|
|
237
|
+
.querySelector('button[aria-label="dispatch.pages.pinApp"]')
|
|
238
|
+
?.getAttribute("aria-pressed"),
|
|
239
|
+
).toBe("false");
|
|
240
|
+
const manageLink = mailRow.querySelector('a[href="/settings/apps"]');
|
|
241
|
+
const manageButton = [...mailRow.querySelectorAll("button")].find(
|
|
242
|
+
(button) => button.textContent === "dispatch.pages.manageHostedApps",
|
|
243
|
+
);
|
|
244
|
+
if (manage === "link") {
|
|
245
|
+
expect(manageLink?.textContent).toBe("dispatch.pages.manageHostedApps");
|
|
246
|
+
} else {
|
|
247
|
+
expect(manageLink).toBeNull();
|
|
248
|
+
expect(manageButton?.disabled).toBe(true);
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
it.each([
|
|
254
|
+
["loading", null],
|
|
255
|
+
["failed", new Error("Workspace app list failed")],
|
|
256
|
+
])(
|
|
257
|
+
"lists no apps until the workspace list arrives (%s)",
|
|
258
|
+
async (_state, error) => {
|
|
259
|
+
queryState.workspaceApps = undefined;
|
|
260
|
+
queryState.workspaceAppsError = error;
|
|
261
|
+
queryState.connectedApps = [
|
|
262
|
+
{
|
|
263
|
+
id: "mail",
|
|
264
|
+
name: "Mail",
|
|
265
|
+
url: "https://mail.agent-native.com",
|
|
266
|
+
source: "builtin",
|
|
267
|
+
},
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
await renderAppsRoute();
|
|
271
|
+
|
|
272
|
+
expect(container.textContent).not.toContain("No workspace apps");
|
|
273
|
+
expect(container.textContent).not.toContain("Mail");
|
|
274
|
+
},
|
|
275
|
+
);
|
|
276
|
+
|
|
172
277
|
it("shows the empty state after successful app discovery", async () => {
|
|
173
278
|
await renderAppsRoute();
|
|
174
279
|
|
|
175
280
|
expect(container.textContent).toContain("No workspace apps");
|
|
176
281
|
});
|
|
177
282
|
|
|
283
|
+
it("keeps Add app when app discovery fails", async () => {
|
|
284
|
+
queryState.workspaceApps = [];
|
|
285
|
+
queryState.connectedAppsError = new Error("discovery down");
|
|
286
|
+
await renderAppsRoute();
|
|
287
|
+
|
|
288
|
+
expect(container.querySelectorAll("[data-create-app]")).toHaveLength(1);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("offers one Add app when only hosted apps are listed", async () => {
|
|
292
|
+
queryState.connectedApps = [
|
|
293
|
+
{
|
|
294
|
+
id: "mail",
|
|
295
|
+
name: "Mail",
|
|
296
|
+
url: "https://mail.agent-native.com",
|
|
297
|
+
source: "builtin",
|
|
298
|
+
},
|
|
299
|
+
];
|
|
300
|
+
await renderAppsRoute();
|
|
301
|
+
|
|
302
|
+
expect(container.textContent).toContain("No workspace apps");
|
|
303
|
+
expect(container.querySelectorAll("[data-create-app]")).toHaveLength(1);
|
|
304
|
+
});
|
|
305
|
+
|
|
178
306
|
it("identifies each app discovery query when both fail", async () => {
|
|
179
307
|
queryState.workspaceAppsError = new Error("Workspace app list failed");
|
|
180
308
|
queryState.connectedAppsError = new Error("Connected agent list failed");
|
|
@@ -11,14 +11,14 @@ import { useState } from "react";
|
|
|
11
11
|
import { Outlet, useParams } from "react-router";
|
|
12
12
|
|
|
13
13
|
import { ActionQueryError } from "../../components/action-query-error";
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
AppList,
|
|
18
|
-
} from "../../components/app-list-row";
|
|
14
|
+
import { AppCardTiers } from "../../components/app-card-tiers";
|
|
15
|
+
import { AppList, AppListRow } from "../../components/app-list-row";
|
|
16
|
+
import { AppListActions } from "../../components/app-tier-sections";
|
|
19
17
|
import { CommunityAppsSection } from "../../components/community-apps-section";
|
|
20
|
-
import {
|
|
21
|
-
|
|
18
|
+
import {
|
|
19
|
+
CreateAppPopover,
|
|
20
|
+
NewAppButton,
|
|
21
|
+
} from "../../components/create-app-popover";
|
|
22
22
|
import { DispatchShell } from "../../components/dispatch-shell";
|
|
23
23
|
import { Button } from "../../components/ui/button";
|
|
24
24
|
import {
|
|
@@ -32,20 +32,15 @@ import {
|
|
|
32
32
|
WorkspaceAppSearch,
|
|
33
33
|
WorkspaceAppSearchEmpty,
|
|
34
34
|
} from "../../components/workspace-app-search";
|
|
35
|
-
import {
|
|
36
|
-
filterBuiltInApps,
|
|
37
|
-
type ConnectedAppSummary,
|
|
38
|
-
} from "../../lib/other-apps";
|
|
39
35
|
import { cn } from "../../lib/utils";
|
|
40
36
|
import {
|
|
37
|
+
hasPinnedWorkspaceApp,
|
|
41
38
|
orderWorkspaceApps,
|
|
42
39
|
useWorkspaceAppLayout,
|
|
43
40
|
workspaceAppMatchesQuery,
|
|
44
41
|
} from "../../lib/workspace-app-layout";
|
|
45
|
-
import {
|
|
46
|
-
|
|
47
|
-
type WorkspaceAppSummary,
|
|
48
|
-
} from "../../lib/workspace-apps";
|
|
42
|
+
import { useWorkspaceAppLists } from "../../lib/workspace-app-lists";
|
|
43
|
+
import { isWorkspaceAppVisibleInDefaultLaunchers } from "../../lib/workspace-apps";
|
|
49
44
|
|
|
50
45
|
export function meta() {
|
|
51
46
|
return [{ title: "Apps — Dispatch" }];
|
|
@@ -67,16 +62,14 @@ function AppsRoute() {
|
|
|
67
62
|
const [showPending, setShowPending] = useState(false);
|
|
68
63
|
const [showHidden, setShowHidden] = useState(false);
|
|
69
64
|
const [searchQuery, setSearchQuery] = useState("");
|
|
70
|
-
const { layout
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
);
|
|
79
|
-
const { data: apps = [], isLoading: appsLoading } = appsQuery;
|
|
65
|
+
const { layout } = useWorkspaceAppLayout();
|
|
66
|
+
const {
|
|
67
|
+
status: appsStatus,
|
|
68
|
+
workspaceApps: apps,
|
|
69
|
+
hostedApps: defaultApps,
|
|
70
|
+
appsQuery,
|
|
71
|
+
connectedAppsQuery,
|
|
72
|
+
} = useWorkspaceAppLists();
|
|
80
73
|
const { data: workspace } = useActionQuery(
|
|
81
74
|
"get-workspace-info",
|
|
82
75
|
{},
|
|
@@ -84,18 +77,14 @@ function AppsRoute() {
|
|
|
84
77
|
);
|
|
85
78
|
const ws = workspace as WorkspaceInfo | undefined;
|
|
86
79
|
const workspaceLabel = ws?.displayName ?? ws?.name ?? null;
|
|
87
|
-
const allApps =
|
|
88
|
-
isWorkspaceAppVisibleInDefaultLaunchers,
|
|
89
|
-
);
|
|
80
|
+
const allApps = apps.filter(isWorkspaceAppVisibleInDefaultLaunchers);
|
|
90
81
|
const visibleApps = allApps.filter((app) => !app.archived);
|
|
91
82
|
const activeApps = visibleApps.filter((app) => app.status !== "pending");
|
|
92
83
|
const pendingApps = visibleApps.filter((app) => app.status === "pending");
|
|
93
84
|
const archivedApps = allApps.filter((app) => app.archived);
|
|
94
|
-
const defaultApps = filterBuiltInApps(connectedAppsQuery.data ?? [], allApps);
|
|
95
|
-
const orderedActiveApps = orderWorkspaceApps(activeApps, layout);
|
|
96
85
|
const orderedPendingApps = orderWorkspaceApps(pendingApps, layout);
|
|
97
86
|
const orderedArchivedApps = orderWorkspaceApps(archivedApps, layout);
|
|
98
|
-
const filteredActiveApps =
|
|
87
|
+
const filteredActiveApps = activeApps.filter((app) =>
|
|
99
88
|
workspaceAppMatchesQuery(app, searchQuery),
|
|
100
89
|
);
|
|
101
90
|
const filteredPendingApps = orderedPendingApps.filter((app) =>
|
|
@@ -112,11 +101,14 @@ function AppsRoute() {
|
|
|
112
101
|
filteredPendingApps.length > 0 ||
|
|
113
102
|
filteredArchivedApps.length > 0 ||
|
|
114
103
|
filteredDefaultApps.length > 0;
|
|
115
|
-
const showAppSkeletons =
|
|
116
|
-
(appsLoading || connectedAppsQuery.isLoading) &&
|
|
117
|
-
allApps.length === 0 &&
|
|
118
|
-
defaultApps.length === 0;
|
|
104
|
+
const showAppSkeletons = appsStatus === "loading";
|
|
119
105
|
const appDiscoveryFailed = appsQuery.isError || connectedAppsQuery.isError;
|
|
106
|
+
const showsEmptyAppsState =
|
|
107
|
+
!showAppSkeletons &&
|
|
108
|
+
!appDiscoveryFailed &&
|
|
109
|
+
!searchQuery.trim() &&
|
|
110
|
+
activeApps.length === 0 &&
|
|
111
|
+
pendingApps.length === 0;
|
|
120
112
|
|
|
121
113
|
return (
|
|
122
114
|
<DispatchShell
|
|
@@ -129,58 +121,8 @@ function AppsRoute() {
|
|
|
129
121
|
: t("dispatch.pages.appsDescription")
|
|
130
122
|
}
|
|
131
123
|
>
|
|
132
|
-
<div className="space-y-8">
|
|
124
|
+
<div className="mx-auto w-full max-w-[1000px] space-y-8">
|
|
133
125
|
<section className="space-y-3">
|
|
134
|
-
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
135
|
-
<div className="flex min-w-0 items-start gap-2">
|
|
136
|
-
<IconApps
|
|
137
|
-
size={16}
|
|
138
|
-
className="mt-0.5 shrink-0 text-muted-foreground"
|
|
139
|
-
/>
|
|
140
|
-
<div className="min-w-0">
|
|
141
|
-
<h2 className="truncate text-sm font-semibold text-foreground">
|
|
142
|
-
{t("dispatch.pages.yourApps", { defaultValue: "Your apps" })}
|
|
143
|
-
</h2>
|
|
144
|
-
</div>
|
|
145
|
-
</div>
|
|
146
|
-
<div className="flex min-w-0 flex-wrap items-center justify-end gap-2">
|
|
147
|
-
{!showAppSkeletons ? (
|
|
148
|
-
<WorkspaceAppSearch
|
|
149
|
-
className="w-full sm:w-[250px]"
|
|
150
|
-
query={searchQuery}
|
|
151
|
-
onQueryChange={setSearchQuery}
|
|
152
|
-
/>
|
|
153
|
-
) : null}
|
|
154
|
-
{activeApps.length > 0 ||
|
|
155
|
-
pendingApps.length > 0 ||
|
|
156
|
-
defaultApps.length > 0 ? (
|
|
157
|
-
<CreateAppPopover
|
|
158
|
-
align="end"
|
|
159
|
-
trigger={
|
|
160
|
-
<Button size="sm" variant="outline">
|
|
161
|
-
<IconPlus size={15} className="mr-1.5" />
|
|
162
|
-
{t("dispatch.pages.addApp", {
|
|
163
|
-
defaultValue: "Add app",
|
|
164
|
-
})}
|
|
165
|
-
</Button>
|
|
166
|
-
}
|
|
167
|
-
/>
|
|
168
|
-
) : null}
|
|
169
|
-
</div>
|
|
170
|
-
</div>
|
|
171
|
-
|
|
172
|
-
{!showAppSkeletons && allApps.length > 0 && persistenceError ? (
|
|
173
|
-
<p className="text-xs text-muted-foreground" role="status">
|
|
174
|
-
{persistenceError === "both"
|
|
175
|
-
? t("dispatch.pages.appPinSaveFailed", {
|
|
176
|
-
defaultValue: "App pins could not be saved.",
|
|
177
|
-
})
|
|
178
|
-
: t("dispatch.pages.appPinSavedLocally", {
|
|
179
|
-
defaultValue: "App pins are saved on this device only.",
|
|
180
|
-
})}
|
|
181
|
-
</p>
|
|
182
|
-
) : null}
|
|
183
|
-
|
|
184
126
|
{appsQuery.isError ? (
|
|
185
127
|
<ActionQueryError
|
|
186
128
|
error={appsQuery.error}
|
|
@@ -195,39 +137,50 @@ function AppsRoute() {
|
|
|
195
137
|
queryName="list-connected-agents"
|
|
196
138
|
/>
|
|
197
139
|
) : null}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
140
|
+
<AppCardTiers
|
|
141
|
+
workspaceApps={showAppSkeletons ? [] : filteredActiveApps}
|
|
142
|
+
hostedApps={showAppSkeletons ? [] : filteredDefaultApps}
|
|
143
|
+
appsTitle={t("dispatch.pages.workspaceApps")}
|
|
144
|
+
hasPinnedApps={
|
|
145
|
+
!showAppSkeletons &&
|
|
146
|
+
hasPinnedWorkspaceApp([...activeApps, ...defaultApps], layout)
|
|
147
|
+
}
|
|
148
|
+
actions={
|
|
149
|
+
<AppListActions
|
|
150
|
+
search={
|
|
151
|
+
!showAppSkeletons ? (
|
|
152
|
+
<WorkspaceAppSearch
|
|
153
|
+
className="w-full sm:w-[250px]"
|
|
154
|
+
query={searchQuery}
|
|
155
|
+
onQueryChange={setSearchQuery}
|
|
156
|
+
/>
|
|
157
|
+
) : undefined
|
|
158
|
+
}
|
|
159
|
+
// The empty state carries its own Add app.
|
|
160
|
+
newApp={
|
|
161
|
+
!showAppSkeletons && !showsEmptyAppsState ? (
|
|
162
|
+
<NewAppButton />
|
|
163
|
+
) : undefined
|
|
164
|
+
}
|
|
165
|
+
/>
|
|
166
|
+
}
|
|
167
|
+
appsFallback={
|
|
168
|
+
showAppSkeletons ? (
|
|
169
|
+
<AppsSkeletonGrid />
|
|
170
|
+
) : appDiscoveryFailed ? null : searchQuery.trim() ? (
|
|
171
|
+
hasSearchResults ? null : (
|
|
172
|
+
<WorkspaceAppSearchEmpty
|
|
173
|
+
query={searchQuery}
|
|
174
|
+
onClear={() => setSearchQuery("")}
|
|
175
|
+
/>
|
|
176
|
+
)
|
|
177
|
+
) : showsEmptyAppsState ? (
|
|
178
|
+
<EmptyAppsState />
|
|
179
|
+
) : activeApps.length > 0 ? null : (
|
|
180
|
+
<EmptyActiveAppsState />
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
/>
|
|
231
184
|
</section>
|
|
232
185
|
|
|
233
186
|
{pendingApps.length > 0 &&
|
|
@@ -276,13 +229,9 @@ function AppsRoute() {
|
|
|
276
229
|
</CollapsibleTrigger>
|
|
277
230
|
</div>
|
|
278
231
|
<CollapsibleContent>
|
|
279
|
-
<AppList
|
|
232
|
+
<AppList layout="grid">
|
|
280
233
|
{filteredPendingApps.map((app) => (
|
|
281
|
-
<WorkspaceAppCard
|
|
282
|
-
key={app.id}
|
|
283
|
-
app={app}
|
|
284
|
-
className={APP_LIST_GRID_ROW_CLASS}
|
|
285
|
-
/>
|
|
234
|
+
<WorkspaceAppCard key={app.id} app={app} />
|
|
286
235
|
))}
|
|
287
236
|
</AppList>
|
|
288
237
|
</CollapsibleContent>
|
|
@@ -336,13 +285,9 @@ function AppsRoute() {
|
|
|
336
285
|
</CollapsibleTrigger>
|
|
337
286
|
</div>
|
|
338
287
|
<CollapsibleContent>
|
|
339
|
-
<AppList
|
|
288
|
+
<AppList layout="grid">
|
|
340
289
|
{filteredArchivedApps.map((app) => (
|
|
341
|
-
<WorkspaceAppCard
|
|
342
|
-
key={app.id}
|
|
343
|
-
app={app}
|
|
344
|
-
className={APP_LIST_GRID_ROW_CLASS}
|
|
345
|
-
/>
|
|
290
|
+
<WorkspaceAppCard key={app.id} app={app} />
|
|
346
291
|
))}
|
|
347
292
|
</AppList>
|
|
348
293
|
</CollapsibleContent>
|
|
@@ -358,22 +303,16 @@ function AppsRoute() {
|
|
|
358
303
|
|
|
359
304
|
function AppsSkeletonGrid() {
|
|
360
305
|
return (
|
|
361
|
-
<AppList
|
|
306
|
+
<AppList layout="grid">
|
|
362
307
|
{Array.from({ length: 3 }).map((_, index) => (
|
|
363
|
-
<
|
|
364
|
-
key={index}
|
|
365
|
-
className={cn(
|
|
366
|
-
"flex min-w-0 items-center gap-3 border-b px-4 py-3.5 last:border-b-0",
|
|
367
|
-
APP_LIST_GRID_ROW_CLASS,
|
|
368
|
-
)}
|
|
369
|
-
>
|
|
308
|
+
<AppListRow key={index}>
|
|
370
309
|
<Skeleton className="size-8 shrink-0 rounded-md" />
|
|
371
310
|
<div className="min-w-0 flex-1 space-y-2">
|
|
372
311
|
<Skeleton className="h-4 w-32" />
|
|
373
312
|
<Skeleton className="h-3 w-2/3" />
|
|
374
313
|
</div>
|
|
375
314
|
<Skeleton className="h-8 w-24 shrink-0 rounded-md" />
|
|
376
|
-
</
|
|
315
|
+
</AppListRow>
|
|
377
316
|
))}
|
|
378
317
|
</AppList>
|
|
379
318
|
);
|