diff --git a/src/Explorer.js b/src/Explorer.js index f692c8c..dd8c09f 100644 --- a/src/Explorer.js +++ b/src/Explorer.js @@ -30,7 +30,6 @@ import { import type { ArgumentNode, - ASTNode, DocumentNode, FieldNode, GraphQLArgument, @@ -121,8 +120,12 @@ type Props = { showAttribution: boolean, }; +type OperationType = 'query' | 'mutation' | 'subscription' | 'fragment'; +type NewOperationType = 'query' | 'mutation' | 'subscription'; + type State = {| - operation: OperationDefinitionNode, + operation: ?OperationDefinitionNode, + newOperationType: NewOperationType, |}; type Selections = $ReadOnlyArray; @@ -1234,9 +1237,9 @@ class FieldView extends React.PureComponent { const type = unwrapOutputType(field.type); const args = field.args.sort((a, b) => a.name.localeCompare(b.name)); let className = 'graphiql-explorer-node'; - + if (field.isDeprecated) { - className += ' deprecated'; + className += ' graphiql-explorer-deprecated'; } const node = ( @@ -1267,7 +1270,11 @@ class FieldView extends React.PureComponent { styleConfig={this.props.styleConfig} /> )} - {field.name} + + {field.name} + {selection && args.length ? (
@@ -1427,8 +1434,9 @@ const defaultStyles = { type RootViewProps = {| schema: GraphQLSchema, + isLast: boolean, fields: ?GraphQLFieldMap, - operation: 'query' | 'mutation' | 'subscription' | 'fragment', + operation: OperationType, name: ?string, onTypeName: ?string, definition: FragmentDefinitionNode | OperationDefinitionNode, @@ -1444,6 +1452,7 @@ type RootViewProps = {| |}; class RootView extends React.PureComponent { + state = {newOperationType: 'query'}; _previousOperationDef: ?OperationDefinitionNode | ?FragmentDefinitionNode; _modifySelections = (selections: Selections) => { @@ -1459,10 +1468,7 @@ class RootView extends React.PureComponent { let newOperationDef: ?OperationDefinitionNode | ?FragmentDefinitionNode; - if (selections.length === 0) { - this._previousOperationDef = operationDef; - newOperationDef = null; - } else if (operationDef.kind === 'FragmentDefinition') { + if (operationDef.kind === 'FragmentDefinition') { newOperationDef = { ...operationDef, selectionSet: { @@ -1471,11 +1477,29 @@ class RootView extends React.PureComponent { }, }; } else if (operationDef.kind === 'OperationDefinition') { + let cleanedSelections = selections.filter(selection => { + return !( + selection.kind === 'Field' && selection.name.value === '__typename' + ); + }); + + if (cleanedSelections.length === 0) { + cleanedSelections = [ + { + kind: 'Field', + name: { + kind: 'Name', + value: '__typename ## Placeholder value', + }, + }, + ]; + } + newOperationDef = { ...operationDef, selectionSet: { ...operationDef.selectionSet, - selections, + selections: cleanedSelections, }, }; } @@ -1512,7 +1536,8 @@ class RootView extends React.PureComponent {
@@ -1601,6 +1626,8 @@ class Explorer extends React.PureComponent { getDefaultScalarArgValue: defaultGetDefaultScalarArgValue, }; + state = {newOperationType: 'query', operation: null}; + _ref: ?any; _resetScroll = () => { const container = this._ref; @@ -1611,8 +1638,13 @@ class Explorer extends React.PureComponent { componentDidMount() { this._resetScroll(); } + _onEdit = (query: string): void => this.props.onEdit(query); + _setAddOperationType = (value: NewOperationType) => { + this.setState({newOperationType: value}); + }; + render() { const {schema, query, makeDefaultArg} = this.props; @@ -1697,7 +1729,7 @@ class Explorer extends React.PureComponent { }; }; - const addOperation = (kind: 'query' | 'mutation' | 'subscription') => { + const addOperation = (kind: NewOperationType) => { const existingDefs = parsedQuery.definitions; const viewingDefaultOperation = @@ -1766,6 +1798,81 @@ class Explorer extends React.PureComponent { this.props.onEdit(print(newOperationDef)); }; + const actionsOptions = [ + !!queryFields ? ( + + ) : null, + !!mutationFields ? ( + + ) : null, + !!subscriptionFields ? ( + + ) : null, + ].filter(Boolean); + + const actionsEl = + actionsOptions.length === 0 ? null : ( +
+
event.preventDefault()}> + + +
+
+ ); + + const attribution = this.props.showAttribution ? : null; + return (
{ @@ -1779,121 +1886,103 @@ class Explorer extends React.PureComponent { padding: 8, fontFamily: 'Consolas, Inconsolata, "Droid Sans Mono", Monaco, monospace', + display: 'flex', + flexDirection: 'column', + height: '100%', }} className="graphiql-explorer-root"> - {relevantOperations.map( - ( - operation: OperationDefinitionNode | FragmentDefinitionNode, - index, - ) => { - const operationName = - operation && operation.name && operation.name.value; - - const operationKind = - operation.kind === 'FragmentDefinition' - ? 'fragment' - : (operation && operation.operation) || 'query'; - - const onOperationRename = newName => { - const newOperationDef = renameOperation(operation, newName); - this.props.onEdit(print(newOperationDef)); - }; - - const fragmentType = - operation.kind === 'FragmentDefinition' && - operation.typeCondition.kind === 'NamedType' && - schema.getType(operation.typeCondition.name.value); - - const fragmentFields = - fragmentType instanceof GraphQLObjectType - ? fragmentType.getFields() - : null; - - const fields = - operationKind === 'query' - ? queryFields - : operationKind === 'mutation' - ? mutationFields - : operationKind === 'subscription' - ? subscriptionFields - : operation.kind === 'FragmentDefinition' - ? fragmentFields - : null; - - const fragmentTypeName = - operation.kind === 'FragmentDefinition' - ? operation.typeCondition.name.value - : null; - - return ( - { - const newQuery = { - ...parsedQuery, - definitions: parsedQuery.definitions.map( - existingDefinition => - existingDefinition === operation - ? newDefinition - : existingDefinition, - ), - }; - - const textualNewQuery = print(newQuery); - - this.props.onEdit(textualNewQuery); - }} - schema={schema} - getDefaultFieldNames={getDefaultFieldNames} - getDefaultScalarArgValue={getDefaultScalarArgValue} - makeDefaultArg={makeDefaultArg} - onRunOperation={() => { - if (!!this.props.onRunOperation) { - this.props.onRunOperation(operationName); - } - }} - styleConfig={styleConfig} - /> - ); - }, - )}
- {!!queryFields ? ( - - ) : null} - {!!mutationFields ? ( - - ) : null} - {!!subscriptionFields ? ( - - ) : null} + style={{ + flexGrow: '1', + overflow: 'scroll', + }}> + {relevantOperations.map( + ( + operation: OperationDefinitionNode | FragmentDefinitionNode, + index, + ) => { + const operationName = + operation && operation.name && operation.name.value; + + const operationKind = + operation.kind === 'FragmentDefinition' + ? 'fragment' + : (operation && operation.operation) || 'query'; + + const onOperationRename = newName => { + const newOperationDef = renameOperation(operation, newName); + this.props.onEdit(print(newOperationDef)); + }; + + const fragmentType = + operation.kind === 'FragmentDefinition' && + operation.typeCondition.kind === 'NamedType' && + schema.getType(operation.typeCondition.name.value); + + const fragmentFields = + fragmentType instanceof GraphQLObjectType + ? fragmentType.getFields() + : null; + + const fields = + operationKind === 'query' + ? queryFields + : operationKind === 'mutation' + ? mutationFields + : operationKind === 'subscription' + ? subscriptionFields + : operation.kind === 'FragmentDefinition' + ? fragmentFields + : null; + + const fragmentTypeName = + operation.kind === 'FragmentDefinition' + ? operation.typeCondition.name.value + : null; + + return ( + { + const newQuery = { + ...parsedQuery, + definitions: parsedQuery.definitions.map( + existingDefinition => + existingDefinition === operation + ? newDefinition + : existingDefinition, + ), + }; + + const textualNewQuery = print(newQuery); + + this.props.onEdit(textualNewQuery); + }} + schema={schema} + getDefaultFieldNames={getDefaultFieldNames} + getDefaultScalarArgValue={getDefaultScalarArgValue} + makeDefaultArg={makeDefaultArg} + onRunOperation={() => { + if (!!this.props.onRunOperation) { + this.props.onRunOperation(operationName); + } + }} + styleConfig={styleConfig} + /> + ); + }, + )} + {attribution}
+ + {actionsEl}
); } @@ -1930,23 +2019,25 @@ class ErrorBoundary extends React.Component< class ExplorerWrapper extends React.PureComponent { static defaultValue = defaultValue; static defaultProps = { - width: 380, + width: 320, title: 'Explorer', }; render() { - const attribution = this.props.showAttribution ? : null; return (
-
-
{this.props.title}
+
+
{this.props.title}
{
-
-
- - - - {attribution} -
+
+ + +
);