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.
Files changed (44) hide show
  1. package/README.en.md +13 -10
  2. package/README.md +14 -9
  3. package/cordis.patch.yml +10 -5
  4. package/lib/client.js +3319 -1093
  5. package/lib/index.js +211 -9
  6. package/lib/types/client/api.d.ts +12 -0
  7. package/lib/types/client/composer.d.ts +5 -1
  8. package/lib/types/client/exec-output.d.ts +58 -0
  9. package/lib/types/client/i18n.d.ts +1 -1
  10. package/lib/types/client/scm/CommitPane.d.ts +73 -0
  11. package/lib/types/client/scm/ExecPanel.d.ts +34 -0
  12. package/lib/types/client/scm/HistoryPanel.d.ts +12 -4
  13. package/lib/types/client/scm/actions.d.ts +50 -3
  14. package/lib/types/client/scm/commit-model.d.ts +119 -0
  15. package/lib/types/client/store.d.ts +9 -3
  16. package/lib/types/client/vsc/kit.d.ts +16 -0
  17. package/lib/types/git-format.d.ts +5 -0
  18. package/lib/types/git-ops.d.ts +57 -2
  19. package/lib/types/index.d.ts +5 -0
  20. package/lib/types/pool.d.ts +9 -0
  21. package/lib/types/protocol.d.ts +23 -0
  22. package/lib/types/timeouts.d.ts +17 -2
  23. package/package.json +37 -37
  24. package/src/client/SettingsHosts.tsx +2 -1
  25. package/src/client/api.ts +61 -0
  26. package/src/client/composer.ts +11 -2
  27. package/src/client/exec-output.ts +129 -0
  28. package/src/client/i18n.ts +133 -4
  29. package/src/client/scm/CommitPane.tsx +726 -0
  30. package/src/client/scm/ExecPanel.tsx +124 -0
  31. package/src/client/scm/HistoryPanel.tsx +483 -104
  32. package/src/client/scm/ScmPanel.tsx +247 -9
  33. package/src/client/scm/actions.ts +93 -10
  34. package/src/client/scm/commit-model.ts +317 -0
  35. package/src/client/store.tsx +13 -6
  36. package/src/client/vsc/kit.tsx +71 -0
  37. package/src/client/vsc/vsc.css +647 -0
  38. package/src/git-format.ts +7 -3
  39. package/src/git-ops.ts +83 -7
  40. package/src/index.ts +10 -2
  41. package/src/pool.ts +84 -4
  42. package/src/protocol.ts +14 -0
  43. package/src/routes.ts +135 -1
  44. 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
+ }