From 7811f3b53e4edaf4510d808d88cc875ea71670c4 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Tue, 7 Nov 2023 02:36:27 +0530 Subject: [PATCH] refactor: move download file out of common into platform --- .../collections/graphql/ImportExport.vue | 40 +++++---- .../src/components/collections/index.vue | 38 ++++----- .../components/environments/ImportExport.vue | 32 ++++--- .../src/components/graphql/Response.vue | 35 +++++--- .../src/components/graphql/Sidebar.vue | 39 ++++++--- .../src/composables/lens-actions.ts | 39 +++++---- .../import-export/export/environment.ts | 28 ++++--- .../hoppscotch-common/src/platform/index.ts | 2 + packages/hoppscotch-common/src/platform/io.ts | 84 +++++++++++++++++++ .../hoppscotch-common/src/platform/std/io.ts | 37 ++++++++ .../src-tauri/Cargo.lock | 79 +++++++++++++++++ .../src-tauri/Cargo.toml | 2 +- .../src-tauri/tauri.conf.json | 12 +-- .../hoppscotch-selfhost-desktop/src/main.ts | 2 + .../src/platform/io.ts | 24 ++++++ packages/hoppscotch-selfhost-web/src/main.ts | 2 + 16 files changed, 392 insertions(+), 103 deletions(-) create mode 100644 packages/hoppscotch-common/src/platform/io.ts create mode 100644 packages/hoppscotch-common/src/platform/std/io.ts create mode 100644 packages/hoppscotch-selfhost-desktop/src/platform/io.ts diff --git a/packages/hoppscotch-common/src/components/collections/graphql/ImportExport.vue b/packages/hoppscotch-common/src/components/collections/graphql/ImportExport.vue index ec92bbfc3..0c30de333 100644 --- a/packages/hoppscotch-common/src/components/collections/graphql/ImportExport.vue +++ b/packages/hoppscotch-common/src/components/collections/graphql/ImportExport.vue @@ -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()) + } } diff --git a/packages/hoppscotch-common/src/components/collections/index.vue b/packages/hoppscotch-common/src/components/collections/index.vue index c903a068f..a19370048 100644 --- a/packages/hoppscotch-common/src/components/collections/index.vue +++ b/packages/hoppscotch-common/src/components/collections/index.vue @@ -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 } ) diff --git a/packages/hoppscotch-common/src/components/environments/ImportExport.vue b/packages/hoppscotch-common/src/components/environments/ImportExport.vue index a84461d3e..4fa07b55e 100644 --- a/packages/hoppscotch-common/src/components/environments/ImportExport.vue +++ b/packages/hoppscotch-common/src/components/environments/ImportExport.vue @@ -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) => { diff --git a/packages/hoppscotch-common/src/components/graphql/Response.vue b/packages/hoppscotch-common/src/components/graphql/Response.vue index 76f7ae65f..54010711a 100644 --- a/packages/hoppscotch-common/src/components/graphql/Response.vue +++ b/packages/hoppscotch-common/src/components/graphql/Response.vue @@ -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( diff --git a/packages/hoppscotch-common/src/components/graphql/Sidebar.vue b/packages/hoppscotch-common/src/components/graphql/Sidebar.vue index b297814ab..fbf51e047 100644 --- a/packages/hoppscotch-common/src/components/graphql/Sidebar.vue +++ b/packages/hoppscotch-common/src/components/graphql/Sidebar.vue @@ -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 = () => { diff --git a/packages/hoppscotch-common/src/composables/lens-actions.ts b/packages/hoppscotch-common/src/composables/lens-actions.ts index c2f22a614..160365c9a 100644 --- a/packages/hoppscotch-common/src/composables/lens-actions.ts +++ b/packages/hoppscotch-common/src/composables/lens-actions.ts @@ -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) { 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, diff --git a/packages/hoppscotch-common/src/helpers/import-export/export/environment.ts b/packages/hoppscotch-common/src/helpers/import-export/export/environment.ts index 5bc6a42e0..82c812e57 100644 --- a/packages/hoppscotch-common/src/helpers/import-export/export/environment.ts +++ b/packages/hoppscotch-common/src/helpers/import-export/export/environment.ts @@ -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 } diff --git a/packages/hoppscotch-common/src/platform/index.ts b/packages/hoppscotch-common/src/platform/index.ts index 7bb2d9f7d..e527d6174 100644 --- a/packages/hoppscotch-common/src/platform/index.ts +++ b/packages/hoppscotch-common/src/platform/index.ts @@ -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 diff --git a/packages/hoppscotch-common/src/platform/io.ts b/packages/hoppscotch-common/src/platform/io.ts new file mode 100644 index 000000000..97d32a7a5 --- /dev/null +++ b/packages/hoppscotch-common/src/platform/io.ts @@ -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 +} diff --git a/packages/hoppscotch-common/src/platform/std/io.ts b/packages/hoppscotch-common/src/platform/std/io.ts new file mode 100644 index 000000000..568a7ecbf --- /dev/null +++ b/packages/hoppscotch-common/src/platform/std/io.ts @@ -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" }) + }, +} diff --git a/packages/hoppscotch-selfhost-desktop/src-tauri/Cargo.lock b/packages/hoppscotch-selfhost-desktop/src-tauri/Cargo.lock index f4e5ba887..5ce14de2e 100644 --- a/packages/hoppscotch-selfhost-desktop/src-tauri/Cargo.lock +++ b/packages/hoppscotch-selfhost-desktop/src-tauri/Cargo.lock @@ -1896,6 +1896,17 @@ dependencies = [ "objc_exception", ] +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + [[package]] name = "objc-sys" version = "0.3.1" @@ -2510,6 +2521,30 @@ dependencies = [ "winreg 0.50.0", ] +[[package]] +name = "rfd" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea" +dependencies = [ + "block", + "dispatch", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "lazy_static", + "log", + "objc", + "objc-foundation", + "objc_id", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.37.0", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -3059,6 +3094,7 @@ dependencies = [ "raw-window-handle", "regex", "reqwest", + "rfd", "semver", "serde", "serde_json", @@ -3888,6 +3924,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" +dependencies = [ + "windows_aarch64_msvc 0.37.0", + "windows_i686_gnu 0.37.0", + "windows_i686_msvc 0.37.0", + "windows_x86_64_gnu 0.37.0", + "windows_x86_64_msvc 0.37.0", +] + [[package]] name = "windows" version = "0.39.0" @@ -4037,6 +4086,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" + [[package]] name = "windows_aarch64_msvc" version = "0.39.0" @@ -4055,6 +4110,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_i686_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" + [[package]] name = "windows_i686_gnu" version = "0.39.0" @@ -4073,6 +4134,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" + [[package]] name = "windows_i686_msvc" version = "0.39.0" @@ -4091,6 +4158,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_x86_64_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" + [[package]] name = "windows_x86_64_gnu" version = "0.39.0" @@ -4121,6 +4194,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" + [[package]] name = "windows_x86_64_msvc" version = "0.39.0" diff --git a/packages/hoppscotch-selfhost-desktop/src-tauri/Cargo.toml b/packages/hoppscotch-selfhost-desktop/src-tauri/Cargo.toml index b46dae3d8..7de7030bf 100644 --- a/packages/hoppscotch-selfhost-desktop/src-tauri/Cargo.toml +++ b/packages/hoppscotch-selfhost-desktop/src-tauri/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" tauri-build = { version = "1.4.0", features = [] } [dependencies] -tauri = { version = "1.4.1", features = ["http-all", "os-all", "shell-open", "window-start-dragging", "http-multipart"] } +tauri = { version = "1.4.1", features = [ "dialog-save", "fs-write-file", "http-all", "os-all", "shell-open", "window-start-dragging", "http-multipart"] } tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } tauri-plugin-deep-link = { git = "https://github.com/FabianLars/tauri-plugin-deep-link", branch = "main" } tauri-plugin-window-state = "0.1.0" diff --git a/packages/hoppscotch-selfhost-desktop/src-tauri/tauri.conf.json b/packages/hoppscotch-selfhost-desktop/src-tauri/tauri.conf.json index da68333b4..916edca72 100644 --- a/packages/hoppscotch-selfhost-desktop/src-tauri/tauri.conf.json +++ b/packages/hoppscotch-selfhost-desktop/src-tauri/tauri.conf.json @@ -20,14 +20,16 @@ "os": { "all": true }, + "fs": { + "writeFile": true + }, + "dialog": { + "save": true + }, "http": { "all": true, "request": true, - "scope": [ - "http://*", - "https://*", - "wss://*" - ] + "scope": ["http://*", "https://*", "wss://*"] }, "window": { "startDragging": true diff --git a/packages/hoppscotch-selfhost-desktop/src/main.ts b/packages/hoppscotch-selfhost-desktop/src/main.ts index 398c3ff3f..c150e3d3a 100644 --- a/packages/hoppscotch-selfhost-desktop/src/main.ts +++ b/packages/hoppscotch-selfhost-desktop/src/main.ts @@ -16,6 +16,7 @@ import { appWindow } from "@tauri-apps/api/window" import { stdFooterItems } from "@hoppscotch/common/platform/std/ui/footerItem" import { stdSupportOptionItems } from "@hoppscotch/common/platform/std/ui/supportOptionsItem" import { useMousePressed } from "@vueuse/core" +import { ioDef } from "./platform/io" const headerPaddingLeft = ref("0px") const headerPaddingTop = ref("0px") @@ -29,6 +30,7 @@ createHoppApp("#app", { paddingTop: headerPaddingTop, }, }, + io: ioDef, auth: authDef, sync: { environments: environmentsDef, diff --git a/packages/hoppscotch-selfhost-desktop/src/platform/io.ts b/packages/hoppscotch-selfhost-desktop/src/platform/io.ts new file mode 100644 index 000000000..0d4b9d2b8 --- /dev/null +++ b/packages/hoppscotch-selfhost-desktop/src/platform/io.ts @@ -0,0 +1,24 @@ +import { IOPlatformDef } from "@hoppscotch/common/platform/io" +import { save } from "@tauri-apps/api/dialog" +import { writeBinaryFile, writeTextFile } from "@tauri-apps/api/fs" + +export const ioDef: IOPlatformDef = { + async saveFileWithDialog(opts) { + const path = await save({ + filters: opts.filters, + defaultPath: opts.suggestedFilename, + }) + + if (path === null) { + return { type: "cancelled" } + } + + if (typeof opts.data === "string") { + await writeTextFile(path, opts.data) + } else { + await writeBinaryFile(path, opts.data) + } + + return { type: "saved", path } + }, +} diff --git a/packages/hoppscotch-selfhost-web/src/main.ts b/packages/hoppscotch-selfhost-web/src/main.ts index 1b7420dbf..6d3a3278e 100644 --- a/packages/hoppscotch-selfhost-web/src/main.ts +++ b/packages/hoppscotch-selfhost-web/src/main.ts @@ -11,6 +11,7 @@ import { ExtensionInspectorService } from "@hoppscotch/common/platform/std/inspe import { ExtensionInterceptorService } from "@hoppscotch/common/platform/std/interceptors/extension" import { stdFooterItems } from "@hoppscotch/common/platform/std/ui/footerItem" import { stdSupportOptionItems } from "@hoppscotch/common/platform/std/ui/supportOptionsItem" +import { browserIODef } from "@hoppscotch/common/platform/std/io" createHoppApp("#app", { ui: { @@ -18,6 +19,7 @@ createHoppApp("#app", { additionalSupportOptionsMenuItems: stdSupportOptionItems, }, auth: authDef, + io: browserIODef, sync: { environments: environmentsDef, collections: collectionsDef,