diff --git a/package-lock.json b/package-lock.json index 76cfe4c18..f4652d39e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9996,32 +9996,6 @@ "graphql": ">=0.11 <=16" } }, - "node_modules/handlebars": { - "version": "4.7.8", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/handlebars/node_modules/minimist": { - "version": "1.2.8", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/har-schema": { "version": "2.0.0", "license": "ISC", @@ -10433,7 +10407,6 @@ }, "node_modules/iconv-lite": { "version": "0.6.3", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -12985,6 +12958,7 @@ }, "node_modules/neo-async": { "version": "2.6.2", + "dev": true, "license": "MIT" }, "node_modules/new-github-issue-url": { @@ -16443,6 +16417,7 @@ }, "node_modules/source-map": { "version": "0.6.1", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -17737,17 +17712,6 @@ "version": "1.0.6", "license": "MIT" }, - "node_modules/uglify-js": { - "version": "3.17.4", - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/underscore": { "version": "1.6.0" }, @@ -18363,10 +18327,6 @@ "dev": true, "license": "MIT" }, - "node_modules/wordwrap": { - "version": "1.0.0", - "license": "MIT" - }, "node_modules/wrap-ansi": { "version": "6.2.0", "license": "MIT", @@ -19753,6 +19713,7 @@ "graphql": "^16.6.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", + "iconv-lite": "^0.6.3", "is-valid-path": "^0.1.1", "js-yaml": "^4.1.0", "json-bigint": "^1.0.0", @@ -20806,6 +20767,7 @@ "version": "0.12.0", "license": "MIT", "dependencies": { + "@usebruno/common": "0.1.0", "@usebruno/query": "0.1.0", "ajv": "^8.12.0", "ajv-formats": "^2.1.1", @@ -20815,7 +20777,6 @@ "chai": "^4.3.7", "chai-string": "^1.5.0", "crypto-js": "^4.1.1", - "handlebars": "^4.7.8", "json-query": "^2.2.2", "lodash": "^4.17.21", "moment": "^2.29.4", @@ -25207,6 +25168,7 @@ "@usebruno/js": { "version": "file:packages/bruno-js", "requires": { + "@usebruno/common": "0.1.0", "@usebruno/query": "0.1.0", "ajv": "^8.12.0", "ajv-formats": "^2.1.1", @@ -25216,7 +25178,6 @@ "chai": "^4.3.7", "chai-string": "^1.5.0", "crypto-js": "^4.1.1", - "handlebars": "^4.7.8", "json-query": "^2.2.2", "lodash": "^4.17.21", "moment": "^2.29.4", @@ -26162,6 +26123,7 @@ "graphql": "^16.6.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", + "iconv-lite": "^0.6.3", "is-valid-path": "^0.1.1", "js-yaml": "^4.1.0", "json-bigint": "^1.0.0", @@ -29284,21 +29246,6 @@ "graphql-ws": { "version": "5.12.1" }, - "handlebars": { - "version": "4.7.8", - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.8" - } - } - }, "har-schema": { "version": "2.0.0" }, @@ -29543,7 +29490,6 @@ }, "iconv-lite": { "version": "0.6.3", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" } @@ -31169,7 +31115,8 @@ "version": "0.6.3" }, "neo-async": { - "version": "2.6.2" + "version": "2.6.2", + "dev": true }, "new-github-issue-url": { "version": "0.2.1" @@ -33261,7 +33208,8 @@ } }, "source-map": { - "version": "0.6.1" + "version": "0.6.1", + "dev": true }, "source-map-js": { "version": "1.0.2" @@ -34060,10 +34008,6 @@ "uc.micro": { "version": "1.0.6" }, - "uglify-js": { - "version": "3.17.4", - "optional": true - }, "underscore": { "version": "1.6.0" }, @@ -34458,9 +34402,6 @@ "version": "2.0.1", "dev": true }, - "wordwrap": { - "version": "1.0.0" - }, "wrap-ansi": { "version": "6.2.0", "requires": { diff --git a/packages/bruno-app/src/components/FolderSettings/Headers/StyledWrapper.js b/packages/bruno-app/src/components/FolderSettings/Headers/StyledWrapper.js new file mode 100644 index 000000000..9f723cb81 --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/Headers/StyledWrapper.js @@ -0,0 +1,56 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + table { + width: 100%; + border-collapse: collapse; + font-weight: 600; + table-layout: fixed; + + thead, + td { + border: 1px solid ${(props) => props.theme.table.border}; + } + + thead { + color: ${(props) => props.theme.table.thead.color}; + font-size: 0.8125rem; + user-select: none; + } + td { + padding: 6px 10px; + + &:nth-child(1) { + width: 30%; + } + + &:nth-child(3) { + width: 70px; + } + } + } + + .btn-add-header { + font-size: 0.8125rem; + } + + input[type='text'] { + width: 100%; + border: solid 1px transparent; + outline: none !important; + background-color: inherit; + + &:focus { + outline: none !important; + border: solid 1px transparent; + } + } + + input[type='checkbox'] { + cursor: pointer; + position: relative; + top: 1px; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/FolderSettings/Headers/index.js b/packages/bruno-app/src/components/FolderSettings/Headers/index.js new file mode 100644 index 000000000..550a835c2 --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/Headers/index.js @@ -0,0 +1,153 @@ +import React from 'react'; +import get from 'lodash/get'; +import cloneDeep from 'lodash/cloneDeep'; +import { IconTrash } from '@tabler/icons'; +import { useDispatch } from 'react-redux'; +import { useTheme } from 'providers/Theme'; +import { addFolderHeader, updateFolderHeader, deleteFolderHeader } from 'providers/ReduxStore/slices/collections'; +import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions'; +import SingleLineEditor from 'components/SingleLineEditor'; +import StyledWrapper from './StyledWrapper'; +import { headers as StandardHTTPHeaders } from 'know-your-http-well'; +const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header); + +const Headers = ({ collection, folder }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + const headers = get(folder, 'root.request.headers', []); + + const addHeader = () => { + dispatch( + addFolderHeader({ + collectionUid: collection.uid, + folderUid: folder.uid + }) + ); + }; + + const handleSave = () => dispatch(saveFolderRoot(collection.uid, folder.uid)); + const handleHeaderValueChange = (e, _header, type) => { + const header = cloneDeep(_header); + switch (type) { + case 'name': { + header.name = e.target.value; + break; + } + case 'value': { + header.value = e.target.value; + break; + } + case 'enabled': { + header.enabled = e.target.checked; + break; + } + } + dispatch( + updateFolderHeader({ + header: header, + collectionUid: collection.uid, + folderUid: folder.uid + }) + ); + }; + + const handleRemoveHeader = (header) => { + dispatch( + deleteFolderHeader({ + headerUid: header.uid, + collectionUid: collection.uid, + folderUid: folder.uid + }) + ); + }; + + return ( + +
+ Request headers that will be sent with every request inside this folder. +
+ + + + + + + + + + {headers && headers.length + ? headers.map((header) => { + return ( + + + + + + ); + }) + : null} + +
NameValue
+ + handleHeaderValueChange( + { + target: { + value: newValue + } + }, + header, + 'name' + ) + } + autocomplete={headerAutoCompleteList} + collection={collection} + /> + + + handleHeaderValueChange( + { + target: { + value: newValue + } + }, + header, + 'value' + ) + } + collection={collection} + /> + +
+ handleHeaderValueChange(e, header, 'enabled')} + /> + +
+
+ + +
+ +
+
+ ); +}; +export default Headers; diff --git a/packages/bruno-app/src/components/FolderSettings/Script/StyledWrapper.js b/packages/bruno-app/src/components/FolderSettings/Script/StyledWrapper.js new file mode 100644 index 000000000..66ba1ed3d --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/Script/StyledWrapper.js @@ -0,0 +1,13 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + div.CodeMirror { + height: inherit; + } + + div.title { + color: var(--color-tab-inactive); + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/FolderSettings/Script/index.js b/packages/bruno-app/src/components/FolderSettings/Script/index.js new file mode 100644 index 000000000..6c51c062d --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/Script/index.js @@ -0,0 +1,81 @@ +import React from 'react'; +import get from 'lodash/get'; +import { useDispatch, useSelector } from 'react-redux'; +import CodeEditor from 'components/CodeEditor'; +import { updateFolderRequestScript, updateFolderResponseScript } from 'providers/ReduxStore/slices/collections'; +import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions'; +import { useTheme } from 'providers/Theme'; +import StyledWrapper from './StyledWrapper'; + +const Script = ({ collection, folder }) => { + const dispatch = useDispatch(); + const requestScript = get(folder, 'root.request.script.req', ''); + const responseScript = get(folder, 'root.request.script.res', ''); + + const { displayedTheme } = useTheme(); + const preferences = useSelector((state) => state.app.preferences); + + const onRequestScriptEdit = (value) => { + dispatch( + updateFolderRequestScript({ + script: value, + collectionUid: collection.uid, + folderUid: folder.uid + }) + ); + }; + + const onResponseScriptEdit = (value) => { + dispatch( + updateFolderResponseScript({ + script: value, + collectionUid: collection.uid, + folderUid: folder.uid + }) + ); + }; + + const handleSave = () => { + dispatch(saveFolderRoot(collection.uid, folder.uid)); + }; + + return ( + +
+ Pre and post-request scripts that will run before and after any request inside this folder is sent. +
+
+
Pre Request
+ +
+
+
Post Response
+ +
+ +
+ +
+
+ ); +}; + +export default Script; diff --git a/packages/bruno-app/src/components/FolderSettings/StyledWrapper.js b/packages/bruno-app/src/components/FolderSettings/StyledWrapper.js new file mode 100644 index 000000000..b88a31e0d --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/StyledWrapper.js @@ -0,0 +1,46 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + max-width: 800px; + + div.tabs { + div.tab { + padding: 6px 0px; + border: none; + border-bottom: solid 2px transparent; + margin-right: 1.25rem; + color: var(--color-tab-inactive); + cursor: pointer; + + &:focus, + &:active, + &:focus-within, + &:focus-visible, + &:target { + outline: none !important; + box-shadow: none !important; + } + + &.active { + color: ${(props) => props.theme.tabs.active.color} !important; + border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important; + } + } + } + table { + thead, + td { + border: 1px solid ${(props) => props.theme.table.border}; + + li { + background-color: ${(props) => props.theme.bg} !important; + } + } + } + + .muted { + color: ${(props) => props.theme.colors.text.muted}; + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/FolderSettings/Tests/StyledWrapper.js b/packages/bruno-app/src/components/FolderSettings/Tests/StyledWrapper.js new file mode 100644 index 000000000..ec278887d --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/Tests/StyledWrapper.js @@ -0,0 +1,5 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div``; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/FolderSettings/Tests/index.js b/packages/bruno-app/src/components/FolderSettings/Tests/index.js new file mode 100644 index 000000000..b163c6b1e --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/Tests/index.js @@ -0,0 +1,51 @@ +import React from 'react'; +import get from 'lodash/get'; +import { useDispatch, useSelector } from 'react-redux'; +import CodeEditor from 'components/CodeEditor'; +import { updateFolderTests } from 'providers/ReduxStore/slices/collections'; +import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions'; +import { useTheme } from 'providers/Theme'; +import StyledWrapper from './StyledWrapper'; + +const Tests = ({ collection, folder }) => { + const dispatch = useDispatch(); + const tests = get(folder, 'root.request.tests', ''); + + const { displayedTheme } = useTheme(); + const preferences = useSelector((state) => state.app.preferences); + + const onEdit = (value) => { + dispatch( + updateFolderTests({ + tests: value, + collectionUid: collection.uid, + folderUid: folder.uid + }) + ); + }; + + const handleSave = () => dispatch(saveFolderRoot(collection.uid, folder.uid)); + + return ( + +
These tests will run any time a request in this collection is sent.
+ + +
+ +
+
+ ); +}; + +export default Tests; diff --git a/packages/bruno-app/src/components/FolderSettings/Vars/StyledWrapper.js b/packages/bruno-app/src/components/FolderSettings/Vars/StyledWrapper.js new file mode 100644 index 000000000..44b01b464 --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/Vars/StyledWrapper.js @@ -0,0 +1,9 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + div.title { + color: var(--color-tab-inactive); + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/StyledWrapper.js b/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/StyledWrapper.js new file mode 100644 index 000000000..efacc8288 --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/StyledWrapper.js @@ -0,0 +1,56 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + table { + width: 100%; + border-collapse: collapse; + font-weight: 600; + table-layout: fixed; + + thead, + td { + border: 1px solid ${(props) => props.theme.table.border}; + } + + thead { + color: ${(props) => props.theme.table.thead.color}; + font-size: 0.8125rem; + user-select: none; + } + td { + padding: 6px 10px; + + &:nth-child(1) { + width: 30%; + } + + &:nth-child(3) { + width: 70px; + } + } + } + + .btn-add-var { + font-size: 0.8125rem; + } + + input[type='text'] { + width: 100%; + border: solid 1px transparent; + outline: none !important; + background-color: inherit; + + &:focus { + outline: none !important; + border: solid 1px transparent; + } + } + + input[type='checkbox'] { + cursor: pointer; + position: relative; + top: 1px; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/index.js b/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/index.js new file mode 100644 index 000000000..dcd84d73c --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/index.js @@ -0,0 +1,161 @@ +import React from 'react'; +import cloneDeep from 'lodash/cloneDeep'; +import { IconTrash } from '@tabler/icons'; +import { useDispatch } from 'react-redux'; +import { useTheme } from 'providers/Theme'; +import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions'; +import SingleLineEditor from 'components/SingleLineEditor'; +import Tooltip from 'components/Tooltip'; +import StyledWrapper from './StyledWrapper'; +import toast from 'react-hot-toast'; +import { variableNameRegex } from 'utils/common/regex'; +import { addFolderVar, deleteFolderVar, updateFolderVar } from 'providers/ReduxStore/slices/collections/index'; + +const VarsTable = ({ folder, collection, vars, varType }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + + const addVar = () => { + dispatch( + addFolderVar({ + collectionUid: collection.uid, + folderUid: folder.uid, + type: varType + }) + ); + }; + + const onSave = () => dispatch(saveFolderRoot(collection.uid, folder.uid)); + const handleVarChange = (e, v, type) => { + const _var = cloneDeep(v); + switch (type) { + case 'name': { + const value = e.target.value; + + if (variableNameRegex.test(value) === false) { + toast.error( + 'Variable contains invalid characters! Variables must only contain alpha-numeric characters, "-", "_", "."' + ); + return; + } + + _var.name = value; + break; + } + case 'value': { + _var.value = e.target.value; + break; + } + case 'enabled': { + _var.enabled = e.target.checked; + break; + } + } + dispatch( + updateFolderVar({ + type: varType, + var: _var, + folderUid: folder.uid, + collectionUid: collection.uid + }) + ); + }; + + const handleRemoveVar = (_var) => { + dispatch( + deleteFolderVar({ + type: varType, + varUid: _var.uid, + folderUid: folder.uid, + collectionUid: collection.uid + }) + ); + }; + + return ( + + + + + + {varType === 'request' ? ( + + ) : ( + + )} + + + + + {vars && vars.length + ? vars.map((_var) => { + return ( + + + + + + ); + }) + : null} + +
Name +
+ Value + +
+
+
+ Expr + +
+
+ handleVarChange(e, _var, 'name')} + /> + + + handleVarChange( + { + target: { + value: newValue + } + }, + _var, + 'value' + ) + } + collection={collection} + /> + +
+ handleVarChange(e, _var, 'enabled')} + /> + +
+
+ +
+ ); +}; +export default VarsTable; diff --git a/packages/bruno-app/src/components/FolderSettings/Vars/index.js b/packages/bruno-app/src/components/FolderSettings/Vars/index.js new file mode 100644 index 000000000..8f9cab4d2 --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/Vars/index.js @@ -0,0 +1,32 @@ +import React from 'react'; +import get from 'lodash/get'; +import VarsTable from './VarsTable'; +import StyledWrapper from './StyledWrapper'; +import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions'; +import { useDispatch } from 'react-redux'; + +const Vars = ({ collection, folder }) => { + const dispatch = useDispatch(); + const requestVars = get(folder, 'root.request.vars.req', []); + const responseVars = get(folder, 'root.request.vars.res', []); + const handleSave = () => dispatch(saveFolderRoot(collection.uid, folder.uid)); + return ( + +
+
Pre Request
+ +
+
+
Post Response
+ +
+
+ +
+
+ ); +}; + +export default Vars; diff --git a/packages/bruno-app/src/components/FolderSettings/index.js b/packages/bruno-app/src/components/FolderSettings/index.js new file mode 100644 index 000000000..6dcd9cfd2 --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/index.js @@ -0,0 +1,75 @@ +import React from 'react'; +import classnames from 'classnames'; +import { updatedFolderSettingsSelectedTab } from 'providers/ReduxStore/slices/collections'; +import { useDispatch } from 'react-redux'; +import Headers from './Headers'; +import Script from './Script'; +import Tests from './Tests'; +import StyledWrapper from './StyledWrapper'; +import Vars from './Vars'; + +const FolderSettings = ({ collection, folder }) => { + const dispatch = useDispatch(); + let tab = 'headers'; + const { folderLevelSettingsSelectedTab } = collection; + if (folderLevelSettingsSelectedTab?.[folder.uid]) { + tab = folderLevelSettingsSelectedTab[folder.uid]; + } + + const setTab = (tab) => { + dispatch( + updatedFolderSettingsSelectedTab({ + collectionUid: collection.uid, + folderUid: folder.uid, + tab + }) + ); + }; + + const getTabPanel = (tab) => { + switch (tab) { + case 'headers': { + return ; + } + case 'script': { + return