feat: graphql rewrite to new state system
This commit is contained in:
475
components/graphql/Sidebar.vue
Normal file
475
components/graphql/Sidebar.vue
Normal file
@@ -0,0 +1,475 @@
|
||||
<template>
|
||||
<aside>
|
||||
<SmartTabs styles="sticky z-10 top-0">
|
||||
<SmartTab :id="'docs'" :label="`Docs`" :selected="true">
|
||||
<AppSection label="docs">
|
||||
<div
|
||||
class="
|
||||
bg-primaryLight
|
||||
flex flex-col
|
||||
top-sidebarPrimaryStickyFold
|
||||
z-10
|
||||
sticky
|
||||
"
|
||||
>
|
||||
<div class="search-wrapper">
|
||||
<input
|
||||
v-model="graphqlFieldsFilterText"
|
||||
type="search"
|
||||
:placeholder="$t('search')"
|
||||
class="
|
||||
bg-primaryLight
|
||||
flex
|
||||
font-semibold font-mono
|
||||
w-full
|
||||
py-2
|
||||
pr-2
|
||||
pl-9
|
||||
truncate
|
||||
focus:outline-none
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<SmartTabs
|
||||
ref="gqlTabs"
|
||||
styles="
|
||||
border-t border-dividerLight sticky z-8 top-sidebarSecondaryStickyFold"
|
||||
>
|
||||
<div class="gqlTabs">
|
||||
<SmartTab
|
||||
v-if="queryFields.length > 0"
|
||||
:id="'queries'"
|
||||
:label="$t('queries')"
|
||||
:selected="true"
|
||||
class="divide-y divide-dividerLight"
|
||||
>
|
||||
<GraphqlField
|
||||
v-for="(field, index) in filteredQueryFields"
|
||||
:key="`field-${index}`"
|
||||
:gql-field="field"
|
||||
:jump-type-callback="handleJumpToType"
|
||||
class="p-4"
|
||||
/>
|
||||
</SmartTab>
|
||||
<SmartTab
|
||||
v-if="mutationFields.length > 0"
|
||||
:id="'mutations'"
|
||||
:label="$t('mutations')"
|
||||
class="divide-y divide-dividerLight"
|
||||
>
|
||||
<GraphqlField
|
||||
v-for="(field, index) in filteredMutationFields"
|
||||
:key="`field-${index}`"
|
||||
:gql-field="field"
|
||||
:jump-type-callback="handleJumpToType"
|
||||
class="p-4"
|
||||
/>
|
||||
</SmartTab>
|
||||
<SmartTab
|
||||
v-if="subscriptionFields.length > 0"
|
||||
:id="'subscriptions'"
|
||||
:label="$t('subscriptions')"
|
||||
class="divide-y divide-dividerLight"
|
||||
>
|
||||
<GraphqlField
|
||||
v-for="(field, index) in filteredSubscriptionFields"
|
||||
:key="`field-${index}`"
|
||||
:gql-field="field"
|
||||
:jump-type-callback="handleJumpToType"
|
||||
class="p-4"
|
||||
/>
|
||||
</SmartTab>
|
||||
<SmartTab
|
||||
v-if="graphqlTypes.length > 0"
|
||||
:id="'types'"
|
||||
ref="typesTab"
|
||||
:label="$t('types')"
|
||||
class="divide-y divide-dividerLight"
|
||||
>
|
||||
<GraphqlType
|
||||
v-for="(type, index) in filteredGraphqlTypes"
|
||||
:key="`type-${index}`"
|
||||
:gql-type="type"
|
||||
:gql-types="graphqlTypes"
|
||||
:is-highlighted="isGqlTypeHighlighted(type)"
|
||||
:highlighted-fields="getGqlTypeHighlightedFields(type)"
|
||||
:jump-type-callback="handleJumpToType"
|
||||
/>
|
||||
</SmartTab>
|
||||
</div>
|
||||
</SmartTabs>
|
||||
<div
|
||||
v-if="
|
||||
queryFields.length === 0 &&
|
||||
mutationFields.length === 0 &&
|
||||
subscriptionFields.length === 0 &&
|
||||
graphqlTypes.length === 0
|
||||
"
|
||||
class="
|
||||
flex flex-col
|
||||
text-secondaryLight
|
||||
p-4
|
||||
items-center
|
||||
justify-center
|
||||
"
|
||||
>
|
||||
<i class="opacity-75 pb-2 material-icons">link</i>
|
||||
<span class="text-center">
|
||||
{{ $t("empty.schema") }}
|
||||
</span>
|
||||
</div>
|
||||
</AppSection>
|
||||
</SmartTab>
|
||||
|
||||
<SmartTab :id="'history'" :label="$t('history')">
|
||||
<History
|
||||
ref="graphqlHistoryComponent"
|
||||
:page="'graphql'"
|
||||
@useHistory="handleUseHistory"
|
||||
/>
|
||||
</SmartTab>
|
||||
|
||||
<SmartTab :id="'collections'" :label="$t('collections')">
|
||||
<CollectionsGraphql />
|
||||
</SmartTab>
|
||||
|
||||
<SmartTab :id="'schema'" :label="`Schema`">
|
||||
<AppSection ref="schema" label="schema">
|
||||
<div
|
||||
v-if="schemaString"
|
||||
class="
|
||||
bg-primary
|
||||
border-b border-dividerLight
|
||||
flex flex-1
|
||||
top-sidebarPrimaryStickyFold
|
||||
pl-4
|
||||
z-10
|
||||
sticky
|
||||
items-center
|
||||
justify-between
|
||||
"
|
||||
>
|
||||
<label class="font-semibold">
|
||||
{{ $t("schema") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
ref="downloadSchema"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('download_file')"
|
||||
:icon="downloadSchemaIcon"
|
||||
@click.native="downloadSchema"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
ref="copySchemaCode"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.copy')"
|
||||
:icon="copySchemaIcon"
|
||||
@click.native="copySchema"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<SmartAceEditor
|
||||
v-if="schemaString"
|
||||
v-model="schemaString"
|
||||
:lang="'graphqlschema'"
|
||||
:options="{
|
||||
maxLines: Infinity,
|
||||
minLines: 16,
|
||||
fontSize: '12px',
|
||||
autoScrollEditorIntoView: true,
|
||||
readOnly: true,
|
||||
showPrintMargin: false,
|
||||
useWorker: false,
|
||||
}"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="
|
||||
flex flex-col
|
||||
text-secondaryLight
|
||||
p-4
|
||||
items-center
|
||||
justify-center
|
||||
"
|
||||
>
|
||||
<i class="opacity-75 pb-2 material-icons">link</i>
|
||||
<span class="text-center">
|
||||
{{ $t("empty.schema") }}
|
||||
</span>
|
||||
</div>
|
||||
</AppSection>
|
||||
</SmartTab>
|
||||
</SmartTabs>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
nextTick,
|
||||
PropType,
|
||||
ref,
|
||||
useContext,
|
||||
} from "@nuxtjs/composition-api"
|
||||
import { GraphQLField, GraphQLType } from 'graphql'
|
||||
import { map } from 'rxjs/operators'
|
||||
import { GQLConnection } from '~/helpers/GQLConnection'
|
||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||
import { useReadonlyStream } from '~/helpers/utils/composables'
|
||||
import {
|
||||
GQLHeader,
|
||||
setGQLHeaders,
|
||||
setGQLQuery,
|
||||
setGQLResponse,
|
||||
setGQLURL,
|
||||
setGQLVariables,
|
||||
} from "~/newstore/GQLSession"
|
||||
|
||||
function isTextFoundInGraphqlFieldObject(
|
||||
text: string,
|
||||
field: GraphQLField<any, any>
|
||||
) {
|
||||
const normalizedText = text.toLowerCase()
|
||||
|
||||
const isFilterTextFoundInDescription = field.description
|
||||
? field.description.toLowerCase().includes(normalizedText)
|
||||
: false
|
||||
const isFilterTextFoundInName = field.name
|
||||
.toLowerCase()
|
||||
.includes(normalizedText)
|
||||
|
||||
return isFilterTextFoundInDescription || isFilterTextFoundInName
|
||||
}
|
||||
|
||||
function getFilteredGraphqlFields(
|
||||
filterText: string,
|
||||
fields: GraphQLField<any, any>[]
|
||||
) {
|
||||
if (!filterText) return fields
|
||||
|
||||
return fields.filter((field) =>
|
||||
isTextFoundInGraphqlFieldObject(filterText, field)
|
||||
)
|
||||
}
|
||||
|
||||
function getFilteredGraphqlTypes(filterText: string, types: GraphQLType[]) {
|
||||
if (!filterText) return types
|
||||
|
||||
return types.filter((type) => {
|
||||
const isFilterTextMatching = isTextFoundInGraphqlFieldObject(
|
||||
filterText,
|
||||
type as any
|
||||
)
|
||||
|
||||
if (isFilterTextMatching) {
|
||||
return true
|
||||
}
|
||||
|
||||
const isFilterTextMatchingAtLeastOneField = Object.values(
|
||||
(type as any)._fields || {}
|
||||
).some((field) => isTextFoundInGraphqlFieldObject(filterText, field as any))
|
||||
|
||||
return isFilterTextMatchingAtLeastOneField
|
||||
})
|
||||
}
|
||||
|
||||
function resolveRootType(type: GraphQLType) {
|
||||
let t: any = type
|
||||
while (t.ofType) t = t.ofType
|
||||
return t
|
||||
}
|
||||
|
||||
type GQLHistoryEntry = {
|
||||
url: string
|
||||
headers: GQLHeader[]
|
||||
query: string
|
||||
response: string
|
||||
variables: string
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
conn: {
|
||||
type: Object as PropType<GQLConnection>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const {
|
||||
$toast,
|
||||
app: { i18n },
|
||||
} = useContext()
|
||||
const t = i18n.t.bind(i18n)
|
||||
|
||||
const queryFields = useReadonlyStream(
|
||||
props.conn.queryFields$.pipe(map((x) => x ?? [])),
|
||||
[]
|
||||
)
|
||||
const mutationFields = useReadonlyStream(
|
||||
props.conn.mutationFields$.pipe(map((x) => x ?? [])),
|
||||
[]
|
||||
)
|
||||
const subscriptionFields = useReadonlyStream(
|
||||
props.conn.subscriptionFields$.pipe(map((x) => x ?? [])),
|
||||
[]
|
||||
)
|
||||
const graphqlTypes = useReadonlyStream(
|
||||
props.conn.graphqlTypes$.pipe(map((x) => x ?? [])),
|
||||
[]
|
||||
)
|
||||
|
||||
const downloadSchemaIcon = ref("save_alt")
|
||||
const copySchemaIcon = ref("content_copy")
|
||||
|
||||
const graphqlFieldsFilterText = ref("")
|
||||
|
||||
const gqlTabs = ref<any | null>(null)
|
||||
const typesTab = ref<any | null>(null)
|
||||
|
||||
const filteredQueryFields = computed(() => {
|
||||
return getFilteredGraphqlFields(
|
||||
graphqlFieldsFilterText.value,
|
||||
queryFields.value as any
|
||||
)
|
||||
})
|
||||
|
||||
const filteredMutationFields = computed(() => {
|
||||
return getFilteredGraphqlFields(
|
||||
graphqlFieldsFilterText.value,
|
||||
mutationFields.value as any
|
||||
)
|
||||
})
|
||||
|
||||
const filteredSubscriptionFields = computed(() => {
|
||||
return getFilteredGraphqlFields(
|
||||
graphqlFieldsFilterText.value,
|
||||
subscriptionFields.value as any
|
||||
)
|
||||
})
|
||||
|
||||
const filteredGraphqlTypes = computed(() => {
|
||||
return getFilteredGraphqlTypes(
|
||||
graphqlFieldsFilterText.value,
|
||||
graphqlTypes.value as any
|
||||
)
|
||||
})
|
||||
|
||||
const isGqlTypeHighlighted = (gqlType: GraphQLType) => {
|
||||
if (!graphqlFieldsFilterText.value) return false
|
||||
|
||||
return isTextFoundInGraphqlFieldObject(
|
||||
graphqlFieldsFilterText.value,
|
||||
gqlType as any
|
||||
)
|
||||
}
|
||||
|
||||
const getGqlTypeHighlightedFields = (gqlType: GraphQLType) => {
|
||||
if (!graphqlFieldsFilterText.value) return []
|
||||
|
||||
const fields = Object.values((gqlType as any)._fields || {})
|
||||
if (!fields || fields.length === 0) return []
|
||||
|
||||
return fields.filter((field) =>
|
||||
isTextFoundInGraphqlFieldObject(
|
||||
graphqlFieldsFilterText.value,
|
||||
field as any
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const handleJumpToType = async (type: GraphQLType) => {
|
||||
gqlTabs.value.selectTab(typesTab.value)
|
||||
await nextTick()
|
||||
|
||||
const rootTypeName = resolveRootType(type).name
|
||||
|
||||
const target = document.getElementById(`type_${rootTypeName}`)
|
||||
if (target) {
|
||||
gqlTabs.value.$el
|
||||
.querySelector(".gqlTabs")
|
||||
.scrollTo({ top: target.offsetTop, behavior: "smooth" })
|
||||
}
|
||||
}
|
||||
const schemaString = useReadonlyStream(
|
||||
props.conn.schemaString$.pipe(map((x) => x ?? "")),
|
||||
""
|
||||
)
|
||||
|
||||
const downloadSchema = () => {
|
||||
const dataToWrite = JSON.stringify(schemaString.value, null, 2)
|
||||
const file = new Blob([dataToWrite], { type: "application/graphql" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
a.download = `${
|
||||
url.split("/").pop()!.split("#")[0].split("?")[0]
|
||||
}.graphql`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
downloadSchemaIcon.value = "done"
|
||||
$toast.success(t("download_started").toString(), {
|
||||
icon: "done",
|
||||
})
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
downloadSchemaIcon.value = "save_alt"
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const copySchema = () => {
|
||||
if (!schemaString.value) return
|
||||
|
||||
copyToClipboard(schemaString.value)
|
||||
copySchemaIcon.value = "done"
|
||||
setTimeout(() => (copySchemaIcon.value = "content_copy"), 1000)
|
||||
}
|
||||
|
||||
const handleUseHistory = (entry: GQLHistoryEntry) => {
|
||||
const url = entry.url
|
||||
const headers = entry.headers
|
||||
const gqlQueryString = entry.query
|
||||
const variableString = entry.variables
|
||||
const responseText = entry.response
|
||||
|
||||
setGQLURL(url)
|
||||
setGQLHeaders(headers)
|
||||
setGQLQuery(gqlQueryString)
|
||||
setGQLVariables(variableString)
|
||||
setGQLResponse(responseText)
|
||||
props.conn.reset()
|
||||
}
|
||||
|
||||
return {
|
||||
queryFields,
|
||||
mutationFields,
|
||||
subscriptionFields,
|
||||
graphqlTypes,
|
||||
schemaString,
|
||||
|
||||
graphqlFieldsFilterText,
|
||||
|
||||
filteredQueryFields,
|
||||
filteredMutationFields,
|
||||
filteredSubscriptionFields,
|
||||
filteredGraphqlTypes,
|
||||
|
||||
isGqlTypeHighlighted,
|
||||
getGqlTypeHighlightedFields,
|
||||
|
||||
gqlTabs,
|
||||
typesTab,
|
||||
handleJumpToType,
|
||||
|
||||
downloadSchema,
|
||||
downloadSchemaIcon,
|
||||
copySchemaIcon,
|
||||
copySchema,
|
||||
handleUseHistory,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user