mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
feat: design revamp
This commit is contained in:
@@ -18,6 +18,28 @@ const StyledWrapper = styled.div`
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.CodeMirror-linenumber {
|
||||
text-align: left !important;
|
||||
padding-left: 3px !important;
|
||||
}
|
||||
|
||||
/* Override default lint highlight background when emphasizing the gutter */
|
||||
.CodeMirror-lint-line-error,
|
||||
.CodeMirror-lint-line-warning {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
/* Style line numbers when there's a lint issue */
|
||||
.CodeMirror-lint-line-error .CodeMirror-linenumber {
|
||||
color: #d32f2f !important;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-line-warning .CodeMirror-linenumber {
|
||||
color: #f57c00 !important;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Removes the glow outline around the folded json */
|
||||
.CodeMirror-foldmarker {
|
||||
text-shadow: none;
|
||||
@@ -73,41 +95,48 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-property,
|
||||
.cm-s-monokai span.cm-attribute {
|
||||
color: #9cdcfe !important;
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-string {
|
||||
color: #ce9178 !important;
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-number {
|
||||
color: #b5cea8 !important;
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-atom {
|
||||
color: #569cd6 !important;
|
||||
.cm-s-default, .cm-s-monokai {
|
||||
span.cm-def {
|
||||
color: ${(props) => props.theme.codemirror.tokens.definition} !important;
|
||||
}
|
||||
span.cm-property {
|
||||
color: ${(props) => props.theme.codemirror.tokens.property} !important;
|
||||
}
|
||||
span.cm-string {
|
||||
color: ${(props) => props.theme.codemirror.tokens.string} !important;
|
||||
}
|
||||
span.cm-number {
|
||||
color: ${(props) => props.theme.codemirror.tokens.number} !important;
|
||||
}
|
||||
span.cm-atom {
|
||||
color: ${(props) => props.theme.codemirror.tokens.atom} !important;
|
||||
}
|
||||
span.cm-variable {
|
||||
color: ${(props) => props.theme.codemirror.tokens.variable} !important;
|
||||
}
|
||||
span.cm-keyword {
|
||||
color: ${(props) => props.theme.codemirror.tokens.keyword} !important;
|
||||
}
|
||||
span.cm-comment {
|
||||
color: ${(props) => props.theme.codemirror.tokens.comment} !important;
|
||||
}
|
||||
span.cm-operator {
|
||||
color: ${(props) => props.theme.codemirror.tokens.operator} !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Variable validation colors */
|
||||
.cm-variable-valid {
|
||||
color: green;
|
||||
color: #5fad89 !important; /* Soft sage */
|
||||
}
|
||||
.cm-variable-invalid {
|
||||
color: red;
|
||||
color: #d17b7b !important; /* Soft coral */
|
||||
}
|
||||
|
||||
.CodeMirror-search-hint {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.cm-s-default span.cm-property {
|
||||
color: #1f61a0 !important;
|
||||
}
|
||||
|
||||
.cm-s-default span.cm-variable {
|
||||
color: #397d13 !important;
|
||||
}
|
||||
|
||||
|
||||
//matching bracket fix
|
||||
.CodeMirror-matchingbracket {
|
||||
@@ -126,6 +155,31 @@ const StyledWrapper = styled.div`
|
||||
.cm-search-current {
|
||||
background: rgba(255, 193, 7, 0.4);
|
||||
}
|
||||
|
||||
.lint-error-tooltip {
|
||||
position: fixed;
|
||||
z-index: 10000;
|
||||
background: ${(props) => props.theme.codemirror.bg};
|
||||
border-radius: ${(props) => props.theme.border.radius.base};
|
||||
padding: 8px 12px;
|
||||
max-width: 400px;
|
||||
box-shadow: ${(props) => props.theme.shadow.sm};
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
line-height: 1.5;
|
||||
pointer-events: none;
|
||||
|
||||
.lint-tooltip-message {
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.lint-tooltip-message.error {
|
||||
color: ${(props) => props.theme.colors.text.danger};
|
||||
}
|
||||
|
||||
.lint-tooltip-message.warning {
|
||||
color: ${(props) => props.theme.colors.text.warning};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -15,6 +15,7 @@ import { JSHINT } from 'jshint';
|
||||
import stripJsonComments from 'strip-json-comments';
|
||||
import { getAllVariables } from 'utils/collections';
|
||||
import { setupLinkAware } from 'utils/codemirror/linkAware';
|
||||
import { setupLintErrorTooltip } from 'utils/codemirror/lint-errors';
|
||||
import CodeMirrorSearch from 'components/CodeMirrorSearch';
|
||||
|
||||
const CodeMirror = require('codemirror');
|
||||
@@ -37,7 +38,8 @@ export default class CodeEditor extends React.Component {
|
||||
this.lintOptions = {
|
||||
esversion: 11,
|
||||
expr: true,
|
||||
asi: true
|
||||
asi: true,
|
||||
highlightLines: true
|
||||
};
|
||||
|
||||
this.state = {
|
||||
@@ -64,7 +66,7 @@ export default class CodeEditor extends React.Component {
|
||||
matchBrackets: true,
|
||||
showCursorWhenSelecting: true,
|
||||
foldGutter: true,
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
lint: this.lintOptions,
|
||||
readOnly: this.props.readOnly,
|
||||
scrollbarStyle: 'overlay',
|
||||
@@ -207,6 +209,9 @@ export default class CodeEditor extends React.Component {
|
||||
);
|
||||
|
||||
setupLinkAware(editor);
|
||||
|
||||
// Setup lint error tooltip on line number hover
|
||||
this.cleanupLintErrorTooltip = setupLintErrorTooltip(editor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,6 +277,10 @@ export default class CodeEditor extends React.Component {
|
||||
this.editor?._destroyLinkAware?.();
|
||||
this.editor.off('change', this._onEdit);
|
||||
this.editor.off('scroll', this.onScroll);
|
||||
|
||||
// Clean up lint error tooltip
|
||||
this.cleanupLintErrorTooltip?.();
|
||||
|
||||
this.editor = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ const Wrapper = styled.div`
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
color: ${(props) => props.theme.dropdown.color};
|
||||
background-color: ${(props) => props.theme.dropdown.bg};
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05);
|
||||
border-radius: 10px;
|
||||
box-shadow: ${(props) => props.theme.shadow.sm};
|
||||
border-radius: ${(props) => props.theme.border.radius.base};
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
max-width: unset !important;
|
||||
|
||||
@@ -2,17 +2,19 @@ import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
height: 2.3rem;
|
||||
border: ${(props) => props.theme.requestTabPanel.url.border};
|
||||
border-radius: ${(props) => props.theme.border.radius.base};
|
||||
|
||||
.method-selector-container {
|
||||
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-top-left-radius: ${(props) => props.theme.border.radius.base};
|
||||
border-bottom-left-radius: ${(props) => props.theme.border.radius.base};
|
||||
}
|
||||
|
||||
.input-container {
|
||||
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
border-top-right-radius: ${(props) => props.theme.border.radius.base};
|
||||
border-bottom-right-radius: ${(props) => props.theme.border.radius.base};
|
||||
|
||||
input {
|
||||
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
|
||||
|
||||
@@ -27,7 +27,7 @@ const Icon = forwardRef(function IconComponent(
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
className="font-medium px-2 w-full focus:bg-transparent"
|
||||
className="px-2 w-full focus:bg-transparent"
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
onBlur={handleBlur}
|
||||
@@ -46,7 +46,7 @@ const Icon = forwardRef(function IconComponent(
|
||||
className="cursor-pointer flex items-center text-left w-full"
|
||||
>
|
||||
<span
|
||||
className="font-medium px-2 truncate method-span"
|
||||
className="px-2 truncate method-span"
|
||||
id="create-new-request-method"
|
||||
title={inputValue}
|
||||
>
|
||||
|
||||
@@ -2,17 +2,19 @@ import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
height: 2.3rem;
|
||||
border: ${(props) => props.theme.requestTabPanel.url.border};
|
||||
border-radius: ${(props) => props.theme.border.radius.base};
|
||||
|
||||
div.method-selector-container {
|
||||
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-top-left-radius: ${(props) => props.theme.border.radius.base};
|
||||
border-bottom-left-radius: ${(props) => props.theme.border.radius.base};
|
||||
}
|
||||
|
||||
div.input-container {
|
||||
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
border-top-right-radius: ${(props) => props.theme.border.radius.base};
|
||||
border-bottom-right-radius: ${(props) => props.theme.border.radius.base};
|
||||
|
||||
input {
|
||||
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
|
||||
|
||||
@@ -3,11 +3,12 @@ import styled from 'styled-components';
|
||||
const StyledWrapper = styled.div`
|
||||
height: 2.3rem;
|
||||
position: relative;
|
||||
border: ${(props) => props.theme.requestTabPanel.url.border};
|
||||
border-radius: ${(props) => props.theme.border.radius.base};
|
||||
|
||||
.input-container {
|
||||
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
border-radius: ${(props) => props.theme.border.radius.base};
|
||||
|
||||
input {
|
||||
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
|
||||
|
||||
@@ -53,10 +53,16 @@ const Wrapper = styled.div`
|
||||
right: -3px;
|
||||
transition: opacity 0.2s ease;
|
||||
|
||||
&:hover div.drag-request-border {
|
||||
width: 2px;
|
||||
div.drag-request-border {
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
border-left: solid 1px ${(props) => props.theme.sidebar.dragbar};
|
||||
border-left: solid 1px ${(props) => props.theme.sidebar.dragbar.border};
|
||||
}
|
||||
|
||||
&:hover div.drag-request-border {
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
border-left: solid 1px ${(props) => props.theme.sidebar.dragbar.activeBorder};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,8 +1,31 @@
|
||||
const colors = {
|
||||
BRAND: '#546de5',
|
||||
TEXT: '#d4d4d4',
|
||||
TEXT_LINK: '#569cd6',
|
||||
BACKGROUND: '#1e1e1e',
|
||||
|
||||
GRAY_1: '#666666',
|
||||
GRAY_2: '#444444',
|
||||
GRAY_3: '#252526',
|
||||
|
||||
CODEMIRROR_TOKENS: {
|
||||
DEFINITION: '#9ccc9c', // Softer, brighter sage — better contrast
|
||||
PROPERTY: '#7dcfff', // Soft sky blue, high clarity without being loud
|
||||
STRING: '#d7ba7d', // VSCode-like warm string tone
|
||||
NUMBER: '#4ec9b0', // Standard teal with higher clarity
|
||||
ATOM: '#c586c0', // Brighter lavender, matches VSCode purple
|
||||
VARIABLE: '#4fc1ff', // Clear aqua-blue (used widely in dark themes)
|
||||
KEYWORD: '#c58679', // Coral-ish but muted to avoid eye strain
|
||||
COMMENT: '#6a9955', // Greenish-slate — very readable & subtle
|
||||
OPERATOR: '#d4d4d4' // Light gray — consistent with dark mode operators
|
||||
}
|
||||
};
|
||||
|
||||
const darkTheme = {
|
||||
brand: '#546de5',
|
||||
text: '#d4d4d4',
|
||||
textLink: '#569cd6',
|
||||
bg: '#1e1e1e',
|
||||
brand: colors.BRAND,
|
||||
text: colors.TEXT,
|
||||
textLink: colors.TEXT_LINK,
|
||||
bg: colors.BACKGROUND,
|
||||
|
||||
font: {
|
||||
size: {
|
||||
@@ -15,13 +38,29 @@ const darkTheme = {
|
||||
}
|
||||
},
|
||||
|
||||
shadow: {
|
||||
sm: '0 1px 3px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(0, 0, 0, 0.3)',
|
||||
md: '0 2px 8px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(0, 0, 0, 0.4)',
|
||||
lg: '0 2px 12px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(0, 0, 0, 0.4)'
|
||||
},
|
||||
|
||||
border: {
|
||||
radius: {
|
||||
sm: '4px',
|
||||
base: '6px',
|
||||
md: '8px',
|
||||
lg: '10px',
|
||||
xl: '12px'
|
||||
}
|
||||
},
|
||||
|
||||
colors: {
|
||||
text: {
|
||||
green: 'rgb(11 178 126)',
|
||||
danger: '#f06f57',
|
||||
muted: '#9d9d9d',
|
||||
purple: '#cd56d6',
|
||||
yellow: '#f59e0b'
|
||||
yellow: '#d9a342'
|
||||
},
|
||||
bg: {
|
||||
danger: '#d03544'
|
||||
@@ -57,8 +96,11 @@ const darkTheme = {
|
||||
sidebar: {
|
||||
color: '#ccc',
|
||||
muted: '#9d9d9d',
|
||||
bg: '#252526',
|
||||
dragbar: '#666666',
|
||||
bg: colors.GRAY_3,
|
||||
dragbar: {
|
||||
border: 'transparent',
|
||||
activeBorder: colors.GRAY_1
|
||||
},
|
||||
|
||||
badge: {
|
||||
bg: '#3D3D3D'
|
||||
@@ -98,8 +140,8 @@ const darkTheme = {
|
||||
shadow: 'rgb(0 0 0 / 36%) 0px 2px 8px',
|
||||
separator: '#444',
|
||||
labelBg: '#4a4949',
|
||||
selectedBg: '#F59E0B14',
|
||||
selectedColor: '#F59E0B',
|
||||
selectedBg: '#d9a34214',
|
||||
selectedColor: '#d9a342',
|
||||
mutedText: '#9B9B9B',
|
||||
primaryText: '#D4D4D4',
|
||||
secondaryText: '#9CA3AF',
|
||||
@@ -118,16 +160,17 @@ const darkTheme = {
|
||||
head: '#d69956'
|
||||
},
|
||||
grpc: '#6366f1',
|
||||
ws: '#f59e0b',
|
||||
ws: '#d9a342',
|
||||
gql: '#e535ab'
|
||||
},
|
||||
|
||||
requestTabPanel: {
|
||||
url: {
|
||||
bg: '#3D3D3D',
|
||||
bg: colors.BACKGROUND,
|
||||
icon: 'rgb(204, 204, 204)',
|
||||
iconDanger: '#fa5343',
|
||||
errorHoverBg: '#4a2a2a'
|
||||
errorHoverBg: '#4a2a2a',
|
||||
border: `solid 1px ${colors.GRAY_2}`
|
||||
},
|
||||
dragbar: {
|
||||
border: '#444',
|
||||
@@ -252,7 +295,7 @@ const darkTheme = {
|
||||
tabs: {
|
||||
active: {
|
||||
color: '#CCCCCC',
|
||||
border: '#F59E0B'
|
||||
border: '#d9a342'
|
||||
},
|
||||
secondary: {
|
||||
active: {
|
||||
@@ -287,14 +330,14 @@ const darkTheme = {
|
||||
},
|
||||
|
||||
codemirror: {
|
||||
bg: '#1e1e1e',
|
||||
border: '#373737',
|
||||
bg: colors.BACKGROUND,
|
||||
border: colors.BACKGROUND,
|
||||
placeholder: {
|
||||
color: '#a2a2a2',
|
||||
opacity: 0.5
|
||||
},
|
||||
gutter: {
|
||||
bg: '#262626'
|
||||
bg: colors.BACKGROUND
|
||||
},
|
||||
variable: {
|
||||
valid: 'rgb(11 178 126)',
|
||||
@@ -313,6 +356,17 @@ const darkTheme = {
|
||||
editorBorder: '#3D3D3D'
|
||||
}
|
||||
},
|
||||
tokens: {
|
||||
definition: colors.CODEMIRROR_TOKENS.DEFINITION,
|
||||
property: colors.CODEMIRROR_TOKENS.PROPERTY,
|
||||
string: colors.CODEMIRROR_TOKENS.STRING,
|
||||
number: colors.CODEMIRROR_TOKENS.NUMBER,
|
||||
atom: colors.CODEMIRROR_TOKENS.ATOM,
|
||||
variable: colors.CODEMIRROR_TOKENS.VARIABLE,
|
||||
keyword: colors.CODEMIRROR_TOKENS.KEYWORD,
|
||||
comment: colors.CODEMIRROR_TOKENS.COMMENT,
|
||||
operator: colors.CODEMIRROR_TOKENS.OPERATOR
|
||||
},
|
||||
searchLineHighlightCurrent: 'rgba(120,120,120,0.18)',
|
||||
searchMatch: '#FFD700',
|
||||
searchMatchActive: '#FFFF00'
|
||||
@@ -346,7 +400,7 @@ const darkTheme = {
|
||||
tooltip: {
|
||||
bg: '#1f1f1f',
|
||||
color: '#ffffff',
|
||||
shortcutColor: '#f59e0b'
|
||||
shortcutColor: '#d9a342'
|
||||
},
|
||||
|
||||
infoTip: {
|
||||
@@ -463,7 +517,7 @@ const darkTheme = {
|
||||
hoverBg: 'rgba(255, 255, 255, 0.05)',
|
||||
selected: {
|
||||
bg: 'rgba(245, 158, 11, 0.2)',
|
||||
border: '#f59e0b'
|
||||
border: '#d9a342'
|
||||
},
|
||||
text: '#d4d4d4',
|
||||
secondaryText: '#9d9d9d',
|
||||
@@ -492,8 +546,8 @@ const darkTheme = {
|
||||
},
|
||||
|
||||
examples: {
|
||||
buttonBg: '#F59E0B1A',
|
||||
buttonColor: '#F59E0B',
|
||||
buttonBg: '#d9a3421A',
|
||||
buttonColor: '#d9a342',
|
||||
buttonText: '#fff',
|
||||
buttonIconColor: '#fff',
|
||||
border: '#444',
|
||||
|
||||
@@ -1,8 +1,33 @@
|
||||
const colors = {
|
||||
BRAND: '#546de5',
|
||||
TEXT: 'rgb(52, 52, 52)',
|
||||
TEXT_LINK: '#1663bb',
|
||||
BACKGROUND: '#fff',
|
||||
WHITE: '#fff',
|
||||
BLACK: '#000',
|
||||
GRAY_1: '#f8f8f8',
|
||||
GRAY_2: '#eaeaea',
|
||||
GRAY_3: '#e5e5e5',
|
||||
GRAY_4: '#cbcbcb',
|
||||
|
||||
CODEMIRROR_TOKENS: {
|
||||
DEFINITION: '#566f4e', // Deep moss
|
||||
PROPERTY: '#4b7bbb', // Muted azure
|
||||
STRING: '#a06e3b', // Warm bronze
|
||||
NUMBER: '#3d8b7c', // Muted jade
|
||||
ATOM: '#8169ad', // Soft plum
|
||||
VARIABLE: '#3f7b6f', // Deep teal
|
||||
KEYWORD: '#b95d6a', // Muted ruby
|
||||
COMMENT: '#8997aa', // Cool gray
|
||||
OPERATOR: '#6b7a8f' // Slate blue
|
||||
}
|
||||
};
|
||||
|
||||
const lightTheme = {
|
||||
brand: '#546de5',
|
||||
text: 'rgb(52, 52, 52)',
|
||||
textLink: '#1663bb',
|
||||
bg: '#fff',
|
||||
brand: colors.BRAND,
|
||||
text: colors.TEXT,
|
||||
textLink: colors.TEXT_LINK,
|
||||
bg: colors.BACKGROUND,
|
||||
|
||||
font: {
|
||||
size: {
|
||||
@@ -15,10 +40,27 @@ const lightTheme = {
|
||||
}
|
||||
},
|
||||
|
||||
shadow: {
|
||||
sm: '0 1px 3px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(0, 0, 0, 0.05)',
|
||||
md: '0 2px 8px rgba(0, 0, 0, 0.14), 0 0 0 1px rgba(0, 0, 0, 0.06)',
|
||||
lg: '0 2px 12px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05)'
|
||||
},
|
||||
|
||||
border: {
|
||||
radius: {
|
||||
sm: '4px',
|
||||
base: '6px',
|
||||
md: '8px',
|
||||
lg: '10px',
|
||||
xl: '12px'
|
||||
}
|
||||
},
|
||||
|
||||
colors: {
|
||||
text: {
|
||||
green: '#047857',
|
||||
danger: '#B91C1C',
|
||||
warning: '#f57c00',
|
||||
muted: '#838383',
|
||||
purple: '#8e44ad',
|
||||
yellow: '#d97706'
|
||||
@@ -57,11 +99,14 @@ const lightTheme = {
|
||||
sidebar: {
|
||||
color: 'rgb(52, 52, 52)',
|
||||
muted: '#4b5563',
|
||||
bg: '#F3F3F3',
|
||||
dragbar: 'rgb(200, 200, 200)',
|
||||
bg: colors.GRAY_1,
|
||||
dragbar: {
|
||||
border: colors.GRAY_3,
|
||||
activeBorder: colors.GRAY_4
|
||||
},
|
||||
|
||||
badge: {
|
||||
bg: '#e1e1e1'
|
||||
bg: '#eaeaea'
|
||||
},
|
||||
|
||||
search: {
|
||||
@@ -71,11 +116,11 @@ const lightTheme = {
|
||||
|
||||
collection: {
|
||||
item: {
|
||||
bg: '#e1e1e1',
|
||||
hoverBg: '#e7e7e7',
|
||||
indentBorder: 'solid 1px #e1e1e1',
|
||||
bg: colors.GRAY_2,
|
||||
hoverBg: colors.GRAY_2,
|
||||
indentBorder: `solid 1px ${colors.GRAY_3}`,
|
||||
active: {
|
||||
indentBorder: 'solid 1px #d0d0d0'
|
||||
indentBorder: `solid 1px ${colors.GRAY_3}`
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -124,10 +169,11 @@ const lightTheme = {
|
||||
|
||||
requestTabPanel: {
|
||||
url: {
|
||||
bg: '#f3f3f3',
|
||||
bg: colors.WHITE,
|
||||
icon: '#515151',
|
||||
iconDanger: '#d91f11',
|
||||
errorHoverBg: '#fef2f2'
|
||||
errorHoverBg: '#fef2f2',
|
||||
border: `solid 1px ${colors.GRAY_3}`
|
||||
},
|
||||
dragbar: {
|
||||
border: '#efefef',
|
||||
@@ -288,14 +334,14 @@ const lightTheme = {
|
||||
},
|
||||
|
||||
codemirror: {
|
||||
bg: 'white',
|
||||
border: '#efefef',
|
||||
bg: colors.WHITE,
|
||||
border: colors.WHITE,
|
||||
placeholder: {
|
||||
color: '#a2a2a2',
|
||||
opacity: 0.75
|
||||
},
|
||||
gutter: {
|
||||
bg: '#f3f3f3'
|
||||
bg: colors.WHITE
|
||||
},
|
||||
variable: {
|
||||
valid: '#047857',
|
||||
@@ -314,6 +360,17 @@ const lightTheme = {
|
||||
editorBorder: '#EFEFEF'
|
||||
}
|
||||
},
|
||||
tokens: {
|
||||
definition: colors.CODEMIRROR_TOKENS.DEFINITION,
|
||||
property: colors.CODEMIRROR_TOKENS.PROPERTY,
|
||||
string: colors.CODEMIRROR_TOKENS.STRING,
|
||||
number: colors.CODEMIRROR_TOKENS.NUMBER,
|
||||
atom: colors.CODEMIRROR_TOKENS.ATOM,
|
||||
variable: colors.CODEMIRROR_TOKENS.VARIABLE,
|
||||
keyword: colors.CODEMIRROR_TOKENS.KEYWORD,
|
||||
comment: colors.CODEMIRROR_TOKENS.COMMENT,
|
||||
operator: colors.CODEMIRROR_TOKENS.OPERATOR
|
||||
},
|
||||
searchLineHighlightCurrent: 'rgba(120,120,120,0.10)',
|
||||
searchMatch: '#B8860B',
|
||||
searchMatchActive: '#DAA520'
|
||||
|
||||
141
packages/bruno-app/src/utils/codemirror/lint-errors.js
Normal file
141
packages/bruno-app/src/utils/codemirror/lint-errors.js
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Lint Error Tooltip for CodeMirror
|
||||
* Shows lint errors in a popover when hovering over line numbers
|
||||
*/
|
||||
|
||||
let activeTooltip = null;
|
||||
|
||||
/**
|
||||
* Get lint errors for a specific line from the editor's lint state
|
||||
* @param {CodeMirror} editor - The CodeMirror editor instance
|
||||
* @param {number} lineNumber - The 0-indexed line number
|
||||
* @returns {Array} Array of lint error annotations
|
||||
*/
|
||||
function getLintErrorsForLine(editor, lineNumber) {
|
||||
if (!editor) return [];
|
||||
|
||||
const errors = [];
|
||||
const lintState = editor.state.lint;
|
||||
|
||||
if (lintState && lintState.marked) {
|
||||
lintState.marked.forEach((mark) => {
|
||||
if (mark.__annotation) {
|
||||
// Use annotation's from position directly (mark.find() can return null if lines array is empty)
|
||||
const annotationLine = mark.__annotation.from?.line;
|
||||
|
||||
if (annotationLine === lineNumber) {
|
||||
// Avoid duplicate messages
|
||||
if (!errors.find((e) => e.message === mark.__annotation.message)) {
|
||||
errors.push(mark.__annotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the lint error tooltip next to the target element
|
||||
* @param {Array} errors - Array of lint error annotations
|
||||
* @param {HTMLElement} targetElement - The element to position the tooltip near
|
||||
* @param {HTMLElement} container - The container to append the tooltip to
|
||||
*/
|
||||
function showLintTooltip(errors, targetElement, container) {
|
||||
hideLintTooltip();
|
||||
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.className = 'lint-error-tooltip';
|
||||
|
||||
errors.forEach((error, index) => {
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = `lint-tooltip-message ${error.severity || 'error'}`;
|
||||
errorDiv.textContent = error.message;
|
||||
tooltip.appendChild(errorDiv);
|
||||
});
|
||||
|
||||
container.appendChild(tooltip);
|
||||
activeTooltip = tooltip;
|
||||
|
||||
// Position the tooltip
|
||||
const rect = targetElement.getBoundingClientRect();
|
||||
tooltip.style.left = `${rect.right + 8}px`;
|
||||
tooltip.style.top = `${rect.top + (rect.height / 2)}px`;
|
||||
tooltip.style.transform = 'translateY(-50%)';
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide and remove the active lint error tooltip
|
||||
*/
|
||||
function hideLintTooltip() {
|
||||
if (activeTooltip) {
|
||||
activeTooltip.remove();
|
||||
activeTooltip = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup lint error tooltip functionality for a CodeMirror editor
|
||||
* Shows lint errors when hovering over line numbers
|
||||
*
|
||||
* @param {CodeMirror} editor - The CodeMirror editor instance
|
||||
* @returns {Function} Cleanup function to remove event listeners
|
||||
*/
|
||||
export function setupLintErrorTooltip(editor) {
|
||||
const wrapper = editor.getWrapperElement();
|
||||
// Get the StyledWrapper container (parent of CodeMirror wrapper)
|
||||
const container = wrapper.closest('.graphiql-container') || wrapper.parentElement;
|
||||
|
||||
const handleMouseOver = (e) => {
|
||||
const target = e.target;
|
||||
|
||||
// Check if hovering over a line number element
|
||||
if (target.classList.contains('CodeMirror-linenumber')) {
|
||||
const lineNumber = parseInt(target.textContent, 10) - 1; // 0-indexed
|
||||
|
||||
if (isNaN(lineNumber) || lineNumber < 0) {
|
||||
hideLintTooltip();
|
||||
return;
|
||||
}
|
||||
|
||||
const lintErrors = getLintErrorsForLine(editor, lineNumber);
|
||||
|
||||
if (lintErrors.length > 0) {
|
||||
showLintTooltip(lintErrors, target, container);
|
||||
} else {
|
||||
hideLintTooltip();
|
||||
}
|
||||
} else if (!target.closest('.lint-error-tooltip')) {
|
||||
hideLintTooltip();
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseOut = (e) => {
|
||||
const relatedTarget = e.relatedTarget;
|
||||
// Don't hide if moving to another line number or the tooltip
|
||||
if (relatedTarget
|
||||
&& (relatedTarget.classList?.contains('CodeMirror-linenumber')
|
||||
|| relatedTarget.closest?.('.lint-error-tooltip'))) {
|
||||
return;
|
||||
}
|
||||
hideLintTooltip();
|
||||
};
|
||||
|
||||
const handleScroll = () => {
|
||||
hideLintTooltip();
|
||||
};
|
||||
|
||||
// Add event listeners
|
||||
wrapper.addEventListener('mouseover', handleMouseOver);
|
||||
wrapper.addEventListener('mouseout', handleMouseOut);
|
||||
editor.on('scroll', handleScroll);
|
||||
|
||||
// Return cleanup function
|
||||
return () => {
|
||||
wrapper.removeEventListener('mouseover', handleMouseOver);
|
||||
wrapper.removeEventListener('mouseout', handleMouseOut);
|
||||
editor.off('scroll', handleScroll);
|
||||
hideLintTooltip();
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user