From 9371457d0d44057d794f8fcd507439cfa4d49745 Mon Sep 17 00:00:00 2001
From: Nivedin <53208152+nivedin@users.noreply.github.com>
Date: Wed, 21 May 2025 21:03:58 +0530
Subject: [PATCH] chore: display platform links in app header (#5091)
---
.../hoppscotch-common/assets/icons/apple.svg | 1 +
.../hoppscotch-common/assets/icons/linux.svg | 1 +
.../assets/icons/windows.svg | 1 +
packages/hoppscotch-common/locales/en.json | 7 ++
.../src/components/app/Header.vue | 79 +++++++++++---
.../src/platform/additionalLinks.ts | 8 ++
.../hoppscotch-common/src/platform/index.ts | 2 +
.../src/services/additionalLinks.service.ts | 55 ++++++++++
packages/hoppscotch-selfhost-web/src/main.ts | 2 +
.../headerDownloadableLinks.service.ts | 101 ++++++++++++++++++
10 files changed, 240 insertions(+), 17 deletions(-)
create mode 100644 packages/hoppscotch-common/assets/icons/apple.svg
create mode 100644 packages/hoppscotch-common/assets/icons/linux.svg
create mode 100644 packages/hoppscotch-common/assets/icons/windows.svg
create mode 100644 packages/hoppscotch-common/src/platform/additionalLinks.ts
create mode 100644 packages/hoppscotch-common/src/services/additionalLinks.service.ts
create mode 100644 packages/hoppscotch-selfhost-web/src/services/headerDownloadableLinks.service.ts
diff --git a/packages/hoppscotch-common/assets/icons/apple.svg b/packages/hoppscotch-common/assets/icons/apple.svg
new file mode 100644
index 00000000..6e9303c3
--- /dev/null
+++ b/packages/hoppscotch-common/assets/icons/apple.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/hoppscotch-common/assets/icons/linux.svg b/packages/hoppscotch-common/assets/icons/linux.svg
new file mode 100644
index 00000000..393e95e8
--- /dev/null
+++ b/packages/hoppscotch-common/assets/icons/linux.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/hoppscotch-common/assets/icons/windows.svg b/packages/hoppscotch-common/assets/icons/windows.svg
new file mode 100644
index 00000000..92b6d8cb
--- /dev/null
+++ b/packages/hoppscotch-common/assets/icons/windows.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/hoppscotch-common/locales/en.json b/packages/hoppscotch-common/locales/en.json
index cf565fdc..4b8cf526 100644
--- a/packages/hoppscotch-common/locales/en.json
+++ b/packages/hoppscotch-common/locales/en.json
@@ -118,6 +118,13 @@
"add_pfx_or_pkcs_file": "Add PFX/PKCS12 File"
},
"app": {
+ "additional_links": {
+ "macOS": "macOS",
+ "windows": "Windows",
+ "linux": "Linux",
+ "web_app": "Web App",
+ "cli": "CLI"
+ },
"chat_with_us": "Chat with us",
"contact_us": "Contact us",
"cookies": "Cookies",
diff --git a/packages/hoppscotch-common/src/components/app/Header.vue b/packages/hoppscotch-common/src/components/app/Header.vue
index 705b5b31..6d72a9a3 100644
--- a/packages/hoppscotch-common/src/components/app/Header.vue
+++ b/packages/hoppscotch-common/src/components/app/Header.vue
@@ -59,14 +59,47 @@
class="col-span-2 flex items-center justify-between space-x-2"
>
-
+
+
+
+
+
+
+
+
+
+
+
(null) : ref(null)
+const downloadableLinksRef =
+ kernelMode === "web" ? ref(null) : ref(null)
const isUserAdmin = ref(false)
@@ -380,14 +415,6 @@ const workspaceSelectorFlagEnabled = computed(
() => !!platform.platformFeatureFlags.workspaceSwitcherLogin?.value
)
-/**
- * Once the PWA code is initialized, this holds a method
- * that can be called to show the user the installation
- * prompt.
- */
-
-const showInstallButton = computed(() => !!pwaDefferedPrompt.value)
-
/**
* Show the dashboard link if the user is not on the default cloud instance and is an admin
*/
@@ -420,6 +447,24 @@ const offlineBanner: BannerContent = {
dismissible: true,
}
+const additionalLinks = useService(AdditionalLinksService)
+
+platform.additionalLinks?.forEach((linkSet) => {
+ useService(linkSet)
+})
+
+const downloadableLinks = computed(() => {
+ if (kernelMode !== "web") return null
+
+ const headerDownloadableLink = additionalLinks?.getLinkSet(
+ "HEADER_DOWNLOADABLE_LINKS"
+ )
+
+ if (!headerDownloadableLink) return null
+
+ return headerDownloadableLink.getLinks().value
+})
+
// Show the offline banner if the app is offline
const network = reactive(useNetwork())
const isOnline = computed(() => network.isOnline)
diff --git a/packages/hoppscotch-common/src/platform/additionalLinks.ts b/packages/hoppscotch-common/src/platform/additionalLinks.ts
new file mode 100644
index 00000000..38ba4e6f
--- /dev/null
+++ b/packages/hoppscotch-common/src/platform/additionalLinks.ts
@@ -0,0 +1,8 @@
+import { Container, ServiceClassInstance } from "dioc"
+import { AdditionalLinksService } from "~/services/additionalLinks.service"
+
+export type AdditionalLinksPlatformDef = Array<
+ ServiceClassInstance & {
+ new (c: Container): AdditionalLinksService
+ }
+>
diff --git a/packages/hoppscotch-common/src/platform/index.ts b/packages/hoppscotch-common/src/platform/index.ts
index 765782bd..b79e475b 100644
--- a/packages/hoppscotch-common/src/platform/index.ts
+++ b/packages/hoppscotch-common/src/platform/index.ts
@@ -20,6 +20,7 @@ import { UIPlatformDef } from "./ui"
import { BackendPlatformDef } from "./backend"
import { OrganizationPlatformDef } from "./organization"
import { KernelIO } from "./kernel-io"
+import { AdditionalLinksPlatformDef } from "./additionalLinks"
export type PlatformDef = {
ui?: UIPlatformDef
@@ -69,6 +70,7 @@ export type PlatformDef = {
experiments?: ExperimentsPlatformDef
backend: BackendPlatformDef
organization?: OrganizationPlatformDef
+ additionalLinks?: AdditionalLinksPlatformDef
}
export let platform: PlatformDef
diff --git a/packages/hoppscotch-common/src/services/additionalLinks.service.ts b/packages/hoppscotch-common/src/services/additionalLinks.service.ts
new file mode 100644
index 00000000..827d3568
--- /dev/null
+++ b/packages/hoppscotch-common/src/services/additionalLinks.service.ts
@@ -0,0 +1,55 @@
+import { Service } from "dioc"
+import { Component, ComputedRef, Ref } from "vue"
+import { getI18n } from "~/modules/i18n"
+
+export type Link = {
+ id: string
+ text: (t: ReturnType) => string
+ icon: Component
+ action: { type: "link"; href: string } | { type: "custom"; do: () => void }
+ show?: ComputedRef
+}
+
+// The possible links IDs
+type LinkID = "HEADER_DOWNLOADABLE_LINKS" | string
+
+export interface AdditionalLinkSet {
+ linkSetID: LinkID
+ getLinks: () => Ref
+}
+
+/**
+ * Service to manage additional links in the app
+ * This service is used to register and retrieve link sets
+ * that can be displayed in the app's UI.
+ * Each link set can contain multiple links, each with its own action.
+ * The links can be displayed in different parts of the app, such as the header or footer.
+ */
+export class AdditionalLinksService extends Service {
+ public static readonly ID = "ADDITIONAL_LINKS_SERVICE"
+
+ private additionalLinkSets: Map = new Map()
+
+ /**
+ * Registers a link set with the LinksService
+ * @param linkSet The link set to register
+ */
+ public registerAdditionalSet(linkSet: AdditionalLinkSet) {
+ this.additionalLinkSets.set(linkSet.linkSetID, linkSet)
+ }
+
+ /**
+ * Gets all registered link sets
+ */
+ public getAllLinkSets(): IterableIterator<[string, AdditionalLinkSet]> {
+ return this.additionalLinkSets.entries()
+ }
+
+ /**
+ * Gets a link set by its ID
+ * @param linkSetID The ID of the link set to get
+ */
+ public getLinkSet(linkSetID: LinkID): AdditionalLinkSet | undefined {
+ return this.additionalLinkSets.get(linkSetID)
+ }
+}
diff --git a/packages/hoppscotch-selfhost-web/src/main.ts b/packages/hoppscotch-selfhost-web/src/main.ts
index 08f73c61..39927b1f 100644
--- a/packages/hoppscotch-selfhost-web/src/main.ts
+++ b/packages/hoppscotch-selfhost-web/src/main.ts
@@ -26,6 +26,7 @@ import { AgentKernelInterceptorService } from "@hoppscotch/common/platform/std/k
import { ProxyKernelInterceptorService } from "@hoppscotch/common/platform/std/kernel-interceptors/proxy"
import { ExtensionKernelInterceptorService } from "@hoppscotch/common/platform/std/kernel-interceptors/extension"
import { BrowserKernelInterceptorService } from "@hoppscotch/common/platform/std/kernel-interceptors/browser"
+import { HeaderDownloadableLinksService } from "./services/headerDownloadableLinks.service"
type Platform = "web" | "desktop"
@@ -98,6 +99,7 @@ async function initApp() {
},
infra: InfraPlatform,
backend: stdBackendDef,
+ additionalLinks: [HeaderDownloadableLinksService],
})
if (kernelMode === "desktop") {
diff --git a/packages/hoppscotch-selfhost-web/src/services/headerDownloadableLinks.service.ts b/packages/hoppscotch-selfhost-web/src/services/headerDownloadableLinks.service.ts
new file mode 100644
index 00000000..b9dd9fe8
--- /dev/null
+++ b/packages/hoppscotch-selfhost-web/src/services/headerDownloadableLinks.service.ts
@@ -0,0 +1,101 @@
+import { Service } from "dioc"
+import { computed, markRaw, Ref, ref } from "vue"
+import { installPWA, pwaDefferedPrompt } from "@hoppscotch/common/modules/pwa"
+import IconGlobe from "~icons/lucide/globe"
+import IconCLI from "~icons/lucide/square-terminal"
+import IconApple from "~icons/hopp/apple"
+import IconWindows from "~icons/hopp/windows"
+import IconLinux from "~icons/hopp/linux"
+
+import {
+ AdditionalLinkSet,
+ AdditionalLinksService,
+ Link,
+} from "@hoppscotch/common/services/additionalLinks.service"
+
+const macOS: Link = {
+ id: "whats-new",
+ text: (t) => t("app.additional_links.macOS"),
+ icon: markRaw(IconApple),
+ action: {
+ type: "link",
+ href: "https://hoppscotch.com/download?platform=macOS",
+ },
+}
+
+const windows: Link = {
+ id: "windows",
+ text: (t) => t("app.additional_links.windows"),
+ icon: markRaw(IconWindows),
+ action: {
+ type: "link",
+ href: "https://hoppscotch.com/download?platform=windows",
+ },
+}
+
+const linux: Link = {
+ id: "linux",
+ text: (t) => t("app.additional_links.linux"),
+ icon: markRaw(IconLinux),
+ action: {
+ type: "link",
+ href: "https://hoppscotch.com/download?platform=linux",
+ },
+}
+
+const pwa: Link = {
+ id: "pwa",
+ text: (t) => t("app.additional_links.web_app"),
+ icon: IconGlobe,
+ action: {
+ type: "custom",
+ do: () => {
+ installPWA()
+ },
+ },
+ show: computed(() => !!pwaDefferedPrompt.value),
+}
+
+const cli: Link = {
+ id: "cli",
+ text: (t) => t("app.additional_links.cli"),
+ icon: IconCLI,
+ action: {
+ type: "link",
+ href: "https://docs.hoppscotch.io/documentation/clients/cli/overview",
+ },
+}
+
+/**
+ * Service to manage the downloadable links in the app header.
+ */
+export class HeaderDownloadableLinksService
+ extends Service
+ implements AdditionalLinkSet
+{
+ public static readonly ID = "HEADER_DOWNLOADABLE_LINKS_SERVICE"
+ public readonly linkSetID = "HEADER_DOWNLOADABLE_LINKS"
+
+ private readonly additionalLinkSet = this.bind(AdditionalLinksService)
+
+ /**
+ * List of downloadable links to be shown in the header
+ * This includes showing the link to the desktop app, PWA, CLI.
+ */
+ private headerDownloadableLinks = ref([
+ macOS,
+ windows,
+ linux,
+ pwa,
+ cli,
+ ])
+
+ override onServiceInit() {
+ this.additionalLinkSet.registerAdditionalSet(this)
+ }
+
+ getLinks(): Ref {
+ // @ts-expect-error show type not recognizing ComputedRef
+ return this.headerDownloadableLinks
+ }
+}