@nextclaw/ui 0.14.4 → 0.15.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 +54 -0
- package/dist/assets/api-DLtsiBKm.js +13 -0
- package/dist/assets/appearance-settings-page-CtkeYXux.js +1 -0
- package/dist/assets/book-open-BQwWBDKb.js +1 -0
- package/dist/assets/channels-list-page-Cv8o9Hxg.js +8 -0
- package/dist/assets/chat-page-BtA7H1Ea.js +1 -0
- package/dist/assets/{config-split-page-CRhlZ_pH.js → config-split-page-EidRBpP2.js} +1 -1
- package/dist/assets/confirm-dialog-iIJFFp2T.js +5 -0
- package/dist/assets/desktop-update-config-C6n_TB1r.js +1 -0
- package/dist/assets/{dist-CikkhOlq.js → dist-CBxWOazz.js} +1 -1
- package/dist/assets/{dist-PVA_xL1D.js → dist-D2eZC3OR.js} +1 -1
- package/dist/assets/doc-browser-B76XOXmW.js +1 -0
- package/dist/assets/doc-browser-DBnj9UMB.js +1 -0
- package/dist/assets/{doc-browser-context-MTw4EvKd.js → doc-browser-context-Sv-044Cl.js} +1 -1
- package/dist/assets/{ellipsis-y6vDtq4M.js → ellipsis-2uzhypw5.js} +1 -1
- package/dist/assets/es2015-DhQo_oS6.js +41 -0
- package/dist/assets/{external-link-DVJIQf_G.js → external-link-CM5dnUI6.js} +1 -1
- package/dist/assets/host-capabilities-9bB_998Z.js +1 -0
- package/dist/assets/i18n-provider-di1waTGX.js +1 -0
- package/dist/assets/index-CBRP-7-a.css +1 -0
- package/dist/assets/index-Z1fYpbNd.js +104 -0
- package/dist/assets/loader-circle-BjmN5DeJ.js +1 -0
- package/dist/assets/mcp-marketplace-page-C_MVQUbT.js +9 -0
- package/dist/assets/mcp-marketplace-page-CoZIiD6G.js +1 -0
- package/dist/assets/model-config-page-Cdr9P6TQ.js +1 -0
- package/dist/assets/plus-3ztRZD_l.js +1 -0
- package/dist/assets/provider-scoped-model-input-g1WkGiJ8.js +1 -0
- package/dist/assets/providers-config-page-CWlO7J4A.js +1 -0
- package/dist/assets/{react-VNqT5Zp7.js → react-DjpzoZXB.js} +1 -1
- package/dist/assets/refresh-cw-DaTv3zxE.js +1 -0
- package/dist/assets/remote-CKWZfVVB.js +1 -0
- package/dist/assets/rotate-cw-CHcaRUDz.js +1 -0
- package/dist/assets/runtime-config-page-CfTqh1nv.js +1 -0
- package/dist/assets/{save-BOJ477H-.js → save-BZof1uie.js} +1 -1
- package/dist/assets/{search-BEti5beA.js → search-MxAarS_g.js} +1 -1
- package/dist/assets/search-config-page-Dm8daYny.js +1 -0
- package/dist/assets/{secrets-config-page--IzrIOid.js → secrets-config-page-D8AbcR05.js} +2 -2
- package/dist/assets/security-config-Ds0LsJmo.js +1 -0
- package/dist/assets/select-D7GcNP0z.js +1 -0
- package/dist/assets/setting-row-CuozlnJd.js +1 -0
- package/dist/assets/side-dock-BcLau5S_.js +3 -0
- package/dist/assets/skeleton-shfmXuYp.js +1 -0
- package/dist/assets/switch-C1BWMU5c.js +1 -0
- package/dist/assets/tag-chip-DCUrQUCP.js +1 -0
- package/dist/assets/tooltip-DfAHHA76.js +1 -0
- package/dist/assets/use-config-DAdbT5QJ.js +1 -0
- package/dist/assets/use-confirm-dialog-eWcJ6V-s.js +1 -0
- package/dist/assets/x-DqL1mYLC.js +1 -0
- package/dist/index.html +53 -17
- package/index.html +25 -2
- package/package.json +9 -9
- package/src/app/components/layout/__tests__/app-layout.test.tsx +27 -0
- package/src/app/components/layout/__tests__/settings-entry-page.test.tsx +9 -1
- package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +6 -3
- package/src/app/components/layout/app-layout.tsx +10 -3
- package/src/app/components/layout/sidebar-items.tsx +8 -8
- package/src/app/components/layout/sidebar-rail.styles.ts +4 -2
- package/src/app/components/layout/sidebar.tsx +21 -28
- package/src/app/configs/app-navigation.config.ts +11 -5
- package/src/app/index.tsx +8 -0
- package/src/app/managers/__tests__/viewport-layout.manager.test.ts +59 -0
- package/src/app/managers/viewport-layout.manager.ts +39 -0
- package/src/app/presenters/app.presenter.ts +25 -2
- package/src/app/styles/design-system.css +91 -4
- package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +86 -0
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +47 -17
- package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +7 -0
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +9 -5
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +5 -5
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +51 -2
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +170 -1
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +23 -1
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +24 -7
- package/src/features/chat/features/conversation/components/session-queued-input-rows.tsx +96 -0
- package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-controller.test.tsx +181 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +351 -89
- package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +29 -4
- package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +28 -3
- package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -8
- package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +50 -14
- package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +77 -6
- package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +21 -12
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +166 -0
- package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +19 -11
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +38 -64
- package/src/features/chat/features/message/utils/__tests__/chat-message-process-summary.utils.test.ts +59 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message-show-content-tool-card.utils.test.ts +56 -1
- package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +0 -2
- package/src/features/chat/features/message/utils/chat-message-process-summary.utils.ts +80 -0
- package/src/features/chat/features/message/utils/chat-message-show-content-tool-card.utils.ts +12 -3
- package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +60 -0
- package/src/features/chat/features/message/utils/chat-message.utils.ts +3 -0
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime.test.tsx +39 -0
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +2 -2
- package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +3 -1
- package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +1 -1
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +270 -115
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +121 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +75 -20
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +70 -5
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +83 -0
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +79 -1
- package/src/features/chat/managers/__tests__/chat-ui.manager.test.ts +2 -0
- package/src/features/chat/managers/chat-thread.manager.ts +9 -3
- package/src/features/chat/managers/chat-ui.manager.ts +5 -1
- package/src/features/chat/presenters/chat.presenter.ts +2 -1
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +4 -0
- package/src/features/chat/stores/chat-thread.store.ts +6 -64
- package/src/features/chat/types/chat-message.types.ts +4 -0
- package/src/features/marketplace/components/__tests__/marketplace-page-detail.test.tsx +33 -26
- package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +1 -0
- package/src/features/marketplace/components/curated-shelves/__tests__/marketplace-curated-scene-route.test.tsx +1 -0
- package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.tsx +115 -0
- package/src/features/marketplace/components/marketplace-detail-doc.tsx +441 -0
- package/src/features/marketplace/components/mcp/__tests__/mcp-marketplace-page.test.tsx +3 -3
- package/src/features/marketplace/components/mcp/mcp-marketplace-doc.ts +0 -60
- package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +64 -29
- package/src/features/marketplace/hooks/use-marketplace-item-detail.ts +42 -31
- package/src/features/marketplace/index.ts +3 -0
- package/src/features/marketplace/stores/marketplace-detail-doc.store.ts +38 -0
- package/src/features/panel-apps/components/__tests__/panel-apps-list.test.tsx +32 -2
- package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +11 -0
- package/src/features/pwa/managers/pwa-shell-theme.manager.ts +8 -3
- package/src/features/settings/pages/__tests__/appearance-settings-page.test.tsx +25 -0
- package/src/features/settings/pages/appearance-settings-page.tsx +40 -0
- package/src/features/side-dock/components/__tests__/side-dock.test.tsx +44 -2
- package/src/features/side-dock/components/side-dock.tsx +100 -13
- package/src/features/side-dock/configs/side-dock-built-in-items.config.ts +10 -0
- package/src/features/side-dock/managers/__tests__/side-dock.manager.test.ts +32 -2
- package/src/features/side-dock/managers/side-dock.manager.ts +17 -7
- package/src/features/side-dock/stores/side-dock.store.ts +9 -0
- package/src/features/side-dock/types/side-dock.types.ts +10 -3
- package/src/features/side-dock/utils/side-dock-item.utils.ts +15 -2
- package/src/index.css +501 -27
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +1 -1
- package/src/shared/components/doc-browser/__tests__/doc-browser-context.test.tsx +30 -1
- package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +206 -38
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +34 -11
- package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +1 -1
- package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +23 -5
- package/src/shared/components/doc-browser/doc-browser.tsx +128 -75
- package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +9 -1
- package/src/shared/components/doc-browser/utils/doc-browser-floating-panel.utils.ts +83 -0
- package/src/shared/components/ui/__tests__/dialog.test.tsx +27 -0
- package/src/shared/components/ui/actions/icon-action-button.tsx +14 -2
- package/src/shared/components/ui/dialog.tsx +2 -2
- package/src/shared/components/ui/popover.tsx +1 -1
- package/src/shared/components/ui/select.tsx +1 -1
- package/src/shared/components/ui/switch.tsx +4 -3
- package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +54 -0
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +4 -5
- package/src/shared/components/ui/tooltip.tsx +1 -1
- package/src/shared/lib/api/utils/server-path.utils.ts +27 -1
- package/src/shared/lib/i18n/locales/en-US/chat.json +15 -6
- package/src/shared/lib/i18n/locales/en-US/core.json +2 -0
- package/src/shared/lib/i18n/locales/en-US/doc-browser.json +11 -0
- package/src/shared/lib/i18n/locales/en-US/marketplace.json +5 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +15 -6
- package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -0
- package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +11 -0
- package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +5 -0
- package/src/shared/lib/session-project/__tests__/workspace-file-breadcrumb.test.ts +14 -0
- package/src/shared/lib/session-project/index.ts +1 -1
- package/src/shared/lib/session-project/{workspace-file-breadcrumb.ts → workspace-file-breadcrumb.utils.ts} +62 -8
- package/src/shared/lib/theme/index.ts +51 -13
- package/dist/assets/api-BP_8IaxB.js +0 -13
- package/dist/assets/channels-list-page-DEh5Jjqx.js +0 -8
- package/dist/assets/chat-page-BRB0N5WW.js +0 -105
- package/dist/assets/confirm-dialog-DsivFJ4r.js +0 -5
- package/dist/assets/desktop-update-config-B0w9p2ep.js +0 -1
- package/dist/assets/doc-browser-BoNJ7-tE.js +0 -1
- package/dist/assets/doc-browser-CdJ_d4ik.js +0 -1
- package/dist/assets/index-BiE-K8-q.css +0 -1
- package/dist/assets/index-D9BAE_L_.js +0 -105
- package/dist/assets/mcp-marketplace-page-B1SZNY5m.js +0 -40
- package/dist/assets/mcp-marketplace-page-Cq_Tw42T.js +0 -1
- package/dist/assets/model-config-page-DotaTUHb.js +0 -1
- package/dist/assets/plus-BbZqquJe.js +0 -1
- package/dist/assets/provider-scoped-model-input-DsFRuWJH.js +0 -1
- package/dist/assets/providers-config-page-DW4tQSLN.js +0 -1
- package/dist/assets/remote-B8TiUfwW.js +0 -1
- package/dist/assets/rotate-cw-DjHDTkCv.js +0 -1
- package/dist/assets/runtime-config-page-S6ErRHR8.js +0 -1
- package/dist/assets/search-config-page-DGj5am03.js +0 -1
- package/dist/assets/select-hoj8dK4K.js +0 -41
- package/dist/assets/tag-chip-T8-h-die.js +0 -1
- package/dist/assets/use-config-CjRV2hwL.js +0 -1
- package/dist/assets/x-D9PXs_xP.js +0 -1
- package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.ts +0 -40
- package/src/features/marketplace/components/detail-doc/marketplace-detail-doc-renderer.ts +0 -201
- package/src/features/marketplace/components/marketplace-detail-doc.ts +0 -142
- /package/dist/assets/{config-hints-BkYBc7z0.js → config-hints-fGnUjDe9.js} +0 -0
- /package/dist/assets/{middleware-CkFT3R4v.js → middleware-BILWbJE4.js} +0 -0
|
@@ -4,7 +4,10 @@ import type {
|
|
|
4
4
|
} from "@/shared/lib/api";
|
|
5
5
|
import { fetchMarketplaceSkillContent } from "@/shared/lib/api";
|
|
6
6
|
import { useDocBrowser } from "@/shared/components/doc-browser";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
createMarketplaceDetailDocId,
|
|
9
|
+
openMarketplaceDetailDoc,
|
|
10
|
+
} from "@/features/marketplace/components/marketplace-detail-doc";
|
|
8
11
|
import { pickLocalizedText } from "@/features/marketplace/components/marketplace-localization";
|
|
9
12
|
import { t } from "@/shared/lib/i18n";
|
|
10
13
|
import { useRef } from "react";
|
|
@@ -23,25 +26,26 @@ export function useMarketplaceItemDetail(localeFallbacks: string[]) {
|
|
|
23
26
|
record?.id ??
|
|
24
27
|
record?.spec ??
|
|
25
28
|
t("marketplaceUnknownItem");
|
|
29
|
+
const detailId = createMarketplaceDetailDocId(
|
|
30
|
+
item ? "skill" : "skill-local",
|
|
31
|
+
item?.slug ?? record?.id ?? record?.spec ?? title,
|
|
32
|
+
);
|
|
26
33
|
const dedupeKey = item
|
|
27
34
|
? `marketplace:${item.type}:${item.slug}`
|
|
28
35
|
: `marketplace:${record?.type ?? "unknown"}:${record?.id ?? record?.spec ?? title}`;
|
|
29
|
-
const openOptions = { title, kind: "content" as const, dedupeKey };
|
|
30
|
-
const updateOptions = { ...openOptions, activate: false };
|
|
31
36
|
const typeLabel = t("marketplaceTypeSkill");
|
|
32
37
|
|
|
33
38
|
if (!item) {
|
|
34
|
-
docBrowser
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
);
|
|
39
|
+
openMarketplaceDetailDoc(docBrowser, {
|
|
40
|
+
id: detailId,
|
|
41
|
+
title,
|
|
42
|
+
typeLabel,
|
|
43
|
+
spec: record?.spec ?? "-",
|
|
44
|
+
status: "ready",
|
|
45
|
+
summary: t("marketplaceInstalledLocalSummary"),
|
|
46
|
+
metadataRaw: JSON.stringify(record ?? {}, null, 2),
|
|
47
|
+
contentRaw: "-",
|
|
48
|
+
});
|
|
45
49
|
return;
|
|
46
50
|
}
|
|
47
51
|
|
|
@@ -53,26 +57,30 @@ export function useMarketplaceItemDetail(localeFallbacks: string[]) {
|
|
|
53
57
|
item.summary,
|
|
54
58
|
localeFallbacks,
|
|
55
59
|
);
|
|
56
|
-
docBrowser
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
openMarketplaceDetailDoc(docBrowser, {
|
|
61
|
+
id: detailId,
|
|
62
|
+
title,
|
|
63
|
+
typeLabel,
|
|
64
|
+
spec: item.install.spec,
|
|
65
|
+
status: "loading",
|
|
66
|
+
summary,
|
|
67
|
+
tags: item.tags,
|
|
68
|
+
author: item.author,
|
|
69
|
+
});
|
|
65
70
|
|
|
66
71
|
try {
|
|
67
72
|
const content = await fetchMarketplaceSkillContent(item.slug);
|
|
68
73
|
if (detailRequestRef.current.byKey.get(dedupeKey) !== requestId) {
|
|
69
74
|
return;
|
|
70
75
|
}
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
openMarketplaceDetailDoc(
|
|
77
|
+
docBrowser,
|
|
78
|
+
{
|
|
79
|
+
id: detailId,
|
|
73
80
|
title,
|
|
74
81
|
typeLabel,
|
|
75
82
|
spec: item.install.spec,
|
|
83
|
+
status: "ready",
|
|
76
84
|
summary,
|
|
77
85
|
metadataRaw: content.metadataRaw,
|
|
78
86
|
contentRaw: content.bodyRaw || content.raw,
|
|
@@ -80,18 +88,21 @@ export function useMarketplaceItemDetail(localeFallbacks: string[]) {
|
|
|
80
88
|
sourceLabel: `Source (${content.source})`,
|
|
81
89
|
tags: item.tags,
|
|
82
90
|
author: item.author,
|
|
83
|
-
}
|
|
84
|
-
|
|
91
|
+
},
|
|
92
|
+
{ activate: false },
|
|
85
93
|
);
|
|
86
94
|
} catch (error) {
|
|
87
95
|
if (detailRequestRef.current.byKey.get(dedupeKey) !== requestId) {
|
|
88
96
|
return;
|
|
89
97
|
}
|
|
90
|
-
|
|
91
|
-
|
|
98
|
+
openMarketplaceDetailDoc(
|
|
99
|
+
docBrowser,
|
|
100
|
+
{
|
|
101
|
+
id: detailId,
|
|
92
102
|
title,
|
|
93
103
|
typeLabel,
|
|
94
104
|
spec: item.install.spec,
|
|
105
|
+
status: "error",
|
|
95
106
|
summary,
|
|
96
107
|
metadataRaw: JSON.stringify(
|
|
97
108
|
{ error: error instanceof Error ? error.message : String(error) },
|
|
@@ -99,8 +110,8 @@ export function useMarketplaceItemDetail(localeFallbacks: string[]) {
|
|
|
99
110
|
2,
|
|
100
111
|
),
|
|
101
112
|
contentRaw: t("marketplaceOperationFailed"),
|
|
102
|
-
}
|
|
103
|
-
|
|
113
|
+
},
|
|
114
|
+
{ activate: false },
|
|
104
115
|
);
|
|
105
116
|
}
|
|
106
117
|
};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export { MarketplacePage } from './components/marketplace-page';
|
|
2
2
|
export { McpMarketplacePage } from './components/mcp/mcp-marketplace-page';
|
|
3
|
+
export {
|
|
4
|
+
MARKETPLACE_DETAIL_DOC_BROWSER_RENDERERS,
|
|
5
|
+
} from './components/marketplace-detail-doc';
|
|
3
6
|
export {
|
|
4
7
|
useInstallMarketplaceItem,
|
|
5
8
|
useManageMarketplaceItem,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
|
|
3
|
+
export type MarketplaceDetailDocStatus = 'loading' | 'ready' | 'error';
|
|
4
|
+
|
|
5
|
+
export type MarketplaceDetailDocEntry = {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
typeLabel: string;
|
|
9
|
+
spec: string;
|
|
10
|
+
status: MarketplaceDetailDocStatus;
|
|
11
|
+
summary?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
metadataRaw?: string;
|
|
14
|
+
contentRaw?: string;
|
|
15
|
+
sourceUrl?: string;
|
|
16
|
+
sourceLabel?: string;
|
|
17
|
+
tags?: string[];
|
|
18
|
+
author?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type MarketplaceDetailDocStore = {
|
|
22
|
+
entries: Record<string, MarketplaceDetailDocEntry>;
|
|
23
|
+
setEntry: (entry: MarketplaceDetailDocEntry) => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const useMarketplaceDetailDocStore = create<MarketplaceDetailDocStore>((set) => ({
|
|
27
|
+
entries: {},
|
|
28
|
+
setEntry: (entry) => set((state) => ({
|
|
29
|
+
entries: {
|
|
30
|
+
...state.entries,
|
|
31
|
+
[entry.id]: entry,
|
|
32
|
+
},
|
|
33
|
+
})),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
export function setMarketplaceDetailDocEntry(entry: MarketplaceDetailDocEntry): void {
|
|
37
|
+
useMarketplaceDetailDocStore.getState().setEntry(entry);
|
|
38
|
+
}
|
|
@@ -20,8 +20,10 @@ const mocks = vi.hoisted(() => ({
|
|
|
20
20
|
title: string;
|
|
21
21
|
updatedAt: string;
|
|
22
22
|
}>,
|
|
23
|
+
grantPanelAppClient: vi.fn(),
|
|
23
24
|
navigate: vi.fn(),
|
|
24
25
|
refetchPanelApps: vi.fn(),
|
|
26
|
+
requestAuthorization: vi.fn(async () => true),
|
|
25
27
|
requestDraft: vi.fn(),
|
|
26
28
|
}));
|
|
27
29
|
|
|
@@ -31,7 +33,7 @@ vi.mock('@/app/components/app-presenter-provider', () => ({
|
|
|
31
33
|
requestDraft: mocks.requestDraft,
|
|
32
34
|
},
|
|
33
35
|
serviceActionAuthorizationManager: {
|
|
34
|
-
requestAuthorization:
|
|
36
|
+
requestAuthorization: mocks.requestAuthorization,
|
|
35
37
|
},
|
|
36
38
|
}),
|
|
37
39
|
}));
|
|
@@ -47,7 +49,7 @@ vi.mock('@/features/panel-apps/hooks/use-panel-apps', () => ({
|
|
|
47
49
|
mutate: vi.fn(),
|
|
48
50
|
}),
|
|
49
51
|
useGrantPanelAppClient: () => ({
|
|
50
|
-
mutateAsync:
|
|
52
|
+
mutateAsync: mocks.grantPanelAppClient,
|
|
51
53
|
}),
|
|
52
54
|
usePanelApps: () => ({
|
|
53
55
|
data: {
|
|
@@ -91,8 +93,11 @@ function createPanelAppEntry(overrides: Partial<(typeof mocks.entries)[number]>
|
|
|
91
93
|
describe('PanelAppsList', () => {
|
|
92
94
|
beforeEach(() => {
|
|
93
95
|
mocks.entries = [];
|
|
96
|
+
mocks.grantPanelAppClient.mockReset();
|
|
94
97
|
mocks.navigate.mockReset();
|
|
95
98
|
mocks.refetchPanelApps.mockReset();
|
|
99
|
+
mocks.requestAuthorization.mockReset();
|
|
100
|
+
mocks.requestAuthorization.mockResolvedValue(true);
|
|
96
101
|
mocks.requestDraft.mockReset();
|
|
97
102
|
});
|
|
98
103
|
|
|
@@ -134,4 +139,29 @@ describe('PanelAppsList', () => {
|
|
|
134
139
|
expect(screen.getByText('No panel apps in this view')).toBeTruthy();
|
|
135
140
|
expect(screen.queryByText('Create your first panel app')).toBeNull();
|
|
136
141
|
});
|
|
142
|
+
|
|
143
|
+
it('continues opening a client panel app after authorization', async () => {
|
|
144
|
+
const user = userEvent.setup();
|
|
145
|
+
const onOpenPanelApp = vi.fn();
|
|
146
|
+
const entry = createPanelAppEntry({
|
|
147
|
+
appId: 'demo-app',
|
|
148
|
+
clientDeclared: true,
|
|
149
|
+
clientGranted: false,
|
|
150
|
+
});
|
|
151
|
+
mocks.entries = [entry];
|
|
152
|
+
|
|
153
|
+
render(<PanelAppsList onOpenPanelApp={onOpenPanelApp} />);
|
|
154
|
+
|
|
155
|
+
await user.click(screen.getByRole('button', { name: /Demo Panel/ }));
|
|
156
|
+
|
|
157
|
+
expect(mocks.requestAuthorization).toHaveBeenCalledWith(expect.objectContaining({
|
|
158
|
+
panelAppId: 'demo-app',
|
|
159
|
+
actions: [expect.objectContaining({
|
|
160
|
+
actionId: 'nextclaw.client',
|
|
161
|
+
risk: 'dangerous',
|
|
162
|
+
})],
|
|
163
|
+
}));
|
|
164
|
+
expect(mocks.grantPanelAppClient).toHaveBeenCalledWith('demo-app');
|
|
165
|
+
expect(onOpenPanelApp).toHaveBeenCalledWith(entry);
|
|
166
|
+
});
|
|
137
167
|
});
|
|
@@ -6,7 +6,9 @@ describe('PwaShellThemeManager', () => {
|
|
|
6
6
|
document.head.innerHTML = '<meta name="theme-color" content="#FFFFFF" />';
|
|
7
7
|
document.body.innerHTML = '<div id="root"></div>';
|
|
8
8
|
document.documentElement.removeAttribute('data-theme');
|
|
9
|
+
document.documentElement.removeAttribute('data-theme-appearance');
|
|
9
10
|
document.documentElement.style.backgroundColor = '';
|
|
11
|
+
document.documentElement.style.colorScheme = '';
|
|
10
12
|
document.body.style.backgroundColor = '';
|
|
11
13
|
});
|
|
12
14
|
|
|
@@ -72,6 +74,15 @@ describe('PwaShellThemeManager', () => {
|
|
|
72
74
|
expect(document.body.style.backgroundColor).toBe('rgb(248, 248, 247)');
|
|
73
75
|
});
|
|
74
76
|
|
|
77
|
+
it('applies night shell colors and dark color scheme', () => {
|
|
78
|
+
pwaShellThemeManager.syncTheme('night');
|
|
79
|
+
|
|
80
|
+
const meta = document.querySelector('meta[name="theme-color"]');
|
|
81
|
+
expect(meta?.getAttribute('content')).toBe('#101318');
|
|
82
|
+
expect(document.body.style.backgroundColor).toBe('rgb(16, 19, 24)');
|
|
83
|
+
expect(document.documentElement.style.colorScheme).toBe('dark');
|
|
84
|
+
});
|
|
85
|
+
|
|
75
86
|
it('maps the legacy leaf theme to warm shell colors', () => {
|
|
76
87
|
document.documentElement.setAttribute('data-theme', 'leaf');
|
|
77
88
|
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
getThemeAppearance,
|
|
3
|
+
normalizeTheme,
|
|
4
|
+
type UiTheme,
|
|
5
|
+
} from '@/shared/lib/theme';
|
|
2
6
|
|
|
3
7
|
const PWA_SHELL_THEME_COLORS: Record<UiTheme, string> = {
|
|
4
8
|
natural: '#FAF9F7',
|
|
@@ -7,7 +11,8 @@ const PWA_SHELL_THEME_COLORS: Record<UiTheme, string> = {
|
|
|
7
11
|
cool: '#F8FAFC',
|
|
8
12
|
dawn: '#FAF7F4',
|
|
9
13
|
graphite: '#F8F8F7',
|
|
10
|
-
|
|
14
|
+
night: '#101318',
|
|
15
|
+
probe: '#FEFCF6',
|
|
11
16
|
};
|
|
12
17
|
|
|
13
18
|
export class PwaShellThemeManager {
|
|
@@ -19,7 +24,7 @@ export class PwaShellThemeManager {
|
|
|
19
24
|
const themeColor = PWA_SHELL_THEME_COLORS[theme];
|
|
20
25
|
this.updateThemeMeta(themeColor);
|
|
21
26
|
this.updateSurfaceBackgrounds(themeColor);
|
|
22
|
-
document.documentElement.style.colorScheme =
|
|
27
|
+
document.documentElement.style.colorScheme = getThemeAppearance(theme);
|
|
23
28
|
};
|
|
24
29
|
|
|
25
30
|
syncCurrentTheme = () => {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
2
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
3
|
+
import { AppearanceSettingsPage } from '@/features/settings/pages/appearance-settings-page';
|
|
4
|
+
import { useSideDockStore } from '@/features/side-dock';
|
|
5
|
+
|
|
6
|
+
describe('AppearanceSettingsPage', () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
window.localStorage.clear();
|
|
9
|
+
useSideDockStore.getState().setVisible(true);
|
|
10
|
+
useSideDockStore.getState().setPinnedItems([]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('reopens the SideDock from the appearance settings switch', () => {
|
|
14
|
+
useSideDockStore.getState().setVisible(false);
|
|
15
|
+
|
|
16
|
+
render(<AppearanceSettingsPage />);
|
|
17
|
+
|
|
18
|
+
const visibilitySwitch = screen.getByRole('switch', { name: 'Show SideDock' });
|
|
19
|
+
expect(visibilitySwitch.getAttribute('aria-checked')).toBe('false');
|
|
20
|
+
|
|
21
|
+
fireEvent.click(visibilitySwitch);
|
|
22
|
+
|
|
23
|
+
expect(useSideDockStore.getState().isVisible).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Palette } from 'lucide-react';
|
|
2
|
+
import { PageHeader, PageLayout } from '@/app/components/layout/page-layout';
|
|
3
|
+
import { SettingRow } from '@/shared/components/settings/setting-row';
|
|
4
|
+
import { Card, CardContent, CardHeader, CardTitle } from '@/shared/components/ui/card';
|
|
5
|
+
import { Switch } from '@/shared/components/ui/switch';
|
|
6
|
+
import { useSideDockStore } from '@/features/side-dock';
|
|
7
|
+
import { t } from '@/shared/lib/i18n';
|
|
8
|
+
|
|
9
|
+
export function AppearanceSettingsPage() {
|
|
10
|
+
const isSideDockVisible = useSideDockStore((state) => state.isVisible);
|
|
11
|
+
const setSideDockVisible = useSideDockStore((state) => state.setVisible);
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<PageLayout>
|
|
15
|
+
<PageHeader title={t('appearance')} />
|
|
16
|
+
<Card>
|
|
17
|
+
<CardHeader>
|
|
18
|
+
<CardTitle className="flex items-center gap-2">
|
|
19
|
+
<Palette className="h-4 w-4" />
|
|
20
|
+
{t('appearance')}
|
|
21
|
+
</CardTitle>
|
|
22
|
+
</CardHeader>
|
|
23
|
+
<CardContent>
|
|
24
|
+
<SettingRow
|
|
25
|
+
title={t('sideDockVisibilityTitle')}
|
|
26
|
+
description={t('sideDockVisibilityDescription')}
|
|
27
|
+
control={
|
|
28
|
+
<Switch
|
|
29
|
+
id="appearance-side-dock-visible"
|
|
30
|
+
aria-label={t('sideDockVisibilityTitle')}
|
|
31
|
+
checked={isSideDockVisible}
|
|
32
|
+
onCheckedChange={setSideDockVisible}
|
|
33
|
+
/>
|
|
34
|
+
}
|
|
35
|
+
/>
|
|
36
|
+
</CardContent>
|
|
37
|
+
</Card>
|
|
38
|
+
</PageLayout>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { fireEvent, render, screen } from '@testing-library/react';
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
2
2
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
getSideDockBuiltInItems,
|
|
5
|
+
SIDE_DOCK_GITHUB_PROJECT_ITEM_ID,
|
|
6
|
+
} from '@/features/side-dock/configs/side-dock-built-in-items.config';
|
|
4
7
|
import { SideDock } from '@/features/side-dock/components/side-dock';
|
|
5
8
|
import { useSideDockStore } from '@/features/side-dock/stores/side-dock.store';
|
|
6
9
|
import type { SideDockManager } from '@/features/side-dock/managers/side-dock.manager';
|
|
@@ -12,6 +15,7 @@ import { createDefaultDocBrowserState } from '@/shared/components/doc-browser/ut
|
|
|
12
15
|
describe('SideDock', () => {
|
|
13
16
|
beforeEach(() => {
|
|
14
17
|
window.localStorage.clear();
|
|
18
|
+
useSideDockStore.getState().setVisible(true);
|
|
15
19
|
useSideDockStore.getState().setPinnedItems([]);
|
|
16
20
|
useDocBrowserStore.getState().setSnapshot(createDefaultDocBrowserState());
|
|
17
21
|
});
|
|
@@ -40,6 +44,44 @@ describe('SideDock', () => {
|
|
|
40
44
|
expect(openItem).toHaveBeenCalledWith(builtInItems[0]);
|
|
41
45
|
});
|
|
42
46
|
|
|
47
|
+
it('places the GitHub project shortcut in the utility section', () => {
|
|
48
|
+
const manager = { openItem: vi.fn() } as unknown as SideDockManager;
|
|
49
|
+
const docBrowserManager = new DocBrowserManager();
|
|
50
|
+
|
|
51
|
+
render(
|
|
52
|
+
<DocBrowserProvider manager={docBrowserManager}>
|
|
53
|
+
<SideDock manager={manager} />
|
|
54
|
+
</DocBrowserProvider>,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const utilitySection = screen.getByTestId('side-dock-utility');
|
|
58
|
+
expect(utilitySection.className).toContain('mt-auto');
|
|
59
|
+
expect(utilitySection.querySelector(`[data-side-dock-item-id="${SIDE_DOCK_GITHUB_PROJECT_ITEM_ID}"]`)).toBeTruthy();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('confirms before hiding the dock and persists the visibility preference', async () => {
|
|
63
|
+
const manager = { openItem: vi.fn() } as unknown as SideDockManager;
|
|
64
|
+
const docBrowserManager = new DocBrowserManager();
|
|
65
|
+
|
|
66
|
+
render(
|
|
67
|
+
<DocBrowserProvider manager={docBrowserManager}>
|
|
68
|
+
<SideDock manager={manager} />
|
|
69
|
+
</DocBrowserProvider>,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const hideButton = document.querySelector('[data-side-dock-action="hide"]');
|
|
73
|
+
expect(hideButton).toBeTruthy();
|
|
74
|
+
|
|
75
|
+
fireEvent.click(hideButton as Element);
|
|
76
|
+
|
|
77
|
+
expect(await screen.findByText('You can turn it back on from Settings > Appearance.')).toBeTruthy();
|
|
78
|
+
fireEvent.click(screen.getByRole('button', { name: 'Hide' }));
|
|
79
|
+
|
|
80
|
+
await waitFor(() => {
|
|
81
|
+
expect(useSideDockStore.getState().isVisible).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
43
85
|
it('does not highlight the default docs tab while DocBrowser is closed', () => {
|
|
44
86
|
const manager = { openItem: vi.fn() } as unknown as SideDockManager;
|
|
45
87
|
const docBrowserManager = new DocBrowserManager();
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { AppWindow, BookOpen, Boxes, Plus, X } from 'lucide-react';
|
|
1
|
+
import { AppWindow, BookOpen, Boxes, Github, PanelRightClose, Plus, X } from 'lucide-react';
|
|
2
2
|
import type { LucideIcon } from 'lucide-react';
|
|
3
3
|
import type { SideDockManager } from '@/features/side-dock/managers/side-dock.manager';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getSideDockBuiltInItems,
|
|
6
|
+
SIDE_DOCK_GITHUB_PROJECT_ITEM_ID,
|
|
7
|
+
} from '@/features/side-dock/configs/side-dock-built-in-items.config';
|
|
5
8
|
import { useSideDockStore } from '@/features/side-dock/stores/side-dock.store';
|
|
6
9
|
import type {
|
|
7
10
|
SideDockIconName,
|
|
@@ -19,6 +22,7 @@ import {
|
|
|
19
22
|
TooltipProvider,
|
|
20
23
|
TooltipTrigger,
|
|
21
24
|
} from '@/shared/components/ui/tooltip';
|
|
25
|
+
import { useConfirmDialog } from '@/shared/hooks/use-confirm-dialog';
|
|
22
26
|
import { t } from '@/shared/lib/i18n';
|
|
23
27
|
import { cn } from '@/shared/lib/utils';
|
|
24
28
|
|
|
@@ -29,11 +33,14 @@ type SideDockProps = {
|
|
|
29
33
|
const SIDE_DOCK_ICON_COMPONENTS: Record<SideDockIconName, LucideIcon> = {
|
|
30
34
|
apps: Boxes,
|
|
31
35
|
docs: BookOpen,
|
|
36
|
+
github: Github,
|
|
32
37
|
'new-tab': Plus,
|
|
33
38
|
'panel-app': AppWindow,
|
|
34
39
|
'service-apps': AppWindow,
|
|
35
40
|
};
|
|
36
41
|
|
|
42
|
+
const SIDE_DOCK_UTILITY_ITEM_IDS = new Set<string>([SIDE_DOCK_GITHUB_PROJECT_ITEM_ID]);
|
|
43
|
+
|
|
37
44
|
const SIDE_DOCK_EMOJI_ICON_PATTERN = /^\p{Extended_Pictographic}(?:\uFE0F|\uFE0E)?(?:\u200D\p{Extended_Pictographic}(?:\uFE0F|\uFE0E)?)*$/u;
|
|
38
45
|
|
|
39
46
|
function isSideDockEmojiIcon(value: string): boolean {
|
|
@@ -117,10 +124,45 @@ function SideDockButton({
|
|
|
117
124
|
);
|
|
118
125
|
}
|
|
119
126
|
|
|
127
|
+
function SideDockActionButton({
|
|
128
|
+
icon: Icon,
|
|
129
|
+
label,
|
|
130
|
+
onClick,
|
|
131
|
+
}: {
|
|
132
|
+
icon: LucideIcon;
|
|
133
|
+
label: string;
|
|
134
|
+
onClick: () => void;
|
|
135
|
+
}) {
|
|
136
|
+
return (
|
|
137
|
+
<Tooltip>
|
|
138
|
+
<TooltipTrigger asChild>
|
|
139
|
+
<button
|
|
140
|
+
type="button"
|
|
141
|
+
aria-label={label}
|
|
142
|
+
title={label}
|
|
143
|
+
data-side-dock-action="hide"
|
|
144
|
+
onClick={onClick}
|
|
145
|
+
className={cn(
|
|
146
|
+
'flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors',
|
|
147
|
+
'hover:bg-primary/10 hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
148
|
+
)}
|
|
149
|
+
>
|
|
150
|
+
<Icon className="h-5 w-5" aria-hidden="true" />
|
|
151
|
+
</button>
|
|
152
|
+
</TooltipTrigger>
|
|
153
|
+
<TooltipContent side="left">{label}</TooltipContent>
|
|
154
|
+
</Tooltip>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
120
158
|
function normalizeSideDockUri(uri?: string): string {
|
|
121
159
|
return uri?.trim() ?? '';
|
|
122
160
|
}
|
|
123
161
|
|
|
162
|
+
function getSideDockResourceUri(item: SideDockItem): string | null {
|
|
163
|
+
return item.target.type === 'right-panel-resource' ? item.target.uri : null;
|
|
164
|
+
}
|
|
165
|
+
|
|
124
166
|
function isSideDockItemActive(
|
|
125
167
|
item: SideDockItem,
|
|
126
168
|
isDocBrowserOpen: boolean,
|
|
@@ -130,7 +172,10 @@ function isSideDockItemActive(
|
|
|
130
172
|
if (!isDocBrowserOpen || !currentTab) {
|
|
131
173
|
return false;
|
|
132
174
|
}
|
|
133
|
-
const itemUri = normalizeSideDockUri(item
|
|
175
|
+
const itemUri = normalizeSideDockUri(getSideDockResourceUri(item) ?? undefined);
|
|
176
|
+
if (!itemUri) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
134
179
|
const currentResourceUri = normalizeSideDockUri(currentTab.resourceUri);
|
|
135
180
|
if (
|
|
136
181
|
(currentResourceUri && currentResourceUri === itemUri)
|
|
@@ -151,16 +196,35 @@ function hasExactActiveSideDockItem(items: SideDockItem[], currentTab?: DocBrows
|
|
|
151
196
|
const currentResourceUri = normalizeSideDockUri(currentTab.resourceUri);
|
|
152
197
|
const currentUrl = normalizeSideDockUri(currentTab.currentUrl);
|
|
153
198
|
return items.some((item) => {
|
|
154
|
-
const itemUri = normalizeSideDockUri(item
|
|
199
|
+
const itemUri = normalizeSideDockUri(getSideDockResourceUri(item) ?? undefined);
|
|
155
200
|
return itemUri === currentResourceUri || itemUri === currentUrl;
|
|
156
201
|
});
|
|
157
202
|
}
|
|
158
203
|
|
|
204
|
+
function isSideDockUtilityItem(item: SideDockItem): boolean {
|
|
205
|
+
return SIDE_DOCK_UTILITY_ITEM_IDS.has(item.id);
|
|
206
|
+
}
|
|
207
|
+
|
|
159
208
|
export function SideDock({ manager }: SideDockProps) {
|
|
160
209
|
const pinnedItems = useSideDockStore((state) => state.pinnedItems);
|
|
210
|
+
const setSideDockVisible = useSideDockStore((state) => state.setVisible);
|
|
161
211
|
const { currentTab, isOpen } = useDocBrowser();
|
|
212
|
+
const { confirm, ConfirmDialog } = useConfirmDialog();
|
|
162
213
|
const items = mergeSideDockItems(getSideDockBuiltInItems(), pinnedItems);
|
|
214
|
+
const mainItems = items.filter((item) => !isSideDockUtilityItem(item));
|
|
215
|
+
const utilityItems = items.filter(isSideDockUtilityItem);
|
|
163
216
|
const hasExactActiveItem = hasExactActiveSideDockItem(items, currentTab);
|
|
217
|
+
const handleHideSideDock = async (): Promise<void> => {
|
|
218
|
+
const confirmed = await confirm({
|
|
219
|
+
title: t('sideDockHideConfirmTitle'),
|
|
220
|
+
description: t('sideDockHideConfirmDescription'),
|
|
221
|
+
confirmLabel: t('sideDockHideConfirmAction'),
|
|
222
|
+
cancelLabel: t('cancel'),
|
|
223
|
+
});
|
|
224
|
+
if (confirmed) {
|
|
225
|
+
setSideDockVisible(false);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
164
228
|
|
|
165
229
|
return (
|
|
166
230
|
<TooltipProvider delayDuration={250}>
|
|
@@ -168,15 +232,38 @@ export function SideDock({ manager }: SideDockProps) {
|
|
|
168
232
|
data-testid="side-dock"
|
|
169
233
|
className="z-30 flex h-full w-14 shrink-0 flex-col items-center gap-1 border-l border-border/60 bg-background/95 px-2 py-3"
|
|
170
234
|
>
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
235
|
+
<div className="flex w-full flex-col items-center gap-1">
|
|
236
|
+
{mainItems.map((item) => (
|
|
237
|
+
<SideDockButton
|
|
238
|
+
key={item.id}
|
|
239
|
+
active={isSideDockItemActive(item, isOpen, currentTab, hasExactActiveItem)}
|
|
240
|
+
item={item}
|
|
241
|
+
onOpen={manager.openItem}
|
|
242
|
+
onUnpin={(dockItem) => manager.unpinItem(dockItem.id)}
|
|
243
|
+
/>
|
|
244
|
+
))}
|
|
245
|
+
</div>
|
|
246
|
+
{utilityItems.length > 0 ? (
|
|
247
|
+
<div data-testid="side-dock-utility" className="mt-auto flex w-full flex-col items-center gap-1">
|
|
248
|
+
{utilityItems.map((item) => (
|
|
249
|
+
<SideDockButton
|
|
250
|
+
key={item.id}
|
|
251
|
+
active={isSideDockItemActive(item, isOpen, currentTab, hasExactActiveItem)}
|
|
252
|
+
item={item}
|
|
253
|
+
onOpen={manager.openItem}
|
|
254
|
+
onUnpin={(dockItem) => manager.unpinItem(dockItem.id)}
|
|
255
|
+
/>
|
|
256
|
+
))}
|
|
257
|
+
<SideDockActionButton
|
|
258
|
+
icon={PanelRightClose}
|
|
259
|
+
label={t('sideDockHide')}
|
|
260
|
+
onClick={() => {
|
|
261
|
+
void handleHideSideDock();
|
|
262
|
+
}}
|
|
263
|
+
/>
|
|
264
|
+
</div>
|
|
265
|
+
) : null}
|
|
266
|
+
<ConfirmDialog />
|
|
180
267
|
</aside>
|
|
181
268
|
</TooltipProvider>
|
|
182
269
|
);
|
|
@@ -7,6 +7,8 @@ import type { SideDockItem } from '@/features/side-dock/types/side-dock.types';
|
|
|
7
7
|
import { t } from '@/shared/lib/i18n';
|
|
8
8
|
|
|
9
9
|
export const SIDE_DOCK_DOCS_URL = 'nextclaw://docs';
|
|
10
|
+
export const SIDE_DOCK_GITHUB_PROJECT_ITEM_ID = 'github-project';
|
|
11
|
+
export const SIDE_DOCK_GITHUB_PROJECT_URL = 'https://github.com/Peiiii/nextclaw';
|
|
10
12
|
|
|
11
13
|
export function getSideDockBuiltInItems(): SideDockItem[] {
|
|
12
14
|
return [
|
|
@@ -42,5 +44,13 @@ export function getSideDockBuiltInItems(): SideDockItem[] {
|
|
|
42
44
|
removable: false,
|
|
43
45
|
target: { type: 'right-panel-resource', uri: RIGHT_PANEL_HOME_URL },
|
|
44
46
|
},
|
|
47
|
+
{
|
|
48
|
+
builtIn: true,
|
|
49
|
+
icon: { type: 'builtin', name: 'github' },
|
|
50
|
+
id: SIDE_DOCK_GITHUB_PROJECT_ITEM_ID,
|
|
51
|
+
label: t('sideDockGitHubProject'),
|
|
52
|
+
removable: false,
|
|
53
|
+
target: { type: 'external-url', url: SIDE_DOCK_GITHUB_PROJECT_URL },
|
|
54
|
+
},
|
|
45
55
|
];
|
|
46
56
|
}
|