@chatcode/cco-better-sidebar 0.18.0-alpha.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/LICENSE +21 -0
- package/README.md +554 -0
- package/README_EN.md +563 -0
- package/assets/logo.svg +1 -0
- package/cordis.patch.yml +79 -0
- package/lib/client-editor.js +55744 -0
- package/lib/client-mermaid.js +208256 -0
- package/lib/client-registry.js +30378 -0
- package/lib/client-terminal.js +16894 -0
- package/lib/client.js +30378 -0
- package/lib/index.js +6897 -0
- package/lib/invariant.js +22 -0
- package/lib/types/agent-opens.d.ts +65 -0
- package/lib/types/agent-pty.d.ts +226 -0
- package/lib/types/browser-probe.d.ts +15 -0
- package/lib/types/bundle-route.d.ts +12 -0
- package/lib/types/chatcode-update.d.ts +47 -0
- package/lib/types/client/AccountMenu.d.ts +1 -0
- package/lib/types/client/AiRatioSection.d.ts +3 -0
- package/lib/types/client/BrowserView.d.ts +39 -0
- package/lib/types/client/ChatcodeGate.d.ts +6 -0
- package/lib/types/client/CodingPlanModelsSettings.d.ts +9 -0
- package/lib/types/client/DiffTab.d.ts +6 -0
- package/lib/types/client/DiffView.d.ts +51 -0
- package/lib/types/client/EditorHost.d.ts +13 -0
- package/lib/types/client/FenceErrorNotice.d.ts +5 -0
- package/lib/types/client/FileTree.d.ts +41 -0
- package/lib/types/client/FreeWindow.d.ts +29 -0
- package/lib/types/client/GitView.d.ts +12 -0
- package/lib/types/client/MarkdownHtml.d.ts +32 -0
- package/lib/types/client/OrphanedTab.d.ts +11 -0
- package/lib/types/client/PdfView.d.ts +6 -0
- package/lib/types/client/ReleaseNotesDialog.d.ts +11 -0
- package/lib/types/client/RenderBoundary.d.ts +32 -0
- package/lib/types/client/SandboxStatusBar.d.ts +10 -0
- package/lib/types/client/SideCardSection.d.ts +74 -0
- package/lib/types/client/Sidebar.d.ts +8 -0
- package/lib/types/client/TabBar.d.ts +54 -0
- package/lib/types/client/TerminalView.d.ts +24 -0
- package/lib/types/client/TextEditor.d.ts +10 -0
- package/lib/types/client/TreePanel.d.ts +27 -0
- package/lib/types/client/UpdateDialog.d.ts +10 -0
- package/lib/types/client/UpdateSettingsPanel.d.ts +1 -0
- package/lib/types/client/UpdateSettingsSection.d.ts +10 -0
- package/lib/types/client/UploadOverlay.d.ts +20 -0
- package/lib/types/client/UsageHeatmapSection.d.ts +4 -0
- package/lib/types/client/add-plugin-modal.d.ts +15 -0
- package/lib/types/client/api.d.ts +345 -0
- package/lib/types/client/binary-download.d.ts +12 -0
- package/lib/types/client/breakpoints.d.ts +17 -0
- package/lib/types/client/browser.d.ts +60 -0
- package/lib/types/client/builtins/index.d.ts +18 -0
- package/lib/types/client/builtins/tabs.d.ts +11 -0
- package/lib/types/client/builtins/viewers.d.ts +3 -0
- package/lib/types/client/chat-presentation.d.ts +13 -0
- package/lib/types/client/chunk-loader.d.ts +124 -0
- package/lib/types/client/chunks/editor.d.ts +9 -0
- package/lib/types/client/chunks/mermaid.d.ts +10 -0
- package/lib/types/client/chunks/terminal.d.ts +9 -0
- package/lib/types/client/cm-themes.d.ts +24 -0
- package/lib/types/client/conversation-draft.d.ts +14 -0
- package/lib/types/client/desktop-env.d.ts +35 -0
- package/lib/types/client/editor-load.d.ts +66 -0
- package/lib/types/client/frame-batcher.d.ts +24 -0
- package/lib/types/client/icons.d.ts +66 -0
- package/lib/types/client/image-types.d.ts +3 -0
- package/lib/types/client/ime-guard.d.ts +36 -0
- package/lib/types/client/index.d.ts +19 -0
- package/lib/types/client/intercept.d.ts +44 -0
- package/lib/types/client/lang.d.ts +14 -0
- package/lib/types/client/layout-push.d.ts +15 -0
- package/lib/types/client/lazy-chunk.d.ts +25 -0
- package/lib/types/client/link-intercept.d.ts +42 -0
- package/lib/types/client/locales-ar.d.ts +18 -0
- package/lib/types/client/locales-de.d.ts +4 -0
- package/lib/types/client/locales-fr.d.ts +10 -0
- package/lib/types/client/locales-hi.d.ts +17 -0
- package/lib/types/client/locales-id.d.ts +15 -0
- package/lib/types/client/locales-it.d.ts +8 -0
- package/lib/types/client/locales-ja.d.ts +17 -0
- package/lib/types/client/locales-ko.d.ts +9 -0
- package/lib/types/client/locales-nl.d.ts +16 -0
- package/lib/types/client/locales-pl.d.ts +19 -0
- package/lib/types/client/locales-pt.d.ts +1 -0
- package/lib/types/client/locales-ru.d.ts +13 -0
- package/lib/types/client/locales-sv.d.ts +1 -0
- package/lib/types/client/locales-th.d.ts +17 -0
- package/lib/types/client/locales-tr.d.ts +17 -0
- package/lib/types/client/locales-vi.d.ts +17 -0
- package/lib/types/client/locales-zh-HK.d.ts +32 -0
- package/lib/types/client/locales-zh-MO.d.ts +32 -0
- package/lib/types/client/locales-zh-TW.d.ts +32 -0
- package/lib/types/client/locales.d.ts +571 -0
- package/lib/types/client/markdown-html.d.ts +96 -0
- package/lib/types/client/markdown-images.d.ts +45 -0
- package/lib/types/client/markdown-labels.d.ts +21 -0
- package/lib/types/client/md-toc.d.ts +4 -0
- package/lib/types/client/mermaid-blocks.d.ts +47 -0
- package/lib/types/client/mermaid-sanitize.d.ts +2 -0
- package/lib/types/client/mermaid.d.ts +12 -0
- package/lib/types/client/open-when-sized.d.ts +20 -0
- package/lib/types/client/open-with-settings.d.ts +4 -0
- package/lib/types/client/open-with.d.ts +87 -0
- package/lib/types/client/openpath-intercept.d.ts +52 -0
- package/lib/types/client/paths.d.ts +43 -0
- package/lib/types/client/pdf-types.d.ts +2 -0
- package/lib/types/client/pinned.d.ts +86 -0
- package/lib/types/client/plugin-settings.d.ts +8 -0
- package/lib/types/client/plugins-shared.d.ts +27 -0
- package/lib/types/client/plugins-tabs.d.ts +3 -0
- package/lib/types/client/plugins-viewers.d.ts +3 -0
- package/lib/types/client/prefs.d.ts +41 -0
- package/lib/types/client/produced-files.d.ts +29 -0
- package/lib/types/client/selection-payload.d.ts +27 -0
- package/lib/types/client/service.d.ts +445 -0
- package/lib/types/client/session-pins.d.ts +14 -0
- package/lib/types/client/settings-nav-icon.d.ts +27 -0
- package/lib/types/client/shell-presets.d.ts +48 -0
- package/lib/types/client/split-pane.d.ts +38 -0
- package/lib/types/client/state.d.ts +480 -0
- package/lib/types/client/tab-content-memo.d.ts +40 -0
- package/lib/types/client/terminal-font.d.ts +78 -0
- package/lib/types/client/terminal-links.d.ts +119 -0
- package/lib/types/client/theme.d.ts +45 -0
- package/lib/types/client/titlebar-strip.d.ts +23 -0
- package/lib/types/client/ui-tweaks/appicons.d.ts +16 -0
- package/lib/types/client/ui-tweaks/archive.d.ts +42 -0
- package/lib/types/client/ui-tweaks/branding.d.ts +45 -0
- package/lib/types/client/ui-tweaks/cachehit.d.ts +41 -0
- package/lib/types/client/ui-tweaks/gitbar.d.ts +103 -0
- package/lib/types/client/ui-tweaks/index.d.ts +279 -0
- package/lib/types/client/ui-tweaks/mcp.d.ts +28 -0
- package/lib/types/client/ui-tweaks/notifier.d.ts +125 -0
- package/lib/types/client/ui-tweaks/whale.d.ts +55 -0
- package/lib/types/client/upload.d.ts +69 -0
- package/lib/types/client/useChatcodeAccount.d.ts +10 -0
- package/lib/types/client/wco.d.ts +47 -0
- package/lib/types/codingplan.d.ts +37 -0
- package/lib/types/config.d.ts +95 -0
- package/lib/types/context-types.d.ts +540 -0
- package/lib/types/fs-operations.d.ts +30 -0
- package/lib/types/fs-search.d.ts +24 -0
- package/lib/types/fs-tree.d.ts +51 -0
- package/lib/types/git.d.ts +124 -0
- package/lib/types/html-route.d.ts +59 -0
- package/lib/types/index.d.ts +47 -0
- package/lib/types/invariant.d.ts +15 -0
- package/lib/types/login-route.d.ts +2 -0
- package/lib/types/open-external.d.ts +24 -0
- package/lib/types/path-security.d.ts +26 -0
- package/lib/types/prefs-shared.d.ts +257 -0
- package/lib/types/pty-deps.d.ts +79 -0
- package/lib/types/pty-manager.d.ts +168 -0
- package/lib/types/tools.d.ts +34 -0
- package/lib/types/trust-fence.d.ts +24 -0
- package/lib/types/ui-tweaks/archive.d.ts +71 -0
- package/lib/types/ui-tweaks/config.d.ts +186 -0
- package/lib/types/ui-tweaks/git-web.d.ts +43 -0
- package/lib/types/ui-tweaks/git.d.ts +311 -0
- package/lib/types/ui-tweaks/index.d.ts +17 -0
- package/lib/types/ui-tweaks/mcp.d.ts +103 -0
- package/lib/types/ui-tweaks/turn-outcome.d.ts +57 -0
- package/lib/types/ui-tweaks/web.d.ts +55 -0
- package/lib/types/wire.d.ts +38 -0
- package/package.json +213 -0
- package/scripts/install.ps1 +310 -0
- package/scripts/install.sh +287 -0
- package/src/agent-opens.ts +265 -0
- package/src/agent-pty.ts +527 -0
- package/src/browser-probe.ts +26 -0
- package/src/bundle-route.ts +129 -0
- package/src/chatcode-update.ts +193 -0
- package/src/client/AccountMenu.tsx +177 -0
- package/src/client/AiRatioSection.module.css +329 -0
- package/src/client/AiRatioSection.tsx +292 -0
- package/src/client/BrowserView.tsx +289 -0
- package/src/client/ChatcodeGate.tsx +35 -0
- package/src/client/CodingPlanModelsSettings.module.css +118 -0
- package/src/client/CodingPlanModelsSettings.tsx +101 -0
- package/src/client/DiffTab.tsx +114 -0
- package/src/client/DiffView.tsx +305 -0
- package/src/client/EditorHost.tsx +563 -0
- package/src/client/FenceErrorNotice.tsx +40 -0
- package/src/client/FileTree.tsx +701 -0
- package/src/client/FreeWindow.tsx +337 -0
- package/src/client/GitView.tsx +722 -0
- package/src/client/MarkdownHtml.tsx +296 -0
- package/src/client/OrphanedTab.tsx +27 -0
- package/src/client/PdfView.tsx +110 -0
- package/src/client/ReleaseNotesDialog.tsx +91 -0
- package/src/client/RenderBoundary.tsx +49 -0
- package/src/client/SandboxStatusBar.tsx +60 -0
- package/src/client/SideCardSection.module.css +932 -0
- package/src/client/SideCardSection.tsx +1169 -0
- package/src/client/Sidebar.tsx +1857 -0
- package/src/client/TabBar.tsx +365 -0
- package/src/client/TerminalView.tsx +424 -0
- package/src/client/TextEditor.tsx +468 -0
- package/src/client/TreePanel.tsx +279 -0
- package/src/client/UpdateDialog.tsx +186 -0
- package/src/client/UpdateSettingsPanel.tsx +29 -0
- package/src/client/UpdateSettingsSection.tsx +22 -0
- package/src/client/UploadOverlay.tsx +62 -0
- package/src/client/UsageHeatmapSection.module.css +440 -0
- package/src/client/UsageHeatmapSection.tsx +301 -0
- package/src/client/add-plugin-modal.tsx +199 -0
- package/src/client/api.ts +433 -0
- package/src/client/binary-download.tsx +23 -0
- package/src/client/breakpoints.ts +61 -0
- package/src/client/browser.ts +167 -0
- package/src/client/builtins/index.ts +37 -0
- package/src/client/builtins/tabs.tsx +275 -0
- package/src/client/builtins/viewers.tsx +125 -0
- package/src/client/chat-presentation.ts +95 -0
- package/src/client/chunk-loader.ts +350 -0
- package/src/client/chunks/editor.tsx +9 -0
- package/src/client/chunks/mermaid.tsx +10 -0
- package/src/client/chunks/terminal.tsx +9 -0
- package/src/client/cm-themes.ts +129 -0
- package/src/client/conversation-draft.ts +29 -0
- package/src/client/css-modules.d.ts +5 -0
- package/src/client/desktop-env.ts +72 -0
- package/src/client/editor-load.ts +92 -0
- package/src/client/frame-batcher.ts +56 -0
- package/src/client/icons.tsx +170 -0
- package/src/client/image-types.ts +8 -0
- package/src/client/ime-guard.ts +47 -0
- package/src/client/index.tsx +501 -0
- package/src/client/intercept.tsx +172 -0
- package/src/client/lang.ts +225 -0
- package/src/client/layout-push.ts +37 -0
- package/src/client/layout.css +156 -0
- package/src/client/lazy-chunk.tsx +89 -0
- package/src/client/link-intercept.ts +73 -0
- package/src/client/locales-ar.ts +387 -0
- package/src/client/locales-de.ts +372 -0
- package/src/client/locales-fr.ts +379 -0
- package/src/client/locales-hi.ts +386 -0
- package/src/client/locales-id.ts +384 -0
- package/src/client/locales-it.ts +377 -0
- package/src/client/locales-ja.ts +486 -0
- package/src/client/locales-ko.ts +378 -0
- package/src/client/locales-nl.ts +384 -0
- package/src/client/locales-pl.ts +388 -0
- package/src/client/locales-pt.ts +369 -0
- package/src/client/locales-ru.ts +384 -0
- package/src/client/locales-sv.ts +369 -0
- package/src/client/locales-th.ts +386 -0
- package/src/client/locales-tr.ts +386 -0
- package/src/client/locales-vi.ts +386 -0
- package/src/client/locales-zh-HK.ts +401 -0
- package/src/client/locales-zh-MO.ts +401 -0
- package/src/client/locales-zh-TW.ts +401 -0
- package/src/client/locales.ts +1229 -0
- package/src/client/markdown-html.ts +331 -0
- package/src/client/markdown-images.ts +137 -0
- package/src/client/markdown-labels.tsx +31 -0
- package/src/client/md-toc.tsx +127 -0
- package/src/client/mermaid-blocks.ts +110 -0
- package/src/client/mermaid-sanitize.ts +93 -0
- package/src/client/mermaid.tsx +406 -0
- package/src/client/open-when-sized.ts +43 -0
- package/src/client/open-with-settings.tsx +135 -0
- package/src/client/open-with.ts +221 -0
- package/src/client/openpath-intercept.ts +79 -0
- package/src/client/paths.ts +63 -0
- package/src/client/pdf-types.ts +4 -0
- package/src/client/pinned.ts +184 -0
- package/src/client/plugin-settings.ts +43 -0
- package/src/client/plugins-shared.ts +29 -0
- package/src/client/plugins-tabs.ts +153 -0
- package/src/client/plugins-viewers.ts +44 -0
- package/src/client/prefs.ts +216 -0
- package/src/client/produced-files.ts +120 -0
- package/src/client/selection-payload.ts +86 -0
- package/src/client/service.ts +873 -0
- package/src/client/session-pins.tsx +334 -0
- package/src/client/settings-nav-icon.ts +55 -0
- package/src/client/shell-presets.ts +83 -0
- package/src/client/sidebar.module.css +3607 -0
- package/src/client/split-pane.tsx +333 -0
- package/src/client/state.ts +1646 -0
- package/src/client/tab-content-memo.ts +55 -0
- package/src/client/terminal-font.ts +224 -0
- package/src/client/terminal-links.ts +194 -0
- package/src/client/theme.ts +110 -0
- package/src/client/titlebar-strip.ts +37 -0
- package/src/client/ui-tweaks/appicons.ts +24 -0
- package/src/client/ui-tweaks/archive.tsx +281 -0
- package/src/client/ui-tweaks/branding.tsx +232 -0
- package/src/client/ui-tweaks/cachehit.tsx +122 -0
- package/src/client/ui-tweaks/gitbar.tsx +2559 -0
- package/src/client/ui-tweaks/index.tsx +1696 -0
- package/src/client/ui-tweaks/mcp.tsx +582 -0
- package/src/client/ui-tweaks/notifier.ts +514 -0
- package/src/client/ui-tweaks/whale.tsx +171 -0
- package/src/client/upload.ts +187 -0
- package/src/client/useChatcodeAccount.ts +103 -0
- package/src/client/wco.ts +120 -0
- package/src/codingplan.ts +146 -0
- package/src/config.ts +207 -0
- package/src/context-types.ts +559 -0
- package/src/fs-operations.ts +97 -0
- package/src/fs-search.ts +113 -0
- package/src/fs-tree.ts +158 -0
- package/src/git.ts +446 -0
- package/src/html-route.ts +106 -0
- package/src/index.ts +1524 -0
- package/src/invariant.ts +32 -0
- package/src/login-route.ts +26 -0
- package/src/open-external.ts +90 -0
- package/src/path-security.ts +81 -0
- package/src/prefs-shared.ts +303 -0
- package/src/pty-deps.ts +241 -0
- package/src/pty-manager.ts +376 -0
- package/src/tools.ts +482 -0
- package/src/trust-fence.ts +84 -0
- package/src/ui-tweaks/archive.ts +293 -0
- package/src/ui-tweaks/config.ts +257 -0
- package/src/ui-tweaks/git-web.ts +421 -0
- package/src/ui-tweaks/git.ts +1524 -0
- package/src/ui-tweaks/index.ts +61 -0
- package/src/ui-tweaks/mcp.ts +535 -0
- package/src/ui-tweaks/turn-outcome.ts +105 -0
- package/src/ui-tweaks/web.ts +225 -0
- package/src/wire.ts +110 -0
- package/vendor/addon-fit.js +2 -0
- package/vendor/xterm.css +218 -0
- package/vendor/xterm.js +5 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { useEffect, useId, useMemo, useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
IconChevronLeftOutline14,
|
|
4
|
+
IconChevronRightOutline14,
|
|
5
|
+
IconLoadingOutline16,
|
|
6
|
+
IconRefreshOutline14,
|
|
7
|
+
} from '@deepseek-ai/dsh-client-ui-primitives'
|
|
8
|
+
import clsx from 'clsx'
|
|
9
|
+
// Type-only: pulls the settings shell's SlotMap merges ('settings.section').
|
|
10
|
+
import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
11
|
+
import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
|
12
|
+
import { api, SidebarApiError, type ChatCodeTokenDay } from './api.ts'
|
|
13
|
+
import { isZh, t } from './locales.ts'
|
|
14
|
+
import css from './UsageHeatmapSection.module.css'
|
|
15
|
+
|
|
16
|
+
/** Full section props: the settings shell's runtime share (close, …). */
|
|
17
|
+
export type UsageHeatmapSectionProps = PropsRuntime<'settings.section'>
|
|
18
|
+
|
|
19
|
+
/** One rendered day cell. */
|
|
20
|
+
interface DayCell {
|
|
21
|
+
day: number
|
|
22
|
+
key: string
|
|
23
|
+
today: boolean
|
|
24
|
+
future: boolean
|
|
25
|
+
tokens: number
|
|
26
|
+
tokensIn: number
|
|
27
|
+
tokensOut: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** One month query's outcome. */
|
|
31
|
+
type LoadState =
|
|
32
|
+
| { kind: 'loading' }
|
|
33
|
+
| { kind: 'ready'; days: Map<string, ChatCodeTokenDay> }
|
|
34
|
+
| { kind: 'signin' }
|
|
35
|
+
| { kind: 'error'; message: string }
|
|
36
|
+
|
|
37
|
+
const startOfMonth = (date: Date): Date => new Date(date.getFullYear(), date.getMonth(), 1)
|
|
38
|
+
|
|
39
|
+
const dayKey = (year: number, month: number, day: number): string =>
|
|
40
|
+
`${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`
|
|
41
|
+
|
|
42
|
+
/** Coerce one platform number defensively (absent/negative/garbage → 0). */
|
|
43
|
+
const num = (value: unknown): number => {
|
|
44
|
+
const parsed = typeof value === 'number' ? value : Number(value)
|
|
45
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Compact token count (40477872 → “4048万” / “40M”). */
|
|
49
|
+
const compactTokens = (value: number): string =>
|
|
50
|
+
new Intl.NumberFormat(isZh() ? 'zh-CN' : 'en-US', { notation: 'compact', maximumFractionDigits: 1 }).format(value)
|
|
51
|
+
|
|
52
|
+
/** Full token count with separators (tooltip and a11y text). */
|
|
53
|
+
const fullTokens = (value: number): string => new Intl.NumberFormat().format(Math.round(value))
|
|
54
|
+
|
|
55
|
+
/** “2026年9月” / “September 2026”. */
|
|
56
|
+
const monthLabelOf = (anchor: Date): string =>
|
|
57
|
+
new Intl.DateTimeFormat(isZh() ? 'zh-CN' : 'en-US', { year: 'numeric', month: 'long' }).format(anchor)
|
|
58
|
+
|
|
59
|
+
/** “9月14日” / “Sep 14”. */
|
|
60
|
+
const shortDateOf = (anchor: Date, day: number): string =>
|
|
61
|
+
new Intl.DateTimeFormat(isZh() ? 'zh-CN' : 'en-US', { month: 'short', day: 'numeric' }).format(new Date(anchor.getFullYear(), anchor.getMonth(), day))
|
|
62
|
+
|
|
63
|
+
export function UsageHeatmapSection(_props: UsageHeatmapSectionProps) {
|
|
64
|
+
const [anchor, setAnchor] = useState(() => startOfMonth(new Date()))
|
|
65
|
+
const [load, setLoad] = useState<LoadState>({ kind: 'loading' })
|
|
66
|
+
const [reloadKey, setReloadKey] = useState(0)
|
|
67
|
+
const [hover, setHover] = useState<{ cell: DayCell; left: number; top: number; below: boolean } | null>(null)
|
|
68
|
+
const tooltipId = useId()
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
const dismiss = (): void => { setHover(null) }
|
|
72
|
+
window.addEventListener('scroll', dismiss, true)
|
|
73
|
+
window.addEventListener('resize', dismiss)
|
|
74
|
+
return () => {
|
|
75
|
+
window.removeEventListener('scroll', dismiss, true)
|
|
76
|
+
window.removeEventListener('resize', dismiss)
|
|
77
|
+
}
|
|
78
|
+
}, [])
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
const controller = new AbortController()
|
|
82
|
+
setLoad({ kind: 'loading' })
|
|
83
|
+
setHover(null)
|
|
84
|
+
const year = anchor.getFullYear()
|
|
85
|
+
const month = anchor.getMonth()
|
|
86
|
+
const lastDay = new Date(year, month + 1, 0).getDate()
|
|
87
|
+
api.chatcodeTokenTrend(dayKey(year, month, 1), dayKey(year, month, lastDay), controller.signal)
|
|
88
|
+
.then(rows => {
|
|
89
|
+
const days = new Map<string, ChatCodeTokenDay>()
|
|
90
|
+
for (const row of rows) days.set(String(row.statDate), row)
|
|
91
|
+
setLoad({ kind: 'ready', days })
|
|
92
|
+
})
|
|
93
|
+
.catch((error: unknown) => {
|
|
94
|
+
if (controller.signal.aborted) return
|
|
95
|
+
const code = error instanceof SidebarApiError ? error.code : ''
|
|
96
|
+
if (code === 'chatcode-unavailable' || code === 'chatcode-unauthorized') {
|
|
97
|
+
setLoad({ kind: 'signin' })
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
setLoad({ kind: 'error', message: error instanceof Error ? error.message : String(error) })
|
|
101
|
+
})
|
|
102
|
+
return () => { controller.abort() }
|
|
103
|
+
}, [anchor, reloadKey])
|
|
104
|
+
|
|
105
|
+
const cells = useMemo<DayCell[]>(() => {
|
|
106
|
+
const year = anchor.getFullYear()
|
|
107
|
+
const month = anchor.getMonth()
|
|
108
|
+
const daysInMonth = new Date(year, month + 1, 0).getDate()
|
|
109
|
+
const today = new Date()
|
|
110
|
+
const sameMonth = year === today.getFullYear() && month === today.getMonth()
|
|
111
|
+
const futureMonth = year > today.getFullYear() || (year === today.getFullYear() && month > today.getMonth())
|
|
112
|
+
const days = load.kind === 'ready' ? load.days : null
|
|
113
|
+
const list: DayCell[] = []
|
|
114
|
+
for (let day = 1; day <= daysInMonth; day++) {
|
|
115
|
+
const row = days?.get(dayKey(year, month, day))
|
|
116
|
+
list.push({
|
|
117
|
+
day,
|
|
118
|
+
key: dayKey(year, month, day),
|
|
119
|
+
today: sameMonth && day === today.getDate(),
|
|
120
|
+
future: futureMonth || (sameMonth && day > today.getDate()),
|
|
121
|
+
tokens: num(row?.totalTokens),
|
|
122
|
+
tokensIn: num(row?.tokensIn),
|
|
123
|
+
tokensOut: num(row?.tokensOut),
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
return list
|
|
127
|
+
}, [anchor, load])
|
|
128
|
+
|
|
129
|
+
const stats = useMemo(() => {
|
|
130
|
+
let total = 0
|
|
131
|
+
let peak = 0
|
|
132
|
+
let peakDay = 0
|
|
133
|
+
let active = 0
|
|
134
|
+
for (const cell of cells) {
|
|
135
|
+
if (cell.future) continue
|
|
136
|
+
total += cell.tokens
|
|
137
|
+
if (cell.tokens > peak) {
|
|
138
|
+
peak = cell.tokens
|
|
139
|
+
peakDay = cell.day
|
|
140
|
+
}
|
|
141
|
+
if (cell.tokens > 0) active++
|
|
142
|
+
}
|
|
143
|
+
return { total, peak, peakDay, active }
|
|
144
|
+
}, [cells])
|
|
145
|
+
|
|
146
|
+
const leadingBlanks = (new Date(anchor.getFullYear(), anchor.getMonth(), 1).getDay() + 6) % 7
|
|
147
|
+
const weekdays = [...t('usageWeekdays')]
|
|
148
|
+
const nextMonthDisabled = anchor.getTime() >= startOfMonth(new Date()).getTime()
|
|
149
|
+
const reload = (): void => { setReloadKey(key => key + 1) }
|
|
150
|
+
const goPrev = (): void => { setAnchor(current => new Date(current.getFullYear(), current.getMonth() - 1, 1)) }
|
|
151
|
+
const goNext = (): void => { setAnchor(current => new Date(current.getFullYear(), current.getMonth() + 1, 1)) }
|
|
152
|
+
|
|
153
|
+
const heatLevel = (cell: DayCell): number =>
|
|
154
|
+
cell.future || cell.tokens === 0 || stats.peak === 0 ? 0 : Math.ceil(cell.tokens / stats.peak * 4)
|
|
155
|
+
|
|
156
|
+
const showDetails = (cell: DayCell, target: HTMLElement): void => {
|
|
157
|
+
if (cell.future) return
|
|
158
|
+
const rect = target.getBoundingClientRect()
|
|
159
|
+
const below = rect.top < 160
|
|
160
|
+
setHover({ cell, left: rect.left + rect.width / 2, top: below ? rect.bottom : rect.top, below })
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div className={css.section}>
|
|
165
|
+
<div className={css.group}>
|
|
166
|
+
<div className={css.headerRow}>
|
|
167
|
+
<div className={css.monthNav}>
|
|
168
|
+
<button type="button" className={css.monthButton} aria-label={t('usagePrevMonth')} onClick={goPrev}>
|
|
169
|
+
<IconChevronLeftOutline14 size={14} />
|
|
170
|
+
</button>
|
|
171
|
+
<span className={css.monthLabel} aria-live="polite">{monthLabelOf(anchor)}</span>
|
|
172
|
+
<button
|
|
173
|
+
type="button"
|
|
174
|
+
className={css.monthButton}
|
|
175
|
+
aria-label={t('usageNextMonth')}
|
|
176
|
+
onClick={goNext}
|
|
177
|
+
disabled={nextMonthDisabled}
|
|
178
|
+
>
|
|
179
|
+
<IconChevronRightOutline14 size={14} />
|
|
180
|
+
</button>
|
|
181
|
+
</div>
|
|
182
|
+
<button type="button" className={css.refreshButton} onClick={reload} disabled={load.kind === 'loading'}>
|
|
183
|
+
<IconRefreshOutline14 size={14} className={load.kind === 'loading' ? css.spin : undefined} />
|
|
184
|
+
<span>{t('usageRefresh')}</span>
|
|
185
|
+
</button>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
{load.kind === 'ready' ? (
|
|
189
|
+
<>
|
|
190
|
+
<div className={css.overview}>
|
|
191
|
+
<div className={css.total}>
|
|
192
|
+
<span className={css.statLabel}>{t('usageStatTotal')}</span>
|
|
193
|
+
<div className={css.totalNumber} title={fullTokens(stats.total)}>
|
|
194
|
+
<span className={css.totalValue}>{compactTokens(stats.total)}</span>
|
|
195
|
+
<span className={css.totalUnit}>Tokens</span>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
<div className={css.stats}>
|
|
199
|
+
<div className={css.stat}>
|
|
200
|
+
<span className={css.statLabel}>{t('usageStatPeak')}</span>
|
|
201
|
+
<span className={css.statValue} title={stats.peakDay > 0 ? shortDateOf(anchor, stats.peakDay) : undefined}>{compactTokens(stats.peak)}</span>
|
|
202
|
+
</div>
|
|
203
|
+
<div className={css.stat}>
|
|
204
|
+
<span className={css.statLabel}>{t('usageStatActive')}</span>
|
|
205
|
+
<span className={css.statValue}>{stats.active}</span>
|
|
206
|
+
</div>
|
|
207
|
+
<div className={css.stat}>
|
|
208
|
+
<span className={css.statLabel}>{t('usageStatAvg')}</span>
|
|
209
|
+
<span className={css.statValue}>{compactTokens(stats.active > 0 ? stats.total / stats.active : 0)}</span>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<div className={css.gridBody}>
|
|
215
|
+
<div className={css.weekdays} aria-hidden="true">
|
|
216
|
+
{weekdays.map((weekday, index) => <span key={index}>{weekday}</span>)}
|
|
217
|
+
</div>
|
|
218
|
+
<div className={css.grid} role="group" aria-label={monthLabelOf(anchor)}>
|
|
219
|
+
{Array.from({ length: leadingBlanks }, (_, index) => <span key={`blank-${index}`} aria-hidden="true" />)}
|
|
220
|
+
{cells.map(cell => (
|
|
221
|
+
<button
|
|
222
|
+
type="button"
|
|
223
|
+
key={cell.key}
|
|
224
|
+
className={clsx(css.cell, cell.future && css.cellFuture, cell.today && css.cellToday, hover?.cell === cell && css.cellHover)}
|
|
225
|
+
data-level={heatLevel(cell)}
|
|
226
|
+
disabled={cell.future}
|
|
227
|
+
aria-current={cell.today ? 'date' : undefined}
|
|
228
|
+
aria-describedby={hover?.cell === cell ? tooltipId : undefined}
|
|
229
|
+
aria-label={cell.future
|
|
230
|
+
? shortDateOf(anchor, cell.day)
|
|
231
|
+
: `${shortDateOf(anchor, cell.day)} · ${t('usageDayTokens', { tokens: fullTokens(cell.tokens) })} · ${t('usageTokensIn', { tokens: fullTokens(cell.tokensIn) })} · ${t('usageTokensOut', { tokens: fullTokens(cell.tokensOut) })}`}
|
|
232
|
+
onMouseEnter={event => { showDetails(cell, event.currentTarget) }}
|
|
233
|
+
onMouseLeave={() => { setHover(null) }}
|
|
234
|
+
onFocus={event => { showDetails(cell, event.currentTarget) }}
|
|
235
|
+
onBlur={() => { setHover(null) }}
|
|
236
|
+
onClick={event => { showDetails(cell, event.currentTarget) }}
|
|
237
|
+
onKeyDown={event => {
|
|
238
|
+
if (event.key === 'Escape' && hover !== null) {
|
|
239
|
+
event.stopPropagation()
|
|
240
|
+
setHover(null)
|
|
241
|
+
}
|
|
242
|
+
}}
|
|
243
|
+
>
|
|
244
|
+
<span className={css.cellDay}>{cell.day}</span>
|
|
245
|
+
<span className={css.cellTokens} aria-hidden="true">{!cell.future && cell.tokens > 0 ? compactTokens(cell.tokens) : '\u00a0'}</span>
|
|
246
|
+
</button>
|
|
247
|
+
))}
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
<div className={css.footer}>
|
|
252
|
+
{cells.some(cell => cell.today) && <span className={css.todayLabel}><span className={css.todayDot} />{t('usageToday')}</span>}
|
|
253
|
+
<div className={css.legend} aria-hidden="true">
|
|
254
|
+
<span>{t('usageLess')}</span>
|
|
255
|
+
{[0, 1, 2, 3, 4].map(level => (
|
|
256
|
+
<span key={level} className={css.legendSwatch} data-level={level} />
|
|
257
|
+
))}
|
|
258
|
+
<span>{t('usageMore')}</span>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
{stats.total === 0 && <div className={css.empty}>{t('usageEmpty')}</div>}
|
|
263
|
+
</>
|
|
264
|
+
) : load.kind === 'loading' ? (
|
|
265
|
+
<div className={css.state} role="status">
|
|
266
|
+
<IconLoadingOutline16 className={css.spin} size={16} />
|
|
267
|
+
<span>{t('usageLoading')}</span>
|
|
268
|
+
</div>
|
|
269
|
+
) : load.kind === 'signin' ? (
|
|
270
|
+
<div className={css.state}>{t('usageLoginRequired')}</div>
|
|
271
|
+
) : (
|
|
272
|
+
<div className={css.stateError}>
|
|
273
|
+
<span>{load.message === '' ? t('usageError') : `${t('usageError')}:${load.message}`}</span>
|
|
274
|
+
<button type="button" className={css.retry} onClick={reload}>
|
|
275
|
+
{t('usageRetry')}
|
|
276
|
+
</button>
|
|
277
|
+
</div>
|
|
278
|
+
)}
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
{hover !== null && !hover.cell.future && (
|
|
282
|
+
<div
|
|
283
|
+
id={tooltipId}
|
|
284
|
+
className={clsx(css.tooltip, hover.below && css.tooltipBelow)}
|
|
285
|
+
role="tooltip"
|
|
286
|
+
style={{
|
|
287
|
+
left: Math.min(Math.max(hover.left, 124), window.innerWidth - 124),
|
|
288
|
+
top: hover.top,
|
|
289
|
+
}}
|
|
290
|
+
>
|
|
291
|
+
<span className={css.tooltipDate}>{shortDateOf(anchor, hover.cell.day)}{hover.cell.today && <span className={css.tooltipToday}>{t('usageToday')}</span>}</span>
|
|
292
|
+
<span className={css.tooltipTokens}>{t('usageDayTokens', { tokens: fullTokens(hover.cell.tokens) })}</span>
|
|
293
|
+
<div className={css.tooltipSplit}>
|
|
294
|
+
<span>{t('usageTokensIn', { tokens: fullTokens(hover.cell.tokensIn) })}</span>
|
|
295
|
+
<span>{t('usageTokensOut', { tokens: fullTokens(hover.cell.tokensOut) })}</span>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
)}
|
|
299
|
+
</div>
|
|
300
|
+
)
|
|
301
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "add plugin" modals (Side card settings → the dashed cards at the
|
|
3
|
+
* end of the 侧边栏内容 / 文件预览 grids): declare that the sidebar's
|
|
4
|
+
* extension points — tab pages and file previewers — are open to plugins
|
|
5
|
+
* (registered through `ctx.betterSidebar`), point at the GitHub topic page
|
|
6
|
+
* for discovery, and show the repo's recommended plugin catalog of the
|
|
7
|
+
* matching kind (name / url / description / install script).
|
|
8
|
+
*
|
|
9
|
+
* Per entry there are two actions:
|
|
10
|
+
* - 「跳转」opens the plugin's repo in a REAL new browser tab (window.open
|
|
11
|
+
* — a button, so the sidebar link takeover cannot reroute it);
|
|
12
|
+
* - 「安装」only COPIES the install script to the clipboard (writeClipboard)
|
|
13
|
+
* with a transient "已复制" feedback on the button — the user pastes and
|
|
14
|
+
* runs it wherever they manage their DSH profile. No terminal is opened,
|
|
15
|
+
* nothing is closed, nothing can fail outward.
|
|
16
|
+
*
|
|
17
|
+
* The body is extracted as {@link PluginListBody} so tests render it
|
|
18
|
+
* directly — the Modal primitive runs hooks unconditionally, so an open
|
|
19
|
+
* Modal must never be renderToString'd (same rule as the settingsFor popup
|
|
20
|
+
* in SideCardSection); the modal itself mounts only while open.
|
|
21
|
+
*/
|
|
22
|
+
import { useState, type ReactNode } from 'react'
|
|
23
|
+
import { Modal, writeClipboard } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
24
|
+
import type { BetterSidebarService } from './service.ts'
|
|
25
|
+
import { PLUGIN_TOPIC_URL, type PluginEntry } from './plugins-shared.ts'
|
|
26
|
+
import { builtinTabPlugins } from './plugins-tabs.ts'
|
|
27
|
+
import { builtinViewerPlugins } from './plugins-viewers.ts'
|
|
28
|
+
import { t } from './locales.ts'
|
|
29
|
+
import css from './SideCardSection.module.css'
|
|
30
|
+
|
|
31
|
+
/** Which extension point the modal is adding a plugin for. */
|
|
32
|
+
export type PluginKind = 'tab' | 'viewer'
|
|
33
|
+
|
|
34
|
+
/** The catalog of one kind (kept in two repo files: plugins-tabs.ts /
|
|
35
|
+
* plugins-viewers.ts). */
|
|
36
|
+
function catalogOf(kind: PluginKind): readonly PluginEntry[] {
|
|
37
|
+
return kind === 'tab' ? builtinTabPlugins : builtinViewerPlugins
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** How long the "已复制" feedback stays on the copy button. */
|
|
41
|
+
const COPIED_FEEDBACK_MS = 1500
|
|
42
|
+
|
|
43
|
+
/** The modal body: the GitHub topic button + the recommended plugin list
|
|
44
|
+
* with per-entry jump/copy buttons (extracted for direct testing). */
|
|
45
|
+
export function PluginListBody(props: { service: BetterSidebarService; kind: PluginKind }) {
|
|
46
|
+
const { service, kind } = props
|
|
47
|
+
// Which entry's copy button currently shows the "已复制" feedback.
|
|
48
|
+
const [copiedId, setCopiedId] = useState<string | null>(null)
|
|
49
|
+
// Live catalog filter (name / id / description). A free-text search keeps
|
|
50
|
+
// the list usable once the catalogs grow beyond a handful of entries:
|
|
51
|
+
// every keystroke narrows the rendered rows, so a long catalog never
|
|
52
|
+
// becomes a wall of scrolling.
|
|
53
|
+
const [query, setQuery] = useState('')
|
|
54
|
+
|
|
55
|
+
const catalog = catalogOf(kind)
|
|
56
|
+
const needle = query.trim().toLowerCase()
|
|
57
|
+
const matches = (entry: PluginEntry): boolean => {
|
|
58
|
+
if (needle === '') return true
|
|
59
|
+
const description = typeof entry.description === 'function' ? entry.description() : entry.description
|
|
60
|
+
return entry.name.toLowerCase().includes(needle)
|
|
61
|
+
|| entry.id.toLowerCase().includes(needle)
|
|
62
|
+
|| description.toLowerCase().includes(needle)
|
|
63
|
+
}
|
|
64
|
+
const filtered = catalog.filter(matches)
|
|
65
|
+
// Group by the optional category (stable order: category order of first
|
|
66
|
+
// appearance, uncategorized entries last under the plain list).
|
|
67
|
+
const groups = new Map<string | undefined, PluginEntry[]>()
|
|
68
|
+
for (const entry of filtered) {
|
|
69
|
+
const key = entry.category === undefined ? undefined : (typeof entry.category === 'function' ? entry.category() : entry.category)
|
|
70
|
+
const list = groups.get(key)
|
|
71
|
+
if (list === undefined) groups.set(key, [entry])
|
|
72
|
+
else list.push(entry)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Copy the entry's install script to the clipboard and flash the button's
|
|
76
|
+
* "已复制" label for a moment. The feedback ONLY appears after a
|
|
77
|
+
* successful write — when the clipboard is unavailable or denied
|
|
78
|
+
* (writeClipboard resolves false) nothing is shown, so the user is never
|
|
79
|
+
* told to paste a command that was not placed on the clipboard. Never
|
|
80
|
+
* closes anything, never throws outward. */
|
|
81
|
+
const copy = async (entry: PluginEntry): Promise<void> => {
|
|
82
|
+
const written = await writeClipboard(entry.install)
|
|
83
|
+
if (!written) return
|
|
84
|
+
setCopiedId(entry.id)
|
|
85
|
+
window.setTimeout(() => {
|
|
86
|
+
setCopiedId(current => (current === entry.id ? null : current))
|
|
87
|
+
}, COPIED_FEEDBACK_MS)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Open the plugin's repo in a REAL new browser tab (window.open — a
|
|
91
|
+
* button, so the sidebar link takeover cannot reroute it). */
|
|
92
|
+
const jump = (entry: PluginEntry): void => {
|
|
93
|
+
window.open(entry.url, '_blank', 'noopener')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** One catalog row (extracted so the group render stays flat). */
|
|
97
|
+
const renderEntry = (entry: PluginEntry): ReactNode => (
|
|
98
|
+
<div key={entry.id} className={css.pluginEntry}>
|
|
99
|
+
<div className={css.pluginEntryHead}>
|
|
100
|
+
{/* The name is a BUTTON on the same window.open path as the
|
|
101
|
+
jump button: as an anchor it would be caught by the
|
|
102
|
+
document-capture link takeover (which ignores
|
|
103
|
+
target=_blank) and land in the sidebar browser. */}
|
|
104
|
+
<button
|
|
105
|
+
type="button"
|
|
106
|
+
className={css.pluginName}
|
|
107
|
+
aria-label={`${t('openPlugin')}: ${entry.name}`}
|
|
108
|
+
onClick={() => { jump(entry) }}
|
|
109
|
+
>
|
|
110
|
+
{entry.name}
|
|
111
|
+
</button>
|
|
112
|
+
<span className={css.pluginEntryActions}>
|
|
113
|
+
<button
|
|
114
|
+
type="button"
|
|
115
|
+
className={css.pluginJumpBtn}
|
|
116
|
+
aria-label={`${t('openPlugin')}: ${entry.name}`}
|
|
117
|
+
onClick={() => { jump(entry) }}
|
|
118
|
+
>
|
|
119
|
+
{t('openPlugin')}
|
|
120
|
+
</button>
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
className={css.pluginCopyBtn}
|
|
124
|
+
aria-label={`${t('copyInstall')}: ${entry.name}`}
|
|
125
|
+
onClick={() => { copy(entry) }}
|
|
126
|
+
>
|
|
127
|
+
{copiedId === entry.id ? t('copied') : t('copy')}
|
|
128
|
+
</button>
|
|
129
|
+
</span>
|
|
130
|
+
</div>
|
|
131
|
+
<div className={css.pluginDesc}>
|
|
132
|
+
{typeof entry.description === 'function' ? entry.description() : entry.description}
|
|
133
|
+
</div>
|
|
134
|
+
<code className={css.pluginInstall}>{entry.install}</code>
|
|
135
|
+
</div>
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<div className={css.pluginList}>
|
|
140
|
+
<button
|
|
141
|
+
type="button"
|
|
142
|
+
className={css.pluginTopicBtn}
|
|
143
|
+
onClick={() => { window.open(PLUGIN_TOPIC_URL, '_blank', 'noopener') }}
|
|
144
|
+
>
|
|
145
|
+
{t('addPluginsBrowseMore')}
|
|
146
|
+
</button>
|
|
147
|
+
<input
|
|
148
|
+
type="search"
|
|
149
|
+
className={css.pluginSearch}
|
|
150
|
+
placeholder={t('addPluginsSearch')}
|
|
151
|
+
aria-label={t('addPluginsSearch')}
|
|
152
|
+
value={query}
|
|
153
|
+
onChange={event => { setQuery(event.currentTarget.value) }}
|
|
154
|
+
/>
|
|
155
|
+
<div className={css.groupHeading}>
|
|
156
|
+
<span>{t('addPluginsRecommended')}</span>
|
|
157
|
+
<span className={css.count}>{filtered.length}</span>
|
|
158
|
+
</div>
|
|
159
|
+
{catalog.length === 0 ? (
|
|
160
|
+
<div className={css.pluginEmpty}>{t('addPluginsEmpty')}</div>
|
|
161
|
+
) : filtered.length === 0 ? (
|
|
162
|
+
<div className={css.pluginEmpty}>{t('addPluginsNoMatch')}</div>
|
|
163
|
+
) : (
|
|
164
|
+
<div className={css.pluginEntries}>
|
|
165
|
+
{[...groups.entries()].map(([category, entries]) => (
|
|
166
|
+
<div key={category ?? '\u0000'} className={css.pluginGroup}>
|
|
167
|
+
{category !== undefined && (
|
|
168
|
+
<div className={css.pluginGroupHeading}>{category}</div>
|
|
169
|
+
)}
|
|
170
|
+
{entries.map(renderEntry)}
|
|
171
|
+
</div>
|
|
172
|
+
))}
|
|
173
|
+
</div>
|
|
174
|
+
)}
|
|
175
|
+
</div>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** The modal itself (mounted only while open — see the module comment). */
|
|
180
|
+
export function AddPluginModal(props: { service: BetterSidebarService; onClose: () => void; kind: PluginKind }) {
|
|
181
|
+
const { service, onClose, kind } = props
|
|
182
|
+
return (
|
|
183
|
+
<Modal
|
|
184
|
+
open
|
|
185
|
+
onClose={onClose}
|
|
186
|
+
title={kind === 'tab' ? t('addPluginsTabCard') : t('addPluginsViewerCard')}
|
|
187
|
+
description={kind === 'tab' ? t('addPluginsTabDesc') : t('addPluginsViewerDesc')}
|
|
188
|
+
closeLabel={t('close')}
|
|
189
|
+
className={css.pluginModal}
|
|
190
|
+
footer={(
|
|
191
|
+
<button type="button" className={css.done} onClick={onClose}>
|
|
192
|
+
{t('settingsDone')}
|
|
193
|
+
</button>
|
|
194
|
+
)}
|
|
195
|
+
>
|
|
196
|
+
<PluginListBody service={service} kind={kind} />
|
|
197
|
+
</Modal>
|
|
198
|
+
)
|
|
199
|
+
}
|