diff --git a/packages/hoppscotch-common/assets/scss/styles.scss b/packages/hoppscotch-common/assets/scss/styles.scss index dc7dd3a51..f5cfaeedb 100644 --- a/packages/hoppscotch-common/assets/scss/styles.scss +++ b/packages/hoppscotch-common/assets/scss/styles.scss @@ -574,3 +574,11 @@ details[open] summary .indicator { @apply rounded; @apply border-0; } + +.gql-operation-not-highlight { + opacity: 0.5; +} + +.gql-operation-highlight { + opacity: 1; +} diff --git a/packages/hoppscotch-common/locales/en.json b/packages/hoppscotch-common/locales/en.json index 661ddda20..3e893cfa9 100644 --- a/packages/hoppscotch-common/locales/en.json +++ b/packages/hoppscotch-common/locales/en.json @@ -153,6 +153,7 @@ "remove_telemetry": "Are you sure you want to opt-out of Telemetry?", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "save_unsaved_tab": "Do you want to save changes made in this tab?", + "close_unsaved_tab": "Are you sure you want to close this tab?", "close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.", "sync": "Would you like to restore your workspace from cloud? This will discard your local progress." }, @@ -281,6 +282,10 @@ "graphql": { "mutations": "Mutations", "schema": "Schema", + "switch_connection": "Switch connection", + "connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is", + "connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is", + "connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?", "subscriptions": "Subscriptions" }, "group": { @@ -473,6 +478,7 @@ "rename": "Rename Request", "renamed": "Request renamed", "run": "Run", + "stop": "Stop", "save": "Save", "save_as": "Save as", "saved": "Request saved", @@ -578,6 +584,10 @@ "show_all": "Keyboard shortcuts", "title": "General" }, + "others": { + "title": "Others", + "prettify": "Prettify Editor's Content" + }, "miscellaneous": { "invite": "Invite people to Hoppscotch", "title": "Miscellaneous" diff --git a/packages/hoppscotch-common/src/components.d.ts b/packages/hoppscotch-common/src/components.d.ts index 1bfcbcd9b..6937ebc79 100644 --- a/packages/hoppscotch-common/src/components.d.ts +++ b/packages/hoppscotch-common/src/components.d.ts @@ -1,11 +1,11 @@ -// generated by unplugin-vue-components -// We suggest you to commit this file into source control +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-components // Read more: https://github.com/vuejs/core/pull/3399 -import '@vue/runtime-core' - export {} -declare module '@vue/runtime-core' { +declare module 'vue' { export interface GlobalComponents { AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default'] AppAnnouncement: typeof import('./components/app/Announcement.vue')['default'] @@ -72,12 +72,17 @@ declare module '@vue/runtime-core' { FirebaseLogout: typeof import('./components/firebase/Logout.vue')['default'] GraphqlAuthorization: typeof import('./components/graphql/Authorization.vue')['default'] GraphqlField: typeof import('./components/graphql/Field.vue')['default'] + GraphqlHeaders: typeof import('./components/graphql/Headers.vue')['default'] + GraphqlQuery: typeof import('./components/graphql/Query.vue')['default'] GraphqlRequest: typeof import('./components/graphql/Request.vue')['default'] GraphqlRequestOptions: typeof import('./components/graphql/RequestOptions.vue')['default'] + GraphqlRequestTab: typeof import('./components/graphql/RequestTab.vue')['default'] GraphqlResponse: typeof import('./components/graphql/Response.vue')['default'] GraphqlSidebar: typeof import('./components/graphql/Sidebar.vue')['default'] + GraphqlSubscriptionLog: typeof import('./components/graphql/SubscriptionLog.vue')['default'] GraphqlType: typeof import('./components/graphql/Type.vue')['default'] GraphqlTypeLink: typeof import('./components/graphql/TypeLink.vue')['default'] + GraphqlVariable: typeof import('./components/graphql/Variable.vue')['default'] History: typeof import('./components/history/index.vue')['default'] HistoryGraphqlCard: typeof import('./components/history/graphql/Card.vue')['default'] HistoryRestCard: typeof import('./components/history/rest/Card.vue')['default'] @@ -90,7 +95,6 @@ declare module '@vue/runtime-core' { HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand'] HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip'] HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput'] - HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection'] HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem'] HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink'] HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal'] @@ -104,6 +108,7 @@ declare module '@vue/runtime-core' { HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab'] HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs'] HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle'] + HoppSmartTree: typeof import('@hoppscotch/ui')['HoppSmartTree'] HoppSmartWindow: typeof import('@hoppscotch/ui')['HoppSmartWindow'] HoppSmartWindows: typeof import('@hoppscotch/ui')['HoppSmartWindows'] HttpAuthorization: typeof import('./components/http/Authorization.vue')['default'] @@ -136,7 +141,6 @@ declare module '@vue/runtime-core' { IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default'] IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default'] IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default'] - IconLucideBrush: typeof import('~icons/lucide/brush')['default'] IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default'] IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default'] IconLucideGlobe: typeof import('~icons/lucide/globe')['default'] @@ -146,10 +150,9 @@ declare module '@vue/runtime-core' { IconLucideLayers: typeof import('~icons/lucide/layers')['default'] IconLucideListEnd: typeof import('~icons/lucide/list-end')['default'] IconLucideMinus: typeof import('~icons/lucide/minus')['default'] - IconLucideRss: typeof import('~icons/lucide/rss')['default'] IconLucideSearch: typeof import('~icons/lucide/search')['default'] IconLucideUsers: typeof import('~icons/lucide/users')['default'] - IconLucideVerified: typeof import('~icons/lucide/verified')['default'] + InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default'] LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default'] LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default'] LensesRenderersAudioLensRenderer: typeof import('./components/lenses/renderers/AudioLensRenderer.vue')['default'] @@ -214,5 +217,4 @@ declare module '@vue/runtime-core' { WorkspaceCurrent: typeof import('./components/workspace/Current.vue')['default'] WorkspaceSelector: typeof import('./components/workspace/Selector.vue')['default'] } - } diff --git a/packages/hoppscotch-common/src/components/collections/SaveRequest.vue b/packages/hoppscotch-common/src/components/collections/SaveRequest.vue index 5dd260d95..e591534ab 100644 --- a/packages/hoppscotch-common/src/components/collections/SaveRequest.vue +++ b/packages/hoppscotch-common/src/components/collections/SaveRequest.vue @@ -71,7 +71,6 @@ import { updateTeamRequest, } from "~/helpers/backend/mutations/TeamRequest" import { Picked } from "~/helpers/types/HoppPicked" -import { getGQLSession, useGQLRequestName } from "~/newstore/GQLSession" import { useI18n } from "@composables/i18n" import { useToast } from "@composables/toast" import { @@ -82,8 +81,9 @@ import { } from "~/newstore/collections" import { GQLError } from "~/helpers/backend/GQLClient" import { computedWithControl } from "@vueuse/core" -import { currentActiveTab } from "~/helpers/rest/tab" import { platform } from "~/platform" +import { currentActiveTab as activeRESTTab } from "~/helpers/rest/tab" +import { currentActiveTab as activeGQLTab } from "~/helpers/graphql/tab" const t = useI18n() const toast = useToast() @@ -122,10 +122,14 @@ const emit = defineEmits<{ (e: "hide-modal"): void }>() -const gqlRequestName = useGQLRequestName() +const gqlRequestName = computedWithControl( + () => activeGQLTab.value, + () => activeGQLTab.value.document.request.name +) + const restRequestName = computedWithControl( - () => currentActiveTab.value, - () => currentActiveTab.value.document.request.name + () => activeRESTTab.value, + () => activeRESTTab.value.document.request.name ) const reqName = computed(() => { @@ -141,11 +145,13 @@ const reqName = computed(() => { const requestName = ref(reqName.value) watch( - () => [currentActiveTab.value, gqlRequestName.value], + () => [activeRESTTab.value, activeGQLTab.value], () => { if (props.mode === "rest") { - requestName.value = currentActiveTab.value?.document.request.name ?? "" - } else requestName.value = gqlRequestName.value + requestName.value = activeRESTTab.value?.document.request.name ?? "" + } else { + requestName.value = activeGQLTab.value?.document.request.name ?? "" + } } ) @@ -202,15 +208,10 @@ const saveRequestAs = async () => { return } - let requestUpdated - - if (props.request) { - requestUpdated = cloneDeep(props.request) - } else if (props.mode === "rest") { - requestUpdated = cloneDeep(currentActiveTab.value.document.request) - } else { - requestUpdated = cloneDeep(getGQLSession().request) - } + const requestUpdated = + props.mode === "rest" + ? cloneDeep(activeRESTTab.value.document.request) + : cloneDeep(activeGQLTab.value.document.request) requestUpdated.name = requestName.value @@ -223,7 +224,7 @@ const saveRequestAs = async () => { requestUpdated ) - currentActiveTab.value.document = { + activeRESTTab.value.document = { request: requestUpdated, isDirty: false, saveContext: { @@ -250,7 +251,7 @@ const saveRequestAs = async () => { requestUpdated ) - currentActiveTab.value.document = { + activeRESTTab.value.document = { request: requestUpdated, isDirty: false, saveContext: { @@ -278,7 +279,7 @@ const saveRequestAs = async () => { requestUpdated ) - currentActiveTab.value.document = { + activeRESTTab.value.document = { request: requestUpdated, isDirty: false, saveContext: { @@ -438,7 +439,7 @@ const updateTeamCollectionOrFolder = ( (result) => { const { createRequestInCollection } = result - currentActiveTab.value.document = { + activeRESTTab.value.document = { request: requestUpdated, isDirty: false, saveContext: { @@ -459,7 +460,7 @@ const updateTeamCollectionOrFolder = ( const requestSaved = () => { toast.success(`${t("request.added")}`) nextTick(() => { - currentActiveTab.value.document.isDirty = false + activeRESTTab.value.document.isDirty = false }) hideModal() } diff --git a/packages/hoppscotch-common/src/components/collections/graphql/AddRequest.vue b/packages/hoppscotch-common/src/components/collections/graphql/AddRequest.vue index aeeacb9a8..a956abd2e 100644 --- a/packages/hoppscotch-common/src/components/collections/graphql/AddRequest.vue +++ b/packages/hoppscotch-common/src/components/collections/graphql/AddRequest.vue @@ -36,7 +36,7 @@ import { ref, watch } from "vue" import { useI18n } from "@composables/i18n" import { useToast } from "@composables/toast" -import { getGQLSession } from "~/newstore/GQLSession" +import { currentActiveTab } from "~/helpers/graphql/tab" const toast = useToast() const t = useI18n() @@ -63,7 +63,7 @@ watch( () => props.show, (show) => { if (show) { - editingName.value = getGQLSession().request.name + editingName.value = currentActiveTab.value?.document.request.name } } ) diff --git a/packages/hoppscotch-common/src/components/collections/graphql/Request.vue b/packages/hoppscotch-common/src/components/collections/graphql/Request.vue index 0071a169a..c4adff852 100644 --- a/packages/hoppscotch-common/src/components/collections/graphql/Request.vue +++ b/packages/hoppscotch-common/src/components/collections/graphql/Request.vue @@ -132,7 +132,7 @@ import { useToast } from "@composables/toast" import { HoppGQLRequest, makeGQLRequest } from "@hoppscotch/data" import { cloneDeep } from "lodash-es" import { removeGraphqlRequest } from "~/newstore/collections" -import { setGQLSession } from "~/newstore/GQLSession" +import { createNewTab } from "~/helpers/graphql/tab" // Template refs const tippyActions = ref(null) @@ -179,7 +179,7 @@ const selectRequest = () => { if (props.saveRequest) { pick() } else { - setGQLSession({ + createNewTab({ request: cloneDeep( makeGQLRequest({ name: props.request.name, @@ -190,8 +190,7 @@ const selectRequest = () => { auth: props.request.auth, }) ), - schema: "", - response: "", + isDirty: false, }) } } diff --git a/packages/hoppscotch-common/src/components/collections/graphql/index.vue b/packages/hoppscotch-common/src/components/collections/graphql/index.vue index 76384a2a7..3b00c1c4e 100644 --- a/packages/hoppscotch-common/src/components/collections/graphql/index.vue +++ b/packages/hoppscotch-common/src/components/collections/graphql/index.vue @@ -137,7 +137,6 @@ import { addGraphqlFolder, saveGraphqlRequestAs, } from "~/newstore/collections" -import { getGQLSession, setGQLSession } from "~/newstore/GQLSession" import IconPlus from "~icons/lucide/plus" import IconHelpCircle from "~icons/lucide/help-circle" @@ -146,6 +145,7 @@ import { useI18n } from "@composables/i18n" import { useReadonlyStream } from "@composables/stream" import { useColorMode } from "@composables/theming" import { platform } from "~/platform" +import { createNewTab, currentActiveTab } from "~/helpers/graphql/tab" export default defineComponent({ props: { @@ -267,15 +267,15 @@ export default defineComponent({ }, onAddRequest({ name, path }) { const newRequest = { - ...getGQLSession().request, + ...currentActiveTab.value.document.request, name, } saveGraphqlRequestAs(path, newRequest) - setGQLSession({ + + createNewTab({ request: newRequest, - schema: "", - response: "", + isDirty: false, }) platform.analytics?.logEvent({ diff --git a/packages/hoppscotch-common/src/components/graphql/Authorization.vue b/packages/hoppscotch-common/src/components/graphql/Authorization.vue index ec700962e..b32384e87 100644 --- a/packages/hoppscotch-common/src/components/graphql/Authorization.vue +++ b/packages/hoppscotch-common/src/components/graphql/Authorization.vue @@ -1,7 +1,7 @@ diff --git a/packages/hoppscotch-common/src/components/graphql/RequestOptions.vue b/packages/hoppscotch-common/src/components/graphql/RequestOptions.vue index eae4665d8..ec7f4524f 100644 --- a/packages/hoppscotch-common/src/components/graphql/RequestOptions.vue +++ b/packages/hoppscotch-common/src/components/graphql/RequestOptions.vue @@ -2,311 +2,42 @@
-
- -
- - - - - - - -
-
-
+
-
- -
- - - - - -
-
-
+
-
- -
- - - - - -
-
-
-
- - - - - - -
+
- +
diff --git a/packages/hoppscotch-common/src/components/graphql/Response.vue b/packages/hoppscotch-common/src/components/graphql/Response.vue index 1bbfd7f7d..89bc08486 100644 --- a/packages/hoppscotch-common/src/components/graphql/Response.vue +++ b/packages/hoppscotch-common/src/components/graphql/Response.vue @@ -1,14 +1,6 @@ @@ -52,22 +50,34 @@ import IconWrapText from "~icons/lucide/wrap-text" import IconDownload from "~icons/lucide/download" import IconCheck from "~icons/lucide/check" import IconCopy from "~icons/lucide/copy" -import { reactive, ref } from "vue" +import { computed, reactive, ref } from "vue" import { refAutoReset } from "@vueuse/core" import { useCodemirror } from "@composables/codemirror" import { copyToClipboard } from "~/helpers/utils/clipboard" -import { useReadonlyStream } from "@composables/stream" import { useI18n } from "@composables/i18n" import { useToast } from "@composables/toast" -import { gqlResponse$ } from "~/newstore/GQLSession" import { defineActionHandler } from "~/helpers/actions" import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils" +import { GQLResponseEvent } from "~/helpers/graphql/connection" const t = useI18n() - const toast = useToast() -const responseString = useReadonlyStream(gqlResponse$, "") +const props = withDefaults( + defineProps<{ + response: GQLResponseEvent[] | null + }>(), + { + response: null, + } +) + +const responseString = computed(() => { + if (props.response?.length === 1) { + return JSON.stringify(JSON.parse(props.response[0].data), null, 2) + } + return "" +}) const schemaEditor = ref(null) const linewrapEnabled = ref(true) @@ -95,14 +105,14 @@ const copyResponseIcon = refAutoReset( 1000 ) -const copyResponse = () => { - copyToClipboard(responseString.value!) +const copyResponse = (str: string) => { + copyToClipboard(str) copyResponseIcon.value = IconCheck toast.success(`${t("state.copied_to_clipboard")}`) } -const downloadResponse = () => { - const dataToWrite = responseString.value +const downloadResponse = (str: string) => { + const dataToWrite = str const file = new Blob([dataToWrite!], { type: "application/json" }) const a = document.createElement("a") const url = URL.createObjectURL(file) @@ -118,6 +128,10 @@ const downloadResponse = () => { }, 1000) } -defineActionHandler("response.file.download", () => downloadResponse()) -defineActionHandler("response.copy", () => copyResponse()) +defineActionHandler("response.file.download", () => + downloadResponse.bind(responseString.value) +) +defineActionHandler("response.copy", () => + copyResponse.bind(responseString.value) +) diff --git a/packages/hoppscotch-common/src/components/graphql/Sidebar.vue b/packages/hoppscotch-common/src/components/graphql/Sidebar.vue index 3b9faf5ce..21641afd1 100644 --- a/packages/hoppscotch-common/src/components/graphql/Sidebar.vue +++ b/packages/hoppscotch-common/src/components/graphql/Sidebar.vue @@ -5,20 +5,6 @@ vertical render-inactive-tabs > - - - - - - + + + + + + + @@ -188,29 +189,24 @@ import IconCopy from "~icons/lucide/copy" import IconBox from "~icons/lucide/box" import { computed, nextTick, reactive, ref } from "vue" import { GraphQLField, GraphQLType } from "graphql" -import { map } from "rxjs/operators" -import { GQLHeader } from "@hoppscotch/data" import { refAutoReset } from "@vueuse/core" import { useCodemirror } from "@composables/codemirror" -import { GQLConnection } from "@helpers/GQLConnection" import { copyToClipboard } from "@helpers/utils/clipboard" -import { useReadonlyStream } from "@composables/stream" import { useI18n } from "@composables/i18n" import { useToast } from "@composables/toast" import { useColorMode } from "@composables/theming" import { - setGQLAuth, - setGQLHeaders, - setGQLQuery, - setGQLResponse, - setGQLURL, - setGQLVariables, -} from "~/newstore/GQLSession" + graphqlTypes, + mutationFields, + queryFields, + schemaString, + subscriptionFields, +} from "~/helpers/graphql/connection" type NavigationTabs = "history" | "collection" | "docs" | "schema" type GqlTabs = "queries" | "mutations" | "subscriptions" | "types" -const selectedNavigationTab = ref("history") +const selectedNavigationTab = ref("docs") const selectedGqlTab = ref("queries") const t = useI18n() @@ -270,40 +266,8 @@ function resolveRootType(type: GraphQLType) { return t } -type GQLHistoryEntry = { - url: string - headers: GQLHeader[] - query: string - response: string - variables: string -} - -const props = defineProps<{ - conn: GQLConnection -}>() - const toast = useToast() -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 = refAutoReset( IconDownload, 1000 @@ -390,11 +354,6 @@ const handleJumpToType = async (type: GraphQLType) => { } } -const schemaString = useReadonlyStream( - props.conn.schemaString$.pipe(map((x) => x ?? "")), - "" -) - const schemaEditor = ref(null) const linewrapEnabled = ref(true) @@ -436,23 +395,4 @@ const copySchema = () => { copyToClipboard(schemaString.value) copySchemaIcon.value = IconCheck } - -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) - setGQLAuth({ - authType: "none", - authActive: true, - }) - props.conn.reset() -} diff --git a/packages/hoppscotch-common/src/components/graphql/SubscriptionLog.vue b/packages/hoppscotch-common/src/components/graphql/SubscriptionLog.vue new file mode 100644 index 000000000..74f671784 --- /dev/null +++ b/packages/hoppscotch-common/src/components/graphql/SubscriptionLog.vue @@ -0,0 +1,125 @@ + + + diff --git a/packages/hoppscotch-common/src/components/graphql/Type.vue b/packages/hoppscotch-common/src/components/graphql/Type.vue index b5fd87fa0..e020c18d2 100644 --- a/packages/hoppscotch-common/src/components/graphql/Type.vue +++ b/packages/hoppscotch-common/src/components/graphql/Type.vue @@ -55,51 +55,48 @@
-