diff --git a/packages/hoppscotch-common/locales/en.json b/packages/hoppscotch-common/locales/en.json index 2f8a779fb..1a9aa89df 100644 --- a/packages/hoppscotch-common/locales/en.json +++ b/packages/hoppscotch-common/locales/en.json @@ -78,6 +78,7 @@ "contact_us": "Contact us", "cookies": "Cookies", "copy": "Copy", + "copy_interface_type": "Copy interface type", "copy_user_id": "Copy User Auth Token", "developer_option": "Developer options", "developer_option_description": "Developer tools which helps in development and maintenance of Hoppscotch.", @@ -780,6 +781,7 @@ "connection_failed": "Connection failed", "connection_lost": "Connection lost", "copied_to_clipboard": "Copied to clipboard", + "copied_interface_to_clipboard": "Copied {language} interface type to clipboard", "deleted": "Deleted", "deprecated": "DEPRECATED", "disabled": "Disabled", diff --git a/packages/hoppscotch-common/package.json b/packages/hoppscotch-common/package.json index a5f50a23d..2a0c9bbd6 100644 --- a/packages/hoppscotch-common/package.json +++ b/packages/hoppscotch-common/package.json @@ -76,6 +76,7 @@ "postman-collection": "^4.2.0", "process": "^0.11.10", "qs": "^6.11.2", + "quicktype-core": "^23.0.79", "rxjs": "^7.8.1", "set-cookie-parser": "^2.6.0", "set-cookie-parser-es": "^1.0.5", diff --git a/packages/hoppscotch-common/src/components.d.ts b/packages/hoppscotch-common/src/components.d.ts index 89d47a60b..72a3dc6ec 100644 --- a/packages/hoppscotch-common/src/components.d.ts +++ b/packages/hoppscotch-common/src/components.d.ts @@ -160,6 +160,7 @@ declare module 'vue' { 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'] InterceptorsErrorPlaceholder: typeof import('./components/interceptors/ErrorPlaceholder.vue')['default'] InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default'] LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default'] diff --git a/packages/hoppscotch-common/src/components/graphql/Response.vue b/packages/hoppscotch-common/src/components/graphql/Response.vue index 979503773..1a5d31407 100644 --- a/packages/hoppscotch-common/src/components/graphql/Response.vue +++ b/packages/hoppscotch-common/src/components/graphql/Response.vue @@ -25,7 +25,7 @@ :title="`${t( 'action.download_file' )} ${getSpecialKey()}J`" - :icon="downloadResponseIcon" + :icon="downloadIcon" @click="downloadResponse" /> + + + +
@@ -59,22 +91,22 @@ diff --git a/packages/hoppscotch-common/src/components/lenses/renderers/JSONLensRenderer.vue b/packages/hoppscotch-common/src/components/lenses/renderers/JSONLensRenderer.vue index 3049b91d2..7c28c11d9 100644 --- a/packages/hoppscotch-common/src/components/lenses/renderers/JSONLensRenderer.vue +++ b/packages/hoppscotch-common/src/components/lenses/renderers/JSONLensRenderer.vue @@ -44,6 +44,39 @@ :icon="copyIcon" @click="copyResponse" /> + + + +
import IconWrapText from "~icons/lucide/wrap-text" import IconFilter from "~icons/lucide/filter" +import IconMore from "~icons/lucide/more-horizontal" import IconHelpCircle from "~icons/lucide/help-circle" +import IconCopy from "~icons/lucide/copy" import * as LJSON from "lossless-json" import * as O from "fp-ts/Option" import * as E from "fp-ts/Either" @@ -221,9 +256,11 @@ import { useCopyResponse, useResponseBody, useDownloadResponse, + useCopyInterface, } from "@composables/lens-actions" import { defineActionHandler } from "~/helpers/actions" import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils" +import interfaceLanguages from "~/helpers/utils/interfaceLanguages" const t = useI18n() @@ -235,6 +272,13 @@ const { responseBodyText } = useResponseBody(props.response) const toggleFilter = ref(false) const filterQueryText = ref("") +const copiedInterfaceLanguage = ref("") + +const runCopyInterface = (language: string) => { + copyInterface(language).then(() => { + copiedInterfaceLanguage.value = language + }) +} type BodyParseError = | { type: "JSON_PARSE_FAILED" } @@ -319,6 +363,7 @@ const filterResponseError = computed(() => ) const { copyIcon, copyResponse } = useCopyResponse(jsonBodyText) +const { copyInterfaceIcon, copyInterface } = useCopyInterface(jsonBodyText) const { downloadIcon, downloadResponse } = useDownloadResponse( "application/json", jsonBodyText @@ -327,6 +372,7 @@ const { downloadIcon, downloadResponse } = useDownloadResponse( // Template refs const tippyActions = ref(null) const jsonResponse = ref(null) +const copyInterfaceTippyActions = ref(null) const linewrapEnabled = ref(true) const { cursor } = useCodemirror( diff --git a/packages/hoppscotch-common/src/composables/lens-actions.ts b/packages/hoppscotch-common/src/composables/lens-actions.ts index 160365c9a..9e02ac3c9 100644 --- a/packages/hoppscotch-common/src/composables/lens-actions.ts +++ b/packages/hoppscotch-common/src/composables/lens-actions.ts @@ -11,6 +11,29 @@ import { refAutoReset } from "@vueuse/core" import { copyToClipboard } from "@helpers/utils/clipboard" import { HoppRESTResponse } from "@helpers/types/HoppRESTResponse" import { platform } from "~/platform" +import jsonToLanguage from "~/helpers/utils/json-to-language" + +export function useCopyInterface(responseBodyText: Ref) { + const toast = useToast() + const t = useI18n() + + const copyInterfaceIcon = refAutoReset(IconCopy, 1000) + + const copyInterface = async (targetLanguage: string) => { + jsonToLanguage(targetLanguage, responseBodyText.value).then((res) => { + copyToClipboard(res.lines.join("\n")) + copyInterfaceIcon.value = IconCheck + toast.success( + t("state.copied_interface_to_clipboard", { language: targetLanguage }) + ) + }) + } + + return { + copyInterfaceIcon, + copyInterface, + } +} export function useCopyResponse(responseBodyText: Ref) { const toast = useToast() diff --git a/packages/hoppscotch-common/src/helpers/utils/interfaceLanguages.ts b/packages/hoppscotch-common/src/helpers/utils/interfaceLanguages.ts new file mode 100644 index 000000000..253308648 --- /dev/null +++ b/packages/hoppscotch-common/src/helpers/utils/interfaceLanguages.ts @@ -0,0 +1,26 @@ +const interfaceLanguages = [ + "cJSON", + "C++", + "C#", + "Crystal", + "Dart", + "Elm", + "Flow", + "Go", + "Haskell", + "Java", + "JavaScript", + "Kotlin", + "Objective-C", + "PHP", + "Pike", + "Python", + "Ruby", + "Rust", + "Scala3", + "Smithy", + "Swift", + "TypeScript", +] + +export default interfaceLanguages diff --git a/packages/hoppscotch-common/src/helpers/utils/json-to-language.ts b/packages/hoppscotch-common/src/helpers/utils/json-to-language.ts new file mode 100644 index 000000000..f1bf21384 --- /dev/null +++ b/packages/hoppscotch-common/src/helpers/utils/json-to-language.ts @@ -0,0 +1,27 @@ +import { + quicktype, + InputData, + jsonInputForTargetLanguage, +} from "quicktype-core" + +async function jsonToLanguage(targetLanguage: string, jsonString: string) { + const jsonInput = jsonInputForTargetLanguage(targetLanguage) + + await jsonInput.addSource({ + name: "JSONSchema", + samples: [jsonString], + }) + + const inputData = new InputData() + inputData.addInput(jsonInput) + + return await quicktype({ + inputData, + lang: targetLanguage, + rendererOptions: { + "just-types": true, + }, + }) +} + +export default jsonToLanguage diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index afdc6ffb6..f161e81d5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -532,6 +532,9 @@ importers: qs: specifier: ^6.11.2 version: 6.11.2 + quicktype-core: + specifier: ^23.0.79 + version: 23.0.79 rxjs: specifier: ^7.8.1 version: 7.8.1 @@ -5509,6 +5512,10 @@ packages: resolution: {integrity: sha512-iNZz251NVB1sENvrTRt7x5t1yuaYai/QsBRwYtEiot+R33Ks8CQnDZxY2kbrFdEFwTJVqb7RyObjgGVuysTRUw==} dev: false + /@glideapps/ts-necessities@2.1.3: + resolution: {integrity: sha512-q9U8v/n9qbkd2zDYjuX3qtlbl+OIyI9zF+zQhZjfYOE9VMDH7tfcUSJ9p0lXoY3lxmGFne09yi4iiNeQUwV7AA==} + dev: false + /@graphql-codegen/add@4.0.1(graphql@16.6.0): resolution: {integrity: sha512-A7k+9eRfrKyyNfhWEN/0eKz09R5cp4XXxUuNLQAVm/aohmVI2xdMV4lM02rTlM6Pyou3cU/v0iZnhgo6IRpqeg==} peerDependencies: @@ -9253,7 +9260,7 @@ packages: resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==} dependencies: '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.19 + '@jridgewell/trace-mapping': 0.3.20 /@jridgewell/sourcemap-codec@1.4.14: resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} @@ -9287,7 +9294,6 @@ packages: dependencies: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 - dev: true /@jridgewell/trace-mapping@0.3.9: resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} @@ -10978,6 +10984,10 @@ packages: resolution: {integrity: sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==} dev: true + /@types/urijs@1.19.23: + resolution: {integrity: sha512-3Zbk6RzmIpvKTNEHO2RcPOGHM++BQEITMqBRR1Ju32WbruhV/pygYgxiP3xA0b1B88zjzs0Izzjxsbj768+IjA==} + dev: false + /@types/uuid@9.0.2: resolution: {integrity: sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==} dev: true @@ -13738,6 +13748,10 @@ packages: dependencies: fill-range: 7.0.1 + /browser-or-node@2.1.1: + resolution: {integrity: sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==} + dev: false + /browser-process-hrtime@1.0.0: resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==} dev: true @@ -14209,6 +14223,10 @@ packages: resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==} dev: true + /collection-utils@1.0.1: + resolution: {integrity: sha512-LA2YTIlR7biSpXkKYwwuzGjwL5rjWEZVOSnvdUc7gObvWe4WkjxOpfrdhoP7Hs09YWDVfg0Mal9BpAqLfVEzQg==} + dev: false + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -14605,6 +14623,14 @@ packages: - encoding dev: true + /cross-fetch@4.0.0: + resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==} + dependencies: + node-fetch: 2.6.12 + transitivePeerDependencies: + - encoding + dev: false + /cross-spawn@6.0.5: resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} engines: {node: '>=4.8'} @@ -18769,6 +18795,10 @@ packages: tslib: 2.6.2 dev: true + /is-url@1.2.4: + resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + dev: false + /is-weakmap@2.0.1: resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} dev: true @@ -20475,6 +20505,10 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} + /js-base64@3.7.5: + resolution: {integrity: sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==} + dev: false + /js-beautify@1.14.7: resolution: {integrity: sha512-5SOX1KXPFKx+5f6ZrPsIPEY7NwKeQz47n3jm2i+XeHx9MoRsfQenlOP13FQhWvg8JRS0+XLO6XYUQ2GX+q+T9A==} engines: {node: '>=10'} @@ -21284,7 +21318,7 @@ packages: resolution: {integrity: sha1-e8xrYp46Q+hx1+Kaymrop/FcuyA=} dependencies: errno: 0.1.8 - readable-stream: 2.3.7 + readable-stream: 2.3.8 dev: false /memorystream@0.3.1: @@ -22475,6 +22509,10 @@ packages: resolution: {integrity: sha512-KPbL9KAB0ASvhSDbOrZBaccXS+/s7/LIofbPyERww8hM5Ko71GUJQ6Nmg0BWqj8phAIT8zdf/Sd/RftHU9i2HA==} dev: false + /pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + dev: false + /pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} dev: false @@ -22805,7 +22843,6 @@ packages: /pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} - dev: true /portfinder@1.0.32: resolution: {integrity: sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==} @@ -23377,6 +23414,28 @@ packages: resolution: {integrity: sha512-7b32VY45/Rmo/S81W0VcHnsPW9yvCbGOCjf+xl8XYcQIL/FmbfmwroJPnyXHRYC0HO8mk8cTkvsMC+0bR/NrJA==} dev: false + /quicktype-core@23.0.79: + resolution: {integrity: sha512-Auzy8AhorFt6YGeB53/dzUSINmKKassAyCsr2wpNgG9XoC3i6oUoLuySNUzYIkyCFCGmKdBRBQeyAqPOVteoYw==} + dependencies: + '@glideapps/ts-necessities': 2.1.3 + '@types/urijs': 1.19.23 + browser-or-node: 2.1.1 + collection-utils: 1.0.1 + cross-fetch: 4.0.0 + is-url: 1.2.4 + js-base64: 3.7.5 + lodash: 4.17.21 + pako: 1.0.11 + pluralize: 8.0.0 + readable-stream: 4.4.2 + unicode-properties: 1.4.1 + urijs: 1.19.11 + wordwrap: 1.0.0 + yaml: 2.3.1 + transitivePeerDependencies: + - encoding + dev: false + /ramda-adjunct@2.36.0(ramda@0.27.2): resolution: {integrity: sha512-8w+/Hx73oByS+vo+BfAPOG3HYL2ay6O5fjrJpR7NFxMoFWksKz6vSOtvjqdfMM6MfAimHizq9tpdI0OD4xbKog==} engines: {node: '>=0.10.3'} @@ -23486,18 +23545,6 @@ packages: string_decoder: 0.10.31 dev: false - /readable-stream@2.3.7: - resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - dev: false - /readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} dependencies: @@ -23517,6 +23564,17 @@ packages: string_decoder: 1.1.1 util-deprecate: 1.0.2 + /readable-stream@4.4.2: + resolution: {integrity: sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + dev: false + /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -24702,6 +24760,12 @@ packages: dependencies: safe-buffer: 5.1.2 + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + dev: false + /stringify-object@3.3.0: resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} engines: {node: '>=4'} @@ -25141,7 +25205,7 @@ packages: hasBin: true dependencies: '@jridgewell/source-map': 0.3.5 - acorn: 8.10.0 + acorn: 8.11.2 commander: 2.20.3 source-map-support: 0.5.21 dev: true @@ -25201,6 +25265,10 @@ packages: resolution: {integrity: sha512-pkJC8uIP/gxDHxNQUBUbjHyl6oZfT+ofn7tbaHW+CFIUjI+Q2MBbHcx1JSBQfhDaTcO9bNg328q0i7Vk5PismQ==} dev: false + /tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + dev: false + /tiny-invariant@1.2.0: resolution: {integrity: sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==} dev: true @@ -26041,10 +26109,24 @@ packages: resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} engines: {node: '>=4'} + /unicode-properties@1.4.1: + resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==} + dependencies: + base64-js: 1.5.1 + unicode-trie: 2.0.0 + dev: false + /unicode-property-aliases-ecmascript@2.1.0: resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} engines: {node: '>=4'} + /unicode-trie@2.0.0: + resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + dev: false + /union@0.5.0: resolution: {integrity: sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==} engines: {node: '>= 0.8.0'} @@ -26585,6 +26667,10 @@ packages: dependencies: punycode: 2.3.0 + /urijs@1.19.11: + resolution: {integrity: sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==} + dev: false + /url-join@4.0.1: resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} dev: true