From 6f2be3c5a51dc9e2669abd9ea162b4bf35fae6cd Mon Sep 17 00:00:00 2001 From: NotThorny <107363768+NotThorny@users.noreply.github.com> Date: Fri, 14 Nov 2025 02:09:10 -0700 Subject: [PATCH] Add profiles --- package.json | 2 +- src-tauri/lang/en.json | 6 +- src-tauri/src/config.rs | 21 ++++-- src-tauri/src/main.rs | 20 +++++- src-tauri/src/patch.rs | 2 +- src-tauri/tauri.conf.json | 2 +- src/ui/Main.tsx | 2 + src/ui/components/ServerLaunchSection.css | 21 +++++- src/ui/components/ServerLaunchSection.tsx | 45 +++++++++++- src/ui/components/menu/Options.css | 5 ++ src/ui/components/menu/Options.tsx | 85 +++++++++++++++++++---- src/ui/components/news/NewsSection.tsx | 2 +- src/utils/configuration.ts | 74 +++++++++++++++++++- 13 files changed, 260 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index b02aa03..f436dcf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cultivation", - "version": "1.6.3", + "version": "1.7.0", "private": true, "dependencies": { "@tauri-apps/api": "^1.0.0-rc.5", diff --git a/src-tauri/lang/en.json b/src-tauri/lang/en.json index 7212e2e..4b481c9 100644 --- a/src-tauri/lang/en.json +++ b/src-tauri/lang/en.json @@ -1,7 +1,7 @@ { "lang_name": "English", "main": { - "title": "Cultivation", + "title": "Cultivation: Thorny Edition", "launch_button": "Launch", "gc_enable": "Connect to Grasscutter", "https_enable": "Use HTTPS", @@ -39,7 +39,9 @@ "web_cache": "Delete webCaches folder", "launch_args": "Launch Args", "offline_mode": "Offline Mode", - "fix_res": "Fix Login Timeout" + "fix_res": "Fix Login Timeout", + "show_version": "Show game version in buttons", + "save_profile": "Save profile" }, "downloads": { "grasscutter_fullbuild": "Download Grasscutter 4.0 All-in-One", diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index 3c806c4..28789d8 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -30,20 +30,33 @@ pub struct Configuration { pub redirect_more: Option, pub launch_args: Option, pub offline_mode: Option, + pub show_version: Option, + pub profile: Option, } -pub fn config_path() -> PathBuf { +pub fn config_path(profile: String) -> PathBuf { let mut path = tauri::api::path::data_dir().unwrap(); path.push("cultivation"); - path.push("configuration.json"); + if profile.as_str() == "default" { + path.push("configuration.json"); + } else { + path.push("profile"); + path.push(profile); + } path } -pub fn get_config() -> Configuration { - let path = config_path(); +pub fn get_config(profile_name: String) -> Configuration { + let path = config_path(profile_name); let config = std::fs::read_to_string(path).unwrap_or("{}".to_string()); let config: Configuration = serde_json::from_str(&config).unwrap_or_default(); + let default = String::from("default"); + let prof = config.profile.as_ref().unwrap_or(&default); + if *prof != String::from("default") { + get_config(prof.clone()); + } + config } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 718305b..8e74e1d 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -101,7 +101,7 @@ async fn parse_args(inp: &Vec) -> Result { args.parse(inp).unwrap(); - let config = config::get_config(); + let config = config::get_config(String::from("default")); if args.value_of("help")? { println!("{}", args.full_usage()); @@ -207,6 +207,7 @@ fn main() -> Result<(), ArgsError> { is_grasscutter_running, restart_grasscutter, get_theme_list, + get_profile_list, system_helpers::run_command, system_helpers::run_program, system_helpers::run_program_args, @@ -536,3 +537,20 @@ async fn get_theme_list(data_dir: String) -> Vec> { themes } + +#[tauri::command] +async fn get_profile_list(data_dir: String) -> Vec { + let profile_loc = format!("{}/profiles", data_dir); + + // Ensure folder exists + if !std::path::Path::new(&profile_loc).exists() { + std::fs::create_dir_all(&profile_loc).unwrap(); + } + + let mut p_list = Vec::new(); + for entry in std::fs::read_dir(&profile_loc).unwrap() { + p_list.push(entry.unwrap().file_name().into_string().unwrap()); + } + + p_list +} diff --git a/src-tauri/src/patch.rs b/src-tauri/src/patch.rs index bff8bb7..36f5907 100644 --- a/src-tauri/src/patch.rs +++ b/src-tauri/src/patch.rs @@ -349,7 +349,7 @@ pub async fn unpatch_game() -> bool { } pub async fn get_game_rsa_path() -> Option { - let config = config::get_config(); + let config = config::get_config(String::from("default")); config.game_install_path.as_ref()?; diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index e189ef4..6be4fd2 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -7,7 +7,7 @@ }, "package": { "productName": "Cultivation", - "version": "1.6.3" + "version": "1.7.0" }, "tauri": { "allowlist": { diff --git a/src/ui/Main.tsx b/src/ui/Main.tsx index 475776f..84076b6 100644 --- a/src/ui/Main.tsx +++ b/src/ui/Main.tsx @@ -170,6 +170,8 @@ export class Main extends React.Component { } // Ensure old configs are updated to use RSA + const updatedProfile = await getConfigOption('profile') + await setConfigOption('profile', updatedProfile) const updatedConfig = await getConfigOption('patch_rsa') await setConfigOption('patch_rsa', updatedConfig) diff --git a/src/ui/components/ServerLaunchSection.css b/src/ui/components/ServerLaunchSection.css index 3fb6c8a..1b715a9 100644 --- a/src/ui/components/ServerLaunchSection.css +++ b/src/ui/components/ServerLaunchSection.css @@ -14,7 +14,7 @@ } #playButton > div { - margin-bottom: 6px; + margin-bottom: 2px; } #playButton .BigButton { @@ -26,10 +26,27 @@ #serverControls { color: white; - + display: flex; + justify-content: space-between; + flex-direction: row; text-shadow: 1px 1px 8px black; } +#menuOptionsContainerProfiles { + justify-self: right; + align-self: right; + padding-left: 8px; + width: min-content; +} + +#serverControls select { + width: 150px; + height: 30px; + border: none; + border-bottom: 2px solid #cecece; + border-radius: 6px; +} + .BottomSection .CheckboxDisplay { border-color: #c5c5c5; background: #fff; diff --git a/src/ui/components/ServerLaunchSection.tsx b/src/ui/components/ServerLaunchSection.tsx index 8840174..8c4acdb 100644 --- a/src/ui/components/ServerLaunchSection.tsx +++ b/src/ui/components/ServerLaunchSection.tsx @@ -3,7 +3,7 @@ import Checkbox from './common/Checkbox' import BigButton from './common/BigButton' import TextInput from './common/TextInput' import HelpButton from './common/HelpButton' -import { getConfig, saveConfig, setConfigOption } from '../../utils/configuration' +import { getConfig, saveConfig, setConfigOption, setProfileOption } from '../../utils/configuration' import { translate } from '../../utils/language' import { invoke } from '@tauri-apps/api/tauri' @@ -43,6 +43,8 @@ interface IState { migotoSet: boolean unElevated: boolean + profile: string + profiles: string[] } export default class ServerLaunchSection extends React.Component { @@ -66,6 +68,8 @@ export default class ServerLaunchSection extends React.Component akebiSet: false, migotoSet: false, unElevated: false, + profile: 'default', + profiles: ['default'], } this.toggleGrasscutter = this.toggleGrasscutter.bind(this) @@ -75,6 +79,7 @@ export default class ServerLaunchSection extends React.Component this.toggleHttps = this.toggleHttps.bind(this) this.launchServer = this.launchServer.bind(this) this.setButtonLabel = this.setButtonLabel.bind(this) + this.setProfile = this.setProfile.bind(this) listen('start_grasscutter', async () => { this.launchServer() @@ -102,6 +107,8 @@ export default class ServerLaunchSection extends React.Component akebiSet: config.akebi_path !== '', migotoSet: config.migoto_path !== '', unElevated: config.un_elevated || false, + profile: config.profile || 'default', + profiles: (await this.getProfileList()).map((t) => t), }) this.setButtonLabel() @@ -393,6 +400,25 @@ export default class ServerLaunchSection extends React.Component } } + async setProfile(value: string) { + this.setState({ profile: value }) + await setProfileOption('profile', value) + window.location.reload() + } + + async getProfileList() { + const profiles: string[] = await invoke('get_profile_list', { + dataDir: `${await dataDir()}/cultivation`, + }) + const list = ['default'] + + profiles.forEach((t) => { + list.push(t.split('.json')[0]) + }) + + return list + } + render() { return (
@@ -403,6 +429,23 @@ export default class ServerLaunchSection extends React.Component onChange={this.toggleGrasscutter} checked={this.state.grasscutterEnabled} /> +
{this.state.grasscutterEnabled && ( diff --git a/src/ui/components/menu/Options.css b/src/ui/components/menu/Options.css index 58b4f9e..a4cf02e 100644 --- a/src/ui/components/menu/Options.css +++ b/src/ui/components/menu/Options.css @@ -20,3 +20,8 @@ .OptionSection .HelpButton img { filter: invert(0%) sepia(91%) saturate(7464%) hue-rotate(101deg) brightness(0%) contrast(107%); } + +input#profile_name { + height: 25px; + border-radius: 6px; +} diff --git a/src/ui/components/menu/Options.tsx b/src/ui/components/menu/Options.tsx index cdb08c9..2237e5d 100644 --- a/src/ui/components/menu/Options.tsx +++ b/src/ui/components/menu/Options.tsx @@ -4,7 +4,14 @@ import { dataDir } from '@tauri-apps/api/path' import DirInput from '../common/DirInput' import Menu from './Menu' import Tr, { getLanguages } from '../../../utils/language' -import { setConfigOption, getConfig, getConfigOption, Configuration } from '../../../utils/configuration' +import { + setConfigOption, + getConfig, + getConfigOption, + Configuration, + saveNewProfileConfig, + setProfileOption, +} from '../../../utils/configuration' import Checkbox from '../common/Checkbox' import Divider from './Divider' import { getThemeList } from '../../../utils/themes' @@ -57,6 +64,8 @@ interface IState { launch_args: string offline_mode: boolean newer_game: boolean + show_version: boolean + profile_name: string // Linux stuff grasscutter_elevation: string @@ -95,6 +104,8 @@ export default class Options extends React.Component { launch_args: '', offline_mode: false, newer_game: false, + show_version: true, + profile_name: '', // Linux stuff grasscutter_elevation: GrasscutterElevation.None, @@ -118,6 +129,9 @@ export default class Options extends React.Component { this.addMigotoDelay = this.addMigotoDelay.bind(this) this.toggleUnElevatedGame = this.toggleUnElevatedGame.bind(this) this.setLaunchArgs = this.setLaunchArgs.bind(this) + this.toggleShowVersion = this.toggleShowVersion.bind(this) + this.setProfileName = this.setProfileName.bind(this) + this.saveProfile = this.saveProfile.bind(this) } async componentDidMount() { @@ -156,6 +170,7 @@ export default class Options extends React.Component { launch_args: config.launch_args, offline_mode: config.offline_mode || false, newer_game: config.newer_game || false, + show_version: config.show_version || false, // Linux stuff grasscutter_elevation: config.grasscutter_elevation || GrasscutterElevation.None, @@ -350,6 +365,17 @@ export default class Options extends React.Component { }) } + async toggleShowVersion() { + const changedVal = !(await getConfigOption('show_version')) + await setConfigOption('show_version', changedVal) + + this.setState({ + show_version: changedVal, + }) + + emit('set_config', { show_version: changedVal }) + } + async setGCElevation(value: string) { setConfigOption('grasscutter_elevation', value) @@ -487,6 +513,28 @@ export default class Options extends React.Component { }) } + setProfileName(text: string) { + this.setState({ + profile_name: text, + }) + } + + async saveProfile() { + if (this.state.profile_name == '') { + alert('No name set') + return + } + + const config = await getConfig() + await saveNewProfileConfig(config, this.state.profile_name) + await setProfileOption('profile', this.state.profile_name) + this.setState({ + profile_name: '', + }) + + window.location.reload() + } + render() { return ( @@ -659,6 +707,24 @@ export default class Options extends React.Component { +
+ + + + {'Save'} + +
+ + + - - {/*