@morndream/mihomo-tui 0.5.0-rc.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 (145) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/LICENSE +21 -0
  3. package/README.md +462 -0
  4. package/bin/mihomo-tui +34 -0
  5. package/dist/App.js +161 -0
  6. package/dist/App.js.map +1 -0
  7. package/dist/api/client.js +239 -0
  8. package/dist/api/client.js.map +1 -0
  9. package/dist/api/status.js +43 -0
  10. package/dist/api/status.js.map +1 -0
  11. package/dist/api/stream.js +139 -0
  12. package/dist/api/stream.js.map +1 -0
  13. package/dist/api/types.js +6 -0
  14. package/dist/api/types.js.map +1 -0
  15. package/dist/cli.js +221 -0
  16. package/dist/cli.js.map +1 -0
  17. package/dist/commands/conn.js +122 -0
  18. package/dist/commands/conn.js.map +1 -0
  19. package/dist/commands/logs.js +99 -0
  20. package/dist/commands/logs.js.map +1 -0
  21. package/dist/commands/output.js +177 -0
  22. package/dist/commands/output.js.map +1 -0
  23. package/dist/commands/provider.js +140 -0
  24. package/dist/commands/provider.js.map +1 -0
  25. package/dist/commands/proxy.js +161 -0
  26. package/dist/commands/proxy.js.map +1 -0
  27. package/dist/commands/proxyEnv.js +193 -0
  28. package/dist/commands/proxyEnv.js.map +1 -0
  29. package/dist/commands/rules.js +87 -0
  30. package/dist/commands/rules.js.map +1 -0
  31. package/dist/commands/status.js +75 -0
  32. package/dist/commands/status.js.map +1 -0
  33. package/dist/components/ConfirmDialog.js +31 -0
  34. package/dist/components/ConfirmDialog.js.map +1 -0
  35. package/dist/components/DelayBadge.js +27 -0
  36. package/dist/components/DelayBadge.js.map +1 -0
  37. package/dist/components/InputDialog.js +325 -0
  38. package/dist/components/InputDialog.js.map +1 -0
  39. package/dist/components/ListDialog.js +86 -0
  40. package/dist/components/ListDialog.js.map +1 -0
  41. package/dist/components/ProgressDialog.js +34 -0
  42. package/dist/components/ProgressDialog.js.map +1 -0
  43. package/dist/components/ScrollList.js +26 -0
  44. package/dist/components/ScrollList.js.map +1 -0
  45. package/dist/components/StatusBar.js +58 -0
  46. package/dist/components/StatusBar.js.map +1 -0
  47. package/dist/components/TextEditor.js +112 -0
  48. package/dist/components/TextEditor.js.map +1 -0
  49. package/dist/components/textEditorModel.js +154 -0
  50. package/dist/components/textEditorModel.js.map +1 -0
  51. package/dist/config/manager.js +296 -0
  52. package/dist/config/manager.js.map +1 -0
  53. package/dist/config/preserveGroups.js +9 -0
  54. package/dist/config/preserveGroups.js.map +1 -0
  55. package/dist/config/ruleDocument.js +88 -0
  56. package/dist/config/ruleDocument.js.map +1 -0
  57. package/dist/config/ruleService.js +256 -0
  58. package/dist/config/ruleService.js.map +1 -0
  59. package/dist/config/ruleValidation.js +98 -0
  60. package/dist/config/ruleValidation.js.map +1 -0
  61. package/dist/config/service.js +63 -0
  62. package/dist/config/service.js.map +1 -0
  63. package/dist/config/settingsService.js +162 -0
  64. package/dist/config/settingsService.js.map +1 -0
  65. package/dist/config/skeleton.js +324 -0
  66. package/dist/config/skeleton.js.map +1 -0
  67. package/dist/config/subscriptionService.js +284 -0
  68. package/dist/config/subscriptionService.js.map +1 -0
  69. package/dist/config/subscriptions.js +200 -0
  70. package/dist/config/subscriptions.js.map +1 -0
  71. package/dist/config/types.js +5 -0
  72. package/dist/config/types.js.map +1 -0
  73. package/dist/config.js +101 -0
  74. package/dist/config.js.map +1 -0
  75. package/dist/hooks/useProviders.js +138 -0
  76. package/dist/hooks/useProviders.js.map +1 -0
  77. package/dist/hooks/useProxies.js +298 -0
  78. package/dist/hooks/useProxies.js.map +1 -0
  79. package/dist/hooks/useStream.js +127 -0
  80. package/dist/hooks/useStream.js.map +1 -0
  81. package/dist/kernel/installer.js +317 -0
  82. package/dist/kernel/installer.js.map +1 -0
  83. package/dist/kernel/releases.js +111 -0
  84. package/dist/kernel/releases.js.map +1 -0
  85. package/dist/kernel/sources.js +98 -0
  86. package/dist/kernel/sources.js.map +1 -0
  87. package/dist/rules/editor.js +181 -0
  88. package/dist/rules/editor.js.map +1 -0
  89. package/dist/rules/matcher.js +83 -0
  90. package/dist/rules/matcher.js.map +1 -0
  91. package/dist/rules/model.js +25 -0
  92. package/dist/rules/model.js.map +1 -0
  93. package/dist/rules/runtime.js +46 -0
  94. package/dist/rules/runtime.js.map +1 -0
  95. package/dist/rules/types.js +10 -0
  96. package/dist/rules/types.js.map +1 -0
  97. package/dist/ui/FooterLine.js +62 -0
  98. package/dist/ui/FooterLine.js.map +1 -0
  99. package/dist/ui/Panel.js +47 -0
  100. package/dist/ui/Panel.js.map +1 -0
  101. package/dist/ui/RowIndicator.js +25 -0
  102. package/dist/ui/RowIndicator.js.map +1 -0
  103. package/dist/ui/SectionHeader.js +14 -0
  104. package/dist/ui/SectionHeader.js.map +1 -0
  105. package/dist/ui/StatusDot.js +15 -0
  106. package/dist/ui/StatusDot.js.map +1 -0
  107. package/dist/ui/TopBar.js +81 -0
  108. package/dist/ui/TopBar.js.map +1 -0
  109. package/dist/ui/exitGuard.js +20 -0
  110. package/dist/ui/exitGuard.js.map +1 -0
  111. package/dist/ui/keyCapture.js +28 -0
  112. package/dist/ui/keyCapture.js.map +1 -0
  113. package/dist/ui/layout.js +32 -0
  114. package/dist/ui/layout.js.map +1 -0
  115. package/dist/ui/rowIndicator.js +42 -0
  116. package/dist/ui/rowIndicator.js.map +1 -0
  117. package/dist/ui/tableHeader.js +33 -0
  118. package/dist/ui/tableHeader.js.map +1 -0
  119. package/dist/ui/theme.js +47 -0
  120. package/dist/ui/theme.js.map +1 -0
  121. package/dist/ui/tone.js +131 -0
  122. package/dist/ui/tone.js.map +1 -0
  123. package/dist/version.js +27 -0
  124. package/dist/version.js.map +1 -0
  125. package/dist/views/Conns.js +107 -0
  126. package/dist/views/Conns.js.map +1 -0
  127. package/dist/views/Logs.js +89 -0
  128. package/dist/views/Logs.js.map +1 -0
  129. package/dist/views/Providers.js +392 -0
  130. package/dist/views/Providers.js.map +1 -0
  131. package/dist/views/Proxies.js +233 -0
  132. package/dist/views/Proxies.js.map +1 -0
  133. package/dist/views/RuleEditor.js +356 -0
  134. package/dist/views/RuleEditor.js.map +1 -0
  135. package/dist/views/Rules.js +286 -0
  136. package/dist/views/Rules.js.map +1 -0
  137. package/dist/views/Settings.js +734 -0
  138. package/dist/views/Settings.js.map +1 -0
  139. package/dist/views/groupDisplay.js +53 -0
  140. package/dist/views/groupDisplay.js.map +1 -0
  141. package/dist/views/subscriptionFields.js +59 -0
  142. package/dist/views/subscriptionFields.js.map +1 -0
  143. package/dist/views/yamlFile.js +32 -0
  144. package/dist/views/yamlFile.js.map +1 -0
  145. package/package.json +66 -0
@@ -0,0 +1,34 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * 进度对话框(设计稿 5.3):步骤计数、百分比条、已完成步骤清单。
4
+ * cancelable 时响应 ESC(仅在安全点提供——由调用方决定是否传入 onCancel)。
5
+ */
6
+ import { Box, Text, useInput } from 'ink';
7
+ import { colors } from '../ui/theme.js';
8
+ import { useKeyCapture } from '../ui/keyCapture.js';
9
+ /** 纯函数:生成固定宽度的进度条,如 [#####-----]。
10
+ * 用 ASCII 而非 █░:块元素属 East Asian Ambiguous 宽度,在 ambiguous=wide
11
+ * 配置的真实终端下按 2 列渲染,一整条 10~20 字符的进度条会把行宽撑出
12
+ * 面板导致折行(本机 pyte 工具链测不出,属真机终端风险,保守处理)。
13
+ */
14
+ export function progressBar(fraction, width = 20) {
15
+ const clamped = Math.min(1, Math.max(0, fraction));
16
+ const filled = Math.round(clamped * width);
17
+ return `[${'#'.repeat(filled)}${'-'.repeat(width - filled)}]`;
18
+ }
19
+ /** 纯函数:当前进度百分比(0–100),total 为 0 时按完成计 */
20
+ export function progressPercent(step, total) {
21
+ if (total <= 0)
22
+ return 100;
23
+ return Math.min(100, Math.round((step / total) * 100));
24
+ }
25
+ export function ProgressDialog({ title, current, total, step, completed = [], detail, cancelable = false, onCancel, width = 56, }) {
26
+ useKeyCapture(true);
27
+ useInput((_input, key) => {
28
+ if (cancelable && key.escape)
29
+ onCancel?.();
30
+ });
31
+ const percent = progressPercent(step, total);
32
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: colors.accent, paddingX: 1, width: width, children: [_jsx(Text, { bold: true, children: ` ${title}` }), _jsx(Text, { children: ` [${Math.min(step + 1, total)}/${total}] ${current}` }), detail ? _jsx(Text, { dimColor: true, children: ` ${detail}` }) : null, _jsx(Text, { color: colors.accent, children: ` ${progressBar(percent / 100)} ${percent}%` }), completed.map((label) => (_jsx(Text, { color: colors.success, children: ` ✓ ${label}` }, label))), cancelable ? _jsx(Text, { dimColor: true, children: ' ESC 取消' }) : null] }));
33
+ }
34
+ //# sourceMappingURL=ProgressDialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProgressDialog.js","sourceRoot":"","sources":["../../src/components/ProgressDialog.tsx"],"names":[],"mappings":";AAAA;;;GAGG;AACH,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAmBnD;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,KAAK,GAAG,EAAE;IACtD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAA;IAClD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,CAAA;IAC1C,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;AAC/D,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,KAAa;IACzD,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,GAAG,CAAA;IAC1B,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAA;AACxD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,EAC7B,KAAK,EACL,OAAO,EACP,KAAK,EACL,IAAI,EACJ,SAAS,GAAG,EAAE,EACd,MAAM,EACN,UAAU,GAAG,KAAK,EAClB,QAAQ,EACR,KAAK,GAAG,EAAE,GACU;IACpB,aAAa,CAAC,IAAI,CAAC,CAAA;IACnB,QAAQ,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;QACvB,IAAI,UAAU,IAAI,GAAG,CAAC,MAAM;YAAE,QAAQ,EAAE,EAAE,CAAA;IAC5C,CAAC,CAAC,CAAA;IAEF,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC5C,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,WAAW,EAAC,OAAO,EAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,aACnG,KAAC,IAAI,IAAC,IAAI,kBAAE,IAAI,KAAK,EAAE,GAAQ,EAC/B,KAAC,IAAI,cAAE,KAAK,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,EAAE,KAAK,CAAC,IAAI,KAAK,KAAK,OAAO,EAAE,GAAQ,EACnE,MAAM,CAAC,CAAC,CAAC,KAAC,IAAI,IAAC,QAAQ,kBAAE,IAAI,MAAM,EAAE,GAAQ,CAAC,CAAC,CAAC,IAAI,EACrD,KAAC,IAAI,IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,YAAG,IAAI,WAAW,CAAC,OAAO,GAAG,GAAG,CAAC,IAAI,OAAO,GAAG,GAAQ,EAChF,SAAS,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CACxB,KAAC,IAAI,IAAa,KAAK,EAAE,MAAM,CAAC,OAAO,YAAG,MAAM,KAAK,EAAE,IAA5C,KAAK,CAA+C,CAChE,CAAC,EACD,UAAU,CAAC,CAAC,CAAC,KAAC,IAAI,IAAC,QAAQ,kBAAE,SAAS,GAAQ,CAAC,CAAC,CAAC,IAAI,IAClD,CACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * 可滚动列表。终端行数有限,几百个节点必须按视口裁剪,
4
+ * 否则 Ink 会把整个列表都画出来把屏幕顶飞。
5
+ */
6
+ import { Box, Text } from 'ink';
7
+ /** 计算视口起始下标,让选中项尽量居中,且不越界 */
8
+ export function windowStart(selected, total, height) {
9
+ if (total <= height)
10
+ return 0;
11
+ const half = Math.floor(height / 2);
12
+ return Math.max(0, Math.min(selected - half, total - height));
13
+ }
14
+ export function ScrollList({ items, selected, height, renderItem, emptyText = '(空)', }) {
15
+ if (items.length === 0) {
16
+ return (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: emptyText }) }));
17
+ }
18
+ const rows = Math.max(1, height);
19
+ const start = windowStart(selected, items.length, rows);
20
+ const visible = items.slice(start, start + rows);
21
+ return (_jsxs(Box, { flexDirection: "column", children: [visible.map((item, offset) => {
22
+ const index = start + offset;
23
+ return (_jsx(Box, { children: renderItem(item, index, index === selected) }, index));
24
+ }), items.length > rows && (_jsx(Text, { dimColor: true, children: ` ${start + 1}-${Math.min(start + rows, items.length)} / ${items.length}` }))] }));
25
+ }
26
+ //# sourceMappingURL=ScrollList.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScrollList.js","sourceRoot":"","sources":["../../src/components/ScrollList.tsx"],"names":[],"mappings":";AAAA;;;GAGG;AACH,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAY/B,6BAA6B;AAC7B,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,KAAa,EAAE,MAAc;IACzE,IAAI,KAAK,IAAI,MAAM;QAAE,OAAO,CAAC,CAAA;IAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IACnC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC,CAAA;AAC/D,CAAC;AAED,MAAM,UAAU,UAAU,CAAI,EAC5B,KAAK,EACL,QAAQ,EACR,MAAM,EACN,UAAU,EACV,SAAS,GAAG,KAAK,GACE;IACnB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,CACL,KAAC,GAAG,cACF,KAAC,IAAI,IAAC,QAAQ,kBAAE,SAAS,GAAQ,GAC7B,CACP,CAAA;IACH,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;IAChC,MAAM,KAAK,GAAG,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IACvD,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC,CAAA;IAEhD,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACxB,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;gBAC5B,MAAM,KAAK,GAAG,KAAK,GAAG,MAAM,CAAA;gBAC5B,OAAO,CACL,KAAC,GAAG,cAAc,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,KAAK,QAAQ,CAAC,IAAnD,KAAK,CAAqD,CACrE,CAAA;YACH,CAAC,CAAC,EACD,KAAK,CAAC,MAAM,GAAG,IAAI,IAAI,CACtB,KAAC,IAAI,IAAC,QAAQ,kBACX,KAAK,KAAK,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,MAAM,EAAE,GACtE,CACR,IACG,CACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,58 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** 底部状态栏:内核版本、实时流量、内存、连接状态(配色全部来自 theme)。
3
+ *
4
+ * 响应式降级(2026-09-22 标准化美化):
5
+ * - Full(≥100列):完整指标(版本、模式、瞬时、累计、内存、连接状态)
6
+ * - Compact(72~99列):精简指标(版本、模式、瞬时、连接状态;隐藏累计与内存)
7
+ * - TooSmall(<72列):不渲染状态栏(由 TopBar 显示降级提示)
8
+ */
9
+ import { Box, Text, useStdout } from 'ink';
10
+ import { formatBytes } from '../commands/output.js';
11
+ import { StatusChip } from '../ui/StatusDot.js';
12
+ import { colors } from '../ui/theme.js';
13
+ import { classifySize } from '../ui/layout.js';
14
+ /** 连接状态 → 语义四档:断开是故障(红),重连/连接中是注意(黄),已连接是健康(绿) */
15
+ export function stateTone(state) {
16
+ switch (state) {
17
+ case 'open':
18
+ return 'positive';
19
+ case 'reconnecting':
20
+ case 'connecting':
21
+ return 'caution';
22
+ default:
23
+ return 'negative';
24
+ }
25
+ }
26
+ export function stateLabel(state, nodeName) {
27
+ const nodeDisplay = nodeName ? `(${nodeName})` : '';
28
+ switch (state) {
29
+ case 'open':
30
+ return `已连接${nodeDisplay}`;
31
+ case 'reconnecting':
32
+ return '重连中';
33
+ case 'closed':
34
+ return '已断开';
35
+ default:
36
+ return '连接中';
37
+ }
38
+ }
39
+ export function StatusBar(props) {
40
+ const { stdout } = useStdout();
41
+ const columns = stdout?.columns ?? 80;
42
+ const sizeClass = classifySize(columns);
43
+ // TooSmall 时不渲染状态栏(TopBar 已显示降级提示)
44
+ if (sizeClass === 'TooSmall') {
45
+ return null;
46
+ }
47
+ const showTotals = sizeClass === 'Full';
48
+ const showMem = sizeClass === 'Full';
49
+ const tone = stateTone(props.state);
50
+ const modeNames = {
51
+ rule: '规则',
52
+ global: '全局',
53
+ direct: '直连'
54
+ };
55
+ const modeDisplay = props.mode ? modeNames[props.mode] ?? props.mode : '?';
56
+ return (_jsx(Box, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: colors.surfaceBorder, paddingX: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: colors.muted, children: `mihomo ${props.version ?? '?'} │ ${modeDisplay} │ ` }), _jsxs(Text, { color: colors.success, children: ["\u2191", formatBytes(props.up), "/s"] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: colors.success, children: ["\u2193", formatBytes(props.down), "/s"] }), showTotals ? (_jsx(Text, { color: colors.muted, children: ` │ 总 ↑${formatBytes(props.upTotal)} ↓${formatBytes(props.downTotal)}` })) : null, showMem ? _jsx(Text, { color: colors.muted, children: ` │ mem ${formatBytes(props.memory)}` }) : null, _jsx(Text, { color: colors.muted, children: ' │ ' }), _jsx(StatusChip, { tone: tone, children: stateLabel(props.state, sizeClass === 'Full' ? props.currentNode : '') })] }) }));
57
+ }
58
+ //# sourceMappingURL=StatusBar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StatusBar.js","sourceRoot":"","sources":["../../src/components/StatusBar.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * 通用多行文本编辑器(v0.3.0 §7.4)。
4
+ *
5
+ * 与文件、订阅无关:调用方传入初始文本、校验函数与保存/放弃回调,
6
+ * 因此订阅 YAML、规则、本地节点等场景都能复用。
7
+ *
8
+ * 文档模型在 textEditor.ts(纯函数,按码点编辑);本组件只负责渲染、
9
+ * 键盘与粘贴绑定。ESC 有未保存修改时先弹确认再退出。
10
+ *
11
+ * 键位:方向键/Home/End 移动,字符输入,Backspace/Delete 删除,Enter 换行,
12
+ * Ctrl-S 校验后保存,ESC 放弃。
13
+ */
14
+ import { useRef, useState } from 'react';
15
+ import { Box, Text, useInput, usePaste } from 'ink';
16
+ import { colors } from '../ui/theme.js';
17
+ import { Panel } from '../ui/Panel.js';
18
+ import { useKeyCapture } from '../ui/keyCapture.js';
19
+ import { ConfirmDialog } from './ConfirmDialog.js';
20
+ import { fromText, reduce, toText, visibleLines, } from './textEditorModel.js';
21
+ export function TextEditor({ title, initialText, height, width, validate, onSave, onCancel, }) {
22
+ const [state, setState] = useState(() => fromText(initialText));
23
+ const [error, setError] = useState(undefined);
24
+ const [confirmingCancel, setConfirmingCancel] = useState(false);
25
+ const stateRef = useRef(state);
26
+ stateRef.current = state;
27
+ // 可视行数:面板顶线 1 + 底栏 1 + 边框余量
28
+ const viewHeight = Math.max(1, height - 4);
29
+ const gutterWidth = String(state.lines.length).length + 1;
30
+ const textWidth = Math.max(1, width - gutterWidth - 4);
31
+ const dirty = toText(state) !== initialText.replace(/\r\n/g, '\n');
32
+ useKeyCapture(true);
33
+ usePaste((text) => {
34
+ setState((prev) => reduce(prev, { type: 'insert', text }, viewHeight));
35
+ setError(undefined);
36
+ });
37
+ useInput((input, key) => {
38
+ if (confirmingCancel)
39
+ return;
40
+ if (key.escape) {
41
+ if (dirty)
42
+ setConfirmingCancel(true);
43
+ else
44
+ onCancel();
45
+ return;
46
+ }
47
+ // Ctrl-S 保存
48
+ if (key.ctrl && input === 's') {
49
+ const text = toText(stateRef.current);
50
+ const problem = validate?.(text);
51
+ if (problem) {
52
+ setError(problem);
53
+ return;
54
+ }
55
+ onSave(text);
56
+ return;
57
+ }
58
+ const action = keyAction(input, key);
59
+ if (!action)
60
+ return;
61
+ setState((prev) => reduce(prev, action, viewHeight));
62
+ setError(undefined);
63
+ });
64
+ if (confirmingCancel) {
65
+ return (_jsx(ConfirmDialog, { message: ['放弃未保存的修改?', '已编辑的内容将丢失'], onConfirm: onCancel, onCancel: () => setConfirmingCancel(false) }));
66
+ }
67
+ const lines = visibleLines(state, viewHeight, textWidth);
68
+ const status = error
69
+ ? error
70
+ : dirty
71
+ ? `已修改 · ${state.lines.length} 行`
72
+ : `${state.lines.length} 行`;
73
+ return (_jsx(Panel, { title: title, width: width, fillHeight: true, children: _jsxs(Box, { flexDirection: "column", children: [lines.map((line) => (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: `${String(line.index + 1).padStart(gutterWidth - 1)} ` }), _jsx(Text, { children: renderLine(line.text, line.cursor ? line.cursorOffset : -1) })] }, line.index))), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: error ? colors.danger : colors.muted, children: status }), _jsx(Text, { dimColor: true, children: ' Ctrl-S 保存 ESC 放弃' })] })] }) }));
74
+ }
75
+ /** 光标位置反色显示;光标在行尾时补一个空块 */
76
+ function renderLine(text, cursorOffset) {
77
+ if (cursorOffset < 0)
78
+ return text;
79
+ const chars = [...text];
80
+ const before = chars.slice(0, cursorOffset).join('');
81
+ const at = chars[cursorOffset] ?? ' ';
82
+ const after = chars.slice(cursorOffset + 1).join('');
83
+ return (_jsxs(_Fragment, { children: [before, _jsx(Text, { inverse: true, children: at }), after] }));
84
+ }
85
+ function keyAction(input, key) {
86
+ if (key.upArrow)
87
+ return { type: 'move', dir: 'up' };
88
+ if (key.downArrow)
89
+ return { type: 'move', dir: 'down' };
90
+ if (key.leftArrow)
91
+ return { type: 'move', dir: 'left' };
92
+ if (key.rightArrow)
93
+ return { type: 'move', dir: 'right' };
94
+ if (key.ctrl && input === 'a')
95
+ return { type: 'move', dir: 'home' };
96
+ if (key.ctrl && input === 'e')
97
+ return { type: 'move', dir: 'end' };
98
+ if (key.return)
99
+ return { type: 'insert', text: '\n' };
100
+ if (key.backspace)
101
+ return { type: 'backspace' };
102
+ if (key.delete)
103
+ return { type: 'delete' };
104
+ if (key.ctrl || input.length === 0)
105
+ return undefined;
106
+ // 过滤控制字符,其余(含粘贴兜底的多字符)按插入处理
107
+ const cleaned = [...input].filter((char) => char >= ' ' && char !== '\x7f').join('');
108
+ if (!cleaned)
109
+ return undefined;
110
+ return { type: 'insert', text: cleaned };
111
+ }
112
+ //# sourceMappingURL=TextEditor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TextEditor.js","sourceRoot":"","sources":["../../src/components/TextEditor.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * 多行文本编辑器的文档模型(v0.3.0 §7.3)。纯逻辑,不依赖 Ink。
3
+ *
4
+ * 行内位置一律按码点(code point)计,emoji 等代理对不会被切断。
5
+ * 滚动语义借鉴 mihari viewport.go 的 EnsureLineVisible:光标行始终
6
+ * 保持在可视窗口内,窗口不越界。
7
+ */
8
+ import { inputWindow } from './InputDialog.js';
9
+ /** 把文本拆成行;空串得到一个空行,便于光标落位 */
10
+ export function fromText(text) {
11
+ const normalized = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
12
+ const lines = normalized.split('\n');
13
+ return { lines: lines.length > 0 ? lines : [''], row: 0, col: 0, scroll: 0 };
14
+ }
15
+ export function toText(state) {
16
+ return state.lines.join('\n');
17
+ }
18
+ const chars = (line) => [...line];
19
+ /**
20
+ * 归约一次编辑动作。viewHeight 是可视行数,用于在动作后把光标行
21
+ * 滚进窗口;传 0 表示不滚动。
22
+ */
23
+ export function reduce(state, action, viewHeight = 0) {
24
+ const next = applyAction(state, action);
25
+ return viewHeight > 0 ? scrollToCursor(next, viewHeight) : next;
26
+ }
27
+ function applyAction(state, action) {
28
+ switch (action.type) {
29
+ case 'insert':
30
+ return insertText(state, action.text);
31
+ case 'backspace':
32
+ return backspace(state);
33
+ case 'delete':
34
+ return deleteForward(state);
35
+ case 'move':
36
+ return move(state, action.dir);
37
+ }
38
+ }
39
+ /** 插入文本,可含换行(粘贴多行一次完成) */
40
+ function insertText(state, text) {
41
+ const lines = state.lines.slice();
42
+ const current = chars(lines[state.row] ?? '');
43
+ const before = current.slice(0, state.col).join('');
44
+ const after = current.slice(state.col).join('');
45
+ const inserted = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
46
+ const first = inserted[0] ?? '';
47
+ const last = inserted[inserted.length - 1] ?? '';
48
+ lines.splice(state.row, 1, before + first, ...inserted.slice(1, -1), last + after);
49
+ // 上面在 inserted 只有一段时会重复,收敛掉
50
+ if (inserted.length === 1) {
51
+ lines.splice(state.row, 2, before + first + after);
52
+ }
53
+ return {
54
+ ...state,
55
+ lines,
56
+ row: state.row + inserted.length - 1,
57
+ col: [...(inserted.length === 1 ? before + first : last)].length,
58
+ };
59
+ }
60
+ /** 退格:行内删一个码点;行首则把当前行并入上一行 */
61
+ function backspace(state) {
62
+ const lines = state.lines.slice();
63
+ const current = chars(lines[state.row] ?? '');
64
+ if (state.col > 0) {
65
+ lines[state.row] = [...current.slice(0, state.col - 1), ...current.slice(state.col)].join('');
66
+ return { ...state, lines, col: state.col - 1 };
67
+ }
68
+ if (state.row === 0)
69
+ return state;
70
+ const prev = lines[state.row - 1] ?? '';
71
+ lines.splice(state.row - 1, 2, prev + current.join(''));
72
+ return { ...state, lines, row: state.row - 1, col: [...prev].length };
73
+ }
74
+ /** 向前删除:行内删光标后一个码点;行尾则合并下一行 */
75
+ function deleteForward(state) {
76
+ const lines = state.lines.slice();
77
+ const current = chars(lines[state.row] ?? '');
78
+ if (state.col < current.length) {
79
+ lines[state.row] = [...current.slice(0, state.col), ...current.slice(state.col + 1)].join('');
80
+ return { ...state, lines };
81
+ }
82
+ if (state.row >= lines.length - 1)
83
+ return state;
84
+ lines.splice(state.row, 2, current.join('') + (lines[state.row + 1] ?? ''));
85
+ return { ...state, lines };
86
+ }
87
+ function move(state, dir) {
88
+ const lineLen = chars(state.lines[state.row] ?? '').length;
89
+ switch (dir) {
90
+ case 'left':
91
+ if (state.col > 0)
92
+ return { ...state, col: state.col - 1 };
93
+ if (state.row === 0)
94
+ return state;
95
+ return { ...state, row: state.row - 1, col: chars(state.lines[state.row - 1] ?? '').length };
96
+ case 'right':
97
+ if (state.col < lineLen)
98
+ return { ...state, col: state.col + 1 };
99
+ if (state.row >= state.lines.length - 1)
100
+ return state;
101
+ return { ...state, row: state.row + 1, col: 0 };
102
+ case 'up':
103
+ return moveVertical(state, -1);
104
+ case 'down':
105
+ return moveVertical(state, 1);
106
+ case 'home':
107
+ return { ...state, col: 0 };
108
+ case 'end':
109
+ return { ...state, col: lineLen };
110
+ }
111
+ }
112
+ /** 上下移动时列号尽量保持,目标行更短则落到行尾 */
113
+ function moveVertical(state, delta) {
114
+ const row = Math.min(Math.max(0, state.row + delta), state.lines.length - 1);
115
+ const len = chars(state.lines[row] ?? '').length;
116
+ return { ...state, row, col: Math.min(state.col, len) };
117
+ }
118
+ /**
119
+ * 让光标行保持在可视窗口内(借鉴 mihari EnsureLineVisible)。
120
+ * 光标在窗口之上 → 窗口上移到光标行;在窗口之下 → 窗口下移使光标落在末行。
121
+ */
122
+ export function scrollToCursor(state, viewHeight) {
123
+ if (viewHeight <= 0)
124
+ return state;
125
+ const maxScroll = Math.max(0, state.lines.length - viewHeight);
126
+ let scroll = Math.min(state.scroll, maxScroll);
127
+ if (state.row < scroll)
128
+ scroll = state.row;
129
+ if (state.row >= scroll + viewHeight)
130
+ scroll = state.row - viewHeight + 1;
131
+ return { ...state, scroll: Math.min(Math.max(0, scroll), maxScroll) };
132
+ }
133
+ /**
134
+ * 取可视窗口内的行。每行按 maxWidth 做横向开窗:光标行以光标为中心,
135
+ * 其余行显示开头。复用 InputDialog 的 inputWindow。
136
+ */
137
+ export function visibleLines(state, viewHeight, maxWidth) {
138
+ const start = state.scroll;
139
+ const end = Math.min(state.lines.length, start + Math.max(0, viewHeight));
140
+ const result = [];
141
+ for (let index = start; index < end; index++) {
142
+ const line = state.lines[index] ?? '';
143
+ const isCursor = index === state.row;
144
+ const windowed = inputWindow(line, isCursor ? state.col : 0, maxWidth);
145
+ result.push({
146
+ index,
147
+ text: windowed.text,
148
+ cursor: isCursor,
149
+ cursorOffset: isCursor ? windowed.cursorOffset : -1,
150
+ });
151
+ }
152
+ return result;
153
+ }
154
+ //# sourceMappingURL=textEditorModel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"textEditorModel.js","sourceRoot":"","sources":["../../src/components/textEditorModel.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAmB9C,6BAA6B;AAC7B,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IACnE,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACpC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAA;AAC9E,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,KAAkB;IACvC,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC/B,CAAC;AAED,MAAM,KAAK,GAAG,CAAC,IAAY,EAAY,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;AAEnD;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,KAAkB,EAAE,MAAoB,EAAE,UAAU,GAAG,CAAC;IAC7E,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IACvC,OAAO,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AACjE,CAAC;AAED,SAAS,WAAW,CAAC,KAAkB,EAAE,MAAoB;IAC3D,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,QAAQ;YACX,OAAO,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;QACvC,KAAK,WAAW;YACd,OAAO,SAAS,CAAC,KAAK,CAAC,CAAA;QACzB,KAAK,QAAQ;YACX,OAAO,aAAa,CAAC,KAAK,CAAC,CAAA;QAC7B,KAAK,MAAM;YACT,OAAO,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,CAAA;IAClC,CAAC;AACH,CAAC;AAED,0BAA0B;AAC1B,SAAS,UAAU,CAAC,KAAkB,EAAE,IAAY;IAClD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;IACjC,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAA;IAC7C,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACnD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAC7E,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IAC/B,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;IAChD,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,KAAK,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,GAAG,KAAK,CAAC,CAAA;IAClF,4BAA4B;IAC5B,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC,CAAA;IACpD,CAAC;IACD,OAAO;QACL,GAAG,KAAK;QACR,KAAK;QACL,GAAG,EAAE,KAAK,CAAC,GAAG,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC;QACpC,GAAG,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM;KACjE,CAAA;AACH,CAAC;AAED,8BAA8B;AAC9B,SAAS,SAAS,CAAC,KAAkB;IACnC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;IACjC,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAA;IAC7C,IAAI,KAAK,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;QAClB,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAC7F,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,GAAG,CAAC,EAAE,CAAA;IAChD,CAAC;IACD,IAAI,KAAK,CAAC,GAAG,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACjC,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;IACvC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;IACvD,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,EAAE,CAAA;AACvE,CAAC;AAED,+BAA+B;AAC/B,SAAS,aAAa,CAAC,KAAkB;IACvC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;IACjC,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAA;IAC7C,IAAI,KAAK,CAAC,GAAG,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;QAC/B,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAC7F,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,CAAA;IAC5B,CAAC;IACD,IAAI,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAA;IAC/C,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;IAC3E,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,CAAA;AAC5B,CAAC;AAED,SAAS,IAAI,CAAC,KAAkB,EAAE,GAAgH;IAChJ,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAA;IAC1D,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,MAAM;YACT,IAAI,KAAK,CAAC,GAAG,GAAG,CAAC;gBAAE,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,GAAG,CAAC,EAAE,CAAA;YAC1D,IAAI,KAAK,CAAC,GAAG,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAA;YACjC,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAA;QAC9F,KAAK,OAAO;YACV,IAAI,KAAK,CAAC,GAAG,GAAG,OAAO;gBAAE,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,GAAG,CAAC,EAAE,CAAA;YAChE,IAAI,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,KAAK,CAAA;YACrD,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAA;QACjD,KAAK,IAAI;YACP,OAAO,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;QAChC,KAAK,MAAM;YACT,OAAO,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;QAC/B,KAAK,MAAM;YACT,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE,CAAA;QAC7B,KAAK,KAAK;YACR,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,CAAA;IACrC,CAAC;AACH,CAAC;AAED,6BAA6B;AAC7B,SAAS,YAAY,CAAC,KAAkB,EAAE,KAAa;IACrD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAC5E,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAA;IAChD,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAA;AACzD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,KAAkB,EAAE,UAAkB;IACnE,IAAI,UAAU,IAAI,CAAC;QAAE,OAAO,KAAK,CAAA;IACjC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,CAAA;IAC9D,IAAI,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IAC9C,IAAI,KAAK,CAAC,GAAG,GAAG,MAAM;QAAE,MAAM,GAAG,KAAK,CAAC,GAAG,CAAA;IAC1C,IAAI,KAAK,CAAC,GAAG,IAAI,MAAM,GAAG,UAAU;QAAE,MAAM,GAAG,KAAK,CAAC,GAAG,GAAG,UAAU,GAAG,CAAC,CAAA;IACzE,OAAO,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,SAAS,CAAC,EAAE,CAAA;AACvE,CAAC;AAYD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,KAAkB,EAAE,UAAkB,EAAE,QAAgB;IACnF,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAA;IAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAA;IACzE,MAAM,MAAM,GAAkB,EAAE,CAAA;IAChC,KAAK,IAAI,KAAK,GAAG,KAAK,EAAE,KAAK,GAAG,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC;QAC7C,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;QACrC,MAAM,QAAQ,GAAG,KAAK,KAAK,KAAK,CAAC,GAAG,CAAA;QACpC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAA;QACtE,MAAM,CAAC,IAAI,CAAC;YACV,KAAK;YACL,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,MAAM,EAAE,QAAQ;YAChB,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;SACpD,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC"}