mirror of
https://github.com/LCE-Hub/LCE-Emerald-Launcher.git
synced 2026-09-23 08:32:51 +00:00
feat: local custom TUs support
This commit is contained in:
+63
-6
@@ -35,6 +35,7 @@ pub struct CustomEdition {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub desc: String,
|
pub desc: String,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
|
pub path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
@@ -146,10 +147,10 @@ fn find_executable_recursive(root: &PathBuf, file_name: &str) -> Option<PathBuf>
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_macos_runtime_installed(app: &AppHandle) -> bool {
|
fn is_macos_runtime_installed(_app: &AppHandle) -> bool {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
let runtime_dir = get_macos_runtime_dir(app);
|
let runtime_dir = get_macos_runtime_dir(_app);
|
||||||
let toolkit_dir = runtime_dir.join("toolkit");
|
let toolkit_dir = runtime_dir.join("toolkit");
|
||||||
if !toolkit_dir.exists() {
|
if !toolkit_dir.exists() {
|
||||||
return false;
|
return false;
|
||||||
@@ -280,6 +281,19 @@ fn import_theme(app: AppHandle) -> Result<String, String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn pick_folder() -> Result<String, String> {
|
||||||
|
let folder = rfd::FileDialog::new()
|
||||||
|
.set_title("Select Custom TU Folder")
|
||||||
|
.pick_folder();
|
||||||
|
|
||||||
|
if let Some(path) = folder {
|
||||||
|
Ok(path.to_string_lossy().to_string())
|
||||||
|
} else {
|
||||||
|
Err("CANCELED".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn get_available_runners(app: AppHandle) -> Vec<Runner> {
|
fn get_available_runners(app: AppHandle) -> Vec<Runner> {
|
||||||
let mut runners = Vec::new();
|
let mut runners = Vec::new();
|
||||||
@@ -456,13 +470,36 @@ async fn download_runner(app: AppHandle, state: State<'_, DownloadState>, name:
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn check_game_installed(app: AppHandle, instance_id: String) -> bool {
|
fn check_game_installed(app: AppHandle, instance_id: String) -> bool {
|
||||||
|
let config = load_config(app.clone());
|
||||||
|
if let Some(ref editions) = config.custom_editions {
|
||||||
|
if let Some(edition) = editions.iter().find(|e| e.id == instance_id) {
|
||||||
|
if let Some(ref path) = edition.path {
|
||||||
|
return PathBuf::from(path).join("Minecraft.Client.exe").exists();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
get_app_dir(&app).join("instances").join(&instance_id).join("Minecraft.Client.exe").exists()
|
get_app_dir(&app).join("instances").join(&instance_id).join("Minecraft.Client.exe").exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn open_instance_folder(app: AppHandle, instance_id: String) {
|
fn open_instance_folder(app: AppHandle, instance_id: String) {
|
||||||
let dir = get_app_dir(&app).join("instances").join(&instance_id);
|
let config = load_config(app.clone());
|
||||||
|
let dir = if let Some(ref editions) = config.custom_editions {
|
||||||
|
if let Some(edition) = editions.iter().find(|e| e.id == instance_id) {
|
||||||
|
if let Some(ref path) = edition.path {
|
||||||
|
Some(PathBuf::from(path))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let dir = dir.unwrap_or_else(|| get_app_dir(&app).join("instances").join(&instance_id));
|
||||||
if dir.exists() {
|
if dir.exists() {
|
||||||
let _ = app.opener().open_path(dir.to_str().unwrap(), None::<&str>);
|
let _ = app.opener().open_path(dir.to_str().unwrap(), None::<&str>);
|
||||||
}
|
}
|
||||||
@@ -471,6 +508,15 @@ fn open_instance_folder(app: AppHandle, instance_id: String) {
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn delete_instance(app: AppHandle, instance_id: String) -> Result<(), String> {
|
fn delete_instance(app: AppHandle, instance_id: String) -> Result<(), String> {
|
||||||
|
let config = load_config(app.clone());
|
||||||
|
if let Some(ref editions) = config.custom_editions {
|
||||||
|
if let Some(edition) = editions.iter().find(|e| e.id == instance_id) {
|
||||||
|
if edition.path.is_some() {
|
||||||
|
// Do not delete files for custom path instances
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
let dir = get_app_dir(&app).join("instances").join(&instance_id);
|
let dir = get_app_dir(&app).join("instances").join(&instance_id);
|
||||||
if dir.exists() {
|
if dir.exists() {
|
||||||
let _ = fs::remove_dir_all(dir);
|
let _ = fs::remove_dir_all(dir);
|
||||||
@@ -1187,7 +1233,18 @@ async fn launch_game(app: AppHandle, state: State<'_, GameState>, instance_id: S
|
|||||||
}
|
}
|
||||||
|
|
||||||
let _ = perform_dlc_sync(&app, &instance_dir)?;
|
let _ = perform_dlc_sync(&app, &instance_dir)?;
|
||||||
let game_exe = instance_dir.join("Minecraft.Client.exe");
|
|
||||||
|
let mut custom_path = None;
|
||||||
|
if let Some(ref editions) = config.custom_editions {
|
||||||
|
if let Some(edition) = editions.iter().find(|e| e.id == instance_id) {
|
||||||
|
if let Some(ref path) = edition.path {
|
||||||
|
custom_path = Some(PathBuf::from(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let working_dir = custom_path.unwrap_or_else(|| instance_dir.clone());
|
||||||
|
let game_exe = working_dir.join("Minecraft.Client.exe");
|
||||||
if !game_exe.exists() {
|
if !game_exe.exists() {
|
||||||
return Err("Game executable not found in instance folder.".into());
|
return Err("Game executable not found in instance folder.".into());
|
||||||
}
|
}
|
||||||
@@ -1226,7 +1283,7 @@ async fn launch_game(app: AppHandle, state: State<'_, GameState>, instance_id: S
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd.arg(&game_exe)
|
cmd.arg(&game_exe)
|
||||||
.current_dir(&instance_dir);
|
.current_dir(&working_dir);
|
||||||
|
|
||||||
let child = cmd.spawn().map_err(|e| e.to_string())?;
|
let child = cmd.spawn().map_err(|e| e.to_string())?;
|
||||||
{
|
{
|
||||||
@@ -1532,7 +1589,7 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.invoke_handler(tauri::generate_handler![setup_macos_runtime, launch_game, stop_game, check_game_installed, save_config, load_config, download_and_install, open_instance_folder, cancel_download, get_available_runners, get_external_palettes, import_theme, download_runner, delete_instance, sync_dlc, fetch_skin, workshop_install, workshop_uninstall, workshop_list_installed, get_screenshots, delete_screenshot, open_screenshot_folder, save_global_skin_pck, check_game_update, check_macos_runtime_installed, check_macos_runtime_installed_fast])
|
.invoke_handler(tauri::generate_handler![setup_macos_runtime, launch_game, stop_game, check_game_installed, save_config, load_config, download_and_install, open_instance_folder, cancel_download, get_available_runners, get_external_palettes, import_theme, pick_folder, download_runner, delete_instance, sync_dlc, fetch_skin, workshop_install, workshop_uninstall, workshop_list_installed, get_screenshots, delete_screenshot, open_screenshot_folder, save_global_skin_pck, check_game_update, check_macos_runtime_installed, check_macos_runtime_installed_fast])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,8 @@ const ModalButton = memo(function ModalButton({
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onMouseEnter={() => setIsHovered(true)}
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
onMouseLeave={() => setIsHovered(false)}
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
className={`flex-1 h-12 flex items-center justify-center text-xl mc-text-shadow transition-colors outline-none border-none bg-transparent ${
|
className={`flex-1 h-12 flex items-center justify-center text-xl mc-text-shadow transition-colors outline-none border-none bg-transparent ${isDanger ? "text-red-500" : "text-white"
|
||||||
isDanger ? "text-red-500" : "text-white"
|
} ${isHovered ? (isDanger ? "text-red-400" : "text-[#FFFF55]") : ""}`}
|
||||||
} ${isHovered ? (isDanger ? "text-red-400" : "text-[#FFFF55]") : ""}`}
|
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: isHovered
|
backgroundImage: isHovered
|
||||||
? "url('/images/button_highlighted.png')"
|
? "url('/images/button_highlighted.png')"
|
||||||
@@ -41,10 +40,12 @@ export default function CustomTUModal({
|
|||||||
playPressSound,
|
playPressSound,
|
||||||
playBackSound,
|
playBackSound,
|
||||||
editingEdition = null,
|
editingEdition = null,
|
||||||
|
initialPath = "",
|
||||||
}: any) {
|
}: any) {
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [desc, setDesc] = useState("");
|
const [desc, setDesc] = useState("");
|
||||||
const [url, setUrl] = useState("");
|
const [url, setUrl] = useState("");
|
||||||
|
const [path, setPath] = useState("");
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [focusIndex, setFocusIndex] = useState(0);
|
const [focusIndex, setFocusIndex] = useState(0);
|
||||||
|
|
||||||
@@ -53,13 +54,17 @@ export default function CustomTUModal({
|
|||||||
setName(editingEdition.name);
|
setName(editingEdition.name);
|
||||||
setDesc(editingEdition.desc);
|
setDesc(editingEdition.desc);
|
||||||
setUrl(editingEdition.url);
|
setUrl(editingEdition.url);
|
||||||
|
setPath(editingEdition.path || "");
|
||||||
|
} else if (isOpen && initialPath) {
|
||||||
|
setPath(initialPath);
|
||||||
} else if (!isOpen) {
|
} else if (!isOpen) {
|
||||||
setName("");
|
setName("");
|
||||||
setDesc("");
|
setDesc("");
|
||||||
setUrl("");
|
setUrl("");
|
||||||
|
setPath("");
|
||||||
setError("");
|
setError("");
|
||||||
}
|
}
|
||||||
}, [editingEdition, isOpen]);
|
}, [editingEdition, isOpen, initialPath]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
@@ -88,25 +93,30 @@ export default function CustomTUModal({
|
|||||||
};
|
};
|
||||||
window.addEventListener("keydown", handleKey);
|
window.addEventListener("keydown", handleKey);
|
||||||
return () => window.removeEventListener("keydown", handleKey);
|
return () => window.removeEventListener("keydown", handleKey);
|
||||||
}, [isOpen, focusIndex, name, desc, url]);
|
}, [isOpen, focusIndex, name, desc, url, path]);
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
const handleImport = () => {
|
const handleImport = () => {
|
||||||
if (!name || !url) {
|
if (!name) {
|
||||||
setError("Name and URL are required");
|
setError("Name is required");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!url.startsWith("http")) {
|
if (!url && !path) {
|
||||||
|
setError("URL or Path is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (url && !url.startsWith("http")) {
|
||||||
setError("Invalid URL");
|
setError("Invalid URL");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setError("");
|
setError("");
|
||||||
onImport({ name, desc: desc || "Custom imported TU", url });
|
onImport({ name, desc: desc || "Custom imported TU", url, path: path || undefined });
|
||||||
onClose();
|
onClose();
|
||||||
setName("");
|
setName("");
|
||||||
setDesc("");
|
setDesc("");
|
||||||
setUrl("");
|
setUrl("");
|
||||||
|
setPath("");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -114,19 +124,19 @@ export default function CustomTUModal({
|
|||||||
<div
|
<div
|
||||||
className="relative w-[400px] p-6 flex flex-col items-center"
|
className="relative w-[400px] p-6 flex flex-col items-center"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: "url('/images/Download_Background.png')",
|
backgroundImage: "url('/images/background.png')",
|
||||||
backgroundSize: "100% 100%",
|
backgroundSize: "100% 100%",
|
||||||
backgroundRepeat: "no-repeat",
|
backgroundRepeat: "no-repeat",
|
||||||
imageRendering: "pixelated",
|
imageRendering: "pixelated",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h2 className="text-xl text-white mc-text-shadow mb-4 text-center">
|
<h2 className="text-xl text-black mc-text-shadow mb-4 text-center">
|
||||||
{editingEdition ? "Edit Custom TU" : "Import Custom TU"}
|
{editingEdition ? "Edit Custom TU" : "Import Custom TU"}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="flex flex-col gap-4 w-full">
|
<div className="flex flex-col gap-4 w-full">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<label className="text-white text-sm mc-text-shadow uppercase tracking-widest">
|
<label className="text-gray text-sm mc-text-shadow uppercase tracking-widest">
|
||||||
TU Name
|
TU Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -142,7 +152,7 @@ export default function CustomTUModal({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<label className="text-white text-sm mc-text-shadow uppercase tracking-widest">
|
<label className="text-gray text-sm mc-text-shadow uppercase tracking-widest">
|
||||||
Description (Optional)
|
Description (Optional)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -157,7 +167,7 @@ export default function CustomTUModal({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<label className="text-white text-sm mc-text-shadow uppercase tracking-widest">
|
<label className="text-gray text-sm mc-text-shadow uppercase tracking-widest">
|
||||||
Download URL (.zip)
|
Download URL (.zip)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -165,12 +175,27 @@ export default function CustomTUModal({
|
|||||||
value={url}
|
value={url}
|
||||||
onChange={(e) => setUrl(e.target.value)}
|
onChange={(e) => setUrl(e.target.value)}
|
||||||
onFocus={() => setFocusIndex(2)}
|
onFocus={() => setFocusIndex(2)}
|
||||||
placeholder="https://example.com/mod.zip"
|
placeholder="optional if path is set"
|
||||||
className="w-full h-10 px-3 bg-black/40 border-2 border-[#373737] text-white text-base outline-none font-['Mojangles']"
|
className="w-full h-10 px-3 bg-black/40 border-2 border-[#373737] text-white text-base outline-none font-['Mojangles']"
|
||||||
style={{ imageRendering: "pixelated" }}
|
style={{ imageRendering: "pixelated" }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{path && (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<label className="text-gray text-sm mc-text-shadow uppercase tracking-widest">
|
||||||
|
Local Path
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readOnly
|
||||||
|
value={path}
|
||||||
|
className="w-full h-10 px-3 bg-black/20 border-2 border-[#222] text-black text-xs outline-none font-['Mojangles'] cursor-not-allowed"
|
||||||
|
style={{ imageRendering: "pixelated" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="text-red-500 text-center mc-text-shadow uppercase text-xs tracking-widest">
|
<div className="text-red-500 text-center mc-text-shadow uppercase text-xs tracking-widest">
|
||||||
{error}
|
{error}
|
||||||
|
|||||||
@@ -22,9 +22,8 @@ const DeleteConfirmButton = memo(function DeleteConfirmButton({
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onMouseEnter={() => setIsHovered(true)}
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
onMouseLeave={() => setIsHovered(false)}
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
className={`w-24 h-10 flex items-center justify-center mc-text-shadow transition-colors ${
|
className={`w-24 h-10 flex items-center justify-center mc-text-shadow transition-colors ${isDanger ? "text-red-500" : "text-white"
|
||||||
isDanger ? "text-red-500" : "text-white"
|
} ${isHovered ? (isDanger ? "text-red-400" : "text-[#FFFF55]") : ""}`}
|
||||||
} ${isHovered ? (isDanger ? "text-red-400" : "text-[#FFFF55]") : ""}`}
|
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: isHovered
|
backgroundImage: isHovered
|
||||||
? "url('/images/button_highlighted.png')"
|
? "url('/images/button_highlighted.png')"
|
||||||
@@ -43,22 +42,20 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
const { profile: selectedProfile, setProfile: setSelectedProfile, animationsEnabled } = useConfig();
|
const { profile: selectedProfile, setProfile: setSelectedProfile, animationsEnabled } = useConfig();
|
||||||
const { playPressSound, playBackSound } = useAudio();
|
const { playPressSound, playBackSound } = useAudio();
|
||||||
const { editions, installs: installedVersions, toggleInstall, handleUninstall, handleCancelDownload, deleteCustomEdition: onDeleteEdition, addCustomEdition: onAddEdition, updateCustomEdition: onUpdateEdition, downloadingId, downloadProgress } = useGame();
|
const { editions, installs: installedVersions, toggleInstall, handleUninstall, handleCancelDownload, deleteCustomEdition: onDeleteEdition, addCustomEdition: onAddEdition, updateCustomEdition: onUpdateEdition, downloadingId, downloadProgress } = useGame();
|
||||||
|
|
||||||
const [focusIndex, setFocusIndex] = useState<number>(0);
|
const [focusIndex, setFocusIndex] = useState<number>(0);
|
||||||
const [focusBtn, setFocusBtn] = useState<number>(0);
|
const [focusBtn, setFocusBtn] = useState<number>(0);
|
||||||
const [isImportModalOpen, setIsImportModalOpen] = useState(false);
|
const [isImportModalOpen, setIsImportModalOpen] = useState(false);
|
||||||
const [editingEdition, setEditingEdition] = useState<any>(null);
|
const [editingEdition, setEditingEdition] = useState<any>(null);
|
||||||
const [hoveredBtn, setHoveredBtn] = useState<{row: number, btn: string} | null>(null);
|
const [initialPath, setInitialPath] = useState<string>("");
|
||||||
|
const [hoveredBtn, setHoveredBtn] = useState<{ row: number, btn: string } | null>(null);
|
||||||
const [deleteConfirmEdition, setDeleteConfirmEdition] = useState<any>(null);
|
const [deleteConfirmEdition, setDeleteConfirmEdition] = useState<any>(null);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const listRef = useRef<HTMLDivElement>(null);
|
const listRef = useRef<HTMLDivElement>(null);
|
||||||
|
const ITEM_COUNT = editions.length + 3;
|
||||||
const ITEM_COUNT = editions.length + 2; // +1 for "+" button, +1 for Done button
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (document.activeElement?.tagName === "INPUT") return;
|
if (document.activeElement?.tagName === "INPUT") return;
|
||||||
|
|
||||||
if (e.key === "Escape" || e.key === "Backspace") {
|
if (e.key === "Escape" || e.key === "Backspace") {
|
||||||
playBackSound();
|
playBackSound();
|
||||||
setActiveView("main");
|
setActiveView("main");
|
||||||
@@ -139,6 +136,9 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
} else if (focusIndex === editions.length) {
|
} else if (focusIndex === editions.length) {
|
||||||
playPressSound();
|
playPressSound();
|
||||||
setIsImportModalOpen(true);
|
setIsImportModalOpen(true);
|
||||||
|
} else if (focusIndex === editions.length + 1) {
|
||||||
|
playPressSound();
|
||||||
|
handleImportFolder();
|
||||||
} else {
|
} else {
|
||||||
playBackSound();
|
playBackSound();
|
||||||
setActiveView("main");
|
setActiveView("main");
|
||||||
@@ -174,7 +174,7 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
|
|
||||||
const handleEditionClick = (edition: any, index: number) => {
|
const handleEditionClick = (edition: any, index: number) => {
|
||||||
const isInstalled = installedVersions.includes(edition.id);
|
const isInstalled = installedVersions.includes(edition.id);
|
||||||
|
|
||||||
if (isInstalled) {
|
if (isInstalled) {
|
||||||
playPressSound();
|
playPressSound();
|
||||||
setSelectedProfile(edition.id);
|
setSelectedProfile(edition.id);
|
||||||
@@ -182,6 +182,18 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
setFocusIndex(index);
|
setFocusIndex(index);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleImportFolder = async () => {
|
||||||
|
try {
|
||||||
|
const folder = await TauriService.pickFolder();
|
||||||
|
if (folder) {
|
||||||
|
setInitialPath(folder);
|
||||||
|
setIsImportModalOpen(true);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e !== "CANCELED") console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
@@ -195,7 +207,7 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
Versions
|
Versions
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="w-full min-w-[480px] p-6 mb-4"
|
className="w-full min-w-[480px] p-6 mb-4"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: "url('/images/background.png')",
|
backgroundImage: "url('/images/background.png')",
|
||||||
@@ -222,11 +234,9 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
<div
|
<div
|
||||||
key={edition.id}
|
key={edition.id}
|
||||||
data-index={i}
|
data-index={i}
|
||||||
className={`w-[calc(100%-16px)] mx-2 flex items-center gap-3 p-2 rounded-sm ${
|
className={`w-[calc(100%-16px)] mx-2 flex items-center gap-3 p-2 rounded-sm ${isSelected && !isComingSoon ? "bg-[#404040]/50" : ""
|
||||||
isSelected && !isComingSoon ? "bg-[#404040]/50" : ""
|
} ${isFocused && !isComingSoon ? "ring-2 ring-white" : ""} ${isComingSoon ? "opacity-50 cursor-not-allowed" : ""
|
||||||
} ${isFocused && !isComingSoon ? "ring-2 ring-white" : ""} ${
|
}`}
|
||||||
isComingSoon ? "opacity-50 cursor-not-allowed" : ""
|
|
||||||
}`}
|
|
||||||
onMouseEnter={() => !isComingSoon && setFocusIndex(i)}
|
onMouseEnter={() => !isComingSoon && setFocusIndex(i)}
|
||||||
>
|
>
|
||||||
<div className="w-6 flex items-center justify-center flex-shrink-0">
|
<div className="w-6 flex items-center justify-center flex-shrink-0">
|
||||||
@@ -261,13 +271,12 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => !isComingSoon && handleEditionClick(edition, i)}
|
onClick={() => !isComingSoon && handleEditionClick(edition, i)}
|
||||||
disabled={isComingSoon}
|
disabled={isComingSoon}
|
||||||
className={`flex-1 text-left min-w-0 outline-none rounded ${
|
className={`flex-1 text-left min-w-0 outline-none rounded ${focusIndex === i && focusBtn === 0 && !isComingSoon ? "ring-2 ring-white" : ""
|
||||||
focusIndex === i && focusBtn === 0 && !isComingSoon ? "ring-2 ring-white" : ""
|
} ${isComingSoon ? "cursor-not-allowed" : ""}`}
|
||||||
} ${isComingSoon ? "cursor-not-allowed" : ""}`}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{edition.logo && (
|
{edition.logo && (
|
||||||
<img
|
<img
|
||||||
src={edition.logo}
|
src={edition.logo}
|
||||||
alt=""
|
alt=""
|
||||||
className="w-5 h-5 object-contain flex-shrink-0"
|
className="w-5 h-5 object-contain flex-shrink-0"
|
||||||
@@ -275,9 +284,8 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<span
|
<span
|
||||||
className={`text-xl tracking-wide truncate ${
|
className={`text-xl tracking-wide truncate ${isSelected ? "text-white" : "text-black"
|
||||||
isSelected ? "text-white" : "text-black"
|
}`}
|
||||||
}`}
|
|
||||||
style={{ textShadow: "none" }}
|
style={{ textShadow: "none" }}
|
||||||
>
|
>
|
||||||
{edition.name}
|
{edition.name}
|
||||||
@@ -288,9 +296,8 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className={`text-base font-medium leading-tight ${
|
<p className={`text-base font-medium leading-tight ${isSelected ? "text-[#DDDDDD]" : "text-[#666666]"
|
||||||
isSelected ? "text-[#DDDDDD]" : "text-[#666666]"
|
}`}>
|
||||||
}`}>
|
|
||||||
{edition.desc}
|
{edition.desc}
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
@@ -303,7 +310,7 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleCancelDownload();
|
handleCancelDownload();
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => setHoveredBtn({row: i, btn: 'cancel'})}
|
onMouseEnter={() => setHoveredBtn({ row: i, btn: 'cancel' })}
|
||||||
onMouseLeave={() => setHoveredBtn(null)}
|
onMouseLeave={() => setHoveredBtn(null)}
|
||||||
className="w-8 h-8 flex items-center justify-center text-red-600"
|
className="w-8 h-8 flex items-center justify-center text-red-600"
|
||||||
style={{
|
style={{
|
||||||
@@ -315,7 +322,7 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="square">
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="square">
|
||||||
<path d="M18 6L6 18M6 6l12 12"/>
|
<path d="M18 6L6 18M6 6l12 12" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
) : edition.comingSoon ? (
|
) : edition.comingSoon ? (
|
||||||
@@ -326,7 +333,7 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!downloadingId) toggleInstall(edition.id);
|
if (!downloadingId) toggleInstall(edition.id);
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => setHoveredBtn({row: i, btn: 'download'})}
|
onMouseEnter={() => setHoveredBtn({ row: i, btn: 'download' })}
|
||||||
onMouseLeave={() => setHoveredBtn(null)}
|
onMouseLeave={() => setHoveredBtn(null)}
|
||||||
className={`w-8 h-8 flex items-center justify-center ${downloadingId ? "text-gray-400 cursor-not-allowed" : "text-[#3a3a3a]"}`}
|
className={`w-8 h-8 flex items-center justify-center ${downloadingId ? "text-gray-400 cursor-not-allowed" : "text-[#3a3a3a]"}`}
|
||||||
style={{
|
style={{
|
||||||
@@ -355,7 +362,7 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleCancelDownload();
|
handleCancelDownload();
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => setHoveredBtn({row: i, btn: 'cancel'})}
|
onMouseEnter={() => setHoveredBtn({ row: i, btn: 'cancel' })}
|
||||||
onMouseLeave={() => setHoveredBtn(null)}
|
onMouseLeave={() => setHoveredBtn(null)}
|
||||||
className="w-8 h-8 flex items-center justify-center text-red-600"
|
className="w-8 h-8 flex items-center justify-center text-red-600"
|
||||||
style={{
|
style={{
|
||||||
@@ -367,7 +374,7 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="square">
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="square">
|
||||||
<path d="M18 6L6 18M6 6l12 12"/>
|
<path d="M18 6L6 18M6 6l12 12" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
@@ -377,7 +384,7 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!downloadingId) toggleInstall(edition.id);
|
if (!downloadingId) toggleInstall(edition.id);
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => setHoveredBtn({row: i, btn: 'update'})}
|
onMouseEnter={() => setHoveredBtn({ row: i, btn: 'update' })}
|
||||||
onMouseLeave={() => setHoveredBtn(null)}
|
onMouseLeave={() => setHoveredBtn(null)}
|
||||||
className={`w-8 h-8 flex items-center justify-center ${downloadingId ? "text-gray-400 cursor-not-allowed" : "text-[#3a3a3a]"}`}
|
className={`w-8 h-8 flex items-center justify-center ${downloadingId ? "text-gray-400 cursor-not-allowed" : "text-[#3a3a3a]"}`}
|
||||||
style={{
|
style={{
|
||||||
@@ -403,82 +410,33 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
playPressSound();
|
playPressSound();
|
||||||
TauriService.openInstanceFolder(edition.id);
|
TauriService.openInstanceFolder(edition.id);
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => setHoveredBtn({row: i, btn: 'folder'})}
|
onMouseEnter={() => setHoveredBtn({ row: i, btn: 'folder' })}
|
||||||
onMouseLeave={() => setHoveredBtn(null)}
|
|
||||||
className="w-8 h-8 flex items-center justify-center text-[#3a3a3a]"
|
|
||||||
style={{
|
|
||||||
backgroundImage: (hoveredBtn?.row === i && hoveredBtn?.btn === 'folder') || (focusIndex === i && focusBtn === 2)
|
|
||||||
? "url('/images/Button_Square_Highlighted.png')"
|
|
||||||
: "url('/images/Button_Square.png')",
|
|
||||||
backgroundSize: "100% 100%",
|
|
||||||
imageRendering: "pixelated",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="/images/Folder_Icon.png"
|
|
||||||
alt="Folder"
|
|
||||||
className="w-6 h-6 object-contain"
|
|
||||||
style={{ imageRendering: "pixelated" }}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
playBackSound();
|
|
||||||
setDeleteConfirmEdition(edition);
|
|
||||||
}}
|
|
||||||
onMouseEnter={() => setHoveredBtn({row: i, btn: 'delete'})}
|
|
||||||
onMouseLeave={() => setHoveredBtn(null)}
|
|
||||||
className="w-8 h-8 flex items-center justify-center text-[#3a3a3a]"
|
|
||||||
style={{
|
|
||||||
backgroundImage: (hoveredBtn?.row === i && hoveredBtn?.btn === 'delete') || (focusIndex === i && focusBtn === 3)
|
|
||||||
? "url('/images/Button_Square_Highlighted.png')"
|
|
||||||
: "url('/images/Button_Square.png')",
|
|
||||||
backgroundSize: "100% 100%",
|
|
||||||
imageRendering: "pixelated",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="/images/Trash_Bin_Icon.png"
|
|
||||||
alt="Delete"
|
|
||||||
className="w-6 h-6 object-contain"
|
|
||||||
style={{ imageRendering: "pixelated" }}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
{isCustom && (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
playPressSound();
|
|
||||||
setEditingEdition(edition);
|
|
||||||
setIsImportModalOpen(true);
|
|
||||||
}}
|
|
||||||
onMouseEnter={() => setHoveredBtn({row: i, btn: 'edit'})}
|
|
||||||
onMouseLeave={() => setHoveredBtn(null)}
|
onMouseLeave={() => setHoveredBtn(null)}
|
||||||
className="w-8 h-8 flex items-center justify-center text-[#3a3a3a]"
|
className="w-8 h-8 flex items-center justify-center text-[#3a3a3a]"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: (hoveredBtn?.row === i && hoveredBtn?.btn === 'edit') || (focusIndex === i && focusBtn === 4)
|
backgroundImage: (hoveredBtn?.row === i && hoveredBtn?.btn === 'folder') || (focusIndex === i && focusBtn === 2)
|
||||||
? "url('/images/Button_Square_Highlighted.png')"
|
? "url('/images/Button_Square_Highlighted.png')"
|
||||||
: "url('/images/Button_Square.png')",
|
: "url('/images/Button_Square.png')",
|
||||||
backgroundSize: "100% 100%",
|
backgroundSize: "100% 100%",
|
||||||
imageRendering: "pixelated",
|
imageRendering: "pixelated",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="square">
|
<img
|
||||||
<path d="M12 20h9"/>
|
src="/images/Folder_Icon.png"
|
||||||
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
|
alt="Folder"
|
||||||
</svg>
|
className="w-6 h-6 object-contain"
|
||||||
|
style={{ imageRendering: "pixelated" }}
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
playBackSound();
|
playBackSound();
|
||||||
onDeleteEdition(edition.id);
|
setDeleteConfirmEdition(edition);
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => setHoveredBtn({row: i, btn: 'delete'})}
|
onMouseEnter={() => setHoveredBtn({ row: i, btn: 'delete' })}
|
||||||
onMouseLeave={() => setHoveredBtn(null)}
|
onMouseLeave={() => setHoveredBtn(null)}
|
||||||
className="w-8 h-8 flex items-center justify-center text-red-600"
|
className="w-8 h-8 flex items-center justify-center text-[#3a3a3a]"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: (hoveredBtn?.row === i && hoveredBtn?.btn === 'delete') || (focusIndex === i && focusBtn === 3)
|
backgroundImage: (hoveredBtn?.row === i && hoveredBtn?.btn === 'delete') || (focusIndex === i && focusBtn === 3)
|
||||||
? "url('/images/Button_Square_Highlighted.png')"
|
? "url('/images/Button_Square_Highlighted.png')"
|
||||||
@@ -487,29 +445,79 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
imageRendering: "pixelated",
|
imageRendering: "pixelated",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="square">
|
<img
|
||||||
<polyline points="3 6 5 6 21 6"/>
|
src="/images/Trash_Bin_Icon.png"
|
||||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
|
alt="Delete"
|
||||||
</svg>
|
className="w-6 h-6 object-contain"
|
||||||
|
style={{ imageRendering: "pixelated" }}
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
{isCustom && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
playPressSound();
|
||||||
|
setEditingEdition(edition);
|
||||||
|
setIsImportModalOpen(true);
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setHoveredBtn({ row: i, btn: 'edit' })}
|
||||||
|
onMouseLeave={() => setHoveredBtn(null)}
|
||||||
|
className="w-8 h-8 flex items-center justify-center text-[#3a3a3a]"
|
||||||
|
style={{
|
||||||
|
backgroundImage: (hoveredBtn?.row === i && hoveredBtn?.btn === 'edit') || (focusIndex === i && focusBtn === 4)
|
||||||
|
? "url('/images/Button_Square_Highlighted.png')"
|
||||||
|
: "url('/images/Button_Square.png')",
|
||||||
|
backgroundSize: "100% 100%",
|
||||||
|
imageRendering: "pixelated",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="square">
|
||||||
|
<path d="M12 20h9" />
|
||||||
|
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
playBackSound();
|
||||||
|
onDeleteEdition(edition.id);
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setHoveredBtn({ row: i, btn: 'delete' })}
|
||||||
|
onMouseLeave={() => setHoveredBtn(null)}
|
||||||
|
className="w-8 h-8 flex items-center justify-center text-red-600"
|
||||||
|
style={{
|
||||||
|
backgroundImage: (hoveredBtn?.row === i && hoveredBtn?.btn === 'delete') || (focusIndex === i && focusBtn === 3)
|
||||||
|
? "url('/images/Button_Square_Highlighted.png')"
|
||||||
|
: "url('/images/Button_Square.png')",
|
||||||
|
backgroundSize: "100% 100%",
|
||||||
|
imageRendering: "pixelated",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="square">
|
||||||
|
<polyline points="3 6 5 6 21 6" />
|
||||||
|
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<div className="w-full flex items-center justify-center p-2 mt-1">
|
<div className="w-full flex items-center justify-center gap-4 p-2 mt-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
playPressSound();
|
playPressSound();
|
||||||
|
setInitialPath("");
|
||||||
setIsImportModalOpen(true);
|
setIsImportModalOpen(true);
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => setHoveredBtn({row: editions.length, btn: 'add'})}
|
onMouseEnter={() => setFocusIndex(editions.length)}
|
||||||
onMouseLeave={() => setHoveredBtn(null)}
|
onMouseLeave={() => setHoveredBtn(null)}
|
||||||
className="w-8 h-8 flex items-center justify-center text-[#3a3a3a]"
|
className="w-8 h-8 flex items-center justify-center text-[#3a3a3a]"
|
||||||
style={{
|
style={{
|
||||||
@@ -521,9 +529,34 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="square">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="square">
|
||||||
<path d="M12 5v14M5 12h14"/>
|
<path d="M12 5v14M5 12h14" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
playPressSound();
|
||||||
|
handleImportFolder();
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setFocusIndex(editions.length + 1)}
|
||||||
|
onMouseLeave={() => setHoveredBtn(null)}
|
||||||
|
title="Import Custom TU"
|
||||||
|
className="w-8 h-8 flex items-center justify-center text-[#3a3a3a]"
|
||||||
|
style={{
|
||||||
|
backgroundImage: (hoveredBtn?.row === editions.length && hoveredBtn?.btn === 'folder_import') || focusIndex === editions.length + 1
|
||||||
|
? "url('/images/Button_Square_Highlighted.png')"
|
||||||
|
: "url('/images/Button_Square.png')",
|
||||||
|
backgroundSize: "100% 100%",
|
||||||
|
imageRendering: "pixelated",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/images/Folder_Icon.png"
|
||||||
|
alt="Import Custom TU"
|
||||||
|
className="w-5 h-5 object-contain"
|
||||||
|
style={{ imageRendering: "pixelated" }}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -531,15 +564,15 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
|
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<button
|
<button
|
||||||
data-index={editions.length + 1}
|
data-index={editions.length + 2}
|
||||||
onMouseEnter={() => setFocusIndex(editions.length + 1)}
|
onMouseEnter={() => setFocusIndex(editions.length + 2)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
playBackSound();
|
playBackSound();
|
||||||
setActiveView("main");
|
setActiveView("main");
|
||||||
}}
|
}}
|
||||||
className="w-48 h-10 flex items-center justify-center text-xl mc-text-shadow outline-none border-none text-white"
|
className="w-48 h-10 flex items-center justify-center text-xl mc-text-shadow outline-none border-none text-white"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: focusIndex === editions.length + 1
|
backgroundImage: focusIndex === editions.length + 2
|
||||||
? "url('/images/button_highlighted.png')"
|
? "url('/images/button_highlighted.png')"
|
||||||
: "url('/images/Button_Background.png')",
|
: "url('/images/Button_Background.png')",
|
||||||
backgroundSize: "100% 100%",
|
backgroundSize: "100% 100%",
|
||||||
@@ -555,6 +588,7 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
onClose={() => {
|
onClose={() => {
|
||||||
setIsImportModalOpen(false);
|
setIsImportModalOpen(false);
|
||||||
setEditingEdition(null);
|
setEditingEdition(null);
|
||||||
|
setInitialPath("");
|
||||||
}}
|
}}
|
||||||
onImport={(ed: any) => {
|
onImport={(ed: any) => {
|
||||||
if (editingEdition) {
|
if (editingEdition) {
|
||||||
@@ -567,6 +601,7 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
playPressSound={playPressSound}
|
playPressSound={playPressSound}
|
||||||
playBackSound={playBackSound}
|
playBackSound={playBackSound}
|
||||||
editingEdition={editingEdition}
|
editingEdition={editingEdition}
|
||||||
|
initialPath={initialPath}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{deleteConfirmEdition && (
|
{deleteConfirmEdition && (
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ export function useGameManager({
|
|||||||
}, [profile]);
|
}, [profile]);
|
||||||
|
|
||||||
const addCustomEdition = useCallback(
|
const addCustomEdition = useCallback(
|
||||||
(edition: { name: string; desc: string; url: string }) => {
|
(edition: { name: string; desc: string; url: string; path?: string }) => {
|
||||||
const id = `custom_${Date.now()}`;
|
const id = `custom_${Date.now()}`;
|
||||||
const newEdition = {
|
const newEdition = {
|
||||||
...edition,
|
...edition,
|
||||||
@@ -240,7 +240,7 @@ export function useGameManager({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const updateCustomEdition = useCallback(
|
const updateCustomEdition = useCallback(
|
||||||
(id: string, updated: { name: string; desc: string; url: string }) => {
|
(id: string, updated: { name: string; desc: string; url: string; path?: string }) => {
|
||||||
setCustomEditions(
|
setCustomEditions(
|
||||||
customEditions.map((e) => (e.id === id ? { ...e, ...updated } : e)),
|
customEditions.map((e) => (e.id === id ? { ...e, ...updated } : e)),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export interface CustomEdition {
|
|||||||
name: string;
|
name: string;
|
||||||
desc: string;
|
desc: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
path?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppConfig {
|
export interface AppConfig {
|
||||||
@@ -198,4 +199,8 @@ export class TauriService {
|
|||||||
static async checkGameUpdate(instanceId: string, url: string): Promise<boolean> {
|
static async checkGameUpdate(instanceId: string, url: string): Promise<boolean> {
|
||||||
return invoke("check_game_update", { instanceId, url });
|
return invoke("check_game_update", { instanceId, url });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async pickFolder(): Promise<string> {
|
||||||
|
return invoke("pick_folder");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user