feat: start checks
This commit is contained in:
1
TODO.md
1
TODO.md
@ -1,7 +1,6 @@
|
|||||||
### Short-term
|
### Short-term
|
||||||
|
|
||||||
- CHUNITHM support
|
- CHUNITHM support
|
||||||
- Start checks
|
|
||||||
- https://gitea.tendokyu.moe/TeamTofuShop/segatools/issues/63
|
- https://gitea.tendokyu.moe/TeamTofuShop/segatools/issues/63
|
||||||
|
|
||||||
### Long-term
|
### Long-term
|
||||||
|
@ -1,19 +1,14 @@
|
|||||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||||
|
use crate::model::config::GlobalConfig;
|
||||||
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;
|
||||||
use crate::util;
|
use crate::util;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Default)]
|
|
||||||
pub struct GlobalConfig {
|
|
||||||
pub recent_profile: Option<(Game, String)>
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GlobalState {
|
pub struct GlobalState {
|
||||||
pub remain_open: bool
|
pub remain_open: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppData {
|
pub struct AppData {
|
||||||
@ -34,6 +29,8 @@ impl AppData {
|
|||||||
None => None
|
None => None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
log::debug!("Recent profile: {:?}", profile);
|
||||||
|
|
||||||
AppData {
|
AppData {
|
||||||
profile: profile,
|
profile: profile,
|
||||||
pkgs: PackageStore::new(apph.clone()),
|
pkgs: PackageStore::new(apph.clone()),
|
||||||
@ -43,7 +40,7 @@ impl AppData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(&self) -> Result<(), std::io::Error> {
|
pub fn write(&self) -> Result<(), std::io::Error> {
|
||||||
std::fs::write(util::config_dir().join("config.json"), serde_json::to_string(&self.cfg)?)
|
std::fs::write(util::config_dir().join("config.json"), serde_json::to_string_pretty(&self.cfg)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn switch_profile(&mut self, game: Game, name: String) -> Result<()> {
|
pub fn switch_profile(&mut self, game: Game, name: String) -> Result<()> {
|
||||||
@ -72,12 +69,12 @@ impl AppData {
|
|||||||
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.pkgs_mut().insert(key);
|
profile.mod_pkgs_mut().insert(key);
|
||||||
for d in &loc.dependencies {
|
for d in &loc.dependencies {
|
||||||
_ = self.toggle_package(d.clone(), true);
|
_ = self.toggle_package(d.clone(), true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
profile.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) {
|
||||||
@ -92,10 +89,13 @@ impl AppData {
|
|||||||
|
|
||||||
pub fn sum_packages(&self, p: &AnyProfile) -> String {
|
pub fn sum_packages(&self, p: &AnyProfile) -> String {
|
||||||
let mut hasher = DefaultHasher::new();
|
let mut hasher = DefaultHasher::new();
|
||||||
for pkg in p.pkgs().into_iter() {
|
for key in p.mod_pkgs().into_iter() {
|
||||||
let x = self.pkgs.get(&pkg).unwrap().loc.as_ref().unwrap();
|
if let Ok(pkg) = self.pkgs.get(&key) {
|
||||||
pkg.hash(&mut hasher);
|
if let Some(loc) = &pkg.loc {
|
||||||
x.version.hash(&mut hasher);
|
key.hash(&mut hasher);
|
||||||
|
loc.version.hash(&mut hasher);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
hasher.finish().to_string()
|
hasher.finish().to_string()
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,50 @@ use tokio::fs;
|
|||||||
use tauri::{AppHandle, Manager, State};
|
use tauri::{AppHandle, Manager, State};
|
||||||
use crate::model::misc::Game;
|
use crate::model::misc::Game;
|
||||||
use crate::pkg::{Package, PkgKey};
|
use crate::pkg::{Package, PkgKey};
|
||||||
use crate::pkg_store::InstallResult;
|
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;
|
||||||
|
use crate::model::misc::StartCheckError;
|
||||||
use crate::util;
|
use crate::util;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn start_check(state: State<'_, Mutex<AppData>>) -> Result<Vec<StartCheckError>, String> {
|
||||||
|
log::debug!("invoke: start_check");
|
||||||
|
|
||||||
|
let appd = state.lock().await;
|
||||||
|
let prf = appd.profile.as_ref().ok_or_else(|| format!("No profile to check"))?;
|
||||||
|
let pkgs = appd.pkgs.get_all();
|
||||||
|
|
||||||
|
let mut res = Vec::new();
|
||||||
|
for key in prf.mod_pkgs() {
|
||||||
|
if let Some(pkg) = pkgs.get(key) {
|
||||||
|
if let Some(loc) = &pkg.loc {
|
||||||
|
for dep in &loc.dependencies {
|
||||||
|
if !prf.mod_pkgs().contains(dep) {
|
||||||
|
res.push(StartCheckError::MissingDependency(key.clone(), dep.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.push(StartCheckError::MissingLocalPackage(key.clone()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.push(StartCheckError::MissingRemotePackage(key.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for key in prf.special_pkgs() {
|
||||||
|
log::debug!("special: {}", key);
|
||||||
|
if let Some(pkg) = pkgs.get(&key) {
|
||||||
|
if pkg.loc.is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.push(StartCheckError::MissingTool(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn startline(app: AppHandle) -> Result<(), String> {
|
pub async fn startline(app: AppHandle) -> Result<(), String> {
|
||||||
log::debug!("invoke: startline");
|
log::debug!("invoke: startline");
|
||||||
@ -110,9 +148,25 @@ pub async fn get_all_packages(state: State<'_, Mutex<AppData>>) -> Result<HashMa
|
|||||||
pub async fn fetch_listings(state: State<'_, Mutex<AppData>>) -> Result<(), String> {
|
pub async fn fetch_listings(state: State<'_, Mutex<AppData>>) -> Result<(), String> {
|
||||||
log::debug!("invoke: fetch_listings");
|
log::debug!("invoke: fetch_listings");
|
||||||
|
|
||||||
|
{
|
||||||
|
let appd = state.lock().await;
|
||||||
|
if !appd.pkgs.is_offline() {
|
||||||
|
log::warn!("fetch_listings: already done");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if appd.cfg.offline_mode {
|
||||||
|
log::info!("fetch_listings: skipped");
|
||||||
|
return Err("offline mode".to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let listings = PackageStore::fetch_listings().await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let mut appd = state.lock().await;
|
let mut appd = state.lock().await;
|
||||||
appd.pkgs.fetch_listings().await
|
appd.pkgs.process_fetched_listings(listings);
|
||||||
.map_err(|e| e.to_string())
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
@ -144,6 +144,10 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
cmd::start_check,
|
||||||
|
cmd::startline,
|
||||||
|
cmd::kill,
|
||||||
|
|
||||||
cmd::get_package,
|
cmd::get_package,
|
||||||
cmd::get_all_packages,
|
cmd::get_all_packages,
|
||||||
cmd::reload_all_packages,
|
cmd::reload_all_packages,
|
||||||
@ -161,9 +165,6 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
cmd::get_current_profile,
|
cmd::get_current_profile,
|
||||||
cmd::save_current_profile,
|
cmd::save_current_profile,
|
||||||
|
|
||||||
cmd::startline,
|
|
||||||
cmd::kill,
|
|
||||||
|
|
||||||
cmd::list_displays,
|
cmd::list_displays,
|
||||||
cmd::list_platform_capabilities,
|
cmd::list_platform_capabilities,
|
||||||
cmd::list_directories,
|
cmd::list_directories,
|
||||||
@ -189,8 +190,8 @@ fn deep_link(app: AppHandle, args: Vec<String>) {
|
|||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let mutex = app.state::<Mutex<AppData>>();
|
let mutex = app.state::<Mutex<AppData>>();
|
||||||
let mut appd = mutex.lock().await;
|
let mut appd = mutex.lock().await;
|
||||||
if let Err(e) = appd.pkgs.fetch_listings().await {
|
if appd.pkgs.is_offline() {
|
||||||
log::warn!("Deep link fetch failed: {:?}", e);
|
log::warn!("Deep link installation failed: offline");
|
||||||
} else if let Err(e) = appd.pkgs.install_package(&key, true, true).await {
|
} else if let Err(e) = appd.pkgs.install_package(&key, true, true).await {
|
||||||
log::warn!("Deep link installation failed: {}", e.to_string());
|
log::warn!("Deep link installation failed: {}", e.to_string());
|
||||||
}
|
}
|
||||||
|
@ -1,102 +1,10 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::pkg::PkgKey;
|
use super::misc::Game;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct Segatools {
|
pub struct GlobalConfig {
|
||||||
pub target: PathBuf,
|
pub recent_profile: Option<(Game, String)>,
|
||||||
pub hook: Option<PkgKey>,
|
|
||||||
pub io: Option<PkgKey>,
|
|
||||||
pub amfs: PathBuf,
|
|
||||||
pub option: PathBuf,
|
|
||||||
pub appdata: PathBuf,
|
|
||||||
pub enable_aime: bool,
|
|
||||||
pub intel: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Segatools {
|
#[serde(default)]
|
||||||
fn default() -> Self {
|
pub offline_mode: bool,
|
||||||
Segatools {
|
|
||||||
target: PathBuf::default(),
|
|
||||||
hook: Some(PkgKey("segatools-mu3hook".to_owned())),
|
|
||||||
io: None,
|
|
||||||
amfs: PathBuf::default(),
|
|
||||||
option: PathBuf::default(),
|
|
||||||
appdata: PathBuf::from("appdata"),
|
|
||||||
enable_aime: false,
|
|
||||||
intel: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Default, PartialEq)]
|
|
||||||
pub enum DisplayMode {
|
|
||||||
Window,
|
|
||||||
#[default] Borderless,
|
|
||||||
Fullscreen
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
|
||||||
pub struct Display {
|
|
||||||
pub target: String,
|
|
||||||
pub rez: (i32, i32),
|
|
||||||
pub mode: DisplayMode,
|
|
||||||
pub rotation: i32,
|
|
||||||
pub frequency: i32,
|
|
||||||
pub borderless_fullscreen: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Display {
|
|
||||||
fn default() -> Self {
|
|
||||||
Display {
|
|
||||||
target: "default".to_owned(),
|
|
||||||
rez: (1080, 1920),
|
|
||||||
mode: DisplayMode::Borderless,
|
|
||||||
rotation: 0,
|
|
||||||
frequency: 60,
|
|
||||||
borderless_fullscreen: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Default, PartialEq)]
|
|
||||||
pub enum NetworkType {
|
|
||||||
#[default] Remote,
|
|
||||||
Artemis,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Default)]
|
|
||||||
pub struct Network {
|
|
||||||
pub network_type: NetworkType,
|
|
||||||
|
|
||||||
pub local_path: PathBuf,
|
|
||||||
pub local_console: bool,
|
|
||||||
|
|
||||||
pub remote_address: String,
|
|
||||||
pub keychip: String,
|
|
||||||
|
|
||||||
pub subnet: String,
|
|
||||||
pub suffix: Option<i32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Default)]
|
|
||||||
pub struct BepInEx {
|
|
||||||
pub console: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
|
||||||
pub struct Wine {
|
|
||||||
pub runtime: PathBuf,
|
|
||||||
pub prefix: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Wine {
|
|
||||||
fn default() -> Self {
|
|
||||||
Wine {
|
|
||||||
runtime: PathBuf::from("/usr/bin/wine"),
|
|
||||||
prefix: std::env::var("HOME")
|
|
||||||
.and_then(|home| Ok(PathBuf::from(home).join(".wine")))
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use crate::pkg::PkgKey;
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
||||||
pub enum Game {
|
pub enum Game {
|
||||||
@ -26,3 +27,11 @@ impl std::fmt::Display for Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum StartCheckError {
|
||||||
|
MissingRemotePackage(PkgKey),
|
||||||
|
MissingLocalPackage(PkgKey),
|
||||||
|
MissingDependency(PkgKey, PkgKey),
|
||||||
|
MissingTool(PkgKey),
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
pub mod local;
|
pub mod local;
|
||||||
pub mod misc;
|
pub mod misc;
|
||||||
pub mod rainy;
|
pub mod rainy;
|
||||||
|
pub mod profile;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod segatools_base;
|
pub mod segatools_base;
|
102
rust/src/model/profile.rs
Normal file
102
rust/src/model/profile.rs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use crate::pkg::PkgKey;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
|
pub struct Segatools {
|
||||||
|
pub target: PathBuf,
|
||||||
|
pub hook: Option<PkgKey>,
|
||||||
|
pub io: Option<PkgKey>,
|
||||||
|
pub amfs: PathBuf,
|
||||||
|
pub option: PathBuf,
|
||||||
|
pub appdata: PathBuf,
|
||||||
|
pub enable_aime: bool,
|
||||||
|
pub intel: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Segatools {
|
||||||
|
fn default() -> Self {
|
||||||
|
Segatools {
|
||||||
|
target: PathBuf::default(),
|
||||||
|
hook: Some(PkgKey("segatools-mu3hook".to_owned())),
|
||||||
|
io: None,
|
||||||
|
amfs: PathBuf::default(),
|
||||||
|
option: PathBuf::default(),
|
||||||
|
appdata: PathBuf::from("appdata"),
|
||||||
|
enable_aime: false,
|
||||||
|
intel: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Default, PartialEq)]
|
||||||
|
pub enum DisplayMode {
|
||||||
|
Window,
|
||||||
|
#[default] Borderless,
|
||||||
|
Fullscreen
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
|
pub struct Display {
|
||||||
|
pub target: String,
|
||||||
|
pub rez: (i32, i32),
|
||||||
|
pub mode: DisplayMode,
|
||||||
|
pub rotation: i32,
|
||||||
|
pub frequency: i32,
|
||||||
|
pub borderless_fullscreen: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Display {
|
||||||
|
fn default() -> Self {
|
||||||
|
Display {
|
||||||
|
target: "default".to_owned(),
|
||||||
|
rez: (1080, 1920),
|
||||||
|
mode: DisplayMode::Borderless,
|
||||||
|
rotation: 0,
|
||||||
|
frequency: 60,
|
||||||
|
borderless_fullscreen: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Default, PartialEq)]
|
||||||
|
pub enum NetworkType {
|
||||||
|
#[default] Remote,
|
||||||
|
Artemis,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Default)]
|
||||||
|
pub struct Network {
|
||||||
|
pub network_type: NetworkType,
|
||||||
|
|
||||||
|
pub local_path: PathBuf,
|
||||||
|
pub local_console: bool,
|
||||||
|
|
||||||
|
pub remote_address: String,
|
||||||
|
pub keychip: String,
|
||||||
|
|
||||||
|
pub subnet: String,
|
||||||
|
pub suffix: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Default)]
|
||||||
|
pub struct BepInEx {
|
||||||
|
pub console: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
|
pub struct Wine {
|
||||||
|
pub runtime: PathBuf,
|
||||||
|
pub prefix: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Wine {
|
||||||
|
fn default() -> Self {
|
||||||
|
Wine {
|
||||||
|
runtime: PathBuf::from("/usr/bin/wine"),
|
||||||
|
prefix: std::env::var("HOME")
|
||||||
|
.and_then(|home| Ok(PathBuf::from(home).join(".wine")))
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use ini::Ini;
|
use ini::Ini;
|
||||||
use crate::{model::config::BepInEx, profiles::ProfilePaths};
|
use crate::{model::profile::BepInEx, profiles::ProfilePaths};
|
||||||
|
|
||||||
impl BepInEx {
|
impl BepInEx {
|
||||||
pub fn line_up(&self, p: &impl ProfilePaths) -> Result<()> {
|
pub fn line_up(&self, p: &impl ProfilePaths) -> Result<()> {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
use crate::model::config::{Display, DisplayMode};
|
use crate::model::profile::{Display, DisplayMode};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use displayz::{query_displays, DisplaySet};
|
use displayz::{query_displays, DisplaySet};
|
||||||
use tauri::{AppHandle, Listener};
|
use tauri::{AppHandle, Listener};
|
||||||
|
@ -2,7 +2,7 @@ use std::{path::PathBuf, process::Command};
|
|||||||
use yaml_rust2::YamlLoader;
|
use yaml_rust2::YamlLoader;
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use ini::Ini;
|
use ini::Ini;
|
||||||
use crate::model::config::{Network, NetworkType};
|
use crate::model::profile::{Network, NetworkType};
|
||||||
|
|
||||||
impl Network {
|
impl Network {
|
||||||
pub fn line_up(&self, ini: &mut Ini) -> Result<()> {
|
pub fn line_up(&self, ini: &mut Ini) -> Result<()> {
|
||||||
@ -48,9 +48,10 @@ impl Network {
|
|||||||
cmd = Command::new("cmd.exe");
|
cmd = Command::new("cmd.exe");
|
||||||
cmd.arg("/C");
|
cmd.arg("/C");
|
||||||
|
|
||||||
if self.local_console == true {
|
// if self.local_console == true {
|
||||||
cmd.arg("start");
|
cmd.arg("start");
|
||||||
}
|
cmd.arg("/min");
|
||||||
|
// }
|
||||||
} else {
|
} else {
|
||||||
cmd = Command::new("sh");
|
cmd = Command::new("sh");
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use ini::Ini;
|
use ini::Ini;
|
||||||
use crate::{model::{config::Segatools, segatools_base::segatools_base}, profiles::ProfilePaths, util::{self, PathStr}};
|
use crate::{model::{profile::Segatools, segatools_base::segatools_base}, profiles::ProfilePaths, util::{self, PathStr}};
|
||||||
|
|
||||||
impl Segatools {
|
impl Segatools {
|
||||||
pub async fn line_up(&self, p: &impl ProfilePaths) -> Result<Ini> {
|
pub async fn line_up(&self, p: &impl ProfilePaths) -> Result<Ini> {
|
||||||
|
@ -12,9 +12,9 @@ use crate::download_handler::DownloadHandler;
|
|||||||
|
|
||||||
pub struct PackageStore {
|
pub struct PackageStore {
|
||||||
store: HashMap<PkgKey, Package>,
|
store: HashMap<PkgKey, Package>,
|
||||||
has_fetched: bool,
|
|
||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
dlh: DownloadHandler
|
dlh: DownloadHandler,
|
||||||
|
offline: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
@ -31,9 +31,9 @@ impl PackageStore {
|
|||||||
pub fn new(app: AppHandle) -> PackageStore {
|
pub fn new(app: AppHandle) -> PackageStore {
|
||||||
PackageStore {
|
PackageStore {
|
||||||
store: HashMap::new(),
|
store: HashMap::new(),
|
||||||
has_fetched: false,
|
|
||||||
app: app.clone(),
|
app: app.clone(),
|
||||||
dlh: DownloadHandler::new(app)
|
dlh: DownloadHandler::new(app),
|
||||||
|
offline: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,11 +75,7 @@ impl PackageStore {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fetch_listings(&mut self) -> Result<()> {
|
pub async fn fetch_listings() -> Result<Vec<rainy::V1Package>> {
|
||||||
if self.has_fetched {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
use async_compression::futures::bufread::GzipDecoder;
|
use async_compression::futures::bufread::GzipDecoder;
|
||||||
use futures::{
|
use futures::{
|
||||||
io::{self, BufReader, ErrorKind},
|
io::{self, BufReader, ErrorKind},
|
||||||
@ -87,6 +83,7 @@ impl PackageStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let response = reqwest::get("https://rainy.patafour.zip/c/ongeki/api/v1/package/").await?;
|
let response = reqwest::get("https://rainy.patafour.zip/c/ongeki/api/v1/package/").await?;
|
||||||
|
|
||||||
let reader = response
|
let reader = response
|
||||||
.bytes_stream()
|
.bytes_stream()
|
||||||
.map_err(|e| io::Error::new(ErrorKind::Other, e))
|
.map_err(|e| io::Error::new(ErrorKind::Other, e))
|
||||||
@ -96,9 +93,14 @@ impl PackageStore {
|
|||||||
let mut data = String::new();
|
let mut data = String::new();
|
||||||
decoder.read_to_string(&mut data).await?;
|
decoder.read_to_string(&mut data).await?;
|
||||||
|
|
||||||
let listings: Vec<rainy::V1Package> = serde_json::from_str(&data)
|
Ok(serde_json::from_str(&data)?)
|
||||||
.expect("Invalid JSON");
|
}
|
||||||
|
|
||||||
|
pub fn is_offline(&self) -> bool {
|
||||||
|
self.offline
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_fetched_listings(&mut self, listings: Vec<rainy::V1Package>) {
|
||||||
for listing in listings {
|
for listing in listings {
|
||||||
// This is None if the package has no versions for whatever reason
|
// This is None if the package has no versions for whatever reason
|
||||||
if let Some(r) = Package::from_rainy(listing) {
|
if let Some(r) = Package::from_rainy(listing) {
|
||||||
@ -114,9 +116,7 @@ impl PackageStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.has_fetched = true;
|
self.offline = false;
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn install_package(&mut self, key: &PkgKey, force: bool, install_deps: bool) -> Result<InstallResult> {
|
pub async fn install_package(&mut self, key: &PkgKey, force: bool, install_deps: bool) -> Result<InstallResult> {
|
||||||
|
@ -59,17 +59,30 @@ impl AnyProfile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn mod_pkgs(&self) -> &BTreeSet<PkgKey> {
|
||||||
pub fn pkgs(&self) -> &BTreeSet<PkgKey> {
|
|
||||||
match self {
|
match self {
|
||||||
Self::OngekiProfile(p) => &p.mods
|
Self::OngekiProfile(p) => &p.mods
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn pkgs_mut(&mut self) -> &mut BTreeSet<PkgKey> {
|
pub fn mod_pkgs_mut(&mut self) -> &mut BTreeSet<PkgKey> {
|
||||||
match self {
|
match self {
|
||||||
Self::OngekiProfile(p) => &mut p.mods
|
Self::OngekiProfile(p) => &mut p.mods
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn special_pkgs(&self) -> Vec<PkgKey> {
|
||||||
|
let mut res = Vec::new();
|
||||||
|
match self {
|
||||||
|
Self::OngekiProfile(p) => {
|
||||||
|
if let Some(hook) = &p.sgt.hook {
|
||||||
|
res.push(hook.clone());
|
||||||
|
}
|
||||||
|
if let Some(io) = &p.sgt.io {
|
||||||
|
res.push(io.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
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) => {
|
||||||
@ -80,7 +93,7 @@ impl AnyProfile {
|
|||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
if let Some(info) = info {
|
if let Some(info) = info {
|
||||||
use crate::model::config::Display;
|
use crate::model::profile::Display;
|
||||||
if res.is_ok() {
|
if res.is_ok() {
|
||||||
Display::wait_for_exit(_app, info);
|
Display::wait_for_exit(_app, info);
|
||||||
} else {
|
} else {
|
||||||
@ -141,6 +154,14 @@ impl AnyProfile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for AnyProfile {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::OngekiProfile(p) => f.debug_tuple("ongeki").field(&p.name).finish(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn list_profiles() -> Result<Vec<ProfileMeta>> {
|
pub async fn list_profiles() -> Result<Vec<ProfileMeta>> {
|
||||||
let path = std::fs::read_dir(util::config_dir())?;
|
let path = std::fs::read_dir(util::config_dir())?;
|
||||||
|
|
||||||
|
@ -2,9 +2,9 @@ use serde::{Deserialize, Serialize};
|
|||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
use tauri::Emitter;
|
use tauri::Emitter;
|
||||||
use std::{collections::BTreeSet, path::PathBuf, process::Stdio};
|
use std::{collections::BTreeSet, path::PathBuf, process::Stdio};
|
||||||
use crate::model::config::BepInEx;
|
use crate::model::profile::BepInEx;
|
||||||
use crate::profiles::fixed_name;
|
use crate::profiles::fixed_name;
|
||||||
use crate::{model::{config::{Display, DisplayMode, Network, Segatools}, misc::Game, segatools_base::segatools_base}, pkg::PkgKey, util};
|
use crate::{model::{profile::{Display, DisplayMode, Network, Segatools}, misc::Game, segatools_base::segatools_base}, pkg::PkgKey, util};
|
||||||
use super::{Profile, ProfileMeta, ProfilePaths};
|
use super::{Profile, ProfileMeta, ProfilePaths};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
@ -25,7 +25,7 @@ pub struct OngekiProfile {
|
|||||||
pub bepinex: BepInEx,
|
pub bepinex: BepInEx,
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
pub wine: crate::model::config::Wine,
|
pub wine: crate::model::profile::Wine,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Profile for OngekiProfile {
|
impl Profile for OngekiProfile {
|
||||||
@ -40,7 +40,7 @@ impl Profile for OngekiProfile {
|
|||||||
network: Network::default(),
|
network: Network::default(),
|
||||||
bepinex: BepInEx::default(),
|
bepinex: BepInEx::default(),
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
wine: crate::model::config::Wine::default(),
|
wine: crate::model::profile::Wine::default(),
|
||||||
};
|
};
|
||||||
p.save()?;
|
p.save()?;
|
||||||
std::fs::write(p.config_dir().join("segatools-base.ini"), segatools_base())?;
|
std::fs::write(p.config_dir().join("segatools-base.ini"), segatools_base())?;
|
||||||
|
@ -38,8 +38,8 @@ onMounted(async () => {
|
|||||||
await Promise.all([prf.reloadList(), prf.reload()]);
|
await Promise.all([prf.reloadList(), prf.reload()]);
|
||||||
|
|
||||||
if (prf.current !== null) {
|
if (prf.current !== null) {
|
||||||
await pkg.reloadAll();
|
|
||||||
currentTab.value = 0;
|
currentTab.value = 0;
|
||||||
|
await pkg.reloadAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch_promise.then(async () => {
|
fetch_promise.then(async () => {
|
||||||
@ -65,7 +65,7 @@ onMounted(async () => {
|
|||||||
><div class="pi pi-list-check"></div
|
><div class="pi pi-list-check"></div
|
||||||
></Tab>
|
></Tab>
|
||||||
<Tab
|
<Tab
|
||||||
v-if="!pkg.offline"
|
v-if="pkg.networkStatus === 'online'"
|
||||||
:disabled="isProfileDisabled"
|
:disabled="isProfileDisabled"
|
||||||
:value="1"
|
:value="1"
|
||||||
><div class="pi pi-download"></div
|
><div class="pi pi-download"></div
|
||||||
@ -98,7 +98,14 @@ onMounted(async () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
v-if="pkg.offline"
|
v-if="pkg.networkStatus === 'connecting'"
|
||||||
|
class="shrink self-center"
|
||||||
|
icon="pi pi-sync pi-spin"
|
||||||
|
size="small"
|
||||||
|
:disabled="true"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
v-if="pkg.networkStatus === 'offline'"
|
||||||
class="shrink self-center"
|
class="shrink self-center"
|
||||||
icon="pi pi-sync"
|
icon="pi pi-sync"
|
||||||
size="small"
|
size="small"
|
||||||
@ -151,6 +158,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<style lang="css">
|
<style lang="css">
|
||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
|
@import 'primeicons/primeicons.css';
|
||||||
|
|
||||||
.p-tablist-tab-list {
|
.p-tablist-tab-list {
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
|
23
src/components/LinkButton.vue
Normal file
23
src/components/LinkButton.vue
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Button from 'primevue/button';
|
||||||
|
import { open } from '@tauri-apps/plugin-shell';
|
||||||
|
import { Package } from '../types';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
pkg: Object as () => Package,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button
|
||||||
|
rounded
|
||||||
|
icon="pi pi-external-link"
|
||||||
|
severity="info"
|
||||||
|
aria-label="storepage"
|
||||||
|
size="small"
|
||||||
|
class="self-center ml-2"
|
||||||
|
style="width: 2rem; height: 2rem"
|
||||||
|
:disabled="!pkg?.rmt"
|
||||||
|
v-on:click="open(pkg!.rmt!.package_url)"
|
||||||
|
/>
|
||||||
|
</template>
|
@ -1,21 +1,25 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
import Button from 'primevue/button';
|
||||||
import Fieldset from 'primevue/fieldset';
|
import Fieldset from 'primevue/fieldset';
|
||||||
import ModListEntry from './ModListEntry.vue';
|
import ModListEntry from './ModListEntry.vue';
|
||||||
import { usePkgStore } from '../stores';
|
import ModTitlecard from './ModTitlecard.vue';
|
||||||
|
import { usePkgStore, usePrfStore } from '../stores';
|
||||||
|
import { Package } from '../types';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
search: String,
|
search: String,
|
||||||
});
|
});
|
||||||
|
|
||||||
const pkg = usePkgStore();
|
const pkgs = usePkgStore();
|
||||||
|
const prf = usePrfStore();
|
||||||
const empty = ref(true);
|
const empty = ref(true);
|
||||||
|
|
||||||
const group = () => {
|
const group = () => {
|
||||||
const a = Object.assign(
|
const a = Object.assign(
|
||||||
{},
|
{},
|
||||||
Object.groupBy(
|
Object.groupBy(
|
||||||
pkg.allLocal
|
pkgs.allLocal
|
||||||
.filter(
|
.filter(
|
||||||
(p) =>
|
(p) =>
|
||||||
props.search === undefined ||
|
props.search === undefined ||
|
||||||
@ -34,9 +38,36 @@ const group = () => {
|
|||||||
empty.value = Object.keys(a).length === 0;
|
empty.value = Object.keys(a).length === 0;
|
||||||
return a;
|
return a;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const missing = computed(() => {
|
||||||
|
return prf.current?.mods.filter((m) => !pkgs.hasLocal(m));
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<Fieldset legend="Missing" v-if="(missing?.length ?? 0) > 0">
|
||||||
|
<div class="flex items-center" v-for="p in missing">
|
||||||
|
<ModTitlecard
|
||||||
|
show-namespace
|
||||||
|
:pkg="
|
||||||
|
{
|
||||||
|
namespace: p.split('-')[0],
|
||||||
|
name: p.split('-')[1],
|
||||||
|
} as Package
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
rounded
|
||||||
|
icon="pi pi-minus"
|
||||||
|
severity="danger"
|
||||||
|
aria-label="install"
|
||||||
|
size="small"
|
||||||
|
class="self-center ml-4"
|
||||||
|
style="width: 2rem; height: 2rem"
|
||||||
|
v-on:click="prf.togglePkg(p, false)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Fieldset>
|
||||||
<Fieldset v-for="(namespace, key) in group()" :legend="key.toString()">
|
<Fieldset v-for="(namespace, key) in group()" :legend="key.toString()">
|
||||||
<ModListEntry v-for="p in namespace" :pkg="p" />
|
<ModListEntry v-for="p in namespace" :pkg="p" />
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
|
@ -4,12 +4,14 @@ import Button from 'primevue/button';
|
|||||||
import ToggleSwitch from 'primevue/toggleswitch';
|
import ToggleSwitch from 'primevue/toggleswitch';
|
||||||
import { open } from '@tauri-apps/plugin-shell';
|
import { open } from '@tauri-apps/plugin-shell';
|
||||||
import InstallButton from './InstallButton.vue';
|
import InstallButton from './InstallButton.vue';
|
||||||
|
import LinkButton from './LinkButton.vue';
|
||||||
import ModTitlecard from './ModTitlecard.vue';
|
import ModTitlecard from './ModTitlecard.vue';
|
||||||
import UpdateButton from './UpdateButton.vue';
|
import UpdateButton from './UpdateButton.vue';
|
||||||
import { usePrfStore } from '../stores';
|
import { usePkgStore, usePrfStore } from '../stores';
|
||||||
import { Package } from '../types';
|
import { Package } from '../types';
|
||||||
|
|
||||||
const prf = usePrfStore();
|
const prf = usePrfStore();
|
||||||
|
const pkgs = usePkgStore();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
pkg: Object as () => Package,
|
pkg: Object as () => Package,
|
||||||
@ -27,13 +29,14 @@ const model = computed({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<ModTitlecard showVersion :pkg="pkg" />
|
<ModTitlecard show-version show-icon show-description :pkg="pkg" />
|
||||||
<UpdateButton :pkg="pkg" />
|
<UpdateButton :pkg="pkg" />
|
||||||
<!-- @vue-expect-error Can't 'as any' because it breaks VSCode -->
|
<!-- @vue-expect-error Can't 'as any' because it breaks VSCode -->
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
|
v-if="pkg?.loc?.kind === 'Mod' || pkg?.loc?.kind === 'Unsupported'"
|
||||||
class="scale-[1.33] shrink-0"
|
class="scale-[1.33] shrink-0"
|
||||||
inputId="switch"
|
inputId="switch"
|
||||||
:disabled="!pkg?.loc"
|
:disabled="pkg!.loc!.kind === 'Unsupported'"
|
||||||
v-model="model"
|
v-model="model"
|
||||||
/>
|
/>
|
||||||
<InstallButton :pkg="pkg" />
|
<InstallButton :pkg="pkg" />
|
||||||
@ -47,17 +50,7 @@ const model = computed({
|
|||||||
style="width: 2rem; height: 2rem"
|
style="width: 2rem; height: 2rem"
|
||||||
v-on:click="pkg?.loc && open(pkg.loc.path ?? '')"
|
v-on:click="pkg?.loc && open(pkg.loc.path ?? '')"
|
||||||
/>
|
/>
|
||||||
<Button
|
<LinkButton v-if="pkgs.networkStatus === 'online'" :pkg="pkg" />
|
||||||
v-if="pkg?.rmt"
|
|
||||||
rounded
|
|
||||||
icon="pi pi-external-link"
|
|
||||||
severity="info"
|
|
||||||
aria-label="delete"
|
|
||||||
size="small"
|
|
||||||
class="ml-2 shrink-0"
|
|
||||||
style="width: 2rem; height: 2rem"
|
|
||||||
v-on:click="open(pkg.rmt.package_url ?? '')"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -33,14 +33,18 @@ const list = () => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex gap-4 items-center">
|
<div class="flex gap-4 items-center">
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<div class="grow">Show installed</div>
|
||||||
|
<ToggleSwitch v-model="pkgs.showInstalled" />
|
||||||
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<div class="text-amber-400 grow">Show deprecated</div>
|
<div class="text-amber-400 grow">Show deprecated</div>
|
||||||
<ToggleSwitch v-model="pkgs.showDeprecated" />
|
<ToggleSwitch v-model="pkgs.showDeprecated" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<!-- <div class="flex gap-2">
|
||||||
<div class="text-red-400 grow">Show NSFW</div>
|
<div class="text-red-400 grow">Show NSFW</div>
|
||||||
<ToggleSwitch v-model="pkgs.showNSFW" />
|
<ToggleSwitch v-model="pkgs.showNSFW" />
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2 grow">
|
<div class="flex flex-col gap-2 grow">
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
@ -67,7 +71,3 @@ const list = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="empty" class="text-3xl">∅</div>
|
<div v-if="empty" class="text-3xl">∅</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@import 'primeicons/primeicons.css';
|
|
||||||
</style>
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Button from 'primevue/button';
|
|
||||||
import { open } from '@tauri-apps/plugin-shell';
|
|
||||||
import InstallButton from './InstallButton.vue';
|
import InstallButton from './InstallButton.vue';
|
||||||
|
import LinkButton from './LinkButton.vue';
|
||||||
import ModTitlecard from './ModTitlecard.vue';
|
import ModTitlecard from './ModTitlecard.vue';
|
||||||
import { Package } from '../types';
|
import { Package } from '../types';
|
||||||
|
|
||||||
@ -11,21 +10,13 @@ defineProps({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ModTitlecard :pkg="pkg" show-namespace show-categories />
|
<ModTitlecard
|
||||||
<InstallButton :pkg="pkg" />
|
:pkg="pkg"
|
||||||
<Button
|
show-namespace
|
||||||
rounded
|
show-categories
|
||||||
icon="pi pi-external-link"
|
show-icon
|
||||||
severity="info"
|
show-description
|
||||||
aria-label="storepage"
|
|
||||||
size="small"
|
|
||||||
class="self-center ml-2"
|
|
||||||
style="width: 2rem; height: 2rem"
|
|
||||||
:disabled="!pkg?.rmt"
|
|
||||||
v-on:click="open(pkg?.rmt?.package_url ?? '')"
|
|
||||||
/>
|
/>
|
||||||
|
<InstallButton :pkg="pkg" />
|
||||||
|
<LinkButton :pkg="pkg" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@import 'primeicons/primeicons.css';
|
|
||||||
</style>
|
|
||||||
|
@ -9,6 +9,8 @@ const props = defineProps({
|
|||||||
showNamespace: Boolean,
|
showNamespace: Boolean,
|
||||||
showVersion: Boolean,
|
showVersion: Boolean,
|
||||||
showCategories: Boolean,
|
showCategories: Boolean,
|
||||||
|
showDescription: Boolean,
|
||||||
|
showIcon: Boolean,
|
||||||
});
|
});
|
||||||
|
|
||||||
const iconSrc = () => {
|
const iconSrc = () => {
|
||||||
@ -26,12 +28,13 @@ const iconSrc = () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<img
|
<img
|
||||||
|
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"
|
||||||
/>
|
/>
|
||||||
<label class="m-3 align-middle text grow z-5 h-50px" for="switch">
|
<label class="m-3 align-middle text grow z-5 h-50px">
|
||||||
<div>
|
<div>
|
||||||
<span class="text-lg">
|
<span class="text-lg">
|
||||||
{{ pkg?.name ?? 'Untitled' }}
|
{{ pkg?.name ?? 'Untitled' }}
|
||||||
@ -48,6 +51,18 @@ const iconSrc = () => {
|
|||||||
class="pi pi-exclamation-triangle ml-1 text-red-400"
|
class="pi pi-exclamation-triangle ml-1 text-red-400"
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="pkg?.loc?.kind === 'Hook'"
|
||||||
|
v-tooltip="'Hook'"
|
||||||
|
class="pi pi-wrench ml-1 text-blue-400"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="pkg?.loc?.kind === 'IO'"
|
||||||
|
v-tooltip="'IO'"
|
||||||
|
class="pi pi-wrench ml-1 text-green-400"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="showNamespace && pkg?.namespace"
|
v-if="showNamespace && pkg?.namespace"
|
||||||
class="text-sm opacity-75"
|
class="text-sm opacity-75"
|
||||||
@ -69,8 +84,8 @@ const iconSrc = () => {
|
|||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm opacity-75">
|
<div v-if="showDescription" class="text-sm opacity-75">
|
||||||
{{ pkg?.description ?? 'No description' }}
|
{{ pkg?.description || 'No description' }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showCategories" class="mt-1 flex gap-1">
|
<div v-if="showCategories" class="mt-1 flex gap-1">
|
||||||
<span class="text-xs" v-for="c in pkg?.rmt?.categories"
|
<span class="text-xs" v-for="c in pkg?.rmt?.categories"
|
||||||
|
@ -250,12 +250,12 @@ const extraDisplayOptionsDisabled = computed(() => {
|
|||||||
"
|
"
|
||||||
></FilePicker>
|
></FilePicker>
|
||||||
</OptionRow>
|
</OptionRow>
|
||||||
<OptionRow
|
<!-- <OptionRow
|
||||||
v-if="prf.current!.network.network_type == 'Artemis'"
|
v-if="prf.current!.network.network_type == 'Artemis'"
|
||||||
title="ARTEMiS console"
|
title="ARTEMiS console"
|
||||||
>
|
>
|
||||||
<ToggleSwitch v-model="prf.current!.network.local_console" />
|
<ToggleSwitch v-model="prf.current!.network.local_console" />
|
||||||
</OptionRow>
|
</OptionRow> -->
|
||||||
<OptionRow
|
<OptionRow
|
||||||
v-if="prf.current!.network.network_type == 'Remote'"
|
v-if="prf.current!.network.network_type == 'Remote'"
|
||||||
title="Server address"
|
title="Server address"
|
||||||
|
@ -1,20 +1,52 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Ref, computed, ref } from 'vue';
|
import { Ref, computed, ref } from 'vue';
|
||||||
import Button from 'primevue/button';
|
import Button from 'primevue/button';
|
||||||
|
import ConfirmDialog from 'primevue/confirmdialog';
|
||||||
|
import ScrollPanel from 'primevue/scrollpanel';
|
||||||
|
import { useConfirm } from 'primevue/useconfirm';
|
||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
import { invoke } from '../invoke';
|
import { invoke } from '../invoke';
|
||||||
import { usePrfStore } from '../stores';
|
import { usePrfStore } from '../stores';
|
||||||
|
|
||||||
const prf = usePrfStore();
|
const prf = usePrfStore();
|
||||||
|
const confirmDialog = useConfirm();
|
||||||
|
|
||||||
type StartStatus = 'ready' | 'preparing' | 'running';
|
type StartStatus = 'ready' | 'preparing' | 'running';
|
||||||
const startStatus: Ref<StartStatus> = ref('ready');
|
const startStatus: Ref<StartStatus> = ref('ready');
|
||||||
|
|
||||||
const startline = async () => {
|
const startline = async (force: boolean) => {
|
||||||
startStatus.value = 'preparing';
|
startStatus.value = 'preparing';
|
||||||
|
|
||||||
|
if (!force) {
|
||||||
|
const start_check: object[] = await invoke('start_check');
|
||||||
|
if (start_check.length > 0) {
|
||||||
|
const message = start_check.map((o) => {
|
||||||
|
if ('MissingRemotePackage' in o) {
|
||||||
|
return `Package missing: ${o.MissingRemotePackage}`;
|
||||||
|
} else if ('MissingLocalPackage' in o) {
|
||||||
|
return `Package missing: ${o.MissingLocalPackage}`;
|
||||||
|
} else if ('MissingDependency' in o) {
|
||||||
|
return `Dependency missing: ${o.MissingDependency}`;
|
||||||
|
} else if ('MissingTool' in o) {
|
||||||
|
return `Tool missing: ${o.MissingTool}`;
|
||||||
|
} else {
|
||||||
|
return 'Unknown error';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
confirmDialog.require({
|
||||||
|
message: message.join('\n'),
|
||||||
|
header: 'Start check failed',
|
||||||
|
accept: () => {
|
||||||
|
startline(true);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
startStatus.value = 'ready';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await invoke('startline');
|
await invoke('startline');
|
||||||
} catch (e) {
|
} catch (_) {
|
||||||
startStatus.value = 'ready';
|
startStatus.value = 'ready';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -44,9 +76,52 @@ listen('launch-start', () => {
|
|||||||
listen('launch-end', () => {
|
listen('launch-end', () => {
|
||||||
startStatus.value = 'ready';
|
startStatus.value = 'ready';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const messageSplit = (message: any) => {
|
||||||
|
return message.message?.split('\n');
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<ConfirmDialog>
|
||||||
|
<template #container="{ message, acceptCallback, rejectCallback }">
|
||||||
|
<div
|
||||||
|
class="flex flex-col p-8 bg-surface-0 dark:bg-surface-900 rounded"
|
||||||
|
>
|
||||||
|
<span class="font-bold self-center text-2xl block mb-2 mt-2">{{
|
||||||
|
message.header
|
||||||
|
}}</span>
|
||||||
|
<ScrollPanel
|
||||||
|
v-if="messageSplit(message).length > 5"
|
||||||
|
style="width: 100%; height: 40vh"
|
||||||
|
>
|
||||||
|
<p v-for="m in messageSplit(message)">
|
||||||
|
{{ m }}
|
||||||
|
</p></ScrollPanel
|
||||||
|
>
|
||||||
|
<div v-else>
|
||||||
|
<p v-for="m in messageSplit(message)">
|
||||||
|
{{ m }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex self-center items-center gap-2 mt-6">
|
||||||
|
<Button
|
||||||
|
label="Run anyway"
|
||||||
|
@click="acceptCallback"
|
||||||
|
size="small"
|
||||||
|
class="w-32"
|
||||||
|
></Button>
|
||||||
|
<Button
|
||||||
|
label="Cancel"
|
||||||
|
outlined
|
||||||
|
size="small"
|
||||||
|
@click="rejectCallback"
|
||||||
|
class="w-32"
|
||||||
|
></Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ConfirmDialog>
|
||||||
<Button
|
<Button
|
||||||
v-if="startStatus === 'ready'"
|
v-if="startStatus === 'ready'"
|
||||||
v-tooltip="disabledTooltip"
|
v-tooltip="disabledTooltip"
|
||||||
@ -56,7 +131,7 @@ listen('launch-end', () => {
|
|||||||
aria-label="start"
|
aria-label="start"
|
||||||
size="small"
|
size="small"
|
||||||
class="m-2.5"
|
class="m-2.5"
|
||||||
@click="startline()"
|
@click="startline(false)"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
v-else-if="startStatus === 'preparing'"
|
v-else-if="startStatus === 'preparing'"
|
||||||
|
@ -3,6 +3,7 @@ import { createPinia } from 'pinia';
|
|||||||
import { definePreset } from '@primevue/themes';
|
import { definePreset } from '@primevue/themes';
|
||||||
import Theme from '@primevue/themes/aura';
|
import Theme from '@primevue/themes/aura';
|
||||||
import PrimeVue from 'primevue/config';
|
import PrimeVue from 'primevue/config';
|
||||||
|
import ConfirmationService from 'primevue/confirmationservice';
|
||||||
import Tooltip from 'primevue/tooltip';
|
import Tooltip from 'primevue/tooltip';
|
||||||
import App from './components/App.vue';
|
import App from './components/App.vue';
|
||||||
import { changePrimaryColor } from './util';
|
import { changePrimaryColor } from './util';
|
||||||
@ -18,6 +19,7 @@ app.use(PrimeVue, {
|
|||||||
preset: Preset,
|
preset: Preset,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
app.use(ConfirmationService);
|
||||||
changePrimaryColor(null);
|
changePrimaryColor(null);
|
||||||
app.directive('tooltip', Tooltip);
|
app.directive('tooltip', Tooltip);
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
@ -57,7 +57,8 @@ export const useGeneralStore = defineStore('general', () => {
|
|||||||
export const usePkgStore = defineStore('pkg', {
|
export const usePkgStore = defineStore('pkg', {
|
||||||
state: (): {
|
state: (): {
|
||||||
pkg: { [key: string]: Package };
|
pkg: { [key: string]: Package };
|
||||||
offline: boolean;
|
networkStatus: 'connecting' | 'offline' | 'online';
|
||||||
|
showInstalled: boolean;
|
||||||
showDeprecated: boolean;
|
showDeprecated: boolean;
|
||||||
showNSFW: boolean;
|
showNSFW: boolean;
|
||||||
availableCategories: Set<string>;
|
availableCategories: Set<string>;
|
||||||
@ -66,7 +67,8 @@ export const usePkgStore = defineStore('pkg', {
|
|||||||
} => {
|
} => {
|
||||||
return {
|
return {
|
||||||
pkg: {},
|
pkg: {},
|
||||||
offline: false,
|
networkStatus: 'connecting',
|
||||||
|
showInstalled: false,
|
||||||
showDeprecated: false,
|
showDeprecated: false,
|
||||||
showNSFW: false,
|
showNSFW: false,
|
||||||
availableCategories: new Set(),
|
availableCategories: new Set(),
|
||||||
@ -79,12 +81,14 @@ export const usePkgStore = defineStore('pkg', {
|
|||||||
fromName: (state) => (namespace: string, name: string) =>
|
fromName: (state) => (namespace: string, name: string) =>
|
||||||
state.pkg[`${namespace}-${name}`] ?? null,
|
state.pkg[`${namespace}-${name}`] ?? null,
|
||||||
all: (state) => Object.values(state),
|
all: (state) => Object.values(state),
|
||||||
allLocal: (state) =>
|
allLocal: (state) => Object.values(state.pkg).filter((p) => p.loc),
|
||||||
Object.values(state.pkg).filter((p) => p.loc?.kind === 'Mod'),
|
hasLocal: (state) => (key: string) =>
|
||||||
|
key in state.pkg && state.pkg[key].loc,
|
||||||
allRemote: (state) =>
|
allRemote: (state) =>
|
||||||
Object.values(state.pkg).filter(
|
Object.values(state.pkg).filter(
|
||||||
(p) =>
|
(p) =>
|
||||||
p.rmt !== null &&
|
p.rmt !== null &&
|
||||||
|
(state.showInstalled || !p.loc) &&
|
||||||
(state.showDeprecated || !p.rmt.deprecated) &&
|
(state.showDeprecated || !p.rmt.deprecated) &&
|
||||||
(state.showNSFW || !p.rmt.nsfw) &&
|
(state.showNSFW || !p.rmt.nsfw) &&
|
||||||
(state.includeCategories.length === 0 ||
|
(state.includeCategories.length === 0 ||
|
||||||
@ -154,15 +158,16 @@ export const usePkgStore = defineStore('pkg', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async fetch(nopopup: boolean) {
|
async fetch(nopopup: boolean) {
|
||||||
|
this.networkStatus = 'connecting';
|
||||||
try {
|
try {
|
||||||
if (nopopup) {
|
if (nopopup) {
|
||||||
await invoke_nopopup('fetch_listings');
|
await invoke_nopopup('fetch_listings');
|
||||||
} else {
|
} else {
|
||||||
await invoke('fetch_listings');
|
await invoke('fetch_listings');
|
||||||
}
|
}
|
||||||
this.offline = false;
|
this.networkStatus = 'online';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.offline = true;
|
this.networkStatus = 'offline';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.reloadAll();
|
await this.reloadAll();
|
||||||
@ -243,11 +248,17 @@ export const usePrfStore = defineStore('prf', () => {
|
|||||||
list.value = (await invoke('list_profiles')) as ProfileMeta[];
|
list.value = (await invoke('list_profiles')) as ProfileMeta[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const togglePkg = async (pkg: Package | undefined, enable: boolean) => {
|
const togglePkg = async (
|
||||||
|
pkg: Package | string | undefined,
|
||||||
|
enable: boolean
|
||||||
|
) => {
|
||||||
if (pkg === undefined) {
|
if (pkg === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await invoke('toggle_package', { key: pkgKey(pkg), enable });
|
await invoke('toggle_package', {
|
||||||
|
key: typeof pkg === 'string' ? pkg : pkgKey(pkg),
|
||||||
|
enable,
|
||||||
|
});
|
||||||
await reload();
|
await reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user