mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-16 04:11:29 +00:00
use themes within grpc (#6568)
* rm: redundant code * add: theme to grpc * keep same color for grpc as that of sidebar * fix: checkbox color * fix: use dropdown colors * rm: redundant lines * use: theme for grpc icons
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
// UNARY - Single request, single response (Blue)
|
||||
export const IconGrpcUnary = ({ size = 18, strokeWidth = 1.5, className = '' }) => (
|
||||
export const IconGrpcUnary = ({ size = 18, strokeWidth = 1.5, className = '', color = '#3B82F6' }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
@@ -14,16 +14,16 @@ export const IconGrpcUnary = ({ size = 18, strokeWidth = 1.5, className = '' })
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
{/* Request arrow (top) - right */}
|
||||
<path d="M3 8h18" stroke="#3B82F6" strokeWidth={strokeWidth} />
|
||||
<path d="M18 5l3 3l-3 3" stroke="#3B82F6" strokeWidth={strokeWidth} />
|
||||
<path d="M3 8h18" stroke={color} strokeWidth={strokeWidth} />
|
||||
<path d="M18 5l3 3l-3 3" stroke={color} strokeWidth={strokeWidth} />
|
||||
{/* Response arrow (bottom) - left */}
|
||||
<path d="M21 16h-18" stroke="#3B82F6" strokeWidth={strokeWidth} />
|
||||
<path d="M6 13l-3 3l3 3" stroke="#3B82F6" strokeWidth={strokeWidth} />
|
||||
<path d="M21 16h-18" stroke={color} strokeWidth={strokeWidth} />
|
||||
<path d="M6 13l-3 3l3 3" stroke={color} strokeWidth={strokeWidth} />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// CLIENT_STREAMING - Streaming request, single response (Purple)
|
||||
export const IconGrpcClientStreaming = ({ size = 18, strokeWidth = 1.5, className = '' }) => (
|
||||
export const IconGrpcClientStreaming = ({ size = 18, strokeWidth = 1.5, className = '', color = '#8B5CF6' }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
@@ -36,17 +36,17 @@ export const IconGrpcClientStreaming = ({ size = 18, strokeWidth = 1.5, classNam
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
{/* Request arrow (top) - right with double heads */}
|
||||
<path d="M3 8h18" stroke="#8B5CF6" strokeWidth={strokeWidth} />
|
||||
<path d="M18 5l3 3l-3 3" stroke="#8B5CF6" strokeWidth={strokeWidth} />
|
||||
<path d="M14 5l3 3l-3 3" stroke="#8B5CF6" strokeWidth={strokeWidth} />
|
||||
<path d="M3 8h18" stroke={color} strokeWidth={strokeWidth} />
|
||||
<path d="M18 5l3 3l-3 3" stroke={color} strokeWidth={strokeWidth} />
|
||||
<path d="M14 5l3 3l-3 3" stroke={color} strokeWidth={strokeWidth} />
|
||||
{/* Response arrow (bottom) - left */}
|
||||
<path d="M21 16h-18" stroke="#8B5CF6" strokeWidth={strokeWidth} />
|
||||
<path d="M6 13l-3 3l3 3" stroke="#8B5CF6" strokeWidth={strokeWidth} />
|
||||
<path d="M21 16h-18" stroke={color} strokeWidth={strokeWidth} />
|
||||
<path d="M6 13l-3 3l3 3" stroke={color} strokeWidth={strokeWidth} />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// SERVER_STREAMING - Single request, streaming response (Green)
|
||||
export const IconGrpcServerStreaming = ({ size = 18, strokeWidth = 1.5, className = '' }) => (
|
||||
export const IconGrpcServerStreaming = ({ size = 18, strokeWidth = 1.5, className = '', color = '#10B981' }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
@@ -59,17 +59,17 @@ export const IconGrpcServerStreaming = ({ size = 18, strokeWidth = 1.5, classNam
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
{/* Request arrow (top) - right */}
|
||||
<path d="M3 8h18" stroke="#10B981" strokeWidth={strokeWidth} />
|
||||
<path d="M18 5l3 3l-3 3" stroke="#10B981" strokeWidth={strokeWidth} />
|
||||
<path d="M3 8h18" stroke={color} strokeWidth={strokeWidth} />
|
||||
<path d="M18 5l3 3l-3 3" stroke={color} strokeWidth={strokeWidth} />
|
||||
{/* Response arrow (bottom) - left with double heads */}
|
||||
<path d="M21 16h-18" stroke="#10B981" strokeWidth={strokeWidth} />
|
||||
<path d="M6 13l-3 3l3 3" stroke="#10B981" strokeWidth={strokeWidth} />
|
||||
<path d="M10 13l-3 3l3 3" stroke="#10B981" strokeWidth={strokeWidth} />
|
||||
<path d="M21 16h-18" stroke={color} strokeWidth={strokeWidth} />
|
||||
<path d="M6 13l-3 3l3 3" stroke={color} strokeWidth={strokeWidth} />
|
||||
<path d="M10 13l-3 3l3 3" stroke={color} strokeWidth={strokeWidth} />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// BIDI_STREAMING - Streaming request, streaming response (Orange)
|
||||
export const IconGrpcBidiStreaming = ({ size = 18, strokeWidth = 1.5, className = '' }) => (
|
||||
export const IconGrpcBidiStreaming = ({ size = 18, strokeWidth = 1.5, className = '', color = '#F97316' }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
@@ -82,12 +82,12 @@ export const IconGrpcBidiStreaming = ({ size = 18, strokeWidth = 1.5, className
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
{/* Request arrow (top) - right with double heads */}
|
||||
<path d="M3 8h18" stroke="#F97316" strokeWidth={strokeWidth} />
|
||||
<path d="M18 5l3 3l-3 3" stroke="#F97316" strokeWidth={strokeWidth} />
|
||||
<path d="M14 5l3 3l-3 3" stroke="#F97316" strokeWidth={strokeWidth} />
|
||||
<path d="M3 8h18" stroke={color} strokeWidth={strokeWidth} />
|
||||
<path d="M18 5l3 3l-3 3" stroke={color} strokeWidth={strokeWidth} />
|
||||
<path d="M14 5l3 3l-3 3" stroke={color} strokeWidth={strokeWidth} />
|
||||
{/* Response arrow (bottom) - left with double heads */}
|
||||
<path d="M21 16h-18" stroke="#F97316" strokeWidth={strokeWidth} />
|
||||
<path d="M6 13l-3 3l3 3" stroke="#F97316" strokeWidth={strokeWidth} />
|
||||
<path d="M10 13l-3 3l3 3" stroke="#F97316" strokeWidth={strokeWidth} />
|
||||
<path d="M21 16h-18" stroke={color} strokeWidth={strokeWidth} />
|
||||
<path d="M6 13l-3 3l3 3" stroke={color} strokeWidth={strokeWidth} />
|
||||
<path d="M10 13l-3 3l3 3" stroke={color} strokeWidth={strokeWidth} />
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
import styled from 'styled-components';
|
||||
import { rgba } from 'polished';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
.method-dropdown-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.method-dropdown-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 0.5rem;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.method-dropdown-trigger-icon {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.method-dropdown-trigger-text {
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
white-space: nowrap;
|
||||
color: ${(props) => props.theme.dropdown.color};
|
||||
}
|
||||
|
||||
.method-dropdown-caret {
|
||||
margin-left: 0.25rem;
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
fill: ${(props) => props.theme.colors.text.muted};
|
||||
}
|
||||
|
||||
.method-dropdown-list {
|
||||
max-height: 24rem;
|
||||
overflow-y: auto;
|
||||
width: 24rem;
|
||||
min-width: 15rem;
|
||||
}
|
||||
|
||||
.method-dropdown-service-group {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.method-dropdown-service-header {
|
||||
padding: 0.25rem 0.75rem;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
background-color: ${(props) => props.theme.dropdown.separator};
|
||||
color: ${(props) => props.theme.dropdown.color};
|
||||
}
|
||||
|
||||
.method-dropdown-method-item {
|
||||
padding: 0.5rem 0.75rem;
|
||||
width: 100%;
|
||||
border-left-width: 2px;
|
||||
border-left-style: solid;
|
||||
border-left-color: transparent;
|
||||
transition: all 200ms;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props) => props.theme.dropdown.hoverBg};
|
||||
}
|
||||
|
||||
&--selected {
|
||||
border-left-color: ${(props) => props.theme.dropdown.selectedColor};
|
||||
background-color: ${(props) => rgba(props.theme.dropdown.selectedColor, 0.2)};
|
||||
}
|
||||
|
||||
&--focused {
|
||||
background-color: ${(props) => props.theme.dropdown.hoverBg};
|
||||
}
|
||||
}
|
||||
|
||||
.method-dropdown-method-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.method-dropdown-method-icon {
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
margin-right: 0.75rem;
|
||||
color: ${(props) => props.theme.dropdown.iconColor};
|
||||
}
|
||||
|
||||
.method-dropdown-method-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.method-dropdown-method-name {
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.dropdown.color};
|
||||
}
|
||||
|
||||
.method-dropdown-method-type {
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
color: ${(props) => props.theme.dropdown.mutedText};
|
||||
}
|
||||
|
||||
.method-dropdown-empty-state {
|
||||
padding: 0.5rem 0.75rem;
|
||||
width: 100%;
|
||||
transition: all 200ms;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.method-dropdown-empty-state-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
margin-right: 0.75rem;
|
||||
color: ${(props) => props.theme.dropdown.mutedText};
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
import SearchInput from 'components/SearchInput/index';
|
||||
import { search } from 'fast-fuzzy';
|
||||
import React, { forwardRef, useEffect, useRef, useState } from 'react';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const MethodDropdown = ({
|
||||
grpcMethods,
|
||||
@@ -16,6 +18,7 @@ const MethodDropdown = ({
|
||||
onMethodSelect,
|
||||
onMethodDropdownCreate
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [focusedIndex, setFocusedIndex] = useState(-1);
|
||||
const searchInputRef = useRef();
|
||||
@@ -58,32 +61,26 @@ const MethodDropdown = ({
|
||||
const getIconForMethodType = (type) => {
|
||||
switch (type) {
|
||||
case 'unary':
|
||||
return <IconGrpcUnary size={20} strokeWidth={2} />;
|
||||
return <IconGrpcUnary size={20} strokeWidth={2} color={theme.request.methods.get} />;
|
||||
case 'client-streaming':
|
||||
return <IconGrpcClientStreaming size={20} strokeWidth={2} />;
|
||||
return <IconGrpcClientStreaming size={20} strokeWidth={2} color={theme.request.methods.post} />;
|
||||
case 'server-streaming':
|
||||
return <IconGrpcServerStreaming size={20} strokeWidth={2} />;
|
||||
return <IconGrpcServerStreaming size={20} strokeWidth={2} color={theme.request.methods.put} />;
|
||||
case 'bidi-streaming':
|
||||
return <IconGrpcBidiStreaming size={20} strokeWidth={2} />;
|
||||
return <IconGrpcBidiStreaming size={20} strokeWidth={2} color={theme.colors.text.purple} />;
|
||||
default:
|
||||
return <IconGrpcUnary size={20} strokeWidth={2} />;
|
||||
return <IconGrpcUnary size={20} strokeWidth={2} color={theme.request.methods.get} />;
|
||||
}
|
||||
};
|
||||
|
||||
const MethodsDropdownIcon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="flex items-center justify-center ml-2 cursor-pointer select-none" data-testid="grpc-method-dropdown-trigger">
|
||||
{selectedGrpcMethod && <div className="mr-2">{getIconForMethodType(selectedGrpcMethod.type)}</div>}
|
||||
<span className="text-xs">
|
||||
{selectedGrpcMethod ? (
|
||||
<span className="dark:text-neutral-300 text-neutral-700 text-nowrap" data-testid="selected-grpc-method-name">
|
||||
{selectedGrpcMethod.path.split('.').at(-1) || selectedGrpcMethod.path}
|
||||
</span>
|
||||
) : (
|
||||
<span className="dark:text-neutral-300 text-neutral-700 text-nowrap">Select Method </span>
|
||||
)}
|
||||
<div ref={ref} className="method-dropdown-trigger" data-testid="grpc-method-dropdown-trigger">
|
||||
{selectedGrpcMethod && <div className="method-dropdown-trigger-icon">{getIconForMethodType(selectedGrpcMethod.type)}</div>}
|
||||
<span className="method-dropdown-trigger-text" data-testid="selected-grpc-method-name">
|
||||
{selectedGrpcMethod ? (selectedGrpcMethod.path.split('.').at(-1) || selectedGrpcMethod.path) : 'Select Method'}
|
||||
</span>
|
||||
<IconChevronDown className="caret ml-1" size={14} strokeWidth={2} />
|
||||
<IconChevronDown className="method-dropdown-caret" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -145,76 +142,75 @@ const MethodDropdown = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center h-full mr-2" data-testid="grpc-methods-dropdown">
|
||||
<Dropdown onCreate={onMethodDropdownCreate} icon={<MethodsDropdownIcon />} placement="bottom-end" style={{ maxWidth: 'unset' }} onShow={handleDropdownShow}>
|
||||
<SearchInput
|
||||
searchText={searchText}
|
||||
setSearchText={setSearchText}
|
||||
placeholder="Search"
|
||||
ref={searchInputRef}
|
||||
onKeyDown={handleKeyDown}
|
||||
onBlur={focusSearchInput}
|
||||
onChange={handleSearchChange}
|
||||
className="mt-2 mb-3 "
|
||||
data-testid="grpc-methods-search-input"
|
||||
/>
|
||||
<div ref={listRef} className="max-h-96 overflow-y-auto w-96 min-w-60" data-testid="grpc-methods-list">
|
||||
{Object.entries(groupedMethods).map(([serviceName, methods], serviceIndex) => (
|
||||
<div key={serviceIndex} className="service-group mb-2" onKeyDown={handleKeyDown} tabIndex={0}>
|
||||
<div className="service-header px-3 py-1 bg-neutral-100 dark:bg-neutral-800 font-medium truncate sticky top-0 z-10">
|
||||
{serviceName || 'Default Service'}
|
||||
</div>
|
||||
<div className="service-methods">
|
||||
{methods.map((method, methodIndex) => {
|
||||
const globalMethodIndex
|
||||
= Object.values(groupedMethods)
|
||||
.slice(0, serviceIndex)
|
||||
.reduce((acc, group) => acc + group.length, 0) + methodIndex;
|
||||
return (
|
||||
<div
|
||||
key={`${serviceIndex}-${methodIndex}`}
|
||||
className={`py-2 px-3 w-full border-l-2 transition-all duration-200 relative group ${
|
||||
selectedGrpcMethod && selectedGrpcMethod.path === method.path
|
||||
? 'border-yellow-500 bg-yellow-500/20 dark:bg-yellow-900/20'
|
||||
: 'border-transparent hover:bg-black/5 dark:hover:bg-white/5'
|
||||
} ${focusedIndex === globalMethodIndex
|
||||
? 'bg-black/5 dark:bg-white/5' : ''}`}
|
||||
onClick={() => handleGrpcMethodSelect(method)}
|
||||
data-index={globalMethodIndex}
|
||||
data-testid="grpc-method-item"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="text-xs mr-3 text-gray-500">
|
||||
{getIconForMethodType(method.type)}
|
||||
</div>
|
||||
<div className="flex flex-col flex-1">
|
||||
<div className="font-medium text-gray-900 dark:text-gray-100">
|
||||
{method.methodName}
|
||||
<StyledWrapper>
|
||||
<div className="method-dropdown-container" data-testid="grpc-methods-dropdown">
|
||||
<Dropdown onCreate={onMethodDropdownCreate} icon={<MethodsDropdownIcon />} placement="bottom-end" style={{ maxWidth: 'unset' }} onShow={handleDropdownShow}>
|
||||
<SearchInput
|
||||
searchText={searchText}
|
||||
setSearchText={setSearchText}
|
||||
placeholder="Search"
|
||||
ref={searchInputRef}
|
||||
onKeyDown={handleKeyDown}
|
||||
onBlur={focusSearchInput}
|
||||
onChange={handleSearchChange}
|
||||
className="mt-2 mb-3 "
|
||||
data-testid="grpc-methods-search-input"
|
||||
/>
|
||||
<div ref={listRef} className="method-dropdown-list" data-testid="grpc-methods-list">
|
||||
{Object.entries(groupedMethods).map(([serviceName, methods], serviceIndex) => (
|
||||
<div key={serviceIndex} className="method-dropdown-service-group" onKeyDown={handleKeyDown} tabIndex={0}>
|
||||
<div className="method-dropdown-service-header">
|
||||
{serviceName || 'Default Service'}
|
||||
</div>
|
||||
<div>
|
||||
{methods.map((method, methodIndex) => {
|
||||
const globalMethodIndex
|
||||
= Object.values(groupedMethods)
|
||||
.slice(0, serviceIndex)
|
||||
.reduce((acc, group) => acc + group.length, 0) + methodIndex;
|
||||
const isSelected = selectedGrpcMethod && selectedGrpcMethod.path === method.path;
|
||||
const isFocused = focusedIndex === globalMethodIndex;
|
||||
return (
|
||||
<div
|
||||
key={`${serviceIndex}-${methodIndex}`}
|
||||
className={`method-dropdown-method-item ${
|
||||
isSelected ? 'method-dropdown-method-item--selected' : ''
|
||||
} ${isFocused ? 'method-dropdown-method-item--focused' : ''}`}
|
||||
onClick={() => handleGrpcMethodSelect(method)}
|
||||
data-index={globalMethodIndex}
|
||||
data-testid="grpc-method-item"
|
||||
>
|
||||
<div className="method-dropdown-method-content">
|
||||
<div className="method-dropdown-method-icon">
|
||||
{getIconForMethodType(method.type)}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{method.type}
|
||||
<div className="method-dropdown-method-details">
|
||||
<div className="method-dropdown-method-name">
|
||||
{method.methodName}
|
||||
</div>
|
||||
<div className="method-dropdown-method-type">
|
||||
{method.type}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
|
||||
{filteredMethods.length === 0 && (
|
||||
<div className="py-2 px-3 w-full transition-all duration-200 relative group">
|
||||
<div className="flex items-center">
|
||||
<div className="text-xs mr-3 text-gray-500">
|
||||
{filteredMethods.length === 0 && (
|
||||
<div className="method-dropdown-empty-state">
|
||||
<div className="method-dropdown-empty-state-text">
|
||||
No methods found for the search term
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
.proto-file-dropdown-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.proto-file-dropdown-icon {
|
||||
margin-right: 0.25rem;
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
}
|
||||
|
||||
.proto-file-dropdown-text {
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
white-space: nowrap;
|
||||
color: ${(props) => props.theme.dropdown.color};
|
||||
}
|
||||
|
||||
.proto-file-dropdown-caret {
|
||||
margin-left: 0.25rem;
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
fill: ${(props) => props.theme.colors.text.muted};
|
||||
}
|
||||
|
||||
.proto-file-dropdown-content {
|
||||
max-height: fit-content;
|
||||
overflow-y: auto;
|
||||
width: 30rem;
|
||||
}
|
||||
|
||||
.proto-file-dropdown-mode-section {
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-bottom: 1px solid ${(props) => props.theme.border.border1};
|
||||
}
|
||||
|
||||
.proto-file-dropdown-mode-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.proto-file-dropdown-mode-options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.proto-file-dropdown-mode-option {
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
|
||||
&--active {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.proto-file-dropdown-reflection-message {
|
||||
padding: 0.5rem 0.75rem;
|
||||
color: ${(props) => props.theme.overlay.overlay1};
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -10,6 +10,7 @@ import Dropdown from 'components/Dropdown/index';
|
||||
import ToggleSwitch from 'components/ToggleSwitch/index';
|
||||
import { TabNavigation, ProtoFilesTab, ImportPathsTab } from '../Tabs';
|
||||
import useProtoFileManagement from 'hooks/useProtoFileManagement/index';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const ProtoFileDropdown = ({
|
||||
collection,
|
||||
@@ -121,96 +122,95 @@ const ProtoFileDropdown = ({
|
||||
|
||||
const ProtoFileDropdownIcon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="flex items-center justify-center cursor-pointer select-none" onClick={() => setShowProtoDropdown((prev) => !prev)} data-testid="grpc-proto-file-dropdown-icon">
|
||||
{isReflectionMode ? (<></>
|
||||
) : (
|
||||
<IconFile size={20} strokeWidth={1.5} className="mr-1 text-neutral-400" />
|
||||
<div ref={ref} className="proto-file-dropdown-container" onClick={() => setShowProtoDropdown((prev) => !prev)} data-testid="grpc-proto-file-dropdown-icon">
|
||||
{!isReflectionMode && (
|
||||
<IconFile size={20} strokeWidth={1.5} className="proto-file-dropdown-icon" />
|
||||
)}
|
||||
<span className="text-xs dark:text-neutral-300 text-neutral-700 text-nowrap">
|
||||
<span className="proto-file-dropdown-text">
|
||||
{isReflectionMode ? 'Using Reflection' : (protoFilePath ? getBasename(collection.pathname, protoFilePath) : 'Select Proto File')}
|
||||
</span>
|
||||
<IconChevronDown className="caret ml-1" size={14} strokeWidth={2} />
|
||||
<IconChevronDown className="proto-file-dropdown-caret" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="proto-file-dropdown">
|
||||
<Dropdown
|
||||
onCreate={onProtoDropdownCreate}
|
||||
icon={<ProtoFileDropdownIcon />}
|
||||
placement="bottom-end"
|
||||
visible={showProtoDropdown}
|
||||
onClickOutside={() => setShowProtoDropdown(false)}
|
||||
data-testid="grpc-proto-file-dropdown"
|
||||
>
|
||||
<div className="max-h-fit overflow-y-auto w-[30rem]">
|
||||
<div className="px-3 py-2 border-b border-neutral-200 dark:border-neutral-700" data-testid="grpc-mode-toggle">
|
||||
<div className="flex items-center justify-between">
|
||||
<span>Mode</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`text-xs ${!isReflectionMode ? 'font-medium' : 'text-neutral-500'}`} style={{ color: !isReflectionMode ? theme.colors.text.yellow : undefined }}>
|
||||
Proto File
|
||||
</span>
|
||||
<ToggleSwitch
|
||||
isOn={isReflectionMode}
|
||||
handleToggle={onReflectionModeToggle}
|
||||
size="2xs"
|
||||
activeColor={theme.colors.text.yellow}
|
||||
/>
|
||||
<span className={`text-xs ${isReflectionMode ? 'font-medium' : 'text-neutral-500'}`} style={{ color: isReflectionMode ? theme.colors.text.yellow : undefined }}>
|
||||
Reflection
|
||||
</span>
|
||||
<StyledWrapper>
|
||||
<div className="proto-file-dropdown">
|
||||
<Dropdown
|
||||
onCreate={onProtoDropdownCreate}
|
||||
icon={<ProtoFileDropdownIcon />}
|
||||
placement="bottom-end"
|
||||
visible={showProtoDropdown}
|
||||
onClickOutside={() => setShowProtoDropdown(false)}
|
||||
data-testid="grpc-proto-file-dropdown"
|
||||
>
|
||||
<div className="proto-file-dropdown-content">
|
||||
<div className="proto-file-dropdown-mode-section" data-testid="grpc-mode-toggle">
|
||||
<div className="proto-file-dropdown-mode-controls">
|
||||
<span>Mode</span>
|
||||
<div className="proto-file-dropdown-mode-options">
|
||||
<span className={`proto-file-dropdown-mode-option ${!isReflectionMode ? 'proto-file-dropdown-mode-option--active' : ''}`} style={{ color: !isReflectionMode ? theme.primary.solid : undefined }}>
|
||||
Proto File
|
||||
</span>
|
||||
<ToggleSwitch
|
||||
isOn={isReflectionMode}
|
||||
handleToggle={onReflectionModeToggle}
|
||||
size="2xs"
|
||||
activeColor={theme.primary.solid}
|
||||
/>
|
||||
<span className={`proto-file-dropdown-mode-option ${isReflectionMode ? 'proto-file-dropdown-mode-option--active' : ''}`} style={{ color: isReflectionMode ? theme.primary.solid : undefined }}>
|
||||
Reflection
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!isReflectionMode && (
|
||||
<TabNavigation
|
||||
activeTab={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
collectionProtoFiles={protoFileManagement.protoFiles}
|
||||
collectionImportPaths={protoFileManagement.importPaths}
|
||||
/>
|
||||
)}
|
||||
{!isReflectionMode && (
|
||||
<TabNavigation
|
||||
activeTab={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
collectionProtoFiles={protoFileManagement.protoFiles}
|
||||
collectionImportPaths={protoFileManagement.importPaths}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isReflectionMode && (
|
||||
<>
|
||||
{activeTab === 'protofiles' && (
|
||||
<ProtoFilesTab
|
||||
collectionProtoFiles={protoFileManagement.protoFiles}
|
||||
invalidProtoFiles={invalidProtoFiles}
|
||||
protoFilePath={protoFilePath}
|
||||
collection={collection}
|
||||
onSelectCollectionProtoFile={handleSelectCollectionProtoFile}
|
||||
onOpenCollectionProtobufSettings={handleOpenCollectionProtobufSettings}
|
||||
onSelectProtoFile={handleSelectProtoFile}
|
||||
setShowProtoDropdown={setShowProtoDropdown}
|
||||
/>
|
||||
)}
|
||||
{!isReflectionMode && (
|
||||
<>
|
||||
{activeTab === 'protofiles' && (
|
||||
<ProtoFilesTab
|
||||
collectionProtoFiles={protoFileManagement.protoFiles}
|
||||
invalidProtoFiles={invalidProtoFiles}
|
||||
protoFilePath={protoFilePath}
|
||||
collection={collection}
|
||||
onSelectCollectionProtoFile={handleSelectCollectionProtoFile}
|
||||
onOpenCollectionProtobufSettings={handleOpenCollectionProtobufSettings}
|
||||
onSelectProtoFile={handleSelectProtoFile}
|
||||
setShowProtoDropdown={setShowProtoDropdown}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === 'importpaths' && (
|
||||
<ImportPathsTab
|
||||
collectionImportPaths={protoFileManagement.importPaths}
|
||||
invalidImportPaths={invalidImportPaths}
|
||||
onOpenCollectionProtobufSettings={handleOpenCollectionProtobufSettings}
|
||||
onBrowseImportPath={handleBrowseImportPath}
|
||||
onToggleImportPath={handleToggleImportPath}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{activeTab === 'importpaths' && (
|
||||
<ImportPathsTab
|
||||
collectionImportPaths={protoFileManagement.importPaths}
|
||||
invalidImportPaths={invalidImportPaths}
|
||||
onOpenCollectionProtobufSettings={handleOpenCollectionProtobufSettings}
|
||||
onBrowseImportPath={handleBrowseImportPath}
|
||||
onToggleImportPath={handleToggleImportPath}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{isReflectionMode && (
|
||||
<div className="px-3 py-2">
|
||||
<div className="text-neutral-600 dark:text-neutral-400 mb-2">
|
||||
{isReflectionMode && (
|
||||
<div className="proto-file-dropdown-reflection-message">
|
||||
Using server reflection to discover gRPC methods.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ const Wrapper = styled.div`
|
||||
margin-right: 0.5rem;
|
||||
cursor: pointer;
|
||||
color: ${(props) => props.theme.grpc.importPaths.item.checkbox.color};
|
||||
accent-color: ${(props) => props.theme.colors.accent};
|
||||
}
|
||||
|
||||
.item-text {
|
||||
|
||||
@@ -2,7 +2,7 @@ import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
.tab-container {
|
||||
background-color: ${(props) => props.theme.grpc.tabNav.container.bg};
|
||||
background-color: ${(props) => props.theme.dropdown.separator};
|
||||
}
|
||||
.tab-button {
|
||||
background-color: ${(props) => props.theme.grpc.tabNav.button.inactive.bg};
|
||||
|
||||
@@ -297,7 +297,7 @@ const GrpcQueryUrl = ({ item, collection, handleRun }) => {
|
||||
<StyledWrapper className="flex items-center relative" data-testid="grpc-query-url-container">
|
||||
<div className="flex items-center h-full method-selector-container">
|
||||
<div className="flex items-center justify-center h-full w-16" data-testid="grpc-method-indicator">
|
||||
<span className="text-xs text-indigo-500 font-bold">gRPC</span>
|
||||
<span className="text-xs font-bold" style={{ color: theme.request.grpc }}>gRPC</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center w-full input-container h-full relative">
|
||||
|
||||
@@ -32,7 +32,6 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
const isMac = isMacOS();
|
||||
const saveShortcut = isMac ? 'Cmd + S' : 'Ctrl + S';
|
||||
const editorRef = useRef(null);
|
||||
const isGrpc = item.type === 'grpc-request';
|
||||
const isLoading = ['queued', 'sending'].includes(item.requestState);
|
||||
|
||||
const [generateCodeItemModalOpen, setGenerateCodeItemModalOpen] = useState(false);
|
||||
@@ -372,13 +371,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
return (
|
||||
<StyledWrapper className="flex items-center w-full">
|
||||
<div className="flex items-center h-full min-w-fit">
|
||||
{isGrpc ? (
|
||||
<div className="flex items-center justify-center h-full w-16">
|
||||
<span className="text-xs text-indigo-500 font-bold">gRPC</span>
|
||||
</div>
|
||||
) : (
|
||||
<HttpMethodSelector method={method} onMethodSelect={onMethodSelect} />
|
||||
)}
|
||||
<HttpMethodSelector method={method} onMethodSelect={onMethodSelect} />
|
||||
</div>
|
||||
<div
|
||||
id="request-url"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// VS Code Light+ Theme for Bruno
|
||||
// Based on the default Visual Studio Code Light+ theme
|
||||
import { rgba } from 'polished';
|
||||
|
||||
const colors = {
|
||||
// VS Code Light+ Core Colors
|
||||
@@ -488,12 +489,12 @@ const vscodeLightTheme = {
|
||||
text: colors.RED,
|
||||
link: {
|
||||
color: colors.RED,
|
||||
hoverColor: '#e03e3e'
|
||||
hoverColor: colors.RED
|
||||
}
|
||||
},
|
||||
item: {
|
||||
bg: 'transparent',
|
||||
hoverBg: 'rgba(0, 0, 0, 0.05)',
|
||||
hoverBg: rgba(colors.BLACK, 0.05),
|
||||
text: colors.TEXT,
|
||||
icon: colors.TEXT_MUTED,
|
||||
checkbox: {
|
||||
@@ -527,14 +528,14 @@ const vscodeLightTheme = {
|
||||
text: colors.RED,
|
||||
link: {
|
||||
color: colors.RED,
|
||||
hoverColor: '#e03e3e'
|
||||
hoverColor: colors.RED
|
||||
}
|
||||
},
|
||||
item: {
|
||||
bg: 'transparent',
|
||||
hoverBg: 'rgba(0, 0, 0, 0.05)',
|
||||
hoverBg: rgba(colors.BLACK, 0.05),
|
||||
selected: {
|
||||
bg: 'rgba(0, 122, 204, 0.15)',
|
||||
bg: rgba(colors.BRAND, 0.15),
|
||||
border: colors.BRAND
|
||||
},
|
||||
text: colors.TEXT,
|
||||
|
||||
Reference in New Issue
Block a user