Skip to content

Instantly share code, notes, and snippets.

@ggascoigne
Created July 28, 2021 21:20
Show Gist options
  • Save ggascoigne/7160ba6c4b52b7f615b3df2f5a68287d to your computer and use it in GitHub Desktop.
Save ggascoigne/7160ba6c4b52b7f615b3df2f5a68287d to your computer and use it in GitHub Desktop.
GraphiQl editor with Schema Reloading - also uses GraphiqlExplorer
import 'graphiql/graphiql.css'
import { Trans, t } from '@lingui/macro'
import {
InputLabel,
MenuItem,
Select,
createStyles,
makeStyles,
} from '@material-ui/core'
import { i18n } from '@tw/common/I18nLoader'
import classNames from 'classnames'
import RealGraphiQL from 'graphiql'
import GraphiQLExplorer from 'graphiql-explorer'
import {
GraphQLSchema,
buildClientSchema,
getIntrospectionQuery,
parse,
} from 'graphql'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useSelector } from 'react-redux'
type ApiOption = { text: string; value: string; hidden?: boolean }
const apis: Array<ApiOption>= [
{
text: 'Custom',
value: 'http://localhost:4000/graphql',
},
// we have a lot of separate apis, normally they are listed here
]
export const useStyles = makeStyles(
createStyles({
box: {
boxSizing: 'content-box',
display: 'flex',
flexDirection: 'row',
width: '100%',
},
'@global': {
'.graphiql-explorer-root': {
overflow: 'unset !important',
padding: '0 !important',
},
'.graphiql-explorer-root > :first-child': {
padding: '8px 8px 0 8px',
overflowX: 'hidden !important',
},
'.graphiql-explorer-root > :nth-child(2)': {
padding: '0px 8px 0 8px',
},
'.graphiql-container .historyPaneWrap': {
width: '300px !important',
boxShadow: 'none !important',
},
},
apiLabel: {
padding: '0.5em 0.6em 0 1em',
},
})
)
type Props = { auth: any }
const GraphiQL: React.FC<Props> = (props) => {
const _graphiql = useRef(null)
const [schema, setSchema] = useState<GraphQLSchema | null>(null)
const [query, setQuery] = useState<string>('')
const [explorerIsOpen, setExplorerIsOpen] = useState<boolean>(true)
const [selectedApi, setSelectedApi] = useState(apis[0].value)
const classes = useStyles({})
const {
auth: { jwtToken },
} = props
const graphQLFetcher = useCallback(
(jwtToken) => (graphQLParams) =>
fetch(selectedApi, {
method: 'post',
headers: {
'X-Requested-With': 'graphiql',
'Content-Type': 'application/json',
Authorization: `Bearer ${jwtToken}`,
},
body: JSON.stringify(graphQLParams),
}).then((response) => response.json()),
[selectedApi]
)
const handleInspectOperation = useCallback(
(cm: any, mousePos: { line: number; ch: number }) => {
const parsedQuery = parse(query || '')
if (!parsedQuery) {
console.error("Couldn't parse query document")
return null
}
const token = cm.getTokenAt(mousePos)
const start = { line: mousePos.line, ch: token.start }
const end = { line: mousePos.line, ch: token.end }
const relevantMousePos = {
start: cm.indexFromPos(start),
end: cm.indexFromPos(end),
}
const position = relevantMousePos
const def = parsedQuery.definitions.find((definition) => {
if (!definition.loc) {
console.log('Missing location information for definition')
return false
}
const { start, end } = definition.loc
return start <= position.start && end >= position.end
})
if (!def) {
console.error(
'Unable to find definition corresponding to mouse position'
)
return null
}
const operationKind =
def.kind === 'OperationDefinition'
? def.operation
: def.kind === 'FragmentDefinition'
? 'fragment'
: 'unknown'
const operationName =
def.kind === 'OperationDefinition' && !!def.name
? def.name.value
: def.kind === 'FragmentDefinition' && !!def.name
? def.name.value
: 'unknown'
const selector = `.graphiql-explorer-root #${operationKind}-${operationName}`
const el = document.querySelector(selector)
el && el.scrollIntoView()
},
[query]
)
const reloadSchema = useCallback(() => {
graphQLFetcher(jwtToken)({
query: getIntrospectionQuery(),
}).then((result) => {
const editor = _graphiql.current.getQueryEditor()
editor &&
editor.setOption('extraKeys', {
...(editor.options.extraKeys || {}),
'Shift-Alt-LeftClick': handleInspectOperation,
})
setSchema(buildClientSchema(result.data))
})
}, [graphQLFetcher, handleInspectOperation, jwtToken])
useEffect(() => {
reloadSchema()
}, [reloadSchema])
const handleEditQuery = useCallback((query: string) => setQuery(query), [])
const handleToggleExplorer = useCallback(() => {
setExplorerIsOpen((old) => !old)
}, [])
const handleApiChange = useCallback((_, data) => {
setSelectedApi(data.props.value)
console.log(`setting selectedApi = ${data.props.value}`)
}, [])
const renderApiValue = useCallback(
(value: string) => apis.find((c) => value === c.value).text,
[apis]
)
return (
<div className={classNames(classes.box, 'graphiql-container')}>
<GraphiQLExplorer
schema={schema}
query={query}
onEdit={handleEditQuery}
onRunOperation={(operationName: string) =>
_graphiql.current.handleRunQuery(operationName)
}
explorerIsOpen={explorerIsOpen}
onToggleExplorer={handleToggleExplorer}
/>
<RealGraphiQL
ref={_graphiql}
fetcher={graphQLFetcher(jwtToken)}
schema={schema}
query={query}
onEditQuery={handleEditQuery}
>
<RealGraphiQL.Toolbar>
<RealGraphiQL.Button
onClick={() => _graphiql.current.handlePrettifyQuery()}
label={i18n._(t`Prettify`)}
title={i18n._(t`Prettify Query (Shift-Ctrl-P)`)}
/>
<RealGraphiQL.Button
onClick={() => _graphiql.current.handleMergeQuery()}
title={i18n._(t`Merge Query (Shift-Ctrl-M)`)}
label={i18n._(t`Merge`)}
/>
<RealGraphiQL.Button
onClick={reloadSchema}
title={i18n._(t`Reload Schema`)}
label={i18n._(t`Reload`)}
/>{' '}
<RealGraphiQL.Button
onClick={() => _graphiql.current.handleToggleHistory()}
label={i18n._(t`History`)}
title={i18n._(t`Show History`)}
/>
<RealGraphiQL.Button
onClick={handleToggleExplorer}
label={i18n._(t`Explorer`)}
title={i18n._(t`Toggle Explorer`)}
/>
<InputLabel className={classes.apiLabel}>
<Trans>Api</Trans>
</InputLabel>
<Select
disableUnderline
name="apiSelect"
onChange={handleApiChange}
renderValue={renderApiValue}
value={selectedApi}
>
{apis.map((option, index) => (
<MenuItem key={index} value={option.value}>
{option.text}
</MenuItem>
))}
</Select>
</RealGraphiQL.Toolbar>
</RealGraphiQL>
</div>
)
}
export default GraphiQL
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment