mirror of
https://github.com/usebruno/bruno.git
synced 2025-05-05 23:42:58 +00:00
feat: folder sequencing (#4595)
Co-authored-by: Pooja Belaramani <pooja@usebruno.com> Co-authored-by: naman-bruno <naman@usebruno.com> Co-authored-by: lohit <lohit@usebruno.com>
This commit is contained in:
parent
526fcabffe
commit
38c307d6f1
62
package-lock.json
generated
62
package-lock.json
generated
@ -54,6 +54,7 @@
|
|||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||||
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/gen-mapping": "^0.3.5",
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
@ -1474,6 +1475,7 @@
|
|||||||
"version": "7.26.0",
|
"version": "7.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz",
|
||||||
"integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
|
"integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
@ -1504,6 +1506,7 @@
|
|||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@ -1521,6 +1524,7 @@
|
|||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@babel/generator": {
|
"node_modules/@babel/generator": {
|
||||||
@ -1800,6 +1804,7 @@
|
|||||||
"version": "7.26.0",
|
"version": "7.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz",
|
||||||
"integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==",
|
"integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/template": "^7.25.9",
|
"@babel/template": "^7.25.9",
|
||||||
@ -7784,6 +7789,7 @@
|
|||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||||
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
|
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/lodash": {
|
"node_modules/@types/lodash": {
|
||||||
@ -7806,6 +7812,7 @@
|
|||||||
"version": "12.2.3",
|
"version": "12.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz",
|
||||||
"integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==",
|
"integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/linkify-it": "*",
|
"@types/linkify-it": "*",
|
||||||
@ -7816,6 +7823,7 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
|
||||||
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/ms": {
|
"node_modules/@types/ms": {
|
||||||
@ -11060,6 +11068,7 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/cookie": {
|
"node_modules/cookie": {
|
||||||
@ -12670,6 +12679,7 @@
|
|||||||
"version": "0.1.13",
|
"version": "0.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -13637,6 +13647,7 @@
|
|||||||
"version": "1.0.0-beta.2",
|
"version": "1.0.0-beta.2",
|
||||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||||
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
|
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@ -24196,7 +24207,7 @@
|
|||||||
"version": "4.9.5",
|
"version": "4.9.5",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@ -26303,9 +26314,7 @@
|
|||||||
"@rollup/plugin-typescript": "^12.1.2",
|
"@rollup/plugin-typescript": "^12.1.2",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"babel-jest": "^29.7.0",
|
"babel-jest": "^29.7.0",
|
||||||
"cheerio": "^1.0.0",
|
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"playwright": "^1.52.0",
|
|
||||||
"rollup": "3.29.5",
|
"rollup": "3.29.5",
|
||||||
"rollup-plugin-dts": "^5.0.0",
|
"rollup-plugin-dts": "^5.0.0",
|
||||||
"rollup-plugin-peer-deps-external": "^2.2.4",
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
||||||
@ -26815,21 +26824,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/bruno-common/node_modules/fsevents": {
|
|
||||||
"version": "2.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
|
||||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/bruno-common/node_modules/ms": {
|
"packages/bruno-common/node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@ -26837,38 +26831,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"packages/bruno-common/node_modules/playwright": {
|
|
||||||
"version": "1.52.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz",
|
|
||||||
"integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"playwright-core": "1.52.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"playwright": "cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"fsevents": "2.3.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/bruno-common/node_modules/playwright-core": {
|
|
||||||
"version": "1.52.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz",
|
|
||||||
"integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"bin": {
|
|
||||||
"playwright-core": "cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/bruno-common/node_modules/typescript": {
|
"packages/bruno-common/node_modules/typescript": {
|
||||||
"version": "5.8.3",
|
"version": "5.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||||
|
@ -72,7 +72,7 @@ const Info = ({ collection }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{showShareCollectionModal && <ShareCollection collection={collection} onClose={handleToggleShowShareCollectionModal(false)} />}
|
{showShareCollectionModal && <ShareCollection collectionUid={collection.uid} collectionPathname={collection.pathname} onClose={handleToggleShowShareCollectionModal(false)} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,7 +28,7 @@ const FolderSettings = ({ collection, folder }) => {
|
|||||||
tab = folderLevelSettingsSelectedTab[folder?.uid];
|
tab = folderLevelSettingsSelectedTab[folder?.uid];
|
||||||
}
|
}
|
||||||
|
|
||||||
const folderRoot = collection?.items.find((item) => item.uid === folder?.uid)?.root;
|
const folderRoot = folder?.root;
|
||||||
const hasScripts = folderRoot?.request?.script?.res || folderRoot?.request?.script?.req;
|
const hasScripts = folderRoot?.request?.script?.res || folderRoot?.request?.script?.req;
|
||||||
const hasTests = folderRoot?.request?.tests;
|
const hasTests = folderRoot?.request?.tests;
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{generateCodeItemModalOpen && (
|
{generateCodeItemModalOpen && (
|
||||||
<GenerateCodeItem collection={collection} item={item} onClose={() => setGenerateCodeItemModalOpen(false)} />
|
<GenerateCodeItem collectionUid={collection.uid} item={item} onClose={() => setGenerateCodeItemModalOpen(false)} />
|
||||||
)}
|
)}
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
|
@ -261,13 +261,14 @@ function RequestTabMenu({ onDropdownCreate, collectionRequestTabs, tabIndex, col
|
|||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{showAddNewRequestModal && (
|
{showAddNewRequestModal && (
|
||||||
<NewRequest collection={collection} onClose={() => setShowAddNewRequestModal(false)} />
|
<NewRequest collectionUid={collection.uid} collectionPathname={collection.pathname} onClose={() => setShowAddNewRequestModal(false)} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showCloneRequestModal && (
|
{showCloneRequestModal && (
|
||||||
<CloneCollectionItem
|
<CloneCollectionItem
|
||||||
item={currentTabItem}
|
item={currentTabItem}
|
||||||
collection={collection}
|
collectionUid={collection.uid}
|
||||||
|
collectionPathname={collection.pathname}
|
||||||
onClose={() => setShowCloneRequestModal(false)}
|
onClose={() => setShowCloneRequestModal(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -79,7 +79,7 @@ const RequestTabs = () => {
|
|||||||
return (
|
return (
|
||||||
<StyledWrapper className={getRootClassname()}>
|
<StyledWrapper className={getRootClassname()}>
|
||||||
{newRequestModalOpen && (
|
{newRequestModalOpen && (
|
||||||
<NewRequest collection={activeCollection} onClose={() => setNewRequestModalOpen(false)} />
|
<NewRequest collectionUid={activeCollection?.uid} collectionPathname={activeCollection?.pathname} onClose={() => setNewRequestModalOpen(false)} />
|
||||||
)}
|
)}
|
||||||
{collectionRequestTabs && collectionRequestTabs.length ? (
|
{collectionRequestTabs && collectionRequestTabs.length ? (
|
||||||
<>
|
<>
|
||||||
|
@ -7,8 +7,11 @@ import exportBrunoCollection from 'utils/collections/export';
|
|||||||
import exportPostmanCollection from 'utils/exporters/postman-collection';
|
import exportPostmanCollection from 'utils/exporters/postman-collection';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import { transformCollectionToSaveToExportAsFile } from 'utils/collections/index';
|
import { transformCollectionToSaveToExportAsFile } from 'utils/collections/index';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { findCollectionByUid } from 'utils/collections/index';
|
||||||
|
|
||||||
const ShareCollection = ({ onClose, collection }) => {
|
const ShareCollection = ({ onClose, collectionUid }) => {
|
||||||
|
const collection = useSelector(state => findCollectionByUid(state.collections.collections, collectionUid));
|
||||||
const handleExportBrunoCollection = () => {
|
const handleExportBrunoCollection = () => {
|
||||||
const collectionCopy = cloneDeep(collection);
|
const collectionCopy = cloneDeep(collection);
|
||||||
exportBrunoCollection(transformCollectionToSaveToExportAsFile(collectionCopy));
|
exportBrunoCollection(transformCollectionToSaveToExportAsFile(collectionCopy));
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useRef, useEffect } from 'react';
|
import React, { useRef, useEffect } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { browseDirectory } from 'providers/ReduxStore/slices/collections/actions';
|
import { browseDirectory } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
@ -11,11 +11,13 @@ import Help from 'components/Help';
|
|||||||
import PathDisplay from 'components/PathDisplay';
|
import PathDisplay from 'components/PathDisplay';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { IconArrowBackUp, IconEdit } from "@tabler/icons";
|
import { IconArrowBackUp, IconEdit } from "@tabler/icons";
|
||||||
|
import { findCollectionByUid } from 'utils/collections/index';
|
||||||
|
|
||||||
const CloneCollection = ({ onClose, collection }) => {
|
const CloneCollection = ({ onClose, collectionUid }) => {
|
||||||
const inputRef = useRef();
|
const inputRef = useRef();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [isEditing, toggleEditing] = useState(false);
|
const [isEditing, toggleEditing] = useState(false);
|
||||||
|
const collection = useSelector(state => findCollectionByUid(state.collections.collections, collectionUid));
|
||||||
const { name } = collection;
|
const { name } = collection;
|
||||||
|
|
||||||
const formik = useFormik({
|
const formik = useFormik({
|
||||||
@ -46,7 +48,7 @@ const CloneCollection = ({ onClose, collection }) => {
|
|||||||
values.collectionName,
|
values.collectionName,
|
||||||
values.collectionFolderName,
|
values.collectionFolderName,
|
||||||
values.collectionLocation,
|
values.collectionLocation,
|
||||||
collection.pathname
|
collection?.pathname
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -15,7 +15,7 @@ import Portal from 'components/Portal';
|
|||||||
import Dropdown from 'components/Dropdown';
|
import Dropdown from 'components/Dropdown';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const CloneCollectionItem = ({ collection, item, onClose }) => {
|
const CloneCollectionItem = ({ collectionUid, collectionPathname, item, onClose }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const isFolder = isItemAFolder(item);
|
const isFolder = isItemAFolder(item);
|
||||||
const inputRef = useRef();
|
const inputRef = useRef();
|
||||||
@ -49,7 +49,7 @@ const CloneCollectionItem = ({ collection, item, onClose }) => {
|
|||||||
.test('not-reserved', `The file names "collection" and "folder" are reserved in bruno`, value => !['collection', 'folder'].includes(value))
|
.test('not-reserved', `The file names "collection" and "folder" are reserved in bruno`, value => !['collection', 'folder'].includes(value))
|
||||||
}),
|
}),
|
||||||
onSubmit: (values) => {
|
onSubmit: (values) => {
|
||||||
dispatch(cloneItem(values.name, values.filename, item.uid, collection.uid))
|
dispatch(cloneItem(values.name, values.filename, item.uid, collectionUid))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success('Request cloned!');
|
toast.success('Request cloned!');
|
||||||
onClose();
|
onClose();
|
||||||
@ -172,8 +172,7 @@ const CloneCollectionItem = ({ collection, item, onClose }) => {
|
|||||||
) : (
|
) : (
|
||||||
<div className='relative flex flex-row gap-1 items-center justify-between'>
|
<div className='relative flex flex-row gap-1 items-center justify-between'>
|
||||||
<PathDisplay
|
<PathDisplay
|
||||||
collection={collection}
|
dirName={path.relative(collectionPathname, path.dirname(item?.pathname))}
|
||||||
dirName={path.relative(collection?.pathname, path.dirname(item?.pathname))}
|
|
||||||
baseName={formik.values.filename}
|
baseName={formik.values.filename}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,11 +7,11 @@ import { deleteItem } from 'providers/ReduxStore/slices/collections/actions';
|
|||||||
import { recursivelyGetAllItemUids } from 'utils/collections';
|
import { recursivelyGetAllItemUids } from 'utils/collections';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const DeleteCollectionItem = ({ onClose, item, collection }) => {
|
const DeleteCollectionItem = ({ onClose, item, collectionUid }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const isFolder = isItemAFolder(item);
|
const isFolder = isItemAFolder(item);
|
||||||
const onConfirm = () => {
|
const onConfirm = () => {
|
||||||
dispatch(deleteItem(item.uid, collection.uid)).then(() => {
|
dispatch(deleteItem(item.uid, collectionUid)).then(() => {
|
||||||
|
|
||||||
if (isFolder) {
|
if (isFolder) {
|
||||||
// close all tabs that belong to the folder
|
// close all tabs that belong to the folder
|
||||||
|
@ -10,9 +10,11 @@ import { getLanguages } from 'utils/codegenerator/targets';
|
|||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { getGlobalEnvironmentVariables } from 'utils/collections/index';
|
import { getGlobalEnvironmentVariables } from 'utils/collections/index';
|
||||||
|
|
||||||
const GenerateCodeItem = ({ collection, item, onClose }) => {
|
const GenerateCodeItem = ({ collectionUid, item, onClose }) => {
|
||||||
const languages = getLanguages();
|
const languages = getLanguages();
|
||||||
|
|
||||||
|
const collection = useSelector(state => state.collections.collections?.find(c => c.uid === collectionUid));
|
||||||
|
|
||||||
const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);
|
const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);
|
||||||
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
|
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ import Portal from 'components/Portal';
|
|||||||
import Dropdown from 'components/Dropdown';
|
import Dropdown from 'components/Dropdown';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const RenameCollectionItem = ({ collection, item, onClose }) => {
|
const RenameCollectionItem = ({ collectionUid, collectionPathname, item, onClose }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const isFolder = isItemAFolder(item);
|
const isFolder = isItemAFolder(item);
|
||||||
const inputRef = useRef();
|
const inputRef = useRef();
|
||||||
@ -57,13 +57,13 @@ const RenameCollectionItem = ({ collection, item, onClose }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!isFolder && item.draft) {
|
if (!isFolder && item.draft) {
|
||||||
await dispatch(saveRequest(item.uid, collection.uid, true));
|
await dispatch(saveRequest(item.uid, collectionUid, true));
|
||||||
}
|
}
|
||||||
const { name: newName, filename: newFilename } = values;
|
const { name: newName, filename: newFilename } = values;
|
||||||
try {
|
try {
|
||||||
let renameConfig = {
|
let renameConfig = {
|
||||||
itemUid: item.uid,
|
itemUid: item.uid,
|
||||||
collectionUid: collection.uid,
|
collectionUid,
|
||||||
};
|
};
|
||||||
renameConfig['newName'] = newName;
|
renameConfig['newName'] = newName;
|
||||||
if (itemFilename !== newFilename) {
|
if (itemFilename !== newFilename) {
|
||||||
@ -191,8 +191,7 @@ const RenameCollectionItem = ({ collection, item, onClose }) => {
|
|||||||
) : (
|
) : (
|
||||||
<div className='relative flex flex-row gap-1 items-center justify-between'>
|
<div className='relative flex flex-row gap-1 items-center justify-between'>
|
||||||
<PathDisplay
|
<PathDisplay
|
||||||
collection={collection}
|
dirName={path.relative(collectionPathname, path.dirname(item?.pathname))}
|
||||||
dirName={path.relative(collection?.pathname, path.dirname(item?.pathname))}
|
|
||||||
baseName={formik.values.filename}
|
baseName={formik.values.filename}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,16 +2,18 @@ import React from 'react';
|
|||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { uuid } from 'utils/common';
|
import { uuid } from 'utils/common';
|
||||||
import Modal from 'components/Modal';
|
import Modal from 'components/Modal';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||||
import { runCollectionFolder } from 'providers/ReduxStore/slices/collections/actions';
|
import { runCollectionFolder } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import { flattenItems } from 'utils/collections';
|
import { flattenItems } from 'utils/collections';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import { areItemsLoading } from 'utils/collections';
|
import { areItemsLoading } from 'utils/collections';
|
||||||
|
|
||||||
const RunCollectionItem = ({ collection, item, onClose }) => {
|
const RunCollectionItem = ({ collectionUid, item, onClose }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const collection = useSelector(state => state.collections.collections?.find(c => c.uid === collectionUid));
|
||||||
|
|
||||||
const onSubmit = (recursive) => {
|
const onSubmit = (recursive) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
addTab({
|
addTab({
|
||||||
@ -34,8 +36,6 @@ const RunCollectionItem = ({ collection, item, onClose }) => {
|
|||||||
const recursiveRunLength = getRequestsCount(flattenedItems);
|
const recursiveRunLength = getRequestsCount(flattenedItems);
|
||||||
|
|
||||||
const isFolderLoading = areItemsLoading(item);
|
const isFolderLoading = areItemsLoading(item);
|
||||||
console.log(item);
|
|
||||||
console.log(isFolderLoading);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper>
|
<StyledWrapper>
|
||||||
|
@ -22,6 +22,65 @@ const Wrapper = styled.div`
|
|||||||
height: 1.875rem;
|
height: 1.875rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
/* Common styles for drop indicators */
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: ${(props) => props.theme.dragAndDrop.border};
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drop target styles */
|
||||||
|
&.drop-target {
|
||||||
|
background-color: ${(props) => props.theme.dragAndDrop.hoverBg};
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.drop-target-above {
|
||||||
|
&::before {
|
||||||
|
opacity: 1;
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.drop-target-below {
|
||||||
|
&::after {
|
||||||
|
opacity: 1;
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inside drop target style */
|
||||||
|
&.drop-target {
|
||||||
|
&::before {
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 100%;
|
||||||
|
opacity: 1;
|
||||||
|
background: ${(props) => props.theme.dragAndDrop.hoverBg};
|
||||||
|
border: ${(props) => props.theme.dragAndDrop.borderStyle} ${(props) => props.theme.dragAndDrop.border};
|
||||||
|
// border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.rotate-90 {
|
.rotate-90 {
|
||||||
transform: rotateZ(90deg);
|
transform: rotateZ(90deg);
|
||||||
@ -45,6 +104,20 @@ const Wrapper = styled.div`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.item-target {
|
||||||
|
background: #ccc3;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.item-seperator {
|
||||||
|
.seperator {
|
||||||
|
bottom: 0px;
|
||||||
|
position: absolute;
|
||||||
|
height: 3px;
|
||||||
|
width: 100%;
|
||||||
|
background: #ccc3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.item-focused-in-tab {
|
&.item-focused-in-tab {
|
||||||
background: ${(props) => props.theme.sidebar.collection.item.bg};
|
background: ${(props) => props.theme.sidebar.collection.item.bg};
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useRef, forwardRef, useEffect } from 'react';
|
import React, { useState, useRef, forwardRef } from 'react';
|
||||||
import range from 'lodash/range';
|
import range from 'lodash/range';
|
||||||
import filter from 'lodash/filter';
|
import filter from 'lodash/filter';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
@ -6,7 +6,7 @@ import { useDrag, useDrop } from 'react-dnd';
|
|||||||
import { IconChevronRight, IconDots } from '@tabler/icons';
|
import { IconChevronRight, IconDots } from '@tabler/icons';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { addTab, focusTab, makeTabPermanent } from 'providers/ReduxStore/slices/tabs';
|
import { addTab, focusTab, makeTabPermanent } from 'providers/ReduxStore/slices/tabs';
|
||||||
import { moveItem, showInFolder, sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
import { handleCollectionItemDrop, moveItem, sendRequest, showInFolder, updateItemsSequences } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import { collectionFolderClicked } from 'providers/ReduxStore/slices/collections';
|
import { collectionFolderClicked } from 'providers/ReduxStore/slices/collections';
|
||||||
import Dropdown from 'components/Dropdown';
|
import Dropdown from 'components/Dropdown';
|
||||||
import NewRequest from 'components/Sidebar/NewRequest';
|
import NewRequest from 'components/Sidebar/NewRequest';
|
||||||
@ -26,13 +26,21 @@ import NetworkError from 'components/ResponsePane/NetworkError/index';
|
|||||||
import CollectionItemInfo from './CollectionItemInfo/index';
|
import CollectionItemInfo from './CollectionItemInfo/index';
|
||||||
import CollectionItemIcon from './CollectionItemIcon';
|
import CollectionItemIcon from './CollectionItemIcon';
|
||||||
import { scrollToTheActiveTab } from 'utils/tabs';
|
import { scrollToTheActiveTab } from 'utils/tabs';
|
||||||
|
import { isTabForItemActive as isTabForItemActiveSelector, isTabForItemPresent as isTabForItemPresentSelector } from 'src/selectors/tab';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
const CollectionItem = ({ item, collection, searchText }) => {
|
const CollectionItem = ({ item, collectionUid, collectionPathname, searchText }) => {
|
||||||
const tabs = useSelector((state) => state.tabs.tabs);
|
const _isTabForItemActiveSelector = isTabForItemActiveSelector({ itemUid: item.uid });
|
||||||
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
|
const isTabForItemActive = useSelector(_isTabForItemActiveSelector, isEqual);
|
||||||
|
|
||||||
|
const _isTabForItemPresentSelector = isTabForItemPresentSelector({ itemUid: item.uid });
|
||||||
|
const isTabForItemPresent = useSelector(_isTabForItemPresentSelector, isEqual);
|
||||||
|
|
||||||
const isSidebarDragging = useSelector((state) => state.app.isDragging);
|
const isSidebarDragging = useSelector((state) => state.app.isDragging);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const collectionItemRef = useRef(null);
|
|
||||||
|
// We use a single ref for drag and drop.
|
||||||
|
const ref = useRef(null);
|
||||||
|
|
||||||
const [renameItemModalOpen, setRenameItemModalOpen] = useState(false);
|
const [renameItemModalOpen, setRenameItemModalOpen] = useState(false);
|
||||||
const [cloneItemModalOpen, setCloneItemModalOpen] = useState(false);
|
const [cloneItemModalOpen, setCloneItemModalOpen] = useState(false);
|
||||||
@ -44,9 +52,12 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
const [itemInfoModalOpen, setItemInfoModalOpen] = useState(false);
|
const [itemInfoModalOpen, setItemInfoModalOpen] = useState(false);
|
||||||
const hasSearchText = searchText && searchText?.trim()?.length;
|
const hasSearchText = searchText && searchText?.trim()?.length;
|
||||||
const itemIsCollapsed = hasSearchText ? false : item.collapsed;
|
const itemIsCollapsed = hasSearchText ? false : item.collapsed;
|
||||||
|
const isFolder = isItemAFolder(item);
|
||||||
|
|
||||||
|
const [dropType, setDropType] = useState(null); // 'adjacent' or 'inside'
|
||||||
|
|
||||||
const [{ isDragging }, drag] = useDrag({
|
const [{ isDragging }, drag] = useDrag({
|
||||||
type: `collection-item-${collection.uid}`,
|
type: `collection-item-${collectionUid}`,
|
||||||
item: item,
|
item: item,
|
||||||
collect: (monitor) => ({
|
collect: (monitor) => ({
|
||||||
isDragging: monitor.isDragging()
|
isDragging: monitor.isDragging()
|
||||||
@ -56,21 +67,51 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [{ isOver }, drop] = useDrop({
|
const determineDropType = (monitor) => {
|
||||||
accept: `collection-item-${collection.uid}`,
|
const hoverBoundingRect = ref.current?.getBoundingClientRect();
|
||||||
drop: (draggedItem) => {
|
const clientOffset = monitor.getClientOffset();
|
||||||
dispatch(moveItem(collection.uid, draggedItem.uid, item.uid));
|
if (!hoverBoundingRect || !clientOffset) return null;
|
||||||
|
|
||||||
|
const clientY = clientOffset.y - hoverBoundingRect.top;
|
||||||
|
const folderUpperThreshold = hoverBoundingRect.height * 0.35;
|
||||||
|
const fileUpperThreshold = hoverBoundingRect.height * 0.5;
|
||||||
|
|
||||||
|
if (isItemAFolder(item)) {
|
||||||
|
return clientY < folderUpperThreshold ? 'adjacent' : 'inside';
|
||||||
|
} else {
|
||||||
|
return clientY < fileUpperThreshold ? 'adjacent' : null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const [{ isOver, canDrop }, drop] = useDrop({
|
||||||
|
accept: `collection-item-${collectionUid}`,
|
||||||
|
hover: (draggedItem, monitor) => {
|
||||||
|
const { uid: targetItemUid } = item;
|
||||||
|
const { uid: draggedItemUid } = draggedItem;
|
||||||
|
|
||||||
|
if (draggedItemUid === targetItemUid) return;
|
||||||
|
|
||||||
|
const dropType = determineDropType(monitor);
|
||||||
|
setDropType(dropType);
|
||||||
},
|
},
|
||||||
canDrop: (draggedItem) => {
|
drop: async (draggedItem, monitor) => {
|
||||||
return draggedItem.uid !== item.uid;
|
const { uid: targetItemUid } = item;
|
||||||
|
const { uid: draggedItemUid } = draggedItem;
|
||||||
|
|
||||||
|
if (draggedItemUid === targetItemUid) return;
|
||||||
|
|
||||||
|
const dropType = determineDropType(monitor);
|
||||||
|
if (!dropType) return;
|
||||||
|
|
||||||
|
await dispatch(handleCollectionItemDrop({ targetItem: item, draggedItem, dropType, collectionUid }))
|
||||||
|
setDropType(null);
|
||||||
},
|
},
|
||||||
|
canDrop: (draggedItem) => draggedItem.uid !== item.uid,
|
||||||
collect: (monitor) => ({
|
collect: (monitor) => ({
|
||||||
isOver: monitor.isOver(),
|
isOver: monitor.isOver()
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
drag(drop(collectionItemRef));
|
|
||||||
|
|
||||||
const dropdownTippyRef = useRef();
|
const dropdownTippyRef = useRef();
|
||||||
const MenuIcon = forwardRef((props, ref) => {
|
const MenuIcon = forwardRef((props, ref) => {
|
||||||
return (
|
return (
|
||||||
@ -84,13 +125,15 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
'rotate-90': !itemIsCollapsed
|
'rotate-90': !itemIsCollapsed
|
||||||
});
|
});
|
||||||
|
|
||||||
const itemRowClassName = classnames('flex collection-item-name items-center', {
|
const itemRowClassName = classnames('flex collection-item-name relative items-center', {
|
||||||
'item-focused-in-tab': item.uid == activeTabUid,
|
'item-focused-in-tab': isTabForItemActive,
|
||||||
'item-hovered': isOver
|
'item-hovered': isOver && canDrop,
|
||||||
|
'drop-target': isOver && dropType === 'inside',
|
||||||
|
'drop-target-above': isOver && dropType === 'adjacent'
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleRun = async () => {
|
const handleRun = async () => {
|
||||||
dispatch(sendRequest(item, collection.uid)).catch((err) =>
|
dispatch(sendRequest(item, collectionUid)).catch((err) =>
|
||||||
toast.custom((t) => <NetworkError onClose={() => toast.dismiss(t.id)} />, {
|
toast.custom((t) => <NetworkError onClose={() => toast.dismiss(t.id)} />, {
|
||||||
duration: 5000
|
duration: 5000
|
||||||
})
|
})
|
||||||
@ -101,12 +144,10 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
if (event && event.detail != 1) return;
|
if (event && event.detail != 1) return;
|
||||||
//scroll to the active tab
|
//scroll to the active tab
|
||||||
setTimeout(scrollToTheActiveTab, 50);
|
setTimeout(scrollToTheActiveTab, 50);
|
||||||
|
|
||||||
const isRequest = isItemARequest(item);
|
const isRequest = isItemARequest(item);
|
||||||
|
|
||||||
if (isRequest) {
|
if (isRequest) {
|
||||||
dispatch(hideHomePage());
|
dispatch(hideHomePage());
|
||||||
if (itemIsOpenedInTabs(item, tabs)) {
|
if (isTabForItemPresent) {
|
||||||
dispatch(
|
dispatch(
|
||||||
focusTab({
|
focusTab({
|
||||||
uid: item.uid
|
uid: item.uid
|
||||||
@ -114,11 +155,10 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
addTab({
|
addTab({
|
||||||
uid: item.uid,
|
uid: item.uid,
|
||||||
collectionUid: collection.uid,
|
collectionUid: collectionUid,
|
||||||
requestPaneTab: getDefaultRequestPaneTab(item),
|
requestPaneTab: getDefaultRequestPaneTab(item),
|
||||||
type: 'request',
|
type: 'request',
|
||||||
})
|
})
|
||||||
@ -127,14 +167,14 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
dispatch(
|
dispatch(
|
||||||
addTab({
|
addTab({
|
||||||
uid: item.uid,
|
uid: item.uid,
|
||||||
collectionUid: collection.uid,
|
collectionUid: collectionUid,
|
||||||
type: 'folder-settings',
|
type: 'folder-settings',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
dispatch(
|
dispatch(
|
||||||
collectionFolderClicked({
|
collectionFolderClicked({
|
||||||
itemUid: item.uid,
|
itemUid: item.uid,
|
||||||
collectionUid: collection.uid
|
collectionUid: collectionUid
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -146,10 +186,10 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
dispatch(
|
dispatch(
|
||||||
collectionFolderClicked({
|
collectionFolderClicked({
|
||||||
itemUid: item.uid,
|
itemUid: item.uid,
|
||||||
collectionUid: collection.uid
|
collectionUid: collectionUid
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleRightClick = (event) => {
|
const handleRightClick = (event) => {
|
||||||
const _menuDropdown = dropdownTippyRef.current;
|
const _menuDropdown = dropdownTippyRef.current;
|
||||||
@ -164,7 +204,6 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
|
|
||||||
let indents = range(item.depth);
|
let indents = range(item.depth);
|
||||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||||
const isFolder = isItemAFolder(item);
|
|
||||||
|
|
||||||
const className = classnames('flex flex-col w-full', {
|
const className = classnames('flex flex-col w-full', {
|
||||||
'is-sidebar-dragging': isSidebarDragging
|
'is-sidebar-dragging': isSidebarDragging
|
||||||
@ -183,49 +222,14 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleDoubleClick = (event) => {
|
const handleDoubleClick = (event) => {
|
||||||
dispatch(makeTabPermanent({ uid: item.uid }))
|
dispatch(makeTabPermanent({ uid: item.uid }));
|
||||||
};
|
};
|
||||||
|
|
||||||
// we need to sort request items by seq property
|
// Sort items by their "seq" property.
|
||||||
const sortRequestItems = (items = []) => {
|
const sortItemsBySequence = (items = []) => {
|
||||||
return items.sort((a, b) => a.seq - b.seq);
|
return items.sort((a, b) => a.seq - b.seq);
|
||||||
};
|
};
|
||||||
|
|
||||||
// we need to sort folder items by name alphabetically
|
|
||||||
const sortFolderItems = (items = []) => {
|
|
||||||
return items.sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
};
|
|
||||||
const handleGenerateCode = (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
dropdownTippyRef.current.hide();
|
|
||||||
if (item?.request?.url !== '' || (item?.draft?.request?.url !== undefined && item?.draft?.request?.url !== '')) {
|
|
||||||
setGenerateCodeItemModalOpen(true);
|
|
||||||
} else {
|
|
||||||
toast.error('URL is required');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const viewFolderSettings = () => {
|
|
||||||
if (isItemAFolder(item)) {
|
|
||||||
if (itemIsOpenedInTabs(item, tabs)) {
|
|
||||||
dispatch(
|
|
||||||
focusTab({
|
|
||||||
uid: item.uid
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(
|
|
||||||
addTab({
|
|
||||||
uid: item.uid,
|
|
||||||
collectionUid: collection.uid,
|
|
||||||
type: 'folder-settings'
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleShowInFolder = () => {
|
const handleShowInFolder = () => {
|
||||||
dispatch(showInFolder(item.pathname)).catch((error) => {
|
dispatch(showInFolder(item.pathname)).catch((error) => {
|
||||||
console.error('Error opening the folder', error);
|
console.error('Error opening the folder', error);
|
||||||
@ -233,62 +237,89 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestItems = sortRequestItems(filter(item.items, (i) => isItemARequest(i)));
|
const folderItems = sortItemsBySequence(filter(item.items, (i) => isItemAFolder(i)));
|
||||||
const folderItems = sortFolderItems(filter(item.items, (i) => isItemAFolder(i)));
|
const requestItems = sortItemsBySequence(filter(item.items, (i) => isItemARequest(i)));
|
||||||
|
|
||||||
|
const handleGenerateCode = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
if (
|
||||||
|
(item?.request?.url !== '') ||
|
||||||
|
(item?.draft?.request?.url !== undefined && item?.draft?.request?.url !== '')
|
||||||
|
) {
|
||||||
|
setGenerateCodeItemModalOpen(true);
|
||||||
|
} else {
|
||||||
|
toast.error('URL is required');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const viewFolderSettings = () => {
|
||||||
|
if (isItemAFolder(item)) {
|
||||||
|
if (itemIsOpenedInTabs(item, tabs)) {
|
||||||
|
dispatch(focusTab({ uid: item.uid }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(
|
||||||
|
addTab({
|
||||||
|
uid: item.uid,
|
||||||
|
collectionUid,
|
||||||
|
type: 'folder-settings'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className={className}>
|
<StyledWrapper className={className}>
|
||||||
{renameItemModalOpen && (
|
{renameItemModalOpen && (
|
||||||
<RenameCollectionItem item={item} collection={collection} onClose={() => setRenameItemModalOpen(false)} />
|
<RenameCollectionItem item={item} collectionUid={collectionUid} collectionPathname={collectionPathname} onClose={() => setRenameItemModalOpen(false)} />
|
||||||
)}
|
)}
|
||||||
{cloneItemModalOpen && (
|
{cloneItemModalOpen && (
|
||||||
<CloneCollectionItem item={item} collection={collection} onClose={() => setCloneItemModalOpen(false)} />
|
<CloneCollectionItem item={item} collectionUid={collectionUid} collectionPathname={collectionPathname} onClose={() => setCloneItemModalOpen(false)} />
|
||||||
)}
|
)}
|
||||||
{deleteItemModalOpen && (
|
{deleteItemModalOpen && (
|
||||||
<DeleteCollectionItem item={item} collection={collection} onClose={() => setDeleteItemModalOpen(false)} />
|
<DeleteCollectionItem item={item} collectionUid={collectionUid} collectionPathname={collectionPathname} onClose={() => setDeleteItemModalOpen(false)} />
|
||||||
)}
|
)}
|
||||||
{newRequestModalOpen && (
|
{newRequestModalOpen && (
|
||||||
<NewRequest item={item} collection={collection} onClose={() => setNewRequestModalOpen(false)} />
|
<NewRequest item={item} collectionUid={collectionUid} collectionPathname={collectionPathname} onClose={() => setNewRequestModalOpen(false)} />
|
||||||
)}
|
)}
|
||||||
{newFolderModalOpen && (
|
{newFolderModalOpen && (
|
||||||
<NewFolder item={item} collection={collection} onClose={() => setNewFolderModalOpen(false)} />
|
<NewFolder item={item} collectionUid={collectionUid} collectionPathname={collectionPathname} onClose={() => setNewFolderModalOpen(false)} />
|
||||||
)}
|
)}
|
||||||
{runCollectionModalOpen && (
|
{runCollectionModalOpen && (
|
||||||
<RunCollectionItem collection={collection} item={item} onClose={() => setRunCollectionModalOpen(false)} />
|
<RunCollectionItem collectionUid={collectionUid} item={item} onClose={() => setRunCollectionModalOpen(false)} />
|
||||||
)}
|
)}
|
||||||
{generateCodeItemModalOpen && (
|
{generateCodeItemModalOpen && (
|
||||||
<GenerateCodeItem collection={collection} item={item} onClose={() => setGenerateCodeItemModalOpen(false)} />
|
<GenerateCodeItem collectionUid={collectionUid} item={item} onClose={() => setGenerateCodeItemModalOpen(false)} />
|
||||||
)}
|
)}
|
||||||
{itemInfoModalOpen && (
|
{itemInfoModalOpen && (
|
||||||
<CollectionItemInfo item={item} collection={collection} onClose={() => setItemInfoModalOpen(false)} />
|
<CollectionItemInfo item={item} onClose={() => setItemInfoModalOpen(false)} />
|
||||||
)}
|
)}
|
||||||
<div className={itemRowClassName} ref={collectionItemRef}>
|
<div
|
||||||
|
className={itemRowClassName}
|
||||||
|
ref={(node) => {
|
||||||
|
ref.current = node;
|
||||||
|
drag(drop(node));
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className="flex items-center h-full w-full">
|
<div className="flex items-center h-full w-full">
|
||||||
{indents && indents.length
|
{indents && indents.length
|
||||||
? indents.map((i) => {
|
? indents.map((i) => (
|
||||||
return (
|
<div
|
||||||
<div
|
onClick={handleClick}
|
||||||
onClick={handleClick}
|
onContextMenu={handleRightClick}
|
||||||
onContextMenu={handleRightClick}
|
onDoubleClick={handleDoubleClick}
|
||||||
onDoubleClick={handleDoubleClick}
|
className="indent-block"
|
||||||
className="indent-block"
|
key={i}
|
||||||
key={i}
|
style={{ width: 16, minWidth: 16, height: '100%' }}
|
||||||
style={{
|
>
|
||||||
width: 16,
|
{/* Indent */}
|
||||||
minWidth: 16,
|
</div>
|
||||||
height: '100%'
|
))
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Indent */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: null}
|
: null}
|
||||||
<div
|
<div
|
||||||
className="flex flex-grow items-center h-full overflow-hidden"
|
className="flex flex-grow items-center h-full overflow-hidden"
|
||||||
style={{
|
style={{ paddingLeft: 8 }}
|
||||||
paddingLeft: 8
|
|
||||||
}}
|
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onContextMenu={handleRightClick}
|
onContextMenu={handleRightClick}
|
||||||
onDoubleClick={handleDoubleClick}
|
onDoubleClick={handleDoubleClick}
|
||||||
@ -304,10 +335,7 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="ml-1 flex w-full h-full items-center overflow-hidden">
|
||||||
<div
|
|
||||||
className="ml-1 flex w-full h-full items-center overflow-hidden"
|
|
||||||
>
|
|
||||||
<CollectionItemIcon item={item} />
|
<CollectionItemIcon item={item} />
|
||||||
<span className="item-name" title={item.name}>
|
<span className="item-name" title={item.name}>
|
||||||
{item.name}
|
{item.name}
|
||||||
@ -429,17 +457,16 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!itemIsCollapsed ? (
|
{!itemIsCollapsed ? (
|
||||||
<div>
|
<div>
|
||||||
{folderItems && folderItems.length
|
{folderItems && folderItems.length
|
||||||
? folderItems.map((i) => {
|
? folderItems.map((i) => {
|
||||||
return <CollectionItem key={i.uid} item={i} collection={collection} searchText={searchText} />;
|
return <CollectionItem key={i.uid} item={i} collectionUid={collectionUid} collectionPathname={collectionPathname} searchText={searchText} />;
|
||||||
})
|
})
|
||||||
: null}
|
: null}
|
||||||
{requestItems && requestItems.length
|
{requestItems && requestItems.length
|
||||||
? requestItems.map((i) => {
|
? requestItems.map((i) => {
|
||||||
return <CollectionItem key={i.uid} item={i} collection={collection} searchText={searchText} />;
|
return <CollectionItem key={i.uid} item={i} collectionUid={collectionUid} collectionPathname={collectionPathname} searchText={searchText} />;
|
||||||
})
|
})
|
||||||
: null}
|
: null}
|
||||||
</div>
|
</div>
|
||||||
@ -448,4 +475,4 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CollectionItem;
|
export default React.memo(CollectionItem);
|
@ -1,12 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import Modal from 'components/Modal';
|
import Modal from 'components/Modal';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { IconFiles } from '@tabler/icons';
|
import { IconFiles } from '@tabler/icons';
|
||||||
import { removeCollection } from 'providers/ReduxStore/slices/collections/actions';
|
import { removeCollection } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import { findCollectionByUid } from 'utils/collections/index';
|
||||||
|
|
||||||
const RemoveCollection = ({ onClose, collection }) => {
|
const RemoveCollection = ({ onClose, collectionUid }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const collection = useSelector(state => findCollectionByUid(state.collections.collections, collectionUid));
|
||||||
|
|
||||||
const onConfirm = () => {
|
const onConfirm = () => {
|
||||||
dispatch(removeCollection(collection.uid))
|
dispatch(removeCollection(collection.uid))
|
||||||
|
@ -2,13 +2,15 @@ import React, { useRef, useEffect } from 'react';
|
|||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import Modal from 'components/Modal';
|
import Modal from 'components/Modal';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { renameCollection } from 'providers/ReduxStore/slices/collections/actions';
|
import { renameCollection } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import { findCollectionByUid } from 'utils/collections/index';
|
||||||
|
|
||||||
const RenameCollection = ({ collection, onClose }) => {
|
const RenameCollection = ({ collectionUid, onClose }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const inputRef = useRef();
|
const inputRef = useRef();
|
||||||
|
const collection = useSelector(state => findCollectionByUid(state.collections.collections, collectionUid));
|
||||||
const formik = useFormik({
|
const formik = useFormik({
|
||||||
enableReinitialize: true,
|
enableReinitialize: true,
|
||||||
initialValues: {
|
initialValues: {
|
||||||
|
@ -62,6 +62,36 @@ const Wrapper = styled.div`
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.drop-target {
|
||||||
|
background-color: ${(props) => props.theme.dragAndDrop.hoverBg};
|
||||||
|
transition: ${(props) => props.theme.dragAndDrop.transition};
|
||||||
|
}
|
||||||
|
|
||||||
|
&.drop-target-above {
|
||||||
|
border: none;
|
||||||
|
border-top: ${(props) => props.theme.dragAndDrop.borderStyle} ${(props) => props.theme.dragAndDrop.border};
|
||||||
|
margin-top: -2px;
|
||||||
|
background: transparent;
|
||||||
|
transition: ${(props) => props.theme.dragAndDrop.transition};
|
||||||
|
}
|
||||||
|
|
||||||
|
&.drop-target-below {
|
||||||
|
border: none;
|
||||||
|
border-bottom: ${(props) => props.theme.dragAndDrop.borderStyle} ${(props) => props.theme.dragAndDrop.border};
|
||||||
|
margin-bottom: -2px;
|
||||||
|
background: transparent;
|
||||||
|
transition: ${(props) => props.theme.dragAndDrop.transition};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-name.drop-target {
|
||||||
|
border: ${(props) => props.theme.dragAndDrop.borderStyle} ${(props) => props.theme.dragAndDrop.border};
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: ${(props) => props.theme.dragAndDrop.hoverBg};
|
||||||
|
margin: -2px;
|
||||||
|
transition: ${(props) => props.theme.dragAndDrop.transition};
|
||||||
|
box-shadow: 0 0 0 2px ${(props) => props.theme.dragAndDrop.hoverBg};
|
||||||
}
|
}
|
||||||
|
|
||||||
#sidebar-collection-name {
|
#sidebar-collection-name {
|
||||||
|
@ -6,7 +6,7 @@ import { useDrop, useDrag } from 'react-dnd';
|
|||||||
import { IconChevronRight, IconDots, IconLoader2 } from '@tabler/icons';
|
import { IconChevronRight, IconDots, IconLoader2 } from '@tabler/icons';
|
||||||
import Dropdown from 'components/Dropdown';
|
import Dropdown from 'components/Dropdown';
|
||||||
import { collapseCollection } from 'providers/ReduxStore/slices/collections';
|
import { collapseCollection } from 'providers/ReduxStore/slices/collections';
|
||||||
import { mountCollection, moveItemToRootOfCollection, moveCollectionAndPersist } from 'providers/ReduxStore/slices/collections/actions';
|
import { mountCollection, moveCollectionAndPersist, handleCollectionItemDrop } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { addTab, makeTabPermanent } from 'providers/ReduxStore/slices/tabs';
|
import { addTab, makeTabPermanent } from 'providers/ReduxStore/slices/tabs';
|
||||||
import NewRequest from 'components/Sidebar/NewRequest';
|
import NewRequest from 'components/Sidebar/NewRequest';
|
||||||
@ -33,7 +33,7 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const isLoading = areItemsLoading(collection);
|
const isLoading = areItemsLoading(collection);
|
||||||
const collectionRef = useRef(null);
|
const collectionRef = useRef(null);
|
||||||
|
|
||||||
const menuDropdownTippyRef = useRef();
|
const menuDropdownTippyRef = useRef();
|
||||||
const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref);
|
const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref);
|
||||||
const MenuIcon = forwardRef((props, ref) => {
|
const MenuIcon = forwardRef((props, ref) => {
|
||||||
@ -144,7 +144,7 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
drop: (draggedItem, monitor) => {
|
drop: (draggedItem, monitor) => {
|
||||||
const itemType = monitor.getItemType();
|
const itemType = monitor.getItemType();
|
||||||
if (isCollectionItem(itemType)) {
|
if (isCollectionItem(itemType)) {
|
||||||
dispatch(moveItemToRootOfCollection(collection.uid, draggedItem.uid))
|
dispatch(handleCollectionItemDrop({ targetItem: collection, draggedItem, dropType: 'inside', collectionUid: collection.uid }))
|
||||||
} else {
|
} else {
|
||||||
dispatch(moveCollectionAndPersist({draggedItem, targetItem: collection}));
|
dispatch(moveCollectionAndPersist({draggedItem, targetItem: collection}));
|
||||||
}
|
}
|
||||||
@ -170,33 +170,28 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// we need to sort request items by seq property
|
// we need to sort request items by seq property
|
||||||
const sortRequestItems = (items = []) => {
|
const sortItemsBySequence = (items = []) => {
|
||||||
return items.sort((a, b) => a.seq - b.seq);
|
return items.sort((a, b) => a.seq - b.seq);
|
||||||
};
|
};
|
||||||
|
|
||||||
// we need to sort folder items by name alphabetically
|
const requestItems = sortItemsBySequence(filter(collection.items, (i) => isItemARequest(i)));
|
||||||
const sortFolderItems = (items = []) => {
|
const folderItems = sortItemsBySequence(filter(collection.items, (i) => isItemAFolder(i)));
|
||||||
return items.sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
};
|
|
||||||
|
|
||||||
const requestItems = sortRequestItems(filter(collection.items, (i) => isItemARequest(i)));
|
|
||||||
const folderItems = sortFolderItems(filter(collection.items, (i) => isItemAFolder(i)));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="flex flex-col">
|
<StyledWrapper className="flex flex-col">
|
||||||
{showNewRequestModal && <NewRequest collection={collection} onClose={() => setShowNewRequestModal(false)} />}
|
{showNewRequestModal && <NewRequest collectionUid={collection.uid} collectionPathname={collection.pathname} onClose={() => setShowNewRequestModal(false)} />}
|
||||||
{showNewFolderModal && <NewFolder collection={collection} onClose={() => setShowNewFolderModal(false)} />}
|
{showNewFolderModal && <NewFolder collectionUid={collection.uid} collectionPathname={collection.pathname} onClose={() => setShowNewFolderModal(false)} />}
|
||||||
{showRenameCollectionModal && (
|
{showRenameCollectionModal && (
|
||||||
<RenameCollection collection={collection} onClose={() => setShowRenameCollectionModal(false)} />
|
<RenameCollection collectionUid={collection.uid} collectionPathname={collection.pathname} onClose={() => setShowRenameCollectionModal(false)} />
|
||||||
)}
|
)}
|
||||||
{showRemoveCollectionModal && (
|
{showRemoveCollectionModal && (
|
||||||
<RemoveCollection collection={collection} onClose={() => setShowRemoveCollectionModal(false)} />
|
<RemoveCollection collectionUid={collection.uid} collectionPathname={collection.pathname} onClose={() => setShowRemoveCollectionModal(false)} />
|
||||||
)}
|
)}
|
||||||
{showShareCollectionModal && (
|
{showShareCollectionModal && (
|
||||||
<ShareCollection collection={collection} onClose={() => setShowShareCollectionModal(false)} />
|
<ShareCollection collectionUid={collection.uid} collectionPathname={collection.pathname} onClose={() => setShowShareCollectionModal(false)} />
|
||||||
)}
|
)}
|
||||||
{showCloneCollectionModalOpen && (
|
{showCloneCollectionModalOpen && (
|
||||||
<CloneCollection collection={collection} onClose={() => setShowCloneCollectionModalOpen(false)} />
|
<CloneCollection collectionUid={collection.uid} collectionPathname={collection.pathname} onClose={() => setShowCloneCollectionModalOpen(false)} />
|
||||||
)}
|
)}
|
||||||
<div className={collectionRowClassName}
|
<div className={collectionRowClassName}
|
||||||
ref={collectionRef}
|
ref={collectionRef}
|
||||||
@ -300,16 +295,12 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
<div>
|
<div>
|
||||||
{!collectionIsCollapsed ? (
|
{!collectionIsCollapsed ? (
|
||||||
<div>
|
<div>
|
||||||
{folderItems && folderItems.length
|
{folderItems?.map?.((i) => {
|
||||||
? folderItems.map((i) => {
|
return <CollectionItem key={i.uid} item={i} collectionUid={collection.uid} collectionPathname={collection.pathname} searchText={searchText} />;
|
||||||
return <CollectionItem key={i.uid} item={i} collection={collection} searchText={searchText} />;
|
})}
|
||||||
})
|
{requestItems?.map?.((i) => {
|
||||||
: null}
|
return <CollectionItem key={i.uid} item={i} collectionUid={collection.uid} collectionPathname={collection.pathname} searchText={searchText} />;
|
||||||
{requestItems && requestItems.length
|
})}
|
||||||
? requestItems.map((i) => {
|
|
||||||
return <CollectionItem key={i.uid} item={i} collection={collection} searchText={searchText} />;
|
|
||||||
})
|
|
||||||
: null}
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,7 +14,7 @@ import Dropdown from "components/Dropdown";
|
|||||||
import { IconCaretDown } from "@tabler/icons";
|
import { IconCaretDown } from "@tabler/icons";
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const NewFolder = ({ collection, item, onClose }) => {
|
const NewFolder = ({ collectionUid, item, onClose }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const inputRef = useRef();
|
const inputRef = useRef();
|
||||||
const [isEditing, toggleEditing] = useState(false);
|
const [isEditing, toggleEditing] = useState(false);
|
||||||
@ -52,7 +52,7 @@ const NewFolder = ({ collection, item, onClose }) => {
|
|||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
onSubmit: (values) => {
|
onSubmit: (values) => {
|
||||||
dispatch(newFolder(values.folderName, values.directoryName, collection.uid, item ? item.uid : null))
|
dispatch(newFolder(values.folderName, values.directoryName, collectionUid, item ? item.uid : null))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success('New folder created!');
|
toast.success('New folder created!');
|
||||||
onClose();
|
onClose();
|
||||||
|
@ -5,7 +5,7 @@ import toast from 'react-hot-toast';
|
|||||||
import path from 'utils/common/path';
|
import path from 'utils/common/path';
|
||||||
import { uuid } from 'utils/common';
|
import { uuid } from 'utils/common';
|
||||||
import Modal from 'components/Modal';
|
import Modal from 'components/Modal';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { newEphemeralHttpRequest } from 'providers/ReduxStore/slices/collections';
|
import { newEphemeralHttpRequest } from 'providers/ReduxStore/slices/collections';
|
||||||
import { newHttpRequest } from 'providers/ReduxStore/slices/collections/actions';
|
import { newHttpRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||||
@ -20,9 +20,11 @@ import Portal from 'components/Portal';
|
|||||||
import Help from 'components/Help';
|
import Help from 'components/Help';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
const NewRequest = ({ collectionUid, item, isEphemeral, onClose }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const inputRef = useRef();
|
const inputRef = useRef();
|
||||||
|
|
||||||
|
const collection = useSelector(state => state.collections.collections?.find(c => c.uid === collectionUid));
|
||||||
const {
|
const {
|
||||||
brunoConfig: { presets: collectionPresets = {} }
|
brunoConfig: { presets: collectionPresets = {} }
|
||||||
} = collection;
|
} = collection;
|
||||||
@ -135,14 +137,14 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
requestType: values.requestType,
|
requestType: values.requestType,
|
||||||
requestUrl: values.requestUrl,
|
requestUrl: values.requestUrl,
|
||||||
requestMethod: values.requestMethod,
|
requestMethod: values.requestMethod,
|
||||||
collectionUid: collection.uid
|
collectionUid: collectionUid
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
dispatch(
|
dispatch(
|
||||||
addTab({
|
addTab({
|
||||||
uid: uid,
|
uid: uid,
|
||||||
collectionUid: collection.uid,
|
collectionUid: collectionUid,
|
||||||
requestPaneTab: getDefaultRequestPaneTab({ type: values.requestType })
|
requestPaneTab: getDefaultRequestPaneTab({ type: values.requestType })
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -158,7 +160,7 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
requestType: curlRequestTypeDetected,
|
requestType: curlRequestTypeDetected,
|
||||||
requestUrl: request.url,
|
requestUrl: request.url,
|
||||||
requestMethod: request.method,
|
requestMethod: request.method,
|
||||||
collectionUid: collection.uid,
|
collectionUid: collectionUid,
|
||||||
itemUid: item ? item.uid : null,
|
itemUid: item ? item.uid : null,
|
||||||
headers: request.headers,
|
headers: request.headers,
|
||||||
body: request.body,
|
body: request.body,
|
||||||
@ -178,7 +180,7 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
requestType: values.requestType,
|
requestType: values.requestType,
|
||||||
requestUrl: values.requestUrl,
|
requestUrl: values.requestUrl,
|
||||||
requestMethod: values.requestMethod,
|
requestMethod: values.requestMethod,
|
||||||
collectionUid: collection.uid,
|
collectionUid: collectionUid,
|
||||||
itemUid: item ? item.uid : null
|
itemUid: item ? item.uid : null
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -389,8 +391,6 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
) : (
|
) : (
|
||||||
<div className='relative flex flex-row gap-1 items-center justify-between'>
|
<div className='relative flex flex-row gap-1 items-center justify-between'>
|
||||||
<PathDisplay
|
<PathDisplay
|
||||||
collection={collection}
|
|
||||||
dirName={path.relative(collection?.pathname, item?.pathname || collection?.pathname)}
|
|
||||||
baseName={formik.values.filename? `${formik.values.filename}.bru` : ''}
|
baseName={formik.values.filename? `${formik.values.filename}.bru` : ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,12 +13,9 @@ import {
|
|||||||
findEnvironmentInCollection,
|
findEnvironmentInCollection,
|
||||||
findItemInCollection,
|
findItemInCollection,
|
||||||
findParentItemInCollection,
|
findParentItemInCollection,
|
||||||
getItemsToResequence,
|
|
||||||
isItemAFolder,
|
isItemAFolder,
|
||||||
refreshUidsInItem,
|
refreshUidsInItem,
|
||||||
isItemARequest,
|
isItemARequest,
|
||||||
moveCollectionItem,
|
|
||||||
moveCollectionItemToRootOfCollection,
|
|
||||||
transformRequestToSaveToFilesystem
|
transformRequestToSaveToFilesystem
|
||||||
} from 'utils/collections';
|
} from 'utils/collections';
|
||||||
import { uuid, waitForNextTick } from 'utils/common';
|
import { uuid, waitForNextTick } from 'utils/common';
|
||||||
@ -47,8 +44,7 @@ import { closeAllCollectionTabs } from 'providers/ReduxStore/slices/tabs';
|
|||||||
import { resolveRequestFilename } from 'utils/common/platform';
|
import { resolveRequestFilename } from 'utils/common/platform';
|
||||||
import { parsePathParams, parseQueryParams, splitOnFirst } from 'utils/url/index';
|
import { parsePathParams, parseQueryParams, splitOnFirst } from 'utils/url/index';
|
||||||
import { sendCollectionOauth2Request as _sendCollectionOauth2Request } from 'utils/network/index';
|
import { sendCollectionOauth2Request as _sendCollectionOauth2Request } from 'utils/network/index';
|
||||||
import { getGlobalEnvironmentVariables } from 'utils/collections/index';
|
import { getGlobalEnvironmentVariables, findCollectionByPathname, findEnvironmentInCollectionByName, getReorderedItemsInTargetDirectory, resetSequencesInFolder, getReorderedItemsInSourceDirectory } from 'utils/collections/index';
|
||||||
import { findCollectionByPathname, findEnvironmentInCollectionByName } from 'utils/collections/index';
|
|
||||||
import { sanitizeName } from 'utils/common/regex';
|
import { sanitizeName } from 'utils/common/regex';
|
||||||
import { safeParseJSON, safeStringifyJSON } from 'utils/common/index';
|
import { safeParseJSON, safeStringifyJSON } from 'utils/common/index';
|
||||||
|
|
||||||
@ -358,6 +354,8 @@ export const runCollectionFolder = (collectionUid, folderUid, recursive, delay)
|
|||||||
export const newFolder = (folderName, directoryName, collectionUid, itemUid) => (dispatch, getState) => {
|
export const newFolder = (folderName, directoryName, collectionUid, itemUid) => (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||||
|
const parentItem = itemUid ? findItemInCollection(collection, itemUid) : collection;
|
||||||
|
const items = filter(parentItem.items, (i) => isItemAFolder(i) || isItemARequest(i));
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
@ -372,10 +370,27 @@ export const newFolder = (folderName, directoryName, collectionUid, itemUid) =>
|
|||||||
if (!folderWithSameNameExists) {
|
if (!folderWithSameNameExists) {
|
||||||
const fullName = path.join(collection.pathname, directoryName);
|
const fullName = path.join(collection.pathname, directoryName);
|
||||||
const { ipcRenderer } = window;
|
const { ipcRenderer } = window;
|
||||||
|
|
||||||
ipcRenderer
|
ipcRenderer
|
||||||
.invoke('renderer:new-folder', fullName, folderName)
|
.invoke('renderer:new-folder', fullName)
|
||||||
.then(() => resolve())
|
.then(async () => {
|
||||||
|
const folderData = {
|
||||||
|
name: folderName,
|
||||||
|
pathname: fullName,
|
||||||
|
root: {
|
||||||
|
meta: {
|
||||||
|
name: folderName,
|
||||||
|
seq: items?.length + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ipcRenderer
|
||||||
|
.invoke('renderer:save-folder-root', folderData)
|
||||||
|
.then(resolve)
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error('Failed to save folder settings!');
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
})
|
||||||
.catch((error) => reject(error));
|
.catch((error) => reject(error));
|
||||||
} else {
|
} else {
|
||||||
return reject(new Error('Duplicate folder names under same parent folder are not allowed'));
|
return reject(new Error('Duplicate folder names under same parent folder are not allowed'));
|
||||||
@ -392,8 +407,26 @@ export const newFolder = (folderName, directoryName, collectionUid, itemUid) =>
|
|||||||
const { ipcRenderer } = window;
|
const { ipcRenderer } = window;
|
||||||
|
|
||||||
ipcRenderer
|
ipcRenderer
|
||||||
.invoke('renderer:new-folder', fullName, folderName)
|
.invoke('renderer:new-folder', fullName)
|
||||||
.then(() => resolve())
|
.then(async () => {
|
||||||
|
const folderData = {
|
||||||
|
name: folderName,
|
||||||
|
pathname: fullName,
|
||||||
|
root: {
|
||||||
|
meta: {
|
||||||
|
name: folderName,
|
||||||
|
seq: items?.length + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ipcRenderer
|
||||||
|
.invoke('renderer:save-folder-root', folderData)
|
||||||
|
.then(resolve)
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error('Failed to save folder settings!');
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
})
|
||||||
.catch((error) => reject(error));
|
.catch((error) => reject(error));
|
||||||
} else {
|
} else {
|
||||||
return reject(new Error('Duplicate folder names under same parent folder are not allowed'));
|
return reject(new Error('Duplicate folder names under same parent folder are not allowed'));
|
||||||
@ -495,7 +528,8 @@ export const cloneItem = (newName, newFilename, itemUid, collectionUid) => (disp
|
|||||||
set(item, 'name', newName);
|
set(item, 'name', newName);
|
||||||
set(item, 'filename', newFilename);
|
set(item, 'filename', newFilename);
|
||||||
set(item, 'root.meta.name', newName);
|
set(item, 'root.meta.name', newName);
|
||||||
|
set(item, 'root.meta.seq', parentFolder?.items?.length + 1);
|
||||||
|
|
||||||
const collectionPath = path.join(parentFolder.pathname, newFilename);
|
const collectionPath = path.join(parentFolder.pathname, newFilename);
|
||||||
ipcRenderer.invoke('renderer:clone-folder', item, collectionPath).then(resolve).catch(reject);
|
ipcRenderer.invoke('renderer:clone-folder', item, collectionPath).then(resolve).catch(reject);
|
||||||
return;
|
return;
|
||||||
@ -594,176 +628,129 @@ export const deleteItem = (itemUid, collectionUid) => (dispatch, getState) => {
|
|||||||
export const sortCollections = (payload) => (dispatch) => {
|
export const sortCollections = (payload) => (dispatch) => {
|
||||||
dispatch(_sortCollections(payload));
|
dispatch(_sortCollections(payload));
|
||||||
};
|
};
|
||||||
export const moveItem = (collectionUid, draggedItemUid, targetItemUid) => (dispatch, getState) => {
|
|
||||||
|
export const moveItem = ({ targetDirname, sourcePathname }) => (dispatch, getState) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const { ipcRenderer } = window;
|
||||||
|
|
||||||
|
ipcRenderer.invoke('renderer:move-item', { targetDirname, sourcePathname })
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handleCollectionItemDrop = ({ targetItem, draggedItem, dropType, collectionUid }) => (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||||
|
const { uid: draggedItemUid, pathname: draggedItemPathname } = draggedItem;
|
||||||
|
const { uid: targetItemUid, pathname: targetItemPathname } = targetItem;
|
||||||
|
const targetItemDirectory = findParentItemInCollection(collection, targetItemUid) || collection;
|
||||||
|
const targetItemDirectoryItems = cloneDeep(targetItemDirectory.items);
|
||||||
|
const draggedItemDirectory = findParentItemInCollection(collection, draggedItemUid) || collection;
|
||||||
|
const draggedItemDirectoryItems = cloneDeep(draggedItemDirectory.items);
|
||||||
|
|
||||||
|
const calculateDraggedItemNewPathname = ({ draggedItem, targetItem, dropType }) => {
|
||||||
|
const { pathname: targetItemPathname } = targetItem;
|
||||||
|
const { filename: draggedItemFilename } = draggedItem;
|
||||||
|
const targetItemDirname = path.dirname(targetItemPathname);
|
||||||
|
const isTargetTheCollection = targetItemPathname === collection.pathname;
|
||||||
|
const isTargetItemAFolder = isItemAFolder(targetItem);
|
||||||
|
|
||||||
|
if (dropType === 'inside' && (isTargetItemAFolder || isTargetTheCollection)) {
|
||||||
|
return path.join(targetItemPathname, draggedItemFilename)
|
||||||
|
} else if (dropType === 'adjacent') {
|
||||||
|
return path.join(targetItemDirname, draggedItemFilename)
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMoveToNewLocation = async ({ draggedItem, draggedItemDirectoryItems, targetItem, targetItemDirectoryItems, newPathname, dropType }) => {
|
||||||
|
const { uid: targetItemUid } = targetItem;
|
||||||
|
const { pathname: draggedItemPathname, uid: draggedItemUid } = draggedItem;
|
||||||
|
|
||||||
|
const newDirname = path.dirname(newPathname);
|
||||||
|
await dispatch(moveItem({
|
||||||
|
targetDirname: newDirname,
|
||||||
|
sourcePathname: draggedItemPathname
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Update sequences in the source directory
|
||||||
|
if (draggedItemDirectoryItems?.length) {
|
||||||
|
// reorder items in the source directory
|
||||||
|
const draggedItemDirectoryItemsWithoutDraggedItem = draggedItemDirectoryItems.filter(i => i.uid !== draggedItemUid);
|
||||||
|
const reorderedSourceItems = getReorderedItemsInSourceDirectory({ items: draggedItemDirectoryItemsWithoutDraggedItem });
|
||||||
|
if (reorderedSourceItems?.length) {
|
||||||
|
await dispatch(updateItemsSequences({ itemsToResequence: reorderedSourceItems }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update sequences in the target directory (if dropping adjacent)
|
||||||
|
if (dropType === 'adjacent') {
|
||||||
|
const targetItemSequence = targetItemDirectoryItems.findIndex(i => i.uid === targetItemUid)?.seq;
|
||||||
|
|
||||||
|
const draggedItemWithNewPathAndSequence = {
|
||||||
|
...draggedItem,
|
||||||
|
pathname: newPathname,
|
||||||
|
seq: targetItemSequence
|
||||||
|
};
|
||||||
|
|
||||||
|
// draggedItem is added to the targetItem's directory
|
||||||
|
const reorderedTargetItems = getReorderedItemsInTargetDirectory({
|
||||||
|
items: [ ...targetItemDirectoryItems, draggedItemWithNewPathAndSequence ],
|
||||||
|
targetItemUid,
|
||||||
|
draggedItemUid
|
||||||
|
});
|
||||||
|
|
||||||
|
if (reorderedTargetItems?.length) {
|
||||||
|
await dispatch(updateItemsSequences({ itemsToResequence: reorderedTargetItems }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReorderInSameLocation = async ({ draggedItem, targetItem, targetItemDirectoryItems }) => {
|
||||||
|
const { uid: targetItemUid } = targetItem;
|
||||||
|
const { uid: draggedItemUid } = draggedItem;
|
||||||
|
|
||||||
|
// reorder items in the targetItem's directory
|
||||||
|
const reorderedItems = getReorderedItemsInTargetDirectory({
|
||||||
|
items: targetItemDirectoryItems,
|
||||||
|
targetItemUid,
|
||||||
|
draggedItemUid
|
||||||
|
});
|
||||||
|
|
||||||
|
if (reorderedItems?.length) {
|
||||||
|
await dispatch(updateItemsSequences({ itemsToResequence: reorderedItems }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const newPathname = calculateDraggedItemNewPathname({ draggedItem, targetItem, dropType });
|
||||||
|
if (!newPathname) return;
|
||||||
|
if (targetItemPathname?.startsWith(draggedItemPathname)) return;
|
||||||
|
if (newPathname !== draggedItemPathname) {
|
||||||
|
await handleMoveToNewLocation({ targetItem, targetItemDirectoryItems, draggedItem, draggedItemDirectoryItems, newPathname, dropType });
|
||||||
|
} else {
|
||||||
|
await handleReorderInSameLocation({ draggedItem, targetItemDirectoryItems, targetItem });
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error(error?.message);
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateItemsSequences = ({ itemsToResequence }) => (dispatch, getState) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!collection) {
|
const { ipcRenderer } = window;
|
||||||
return reject(new Error('Collection not found'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const collectionCopy = cloneDeep(collection);
|
ipcRenderer.invoke('renderer:resequence-items', itemsToResequence)
|
||||||
const draggedItem = findItemInCollection(collectionCopy, draggedItemUid);
|
.then(resolve)
|
||||||
const targetItem = findItemInCollection(collectionCopy, targetItemUid);
|
.catch(reject);
|
||||||
|
|
||||||
if (!draggedItem) {
|
|
||||||
return reject(new Error('Dragged item not found'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetItem) {
|
|
||||||
return reject(new Error('Target item not found'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const draggedItemParent = findParentItemInCollection(collectionCopy, draggedItemUid);
|
|
||||||
const targetItemParent = findParentItemInCollection(collectionCopy, targetItemUid);
|
|
||||||
const sameParent = draggedItemParent === targetItemParent;
|
|
||||||
|
|
||||||
// file item dragged onto another file item and both are in the same folder
|
|
||||||
// this is also true when both items are at the root level
|
|
||||||
if (isItemARequest(draggedItem) && isItemARequest(targetItem) && sameParent) {
|
|
||||||
moveCollectionItem(collectionCopy, draggedItem, targetItem);
|
|
||||||
const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy);
|
|
||||||
|
|
||||||
return ipcRenderer
|
|
||||||
.invoke('renderer:resequence-items', itemsToResequence)
|
|
||||||
.then(resolve)
|
|
||||||
.catch((error) => reject(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
// file item dragged onto another file item which is at the root level
|
|
||||||
if (isItemARequest(draggedItem) && isItemARequest(targetItem) && !targetItemParent) {
|
|
||||||
const draggedItemPathname = draggedItem.pathname;
|
|
||||||
moveCollectionItem(collectionCopy, draggedItem, targetItem);
|
|
||||||
const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy);
|
|
||||||
const itemsToResequence2 = getItemsToResequence(targetItemParent, collectionCopy);
|
|
||||||
|
|
||||||
return ipcRenderer
|
|
||||||
.invoke('renderer:move-file-item', draggedItemPathname, collectionCopy.pathname)
|
|
||||||
.then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence))
|
|
||||||
.then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence2))
|
|
||||||
.then(resolve)
|
|
||||||
.catch((error) => reject(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
// file item dragged onto another file item and both are in different folders
|
|
||||||
if (isItemARequest(draggedItem) && isItemARequest(targetItem) && !sameParent) {
|
|
||||||
const draggedItemPathname = draggedItem.pathname;
|
|
||||||
moveCollectionItem(collectionCopy, draggedItem, targetItem);
|
|
||||||
const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy);
|
|
||||||
const itemsToResequence2 = getItemsToResequence(targetItemParent, collectionCopy);
|
|
||||||
|
|
||||||
return ipcRenderer
|
|
||||||
.invoke('renderer:move-file-item', draggedItemPathname, targetItemParent.pathname)
|
|
||||||
.then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence))
|
|
||||||
.then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence2))
|
|
||||||
.then(resolve)
|
|
||||||
.catch((error) => reject(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
// file item dragged into its own folder
|
|
||||||
if (isItemARequest(draggedItem) && isItemAFolder(targetItem) && draggedItemParent === targetItem) {
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
// file item dragged into another folder
|
|
||||||
if (isItemARequest(draggedItem) && isItemAFolder(targetItem) && draggedItemParent !== targetItem) {
|
|
||||||
const draggedItemPathname = draggedItem.pathname;
|
|
||||||
moveCollectionItem(collectionCopy, draggedItem, targetItem);
|
|
||||||
const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy);
|
|
||||||
const itemsToResequence2 = getItemsToResequence(targetItem, collectionCopy);
|
|
||||||
|
|
||||||
return ipcRenderer
|
|
||||||
.invoke('renderer:move-file-item', draggedItemPathname, targetItem.pathname)
|
|
||||||
.then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence))
|
|
||||||
.then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence2))
|
|
||||||
.then(resolve)
|
|
||||||
.catch((error) => reject(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
// end of the file drags, now let's handle folder drags
|
|
||||||
// folder drags are simpler since we don't allow ordering of folders
|
|
||||||
|
|
||||||
// folder dragged into its own folder
|
|
||||||
if (isItemAFolder(draggedItem) && isItemAFolder(targetItem) && draggedItemParent === targetItem) {
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
// folder dragged into a file which is at the same level
|
|
||||||
// this is also true when both items are at the root level
|
|
||||||
if (isItemAFolder(draggedItem) && isItemARequest(targetItem) && sameParent) {
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
// folder dragged into a file which is a child of the folder
|
|
||||||
if (isItemAFolder(draggedItem) && isItemARequest(targetItem) && draggedItem === targetItemParent) {
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
// folder dragged into a file which is at the root level
|
|
||||||
if (isItemAFolder(draggedItem) && isItemARequest(targetItem) && !targetItemParent) {
|
|
||||||
const draggedItemPathname = draggedItem.pathname;
|
|
||||||
|
|
||||||
return ipcRenderer
|
|
||||||
.invoke('renderer:move-folder-item', draggedItemPathname, collectionCopy.pathname)
|
|
||||||
.then(resolve)
|
|
||||||
.catch((error) => reject(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
// folder dragged into another folder
|
|
||||||
if (isItemAFolder(draggedItem) && isItemAFolder(targetItem) && draggedItemParent !== targetItem) {
|
|
||||||
const draggedItemPathname = draggedItem.pathname;
|
|
||||||
|
|
||||||
return ipcRenderer
|
|
||||||
.invoke('renderer:move-folder-item', draggedItemPathname, targetItem.pathname)
|
|
||||||
.then(resolve)
|
|
||||||
.catch((error) => reject(error));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
export const moveItemToRootOfCollection = (collectionUid, draggedItemUid) => (dispatch, getState) => {
|
|
||||||
const state = getState();
|
|
||||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (!collection) {
|
|
||||||
return reject(new Error('Collection not found'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const collectionCopy = cloneDeep(collection);
|
|
||||||
const draggedItem = findItemInCollection(collectionCopy, draggedItemUid);
|
|
||||||
if (!draggedItem) {
|
|
||||||
return reject(new Error('Dragged item not found'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const draggedItemParent = findParentItemInCollection(collectionCopy, draggedItemUid);
|
|
||||||
// file item is already at the root level
|
|
||||||
if (!draggedItemParent) {
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
const draggedItemPathname = draggedItem.pathname;
|
|
||||||
moveCollectionItemToRootOfCollection(collectionCopy, draggedItem);
|
|
||||||
|
|
||||||
if (isItemAFolder(draggedItem)) {
|
|
||||||
return ipcRenderer
|
|
||||||
.invoke('renderer:move-folder-item', draggedItemPathname, collectionCopy.pathname)
|
|
||||||
.then(resolve)
|
|
||||||
.catch((error) => reject(error));
|
|
||||||
} else {
|
|
||||||
const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy);
|
|
||||||
const itemsToResequence2 = getItemsToResequence(collectionCopy, collectionCopy);
|
|
||||||
|
|
||||||
return ipcRenderer
|
|
||||||
.invoke('renderer:move-file-item', draggedItemPathname, collectionCopy.pathname)
|
|
||||||
.then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence))
|
|
||||||
.then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence2))
|
|
||||||
.then(resolve)
|
|
||||||
.catch((error) => reject(error));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const newHttpRequest = (params) => (dispatch, getState) => {
|
export const newHttpRequest = (params) => (dispatch, getState) => {
|
||||||
const { requestName, filename, requestType, requestUrl, requestMethod, collectionUid, itemUid, headers, body, auth } = params;
|
const { requestName, filename, requestType, requestUrl, requestMethod, collectionUid, itemUid, headers, body, auth } = params;
|
||||||
@ -823,8 +810,8 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
|
|||||||
collection.items,
|
collection.items,
|
||||||
(i) => i.type !== 'folder' && trim(i.filename) === trim(resolvedFilename)
|
(i) => i.type !== 'folder' && trim(i.filename) === trim(resolvedFilename)
|
||||||
);
|
);
|
||||||
const requestItems = filter(collection.items, (i) => i.type !== 'folder');
|
const items = filter(collection.items, (i) => isItemAFolder(i) || isItemARequest(i));
|
||||||
item.seq = requestItems.length + 1;
|
item.seq = items.length + 1;
|
||||||
|
|
||||||
if (!reqWithSameNameExists) {
|
if (!reqWithSameNameExists) {
|
||||||
const fullName = path.join(collection.pathname, resolvedFilename);
|
const fullName = path.join(collection.pathname, resolvedFilename);
|
||||||
@ -852,8 +839,8 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
|
|||||||
currentItem.items,
|
currentItem.items,
|
||||||
(i) => i.type !== 'folder' && trim(i.filename) === trim(resolvedFilename)
|
(i) => i.type !== 'folder' && trim(i.filename) === trim(resolvedFilename)
|
||||||
);
|
);
|
||||||
const requestItems = filter(currentItem.items, (i) => i.type !== 'folder');
|
const items = filter(currentItem.items, (i) => isItemAFolder(i) || isItemARequest(i));
|
||||||
item.seq = requestItems.length + 1;
|
item.seq = items.length + 1;
|
||||||
if (!reqWithSameNameExists) {
|
if (!reqWithSameNameExists) {
|
||||||
const fullName = path.join(currentItem.pathname, resolvedFilename);
|
const fullName = path.join(currentItem.pathname, resolvedFilename);
|
||||||
const { ipcRenderer } = window;
|
const { ipcRenderer } = window;
|
||||||
|
@ -1719,6 +1719,9 @@ export const collectionsSlice = createSlice({
|
|||||||
folderItem.name = file?.data?.meta?.name;
|
folderItem.name = file?.data?.meta?.name;
|
||||||
}
|
}
|
||||||
folderItem.root = file.data;
|
folderItem.root = file.data;
|
||||||
|
if (file?.data?.meta?.seq) {
|
||||||
|
folderItem.seq = file.data?.meta?.seq;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1798,6 +1801,7 @@ export const collectionsSlice = createSlice({
|
|||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
pathname: currentPath,
|
pathname: currentPath,
|
||||||
name: dir?.meta?.name || directoryName,
|
name: dir?.meta?.name || directoryName,
|
||||||
|
seq: dir?.meta?.seq || 1,
|
||||||
filename: directoryName,
|
filename: directoryName,
|
||||||
collapsed: true,
|
collapsed: true,
|
||||||
type: 'folder',
|
type: 'folder',
|
||||||
@ -1829,6 +1833,9 @@ export const collectionsSlice = createSlice({
|
|||||||
if (file?.data?.meta?.name) {
|
if (file?.data?.meta?.name) {
|
||||||
folderItem.name = file?.data?.meta?.name;
|
folderItem.name = file?.data?.meta?.name;
|
||||||
}
|
}
|
||||||
|
if (file?.data?.meta?.seq) {
|
||||||
|
folderItem.seq = file?.data?.meta?.seq;
|
||||||
|
}
|
||||||
folderItem.root = file.data;
|
folderItem.root = file.data;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
9
packages/bruno-app/src/selectors/tab.js
Normal file
9
packages/bruno-app/src/selectors/tab.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
export const isTabForItemActive = ({ itemUid }) => createSelector([
|
||||||
|
(state) => state.tabs?.activeTabUid
|
||||||
|
], (activeTabUid) => activeTabUid === itemUid);
|
||||||
|
|
||||||
|
export const isTabForItemPresent = ({ itemUid }) => createSelector([
|
||||||
|
(state) => state.tabs.tabs,
|
||||||
|
], (tabs) => tabs.some((tab) => tab.uid === itemUid));
|
@ -281,6 +281,12 @@ const darkTheme = {
|
|||||||
color: 'rgb(52 51 49)'
|
color: 'rgb(52 51 49)'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
dragAndDrop: {
|
||||||
|
border: '#666666',
|
||||||
|
borderStyle: '2px solid',
|
||||||
|
hoverBg: 'rgba(102, 102, 102, 0.08)',
|
||||||
|
transition: 'all 0.1s ease'
|
||||||
|
},
|
||||||
infoTip: {
|
infoTip: {
|
||||||
bg: '#1f1f1f',
|
bg: '#1f1f1f',
|
||||||
border: '#333333',
|
border: '#333333',
|
||||||
|
@ -282,6 +282,12 @@ const lightTheme = {
|
|||||||
color: 'rgb(152 151 149)'
|
color: 'rgb(152 151 149)'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
dragAndDrop: {
|
||||||
|
border: '#8b8b8b', // Using the same gray as focusBorder from input
|
||||||
|
borderStyle: '2px solid',
|
||||||
|
hoverBg: 'rgba(139, 139, 139, 0.05)', // Matching the border color with reduced opacity
|
||||||
|
transition: 'all 0.1s ease'
|
||||||
|
},
|
||||||
infoTip: {
|
infoTip: {
|
||||||
bg: 'white',
|
bg: 'white',
|
||||||
border: '#e0e0e0',
|
border: '#e0e0e0',
|
||||||
|
@ -98,6 +98,14 @@ export const findItemInCollectionByPathname = (collection, pathname) => {
|
|||||||
return findItemByPathname(flattenedItems, pathname);
|
return findItemByPathname(flattenedItems, pathname);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const findParentItemInCollectionByPathname = (collection, pathname) => {
|
||||||
|
let flattenedItems = flattenItems(collection.items);
|
||||||
|
|
||||||
|
return find(flattenedItems, (item) => {
|
||||||
|
return item.items && find(item.items, (i) => i.pathname === pathname);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const findItemInCollection = (collection, itemUid) => {
|
export const findItemInCollection = (collection, itemUid) => {
|
||||||
let flattenedItems = flattenItems(collection.items);
|
let flattenedItems = flattenItems(collection.items);
|
||||||
|
|
||||||
@ -150,90 +158,6 @@ export const getItemsLoadStats = (folder) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const moveCollectionItem = (collection, draggedItem, targetItem) => {
|
|
||||||
let draggedItemParent = findParentItemInCollection(collection, draggedItem.uid);
|
|
||||||
|
|
||||||
if (draggedItemParent) {
|
|
||||||
draggedItemParent.items = sortBy(draggedItemParent.items, (item) => item.seq);
|
|
||||||
draggedItemParent.items = filter(draggedItemParent.items, (i) => i.uid !== draggedItem.uid);
|
|
||||||
draggedItem.pathname = path.join(draggedItemParent.pathname, draggedItem.filename);
|
|
||||||
} else {
|
|
||||||
collection.items = sortBy(collection.items, (item) => item.seq);
|
|
||||||
collection.items = filter(collection.items, (i) => i.uid !== draggedItem.uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetItem.type === 'folder') {
|
|
||||||
targetItem.items = sortBy(targetItem.items || [], (item) => item.seq);
|
|
||||||
targetItem.items.push(draggedItem);
|
|
||||||
draggedItem.pathname = path.join(targetItem.pathname, draggedItem.filename);
|
|
||||||
} else {
|
|
||||||
let targetItemParent = findParentItemInCollection(collection, targetItem.uid);
|
|
||||||
|
|
||||||
if (targetItemParent) {
|
|
||||||
targetItemParent.items = sortBy(targetItemParent.items, (item) => item.seq);
|
|
||||||
let targetItemIndex = findIndex(targetItemParent.items, (i) => i.uid === targetItem.uid);
|
|
||||||
targetItemParent.items.splice(targetItemIndex + 1, 0, draggedItem);
|
|
||||||
draggedItem.pathname = path.join(targetItemParent.pathname, draggedItem.filename);
|
|
||||||
} else {
|
|
||||||
collection.items = sortBy(collection.items, (item) => item.seq);
|
|
||||||
let targetItemIndex = findIndex(collection.items, (i) => i.uid === targetItem.uid);
|
|
||||||
collection.items.splice(targetItemIndex + 1, 0, draggedItem);
|
|
||||||
draggedItem.pathname = path.join(collection.pathname, draggedItem.filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const moveCollectionItemToRootOfCollection = (collection, draggedItem) => {
|
|
||||||
let draggedItemParent = findParentItemInCollection(collection, draggedItem.uid);
|
|
||||||
|
|
||||||
// If the dragged item is already at the root of the collection, do nothing
|
|
||||||
if (!draggedItemParent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
draggedItemParent.items = sortBy(draggedItemParent.items, (item) => item.seq);
|
|
||||||
draggedItemParent.items = filter(draggedItemParent.items, (i) => i.uid !== draggedItem.uid);
|
|
||||||
collection.items = sortBy(collection.items, (item) => item.seq);
|
|
||||||
collection.items.push(draggedItem);
|
|
||||||
if (draggedItem.type == 'folder') {
|
|
||||||
draggedItem.pathname = path.join(collection.pathname, draggedItem.name);
|
|
||||||
} else {
|
|
||||||
draggedItem.pathname = path.join(collection.pathname, draggedItem.filename);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getItemsToResequence = (parent, collection) => {
|
|
||||||
let itemsToResequence = [];
|
|
||||||
|
|
||||||
if (!parent) {
|
|
||||||
let index = 1;
|
|
||||||
each(collection.items, (item) => {
|
|
||||||
if (isItemARequest(item)) {
|
|
||||||
itemsToResequence.push({
|
|
||||||
pathname: item.pathname,
|
|
||||||
seq: index++
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return itemsToResequence;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parent.items && parent.items.length) {
|
|
||||||
let index = 1;
|
|
||||||
each(parent.items, (item) => {
|
|
||||||
if (isItemARequest(item)) {
|
|
||||||
itemsToResequence.push({
|
|
||||||
pathname: item.pathname,
|
|
||||||
seq: index++
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return itemsToResequence;
|
|
||||||
}
|
|
||||||
|
|
||||||
return itemsToResequence;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const transformCollectionToSaveToExportAsFile = (collection, options = {}) => {
|
export const transformCollectionToSaveToExportAsFile = (collection, options = {}) => {
|
||||||
const copyHeaders = (headers) => {
|
const copyHeaders = (headers) => {
|
||||||
return map(headers, (header) => {
|
return map(headers, (header) => {
|
||||||
@ -502,6 +426,7 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
|
|||||||
if (meta?.name) {
|
if (meta?.name) {
|
||||||
di.root.meta = {};
|
di.root.meta = {};
|
||||||
di.root.meta.name = meta?.name;
|
di.root.meta.name = meta?.name;
|
||||||
|
di.root.meta.seq = meta?.seq;
|
||||||
}
|
}
|
||||||
if (!Object.keys(di.root.request)?.length) {
|
if (!Object.keys(di.root.request)?.length) {
|
||||||
delete di.root.request;
|
delete di.root.request;
|
||||||
@ -1086,3 +1011,62 @@ export const getFormattedCollectionOauth2Credentials = ({ oauth2Credentials = []
|
|||||||
});
|
});
|
||||||
return credentialsVariables;
|
return credentialsVariables;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// item sequence utils - START
|
||||||
|
|
||||||
|
export const resetSequencesInFolder = (folderItems) => {
|
||||||
|
const items = folderItems;
|
||||||
|
const sortedItems = items.sort((a, b) => a.seq - b.seq);
|
||||||
|
return sortedItems.map((item, index) => {
|
||||||
|
item.seq = index + 1;
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isItemBetweenSequences = (itemSequence, sourceItemSequence, targetItemSequence) => {
|
||||||
|
if (targetItemSequence > sourceItemSequence) {
|
||||||
|
return itemSequence > sourceItemSequence && itemSequence < targetItemSequence;
|
||||||
|
}
|
||||||
|
return itemSequence < sourceItemSequence && itemSequence >= targetItemSequence;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const calculateNewSequence = (isDraggedItem, targetSequence, draggedSequence) => {
|
||||||
|
if (!isDraggedItem) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return targetSequence > draggedSequence ? targetSequence - 1 : targetSequence;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getReorderedItemsInTargetDirectory = ({ items, targetItemUid, draggedItemUid }) => {
|
||||||
|
const itemsWithFixedSequences = resetSequencesInFolder(cloneDeep(items));
|
||||||
|
const targetItem = findItem(itemsWithFixedSequences, targetItemUid);
|
||||||
|
const draggedItem = findItem(itemsWithFixedSequences, draggedItemUid);
|
||||||
|
const targetSequence = targetItem?.seq;
|
||||||
|
const draggedSequence = draggedItem?.seq;
|
||||||
|
itemsWithFixedSequences?.forEach(item => {
|
||||||
|
const isDraggedItem = item?.uid === draggedItemUid;
|
||||||
|
const isBetween = isItemBetweenSequences(item?.seq, draggedSequence, targetSequence);
|
||||||
|
if (isBetween) {
|
||||||
|
item.seq += targetSequence > draggedSequence ? -1 : 1;
|
||||||
|
}
|
||||||
|
const newSequence = calculateNewSequence(isDraggedItem, targetSequence, draggedSequence);
|
||||||
|
if (newSequence !== null) {
|
||||||
|
item.seq = newSequence;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// only return items that have been reordered
|
||||||
|
return itemsWithFixedSequences.filter(item =>
|
||||||
|
items?.find(originalItem => originalItem?.uid === item?.uid)?.seq !== item?.seq
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getReorderedItemsInSourceDirectory = ({ items }) => {
|
||||||
|
const itemsWithFixedSequences = resetSequencesInFolder(cloneDeep(items));
|
||||||
|
return itemsWithFixedSequences.filter(item =>
|
||||||
|
items?.find(originalItem => originalItem?.uid === item?.uid)?.seq !== item?.seq
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// item sequence utils - END
|
||||||
|
|
||||||
|
@ -0,0 +1,126 @@
|
|||||||
|
import { resetSequencesInFolder, isItemBetweenSequences } from 'utils/collections/index';
|
||||||
|
|
||||||
|
describe('resetSequencesInFolder', () => {
|
||||||
|
it('should fix the sequences in the folder 1', () => {
|
||||||
|
const folder = {
|
||||||
|
items: [
|
||||||
|
{ uid: '1', seq: 1 },
|
||||||
|
{ uid: '2', seq: 3 },
|
||||||
|
{ uid: '3', seq: 6 },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const fixedFolder = resetSequencesInFolder(folder.items);
|
||||||
|
expect(fixedFolder).toEqual([
|
||||||
|
{ uid: '1', seq: 1 },
|
||||||
|
{ uid: '2', seq: 2 },
|
||||||
|
{ uid: '3', seq: 3 },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should fix the sequences in the folder 2', () => {
|
||||||
|
const folder = {
|
||||||
|
items: [
|
||||||
|
{ uid: '1', seq: 3 },
|
||||||
|
{ uid: '2', seq: 1 },
|
||||||
|
{ uid: '3', seq: 2 },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const fixedFolder = resetSequencesInFolder(folder.items);
|
||||||
|
expect(fixedFolder).toEqual([
|
||||||
|
{ uid: '2', seq: 1 },
|
||||||
|
{ uid: '3', seq: 2 },
|
||||||
|
{ uid: '1', seq: 3 },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fix the sequences in the folder with missing sequences', () => {
|
||||||
|
const folder = {
|
||||||
|
items: [
|
||||||
|
{ uid: '1', seq: 1 },
|
||||||
|
{ uid: '2', type: 'folder' },
|
||||||
|
{ uid: '3', type: 'folder' },
|
||||||
|
{ uid: '4', seq: 7 },
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const fixedFolder = resetSequencesInFolder(folder.items);
|
||||||
|
expect(fixedFolder).toEqual([
|
||||||
|
{ uid: '1', seq: 1 },
|
||||||
|
{ uid: '2', seq: 2, type: 'folder' },
|
||||||
|
{ uid: '3', seq: 3, type: 'folder' },
|
||||||
|
{ uid: '4', seq: 4 },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fix the sequences in the folder with same sequences', () => {
|
||||||
|
const folder = {
|
||||||
|
items: [
|
||||||
|
{ uid: '1', seq: 2 },
|
||||||
|
{ uid: '2', seq: 2 },
|
||||||
|
{ uid: '3', seq: 3 },
|
||||||
|
{ uid: '4', seq: 1 },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const fixedFolder = resetSequencesInFolder(folder.items);
|
||||||
|
expect(fixedFolder).toEqual([
|
||||||
|
{ uid: '4', seq: 1 },
|
||||||
|
{ uid: '1', seq: 2 },
|
||||||
|
{ uid: '2', seq: 3 },
|
||||||
|
{ uid: '3', seq: 4 },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isItemBetweenSequences', () => {
|
||||||
|
it('should return true if the item is between the sequences 1', () => {
|
||||||
|
const item = { uid: '1', seq: 2 };
|
||||||
|
const draggedSequence = 1;
|
||||||
|
const targetSequence = 5;
|
||||||
|
const result = isItemBetweenSequences(item?.seq, draggedSequence, targetSequence);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if the item is between the sequences 2', () => {
|
||||||
|
const item = { uid: '1', seq: 2 };
|
||||||
|
const draggedSequence = 1;
|
||||||
|
const targetSequence = 5;
|
||||||
|
const result = isItemBetweenSequences(item?.seq, draggedSequence, targetSequence);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if the item is between the sequences 3', () => {
|
||||||
|
const item = { uid: '1', seq: 4 };
|
||||||
|
const draggedSequence = 1;
|
||||||
|
const targetSequence = 5;
|
||||||
|
const result = isItemBetweenSequences(item?.seq, draggedSequence, targetSequence);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if the item is between the sequences 4', () => {
|
||||||
|
const item = { uid: '1', seq: 1 };
|
||||||
|
const draggedSequence = 5;
|
||||||
|
const targetSequence = 1;
|
||||||
|
const result = isItemBetweenSequences(item?.seq, draggedSequence, targetSequence);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if the item is between the sequences 1', () => {
|
||||||
|
const item = { uid: '1', seq: 1 };
|
||||||
|
const draggedSequence = 1;
|
||||||
|
const targetSequence = 5;
|
||||||
|
const result = isItemBetweenSequences(item?.seq, draggedSequence, targetSequence);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if the item is between the sequences 2', () => {
|
||||||
|
const item = { uid: '1', seq: 5 };
|
||||||
|
const draggedSequence = 1;
|
||||||
|
const targetSequence = 5;
|
||||||
|
const result = isItemBetweenSequences(item?.seq, draggedSequence, targetSequence);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
@ -252,7 +252,7 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
|
|||||||
const requestMap = {};
|
const requestMap = {};
|
||||||
const requestMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'TRACE']
|
const requestMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'TRACE']
|
||||||
|
|
||||||
each(item, (i) => {
|
each(item, (i, index) => {
|
||||||
if (isItemAFolder(i)) {
|
if (isItemAFolder(i)) {
|
||||||
const baseFolderName = i.name || 'Untitled Folder';
|
const baseFolderName = i.name || 'Untitled Folder';
|
||||||
let folderName = baseFolderName;
|
let folderName = baseFolderName;
|
||||||
@ -268,6 +268,7 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
|
|||||||
name: folderName,
|
name: folderName,
|
||||||
type: 'folder',
|
type: 'folder',
|
||||||
items: [],
|
items: [],
|
||||||
|
seq: index + 1,
|
||||||
root: {
|
root: {
|
||||||
docs: i.description || '',
|
docs: i.description || '',
|
||||||
meta: {
|
meta: {
|
||||||
@ -332,6 +333,7 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
|
|||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
name: requestName,
|
name: requestName,
|
||||||
type: 'http-request',
|
type: 'http-request',
|
||||||
|
seq: index + 1,
|
||||||
request: {
|
request: {
|
||||||
url: url,
|
url: url,
|
||||||
method: i?.request?.method?.toUpperCase(),
|
method: i?.request?.method?.toUpperCase(),
|
||||||
|
@ -73,92 +73,93 @@ const expectedOutput = {
|
|||||||
"version": "1",
|
"version": "1",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"uid": "mockeduuidvalue123456",
|
"uid": "mockeduuidvalue123456",
|
||||||
"name": "folder",
|
"name": "folder",
|
||||||
"type": "folder",
|
"type": "folder",
|
||||||
"items": [
|
"seq": 1,
|
||||||
{
|
"items": [
|
||||||
"uid": "mockeduuidvalue123456",
|
{
|
||||||
"name": "request",
|
"uid": "mockeduuidvalue123456",
|
||||||
"type": "http-request",
|
"name": "request",
|
||||||
"request": {
|
"type": "http-request",
|
||||||
"url": "https://usebruno.com",
|
"seq": 1,
|
||||||
"method": "GET",
|
"request": {
|
||||||
"auth": {
|
"url": "https://usebruno.com",
|
||||||
"mode": "none",
|
"method": "GET",
|
||||||
"basic": null,
|
"auth": {
|
||||||
"bearer": null,
|
"mode": "none",
|
||||||
"awsv4": null,
|
"basic": null,
|
||||||
"apikey": null,
|
"bearer": null,
|
||||||
"oauth2": null,
|
"awsv4": null,
|
||||||
"digest": null
|
"apikey": null,
|
||||||
},
|
"oauth2": null,
|
||||||
"headers": [],
|
"digest": null
|
||||||
"params": [],
|
},
|
||||||
"body": {
|
"headers": [],
|
||||||
"mode": "none",
|
"params": [],
|
||||||
"json": null,
|
"body": {
|
||||||
"text": null,
|
"mode": "none",
|
||||||
"xml": null,
|
"json": null,
|
||||||
"formUrlEncoded": [],
|
"text": null,
|
||||||
"multipartForm": []
|
"xml": null,
|
||||||
},
|
"formUrlEncoded": [],
|
||||||
"docs": ""
|
"multipartForm": []
|
||||||
},
|
},
|
||||||
"seq": 1
|
"docs": ""
|
||||||
}
|
}
|
||||||
],
|
}
|
||||||
"root": {
|
],
|
||||||
"docs": "",
|
"root": {
|
||||||
"meta": {
|
"docs": "",
|
||||||
"name": "folder"
|
"meta": {
|
||||||
},
|
"name": "folder"
|
||||||
"request": {
|
},
|
||||||
"auth": {
|
"request": {
|
||||||
"mode": "none",
|
"auth": {
|
||||||
"basic": null,
|
"mode": "none",
|
||||||
"bearer": null,
|
"basic": null,
|
||||||
"awsv4": null,
|
"bearer": null,
|
||||||
"apikey": null,
|
"awsv4": null,
|
||||||
"oauth2": null,
|
"apikey": null,
|
||||||
"digest": null
|
"oauth2": null,
|
||||||
},
|
"digest": null
|
||||||
"headers": [],
|
},
|
||||||
"script": {},
|
"headers": [],
|
||||||
"tests": "",
|
"script": {},
|
||||||
"vars": {}
|
"tests": "",
|
||||||
}
|
"vars": {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uid": "mockeduuidvalue123456",
|
"uid": "mockeduuidvalue123456",
|
||||||
"name": "request",
|
"name": "request",
|
||||||
"type": "http-request",
|
"type": "http-request",
|
||||||
"request": {
|
"seq": 2,
|
||||||
"url": "https://usebruno.com",
|
"request": {
|
||||||
"method": "GET",
|
"url": "https://usebruno.com",
|
||||||
"auth": {
|
"method": "GET",
|
||||||
"mode": "none",
|
"auth": {
|
||||||
"basic": null,
|
"mode": "none",
|
||||||
"bearer": null,
|
"basic": null,
|
||||||
"awsv4": null,
|
"bearer": null,
|
||||||
"apikey": null,
|
"awsv4": null,
|
||||||
"oauth2": null,
|
"apikey": null,
|
||||||
"digest": null
|
"oauth2": null,
|
||||||
},
|
"digest": null
|
||||||
"headers": [],
|
},
|
||||||
"params": [],
|
"headers": [],
|
||||||
"body": {
|
"params": [],
|
||||||
"mode": "none",
|
"body": {
|
||||||
"json": null,
|
"mode": "none",
|
||||||
"text": null,
|
"json": null,
|
||||||
"xml": null,
|
"text": null,
|
||||||
"formUrlEncoded": [],
|
"xml": null,
|
||||||
"multipartForm": []
|
"formUrlEncoded": [],
|
||||||
},
|
"multipartForm": []
|
||||||
"docs": ""
|
},
|
||||||
},
|
"docs": ""
|
||||||
"seq": 1
|
},
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"environments": [],
|
"environments": [],
|
||||||
|
@ -220,7 +220,6 @@ const add = async (win, pathname, collectionUid, collectionPath, useWorkerThread
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is this a folder.bru file?
|
|
||||||
if (path.basename(pathname) === 'folder.bru') {
|
if (path.basename(pathname) === 'folder.bru') {
|
||||||
const file = {
|
const file = {
|
||||||
meta: {
|
meta: {
|
||||||
@ -327,16 +326,25 @@ const addDirectory = async (win, pathname, collectionUid, collectionPath) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let name = path.basename(pathname);
|
let name = path.basename(pathname);
|
||||||
|
let seq = 1;
|
||||||
|
const folderBruFilePath = path.join(pathname, `folder.bru`);
|
||||||
|
|
||||||
|
if (fs.existsSync(folderBruFilePath)) {
|
||||||
|
let folderBruFileContent = fs.readFileSync(folderBruFilePath, 'utf8');
|
||||||
|
let folderBruData = await collectionBruToJson(folderBruFileContent);
|
||||||
|
name = folderBruData?.meta?.name || name;
|
||||||
|
seq = folderBruData?.meta?.seq || seq;
|
||||||
|
}
|
||||||
|
|
||||||
const directory = {
|
const directory = {
|
||||||
meta: {
|
meta: {
|
||||||
collectionUid,
|
collectionUid,
|
||||||
pathname,
|
pathname,
|
||||||
name
|
name,
|
||||||
|
seq
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
win.webContents.send('main:collection-tree-updated', 'addDir', directory);
|
win.webContents.send('main:collection-tree-updated', 'addDir', directory);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -29,9 +29,11 @@ const collectionBruToJson = async (data, parsed = false) => {
|
|||||||
// add meta if it exists
|
// add meta if it exists
|
||||||
// this is only for folder bru file
|
// this is only for folder bru file
|
||||||
// in the future, all of this will be replaced by standard bru lang
|
// in the future, all of this will be replaced by standard bru lang
|
||||||
if (json.meta) {
|
const sequence = _.get(json, 'meta.seq');
|
||||||
|
if (json?.meta) {
|
||||||
transformedJson.meta = {
|
transformedJson.meta = {
|
||||||
name: json.meta.name
|
name: json.meta.name,
|
||||||
|
seq: !isNaN(sequence) ? Number(sequence) : 1
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,9 +63,11 @@ const jsonToCollectionBru = async (json, isFolder) => {
|
|||||||
// add meta if it exists
|
// add meta if it exists
|
||||||
// this is only for folder bru file
|
// this is only for folder bru file
|
||||||
// in the future, all of this will be replaced by standard bru lang
|
// in the future, all of this will be replaced by standard bru lang
|
||||||
|
const sequence = _.get(json, 'meta.seq');
|
||||||
if (json?.meta) {
|
if (json?.meta) {
|
||||||
collectionBruJson.meta = {
|
collectionBruJson.meta = {
|
||||||
name: json.meta.name
|
name: json.meta.name,
|
||||||
|
seq: !isNaN(sequence) ? Number(sequence) : 1
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const fsPromises = require('fs/promises');
|
||||||
const fsExtra = require('fs-extra');
|
const fsExtra = require('fs-extra');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
@ -22,7 +23,9 @@ const {
|
|||||||
hasSubDirectories,
|
hasSubDirectories,
|
||||||
getCollectionStats,
|
getCollectionStats,
|
||||||
sizeInMB,
|
sizeInMB,
|
||||||
safeWriteFileSync
|
safeWriteFileSync,
|
||||||
|
copyPath,
|
||||||
|
removePath
|
||||||
} = require('../utils/filesystem');
|
} = require('../utils/filesystem');
|
||||||
const { openCollectionDialog } = require('../app/collections');
|
const { openCollectionDialog } = require('../app/collections');
|
||||||
const { generateUidBasedOnHash, stringifyJson, safeParseJSON, safeStringifyJSON } = require('../utils/common');
|
const { generateUidBasedOnHash, stringifyJson, safeParseJSON, safeStringifyJSON } = require('../utils/common');
|
||||||
@ -32,11 +35,10 @@ const EnvironmentSecretsStore = require('../store/env-secrets');
|
|||||||
const CollectionSecurityStore = require('../store/collection-security');
|
const CollectionSecurityStore = require('../store/collection-security');
|
||||||
const UiStateSnapshotStore = require('../store/ui-state-snapshot');
|
const UiStateSnapshotStore = require('../store/ui-state-snapshot');
|
||||||
const interpolateVars = require('./network/interpolate-vars');
|
const interpolateVars = require('./network/interpolate-vars');
|
||||||
const { getEnvVars, getTreePathFromCollectionToItem, mergeVars } = require('../utils/collection');
|
const { getEnvVars, getTreePathFromCollectionToItem, mergeVars, parseBruFileMeta, hydrateRequestWithUuid, transformRequestToSaveToFilesystem } = require('../utils/collection');
|
||||||
const { getProcessEnvVars } = require('../store/process-env');
|
const { getProcessEnvVars } = require('../store/process-env');
|
||||||
const { getOAuth2TokenUsingAuthorizationCode, getOAuth2TokenUsingClientCredentials, getOAuth2TokenUsingPasswordCredentials, refreshOauth2Token } = require('../utils/oauth2');
|
const { getOAuth2TokenUsingAuthorizationCode, getOAuth2TokenUsingClientCredentials, getOAuth2TokenUsingPasswordCredentials, refreshOauth2Token } = require('../utils/oauth2');
|
||||||
const { getCertsAndProxyConfig } = require('./network');
|
const { getCertsAndProxyConfig } = require('./network');
|
||||||
const { parseBruFileMeta, hydrateRequestWithUuid } = require('../utils/collection');
|
|
||||||
|
|
||||||
const environmentSecretsStore = new EnvironmentSecretsStore();
|
const environmentSecretsStore = new EnvironmentSecretsStore();
|
||||||
const collectionSecurityStore = new CollectionSecurityStore();
|
const collectionSecurityStore = new CollectionSecurityStore();
|
||||||
@ -192,12 +194,15 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
|||||||
|
|
||||||
ipcMain.handle('renderer:save-folder-root', async (event, folder) => {
|
ipcMain.handle('renderer:save-folder-root', async (event, folder) => {
|
||||||
try {
|
try {
|
||||||
const { name: folderName, root: folderRoot, pathname: folderPathname } = folder;
|
const { name: folderName, root: folderRoot = {}, pathname: folderPathname } = folder;
|
||||||
const folderBruFilePath = path.join(folderPathname, 'folder.bru');
|
const folderBruFilePath = path.join(folderPathname, 'folder.bru');
|
||||||
|
|
||||||
folderRoot.meta = {
|
if (!folderRoot.meta) {
|
||||||
name: folderName
|
folderRoot.meta = {
|
||||||
};
|
name: folderName,
|
||||||
|
seq: 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const content = await jsonToCollectionBru(
|
const content = await jsonToCollectionBru(
|
||||||
folderRoot,
|
folderRoot,
|
||||||
@ -376,14 +381,16 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
|||||||
if (fs.existsSync(folderBruFilePath)) {
|
if (fs.existsSync(folderBruFilePath)) {
|
||||||
const oldFolderBruFileContent = await fs.promises.readFile(folderBruFilePath, 'utf8');
|
const oldFolderBruFileContent = await fs.promises.readFile(folderBruFilePath, 'utf8');
|
||||||
folderBruFileJsonContent = await collectionBruToJson(oldFolderBruFileContent);
|
folderBruFileJsonContent = await collectionBruToJson(oldFolderBruFileContent);
|
||||||
|
folderBruFileJsonContent.meta.name = newName;
|
||||||
} else {
|
} else {
|
||||||
folderBruFileJsonContent = {};
|
folderBruFileJsonContent = {
|
||||||
|
meta: {
|
||||||
|
name: newName,
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
folderBruFileJsonContent.meta = {
|
|
||||||
name: newName,
|
|
||||||
};
|
|
||||||
|
|
||||||
const folderBruFileContent = await jsonToCollectionBru(folderBruFileJsonContent, true);
|
const folderBruFileContent = await jsonToCollectionBru(folderBruFileJsonContent, true);
|
||||||
await writeFile(folderBruFilePath, folderBruFileContent);
|
await writeFile(folderBruFilePath, folderBruFileContent);
|
||||||
|
|
||||||
@ -425,14 +432,16 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
|||||||
if (fs.existsSync(folderBruFilePath)) {
|
if (fs.existsSync(folderBruFilePath)) {
|
||||||
const oldFolderBruFileContent = await fs.promises.readFile(folderBruFilePath, 'utf8');
|
const oldFolderBruFileContent = await fs.promises.readFile(folderBruFilePath, 'utf8');
|
||||||
folderBruFileJsonContent = await collectionBruToJson(oldFolderBruFileContent);
|
folderBruFileJsonContent = await collectionBruToJson(oldFolderBruFileContent);
|
||||||
|
folderBruFileJsonContent.meta.name = newName;
|
||||||
} else {
|
} else {
|
||||||
folderBruFileJsonContent = {};
|
folderBruFileJsonContent = {
|
||||||
|
meta: {
|
||||||
|
name: newName,
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
folderBruFileJsonContent.meta = {
|
|
||||||
name: newName,
|
|
||||||
};
|
|
||||||
|
|
||||||
const folderBruFileContent = await jsonToCollectionBru(folderBruFileJsonContent, true);
|
const folderBruFileContent = await jsonToCollectionBru(folderBruFileJsonContent, true);
|
||||||
await writeFile(folderBruFilePath, folderBruFileContent);
|
await writeFile(folderBruFilePath, folderBruFileContent);
|
||||||
|
|
||||||
@ -512,6 +521,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
|||||||
let data = {
|
let data = {
|
||||||
meta: {
|
meta: {
|
||||||
name: folderName,
|
name: folderName,
|
||||||
|
seq: 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const content = await jsonToCollectionBru(data, true); // isFolder flag
|
const content = await jsonToCollectionBru(data, true); // isFolder flag
|
||||||
@ -598,6 +608,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
|||||||
|
|
||||||
if (item?.root?.meta?.name) {
|
if (item?.root?.meta?.name) {
|
||||||
const folderBruFilePath = path.join(folderPath, 'folder.bru');
|
const folderBruFilePath = path.join(folderPath, 'folder.bru');
|
||||||
|
item.root.meta.seq = item.seq;
|
||||||
const folderContent = await jsonToCollectionBru(
|
const folderContent = await jsonToCollectionBru(
|
||||||
item.root,
|
item.root,
|
||||||
true // isFolder
|
true // isFolder
|
||||||
@ -731,17 +742,42 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
|||||||
|
|
||||||
ipcMain.handle('renderer:resequence-items', async (event, itemsToResequence) => {
|
ipcMain.handle('renderer:resequence-items', async (event, itemsToResequence) => {
|
||||||
try {
|
try {
|
||||||
for await (let item of itemsToResequence) {
|
for (let item of itemsToResequence) {
|
||||||
const bru = fs.readFileSync(item.pathname, 'utf8');
|
if (item?.type === 'folder') {
|
||||||
const jsonData = await bruToJsonViaWorker(bru);
|
const folderRootPath = path.join(item.pathname, 'folder.bru');
|
||||||
|
let folderBruJsonData = {
|
||||||
if (jsonData.seq !== item.seq) {
|
meta: {
|
||||||
jsonData.seq = item.seq;
|
name: path.basename(item?.pathname),
|
||||||
const content = await jsonToBruViaWorker(jsonData);
|
seq: item?.seq || 1
|
||||||
await writeFile(item.pathname, content);
|
}
|
||||||
|
};
|
||||||
|
if (fs.existsSync(folderRootPath)) {
|
||||||
|
const bru = fs.readFileSync(folderRootPath, 'utf8');
|
||||||
|
folderBruJsonData = await collectionBruToJson(bru);
|
||||||
|
if (!folderBruJsonData?.meta) {
|
||||||
|
folderBruJsonData.meta = {
|
||||||
|
name: path.basename(item?.pathname),
|
||||||
|
seq: item?.seq || 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (folderBruJsonData?.meta?.seq === item.seq) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
folderBruJsonData.meta.seq = item.seq;
|
||||||
|
}
|
||||||
|
const content = await jsonToCollectionBru(folderBruJsonData);
|
||||||
|
await writeFile(folderRootPath, content);
|
||||||
|
} else {
|
||||||
|
if (fs.existsSync(item.pathname)) {
|
||||||
|
const itemToSave = transformRequestToSaveToFilesystem(item);
|
||||||
|
const content = await jsonToBruViaWorker(itemToSave);
|
||||||
|
await writeFile(item.pathname, content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Error in resequence-items:', error);
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -760,6 +796,17 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('renderer:move-item', async (event, { targetDirname, sourcePathname }) => {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(targetDirname)) {
|
||||||
|
await copyPath(sourcePathname, targetDirname);
|
||||||
|
await removePath(sourcePathname);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('renderer:move-folder-item', async (event, folderPath, destinationPath) => {
|
ipcMain.handle('renderer:move-folder-item', async (event, folderPath, destinationPath) => {
|
||||||
try {
|
try {
|
||||||
const folderName = path.basename(folderPath);
|
const folderName = path.basename(folderPath);
|
||||||
|
@ -1255,6 +1255,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
folderUid
|
folderUid
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log("error", error);
|
||||||
deleteCancelToken(cancelTokenUid);
|
deleteCancelToken(cancelTokenUid);
|
||||||
mainWindow.webContents.send('main:run-folder-event', {
|
mainWindow.webContents.send('main:run-folder-event', {
|
||||||
type: 'testrun-ended',
|
type: 'testrun-ended',
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const { get, each, find, compact, filter } = require('lodash');
|
const { get, each, find, compact, isString, filter } = require('lodash');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const { getRequestUid } = require('../cache/requestUids');
|
const { getRequestUid } = require('../cache/requestUids');
|
||||||
const { uuid } = require('./common');
|
const { uuid } = require('./common');
|
||||||
@ -205,6 +205,14 @@ const findParentItemInCollection = (collection, itemUid) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const findParentItemInCollectionByPathname = (collection, pathname) => {
|
||||||
|
let flattenedItems = flattenItems(collection.items);
|
||||||
|
|
||||||
|
return find(flattenedItems, (item) => {
|
||||||
|
return item.items && find(item.items, (i) => i.pathname === pathname);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const getTreePathFromCollectionToItem = (collection, _item) => {
|
const getTreePathFromCollectionToItem = (collection, _item) => {
|
||||||
let path = [];
|
let path = [];
|
||||||
let item = findItemInCollection(collection, _item.uid);
|
let item = findItemInCollection(collection, _item.uid);
|
||||||
@ -272,12 +280,73 @@ const findItemInCollectionByPathname = (collection, pathname) => {
|
|||||||
return findItemByPathname(flattenedItems, pathname);
|
return findItemByPathname(flattenedItems, pathname);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const replaceTabsWithSpaces = (str, numSpaces = 2) => {
|
||||||
|
if (!str || !str.length || !isString(str)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return str.replaceAll('\t', ' '.repeat(numSpaces));
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformRequestToSaveToFilesystem = (item) => {
|
||||||
|
const _item = item.draft ? item.draft : item;
|
||||||
|
const itemToSave = {
|
||||||
|
uid: _item.uid,
|
||||||
|
type: _item.type,
|
||||||
|
name: _item.name,
|
||||||
|
seq: _item.seq,
|
||||||
|
request: {
|
||||||
|
method: _item.request.method,
|
||||||
|
url: _item.request.url,
|
||||||
|
params: [],
|
||||||
|
headers: [],
|
||||||
|
auth: _item.request.auth,
|
||||||
|
body: _item.request.body,
|
||||||
|
script: _item.request.script,
|
||||||
|
vars: _item.request.vars,
|
||||||
|
assertions: _item.request.assertions,
|
||||||
|
tests: _item.request.tests,
|
||||||
|
docs: _item.request.docs
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
each(_item.request.params, (param) => {
|
||||||
|
itemToSave.request.params.push({
|
||||||
|
uid: param.uid,
|
||||||
|
name: param.name,
|
||||||
|
value: param.value,
|
||||||
|
description: param.description,
|
||||||
|
type: param.type,
|
||||||
|
enabled: param.enabled
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
each(_item.request.headers, (header) => {
|
||||||
|
itemToSave.request.headers.push({
|
||||||
|
uid: header.uid,
|
||||||
|
name: header.name,
|
||||||
|
value: header.value,
|
||||||
|
description: header.description,
|
||||||
|
enabled: header.enabled
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (itemToSave.request.body.mode === 'json') {
|
||||||
|
itemToSave.request.body = {
|
||||||
|
...itemToSave.request.body,
|
||||||
|
json: replaceTabsWithSpaces(itemToSave.request.body.json)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemToSave;
|
||||||
|
}
|
||||||
|
|
||||||
const sortCollection = (collection) => {
|
const sortCollection = (collection) => {
|
||||||
const items = collection.items || [];
|
const items = collection.items || [];
|
||||||
let folderItems = filter(items, (item) => item.type === 'folder');
|
let folderItems = filter(items, (item) => item.type === 'folder');
|
||||||
let requestItems = filter(items, (item) => item.type !== 'folder');
|
let requestItems = filter(items, (item) => item.type !== 'folder');
|
||||||
|
|
||||||
folderItems = folderItems.sort((a, b) => a.name.localeCompare(b.name));
|
folderItems = folderItems.sort((a, b) => a.seq - b.seq);
|
||||||
requestItems = requestItems.sort((a, b) => a.seq - b.seq);
|
requestItems = requestItems.sort((a, b) => a.seq - b.seq);
|
||||||
|
|
||||||
collection.items = folderItems.concat(requestItems);
|
collection.items = folderItems.concat(requestItems);
|
||||||
@ -292,7 +361,7 @@ const sortFolder = (folder = {}) => {
|
|||||||
let folderItems = filter(items, (item) => item.type === 'folder');
|
let folderItems = filter(items, (item) => item.type === 'folder');
|
||||||
let requestItems = filter(items, (item) => item.type !== 'folder');
|
let requestItems = filter(items, (item) => item.type !== 'folder');
|
||||||
|
|
||||||
folderItems = folderItems.sort((a, b) => a.name.localeCompare(b.name));
|
folderItems = folderItems.sort((a, b) => a.seq - b.seq);
|
||||||
requestItems = requestItems.sort((a, b) => a.seq - b.seq);
|
requestItems = requestItems.sort((a, b) => a.seq - b.seq);
|
||||||
|
|
||||||
folder.items = folderItems.concat(requestItems);
|
folder.items = folderItems.concat(requestItems);
|
||||||
@ -410,11 +479,13 @@ module.exports = {
|
|||||||
findItemByPathname,
|
findItemByPathname,
|
||||||
findItemInCollectionByPathname,
|
findItemInCollectionByPathname,
|
||||||
findParentItemInCollection,
|
findParentItemInCollection,
|
||||||
|
findParentItemInCollectionByPathname,
|
||||||
parseBruFileMeta,
|
parseBruFileMeta,
|
||||||
|
hydrateRequestWithUuid,
|
||||||
|
transformRequestToSaveToFilesystem,
|
||||||
sortCollection,
|
sortCollection,
|
||||||
sortFolder,
|
sortFolder,
|
||||||
getAllRequestsInFolderRecursively,
|
getAllRequestsInFolderRecursively,
|
||||||
getEnvVars,
|
getEnvVars,
|
||||||
getFormattedCollectionOauth2Credentials,
|
getFormattedCollectionOauth2Credentials
|
||||||
hydrateRequestWithUuid
|
|
||||||
};
|
};
|
@ -282,6 +282,48 @@ function safeWriteFileSync(filePath, data) {
|
|||||||
fs.writeFileSync(safePath, data);
|
fs.writeFileSync(safePath, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recursively copies a source <file/directory> to a destination <directory>.
|
||||||
|
const copyPath = async (source, destination) => {
|
||||||
|
let targetPath = `${destination}/${path.basename(source)}`;
|
||||||
|
|
||||||
|
const targetPathExists = await fsPromises.access(targetPath).then(() => true).catch(() => false);
|
||||||
|
if (targetPathExists) {
|
||||||
|
throw new Error(`Cannot copy, ${path.basename(source)} already exists in ${path.basename(destination)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const copy = async (source, destination) => {
|
||||||
|
const stat = await fsPromises.lstat(source);
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
await fsPromises.mkdir(destination, { recursive: true });
|
||||||
|
const entries = await fsPromises.readdir(source);
|
||||||
|
for (const entry of entries) {
|
||||||
|
const srcPath = path.join(source, entry);
|
||||||
|
const destPath = path.join(destination, entry);
|
||||||
|
await copy(srcPath, destPath);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await fsPromises.copyFile(source, destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await copy(source, targetPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively removes a source <file/directory>.
|
||||||
|
const removePath = async (source) => {
|
||||||
|
const stat = await fsPromises.lstat(source);
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
const entries = await fsPromises.readdir(source);
|
||||||
|
for (const entry of entries) {
|
||||||
|
const entryPath = path.join(source, entry);
|
||||||
|
await removePath(entryPath);
|
||||||
|
}
|
||||||
|
await fsPromises.rmdir(source);
|
||||||
|
} else {
|
||||||
|
await fsPromises.unlink(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
isValidPathname,
|
isValidPathname,
|
||||||
exists,
|
exists,
|
||||||
@ -308,5 +350,7 @@ module.exports = {
|
|||||||
getCollectionStats,
|
getCollectionStats,
|
||||||
sizeInMB,
|
sizeInMB,
|
||||||
safeWriteFile,
|
safeWriteFile,
|
||||||
safeWriteFileSync
|
safeWriteFileSync,
|
||||||
|
copyPath,
|
||||||
|
removePath
|
||||||
};
|
};
|
||||||
|
116
packages/bruno-electron/src/utils/tests/filesystem/index.spec.js
Normal file
116
packages/bruno-electron/src/utils/tests/filesystem/index.spec.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs/promises');
|
||||||
|
const os = require('os');
|
||||||
|
const { copyPath, removePath } = require('../../filesystem');
|
||||||
|
const { initialCollectionStructure, finalCollectionStructure } = require('../fixtures/filesystem/copypath-removepath');
|
||||||
|
|
||||||
|
describe('File System Operations', () => {
|
||||||
|
let tempDir;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
// Create a temporary directory for each test
|
||||||
|
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bruno-test-'));
|
||||||
|
await createFilesAndFolders(tempDir, initialCollectionStructure);
|
||||||
|
const result = await verifyFilesAndFolders(tempDir, initialCollectionStructure);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
// clean up after each test
|
||||||
|
await fs.rm(tempDir, { recursive: true, force: true });
|
||||||
|
// confirm the temp directory is deleted
|
||||||
|
expect(await fs.access(tempDir).then(() => true).catch(() => false)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('copyPath and removePath', () => {
|
||||||
|
it('should move files and folder items multiple times', async () => {
|
||||||
|
|
||||||
|
{
|
||||||
|
const sourcePath = path.join(tempDir, 'folder_1', 'file_2.bru');
|
||||||
|
const destDir = path.join(tempDir, 'folder_1', 'folder_1_1');
|
||||||
|
await copyPath(sourcePath, destDir);
|
||||||
|
await removePath(sourcePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const sourcePath = path.join(tempDir, 'folder_2');
|
||||||
|
const destDir = path.join(tempDir, 'folder_1', 'folder_1_1');
|
||||||
|
await copyPath(sourcePath, destDir);
|
||||||
|
await removePath(sourcePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const sourcePath = path.join(tempDir, 'folder_1', 'folder_1_1', 'folder_2', 'file_2_2.bru');
|
||||||
|
const destDir = path.join(tempDir, 'folder_1');
|
||||||
|
await copyPath(sourcePath, destDir);
|
||||||
|
await removePath(sourcePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const sourcePath = path.join(tempDir, 'folder_1', 'folder_1_1', 'folder_2', 'folder_2_1');
|
||||||
|
const destDir = path.join(tempDir);
|
||||||
|
await copyPath(sourcePath, destDir);
|
||||||
|
await removePath(sourcePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await verifyFilesAndFolders(tempDir, finalCollectionStructure);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should throw an error move file/folder if the destination has the same filename', async () => {
|
||||||
|
{
|
||||||
|
const sourcePath = path.join(tempDir, 'folder_1', 'file_dup.bru');
|
||||||
|
const destDir = path.join(tempDir, 'folder_1');
|
||||||
|
await expect(copyPath(sourcePath, destDir)).rejects.toThrow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// create folders and files recursively based on the defined json structure
|
||||||
|
const createFilesAndFolders = async (dir, filesAndFolders) => {
|
||||||
|
for (const item of filesAndFolders) {
|
||||||
|
const itemPath = path.join(dir, item.name);
|
||||||
|
if (item.type === 'folder') {
|
||||||
|
await fs.mkdir(itemPath, { recursive: true });
|
||||||
|
await createFilesAndFolders(itemPath, item.files);
|
||||||
|
} else {
|
||||||
|
await fs.writeFile(itemPath, item.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if a file/folder doesnt exist, return false
|
||||||
|
// should only contain files and folders that are defined in the json structure
|
||||||
|
const verifyFilesAndFolders = async (dir, filesAndFolders) => {
|
||||||
|
const verify = async (dir, filesAndFolders) => {
|
||||||
|
const files = await fs.readdir(dir);
|
||||||
|
if (files.length !== filesAndFolders.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const file of files) {
|
||||||
|
const itemPath = path.join(dir, file);
|
||||||
|
const item = filesAndFolders.find(f => f.name === file);
|
||||||
|
if (!item) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (item.type === 'folder') {
|
||||||
|
return await verify(itemPath, item.files);
|
||||||
|
} else {
|
||||||
|
return await fs.readFile(itemPath, 'utf8').then(content => content === item.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const verified = await verify(dir, filesAndFolders);
|
||||||
|
return verified;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
155
packages/bruno-electron/src/utils/tests/fixtures/filesystem/copypath-removepath.js
vendored
Normal file
155
packages/bruno-electron/src/utils/tests/fixtures/filesystem/copypath-removepath.js
vendored
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
const initialCollectionStructure = [
|
||||||
|
{
|
||||||
|
"name": "folder_1",
|
||||||
|
"type": "folder",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "file_1.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_1_content"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "file_2.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_2_content"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "folder_1_1",
|
||||||
|
"type": "folder",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "file_1_1.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_1_1_content"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "file_1_2.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_1_2_content"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "file_1_3.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_1_3_content"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "file_dup.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_dup_content"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "folder_2",
|
||||||
|
"type": "folder",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "file_2_1.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_2_1_content"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "file_2_2.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_2_2_content"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "folder_2_1",
|
||||||
|
"type": "folder",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "file_2_1_1.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_2_1_1_content"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "file_dup.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_dup_content"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const finalCollectionStructure = [
|
||||||
|
{
|
||||||
|
"name": "folder_1",
|
||||||
|
"type": "folder",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "file_1.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_1_content"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "folder_1_1",
|
||||||
|
"type": "folder",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "file_1_1.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_1_1_content"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "file_1_2.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_1_2_content"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "file_2.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_2_content"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "folder_2",
|
||||||
|
"type": "folder",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "file_2_1.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_2_1_content"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "file_1_3.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_1_3_content"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "file_2_2.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_2_2_content"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "file_dup.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_dup_content"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "folder_2_1",
|
||||||
|
"type": "folder",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "file_2_1_1.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_2_1_1_content"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "file_dup.bru",
|
||||||
|
"type": "file",
|
||||||
|
"content": "file_dup_content"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = { initialCollectionStructure, finalCollectionStructure };
|
@ -340,7 +340,8 @@ const folderRootSchema = Yup.object({
|
|||||||
.nullable(),
|
.nullable(),
|
||||||
docs: Yup.string().nullable(),
|
docs: Yup.string().nullable(),
|
||||||
meta: Yup.object({
|
meta: Yup.object({
|
||||||
name: Yup.string().nullable()
|
name: Yup.string().nullable(),
|
||||||
|
seq: Yup.number().min(1).nullable()
|
||||||
})
|
})
|
||||||
.noUnknown(true)
|
.noUnknown(true)
|
||||||
.strict()
|
.strict()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user