Plack-App-GraphQL/lib/Plack/App/GraphQL/UITemplate.pm
package Plack::App::GraphQL::UITemplate;
use Moo;
use Plack::Util;
has tt => (
is => 'ro',
required => 1,
builder => '_build_tt',
handles => {
tt_process => 'process',
}
);
sub _build_tt {
return Plack::Util::load_class('Template::Tiny')->new();
}
has template => (
is => 'ro',
required => 1,
builder => '_build_template',
);
sub _build_template {
$/ = undef;
return my $data = <DATA>;
}
has json_encoder => (
is => 'ro',
required => 1,
handles => {
json_encode => 'encode',
},
);
has graphiql_version => (
is => 'ro',
required => 1,
default => 'latest',
);
sub safe_serialize {
my ($self, $data) = @_;
if($data) {
my $json = $self->json_encode($data);
$json =~ s#/#\\/#g;
return $json;
} else {
return 'undefined';
}
}
sub process {
my ($self, $req) = @_;
my $query = $req->query_parameters;
my %args = $self->args_from_query($query);
return my $body = $self->process_args(%args);
}
sub args_from_query {
my ($self, $query) = @_;
return my %args = (
graphiql_version => $self->graphiql_version,
queryString => $self->safe_serialize( $query->{'query'} ),
operationName => $self->safe_serialize( $query->{'operationName'} ),
resultString => $self->safe_serialize( $query->{'result'} ),
variablesString => $self->safe_serialize( $query->{'variables'} ),
);
}
sub process_args {
my ($self, %args) = @_;
my $input = $self->template;
$self->tt_process(\$input, \%args, \my $output);
return $output;
}
1;
=head1 NAME
Plack::App::GraphQL::UITemplate - Template and processing for the GraphQL UI
=head1 SYNOPSIS
There's nothing really for end users here. Its just refactored into its own
package for code organization purposes.
=head1 DESCRIPTION
This is a package used to prepare and return an HTML response when you have the
'ui' flag enabled (probably for development) and the client requests an HTML
response. This is based on L<https://github.com/graphql/graphiql>
Feel free to make your own improved development / query interface and put it on
CPAN!
=head1 AUTHOR
John Napiorkowski
=head1 SEE ALSO
L<Plack::App::GraphQL>
=cut
__DATA__
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>GraphiQL</title>
<meta name="robots" content="noindex" />
<style>
html, body {
height: 100%;
margin: 0;
overflow: hidden;
width: 100%;
}
</style>
<link href="//cdn.jsdelivr.net/npm/graphiql@[% graphiql_version %]/graphiql.css" rel="stylesheet" />
<script src="//cdn.jsdelivr.net/fetch/0.9.0/fetch.min.js"></script>
<script src="//cdn.jsdelivr.net/react/15.4.2/react.min.js"></script>
<script src="//cdn.jsdelivr.net/react/15.4.2/react-dom.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/graphiql@[% graphiql_version %]/graphiql.min.js"></script>
</head>
<body>
<script>
// Collect the URL parameters
var parameters = {};
window.location.search.substr(1).split('&').forEach(function (entry) {
var eq = entry.indexOf('=');
if (eq >= 0) {
parameters[decodeURIComponent(entry.slice(0, eq))] =
decodeURIComponent(entry.slice(eq + 1));
}
});
// Produce a Location query string from a parameter object.
function locationQuery(params) {
return '?' + Object.keys(params).filter(function (key) {
return Boolean(params[key]);
}).map(function (key) {
return encodeURIComponent(key) + '=' +
encodeURIComponent(params[key]);
}).join('&');
}
// Derive a fetch URL from the current URL, sans the GraphQL parameters.
var graphqlParamNames = {
query: true,
variables: true,
operationName: true
};
var otherParams = {};
for (var k in parameters) {
if (parameters.hasOwnProperty(k) && graphqlParamNames[k] !== true) {
otherParams[k] = parameters[k];
}
}
var fetchURL = locationQuery(otherParams);
// Defines a GraphQL fetcher using the fetch API.
function graphQLFetcher(graphQLParams) {
return fetch(fetchURL, {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(graphQLParams),
credentials: 'include',
}).then(function (response) {
return response.text();
}).then(function (responseBody) {
try {
return JSON.parse(responseBody);
} catch (error) {
return responseBody;
}
});
}
// When the query and variables string is edited, update the URL bar so
// that it can be easily shared.
function onEditQuery(newQuery) {
parameters.query = newQuery;
updateURL();
}
function onEditVariables(newVariables) {
parameters.variables = newVariables;
updateURL();
}
function onEditOperationName(newOperationName) {
parameters.operationName = newOperationName;
updateURL();
}
function updateURL() {
history.replaceState(null, null, locationQuery(parameters));
}
// Render <GraphiQL /> into the body.
ReactDOM.render(
React.createElement(GraphiQL, {
fetcher: graphQLFetcher,
onEditQuery: onEditQuery,
onEditVariables: onEditVariables,
onEditOperationName: onEditOperationName,
query: [% queryString %],
response: [% resultString %],
variables: [% variablesString %],
operationName: [% operationName %],
}),
document.body
);
</script>
</body>
</html>