Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add shortcuts to brush tools #8519

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open

Conversation

klakhov
Copy link
Contributor

@klakhov klakhov commented Oct 8, 2024

Motivation and context

How has this been tested?

Checklist

  • I submit my changes into the develop branch
  • I have created a changelog fragment
  • I have updated the documentation accordingly
  • I have added tests to cover my changes
  • I have linked related issues (see GitHub docs)
  • I have increased versions of npm packages if it is necessary
    (cvat-canvas,
    cvat-core,
    cvat-data and
    cvat-ui)

License

  • I submit my code changes under the same MIT License that covers the project.
    Feel free to contact the maintainers if that's a concern.

Summary by CodeRabbit

  • New Features

    • Introduced keyboard shortcuts for various brush tools, enhancing user efficiency.
    • Added tooltips displaying keyboard shortcuts for better usability.
    • Implemented a LabelSelector component for improved label selection.
  • Improvements

    • Updated sorting logic for shortcut settings to organize items by their defined weights.
  • Refactor

    • Enhanced state management and control flow for brush tool interactions.
    • Added a new optional weight property to the key mapping interface for better customization.

Copy link
Contributor

coderabbitai bot commented Oct 8, 2024

Walkthrough

The changes involve enhancements to the BrushTools component, including the introduction of keyboard shortcuts for various brush tools, refactoring of state management, and improved rendering logic with tooltips. Additionally, the sorting logic for shortcut items in the ShortcutsSettingsComponent has been updated to sort by a new weight property. A new optional weight property has also been added to the KeyMapItem interface, allowing for more customizable key mappings.

Changes

File Path Change Summary
cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-tools.tsx Added keyboard shortcuts for brush tools, refactored state management, updated rendering with tooltips, and modified layout with a LabelSelector.
cvat-ui/src/components/header/settings-modal/shortcut-settings.tsx Updated sorting logic for viewFilteredItems to sort by weight property in ascending order.
cvat-ui/src/utils/mousetrap-react.tsx Added optional weight property to KeyMapItem interface for key mapping customization.

Poem

In the brush tools where colors play,
Shortcuts now dance in a lively array.
With weights to guide the order so neat,
Our canvas awaits, a creative retreat! 🎨✨
So hop to it, dear friends, don’t delay,
Let’s paint our dreams in a whimsical way! 🐇


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

sonarcloud bot commented Oct 8, 2024

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (2)
cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-tools.tsx (2)

Line range hint 159-172: Ensure label color is always defined

The code assumes that label.color is always defined. To prevent potential runtime errors, consider adding a fallback color or handling the case where label.color might be undefined.

Apply this diff to handle undefined label colors:

-            color: label.color as string,
+            color: label?.color ?? defaultColor,

Replace defaultColor with an appropriate fallback color value.


284-305: Redundant disabled prop on continue button

The disabled prop on the "Continue" button is unnecessarily dependent on editableState, but the button is already conditionally rendered based on !editableState. This redundancy can be removed to simplify the code.

Apply this diff to remove the redundant disabled prop:

<CVATTooltip title={`Continue ${normalizedKeyMap.SWITCH_REDRAW_MODE_STANDARD_CONTROLS}`}>
    <Button
        type='text'
-       disabled={!!editableState}
        className='cvat-brush-tools-continue'
        icon={<Icon component={PlusIcon} />}
        onClick={() => {
            // ...
        }}
    />
</CVATTooltip>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 0572fa1 and 8f7a7dd.

📒 Files selected for processing (3)
  • cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-tools.tsx (6 hunks)
  • cvat-ui/src/components/header/settings-modal/shortcut-settings.tsx (1 hunks)
  • cvat-ui/src/utils/mousetrap-react.tsx (1 hunks)
🧰 Additional context used
🔇 Additional comments (7)
cvat-ui/src/utils/mousetrap-react.tsx (1)

18-18: Consider documenting the purpose and usage of the weight property.

The addition of the optional weight property to the KeyMapItem interface is a good enhancement that allows for more flexible key mapping configurations. However, to ensure proper usage and maintainability, consider the following suggestions:

  1. Add a brief comment explaining the purpose and expected usage of the weight property. This will help other developers understand how to use it correctly.

  2. Update any relevant documentation or README files to mention this new property and its implications on shortcut configurations.

  3. Verify if any other files in the project need to be updated to utilize this new property, especially those related to shortcut management or display.

  4. If there's a sorting mechanism for shortcuts, ensure it's updated to consider the weight property.

To ensure consistent usage of the new weight property, let's check for any potential usage or sorting mechanisms:

cvat-ui/src/components/header/settings-modal/shortcut-settings.tsx (1)

77-77: 💡 Codebase verification

🛠️ Refactor suggestion

Missing weight Property in keyMap Items Affects Sorting Logic

The weight property used in the sorting logic is missing or improperly typed in all instances of keyMap across the following files:

  • cvat-ui/src/utils/hooks.ts
  • cvat-ui/src/reducers/shortcuts-reducer.ts
  • cvat-ui/src/components/shortcuts.context.tsx
  • cvat-ui/src/components/shortcuts-dialog/shortcuts-dialog.tsx
  • cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/shortcuts-select.tsx
  • cvat-ui/src/components/layout-grid/layout-grid.tsx
  • cvat-ui/src/components/labels-editor/skeleton-configurator.tsx

Impact:

  • The sorting based on weight will not function as intended, potentially leading to incorrect ordering of shortcut items.
  • This may also cause runtime errors if the weight property is expected to be a number but is undefined.

Recommended Actions:

  1. Define the weight Property:

    • Ensure that the weight property is added to all keyMap items with appropriate type definitions.
  2. Update Type Definitions:

    • Modify the KeyMapItem interface to include the weight property, ensuring type safety.
  3. Review Sorting Logic:

    • Verify and test the sorting logic to confirm it behaves correctly once the weight property is properly implemented.
🔗 Analysis chain

Enhance sorting logic with proper type checking and performance considerations.

The addition of sorting based on the weight property is a good improvement for customizing the order of shortcut items. However, there are a few points to consider:

  1. Type safety: Ensure that the weight property is properly typed in the KeyMapItem interface to prevent potential runtime errors.
  2. Consistency: Verify that all items in the keyMap have the weight property defined, or provide a fallback mechanism for items without it.
  3. Performance: For large lists, consider moving the sorting logic outside of the useMemo hook to optimize performance.

Consider refactoring the code as follows:

// Add this type to your interfaces or types file
interface WeightedKeyMapItem extends KeyMapItem {
  weight?: number;
}

// In your component
const sortedFilteredKeyMap = useMemo(() => {
  return filteredKeyMap.sort(([, item1], [, item2]) => {
    const weight1 = (item1 as WeightedKeyMapItem).weight ?? 0;
    const weight2 = (item2 as WeightedKeyMapItem).weight ?? 0;
    return weight1 - weight2;
  });
}, [filteredKeyMap]);

// Then use sortedFilteredKeyMap instead of filteredKeyMap in your items computation

Please run the following script to verify the presence and type of the weight property across all keyMap items:

This will help ensure that the weight property is consistently implemented across the codebase.


🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the presence and type of the 'weight' property in keyMap items

# Test: Search for keyMap definitions and their item properties
rg --type typescript -g '!*.test.ts' -g '!*.spec.ts' --multiline --multiline-dotall 'const\s+keyMap\s*=\s*{[^}]+}' -A 10

Length of output: 255


Script:

#!/bin/bash
# Description: Verify the presence and type of the 'weight' property in keyMap items

# Corrected regex: Escaped braces to prevent parse errors
rg --type typescript -g '!*.test.ts' -g '!*.spec.ts' --multiline --multiline-dotall 'const\s+keyMap\s*=\s*\{[^}]+\}' -A 10

Length of output: 159


Script:

#!/bin/bash
# Description: Verify the presence and type of the 'weight' property in keyMap items

# Use file extensions to target TypeScript files
rg -g '*.ts' -g '*.tsx' --multiline --multiline-dotall 'const\s+keyMap\s*=\s*\{[^}]+\}' -A 10

Length of output: 95


Script:

#!/bin/bash
# Description: Broadly search for any declarations or imports of 'keyMap' in TypeScript files

# Search for any variable declarations (const, let, var) of keyMap
rg -g '*.ts' -g '*.tsx' --ignore-file .gitignore '\b(const|let|var)\s+keyMap\b'

# Additionally, search for imports of keyMap from other modules
rg -g '*.ts' -g '*.tsx' --ignore-file .gitignore 'import\s+[^;]*\bkeyMap\b[^;]*;'

Length of output: 1024


Script:

#!/bin/bash
# Description: Verify the presence and type of the 'weight' property in keyMap items across identified files

# List of files where keyMap is defined
FILES=(
  "cvat-ui/src/utils/hooks.ts"
  "cvat-ui/src/reducers/shortcuts-reducer.ts"
  "cvat-ui/src/components/shortcuts.context.tsx"
  "cvat-ui/src/components/shortcuts-dialog/shortcuts-dialog.tsx"
  "cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/shortcuts-select.tsx"
  "cvat-ui/src/components/layout-grid/layout-grid.tsx"
  "cvat-ui/src/components/labels-editor/skeleton-configurator.tsx"
)

# Iterate over each file and search for the 'weight' property in keyMap
for FILE in "${FILES[@]}"; do
  echo "Checking 'weight' property in $FILE:"
  rg --multiline --multiline-dotall 'keyMap\s*=\s*\{[^}]*\bweight\s*:\s*\d+' "$FILE" && echo "-> 'weight' property found and properly typed." || echo "-> 'weight' property missing or improperly typed."
  echo ""
done

Length of output: 3767

cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-tools.tsx (5)

39-69: Component shortcuts are correctly defined and registered

The componentShortcuts object is well-defined with appropriate names, descriptions, sequences, scopes, and weights for each shortcut. The registration of these shortcuts using registerComponentShortcuts(componentShortcuts); is correctly implemented.


93-105: Tool selection callbacks are properly implemented

The use of useCallback hooks for tool selection functions (setBrushTool, setEraserTool, setPolygonTool, setPolygonRemoveTool) optimizes performance by preventing unnecessary re-renders. The blocked tools are appropriately handled to prevent activation when they are disabled.


106-111: Shortcut handlers are correctly mapped

The handlers object accurately maps the component shortcuts to their respective callback functions, ensuring that keyboard shortcuts activate the correct tools.


261-266: GlobalHotKeys component is properly integrated

The GlobalHotKeys component is correctly set up with the keyMap and handlers, ensuring that the keyboard shortcuts are active within this component.


370-381: Ensure consistent label selection handling

When changing the label using the LabelSelector, it's good practice to verify that the selected label exists and handle any potential errors if it doesn't. Although the current implementation checks if labelID is an integer, additional validation can enhance robustness.

Run the following script to check for any references to non-existent label IDs:

✅ Verification successful

Label ID Validation Passed

All label IDs used in the LabelSelector are properly defined and validated. No undefined label IDs were found.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that all label IDs used in the code correspond to existing labels.

# Test: Search for label IDs in the codebase and cross-reference with defined labels.
# Expect: All label IDs used are defined in the labels list.

# Get all label IDs from the code
rg --type js --pcre2 "(?<=(labelID\s*:\s*))\d+" -o -r '$0' --no-filename | sort -u > used_label_ids.txt

# Get all defined label IDs
rg --type js "id\s*:\s*\d+" -o -r '$0' --no-filename | sort -u > defined_label_ids.txt

# Compare the two lists
comm -23 used_label_ids.txt defined_label_ids.txt > undefined_label_ids.txt

if [ -s undefined_label_ids.txt ]; then
    echo "Undefined label IDs found:"
    cat undefined_label_ids.txt
else
    echo "All label IDs are properly defined."
fi

# Clean up
rm used_label_ids.txt defined_label_ids.txt undefined_label_ids.txt

Length of output: 547


Script:

#!/bin/bash
# Description: Verify that all label IDs used in the code correspond to existing labels.

# Test: Search for label IDs in the codebase and cross-reference with defined labels.
# Expect: All label IDs used are defined in the labels list.

# Get all label IDs from the code by capturing the value after 'labelID: '
rg --type js '"labelID"\s*:\s*(\d+)' -o -r '$1' --no-filename | sort -u > used_label_ids.txt

# Get all defined label IDs by capturing the value after 'id: '
rg --type js '"id"\s*:\s*(\d+)' -o -r '$1' --no-filename | sort -u > defined_label_ids.txt

# Compare the two lists to find used label IDs that are not defined
grep -Fxv -f defined_label_ids.txt used_label_ids.txt > undefined_label_ids.txt

if [ -s undefined_label_ids.txt ]; then
    echo "Undefined label IDs found:"
    cat undefined_label_ids.txt
else
    echo "All label IDs are properly defined."
fi

# Clean up
rm used_label_ids.txt defined_label_ids.txt undefined_label_ids.txt

Length of output: 386

Comment on lines +344 to +362
<CVATTooltip title='Brush size [Hold Alt + Right Mouse Click + Drag Left/Right]'>
<InputNumber
className='cvat-brush-tools-brush-size'
value={brushSize}
min={MIN_BRUSH_SIZE}
onChange={(value: number | null) => {
if (typeof value === 'number' && Number.isInteger(value) && value >= MIN_BRUSH_SIZE) {
setBrushSize(value);
}
}}
/>
</CVATTooltip>
) : null}
{ ['brush', 'eraser'].includes(currentTool) ? (
<Select value={brushForm} onChange={(value: 'circle' | 'square') => setBrushForm(value)}>
<Select.Option value='circle'>Circle</Select.Option>
<Select.Option value='square'>Square</Select.Option>
</Select>
) : null}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve accessibility of brush size and form selectors

Consider adding labels to the InputNumber and Select components for better accessibility and user experience. This helps screen readers to identify the purpose of the input fields.

Apply this change to add labels:

<CVATTooltip title='Brush size [Hold Alt + Right Mouse Click + Drag Left/Right]'>
+   <label htmlFor='brush-size'>Brush Size:</label>
    <InputNumber
+       id='brush-size'
        className='cvat-brush-tools-brush-size'
        // ...
    />
</CVATTooltip>

+<label htmlFor='brush-form'>Brush Shape:</label>
<Select
+   id='brush-form'
    value={brushForm}
    onChange={(value: 'circle' | 'square') => setBrushForm(value)}
>
    <Select.Option value='circle'>Circle</Select.Option>
    <Select.Option value='square'>Square</Select.Option>
</Select>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<CVATTooltip title='Brush size [Hold Alt + Right Mouse Click + Drag Left/Right]'>
<InputNumber
className='cvat-brush-tools-brush-size'
value={brushSize}
min={MIN_BRUSH_SIZE}
onChange={(value: number | null) => {
if (typeof value === 'number' && Number.isInteger(value) && value >= MIN_BRUSH_SIZE) {
setBrushSize(value);
}
}}
/>
</CVATTooltip>
) : null}
{ ['brush', 'eraser'].includes(currentTool) ? (
<Select value={brushForm} onChange={(value: 'circle' | 'square') => setBrushForm(value)}>
<Select.Option value='circle'>Circle</Select.Option>
<Select.Option value='square'>Square</Select.Option>
</Select>
) : null}
<CVATTooltip title='Brush size [Hold Alt + Right Mouse Click + Drag Left/Right]'>
<label htmlFor='brush-size'>Brush Size:</label>
<InputNumber
id='brush-size'
className='cvat-brush-tools-brush-size'
value={brushSize}
min={MIN_BRUSH_SIZE}
onChange={(value: number | null) => {
if (typeof value === 'number' && Number.isInteger(value) && value >= MIN_BRUSH_SIZE) {
setBrushSize(value);
}
}}
/>
</CVATTooltip>
) : null}
{ ['brush', 'eraser'].includes(currentTool) ? (
<>
<label htmlFor='brush-form'>Brush Shape:</label>
<Select
id='brush-form'
value={brushForm}
onChange={(value: 'circle' | 'square') => setBrushForm(value)}
>
<Select.Option value='circle'>Circle</Select.Option>
<Select.Option value='square'>Square</Select.Option>
</Select>
</>
) : null}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant