chore: establish YCode replication baseline and TODO
This commit is contained in:
commit
6dc59f89ba
1118 files changed
+196002
No files matched your search
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* 从 icons.json 生成 src/renderer/src/components/icons.tsx
|
||||
*
|
||||
* 用法:node scripts/gen-icons.mjs <iconsJson> <outTsx>
|
||||
*/
|
||||
|
||||
import { readFile, writeFile } from 'node:fs/promises'
|
||||
|
||||
const src = process.argv[2] ?? 'C:/Users/Administrator/.ycode/captures/icons.json'
|
||||
const out = process.argv[3] ?? 'src/renderer/src/components/icons.tsx'
|
||||
/** 第二轮采集的导航/账号图标(可选) */
|
||||
const navSource = process.argv[4] ?? 'C:/Users/Administrator/.ycode/captures/nav-icons.json'
|
||||
/** 第三轮:标题栏左上操作区图标(可选) */
|
||||
const topbarSource = process.argv[5] ?? 'C:/Users/Administrator/.ycode/captures/topbar-icons.json'
|
||||
|
||||
/** 图标名 → [target, svgIndex, 覆盖的渲染尺寸] */
|
||||
const MAP = {
|
||||
IconNewTask: ['newTask', 0, 16],
|
||||
IconChevronDown: ['templates', 1, 14],
|
||||
IconFilter: ['topbarFilter', 0, 16],
|
||||
IconCompact: ['sectionCompact', 0, 16],
|
||||
IconSectionAdd: ['sectionAdd', 0, 16],
|
||||
IconPlus: ['inputToolbarLeft', 0, 16],
|
||||
IconShield: ['inputToolbarLeft', 1, 16],
|
||||
IconChevronSmall: ['inputToolbarRight', 2, 12],
|
||||
IconMic: ['inputToolbarRight', 3, 16],
|
||||
IconStop: ['sendButton', 0, 32],
|
||||
IconTab: ['tabIcon', 0, 13],
|
||||
IconClose: ['tabClose', 0, 14],
|
||||
IconChevronRight: ['artifactActions', 0, 8],
|
||||
IconPanelCollapse: ['panelActions', 0, 16],
|
||||
IconPanelRight: ['panelActions', 1, 16]
|
||||
}
|
||||
|
||||
/**
|
||||
* 第二轮:从 nav-icons.json 采集的导航与账号区图标。
|
||||
* 键为采集时的标签文案 / 选择器。
|
||||
*/
|
||||
const NAV_MAP = {
|
||||
IconNavAssistant: '助理Beta',
|
||||
IconNavExperts: '专家·技能·连接器',
|
||||
IconNavScheduled: '定时任务',
|
||||
IconNavLibrary: '资料库',
|
||||
IconNavMore: '更多',
|
||||
IconBell: '.msg-center-bell-btn',
|
||||
IconMiniprogram: '.user-menu-trigger-miniprogram',
|
||||
IconDiscover: '.industry-template-switcher__host button'
|
||||
}
|
||||
|
||||
const NAV_SIZES = {
|
||||
IconNavAssistant: 16,
|
||||
IconNavExperts: 16,
|
||||
IconNavScheduled: 16,
|
||||
IconNavLibrary: 16,
|
||||
IconNavMore: 16,
|
||||
IconBell: 16,
|
||||
IconMiniprogram: 16,
|
||||
IconDiscover: 14
|
||||
}
|
||||
|
||||
/** HTML 连字符属性 → JSX 驼峰属性 */
|
||||
const JSX_ATTRS = {
|
||||
'fill-rule': 'fillRule',
|
||||
'clip-rule': 'clipRule',
|
||||
'stroke-width': 'strokeWidth',
|
||||
'stroke-linecap': 'strokeLinecap',
|
||||
'stroke-linejoin': 'strokeLinejoin',
|
||||
'stroke-dasharray': 'strokeDasharray',
|
||||
'stroke-dashoffset': 'strokeDashoffset',
|
||||
'stroke-miterlimit': 'strokeMiterlimit',
|
||||
'clip-path': 'clipPath',
|
||||
'fill-opacity': 'fillOpacity',
|
||||
'stroke-opacity': 'strokeOpacity'
|
||||
}
|
||||
|
||||
/** 只保留几何与绘制属性,去掉 class / aria 等宿主细节 */
|
||||
function sanitize(svg, size) {
|
||||
let out = svg
|
||||
out = out.replace(/\sclass="[^"]*"/g, '')
|
||||
out = out.replace(/\sstyle="[^"]*"/g, '')
|
||||
out = out.replace(/\saria-hidden="[^"]*"/g, '')
|
||||
out = out.replace(/\sxmlns="[^"]*"/g, '')
|
||||
// 统一由外层控制尺寸
|
||||
out = out.replace(/\swidth="[^"]*"/, '')
|
||||
out = out.replace(/\sheight="[^"]*"/, '')
|
||||
for (const [html, jsx] of Object.entries(JSX_ATTRS)) {
|
||||
out = out.replace(new RegExp(`\\s${html}=`, 'g'), ` ${jsx}=`)
|
||||
}
|
||||
out = out.replace(/<svg /, `<svg width="__SIZE__" height="__SIZE__" `)
|
||||
if (size) out = out.replace(/__SIZE__/g, String(size))
|
||||
return out
|
||||
}
|
||||
|
||||
const data = JSON.parse(await readFile(src, 'utf8'))
|
||||
|
||||
const parts = [
|
||||
'/**',
|
||||
' * 图标组件。',
|
||||
' *',
|
||||
' * ⚠️ 来源说明:本文件中的 SVG 路径取自本机安装的 WorkBuddy 运行时界面(CDP 采集,',
|
||||
' * 见 scripts/wb-icons.mjs + scripts/gen-icons.mjs),用于当前阶段的 1:1 视觉还原。',
|
||||
' * 这些矢量路径属于第三方产品的界面资产,**仅作临时占位**;',
|
||||
' * 正式发布前必须替换为自有图标或开源图标集(如 Lucide / VS Code codicons,后者为 MIT)。',
|
||||
' *',
|
||||
' * 重新生成:node scripts/gen-icons.mjs <icons.json> <输出路径>',
|
||||
' */',
|
||||
'',
|
||||
"import type { SVGProps } from 'react'",
|
||||
'',
|
||||
'type IconProps = SVGProps<SVGSVGElement>',
|
||||
''
|
||||
]
|
||||
|
||||
for (const [name, [target, index, size]] of Object.entries(MAP)) {
|
||||
const entry = data[target]
|
||||
const svg = entry?.svgs?.[index]
|
||||
if (!svg) {
|
||||
parts.push(`// ${name}: 采集数据缺失(${target}[${index}])`)
|
||||
parts.push('')
|
||||
continue
|
||||
}
|
||||
const cleaned = sanitize(svg, size)
|
||||
.replace(/\sfill="currentColor"/g, ' fill="currentColor"')
|
||||
.replace(/^<svg /, `<svg {...props} `)
|
||||
parts.push(`export function ${name}(props: IconProps) {`)
|
||||
parts.push(' return (')
|
||||
parts.push(' ' + cleaned)
|
||||
parts.push(' )')
|
||||
parts.push('}')
|
||||
parts.push('')
|
||||
}
|
||||
|
||||
/* ---- 第二轮:导航与账号区图标 ---- */
|
||||
let navData = null
|
||||
if (navSource) {
|
||||
try {
|
||||
navData = JSON.parse(await readFile(navSource, 'utf8'))
|
||||
} catch {
|
||||
navData = null
|
||||
}
|
||||
}
|
||||
|
||||
for (const [name, key] of Object.entries(NAV_MAP)) {
|
||||
const svg = navData?.[key]
|
||||
if (!svg) {
|
||||
parts.push(`// ${name}: 导航采集数据缺失(${key})`)
|
||||
parts.push('')
|
||||
continue
|
||||
}
|
||||
const cleaned = sanitize(svg, NAV_SIZES[name])
|
||||
.replace(/\sfill="currentColor"/g, ' fill="currentColor"')
|
||||
.replace(/^<svg /, `<svg {...props} `)
|
||||
parts.push(`export function ${name}(props: IconProps) {`)
|
||||
parts.push(' return (')
|
||||
parts.push(' ' + cleaned)
|
||||
parts.push(' )')
|
||||
parts.push('}')
|
||||
parts.push('')
|
||||
}
|
||||
|
||||
/* ---- 第三轮:标题栏左上操作区(收起侧边栏 / 搜索 / 筛选)---- */
|
||||
const TOPBAR_MAP = {
|
||||
IconCollapseSidebar: '收起侧边栏',
|
||||
IconSearchTopbar: '搜索'
|
||||
}
|
||||
|
||||
let topbarData = null
|
||||
if (topbarSource) {
|
||||
try {
|
||||
topbarData = JSON.parse(await readFile(topbarSource, 'utf8'))
|
||||
} catch {
|
||||
topbarData = null
|
||||
}
|
||||
}
|
||||
|
||||
for (const [name, title] of Object.entries(TOPBAR_MAP)) {
|
||||
const entry = Array.isArray(topbarData) ? topbarData.find((n) => n.title === title) : null
|
||||
if (!entry?.svg) {
|
||||
parts.push(`// ${name}: 标题栏采集数据缺失(${title})`)
|
||||
parts.push('')
|
||||
continue
|
||||
}
|
||||
const cleaned = sanitize(entry.svg, 16)
|
||||
.replace(/\sfill="currentColor"/g, ' fill="currentColor"')
|
||||
.replace(/^<svg /, `<svg {...props} `)
|
||||
parts.push(`export function ${name}(props: IconProps) {`)
|
||||
parts.push(' return (')
|
||||
parts.push(' ' + cleaned)
|
||||
parts.push(' )')
|
||||
parts.push('}')
|
||||
parts.push('')
|
||||
}
|
||||
|
||||
await writeFile(out, parts.join('\n'), 'utf8')
|
||||
console.log('已生成', out)
|
||||
console.log('图标数:', Object.keys(MAP).length)
|
||||
Reference in new issue
Block a user