forked from akanyan/STARTLINER
feat: etc
This commit is contained in:
1
.prettierignore
Normal file
1
.prettierignore
Normal file
@ -0,0 +1 @@
|
||||
rust/**/*
|
@ -64,7 +64,8 @@ Arbitrary scripts are not supported by design and that will probably never chang
|
||||
- CHUNITHM support
|
||||
- segatools as a special package
|
||||
- Progress bars and other GUI sugar
|
||||
- Rebuilding the profile only when necessary
|
||||
- Search bar
|
||||
- Start check
|
||||
|
||||
### Endgame
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-deep-link": "~2",
|
||||
"@tauri-apps/plugin-dialog": "~2",
|
||||
"@tauri-apps/plugin-fs": "~2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-shell": "~2",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
|
1
rust/Cargo.lock
generated
1
rust/Cargo.lock
generated
@ -4341,6 +4341,7 @@ dependencies = [
|
||||
"tauri-build",
|
||||
"tauri-plugin-deep-link",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-fs",
|
||||
"tauri-plugin-opener",
|
||||
"tauri-plugin-shell",
|
||||
"tauri-plugin-single-instance",
|
||||
|
@ -39,6 +39,7 @@ async-std = "1.13.0"
|
||||
closure = "0.3.0"
|
||||
derive_more = { version = "2.0.1", features = ["display"] }
|
||||
junction = "1.2.0"
|
||||
tauri-plugin-fs = "2"
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
|
||||
|
@ -1,20 +1,18 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"opener:default",
|
||||
"core:window:allow-close",
|
||||
"core:app:allow-app-hide",
|
||||
"shell:default",
|
||||
"shell:default",
|
||||
"opener:default",
|
||||
"dialog:default",
|
||||
"dialog:default",
|
||||
"deep-link:default"
|
||||
]
|
||||
}
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"opener:default",
|
||||
"core:window:allow-close",
|
||||
"core:app:allow-app-hide",
|
||||
"shell:default",
|
||||
"dialog:default",
|
||||
"deep-link:default",
|
||||
"fs:default",
|
||||
"fs:allow-data-read-recursive",
|
||||
"fs:allow-data-write-recursive"
|
||||
]
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
|
||||
use crate::pkg::PkgKey;
|
||||
use crate::Profile;
|
||||
use crate::pkg_store::PackageStore;
|
||||
use crate::Profile;
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
pub struct AppData {
|
||||
pub profile: Option<Profile>,
|
||||
pub pkgs: PackageStore
|
||||
pub pkgs: PackageStore,
|
||||
}
|
||||
|
||||
impl AppData {
|
||||
@ -37,4 +39,14 @@ impl AppData {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sum_packages(&self, p: &Profile) -> String {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
for pkg in &p.mods {
|
||||
let x = self.pkgs.get(pkg).unwrap().loc.as_ref().unwrap();
|
||||
pkg.hash(&mut hasher);
|
||||
x.version.hash(&mut hasher);
|
||||
}
|
||||
hasher.finish().to_string()
|
||||
}
|
||||
}
|
||||
|
@ -20,16 +20,24 @@ pub async fn startline(app: AppHandle) -> Result<(), String> {
|
||||
let appd = state.lock().await;
|
||||
|
||||
if let Some(p) = &appd.profile {
|
||||
// TODO if p.needsUpdate
|
||||
liner::line_up(p).await.expect("Line-up failed");
|
||||
let hash = appd.sum_packages(p);
|
||||
liner::line_up(p, hash).await
|
||||
.map_err(|e| e.to_string())?;
|
||||
start::start(p, app_copy)
|
||||
.map_err(|e| { log::error!("Error launching: {}", e.to_string()); e.to_string() })
|
||||
//Ok(())
|
||||
.map_err(|e| e.to_string())
|
||||
} else {
|
||||
Err("No profile".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn kill() -> Result<(), String> {
|
||||
start::pkill("amdaemon.exe").await;
|
||||
// The start routine will kill the other process
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_package(state: State<'_, tokio::sync::Mutex<AppData>>, key: PkgKey) -> Result<InstallResult, String> {
|
||||
log::debug!("invoke: install_package({})", key);
|
||||
|
@ -107,7 +107,8 @@ pub async fn run(_args: Vec<String>) {
|
||||
cmd::init_profile,
|
||||
cmd::save_profile,
|
||||
cmd::startline,
|
||||
cmd::set_cfg
|
||||
cmd::kill,
|
||||
cmd::set_cfg,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
@ -17,9 +17,23 @@ async fn symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io::Resul
|
||||
junction::create(src, dst)
|
||||
}
|
||||
|
||||
pub async fn line_up(p: &Profile) -> Result<()> {
|
||||
let dir_out = util::profile_dir(&p);
|
||||
log::info!("Preparing {}", dir_out.to_string_lossy());
|
||||
pub async fn line_up(p: &Profile, pkg_hash: String) -> Result<()> {
|
||||
let hash_path = p.dir().join(".sl-state");
|
||||
let prev_hash = fs::read_to_string(&hash_path).await.unwrap_or_default();
|
||||
if prev_hash != pkg_hash {
|
||||
log::debug!("state {} -> {}", prev_hash, pkg_hash);
|
||||
fs::write(hash_path, pkg_hash).await
|
||||
.map_err(|e| anyhow!("Unable to write the state file: {}", e))?;
|
||||
prepare_packages(p).await?;
|
||||
}
|
||||
|
||||
prepare_config(p).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn prepare_packages(p: &Profile) -> Result<()> {
|
||||
let dir_out = p.dir();
|
||||
|
||||
let mut futures = JoinSet::new();
|
||||
if dir_out.join("BepInEx").exists() {
|
||||
@ -34,25 +48,29 @@ pub async fn line_up(p: &Profile) -> Result<()> {
|
||||
|
||||
for m in &p.mods {
|
||||
log::debug!("Preparing {}", m);
|
||||
let dir_out = util::profile_dir(&p);
|
||||
let (namespace, name) = m.0.split_at(m.0.find("-").expect("Invalid mod definition"));
|
||||
let bpx = util::pkg_dir_of(namespace, &name[1..])
|
||||
let bpx_dir = util::pkg_dir_of(namespace, &name[1..])
|
||||
.join("app")
|
||||
.join("BepInEx");
|
||||
if bpx.exists() {
|
||||
util::copy_recursive(&bpx, &dir_out.join("BepInEx"))?;
|
||||
if bpx_dir.exists() {
|
||||
util::copy_recursive(&bpx_dir, &dir_out.join("BepInEx"))?;
|
||||
}
|
||||
|
||||
let opt = util::pkg_dir_of(namespace, &name[1..]).join("option");
|
||||
if opt.exists() {
|
||||
let x = opt.read_dir().unwrap().next().unwrap()?;
|
||||
let opt_dir = util::pkg_dir_of(namespace, &name[1..]).join("option");
|
||||
if opt_dir.exists() {
|
||||
let x = opt_dir.read_dir().unwrap().next().unwrap()?;
|
||||
if x.metadata()?.is_dir() {
|
||||
symlink(&x.path(), &dir_out.join("option").join(x.file_name())).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Todo temporary
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn prepare_config(p: &Profile) -> Result<()> {
|
||||
let dir_out = p.dir();
|
||||
|
||||
let ini_in_raw = fs::read_to_string(p.exe_dir.join("segatools.ini")).await?;
|
||||
let ini_in = Ini::load_from_str(&ini_in_raw)?;
|
||||
let mut opt_dir_in = PathBuf::from(
|
||||
@ -78,7 +96,7 @@ pub async fn line_up(p: &Profile) -> Result<()> {
|
||||
util::path_to_str(dir_out.join("BepInEx").join("core").join("BepInEx.Preloader.dll"))?
|
||||
);
|
||||
|
||||
if prepare_aime(p).await.unwrap_or(false) {
|
||||
if p.get_bool("aime", false) {
|
||||
ini_out.with_section(Some("aime"))
|
||||
.set("enable", "1")
|
||||
.set("aimePath", util::path_to_str(dir_out.join("aime.txt"))?);
|
||||
@ -93,16 +111,4 @@ pub async fn line_up(p: &Profile) -> Result<()> {
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Todo multiple codes
|
||||
async fn prepare_aime(p: &Profile) -> Result<bool> {
|
||||
if p.get_bool("aime", true) {
|
||||
if let Some(code) = p.cfg.get("aime-code") {
|
||||
let code = code.as_str().expect("Invalid config");
|
||||
fs::write(util::profile_dir(&p).join("aime.txt"), code).await?;
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
@ -6,7 +6,7 @@ use tokio::fs;
|
||||
use crate::{model::{local, rainy}, util};
|
||||
|
||||
// {namespace}-{name}
|
||||
#[derive(Eq, Hash, PartialEq, Clone, Serialize, Deserialize, Display)]
|
||||
#[derive(Eq, Hash, PartialEq, PartialOrd, Ord, Clone, Serialize, Deserialize, Display)]
|
||||
pub struct PkgKey(pub String);
|
||||
|
||||
// {namespace}-{name}-{version}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use std::{collections::{HashMap, HashSet}, path::PathBuf};
|
||||
use anyhow::Result;
|
||||
use std::{collections::{BTreeSet, HashMap}, path::PathBuf};
|
||||
use crate::{model::misc, pkg::PkgKey, util};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
@ -11,7 +12,7 @@ pub struct Profile {
|
||||
pub game: misc::Game,
|
||||
pub exe_dir: PathBuf,
|
||||
pub name: String,
|
||||
pub mods: HashSet<PkgKey>,
|
||||
pub mods: BTreeSet<PkgKey>,
|
||||
pub wine_runtime: Option<PathBuf>,
|
||||
pub wine_prefix: Option<PathBuf>,
|
||||
// cfg is temporarily just a map to make iteration easier
|
||||
@ -25,7 +26,7 @@ impl Profile {
|
||||
game: misc::Game::Ongeki,
|
||||
exe_dir: exe_path.parent().unwrap().to_owned(),
|
||||
name: "ongeki-default".to_owned(),
|
||||
mods: HashSet::new(),
|
||||
mods: BTreeSet::new(),
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
wine_runtime: Some(std::path::Path::new("/usr/bin/wine").to_path_buf()),
|
||||
@ -45,6 +46,13 @@ impl Profile {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dir(&self) -> PathBuf {
|
||||
util::get_dirs()
|
||||
.data_dir()
|
||||
.join("profile-".to_owned() + &self.name)
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
pub fn load() -> Option<Profile> {
|
||||
let path = util::get_dirs()
|
||||
.config_dir()
|
||||
@ -65,6 +73,11 @@ impl Profile {
|
||||
log::info!("Written to {}", path.to_string_lossy());
|
||||
}
|
||||
|
||||
pub fn get_cfg(&self, key: &str) -> Result<&serde_json::Value> {
|
||||
self.cfg.get(key)
|
||||
.ok_or_else(|| anyhow::anyhow!("Invalid config entry {}", key))
|
||||
}
|
||||
|
||||
pub fn get_bool(&self, key: &str, default: bool) -> bool {
|
||||
self.cfg.get(key)
|
||||
.and_then(|c| c.as_bool())
|
||||
|
@ -1,50 +1,40 @@
|
||||
use anyhow::Result;
|
||||
use std::fs::File;
|
||||
use tokio::process::Command;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use std::process::Stdio;
|
||||
use crate::profile::Profile;
|
||||
use crate::util;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn start(p: &Profile, app: AppHandle) -> Result<()> {
|
||||
let p = p.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let rv = Command::new(p.wine_runtime.as_ref().unwrap())
|
||||
.env(
|
||||
"SEGATOOLS_CONFIG_PATH",
|
||||
util::profile_dir(&p).join("segatools.ini"),
|
||||
)
|
||||
.env("WINEPREFIX", p.wine_prefix.as_ref().unwrap())
|
||||
.arg(p.exe_dir.join("start.bat"))
|
||||
.spawn();
|
||||
match rv {
|
||||
Ok(mut child) => {
|
||||
_ = child.wait().await;
|
||||
log::debug!("Fin");
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Fail: {}", e);
|
||||
}
|
||||
}
|
||||
_ = app.emit("launch-end", "");
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
static CREATE_NO_WINDOW: i32 = 0x08000000;
|
||||
|
||||
pub fn start(p: &Profile, app: AppHandle) -> Result<()> {
|
||||
use std::process::Stdio;
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
let create_no_window = 0x08000000;
|
||||
|
||||
let ini_path = util::profile_dir(&p).join("segatools.ini");
|
||||
let ini_path = p.dir().join("segatools.ini");
|
||||
|
||||
log::debug!("With path {}", ini_path.to_string_lossy());
|
||||
log::info!("Launching amdaemon");
|
||||
|
||||
let mut amd_builder = Command::new("cmd.exe");
|
||||
let mut game_builder = Command::new(p.exe_dir.join("inject.exe"));
|
||||
let mut game_builder;
|
||||
let mut amd_builder;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
game_builder = Command::new(p.exe_dir.join("inject.exe"));
|
||||
amd_builder = Command::new("cmd.exe");
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let wine = p.wine_runtime.as_ref()
|
||||
.expect("No wine path specified");
|
||||
|
||||
game_builder = Command::new(wine);
|
||||
amd_builder = Command::new(wine);
|
||||
|
||||
game_builder.arg(p.exe_dir.join("inject.exe"));
|
||||
amd_builder.arg("cmd.exe");
|
||||
}
|
||||
|
||||
let display_mode = p.get_str("display-mode", "borderless");
|
||||
|
||||
@ -76,24 +66,38 @@ pub fn start(p: &Profile, app: AppHandle) -> Result<()> {
|
||||
game_builder.arg("-popupwindow");
|
||||
}
|
||||
|
||||
if !cfg!(debug_assertions) {
|
||||
amd_builder
|
||||
.creation_flags(create_no_window)
|
||||
// Obviously, this is a meme
|
||||
// Output will be handled properly at a later time
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let wineprefix = p.wine_prefix.as_ref()
|
||||
.expect("No wineprefix specified");
|
||||
amd_builder.env("WINEPREFIX", wineprefix);
|
||||
game_builder.env("WINEPREFIX", wineprefix);
|
||||
}
|
||||
|
||||
game_builder
|
||||
.creation_flags(create_no_window)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
|
||||
let amd_log = File::create(p.dir().join("amdaemon.log"))?;
|
||||
let game_log = File::create(p.dir().join("mu3.log"))?;
|
||||
|
||||
amd_builder
|
||||
.stdout(Stdio::from(amd_log));
|
||||
// do they use stderr?
|
||||
|
||||
game_builder
|
||||
.stdout(Stdio::from(game_log));
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
amd_builder.creation_flags(CREATE_NO_WINDOW);
|
||||
game_builder.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
|
||||
if p.get_bool("intel", false) == true {
|
||||
amd_builder.env("OPENSSL_ia32cap", ":~0x20000000");
|
||||
}
|
||||
|
||||
log::info!("Launching amdaemon: {:?}", amd_builder);
|
||||
log::info!("Launching mu3: {:?}", game_builder);
|
||||
|
||||
let mut amd = amd_builder.spawn()?;
|
||||
let mut game = game_builder.spawn()?;
|
||||
|
||||
@ -101,19 +105,24 @@ pub fn start(p: &Profile, app: AppHandle) -> Result<()> {
|
||||
let mut set = JoinSet::new();
|
||||
|
||||
set.spawn(async move {
|
||||
amd.wait().await.expect("amdaemon failed to run")
|
||||
(amd.wait().await.expect("amdaemon failed to run"), "amdaemon.exe")
|
||||
});
|
||||
|
||||
set.spawn(async move {
|
||||
game.wait().await.expect("mu3 failed to run")
|
||||
(game.wait().await.expect("mu3 failed to run"), "mu3.exe")
|
||||
});
|
||||
|
||||
let res = set.join_next().await.expect("No spawn").expect("No result");
|
||||
_ = app.emit("launch-start", "");
|
||||
|
||||
log::info!("One of the processes died with return code {}", res);
|
||||
let (rc, process_name) = set.join_next().await.expect("No spawn").expect("No result");
|
||||
|
||||
_ = Command::new("taskkill.exe").arg("/f").arg("/im").arg("amdaemon.exe").creation_flags(create_no_window).output().await;
|
||||
_ = Command::new("taskkill.exe").arg("/f").arg("/im").arg("mu3.exe").creation_flags(create_no_window).output().await;
|
||||
log::info!("{} died with return code {}", process_name, rc);
|
||||
|
||||
if process_name == "amdaemon.exe" {
|
||||
pkill("mu3.exe").await;
|
||||
} else {
|
||||
pkill("amdaemon.exe").await;
|
||||
}
|
||||
|
||||
set.join_next().await.expect("No spawn").expect("No result");
|
||||
|
||||
@ -123,4 +132,16 @@ pub fn start(p: &Profile, app: AppHandle) -> Result<()> {
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub async fn pkill(process_name: &str) {
|
||||
_ = Command::new("taskkill.exe").arg("/f").arg("/im").arg(process_name)
|
||||
.creation_flags(CREATE_NO_WINDOW).output().await;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn pkill(process_name: &str) {
|
||||
_ = Command::new("pkill").arg(process_name)
|
||||
.output().await;
|
||||
}
|
@ -2,8 +2,6 @@ use anyhow::{anyhow, Result};
|
||||
use directories::ProjectDirs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::profile::Profile;
|
||||
|
||||
pub fn get_dirs() -> ProjectDirs {
|
||||
ProjectDirs::from("org", "7EVENDAYSHOLIDAYS", "STARTLINER")
|
||||
.expect("Unable to set up config directories")
|
||||
@ -21,13 +19,6 @@ pub fn pkg_dir_of(namespace: &str, name: &str) -> PathBuf {
|
||||
pkg_dir().join(format!("{}-{}", namespace, name)).to_owned()
|
||||
}
|
||||
|
||||
pub fn profile_dir(p: &Profile) -> PathBuf {
|
||||
get_dirs()
|
||||
.data_dir()
|
||||
.join("profile-".to_owned() + &p.name)
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
pub fn cache_dir() -> PathBuf {
|
||||
get_dirs().cache_dir().to_owned()
|
||||
}
|
||||
|
@ -6,13 +6,12 @@ import TabList from 'primevue/tablist';
|
||||
import TabPanel from 'primevue/tabpanel';
|
||||
import TabPanels from 'primevue/tabpanels';
|
||||
import Tabs from 'primevue/tabs';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { onOpenUrl } from '@tauri-apps/plugin-deep-link';
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import ModList from './ModList.vue';
|
||||
import ModStore from './ModStore.vue';
|
||||
import Options from './Options.vue';
|
||||
import StartButton from './StartButton.vue';
|
||||
import { usePkgStore } from '../stores';
|
||||
import { changePrimaryColor } from '../util';
|
||||
|
||||
@ -20,7 +19,6 @@ const store = usePkgStore();
|
||||
store.setupListeners();
|
||||
|
||||
const currentTab = ref('3');
|
||||
const startEnabled = ref(false);
|
||||
|
||||
const loadProfile = async (openWindow: boolean) => {
|
||||
await store.reloadProfile();
|
||||
@ -42,7 +40,6 @@ const loadProfile = async (openWindow: boolean) => {
|
||||
}
|
||||
if (store.profile !== null) {
|
||||
changePrimaryColor(store.profile.game);
|
||||
startEnabled.value = true;
|
||||
currentTab.value = '0';
|
||||
}
|
||||
|
||||
@ -51,11 +48,6 @@ const loadProfile = async (openWindow: boolean) => {
|
||||
|
||||
const isProfileDisabled = computed(() => store.profile === null);
|
||||
|
||||
const startline = async () => {
|
||||
startEnabled.value = false;
|
||||
await invoke('startline');
|
||||
};
|
||||
|
||||
onOpenUrl((urls) => {
|
||||
console.log('deep link:', urls);
|
||||
});
|
||||
@ -63,10 +55,6 @@ onOpenUrl((urls) => {
|
||||
onMounted(async () => {
|
||||
await loadProfile(false);
|
||||
});
|
||||
|
||||
listen('launch-end', () => {
|
||||
startEnabled.value = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -87,15 +75,7 @@ listen('launch-end', () => {
|
||||
><div class="pi pi-question-circle"></div
|
||||
></Tab>
|
||||
<div class="grow"></div>
|
||||
<Button
|
||||
:disabled="!startEnabled"
|
||||
icon="pi pi-play"
|
||||
label="START"
|
||||
aria-label="start"
|
||||
size="small"
|
||||
class="m-2.5"
|
||||
@click="startline()"
|
||||
/>
|
||||
<StartButton />
|
||||
</TabList>
|
||||
</div>
|
||||
<TabPanels class="w-full grow mt-[3rem]">
|
||||
|
@ -1,10 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import Fieldset from 'primevue/fieldset';
|
||||
import InputNumber from 'primevue/inputnumber';
|
||||
import InputText from 'primevue/inputtext';
|
||||
import RadioButton from 'primevue/radiobutton';
|
||||
import Toggle from 'primevue/toggleswitch';
|
||||
import * as path from '@tauri-apps/api/path';
|
||||
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
|
||||
import { usePkgStore } from '../stores';
|
||||
|
||||
const store = usePkgStore();
|
||||
@ -23,7 +25,32 @@ const cfgRezW = _cfg('rez-w', 1080);
|
||||
const cfgRezH = _cfg('rez-h', 1920);
|
||||
const cfgDisplayMode = _cfg('display-mode', 'borderless');
|
||||
const cfgAime = _cfg('aime', false);
|
||||
const cfgAimeCode = _cfg('aime-code', '');
|
||||
|
||||
const aimeCode = ref('');
|
||||
// temp
|
||||
let aimePath = '';
|
||||
|
||||
(async () => {
|
||||
aimePath = await path.join(
|
||||
await path.dataDir(),
|
||||
'startliner/profile-ongeki-default/aime.txt'
|
||||
);
|
||||
aimeCode.value = await readTextFile(aimePath);
|
||||
})();
|
||||
|
||||
path.homeDir().then(console.log);
|
||||
|
||||
const aimeCodeModel = computed({
|
||||
get() {
|
||||
return aimeCode.value;
|
||||
},
|
||||
async set(value: string) {
|
||||
aimeCode.value = value;
|
||||
if (value.match(/^[0-9]{20}$/)) {
|
||||
await writeTextFile(aimePath, aimeCode.value);
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -98,13 +125,9 @@ const cfgAimeCode = _cfg('aime-code', '');
|
||||
class="shrink"
|
||||
size="small"
|
||||
:disabled="store.cfg('aime') !== true"
|
||||
:invalid="
|
||||
store.cfg('aime') === true &&
|
||||
store.cfg('aime-code')?.toString().length !== 20
|
||||
"
|
||||
:maxlength="20"
|
||||
placeholder="00000000000000000000"
|
||||
v-model="cfgAimeCode"
|
||||
v-model="aimeCodeModel"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
59
src/components/StartButton.vue
Normal file
59
src/components/StartButton.vue
Normal file
@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { Ref, ref } from 'vue';
|
||||
import Button from 'primevue/button';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
|
||||
type StartStatus = 'ready' | 'preparing' | 'running';
|
||||
const startStatus: Ref<StartStatus> = ref('ready');
|
||||
|
||||
const startline = async () => {
|
||||
startStatus.value = 'preparing';
|
||||
await invoke('startline');
|
||||
};
|
||||
|
||||
const kill = async () => {
|
||||
await invoke('kill');
|
||||
startStatus.value = 'ready';
|
||||
};
|
||||
|
||||
listen('launch-start', () => {
|
||||
startStatus.value = 'running';
|
||||
});
|
||||
|
||||
listen('launch-end', () => {
|
||||
startStatus.value = 'ready';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button
|
||||
v-if="startStatus === 'ready'"
|
||||
:disabled="false"
|
||||
icon="pi pi-play"
|
||||
label="START"
|
||||
aria-label="start"
|
||||
size="small"
|
||||
class="m-2.5"
|
||||
@click="startline()"
|
||||
/>
|
||||
<Button
|
||||
v-else-if="startStatus === 'preparing'"
|
||||
disabled
|
||||
icon="pi pi-spin pi-spinner"
|
||||
label="START"
|
||||
aria-label="start"
|
||||
size="small"
|
||||
class="m-2.5"
|
||||
/>
|
||||
<Button
|
||||
v-else
|
||||
:disabled="false"
|
||||
icon="pi pi-ban"
|
||||
label="STOP"
|
||||
aria-label="stop"
|
||||
size="small"
|
||||
class="m-2.5"
|
||||
@click="kill()"
|
||||
/>
|
||||
</template>
|
Reference in New Issue
Block a user