@lijian-ui/dsh-term 0.1.2 → 0.2.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/lib/client.js +285 -43843
- package/lib/client.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/tsconfig.client.tsbuildinfo +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/term/AnimatedDock.d.ts +10 -20
- package/lib/types/client/term/AnimatedDock.d.ts.map +1 -1
- package/lib/types/client/term/DockItem.d.ts +15 -0
- package/lib/types/client/term/DockItem.d.ts.map +1 -0
- package/lib/types/client/term/TerminalPanel.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +117 -43
- package/src/client/term/AnimatedDock.tsx +25 -114
- package/src/client/term/DockItem.tsx +42 -0
- package/src/client/term/TerminalPanel.tsx +28 -4
package/src/client/index.ts
CHANGED
|
@@ -42,8 +42,36 @@ const EV = {
|
|
|
42
42
|
terminalState: 'dsh-dock:terminal-state',
|
|
43
43
|
} as const
|
|
44
44
|
|
|
45
|
-
/**
|
|
46
|
-
const
|
|
45
|
+
/** Terminal column width bounds and persistence. */
|
|
46
|
+
const MIN_TERM_WIDTH = 200
|
|
47
|
+
const MAX_TERM_WIDTH = 600
|
|
48
|
+
const DEFAULT_TERM_WIDTH = 300
|
|
49
|
+
const TERM_WIDTH_KEY = 'dsh-term-width-px'
|
|
50
|
+
const TERM_HANDLE_WIDTH = 8
|
|
51
|
+
|
|
52
|
+
function readTermWidth(): number {
|
|
53
|
+
try {
|
|
54
|
+
const raw = localStorage.getItem(TERM_WIDTH_KEY)
|
|
55
|
+
if (raw === null) return DEFAULT_TERM_WIDTH
|
|
56
|
+
const v = Number(raw)
|
|
57
|
+
if (!Number.isFinite(v) || v < MIN_TERM_WIDTH || v > MAX_TERM_WIDTH) return DEFAULT_TERM_WIDTH
|
|
58
|
+
return v
|
|
59
|
+
} catch { return DEFAULT_TERM_WIDTH }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function writeTermWidth(v: number): void {
|
|
63
|
+
try { localStorage.setItem(TERM_WIDTH_KEY, String(Math.round(v))) } catch { /* best-effort */ }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Inject the drag-handle visual styles once. */
|
|
67
|
+
function adoptHandleStyles(): void {
|
|
68
|
+
const id = 'dsh-term-handle-style'
|
|
69
|
+
if (document.getElementById(id) !== null) return
|
|
70
|
+
const tag = document.createElement('style')
|
|
71
|
+
tag.id = id
|
|
72
|
+
tag.textContent = '.dsh-term-handle{touch-action:none;cursor:col-resize}'
|
|
73
|
+
document.head.appendChild(tag)
|
|
74
|
+
}
|
|
47
75
|
|
|
48
76
|
/** Locate the frame grid (same heuristic file-manager uses). */
|
|
49
77
|
function findFrame(): HTMLElement | null {
|
|
@@ -73,34 +101,6 @@ function whenFrameReady(cb: (frame: HTMLElement) => void): () => void {
|
|
|
73
101
|
return () => obs.disconnect()
|
|
74
102
|
}
|
|
75
103
|
|
|
76
|
-
/** Inject the launcher button styles once (the button is a raw DOM node). */
|
|
77
|
-
function adoptLauncherStyles(): void {
|
|
78
|
-
const id = 'dsh-term-launcher-style'
|
|
79
|
-
if (document.getElementById(id) !== null) return
|
|
80
|
-
const tag = document.createElement('style')
|
|
81
|
-
tag.id = id
|
|
82
|
-
tag.textContent = `
|
|
83
|
-
.dsh-term-launcher {
|
|
84
|
-
position: fixed;
|
|
85
|
-
right: 12px;
|
|
86
|
-
bottom: 12px;
|
|
87
|
-
z-index: 40;
|
|
88
|
-
height: 30px;
|
|
89
|
-
padding: 0 14px;
|
|
90
|
-
border-radius: 6px;
|
|
91
|
-
border: 1px solid var(--aion-bg-3, #e5e6eb);
|
|
92
|
-
background: var(--aion-bg-1, #ffffff);
|
|
93
|
-
color: var(--aion-fg-1, #1f2329);
|
|
94
|
-
font-size: 13px;
|
|
95
|
-
font-family: var(--aion-font-sans, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif);
|
|
96
|
-
cursor: pointer;
|
|
97
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
98
|
-
}
|
|
99
|
-
.dsh-term-launcher:hover {
|
|
100
|
-
background: var(--aion-bg-2, #f2f3f5);
|
|
101
|
-
}`
|
|
102
|
-
document.head.appendChild(tag)
|
|
103
|
-
}
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
106
|
* Parse a `grid-template-columns` string into raw tokens. Named lines
|
|
@@ -165,6 +165,7 @@ export function apply(ctx: ClientContext): void {
|
|
|
165
165
|
col.dataset.dshTermCol = ''
|
|
166
166
|
col.style.minWidth = '0'
|
|
167
167
|
col.style.height = '100%'
|
|
168
|
+
col.style.display = 'none'
|
|
168
169
|
frame.appendChild(col)
|
|
169
170
|
|
|
170
171
|
// Keep the terminal the rightmost column even if file-manager appends
|
|
@@ -174,18 +175,24 @@ export function apply(ctx: ClientContext): void {
|
|
|
174
175
|
})
|
|
175
176
|
keepLast.observe(frame, { childList: true })
|
|
176
177
|
|
|
177
|
-
// Re-open button (after a collapse). Hidden while the column is open.
|
|
178
|
-
adoptLauncherStyles()
|
|
179
|
-
const launcher = document.createElement('button')
|
|
180
|
-
launcher.type = 'button'
|
|
181
|
-
launcher.className = 'dsh-term-launcher'
|
|
182
|
-
launcher.textContent = '终端'
|
|
183
|
-
launcher.setAttribute('aria-label', '打开终端')
|
|
184
|
-
launcher.style.display = 'none'
|
|
185
|
-
document.body.appendChild(launcher)
|
|
186
178
|
|
|
187
179
|
// Open/closed state. Closed by default (the column is a docked track).
|
|
188
180
|
let terminalOpen = false
|
|
181
|
+
let terminalWidth = readTermWidth()
|
|
182
|
+
|
|
183
|
+
// The drag handle on the terminal column's left edge (drag left = wider).
|
|
184
|
+
adoptHandleStyles()
|
|
185
|
+
const handle = document.createElement('div')
|
|
186
|
+
handle.className = 'dsh-term-handle'
|
|
187
|
+
handle.style.position = 'absolute'
|
|
188
|
+
handle.style.top = '0'
|
|
189
|
+
handle.style.bottom = '0'
|
|
190
|
+
handle.style.width = `${TERM_HANDLE_WIDTH}px`
|
|
191
|
+
handle.style.marginLeft = `${-TERM_HANDLE_WIDTH / 2}px`
|
|
192
|
+
handle.style.zIndex = '30'
|
|
193
|
+
handle.style.cursor = 'col-resize'
|
|
194
|
+
handle.style.display = 'none'
|
|
195
|
+
frame.appendChild(handle)
|
|
189
196
|
|
|
190
197
|
/**
|
|
191
198
|
* Re-assert the frame grid: keep file-manager's tracks, append our
|
|
@@ -205,11 +212,19 @@ export function apply(ctx: ClientContext): void {
|
|
|
205
212
|
// (e.g. the shell's own 3-track write landed before file-manager
|
|
206
213
|
// re-applied). Touching it now would desync file-manager's width math.
|
|
207
214
|
if (tokens.length !== baseLen) return
|
|
208
|
-
if (terminalOpen) tokens.push('[dsh-term]', `${
|
|
215
|
+
if (terminalOpen) tokens.push('[dsh-term]', `${terminalWidth}px`)
|
|
209
216
|
const next = tokens.join(' ')
|
|
210
217
|
if (next !== frame.style.gridTemplateColumns) {
|
|
211
218
|
frame.style.gridTemplateColumns = next
|
|
212
219
|
}
|
|
220
|
+
// Keep the drag handle glued to the terminal column's left edge.
|
|
221
|
+
if (terminalOpen) {
|
|
222
|
+
const frameRect = frame.getBoundingClientRect()
|
|
223
|
+
const colRect = col.getBoundingClientRect()
|
|
224
|
+
const leftEdge = colRect.left - frameRect.left
|
|
225
|
+
handle.style.left = `${Math.round(leftEdge)}px`
|
|
226
|
+
}
|
|
227
|
+
handle.style.display = terminalOpen ? 'block' : 'none'
|
|
213
228
|
}
|
|
214
229
|
|
|
215
230
|
// Re-assert after file-manager rewrites the grid (drag / resize / collapse).
|
|
@@ -223,13 +238,72 @@ export function apply(ctx: ClientContext): void {
|
|
|
223
238
|
const setVisible = (open: boolean): void => {
|
|
224
239
|
terminalOpen = open
|
|
225
240
|
col.style.display = open ? 'flex' : 'none'
|
|
226
|
-
launcher.style.display = open ? 'none' : 'flex'
|
|
227
241
|
reconcileGrid()
|
|
228
242
|
window.dispatchEvent(new CustomEvent(EV.terminalState, { detail: open }))
|
|
229
243
|
}
|
|
230
244
|
const onToggleTerminal = (): void => setVisible(!terminalOpen)
|
|
231
245
|
window.addEventListener(EV.toggleTerminal, onToggleTerminal)
|
|
232
|
-
|
|
246
|
+
|
|
247
|
+
// Drag the handle to resize the terminal column (left = wider).
|
|
248
|
+
handle.addEventListener('pointerdown', (event: PointerEvent): void => {
|
|
249
|
+
if (event.button !== 0) return
|
|
250
|
+
event.preventDefault()
|
|
251
|
+
handle.setPointerCapture(event.pointerId)
|
|
252
|
+
const startX = event.clientX
|
|
253
|
+
const startWidth = terminalWidth
|
|
254
|
+
let rafId: number | null = null
|
|
255
|
+
let pendingWidth: number | null = null
|
|
256
|
+
let latestWidth = startWidth
|
|
257
|
+
|
|
258
|
+
document.body.style.userSelect = 'none'
|
|
259
|
+
document.body.style.cursor = 'col-resize'
|
|
260
|
+
frame.setAttribute('data-dragging', '')
|
|
261
|
+
handle.setAttribute('data-dragging', '')
|
|
262
|
+
|
|
263
|
+
const flush = (): void => {
|
|
264
|
+
if (pendingWidth === null) return
|
|
265
|
+
latestWidth = pendingWidth
|
|
266
|
+
terminalWidth = pendingWidth
|
|
267
|
+
reconcileGrid()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const computeWidth = (clientX: number): number => {
|
|
271
|
+
const deltaX = startX - clientX
|
|
272
|
+
return Math.min(MAX_TERM_WIDTH, Math.max(MIN_TERM_WIDTH, startWidth + deltaX))
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const finish = (clientX: number | null): void => {
|
|
276
|
+
handle.removeEventListener('pointermove', onMove)
|
|
277
|
+
handle.removeEventListener('pointerup', onUp)
|
|
278
|
+
handle.removeEventListener('pointercancel', onCancel)
|
|
279
|
+
try { handle.releasePointerCapture(event.pointerId) } catch { /* already released */ }
|
|
280
|
+
document.body.style.userSelect = ''
|
|
281
|
+
document.body.style.cursor = ''
|
|
282
|
+
frame.removeAttribute('data-dragging')
|
|
283
|
+
handle.removeAttribute('data-dragging')
|
|
284
|
+
if (rafId !== null) { cancelAnimationFrame(rafId); rafId = null }
|
|
285
|
+
flush()
|
|
286
|
+
const finalWidth = clientX === null ? latestWidth : computeWidth(clientX)
|
|
287
|
+
terminalWidth = finalWidth
|
|
288
|
+
reconcileGrid()
|
|
289
|
+
writeTermWidth(finalWidth)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const onMove = (e: PointerEvent): void => {
|
|
293
|
+
if (e.buttons === 0) { finish(e.clientX); return }
|
|
294
|
+
pendingWidth = computeWidth(e.clientX)
|
|
295
|
+
if (rafId === null) {
|
|
296
|
+
rafId = requestAnimationFrame(() => { rafId = null; flush() })
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const onUp = (e: PointerEvent): void => finish(e.clientX)
|
|
300
|
+
const onCancel = (): void => finish(null)
|
|
301
|
+
|
|
302
|
+
handle.addEventListener('pointermove', onMove)
|
|
303
|
+
handle.addEventListener('pointerup', onUp)
|
|
304
|
+
handle.addEventListener('pointercancel', onCancel)
|
|
305
|
+
})
|
|
306
|
+
|
|
233
307
|
root.render(createElement(TerminalPanel, { ctx, api, onClose: () => setVisible(false) }))
|
|
234
308
|
|
|
235
309
|
disposeFrame = () => {
|
|
@@ -238,7 +312,7 @@ export function apply(ctx: ClientContext): void {
|
|
|
238
312
|
window.removeEventListener(EV.toggleTerminal, onToggleTerminal)
|
|
239
313
|
root.unmount()
|
|
240
314
|
col.remove()
|
|
241
|
-
|
|
315
|
+
handle.remove()
|
|
242
316
|
}
|
|
243
317
|
})
|
|
244
318
|
return () => {
|
|
@@ -1,132 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AnimatedDock —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* AnimatedDock — the terminal dock button rendered into the conversation
|
|
3
|
+
* session header (the `conversation.session.header.utilities` slot, ordered to
|
|
4
|
+
* the LEFT of the official "Session log" button).
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* dock stays perfectly stable under the cursor.
|
|
12
|
-
* Icons come from lucide-react. The dock itself is a frosted-glass pill.
|
|
6
|
+
* Each plugin owns its own dock button independently: dsh-term renders this
|
|
7
|
+
* terminal button, dsh-file-manager renders its own file-panel button. The two
|
|
8
|
+
* are fully decoupled — install either alone and you get just its button;
|
|
9
|
+
* install both and both buttons appear side by side with shared magnification
|
|
10
|
+
* (coordinated through window CustomEvents, see DockItem.tsx).
|
|
13
11
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* bundle):
|
|
17
|
-
*
|
|
18
|
-
* - terminal -> `dsh-dock:toggle-terminal` (handled in dsh-term index)
|
|
19
|
-
* - file -> `dsh-dock:toggle-filepanel` (handled in dsh-file-manager)
|
|
20
|
-
*
|
|
21
|
-
* Active (open) state is mirrored back through `dsh-dock:terminal-state` /
|
|
22
|
-
* `dsh-dock:filepanel-state` so the icons highlight in lockstep with the
|
|
23
|
-
* actual panels.
|
|
12
|
+
* Clicking broadcasts `dsh-dock:toggle-terminal` (handled in dsh-term index);
|
|
13
|
+
* the open state is mirrored back through `dsh-dock:terminal-state`.
|
|
24
14
|
* @module dsh-term/client/AnimatedDock
|
|
25
15
|
*/
|
|
16
|
+
import { useEffect, useState, type ReactElement } from 'react'
|
|
17
|
+
import { DockItem } from './DockItem.tsx'
|
|
26
18
|
|
|
27
|
-
import { useEffect, useRef, useState, type ReactElement, type ReactNode } from 'react'
|
|
28
|
-
import { motion, useMotionValue, useSpring, useTransform, type MotionValue } from 'framer-motion'
|
|
29
|
-
import { Terminal, FolderOpen } from 'lucide-react'
|
|
30
|
-
import styles from './animated-dock.module.css'
|
|
31
|
-
|
|
32
|
-
/** Cross-plugin event names (kept as literals to avoid cross-bundle imports). */
|
|
33
19
|
const EV = {
|
|
34
20
|
toggleTerminal: 'dsh-dock:toggle-terminal',
|
|
35
|
-
toggleFile: 'dsh-dock:toggle-filepanel',
|
|
36
21
|
terminalState: 'dsh-dock:terminal-state',
|
|
37
|
-
fileState: 'dsh-dock:filepanel-state',
|
|
38
22
|
} as const
|
|
39
23
|
|
|
40
|
-
/** Resting / peak icon size (px). */
|
|
41
|
-
const BASE = 40
|
|
42
|
-
const MAX = 58
|
|
43
|
-
/** Magnification factor at the cursor (MAX / BASE). Driven via CSS `scale()`
|
|
44
|
-
* so it never reflows the layout — this is what removed the old "jitter". */
|
|
45
|
-
const MAX_SCALE = MAX / BASE
|
|
46
|
-
/** Cursor distance (px) over which magnification falls to zero. */
|
|
47
|
-
const DIST = 120
|
|
48
|
-
/** Spring tuning. Near-critically damped (ratio ≈ 0.94) so the icon settles
|
|
49
|
-
* smoothly with essentially no overshoot. The old `damping: 0.35` was
|
|
50
|
-
* effectively zero damping and caused the wild oscillation. */
|
|
51
|
-
const DAMPING = 28
|
|
52
|
-
const STIFFNESS = 220
|
|
53
|
-
|
|
54
24
|
export function AnimatedDock(): ReactElement {
|
|
55
|
-
const
|
|
56
|
-
const [terminalActive, setTerminalActive] = useState(false)
|
|
57
|
-
const [fileActive, setFileActive] = useState(false)
|
|
58
|
-
|
|
59
|
-
// Mirror the real panel open-state back into the icon highlight.
|
|
25
|
+
const [active, setActive] = useState(false)
|
|
60
26
|
useEffect(() => {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
window.
|
|
64
|
-
window.addEventListener(EV.fileState, onFile)
|
|
65
|
-
return () => {
|
|
66
|
-
window.removeEventListener(EV.terminalState, onTerm)
|
|
67
|
-
window.removeEventListener(EV.fileState, onFile)
|
|
68
|
-
}
|
|
27
|
+
const onState = (e: Event): void => setActive(Boolean((e as CustomEvent).detail))
|
|
28
|
+
window.addEventListener(EV.terminalState, onState)
|
|
29
|
+
return () => window.removeEventListener(EV.terminalState, onState)
|
|
69
30
|
}, [])
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<motion.div
|
|
73
|
-
className={styles.dock}
|
|
74
|
-
onMouseMove={(e) => mouseX.set(e.clientX)}
|
|
75
|
-
onMouseLeave={() => mouseX.set(Infinity)}
|
|
76
|
-
aria-label="工具坞"
|
|
77
|
-
>
|
|
78
|
-
<DockItem
|
|
79
|
-
mouseX={mouseX}
|
|
80
|
-
active={terminalActive}
|
|
81
|
-
label="终端"
|
|
82
|
-
onClick={() => window.dispatchEvent(new CustomEvent(EV.toggleTerminal))}
|
|
83
|
-
>
|
|
84
|
-
<Terminal size={20} strokeWidth={2} />
|
|
85
|
-
</DockItem>
|
|
86
|
-
<DockItem
|
|
87
|
-
mouseX={mouseX}
|
|
88
|
-
active={fileActive}
|
|
89
|
-
label="文件面板"
|
|
90
|
-
onClick={() => window.dispatchEvent(new CustomEvent(EV.toggleFile))}
|
|
91
|
-
>
|
|
92
|
-
<FolderOpen size={20} strokeWidth={2} />
|
|
93
|
-
</DockItem>
|
|
94
|
-
</motion.div>
|
|
95
|
-
)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** A single magnifying icon. Its `scale` is a spring driven by cursor distance.
|
|
99
|
-
* Because we animate `transform` (not `width`), the layout never reflows, so
|
|
100
|
-
* there is no neighbour-push feedback loop and the dock stays rock-steady. */
|
|
101
|
-
function DockItem(props: {
|
|
102
|
-
mouseX: MotionValue<number>
|
|
103
|
-
active: boolean
|
|
104
|
-
label: string
|
|
105
|
-
onClick: () => void
|
|
106
|
-
children: ReactNode
|
|
107
|
-
}): ReactElement {
|
|
108
|
-
const ref = useRef<HTMLButtonElement>(null)
|
|
109
|
-
const distance = useTransform(props.mouseX, (val) => {
|
|
110
|
-
const bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: BASE }
|
|
111
|
-
return val - bounds.x - bounds.width / 2
|
|
112
|
-
})
|
|
113
|
-
const scale = useSpring(
|
|
114
|
-
useTransform(distance, [-DIST, 0, DIST], [1, MAX_SCALE, 1]),
|
|
115
|
-
{ damping: DAMPING, stiffness: STIFFNESS },
|
|
116
|
-
)
|
|
117
31
|
return (
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
className={`${styles.item}${props.active ? ` ${styles.active}` : ''}`}
|
|
123
|
-
onClick={props.onClick}
|
|
124
|
-
aria-label={props.label}
|
|
125
|
-
aria-pressed={props.active}
|
|
126
|
-
title={props.label}
|
|
32
|
+
<DockItem
|
|
33
|
+
active={active}
|
|
34
|
+
label="终端"
|
|
35
|
+
onClick={() => window.dispatchEvent(new CustomEvent(EV.toggleTerminal))}
|
|
127
36
|
>
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
37
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
38
|
+
<polyline points="4 17 10 11 4 5" />
|
|
39
|
+
<line x1="12" y1="19" x2="20" y2="19" />
|
|
40
|
+
</svg>
|
|
41
|
+
</DockItem>
|
|
131
42
|
)
|
|
132
43
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DockItem — a round dock button for the conversation session header.
|
|
3
|
+
* Each plugin owns its own DockItem copy (terminal in dsh-term, file-panel in
|
|
4
|
+
* dsh-file-manager). Hover scales the button up via pure CSS — no JS event
|
|
5
|
+
* coordination, no shared motion value.
|
|
6
|
+
* @module dsh-term/client/DockItem
|
|
7
|
+
*/
|
|
8
|
+
import { type ReactElement, type ReactNode } from 'react'
|
|
9
|
+
|
|
10
|
+
const STYLE_ID = 'dsh-dock-item-style'
|
|
11
|
+
|
|
12
|
+
function ensureStyle(): void {
|
|
13
|
+
if (typeof document === 'undefined') return
|
|
14
|
+
if (document.getElementById(STYLE_ID) !== null) return
|
|
15
|
+
const tag = document.createElement('style')
|
|
16
|
+
tag.id = STYLE_ID
|
|
17
|
+
tag.textContent = '.dsh-dock-item{width:32px;height:32px;border:none;border-radius:9999px;background:#000;color:#fff;cursor:pointer;position:relative;transition:transform .15s ease,background .18s ease;display:inline-flex;align-items:center;justify-content:center}.dsh-dock-item:hover{transform:scale(1.15);background:#1a1a1a}.dsh-dock-item-active{background:var(--aion-primary,#6ea0ff)}.dsh-dock-tooltip{position:absolute;top:calc(100% + 6px);left:50%;transform:translateX(-50%);padding:2px 8px;font-size:12px;line-height:1.4;white-space:nowrap;color:#fff;background:rgba(20,20,30,.9);border:1px solid rgba(255,255,255,.12);border-radius:6px;opacity:0;pointer-events:none;transition:opacity .15s}.dsh-dock-item:hover .dsh-dock-tooltip{opacity:1}'
|
|
18
|
+
document.head.appendChild(tag)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
ensureStyle()
|
|
22
|
+
|
|
23
|
+
export function DockItem(props: {
|
|
24
|
+
active: boolean
|
|
25
|
+
label: string
|
|
26
|
+
onClick: () => void
|
|
27
|
+
children: ReactNode
|
|
28
|
+
}): ReactElement {
|
|
29
|
+
return (
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
className={`dsh-dock-item${props.active ? ' dsh-dock-item-active' : ''}`}
|
|
33
|
+
onClick={props.onClick}
|
|
34
|
+
aria-label={props.label}
|
|
35
|
+
aria-pressed={props.active}
|
|
36
|
+
title={props.label}
|
|
37
|
+
>
|
|
38
|
+
<span className="dsh-dock-tooltip">{props.label}</span>
|
|
39
|
+
{props.children}
|
|
40
|
+
</button>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -45,11 +45,35 @@ function adoptXtermStyles(): void {
|
|
|
45
45
|
/** Terminal theme matching the shell's light/dark marker. */
|
|
46
46
|
function themeOf(): Record<string, string> {
|
|
47
47
|
const dark = document.body.dataset.dsDarkTheme !== undefined
|
|
48
|
+
if (dark) {
|
|
49
|
+
return {
|
|
50
|
+
background: '#0e0e0e',
|
|
51
|
+
foreground: '#ced3da',
|
|
52
|
+
cursor: '#ced3da',
|
|
53
|
+
selectionBackground: 'rgba(255,255,255,0.2)',
|
|
54
|
+
}
|
|
55
|
+
}
|
|
48
56
|
return {
|
|
49
|
-
background:
|
|
50
|
-
foreground:
|
|
51
|
-
cursor:
|
|
52
|
-
selectionBackground:
|
|
57
|
+
background: '#ffffff',
|
|
58
|
+
foreground: '#1f2329',
|
|
59
|
+
cursor: '#1f2329',
|
|
60
|
+
selectionBackground: 'rgba(22,93,255,0.25)',
|
|
61
|
+
black: '#1f2329',
|
|
62
|
+
red: '#d4393b',
|
|
63
|
+
green: '#167c2e',
|
|
64
|
+
yellow: '#b58900',
|
|
65
|
+
blue: '#0a4d8c',
|
|
66
|
+
magenta: '#a020a0',
|
|
67
|
+
cyan: '#0379a6',
|
|
68
|
+
white: '#5a5f66',
|
|
69
|
+
brightBlack: '#6a6f76',
|
|
70
|
+
brightRed: '#e5585a',
|
|
71
|
+
brightGreen: '#1a9c3a',
|
|
72
|
+
brightYellow: '#d6a200',
|
|
73
|
+
brightBlue: '#1a6fcf',
|
|
74
|
+
brightMagenta: '#c040c0',
|
|
75
|
+
brightCyan: '#1aa0d6',
|
|
76
|
+
brightWhite: '#2f353b',
|
|
53
77
|
}
|
|
54
78
|
}
|
|
55
79
|
|