@linxin666/dsh-usage 0.3.24 → 0.4.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/README.i18n.yaml +2 -2
- package/README.md +4 -4
- package/README.zh.md +4 -4
- package/icon.svg +3 -0
- package/lib/client.js +540 -655
- package/lib/client.js.map +1 -1
- package/lib/index.js +92 -75
- package/lib/types/client/UsageFootCard.d.ts +55 -0
- package/lib/types/client/UsageSectionCard.d.ts +3 -3
- package/lib/types/client/foot-card-mount.d.ts +34 -0
- package/lib/types/client/index.d.ts +21 -8
- package/lib/types/client/locales.d.ts +6 -9
- package/lib/types/host/http.d.ts +18 -1
- package/lib/types/host/usage-service.d.ts +4 -3
- package/lib/types/index.d.ts +14 -0
- package/package.json +19 -17
- package/src/client/UsageFootCard.tsx +300 -0
- package/src/client/UsageSectionCard.tsx +41 -9
- package/src/client/foot-card-mount.tsx +112 -0
- package/src/client/index.ts +53 -33
- package/src/client/locales.ts +12 -18
- package/src/client/usage.module.css +101 -165
- package/src/host/http.ts +23 -1
- package/src/host/usage-service.ts +9 -4
- package/src/index.ts +59 -55
- package/lib/types/client/UsageSidebarPanel.d.ts +0 -34
- package/lib/types/client/sidebar-entry-core.d.ts +0 -87
- package/lib/types/client/sidebar-entry.d.ts +0 -35
- package/lib/types/client/sidebar-panel-mount.d.ts +0 -40
- package/src/client/UsageSidebarPanel.tsx +0 -196
- package/src/client/sidebar-entry-core.ts +0 -321
- package/src/client/sidebar-entry.ts +0 -101
- package/src/client/sidebar-panel-mount.tsx +0 -113
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sidebar entry injection for the usage panel (issue #1592) — wiring over the
|
|
3
|
-
* shared core. Unlike the panel-takeover family (task board / SSH / skill
|
|
4
|
-
* center) this row does not participate in the family block, so it is placed
|
|
5
|
-
* directly under the New Session row and ordered after those entries. The row
|
|
6
|
-
* seats the panel's controls as trailing actions: a refresh command and a
|
|
7
|
-
* collapse chevron mirroring the open state, so the panel below renders only
|
|
8
|
-
* its content.
|
|
9
|
-
* @module @linxin666/dsh-usage/client/sidebar-entry
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { t } from './locales.ts'
|
|
13
|
-
import css from './usage.module.css'
|
|
14
|
-
import { mountSidebarEntry as mountSharedSidebarEntry, type SidebarEntryAction } from './sidebar-entry-core.ts'
|
|
15
|
-
|
|
16
|
-
/** Stable data attribute identifying the injected entry row. */
|
|
17
|
-
export const ENTRY_SELECTOR = '[data-dsh-usage-entry]'
|
|
18
|
-
|
|
19
|
-
/** Inline gauge glyph normalized to the shell's 18px navigation glyph size. */
|
|
20
|
-
const ICON = '<svg viewBox="0 0 16 16" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 12.5a7 7 0 0 1 11 -4.3"/><path d="M8 12.5V8.2l2.9-2.1"/><circle cx="8" cy="12.5" r="1"/></svg>'
|
|
21
|
-
|
|
22
|
-
/** Refresh glyph: a clockwise circular arrow, 14px like the sibling action icons. */
|
|
23
|
-
const REFRESH_ICON = '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13.4 8a5.4 5.4 0 1 1-1.58-3.82"/><path d="M13.4 2.6v2.7h-2.7"/></svg>'
|
|
24
|
-
|
|
25
|
-
/** Disclosure chevrons: down while expanded, right while collapsed. */
|
|
26
|
-
const CHEVRON_OPEN_ICON = '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m4 6 4 4 4-4"/></svg>'
|
|
27
|
-
const CHEVRON_CLOSED_ICON = '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 4 4 4-4 4"/></svg>'
|
|
28
|
-
|
|
29
|
-
/** Refresh cooldown: one forced probe cycle per click, the icon spins meanwhile. */
|
|
30
|
-
const REFRESH_COOLDOWN_MS = 3000
|
|
31
|
-
|
|
32
|
-
/** Locale-change subscription the shared core asks for (ctx.locale.subscribe shape). */
|
|
33
|
-
export interface LocaleRefreshSource { subscribe(listener: () => void): () => void }
|
|
34
|
-
|
|
35
|
-
/** Panel-facing callbacks and state the entry row's controls drive. */
|
|
36
|
-
export interface UsageSidebarEntryHandlers {
|
|
37
|
-
/** Toggles the panel (the row's main area and the collapse chevron). */
|
|
38
|
-
onToggle(): void
|
|
39
|
-
/** Forces one host probe cycle (the refresh action). */
|
|
40
|
-
onRefresh(): void
|
|
41
|
-
/** Reads the panel's expanded state (row highlight and chevron direction). */
|
|
42
|
-
isOpen(): boolean
|
|
43
|
-
/** Subscribes to expanded-state changes (the chevron mirrors them live). */
|
|
44
|
-
subscribeOpen(listener: () => void): () => void
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Trailing actions seated at the row's right edge. */
|
|
48
|
-
function entryActions(handlers: UsageSidebarEntryHandlers): readonly SidebarEntryAction[] {
|
|
49
|
-
return [
|
|
50
|
-
{
|
|
51
|
-
id: 'refresh',
|
|
52
|
-
icon: REFRESH_ICON,
|
|
53
|
-
label: () => t('usage.refresh'),
|
|
54
|
-
onClick: (button) => {
|
|
55
|
-
handlers.onRefresh()
|
|
56
|
-
button.disabled = true
|
|
57
|
-
window.setTimeout(() => { button.disabled = false }, REFRESH_COOLDOWN_MS)
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
id: 'collapse',
|
|
62
|
-
icon: CHEVRON_OPEN_ICON,
|
|
63
|
-
inactiveIcon: CHEVRON_CLOSED_ICON,
|
|
64
|
-
label: (open) => t(open ? 'usage.sidebar.toggle.collapse' : 'usage.sidebar.toggle.expand'),
|
|
65
|
-
onClick: () => { handlers.onToggle() },
|
|
66
|
-
},
|
|
67
|
-
]
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Mount the usage sidebar entry.
|
|
72
|
-
* @param handlers - the panel's toggle/refresh callbacks and open-state face.
|
|
73
|
-
* @param locale - locale-change source; when given, re-applies the label on a
|
|
74
|
-
* language switch (the plain-DOM row otherwise keeps the mount-time copy).
|
|
75
|
-
* @returns disposer removing the entry and its observers.
|
|
76
|
-
*/
|
|
77
|
-
export function mountSidebarEntry(
|
|
78
|
-
handlers: UsageSidebarEntryHandlers,
|
|
79
|
-
locale?: LocaleRefreshSource,
|
|
80
|
-
): () => void {
|
|
81
|
-
return mountSharedSidebarEntry({
|
|
82
|
-
rowAttribute: 'data-dsh-usage-entry',
|
|
83
|
-
rowSelector: ENTRY_SELECTOR,
|
|
84
|
-
plugin: 'usage',
|
|
85
|
-
icon: ICON,
|
|
86
|
-
css,
|
|
87
|
-
label: () => t('usage.sidebar.entry.label'),
|
|
88
|
-
tooltip: () => t('usage.sidebar.entry.tooltip'),
|
|
89
|
-
refresh: locale === undefined ? undefined : { subscribe: (listener) => locale.subscribe(listener) },
|
|
90
|
-
onToggle: handlers.onToggle,
|
|
91
|
-
actions: entryActions(handlers),
|
|
92
|
-
// Directly after the family entries (task board / SSH / skill center) and
|
|
93
|
-
// before the workspace browser, matching the requested sidebar order.
|
|
94
|
-
position: 'after',
|
|
95
|
-
familySelectors: ['[data-dsh-taskboard-entry]', '[data-dsh-ssh-entry]', '[data-dsh-skill-explorer-entry]', '[data-dsh-usage-entry]'],
|
|
96
|
-
active: {
|
|
97
|
-
subscribe: handlers.subscribeOpen,
|
|
98
|
-
isOpen: handlers.isOpen,
|
|
99
|
-
},
|
|
100
|
-
})
|
|
101
|
-
}
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sidebar usage panel mounting (issue #1592).
|
|
3
|
-
*
|
|
4
|
-
* The panel is a plain DOM row inserted into the sidebar right below the
|
|
5
|
-
* usage entry row, carrying its own React root. Placement self-heals with the
|
|
6
|
-
* same page-wide body-mutation hub the entry row uses; the entry row stays the
|
|
7
|
-
* anchor, so the sidebar order never depends on this module's timing.
|
|
8
|
-
*
|
|
9
|
-
* The mount owns the panel's single open state: initialized from localStorage,
|
|
10
|
-
* flipped by the entry row's toggle controls, and mirrored to the React body
|
|
11
|
-
* through the subscribe/isOpen face (the same face the entry row's chevron
|
|
12
|
-
* and highlight read).
|
|
13
|
-
* @module @linxin666/dsh-usage/client/sidebar-panel-mount
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { createElement } from 'react'
|
|
17
|
-
import { createRoot, type Root } from 'react-dom/client'
|
|
18
|
-
import { subscribeBodyInvalidations } from './body-mutations.ts'
|
|
19
|
-
import { UsageSidebarPanel, type OpenStateSource } from './UsageSidebarPanel.tsx'
|
|
20
|
-
import type { UsageStoreInstance } from './usage-store.ts'
|
|
21
|
-
import { ENTRY_SELECTOR } from './sidebar-entry.ts'
|
|
22
|
-
|
|
23
|
-
/** localStorage key holding the collapsed flag ('1' = collapsed). */
|
|
24
|
-
export const COLLAPSED_STORAGE_KEY = 'dsh-usage.sidebar.collapsed'
|
|
25
|
-
|
|
26
|
-
/** Read the persisted collapsed flag (absent = expanded). */
|
|
27
|
-
function readCollapsed(): boolean {
|
|
28
|
-
try {
|
|
29
|
-
return window.localStorage.getItem(COLLAPSED_STORAGE_KEY) === '1'
|
|
30
|
-
} catch {
|
|
31
|
-
// Storage can be unavailable (privacy modes); the panel stays expanded.
|
|
32
|
-
return false
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Persist the collapsed flag; storage failures keep the session state. */
|
|
37
|
-
function writeCollapsed(collapsed: boolean): void {
|
|
38
|
-
try {
|
|
39
|
-
window.localStorage.setItem(COLLAPSED_STORAGE_KEY, collapsed ? '1' : '0')
|
|
40
|
-
} catch {
|
|
41
|
-
// Storage unavailable: the state still flips for this session.
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** Panel inputs the mount forwards into the React tree. */
|
|
46
|
-
export interface UsagePanelMountProps {
|
|
47
|
-
store: UsageStoreInstance
|
|
48
|
-
/** Fetch one overview now. */
|
|
49
|
-
poll: () => void
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Mounted panel controller: the entry row drives the toggle and mirrors the state. */
|
|
53
|
-
export interface UsagePanelMount {
|
|
54
|
-
/** Whether the panel body is currently expanded. */
|
|
55
|
-
isOpen(): boolean
|
|
56
|
-
/** Subscribe to open/collapse changes (the entry row's chevron and highlight). */
|
|
57
|
-
subscribe(listener: () => void): () => void
|
|
58
|
-
/** Flip the open state, persist it, and notify subscribers. */
|
|
59
|
-
toggle(): void
|
|
60
|
-
/** Unmount the panel and remove its container. */
|
|
61
|
-
dispose(): void
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Mount the sidebar usage panel below its entry row.
|
|
66
|
-
* @param props - the store and poll callback of the apply body.
|
|
67
|
-
* @returns the panel controller.
|
|
68
|
-
*/
|
|
69
|
-
export function mountUsagePanel(props: UsagePanelMountProps): UsagePanelMount {
|
|
70
|
-
const container = document.createElement('div')
|
|
71
|
-
container.setAttribute('data-dsh-usage-view', '')
|
|
72
|
-
|
|
73
|
-
const listeners = new Set<() => void>()
|
|
74
|
-
let open = !readCollapsed()
|
|
75
|
-
const emit = (): void => { for (const listener of [...listeners]) listener() }
|
|
76
|
-
const openState: OpenStateSource = {
|
|
77
|
-
subscribe: (listener: () => void) => {
|
|
78
|
-
listeners.add(listener)
|
|
79
|
-
return () => { listeners.delete(listener) }
|
|
80
|
-
},
|
|
81
|
-
isOpen: () => open,
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const root: Root = createRoot(container)
|
|
85
|
-
root.render(createElement(UsageSidebarPanel, { store: props.store, poll: props.poll, open: openState }))
|
|
86
|
-
|
|
87
|
-
/** Keep the panel as the row directly after the usage entry. */
|
|
88
|
-
const place = (): void => {
|
|
89
|
-
const entry = document.querySelector<HTMLElement>(ENTRY_SELECTOR)
|
|
90
|
-
if (entry === null || entry.parentElement === null) return
|
|
91
|
-
if (container.parentElement !== entry.parentElement || entry.nextElementSibling !== container) {
|
|
92
|
-
entry.parentElement.insertBefore(container, entry.nextElementSibling)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
place()
|
|
96
|
-
const unsubscribeBody = subscribeBodyInvalidations(place)
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
isOpen: () => open,
|
|
100
|
-
subscribe: openState.subscribe,
|
|
101
|
-
toggle: () => {
|
|
102
|
-
open = !open
|
|
103
|
-
writeCollapsed(!open)
|
|
104
|
-
emit()
|
|
105
|
-
},
|
|
106
|
-
dispose: () => {
|
|
107
|
-
unsubscribeBody()
|
|
108
|
-
root.unmount()
|
|
109
|
-
container.remove()
|
|
110
|
-
listeners.clear()
|
|
111
|
-
},
|
|
112
|
-
}
|
|
113
|
-
}
|