mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-25 01:14:23 +01:00
feat: Request and Responses Panes, CodeMirror View
This commit is contained in:
parent
f6732e66a0
commit
8b586bdfae
5104
package-lock.json
generated
5104
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -5,8 +5,6 @@
|
|||||||
"packages/grafnode-components",
|
"packages/grafnode-components",
|
||||||
"packages/grafnode-www"
|
"packages/grafnode-www"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.16.0",
|
"@babel/core": "^7.16.0",
|
||||||
"@babel/preset-env": "^7.16.4",
|
"@babel/preset-env": "^7.16.4",
|
||||||
@ -17,6 +15,7 @@
|
|||||||
"html-loader": "^3.0.1",
|
"html-loader": "^3.0.1",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
"lerna": "^4.0.0",
|
"lerna": "^4.0.0",
|
||||||
|
"mini-css-extract-plugin": "^2.4.5",
|
||||||
"style-loader": "^3.3.1",
|
"style-loader": "^3.3.1",
|
||||||
"webpack": "^5.64.4",
|
"webpack": "^5.64.4",
|
||||||
"webpack-cli": "^4.9.1"
|
"webpack-cli": "^4.9.1"
|
||||||
|
3804
packages/grafnode-components/package-lock.json
generated
Normal file
3804
packages/grafnode-components/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"watch:build": "webpack --watch --mode development",
|
||||||
"build": "webpack --mode production",
|
"build": "webpack --mode production",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
@ -24,6 +25,7 @@
|
|||||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||||
"@fortawesome/react-fontawesome": "^0.1.16",
|
"@fortawesome/react-fontawesome": "^0.1.16",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
|
"codemirror": "^5.64.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-tabs": "^3.2.3",
|
"react-tabs": "^3.2.3",
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
div.CodeMirror {
|
||||||
|
border: solid 1px #e1e1e1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
||||||
|
|
@ -0,0 +1,46 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import * as CodeMirror from 'codemirror';
|
||||||
|
|
||||||
|
const QueryEditor = ({query, onChange, width}) => {
|
||||||
|
const [cmEditor, setCmEditor] = useState(null);
|
||||||
|
const editor = useRef();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editor.current && !cmEditor) {
|
||||||
|
const _cmEditor = CodeMirror.fromTextArea(editor.current, {
|
||||||
|
value: '',
|
||||||
|
lineNumbers: true
|
||||||
|
});
|
||||||
|
|
||||||
|
_cmEditor.setValue(query || 'query { }');
|
||||||
|
|
||||||
|
setCmEditor(_cmEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if(cmEditor) {
|
||||||
|
cmEditor.toTextArea();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [editor.current, cmEditor]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper>
|
||||||
|
<div className="mt-4">
|
||||||
|
<textarea
|
||||||
|
id="operation"
|
||||||
|
style={{
|
||||||
|
width: `${width}px`,
|
||||||
|
height: '400px'
|
||||||
|
}}
|
||||||
|
ref={editor}
|
||||||
|
className="cm-editor"
|
||||||
|
>
|
||||||
|
</textarea>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QueryEditor;
|
@ -0,0 +1,26 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
div.CodeMirror {
|
||||||
|
border: solid 1px #e1e1e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.overlay{
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
background: rgb(243 243 243 / 78%);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
||||||
|
|
@ -0,0 +1,66 @@
|
|||||||
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
|
import { IconRefresh } from '@tabler/icons';
|
||||||
|
import StopWatch from '../StopWatch';
|
||||||
|
import * as CodeMirror from 'codemirror';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const QueryResult = ({data, isLoading, width}) => {
|
||||||
|
const [cmEditor, setCmEditor] = useState(null);
|
||||||
|
const editor = useRef();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editor.current && !cmEditor) {
|
||||||
|
const _cmEditor = CodeMirror.fromTextArea(editor.current, {
|
||||||
|
value: '',
|
||||||
|
lineNumbers: true
|
||||||
|
});
|
||||||
|
|
||||||
|
setCmEditor(_cmEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(editor.current && cmEditor && data && !isLoading) {
|
||||||
|
cmEditor.setValue(JSON.stringify(data, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if(cmEditor) {
|
||||||
|
cmEditor.toTextArea();
|
||||||
|
setCmEditor(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [editor.current, cmEditor, data]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="mt-4" style={{position: 'relative'}}>
|
||||||
|
{isLoading && (
|
||||||
|
<div className="overlay">
|
||||||
|
<div style={{marginBottom: 15, fontSize: 26}}>
|
||||||
|
<div style={{display: 'inline-block', fontSize: 24, marginLeft: 5, marginRight: 5}}>
|
||||||
|
<StopWatch/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<IconRefresh size={24} className="animate-spin"/>
|
||||||
|
<button
|
||||||
|
className="mt-4 uppercase bg-gray-200 active:bg-blueGray-600 text-xs px-4 py-2 rounded shadow hover:shadow-md outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150" type="button"
|
||||||
|
>
|
||||||
|
Cancel Request
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="operation"
|
||||||
|
style={{
|
||||||
|
width: `${width}px`,
|
||||||
|
height: '400px'
|
||||||
|
}}
|
||||||
|
ref={editor}
|
||||||
|
className="cm-editor"
|
||||||
|
>
|
||||||
|
</textarea>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QueryResult;
|
@ -0,0 +1,26 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
height: 2.3rem;
|
||||||
|
|
||||||
|
div.method-selector {
|
||||||
|
border: solid 1px #cfcfcf;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.input-container {
|
||||||
|
border: solid 1px #cfcfcf;
|
||||||
|
|
||||||
|
input {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
107
packages/grafnode-components/src/components/QueryUrl/index.js
Normal file
107
packages/grafnode-components/src/components/QueryUrl/index.js
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import React, { useRef, forwardRef } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { faCaretDown } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import Dropdown from '../Dropdown';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const QueryUrl = ({value, onChange, handleRun}) => {
|
||||||
|
const dropdownTippyRef = useRef();
|
||||||
|
const viewProfile = () => {};
|
||||||
|
|
||||||
|
const Icon = forwardRef((props, ref) => {
|
||||||
|
return (
|
||||||
|
<div ref={ref} className="user-icon items-center justify-center pl-3 py-2 select-none">
|
||||||
|
GET <FontAwesomeIcon className="ml-4 mr-1 text-gray-500" icon={faCaretDown} style={{fontSize: 13}}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const onDropdownCreate = (ref) => dropdownTippyRef.current = ref;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="mt-3 flex items-center">
|
||||||
|
<div className="flex items-center cursor-pointer user-action-dropdown h-full method-selector pr-3">
|
||||||
|
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement='bottom-start'>
|
||||||
|
<div>
|
||||||
|
<div className="dropdown-item" onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
viewProfile();
|
||||||
|
}}>
|
||||||
|
GET
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="dropdown-item" onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
viewProfile();
|
||||||
|
}}>
|
||||||
|
POST
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="dropdown-item" onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
viewProfile();
|
||||||
|
}}>
|
||||||
|
PUT
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="dropdown-item" onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
viewProfile();
|
||||||
|
}}>
|
||||||
|
DELETE
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="dropdown-item" onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
viewProfile();
|
||||||
|
}}>
|
||||||
|
PATCH
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="dropdown-item" onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
viewProfile();
|
||||||
|
}}>
|
||||||
|
OPTIONS
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="dropdown-item" onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
viewProfile();
|
||||||
|
}}>
|
||||||
|
HEAD
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center flex-grow input-container h-full">
|
||||||
|
<input
|
||||||
|
className="px-3 w-full"
|
||||||
|
type="text" value={value} onChange={(event) => onChange(event.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
style={{backgroundColor: '#8e44ad'}}
|
||||||
|
className="flex items-center h-full text-white active:bg-blue-600 font-bold text-xs px-4 py-2 ml-2 uppercase rounded shadow hover:shadow-md outline-none focus:outline-none ease-linear transition-all duration-150"
|
||||||
|
onClick={handleRun}
|
||||||
|
>
|
||||||
|
<span style={{marginLeft: 5}}>Send</span>
|
||||||
|
</button>
|
||||||
|
</StyledWrapper>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
QueryUrl.propTypes = {
|
||||||
|
value: PropTypes.string,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
handleRun: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QueryUrl;
|
@ -0,0 +1,55 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
.react-tabs__tab-list {
|
||||||
|
border-bottom: none !important;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-left: 0 !important;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.react-tabs__tab {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 6px 0px;
|
||||||
|
border: none;
|
||||||
|
user-select: none;
|
||||||
|
border-bottom: solid 2px transparent;
|
||||||
|
margin-right: 20px;
|
||||||
|
color: rgb(117 117 117);
|
||||||
|
outline: none !important;
|
||||||
|
|
||||||
|
&:focus, &:active, &:focus-within, &:focus-visible, &:target {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tabs__tab--selected {
|
||||||
|
border: none;
|
||||||
|
color: #322e2c !important;
|
||||||
|
border-bottom: solid 2px #8e44ad !important;
|
||||||
|
border-color: #8e44ad !important;
|
||||||
|
background: inherit;
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
|
||||||
|
&:focus, &:active, &:focus-within, &:focus-visible, &:target {
|
||||||
|
border: none;
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border-bottom: solid 2px #8e44ad !important;
|
||||||
|
border-color: #8e44ad !important;
|
||||||
|
background: inherit;
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import QueryEditor from '../QueryEditor';
|
||||||
|
|
||||||
|
const RequestPane = ({leftPaneWidth, query, onQueryChange}) => {
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="">
|
||||||
|
<Tabs className='react-tabs mt-1 flex flex-grow flex-col' forceRenderTabPanel>
|
||||||
|
<TabList>
|
||||||
|
<Tab tabIndex="-1">Query</Tab>
|
||||||
|
<Tab tabIndex="-1">Headers</Tab>
|
||||||
|
</TabList>
|
||||||
|
<TabPanel>
|
||||||
|
<QueryEditor
|
||||||
|
width={leftPaneWidth}
|
||||||
|
query={query}
|
||||||
|
onChange={onQueryChange}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
Headers
|
||||||
|
</TabPanel>
|
||||||
|
</Tabs>
|
||||||
|
</StyledWrapper>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RequestPane;
|
@ -0,0 +1,6 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,141 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import find from 'lodash/find';
|
||||||
|
import { rawRequest, gql } from 'graphql-request';
|
||||||
|
import QueryUrl from '../QueryUrl';
|
||||||
|
import RequestPane from '../RequestPane';
|
||||||
|
import ResponsePane from '../ResponsePane';
|
||||||
|
import {
|
||||||
|
flattenItems,
|
||||||
|
findItem
|
||||||
|
} from '../../utils';
|
||||||
|
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const RequestTabPanel = ({collections, activeRequestTabId, requestTabs}) => {
|
||||||
|
if(typeof window == 'undefined') {
|
||||||
|
return <div></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let asideWidth = 200;
|
||||||
|
let [data, setData] = useState({});
|
||||||
|
let [url, setUrl] = useState('https://api.spacex.land/graphql');
|
||||||
|
let [query, setQuery] = useState('');
|
||||||
|
let [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [leftPaneWidth, setLeftPaneWidth] = useState(500);
|
||||||
|
const [rightPaneWidth, setRightPaneWidth] = useState(window.innerWidth - 700 - asideWidth);
|
||||||
|
const [dragging, setDragging] = useState(false);
|
||||||
|
const handleMouseMove = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if(dragging) {
|
||||||
|
setLeftPaneWidth(e.clientX - asideWidth );
|
||||||
|
setRightPaneWidth(window.innerWidth - (e.clientX));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleMouseUp = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setDragging(false);
|
||||||
|
};
|
||||||
|
const handleMouseDown = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setDragging(true);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener('mouseup', handleMouseUp);
|
||||||
|
document.addEventListener('mousemove', handleMouseMove);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mouseup', handleMouseUp);
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
};
|
||||||
|
}, [dragging, leftPaneWidth]);
|
||||||
|
|
||||||
|
const onUrlChange = (value) => {
|
||||||
|
setUrl(value);
|
||||||
|
};
|
||||||
|
const onQueryChange = (value) => {
|
||||||
|
setQuery(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!activeRequestTabId) {
|
||||||
|
return (
|
||||||
|
<div className="pb-4 px-4">No request selected</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const focusedTab = find(requestTabs, (rt) => rt.id === activeRequestTabId);
|
||||||
|
|
||||||
|
if(!focusedTab || !focusedTab.id) {
|
||||||
|
return (
|
||||||
|
<div className="pb-4 px-4">An error occured!</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const collection = find(collections, (c) => c.id === focusedTab.collectionId);
|
||||||
|
const flattenedItems = flattenItems(collection.items);
|
||||||
|
const item = findItem(flattenedItems, activeRequestTabId);
|
||||||
|
|
||||||
|
const runQuery = async () => {
|
||||||
|
const query = gql`${item.request.body.graphql.query}`;
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
const { data, errors, extensions, headers, status } = await rawRequest(item.request.url, query);
|
||||||
|
setData(data);
|
||||||
|
setIsLoading(false);
|
||||||
|
console.log(data);
|
||||||
|
console.log(headers);
|
||||||
|
|
||||||
|
// request(item.request.url, gql`${item.request.body.graphql.query}`)
|
||||||
|
// .then((data, stuff) => {
|
||||||
|
// console.log(data);
|
||||||
|
// console.log(stuff);
|
||||||
|
// setData(data);
|
||||||
|
// setIsLoading(false);
|
||||||
|
// })
|
||||||
|
// .catch((err) => {
|
||||||
|
// setIsLoading(false);
|
||||||
|
// console.log(err);
|
||||||
|
// });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper>
|
||||||
|
<div
|
||||||
|
className="pb-4 px-4"
|
||||||
|
style={{
|
||||||
|
borderBottom: 'solid 1px #e1e1e1'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="pt-2 text-gray-600">{item.name}</div>
|
||||||
|
<QueryUrl
|
||||||
|
value = {url}
|
||||||
|
onChange={onUrlChange}
|
||||||
|
handleRun={runQuery}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<section className="main">
|
||||||
|
<section className="request-pane px-4">
|
||||||
|
<div style={{width: `${leftPaneWidth}px`}}>
|
||||||
|
<RequestPane
|
||||||
|
leftPaneWidth={leftPaneWidth}
|
||||||
|
query={item.request.body.graphql.query}
|
||||||
|
onQueryChange={onQueryChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="drag-request" onMouseDown={handleMouseDown}>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section className="response-pane px-4 flex-grow">
|
||||||
|
<ResponsePane
|
||||||
|
rightPaneWidth={rightPaneWidth}
|
||||||
|
data={data}
|
||||||
|
isLoading={isLoading}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</StyledWrapper>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RequestTabPanel;
|
@ -0,0 +1,26 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
div.tabs {
|
||||||
|
div.tab {
|
||||||
|
padding: 6px 0px;
|
||||||
|
border: none;
|
||||||
|
border-bottom: solid 2px transparent;
|
||||||
|
margin-right: 20px;
|
||||||
|
color: rgb(117 117 117);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:focus, &:active, &:focus-within, &:focus-visible, &:target {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #322e2c !important;
|
||||||
|
border-bottom: solid 2px #8e44ad !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,49 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import QueryResult from '../QueryResult';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const ResponsePane = ({rightPaneWidth, data, isLoading}) => {
|
||||||
|
const [selectedTab, setSelectedTab] = useState('response');
|
||||||
|
|
||||||
|
const getTabClassname = (tabName) => {
|
||||||
|
return classnames(`tab select-none ${tabName}`, {
|
||||||
|
'active': tabName === selectedTab
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTabPanel = (tab) => {
|
||||||
|
switch(tab) {
|
||||||
|
case 'response': {
|
||||||
|
return (
|
||||||
|
<QueryResult
|
||||||
|
width={rightPaneWidth}
|
||||||
|
data={data}
|
||||||
|
isLoading={isLoading}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case 'headers': {
|
||||||
|
return <div>Headers</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return <div>404 | Not found</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="">
|
||||||
|
<div className="flex items-center tabs mt-1" role="tablist">
|
||||||
|
<div className={getTabClassname('response')} role="tab" onClick={() => setSelectedTab('response')}>Response</div>
|
||||||
|
<div className={getTabClassname('headers')} role="tab" onClick={() => setSelectedTab('headers')}>Headers</div>
|
||||||
|
</div>
|
||||||
|
<section>
|
||||||
|
{getTabPanel(selectedTab)}
|
||||||
|
</section>
|
||||||
|
</StyledWrapper>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResponsePane;
|
@ -0,0 +1,17 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
.spinner {
|
||||||
|
display: inline-block;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
border: 0.25em solid currentColor;
|
||||||
|
border-right-color: transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spinner-border .75s linear infinite;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
||||||
|
|
18
packages/grafnode-components/src/components/Spinner/index.js
Normal file
18
packages/grafnode-components/src/components/Spinner/index.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
// Todo: Size, Color config support
|
||||||
|
const Spinner = ({size, color, children}) => {
|
||||||
|
return (
|
||||||
|
<StyledWrapper>
|
||||||
|
<div className="animate-spin"></div>
|
||||||
|
{children &&
|
||||||
|
<div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</StyledWrapper>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Spinner;
|
@ -0,0 +1,30 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
const StopWatch = () => {
|
||||||
|
const [milliseconds, setMilliseconds] = useState(0);
|
||||||
|
|
||||||
|
const tickInterval = 200;
|
||||||
|
const tick = () => {
|
||||||
|
setMilliseconds(milliseconds + tickInterval);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let timerID = setInterval(() => tick(), tickInterval);
|
||||||
|
return () => {
|
||||||
|
clearInterval(timerID);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if(milliseconds < 1000) {
|
||||||
|
return 'Loading...';
|
||||||
|
}
|
||||||
|
|
||||||
|
let seconds = milliseconds/1000;
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{seconds.toFixed(1)}s
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StopWatch;
|
@ -1,9 +1,11 @@
|
|||||||
import Navbar from './components/Navbar';
|
import Navbar from './components/Navbar';
|
||||||
import Sidebar from './components/Sidebar';
|
import Sidebar from './components/Sidebar';
|
||||||
import RequestTabs from './components/RequestTabs';
|
import RequestTabs from './components/RequestTabs';
|
||||||
|
import RequestTabPanel from './components/RequestTabPanel';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Navbar,
|
Navbar,
|
||||||
Sidebar,
|
Sidebar,
|
||||||
RequestTabs
|
RequestTabs,
|
||||||
|
RequestTabPanel
|
||||||
};
|
};
|
||||||
|
32
packages/grafnode-components/src/utils.js
Normal file
32
packages/grafnode-components/src/utils.js
Normal file
@ -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);
|
||||||
|
};
|
@ -1,8 +1,10 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: "./src/index.js",
|
entry: "./src/index.js",
|
||||||
output: {
|
output: {
|
||||||
|
publicPath: '',
|
||||||
globalObject: 'this',
|
globalObject: 'this',
|
||||||
filename: "index.js",
|
filename: "index.js",
|
||||||
path: path.resolve(__dirname, "dist"),
|
path: path.resolve(__dirname, "dist"),
|
||||||
@ -17,15 +19,27 @@ module.exports = {
|
|||||||
use: {
|
use: {
|
||||||
loader: "babel-loader"
|
loader: "babel-loader"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: [ MiniCssExtractPlugin.loader, 'css-loader' ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
externals: {
|
externals: {
|
||||||
'react': 'react',
|
'react': 'react',
|
||||||
|
'lodash': 'lodash',
|
||||||
'styled-components': 'styled-components',
|
'styled-components': 'styled-components',
|
||||||
'@tabler/icon': '@tabler/icon',
|
'@tippyjs/react': '@tippyjs/react',
|
||||||
|
'@tabler/icons': '@tabler/icons',
|
||||||
'@fortawesome/free-solid-svg-icons': '@fortawesome/free-solid-svg-icons',
|
'@fortawesome/free-solid-svg-icons': '@fortawesome/free-solid-svg-icons',
|
||||||
'@fortawesome/react-fontawesome': '@fortawesome/react-fontawesome',
|
'@fortawesome/react-fontawesome': '@fortawesome/react-fontawesome',
|
||||||
'classnames': 'classnames'
|
'classnames': 'classnames',
|
||||||
}
|
'react-tabs': 'react-tabs',
|
||||||
|
'codemirror': 'codemirror',
|
||||||
|
'graphql-request': 'graphql-request'
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new MiniCssExtractPlugin()
|
||||||
|
]
|
||||||
};
|
};
|
3896
packages/grafnode-run/package-lock.json
generated
3896
packages/grafnode-run/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -27,11 +27,15 @@
|
|||||||
"@tabler/icons": "^1.46.0",
|
"@tabler/icons": "^1.46.0",
|
||||||
"@tippyjs/react": "^4.2.6",
|
"@tippyjs/react": "^4.2.6",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
|
"codemirror": "^5.64.0",
|
||||||
|
"graphql-request": "^3.7.0",
|
||||||
"immer": "^9.0.7",
|
"immer": "^9.0.7",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"nanoid": "^3.1.30",
|
"nanoid": "^3.1.30",
|
||||||
"next": "12.0.4",
|
"next": "12.0.4",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
|
"react-tabs": "^3.2.3",
|
||||||
"styled-components": "^5.3.3",
|
"styled-components": "^5.3.3",
|
||||||
"tailwindcss": "^2.2.19"
|
"tailwindcss": "^2.2.19"
|
||||||
},
|
},
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import dynamic from 'next/dynamic'
|
||||||
import {
|
import {
|
||||||
Navbar,
|
Navbar,
|
||||||
RequestTabs,
|
RequestTabs,
|
||||||
Sidebar
|
Sidebar
|
||||||
} from '@grafnode/components';
|
} from '@grafnode/components';
|
||||||
import actions from 'providers/Store/actions';
|
import actions from 'providers/Store/actions';
|
||||||
import { useStore } from 'providers/Store';
|
import { useStore } from '../../providers/Store/index';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const RequestTabPanel = dynamic(import('@grafnode/components').then(mod => mod.RequestTabPanel), { ssr: false });
|
||||||
|
|
||||||
export default function Main() {
|
export default function Main() {
|
||||||
|
// disable ssr
|
||||||
|
if(typeof window === 'undefined') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const [state, dispatch] = useStore();
|
const [state, dispatch] = useStore();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -17,8 +25,6 @@ export default function Main() {
|
|||||||
activeRequestTabId
|
activeRequestTabId
|
||||||
} = state;
|
} = state;
|
||||||
|
|
||||||
console.log(actions);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
@ -36,6 +42,11 @@ export default function Main() {
|
|||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
activeRequestTabId={activeRequestTabId}
|
activeRequestTabId={activeRequestTabId}
|
||||||
/>
|
/>
|
||||||
|
<RequestTabPanel
|
||||||
|
collections={collections}
|
||||||
|
requestTabs={requestTabs}
|
||||||
|
activeRequestTabId={activeRequestTabId}
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,6 +2,8 @@ import { StoreProvider } from 'providers/Store';
|
|||||||
|
|
||||||
import '../styles/globals.css'
|
import '../styles/globals.css'
|
||||||
import 'tailwindcss/dist/tailwind.min.css';
|
import 'tailwindcss/dist/tailwind.min.css';
|
||||||
|
import 'react-tabs/style/react-tabs.css';
|
||||||
|
import 'codemirror/lib/codemirror.css';
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }) {
|
function MyApp({ Component, pageProps }) {
|
||||||
return (
|
return (
|
||||||
|
Loading…
Reference in New Issue
Block a user