forked from akanyan/STARTLINER
feat: amnet integration
This commit is contained in:
1
rust/Cargo.lock
generated
1
rust/Cargo.lock
generated
@ -4567,6 +4567,7 @@ dependencies = [
|
|||||||
"derive_more 2.0.1",
|
"derive_more 2.0.1",
|
||||||
"directories",
|
"directories",
|
||||||
"displayz",
|
"displayz",
|
||||||
|
"enumflags2",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures",
|
"futures",
|
||||||
"junction",
|
"junction",
|
||||||
|
@ -41,6 +41,7 @@ derive_more = { version = "2.0.1", features = ["display"] }
|
|||||||
junction = "1.2.0"
|
junction = "1.2.0"
|
||||||
tauri-plugin-fs = "2"
|
tauri-plugin-fs = "2"
|
||||||
yaml-rust2 = "0.10.0"
|
yaml-rust2 = "0.10.0"
|
||||||
|
enumflags2 = { version = "0.7.11", features = ["serde"] }
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
tauri-plugin-cli = "2"
|
tauri-plugin-cli = "2"
|
||||||
|
@ -2,16 +2,38 @@ use std::path::PathBuf;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::pkg::PkgKey;
|
use crate::pkg::PkgKey;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
|
pub enum Aime {
|
||||||
|
BuiltIn,
|
||||||
|
AMNet(PkgKey),
|
||||||
|
Other(PkgKey),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
|
pub struct AMNet {
|
||||||
|
pub name: String,
|
||||||
|
pub addr: String,
|
||||||
|
pub physical: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AMNet {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { name: Default::default(), addr: "http://+:6070".to_string(), physical: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
pub struct Segatools {
|
pub struct Segatools {
|
||||||
pub target: PathBuf,
|
pub target: PathBuf,
|
||||||
pub hook: Option<PkgKey>,
|
pub hook: Option<PkgKey>,
|
||||||
pub io: Option<PkgKey>,
|
pub io: Option<PkgKey>,
|
||||||
|
pub aime: Option<Aime>,
|
||||||
pub amfs: PathBuf,
|
pub amfs: PathBuf,
|
||||||
pub option: PathBuf,
|
pub option: PathBuf,
|
||||||
pub appdata: PathBuf,
|
pub appdata: PathBuf,
|
||||||
pub enable_aime: bool,
|
|
||||||
pub intel: bool,
|
pub intel: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub amnet: AMNet,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Segatools {
|
impl Default for Segatools {
|
||||||
@ -23,8 +45,9 @@ impl Default for Segatools {
|
|||||||
amfs: PathBuf::default(),
|
amfs: PathBuf::default(),
|
||||||
option: PathBuf::default(),
|
option: PathBuf::default(),
|
||||||
appdata: PathBuf::from("appdata"),
|
appdata: PathBuf::from("appdata"),
|
||||||
enable_aime: false,
|
aime: Some(Aime::BuiltIn),
|
||||||
intel: false
|
intel: false,
|
||||||
|
amnet: AMNet::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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::{profile::Segatools, segatools_base::segatools_base}, profiles::ProfilePaths, util::{self, PathStr}};
|
use crate::{model::{profile::{Aime, Segatools}, segatools_base::segatools_base}, profiles::ProfilePaths, util::{self, PathStr}};
|
||||||
|
|
||||||
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> {
|
||||||
@ -50,10 +50,30 @@ impl Segatools {
|
|||||||
pfx_dir.join("BepInEx").join("core").join("BepInEx.Preloader.dll").stringify()?
|
pfx_dir.join("BepInEx").join("core").join("BepInEx.Preloader.dll").stringify()?
|
||||||
);
|
);
|
||||||
|
|
||||||
if self.enable_aime {
|
if let Some(aime) = &self.aime {
|
||||||
ini_out.with_section(Some("aime"))
|
ini_out.with_section(Some("aime"))
|
||||||
.set("enable", "1")
|
.set("enable", "1")
|
||||||
.set("aimePath", p.config_dir().join("aime.txt").stringify()?);
|
.set("aimePath", p.config_dir().join("aime.txt").stringify()?);
|
||||||
|
if let Aime::AMNet(key) = aime {
|
||||||
|
let mut aimeio = ini_out.with_section(Some("aimeio"));
|
||||||
|
aimeio
|
||||||
|
.set("path", util::pkg_dir().join(key.to_string()).join("segatools").join("aimeio.dll").stringify()?)
|
||||||
|
.set("gameId", "SDDT")
|
||||||
|
.set("serverAddress", &self.amnet.addr)
|
||||||
|
.set("useAimeDBForPhysicalCards", if self.amnet.physical { "1" } else { "0" })
|
||||||
|
.set("enableKeyboardMode", "0");
|
||||||
|
|
||||||
|
if let Ok(keyboard_code) = std::fs::read_to_string(p.config_dir().join("aime.txt")) {
|
||||||
|
log::debug!("{} {}", keyboard_code, keyboard_code.len());
|
||||||
|
if keyboard_code.len() == 20 {
|
||||||
|
aimeio.set("enableKeyboardMode", "1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.amnet.name.len() > 0 {
|
||||||
|
aimeio.set("serverName", &self.amnet.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ini_out.with_section(Some("aime"))
|
ini_out.with_section(Some("aime"))
|
||||||
.set("enable", "0");
|
.set("enable", "0");
|
||||||
|
@ -3,6 +3,7 @@ use derive_more::Display;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{collections::BTreeSet, path::{Path, PathBuf}};
|
use std::{collections::BTreeSet, path::{Path, PathBuf}};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
use enumflags2::{bitflags, BitFlags};
|
||||||
use crate::{model::{local::{self, PackageManifest}, rainy}, util};
|
use crate::{model::{local::{self, PackageManifest}, rainy}, util};
|
||||||
|
|
||||||
// {namespace}-{name}
|
// {namespace}-{name}
|
||||||
@ -24,25 +25,33 @@ pub struct Package {
|
|||||||
pub rmt: Option<Remote>
|
pub rmt: Option<Remote>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum Kind {
|
pub enum Status {
|
||||||
Unchecked,
|
Unchecked,
|
||||||
Unsupported,
|
Unsupported,
|
||||||
#[default] Mod,
|
OK(BitFlags<Feature>)
|
||||||
Hook,
|
|
||||||
IO,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Serialize, Deserialize)]
|
#[bitflags]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum Feature {
|
||||||
|
Hook,
|
||||||
|
GameIO,
|
||||||
|
Aime,
|
||||||
|
AMNet,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct Local {
|
pub struct Local {
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub dependencies: BTreeSet<PkgKey>,
|
pub dependencies: BTreeSet<PkgKey>,
|
||||||
pub kind: Kind
|
pub status: Status,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct Remote {
|
pub struct Remote {
|
||||||
pub version: String,
|
pub version: String,
|
||||||
@ -90,7 +99,7 @@ impl Package {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
let kind = Self::parse_kind(&mft);
|
let status = Self::parse_status(&mft);
|
||||||
let dependencies = Self::sanitize_deps(mft.dependencies);
|
let dependencies = Self::sanitize_deps(mft.dependencies);
|
||||||
|
|
||||||
Ok(Package {
|
Ok(Package {
|
||||||
@ -101,7 +110,7 @@ impl Package {
|
|||||||
loc: Some(Local {
|
loc: Some(Local {
|
||||||
version: mft.version_number,
|
version: mft.version_number,
|
||||||
path: dir.to_owned(),
|
path: dir.to_owned(),
|
||||||
kind,
|
status,
|
||||||
dependencies
|
dependencies
|
||||||
}),
|
}),
|
||||||
rmt: None
|
rmt: None
|
||||||
@ -174,24 +183,31 @@ impl Package {
|
|||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_kind(mft: &PackageManifest) -> Kind {
|
fn parse_status(mft: &PackageManifest) -> Status {
|
||||||
if mft.installers.len() == 0 {
|
if mft.installers.len() == 0 {
|
||||||
return Kind::Mod;//Unchecked
|
return Status::OK(BitFlags::default());//Unchecked
|
||||||
} else if mft.installers.len() == 1 {
|
} else if mft.installers.len() == 1 {
|
||||||
if let Some(serde_json::Value::String(id)) = &mft.installers[0].get("identifier") {
|
if let Some(serde_json::Value::String(id)) = &mft.installers[0].get("identifier") {
|
||||||
if id == "rainycolor" {
|
if id == "rainycolor" {
|
||||||
return Kind::Mod
|
return Status::OK(BitFlags::default());
|
||||||
} else if id == "segatools" {
|
} else if id == "segatools" {
|
||||||
|
// Multiple features in the same dll (yubideck etc.) should be supported at some point
|
||||||
|
let mut flags = BitFlags::default();
|
||||||
if let Some(serde_json::Value::String(module)) = mft.installers[0].get("module") {
|
if let Some(serde_json::Value::String(module)) = mft.installers[0].get("module") {
|
||||||
if module.ends_with("hook") {
|
if module.ends_with("hook") {
|
||||||
return Kind::Hook;
|
flags |= Feature::Hook;
|
||||||
|
} else if module == "amnet" {
|
||||||
|
flags |= Feature::AMNet | Feature::Aime;
|
||||||
|
} else if module == "aimeio" {
|
||||||
|
flags |= Feature::Aime;
|
||||||
} else if module.ends_with("io") {
|
} else if module.ends_with("io") {
|
||||||
return Kind::IO;
|
flags |= Feature::GameIO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Status::OK(flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Kind::Unsupported
|
Status::Unsupported
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -58,11 +58,19 @@ const iconSrc = () => {
|
|||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="pkg?.loc?.kind === 'IO'"
|
v-if="pkg?.loc?.kind === 'GameIO'"
|
||||||
v-tooltip="'IO'"
|
v-tooltip="'IO'"
|
||||||
class="pi pi-wrench ml-1 text-green-400"
|
class="pi pi-wrench ml-1 text-green-400"
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="
|
||||||
|
pkg?.loc?.kind === 'AMNet' || pkg?.loc?.kind === 'AimeOther'
|
||||||
|
"
|
||||||
|
v-tooltip="'Aime'"
|
||||||
|
class="pi pi-wrench ml-1 text-purple-400"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="showNamespace && pkg?.namespace"
|
v-if="showNamespace && pkg?.namespace"
|
||||||
class="text-sm opacity-75"
|
class="text-sm opacity-75"
|
||||||
|
@ -13,9 +13,10 @@ import OptionCategory from './OptionCategory.vue';
|
|||||||
import OptionRow from './OptionRow.vue';
|
import OptionRow from './OptionRow.vue';
|
||||||
import { invoke } from '../invoke';
|
import { invoke } from '../invoke';
|
||||||
import { usePkgStore, usePrfStore } from '../stores';
|
import { usePkgStore, usePrfStore } from '../stores';
|
||||||
import { pkgKey } from '../util';
|
import { Feature } from '../types';
|
||||||
|
import { hasFeature, pkgKey } from '../util';
|
||||||
|
|
||||||
const pkg = usePkgStore();
|
const pkgs = usePkgStore();
|
||||||
const prf = usePrfStore();
|
const prf = usePrfStore();
|
||||||
|
|
||||||
const aimeCode = ref('');
|
const aimeCode = ref('');
|
||||||
@ -53,7 +54,7 @@ const aimeCodeModel = computed({
|
|||||||
},
|
},
|
||||||
async set(value: string) {
|
async set(value: string) {
|
||||||
aimeCode.value = value;
|
aimeCode.value = value;
|
||||||
if (value.match(/^[0-9]{20}$/)) {
|
if (value.match(/^[0-9]{20}$/) || value.length === 0) {
|
||||||
const aime_path = await path.join(await prf.configDir, 'aime.txt');
|
const aime_path = await path.join(await prf.configDir, 'aime.txt');
|
||||||
await writeTextFile(aime_path, aimeCode.value);
|
await writeTextFile(aime_path, aimeCode.value);
|
||||||
}
|
}
|
||||||
@ -111,7 +112,7 @@ const extraDisplayOptionsDisabled = computed(() => {
|
|||||||
<Select
|
<Select
|
||||||
v-model="prf.current!.sgt.hook"
|
v-model="prf.current!.sgt.hook"
|
||||||
:options="
|
:options="
|
||||||
pkg.hooks.map((p) => {
|
pkgs.hooks.map((p) => {
|
||||||
return { title: pkgKey(p), value: pkgKey(p) };
|
return { title: pkgKey(p), value: pkgKey(p) };
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
@ -125,7 +126,7 @@ const extraDisplayOptionsDisabled = computed(() => {
|
|||||||
placeholder="segatools built-in"
|
placeholder="segatools built-in"
|
||||||
:options="[
|
:options="[
|
||||||
{ title: 'segatools built-in', value: null },
|
{ title: 'segatools built-in', value: null },
|
||||||
...pkg.ios.map((p) => {
|
...pkgs.gameIOs.map((p) => {
|
||||||
return { title: pkgKey(p), value: pkgKey(p) };
|
return { title: pkgKey(p), value: pkgKey(p) };
|
||||||
}),
|
}),
|
||||||
]"
|
]"
|
||||||
@ -299,23 +300,65 @@ const extraDisplayOptionsDisabled = computed(() => {
|
|||||||
/>
|
/>
|
||||||
</OptionRow>
|
</OptionRow>
|
||||||
</OptionCategory>
|
</OptionCategory>
|
||||||
<OptionCategory title="Misc">
|
<OptionCategory title="Aime">
|
||||||
<OptionRow title="OpenSSL bug workaround for Intel ≥10th gen">
|
|
||||||
<ToggleSwitch v-model="prf.current!.sgt.intel" />
|
|
||||||
</OptionRow>
|
|
||||||
<OptionRow title="Aime emulation">
|
<OptionRow title="Aime emulation">
|
||||||
<ToggleSwitch v-model="prf.current!.sgt.enable_aime" />
|
<Select
|
||||||
|
v-model="prf.current!.sgt.aime"
|
||||||
|
:options="[
|
||||||
|
{ title: 'disabled', value: null },
|
||||||
|
{ title: 'segatools built-in', value: 'BuiltIn' },
|
||||||
|
...pkgs.aimes.map((p) => {
|
||||||
|
return {
|
||||||
|
title: pkgKey(p),
|
||||||
|
value: hasFeature(p, Feature.AMNet)
|
||||||
|
? { AMNet: pkgKey(p) }
|
||||||
|
: { Other: pkgKey(p) },
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
]"
|
||||||
|
placeholder="none"
|
||||||
|
option-label="title"
|
||||||
|
option-value="value"
|
||||||
|
></Select>
|
||||||
</OptionRow>
|
</OptionRow>
|
||||||
<OptionRow title="Aime code">
|
<OptionRow title="Aime code">
|
||||||
<InputText
|
<InputText
|
||||||
class="shrink"
|
class="shrink"
|
||||||
size="small"
|
size="small"
|
||||||
:disabled="prf.current?.sgt.enable_aime !== true"
|
:disabled="prf.current!.sgt.aime === null"
|
||||||
:maxlength="20"
|
:maxlength="20"
|
||||||
placeholder="00000000000000000000"
|
placeholder="00000000000000000000"
|
||||||
v-model="aimeCodeModel"
|
v-model="aimeCodeModel"
|
||||||
/>
|
/>
|
||||||
</OptionRow>
|
</OptionRow>
|
||||||
|
<div v-if="prf.current!.sgt.aime?.hasOwnProperty('AMNet')">
|
||||||
|
<OptionRow title="Server name">
|
||||||
|
<InputText
|
||||||
|
class="shrink"
|
||||||
|
size="small"
|
||||||
|
placeholder="CHUNI-PENGUIN"
|
||||||
|
:maxlength="50"
|
||||||
|
v-model="prf.current!.sgt.amnet.name"
|
||||||
|
/>
|
||||||
|
</OptionRow>
|
||||||
|
<OptionRow title="Server address">
|
||||||
|
<InputText
|
||||||
|
class="shrink"
|
||||||
|
size="small"
|
||||||
|
placeholder="http://+:6070"
|
||||||
|
:maxlength="50"
|
||||||
|
v-model="prf.current!.sgt.amnet.addr"
|
||||||
|
/>
|
||||||
|
</OptionRow>
|
||||||
|
<OptionRow title="Use AiMeDB for physical cards">
|
||||||
|
<ToggleSwitch v-model="prf.current!.sgt.amnet.physical" />
|
||||||
|
</OptionRow>
|
||||||
|
</div>
|
||||||
|
</OptionCategory>
|
||||||
|
<OptionCategory title="Misc">
|
||||||
|
<OptionRow title="OpenSSL bug workaround for Intel ≥10th gen">
|
||||||
|
<ToggleSwitch v-model="prf.current!.sgt.intel" />
|
||||||
|
</OptionRow>
|
||||||
<OptionRow title="More segatools options">
|
<OptionRow title="More segatools options">
|
||||||
<FileEditor filename="segatools-base.ini" />
|
<FileEditor filename="segatools-base.ini" />
|
||||||
</OptionRow>
|
</OptionRow>
|
||||||
|
@ -3,8 +3,8 @@ import { defineStore } from 'pinia';
|
|||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
import * as path from '@tauri-apps/api/path';
|
import * as path from '@tauri-apps/api/path';
|
||||||
import { invoke, invoke_nopopup } from './invoke';
|
import { invoke, invoke_nopopup } from './invoke';
|
||||||
import { Dirs, Game, Package, Profile, ProfileMeta } from './types';
|
import { Dirs, Feature, Game, Package, Profile, ProfileMeta } from './types';
|
||||||
import { changePrimaryColor, pkgKey } from './util';
|
import { changePrimaryColor, hasFeature, pkgKey } from './util';
|
||||||
|
|
||||||
type InstallStatus = {
|
type InstallStatus = {
|
||||||
pkg: string;
|
pkg: string;
|
||||||
@ -101,9 +101,13 @@ export const usePkgStore = defineStore('pkg', {
|
|||||||
))
|
))
|
||||||
),
|
),
|
||||||
hooks: (state) =>
|
hooks: (state) =>
|
||||||
Object.values(state.pkg).filter((p) => p.loc?.kind === 'Hook'),
|
Object.values(state.pkg).filter((p) => hasFeature(p, Feature.Hook)),
|
||||||
ios: (state) =>
|
gameIOs: (state) =>
|
||||||
Object.values(state.pkg).filter((p) => p.loc?.kind === 'IO'),
|
Object.values(state.pkg).filter((p) =>
|
||||||
|
hasFeature(p, Feature.GameIO)
|
||||||
|
),
|
||||||
|
aimes: (state) =>
|
||||||
|
Object.values(state.pkg).filter((p) => hasFeature(p, Feature.Aime)),
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setupListeners() {
|
setupListeners() {
|
||||||
|
23
src/types.ts
23
src/types.ts
@ -7,7 +7,7 @@ export interface Package {
|
|||||||
version: string;
|
version: string;
|
||||||
path: string;
|
path: string;
|
||||||
dependencies: string[];
|
dependencies: string[];
|
||||||
kind: 'Unchecked' | 'Unsupported' | 'Mod' | 'Hook' | 'IO';
|
status: Status;
|
||||||
} | null;
|
} | null;
|
||||||
rmt: {
|
rmt: {
|
||||||
version: string;
|
version: string;
|
||||||
@ -22,6 +22,20 @@ export interface Package {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum Feature {
|
||||||
|
Hook = 0b00001,
|
||||||
|
GameIO = 0b00010,
|
||||||
|
Aime = 0b00100,
|
||||||
|
AMNet = 0b01000,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Status =
|
||||||
|
| 'Unchecked'
|
||||||
|
| 'Unsupported'
|
||||||
|
| {
|
||||||
|
OK: Feature;
|
||||||
|
};
|
||||||
|
|
||||||
export type Game = 'ongeki' | 'chunithm';
|
export type Game = 'ongeki' | 'chunithm';
|
||||||
|
|
||||||
export interface ProfileMeta {
|
export interface ProfileMeta {
|
||||||
@ -36,8 +50,13 @@ export interface SegatoolsConfig {
|
|||||||
amfs: string;
|
amfs: string;
|
||||||
option: string;
|
option: string;
|
||||||
appdata: string;
|
appdata: string;
|
||||||
enable_aime: boolean;
|
aime: { AMNet: string } | { Other: string } | null;
|
||||||
intel: boolean;
|
intel: boolean;
|
||||||
|
amnet: {
|
||||||
|
name: string;
|
||||||
|
addr: string;
|
||||||
|
physical: boolean;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DisplayConfig {
|
export interface DisplayConfig {
|
||||||
|
10
src/util.ts
10
src/util.ts
@ -1,5 +1,5 @@
|
|||||||
import { updatePrimaryPalette } from '@primevue/themes';
|
import { updatePrimaryPalette } from '@primevue/themes';
|
||||||
import { Game, Package } from './types';
|
import { Feature, Game, Package } from './types';
|
||||||
|
|
||||||
export const changePrimaryColor = (game: Game | null) => {
|
export const changePrimaryColor = (game: Game | null) => {
|
||||||
const color =
|
const color =
|
||||||
@ -45,3 +45,11 @@ export const needsUpdate = (pkg: Package | undefined) => {
|
|||||||
}
|
}
|
||||||
return l1 < r1;
|
return l1 < r1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const hasFeature = (pkg: Package, feature: Feature) => {
|
||||||
|
return (
|
||||||
|
pkg.loc !== null &&
|
||||||
|
typeof pkg.loc?.status !== 'string' &&
|
||||||
|
pkg.loc.status.OK & feature
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user