diff --git a/package-lock.json b/package-lock.json index 8bd25f8d3c..a866931e45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3151,6 +3151,15 @@ "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" }, + "node_modules/@icons/material": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", + "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -14407,6 +14416,12 @@ "node": ">=10" } }, + "node_modules/material-colors": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", + "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==", + "license": "ISC" + }, "node_modules/mdn-data": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", @@ -17003,6 +17018,24 @@ "node": ">=0.10.0" } }, + "node_modules/react-color": { + "version": "2.19.3", + "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz", + "integrity": "sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==", + "license": "MIT", + "dependencies": { + "@icons/material": "^0.2.4", + "lodash": "^4.17.15", + "lodash-es": "^4.17.15", + "material-colors": "^1.2.1", + "prop-types": "^15.5.10", + "reactcss": "^1.2.0", + "tinycolor2": "^1.4.1" + }, + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-copy-to-clipboard": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", @@ -17263,6 +17296,15 @@ "react-dom": ">=16.14.0" } }, + "node_modules/reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", + "license": "MIT", + "dependencies": { + "lodash": "^4.0.1" + } + }, "node_modules/read-binary-file-arch": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", @@ -19536,6 +19578,12 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, "node_modules/tippy.js": { "version": "6.3.7", "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", @@ -20567,6 +20615,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", diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index f4d5fd0f82..6deac356d7 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -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", diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSelector/StyledWrapper.js b/packages/bruno-app/src/components/Environments/EnvironmentSelector/StyledWrapper.js index b1c09d5f24..25b418c54e 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSelector/StyledWrapper.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSelector/StyledWrapper.js @@ -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 { diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSelector/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSelector/index.js index 8bfbb075eb..8e7a7d9880 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSelector/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSelector/index.js @@ -50,11 +50,11 @@ const EnvironmentSelector = ({ collection }) => { }; return ( - +
} placement="bottom-end">
Collection Environments
- {environments && environments.length + {environments?.length ? environments.map((e) => (
{ dropdownTippyRef.current.hide(); }} > - {e.name} + + {e.name}
)) : null} diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentColor/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentColor/index.js new file mode 100644 index 0000000000..9fa9a5e8b9 --- /dev/null +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentColor/index.js @@ -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 ( + onColorChange(color.hex)} + /> + ); +}; +export default EnvironmentColor; diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js index f9fca74ec8..96423edbff 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js @@ -4,6 +4,7 @@ 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); @@ -11,7 +12,10 @@ const EnvironmentDetails = ({ environment, collection, setIsModified }) => { const [openCopyModal, setOpenCopyModal] = useState(false); return ( -
+
{openEditModal && ( setOpenEditModal(false)} environment={environment} collection={collection} /> )} @@ -27,7 +31,7 @@ const EnvironmentDetails = ({ environment, collection, setIsModified }) => { )}
- + {environment.name}
@@ -37,9 +41,8 @@ const EnvironmentDetails = ({ environment, collection, setIsModified }) => {
-
- -
+ +
); }; diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js index 4517bd8d3f..0cf040aebf 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js @@ -9,8 +9,9 @@ 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); @@ -18,7 +19,7 @@ const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collecti 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(() => { @@ -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) { @@ -100,17 +107,15 @@ const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collecti
)}
- {environments && - environments.length && - environments.map((env) => ( -
handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle clicks - > - {env.name} -
- ))} + {environments?.map((env) => ( +
handleEnvironmentClick(env)} + > + {env.name} +
+ ))}
handleCreateEnvClick()}> + Create
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js index 3a17e2ecd0..9edf11e2d8 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js @@ -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 ( @@ -66,13 +65,7 @@ const EnvironmentSettings = ({ collection, onClose }) => { return ( - + ); }; diff --git a/packages/bruno-app/src/components/RequestTabs/StyledWrapper.js b/packages/bruno-app/src/components/RequestTabs/StyledWrapper.js index 93829cca94..0f35845a29 100644 --- a/packages/bruno-app/src/components/RequestTabs/StyledWrapper.js +++ b/packages/bruno-app/src/components/RequestTabs/StyledWrapper.js @@ -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; } diff --git a/packages/bruno-app/src/components/RequestTabs/index.js b/packages/bruno-app/src/components/RequestTabs/index.js index d0cd0b459c..ff384bf19e 100644 --- a/packages/bruno-app/src/components/RequestTabs/index.js +++ b/packages/bruno-app/src/components/RequestTabs/index.js @@ -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'; @@ -75,9 +76,11 @@ const RequestTabs = () => { 'has-chevrons': showChevrons }); }; + + const activeEnvironment = findEnvironmentInCollection(activeCollection, activeCollection.activeEnvironmentUid); // Todo: Must support ephemeral requests return ( - + {newRequestModalOpen && ( setNewRequestModalOpen(false)} /> )} diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index 066889d682..39f50eb4f7 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -30,6 +30,7 @@ import { removeCollection as _removeCollection, selectEnvironment as _selectEnvironment, sortCollections as _sortCollections, + saveEnvironmentColor as _saveEnvironmentColor, requestCancelled, resetRunResults, responseReceived, @@ -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(); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index b7ef2f86e5..b4f7fe0d19 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -135,6 +135,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; } } }, @@ -1749,6 +1760,7 @@ export const { updatedFolderSettingsSelectedTab, collectionUnlinkEnvFileEvent, saveEnvironment, + saveEnvironmentColor, selectEnvironment, newItem, deleteItem, diff --git a/packages/bruno-lang/v2/src/envToJson.js b/packages/bruno-lang/v2/src/envToJson.js index eef4de375d..c20b229862 100644 --- a/packages/bruno-lang/v2/src/envToJson.js +++ b/packages/bruno-lang/v2/src/envToJson.js @@ -2,7 +2,7 @@ const ohm = require('ohm-js'); const _ = require('lodash'); const grammar = ohm.grammar(`Bru { - BruEnvFile = (vars | secretvars)* + BruEnvFile = (vars | secretvars | color)* nl = "\\r"? "\\n" st = " " | "\\t" @@ -27,6 +27,7 @@ const grammar = ohm.grammar(`Bru { secretvars = "vars:secret" array vars = "vars" dictionary + color = "color:" any* }`); const mapPairListToKeyValPairs = (pairList = []) => { @@ -151,6 +152,11 @@ const sem = grammar.createSemantics().addAttribute('ast', { return { variables: vars }; + }, + color: (_1, anystring) => { + return { + color: anystring.sourceString.trim() + }; } }); diff --git a/packages/bruno-lang/v2/src/jsonToEnv.js b/packages/bruno-lang/v2/src/jsonToEnv.js index 42d0a4281d..4cea76f403 100644 --- a/packages/bruno-lang/v2/src/jsonToEnv.js +++ b/packages/bruno-lang/v2/src/jsonToEnv.js @@ -18,13 +18,16 @@ const envToJson = (json) => { return ` ${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')} @@ -36,6 +39,10 @@ ${vars.join('\n')} output += `vars:secret [ ${secretVars.join(',\n')} ] +`; + } + if (color) { + output += `color: ${color} `; } diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js index 11561c5284..cadeeff45d 100644 --- a/packages/bruno-schema/src/collections/index.js +++ b/packages/bruno-schema/src/collections/index.js @@ -15,7 +15,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();