better commandbar ui

This commit is contained in:
Mathew Pareles 2025-05-05 21:36:20 -07:00
parent decd0c9bc4
commit e0156803ff
2 changed files with 187 additions and 217 deletions

View file

@ -2420,53 +2420,6 @@ const _ChatBubble = ({ threadId, chatMessage, currCheckpointIdx, isCommitted, me
}
export const AcceptAllButtonWrapper = ({ text, onClick, className }: { text: string, onClick: () => void, className?: string }) => (
<button
className={`
px-1 py-0.5
flex items-center gap-1
text-white text-[11px] text-nowrap
rounded-md
cursor-pointer
${className}
`}
style={{
backgroundColor: acceptAllBg,
border: acceptBorder,
}}
type='button'
onClick={onClick}
>
{text ? <span>{text}</span> : <Check size={16} />}
</button>
)
export const RejectAllButtonWrapper = ({ text, onClick, className }: { text: string, onClick: () => void, className?: string }) => (
<button
className={`
px-1 py-0.5
flex items-center gap-1
text-white text-[11px] text-nowrap
rounded-md
cursor-pointer
${className}
`}
style={{
backgroundColor: rejectAllBg,
border: rejectBorder,
}}
type='button'
onClick={onClick}
>
{text ? <span>{text}</span> : <X size={16} />}
</button>
)
const CommandBarInChat = () => {
const { stateOfURI: commandBarStateOfURI, sortedURIs: sortedCommandBarURIs } = useCommandBarState()
const numFilesChanged = sortedCommandBarURIs.length

View file

@ -9,9 +9,9 @@ import { useAccessor, useCommandBarState, useIsDark } from '../util/services.js'
import '../styles.css'
import { useCallback, useEffect, useState, useRef } from 'react';
import { ScrollType } from '../../../../../../../editor/common/editorCommon.js';
import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectBg, rejectBorder } from '../../../../common/helpers/colors.js';
import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg, rejectBg, rejectBorder } from '../../../../common/helpers/colors.js';
import { VoidCommandBarProps } from '../../../voidCommandBarService.js';
import { AcceptAllButtonWrapper, RejectAllButtonWrapper } from '../sidebar-tsx/SidebarChat.js';
import { Check, EllipsisVertical, Menu, MoveDown, MoveLeft, MoveRight, MoveUp, X } from 'lucide-react';
export const VoidCommandBarMain = ({ uri, editor }: VoidCommandBarProps) => {
const isDark = useIsDark()
@ -23,8 +23,6 @@ export const VoidCommandBarMain = ({ uri, editor }: VoidCommandBarProps) => {
</div>
}
const stepIdx = (currIdx: number | null, len: number, step: -1 | 1) => {
if (len === 0) return null
return ((currIdx ?? 0) + step + len) % len // for some reason, small negatives are kept negative. just add len to offset
@ -32,7 +30,55 @@ const stepIdx = (currIdx: number | null, len: number, step: -1 | 1) => {
const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
export const AcceptAllButtonWrapper = ({ text, onClick, className }: { text: string, onClick: () => void, className?: string }) => (
<button
className={`
px-2 py-0.5
flex items-center gap-1
text-white text-[11px] text-nowrap
h-full rounded-none
cursor-pointer
${className}
`}
style={{
backgroundColor: 'var(--vscode-button-background)',
color: 'var(--vscode-button-foreground)',
border: 'none',
}}
type='button'
onClick={onClick}
>
{text ? <span>{text}</span> : <Check size={16} />}
</button>
)
export const RejectAllButtonWrapper = ({ text, onClick, className }: { text: string, onClick: () => void, className?: string }) => (
<button
className={`
px-2 py-0.5
flex items-center gap-1
text-white text-[11px] text-nowrap
h-full rounded-none
cursor-pointer
${className}
`}
style={{
backgroundColor: 'var(--vscode-button-secondaryBackground)',
color: 'var(--vscode-button-secondaryForeground)',
border: 'none',
}}
type='button'
onClick={onClick}
>
{text ? <span>{text}</span> : <X size={16} />}
</button>
)
export const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
const accessor = useAccessor()
const editCodeService = accessor.get('IEditCodeService')
const editorService = accessor.get('ICodeEditorService')
@ -41,11 +87,7 @@ const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
const commandBarService = accessor.get('IVoidCommandBarService')
const voidModelService = accessor.get('IVoidModelService')
const { stateOfURI: commandBarState, sortedURIs: sortedCommandBarURIs } = useCommandBarState()
// useEffect(() => {
// console.log('MOUNTING!!!')
// }, [])
const [showAcceptRejectAllButtons, setShowAcceptRejectAllButtons] = useState(false)
// latestUriIdx is used to remember place in leftRight
const _latestValidUriIdxRef = useRef<number | null>(null)
@ -111,7 +153,7 @@ const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
const { model } = await voidModelService.getModelSafe(nextURI)
if (model) {
// switch to the URI
editorService.openCodeEditor({ resource: nextURI, options: { revealIfVisible: true } }, editor)
editorService.openCodeEditor({ resource: model.uri, options: { revealIfVisible: true } }, editor)
}
}
@ -119,7 +161,6 @@ const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
const sortedDiffIds = uri ? commandBarState[uri.fsPath]?.sortedDiffIds ?? [] : []
const sortedDiffZoneIds = uri ? commandBarState[uri.fsPath]?.sortedDiffZoneIds ?? [] : []
const isADiffInThisFile = sortedDiffIds.length !== 0
const isADiffZoneInThisFile = sortedDiffZoneIds.length !== 0
const isADiffZoneInAnyFile = sortedCommandBarURIs.length !== 0
@ -133,185 +174,161 @@ const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
const prevURIIdx = getNextUriIdx(-1)
const upDownDisabled = prevDiffIdx === null || nextDiffIdx === null
const leftRightDisabled = prevURIIdx === null || nextURIIdx === null // || (sortedCommandBarURIs.length === 1 && isADiffZoneInThisFile)
const upButton = <button
className={`
size-6 rounded cursor-default
hover:bg-void-bg-1-alt
`}// --border border-void-border-3 focus:border-void-border-1
disabled={upDownDisabled}
onClick={() => { goToDiffIdx(prevDiffIdx) }}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
goToDiffIdx(prevDiffIdx);
}
}}
></button>
const downButton = <button
className={`
size-6 rounded cursor-default
hover:bg-void-bg-1-alt
`}
disabled={upDownDisabled}
onClick={() => { goToDiffIdx(nextDiffIdx) }}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
goToDiffIdx(nextDiffIdx);
}
}}
></button>
const leftButton = <button
className={`
size-6 rounded cursor-default
hover:bg-void-bg-1-alt
`}
disabled={leftRightDisabled}
onClick={() => goToURIIdx(prevURIIdx)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
goToURIIdx(prevURIIdx);
}
}}
></button>
const rightButton = <button
className={`
size-6 rounded cursor-default
hover:bg-void-bg-1-alt
`}
disabled={leftRightDisabled}
onClick={() => goToURIIdx(nextURIIdx)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
goToURIIdx(nextURIIdx);
}
}}
></button>
const leftRightDisabled = prevURIIdx === null || nextURIIdx === null
// accept/reject if current URI has changes
const onAcceptAll = () => {
const onAcceptFile = () => {
if (!uri) return
editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false, _addToHistory: true })
metricsService.capture('Accept All', {})
}
const onRejectAll = () => {
const onRejectFile = () => {
if (!uri) return
editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false, _addToHistory: true })
metricsService.capture('Reject All', {})
}
if (!isADiffZoneInAnyFile) return null
// const acceptAllButton = <button
// className='text-nowrap'
// onClick={onAcceptAll}
// style={{
// backgroundColor: acceptAllBg,
// border: acceptBorder,
// color: buttonTextColor,
// fontSize: buttonFontSize,
// padding: '2px 4px',
// borderRadius: '6px',
// cursor: 'pointer'
// }}
// >
// Accept File
// </button>
return (
<div className="pointer-events-auto">
// const rejectAllButton = <button
// className='text-nowrap'
// onClick={onRejectAll}
// style={{
// backgroundColor: rejectBg,
// border: rejectBorder,
// color: 'white',
// fontSize: buttonFontSize,
// padding: '2px 4px',
// borderRadius: '6px',
// cursor: 'pointer'
// }}
// >
// Reject File
// </button>
const acceptAllButton = <AcceptAllButtonWrapper
text={'Keep Changes'}
onClick={onAcceptAll}
/>
{/* Accept All / Reject All buttons that appear when the vertical ellipsis is clicked */}
{showAcceptRejectAllButtons && showAcceptRejectAll && (
<div className="flex justify-end mb-1">
<div className="inline-flex bg-void-bg-2 rounded shadow-md border border-void-border-2 overflow-hidden">
<div className="flex items-center [&>*]:border-r [&>*]:border-void-border-2 [&>*:last-child]:border-r-0">
<AcceptAllButtonWrapper
text="Accept All"
onClick={() => {
onAcceptFile();
setShowAcceptRejectAllButtons(false);
}}
/>
<RejectAllButtonWrapper
text="Reject All"
onClick={() => {
onRejectFile();
setShowAcceptRejectAllButtons(false);
}}
/>
</div>
</div>
</div>
)}
const rejectAllButton = <RejectAllButtonWrapper
text={'Reject All'}
onClick={onRejectAll}
/>
<div className="flex items-center bg-void-bg-2 rounded shadow-md border border-void-border-2 [&>*:first-child]:pl-3 [&>*:last-child]:pr-3 [&>*]:px-3 [&>*]:border-r [&>*]:border-void-border-2 [&>*:last-child]:border-r-0">
const acceptRejectAllButtons = <div className="flex items-center gap-1 text-sm">
{acceptAllButton}
{rejectAllButton}
</div>
{/* Diff Navigation Group */}
<div className="flex items-center">
<button
className="cursor-pointer"
disabled={upDownDisabled}
onClick={() => goToDiffIdx(prevDiffIdx)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
goToDiffIdx(prevDiffIdx);
}
}}
title="Previous diff"
>
<MoveUp className='size-3 transition-opacity duration-200 opacity-70 hover:opacity-100' />
</button>
<span className="text-xs whitespace-nowrap px-1">
{isADiffInThisFile
? `Diff ${(currDiffIdx ?? 0) + 1} of ${sortedDiffIds.length}`
: streamState === 'streaming'
? 'No changes yet'
: 'No changes'
}
// const closeCommandBar = useCallback(() => {
// commandService.executeCommand('void.hideCommandBar');
// }, [commandService]);
</span>
<button
className="cursor-pointer"
disabled={upDownDisabled}
onClick={() => goToDiffIdx(nextDiffIdx)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
goToDiffIdx(nextDiffIdx);
}
}}
title="Next diff"
>
<MoveDown className='size-3 transition-opacity duration-200 opacity-70 hover:opacity-100' />
</button>
</div>
// const hideButton = <button
// className='ml-auto pointer-events-auto'
// onClick={closeCommandBar}
// style={{
// color: buttonTextColor,
// fontSize: buttonFontSize,
// padding: '2px 4px',
// borderRadius: '6px',
// cursor: 'pointer'
// }}
// title="Close command bar"
// >x
// </button>
const leftRightUpDownButtons = <div className='p-1 gap-1 flex flex-col items-center bg-void-bg-2 rounded shadow-md border border-void-border-2 w-full'>
<div className="flex flex-col gap-1">
{/* Changes in file */}
<div className={`${!isADiffZoneInThisFile ? 'hidden' : ''} flex items-center ${upDownDisabled ? 'opacity-50' : ''}`}>
{upButton}
{downButton}
<span className="min-w-16 px-2 text-xs leading-[1]">
{isADiffInThisFile ?
`Diff ${(currDiffIdx ?? 0) + 1} of ${sortedDiffIds.length}`
: streamState === 'streaming' ?
'No changes yet'
: `No changes`
}
</span>
</div>
{/* Files */}
<div className={`${!isADiffZoneInAnyFile ? 'hidden' : ''} flex items-center ${leftRightDisabled ? 'opacity-50' : ''}`}>
{leftButton}
{/* <div className="w-px h-3 bg-void-border-3 mx-0.5 shadow-sm"></div> */}
{rightButton}
{/* <div className="w-px h-3 bg-void-border-3 mx-0.5 shadow-sm"></div> */}
<span className="min-w-16 px-2 text-xs leading-[1]">
{currFileIdx !== null ?
`File ${currFileIdx + 1} of ${sortedCommandBarURIs.length}`
: `${sortedCommandBarURIs.length} file${sortedCommandBarURIs.length === 1 ? '' : 's'} changed`
}
</span>
{/* File Navigation Group */}
<div className="flex items-center">
<button
className="cursor-pointer"
disabled={leftRightDisabled}
onClick={() => goToURIIdx(prevURIIdx)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
goToURIIdx(prevURIIdx);
}
}}
title="Previous file"
>
<MoveLeft className='size-3 transition-opacity duration-200 opacity-70 hover:opacity-100' />
</button>
<span className="text-xs whitespace-nowrap px-1 mx-0.5">
{currFileIdx !== null
? `File ${currFileIdx + 1} of ${sortedCommandBarURIs.length}`
: `${sortedCommandBarURIs.length} file${sortedCommandBarURIs.length === 1 ? '' : 's'}`
}
</span>
<button
className="cursor-pointer"
disabled={leftRightDisabled}
onClick={() => goToURIIdx(nextURIIdx)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
goToURIIdx(nextURIIdx);
}
}}
title="Next file"
>
<MoveRight className='size-3 transition-opacity duration-200 opacity-70 hover:opacity-100' />
</button>
</div>
{/* Accept/Reject buttons - only shown when appropriate */}
{showAcceptRejectAll && (
<div className='flex self-stretch gap-0 !px-0 !py-0'>
<AcceptAllButtonWrapper
text="Accept File"
onClick={onAcceptFile}
/>
<RejectAllButtonWrapper
text="Reject File"
onClick={onRejectFile}
/>
</div>
)}
{/* Triple colon menu button */}
{showAcceptRejectAll && <div className='!px-1 !py-0 flex justify-center items-center'>
<EllipsisVertical
className="cursor-pointer size-3"
onClick={() => setShowAcceptRejectAllButtons(!showAcceptRejectAllButtons)}
/>
</div>}
</div>
</div>
</div>
return <div className={`flex flex-col items-center gap-y-2 pointer-events-auto`}>
{showAcceptRejectAll && acceptRejectAllButtons}
{leftRightUpDownButtons}
</div>
)
}