diff --git a/src-tauri/lang/en.json b/src-tauri/lang/en.json index 4696d01..04ae9da 100644 --- a/src-tauri/lang/en.json +++ b/src-tauri/lang/en.json @@ -16,8 +16,9 @@ "grasscutter_jar": "Set Grasscutter JAR", "java_path": "Set Custom Java Path", "grasscutter_with_game": "Automatically launch Grasscutter with game", - "language": "Select Language (requires restart)", - "background": "Set Custom Background (link or image file)" + "language": "Select Language", + "background": "Set Custom Background (link or image file)", + "theme": "Set Theme" }, "downloads": { "grasscutter_stable_data": "Download Grasscutter Stable Data", diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 3576fa7..0f1aae1 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -50,6 +50,7 @@ fn main() { system_helpers::run_jar, system_helpers::open_in_browser, system_helpers::copy_file, + system_helpers::install_location, proxy::set_proxy_addr, proxy::generate_ca_files, unzip::unzip, @@ -145,8 +146,8 @@ async fn req_get(url: String) -> String { } #[tauri::command] -async fn get_theme_list() -> Vec> { - let theme_loc = format!("{}/themes", system_helpers::install_location()); +async fn get_theme_list(dataDir: String) -> Vec> { + let theme_loc = format!("{}/themes", dataDir); // Ensure folder exists if !std::path::Path::new(&theme_loc).exists() { diff --git a/src-tauri/src/system_helpers.rs b/src-tauri/src/system_helpers.rs index 8beb1e1..bffed4c 100644 --- a/src-tauri/src/system_helpers.rs +++ b/src-tauri/src/system_helpers.rs @@ -77,6 +77,7 @@ pub fn copy_file(path: String, new_path: String) -> bool { } } +#[tauri::command] pub fn install_location() -> String { let mut exe_path = std::env::current_exe().unwrap(); diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 03b9cf6..0f36bd0 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -18,9 +18,10 @@ import Game from './components/menu/Game' import RightBar from './components/RightBar' import { getConfigOption, setConfigOption } from '../utils/configuration' import { invoke } from '@tauri-apps/api' -import { dataDir } from '@tauri-apps/api/path' +import { appDir, dataDir } from '@tauri-apps/api/path' import { appWindow } from '@tauri-apps/api/window' import { convertFileSrc } from '@tauri-apps/api/tauri' +import { getTheme, loadTheme } from '../utils/themes' interface IProps { [key: string]: never; @@ -82,6 +83,13 @@ class App extends React.Component { const game_path = game_exe.substring(0, game_exe.replace(/\\/g, '/').lastIndexOf('/')) const root_path = game_path.substring(0, game_path.replace(/\\/g, '/').lastIndexOf('/')) + // Load a theme if it exists + const theme = await getConfigOption('theme') + if (theme) { + const themeObj = await getTheme(theme) + loadTheme(themeObj, document) + } + if(!custom_bg || !/png|jpg|jpeg$/.test(custom_bg)) { if(game_path) { // Get the bg by invoking, then set the background to that bg. diff --git a/src/ui/components/menu/Options.tsx b/src/ui/components/menu/Options.tsx index d74a086..1498823 100644 --- a/src/ui/components/menu/Options.tsx +++ b/src/ui/components/menu/Options.tsx @@ -1,13 +1,15 @@ import React from 'react' +import { invoke } from '@tauri-apps/api' +import { dataDir } from '@tauri-apps/api/path' import DirInput from '../common/DirInput' import Menu from './Menu' import Tr, { getLanguages } from '../../../utils/language' -import './Options.css' import { setConfigOption, getConfig, getConfigOption } from '../../../utils/configuration' import Checkbox from '../common/Checkbox' import Divider from './Divider' -import { invoke } from '@tauri-apps/api' -import { dataDir } from '@tauri-apps/api/path' +import { getThemeList } from '../../../utils/themes' + +import './Options.css' interface IProps { closeFn: () => void; @@ -21,6 +23,8 @@ interface IState { language_options: { [key: string]: string }[], current_language: string bg_url_or_path: string + themes: string[] + theme: string } export default class Options extends React.Component { @@ -34,7 +38,9 @@ export default class Options extends React.Component { grasscutter_with_game: false, language_options: [], current_language: 'en', - bg_url_or_path: '' + bg_url_or_path: '', + themes: ['default'], + theme: '' } this.toggleGrasscutterWithGame = this.toggleGrasscutterWithGame.bind(this) @@ -52,7 +58,9 @@ export default class Options extends React.Component { grasscutter_with_game: config.grasscutter_with_game || false, language_options: languages, current_language: config.language || 'en', - bg_url_or_path: config.customBackground || '' + bg_url_or_path: config.customBackground || '', + themes: (await getThemeList()).map(t => t.name), + theme: config.theme || 'default' }) this.forceUpdate() @@ -70,8 +78,14 @@ export default class Options extends React.Component { setConfigOption('java_path', value) } - setLanguage(value: string) { - setConfigOption('language', value) + async setLanguage(value: string) { + await setConfigOption('language', value) + window.location.reload() + } + + async setTheme(value: string) { + await setConfigOption('theme', value) + window.location.reload() } async toggleGrasscutterWithGame() { @@ -140,6 +154,28 @@ export default class Options extends React.Component { +
+
+ +
+
+ +
+
+ + +
diff --git a/src/utils/configuration.ts b/src/utils/configuration.ts index 4594d4a..2893655 100644 --- a/src/utils/configuration.ts +++ b/src/utils/configuration.ts @@ -18,6 +18,7 @@ let defaultConfig: Configuration language: 'en', customBackground: '', cert_generated: false, + theme: 'default' } })() @@ -37,6 +38,7 @@ export interface Configuration { language: string customBackground: string cert_generated: boolean + theme: string; } export async function setConfigOption(key: string, value: any): Promise { diff --git a/src/utils/themes.ts b/src/utils/themes.ts index 462959c..e0c3309 100644 --- a/src/utils/themes.ts +++ b/src/utils/themes.ts @@ -1,4 +1,6 @@ import { invoke } from '@tauri-apps/api' +import { dataDir } from '@tauri-apps/api/path' +import { convertFileSrc } from '@tauri-apps/api/tauri' interface Theme { name: string @@ -24,10 +26,34 @@ interface ThemeList extends Theme { path: string } +const defaultTheme = { + name: 'default', + version: '1.0.0', + description: 'Default theme', + includes: { + css: [], + js: [] + }, + path: 'default' +} export async function getThemeList() { // Do some invoke to backend to get the theme list - const themes = await invoke('get_theme_list') as BackendThemeList[] - const list: ThemeList[] = [] + const themes = await invoke('get_theme_list', { + dataDir: `${await dataDir()}/cultivation` + }) as BackendThemeList[] + const list: ThemeList[] = [ + // ALWAYS include default theme + { + name: 'default', + version: '1.0.0', + description: 'Default theme', + includes: { + css: [], + js: [] + }, + path: 'default' + } + ] themes.forEach(t => { let obj @@ -44,6 +70,59 @@ export async function getThemeList() { return list } -export async function loadTheme(theme: string) { - // Do some invoke to backend to load the theme +export async function getTheme(name: string) { + const themes = await getThemeList() + + return themes.find(t => t.name === name) || defaultTheme +} + +export async function loadTheme(theme: ThemeList, document: Document) { + // We are going to dynamically load stylesheets into the document + const head = document.head + + // Get all CSS includes + const cssIncludes = theme.includes.css + const jsIncludes = theme.includes.js + + // Load CSS files + cssIncludes.forEach(css => { + if (!css) return + + const link = document.createElement('link') + + link.rel = 'stylesheet' + link.href = convertFileSrc(theme.path + '/' + css) + head.appendChild(link) + }) + + // Load JS files + jsIncludes.forEach(js => { + if (!js) return + + const script = document.createElement('script') + + script.src = convertFileSrc(theme.path + '/' + js) + head.appendChild(script) + }) + + // Set custom background + if (theme.customBackgroundURL) { + document.body.style.backgroundImage = `url('${theme.customBackgroundURL}')` + } + + // Set custom background + if (theme.customBackgroundPath) { + const bgPath = await dataDir() + 'cultivation/grasscutter/theme.png' + + // Save the background to our data dir + await invoke('copy_file', { + path: theme.path + '/' + theme.customBackgroundPath, + new_path: bgPath + }) + + // Set the background + document.body.style.backgroundImage = `url('${bgPath}')` + } + + return } \ No newline at end of file