feat: Add button visibility depending on various factors - dev mode hide stage / commit - buttons show tooltips / are greyed out when they cant be clicked

This commit is contained in:
Sam Chau
2024-09-19 09:46:39 +09:30
parent 8df03175a2
commit daa3a70c2c
3 changed files with 164 additions and 142 deletions

View File

@@ -2,6 +2,37 @@ import React from 'react';
import {Loader, RotateCcw, Download, CheckCircle, Plus} from 'lucide-react'; import {Loader, RotateCcw, Download, CheckCircle, Plus} from 'lucide-react';
import Tooltip from '../../ui/Tooltip'; import Tooltip from '../../ui/Tooltip';
const ActionButton = ({
onClick,
disabled,
loading,
icon,
text,
className,
disabledTooltip
}) => {
const baseClassName =
'flex items-center px-4 py-2 text-white rounded-md transition-all duration-200 ease-in-out text-xs';
const enabledClassName = `${baseClassName} ${className} hover:opacity-80`;
const disabledClassName = `${baseClassName} ${className} opacity-50 cursor-not-allowed`;
return (
<Tooltip content={disabled ? disabledTooltip : text}>
<button
onClick={onClick}
className={disabled ? disabledClassName : enabledClassName}
disabled={disabled || loading}>
{loading ? (
<Loader size={12} className='animate-spin mr-1' />
) : (
React.cloneElement(icon, {className: 'mr-1', size: 12})
)}
{text}
</button>
</Tooltip>
);
};
const ActionButtons = ({ const ActionButtons = ({
isDevMode, isDevMode,
selectedOutgoingChanges, selectedOutgoingChanges,
@@ -12,106 +43,67 @@ const ActionButtons = ({
onStageSelected, onStageSelected,
onCommitSelected, onCommitSelected,
onRevertSelected, onRevertSelected,
onPullSelected, onPullSelected
getStageButtonTooltip,
getCommitButtonTooltip,
getRevertButtonTooltip
}) => { }) => {
const canStage =
isDevMode &&
selectedOutgoingChanges.length > 0 &&
selectionType !== 'staged';
const canCommit =
isDevMode &&
selectedOutgoingChanges.length > 0 &&
commitMessage.trim() &&
selectionType !== 'unstaged';
const canRevert = selectedOutgoingChanges.length > 0;
const canPull = selectedIncomingChanges.length > 0;
return ( return (
<div className='mt-4 flex justify-end space-x-2'> <div className='mt-4 flex justify-start space-x-2'>
{isDevMode && ( {isDevMode && (
<> <>
{/* Stage */} <ActionButton
{selectedOutgoingChanges.length > 0 && onClick={() => onStageSelected(selectedOutgoingChanges)}
selectionType !== 'staged' && ( disabled={!canStage}
<Tooltip content={getStageButtonTooltip()}> loading={loadingAction === 'stage_selected'}
<button icon={<Plus />}
onClick={() => text='Stage'
onStageSelected(selectedOutgoingChanges) className='bg-green-600'
} disabledTooltip='Select unstaged files to enable staging'
className='flex items-center px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors duration-200 ease-in-out text-xs' />
disabled={ <ActionButton
loadingAction === 'stage_selected' onClick={() =>
}> onCommitSelected(
{loadingAction === 'stage_selected' ? ( selectedOutgoingChanges,
<Loader commitMessage
size={12} )
className='animate-spin' }
/> disabled={!canCommit}
) : ( loading={loadingAction === 'commit_selected'}
<Plus className='mr-1' size={12} /> icon={<CheckCircle />}
)} text='Commit'
Stage Selected className='bg-blue-600'
</button> disabledTooltip='Select staged files and enter a commit message to enable committing'
</Tooltip> />
)}
{/* Commit */}
{selectedOutgoingChanges.length > 0 &&
commitMessage.trim() &&
selectionType !== 'unstaged' && (
<Tooltip content={getCommitButtonTooltip()}>
<button
onClick={() =>
onCommitSelected(
selectedOutgoingChanges,
commitMessage
)
}
className='flex items-center px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors duration-200 ease-in-out text-xs'
disabled={
loadingAction === 'commit_selected'
}>
{loadingAction === 'commit_selected' ? (
<Loader
size={12}
className='animate-spin'
/>
) : (
<CheckCircle
className='mr-1'
size={12}
/>
)}
Commit Selected
</button>
</Tooltip>
)}
</> </>
)} )}
{/* Revert */} <ActionButton
{selectedOutgoingChanges.length > 0 && ( onClick={() => onRevertSelected(selectedOutgoingChanges)}
<Tooltip content={getRevertButtonTooltip()}> disabled={!canRevert}
<button loading={loadingAction === 'revert_selected'}
onClick={() => icon={<RotateCcw />}
onRevertSelected(selectedOutgoingChanges) text='Revert'
} className='bg-red-600'
className='flex items-center px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors duration-200 ease-in-out text-xs' disabledTooltip='Select files to revert'
disabled={loadingAction === 'revert_selected'}> />
{loadingAction === 'revert_selected' ? ( <ActionButton
<Loader size={12} className='animate-spin' /> onClick={() => onPullSelected(selectedIncomingChanges)}
) : ( disabled={!canPull}
<RotateCcw className='mr-1' size={12} /> loading={loadingAction === 'pull_changes'}
)} icon={<Download />}
Revert Selected text='Pull'
</button> className='bg-yellow-600'
</Tooltip> disabledTooltip='Select incoming changes to pull'
)} />
{/* Pull */}
{selectedIncomingChanges.length > 0 && (
<Tooltip content='Pull selected changes'>
<button
onClick={() => onPullSelected(selectedIncomingChanges)}
className='flex items-center px-4 py-2 bg-yellow-600 text-white rounded-md hover:bg-yellow-700 transition-colors duration-200 ease-in-out text-xs'
disabled={loadingAction === 'pull_changes'}>
{loadingAction === 'pull_changes' ? (
<Loader size={12} className='animate-spin' />
) : (
<Download className='mr-1' size={12} />
)}
Pull Selected
</button>
</Tooltip>
)}
</div> </div>
); );
}; };

View File

@@ -109,22 +109,46 @@ const StatusContainer = ({
} }
}, [status]); }, [status]);
const hasChanges =
status.incoming_changes.length > 0 ||
status.outgoing_changes.length > 0;
return ( return (
<div className='dark:bg-gray-800 border border-gray-200 dark:border-gray-700 p-4 rounded-md'> <div className='dark:bg-gray-800 border border-gray-200 dark:border-gray-700 p-4 rounded-md'>
<div className='flex items-center'> <div className='flex items-center justify-between'>
<GitMerge className='mr-2 text-green-400' size={14} /> <div className='flex items-center'>
<h3 className='text-m font-semibold text-gray-100 mr-2'> <GitMerge className='mr-2 text-green-400' size={14} />
Sync Status: <h3 className='text-m font-semibold text-gray-100 mr-2'>
</h3> Sync Status:
{status.incoming_changes.length === 0 && </h3>
status.outgoing_changes.length === 0 ? ( {!hasChanges ? (
<span className='text-m font-medium'> <span className='text-m font-medium'>
{noChangesMessage} {noChangesMessage}
</span> </span>
) : ( ) : (
<span className='text-gray-400 text-m flex items-center'> <span className='text-gray-400 text-m flex items-center'>
Out of Date! Out of Date!
</span> </span>
)}
</div>
{!hasChanges && (
<div className='flex-shrink-0'>
<ActionButtons
isDevMode={isDevMode}
selectedOutgoingChanges={selectedOutgoingChanges}
selectedIncomingChanges={selectedIncomingChanges}
selectionType={selectionType}
commitMessage={commitMessage}
loadingAction={loadingAction}
onStageSelected={onStageSelected}
onCommitSelected={onCommitSelected}
onRevertSelected={onRevertSelected}
onPullSelected={onPullSelected}
getStageButtonTooltip={getStageButtonTooltip}
getCommitButtonTooltip={getCommitButtonTooltip}
getRevertButtonTooltip={getRevertButtonTooltip}
/>
</div>
)} )}
</div> </div>
@@ -160,30 +184,36 @@ const StatusContainer = ({
/> />
)} )}
<CommitSection {hasChanges && (
status={status} <>
commitMessage={commitMessage} <CommitSection
setCommitMessage={setCommitMessage} status={status}
loadingAction={loadingAction} commitMessage={commitMessage}
hasIncomingChanges={status.incoming_changes.length > 0} setCommitMessage={setCommitMessage}
isDevMode={isDevMode} loadingAction={loadingAction}
/> hasIncomingChanges={status.incoming_changes.length > 0}
isDevMode={isDevMode}
/>
<ActionButtons <div className='mt-4 flex justify-end'>
isDevMode={isDevMode} <ActionButtons
selectedOutgoingChanges={selectedOutgoingChanges} isDevMode={isDevMode}
selectedIncomingChanges={selectedIncomingChanges} selectedOutgoingChanges={selectedOutgoingChanges}
selectionType={selectionType} selectedIncomingChanges={selectedIncomingChanges}
commitMessage={commitMessage} selectionType={selectionType}
loadingAction={loadingAction} commitMessage={commitMessage}
onStageSelected={onStageSelected} loadingAction={loadingAction}
onCommitSelected={onCommitSelected} onStageSelected={onStageSelected}
onRevertSelected={onRevertSelected} onCommitSelected={onCommitSelected}
onPullSelected={onPullSelected} onRevertSelected={onRevertSelected}
getStageButtonTooltip={getStageButtonTooltip} onPullSelected={onPullSelected}
getCommitButtonTooltip={getCommitButtonTooltip} getStageButtonTooltip={getStageButtonTooltip}
getRevertButtonTooltip={getRevertButtonTooltip} getCommitButtonTooltip={getCommitButtonTooltip}
/> getRevertButtonTooltip={getRevertButtonTooltip}
/>
</div>
</>
)}
</div> </div>
); );
}; };

View File

@@ -14,18 +14,18 @@ export const statusLoadingMessages = [
]; ];
export const noChangesMessages = [ export const noChangesMessages = [
'No changes detected. Your regex is so precise, it could find a needle in a haystack... made of needles. 🧵🔍', 'All synced up! Smooth sailing ahead.',
'All quiet on the commit front. Your custom formats are so perfect, even perfectionists are jealous. 🏆', "No changes detected. We're all good here.",
"No updates needed. Your media automation is running so smoothly, it's making butter jealous. 🧈", "Everything's in order. Carry on!",
'Zero modifications. Your torrent setup is seeding so efficiently, farmers are asking for advice. 🌾', 'Up to date and ready to go.',
"No edits required. Your regex fu is so strong, it's bench-pressing parentheses for fun. 💪()", 'All quiet on the regex front!',
'Unchanged status. Your Plex library is so well-organized, librarians are taking notes. 📚🤓', 'Perfectly synced! Nice work.',
"No alterations found. Your file naming scheme is so consistent, it's bringing tears to OCD eyes. 😢👀", "No updates needed. You're all set.",
"All systems nominal. Your download queue is so orderly, it's making Marie Kondo question her career. 🧹✨", "Everything's hunky-dory in your repository.",
"No revisions necessary. Your automation scripts are so smart, they're solving captchas for fun. 🤖🧩", 'All good in the code neighborhood.',
'Steady as she goes. Your media collection is so complete, Netflix is asking you for recommendations. 🎬👑' 'Sync complete! Ship-Shape.'
]; ];
export const getRandomMessage = (messages) => { export const getRandomMessage = messages => {
return messages[Math.floor(Math.random() * messages.length)]; return messages[Math.floor(Math.random() * messages.length)];
}; };