forked from extern/bruno
feat: environment variables grid
This commit is contained in:
parent
6a36313e0e
commit
2efc11ff6b
@ -26,7 +26,7 @@
|
||||
"graphql": "^16.2.0",
|
||||
"graphql-request": "^3.7.0",
|
||||
"idb": "^7.0.0",
|
||||
"immer": "^9.0.12",
|
||||
"immer": "^9.0.15",
|
||||
"lodash": "^4.17.21",
|
||||
"mousetrap": "^1.6.5",
|
||||
"nanoid": "3.3.4",
|
||||
|
@ -0,0 +1,45 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
|
||||
thead, td {
|
||||
border: 1px solid #efefef;
|
||||
}
|
||||
|
||||
thead {
|
||||
color: #616161;
|
||||
font-size: 0.8125rem;
|
||||
user-select: none;
|
||||
}
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-add-param {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
border: solid 1px transparent;
|
||||
outline: none !important;
|
||||
|
||||
&:focus{
|
||||
outline: none !important;
|
||||
border: solid 1px transparent;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
@ -0,0 +1,127 @@
|
||||
import React, { useReducer } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import reducer from './reducer';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const EnvironmentVariables = ({environment, collection}) => {
|
||||
const dispatch = useDispatch();
|
||||
const [state, reducerDispatch] = useReducer(reducer, {hasChanges: false, variables: environment.variables || []});
|
||||
const {
|
||||
variables,
|
||||
hasChanges
|
||||
} = state;
|
||||
|
||||
const saveChanges = () => {
|
||||
dispatch(saveEnvironment(cloneDeep(variables), environment.uid, collection.uid))
|
||||
.then(() => {
|
||||
toast.success("Changes saved successfully");
|
||||
reducerDispatch({
|
||||
type: 'CHANGES_SAVED'
|
||||
});
|
||||
})
|
||||
.catch(() => toast.error("An error occured while saving the changes"));
|
||||
};
|
||||
|
||||
const addVariable = () => {
|
||||
reducerDispatch({
|
||||
type: 'ADD_VAR'
|
||||
});
|
||||
};
|
||||
|
||||
const handleVarChange = (e, _variable, type) => {
|
||||
const variable = cloneDeep(_variable);
|
||||
switch(type) {
|
||||
case 'name' : {
|
||||
variable.name = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'value' : {
|
||||
variable.value = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'enabled' : {
|
||||
variable.enabled = e.target.checked;
|
||||
break;
|
||||
}
|
||||
}
|
||||
reducerDispatch({
|
||||
type: 'UPDATE_VAR',
|
||||
variable
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoveVars = (variable) => {
|
||||
reducerDispatch({
|
||||
type: 'DELETE_VAR',
|
||||
variable
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full mt-6 mb-6">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>Value</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{variables && variables.length ? variables.map((variable, index) => {
|
||||
return (
|
||||
<tr key={variable.uid}>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
value={variable.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleVarChange(e, variable, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
value={variable.value}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleVarChange(e, variable, 'value')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={variable.enabled}
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleVarChange(e, variable, 'enabled')}
|
||||
/>
|
||||
<button onClick={() => handleRemoveVars(variable)}>
|
||||
<IconTrash strokeWidth={1.5} size={20}/>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div>
|
||||
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addVariable}>+ Add Variable</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit" className="submit btn btn-md btn-secondary mt-2" disabled={!hasChanges} onClick={saveChanges}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
)
|
||||
};
|
||||
export default EnvironmentVariables;
|
@ -0,0 +1,50 @@
|
||||
import produce from 'immer';
|
||||
import find from 'lodash/find';
|
||||
import filter from 'lodash/filter';
|
||||
import { uuid } from 'utils/common';
|
||||
|
||||
const reducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case 'ADD_VAR': {
|
||||
return produce(state, (draft) => {
|
||||
draft.variables.push({
|
||||
uid: uuid(),
|
||||
name: '',
|
||||
value: '',
|
||||
type: 'text',
|
||||
enabled: true
|
||||
});
|
||||
draft.hasChanges = true;
|
||||
});
|
||||
}
|
||||
|
||||
case 'UPDATE_VAR': {
|
||||
return produce(state, (draft) => {
|
||||
const variable = find(draft.variables, (v) => v.uid === action.variable.uid);
|
||||
variable.name = action.variable.name;
|
||||
variable.value = action.variable.value;
|
||||
variable.enabled = action.variable.enabled;
|
||||
draft.hasChanges = true;
|
||||
});
|
||||
}
|
||||
|
||||
case 'DELETE_VAR': {
|
||||
return produce(state, (draft) => {
|
||||
draft.variables = filter(draft.variables, (v) => v.uid !== action.variable.uid);
|
||||
draft.hasChanges = true;
|
||||
});
|
||||
}
|
||||
|
||||
case 'CHANGES_SAVED': {
|
||||
return produce(state, (draft) => {
|
||||
draft.hasChanges = false;
|
||||
});
|
||||
}
|
||||
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
@ -1,23 +1,32 @@
|
||||
import React, {useState } from "react";
|
||||
import { IconEdit, IconTrash } from "@tabler/icons";
|
||||
import { IconEdit, IconTrash, IconDatabase } from "@tabler/icons";
|
||||
import EnvironmentVariables from './EnvironmentVariables';
|
||||
import RenameEnvironment from "../../RenameEnvironment";
|
||||
import DeleteEnvironment from "../../DeleteEnvironment";
|
||||
|
||||
const EnvironmentDetails = ({environment, collection}) => {
|
||||
const [ openEditModal, setOpenEditModal] = useState(false);
|
||||
const [ openDeleteModal, setOpenDeleteModal] = useState(false);
|
||||
console.log(environment);
|
||||
|
||||
return (
|
||||
<div className="ml-6 flex-grow flex pt-6" style={{maxWidth: '700px'}}>
|
||||
<div className="px-6 flex-grow flex flex-col pt-6" style={{maxWidth: '700px'}}>
|
||||
{openEditModal && <RenameEnvironment onClose={() => setOpenEditModal(false)} environment={environment} collection={collection}/>}
|
||||
{openDeleteModal && <DeleteEnvironment onClose={() => setOpenDeleteModal(false)} environment={environment} collection={collection}/>}
|
||||
<div className="flex flex-grow">
|
||||
<div className="flex-grow font-medium">{environment.name}</div>
|
||||
<div className="flex gap-x-4 pl-4 pr-6">
|
||||
<div className="flex">
|
||||
<div className="flex flex-grow items-center">
|
||||
<IconDatabase className="cursor-pointer" size={20} strokeWidth={1.5}/>
|
||||
<span className="ml-1 font-semibold">{environment.name}</span>
|
||||
</div>
|
||||
<div className="flex gap-x-4 pl-4">
|
||||
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenEditModal(true)}/>
|
||||
<IconTrash className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenDeleteModal(true)}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<EnvironmentVariables key={environment.uid} environment={environment} collection={collection}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
|
@ -35,6 +35,7 @@ import {
|
||||
addEnvironment as _addEnvironment,
|
||||
renameEnvironment as _renameEnvironment,
|
||||
deleteEnvironment as _deleteEnvironment,
|
||||
saveEnvironment as _saveEnvironment,
|
||||
createCollection as _createCollection,
|
||||
renameCollection as _renameCollection,
|
||||
deleteCollection as _deleteCollection,
|
||||
@ -691,6 +692,33 @@ export const deleteEnvironment = (environmentUid, collectionUid) => (dispatch,
|
||||
});
|
||||
};
|
||||
|
||||
export const saveEnvironment = (variables, environmentUid, collectionUid) => (dispatch, getState) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const state = getState();
|
||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||
if(!collection) {
|
||||
return reject(new Error('Collection not found'));
|
||||
}
|
||||
|
||||
const collectionCopy = cloneDeep(collection);
|
||||
const environment = findEnvironmentInCollection(collectionCopy, environmentUid);
|
||||
if(!environment) {
|
||||
return reject(new Error('Environment not found'));
|
||||
}
|
||||
|
||||
environment.variables = variables;
|
||||
|
||||
const collectionToSave = transformCollectionToSaveToIdb(collectionCopy);
|
||||
|
||||
collectionSchema
|
||||
.validate(collectionToSave)
|
||||
.then(() => saveCollectionToIdb(window.__idb, collectionToSave))
|
||||
.then(() => dispatch(_saveEnvironment({variables, environmentUid, collectionUid})))
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
export const removeLocalCollection = (collectionUid) => (dispatch, getState) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const state = getState();
|
||||
|
@ -103,6 +103,18 @@ export const collectionsSlice = createSlice({
|
||||
}
|
||||
}
|
||||
},
|
||||
saveEnvironment: (state, action) => {
|
||||
const { variables, environmentUid, collectionUid } = action.payload;
|
||||
const collection = findCollectionByUid(state.collections, collectionUid);
|
||||
|
||||
if(collection) {
|
||||
const environment = findEnvironmentInCollection(collection, environmentUid);
|
||||
|
||||
if(environment) {
|
||||
environment.variables = variables;
|
||||
}
|
||||
}
|
||||
},
|
||||
newItem: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
|
||||
@ -727,6 +739,7 @@ export const {
|
||||
addEnvironment,
|
||||
renameEnvironment,
|
||||
deleteEnvironment,
|
||||
saveEnvironment,
|
||||
newItem,
|
||||
deleteItem,
|
||||
renameItem,
|
||||
|
Loading…
Reference in New Issue
Block a user