Skip to content

Commit

Permalink
Schema polling autoupdate (#934)
Browse files Browse the repository at this point in the history
* Merged from sdl-tabs https://github.com/Novvum/graphql-playground.git

* Added and integrated setting's key for 'schema.disableComments'

* Merged changes from disableComments branch

* Fixes and style changes matching @kuldar's design specs. Details on graphql/graphql-playground#897 comments

* Still not able to cmd+save settings. Might be a deeper issue. However, save button saves disabled comments

* Added tabWidth prop to SideTab.tsx to provide specific widths for different tabs.

* Fix for Tab Spacing in collapsed state
Fix for additional line-breaks after each item

* ugly version is done

* moved icons to the left

* making polling global

* Updates for using 'esc' on keydown to close tabs

* added polling config

* Updated createSDL.tsx

Schema will now default to true for commentsDisabled
and commentDescription properties

* Fixed Electron

* Hiding reload icon if polling is enabled

* * Updated schema polling icon
* Schema only updates in state if it has changed

* code cleanup

* Merged from sdl-tabs https://github.com/Novvum/graphql-playground.git

* Added and integrated setting's key for 'schema.disableComments'

* Merged changes from disableComments branch

* Fixes and style changes matching @kuldar's design specs. Details on graphql/graphql-playground#897 comments

* Added tabWidth prop to SideTab.tsx to provide specific widths for different tabs.

* Fix for Tab Spacing in collapsed state
Fix for additional line-breaks after each item

* ugly version is done

* moved icons to the left

* making polling global

* Updates for using 'esc' on keydown to close tabs

* added polling config

* Updated createSDL.tsx

Schema will now default to true for commentsDisabled
and commentDescription properties

* Fixed Electron

* Hiding reload icon if polling is enabled

* * Updated schema polling icon
* Schema only updates in state if it has changed

* code cleanup

* removed questiomark

* removed duplicate funciton

* updated files from upstream

* yarn.lock from upstream

* using printSchema instead of JSON.stringify to compare schemas

* reusing reload icon

* "Refresh to see changes" feature in SCHEMA tab

* disabled animation for polling

* changed polling icon and moved back to right side.

* automatically updating schema view without scrolling when schema updates

* moved reload icon back to the left

* automatically updating schema view without scrolling when schema updates

* more accurate schema diff checking
  • Loading branch information
filippbudko committed Jan 27, 2019
1 parent 16b49d2 commit 7e290dc
Show file tree
Hide file tree
Showing 17 changed files with 242 additions and 42 deletions.
29 changes: 25 additions & 4 deletions packages/graphql-playground-react/src/components/Playground.tsx
Expand Up @@ -27,7 +27,7 @@ import {
} from '../state/sessions/actions'
import { setConfigString } from '../state/general/actions'
import { initState } from '../state/workspace/actions'
import { GraphQLSchema } from 'graphql'
import { GraphQLSchema, printSchema } from 'graphql'
import { createStructuredSelector } from 'reselect'
import {
getIsConfigTab,
Expand Down Expand Up @@ -143,7 +143,11 @@ export class Playground extends React.PureComponent<Props & ReduxProps, State> {
if (props.schema) {
return
}
if (this.mounted && this.state.schema) {
if (
this.mounted &&
this.state.schema &&
!props.settings['schema.enablePolling']
) {
this.setState({ schema: undefined })
}
let first = true
Expand Down Expand Up @@ -243,6 +247,7 @@ export class Playground extends React.PureComponent<Props & ReduxProps, State> {
async schemaGetter(propsInput?: Props & ReduxProps) {
const props = this.props || propsInput
const endpoint = props.sessionEndpoint || props.endpoint
const currentSchema = this.state.schema
try {
const data = {
endpoint,
Expand All @@ -258,11 +263,11 @@ export class Playground extends React.PureComponent<Props & ReduxProps, State> {
data.endpoint === this.props.endpoint ||
data.endpoint === this.props.sessionEndpoint
) {
this.setState({ schema: newSchema })
this.updateSchema(currentSchema, newSchema, props)
}
})
if (schema) {
this.setState({ schema: schema.schema })
this.updateSchema(currentSchema, schema.schema, props)
this.props.schemaFetchingSuccess(data.endpoint, schema.tracingSupported)
this.backoff.stop()
}
Expand Down Expand Up @@ -349,6 +354,22 @@ export class Playground extends React.PureComponent<Props & ReduxProps, State> {
)
}

private updateSchema(
currentSchema: GraphQLSchema | undefined,
newSchema: GraphQLSchema,
props: Readonly<{ children?: React.ReactNode }> &
Readonly<Props & ReduxProps>,
) {
const currentSchemaStr = currentSchema ? printSchema(currentSchema) : null
const newSchemaStr = printSchema(newSchema)
if (
newSchemaStr !== currentSchemaStr ||
!props.settings['schema.enablePolling']
) {
this.setState({ schema: newSchema })
}
}

get httpApiPrefix() {
return this.props.endpoint.match(/(https?:\/\/.*?)\/?/)![1]
}
Expand Down
@@ -1,5 +1,5 @@
import * as React from 'react'
import { GraphQLSchema } from 'graphql'
import { GraphQLSchema, printSchema } from 'graphql'
import EditorWrapper from '../EditorWrapper'
import { styled } from '../../../styled'
import { getSDL } from '../util/createSDL'
Expand Down Expand Up @@ -64,10 +64,12 @@ class SDLEditor extends React.PureComponent<Props, { overflowY: boolean }> {
this.editor.on('scroll', this.handleScroll)
this.editor.refresh()
}

componentDidUpdate(prevProps: Props) {
const CodeMirror = require('codemirror')
if (this.props.schema !== prevProps.schema) {
const currentSchemaStr = this.props.schema && printSchema(this.props.schema)
const prevSchemaStr = prevProps.schema && printSchema(prevProps.schema)
if (currentSchemaStr !== prevSchemaStr) {
const initialScroll = this.editor.getScrollInfo()
this.cachedValue =
getSDL(
this.props.schema,
Expand All @@ -79,6 +81,9 @@ class SDLEditor extends React.PureComponent<Props, { overflowY: boolean }> {
this.props.settings['schema.disableComments'],
),
)
if (this.props.settings['schema.enablePolling']) {
this.editor.scrollTo(initialScroll.left, initialScroll.top)
}
CodeMirror.signal(this.editor, 'change', this.editor)
}
if (this.props.width !== prevProps.width) {
Expand Down
Expand Up @@ -80,10 +80,12 @@ export default class SDLHeader extends React.Component<SDLHeaderProps, State> {

const SchemaHeader = styled.div`
display: flex;
flex-direction: row;
height: 64px;
width: 100%;
margin-right: 108px;
align-items: center;
justify-content: space-between;
justify-content: flex-start;
outline: none;
user-select: none;
`
Expand All @@ -99,7 +101,6 @@ const Box = styled.div`
`

const Title = styled.div`
flex: 1;
color: ${p => styleHelper(p).title};
cursor: default;
font-size: 14px;
Expand All @@ -109,6 +110,7 @@ const Title = styled.div`
letter-spacing: 1px;
user-select: none !important;
padding: 16px;
padding-right: 5px;
`

const Download = styled(Button)`
Expand Down Expand Up @@ -136,6 +138,7 @@ const styleHelper = p => {
if (p.theme.mode === 'dark') {
return {
title: 'white',
subtitle: '#8B959C',
download: {
text: p.open ? '#8B959C' : 'white',
button: p.theme.colours.darkBlue,
Expand All @@ -148,6 +151,7 @@ const styleHelper = p => {
}
return {
title: p.theme.colours.darkBlue,
subtitle: 'rgba(61, 88, 102, 0.5)',
download: {
text: p.open ? 'rgba(61, 88, 102, 0.5)' : p.theme.colours.darkBlue,
button: '#f6f6f6',
Expand Down
Expand Up @@ -32,6 +32,7 @@ interface DispatchFromProps {
toggleDocs: (sessionId: string) => any
setDocsVisible: (sessionId: string, open: boolean) => any
changeWidthDocs: (sessionId: string, width: number) => any
setSchemaUpdated: () => void
}

class SDLView extends React.Component<
Expand Down
@@ -0,0 +1,42 @@
import * as React from 'react'
import PollingIcon from './PollingIcon'

export interface Props {
isPollingSchema: boolean
onReloadSchema: () => void
}

class SchemaPolling extends React.Component<Props> {
timer: any

componentDidMount() {
this.startPolling()
}
componentWillUnmount() {
this.clearTimer()
}
componentWillReceiveProps(nextProps: Props) {
if (nextProps.isPollingSchema !== this.props.isPollingSchema) {
this.startPolling(nextProps)
}
}

render() {
return <PollingIcon animate={true} />
}
private startPolling(props: Props = this.props) {
this.clearTimer()
if (props.isPollingSchema) {
this.timer = setInterval(() => props.onReloadSchema(), 2000)
}
}

private clearTimer() {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
}
}

export default SchemaPolling
@@ -0,0 +1,49 @@
import * as React from 'react'
import { styled, keyframes, css } from '../../../styled/index'
import BasePositioner from './Positioner'

export interface Props {
animate: boolean
disabled?: boolean
onClick?: () => void
}

const PollingIcon: React.SFC<Props> = props => (
<Positioner onClick={props.onClick} title="Polling Schema">
<Icon animate={props.animate} />
</Positioner>
)

export default PollingIcon

const pulse = keyframes`
0% {
box-shadow: 0 0 0 0 rgba(139, 149, 156, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(139, 149, 156, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(139, 149, 156, 0);
}
`

const Positioner = styled(BasePositioner)`
display: flex;
justify-content: center;
align-items: center;
`
const Icon = styled.div`
display: block;
width: 8px;
height: 8px;
border-radius: 50%;
background: ${p => p.theme.editorColours.pollingIcon};
box-shadow: 0 0 0 ${p => p.theme.editorColours.pollingIconShadow};
${p =>
p.animate
? css`
animation: ${pulse} 2s infinite;
`
: undefined};
`
@@ -0,0 +1,6 @@
import { styled } from '../../../styled/index'

export default styled.div`
width: 20px;
height: 20px;
`
@@ -0,0 +1,16 @@
import * as React from 'react'
import ReloadIcon from './ReloadIcon'

export interface Props {
isReloadingSchema: boolean
onReloadSchema?: () => void
}

const Reload: React.SFC<Props> = props => (
<ReloadIcon
animate={props.isReloadingSchema}
onClick={props.onReloadSchema}
/>
)

export default Reload
@@ -1,26 +1,32 @@
import * as React from 'react'
import { styled, keyframes } from '../../../styled/index'
import { styled, keyframes, css } from '../../../styled/index'
import BasePositioner from './Positioner'

export interface Props {
isReloadingSchema: boolean
onReloadSchema?: () => void
animate: boolean
disabled?: boolean
onClick?: () => void
}

const ReloadIcon: React.SFC<Props> = props => (
<Positioner onClick={props.onReloadSchema} title="Reload Schema">
<Svg viewBox="0 0 20 20">
<Positioner
onClick={props.onClick}
title="Reload Schema"
disabled={props.disabled}
>
<Svg viewBox="0 0 20 20" disabled={props.disabled}>
<Circle
cx="9.5"
cy="10"
r="6"
strokeWidth="1.5"
fill="none"
strokeLinecap="round"
isReloadingSchema={props.isReloadingSchema}
animate={props.animate}
/>
<Icon
d="M4.83 4.86a6.92 6.92 0 0 1 11.3 2.97l.41-1.23c.13-.4.56-.6.95-.47.4.13.6.56.47.95l-1.13 3.33a.76.76 0 0 1-.7.5.72.72 0 0 1-.43-.12l-2.88-1.92a.76.76 0 0 1-.2-1.04.75.75 0 0 1 1.03-.2l1.06.7A5.34 5.34 0 0 0 9.75 4.5a5.44 5.44 0 0 0-5.64 5.22 5.42 5.42 0 0 0 5.24 5.62c.41 0 .74.36.72.78a.75.75 0 0 1-.75.72H9.3a6.9 6.9 0 0 1-6.68-7.18 6.88 6.88 0 0 1 2.22-4.81z"
isReloadingSchema={props.isReloadingSchema}
animate={props.animate}
/>
</Svg>
</Positioner>
Expand Down Expand Up @@ -50,45 +56,42 @@ const refreshFrames = keyframes`
// again the animation
const reloadAction = props => keyframes`
0% {
transform: rotate(${props.isReloadingSchema ? 0 : 360}deg);
transform: rotate(${props.animate ? 0 : 360}deg);
}
100% {
transform: rotate(${props.isReloadingSchema ? 360 : 720}deg);
transform: rotate(${props.animate ? 360 : 720}deg);
}`

const Positioner = styled.div`
position: absolute;
right: 5px;
width: 20px;
height: 20px;
cursor: pointer;
transform: rotateY(180deg);
`

const Svg = styled.svg`
fill: ${p => p.theme.editorColours.icon};
transition: 0.1s linear all;
&:hover {
fill: ${p => p.theme.editorColours.iconHover};
}
${p =>
p.disabled
? undefined
: css`
&:hover {
fill: ${p => p.theme.editorColours.iconHover};
}
`};
`
const Positioner = styled(BasePositioner)`
cursor: ${({ disabled = false }) => (disabled ? 'auto' : 'pointer')};
transform: rotateY(180deg);
`

const Circle = styled<Props, 'circle'>('circle')`
fill: none;
stroke: ${p => p.theme.editorColours.icon};
stroke-dasharray: 37.68;
transition: opacity 0.3s ease-in-out;
opacity: ${p => (p.isReloadingSchema ? 1 : 0)};
opacity: ${p => (p.animate ? 1 : 0)};
transform-origin: 9.5px 10px;
animation: ${refreshFrames} 2s linear
${p => (p.isReloadingSchema ? 'infinite' : '')};
animation: ${refreshFrames} 2s linear ${p => (p.animate ? 'infinite' : '')};
`

const Icon = styled<Props, 'path'>('path')`
transition: opacity 0.3s ease-in-out;
opacity: ${p => (p.isReloadingSchema ? 0 : 1)};
opacity: ${p => (p.animate ? 0 : 1)};
transform-origin: 9.5px 10px;
animation: ${reloadAction} 0.5s linear;
`
@@ -0,0 +1,26 @@
import * as React from 'react'
import ReloadIcon from './Reload'
import PollingIcon from './Polling'

export interface Props {
isPollingSchema: boolean
isReloadingSchema: boolean
onReloadSchema: () => any
}

export default (props: Props) => {
if (props.isPollingSchema) {
return (
<PollingIcon
isPollingSchema={props.isPollingSchema}
onReloadSchema={props.onReloadSchema}
/>
)
}
return (
<ReloadIcon
isReloadingSchema={props.isReloadingSchema}
onReloadSchema={props.onReloadSchema}
/>
)
}

0 comments on commit 7e290dc

Please sign in to comment.