From 9822ceec6c89693ba60019776563c92736a40f32 Mon Sep 17 00:00:00 2001 From: shubh-bruno Date: Wed, 15 Apr 2026 15:17:42 +0530 Subject: [PATCH] fix: qol fixes for keybindings (#7709) * fix: keybindings issues * chore: let SingleLineEditor handle it's own handleSubmit * fix: resolve issues * fix: disable reset default if none are changed * fix: exlude transient request from reopen last closed tabs * fix: updated all hardcoded colors to respective theme colors * chore: pick color from theme --------- Co-authored-by: shubh-bruno Co-authored-by: Sid --- .../src/components/CodeEditor/index.js | 4 +- .../Preferences/Keybindings/StyledWrapper.js | 59 +++++++++++++------ .../Preferences/Keybindings/index.js | 57 ++++++++++++------ .../RequestPane/QueryEditor/index.js | 6 ++ .../RequestTabs/RequestTab/index.js | 6 +- .../components/Sidebar/NewRequest/index.js | 13 +--- .../middlewares/tasks/middleware.js | 3 +- .../src/providers/ReduxStore/slices/tabs.js | 11 ++-- 8 files changed, 104 insertions(+), 55 deletions(-) diff --git a/packages/bruno-app/src/components/CodeEditor/index.js b/packages/bruno-app/src/components/CodeEditor/index.js index 9a269c314..129260021 100644 --- a/packages/bruno-app/src/components/CodeEditor/index.js +++ b/packages/bruno-app/src/components/CodeEditor/index.js @@ -84,8 +84,8 @@ export default class CodeEditor extends React.Component { this.searchBarRef.current?.focus(); }); }, - 'Cmd-H': 'replace', - 'Ctrl-H': 'replace', + 'Cmd-H': this.props.readOnly ? false : 'replace', + 'Ctrl-H': this.props.readOnly ? false : 'replace', 'Tab': function (cm) { cm.getSelection().includes('\n') || editor.getLine(cm.getCursor().line) == cm.getSelection() ? cm.execCommand('indentMore') diff --git a/packages/bruno-app/src/components/Preferences/Keybindings/StyledWrapper.js b/packages/bruno-app/src/components/Preferences/Keybindings/StyledWrapper.js index f09b1e465..ff4b93c09 100644 --- a/packages/bruno-app/src/components/Preferences/Keybindings/StyledWrapper.js +++ b/packages/bruno-app/src/components/Preferences/Keybindings/StyledWrapper.js @@ -39,7 +39,6 @@ const StyledWrapper = styled.div` .section-divider { height: 1px; background: ${(props) => props.theme.input.border}; - margin: 10px 0; } .tables-container { @@ -75,7 +74,7 @@ const StyledWrapper = styled.div` thead { color: ${(props) => props.theme.table.thead.color} !important; - background: ${(props) => props.theme.sidebar.bg}; + background: ${(props) => props.theme.table.striped}; user-select: none; td { @@ -100,9 +99,8 @@ const StyledWrapper = styled.div` tr { transition: background 0.1s ease; height: 30px; - td { - padding: 0 10px !important; + padding: 0px 10px !important; border: none !important; vertical-align: middle; background: transparent; @@ -111,7 +109,7 @@ const StyledWrapper = styled.div` } tr:hover:not(.row-editing) td { - background: ${(props) => props.theme.sidebar.bg}; + background: ${(props) => props.theme.background.surface0}; cursor: pointer; } @@ -120,7 +118,7 @@ const StyledWrapper = styled.div` } tr.section-heading-row td { - font-weight: 600; + font-weight: 700; padding: 6px 10px !important; user-select: none; } @@ -131,8 +129,28 @@ const StyledWrapper = styled.div` } tr.section-last-row td { + border-bottom: none !important; + } + + tr.section-spacer-row { + height: 8px; + pointer-events: none; + } + + tr.section-spacer-row td { + padding: 0 !important; + height: 8px; + line-height: 8px; + font-size: 0; + background: transparent !important; + border: none !important; border-bottom: solid 1px ${(props) => props.theme.border.border0} !important; } + + tr.section-spacer-row:hover td { + background: transparent !important; + cursor: default; + } } .keybinding-row { @@ -180,7 +198,7 @@ const StyledWrapper = styled.div` } .shortcut-input--editing { - outline: 1px solid #E4AE49; + outline: 1px solid ${(props) => props.theme.status.warning.border}; border-radius: 4px; min-width: 100%; max-width: 100%; @@ -189,7 +207,7 @@ const StyledWrapper = styled.div` } .shortcut-input--error.shortcut-input--editing { - outline: 1px solid #CE4F3B; + outline: 1px solid ${(props) => props.theme.status.danger.border}; min-width: 100%; max-width: 100%; } @@ -220,39 +238,41 @@ const StyledWrapper = styled.div` align-items: center; justify-content: center; min-width: 20px; - height: 22px; + height: 20px; padding: 2px; border-radius: 3px; border: 1px solid ${(props) => props.theme.input.border}; background: ${(props) => props.theme.background.base}; color: ${(props) => props.theme.table.input.color}; - font-size: 12px; + font-size: 10px; font-weight: 500; line-height: 1; } - tbody tr.row-success td { - background: #2E8A540F; + tbody tr.row-success td, + tbody tr.row-success:hover td { + background: ${(props) => props.theme.status.success.background} !important; } - tbody tr.row-error td { - background: #D32F2F0F; + tbody tr.row-error td, + tbody tr.row-error:hover td { + background: ${(props) => props.theme.status.danger.background} !important; } .success-icon { - color: #2E8A54; + color: ${(props) => props.theme.status.success.text}; display: inline-flex; align-items: center; } .error-icon { - color: #CE4F3B; + color: ${(props) => props.theme.status.danger.text}; display: inline-flex; align-items: center; } .input-error-icon { - color: #CE4F3B; + color: ${(props) => props.theme.status.danger.text}; display: inline-flex; align-items: center; margin-left: auto; @@ -294,6 +314,11 @@ const StyledWrapper = styled.div` border-radius: 6px; padding: 0px 6px; cursor: pointer; + + &:disabled { + opacity: 0.45; + cursor: not-allowed; + } } .action-btn { diff --git a/packages/bruno-app/src/components/Preferences/Keybindings/index.js b/packages/bruno-app/src/components/Preferences/Keybindings/index.js index e3863fb8b..4e2153056 100644 --- a/packages/bruno-app/src/components/Preferences/Keybindings/index.js +++ b/packages/bruno-app/src/components/Preferences/Keybindings/index.js @@ -1,4 +1,4 @@ -import React, { useMemo, useRef, useState, useEffect } from 'react'; +import React, { useMemo, useRef, useState, useEffect, Fragment } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useTheme } from 'providers/Theme'; @@ -10,6 +10,7 @@ import { savePreferences } from 'providers/ReduxStore/slices/app'; import { KEY_BINDING_SECTIONS } from 'providers/Hotkeys/keyMappings.js'; import { Tooltip } from 'react-tooltip'; import ToggleSwitch from 'components/ToggleSwitch/index'; +import toast from 'react-hot-toast'; const SEP = '+bind+'; const getOS = () => (isMacOS() ? 'mac' : 'windows'); @@ -82,10 +83,10 @@ const renderDisplayValue = (displayValue, os) => { return ( {parsed.map((keysArr, index) => ( - + {index > 0 && - } {renderKeycaps(keysArr, os)} - + ))} ); @@ -218,23 +219,21 @@ const RESERVED_BY_OS = { comboSignature(['f12']) // Dashboard (older macOS) ]), windows: new Set([ + // System-level shortcuts (intercepted by Windows before reaching the app) comboSignature(['alt', 'tab']), + comboSignature(['alt', 'shift', 'tab']), comboSignature(['alt', 'f4']), - comboSignature(['f1']), // Windows Help + comboSignature(['alt', 'esc']), + comboSignature(['alt', 'space']), comboSignature(['ctrl', 'alt', 'delete']), - comboSignature(['command', 'l']), - comboSignature(['command', 'd']), - comboSignature(['command', 'e']), - comboSignature(['command', 'r']), - comboSignature(['command', 'i']), - comboSignature(['command', 's']), - comboSignature(['command', 'a']), - comboSignature(['command', 'x']), - comboSignature(['command', 'm']), - comboSignature(['command', 'tab']), comboSignature(['ctrl', 'shift', 'esc']), + // Function keys + comboSignature(['f1']), // Windows Help + comboSignature(['f11']), // Fullscreen toggle + comboSignature(['f12']), // DevTools // Undo/Redo - standard text editing shortcuts that browsers handle natively comboSignature(['ctrl', 'z']), + comboSignature(['ctrl', 'y']), comboSignature(['ctrl', 'shift', 'z']), // Toggle Developer Tools comboSignature(['ctrl', 'shift', 'i']) @@ -493,7 +492,7 @@ const Keybindings = () => { if (buildUsedSignatures(action).has(sig)) { return { code: ERROR.DUPLICATE, - message: 'That shortcut is already in use.' + message: 'This shortcut is already in use.' }; } @@ -562,9 +561,24 @@ const Keybindings = () => { return next; }); - persistToPreferences(action, def); + // Remove the entry from user preferences entirely so falls back to default. + // This also keeps `hasCustomizedKeybindings` accurate. + const nextKeyBindings = { ...(preferences?.keyBindings || {}) }; + delete nextKeyBindings[action]; + + const updatedPreferences = { + ...preferences, + keyBindings: nextKeyBindings + }; + + dispatch(savePreferences(updatedPreferences)); }; + const hasCustomizedKeybindings = useMemo(() => { + const userKeyBindings = preferences?.keyBindings || {}; + return Object.keys(userKeyBindings).length > 0; + }, [preferences?.keyBindings]); + const resetAllKeybindings = () => { const updatedPreferences = { ...preferences, @@ -572,6 +586,7 @@ const Keybindings = () => { }; dispatch(savePreferences(updatedPreferences)); + toast.success('All shortcuts have been reset to default'); }; const startEditing = (action) => { @@ -799,6 +814,7 @@ const Keybindings = () => { onClick={resetAllKeybindings} className="reset-btn" data-testid="reset-all-keybindings-btn" + disabled={!hasCustomizedKeybindings} > Reset Default @@ -817,7 +833,7 @@ const Keybindings = () => { {groupedKeyMappings.map((section, sectionIndex) => ( - + {section.heading} @@ -946,7 +962,12 @@ const Keybindings = () => { ); })} - + {sectionIndex < groupedKeyMappings.length - 1 && ( + +   + + )} + ))} diff --git a/packages/bruno-app/src/components/RequestPane/QueryEditor/index.js b/packages/bruno-app/src/components/RequestPane/QueryEditor/index.js index 1a936f5a1..ee16fa1fb 100644 --- a/packages/bruno-app/src/components/RequestPane/QueryEditor/index.js +++ b/packages/bruno-app/src/components/RequestPane/QueryEditor/index.js @@ -137,6 +137,12 @@ export default class QueryEditor extends React.Component { this.addOverlay(); setupLinkAware(editor); + + // Add mousetrap class so Mousetrap captures shortcuts even when CodeMirror is focused + const cmInput = editor.getInputField(); + if (cmInput) { + cmInput.classList.add('mousetrap'); + } } componentDidUpdate(prevProps) { diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js index 10a488875..9d7b5b170 100644 --- a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js @@ -225,7 +225,11 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi if (tab.type === 'environment-settings') { if (collection?.environmentsDraft) { const { environmentUid, variables } = collection.environmentsDraft; - dispatch(saveEnvironment(variables, environmentUid, collection.uid)); + if (environmentUid?.startsWith('dotenv:')) { + window.dispatchEvent(new Event('dotenv-save')); + } else { + dispatch(saveEnvironment(variables, environmentUid, collection.uid)); + } } } else if (tab.type === 'global-environment-settings') { if (globalEnvironmentDraft) { diff --git a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js index 4023d801d..f44ac7f1e 100644 --- a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js @@ -316,18 +316,6 @@ const NewRequest = ({ collectionUid, item, isEphemeral, onClose }) => {
{ - if (e.key === 'Enter' && !e.defaultPrevented) { - const isTextInput - = ['input', 'textarea'].includes(e.target.tagName.toLowerCase()) - || e.target.isContentEditable; - - if (!isTextInput) { - e.preventDefault(); - formik.handleSubmit(); - } - } - }} >