diff --git a/package-lock.json b/package-lock.json
index 9d577e71b..8d95d677c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,10 +14,13 @@
"@fortawesome/react-fontawesome": "^0.1.16",
"@grafnode/www": "^0.0.1",
"@tabler/icons": "^1.46.0",
+ "@tippyjs/react": "^4.2.6",
"babel-plugin-styled-components": "^2.0.2",
"babel-preset-next": "^1.4.0",
"eslint": "7.32.0",
"eslint-config-next": "12.0.4",
+ "immer": "^9.0.7",
+ "nanoid": "^3.1.30",
"next": "12.0.4",
"react": "17.0.2",
"react-dom": "17.0.2",
@@ -3529,6 +3532,15 @@
"@octokit/openapi-types": "^11.2.0"
}
},
+ "node_modules/@popperjs/core": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.0.tgz",
+ "integrity": "sha512-zrsUxjLOKAzdewIDRWy9nsV1GQsKBCWaGwsZQlCgr6/q+vjyZhFgqedLfFBuI9anTPEUT4APq9Mu0SZBTzIcGQ==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
"node_modules/@rushstack/eslint-patch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz",
@@ -3547,6 +3559,18 @@
"react-dom": "^16.x || 17.x"
}
},
+ "node_modules/@tippyjs/react": {
+ "version": "4.2.6",
+ "resolved": "https://registry.npmjs.org/@tippyjs/react/-/react-4.2.6.tgz",
+ "integrity": "sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==",
+ "dependencies": {
+ "tippy.js": "^6.3.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
"node_modules/@tootallnate/once": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
@@ -7905,6 +7929,15 @@
"node": ">=12.0.0"
}
},
+ "node_modules/immer": {
+ "version": "9.0.7",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.7.tgz",
+ "integrity": "sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
"node_modules/import-cwd": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz",
@@ -13326,6 +13359,14 @@
"node": ">=0.6.0"
}
},
+ "node_modules/tippy.js": {
+ "version": "6.3.7",
+ "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
+ "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
+ "dependencies": {
+ "@popperjs/core": "^2.9.0"
+ }
+ },
"node_modules/tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
@@ -16858,6 +16899,11 @@
"@octokit/openapi-types": "^11.2.0"
}
},
+ "@popperjs/core": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.0.tgz",
+ "integrity": "sha512-zrsUxjLOKAzdewIDRWy9nsV1GQsKBCWaGwsZQlCgr6/q+vjyZhFgqedLfFBuI9anTPEUT4APq9Mu0SZBTzIcGQ=="
+ },
"@rushstack/eslint-patch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz",
@@ -16869,6 +16915,14 @@
"integrity": "sha512-GufZYxw32OcqejSUpn5XdZi7zP/d+tUZH3S+mMlv3AnMn6MStOBKXOxqWYrJ529hjj1m5JHeghwHmHpj3SRJYg==",
"requires": {}
},
+ "@tippyjs/react": {
+ "version": "4.2.6",
+ "resolved": "https://registry.npmjs.org/@tippyjs/react/-/react-4.2.6.tgz",
+ "integrity": "sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==",
+ "requires": {
+ "tippy.js": "^6.3.1"
+ }
+ },
"@tootallnate/once": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
@@ -20274,6 +20328,11 @@
"queue": "6.0.2"
}
},
+ "immer": {
+ "version": "9.0.7",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.7.tgz",
+ "integrity": "sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA=="
+ },
"import-cwd": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz",
@@ -24425,6 +24484,14 @@
"setimmediate": "^1.0.4"
}
},
+ "tippy.js": {
+ "version": "6.3.7",
+ "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
+ "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
+ "requires": {
+ "@popperjs/core": "^2.9.0"
+ }
+ },
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
diff --git a/packages/grafnode-components/src/components/Dropdown/StyledWrapper.js b/packages/grafnode-components/src/components/Dropdown/StyledWrapper.js
new file mode 100644
index 000000000..83ce5785d
--- /dev/null
+++ b/packages/grafnode-components/src/components/Dropdown/StyledWrapper.js
@@ -0,0 +1,34 @@
+import styled from 'styled-components';
+
+const Wrapper = styled.div`
+ .dropdown-toggle {
+ &:hover {
+ color: black;
+ }
+ }
+
+ .tippy-box {
+ min-width: 135px;
+ background-color: white;
+ color: rgb(82 82 82);
+ box-shadow: rgb(50 50 93 / 25%) 0px 6px 12px -2px, rgb(0 0 0 / 30%) 0px 3px 7px -3px;
+
+ .tippy-content {
+ padding-left: 0;
+ padding-right: 0;
+
+ .dropdown-item {
+ display: flex;
+ align-items: center;
+ padding: .3rem .5rem;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #eee;
+ }
+ }
+ }
+ }
+`;
+
+export default Wrapper;
diff --git a/packages/grafnode-components/src/components/Dropdown/index.js b/packages/grafnode-components/src/components/Dropdown/index.js
new file mode 100644
index 000000000..ae30d125f
--- /dev/null
+++ b/packages/grafnode-components/src/components/Dropdown/index.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import Tippy from '@tippyjs/react';
+import StyledWrapper from './StyledWrapper';
+
+const Dropdown = ({icon, children, onCreate, placement}) => {
+ return (
+
+
+ {icon}
+
+
+ );
+};
+
+export default Dropdown;
diff --git a/packages/grafnode-components/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js b/packages/grafnode-components/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js
new file mode 100644
index 000000000..e80da781c
--- /dev/null
+++ b/packages/grafnode-components/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js
@@ -0,0 +1,19 @@
+import styled from 'styled-components';
+
+const Wrapper = styled.div`
+ .collection-item-name {
+ height: 1.875rem;
+ cursor: pointer;
+ user-select: none;
+
+ .rotate-90 {
+ transform: rotateZ(90deg);
+ }
+
+ &.item-focused-in-tab, &:hover {
+ background:#ededed;
+ }
+ }
+`;
+
+export default Wrapper;
diff --git a/packages/grafnode-components/src/components/Sidebar/Collections/Collection/CollectionItem/index.js b/packages/grafnode-components/src/components/Sidebar/Collections/Collection/CollectionItem/index.js
new file mode 100644
index 000000000..57fb896d5
--- /dev/null
+++ b/packages/grafnode-components/src/components/Sidebar/Collections/Collection/CollectionItem/index.js
@@ -0,0 +1,84 @@
+import React from 'react';
+import range from 'lodash/range';
+import { IconChevronRight } from '@tabler/icons';
+import classnames from 'classnames';
+
+import StyledWrapper from './StyledWrapper';
+
+const CollectionItem = ({item, collectionId, actions, dispatch, activeRequestTabId}) => {
+
+ const iconClassName = classnames({
+ 'rotate-90': item.collapsed
+ });
+
+ const itemRowClassName = classnames('flex collection-item-name items-center', {
+ 'item-focused-in-tab': item.id == activeRequestTabId
+ });
+
+ const handleClick = () => {
+ dispatch({
+ type: actions.SIDEBAR_COLLECTION_ITEM_CLICK,
+ itemId: item.id,
+ collectionId: collectionId
+ });
+ };
+
+ let indents = range(item.depth);
+
+ return (
+
+
+
+ {indents && indents.length ? indents.map((i) => {
+ return (
+
+ {/* Indent */}
+
+ );
+ }) : null}
+
+
+ {item.items && item.items.length ? (
+
+ ) : null}
+
+
+
{item.name}
+
+
+
+
+ {item.collapsed ? (
+
+ {item.items && item.items.length ? item.items.map((i) => {
+ return
+ }) : null}
+
+ ) : null}
+
+ );
+};
+
+export default CollectionItem;
\ No newline at end of file
diff --git a/packages/grafnode-components/src/components/Sidebar/Collections/Collection/StyledWrapper.js b/packages/grafnode-components/src/components/Sidebar/Collections/Collection/StyledWrapper.js
new file mode 100644
index 000000000..3e50288a2
--- /dev/null
+++ b/packages/grafnode-components/src/components/Sidebar/Collections/Collection/StyledWrapper.js
@@ -0,0 +1,21 @@
+import styled from 'styled-components';
+
+const Wrapper = styled.div`
+ .collection-name {
+ height: 1.875rem;
+ cursor: pointer;
+ user-select: none;
+ padding-left: 8px;
+ padding-right: 8px;
+
+ .rotate-90 {
+ transform: rotateZ(90deg);
+ }
+
+ &:hover {
+ background:#ededed;
+ }
+ }
+`;
+
+export default Wrapper;
diff --git a/packages/grafnode-components/src/components/Sidebar/Collections/Collection/index.js b/packages/grafnode-components/src/components/Sidebar/Collections/Collection/index.js
new file mode 100644
index 000000000..0d1f4b770
--- /dev/null
+++ b/packages/grafnode-components/src/components/Sidebar/Collections/Collection/index.js
@@ -0,0 +1,48 @@
+import React from 'react';
+import { IconChevronRight } from '@tabler/icons';
+import CollectionItem from './CollectionItem';
+import classnames from 'classnames';
+
+import StyledWrapper from './StyledWrapper';
+
+const Collection = ({collection, actions, dispatch, activeRequestTabId}) => {
+
+ const iconClassName = classnames({
+ 'rotate-90': collection.collapsed
+ });
+
+ const handleClick = () => {
+ dispatch({
+ type: actions.SIDEBAR_COLLECTION_CLICK,
+ id: collection.id
+ });
+ };
+
+ return (
+
+
+
+ {collection.name}
+
+
+
+ {collection.collapsed ? (
+
+ {collection.items && collection.items.length ? collection.items.map((i) => {
+ return
+ }) : null}
+
+ ) : null}
+
+
+ );
+};
+
+export default Collection;
\ No newline at end of file
diff --git a/packages/grafnode-components/src/components/Sidebar/Collections/index.js b/packages/grafnode-components/src/components/Sidebar/Collections/index.js
new file mode 100644
index 000000000..93199b4fe
--- /dev/null
+++ b/packages/grafnode-components/src/components/Sidebar/Collections/index.js
@@ -0,0 +1,20 @@
+import React from 'react';
+import Collection from './Collection';
+
+const Collections = ({collections, actions, dispatch, activeRequestTabId}) => {
+ return (
+
+ {collections && collections.length ? collections.map((c) => {
+ return
+ }) : null}
+
+ );
+};
+
+export default Collections;
\ No newline at end of file
diff --git a/packages/grafnode-components/src/components/Sidebar/index.js b/packages/grafnode-components/src/components/Sidebar/index.js
new file mode 100644
index 000000000..1ad793e02
--- /dev/null
+++ b/packages/grafnode-components/src/components/Sidebar/index.js
@@ -0,0 +1,38 @@
+import React from 'react';
+import Collections from './Collections';
+import { IconDatabase, IconSearch } from '@tabler/icons';
+
+const Sidebar = ({collections, actions, dispatch, activeRequestTabId}) => {
+ return (
+
+ );
+};
+
+export default Sidebar;
\ No newline at end of file
diff --git a/packages/grafnode-components/src/index.js b/packages/grafnode-components/src/index.js
index e4a1c8de5..9753a4832 100644
--- a/packages/grafnode-components/src/index.js
+++ b/packages/grafnode-components/src/index.js
@@ -1,4 +1,7 @@
import Navbar from './components/Navbar';
+import Sidebar from './components/Sidebar';
-export default Navbar;
-
+export {
+ Navbar,
+ Sidebar
+};
diff --git a/packages/grafnode-run/jsconfig.json b/packages/grafnode-run/jsconfig.json
new file mode 100644
index 000000000..f0c3aa3ad
--- /dev/null
+++ b/packages/grafnode-run/jsconfig.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "target": "es2017",
+ "allowSyntheticDefaultImports": false,
+ "baseUrl": "./",
+ "paths": {
+ "components/*": ["src/components/*"],
+ "pageComponents/*": ["src/pageComponents/*"],
+ "providers/*": ["src/providers/*"]
+ }
+ },
+ "exclude": ["node_modules", "dist"]
+}
\ No newline at end of file
diff --git a/packages/grafnode-run/package-lock.json b/packages/grafnode-run/package-lock.json
index 37d59a526..1ab1e9add 100644
--- a/packages/grafnode-run/package-lock.json
+++ b/packages/grafnode-run/package-lock.json
@@ -13,6 +13,9 @@
"@fortawesome/react-fontawesome": "^0.1.16",
"@grafnode/www": "^0.0.1",
"@tabler/icons": "^1.46.0",
+ "@tippyjs/react": "^4.2.6",
+ "immer": "^9.0.7",
+ "nanoid": "^3.1.30",
"next": "12.0.4",
"react": "17.0.2",
"react-dom": "17.0.2",
@@ -2623,6 +2626,15 @@
"node": ">= 8"
}
},
+ "node_modules/@popperjs/core": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.0.tgz",
+ "integrity": "sha512-zrsUxjLOKAzdewIDRWy9nsV1GQsKBCWaGwsZQlCgr6/q+vjyZhFgqedLfFBuI9anTPEUT4APq9Mu0SZBTzIcGQ==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
"node_modules/@rushstack/eslint-patch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz",
@@ -2642,6 +2654,18 @@
"react-dom": "^16.x || 17.x"
}
},
+ "node_modules/@tippyjs/react": {
+ "version": "4.2.6",
+ "resolved": "https://registry.npmjs.org/@tippyjs/react/-/react-4.2.6.tgz",
+ "integrity": "sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==",
+ "dependencies": {
+ "tippy.js": "^6.3.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
"node_modules/@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@@ -5059,6 +5083,15 @@
"node": ">=12.0.0"
}
},
+ "node_modules/immer": {
+ "version": "9.0.7",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.7.tgz",
+ "integrity": "sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
"node_modules/import-cwd": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz",
@@ -7565,6 +7598,14 @@
"node": ">=0.6.0"
}
},
+ "node_modules/tippy.js": {
+ "version": "6.3.7",
+ "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
+ "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
+ "dependencies": {
+ "@popperjs/core": "^2.9.0"
+ }
+ },
"node_modules/tmp": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
@@ -9782,6 +9823,11 @@
"fastq": "^1.6.0"
}
},
+ "@popperjs/core": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.0.tgz",
+ "integrity": "sha512-zrsUxjLOKAzdewIDRWy9nsV1GQsKBCWaGwsZQlCgr6/q+vjyZhFgqedLfFBuI9anTPEUT4APq9Mu0SZBTzIcGQ=="
+ },
"@rushstack/eslint-patch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz",
@@ -9794,6 +9840,14 @@
"integrity": "sha512-GufZYxw32OcqejSUpn5XdZi7zP/d+tUZH3S+mMlv3AnMn6MStOBKXOxqWYrJ529hjj1m5JHeghwHmHpj3SRJYg==",
"requires": {}
},
+ "@tippyjs/react": {
+ "version": "4.2.6",
+ "resolved": "https://registry.npmjs.org/@tippyjs/react/-/react-4.2.6.tgz",
+ "integrity": "sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==",
+ "requires": {
+ "tippy.js": "^6.3.1"
+ }
+ },
"@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@@ -11646,6 +11700,11 @@
"queue": "6.0.2"
}
},
+ "immer": {
+ "version": "9.0.7",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.7.tgz",
+ "integrity": "sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA=="
+ },
"import-cwd": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz",
@@ -13517,6 +13576,14 @@
"setimmediate": "^1.0.4"
}
},
+ "tippy.js": {
+ "version": "6.3.7",
+ "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
+ "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
+ "requires": {
+ "@popperjs/core": "^2.9.0"
+ }
+ },
"tmp": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
diff --git a/packages/grafnode-run/package.json b/packages/grafnode-run/package.json
index 995554cf5..a70bdef53 100644
--- a/packages/grafnode-run/package.json
+++ b/packages/grafnode-run/package.json
@@ -25,6 +25,9 @@
"@fortawesome/react-fontawesome": "^0.1.16",
"@grafnode/www": "^0.0.1",
"@tabler/icons": "^1.46.0",
+ "@tippyjs/react": "^4.2.6",
+ "immer": "^9.0.7",
+ "nanoid": "^3.1.30",
"next": "12.0.4",
"react": "17.0.2",
"react-dom": "17.0.2",
diff --git a/packages/grafnode-run/src/pageComponents/Main/StyledWrapper.js b/packages/grafnode-run/src/pageComponents/Main/StyledWrapper.js
new file mode 100644
index 000000000..2d3ccdd1b
--- /dev/null
+++ b/packages/grafnode-run/src/pageComponents/Main/StyledWrapper.js
@@ -0,0 +1,60 @@
+import styled from 'styled-components';
+
+const Wrapper = styled.div`
+ display: flex;
+ width: 100%;
+ height: 100%;
+ min-height: calc(100vh - 38px);
+
+ aside {
+ min-width: 230px;
+ border-right: solid 1px #e1e1e1;
+ }
+
+ section.main {
+ display: flex;
+
+ section.request-pane, section.response-pane {
+ }
+ }
+
+ div.drag-request {
+ display: flex;
+ width: 1px;
+ padding: 0;
+ cursor: col-resize;
+ background: #e1e1e1;
+
+ &:hover {
+ background: silver;
+ }
+ }
+
+ .fw-600 {
+ font-weight: 600;
+ }
+
+ .react-tabs {
+ .react-tabs__tab-list {
+ padding-left: 1rem;
+ border-bottom: 1px solid #cfcfcf;
+
+ .react-tabs__tab--selected {
+ border-color: #cfcfcf;
+ }
+ }
+ }
+
+ .collection-filter {
+ input {
+ border: 1px solid rgb(211 211 211);
+ border-radius: 2px;
+
+ &:focus {
+ outline: none;
+ }
+ }
+ }
+`;
+
+export default Wrapper;
diff --git a/packages/grafnode-run/src/pageComponents/Main/index.js b/packages/grafnode-run/src/pageComponents/Main/index.js
new file mode 100644
index 000000000..c631f3797
--- /dev/null
+++ b/packages/grafnode-run/src/pageComponents/Main/index.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import {Navbar, Sidebar} from '@grafnode/components';
+import actions from 'providers/Store/actions';
+import { useStore } from 'providers/Store';
+import StyledWrapper from './StyledWrapper';
+
+export default function Main() {
+ const [state, dispatch] = useStore();
+
+ const {
+ collections,
+ activeRequestTabId
+ } = state;
+
+ console.log(actions);
+
+ return (
+
+
+
+
+
+ Request & Response Tabs
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/packages/grafnode-run/src/pages/_app.js b/packages/grafnode-run/src/pages/_app.js
index 5a8f965a4..f7e259e84 100644
--- a/packages/grafnode-run/src/pages/_app.js
+++ b/packages/grafnode-run/src/pages/_app.js
@@ -1,8 +1,14 @@
+import { StoreProvider } from 'providers/Store';
+
import '../styles/globals.css'
import 'tailwindcss/dist/tailwind.min.css';
function MyApp({ Component, pageProps }) {
- return
+ return (
+
+
+
+ );
}
export default MyApp
diff --git a/packages/grafnode-run/src/pages/index.js b/packages/grafnode-run/src/pages/index.js
index ee372a94a..413ef924b 100644
--- a/packages/grafnode-run/src/pages/index.js
+++ b/packages/grafnode-run/src/pages/index.js
@@ -1,5 +1,5 @@
import Head from 'next/head';
-import Navbar from '@grafnode/components';
+import Main from 'pageComponents/Main';
export default function Home() {
return (
@@ -10,7 +10,7 @@ export default function Home() {
-
+
)
diff --git a/packages/grafnode-run/src/providers/Store/actions.js b/packages/grafnode-run/src/providers/Store/actions.js
new file mode 100644
index 000000000..dc1a58ac3
--- /dev/null
+++ b/packages/grafnode-run/src/providers/Store/actions.js
@@ -0,0 +1,11 @@
+const SIDEBAR_COLLECTION_CLICK = "SIDEBAR_COLLECTION_CLICK";
+const SIDEBAR_COLLECTION_ITEM_CLICK = "SIDEBAR_COLLECTION_ITEM_CLICK";
+const REQUEST_TAB_CLICK = "REQUEST_TAB_CLICK";
+const REQUEST_TAB_CLOSE = "REQUEST_TAB_CLOSE";
+
+export default {
+ SIDEBAR_COLLECTION_CLICK,
+ SIDEBAR_COLLECTION_ITEM_CLICK,
+ REQUEST_TAB_CLICK,
+ REQUEST_TAB_CLOSE
+};
diff --git a/packages/grafnode-run/src/providers/Store/index.js b/packages/grafnode-run/src/providers/Store/index.js
new file mode 100644
index 000000000..6fda13cca
--- /dev/null
+++ b/packages/grafnode-run/src/providers/Store/index.js
@@ -0,0 +1,79 @@
+import React, { useContext, useReducer, createContext } from 'react';
+import reducer from './reducer';
+import { nanoid } from 'nanoid';
+
+export const StoreContext = createContext();
+
+const tabId1 = nanoid();
+
+const collection = {
+ "id": nanoid(),
+ "name": "SpaceX",
+ "items": [
+ {
+ "id": nanoid(),
+ "name": "Launches",
+ "depth": 1,
+ "items": [
+ {
+ "id": nanoid(),
+ "depth": 2,
+ "name": "Capsules",
+ "request": {
+ "url": "https://api.spacex.land/graphql/",
+ "method": "POST",
+ "headers": [],
+ "body": {
+ "mimeType": "application/graphql",
+ "graphql": {
+ "query": "{\n launchesPast(limit: 10) {\n mission_name\n launch_date_local\n launch_site {\n site_name_long\n }\n links {\n article_link\n video_link\n }\n rocket {\n rocket_name\n first_stage {\n cores {\n flight\n core {\n reuse_count\n status\n }\n }\n }\n second_stage {\n payloads {\n payload_type\n payload_mass_kg\n payload_mass_lbs\n }\n }\n }\n ships {\n name\n home_port\n image\n }\n }\n}",
+ "variables": ""
+ }
+ }
+ }
+ },
+ {
+ "id": nanoid(),
+ "depth": 2,
+ "name": "Missions",
+ "request": {
+ "url": "https://api.spacex.land/graphql/",
+ "method": "POST",
+ "headers": [],
+ "body": {
+ "mimeType": "application/graphql",
+ "graphql": {
+ "query": "{\n launches {\n launch_site {\n site_id\n site_name\n site_name_long\n }\n launch_success\n }\n}",
+ "variables": ""
+ }
+ }
+ }
+ }
+ ]
+ }
+ ]
+};
+
+const initialState = {
+ collections: [collection],
+ activeRequestTabId: null,
+ requestTabs: []
+};
+
+export const StoreProvider = props => {
+ const [state, dispatch] = useReducer(reducer, initialState);
+
+ return ;
+};
+
+export const useStore = () => {
+ const context = useContext(StoreContext);
+
+ if (context === undefined) {
+ throw new Error(`useStore must be used within a StoreProvider`);
+ }
+
+ return context;
+};
+
+export default StoreProvider;
diff --git a/packages/grafnode-run/src/providers/Store/reducer.js b/packages/grafnode-run/src/providers/Store/reducer.js
new file mode 100644
index 000000000..cfea0b46a
--- /dev/null
+++ b/packages/grafnode-run/src/providers/Store/reducer.js
@@ -0,0 +1,79 @@
+import produce from 'immer';
+import find from 'lodash/find';
+import filter from 'lodash/filter';
+import actions from './actions';
+import {
+ flattenItems,
+ findItem,
+ isItemARequest,
+ itemIsOpenedInTabs
+} from './utils';
+
+const reducer = (state, action) => {
+ switch (action.type) {
+ case actions.SIDEBAR_COLLECTION_CLICK: {
+ return produce(state, (draft) => {
+ const collecton = find(draft.collections, (c) => c.id === action.id);
+
+ if(collecton) {
+ collecton.collapsed = !collecton.collapsed;
+ }
+ });
+ }
+
+ case actions.SIDEBAR_COLLECTION_ITEM_CLICK: {
+ return produce(state, (draft) => {
+ const collecton = find(draft.collections, (c) => c.id === action.collectionId);
+
+ if(collecton) {
+ let flattenedItems = flattenItems(collecton.items);
+ let item = findItem(flattenedItems, action.itemId);
+
+ if(item) {
+ item.collapsed = !item.collapsed;
+
+ if(isItemARequest(item)) {
+ if(itemIsOpenedInTabs(item, draft.requestTabs)) {
+ draft.activeRequestTabId = item.id;
+ } else {
+ draft.requestTabs.push({
+ id: item.id,
+ name: item.name,
+ method: item.request.method,
+ collectionId: collecton.id
+ });
+ draft.activeRequestTabId = item.id;
+ }
+ }
+ }
+ }
+ });
+ }
+
+ case actions.REQUEST_TAB_CLICK: {
+ return produce(state, (draft) => {
+ draft.activeRequestTabId = action.requestTab.id;
+ });
+ }
+
+
+ case actions.REQUEST_TAB_CLOSE: {
+ return produce(state, (draft) => {
+ draft.requestTabs = filter(draft.requestTabs, (rt) => rt.id !== action.requestTab.id);
+
+ if(draft.requestTabs && draft.requestTabs.length) {
+ draft.activeRequestTabId = draft.requestTabs[0].id;
+ console.log(draft.activeRequestTabId);
+ } else {
+ draft.activeRequestTabId = null;
+ }
+ });
+ }
+
+ default: {
+ return state;
+ }
+ }
+}
+
+export default reducer;
\ No newline at end of file
diff --git a/packages/grafnode-run/src/providers/Store/utils.js b/packages/grafnode-run/src/providers/Store/utils.js
new file mode 100644
index 000000000..72ebc4e9c
--- /dev/null
+++ b/packages/grafnode-run/src/providers/Store/utils.js
@@ -0,0 +1,32 @@
+import each from 'lodash/each';
+import find from 'lodash/find';
+
+export const flattenItems = (items = []) => {
+ const flattenedItems = [];
+
+ const flatten = (itms, flattened) => {
+ each(itms, (i) => {
+ flattened.push(i);
+
+ if(i.items && i.items.length) {
+ flatten(i.items, flattened);
+ }
+ })
+ }
+
+ flatten(items, flattenedItems);
+
+ return flattenedItems;
+};
+
+export const findItem = (items = [], itemId) => {
+ return find(items, (i) => i.id === itemId);
+};
+
+export const isItemARequest = (item) => {
+ return item.hasOwnProperty('request');
+};
+
+export const itemIsOpenedInTabs = (item, tabs) => {
+ return find(tabs, (t) => t.id === item.id);
+};
diff --git a/packages/grafnode-run/src/styles/globals.css b/packages/grafnode-run/src/styles/globals.css
index e5e2dcc23..78178bcd3 100644
--- a/packages/grafnode-run/src/styles/globals.css
+++ b/packages/grafnode-run/src/styles/globals.css
@@ -1,16 +1,16 @@
-html,
-body {
- padding: 0;
+html, body {
margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
- Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+ padding: 0;
+ font-size: 1rem;
+ color: rgb(62 62 62);
+
+ font-kerning: none;
+ text-rendering: optimizeSpeed;
+ letter-spacing: normal;
+ /* font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, "Apple Color Emoji", Arial, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol"; */
+ font-family: Inter, sans-serif !important;
}
-a {
- color: inherit;
- text-decoration: none;
-}
-
-* {
- box-sizing: border-box;
-}
+body {
+ font-size: 0.875rem;
+}
\ No newline at end of file