feat: misc improvements
This commit is contained in:
@ -1,5 +1,6 @@
|
|||||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||||
use crate::model::config::GlobalConfig;
|
use crate::model::config::GlobalConfig;
|
||||||
|
use crate::pkg::{Feature, Status};
|
||||||
use crate::profiles::AnyProfile;
|
use crate::profiles::AnyProfile;
|
||||||
use crate::{model::misc::Game, pkg::PkgKey};
|
use crate::{model::misc::Game, pkg::PkgKey};
|
||||||
use crate::pkg_store::PackageStore;
|
use crate::pkg_store::PackageStore;
|
||||||
@ -18,6 +19,13 @@ pub struct AppData {
|
|||||||
pub state: GlobalState,
|
pub state: GlobalState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||||
|
pub enum ToggleAction {
|
||||||
|
Disable,
|
||||||
|
EnableSelf,
|
||||||
|
EnableRecursive,
|
||||||
|
}
|
||||||
|
|
||||||
impl AppData {
|
impl AppData {
|
||||||
pub fn new(apph: AppHandle) -> AppData {
|
pub fn new(apph: AppHandle) -> AppData {
|
||||||
let cfg = std::fs::read_to_string(util::config_dir().join("config.json"))
|
let cfg = std::fs::read_to_string(util::config_dir().join("config.json"))
|
||||||
@ -59,26 +67,34 @@ impl AppData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_package(&mut self, key: PkgKey, enable: bool) -> Result<()> {
|
pub fn toggle_package(&mut self, key: PkgKey, action: ToggleAction) -> Result<()> {
|
||||||
log::debug!("toggle: {} {}", key, enable);
|
log::debug!("toggle: {} {:?}", key, action);
|
||||||
|
|
||||||
let profile = self.profile.as_mut().ok_or_else(|| anyhow!("No profile"))?;
|
let profile = self.profile.as_mut().ok_or_else(|| anyhow!("No profile"))?;
|
||||||
|
|
||||||
if enable {
|
if action != ToggleAction::Disable {
|
||||||
let pkg = self.pkgs.get(&key)?;
|
let pkg = self.pkgs.get(&key)?;
|
||||||
let loc = pkg.loc
|
let loc = pkg.loc
|
||||||
.clone()
|
.clone()
|
||||||
.ok_or_else(|| anyhow!("Attempted to enable a non-existent package"))?;
|
.ok_or_else(|| anyhow!("Attempted to enable a non-existent package"))?;
|
||||||
profile.mod_pkgs_mut().insert(key);
|
|
||||||
for d in &loc.dependencies {
|
if let Status::OK(feature_set) = loc.status {
|
||||||
_ = self.toggle_package(d.clone(), true);
|
log::debug!("{:?}", feature_set);
|
||||||
|
if feature_set.contains(Feature::Mod) {
|
||||||
|
profile.mod_pkgs_mut().insert(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if action == ToggleAction::EnableRecursive {
|
||||||
|
for d in &loc.dependencies {
|
||||||
|
_ = self.toggle_package(d.clone(), action);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
profile.mod_pkgs_mut().remove(&key);
|
profile.mod_pkgs_mut().remove(&key);
|
||||||
for (ckey, pkg) in self.pkgs.get_all() {
|
for (ckey, pkg) in self.pkgs.get_all() {
|
||||||
if let Some(loc) = pkg.loc {
|
if let Some(loc) = pkg.loc {
|
||||||
if loc.dependencies.contains(&key) {
|
if loc.dependencies.contains(&key) {
|
||||||
self.toggle_package(ckey, false)?;
|
self.toggle_package(ckey, action)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,4 +115,10 @@ impl AppData {
|
|||||||
}
|
}
|
||||||
hasher.finish().to_string()
|
hasher.finish().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn fix(&mut self) {
|
||||||
|
if let Some(p) = &mut self.profile {
|
||||||
|
p.fix(&self.pkgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use crate::pkg::{Package, PkgKey};
|
|||||||
use crate::pkg_store::{InstallResult, PackageStore};
|
use crate::pkg_store::{InstallResult, PackageStore};
|
||||||
use crate::profiles::ongeki::OngekiProfile;
|
use crate::profiles::ongeki::OngekiProfile;
|
||||||
use crate::profiles::{self, AnyProfile, Profile, ProfileMeta, ProfilePaths};
|
use crate::profiles::{self, AnyProfile, Profile, ProfileMeta, ProfilePaths};
|
||||||
use crate::appdata::AppData;
|
use crate::appdata::{AppData, ToggleAction};
|
||||||
use crate::model::misc::StartCheckError;
|
use crate::model::misc::StartCheckError;
|
||||||
use crate::util;
|
use crate::util;
|
||||||
|
|
||||||
@ -103,6 +103,9 @@ pub async fn delete_package(state: State<'_, tokio::sync::Mutex<AppData>>, key:
|
|||||||
let mut appd = state.lock().await;
|
let mut appd = state.lock().await;
|
||||||
appd.pkgs.delete_package(&key, true)
|
appd.pkgs.delete_package(&key, true)
|
||||||
.await
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
appd.toggle_package(key, ToggleAction::Disable)
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +124,7 @@ pub async fn toggle_package(state: State<'_, tokio::sync::Mutex<AppData>>, key:
|
|||||||
log::debug!("invoke: toggle_package({}, {})", key, enable);
|
log::debug!("invoke: toggle_package({}, {})", key, enable);
|
||||||
|
|
||||||
let mut appd = state.lock().await;
|
let mut appd = state.lock().await;
|
||||||
appd.toggle_package(key, enable)
|
appd.toggle_package(key, if enable { ToggleAction::EnableRecursive } else { ToggleAction::Disable })
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,6 +208,7 @@ pub async fn load_profile(state: State<'_, Mutex<AppData>>, game: Game, name: St
|
|||||||
|
|
||||||
let mut appd = state.lock().await;
|
let mut appd = state.lock().await;
|
||||||
appd.switch_profile(game, name).map_err(|e| e.to_string())?;
|
appd.switch_profile(game, name).map_err(|e| e.to_string())?;
|
||||||
|
appd.fix();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,14 +294,31 @@ pub async fn get_current_profile(state: State<'_, Mutex<AppData>>) -> Result<Opt
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn save_current_profile(state: State<'_, Mutex<AppData>>, profile: AnyProfile) -> Result<(), String> {
|
pub async fn sync_current_profile(state: State<'_, Mutex<AppData>>, profile: AnyProfile) -> Result<(), String> {
|
||||||
|
log::debug!("invoke: sync_current_profile");
|
||||||
|
|
||||||
|
let mut appd = state.lock().await;
|
||||||
|
if let Some(p) = &mut appd.profile {
|
||||||
|
p.sync(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn save_current_profile(state: State<'_, Mutex<AppData>>) -> Result<(), String> {
|
||||||
log::debug!("invoke: save_current_profile");
|
log::debug!("invoke: save_current_profile");
|
||||||
|
|
||||||
let mut appd = state.lock().await;
|
let mut appd = state.lock().await;
|
||||||
profile.save().map_err(|e| e.to_string())?;
|
appd.fix();
|
||||||
appd.profile = Some(profile);
|
match &mut appd.profile {
|
||||||
|
Some(p) => {
|
||||||
Ok(())
|
p.save().map_err(|e| e.to_string())
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
Err("no profile to save".to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
@ -8,15 +8,19 @@ mod appdata;
|
|||||||
mod modules;
|
mod modules;
|
||||||
mod profiles;
|
mod profiles;
|
||||||
|
|
||||||
|
use std::sync::OnceLock;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use closure::closure;
|
use closure::closure;
|
||||||
use appdata::AppData;
|
use appdata::{AppData, ToggleAction};
|
||||||
use model::misc::Game;
|
use model::misc::Game;
|
||||||
use pkg::PkgKey;
|
use pkg::PkgKey;
|
||||||
use tauri::{AppHandle, Listener, Manager};
|
use pkg_store::Payload;
|
||||||
|
use tauri::{AppHandle, Listener, Manager, RunEvent};
|
||||||
use tauri_plugin_deep_link::DeepLinkExt;
|
use tauri_plugin_deep_link::DeepLinkExt;
|
||||||
use tauri_plugin_cli::CliExt;
|
use tauri_plugin_cli::CliExt;
|
||||||
use tokio::{sync::Mutex, fs, try_join};
|
use tokio::{fs, sync::Mutex, try_join};
|
||||||
|
|
||||||
|
static EXIT_REQUESTED: OnceLock<()> = OnceLock::new();
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub async fn run(_args: Vec<String>) {
|
pub async fn run(_args: Vec<String>) {
|
||||||
@ -30,7 +34,7 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
);
|
);
|
||||||
|
|
||||||
tauri::Builder::default()
|
let tauri = tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_single_instance::init(|app, args, _cwd| {
|
.plugin(tauri_plugin_single_instance::init(|app, args, _cwd| {
|
||||||
let _ = app
|
let _ = app
|
||||||
.get_webview_window("main")
|
.get_webview_window("main")
|
||||||
@ -65,8 +69,8 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
} else {
|
} else {
|
||||||
tauri::WebviewWindowBuilder::new(app, "main", tauri::WebviewUrl::App("index.html".into()))
|
tauri::WebviewWindowBuilder::new(app, "main", tauri::WebviewUrl::App("index.html".into()))
|
||||||
.title("STARTLINER")
|
.title("STARTLINER")
|
||||||
.inner_size(600f64, 500f64)
|
.inner_size(640f64, 480f64)
|
||||||
.min_inner_size(600f64, 500f64)
|
.min_inner_size(640f64, 480f64)
|
||||||
.build()?;
|
.build()?;
|
||||||
start_immediately = false;
|
start_immediately = false;
|
||||||
}
|
}
|
||||||
@ -102,17 +106,19 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.listen("download-end", closure!(clone apph, |ev| {
|
app.listen("download-end", closure!(clone apph, |ev| {
|
||||||
|
log::debug!("download-end triggered: {}", ev.payload());
|
||||||
let raw = ev.payload();
|
let raw = ev.payload();
|
||||||
let key = PkgKey(raw[1..raw.len()-1].to_owned());
|
let key = PkgKey(raw[1..raw.len()-1].to_owned());
|
||||||
let apph = apph.clone();
|
let apph = apph.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let mutex = apph.state::<Mutex<AppData>>();
|
let mutex = apph.state::<Mutex<AppData>>();
|
||||||
let mut appd = mutex.lock().await;
|
let mut appd = mutex.lock().await;
|
||||||
_ = appd.pkgs.install_package(&key, true, false).await;
|
log::debug!("download-end install {:?}", appd.pkgs.install_package(&key, true, false).await);
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
app.listen("launch-end", closure!(clone apph, |_| {
|
app.listen("launch-end", closure!(clone apph, |_| {
|
||||||
|
log::debug!("launch-end triggered");
|
||||||
let apph = apph.clone();
|
let apph = apph.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let mutex = apph.state::<Mutex<AppData>>();
|
let mutex = apph.state::<Mutex<AppData>>();
|
||||||
@ -123,6 +129,26 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
app.listen("install-end-prelude", closure!(clone apph, |ev| {
|
||||||
|
log::debug!("install-end-prelude triggered: {}", ev.payload());
|
||||||
|
let payload = serde_json::from_str::<Payload>(ev.payload());
|
||||||
|
let apph = apph.clone();
|
||||||
|
if let Ok(payload) = payload {
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
let mutex = apph.state::<Mutex<AppData>>();
|
||||||
|
let mut appd = mutex.lock().await;
|
||||||
|
log::debug!(
|
||||||
|
"install-end-prelude toggle {:?}",
|
||||||
|
appd.toggle_package(payload.pkg.clone(), ToggleAction::EnableSelf)
|
||||||
|
);
|
||||||
|
use tauri::Emitter;
|
||||||
|
log::debug!("install-end {:?}", apph.emit("install-end", payload));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log::error!("install-end-prelude: invalid payload: {}", ev.payload());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
if start_immediately == true {
|
if start_immediately == true {
|
||||||
let apph = apph.clone();
|
let apph = apph.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
@ -163,14 +189,40 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
cmd::duplicate_profile,
|
cmd::duplicate_profile,
|
||||||
cmd::delete_profile,
|
cmd::delete_profile,
|
||||||
cmd::get_current_profile,
|
cmd::get_current_profile,
|
||||||
|
cmd::sync_current_profile,
|
||||||
cmd::save_current_profile,
|
cmd::save_current_profile,
|
||||||
|
|
||||||
cmd::list_displays,
|
cmd::list_displays,
|
||||||
cmd::list_platform_capabilities,
|
cmd::list_platform_capabilities,
|
||||||
cmd::list_directories,
|
cmd::list_directories,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.build(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while building tauri application");
|
||||||
|
|
||||||
|
tauri.run(move |app, event| {
|
||||||
|
match event {
|
||||||
|
RunEvent::ExitRequested { api, code, .. } => {
|
||||||
|
log::debug!("exit request {:?} {:?}", api, code);
|
||||||
|
if EXIT_REQUESTED.get().is_none() {
|
||||||
|
_= EXIT_REQUESTED.set(());
|
||||||
|
api.prevent_exit();
|
||||||
|
let app = app.clone();
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
let mutex = app.state::<Mutex<AppData>>();
|
||||||
|
let appd = mutex.lock().await;
|
||||||
|
if let Some(p) = &appd.profile {
|
||||||
|
log::debug!("save: {:?}", p.save());
|
||||||
|
app.exit(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RunEvent::Exit => {
|
||||||
|
log::info!("exit");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deep_link(app: AppHandle, args: Vec<String>) {
|
fn deep_link(app: AppHandle, args: Vec<String>) {
|
||||||
|
@ -2,9 +2,10 @@ use std::path::PathBuf;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::pkg::PkgKey;
|
use crate::pkg::PkgKey;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
#[derive(Deserialize, Serialize, Clone, Default, PartialEq)]
|
||||||
pub enum Aime {
|
pub enum Aime {
|
||||||
BuiltIn,
|
Disabled,
|
||||||
|
#[default] BuiltIn,
|
||||||
AMNet(PkgKey),
|
AMNet(PkgKey),
|
||||||
Other(PkgKey),
|
Other(PkgKey),
|
||||||
}
|
}
|
||||||
@ -27,7 +28,8 @@ pub struct Segatools {
|
|||||||
pub target: PathBuf,
|
pub target: PathBuf,
|
||||||
pub hook: Option<PkgKey>,
|
pub hook: Option<PkgKey>,
|
||||||
pub io: Option<PkgKey>,
|
pub io: Option<PkgKey>,
|
||||||
pub aime: Option<Aime>,
|
#[serde(default)]
|
||||||
|
pub aime: Aime,
|
||||||
pub amfs: PathBuf,
|
pub amfs: PathBuf,
|
||||||
pub option: PathBuf,
|
pub option: PathBuf,
|
||||||
pub appdata: PathBuf,
|
pub appdata: PathBuf,
|
||||||
@ -45,7 +47,7 @@ impl Default for Segatools {
|
|||||||
amfs: PathBuf::default(),
|
amfs: PathBuf::default(),
|
||||||
option: PathBuf::default(),
|
option: PathBuf::default(),
|
||||||
appdata: PathBuf::from("appdata"),
|
appdata: PathBuf::from("appdata"),
|
||||||
aime: Some(Aime::BuiltIn),
|
aime: Aime::default(),
|
||||||
intel: false,
|
intel: false,
|
||||||
amnet: AMNet::default(),
|
amnet: AMNet::default(),
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ impl Network {
|
|||||||
cmd.arg(&self.local_path);
|
cmd.arg(&self.local_path);
|
||||||
cmd.current_dir(artemis_dir);
|
cmd.current_dir(artemis_dir);
|
||||||
cmd.spawn()
|
cmd.spawn()
|
||||||
.map_err(|e| anyhow!("Unable to spawn artemis: {}", e))?;
|
.map_err(|e| anyhow!("unable to spawn artemis: {}", e))?;
|
||||||
} else {
|
} else {
|
||||||
log::warn!("unable to parse the artemis hostname");
|
log::warn!("unable to parse the artemis hostname");
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,34 @@ use std::path::PathBuf;
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use ini::Ini;
|
use ini::Ini;
|
||||||
use crate::{model::{profile::{Aime, Segatools}, segatools_base::segatools_base}, profiles::ProfilePaths, util::{self, PathStr}};
|
use crate::{model::{profile::{Aime, Segatools}, segatools_base::segatools_base}, profiles::ProfilePaths, util::{self, PathStr}};
|
||||||
|
use crate::pkg_store::PackageStore;
|
||||||
|
|
||||||
impl Segatools {
|
impl Segatools {
|
||||||
|
pub fn fix(&mut self, store: &PackageStore) {
|
||||||
|
macro_rules! remove_if_nonpresent {
|
||||||
|
($item:expr,$key:expr,$emptyval:expr,$store:expr) => {
|
||||||
|
if let Ok(pkg) = $store.get($key) {
|
||||||
|
if pkg.loc.is_none() {
|
||||||
|
$item = $emptyval;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$item = $emptyval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(key) = &self.hook {
|
||||||
|
remove_if_nonpresent!(self.hook, key, None, store);
|
||||||
|
}
|
||||||
|
if let Some(key) = &self.io {
|
||||||
|
remove_if_nonpresent!(self.io, key, None, store);
|
||||||
|
}
|
||||||
|
match &self.aime {
|
||||||
|
Aime::AMNet(key) => remove_if_nonpresent!(self.aime, key, Aime::BuiltIn, store),
|
||||||
|
Aime::Other(key) => remove_if_nonpresent!(self.aime, key, Aime::BuiltIn, store),
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
pub async fn line_up(&self, p: &impl ProfilePaths) -> Result<Ini> {
|
pub async fn line_up(&self, p: &impl ProfilePaths) -> Result<Ini> {
|
||||||
log::debug!("begin line-up: segatools");
|
log::debug!("begin line-up: segatools");
|
||||||
|
|
||||||
@ -50,11 +76,11 @@ impl Segatools {
|
|||||||
pfx_dir.join("BepInEx").join("core").join("BepInEx.Preloader.dll").stringify()?
|
pfx_dir.join("BepInEx").join("core").join("BepInEx.Preloader.dll").stringify()?
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(aime) = &self.aime {
|
if self.aime != Aime::Disabled {
|
||||||
ini_out.with_section(Some("aime"))
|
ini_out.with_section(Some("aime"))
|
||||||
.set("enable", "1")
|
.set("enable", "1")
|
||||||
.set("aimePath", p.config_dir().join("aime.txt").stringify()?);
|
.set("aimePath", p.config_dir().join("aime.txt").stringify()?);
|
||||||
if let Aime::AMNet(key) = aime {
|
if let Aime::AMNet(key) = &self.aime {
|
||||||
let mut aimeio = ini_out.with_section(Some("aimeio"));
|
let mut aimeio = ini_out.with_section(Some("aimeio"));
|
||||||
aimeio
|
aimeio
|
||||||
.set("path", util::pkg_dir().join(key.to_string()).join("segatools").join("aimeio.dll").stringify()?)
|
.set("path", util::pkg_dir().join(key.to_string()).join("segatools").join("aimeio.dll").stringify()?)
|
||||||
|
@ -20,7 +20,6 @@ pub struct Package {
|
|||||||
pub namespace: String,
|
pub namespace: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub icon: String,
|
|
||||||
pub loc: Option<Local>,
|
pub loc: Option<Local>,
|
||||||
pub rmt: Option<Remote>
|
pub rmt: Option<Remote>
|
||||||
}
|
}
|
||||||
@ -50,6 +49,7 @@ pub struct Local {
|
|||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub dependencies: BTreeSet<PkgKey>,
|
pub dependencies: BTreeSet<PkgKey>,
|
||||||
pub status: Status,
|
pub status: Status,
|
||||||
|
pub icon: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
@ -58,6 +58,7 @@ pub struct Remote {
|
|||||||
pub version: String,
|
pub version: String,
|
||||||
pub package_url: String,
|
pub package_url: String,
|
||||||
pub download_url: String,
|
pub download_url: String,
|
||||||
|
pub icon: String,
|
||||||
pub deprecated: bool,
|
pub deprecated: bool,
|
||||||
pub nsfw: bool,
|
pub nsfw: bool,
|
||||||
pub categories: Vec<String>,
|
pub categories: Vec<String>,
|
||||||
@ -76,11 +77,11 @@ impl Package {
|
|||||||
namespace: p.owner,
|
namespace: p.owner,
|
||||||
name: v.name,
|
name: v.name,
|
||||||
description: v.description,
|
description: v.description,
|
||||||
icon: v.icon,
|
|
||||||
loc: None,
|
loc: None,
|
||||||
rmt: Some(Remote {
|
rmt: Some(Remote {
|
||||||
package_url: p.package_url,
|
package_url: p.package_url,
|
||||||
download_url: v.download_url,
|
download_url: v.download_url,
|
||||||
|
icon: v.icon,
|
||||||
deprecated: p.is_deprecated,
|
deprecated: p.is_deprecated,
|
||||||
nsfw: p.has_nsfw_content,
|
nsfw: p.has_nsfw_content,
|
||||||
version: v.version_number,
|
version: v.version_number,
|
||||||
@ -107,10 +108,10 @@ impl Package {
|
|||||||
namespace: Self::dir_to_namespace(&dir)?,
|
namespace: Self::dir_to_namespace(&dir)?,
|
||||||
name: mft.name.clone(),
|
name: mft.name.clone(),
|
||||||
description: mft.description.clone(),
|
description: mft.description.clone(),
|
||||||
icon,
|
|
||||||
loc: Some(Local {
|
loc: Some(Local {
|
||||||
version: mft.version_number,
|
version: mft.version_number,
|
||||||
path: dir.to_owned(),
|
path: dir.to_owned(),
|
||||||
|
icon,
|
||||||
status,
|
status,
|
||||||
dependencies
|
dependencies
|
||||||
}),
|
}),
|
||||||
|
@ -22,7 +22,7 @@ pub struct Payload {
|
|||||||
pub pkg: PkgKey
|
pub pkg: PkgKey
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
|
||||||
pub enum InstallResult {
|
pub enum InstallResult {
|
||||||
Ready, Deferred
|
Ready, Deferred
|
||||||
}
|
}
|
||||||
@ -167,7 +167,7 @@ impl PackageStore {
|
|||||||
archive.extract(path)?;
|
archive.extract(path)?;
|
||||||
self.reload_package(key.to_owned()).await;
|
self.reload_package(key.to_owned()).await;
|
||||||
|
|
||||||
self.app.emit("install-end", Payload {
|
self.app.emit("install-end-prelude", Payload {
|
||||||
pkg: key.to_owned()
|
pkg: key.to_owned()
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@ -189,7 +189,7 @@ impl PackageStore {
|
|||||||
let rv = Self::clean_up_package(&path).await;
|
let rv = Self::clean_up_package(&path).await;
|
||||||
|
|
||||||
if rv.is_ok() {
|
if rv.is_ok() {
|
||||||
self.app.emit("install-end", Payload {
|
self.app.emit("install-end-prelude", Payload {
|
||||||
pkg: key.to_owned()
|
pkg: key.to_owned()
|
||||||
})?;
|
})?;
|
||||||
log::info!("deleted {}", key);
|
log::info!("deleted {}", key);
|
||||||
|
@ -3,7 +3,7 @@ use ongeki::OngekiProfile;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
use std::{collections::BTreeSet, path::{Path, PathBuf}};
|
use std::{collections::BTreeSet, path::{Path, PathBuf}};
|
||||||
use crate::{model::misc::Game, modules::package::prepare_packages, pkg::PkgKey, util};
|
use crate::{model::misc::Game, modules::package::prepare_packages, pkg::PkgKey, pkg_store::PackageStore, util};
|
||||||
|
|
||||||
pub mod ongeki;
|
pub mod ongeki;
|
||||||
|
|
||||||
@ -83,6 +83,26 @@ impl AnyProfile {
|
|||||||
}
|
}
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
pub fn fix(&mut self, store: &PackageStore) {
|
||||||
|
match self {
|
||||||
|
Self::OngekiProfile(p) => p.sgt.fix(store)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn sync(&mut self, source: AnyProfile) {
|
||||||
|
match self {
|
||||||
|
Self::OngekiProfile(p) => {
|
||||||
|
#[allow(irrefutable_let_patterns)]
|
||||||
|
if let AnyProfile::OngekiProfile(source) = source {
|
||||||
|
p.bepinex = source.bepinex;
|
||||||
|
p.display = source.display;
|
||||||
|
p.network = source.network;
|
||||||
|
p.sgt = source.sgt;
|
||||||
|
} else {
|
||||||
|
log::error!("sync: invalid profile type {:?}", source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
pub async fn line_up(&self, pkg_hash: String, _app: AppHandle) -> Result<()> {
|
pub async fn line_up(&self, pkg_hash: String, _app: AppHandle) -> Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Self::OngekiProfile(_p) => {
|
Self::OngekiProfile(_p) => {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
import Chip from 'primevue/chip';
|
import Chip from 'primevue/chip';
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||||
import { Feature, Package } from '../types';
|
import { Feature, Package } from '../types';
|
||||||
@ -13,8 +14,8 @@ const props = defineProps({
|
|||||||
showIcon: Boolean,
|
showIcon: Boolean,
|
||||||
});
|
});
|
||||||
|
|
||||||
const iconSrc = () => {
|
const iconSrc = computed(() => {
|
||||||
const icon = props.pkg?.icon;
|
const icon = props.pkg?.loc?.icon ?? props.pkg?.rmt?.icon;
|
||||||
|
|
||||||
if (icon === undefined) {
|
if (icon === undefined) {
|
||||||
return '';
|
return '';
|
||||||
@ -23,13 +24,13 @@ const iconSrc = () => {
|
|||||||
} else {
|
} else {
|
||||||
return convertFileSrc(icon);
|
return convertFileSrc(icon);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<img
|
<img
|
||||||
v-if="showIcon"
|
v-if="showIcon"
|
||||||
:src="iconSrc()"
|
:src="iconSrc"
|
||||||
class="self-center rounded-sm"
|
class="self-center rounded-sm"
|
||||||
width="32px"
|
width="32px"
|
||||||
height="32px"
|
height="32px"
|
||||||
@ -66,7 +67,7 @@ const iconSrc = () => {
|
|||||||
<span
|
<span
|
||||||
v-if="hasFeature(pkg, Feature.Aime)"
|
v-if="hasFeature(pkg, Feature.Aime)"
|
||||||
v-tooltip="'Aime'"
|
v-tooltip="'Aime'"
|
||||||
class="pi pi-wrench ml-1 text-purple-400"
|
class="pi pi-credit-card ml-1 text-purple-400"
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
@ -29,24 +29,48 @@ const displayList: Ref<{ title: string; value: string }[]> = ref([
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
invoke('list_platform_capabilities')
|
const loadDisplays = () => {
|
||||||
.then(async (v: unknown) => {
|
const newList = [
|
||||||
if (Array.isArray(v)) {
|
{
|
||||||
capabilities.value.push(...v);
|
title: 'Primary',
|
||||||
}
|
value: 'default',
|
||||||
|
},
|
||||||
if (capabilities.value.includes('display')) {
|
];
|
||||||
for (const [devName, devString] of (await invoke(
|
invoke('list_platform_capabilities')
|
||||||
'list_displays'
|
.then(async (v: unknown) => {
|
||||||
)) as Array<[string, string]>) {
|
let different = false;
|
||||||
displayList.value.push({
|
if (Array.isArray(v)) {
|
||||||
title: `${devName.replace('\\\\.\\', '')} (${devString})`,
|
capabilities.value.push(...v);
|
||||||
value: devName,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
if (capabilities.value.includes('display')) {
|
||||||
})
|
for (const [devName, devString] of (await invoke(
|
||||||
.catch(() => {});
|
'list_displays'
|
||||||
|
)) as Array<[string, string]>) {
|
||||||
|
newList.push({
|
||||||
|
title: `${devName.replace('\\\\.\\', '')} (${devString})`,
|
||||||
|
value: devName,
|
||||||
|
});
|
||||||
|
if (
|
||||||
|
displayList.value.find(
|
||||||
|
(item) => item.value === devName
|
||||||
|
) === undefined
|
||||||
|
) {
|
||||||
|
different = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (displayList.value.length !== newList.length) {
|
||||||
|
different = true;
|
||||||
|
}
|
||||||
|
if (different) {
|
||||||
|
displayList.value = newList;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
loadDisplays();
|
||||||
|
prf.reload();
|
||||||
|
|
||||||
const aimeCodeModel = computed({
|
const aimeCodeModel = computed({
|
||||||
get() {
|
get() {
|
||||||
@ -145,6 +169,8 @@ const extraDisplayOptionsDisabled = computed(() => {
|
|||||||
:options="displayList"
|
:options="displayList"
|
||||||
option-label="title"
|
option-label="title"
|
||||||
option-value="value"
|
option-value="value"
|
||||||
|
placeholder="(Disconnected)"
|
||||||
|
@show="loadDisplays"
|
||||||
></Select>
|
></Select>
|
||||||
</OptionRow>
|
</OptionRow>
|
||||||
<OptionRow class="number-input" title="Game resolution">
|
<OptionRow class="number-input" title="Game resolution">
|
||||||
@ -212,8 +238,9 @@ const extraDisplayOptionsDisabled = computed(() => {
|
|||||||
/>
|
/>
|
||||||
</OptionRow>
|
</OptionRow>
|
||||||
<OptionRow
|
<OptionRow
|
||||||
title="Match display resolution with the game"
|
title="Borderless fullscreen"
|
||||||
v-if="capabilities.includes('display')"
|
v-if="capabilities.includes('display')"
|
||||||
|
tooltip="Match display resolution with the game."
|
||||||
>
|
>
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
:disabled="
|
:disabled="
|
||||||
@ -305,7 +332,7 @@ const extraDisplayOptionsDisabled = computed(() => {
|
|||||||
<Select
|
<Select
|
||||||
v-model="prf.current!.sgt.aime"
|
v-model="prf.current!.sgt.aime"
|
||||||
:options="[
|
:options="[
|
||||||
{ title: 'disabled', value: null },
|
{ title: 'none', value: 'Disabled' },
|
||||||
{ title: 'segatools built-in', value: 'BuiltIn' },
|
{ title: 'segatools built-in', value: 'BuiltIn' },
|
||||||
...pkgs.aimes.map((p) => {
|
...pkgs.aimes.map((p) => {
|
||||||
return {
|
return {
|
||||||
@ -325,7 +352,7 @@ const extraDisplayOptionsDisabled = computed(() => {
|
|||||||
<InputText
|
<InputText
|
||||||
class="shrink"
|
class="shrink"
|
||||||
size="small"
|
size="small"
|
||||||
:disabled="prf.current!.sgt.aime === null"
|
:disabled="prf.current!.sgt.aime === 'Disabled'"
|
||||||
:maxlength="20"
|
:maxlength="20"
|
||||||
placeholder="00000000000000000000"
|
placeholder="00000000000000000000"
|
||||||
v-model="aimeCodeModel"
|
v-model="aimeCodeModel"
|
||||||
@ -350,7 +377,10 @@ const extraDisplayOptionsDisabled = computed(() => {
|
|||||||
v-model="prf.current!.sgt.amnet.addr"
|
v-model="prf.current!.sgt.amnet.addr"
|
||||||
/>
|
/>
|
||||||
</OptionRow>
|
</OptionRow>
|
||||||
<OptionRow title="Use AiMeDB for physical cards">
|
<OptionRow
|
||||||
|
title="Use AiMeDB for physical cards"
|
||||||
|
tooltip="Whether physical cards should use AiMeDB to retrieve access codes. If the game is using a hosted network, enable this option to load the same account data/profile as you would get on a physical cab."
|
||||||
|
>
|
||||||
<ToggleSwitch v-model="prf.current!.sgt.amnet.physical" />
|
<ToggleSwitch v-model="prf.current!.sgt.amnet.physical" />
|
||||||
</OptionRow>
|
</OptionRow>
|
||||||
</div>
|
</div>
|
||||||
@ -383,6 +413,11 @@ const extraDisplayOptionsDisabled = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.p-inputtext {
|
.p-inputtext {
|
||||||
font-family: monospace;
|
width: 40vw;
|
||||||
|
font-family: monospace !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-select {
|
||||||
|
width: 40vw;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -7,6 +7,7 @@ const category = getCurrentInstance()?.parent?.parent?.parent?.parent; // yes in
|
|||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
title: String,
|
title: String,
|
||||||
|
tooltip: String,
|
||||||
});
|
});
|
||||||
|
|
||||||
const searched = computed(() => {
|
const searched = computed(() => {
|
||||||
@ -24,7 +25,15 @@ const searched = computed(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="searched" class="flex flex-row w-full p-2 gap-2 items-center">
|
<div v-if="searched" class="flex flex-row w-full p-2 gap-2 items-center">
|
||||||
<div class="grow">{{ title }}</div>
|
<div class="grow">
|
||||||
|
<span>{{ title }}</span>
|
||||||
|
<span
|
||||||
|
v-if="tooltip"
|
||||||
|
class="pi pi-question-circle ml-2"
|
||||||
|
v-tooltip="tooltip"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -45,6 +45,7 @@ const startline = async (force: boolean) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
await invoke('save_current_profile');
|
||||||
await invoke('startline');
|
await invoke('startline');
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
startStatus.value = 'ready';
|
startStatus.value = 'ready';
|
||||||
|
@ -182,6 +182,7 @@ export const usePkgStore = defineStore('pkg', {
|
|||||||
export const usePrfStore = defineStore('prf', () => {
|
export const usePrfStore = defineStore('prf', () => {
|
||||||
const current: Ref<Profile | null> = ref(null);
|
const current: Ref<Profile | null> = ref(null);
|
||||||
const list: Ref<ProfileMeta[]> = ref([]);
|
const list: Ref<ProfileMeta[]> = ref([]);
|
||||||
|
let timeout: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
const isPkgEnabled = (pkg: Package | undefined) =>
|
const isPkgEnabled = (pkg: Package | undefined) =>
|
||||||
computed(
|
computed(
|
||||||
@ -281,9 +282,13 @@ export const usePrfStore = defineStore('prf', () => {
|
|||||||
|
|
||||||
watchEffect(async () => {
|
watchEffect(async () => {
|
||||||
if (current.value !== null) {
|
if (current.value !== null) {
|
||||||
await invoke('save_current_profile', {
|
await invoke('sync_current_profile', {
|
||||||
profile: { OngekiProfile: current.value },
|
profile: { OngekiProfile: current.value },
|
||||||
});
|
});
|
||||||
|
if (timeout !== null) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
timeout = setTimeout(() => invoke('save_current_profile'), 2000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2,12 +2,12 @@ export interface Package {
|
|||||||
namespace: string;
|
namespace: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
icon: string;
|
|
||||||
loc: {
|
loc: {
|
||||||
version: string;
|
version: string;
|
||||||
path: string;
|
path: string;
|
||||||
dependencies: string[];
|
dependencies: string[];
|
||||||
status: Status;
|
status: Status;
|
||||||
|
icon: string;
|
||||||
} | null;
|
} | null;
|
||||||
rmt: {
|
rmt: {
|
||||||
version: string;
|
version: string;
|
||||||
@ -16,6 +16,7 @@ export interface Package {
|
|||||||
deprecated: boolean;
|
deprecated: boolean;
|
||||||
nsfw: boolean;
|
nsfw: boolean;
|
||||||
categories: string[];
|
categories: string[];
|
||||||
|
icon: string;
|
||||||
} | null;
|
} | null;
|
||||||
js: {
|
js: {
|
||||||
busy: boolean;
|
busy: boolean;
|
||||||
@ -51,7 +52,7 @@ export interface SegatoolsConfig {
|
|||||||
amfs: string;
|
amfs: string;
|
||||||
option: string;
|
option: string;
|
||||||
appdata: string;
|
appdata: string;
|
||||||
aime: { AMNet: string } | { Other: string } | null;
|
aime: { AMNet: string } | { Other: string } | 'BuiltIn' | 'Disabled';
|
||||||
intel: boolean;
|
intel: boolean;
|
||||||
amnet: {
|
amnet: {
|
||||||
name: string;
|
name: string;
|
||||||
|
Reference in New Issue
Block a user