import { useState, useEffect } from "react"; import { PanelSectionRow, Dropdown, DropdownOption, showModal, ConfirmModal, Field, DialogButton, ButtonItem, ModalRoot, TextField, Focusable, AppOverview, Router } from "@decky/ui"; import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri"; import { getProfiles, createProfile, deleteProfile, renameProfile, setCurrentProfile, ProfilesResult, ProfileResult } from "../api/lsfgApi"; import { showSuccessToast, showErrorToast } from "../utils/toastUtils"; const PROFILES_COLLAPSED_KEY = 'lsfg-profiles-collapsed'; interface TextInputModalProps { title: string; description: string; defaultValue?: string; okText?: string; cancelText?: string; onOK: (value: string) => void; closeModal?: () => void; } function TextInputModal({ title, description, defaultValue = "", okText = "OK", cancelText = "Cancel", onOK, closeModal }: TextInputModalProps) { const [value, setValue] = useState(defaultValue); const handleOK = () => { if (value.trim()) { onOK(value); closeModal?.(); } }; return (

{title}

{description}

setValue(e?.target?.value || "")} style={{ width: "100%" }} />
{cancelText} {okText}
); } interface ProfileManagementProps { currentProfile?: string; onProfileChange?: (profileName: string) => void; } export function ProfileManagement({ currentProfile, onProfileChange }: ProfileManagementProps) { const [profiles, setProfiles] = useState([]); const [selectedProfile, setSelectedProfile] = useState(currentProfile || "decky-lsfg-vk"); const [isLoading, setIsLoading] = useState(false); const [mainRunningApp, setMainRunningApp] = useState(undefined); // Initialize with localStorage value, fallback to false (expanded) if not found const [profilesCollapsed, setProfilesCollapsed] = useState(() => { try { const saved = localStorage.getItem(PROFILES_COLLAPSED_KEY); return saved !== null ? JSON.parse(saved) : false; } catch { return false; } }); // Persist profiles collapse state to localStorage useEffect(() => { try { localStorage.setItem(PROFILES_COLLAPSED_KEY, JSON.stringify(profilesCollapsed)); } catch (error) { console.warn('Failed to save profiles collapse state:', error); } }, [profilesCollapsed]); // Load profiles on component mount useEffect(() => { loadProfiles(); }, []); // Update selected profile when prop changes useEffect(() => { if (currentProfile) { setSelectedProfile(currentProfile); } }, [currentProfile]); // Poll for running app every 2 seconds useEffect(() => { const checkRunningApp = () => { setMainRunningApp(Router.MainRunningApp); }; // Check immediately checkRunningApp(); // Set up polling interval const interval = setInterval(checkRunningApp, 2000); // Cleanup interval on unmount return () => clearInterval(interval); }, []); const loadProfiles = async () => { try { const result: ProfilesResult = await getProfiles(); if (result.success && result.profiles) { setProfiles(result.profiles); if (result.current_profile) { setSelectedProfile(result.current_profile); } } else { console.error("Failed to load profiles:", result.error); showErrorToast("Failed to load profiles", result.error || "Unknown error"); } } catch (error) { console.error("Error loading profiles:", error); showErrorToast("Error loading profiles", String(error)); } }; const handleProfileChange = async (profileName: string) => { setIsLoading(true); try { const result: ProfileResult = await setCurrentProfile(profileName); if (result.success) { setSelectedProfile(profileName); showSuccessToast("Profile switched", `Switched to profile: ${profileName}`); onProfileChange?.(profileName); } else { console.error("Failed to switch profile:", result.error); showErrorToast("Failed to switch profile", result.error || "Unknown error"); } } catch (error) { console.error("Error switching profile:", error); showErrorToast("Error switching profile", String(error)); } finally { setIsLoading(false); } }; const handleCreateProfile = () => { showModal( { if (name.trim()) { createNewProfile(name.trim()); } }} /> ); }; const createNewProfile = async (profileName: string) => { setIsLoading(true); try { const result: ProfileResult = await createProfile(profileName, selectedProfile); if (result.success) { showSuccessToast("Profile created", `Created profile: ${profileName}`); await loadProfiles(); // Automatically switch to the newly created profile await handleProfileChange(profileName); } else { console.error("Failed to create profile:", result.error); showErrorToast("Failed to create profile", result.error || "Unknown error"); } } catch (error) { console.error("Error creating profile:", error); showErrorToast("Error creating profile", String(error)); } finally { setIsLoading(false); } }; const handleDeleteProfile = () => { if (selectedProfile === "decky-lsfg-vk") { showErrorToast("Cannot delete default profile", "The default profile cannot be deleted"); return; } showModal( deleteSelectedProfile()} /> ); }; const deleteSelectedProfile = async () => { setIsLoading(true); try { const result: ProfileResult = await deleteProfile(selectedProfile); if (result.success) { showSuccessToast("Profile deleted", `Deleted profile: ${selectedProfile}`); await loadProfiles(); // If we deleted the current profile, it should have switched to default setSelectedProfile("decky-lsfg-vk"); onProfileChange?.("decky-lsfg-vk"); } else { console.error("Failed to delete profile:", result.error); showErrorToast("Failed to delete profile", result.error || "Unknown error"); } } catch (error) { console.error("Error deleting profile:", error); showErrorToast("Error deleting profile", String(error)); } finally { setIsLoading(false); } }; const handleDropdownChange = (option: DropdownOption) => { if (option.data === "__NEW_PROFILE__") { handleCreateProfile(); } else { handleProfileChange(option.data); } }; const handleRenameProfile = () => { if (selectedProfile === "decky-lsfg-vk") { showErrorToast("Cannot rename default profile", "The default profile cannot be renamed"); return; } showModal( { if (newName.trim() && newName.trim() !== selectedProfile) { renameSelectedProfile(newName.trim()); } }} /> ); }; const renameSelectedProfile = async (newName: string) => { setIsLoading(true); try { const result: ProfileResult = await renameProfile(selectedProfile, newName); if (result.success) { showSuccessToast("Profile renamed", `Renamed profile to: ${newName}`); await loadProfiles(); setSelectedProfile(newName); onProfileChange?.(newName); } else { console.error("Failed to rename profile:", result.error); showErrorToast("Failed to rename profile", result.error || "Unknown error"); } } catch (error) { console.error("Error renaming profile:", error); showErrorToast("Error renaming profile", String(error)); } finally { setIsLoading(false); } }; const profileOptions: DropdownOption[] = [ ...profiles.map((profile: string) => ({ data: profile, label: profile === "decky-lsfg-vk" ? "Default" : profile })), { data: "__NEW_PROFILE__", label: "New Profile" } ]; return ( <>
Select Profile
setProfilesCollapsed(!profilesCollapsed)} > {profilesCollapsed ? ( ) : ( )}
{!profilesCollapsed && ( <> {/* Display currently running game info */} {mainRunningApp && (
{mainRunningApp.display_name} running. Close game to change profile.
)} Rename Delete )} ); }