GraphQL Support (#311)
GraphQL Support Co-authored-by: Liyas Thomas <liyasthomas@gmail.com>
This commit is contained in:
@@ -724,6 +724,7 @@ input[type="radio"] + label {
|
||||
|
||||
input[type="radio"]:checked + label {
|
||||
border-color: var(--fg-color);
|
||||
color: var(--fg-color);
|
||||
}
|
||||
|
||||
input[type="radio"]:checked + label + .tab {
|
||||
|
||||
64
components/graphql/field.vue
Normal file
64
components/graphql/field.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="field-box">
|
||||
<div class="field-title">{{ fieldString }}</div>
|
||||
<div class="field-desc" v-if="gqlField.description">
|
||||
{{ gqlField.description }}
|
||||
</div>
|
||||
|
||||
<div class="field-deprecated" v-if="gqlField.isDeprecated">
|
||||
DEPRECATED
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.field-box {
|
||||
padding: 16px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.field-title {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.field-deprecated {
|
||||
background-color: yellow;
|
||||
color: black;
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
margin: 4px 0;
|
||||
border-radius: 4px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.field-desc {
|
||||
color: var(--fg-light-color);
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
gqlField: Object
|
||||
},
|
||||
|
||||
computed: {
|
||||
fieldString() {
|
||||
const args = (this.gqlField.args || []).reduce((acc, arg, index) => {
|
||||
return (
|
||||
acc +
|
||||
`${arg.name}: ${arg.type.toString()}${
|
||||
index !== this.gqlField.args.length - 1 ? ", " : ""
|
||||
}`
|
||||
);
|
||||
}, "");
|
||||
const argsString = args.length > 0 ? `(${args})` : "";
|
||||
|
||||
return `${
|
||||
this.gqlField.name
|
||||
}${argsString}: ${this.gqlField.type.toString()}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
42
components/graphql/type.vue
Normal file
42
components/graphql/type.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="type-box">
|
||||
<div class="type-title">{{ gqlType.name }}</div>
|
||||
<div class="type-desc" v-if="gqlType.description">
|
||||
{{ gqlType.description }}
|
||||
</div>
|
||||
|
||||
<div v-if="gqlType.getFields">
|
||||
<h5>FIELDS</h5>
|
||||
<div v-for="field in gqlType.getFields()" :key="field.name">
|
||||
<gql-field :gqlField="field" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.type-box {
|
||||
padding: 16px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.type-desc {
|
||||
color: var(--fg-light-color);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.type-title {
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
components: {
|
||||
"gql-field": () => import("./field")
|
||||
},
|
||||
props: {
|
||||
gqlType: {}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -107,6 +107,13 @@
|
||||
to="/websocket"
|
||||
:class="linkActive('/websocket')"
|
||||
v-tooltip.right="'WebSocket'"
|
||||
>
|
||||
<i class="material-icons">settings_input_hdmi</i>
|
||||
</nuxt-link>
|
||||
<nuxt-link
|
||||
to="/graphql"
|
||||
:class="linkActive('/graphql')"
|
||||
v-tooltip.right="'GraphQL'"
|
||||
>
|
||||
<i class="material-icons">cloud</i>
|
||||
</nuxt-link>
|
||||
@@ -149,7 +156,23 @@
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#response" v-tooltip.right="'Response'">
|
||||
<a href="#response" v-tooltip.right="'Communication'">
|
||||
<i class="material-icons">cloud_download</i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<div v-else-if="['/graphql'].includes($route.path)">
|
||||
<nav class="secondary-nav">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#endpoint" v-tooltip.right="'Endpoint'">
|
||||
<i class="material-icons">cloud_upload</i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#schema" v-tooltip.right="'Schema'">
|
||||
<i class="material-icons">cloud_download</i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
13
package-lock.json
generated
13
package-lock.json
generated
@@ -5064,6 +5064,14 @@
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz",
|
||||
"integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q=="
|
||||
},
|
||||
"graphql": {
|
||||
"version": "14.5.8",
|
||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-14.5.8.tgz",
|
||||
"integrity": "sha512-MMwmi0zlVLQKLdGiMfWkgQD7dY/TUKt4L+zgJ/aR0Howebod3aNgP5JkgvAULiR2HPVZaP2VEElqtdidHweLkg==",
|
||||
"requires": {
|
||||
"iterall": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"gzip-size": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz",
|
||||
@@ -5869,6 +5877,11 @@
|
||||
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
|
||||
"dev": true
|
||||
},
|
||||
"iterall": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/iterall/-/iterall-1.2.2.tgz",
|
||||
"integrity": "sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA=="
|
||||
},
|
||||
"jest-worker": {
|
||||
"version": "24.9.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"@nuxtjs/sitemap": "^2.0.1",
|
||||
"@nuxtjs/toast": "^3.3.0",
|
||||
"ace-builds": "^1.4.7",
|
||||
"graphql": "^14.5.8",
|
||||
"nuxt": "^2.10.2",
|
||||
"v-tooltip": "^2.0.2",
|
||||
"vue-virtual-scroll-list": "^1.4.2",
|
||||
|
||||
231
pages/graphql.vue
Normal file
231
pages/graphql.vue
Normal file
@@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="content">
|
||||
<div class="page-columns inner-left">
|
||||
<pw-section class="blue" label="Endpoint" ref="endpoint">
|
||||
<ul>
|
||||
<li>
|
||||
<label for="url">URL</label>
|
||||
<input
|
||||
id="url"
|
||||
type="url"
|
||||
v-model="url"
|
||||
@keyup.enter="getSchema()"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<label for="get" class="hide-on-small-screen"> </label>
|
||||
<button id="get" name="get" @click="getSchema">
|
||||
Get Schema
|
||||
<span><i class="material-icons">send</i></span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</pw-section>
|
||||
|
||||
<pw-section class="green" label="Schema" ref="schema">
|
||||
<Editor
|
||||
:value="schemaString"
|
||||
:lang="'graphqlschema'"
|
||||
:options="{
|
||||
maxLines: '16',
|
||||
minLines: '16',
|
||||
fontSize: '16px',
|
||||
autoScrollEditorIntoView: true,
|
||||
readOnly: true,
|
||||
showPrintMargin: false,
|
||||
useWorker: false
|
||||
}"
|
||||
/>
|
||||
</pw-section>
|
||||
</div>
|
||||
<aside class="sticky-inner inner-right">
|
||||
<pw-section class="purple" label="Docs" ref="docs">
|
||||
<section>
|
||||
<input
|
||||
v-if="queryFields.length > 0"
|
||||
id="queries-tab"
|
||||
type="radio"
|
||||
name="side"
|
||||
checked="checked"
|
||||
/>
|
||||
<label v-if="queryFields.length > 0" for="queries-tab"
|
||||
>Queries</label
|
||||
>
|
||||
<div v-if="queryFields.length > 0" class="tab">
|
||||
<div v-for="field in queryFields" :key="field.name">
|
||||
<gql-field :gqlField="field" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
v-if="mutationFields.length > 0"
|
||||
id="mutations-tab"
|
||||
type="radio"
|
||||
name="side"
|
||||
checked="checked"
|
||||
/>
|
||||
<label v-if="mutationFields.length > 0" for="mutations-tab"
|
||||
>Mutations</label
|
||||
>
|
||||
<div v-if="mutationFields.length > 0" class="tab">
|
||||
<div v-for="field in mutationFields" :key="field.name">
|
||||
<gql-field :gqlField="field" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
v-if="subscriptionFields.length > 0"
|
||||
id="subscriptions-tab"
|
||||
type="radio"
|
||||
name="side"
|
||||
checked="checked"
|
||||
/>
|
||||
<label v-if="subscriptionFields.length > 0" for="subscriptions-tab"
|
||||
>Subscriptions</label
|
||||
>
|
||||
<div v-if="subscriptionFields.length > 0" class="tab">
|
||||
<div v-for="field in subscriptionFields" :key="field.name">
|
||||
<gql-field :gqlField="field" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
v-if="gqlTypes.length > 0"
|
||||
id="gqltypes-tab"
|
||||
type="radio"
|
||||
name="side"
|
||||
checked="checked"
|
||||
/>
|
||||
<label v-if="gqlTypes.length > 0" for="gqltypes-tab">Types</label>
|
||||
<div v-if="gqlTypes.length > 0" class="tab">
|
||||
<div v-for="type in gqlTypes" :key="type.name">
|
||||
<gql-type :gqlType="type" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</pw-section>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tab {
|
||||
max-height: calc(100vh - 172px);
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import * as gql from "graphql";
|
||||
import AceEditor from "../components/ace-editor";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
"pw-section": () => import("../components/section"),
|
||||
"gql-field": () => import("../components/graphql/field"),
|
||||
"gql-type": () => import("../components/graphql/type"),
|
||||
Editor: AceEditor
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
url: "https://rickandmortyapi.com/graphql",
|
||||
schemaString: "",
|
||||
queryFields: [],
|
||||
mutationFields: [],
|
||||
subscriptionFields: [],
|
||||
gqlTypes: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getSchema() {
|
||||
const startTime = Date.now();
|
||||
this.schemaString = "Loading...";
|
||||
|
||||
// Start showing the loading bar as soon as possible.
|
||||
// The nuxt axios module will hide it when the request is made.
|
||||
this.$nuxt.$loading.start();
|
||||
|
||||
axios
|
||||
.post(this.url, {
|
||||
query: gql.getIntrospectionQuery()
|
||||
})
|
||||
.then(res => {
|
||||
const schema = gql.buildClientSchema(res.data.data);
|
||||
this.schemaString = gql.printSchema(schema, {
|
||||
commentDescriptions: true
|
||||
});
|
||||
|
||||
if (schema.getQueryType()) {
|
||||
const fields = schema.getQueryType().getFields();
|
||||
const qFields = [];
|
||||
for (const field in fields) {
|
||||
qFields.push(fields[field]);
|
||||
}
|
||||
this.queryFields = qFields;
|
||||
}
|
||||
|
||||
if (schema.getMutationType()) {
|
||||
const fields = schema.getMutationType().getFields();
|
||||
const mFields = [];
|
||||
for (const field in fields) {
|
||||
mFields.push(fields[field]);
|
||||
}
|
||||
this.mutationFields = mFields;
|
||||
}
|
||||
|
||||
if (schema.getSubscriptionType()) {
|
||||
const fields = schema.getSubscriptionType().getFields();
|
||||
const sFields = [];
|
||||
for (const field in fields) {
|
||||
sFields.push(fields[field]);
|
||||
}
|
||||
this.subscriptionFields = sFields;
|
||||
}
|
||||
|
||||
const typeMap = schema.getTypeMap();
|
||||
const types = [];
|
||||
|
||||
const queryTypeName = schema.getQueryType()
|
||||
? schema.getQueryType().name
|
||||
: "";
|
||||
const mutationTypeName = schema.getMutationType()
|
||||
? schema.getMutationType().name
|
||||
: "";
|
||||
const subscriptionTypeName = schema.getSubscriptionType()
|
||||
? schema.getSubscriptionType().name
|
||||
: "";
|
||||
|
||||
for (const type in typeMap) {
|
||||
if (
|
||||
!typeMap[type].name.startsWith("__") &&
|
||||
![queryTypeName, mutationTypeName, subscriptionTypeName].includes(
|
||||
typeMap[type].name
|
||||
) &&
|
||||
typeMap[type] instanceof gql.GraphQLObjectType
|
||||
) {
|
||||
types.push(typeMap[type]);
|
||||
}
|
||||
}
|
||||
this.gqlTypes = types;
|
||||
|
||||
this.$nuxt.$loading.finish();
|
||||
const duration = Date.now() - startTime;
|
||||
this.$toast.info(`Finished in ${duration}ms`, {
|
||||
icon: "done"
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
this.$nuxt.$loading.finish();
|
||||
this.schemaString = error + ". Check console for details.";
|
||||
this.$toast.error(error + " (F12 for details)", {
|
||||
icon: "error"
|
||||
});
|
||||
console.log("Error", error);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -309,13 +309,10 @@
|
||||
</div>
|
||||
</pw-section>
|
||||
|
||||
<br />
|
||||
|
||||
<section id="options">
|
||||
<input id="tab-one" type="radio" name="options" checked="checked" />
|
||||
<label for="tab-one">Authentication</label>
|
||||
<div class="tab">
|
||||
<br />
|
||||
|
||||
<pw-section
|
||||
class="cyan"
|
||||
@@ -402,7 +399,6 @@
|
||||
<input id="tab-two" type="radio" name="options" />
|
||||
<label for="tab-two">Headers</label>
|
||||
<div class="tab">
|
||||
<br />
|
||||
|
||||
<pw-section class="orange" label="Headers" ref="headers">
|
||||
<ul>
|
||||
@@ -484,7 +480,6 @@
|
||||
<input id="tab-three" type="radio" name="options" />
|
||||
<label for="tab-three">Parameters</label>
|
||||
<div class="tab">
|
||||
<br />
|
||||
|
||||
<pw-section class="pink" label="Parameters" ref="parameters">
|
||||
<ul>
|
||||
@@ -563,8 +558,6 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<br />
|
||||
|
||||
<pw-section
|
||||
class="purple"
|
||||
id="response"
|
||||
|
||||
@@ -56,8 +56,6 @@
|
||||
</ul>
|
||||
</pw-section>
|
||||
|
||||
<br />
|
||||
|
||||
<pw-section class="blue" label="Proxy" ref="proxy">
|
||||
<ul>
|
||||
<li>
|
||||
|
||||
@@ -30,8 +30,6 @@
|
||||
</ul>
|
||||
</pw-section>
|
||||
|
||||
<br />
|
||||
|
||||
<pw-section
|
||||
class="purple"
|
||||
label="Communication"
|
||||
|
||||
Reference in New Issue
Block a user