diff --git a/package-lock.json b/package-lock.json index d735846fa..e9ba6ac3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,11 +20,13 @@ "classnames": "^2.3.1", "codemirror": "^5.64.0", "codemirror-graphql": "^1.2.5", + "escape-html": "^1.0.3", "eslint": "7.32.0", "eslint-config-next": "12.0.4", "graphql-request": "^3.7.0", "immer": "^9.0.7", "lodash": "^4.17.21", + "markdown-it": "^12.2.0", "nanoid": "^3.1.30", "next": "12.0.4", "react": "17.0.2", @@ -7089,6 +7091,11 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -10200,6 +10207,14 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/load-json-file": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-6.2.0.tgz", @@ -10433,6 +10448,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/markdown-it": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.2.0.tgz", + "integrity": "sha512-Wjws+uCrVQRqOoJvze4HCqkKl1AsSh95iFAeQDwnyfxM09divCBSXlDR1uTvyUP3Grzpn4Ru8GeCxYPM8vkCQg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -10443,6 +10486,11 @@ "safe-buffer": "^5.1.2" } }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + }, "node_modules/meow": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", @@ -15154,6 +15202,11 @@ "node": ">=4.2.0" } }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, "node_modules/uglify-js": { "version": "3.14.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.4.tgz", @@ -16039,6 +16092,8 @@ "@fortawesome/react-fontawesome": "^0.1.16", "classnames": "^2.3.1", "codemirror": "^5.64.0", + "escape-html": "^1.0.3", + "markdown-it": "^12.2.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-tabs": "^3.2.3", @@ -21411,6 +21466,11 @@ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -23719,6 +23779,14 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "requires": { + "uc.micro": "^1.0.1" + } + }, "load-json-file": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-6.2.0.tgz", @@ -23916,6 +23984,30 @@ "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", "dev": true }, + "markdown-it": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.2.0.tgz", + "integrity": "sha512-Wjws+uCrVQRqOoJvze4HCqkKl1AsSh95iFAeQDwnyfxM09divCBSXlDR1uTvyUP3Grzpn4Ru8GeCxYPM8vkCQg==", + "requires": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" + } + } + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -23926,6 +24018,11 @@ "safe-buffer": "^5.1.2" } }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + }, "meow": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", @@ -27462,6 +27559,11 @@ "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", "peer": true }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, "uglify-js": { "version": "3.14.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.4.tgz", diff --git a/packages/grafnode-components/package.json b/packages/grafnode-components/package.json index dddf94561..2dd602c81 100644 --- a/packages/grafnode-components/package.json +++ b/packages/grafnode-components/package.json @@ -26,6 +26,8 @@ "@fortawesome/react-fontawesome": "^0.1.16", "classnames": "^2.3.1", "codemirror": "^5.64.0", + "escape-html": "^1.0.3", + "markdown-it": "^12.2.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-tabs": "^3.2.3", diff --git a/packages/grafnode-components/src/components/QueryEditor/StyledWrapper.js b/packages/grafnode-components/src/components/QueryEditor/StyledWrapper.js index 06d21e715..deca905f9 100644 --- a/packages/grafnode-components/src/components/QueryEditor/StyledWrapper.js +++ b/packages/grafnode-components/src/components/QueryEditor/StyledWrapper.js @@ -4,6 +4,10 @@ const StyledWrapper = styled.div` div.CodeMirror { border: solid 1px #e1e1e1; } + + textarea.cm-editor { + position: relative; + } `; export default StyledWrapper; diff --git a/packages/grafnode-components/src/components/QueryEditor/index.js b/packages/grafnode-components/src/components/QueryEditor/index.js index 2e794de8d..ac45078bf 100644 --- a/packages/grafnode-components/src/components/QueryEditor/index.js +++ b/packages/grafnode-components/src/components/QueryEditor/index.js @@ -1,52 +1,218 @@ -import React, { useState, useEffect, useRef } from 'react'; +/** + * Copyright (c) 2021 GraphQL Contributors. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import CodeMirror from 'codemirror'; +import MD from 'markdown-it'; import StyledWrapper from './StyledWrapper'; -import * as CodeMirror from 'codemirror'; -const QueryEditor = ({query, onChange, width}) => { - const [cmEditor, setCmEditor] = useState(null); - const editor = useRef(); +import onHasCompletion from './onHasCompletion'; - useEffect(() => { - if (editor.current && !cmEditor) { - const _cmEditor = CodeMirror.fromTextArea(editor.current, { - value: '', - lineNumbers: true, - matchBrackets: true, - autoCloseBrackets: true, - mode: "graphql", - foldGutter: true, - gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], - lineWrapping: true - }); +const md = new MD(); +const AUTO_COMPLETE_AFTER_KEY = /^[a-zA-Z0-9_@(]$/; - _cmEditor.setValue(query || 'query { }'); +export default class QueryEditor extends React.Component { + constructor(props) { + super(props); - setCmEditor(_cmEditor); + // Keep a cached version of the value, this cache will be updated when the + // editor is updated, which can later be used to protect the editor from + // unnecessary updates during the update lifecycle. + this.cachedValue = props.value || ''; + } + + componentDidMount() { + const editor = (this.editor = CodeMirror(this._node, { + value: this.props.value || '', + lineNumbers: true, + tabSize: 2, + mode: 'graphql', + theme: this.props.editorTheme || 'graphiql', + keyMap: 'sublime', + autoCloseBrackets: true, + matchBrackets: true, + showCursorWhenSelecting: true, + readOnly: this.props.readOnly ? 'nocursor' : false, + foldGutter: { + minFoldSize: 4, + }, + lint: { + schema: this.props.schema, + validationRules: this.props.validationRules ?? null, + // linting accepts string or FragmentDefinitionNode[] + externalFragments: this.props?.externalFragments, + }, + hintOptions: { + schema: this.props.schema, + closeOnUnfocus: false, + completeSingle: false, + container: this._node, + externalFragments: this.props?.externalFragments, + }, + info: { + schema: this.props.schema, + renderDescription: (text) => md.render(text), + onClick: (reference) => + this.props.onClickReference && this.props.onClickReference(reference), + }, + jump: { + schema: this.props.schema, + onClick: (reference) => + this.props.onClickReference && this.props.onClickReference(reference) + }, + gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], + extraKeys: { + 'Cmd-Space': () => + editor.showHint({ completeSingle: true, container: this._node }), + 'Ctrl-Space': () => + editor.showHint({ completeSingle: true, container: this._node }), + 'Alt-Space': () => + editor.showHint({ completeSingle: true, container: this._node }), + 'Shift-Space': () => + editor.showHint({ completeSingle: true, container: this._node }), + 'Shift-Alt-Space': () => + editor.showHint({ completeSingle: true, container: this._node }), + + 'Cmd-Enter': () => { + if (this.props.onRunQuery) { + this.props.onRunQuery(); + } + }, + 'Ctrl-Enter': () => { + if (this.props.onRunQuery) { + this.props.onRunQuery(); + } + }, + + 'Shift-Ctrl-C': () => { + if (this.props.onCopyQuery) { + this.props.onCopyQuery(); + } + }, + + 'Shift-Ctrl-P': () => { + if (this.props.onPrettifyQuery) { + this.props.onPrettifyQuery(); + } + }, + + /* Shift-Ctrl-P is hard coded in Firefox for private browsing so adding an alternative to Pretiffy */ + + 'Shift-Ctrl-F': () => { + if (this.props.onPrettifyQuery) { + this.props.onPrettifyQuery(); + } + }, + + 'Shift-Ctrl-M': () => { + if (this.props.onMergeQuery) { + this.props.onMergeQuery(); + } + }, + 'Cmd-S': () => { + if (this.props.onRunQuery) { + // empty + } + }, + + 'Ctrl-S': () => { + if (this.props.onRunQuery) { + // empty + } + }, + }, + })); + if (editor) { + editor.on('change', this._onEdit); + editor.on('keyup', this._onKeyUp); + editor.on('hasCompletion', this._onHasCompletion); + editor.on('beforeChange', this._onBeforeChange); } + } - return () => { - if(cmEditor) { - cmEditor.toTextArea(); + componentDidUpdate(prevProps) { + // Ensure the changes caused by this update are not interpretted as + // user-input changes which could otherwise result in an infinite + // event loop. + this.ignoreChangeEvent = true; + if (this.props.schema !== prevProps.schema && this.editor) { + this.editor.options.lint.schema = this.props.schema; + this.editor.options.hintOptions.schema = this.props.schema; + this.editor.options.info.schema = this.props.schema; + this.editor.options.jump.schema = this.props.schema; + CodeMirror.signal(this.editor, 'change', this.editor); + } + if ( + this.props.value !== prevProps.value && + this.props.value !== this.cachedValue && + this.editor + ) { + this.cachedValue = this.props.value; + this.editor.setValue(this.props.value); + } + this.ignoreChangeEvent = false; + } + + componentWillUnmount() { + if (this.editor) { + this.editor.off('change', this._onEdit); + this.editor.off('keyup', this._onKeyUp); + this.editor.off('hasCompletion', this._onHasCompletion); + this.editor = null; + } + } + + render() { + return ( + { + this._node = node; + }} + /> + ); + } + + /** + * Public API for retrieving the DOM client height for this component. + */ + getClientHeight() { + return this._node && this._node.clientHeight; + } + + _onKeyUp = (_cm, event) => { + if (AUTO_COMPLETE_AFTER_KEY.test(event.key) && this.editor) { + this.editor.execCommand('autocomplete'); + } + }; + + _onEdit = () => { + if (!this.ignoreChangeEvent && this.editor) { + this.cachedValue = this.editor.getValue(); + if (this.props.onEdit) { + this.props.onEdit(this.cachedValue); } } - }, [editor.current, cmEditor]); + }; - return ( - -
- -
-
- ); -}; + /** + * Render a custom UI for CodeMirror's hint which includes additional info + * about the type and description for the selected context. + */ + _onHasCompletion = (cm, data) => { + onHasCompletion(cm, data, this.props.onHintInformationRender); + }; -export default QueryEditor; \ No newline at end of file + _onBeforeChange(_instance, change) { + // The update function is only present on non-redo, non-undo events. + if (change.origin === 'paste') { + const text = change.text.map(normalizeWhitespace); + change.update(change.from, change.to, text); + } + } +} diff --git a/packages/grafnode-components/src/components/QueryEditor/onHasCompletion.js b/packages/grafnode-components/src/components/QueryEditor/onHasCompletion.js new file mode 100644 index 000000000..05d711864 --- /dev/null +++ b/packages/grafnode-components/src/components/QueryEditor/onHasCompletion.js @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2021 GraphQL Contributors. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import escapeHTML from 'escape-html'; +import MD from 'markdown-it'; + +import { + GraphQLNonNull, + GraphQLList, + GraphQLType, + GraphQLField, +} from 'graphql'; + +const md = new MD(); + +/** + * Render a custom UI for CodeMirror's hint which includes additional info + * about the type and description for the selected context. + */ +export default function onHasCompletion( + _cm, + data, + onHintInformationRender, +) { + const CodeMirror = require('codemirror'); + + let information; + let deprecation; + + // When a hint result is selected, we augment the UI with information. + CodeMirror.on( + data, + 'select', + (ctx, el) => { + // Only the first time (usually when the hint UI is first displayed) + // do we create the information nodes. + if (!information) { + const hintsUl = el.parentNode; + + // This "information" node will contain the additional info about the + // highlighted typeahead option. + information = document.createElement('div'); + information.className = 'CodeMirror-hint-information'; + hintsUl.appendChild(information); + + // This "deprecation" node will contain info about deprecated usage. + deprecation = document.createElement('div'); + deprecation.className = 'CodeMirror-hint-deprecation'; + hintsUl.appendChild(deprecation); + + // When CodeMirror attempts to remove the hint UI, we detect that it was + // removed and in turn remove the information nodes. + let onRemoveFn; + hintsUl.addEventListener( + 'DOMNodeRemoved', + (onRemoveFn = (event) => { + if (event.target === hintsUl) { + hintsUl.removeEventListener('DOMNodeRemoved', onRemoveFn); + information = null; + deprecation = null; + onRemoveFn = null; + } + }), + ); + } + + // Now that the UI has been set up, add info to information. + const description = ctx.description + ? md.render(ctx.description) + : 'Self descriptive.'; + const type = ctx.type + ? '' + renderType(ctx.type) + '' + : ''; + + information.innerHTML = + '
' + + (description.slice(0, 3) === '

' + ? '

' + type + description.slice(3) + : type + description) + + '

'; + + if (ctx && deprecation && ctx.deprecationReason) { + const reason = ctx.deprecationReason + ? md.render(ctx.deprecationReason) + : ''; + deprecation.innerHTML = + 'Deprecated' + reason; + deprecation.style.display = 'block'; + } else if (deprecation) { + deprecation.style.display = 'none'; + } + + // Additional rendering? + if (onHintInformationRender) { + onHintInformationRender(information); + } + }, + ); +} + +function renderType(type) { + if (type instanceof GraphQLNonNull) { + return `${renderType(type.ofType)}!`; + } + if (type instanceof GraphQLList) { + return `[${renderType(type.ofType)}]`; + } + return `${escapeHTML(type.name)}`; +} diff --git a/packages/grafnode-components/src/components/RequestPane/index.js b/packages/grafnode-components/src/components/RequestPane/index.js index fa491fa40..ea7f96911 100644 --- a/packages/grafnode-components/src/components/RequestPane/index.js +++ b/packages/grafnode-components/src/components/RequestPane/index.js @@ -3,7 +3,7 @@ import { Tab, TabList, TabPanel, Tabs } from 'react-tabs'; import StyledWrapper from './StyledWrapper'; import QueryEditor from '../QueryEditor'; -const RequestPane = ({leftPaneWidth, query, onQueryChange}) => { +const RequestPane = ({onRunQuery, schema, leftPaneWidth, value, onQueryChange}) => { return ( @@ -13,8 +13,10 @@ const RequestPane = ({leftPaneWidth, query, onQueryChange}) => { diff --git a/packages/grafnode-components/src/components/RequestTabPanel/index.js b/packages/grafnode-components/src/components/RequestTabPanel/index.js index cbc6009ea..ebf237eeb 100644 --- a/packages/grafnode-components/src/components/RequestTabPanel/index.js +++ b/packages/grafnode-components/src/components/RequestTabPanel/index.js @@ -8,6 +8,7 @@ import { flattenItems, findItem } from '../../utils'; +import useGraphqlSchema from '../../hooks/useGraphqlSchema'; import StyledWrapper from './StyledWrapper'; @@ -19,6 +20,9 @@ const RequestTabPanel = ({collections, activeRequestTabId, requestTabs}) => { let asideWidth = 200; let [data, setData] = useState({}); let [url, setUrl] = useState('https://api.spacex.land/graphql'); + let { + schema + } = useGraphqlSchema('https://api.spacex.land/graphql'); let [query, setQuery] = useState(''); let [isLoading, setIsLoading] = useState(false); const [leftPaneWidth, setLeftPaneWidth] = useState(500); @@ -49,12 +53,8 @@ const RequestTabPanel = ({collections, activeRequestTabId, requestTabs}) => { }; }, [dragging, leftPaneWidth]); - const onUrlChange = (value) => { - setUrl(value); - }; - const onQueryChange = (value) => { - setQuery(value); - }; + const onUrlChange = (value) => setUrl(value); + const onQueryChange = (value) => setQuery(value); if(!activeRequestTabId) { return ( @@ -116,8 +116,10 @@ const RequestTabPanel = ({collections, activeRequestTabId, requestTabs}) => {
diff --git a/packages/grafnode-components/src/hooks/useGraphqlSchema/index.js b/packages/grafnode-components/src/hooks/useGraphqlSchema/index.js new file mode 100644 index 000000000..f5e7cebb7 --- /dev/null +++ b/packages/grafnode-components/src/hooks/useGraphqlSchema/index.js @@ -0,0 +1,45 @@ +import { useState, useEffect } from 'react'; +import { getIntrospectionQuery, buildClientSchema } from 'graphql'; + +const useGraphqlSchema =(endpoint) => { + const [isLoaded, setIsLoaded] = useState(false); + const [schema, setSchema] = useState(null); + const [error, setError] = useState(null); + + const introspectionQuery = getIntrospectionQuery(); + const queryParams = { + query: introspectionQuery + } + + useEffect(() => { + fetch(endpoint, { + method: "POST", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify(queryParams) + }) + .then((res) => res.json()) + .then((s) => { + if(s && s.data) { + setSchema(buildClientSchema(s.data)); + setIsLoaded(true); + } else { + return Promise.reject(new Error('An error occurred while introspecting schema')); + } + }) + .catch((err) => { + setError(err); + console.log(err); + }); + }, []); + + return { + isLoaded, + schema, + error + }; +} + +export default useGraphqlSchema; \ No newline at end of file diff --git a/packages/grafnode-components/webpack.config.js b/packages/grafnode-components/webpack.config.js index 02d1f8600..834fa7482 100644 --- a/packages/grafnode-components/webpack.config.js +++ b/packages/grafnode-components/webpack.config.js @@ -37,6 +37,9 @@ module.exports = { 'classnames': 'classnames', 'react-tabs': 'react-tabs', 'codemirror': 'codemirror', + 'graphql': 'graphql', + 'escape-html': 'escape-html', + 'markdown-it': 'markdown-it', 'graphql-request': 'graphql-request' }, plugins: [ diff --git a/packages/grafnode-run/package-lock.json b/packages/grafnode-run/package-lock.json index 664095868..2446473ee 100644 --- a/packages/grafnode-run/package-lock.json +++ b/packages/grafnode-run/package-lock.json @@ -17,9 +17,11 @@ "classnames": "^2.3.1", "codemirror": "^5.64.0", "codemirror-graphql": "^1.2.5", + "escape-html": "^1.0.3", "graphql-request": "^3.7.0", "immer": "^9.0.7", "lodash": "^4.17.21", + "markdown-it": "^12.2.0", "nanoid": "^3.1.30", "next": "12.0.4", "react": "17.0.2", @@ -4279,6 +4281,14 @@ "node": ">=8.6" } }, + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -4356,6 +4366,11 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -6248,6 +6263,14 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", @@ -6375,6 +6398,26 @@ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, + "node_modules/markdown-it": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.2.0.tgz", + "integrity": "sha512-Wjws+uCrVQRqOoJvze4HCqkKl1AsSh95iFAeQDwnyfxM09divCBSXlDR1uTvyUP3Grzpn4Ru8GeCxYPM8vkCQg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -6385,6 +6428,11 @@ "safe-buffer": "^5.1.2" } }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -8833,6 +8881,11 @@ "node": ">=4.2.0" } }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, "node_modules/unbox-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", @@ -12405,6 +12458,11 @@ "ansi-colors": "^4.1.1" } }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -12467,6 +12525,11 @@ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -13848,6 +13911,14 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "requires": { + "uc.micro": "^1.0.1" + } + }, "loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", @@ -13953,6 +14024,25 @@ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, + "markdown-it": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.2.0.tgz", + "integrity": "sha512-Wjws+uCrVQRqOoJvze4HCqkKl1AsSh95iFAeQDwnyfxM09divCBSXlDR1uTvyUP3Grzpn4Ru8GeCxYPM8vkCQg==", + "requires": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + } + } + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -13963,6 +14053,11 @@ "safe-buffer": "^5.1.2" } }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -15763,6 +15858,11 @@ "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", "peer": true }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, "unbox-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", diff --git a/packages/grafnode-run/package.json b/packages/grafnode-run/package.json index 0f3068caa..59a8f002a 100644 --- a/packages/grafnode-run/package.json +++ b/packages/grafnode-run/package.json @@ -29,9 +29,11 @@ "classnames": "^2.3.1", "codemirror": "^5.64.0", "codemirror-graphql": "^1.2.5", + "escape-html": "^1.0.3", "graphql-request": "^3.7.0", "immer": "^9.0.7", "lodash": "^4.17.21", + "markdown-it": "^12.2.0", "nanoid": "^3.1.30", "next": "12.0.4", "react": "17.0.2", diff --git a/packages/grafnode-run/src/pageComponents/Main/index.js b/packages/grafnode-run/src/pageComponents/Main/index.js index 94484d663..100da9726 100644 --- a/packages/grafnode-run/src/pageComponents/Main/index.js +++ b/packages/grafnode-run/src/pageComponents/Main/index.js @@ -13,10 +13,23 @@ import 'codemirror/mode/javascript/javascript'; import 'codemirror/addon/edit/matchbrackets'; import 'codemirror/addon/fold/brace-fold'; import 'codemirror/addon/fold/foldgutter'; +import 'codemirror/addon/hint/show-hint'; +import 'codemirror/keymap/sublime'; +import 'codemirror/addon/comment/comment'; +import 'codemirror/addon/edit/closebrackets'; +import 'codemirror/addon/search/search'; +import 'codemirror/addon/search/searchcursor'; +import 'codemirror/addon/search/jump-to-line'; +import 'codemirror/addon/dialog/dialog'; +import 'codemirror-graphql/hint'; +import 'codemirror-graphql/lint'; +import 'codemirror-graphql/info'; +import 'codemirror-graphql/jump'; import 'codemirror-graphql/mode'; import 'codemirror/addon/fold/foldgutter.css'; +import 'codemirror/addon/hint/show-hint.css'; const RequestTabPanel = dynamic(import('@grafnode/components').then(mod => mod.RequestTabPanel), { ssr: false });