mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-21 15:33:11 +01:00
feat: Request and Responses Panes, CodeMirror View
This commit is contained in:
parent
f6732e66a0
commit
8b586bdfae
5130
package-lock.json
generated
5130
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -5,8 +5,6 @@
|
||||
"packages/grafnode-components",
|
||||
"packages/grafnode-www"
|
||||
],
|
||||
"dependencies": {
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.16.0",
|
||||
"@babel/preset-env": "^7.16.4",
|
||||
@ -17,6 +15,7 @@
|
||||
"html-loader": "^3.0.1",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"lerna": "^4.0.0",
|
||||
"mini-css-extract-plugin": "^2.4.5",
|
||||
"style-loader": "^3.3.1",
|
||||
"webpack": "^5.64.4",
|
||||
"webpack-cli": "^4.9.1"
|
||||
|
2
packages/grafnode-components/.gitignore
vendored
2
packages/grafnode-components/.gitignore
vendored
@ -25,4 +25,4 @@ yarn-error.log*
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.production.local
|
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": "",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"watch:build": "webpack --watch --mode development",
|
||||
"build": "webpack --mode production",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
@ -24,6 +25,7 @@
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||
"@fortawesome/react-fontawesome": "^0.1.16",
|
||||
"classnames": "^2.3.1",
|
||||
"codemirror": "^5.64.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"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 Sidebar from './components/Sidebar';
|
||||
import RequestTabs from './components/RequestTabs';
|
||||
import RequestTabPanel from './components/RequestTabPanel';
|
||||
|
||||
export {
|
||||
Navbar,
|
||||
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 MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
|
||||
module.exports = {
|
||||
entry: "./src/index.js",
|
||||
output: {
|
||||
publicPath: '',
|
||||
globalObject: 'this',
|
||||
filename: "index.js",
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
@ -17,15 +19,27 @@ module.exports = {
|
||||
use: {
|
||||
loader: "babel-loader"
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [ MiniCssExtractPlugin.loader, 'css-loader' ]
|
||||
}
|
||||
]
|
||||
},
|
||||
externals: {
|
||||
'react': 'react',
|
||||
'lodash': 'lodash',
|
||||
'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/react-fontawesome': '@fortawesome/react-fontawesome',
|
||||
'classnames': 'classnames'
|
||||
}
|
||||
'classnames': 'classnames',
|
||||
'react-tabs': 'react-tabs',
|
||||
'codemirror': 'codemirror',
|
||||
'graphql-request': 'graphql-request'
|
||||
},
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin()
|
||||
]
|
||||
};
|
3918
packages/grafnode-run/package-lock.json
generated
3918
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",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"classnames": "^2.3.1",
|
||||
"codemirror": "^5.64.0",
|
||||
"graphql-request": "^3.7.0",
|
||||
"immer": "^9.0.7",
|
||||
"lodash": "^4.17.21",
|
||||
"nanoid": "^3.1.30",
|
||||
"next": "12.0.4",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-tabs": "^3.2.3",
|
||||
"styled-components": "^5.3.3",
|
||||
"tailwindcss": "^2.2.19"
|
||||
},
|
||||
|
@ -1,14 +1,22 @@
|
||||
import React from 'react';
|
||||
import dynamic from 'next/dynamic'
|
||||
import {
|
||||
Navbar,
|
||||
RequestTabs,
|
||||
Sidebar
|
||||
} from '@grafnode/components';
|
||||
import actions from 'providers/Store/actions';
|
||||
import { useStore } from 'providers/Store';
|
||||
import { useStore } from '../../providers/Store/index';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const RequestTabPanel = dynamic(import('@grafnode/components').then(mod => mod.RequestTabPanel), { ssr: false });
|
||||
|
||||
export default function Main() {
|
||||
// disable ssr
|
||||
if(typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [state, dispatch] = useStore();
|
||||
|
||||
const {
|
||||
@ -17,8 +25,6 @@ export default function Main() {
|
||||
activeRequestTabId
|
||||
} = state;
|
||||
|
||||
console.log(actions);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar />
|
||||
@ -36,6 +42,11 @@ export default function Main() {
|
||||
dispatch={dispatch}
|
||||
activeRequestTabId={activeRequestTabId}
|
||||
/>
|
||||
<RequestTabPanel
|
||||
collections={collections}
|
||||
requestTabs={requestTabs}
|
||||
activeRequestTabId={activeRequestTabId}
|
||||
/>
|
||||
</section>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
|
@ -2,6 +2,8 @@ import { StoreProvider } from 'providers/Store';
|
||||
|
||||
import '../styles/globals.css'
|
||||
import 'tailwindcss/dist/tailwind.min.css';
|
||||
import 'react-tabs/style/react-tabs.css';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
return (
|
||||
|
Loading…
Reference in New Issue
Block a user