From a020b26342f552d7d6e16e37b93e93cfe16349e4 Mon Sep 17 00:00:00 2001 From: rajinwonderland Date: Mon, 3 Dec 2018 10:42:48 -0800 Subject: [PATCH 01/10] Merged from sdl-tabs https://github.com/Novvum/graphql-playground.git --- .../Playground/DocExplorer/ColumnDoc.tsx | 24 +- .../Playground/DocExplorer/DocsStyles.tsx | 16 +- .../DocExplorer/DocsTypes/EnumTypeSchema.tsx | 5 +- .../Playground/DocExplorer/ErrorContainer.tsx | 13 + .../Playground/DocExplorer/GraphDocs.tsx | 687 ++++----- .../Playground/ExplorerTabs/SideTab.tsx | 55 + .../Playground/ExplorerTabs/SideTabs.tsx | 388 +++++ .../components/Playground/GraphQLEditor.tsx | 1260 +++++++++-------- .../Playground/SchemaExplorer/SDLEditor.tsx | 111 ++ .../SchemaExplorer/SDLTypes/SDLDocType.tsx | 86 ++ .../SchemaExplorer/SDLTypes/SDLFieldDoc.tsx | 54 + .../SchemaExplorer/SDLTypes/SDLStyles.tsx | 178 +++ .../SchemaExplorer/SDLTypes/SDLType.tsx | 130 ++ .../SchemaExplorer/SDLTypes/SDLUnionType.tsx | 22 + .../Playground/SchemaExplorer/SDLView.tsx | 122 ++ .../components/Playground/SchemaFetcher.ts | 6 +- .../components/Playground/TopBar/TopBar.tsx | 360 ++--- .../components/Playground/util/createSDL.ts | 104 ++ .../Playground/util/immutableMemoize.ts | 16 +- .../src/components/asyncComponent.tsx | 26 + .../src/components/util.ts | 50 +- .../src/localDevIndex.tsx | 156 +- .../src/state/docs/actions.ts | 32 +- .../src/state/docs/reducers.ts | 20 +- .../src/styled/theme.ts | 592 ++++---- .../graphql-playground-react/src/utils.ts | 34 +- 26 files changed, 2845 insertions(+), 1702 deletions(-) create mode 100644 packages/graphql-playground-react/src/components/Playground/DocExplorer/ErrorContainer.tsx create mode 100644 packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx create mode 100644 packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx create mode 100644 packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLEditor.tsx create mode 100644 packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLDocType.tsx create mode 100644 packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLFieldDoc.tsx create mode 100644 packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLStyles.tsx create mode 100644 packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLType.tsx create mode 100644 packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLUnionType.tsx create mode 100644 packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLView.tsx create mode 100644 packages/graphql-playground-react/src/components/Playground/util/createSDL.ts create mode 100644 packages/graphql-playground-react/src/components/asyncComponent.tsx diff --git a/packages/graphql-playground-react/src/components/Playground/DocExplorer/ColumnDoc.tsx b/packages/graphql-playground-react/src/components/Playground/DocExplorer/ColumnDoc.tsx index af03eb2f5..ef4e16f87 100644 --- a/packages/graphql-playground-react/src/components/Playground/DocExplorer/ColumnDoc.tsx +++ b/packages/graphql-playground-react/src/components/Playground/DocExplorer/ColumnDoc.tsx @@ -3,27 +3,27 @@ import { columnWidth } from '../../../constants' import { styled } from '../../../styled' export interface Props { - children: any - overflow?: boolean - width?: number + children: any + overflow?: boolean + width?: number } const ColumnDoc = ({ - children, - overflow = true, - width = columnWidth, + children, + overflow = true, + width = columnWidth, }: Props) => { - return ( - - {children} - - ) + return ( + + {children} + + ) } export default ColumnDoc interface ColumnProps { - overflow: boolean + overflow: boolean } const Column = styled('div')` diff --git a/packages/graphql-playground-react/src/components/Playground/DocExplorer/DocsStyles.tsx b/packages/graphql-playground-react/src/components/Playground/DocExplorer/DocsStyles.tsx index 7a5323143..f983751e3 100644 --- a/packages/graphql-playground-react/src/components/Playground/DocExplorer/DocsStyles.tsx +++ b/packages/graphql-playground-react/src/components/Playground/DocExplorer/DocsStyles.tsx @@ -2,13 +2,13 @@ import * as React from 'react' import { styled } from '../../../styled' const Title = styled.div` - color: rgba(0, 0, 0, 0.3); - cursor: default; - font-size: 14px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 1px; - padding: 16px; - user-select: none; + color: rgba(0, 0, 0, 0.3); + cursor: default; + font-size: 14px; + font-weight: 600; + text-transform: uppercase !important; + letter-spacing: 1px; + padding: 16px; + user-select: none; ` export const CategoryTitle = ({ children }) => {children} diff --git a/packages/graphql-playground-react/src/components/Playground/DocExplorer/DocsTypes/EnumTypeSchema.tsx b/packages/graphql-playground-react/src/components/Playground/DocExplorer/DocsTypes/EnumTypeSchema.tsx index 25ca1343a..795219b89 100644 --- a/packages/graphql-playground-react/src/components/Playground/DocExplorer/DocsTypes/EnumTypeSchema.tsx +++ b/packages/graphql-playground-react/src/components/Playground/DocExplorer/DocsTypes/EnumTypeSchema.tsx @@ -4,10 +4,11 @@ import { DocType } from './DocType' export interface EnumTypeSchemaProps { type: any + sdlType?: boolean } -const EnumTypeSchema = ({ type }: EnumTypeSchemaProps) => { - const values = type.getValues() +const EnumTypeSchema = ({ type, sdlType }: EnumTypeSchemaProps) => { + const values = sdlType ? type._values : type.getValues() const deprecatedValues = values.filter((value: any) => value.isDeprecated) return ( diff --git a/packages/graphql-playground-react/src/components/Playground/DocExplorer/ErrorContainer.tsx b/packages/graphql-playground-react/src/components/Playground/DocExplorer/ErrorContainer.tsx new file mode 100644 index 000000000..0c536bdac --- /dev/null +++ b/packages/graphql-playground-react/src/components/Playground/DocExplorer/ErrorContainer.tsx @@ -0,0 +1,13 @@ +import { styled } from '../../../styled' +export const ErrorContainer = styled.div` + font-weight: bold; + left: 0; + letter-spacing: 1px; + opacity: 0.5; + position: absolute; + right: 0; + text-align: center; + text-transform: uppercase; + top: 50%; + transform: translate(0, -50%); +` diff --git a/packages/graphql-playground-react/src/components/Playground/DocExplorer/GraphDocs.tsx b/packages/graphql-playground-react/src/components/Playground/DocExplorer/GraphDocs.tsx index 382a6ae20..bc2083494 100644 --- a/packages/graphql-playground-react/src/components/Playground/DocExplorer/GraphDocs.tsx +++ b/packages/graphql-playground-react/src/components/Playground/DocExplorer/GraphDocs.tsx @@ -2,484 +2,279 @@ import * as React from 'react' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import * as keycode from 'keycode' -import { getLeft } from 'graphiql/dist/utility/elementPosition' import FieldDoc from './FieldDoc' import ColumnDoc from './ColumnDoc' import { - addStack, - toggleDocs, - changeWidthDocs, - changeKeyMove, - setDocsVisible, + addStack, + toggleDocs, + changeWidthDocs, + changeKeyMove, + setDocsVisible, } from '../../../state/docs/actions' import Spinner from '../../Spinner' import { columnWidth } from '../../../constants' import RootColumn from './RootColumn' import { - serialize, - getElementRoot, - serializeRoot, - getElement, + serialize, + getElementRoot, + serializeRoot, + getElement, } from '../util/stack' -import { GraphQLSchema } from 'graphql' import { getSessionDocs } from '../../../state/docs/selectors' import { getSelectedSessionIdFromRoot } from '../../../state/sessions/selectors' import { createStructuredSelector } from 'reselect' +import { SideTabContentProps } from '../ExplorerTabs/SideTabs' +import { ErrorContainer } from './ErrorContainer' import { styled } from '../../../styled' interface StateFromProps { - docs: { - navStack: any[] - docsOpen: boolean - docsWidth: number - keyMove: boolean - } + docs: { + navStack: any[] + docsOpen: boolean + docsWidth: number + keyMove: boolean + } } interface DispatchFromProps { - addStack: (sessionId: string, field: any, x: number, y: number) => any - toggleDocs: (sessionId: string) => any - setDocsVisible: (sessionId: string, open: boolean) => any - changeWidthDocs: (sessionId: string, width: number) => any - changeKeyMove: (sessionId: string, move: boolean) => any -} - -export interface Props { - schema: GraphQLSchema - sessionId: string + addStack: (sessionId: string, field: any, x: number, y: number) => any + toggleDocs: (sessionId: string) => any + setDocsVisible: (sessionId: string, open: boolean) => any + changeWidthDocs: (sessionId: string, width: number) => any + changeKeyMove: (sessionId: string, move: boolean) => any } export interface State { - searchValue: string - widthMap: any + searchValue: string + widthMap: any } class GraphDocs extends React.Component< - Props & StateFromProps & DispatchFromProps, - State + SideTabContentProps & StateFromProps & DispatchFromProps, + State > { - ref - private refDocExplorer: any - private clientX: number = 0 - private clientY: number = 0 - - constructor(props) { - super(props) - this.state = { - searchValue: '', - widthMap: {}, - } - ;(window as any).d = this - } - - componentWillReceiveProps(nextProps: Props & StateFromProps) { - // If user use default column size % columnWidth - // Make the column follow the clicks - if ( - this.props.docs.navStack.length !== nextProps.docs.navStack.length || - this.props.docs.navStack.slice(-1)[0] !== - nextProps.docs.navStack.slice(-1)[0] || - (!this.props.schema && nextProps.schema) - ) { - this.setWidth(nextProps) - } - } - - setWidth(props: any = this.props) { - requestAnimationFrame(() => { - const width = this.getWidth(props) - this.props.changeWidthDocs( - props.sessionId, - Math.min(width, window.innerWidth - 86), - ) - }) - } - - getWidth(props: any = this.props) { - const rootWidth = this.state.widthMap.root || columnWidth - const stackWidths = props.docs.navStack.map( - stack => this.state.widthMap[stack.field.path] || columnWidth, - ) - - return [rootWidth].concat(stackWidths).reduce((acc, curr) => acc + curr, 0) - } - - componentDidMount() { - this.setWidth() - } - - render() { - const { docsOpen, docsWidth, navStack } = this.props.docs - const { schema } = this.props - const docsStyle = { width: docsOpen ? docsWidth : 0 } - - let emptySchema - if (schema === undefined) { - // Schema is undefined when it is being loaded via introspection. - emptySchema = - } else if (schema === null) { - // Schema is null when it explicitly does not exist, typically due to - // an error during introspection. - emptySchema = {'No Schema Available'} - } - - return ( - - Schema - - - - - {emptySchema && {emptySchema}} - {!emptySchema && - schema && ( - - )} - {navStack.map((stack, index) => ( - - - - ))} - - - - ) - } - - setRef = ref => { - this.ref = ref - } - - public showDocFromType = type => { - this.props.setDocsVisible(this.props.sessionId, true) - this.props.addStack(this.props.sessionId, type, 0, 0) - } - - private setDocExplorerRef = ref => { - this.refDocExplorer = ref - } - - private handleSearch = (value: string) => { - this.setState({ searchValue: value }) - } - - private handleToggleDocs = () => { - if (!this.props.docs.docsOpen && this.refDocExplorer) { - this.refDocExplorer.focus() - } - this.props.toggleDocs(this.props.sessionId) - this.setWidth() - } - - private handleKeyDown = e => { - // we don't want to interfere with inputs - if ( - e.target instanceof HTMLInputElement || - e.metaKey || - e.shiftKey || - e.altKey || - e.ctrlKey - ) { - return - } - e.preventDefault() - this.props.changeKeyMove(this.props.sessionId, true) - const lastNavStack = - this.props.docs.navStack.length > 0 && - this.props.docs.navStack[this.props.docs.navStack.length - 1] - const beforeLastNavStack = - this.props.docs.navStack.length > 0 && - this.props.docs.navStack[this.props.docs.navStack.length - 2] - const keyPressed = keycode(e) - switch (keyPressed) { - case 'esc': - this.props.setDocsVisible(this.props.sessionId, false) - break - case 'left': - if (beforeLastNavStack) { - this.props.addStack( - this.props.sessionId, - beforeLastNavStack.field, - beforeLastNavStack.x, - beforeLastNavStack.y, - ) - } - break - case 'right': - if (lastNavStack) { - const obj = serialize(this.props.schema, lastNavStack.field) - const firstElement = getElement(obj, 0) - if (firstElement) { - this.props.addStack( - this.props.sessionId, - firstElement, - lastNavStack.x + 1, - 0, - ) - } - } else { - const obj = serializeRoot(this.props.schema) - const element = getElementRoot(obj, 0) - if (element) { - this.props.addStack(this.props.sessionId, element, 0, 0) - } - } - break - case 'up': - case 'down': - if (beforeLastNavStack) { - const obj = serialize(this.props.schema, beforeLastNavStack.field) - const element = getElement( - obj, - keyPressed === 'up' ? lastNavStack.y - 1 : lastNavStack.y + 1, - ) - if (element) { - this.props.addStack( - this.props.sessionId, - element, - lastNavStack.x, - keyPressed === 'up' ? lastNavStack.y - 1 : lastNavStack.y + 1, - ) - } - } else { - const obj = serializeRoot(this.props.schema) - const y = lastNavStack ? lastNavStack.y : 0 - const element = getElementRoot( - obj, - keyPressed === 'up' ? y - 1 : y + 1, - ) - if (element) { - this.props.addStack( - this.props.sessionId, - element, - 0, - keyPressed === 'up' ? y - 1 : y + 1, - ) - } - } - break - } - } - - private handleDocsResizeStart = downEvent => { - downEvent.preventDefault() - - const hadWidth = this.props.docs.docsWidth - const offset = downEvent.clientX - getLeft(downEvent.target) - - let onMouseMove: any = moveEvent => { - if (moveEvent.buttons === 0) { - return onMouseUp() - } - - const app = this.ref - const cursorPos = moveEvent.clientX - getLeft(app) - offset - const newSize = app.clientWidth - cursorPos - const maxSize = window.innerWidth - 50 - const docsSize = maxSize < newSize ? maxSize : newSize - - if (docsSize < 100) { - this.props.setDocsVisible(this.props.sessionId, false) - } else { - this.props.setDocsVisible(this.props.sessionId, true) - this.props.changeWidthDocs(this.props.sessionId, docsSize) - } - } - - let onMouseUp: any = () => { - if (!this.props.docs.docsOpen) { - this.props.changeWidthDocs(this.props.sessionId, hadWidth) - } - - document.removeEventListener('mousemove', onMouseMove) - document.removeEventListener('mouseup', onMouseUp) - onMouseMove = null - onMouseUp = null - } - - document.addEventListener('mousemove', onMouseMove) - document.addEventListener('mouseup', onMouseUp) - } - - private handleMouseMove = e => { - this.clientX = e.clientX - this.clientY = e.clientY - if ( - this.props.docs.keyMove && - this.clientX !== e.clientX && - this.clientY !== e.clientY - ) { - this.props.changeKeyMove(this.props.sessionId, false) - } - } + ref + // private refDocExplorer: any; + + constructor(props) { + super(props) + this.state = { + searchValue: '', + widthMap: {}, + } + ;(window as any).d = this + } + + componentWillReceiveProps(nextProps: SideTabContentProps & StateFromProps) { + // If user use default column size % columnWidth + // Make the column follow the clicks + if ( + this.props.docs.navStack.length !== nextProps.docs.navStack.length || + this.props.docs.navStack.slice(-1)[0] !== + nextProps.docs.navStack.slice(-1)[0] || + (!this.props.schema && nextProps.schema) + ) { + this.setWidth(nextProps) + } + } + + setWidth(props: any = this.props) { + this.props.setWidth(props) + } + + getWidth(props: any = this.props) { + const rootWidth = this.state.widthMap.root || columnWidth + const stackWidths = props.docs.navStack.map( + (stack) => this.state.widthMap[stack.field.path] || columnWidth, + ) + + return [rootWidth].concat(stackWidths).reduce((acc, curr) => acc + curr, 0) + } + + componentDidMount() { + this.setWidth() + } + + render() { + const { navStack } = this.props.docs + const { schema } = this.props + let emptySchema + if (schema === undefined) { + // Schema is undefined when it is being loaded via introspection. + emptySchema = + } else if (schema === null) { + // Schema is null when it explicitly does not exist, typically due to + // an error during introspection. + emptySchema = {'No Schema Available'} + } + + return ( + + {emptySchema && {emptySchema}} + {!emptySchema && + schema && ( + + )} + {navStack.map((stack, index) => ( + + + + ))} + + ) + } + + setRef = (ref) => { + this.ref = ref + } + + public showDocFromType = (type) => { + this.props.addStack(this.props.sessionId, type, 0, 0) + } + + private handleSearch = (value: string) => { + this.setState({ searchValue: value }) + } + + private handleKeyDown = (e) => { + // we don't want to interfere with inputs + if ( + e.target instanceof HTMLInputElement || + e.metaKey || + e.shiftKey || + e.altKey || + e.ctrlKey + ) { + return + } + e.preventDefault() + this.props.changeKeyMove(this.props.sessionId, true) + const lastNavStack = + this.props.docs.navStack.length > 0 && + this.props.docs.navStack[this.props.docs.navStack.length - 1] + const beforeLastNavStack = + this.props.docs.navStack.length > 0 && + this.props.docs.navStack[this.props.docs.navStack.length - 2] + const keyPressed = keycode(e) + switch (keyPressed) { + case 'esc': + this.props.setDocsVisible(this.props.sessionId, false) + break + case 'left': + if (beforeLastNavStack) { + this.props.addStack( + this.props.sessionId, + beforeLastNavStack.field, + beforeLastNavStack.x, + beforeLastNavStack.y, + ) + } + break + case 'right': + if (lastNavStack) { + const obj = serialize(this.props.schema, lastNavStack.field) + const firstElement = getElement(obj, 0) + if (firstElement) { + this.props.addStack( + this.props.sessionId, + firstElement, + lastNavStack.x + 1, + 0, + ) + } + } else { + const obj = serializeRoot(this.props.schema) + const element = getElementRoot(obj, 0) + if (element) { + this.props.addStack(this.props.sessionId, element, 0, 0) + } + } + break + case 'up': + case 'down': + if (beforeLastNavStack) { + const obj = serialize(this.props.schema, beforeLastNavStack.field) + const element = getElement( + obj, + keyPressed === 'up' ? lastNavStack.y - 1 : lastNavStack.y + 1, + ) + if (element) { + this.props.addStack( + this.props.sessionId, + element, + lastNavStack.x, + keyPressed === 'up' ? lastNavStack.y - 1 : lastNavStack.y + 1, + ) + } + } else { + const obj = serializeRoot(this.props.schema) + const y = lastNavStack ? lastNavStack.y : 0 + const element = getElementRoot( + obj, + keyPressed === 'up' ? y - 1 : y + 1, + ) + if (element) { + this.props.addStack( + this.props.sessionId, + element, + 0, + keyPressed === 'up' ? y - 1 : y + 1, + ) + } + } + break + } + } } -const mapDispatchToProps = dispatch => - bindActionCreators( - { - addStack, - toggleDocs, - changeWidthDocs, - changeKeyMove, - setDocsVisible, - }, - dispatch, - ) +const mapDispatchToProps = (dispatch) => + bindActionCreators( + { + addStack, + toggleDocs, + changeWidthDocs, + changeKeyMove, + setDocsVisible, + }, + dispatch, + ) const mapStateToProps = createStructuredSelector({ - docs: getSessionDocs, - sessionId: getSelectedSessionIdFromRoot, + docs: getSessionDocs, + sessionId: getSelectedSessionIdFromRoot, }) -export default connect( - mapStateToProps, - mapDispatchToProps, - null, - { withRef: true }, +export default connect( + mapStateToProps, + mapDispatchToProps, + null, + { withRef: true }, )(GraphDocs) -interface DocsProps { - open: boolean -} - -const Docs = styled('div')` - background: white; - box-shadow: 0 0 8px rgba(0, 0, 0, 0.15); - position: absolute; - right: -2px; - z-index: ${p => (p.open ? 2000 : 3)}; - height: 100%; - font-family: 'Open Sans', sans-serif; - -webkit-font-smoothing: antialiased; - - .doc-type-description p { - padding: 16px; - font-size: 14px; - } - - .field-name { - color: #1f61a0; - } - .type-name { - color: rgb(245, 160, 0); - } - .arg-name { - color: #1f61a9; - } - - code { - font-family: 'Source Code Pro', monospace; - border-radius: 2px; - padding: 1px 2px; - background: rgba(0, 0, 0, 0.06); - } -` - -const DocsExplorer = styled.div` - background: white; - display: flex; - position: relative; - height: 100%; - letter-spacing: 0.3px; - outline: none; - box-shadow: -1px 1px 6px 0 rgba(0, 0, 0, 0.3); - - &::before { - top: 0; - bottom: 0; - background: ${props => props.theme.colours.green}; - position: absolute; - z-index: 3; - left: 0px; - content: ''; - width: 6px; - } -` - const DocsExplorerContainer = styled.div` - display: flex; - position: relative; - height: 100%; - width: 100%; - overflow-x: auto; - overflow-y: hidden; -` - -const DocsResizer = styled.div` - cursor: col-resize; - height: 100%; - left: -5px; - position: absolute; - top: 0; - bottom: 0; - width: 10px; - z-index: 10; -` - -const ErrorContainer = styled.div` - font-weight: bold; - left: 0; - letter-spacing: 1px; - opacity: 0.5; - position: absolute; - right: 0; - text-align: center; - text-transform: uppercase; - top: 50%; - transform: translate(0, -50%); -` - -const DocsButton = styled.div` - position: absolute; - z-index: 2; - left: -50px; - top: 129px; - padding: 6px 10px; - transform: rotate(-90deg); - border-top-left-radius: 2px; - border-top-right-radius: 2px; - color: ${p => p.theme.colours.white}; - background: ${p => p.theme.colours.green}; - box-shadow: -1px 1px 6px 0 rgba(0, 0, 0, 0.3); - text-transform: uppercase; - font-weight: 600; - font-size: 12px; - line-height: 17px; - letter-spacing: 0.45px; - cursor: pointer; -` - -const DocsGradient = styled.div` - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: 20px; - z-index: 1; - pointer-events: none; - content: ''; - background: linear-gradient( - to right, - rgba(255, 255, 255, 1) 30%, - rgba(255, 255, 255, 0) - ); + display: flex; + position: relative; + height: 100%; + width: 100%; + overflow-x: auto; + overflow-y: hidden; + outline: none !important; ` diff --git a/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx new file mode 100644 index 000000000..df98a7564 --- /dev/null +++ b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx @@ -0,0 +1,55 @@ +import * as React from 'react' +import { styled } from '../../../styled' + +export interface Props { + label: string + activeColor: string + children: any + active?: boolean + onClick?: () => any +} + +export default class SideTab extends React.PureComponent { + render() { + const { label, activeColor, active, onClick } = this.props + return ( + + {label} + + ) + } +} + +export interface TabProps { + active: boolean + activeColor: string +} + +const Tab = styled('div')` + z-index: ${(p) => (p.active ? 10 : 2)}; + padding: 6px 10px; + border-top-left-radius: 2px; + border-top-right-radius: 2px; + color: ${(p) => + p.theme.mode === 'dark' + ? p.theme.colours.white + : p.theme.colours[p.active ? 'white' : 'darkBlue']}; + background: ${(p) => + p.active && p.activeColor + ? p.theme.colours[p.activeColor] + : p.theme.mode === 'dark' + ? '#3D5866' + : '#DBDEE0'}; + box-shadow: -1px 1px 6px 0 rgba(0, 0, 0, 0.3); + text-transform: uppercase; + text-align: center; + font-weight: 600; + font-size: 12px; + line-height: 17px; + letter-spacing: 0.45px; + cursor: pointer; + transform: rotate(-90deg); + transform-origin: bottom left; + width: 60px; + margin-top: 65px; +` diff --git a/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx new file mode 100644 index 000000000..79e458b71 --- /dev/null +++ b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx @@ -0,0 +1,388 @@ +import * as React from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' +import * as keycode from 'keycode' +import { getLeft } from 'graphiql/dist/utility/elementPosition' +import { + addStack, + toggleDocs, + changeWidthDocs, + changeKeyMove, + setDocsVisible, +} from '../../../state/docs/actions' +import { GraphQLSchema } from 'graphql' +import { getSessionDocs } from '../../../state/docs/selectors' +import { getSelectedSessionIdFromRoot } from '../../../state/sessions/selectors' +import { createStructuredSelector } from 'reselect' +import { styled } from '../../../styled' +import SideTab from './SideTab' + +interface StateFromProps { + docs: { + navStack: any[] + docsOpen: boolean + docsWidth: number + keyMove: boolean + activeTabIdx: number + } +} + +interface DispatchFromProps { + addStack: (sessionId: string, field: any, x: number, y: number) => any + toggleDocs: (sessionId: string, activeTabIdx?: number | null) => any + setDocsVisible: (sessionId: string, open: boolean, idx?: number | null) => any + changeWidthDocs: (sessionId: string, width: number) => any + changeKeyMove: (sessionId: string, move: boolean) => any +} + +export interface Props { + schema: GraphQLSchema + sessionId: string + children: Array> +} + +export interface SideTabContentProps { + schema: GraphQLSchema + sessionId: string + setWidth: (props: any) => any +} + +export interface State { + searchValue: string + widthMap: any +} + +class SideTabs extends React.Component< + Props & StateFromProps & DispatchFromProps, + State +> { + ref + public activeContentComponent: any // later React.Component<...> + private refContentContainer: any + private clientX: number = 0 + private clientY: number = 0 + constructor(props) { + super(props) + ;(window as any).d = this + } + + setWidth = (props: any = this.props) => { + if (!this.activeContentComponent) { + return + } + if (!this.props.docs.docsOpen) { + return + } + requestAnimationFrame(() => { + const width = this.activeContentComponent.getWidth(props) + this.props.changeWidthDocs( + props.sessionId, + Math.min(width, window.innerWidth - 86), + ) + }) + } + setActiveContentRef = (ref) => { + if (ref) { + this.activeContentComponent = ref.getWrappedInstance() + } + } + + componentDidReceiveProps(prevProps) { + if (!prevProps.docs.activeTabIdx && this.props.docs.activeTabIdx) { + this.props.setDocsVisible( + this.props.sessionId, + true, + this.props.docs.activeTabIdx, + ) + } + if (prevProps.activeTabIdx && !this.props.docs.activeTabIdx) { + this.props.setDocsVisible(this.props.sessionId, false) + } + return this.setWidth() + } + + componentDidMount() { + if (!this.props.docs.activeTabIdx) { + this.props.setDocsVisible(this.props.sessionId, false) + } + return this.setWidth() + } + + render() { + const { docsOpen, docsWidth, activeTabIdx } = this.props.docs + const docsStyle = { width: docsOpen ? docsWidth : 0 } + const activeTab = + docsOpen && + (React.Children.toArray(this.props.children)[ + activeTabIdx + ] as React.ReactElement) + return ( + + + {React.Children.toArray(this.props.children).map( + (child: React.ReactElement, index) => { + return React.cloneElement(child, { + ...child.props, + key: index, + onClick: this.handleTabClick(index), + active: index === activeTabIdx, + }) + }, + )} + + + + + {activeTab && + React.cloneElement(activeTab.props.children, { + ...activeTab.props, + ref: this.setActiveContentRef, + setWidth: this.setWidth, + })} + + + ) + } + + setRef = (ref) => { + this.ref = ref + } + + public showDocFromType = (type) => { + this.props.setDocsVisible(this.props.sessionId, true, 0) + this.activeContentComponent.showDocFromType(type) + } + + private setContentContainerRef = (ref) => { + this.refContentContainer = ref + } + + private handleTabClick = (idx) => () => { + if (!this.props.docs.docsOpen && this.refContentContainer) { + this.refContentContainer.focus() + } + if (this.props.docs.activeTabIdx === idx) { + this.props.setDocsVisible(this.props.sessionId, false) + return this.setWidth() + } + if (this.props.docs.activeTabIdx !== idx) { + this.props.setDocsVisible( + this.props.sessionId, + false, + this.props.docs.activeTabIdx, + ) + this.props.setDocsVisible(this.props.sessionId, true, idx) + return this.setWidth() + } else { + this.props.setDocsVisible(this.props.sessionId, true, idx) + return this.setWidth() + } + } + + private handleKeyDown = (e) => { + // we don't want to interfere with inputs + if ( + e.target instanceof HTMLInputElement || + e.metaKey || + e.shiftKey || + e.altKey || + e.ctrlKey + ) { + return + } + const keyPressed = keycode(e) + switch (keyPressed) { + case 'esc': + this.props.changeKeyMove(this.props.sessionId, true) + e.preventDefault() + this.props.setDocsVisible(this.props.sessionId, false) + break + } + } + + private handleDocsResizeStart = (downEvent) => { + downEvent.preventDefault() + + const hadWidth = this.props.docs.docsWidth + const offset = downEvent.clientX - getLeft(downEvent.target) + + let onMouseMove: any = (moveEvent) => { + if (moveEvent.buttons === 0) { + return onMouseUp() + } + + const app = this.ref + const cursorPos = moveEvent.clientX - getLeft(app) - offset + const newSize = app.clientWidth - cursorPos + const maxSize = window.innerWidth - 50 + const docsSize = maxSize < newSize ? maxSize : newSize + + if (docsSize < 100) { + this.props.setDocsVisible( + this.props.sessionId, + false, + this.props.docs.activeTabIdx, + ) + } else { + this.props.setDocsVisible( + this.props.sessionId, + true, + this.props.docs.activeTabIdx, + ) + this.props.changeWidthDocs(this.props.sessionId, docsSize) + } + } + + let onMouseUp: any = () => { + if (!this.props.docs.docsOpen) { + this.props.changeWidthDocs(this.props.sessionId, hadWidth) + } + + document.removeEventListener('mousemove', onMouseMove) + document.removeEventListener('mouseup', onMouseUp) + onMouseMove = null + onMouseUp = null + } + + document.addEventListener('mousemove', onMouseMove) + document.addEventListener('mouseup', onMouseUp) + } + private handleMouseMove = (e) => { + this.clientX = e.clientX + this.clientY = e.clientY + if ( + this.props.docs.keyMove && + this.clientX !== e.clientX && + this.clientY !== e.clientY + ) { + this.props.changeKeyMove(this.props.sessionId, false) + } + } +} + +const mapDispatchToProps = (dispatch) => + bindActionCreators( + { + addStack, + toggleDocs, + changeWidthDocs, + changeKeyMove, + setDocsVisible, + }, + dispatch, + ) + +const mapStateToProps = createStructuredSelector({ + docs: getSessionDocs, + sessionId: getSelectedSessionIdFromRoot, +}) + +const ConnectedGraphDocs = connect( + mapStateToProps, + mapDispatchToProps, + null, + { withRef: true }, +)(SideTabs) + +ConnectedGraphDocs.Tab = SideTab + +export default ConnectedGraphDocs + +interface TabsProps { + open: boolean +} + +const Tabs = styled('div')` + background: white; + outline: none; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.15); + position: absolute; + right: -2px; + z-index: ${(p) => (p.open ? 2000 : 3)}; + height: 100%; + font-family: 'Open Sans', sans-serif; + -webkit-font-smoothing: antialiased; + .doc-type-description p { + padding: 16px; + font-size: 14px; + } + .field-name { + color: #1f61a0; + } + .type-name { + color: rgb(245, 160, 0); + } + .arg-name { + color: #1f61a9; + } + code { + font-family: 'Source Code Pro', monospace; + border-radius: 2px; + padding: 1px 2px; + background: rgba(0, 0, 0, 0.06); + } +` + +const TabContentContainer = styled.div` + background: white; + display: flex; + position: relative; + height: 100%; + letter-spacing: 0.3px; + box-shadow: -1px 1px 6px 0 rgba(0, 0, 0, 0.3); + + &::before { + top: 0; + bottom: 0; + background: ${(props) => props.theme.colours[props.color] || '#3D5866'}; + position: absolute; + z-index: 3; + left: 0px; + content: ''; + width: 6px; + } +` + +const TabContentResizer = styled.div` + cursor: col-resize; + outline: none !important; + height: 100%; + left: -5px; + position: absolute; + top: 0; + bottom: 0; + width: 10px; + z-index: 10; +` + +const TabsContainer = styled.div` + position: absolute; + outline: none !important; + z-index: 2; + height: 0; + top: 129px; +` + +const TabsGradient = styled.div` + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 20px; + z-index: 1; + pointer-events: none; + content: ''; + background: ${(p) => + p.index === 0 + ? `linear-gradient( + to right, + rgba(255, 255, 255, 1) 30%, + rgba(255, 255, 255, 0))` + : `transparent`}; +` diff --git a/packages/graphql-playground-react/src/components/Playground/GraphQLEditor.tsx b/packages/graphql-playground-react/src/components/Playground/GraphQLEditor.tsx index 1e5e541a6..04611692d 100644 --- a/packages/graphql-playground-react/src/components/Playground/GraphQLEditor.tsx +++ b/packages/graphql-playground-react/src/components/Playground/GraphQLEditor.tsx @@ -1,61 +1,70 @@ import * as React from 'react' import * as ReactDOM from 'react-dom' import { isNamedType, GraphQLSchema } from 'graphql' +import { List } from 'immutable' + +// Query & Response Components import ExecuteButton from './ExecuteButton' import QueryEditor from './QueryEditor' import EditorWrapper, { Container } from './EditorWrapper' import CodeMirrorSizer from 'graphiql/dist/utility/CodeMirrorSizer' -import { fillLeafs } from 'graphiql/dist/utility/fillLeafs' -import { getLeft, getTop } from 'graphiql/dist/utility/elementPosition' -import { connect } from 'react-redux' - +import TopBar from './TopBar/TopBar' +import { + VariableEditorComponent, + HeadersEditorComponent, +} from './VariableEditor' import Spinner from '../Spinner' import Results from './Results' import ResponseTracing from './ResponseTracing' +import { fillLeafs } from 'graphiql/dist/utility/fillLeafs' +import { getLeft, getTop } from 'graphiql/dist/utility/elementPosition' + +// Explorer Components +import SideTab from './ExplorerTabs/SideTab' +import SideTabs from './ExplorerTabs/SideTabs' +import SDLView from './SchemaExplorer/SDLView' import GraphDocs from './DocExplorer/GraphDocs' + import { styled } from '../../styled/index' -import TopBar from './TopBar/TopBar' -import { - VariableEditorComponent, - HeadersEditorComponent, -} from './VariableEditor' + +// Redux Dependencies +import { connect } from 'react-redux' import { createStructuredSelector } from 'reselect' import { - getQueryRunning, - getResponses, - getSubscriptionActive, - getVariableEditorOpen, - getVariableEditorHeight, - getResponseTracingOpen, - getResponseTracingHeight, - getResponseExtensions, - getCurrentQueryStartTime, - getCurrentQueryEndTime, - getTracingSupported, - getEditorFlex, - getQueryVariablesActive, - getHeaders, - getOperations, - getOperationName, - getHeadersCount, - getSelectedSessionIdFromRoot, + getQueryRunning, + getResponses, + getSubscriptionActive, + getVariableEditorOpen, + getVariableEditorHeight, + getResponseTracingOpen, + getResponseTracingHeight, + getResponseExtensions, + getCurrentQueryStartTime, + getCurrentQueryEndTime, + getTracingSupported, + getEditorFlex, + getQueryVariablesActive, + getHeaders, + getOperations, + getOperationName, + getHeadersCount, + getSelectedSessionIdFromRoot, } from '../../state/sessions/selectors' import { - updateQueryFacts, - stopQuery, - runQueryAtPosition, - closeQueryVariables, - openQueryVariables, - openVariables, - closeVariables, - openTracing, - closeTracing, - toggleTracing, - setEditorFlex, - toggleVariables, - fetchSchema, + updateQueryFacts, + stopQuery, + runQueryAtPosition, + closeQueryVariables, + openQueryVariables, + openVariables, + closeVariables, + openTracing, + closeTracing, + toggleTracing, + setEditorFlex, + toggleVariables, + fetchSchema, } from '../../state/sessions/actions' -import { List } from 'immutable' import { ResponseRecord } from '../../state/sessions/reducers' /** @@ -64,651 +73,676 @@ import { ResponseRecord } from '../../state/sessions/reducers' */ export interface Props { - onRef?: any - shareEnabled?: boolean - schema?: GraphQLSchema + onRef?: any + shareEnabled?: boolean + schema?: GraphQLSchema } export interface ReduxProps { - setStacks: (sessionId: string, stack: any[]) => void - updateQueryFacts: () => void - runQueryAtPosition: (position: number) => void - fetchSchema: () => void - openQueryVariables: () => void - closeQueryVariables: () => void - openVariables: (height: number) => void - closeVariables: (height: number) => void - openTracing: (height: number) => void - closeTracing: (height: number) => void - toggleTracing: () => void - toggleVariables: () => void - setEditorFlex: (flex: number) => void - stopQuery: (sessionId: string) => void - navStack: any[] - // sesion props - queryRunning: boolean - responses: List - subscriptionActive: boolean - variableEditorOpen: boolean - variableEditorHeight: number - currentQueryStartTime?: Date - currentQueryEndTime?: Date - responseTracingOpen: boolean - responseTracingHeight: number - responseExtensions: any - tracingSupported?: boolean - editorFlex: number - headers: string - headersCount: number - queryVariablesActive: boolean - operationName: string - query: string - sessionId: string + setStacks: (sessionId: string, stack: any[]) => void + updateQueryFacts: () => void + runQueryAtPosition: (position: number) => void + fetchSchema: () => void + openQueryVariables: () => void + closeQueryVariables: () => void + openVariables: (height: number) => void + closeVariables: (height: number) => void + openTracing: (height: number) => void + closeTracing: (height: number) => void + toggleTracing: () => void + toggleVariables: () => void + setEditorFlex: (flex: number) => void + stopQuery: (sessionId: string) => void + navStack: any[] + // sesion props + queryRunning: boolean + responses: List + subscriptionActive: boolean + variableEditorOpen: boolean + variableEditorHeight: number + currentQueryStartTime?: Date + currentQueryEndTime?: Date + responseTracingOpen: boolean + responseTracingHeight: number + responseExtensions: any + tracingSupported?: boolean + editorFlex: number + headers: string + headersCount: number + queryVariablesActive: boolean + operationName: string + query: string + sessionId: string } export interface SimpleProps { - children?: any + children?: any } export interface ToolbarButtonProps extends SimpleProps { - onClick: (e: any) => void - title: string - label: string + onClick: (e: any) => void + title: string + label: string } class GraphQLEditor extends React.PureComponent { - public codeMirrorSizer - public queryEditorComponent - public variableEditorComponent - public resultComponent - public editorBarComponent - public docExplorerComponent: any // later React.Component<...> - - private queryResizer: any - private responseResizer: any - private queryVariablesRef - private httpHeadersRef - - componentDidMount() { - // Ensure a form of a schema exists (including `null`) and - // if not, fetch one using an introspection query. - // this.props.fetchSchema() - - // Utility for keeping CodeMirror correctly sized. - this.codeMirrorSizer = new CodeMirrorSizer() - ;(global as any).g = this - } - - componentDidUpdate() { - // If this update caused DOM nodes to have changed sizes, update the - // corresponding CodeMirror instance sizes to match. - // const components = [ - // this.queryEditorComponent, - // this.variableEditorComponent, - // this.resultComponent, - // ] - // this.codeMirrorSizer.updateSizes(components) - if (this.resultComponent && Boolean(this.props.subscriptionActive)) { - this.resultComponent.scrollTop = this.resultComponent.scrollHeight - } - } - - render() { - return ( - - - - - - - - - - Query Variables - - - {'HTTP Headers ' + - (this.props.headersCount && this.props.headersCount > 0 - ? `(${this.props.headersCount})` - : '')} - - - {this.props.queryVariablesActive ? ( - - ) : ( - - )} - - - - - - - {this.props.queryRunning && - this.props.responses.size === 0 && } - - {!this.props.queryRunning && - (!this.props.responses || this.props.responses.size === 0) && ( - Hit the Play Button to get a response here - )} - {this.props.subscriptionActive && ( - Listening … - )} - - - - Tracing - - - - - - - - - - ) - } - - setQueryVariablesRef = ref => { - this.queryVariablesRef = ref - } - - setHttpHeadersRef = ref => { - this.httpHeadersRef = ref - } - - setQueryResizer = ref => { - this.queryResizer = ReactDOM.findDOMNode(ref) - } - - setResponseResizer = ref => { - this.responseResizer = ReactDOM.findDOMNode(ref) - } - - setEditorBarComponent = ref => { - this.editorBarComponent = ref - } - - setQueryEditorComponent = ref => { - this.queryEditorComponent = ref - } - - setVariableEditorComponent = ref => { - this.variableEditorComponent = ref - } - - setResultComponent = ref => { - this.resultComponent = ref - } - - setDocExplorerRef = ref => { - if (ref) { - this.docExplorerComponent = ref.getWrappedInstance() - } - } - - handleClickReference = reference => { - this.docExplorerComponent.showDocFromType(reference.field || reference) - } - - /** - * Inspect the query, automatically filling in selection sets for non-leaf - * fields which do not yet have them. - * - * @public - */ - autoCompleteLeafs() { - const { insertions, result } = fillLeafs( - this.props.schema, - this.props.query, - ) as { - insertions: Array<{ index: number; string: string }> - result: string - } - if (insertions && insertions.length > 0) { - const editor = this.queryEditorComponent.getCodeMirror() - editor.operation(() => { - const cursor = editor.getCursor() - const cursorIndex = editor.indexFromPos(cursor) - editor.setValue(result) - let added = 0 - try { - /* tslint:disable-next-line */ - const markers = insertions.map(({ index, string }) => - editor.markText( - editor.posFromIndex(index + added), - editor.posFromIndex(index + (added += string.length)), - { - className: 'autoInsertedLeaf', - clearOnEnter: true, - title: 'Automatically added leaf fields', - }, - ), - ) - setTimeout(() => markers.forEach(marker => marker.clear()), 7000) - } catch (e) { - // - } - let newCursorIndex = cursorIndex - /* tslint:disable-next-line */ - insertions.forEach(({ index, string }) => { - if (index < cursorIndex && string) { - newCursorIndex += string.length - } - }) - editor.setCursor(editor.posFromIndex(newCursorIndex)) - }) - } - - return result - } - - private runQueryAtCursor = () => { - if (this.props.queryRunning) { - this.props.stopQuery(this.props.sessionId) - return - } - - const editor = this.queryEditorComponent.getCodeMirror() - if (editor.hasFocus()) { - const cursor = editor.getCursor() - const cursorIndex = editor.indexFromPos(cursor) - this.props.runQueryAtPosition(cursorIndex) - } - } - - private handleHintInformationRender = elem => { - elem.addEventListener('click', this.onClickHintInformation) - - let onRemoveFn - elem.addEventListener( - 'DOMNodeRemoved', - (onRemoveFn = () => { - elem.removeEventListener('DOMNodeRemoved', onRemoveFn) - elem.removeEventListener('click', this.onClickHintInformation) - }), - ) - } - - private handleResizeStart = downEvent => { - if (!this.didClickDragBar(downEvent)) { - return - } - - downEvent.preventDefault() - - const offset = downEvent.clientX - getLeft(downEvent.target) - - let onMouseMove: any = moveEvent => { - if (moveEvent.buttons === 0) { - return onMouseUp() - } - - const editorBar = ReactDOM.findDOMNode(this.editorBarComponent) - const leftSize = moveEvent.clientX - getLeft(editorBar) - offset - const rightSize = editorBar.clientWidth - leftSize - this.props.setEditorFlex(leftSize / rightSize) - } - - let onMouseUp: any = () => { - document.removeEventListener('mousemove', onMouseMove) - document.removeEventListener('mouseup', onMouseUp) - onMouseMove = null - onMouseUp = null - } - - document.addEventListener('mousemove', onMouseMove) - document.addEventListener('mouseup', onMouseUp) - } - - private didClickDragBar(event) { - // Only for primary unmodified clicks - return ( - event.target === this.queryResizer || - event.target === this.responseResizer - ) - } - - private handleTracingResizeStart = downEvent => { - downEvent.preventDefault() - - let didMove = false - const hadHeight = this.props.responseTracingHeight - const offset = downEvent.clientY - getTop(downEvent.target) - - let onMouseMove: any = moveEvent => { - if (moveEvent.buttons === 0) { - return onMouseUp() - } - - didMove = true - - const editorBar = ReactDOM.findDOMNode(this.editorBarComponent) - const topSize = moveEvent.clientY - getTop(editorBar) - offset - const bottomSize = editorBar.clientHeight - topSize - if (bottomSize < 60) { - this.props.closeTracing(hadHeight) - } else { - this.props.openTracing(hadHeight) - } - } - - let onMouseUp: any = () => { - if (!didMove) { - this.props.toggleTracing() - } - - document.removeEventListener('mousemove', onMouseMove) - document.removeEventListener('mouseup', onMouseUp) - onMouseMove = null - onMouseUp = null - } - - document.addEventListener('mousemove', onMouseMove) - document.addEventListener('mouseup', onMouseUp) - } - - private handleVariableResizeStart = downEvent => { - downEvent.preventDefault() - - let didMove = false - const wasOpen = this.props.variableEditorOpen - const hadHeight = this.props.variableEditorHeight - const offset = downEvent.clientY - getTop(downEvent.target) - - if ( - wasOpen && - (downEvent.target === this.queryVariablesRef || - downEvent.target === this.httpHeadersRef) - ) { - return - } - - let onMouseMove: any = moveEvent => { - if (moveEvent.buttons === 0) { - return onMouseUp() - } - - didMove = true - - const editorBar = ReactDOM.findDOMNode(this.editorBarComponent) - const topSize = moveEvent.clientY - getTop(editorBar) - offset - const bottomSize = editorBar.clientHeight - topSize - if (bottomSize < 60) { - this.props.closeVariables(hadHeight) - } else { - this.props.openVariables(bottomSize) - } - } - - let onMouseUp: any = () => { - if (!didMove) { - this.props.toggleVariables() - } - - document.removeEventListener('mousemove', onMouseMove) - document.removeEventListener('mouseup', onMouseUp) - onMouseMove = null - onMouseUp = null - } - - document.addEventListener('mousemove', onMouseMove) - document.addEventListener('mouseup', onMouseUp) - } - - private onClickHintInformation = event => { - if (event.target.className === 'typeName') { - const typeName = event.target.innerHTML - const schema = this.props.schema - if (schema) { - // TODO: There is no way as of now to retrieve the NAMED_TYPE of a GraphQLList(Type). - // We're therefore removing any '[' or '!' characters, to properly find its NAMED_TYPE. (eg. [Type!]! => Type) - // This should be removed as soon as there's a safer way to do that. - const namedTypeName = typeName.replace(/[\]\[!]/g, '') - const type = schema.getType(namedTypeName) - - if (isNamedType(type)) { - this.docExplorerComponent.showDocFromType(type) - } - } - } - } + public codeMirrorSizer + public queryEditorComponent + public variableEditorComponent + public resultComponent + public editorBarComponent + public docExplorerComponent: any // later React.Component<...> + public graphExplorerComponent: any + public schemaExplorerComponent: any + private queryResizer: any + private responseResizer: any + private queryVariablesRef + private httpHeadersRef + + componentDidMount() { + // Ensure a form of a schema exists (including `null`) and + // if not, fetch one using an introspection query. + // this.props.fetchSchema() + + // Utility for keeping CodeMirror correctly sized. + this.codeMirrorSizer = new CodeMirrorSizer() + ;(global as any).g = this + } + + componentDidUpdate() { + // If this update caused DOM nodes to have changed sizes, update the + // corresponding CodeMirror instance sizes to match. + // const components = [ + // this.queryEditorComponent, + // this.variableEditorComponent, + // this.resultComponent, + // ] + // this.codeMirrorSizer.updateSizes(components) + if (this.resultComponent && Boolean(this.props.subscriptionActive)) { + this.resultComponent.scrollTop = this.resultComponent.scrollHeight + } + } + + render() { + return ( + + + + + + + + + + Query Variables + + + {'HTTP Headers ' + + (this.props.headersCount && this.props.headersCount > 0 + ? `(${this.props.headersCount})` + : '')} + + + {this.props.queryVariablesActive ? ( + + ) : ( + + )} + + + + + + + {this.props.queryRunning && + this.props.responses.size === 0 && } + + {!this.props.queryRunning && + (!this.props.responses || this.props.responses.size === 0) && ( + Hit the Play Button to get a response here + )} + {this.props.subscriptionActive && ( + Listening … + )} + + + + Tracing + + + + + + + + + + + + + + + + + ) + } + + setQueryVariablesRef = (ref) => { + this.queryVariablesRef = ref + } + + setHttpHeadersRef = (ref) => { + this.httpHeadersRef = ref + } + + setQueryResizer = (ref) => { + this.queryResizer = ReactDOM.findDOMNode(ref) + } + + setResponseResizer = (ref) => { + this.responseResizer = ReactDOM.findDOMNode(ref) + } + + setEditorBarComponent = (ref) => { + this.editorBarComponent = ref + } + + setQueryEditorComponent = (ref) => { + this.queryEditorComponent = ref + } + + setVariableEditorComponent = (ref) => { + this.variableEditorComponent = ref + } + + setResultComponent = (ref) => { + this.resultComponent = ref + } + + setDocExplorerRef = (ref) => { + if (ref) { + this.docExplorerComponent = ref.getWrappedInstance() + } + } + setGraphExplorerRef = (ref) => { + if (ref) { + this.graphExplorerComponent = ref.getWrappedInstance() + } + } + setSchemaExplorerRef = (ref) => { + if (ref) { + this.schemaExplorerComponent = ref.getWrappedInstance() + } + } + + handleClickReference = (reference) => { + this.docExplorerComponent.showDocFromType(reference.field || reference) + } + + /** + * Inspect the query, automatically filling in selection sets for non-leaf + * fields which do not yet have them. + * + * @public + */ + autoCompleteLeafs() { + const { insertions, result } = fillLeafs( + this.props.schema, + this.props.query, + ) as { + insertions: Array<{ index: number; string: string }> + result: string + } + if (insertions && insertions.length > 0) { + const editor = this.queryEditorComponent.getCodeMirror() + editor.operation(() => { + const cursor = editor.getCursor() + const cursorIndex = editor.indexFromPos(cursor) + editor.setValue(result) + let added = 0 + try { + /* tslint:disable-next-line */ + const markers = insertions.map(({ index, string }) => + editor.markText( + editor.posFromIndex(index + added), + editor.posFromIndex(index + (added += string.length)), + { + className: 'autoInsertedLeaf', + clearOnEnter: true, + title: 'Automatically added leaf fields', + }, + ), + ) + setTimeout(() => markers.forEach((marker) => marker.clear()), 7000) + } catch (e) { + // + } + let newCursorIndex = cursorIndex + /* tslint:disable-next-line */ + insertions.forEach(({ index, string }) => { + if (index < cursorIndex && string) { + newCursorIndex += string.length + } + }) + editor.setCursor(editor.posFromIndex(newCursorIndex)) + }) + } + + return result + } + + private runQueryAtCursor = () => { + if (this.props.queryRunning) { + this.props.stopQuery(this.props.sessionId) + return + } + + const editor = this.queryEditorComponent.getCodeMirror() + if (editor.hasFocus()) { + const cursor = editor.getCursor() + const cursorIndex = editor.indexFromPos(cursor) + this.props.runQueryAtPosition(cursorIndex) + } + } + + private handleHintInformationRender = (elem) => { + elem.addEventListener('click', this.onClickHintInformation) + + let onRemoveFn + elem.addEventListener( + 'DOMNodeRemoved', + (onRemoveFn = () => { + elem.removeEventListener('DOMNodeRemoved', onRemoveFn) + elem.removeEventListener('click', this.onClickHintInformation) + }), + ) + } + + private handleResizeStart = (downEvent) => { + if (!this.didClickDragBar(downEvent)) { + return + } + + downEvent.preventDefault() + + const offset = downEvent.clientX - getLeft(downEvent.target) + + let onMouseMove: any = (moveEvent) => { + if (moveEvent.buttons === 0) { + return onMouseUp() + } + + const editorBar = ReactDOM.findDOMNode(this.editorBarComponent) + const leftSize = moveEvent.clientX - getLeft(editorBar) - offset + const rightSize = editorBar.clientWidth - leftSize + this.props.setEditorFlex(leftSize / rightSize) + } + + let onMouseUp: any = () => { + document.removeEventListener('mousemove', onMouseMove) + document.removeEventListener('mouseup', onMouseUp) + onMouseMove = null + onMouseUp = null + } + + document.addEventListener('mousemove', onMouseMove) + document.addEventListener('mouseup', onMouseUp) + } + + private didClickDragBar(event) { + // Only for primary unmodified clicks + return ( + event.target === this.queryResizer || + event.target === this.responseResizer + ) + } + + private handleTracingResizeStart = (downEvent) => { + downEvent.preventDefault() + + let didMove = false + const hadHeight = this.props.responseTracingHeight + const offset = downEvent.clientY - getTop(downEvent.target) + + let onMouseMove: any = (moveEvent) => { + if (moveEvent.buttons === 0) { + return onMouseUp() + } + + didMove = true + + const editorBar = ReactDOM.findDOMNode(this.editorBarComponent) + const topSize = moveEvent.clientY - getTop(editorBar) - offset + const bottomSize = editorBar.clientHeight - topSize + if (bottomSize < 60) { + this.props.closeTracing(hadHeight) + } else { + this.props.openTracing(hadHeight) + } + } + + let onMouseUp: any = () => { + if (!didMove) { + this.props.toggleTracing() + } + + document.removeEventListener('mousemove', onMouseMove) + document.removeEventListener('mouseup', onMouseUp) + onMouseMove = null + onMouseUp = null + } + + document.addEventListener('mousemove', onMouseMove) + document.addEventListener('mouseup', onMouseUp) + } + + private handleVariableResizeStart = (downEvent) => { + downEvent.preventDefault() + + let didMove = false + const wasOpen = this.props.variableEditorOpen + const hadHeight = this.props.variableEditorHeight + const offset = downEvent.clientY - getTop(downEvent.target) + + if ( + wasOpen && + (downEvent.target === this.queryVariablesRef || + downEvent.target === this.httpHeadersRef) + ) { + return + } + + let onMouseMove: any = (moveEvent) => { + if (moveEvent.buttons === 0) { + return onMouseUp() + } + + didMove = true + + const editorBar = ReactDOM.findDOMNode(this.editorBarComponent) + const topSize = moveEvent.clientY - getTop(editorBar) - offset + const bottomSize = editorBar.clientHeight - topSize + if (bottomSize < 60) { + this.props.closeVariables(hadHeight) + } else { + this.props.openVariables(bottomSize) + } + } + + let onMouseUp: any = () => { + if (!didMove) { + this.props.toggleVariables() + } + + document.removeEventListener('mousemove', onMouseMove) + document.removeEventListener('mouseup', onMouseUp) + onMouseMove = null + onMouseUp = null + } + + document.addEventListener('mousemove', onMouseMove) + document.addEventListener('mouseup', onMouseUp) + } + + private onClickHintInformation = (event) => { + if (event.target.className === 'typeName') { + const typeName = event.target.innerHTML + const schema = this.props.schema + if (schema) { + // TODO: There is no way as of now to retrieve the NAMED_TYPE of a GraphQLList(Type). + // We're therefore removing any '[' or '!' characters, to properly find its NAMED_TYPE. (eg. [Type!]! => Type) + // This should be removed as soon as there's a safer way to do that. + const namedTypeName = typeName.replace(/[\]\[!]/g, '') + const type = schema.getType(namedTypeName) + + if (isNamedType(type)) { + this.docExplorerComponent.showDocFromType(type) + } + } + } + } } const mapStateToProps = createStructuredSelector({ - queryRunning: getQueryRunning, - responses: getResponses, - subscriptionActive: getSubscriptionActive, - variableEditorOpen: getVariableEditorOpen, - variableEditorHeight: getVariableEditorHeight, - responseTracingOpen: getResponseTracingOpen, - responseTracingHeight: getResponseTracingHeight, - responseExtensions: getResponseExtensions, - currentQueryStartTime: getCurrentQueryStartTime, - currentQueryEndTime: getCurrentQueryEndTime, - tracingSupported: getTracingSupported, - editorFlex: getEditorFlex, - queryVariablesActive: getQueryVariablesActive, - headers: getHeaders, - operations: getOperations, - operationName: getOperationName, - headersCount: getHeadersCount, - sessionId: getSelectedSessionIdFromRoot, + queryRunning: getQueryRunning, + responses: getResponses, + subscriptionActive: getSubscriptionActive, + variableEditorOpen: getVariableEditorOpen, + variableEditorHeight: getVariableEditorHeight, + responseTracingOpen: getResponseTracingOpen, + responseTracingHeight: getResponseTracingHeight, + responseExtensions: getResponseExtensions, + currentQueryStartTime: getCurrentQueryStartTime, + currentQueryEndTime: getCurrentQueryEndTime, + tracingSupported: getTracingSupported, + editorFlex: getEditorFlex, + queryVariablesActive: getQueryVariablesActive, + headers: getHeaders, + operations: getOperations, + operationName: getOperationName, + headersCount: getHeadersCount, + sessionId: getSelectedSessionIdFromRoot, }) export default // TODO fix redux types connect( - mapStateToProps, - { - updateQueryFacts, - stopQuery, - runQueryAtPosition, - openQueryVariables, - closeQueryVariables, - openVariables, - closeVariables, - openTracing, - closeTracing, - toggleTracing, - setEditorFlex, - toggleVariables, - fetchSchema, - }, - null, - { - withRef: true, - }, + mapStateToProps, + { + updateQueryFacts, + stopQuery, + runQueryAtPosition, + openQueryVariables, + closeQueryVariables, + openVariables, + closeVariables, + openTracing, + closeTracing, + toggleTracing, + setEditorFlex, + toggleVariables, + fetchSchema, + }, + null, + { + withRef: true, + }, )(GraphQLEditor) const EditorBar = styled.div` - display: flex; - flex-direction: row; - flex: 1; + display: flex; + flex-direction: row; + flex: 1; ` const ResultWrap = styled.div` - display: flex; - flex-direction: column; - flex: 1; - position: relative; - border-left: none; - background: ${p => p.theme.editorColours.resultBackground}; + display: flex; + flex-direction: column; + flex: 1; + position: relative; + border-left: none; + background: ${(p) => p.theme.editorColours.resultBackground}; ` const DragBar = styled.div` - width: 15px; - position: absolute; - top: 0; - bottom: 0; - cursor: col-resize; + width: 15px; + position: absolute; + top: 0; + bottom: 0; + cursor: col-resize; ` const QueryDragBar = styled(DragBar)` - right: 0px; + right: 0px; ` const ResultDragBar = styled(DragBar)` - left: 0px; - z-index: 1; + left: 0px; + z-index: 1; ` interface DrawerProps { - isOpen: boolean - height: number + isOpen: boolean + height: number } const BottomDrawer = styled('div')` - display: flex; - background: #0b1924; - flex-direction: column; - position: relative; - height: ${props => (props.isOpen ? `${props.height}px` : '43px')}; + display: flex; + background: #0b1924; + flex-direction: column; + position: relative; + height: ${(props) => (props.isOpen ? `${props.height}px` : '43px')}; ` interface TitleProps { - isOpen: boolean - onMouseDown?: any - onClick?: any - ref?: any + isOpen: boolean + onMouseDown?: any + onClick?: any + ref?: any } const BottomDrawerTitle = styled.div` - background: #0b1924; - text-transform: uppercase; - font-weight: 600; - letter-spacing: 0.53px; - line-height: 14px; - font-size: 14px; - padding: 14px 14px 15px 21px; - user-select: none; + background: #0b1924; + text-transform: uppercase; + font-weight: 600; + letter-spacing: 0.53px; + line-height: 14px; + font-size: 14px; + padding: 14px 14px 15px 21px; + user-select: none; ` const VariableEditor = styled(BottomDrawer)` - .CodeMirror { - padding-left: 4px; - width: calc(100% - 4px); - background: ${p => p.theme.editorColours.leftDrawerBackground}; - } - .CodeMirror-lines { - padding: 10px 0 20px 0; - } - .CodeMirror-linenumbers { - background: ${p => p.theme.editorColours.leftDrawerBackground}; - } + .CodeMirror { + padding-left: 4px; + width: calc(100% - 4px); + background: ${(p) => p.theme.editorColours.leftDrawerBackground}; + } + .CodeMirror-lines { + padding: 10px 0 20px 0; + } + .CodeMirror-linenumbers { + background: ${(p) => p.theme.editorColours.leftDrawerBackground}; + } ` const VariableEditorTitle = styled(({ isOpen, ...rest }) => ( - + ))` - cursor: ${p => (p.isOpen ? 'row-resize' : 'n-resize')}; - background: ${p => p.theme.editorColours.leftDrawerBackground}; + cursor: ${(p) => (p.isOpen ? 'row-resize' : 'n-resize')}; + background: ${(p) => p.theme.editorColours.leftDrawerBackground}; ` const VariableEditorSubtitle = styled('span')` - margin-right: 10px; - cursor: pointer; - color: ${p => - p.isOpen - ? p.theme.editorColours.drawerText - : p.theme.editorColours.drawerTextInactive}; - &:last-child { - margin-right: 0; - } + margin-right: 10px; + cursor: pointer; + color: ${(p) => + p.isOpen + ? p.theme.editorColours.drawerText + : p.theme.editorColours.drawerTextInactive}; + &:last-child { + margin-right: 0; + } ` const ResponseTracking = styled(BottomDrawer)` - background: ${p => p.theme.editorColours.rightDrawerBackground}; + background: ${(p) => p.theme.editorColours.rightDrawerBackground}; ` const ResponseTrackingTitle = styled(({ isOpen, ...rest }) => ( - + ))` - text-align: right; - background: ${p => p.theme.editorColours.rightDrawerBackground}; - cursor: ${props => (props.isOpen ? 's-resize' : 'n-resize')}; - color: ${p => p.theme.editorColours.drawerTextInactive}; + text-align: right; + background: ${(p) => p.theme.editorColours.rightDrawerBackground}; + cursor: ${(props) => (props.isOpen ? 's-resize' : 'n-resize')}; + color: ${(p) => p.theme.editorColours.drawerTextInactive}; ` interface QueryProps { - flex: number + flex: number } const QueryWrap = styled('div')` - position: relative; - display: flex; - flex-direction: column; - flex: ${props => props.flex} 1 0%; - border-top: 8px solid ${props => props.theme.editorColours.resultBackground}; + position: relative; + display: flex; + flex-direction: column; + flex: ${(props) => props.flex} 1 0%; + border-top: 8px solid ${(props) => props.theme.editorColours.resultBackground}; ` const Intro = styled.div` - width: 235px; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - color: ${p => p.theme.colours.textInactive}; - font-size: ${p => p.theme.sizes.small16}; - font-family: 'Source Code Pro', 'Consolas', 'Inconsolata', 'Droid Sans Mono', - 'Monaco', monospace; - text-align: center; - letter-spacing: 0.6px; + width: 235px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: ${(p) => p.theme.colours.textInactive}; + font-size: ${(p) => p.theme.sizes.small16}; + font-family: 'Source Code Pro', 'Consolas', 'Inconsolata', 'Droid Sans Mono', + 'Monaco', monospace; + text-align: center; + letter-spacing: 0.6px; ` const Listening = styled.div` - position: absolute; - bottom: 0; - color: ${p => p.theme.editorColours.text}; - background: ${p => p.theme.editorColours.resultBackground}; - font-size: ${p => p.theme.sizes.small16}; - font-family: ${p => p.theme.settings['editor.fontFamily']}; - letter-spacing: 0.6px; - padding-left: 24px; - padding-bottom: 60px; + position: absolute; + bottom: 0; + color: ${(p) => p.theme.editorColours.text}; + background: ${(p) => p.theme.editorColours.resultBackground}; + font-size: ${(p) => p.theme.sizes.small16}; + font-family: ${(p) => p.theme.settings['editor.fontFamily']}; + letter-spacing: 0.6px; + padding-left: 24px; + padding-bottom: 60px; ` diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLEditor.tsx b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLEditor.tsx new file mode 100644 index 000000000..600fd98da --- /dev/null +++ b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLEditor.tsx @@ -0,0 +1,111 @@ +import * as React from 'react' +import { GraphQLSchema } from 'graphql' +import EditorWrapper from '../EditorWrapper' +import { styled } from '../../../styled' +import { getSDL } from '../util/createSDL' + +export interface Props { + schema?: GraphQLSchema | null + getRef?: (ref: SDLEditor) => void + width?: number + sessionId?: string +} + +class SDLEditor extends React.PureComponent { + cachedValue: string + private editor: any + private node: any + + constructor(props) { + super(props) + + // 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 || '' + if (this.props.getRef) { + this.props.getRef(this) + } + } + + componentDidMount() { + // Lazily require to ensure requiring GraphiQL outside of a Browser context + // does not produce an error. + const CodeMirror = require('codemirror') + require('codemirror/addon/fold/brace-fold') + require('codemirror/addon/comment/comment') + require('codemirror-graphql/mode') + + const gutters: any[] = [] + gutters.push('CodeMirror-linenumbers') + + this.editor = CodeMirror(this.node, { + autofocus: false, + value: getSDL(this.props.schema) || '', + lineNumbers: false, + showCursorWhenSelecting: false, + tabSize: 1, + mode: 'graphql', + theme: 'graphiql', + lineWrapping: true, + keyMap: 'sublime', + readOnly: true, + gutters, + }) + ;(global as any).editor = this.editor + this.editor.refresh() + } + + componentDidUpdate(prevProps: Props) { + const CodeMirror = require('codemirror') + if (this.props.schema !== prevProps.schema) { + this.cachedValue = getSDL(this.props.schema) || '' + this.editor.setValue(getSDL(this.props.schema)) + CodeMirror.signal(this.editor, 'change', this.editor) + } + if (this.props.width !== prevProps.width) { + this.editor.refresh() + } + } + + componentWillReceiveProps(nextProps) { + if (this.props.sessionId !== nextProps.sessionId) { + this.editor.scrollTo(0, 0) + } + } + + componentWillUnmount() { + this.editor = null + } + + render() { + return ( + + + + ) + } + + setRef = (ref) => { + this.node = ref + } + + getCodeMirror() { + return this.editor + } + getClientHeight() { + return this.node && this.node.clientHeight + } +} + +export default SDLEditor + +const Editor = styled.div` + flex: 1; + height: auto; + overflow-x: hidden; + overflow-y: scroll; + .CodeMirror { + background: ${(p) => p.theme.editorColours.editorBackground}; + } +` diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLDocType.tsx b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLDocType.tsx new file mode 100644 index 000000000..37bf190af --- /dev/null +++ b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLDocType.tsx @@ -0,0 +1,86 @@ +import * as React from 'react' +import SDLType from './SDLType' +import { styled } from '../../../../styled' + +export interface DocTypeSchemaProps { + type: any + fields: any[] + interfaces: any[] +} + +export default ({ type, fields, interfaces }: DocTypeSchemaProps) => { + const nonDeprecatedFields = fields.filter((data) => !data.isDeprecated) + const deprecatedFields = fields.filter((data) => data.isDeprecated) + return ( + + + {type.instanceOf}{' '} + {type.name}{' '} + {interfaces.length === 0 && {`{`}} + + {interfaces.map((data, index) => ( + implements} + afterNode={ + index === interfaces.length - 1 ? {'{'} : null + } + /> + ))} + {nonDeprecatedFields.map((data) => ( + + ))} + {deprecatedFields.length > 0 &&
} + {deprecatedFields.map((data, index) => ( +
+ + # Deprecated: {data.deprecationReason} + + +
+ ))} + + {'}'} + +
+ ) +} + +const DocTypeSchema = styled.div` + font-size: 14px; + flex: 1; + .doc-category-item { + padding-left: 32px; + } +` + +const DocTypeLine = styled.div` + padding: 6px 16px; + white-space: nowrap; +` + +const DocsTypeName = styled.span` + color: #f25c54; +` + +const DocsTypeInferface = styled(SDLType)` + padding-left: 16px; + .field-name { + color: rgb(245, 160, 0); + } + .type-name { + color: #f25c54; + } +` + +const DocsValueComment = styled.span` + color: ${(p) => p.theme.colours.black50}; + padding-right: 16px; + padding-left: 32px; +` + +const Brace = styled.span` + font-weight: 600; + color: ${(p) => p.theme.colours.darkBlue50}; +` diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLFieldDoc.tsx b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLFieldDoc.tsx new file mode 100644 index 000000000..2eb81391d --- /dev/null +++ b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLFieldDoc.tsx @@ -0,0 +1,54 @@ +import * as React from 'react' +import MarkdownContent from 'graphiql/dist/components/DocExplorer/MarkdownContent' +import SDLDocType from './SDLDocType' +import ScalarTypeSchema from '../../DocExplorer/DocsTypes/ScalarType' +import EnumTypeSchema from '../../DocExplorer/DocsTypes/EnumTypeSchema' +import SDLUnionType from './SDLUnionType' +import { CategoryTitle } from '../../DocExplorer/DocsStyles' +import { styled } from '../../../../styled' + +export interface Props { + schema: any + type: any +} + +export interface State { + showDeprecated: boolean +} + +export default class FieldDoc extends React.Component { + state = { showDeprecated: false } + + render() { + const { type, schema } = this.props + return ( +
+ {`${type.name} details`} + {type.description && + type.description.length > 0 && ( + + )} + {type.instanceOf === 'scalar' && } + {type.instanceOf === 'enum' && ( + + )} + {type.instanceOf === 'union' && ( + + )} + {type.fields.length > 0 && ( + + )} +
+ ) + } +} + +const DocsDescription = styled(MarkdownContent)` + font-size: 14px; + padding: 0 16px 20px 16px; + color: rgba(0, 0, 0, 0.5); +` diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLStyles.tsx b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLStyles.tsx new file mode 100644 index 000000000..3cfbf2c4b --- /dev/null +++ b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLStyles.tsx @@ -0,0 +1,178 @@ +import * as React from 'react' +import { styled } from '../../../../styled' +import { Button } from '../../TopBar/TopBar' +import { GraphQLSchema } from 'graphql' +import { downloadSchema } from '../../util/createSDL' +import { columnWidth } from '../../../../constants' + +interface SDLHeaderProps { + schema: GraphQLSchema +} + +interface State { + open: boolean +} + +class SDLHeader extends React.Component { + constructor(props) { + super(props) + this.state = { + open: false, + } + } + + showOptions = () => { + this.setState({ + open: !this.state.open, + }) + } + + printSDL = () => { + return downloadSchema(this.props.schema, 'sdl') + } + + printIntrospection = () => { + return downloadSchema(this.props.schema, 'json') + } + + render() { + const { open } = this.state + return ( + + Schema + + + Download + + {open && ( + + + + + )} + + + ) + } +} + +export { SDLHeader } +export const SchemaExplorerContainer = styled.div` + position: relative; + height: 100%; + width: 100%; + display: flex; + flex-direction: column; + flex-wrap: wrap; + align-items: stretch; + padding: 8px; + background: ${(p) => styleHelper(p).secBackground}; + font-family: ${(p) => p.theme.settings['editor.fontFamily']}; + font-size: ${(p) => `${p.theme.settings['editor.fontSize']}px`}; + outline: none !important; +` + +const SchemaHeader = styled.div` + display: flex; + height: 64px; + width: 100%; + align-items: center; + justify-content: space-between; +` + +const Box = styled.div` + position: absolute; + top: 16px; + right: 2em; + width: 108px; + display: flex; + flex-wrap: wrap; + flex-direction: column; +` + +const Title = styled.div` + flex: 1; + color: ${(p) => styleHelper(p).title}; + cursor: default; + font-size: 14px; + font-weight: 600; + text-transform: uppercase !important; + font-family: 'Open Sans', sans-serif !important; + letter-spacing: 1px; + user-select: none; + padding: 16px; +` + +const Download = styled(Button)` + flex: 1; + color: ${(p) => styleHelper(p).download['text']}; + background: ${(p) => styleHelper(p).download['button']}; + padding: 12px 9px 12px 9px; + border-radius: 0px; + &:hover { + color: ${(p) => styleHelper(p).buttonTextHover}; + background-color: ${(p) => styleHelper(p).buttonHover}; + } +` + +const Option = styled(Download)` + text-align: left; + width: 100%; + margin-left: 0px; + border-radius: 0px; + z-index: 2000; + background: ${(p) => styleHelper(p).button}; +` + +export interface SDLColumnProps { + children: any + width?: number +} + +const SDLColumn = ({ children, width = columnWidth }: SDLColumnProps) => { + return {children} +} + +export { SDLColumn } + +const Column = styled('div')` + display: flex; + flex: 1 0 auto; + flex-flow: column; + padding-bottom: 20px; + border-right: 1px solid ${(p) => p.theme.colours.black10}; + overflow: hidden; +` + +const styleHelper = (p) => { + if (p.theme.mode === 'dark') { + return { + secBackground: p.theme.editorColours.navigationBar, + title: 'white', + download: { + text: p.open ? p.theme.colours.white30 : 'white', + button: p.open ? '#2e5482' : p.theme.colours.blue, + }, + buttonText: 'white', + button: p.alternate ? '#386bac' : p.theme.colours.blue, + buttonHover: '#2e5482', + buttonTextHover: 'white', + } + } + return { + secBackground: 'white', + download: { + text: p.open ? 'rgba(61, 88, 102, 0.5)' : '#3D5866', + button: '#f6f6f6', + }, + title: 'rgba(0, 0, 0, 0.3)', + buttonText: '#3d5866', + button: p.alternate ? '#EDEDED' : '#f6f6f6', + buttonHover: '#f6f6f6', + buttonTextHover: 'rgba(61, 88, 102, 0.5)', + } +} diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLType.tsx b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLType.tsx new file mode 100644 index 000000000..5dfabc7d5 --- /dev/null +++ b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLType.tsx @@ -0,0 +1,130 @@ +import * as React from 'react' +import { GraphQLList, GraphQLNonNull, isType } from 'graphql' +import ArgumentInline from '../../DocExplorer/ArgumentInline' +import { styled } from '../../../../styled' + +export interface Props { + type: any + className?: string + beforeNode?: JSX.Element | null | false + afterNode?: JSX.Element | null | false + showParentName?: boolean + collapsable?: boolean +} + +export default class SDLType extends React.Component { + render() { + const { + type, + className, + beforeNode, + afterNode, + showParentName, + } = this.props + const isGraphqlType = isType(type) + + const fieldName = + showParentName && type.parent ? ( + + {type.parent.name}.{type.name} + + ) : ( + type.name + ) + + return ( + + {beforeNode} + {beforeNode && ' '} + {!isGraphqlType && ( + + {fieldName} + {type.args && + type.args.length > 0 && [ + '(', + + {type.args.map((arg) => ( + + ))} + , + ')', + ]} + {': '} + + )} + {renderType(type.type || type)} + {type.defaultValue !== undefined ? ( + + {' '} + = {`${type.defaultValue}`} + + ) : ( + undefined + )} + {afterNode && ' '} + {afterNode} + + ) + } +} + +function renderType(type) { + if (type instanceof GraphQLNonNull) { + return ( + + {renderType(type.ofType)} + {'!'} + + ) + } + if (type instanceof GraphQLList) { + return ( + + {'['} + {renderType(type.ofType)} + {']'} + + ) + } + return {type.name} +} + +interface DocsCategoryItemProps { + clickable?: boolean + active?: boolean +} + +const DocsCategoryItem = styled('div')` + position: relative; + padding: 6px 16px; + overflow: auto; + font-size: 14px; + transition: 0.1s background-color; + background: ${(p) => + p.active ? p.theme.colours.black07 : p.theme.colours.white}; + + cursor: ${(p) => (p.clickable ? 'pointer' : 'select')}; + + // &:hover { + // color: ${(p) => p.theme.colours.white}; + // background: #2a7ed3; + // .field-name, + // .type-name, + // .arg-name, + // span { + // color: ${(p) => p.theme.colours.white} !important; + // } + // } + b { + font-weight: 600; + } +` + +const DefaultValue = styled.span` + color: ${(p) => p.theme.colours.black30}; + span { + color: #1f61a9; + } +` diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLUnionType.tsx b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLUnionType.tsx new file mode 100644 index 000000000..e4dba4993 --- /dev/null +++ b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLUnionType.tsx @@ -0,0 +1,22 @@ +import SDLType from './SDLType' +import * as React from 'react' +import { DocType } from '../../DocExplorer/DocsTypes/DocType' + +export interface EnumTypeSchemaProps { + schema: any + type: any +} + +const UnionTypeSchema = ({ schema, type }: EnumTypeSchemaProps) => { + const types = schema.getPossibleTypes(type) + return ( + + union{' '} + {type.name} + {' = '} + {types.map((value) => )} + + ) +} + +export default UnionTypeSchema diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLView.tsx b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLView.tsx new file mode 100644 index 000000000..2f290b9f9 --- /dev/null +++ b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLView.tsx @@ -0,0 +1,122 @@ +import * as React from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' +import { + toggleDocs, + changeWidthDocs, + setDocsVisible, +} from '../../../state/docs/actions' +import Spinner from '../../Spinner' +import { columnWidth } from '../../../constants' +import { SideTabContentProps } from '../ExplorerTabs/SideTabs' +import { getSelectedSessionIdFromRoot } from '../../../state/sessions/selectors' +import { getSessionDocs } from '../../../state/docs/selectors' +import { createStructuredSelector } from 'reselect' +import { ErrorContainer } from '../DocExplorer/ErrorContainer' +import { + SDLHeader, + SchemaExplorerContainer, + SDLColumn, +} from './SDLTypes/SDLStyles' +import SDLEditor from './SDLEditor' + +interface StateFromProps { + docs: { + navStack: any[] + docsOpen: boolean + docsWidth: number + keyMove: boolean + } +} + +interface DispatchFromProps { + toggleDocs: (sessionId: string) => any + setDocsVisible: (sessionId: string, open: boolean) => any + changeWidthDocs: (sessionId: string, width: number) => any +} + +class SDLView extends React.Component< + SideTabContentProps & StateFromProps & DispatchFromProps +> { + ref + constructor(props) { + super(props) + ;(window as any).d = this + } + componentWillReceiveProps(nextProps: SideTabContentProps & StateFromProps) { + // If user use default column size % columnWidth + // Make the column follow the clicks + if (!this.props.schema && nextProps.schema) { + this.setWidth(nextProps) + } + } + + setWidth(props: any = this.props) { + this.props.setWidth(props) + } + + getWidth(props: any = this.props) { + const rootWidth = props.docs.docsWidth || columnWidth + return rootWidth + } + componentDidMount() { + this.setWidth() + } + + render() { + const { schema } = this.props + let emptySchema + if (schema === undefined) { + // Schema is undefined when it is being loaded via introspection. + emptySchema = + } else if (schema === null) { + // Schema is null when it explicitly does not exist, typically due to + // an error during introspection. + emptySchema = {'No Schema Available'} + } + // let types + // if (schema instanceof GraphQLSchema) { + // types = sdlArray(schema) + // } + return ( + + {emptySchema ? ( + {emptySchema} + ) : ( + + + + + )} + + ) + } + setRef = (ref) => { + this.ref = ref + } +} + +const mapDispatchToProps = (dispatch) => + bindActionCreators( + { + toggleDocs, + changeWidthDocs, + setDocsVisible, + }, + dispatch, + ) + +const mapStateToProps = createStructuredSelector({ + docs: getSessionDocs, + sessionId: getSelectedSessionIdFromRoot, +}) + +export default connect( + mapStateToProps, + mapDispatchToProps, + null, + { withRef: true }, +)(SDLView) diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts b/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts index 54d998c23..938fcd6d0 100644 --- a/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts +++ b/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts @@ -71,7 +71,11 @@ export class SchemaFetcher { return new Promise((resolve, reject) => { execute(link, operation).subscribe({ next: schemaData => { - if (schemaData && ((schemaData.errors && schemaData.errors.length > 0) || !schemaData.data)) { + if ( + schemaData && + ((schemaData.errors && schemaData.errors.length > 0) || + !schemaData.data) + ) { throw new Error(JSON.stringify(schemaData, null, 2)) } diff --git a/packages/graphql-playground-react/src/components/Playground/TopBar/TopBar.tsx b/packages/graphql-playground-react/src/components/Playground/TopBar/TopBar.tsx index fc28d14a2..e11479da7 100644 --- a/packages/graphql-playground-react/src/components/Playground/TopBar/TopBar.tsx +++ b/packages/graphql-playground-react/src/components/Playground/TopBar/TopBar.tsx @@ -6,230 +6,230 @@ import Share from '../../Share' import ReloadIcon from './ReloadIcon' import { createStructuredSelector } from 'reselect' import { - getEndpoint, - getSelectedSession, - getIsReloadingSchema, - getEndpointUnreachable, + getEndpoint, + getSelectedSession, + getIsReloadingSchema, + getEndpointUnreachable, } from '../../../state/sessions/selectors' import { connect } from 'react-redux' import { getFixedEndpoint } from '../../../state/general/selectors' import * as PropTypes from 'prop-types' import { - editEndpoint, - prettifyQuery, - refetchSchema, + editEndpoint, + prettifyQuery, + refetchSchema, } from '../../../state/sessions/actions' import { share } from '../../../state/sharing/actions' import { openHistory } from '../../../state/general/actions' export interface Props { - endpoint: string - shareEnabled?: boolean - fixedEndpoint?: boolean - isReloadingSchema: boolean - endpointUnreachable: boolean - - editEndpoint: (value: string) => void - prettifyQuery: () => void - openHistory: () => void - share: () => void - refetchSchema: () => void + endpoint: string + shareEnabled?: boolean + fixedEndpoint?: boolean + isReloadingSchema: boolean + endpointUnreachable: boolean + + editEndpoint: (value: string) => void + prettifyQuery: () => void + openHistory: () => void + share: () => void + refetchSchema: () => void } class TopBar extends React.Component { - static contextTypes = { - store: PropTypes.shape({ - subscribe: PropTypes.func.isRequired, - dispatch: PropTypes.func.isRequired, - getState: PropTypes.func.isRequired, - }), - } - render() { - const { endpointUnreachable } = this.props - return ( - - - - - - {endpointUnreachable ? ( - - Server cannot be reached - - - ) : ( - - )} - - - {this.props.shareEnabled && ( - - - - )} - - ) - } - copyCurlToClipboard = () => { - const curl = this.getCurl() - copy(curl) - } - onChange = e => { - this.props.editEndpoint(e.target.value) - } - onKeyDown = e => { - if (e.keyCode === 13) { - this.props.refetchSchema() - } - } - openHistory = () => { - this.props.openHistory() - } - getCurl = () => { - // no need to rerender the whole time. only on-demand the store is fetched - const session = getSelectedSession(this.context.store.getState()) - let variables - try { - variables = JSON.parse(session.variables) - } catch (e) { - // - } - const data = JSON.stringify({ - query: session.query, - variables, - operationName: session.operationName, - }) - let sessionHeaders - try { - sessionHeaders = JSON.parse(session.headers!) - } catch (e) { - // - } - const headers = { - 'Accept-Encoding': 'gzip, deflate, br', - 'Content-Type': 'application/json', - Accept: 'application/json', - Connection: 'keep-alive', - DNT: '1', - Origin: location.origin || session.endpoint, - ...sessionHeaders, - } - const headersString = Object.keys(headers) - .map(key => { - const value = headers[key] - return `-H '${key}: ${value}'` - }) - .join(' ') - return `curl '${ - session.endpoint - }' ${headersString} --data-binary '${data}' --compressed` - } + static contextTypes = { + store: PropTypes.shape({ + subscribe: PropTypes.func.isRequired, + dispatch: PropTypes.func.isRequired, + getState: PropTypes.func.isRequired, + }), + } + render() { + const { endpointUnreachable } = this.props + return ( + + + + + + {endpointUnreachable ? ( + + Server cannot be reached + + + ) : ( + + )} + + + {this.props.shareEnabled && ( + + + + )} + + ) + } + copyCurlToClipboard = () => { + const curl = this.getCurl() + copy(curl) + } + onChange = (e) => { + this.props.editEndpoint(e.target.value) + } + onKeyDown = (e) => { + if (e.keyCode === 13) { + this.props.refetchSchema() + } + } + openHistory = () => { + this.props.openHistory() + } + getCurl = () => { + // no need to rerender the whole time. only on-demand the store is fetched + const session = getSelectedSession(this.context.store.getState()) + let variables + try { + variables = JSON.parse(session.variables) + } catch (e) { + // + } + const data = JSON.stringify({ + query: session.query, + variables, + operationName: session.operationName, + }) + let sessionHeaders + try { + sessionHeaders = JSON.parse(session.headers!) + } catch (e) { + // + } + const headers = { + 'Accept-Encoding': 'gzip, deflate, br', + 'Content-Type': 'application/json', + Accept: 'application/json', + Connection: 'keep-alive', + DNT: '1', + Origin: location.origin || session.endpoint, + ...sessionHeaders, + } + const headersString = Object.keys(headers) + .map((key) => { + const value = headers[key] + return `-H '${key}: ${value}'` + }) + .join(' ') + return `curl '${ + session.endpoint + }' ${headersString} --data-binary '${data}' --compressed` + } } const mapStateToProps = createStructuredSelector({ - endpoint: getEndpoint, - fixedEndpoint: getFixedEndpoint, - isReloadingSchema: getIsReloadingSchema, - endpointUnreachable: getEndpointUnreachable, + endpoint: getEndpoint, + fixedEndpoint: getFixedEndpoint, + isReloadingSchema: getIsReloadingSchema, + endpointUnreachable: getEndpointUnreachable, }) export default connect( - mapStateToProps, - { - editEndpoint, - prettifyQuery, - openHistory, - share, - refetchSchema, - }, + mapStateToProps, + { + editEndpoint, + prettifyQuery, + openHistory, + share, + refetchSchema, + }, )(TopBar) export const Button = styled.button` - text-transform: uppercase; - font-weight: 600; - color: ${p => p.theme.editorColours.buttonText}; - background: ${p => p.theme.editorColours.button}; - border-radius: 2px; - flex: 0 0 auto; - letter-spacing: 0.53px; - font-size: 14px; - padding: 6px 9px 7px 10px; - margin-left: 6px; - - cursor: pointer; - transition: 0.1s linear background-color; - &:first-child { - margin-left: 0; - } - &:hover { - background-color: ${p => p.theme.editorColours.buttonHover}; - } + text-transform: uppercase; + font-weight: 600; + color: ${(p) => p.theme.editorColours.buttonText}; + background: ${(p) => p.theme.editorColours.button}; + border-radius: 2px; + flex: 0 0 auto; + letter-spacing: 0.53px; + font-size: 14px; + padding: 6px 9px 7px 10px; + margin-left: 6px; + + cursor: pointer; + transition: 0.1s linear background-color; + &:first-child { + margin-left: 0; + } + &:hover { + background-color: ${(p) => p.theme.editorColours.buttonHover}; + } ` const TopBarWrapper = styled.div` - display: flex; - background: ${p => p.theme.editorColours.navigationBar}; - padding: 10px 10px 4px; - align-items: center; + display: flex; + background: ${(p) => p.theme.editorColours.navigationBar}; + padding: 10px 10px 4px; + align-items: center; ` interface UrlBarProps { - active: boolean + active: boolean } const UrlBar = styled('input')` - background: ${p => p.theme.editorColours.button}; - border-radius: 4px; - color: ${p => - p.active - ? p.theme.editorColours.navigationBarText - : p.theme.editorColours.textInactive}; - border: 1px solid ${p => p.theme.editorColours.background}; - padding: 6px 12px; - font-size: 13px; - flex: 1; + background: ${(p) => p.theme.editorColours.button}; + border-radius: 4px; + color: ${(p) => + p.active + ? p.theme.editorColours.navigationBarText + : p.theme.editorColours.textInactive}; + border: 1px solid ${(p) => p.theme.editorColours.background}; + padding: 6px 12px; + font-size: 13px; + flex: 1; ` const UrlBarWrapper = styled.div` - flex: 1; - margin-left: 6px; - position: relative; - display: flex; - align-items: center; + flex: 1; + margin-left: 6px; + position: relative; + display: flex; + align-items: center; ` const ReachError = styled.div` - position: absolute; - right: 5px; - display: flex; - align-items: center; - color: #f25c54; + position: absolute; + right: 5px; + display: flex; + align-items: center; + color: #f25c54; ` const Pulse = styled.div` - width: 16px; - height: 16px; - background-color: ${p => p.theme.editorColours.icon}; - border-radius: 100%; + width: 16px; + height: 16px; + background-color: ${(p) => p.theme.editorColours.icon}; + border-radius: 100%; ` const SpinnerWrapper = styled.div` - position: relative; - margin: 6px; + position: relative; + margin: 6px; ` const Spinner = () => ( - - - + + + ) diff --git a/packages/graphql-playground-react/src/components/Playground/util/createSDL.ts b/packages/graphql-playground-react/src/components/Playground/util/createSDL.ts new file mode 100644 index 000000000..e25a7bd84 --- /dev/null +++ b/packages/graphql-playground-react/src/components/Playground/util/createSDL.ts @@ -0,0 +1,104 @@ +import { + GraphQLEnumType, + GraphQLUnionType, + GraphQLInterfaceType, + GraphQLInputObjectType, + GraphQLSchema, + printSchema, +} from 'graphql' +import { serialize } from './stack' +import { prettify } from '../../../utils' +// import { getRootMap } from './stack' + +interface Options { + commentDescriptions?: boolean +} + +const defaultTypes = [ + '__Schema', + '__Directive', + '__DirectiveLocation', + '__Type', + '__Field', + '__InputValue', + '__EnumValue', + '__TypeKind', + 'String', + 'ID', + 'Boolean', + 'Int', + 'Float', +] + +/* Creates an array of SchemaTypes for the SDLFieldDocs +(A component that is similar to the DocsExplorer) to consume */ +export function sdlArray(schema: GraphQLSchema, options?: Options) { + const objectValues = + Object.values || ((obj) => Object.keys(obj).map((key) => obj[key])) + const typeMap = schema.getTypeMap() + const types = objectValues(typeMap) + .sort((type1, type2) => type1.name.localeCompare(type2.name)) + .filter((type) => !defaultTypes.includes(type.name)) + .map((type) => ({ + ...type, + ...serialize(schema, type), + instanceOf: getTypeInstance(type), + })) + return types +} + +function getTypeInstance(type) { + if (type instanceof GraphQLInterfaceType) { + return 'interface' + } else if (type instanceof GraphQLUnionType) { + return 'union' + } else if (type instanceof GraphQLEnumType) { + return 'enum' + } else if (type instanceof GraphQLInputObjectType) { + return 'input' + } else { + return 'type' + } +} + +// Returns a prettified schema +export function getSDL(schema: GraphQLSchema | null | undefined) { + if (schema instanceof GraphQLSchema) { + return schema && prettify(printSchema(schema), 80) + } + return '' +} + +// Downloads the schema in either .json or .graphql format +export function downloadSchema(schema: GraphQLSchema, type: string) { + if (type === 'sdl') { + const data = getSDL(schema) + const filename = 'schema.graphql' + return download(data, filename) + } else { + const data = JSON.stringify(schema) + const filename = 'instrospectionSchema.json' + return download(data, filename) + } +} + +// Performant option for downloading files +function download(data: any, filename: string, mime?: string) { + const blob = new Blob([data], { type: mime || 'application/octet-stream' }) + if (typeof window.navigator.msSaveBlob !== 'undefined') { + window.navigator.msSaveBlob(blob, filename) + } else { + const blobURL = window.URL.createObjectURL(blob) + const tempLink = document.createElement('a') + tempLink.style.display = 'none' + tempLink.href = blobURL + tempLink.setAttribute('download', filename) + if (typeof tempLink.download === 'undefined') { + tempLink.setAttribute('target', '_blank') + } + document.body.appendChild(tempLink) + tempLink.click() + document.body.removeChild(tempLink) + window.URL.revokeObjectURL(blobURL) + } +} diff --git a/packages/graphql-playground-react/src/components/Playground/util/immutableMemoize.ts b/packages/graphql-playground-react/src/components/Playground/util/immutableMemoize.ts index 9828d61f5..5e92b4a90 100644 --- a/packages/graphql-playground-react/src/components/Playground/util/immutableMemoize.ts +++ b/packages/graphql-playground-react/src/components/Playground/util/immutableMemoize.ts @@ -1,12 +1,12 @@ import { is } from 'immutable' export function immutableMemoize(fn) { - let lastValue - return arg => { - const newValue = fn(arg) - if (!is(lastValue, newValue)) { - lastValue = newValue - } - return lastValue - } + let lastValue + return (arg) => { + const newValue = fn(arg) + if (!is(lastValue, newValue)) { + lastValue = newValue + } + return lastValue + } } diff --git a/packages/graphql-playground-react/src/components/asyncComponent.tsx b/packages/graphql-playground-react/src/components/asyncComponent.tsx new file mode 100644 index 000000000..f07cdd43e --- /dev/null +++ b/packages/graphql-playground-react/src/components/asyncComponent.tsx @@ -0,0 +1,26 @@ +import * as React from 'react' +import Spinner from './Spinner' + +export interface State { + component?: any +} + +const asyncComponent = importComponent => { + return class extends React.Component { + state: State = { + component: null, + } + componentDidMount() { + importComponent().then(cmp => { + this.setState({ component: cmp.default }) + }) + } + + render() { + const C = this.state.component as any + return C ? : + } + } +} + +export default asyncComponent diff --git a/packages/graphql-playground-react/src/components/util.ts b/packages/graphql-playground-react/src/components/util.ts index 2e818134e..2508ac6a5 100644 --- a/packages/graphql-playground-react/src/components/util.ts +++ b/packages/graphql-playground-react/src/components/util.ts @@ -1,34 +1,34 @@ import { GraphQLConfig, GraphQLConfigEnpointConfig } from '../graphqlConfig' export function getActiveEndpoints( - config: GraphQLConfig, - envName: string, - projectName?: string, + config: GraphQLConfig, + envName: string, + projectName?: string, ): { endpoint: string; subscriptionEndpoint?: string; headers?: any } { - if (projectName) { - const env = config.projects![projectName].extensions!.endpoints![envName]! - return getEndpointFromEndpointConfig(env) - } else { - const env = config.extensions!.endpoints![envName]! - return getEndpointFromEndpointConfig(env) - } + if (projectName) { + const env = config.projects![projectName].extensions!.endpoints![envName]! + return getEndpointFromEndpointConfig(env) + } else { + const env = config.extensions!.endpoints![envName]! + return getEndpointFromEndpointConfig(env) + } } export function getEndpointFromEndpointConfig( - env: GraphQLConfigEnpointConfig | string, + env: GraphQLConfigEnpointConfig | string, ) { - if (typeof env === 'string') { - return { - endpoint: env, - subscriptionEndpoint: undefined, - } - } else { - return { - endpoint: env.url, - subscriptionEndpoint: env.subscription - ? env.subscription!.url - : undefined, - headers: env.headers, - } - } + if (typeof env === 'string') { + return { + endpoint: env, + subscriptionEndpoint: undefined, + } + } else { + return { + endpoint: env.url, + subscriptionEndpoint: env.subscription + ? env.subscription!.url + : undefined, + headers: env.headers, + } + } } diff --git a/packages/graphql-playground-react/src/localDevIndex.tsx b/packages/graphql-playground-react/src/localDevIndex.tsx index e8af959b9..392d85c87 100644 --- a/packages/graphql-playground-react/src/localDevIndex.tsx +++ b/packages/graphql-playground-react/src/localDevIndex.tsx @@ -3,81 +3,81 @@ import * as ReactDOM from 'react-dom' import MiddlewareApp from './components/MiddlewareApp' import './index.css' // import { Tab } from './state/sessions/reducers' -// import { LinkCreatorProps } from './state/sessions/fetchingSagas' -// import { ApolloLink } from 'apollo-link' -// import { HttpLink } from 'apollo-link-http' +import { LinkCreatorProps } from './state/sessions/fetchingSagas' +import { ApolloLink } from 'apollo-link' +import { HttpLink } from 'apollo-link-http' // import { exampleSchema } from './fixtures/exampleSchema' if (process.env.NODE_ENV !== 'production') { - /* tslint:disable-next-line */ - // const { whyDidYouUpdate } = require('why-did-you-update') - // whyDidYouUpdate(React) + /* tslint:disable-next-line */ + // const { whyDidYouUpdate } = require('why-did-you-update') + // whyDidYouUpdate(React) } /* tslint:disable-next-line */ ;(window as any)['GraphQLPlayground'] = { - init(element: HTMLElement, options) { - ReactDOM.render( - , - element, - ) - }, + init(element: HTMLElement, options) { + ReactDOM.render( + , + element, + ) + }, } -const configString = `projects: -app: - schemaPath: "src/schema.graphql" - extensions: - endpoints: - default: "http://localhost:4000" -database: - schemaPath: "src/generated/prisma.graphql" - extensions: - prisma: database/prisma.yml` +// const configString = `projects: +// app: +// schemaPath: "src/schema.graphql" +// extensions: +// endpoints: +// default: "http://localhost:4000" +// database: +// schemaPath: "src/generated/prisma.graphql" +// extensions: +// prisma: database/prisma.yml` -const config = { - projects: { - prisma: { - schemaPath: 'src/generated/prisma.graphql', - includes: ['database/seed.graphql'], - extensions: { - prisma: 'database/prisma.yml', - 'prepare-binding': { - output: 'src/generated/prisma.ts', - generator: 'prisma-ts', - }, - endpoints: { - dev2: { - url: 'https://eu1.prisma.sh/public-asdf/session65/dev', - // headers: { - // Authorization: - // 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7InNlcnZpY2UiOiJhc2RmQGRldiIsInJvbGVzIjpbImFkbWluIl19LCJpYXQiOjE1MjM1MTg3NTYsImV4cCI6MTUyNDEyMzU1Nn0.fzKhXa1JpN9M1UGTbS6p2KMUWDrKLxYD3i3a9eVfOQQ', - // }, - }, - }, - }, - }, - app: { - schemaPath: 'src/schema.graphql', - includes: ['queries/{booking,queries}.graphql'], - extensions: { - endpoints: { - default: 'http://localhost:4000', - }, - }, - }, - }, -} +// const config = { +// projects: { +// prisma: { +// schemaPath: 'src/generated/prisma.graphql', +// includes: ['database/seed.graphql'], +// extensions: { +// prisma: 'database/prisma.yml', +// 'prepare-binding': { +// output: 'src/generated/prisma.ts', +// generator: 'prisma-ts', +// }, +// endpoints: { +// dev2: { +// url: 'https://eu1.prisma.sh/public-asdf/session65/dev', +// // headers: { +// // Authorization: +// // 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7InNlcnZpY2UiOiJhc2RmQGRldiIsInJvbGVzIjpbImFkbWluIl19LCJpYXQiOjE1MjM1MTg3NTYsImV4cCI6MTUyNDEyMzU1Nn0.fzKhXa1JpN9M1UGTbS6p2KMUWDrKLxYD3i3a9eVfOQQ', +// // }, +// }, +// }, +// }, +// }, +// app: { +// schemaPath: 'src/schema.graphql', +// includes: ['queries/{booking,queries}.graphql'], +// extensions: { +// endpoints: { +// default: 'http://localhost:4000', +// }, +// }, +// }, +// }, +// } // const tabs: Tab[] = [ // { @@ -91,21 +91,21 @@ const config = { // }, // ] -// const customLinkCreator = ( -// session: LinkCreatorProps, -// wsEndpoint?: string, -// ): { link: ApolloLink } => { -// const { headers, credentials } = session +const customLinkCreator = ( + session: LinkCreatorProps, + wsEndpoint?: string, +): { link: ApolloLink } => { + const { headers, credentials } = session -// const link = new HttpLink({ -// uri: session.endpoint, -// fetch, -// headers, -// credentials, -// }) + const link = new HttpLink({ + uri: session.endpoint, + fetch, + headers, + credentials, + }) -// return { link } -// } + return { link } +} // const lightEditorColours = { // property: '#328c8c', diff --git a/packages/graphql-playground-react/src/state/docs/actions.ts b/packages/graphql-playground-react/src/state/docs/actions.ts index 650ec8ba1..8b52673e8 100644 --- a/packages/graphql-playground-react/src/state/docs/actions.ts +++ b/packages/graphql-playground-react/src/state/docs/actions.ts @@ -1,19 +1,23 @@ import { createActions } from 'redux-actions' export const { - setStacks, - addStack, - toggleDocs, - changeWidthDocs, - changeKeyMove, - showDocForReference, - setDocsVisible, + setStacks, + addStack, + toggleDocs, + setDocsVisible, + changeWidthDocs, + changeKeyMove, + showDocForReference, } = createActions({ - SET_STACKS: (sessionId, stacks) => ({ sessionId, stacks }), - ADD_STACK: (sessionId, field, x, y) => ({ sessionId, field, x, y }), - TOGGLE_DOCS: sessionId => ({ sessionId }), - SET_DOCS_VISIBLE: (sessionId, open) => ({ sessionId, open }), - CHANGE_WIDTH_DOCS: (sessionId, width) => ({ sessionId, width }), - CHANGE_KEY_MOVE: (sessionId, move) => ({ sessionId, move }), - SHOW_DOC_FOR_REFERENCE: reference => ({ reference }), + SET_STACKS: (sessionId, stacks) => ({ sessionId, stacks }), + ADD_STACK: (sessionId, field, x, y) => ({ sessionId, field, x, y }), + TOGGLE_DOCS: (sessionId, activeTabIdx) => ({ sessionId, activeTabIdx }), + SET_DOCS_VISIBLE: (sessionId, open, activeTabIdx?) => ({ + sessionId, + open, + activeTabIdx, + }), + CHANGE_WIDTH_DOCS: (sessionId, width) => ({ sessionId, width }), + CHANGE_KEY_MOVE: (sessionId, move) => ({ sessionId, move }), + SHOW_DOC_FOR_REFERENCE: (reference) => ({ reference }), }) diff --git a/packages/graphql-playground-react/src/state/docs/reducers.ts b/packages/graphql-playground-react/src/state/docs/reducers.ts index 3c5c57325..87de43f36 100644 --- a/packages/graphql-playground-react/src/state/docs/reducers.ts +++ b/packages/graphql-playground-react/src/state/docs/reducers.ts @@ -16,12 +16,14 @@ export interface DocsSessionState { readonly docsOpen: boolean readonly docsWidth: number readonly keyMove: boolean + readonly activeTabIdx: number | null } export class DocsSession extends Record({ navStack: List([]), docsOpen: false, docsWidth: columnWidth, + activeTabIdx: null, keyMove: false, }) { toJSON() { @@ -60,14 +62,28 @@ export default handleActions( }) return state.set(sessionId, session) }, - TOGGLE_DOCS: (state, { payload: { sessionId } }) => { + TOGGLE_DOCS: (state, { payload: { sessionId, activeTabIdx } }) => { let session = getSession(state, sessionId) session = session.set('docsOpen', !session.docsOpen) + if (typeof activeTabIdx === 'number') { + session = session.set( + 'activeTabIdx', + session.docsOpen ? activeTabIdx : null, + ) + } return state.set(sessionId, session) }, - SET_DOCS_VISIBLE: (state, { payload: { sessionId, open } }) => { + SET_DOCS_VISIBLE: ( + state, + { payload: { sessionId, open, activeTabIdx } }, + ) => { let session = getSession(state, sessionId) session = session.set('docsOpen', !!open) + if (!session.docsOpen) { + session = session.set('activeTabIdx', null) + } else if (typeof activeTabIdx === 'number') { + session = session.set('activeTabIdx', activeTabIdx) + } return state.set(sessionId, session) }, CHANGE_WIDTH_DOCS: (state, { payload: { sessionId, width } }) => { diff --git a/packages/graphql-playground-react/src/styled/theme.ts b/packages/graphql-playground-react/src/styled/theme.ts index 8bd101dff..e63896538 100644 --- a/packages/graphql-playground-react/src/styled/theme.ts +++ b/packages/graphql-playground-react/src/styled/theme.ts @@ -2,340 +2,340 @@ import { ISettings } from '../types' import { defaultSettings } from '../state/workspace/reducers' export interface Colours { - green: string - darkBlue: string - darkBlue50: string - darkBlue60: string - darkBlue80: string - darkBlue30: string - darkBlue20: string - darkBlue10: string - darkerBlue: string - darkestBlue: string - white80: string - white70: string - white60: string - white30: string - white20: string - white10: string - white: string - black02: string - black04: string - black07: string - black10: string - black30: string - black40: string - black50: string - paleText: string - paleGrey: string - red: string - blue: string - orange: string - purple: string - lightGrey: string - lighterGrey: string - // New dynamic styles - text: string - textInactive: string + green: string + darkBlue: string + darkBlue50: string + darkBlue60: string + darkBlue80: string + darkBlue30: string + darkBlue20: string + darkBlue10: string + darkerBlue: string + darkestBlue: string + white80: string + white70: string + white60: string + white30: string + white20: string + white10: string + white: string + black02: string + black04: string + black07: string + black10: string + black30: string + black40: string + black50: string + paleText: string + paleGrey: string + red: string + blue: string + orange: string + purple: string + lightGrey: string + lighterGrey: string + // New dynamic styles + text: string + textInactive: string } export interface EditorColours { - property: string - comment: string - punctuation: string - keyword: string - def: string - qualifier: string - attribute: string - number: string - string: string - builtin: string - string2: string - variable: string - meta: string - atom: string - ws: string - selection: string - cursorColor: string + property: string + comment: string + punctuation: string + keyword: string + def: string + qualifier: string + attribute: string + number: string + string: string + builtin: string + string2: string + variable: string + meta: string + atom: string + ws: string + selection: string + cursorColor: string - text: string - textInactive: string - background: string - sidebarTop: string - sidebar: string - sidebarBottom: string - sidebarItemActive: string - sidebarItemSide: string - sidebarItemSessions: string - tab: string - tabInactive: string - tabText: string - navigationBar: string - navigationBarText: string - editorBackground: string - resultBackground: string - leftDrawerBackground: string - rightDrawerBackground: string - drawerText: string - drawerTextInactive: string - executeButton: string - executeButtonBorder: string - executeButtonHover: string - executeButtonSubscription: string - executeButtonSubscriptionHover: string - icon: string - iconHover: string - button: string - buttonHover: string - buttonText: string - buttonWorkspace: string - buttonWorkspaceHover: string - buttonWorkspaceText: string - circle: string + text: string + textInactive: string + background: string + sidebarTop: string + sidebar: string + sidebarBottom: string + sidebarItemActive: string + sidebarItemSide: string + sidebarItemSessions: string + tab: string + tabInactive: string + tabText: string + navigationBar: string + navigationBarText: string + editorBackground: string + resultBackground: string + leftDrawerBackground: string + rightDrawerBackground: string + drawerText: string + drawerTextInactive: string + executeButton: string + executeButtonBorder: string + executeButtonHover: string + executeButtonSubscription: string + executeButtonSubscriptionHover: string + icon: string + iconHover: string + button: string + buttonHover: string + buttonText: string + buttonWorkspace: string + buttonWorkspaceHover: string + buttonWorkspaceText: string + circle: string } export const darkColours: Colours = { - green: '#27ae60', - darkBlue: 'rgb(23, 42, 58)', - darkBlue50: 'rgba(23, 42, 58, 0.5)', - darkBlue80: 'rgba(23, 42, 58, 0.8)', - darkBlue60: 'rgba(23, 42, 58, 0.6)', - darkBlue30: 'rgba(23, 42, 58, 0.3)', - darkBlue20: 'rgba(23, 42, 58, 0.2)', - darkBlue10: 'rgba(23, 42, 58, 0.1)', - darkerBlue: '#0F202D', - darkestBlue: 'rgb(11,20,28)', - white10: 'rgba(255, 255, 255, 0.1)', - white20: 'rgba(255, 255, 255, 0.2)', - white30: 'rgba(255, 255, 255, 0.3)', - white60: 'rgba(255, 255, 255, 0.6)', - white70: 'rgba(255, 255, 255, 0.7)', - white80: 'rgba(255, 255, 255, 0.8)', - white: 'rgba(255, 255, 255, 1)', - black02: 'rgba(0, 0, 0, 0.02)', - black07: 'rgba(0, 0, 0, 0.07)', - black04: 'rgba(0, 0, 0, 0.04)', - black10: 'rgba(0, 0, 0, 0.1)', - black30: 'rgba(0, 0, 0, 0.3)', - black40: 'rgba(0, 0, 0, 0.4)', - black50: 'rgba(0, 0, 0, 0.5)', - red: '#f25c54', - orange: 'rgba(241, 143, 1, 1)', - blue: 'rgba(42, 126, 210, 1)', - purple: 'rgb(164, 3, 111)', + green: '#27ae60', + darkBlue: 'rgb(23, 42, 58)', + darkBlue50: 'rgba(23, 42, 58, 0.5)', + darkBlue80: 'rgba(23, 42, 58, 0.8)', + darkBlue60: 'rgba(23, 42, 58, 0.6)', + darkBlue30: 'rgba(23, 42, 58, 0.3)', + darkBlue20: 'rgba(23, 42, 58, 0.2)', + darkBlue10: 'rgba(23, 42, 58, 0.1)', + darkerBlue: '#0F202D', + darkestBlue: 'rgb(11,20,28)', + white10: 'rgba(255, 255, 255, 0.1)', + white20: 'rgba(255, 255, 255, 0.2)', + white30: 'rgba(255, 255, 255, 0.3)', + white60: 'rgba(255, 255, 255, 0.6)', + white70: 'rgba(255, 255, 255, 0.7)', + white80: 'rgba(255, 255, 255, 0.8)', + white: 'rgba(255, 255, 255, 1)', + black02: 'rgba(0, 0, 0, 0.02)', + black07: 'rgba(0, 0, 0, 0.07)', + black04: 'rgba(0, 0, 0, 0.04)', + black10: 'rgba(0, 0, 0, 0.1)', + black30: 'rgba(0, 0, 0, 0.3)', + black40: 'rgba(0, 0, 0, 0.4)', + black50: 'rgba(0, 0, 0, 0.5)', + red: '#f25c54', + orange: 'rgba(241, 143, 1, 1)', + blue: 'rgba(42, 126, 210, 1)', + purple: 'rgb(164, 3, 111)', - paleText: 'rgba(0, 0, 0, 0.5)', - paleGrey: '#f3f4f4', // use for bgs, borders, etc - lightGrey: '#eeeff0', - lighterGrey: '#f6f7f7', - // New colors - text: 'rgba(255,255,255,0.6)', - textInactive: '#555e66', + paleText: 'rgba(0, 0, 0, 0.5)', + paleGrey: '#f3f4f4', // use for bgs, borders, etc + lightGrey: '#eeeff0', + lighterGrey: '#f6f7f7', + // New colors + text: 'rgba(255,255,255,0.6)', + textInactive: '#555e66', } export const lightColours: Colours = { - green: '#27ae60', - darkBlue: 'rgb(23, 42, 58)', - darkBlue50: 'rgba(23, 42, 58, 0.5)', - darkBlue80: 'rgba(23, 42, 58, 0.8)', - darkBlue60: 'rgba(23, 42, 58, 0.6)', - darkBlue30: 'rgba(23, 42, 58, 0.3)', - darkBlue20: 'rgba(23, 42, 58, 0.2)', - darkBlue10: 'rgba(23, 42, 58, 0.1)', - darkerBlue: '#0F202D', - darkestBlue: 'rgb(11,20,28)', - white10: 'rgba(255, 255, 255, 0.1)', - white20: 'rgba(255, 255, 255, 0.2)', - white30: 'rgba(255, 255, 255, 0.3)', - white60: 'rgba(255, 255, 255, 0.6)', - white70: 'rgba(255, 255, 255, 0.7)', - white80: 'rgba(255, 255, 255, 0.8)', - white: 'rgba(255, 255, 255, 1)', - black02: 'rgba(0, 0, 0, 0.02)', - black04: 'rgba(0, 0, 0, 0.04)', - black10: 'rgba(0, 0, 0, 0.1)', - black07: 'rgba(0, 0, 0, 0.07)', - black30: 'rgba(0, 0, 0, 0.3)', - black40: 'rgba(0, 0, 0, 0.4)', - black50: 'rgba(0, 0, 0, 0.5)', - red: '#f25c54', - orange: 'rgba(241, 143, 1, 1)', - blue: 'rgba(42, 126, 210, 1)', - purple: 'rgb(164, 3, 111)', + green: '#27ae60', + darkBlue: 'rgb(23, 42, 58)', + darkBlue50: 'rgba(23, 42, 58, 0.5)', + darkBlue80: 'rgba(23, 42, 58, 0.8)', + darkBlue60: 'rgba(23, 42, 58, 0.6)', + darkBlue30: 'rgba(23, 42, 58, 0.3)', + darkBlue20: 'rgba(23, 42, 58, 0.2)', + darkBlue10: 'rgba(23, 42, 58, 0.1)', + darkerBlue: '#0F202D', + darkestBlue: 'rgb(11,20,28)', + white10: 'rgba(255, 255, 255, 0.1)', + white20: 'rgba(255, 255, 255, 0.2)', + white30: 'rgba(255, 255, 255, 0.3)', + white60: 'rgba(255, 255, 255, 0.6)', + white70: 'rgba(255, 255, 255, 0.7)', + white80: 'rgba(255, 255, 255, 0.8)', + white: 'rgba(255, 255, 255, 1)', + black02: 'rgba(0, 0, 0, 0.02)', + black04: 'rgba(0, 0, 0, 0.04)', + black10: 'rgba(0, 0, 0, 0.1)', + black07: 'rgba(0, 0, 0, 0.07)', + black30: 'rgba(0, 0, 0, 0.3)', + black40: 'rgba(0, 0, 0, 0.4)', + black50: 'rgba(0, 0, 0, 0.5)', + red: '#f25c54', + orange: 'rgba(241, 143, 1, 1)', + blue: 'rgba(42, 126, 210, 1)', + purple: 'rgb(164, 3, 111)', - paleText: 'rgba(0, 0, 0, 0.5)', - paleGrey: '#f3f4f4', // use for bgs, borders, etc - lightGrey: '#eeeff0', - lighterGrey: '#f6f7f7', - // New colors - text: 'rgba(0,0,0,.7)', - textInactive: 'rgba(0,0,0,.3)', + paleText: 'rgba(0, 0, 0, 0.5)', + paleGrey: '#f3f4f4', // use for bgs, borders, etc + lightGrey: '#eeeff0', + lighterGrey: '#f6f7f7', + // New colors + text: 'rgba(0,0,0,.7)', + textInactive: 'rgba(0,0,0,.3)', } export const darkEditorColours: EditorColours = { - property: 'rgb(41, 185, 115)', - comment: 'rgba(255, 255, 255, 0.3)', - punctuation: 'rgba(255, 255, 255, 0.4)', - keyword: 'rgb(42, 126, 211)', - def: 'rgb(56, 189, 193)', - qualifier: '#1c92a9', - attribute: 'rgb(247, 116, 102)', - number: '#2882f9', - string: '#d64292', - builtin: '#d47509', - string2: '#0b7fc7', - variable: 'rgb(181, 34, 130)', - meta: '#b33086', - atom: 'rgb(249, 233, 34)', - ws: 'rgba(255, 255, 255, 0.4)', - selection: 'rgba(255, 255, 255, 0.1)', - cursorColor: 'rgba(255, 255, 255, 0.4)', + property: 'rgb(41, 185, 115)', + comment: 'rgba(255, 255, 255, 0.3)', + punctuation: 'rgba(255, 255, 255, 0.4)', + keyword: 'rgb(42, 126, 211)', + def: 'rgb(56, 189, 193)', + qualifier: '#1c92a9', + attribute: 'rgb(247, 116, 102)', + number: '#2882f9', + string: '#d64292', + builtin: '#d47509', + string2: '#0b7fc7', + variable: 'rgb(181, 34, 130)', + meta: '#b33086', + atom: 'rgb(249, 233, 34)', + ws: 'rgba(255, 255, 255, 0.4)', + selection: 'rgba(255, 255, 255, 0.1)', + cursorColor: 'rgba(255, 255, 255, 0.4)', - text: '#fff', - textInactive: 'rgba(255, 255, 255, 0.6)', - background: '#09141c', - sidebarTop: '#0f202d', - sidebar: '#172b3a', - sidebarBottom: '#172b3a', - sidebarItemActive: 'rgb(23, 42, 58)', - sidebarItemSide: '#27ae60', - sidebarItemSessions: 'rgba(255, 255, 255, 0.05)', - tab: '#172b3a', - tabInactive: '#0f202d', - tabText: '#fff', - navigationBar: '#172b3a', - navigationBarText: 'rgba(255, 255, 255, 0.6)', - editorBackground: '#0f202d', - resultBackground: '#172b3a', - leftDrawerBackground: '#0b1924', - rightDrawerBackground: '#0b1924', - drawerText: 'rgba(255,255,255,0.6)', - drawerTextInactive: '#555e66', - executeButton: 'rgb(185, 191, 196)', - executeButtonBorder: 'rgb(11, 20, 28)', - executeButtonHover: 'rgb(195, 201, 206)', - executeButtonSubscription: '#f25c54', - executeButtonSubscriptionHover: '#f36c65', - icon: 'rgb(74, 85, 95)', - iconHover: 'rgba(255, 255, 255, 0.6)', - button: '#0F202D', - buttonHover: '#122535', - buttonText: 'rgba(255,255,255,0.6)', - buttonWorkspace: '#b9bfc4', - buttonWorkspaceHover: '#a4acb2', - buttonWorkspaceText: 'rgb(23, 42, 58)', - circle: 'rgba(255, 255, 255, 0.4)', + text: '#fff', + textInactive: 'rgba(255, 255, 255, 0.6)', + background: '#09141c', + sidebarTop: '#0f202d', + sidebar: '#172b3a', + sidebarBottom: '#172b3a', + sidebarItemActive: 'rgb(23, 42, 58)', + sidebarItemSide: '#27ae60', + sidebarItemSessions: 'rgba(255, 255, 255, 0.05)', + tab: '#172b3a', + tabInactive: '#0f202d', + tabText: '#fff', + navigationBar: '#172b3a', + navigationBarText: 'rgba(255, 255, 255, 0.6)', + editorBackground: '#0f202d', + resultBackground: '#172b3a', + leftDrawerBackground: '#0b1924', + rightDrawerBackground: '#0b1924', + drawerText: 'rgba(255,255,255,0.6)', + drawerTextInactive: '#555e66', + executeButton: 'rgb(185, 191, 196)', + executeButtonBorder: 'rgb(11, 20, 28)', + executeButtonHover: 'rgb(195, 201, 206)', + executeButtonSubscription: '#f25c54', + executeButtonSubscriptionHover: '#f36c65', + icon: 'rgb(74, 85, 95)', + iconHover: 'rgba(255, 255, 255, 0.6)', + button: '#0F202D', + buttonHover: '#122535', + buttonText: 'rgba(255,255,255,0.6)', + buttonWorkspace: '#b9bfc4', + buttonWorkspaceHover: '#a4acb2', + buttonWorkspaceText: 'rgb(23, 42, 58)', + circle: 'rgba(255, 255, 255, 0.4)', } export const lightEditorColours: EditorColours = { - property: '#328c8c', // - comment: 'rgba(0, 0, 0, 0.3)', // - punctuation: 'rgba(23,42,58,.8)', // - keyword: '#366b6b', // - def: 'rgb(56, 189, 193)', // - qualifier: '#1c92a9', // - attribute: '#b56531', // - number: '#1f6ed6;', // - string: '#d64292', // - builtin: '#d47509', // - string2: '#0b7fc7', // - variable: 'rgb(236, 95, 103)', // - meta: '#b33086', // - atom: 'rgb(245, 160, 0)', // - ws: 'rgba(23, 42, 58, 0.8)', // - selection: '#d1e9fd', - cursorColor: 'rgba(0, 0, 0, 0.4)', + property: '#328c8c', // + comment: 'rgba(0, 0, 0, 0.3)', // + punctuation: 'rgba(23,42,58,.8)', // + keyword: '#366b6b', // + def: 'rgb(56, 189, 193)', // + qualifier: '#1c92a9', // + attribute: '#b56531', // + number: '#1f6ed6;', // + string: '#d64292', // + builtin: '#d47509', // + string2: '#0b7fc7', // + variable: 'rgb(236, 95, 103)', // + meta: '#b33086', // + atom: 'rgb(245, 160, 0)', // + ws: 'rgba(23, 42, 58, 0.8)', // + selection: '#d1e9fd', + cursorColor: 'rgba(0, 0, 0, 0.4)', - text: 'rgba(0, 0, 0, 0.7)', - textInactive: 'rgba(0, 0, 0, 0.3)', - background: '#dbdee0', - sidebarTop: '#eeeff0', - sidebar: '#eeeff0', - sidebarBottom: '#f6f7f7', - sidebarItemActive: '#f6f7f7', - sidebarItemSide: '#27ae60', - sidebarItemSessions: '#dbdee0', - tab: '#eeeff0', - tabInactive: '#e7eaec', - tabText: 'rgba(23, 42, 58, .8)', - navigationBar: '#eeeff0', - navigationBarText: 'rgba(23, 42, 58, 0.8)', - editorBackground: '#f6f7f7', - resultBackground: '#eeeff0', - leftDrawerBackground: '#e9eaea', - rightDrawerBackground: '#e5e7e7', - drawerText: 'rgba(0, 0, 0, 0.7)', - drawerTextInactive: 'rgba(0, 0, 0, 0.3)', - executeButton: 'rgb(115, 127, 136)', - executeButtonBorder: '#eeeff0', - executeButtonHover: '', - executeButtonSubscription: '#f25c54', - executeButtonSubscriptionHover: '#f36c65', - icon: 'rgb(194, 200, 203)', - iconHover: 'rgba(23, 42, 58, 0.6)', - button: '#d8dbde', - buttonHover: 'rgba(20, 37, 51, 0.2)', - buttonText: 'rgba(23, 42, 58, 0.8)', - buttonWorkspace: 'rgb(185, 191, 196)', - buttonWorkspaceHover: 'rgb(157, 166, 173)', - buttonWorkspaceText: 'rgb(238, 239, 240)', - circle: 'rgba(23,42,58,.4)', + text: 'rgba(0, 0, 0, 0.7)', + textInactive: 'rgba(0, 0, 0, 0.3)', + background: '#dbdee0', + sidebarTop: '#eeeff0', + sidebar: '#eeeff0', + sidebarBottom: '#f6f7f7', + sidebarItemActive: '#f6f7f7', + sidebarItemSide: '#27ae60', + sidebarItemSessions: '#dbdee0', + tab: '#eeeff0', + tabInactive: '#e7eaec', + tabText: 'rgba(23, 42, 58, .8)', + navigationBar: '#eeeff0', + navigationBarText: 'rgba(23, 42, 58, 0.8)', + editorBackground: '#f6f7f7', + resultBackground: '#eeeff0', + leftDrawerBackground: '#e9eaea', + rightDrawerBackground: '#e5e7e7', + drawerText: 'rgba(0, 0, 0, 0.7)', + drawerTextInactive: 'rgba(0, 0, 0, 0.3)', + executeButton: 'rgb(115, 127, 136)', + executeButtonBorder: '#eeeff0', + executeButtonHover: '', + executeButtonSubscription: '#f25c54', + executeButtonSubscriptionHover: '#f36c65', + icon: 'rgb(194, 200, 203)', + iconHover: 'rgba(23, 42, 58, 0.6)', + button: '#d8dbde', + buttonHover: 'rgba(20, 37, 51, 0.2)', + buttonText: 'rgba(23, 42, 58, 0.8)', + buttonWorkspace: 'rgb(185, 191, 196)', + buttonWorkspaceHover: 'rgb(157, 166, 173)', + buttonWorkspaceText: 'rgb(238, 239, 240)', + circle: 'rgba(23,42,58,.4)', } export interface Sizes { - small6: string - small10: string - small12: string - small16: string - medium25: string - smallRadius: string - fontLight: string - fontSemiBold: string - fontTiny: string - fontSmall: string - fontMedium: string + small6: string + small10: string + small12: string + small16: string + medium25: string + smallRadius: string + fontLight: string + fontSemiBold: string + fontTiny: string + fontSmall: string + fontMedium: string } export const sizes: Sizes = { - small6: '6px', - small10: '10px', - small12: '12px', - small16: '16px', - medium25: '25px', + small6: '6px', + small10: '10px', + small12: '12px', + small16: '16px', + medium25: '25px', - // font weights - fontLight: '300', - fontSemiBold: '600', + // font weights + fontLight: '300', + fontSemiBold: '600', - // font sizes - fontTiny: '12px', - fontSmall: '14px', - fontMedium: '20px', + // font sizes + fontTiny: '12px', + fontSmall: '14px', + fontMedium: '20px', - // others - smallRadius: '2px', + // others + smallRadius: '2px', } export interface Shorthands { - [x: string]: any + [x: string]: any } export const shorthands: Shorthands = {} export interface ThemeInterface { - mode: 'light' | 'dark' - colours: Colours - sizes: Sizes - shorthands: Shorthands - editorColours: EditorColours - settings: ISettings + mode: 'light' | 'dark' + colours: Colours + sizes: Sizes + shorthands: Shorthands + editorColours: EditorColours + settings: ISettings } export const theme: any = { - mode: 'dark', - colours: darkColours, - sizes, - shorthands, - editorColours: darkEditorColours, - settings: defaultSettings, + mode: 'dark', + colours: darkColours, + sizes, + shorthands, + editorColours: darkEditorColours, + settings: defaultSettings, } diff --git a/packages/graphql-playground-react/src/utils.ts b/packages/graphql-playground-react/src/utils.ts index 4b2f1cc94..19b12e1a3 100644 --- a/packages/graphql-playground-react/src/utils.ts +++ b/packages/graphql-playground-react/src/utils.ts @@ -3,27 +3,27 @@ import * as graphql from 'prettier/parser-graphql' // tslint:disable export function safely(cb: any) { - return function*(...args) { - try { - yield cb(...args) - } catch (e) { - console.error(e) - } - } + return function*(...args) { + try { + yield cb(...args) + } catch (e) { + console.error(e) + } + } } export function prettify(query: string, printWidth: number) { - return prettier.format(query, { - parser: 'graphql', - printWidth, - plugins: [graphql], - }) + return prettier.format(query, { + parser: 'graphql', + printWidth, + plugins: [graphql], + }) } export function isIframe() { - try { - return window.self !== window.top - } catch (e) { - return true - } + try { + return window.self !== window.top + } catch (e) { + return true + } } From 56b0aa8c15edf899bf77d8e50a40d476a7365122 Mon Sep 17 00:00:00 2001 From: rajinwonderland Date: Mon, 3 Dec 2018 16:16:00 -0800 Subject: [PATCH 02/10] Added and integrated setting's key for 'schema.disableComments' --- README.md | 187 +++--- .../Playground/SchemaExplorer/SDLEditor.tsx | 189 +++--- .../Playground/SchemaExplorer/SDLView.tsx | 178 +++--- .../components/Playground/util/createSDL.ts | 160 ++--- .../src/state/workspace/reducers.ts | 1 + .../src/styled/theme.ts | 595 +++++++++--------- .../graphql-playground-react/src/types.ts | 1 + 7 files changed, 676 insertions(+), 635 deletions(-) diff --git a/README.md b/README.md index 78ea4505d..a338f9311 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,11 @@ $ brew cask install graphql-playground ## Features -* ✨ Context-aware autocompletion & error highlighting -* 📚 Interactive, multi-column docs (keyboard support) -* ⚡️ Supports real-time GraphQL Subscriptions -* ⚙ GraphQL Config support with multiple Projects & Endpoints -* 🚥 Apollo Tracing support +- ✨ Context-aware autocompletion & error highlighting +- 📚 Interactive, multi-column docs (keyboard support) +- ⚡️ Supports real-time GraphQL Subscriptions +- ⚙ GraphQL Config support with multiple Projects & Endpoints +- 🚥 Apollo Tracing support ## FAQ @@ -27,12 +27,12 @@ $ brew cask install graphql-playground GraphQL Playground uses components of GraphiQL under the hood but is meant as a more powerful GraphQL IDE enabling better (local) development workflows. Compared to GraphiQL, the GraphQL Playground ships with the following additional features: -* Interactive, multi-column schema documentation -* Automatic schema reloading -* Support for GraphQL Subscriptions -* Query history -* Configuration of HTTP headers -* Tabs +- Interactive, multi-column schema documentation +- Automatic schema reloading +- Support for GraphQL Subscriptions +- Query history +- Configuration of HTTP headers +- Tabs See the following question for more additonal features. @@ -40,8 +40,8 @@ See the following question for more additonal features. The desktop app is the same as the web version but includes these additional features: -* Partial support for [graphql-config](https://github.com/prismagraphql/graphql-config) enabling features like multi-environment setups (no support for sending HTTP headers). -* Double click on `*.graphql` files. +- Partial support for [graphql-config](https://github.com/prismagraphql/graphql-config) enabling features like multi-environment setups (no support for sending HTTP headers). +- Double click on `*.graphql` files. ### How does GraphQL Bin work? @@ -77,43 +77,44 @@ These are the settings currently available: The React component `` and all middlewares expose the following options: -* `props` (Middlewares & React Component) - * `endpoint` [`string`](optional) - the GraphQL endpoint url. - * `subscriptionEndpoint` [`string`](optional) - the GraphQL subscriptions endpoint url. - * `workspaceName` [`string`](optional) - in case you provide a GraphQL Config, you can name your workspace here - * `config` [`string`](optional) - the JSON of a GraphQL Config. See an example [here](https://github.com/prismagraphql/graphql-playground/blob/master/packages/graphql-playground-react/src/localDevIndex.tsx#L47) - * `settings` [`ISettings`](optional) - Editor settings in json format as [described here](https://github.com/prismagraphql/graphql-playground#settings) +- `props` (Middlewares & React Component) + - `endpoint` [`string`](optional) - the GraphQL endpoint url. + - `subscriptionEndpoint` [`string`](optional) - the GraphQL subscriptions endpoint url. + - `workspaceName` [`string`](optional) - in case you provide a GraphQL Config, you can name your workspace here + - `config` [`string`](optional) - the JSON of a GraphQL Config. See an example [here](https://github.com/prismagraphql/graphql-playground/blob/master/packages/graphql-playground-react/src/localDevIndex.tsx#L47) + - `settings` [`ISettings`](optional) - Editor settings in json format as [described here](https://github.com/prismagraphql/graphql-playground#settings) ```ts interface ISettings { - 'general.betaUpdates': boolean - 'editor.theme': Theme - 'editor.reuseHeaders': boolean - 'tracing.hideTracingResponse': boolean - 'editor.fontSize': number - 'editor.fontFamily': string - 'request.credentials': string + 'general.betaUpdates': boolean + 'editor.theme': Theme + 'editor.reuseHeaders': boolean + 'tracing.hideTracingResponse': boolean + 'editor.fontSize': number + 'editor.fontFamily': string + 'request.credentials': string + 'schema.disableComments': boolean } ``` -* `schema` [`IntrospectionResult`](optional) - The result of an introspection query (an object of this form: `{__schema: {...}}`) The playground automatically fetches the schema from the endpoint. This is only needed when you want to override the schema. -* `tabs` [`Tab[]`](optional) - An array of tabs to inject. **Note: When using this feature, tabs will be resetted each time the page is reloaded** +- `schema` [`IntrospectionResult`](optional) - The result of an introspection query (an object of this form: `{__schema: {...}}`) The playground automatically fetches the schema from the endpoint. This is only needed when you want to override the schema. +- `tabs` [`Tab[]`](optional) - An array of tabs to inject. **Note: When using this feature, tabs will be resetted each time the page is reloaded** ```ts interface Tab { - endpoint: string - query: string - name?: string - variables?: string - responses?: string[] - headers?: { [key: string]: string } + endpoint: string + query: string + name?: string + variables?: string + responses?: string[] + headers?: { [key: string]: string } } ``` In addition to this, the React app provides some more properties: -* `props` (React Component) -* `createApolloLink` [`(session: Session) => ApolloLink`] - this is the equivalent to the `fetcher` of GraphiQL. For each query that is being executed, this function will be called +- `props` (React Component) +- `createApolloLink` [`(session: Session) => ApolloLink`] - this is the equivalent to the `fetcher` of GraphiQL. For each query that is being executed, this function will be called `createApolloLink` is only available in the React Component and not the middlewares, because the content must be serializable as it is being printed into a HTML template. @@ -145,7 +146,10 @@ The GraphQL Playground requires **React 16**. Including Fonts (`1.`) ```html - + ``` Including stylesheet and the component (`2., 3.`) @@ -157,10 +161,10 @@ import { Provider } from 'react-redux' import { Playground, store } from 'graphql-playground-react' ReactDOM.render( - - - , - document.body, + + + , + document.body, ) ``` @@ -180,13 +184,13 @@ yarn add graphql-playground-middleware-lambda We have a full example for each of the frameworks below: -* **Express:** See [packages/graphql-playground-middleware-express/examples/basic](https://github.com/prismagraphql/graphql-playground/tree/master/packages/graphql-playground-middleware-express/examples/basic) +- **Express:** See [packages/graphql-playground-middleware-express/examples/basic](https://github.com/prismagraphql/graphql-playground/tree/master/packages/graphql-playground-middleware-express/examples/basic) -* **Hapi:** See [packages/graphql-playground-middleware-hapi](https://github.com/prismagraphql/graphql-playground/tree/master/packages/graphql-playground-middleware-hapi) +- **Hapi:** See [packages/graphql-playground-middleware-hapi](https://github.com/prismagraphql/graphql-playground/tree/master/packages/graphql-playground-middleware-hapi) -* **Koa:** See [packages/graphql-playground-middleware-koa](https://github.com/prismagraphql/graphql-playground/tree/master/packages/graphql-playground-middleware-koa) +- **Koa:** See [packages/graphql-playground-middleware-koa](https://github.com/prismagraphql/graphql-playground/tree/master/packages/graphql-playground-middleware-koa) -* **Lambda (as serverless handler):** See [serverless-graphql-apollo](https://github.com/serverless/serverless-graphql-apollo) or a quick example below. +- **Lambda (as serverless handler):** See [serverless-graphql-apollo](https://github.com/serverless/serverless-graphql-apollo) or a quick example below. ### As serverless handler @@ -206,18 +210,18 @@ import lambdaPlayground from 'graphql-playground-middleware-lambda' // const lambdaPlayground = require('graphql-playground-middleware-lambda').default exports.graphqlHandler = function graphqlHandler(event, context, callback) { - function callbackFilter(error, output) { - // eslint-disable-next-line no-param-reassign - output.headers['Access-Control-Allow-Origin'] = '*' - callback(error, output) - } - - const handler = graphqlLambda({ schema: myGraphQLSchema }) - return handler(event, context, callbackFilter) + function callbackFilter(error, output) { + // eslint-disable-next-line no-param-reassign + output.headers['Access-Control-Allow-Origin'] = '*' + callback(error, output) + } + + const handler = graphqlLambda({ schema: myGraphQLSchema }) + return handler(event, context, callbackFilter) } exports.playgroundHandler = lambdaPlayground({ - endpoint: '/dev/graphql', + endpoint: '/dev/graphql', }) ``` @@ -228,17 +232,17 @@ functions: graphql: handler: handler.graphqlHandler events: - - http: - path: graphql - method: post - cors: true + - http: + path: graphql + method: post + cors: true playground: handler: handler.playgroundHandler events: - - http: - path: playground - method: get - cors: true + - http: + path: playground + method: get + cors: true ``` ## Development @@ -253,32 +257,33 @@ Open [localhost:3000/localDev.html?endpoint=https://api.graph.cool/simple/v1/swapi](http://localhost:3000/localDev.html?endpoint=https://api.graph.cool/simple/v1/swapi) for local development! ## Custom Theme + From `graphql-playground-react@1.7.0` on you can provide a `codeTheme` property to the React Component to customize your color theme. These are the available options: -```ts +```ts export interface EditorColours { - property: string - comment: string - punctuation: string - keyword: string - def: string - qualifier: string - attribute: string - number: string - string: string - builtin: string - string2: string - variable: string - meta: string - atom: string - ws: string - selection: string - cursorColor: string - editorBackground: string - resultBackground: string - leftDrawerBackground: string - rightDrawerBackground: string + property: string + comment: string + punctuation: string + keyword: string + def: string + qualifier: string + attribute: string + number: string + string: string + builtin: string + string2: string + variable: string + meta: string + atom: string + ws: string + selection: string + cursorColor: string + editorBackground: string + resultBackground: string + leftDrawerBackground: string + rightDrawerBackground: string } ``` @@ -290,13 +295,13 @@ This is repository is a "mono repo" and contains multiple packages using [Yarn w In the folder `packages` you'll find the following packages: -* `graphql-playground-electron`: Cross-platform electron app which uses `graphql-playground-react` -* `graphql-playground-html`: Simple HTML page rendering a version of `graphql-playground-react` hosted on JSDeliver -* `graphql-playground-middleware-express`: Express middleware using `graphql-playground-html` -* `graphql-playground-middleware-hapi`: Hapi middleware using `graphql-playground-html` -* `graphql-playground-middleware-koa`: Koa middleware using `graphql-playground-html` -* `graphql-playground-middleware-lambda`: AWS Lambda middleware using `graphql-playground-html` -* `graphql-playground-react`: Core of GraphQL Playground built with ReactJS +- `graphql-playground-electron`: Cross-platform electron app which uses `graphql-playground-react` +- `graphql-playground-html`: Simple HTML page rendering a version of `graphql-playground-react` hosted on JSDeliver +- `graphql-playground-middleware-express`: Express middleware using `graphql-playground-html` +- `graphql-playground-middleware-hapi`: Hapi middleware using `graphql-playground-html` +- `graphql-playground-middleware-koa`: Koa middleware using `graphql-playground-html` +- `graphql-playground-middleware-lambda`: AWS Lambda middleware using `graphql-playground-html` +- `graphql-playground-react`: Core of GraphQL Playground built with ReactJS diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLEditor.tsx b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLEditor.tsx index 600fd98da..fa08d08ea 100644 --- a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLEditor.tsx +++ b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLEditor.tsx @@ -3,109 +3,130 @@ import { GraphQLSchema } from 'graphql' import EditorWrapper from '../EditorWrapper' import { styled } from '../../../styled' import { getSDL } from '../util/createSDL' +import { ISettings } from '../../../types' export interface Props { - schema?: GraphQLSchema | null - getRef?: (ref: SDLEditor) => void - width?: number - sessionId?: string + schema?: GraphQLSchema | null + getRef?: (ref: SDLEditor) => void + width?: number + sessionId?: string + settings: ISettings } class SDLEditor extends React.PureComponent { - cachedValue: string - private editor: any - private node: any + cachedValue: string + private editor: any + private node: any - constructor(props) { - super(props) + constructor(props) { + super(props) - // 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 || '' - if (this.props.getRef) { - this.props.getRef(this) - } - } + // 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 || '' + if (this.props.getRef) { + this.props.getRef(this) + } + } - componentDidMount() { - // Lazily require to ensure requiring GraphiQL outside of a Browser context - // does not produce an error. - const CodeMirror = require('codemirror') - require('codemirror/addon/fold/brace-fold') - require('codemirror/addon/comment/comment') - require('codemirror-graphql/mode') + componentDidMount() { + // Lazily require to ensure requiring GraphiQL outside of a Browser context + // does not produce an error. + const CodeMirror = require('codemirror') + require('codemirror/addon/fold/brace-fold') + require('codemirror/addon/comment/comment') + require('codemirror-graphql/mode') - const gutters: any[] = [] - gutters.push('CodeMirror-linenumbers') + const gutters: any[] = [] + gutters.push('CodeMirror-linenumbers') - this.editor = CodeMirror(this.node, { - autofocus: false, - value: getSDL(this.props.schema) || '', - lineNumbers: false, - showCursorWhenSelecting: false, - tabSize: 1, - mode: 'graphql', - theme: 'graphiql', - lineWrapping: true, - keyMap: 'sublime', - readOnly: true, - gutters, - }) - ;(global as any).editor = this.editor - this.editor.refresh() - } + this.editor = CodeMirror(this.node, { + autofocus: false, + value: + getSDL( + this.props.schema, + this.props.settings['schema.disableComments'], + ) || '', + lineNumbers: false, + showCursorWhenSelecting: false, + tabSize: 1, + mode: 'graphql', + theme: 'graphiql', + lineWrapping: true, + keyMap: 'sublime', + readOnly: true, + gutters, + }) + ;(global as any).editor = this.editor + this.editor.refresh() + } - componentDidUpdate(prevProps: Props) { - const CodeMirror = require('codemirror') - if (this.props.schema !== prevProps.schema) { - this.cachedValue = getSDL(this.props.schema) || '' - this.editor.setValue(getSDL(this.props.schema)) - CodeMirror.signal(this.editor, 'change', this.editor) - } - if (this.props.width !== prevProps.width) { - this.editor.refresh() - } - } + componentDidUpdate(prevProps: Props) { + const CodeMirror = require('codemirror') + if (this.props.schema !== prevProps.schema) { + this.cachedValue = + getSDL( + this.props.schema, + this.props.settings['schema.disableComments'], + ) || '' + this.editor.setValue( + getSDL( + this.props.schema, + this.props.settings['schema.disableComments'], + ), + ) + CodeMirror.signal(this.editor, 'change', this.editor) + } + if (this.props.width !== prevProps.width) { + this.editor.refresh() + } + if ( + this.props.settings['schema.disableComments'] !== + prevProps.settings['schema.disableComments'] + ) { + this.editor.refresh() + } + } - componentWillReceiveProps(nextProps) { - if (this.props.sessionId !== nextProps.sessionId) { - this.editor.scrollTo(0, 0) - } - } + componentWillReceiveProps(nextProps) { + if (this.props.sessionId !== nextProps.sessionId) { + this.editor.scrollTo(0, 0) + } + } - componentWillUnmount() { - this.editor = null - } + componentWillUnmount() { + this.editor = null + } - render() { - return ( - - - - ) - } + render() { + return ( + + + + ) + } - setRef = (ref) => { - this.node = ref - } + setRef = ref => { + this.node = ref + } - getCodeMirror() { - return this.editor - } - getClientHeight() { - return this.node && this.node.clientHeight - } + getCodeMirror() { + return this.editor + } + getClientHeight() { + return this.node && this.node.clientHeight + } } export default SDLEditor const Editor = styled.div` - flex: 1; - height: auto; - overflow-x: hidden; - overflow-y: scroll; - .CodeMirror { - background: ${(p) => p.theme.editorColours.editorBackground}; - } + flex: 1; + height: auto; + overflow-x: hidden; + overflow-y: scroll; + .CodeMirror { + background: ${p => p.theme.editorColours.editorBackground}; + } ` diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLView.tsx b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLView.tsx index 2f290b9f9..62e8b2a5f 100644 --- a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLView.tsx +++ b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLView.tsx @@ -2,9 +2,9 @@ import * as React from 'react' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import { - toggleDocs, - changeWidthDocs, - setDocsVisible, + toggleDocs, + changeWidthDocs, + setDocsVisible, } from '../../../state/docs/actions' import Spinner from '../../Spinner' import { columnWidth } from '../../../constants' @@ -14,109 +14,113 @@ import { getSessionDocs } from '../../../state/docs/selectors' import { createStructuredSelector } from 'reselect' import { ErrorContainer } from '../DocExplorer/ErrorContainer' import { - SDLHeader, - SchemaExplorerContainer, - SDLColumn, + SDLHeader, + SchemaExplorerContainer, + SDLColumn, } from './SDLTypes/SDLStyles' import SDLEditor from './SDLEditor' +import { getSettings } from '../../../state/workspace/reducers' interface StateFromProps { - docs: { - navStack: any[] - docsOpen: boolean - docsWidth: number - keyMove: boolean - } + docs: { + navStack: any[] + docsOpen: boolean + docsWidth: number + keyMove: boolean + } + settings } interface DispatchFromProps { - toggleDocs: (sessionId: string) => any - setDocsVisible: (sessionId: string, open: boolean) => any - changeWidthDocs: (sessionId: string, width: number) => any + toggleDocs: (sessionId: string) => any + setDocsVisible: (sessionId: string, open: boolean) => any + changeWidthDocs: (sessionId: string, width: number) => any } class SDLView extends React.Component< - SideTabContentProps & StateFromProps & DispatchFromProps + SideTabContentProps & StateFromProps & DispatchFromProps > { - ref - constructor(props) { - super(props) - ;(window as any).d = this - } - componentWillReceiveProps(nextProps: SideTabContentProps & StateFromProps) { - // If user use default column size % columnWidth - // Make the column follow the clicks - if (!this.props.schema && nextProps.schema) { - this.setWidth(nextProps) - } - } + ref + constructor(props) { + super(props) + ;(window as any).d = this + } + componentWillReceiveProps(nextProps: SideTabContentProps & StateFromProps) { + // If user use default column size % columnWidth + // Make the column follow the clicks + if (!this.props.schema && nextProps.schema) { + this.setWidth(nextProps) + } + } - setWidth(props: any = this.props) { - this.props.setWidth(props) - } + setWidth(props: any = this.props) { + this.props.setWidth(props) + } - getWidth(props: any = this.props) { - const rootWidth = props.docs.docsWidth || columnWidth - return rootWidth - } - componentDidMount() { - this.setWidth() - } + getWidth(props: any = this.props) { + const rootWidth = props.docs.docsWidth || columnWidth + return rootWidth + } + componentDidMount() { + this.setWidth() + } - render() { - const { schema } = this.props - let emptySchema - if (schema === undefined) { - // Schema is undefined when it is being loaded via introspection. - emptySchema = - } else if (schema === null) { - // Schema is null when it explicitly does not exist, typically due to - // an error during introspection. - emptySchema = {'No Schema Available'} - } - // let types - // if (schema instanceof GraphQLSchema) { - // types = sdlArray(schema) - // } - return ( - - {emptySchema ? ( - {emptySchema} - ) : ( - - - - - )} - - ) - } - setRef = (ref) => { - this.ref = ref - } + render() { + const { schema, settings } = this.props + let emptySchema + if (schema === undefined) { + // Schema is undefined when it is being loaded via introspection. + emptySchema = + } else if (schema === null) { + // Schema is null when it explicitly does not exist, typically due to + // an error during introspection. + emptySchema = {'No Schema Available'} + } + // let types + // if (schema instanceof GraphQLSchema) { + // types = sdlArray(schema) + // } + return ( + + {emptySchema ? ( + {emptySchema} + ) : ( + + + + + )} + + ) + } + setRef = ref => { + this.ref = ref + } } -const mapDispatchToProps = (dispatch) => - bindActionCreators( - { - toggleDocs, - changeWidthDocs, - setDocsVisible, - }, - dispatch, - ) +const mapDispatchToProps = dispatch => + bindActionCreators( + { + toggleDocs, + changeWidthDocs, + setDocsVisible, + }, + dispatch, + ) const mapStateToProps = createStructuredSelector({ - docs: getSessionDocs, - sessionId: getSelectedSessionIdFromRoot, + settings: getSettings, + docs: getSessionDocs, + sessionId: getSelectedSessionIdFromRoot, }) export default connect( - mapStateToProps, - mapDispatchToProps, - null, - { withRef: true }, + mapStateToProps, + mapDispatchToProps, + null, + { withRef: true }, )(SDLView) diff --git a/packages/graphql-playground-react/src/components/Playground/util/createSDL.ts b/packages/graphql-playground-react/src/components/Playground/util/createSDL.ts index e25a7bd84..84638b81f 100644 --- a/packages/graphql-playground-react/src/components/Playground/util/createSDL.ts +++ b/packages/graphql-playground-react/src/components/Playground/util/createSDL.ts @@ -1,104 +1,116 @@ import { - GraphQLEnumType, - GraphQLUnionType, - GraphQLInterfaceType, - GraphQLInputObjectType, - GraphQLSchema, - printSchema, + GraphQLEnumType, + GraphQLUnionType, + GraphQLInterfaceType, + GraphQLInputObjectType, + GraphQLSchema, + printSchema, } from 'graphql' import { serialize } from './stack' import { prettify } from '../../../utils' // import { getRootMap } from './stack' interface Options { - commentDescriptions?: boolean + commentDescriptions?: boolean } const defaultTypes = [ - '__Schema', - '__Directive', - '__DirectiveLocation', - '__Type', - '__Field', - '__InputValue', - '__EnumValue', - '__TypeKind', - 'String', - 'ID', - 'Boolean', - 'Int', - 'Float', + '__Schema', + '__Directive', + '__DirectiveLocation', + '__Type', + '__Field', + '__InputValue', + '__EnumValue', + '__TypeKind', + 'String', + 'ID', + 'Boolean', + 'Int', + 'Float', ] /* Creates an array of SchemaTypes for the SDLFieldDocs (A component that is similar to the DocsExplorer) to consume */ export function sdlArray(schema: GraphQLSchema, options?: Options) { - const objectValues = - Object.values || ((obj) => Object.keys(obj).map((key) => obj[key])) - const typeMap = schema.getTypeMap() - const types = objectValues(typeMap) - .sort((type1, type2) => type1.name.localeCompare(type2.name)) - .filter((type) => !defaultTypes.includes(type.name)) - .map((type) => ({ - ...type, - ...serialize(schema, type), - instanceOf: getTypeInstance(type), - })) - return types + const objectValues = + Object.values || (obj => Object.keys(obj).map(key => obj[key])) + const typeMap = schema.getTypeMap() + const types = objectValues(typeMap) + .sort((type1, type2) => type1.name.localeCompare(type2.name)) + .filter(type => !defaultTypes.includes(type.name)) + .map(type => ({ + ...type, + ...serialize(schema, type), + instanceOf: getTypeInstance(type), + })) + return types } function getTypeInstance(type) { - if (type instanceof GraphQLInterfaceType) { - return 'interface' - } else if (type instanceof GraphQLUnionType) { - return 'union' - } else if (type instanceof GraphQLEnumType) { - return 'enum' - } else if (type instanceof GraphQLInputObjectType) { - return 'input' - } else { - return 'type' - } + if (type instanceof GraphQLInterfaceType) { + return 'interface' + } else if (type instanceof GraphQLUnionType) { + return 'union' + } else if (type instanceof GraphQLEnumType) { + return 'enum' + } else if (type instanceof GraphQLInputObjectType) { + return 'input' + } else { + return 'type' + } } // Returns a prettified schema -export function getSDL(schema: GraphQLSchema | null | undefined) { - if (schema instanceof GraphQLSchema) { - return schema && prettify(printSchema(schema), 80) - } - return '' +export function getSDL( + schema: GraphQLSchema | null | undefined, + commentsDisabled: boolean, +) { + if (schema instanceof GraphQLSchema) { + const sdl = printSchema(schema, { commentDescriptions: false }) + if (commentsDisabled) { + const sdlWithNewLines = sdl.replace(/(\#[\w\'\s\r\n\*](.*)$)/gm, '') + const sdlWithoutComments = prettify(sdlWithNewLines, 80).replace( + /^\s*$(?:\r\n?|\n)/gm, + '', + ) + return sdlWithoutComments + } + return sdl + } + return '' } // Downloads the schema in either .json or .graphql format export function downloadSchema(schema: GraphQLSchema, type: string) { - if (type === 'sdl') { - const data = getSDL(schema) - const filename = 'schema.graphql' - return download(data, filename) - } else { - const data = JSON.stringify(schema) - const filename = 'instrospectionSchema.json' - return download(data, filename) - } + if (type === 'sdl') { + const data = getSDL(schema, false) + const filename = 'schema.graphql' + return download(data, filename) + } else { + const data = JSON.stringify(schema) + const filename = 'instrospectionSchema.json' + return download(data, filename) + } } // Performant option for downloading files function download(data: any, filename: string, mime?: string) { - const blob = new Blob([data], { type: mime || 'application/octet-stream' }) - if (typeof window.navigator.msSaveBlob !== 'undefined') { - window.navigator.msSaveBlob(blob, filename) - } else { - const blobURL = window.URL.createObjectURL(blob) - const tempLink = document.createElement('a') - tempLink.style.display = 'none' - tempLink.href = blobURL - tempLink.setAttribute('download', filename) - if (typeof tempLink.download === 'undefined') { - tempLink.setAttribute('target', '_blank') - } - document.body.appendChild(tempLink) - tempLink.click() - document.body.removeChild(tempLink) - window.URL.revokeObjectURL(blobURL) - } + const blob = new Blob([data], { type: mime || 'application/octet-stream' }) + if (typeof window.navigator.msSaveBlob !== 'undefined') { + window.navigator.msSaveBlob(blob, filename) + } else { + const blobURL = window.URL.createObjectURL(blob) + const tempLink = document.createElement('a') + tempLink.style.display = 'none' + tempLink.href = blobURL + tempLink.setAttribute('download', filename) + if (typeof tempLink.download === 'undefined') { + tempLink.setAttribute('target', '_blank') + } + document.body.appendChild(tempLink) + tempLink.click() + document.body.removeChild(tempLink) + window.URL.revokeObjectURL(blobURL) + } } diff --git a/packages/graphql-playground-react/src/state/workspace/reducers.ts b/packages/graphql-playground-react/src/state/workspace/reducers.ts index 1e9700194..58b9e0079 100644 --- a/packages/graphql-playground-react/src/state/workspace/reducers.ts +++ b/packages/graphql-playground-react/src/state/workspace/reducers.ts @@ -50,6 +50,7 @@ export const defaultSettings: ISettings = { 'prettier.printWidth': 80, 'request.credentials': 'omit', 'tracing.hideTracingResponse': true, + 'schema.disableComments': true, } // tslint:disable-next-line:max-classes-per-file diff --git a/packages/graphql-playground-react/src/styled/theme.ts b/packages/graphql-playground-react/src/styled/theme.ts index e63896538..aba1b68b2 100644 --- a/packages/graphql-playground-react/src/styled/theme.ts +++ b/packages/graphql-playground-react/src/styled/theme.ts @@ -2,340 +2,337 @@ import { ISettings } from '../types' import { defaultSettings } from '../state/workspace/reducers' export interface Colours { - green: string - darkBlue: string - darkBlue50: string - darkBlue60: string - darkBlue80: string - darkBlue30: string - darkBlue20: string - darkBlue10: string - darkerBlue: string - darkestBlue: string - white80: string - white70: string - white60: string - white30: string - white20: string - white10: string - white: string - black02: string - black04: string - black07: string - black10: string - black30: string - black40: string - black50: string - paleText: string - paleGrey: string - red: string - blue: string - orange: string - purple: string - lightGrey: string - lighterGrey: string - // New dynamic styles - text: string - textInactive: string + green: string + darkBlue: string + darkBlue50: string + darkBlue60: string + darkBlue80: string + darkBlue30: string + darkBlue20: string + darkBlue10: string + darkerBlue: string + darkestBlue: string + white80: string + white70: string + white60: string + white30: string + white20: string + white10: string + white: string + black02: string + black04: string + black07: string + black10: string + black30: string + black40: string + black50: string + paleText: string + paleGrey: string + red: string + blue: string + orange: string + purple: string + lightGrey: string + lighterGrey: string + // New dynamic styles + text: string + textInactive: string } export interface EditorColours { - property: string - comment: string - punctuation: string - keyword: string - def: string - qualifier: string - attribute: string - number: string - string: string - builtin: string - string2: string - variable: string - meta: string - atom: string - ws: string - selection: string - cursorColor: string + property: string + comment: string + punctuation: string + keyword: string + def: string + qualifier: string + attribute: string + number: string + string: string + builtin: string + string2: string + variable: string + meta: string + atom: string + ws: string + selection: string + cursorColor: string - text: string - textInactive: string - background: string - sidebarTop: string - sidebar: string - sidebarBottom: string - sidebarItemActive: string - sidebarItemSide: string - sidebarItemSessions: string - tab: string - tabInactive: string - tabText: string - navigationBar: string - navigationBarText: string - editorBackground: string - resultBackground: string - leftDrawerBackground: string - rightDrawerBackground: string - drawerText: string - drawerTextInactive: string - executeButton: string - executeButtonBorder: string - executeButtonHover: string - executeButtonSubscription: string - executeButtonSubscriptionHover: string - icon: string - iconHover: string - button: string - buttonHover: string - buttonText: string - buttonWorkspace: string - buttonWorkspaceHover: string - buttonWorkspaceText: string - circle: string + text: string + textInactive: string + background: string + sidebarTop: string + sidebar: string + sidebarBottom: string + sidebarItemActive: string + sidebarItemSide: string + sidebarItemSessions: string + tab: string + tabInactive: string + tabText: string + navigationBar: string + navigationBarText: string + editorBackground: string + resultBackground: string + leftDrawerBackground: string + rightDrawerBackground: string + drawerText: string + drawerTextInactive: string + executeButton: string + executeButtonBorder: string + executeButtonHover: string + executeButtonSubscription: string + executeButtonSubscriptionHover: string + icon: string + iconHover: string + button: string + buttonHover: string + buttonText: string + buttonWorkspace: string + buttonWorkspaceHover: string + buttonWorkspaceText: string + circle: string } export const darkColours: Colours = { - green: '#27ae60', - darkBlue: 'rgb(23, 42, 58)', - darkBlue50: 'rgba(23, 42, 58, 0.5)', - darkBlue80: 'rgba(23, 42, 58, 0.8)', - darkBlue60: 'rgba(23, 42, 58, 0.6)', - darkBlue30: 'rgba(23, 42, 58, 0.3)', - darkBlue20: 'rgba(23, 42, 58, 0.2)', - darkBlue10: 'rgba(23, 42, 58, 0.1)', - darkerBlue: '#0F202D', - darkestBlue: 'rgb(11,20,28)', - white10: 'rgba(255, 255, 255, 0.1)', - white20: 'rgba(255, 255, 255, 0.2)', - white30: 'rgba(255, 255, 255, 0.3)', - white60: 'rgba(255, 255, 255, 0.6)', - white70: 'rgba(255, 255, 255, 0.7)', - white80: 'rgba(255, 255, 255, 0.8)', - white: 'rgba(255, 255, 255, 1)', - black02: 'rgba(0, 0, 0, 0.02)', - black07: 'rgba(0, 0, 0, 0.07)', - black04: 'rgba(0, 0, 0, 0.04)', - black10: 'rgba(0, 0, 0, 0.1)', - black30: 'rgba(0, 0, 0, 0.3)', - black40: 'rgba(0, 0, 0, 0.4)', - black50: 'rgba(0, 0, 0, 0.5)', - red: '#f25c54', - orange: 'rgba(241, 143, 1, 1)', - blue: 'rgba(42, 126, 210, 1)', - purple: 'rgb(164, 3, 111)', + green: '#27ae60', + darkBlue: 'rgb(23, 42, 58)', + darkBlue50: 'rgba(23, 42, 58, 0.5)', + darkBlue80: 'rgba(23, 42, 58, 0.8)', + darkBlue60: 'rgba(23, 42, 58, 0.6)', + darkBlue30: 'rgba(23, 42, 58, 0.3)', + darkBlue20: 'rgba(23, 42, 58, 0.2)', + darkBlue10: 'rgba(23, 42, 58, 0.1)', + darkerBlue: '#0F202D', + darkestBlue: 'rgb(11,20,28)', + white10: 'rgba(255, 255, 255, 0.1)', + white20: 'rgba(255, 255, 255, 0.2)', + white30: 'rgba(255, 255, 255, 0.3)', + white60: 'rgba(255, 255, 255, 0.6)', + white70: 'rgba(255, 255, 255, 0.7)', + white80: 'rgba(255, 255, 255, 0.8)', + white: 'rgba(255, 255, 255, 1)', + black02: 'rgba(0, 0, 0, 0.02)', + black07: 'rgba(0, 0, 0, 0.07)', + black04: 'rgba(0, 0, 0, 0.04)', + black10: 'rgba(0, 0, 0, 0.1)', + black30: 'rgba(0, 0, 0, 0.3)', + black40: 'rgba(0, 0, 0, 0.4)', + black50: 'rgba(0, 0, 0, 0.5)', + red: '#f25c54', + orange: 'rgba(241, 143, 1, 1)', + blue: 'rgba(42, 126, 210, 1)', + purple: 'rgb(164, 3, 111)', - paleText: 'rgba(0, 0, 0, 0.5)', - paleGrey: '#f3f4f4', // use for bgs, borders, etc - lightGrey: '#eeeff0', - lighterGrey: '#f6f7f7', - // New colors - text: 'rgba(255,255,255,0.6)', - textInactive: '#555e66', + paleText: 'rgba(0, 0, 0, 0.5)', + paleGrey: '#f3f4f4', // use for bgs, borders, etc + lightGrey: '#eeeff0', + lighterGrey: '#f6f7f7', + // New colors + text: 'rgba(255,255,255,0.6)', + textInactive: '#555e66', } export const lightColours: Colours = { - green: '#27ae60', - darkBlue: 'rgb(23, 42, 58)', - darkBlue50: 'rgba(23, 42, 58, 0.5)', - darkBlue80: 'rgba(23, 42, 58, 0.8)', - darkBlue60: 'rgba(23, 42, 58, 0.6)', - darkBlue30: 'rgba(23, 42, 58, 0.3)', - darkBlue20: 'rgba(23, 42, 58, 0.2)', - darkBlue10: 'rgba(23, 42, 58, 0.1)', - darkerBlue: '#0F202D', - darkestBlue: 'rgb(11,20,28)', - white10: 'rgba(255, 255, 255, 0.1)', - white20: 'rgba(255, 255, 255, 0.2)', - white30: 'rgba(255, 255, 255, 0.3)', - white60: 'rgba(255, 255, 255, 0.6)', - white70: 'rgba(255, 255, 255, 0.7)', - white80: 'rgba(255, 255, 255, 0.8)', - white: 'rgba(255, 255, 255, 1)', - black02: 'rgba(0, 0, 0, 0.02)', - black04: 'rgba(0, 0, 0, 0.04)', - black10: 'rgba(0, 0, 0, 0.1)', - black07: 'rgba(0, 0, 0, 0.07)', - black30: 'rgba(0, 0, 0, 0.3)', - black40: 'rgba(0, 0, 0, 0.4)', - black50: 'rgba(0, 0, 0, 0.5)', - red: '#f25c54', - orange: 'rgba(241, 143, 1, 1)', - blue: 'rgba(42, 126, 210, 1)', - purple: 'rgb(164, 3, 111)', - - paleText: 'rgba(0, 0, 0, 0.5)', - paleGrey: '#f3f4f4', // use for bgs, borders, etc - lightGrey: '#eeeff0', - lighterGrey: '#f6f7f7', - // New colors - text: 'rgba(0,0,0,.7)', - textInactive: 'rgba(0,0,0,.3)', + green: '#27ae60', + darkBlue: 'rgb(23, 42, 58)', + darkBlue50: 'rgba(23, 42, 58, 0.5)', + darkBlue80: 'rgba(23, 42, 58, 0.8)', + darkBlue60: 'rgba(23, 42, 58, 0.6)', + darkBlue30: 'rgba(23, 42, 58, 0.3)', + darkBlue20: 'rgba(23, 42, 58, 0.2)', + darkBlue10: 'rgba(23, 42, 58, 0.1)', + darkerBlue: '#0F202D', + darkestBlue: 'rgb(11,20,28)', + white10: 'rgba(255, 255, 255, 0.1)', + white20: 'rgba(255, 255, 255, 0.2)', + white30: 'rgba(255, 255, 255, 0.3)', + white60: 'rgba(255, 255, 255, 0.6)', + white70: 'rgba(255, 255, 255, 0.7)', + white80: 'rgba(255, 255, 255, 0.8)', + white: 'rgba(255, 255, 255, 1)', + black02: 'rgba(0, 0, 0, 0.02)', + black04: 'rgba(0, 0, 0, 0.04)', + black10: 'rgba(0, 0, 0, 0.1)', + black07: 'rgba(0, 0, 0, 0.07)', + black30: 'rgba(0, 0, 0, 0.3)', + black40: 'rgba(0, 0, 0, 0.4)', + black50: 'rgba(0, 0, 0, 0.5)', + red: '#f25c54', + orange: 'rgba(241, 143, 1, 1)', + blue: 'rgba(42, 126, 210, 1)', + purple: 'rgb(164, 3, 111)', + paleText: 'rgba(0, 0, 0, 0.5)', + paleGrey: '#f3f4f4', // use for bgs, borders, etc + lightGrey: '#eeeff0', + lighterGrey: '#f6f7f7', + // New colors + text: 'rgba(0,0,0,.7)', + textInactive: 'rgba(0,0,0,.3)', } export const darkEditorColours: EditorColours = { - property: 'rgb(41, 185, 115)', - comment: 'rgba(255, 255, 255, 0.3)', - punctuation: 'rgba(255, 255, 255, 0.4)', - keyword: 'rgb(42, 126, 211)', - def: 'rgb(56, 189, 193)', - qualifier: '#1c92a9', - attribute: 'rgb(247, 116, 102)', - number: '#2882f9', - string: '#d64292', - builtin: '#d47509', - string2: '#0b7fc7', - variable: 'rgb(181, 34, 130)', - meta: '#b33086', - atom: 'rgb(249, 233, 34)', - ws: 'rgba(255, 255, 255, 0.4)', - selection: 'rgba(255, 255, 255, 0.1)', - cursorColor: 'rgba(255, 255, 255, 0.4)', - - text: '#fff', - textInactive: 'rgba(255, 255, 255, 0.6)', - background: '#09141c', - sidebarTop: '#0f202d', - sidebar: '#172b3a', - sidebarBottom: '#172b3a', - sidebarItemActive: 'rgb(23, 42, 58)', - sidebarItemSide: '#27ae60', - sidebarItemSessions: 'rgba(255, 255, 255, 0.05)', - tab: '#172b3a', - tabInactive: '#0f202d', - tabText: '#fff', - navigationBar: '#172b3a', - navigationBarText: 'rgba(255, 255, 255, 0.6)', - editorBackground: '#0f202d', - resultBackground: '#172b3a', - leftDrawerBackground: '#0b1924', - rightDrawerBackground: '#0b1924', - drawerText: 'rgba(255,255,255,0.6)', - drawerTextInactive: '#555e66', - executeButton: 'rgb(185, 191, 196)', - executeButtonBorder: 'rgb(11, 20, 28)', - executeButtonHover: 'rgb(195, 201, 206)', - executeButtonSubscription: '#f25c54', - executeButtonSubscriptionHover: '#f36c65', - icon: 'rgb(74, 85, 95)', - iconHover: 'rgba(255, 255, 255, 0.6)', - button: '#0F202D', - buttonHover: '#122535', - buttonText: 'rgba(255,255,255,0.6)', - buttonWorkspace: '#b9bfc4', - buttonWorkspaceHover: '#a4acb2', - buttonWorkspaceText: 'rgb(23, 42, 58)', - circle: 'rgba(255, 255, 255, 0.4)', + property: 'rgb(41, 185, 115)', + comment: 'rgba(255, 255, 255, 0.3)', + punctuation: 'rgba(255, 255, 255, 0.4)', + keyword: 'rgb(42, 126, 211)', + def: 'rgb(56, 189, 193)', + qualifier: '#1c92a9', + attribute: 'rgb(247, 116, 102)', + number: '#2882f9', + string: '#d64292', + builtin: '#d47509', + string2: '#0b7fc7', + variable: 'rgb(181, 34, 130)', + meta: '#b33086', + atom: 'rgb(249, 233, 34)', + ws: 'rgba(255, 255, 255, 0.4)', + selection: 'rgba(255, 255, 255, 0.1)', + cursorColor: 'rgba(255, 255, 255, 0.4)', + text: '#fff', + textInactive: 'rgba(255, 255, 255, 0.6)', + background: '#09141c', + sidebarTop: '#0f202d', + sidebar: '#172b3a', + sidebarBottom: '#172b3a', + sidebarItemActive: 'rgb(23, 42, 58)', + sidebarItemSide: '#27ae60', + sidebarItemSessions: 'rgba(255, 255, 255, 0.05)', + tab: '#172b3a', + tabInactive: '#0f202d', + tabText: '#fff', + navigationBar: '#172b3a', + navigationBarText: 'rgba(255, 255, 255, 0.6)', + editorBackground: '#0f202d', + resultBackground: '#172b3a', + leftDrawerBackground: '#0b1924', + rightDrawerBackground: '#0b1924', + drawerText: 'rgba(255,255,255,0.6)', + drawerTextInactive: '#555e66', + executeButton: 'rgb(185, 191, 196)', + executeButtonBorder: 'rgb(11, 20, 28)', + executeButtonHover: 'rgb(195, 201, 206)', + executeButtonSubscription: '#f25c54', + executeButtonSubscriptionHover: '#f36c65', + icon: 'rgb(74, 85, 95)', + iconHover: 'rgba(255, 255, 255, 0.6)', + button: '#0F202D', + buttonHover: '#122535', + buttonText: 'rgba(255,255,255,0.6)', + buttonWorkspace: '#b9bfc4', + buttonWorkspaceHover: '#a4acb2', + buttonWorkspaceText: 'rgb(23, 42, 58)', + circle: 'rgba(255, 255, 255, 0.4)', } export const lightEditorColours: EditorColours = { - property: '#328c8c', // - comment: 'rgba(0, 0, 0, 0.3)', // - punctuation: 'rgba(23,42,58,.8)', // - keyword: '#366b6b', // - def: 'rgb(56, 189, 193)', // - qualifier: '#1c92a9', // - attribute: '#b56531', // - number: '#1f6ed6;', // - string: '#d64292', // - builtin: '#d47509', // - string2: '#0b7fc7', // - variable: 'rgb(236, 95, 103)', // - meta: '#b33086', // - atom: 'rgb(245, 160, 0)', // - ws: 'rgba(23, 42, 58, 0.8)', // - selection: '#d1e9fd', - cursorColor: 'rgba(0, 0, 0, 0.4)', - - text: 'rgba(0, 0, 0, 0.7)', - textInactive: 'rgba(0, 0, 0, 0.3)', - background: '#dbdee0', - sidebarTop: '#eeeff0', - sidebar: '#eeeff0', - sidebarBottom: '#f6f7f7', - sidebarItemActive: '#f6f7f7', - sidebarItemSide: '#27ae60', - sidebarItemSessions: '#dbdee0', - tab: '#eeeff0', - tabInactive: '#e7eaec', - tabText: 'rgba(23, 42, 58, .8)', - navigationBar: '#eeeff0', - navigationBarText: 'rgba(23, 42, 58, 0.8)', - editorBackground: '#f6f7f7', - resultBackground: '#eeeff0', - leftDrawerBackground: '#e9eaea', - rightDrawerBackground: '#e5e7e7', - drawerText: 'rgba(0, 0, 0, 0.7)', - drawerTextInactive: 'rgba(0, 0, 0, 0.3)', - executeButton: 'rgb(115, 127, 136)', - executeButtonBorder: '#eeeff0', - executeButtonHover: '', - executeButtonSubscription: '#f25c54', - executeButtonSubscriptionHover: '#f36c65', - icon: 'rgb(194, 200, 203)', - iconHover: 'rgba(23, 42, 58, 0.6)', - button: '#d8dbde', - buttonHover: 'rgba(20, 37, 51, 0.2)', - buttonText: 'rgba(23, 42, 58, 0.8)', - buttonWorkspace: 'rgb(185, 191, 196)', - buttonWorkspaceHover: 'rgb(157, 166, 173)', - buttonWorkspaceText: 'rgb(238, 239, 240)', - circle: 'rgba(23,42,58,.4)', + property: '#328c8c', // + comment: 'rgba(0, 0, 0, 0.3)', // + punctuation: 'rgba(23,42,58,.8)', // + keyword: '#366b6b', // + def: 'rgb(56, 189, 193)', // + qualifier: '#1c92a9', // + attribute: '#b56531', // + number: '#1f6ed6;', // + string: '#d64292', // + builtin: '#d47509', // + string2: '#0b7fc7', // + variable: 'rgb(236, 95, 103)', // + meta: '#b33086', // + atom: 'rgb(245, 160, 0)', // + ws: 'rgba(23, 42, 58, 0.8)', // + selection: '#d1e9fd', + cursorColor: 'rgba(0, 0, 0, 0.4)', + text: 'rgba(0, 0, 0, 0.7)', + textInactive: 'rgba(0, 0, 0, 0.3)', + background: '#dbdee0', + sidebarTop: '#eeeff0', + sidebar: '#eeeff0', + sidebarBottom: '#f6f7f7', + sidebarItemActive: '#f6f7f7', + sidebarItemSide: '#27ae60', + sidebarItemSessions: '#dbdee0', + tab: '#eeeff0', + tabInactive: '#e7eaec', + tabText: 'rgba(23, 42, 58, .8)', + navigationBar: '#eeeff0', + navigationBarText: 'rgba(23, 42, 58, 0.8)', + editorBackground: '#f6f7f7', + resultBackground: '#eeeff0', + leftDrawerBackground: '#e9eaea', + rightDrawerBackground: '#e5e7e7', + drawerText: 'rgba(0, 0, 0, 0.7)', + drawerTextInactive: 'rgba(0, 0, 0, 0.3)', + executeButton: 'rgb(115, 127, 136)', + executeButtonBorder: '#eeeff0', + executeButtonHover: '', + executeButtonSubscription: '#f25c54', + executeButtonSubscriptionHover: '#f36c65', + icon: 'rgb(194, 200, 203)', + iconHover: 'rgba(23, 42, 58, 0.6)', + button: '#d8dbde', + buttonHover: 'rgba(20, 37, 51, 0.2)', + buttonText: 'rgba(23, 42, 58, 0.8)', + buttonWorkspace: 'rgb(185, 191, 196)', + buttonWorkspaceHover: 'rgb(157, 166, 173)', + buttonWorkspaceText: 'rgb(238, 239, 240)', + circle: 'rgba(23,42,58,.4)', } export interface Sizes { - small6: string - small10: string - small12: string - small16: string - medium25: string - smallRadius: string - fontLight: string - fontSemiBold: string - fontTiny: string - fontSmall: string - fontMedium: string + small6: string + small10: string + small12: string + small16: string + medium25: string + smallRadius: string + fontLight: string + fontSemiBold: string + fontTiny: string + fontSmall: string + fontMedium: string } export const sizes: Sizes = { - small6: '6px', - small10: '10px', - small12: '12px', - small16: '16px', - medium25: '25px', + small6: '6px', + small10: '10px', + small12: '12px', + small16: '16px', + medium25: '25px', - // font weights - fontLight: '300', - fontSemiBold: '600', + // font weights + fontLight: '300', + fontSemiBold: '600', - // font sizes - fontTiny: '12px', - fontSmall: '14px', - fontMedium: '20px', + // font sizes + fontTiny: '12px', + fontSmall: '14px', + fontMedium: '20px', - // others - smallRadius: '2px', + // others + smallRadius: '2px', } export interface Shorthands { - [x: string]: any + [x: string]: any } export const shorthands: Shorthands = {} export interface ThemeInterface { - mode: 'light' | 'dark' - colours: Colours - sizes: Sizes - shorthands: Shorthands - editorColours: EditorColours - settings: ISettings + mode: 'light' | 'dark' + colours: Colours + sizes: Sizes + shorthands: Shorthands + editorColours: EditorColours + settings: ISettings } export const theme: any = { - mode: 'dark', - colours: darkColours, - sizes, - shorthands, - editorColours: darkEditorColours, - settings: defaultSettings, + mode: 'dark', + colours: darkColours, + sizes, + shorthands, + editorColours: darkEditorColours, + settings: defaultSettings, } diff --git a/packages/graphql-playground-react/src/types.ts b/packages/graphql-playground-react/src/types.ts index 20b83d923..2ae56f7ba 100644 --- a/packages/graphql-playground-react/src/types.ts +++ b/packages/graphql-playground-react/src/types.ts @@ -26,4 +26,5 @@ export interface ISettings { ['prettier.printWidth']: number ['tracing.hideTracingResponse']: boolean ['request.credentials']: 'omit' | 'include' | 'same-origin' + ['schema.disableComments']: boolean } From cd5f5d867bc38a2b8b0bb6735af0fad4fd3e5415 Mon Sep 17 00:00:00 2001 From: rajinwonderland Date: Mon, 3 Dec 2018 16:28:08 -0800 Subject: [PATCH 03/10] Merged changes from disableComments branch --- .../Playground/DocExplorer/ColumnDoc.tsx | 24 +- .../Playground/DocExplorer/DocsStyles.tsx | 16 +- .../Playground/DocExplorer/GraphDocs.tsx | 452 +++--- .../Playground/ExplorerTabs/SideTab.tsx | 82 +- .../Playground/ExplorerTabs/SideTabs.tsx | 648 ++++----- .../components/Playground/GraphQLEditor.tsx | 1260 ++++++++--------- .../SchemaExplorer/SDLTypes/SDLDocType.tsx | 118 +- .../SchemaExplorer/SDLTypes/SDLFieldDoc.tsx | 66 +- .../SchemaExplorer/SDLTypes/SDLStyles.tsx | 262 ++-- .../SchemaExplorer/SDLTypes/SDLType.tsx | 176 +-- .../SchemaExplorer/SDLTypes/SDLUnionType.tsx | 22 +- .../components/Playground/TopBar/TopBar.tsx | 360 ++--- .../Playground/util/immutableMemoize.ts | 16 +- .../src/components/util.ts | 50 +- .../src/localDevIndex.tsx | 58 +- .../src/state/docs/actions.ts | 36 +- .../graphql-playground-react/src/utils.ts | 34 +- 17 files changed, 1840 insertions(+), 1840 deletions(-) diff --git a/packages/graphql-playground-react/src/components/Playground/DocExplorer/ColumnDoc.tsx b/packages/graphql-playground-react/src/components/Playground/DocExplorer/ColumnDoc.tsx index ef4e16f87..af03eb2f5 100644 --- a/packages/graphql-playground-react/src/components/Playground/DocExplorer/ColumnDoc.tsx +++ b/packages/graphql-playground-react/src/components/Playground/DocExplorer/ColumnDoc.tsx @@ -3,27 +3,27 @@ import { columnWidth } from '../../../constants' import { styled } from '../../../styled' export interface Props { - children: any - overflow?: boolean - width?: number + children: any + overflow?: boolean + width?: number } const ColumnDoc = ({ - children, - overflow = true, - width = columnWidth, + children, + overflow = true, + width = columnWidth, }: Props) => { - return ( - - {children} - - ) + return ( + + {children} + + ) } export default ColumnDoc interface ColumnProps { - overflow: boolean + overflow: boolean } const Column = styled('div')` diff --git a/packages/graphql-playground-react/src/components/Playground/DocExplorer/DocsStyles.tsx b/packages/graphql-playground-react/src/components/Playground/DocExplorer/DocsStyles.tsx index f983751e3..f4d05071c 100644 --- a/packages/graphql-playground-react/src/components/Playground/DocExplorer/DocsStyles.tsx +++ b/packages/graphql-playground-react/src/components/Playground/DocExplorer/DocsStyles.tsx @@ -2,13 +2,13 @@ import * as React from 'react' import { styled } from '../../../styled' const Title = styled.div` - color: rgba(0, 0, 0, 0.3); - cursor: default; - font-size: 14px; - font-weight: 600; - text-transform: uppercase !important; - letter-spacing: 1px; - padding: 16px; - user-select: none; + color: rgba(0, 0, 0, 0.3); + cursor: default; + font-size: 14px; + font-weight: 600; + text-transform: uppercase !important; + letter-spacing: 1px; + padding: 16px; + user-select: none; ` export const CategoryTitle = ({ children }) => {children} diff --git a/packages/graphql-playground-react/src/components/Playground/DocExplorer/GraphDocs.tsx b/packages/graphql-playground-react/src/components/Playground/DocExplorer/GraphDocs.tsx index bc2083494..99e0c0eff 100644 --- a/packages/graphql-playground-react/src/components/Playground/DocExplorer/GraphDocs.tsx +++ b/packages/graphql-playground-react/src/components/Playground/DocExplorer/GraphDocs.tsx @@ -5,20 +5,20 @@ import * as keycode from 'keycode' import FieldDoc from './FieldDoc' import ColumnDoc from './ColumnDoc' import { - addStack, - toggleDocs, - changeWidthDocs, - changeKeyMove, - setDocsVisible, + addStack, + toggleDocs, + changeWidthDocs, + changeKeyMove, + setDocsVisible, } from '../../../state/docs/actions' import Spinner from '../../Spinner' import { columnWidth } from '../../../constants' import RootColumn from './RootColumn' import { - serialize, - getElementRoot, - serializeRoot, - getElement, + serialize, + getElementRoot, + serializeRoot, + getElement, } from '../util/stack' import { getSessionDocs } from '../../../state/docs/selectors' import { getSelectedSessionIdFromRoot } from '../../../state/sessions/selectors' @@ -28,253 +28,253 @@ import { ErrorContainer } from './ErrorContainer' import { styled } from '../../../styled' interface StateFromProps { - docs: { - navStack: any[] - docsOpen: boolean - docsWidth: number - keyMove: boolean - } + docs: { + navStack: any[] + docsOpen: boolean + docsWidth: number + keyMove: boolean + } } interface DispatchFromProps { - addStack: (sessionId: string, field: any, x: number, y: number) => any - toggleDocs: (sessionId: string) => any - setDocsVisible: (sessionId: string, open: boolean) => any - changeWidthDocs: (sessionId: string, width: number) => any - changeKeyMove: (sessionId: string, move: boolean) => any + addStack: (sessionId: string, field: any, x: number, y: number) => any + toggleDocs: (sessionId: string) => any + setDocsVisible: (sessionId: string, open: boolean) => any + changeWidthDocs: (sessionId: string, width: number) => any + changeKeyMove: (sessionId: string, move: boolean) => any } export interface State { - searchValue: string - widthMap: any + searchValue: string + widthMap: any } class GraphDocs extends React.Component< - SideTabContentProps & StateFromProps & DispatchFromProps, - State + SideTabContentProps & StateFromProps & DispatchFromProps, + State > { - ref - // private refDocExplorer: any; + ref + // private refDocExplorer: any; - constructor(props) { - super(props) - this.state = { - searchValue: '', - widthMap: {}, - } - ;(window as any).d = this - } + constructor(props) { + super(props) + this.state = { + searchValue: '', + widthMap: {}, + } + ;(window as any).d = this + } - componentWillReceiveProps(nextProps: SideTabContentProps & StateFromProps) { - // If user use default column size % columnWidth - // Make the column follow the clicks - if ( - this.props.docs.navStack.length !== nextProps.docs.navStack.length || - this.props.docs.navStack.slice(-1)[0] !== - nextProps.docs.navStack.slice(-1)[0] || - (!this.props.schema && nextProps.schema) - ) { - this.setWidth(nextProps) - } - } + componentWillReceiveProps(nextProps: SideTabContentProps & StateFromProps) { + // If user use default column size % columnWidth + // Make the column follow the clicks + if ( + this.props.docs.navStack.length !== nextProps.docs.navStack.length || + this.props.docs.navStack.slice(-1)[0] !== + nextProps.docs.navStack.slice(-1)[0] || + (!this.props.schema && nextProps.schema) + ) { + this.setWidth(nextProps) + } + } - setWidth(props: any = this.props) { - this.props.setWidth(props) - } + setWidth(props: any = this.props) { + this.props.setWidth(props) + } - getWidth(props: any = this.props) { - const rootWidth = this.state.widthMap.root || columnWidth - const stackWidths = props.docs.navStack.map( - (stack) => this.state.widthMap[stack.field.path] || columnWidth, - ) + getWidth(props: any = this.props) { + const rootWidth = this.state.widthMap.root || columnWidth + const stackWidths = props.docs.navStack.map( + stack => this.state.widthMap[stack.field.path] || columnWidth, + ) - return [rootWidth].concat(stackWidths).reduce((acc, curr) => acc + curr, 0) - } + return [rootWidth].concat(stackWidths).reduce((acc, curr) => acc + curr, 0) + } - componentDidMount() { - this.setWidth() - } + componentDidMount() { + this.setWidth() + } - render() { - const { navStack } = this.props.docs - const { schema } = this.props - let emptySchema - if (schema === undefined) { - // Schema is undefined when it is being loaded via introspection. - emptySchema = - } else if (schema === null) { - // Schema is null when it explicitly does not exist, typically due to - // an error during introspection. - emptySchema = {'No Schema Available'} - } + render() { + const { navStack } = this.props.docs + const { schema } = this.props + let emptySchema + if (schema === undefined) { + // Schema is undefined when it is being loaded via introspection. + emptySchema = + } else if (schema === null) { + // Schema is null when it explicitly does not exist, typically due to + // an error during introspection. + emptySchema = {'No Schema Available'} + } - return ( - - {emptySchema && {emptySchema}} - {!emptySchema && - schema && ( - - )} - {navStack.map((stack, index) => ( - - - - ))} - - ) - } + return ( + + {emptySchema && {emptySchema}} + {!emptySchema && + schema && ( + + )} + {navStack.map((stack, index) => ( + + + + ))} + + ) + } - setRef = (ref) => { - this.ref = ref - } + setRef = ref => { + this.ref = ref + } - public showDocFromType = (type) => { - this.props.addStack(this.props.sessionId, type, 0, 0) - } + public showDocFromType = type => { + this.props.addStack(this.props.sessionId, type, 0, 0) + } - private handleSearch = (value: string) => { - this.setState({ searchValue: value }) - } + private handleSearch = (value: string) => { + this.setState({ searchValue: value }) + } - private handleKeyDown = (e) => { - // we don't want to interfere with inputs - if ( - e.target instanceof HTMLInputElement || - e.metaKey || - e.shiftKey || - e.altKey || - e.ctrlKey - ) { - return - } - e.preventDefault() - this.props.changeKeyMove(this.props.sessionId, true) - const lastNavStack = - this.props.docs.navStack.length > 0 && - this.props.docs.navStack[this.props.docs.navStack.length - 1] - const beforeLastNavStack = - this.props.docs.navStack.length > 0 && - this.props.docs.navStack[this.props.docs.navStack.length - 2] - const keyPressed = keycode(e) - switch (keyPressed) { - case 'esc': - this.props.setDocsVisible(this.props.sessionId, false) - break - case 'left': - if (beforeLastNavStack) { - this.props.addStack( - this.props.sessionId, - beforeLastNavStack.field, - beforeLastNavStack.x, - beforeLastNavStack.y, - ) - } - break - case 'right': - if (lastNavStack) { - const obj = serialize(this.props.schema, lastNavStack.field) - const firstElement = getElement(obj, 0) - if (firstElement) { - this.props.addStack( - this.props.sessionId, - firstElement, - lastNavStack.x + 1, - 0, - ) - } - } else { - const obj = serializeRoot(this.props.schema) - const element = getElementRoot(obj, 0) - if (element) { - this.props.addStack(this.props.sessionId, element, 0, 0) - } - } - break - case 'up': - case 'down': - if (beforeLastNavStack) { - const obj = serialize(this.props.schema, beforeLastNavStack.field) - const element = getElement( - obj, - keyPressed === 'up' ? lastNavStack.y - 1 : lastNavStack.y + 1, - ) - if (element) { - this.props.addStack( - this.props.sessionId, - element, - lastNavStack.x, - keyPressed === 'up' ? lastNavStack.y - 1 : lastNavStack.y + 1, - ) - } - } else { - const obj = serializeRoot(this.props.schema) - const y = lastNavStack ? lastNavStack.y : 0 - const element = getElementRoot( - obj, - keyPressed === 'up' ? y - 1 : y + 1, - ) - if (element) { - this.props.addStack( - this.props.sessionId, - element, - 0, - keyPressed === 'up' ? y - 1 : y + 1, - ) - } - } - break - } - } + private handleKeyDown = e => { + // we don't want to interfere with inputs + if ( + e.target instanceof HTMLInputElement || + e.metaKey || + e.shiftKey || + e.altKey || + e.ctrlKey + ) { + return + } + e.preventDefault() + this.props.changeKeyMove(this.props.sessionId, true) + const lastNavStack = + this.props.docs.navStack.length > 0 && + this.props.docs.navStack[this.props.docs.navStack.length - 1] + const beforeLastNavStack = + this.props.docs.navStack.length > 0 && + this.props.docs.navStack[this.props.docs.navStack.length - 2] + const keyPressed = keycode(e) + switch (keyPressed) { + case 'esc': + this.props.setDocsVisible(this.props.sessionId, false) + break + case 'left': + if (beforeLastNavStack) { + this.props.addStack( + this.props.sessionId, + beforeLastNavStack.field, + beforeLastNavStack.x, + beforeLastNavStack.y, + ) + } + break + case 'right': + if (lastNavStack) { + const obj = serialize(this.props.schema, lastNavStack.field) + const firstElement = getElement(obj, 0) + if (firstElement) { + this.props.addStack( + this.props.sessionId, + firstElement, + lastNavStack.x + 1, + 0, + ) + } + } else { + const obj = serializeRoot(this.props.schema) + const element = getElementRoot(obj, 0) + if (element) { + this.props.addStack(this.props.sessionId, element, 0, 0) + } + } + break + case 'up': + case 'down': + if (beforeLastNavStack) { + const obj = serialize(this.props.schema, beforeLastNavStack.field) + const element = getElement( + obj, + keyPressed === 'up' ? lastNavStack.y - 1 : lastNavStack.y + 1, + ) + if (element) { + this.props.addStack( + this.props.sessionId, + element, + lastNavStack.x, + keyPressed === 'up' ? lastNavStack.y - 1 : lastNavStack.y + 1, + ) + } + } else { + const obj = serializeRoot(this.props.schema) + const y = lastNavStack ? lastNavStack.y : 0 + const element = getElementRoot( + obj, + keyPressed === 'up' ? y - 1 : y + 1, + ) + if (element) { + this.props.addStack( + this.props.sessionId, + element, + 0, + keyPressed === 'up' ? y - 1 : y + 1, + ) + } + } + break + } + } } -const mapDispatchToProps = (dispatch) => - bindActionCreators( - { - addStack, - toggleDocs, - changeWidthDocs, - changeKeyMove, - setDocsVisible, - }, - dispatch, - ) +const mapDispatchToProps = dispatch => + bindActionCreators( + { + addStack, + toggleDocs, + changeWidthDocs, + changeKeyMove, + setDocsVisible, + }, + dispatch, + ) const mapStateToProps = createStructuredSelector({ - docs: getSessionDocs, - sessionId: getSelectedSessionIdFromRoot, + docs: getSessionDocs, + sessionId: getSelectedSessionIdFromRoot, }) export default connect( - mapStateToProps, - mapDispatchToProps, - null, - { withRef: true }, + mapStateToProps, + mapDispatchToProps, + null, + { withRef: true }, )(GraphDocs) const DocsExplorerContainer = styled.div` - display: flex; - position: relative; - height: 100%; - width: 100%; - overflow-x: auto; - overflow-y: hidden; - outline: none !important; + display: flex; + position: relative; + height: 100%; + width: 100%; + overflow-x: auto; + overflow-y: hidden; + outline: none !important; ` diff --git a/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx index df98a7564..299dad9c4 100644 --- a/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx +++ b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx @@ -2,54 +2,54 @@ import * as React from 'react' import { styled } from '../../../styled' export interface Props { - label: string - activeColor: string - children: any - active?: boolean - onClick?: () => any + label: string + activeColor: string + children: any + active?: boolean + onClick?: () => any } export default class SideTab extends React.PureComponent { - render() { - const { label, activeColor, active, onClick } = this.props - return ( - - {label} - - ) - } + render() { + const { label, activeColor, active, onClick } = this.props + return ( + + {label} + + ) + } } export interface TabProps { - active: boolean - activeColor: string + active: boolean + activeColor: string } const Tab = styled('div')` - z-index: ${(p) => (p.active ? 10 : 2)}; - padding: 6px 10px; - border-top-left-radius: 2px; - border-top-right-radius: 2px; - color: ${(p) => - p.theme.mode === 'dark' - ? p.theme.colours.white - : p.theme.colours[p.active ? 'white' : 'darkBlue']}; - background: ${(p) => - p.active && p.activeColor - ? p.theme.colours[p.activeColor] - : p.theme.mode === 'dark' - ? '#3D5866' - : '#DBDEE0'}; - box-shadow: -1px 1px 6px 0 rgba(0, 0, 0, 0.3); - text-transform: uppercase; - text-align: center; - font-weight: 600; - font-size: 12px; - line-height: 17px; - letter-spacing: 0.45px; - cursor: pointer; - transform: rotate(-90deg); - transform-origin: bottom left; - width: 60px; - margin-top: 65px; + z-index: ${p => (p.active ? 10 : 2)}; + padding: 6px 10px; + border-top-left-radius: 2px; + border-top-right-radius: 2px; + color: ${p => + p.theme.mode === 'dark' + ? p.theme.colours.white + : p.theme.colours[p.active ? 'white' : 'darkBlue']}; + background: ${p => + p.active && p.activeColor + ? p.theme.colours[p.activeColor] + : p.theme.mode === 'dark' + ? '#3D5866' + : '#DBDEE0'}; + box-shadow: -1px 1px 6px 0 rgba(0, 0, 0, 0.3); + text-transform: uppercase; + text-align: center; + font-weight: 600; + font-size: 12px; + line-height: 17px; + letter-spacing: 0.45px; + cursor: pointer; + transform: rotate(-90deg); + transform-origin: bottom left; + width: 60px; + margin-top: 65px; ` diff --git a/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx index 79e458b71..0b9feca8c 100644 --- a/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx +++ b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx @@ -4,11 +4,11 @@ import { connect } from 'react-redux' import * as keycode from 'keycode' import { getLeft } from 'graphiql/dist/utility/elementPosition' import { - addStack, - toggleDocs, - changeWidthDocs, - changeKeyMove, - setDocsVisible, + addStack, + toggleDocs, + changeWidthDocs, + changeKeyMove, + setDocsVisible, } from '../../../state/docs/actions' import { GraphQLSchema } from 'graphql' import { getSessionDocs } from '../../../state/docs/selectors' @@ -18,276 +18,276 @@ import { styled } from '../../../styled' import SideTab from './SideTab' interface StateFromProps { - docs: { - navStack: any[] - docsOpen: boolean - docsWidth: number - keyMove: boolean - activeTabIdx: number - } + docs: { + navStack: any[] + docsOpen: boolean + docsWidth: number + keyMove: boolean + activeTabIdx: number + } } interface DispatchFromProps { - addStack: (sessionId: string, field: any, x: number, y: number) => any - toggleDocs: (sessionId: string, activeTabIdx?: number | null) => any - setDocsVisible: (sessionId: string, open: boolean, idx?: number | null) => any - changeWidthDocs: (sessionId: string, width: number) => any - changeKeyMove: (sessionId: string, move: boolean) => any + addStack: (sessionId: string, field: any, x: number, y: number) => any + toggleDocs: (sessionId: string, activeTabIdx?: number | null) => any + setDocsVisible: (sessionId: string, open: boolean, idx?: number | null) => any + changeWidthDocs: (sessionId: string, width: number) => any + changeKeyMove: (sessionId: string, move: boolean) => any } export interface Props { - schema: GraphQLSchema - sessionId: string - children: Array> + schema: GraphQLSchema + sessionId: string + children: Array> } export interface SideTabContentProps { - schema: GraphQLSchema - sessionId: string - setWidth: (props: any) => any + schema: GraphQLSchema + sessionId: string + setWidth: (props: any) => any } export interface State { - searchValue: string - widthMap: any + searchValue: string + widthMap: any } class SideTabs extends React.Component< - Props & StateFromProps & DispatchFromProps, - State + Props & StateFromProps & DispatchFromProps, + State > { - ref - public activeContentComponent: any // later React.Component<...> - private refContentContainer: any - private clientX: number = 0 - private clientY: number = 0 - constructor(props) { - super(props) - ;(window as any).d = this - } - - setWidth = (props: any = this.props) => { - if (!this.activeContentComponent) { - return - } - if (!this.props.docs.docsOpen) { - return - } - requestAnimationFrame(() => { - const width = this.activeContentComponent.getWidth(props) - this.props.changeWidthDocs( - props.sessionId, - Math.min(width, window.innerWidth - 86), - ) - }) - } - setActiveContentRef = (ref) => { - if (ref) { - this.activeContentComponent = ref.getWrappedInstance() - } - } - - componentDidReceiveProps(prevProps) { - if (!prevProps.docs.activeTabIdx && this.props.docs.activeTabIdx) { - this.props.setDocsVisible( - this.props.sessionId, - true, - this.props.docs.activeTabIdx, - ) - } - if (prevProps.activeTabIdx && !this.props.docs.activeTabIdx) { - this.props.setDocsVisible(this.props.sessionId, false) - } - return this.setWidth() - } - - componentDidMount() { - if (!this.props.docs.activeTabIdx) { - this.props.setDocsVisible(this.props.sessionId, false) - } - return this.setWidth() - } - - render() { - const { docsOpen, docsWidth, activeTabIdx } = this.props.docs - const docsStyle = { width: docsOpen ? docsWidth : 0 } - const activeTab = - docsOpen && - (React.Children.toArray(this.props.children)[ - activeTabIdx - ] as React.ReactElement) - return ( - - - {React.Children.toArray(this.props.children).map( - (child: React.ReactElement, index) => { - return React.cloneElement(child, { - ...child.props, - key: index, - onClick: this.handleTabClick(index), - active: index === activeTabIdx, - }) - }, - )} - - - - - {activeTab && - React.cloneElement(activeTab.props.children, { - ...activeTab.props, - ref: this.setActiveContentRef, - setWidth: this.setWidth, - })} - - - ) - } - - setRef = (ref) => { - this.ref = ref - } - - public showDocFromType = (type) => { - this.props.setDocsVisible(this.props.sessionId, true, 0) - this.activeContentComponent.showDocFromType(type) - } - - private setContentContainerRef = (ref) => { - this.refContentContainer = ref - } - - private handleTabClick = (idx) => () => { - if (!this.props.docs.docsOpen && this.refContentContainer) { - this.refContentContainer.focus() - } - if (this.props.docs.activeTabIdx === idx) { - this.props.setDocsVisible(this.props.sessionId, false) - return this.setWidth() - } - if (this.props.docs.activeTabIdx !== idx) { - this.props.setDocsVisible( - this.props.sessionId, - false, - this.props.docs.activeTabIdx, - ) - this.props.setDocsVisible(this.props.sessionId, true, idx) - return this.setWidth() - } else { - this.props.setDocsVisible(this.props.sessionId, true, idx) - return this.setWidth() - } - } - - private handleKeyDown = (e) => { - // we don't want to interfere with inputs - if ( - e.target instanceof HTMLInputElement || - e.metaKey || - e.shiftKey || - e.altKey || - e.ctrlKey - ) { - return - } - const keyPressed = keycode(e) - switch (keyPressed) { - case 'esc': - this.props.changeKeyMove(this.props.sessionId, true) - e.preventDefault() - this.props.setDocsVisible(this.props.sessionId, false) - break - } - } - - private handleDocsResizeStart = (downEvent) => { - downEvent.preventDefault() - - const hadWidth = this.props.docs.docsWidth - const offset = downEvent.clientX - getLeft(downEvent.target) - - let onMouseMove: any = (moveEvent) => { - if (moveEvent.buttons === 0) { - return onMouseUp() - } - - const app = this.ref - const cursorPos = moveEvent.clientX - getLeft(app) - offset - const newSize = app.clientWidth - cursorPos - const maxSize = window.innerWidth - 50 - const docsSize = maxSize < newSize ? maxSize : newSize - - if (docsSize < 100) { - this.props.setDocsVisible( - this.props.sessionId, - false, - this.props.docs.activeTabIdx, - ) - } else { - this.props.setDocsVisible( - this.props.sessionId, - true, - this.props.docs.activeTabIdx, - ) - this.props.changeWidthDocs(this.props.sessionId, docsSize) - } - } - - let onMouseUp: any = () => { - if (!this.props.docs.docsOpen) { - this.props.changeWidthDocs(this.props.sessionId, hadWidth) - } - - document.removeEventListener('mousemove', onMouseMove) - document.removeEventListener('mouseup', onMouseUp) - onMouseMove = null - onMouseUp = null - } - - document.addEventListener('mousemove', onMouseMove) - document.addEventListener('mouseup', onMouseUp) - } - private handleMouseMove = (e) => { - this.clientX = e.clientX - this.clientY = e.clientY - if ( - this.props.docs.keyMove && - this.clientX !== e.clientX && - this.clientY !== e.clientY - ) { - this.props.changeKeyMove(this.props.sessionId, false) - } - } + ref + public activeContentComponent: any // later React.Component<...> + private refContentContainer: any + private clientX: number = 0 + private clientY: number = 0 + constructor(props) { + super(props) + ;(window as any).d = this + } + + setWidth = (props: any = this.props) => { + if (!this.activeContentComponent) { + return + } + if (!this.props.docs.docsOpen) { + return + } + requestAnimationFrame(() => { + const width = this.activeContentComponent.getWidth(props) + this.props.changeWidthDocs( + props.sessionId, + Math.min(width, window.innerWidth - 86), + ) + }) + } + setActiveContentRef = ref => { + if (ref) { + this.activeContentComponent = ref.getWrappedInstance() + } + } + + componentDidReceiveProps(prevProps) { + if (!prevProps.docs.activeTabIdx && this.props.docs.activeTabIdx) { + this.props.setDocsVisible( + this.props.sessionId, + true, + this.props.docs.activeTabIdx, + ) + } + if (prevProps.activeTabIdx && !this.props.docs.activeTabIdx) { + this.props.setDocsVisible(this.props.sessionId, false) + } + return this.setWidth() + } + + componentDidMount() { + if (!this.props.docs.activeTabIdx) { + this.props.setDocsVisible(this.props.sessionId, false) + } + return this.setWidth() + } + + render() { + const { docsOpen, docsWidth, activeTabIdx } = this.props.docs + const docsStyle = { width: docsOpen ? docsWidth : 0 } + const activeTab = + docsOpen && + (React.Children.toArray(this.props.children)[ + activeTabIdx + ] as React.ReactElement) + return ( + + + {React.Children.toArray(this.props.children).map( + (child: React.ReactElement, index) => { + return React.cloneElement(child, { + ...child.props, + key: index, + onClick: this.handleTabClick(index), + active: index === activeTabIdx, + }) + }, + )} + + + + + {activeTab && + React.cloneElement(activeTab.props.children, { + ...activeTab.props, + ref: this.setActiveContentRef, + setWidth: this.setWidth, + })} + + + ) + } + + setRef = ref => { + this.ref = ref + } + + public showDocFromType = type => { + this.props.setDocsVisible(this.props.sessionId, true, 0) + this.activeContentComponent.showDocFromType(type) + } + + private setContentContainerRef = ref => { + this.refContentContainer = ref + } + + private handleTabClick = idx => () => { + if (!this.props.docs.docsOpen && this.refContentContainer) { + this.refContentContainer.focus() + } + if (this.props.docs.activeTabIdx === idx) { + this.props.setDocsVisible(this.props.sessionId, false) + return this.setWidth() + } + if (this.props.docs.activeTabIdx !== idx) { + this.props.setDocsVisible( + this.props.sessionId, + false, + this.props.docs.activeTabIdx, + ) + this.props.setDocsVisible(this.props.sessionId, true, idx) + return this.setWidth() + } else { + this.props.setDocsVisible(this.props.sessionId, true, idx) + return this.setWidth() + } + } + + private handleKeyDown = e => { + // we don't want to interfere with inputs + if ( + e.target instanceof HTMLInputElement || + e.metaKey || + e.shiftKey || + e.altKey || + e.ctrlKey + ) { + return + } + const keyPressed = keycode(e) + switch (keyPressed) { + case 'esc': + this.props.changeKeyMove(this.props.sessionId, true) + e.preventDefault() + this.props.setDocsVisible(this.props.sessionId, false) + break + } + } + + private handleDocsResizeStart = downEvent => { + downEvent.preventDefault() + + const hadWidth = this.props.docs.docsWidth + const offset = downEvent.clientX - getLeft(downEvent.target) + + let onMouseMove: any = moveEvent => { + if (moveEvent.buttons === 0) { + return onMouseUp() + } + + const app = this.ref + const cursorPos = moveEvent.clientX - getLeft(app) - offset + const newSize = app.clientWidth - cursorPos + const maxSize = window.innerWidth - 50 + const docsSize = maxSize < newSize ? maxSize : newSize + + if (docsSize < 100) { + this.props.setDocsVisible( + this.props.sessionId, + false, + this.props.docs.activeTabIdx, + ) + } else { + this.props.setDocsVisible( + this.props.sessionId, + true, + this.props.docs.activeTabIdx, + ) + this.props.changeWidthDocs(this.props.sessionId, docsSize) + } + } + + let onMouseUp: any = () => { + if (!this.props.docs.docsOpen) { + this.props.changeWidthDocs(this.props.sessionId, hadWidth) + } + + document.removeEventListener('mousemove', onMouseMove) + document.removeEventListener('mouseup', onMouseUp) + onMouseMove = null + onMouseUp = null + } + + document.addEventListener('mousemove', onMouseMove) + document.addEventListener('mouseup', onMouseUp) + } + private handleMouseMove = e => { + this.clientX = e.clientX + this.clientY = e.clientY + if ( + this.props.docs.keyMove && + this.clientX !== e.clientX && + this.clientY !== e.clientY + ) { + this.props.changeKeyMove(this.props.sessionId, false) + } + } } -const mapDispatchToProps = (dispatch) => - bindActionCreators( - { - addStack, - toggleDocs, - changeWidthDocs, - changeKeyMove, - setDocsVisible, - }, - dispatch, - ) +const mapDispatchToProps = dispatch => + bindActionCreators( + { + addStack, + toggleDocs, + changeWidthDocs, + changeKeyMove, + setDocsVisible, + }, + dispatch, + ) const mapStateToProps = createStructuredSelector({ - docs: getSessionDocs, - sessionId: getSelectedSessionIdFromRoot, + docs: getSessionDocs, + sessionId: getSelectedSessionIdFromRoot, }) const ConnectedGraphDocs = connect( - mapStateToProps, - mapDispatchToProps, - null, - { withRef: true }, + mapStateToProps, + mapDispatchToProps, + null, + { withRef: true }, )(SideTabs) ConnectedGraphDocs.Tab = SideTab @@ -295,94 +295,94 @@ ConnectedGraphDocs.Tab = SideTab export default ConnectedGraphDocs interface TabsProps { - open: boolean + open: boolean } const Tabs = styled('div')` - background: white; - outline: none; - box-shadow: 0 0 8px rgba(0, 0, 0, 0.15); - position: absolute; - right: -2px; - z-index: ${(p) => (p.open ? 2000 : 3)}; - height: 100%; - font-family: 'Open Sans', sans-serif; - -webkit-font-smoothing: antialiased; - .doc-type-description p { - padding: 16px; - font-size: 14px; - } - .field-name { - color: #1f61a0; - } - .type-name { - color: rgb(245, 160, 0); - } - .arg-name { - color: #1f61a9; - } - code { - font-family: 'Source Code Pro', monospace; - border-radius: 2px; - padding: 1px 2px; - background: rgba(0, 0, 0, 0.06); - } + background: white; + outline: none; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.15); + position: absolute; + right: -2px; + z-index: ${p => (p.open ? 2000 : 3)}; + height: 100%; + font-family: 'Open Sans', sans-serif; + -webkit-font-smoothing: antialiased; + .doc-type-description p { + padding: 16px; + font-size: 14px; + } + .field-name { + color: #1f61a0; + } + .type-name { + color: rgb(245, 160, 0); + } + .arg-name { + color: #1f61a9; + } + code { + font-family: 'Source Code Pro', monospace; + border-radius: 2px; + padding: 1px 2px; + background: rgba(0, 0, 0, 0.06); + } ` const TabContentContainer = styled.div` - background: white; - display: flex; - position: relative; - height: 100%; - letter-spacing: 0.3px; - box-shadow: -1px 1px 6px 0 rgba(0, 0, 0, 0.3); - - &::before { - top: 0; - bottom: 0; - background: ${(props) => props.theme.colours[props.color] || '#3D5866'}; - position: absolute; - z-index: 3; - left: 0px; - content: ''; - width: 6px; - } + background: white; + display: flex; + position: relative; + height: 100%; + letter-spacing: 0.3px; + box-shadow: -1px 1px 6px 0 rgba(0, 0, 0, 0.3); + + &::before { + top: 0; + bottom: 0; + background: ${props => props.theme.colours[props.color] || '#3D5866'}; + position: absolute; + z-index: 3; + left: 0px; + content: ''; + width: 6px; + } ` const TabContentResizer = styled.div` - cursor: col-resize; - outline: none !important; - height: 100%; - left: -5px; - position: absolute; - top: 0; - bottom: 0; - width: 10px; - z-index: 10; + cursor: col-resize; + outline: none !important; + height: 100%; + left: -5px; + position: absolute; + top: 0; + bottom: 0; + width: 10px; + z-index: 10; ` const TabsContainer = styled.div` - position: absolute; - outline: none !important; - z-index: 2; - height: 0; - top: 129px; + position: absolute; + outline: none !important; + z-index: 2; + height: 0; + top: 129px; ` const TabsGradient = styled.div` - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: 20px; - z-index: 1; - pointer-events: none; - content: ''; - background: ${(p) => - p.index === 0 - ? `linear-gradient( + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 20px; + z-index: 1; + pointer-events: none; + content: ''; + background: ${p => + p.index === 0 + ? `linear-gradient( to right, rgba(255, 255, 255, 1) 30%, rgba(255, 255, 255, 0))` - : `transparent`}; + : `transparent`}; ` diff --git a/packages/graphql-playground-react/src/components/Playground/GraphQLEditor.tsx b/packages/graphql-playground-react/src/components/Playground/GraphQLEditor.tsx index 04611692d..a195edcd9 100644 --- a/packages/graphql-playground-react/src/components/Playground/GraphQLEditor.tsx +++ b/packages/graphql-playground-react/src/components/Playground/GraphQLEditor.tsx @@ -10,8 +10,8 @@ import EditorWrapper, { Container } from './EditorWrapper' import CodeMirrorSizer from 'graphiql/dist/utility/CodeMirrorSizer' import TopBar from './TopBar/TopBar' import { - VariableEditorComponent, - HeadersEditorComponent, + VariableEditorComponent, + HeadersEditorComponent, } from './VariableEditor' import Spinner from '../Spinner' import Results from './Results' @@ -31,39 +31,39 @@ import { styled } from '../../styled/index' import { connect } from 'react-redux' import { createStructuredSelector } from 'reselect' import { - getQueryRunning, - getResponses, - getSubscriptionActive, - getVariableEditorOpen, - getVariableEditorHeight, - getResponseTracingOpen, - getResponseTracingHeight, - getResponseExtensions, - getCurrentQueryStartTime, - getCurrentQueryEndTime, - getTracingSupported, - getEditorFlex, - getQueryVariablesActive, - getHeaders, - getOperations, - getOperationName, - getHeadersCount, - getSelectedSessionIdFromRoot, + getQueryRunning, + getResponses, + getSubscriptionActive, + getVariableEditorOpen, + getVariableEditorHeight, + getResponseTracingOpen, + getResponseTracingHeight, + getResponseExtensions, + getCurrentQueryStartTime, + getCurrentQueryEndTime, + getTracingSupported, + getEditorFlex, + getQueryVariablesActive, + getHeaders, + getOperations, + getOperationName, + getHeadersCount, + getSelectedSessionIdFromRoot, } from '../../state/sessions/selectors' import { - updateQueryFacts, - stopQuery, - runQueryAtPosition, - closeQueryVariables, - openQueryVariables, - openVariables, - closeVariables, - openTracing, - closeTracing, - toggleTracing, - setEditorFlex, - toggleVariables, - fetchSchema, + updateQueryFacts, + stopQuery, + runQueryAtPosition, + closeQueryVariables, + openQueryVariables, + openVariables, + closeVariables, + openTracing, + closeTracing, + toggleTracing, + setEditorFlex, + toggleVariables, + fetchSchema, } from '../../state/sessions/actions' import { ResponseRecord } from '../../state/sessions/reducers' @@ -73,676 +73,676 @@ import { ResponseRecord } from '../../state/sessions/reducers' */ export interface Props { - onRef?: any - shareEnabled?: boolean - schema?: GraphQLSchema + onRef?: any + shareEnabled?: boolean + schema?: GraphQLSchema } export interface ReduxProps { - setStacks: (sessionId: string, stack: any[]) => void - updateQueryFacts: () => void - runQueryAtPosition: (position: number) => void - fetchSchema: () => void - openQueryVariables: () => void - closeQueryVariables: () => void - openVariables: (height: number) => void - closeVariables: (height: number) => void - openTracing: (height: number) => void - closeTracing: (height: number) => void - toggleTracing: () => void - toggleVariables: () => void - setEditorFlex: (flex: number) => void - stopQuery: (sessionId: string) => void - navStack: any[] - // sesion props - queryRunning: boolean - responses: List - subscriptionActive: boolean - variableEditorOpen: boolean - variableEditorHeight: number - currentQueryStartTime?: Date - currentQueryEndTime?: Date - responseTracingOpen: boolean - responseTracingHeight: number - responseExtensions: any - tracingSupported?: boolean - editorFlex: number - headers: string - headersCount: number - queryVariablesActive: boolean - operationName: string - query: string - sessionId: string + setStacks: (sessionId: string, stack: any[]) => void + updateQueryFacts: () => void + runQueryAtPosition: (position: number) => void + fetchSchema: () => void + openQueryVariables: () => void + closeQueryVariables: () => void + openVariables: (height: number) => void + closeVariables: (height: number) => void + openTracing: (height: number) => void + closeTracing: (height: number) => void + toggleTracing: () => void + toggleVariables: () => void + setEditorFlex: (flex: number) => void + stopQuery: (sessionId: string) => void + navStack: any[] + // sesion props + queryRunning: boolean + responses: List + subscriptionActive: boolean + variableEditorOpen: boolean + variableEditorHeight: number + currentQueryStartTime?: Date + currentQueryEndTime?: Date + responseTracingOpen: boolean + responseTracingHeight: number + responseExtensions: any + tracingSupported?: boolean + editorFlex: number + headers: string + headersCount: number + queryVariablesActive: boolean + operationName: string + query: string + sessionId: string } export interface SimpleProps { - children?: any + children?: any } export interface ToolbarButtonProps extends SimpleProps { - onClick: (e: any) => void - title: string - label: string + onClick: (e: any) => void + title: string + label: string } class GraphQLEditor extends React.PureComponent { - public codeMirrorSizer - public queryEditorComponent - public variableEditorComponent - public resultComponent - public editorBarComponent - public docExplorerComponent: any // later React.Component<...> - public graphExplorerComponent: any - public schemaExplorerComponent: any - private queryResizer: any - private responseResizer: any - private queryVariablesRef - private httpHeadersRef - - componentDidMount() { - // Ensure a form of a schema exists (including `null`) and - // if not, fetch one using an introspection query. - // this.props.fetchSchema() - - // Utility for keeping CodeMirror correctly sized. - this.codeMirrorSizer = new CodeMirrorSizer() - ;(global as any).g = this - } - - componentDidUpdate() { - // If this update caused DOM nodes to have changed sizes, update the - // corresponding CodeMirror instance sizes to match. - // const components = [ - // this.queryEditorComponent, - // this.variableEditorComponent, - // this.resultComponent, - // ] - // this.codeMirrorSizer.updateSizes(components) - if (this.resultComponent && Boolean(this.props.subscriptionActive)) { - this.resultComponent.scrollTop = this.resultComponent.scrollHeight - } - } - - render() { - return ( - - - - - - - - - - Query Variables - - - {'HTTP Headers ' + - (this.props.headersCount && this.props.headersCount > 0 - ? `(${this.props.headersCount})` - : '')} - - - {this.props.queryVariablesActive ? ( - - ) : ( - - )} - - - - - - - {this.props.queryRunning && - this.props.responses.size === 0 && } - - {!this.props.queryRunning && - (!this.props.responses || this.props.responses.size === 0) && ( - Hit the Play Button to get a response here - )} - {this.props.subscriptionActive && ( - Listening … - )} - - - - Tracing - - - - - - - - - - - - - - - - - ) - } - - setQueryVariablesRef = (ref) => { - this.queryVariablesRef = ref - } - - setHttpHeadersRef = (ref) => { - this.httpHeadersRef = ref - } - - setQueryResizer = (ref) => { - this.queryResizer = ReactDOM.findDOMNode(ref) - } - - setResponseResizer = (ref) => { - this.responseResizer = ReactDOM.findDOMNode(ref) - } - - setEditorBarComponent = (ref) => { - this.editorBarComponent = ref - } - - setQueryEditorComponent = (ref) => { - this.queryEditorComponent = ref - } - - setVariableEditorComponent = (ref) => { - this.variableEditorComponent = ref - } - - setResultComponent = (ref) => { - this.resultComponent = ref - } - - setDocExplorerRef = (ref) => { - if (ref) { - this.docExplorerComponent = ref.getWrappedInstance() - } - } - setGraphExplorerRef = (ref) => { - if (ref) { - this.graphExplorerComponent = ref.getWrappedInstance() - } - } - setSchemaExplorerRef = (ref) => { - if (ref) { - this.schemaExplorerComponent = ref.getWrappedInstance() - } - } - - handleClickReference = (reference) => { - this.docExplorerComponent.showDocFromType(reference.field || reference) - } - - /** - * Inspect the query, automatically filling in selection sets for non-leaf - * fields which do not yet have them. - * - * @public - */ - autoCompleteLeafs() { - const { insertions, result } = fillLeafs( - this.props.schema, - this.props.query, - ) as { - insertions: Array<{ index: number; string: string }> - result: string - } - if (insertions && insertions.length > 0) { - const editor = this.queryEditorComponent.getCodeMirror() - editor.operation(() => { - const cursor = editor.getCursor() - const cursorIndex = editor.indexFromPos(cursor) - editor.setValue(result) - let added = 0 - try { - /* tslint:disable-next-line */ - const markers = insertions.map(({ index, string }) => - editor.markText( - editor.posFromIndex(index + added), - editor.posFromIndex(index + (added += string.length)), - { - className: 'autoInsertedLeaf', - clearOnEnter: true, - title: 'Automatically added leaf fields', - }, - ), - ) - setTimeout(() => markers.forEach((marker) => marker.clear()), 7000) - } catch (e) { - // - } - let newCursorIndex = cursorIndex - /* tslint:disable-next-line */ - insertions.forEach(({ index, string }) => { - if (index < cursorIndex && string) { - newCursorIndex += string.length - } - }) - editor.setCursor(editor.posFromIndex(newCursorIndex)) - }) - } - - return result - } - - private runQueryAtCursor = () => { - if (this.props.queryRunning) { - this.props.stopQuery(this.props.sessionId) - return - } - - const editor = this.queryEditorComponent.getCodeMirror() - if (editor.hasFocus()) { - const cursor = editor.getCursor() - const cursorIndex = editor.indexFromPos(cursor) - this.props.runQueryAtPosition(cursorIndex) - } - } - - private handleHintInformationRender = (elem) => { - elem.addEventListener('click', this.onClickHintInformation) - - let onRemoveFn - elem.addEventListener( - 'DOMNodeRemoved', - (onRemoveFn = () => { - elem.removeEventListener('DOMNodeRemoved', onRemoveFn) - elem.removeEventListener('click', this.onClickHintInformation) - }), - ) - } - - private handleResizeStart = (downEvent) => { - if (!this.didClickDragBar(downEvent)) { - return - } - - downEvent.preventDefault() - - const offset = downEvent.clientX - getLeft(downEvent.target) - - let onMouseMove: any = (moveEvent) => { - if (moveEvent.buttons === 0) { - return onMouseUp() - } - - const editorBar = ReactDOM.findDOMNode(this.editorBarComponent) - const leftSize = moveEvent.clientX - getLeft(editorBar) - offset - const rightSize = editorBar.clientWidth - leftSize - this.props.setEditorFlex(leftSize / rightSize) - } - - let onMouseUp: any = () => { - document.removeEventListener('mousemove', onMouseMove) - document.removeEventListener('mouseup', onMouseUp) - onMouseMove = null - onMouseUp = null - } - - document.addEventListener('mousemove', onMouseMove) - document.addEventListener('mouseup', onMouseUp) - } - - private didClickDragBar(event) { - // Only for primary unmodified clicks - return ( - event.target === this.queryResizer || - event.target === this.responseResizer - ) - } - - private handleTracingResizeStart = (downEvent) => { - downEvent.preventDefault() - - let didMove = false - const hadHeight = this.props.responseTracingHeight - const offset = downEvent.clientY - getTop(downEvent.target) - - let onMouseMove: any = (moveEvent) => { - if (moveEvent.buttons === 0) { - return onMouseUp() - } - - didMove = true - - const editorBar = ReactDOM.findDOMNode(this.editorBarComponent) - const topSize = moveEvent.clientY - getTop(editorBar) - offset - const bottomSize = editorBar.clientHeight - topSize - if (bottomSize < 60) { - this.props.closeTracing(hadHeight) - } else { - this.props.openTracing(hadHeight) - } - } - - let onMouseUp: any = () => { - if (!didMove) { - this.props.toggleTracing() - } - - document.removeEventListener('mousemove', onMouseMove) - document.removeEventListener('mouseup', onMouseUp) - onMouseMove = null - onMouseUp = null - } - - document.addEventListener('mousemove', onMouseMove) - document.addEventListener('mouseup', onMouseUp) - } - - private handleVariableResizeStart = (downEvent) => { - downEvent.preventDefault() - - let didMove = false - const wasOpen = this.props.variableEditorOpen - const hadHeight = this.props.variableEditorHeight - const offset = downEvent.clientY - getTop(downEvent.target) - - if ( - wasOpen && - (downEvent.target === this.queryVariablesRef || - downEvent.target === this.httpHeadersRef) - ) { - return - } - - let onMouseMove: any = (moveEvent) => { - if (moveEvent.buttons === 0) { - return onMouseUp() - } - - didMove = true - - const editorBar = ReactDOM.findDOMNode(this.editorBarComponent) - const topSize = moveEvent.clientY - getTop(editorBar) - offset - const bottomSize = editorBar.clientHeight - topSize - if (bottomSize < 60) { - this.props.closeVariables(hadHeight) - } else { - this.props.openVariables(bottomSize) - } - } - - let onMouseUp: any = () => { - if (!didMove) { - this.props.toggleVariables() - } - - document.removeEventListener('mousemove', onMouseMove) - document.removeEventListener('mouseup', onMouseUp) - onMouseMove = null - onMouseUp = null - } - - document.addEventListener('mousemove', onMouseMove) - document.addEventListener('mouseup', onMouseUp) - } - - private onClickHintInformation = (event) => { - if (event.target.className === 'typeName') { - const typeName = event.target.innerHTML - const schema = this.props.schema - if (schema) { - // TODO: There is no way as of now to retrieve the NAMED_TYPE of a GraphQLList(Type). - // We're therefore removing any '[' or '!' characters, to properly find its NAMED_TYPE. (eg. [Type!]! => Type) - // This should be removed as soon as there's a safer way to do that. - const namedTypeName = typeName.replace(/[\]\[!]/g, '') - const type = schema.getType(namedTypeName) - - if (isNamedType(type)) { - this.docExplorerComponent.showDocFromType(type) - } - } - } - } + public codeMirrorSizer + public queryEditorComponent + public variableEditorComponent + public resultComponent + public editorBarComponent + public docExplorerComponent: any // later React.Component<...> + public graphExplorerComponent: any + public schemaExplorerComponent: any + private queryResizer: any + private responseResizer: any + private queryVariablesRef + private httpHeadersRef + + componentDidMount() { + // Ensure a form of a schema exists (including `null`) and + // if not, fetch one using an introspection query. + // this.props.fetchSchema() + + // Utility for keeping CodeMirror correctly sized. + this.codeMirrorSizer = new CodeMirrorSizer() + ;(global as any).g = this + } + + componentDidUpdate() { + // If this update caused DOM nodes to have changed sizes, update the + // corresponding CodeMirror instance sizes to match. + // const components = [ + // this.queryEditorComponent, + // this.variableEditorComponent, + // this.resultComponent, + // ] + // this.codeMirrorSizer.updateSizes(components) + if (this.resultComponent && Boolean(this.props.subscriptionActive)) { + this.resultComponent.scrollTop = this.resultComponent.scrollHeight + } + } + + render() { + return ( + + + + + + + + + + Query Variables + + + {'HTTP Headers ' + + (this.props.headersCount && this.props.headersCount > 0 + ? `(${this.props.headersCount})` + : '')} + + + {this.props.queryVariablesActive ? ( + + ) : ( + + )} + + + + + + + {this.props.queryRunning && + this.props.responses.size === 0 && } + + {!this.props.queryRunning && + (!this.props.responses || this.props.responses.size === 0) && ( + Hit the Play Button to get a response here + )} + {this.props.subscriptionActive && ( + Listening … + )} + + + + Tracing + + + + + + + + + + + + + + + + + ) + } + + setQueryVariablesRef = ref => { + this.queryVariablesRef = ref + } + + setHttpHeadersRef = ref => { + this.httpHeadersRef = ref + } + + setQueryResizer = ref => { + this.queryResizer = ReactDOM.findDOMNode(ref) + } + + setResponseResizer = ref => { + this.responseResizer = ReactDOM.findDOMNode(ref) + } + + setEditorBarComponent = ref => { + this.editorBarComponent = ref + } + + setQueryEditorComponent = ref => { + this.queryEditorComponent = ref + } + + setVariableEditorComponent = ref => { + this.variableEditorComponent = ref + } + + setResultComponent = ref => { + this.resultComponent = ref + } + + setDocExplorerRef = ref => { + if (ref) { + this.docExplorerComponent = ref.getWrappedInstance() + } + } + setGraphExplorerRef = ref => { + if (ref) { + this.graphExplorerComponent = ref.getWrappedInstance() + } + } + setSchemaExplorerRef = ref => { + if (ref) { + this.schemaExplorerComponent = ref.getWrappedInstance() + } + } + + handleClickReference = reference => { + this.docExplorerComponent.showDocFromType(reference.field || reference) + } + + /** + * Inspect the query, automatically filling in selection sets for non-leaf + * fields which do not yet have them. + * + * @public + */ + autoCompleteLeafs() { + const { insertions, result } = fillLeafs( + this.props.schema, + this.props.query, + ) as { + insertions: Array<{ index: number; string: string }> + result: string + } + if (insertions && insertions.length > 0) { + const editor = this.queryEditorComponent.getCodeMirror() + editor.operation(() => { + const cursor = editor.getCursor() + const cursorIndex = editor.indexFromPos(cursor) + editor.setValue(result) + let added = 0 + try { + /* tslint:disable-next-line */ + const markers = insertions.map(({ index, string }) => + editor.markText( + editor.posFromIndex(index + added), + editor.posFromIndex(index + (added += string.length)), + { + className: 'autoInsertedLeaf', + clearOnEnter: true, + title: 'Automatically added leaf fields', + }, + ), + ) + setTimeout(() => markers.forEach(marker => marker.clear()), 7000) + } catch (e) { + // + } + let newCursorIndex = cursorIndex + /* tslint:disable-next-line */ + insertions.forEach(({ index, string }) => { + if (index < cursorIndex && string) { + newCursorIndex += string.length + } + }) + editor.setCursor(editor.posFromIndex(newCursorIndex)) + }) + } + + return result + } + + private runQueryAtCursor = () => { + if (this.props.queryRunning) { + this.props.stopQuery(this.props.sessionId) + return + } + + const editor = this.queryEditorComponent.getCodeMirror() + if (editor.hasFocus()) { + const cursor = editor.getCursor() + const cursorIndex = editor.indexFromPos(cursor) + this.props.runQueryAtPosition(cursorIndex) + } + } + + private handleHintInformationRender = elem => { + elem.addEventListener('click', this.onClickHintInformation) + + let onRemoveFn + elem.addEventListener( + 'DOMNodeRemoved', + (onRemoveFn = () => { + elem.removeEventListener('DOMNodeRemoved', onRemoveFn) + elem.removeEventListener('click', this.onClickHintInformation) + }), + ) + } + + private handleResizeStart = downEvent => { + if (!this.didClickDragBar(downEvent)) { + return + } + + downEvent.preventDefault() + + const offset = downEvent.clientX - getLeft(downEvent.target) + + let onMouseMove: any = moveEvent => { + if (moveEvent.buttons === 0) { + return onMouseUp() + } + + const editorBar = ReactDOM.findDOMNode(this.editorBarComponent) + const leftSize = moveEvent.clientX - getLeft(editorBar) - offset + const rightSize = editorBar.clientWidth - leftSize + this.props.setEditorFlex(leftSize / rightSize) + } + + let onMouseUp: any = () => { + document.removeEventListener('mousemove', onMouseMove) + document.removeEventListener('mouseup', onMouseUp) + onMouseMove = null + onMouseUp = null + } + + document.addEventListener('mousemove', onMouseMove) + document.addEventListener('mouseup', onMouseUp) + } + + private didClickDragBar(event) { + // Only for primary unmodified clicks + return ( + event.target === this.queryResizer || + event.target === this.responseResizer + ) + } + + private handleTracingResizeStart = downEvent => { + downEvent.preventDefault() + + let didMove = false + const hadHeight = this.props.responseTracingHeight + const offset = downEvent.clientY - getTop(downEvent.target) + + let onMouseMove: any = moveEvent => { + if (moveEvent.buttons === 0) { + return onMouseUp() + } + + didMove = true + + const editorBar = ReactDOM.findDOMNode(this.editorBarComponent) + const topSize = moveEvent.clientY - getTop(editorBar) - offset + const bottomSize = editorBar.clientHeight - topSize + if (bottomSize < 60) { + this.props.closeTracing(hadHeight) + } else { + this.props.openTracing(hadHeight) + } + } + + let onMouseUp: any = () => { + if (!didMove) { + this.props.toggleTracing() + } + + document.removeEventListener('mousemove', onMouseMove) + document.removeEventListener('mouseup', onMouseUp) + onMouseMove = null + onMouseUp = null + } + + document.addEventListener('mousemove', onMouseMove) + document.addEventListener('mouseup', onMouseUp) + } + + private handleVariableResizeStart = downEvent => { + downEvent.preventDefault() + + let didMove = false + const wasOpen = this.props.variableEditorOpen + const hadHeight = this.props.variableEditorHeight + const offset = downEvent.clientY - getTop(downEvent.target) + + if ( + wasOpen && + (downEvent.target === this.queryVariablesRef || + downEvent.target === this.httpHeadersRef) + ) { + return + } + + let onMouseMove: any = moveEvent => { + if (moveEvent.buttons === 0) { + return onMouseUp() + } + + didMove = true + + const editorBar = ReactDOM.findDOMNode(this.editorBarComponent) + const topSize = moveEvent.clientY - getTop(editorBar) - offset + const bottomSize = editorBar.clientHeight - topSize + if (bottomSize < 60) { + this.props.closeVariables(hadHeight) + } else { + this.props.openVariables(bottomSize) + } + } + + let onMouseUp: any = () => { + if (!didMove) { + this.props.toggleVariables() + } + + document.removeEventListener('mousemove', onMouseMove) + document.removeEventListener('mouseup', onMouseUp) + onMouseMove = null + onMouseUp = null + } + + document.addEventListener('mousemove', onMouseMove) + document.addEventListener('mouseup', onMouseUp) + } + + private onClickHintInformation = event => { + if (event.target.className === 'typeName') { + const typeName = event.target.innerHTML + const schema = this.props.schema + if (schema) { + // TODO: There is no way as of now to retrieve the NAMED_TYPE of a GraphQLList(Type). + // We're therefore removing any '[' or '!' characters, to properly find its NAMED_TYPE. (eg. [Type!]! => Type) + // This should be removed as soon as there's a safer way to do that. + const namedTypeName = typeName.replace(/[\]\[!]/g, '') + const type = schema.getType(namedTypeName) + + if (isNamedType(type)) { + this.docExplorerComponent.showDocFromType(type) + } + } + } + } } const mapStateToProps = createStructuredSelector({ - queryRunning: getQueryRunning, - responses: getResponses, - subscriptionActive: getSubscriptionActive, - variableEditorOpen: getVariableEditorOpen, - variableEditorHeight: getVariableEditorHeight, - responseTracingOpen: getResponseTracingOpen, - responseTracingHeight: getResponseTracingHeight, - responseExtensions: getResponseExtensions, - currentQueryStartTime: getCurrentQueryStartTime, - currentQueryEndTime: getCurrentQueryEndTime, - tracingSupported: getTracingSupported, - editorFlex: getEditorFlex, - queryVariablesActive: getQueryVariablesActive, - headers: getHeaders, - operations: getOperations, - operationName: getOperationName, - headersCount: getHeadersCount, - sessionId: getSelectedSessionIdFromRoot, + queryRunning: getQueryRunning, + responses: getResponses, + subscriptionActive: getSubscriptionActive, + variableEditorOpen: getVariableEditorOpen, + variableEditorHeight: getVariableEditorHeight, + responseTracingOpen: getResponseTracingOpen, + responseTracingHeight: getResponseTracingHeight, + responseExtensions: getResponseExtensions, + currentQueryStartTime: getCurrentQueryStartTime, + currentQueryEndTime: getCurrentQueryEndTime, + tracingSupported: getTracingSupported, + editorFlex: getEditorFlex, + queryVariablesActive: getQueryVariablesActive, + headers: getHeaders, + operations: getOperations, + operationName: getOperationName, + headersCount: getHeadersCount, + sessionId: getSelectedSessionIdFromRoot, }) export default // TODO fix redux types connect( - mapStateToProps, - { - updateQueryFacts, - stopQuery, - runQueryAtPosition, - openQueryVariables, - closeQueryVariables, - openVariables, - closeVariables, - openTracing, - closeTracing, - toggleTracing, - setEditorFlex, - toggleVariables, - fetchSchema, - }, - null, - { - withRef: true, - }, + mapStateToProps, + { + updateQueryFacts, + stopQuery, + runQueryAtPosition, + openQueryVariables, + closeQueryVariables, + openVariables, + closeVariables, + openTracing, + closeTracing, + toggleTracing, + setEditorFlex, + toggleVariables, + fetchSchema, + }, + null, + { + withRef: true, + }, )(GraphQLEditor) const EditorBar = styled.div` - display: flex; - flex-direction: row; - flex: 1; + display: flex; + flex-direction: row; + flex: 1; ` const ResultWrap = styled.div` - display: flex; - flex-direction: column; - flex: 1; - position: relative; - border-left: none; - background: ${(p) => p.theme.editorColours.resultBackground}; + display: flex; + flex-direction: column; + flex: 1; + position: relative; + border-left: none; + background: ${p => p.theme.editorColours.resultBackground}; ` const DragBar = styled.div` - width: 15px; - position: absolute; - top: 0; - bottom: 0; - cursor: col-resize; + width: 15px; + position: absolute; + top: 0; + bottom: 0; + cursor: col-resize; ` const QueryDragBar = styled(DragBar)` - right: 0px; + right: 0px; ` const ResultDragBar = styled(DragBar)` - left: 0px; - z-index: 1; + left: 0px; + z-index: 1; ` interface DrawerProps { - isOpen: boolean - height: number + isOpen: boolean + height: number } const BottomDrawer = styled('div')` - display: flex; - background: #0b1924; - flex-direction: column; - position: relative; - height: ${(props) => (props.isOpen ? `${props.height}px` : '43px')}; + display: flex; + background: #0b1924; + flex-direction: column; + position: relative; + height: ${props => (props.isOpen ? `${props.height}px` : '43px')}; ` interface TitleProps { - isOpen: boolean - onMouseDown?: any - onClick?: any - ref?: any + isOpen: boolean + onMouseDown?: any + onClick?: any + ref?: any } const BottomDrawerTitle = styled.div` - background: #0b1924; - text-transform: uppercase; - font-weight: 600; - letter-spacing: 0.53px; - line-height: 14px; - font-size: 14px; - padding: 14px 14px 15px 21px; - user-select: none; + background: #0b1924; + text-transform: uppercase; + font-weight: 600; + letter-spacing: 0.53px; + line-height: 14px; + font-size: 14px; + padding: 14px 14px 15px 21px; + user-select: none; ` const VariableEditor = styled(BottomDrawer)` - .CodeMirror { - padding-left: 4px; - width: calc(100% - 4px); - background: ${(p) => p.theme.editorColours.leftDrawerBackground}; - } - .CodeMirror-lines { - padding: 10px 0 20px 0; - } - .CodeMirror-linenumbers { - background: ${(p) => p.theme.editorColours.leftDrawerBackground}; - } + .CodeMirror { + padding-left: 4px; + width: calc(100% - 4px); + background: ${p => p.theme.editorColours.leftDrawerBackground}; + } + .CodeMirror-lines { + padding: 10px 0 20px 0; + } + .CodeMirror-linenumbers { + background: ${p => p.theme.editorColours.leftDrawerBackground}; + } ` const VariableEditorTitle = styled(({ isOpen, ...rest }) => ( - + ))` - cursor: ${(p) => (p.isOpen ? 'row-resize' : 'n-resize')}; - background: ${(p) => p.theme.editorColours.leftDrawerBackground}; + cursor: ${p => (p.isOpen ? 'row-resize' : 'n-resize')}; + background: ${p => p.theme.editorColours.leftDrawerBackground}; ` const VariableEditorSubtitle = styled('span')` - margin-right: 10px; - cursor: pointer; - color: ${(p) => - p.isOpen - ? p.theme.editorColours.drawerText - : p.theme.editorColours.drawerTextInactive}; - &:last-child { - margin-right: 0; - } + margin-right: 10px; + cursor: pointer; + color: ${p => + p.isOpen + ? p.theme.editorColours.drawerText + : p.theme.editorColours.drawerTextInactive}; + &:last-child { + margin-right: 0; + } ` const ResponseTracking = styled(BottomDrawer)` - background: ${(p) => p.theme.editorColours.rightDrawerBackground}; + background: ${p => p.theme.editorColours.rightDrawerBackground}; ` const ResponseTrackingTitle = styled(({ isOpen, ...rest }) => ( - + ))` - text-align: right; - background: ${(p) => p.theme.editorColours.rightDrawerBackground}; - cursor: ${(props) => (props.isOpen ? 's-resize' : 'n-resize')}; - color: ${(p) => p.theme.editorColours.drawerTextInactive}; + text-align: right; + background: ${p => p.theme.editorColours.rightDrawerBackground}; + cursor: ${props => (props.isOpen ? 's-resize' : 'n-resize')}; + color: ${p => p.theme.editorColours.drawerTextInactive}; ` interface QueryProps { - flex: number + flex: number } const QueryWrap = styled('div')` - position: relative; - display: flex; - flex-direction: column; - flex: ${(props) => props.flex} 1 0%; - border-top: 8px solid ${(props) => props.theme.editorColours.resultBackground}; + position: relative; + display: flex; + flex-direction: column; + flex: ${props => props.flex} 1 0%; + border-top: 8px solid ${props => props.theme.editorColours.resultBackground}; ` const Intro = styled.div` - width: 235px; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - color: ${(p) => p.theme.colours.textInactive}; - font-size: ${(p) => p.theme.sizes.small16}; - font-family: 'Source Code Pro', 'Consolas', 'Inconsolata', 'Droid Sans Mono', - 'Monaco', monospace; - text-align: center; - letter-spacing: 0.6px; + width: 235px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: ${p => p.theme.colours.textInactive}; + font-size: ${p => p.theme.sizes.small16}; + font-family: 'Source Code Pro', 'Consolas', 'Inconsolata', 'Droid Sans Mono', + 'Monaco', monospace; + text-align: center; + letter-spacing: 0.6px; ` const Listening = styled.div` - position: absolute; - bottom: 0; - color: ${(p) => p.theme.editorColours.text}; - background: ${(p) => p.theme.editorColours.resultBackground}; - font-size: ${(p) => p.theme.sizes.small16}; - font-family: ${(p) => p.theme.settings['editor.fontFamily']}; - letter-spacing: 0.6px; - padding-left: 24px; - padding-bottom: 60px; + position: absolute; + bottom: 0; + color: ${p => p.theme.editorColours.text}; + background: ${p => p.theme.editorColours.resultBackground}; + font-size: ${p => p.theme.sizes.small16}; + font-family: ${p => p.theme.settings['editor.fontFamily']}; + letter-spacing: 0.6px; + padding-left: 24px; + padding-bottom: 60px; ` diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLDocType.tsx b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLDocType.tsx index 37bf190af..45c04deb8 100644 --- a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLDocType.tsx +++ b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLDocType.tsx @@ -3,84 +3,84 @@ import SDLType from './SDLType' import { styled } from '../../../../styled' export interface DocTypeSchemaProps { - type: any - fields: any[] - interfaces: any[] + type: any + fields: any[] + interfaces: any[] } export default ({ type, fields, interfaces }: DocTypeSchemaProps) => { - const nonDeprecatedFields = fields.filter((data) => !data.isDeprecated) - const deprecatedFields = fields.filter((data) => data.isDeprecated) - return ( - - - {type.instanceOf}{' '} - {type.name}{' '} - {interfaces.length === 0 && {`{`}} - - {interfaces.map((data, index) => ( - implements} - afterNode={ - index === interfaces.length - 1 ? {'{'} : null - } - /> - ))} - {nonDeprecatedFields.map((data) => ( - - ))} - {deprecatedFields.length > 0 &&
} - {deprecatedFields.map((data, index) => ( -
- - # Deprecated: {data.deprecationReason} - - -
- ))} - - {'}'} - -
- ) + const nonDeprecatedFields = fields.filter(data => !data.isDeprecated) + const deprecatedFields = fields.filter(data => data.isDeprecated) + return ( + + + {type.instanceOf}{' '} + {type.name}{' '} + {interfaces.length === 0 && {`{`}} + + {interfaces.map((data, index) => ( + implements} + afterNode={ + index === interfaces.length - 1 ? {'{'} : null + } + /> + ))} + {nonDeprecatedFields.map(data => ( + + ))} + {deprecatedFields.length > 0 &&
} + {deprecatedFields.map((data, index) => ( +
+ + # Deprecated: {data.deprecationReason} + + +
+ ))} + + {'}'} + +
+ ) } const DocTypeSchema = styled.div` - font-size: 14px; - flex: 1; - .doc-category-item { - padding-left: 32px; - } + font-size: 14px; + flex: 1; + .doc-category-item { + padding-left: 32px; + } ` const DocTypeLine = styled.div` - padding: 6px 16px; - white-space: nowrap; + padding: 6px 16px; + white-space: nowrap; ` const DocsTypeName = styled.span` - color: #f25c54; + color: #f25c54; ` const DocsTypeInferface = styled(SDLType)` - padding-left: 16px; - .field-name { - color: rgb(245, 160, 0); - } - .type-name { - color: #f25c54; - } + padding-left: 16px; + .field-name { + color: rgb(245, 160, 0); + } + .type-name { + color: #f25c54; + } ` const DocsValueComment = styled.span` - color: ${(p) => p.theme.colours.black50}; - padding-right: 16px; - padding-left: 32px; + color: ${p => p.theme.colours.black50}; + padding-right: 16px; + padding-left: 32px; ` const Brace = styled.span` - font-weight: 600; - color: ${(p) => p.theme.colours.darkBlue50}; + font-weight: 600; + color: ${p => p.theme.colours.darkBlue50}; ` diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLFieldDoc.tsx b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLFieldDoc.tsx index 2eb81391d..2f0c21e56 100644 --- a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLFieldDoc.tsx +++ b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLFieldDoc.tsx @@ -8,47 +8,47 @@ import { CategoryTitle } from '../../DocExplorer/DocsStyles' import { styled } from '../../../../styled' export interface Props { - schema: any - type: any + schema: any + type: any } export interface State { - showDeprecated: boolean + showDeprecated: boolean } export default class FieldDoc extends React.Component { - state = { showDeprecated: false } + state = { showDeprecated: false } - render() { - const { type, schema } = this.props - return ( -
- {`${type.name} details`} - {type.description && - type.description.length > 0 && ( - - )} - {type.instanceOf === 'scalar' && } - {type.instanceOf === 'enum' && ( - - )} - {type.instanceOf === 'union' && ( - - )} - {type.fields.length > 0 && ( - - )} -
- ) - } + render() { + const { type, schema } = this.props + return ( +
+ {`${type.name} details`} + {type.description && + type.description.length > 0 && ( + + )} + {type.instanceOf === 'scalar' && } + {type.instanceOf === 'enum' && ( + + )} + {type.instanceOf === 'union' && ( + + )} + {type.fields.length > 0 && ( + + )} +
+ ) + } } const DocsDescription = styled(MarkdownContent)` - font-size: 14px; - padding: 0 16px 20px 16px; - color: rgba(0, 0, 0, 0.5); + font-size: 14px; + padding: 0 16px 20px 16px; + color: rgba(0, 0, 0, 0.5); ` diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLStyles.tsx b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLStyles.tsx index 3cfbf2c4b..8c7187738 100644 --- a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLStyles.tsx +++ b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLStyles.tsx @@ -6,173 +6,173 @@ import { downloadSchema } from '../../util/createSDL' import { columnWidth } from '../../../../constants' interface SDLHeaderProps { - schema: GraphQLSchema + schema: GraphQLSchema } interface State { - open: boolean + open: boolean } class SDLHeader extends React.Component { - constructor(props) { - super(props) - this.state = { - open: false, - } - } - - showOptions = () => { - this.setState({ - open: !this.state.open, - }) - } - - printSDL = () => { - return downloadSchema(this.props.schema, 'sdl') - } - - printIntrospection = () => { - return downloadSchema(this.props.schema, 'json') - } - - render() { - const { open } = this.state - return ( - - Schema - - - Download - - {open && ( - - - - - )} - - - ) - } + constructor(props) { + super(props) + this.state = { + open: false, + } + } + + showOptions = () => { + this.setState({ + open: !this.state.open, + }) + } + + printSDL = () => { + return downloadSchema(this.props.schema, 'sdl') + } + + printIntrospection = () => { + return downloadSchema(this.props.schema, 'json') + } + + render() { + const { open } = this.state + return ( + + Schema + + + Download + + {open && ( + + + + + )} + + + ) + } } export { SDLHeader } export const SchemaExplorerContainer = styled.div` - position: relative; - height: 100%; - width: 100%; - display: flex; - flex-direction: column; - flex-wrap: wrap; - align-items: stretch; - padding: 8px; - background: ${(p) => styleHelper(p).secBackground}; - font-family: ${(p) => p.theme.settings['editor.fontFamily']}; - font-size: ${(p) => `${p.theme.settings['editor.fontSize']}px`}; - outline: none !important; + position: relative; + height: 100%; + width: 100%; + display: flex; + flex-direction: column; + flex-wrap: wrap; + align-items: stretch; + padding: 8px; + background: ${p => styleHelper(p).secBackground}; + font-family: ${p => p.theme.settings['editor.fontFamily']}; + font-size: ${p => `${p.theme.settings['editor.fontSize']}px`}; + outline: none !important; ` const SchemaHeader = styled.div` - display: flex; - height: 64px; - width: 100%; - align-items: center; - justify-content: space-between; + display: flex; + height: 64px; + width: 100%; + align-items: center; + justify-content: space-between; ` const Box = styled.div` - position: absolute; - top: 16px; - right: 2em; - width: 108px; - display: flex; - flex-wrap: wrap; - flex-direction: column; + position: absolute; + top: 16px; + right: 2em; + width: 108px; + display: flex; + flex-wrap: wrap; + flex-direction: column; ` const Title = styled.div` - flex: 1; - color: ${(p) => styleHelper(p).title}; - cursor: default; - font-size: 14px; - font-weight: 600; - text-transform: uppercase !important; - font-family: 'Open Sans', sans-serif !important; - letter-spacing: 1px; - user-select: none; - padding: 16px; + flex: 1; + color: ${p => styleHelper(p).title}; + cursor: default; + font-size: 14px; + font-weight: 600; + text-transform: uppercase !important; + font-family: 'Open Sans', sans-serif !important; + letter-spacing: 1px; + user-select: none; + padding: 16px; ` const Download = styled(Button)` - flex: 1; - color: ${(p) => styleHelper(p).download['text']}; - background: ${(p) => styleHelper(p).download['button']}; - padding: 12px 9px 12px 9px; - border-radius: 0px; - &:hover { - color: ${(p) => styleHelper(p).buttonTextHover}; - background-color: ${(p) => styleHelper(p).buttonHover}; - } + flex: 1; + color: ${p => styleHelper(p).download['text']}; + background: ${p => styleHelper(p).download['button']}; + padding: 12px 9px 12px 9px; + border-radius: 0px; + &:hover { + color: ${p => styleHelper(p).buttonTextHover}; + background-color: ${p => styleHelper(p).buttonHover}; + } ` const Option = styled(Download)` - text-align: left; - width: 100%; - margin-left: 0px; - border-radius: 0px; - z-index: 2000; - background: ${(p) => styleHelper(p).button}; + text-align: left; + width: 100%; + margin-left: 0px; + border-radius: 0px; + z-index: 2000; + background: ${p => styleHelper(p).button}; ` export interface SDLColumnProps { - children: any - width?: number + children: any + width?: number } const SDLColumn = ({ children, width = columnWidth }: SDLColumnProps) => { - return {children} + return {children} } export { SDLColumn } const Column = styled('div')` - display: flex; - flex: 1 0 auto; - flex-flow: column; - padding-bottom: 20px; - border-right: 1px solid ${(p) => p.theme.colours.black10}; - overflow: hidden; + display: flex; + flex: 1 0 auto; + flex-flow: column; + padding-bottom: 20px; + border-right: 1px solid ${p => p.theme.colours.black10}; + overflow: hidden; ` -const styleHelper = (p) => { - if (p.theme.mode === 'dark') { - return { - secBackground: p.theme.editorColours.navigationBar, - title: 'white', - download: { - text: p.open ? p.theme.colours.white30 : 'white', - button: p.open ? '#2e5482' : p.theme.colours.blue, - }, - buttonText: 'white', - button: p.alternate ? '#386bac' : p.theme.colours.blue, - buttonHover: '#2e5482', - buttonTextHover: 'white', - } - } - return { - secBackground: 'white', - download: { - text: p.open ? 'rgba(61, 88, 102, 0.5)' : '#3D5866', - button: '#f6f6f6', - }, - title: 'rgba(0, 0, 0, 0.3)', - buttonText: '#3d5866', - button: p.alternate ? '#EDEDED' : '#f6f6f6', - buttonHover: '#f6f6f6', - buttonTextHover: 'rgba(61, 88, 102, 0.5)', - } +const styleHelper = p => { + if (p.theme.mode === 'dark') { + return { + secBackground: p.theme.editorColours.navigationBar, + title: 'white', + download: { + text: p.open ? p.theme.colours.white30 : 'white', + button: p.open ? '#2e5482' : p.theme.colours.blue, + }, + buttonText: 'white', + button: p.alternate ? '#386bac' : p.theme.colours.blue, + buttonHover: '#2e5482', + buttonTextHover: 'white', + } + } + return { + secBackground: 'white', + download: { + text: p.open ? 'rgba(61, 88, 102, 0.5)' : '#3D5866', + button: '#f6f6f6', + }, + title: 'rgba(0, 0, 0, 0.3)', + buttonText: '#3d5866', + button: p.alternate ? '#EDEDED' : '#f6f6f6', + buttonHover: '#f6f6f6', + buttonTextHover: 'rgba(61, 88, 102, 0.5)', + } } diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLType.tsx b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLType.tsx index 5dfabc7d5..b240375fc 100644 --- a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLType.tsx +++ b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLType.tsx @@ -4,96 +4,96 @@ import ArgumentInline from '../../DocExplorer/ArgumentInline' import { styled } from '../../../../styled' export interface Props { - type: any - className?: string - beforeNode?: JSX.Element | null | false - afterNode?: JSX.Element | null | false - showParentName?: boolean - collapsable?: boolean + type: any + className?: string + beforeNode?: JSX.Element | null | false + afterNode?: JSX.Element | null | false + showParentName?: boolean + collapsable?: boolean } export default class SDLType extends React.Component { - render() { - const { - type, - className, - beforeNode, - afterNode, - showParentName, - } = this.props - const isGraphqlType = isType(type) + render() { + const { + type, + className, + beforeNode, + afterNode, + showParentName, + } = this.props + const isGraphqlType = isType(type) - const fieldName = - showParentName && type.parent ? ( - - {type.parent.name}.{type.name} - - ) : ( - type.name - ) + const fieldName = + showParentName && type.parent ? ( + + {type.parent.name}.{type.name} + + ) : ( + type.name + ) - return ( - - {beforeNode} - {beforeNode && ' '} - {!isGraphqlType && ( - - {fieldName} - {type.args && - type.args.length > 0 && [ - '(', - - {type.args.map((arg) => ( - - ))} - , - ')', - ]} - {': '} - - )} - {renderType(type.type || type)} - {type.defaultValue !== undefined ? ( - - {' '} - = {`${type.defaultValue}`} - - ) : ( - undefined - )} - {afterNode && ' '} - {afterNode} - - ) - } + return ( + + {beforeNode} + {beforeNode && ' '} + {!isGraphqlType && ( + + {fieldName} + {type.args && + type.args.length > 0 && [ + '(', + + {type.args.map(arg => ( + + ))} + , + ')', + ]} + {': '} + + )} + {renderType(type.type || type)} + {type.defaultValue !== undefined ? ( + + {' '} + = {`${type.defaultValue}`} + + ) : ( + undefined + )} + {afterNode && ' '} + {afterNode} + + ) + } } function renderType(type) { - if (type instanceof GraphQLNonNull) { - return ( - - {renderType(type.ofType)} - {'!'} - - ) - } - if (type instanceof GraphQLList) { - return ( - - {'['} - {renderType(type.ofType)} - {']'} - - ) - } - return {type.name} + if (type instanceof GraphQLNonNull) { + return ( + + {renderType(type.ofType)} + {'!'} + + ) + } + if (type instanceof GraphQLList) { + return ( + + {'['} + {renderType(type.ofType)} + {']'} + + ) + } + return {type.name} } interface DocsCategoryItemProps { - clickable?: boolean - active?: boolean + clickable?: boolean + active?: boolean } const DocsCategoryItem = styled('div')` @@ -102,19 +102,19 @@ const DocsCategoryItem = styled('div')` overflow: auto; font-size: 14px; transition: 0.1s background-color; - background: ${(p) => - p.active ? p.theme.colours.black07 : p.theme.colours.white}; + background: ${p => + p.active ? p.theme.colours.black07 : p.theme.colours.white}; - cursor: ${(p) => (p.clickable ? 'pointer' : 'select')}; + cursor: ${p => (p.clickable ? 'pointer' : 'select')}; // &:hover { - // color: ${(p) => p.theme.colours.white}; + // color: ${p => p.theme.colours.white}; // background: #2a7ed3; // .field-name, // .type-name, // .arg-name, // span { - // color: ${(p) => p.theme.colours.white} !important; + // color: ${p => p.theme.colours.white} !important; // } // } b { @@ -123,8 +123,8 @@ const DocsCategoryItem = styled('div')` ` const DefaultValue = styled.span` - color: ${(p) => p.theme.colours.black30}; - span { - color: #1f61a9; - } + color: ${p => p.theme.colours.black30}; + span { + color: #1f61a9; + } ` diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLUnionType.tsx b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLUnionType.tsx index e4dba4993..582220870 100644 --- a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLUnionType.tsx +++ b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLUnionType.tsx @@ -3,20 +3,20 @@ import * as React from 'react' import { DocType } from '../../DocExplorer/DocsTypes/DocType' export interface EnumTypeSchemaProps { - schema: any - type: any + schema: any + type: any } const UnionTypeSchema = ({ schema, type }: EnumTypeSchemaProps) => { - const types = schema.getPossibleTypes(type) - return ( - - union{' '} - {type.name} - {' = '} - {types.map((value) => )} - - ) + const types = schema.getPossibleTypes(type) + return ( + + union{' '} + {type.name} + {' = '} + {types.map(value => )} + + ) } export default UnionTypeSchema diff --git a/packages/graphql-playground-react/src/components/Playground/TopBar/TopBar.tsx b/packages/graphql-playground-react/src/components/Playground/TopBar/TopBar.tsx index e11479da7..fc28d14a2 100644 --- a/packages/graphql-playground-react/src/components/Playground/TopBar/TopBar.tsx +++ b/packages/graphql-playground-react/src/components/Playground/TopBar/TopBar.tsx @@ -6,230 +6,230 @@ import Share from '../../Share' import ReloadIcon from './ReloadIcon' import { createStructuredSelector } from 'reselect' import { - getEndpoint, - getSelectedSession, - getIsReloadingSchema, - getEndpointUnreachable, + getEndpoint, + getSelectedSession, + getIsReloadingSchema, + getEndpointUnreachable, } from '../../../state/sessions/selectors' import { connect } from 'react-redux' import { getFixedEndpoint } from '../../../state/general/selectors' import * as PropTypes from 'prop-types' import { - editEndpoint, - prettifyQuery, - refetchSchema, + editEndpoint, + prettifyQuery, + refetchSchema, } from '../../../state/sessions/actions' import { share } from '../../../state/sharing/actions' import { openHistory } from '../../../state/general/actions' export interface Props { - endpoint: string - shareEnabled?: boolean - fixedEndpoint?: boolean - isReloadingSchema: boolean - endpointUnreachable: boolean - - editEndpoint: (value: string) => void - prettifyQuery: () => void - openHistory: () => void - share: () => void - refetchSchema: () => void + endpoint: string + shareEnabled?: boolean + fixedEndpoint?: boolean + isReloadingSchema: boolean + endpointUnreachable: boolean + + editEndpoint: (value: string) => void + prettifyQuery: () => void + openHistory: () => void + share: () => void + refetchSchema: () => void } class TopBar extends React.Component { - static contextTypes = { - store: PropTypes.shape({ - subscribe: PropTypes.func.isRequired, - dispatch: PropTypes.func.isRequired, - getState: PropTypes.func.isRequired, - }), - } - render() { - const { endpointUnreachable } = this.props - return ( - - - - - - {endpointUnreachable ? ( - - Server cannot be reached - - - ) : ( - - )} - - - {this.props.shareEnabled && ( - - - - )} - - ) - } - copyCurlToClipboard = () => { - const curl = this.getCurl() - copy(curl) - } - onChange = (e) => { - this.props.editEndpoint(e.target.value) - } - onKeyDown = (e) => { - if (e.keyCode === 13) { - this.props.refetchSchema() - } - } - openHistory = () => { - this.props.openHistory() - } - getCurl = () => { - // no need to rerender the whole time. only on-demand the store is fetched - const session = getSelectedSession(this.context.store.getState()) - let variables - try { - variables = JSON.parse(session.variables) - } catch (e) { - // - } - const data = JSON.stringify({ - query: session.query, - variables, - operationName: session.operationName, - }) - let sessionHeaders - try { - sessionHeaders = JSON.parse(session.headers!) - } catch (e) { - // - } - const headers = { - 'Accept-Encoding': 'gzip, deflate, br', - 'Content-Type': 'application/json', - Accept: 'application/json', - Connection: 'keep-alive', - DNT: '1', - Origin: location.origin || session.endpoint, - ...sessionHeaders, - } - const headersString = Object.keys(headers) - .map((key) => { - const value = headers[key] - return `-H '${key}: ${value}'` - }) - .join(' ') - return `curl '${ - session.endpoint - }' ${headersString} --data-binary '${data}' --compressed` - } + static contextTypes = { + store: PropTypes.shape({ + subscribe: PropTypes.func.isRequired, + dispatch: PropTypes.func.isRequired, + getState: PropTypes.func.isRequired, + }), + } + render() { + const { endpointUnreachable } = this.props + return ( + + + + + + {endpointUnreachable ? ( + + Server cannot be reached + + + ) : ( + + )} + + + {this.props.shareEnabled && ( + + + + )} + + ) + } + copyCurlToClipboard = () => { + const curl = this.getCurl() + copy(curl) + } + onChange = e => { + this.props.editEndpoint(e.target.value) + } + onKeyDown = e => { + if (e.keyCode === 13) { + this.props.refetchSchema() + } + } + openHistory = () => { + this.props.openHistory() + } + getCurl = () => { + // no need to rerender the whole time. only on-demand the store is fetched + const session = getSelectedSession(this.context.store.getState()) + let variables + try { + variables = JSON.parse(session.variables) + } catch (e) { + // + } + const data = JSON.stringify({ + query: session.query, + variables, + operationName: session.operationName, + }) + let sessionHeaders + try { + sessionHeaders = JSON.parse(session.headers!) + } catch (e) { + // + } + const headers = { + 'Accept-Encoding': 'gzip, deflate, br', + 'Content-Type': 'application/json', + Accept: 'application/json', + Connection: 'keep-alive', + DNT: '1', + Origin: location.origin || session.endpoint, + ...sessionHeaders, + } + const headersString = Object.keys(headers) + .map(key => { + const value = headers[key] + return `-H '${key}: ${value}'` + }) + .join(' ') + return `curl '${ + session.endpoint + }' ${headersString} --data-binary '${data}' --compressed` + } } const mapStateToProps = createStructuredSelector({ - endpoint: getEndpoint, - fixedEndpoint: getFixedEndpoint, - isReloadingSchema: getIsReloadingSchema, - endpointUnreachable: getEndpointUnreachable, + endpoint: getEndpoint, + fixedEndpoint: getFixedEndpoint, + isReloadingSchema: getIsReloadingSchema, + endpointUnreachable: getEndpointUnreachable, }) export default connect( - mapStateToProps, - { - editEndpoint, - prettifyQuery, - openHistory, - share, - refetchSchema, - }, + mapStateToProps, + { + editEndpoint, + prettifyQuery, + openHistory, + share, + refetchSchema, + }, )(TopBar) export const Button = styled.button` - text-transform: uppercase; - font-weight: 600; - color: ${(p) => p.theme.editorColours.buttonText}; - background: ${(p) => p.theme.editorColours.button}; - border-radius: 2px; - flex: 0 0 auto; - letter-spacing: 0.53px; - font-size: 14px; - padding: 6px 9px 7px 10px; - margin-left: 6px; - - cursor: pointer; - transition: 0.1s linear background-color; - &:first-child { - margin-left: 0; - } - &:hover { - background-color: ${(p) => p.theme.editorColours.buttonHover}; - } + text-transform: uppercase; + font-weight: 600; + color: ${p => p.theme.editorColours.buttonText}; + background: ${p => p.theme.editorColours.button}; + border-radius: 2px; + flex: 0 0 auto; + letter-spacing: 0.53px; + font-size: 14px; + padding: 6px 9px 7px 10px; + margin-left: 6px; + + cursor: pointer; + transition: 0.1s linear background-color; + &:first-child { + margin-left: 0; + } + &:hover { + background-color: ${p => p.theme.editorColours.buttonHover}; + } ` const TopBarWrapper = styled.div` - display: flex; - background: ${(p) => p.theme.editorColours.navigationBar}; - padding: 10px 10px 4px; - align-items: center; + display: flex; + background: ${p => p.theme.editorColours.navigationBar}; + padding: 10px 10px 4px; + align-items: center; ` interface UrlBarProps { - active: boolean + active: boolean } const UrlBar = styled('input')` - background: ${(p) => p.theme.editorColours.button}; - border-radius: 4px; - color: ${(p) => - p.active - ? p.theme.editorColours.navigationBarText - : p.theme.editorColours.textInactive}; - border: 1px solid ${(p) => p.theme.editorColours.background}; - padding: 6px 12px; - font-size: 13px; - flex: 1; + background: ${p => p.theme.editorColours.button}; + border-radius: 4px; + color: ${p => + p.active + ? p.theme.editorColours.navigationBarText + : p.theme.editorColours.textInactive}; + border: 1px solid ${p => p.theme.editorColours.background}; + padding: 6px 12px; + font-size: 13px; + flex: 1; ` const UrlBarWrapper = styled.div` - flex: 1; - margin-left: 6px; - position: relative; - display: flex; - align-items: center; + flex: 1; + margin-left: 6px; + position: relative; + display: flex; + align-items: center; ` const ReachError = styled.div` - position: absolute; - right: 5px; - display: flex; - align-items: center; - color: #f25c54; + position: absolute; + right: 5px; + display: flex; + align-items: center; + color: #f25c54; ` const Pulse = styled.div` - width: 16px; - height: 16px; - background-color: ${(p) => p.theme.editorColours.icon}; - border-radius: 100%; + width: 16px; + height: 16px; + background-color: ${p => p.theme.editorColours.icon}; + border-radius: 100%; ` const SpinnerWrapper = styled.div` - position: relative; - margin: 6px; + position: relative; + margin: 6px; ` const Spinner = () => ( - - - + + + ) diff --git a/packages/graphql-playground-react/src/components/Playground/util/immutableMemoize.ts b/packages/graphql-playground-react/src/components/Playground/util/immutableMemoize.ts index 5e92b4a90..9828d61f5 100644 --- a/packages/graphql-playground-react/src/components/Playground/util/immutableMemoize.ts +++ b/packages/graphql-playground-react/src/components/Playground/util/immutableMemoize.ts @@ -1,12 +1,12 @@ import { is } from 'immutable' export function immutableMemoize(fn) { - let lastValue - return (arg) => { - const newValue = fn(arg) - if (!is(lastValue, newValue)) { - lastValue = newValue - } - return lastValue - } + let lastValue + return arg => { + const newValue = fn(arg) + if (!is(lastValue, newValue)) { + lastValue = newValue + } + return lastValue + } } diff --git a/packages/graphql-playground-react/src/components/util.ts b/packages/graphql-playground-react/src/components/util.ts index 2508ac6a5..2e818134e 100644 --- a/packages/graphql-playground-react/src/components/util.ts +++ b/packages/graphql-playground-react/src/components/util.ts @@ -1,34 +1,34 @@ import { GraphQLConfig, GraphQLConfigEnpointConfig } from '../graphqlConfig' export function getActiveEndpoints( - config: GraphQLConfig, - envName: string, - projectName?: string, + config: GraphQLConfig, + envName: string, + projectName?: string, ): { endpoint: string; subscriptionEndpoint?: string; headers?: any } { - if (projectName) { - const env = config.projects![projectName].extensions!.endpoints![envName]! - return getEndpointFromEndpointConfig(env) - } else { - const env = config.extensions!.endpoints![envName]! - return getEndpointFromEndpointConfig(env) - } + if (projectName) { + const env = config.projects![projectName].extensions!.endpoints![envName]! + return getEndpointFromEndpointConfig(env) + } else { + const env = config.extensions!.endpoints![envName]! + return getEndpointFromEndpointConfig(env) + } } export function getEndpointFromEndpointConfig( - env: GraphQLConfigEnpointConfig | string, + env: GraphQLConfigEnpointConfig | string, ) { - if (typeof env === 'string') { - return { - endpoint: env, - subscriptionEndpoint: undefined, - } - } else { - return { - endpoint: env.url, - subscriptionEndpoint: env.subscription - ? env.subscription!.url - : undefined, - headers: env.headers, - } - } + if (typeof env === 'string') { + return { + endpoint: env, + subscriptionEndpoint: undefined, + } + } else { + return { + endpoint: env.url, + subscriptionEndpoint: env.subscription + ? env.subscription!.url + : undefined, + headers: env.headers, + } + } } diff --git a/packages/graphql-playground-react/src/localDevIndex.tsx b/packages/graphql-playground-react/src/localDevIndex.tsx index 392d85c87..6b4838197 100644 --- a/packages/graphql-playground-react/src/localDevIndex.tsx +++ b/packages/graphql-playground-react/src/localDevIndex.tsx @@ -9,29 +9,29 @@ import { HttpLink } from 'apollo-link-http' // import { exampleSchema } from './fixtures/exampleSchema' if (process.env.NODE_ENV !== 'production') { - /* tslint:disable-next-line */ - // const { whyDidYouUpdate } = require('why-did-you-update') - // whyDidYouUpdate(React) + /* tslint:disable-next-line */ + // const { whyDidYouUpdate } = require('why-did-you-update') + // whyDidYouUpdate(React) } /* tslint:disable-next-line */ ;(window as any)['GraphQLPlayground'] = { - init(element: HTMLElement, options) { - ReactDOM.render( - , - element, - ) - }, + init(element: HTMLElement, options) { + ReactDOM.render( + , + element, + ) + }, } // const configString = `projects: @@ -92,19 +92,19 @@ if (process.env.NODE_ENV !== 'production') { // ] const customLinkCreator = ( - session: LinkCreatorProps, - wsEndpoint?: string, + session: LinkCreatorProps, + wsEndpoint?: string, ): { link: ApolloLink } => { - const { headers, credentials } = session + const { headers, credentials } = session - const link = new HttpLink({ - uri: session.endpoint, - fetch, - headers, - credentials, - }) + const link = new HttpLink({ + uri: session.endpoint, + fetch, + headers, + credentials, + }) - return { link } + return { link } } // const lightEditorColours = { diff --git a/packages/graphql-playground-react/src/state/docs/actions.ts b/packages/graphql-playground-react/src/state/docs/actions.ts index 8b52673e8..074324dfc 100644 --- a/packages/graphql-playground-react/src/state/docs/actions.ts +++ b/packages/graphql-playground-react/src/state/docs/actions.ts @@ -1,23 +1,23 @@ import { createActions } from 'redux-actions' export const { - setStacks, - addStack, - toggleDocs, - setDocsVisible, - changeWidthDocs, - changeKeyMove, - showDocForReference, + setStacks, + addStack, + toggleDocs, + setDocsVisible, + changeWidthDocs, + changeKeyMove, + showDocForReference, } = createActions({ - SET_STACKS: (sessionId, stacks) => ({ sessionId, stacks }), - ADD_STACK: (sessionId, field, x, y) => ({ sessionId, field, x, y }), - TOGGLE_DOCS: (sessionId, activeTabIdx) => ({ sessionId, activeTabIdx }), - SET_DOCS_VISIBLE: (sessionId, open, activeTabIdx?) => ({ - sessionId, - open, - activeTabIdx, - }), - CHANGE_WIDTH_DOCS: (sessionId, width) => ({ sessionId, width }), - CHANGE_KEY_MOVE: (sessionId, move) => ({ sessionId, move }), - SHOW_DOC_FOR_REFERENCE: (reference) => ({ reference }), + SET_STACKS: (sessionId, stacks) => ({ sessionId, stacks }), + ADD_STACK: (sessionId, field, x, y) => ({ sessionId, field, x, y }), + TOGGLE_DOCS: (sessionId, activeTabIdx) => ({ sessionId, activeTabIdx }), + SET_DOCS_VISIBLE: (sessionId, open, activeTabIdx?) => ({ + sessionId, + open, + activeTabIdx, + }), + CHANGE_WIDTH_DOCS: (sessionId, width) => ({ sessionId, width }), + CHANGE_KEY_MOVE: (sessionId, move) => ({ sessionId, move }), + SHOW_DOC_FOR_REFERENCE: reference => ({ reference }), }) diff --git a/packages/graphql-playground-react/src/utils.ts b/packages/graphql-playground-react/src/utils.ts index 19b12e1a3..4b2f1cc94 100644 --- a/packages/graphql-playground-react/src/utils.ts +++ b/packages/graphql-playground-react/src/utils.ts @@ -3,27 +3,27 @@ import * as graphql from 'prettier/parser-graphql' // tslint:disable export function safely(cb: any) { - return function*(...args) { - try { - yield cb(...args) - } catch (e) { - console.error(e) - } - } + return function*(...args) { + try { + yield cb(...args) + } catch (e) { + console.error(e) + } + } } export function prettify(query: string, printWidth: number) { - return prettier.format(query, { - parser: 'graphql', - printWidth, - plugins: [graphql], - }) + return prettier.format(query, { + parser: 'graphql', + printWidth, + plugins: [graphql], + }) } export function isIframe() { - try { - return window.self !== window.top - } catch (e) { - return true - } + try { + return window.self !== window.top + } catch (e) { + return true + } } From 9c2f2aff1c024efb03a54a5cfc31c06d23125773 Mon Sep 17 00:00:00 2001 From: rajinwonderland Date: Tue, 4 Dec 2018 18:02:43 -0800 Subject: [PATCH 04/10] Fixes and style changes matching @kuldar's design specs. Details on https://github.com/prisma/graphql-playground/pull/897 comments --- .../Playground/DocExplorer/SearchBox.tsx | 2 +- .../Playground/ExplorerTabs/SideTab.tsx | 5 +- .../Playground/ExplorerTabs/SideTabs.tsx | 5 +- .../Playground/SchemaExplorer/SDLEditor.tsx | 38 ++++- .../Playground/SchemaExplorer/SDLHeader.tsx | 160 ++++++++++++++++++ .../SchemaExplorer/SDLTypes/SDLStyles.tsx | 145 +--------------- .../Playground/SchemaExplorer/SDLView.tsx | 7 +- 7 files changed, 205 insertions(+), 157 deletions(-) create mode 100644 packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLHeader.tsx diff --git a/packages/graphql-playground-react/src/components/Playground/DocExplorer/SearchBox.tsx b/packages/graphql-playground-react/src/components/Playground/DocExplorer/SearchBox.tsx index f66763e38..e895a2dfb 100644 --- a/packages/graphql-playground-react/src/components/Playground/DocExplorer/SearchBox.tsx +++ b/packages/graphql-playground-react/src/components/Playground/DocExplorer/SearchBox.tsx @@ -43,7 +43,7 @@ export default class SearchBox extends React.Component { onChange={this.handleChange} type="text" value={this.state.value} - placeholder={this.props.placeholder || 'Search the schema ...'} + placeholder={this.props.placeholder || 'Search the docs ...'} /> ) diff --git a/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx index 299dad9c4..3ee056d57 100644 --- a/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx +++ b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx @@ -27,7 +27,7 @@ export interface TabProps { const Tab = styled('div')` z-index: ${p => (p.active ? 10 : 2)}; - padding: 6px 10px; + padding: 8px; border-top-left-radius: 2px; border-top-right-radius: 2px; color: ${p => @@ -45,11 +45,10 @@ const Tab = styled('div')` text-align: center; font-weight: 600; font-size: 12px; - line-height: 17px; + line-height: 12px; letter-spacing: 0.45px; cursor: pointer; transform: rotate(-90deg); transform-origin: bottom left; - width: 60px; margin-top: 65px; ` diff --git a/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx index 0b9feca8c..92831b375 100644 --- a/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx +++ b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx @@ -87,7 +87,7 @@ class SideTabs extends React.Component< } } - componentDidReceiveProps(prevProps) { + componentDidUpdate(prevProps) { if (!prevProps.docs.activeTabIdx && this.props.docs.activeTabIdx) { this.props.setDocsVisible( this.props.sessionId, @@ -336,7 +336,8 @@ const TabContentContainer = styled.div` height: 100%; letter-spacing: 0.3px; box-shadow: -1px 1px 6px 0 rgba(0, 0, 0, 0.3); - + outline: none; + user-select: none; &::before { top: 0; bottom: 0; diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLEditor.tsx b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLEditor.tsx index fa08d08ea..39f9ca763 100644 --- a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLEditor.tsx +++ b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLEditor.tsx @@ -13,14 +13,16 @@ export interface Props { settings: ISettings } -class SDLEditor extends React.PureComponent { +class SDLEditor extends React.PureComponent { cachedValue: string private editor: any private node: any constructor(props) { super(props) - + this.state = { + overflowY: false, + } // 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. @@ -53,12 +55,13 @@ class SDLEditor extends React.PureComponent { tabSize: 1, mode: 'graphql', theme: 'graphiql', - lineWrapping: true, + // lineWrapping: true, keyMap: 'sublime', readOnly: true, gutters, }) ;(global as any).editor = this.editor + this.editor.on('scroll', this.handleScroll) this.editor.refresh() } @@ -96,12 +99,26 @@ class SDLEditor extends React.PureComponent { } componentWillUnmount() { + this.editor.off('scroll') this.editor = null } + handleScroll = e => { + if (e.doc.scrollTop > 0) { + return this.setState({ + overflowY: true, + }) + } + return this.setState({ + overflowY: false, + }) + } + render() { + const { overflowY } = this.state return ( + {overflowY && } ) @@ -127,6 +144,19 @@ const Editor = styled.div` overflow-x: hidden; overflow-y: scroll; .CodeMirror { - background: ${p => p.theme.editorColours.editorBackground}; + background: ${p => + p.theme.mode === 'dark' + ? p.theme.editorColours.editorBackground + : 'white'}; + padding-left: 20px; } ` +const OverflowShadow = styled.div` + position: fixed: + top: 0; + left: 0; + right: 0; + height: 1px; + box-shadow: 0px 1px 3px rgba(17, 17, 17, 0.1); + z-index: 1000; +` diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLHeader.tsx b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLHeader.tsx new file mode 100644 index 000000000..d9c93d5f1 --- /dev/null +++ b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLHeader.tsx @@ -0,0 +1,160 @@ +import * as React from 'react' +import { styled } from '../../../styled' +import { Button } from '../TopBar/TopBar' +import { GraphQLSchema } from 'graphql' +import { downloadSchema } from '../util/createSDL' + +interface SDLHeaderProps { + schema: GraphQLSchema +} + +interface State { + open: boolean +} + +export default class SDLHeader extends React.Component { + ref + private node: any + constructor(props) { + super(props) + this.state = { + open: false, + } + } + + componentWillMount() { + document.addEventListener('mousedown', this.handleClick, false) + } + + componentWillUnmount() { + document.removeEventListener('mousedown', this.handleClick, false) + } + + handleClick = e => { + if (this.node.contains(e.target)) { + return + } + return this.setState({ + open: false, + }) + } + + showOptions = () => { + this.setState({ + open: !this.state.open, + }) + } + + printSDL = () => { + return downloadSchema(this.props.schema, 'sdl') + } + + printIntrospection = () => { + return downloadSchema(this.props.schema, 'json') + } + + setRef = ref => { + this.node = ref + } + + render() { + const { open } = this.state + return ( + + Schema + + + Download + + {open && ( + + + + + )} + + + ) + } +} + +const SchemaHeader = styled.div` + display: flex; + height: 64px; + width: 100%; + align-items: center; + justify-content: space-between; + outline: none; + user-select: none; +` + +const Box = styled.div` + position: absolute; + top: 16px; + right: 2em; + width: 108px; + display: flex; + flex-wrap: wrap; + flex-direction: column; +` + +const Title = styled.div` + flex: 1; + color: ${p => styleHelper(p).title}; + cursor: default; + font-size: 14px; + font-weight: 600; + text-transform: uppercase !important; + font-family: 'Open Sans', sans-serif !important; + letter-spacing: 1px; + user-select: none !important; + padding: 16px; +` + +const Download = styled(Button)` + flex: 1; + color: ${p => styleHelper(p).download['text']}; + background: ${p => styleHelper(p).download['button']}; + height: 32px; + border-radius: 2px; + &:hover { + color: ${p => styleHelper(p).buttonTextHover}; + background-color: ${p => styleHelper(p).buttonHover}; + } +` + +const Option = styled(Download)` + text-align: left; + width: 100%; + margin-left: 0px; + border-radius: 0px; + z-index: 2000; + background: ${p => styleHelper(p).button}; +` + +const styleHelper = p => { + if (p.theme.mode === 'dark') { + return { + title: 'white', + download: { + text: p.open ? '#8B959C' : 'white', + button: p.theme.colours.darkBlue, + }, + buttonText: 'white', + button: p.theme.colours.darkBlue, + buttonHover: '#2B3C48', + buttonTextHover: 'white', + } + } + return { + title: p.theme.colours.darkBlue, + download: { + text: p.open ? 'rgba(61, 88, 102, 0.5)' : p.theme.colours.darkBlue, + button: '#f6f6f6', + }, + buttonText: p.theme.colours.darkBlue, + button: '#f6f6f6', + buttonHover: '#EDEDED', + buttonTextHover: p.theme.colours.darkBlue, + } +} diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLStyles.tsx b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLStyles.tsx index 8c7187738..ea87d1389 100644 --- a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLStyles.tsx +++ b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLTypes/SDLStyles.tsx @@ -1,66 +1,7 @@ import * as React from 'react' import { styled } from '../../../../styled' -import { Button } from '../../TopBar/TopBar' -import { GraphQLSchema } from 'graphql' -import { downloadSchema } from '../../util/createSDL' import { columnWidth } from '../../../../constants' -interface SDLHeaderProps { - schema: GraphQLSchema -} - -interface State { - open: boolean -} - -class SDLHeader extends React.Component { - constructor(props) { - super(props) - this.state = { - open: false, - } - } - - showOptions = () => { - this.setState({ - open: !this.state.open, - }) - } - - printSDL = () => { - return downloadSchema(this.props.schema, 'sdl') - } - - printIntrospection = () => { - return downloadSchema(this.props.schema, 'json') - } - - render() { - const { open } = this.state - return ( - - Schema - - - Download - - {open && ( - - - - - )} - - - ) - } -} - -export { SDLHeader } export const SchemaExplorerContainer = styled.div` position: relative; height: 100%; @@ -69,65 +10,14 @@ export const SchemaExplorerContainer = styled.div` flex-direction: column; flex-wrap: wrap; align-items: stretch; - padding: 8px; - background: ${p => styleHelper(p).secBackground}; + padding: 0px 8px 8px 8px; + background: ${p => + p.theme.mode === 'dark' ? p.theme.editorColours.editorBackground : 'white'}; font-family: ${p => p.theme.settings['editor.fontFamily']}; font-size: ${p => `${p.theme.settings['editor.fontSize']}px`}; outline: none !important; ` -const SchemaHeader = styled.div` - display: flex; - height: 64px; - width: 100%; - align-items: center; - justify-content: space-between; -` - -const Box = styled.div` - position: absolute; - top: 16px; - right: 2em; - width: 108px; - display: flex; - flex-wrap: wrap; - flex-direction: column; -` - -const Title = styled.div` - flex: 1; - color: ${p => styleHelper(p).title}; - cursor: default; - font-size: 14px; - font-weight: 600; - text-transform: uppercase !important; - font-family: 'Open Sans', sans-serif !important; - letter-spacing: 1px; - user-select: none; - padding: 16px; -` - -const Download = styled(Button)` - flex: 1; - color: ${p => styleHelper(p).download['text']}; - background: ${p => styleHelper(p).download['button']}; - padding: 12px 9px 12px 9px; - border-radius: 0px; - &:hover { - color: ${p => styleHelper(p).buttonTextHover}; - background-color: ${p => styleHelper(p).buttonHover}; - } -` - -const Option = styled(Download)` - text-align: left; - width: 100%; - margin-left: 0px; - border-radius: 0px; - z-index: 2000; - background: ${p => styleHelper(p).button}; -` - export interface SDLColumnProps { children: any width?: number @@ -147,32 +37,3 @@ const Column = styled('div')` border-right: 1px solid ${p => p.theme.colours.black10}; overflow: hidden; ` - -const styleHelper = p => { - if (p.theme.mode === 'dark') { - return { - secBackground: p.theme.editorColours.navigationBar, - title: 'white', - download: { - text: p.open ? p.theme.colours.white30 : 'white', - button: p.open ? '#2e5482' : p.theme.colours.blue, - }, - buttonText: 'white', - button: p.alternate ? '#386bac' : p.theme.colours.blue, - buttonHover: '#2e5482', - buttonTextHover: 'white', - } - } - return { - secBackground: 'white', - download: { - text: p.open ? 'rgba(61, 88, 102, 0.5)' : '#3D5866', - button: '#f6f6f6', - }, - title: 'rgba(0, 0, 0, 0.3)', - buttonText: '#3d5866', - button: p.alternate ? '#EDEDED' : '#f6f6f6', - buttonHover: '#f6f6f6', - buttonTextHover: 'rgba(61, 88, 102, 0.5)', - } -} diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLView.tsx b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLView.tsx index 62e8b2a5f..7050a23f6 100644 --- a/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLView.tsx +++ b/packages/graphql-playground-react/src/components/Playground/SchemaExplorer/SDLView.tsx @@ -13,11 +13,8 @@ import { getSelectedSessionIdFromRoot } from '../../../state/sessions/selectors' import { getSessionDocs } from '../../../state/docs/selectors' import { createStructuredSelector } from 'reselect' import { ErrorContainer } from '../DocExplorer/ErrorContainer' -import { - SDLHeader, - SchemaExplorerContainer, - SDLColumn, -} from './SDLTypes/SDLStyles' +import { SchemaExplorerContainer, SDLColumn } from './SDLTypes/SDLStyles' +import SDLHeader from './SDLHeader' import SDLEditor from './SDLEditor' import { getSettings } from '../../../state/workspace/reducers' From 545b7e95bdf23782f49a905d184060004f2433e3 Mon Sep 17 00:00:00 2001 From: rajinwonderland Date: Tue, 4 Dec 2018 18:23:05 -0800 Subject: [PATCH 05/10] Still not able to cmd+save settings. Might be a deeper issue. However, save button saves disabled comments From b8f4df9ce1a231379071523628b759628a5c0f21 Mon Sep 17 00:00:00 2001 From: rajinwonderland Date: Wed, 5 Dec 2018 11:44:19 -0800 Subject: [PATCH 06/10] Added tabWidth prop to SideTab.tsx to provide specific widths for different tabs. --- .../components/Playground/ExplorerTabs/SideTab.tsx | 13 ++++++++++--- .../src/components/Playground/GraphQLEditor.tsx | 4 ++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx index 3ee056d57..2ade189e0 100644 --- a/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx +++ b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx @@ -6,14 +6,20 @@ export interface Props { activeColor: string children: any active?: boolean + tabWidth?: string onClick?: () => any } export default class SideTab extends React.PureComponent { render() { - const { label, activeColor, active, onClick } = this.props + const { label, activeColor, active, onClick, tabWidth } = this.props return ( - + {label} ) @@ -27,7 +33,7 @@ export interface TabProps { const Tab = styled('div')` z-index: ${p => (p.active ? 10 : 2)}; - padding: 8px; + padding: 8px 8px 8px 8px; border-top-left-radius: 2px; border-top-right-radius: 2px; color: ${p => @@ -51,4 +57,5 @@ const Tab = styled('div')` transform: rotate(-90deg); transform-origin: bottom left; margin-top: 65px; + width: ${p => p.tabWidth || '100%'}; ` diff --git a/packages/graphql-playground-react/src/components/Playground/GraphQLEditor.tsx b/packages/graphql-playground-react/src/components/Playground/GraphQLEditor.tsx index a195edcd9..e69514b7b 100644 --- a/packages/graphql-playground-react/src/components/Playground/GraphQLEditor.tsx +++ b/packages/graphql-playground-react/src/components/Playground/GraphQLEditor.tsx @@ -261,13 +261,13 @@ class GraphQLEditor extends React.PureComponent { - + - + Date: Thu, 6 Dec 2018 12:45:17 -0800 Subject: [PATCH 07/10] Fix for Tab Spacing in collapsed state Fix for additional line-breaks after each item --- .../Playground/ExplorerTabs/SideTab.tsx | 3 +- .../Playground/ExplorerTabs/SideTabs.tsx | 2 +- .../components/Playground/util/createSDL.ts | 35 ++++++++++++++----- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx index 2ade189e0..83214db0b 100644 --- a/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx +++ b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTab.tsx @@ -34,8 +34,7 @@ export interface TabProps { const Tab = styled('div')` z-index: ${p => (p.active ? 10 : 2)}; padding: 8px 8px 8px 8px; - border-top-left-radius: 2px; - border-top-right-radius: 2px; + border-radius: 2px 2px 0px 0px; color: ${p => p.theme.mode === 'dark' ? p.theme.colours.white diff --git a/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx index 92831b375..80bd94e9a 100644 --- a/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx +++ b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx @@ -303,7 +303,7 @@ const Tabs = styled('div')` outline: none; box-shadow: 0 0 8px rgba(0, 0, 0, 0.15); position: absolute; - right: -2px; + right: 0px; z-index: ${p => (p.open ? 2000 : 3)}; height: 100%; font-family: 'Open Sans', sans-serif; diff --git a/packages/graphql-playground-react/src/components/Playground/util/createSDL.ts b/packages/graphql-playground-react/src/components/Playground/util/createSDL.ts index 84638b81f..d00071fd3 100644 --- a/packages/graphql-playground-react/src/components/Playground/util/createSDL.ts +++ b/packages/graphql-playground-react/src/components/Playground/util/createSDL.ts @@ -61,22 +61,41 @@ function getTypeInstance(type) { } } +// Adds Line Breaks to Schema View +function addLineBreaks(sdl: string, commentsDisabled?: boolean) { + const noNewLines = sdl.replace(/^\s*$(?:\r\n?|\n)/gm, '') + // Line Break all Brackets + const breakBrackets = noNewLines.replace(/[}]/gm, '$&\r\n') + // Line Break all Scalars + const withLineBreaks = breakBrackets.replace(/(?:scalar )\w+/g, '$&\r\n') + + if (commentsDisabled) { + return withLineBreaks + } + // Special Line Breaking for Comments + const withCommentLineBreaks = withLineBreaks.replace( + /(?:\#[\w\'\s\r\n\*](.*)$)/gm, + '$&\r', + ) + return withCommentLineBreaks +} + // Returns a prettified schema export function getSDL( schema: GraphQLSchema | null | undefined, commentsDisabled: boolean, ) { if (schema instanceof GraphQLSchema) { - const sdl = printSchema(schema, { commentDescriptions: false }) + const rawSdl = printSchema(schema, { commentDescriptions: false }) if (commentsDisabled) { - const sdlWithNewLines = sdl.replace(/(\#[\w\'\s\r\n\*](.*)$)/gm, '') - const sdlWithoutComments = prettify(sdlWithNewLines, 80).replace( - /^\s*$(?:\r\n?|\n)/gm, - '', - ) - return sdlWithoutComments + // Removes Comments but still has new lines + const sdlWithNewLines = rawSdl.replace(/(\#[\w\'\s\r\n\*](.*)$)/gm, '') + // Removes newlines left behind by Comments + const sdlWithoutComments = prettify(sdlWithNewLines, 80) + return addLineBreaks(sdlWithoutComments, commentsDisabled) } - return sdl + const sdl = prettify(rawSdl, 80) + return addLineBreaks(sdl) } return '' } From 60d99444dbe3f6cddb7fb14394b2c85aee9273ab Mon Sep 17 00:00:00 2001 From: rajinwonderland Date: Tue, 18 Dec 2018 15:22:21 -0800 Subject: [PATCH 08/10] Updates for using 'esc' on keydown to close tabs --- .../components/Playground/ExplorerTabs/SideTabs.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx index 80bd94e9a..c963ed331 100644 --- a/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx +++ b/packages/graphql-playground-react/src/components/Playground/ExplorerTabs/SideTabs.tsx @@ -98,7 +98,13 @@ class SideTabs extends React.Component< if (prevProps.activeTabIdx && !this.props.docs.activeTabIdx) { this.props.setDocsVisible(this.props.sessionId, false) } - return this.setWidth() + if ( + this.props.docs.activeTabIdx !== prevProps.docs.activeTabIdx && + this.refContentContainer + ) { + this.refContentContainer.focus() + } + this.setWidth() } componentDidMount() { @@ -164,9 +170,6 @@ class SideTabs extends React.Component< } private handleTabClick = idx => () => { - if (!this.props.docs.docsOpen && this.refContentContainer) { - this.refContentContainer.focus() - } if (this.props.docs.activeTabIdx === idx) { this.props.setDocsVisible(this.props.sessionId, false) return this.setWidth() From a4ecd48707278784e418bf6abf60ebfd9c6fdc53 Mon Sep 17 00:00:00 2001 From: rajinwonderland Date: Wed, 19 Dec 2018 15:08:46 -0800 Subject: [PATCH 09/10] Updated createSDL.tsx Schema will now default to true for commentsDisabled and commentDescription properties --- .../src/components/Playground/util/createSDL.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/graphql-playground-react/src/components/Playground/util/createSDL.ts b/packages/graphql-playground-react/src/components/Playground/util/createSDL.ts index d00071fd3..63546d8bd 100644 --- a/packages/graphql-playground-react/src/components/Playground/util/createSDL.ts +++ b/packages/graphql-playground-react/src/components/Playground/util/createSDL.ts @@ -62,7 +62,7 @@ function getTypeInstance(type) { } // Adds Line Breaks to Schema View -function addLineBreaks(sdl: string, commentsDisabled?: boolean) { +function addLineBreaks(sdl: string, commentsDisabled?: boolean = true) { const noNewLines = sdl.replace(/^\s*$(?:\r\n?|\n)/gm, '') // Line Break all Brackets const breakBrackets = noNewLines.replace(/[}]/gm, '$&\r\n') @@ -83,10 +83,10 @@ function addLineBreaks(sdl: string, commentsDisabled?: boolean) { // Returns a prettified schema export function getSDL( schema: GraphQLSchema | null | undefined, - commentsDisabled: boolean, + commentsDisabled: boolean = true, ) { if (schema instanceof GraphQLSchema) { - const rawSdl = printSchema(schema, { commentDescriptions: false }) + const rawSdl = printSchema(schema, { commentDescriptions: true }) if (commentsDisabled) { // Removes Comments but still has new lines const sdlWithNewLines = rawSdl.replace(/(\#[\w\'\s\r\n\*](.*)$)/gm, '') From 11743437cb0a04070169ef494e7cfc1931ce9b4a Mon Sep 17 00:00:00 2001 From: rajinwonderland Date: Wed, 19 Dec 2018 16:13:25 -0800 Subject: [PATCH 10/10] Fixed Electron --- packages/graphql-playground-electron/yarn.lock | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/graphql-playground-electron/yarn.lock b/packages/graphql-playground-electron/yarn.lock index a83870a16..8be332b84 100644 --- a/packages/graphql-playground-electron/yarn.lock +++ b/packages/graphql-playground-electron/yarn.lock @@ -3695,7 +3695,6 @@ graphql-config-extension-graphcool@1.0.8: graphql-config-extension-prisma@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/graphql-config-extension-prisma/-/graphql-config-extension-prisma-0.2.5.tgz#f67db5b6f882ac76096fb2e68a18ec81c7d51fd2" - integrity sha512-7Qh3TzZS3hwZpJbTNfTHXBM6UbzV7DMik9Mc95Rz76yTAs7Wr83xBFsH4Ap1NWlqBgANfO3cLLI4YomDJmO5SA== dependencies: graphql-config "2.2.1" prisma-yml "1.20.0-beta.18" @@ -3766,10 +3765,9 @@ graphql-playground-html@^1.6.0: version "1.6.5" resolved "https://registry.yarnpkg.com/graphql-playground-html/-/graphql-playground-html-1.6.5.tgz#0de1a68db891f7356312cf03dcf371d67388e95a" -graphql-playground-react@1.7.11: +graphql-playground-react@^1.7.11: version "1.7.11" resolved "https://registry.yarnpkg.com/graphql-playground-react/-/graphql-playground-react-1.7.11.tgz#a45a1c500de9c4c22a32e3c00ada17cd1a154785" - integrity sha512-ndEoQRHY313mQ+m017KfRcUwCS7b0F+bc67uLShHSpN071oieOI86RTgf9o87Ylq5RI2CfyKRSZyCle6EBooGg== dependencies: apollo-link "^1.0.7" apollo-link-http "^1.3.2"