dsh-gufa-code 0.0.0-stage → 1.0.1

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 (158) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -2
  3. package/client.js +11548 -0
  4. package/cordis.patch.yml +8 -0
  5. package/docs/icon-180.png +0 -0
  6. package/docs/icon.svg +18 -0
  7. package/docs/market-submission.md +32 -0
  8. package/docs/screenshot-ide.jpg +0 -0
  9. package/host/assets.js +31 -0
  10. package/host/chat.js +523 -0
  11. package/host/context.js +438 -0
  12. package/host/dev.js +35 -0
  13. package/host/fs.js +306 -0
  14. package/host/git.js +532 -0
  15. package/host/project.js +305 -0
  16. package/host/ssh.js +54 -0
  17. package/host/terminal-pty.js +237 -0
  18. package/host/terminal.js +947 -0
  19. package/icon.svg +18 -0
  20. package/index.js +63 -0
  21. package/locale/en.json +121 -0
  22. package/locale/zh.json +121 -0
  23. package/package.json +67 -4
  24. package/screenshots.json +3 -0
  25. package/scripts/build-client.mjs +38 -0
  26. package/scripts/selftest-client.mjs +457 -0
  27. package/scripts/selftest-host.mjs +197 -0
  28. package/scripts/selftest-http.mjs +284 -0
  29. package/scripts/selftest-router.mjs +101 -0
  30. package/scripts/selftest-terminal-client.mjs +314 -0
  31. package/scripts/selftest-terminal-pty.mjs +190 -0
  32. package/scripts/selftest-terminal.mjs +351 -0
  33. package/scripts/test-chat-render.mjs +702 -0
  34. package/scripts/test-chat.mjs +761 -0
  35. package/scripts/test-editor.mjs +516 -0
  36. package/scripts/verify-workspace-sessions.mjs +135 -0
  37. package/src/client/00-core.js +1686 -0
  38. package/src/client/10-icons.js +205 -0
  39. package/src/client/20-editor.js +1318 -0
  40. package/src/client/30-terminal.js +1510 -0
  41. package/src/client/40-chat.js +1184 -0
  42. package/src/client/46-gitgraph.js +278 -0
  43. package/src/client/50-panels.js +916 -0
  44. package/src/client/55-backdrop.js +508 -0
  45. package/src/client/60-dock.js +662 -0
  46. package/src/client/85-shortcuts.js +516 -0
  47. package/src/client/90-app.js +2272 -0
  48. package/src/client/99-main.js +449 -0
  49. package/vendor/monaco/LICENSE +21 -0
  50. package/vendor/monaco/ThirdPartyNotices.txt +448 -0
  51. package/vendor/monaco/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
  52. package/vendor/monaco/vs/base/worker/workerMain.js +31 -0
  53. package/vendor/monaco/vs/basic-languages/abap/abap.js +10 -0
  54. package/vendor/monaco/vs/basic-languages/apex/apex.js +10 -0
  55. package/vendor/monaco/vs/basic-languages/azcli/azcli.js +10 -0
  56. package/vendor/monaco/vs/basic-languages/bat/bat.js +10 -0
  57. package/vendor/monaco/vs/basic-languages/bicep/bicep.js +11 -0
  58. package/vendor/monaco/vs/basic-languages/cameligo/cameligo.js +10 -0
  59. package/vendor/monaco/vs/basic-languages/clojure/clojure.js +10 -0
  60. package/vendor/monaco/vs/basic-languages/coffee/coffee.js +10 -0
  61. package/vendor/monaco/vs/basic-languages/cpp/cpp.js +10 -0
  62. package/vendor/monaco/vs/basic-languages/csharp/csharp.js +10 -0
  63. package/vendor/monaco/vs/basic-languages/csp/csp.js +10 -0
  64. package/vendor/monaco/vs/basic-languages/css/css.js +12 -0
  65. package/vendor/monaco/vs/basic-languages/cypher/cypher.js +10 -0
  66. package/vendor/monaco/vs/basic-languages/dart/dart.js +10 -0
  67. package/vendor/monaco/vs/basic-languages/dockerfile/dockerfile.js +10 -0
  68. package/vendor/monaco/vs/basic-languages/ecl/ecl.js +10 -0
  69. package/vendor/monaco/vs/basic-languages/elixir/elixir.js +10 -0
  70. package/vendor/monaco/vs/basic-languages/flow9/flow9.js +10 -0
  71. package/vendor/monaco/vs/basic-languages/freemarker2/freemarker2.js +12 -0
  72. package/vendor/monaco/vs/basic-languages/fsharp/fsharp.js +10 -0
  73. package/vendor/monaco/vs/basic-languages/go/go.js +10 -0
  74. package/vendor/monaco/vs/basic-languages/graphql/graphql.js +10 -0
  75. package/vendor/monaco/vs/basic-languages/handlebars/handlebars.js +10 -0
  76. package/vendor/monaco/vs/basic-languages/hcl/hcl.js +10 -0
  77. package/vendor/monaco/vs/basic-languages/html/html.js +10 -0
  78. package/vendor/monaco/vs/basic-languages/ini/ini.js +10 -0
  79. package/vendor/monaco/vs/basic-languages/java/java.js +10 -0
  80. package/vendor/monaco/vs/basic-languages/javascript/javascript.js +10 -0
  81. package/vendor/monaco/vs/basic-languages/julia/julia.js +10 -0
  82. package/vendor/monaco/vs/basic-languages/kotlin/kotlin.js +10 -0
  83. package/vendor/monaco/vs/basic-languages/less/less.js +11 -0
  84. package/vendor/monaco/vs/basic-languages/lexon/lexon.js +10 -0
  85. package/vendor/monaco/vs/basic-languages/liquid/liquid.js +10 -0
  86. package/vendor/monaco/vs/basic-languages/lua/lua.js +10 -0
  87. package/vendor/monaco/vs/basic-languages/m3/m3.js +10 -0
  88. package/vendor/monaco/vs/basic-languages/markdown/markdown.js +10 -0
  89. package/vendor/monaco/vs/basic-languages/mdx/mdx.js +10 -0
  90. package/vendor/monaco/vs/basic-languages/mips/mips.js +10 -0
  91. package/vendor/monaco/vs/basic-languages/msdax/msdax.js +10 -0
  92. package/vendor/monaco/vs/basic-languages/mysql/mysql.js +10 -0
  93. package/vendor/monaco/vs/basic-languages/objective-c/objective-c.js +10 -0
  94. package/vendor/monaco/vs/basic-languages/pascal/pascal.js +10 -0
  95. package/vendor/monaco/vs/basic-languages/pascaligo/pascaligo.js +10 -0
  96. package/vendor/monaco/vs/basic-languages/perl/perl.js +10 -0
  97. package/vendor/monaco/vs/basic-languages/pgsql/pgsql.js +10 -0
  98. package/vendor/monaco/vs/basic-languages/php/php.js +10 -0
  99. package/vendor/monaco/vs/basic-languages/pla/pla.js +10 -0
  100. package/vendor/monaco/vs/basic-languages/postiats/postiats.js +10 -0
  101. package/vendor/monaco/vs/basic-languages/powerquery/powerquery.js +10 -0
  102. package/vendor/monaco/vs/basic-languages/powershell/powershell.js +10 -0
  103. package/vendor/monaco/vs/basic-languages/protobuf/protobuf.js +11 -0
  104. package/vendor/monaco/vs/basic-languages/pug/pug.js +10 -0
  105. package/vendor/monaco/vs/basic-languages/python/python.js +10 -0
  106. package/vendor/monaco/vs/basic-languages/qsharp/qsharp.js +10 -0
  107. package/vendor/monaco/vs/basic-languages/r/r.js +10 -0
  108. package/vendor/monaco/vs/basic-languages/razor/razor.js +10 -0
  109. package/vendor/monaco/vs/basic-languages/redis/redis.js +10 -0
  110. package/vendor/monaco/vs/basic-languages/redshift/redshift.js +10 -0
  111. package/vendor/monaco/vs/basic-languages/restructuredtext/restructuredtext.js +10 -0
  112. package/vendor/monaco/vs/basic-languages/ruby/ruby.js +10 -0
  113. package/vendor/monaco/vs/basic-languages/rust/rust.js +10 -0
  114. package/vendor/monaco/vs/basic-languages/sb/sb.js +10 -0
  115. package/vendor/monaco/vs/basic-languages/scala/scala.js +10 -0
  116. package/vendor/monaco/vs/basic-languages/scheme/scheme.js +10 -0
  117. package/vendor/monaco/vs/basic-languages/scss/scss.js +12 -0
  118. package/vendor/monaco/vs/basic-languages/shell/shell.js +10 -0
  119. package/vendor/monaco/vs/basic-languages/solidity/solidity.js +10 -0
  120. package/vendor/monaco/vs/basic-languages/sophia/sophia.js +10 -0
  121. package/vendor/monaco/vs/basic-languages/sparql/sparql.js +10 -0
  122. package/vendor/monaco/vs/basic-languages/sql/sql.js +10 -0
  123. package/vendor/monaco/vs/basic-languages/st/st.js +10 -0
  124. package/vendor/monaco/vs/basic-languages/swift/swift.js +13 -0
  125. package/vendor/monaco/vs/basic-languages/systemverilog/systemverilog.js +10 -0
  126. package/vendor/monaco/vs/basic-languages/tcl/tcl.js +10 -0
  127. package/vendor/monaco/vs/basic-languages/twig/twig.js +10 -0
  128. package/vendor/monaco/vs/basic-languages/typescript/typescript.js +10 -0
  129. package/vendor/monaco/vs/basic-languages/typespec/typespec.js +10 -0
  130. package/vendor/monaco/vs/basic-languages/vb/vb.js +10 -0
  131. package/vendor/monaco/vs/basic-languages/wgsl/wgsl.js +307 -0
  132. package/vendor/monaco/vs/basic-languages/xml/xml.js +10 -0
  133. package/vendor/monaco/vs/basic-languages/yaml/yaml.js +10 -0
  134. package/vendor/monaco/vs/editor/editor.main.css +8 -0
  135. package/vendor/monaco/vs/editor/editor.main.js +798 -0
  136. package/vendor/monaco/vs/language/css/cssMode.js +13 -0
  137. package/vendor/monaco/vs/language/css/cssWorker.js +77 -0
  138. package/vendor/monaco/vs/language/html/htmlMode.js +13 -0
  139. package/vendor/monaco/vs/language/html/htmlWorker.js +454 -0
  140. package/vendor/monaco/vs/language/json/jsonMode.js +19 -0
  141. package/vendor/monaco/vs/language/json/jsonWorker.js +42 -0
  142. package/vendor/monaco/vs/language/typescript/tsMode.js +20 -0
  143. package/vendor/monaco/vs/language/typescript/tsWorker.js +51328 -0
  144. package/vendor/monaco/vs/loader.js +11 -0
  145. package/vendor/monaco/vs/nls.messages.de.js +21 -0
  146. package/vendor/monaco/vs/nls.messages.es.js +21 -0
  147. package/vendor/monaco/vs/nls.messages.fr.js +19 -0
  148. package/vendor/monaco/vs/nls.messages.it.js +19 -0
  149. package/vendor/monaco/vs/nls.messages.ja.js +21 -0
  150. package/vendor/monaco/vs/nls.messages.ko.js +19 -0
  151. package/vendor/monaco/vs/nls.messages.ru.js +21 -0
  152. package/vendor/monaco/vs/nls.messages.zh-cn.js +21 -0
  153. package/vendor/monaco/vs/nls.messages.zh-tw.js +19 -0
  154. package/vendor/xterm/LICENSE +21 -0
  155. package/vendor/xterm/LICENSE.addon-fit +19 -0
  156. package/vendor/xterm/addon-fit.js +2 -0
  157. package/vendor/xterm/xterm.css +285 -0
  158. package/vendor/xterm/xterm.js +2 -0
@@ -0,0 +1,278 @@
1
+ /**
2
+ * 46-gitgraph:Git Graph 视图(类似 Cursor / Git Graph 扩展的提交图)。
3
+ * 以编辑器标签页的形式打开;纯前端计算泳道布局,Host 只提供 git log 数据。
4
+ */
5
+
6
+ /** 泳道颜色(跟随强调色,深浅主题都可读)。 */
7
+ const GUFA_GRAPH_COLORS = ['#e06c75', '#61afef', '#98c379', '#e5c07b', '#c678dd', '#56b6c2', '#d19a66', '#7f9cf5']
8
+
9
+ /**
10
+ * 计算提交图泳道布局(纯函数,供测试)。
11
+ * @param {Array<{hash:string, parents:string[]}>} commits 新→旧
12
+ * @returns {{rows: Array<object>, laneCount: number, hashRow: Map<string, number>}}
13
+ */
14
+ G.gitGraphLayout = function gitGraphLayout(commits) {
15
+ const lanes = []
16
+ const rows = []
17
+ const hashRow = new Map()
18
+ for (const commit of commits) {
19
+ let lane = lanes.indexOf(commit.hash)
20
+ if (lane === -1) {
21
+ const free = lanes.indexOf(null)
22
+ lane = free === -1 ? lanes.length : free
23
+ lanes[lane] = commit.hash
24
+ }
25
+ rows.push({ commit, lane, lanesBefore: lanes.slice() })
26
+ hashRow.set(commit.hash, rows.length - 1)
27
+ lanes[lane] = commit.parents.length > 0 ? commit.parents[0] : null
28
+ let insertAt = lane + 1
29
+ for (const parent of commit.parents.slice(1)) {
30
+ if (lanes.indexOf(parent) === -1) {
31
+ lanes.splice(insertAt, 0, parent)
32
+ insertAt += 1
33
+ }
34
+ }
35
+ while (lanes.length > 0 && lanes[lanes.length - 1] === null) lanes.pop()
36
+ }
37
+ for (let index = 0; index < rows.length; index += 1) {
38
+ rows[index].lanesAfter = index + 1 < rows.length ? rows[index + 1].lanesBefore.slice() : []
39
+ }
40
+ let laneCount = 1
41
+ for (const row of rows) {
42
+ laneCount = Math.max(laneCount, row.lane + 1, row.lanesBefore.length, row.lanesAfter.length)
43
+ }
44
+ return { rows, laneCount, hashRow }
45
+ }
46
+
47
+ /** 提交行(含泳道 SVG)。 */
48
+ function gufaGraphRow(props) {
49
+ const t = G.useT()
50
+ const row = props.row
51
+ const laneWidth = 14
52
+ const rowHeight = 26
53
+ const width = Math.max(laneWidth, props.laneCount * laneWidth)
54
+ const cx = (lane) => lane * laneWidth + laneWidth / 2
55
+ const cy = rowHeight / 2
56
+ const color = (lane) => GUFA_GRAPH_COLORS[lane % GUFA_GRAPH_COLORS.length]
57
+ const elements = []
58
+ // 上方泳道竖线
59
+ for (let lane = 0; lane < row.lanesBefore.length; lane += 1) {
60
+ if (row.lanesBefore[lane] === null || row.lanesBefore[lane] === undefined) continue
61
+ elements.push(h('line', { key: `b:${lane}`, x1: cx(lane), y1: 0, x2: cx(lane), y2: cy, stroke: color(lane), strokeWidth: 1.6, opacity: 0.85 }))
62
+ }
63
+ // 下方泳道竖线
64
+ for (let lane = 0; lane < row.lanesAfter.length; lane += 1) {
65
+ if (row.lanesAfter[lane] === null || row.lanesAfter[lane] === undefined) continue
66
+ elements.push(h('line', { key: `a:${lane}`, x1: cx(lane), y1: cy, x2: cx(lane), y2: rowHeight, stroke: color(lane), strokeWidth: 1.6, opacity: 0.85 }))
67
+ }
68
+ // 提交 → 父提交连线
69
+ const parents = row.commit.parents ?? []
70
+ parents.forEach((parent, index) => {
71
+ let targetLane = -1
72
+ const targetRow = props.hashRow.get(parent)
73
+ if (targetRow !== undefined) targetLane = props.rows[targetRow].lane
74
+ else targetLane = row.lanesAfter.indexOf(parent)
75
+ if (targetLane === -1) targetLane = row.lane
76
+ if (targetLane === row.lane) return
77
+ const x1 = cx(row.lane)
78
+ const x2 = cx(targetLane)
79
+ elements.push(
80
+ h('path', {
81
+ key: `p:${index}`,
82
+ d: `M ${x1} ${cy} C ${x1} ${cy + 10}, ${x2} ${cy + 8}, ${x2} ${rowHeight}`,
83
+ fill: 'none',
84
+ stroke: color(targetLane),
85
+ strokeWidth: 1.6,
86
+ opacity: 0.9,
87
+ }),
88
+ )
89
+ })
90
+ // 提交节点
91
+ elements.push(h('circle', { key: 'dot', cx: cx(row.lane), cy, r: 3.6, fill: color(row.lane), stroke: 'var(--gufa-bg, #111)', strokeWidth: 1.2 }))
92
+ return h(
93
+ 'div',
94
+ {
95
+ className: 'gufa-graph-row',
96
+ 'data-selected': props.selected === true ? '' : undefined,
97
+ onClick: () => props.onSelect(row.commit),
98
+ title: row.commit.subject,
99
+ },
100
+ h('span', { className: 'gufa-graph-svg', style: { width, minWidth: width } }, h('svg', { width, height: rowHeight }, ...elements)),
101
+ h('span', { className: 'gufa-graph-refs' }, ...(row.commit.refs ?? []).slice(0, 3).map((ref) => {
102
+ const current = ref.startsWith('HEAD -> ')
103
+ const clean = current ? ref.slice('HEAD -> '.length) : ref
104
+ return h('span', { key: ref, className: 'gufa-graph-ref', 'data-current': current || clean === props.branch ? '' : undefined }, current ? `HEAD -> ${clean}` : clean)
105
+ })),
106
+ h('span', { className: 'gufa-graph-subject' }, row.commit.subject),
107
+ h('span', { className: 'gufa-graph-meta' }, row.commit.author),
108
+ h('span', { className: 'gufa-graph-meta' }, row.commit.when),
109
+ )
110
+ }
111
+
112
+ G.GitGraphView = function GitGraphView(props) {
113
+ const t = G.useT()
114
+ const root = typeof props.root === 'string' && props.root !== '' ? props.root : null
115
+ const [state, setState] = useState({ status: 'loading', commits: [], branch: '', error: '' })
116
+ const [selected, setSelected] = useState(null)
117
+ const [detail, setDetail] = useState({ status: 'idle', files: [], error: '' })
118
+ const [showAll, setShowAll] = useState(true)
119
+ const [copyTip, setCopyTip] = useState('')
120
+
121
+ useEffect(() => {
122
+ if (root === null) {
123
+ setState({ status: 'ready', commits: [], branch: '', error: '' })
124
+ return undefined
125
+ }
126
+ let cancelled = false
127
+ setState((current) => ({ ...current, status: current.commits.length === 0 ? 'loading' : 'refreshing', error: '' }))
128
+ void G.api('git.graph', { root, all: showAll })
129
+ .then((value) => {
130
+ if (cancelled) return
131
+ setState({
132
+ status: 'ready',
133
+ commits: Array.isArray(value?.commits) ? value.commits : [],
134
+ branch: typeof value?.branch === 'string' ? value.branch : '',
135
+ error: typeof value?.error === 'string' ? value.error : '',
136
+ })
137
+ })
138
+ .catch((error) => {
139
+ if (cancelled) return
140
+ setState({ status: 'error', commits: [], branch: '', error: G.errorText(error) })
141
+ })
142
+ return () => {
143
+ cancelled = true
144
+ }
145
+ }, [root, props.refreshToken, showAll])
146
+
147
+ /* 选中提交后拉取改动文件列表 */
148
+ useEffect(() => {
149
+ if (root === null || selected === null) {
150
+ setDetail({ status: 'idle', files: [], error: '' })
151
+ return undefined
152
+ }
153
+ let cancelled = false
154
+ setDetail({ status: 'loading', files: [], error: '' })
155
+ void G.api('git.commitFiles', { root, hash: selected.hash })
156
+ .then((value) => {
157
+ if (cancelled) return
158
+ setDetail({ status: 'ready', files: Array.isArray(value?.files) ? value.files : [], error: '' })
159
+ })
160
+ .catch((error) => {
161
+ if (cancelled) return
162
+ setDetail({ status: 'error', files: [], error: G.errorText(error) })
163
+ })
164
+ return () => {
165
+ cancelled = true
166
+ }
167
+ }, [root, selected])
168
+
169
+ const layout = useMemo(() => G.gitGraphLayout(state.commits), [state.commits])
170
+
171
+ const copyHash = (hash) => {
172
+ try {
173
+ void navigator.clipboard?.writeText(hash)
174
+ setCopyTip(t('graph.copied'))
175
+ window.setTimeout(() => setCopyTip(''), 1500)
176
+ } catch {
177
+ /* ignore */
178
+ }
179
+ }
180
+
181
+ const header = h(
182
+ 'div',
183
+ { className: 'gufa-graph-head' },
184
+ h(G.Icon, { name: 'graph', size: 14 }),
185
+ h('span', { className: 'gufa-panel-head-title' }, t('graph.title')),
186
+ state.branch !== '' ? h('span', { className: 'gufa-graph-branch' }, h(G.Icon, { name: 'branch', size: 12 }), state.branch) : null,
187
+ h('div', { style: { flex: '1 1 auto' } }),
188
+ copyTip !== '' ? h('span', { className: 'gufa-hint' }, copyTip) : null,
189
+ h(G.Button, { icon: 'branch', title: t('graph.toggleAll'), onClick: () => setShowAll((value) => value !== true) }, showAll === true ? t('graph.allBranches') : t('graph.currentBranch')),
190
+ h(G.IconButton, { title: t('graph.refresh'), onClick: () => props.onRefresh?.() }, h(G.Icon, { name: 'refresh', size: 13 })),
191
+ )
192
+
193
+ if (root === null) {
194
+ return h('div', { className: 'gufa-graph-view' }, header, h(G.Empty, { icon: 'branch', title: t('graph.noProject') }))
195
+ }
196
+ if (state.status === 'loading') {
197
+ return h('div', { className: 'gufa-graph-view' }, header, h('div', { className: 'gufa-graph-loading' }, h(G.Spinner), t('common.loading')))
198
+ }
199
+ if (state.error !== '') {
200
+ return h('div', { className: 'gufa-graph-view' }, header, h('div', { className: 'gufa-error-banner', style: { margin: 10 } }, state.error))
201
+ }
202
+ if (state.commits.length === 0) {
203
+ return h('div', { className: 'gufa-graph-view' }, header, h(G.Empty, { icon: 'branch', title: t('graph.noCommits') }))
204
+ }
205
+
206
+ const aside = selected === null
207
+ ? h('div', { className: 'gufa-graph-aside gufa-graph-aside-empty' }, t('graph.selectCommitHint'))
208
+ : h(
209
+ 'div',
210
+ { className: 'gufa-graph-aside' },
211
+ h(
212
+ 'div',
213
+ { className: 'gufa-graph-commit' },
214
+ h('div', { className: 'gufa-graph-commit-subject' }, selected.subject),
215
+ h('div', { className: 'gufa-graph-commit-meta' }, h('code', null, selected.short), h('span', null, selected.author), h('span', null, selected.when)),
216
+ h(
217
+ 'div',
218
+ { className: 'gufa-graph-commit-actions' },
219
+ h(G.Button, { icon: 'files', onClick: () => copyHash(selected.hash) }, t('graph.copyHash')),
220
+ selected.refs !== null && selected.refs !== undefined && selected.refs.length > 0
221
+ ? h('span', { className: 'gufa-graph-refs' }, ...selected.refs.slice(0, 4).map((ref) => h('span', { key: ref, className: 'gufa-graph-ref' }, ref)))
222
+ : null,
223
+ ),
224
+ ),
225
+ h('div', { className: 'gufa-graph-files-head' }, t('graph.changedFiles'), detail.status === 'ready' ? h('span', { className: 'gufa-graph-files-count' }, String(detail.files.length)) : null),
226
+ detail.status === 'loading'
227
+ ? h('div', { className: 'gufa-graph-loading', style: { height: 80 } }, h(G.Spinner), t('common.loading'))
228
+ : detail.status === 'error'
229
+ ? h('div', { className: 'gufa-error-banner', style: { margin: 8 } }, detail.error)
230
+ : detail.files.length === 0
231
+ ? h('div', { className: 'gufa-hint', style: { padding: '8px 12px' } }, t('graph.noChangedFiles'))
232
+ : h(
233
+ 'div',
234
+ { className: 'gufa-graph-files' },
235
+ ...detail.files.map((file) =>
236
+ h(
237
+ 'button',
238
+ {
239
+ key: `${file.status}:${file.path}`,
240
+ type: 'button',
241
+ className: 'gufa-graph-file',
242
+ title: t('graph.openFileDiff'),
243
+ onClick: () => props.onOpenCommitFile?.(selected, file),
244
+ },
245
+ h('span', { className: 'gufa-graph-file-status', 'data-kind': file.status }, file.status),
246
+ h('span', { className: 'gufa-graph-file-path' }, file.path),
247
+ ),
248
+ ),
249
+ ),
250
+ )
251
+
252
+ return h(
253
+ 'div',
254
+ { className: 'gufa-graph-view' },
255
+ header,
256
+ h(
257
+ 'div',
258
+ { className: 'gufa-graph-split' },
259
+ h(
260
+ 'div',
261
+ { className: 'gufa-graph-body gufa-scroll' },
262
+ ...layout.rows.map((row) =>
263
+ h(gufaGraphRow, {
264
+ key: row.commit.hash,
265
+ row,
266
+ rows: layout.rows,
267
+ hashRow: layout.hashRow,
268
+ laneCount: layout.laneCount,
269
+ branch: state.branch,
270
+ selected: selected !== null && selected.hash === row.commit.hash,
271
+ onSelect: (commit) => setSelected(commit),
272
+ }),
273
+ ),
274
+ ),
275
+ aside,
276
+ ),
277
+ )
278
+ }