diff --git a/package-lock.json b/package-lock.json index b0942b20b..a50cf70fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30606,6 +30606,7 @@ "@babel/core": "^7.27.1", "@babel/preset-env": "^7.27.2", "@babel/preset-react": "^7.27.1", + "@babel/preset-typescript": "^7.22.0", "@rsbuild/core": "^1.1.2", "@rsbuild/plugin-babel": "^1.0.3", "@rsbuild/plugin-node-polyfill": "^1.2.0", @@ -30633,6 +30634,21 @@ "webpack-cli": "^4.9.1" } }, + "packages/bruno-app/node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, "packages/bruno-app/node_modules/@babel/compat-data": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", @@ -30643,19 +30659,49 @@ "node": ">=6.9.0" } }, - "packages/bruno-app/node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", - "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "packages/bruno-app/node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "packages/bruno-app/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "packages/bruno-app/node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.27.1", + "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "engines": { @@ -30704,14 +30750,14 @@ } }, "packages/bruno-app/node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -30730,6 +30776,16 @@ "node": ">=6.9.0" } }, + "packages/bruno-app/node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "packages/bruno-app/node_modules/@babel/helper-remap-async-to-generator": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", @@ -30749,15 +30805,15 @@ } }, "packages/bruno-app/node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -30795,6 +30851,22 @@ "node": ">=6.9.0" } }, + "packages/bruno-app/node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "packages/bruno-app/node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", @@ -30911,6 +30983,22 @@ "@babel/core": "^7.0.0-0" } }, + "packages/bruno-app/node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "packages/bruno-app/node_modules/@babel/plugin-transform-arrow-functions": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", @@ -31681,6 +31769,26 @@ "@babel/core": "^7.0.0-0" } }, + "packages/bruno-app/node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "packages/bruno-app/node_modules/@babel/plugin-transform-unicode-escapes": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", @@ -31842,6 +31950,74 @@ "semver": "bin/semver.js" } }, + "packages/bruno-app/node_modules/@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "packages/bruno-app/node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "packages/bruno-app/node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "packages/bruno-app/node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, "packages/bruno-app/node_modules/@testing-library/react": { "version": "16.3.0", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", @@ -31980,6 +32156,24 @@ "yarn": ">=1" } }, + "packages/bruno-app/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "packages/bruno-app/node_modules/electron-to-chromium": { "version": "1.5.157", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.157.tgz", @@ -32049,6 +32243,13 @@ "url": "https://opencollective.com/express" } }, + "packages/bruno-app/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "packages/bruno-app/node_modules/qs": { "version": "6.14.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", diff --git a/packages/bruno-app/.babelrc b/packages/bruno-app/.babelrc index 3d8b68884..c7a951c87 100644 --- a/packages/bruno-app/.babelrc +++ b/packages/bruno-app/.babelrc @@ -1,4 +1,4 @@ { - "presets": ["@babel/preset-env", "@babel/preset-react"], + "presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"], "plugins": [["styled-components", { "ssr": true }]] } \ No newline at end of file diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index e5c95f83f..5ee0b4ff8 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -100,6 +100,7 @@ "@babel/core": "^7.27.1", "@babel/preset-env": "^7.27.2", "@babel/preset-react": "^7.27.1", + "@babel/preset-typescript": "^7.22.0", "@rsbuild/core": "^1.1.2", "@rsbuild/plugin-babel": "^1.0.3", "@rsbuild/plugin-node-polyfill": "^1.2.0", diff --git a/packages/bruno-app/src/components/CollectionSettings/Headers/index.js b/packages/bruno-app/src/components/CollectionSettings/Headers/index.js index 1385a98ae..140422f73 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Headers/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Headers/index.js @@ -1,9 +1,10 @@ import React, { useState, useCallback } from 'react'; import get from 'lodash/get'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useTheme } from 'providers/Theme'; import { setCollectionHeaders } from 'providers/ReduxStore/slices/collections'; import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions'; +import { updateTableColumnWidths } from 'providers/ReduxStore/slices/tabs'; import SingleLineEditor from 'components/SingleLineEditor'; import EditableTable from 'components/EditableTable'; import StyledWrapper from './StyledWrapper'; @@ -18,11 +19,21 @@ const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header); const Headers = ({ collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); + const tabs = useSelector((state) => state.tabs.tabs); + const activeTabUid = useSelector((state) => state.tabs.activeTabUid); const headers = collection.draft?.root ? get(collection, 'draft.root.request.headers', []) : get(collection, 'root.request.headers', []); const [isBulkEditMode, setIsBulkEditMode] = useState(false); + // Get column widths from Redux + const focusedTab = tabs?.find((t) => t.uid === activeTabUid); + const collectionHeadersWidths = focusedTab?.tableColumnWidths?.['collection-headers'] || {}; + + const handleColumnWidthsChange = (tableId, widths) => { + dispatch(updateTableColumnWidths({ uid: activeTabUid, tableId, widths })); + }; + const toggleBulkEditMode = () => { setIsBulkEditMode(!isBulkEditMode); }; @@ -114,11 +125,14 @@ const Headers = ({ collection }) => { Add request headers that will be sent with every request in this collection. handleColumnWidthsChange('collection-headers', widths)} />
handleColumnWidthsChange('folder-headers', widths)} />
{pathParams && pathParams.length > 0 ? ( {}} @@ -173,6 +189,8 @@ const QueryParams = ({ item, collection }) => { showCheckbox={false} showDelete={false} showAddRow={false} + columnWidths={pathParamsWidths} + onColumnWidthsChange={(widths) => handleColumnWidthsChange('path-params', widths)} /> ) : (
diff --git a/packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js b/packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js index 058651543..f23bafbae 100644 --- a/packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js +++ b/packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js @@ -1,9 +1,10 @@ import React, { useState, useCallback } from 'react'; import get from 'lodash/get'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useTheme } from 'providers/Theme'; import { moveRequestHeader, setRequestHeaders } from 'providers/ReduxStore/slices/collections'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; +import { updateTableColumnWidths } from 'providers/ReduxStore/slices/tabs'; import SingleLineEditor from 'components/SingleLineEditor'; import EditableTable from 'components/EditableTable'; import StyledWrapper from './StyledWrapper'; @@ -17,9 +18,19 @@ const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header); const RequestHeaders = ({ item, collection, addHeaderText }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); + const tabs = useSelector((state) => state.tabs.tabs); + const activeTabUid = useSelector((state) => state.tabs.activeTabUid); const headers = item.draft ? get(item, 'draft.request.headers') : get(item, 'request.headers'); const [isBulkEditMode, setIsBulkEditMode] = useState(false); + // Get column widths from Redux + const focusedTab = tabs?.find((t) => t.uid === activeTabUid); + const headersWidths = focusedTab?.tableColumnWidths?.['request-headers'] || {}; + + const handleColumnWidthsChange = (tableId, widths) => { + dispatch(updateTableColumnWidths({ uid: activeTabUid, tableId, widths })); + }; + const onSave = () => dispatch(saveRequest(item.uid, collection.uid)); const handleRun = () => dispatch(sendRequest(item, collection.uid)); @@ -123,6 +134,7 @@ const RequestHeaders = ({ item, collection, addHeaderText }) => { return ( { getRowError={getRowError} reorderable={true} onReorder={handleHeaderDrag} + columnWidths={headersWidths} + onColumnWidthsChange={(widths) => handleColumnWidthsChange('request-headers', widths)} />
- -
- ) : null} -
+ {item.type === 'graphql-request' ? ( +
+ (docExplorerRef.current = r)}> + + +
+ ) : null} + + ); }; diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFileBody/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFileBody/index.js index 19935a2f1..e980b1da5 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFileBody/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFileBody/index.js @@ -1,8 +1,9 @@ import React, { useState, useMemo, useCallback } from 'react'; import { get } from 'lodash'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useTheme } from 'providers/Theme'; import { updateResponseExampleFileBodyParams } from 'providers/ReduxStore/slices/collections'; +import { updateTableColumnWidths } from 'providers/ReduxStore/slices/tabs'; import mime from 'mime-types'; import path from 'utils/common/path'; import EditableTable from 'components/EditableTable'; @@ -14,6 +15,16 @@ import RadioButton from 'components/RadioButton'; const ResponseExampleFileBody = ({ item, collection, exampleUid, editMode = false }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); + const tabs = useSelector((state) => state.tabs.tabs); + const activeTabUid = useSelector((state) => state.tabs.activeTabUid); + + // Get column widths from Redux + const focusedTab = tabs?.find((t) => t.uid === activeTabUid); + const fileBodyWidths = focusedTab?.tableColumnWidths?.['example-file-body'] || {}; + + const handleColumnWidthsChange = (tableId, widths) => { + dispatch(updateTableColumnWidths({ uid: activeTabUid, tableId, widths })); + }; // Get file data from the specific example const params = useMemo(() => { @@ -180,6 +191,9 @@ const ResponseExampleFileBody = ({ item, collection, exampleUid, editMode = fals return ( handleColumnWidthsChange('example-file-body', widths)} columns={columns} rows={params || []} onChange={handleParamsChange} diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFormUrlEncodedParams/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFormUrlEncodedParams/index.js index c3a2ee4e1..246fab3c2 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFormUrlEncodedParams/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFormUrlEncodedParams/index.js @@ -1,8 +1,9 @@ import React, { useMemo, useCallback } from 'react'; import get from 'lodash/get'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useTheme } from 'providers/Theme'; import { updateResponseExampleFormUrlEncodedParams } from 'providers/ReduxStore/slices/collections'; +import { updateTableColumnWidths } from 'providers/ReduxStore/slices/tabs'; import EditableTable from 'components/EditableTable'; import MultiLineEditor from 'components/MultiLineEditor'; import StyledWrapper from './StyledWrapper'; @@ -10,6 +11,16 @@ import StyledWrapper from './StyledWrapper'; const ResponseExampleFormUrlEncodedParams = ({ item, collection, exampleUid, editMode = false }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); + const tabs = useSelector((state) => state.tabs.tabs); + const activeTabUid = useSelector((state) => state.tabs.activeTabUid); + + // Get column widths from Redux + const focusedTab = tabs?.find((t) => t.uid === activeTabUid); + const formUrlEncodedWidths = focusedTab?.tableColumnWidths?.['example-form-url-encoded'] || {}; + + const handleColumnWidthsChange = (tableId, widths) => { + dispatch(updateTableColumnWidths({ uid: activeTabUid, tableId, widths })); + }; const params = useMemo(() => { return item.draft @@ -87,6 +98,9 @@ const ResponseExampleFormUrlEncodedParams = ({ item, collection, exampleUid, edi return ( handleColumnWidthsChange('example-form-url-encoded', widths)} columns={columns} rows={params || []} onChange={handleParamsChange} diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleHeaders/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleHeaders/index.js index 1087cec62..d982134e9 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleHeaders/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleHeaders/index.js @@ -1,8 +1,9 @@ import React, { useState, useMemo, useCallback } from 'react'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useTheme } from 'providers/Theme'; import get from 'lodash/get'; import { moveResponseExampleRequestHeader, setResponseExampleRequestHeaders } from 'providers/ReduxStore/slices/collections'; +import { updateTableColumnWidths } from 'providers/ReduxStore/slices/tabs'; import EditableTable from 'components/EditableTable'; import SingleLineEditor from 'components/SingleLineEditor'; import BulkEditor from 'components/BulkEditor'; @@ -15,8 +16,18 @@ const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header); const ResponseExampleHeaders = ({ editMode, item, collection, exampleUid }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); + const tabs = useSelector((state) => state.tabs.tabs); + const activeTabUid = useSelector((state) => state.tabs.activeTabUid); const [isBulkEditMode, setIsBulkEditMode] = useState(false); + // Get column widths from Redux + const focusedTab = tabs?.find((t) => t.uid === activeTabUid); + const exampleHeadersWidths = focusedTab?.tableColumnWidths?.['example-headers'] || {}; + + const handleColumnWidthsChange = (tableId, widths) => { + dispatch(updateTableColumnWidths({ uid: activeTabUid, tableId, widths })); + }; + const headers = useMemo(() => { return item.draft ? get(item, 'draft.examples', []).find((e) => e.uid === exampleUid)?.request?.headers || [] @@ -132,6 +143,9 @@ const ResponseExampleHeaders = ({ editMode, item, collection, exampleUid }) => {
Headers
handleColumnWidthsChange('example-headers', widths)} columns={columns} rows={headers || []} onChange={handleHeadersChange} diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/index.js index 1b6d782ee..5afd01459 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/index.js @@ -1,10 +1,11 @@ import React, { useMemo, useCallback } from 'react'; import get from 'lodash/get'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useTheme } from 'providers/Theme'; import { IconUpload, IconX, IconFile } from '@tabler/icons'; import { updateResponseExampleMultipartFormParams } from 'providers/ReduxStore/slices/collections'; import { browseFiles } from 'providers/ReduxStore/slices/collections/actions'; +import { updateTableColumnWidths } from 'providers/ReduxStore/slices/tabs'; import mime from 'mime-types'; import path from 'utils/common/path'; import EditableTable from 'components/EditableTable'; @@ -16,6 +17,16 @@ import { isWindowsOS } from 'utils/common/platform'; const ResponseExampleMultipartFormParams = ({ item, collection, exampleUid, editMode = false }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); + const tabs = useSelector((state) => state.tabs.tabs); + const activeTabUid = useSelector((state) => state.tabs.activeTabUid); + + // Get column widths from Redux + const focusedTab = tabs?.find((t) => t.uid === activeTabUid); + const multipartFormWidths = focusedTab?.tableColumnWidths?.['example-multipart-form'] || {}; + + const handleColumnWidthsChange = (tableId, widths) => { + dispatch(updateTableColumnWidths({ uid: activeTabUid, tableId, widths })); + }; const params = useMemo(() => { return item.draft @@ -258,6 +269,9 @@ const ResponseExampleMultipartFormParams = ({ item, collection, exampleUid, edit return ( handleColumnWidthsChange('example-multipart-form', widths)} columns={columns} rows={params || []} onChange={handleParamsChange} diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleParams/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleParams/index.js index 3ae4b3fd3..556eae7b4 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleParams/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleParams/index.js @@ -1,8 +1,9 @@ import React, { useState, useMemo, useCallback } from 'react'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useTheme } from 'providers/Theme'; import get from 'lodash/get'; import { moveResponseExampleParam, setResponseExampleParams } from 'providers/ReduxStore/slices/collections'; +import { updateTableColumnWidths } from 'providers/ReduxStore/slices/tabs'; import EditableTable from 'components/EditableTable'; import SingleLineEditor from 'components/SingleLineEditor'; import BulkEditor from 'components/BulkEditor'; @@ -12,8 +13,19 @@ import StyledWrapper from './StyledWrapper'; const ResponseExampleParams = ({ editMode, item, collection, exampleUid }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); + const tabs = useSelector((state) => state.tabs.tabs); + const activeTabUid = useSelector((state) => state.tabs.activeTabUid); const [isBulkEditMode, setIsBulkEditMode] = useState(false); + // Get column widths from Redux + const focusedTab = tabs?.find((t) => t.uid === activeTabUid); + const exampleQueryParamsWidths = focusedTab?.tableColumnWidths?.['example-query-params'] || {}; + const examplePathParamsWidths = focusedTab?.tableColumnWidths?.['example-path-params'] || {}; + + const handleColumnWidthsChange = (tableId, widths) => { + dispatch(updateTableColumnWidths({ uid: activeTabUid, tableId, widths })); + }; + const params = useMemo(() => { return item.draft ? get(item, 'draft.examples', []).find((e) => e.uid === exampleUid)?.request?.params || [] @@ -185,6 +197,7 @@ const ResponseExampleParams = ({ editMode, item, collection, exampleUid }) => {
Query parameters
{ showAddRow={editMode} showDelete={editMode} disableCheckbox={!editMode} + columnWidths={exampleQueryParamsWidths} + onColumnWidthsChange={(widths) => handleColumnWidthsChange('example-query-params', widths)} /> {editMode && (
@@ -221,6 +236,7 @@ const ResponseExampleParams = ({ editMode, item, collection, exampleUid }) => {
{ showDelete={false} showAddRow={false} reorderable={false} + columnWidths={examplePathParamsWidths} + onColumnWidthsChange={(widths) => handleColumnWidthsChange('example-path-params', widths)} /> )} diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleResponsePane/ResponseExampleResponseHeaders/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleResponsePane/ResponseExampleResponseHeaders/index.js index e15a9ee2b..45c232f06 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleResponsePane/ResponseExampleResponseHeaders/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleResponsePane/ResponseExampleResponseHeaders/index.js @@ -1,8 +1,9 @@ import React, { useState, useMemo, useCallback } from 'react'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useTheme } from 'providers/Theme'; import get from 'lodash/get'; import { moveResponseExampleHeader, setResponseExampleHeaders, updateResponseExampleResponse } from 'providers/ReduxStore/slices/collections'; +import { updateTableColumnWidths } from 'providers/ReduxStore/slices/tabs'; import { getBodyType } from 'utils/responseBodyProcessor'; import EditableTable from 'components/EditableTable'; import SingleLineEditor from 'components/SingleLineEditor'; @@ -16,8 +17,18 @@ const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header); const ResponseExampleResponseHeaders = ({ editMode, item, collection, exampleUid }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); + const tabs = useSelector((state) => state.tabs.tabs); + const activeTabUid = useSelector((state) => state.tabs.activeTabUid); const [isBulkEditMode, setIsBulkEditMode] = useState(false); + // Get column widths from Redux + const focusedTab = tabs?.find((t) => t.uid === activeTabUid); + const responseHeadersWidths = focusedTab?.tableColumnWidths?.['example-response-headers'] || {}; + + const handleColumnWidthsChange = (tableId, widths) => { + dispatch(updateTableColumnWidths({ uid: activeTabUid, tableId, widths })); + }; + const headers = useMemo(() => { return item.draft ? get(item, 'draft.examples', []).find((e) => e.uid === exampleUid)?.response?.headers || [] : get(item, 'examples', []).find((e) => e.uid === exampleUid)?.response?.headers || []; }, [item, exampleUid]); @@ -170,6 +181,9 @@ const ResponseExampleResponseHeaders = ({ editMode, item, collection, exampleUid return ( handleColumnWidthsChange('example-response-headers', widths)} columns={columns} rows={headers || []} onChange={handleHeadersChange} diff --git a/packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js b/packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js index be6a15aca..1120ba769 100644 --- a/packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js +++ b/packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js @@ -82,7 +82,7 @@ const GrpcResponsePane = ({ item, collection }) => { return ; } case 'timeline': { - return ; + return ; } default: { return
404 | Not found
; @@ -152,7 +152,7 @@ const GrpcResponsePane = ({ item, collection }) => { {isLoading ? : null} {!item?.response ? ( focusedTab?.responsePaneTab === 'timeline' && requestTimeline?.length ? ( - + ) : null ) : ( <>{getTabPanel(focusedTab.responsePaneTab)} diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultFilter/index.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultFilter/index.js index 5320b4195..0f4d03c63 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultFilter/index.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultFilter/index.js @@ -1,21 +1,29 @@ import { IconFilter, IconX } from '@tabler/icons'; -import React, { useMemo } from 'react'; -import { useRef } from 'react'; -import { useState } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; import { Tooltip as ReactInfotip } from 'react-tooltip'; -const QueryResultFilter = ({ filter, onChange, mode }) => { +const QueryResultFilter = ({ filter, filterExpanded, onChange, onExpandChange, mode }) => { const inputRef = useRef(null); - const [isExpanded, toggleExpand] = useState(false); + const [isExpanded, setIsExpanded] = useState(filterExpanded || false); const handleFilterClick = () => { - // Toggle filter search bar - toggleExpand(!isExpanded); - // Reset filter search input - onChange({ target: { value: '' } }); - // Reset input value - if (inputRef?.current) { - inputRef.current.value = ''; + const newExpanded = !isExpanded; + setIsExpanded(newExpanded); + // Reset filter search input when closing + if (!newExpanded) { + onChange(''); + if (inputRef?.current) { + inputRef.current.value = ''; + } + } + if (onExpandChange) { + onExpandChange(newExpanded); + } + }; + + const handleInputChange = (e) => { + if (onChange) { + onChange(e.target.value); } }; @@ -53,6 +61,7 @@ const QueryResultFilter = ({ filter, onChange, mode }) => { type="text" name="response-filter" id="response-filter" + value={filter || ''} placeholder={placeholderText} autoComplete="off" autoCorrect="off" @@ -61,7 +70,7 @@ const QueryResultFilter = ({ filter, onChange, mode }) => { className={`block ml-14 p-2 py-1 transition-all duration-200 ease-in-out border border-gray-300 rounded-md ${ isExpanded ? 'w-full opacity-100 pointer-events-auto' : 'w-[0] opacity-0' }`} - onChange={onChange} + onChange={handleInputChange} />
{isExpanded ? : } diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js index b6f40be58..e936028b2 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js @@ -98,10 +98,13 @@ const QueryResult = ({ headers, error, selectedFormat, // one of the options in PREVIEW_FORMAT_OPTIONS - selectedTab // 'editor' or 'preview' + selectedTab, // 'editor' or 'preview' + filter, + filterExpanded, + onFilterChange, + onFilterExpandChange }) => { const contentType = getContentType(headers); - const [filter, setFilter] = useState(null); const [showLargeResponse, setShowLargeResponse] = useState(false); const { displayedTheme } = useTheme(); @@ -134,9 +137,11 @@ const QueryResult = ({ [data, dataBuffer, selectedFormat, filter, isLargeResponse, showLargeResponse] ); - const debouncedResultFilterOnChange = debounce((e) => { - setFilter(e.target.value); - }, 250); + const handleFilterChange = (value) => { + if (onFilterChange) { + onFilterChange(value); + } + }; const previewMode = useMemo(() => { // Derive preview mode based on selected format @@ -213,7 +218,13 @@ const QueryResult = ({ />
{queryFilterEnabled && ( - + )} diff --git a/packages/bruno-app/src/components/ResponsePane/Timeline/GrpcTimelineItem/index.js b/packages/bruno-app/src/components/ResponsePane/Timeline/GrpcTimelineItem/index.js index 0841b0977..014b7e131 100644 --- a/packages/bruno-app/src/components/ResponsePane/Timeline/GrpcTimelineItem/index.js +++ b/packages/bruno-app/src/components/ResponsePane/Timeline/GrpcTimelineItem/index.js @@ -13,6 +13,7 @@ import { IconSend } from '@tabler/icons'; import StyledWrapper from './StyledWrapper'; +import { usePersistedState } from 'hooks/usePersistedState/index'; // Event type display names const EventTypeNames = { @@ -26,9 +27,12 @@ const EventTypeNames = { cancel: 'Cancelled' }; -const GrpcTimelineItem = ({ timestamp, request, response, eventType, eventData, item }) => { - const [isCollapsed, setIsCollapsed] = useState(true); - const toggleCollapse = () => setIsCollapsed((prev) => !prev); +const GrpcTimelineItem = ({ timestamp, request, response, eventType, collection, eventData, item }) => { + const [isExpanded, onToggleExpand] = usePersistedState({ + key: `grpc-timeline-${timestamp}`, + default: false + }); + const toggleCollapse = () => onToggleExpand(!isExpanded); // Use requestSent if available, otherwise fall back to request const effectiveRequest = item.requestSent || request || item.request || {}; @@ -247,7 +251,7 @@ const GrpcTimelineItem = ({ timestamp, request, response, eventType, eventData, return (
- {isCollapsed ? : } + {!isExpanded ? : }
{eventIcon}
@@ -272,7 +276,7 @@ const GrpcTimelineItem = ({ timestamp, request, response, eventType, eventData,
{url}
{/* Expanded content - only show for non-status items */} - {!isCollapsed && renderEventContent()} + {isExpanded && renderEventContent()} ); }; diff --git a/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/index.js b/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/index.js index 671909414..030ba6fb0 100644 --- a/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/index.js +++ b/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/index.js @@ -7,10 +7,14 @@ import Method from './Common/Method/index'; import Status from './Common/Status/index'; import { RelativeTime } from './Common/Time/index'; import StyledWrapper from './StyledWrapper'; +import { usePersistedState } from 'hooks/usePersistedState/index'; const TimelineItem = ({ timestamp, request, response, item, collection, isOauth2, hideTimestamp = false }) => { const { theme } = useTheme(); - const [isCollapsed, _toggleCollapse] = useState(false); + const [isCollapsed, _toggleCollapse] = usePersistedState({ + key: `timeline-${timestamp}`, + default: false + }); const [activeTab, setActiveTab] = useState('request'); const toggleCollapse = () => _toggleCollapse((prev) => !prev); const { method, status, statusCode, statusText, url = '' } = request || {}; diff --git a/packages/bruno-app/src/components/ResponsePane/Timeline/index.js b/packages/bruno-app/src/components/ResponsePane/Timeline/index.js index d371dcc04..605f6f232 100644 --- a/packages/bruno-app/src/components/ResponsePane/Timeline/index.js +++ b/packages/bruno-app/src/components/ResponsePane/Timeline/index.js @@ -43,7 +43,7 @@ const getEffectiveAuthSource = (collection, item) => { return effectiveSource; }; -const Timeline = ({ collection, item }) => { +const Timeline = ({ collection, item, activeTabUid }) => { // Get the effective auth source if auth mode is inherit const authSource = getEffectiveAuthSource(collection, item); const isGrpcRequest = item.type === 'grpc-request' || item.type === 'ws-request'; diff --git a/packages/bruno-app/src/components/ResponsePane/WsResponsePane/index.js b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/index.js index 5722a4166..dd86e7aec 100644 --- a/packages/bruno-app/src/components/ResponsePane/WsResponsePane/index.js +++ b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/index.js @@ -71,7 +71,7 @@ const WSResponsePane = ({ item, collection }) => { return ; } case 'timeline': { - return ; + return ; } default: { return
404 | Not found
; @@ -141,7 +141,7 @@ const WSResponsePane = ({ item, collection }) => { {isLoading ? : null} {!item?.response ? ( focusedTab?.responsePaneTab === 'timeline' && requestTimeline?.length ? ( - + ) : null ) : ( <>{getTabPanel(focusedTab.responsePaneTab)} diff --git a/packages/bruno-app/src/components/ResponsePane/index.js b/packages/bruno-app/src/components/ResponsePane/index.js index 617f862dc..c00e890f9 100644 --- a/packages/bruno-app/src/components/ResponsePane/index.js +++ b/packages/bruno-app/src/components/ResponsePane/index.js @@ -1,7 +1,7 @@ import React, { useState, useEffect, useMemo, useRef, useCallback } from 'react'; import find from 'lodash/find'; import { useDispatch, useSelector } from 'react-redux'; -import { updateResponsePaneTab, updateResponseFormat, updateResponseViewTab } from 'providers/ReduxStore/slices/tabs'; +import { updateResponsePaneTab, updateResponseFormat, updateResponseViewTab, updateResponseFilter, updateResponseFilterExpanded } from 'providers/ReduxStore/slices/tabs'; import QueryResult from './QueryResult'; import Overlay from './Overlay'; import Placeholder from './Placeholder'; @@ -168,6 +168,10 @@ const ResponsePane = ({ item, collection }) => { key={item.filename} selectedFormat={selectedFormat} selectedTab={selectedViewTab} + filter={focusedTab?.responseFilter} + filterExpanded={focusedTab?.responseFilterExpanded} + onFilterChange={(value) => dispatch(updateResponseFilter({ uid: activeTabUid, responseFilter: value }))} + onFilterExpandChange={(expanded) => dispatch(updateResponseFilterExpanded({ uid: activeTabUid, responseFilterExpanded: expanded }))} /> ); } @@ -175,7 +179,7 @@ const ResponsePane = ({ item, collection }) => { return ; } case 'timeline': { - return ; + return ; } case 'tests': { return ( @@ -313,6 +317,7 @@ const ResponsePane = ({ item, collection }) => { ) : null ) : ( diff --git a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js index a123798f3..369377d30 100644 --- a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js +++ b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js @@ -40,6 +40,7 @@ const EnvironmentVariables = ({ environment, setIsModified, collection, searchQu return ( (''); + +export function usePersistenceScope(): string { + return useContext(ScopedPersistedContext); +} + +export function ScopedPersistenceProvider({ scope, children }: { scope: string; children: ReactNode }) { + return {children}; +} + +export function clearPersistedScope(scope: string) { + const prefix = `persisted::${scope}::`; + Object.keys(localStorage) + .filter((k) => k.startsWith(prefix)) + .forEach((k) => localStorage.removeItem(k)); +} diff --git a/packages/bruno-app/src/hooks/usePersistedState/index.ts b/packages/bruno-app/src/hooks/usePersistedState/index.ts new file mode 100644 index 000000000..652210701 --- /dev/null +++ b/packages/bruno-app/src/hooks/usePersistedState/index.ts @@ -0,0 +1,48 @@ +import type { Dispatch, SetStateAction } from 'react'; +import { useCallback, useState, useEffect } from 'react'; +import { usePersistenceScope } from './PersistedScopeProvider'; + +type Options = { + key: string; + default: T; +}; + +export { ScopedPersistenceProvider as PersistedScopeProvider, clearPersistedScope } from './PersistedScopeProvider'; + +export function usePersistedState(options: Options): [T, Dispatch>] { + const scope = usePersistenceScope(); + const storageKey = scope ? `persisted::${scope}::${options.key}` : options.key; + + const [state, setState] = useState(options.default ?? undefined); + + useEffect(() => { + const raw = localStorage.getItem(storageKey); + const existingState = JSON.parse(raw); + + if (existingState !== undefined) { + setState(existingState); + } + + return; + }, [storageKey]); + + const onSet = useCallback( + (value: T | ((prev: T) => T)) => { + let _next: T; + if (typeof value === 'function') { + setState((prev) => { + _next = (value as (prev: T) => T)(prev); + localStorage.setItem(storageKey, JSON.stringify(_next)); + return _next; + }); + } else { + _next = value; + setState(_next); + localStorage.setItem(storageKey, JSON.stringify(_next)); + } + }, + [storageKey] + ); + + return [state, onSet]; +} 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 aaced8c05..90e600e6e 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -90,6 +90,7 @@ import { addTab } from 'providers/ReduxStore/slices/tabs'; import { updateSettingsSelectedTab } from './index'; import { saveGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments'; import { getTabToFocusForCurrentWorkspace } from 'providers/ReduxStore/slices/workspaces/getTabToFocusForCurrentWorkspace'; +import { clearPersistedScope } from 'hooks/usePersistedState/PersistedScopeProvider'; // generate a unique names const generateUniqueName = (originalName, existingItems, isFolder) => { @@ -3146,6 +3147,7 @@ export const closeTabs = ({ tabUids }) => async (dispatch, getState) => { // Find transient items and group by temp directory before closing tabs const transientByTempDir = {}; each(tabUids, (tabUid) => { + clearPersistedScope(tabUid); for (const collection of collections) { const item = findItemInCollection(collection, tabUid); if (item?.isTransient && item.pathname) { diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js b/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js index c47d2f7e8..22603fb04 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js @@ -88,7 +88,12 @@ export const tabsSlice = createSlice({ responsePaneScrollPosition: null, responseFormat: null, responseViewTab: null, + responseFilter: null, + responseFilterExpanded: false, + gqlDocsOpen: false, + tableColumnWidths: {}, scriptPaneTab: null, + docsEditing: false, type: type || 'request', ...(uid ? { folderUid: uid } : {}), preview: preview !== undefined @@ -182,6 +187,44 @@ export const tabsSlice = createSlice({ tab.responseViewTab = action.payload.responseViewTab; } }, + updateResponseFilter: (state, action) => { + const tab = find(state.tabs, (t) => t.uid === action.payload.uid); + + if (tab) { + tab.responseFilter = action.payload.responseFilter; + } + }, + updateResponseFilterExpanded: (state, action) => { + const tab = find(state.tabs, (t) => t.uid === action.payload.uid); + + if (tab) { + tab.responseFilterExpanded = action.payload.responseFilterExpanded; + } + }, + updateDocsEditing: (state, action) => { + const tab = find(state.tabs, (t) => t.uid === action.payload.uid); + + if (tab) { + tab.docsEditing = action.payload.docsEditing; + } + }, + updateGqlDocsOpen: (state, action) => { + const tab = find(state.tabs, (t) => t.uid === action.payload.uid); + + if (tab) { + tab.gqlDocsOpen = action.payload.gqlDocsOpen; + } + }, + updateTableColumnWidths: (state, action) => { + const tab = find(state.tabs, (t) => t.uid === action.payload.uid); + + if (tab) { + if (!tab.tableColumnWidths) { + tab.tableColumnWidths = {}; + } + tab.tableColumnWidths[action.payload.tableId] = action.payload.widths; + } + }, updateScriptPaneTab: (state, action) => { const tab = find(state.tabs, (t) => t.uid === action.payload.uid); @@ -283,6 +326,11 @@ export const { updateRequestBodyScrollPosition, updateResponseFormat, updateResponseViewTab, + updateResponseFilter, + updateResponseFilterExpanded, + updateDocsEditing, + updateGqlDocsOpen, + updateTableColumnWidths, updateScriptPaneTab, closeTabs, closeAllCollectionTabs,