ui: toggle back off after failure (#1911)

This commit is contained in:
Lily Delalande
2025-03-28 17:10:18 -04:00
committed by GitHub
parent a837404248
commit b22d173425
3 changed files with 56 additions and 14 deletions

View File

@@ -49,14 +49,22 @@ export default function ExtensionsSection() {
const toggleDirection = extension.enabled ? 'toggleOff' : 'toggleOn';
const extensionConfig = extractExtensionConfig(extension);
await toggleExtension({
toggle: toggleDirection,
extensionConfig: extensionConfig,
addToConfig: addExtension,
toastOptions: { silent: false },
});
try {
await toggleExtension({
toggle: toggleDirection,
extensionConfig: extensionConfig,
addToConfig: addExtension,
toastOptions: { silent: false },
});
await fetchExtensions(); // Refresh the list after toggling
await fetchExtensions(); // Refresh the list after successful toggle
return true; // Indicate success
} catch (error) {
console.error('Toggle extension failed:', error);
// Don't refresh the extension list on failure - this allows our visual state rollback to work
// The actual state in the config hasn't changed anyway
throw error; // Re-throw to let the ExtensionItem component know it failed
}
};
const handleConfigureClick = (extension: FixedExtensionEntry) => {

View File

@@ -1,5 +1,4 @@
// ExtensionItem.tsx
import React from 'react';
import React, { useState, useEffect } from 'react';
import { Switch } from '../../../ui/switch';
import { Gear } from '../../../icons/Gear';
import { FixedExtensionEntry } from '../../../ConfigContext';
@@ -7,11 +6,46 @@ import { getSubtitle, getFriendlyTitle } from './ExtensionList';
interface ExtensionItemProps {
extension: FixedExtensionEntry;
onToggle: (extension: FixedExtensionEntry) => void;
onToggle: (extension: FixedExtensionEntry) => Promise<boolean | void>;
onConfigure: (extension: FixedExtensionEntry) => void;
}
export default function ExtensionItem({ extension, onToggle, onConfigure }: ExtensionItemProps) {
// Add local state to track the visual toggle state
const [visuallyEnabled, setVisuallyEnabled] = useState(extension.enabled);
// Track if we're in the process of toggling
const [isToggling, setIsToggling] = useState(false);
const handleToggle = async (ext: FixedExtensionEntry) => {
// Prevent multiple toggles while one is in progress
if (isToggling) return;
setIsToggling(true);
// Immediately update visual state
const newState = !ext.enabled;
setVisuallyEnabled(newState);
try {
// Call the actual toggle function that performs the async operation
await onToggle(ext);
// Success case is handled by the useEffect below when extension.enabled changes
} catch (error) {
// If there was an error, revert the visual state
console.log('Toggle failed, reverting visual state');
setVisuallyEnabled(!newState);
} finally {
setIsToggling(false);
}
};
// Update visual state when the actual extension state changes
useEffect(() => {
if (!isToggling) {
setVisuallyEnabled(extension.enabled);
}
}, [extension.enabled, isToggling]);
const renderFormattedSubtitle = () => {
const subtitle = getSubtitle(extension);
return subtitle.split('\n').map((part, index) => (
@@ -21,6 +55,7 @@ export default function ExtensionItem({ extension, onToggle, onConfigure }: Exte
</React.Fragment>
));
};
return (
<div className="rounded-lg border border-borderSubtle p-4 mb-2">
<div className="flex items-center justify-between mb-2">
@@ -36,8 +71,8 @@ export default function ExtensionItem({ extension, onToggle, onConfigure }: Exte
</button>
)}
<Switch
checked={extension.enabled}
onCheckedChange={() => onToggle(extension)}
checked={(isToggling && visuallyEnabled) || extension.enabled}
onCheckedChange={() => handleToggle(extension)}
variant="mono"
/>
</div>

View File

@@ -7,7 +7,7 @@ import { combineCmdAndArgs } from '../utils';
interface ExtensionListProps {
extensions: FixedExtensionEntry[];
onToggle: (extension: FixedExtensionEntry) => void;
onToggle: (extension: FixedExtensionEntry) => Promise<boolean | void>;
onConfigure: (extension: FixedExtensionEntry) => void;
}
@@ -25,7 +25,6 @@ export default function ExtensionList({ extensions, onToggle, onConfigure }: Ext
</div>
);
}
// Helper functions
// Helper function to get a friendly title from extension name
export function getFriendlyTitle(extension: FixedExtensionEntry): string {