From 4104e28053fc03b3875958c7bf56ec6fbc5aab84 Mon Sep 17 00:00:00 2001
From: xXJSONDeruloXx <danielhimebauch@gmail.com>
Date: Sat, 16 Aug 2025 12:05:10 -0400
Subject: initial prof selector and creator ui

---
 src/components/ProfileManagement.tsx | 307 +++++++++++++++++++++++++++++++++++
 1 file changed, 307 insertions(+)
 create mode 100644 src/components/ProfileManagement.tsx

(limited to 'src/components/ProfileManagement.tsx')

diff --git a/src/components/ProfileManagement.tsx b/src/components/ProfileManagement.tsx
new file mode 100644
index 0000000..a499836
--- /dev/null
+++ b/src/components/ProfileManagement.tsx
@@ -0,0 +1,307 @@
+import { useState, useEffect } from "react";
+import {
+  PanelSection,
+  PanelSectionRow,
+  Dropdown,
+  DropdownOption,
+  showModal,
+  ConfirmModal,
+  Field,
+  DialogButton,
+  Focusable,
+  ModalRoot,
+  TextField
+} from "@decky/ui";
+import { 
+  getProfiles, 
+  createProfile, 
+  deleteProfile, 
+  renameProfile, 
+  setCurrentProfile,
+  ProfilesResult,
+  ProfileResult
+} from "../api/lsfgApi";
+import { showSuccessToast, showErrorToast } from "../utils/toastUtils";
+
+interface ProfileManagementProps {
+  currentProfile?: string;
+  onProfileChange?: (profileName: string) => void;
+}
+
+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 = () => {
+    onOK(value);
+    closeModal?.();
+  };
+
+  return (
+    <ModalRoot>
+      <div style={{ padding: "16px", minWidth: "300px" }}>
+        <h2 style={{ marginBottom: "16px" }}>{title}</h2>
+        <p style={{ marginBottom: "16px" }}>{description}</p>
+        
+        <Field label="Name">
+          <TextField
+            value={value}
+            onChange={(e) => setValue(e?.target?.value || "")}
+          />
+        </Field>
+        
+        <div style={{ display: "flex", gap: "8px", marginTop: "16px" }}>
+          <DialogButton onClick={handleOK} disabled={!value.trim()}>
+            {okText}
+          </DialogButton>
+          <DialogButton onClick={closeModal}>
+            {cancelText}
+          </DialogButton>
+        </div>
+      </div>
+    </ModalRoot>
+  );
+}
+
+interface ProfileManagementProps {
+  currentProfile?: string;
+  onProfileChange?: (profileName: string) => void;
+}
+
+export function ProfileManagement({ currentProfile, onProfileChange }: ProfileManagementProps) {
+  const [profiles, setProfiles] = useState<string[]>([]);
+  const [selectedProfile, setSelectedProfile] = useState<string>(currentProfile || "decky-lsfg-vk");
+  const [isLoading, setIsLoading] = useState(false);
+
+  // Load profiles on component mount
+  useEffect(() => {
+    loadProfiles();
+  }, []);
+
+  // Update selected profile when prop changes
+  useEffect(() => {
+    if (currentProfile) {
+      setSelectedProfile(currentProfile);
+    }
+  }, [currentProfile]);
+
+  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(
+      <TextInputModal
+        title="Create New Profile"
+        description="Enter a name for the new profile. The current profile's settings will be copied."
+        okText="Create"
+        cancelText="Cancel"
+        onOK={(name: string) => {
+          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();
+      } 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(
+      <ConfirmModal
+        strTitle="Delete Profile"
+        strDescription={`Are you sure you want to delete the profile "${selectedProfile}"? This action cannot be undone.`}
+        strOKButtonText="Delete"
+        strCancelButtonText="Cancel"
+        onOK={() => 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 handleRenameProfile = () => {
+    if (selectedProfile === "decky-lsfg-vk") {
+      showErrorToast("Cannot rename default profile", "The default profile cannot be renamed");
+      return;
+    }
+
+    showModal(
+      <TextInputModal
+        title="Rename Profile"
+        description={`Enter a new name for the profile "${selectedProfile}".`}
+        defaultValue={selectedProfile}
+        okText="Rename"
+        cancelText="Cancel"
+        onOK={(newName: string) => {
+          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 => ({
+    data: profile,
+    label: profile === "decky-lsfg-vk" ? `${profile} (default)` : profile
+  }));
+
+  return (
+    <PanelSection title="Profile Management">
+      <PanelSectionRow>
+        <Field label="Current Profile">
+          <Dropdown
+            rgOptions={profileOptions}
+            selectedOption={selectedProfile}
+            onChange={(option) => handleProfileChange(option.data)}
+            disabled={isLoading}
+          />
+        </Field>
+      </PanelSectionRow>
+
+      <PanelSectionRow>
+        <Focusable style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
+          <DialogButton
+            onClick={handleCreateProfile}
+            disabled={isLoading}
+            style={{ flex: "1", minWidth: "120px" }}
+          >
+            New Profile
+          </DialogButton>
+          
+          <DialogButton
+            onClick={handleRenameProfile}
+            disabled={isLoading || selectedProfile === "decky-lsfg-vk"}
+            style={{ flex: "1", minWidth: "120px" }}
+          >
+            Rename
+          </DialogButton>
+          
+          <DialogButton
+            onClick={handleDeleteProfile}
+            disabled={isLoading || selectedProfile === "decky-lsfg-vk"}
+            style={{ flex: "1", minWidth: "120px" }}
+          >
+            Delete
+          </DialogButton>
+        </Focusable>
+      </PanelSectionRow>
+    </PanelSection>
+  );
+}
-- 
cgit v1.2.3


From 149ecf2d8bf5eaacb2e71d7006d3b38f15b91104 Mon Sep 17 00:00:00 2001
From: xXJSONDeruloXx <danielhimebauch@gmail.com>
Date: Sat, 16 Aug 2025 12:27:11 -0400
Subject: fix: stuck ui states on newly created profile in conf items

---
 src/components/ProfileManagement.tsx | 2 ++
 1 file changed, 2 insertions(+)

(limited to 'src/components/ProfileManagement.tsx')

diff --git a/src/components/ProfileManagement.tsx b/src/components/ProfileManagement.tsx
index a499836..83b5fa4 100644
--- a/src/components/ProfileManagement.tsx
+++ b/src/components/ProfileManagement.tsx
@@ -163,6 +163,8 @@ export function ProfileManagement({ currentProfile, onProfileChange }: ProfileMa
       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");
-- 
cgit v1.2.3


From 4c6939b1938f044a7511f617406e35de463c1b35 Mon Sep 17 00:00:00 2001
From: xXJSONDeruloXx <danielhimebauch@gmail.com>
Date: Sat, 16 Aug 2025 12:39:32 -0400
Subject: fix styling of prof buttons

---
 src/components/ProfileManagement.tsx | 68 +++++++++++++++++++-----------------
 1 file changed, 36 insertions(+), 32 deletions(-)

(limited to 'src/components/ProfileManagement.tsx')

diff --git a/src/components/ProfileManagement.tsx b/src/components/ProfileManagement.tsx
index 83b5fa4..d5e5521 100644
--- a/src/components/ProfileManagement.tsx
+++ b/src/components/ProfileManagement.tsx
@@ -8,7 +8,7 @@ import {
   ConfirmModal,
   Field,
   DialogButton,
-  Focusable,
+  ButtonItem,
   ModalRoot,
   TextField
 } from "@decky/ui";
@@ -268,41 +268,45 @@ export function ProfileManagement({ currentProfile, onProfileChange }: ProfileMa
     <PanelSection title="Profile Management">
       <PanelSectionRow>
         <Field label="Current Profile">
-          <Dropdown
-            rgOptions={profileOptions}
-            selectedOption={selectedProfile}
-            onChange={(option) => handleProfileChange(option.data)}
-            disabled={isLoading}
-          />
+          <div style={{ width: "100%" }}>
+            <Dropdown
+              rgOptions={profileOptions}
+              selectedOption={selectedProfile}
+              onChange={(option) => handleProfileChange(option.data)}
+              disabled={isLoading}
+            />
+          </div>
         </Field>
       </PanelSectionRow>
 
       <PanelSectionRow>
-        <Focusable style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
-          <DialogButton
-            onClick={handleCreateProfile}
-            disabled={isLoading}
-            style={{ flex: "1", minWidth: "120px" }}
-          >
-            New Profile
-          </DialogButton>
-          
-          <DialogButton
-            onClick={handleRenameProfile}
-            disabled={isLoading || selectedProfile === "decky-lsfg-vk"}
-            style={{ flex: "1", minWidth: "120px" }}
-          >
-            Rename
-          </DialogButton>
-          
-          <DialogButton
-            onClick={handleDeleteProfile}
-            disabled={isLoading || selectedProfile === "decky-lsfg-vk"}
-            style={{ flex: "1", minWidth: "120px" }}
-          >
-            Delete
-          </DialogButton>
-        </Focusable>
+        <ButtonItem
+          layout="below"
+          onClick={handleCreateProfile}
+          disabled={isLoading}
+        >
+          New Profile
+        </ButtonItem>
+      </PanelSectionRow>
+      
+      <PanelSectionRow>
+        <ButtonItem
+          layout="below"
+          onClick={handleRenameProfile}
+          disabled={isLoading || selectedProfile === "decky-lsfg-vk"}
+        >
+          Rename
+        </ButtonItem>
+      </PanelSectionRow>
+      
+      <PanelSectionRow>
+        <ButtonItem
+          layout="below"
+          onClick={handleDeleteProfile}
+          disabled={isLoading || selectedProfile === "decky-lsfg-vk"}
+        >
+          Delete
+        </ButtonItem>
       </PanelSectionRow>
     </PanelSection>
   );
-- 
cgit v1.2.3


From ef9ef1b2bbd9fa34375578b4091a8729d25c94ab Mon Sep 17 00:00:00 2001
From: xXJSONDeruloXx <danielhimebauch@gmail.com>
Date: Sat, 16 Aug 2025 15:11:56 -0400
Subject: rm unneeded line in dd for profiles

---
 src/components/ProfileManagement.tsx | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

(limited to 'src/components/ProfileManagement.tsx')

diff --git a/src/components/ProfileManagement.tsx b/src/components/ProfileManagement.tsx
index d5e5521..79d3aa1 100644
--- a/src/components/ProfileManagement.tsx
+++ b/src/components/ProfileManagement.tsx
@@ -265,10 +265,9 @@ export function ProfileManagement({ currentProfile, onProfileChange }: ProfileMa
   }));
 
   return (
-    <PanelSection title="Profile Management">
+    <PanelSection title="Select Profile">
       <PanelSectionRow>
-        <Field label="Current Profile">
-          <div style={{ width: "100%" }}>
+          <div>
             <Dropdown
               rgOptions={profileOptions}
               selectedOption={selectedProfile}
@@ -276,7 +275,6 @@ export function ProfileManagement({ currentProfile, onProfileChange }: ProfileMa
               disabled={isLoading}
             />
           </div>
-        </Field>
       </PanelSectionRow>
 
       <PanelSectionRow>
-- 
cgit v1.2.3


From 79cd9296e9aaf2453030b014712e95312f201514 Mon Sep 17 00:00:00 2001
From: xXJSONDeruloXx <danielhimebauch@gmail.com>
Date: Sat, 16 Aug 2025 15:29:04 -0400
Subject: fix width of DD after rm of label

---
 src/components/ProfileManagement.tsx | 20 ++++++++++++--------
 1 file changed, 12 insertions(+), 8 deletions(-)

(limited to 'src/components/ProfileManagement.tsx')

diff --git a/src/components/ProfileManagement.tsx b/src/components/ProfileManagement.tsx
index 79d3aa1..709a28c 100644
--- a/src/components/ProfileManagement.tsx
+++ b/src/components/ProfileManagement.tsx
@@ -267,14 +267,18 @@ export function ProfileManagement({ currentProfile, onProfileChange }: ProfileMa
   return (
     <PanelSection title="Select Profile">
       <PanelSectionRow>
-          <div>
-            <Dropdown
-              rgOptions={profileOptions}
-              selectedOption={selectedProfile}
-              onChange={(option) => handleProfileChange(option.data)}
-              disabled={isLoading}
-            />
-          </div>
+        <Field
+          label=""
+          childrenLayout="below"
+          childrenContainerWidth="max"
+        >
+          <Dropdown
+            rgOptions={profileOptions}
+            selectedOption={selectedProfile}
+            onChange={(option) => handleProfileChange(option.data)}
+            disabled={isLoading}
+          />
+        </Field>
       </PanelSectionRow>
 
       <PanelSectionRow>
-- 
cgit v1.2.3


From ca74a157ca5cc22c9b4292c895af35be96b54588 Mon Sep 17 00:00:00 2001
From: xXJSONDeruloXx <danielhimebauch@gmail.com>
Date: Sat, 16 Aug 2025 15:35:49 -0400
Subject: move new profile into dropdown

---
 src/components/ProfileManagement.tsx | 34 +++++++++++++++++++---------------
 1 file changed, 19 insertions(+), 15 deletions(-)

(limited to 'src/components/ProfileManagement.tsx')

diff --git a/src/components/ProfileManagement.tsx b/src/components/ProfileManagement.tsx
index 709a28c..ef68844 100644
--- a/src/components/ProfileManagement.tsx
+++ b/src/components/ProfileManagement.tsx
@@ -216,6 +216,14 @@ export function ProfileManagement({ currentProfile, onProfileChange }: ProfileMa
     }
   };
 
+  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");
@@ -259,10 +267,16 @@ export function ProfileManagement({ currentProfile, onProfileChange }: ProfileMa
     }
   };
 
-  const profileOptions: DropdownOption[] = profiles.map(profile => ({
-    data: profile,
-    label: profile === "decky-lsfg-vk" ? `${profile} (default)` : profile
-  }));
+  const profileOptions: DropdownOption[] = [
+    ...profiles.map((profile: string) => ({
+      data: profile,
+      label: profile === "decky-lsfg-vk" ? "Default" : profile
+    })),
+    {
+      data: "__NEW_PROFILE__",
+      label: "New Profile"
+    }
+  ];
 
   return (
     <PanelSection title="Select Profile">
@@ -275,21 +289,11 @@ export function ProfileManagement({ currentProfile, onProfileChange }: ProfileMa
           <Dropdown
             rgOptions={profileOptions}
             selectedOption={selectedProfile}
-            onChange={(option) => handleProfileChange(option.data)}
+            onChange={handleDropdownChange}
             disabled={isLoading}
           />
         </Field>
       </PanelSectionRow>
-
-      <PanelSectionRow>
-        <ButtonItem
-          layout="below"
-          onClick={handleCreateProfile}
-          disabled={isLoading}
-        >
-          New Profile
-        </ButtonItem>
-      </PanelSectionRow>
       
       <PanelSectionRow>
         <ButtonItem
-- 
cgit v1.2.3


From 44ed9e7b49e48601d8d0577251716e9d8a314bd0 Mon Sep 17 00:00:00 2001
From: xXJSONDeruloXx <danielhimebauch@gmail.com>
Date: Sat, 16 Aug 2025 15:42:29 -0400
Subject: fix width of new profile text field

---
 src/components/ProfileManagement.tsx | 31 +++++++++++++++++++------------
 1 file changed, 19 insertions(+), 12 deletions(-)

(limited to 'src/components/ProfileManagement.tsx')

diff --git a/src/components/ProfileManagement.tsx b/src/components/ProfileManagement.tsx
index ef68844..543609f 100644
--- a/src/components/ProfileManagement.tsx
+++ b/src/components/ProfileManagement.tsx
@@ -56,24 +56,31 @@ function TextInputModal({
 
   return (
     <ModalRoot>
-      <div style={{ padding: "16px", minWidth: "300px" }}>
+      <div style={{ padding: "16px", minWidth: "400px" }}>
         <h2 style={{ marginBottom: "16px" }}>{title}</h2>
-        <p style={{ marginBottom: "16px" }}>{description}</p>
+        <p style={{ marginBottom: "24px" }}>{description}</p>
         
-        <Field label="Name">
-          <TextField
-            value={value}
-            onChange={(e) => setValue(e?.target?.value || "")}
-          />
-        </Field>
+        <div style={{ marginBottom: "24px" }}>
+          <Field 
+            label="Name"
+            childrenLayout="below"
+            childrenContainerWidth="max"
+          >
+            <TextField
+              value={value}
+              onChange={(e) => setValue(e?.target?.value || "")}
+              style={{ width: "100%" }}
+            />
+          </Field>
+        </div>
         
-        <div style={{ display: "flex", gap: "8px", marginTop: "16px" }}>
-          <DialogButton onClick={handleOK} disabled={!value.trim()}>
-            {okText}
-          </DialogButton>
+        <div style={{ display: "flex", gap: "8px", justifyContent: "flex-end" }}>
           <DialogButton onClick={closeModal}>
             {cancelText}
           </DialogButton>
+          <DialogButton onClick={handleOK} disabled={!value.trim()}>
+            {okText}
+          </DialogButton>
         </div>
       </div>
     </ModalRoot>
-- 
cgit v1.2.3


From b01f9510fa5c0885c320d5231a163cb70c1df515 Mon Sep 17 00:00:00 2001
From: xXJSONDeruloXx <danielhimebauch@gmail.com>
Date: Sat, 16 Aug 2025 16:09:11 -0400
Subject: fix focusable elements for new profile creation modal

---
 src/components/ProfileManagement.tsx | 26 ++++++++++++++++++++------
 1 file changed, 20 insertions(+), 6 deletions(-)

(limited to 'src/components/ProfileManagement.tsx')

diff --git a/src/components/ProfileManagement.tsx b/src/components/ProfileManagement.tsx
index 543609f..67f0645 100644
--- a/src/components/ProfileManagement.tsx
+++ b/src/components/ProfileManagement.tsx
@@ -10,7 +10,8 @@ import {
   DialogButton,
   ButtonItem,
   ModalRoot,
-  TextField
+  TextField,
+  Focusable
 } from "@decky/ui";
 import { 
   getProfiles, 
@@ -50,8 +51,10 @@ function TextInputModal({
   const [value, setValue] = useState(defaultValue);
 
   const handleOK = () => {
-    onOK(value);
-    closeModal?.();
+    if (value.trim()) {
+      onOK(value);
+      closeModal?.();
+    }
   };
 
   return (
@@ -74,14 +77,25 @@ function TextInputModal({
           </Field>
         </div>
         
-        <div style={{ display: "flex", gap: "8px", justifyContent: "flex-end" }}>
+        <Focusable
+          style={{
+            display: "flex",
+            justifyContent: "flex-end",
+            gap: "8px",
+            marginTop: "16px"
+          }}
+          flow-children="horizontal"
+        >
           <DialogButton onClick={closeModal}>
             {cancelText}
           </DialogButton>
-          <DialogButton onClick={handleOK} disabled={!value.trim()}>
+          <DialogButton 
+            onClick={handleOK} 
+            disabled={!value.trim()}
+          >
             {okText}
           </DialogButton>
-        </div>
+        </Focusable>
       </div>
     </ModalRoot>
   );
-- 
cgit v1.2.3

