feat(instance): add&delete game server through SJMCL#1328
feat(instance): add&delete game server through SJMCL#1328UNIkeEN merged 9 commits intoUNIkeEN:mainfrom
Conversation
modified: src-tauri/src/instance/models/misc.rs modified: src-tauri/src/lib.rs modified: src/enums/service-error.ts modified: src/locales/en.json modified: src/locales/zh-Hans.json modified: src/pages/instances/details/[id]/worlds.tsx modified: src/services/instance.ts
|
改完了(大概(( |
|
我去把上一个关掉 |
|
添加了删除按钮和删除命令 |
|
感谢在溪不然我都忘了( |
|
还要感谢在溪的部分代码(真的很有帮助( |
src-tauri/src/instance/commands.rs
Outdated
|
|
||
| let mut existing_servers = load_servers_info_from_path(&servers_dat_path).await?; | ||
|
|
||
| let original_len = existing_servers.len(); |
src-tauri/src/instance/commands.rs
Outdated
| Ok(()) | ||
| } | ||
|
|
||
| async fn save_servers_to_nbt(path: &PathBuf, servers: &[GameServerInfo]) -> SJMCLResult<()> { |
| import { UNIXToISOString, formatRelativeTime } from "@/utils/datetime"; | ||
| import { base64ImgSrc } from "@/utils/string"; | ||
|
|
||
| const DeleteServerDialog: React.FC<{ |
| }); | ||
| } | ||
| } catch (error) { | ||
| toast({ |
|
|
||
| if (response.status === "success") { | ||
| toast({ | ||
| title: t("InstanceWorldsPage.serverList.deleteSuccess"), |
| <Empty withIcon={false} size="sm" /> | ||
| )} | ||
| </Section> | ||
| <Modal isOpen={isAddServerModalOpen} onClose={onAddServerModalClose}> |
There was a problem hiding this comment.
本项目中的所有复杂逻辑 modal 应该均为独立组件,且包含 handle 逻辑
刷新逻辑应作为 onSuccess 传入
There was a problem hiding this comment.
已经将输入弹窗独立为函数组件
小问题:使用onSuccess时,短时间内添加多个服务器会导致重复刷新,有概率出现列表显示错误(目前还不知道什么原因),所以现在没有用onSuccess,而是在成功之后直接刷新,测试没有问题。
There was a problem hiding this comment.
已经将输入弹窗独立为函数组件 小问题:使用onSuccess时,短时间内添加多个服务器会导致重复刷新,有概率出现列表显示错误(目前还不知道什么原因),所以现在没有用onSuccess,而是在成功之后直接刷新,测试没有问题。
我没有看到此条 PR 在 modals/ 下新增了 add-server-modal.tsx?
There was a problem hiding this comment.
是要单独放到\src\components\modals里面吗
| autoFocus | ||
| /> | ||
| </FormControl> | ||
| <FormControl isRequired> |
There was a problem hiding this comment.
isRequired 的 FormControl 是否需要添加 FormErrMsg,请参考其他 input 类 modal
There was a problem hiding this comment.
添加了FormErrMsg,调整了服务器地址和服务器名称的顺序
modified: src-tauri/src/instance/helpers/server.rs modified: src/locales/en.json modified: src/locales/zh-Hans.json modified: src/pages/instances/details/[id]/worlds.tsx
src/locales/en.json
Outdated
| "offline": "Offline" | ||
| }, | ||
| "delete": "Delete this Server", | ||
| "deleteConfirm": { |
There was a problem hiding this comment.
修改为deletegameserveralertdialog部分
| handleRetrieveGameServerList(false); | ||
| handleRetrieveGameServerList(true); | ||
|
|
||
| // refresh every minute to query server info |
| import { UNIXToISOString, formatRelativeTime } from "@/utils/datetime"; | ||
| import { base64ImgSrc } from "@/utils/string"; | ||
|
|
||
| function AddGameServerModal(props: any) { |
There was a problem hiding this comment.
- 与现有项目内所有 modal 定义方式与代码文件结构不一致
- Typescript 项目请勿使用 any
There was a problem hiding this comment.
按照worldleveldata的格式改了一下,现在应该没问题((?
There was a problem hiding this comment.
globalsharedmodal和其他modal区别在能否用opensharedmodal调用吗((上次没问清,我的问题
| btnOK: t("General.delete"), | ||
| isAlert: true, | ||
| onOKCallback: () => { | ||
| setServerToDelete(server); |
There was a problem hiding this comment.
哪一个(
如果是isAlert的话,删除服务器的行为是不是按钮也应该是红的(
| serverName={serverName} | ||
| onNameChange={(e: any) => setServerName(e.target.value)} | ||
| serverAddress={serverAddress} | ||
| onAddressChange={(e: any) => setServerAddress(e.target.value)} |
There was a problem hiding this comment.
请考虑这么多参数的设计是否合理,相同类型的 AddAuthServerModal 只需要传入 isOpen 和 onClose
modified: src/locales/en.json modified: src/locales/zh-Hans.json modified: src/pages/instances/details/[id]/worlds.tsx
|
修改了addGameServerModal ,独立为modal文件夹下的文件,将国际化内容放到worldleveldatamodal后边了(应该没问题 |
可是国际化内容最外层是按照字母序排序的啊 |
f1e8bbc to
20c84cb
Compare
There was a problem hiding this comment.
Pull request overview
This PR adds functionality to add and delete Minecraft game servers directly from the SJMCL launcher interface, addressing issue #1169. Users can now manage their server list through a dedicated modal dialog without manually editing the servers.dat file.
Changes:
- Implements Rust backend commands (
add_game_server,delete_game_server) to manipulate theservers.datNBT file - Creates a new frontend modal (
AddGameServerModal) for adding servers with address and name fields - Integrates server management UI into the instance worlds page with add/delete/launch buttons
- Adds comprehensive localization support for both English and Chinese languages
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src-tauri/src/lib.rs | Registers new add_game_server and delete_game_server commands |
| src-tauri/src/instance/commands.rs | Implements add/delete server commands with duplicate checking and NBT file operations |
| src-tauri/src/instance/helpers/server.rs | Adds NBT conversion traits, path helper, and save function for server data |
| src-tauri/src/instance/models/misc.rs | Adds DuplicateServer and FileOperationError enum variants |
| src/services/instance.ts | Exposes TypeScript service methods for add/delete game server operations |
| src/components/modals/add-game-server-modal.tsx | New modal component for adding game servers with validation |
| src/pages/instances/details/[id]/worlds.tsx | Integrates add/delete server UI with confirmation dialogs and auto-refresh |
| src/enums/service-error.ts | Adds DuplicateServer and FileOperationError to frontend error enum |
| src/locales/en.json | English translations for server management UI and error messages |
| src/locales/zh-Hans.json | Chinese translations for server management UI and error messages |
| pub async fn delete_game_server( | ||
| app: AppHandle, | ||
| instance_id: String, | ||
| server_addr: String, | ||
| ) -> SJMCLResult<()> { | ||
| let nbt_path = match get_servers_nbt_path_by_instance_id(&app, &instance_id) { | ||
| Some(path) => path, | ||
| None => return Err(InstanceError::InstanceNotFoundByID.into()), | ||
| }; | ||
| let mut existing_servers = load_servers_info_from_nbt(&nbt_path).await?; | ||
|
|
||
| existing_servers.retain(|server| server.ip != server_addr); | ||
| save_servers_to_nbt(&nbt_path, &existing_servers) | ||
| .await | ||
| .map_err(|_| InstanceError::FileOperationError)?; | ||
|
|
||
| Ok(()) | ||
| } |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| DuplicateServer, | ||
| FileNotFoundError, | ||
| InvalidSourcePath, | ||
| FileCreationFailed, | ||
| FileCopyFailed, | ||
| FileMoveFailed, | ||
| FileOperationError, |
There was a problem hiding this comment.
The localization files define a SERVER_NOT_FOUND error message but the corresponding error variant is missing from the InstanceError enum. Either add the ServerNotFound variant to the enum (and use it in the delete_game_server command), or remove the unused error message from the localization files. For consistency with the error messages defined, adding the variant is recommended.
| const urlObj = new URL(presetUrl); | ||
| const extractedName = urlObj.hostname || presetUrl; |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
| </FormLabel> | ||
| <Input | ||
| id="serverUrl" | ||
| type="url" |
There was a problem hiding this comment.
The input type "url" is semantically incorrect for Minecraft server addresses. These addresses are typically in the format "example.com" or "example.com:25565", which are not valid URLs (they lack a protocol like http:// or https://). Using type="url" may cause browser validation issues and confuse users.
Change the input type to "text" to properly accept Minecraft server address formats.
| type="url" | |
| type="text" |
| (instanceId: string) => { | ||
| const finalServerName = serverName.trim() || serverNamePlaceholder; | ||
| setIsLoading(true); | ||
| InstanceService.addGameServer(instanceId, serverUrl, finalServerName) |
There was a problem hiding this comment.
The server address is not trimmed before being sent to the backend. If users accidentally include leading or trailing whitespace in the address field, it will be saved as-is, which could cause connection issues and duplicate entries (since "example.com" and " example.com" would be treated as different servers).
Consider trimming the serverUrl before sending it: InstanceService.addGameServer(instanceId, serverUrl.trim(), finalServerName)
| InstanceService.addGameServer(instanceId, serverUrl, finalServerName) | |
| InstanceService.addGameServer(instanceId, serverUrl.trim(), finalServerName) |
| }, 60000); | ||
| return () => clearInterval(intervalId); | ||
| }, [instanceId, handleRetrieveGameServerList]); | ||
| }, [instanceId, handleRetrieveGameServerList, refreshGameServerList]); |
There was a problem hiding this comment.
The useEffect dependency array includes both handleRetrieveGameServerList and refreshGameServerList, but since refreshGameServerList only depends on handleRetrieveGameServerList, including both is redundant. The effect should depend only on the primitive dependency (if needed at all).
However, in this case, since the interval only calls handleRetrieveGameServerList(true) directly, the dependency array could be simplified to just [handleRetrieveGameServerList] and remove the refreshGameServerList() call from the effect body (or keep both but remove refreshGameServerList from the dependency array).
| }, [instanceId, handleRetrieveGameServerList, refreshGameServerList]); | |
| }, [handleRetrieveGameServerList]); |
|
测试没有问题。可以正常添加删除服务器,并且成功后会刷新列表。默认服务器名称正常。 |

Checklist
This PR is a ..
Related Issues
partly fix #1169
Description
Additional Context