mirror of
https://github.com/LCE-Hub/LCE-Emerald-Launcher.git
synced 2026-09-24 17:11:00 +00:00
feat: initial goldmapper support
This commit is contained in:
@@ -5,6 +5,11 @@ use crate::types::{AppConfig, ThemePalette};
|
||||
use crate::util;
|
||||
#[tauri::command]
|
||||
pub fn save_config(app: AppHandle, config_val: AppConfig) {
|
||||
let mut config_val = config_val;
|
||||
if config_val.goldmapper_mappings.is_none() {
|
||||
let existing = config::load_config_raw(app.clone());
|
||||
config_val.goldmapper_mappings = existing.goldmapper_mappings;
|
||||
}
|
||||
config::save_config_raw(&app, &config_val);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use tauri_plugin_opener::OpenerExt;
|
||||
use tokio::io::AsyncRead;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::sync::Mutex;
|
||||
use crate::commands::goldmapper;
|
||||
use crate::commands::runners;
|
||||
use crate::config;
|
||||
use crate::types::AppConfig;
|
||||
@@ -139,8 +140,14 @@ async fn launch_game_desktop(
|
||||
all_args.push(a.to_string());
|
||||
}
|
||||
all_args.extend(args.clone());
|
||||
all_args.push(game_exe.to_string_lossy().to_string());
|
||||
all_args.extend(extra_args.clone());
|
||||
let goldmapper_args =
|
||||
goldmapper::build_launch_args(&app, &config_val, &game_exe, &extra_args)?;
|
||||
if let Some(gm_args) = goldmapper_args {
|
||||
all_args.extend(gm_args);
|
||||
} else {
|
||||
all_args.push(game_exe.to_string_lossy().to_string());
|
||||
all_args.extend(extra_args.clone());
|
||||
}
|
||||
let (final_prog, final_args) = apply_launch_prefix(prog, all_args, &config_val);
|
||||
let mut cmd = tokio::process::Command::new(&final_prog);
|
||||
for a in &final_args {
|
||||
@@ -228,7 +235,13 @@ async fn launch_game_desktop(
|
||||
])
|
||||
};
|
||||
|
||||
mac_args.extend(extra_args.clone());
|
||||
let goldmapper_args =
|
||||
goldmapper::build_launch_args(&app, &config_val, &game_exe, &extra_args)?;
|
||||
if let Some(gm_args) = goldmapper_args {
|
||||
mac_args.extend(gm_args);
|
||||
} else {
|
||||
mac_args.extend(extra_args.clone());
|
||||
}
|
||||
|
||||
let (final_prog, final_args) = apply_launch_prefix(&mac_prog, mac_args, &config_val);
|
||||
let mut cmd = tokio::process::Command::new(&final_prog);
|
||||
@@ -289,13 +302,22 @@ async fn launch_game_desktop(
|
||||
not(target_os = "android")
|
||||
))]
|
||||
{
|
||||
let exe_str = game_exe.to_string_lossy().to_string();
|
||||
let all_args: Vec<String> = extra_args.clone();
|
||||
let (final_prog, final_args) = apply_launch_prefix(&exe_str, all_args, &config_val);
|
||||
let goldmapper_args =
|
||||
goldmapper::build_launch_args(&app, &config_val, &game_exe, &extra_args)?;
|
||||
let (prog_str, all_args): (String, Vec<String>) = match goldmapper_args {
|
||||
Some(gm_args) => (
|
||||
gm_args[0].clone(),
|
||||
gm_args[1..].to_vec(),
|
||||
),
|
||||
None => (game_exe.to_string_lossy().to_string(), extra_args.clone()),
|
||||
};
|
||||
let (final_prog, final_args) = apply_launch_prefix(&prog_str, all_args, &config_val);
|
||||
let mut cmd = tokio::process::Command::new(&final_prog);
|
||||
for a in &final_args {
|
||||
cmd.arg(a);
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
cmd.creation_flags(0x08000000);
|
||||
#[cfg(unix)]
|
||||
cmd.process_group(0);
|
||||
apply_launch_env_vars(&mut cmd, &config_val);
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use tauri::{AppHandle, Manager};
|
||||
use crate::config;
|
||||
use crate::types::{AppConfig, GoldMapperMapping};
|
||||
use crate::util;
|
||||
|
||||
const CONTROLLER_TARGETS: [&str; 14] = [
|
||||
"PAD_A",
|
||||
"PAD_B",
|
||||
"PAD_X",
|
||||
"PAD_Y",
|
||||
"PAD_LB",
|
||||
"PAD_RB",
|
||||
"PAD_BACK",
|
||||
"PAD_START",
|
||||
"PAD_LTHUMB",
|
||||
"PAD_RTHUMB",
|
||||
"PAD_DPAD_UP",
|
||||
"PAD_DPAD_DOWN",
|
||||
"PAD_DPAD_LEFT",
|
||||
"PAD_DPAD_RIGHT",
|
||||
];
|
||||
|
||||
fn push_key(mappings: &mut Vec<GoldMapperMapping>, name: String) {
|
||||
mappings.push(GoldMapperMapping {
|
||||
to: name.clone(),
|
||||
from: name,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn default_mappings() -> Vec<GoldMapperMapping> {
|
||||
let mut mappings = Vec::new();
|
||||
for b in b'A'..=b'Z' {
|
||||
push_key(&mut mappings, format!("KEY_{}", b as char));
|
||||
}
|
||||
for n in 0..=9 {
|
||||
push_key(&mut mappings, format!("KEY_{n}"));
|
||||
}
|
||||
for f in 1..=12 {
|
||||
push_key(&mut mappings, format!("KEY_F{f}"));
|
||||
}
|
||||
for k in [
|
||||
"KEY_SPACE",
|
||||
"KEY_RETURN",
|
||||
"KEY_ESCAPE",
|
||||
"KEY_TAB",
|
||||
"KEY_BACKSPACE",
|
||||
"KEY_DELETE",
|
||||
"KEY_INSERT",
|
||||
"KEY_HOME",
|
||||
"KEY_END",
|
||||
"KEY_PAGEUP",
|
||||
"KEY_PAGEDOWN",
|
||||
"KEY_UP",
|
||||
"KEY_DOWN",
|
||||
"KEY_LEFT",
|
||||
"KEY_RIGHT",
|
||||
"KEY_PRINTSCREEN",
|
||||
"KEY_PAUSE",
|
||||
"KEY_CAPSLOCK",
|
||||
"KEY_NUMLOCK",
|
||||
"KEY_SCROLLLOCK",
|
||||
"KEY_LSHIFT",
|
||||
"KEY_RSHIFT",
|
||||
"KEY_LCTRL",
|
||||
"KEY_RCTRL",
|
||||
"KEY_LALT",
|
||||
"KEY_RALT",
|
||||
"KEY_LWIN",
|
||||
"KEY_RWIN",
|
||||
"KEY_APPS",
|
||||
] {
|
||||
push_key(&mut mappings, k.to_string());
|
||||
}
|
||||
for n in 0..=9 {
|
||||
push_key(&mut mappings, format!("KEY_NUMPAD{n}"));
|
||||
}
|
||||
for k in [
|
||||
"KEY_MULTIPLY",
|
||||
"KEY_ADD",
|
||||
"KEY_SUBTRACT",
|
||||
"KEY_DECIMAL",
|
||||
"KEY_DIVIDE",
|
||||
"KEY_SEMICOLON",
|
||||
"KEY_EQUALS",
|
||||
"KEY_COMMA",
|
||||
"KEY_MINUS",
|
||||
"KEY_PERIOD",
|
||||
"KEY_SLASH",
|
||||
"KEY_GRAVE",
|
||||
"KEY_LBRACKET",
|
||||
"KEY_BACKSLASH",
|
||||
"KEY_RBRACKET",
|
||||
"KEY_APOSTROPHE",
|
||||
] {
|
||||
push_key(&mut mappings, k.to_string());
|
||||
}
|
||||
for m in ["MOUSE_LEFT", "MOUSE_RIGHT", "MOUSE_MIDDLE"] {
|
||||
push_key(&mut mappings, m.to_string());
|
||||
}
|
||||
for (i, target) in CONTROLLER_TARGETS.iter().enumerate() {
|
||||
mappings.push(GoldMapperMapping {
|
||||
from: format!("DINPUT_{i}"),
|
||||
to: (*target).to_string(),
|
||||
});
|
||||
}
|
||||
mappings
|
||||
}
|
||||
|
||||
pub fn resolve_resource(app: &AppHandle, name: &str) -> Option<PathBuf> {
|
||||
use tauri::path::BaseDirectory;
|
||||
if let Ok(p) = app.path().resolve(format!("resources/{name}"), BaseDirectory::Resource) {
|
||||
if p.exists() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
if let Ok(current) = std::env::current_dir() {
|
||||
let p = current.join("src-tauri").join("resources").join(name);
|
||||
if p.exists() {
|
||||
return Some(p);
|
||||
}
|
||||
let p = current.join("resources").join(name);
|
||||
if p.exists() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn write_runtime_config(app: &AppHandle, config_val: &AppConfig) -> Result<PathBuf, String> {
|
||||
let dir = util::get_app_dir(app).join("GoldMapper");
|
||||
fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
|
||||
let path = dir.join("config.json");
|
||||
let mappings = config_val
|
||||
.goldmapper_mappings
|
||||
.clone()
|
||||
.unwrap_or_else(default_mappings);
|
||||
let json = serde_json::to_string(&serde_json::json!({ "mappings": mappings }))
|
||||
.map_err(|e| e.to_string())?;
|
||||
fs::write(&path, json).map_err(|e| e.to_string())?;
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub fn build_launch_args(
|
||||
app: &AppHandle,
|
||||
config_val: &AppConfig,
|
||||
game_exe: &PathBuf,
|
||||
extra_args: &[String],
|
||||
) -> Result<Option<Vec<String>>, String> {
|
||||
if !config_val.goldmapper_enabled.unwrap_or(true) {
|
||||
return Ok(None);
|
||||
}
|
||||
let launcher =
|
||||
resolve_resource(app, "GoldMapperLauncher.exe").ok_or_else(|| "GoldMapperLauncher.exe not found in resources".to_string())?;
|
||||
let config_path = write_runtime_config(app, config_val)?;
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut args = vec![
|
||||
launcher.to_string_lossy().to_string(),
|
||||
config_path.to_string_lossy().to_string(),
|
||||
game_exe.to_string_lossy().to_string(),
|
||||
];
|
||||
args.extend(extra_args.iter().cloned());
|
||||
Ok(Some(args))
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let mut args = vec![
|
||||
util::unix_path_to_wine_z_path(&launcher),
|
||||
util::unix_path_to_wine_z_path(&config_path),
|
||||
util::unix_path_to_wine_z_path(game_exe),
|
||||
];
|
||||
args.extend(extra_args.iter().cloned());
|
||||
Ok(Some(args))
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn goldmapper_get_defaults() -> Vec<GoldMapperMapping> {
|
||||
default_mappings()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn goldmapper_load_config(app: AppHandle) -> Vec<GoldMapperMapping> {
|
||||
config::load_config_raw(app.clone())
|
||||
.goldmapper_mappings
|
||||
.unwrap_or_else(default_mappings)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn goldmapper_save_config(
|
||||
app: AppHandle,
|
||||
mappings: Vec<GoldMapperMapping>,
|
||||
) -> Result<(), String> {
|
||||
let mut config_val = config::load_config_raw(app.clone());
|
||||
config_val.goldmapper_mappings = Some(mappings);
|
||||
println!(
|
||||
"[GoldMapper] config saved: {}",
|
||||
serde_json::json!({ "mappings": config_val.goldmapper_mappings })
|
||||
);
|
||||
config::save_config_raw(&app, &config_val);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn goldmapper_reset_config(app: AppHandle) -> Vec<GoldMapperMapping> {
|
||||
let mut config_val = config::load_config_raw(app.clone());
|
||||
config_val.goldmapper_mappings = None;
|
||||
config::save_config_raw(&app, &config_val);
|
||||
default_mappings()
|
||||
}
|
||||
@@ -4,6 +4,7 @@ pub mod dlc;
|
||||
pub mod download;
|
||||
pub mod file_dialogs;
|
||||
pub mod game;
|
||||
pub mod goldmapper;
|
||||
pub mod java2lce;
|
||||
pub mod macos_setup;
|
||||
pub mod platform;
|
||||
|
||||
@@ -40,6 +40,8 @@ pub fn load_config_raw(app: AppHandle) -> AppConfig {
|
||||
instance_launch_args: None,
|
||||
android_runner: None,
|
||||
android_audio_backend: None,
|
||||
goldmapper_enabled: Some(true),
|
||||
goldmapper_mappings: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ use commands::dlc;
|
||||
use commands::download;
|
||||
use commands::file_dialogs;
|
||||
use commands::game;
|
||||
use commands::goldmapper;
|
||||
use commands::macos_setup;
|
||||
use commands::platform as platform_cmd;
|
||||
use commands::plugins;
|
||||
@@ -149,6 +150,10 @@ pub fn run() {
|
||||
game::switch_proton,
|
||||
game::install_latest_driver,
|
||||
game::set_audio_backend,
|
||||
goldmapper::goldmapper_get_defaults,
|
||||
goldmapper::goldmapper_load_config,
|
||||
goldmapper::goldmapper_save_config,
|
||||
goldmapper::goldmapper_reset_config,
|
||||
])
|
||||
.setup(|app| {
|
||||
let app_handle = app.handle().clone();
|
||||
|
||||
@@ -39,6 +39,12 @@ pub struct InstanceLaunchArgs {
|
||||
pub args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct GoldMapperMapping {
|
||||
pub from: String,
|
||||
pub to: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AppConfig {
|
||||
@@ -69,6 +75,8 @@ pub struct AppConfig {
|
||||
pub instance_launch_args: Option<std::collections::HashMap<String, InstanceLaunchArgs>>,
|
||||
pub android_runner: Option<String>,
|
||||
pub android_audio_backend: Option<String>,
|
||||
pub goldmapper_enabled: Option<bool>,
|
||||
pub goldmapper_mappings: Option<Vec<GoldMapperMapping>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
"bundleMediaFramework": true
|
||||
}
|
||||
},
|
||||
"resources": ["resources/DLC", "resources/GoldMapperLib.dll", "resources/GoldMapperLauncher.exe"],
|
||||
"resources": ["resources/DLC", "resources/GoldMapperLib.dll", "resources/GoldMapperLauncher.exe", "resources/SDL2.dll"],
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/64x64.png",
|
||||
|
||||
@@ -295,7 +295,10 @@ const CreditsView = memo(function CreditsView() {
|
||||
roles: [
|
||||
{
|
||||
role: "",
|
||||
members: [{ name: "faisal508508", url: "#" }],
|
||||
members: [
|
||||
{ name: "faisal508508", url: "#" },
|
||||
{ name: "HingedxHooligan", url: "#" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -0,0 +1,583 @@
|
||||
import { useState, useEffect, useRef, useCallback, useMemo, memo } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
TauriService,
|
||||
type GoldMapperMapping,
|
||||
} from "../../services/TauriService";
|
||||
import { useUI, useConfig, useAudio } from "../../context/LauncherContext";
|
||||
|
||||
const EMERALD_ICON = "/images/emerald_0.png";
|
||||
|
||||
const KEY_FALLBACK = [
|
||||
"KEY_A",
|
||||
"KEY_D",
|
||||
"KEY_S",
|
||||
"KEY_W",
|
||||
"KEY_SPACE",
|
||||
"KEY_RETURN",
|
||||
"KEY_ESCAPE",
|
||||
"KEY_LSHIFT",
|
||||
"KEY_LCTRL",
|
||||
];
|
||||
|
||||
const MOUSE_IDS = ["MOUSE_LEFT", "MOUSE_MIDDLE", "MOUSE_RIGHT"];
|
||||
|
||||
const MOUSE_LABELS: Record<string, string> = {
|
||||
MOUSE_LEFT: "Left Click",
|
||||
MOUSE_MIDDLE: "Middle Click",
|
||||
MOUSE_RIGHT: "Right Click",
|
||||
};
|
||||
|
||||
const CONTROLLER_FALLBACK = [
|
||||
"PAD_A",
|
||||
"PAD_B",
|
||||
"PAD_X",
|
||||
"PAD_Y",
|
||||
"PAD_LB",
|
||||
"PAD_RB",
|
||||
"PAD_BACK",
|
||||
"PAD_START",
|
||||
"PAD_LTHUMB",
|
||||
"PAD_RTHUMB",
|
||||
"PAD_DPAD_UP",
|
||||
"PAD_DPAD_DOWN",
|
||||
"PAD_DPAD_LEFT",
|
||||
"PAD_DPAD_RIGHT",
|
||||
];
|
||||
|
||||
const DINPUT_ROWS: GoldMapperMapping[] = CONTROLLER_FALLBACK.map(
|
||||
(target, i) => ({ from: `DINPUT_${i}`, to: target }),
|
||||
);
|
||||
|
||||
const displayName = (id: string) =>
|
||||
id.replace(/^(KEY|PAD)_/, "").replace(/_/g, " ");
|
||||
|
||||
type Row =
|
||||
| { kind: "reset"; key: string }
|
||||
| { kind: "enable"; key: string }
|
||||
| { kind: "header"; key: string; label: string }
|
||||
| { kind: "bind"; key: string; id: string; label: string };
|
||||
|
||||
const stoneButtonStyle = (highlighted: boolean) => ({
|
||||
backgroundImage: highlighted
|
||||
? "url('/images/button_highlighted.png')"
|
||||
: "url('/images/Button_Background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated" as const,
|
||||
});
|
||||
|
||||
const GoldMapperView = memo(function GoldMapperView() {
|
||||
const { setActiveView } = useUI();
|
||||
const { animationsEnabled, goldmapperEnabled, setGoldmapperEnabled } =
|
||||
useConfig();
|
||||
const { playPressSound, playBackSound } = useAudio();
|
||||
const [keyboardIds, setKeyboardIds] = useState<string[]>(KEY_FALLBACK);
|
||||
const [controllerIds, setControllerIds] =
|
||||
useState<string[]>(CONTROLLER_FALLBACK);
|
||||
const [binds, setBinds] = useState<Record<string, string>>({});
|
||||
const [editing, setEditing] = useState<string | null>(null);
|
||||
const [modalFocusIndex, setModalFocusIndex] = useState(0);
|
||||
const [keyInput, setKeyInput] = useState("");
|
||||
const [keyInputError, setKeyInputError] = useState<string | null>(null);
|
||||
const [focusIndex, setFocusIndex] = useState<number | null>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
TauriService.goldMapperGetDefaults()
|
||||
.then((defaults) => {
|
||||
setKeyboardIds(
|
||||
defaults.filter((m) => m.from.startsWith("KEY_")).map((m) => m.from),
|
||||
);
|
||||
setControllerIds(
|
||||
Array.from(
|
||||
new Set(
|
||||
defaults
|
||||
.filter((m) => m.from.startsWith("DINPUT_"))
|
||||
.map((m) => m.to),
|
||||
),
|
||||
),
|
||||
);
|
||||
})
|
||||
.catch(console.error);
|
||||
TauriService.goldMapperLoadConfig()
|
||||
.then((rows) => {
|
||||
const loaded: Record<string, string> = {};
|
||||
for (const m of rows) {
|
||||
if (/^(KEY|MOUSE|PAD)_/.test(m.from)) {
|
||||
loaded[m.from] = m.to;
|
||||
}
|
||||
}
|
||||
setBinds(loaded);
|
||||
})
|
||||
.catch(console.error);
|
||||
}, []);
|
||||
|
||||
const getTo = useCallback(
|
||||
(id: string) => binds[id] ?? id,
|
||||
[binds],
|
||||
);
|
||||
|
||||
const buildPayload = useCallback(
|
||||
(nextBinds: Record<string, string>): GoldMapperMapping[] => {
|
||||
const rows: GoldMapperMapping[] = [];
|
||||
for (const id of [...keyboardIds, ...MOUSE_IDS, ...controllerIds]) {
|
||||
const to = nextBinds[id] ?? id;
|
||||
if (to !== id) {
|
||||
rows.push({ from: id, to });
|
||||
}
|
||||
}
|
||||
rows.push(...DINPUT_ROWS);
|
||||
return rows;
|
||||
},
|
||||
[keyboardIds, controllerIds],
|
||||
);
|
||||
|
||||
const handleResetToDefaults = useCallback(() => {
|
||||
playPressSound();
|
||||
setBinds({});
|
||||
console.log("[GoldMapper] reset to defaults");
|
||||
TauriService.goldMapperResetConfig().catch(console.error);
|
||||
}, [playPressSound]);
|
||||
|
||||
const handleToggleEnabled = useCallback(() => {
|
||||
playPressSound();
|
||||
setGoldmapperEnabled(!goldmapperEnabled);
|
||||
}, [playPressSound, goldmapperEnabled, setGoldmapperEnabled]);
|
||||
|
||||
const handleBack = useCallback(() => {
|
||||
playBackSound();
|
||||
setActiveView("settings");
|
||||
}, [playBackSound, setActiveView]);
|
||||
|
||||
const openBind = useCallback(
|
||||
(id: string) => {
|
||||
playPressSound();
|
||||
setEditing(id);
|
||||
setModalFocusIndex(0);
|
||||
setKeyInput(
|
||||
/^KEY_/.test(getTo(id)) ? displayName(getTo(id)) : "",
|
||||
);
|
||||
setKeyInputError(null);
|
||||
},
|
||||
[playPressSound, getTo],
|
||||
);
|
||||
|
||||
const closeModal = useCallback(() => {
|
||||
playBackSound();
|
||||
(document.activeElement as HTMLElement | null)?.blur();
|
||||
setEditing(null);
|
||||
}, [playBackSound]);
|
||||
|
||||
const pickTarget = useCallback(
|
||||
(sourceId: string, targetId: string) => {
|
||||
playPressSound();
|
||||
const next = { ...binds, [sourceId]: targetId };
|
||||
setBinds(next);
|
||||
const payload = buildPayload(next);
|
||||
console.log(
|
||||
"[GoldMapper] saving config:",
|
||||
JSON.stringify({ mappings: payload }),
|
||||
);
|
||||
TauriService.goldMapperSaveConfig(payload).catch(console.error);
|
||||
(document.activeElement as HTMLElement | null)?.blur();
|
||||
setEditing(null);
|
||||
},
|
||||
[playPressSound, binds, buildPayload],
|
||||
);
|
||||
|
||||
const submitKeyInput = useCallback(() => {
|
||||
if (editing === null) return;
|
||||
const norm = keyInput
|
||||
.trim()
|
||||
.toUpperCase()
|
||||
.replace(/\s+/g, "_")
|
||||
.replace(/^KEY_/, "");
|
||||
if (!norm || !keyboardIds.includes(`KEY_${norm}`)) {
|
||||
setKeyInputError("Unknown key name");
|
||||
return;
|
||||
}
|
||||
pickTarget(editing, `KEY_${norm}`);
|
||||
}, [editing, keyInput, keyboardIds, pickTarget]);
|
||||
|
||||
const rows: Row[] = useMemo(() => {
|
||||
const list: Row[] = [{ kind: "reset", key: "reset" }];
|
||||
list.push({ kind: "enable", key: "enable" });
|
||||
list.push({ kind: "header", key: "controller_header", label: "Controller" });
|
||||
for (const id of controllerIds) {
|
||||
list.push({
|
||||
kind: "bind",
|
||||
key: `controller_${id}`,
|
||||
id,
|
||||
label: displayName(id),
|
||||
});
|
||||
}
|
||||
list.push({ kind: "header", key: "mouse_header", label: "Mouse" });
|
||||
for (const id of MOUSE_IDS) {
|
||||
list.push({
|
||||
kind: "bind",
|
||||
key: `mouse_${id}`,
|
||||
id,
|
||||
label: MOUSE_LABELS[id],
|
||||
});
|
||||
}
|
||||
list.push({ kind: "header", key: "keyboard_header", label: "Keyboard" });
|
||||
for (const id of keyboardIds) {
|
||||
list.push({
|
||||
kind: "bind",
|
||||
key: `keyboard_${id}`,
|
||||
id,
|
||||
label: displayName(id),
|
||||
});
|
||||
}
|
||||
return list;
|
||||
}, [controllerIds, keyboardIds]);
|
||||
|
||||
const focusableCount = rows.filter((r) => r.kind !== "header").length;
|
||||
const modalItemCount = MOUSE_IDS.length + controllerIds.length + 2;
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (editing !== null) {
|
||||
const activeTag = document.activeElement?.tagName;
|
||||
if (activeTag === "INPUT") {
|
||||
if (e.key === "Escape") {
|
||||
closeModal();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
closeModal();
|
||||
return;
|
||||
}
|
||||
if (e.key === "ArrowDown" || e.key === "Tab") {
|
||||
e.preventDefault();
|
||||
setModalFocusIndex((prev) => (prev + 1) % modalItemCount);
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
setModalFocusIndex(
|
||||
(prev) => (prev - 1 + modalItemCount) % modalItemCount,
|
||||
);
|
||||
} else if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
if (modalFocusIndex < MOUSE_IDS.length + controllerIds.length) {
|
||||
const allTargets = [...MOUSE_IDS, ...controllerIds];
|
||||
pickTarget(editing, allTargets[modalFocusIndex]);
|
||||
} else if (
|
||||
modalFocusIndex ===
|
||||
MOUSE_IDS.length + controllerIds.length
|
||||
) {
|
||||
submitKeyInput();
|
||||
} else {
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
handleBack();
|
||||
return;
|
||||
}
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
setFocusIndex((prev) =>
|
||||
prev === null || prev >= focusableCount - 1 ? 0 : prev + 1,
|
||||
);
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
setFocusIndex((prev) =>
|
||||
prev === null || prev <= 0 ? focusableCount - 1 : prev - 1,
|
||||
);
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [
|
||||
focusableCount,
|
||||
handleBack,
|
||||
editing,
|
||||
closeModal,
|
||||
modalItemCount,
|
||||
modalFocusIndex,
|
||||
controllerIds.length,
|
||||
pickTarget,
|
||||
submitKeyInput,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (focusIndex === null || editing !== null) return;
|
||||
const el = containerRef.current?.querySelector(
|
||||
`[data-focus-index="${focusIndex}"]`,
|
||||
) as HTMLElement | null;
|
||||
el?.focus();
|
||||
}, [focusIndex, editing]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editing === null) return;
|
||||
const el = document.querySelector(
|
||||
`[data-modal-index="${modalFocusIndex}"]`,
|
||||
) as HTMLElement | null;
|
||||
el?.focus();
|
||||
}, [modalFocusIndex, editing]);
|
||||
|
||||
let focusCounter = 0;
|
||||
const nextFocusIndex = () => {
|
||||
const idx = focusCounter;
|
||||
focusCounter += 1;
|
||||
return idx;
|
||||
};
|
||||
|
||||
const renderRow = (row: Row) => {
|
||||
if (row.kind === "header") {
|
||||
return (
|
||||
<div key={row.key} className="w-full px-1 pt-3 pb-1">
|
||||
<span className="text-sm text-black tracking-widest">
|
||||
{row.label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isReset = row.kind === "reset";
|
||||
const isEnable = row.kind === "enable";
|
||||
const focusIdx = nextFocusIndex();
|
||||
const focused = focusIndex === focusIdx;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={row.key}
|
||||
data-focus-index={focusIdx}
|
||||
onFocus={() => setFocusIndex(focusIdx)}
|
||||
onMouseEnter={() => setFocusIndex(focusIdx)}
|
||||
onClick={
|
||||
isReset
|
||||
? handleResetToDefaults
|
||||
: isEnable
|
||||
? handleToggleEnabled
|
||||
: () => openBind(row.id)
|
||||
}
|
||||
className={`w-full h-10 flex items-center pl-6 pr-4 outline-none border-none shrink-0 transition-colors ${
|
||||
focused
|
||||
? "text-[#ffff00]"
|
||||
: isEnable
|
||||
? "text-[#333333]"
|
||||
: "text-white"
|
||||
} ${isReset ? "justify-center hover:text-[#ffff00]" : ""}`}
|
||||
style={isEnable ? undefined : stoneButtonStyle(focused)}
|
||||
>
|
||||
{isEnable && (
|
||||
<div className="relative w-6 h-6 mr-3 shrink-0 flex items-center justify-center">
|
||||
<img
|
||||
src={
|
||||
focused
|
||||
? "/images/checkbox_highlighted.png"
|
||||
: "/images/checkbox.png"
|
||||
}
|
||||
alt="checkbox"
|
||||
className="absolute inset-0 w-full h-full object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
{goldmapperEnabled && (
|
||||
<img
|
||||
src="/images/check.png"
|
||||
alt="checked"
|
||||
className="relative z-10 w-6 h-6 object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<span
|
||||
className={`tracking-widest text-lg mc-text-shadow truncate ${
|
||||
isReset ? "" : "flex-1 text-left"
|
||||
}`}
|
||||
>
|
||||
{isReset
|
||||
? "Reset to Defaults"
|
||||
: isEnable
|
||||
? "Enable GoldMapper"
|
||||
: row.label}
|
||||
</span>
|
||||
{!isReset &&
|
||||
!isEnable &&
|
||||
row.kind === "bind" && (
|
||||
<span className="tracking-widest text-base opacity-70 ml-3 shrink-0">
|
||||
{displayName(getTo(row.id))}
|
||||
</span>
|
||||
)}
|
||||
{isEnable && (
|
||||
<img
|
||||
src={EMERALD_ICON}
|
||||
alt=""
|
||||
className="w-6 h-6 object-contain shrink-0 ml-3"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
transition={{ duration: animationsEnabled ? 0.3 : 0 }}
|
||||
className="flex flex-col items-center w-full max-w-5xl"
|
||||
>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="w-[720px] max-w-[92vw] h-[560px] max-h-[62vh] p-4 flex flex-col gap-2 overflow-y-auto settings-scrollbar mc-options-bg"
|
||||
>
|
||||
{rows.map(renderRow)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onMouseEnter={() => setFocusIndex(null)}
|
||||
onClick={handleBack}
|
||||
className="w-40 h-10 flex items-center justify-center transition-colors text-xl mc-text-shadow outline-none border-none hover:text-[#ffff00] mt-4 text-white"
|
||||
style={{
|
||||
backgroundImage: "url('/images/Button_Background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
|
||||
{editing !== null && (
|
||||
<div
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 outline-none border-none"
|
||||
onMouseDown={(e) => {
|
||||
if (e.target === e.currentTarget) closeModal();
|
||||
}}
|
||||
>
|
||||
<div className="relative w-[620px] max-w-[95vw] h-[580px] max-h-[88vh] p-5 flex flex-col font-['Mojangles'] mc-options-bg">
|
||||
<h2 className="text-xl text-black mc-text-shadow mb-4 text-center">
|
||||
Assign {displayName(editing)}
|
||||
</h2>
|
||||
|
||||
<div className="w-full flex-1 min-h-0 overflow-y-auto custom-scrollbar mb-4">
|
||||
<div className="mb-3">
|
||||
<h3 className="text-[#333333] mc-text-shadow uppercase tracking-widest text-sm px-3 pt-2 pb-1">
|
||||
Mouse
|
||||
</h3>
|
||||
<div className="grid grid-cols-4 gap-2 p-1 content-start">
|
||||
{MOUSE_IDS.map((id, i) => (
|
||||
<button
|
||||
key={id}
|
||||
data-modal-index={i}
|
||||
onFocus={() => setModalFocusIndex(i)}
|
||||
onMouseEnter={() => setModalFocusIndex(i)}
|
||||
onClick={() => pickTarget(editing, id)}
|
||||
className={`h-10 px-2 flex items-center justify-center text-sm tracking-widest outline-none border-none cursor-pointer transition-colors ${
|
||||
getTo(editing) === id
|
||||
? "text-[#ffff00]"
|
||||
: "text-white"
|
||||
}`}
|
||||
style={stoneButtonStyle(modalFocusIndex === i)}
|
||||
>
|
||||
<span className="truncate">{MOUSE_LABELS[id]}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<h3 className="text-[#333333] mc-text-shadow uppercase tracking-widest text-sm px-3 pt-2 pb-1">
|
||||
Controller
|
||||
</h3>
|
||||
<div className="grid grid-cols-4 gap-2 p-1 content-start">
|
||||
{controllerIds.map((id, i) => {
|
||||
const idx = MOUSE_IDS.length + i;
|
||||
return (
|
||||
<button
|
||||
key={id}
|
||||
data-modal-index={idx}
|
||||
onFocus={() => setModalFocusIndex(idx)}
|
||||
onMouseEnter={() => setModalFocusIndex(idx)}
|
||||
onClick={() => pickTarget(editing, id)}
|
||||
className={`h-10 px-2 flex items-center justify-center text-sm tracking-widest outline-none border-none cursor-pointer transition-colors ${
|
||||
getTo(editing) === id
|
||||
? "text-[#ffff00]"
|
||||
: "text-white"
|
||||
}`}
|
||||
style={stoneButtonStyle(modalFocusIndex === idx)}
|
||||
>
|
||||
<span className="truncate">{displayName(id)}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<h3 className="text-[#333333] mc-text-shadow uppercase tracking-widest text-sm px-3 pt-2 pb-1">
|
||||
Keyboard
|
||||
</h3>
|
||||
<input
|
||||
data-modal-index={MOUSE_IDS.length + controllerIds.length}
|
||||
value={keyInput}
|
||||
onChange={(e) => {
|
||||
setKeyInput(e.target.value);
|
||||
setKeyInputError(null);
|
||||
}}
|
||||
onFocus={() =>
|
||||
setModalFocusIndex(MOUSE_IDS.length + controllerIds.length)
|
||||
}
|
||||
onMouseEnter={() =>
|
||||
setModalFocusIndex(MOUSE_IDS.length + controllerIds.length)
|
||||
}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") submitKeyInput();
|
||||
}}
|
||||
placeholder="Type a key name and press Enter"
|
||||
className={`w-full h-10 px-3 bg-black/40 border-2 text-white text-base outline-none text-center ${
|
||||
keyInputError
|
||||
? "border-red-600"
|
||||
: "border-[#373737] focus:border-[#FFFF55]"
|
||||
}`}
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
{keyInputError && (
|
||||
<p className="text-red-600 text-xs mt-1 px-3">
|
||||
{keyInputError}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
data-modal-index={MOUSE_IDS.length + controllerIds.length + 1}
|
||||
onFocus={() =>
|
||||
setModalFocusIndex(MOUSE_IDS.length + controllerIds.length + 1)
|
||||
}
|
||||
onMouseEnter={() =>
|
||||
setModalFocusIndex(MOUSE_IDS.length + controllerIds.length + 1)
|
||||
}
|
||||
onClick={closeModal}
|
||||
className={`w-full h-12 flex items-center justify-center text-xl mc-text-shadow transition-colors outline-none border-none cursor-pointer ${
|
||||
modalFocusIndex ===
|
||||
MOUSE_IDS.length + controllerIds.length + 1
|
||||
? "text-[#ffff00]"
|
||||
: "text-white"
|
||||
}`}
|
||||
style={{
|
||||
backgroundImage:
|
||||
modalFocusIndex ===
|
||||
MOUSE_IDS.length + controllerIds.length + 1
|
||||
? "url('/images/button_highlighted.png')"
|
||||
: "url('/images/Button_Background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
});
|
||||
|
||||
export default GoldMapperView;
|
||||
@@ -318,6 +318,17 @@ const SettingsView = memo(function SettingsView() {
|
||||
setFocusIndex(0);
|
||||
},
|
||||
});
|
||||
if (!isAndroid) {
|
||||
items.push({
|
||||
id: "controls_menu",
|
||||
label: "Controls",
|
||||
type: "button",
|
||||
onClick: () => {
|
||||
playPressSound();
|
||||
setActiveView("goldmapper");
|
||||
},
|
||||
});
|
||||
}
|
||||
items.push({
|
||||
id: "plugins_menu",
|
||||
label: "Plugins",
|
||||
|
||||
@@ -70,7 +70,7 @@ const DEFAULT_SKINS: SavedSkin[] = [
|
||||
url: "/Skins/PrismaChunk0.png",
|
||||
isSlim: false,
|
||||
},
|
||||
{ id: "amy", name: "Amy", url: "/Skins/amy.png", isSlim: true }, //neo: she's the best btw
|
||||
{ id: "amy", name: "Amy", url: "/Skins/amy.png", isSlim: true }, //neo: :c
|
||||
{ id: "avalilac", name: "AvaLilac", url: "/Skins/ava.png", isSlim: true },
|
||||
{ id: "huckle", name: "Huckle", url: "/Skins/huckle.png", isSlim: true },
|
||||
{
|
||||
@@ -604,9 +604,9 @@ const SkinsView = memo(function SkinsView() {
|
||||
playPressSound();
|
||||
setActiveView("skin-editor");
|
||||
}}
|
||||
className={`w-40 h-10 flex items-center
|
||||
justify-center transition-colors text-2xl
|
||||
mc-text-shadow outline-none border-none hover:text-[#FFFF55]
|
||||
className={`w-40 h-10 flex items-center
|
||||
justify-center transition-colors text-2xl
|
||||
mc-text-shadow outline-none border-none hover:text-[#FFFF55]
|
||||
${focusIndex === 2 ? "text-[#FFFF55]" : "text-white"}`}
|
||||
style={{
|
||||
backgroundImage:
|
||||
|
||||
@@ -111,6 +111,7 @@ export function LauncherProvider({ children }: { children: React.ReactNode }) {
|
||||
configRaw.instanceLaunchArgs,
|
||||
configRaw.androidRunner,
|
||||
configRaw.androidAudioBackend,
|
||||
configRaw.goldmapperEnabled,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -213,6 +214,7 @@ export function LauncherProvider({ children }: { children: React.ReactNode }) {
|
||||
startFullscreen: config.startFullscreen,
|
||||
skipIntro: config.skipIntro,
|
||||
instanceLaunchArgs: config.instanceLaunchArgs,
|
||||
goldmapperEnabled: config.goldmapperEnabled,
|
||||
}).catch(console.error);
|
||||
}
|
||||
}, [
|
||||
@@ -239,6 +241,7 @@ export function LauncherProvider({ children }: { children: React.ReactNode }) {
|
||||
config.startFullscreen,
|
||||
config.skipIntro,
|
||||
config.instanceLaunchArgs,
|
||||
config.goldmapperEnabled,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useLocalStorage } from "./useLocalStorage";
|
||||
import { TauriService, type CustomEdition } from "../services/TauriService";
|
||||
import {
|
||||
TauriService,
|
||||
type CustomEdition,
|
||||
type GoldMapperMapping,
|
||||
} from "../services/TauriService";
|
||||
export function useAppConfig() {
|
||||
const [username, setUsername] = useLocalStorage("lce-username", "Steve");
|
||||
const [theme, setTheme] = useLocalStorage("lce-theme", "Modern");
|
||||
@@ -31,6 +35,8 @@ export function useAppConfig() {
|
||||
>({});
|
||||
const [androidRunner, setAndroidRunner] = useLocalStorage<string | undefined>("lce-android-runner", undefined);
|
||||
const [androidAudioBackend, setAndroidAudioBackend] = useLocalStorage<"alsa" | "pulseaudio">("lce-android-audio", "pulseaudio");
|
||||
const [goldmapperEnabled, setGoldmapperEnabled] = useLocalStorage("lce-goldmapper", true);
|
||||
const [goldmapperMappings, setGoldmapperMappings] = useState<GoldMapperMapping[] | undefined>();
|
||||
useEffect(() => {
|
||||
TauriService.loadConfig().then((config) => {
|
||||
if (config.username) setUsername(config.username);
|
||||
@@ -57,6 +63,8 @@ export function useAppConfig() {
|
||||
if (config.instanceLaunchArgs) setInstanceLaunchArgs(config.instanceLaunchArgs);
|
||||
if (config.androidRunner) setAndroidRunner(config.androidRunner);
|
||||
if (config.androidAudioBackend) setAndroidAudioBackend(config.androidAudioBackend);
|
||||
if (config.goldmapperEnabled !== undefined) setGoldmapperEnabled(config.goldmapperEnabled);
|
||||
if (config.goldmapperMappings) setGoldmapperMappings(config.goldmapperMappings);
|
||||
setIsLoaded(true);
|
||||
});
|
||||
}, []);
|
||||
@@ -87,9 +95,11 @@ export function useAppConfig() {
|
||||
instanceLaunchArgs,
|
||||
androidRunner,
|
||||
androidAudioBackend,
|
||||
goldmapperEnabled,
|
||||
goldmapperMappings,
|
||||
}).catch(console.error);
|
||||
}
|
||||
}, [username, theme, linuxRunner, perfBoost, profile, customEditions, customPaths, customizations, animationsEnabled, vfxEnabled, rpcEnabled, startFullscreen, musicVol, sfxVol, legacyMode, mangohudEnabled, extraLaunchArgs, launchPrefix, launchEnvVars, skipIntro, isLoaded, instanceLaunchArgs, androidRunner, androidAudioBackend]);
|
||||
}, [username, theme, linuxRunner, perfBoost, profile, customEditions, customPaths, customizations, animationsEnabled, vfxEnabled, rpcEnabled, startFullscreen, musicVol, sfxVol, legacyMode, mangohudEnabled, extraLaunchArgs, launchPrefix, launchEnvVars, skipIntro, isLoaded, instanceLaunchArgs, androidRunner, androidAudioBackend, goldmapperEnabled, goldmapperMappings]);
|
||||
|
||||
return {
|
||||
username,
|
||||
@@ -145,5 +155,9 @@ export function useAppConfig() {
|
||||
setAndroidRunner,
|
||||
androidAudioBackend,
|
||||
setAndroidAudioBackend,
|
||||
goldmapperEnabled,
|
||||
setGoldmapperEnabled,
|
||||
goldmapperMappings,
|
||||
setGoldmapperMappings,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -74,12 +74,12 @@ export const BASE_EDITIONS = [
|
||||
id: "360revived",
|
||||
name: "360 Revived",
|
||||
desc: "PC port of Xbox 360 Edition TU19",
|
||||
url: "https://github.com/BlackHoleSpirit/360-Revived/releases/download/nightly/360-Revived.zip",
|
||||
url: HIDDEN_INSTANCE_URL, //neo: was "https://github.com/BlackHoleSpirit/360-Revived/releases/download/nightly/360-Revived.zip"
|
||||
titleImage: "/images/minecraft_title_360revived.png",
|
||||
supportsSlimSkins: false,
|
||||
logo: "/images/360_revived.png",
|
||||
panorama: "360revived",
|
||||
hideOnAndroid: true, //neo: 360revived shows a black screen on Android
|
||||
hideOnAndroid: true, //neo: [NOT EFFECT BECAUSE OF `url`] 360revived shows a black screen on Android
|
||||
},
|
||||
{
|
||||
id: "legacy_nether_fork",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { motion, AnimatePresence, MotionConfig } from "framer-motion";
|
||||
import "../css/App.css";
|
||||
import HomeView from "../components/views/HomeView";
|
||||
import SettingsView from "../components/views/SettingsView";
|
||||
import GoldMapperView from "../components/views/GoldMapperView";
|
||||
import VersionsView from "../components/views/VersionsView";
|
||||
import DevtoolsView from "../components/views/DevtoolsView";
|
||||
import GuidesView from "../components/views/GuidesView";
|
||||
@@ -305,6 +306,7 @@ export default function App() {
|
||||
"options-editor",
|
||||
"model-editor",
|
||||
"swf-editor",
|
||||
"goldmapper",
|
||||
]);
|
||||
useEffect(() => {
|
||||
const handleContextMenu = (e: MouseEvent) => e.preventDefault();
|
||||
@@ -671,6 +673,9 @@ export default function App() {
|
||||
{activeView === "settings" && (
|
||||
<SettingsView key="settings-view" />
|
||||
)}
|
||||
{activeView === "goldmapper" && (
|
||||
<GoldMapperView key="goldmapper-view" />
|
||||
)}
|
||||
{activeView === "versions" && (
|
||||
<VersionsView key="versions-view" />
|
||||
)}
|
||||
|
||||
@@ -23,6 +23,11 @@ export interface CustomEdition {
|
||||
logo?: string;
|
||||
}
|
||||
|
||||
export interface GoldMapperMapping {
|
||||
from: string;
|
||||
to: string;
|
||||
}
|
||||
|
||||
export interface AppConfig {
|
||||
username: string;
|
||||
linuxRunner?: string;
|
||||
@@ -54,6 +59,8 @@ export interface AppConfig {
|
||||
>;
|
||||
androidRunner?: string;
|
||||
androidAudioBackend?: "alsa" | "pulseaudio";
|
||||
goldmapperEnabled?: boolean;
|
||||
goldmapperMappings?: GoldMapperMapping[];
|
||||
}
|
||||
|
||||
export interface ThemePalette {
|
||||
@@ -494,4 +501,22 @@ export class TauriService {
|
||||
static async setAudioBackend(backend: string): Promise<void> {
|
||||
return invoke("set_audio_backend", { backend });
|
||||
}
|
||||
|
||||
static async goldMapperGetDefaults(): Promise<GoldMapperMapping[]> {
|
||||
return invoke("goldmapper_get_defaults");
|
||||
}
|
||||
|
||||
static async goldMapperLoadConfig(): Promise<GoldMapperMapping[]> {
|
||||
return invoke("goldmapper_load_config");
|
||||
}
|
||||
|
||||
static async goldMapperSaveConfig(
|
||||
mappings: GoldMapperMapping[],
|
||||
): Promise<void> {
|
||||
return invoke("goldmapper_save_config", { mappings });
|
||||
}
|
||||
|
||||
static async goldMapperResetConfig(): Promise<GoldMapperMapping[]> {
|
||||
return invoke("goldmapper_reset_config");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user