mirror of
https://github.com/Grasscutters/Cultivation.git
synced 2025-12-15 16:44:43 +01:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -16,8 +16,9 @@
|
|||||||
"grasscutter_jar": "Set Grasscutter JAR",
|
"grasscutter_jar": "Set Grasscutter JAR",
|
||||||
"java_path": "Set Custom Java Path",
|
"java_path": "Set Custom Java Path",
|
||||||
"grasscutter_with_game": "Automatically launch Grasscutter with game",
|
"grasscutter_with_game": "Automatically launch Grasscutter with game",
|
||||||
"language": "Select Language (requires restart)",
|
"language": "Select Language",
|
||||||
"background": "Set Custom Background (link or image file)"
|
"background": "Set Custom Background (link or image file)",
|
||||||
|
"theme": "Set Theme"
|
||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"grasscutter_stable_data": "Download Grasscutter Stable Data",
|
"grasscutter_stable_data": "Download Grasscutter Stable Data",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ windows_subsystem = "windows"
|
|||||||
)]
|
)]
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use std::sync::Mutex;
|
use std::{sync::Mutex, collections::HashMap};
|
||||||
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use sysinfo::{System, SystemExt};
|
use sysinfo::{System, SystemExt};
|
||||||
@@ -44,11 +44,13 @@ fn main() {
|
|||||||
get_bg_file,
|
get_bg_file,
|
||||||
base64_decode,
|
base64_decode,
|
||||||
is_game_running,
|
is_game_running,
|
||||||
|
get_theme_list,
|
||||||
system_helpers::run_command,
|
system_helpers::run_command,
|
||||||
system_helpers::run_program,
|
system_helpers::run_program,
|
||||||
system_helpers::run_jar,
|
system_helpers::run_jar,
|
||||||
system_helpers::open_in_browser,
|
system_helpers::open_in_browser,
|
||||||
system_helpers::copy_file,
|
system_helpers::copy_file,
|
||||||
|
system_helpers::install_location,
|
||||||
proxy::set_proxy_addr,
|
proxy::set_proxy_addr,
|
||||||
proxy::generate_ca_files,
|
proxy::generate_ca_files,
|
||||||
unzip::unzip,
|
unzip::unzip,
|
||||||
@@ -143,6 +145,42 @@ async fn req_get(url: String) -> String {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn get_theme_list(dataDir: String) -> Vec<HashMap<String, String>> {
|
||||||
|
let theme_loc = format!("{}/themes", dataDir);
|
||||||
|
|
||||||
|
// Ensure folder exists
|
||||||
|
if !std::path::Path::new(&theme_loc).exists() {
|
||||||
|
std::fs::create_dir_all(&theme_loc).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read each index.json folder in each theme folder
|
||||||
|
let mut themes = Vec::new();
|
||||||
|
|
||||||
|
for entry in std::fs::read_dir(&theme_loc).unwrap() {
|
||||||
|
let entry = entry.unwrap();
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
if path.is_dir() {
|
||||||
|
let index_path = format!("{}/index.json", path.to_str().unwrap());
|
||||||
|
|
||||||
|
if std::path::Path::new(&index_path).exists() {
|
||||||
|
let theme_json = std::fs::read_to_string(&index_path).unwrap();
|
||||||
|
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
|
map.insert("json".to_string(), theme_json);
|
||||||
|
map.insert("path".to_string(), path.to_str().unwrap().to_string());
|
||||||
|
|
||||||
|
// Push key-value pair containing "json" and "path"
|
||||||
|
themes.push(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return themes;
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn get_bg_file(bg_path: String, appdata: String) -> String {
|
async fn get_bg_file(bg_path: String, appdata: String) -> String {
|
||||||
let copy_loc = appdata;
|
let copy_loc = appdata;
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ pub fn run_command(command: String) {
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn run_jar(path: String, execute_in: String, java_path: String) {
|
pub fn run_jar(path: String, execute_in: String, java_path: String) {
|
||||||
let command = if java_path.is_empty() {
|
let command = if java_path.is_empty() {
|
||||||
format!("java -jar {}", path)
|
format!("java -jar \"{}\"", path)
|
||||||
} else {
|
} else {
|
||||||
format!("\"{}\" -jar {}", java_path, path)
|
format!("\"{}\" -jar \"{}\"", java_path, path)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Open the program from the specified path.
|
// Open the program from the specified path.
|
||||||
@@ -77,6 +77,7 @@ pub fn copy_file(path: String, new_path: String) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
pub fn install_location() -> String {
|
pub fn install_location() -> String {
|
||||||
let mut exe_path = std::env::current_exe().unwrap();
|
let mut exe_path = std::env::current_exe().unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": {
|
"security": {
|
||||||
"csp": "default-src 'self'; img-src 'self' asset: https://asset.localhost"
|
"csp": "default-src 'self' https://asset.localhost; img-src 'self'; img-src https://* asset: https://asset.localhost"
|
||||||
},
|
},
|
||||||
"updater": {
|
"updater": {
|
||||||
"active": false
|
"active": false
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ import Game from './components/menu/Game'
|
|||||||
import RightBar from './components/RightBar'
|
import RightBar from './components/RightBar'
|
||||||
import { getConfigOption, setConfigOption } from '../utils/configuration'
|
import { getConfigOption, setConfigOption } from '../utils/configuration'
|
||||||
import { invoke } from '@tauri-apps/api'
|
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 { appWindow } from '@tauri-apps/api/window'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||||
|
import { getTheme, loadTheme } from '../utils/themes'
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
[key: string]: never;
|
[key: string]: never;
|
||||||
@@ -79,8 +80,15 @@ class App extends React.Component<IProps, IState> {
|
|||||||
const cert_generated = await getConfigOption('cert_generated')
|
const cert_generated = await getConfigOption('cert_generated')
|
||||||
const game_exe = await getConfigOption('game_install_path')
|
const game_exe = await getConfigOption('game_install_path')
|
||||||
const custom_bg = await getConfigOption('customBackground')
|
const custom_bg = await getConfigOption('customBackground')
|
||||||
const game_path = game_exe.substring(0, game_exe.replace(/\\/g, '/').lastIndexOf('/'))
|
const game_path = game_exe?.substring(0, game_exe.replace(/\\/g, '/').lastIndexOf('/')) || ''
|
||||||
const root_path = game_path.substring(0, game_path.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 && theme !== 'default') {
|
||||||
|
const themeObj = await getTheme(theme)
|
||||||
|
loadTheme(themeObj, document)
|
||||||
|
}
|
||||||
|
|
||||||
if(!custom_bg || !/png|jpg|jpeg$/.test(custom_bg)) {
|
if(!custom_bg || !/png|jpg|jpeg$/.test(custom_bg)) {
|
||||||
if(game_path) {
|
if(game_path) {
|
||||||
@@ -98,8 +106,12 @@ class App extends React.Component<IProps, IState> {
|
|||||||
const isUrl = /^(?:http(s)?:\/\/)/gm.test(custom_bg)
|
const isUrl = /^(?:http(s)?:\/\/)/gm.test(custom_bg)
|
||||||
|
|
||||||
if (!isUrl) {
|
if (!isUrl) {
|
||||||
|
const isValid = await invoke('dir_exists', {
|
||||||
|
path: custom_bg
|
||||||
|
})
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
bgFile: convertFileSrc(custom_bg)
|
bgFile: isValid ? convertFileSrc(custom_bg) : DEFAULT_BG
|
||||||
}, this.forceUpdate)
|
}, this.forceUpdate)
|
||||||
} else {
|
} else {
|
||||||
// Check if URL returns a valid image.
|
// Check if URL returns a valid image.
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ interface IProps {
|
|||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
folder?: boolean
|
folder?: boolean
|
||||||
|
customClearBehaviour?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
@@ -94,7 +95,9 @@ export default class DirInput extends React.Component<IProps, IState> {
|
|||||||
this.setState({ value: text })
|
this.setState({ value: text })
|
||||||
|
|
||||||
if (this.props.onChange) this.props.onChange(text)
|
if (this.props.onChange) this.props.onChange(text)
|
||||||
|
this.forceUpdate()
|
||||||
}}
|
}}
|
||||||
|
customClearBehaviour={this.props.customClearBehaviour}
|
||||||
/>
|
/>
|
||||||
<div className="FileSelectIcon" onClick={this.handleIconClick}>
|
<div className="FileSelectIcon" onClick={this.handleIconClick}>
|
||||||
<img src={File} />
|
<img src={File} />
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ interface IProps {
|
|||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
id?: string;
|
id?: string;
|
||||||
clearable?: boolean;
|
clearable?: boolean;
|
||||||
|
customClearBehaviour?: () => void;
|
||||||
style?: {
|
style?: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
@@ -29,10 +30,6 @@ export default class TextInput extends React.Component<IProps, IState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props: IProps, state: IState) {
|
|
||||||
return { value: props.value || '' }
|
|
||||||
}
|
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
if (this.props.initalValue) {
|
if (this.props.initalValue) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -41,6 +38,10 @@ export default class TextInput extends React.Component<IProps, IState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(props: IProps, state: IState) {
|
||||||
|
return { value: props.value || state.value }
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="TextInputWrapper" style={this.props.style || {}}>
|
<div className="TextInputWrapper" style={this.props.style || {}}>
|
||||||
@@ -51,6 +52,9 @@ export default class TextInput extends React.Component<IProps, IState> {
|
|||||||
{
|
{
|
||||||
this.props.clearable ?
|
this.props.clearable ?
|
||||||
<div className="TextClear" onClick={() => {
|
<div className="TextClear" onClick={() => {
|
||||||
|
// Run custom behaviour first
|
||||||
|
if (this.props.customClearBehaviour) return this.props.customClearBehaviour()
|
||||||
|
|
||||||
this.setState({ value: '' })
|
this.setState({ value: '' })
|
||||||
|
|
||||||
if (this.props.onChange) this.props.onChange('')
|
if (this.props.onChange) this.props.onChange('')
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { invoke } from '@tauri-apps/api'
|
||||||
|
import { dataDir } from '@tauri-apps/api/path'
|
||||||
import DirInput from '../common/DirInput'
|
import DirInput from '../common/DirInput'
|
||||||
import Menu from './Menu'
|
import Menu from './Menu'
|
||||||
import Tr, { getLanguages } from '../../../utils/language'
|
import Tr, { getLanguages } from '../../../utils/language'
|
||||||
import './Options.css'
|
|
||||||
import { setConfigOption, getConfig, getConfigOption } from '../../../utils/configuration'
|
import { setConfigOption, getConfig, getConfigOption } from '../../../utils/configuration'
|
||||||
import Checkbox from '../common/Checkbox'
|
import Checkbox from '../common/Checkbox'
|
||||||
import Divider from './Divider'
|
import Divider from './Divider'
|
||||||
import { invoke } from '@tauri-apps/api'
|
import { getThemeList } from '../../../utils/themes'
|
||||||
import { dataDir } from '@tauri-apps/api/path'
|
|
||||||
|
import './Options.css'
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
closeFn: () => void;
|
closeFn: () => void;
|
||||||
@@ -21,6 +23,8 @@ interface IState {
|
|||||||
language_options: { [key: string]: string }[],
|
language_options: { [key: string]: string }[],
|
||||||
current_language: string
|
current_language: string
|
||||||
bg_url_or_path: string
|
bg_url_or_path: string
|
||||||
|
themes: string[]
|
||||||
|
theme: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Options extends React.Component<IProps, IState> {
|
export default class Options extends React.Component<IProps, IState> {
|
||||||
@@ -34,9 +38,14 @@ export default class Options extends React.Component<IProps, IState> {
|
|||||||
grasscutter_with_game: false,
|
grasscutter_with_game: false,
|
||||||
language_options: [],
|
language_options: [],
|
||||||
current_language: 'en',
|
current_language: 'en',
|
||||||
bg_url_or_path: ''
|
bg_url_or_path: '',
|
||||||
|
themes: ['default'],
|
||||||
|
theme: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setGameExec = this.setGameExec.bind(this)
|
||||||
|
this.setGrasscutterJar = this.setGrasscutterJar.bind(this)
|
||||||
|
this.setJavaPath = this.setJavaPath.bind(this)
|
||||||
this.toggleGrasscutterWithGame = this.toggleGrasscutterWithGame.bind(this)
|
this.toggleGrasscutterWithGame = this.toggleGrasscutterWithGame.bind(this)
|
||||||
this.setCustomBackground = this.setCustomBackground.bind(this)
|
this.setCustomBackground = this.setCustomBackground.bind(this)
|
||||||
}
|
}
|
||||||
@@ -52,7 +61,9 @@ export default class Options extends React.Component<IProps, IState> {
|
|||||||
grasscutter_with_game: config.grasscutter_with_game || false,
|
grasscutter_with_game: config.grasscutter_with_game || false,
|
||||||
language_options: languages,
|
language_options: languages,
|
||||||
current_language: config.language || 'en',
|
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()
|
this.forceUpdate()
|
||||||
@@ -60,18 +71,36 @@ export default class Options extends React.Component<IProps, IState> {
|
|||||||
|
|
||||||
setGameExec(value: string) {
|
setGameExec(value: string) {
|
||||||
setConfigOption('game_install_path', value)
|
setConfigOption('game_install_path', value)
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
game_install_path: value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setGrasscutterJar(value: string) {
|
setGrasscutterJar(value: string) {
|
||||||
setConfigOption('grasscutter_path', value)
|
setConfigOption('grasscutter_path', value)
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
grasscutter_path: value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setJavaPath(value: string) {
|
setJavaPath(value: string) {
|
||||||
setConfigOption('java_path', value)
|
setConfigOption('java_path', value)
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
java_path: value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setLanguage(value: string) {
|
async setLanguage(value: string) {
|
||||||
setConfigOption('language', value)
|
await setConfigOption('language', value)
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
async setTheme(value: string) {
|
||||||
|
await setConfigOption('theme', value)
|
||||||
|
window.location.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
async toggleGrasscutterWithGame() {
|
async toggleGrasscutterWithGame() {
|
||||||
@@ -140,6 +169,28 @@ export default class Options extends React.Component<IProps, IState> {
|
|||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
|
<div className='OptionSection'>
|
||||||
|
<div className='OptionLabel'>
|
||||||
|
<Tr text="options.theme" />
|
||||||
|
</div>
|
||||||
|
<div className='OptionValue'>
|
||||||
|
<select value={this.state.theme} onChange={(event) => {
|
||||||
|
this.setTheme(event.target.value)
|
||||||
|
}}>
|
||||||
|
{this.state.themes.map(t => (
|
||||||
|
<option
|
||||||
|
key={t}
|
||||||
|
value={t}>
|
||||||
|
|
||||||
|
{t}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
<div className='OptionSection'>
|
<div className='OptionSection'>
|
||||||
<div className='OptionLabel'>
|
<div className='OptionLabel'>
|
||||||
<Tr text="options.java_path" />
|
<Tr text="options.java_path" />
|
||||||
@@ -154,7 +205,17 @@ export default class Options extends React.Component<IProps, IState> {
|
|||||||
<Tr text="options.background" />
|
<Tr text="options.background" />
|
||||||
</div>
|
</div>
|
||||||
<div className='OptionValue'>
|
<div className='OptionValue'>
|
||||||
<DirInput onChange={this.setCustomBackground} value={this.state?.bg_url_or_path} extensions={['png', 'jpg', 'jpeg']} readonly={false} />
|
<DirInput
|
||||||
|
onChange={this.setCustomBackground}
|
||||||
|
value={this.state?.bg_url_or_path}
|
||||||
|
extensions={['png', 'jpg', 'jpeg']}
|
||||||
|
readonly={false}
|
||||||
|
clearable={true}
|
||||||
|
customClearBehaviour={async () => {
|
||||||
|
await setConfigOption('customBackground', '')
|
||||||
|
window.location.reload()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.NewsContent tbody::-webkit-scrollbar {
|
.NewsContent tbody::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ let defaultConfig: Configuration
|
|||||||
(async() => {
|
(async() => {
|
||||||
defaultConfig = {
|
defaultConfig = {
|
||||||
toggle_grasscutter: false,
|
toggle_grasscutter: false,
|
||||||
game_install_path: 'C:\\Program Files\\Genshin Impact\\Genshin Impact game\\Genshin Impact.exe',
|
game_install_path: 'C:\\Program Files\\Genshin Impact\\Genshin Impact game\\GenshinImpact.exe',
|
||||||
grasscutter_with_game: false,
|
grasscutter_with_game: false,
|
||||||
grasscutter_path: '',
|
grasscutter_path: '',
|
||||||
java_path: '',
|
java_path: '',
|
||||||
@@ -18,6 +18,7 @@ let defaultConfig: Configuration
|
|||||||
language: 'en',
|
language: 'en',
|
||||||
customBackground: '',
|
customBackground: '',
|
||||||
cert_generated: false,
|
cert_generated: false,
|
||||||
|
theme: 'default'
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ export interface Configuration {
|
|||||||
language: string
|
language: string
|
||||||
customBackground: string
|
customBackground: string
|
||||||
cert_generated: boolean
|
cert_generated: boolean
|
||||||
|
theme: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setConfigOption(key: string, value: any): Promise<void> {
|
export async function setConfigOption(key: string, value: any): Promise<void> {
|
||||||
|
|||||||
128
src/utils/themes.ts
Normal file
128
src/utils/themes.ts
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import { invoke } from '@tauri-apps/api'
|
||||||
|
import { dataDir } from '@tauri-apps/api/path'
|
||||||
|
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||||
|
|
||||||
|
interface Theme {
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
description: string
|
||||||
|
|
||||||
|
// Included custom CSS and JS files
|
||||||
|
includes: {
|
||||||
|
css: string[]
|
||||||
|
js: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
customBackgroundURL?: string
|
||||||
|
customBackgroundPath?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BackendThemeList {
|
||||||
|
json: string
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
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', {
|
||||||
|
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
|
||||||
|
|
||||||
|
try {
|
||||||
|
obj = JSON.parse(t.json)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
list.push({ ...obj, path: t.path })
|
||||||
|
})
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user