桌面应用开发工具【免费下载链接】MooToolHandy tool set for developers项目地址https://gitcode.com/gh_mirrors/mo/MooTool点击查看免费下载本篇围绕 MooTool 的 Compose Multiplatform 移植版next-compose中的一次功能切片展开将图片工具F23与 HTTP 请求工具F09分散在各处的裸FileDialog文件选择统一收敛到「导出目录感知」的共享辅助函数上并通过共享设置项tools.exportDirectory实现上次选择的目录成为下次默认目录。读完后你可以理解该切片的改动范围、三个核心函数的调用链与参数行为、设置项的存储与回退机制以及切片自带的离线验证方式。1. 切片定位基线、范围与验证方式该切片的原始记录位于 DIFF-659其基线是 DIFF-658F24 PDF 切片。两个切片属于同一批次工作让各功能模块F24 PDF、F23 图片、F09 HTTP 等的导入/导出文件选择行为保持一致。切片范围完整继承原文档的三条要点F23图片助手导入多选、批量导出目录、SVG 单文件/多目录另存统一改用chooseFilesWithExportDirectory/chooseFileWithExportDirectorypersistToolsExportDirectory移除原来直接使用裸FileDialog的导入与单文件 SVG 另存逻辑F09HTTP 请求响应另存正文二进制/文本 Tab改用chooseFileWithExportDirectory并在写入成功后调用persistToolsExportDirectory移除裸FileDialog共享persistToolsExportDirectory在用户选择目录时写入该目录路径而非仅写其 parent 目录。验证方式原文档原文保留# JDK 21 ./gradlew :composeApp:desktopTest --offline --no-daemon ./scripts/verify-product-evidence-prep.sh其中第一条从next-compose根目录执行运行 desktop 目标的单元测试--offline --no-daemon保证在离线 CI 环境下可复现第二条位于 verify-product-evidence-prep.sh用于校验产品级证据准备脚本。2. 设置项tools.exportDirectory存储结构与合法性回退这个设置项定义在公共模型 ToolSettings 中Serializable data class ToolSettings( val qrCodeSize: Int 300, val qrErrorCorrection: String M, val randomStringLength: Int 16, val exportDirectory: String , // 空串 使用产品默认导出目录 val translationProvider: String google, // ... )默认值为空字符串语义是使用产品默认目录。用户可以在设置界面显式修改该值SettingsScreen.kt 中的DirectorySettingRow直接读写settings.tools.exportDirectory并在其上方展示settings.tools.exportDirHint提示文案。配置值并非被无条件信任。所有读取方都先经过 VaultPathConfig 的归一化fun normalizedCustomRoot(value: String): String? { val trimmed value.trim() if (trimmed.isEmpty()) return // 空值 保持产品默认 val path runCatching { Path.of(trimmed).normalize() }.getOrNull() ?: return null if (!path.isAbsolute) return null // 相对路径视为非法 return path.toString() } /** 非法或相对的配置路径回退到产品默认 vault。 */ fun effectiveCustomRoot(value: String): String normalizedCustomRoot(value) ?: 这保证了空配置 → 走默认非法/相对路径配置 → 静默回退到默认而不是抛错或写出到意外位置。当设置项为空或非法时产品默认目录的取值逻辑在 ToolsExportWiringPresentation 中注释明确说明其对齐 Electron Desktop 的回退策略/** F10/F22/F24 外置导出/合并初始目录对齐 tools.exportDirectory Electron Desktop 回退。 */ fun defaultExportDirectory(exportDirectorySetting: String): File { val export VaultPathConfig.effectiveCustomRoot(exportDirectorySetting) val desktop File(System.getProperty(user.home), Desktop).takeIf { it.isDirectory } ?: File(System.getProperty(user.home)) return export.takeIf { it.isNotBlank() }?.let { File(it) }?.takeIf { it.isDirectory } ?: desktop }即有效配置的目录且确实存在→ 否则~/Desktop若为目录→ 否则用户主目录。该对象还提供两个派生默认值供调用方预填文件名defaultSvgPath(exportDirectorySetting, assetBaseName)生成导出目录/图名.svgdefaultHostExportFileName(profileName)将非法字符替换为-生成方案名.txt。3. 共享辅助函数ToolsExportDirectory.kt的三个入口三个函数全部集中在 ToolsExportDirectory.kt签名与行为如下。3.1persistToolsExportDirectory写入用户选择的目录fun persistToolsExportDirectory(container: AppContainer, file: File) { val parent when { file.isDirectory - file.absolutePath.takeIf { it.isNotBlank() } else - file.parentFile?.absolutePath?.takeIf { it.isNotBlank() } } ?: return if (!java.nio.file.Path.of(parent).isAbsolute) return if (container.settings.value.tools.exportDirectory parent) return container.updateSettings { current - current.copy(tools current.tools.copy(exportDirectory parent)) } }逐行对应原文档写入该目录路径非仅 parent的要求参数本身是目录时批量导出选择目录的场景直接采用file.absolutePath而不是再取一层 parent参数是文件时另存场景取其parentFile绝对路径非绝对路径直接放弃写入与VaultPathConfig的合法性约束一致与当前设置值相同时跳过更新避免无意义的 settings 变更与持久化。3.2chooseFileWithExportDirectory单文件选择LOAD/SAVE 双模式fun chooseFileWithExportDirectory( container: AppContainer, save: Boolean, title: String, defaultFileName: String , ): File? { val exportDir VaultPathConfig.effectiveCustomRoot(container.settings.value.tools.exportDirectory) val dialog FileDialog(null as Frame?, title, if (save) FileDialog.SAVE else FileDialog.LOAD) if (defaultFileName.isNotBlank()) { dialog.file defaultFileName } if (exportDir.isNotBlank()) { val dir File(exportDir) if (dir.isDirectory) { dialog.directory exportDir } else { dir.parentFile?.takeIf { it.isDirectory }?.let { dialog.directory it.absolutePath } } } dialog.isVisible true val directory dialog.directory ?: return null val name dialog.file ?: return null return File(directory, name) }参数说明参数含义container应用容器用于读取settings.tools.exportDirectory作为初始目录savetrue时构造FileDialog.SAVE另存否则FileDialog.LOAD打开title对话框标题defaultFileName预填文件名SAVE 模式下引导用户为空则不预填两处防御值得注意配置目录存在性检查失败时配置的目录已被删除会回退到其父目录若父目录存在作为初始位置而不是让用户面对失效路径用户取消时dialog.file为 null函数返回 null 而不是空File。3.3chooseFilesWithExportDirectory多选 LOADfun chooseFilesWithExportDirectory( container: AppContainer, title: String, fileFilter: String , ): ListFile通过dialog.isMultipleMode true启用多选fileFilter非空时作为初始文件过滤串对应原文档 F23导入多选场景初始目录的解析与回退逻辑与单文件版完全一致复用effectiveCustomRoot结果解析先读dialog.files多值为空时再降级读取单值dialog.directory dialog.file兼容某些平台/桌面环境下多选模式只回填单值的行为用户取消返回emptyList()。4. F23 图片工具侧的接入调用方在 ImageScreen.kt。三处调用链与原文档范围逐条对应。4.1 导入多选importFilesprivate fun importFiles(container: AppContainer, session: ImageSession, onLoaded: (String?) - Unit) { val picked chooseFilesWithExportDirectory(container, container.t(image.import)) val files picked.filter { ImageLibraryStoreSafe(it.extension) } if (files.isEmpty()) return files.firstOrNull()?.let { persistToolsExportDirectory(container, it) } importPickedFiles(container, session, files, onLoaded) }要点多选加载后经ImageLibraryStore.supported(extension)过滤出受支持扩展名的文件取第一个选中文件调用persistToolsExportDirectory其 parent 即用户本次浏览的目录拖拽导入路径desktopFileDropTarget不触发该持久化只走importPickedFiles。4.2 批量导出目录exportSelectedprivate fun exportSelected(container: AppContainer, session: ImageSession, names: ListString) { val directory chooseDirectory(container, container.t(image.export)) ?: return persistToolsExportDirectory(container, directory) // ImageWiringPresentation.runExportAssets { container.imageLibrary.export(names, directory.toPath()) } // 成功toast lastOutputs失败notifyImageFailure }这里chooseDirectoryJFileChooser的DIRECTORIES_ONLY模式见 ImageScreen.kt#L1196-L1203用ToolsExportWiringPresentation.defaultExportDirectory(...)预填初始目录因此批量导出对话框也会落在上次使用的导出目录上用户选定目录后传入的是目录本身正好命中persistToolsExportDirectory中file.isDirectory分支——即写入该目录路径非仅 parent这条共享要求在实际调用中的体现。4.3 SVG 另存单文件与多目录两条路径chooseSvgTargets 按选中图数量分叉单张图用ToolsExportWiringPresentation.defaultSvgPath(...)预填导出目录/图名.svg作为默认文件名调chooseFileWithExportDirectory(save true, ...)若用户保存时去掉了.svg扩展名会自动补回File(chosen.path .svg)随后persistToolsExportDirectory(container, file)。这就是原文档SVG 单文件另存统一chooseFileWithExportDirectory多张图走chooseDirectory选一个目标目录persistToolsExportDirectory持久化后为每张图生成图名.svg并以内置去重逻辑处理冲突reserved集合防同批重名、candidate.exists()防与既有文件冲突冲突时递增为图名_2.svg、图名_3.svg……实际写盘在vectorize协程中完成写成功才记入written批量失败时删除已写出的部分文件if (targets.size 1) written.forEach { deleteIfExists }保证批量另存不留半成品——这与 feature-parity.md 中 F23 的写失败不损坏原件验收项相互印证。5. F09 HTTP 响应侧的接入调用方在 HttpScreen.kt。saveResponse按当前响应 Tab 分两条写入路径fun saveResponse(visible: HttpResponseResult?) { val bytes HttpEngine.downloadBytes(visible) if (bytes ! null session.responseTab HttpResponseTab.Body) { // 二进制按原始字节另存 val target pickHttpResponseSaveFile(container, binary true) ?: run { /* 取消提示 */ return } when (val outcome HttpResponsePresentation.runWriteResponseBytes(target, bytes)) { is HttpResponsePresentation.WriteExportOutcome.Success - { persistToolsExportDirectory(container, target) // 写入成功后才持久化目录 // toasthttp.responseSaved(path) } is HttpResponsePresentation.WriteExportOutcome.Failure - { /* 报错不持久化 */ return } } return } // 文本Headers/Cookies 等 Tab 的 payload val target pickHttpResponseSaveFile(container, binary false) ?: run { /* 取消 */ return } when (val outcome HttpResponsePresentation.runWriteResponseText(target, payload)) { is WriteExportOutcome.Success - { persistToolsExportDirectory(container, target); /* toast */ } is WriteExportOutcome.Failure - { /* 报错不持久化 */ return } } }两个设计点文件选择统一pickHttpResponseSaveFile 就是chooseFileWithExportDirectory的薄封装二进制 Tab 默认文件名http-response.bin文本 Tab 为http-response.txt初始目录来自tools.exportDirectory。成功后才持久化的时序原文档要求 F09 是成功后persistToolsExportDirectory。从源码看persistToolsExportDirectory被放在WriteExportOutcome.Success分支内——若磁盘写入失败例如权限不足、路径不可写用户上次有效的导出目录不会被一个失败的另存操作覆盖。这与图片侧选定目标即持久化、写盘失败另处理的时序略有差异属于按模块风险取舍的写法。写入本身收敛到 HttpResponsePresentation 的两个可测试函数runWriteResponseText以 UTF-8writeTextrunWriteResponseBytes以writeBytes保留原始字节对齐 F09 在 feature-parity.md 中二进制或无法解码时保留原始字节供另存不把预览当文件的约束。返回值是Success / Failure(error)sealed 结果UI 侧据此决定 toast 与错误提示测试可直接对纯函数断言。6. 行为对照改动前后的可验证差异场景改动前裸FileDialog改动后本切片图片导入F23 多选对话框从系统默认位置打开从上次使用的导出目录打开选中后该目录被持久化图片批量导出F23各工具目录记忆不互通chooseDirectory以tools.exportDirectory为初始目录选中目录直接写入设置SVG 单文件另存F23无默认文件名引导预填导出目录/图名.svg丢失的.svg扩展名自动补回响应另存F09 二进制/文本每次重新选择目录chooseFileWithExportDirectory预填目录仅写盘成功后更新设置非法/被删除的配置目录——effectiveCustomRoot回退默认对话框再回退到该目录的父目录上述目录记忆是跨工具生效的设置项位于全局ToolSettings因此任意一个已接入的工具如 PdfScreen.kt、QrCodeScreen.kt 等同样引用这三个辅助函数的模块更新目录后其他工具的下次选择都会以该目录为起点。7. 验证与复现切片的回归手段即原文档给出的两条命令适用前提是 JDK 21 与离线依赖齐全--offline# 在 next-compose 根目录执行 ./gradlew :composeApp:desktopTest --offline --no-daemon ./scripts/verify-product-evidence-prep.shdesktopTest目标覆盖 desktop 源集的单元测试从源码结构看本切片涉及的两个纯函数域HttpResponsePresentation的写盘结果模型、ToolsExportWiringPresentation的默认路径推导都是可离线断言的对象与仓库中其他切片的测试风格一致。若只想人工核对行为可按第 6 节对照表逐项操作在设置中显式填写一个绝对路径目录 → 图片导入/另存/HTTP 响应另存均应落在该目录把该目录删除后再触发选择 → 对话框应回退到其父目录或产品默认目录且不崩溃。8. 小结DIFF-659 是一个典型的横向切片不新增功能而是把 F23、F09 两个模块中语义相同的文件选择行为收敛到 ToolsExportDirectory.kt 的三个共享函数上并借tools.exportDirectory这一全局设置项VaultPathConfig归一化 ToolsExportWiringPresentation桌面回退实现跨工具的目录记忆。理解这条链路后后续阅读 DIFF-660F14 Crypto 摘要、DIFF-661F05/A01 备份 IO 等同一批次切片时可直接套用本文第 3、4 节的调用链模式。赞分享桌面应用开发工具【免费下载链接】MooToolHandy tool set for developers项目地址https://gitcode.com/gh_mirrors/mo/MooTool点击查看免费下载相关推荐MooTool next-compose配置转换工具的 *ActionEnabled 守卫接线与 tools.exportDirectory 统一导出链MooTool next compose配置转换工具的 ActionEnabled 守卫接线与 tools.exportDirectory 统一导出链 本篇基桌面应用开发工具openclaw-cn 浏览器自动化 CLI 完全指南clawdbot browser 的配置、标签页管理、快照操作与远程控制openclaw cn 浏览器自动化 CLI 完全指南clawdbot browser 的配置、标签页管理、快照操作与远程控制 clawdbot browse桌面应用开发工具MooTool Compose 版二维码导出目录迁移从 Java qrCodeSaveAsPath 到统一 tools.exportDirectory 的 DIFF-189 实战解析MooTool Compose 版二维码导出目录迁移从 Java qrCodeSaveAsPath 到统一 tools.exportDirectory 的 D桌面应用开发工具上一篇Anthropics Prompt Engineering Interactive Tutorial离线使用本地部署方法下一篇MetaMask Extension版本控制策略Git工作流与分支管理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考