refactor: move download file out of common into platform

This commit is contained in:
Andrew Bastin
2023-11-07 02:36:27 +05:30
parent 2757722fd0
commit 7811f3b53e
16 changed files with 392 additions and 103 deletions

View File

@@ -258,7 +258,7 @@ const importFromJSON = () => {
inputChooseFileToImportFrom.value.value = ""
}
const exportJSON = () => {
const exportJSON = async () => {
const dataToWrite = collectionJson.value
const parsedCollections = JSON.parse(dataToWrite)
@@ -268,24 +268,32 @@ const exportJSON = () => {
}
const file = new Blob([dataToWrite], { type: "application/json" })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
platform?.analytics?.logEvent({
type: "HOPP_EXPORT_COLLECTION",
exporter: "json",
platform: "gql",
const filename = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
URL.revokeObjectURL(url)
const result = await platform.io.saveFileWithDialog({
data: dataToWrite,
contentType: "application/json",
suggestedFilename: filename,
filters: [
{
name: "Hoppscotch Collection JSON file",
extensions: ["json"],
},
],
})
// TODO: get uri from meta
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
document.body.appendChild(a)
a.click()
toast.success(t("state.download_started").toString())
setTimeout(() => {
document.body.removeChild(a)
URL.revokeObjectURL(url)
}, 1000)
if (result.type === "unknown" || result.type === "saved") {
platform?.analytics?.logEvent({
type: "HOPP_EXPORT_COLLECTION",
exporter: "json",
platform: "gql",
})
toast.success(t("state.download_started").toString())
}
}
</script>

View File

@@ -1866,28 +1866,25 @@ const getJSONCollection = async () => {
* @param collectionJSON - JSON string of the collection
* @param name - Name of the collection set as the file name
*/
const initializeDownloadCollection = (
const initializeDownloadCollection = async (
collectionJSON: string,
name: string | null
) => {
const file = new Blob([collectionJSON], { type: "application/json" })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
const result = await platform.io.saveFileWithDialog({
data: collectionJSON,
contentType: "application/json",
suggestedFilename: `${name ?? "collection"}.json`,
filters: [
{
name: "Hoppscotch Collection JSON file",
extensions: ["json"],
},
],
})
if (name) {
a.download = `${name}.json`
} else {
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
if (result.type === "unknown" || result.type === "saved") {
toast.success(t("state.download_started").toString())
}
document.body.appendChild(a)
a.click()
toast.success(t("state.download_started").toString())
setTimeout(() => {
document.body.removeChild(a)
URL.revokeObjectURL(url)
}, 1000)
}
/**
@@ -1916,11 +1913,14 @@ const exportData = async (
exportLoading.value = false
return
},
(coll) => {
async (coll) => {
const hoppColl = teamCollToHoppRESTColl(coll)
const collectionJSONString = JSON.stringify(hoppColl)
initializeDownloadCollection(collectionJSONString, hoppColl.name)
await initializeDownloadCollection(
collectionJSONString,
hoppColl.name
)
exportLoading.value = false
}
)

View File

@@ -375,7 +375,7 @@ const importFromPostman = ({
importFromHoppscotch(environments)
}
const exportJSON = () => {
const exportJSON = async () => {
const dataToWrite = environmentJson.value
const parsedCollections = JSON.parse(dataToWrite)
@@ -385,19 +385,27 @@ const exportJSON = () => {
}
const file = new Blob([dataToWrite], { type: "application/json" })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
// TODO: get uri from meta
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
document.body.appendChild(a)
a.click()
toast.success(t("state.download_started").toString())
setTimeout(() => {
document.body.removeChild(a)
URL.revokeObjectURL(url)
}, 1000)
const filename = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
URL.revokeObjectURL(url)
const result = await platform.io.saveFileWithDialog({
data: dataToWrite,
contentType: "application/json",
suggestedFilename: filename,
filters: [
{
name: "JSON file",
extensions: ["json"],
},
],
})
if (result.type === "unknown" || result.type === "saved") {
toast.success(t("state.download_started").toString())
}
}
const getErrorMessage = (err: GQLError<string>) => {

View File

@@ -59,6 +59,7 @@ import { useToast } from "@composables/toast"
import { defineActionHandler } from "~/helpers/actions"
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
import { GQLResponseEvent } from "~/helpers/graphql/connection"
import { platform } from "~/platform"
const t = useI18n()
const toast = useToast()
@@ -111,21 +112,31 @@ const copyResponse = (str: string) => {
toast.success(`${t("state.copied_to_clipboard")}`)
}
const downloadResponse = (str: string) => {
const downloadResponse = async (str: string) => {
const dataToWrite = str
const file = new Blob([dataToWrite!], { type: "application/json" })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}`
document.body.appendChild(a)
a.click()
downloadResponseIcon.value = IconCheck
toast.success(`${t("state.download_started")}`)
setTimeout(() => {
document.body.removeChild(a)
URL.revokeObjectURL(url)
}, 1000)
const filename = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
URL.revokeObjectURL(url)
const result = await platform.io.saveFileWithDialog({
data: dataToWrite,
contentType: "application/json",
suggestedFilename: filename,
filters: [
{
name: "JSON file",
extensions: ["json"],
},
],
})
if (result.type === "unknown" || result.type === "saved") {
downloadResponseIcon.value = IconCheck
toast.success(`${t("state.download_started")}`)
}
}
defineActionHandler(

View File

@@ -202,6 +202,7 @@ import {
schemaString,
subscriptionFields,
} from "~/helpers/graphql/connection"
import { platform } from "~/platform"
type NavigationTabs = "history" | "collection" | "docs" | "schema"
type GqlTabs = "queries" | "mutations" | "subscriptions" | "types"
@@ -372,21 +373,33 @@ useCodemirror(
})
)
const downloadSchema = () => {
const dataToWrite = JSON.stringify(schemaString.value, null, 2)
const downloadSchema = async () => {
const dataToWrite = schemaString.value
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 = IconCheck
toast.success(`${t("state.download_started")}`)
setTimeout(() => {
document.body.removeChild(a)
URL.revokeObjectURL(url)
}, 1000)
const filename = `${
url.split("/").pop()!.split("#")[0].split("?")[0]
}.graphql`
URL.revokeObjectURL(url)
const result = await platform.io.saveFileWithDialog({
data: dataToWrite,
contentType: "application/graphql",
suggestedFilename: filename,
filters: [
{
name: "GraphQL Schema File",
extensions: ["graphql"],
},
],
})
if (result.type === "unknown" || result.type === "saved") {
downloadSchemaIcon.value = IconCheck
toast.success(`${t("state.download_started")}`)
}
}
const copySchema = () => {

View File

@@ -10,6 +10,7 @@ import { useI18n } from "./i18n"
import { refAutoReset } from "@vueuse/core"
import { copyToClipboard } from "@helpers/utils/clipboard"
import { HoppRESTResponse } from "@helpers/types/HoppRESTResponse"
import { platform } from "~/platform"
export function useCopyResponse(responseBodyText: Ref<any>) {
const toast = useToast()
@@ -40,15 +41,14 @@ export function useDownloadResponse(
const toast = useToast()
const t = useI18n()
const downloadResponse = () => {
const downloadResponse = async () => {
const dataToWrite = responseBody.value
const file = new Blob([dataToWrite], { type: contentType })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
// TODO: get uri from meta
a.download = pipe(
// Guess extension and filename
const file = new Blob([dataToWrite], { type: contentType })
const url = URL.createObjectURL(file)
const filename = pipe(
url,
S.split("/"),
RNEA.last,
@@ -58,15 +58,24 @@ export function useDownloadResponse(
RNEA.head
)
document.body.appendChild(a)
a.click()
downloadIcon.value = IconCheck
toast.success(`${t("state.download_started")}`)
setTimeout(() => {
document.body.removeChild(a)
URL.revokeObjectURL(url)
}, 1000)
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,

View File

@@ -1,6 +1,7 @@
import { Environment } from "@hoppscotch/data"
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
import { cloneDeep } from "lodash-es"
import { platform } from "~/platform"
const getEnvironmentJson = (
environmentObj: TeamEnvironment | Environment,
@@ -32,17 +33,24 @@ export const exportAsJSON = (
if (!dataToWrite) return false
const file = new Blob([dataToWrite], { type: "application/json" })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
// Extracts the path from url, removes fragment identifier and query parameters if any, appends the ".json" extension, and assigns it
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
document.body.appendChild(a)
a.click()
setTimeout(() => {
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
}, 0)
URL.revokeObjectURL(url)
platform.io.saveFileWithDialog({
data: dataToWrite,
contentType: "application/json",
// Extracts the path from url, removes fragment identifier and query parameters if any, appends the ".json" extension, and assigns it
suggestedFilename: `${
url.split("/").pop()!.split("#")[0].split("?")[0]
}.json`,
filters: [
{
name: "JSON file",
extensions: ["json"],
},
],
})
return true
}

View File

@@ -9,12 +9,14 @@ import { AnalyticsPlatformDef } from "./analytics"
import { InterceptorsPlatformDef } from "./interceptors"
import { HoppModule } from "~/modules"
import { InspectorsPlatformDef } from "./inspectors"
import { IOPlatformDef } from "./io"
export type PlatformDef = {
ui?: UIPlatformDef
addedHoppModules?: HoppModule[]
auth: AuthPlatformDef
analytics?: AnalyticsPlatformDef
io: IOPlatformDef
sync: {
environments: EnvironmentsPlatformDef
collections: CollectionsPlatformDef

View File

@@ -0,0 +1,84 @@
/**
* Defines how to save a file to the user's filesystem.
*/
export type SaveFileWithDialogOptions = {
/**
* The data to be saved
*/
data: string | ArrayBuffer
/**
* The suggested filename for the file. This name will be shown in the
* save dialog by default when a save is initiated.
*/
suggestedFilename: string
/**
* The content type mime type of the data to be saved.
*
* NOTE: The usage of this data might be platform dependent.
* For example, this field is used in the web, but not in the desktop app.
*/
contentType: string
/**
* Defines the filters (like in Windows, on the right side, where you can
* select the file type) for the file dialog.
*
* NOTE: The usage of this data might be platform dependent.
* For example, this field is used in the web, but not in the desktop app.
*/
filters?: Array<{
/**
* The name of the filter (in Windows, if the filter looks
* like "Images (*.png, *.jpg)", the name would be "Images")
*/
name: string
/**
* The array of extensions that are supported, without the dot.
*/
extensions: string[]
}>
}
export type SaveFileResponse =
| {
/**
* The implementation was unable to determine the status of the save operation.
* This cannot be considered a success or a failure and should be handled as an uncertainity.
* The browser standard implementation (std) returns this value as there is no way to
* check if the user downloaded the file or not.
*/
type: "unknown"
}
| {
/**
* The result is known and the user cancelled the save.
*/
type: "cancelled"
}
| {
/**
* The result is known and the user saved the file.
*/
type: "saved"
/**
* The full path of where the file was saved
*/
path: string
}
/**
* Platform definitions for how to handle IO operations.
*/
export type IOPlatformDef = {
/**
* Defines how to save a file to the user's filesystem.
* The expected behaviour is for the browser to show a prompt to save the file.
*/
saveFileWithDialog: (
opts: SaveFileWithDialogOptions
) => Promise<SaveFileResponse>
}

View File

@@ -0,0 +1,37 @@
import { IOPlatformDef } from "../io"
import { pipe } from "fp-ts/function"
import * as S from "fp-ts/string"
import * as RNEA from "fp-ts/ReadonlyNonEmptyArray"
/**
* Implementation for how to handle IO operations in the browser.
*/
export const browserIODef: IOPlatformDef = {
saveFileWithDialog(opts) {
const file = new Blob([opts.data], { type: opts.contentType })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
a.download = pipe(
url,
S.split("/"),
RNEA.last,
S.split("#"),
RNEA.head,
S.split("?"),
RNEA.head
)
document.body.appendChild(a)
a.click()
setTimeout(() => {
document.body.removeChild(a)
URL.revokeObjectURL(url)
}, 1000)
// Browsers provide no way for us to know the save went successfully.
return Promise.resolve({ type: "unknown" })
},
}