news 2026/10/16 1:41:09

MooTool next-compose:用 *ActionEnabled 纯函数驱动 IPv4/Long 转换钮状态(DIFF-662 切片解析)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MooTool next-compose:用 *ActionEnabled 纯函数驱动 IPv4/Long 转换钮状态(DIFF-662 切片解析)
  • 桌面应用
  • 开发工具

【免费下载链接】MooTool

Handy tool set for developers

项目地址:https://gitcode.com/gh_mirrors/mo/MooTool
点击查看免费下载

本文围绕 MooTool 仓库next-compose目录下的 DIFF-662 切片文档展开,讲清 F11 网络/IP 工具中 IPv4↔Long 两个转换按钮的可点击状态是如何被NetWiringPresentation的两个纯函数(ipv4ToLongActionEnabled/longToIpv4ActionEnabled)派生出来的,以及它们如何委托给NetEngine做校验、在NetScreen中完成接线、并被 desktopTest 单测覆盖。读完你能掌握这套“展示层纯函数 + 引擎委托校验”的按钮状态控制写法,并能复现该切片的验证命令。

切片定位与验证基线

DIFF 系列文档是next-compose/docs/diff/下逐片记录的增量开发日志,每片只聚焦一个最小变更面。DIFF-662 文档 原文极简,但信息完整:

  • 基线:DIFF-661(工作区),即本切片建立在 DIFF-661 之后的工作树上;后续切片 DIFF-663 又以 DIFF-662 为基线,形成链式演进。
  • 范围:F11 的 IPv4↔Long 转换钮ipv4ToLongActionEnabled/longToIpv4ActionEnabled(委托NetEngine校验)+NetScreen接线。
  • 验证(JDK 21 环境):
./gradlew :composeApp:desktopTest --offline --no-daemon ./scripts/verify-product-evidence-prep.sh

其中--offline表示不访问网络解析依赖,--no-daemon避免 Gradle 守护进程残留;证据准备脚本对应仓库中的 verify-product-evidence-prep.sh。验收台账 acceptance.md 的 F11 行也明确把「IPv4/Long 转换ipv4ToLongActionEnabled/longToIpv4ActionEnabled」标注为引用 DIFF-662 的已接线能力,说明该切片在 F11 工具(左侧命令输出区 + 右侧功能区的网络/IP 面板)中属于 IPv4/Long 转换小节的核心状态控制。

NetWiringPresentation:把按钮 enabled 派生成可单测的纯函数

本切片新增的两个函数位于 NetWiringPresentation.kt:

fun ipv4ToLongActionEnabled(ipv4: String): Boolean = runCatching { NetEngine.ipv4ToLong(ipv4.trim()) true }.getOrDefault(false) fun longToIpv4ActionEnabled(longText: String): Boolean = runCatching { NetEngine.longToIpv4(longText.trim()) true }.getOrDefault(false)

写法上有三个值得注意的点:

  1. 校验逻辑零重复。NetWiringPresentation本身不重新实现“什么算合法 IPv4 / 合法 Long”的判断,而是直接调用NetEngine.ipv4ToLong/NetEngine.longToIpv4并捕获其抛出的NetConvertException:转换成功返回true(按钮可点),抛异常返回false(按钮置灰)。这样 UI 的可用态与引擎的真实转换规则永远同源,不会出现“按钮可点但点下去报错”的分裂状态。
  2. trim()先行。两个函数都先去除首尾空白再校验,与同文件中portScanStart等入口对输入统一 trim 的处理风格一致(见 NetWiringPresentation.kt)。
  3. 纯函数、无 Compose 依赖。NetWiringPresentation是一个object,内部只放fun(不含@Composable),因此可以被 desktopTest 直接调用断言,这正是该切片的可测试性来源。同文件还定义了PortScanStart/HostCommandStart这类sealed interface(L5-L13),把“可启动/被拦截 + 错误码”建模为数据,供 UI 分支渲染;转换钮场景更简单,只需一个Boolean。

NetEngine 侧的转换规则与边界

两个按钮最终委托到的引擎实现位于 NetEngine.kt:

fun ipv4ToLong(value: String): Long { val parts = value.trim().split('.') if (parts.size != 4 || parts.any { !it.matches(Regex("""^\d{1,3}$""")) || it.toInt() > 255 }) { throw NetConvertException("Invalid IPv4 address") } return parts.fold(0L) { acc, part -> acc * 256 + part.toInt() } } fun longToIpv4(value: String): String { val number = value.trim().toDoubleOrNull() ?: throw NetConvertException("Invalid IPv4 number") if (number % 1.0 != 0.0 || number < 0 || number > 0xffffffffL.toDouble()) { throw NetConvertException("Invalid IPv4 number") } return longToIpv4(number.toLong()) } fun longToIpv4(value: Long): String { if (value < 0 || value > 0xffffffffL) throw NetConvertException("Invalid IPv4 number") return listOf(24, 16, 8, 0).joinToString(".") { shift -> ((value shr shift) and 0xffL).toString() } }

规则可以从源码直接读出:

  • IPv4 → Long:必须恰好 4 段、每段 1~3 位纯数字且不超过 255;按acc * 256 + 段值折叠成无符号 32 位整数。注意前导零是被接受的——测试断言了ipv4ToLong("127.0.0.01") == 2130706433L(NetEngineTest.kt),因为正则^\d{1,3}$不禁止01。
  • Long → IPv4:字符串入口先toDoubleOrNull(),再用number % 1.0 != 0.0排除小数(如1.5),并用0..0xffffffffL限制在 32 位无符号区间;Long 重载则按 24/16/8/0 位移取低 8 位拼成点分十进制。
  • 失败语义:非法输入统一抛NetConvertException(NetEngine.kt),它区别于命令类错误的NetException(后者携带NetworkErrorCode),让 UI 能对“转换失败”与“命令失败”分别给出提示。

对应的失败边界用例集中在 NetEngineTest.kt:256.0.0.1(段越界)、1.2.3/1.2.3.4.5(段数错误)、::1(IPv6 字面量)、127.0.0.(空段)应全部抛出NetConvertException;反向则-1、4294967296(上界 +1)、1.5(小数)均被拒绝,同时0.0.0.0 ↔ 0、255.255.255.255 ↔ 4294967295两个端点可往返。

NetScreen 接线:按钮 enabled 绑定与点击后的完整副作用

UI 侧接线在 NetScreen.kt 的Section(container.t("net.ipv4Long"))内,布局为「IPv4 输入框 → 两个转换按钮 → Long 输入框」:

Section(container.t("net.ipv4Long")) { LabeledField("IPv4", session.ipv4) { session.ipv4 = it; persist() } Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { MooButton( "${container.t("common.convert")} ↓", p5Toolbar = true, enabled = NetWiringPresentation.ipv4ToLongActionEnabled(session.ipv4), onClick = { runCatching { NetEngine.ipv4ToLong(session.ipv4).toString() } .onSuccess { value -> session.longValue = value session.error = "" container.history.save( ToolId.Net.id, NetHistoryMetadata.OP_IPV4_TO_LONG, value, session.ipv4, value, ) persist() } .onFailure { notifyNetFailure(container, session, convertMessage(container, it)) { persist() } } }) MooButton( "↑ ${container.t("common.convert")}", p5Toolbar = true, enabled = NetWiringPresentation.longToIpv4ActionEnabled(session.longValue), onClick = { /* 对称:longToIpv4 → session.ipv4,历史操作 OP_LONG_TO_IPV4 */ }) } LabeledField("Long", session.longValue) { session.longValue = it; persist() } }

这条链路的完整行为是:

  • enabled 实时派生:MooButton的enabled直接调用本切片新增的NetWiringPresentation.*ActionEnabled。由于session.ipv4/session.longValue是 Compose 可观察状态,用户每次改输入框(onChange触发persist())都会引发重组,按钮置灰/点亮随之自动更新,无需手动刷新。
  • 点击后写回对侧字段:转换成功时把结果写入另一端(session.longValue或session.ipv4),并清空session.error。
  • 写入通用历史:通过container.history.save(ToolId.Net.id, ...)以NetHistoryMetadata.OP_IPV4_TO_LONG/OP_LONG_TO_IPV4操作类型记录本次转换的输入与输出,纳入 F11 工具的历史恢复体系。
  • 失败兜底:虽然按钮已按纯函数置灰,但点击与点击前的状态仍可能存在竞态(例如用户在 IME 合成过程中状态未更新),因此onClick内部仍保留runCatching,失败时经notifyNetFailure+convertMessage给出真实失败原因,而不是静默或伪造结果——这与 acceptance.md 对 F11「失败显示真原因」的总体要求一致。

单测覆盖与回归验证

针对本切片,NetWiringPresentationTest.kt 新增了ipv4LongConvertActionEnabled用例,覆盖按钮态的四个关键输入:

assertTrue(NetWiringPresentation.ipv4ToLongActionEnabled("127.0.0.1")) assertFalse(NetWiringPresentation.ipv4ToLongActionEnabled("")) assertFalse(NetWiringPresentation.ipv4ToLongActionEnabled("256.0.0.1")) assertTrue(NetWiringPresentation.longToIpv4ActionEnabled("2130706433")) assertFalse(NetWiringPresentation.longToIpv4ActionEnabled("")) assertFalse(NetWiringPresentation.longToIpv4ActionEnabled("4294967296"))

即:合法地址/合法数值 → 可点;空串、段越界(256.0.0.1)、超上界(4294967296,比 32 位无符号最大值大 1)→ 置灰。与 NetEngineTest 的引擎级用例配合,形成「引擎转换规则 + 展示层派生规则」双层验证。

回归方式沿用原切片文档给出的两条命令(JDK 21):

./gradlew :composeApp:desktopTest --offline --no-daemon ./scripts/verify-product-evidence-prep.sh

从源码结构看,*ActionEnabled这类命名与纯函数化接线在同仓库 DIFF-654 至 DIFF-669 的多个切片中反复出现(如时间、加密、Cron、计算器等工具各有对应的*ActionEnabled切片),可以推断这是next-compose在逐工具对齐 Electron 行为时统一采用的展示层状态控制模式:把“按钮何时可点”的判断从@Composable体内剥离到可单测的纯函数中,并以对应工具引擎作为唯一校验来源。本文所讲的 F11 IPv4/Long 转换,正是该模式在网络工具上的具体落地。

  • 桌面应用
  • 开发工具

【免费下载链接】MooTool

Handy tool set for developers

项目地址:https://gitcode.com/gh_mirrors/mo/MooTool
点击查看免费下载

相关推荐

上一篇:如何快速掌握threejsfundamentals核心概念:场景、相机与渲染器解析
下一篇:Gleam:分布式执行系统简介与使用教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 1:41:04

Umi-OCR:离线 OCR 三步上手,截图取字到批量识别全搞定

Umi-OCR&#xff1a;离线 OCR 三步上手&#xff0c;截图取字到批量识别全搞定 【免费下载链接】Umi-OCR OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片&#xff0c;PDF文档识别&#xff0c;排除水印/页眉页脚&#xff0c;扫描/生成二维码。…

作者头像 李华
网站建设 2026/10/16 1:36:03

CSS transform: skew 深度解析:从数学原理到实战避坑指南

1. 从一次界面错位说起&#xff1a;为什么偏偏是skew前阵子帮一个做数据可视化的朋友调界面&#xff0c;他拿着屏幕截图问我&#xff1a;“为什么我这个卡片列表在鼠标悬停时&#xff0c;文字会跟着一起歪掉&#xff1f;我只想让背景斜切一下。”我一看代码&#xff0c;问题出在…

作者头像 李华