mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
better commandbar ui
This commit is contained in:
parent
decd0c9bc4
commit
e0156803ff
2 changed files with 187 additions and 217 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue