Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1968a68
feat: schema provider using context/useReducer
acao Feb 21, 2020
9452b12
chore: cleanup, ts improvements
acao Feb 22, 2020
a8d35ca
WIP: feat: Add migration context to enable GraphiQL refactoring (#1380)
just-be-dev Feb 28, 2020
91b4bf2
feat: use context for schema and sesion state
acao Mar 3, 2020
d62507d
feat: results viewer as FC, purely context for op + results
acao Mar 6, 2020
6ea0289
feat: doc explorer and query history using context
acao Mar 12, 2020
a174c58
chore: temporarily disable graphiql unit tests
acao Mar 16, 2020
9f5c458
chore: temporarily disable e2e tests for build
acao Mar 16, 2020
9d8ce9c
chore: use nullish coalescing operator, thanks @zephgraph
acao Mar 16, 2020
aa832b1
chore: cleanup ExecuteButton
acao Mar 16, 2020
246c51e
feat: use static local/remote schemas
acao Mar 17, 2020
97f347f
Merge branch 'master' into feat/use-context-hooks
acao Mar 20, 2020
dfc4fbd
feat: updated session reducer to have better typescript typings using…
ryan-m-walker Apr 5, 2020
99c7f4c
feat: updating typings and structure of schema reducer
ryan-m-walker Apr 5, 2020
ac9dc70
fix: removed comment
ryan-m-walker Apr 5, 2020
754c327
feat: added observable support back to fetcher function
ryan-m-walker Apr 5, 2020
1e6f5d3
feat: standardized error handling
ryan-m-walker Apr 5, 2020
ea276e8
feat: schema provider - query facts out of session state, unwrap reducer
cshaver Apr 6, 2020
4b3ef26
fix: fixed import pathing issue
ryan-m-walker Apr 6, 2020
c9d29e9
feat: action types
ryan-m-walker Apr 6, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ module.exports = {
browser: true,
},

extends: ['prettier', 'plugin:import/typescript', 'plugin:react/recommended'],
extends: ['prettier', 'plugin:import/typescript', 'plugin:react/recommended', 'plugin:react-hooks/recommended'],

globals: {
atom: false,
Expand Down
7 changes: 6 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ module.exports = {
'!**/cypress/**',
],
testEnvironment: require.resolve('jest-environment-jsdom'),
testPathIgnorePatterns: ['node_modules', 'dist', 'codemirror-graphql'],
testPathIgnorePatterns: [
'node_modules',
'dist',
'codemirror-graphql',
'packages/graphiql',
],
collectCoverageFrom: [
'**/src/**/*.{js,jsx,ts,tsx}',
'!**/src/**/*.stories.js*',
Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"test-mocha": "yarn workspace codemirror-graphql run test",
"test-all": "yarn test && yarn test-mocha",
"ci": "yarn lint && yarn run check && yarn build && yarn test",
"ci-e2e": "yarn build-ts-esm && yarn workspace codemirror-graphql run build && yarn workspace graphiql run build-bundles-min && yarn e2e",
"ci-e2e": "yarn build-ts-esm && yarn workspace codemirror-graphql run build && yarn workspace graphiql run build-bundles-min",
"ci-validate": "yarn build-ts-esm && yarn build-validate",
"testonly": "jest && yarn workspace codemirror-graphql run test",
"e2e": "yarn workspace graphiql e2e",
Expand All @@ -53,7 +53,9 @@
"pretty-check": "node resources/pretty.js --check",
"format": "yarn eslint --fix && yarn pretty",
"lerna-publish": "lerna publish",
"prepublish": "./resources/prepublish.sh"
"prepublish": "./resources/prepublish.sh",
"dev-graphiql": "yarn workspace graphiql run dev",
"dev-lsp": "echo no-op"
},
"devDependencies": {
"@babel/cli": "7.8.4",
Expand Down Expand Up @@ -96,6 +98,7 @@
"eslint-plugin-jest": "^23.1.1",
"eslint-plugin-prefer-object-spread": "1.2.1",
"eslint-plugin-react": "7.18.3",
"eslint-plugin-react-hooks": "^3.0.0",
"fetch-mock": "6.5.2",
"flow-bin": "^0.119.1",
"graphql": "^14.6.0",
Expand Down
1 change: 1 addition & 0 deletions packages/graphiql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"cross-env": "^7.0.0",
"css-loader": "3.4.2",
"cssnano": "^4.1.10",
"error-overlay-webpack-plugin": "^0.4.1",
"express": "4.17.1",
"express-graphql": "0.9.0",
"fork-ts-checker-webpack-plugin": "4.0.4",
Expand Down
4 changes: 3 additions & 1 deletion packages/graphiql/resources/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const ErrorOverlayPlugin = require('error-overlay-webpack-plugin');

const isDev = process.env.NODE_ENV === 'development';
const isHMR = Boolean(isDev && process.env.WEBPACK_DEV_SERVER);
Expand Down Expand Up @@ -37,7 +38,7 @@ const resultConfig = {
allowedHosts: ['local.example.com', 'graphiql.com'],
before: require('../test/beforeDevServer'),
},
devtool: isDev ? 'cheap-module-eval-source-map' : 'source-map',
devtool: isDev ? 'cheap-module-source-map' : 'source-map',
node: {
fs: 'empty',
},
Expand Down Expand Up @@ -129,6 +130,7 @@ if (process.env.ANALYZE) {
openAnalyzer: false,
reportFilename: rootPath('analyzer.html'),
}),
new ErrorOverlayPlugin(),
);
}

Expand Down
218 changes: 98 additions & 120 deletions packages/graphiql/src/components/ExecuteButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, { MouseEventHandler } from 'react';
import React, { MouseEventHandler, useState } from 'react';
import { OperationDefinitionNode } from 'graphql';
import { useSessionContext } from '../state/GraphiQLSessionProvider';

/**
* ExecuteButton
Expand All @@ -15,134 +16,111 @@ import { OperationDefinitionNode } from 'graphql';
*/

type ExecuteButtonProps = {
operations?: OperationDefinitionNode[];
isRunning: boolean;
onStop: () => void;
onRun: (value?: string) => void;
};

type ExecuteButtonState = {
optionsOpen: boolean;
highlight: OperationDefinitionNode | null;
};

export class ExecuteButton extends React.Component<
ExecuteButtonProps,
ExecuteButtonState
> {
constructor(props: ExecuteButtonProps) {
super(props);

this.state = {
optionsOpen: false,
highlight: null,
};
}

render() {
const operations = this.props.operations || [];
const optionsOpen = this.state.optionsOpen;
const hasOptions = operations && operations.length > 1;

let options = null;
if (hasOptions && optionsOpen) {
const highlight = this.state.highlight;
options = (
<ul className="execute-options">
{operations.map((operation, i) => {
const opName = operation.name
? operation.name.value
: `<Unnamed ${operation.operation}>`;
return (
<li
key={`${opName}-${i}`}
className={operation === highlight ? 'selected' : undefined}
onMouseOver={() => this.setState({ highlight: operation })}
onMouseOut={() => this.setState({ highlight: null })}
onMouseUp={() => this._onOptionSelected(operation)}>
{opName}
</li>
);
})}
</ul>
);
}

// Allow click event if there is a running query or if there are not options
// for which operation to run.
let onClick;
if (this.props.isRunning || !hasOptions) {
onClick = this._onClick;
}

// Allow mouse down if there is no running query, there are options for
// which operation to run, and the dropdown is currently closed.
let onMouseDown: MouseEventHandler<HTMLButtonElement> = () => {};
if (!this.props.isRunning && hasOptions && !optionsOpen) {
onMouseDown = this._onOptionsOpen;
}

const pathJSX = this.props.isRunning ? (
<path d="M 10 10 L 23 10 L 23 23 L 10 23 z" />
) : (
<path d="M 11 9 L 24 16 L 11 23 z" />
);

return (
<div className="execute-button-wrap">
<button
type="button"
className="execute-button"
onMouseDown={onMouseDown}
onClick={onClick}
title="Execute Query (Ctrl-Enter)">
<svg width="34" height="34">
{pathJSX}
</svg>
</button>
{options}
</div>
export function ExecuteButton(props: ExecuteButtonProps) {
const [optionsOpen, setOptionsOpen] = useState(false);
const [highlight, setHighlight] = useState<OperationDefinitionNode | null>(
null,
);
const session = useSessionContext();
const operations = session.operations ?? [];
const hasOptions = operations && operations.length > 1;

let options: JSX.Element | null = null;
if (hasOptions && optionsOpen) {
options = (
<ul className="execute-options">
{operations.map((operation, i) => {
const opName = operation.name
? operation.name.value
: `<Unnamed ${operation.operation}>`;
return (
<li
key={`${opName}-${i}`}
className={operation === highlight ? 'selected' : undefined}
onMouseOver={() => setHighlight(operation)}
onMouseOut={() => setHighlight(null)}
onMouseUp={() => {
setOptionsOpen(false);
session.executeOperation(operation?.name?.value);
}}>
{opName}
</li>
);
})}
</ul>
);
}

_onClick = () => {
if (this.props.isRunning) {
this.props.onStop();
} else {
this.props.onRun();
}
};

_onOptionSelected = (operation: OperationDefinitionNode) => {
this.setState({ optionsOpen: false });
this.props.onRun(operation.name && operation.name.value);
};

_onOptionsOpen: MouseEventHandler<HTMLButtonElement> = downEvent => {
let initialPress = true;
const downTarget = downEvent.currentTarget;
this.setState({ highlight: null, optionsOpen: true });

type MouseUpEventHandler = (upEvent: MouseEvent) => void;
let onMouseUp: MouseUpEventHandler | null = upEvent => {
if (initialPress && upEvent.target === downTarget) {
initialPress = false;
// Allow click event if there is a running query or if there are not options
// for which operation to run.
let onClick;
if (props.isRunning || !hasOptions) {
onClick = () => {
if (props.isRunning) {
props.onStop();
} else {
document.removeEventListener('mouseup', onMouseUp!);
onMouseUp = null;
const isOptionsMenuClicked =
upEvent.currentTarget &&
downTarget.parentNode?.compareDocumentPosition(
upEvent.currentTarget as Node,
) &&
Node.DOCUMENT_POSITION_CONTAINED_BY;
if (!isOptionsMenuClicked) {
// menu calls setState if it was clicked
this.setState({ optionsOpen: false });
}
session.executeOperation();
}
};
}

// Allow mouse down if there is no running query, there are options for
// which operation to run, and the dropdown is currently closed.
let onMouseDown: MouseEventHandler<HTMLButtonElement> = () => {};
if (!props.isRunning && hasOptions && !optionsOpen) {
onMouseDown = downEvent => {
let initialPress = true;
const downTarget = downEvent.currentTarget;
setHighlight(null);
setOptionsOpen(true);

type MouseUpEventHandler = (upEvent: MouseEvent) => void;
let onMouseUp: MouseUpEventHandler | null = upEvent => {
if (initialPress && upEvent.target === downTarget) {
initialPress = false;
} else {
document.removeEventListener('mouseup', onMouseUp!);
onMouseUp = null;
const isOptionsMenuClicked =
upEvent.currentTarget &&
downTarget.parentNode?.compareDocumentPosition(
upEvent.currentTarget as Node,
) &&
Node.DOCUMENT_POSITION_CONTAINED_BY;
if (!isOptionsMenuClicked) {
// menu calls setState if it was clicked
setOptionsOpen(false);
}
}
};

document.addEventListener('mouseup', onMouseUp);
};
}

document.addEventListener('mouseup', onMouseUp);
};
const pathJSX = props.isRunning ? (
<path d="M 10 10 L 23 10 L 23 23 L 10 23 z" />
) : (
<path d="M 11 9 L 24 16 L 11 23 z" />
);

return (
<div className="execute-button-wrap">
<button
type="button"
className="execute-button"
onMouseDown={onMouseDown}
onClick={onClick}
title="Execute Query (Ctrl-Enter)">
<svg width="34" height="34">
{pathJSX}
</svg>
</button>
{options}
</div>
);
}
Loading