mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-27 22:54:07 +00:00
* associate environment to a color Signed-off-by: mathieu <mathieu.dreano@gmail.com> use StyledWrapper Signed-off-by: mathieu <mathieu.dreano@gmail.com> don't save anything for color if it is not set Signed-off-by: mathieu <mathieu.dreano@gmail.com> use redux store instead of local state remove logs fix selectedEnvironment cleanup add bottom border on active tab * associate environment to a color Signed-off-by: mathieu <mathieu.dreano@gmail.com> * move dependency to appropriate package.json Signed-off-by: mathieu <mathieu.dreano@gmail.com> * use border instead of background color Signed-off-by: mathieu <mathieu.dreano@gmail.com> * simplify onColorChange Signed-off-by: mathieu <mathieu.dreano@gmail.com> * add black, keep backgound on unselected color Signed-off-by: mathieu <mathieu.dreano@gmail.com> * fix conflicts Signed-off-by: mathieu <mathieu.dreano@gmail.com> * associate environment to a color Signed-off-by: mathieu <mathieu.dreano@gmail.com> use StyledWrapper Signed-off-by: mathieu <mathieu.dreano@gmail.com> don't save anything for color if it is not set Signed-off-by: mathieu <mathieu.dreano@gmail.com> use redux store instead of local state remove logs fix selectedEnvironment cleanup add bottom border on active tab # Conflicts: # packages/bruno-app/src/components/Environments/EnvironmentSelector/StyledWrapper.js # packages/bruno-app/src/components/Environments/EnvironmentSelector/index.js # packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js # packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js # packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js # packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js * Update packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentColor/index.js Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * unused selectedEnvironment prop in EnvironmentList Signed-off-by: Mathieu D <mathieu.dreano@decathlon.com> * RequestTab, avoid unnecessary call if undefined activeCollection Signed-off-by: Mathieu D <mathieu.dreano@decathlon.com> * use @uiw/reac-color instead of react-color Signed-off-by: Mathieu D <mathieu.dreano@decathlon.com> --------- Signed-off-by: mathieu <mathieu.dreano@gmail.com> Signed-off-by: Mathieu D <mathieu.dreano@decathlon.com> Co-authored-by: Mathieu D <mathieu.dreano@decathlon.com> Co-authored-by: Anoop M D <anoop@usebruno.com> Co-authored-by: Mathieu DREANO <122891400+mdreano@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
"@tabler/icons": "^1.46.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@uiw/react-color": "^2.9.2",
|
||||
"@usebruno/common": "0.1.0",
|
||||
"@usebruno/graphql-docs": "0.1.0",
|
||||
"@usebruno/schema": "0.7.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { IconPlus, IconDownload, IconSettings } from '@tabler/icons';
|
||||
import { IconPlus, IconDownload, IconSettings, IconDatabase } from '@tabler/icons';
|
||||
import ToolHint from 'components/ToolHint';
|
||||
|
||||
const EnvironmentListContent = ({
|
||||
@@ -38,6 +38,7 @@ const EnvironmentListContent = ({
|
||||
data-tooltip-content={env.name}
|
||||
data-tooltip-hidden={env.name?.length < 90}
|
||||
>
|
||||
<IconDatabase size={16} strokeWidth={1.5} color={env.color} />
|
||||
<span className="max-w-100% truncate no-wrap">{env.name}</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -5,8 +5,8 @@ const Wrapper = styled.div`
|
||||
border-radius: ${(props) => props.theme.border.radius.base};
|
||||
padding: 0.25rem 0.3rem 0.25rem 0.5rem;
|
||||
user-select: none;
|
||||
background-color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.bg};
|
||||
border: 1px solid ${(props) => props.theme.app.collection.toolbar.environmentSelector.border};
|
||||
background-color: ${(props) => props.color ? undefined : 'transparent'};
|
||||
border: 2px solid ${(props) => props.color ?? props.theme.dropdown.selectedColor};
|
||||
line-height: 1rem;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
@@ -15,6 +15,11 @@ const Wrapper = styled.div`
|
||||
background-color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.hoverBg};
|
||||
}
|
||||
|
||||
.active-env-toolhint {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.caret {
|
||||
margin-left: 0.25rem;
|
||||
color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.caret};
|
||||
@@ -24,11 +29,12 @@ const Wrapper = styled.div`
|
||||
|
||||
.env-icon {
|
||||
margin-right: 0.25rem;
|
||||
color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.icon};
|
||||
color: ${(props) => props.color ?? props.theme.dropdown.selectedColor};
|
||||
}
|
||||
|
||||
.env-text {
|
||||
color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.text};
|
||||
color: ${(props) => props.color ?? props.theme.dropdown.selectedColor};
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -65,6 +71,37 @@ const Wrapper = styled.div`
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tippy-box .tippy-content {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
column-gap: 0.35em;
|
||||
padding: 0.35rem 0.6rem;
|
||||
cursor: pointer;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
color: ${(props) => props.theme.dropdown.primaryText};
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: ${(props) => props.theme.dropdown.hoverBg};
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: ${(props) => props.theme.dropdown.selectedBg};
|
||||
color: ${(props) => props.color ?? props.theme.dropdown.selectedColor} !important;
|
||||
}
|
||||
|
||||
&.no-environment {
|
||||
color: ${(props) => props.theme.dropdown.mutedText};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.configure-button {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
||||
@@ -134,14 +134,15 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
{activeCollectionEnvironment && (
|
||||
<>
|
||||
<div className="flex items-center">
|
||||
<IconDatabase size={14} strokeWidth={1.5} className="env-icon" />
|
||||
<ToolHint
|
||||
className="active-env-toolhint"
|
||||
text={activeCollectionEnvironment.name}
|
||||
toolhintId={`collection-env-${activeCollectionEnvironment.uid}`}
|
||||
place="bottom-start"
|
||||
delayShow={1000}
|
||||
hidden={activeCollectionEnvironment.name?.length < 7}
|
||||
>
|
||||
<IconDatabase size={14} strokeWidth={1.5} className="env-icon" />
|
||||
<span className="env-text max-w-24 truncate overflow-hidden">{activeCollectionEnvironment.name}</span>
|
||||
</ToolHint>
|
||||
</div>
|
||||
@@ -182,7 +183,7 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledWrapper width={dropdownWidth}>
|
||||
<StyledWrapper color={activeCollectionEnvironment?.color} width={dropdownWidth}>
|
||||
<div className="environment-selector flex align-center cursor-pointer">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||
{/* Tab Headers */}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { saveEnvironmentColor } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { Circle } from '@uiw/react-color';
|
||||
|
||||
const EnvironmentColor = ({ environment, collectionUid }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onColorChange = useCallback(
|
||||
(color) => {
|
||||
if (color == environment.color) return;
|
||||
dispatch(saveEnvironmentColor(color, environment.uid, collectionUid))
|
||||
.then(() => toast.success('Environment color changed successfully'))
|
||||
.catch(() => toast.error('An error occurred while changing the environment color'));
|
||||
},
|
||||
[dispatch, environment.uid, environment.color, collectionUid]
|
||||
);
|
||||
|
||||
return (
|
||||
<Circle
|
||||
id="environment-color"
|
||||
style={{ gap: 3 }}
|
||||
pointProps={{ style: { width: 14, height: 14, borderRadius: 10 } }}
|
||||
colors={['#000000','#9c27b0','#3f51b5','#03a9f4','#009688','#8bc34a','#ffeb3b','#ff9800','#ff5722','#795548','#607d8b']}
|
||||
color={environment.color}
|
||||
onChange={(color) => onColorChange(color.hex)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default EnvironmentColor;
|
||||
@@ -7,6 +7,8 @@ import toast from 'react-hot-toast';
|
||||
import CopyEnvironment from 'components/Environments/EnvironmentSettings/CopyEnvironment';
|
||||
import DeleteEnvironment from 'components/Environments/EnvironmentSettings/DeleteEnvironment';
|
||||
import EnvironmentVariables from './EnvironmentVariables';
|
||||
import EnvironmentColor from '../EnvironmentDetails/EnvironmentColor';
|
||||
import ToolHint from 'components/ToolHint/index';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
|
||||
@@ -119,7 +121,6 @@ const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
|
||||
{openCopyModal && (
|
||||
<CopyEnvironment onClose={() => setOpenCopyModal(false)} environment={environment} collection={collection} />
|
||||
)}
|
||||
|
||||
<div className="header">
|
||||
<div className={`title-container ${isRenaming ? 'renaming' : ''}`}>
|
||||
{isRenaming ? (
|
||||
@@ -174,9 +175,8 @@ const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="content">
|
||||
<EnvironmentVariables environment={environment} setIsModified={setIsModified} collection={collection} />
|
||||
</div>
|
||||
<EnvironmentColor environment={environment} collectionUid={collection.uid} />
|
||||
<EnvironmentVariables environment={environment} collection={collection} setIsModified={setIsModified} onClose={onClose} />
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { findEnvironmentInCollection, findItem } from 'utils/collections';
|
||||
import usePrevious from 'hooks/usePrevious';
|
||||
import EnvironmentDetails from './EnvironmentDetails';
|
||||
import CreateEnvironment from 'components/Environments/EnvironmentSettings/CreateEnvironment';
|
||||
@@ -24,6 +25,10 @@ const EnvironmentList = ({
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
||||
const EnvironmentList = ({ collection, isModified, setIsModified, onClose, setShowExportModal }) => {
|
||||
const { environments, activeEnvironmentUid } = collection;
|
||||
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
|
||||
const [openCreateModal, setOpenCreateModal] = useState(false);
|
||||
const [openImportModal, setOpenImportModal] = useState(false);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
@@ -38,7 +43,7 @@ const EnvironmentList = ({
|
||||
const [switchEnvConfirmClose, setSwitchEnvConfirmClose] = useState(false);
|
||||
const [originalEnvironmentVariables, setOriginalEnvironmentVariables] = useState([]);
|
||||
|
||||
const envUids = environments ? environments.map((env) => env.uid) : [];
|
||||
const envUids = environments?.map((env) => env.uid) ?? [];
|
||||
const prevEnvUids = usePrevious(envUids);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -63,10 +68,12 @@ const EnvironmentList = ({
|
||||
if (hasSelectedEnvironmentChanged || selectedEnvironment.uid !== _selectedEnvironment?.uid) {
|
||||
setSelectedEnvironment(_selectedEnvironment);
|
||||
}
|
||||
setOriginalEnvironmentVariables(_selectedEnvironment?.variables || []);
|
||||
setOriginalEnvironmentVariables(selectedEnvironment?.variables||[]);
|
||||
setSelectedEnvironment(findItem(environments, selectedEnvironment.uid));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const environment = environments?.find((env) => env.uid === activeEnvironmentUid) || environments?.[0];
|
||||
|
||||
setSelectedEnvironment(environment);
|
||||
@@ -74,15 +81,21 @@ const EnvironmentList = ({
|
||||
}, [environments, activeEnvironmentUid, selectedEnvironment]);
|
||||
|
||||
useEffect(() => {
|
||||
if (prevEnvUids && prevEnvUids.length && envUids.length > prevEnvUids.length) {
|
||||
if (selectedEnvironment) {
|
||||
setSelectedEnvironment(findEnvironmentInCollection(collection, selectedEnvironment.uid));
|
||||
}
|
||||
}, [environments]);
|
||||
|
||||
useEffect(() => {
|
||||
if (prevEnvUids?.length && envUids.length > prevEnvUids.length) {
|
||||
const newEnv = environments.find((env) => !prevEnvUids.includes(env.uid));
|
||||
if (newEnv) {
|
||||
setSelectedEnvironment(newEnv);
|
||||
}
|
||||
}
|
||||
|
||||
if (prevEnvUids && prevEnvUids.length && envUids.length < prevEnvUids.length) {
|
||||
setSelectedEnvironment(environments && environments.length ? environments[0] : null);
|
||||
if (prevEnvUids?.length && envUids.length < prevEnvUids.length) {
|
||||
setSelectedEnvironment(environments?.length ? environments[0] : null);
|
||||
}
|
||||
}, [envUids, environments, prevEnvUids]);
|
||||
|
||||
|
||||
@@ -71,9 +71,8 @@ const Wrapper = styled.div`
|
||||
|
||||
&:not(.active) {
|
||||
background: ${(props) => props.theme.requestTabs.bg};
|
||||
border-color: transparent;
|
||||
border-bottom: 3px solid ${(props) => props.color ?? "transparent"};
|
||||
border-radius: ${(props) => props.theme.border.radius.base};
|
||||
|
||||
}
|
||||
|
||||
&:nth-last-child(1) {
|
||||
@@ -113,7 +112,7 @@ const Wrapper = styled.div`
|
||||
&.active {
|
||||
background: ${(props) => props.theme.bg || '#ffffff'};
|
||||
border: 1px solid ${(props) => props.theme.requestTabs.bottomBorder};
|
||||
border-bottom-color: ${(props) => props.theme.bg || '#ffffff'};
|
||||
border-bottom-color: ${(props) => props.color ?? props.theme.bg ?? '#ffffff'};
|
||||
border-radius: 8px 8px 0 0;
|
||||
z-index: 1;
|
||||
margin-bottom: -2px;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { IconChevronRight, IconChevronLeft } from '@tabler/icons';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { focusTab, reorderTabs } from 'providers/ReduxStore/slices/tabs';
|
||||
import NewRequest from 'components/Sidebar/NewRequest';
|
||||
import { findEnvironmentInCollection } from 'utils/collections';
|
||||
import CollectionToolBar from './CollectionToolBar';
|
||||
import RequestTab from './RequestTab';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
@@ -85,6 +86,17 @@ const RequestTabs = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const activeTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
if (!activeTab) {
|
||||
return <StyledWrapper>Something went wrong!</StyledWrapper>;
|
||||
}
|
||||
|
||||
const activeCollection = find(collections, (c) => c.uid === activeTab.collectionUid);
|
||||
const activeEnvironment = activeCollection
|
||||
? findEnvironmentInCollection(activeCollection, activeCollection.activeEnvironmentUid)
|
||||
: null;
|
||||
const collectionRequestTabs = filter(tabs, (t) => t.collectionUid === activeTab.collectionUid);
|
||||
|
||||
const effectiveSidebarWidth = sidebarCollapsed ? 0 : leftSidebarWidth;
|
||||
const maxTablistWidth = screenWidth - effectiveSidebarWidth - 150;
|
||||
|
||||
@@ -102,9 +114,14 @@ const RequestTabs = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const getRootClassname = () => {
|
||||
return classnames({
|
||||
'has-chevrons': showChevrons
|
||||
});
|
||||
};
|
||||
// Todo: Must support ephemeral requests
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<StyledWrapper color={activeEnvironment?.color} className={getRootClassname()}>
|
||||
{newRequestModalOpen && (
|
||||
<NewRequest collectionUid={activeCollection?.uid} onClose={() => setNewRequestModalOpen(false)} />
|
||||
)}
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
sortCollections as _sortCollections,
|
||||
updateCollectionMountStatus,
|
||||
moveCollection,
|
||||
saveEnvironmentColor as _saveEnvironmentColor,
|
||||
workspaceEnvUpdateEvent,
|
||||
requestCancelled,
|
||||
resetRunResults,
|
||||
@@ -2252,6 +2253,27 @@ export const mergeAndPersistEnvironment
|
||||
});
|
||||
};
|
||||
|
||||
export const saveEnvironmentColor = (color, environmentUid, collectionUid) => (dispatch, getState) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const collection =
|
||||
findCollectionByUid(getState().collections.collections, collectionUid) ??
|
||||
reject(new Error('Collection not found'));
|
||||
const environment =
|
||||
findEnvironmentInCollection(collection, environmentUid) ?? reject(new Error('Environment not found'));
|
||||
const updatedEnvironment = { ...environment, color: color };
|
||||
|
||||
const { ipcRenderer } = window;
|
||||
environmentSchema
|
||||
.validate(updatedEnvironment)
|
||||
// save to file
|
||||
.then(() => ipcRenderer.invoke('renderer:save-environment', collection.pathname, updatedEnvironment))
|
||||
// update store
|
||||
.then(() => dispatch(_saveEnvironmentColor({ color, environmentUid, collectionUid })))
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
export const selectEnvironment = (environmentUid, collectionUid) => (dispatch, getState) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const state = getState();
|
||||
|
||||
@@ -258,6 +258,17 @@ export const collectionsSlice = createSlice({
|
||||
|
||||
if (environment) {
|
||||
environment.variables = variables;
|
||||
environment.color = color;
|
||||
}
|
||||
}
|
||||
},
|
||||
saveEnvironmentColor: (state, action) => {
|
||||
const { color, environmentUid, collectionUid } = action.payload;
|
||||
const collection = findCollectionByUid(state.collections, collectionUid);
|
||||
if (collection) {
|
||||
const environment = findEnvironmentInCollection(collection, environmentUid);
|
||||
if (environment) {
|
||||
environment.color = color;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3464,6 +3475,7 @@ export const {
|
||||
updatedFolderSettingsSelectedTab,
|
||||
collectionUnlinkEnvFileEvent,
|
||||
saveEnvironment,
|
||||
saveEnvironmentColor,
|
||||
selectEnvironment,
|
||||
newItem,
|
||||
deleteItem,
|
||||
|
||||
@@ -12,7 +12,7 @@ const _ = require('lodash');
|
||||
// }
|
||||
const indentLevel = 4;
|
||||
const grammar = ohm.grammar(`Bru {
|
||||
BruEnvFile = (vars | secretvars)*
|
||||
BruEnvFile = (vars | secretvars | color)*
|
||||
|
||||
nl = "\\r"? "\\n"
|
||||
st = " " | "\\t"
|
||||
@@ -43,6 +43,7 @@ const grammar = ohm.grammar(`Bru {
|
||||
|
||||
secretvars = "vars:secret" array
|
||||
vars = "vars" dictionary
|
||||
color = "color:" any*
|
||||
}`);
|
||||
|
||||
const mapPairListToKeyValPairs = (pairList = []) => {
|
||||
@@ -190,6 +191,11 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
||||
return {
|
||||
variables: vars
|
||||
};
|
||||
},
|
||||
color: (_1, anystring) => {
|
||||
return {
|
||||
color: anystring.sourceString.trim()
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -20,13 +20,16 @@ const envToJson = (json) => {
|
||||
return indentString(`${prefix}${name}`);
|
||||
});
|
||||
|
||||
const color = _.get(json, 'color', undefined);
|
||||
|
||||
let output = '';
|
||||
|
||||
if (!variables || !variables.length) {
|
||||
return `vars {
|
||||
output += `vars {
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
let output = '';
|
||||
if (vars.length) {
|
||||
output += `vars {
|
||||
${vars.join('\n')}
|
||||
@@ -38,6 +41,10 @@ ${vars.join('\n')}
|
||||
output += `vars:secret [
|
||||
${secretVars.join(',\n')}
|
||||
]
|
||||
`;
|
||||
}
|
||||
if (color) {
|
||||
output += `color: ${color}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ const environmentVariablesSchema = Yup.object({
|
||||
const environmentSchema = Yup.object({
|
||||
uid: uidSchema,
|
||||
name: Yup.string().min(1).required('name is required'),
|
||||
variables: Yup.array().of(environmentVariablesSchema).required('variables are required')
|
||||
variables: Yup.array().of(environmentVariablesSchema).required('variables are required'),
|
||||
color: Yup.string().optional()
|
||||
})
|
||||
.noUnknown(true)
|
||||
.strict();
|
||||
|
||||
Reference in New Issue
Block a user