mirror of
https://github.com/Grasscutters/Cultivation.git
synced 2025-12-14 16:14:48 +01:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
9
src-tauri/Cargo.lock
generated
9
src-tauri/Cargo.lock
generated
@@ -2091,12 +2091,6 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "minisign-verify"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "933dca44d65cdd53b355d0b73d380a2ff5da71f87f036053188bf1eab6a19881"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -3860,7 +3854,6 @@ checksum = "a34cef4a0ebee0230baaa319b1709c4336f4add550149d2b005a9a5dc5d33617"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"attohttpc",
|
"attohttpc",
|
||||||
"base64",
|
|
||||||
"bincode",
|
"bincode",
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"dirs-next",
|
"dirs-next",
|
||||||
@@ -3874,7 +3867,6 @@ dependencies = [
|
|||||||
"heck 0.4.0",
|
"heck 0.4.0",
|
||||||
"http",
|
"http",
|
||||||
"ignore",
|
"ignore",
|
||||||
"minisign-verify",
|
|
||||||
"notify-rust",
|
"notify-rust",
|
||||||
"objc",
|
"objc",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -3906,7 +3898,6 @@ dependencies = [
|
|||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"webview2-com",
|
"webview2-com",
|
||||||
"windows 0.30.0",
|
"windows 0.30.0",
|
||||||
"zip 0.6.2",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ tauri-build = { version = "1.0.0-rc.8", features = [] }
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tauri = { version = "1.0.0-rc.9", features = ["api-all", "updater"] }
|
tauri = { version = "1.0.0-rc.9", features = ["api-all"] }
|
||||||
|
|
||||||
# Access system process info.
|
# Access system process info.
|
||||||
sysinfo = "0.23.12"
|
sysinfo = "0.23.12"
|
||||||
|
|||||||
@@ -7,9 +7,7 @@
|
|||||||
"ip_placeholder": "Server Address...",
|
"ip_placeholder": "Server Address...",
|
||||||
"port_placeholder": "Port...",
|
"port_placeholder": "Port...",
|
||||||
"files_downloading": "Files Downloading: ",
|
"files_downloading": "Files Downloading: ",
|
||||||
"files_extracting": "Files Extracting: ",
|
"files_extracting": "Files Extracting: "
|
||||||
"port_help_text": "Ensure this is the Dispatch server port, not the Game server port. This is almost always '443'.",
|
|
||||||
"game_help_text": "You do not need to use a separate copy to play with Grasscutter. This is for either downgrading to 2.6 or if you do not have the game installed."
|
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"game_exec": "Set Game Executable",
|
"game_exec": "Set Game Executable",
|
||||||
@@ -46,5 +44,14 @@
|
|||||||
"news": {
|
"news": {
|
||||||
"latest_commits": "Recent Commits",
|
"latest_commits": "Recent Commits",
|
||||||
"latest_version": "Latest Version"
|
"latest_version": "Latest Version"
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"port_help_text": "Ensure this is the Dispatch server port, not the Game server port. This is almost always '443'.",
|
||||||
|
"game_help_text": "You do not need to use a separate copy to play with Grasscutter. This is for either downgrading to 2.6 or if you do not have the game installed.",
|
||||||
|
"gc_stable_jar": "Download the current stable Grasscutter build, which includes jar file and data files.",
|
||||||
|
"gc_dev_jar": "Download the latest development Grasscutter build, which includes jar file and data files.",
|
||||||
|
"gc_stable_data": "Download the current stable Grasscutter data files, which does not come with a jar file. This is useful for updating.",
|
||||||
|
"gc_dev_data": "Download the latest development Grasscutter data files, which does not come with a jar file. This is useful for updating.",
|
||||||
|
"resources": "These are also required to run a Grasscutter server. This button will be grey if you have an existing resources folder with contents inside"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,6 +41,7 @@ pub async fn download_file(window: tauri::Window, url: &str, path: &str) -> Resu
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut downloaded: u64 = 0;
|
let mut downloaded: u64 = 0;
|
||||||
|
let mut total_downloaded: u64 = 0;
|
||||||
|
|
||||||
// File stream
|
// File stream
|
||||||
let mut stream = res.bytes_stream();
|
let mut stream = res.bytes_stream();
|
||||||
@@ -77,6 +78,8 @@ pub async fn download_file(window: tauri::Window, url: &str, path: &str) -> Resu
|
|||||||
let new = min(downloaded + (chunk.len() as u64), total_size);
|
let new = min(downloaded + (chunk.len() as u64), total_size);
|
||||||
downloaded = new;
|
downloaded = new;
|
||||||
|
|
||||||
|
total_downloaded = total_downloaded + chunk.len() as u64;
|
||||||
|
|
||||||
let mut res_hash = std::collections::HashMap::new();
|
let mut res_hash = std::collections::HashMap::new();
|
||||||
|
|
||||||
res_hash.insert(
|
res_hash.insert(
|
||||||
@@ -94,6 +97,11 @@ pub async fn download_file(window: tauri::Window, url: &str, path: &str) -> Resu
|
|||||||
path.to_string(),
|
path.to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
res_hash.insert(
|
||||||
|
"total_downloaded".to_string(),
|
||||||
|
total_downloaded.to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
// Create event to send to frontend
|
// Create event to send to frontend
|
||||||
window.emit("download_progress", &res_hash).unwrap();
|
window.emit("download_progress", &res_hash).unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,3 +23,13 @@ pub fn rename(path: String, new_name: String) {
|
|||||||
pub fn dir_exists(path: &str) -> bool {
|
pub fn dir_exists(path: &str) -> bool {
|
||||||
return fs::metadata(&path).is_ok();
|
return fs::metadata(&path).is_ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn dir_is_empty(path: &str) -> bool {
|
||||||
|
return fs::read_dir(&path).unwrap().count() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn dir_delete(path: &str) {
|
||||||
|
fs::remove_dir_all(path).unwrap();
|
||||||
|
}
|
||||||
@@ -58,6 +58,8 @@ fn main() {
|
|||||||
unzip::unzip,
|
unzip::unzip,
|
||||||
file_helpers::rename,
|
file_helpers::rename,
|
||||||
file_helpers::dir_exists,
|
file_helpers::dir_exists,
|
||||||
|
file_helpers::dir_is_empty,
|
||||||
|
file_helpers::dir_delete,
|
||||||
downloader::download_file,
|
downloader::download_file,
|
||||||
downloader::stop_download,
|
downloader::stop_download,
|
||||||
lang::get_lang,
|
lang::get_lang,
|
||||||
|
|||||||
@@ -15,6 +15,21 @@ export default class MiniDialog extends React.Component<IProps, never> {
|
|||||||
super(props)
|
super(props)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
document.addEventListener('mousedown', (evt) => {
|
||||||
|
const tgt = evt.target as HTMLElement
|
||||||
|
const isInside = tgt.closest('.MiniDialog') !== null
|
||||||
|
|
||||||
|
if (!isInside) {
|
||||||
|
this.props.closeFn()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.removeEventListener('mousedown', this.props.closeFn)
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="MiniDialog">
|
<div className="MiniDialog">
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
ip: config.last_ip || '',
|
ip: config.last_ip || '',
|
||||||
port: config.last_port || '',
|
port: config.last_port || '',
|
||||||
ipPlaceholder: await translate('main.ip_placeholder'),
|
ipPlaceholder: await translate('main.ip_placeholder'),
|
||||||
portPlaceholder: await translate('main.port_placeholder'),
|
portPlaceholder: await translate('help.port_placeholder'),
|
||||||
portHelpText: await translate('main.port_help_text')
|
portHelpText: await translate('help.port_help_text')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +104,23 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
|
|
||||||
// Connect to proxy
|
// Connect to proxy
|
||||||
await invoke('connect', { port: 8365, certificatePath: await dataDir() + '\\cultivation\\ca' })
|
await invoke('connect', { port: 8365, certificatePath: await dataDir() + '\\cultivation\\ca' })
|
||||||
|
|
||||||
|
// Open server as well if the options are set
|
||||||
|
if (config.grasscutter_with_game) {
|
||||||
|
let jarFolder = config.grasscutter_path
|
||||||
|
|
||||||
|
if (jarFolder.includes('/')) {
|
||||||
|
jarFolder = jarFolder.substring(0, config.grasscutter_path.lastIndexOf('/'))
|
||||||
|
} else {
|
||||||
|
jarFolder = jarFolder.substring(0, config.grasscutter_path.lastIndexOf('\\'))
|
||||||
|
}
|
||||||
|
|
||||||
|
await invoke('run_jar', {
|
||||||
|
path: config.grasscutter_path,
|
||||||
|
executeIn: jarFolder,
|
||||||
|
javaPath: config.java_path || ''
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Launch the program
|
// Launch the program
|
||||||
|
|||||||
@@ -20,11 +20,14 @@
|
|||||||
|
|
||||||
.HelpContents {
|
.HelpContents {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.HelpContents .MiniDialog {
|
.HelpContents .MiniDialog {
|
||||||
bottom: 80%;
|
position: absolute;
|
||||||
left: 35%;
|
|
||||||
width: 60%;
|
bottom: 40px;
|
||||||
height: 60%;
|
right: -450%;
|
||||||
|
width: 200px;
|
||||||
|
height: 120px;
|
||||||
}
|
}
|
||||||
@@ -25,3 +25,7 @@
|
|||||||
.DownloadValue .BigButtonText {
|
.DownloadValue .BigButtonText {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.DownloadMenuSection .HelpButton img {
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import Divider from './Divider'
|
|||||||
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 { listen } from '@tauri-apps/api/event'
|
import { listen } from '@tauri-apps/api/event'
|
||||||
|
import HelpButton from '../common/HelpButton'
|
||||||
|
|
||||||
const STABLE_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/stable.zip'
|
const STABLE_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/stable.zip'
|
||||||
const DEV_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/development.zip'
|
const DEV_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/development.zip'
|
||||||
@@ -73,7 +74,9 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
if (gc_path) {
|
if (gc_path) {
|
||||||
const resources_exist: boolean = await invoke('dir_exists', {
|
const resources_exist: boolean = await invoke('dir_exists', {
|
||||||
path: path + '\\resources'
|
path: path + '\\resources'
|
||||||
})
|
}) as boolean && !(await invoke('dir_is_empty', {
|
||||||
|
path: path + '\\resources'
|
||||||
|
})) as boolean
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
grasscutter_set: gc_path !== '',
|
grasscutter_set: gc_path !== '',
|
||||||
@@ -149,6 +152,15 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
async downloadResources() {
|
async downloadResources() {
|
||||||
const folder = await this.getGrasscutterFolder()
|
const folder = await this.getGrasscutterFolder()
|
||||||
this.props.downloadManager.addDownload(RESOURCES_DOWNLOAD, folder + '\\resources.zip', async () => {
|
this.props.downloadManager.addDownload(RESOURCES_DOWNLOAD, folder + '\\resources.zip', async () => {
|
||||||
|
// Delete the existing folder if it exists
|
||||||
|
if (await invoke('dir_exists', {
|
||||||
|
path: folder + '\\resources'
|
||||||
|
})) {
|
||||||
|
await invoke('dir_delete', {
|
||||||
|
path: folder + '\\resources'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
await unzip(folder + '\\resources.zip', folder + '\\', () => {
|
await unzip(folder + '\\resources.zip', folder + '\\', () => {
|
||||||
// Rename folder to resources
|
// Rename folder to resources
|
||||||
invoke('rename', {
|
invoke('rename', {
|
||||||
@@ -183,6 +195,9 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
<Tr text={
|
<Tr text={
|
||||||
this.state.grasscutter_set ? 'downloads.grasscutter_stable' : 'downloads.grasscutter_stable_update'
|
this.state.grasscutter_set ? 'downloads.grasscutter_stable' : 'downloads.grasscutter_stable_update'
|
||||||
} />
|
} />
|
||||||
|
<HelpButton>
|
||||||
|
<Tr text="help.gc_stable_jar" />
|
||||||
|
</HelpButton>
|
||||||
</div>
|
</div>
|
||||||
<div className='DownloadValue'>
|
<div className='DownloadValue'>
|
||||||
<BigButton disabled={this.state.grasscutter_downloading} onClick={this.downloadGrasscutterStable} id="grasscutterStableBtn" >
|
<BigButton disabled={this.state.grasscutter_downloading} onClick={this.downloadGrasscutterStable} id="grasscutterStableBtn" >
|
||||||
@@ -195,6 +210,9 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
<Tr text={
|
<Tr text={
|
||||||
this.state.grasscutter_set ? 'downloads.grasscutter_latest' : 'downloads.grasscutter_latest_update'
|
this.state.grasscutter_set ? 'downloads.grasscutter_latest' : 'downloads.grasscutter_latest_update'
|
||||||
} />
|
} />
|
||||||
|
<HelpButton>
|
||||||
|
<Tr text="help.gc_dev_jar" />
|
||||||
|
</HelpButton>
|
||||||
</div>
|
</div>
|
||||||
<div className='DownloadValue'>
|
<div className='DownloadValue'>
|
||||||
<BigButton disabled={this.state.grasscutter_downloading} onClick={this.downloadGrasscutterLatest} id="grasscutterLatestBtn" >
|
<BigButton disabled={this.state.grasscutter_downloading} onClick={this.downloadGrasscutterLatest} id="grasscutterLatestBtn" >
|
||||||
@@ -210,6 +228,9 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
<Tr text={
|
<Tr text={
|
||||||
this.state.grasscutter_set ? 'downloads.grasscutter_stable_data' : 'downloads.grasscutter_stable_data_update'
|
this.state.grasscutter_set ? 'downloads.grasscutter_stable_data' : 'downloads.grasscutter_stable_data_update'
|
||||||
} />
|
} />
|
||||||
|
<HelpButton>
|
||||||
|
<Tr text="help.gc_stable_data" />
|
||||||
|
</HelpButton>
|
||||||
</div>
|
</div>
|
||||||
<div className='DownloadValue'>
|
<div className='DownloadValue'>
|
||||||
<BigButton disabled={this.state.repo_downloading} onClick={this.downloadGrasscutterStableRepo} id="grasscutterStableRepo" >
|
<BigButton disabled={this.state.repo_downloading} onClick={this.downloadGrasscutterStableRepo} id="grasscutterStableRepo" >
|
||||||
@@ -222,6 +243,9 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
<Tr text={
|
<Tr text={
|
||||||
this.state.grasscutter_set ? 'downloads.grasscutter_latest_data' : 'downloads.grasscutter_latest_data_update'
|
this.state.grasscutter_set ? 'downloads.grasscutter_latest_data' : 'downloads.grasscutter_latest_data_update'
|
||||||
} />
|
} />
|
||||||
|
<HelpButton>
|
||||||
|
<Tr text="help.gc_dev_data" />
|
||||||
|
</HelpButton>
|
||||||
</div>
|
</div>
|
||||||
<div className='DownloadValue'>
|
<div className='DownloadValue'>
|
||||||
<BigButton disabled={this.state.repo_downloading} onClick={this.downloadGrasscutterStableRepo} id="grasscutterDevRepo" >
|
<BigButton disabled={this.state.repo_downloading} onClick={this.downloadGrasscutterStableRepo} id="grasscutterDevRepo" >
|
||||||
@@ -235,6 +259,9 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
<div className='DownloadMenuSection'>
|
<div className='DownloadMenuSection'>
|
||||||
<div className='DownloadLabel'>
|
<div className='DownloadLabel'>
|
||||||
<Tr text="downloads.resources" />
|
<Tr text="downloads.resources" />
|
||||||
|
<HelpButton>
|
||||||
|
<Tr text="help.resources" />
|
||||||
|
</HelpButton>
|
||||||
</div>
|
</div>
|
||||||
<div className='DownloadValue'>
|
<div className='DownloadValue'>
|
||||||
<BigButton disabled={this.state.resources_downloading || !this.state.grasscutter_set || this.state.resources_exist} onClick={this.downloadResources} id="resourcesBtn" >
|
<BigButton disabled={this.state.resources_downloading || !this.state.grasscutter_set || this.state.resources_exist} onClick={this.downloadResources} id="resourcesBtn" >
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export default class DownloadHandler {
|
|||||||
path: string,
|
path: string,
|
||||||
progress: number,
|
progress: number,
|
||||||
total: number,
|
total: number,
|
||||||
|
total_downloaded: number,
|
||||||
status: string,
|
status: string,
|
||||||
startTime: number,
|
startTime: number,
|
||||||
error?: string,
|
error?: string,
|
||||||
@@ -24,16 +25,25 @@ export default class DownloadHandler {
|
|||||||
downloaded: string,
|
downloaded: string,
|
||||||
total: string,
|
total: string,
|
||||||
path: string,
|
path: string,
|
||||||
|
total_downloaded: string,
|
||||||
} = payload
|
} = payload
|
||||||
|
|
||||||
const index = this.downloads.findIndex(download => download.path === obj.path)
|
const index = this.downloads.findIndex(download => download.path === obj.path)
|
||||||
this.downloads[index].progress = parseInt(obj.downloaded, 10)
|
this.downloads[index].progress = parseInt(obj.downloaded, 10)
|
||||||
this.downloads[index].total = parseInt(obj.total, 10)
|
this.downloads[index].total = parseInt(obj.total, 10)
|
||||||
|
this.downloads[index].total_downloaded = parseInt(obj.total_downloaded, 10)
|
||||||
|
|
||||||
// Set download speed based on startTime
|
// Set download speed based on startTime
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const timeDiff = now - this.downloads[index].startTime
|
const timeDiff = now - this.downloads[index].startTime
|
||||||
const speed = (this.downloads[index].progress / timeDiff) * 1000
|
let speed = (this.downloads[index].progress / timeDiff) * 1000
|
||||||
|
|
||||||
|
if (this.downloads[index].total === 0) {
|
||||||
|
// If our total is 0, then we are downloading a file without a size
|
||||||
|
// Calculate the average speed based total_downloaded and startTme
|
||||||
|
speed = (this.downloads[index].total_downloaded / timeDiff) * 1000
|
||||||
|
}
|
||||||
|
|
||||||
this.downloads[index].speed = byteToString(speed) + '/s'
|
this.downloads[index].speed = byteToString(speed) + '/s'
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -104,6 +114,7 @@ export default class DownloadHandler {
|
|||||||
path,
|
path,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
total: 0,
|
total: 0,
|
||||||
|
total_downloaded: 0,
|
||||||
status: 'downloading',
|
status: 'downloading',
|
||||||
startTime: Date.now(),
|
startTime: Date.now(),
|
||||||
onFinish,
|
onFinish,
|
||||||
@@ -134,7 +145,7 @@ export default class DownloadHandler {
|
|||||||
getTotalAverage() {
|
getTotalAverage() {
|
||||||
const files = this.downloads.filter(d => d.status === 'downloading')
|
const files = this.downloads.filter(d => d.status === 'downloading')
|
||||||
const total = files.reduce((acc, d) => acc + d.total, 0)
|
const total = files.reduce((acc, d) => acc + d.total, 0)
|
||||||
const progress = files.reduce((acc, d) => acc + d.progress, 0)
|
const progress = files.reduce((acc, d) => d.progress !== 0 ? acc + d.progress : acc + d.total_downloaded, 0)
|
||||||
let speedStr = '0 B/s'
|
let speedStr = '0 B/s'
|
||||||
|
|
||||||
// Get download speed based on startTimes
|
// Get download speed based on startTimes
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const defaultTheme = {
|
|||||||
export async function getThemeList() {
|
export async function getThemeList() {
|
||||||
// Do some invoke to backend to get the theme list
|
// Do some invoke to backend to get the theme list
|
||||||
const themes = await invoke('get_theme_list', {
|
const themes = await invoke('get_theme_list', {
|
||||||
data_dir: `${await dataDir()}/cultivation`
|
dataDir: `${await dataDir()}/cultivation`
|
||||||
}) as BackendThemeList[]
|
}) as BackendThemeList[]
|
||||||
const list: ThemeList[] = [
|
const list: ThemeList[] = [
|
||||||
// ALWAYS include default theme
|
// ALWAYS include default theme
|
||||||
|
|||||||
Reference in New Issue
Block a user