Merge pull request #153 from NotThorny/ModBrowser-Improved

Improve Mod Browser
This commit is contained in:
SpikeHD
2023-04-26 15:01:46 -07:00
committed by GitHub
13 changed files with 272 additions and 35 deletions

View File

@@ -107,9 +107,9 @@ async fn parse_args(inp: &Vec<String>) -> Result<Args, ArgsError> {
if game_path.is_some() {
if args.value_of("non-elevated-game")? {
system_helpers::run_un_elevated(game_path.unwrap().to_string(), Some(game_args))
system_helpers::run_un_elevated(game_path.unwrap(), Some(game_args))
} else {
system_helpers::run_program(game_path.unwrap().to_string(), Some(game_args))
system_helpers::run_program(game_path.unwrap(), Some(game_args))
}
}
}
@@ -308,6 +308,7 @@ fn is_grasscutter_running() -> bool {
!proc.is_empty()
}
#[cfg(windows)]
#[tauri::command]
fn restart_grasscutter(window: tauri::Window) -> bool {
let pid: usize = *GC_PID.lock().unwrap();
@@ -338,6 +339,11 @@ fn restart_grasscutter(window: tauri::Window) -> bool {
}
}
#[cfg(unix)]
#[tauri::command]
fn restart_grasscutter(window: tauri::Window) {}
#[cfg(windows)]
#[tauri::command]
fn enable_grasscutter_watcher(window: tauri::Window, process: String) {
let grasscutter_name = process.clone();
@@ -398,6 +404,12 @@ fn enable_grasscutter_watcher(window: tauri::Window, process: String) {
});
}
#[cfg(unix)]
#[tauri::command]
fn enable_grasscutter_watcher(window: tauri::Window, process: String) {
let gc_pid = Pid::from(696969);
}
#[tauri::command]
async fn connect(port: u16, certificate_path: String) {
// Log message to console.

View File

@@ -53,9 +53,7 @@ pub async fn unpatch_game() -> bool {
pub async fn get_game_rsa_path() -> Option<String> {
let config = config::get_config();
if config.game_install_path.is_none() {
return None;
}
config.game_install_path.as_ref()?;
let mut game_folder = PathBuf::from(config.game_install_path.unwrap());
game_folder.pop();

View File

@@ -16,7 +16,8 @@ pub async fn get_latest_release() -> Release {
.unwrap();
let text = response.text().await.unwrap();
println!("Response: {}", text);
// This includes ip when github rate limits you, so avoid it for now to avoid leaks through screenshots
//println!("Response: {}", text);
// Parse "tag_name" from JSON
let json: serde_json::Value = serde_json::from_str(&text).unwrap();

View File

@@ -1,12 +1,14 @@
use duct::cmd;
use ini::Ini;
use std::ffi::OsStr;
use std::path::PathBuf;
use windows_service::service::{ServiceAccess, ServiceState::Stopped};
use windows_service::service_manager::{ServiceManager, ServiceManagerAccess};
use std::ffi::OsStr;
#[cfg(windows)]
use registry::{Data, Hive, Security};
use {
registry::{Data, Hive, Security},
windows_service::service::{ServiceAccess, ServiceState::Stopped},
windows_service::service_manager::{ServiceManager, ServiceManagerAccess},
};
#[tauri::command]
pub fn run_program(path: String, args: Option<String>) {
@@ -230,7 +232,7 @@ pub fn service_status(service: String) -> bool {
}
};
let status_result = my_service.query_status();
if status_result.is_ok() {
if let Ok(..) = status_result {
let status = status_result.unwrap();
println!("{} service status: {:?}", service, status.current_state);
if status.current_state == Stopped {
@@ -262,6 +264,12 @@ pub fn start_service(service: String) -> bool {
true
}
#[cfg(unix)]
#[tauri::command]
pub fn start_service(service: String) {
let started = OsStr::new("Started service!");
}
#[cfg(windows)]
#[tauri::command]
pub fn stop_service(service: String) -> bool {
@@ -281,6 +289,10 @@ pub fn stop_service(service: String) -> bool {
true
}
#[cfg(unix)]
#[tauri::command]
pub fn stop_service(service: String) {}
#[cfg(unix)]
#[tauri::command]
pub fn wipe_registry(_exec_name: String) {}

View File

@@ -1,21 +1,27 @@
use http::header;
use once_cell::sync::Lazy;
use reqwest::header::{CONTENT_TYPE, USER_AGENT};
static CLIENT: Lazy<reqwest::Client> = Lazy::new(|| {
let mut headers = header::HeaderMap::new();
headers.insert(USER_AGENT, header::HeaderValue::from_static("cultivation"));
headers.insert(
CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
);
let client = reqwest::Client::builder().default_headers(headers);
client.build().unwrap()
});
pub(crate) async fn query(site: &str) -> String {
let client = reqwest::Client::new();
let response = client
CLIENT
.get(site)
.header(USER_AGENT, "cultivation")
.header(CONTENT_TYPE, "application/json")
.send()
.await
.ok();
if response.is_some() {
response.unwrap().text().await.unwrap()
} else {
false.to_string()
}
.expect("Failed to get web response")
.text()
.await
.unwrap()
}
#[tauri::command]

View File

@@ -7,7 +7,7 @@
},
"package": {
"productName": "Cultivation",
"version": "1.0.26"
"version": "1.0.27"
},
"tauri": {
"allowlist": {

View File

@@ -1,6 +1,6 @@
.Mods {
backdrop-filter: blur(10px);
height: 90%;
height: 80%;
width: 100%;
}

View File

@@ -14,6 +14,8 @@ import Back from '../resources/icons/back.svg'
import Menu from './components/menu/Menu'
import BigButton from './components/common/BigButton'
import Tr from '../utils/language'
import { ModPages } from './components/mods/ModPages'
import TextInput from './components/common/TextInput'
interface IProps {
downloadHandler: DownloadHandler
@@ -23,8 +25,21 @@ interface IState {
isDownloading: boolean
category: string
downloadList: { name: string; url: string; mod: ModData }[] | null
page: number
search: string
}
const pages = [
{
name: -1,
title: '<',
},
{
name: 1,
title: '>',
},
]
const headers = [
{
name: 'ripe',
@@ -46,17 +61,22 @@ const headers = [
* @TODO Categorizaiton/sorting (by likes, views, etc)
*/
export class Mods extends React.Component<IProps, IState> {
timeout: number
constructor(props: IProps) {
super(props)
this.timeout = 0
this.state = {
isDownloading: false,
category: '',
downloadList: null,
page: 1,
search: '',
}
this.setCategory = this.setCategory.bind(this)
this.addDownload = this.addDownload.bind(this)
this.setPage = this.setPage.bind(this)
}
async addDownload(mod: ModData) {
@@ -111,6 +131,29 @@ export class Mods extends React.Component<IProps, IState> {
)
}
async setPage(value: number) {
const current = this.state.page
if (current + value == 0) return
this.setState(
{
page: current + value,
},
this.forceUpdate
)
}
async setSearch(text: string) {
if (this.timeout) clearTimeout(this.timeout)
this.timeout = window.setTimeout(() => {
this.setState(
{
search: text,
},
this.forceUpdate
)
}, 500)
}
render() {
return (
<div className="Mods">
@@ -162,7 +205,30 @@ export class Mods extends React.Component<IProps, IState> {
<ModHeader onChange={this.setCategory} headers={headers} defaultHeader={'ripe'} />
<ModList key={this.state.category} mode={this.state.category} addDownload={this.addDownload} />
{this.state.category != 'installed' && (
<>
<div className="ModPagesPage">
<TextInput
id="search"
key="search"
placeholder={this.state.page.toString()}
onChange={(text: string) => {
this.setSearch(text)
}}
initalValue={''}
/>
</div>
<ModPages onClick={this.setPage} headers={pages} defaultHeader={this.state.page} />
</>
)}
<ModList
key={`${this.state.category}_${this.state.page}_${this.state.search}`}
mode={this.state.category}
addDownload={this.addDownload}
page={this.state.page}
search={this.state.search}
/>
</div>
)
}

View File

@@ -1,6 +1,6 @@
import React from 'react'
import { getConfigOption } from '../../../utils/configuration'
import { getInstalledMods, getMods, ModData, PartialModData } from '../../../utils/gamebanana'
import { getAllMods, getInstalledMods, getMods, ModData, PartialModData } from '../../../utils/gamebanana'
import { LoadingCircle } from './LoadingCircle'
import './ModList.css'
@@ -8,6 +8,8 @@ import { ModTile } from './ModTile'
interface IProps {
mode: string
page: number
search: string
addDownload: (mod: ModData) => void
}
@@ -62,7 +64,17 @@ export class ModList extends React.Component<IProps, IState> {
return
}
const mods = await getMods(this.props.mode)
let mods: ModData[]
if (!(this.props.search == '')) {
// idk the api so just filter all mods to search
mods = (await getAllMods(this.props.mode)).filter((mod) =>
mod.name.toLowerCase().includes(this.props.search.toLowerCase())
)
} else {
mods = await getMods(this.props.mode, this.props.page)
}
const horny = await getConfigOption('horny_mode')
this.setState({

View File

@@ -0,0 +1,66 @@
.ModPages {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-around;
width: 100%;
z-index: 2;
position: relative;
padding: 5px;
font-size: 20px;
font-weight: bold;
color: #fff;
background: rgba(77, 77, 77, 0.6);
}
.ModPagesTitle {
display: flex;
justify-content: center;
z-index: 3;
width: 100%;
max-width: 20%;
}
.ModPagesTitle:hover {
cursor: pointer;
}
.ModPagesTitle.selected {
border-bottom: 0px solid #fff;
}
.ModPagesPage {
position: absolute;
justify-self: center;
left: 50%;
margin-top: 10px;
transform: translateX(-50%);
text-align: center;
padding: -5px;
z-index: 3;
font-size: 15px;
font-weight: bold;
color: #fff;
}
.ModPagesPage input {
text-align: center;
padding: 3px;
border-radius: 3px;
height: 18px;
font-size: 20px;
font-weight: bold;
color: #fff;
background: rgba(77, 77, 77, 0.6);
}
.ModPagesPage .TextInputWrapper {
background: rgba(77, 77, 77, 0.6);
z-index: -1;
display: inline-block;
}

View File

@@ -0,0 +1,54 @@
import React from 'react'
import './ModPages.css'
interface IProps {
headers: {
title: string
name: number
}[]
onClick: (value: number) => void
defaultHeader: number
}
interface IState {
selected: number
}
export class ModPages extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props)
this.state = {
selected: this.props.defaultHeader,
}
}
setSelected(value: number) {
const current = this.state.selected
if (current + value == 0) return
this.setState({
selected: current + value,
})
this.props.onClick(value)
}
render() {
return (
<div className="ModPages">
{this.props.headers.map((header, index) => {
return (
<div
key={index}
className={`ModPagesTitle ${this.state.selected === header.name ? 'selected' : ''}`}
onClick={() => this.setSelected(header.name)}
>
{header.title}
</div>
)
})}
</div>
)
}
}

View File

@@ -124,7 +124,7 @@ export default class NewsSection extends React.Component<IProps, IState> {
case 'latest_version':
news = (
<tr>
<td>Latest version: Grasscutter 1.4.6 - Cultivation 1.0.10</td>
<td>Latest version: Grasscutter 1.4.8 - Cultivation 1.0.27</td>
</tr>
)
break

View File

@@ -117,7 +117,22 @@ interface ModDownload {
containsExe: boolean
}
export async function getMods(mode: string) {
export async function getMods(mode: string, page: number) {
let modList: GamebananaResponse[] = []
const resp = JSON.parse(
await invoke('list_submissions', {
mode,
page: '' + page,
})
)
modList = [...modList, ...resp]
return formatGamebananaData(modList)
}
export async function getAllMods(mode: string) {
let modList: GamebananaResponse[] = []
let hadMods = true
let page = 1
@@ -134,13 +149,8 @@ export async function getMods(mode: string) {
modList = [...modList, ...resp]
page++
console.log(page)
console.log(resp)
}
console.log(modList)
return formatGamebananaData(modList)
}