minecraft-dev-starter 0.0.0-stage → 0.1.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.
- package/AGENTS.md +166 -0
- package/README.md +124 -2
- package/cordis.patch.yml +7 -0
- package/lib/client.js +1521 -0
- package/package.json +54 -4
- package/src/client/Capsule.js +158 -0
- package/src/client/DetailTab.js +228 -0
- package/src/client/Flyout.js +191 -0
- package/src/client/LogTab.js +180 -0
- package/src/client/Skeleton.js +18 -0
- package/src/client/api.js +33 -0
- package/src/client/debug.js +27 -0
- package/src/client/format.js +89 -0
- package/src/client/h.js +8 -0
- package/src/client/icons.js +147 -0
- package/src/client/index.js +205 -0
- package/src/client/runtime.js +41 -0
- package/src/client/selection.js +154 -0
- package/src/client/state.js +233 -0
- package/src/client/store.js +34 -0
- package/src/client/styles.js +329 -0
- package/src/index.js +193 -0
- package/src/lib/api.js +186 -0
- package/src/lib/argv.js +66 -0
- package/src/lib/detect.js +460 -0
- package/src/lib/gradle.js +295 -0
- package/src/lib/java.js +204 -0
- package/src/lib/launches.js +495 -0
- package/src/lib/metadata.js +175 -0
- package/src/lib/scanner.js +131 -0
- package/src/lib/state.js +91 -0
- package/src/lib/text.js +92 -0
- package/src/lib/toml.js +281 -0
- package/src/tools.js +291 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 右侧栏「运行日志」标签:按时间列出这个工作区跑过的实例,默认最新一条。
|
|
3
|
+
*
|
|
4
|
+
* 日志本体在宿主侧已经按行解码(UTF-8 / GBK 回退),这里只负责展示与跟随。
|
|
5
|
+
* @module minecraft-dev-starter/client/LogTab
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useEffect, useRef, useState } from 'react'
|
|
9
|
+
import { call } from './api.js'
|
|
10
|
+
import { h } from './h.js'
|
|
11
|
+
import { IconRefresh, IconWarning } from './icons.js'
|
|
12
|
+
import { launchStatusText, pickLogTarget } from './selection.js'
|
|
13
|
+
import { Skeleton } from './Skeleton.js'
|
|
14
|
+
import { loadLaunches, selectedProject, useLaunches } from './state.js'
|
|
15
|
+
|
|
16
|
+
/** 关掉标签再打开时,尽量回到上次看的那条。 */
|
|
17
|
+
let lastLogId
|
|
18
|
+
|
|
19
|
+
export function LogTab() {
|
|
20
|
+
const launches = useLaunches(1500)
|
|
21
|
+
const project = selectedProject()
|
|
22
|
+
const [logId, setLogId] = useState(/** @type {string | undefined} */ (lastLogId))
|
|
23
|
+
const [log, setLog] = useState(/** @type {any} */ (undefined))
|
|
24
|
+
const [follow, setFollow] = useState(true)
|
|
25
|
+
const [pinned, setPinned] = useState(false)
|
|
26
|
+
const body = useRef(/** @type {HTMLPreElement | null} */ (null))
|
|
27
|
+
|
|
28
|
+
// 历史按当前工程过滤;工程还没识别出来时退回全部
|
|
29
|
+
const projectRoot = project === undefined ? undefined : String(project.root).toLowerCase()
|
|
30
|
+
const scoped = launches.list.filter((entry) => projectRoot === undefined || String(entry.root).toLowerCase() === projectRoot)
|
|
31
|
+
const history = scoped.length > 0 ? scoped : launches.list
|
|
32
|
+
|
|
33
|
+
// 跟随:有实例在跑就跟它;用户手动选过就保持;否则跟当前/最新
|
|
34
|
+
const target = pickLogTarget(history, logId, pinned)
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (target === undefined || target === logId) return
|
|
37
|
+
lastLogId = target
|
|
38
|
+
setLogId(target)
|
|
39
|
+
setLog(undefined)
|
|
40
|
+
// 新实例跑起来时解除手动固定
|
|
41
|
+
if (history.some((entry) => entry.id === target && (entry.status === 'starting' || entry.status === 'running'))) {
|
|
42
|
+
setPinned(false)
|
|
43
|
+
}
|
|
44
|
+
}, [target, logId, history])
|
|
45
|
+
|
|
46
|
+
// 拉日志:运行中的实例每 1.5s 刷一次,结束的不再轮询
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (logId === undefined) {
|
|
49
|
+
setLog(undefined)
|
|
50
|
+
return undefined
|
|
51
|
+
}
|
|
52
|
+
let cancelled = false
|
|
53
|
+
const pull = async () => {
|
|
54
|
+
try {
|
|
55
|
+
const result = await call('launch.logs', { id: logId, offset: 0, limit: 800 })
|
|
56
|
+
if (!cancelled) setLog(result)
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (!cancelled) setLog({ error: error instanceof Error ? error.message : String(error) })
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
void pull()
|
|
62
|
+
const timer = setInterval(() => {
|
|
63
|
+
if (log?.status === 'starting' || log?.status === 'running' || log === undefined) void pull()
|
|
64
|
+
}, 1500)
|
|
65
|
+
return () => {
|
|
66
|
+
cancelled = true
|
|
67
|
+
clearInterval(timer)
|
|
68
|
+
}
|
|
69
|
+
}, [logId, log?.status])
|
|
70
|
+
|
|
71
|
+
// 跟随尾部
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!follow) return
|
|
74
|
+
const element = body.current
|
|
75
|
+
if (element !== null) element.scrollTop = element.scrollHeight
|
|
76
|
+
}, [log?.lines?.length, follow])
|
|
77
|
+
|
|
78
|
+
if (history.length === 0) {
|
|
79
|
+
return h(
|
|
80
|
+
'div',
|
|
81
|
+
{ className: 'mcdev-detail' },
|
|
82
|
+
h('div', { className: 'mcdev-title' }, '运行日志'),
|
|
83
|
+
h('div', { className: 'mcdev-muted', style: { marginTop: 8 } }, '还没有运行记录。用标题栏胶囊的 ▶ 启动一次。'),
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const running = log?.status === 'starting' || log?.status === 'running'
|
|
88
|
+
const tone =
|
|
89
|
+
log?.error !== undefined
|
|
90
|
+
? 'error'
|
|
91
|
+
: running
|
|
92
|
+
? (log?.status === 'starting' ? 'warn' : 'ok')
|
|
93
|
+
: log?.outcome === 'normal'
|
|
94
|
+
? 'idle'
|
|
95
|
+
: 'error'
|
|
96
|
+
|
|
97
|
+
return h(
|
|
98
|
+
'div',
|
|
99
|
+
{ className: 'mcdev-logtab' },
|
|
100
|
+
h(
|
|
101
|
+
'div',
|
|
102
|
+
{ className: 'mcdev-log-head' },
|
|
103
|
+
h(
|
|
104
|
+
'select',
|
|
105
|
+
{
|
|
106
|
+
className: 'mcdev-select',
|
|
107
|
+
value: logId ?? '',
|
|
108
|
+
onChange: (event) => {
|
|
109
|
+
// 手动选过就固定住,等下一次有新实例跑起来再自动跟随
|
|
110
|
+
setPinned(true)
|
|
111
|
+
lastLogId = event.target.value
|
|
112
|
+
setLogId(event.target.value)
|
|
113
|
+
setLog(undefined)
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
history.map((entry) => h('option', { key: entry.id, value: entry.id }, optionLabel(entry))),
|
|
117
|
+
),
|
|
118
|
+
h('span', { className: 'mcdev-dot', 'data-state': tone }),
|
|
119
|
+
h('span', { className: 'mcdev-hint' }, statusText(log, running)),
|
|
120
|
+
h(
|
|
121
|
+
'label',
|
|
122
|
+
{ className: 'mcdev-follow' },
|
|
123
|
+
h('input', { type: 'checkbox', checked: follow, onChange: (event) => setFollow(event.target.checked) }),
|
|
124
|
+
'跟随',
|
|
125
|
+
),
|
|
126
|
+
),
|
|
127
|
+
log?.diagnosis === undefined
|
|
128
|
+
? undefined
|
|
129
|
+
: h(
|
|
130
|
+
'div',
|
|
131
|
+
{ className: 'mcdev-diagnosis' },
|
|
132
|
+
h(
|
|
133
|
+
'div',
|
|
134
|
+
{ className: 'mcdev-diagnosis-hint mcdev-icon-text' },
|
|
135
|
+
h('span', { className: 'mcdev-icon' }, h(IconWarning, undefined)),
|
|
136
|
+
log.diagnosis.hint,
|
|
137
|
+
),
|
|
138
|
+
h('div', { className: 'mcdev-hint mcdev-code' }, log.diagnosis.line),
|
|
139
|
+
),
|
|
140
|
+
log?.error === undefined
|
|
141
|
+
? log === undefined && running
|
|
142
|
+
? h(Skeleton, { rows: 4, hint: '正在读取日志…' })
|
|
143
|
+
: h('pre', { ref: body, className: 'mcdev-logs mcdev-logs-full' }, (log?.lines ?? []).join('\n') || '(还没有输出)')
|
|
144
|
+
: h('div', { className: 'mcdev-error', style: { marginTop: 8 } }, log.error),
|
|
145
|
+
h(
|
|
146
|
+
'div',
|
|
147
|
+
{ style: { marginTop: 8 } },
|
|
148
|
+
h(
|
|
149
|
+
'button',
|
|
150
|
+
{ type: 'button', className: 'mcdev-mini', onClick: () => void loadLaunches() },
|
|
151
|
+
h('span', { className: 'mcdev-icon' }, h(IconRefresh, { size: 12 })),
|
|
152
|
+
'刷新实例列表',
|
|
153
|
+
),
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @param {Record<string, any>} entry
|
|
160
|
+
* @returns {string}
|
|
161
|
+
*/
|
|
162
|
+
function optionLabel(entry) {
|
|
163
|
+
const time = new Date(entry.startedAt)
|
|
164
|
+
const clock = Number.isNaN(time.getTime())
|
|
165
|
+
? '--:--'
|
|
166
|
+
: `${String(time.getHours()).padStart(2, '0')}:${String(time.getMinutes()).padStart(2, '0')}`
|
|
167
|
+
const mark = entry.status === 'running' || entry.status === 'starting' ? '●' : entry.outcome === 'normal' ? '○' : '✕'
|
|
168
|
+
return `${mark} ${entry.label} · ${clock} · ${launchStatusText(entry)}`
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @param {any} log
|
|
173
|
+
* @param {boolean} running
|
|
174
|
+
* @returns {string}
|
|
175
|
+
*/
|
|
176
|
+
function statusText(log, running) {
|
|
177
|
+
if (log === undefined) return '加载中…'
|
|
178
|
+
void running
|
|
179
|
+
return launchStatusText({ status: log.status, outcome: log.outcome, exitCode: log.exitCode })
|
|
180
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 骨架占位:扫描或加载中时给几行灰条,避免面板一片空白。
|
|
3
|
+
* @module minecraft-dev-starter/client/Skeleton
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { h } from './h.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {{ rows?: number, hint?: string }} [props]
|
|
10
|
+
*/
|
|
11
|
+
export function Skeleton({ rows = 3, hint } = {}) {
|
|
12
|
+
return h(
|
|
13
|
+
'div',
|
|
14
|
+
{ className: 'mcdev-skeleton', 'aria-busy': 'true' },
|
|
15
|
+
Array.from({ length: rows }, (_value, index) => h('div', { key: index, className: 'mcdev-skeleton-row' })),
|
|
16
|
+
hint === undefined ? undefined : h('div', { className: 'mcdev-hint' }, hint),
|
|
17
|
+
)
|
|
18
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 客户端 ↔ 宿主的唯一通道。
|
|
3
|
+
*
|
|
4
|
+
* 必须用**相对路径**:桌面版外壳的页面是 `dsh-app://app/`,由外壳把非静态路径
|
|
5
|
+
* 转发给宿主;写成绝对 origin 会变成跨域(宿主不返回 CORS 头)。
|
|
6
|
+
* 依据见 doc/调研-DSH插件机制.md §2.2。
|
|
7
|
+
* @module minecraft-dev-starter/client/api
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @template T
|
|
12
|
+
* @param {string} method
|
|
13
|
+
* @param {unknown} [payload]
|
|
14
|
+
* @returns {Promise<T>}
|
|
15
|
+
*/
|
|
16
|
+
export async function call(method, payload) {
|
|
17
|
+
const response = await fetch(`/mcdev/api/${method}`, {
|
|
18
|
+
method: 'POST',
|
|
19
|
+
cache: 'no-store',
|
|
20
|
+
headers: { 'content-type': 'application/json' },
|
|
21
|
+
body: JSON.stringify(payload ?? {}),
|
|
22
|
+
})
|
|
23
|
+
let body
|
|
24
|
+
try {
|
|
25
|
+
body = await response.json()
|
|
26
|
+
} catch {
|
|
27
|
+
throw new Error(`HTTP ${response.status}:返回不是 JSON`)
|
|
28
|
+
}
|
|
29
|
+
if (body?.ok !== true) {
|
|
30
|
+
throw new Error(body?.error?.message ?? `HTTP ${response.status}`)
|
|
31
|
+
}
|
|
32
|
+
return body.result
|
|
33
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 诊断上报:只在开发期用来回答"客户端到底走到哪一步"。
|
|
3
|
+
* 生产不依赖它,失败也不影响功能。
|
|
4
|
+
* @module minecraft-dev-starter/client/debug
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** 每次页面加载只报一次,避免刷屏。 */
|
|
8
|
+
const reported = new Set()
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {string} event
|
|
12
|
+
* @param {Record<string, unknown>} [detail]
|
|
13
|
+
*/
|
|
14
|
+
export function report(event, detail = {}) {
|
|
15
|
+
if (reported.has(event)) return
|
|
16
|
+
reported.add(event)
|
|
17
|
+
try {
|
|
18
|
+
fetch('/mcdev/api/client.report', {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
cache: 'no-store',
|
|
21
|
+
headers: { 'content-type': 'application/json' },
|
|
22
|
+
body: JSON.stringify({ event, detail }),
|
|
23
|
+
}).catch(() => {})
|
|
24
|
+
} catch {
|
|
25
|
+
/* 上报失败无所谓 */
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 展示层的纯格式化:把画像里的枚举翻成界面文案。不读时钟、不发请求。
|
|
3
|
+
* @module minecraft-dev-starter/client/format
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 面板名。比"MC 工程"更说明用途:这是给模组开发用的运行面板。
|
|
8
|
+
*/
|
|
9
|
+
export const PANEL_TITLE = 'Mod Dev'
|
|
10
|
+
|
|
11
|
+
const LOADER_LABELS = {
|
|
12
|
+
fabric: 'Fabric',
|
|
13
|
+
forge: 'Forge',
|
|
14
|
+
neoforge: 'NeoForge',
|
|
15
|
+
paper: 'Paper',
|
|
16
|
+
spigot: 'Spigot',
|
|
17
|
+
architectury: 'Architectury',
|
|
18
|
+
unknown: '未识别',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const KIND_LABELS = {
|
|
22
|
+
mod: '模组',
|
|
23
|
+
'server-plugin': '服务端插件',
|
|
24
|
+
'multi-loader': '多平台',
|
|
25
|
+
unknown: '未知',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string | undefined} loader
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
export function loaderLabel(loader) {
|
|
33
|
+
return LOADER_LABELS[loader ?? 'unknown'] ?? loader ?? '未识别'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string | undefined} kind
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
export function kindLabel(kind) {
|
|
41
|
+
return KIND_LABELS[kind ?? 'unknown'] ?? kind ?? '未知'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 胶囊上的短标签:单工程显示 loader + MC 版本,多工程显示数量。
|
|
46
|
+
* @param {Array<Record<string, any>>} projects
|
|
47
|
+
* @param {string | undefined} selectedRoot
|
|
48
|
+
* @returns {string}
|
|
49
|
+
*/
|
|
50
|
+
export function capsuleLabel(projects, selectedRoot) {
|
|
51
|
+
if (projects.length === 0) return '未发现 MC 工程'
|
|
52
|
+
const project = projects.find((entry) => entry.root === selectedRoot) ?? projects[0]
|
|
53
|
+
if (projects.length > 1) {
|
|
54
|
+
return `${loaderLabel(project?.loader)} ${project?.minecraftVersion ?? ''} · ${projects.length}`.trim()
|
|
55
|
+
}
|
|
56
|
+
const version = project?.minecraftVersion
|
|
57
|
+
const loader = loaderLabel(project?.loader)
|
|
58
|
+
return version === undefined ? loader : `${loader} ${version}`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {Record<string, any> | undefined} project
|
|
63
|
+
* @returns {string}
|
|
64
|
+
*/
|
|
65
|
+
export function projectTitle(project) {
|
|
66
|
+
if (project === undefined) return '—'
|
|
67
|
+
return project.modName ?? project.name ?? project.root
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {Record<string, any> | undefined} project
|
|
72
|
+
* @returns {string}
|
|
73
|
+
*/
|
|
74
|
+
export function projectSubtitle(project) {
|
|
75
|
+
if (project === undefined) return ''
|
|
76
|
+
const parts = [loaderLabel(project.loader)]
|
|
77
|
+
if (project.minecraftVersion !== undefined) parts.push(String(project.minecraftVersion))
|
|
78
|
+
if (project.loaderVersion !== undefined) parts.push(`loader ${project.loaderVersion}`)
|
|
79
|
+
return parts.join(' · ')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {unknown} value
|
|
84
|
+
* @returns {string}
|
|
85
|
+
*/
|
|
86
|
+
export function text(value) {
|
|
87
|
+
if (value === undefined || value === null || value === '') return '—'
|
|
88
|
+
return String(value)
|
|
89
|
+
}
|
package/src/client/h.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 手绘图标:16px 栅格、1.5 描边、`currentColor`,和系统图标同一套视觉语言。
|
|
3
|
+
*
|
|
4
|
+
* 为什么不 import `@deepseek-ai/dsh-client-ui-primitives`:我们的客户端包是
|
|
5
|
+
* 懒 CJS,顶层 `require` 一旦在某个运行时里解析不到,整个 bundle 都会挂。
|
|
6
|
+
* `dsh-better-sidebar` 对库里没有的字形也是自己画 SVG,这里沿用同一做法。
|
|
7
|
+
*
|
|
8
|
+
* @module minecraft-dev-starter/client/icons
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { h } from './h.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {number} size
|
|
15
|
+
* @param {Array<any>} children
|
|
16
|
+
*/
|
|
17
|
+
function svg(size, children) {
|
|
18
|
+
return h(
|
|
19
|
+
'svg',
|
|
20
|
+
{
|
|
21
|
+
width: size,
|
|
22
|
+
height: size,
|
|
23
|
+
viewBox: '0 0 16 16',
|
|
24
|
+
fill: 'none',
|
|
25
|
+
'aria-hidden': 'true',
|
|
26
|
+
focusable: 'false',
|
|
27
|
+
},
|
|
28
|
+
...children,
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 播放:实心三角,和系统的 run 语义一致。 */
|
|
33
|
+
export function IconPlay({ size = 13 } = {}) {
|
|
34
|
+
return svg(size, [h('path', { key: 'p', d: 'M5.5 3.4v9.2l7.2-4.6z', fill: 'currentColor' })])
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 停止:圆角方块。 */
|
|
38
|
+
export function IconStop({ size = 13 } = {}) {
|
|
39
|
+
return svg(size, [h('rect', { key: 'r', x: 4.2, y: 4.2, width: 7.6, height: 7.6, rx: 1.4, fill: 'currentColor' })])
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** 刷新:缺口圆 + 箭头。 */
|
|
43
|
+
export function IconRefresh({ size = 13 } = {}) {
|
|
44
|
+
return svg(size, [
|
|
45
|
+
h('path', {
|
|
46
|
+
key: 'a',
|
|
47
|
+
d: 'M13 8a5 5 0 1 1-1.46-3.54',
|
|
48
|
+
stroke: 'currentColor',
|
|
49
|
+
strokeWidth: 1.5,
|
|
50
|
+
strokeLinecap: 'round',
|
|
51
|
+
}),
|
|
52
|
+
h('path', {
|
|
53
|
+
key: 'b',
|
|
54
|
+
d: 'M13 2.9v3.3h-3.3',
|
|
55
|
+
stroke: 'currentColor',
|
|
56
|
+
strokeWidth: 1.5,
|
|
57
|
+
strokeLinecap: 'round',
|
|
58
|
+
strokeLinejoin: 'round',
|
|
59
|
+
}),
|
|
60
|
+
])
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 警告:三角 + 感叹号。 */
|
|
64
|
+
export function IconWarning({ size = 13 } = {}) {
|
|
65
|
+
return svg(size, [
|
|
66
|
+
h('path', {
|
|
67
|
+
key: 'a',
|
|
68
|
+
d: 'M8 2.4 14 13H2z',
|
|
69
|
+
stroke: 'currentColor',
|
|
70
|
+
strokeWidth: 1.4,
|
|
71
|
+
strokeLinejoin: 'round',
|
|
72
|
+
}),
|
|
73
|
+
h('path', { key: 'b', d: 'M8 6.4v3.1', stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round' }),
|
|
74
|
+
h('circle', { key: 'c', cx: 8, cy: 11.5, r: 0.85, fill: 'currentColor' }),
|
|
75
|
+
])
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** 展开:下箭头(浮层入口提示用不到时也留着,保持图标集完整)。 */
|
|
79
|
+
export function IconChevronDown({ size = 13 } = {}) {
|
|
80
|
+
return svg(size, [
|
|
81
|
+
h('path', {
|
|
82
|
+
key: 'a',
|
|
83
|
+
d: 'm4.5 6.5 3.5 3.5 3.5-3.5',
|
|
84
|
+
stroke: 'currentColor',
|
|
85
|
+
strokeWidth: 1.5,
|
|
86
|
+
strokeLinecap: 'round',
|
|
87
|
+
strokeLinejoin: 'round',
|
|
88
|
+
}),
|
|
89
|
+
])
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** 前进:右箭头,用于"详情"这类进入动作。 */
|
|
93
|
+
export function IconChevronRight({ size = 13 } = {}) {
|
|
94
|
+
return svg(size, [
|
|
95
|
+
h('path', {
|
|
96
|
+
key: 'a',
|
|
97
|
+
d: 'm6.5 4.5 3.5 3.5-3.5 3.5',
|
|
98
|
+
stroke: 'currentColor',
|
|
99
|
+
strokeWidth: 1.5,
|
|
100
|
+
strokeLinecap: 'round',
|
|
101
|
+
strokeLinejoin: 'round',
|
|
102
|
+
}),
|
|
103
|
+
])
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** 勾选:用于"切换到此工程"这类确认动作。 */
|
|
107
|
+
export function IconCheck({ size = 13 } = {}) {
|
|
108
|
+
return svg(size, [
|
|
109
|
+
h('path', {
|
|
110
|
+
key: 'a',
|
|
111
|
+
d: 'm3.8 8.4 2.7 2.7 5.7-6',
|
|
112
|
+
stroke: 'currentColor',
|
|
113
|
+
strokeWidth: 1.5,
|
|
114
|
+
strokeLinecap: 'round',
|
|
115
|
+
strokeLinejoin: 'round',
|
|
116
|
+
}),
|
|
117
|
+
])
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** 方块:代表"模组工程"(侧边栏标签图标)。 */
|
|
121
|
+
export function IconCube({ size = 14 } = {}) {
|
|
122
|
+
return svg(size, [
|
|
123
|
+
h('path', {
|
|
124
|
+
key: 'a',
|
|
125
|
+
d: 'M8 1.9 13.6 4.7v6.6L8 14.1 2.4 11.3V4.7z',
|
|
126
|
+
stroke: 'currentColor',
|
|
127
|
+
strokeWidth: 1.3,
|
|
128
|
+
strokeLinejoin: 'round',
|
|
129
|
+
}),
|
|
130
|
+
h('path', { key: 'b', d: 'M2.6 4.8 8 7.5l5.4-2.7M8 7.5v6.4', stroke: 'currentColor', strokeWidth: 1.3, strokeLinejoin: 'round' }),
|
|
131
|
+
])
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** 终端:代表"运行日志"(侧边栏标签图标)。 */
|
|
135
|
+
export function IconTerminal({ size = 14 } = {}) {
|
|
136
|
+
return svg(size, [
|
|
137
|
+
h('rect', { key: 'a', x: 1.9, y: 2.9, width: 12.2, height: 10.2, rx: 2, stroke: 'currentColor', strokeWidth: 1.3 }),
|
|
138
|
+
h('path', {
|
|
139
|
+
key: 'b',
|
|
140
|
+
d: 'm4.6 6.4 1.9 1.9-1.9 1.9M8.6 10.4h2.9',
|
|
141
|
+
stroke: 'currentColor',
|
|
142
|
+
strokeWidth: 1.3,
|
|
143
|
+
strokeLinecap: 'round',
|
|
144
|
+
strokeLinejoin: 'round',
|
|
145
|
+
}),
|
|
146
|
+
])
|
|
147
|
+
}
|