Files
hoppscotch/packages/hoppscotch-common/src/composables/lens-actions.ts
Anwarul Islam 9ad6a419c1 feat: new ui for response interface generation (#4105)
* feat: codegen body added

* feat: new ui added for response interface

* feat: generate code component added

* chore: default collection tab

* feat: generate data schema

* chore: clean up

* chore: minor code refactor

* fix: only render if `isDrawerOpen` is true

* chore: clean up

* chore: clean up

---------

Co-authored-by: nivedin <nivedinp@gmail.com>
2024-08-30 12:10:52 +05:30

189 lines
5.2 KiB
TypeScript

import { HoppRESTResponse } from "@helpers/types/HoppRESTResponse"
import { copyToClipboard } from "@helpers/utils/clipboard"
import { refAutoReset } from "@vueuse/core"
import { pipe } from "fp-ts/function"
import * as RNEA from "fp-ts/ReadonlyNonEmptyArray"
import * as S from "fp-ts/string"
import { computed, ComputedRef, onMounted, ref, Ref } from "vue"
import jsonToLanguage from "~/helpers/utils/json-to-language"
import { platform } from "~/platform"
import IconCheck from "~icons/lucide/check"
import IconCopy from "~icons/lucide/copy"
import IconDownload from "~icons/lucide/download"
import { useI18n } from "./i18n"
import { useToast } from "./toast"
export function useCopyInterface(responseBodyText: Ref<string>) {
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<any>) {
const toast = useToast()
const t = useI18n()
const copyIcon = refAutoReset(IconCopy, 1000)
const copyResponse = () => {
copyToClipboard(responseBodyText.value)
copyIcon.value = IconCheck
toast.success(`${t("state.copied_to_clipboard")}`)
}
return {
copyIcon,
copyResponse,
}
}
export type downloadResponseReturnType = (() => void) | Ref<any>
export function useDownloadResponse(
contentType: string,
responseBody: Ref<string | ArrayBuffer>
) {
const downloadIcon = refAutoReset(IconDownload, 1000)
const toast = useToast()
const t = useI18n()
const downloadResponse = async () => {
const dataToWrite = responseBody.value
// Guess extension and filename
const file = new Blob([dataToWrite], { type: contentType })
const url = URL.createObjectURL(file)
const filename = pipe(
url,
S.split("/"),
RNEA.last,
S.split("#"),
RNEA.head,
S.split("?"),
RNEA.head
)
URL.revokeObjectURL(url)
console.log(filename)
// TODO: Look at the mime type and determine extension ?
const result = await platform.io.saveFileWithDialog({
data: dataToWrite,
contentType: contentType,
suggestedFilename: filename,
})
// Assume success if unknown as we cannot determine
if (result.type === "unknown" || result.type === "saved") {
downloadIcon.value = IconCheck
toast.success(`${t("state.download_started")}`)
}
}
return {
downloadIcon,
downloadResponse,
}
}
export function usePreview(
previewEnabledDefault: boolean,
responseBodyText: Ref<string>
): {
previewFrame: Ref<HTMLIFrameElement | null>
previewEnabled: Ref<boolean>
togglePreview: () => void
} {
const previewFrame: Ref<HTMLIFrameElement | null> = ref(null)
const previewEnabled = ref(previewEnabledDefault)
const url = ref("")
// `previewFrame` is a template ref that gets attached to the `iframe` element when the component mounts
// Ensures the HTML content is rendered immediately after a request, persists between tab switches, and is not limited to preview toggles
onMounted(() => updatePreviewFrame())
// Prevent updating the `iframe` element attributes during preview toggle actions after they are set initially
const shouldUpdatePreviewFrame = computed(
() => previewFrame.value?.getAttribute("data-previewing-url") !== url.value
)
const updatePreviewFrame = () => {
if (
previewEnabled.value &&
previewFrame.value &&
shouldUpdatePreviewFrame.value
) {
// Use DOMParser to parse document HTML.
const previewDocument = new DOMParser().parseFromString(
responseBodyText.value,
"text/html"
)
// Inject <base href="..."> tag to head, to fix relative CSS/HTML paths.
previewDocument.head.innerHTML =
`<base href="${url.value}">` + previewDocument.head.innerHTML
// Finally, set the iframe source to the resulting HTML.
previewFrame.value.srcdoc = previewDocument.documentElement.outerHTML
previewFrame.value.setAttribute("data-previewing-url", url.value)
}
}
const togglePreview = () => {
previewEnabled.value = !previewEnabled.value
updatePreviewFrame()
}
return {
previewFrame,
previewEnabled,
togglePreview,
}
}
export function useResponseBody(response: HoppRESTResponse): {
responseBodyText: ComputedRef<string>
} {
const responseBodyText = computed(() => {
if (
response.type === "loading" ||
response.type === "network_fail" ||
response.type === "script_fail" ||
response.type === "fail" ||
response.type === "extension_error"
)
return ""
return getResponseBodyText(response.body)
})
return {
responseBodyText,
}
}
export function getResponseBodyText(body: ArrayBuffer): string {
if (typeof body === "string") return body
const res = new TextDecoder("utf-8").decode(body)
// HACK: Temporary trailing null character issue from the extension fix
return res.replace(/\0+$/, "")
}