dsh-remote-vs 0.9.3 → 0.11.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.en.md +13 -10
- package/README.md +14 -9
- package/cordis.patch.yml +10 -5
- package/lib/client.js +3319 -1093
- package/lib/index.js +211 -9
- package/lib/types/client/api.d.ts +12 -0
- package/lib/types/client/composer.d.ts +5 -1
- package/lib/types/client/exec-output.d.ts +58 -0
- package/lib/types/client/i18n.d.ts +1 -1
- package/lib/types/client/scm/CommitPane.d.ts +73 -0
- package/lib/types/client/scm/ExecPanel.d.ts +34 -0
- package/lib/types/client/scm/HistoryPanel.d.ts +12 -4
- package/lib/types/client/scm/actions.d.ts +50 -3
- package/lib/types/client/scm/commit-model.d.ts +119 -0
- package/lib/types/client/store.d.ts +9 -3
- package/lib/types/client/vsc/kit.d.ts +16 -0
- package/lib/types/git-format.d.ts +5 -0
- package/lib/types/git-ops.d.ts +57 -2
- package/lib/types/index.d.ts +5 -0
- package/lib/types/pool.d.ts +9 -0
- package/lib/types/protocol.d.ts +23 -0
- package/lib/types/timeouts.d.ts +17 -2
- package/package.json +37 -37
- package/src/client/SettingsHosts.tsx +2 -1
- package/src/client/api.ts +61 -0
- package/src/client/composer.ts +11 -2
- package/src/client/exec-output.ts +129 -0
- package/src/client/i18n.ts +133 -4
- package/src/client/scm/CommitPane.tsx +726 -0
- package/src/client/scm/ExecPanel.tsx +124 -0
- package/src/client/scm/HistoryPanel.tsx +483 -104
- package/src/client/scm/ScmPanel.tsx +247 -9
- package/src/client/scm/actions.ts +93 -10
- package/src/client/scm/commit-model.ts +317 -0
- package/src/client/store.tsx +13 -6
- package/src/client/vsc/kit.tsx +71 -0
- package/src/client/vsc/vsc.css +647 -0
- package/src/git-format.ts +7 -3
- package/src/git-ops.ts +83 -7
- package/src/index.ts +10 -2
- package/src/pool.ts +84 -4
- package/src/protocol.ts +14 -0
- package/src/routes.ts +135 -1
- package/src/timeouts.ts +18 -2
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live output panel for the long network verbs (fetch / pull / push / sync).
|
|
3
|
+
*
|
|
4
|
+
* The unary exec route can only answer after the fact, so a 20-minute Gerrit
|
|
5
|
+
* push used to look like a frozen button. This panel is fed by the streaming
|
|
6
|
+
* exec route: it shows the git/remote output while it runs, a running clock,
|
|
7
|
+
* a stop button that really kills the remote process group, and keeps the full
|
|
8
|
+
* (tail-capped) log for copying after the fact.
|
|
9
|
+
*
|
|
10
|
+
* Rendered through a PORTAL on `document.body`: the SCM panel lives inside the
|
|
11
|
+
* sidebar's keep-alive slot, which the host hides with `display:none` when
|
|
12
|
+
* another rail view is picked — and a `position: fixed` descendant of a hidden
|
|
13
|
+
* ancestor disappears with it, taking the only cancel affordance of a running
|
|
14
|
+
* push along. The portal keeps the panel (and its stop button) reachable.
|
|
15
|
+
*/
|
|
16
|
+
import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
|
|
17
|
+
import { createPortal } from 'react-dom'
|
|
18
|
+
import {
|
|
19
|
+
IconButton,
|
|
20
|
+
IconChevronDown,
|
|
21
|
+
IconChevronUp,
|
|
22
|
+
IconClose,
|
|
23
|
+
IconCopy,
|
|
24
|
+
IconStop,
|
|
25
|
+
MiniSpinner,
|
|
26
|
+
copyText,
|
|
27
|
+
toast,
|
|
28
|
+
} from '../vsc/kit.tsx'
|
|
29
|
+
import { foldExecOutput, formatElapsed } from '../exec-output.ts'
|
|
30
|
+
|
|
31
|
+
export type ExecPanelStatus = 'running' | 'done' | 'error' | 'cancelled'
|
|
32
|
+
|
|
33
|
+
export interface ExecPanelState {
|
|
34
|
+
title: string
|
|
35
|
+
output: string
|
|
36
|
+
status: ExecPanelStatus
|
|
37
|
+
/** Wall-clock start, so the timer survives re-renders. */
|
|
38
|
+
startedAt: number
|
|
39
|
+
elapsedMs: number
|
|
40
|
+
/** One-line footer: exit code / duration / error. */
|
|
41
|
+
exitText?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ExecPanelProps {
|
|
45
|
+
state: ExecPanelState
|
|
46
|
+
onCancel: () => void
|
|
47
|
+
onClose: () => void
|
|
48
|
+
t: (key: string) => string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Status chip text: the running state also carries the clock. The exit-code
|
|
52
|
+
* detail belongs to the footer only, or it would be printed twice. */
|
|
53
|
+
function statusText(state: ExecPanelState, t: (key: string) => string): string {
|
|
54
|
+
if (state.status === 'running') return `${t('exec.running')} · ${formatElapsed(state.elapsedMs)}`
|
|
55
|
+
if (state.status === 'cancelled') return t('exec.cancelled')
|
|
56
|
+
return `${state.status === 'done' ? t('exec.done') : t('exec.failed')} · ${formatElapsed(state.elapsedMs)}`
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function panelBody(state: ExecPanelState, folded: string, t: (key: string) => string): string {
|
|
60
|
+
if (folded !== '') return folded
|
|
61
|
+
return state.status === 'running' ? t('exec.waiting') : t('exec.noOutput')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function ExecPanel(props: ExecPanelProps): ReactNode {
|
|
65
|
+
const { state, t } = props
|
|
66
|
+
/** Collapsed keeps the header (clock + stop) but gives the page its space back. */
|
|
67
|
+
const [collapsed, setCollapsed] = useState(false)
|
|
68
|
+
const bodyRef = useRef<HTMLPreElement>(null)
|
|
69
|
+
/** `\r` progress is folded once over the WHOLE log (see foldExecOutput). */
|
|
70
|
+
const folded = useMemo(() => foldExecOutput(state.output), [state.output])
|
|
71
|
+
/** Follow the tail while streaming, until the user scrolls up to read. */
|
|
72
|
+
const followRef = useRef(true)
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const element = bodyRef.current
|
|
75
|
+
if (element === null || collapsed || !followRef.current) return
|
|
76
|
+
element.scrollTop = element.scrollHeight
|
|
77
|
+
}, [folded, state.status, collapsed])
|
|
78
|
+
const onScroll = (): void => {
|
|
79
|
+
const element = bodyRef.current
|
|
80
|
+
if (element === null) return
|
|
81
|
+
followRef.current = element.scrollHeight - element.scrollTop - element.clientHeight < 24
|
|
82
|
+
}
|
|
83
|
+
if (typeof document === 'undefined') return null
|
|
84
|
+
return createPortal(
|
|
85
|
+
<div className="rvs-execpanel" role="dialog" aria-label={state.title}>
|
|
86
|
+
<div className="rvs-execpanel-head">
|
|
87
|
+
<span className="rvs-execpanel-title" title={state.title}>{state.title}</span>
|
|
88
|
+
<span className={`rvs-execpanel-status ${state.status}`}>{statusText(state, t)}</span>
|
|
89
|
+
<span className="rvs-execpanel-spacer" />
|
|
90
|
+
{state.status === 'running' && !collapsed && <MiniSpinner />}
|
|
91
|
+
<IconButton
|
|
92
|
+
title={t('exec.copy')}
|
|
93
|
+
onClick={() => { void copyText(folded).then(() => toast(t('exec.copied'))) }}
|
|
94
|
+
>
|
|
95
|
+
<IconCopy size={13} />
|
|
96
|
+
</IconButton>
|
|
97
|
+
<IconButton
|
|
98
|
+
title={collapsed ? t('exec.expand') : t('exec.collapse')}
|
|
99
|
+
onClick={() => { setCollapsed(value => !value) }}
|
|
100
|
+
>
|
|
101
|
+
{collapsed ? <IconChevronUp size={11} /> : <IconChevronDown size={11} />}
|
|
102
|
+
</IconButton>
|
|
103
|
+
{state.status === 'running'
|
|
104
|
+
? (
|
|
105
|
+
<IconButton title={`${t('exec.cancel')} — ${t('exec.cancelHint')}`} danger onClick={props.onCancel}>
|
|
106
|
+
<IconStop size={12} />
|
|
107
|
+
</IconButton>
|
|
108
|
+
)
|
|
109
|
+
: (
|
|
110
|
+
<IconButton title={t('common.close')} onClick={props.onClose}>
|
|
111
|
+
<IconClose size={11} />
|
|
112
|
+
</IconButton>
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
{collapsed ? null : (
|
|
116
|
+
<pre ref={bodyRef} className="rvs-execpanel-body" aria-label={t('exec.log')} onScroll={onScroll}>
|
|
117
|
+
{panelBody(state, folded, t)}
|
|
118
|
+
</pre>
|
|
119
|
+
)}
|
|
120
|
+
{state.exitText !== undefined && <div className="rvs-execpanel-foot">{state.exitText}</div>}
|
|
121
|
+
</div>,
|
|
122
|
+
document.body,
|
|
123
|
+
)
|
|
124
|
+
}
|