diff options
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/ClipboardButton.tsx | 22 | ||||
| -rw-r--r-- | src/components/ClipboardDisplay.tsx | 139 | ||||
| -rw-r--r-- | src/components/ConfigurationSection.tsx | 167 | ||||
| -rw-r--r-- | src/components/Content.tsx | 87 | ||||
| -rw-r--r-- | src/components/FlatpaksModal.tsx | 151 | ||||
| -rw-r--r-- | src/components/FpsMultiplierControl.tsx | 4 | ||||
| -rw-r--r-- | src/components/NerdStuffModal.tsx | 20 | ||||
| -rw-r--r-- | src/components/PluginUpdateChecker.tsx | 176 | ||||
| -rw-r--r-- | src/components/ProfileManagement.tsx | 11 | ||||
| -rw-r--r-- | src/components/WikiButton.tsx | 22 | ||||
| -rw-r--r-- | src/components/index.ts | 11 |
11 files changed, 281 insertions, 529 deletions
diff --git a/src/components/ClipboardButton.tsx b/src/components/ClipboardButton.tsx deleted file mode 100644 index cac1863..0000000 --- a/src/components/ClipboardButton.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { PanelSectionRow, ButtonItem } from "@decky/ui"; -import { FaBook } from "react-icons/fa"; - -export function ClipboardButton() { - const handleClipboardClick = () => { - window.open("https://github.com/xXJSONDeruloXx/decky-lossless-scaling-vk/wiki/Clipboard", "_blank"); - }; - - return ( - <PanelSectionRow> - <ButtonItem - layout="below" - onClick={handleClipboardClick} - > - <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> - <FaBook /> - <div>Plugin Wiki</div> - </div> - </ButtonItem> - </PanelSectionRow> - ); -} diff --git a/src/components/ClipboardDisplay.tsx b/src/components/ClipboardDisplay.tsx deleted file mode 100644 index 852a50f..0000000 --- a/src/components/ClipboardDisplay.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import { useState, useEffect } from "react"; -import { PanelSectionRow } from "@decky/ui"; -import { FaClipboard, FaEye } from "react-icons/fa"; - -export function ClipboardDisplay() { - const [clipboardContent, setClipboardContent] = useState<string>(""); - const [isReading, setIsReading] = useState(false); - - const readClipboard = async () => { - if (isReading) return; // Prevent concurrent reads - - setIsReading(true); - try { - console.log("ClipboardDisplay: Attempting to read clipboard..."); - - if (!navigator.clipboard) { - console.log("ClipboardDisplay: navigator.clipboard not available"); - setClipboardContent("Clipboard API not available"); - return; - } - - if (!navigator.clipboard.readText) { - console.log("ClipboardDisplay: navigator.clipboard.readText not available"); - setClipboardContent("Clipboard read not supported"); - return; - } - - console.log("ClipboardDisplay: Calling navigator.clipboard.readText()..."); - const content = await navigator.clipboard.readText(); - console.log("ClipboardDisplay: Successfully read clipboard:", content.length, "characters"); - setClipboardContent(content); - } catch (error) { - // This is expected if user hasn't granted clipboard permissions - // or if we're in a context where reading isn't allowed - console.log("ClipboardDisplay: Error reading clipboard:", error); - console.log("ClipboardDisplay: Error name:", (error as Error).name); - console.log("ClipboardDisplay: Error message:", (error as Error).message); - - // More specific error messages based on error type - if (error instanceof DOMException) { - switch (error.name) { - case 'NotAllowedError': - setClipboardContent("Clipboard access denied - check permissions"); - break; - case 'NotFoundError': - setClipboardContent("No clipboard data found"); - break; - case 'SecurityError': - setClipboardContent("Clipboard access blocked by security policy"); - break; - default: - setClipboardContent(`Clipboard error: ${error.name}`); - } - } else { - setClipboardContent("Unable to read clipboard"); - } - } finally { - setIsReading(false); - } - }; - - // Read clipboard on mount and then every 3 seconds - useEffect(() => { - readClipboard(); - - const interval = setInterval(() => { - readClipboard(); - }, 3000); - - return () => clearInterval(interval); - }, []); - - const truncateText = (text: string, maxLength: number = 60) => { - if (text.length <= maxLength) return text; - return text.substring(0, maxLength) + "..."; - }; - - const displayText = truncateText(clipboardContent); - - return ( - <PanelSectionRow> - <div style={{ - padding: "8px", - backgroundColor: "rgba(255, 255, 255, 0.05)", - borderRadius: "4px", - border: "1px solid rgba(255, 255, 255, 0.1)", - marginBottom: "8px" - }}> - <div style={{ - display: "flex", - alignItems: "center", - gap: "8px", - marginBottom: "4px" - }}> - <FaClipboard style={{ color: "#888", fontSize: "12px" }} /> - <div style={{ - fontSize: "12px", - fontWeight: "bold", - color: "#888", - textTransform: "uppercase" - }}> - Current Clipboard - </div> - {isReading && ( - <FaEye style={{ - color: "#888", - fontSize: "10px", - animation: "pulse 1s ease-in-out infinite" - }} /> - )} - </div> - <div style={{ - fontSize: "11px", - color: clipboardContent.includes("error") || - clipboardContent.includes("denied") || - clipboardContent.includes("not available") || - clipboardContent.includes("not supported") || - clipboardContent.includes("blocked") || - clipboardContent === "Unable to read clipboard" - ? "#ff6b6b" - : "#fff", - fontFamily: "monospace", - wordBreak: "break-word", - lineHeight: "1.3", - minHeight: "14px" - }}> - {displayText || "Reading clipboard..."} - </div> - </div> - <style>{` - @keyframes pulse { - 0% { opacity: 0.5; } - 50% { opacity: 1; } - 100% { opacity: 0.5; } - } - `}</style> - </PanelSectionRow> - ); -} diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx index b098b32..0734297 100644 --- a/src/components/ConfigurationSection.tsx +++ b/src/components/ConfigurationSection.tsx @@ -2,7 +2,6 @@ import { PanelSectionRow, ToggleField, SliderField, ButtonItem } from "@decky/ui import { useState, useEffect } from "react"; import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri"; import { ConfigurationData } from "../config/configSchema"; -import { FpsMultiplierControl } from "./FpsMultiplierControl"; import { FLOW_SCALE, PERFORMANCE_MODE, HDR_MODE, EXPERIMENTAL_PRESENT_MODE, DXVK_FRAME_RATE, DISABLE_STEAMDECK_MODE, @@ -14,13 +13,23 @@ interface ConfigurationSectionProps { onConfigChange: (fieldName: keyof ConfigurationData, value: boolean | number | string) => Promise<void>; } -const WORKAROUNDS_COLLAPSED_KEY = 'lsfg-workarounds-collapsed'; +const WORKAROUNDS_COLLAPSED_KEY = "lsfg-workarounds-collapsed"; +const CONFIG_COLLAPSED_KEY = "lsfg-config-collapsed"; export function ConfigurationSection({ config, onConfigChange }: ConfigurationSectionProps) { // Initialize with localStorage value, fallback to true if not found + const [configCollapsed, setConfigCollapsed] = useState(() => { + try { + const saved = localStorage.getItem(CONFIG_COLLAPSED_KEY); + return saved !== null ? JSON.parse(saved) : false; + } catch { + return false; + } + }); + const [workaroundsCollapsed, setWorkaroundsCollapsed] = useState(() => { try { const saved = localStorage.getItem(WORKAROUNDS_COLLAPSED_KEY); @@ -33,9 +42,17 @@ export function ConfigurationSection({ // Persist workarounds collapse state to localStorage useEffect(() => { try { + localStorage.setItem(CONFIG_COLLAPSED_KEY, JSON.stringify(configCollapsed)); + } catch (error) { + console.warn("Failed to save config collapse state:", error); + } + }, [configCollapsed]); + + useEffect(() => { + try { localStorage.setItem(WORKAROUNDS_COLLAPSED_KEY, JSON.stringify(workaroundsCollapsed)); } catch (error) { - console.warn('Failed to save workarounds collapse state:', error); + console.warn("Failed to save workarounds collapse state:", error); } }, [workaroundsCollapsed]); @@ -43,6 +60,8 @@ export function ConfigurationSection({ <> <style> {` + .LSFG_ConfigCollapseButton_Container > div > div > div > button, + .LSFG_ConfigCollapseButton_Container > div > div > div > div > button, .LSFG_WorkaroundsCollapseButton_Container > div > div > div > button { height: 10px !important; } @@ -52,59 +71,100 @@ export function ConfigurationSection({ `} </style> - {/* FPS Multiplier */} - <FpsMultiplierControl config={config} onConfigChange={onConfigChange} /> - + {/* Config Section */} <PanelSectionRow> - <SliderField - label={`Flow Scale (${Math.round(config.flow_scale * 100)}%)`} - description="Lowers internal motion estimation resolution, improving performance slightly" - value={config.flow_scale} - min={0.25} - max={1.0} - step={0.01} - onChange={(value) => onConfigChange(FLOW_SCALE, value)} - /> + <div + style={{ + fontSize: "14px", + fontWeight: "bold", + marginTop: "8px", + marginBottom: "6px", + borderBottom: "1px solid rgba(255, 255, 255, 0.2)", + paddingBottom: "3px", + color: "white" + }} + > + Config + </div> </PanelSectionRow> <PanelSectionRow> - <SliderField - label={`Base FPS Cap${config.dxvk_frame_rate > 0 ? ` (${config.dxvk_frame_rate} FPS)` : ' (Off)'}`} - description="Base framerate cap for DirectX games, before frame multiplier. (Requires game restart to apply)" - value={config.dxvk_frame_rate} - min={0} - max={60} - step={1} - onChange={(value) => onConfigChange(DXVK_FRAME_RATE, value)} - /> + <div + className="LSFG_ConfigCollapseButton_Container" + style={{ marginTop: "-2px", marginBottom: "4px" }} + > + <ButtonItem + layout="below" + bottomSeparator={configCollapsed ? "standard" : "none"} + onClick={() => setConfigCollapsed(!configCollapsed)} + > + {configCollapsed ? ( + <RiArrowDownSFill + style={{ transform: "translate(0, -13px)", fontSize: "1.5em" }} + /> + ) : ( + <RiArrowUpSFill + style={{ transform: "translate(0, -12px)", fontSize: "1.5em" }} + /> + )} + </ButtonItem> + </div> </PanelSectionRow> - <PanelSectionRow> - <ToggleField - label={`Present Mode (${(config.experimental_present_mode || "fifo") === "fifo" ? "FIFO - VSync" : "Mailbox"})`} - description="Toggle between FIFO - VSync (default) and Mailbox presentation modes for better performance or compatibility" - checked={(config.experimental_present_mode || "fifo") === "fifo"} - onChange={(value) => onConfigChange(EXPERIMENTAL_PRESENT_MODE, value ? "fifo" : "mailbox")} - /> - </PanelSectionRow> + {!configCollapsed && ( + <> + <PanelSectionRow> + <SliderField + label={`Flow Scale (${Math.round(config.flow_scale * 100)}%)`} + description="Lowers internal motion estimation resolution, improving performance slightly" + value={config.flow_scale} + min={0.25} + max={1.0} + step={0.01} + onChange={(value) => onConfigChange(FLOW_SCALE, value)} + /> + </PanelSectionRow> - <PanelSectionRow> - <ToggleField - label="Performance Mode" - description="Uses a lighter model for FG (Recommended for most games)" - checked={config.performance_mode} - onChange={(value) => onConfigChange(PERFORMANCE_MODE, value)} - /> - </PanelSectionRow> + <PanelSectionRow> + <SliderField + label={`Base FPS Cap${config.dxvk_frame_rate > 0 ? ` (${config.dxvk_frame_rate} FPS)` : " (Off)"}`} + description="Base framerate cap for DirectX games, before frame multiplier. (Requires game restart to apply)" + value={config.dxvk_frame_rate} + min={0} + max={60} + step={1} + onChange={(value) => onConfigChange(DXVK_FRAME_RATE, value)} + /> + </PanelSectionRow> - <PanelSectionRow> - <ToggleField - label="HDR Mode" - description="Enables HDR mode (only for games that support HDR)" - checked={config.hdr_mode} - onChange={(value) => onConfigChange(HDR_MODE, value)} - /> - </PanelSectionRow> + <PanelSectionRow> + <ToggleField + label={`Present Mode (${(config.experimental_present_mode || "fifo") === "fifo" ? "FIFO - VSync" : "Mailbox"})`} + description="Toggle between FIFO - VSync (default) and Mailbox presentation modes for better performance or compatibility" + checked={(config.experimental_present_mode || "fifo") === "fifo"} + onChange={(value) => onConfigChange(EXPERIMENTAL_PRESENT_MODE, value ? "fifo" : "mailbox")} + /> + </PanelSectionRow> + + <PanelSectionRow> + <ToggleField + label="Performance Mode" + description="Uses a lighter model for FG (Recommended for most games)" + checked={config.performance_mode} + onChange={(value) => onConfigChange(PERFORMANCE_MODE, value)} + /> + </PanelSectionRow> + + <PanelSectionRow> + <ToggleField + label="HDR Mode" + description="Enables HDR mode (only for games that support HDR)" + checked={config.hdr_mode} + onChange={(value) => onConfigChange(HDR_MODE, value)} + /> + </PanelSectionRow> + </> + )} {/* Workarounds Section */} <PanelSectionRow> @@ -112,10 +172,10 @@ export function ConfigurationSection({ style={{ fontSize: "14px", fontWeight: "bold", - marginTop: "16px", - marginBottom: "8px", + marginTop: "8px", + marginBottom: "6px", borderBottom: "1px solid rgba(255, 255, 255, 0.2)", - paddingBottom: "4px", + paddingBottom: "3px", color: "white" }} > @@ -124,7 +184,10 @@ export function ConfigurationSection({ </PanelSectionRow> <PanelSectionRow> - <div className="LSFG_WorkaroundsCollapseButton_Container"> + <div + className="LSFG_WorkaroundsCollapseButton_Container" + style={{ marginTop: "-2px", marginBottom: "4px" }} + > <ButtonItem layout="below" bottomSeparator={workaroundsCollapsed ? "standard" : "none"} diff --git a/src/components/Content.tsx b/src/components/Content.tsx index fdb8672..28eefa7 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -8,14 +8,11 @@ import { InstallationButton } from "./InstallationButton"; import { ConfigurationSection } from "./ConfigurationSection"; import { ProfileManagement } from "./ProfileManagement"; import { UsageInstructions } from "./UsageInstructions"; -// import { WikiButton } from "./WikiButton"; -// import { ClipboardButton } from "./ClipboardButton"; import { SmartClipboardButton } from "./SmartClipboardButton"; import { FgmodClipboardButton } from "./FgmodClipboardButton"; -// import { ClipboardDisplay } from "./ClipboardDisplay"; -// import { PluginUpdateChecker } from "./PluginUpdateChecker"; +import { FpsMultiplierControl } from "./FpsMultiplierControl"; import { NerdStuffModal } from "./NerdStuffModal"; -import FlatpaksModal from "./FlatpaksModal"; +import { FlatpaksModal } from "./FlatpaksModal"; import { ConfigurationData } from "../config/configSchema"; export function Content() { @@ -102,13 +99,30 @@ export function Content() { /> </> )} - - {/* Clipboard buttons - only show if installed */} + + {/* FPS multiplier controls stay above profile selection when installed */} {isInstalled && ( <> - {/* <ClipboardDisplay /> */} - <SmartClipboardButton /> - <FgmodClipboardButton /> + <PanelSectionRow> + <div + style={{ + fontSize: "14px", + fontWeight: "bold", + marginTop: "8px", + marginBottom: "6px", + borderBottom: "1px solid rgba(255, 255, 255, 0.2)", + paddingBottom: "3px", + color: "white" + }} + > + FPS Multiplier + </div> + </PanelSectionRow> + + <FpsMultiplierControl + config={config} + onConfigChange={handleConfigChange} + /> </> )} @@ -131,36 +145,17 @@ export function Content() { /> )} - {/* Usage instructions - always visible for user guidance */} - <UsageInstructions config={config} /> - - {/* Wiki and clipboard buttons - always available for documentation */} - {/* <WikiButton /> */} - {/* <ClipboardButton /> */} - - {/* Plugin Update Checker */} - {/* <PluginUpdateChecker /> */} - - {/* Show installation components at bottom when fully installed */} + {/* Clipboard buttons sit beside usage info for quick access */} {isInstalled && ( <> - <InstallationButton - isInstalled={isInstalled} - isInstalling={isInstalling} - isUninstalling={isUninstalling} - onInstall={onInstall} - onUninstall={onUninstall} - /> - - <StatusDisplay - dllDetected={dllDetected} - dllDetectionStatus={dllDetectionStatus} - isInstalled={isInstalled} - installationStatus={installationStatus} - /> + <SmartClipboardButton /> + <FgmodClipboardButton /> </> )} + {/* Usage instructions - always visible for user guidance */} + <UsageInstructions config={config} /> + {/* Nerd Stuff Button */} <PanelSectionRow> <ButtonItem @@ -177,9 +172,29 @@ export function Content() { layout="below" onClick={handleShowFlatpaks} > - Flatpaks + Flatpak Setup </ButtonItem> </PanelSectionRow> + + {/* Status and uninstall sit at bottom when installed to match desired layout */} + {isInstalled && ( + <> + <StatusDisplay + dllDetected={dllDetected} + dllDetectionStatus={dllDetectionStatus} + isInstalled={isInstalled} + installationStatus={installationStatus} + /> + + <InstallationButton + isInstalled={isInstalled} + isInstalling={isInstalling} + isUninstalling={isUninstalling} + onInstall={onInstall} + onUninstall={onUninstall} + /> + </> + )} </PanelSection> ); } diff --git a/src/components/FlatpaksModal.tsx b/src/components/FlatpaksModal.tsx index ae0c333..ca69ec6 100644 --- a/src/components/FlatpaksModal.tsx +++ b/src/components/FlatpaksModal.tsx @@ -1,4 +1,4 @@ -import { FC, useState, useEffect } from 'react'; +import { FC, useState, useEffect, CSSProperties } from 'react'; import { ModalRoot, DialogBody, @@ -32,7 +32,7 @@ interface FlatpaksModalProps { closeModal?: () => void; } -const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { +export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { const [extensionStatus, setExtensionStatus] = useState<FlatpakExtensionStatus | null>(null); const [flatpakApps, setFlatpakApps] = useState<FlatpakAppInfo | null>(null); const [loading, setLoading] = useState(true); @@ -126,6 +126,40 @@ const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { ); } + const instructionSteps = [ + { + id: 'try-first', + title: 'Try first:', + command: '~/lsfg' + }, + { + id: 'try-full-path', + title: "If that doesn't work, try full path:", + command: '/home/(username)/lsfg' + }, + { + id: 'final-result', + title: 'Final result should look like:', + command: '~/lsfg "usr/bin/flatpak"' + } + ]; + + const focusableInstructionStyle: CSSProperties = { + padding: '10px', + background: 'rgba(0, 0, 0, 0.3)', + borderRadius: '6px', + marginBottom: '12px' + }; + + const commandStyle: CSSProperties = { + fontFamily: 'monospace', + fontSize: '0.85em', + background: 'rgba(0, 0, 0, 0.45)', + padding: '8px', + borderRadius: '4px', + marginTop: '6px' + }; + return ( <ModalRoot closeModal={closeModal}> <DialogHeader>Flatpak Extensions</DialogHeader> @@ -327,80 +361,57 @@ const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { {/* Steam Configuration Instructions */} <DialogControlsSection> <DialogControlsSectionHeader>Steam Configuration</DialogControlsSectionHeader> - - <Focusable> - <div style={{ - padding: '12px', - background: 'rgba(255, 255, 255, 0.1)', - borderRadius: '8px', - margin: '8px 0' - }}> - <div style={{ fontWeight: 'bold', marginBottom: '8px', color: '#fff' }}> - Configure Steam Flatpak Shortcuts - </div> - <div style={{ fontSize: '0.9em', lineHeight: '1.4', marginBottom: '8px' }}> - In Steam, open your flatpak game and click the cog wheel." - </div> - <div style={{ fontSize: '0.9em', lineHeight: '1.4', marginBottom: '12px', color: '#ffa500' }}> - <strong>IMPORTANT:</strong> Set this in TARGET (NOT LAUNCH OPTIONS) - </div> - - <div style={{ fontWeight: 'bold', marginBottom: '6px' }}> - Try first: - </div> - <div style={{ - fontFamily: 'monospace', - fontSize: '0.85em', - background: 'rgba(0, 0, 0, 0.3)', - padding: '8px', - borderRadius: '4px', - marginBottom: '12px' - }}> - ~/lsfg - </div> - - <div style={{ fontWeight: 'bold', marginBottom: '6px' }}> - If that doesn't work, try full path: - </div> - <div style={{ - fontFamily: 'monospace', - fontSize: '0.85em', - background: 'rgba(0, 0, 0, 0.3)', - padding: '8px', - borderRadius: '4px', - marginBottom: '12px' - }}> - /home/(username)/lsfg - </div> - - <div style={{ fontWeight: 'bold', marginBottom: '6px' }}> - Final result should look like: - </div> - <div style={{ - fontFamily: 'monospace', - fontSize: '0.85em', - background: 'rgba(0, 0, 0, 0.3)', - padding: '8px', - borderRadius: '4px' - }}> - ~/lsfg "usr/bin/flatpak" - </div> - - {/* Visual example image */} - <div style={{ marginTop: '16px', textAlign: 'center' }}> - <img - src={flatpakTargetImage} + <div + style={{ + padding: '12px', + background: 'rgba(255, 255, 255, 0.1)', + borderRadius: '8px', + margin: '8px 0', + display: 'flex', + flexDirection: 'column' + }} + > + <div style={{ fontWeight: 'bold', marginBottom: '8px', color: '#fff' }}> + Configure Steam Flatpak Shortcuts + </div> + <div style={{ fontSize: '0.9em', lineHeight: '1.4', marginBottom: '8px' }}> + In Steam, open your flatpak game and click the cog wheel. + </div> + <div style={{ fontSize: '0.9em', lineHeight: '1.4', marginBottom: '12px', color: '#ffa500' }}> + <strong>IMPORTANT:</strong> Set this in TARGET (NOT LAUNCH OPTIONS) + </div> + + {instructionSteps.map((step) => ( + <Focusable + key={step.id} + focusWithinClassName="gpfocuswithin" + onActivate={() => {}} + style={focusableInstructionStyle} + > + <div style={{ fontWeight: 'bold' }}>{step.title}</div> + <div style={commandStyle}>{step.command}</div> + </Focusable> + ))} + + <Focusable + focusWithinClassName="gpfocuswithin" + onActivate={() => {}} + style={{ marginTop: '4px' }} + > + <div style={{ textAlign: 'center' }}> + <img + src={flatpakTargetImage} alt="Steam Properties Target Field Example" - style={{ - maxWidth: '100%', + style={{ + maxWidth: '100%', height: 'auto', border: '1px solid rgba(255, 255, 255, 0.2)', borderRadius: '4px' }} /> </div> - </div> - </Focusable> + </Focusable> + </div> </DialogControlsSection> {/* Close Button */} @@ -419,5 +430,3 @@ const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { </ModalRoot> ); }; - -export default FlatpaksModal; diff --git a/src/components/FpsMultiplierControl.tsx b/src/components/FpsMultiplierControl.tsx index 49d2cd9..5ac31bb 100644 --- a/src/components/FpsMultiplierControl.tsx +++ b/src/components/FpsMultiplierControl.tsx @@ -15,8 +15,8 @@ export function FpsMultiplierControl({ <PanelSectionRow> <Focusable style={{ - marginTop: "10px", - marginBottom: "10px", + marginTop: "6px", + marginBottom: "6px", display: "flex", justifyContent: "center", alignItems: "center" diff --git a/src/components/NerdStuffModal.tsx b/src/components/NerdStuffModal.tsx index b5689c9..b4a79a2 100644 --- a/src/components/NerdStuffModal.tsx +++ b/src/components/NerdStuffModal.tsx @@ -3,7 +3,9 @@ import { ModalRoot, Field, Focusable, - Button + DialogControlsSection, + PanelSectionRow, + ButtonItem } from "@decky/ui"; import { getDllStats, DllStatsResult, getConfigFileContent, getLaunchScriptContent, FileContentResult } from "../api/lsfgApi"; @@ -86,7 +88,7 @@ export function NerdStuffModal({ closeModal }: NerdStuffModalProps) { </Focusable> </Field> - <Field label="SHA256 Hash"> + <Field label="DLL SHA256 Hash"> <Focusable onClick={() => dllStats.dll_sha256 && copyToClipboard(dllStats.dll_sha256)} onActivate={() => dllStats.dll_sha256 && copyToClipboard(dllStats.dll_sha256)} @@ -166,9 +168,17 @@ export function NerdStuffModal({ closeModal }: NerdStuffModalProps) { </Field> )} - <Button onClick={closeModal}> - Close - </Button> + {/* Close Button */} + <DialogControlsSection> + <PanelSectionRow> + <ButtonItem + layout="below" + onClick={closeModal} + > + Close + </ButtonItem> + </PanelSectionRow> + </DialogControlsSection> </> )} </ModalRoot> diff --git a/src/components/PluginUpdateChecker.tsx b/src/components/PluginUpdateChecker.tsx deleted file mode 100644 index 9fa2afb..0000000 --- a/src/components/PluginUpdateChecker.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { - ButtonItem, - PanelSection, - PanelSectionRow, - Field, - Focusable -} from '@decky/ui'; -import { checkForPluginUpdate, downloadPluginUpdate, UpdateCheckResult, UpdateDownloadResult } from '../api/lsfgApi'; - -interface PluginUpdateCheckerProps { - // Add any props if needed -} - -interface UpdateInfo { - updateAvailable: boolean; - currentVersion: string; - latestVersion: string; - releaseNotes: string; - releaseDate: string; - downloadUrl: string; -} - -export const PluginUpdateChecker: React.FC<PluginUpdateCheckerProps> = () => { - const [checkingUpdate, setCheckingUpdate] = useState(false); - const [downloadingUpdate, setDownloadingUpdate] = useState(false); - const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null); - const [updateError, setUpdateError] = useState<string | null>(null); - const [downloadResult, setDownloadResult] = useState<UpdateDownloadResult | null>(null); - - // Auto-hide error messages after 5 seconds - useEffect(() => { - if (updateError) { - const timer = setTimeout(() => { - setUpdateError(null); - }, 5000); - return () => clearTimeout(timer); - } - return undefined; - }, [updateError]); - - const handleCheckForUpdate = async () => { - setCheckingUpdate(true); - setUpdateError(null); - setUpdateInfo(null); - setDownloadResult(null); // Clear previous download result - - try { - const result: UpdateCheckResult = await checkForPluginUpdate(); - - if (result.success) { - setUpdateInfo({ - updateAvailable: result.update_available, - currentVersion: result.current_version, - latestVersion: result.latest_version, - releaseNotes: result.release_notes, - releaseDate: result.release_date, - downloadUrl: result.download_url - }); - - // Simple console log instead of toast since showToast may not be available - if (result.update_available) { - console.log("Update available!", `Version ${result.latest_version} is now available.`); - } else { - console.log("Up to date!", "You have the latest version installed."); - } - } else { - setUpdateError(result.error || "Failed to check for updates"); - } - } catch (error) { - setUpdateError(`Error checking for updates: ${error}`); - } finally { - setCheckingUpdate(false); - } - }; - - const handleDownloadUpdate = async () => { - if (!updateInfo?.downloadUrl) return; - - setDownloadingUpdate(true); - setUpdateError(null); - setDownloadResult(null); - - try { - const result: UpdateDownloadResult = await downloadPluginUpdate(updateInfo.downloadUrl); - - if (result.success) { - setDownloadResult(result); - console.log("✓ Download complete!", `Plugin downloaded to ${result.download_path}`); - } else { - setUpdateError(result.error || "Failed to download update"); - } - } catch (error) { - setUpdateError(`Error downloading update: ${error}`); - } finally { - setDownloadingUpdate(false); - } - }; - - const getStatusMessage = () => { - if (!updateInfo) return null; - - if (updateInfo.updateAvailable) { - if (downloadResult?.success) { - return "✓ v" + updateInfo.latestVersion + " downloaded - ready to install"; - } else { - return "Update available: v" + updateInfo.latestVersion; - } - } else { - return "Up to date (v" + updateInfo.currentVersion + ")"; - } - }; - - return ( - <PanelSection title="PLUGIN UPDATES"> - <PanelSectionRow> - <ButtonItem - layout="below" - onClick={handleCheckForUpdate} - disabled={checkingUpdate} - description={getStatusMessage()} - > - {checkingUpdate ? 'Checking for updates...' : 'Check for Updates'} - </ButtonItem> - </PanelSectionRow> - - {updateInfo && updateInfo.updateAvailable && !downloadResult?.success && ( - <PanelSectionRow> - <ButtonItem - layout="below" - onClick={handleDownloadUpdate} - disabled={downloadingUpdate} - description={`Download version ${updateInfo.latestVersion}`} - > - {downloadingUpdate ? 'Downloading...' : 'Download Update'} - </ButtonItem> - </PanelSectionRow> - )} - - {downloadResult?.success && ( - <> - <PanelSectionRow> - <Field label="Download Complete!"> - <Focusable> - File saved to: {downloadResult.download_path} - </Focusable> - </Field> - </PanelSectionRow> - - <PanelSectionRow> - <Field label="Installation Instructions:"> - <Focusable> - 1. Go to Decky Loader settings - <br />2. Click "Developer" tab - <br />3. Click "Uninstall" next to "decky-lsfg-vk" - <br />4. Click "Install from ZIP" - <br />5. Select the downloaded file - <br />6. Restart Steam or reload plugins - </Focusable> - </Field> - </PanelSectionRow> - </> - )} - - {updateError && ( - <PanelSectionRow> - <Field label="Error:"> - <Focusable> - {updateError} - </Focusable> - </Field> - </PanelSectionRow> - )} - </PanelSection> - ); -}; diff --git a/src/components/ProfileManagement.tsx b/src/components/ProfileManagement.tsx index d3d15a9..62160d9 100644 --- a/src/components/ProfileManagement.tsx +++ b/src/components/ProfileManagement.tsx @@ -367,10 +367,10 @@ export function ProfileManagement({ currentProfile, onProfileChange }: ProfileMa style={{ fontSize: "14px", fontWeight: "bold", - marginTop: "16px", - marginBottom: "8px", + marginTop: "8px", + marginBottom: "6px", borderBottom: "1px solid rgba(255, 255, 255, 0.2)", - paddingBottom: "4px", + paddingBottom: "3px", color: "white" }} > @@ -379,7 +379,10 @@ export function ProfileManagement({ currentProfile, onProfileChange }: ProfileMa </PanelSectionRow> <PanelSectionRow> - <div className="LSFG_ProfilesCollapseButton_Container"> + <div + className="LSFG_ProfilesCollapseButton_Container" + style={{ marginTop: "-2px", marginBottom: "4px" }} + > <ButtonItem layout="below" bottomSeparator={profilesCollapsed ? "standard" : "none"} diff --git a/src/components/WikiButton.tsx b/src/components/WikiButton.tsx deleted file mode 100644 index 065fb8e..0000000 --- a/src/components/WikiButton.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { PanelSectionRow, ButtonItem } from "@decky/ui"; -import { FaBook } from "react-icons/fa"; - -export function WikiButton() { - const handleWikiClick = () => { - window.open("https://github.com/PancakeTAS/lsfg-vk/wiki", "_blank"); - }; - - return ( - <PanelSectionRow> - <ButtonItem - layout="below" - onClick={handleWikiClick} - > - <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> - <FaBook /> - <div>LSFG-VK Wiki</div> - </div> - </ButtonItem> - </PanelSectionRow> - ); -} diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 0000000..bec45ae --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,11 @@ +export { Content } from "./Content"; +export { StatusDisplay } from "./StatusDisplay"; +export { InstallationButton } from "./InstallationButton"; +export { ConfigurationSection } from "./ConfigurationSection"; +export { FpsMultiplierControl } from "./FpsMultiplierControl"; +export { UsageInstructions } from "./UsageInstructions"; +export { SmartClipboardButton } from "./SmartClipboardButton"; +export { FgmodClipboardButton } from "./FgmodClipboardButton"; +export { NerdStuffModal } from "./NerdStuffModal"; +export { FlatpaksModal } from "./FlatpaksModal"; +export { ProfileManagement } from "./ProfileManagement"; |
