diff --git a/src/main/frontend/app/app.css b/src/main/frontend/app/app.css index 39c7b627..3f141b35 100644 --- a/src/main/frontend/app/app.css +++ b/src/main/frontend/app/app.css @@ -182,7 +182,8 @@ body { } .monaco-editor .hunk-glyph-unchecked, -.monaco-editor .hunk-glyph-checked { +.monaco-editor .hunk-glyph-checked, +.monaco-editor .hunk-glyph-hit-area { cursor: pointer !important; display: flex !important; align-items: center; @@ -195,27 +196,25 @@ body { display: block; width: 14px; height: 14px; - border-radius: 3px; - margin-left: 6px; - margin-top: 2px; - border: 1.5px solid #6b7280; - background: transparent; - box-sizing: border-box; + margin-left: 5px; + background-size: 14px 14px; + background-repeat: no-repeat; } -.monaco-editor .hunk-glyph-checked::after { - border-color: #22c55e; - background: #22c55e; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M3.5 7.5L5.5 9.5L10.5 4.5' stroke='white' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - background-size: 14px 14px; +.monaco-editor .hunk-glyph-unchecked::after { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='1' y='1' width='14' height='14' rx='3' fill='none' stroke='%239ca3af' stroke-width='1.5'/%3E%3C/svg%3E"); } .monaco-editor .hunk-glyph-unchecked:hover::after { - border-color: #22c55e; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='1' y='1' width='14' height='14' rx='3' fill='none' stroke='%23fdc300' stroke-width='1.5'/%3E%3C/svg%3E"); +} + +.monaco-editor .hunk-glyph-checked::after { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='1' y='1' width='14' height='14' rx='3' fill='%23fdc300' stroke='%23fdc300' stroke-width='1.5'/%3E%3Cpath d='M4.5 8L7 10.5L11.5 5.5' stroke='white' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); } .monaco-editor .hunk-line-selected { - background: rgba(34, 197, 94, 0.08) !important; + background: color-mix(in srgb, var(--color-brand) 10%, transparent) !important; } .monaco-flow-attribute { @@ -233,6 +232,38 @@ body { color: #808080 !important; } +.react-flow__panel.react-flow__controls { + background-color: var(--color-background); + border: 1px solid var(--color-border); + border-radius: 6px; + box-shadow: none; + overflow: hidden; +} + +.react-flow__panel.react-flow__controls .react-flow__controls-button { + background-color: var(--color-background); + border-bottom: 1px solid var(--color-border); + fill: var(--color-foreground); +} + +.react-flow__panel.react-flow__controls .react-flow__controls-button:last-child { + border-bottom: none; +} + +.react-flow__panel.react-flow__controls .react-flow__controls-button:hover { + background-color: var(--color-hover); +} + +.react-flow__panel.react-flow__controls .react-flow__controls-button svg { + fill: var(--color-foreground); + max-width: 12px; + max-height: 12px; +} + +.react-flow__attribution { + background: transparent !important; +} + :root { /* Allotment Styling */ --focus-border: var(--color-brand); @@ -251,3 +282,26 @@ body { line-height: 1.4; white-space: nowrap; } + +* { + scrollbar-width: thin; + scrollbar-color: var(--color-border) transparent; +} + +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: var(--color-border); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--color-foreground-muted); +} diff --git a/src/main/frontend/app/components/datamapper/basic-components/generate-button.tsx b/src/main/frontend/app/components/datamapper/basic-components/generate-button.tsx index dbf8bbe8..bf1bc07b 100644 --- a/src/main/frontend/app/components/datamapper/basic-components/generate-button.tsx +++ b/src/main/frontend/app/components/datamapper/basic-components/generate-button.tsx @@ -16,7 +16,7 @@ export default function GenerateButton({ return ( +
diff --git a/src/main/frontend/app/components/file-structure/confirm-delete-dialog.tsx b/src/main/frontend/app/components/file-structure/confirm-delete-dialog.tsx index d68d1aba..bcd2e6e4 100644 --- a/src/main/frontend/app/components/file-structure/confirm-delete-dialog.tsx +++ b/src/main/frontend/app/components/file-structure/confirm-delete-dialog.tsx @@ -38,7 +38,7 @@ export default function ConfirmDeleteDialog({ name, isFolder, onConfirm, onCance -
diff --git a/src/main/frontend/app/components/file-structure/editor-file-structure.tsx b/src/main/frontend/app/components/file-structure/editor-file-structure.tsx index 68ea702d..ef30d1d8 100644 --- a/src/main/frontend/app/components/file-structure/editor-file-structure.tsx +++ b/src/main/frontend/app/components/file-structure/editor-file-structure.tsx @@ -15,6 +15,7 @@ import { useShortcut } from '~/hooks/use-shortcut' import { useFileWatcher } from '~/hooks/use-file-watcher' import { getAncestorIds, isVisibleInTree, selectAndReveal, toTreeItemId } from './tree-utilities' import type { ContextMenuState } from './use-file-tree-context-menu' +import IconButton from '~/components/inputs/icon-button' import { Tree, @@ -31,6 +32,7 @@ import { useTreeStore } from '~/stores/tree-store' import EditorFilesDataProvider, { type FileNode } from './editor-data-provider' import { useFileTreeContextMenu } from './use-file-tree-context-menu' import FileTreeDialogs from './file-tree-dialogs' +import TreeActionButton from '../inputs/tree-action-button' const TREE_ID = 'editor-files-tree' @@ -319,19 +321,19 @@ export default function EditorFileStructure() { const renderItemArrow = ({ item, context }: { item: TreeItem; context: TreeItemRenderContext }) => { if (!item.isFolder) return null - const Icon = context.isExpanded ? AltArrowDownIcon : AltArrowRightIcon - - const handleClick = (event: React.MouseEvent) => { - event.stopPropagation() - context.toggleExpandedState() - } + const ArrowIcon = context.isExpanded ? AltArrowDownIcon : AltArrowRightIcon return ( - { + mouseEvent.stopPropagation() + context.toggleExpandedState() + }} onContextMenu={(mouseEvent) => editorContextMenu.openContextMenu(mouseEvent, item.index)} - className="rct-tree-item-arrow-isFolder rct-tree-item-arrow fill-foreground" - /> + > + + ) } @@ -344,7 +346,7 @@ export default function EditorFileStructure() { item: TreeItem context: TreeItemRenderContext }) => { - const Icon = item.isFolder ? (context.isExpanded ? FolderOpenIcon : FolderIcon) : CodeIcon + const ItemIcon = item.isFolder ? (context.isExpanded ? FolderOpenIcon : FolderIcon) : CodeIcon const isRoot = (item.data as { projectRoot?: boolean }).projectRoot ?? false const searchLower = searchTerm.toLowerCase() @@ -353,16 +355,16 @@ export default function EditorFileStructure() { let highlightedTitle: JSX.Element | string = title if (searchTerm && titleLower.includes(searchLower)) { - const parts = title.split(new RegExp(`(${searchTerm})`, 'gi')) + const titleParts = title.split(new RegExp(`(${searchTerm})`, 'gi')) highlightedTitle = ( <> - {parts.map((part, i) => + {titleParts.map((part, partIndex) => part.toLowerCase() === searchLower ? ( - + {part} ) : ( - {part} + {part} ), )} @@ -371,14 +373,12 @@ export default function EditorFileStructure() { const isHighlighted = highlightedItemId === item.index - const actionBtnClass = 'cursor-pointer rounded p-0.5 hover:bg-hover flex-shrink-0' - return (
editorContextMenu.openContextMenu(e, item.index)} + onContextMenu={(mouseEvent) => editorContextMenu.openContextMenu(mouseEvent, item.index)} > - {Icon && } + {ItemIcon && }
{item.isFolder && ( -
{ - mouseEvent.stopPropagation() - triggerItemAction(item.index, editorContextMenu.handleNewFile) - }} - onKeyDown={(keyboardEvent) => - keyboardEvent.key === 'Enter' && triggerItemAction(item.index, editorContextMenu.handleNewFile) - } + onAction={() => triggerItemAction(item.index, editorContextMenu.handleNewFile)} > - -
+ + )} {item.isFolder && ( -
{ - mouseEvent.stopPropagation() - triggerItemAction(item.index, editorContextMenu.handleNewFolder) - }} - onKeyDown={(keyboardEvent) => - keyboardEvent.key === 'Enter' && triggerItemAction(item.index, editorContextMenu.handleNewFolder) - } + onAction={() => triggerItemAction(item.index, editorContextMenu.handleNewFolder)} > - -
+ + )} {!isRoot && ( -
{ - mouseEvent.stopPropagation() - triggerItemAction(item.index, editorContextMenu.handleRename) - }} - onKeyDown={(keyboardEvent) => - keyboardEvent.key === 'Enter' && triggerItemAction(item.index, editorContextMenu.handleRename) - } + onAction={() => triggerItemAction(item.index, editorContextMenu.handleRename)} > - -
+ + )} {!isRoot && ( -
{ - mouseEvent.stopPropagation() - triggerItemAction(item.index, editorContextMenu.handleDelete) - }} - onKeyDown={(keyboardEvent) => - keyboardEvent.key === 'Enter' && triggerItemAction(item.index, editorContextMenu.handleDelete) - } + onAction={() => triggerItemAction(item.index, editorContextMenu.handleDelete)} > - -
+ + )}
@@ -462,35 +426,27 @@ export default function EditorFileStructure() { if (!dataProvider) return - const toolbarBtnClass = 'cursor-pointer rounded p-1 hover:bg-hover text-foreground' - return ( <>
Explorer
- - - + +
@@ -498,8 +454,8 @@ export default function EditorFileStructure() {
{ - void editorContextMenu.openContextMenu(e, 'root') + onContextMenu={(mouseEvent) => { + void editorContextMenu.openContextMenu(mouseEvent, 'root') }} > @@ -20,16 +21,17 @@ export default function InlineRenameInput({ return (
e.preventDefault()}> - onChange(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault() + onChange={(changeEvent) => onChange(changeEvent.target.value)} + onKeyDown={(keyboardEvent) => { + if (keyboardEvent.key === 'Enter') { + keyboardEvent.preventDefault() void onSubmit(itemIndex, value) - } else if (e.key === 'Escape') { + } else if (keyboardEvent.key === 'Escape') { onCancel() } }} diff --git a/src/main/frontend/app/components/file-structure/studio-file-structure.tsx b/src/main/frontend/app/components/file-structure/studio-file-structure.tsx index 332d3f7f..96d79565 100644 --- a/src/main/frontend/app/components/file-structure/studio-file-structure.tsx +++ b/src/main/frontend/app/components/file-structure/studio-file-structure.tsx @@ -1,4 +1,5 @@ import React, { type JSX, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import IconButton from '~/components/inputs/icon-button' import { getListenerIcon } from './tree-utilities' import useTabStore from '~/stores/tab-store' import Search from '~/components/search/search' @@ -26,7 +27,7 @@ import { type TreeItemIndex, UncontrolledTreeEnvironment, } from 'react-complex-tree' -import FilesDataProvider, { +import StudioFilesDataProvider, { type StudioItemData, type StudioFolderData, } from '~/components/file-structure/studio-files-data-provider' @@ -34,10 +35,11 @@ import { useProjectStore } from '~/stores/project-store' import { useTreeStore } from '~/stores/tree-store' import { useStudioContextMenu, detectItemType, getItemName, resolveItemPaths } from './use-studio-context-menu' import StudioFileTreeDialogs from './studio-file-tree-dialogs' +import TreeActionButton from '../inputs/tree-action-button' const TREE_ID = 'studio-files-tree' -function studioTabItemId(activeTab: string, dataProvider: FilesDataProvider): string | null { +function studioTabItemId(activeTab: string, dataProvider: StudioFilesDataProvider): string | null { const firstSep = activeTab.indexOf('::') if (firstSep === -1) return null const configPath = activeTab.slice(0, firstSep) @@ -74,7 +76,7 @@ export default function StudioFileStructure() { const tree = useRef(null) - const [dataProvider, setDataProvider] = useState(null) + const [dataProvider, setDataProvider] = useState(null) const [providerLoading, setProviderLoading] = useState(false) const [selectedItemId, setSelectedItemId] = useState(null) const setTabData = useTabStore((state) => state.setTabData) @@ -185,7 +187,7 @@ export default function StudioFileStructure() { const initProvider = async () => { setProviderLoading(true) - const provider = new FilesDataProvider(project.name) + const provider = new StudioFilesDataProvider(project.name) await provider.init(expandedItemsRef.current) if (isMounted) { @@ -345,19 +347,19 @@ export default function StudioFileStructure() { const renderItemArrow = ({ item, context }: { item: TreeItem; context: TreeItemRenderContext }) => { if (!item.isFolder) return null - const Icon = context.isExpanded ? AltArrowDownIcon : AltArrowRightIcon - - const handleArrowClick = (event: React.MouseEvent) => { - event.stopPropagation() - context.toggleExpandedState() - } + const ArrowIcon = context.isExpanded ? AltArrowDownIcon : AltArrowRightIcon return ( - studioContextMenu.openContextMenu(mouseEvent, item.index)} - className="rct-tree-item-arrow-isFolder rct-tree-item-arrow fill-foreground" - /> +
{ + mouseEvent.stopPropagation() + context.toggleExpandedState() + }} + onContextMenu={(mouseEvent) => studioContextMenu.openContextMenu(mouseEvent, item.index)} + > + +
) } @@ -375,21 +377,20 @@ export default function StudioFileStructure() { ? (item.data as { listenerName: string | null }).listenerName : null - const isObject = typeof item.data === 'object' - - const pathEndsWithXmlExtension = (item.data as Partial).path?.endsWith('.xml') ?? false + const isDataObject = typeof item.data === 'object' + const pathEndsWithXml = (item.data as Partial).path?.endsWith('.xml') ?? false const isRoot = typeof item.data === 'string' - const isConfigFile = item.isFolder && isObject && item.data !== null && pathEndsWithXmlExtension + const isConfigFile = item.isFolder && isDataObject && item.data !== null && pathEndsWithXml const isPlainFolder = item.isFolder && !isConfigFile && !isRoot - let Icon + let ItemIcon if (isConfigFile) { - Icon = SettingsIcon + ItemIcon = SettingsIcon } else if (item.isFolder) { - Icon = context.isExpanded ? FolderOpenIcon : FolderIcon + ItemIcon = context.isExpanded ? FolderOpenIcon : FolderIcon } else { - Icon = getListenerIcon(listenerType) + ItemIcon = getListenerIcon(listenerType) } const searchLower = searchTerm.toLowerCase() @@ -398,31 +399,30 @@ export default function StudioFileStructure() { let highlightedTitle: JSX.Element | string = title if (searchTerm && titleLower.includes(searchLower)) { - const parts = title.split(new RegExp(`(${searchTerm})`, 'gi')) + const titleParts = title.split(new RegExp(`(${searchTerm})`, 'gi')) highlightedTitle = ( <> - {parts.map((part, index) => + {titleParts.map((part, partIndex) => part.toLowerCase() === searchLower ? ( - + {part} ) : ( - {part} + {part} ), )} ) } - const isHighlighted = highlightedItemId == item.index - const actionBtnClass = 'cursor-pointer rounded p-0.5 hover:bg-hover flex-shrink-0' + const isHighlighted = highlightedItemId === item.index return (
studioContextMenu.openContextMenu(mouseEvent, item.index)} > - +
{(isRoot || isPlainFolder) && ( -
{ - mouseEvent.stopPropagation() - triggerItemAction(item.index, studioContextMenu.handleNewConfiguration) - }} - onKeyDown={(keyboardEvent) => - keyboardEvent.key === 'Enter' && triggerItemAction(item.index, studioContextMenu.handleNewConfiguration) - } + onAction={() => triggerItemAction(item.index, studioContextMenu.handleNewConfiguration)} > - -
+ + )} {(isRoot || isPlainFolder) && ( -
{ - mouseEvent.stopPropagation() - triggerItemAction(item.index, studioContextMenu.handleNewFolder) - }} - onKeyDown={(keyboardEvent) => - keyboardEvent.key === 'Enter' && triggerItemAction(item.index, studioContextMenu.handleNewFolder) - } + onAction={() => triggerItemAction(item.index, studioContextMenu.handleNewFolder)} > - -
+ + )} {isConfigFile && ( -
{ - mouseEvent.stopPropagation() - triggerItemAction(item.index, studioContextMenu.handleNewAdapter) - }} - onKeyDown={(keyboardEvent) => - keyboardEvent.key === 'Enter' && triggerItemAction(item.index, studioContextMenu.handleNewAdapter) - } + onAction={() => triggerItemAction(item.index, studioContextMenu.handleNewAdapter)} > - -
+ + )} {!isRoot && ( -
{ - mouseEvent.stopPropagation() - triggerItemAction(item.index, studioContextMenu.handleRename) - }} - onKeyDown={(keyboardEvent) => - keyboardEvent.key === 'Enter' && triggerItemAction(item.index, studioContextMenu.handleRename) - } + onAction={() => triggerItemAction(item.index, studioContextMenu.handleRename)} > - -
+ + )} {!isRoot && ( -
{ - mouseEvent.stopPropagation() - triggerItemAction(item.index, studioContextMenu.handleDelete) - }} - onKeyDown={(keyboardEvent) => - keyboardEvent.key === 'Enter' && triggerItemAction(item.index, studioContextMenu.handleDelete) - } + onAction={() => triggerItemAction(item.index, studioContextMenu.handleDelete)} > - -
+ + )}
) } - if (!project) return

No Project Selected

+ if (!project) return

No Project Selected

if (providerLoading) return if (!dataProvider) - return

No configurations found in src/main/configurations

- - const toolbarBtnClass = 'cursor-pointer rounded p-1 hover:bg-hover text-foreground' + return

No configurations found in src/main/configurations

return ( <>
Explorer
- - - + +
@@ -562,8 +512,8 @@ export default function StudioFileStructure() {
{ - void studioContextMenu.openContextMenu(e, 'root') + onContextMenu={(mouseEvent) => { + void studioContextMenu.openContextMenu(mouseEvent, 'root') }} > { +export default class StudioFilesDataProvider extends BaseFilesDataProvider { private readonly projectName: string private rootPath = '' diff --git a/src/main/frontend/app/components/flow/add-subcomponent-modal.tsx b/src/main/frontend/app/components/flow/add-subcomponent-modal.tsx index 4d10574a..5c96b1ed 100644 --- a/src/main/frontend/app/components/flow/add-subcomponent-modal.tsx +++ b/src/main/frontend/app/components/flow/add-subcomponent-modal.tsx @@ -1,5 +1,6 @@ import { createPortal } from 'react-dom' import Button from '../inputs/button' +import Search from '~/components/search/search' import type { ElementDetails } from '@frankframework/doc-library-core' import { useMemo, useState, type ChangeEvent } from 'react' @@ -80,13 +81,7 @@ export default function AddSubcomponentModal({

Add Subcomponent

{/* Paragraph / content */} - +
    {filteredChildren.length > 0 ? ( @@ -99,7 +94,7 @@ export default function AddSubcomponentModal({ onClick={() => setSelectedElement(child)} onDoubleClick={handleAddChild} className={`cursor-pointer px-3 py-2 ${ - isSelected ? 'bg-foreground-active text-background' : 'hover:bg-foreground-active/10' + isSelected ? 'bg-foreground-active text-background' : 'hover:bg-hover' }`} > {child.name} @@ -107,7 +102,7 @@ export default function AddSubcomponentModal({ ) }) ) : ( -
  • No results found
  • +
  • No results found
  • )}
diff --git a/src/main/frontend/app/components/flow/canvas-context-menu.tsx b/src/main/frontend/app/components/flow/canvas-context-menu.tsx index 0892cd54..312c0a6c 100644 --- a/src/main/frontend/app/components/flow/canvas-context-menu.tsx +++ b/src/main/frontend/app/components/flow/canvas-context-menu.tsx @@ -46,7 +46,7 @@ export default function CanvasContextMenu({ const itemClass = 'flex items-center justify-between gap-6 px-3 py-1.5 text-sm whitespace-nowrap' const enabledClass = `${itemClass} cursor-pointer hover:bg-hover text-foreground` - const disabledClass = `${itemClass} cursor-default text-muted-foreground opacity-50` + const disabledClass = `${itemClass} cursor-default text-foreground-muted opacity-50` function menuItem(label: string, onClick: () => void, enabled: boolean, shortcutId?: string) { return ( @@ -62,7 +62,7 @@ export default function CanvasContextMenu({ } > {label} - {shortcutId && {formatShortcut(shortcutId)}} + {shortcutId && {formatShortcut(shortcutId)}}
) } diff --git a/src/main/frontend/app/components/flow/create-node-modal.tsx b/src/main/frontend/app/components/flow/create-node-modal.tsx index 9ffe6de0..6043b8e2 100644 --- a/src/main/frontend/app/components/flow/create-node-modal.tsx +++ b/src/main/frontend/app/components/flow/create-node-modal.tsx @@ -3,6 +3,8 @@ import useFlowStore from '~/stores/flow-store' import useNodeContextStore from '~/stores/node-context-store' import { useFFDoc } from '@frankframework/doc-library-react' import Button from '../inputs/button' +import CloseButton from '../inputs/close-button' +import Search from '~/components/search/search' import type { Elements, FFDocJson } from '@frankframework/doc-library-core' interface CreateNodeModalProperties { @@ -117,18 +119,17 @@ function CreateNodeModal({ return (
-
-

Add Node

-

Select the element to be added from the list below.

- +

Add Node

+

Select the element to be added from the list below.

+ handleOnChange(event)} - className="border-border focus:ring-foreground-active mb-3 w-full rounded border px-3 py-2 focus:ring focus:outline-none" /> -
-
    +
    +
      {filteredElements.length > 0 ? ( filteredElements.map((element) => { const isSelected = selectedElement === element.name @@ -139,7 +140,7 @@ function CreateNodeModal({ onClick={() => setSelectedElement(element.name)} onDoubleClick={handleCreateNode} className={`cursor-pointer px-3 py-2 ${ - isSelected ? 'bg-foreground-active text-background' : 'hover:bg-foreground-active/10' + isSelected ? 'bg-foreground-active text-background' : 'hover:bg-hover' }`} > {element.name} @@ -147,15 +148,15 @@ function CreateNodeModal({ ) }) ) : ( -
    • No results found
    • +
    • No results found
    • )}
    - -
) diff --git a/src/main/frontend/app/components/git/diff-tab-view.tsx b/src/main/frontend/app/components/git/diff-tab-view.tsx index 17f6834b..642ced2d 100644 --- a/src/main/frontend/app/components/git/diff-tab-view.tsx +++ b/src/main/frontend/app/components/git/diff-tab-view.tsx @@ -5,6 +5,7 @@ import { useTheme } from '~/hooks/use-theme' import { useGitStore } from '~/stores/git-store' import type { DiffTabData } from '~/stores/editor-tab-store' import type { GitHunk } from '~/types/git.types' +import Checkbox from '~/components/inputs/checkbox' type CodeEditor = ReturnType @@ -29,6 +30,7 @@ function applyHunkDecorations( const startLine = hunk.newStart const endLine = hunk.newStart + hunk.newCount - 1 + // Visible checkbox on the first line of the hunk decorations.push({ range: new monaco.Range(startLine, 1, startLine, 1), options: { @@ -37,6 +39,15 @@ function applyHunkDecorations( }, }) + for (let line = startLine + 1; line <= endLine; line++) { + decorations.push({ + range: new monaco.Range(line, 1, line, 1), + options: { + glyphMarginClassName: 'hunk-glyph-hit-area', + }, + }) + } + if (isSelected) { decorations.push({ range: new monaco.Range(startLine, 1, endLine, 1), @@ -127,11 +138,30 @@ export default function DiffTabView({ diffData }: DiffTabViewProps) { setEditorReady(true) }, []) + const selectableHunks = hunks.filter((hunk) => hunk.newCount > 0) + const allSelected = selectableHunks.length > 0 && selectableHunks.every((hunk) => selectedHunks.has(hunk.index)) + const someSelected = selectableHunks.some((hunk) => selectedHunks.has(hunk.index)) + + const handleToggleAllHunks = () => { + for (const hunk of selectableHunks) { + const isSelected = selectedHunks.has(hunk.index) + if (allSelected ? isSelected : !isSelected) { + toggleFileHunk(filePath, hunk.index) + } + } + } + const language = getLanguage(filePath) return ( <> -
+
+ {filePath}
diff --git a/src/main/frontend/app/components/git/git-changes.tsx b/src/main/frontend/app/components/git/git-changes.tsx index 67d13752..994a16fe 100644 --- a/src/main/frontend/app/components/git/git-changes.tsx +++ b/src/main/frontend/app/components/git/git-changes.tsx @@ -1,6 +1,7 @@ -import { useEffect, useRef, useState } from 'react' +import { useState } from 'react' import clsx from 'clsx' import type { GitStatus, FileHunkState } from '~/types/git.types' +import Checkbox from '~/components/inputs/checkbox' interface GitChangesProps { status: GitStatus | null @@ -18,38 +19,6 @@ const variantConfig: Record void - title: string -}) { - const ref = useRef(null) - - useEffect(() => { - if (ref.current) { - ref.current.indeterminate = indeterminate - } - }, [indeterminate]) - - return ( - e.stopPropagation()} - /> - ) -} - function FileSection({ title, files, @@ -80,7 +49,7 @@ function FileSection({ > {collapsed ? '▸' : '▾'} {title} - + {files.length} @@ -107,19 +76,18 @@ function FileSection({
onSelectFile(file)} > -
e.stopPropagation()}> - onToggleFile(file)} - title={checkboxChecked ? 'Deselect all chunks' : 'Select all chunks'} - /> -
+ onToggleFile(file)} + title={checkboxChecked ? 'Deselect all chunks' : 'Select all chunks'} + onClick={(mouseEvent) => mouseEvent.stopPropagation()} + /> {fileName} - {dirPath && {dirPath}} + {dirPath && {dirPath}}
) diff --git a/src/main/frontend/app/components/git/git-commit-box.tsx b/src/main/frontend/app/components/git/git-commit-box.tsx index 51bd7c28..f8f45ba3 100644 --- a/src/main/frontend/app/components/git/git-commit-box.tsx +++ b/src/main/frontend/app/components/git/git-commit-box.tsx @@ -1,3 +1,5 @@ +import Button from '~/components/inputs/button' + interface GitCommitBoxProps { commitMessage: string onMessageChange: (message: string) => void @@ -19,24 +21,25 @@ export default function GitCommitBox({