Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
c4d023ed43 | |||
9b86af282e | |||
2e17e0ae75 | |||
edef5cc6dc | |||
2dad0de4f1 | |||
14a65eb5bb | |||
0add9200a6 | |||
ee49da3665 | |||
f478ad9216 |
31
CHANGELOG.md
31
CHANGELOG.md
@ -1,3 +1,34 @@
|
|||||||
|
## 0.19.1
|
||||||
|
|
||||||
|
- Fixed the update button enabling the package
|
||||||
|
- Fixed deep URLs with rainycolor.org
|
||||||
|
|
||||||
|
## 0.19.0
|
||||||
|
|
||||||
|
- Added diagnostic exports
|
||||||
|
|
||||||
|
## 0.18.3
|
||||||
|
|
||||||
|
- Updated Rainycolor's domain・真
|
||||||
|
|
||||||
|
## 0.18.2
|
||||||
|
|
||||||
|
- Updated Rainycolor's domain
|
||||||
|
|
||||||
|
## 0.18.1
|
||||||
|
|
||||||
|
- Keys can now be unbinded with Esc
|
||||||
|
- Fixed CHUNITHM IR behavior on actual keyboards
|
||||||
|
|
||||||
|
## 0.18.0
|
||||||
|
|
||||||
|
- Added new grouping options to the package list
|
||||||
|
|
||||||
|
## 0.17.0
|
||||||
|
|
||||||
|
- Added a package creation prompt
|
||||||
|
- Added a default package icon
|
||||||
|
|
||||||
## 0.16.0
|
## 0.16.0
|
||||||
|
|
||||||
- Fixed the clear cache button not working
|
- Fixed the clear cache button not working
|
||||||
|
@ -9,7 +9,7 @@ This is a program that seeks to streamline game data configuration, currently su
|
|||||||
|
|
||||||
STARTLINER is four things:
|
STARTLINER is four things:
|
||||||
|
|
||||||
- a mod installer and updater, powered by [Rainycolor Watercolor](https://rainy.patafour.zip),
|
- a mod installer and updater, powered by [Rainycolor Watercolor](https://rainycolor.org),
|
||||||
- a configuration GUI for segatools,
|
- a configuration GUI for segatools,
|
||||||
- a glorified `start.bat` clicker, with automatic monitor setup and rollback,
|
- a glorified `start.bat` clicker, with automatic monitor setup and rollback,
|
||||||
- [an abstraction allowing data configuration without touching the game directory](https://gitea.tendokyu.moe/akanyan/STARTLINER/wiki/Architecture-details).
|
- [an abstraction allowing data configuration without touching the game directory](https://gitea.tendokyu.moe/akanyan/STARTLINER/wiki/Architecture-details).
|
||||||
|
BIN
public/no-icon.png
Normal file
BIN
public/no-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
@ -1,11 +1,12 @@
|
|||||||
use ini::Ini;
|
use ini::Ini;
|
||||||
use log;
|
use log;
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use tauri::{AppHandle, Manager, State};
|
use tauri::{AppHandle, Manager, State};
|
||||||
use crate::model::config::GlobalConfigField;
|
use crate::model::config::GlobalConfigField;
|
||||||
|
use crate::model::local::PackageManifest;
|
||||||
use crate::model::misc::Game;
|
use crate::model::misc::Game;
|
||||||
use crate::model::patch::Patch;
|
use crate::model::patch::Patch;
|
||||||
use crate::modules::package::prepare_dlls;
|
use crate::modules::package::prepare_dlls;
|
||||||
@ -124,12 +125,13 @@ pub async fn kill() -> Result<(), String> {
|
|||||||
pub async fn install_package(
|
pub async fn install_package(
|
||||||
state: State<'_, tokio::sync::Mutex<AppData>>,
|
state: State<'_, tokio::sync::Mutex<AppData>>,
|
||||||
key: PkgKey,
|
key: PkgKey,
|
||||||
force: bool
|
force: bool,
|
||||||
|
enable: bool
|
||||||
) -> Result<InstallResult, String> {
|
) -> Result<InstallResult, String> {
|
||||||
log::debug!("invoke: install_package({})", key);
|
log::debug!("invoke: install_package({})", key);
|
||||||
|
|
||||||
let mut appd = state.lock().await;
|
let mut appd = state.lock().await;
|
||||||
appd.pkgs.install_package(&key, force, true)
|
appd.pkgs.install_package(&key, force, true, enable)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
@ -166,6 +168,65 @@ pub async fn toggle_package(state: State<'_, tokio::sync::Mutex<AppData>>, key:
|
|||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn create_package(
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
website: String,
|
||||||
|
r#type: String,
|
||||||
|
games: Vec<Game>
|
||||||
|
) -> Result<(), String> {
|
||||||
|
log::debug!("invoke: create_package");
|
||||||
|
|
||||||
|
let dir = util::pkg_dir_of("local", &name);
|
||||||
|
|
||||||
|
if dir.exists() {
|
||||||
|
return Err("Package already exists".to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut installers = Vec::new();
|
||||||
|
|
||||||
|
if r#type == "segatools" {
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
map.insert(
|
||||||
|
"identifier".to_owned(),
|
||||||
|
serde_json::Value::String("segatools".to_owned())
|
||||||
|
);
|
||||||
|
installers.push(map);
|
||||||
|
} else if r#type == "native" {
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
map.insert(
|
||||||
|
"identifier".to_owned(),
|
||||||
|
serde_json::Value::String("native_mod".to_owned())
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
"dll-game".to_owned(),
|
||||||
|
serde_json::Value::String("some.dll".to_owned())
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
"dll-amdaemon".to_owned(),
|
||||||
|
serde_json::Value::String("another.dll".to_owned())
|
||||||
|
);
|
||||||
|
installers.push(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
let manifest = PackageManifest {
|
||||||
|
name,
|
||||||
|
version_number: "1.0.0".to_owned(),
|
||||||
|
description,
|
||||||
|
website_url: website,
|
||||||
|
dependencies: BTreeSet::new(),
|
||||||
|
installers,
|
||||||
|
games: Some(games)
|
||||||
|
};
|
||||||
|
|
||||||
|
std::fs::create_dir(&dir).map_err(|e| e.to_string())?;
|
||||||
|
let json = serde_json::to_string_pretty(&manifest).map_err(|e| e.to_string())?;
|
||||||
|
std::fs::write(dir.join("manifest.json"), json).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn reload_all_packages(state: State<'_, tokio::sync::Mutex<AppData>>) -> Result<(), String> {
|
pub async fn reload_all_packages(state: State<'_, tokio::sync::Mutex<AppData>>) -> Result<(), String> {
|
||||||
log::debug!("invoke: reload_all_packages");
|
log::debug!("invoke: reload_all_packages");
|
||||||
@ -420,13 +481,18 @@ pub async fn create_shortcut(_app: AppHandle, profile_meta: ProfileMeta) -> Resu
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn export_profile(state: State<'_, Mutex<AppData>>, export_keychip: bool, files: Vec<String>) -> Result<(), String> {
|
pub async fn export_profile(
|
||||||
|
state: State<'_, Mutex<AppData>>,
|
||||||
|
is_diagnostic: bool,
|
||||||
|
export_keychip: bool,
|
||||||
|
files: Vec<String>
|
||||||
|
) -> Result<(), String> {
|
||||||
log::debug!("invoke: export_profile({:?}, {:?} files)", export_keychip, files.len());
|
log::debug!("invoke: export_profile({:?}, {:?} files)", export_keychip, files.len());
|
||||||
|
|
||||||
let appd = state.lock().await;
|
let appd = state.lock().await;
|
||||||
match &appd.profile {
|
match &appd.profile {
|
||||||
Some(p) => {
|
Some(p) => {
|
||||||
p.export(export_keychip, files)
|
p.export(export_keychip, files, is_diagnostic)
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
@ -17,6 +17,12 @@ pub struct DownloadTick {
|
|||||||
ratio: f32,
|
ratio: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct DownloadEndPayload {
|
||||||
|
pub key: PkgKey,
|
||||||
|
pub enable: bool
|
||||||
|
}
|
||||||
|
|
||||||
impl DownloadHandler {
|
impl DownloadHandler {
|
||||||
pub fn new(app: AppHandle) -> DownloadHandler {
|
pub fn new(app: AppHandle) -> DownloadHandler {
|
||||||
DownloadHandler {
|
DownloadHandler {
|
||||||
@ -25,7 +31,7 @@ impl DownloadHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn download_zip(&mut self, zip_path: &PathBuf, pkg: &Package) -> Result<()> {
|
pub fn download_zip(&mut self, zip_path: &PathBuf, pkg: &Package, enable: bool) -> Result<()> {
|
||||||
let rmt = pkg.rmt.as_ref()
|
let rmt = pkg.rmt.as_ref()
|
||||||
.ok_or_else(|| anyhow!("Attempted to download a package without remote data"))?
|
.ok_or_else(|| anyhow!("Attempted to download a package without remote data"))?
|
||||||
.clone();
|
.clone();
|
||||||
@ -34,12 +40,18 @@ impl DownloadHandler {
|
|||||||
} else {
|
} else {
|
||||||
// TODO clear cache button should clear this
|
// TODO clear cache button should clear this
|
||||||
self.paths.insert(zip_path.clone());
|
self.paths.insert(zip_path.clone());
|
||||||
tauri::async_runtime::spawn(Self::download_zip_proc(self.app.clone(), zip_path.clone(), pkg.key(), rmt));
|
tauri::async_runtime::spawn(Self::download_zip_proc(self.app.clone(), zip_path.clone(), pkg.key(), rmt, enable));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn download_zip_proc(app: AppHandle, zip_path: PathBuf, pkg_key: PkgKey, rmt: Remote) -> Result<()> {
|
async fn download_zip_proc(
|
||||||
|
app: AppHandle,
|
||||||
|
zip_path: PathBuf,
|
||||||
|
pkg_key: PkgKey,
|
||||||
|
rmt: Remote,
|
||||||
|
enable: bool
|
||||||
|
) -> Result<()> {
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
@ -66,7 +78,10 @@ impl DownloadHandler {
|
|||||||
|
|
||||||
log::debug!("downloaded to {:?}", zip_path);
|
log::debug!("downloaded to {:?}", zip_path);
|
||||||
|
|
||||||
app.emit("download-end", pkg_key)?;
|
app.emit("download-end", DownloadEndPayload {
|
||||||
|
key: pkg_key,
|
||||||
|
enable
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -102,16 +102,23 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.listen("download-end", closure!(clone apph, |ev| {
|
app.listen("download-end", closure!(clone apph, |ev| {
|
||||||
let raw = ev.payload();
|
let payload = serde_json::from_str::<download_handler::DownloadEndPayload>(ev.payload());
|
||||||
log::debug!("download-end triggered: {}", raw);
|
match payload {
|
||||||
let key = PkgKey(raw[1..raw.len()-1].to_owned());
|
Ok(payload) => {
|
||||||
let apph = apph.clone();
|
log::debug!("download-end triggered: {:?}", payload);
|
||||||
tauri::async_runtime::spawn(async move {
|
let key = payload.key;
|
||||||
let mutex = apph.state::<Mutex<AppData>>();
|
let apph = apph.clone();
|
||||||
let mut appd = mutex.lock().await;
|
tauri::async_runtime::spawn(async move {
|
||||||
let res = appd.pkgs.install_package(&key, true, false).await;
|
let mutex = apph.state::<Mutex<AppData>>();
|
||||||
log::debug!("download-end install {:?}", res);
|
let mut appd = mutex.lock().await;
|
||||||
});
|
let res = appd.pkgs.install_package(&key, true, false, payload.enable).await;
|
||||||
|
log::debug!("download-end install {:?}", res);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("invalid download payload: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
app.listen("launch-end", closure!(clone apph, |_| {
|
app.listen("launch-end", closure!(clone apph, |_| {
|
||||||
@ -134,11 +141,13 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
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;
|
||||||
let res = appd.toggle_package(payload.pkg.clone(), ToggleAction::EnableSelf);
|
if payload.enable == true {
|
||||||
log::debug!(
|
let res = appd.toggle_package(payload.pkg.clone(), ToggleAction::EnableSelf);
|
||||||
"install-end-prelude toggle {:?}",
|
log::debug!(
|
||||||
res
|
"install-end-prelude toggle {:?}",
|
||||||
);
|
res
|
||||||
|
);
|
||||||
|
}
|
||||||
use tauri::Emitter;
|
use tauri::Emitter;
|
||||||
let res = apph.emit("install-end", payload);
|
let res = apph.emit("install-end", payload);
|
||||||
log::debug!("install-end {:?}", res);
|
log::debug!("install-end {:?}", res);
|
||||||
@ -193,6 +202,7 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
cmd::install_package,
|
cmd::install_package,
|
||||||
cmd::delete_package,
|
cmd::delete_package,
|
||||||
cmd::toggle_package,
|
cmd::toggle_package,
|
||||||
|
cmd::create_package,
|
||||||
|
|
||||||
cmd::list_profiles,
|
cmd::list_profiles,
|
||||||
cmd::init_profile,
|
cmd::init_profile,
|
||||||
@ -260,7 +270,7 @@ fn deep_link(app: AppHandle, args: Vec<String>) {
|
|||||||
log::info!("deep link: {}", url);
|
log::info!("deep link: {}", url);
|
||||||
|
|
||||||
let regex = regex::Regex::new(
|
let regex = regex::Regex::new(
|
||||||
r"rainycolor://v1/install/rainy\.patafour\.zip/([^/]+)/([^/]+)/[0-9]+\.[0-9]+\.[0-9]+/"
|
r"rainycolor://v1/install/www\.rainycolor\.org/([^/]+)/([^/]+)/[0-9]+\.[0-9]+\.[0-9]+/"
|
||||||
).expect("Invalid regex");
|
).expect("Invalid regex");
|
||||||
|
|
||||||
if let Some(caps) = regex.captures(url) {
|
if let Some(caps) = regex.captures(url) {
|
||||||
@ -272,11 +282,13 @@ fn deep_link(app: AppHandle, args: Vec<String>) {
|
|||||||
let mut appd = mutex.lock().await;
|
let mut appd = mutex.lock().await;
|
||||||
if appd.pkgs.is_offline() {
|
if appd.pkgs.is_offline() {
|
||||||
log::warn!("Deep link installation failed: offline");
|
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, true).await {
|
||||||
log::warn!("Deep link installation failed: {}", e.to_string());
|
log::warn!("Deep link installation failed: {}", e.to_string());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log::error!("No caps");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,11 @@ use super::misc::Game;
|
|||||||
|
|
||||||
// manifest.json
|
// manifest.json
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct PackageManifest {
|
pub struct PackageManifest {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub version_number: String,
|
pub version_number: String,
|
||||||
|
pub website_url: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub dependencies: BTreeSet<PkgKeyVersion>,
|
pub dependencies: BTreeSet<PkgKeyVersion>,
|
||||||
|
|
||||||
|
@ -117,12 +117,16 @@ impl Keyboard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Keyboard::Chunithm(kb) => {
|
Keyboard::Chunithm(kb) => {
|
||||||
|
let mut enabled_ir = false;
|
||||||
if kb.enabled {
|
if kb.enabled {
|
||||||
for (i, cell) in kb.cell.iter().enumerate() {
|
for (i, cell) in kb.cell.iter().enumerate() {
|
||||||
ini.with_section(Some("slider")).set(format!("cell{}", i + 1), cell.to_string());
|
ini.with_section(Some("slider")).set(format!("cell{}", i + 1), cell.to_string());
|
||||||
}
|
}
|
||||||
for (i, ir) in kb.ir.iter().enumerate() {
|
for (i, ir) in kb.ir.iter().enumerate() {
|
||||||
ini.with_section(Some("ir")).set(format!("ir{}", i + 1), ir.to_string());
|
ini.with_section(Some("ir")).set(format!("ir{}", i + 1), (*ir).to_string());
|
||||||
|
if i > 0 && *ir != 0 {
|
||||||
|
enabled_ir = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ini.with_section(Some("io3"))
|
ini.with_section(Some("io3"))
|
||||||
.set("test", kb.test.to_string())
|
.set("test", kb.test.to_string())
|
||||||
@ -140,8 +144,13 @@ impl Keyboard {
|
|||||||
.set("service", "0")
|
.set("service", "0")
|
||||||
.set("coin", "0");
|
.set("coin", "0");
|
||||||
}
|
}
|
||||||
ini.with_section(Some("io3"))
|
if enabled_ir {
|
||||||
.set("ir", "0");
|
ini.with_section(Some("io3"))
|
||||||
|
.set("ir", "0");
|
||||||
|
} else {
|
||||||
|
ini.with_section(Some("io3"))
|
||||||
|
.set("ir", kb.ir[0].to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,10 @@ impl PatchFileVec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_patches(&self, target: impl AsRef<Path>) -> Result<Vec<Patch>> {
|
pub fn find_patches(&self, target: impl AsRef<Path>) -> Result<Vec<Patch>> {
|
||||||
|
if !target.as_ref().exists() {
|
||||||
|
log::warn!("invalid target path: {:?}", target.as_ref());
|
||||||
|
anyhow::bail!("Unable to open {:?}. Make sure the game path is correct.", target.as_ref());
|
||||||
|
}
|
||||||
let checksum = try_digest(target.as_ref())?;
|
let checksum = try_digest(target.as_ref())?;
|
||||||
|
|
||||||
let mut res_patches = Vec::new();
|
let mut res_patches = Vec::new();
|
||||||
|
@ -109,7 +109,7 @@ impl Package {
|
|||||||
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.replace("https://rainy.patafour.zip/", "https://www.rainycolor.org/"),
|
||||||
icon: v.icon,
|
icon: v.icon,
|
||||||
deprecated: p.is_deprecated,
|
deprecated: p.is_deprecated,
|
||||||
nsfw: p.has_nsfw_content,
|
nsfw: p.has_nsfw_content,
|
||||||
|
@ -23,7 +23,8 @@ pub struct PackageStore {
|
|||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
pub struct Payload {
|
pub struct Payload {
|
||||||
pub pkg: PkgKey
|
pub pkg: PkgKey,
|
||||||
|
pub enable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
|
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
|
||||||
@ -132,7 +133,7 @@ impl PackageStore {
|
|||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = reqwest::get(format!("https://rainy.patafour.zip/c/{game}/api/v1/package/")).await?;
|
let response = reqwest::get(format!("https://www.rainycolor.org/c/{game}/api/v1/package/")).await?;
|
||||||
|
|
||||||
let reader = response
|
let reader = response
|
||||||
.bytes_stream()
|
.bytes_stream()
|
||||||
@ -180,7 +181,13 @@ impl PackageStore {
|
|||||||
self.offline = false;
|
self.offline = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
|
enable: bool
|
||||||
|
) -> Result<InstallResult> {
|
||||||
log::info!("installation request: {}/{}/{}", key, force, install_deps);
|
log::info!("installation request: {}/{}/{}", key, force, install_deps);
|
||||||
|
|
||||||
let pkg = self.store.get(key)
|
let pkg = self.store.get(key)
|
||||||
@ -193,7 +200,8 @@ impl PackageStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.app.emit("install-start", Payload {
|
self.app.emit("install-start", Payload {
|
||||||
pkg: key.to_owned()
|
pkg: key.to_owned(),
|
||||||
|
enable
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let rmt = pkg.rmt.as_ref()
|
let rmt = pkg.rmt.as_ref()
|
||||||
@ -203,7 +211,7 @@ impl PackageStore {
|
|||||||
let mut set = HashSet::new();
|
let mut set = HashSet::new();
|
||||||
self.resolve_deps(rmt.clone(), &mut set)?;
|
self.resolve_deps(rmt.clone(), &mut set)?;
|
||||||
for dep in set {
|
for dep in set {
|
||||||
Box::pin(self.install_package(&dep, false, false)).await?;
|
Box::pin(self.install_package(&dep, false, false, enable)).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,7 +222,7 @@ impl PackageStore {
|
|||||||
let part_path = zip_path.join(".part");
|
let part_path = zip_path.join(".part");
|
||||||
|
|
||||||
if !zip_path.exists() && !part_path.exists() {
|
if !zip_path.exists() && !part_path.exists() {
|
||||||
self.dlh.download_zip(&zip_path, &pkg)?;
|
self.dlh.download_zip(&zip_path, &pkg, enable)?;
|
||||||
log::debug!("deferring {}", key);
|
log::debug!("deferring {}", key);
|
||||||
return Ok(InstallResult::Deferred);
|
return Ok(InstallResult::Deferred);
|
||||||
}
|
}
|
||||||
@ -230,7 +238,8 @@ impl PackageStore {
|
|||||||
self.reload_package(key.to_owned()).await;
|
self.reload_package(key.to_owned()).await;
|
||||||
|
|
||||||
self.app.emit("install-end-prelude", Payload {
|
self.app.emit("install-end-prelude", Payload {
|
||||||
pkg: key.to_owned()
|
pkg: key.to_owned(),
|
||||||
|
enable
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
log::info!("installed {}", key);
|
log::info!("installed {}", key);
|
||||||
@ -252,7 +261,8 @@ impl PackageStore {
|
|||||||
|
|
||||||
if rv.is_ok() {
|
if rv.is_ok() {
|
||||||
self.app.emit("install-end-prelude", Payload {
|
self.app.emit("install-end-prelude", Payload {
|
||||||
pkg: key.to_owned()
|
pkg: key.to_owned(),
|
||||||
|
enable: false
|
||||||
})?;
|
})?;
|
||||||
log::info!("deleted {}", key);
|
log::info!("deleted {}", key);
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ impl Profile {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn export(&self, export_keychip: bool, extra_files: Vec<String>) -> anyhow::Result<()> {
|
pub fn export(&self, export_keychip: bool, extra_files: Vec<String>, is_diagnostic: bool) -> anyhow::Result<()> {
|
||||||
let mut prf = self.clone();
|
let mut prf = self.clone();
|
||||||
|
|
||||||
let dir = util::config_dir().join("exports");
|
let dir = util::config_dir().join("exports");
|
||||||
@ -45,7 +45,7 @@ impl Profile {
|
|||||||
std::fs::create_dir(&dir)?;
|
std::fs::create_dir(&dir)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = dir.join(format!("{}-{}-template.zip", &self.meta.game, &self.meta.name));
|
let path = dir.join(format!("{}-{}-{}.zip", &self.meta.game, &self.meta.name, if is_diagnostic { "diagnostic" } else { "template" } ));
|
||||||
|
|
||||||
{
|
{
|
||||||
let sgt = &mut prf.data.sgt;
|
let sgt = &mut prf.data.sgt;
|
||||||
@ -66,7 +66,7 @@ impl Profile {
|
|||||||
if network.local_path.is_absolute() {
|
if network.local_path.is_absolute() {
|
||||||
network.local_path = PathBuf::new();
|
network.local_path = PathBuf::new();
|
||||||
}
|
}
|
||||||
if !export_keychip {
|
if !export_keychip || is_diagnostic {
|
||||||
network.keychip = String::new();
|
network.keychip = String::new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,6 +83,29 @@ impl Profile {
|
|||||||
zip.write_all(&std::fs::read(self.config_dir().join(file))?)?;
|
zip.write_all(&std::fs::read(self.config_dir().join(file))?)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if is_diagnostic {
|
||||||
|
let name = "mu3.exe.log";
|
||||||
|
let path = self.data_dir().join(name);
|
||||||
|
if path.exists() {
|
||||||
|
zip.start_file(name, options)?;
|
||||||
|
zip.write_all(&std::fs::read(path)?)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = "chusanApp.exe.log";
|
||||||
|
let path = self.data_dir().join(name);
|
||||||
|
if path.exists() {
|
||||||
|
zip.start_file(name, options)?;
|
||||||
|
zip.write_all(&std::fs::read(path)?)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = "amdaemon.exe.log";
|
||||||
|
let path = self.data_dir().join(name);
|
||||||
|
if path.exists() {
|
||||||
|
zip.start_file(name, options)?;
|
||||||
|
zip.write_all(&std::fs::read(path)?)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
zip.finish()?;
|
zip.finish()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -63,9 +63,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Ongeki
|
// Ongeki
|
||||||
filename: "amdaemon.exe",
|
filename: "amdaemon.exe",
|
||||||
version: "46d47eab",
|
version: "46d47eab",
|
||||||
sha256: '962C76331306D0839AFD40808EA99D83E651D39C4708C448ADE0C77E8BC0A1B0',
|
sha256: '962C76331306D0839AFD40808EA99D83E651D39C4708C448ADE0C77E8BC0A1B0',
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
id: 'standard-localhost',
|
id: 'standard-localhost',
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "STARTLINER",
|
"productName": "STARTLINER",
|
||||||
"version": "0.16.0",
|
"version": "0.19.1",
|
||||||
"identifier": "zip.patafour.startliner",
|
"identifier": "zip.patafour.startliner",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "bun run dev",
|
"beforeDevCommand": "bun run dev",
|
||||||
|
@ -295,7 +295,7 @@ listen<DownloadingStatus>('download-progress', (event) => {
|
|||||||
pkg.hasAvailableUpdates
|
pkg.hasAvailableUpdates
|
||||||
"
|
"
|
||||||
icon="pi pi-download"
|
icon="pi pi-download"
|
||||||
label="UPDATE ALL"
|
:label="t('updateAll')"
|
||||||
size="small"
|
size="small"
|
||||||
class="mr-4 m-2.5"
|
class="mr-4 m-2.5"
|
||||||
@click="pkg.updateAll()"
|
@click="pkg.updateAll()"
|
||||||
|
@ -53,6 +53,6 @@ const remove = async () => {
|
|||||||
class="self-center ml-4"
|
class="self-center ml-4"
|
||||||
style="width: 2rem; height: 2rem"
|
style="width: 2rem; height: 2rem"
|
||||||
:loading="pkg?.js.downloading"
|
:loading="pkg?.js.downloading"
|
||||||
v-on:click="async () => await pkgs.install(pkg)"
|
v-on:click="async () => await pkgs.install(pkg, true)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -4,6 +4,9 @@ import InputText from 'primevue/inputtext';
|
|||||||
import { fromKeycode, toKeycode } from '../keyboard';
|
import { fromKeycode, toKeycode } from '../keyboard';
|
||||||
import { usePrfStore } from '../stores';
|
import { usePrfStore } from '../stores';
|
||||||
import { OngekiButtons } from '../types';
|
import { OngekiButtons } from '../types';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const prf = usePrfStore();
|
const prf = usePrfStore();
|
||||||
|
|
||||||
@ -61,6 +64,10 @@ const handleKey = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.code === 'Escape') {
|
||||||
|
keycode = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (index !== undefined) {
|
if (index !== undefined) {
|
||||||
data[button][index] = keycode;
|
data[button][index] = keycode;
|
||||||
} else {
|
} else {
|
||||||
@ -160,13 +167,24 @@ const fontSize = computed(() => {
|
|||||||
<InputText
|
<InputText
|
||||||
:style="{
|
:style="{
|
||||||
width: small ? '2.8rem' : '5rem',
|
width: small ? '2.8rem' : '5rem',
|
||||||
height: small ? '2.8rem' : tall ? '10rem' : '5rem',
|
height:
|
||||||
|
small && tall
|
||||||
|
? '5rem'
|
||||||
|
: small
|
||||||
|
? '2.8rem'
|
||||||
|
: tall
|
||||||
|
? '10rem'
|
||||||
|
: '5rem',
|
||||||
fontSize,
|
fontSize,
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
}"
|
}"
|
||||||
unstyled
|
unstyled
|
||||||
class="text-center buttoninputtext"
|
class="text-center buttoninputtext"
|
||||||
v-tooltip="tooltip ? `${tooltip}: ${modelValue}` : undefined"
|
v-tooltip="
|
||||||
|
tooltip
|
||||||
|
? `${tooltip}: ${modelValue} ${tooltip.startsWith('ir') ? `\n${t('cfg.keyboard.irTooltip')}` : ''}`
|
||||||
|
: undefined
|
||||||
|
"
|
||||||
@contextmenu.prevent="() => {}"
|
@contextmenu.prevent="() => {}"
|
||||||
@keydown="(ev: KeyboardEvent) => handleKey(button, ev, index)"
|
@keydown="(ev: KeyboardEvent) => handleKey(button, ev, index)"
|
||||||
@mousedown="
|
@mousedown="
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
<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 Dialog from 'primevue/dialog';
|
||||||
import Fieldset from 'primevue/fieldset';
|
import Fieldset from 'primevue/fieldset';
|
||||||
|
import InputText from 'primevue/inputtext';
|
||||||
|
import MultiSelect from 'primevue/multiselect';
|
||||||
|
import SelectButton from 'primevue/selectbutton';
|
||||||
|
import ToggleSwitch from 'primevue/toggleswitch';
|
||||||
|
import { emit } from '@tauri-apps/api/event';
|
||||||
import ModListEntry from './ModListEntry.vue';
|
import ModListEntry from './ModListEntry.vue';
|
||||||
import ModTitlecard from './ModTitlecard.vue';
|
import ModTitlecard from './ModTitlecard.vue';
|
||||||
import { invoke } from '../invoke';
|
import { invoke } from '../invoke';
|
||||||
import { usePkgStore, usePrfStore } from '../stores';
|
import { useClientStore, usePkgStore, usePrfStore } from '../stores';
|
||||||
import { Package } from '../types';
|
import { Feature, Game, Package } from '../types';
|
||||||
import { pkgKey } from '../util';
|
import { pkgKey } from '../util';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
@ -17,38 +23,120 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const pkgs = usePkgStore();
|
const pkgs = usePkgStore();
|
||||||
|
const client = useClientStore();
|
||||||
const prf = usePrfStore();
|
const prf = usePrfStore();
|
||||||
const empty = ref(false);
|
|
||||||
const gameSublist: Ref<string[]> = ref([]);
|
const gameSublist: Ref<string[]> = ref([]);
|
||||||
|
|
||||||
invoke('get_game_packages', {
|
const loadPackages = () => {
|
||||||
game: prf.current?.meta.game ?? null,
|
invoke('get_game_packages', {
|
||||||
}).then((list) => {
|
game: prf.current?.meta.game ?? null,
|
||||||
gameSublist.value = list as string[];
|
}).then((list) => {
|
||||||
|
gameSublist.value = list as string[];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
loadPackages();
|
||||||
|
|
||||||
|
const allCategories = computed(() => {
|
||||||
|
const res = new Set<string>();
|
||||||
|
for (const pkg of pkgs.allLocal) {
|
||||||
|
for (const cat of pkg.rmt?.categories ?? []) {
|
||||||
|
res.add(cat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...res.values()].sort((a, b) => a.localeCompare(b));
|
||||||
});
|
});
|
||||||
|
|
||||||
const group = computed(() => {
|
const local = computed(() => {
|
||||||
const res = Object.assign(
|
return pkgs.allLocal
|
||||||
{},
|
.filter((p) => gameSublist.value.includes(pkgKey(p)))
|
||||||
Object.groupBy(
|
.filter((p) => p.namespace === 'local');
|
||||||
pkgs.allLocal
|
});
|
||||||
.filter((p) => gameSublist.value.includes(pkgKey(p)))
|
|
||||||
.filter(
|
const groupedList = computed(() => {
|
||||||
(p) =>
|
const searchedPkgs = pkgs.allLocal
|
||||||
props.search === undefined ||
|
.filter((p) => gameSublist.value.includes(pkgKey(p)))
|
||||||
p.name
|
.filter((p) => p.namespace !== 'local')
|
||||||
.toLowerCase()
|
.filter(
|
||||||
.includes(props.search.toLowerCase()) ||
|
(p) =>
|
||||||
p.namespace
|
props.search === undefined ||
|
||||||
.toLowerCase()
|
p.name.toLowerCase().includes(props.search.toLowerCase()) ||
|
||||||
.includes(props.search.toLowerCase())
|
p.namespace.toLowerCase().includes(props.search.toLowerCase())
|
||||||
)
|
);
|
||||||
.sort((p1, p2) => p1.namespace.localeCompare(p2.namespace))
|
|
||||||
.sort((p1, p2) => p1.name.localeCompare(p2.name)),
|
let grouped;
|
||||||
({ namespace }) => namespace
|
if (client.pkgListMode === 'namespace') {
|
||||||
)
|
grouped = Object.groupBy(searchedPkgs, ({ namespace }) => namespace);
|
||||||
);
|
} else if (client.pkgListMode === 'type') {
|
||||||
empty.value = Object.keys(res).length === 0;
|
grouped = {
|
||||||
|
standard: [] as Package[],
|
||||||
|
native: [] as Package[],
|
||||||
|
segatools: [] as Package[],
|
||||||
|
unsupported: [] as Package[] | undefined,
|
||||||
|
};
|
||||||
|
grouped.unsupported = [];
|
||||||
|
for (const pkg of searchedPkgs) {
|
||||||
|
const loc = pkg.loc;
|
||||||
|
if (!loc || !loc.status || typeof loc.status === 'string') {
|
||||||
|
grouped.unsupported.push(pkg);
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
loc.status.OK[0] &
|
||||||
|
(Feature.GameDLL | Feature.Mempatcher | Feature.AmdDLL)
|
||||||
|
) {
|
||||||
|
grouped.native.push(pkg);
|
||||||
|
} else if (loc.status.OK[0] & Feature.Mod) {
|
||||||
|
grouped.standard.push(pkg);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
loc.status.OK[0] &
|
||||||
|
(Feature.AMNet |
|
||||||
|
Feature.Aime |
|
||||||
|
Feature.ChuniIO |
|
||||||
|
Feature.ChusanHook |
|
||||||
|
Feature.Mu3IO |
|
||||||
|
Feature.Mu3Hook)
|
||||||
|
) {
|
||||||
|
grouped.segatools.push(pkg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (grouped.unsupported.length === 0) {
|
||||||
|
delete grouped.unsupported;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
grouped = {} as { [key: string]: Package[] };
|
||||||
|
for (const pkg of searchedPkgs) {
|
||||||
|
for (const cat of pkg.rmt?.categories ?? []) {
|
||||||
|
if (client.hiddenCategories.includes(cat)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!(cat in grouped)) {
|
||||||
|
grouped[cat] = [] as Package[];
|
||||||
|
}
|
||||||
|
grouped[cat].push(pkg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let res: [string, Package[]][] = [];
|
||||||
|
for (const [k, v] of Object.entries(grouped)) {
|
||||||
|
if (v !== undefined) {
|
||||||
|
res.push([k, v]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
client.pkgListMode === 'namespace' ||
|
||||||
|
client.pkgListMode === 'category'
|
||||||
|
) {
|
||||||
|
res.sort((a, b) => `${a[0]}`.localeCompare(`${b[0]}`));
|
||||||
|
} else if (client.pkgListMode === 'type') {
|
||||||
|
for (const entry of res) {
|
||||||
|
entry[0] = t(`pkglist.${entry[0]}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -56,12 +144,169 @@ const missing = computed(() => {
|
|||||||
return prf.current?.data.mods.filter((m) => !pkgs.hasLocal(m)) ?? [];
|
return prf.current?.data.mods.filter((m) => !pkgs.hasLocal(m)) ?? [];
|
||||||
});
|
});
|
||||||
|
|
||||||
const emptyVisible = ref(false);
|
const dialogVisible = ref(false);
|
||||||
setTimeout(() => (emptyVisible.value = true), 500);
|
|
||||||
|
const defaultModel = {
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
website: '',
|
||||||
|
type: 'rainy',
|
||||||
|
games: [] as string[],
|
||||||
|
};
|
||||||
|
|
||||||
|
const creatorModel = ref({ ...defaultModel });
|
||||||
|
|
||||||
|
const gameModel = (game: Game) =>
|
||||||
|
computed({
|
||||||
|
get() {
|
||||||
|
return (creatorModel.value.games as string[]).includes(game);
|
||||||
|
},
|
||||||
|
set(v: boolean) {
|
||||||
|
creatorModel.value.games = creatorModel.value.games.filter(
|
||||||
|
(g) => g !== game
|
||||||
|
);
|
||||||
|
if (v) {
|
||||||
|
creatorModel.value.games.push(game);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const gameModelOngeki = gameModel('ongeki');
|
||||||
|
const gameModelChunithm = gameModel('chunithm');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Fieldset :legend="t('store.missing')" v-if="(missing?.length ?? 0) > 0">
|
<Dialog
|
||||||
|
modal
|
||||||
|
:visible="dialogVisible"
|
||||||
|
:closable="false"
|
||||||
|
:header="t('creator.header')"
|
||||||
|
:style="{ width: '500px', scale: client.scaleValue }"
|
||||||
|
class="creation-dialog"
|
||||||
|
>
|
||||||
|
<div style="position: absolute; left: 250px; top: 25px">
|
||||||
|
<a
|
||||||
|
href="https://gitea.tendokyu.moe/akanyan/STARTLINER/wiki/Package-format"
|
||||||
|
target="_blank"
|
||||||
|
style="text-decoration: underline"
|
||||||
|
class="self-center"
|
||||||
|
>{{ t('creator.packageFormat') }}</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<h2>{{ t('creator.basic') }}</h2>
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<InputText
|
||||||
|
size="small"
|
||||||
|
style="width: 100%"
|
||||||
|
:placeholder="t('creator.name')"
|
||||||
|
v-model="creatorModel.name"
|
||||||
|
/>
|
||||||
|
<InputText
|
||||||
|
size="small"
|
||||||
|
style="width: 100%"
|
||||||
|
:placeholder="t('creator.description')"
|
||||||
|
v-model="creatorModel.description"
|
||||||
|
/>
|
||||||
|
<InputText
|
||||||
|
size="small"
|
||||||
|
style="width: 100%"
|
||||||
|
:placeholder="t('creator.website')"
|
||||||
|
v-model="creatorModel.website"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>{{ t('creator.type') }}</h2>
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<SelectButton
|
||||||
|
:options="[
|
||||||
|
{ title: t('creator.rainy'), value: 'rainy' },
|
||||||
|
{ title: t('creator.native'), value: 'native' },
|
||||||
|
{ title: t('creator.segatools'), value: 'segatools' },
|
||||||
|
]"
|
||||||
|
v-model="creatorModel.type"
|
||||||
|
:allow-empty="false"
|
||||||
|
option-label="title"
|
||||||
|
option-value="value"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>{{ t('creator.games') }}</h2>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="flex flex-row">
|
||||||
|
<div class="grow">{{ t('game.ongeki') }}</div>
|
||||||
|
<ToggleSwitch v-model="gameModelOngeki" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row">
|
||||||
|
<div class="grow">{{ t('game.chunithm') }}</div>
|
||||||
|
<ToggleSwitch v-model="gameModelChunithm" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-row mt-5">
|
||||||
|
<Button
|
||||||
|
class="ml-auto mr-1"
|
||||||
|
style="width: 80px"
|
||||||
|
:label="t('ok')"
|
||||||
|
:disabled="creatorModel.games.length === 0"
|
||||||
|
@click="
|
||||||
|
async () => {
|
||||||
|
await invoke('create_package', creatorModel);
|
||||||
|
await pkgs.reloadAll();
|
||||||
|
loadPackages();
|
||||||
|
dialogVisible = false;
|
||||||
|
creatorModel = { ...defaultModel };
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
class="mr-auto ml-1"
|
||||||
|
style="width: 80px"
|
||||||
|
:label="t('cancel')"
|
||||||
|
@click="
|
||||||
|
() => (
|
||||||
|
(dialogVisible = false),
|
||||||
|
(creatorModel = { ...defaultModel })
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
<div class="flex flex-row">
|
||||||
|
<SelectButton
|
||||||
|
:options="[
|
||||||
|
{ title: t('pkglist.namespace'), value: 'namespace' },
|
||||||
|
{ title: t('pkglist.type'), value: 'type' },
|
||||||
|
{ title: t('pkglist.category'), value: 'category' },
|
||||||
|
]"
|
||||||
|
v-model="client.pkgListMode"
|
||||||
|
v-on:update:model-value="
|
||||||
|
client.save();
|
||||||
|
emit('reload-icons');
|
||||||
|
"
|
||||||
|
:allow-empty="false"
|
||||||
|
option-label="title"
|
||||||
|
option-value="value"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="grow text-right mr-2 self-center"
|
||||||
|
v-if="client.pkgListMode === 'category'"
|
||||||
|
>
|
||||||
|
{{ t('pkglist.exclusions') }}
|
||||||
|
</div>
|
||||||
|
<MultiSelect
|
||||||
|
v-if="client.pkgListMode === 'category'"
|
||||||
|
style="width: 30%"
|
||||||
|
:showToggleAll="false"
|
||||||
|
v-model="client.hiddenCategories"
|
||||||
|
v-on:value-change="
|
||||||
|
client.save();
|
||||||
|
emit('reload-icons');
|
||||||
|
"
|
||||||
|
:options="allCategories"
|
||||||
|
class="w-full grow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Fieldset :legend="t('pkglist.missing')" v-if="(missing?.length ?? 0) > 0">
|
||||||
<div class="flex items-center" v-for="p in missing">
|
<div class="flex items-center" v-for="p in missing">
|
||||||
<ModTitlecard
|
<ModTitlecard
|
||||||
show-namespace
|
show-namespace
|
||||||
@ -84,10 +329,28 @@ setTimeout(() => (emptyVisible.value = true), 500);
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
<Fieldset v-for="(namespace, key) in group" :legend="key.toString()">
|
<Fieldset :legend="t('pkglist.local')">
|
||||||
<ModListEntry v-for="p in namespace" :pkg="p" />
|
<ModListEntry v-for="p in local" :pkg="p" />
|
||||||
|
<Button
|
||||||
|
rounded
|
||||||
|
icon="pi pi-plus"
|
||||||
|
severity="success"
|
||||||
|
aria-label="install"
|
||||||
|
size="small"
|
||||||
|
class="self-center"
|
||||||
|
style="width: 2rem; height: 2rem"
|
||||||
|
v-on:click="() => (dialogVisible = true)"
|
||||||
|
/>
|
||||||
|
</Fieldset>
|
||||||
|
<Fieldset v-for="[namespace, pkgs] in groupedList" :legend="namespace">
|
||||||
|
<ModListEntry v-for="p in pkgs" :pkg="p" />
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
<div v-if="empty === true && emptyVisible === true" class="text-3xl fadein">
|
|
||||||
∅
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.creation-dialog h2 {
|
||||||
|
margin-top: 0.6em;
|
||||||
|
margin-bottom: 0.4em;
|
||||||
|
font-size: 110%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -7,7 +7,7 @@ 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 { invoke } from '../invoke';
|
import { invoke } from '../invoke';
|
||||||
import { usePkgStore, usePrfStore } from '../stores';
|
import { useClientStore, usePkgStore, usePrfStore } from '../stores';
|
||||||
import { Feature, Package } from '../types';
|
import { Feature, Package } from '../types';
|
||||||
import { hasFeature } from '../util';
|
import { hasFeature } from '../util';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
@ -16,6 +16,7 @@ const { t } = useI18n();
|
|||||||
|
|
||||||
const prf = usePrfStore();
|
const prf = usePrfStore();
|
||||||
const pkgs = usePkgStore();
|
const pkgs = usePkgStore();
|
||||||
|
const client = useClientStore();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
pkg: Object as () => Package,
|
pkg: Object as () => Package,
|
||||||
@ -39,7 +40,13 @@ if (unsupported.value === true && model.value === true) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<ModTitlecard show-version show-icon show-description :pkg="pkg" />
|
<ModTitlecard
|
||||||
|
show-version
|
||||||
|
show-icon
|
||||||
|
show-description
|
||||||
|
:show-namespace="client.pkgListMode !== 'namespace'"
|
||||||
|
:pkg="pkg"
|
||||||
|
/>
|
||||||
<UpdateButton :pkg="pkg" />
|
<UpdateButton :pkg="pkg" />
|
||||||
<span v-tooltip="unsupported && t('store.incompatible')">
|
<span v-tooltip="unsupported && t('store.incompatible')">
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { ref } 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 { listen } from '@tauri-apps/api/event';
|
||||||
|
import { invoke } from '../invoke';
|
||||||
import { Feature, Package } from '../types';
|
import { Feature, Package } from '../types';
|
||||||
import { hasFeature, needsUpdate } from '../util';
|
import { hasFeature, needsUpdate } from '../util';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
pkg: Object as () => Package,
|
pkg: Object as () => Package,
|
||||||
@ -14,23 +19,34 @@ const props = defineProps({
|
|||||||
showIcon: Boolean,
|
showIcon: Boolean,
|
||||||
});
|
});
|
||||||
|
|
||||||
const iconSrc = computed(() => {
|
const icon = ref('/no-icon.png');
|
||||||
const icon = props.pkg?.loc?.icon ?? props.pkg?.rmt?.icon;
|
|
||||||
|
|
||||||
if (icon === undefined) {
|
const reloadIcons = async () => {
|
||||||
return '';
|
const src = props.pkg?.loc?.icon ?? props.pkg?.rmt?.icon;
|
||||||
} else if (icon.startsWith('https://')) {
|
|
||||||
return icon;
|
if (src === undefined) {
|
||||||
|
icon.value = '/no-icon.png';
|
||||||
|
} else if (src.startsWith('https://')) {
|
||||||
|
icon.value = src;
|
||||||
} else {
|
} else {
|
||||||
return convertFileSrc(icon);
|
const convt = convertFileSrc(src);
|
||||||
|
if (await invoke('file_exists', { path: src })) {
|
||||||
|
icon.value = convt;
|
||||||
|
} else {
|
||||||
|
icon.value = '/no-icon.png';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
reloadIcons();
|
||||||
|
|
||||||
|
listen('reload-icons', reloadIcons);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<img
|
<img
|
||||||
v-if="showIcon"
|
v-if="showIcon"
|
||||||
:src="iconSrc"
|
:src="icon"
|
||||||
class="self-center rounded-sm"
|
class="self-center rounded-sm"
|
||||||
width="32px"
|
width="32px"
|
||||||
height="32px"
|
height="32px"
|
||||||
@ -89,7 +105,12 @@ const iconSrc = computed(() => {
|
|||||||
v-if="showNamespace && pkg?.namespace"
|
v-if="showNamespace && pkg?.namespace"
|
||||||
class="text-sm opacity-75"
|
class="text-sm opacity-75"
|
||||||
>
|
>
|
||||||
by {{ pkg.namespace }}
|
{{
|
||||||
|
t('by', { namespace: pkg.namespace }).replaceAll(
|
||||||
|
' ',
|
||||||
|
' '
|
||||||
|
)
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span class="m-2">
|
<span class="m-2">
|
||||||
<span
|
<span
|
||||||
|
@ -29,23 +29,34 @@ const files = new Set<string>();
|
|||||||
).includes('chunithm');
|
).includes('chunithm');
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
const fileList = {
|
||||||
|
ongeki: ['aime.txt', 'inohara.cfg', 'mu3.ini', 'segatools-base.ini'],
|
||||||
|
chunithm: ['aime.txt', 'saekawa.toml', 'segatools-base.ini'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const diagnosticList = {
|
||||||
|
ongeki: ['mu3.ini', 'segatools-base.ini'],
|
||||||
|
chunithm: ['segatools-base.ini'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const diagnostic = ref(false);
|
||||||
|
|
||||||
const exportTemplate = async () => {
|
const exportTemplate = async () => {
|
||||||
const fl = [...files.values()];
|
const fl = [...files.values()];
|
||||||
exportVisible.value = false;
|
exportVisible.value = false;
|
||||||
await invoke('export_profile', {
|
await invoke('export_profile', {
|
||||||
exportKeychip: exportKeychip.value,
|
exportKeychip: exportKeychip.value,
|
||||||
files: fl,
|
isDiagnostic: diagnostic.value,
|
||||||
|
files:
|
||||||
|
diagnostic.value === true
|
||||||
|
? diagnosticList[prf.current!.meta.game]
|
||||||
|
: fl,
|
||||||
});
|
});
|
||||||
await invoke('open_file', {
|
await invoke('open_file', {
|
||||||
path: await path.join(await general.configDir, 'exports'),
|
path: await path.join(await general.configDir, 'exports'),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileList = {
|
|
||||||
ongeki: ['aime.txt', 'inohara.cfg', 'mu3.ini', 'segatools-base.ini'],
|
|
||||||
chunithm: ['aime.txt', 'saekawa.toml', 'segatools-base.ini'],
|
|
||||||
};
|
|
||||||
|
|
||||||
const fileListCurrent: Ref<string[]> = ref([]);
|
const fileListCurrent: Ref<string[]> = ref([]);
|
||||||
|
|
||||||
const recalcFileList = async () => {
|
const recalcFileList = async () => {
|
||||||
@ -91,16 +102,36 @@ const importPick = async () => {
|
|||||||
:visible="exportVisible"
|
:visible="exportVisible"
|
||||||
:closable="false /*this shit doesn't work */"
|
:closable="false /*this shit doesn't work */"
|
||||||
:header="`${t('profile.export')} ${prf.current?.meta.name}`"
|
:header="`${t('profile.export')} ${prf.current?.meta.name}`"
|
||||||
:style="{ width: '300px', scale: client.scaleValue }"
|
:style="{ width: '330px', scale: client.scaleValue }"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<SelectButton
|
||||||
|
v-model="diagnostic"
|
||||||
|
:options="[
|
||||||
|
{
|
||||||
|
title: t('profile.standardExport'),
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('profile.diagnostic'),
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
:allow-empty="false"
|
||||||
|
option-label="title"
|
||||||
|
option-value="value"
|
||||||
|
>
|
||||||
|
</SelectButton>
|
||||||
|
</div>
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<div class="grow">{{ t('profile.export') }} keychip</div>
|
<div class="grow">{{ t('profile.export') }} keychip</div>
|
||||||
<ToggleSwitch v-model="exportKeychip" />
|
<ToggleSwitch :disabled="diagnostic" v-model="exportKeychip" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row" v-for="f in fileListCurrent">
|
<div class="flex flex-row" v-for="f in fileListCurrent">
|
||||||
<div class="grow">{{ t('profile.export') }} {{ f }}</div>
|
<div class="grow">{{ t('profile.export') }} {{ f }}</div>
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
|
:disabled="diagnostic"
|
||||||
:model-value="true"
|
:model-value="true"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
(v) => {
|
(v) => {
|
||||||
|
@ -17,6 +17,7 @@ const install = async () => {
|
|||||||
await invoke('install_package', {
|
await invoke('install_package', {
|
||||||
key: pkgKey(props.pkg),
|
key: pkgKey(props.pkg),
|
||||||
force: true,
|
force: true,
|
||||||
|
enable: false,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (props.pkg !== undefined) {
|
if (props.pkg !== undefined) {
|
||||||
|
@ -95,7 +95,7 @@ const prf = usePrfStore();
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="prf.current?.meta.game === 'chunithm'">
|
<div v-if="prf.current?.meta.game === 'chunithm'">
|
||||||
<div class="absolute left-1/2 top-1/5">
|
<div class="absolute left-9/17 top-1/12">
|
||||||
<div
|
<div
|
||||||
class="flex flex-row flex-nowrap gap-2 self-center w-full"
|
class="flex flex-row flex-nowrap gap-2 self-center w-full"
|
||||||
>
|
>
|
||||||
@ -108,6 +108,7 @@ const prf = usePrfStore();
|
|||||||
button="ir"
|
button="ir"
|
||||||
:index="idx - 1"
|
:index="idx - 1"
|
||||||
:tooltip="`ir${idx}`"
|
:tooltip="`ir${idx}`"
|
||||||
|
tall
|
||||||
small
|
small
|
||||||
color="rgba(0, 255, 0, 0.2)"
|
color="rgba(0, 255, 0, 0.2)"
|
||||||
/>
|
/>
|
||||||
|
@ -8,6 +8,8 @@ export default {
|
|||||||
next: 'Next',
|
next: 'Next',
|
||||||
skip: 'Skip',
|
skip: 'Skip',
|
||||||
close: 'Close',
|
close: 'Close',
|
||||||
|
by: 'by {namespace}',
|
||||||
|
updateAll: 'UPDATE ALL',
|
||||||
start: {
|
start: {
|
||||||
failed: 'Start check failed',
|
failed: 'Start check failed',
|
||||||
accept: 'Run anyway',
|
accept: 'Run anyway',
|
||||||
@ -43,8 +45,23 @@ export default {
|
|||||||
reallyDelete: 'Are you sure you want to delete {profile}?',
|
reallyDelete: 'Are you sure you want to delete {profile}?',
|
||||||
template: 'STARTLINER template',
|
template: 'STARTLINER template',
|
||||||
importTemplate: 'Import template',
|
importTemplate: 'Import template',
|
||||||
exportTemplate: 'Export template',
|
exportTemplate: 'Export profile',
|
||||||
export: 'Export',
|
export: 'Export',
|
||||||
|
standardExport: 'Template',
|
||||||
|
diagnostic: 'Diagnostic',
|
||||||
|
},
|
||||||
|
creator: {
|
||||||
|
header: 'Package creator',
|
||||||
|
basic: 'Basic information',
|
||||||
|
name: 'Name',
|
||||||
|
description: 'Description',
|
||||||
|
website: 'Website',
|
||||||
|
type: 'Package type',
|
||||||
|
rainy: 'Standard',
|
||||||
|
segatools: 'Segatools',
|
||||||
|
native: 'Native',
|
||||||
|
games: 'Games',
|
||||||
|
packageFormat: 'Package format spec',
|
||||||
},
|
},
|
||||||
store: {
|
store: {
|
||||||
installRecommended: 'Install recommended packages',
|
installRecommended: 'Install recommended packages',
|
||||||
@ -52,10 +69,22 @@ export default {
|
|||||||
deprecated: 'Show deprecated',
|
deprecated: 'Show deprecated',
|
||||||
nsfw: 'Show NSFW',
|
nsfw: 'Show NSFW',
|
||||||
incompatible: 'This package is currently incompatible with STARTLINER.',
|
incompatible: 'This package is currently incompatible with STARTLINER.',
|
||||||
missing: 'Missing',
|
|
||||||
includeCategories: 'Include categories',
|
includeCategories: 'Include categories',
|
||||||
excludeCategories: 'Exclude categories',
|
excludeCategories: 'Exclude categories',
|
||||||
},
|
},
|
||||||
|
pkglist: {
|
||||||
|
missing: 'Missing',
|
||||||
|
local: 'Local packages',
|
||||||
|
namespace: 'By namespace',
|
||||||
|
type: 'By type',
|
||||||
|
category: 'By category',
|
||||||
|
standard: 'Standard mods',
|
||||||
|
native: 'Native mods',
|
||||||
|
segatools: 'segatools',
|
||||||
|
unsupported: 'Unsupported',
|
||||||
|
exclusions: 'Exclusions:',
|
||||||
|
},
|
||||||
patch: {
|
patch: {
|
||||||
loading: 'Loading...',
|
loading: 'Loading...',
|
||||||
noneFound:
|
noneFound:
|
||||||
@ -163,6 +192,8 @@ export default {
|
|||||||
'Only applicable if the IO module is set to segatools built-in (keyboard) or a compatible third-party module (like mu3io.NET)',
|
'Only applicable if the IO module is set to segatools built-in (keyboard) or a compatible third-party module (like mu3io.NET)',
|
||||||
leverMode: 'Lever mode',
|
leverMode: 'Lever mode',
|
||||||
mouse: 'Mouse',
|
mouse: 'Mouse',
|
||||||
|
irTooltip:
|
||||||
|
'When playing on an actual keyboard, only bind ir1; leave the rest unbound',
|
||||||
},
|
},
|
||||||
wine: {
|
wine: {
|
||||||
prefix: 'Wine prefix',
|
prefix: 'Wine prefix',
|
||||||
|
@ -8,6 +8,8 @@ export default {
|
|||||||
next: 'Dalej',
|
next: 'Dalej',
|
||||||
skip: 'Pomiń',
|
skip: 'Pomiń',
|
||||||
close: 'Zamknij',
|
close: 'Zamknij',
|
||||||
|
by: 'od {namespace}',
|
||||||
|
updateAll: 'ZAKTUALIZUJ WSZYSTKO',
|
||||||
start: {
|
start: {
|
||||||
failed: 'Uruchomienie nie powiodło się',
|
failed: 'Uruchomienie nie powiodło się',
|
||||||
accept: 'Uruchom mimo to',
|
accept: 'Uruchom mimo to',
|
||||||
@ -43,8 +45,23 @@ export default {
|
|||||||
reallyDelete: 'Czy na pewno chcesz usunąć {profile}?',
|
reallyDelete: 'Czy na pewno chcesz usunąć {profile}?',
|
||||||
template: 'Szablon',
|
template: 'Szablon',
|
||||||
importTemplate: 'Importuj szablon',
|
importTemplate: 'Importuj szablon',
|
||||||
exportTemplate: 'Eksportuj szablon',
|
exportTemplate: 'Eksportuj profil',
|
||||||
export: 'Eksportuj',
|
export: 'Eksportuj',
|
||||||
|
standardExport: 'Szablon',
|
||||||
|
diagnostic: 'Diagnostyka',
|
||||||
|
},
|
||||||
|
creator: {
|
||||||
|
header: 'Kreator pakietów',
|
||||||
|
basic: 'Podstawowe informacje',
|
||||||
|
name: 'Nazwa',
|
||||||
|
description: 'Opis',
|
||||||
|
website: 'Strona internetowa',
|
||||||
|
type: 'Typ',
|
||||||
|
rainy: 'Standardowy',
|
||||||
|
segatools: 'Segatools',
|
||||||
|
native: 'Natywny',
|
||||||
|
games: 'Gry',
|
||||||
|
packageFormat: 'Specyfikacja formatu',
|
||||||
},
|
},
|
||||||
store: {
|
store: {
|
||||||
installRecommended: 'Dodaj zalecane pakiety',
|
installRecommended: 'Dodaj zalecane pakiety',
|
||||||
@ -53,10 +70,21 @@ export default {
|
|||||||
nsfw: 'Pokaż mityczny O.N.G.E.K.I. Sex Mod dlaczego ta opcja w ogóle tu jest',
|
nsfw: 'Pokaż mityczny O.N.G.E.K.I. Sex Mod dlaczego ta opcja w ogóle tu jest',
|
||||||
incompatible:
|
incompatible:
|
||||||
'Ten pakiet jest obecnie niekompatybilny ze STARTLINEREM.',
|
'Ten pakiet jest obecnie niekompatybilny ze STARTLINEREM.',
|
||||||
missing: 'Niedostępne',
|
|
||||||
includeCategories: 'Włącz kategorie',
|
includeCategories: 'Włącz kategorie',
|
||||||
excludeCategories: 'Wyłącz kategorie',
|
excludeCategories: 'Wyłącz kategorie',
|
||||||
},
|
},
|
||||||
|
pkglist: {
|
||||||
|
missing: 'Niedostępne',
|
||||||
|
local: 'Lokalne pakiety',
|
||||||
|
namespace: 'Po przestrzeni nazw',
|
||||||
|
type: 'Po typie',
|
||||||
|
category: 'Po kategorii',
|
||||||
|
standard: 'Standardowe mody',
|
||||||
|
native: 'Natywne mody',
|
||||||
|
segatools: 'segatools',
|
||||||
|
unsupported: 'Niewspierane',
|
||||||
|
exclusions: 'Czarna lista:',
|
||||||
|
},
|
||||||
patch: {
|
patch: {
|
||||||
loading: 'Wczytuję...',
|
loading: 'Wczytuję...',
|
||||||
noneFound:
|
noneFound:
|
||||||
@ -204,6 +232,8 @@ export default {
|
|||||||
'Dotyczy tylko wtedy, gdy moduł IO jest ustawiony na wbudowaną emulację lub zgodny moduł (np. mu3io.NET)',
|
'Dotyczy tylko wtedy, gdy moduł IO jest ustawiony na wbudowaną emulację lub zgodny moduł (np. mu3io.NET)',
|
||||||
leverMode: 'Tryb wajchy',
|
leverMode: 'Tryb wajchy',
|
||||||
mouse: 'Mysz',
|
mouse: 'Mysz',
|
||||||
|
irTooltip:
|
||||||
|
'Jeśli grasz na klawiaturze, ustaw tylko ir1; pozostałe zostaw wyłączone',
|
||||||
},
|
},
|
||||||
wine: {
|
wine: {
|
||||||
prefix: 'Wine prefix',
|
prefix: 'Wine prefix',
|
||||||
|
@ -189,7 +189,7 @@ export const usePkgStore = defineStore('pkg', {
|
|||||||
await this.reloadAll();
|
await this.reloadAll();
|
||||||
},
|
},
|
||||||
|
|
||||||
async install(pkg: Package | undefined) {
|
async install(pkg: Package | undefined, enable: boolean) {
|
||||||
if (pkg === undefined) {
|
if (pkg === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -198,6 +198,7 @@ export const usePkgStore = defineStore('pkg', {
|
|||||||
await invoke('install_package', {
|
await invoke('install_package', {
|
||||||
key: pkgKey(pkg),
|
key: pkgKey(pkg),
|
||||||
force: true,
|
force: true,
|
||||||
|
enable,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (pkg !== undefined) {
|
if (pkg !== undefined) {
|
||||||
@ -211,6 +212,7 @@ export const usePkgStore = defineStore('pkg', {
|
|||||||
await invoke('install_package', {
|
await invoke('install_package', {
|
||||||
key,
|
key,
|
||||||
force: true,
|
force: true,
|
||||||
|
enable: false,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@ -221,7 +223,7 @@ export const usePkgStore = defineStore('pkg', {
|
|||||||
const list = [];
|
const list = [];
|
||||||
for (const pkg of this.allLocal) {
|
for (const pkg of this.allLocal) {
|
||||||
if (pkg.rmt && pkg.rmt.version > pkg.loc!.version) {
|
if (pkg.rmt && pkg.rmt.version > pkg.loc!.version) {
|
||||||
list.push(this.install(pkg));
|
list.push(this.install(pkg, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Promise.all(list);
|
await Promise.all(list);
|
||||||
@ -375,6 +377,9 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
const onboarded: Ref<Game[]> = ref([]);
|
const onboarded: Ref<Game[]> = ref([]);
|
||||||
const locale: Ref<Locale> = ref('en');
|
const locale: Ref<Locale> = ref('en');
|
||||||
const currentTab: Ref<string> = ref('users');
|
const currentTab: Ref<string> = ref('users');
|
||||||
|
const pkgListMode: Ref<'namespace' | 'type' | 'category'> =
|
||||||
|
ref('namespace');
|
||||||
|
const hiddenCategories: Ref<string[]> = ref([]);
|
||||||
|
|
||||||
const _scaleValue = (value: ScaleType) =>
|
const _scaleValue = (value: ScaleType) =>
|
||||||
value === 's' ? 1 : value === 'm' ? 1.25 : value === 'l' ? 1.5 : 2;
|
value === 's' ? 1 : value === 'm' ? 1.25 : value === 'l' ? 1.5 : 2;
|
||||||
@ -445,6 +450,14 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
if (input.currentTab) {
|
if (input.currentTab) {
|
||||||
currentTab.value = input.currentTab;
|
currentTab.value = input.currentTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.pkgListMode) {
|
||||||
|
pkgListMode.value = input.pkgListMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.hiddenCategories) {
|
||||||
|
hiddenCategories.value = input.hiddenCategories;
|
||||||
|
}
|
||||||
await setLocale(locale.value);
|
await setLocale(locale.value);
|
||||||
await setTheme(theme.value);
|
await setTheme(theme.value);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -484,6 +497,8 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
onboarded: onboarded.value,
|
onboarded: onboarded.value,
|
||||||
locale: locale.value,
|
locale: locale.value,
|
||||||
currentTab: currentTab.value,
|
currentTab: currentTab.value,
|
||||||
|
pkgListMode: pkgListMode.value,
|
||||||
|
hiddenCategories: hiddenCategories.value,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -560,6 +575,8 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
timeout,
|
timeout,
|
||||||
scaleModel,
|
scaleModel,
|
||||||
currentTab,
|
currentTab,
|
||||||
|
pkgListMode,
|
||||||
|
hiddenCategories,
|
||||||
_scaleValue,
|
_scaleValue,
|
||||||
scaleValue,
|
scaleValue,
|
||||||
load,
|
load,
|
||||||
|
Reference in New Issue
Block a user