diff --git a/docs/package.json b/docs/package.json index 854a0cf8..33820334 100644 --- a/docs/package.json +++ b/docs/package.json @@ -17,7 +17,7 @@ "copy-webpack-plugin": "^0.2.0", "es6-promise": "^3.0.2", "extract-text-webpack-plugin": "^0.9.1", - "gatsby": "^0.7.2", + "gatsby": "^0.7.3", "graphiql": "^0.4.2", "graphql": "^0.4.13", "jeet": "^6.1.2", diff --git a/docs/playground/GraphenePlayground.js b/docs/playground/GraphenePlayground.js new file mode 100644 index 00000000..106cd58d --- /dev/null +++ b/docs/playground/GraphenePlayground.js @@ -0,0 +1,234 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { RouteHandler, Link, State } from 'react-router'; +import CodeMirror from 'codemirror'; +import { graphql } from 'graphql'; +import GraphiQL from 'graphiql'; +import schema from './schema'; +import pypyjs_vm from 'pypyjs'; + +import 'codemirror/mode/python/python'; +import 'codemirror/addon/lint/lint'; +import '../css/playground.styl'; + +if (typeof PUBLIC_PATH === "undefined") { + var PUBLIC_PATH = ''; +} + +pypyjs_vm.rootURL = `${PUBLIC_PATH}/playground/lib/`; +pypyjs_vm.cacheKey = 'graphene'; + +CodeMirror.registerHelper('lint', 'python', function (text, options, editor) { + return (options.errors || []).map((error) => { + var tokens = editor.getLineTokens(error.line - 1); + tokens = tokens.filter((token, pos) => { + return !!token.type || token.string.trim().length > 0; + }); + if (!tokens) return []; + return { + message: `${error.name}: ${error.message}`, + severity: 'error', + type: 'syntax', + from: CodeMirror.Pos(error.line - 1, tokens[0].start), + to: CodeMirror.Pos(error.line - 1, tokens[tokens.length-1].end), + }; + }); +}); + +function graphQLFetcher(graphQLParams) { + return graphql(schema, graphQLParams.query); +} + +var default_interpreter; +class Playground extends React.Component { + constructor() { + super(); + this.state = {pypyjs: false, stdout: '', response:''}; + } + stdout() { + console.log('stdout', arguments); + } + componentDidMount() { + if (default_interpreter) { + this.pypy_interpreter = default_interpreter; + this.pypy_interpreter.stdout = this.stdout.bind(this); + } + else { + this.pypy_interpreter = new pypyjs_vm({ + stdin: function(){}, + stdout: this.stdout.bind(this), + stderr: function(){}, + rootURL: `${PUBLIC_PATH}/playground/lib/` + }); + default_interpreter = this.pypy_interpreter; + } + + this.pypyjs = this.pypy_interpreter.ready().then(() => { + return this.pypy_interpreter.exec(` +import graphene +import js +from collections import OrderedDict +from graphql.core.execution.executor import Executor +from graphql.core.execution.middlewares.sync import SynchronousExecutionMiddleware +from graphql.core.error import GraphQLError, format_error + +def get_wrapped(f): + if hasattr(f, 'func_closure') and f.func_closure: + return get_wrapped(f.func_closure[0].cell_contents) + return f + +class TrackResolver(SynchronousExecutionMiddleware): + @staticmethod + def run_resolve_fn(resolver, original_resolver): + if resolver.func.__module__ == '__main__': + line = get_wrapped(resolver.func).resolver.func_code.co_firstlineno + js.globals.markLine(line-3) + return SynchronousExecutionMiddleware.run_resolve_fn(resolver, original_resolver) + +__graphene_executor = Executor([TrackResolver()], map_type=OrderedDict) +`); + }).then(() => { + this.createSchema(this.props.initialSchema); + }).then(() => { + this.setState({pypyjs: true, response:'"Execute the query for see the results"'}); + }); + + window.markLine = (lineNo) => { + this.markLine(lineNo); + } + + this.editor = CodeMirror(ReactDOM.findDOMNode(this.refs.schemaCode), { + value: this.props.initialSchema, + mode: "python", + theme: "graphene", + lineNumbers: true, + tabSize: 4, + indentUnit: 4, + gutters: ["CodeMirror-linenumbers", "breakpoints"], + lint: { + errors: [], + }, + }); + this.editor.on("change", this.onEditorChange.bind(this)); + } + onEditorChange() { + if (this.changeTimeout) { + clearTimeout(this.changeTimeout); + } + if (this.props.onEditSchema) { + var value = this.editor.getValue(); + if (value != this.props.initialSchema) { + this.props.onEditSchema(value) + } + } + + this.changeTimeout = setTimeout(() => + this.updateSchema() + , 300); + } + updateSchema() { + this.createSchema(this.editor.getValue()); + } + createSchema(code) { + if (this.previousCode == code) return; + console.log('createSchema'); + this.validSchema = null; + this.pypyjs.then(() => { + return this.pypy_interpreter.exec(` +schema = None +${code} +assert schema, 'You have to define a schema' +`) + }).then(() => { + console.log('NO ERRORS'); + this.removeErrors(); + this.validSchema = true; + }, (err) => { + this.editor.options.lint.errors = []; + console.log('ERRORS', err); + this.logError(err); + this.validSchema = false; + }).then(this.updateGraphiQL.bind(this)); + this.previousCode = code; + } + updateGraphiQL() { + if (this.validSchema) { + this.refs.graphiql.state.schema = null; + this.refs.graphiql.componentDidMount(); + this.refs.graphiql.forceUpdate(); + this.refs.graphiql.refs.docExplorer.forceUpdate(); + } + } + logError(error) { + var lines = error.trace.split('\n'); + var file_errors = lines.map((errorLine) => { + return errorLine.match(/File "", line (\d+)/); + }).filter((x) => !! x); + if (!file_errors.length) return; + var line = parseInt(file_errors[file_errors.length-1][1]); + error.line = line-2; + if (error.name == "ImportError" && error.message == "No module named django") { + error.message = "Django is not supported yet in Playground editor"; + } + this.editor.options.lint.errors.push(error); + CodeMirror.signal(this.editor, 'change', this.editor); + } + removeErrors() { + this.editor.options.lint.errors = []; + CodeMirror.signal(this.editor, 'change', this.editor); + } + fetcher (graphQLParams) { + if (!this.validSchema) { + return graphQLFetcher(arguments); + } + return this.execute(graphQLParams.query); + } + execute(query) { + // console.log('execute', query); + return this.pypyjs.then(() => { + var x = ` +import json +result = __graphene_executor.execute(schema.schema, '''${query}''') +result_dict = {}; +if result.errors: + result_dict['errors'] = [format_error(e) for e in result.errors] +if result.data: + result_dict['data'] = result.data +result_json = json.dumps(result_dict) +`; + return this.pypy_interpreter.exec(x) + } + ).then(() => + this.pypy_interpreter.get(`result_json`) + ).then((data) => { + var json_data = JSON.parse(data); + return json_data; + }); + } + markLine(lineNo) { + console.log(lineNo); + var hlLine = this.editor.addLineClass(lineNo, "text", "activeline"); + // var mark = this.editor.markText({line: lineNo, ch: 0}, {line: lineNo, ch: 10}, {className: "called-function"}); + setTimeout(() => { + this.editor.removeLineClass(lineNo, "text", "activeline"); + }, 1200); + } + render() { + return ( +
+ {!this.state.pypyjs?
:null} +
+
+ Schema +
+
+
+
+ +
+
+ ); + } +} + +module.exports = Playground; diff --git a/docs/playground/examples/hello.graphql b/docs/playground/examples/hello.graphql new file mode 100644 index 00000000..f8d0ad68 --- /dev/null +++ b/docs/playground/examples/hello.graphql @@ -0,0 +1,4 @@ +query { + hello + ping(to:"Peter") +} diff --git a/docs/playground/examples/hello.schema.py b/docs/playground/examples/hello.schema.py new file mode 100644 index 00000000..b2ce1cfb --- /dev/null +++ b/docs/playground/examples/hello.schema.py @@ -0,0 +1,13 @@ +import graphene + +class Query(graphene.ObjectType): + hello = graphene.String() + ping = graphene.String(to=graphene.String()) + + def resolve_hello(self, args, info): + return 'World' + + def resolve_ping(self, args, info): + return 'Pinging {}'.format(args.get('to')) + +schema = graphene.Schema(query=Query) diff --git a/docs/playground/graphene-js/pypyjs.js b/docs/playground/graphene-js/pypyjs.js index 510abdcf..ac030a44 100644 --- a/docs/playground/graphene-js/pypyjs.js +++ b/docs/playground/graphene-js/pypyjs.js @@ -60,13 +60,6 @@ if (typeof FunctionPromise === "undefined") { throw "FunctionPromise object not found"; } -// Some extra goodies for nodejs. -if (typeof process !== 'undefined') { - if (Object.prototype.toString.call(process) === '[object process]') { - var fs = require("fs"); - var path = require("path"); - } -} // Create functions for handling default stdio streams. // These will be shared by all VM instances by default. diff --git a/docs/playground/page.js b/docs/playground/page.js index c5f732c2..59dae266 100644 --- a/docs/playground/page.js +++ b/docs/playground/page.js @@ -1,258 +1,150 @@ import React from 'react'; -import ReactDOM from 'react-dom'; -import { RouteHandler, Link, State } from 'react-router'; -import CodeMirror from 'codemirror'; -import { graphql } from 'graphql'; -import GraphiQL from 'graphiql'; -import schema from './schema'; -import pypyjs_vm from 'pypyjs'; +import GraphenePlayground from './GraphenePlayground'; -import 'codemirror/mode/python/python'; -import 'codemirror/addon/lint/lint'; -import '../css/playground.styl'; +import _ from 'lodash'; -if (typeof PUBLIC_PATH === "undefined") { - var PUBLIC_PATH = ''; +const DEFAULT_CACHE_KEY = 'default'; + +function filterObject(object, callback, context) { + if (!object) { + return null; + } + var result = {}; + for (var name in object) { + if (hasOwnProperty.call(object, name) && + callback.call(context, object[name], name, object)) { + result[name] = object[name]; + } + } + return result; } -pypyjs_vm.rootURL = `${PUBLIC_PATH}/playground/lib/`; -pypyjs_vm.cacheKey = 'graphene'; - -var baseCode = `import graphene - -class Query(graphene.ObjectType): - hello = graphene.String() - ping = graphene.String(to=graphene.String()) - - def resolve_hello(self, args, info): - return 'World' - - def resolve_ping(self, args, info): - return 'Pinging {}'.format(args.get('to')) - -schema = graphene.Schema(query=Query) -`; - -CodeMirror.registerHelper('lint', 'python', function (text, options, editor) { - return (options.errors || []).map((error) => { - var tokens = editor.getLineTokens(error.line - 1); - tokens = tokens.filter((token, pos) => { - return !!token.type || token.string.trim().length > 0; - }); - if (!tokens) return []; - return { - message: `${error.name}: ${error.message}`, - severity: 'error', - type: 'syntax', - from: CodeMirror.Pos(error.line - 1, tokens[0].start), - to: CodeMirror.Pos(error.line - 1, tokens[tokens.length-1].end), - }; - }); -}); - -// function graphQLFetcher(graphQLParams) { -// return fetch('http://swapi.graphene-python.org/graphql', { -// method: 'post', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify(graphQLParams), -// }).then(response => response.json()); -// } -function graphQLFetcher(graphQLParams) { - return graphql(schema, graphQLParams.query); -} -// var schema = null; - -function syntaxError() { - var marker = document.createElement("div"); - marker.style.color = "#822"; - marker.innerHTML = "●"; - return marker; -} - -var default_interpreter; class Playground extends React.Component { - constructor() { - super(); - this.state = {pypyjs: false, stdout: '', response:''}; - } - stdout() { - console.log('stdout', arguments); - } - componentDidMount() { - if (default_interpreter) { - this.pypy_interpreter = default_interpreter; - this.pypy_interpreter.stdout = this.stdout.bind(this); - } - else { - this.pypy_interpreter = new pypyjs_vm({ - stdin: function(){}, - stdout: this.stdout.bind(this), - stderr: function(){}, - rootURL: `${PUBLIC_PATH}/playground/lib/` - }); - default_interpreter = this.pypy_interpreter; - } + componentWillMount() { + var sourceWasInjected = false; + var queryParams = this.context.router.getCurrentQuery(); - this.pypyjs = this.pypy_interpreter.ready().then(() => { - return this.pypy_interpreter.exec(` -import graphene -import js -from collections import OrderedDict -from graphql.core.execution.executor import Executor -from graphql.core.execution.middlewares.sync import SynchronousExecutionMiddleware -from graphql.core.error import GraphQLError, format_error + var { + cacheKey, + noCache, + } = queryParams; + noCache = (noCache !== undefined) && (noCache !== 'false'); + if (noCache) { + cacheKey = undefined; + } else if (!cacheKey) { + cacheKey = DEFAULT_CACHE_KEY; + } + this.schemaCacheKey = `rp-${cacheKey}-schema`; + this.queryCacheKey = `rp-${cacheKey}-query`; + this.cacheKey = cacheKey; -def get_wrapped(f): - if hasattr(f, 'func_closure') and f.func_closure: - return get_wrapped(f.func_closure[0].cell_contents) - return f - -class TrackResolver(SynchronousExecutionMiddleware): - @staticmethod - def run_resolve_fn(resolver, original_resolver): - if resolver.func.__module__ == '__main__': - line = get_wrapped(resolver.func).resolver.func_code.co_firstlineno - js.globals.markLine(line-3) - return SynchronousExecutionMiddleware.run_resolve_fn(resolver, original_resolver) - -__graphene_executor = Executor([TrackResolver()], map_type=OrderedDict) -`); - }).then(() => { - this.createSchema(baseCode); - }).then(() => { - this.setState({pypyjs: true, response:'"Execute the query for see the results"'}); - }); - - window.markLine = (lineNo) => { - this.markLine(lineNo); + var initialSchema; + var initialQuery; + var storedSchema = localStorage.getItem(this.schemaCacheKey); + var storedQuery = localStorage.getItem(this.queryCacheKey); + if (noCache) { + // Use case #1 + // We use the noCache param to force a playground to have certain contents. + // eg. static example apps + initialSchema = queryParams.schema || ''; + initialQuery = queryParams.query || ''; + sourceWasInjected = true; + queryParams = {}; + } else if (cacheKey === DEFAULT_CACHE_KEY) { + // Use case #2 + // The user loaded the playground without a custom cache key. + // Allow code injection via the URL + // OR load code from localStorage + // OR prime the playground with some default 'hello world' code + if (queryParams.schema != null) { + initialSchema = queryParams.schema; + sourceWasInjected = queryParams.schema !== storedSchema; + } else if (storedSchema != null) { + initialSchema = storedSchema; + } else { + initialSchema = require('!raw!./examples/hello.schema.py'); + } + if (queryParams.query != null) { + initialQuery = queryParams.query; + sourceWasInjected = queryParams.query !== storedQuery; + } else if (storedQuery != null) { + initialQuery = storedQuery; + } else { + initialQuery = require('!raw!./examples/hello.graphql'); + } + queryParams = filterObject({ + schema: queryParams.schema, + query: queryParams.query, + }, v => v !== undefined); + } else if (cacheKey) { + // Use case #3 + // Custom cache keys are useful in cases where you want to embed a playground + // that features both custom boilerplate code AND saves the developer's + // progress, without overwriting the default code cache. eg. a tutorial. + if (storedSchema != null) { + initialSchema = storedSchema; + } else { + initialSchema = queryParams[`schema_${cacheKey}`]; + if (initialSchema != null) { + sourceWasInjected = true; + } + } + if (storedQuery != null) { + initialQuery = storedQuery; + } else { + initialQuery = queryParams[`query_${cacheKey}`]; + if (initialQuery != null) { + sourceWasInjected = true; + } + } + queryParams = {}; } - - this.editor = CodeMirror(ReactDOM.findDOMNode(this.refs.schemaCode), { - value: baseCode, - mode: "python", - theme: "graphene", - lineNumbers: true, - tabSize: 4, - indentUnit: 4, - gutters: ["CodeMirror-linenumbers", "breakpoints"], - lint: { - errors: [], - }, - }); - this.editor.on("change", this.onEditorChange.bind(this)); + this.changeParams(queryParams); + this.state = {initialSchema, initialQuery, sourceWasInjected}; + this.queryParams = queryParams; } - onEditorChange() { - if (this.changeTimeout) { - clearTimeout(this.changeTimeout); - } - this.changeTimeout = setTimeout(() => - this.updateSchema() - , 300); + shouldComponentUpdate() { + return false; } - updateSchema() { - this.createSchema(this.editor.getValue()); - } - createSchema(code) { - if (this.previousCode == code) return; - console.log('createSchema'); - this.validSchema = null; - this.pypyjs.then(() => { - return this.pypy_interpreter.exec(` -schema = None -${code} -assert schema, 'You have to define a schema' -`) - }).then(() => { - console.log('NO ERRORS'); - this.removeErrors(); - this.validSchema = true; - }, (err) => { - this.editor.options.lint.errors = []; - console.log('ERRORS', err); - this.logError(err); - this.validSchema = false; - // this.editor.setGutterMarker(5, "breakpoints", syntaxError()); - }).then(this.updateGraphiQL.bind(this)); - this.previousCode = code; - } - updateGraphiQL() { - if (this.validSchema) { - this.refs.graphiql.state.schema = null; - this.refs.graphiql.componentDidMount(); - this.refs.graphiql.forceUpdate(); - this.refs.graphiql.refs.docExplorer.forceUpdate(); - } - } - logError(error) { - var lines = error.trace.split('\n'); - var file_errors = lines.map((errorLine) => { - return errorLine.match(/File "", line (\d+)/); - }).filter((x) => !! x); - if (!file_errors.length) return; - var line = parseInt(file_errors[file_errors.length-1][1]); - error.line = line-2; - if (error.name == "ImportError" && error.message == "No module named django") { - error.message = "Django is not supported yet in Playground editor"; - } - this.editor.options.lint.errors.push(error); - CodeMirror.signal(this.editor, 'change', this.editor); - } - removeErrors() { - this.editor.options.lint.errors = []; - CodeMirror.signal(this.editor, 'change', this.editor); - } - fetcher (graphQLParams) { - if (!this.validSchema) { - return graphQLFetcher(arguments); - } - return this.execute(graphQLParams.query); - } - execute(query) { - // console.log('execute', query); - return this.pypyjs.then(() => { - var x = ` -import json -result = __graphene_executor.execute(schema.schema, '''${query}''') -result_dict = {}; -if result.errors: - result_dict['errors'] = [format_error(e) for e in result.errors] -if result.data: - result_dict['data'] = result.data -result_json = json.dumps(result_dict) -`; - return this.pypy_interpreter.exec(x) - } - ).then(() => - this.pypy_interpreter.get(`result_json`) - ).then((data) => { - var json_data = JSON.parse(data); - return json_data; - }); - } - markLine(lineNo) { - console.log(lineNo); - var hlLine = this.editor.addLineClass(lineNo, "text", "activeline"); - // var mark = this.editor.markText({line: lineNo, ch: 0}, {line: lineNo, ch: 10}, {className: "called-function"}); - setTimeout(() => { - this.editor.removeLineClass(lineNo, "text", "activeline"); - }, 1200); + changeParams(queryParams) { + var router = this.context.router; + var routeName = router.getCurrentPathname(); + var params = router.getCurrentParams(); + queryParams = _.mapValues(queryParams, encodeURIComponent); + router.replaceWith(routeName, params, queryParams); } render() { - return ( -
- {!this.state.pypyjs?
:null} -
-
- Schema -
-
-
-
- -
-
- ); + return ( { + localStorage.setItem(this.schemaCacheKey, source); + if (this.cacheKey === DEFAULT_CACHE_KEY) { + this.queryParams.schema = source; + if (!this.queryParams.query) { + this.queryParams.query = this.state.initialQuery; + } + this.changeParams(this.queryParams); + } + }} + onEditQuery={(source) => { + localStorage.setItem(this.queryCacheKey, source); + if (this.cacheKey === DEFAULT_CACHE_KEY) { + this.queryParams.query = source; + if (!this.queryParams.schema) { + this.queryParams.schema = this.state.initialSchema; + } + this.changeParams(this.queryParams); + } + }} + />); + } -} +}; + + +Playground.contextTypes = { + router: React.PropTypes.func +}; module.exports = Playground;