mirror of
https://github.com/ow-mods/ow-mod-man.git
synced 2025-12-11 20:15:50 +01:00
Add simple mod loading
This commit is contained in:
parent
2b685aca58
commit
6374c5627e
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -34,9 +34,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.68"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
|
||||
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
|
||||
|
||||
[[package]]
|
||||
name = "async-recursion"
|
||||
@ -1935,6 +1935,8 @@ dependencies = [
|
||||
name = "owmods_gui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"owmods_core",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
|
||||
@ -23,11 +23,18 @@ pub struct RemoteDatabase {
|
||||
pub mods: HashMap<String, RemoteMod>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LocalDatabase {
|
||||
pub mods: HashMap<String, LocalMod>,
|
||||
}
|
||||
|
||||
impl RemoteDatabase {
|
||||
pub fn empty() -> RemoteDatabase {
|
||||
RemoteDatabase {
|
||||
mods: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn fetch(log: &Logger, conf: &Config) -> Result<RemoteDatabase, anyhow::Error> {
|
||||
log!(log, debug, "Fetching Remote DB At {}", conf.database_url);
|
||||
let resp = reqwest::get(&conf.database_url).await?;
|
||||
@ -60,6 +67,12 @@ impl RemoteDatabase {
|
||||
}
|
||||
|
||||
impl LocalDatabase {
|
||||
pub fn empty() -> LocalDatabase {
|
||||
LocalDatabase {
|
||||
mods: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mod(&self, unique_name: &str) -> Option<&LocalMod> {
|
||||
self.mods.get(unique_name)
|
||||
}
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
pub enum ProgressType {
|
||||
Definite,
|
||||
Indefinite,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
pub enum ProgressAction {
|
||||
Download,
|
||||
Extract,
|
||||
Wine,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct LogMessage {
|
||||
pub message: String,
|
||||
}
|
||||
@ -21,6 +26,7 @@ impl LogMessage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
pub enum Log {
|
||||
Debug(LogMessage),
|
||||
Info(LogMessage),
|
||||
@ -48,7 +54,7 @@ pub trait ProgressHandler: Send + Sync {
|
||||
}
|
||||
|
||||
pub struct Logger {
|
||||
backend: Box<dyn LoggerBackend>,
|
||||
pub backend: Box<dyn LoggerBackend>,
|
||||
}
|
||||
|
||||
macro_rules! log_msg {
|
||||
@ -90,3 +96,45 @@ impl Logger {
|
||||
log_msg!(Log::Warning, warning);
|
||||
log_msg!(Log::Error, error);
|
||||
}
|
||||
|
||||
pub struct BasicConsoleBackend;
|
||||
struct IgnoreProgressHandler;
|
||||
|
||||
impl LoggerBackend for BasicConsoleBackend {
|
||||
fn handle_log(&self, log: Log) {
|
||||
match log {
|
||||
Log::Debug(l) => {
|
||||
println!("[DEBUG]: {}", l.message);
|
||||
}
|
||||
Log::Info(l) => {
|
||||
println!("[INFO]: {}", l.message);
|
||||
}
|
||||
Log::Success(l) => {
|
||||
println!("[SUCCESS]: {}", l.message);
|
||||
}
|
||||
Log::Warning(l) => {
|
||||
println!("[WARNING]: {}", l.message);
|
||||
}
|
||||
Log::Error(l) => {
|
||||
println!("[ERROR]: {}", l.message);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn create_progress(
|
||||
&self,
|
||||
_id: &str,
|
||||
_msg: &str,
|
||||
_progress_type: ProgressType,
|
||||
_action_type: ProgressAction,
|
||||
_len: u64,
|
||||
) -> Box<dyn ProgressHandler> {
|
||||
Box::new(IgnoreProgressHandler)
|
||||
}
|
||||
}
|
||||
|
||||
impl ProgressHandler for IgnoreProgressHandler {
|
||||
fn increment(&self, _amount: u64) {}
|
||||
fn change_message(&self, _new_message: &str) {}
|
||||
fn finish(&self, _msg: &str) {}
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ pub struct ModReadMe {
|
||||
pub download_url: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct LocalMod {
|
||||
pub enabled: bool,
|
||||
pub errors: Vec<String>,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
[package]
|
||||
name = "owmods_gui"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -8,10 +9,12 @@ version = "0.1.0"
|
||||
tauri-build = { version = "1.2.1", features = [] }
|
||||
|
||||
[dependencies]
|
||||
owmods_core = { path = "../../owmods_core" }
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.2.4", features = [] }
|
||||
anyhow = "1.0.69"
|
||||
|
||||
[features]
|
||||
default = [ "custom-protocol" ]
|
||||
custom-protocol = [ "tauri/custom-protocol" ]
|
||||
default = ["custom-protocol"]
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
|
||||
86
owmods_gui/backend/src/commands.rs
Normal file
86
owmods_gui/backend/src/commands.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use owmods_core::db::{fetch_local_db, fetch_remote_db};
|
||||
use owmods_core::mods::{LocalMod, RemoteMod};
|
||||
use tauri::Manager;
|
||||
|
||||
use crate::State;
|
||||
|
||||
use crate::logging::get_logger;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn refresh_local_db(
|
||||
handle: tauri::AppHandle,
|
||||
state: tauri::State<'_, State>,
|
||||
) -> Result<(), String> {
|
||||
let log = get_logger(handle.clone());
|
||||
let conf = state.config.read().unwrap();
|
||||
let local_db = fetch_local_db(&log, &*conf).map_err(|err| err.to_string())?;
|
||||
{
|
||||
let mut db = state.local_db.write().unwrap();
|
||||
*db = local_db;
|
||||
}
|
||||
handle.emit_all("LOCAL-REFRESH", "").ok();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_local_mods(state: tauri::State<'_, State>) -> Vec<String> {
|
||||
state
|
||||
.local_db
|
||||
.read()
|
||||
.unwrap()
|
||||
.mods
|
||||
.values()
|
||||
.map(|m| m.manifest.unique_name.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_local_mod(unique_name: &str, state: tauri::State<'_, State>) -> Option<LocalMod> {
|
||||
state
|
||||
.local_db
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_mod(unique_name)
|
||||
.map(|m| m.clone())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn refresh_remote_db(
|
||||
handle: tauri::AppHandle,
|
||||
state: tauri::State<'_, State>,
|
||||
) -> Result<(), String> {
|
||||
let log = get_logger(handle.clone());
|
||||
// Clones to release lock
|
||||
let conf = state.config.read().unwrap().clone();
|
||||
let remote_db = fetch_remote_db(&log, &conf)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
{
|
||||
let mut db = state.remote_db.write().unwrap();
|
||||
*db = remote_db;
|
||||
}
|
||||
handle.emit_all("REMOTE-REFRESH", "").ok();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_remote_mods(state: tauri::State<'_, State>) -> Vec<String> {
|
||||
state
|
||||
.remote_db
|
||||
.read()
|
||||
.unwrap()
|
||||
.mods
|
||||
.values()
|
||||
.map(|m| m.unique_name.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_remote_mod(unique_name: &str, state: tauri::State<'_, State>) -> Option<RemoteMod> {
|
||||
state
|
||||
.remote_db
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_mod(unique_name)
|
||||
.map(|m| m.clone())
|
||||
}
|
||||
111
owmods_gui/backend/src/logging.rs
Normal file
111
owmods_gui/backend/src/logging.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use owmods_core::logging::{
|
||||
Log, Logger, LoggerBackend, ProgressAction, ProgressHandler, ProgressType,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
struct TauriLogBackend {
|
||||
app: AppHandle,
|
||||
}
|
||||
|
||||
struct TauriProgressBackend {
|
||||
id: String,
|
||||
app: AppHandle,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
struct ProgressStartPayload {
|
||||
id: String,
|
||||
message: String,
|
||||
progress_type: ProgressType,
|
||||
progress_action: ProgressAction,
|
||||
len: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
enum ProgressUpdatePayload {
|
||||
Increment { id: String, amount: u64 },
|
||||
ChangeMsg { id: String, new_msg: String },
|
||||
Finish { id: String, msg: String },
|
||||
}
|
||||
|
||||
impl TauriProgressBackend {
|
||||
pub fn new(id: &str, app: AppHandle) -> TauriProgressBackend {
|
||||
TauriProgressBackend {
|
||||
id: id.to_string(),
|
||||
app: app,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LoggerBackend for TauriLogBackend {
|
||||
fn handle_log(&self, log: Log) {
|
||||
self.app.emit_all("LOG", log).ok();
|
||||
}
|
||||
|
||||
fn create_progress(
|
||||
&self,
|
||||
id: &str,
|
||||
msg: &str,
|
||||
progress_type: ProgressType,
|
||||
action_type: ProgressAction,
|
||||
len: u64,
|
||||
) -> Box<dyn ProgressHandler> {
|
||||
self.app
|
||||
.emit_all(
|
||||
"PROGRESS-START",
|
||||
ProgressStartPayload {
|
||||
id: id.to_string(),
|
||||
message: msg.to_string(),
|
||||
progress_type: progress_type,
|
||||
progress_action: action_type,
|
||||
len: len,
|
||||
},
|
||||
)
|
||||
.ok();
|
||||
Box::new(TauriProgressBackend::new(id, self.app.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ProgressHandler for TauriProgressBackend {
|
||||
fn increment(&self, amount: u64) {
|
||||
self.app
|
||||
.emit_all(
|
||||
"PROGRESS-INCREMENT",
|
||||
ProgressUpdatePayload::Increment {
|
||||
id: self.id.clone(),
|
||||
amount: amount,
|
||||
},
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn change_message(&self, new_message: &str) {
|
||||
self.app
|
||||
.emit_all(
|
||||
"PROGRESS-MSG",
|
||||
ProgressUpdatePayload::ChangeMsg {
|
||||
id: self.id.clone(),
|
||||
new_msg: new_message.to_string(),
|
||||
},
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn finish(&self, msg: &str) {
|
||||
self.app
|
||||
.emit_all(
|
||||
"PROGRESS-FINISH",
|
||||
ProgressUpdatePayload::Finish {
|
||||
id: self.id.clone(),
|
||||
msg: msg.to_string(),
|
||||
},
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_logger(handle: AppHandle) -> Logger {
|
||||
let backend = TauriLogBackend { app: handle };
|
||||
Logger::new(Box::new(backend))
|
||||
}
|
||||
@ -3,8 +3,54 @@
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
fn main() {
|
||||
use std::{
|
||||
error::Error,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use commands::*;
|
||||
use logging::get_logger;
|
||||
use owmods_core::{
|
||||
config::{get_config, Config},
|
||||
db::{LocalDatabase, RemoteDatabase},
|
||||
logging::{BasicConsoleBackend, Logger},
|
||||
};
|
||||
|
||||
mod commands;
|
||||
mod logging;
|
||||
|
||||
pub struct State {
|
||||
local_db: Arc<RwLock<LocalDatabase>>,
|
||||
remote_db: Arc<RwLock<RemoteDatabase>>,
|
||||
config: Arc<RwLock<Config>>,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let basic_console = BasicConsoleBackend;
|
||||
let temp_logger = Logger::new(Box::new(basic_console));
|
||||
|
||||
let config = get_config(&temp_logger)?;
|
||||
|
||||
tauri::Builder::default()
|
||||
.manage(State {
|
||||
local_db: Arc::new(RwLock::new(LocalDatabase::empty())),
|
||||
remote_db: Arc::new(RwLock::new(RemoteDatabase::empty())),
|
||||
config: Arc::new(RwLock::new(config)),
|
||||
})
|
||||
.setup(move |app| {
|
||||
get_logger(app.handle()).debug("Starting App");
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
refresh_local_db,
|
||||
get_local_mods,
|
||||
get_local_mod,
|
||||
refresh_remote_db,
|
||||
get_remote_mods,
|
||||
get_remote_mod
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("Error while running tauri application.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -13,14 +13,19 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@picocss/pico": "^1.5.7",
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^4.7.1"
|
||||
"react-icons": "^4.7.1",
|
||||
"react-virtualized-auto-sizer": "^1.0.7",
|
||||
"react-window": "^1.8.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/react": "^18.0.27",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.50.0",
|
||||
"@typescript-eslint/parser": "^5.50.0",
|
||||
"@vitejs/plugin-react": "^3.0.1",
|
||||
|
||||
90
owmods_gui/frontend/pnpm-lock.yaml
generated
90
owmods_gui/frontend/pnpm-lock.yaml
generated
@ -2,9 +2,12 @@ lockfileVersion: 5.4
|
||||
|
||||
specifiers:
|
||||
"@picocss/pico": ^1.5.7
|
||||
"@tauri-apps/api": ^1.2.0
|
||||
"@types/node": ^18.11.18
|
||||
"@types/react": ^18.0.27
|
||||
"@types/react-dom": ^18.0.10
|
||||
"@types/react-virtualized-auto-sizer": ^1.0.1
|
||||
"@types/react-window": ^1.8.5
|
||||
"@typescript-eslint/eslint-plugin": ^5.50.0
|
||||
"@typescript-eslint/parser": ^5.50.0
|
||||
"@vitejs/plugin-react": ^3.0.1
|
||||
@ -14,20 +17,27 @@ specifiers:
|
||||
react: ^18.2.0
|
||||
react-dom: ^18.2.0
|
||||
react-icons: ^4.7.1
|
||||
react-virtualized-auto-sizer: ^1.0.7
|
||||
react-window: ^1.8.8
|
||||
sass: ^1.58.0
|
||||
typescript: ^4.9.5
|
||||
vite: ^4.0.4
|
||||
|
||||
dependencies:
|
||||
"@picocss/pico": 1.5.7
|
||||
"@tauri-apps/api": 1.2.0
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
react-icons: 4.7.1_react@18.2.0
|
||||
react-virtualized-auto-sizer: 1.0.7_biqbaboplfbrettd7655fr4n2y
|
||||
react-window: 1.8.8_biqbaboplfbrettd7655fr4n2y
|
||||
|
||||
devDependencies:
|
||||
"@types/node": 18.11.19
|
||||
"@types/react": 18.0.27
|
||||
"@types/react-dom": 18.0.10
|
||||
"@types/react-virtualized-auto-sizer": 1.0.1
|
||||
"@types/react-window": 1.8.5
|
||||
"@typescript-eslint/eslint-plugin": 5.51.0_b635kmla6dsb4frxfihkw4m47e
|
||||
"@typescript-eslint/parser": 5.51.0_4vsywjlpuriuw3tl5oq6zy5a64
|
||||
"@vitejs/plugin-react": 3.1.0_vite@4.1.1
|
||||
@ -296,6 +306,16 @@ packages:
|
||||
"@babel/helper-plugin-utils": 7.20.2
|
||||
dev: true
|
||||
|
||||
/@babel/runtime/7.20.13:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==
|
||||
}
|
||||
engines: { node: ">=6.9.0" }
|
||||
dependencies:
|
||||
regenerator-runtime: 0.13.11
|
||||
dev: false
|
||||
|
||||
/@babel/template/7.20.7:
|
||||
resolution:
|
||||
{
|
||||
@ -747,6 +767,14 @@ packages:
|
||||
}
|
||||
dev: false
|
||||
|
||||
/@tauri-apps/api/1.2.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-lsI54KI6HGf7VImuf/T9pnoejfgkNoXveP14pVV7XarrQ46rOejIVJLFqHI9sRReJMGdh2YuCoI3cc/yCWCsrw==
|
||||
}
|
||||
engines: { node: ">= 14.6.0", npm: ">= 6.6.0", yarn: ">= 1.19.1" }
|
||||
dev: false
|
||||
|
||||
/@types/json-schema/7.0.11:
|
||||
resolution:
|
||||
{
|
||||
@ -777,6 +805,24 @@ packages:
|
||||
"@types/react": 18.0.27
|
||||
dev: true
|
||||
|
||||
/@types/react-virtualized-auto-sizer/1.0.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-GH8sAnBEM5GV9LTeiz56r4ZhMOUSrP43tAQNSRVxNexDjcNKLCEtnxusAItg1owFUFE6k0NslV26gqVClVvong==
|
||||
}
|
||||
dependencies:
|
||||
"@types/react": 18.0.27
|
||||
dev: true
|
||||
|
||||
/@types/react-window/1.8.5:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw==
|
||||
}
|
||||
dependencies:
|
||||
"@types/react": 18.0.27
|
||||
dev: true
|
||||
|
||||
/@types/react/18.0.27:
|
||||
resolution:
|
||||
{
|
||||
@ -2400,6 +2446,13 @@ packages:
|
||||
"@jridgewell/sourcemap-codec": 1.4.14
|
||||
dev: true
|
||||
|
||||
/memoize-one/5.2.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
|
||||
}
|
||||
dev: false
|
||||
|
||||
/merge2/1.4.1:
|
||||
resolution:
|
||||
{
|
||||
@ -2757,6 +2810,36 @@ packages:
|
||||
engines: { node: ">=0.10.0" }
|
||||
dev: true
|
||||
|
||||
/react-virtualized-auto-sizer/1.0.7_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-Mxi6lwOmjwIjC1X4gABXMJcKHsOo0xWl3E3ugOgufB8GJU+MqrtY35aBuvCYv/razQ1Vbp7h1gWJjGjoNN5pmA==
|
||||
}
|
||||
engines: { node: ">8.0.0" }
|
||||
peerDependencies:
|
||||
react: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0-rc
|
||||
react-dom: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0-rc
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
dev: false
|
||||
|
||||
/react-window/1.8.8_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-D4IiBeRtGXziZ1n0XklnFGu7h9gU684zepqyKzgPNzrsrk7xOCxni+TCckjg2Nr/DiaEEGVVmnhYSlT2rB47dQ==
|
||||
}
|
||||
engines: { node: ">8.0.0" }
|
||||
peerDependencies:
|
||||
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
"@babel/runtime": 7.20.13
|
||||
memoize-one: 5.2.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
dev: false
|
||||
|
||||
/react/18.2.0:
|
||||
resolution:
|
||||
{
|
||||
@ -2777,6 +2860,13 @@ packages:
|
||||
picomatch: 2.3.1
|
||||
dev: true
|
||||
|
||||
/regenerator-runtime/0.13.11:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
|
||||
}
|
||||
dev: false
|
||||
|
||||
/regexp.prototype.flags/1.4.3:
|
||||
resolution:
|
||||
{
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
import Nav from "@components/nav/Nav";
|
||||
import Tabs from "@components/tabs/Tabs";
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
|
||||
// Refresh once to get data
|
||||
invoke("refresh_local_db");
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
|
||||
@ -4,18 +4,20 @@ import DownloadsBadge from "./DownloadsBadge";
|
||||
import DownloadsPopout from "./DownloadsPopout";
|
||||
import NavButton from "../nav/NavButton";
|
||||
|
||||
const Downloads = () => {
|
||||
return <li>
|
||||
<details role="list">
|
||||
<summary>
|
||||
<NavButton labelPlacement="right" ariaLabel="Downloads">
|
||||
<Icon iconType={FaArrowDown} />
|
||||
<DownloadsBadge count={3} />
|
||||
</NavButton>
|
||||
</summary>
|
||||
<DownloadsPopout/>
|
||||
</details>
|
||||
</li> ;
|
||||
}
|
||||
const Downloads = () => {
|
||||
return (
|
||||
<li>
|
||||
<details role="list">
|
||||
<summary>
|
||||
<NavButton labelPlacement="right" ariaLabel="Downloads">
|
||||
<Icon iconType={FaArrowDown} />
|
||||
<DownloadsBadge count={3} />
|
||||
</NavButton>
|
||||
</summary>
|
||||
<DownloadsPopout />
|
||||
</details>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
export default Downloads;
|
||||
|
||||
@ -5,15 +5,17 @@ interface ActiveDownloadProps {
|
||||
}
|
||||
|
||||
interface DownloadPayload {
|
||||
id: string,
|
||||
progress?: number,
|
||||
message: string
|
||||
id: string;
|
||||
progress?: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
const ActiveDownload = (props: ActiveDownloadProps) => {
|
||||
|
||||
// Temp state for rn, will use useSyncExternalStore later.
|
||||
const [progress, setProgress] = useState<DownloadPayload>({id: props.id, message: "Downloading xen.NewHorizons"});
|
||||
const [progress, setProgress] = useState<DownloadPayload>({
|
||||
id: props.id,
|
||||
message: "Downloading xen.NewHorizons"
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="downloads-row">
|
||||
|
||||
@ -1,34 +1,45 @@
|
||||
import Icon from "@components/Icon";
|
||||
import ModActionButton from "@components/mods/ModActionButton";
|
||||
import ModHeader from "@components/mods/ModHeader";
|
||||
import { useTauri } from "@hooks";
|
||||
import { LocalMod } from "@types";
|
||||
import { memo } from "react";
|
||||
import { FaFolder, FaTrash } from "react-icons/fa";
|
||||
import { LocalMod } from "src/types";
|
||||
|
||||
const isEqual = (prev: LocalMod, next: LocalMod) =>
|
||||
prev.manifest.uniqueName === next.manifest.uniqueName;
|
||||
|
||||
const LocalModRow = memo((props: LocalMod) => {
|
||||
return (
|
||||
<details>
|
||||
<ModHeader {...props.manifest}>
|
||||
<ModActionButton ariaLabel="Show Folder">
|
||||
<Icon iconType={FaFolder} />
|
||||
</ModActionButton>
|
||||
<ModActionButton ariaLabel="Uninstall Mod">
|
||||
<Icon iconType={FaTrash} />
|
||||
</ModActionButton>
|
||||
<input
|
||||
className="mod-toggle"
|
||||
type="checkbox"
|
||||
aria-label="enabled"
|
||||
role="switch"
|
||||
value={props.enabled.toString()}
|
||||
/>
|
||||
</ModHeader>
|
||||
Description Not Available
|
||||
</details>
|
||||
const LocalModRow = memo((props: { uniqueName: string }) => {
|
||||
const [status, mod, err] = useTauri<LocalMod, { uniqueName: string }>(
|
||||
"LOCAL-REFRESH",
|
||||
"get_local_mod",
|
||||
{ uniqueName: props.uniqueName }
|
||||
);
|
||||
}, isEqual);
|
||||
|
||||
if (status === "Loading") {
|
||||
return <p>Loading</p>;
|
||||
} else if (status === "Error") {
|
||||
return <p>{err!}</p>;
|
||||
} else {
|
||||
const localMod = mod!;
|
||||
return (
|
||||
<details>
|
||||
<ModHeader {...localMod.manifest}>
|
||||
<ModActionButton ariaLabel="Show Folder">
|
||||
<Icon iconType={FaFolder} />
|
||||
</ModActionButton>
|
||||
<ModActionButton ariaLabel="Uninstall Mod">
|
||||
<Icon iconType={FaTrash} />
|
||||
</ModActionButton>
|
||||
<input
|
||||
className="mod-toggle"
|
||||
type="checkbox"
|
||||
aria-label="enabled"
|
||||
role="switch"
|
||||
checked={localMod.enabled}
|
||||
/>
|
||||
</ModHeader>
|
||||
Description Not Available
|
||||
</details>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default LocalModRow;
|
||||
|
||||
@ -1,27 +1,23 @@
|
||||
import LocalModRow from "@components/mods/local/LocalModRow";
|
||||
import { LocalMod } from "src/types";
|
||||
import { useTauri } from "@hooks";
|
||||
import LocalModRow from "./LocalModRow";
|
||||
|
||||
const LocalMods = () => {
|
||||
const mods: LocalMod[] = [
|
||||
{
|
||||
enabled: true,
|
||||
modPath: "C:/",
|
||||
manifest: {
|
||||
uniqueName: "Bwc9876.TimeSaver",
|
||||
author: "Bwc9876",
|
||||
name: "Time Saver",
|
||||
version: "0.0.1"
|
||||
}
|
||||
}
|
||||
];
|
||||
const [status, mods, err] = useTauri<string[], undefined>("LOCAL-REFRESH", "get_local_mods");
|
||||
|
||||
return (
|
||||
<div className="mod-list">
|
||||
{mods.map((c) => (
|
||||
<LocalModRow key={c.manifest.uniqueName} {...c} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
switch (status) {
|
||||
case "Loading":
|
||||
return <p>Loading</p>;
|
||||
case "Done":
|
||||
return (
|
||||
<div className="mod-list">
|
||||
{mods!.map((m) => (
|
||||
<LocalModRow key={m} uniqueName={m} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
case "Error":
|
||||
return <div>{err!}</div>;
|
||||
}
|
||||
};
|
||||
|
||||
export default LocalMods;
|
||||
|
||||
@ -1,25 +1,39 @@
|
||||
import Icon from "@components/Icon";
|
||||
import ModActionButton from "@components/mods/ModActionButton";
|
||||
import ModHeader from "@components/mods/ModHeader";
|
||||
import { useTauri } from "@hooks";
|
||||
import { memo } from "react";
|
||||
import { FaArrowDown, FaGlobe } from "react-icons/fa";
|
||||
import { RemoteMod } from "src/types";
|
||||
|
||||
const RemoteModRow = memo((props: RemoteMod) => {
|
||||
return (
|
||||
<details>
|
||||
<ModHeader {...props}>
|
||||
<small>{props.downloadCount}</small>
|
||||
<ModActionButton ariaLabel="Install With Dependencies">
|
||||
<Icon iconType={FaArrowDown} />
|
||||
</ModActionButton>
|
||||
<ModActionButton ariaLabel="View On Website">
|
||||
<Icon iconType={FaGlobe} />
|
||||
</ModActionButton>
|
||||
</ModHeader>
|
||||
<small>{props.description}</small>
|
||||
</details>
|
||||
const RemoteModRow = memo((props: { uniqueName: string }) => {
|
||||
const [status, mod, err] = useTauri<RemoteMod, { uniqueName: string }>(
|
||||
"REMOTE-REFRESH",
|
||||
"get_load_mod",
|
||||
{ uniqueName: props.uniqueName }
|
||||
);
|
||||
|
||||
if (status === "Loading") {
|
||||
return <p>Loading...</p>;
|
||||
} else if (status === "Error") {
|
||||
return <p>{err}</p>;
|
||||
} else {
|
||||
const remote_mod = mod!;
|
||||
return (
|
||||
<details>
|
||||
<ModHeader {...remote_mod}>
|
||||
<small>{remote_mod.downloadCount}</small>
|
||||
<ModActionButton ariaLabel="Install With Dependencies">
|
||||
<Icon iconType={FaArrowDown} />
|
||||
</ModActionButton>
|
||||
<ModActionButton ariaLabel="View On Website">
|
||||
<Icon iconType={FaGlobe} />
|
||||
</ModActionButton>
|
||||
</ModHeader>
|
||||
<small>{remote_mod.description}</small>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default RemoteModRow;
|
||||
|
||||
@ -1,27 +1,24 @@
|
||||
import { useTauri } from "@hooks";
|
||||
import { RemoteMod } from "src/types";
|
||||
import RemoteModRow from "./RemoteModRow";
|
||||
|
||||
const RemoteMods = () => {
|
||||
const mods: RemoteMod[] = [
|
||||
{
|
||||
uniqueName: "Bwc9876.TimeSaver",
|
||||
downloadUrl: "google",
|
||||
downloadCount: 50,
|
||||
description: "abooga",
|
||||
repo: "ff",
|
||||
author: "Bwc9876",
|
||||
name: "Time Saver",
|
||||
version: "0.0.1"
|
||||
}
|
||||
];
|
||||
const [status, mods, err] = useTauri<string[], undefined>("REMOTE-REFRESH", "get_remote_mods");
|
||||
|
||||
return (
|
||||
<div className="mod-list">
|
||||
{mods.map((c) => (
|
||||
<RemoteModRow {...c} key={c.uniqueName} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
if (status === "Loading") {
|
||||
return <p>Loading...</p>;
|
||||
} else if (status === "Error") {
|
||||
return <p>{err}</p>;
|
||||
} else {
|
||||
const remote_mods = mods!;
|
||||
return (
|
||||
<div className="mod-list">
|
||||
{remote_mods.map((m) => (
|
||||
<RemoteModRow key={m} uniqueName={m} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default RemoteMods;
|
||||
|
||||
51
owmods_gui/frontend/src/hooks.ts
Normal file
51
owmods_gui/frontend/src/hooks.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export type LoadState = "Loading" | "Done" | "Error";
|
||||
|
||||
const subscribeTauri = (name: string) => {
|
||||
return async (callback: () => void) => {
|
||||
return await listen(name, callback);
|
||||
};
|
||||
};
|
||||
|
||||
const getTauriSnapshot = <T, P>(cmdName: string, payload: P): (() => Promise<T>) => {
|
||||
return async () => {
|
||||
return await invoke(cmdName, payload as Record<string, unknown>);
|
||||
};
|
||||
};
|
||||
|
||||
export const useTauri = <T, P>(
|
||||
eventName: string,
|
||||
commandName: string,
|
||||
commandPayload?: P
|
||||
): [LoadState, T | null, string | null] => {
|
||||
const [status, setStatus] = useState<LoadState>("Loading");
|
||||
const [data, setData] = useState<T | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let u = () => {};
|
||||
if (status !== "Loading") {
|
||||
console.debug(`Begin subscribe to ${eventName}`);
|
||||
subscribeTauri(eventName)(() => setStatus("Loading")).then((unsubscribe) => {
|
||||
u = unsubscribe;
|
||||
});
|
||||
} else {
|
||||
console.debug(`Invoking ${commandName} with args ${commandPayload ?? "null"}`);
|
||||
getTauriSnapshot(commandName, commandPayload)()
|
||||
.then((data) => {
|
||||
setData(data as T);
|
||||
setStatus("Done");
|
||||
})
|
||||
.catch((e) => {
|
||||
setError(e as string);
|
||||
setStatus("Error");
|
||||
});
|
||||
}
|
||||
return u;
|
||||
}, [status]);
|
||||
|
||||
return [status, data, error];
|
||||
};
|
||||
@ -20,7 +20,8 @@
|
||||
"@components/*": ["./src/components/*"],
|
||||
"@styles/*": ["./src/styles/*"],
|
||||
"@assets/*": ["./src/assets/*"],
|
||||
"@types": ["src/types"]
|
||||
"@types": ["src/types"],
|
||||
"@hooks": ["src/hooks"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
|
||||
@ -21,7 +21,8 @@ export default defineConfig({
|
||||
{ find: "@components", replacement: path.resolve(__dirname, "src/components") },
|
||||
{ find: "@styles", replacement: path.resolve(__dirname, "src/styles") },
|
||||
{ find: "@assets", replacement: path.resolve(__dirname, "src/assets") },
|
||||
{ find: "@types", replacement: path.resolve(__dirname, "src/types.ts") }
|
||||
{ find: "@types", replacement: path.resolve(__dirname, "src/types.ts") },
|
||||
{ find: "@hooks", replacement: path.resolve(__dirname, "src/hooks.ts") }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user