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

feat(#304) Environments color 🎨 #1053

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
49 changes: 49 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/bruno-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"qs": "^6.11.0",
"query-string": "^7.0.1",
"react": "18.2.0",
"react-color": "^2.19.3",
"react-copy-to-clipboard": "^5.1.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import styled from 'styled-components';

const Wrapper = styled.div`
.current-environment {
background-color: ${(props) => props.theme.sidebar.badge.bg};
background-color: ${(props) => props.color ? undefined : props.theme.sidebar.badge.bg};
border: 2px solid ${(props) => props.color ?? props.theme.sidebar.badge.bg};
border-radius: 15px;

.caret {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ const EnvironmentSelector = ({ collection }) => {
};

return (
<StyledWrapper>
<StyledWrapper color={activeEnvironment?.color}>
<div className="flex items-center cursor-pointer environment-selector">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<div className="label-item font-medium">Collection Environments</div>
{environments && environments.length
{environments?.length
? environments.map((e) => (
<div
className={`dropdown-item ${e?.uid === activeEnvironmentUid ? 'active' : ''}`}
Expand All @@ -64,7 +64,8 @@ const EnvironmentSelector = ({ collection }) => {
dropdownTippyRef.current.hide();
}}
>
<IconDatabase size={18} strokeWidth={1.5} /> <span className="ml-2 break-all">{e.name}</span>
<IconDatabase color={e.color == '' ? undefined : e.color} size={18} strokeWidth={1.5} />
<span className="ml-2 break-all">{e.name}</span>
</div>
))
: null}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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 { CirclePicker } from 'react-color';

const EnvironmentColor = ({ environment, collectionUid }) => {
const dispatch = useDispatch();

const onColorChange = useCallback(
(color) => (
dispatch(saveEnvironmentColor(color, environment.uid, collectionUid))
.then(() => toast.success('Environment color changed successfully'))
.catch((e) => toast.error('An error occurred while changing the environment color'))
)
);

return (
<CirclePicker
id="environment-color"
circleSize={14}
circleSpacing={3}
colors={['#000000','#9c27b0','#3f51b5','#03a9f4','#009688','#8bc34a','#ffeb3b','#ff9800','#ff5722','#795548','#607d8b']}
color={environment.color}
onChangeComplete={(color) => onColorChange(color.hex)}
/>
);
};
export default EnvironmentColor;
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ import CopyEnvironment from '../../CopyEnvironment';
import DeleteEnvironment from '../../DeleteEnvironment';
import RenameEnvironment from '../../RenameEnvironment';
import EnvironmentVariables from './EnvironmentVariables';
import EnvironmentColor from '../EnvironmentDetails/EnvironmentColor';

const EnvironmentDetails = ({ environment, collection, setIsModified }) => {
const [openEditModal, setOpenEditModal] = useState(false);
const [openDeleteModal, setOpenDeleteModal] = useState(false);
const [openCopyModal, setOpenCopyModal] = useState(false);

return (
<div className="px-6 flex-grow flex flex-col pt-6" style={{ maxWidth: '700px' }}>
<div
className="px-6 flex-grow flex flex-col pt-6"
style={{ maxWidth: '700px', flexDirection: 'column', rowGap: '0.5em' }}
>
{openEditModal && (
<RenameEnvironment onClose={() => setOpenEditModal(false)} environment={environment} collection={collection} />
)}
Expand All @@ -27,7 +31,7 @@ const EnvironmentDetails = ({ environment, collection, setIsModified }) => {
)}
<div className="flex">
<div className="flex flex-grow items-center">
<IconDatabase className="cursor-pointer" size={20} strokeWidth={1.5} />
<IconDatabase className="cursor-pointer" color={environment.color} size={20} strokeWidth={1.5} />
<span className="ml-1 font-semibold break-all">{environment.name}</span>
</div>
<div className="flex gap-x-4 pl-4">
Expand All @@ -37,9 +41,8 @@ const EnvironmentDetails = ({ environment, collection, setIsModified }) => {
</div>
</div>

<div>
<EnvironmentVariables environment={environment} collection={collection} setIsModified={setIsModified} />
</div>
<EnvironmentColor environment={environment} collectionUid={collection.uid} />
<EnvironmentVariables environment={environment} collection={collection} setIsModified={setIsModified}/>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ import ManageSecrets from '../ManageSecrets';
import StyledWrapper from './StyledWrapper';
import ConfirmSwitchEnv from './ConfirmSwitchEnv';

const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collection, isModified, setIsModified }) => {
const EnvironmentList = ({ collection, isModified, setIsModified }) => {
const { environments } = collection;
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
const [openCreateModal, setOpenCreateModal] = useState(false);
const [openImportModal, setOpenImportModal] = useState(false);
const [openManageSecretsModal, setOpenManageSecretsModal] = useState(false);

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(() => {
Expand All @@ -31,22 +32,28 @@ const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collecti
if (environment) {
setSelectedEnvironment(environment);
} else {
setSelectedEnvironment(environments && environments.length ? environments[0] : null);
setSelectedEnvironment(environments?.length ? environments[0] : null);
}
}, [collection, environments, selectedEnvironment]);
}, [collection, 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]);
}, [envUids, collection, prevEnvUids]);

const handleEnvironmentClick = (env) => {
if (!isModified) {
Expand Down Expand Up @@ -100,17 +107,15 @@ const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collecti
</div>
)}
<div className="environments-sidebar flex flex-col">
{environments &&
environments.length &&
environments.map((env) => (
<div
key={env.uid}
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
onClick={() => handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle clicks
>
<span className="break-all">{env.name}</span>
</div>
))}
{environments?.map((env) => (
<div
key={env.uid}
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
onClick={() => handleEnvironmentClick(env)}
>
<span className="break-all">{env.name}</span>
</div>
))}
<div className="btn-create-environment" onClick={() => handleCreateEnvClick()}>
+ <span>Create</span>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,10 @@ const DefaultTab = ({ setTab }) => {
};

const EnvironmentSettings = ({ collection, onClose }) => {
const [isModified, setIsModified] = useState(false);
const { environments } = collection;
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
const [isModified, setIsModified] = useState(false);
const [tab, setTab] = useState('default');
if (!environments || !environments.length) {
if (!environments?.length) {
return (
<StyledWrapper>
<Modal size="md" title="Environments" handleCancel={onClose} hideCancel={true} hideFooter={true}>
Expand All @@ -66,13 +65,7 @@ const EnvironmentSettings = ({ collection, onClose }) => {

return (
<Modal size="lg" title="Environments" handleCancel={onClose} hideFooter={true}>
<EnvironmentList
selectedEnvironment={selectedEnvironment}
setSelectedEnvironment={setSelectedEnvironment}
collection={collection}
isModified={isModified}
setIsModified={setIsModified}
/>
<EnvironmentList collection={collection} isModified={isModified} setIsModified={setIsModified} />
</Modal>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const Wrapper = styled.div`

&.active {
background: ${(props) => props.theme.requestTabs.active.bg};
border-bottom: 3px solid ${(props) => props.color ?? props.theme.sidebar.badge.bg};
font-weight: 500;
}

Expand Down
5 changes: 4 additions & 1 deletion packages/bruno-app/src/components/RequestTabs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { IconChevronRight, IconChevronLeft } from '@tabler/icons';
import { useSelector, useDispatch } from 'react-redux';
import { focusTab } 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';
Expand Down Expand Up @@ -75,9 +76,11 @@ const RequestTabs = () => {
'has-chevrons': showChevrons
});
};

const activeEnvironment = findEnvironmentInCollection(activeCollection, activeCollection.activeEnvironmentUid);
// Todo: Must support ephemeral requests
return (
<StyledWrapper className={getRootClassname()}>
<StyledWrapper color={activeEnvironment?.color} className={getRootClassname()}>
{newRequestModalOpen && (
<NewRequest collection={activeCollection} onClose={() => setNewRequestModalOpen(false)} />
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
removeCollection as _removeCollection,
selectEnvironment as _selectEnvironment,
sortCollections as _sortCollections,
saveEnvironmentColor as _saveEnvironmentColor,
requestCancelled,
resetRunResults,
responseReceived,
Expand Down Expand Up @@ -979,6 +980,26 @@ export const saveEnvironment = (variables, environmentUid, collectionUid) => (di
});
};

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 };

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();
Expand Down
Loading