From bb5eaaa87149e0ae1cda5a62831855e8eb323ad2 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Wed, 31 Jul 2019 11:05:43 -0400 Subject: [PATCH] GraphQL Docs Explorer (#1616) * Implement a GraphQLExplorer for visually browsing schemas * Fix some type stuff and theme * Adjust GraphQL explorer colors for fields to match Codemirror * Explorer state not modified when showing docs from dropdown * A few more small tweaks and fixes --- package-lock.json | 6 +- package.json | 2 +- packages/insomnia-app/.flowconfig | 5 +- packages/insomnia-app/app/renderer.html | 1 + .../ui/components/codemirror/code-editor.js | 16 +- .../editors/body/graph-ql-editor.js | 75 +++++-- .../graph-ql-explorer-field-link.js | 30 +++ .../graph-ql-explorer-field.js | 62 ++++++ .../graph-ql-explorer-schema.js | 60 ++++++ .../graph-ql-explorer-type-link.js | 48 +++++ .../graph-ql-explorer-type.js | 101 +++++++++ .../graph-ql-explorer/graph-ql-explorer.js | 200 ++++++++++++++++++ .../app/ui/components/markdown-preview.js | 35 +-- .../ui/css/components/graph-ql-explorer.less | 86 ++++++++ packages/insomnia-app/app/ui/css/index.less | 1 + packages/insomnia-app/flow-typed/graphql.js | 63 ------ 16 files changed, 690 insertions(+), 101 deletions(-) create mode 100644 packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-field-link.js create mode 100644 packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-field.js create mode 100644 packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-schema.js create mode 100644 packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-type-link.js create mode 100644 packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-type.js create mode 100644 packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer.js create mode 100644 packages/insomnia-app/app/ui/css/components/graph-ql-explorer.less delete mode 100644 packages/insomnia-app/flow-typed/graphql.js diff --git a/package-lock.json b/package-lock.json index 73d5bbe798..0671ba4f99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8291,9 +8291,9 @@ } }, "flow-bin": { - "version": "0.96.0", - "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.96.0.tgz", - "integrity": "sha512-OSxERs0EdhVxEVCst/HmlT/RcnXsQQIRqcfK9J9wC8/93JQj+xQz4RtlsmYe1PSRYaozuDLyPS5pIA81Zwzaww==", + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.104.0.tgz", + "integrity": "sha512-EZXRRmf7m7ET5Lcnwm/I/T8G3d427Bq34vmO3qIlRcPIYloGuVoqRCwjaeezLRDntHkdciagAKbhJ+NTbDjnkw==", "dev": true }, "flush-write-stream": { diff --git a/package.json b/package.json index 485d2ac800..c226853365 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "babel-eslint": "^8.2.3", "babel-jest": "^24.7.1", "babel-loader": "^8.0.5", - "flow-bin": "^0.96.0", + "flow-bin": "^0.104.0", "husky": "^1.0.0-rc.9", "jest": "^24.7.1", "lerna": "^3.14.1", diff --git a/packages/insomnia-app/.flowconfig b/packages/insomnia-app/.flowconfig index 29611527b1..448dbc63b3 100644 --- a/packages/insomnia-app/.flowconfig +++ b/packages/insomnia-app/.flowconfig @@ -1,13 +1,12 @@ [ignore] .*/node_modules/.* .*/__fixtures__/.* +!/node_modules/graphql +!/node_modules/iterall [include] -../../plugins -../../packages [libs] -flow-typed/ [options] esproposal.decorators=ignore diff --git a/packages/insomnia-app/app/renderer.html b/packages/insomnia-app/app/renderer.html index 45880a6c04..d0b7b1c98a 100644 --- a/packages/insomnia-app/app/renderer.html +++ b/packages/insomnia-app/app/renderer.html @@ -11,6 +11,7 @@
+
diff --git a/packages/insomnia-app/app/ui/components/codemirror/code-editor.js b/packages/insomnia-app/app/ui/components/codemirror/code-editor.js index a6589ea4fa..9994e342bb 100644 --- a/packages/insomnia-app/app/ui/components/codemirror/code-editor.js +++ b/packages/insomnia-app/app/ui/components/codemirror/code-editor.js @@ -585,12 +585,20 @@ class CodeEditor extends React.Component { // Strip of charset if there is one const cm = this.codeMirror; Object.keys(options).map(key => { - // Don't set the option if it hasn't changed - if (deepEqual(options[key], cm.options[key])) { - return; + let shouldSetOption = false; + + if (key === 'jump' || key === 'info' || key === 'lint' || key === 'hintOptions') { + // Use stringify here because these could be infinitely recursive due to GraphQL + // schemas + shouldSetOption = JSON.stringify(options[key]) !== JSON.stringify(cm.options[key]); + } else if (!deepEqual(options[key], cm.options[key])) { + // Don't set the option if it hasn't changed + shouldSetOption = true; } - cm.setOption(key, options[key]); + if (shouldSetOption) { + cm.setOption(key, options[key]); + } }); } diff --git a/packages/insomnia-app/app/ui/components/editors/body/graph-ql-editor.js b/packages/insomnia-app/app/ui/components/editors/body/graph-ql-editor.js index 37c3752e93..8011ab5962 100644 --- a/packages/insomnia-app/app/ui/components/editors/body/graph-ql-editor.js +++ b/packages/insomnia-app/app/ui/components/editors/body/graph-ql-editor.js @@ -1,9 +1,11 @@ // @flow import classnames from 'classnames'; import * as React from 'react'; +import * as ReactDOM from 'react-dom'; import autobind from 'autobind-decorator'; import { markdownToHTML } from '../../../../common/markdown-to-html'; -import { parse, print, typeFromAST, type Document as DocumentAST } from 'graphql'; +import type { GraphQLArgument, GraphQLField, GraphQLSchema, GraphQLType } from 'graphql'; +import { parse, print, typeFromAST } from 'graphql'; import { introspectionQuery } from 'graphql/utilities/introspectionQuery'; import { buildClientSchema } from 'graphql/utilities/buildClientSchema'; import type { CodeMirror, TextMarker } from 'codemirror'; @@ -25,6 +27,12 @@ import { newBodyRaw } from '../../../../models/request'; import ResponseDebugModal from '../../modals/response-debug-modal'; import Tooltip from '../../tooltip'; import { Dropdown, DropdownButton, DropdownDivider, DropdownItem } from '../../base/dropdown'; +import GraphqlExplorer from '../../graph-ql-explorer/graph-ql-explorer'; + +const explorerContainer = document.querySelector('#graphql-explorer-container'); +if (!explorerContainer) { + throw new Error('Failed to find #graphql-explorer-container'); +} type GraphQLBody = { query: string, @@ -49,7 +57,7 @@ type Props = { type State = { body: GraphQLBody, - schema: Object | null, + schema: GraphQLSchema | null, schemaFetchError: { message: string, response: ResponsePatch | null, @@ -59,12 +67,18 @@ type State = { hideSchemaFetchErrors: boolean, variablesSyntaxError: string, automaticFetch: boolean, + explorerVisible: boolean, + activeReference: null | { + type: GraphQLType | null, + argument: GraphQLArgument | null, + field: GraphQLField | null, + }, }; @autobind class GraphQLEditor extends React.PureComponent { _disabledOperationMarkers: TextMarker[]; - _documentAST: null | DocumentAST; + _documentAST: null | Object; _isMounted: boolean; _queryEditor: null | CodeMirror; _schemaFetchTimeout: TimeoutID; @@ -92,6 +106,8 @@ class GraphQLEditor extends React.PureComponent { schemaIsFetching: false, hideSchemaFetchErrors: false, variablesSyntaxError: '', + activeReference: null, + explorerVisible: false, automaticFetch, }; } @@ -142,6 +158,15 @@ class GraphQLEditor extends React.PureComponent { return operationName; } + _handleCloseExplorer() { + this.setState({ explorerVisible: false }); + } + + _handleClickReference(reference: Object, e: MouseEvent) { + e.preventDefault(); + this.setState({ explorerVisible: true, activeReference: reference }); + } + _handleQueryFocus() { this._handleQueryUserActivity(); } @@ -314,6 +339,12 @@ class GraphQLEditor extends React.PureComponent { return variableToType; } + _handleShowDocumentation() { + this.setState({ + explorerVisible: true, + }); + } + async _handleRefreshSchema(): Promise { await this._fetchAndSetSchema(this.props.request); } @@ -474,6 +505,11 @@ class GraphQLEditor extends React.PureComponent { return message; } + static renderMarkdown(text: string) { + const html = markdownToHTML(text); + return `
${html}
`; + } + render() { const { content, @@ -492,6 +528,8 @@ class GraphQLEditor extends React.PureComponent { variablesSyntaxError, schemaIsFetching, automaticFetch, + activeReference, + explorerVisible, } = this.state; const { query, variables: variablesObject } = GraphQLEditor._stringToGraphQL(content); @@ -500,6 +538,17 @@ class GraphQLEditor extends React.PureComponent { const variableTypes = this._buildVariableTypes(schema); + // Create portal for GraphQL Explorer + const graphQLExplorerPortal = ReactDOM.createPortal( + , + explorerContainer, + ); + return (
@@ -507,6 +556,9 @@ class GraphQLEditor extends React.PureComponent { schema GraphQL Schema + + Show Documentation + Refresh Schema @@ -532,16 +584,13 @@ class GraphQLEditor extends React.PureComponent { }} infoOptions={{ schema: schema || null, - renderDescription: text => { - const html = markdownToHTML(text); - return `
${html}
`; - }, - // onClick: reference => console.log('CLICK', reference) + renderDescription: GraphQLEditor.renderMarkdown, + onClick: this._handleClickReference, + }} + jumpOptions={{ + schema: schema || null, + onClick: this._handleClickReference, }} - // jumpOptions={{ - // schema: schema || null, - // onClick: reference => console.log('JUMP', reference) - // }} lintOptions={schema ? { schema } : null} fontSize={settings.editorFontSize} indentSize={settings.editorIndentSize} @@ -619,6 +668,8 @@ class GraphQLEditor extends React.PureComponent { Prettify GraphQL
+ + {graphQLExplorerPortal} ); } diff --git a/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-field-link.js b/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-field-link.js new file mode 100644 index 0000000000..6c999f1d53 --- /dev/null +++ b/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-field-link.js @@ -0,0 +1,30 @@ +// @flow +import * as React from 'react'; +import autobind from 'autobind-decorator'; +import type { GraphQLField } from 'graphql'; + +type Props = { + onNavigate: (type: Object) => void, + field: GraphQLField, +}; + +@autobind +class GraphQLExplorerFieldLink extends React.PureComponent { + _handleClick(e: MouseEvent) { + e.preventDefault(); + const { onNavigate, field } = this.props; + onNavigate(field); + } + + render() { + const { field } = this.props; + + return ( + + {field.name} + + ); + } +} + +export default GraphQLExplorerFieldLink; diff --git a/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-field.js b/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-field.js new file mode 100644 index 0000000000..5f038e94d9 --- /dev/null +++ b/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-field.js @@ -0,0 +1,62 @@ +// @flow +import * as React from 'react'; +import type { GraphQLField, GraphQLType } from 'graphql'; +import GraphQLExplorerTypeLink from './graph-ql-explorer-type-link'; +import MarkdownPreview from '../markdown-preview'; + +type Props = { + onNavigateType: (type: GraphQLType) => void, + field: GraphQLField, +}; + +class GraphQLExplorerField extends React.PureComponent { + renderDescription() { + const { field } = this.props; + return ; + } + + renderType() { + const { field, onNavigateType } = this.props; + + return ( + +

Type

+ +
+ ); + } + + renderArgumentsMaybe() { + const { field, onNavigateType } = this.props; + if (!field.args || field.args.length === 0) { + return null; + } + + return ( + +

Arguments

+
    + {field.args.map(a => ( +
  • + {a.name}:{' '} + + {a.description && } +
  • + ))} +
+
+ ); + } + + render() { + return ( +
+ {this.renderDescription()} + {this.renderType()} + {this.renderArgumentsMaybe()} +
+ ); + } +} + +export default GraphQLExplorerField; diff --git a/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-schema.js b/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-schema.js new file mode 100644 index 0000000000..e748c31cd7 --- /dev/null +++ b/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-schema.js @@ -0,0 +1,60 @@ +// @flow +import * as React from 'react'; +import type { GraphQLSchema, GraphQLType } from 'graphql'; +import GraphQLExplorerTypeLink from './graph-ql-explorer-type-link'; + +type Props = { + onNavigateType: (type: GraphQLType) => void, + schema: GraphQLSchema, +}; + +class GraphQLExplorerSchema extends React.PureComponent { + renderQueryType() { + const { schema, onNavigateType } = this.props; + + const type = schema.getQueryType(); + + if (!type) { + return null; + } + + return ( + + query:{' '} + + + ); + } + + renderMutationType() { + const { schema, onNavigateType } = this.props; + + const type = schema.getMutationType(); + + if (!type) { + return null; + } + + return ( + + mutation:{' '} + + + ); + } + + render() { + return ( +
+

A GraphQL schema provides a root type for each kind of operation.

+

Root Types

+
    +
  • {this.renderQueryType()}
  • +
  • {this.renderMutationType()}
  • +
+
+ ); + } +} + +export default GraphQLExplorerSchema; diff --git a/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-type-link.js b/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-type-link.js new file mode 100644 index 0000000000..74d1556123 --- /dev/null +++ b/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-type-link.js @@ -0,0 +1,48 @@ +// @flow +import * as React from 'react'; +import autobind from 'autobind-decorator'; +import { GraphQLList, GraphQLNonNull } from 'graphql'; +import type { GraphQLType } from 'graphql'; + +type Props = { + onNavigate: (type: Object) => void, + type: GraphQLType, +}; + +@autobind +class GraphQLExplorerTypeLink extends React.PureComponent { + _handleClick(e: MouseEvent) { + e.preventDefault(); + const { onNavigate, type } = this.props; + onNavigate(type); + } + + render() { + const { type, onNavigate } = this.props; + + if (type instanceof GraphQLList) { + return ( + + [ + ] + + ); + } + + if (type instanceof GraphQLNonNull) { + return ( + + ! + + ); + } + + return ( + + {type.name} + + ); + } +} + +export default GraphQLExplorerTypeLink; diff --git a/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-type.js b/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-type.js new file mode 100644 index 0000000000..f092555d97 --- /dev/null +++ b/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer-type.js @@ -0,0 +1,101 @@ +// @flow +import * as React from 'react'; +import GraphQLExplorerTypeLink from './graph-ql-explorer-type-link'; +import autobind from 'autobind-decorator'; +import MarkdownPreview from '../markdown-preview'; +import GraphQLExplorerFieldLink from './graph-ql-explorer-field-link'; +import type { GraphQLType, GraphQLField } from 'graphql'; + +type Props = { + onNavigateType: (type: Object) => void, + onNavigateField: (field: Object) => void, + type: GraphQLType, +}; + +@autobind +class GraphQLExplorerType extends React.PureComponent { + _handleNavigateType(type: Object) { + const { onNavigateType } = this.props; + onNavigateType(type); + } + + _handleNavigateField(field: Object) { + const { onNavigateField } = this.props; + onNavigateField(field); + } + + renderDescription() { + const { type } = this.props; + return ; + } + + renderFieldsMaybe() { + const { type, onNavigateType } = this.props; + if (typeof type.getFields !== 'function') { + return null; + } + + // $FlowFixMe + const fields = type.getFields(); + + return ( + +

Fields

+
    + {Object.keys(fields).map(key => { + const field: GraphQLField = (fields[key]: any); + + let argLinks = null; + const args = (field: any).args; + if (args && args.length) { + argLinks = ( + + ( + {args.map(a => ( +
    + {a.name}:{' '} + +
    + ))} + ) +
    + ); + } + + const fieldLink = ( + + ); + + const typeLink = ( + + ); + + const description = field.description; + return ( +
  • + {fieldLink} + {argLinks}: {typeLink} + {description && ( +
    + +
    + )} +
  • + ); + })} +
+
+ ); + } + + render() { + return ( +
+ {this.renderDescription()} + {this.renderFieldsMaybe()} +
+ ); + } +} + +export default GraphQLExplorerType; diff --git a/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer.js b/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer.js new file mode 100644 index 0000000000..a8d0e0dbb6 --- /dev/null +++ b/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer.js @@ -0,0 +1,200 @@ +// @flow +import * as React from 'react'; +import autobind from 'autobind-decorator'; +import GraphQLExplorerField from './graph-ql-explorer-field'; +import GraphQLExplorerType from './graph-ql-explorer-type'; +import type { GraphQLArgument, GraphQLField, GraphQLSchema, GraphQLType } from 'graphql'; +import GraphQLExplorerSchema from './graph-ql-explorer-schema'; + +type Props = { + handleClose: () => void, + schema: GraphQLSchema | null, + visible: boolean, + reference: null | { + type: GraphQLType | null, + argument: GraphQLArgument | null, + field: GraphQLField | null, + }, +}; + +type HistoryItem = { + currentType: null | GraphQLType, + currentField: null | GraphQLField, +}; + +type State = HistoryItem & { + history: Array, +}; + +@autobind +class GraphQLExplorer extends React.PureComponent { + constructor(props: Props) { + super(props); + this.state = { + history: [], + currentType: null, + currentField: null, + }; + } + + _handleNavigateType(type: GraphQLType) { + this.setState({ + currentType: type, + currentField: null, + history: this._addToHistory(), + }); + } + + _handleNavigateField(field: GraphQLField) { + this.setState({ + currentType: field.type, + currentField: field, + history: this._addToHistory(), + }); + } + + _handlePopHistory() { + this.setState(({ history }) => { + const last = history[history.length - 1] || null; + return { + history: history.slice(0, history.length - 1), + currentType: last ? last.currentType : null, + currentField: last ? last.currentField : null, + }; + }); + } + + _addToHistory() { + const { currentType, currentField, history } = this.state; + + // Nothing to add + if (!currentType && !currentField) { + return history; + } + + return [...history, { currentType, currentField }]; + } + + componentWillReceiveProps(nextProps: Props) { + if (!nextProps.reference) { + return; + } + + const { type, field } = nextProps.reference; + const { currentType, currentField } = this.state; + + const compare = (a: any, b: any) => (!a && !b) || (a && b && a.name === b.name); + + const sameType = compare(currentType, type); + const sameField = compare(currentField, field); + const nothingChanged = sameType && sameField; + + if (nothingChanged) { + return; + } + + let history = this._addToHistory(); + + this.setState({ + history, + currentType: type || null, + currentField: field || null, + }); + } + + renderHistoryItem() { + const { history, currentField, currentType } = this.state; + + if (history.length === 0 && (currentType || currentField)) { + return ( + { + e.preventDefault(); + this._handlePopHistory(); + }}> + Schema + + ); + } else if (history.length === 0) { + return null; + } + + const { currentType: lastType, currentField: lastField } = history[history.length - 1]; + + let name = null; + + if (lastField) { + name = (lastField: any).name || 'Unknown'; + } else if (lastType) { + name = (lastType: any).name || 'Unknown'; + } else { + return null; + } + + return ( + { + e.preventDefault(); + this._handlePopHistory(); + }}> + {name} + + ); + } + + render() { + const { schema, handleClose, visible } = this.props; + + if (!visible) { + return null; + } + + const { currentType, currentField } = this.state; + + let child = null; + if (currentField) { + child = ( + + ); + } else if (currentType) { + child = ( + + ); + } else if (schema) { + child = ; + } + + if (!child) { + return null; + } + + const fieldName = currentField ? (currentField: any).name : null; + const typeName = currentType ? (currentType: any).name : null; + const schemaName = schema ? 'Schema' : null; + + return ( +
+
+ {this.renderHistoryItem()} +

{fieldName || typeName || schemaName || 'Unknown'}

+ +
+
{child}
+
+ ); + } +} + +export default GraphQLExplorer; diff --git a/packages/insomnia-app/app/ui/components/markdown-preview.js b/packages/insomnia-app/app/ui/components/markdown-preview.js index 54d73ac148..55537a850d 100644 --- a/packages/insomnia-app/app/ui/components/markdown-preview.js +++ b/packages/insomnia-app/app/ui/components/markdown-preview.js @@ -22,20 +22,25 @@ class MarkdownPreview extends PureComponent { */ _compileMarkdown(markdown) { clearTimeout(this._compileTimeout); - this._compileTimeout = setTimeout(async () => { - try { - const rendered = await this.props.handleRender(markdown); - this.setState({ - compiled: markdownToHTML(rendered), - renderError: '', - }); - } catch (err) { - this.setState({ - renderError: err.message, - compiled: '', - }); - } - }, this.state.compiled ? this.props.debounceMillis : 0); + this._compileTimeout = setTimeout( + async () => { + try { + const { handleRender } = this.props; + const rendered = handleRender ? await handleRender(markdown) : markdown; + const compiled = markdownToHTML(rendered); + this.setState({ + compiled, + renderError: '', + }); + } catch (err) { + this.setState({ + renderError: err.message, + compiled: '', + }); + } + }, + this.state.compiled ? this.props.debounceMillis : 0, + ); } _setPreviewRef(n) { @@ -106,9 +111,9 @@ class MarkdownPreview extends PureComponent { MarkdownPreview.propTypes = { // Required markdown: PropTypes.string.isRequired, - handleRender: PropTypes.func.isRequired, // Optional + handleRender: PropTypes.func, className: PropTypes.string, debounceMillis: PropTypes.number, heading: PropTypes.string, diff --git a/packages/insomnia-app/app/ui/css/components/graph-ql-explorer.less b/packages/insomnia-app/app/ui/css/components/graph-ql-explorer.less new file mode 100644 index 0000000000..6cb0c2614b --- /dev/null +++ b/packages/insomnia-app/app/ui/css/components/graph-ql-explorer.less @@ -0,0 +1,86 @@ +@import '../constants/dimensions'; +@import '../constants/colors'; + +.graphql-explorer { + display: grid; + grid-template-columns: 100%; + grid-template-rows: auto minmax(0, 1fr); + position: fixed; + right: 0; + top: 0; + bottom: 0; + width: 400px; + max-width: 100%; + z-index: 100; + background: var(--color-bg); + box-shadow: 0 0 1rem 0 rgba(0, 0, 0, 0.4); + + .markdown-preview__content *:first-child { + margin-top: @padding-sm; + } + + &__subheading { + border-bottom: 1px solid var(--hl-md); + padding-bottom: @padding-sm; + margin-top: @padding-lg; + margin-bottom: @padding-md; + } + + &__header { + overflow: hidden; + border-bottom: 1px solid @hl-md; + box-sizing: border-box; + height: @height-nav; + line-height: @line-height-md; + color: var(--color-font); + display: flex; + justify-content: space-between; + flex-direction: row; + + h1 { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: @font-size-lg; + font-weight: bold; + } + + & > * { + white-space: nowrap; + padding-left: @padding-md; + display: inline-block; + margin: 0; + } + + &__close-btn { + height: 100% !important; + } + } + + &__body { + box-sizing: border-box; + padding: @padding-md @padding-md 10rem; + overflow: auto; + height: 100%; + max-height: 100%; + } + + &__defs { + &__description { + margin-left: @padding-sm; + color: var(--color-font); + } + + &__arg { + margin-left: @padding-md; + } + + li { + margin-bottom: @padding-md; + } + } + + a { + font-weight: normal !important; + } +} diff --git a/packages/insomnia-app/app/ui/css/index.less b/packages/insomnia-app/app/ui/css/index.less index 144f972ee3..b59c704d55 100644 --- a/packages/insomnia-app/app/ui/css/index.less +++ b/packages/insomnia-app/app/ui/css/index.less @@ -22,6 +22,7 @@ @import 'components/cookie-list'; @import 'components/cookie-modify-modal'; @import 'components/graph-ql-editor'; +@import 'components/graph-ql-explorer'; @import 'components/dropdown'; @import 'components/editable'; @import 'components/environment-modal'; diff --git a/packages/insomnia-app/flow-typed/graphql.js b/packages/insomnia-app/flow-typed/graphql.js deleted file mode 100644 index eb1c7b210c..0000000000 --- a/packages/insomnia-app/flow-typed/graphql.js +++ /dev/null @@ -1,63 +0,0 @@ -// @flow - -declare module 'graphql' { - declare type Tok = { - column: number, - end: number, - kind: string, - line: number, - next: Tok, - prev: Tok, - start: number, - }; - - declare type Loc = { - end: number, - endToken: Tok, - source: Source, - start: number, - startToken: Tok, - }; - - declare type ParseOptions = mixed; - declare type Source = mixed; - declare type Document = { - definitions: Array<{ - kind: string, - loc: Loc, - variableDefinitions: Array<{ - variable: { - name: { - kind: string, - value: string, - }, - }, - type: string, - }>, - name: { - kind: string, - value: string, - }, - }>, - }; - - declare function parse(source: Source | string, options?: ParseOptions): Document; - - declare module.exports: { - parse: typeof parse, - print: *, - typeFromAST: *, - }; -} - -declare module 'graphql/utilities/introspectionQuery' { - declare module.exports: { - introspectionQuery: string, - }; -} - -declare module 'graphql/utilities/buildClientSchema' { - declare module.exports: { - buildClientSchema: Function, - }; -}