chore: display platform links in app header (#5091)
This commit is contained in:
parent
130facb440
commit
9371457d0d
10 changed files with 240 additions and 17 deletions
1
packages/hoppscotch-common/assets/icons/apple.svg
Normal file
1
packages/hoppscotch-common/assets/icons/apple.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="0 0 24 24" fill="#ffffff"><title>Apple</title><path d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701" fill="#606060"/></svg>
|
||||
|
After Width: | Height: | Size: 680 B |
1
packages/hoppscotch-common/assets/icons/linux.svg
Normal file
1
packages/hoppscotch-common/assets/icons/linux.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.4 KiB |
1
packages/hoppscotch-common/assets/icons/windows.svg
Normal file
1
packages/hoppscotch-common/assets/icons/windows.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="0 0 24 24" fill="#E2E8F0"><title>Windows</title><path d="M0 3.449L9.75 2.1v9.451H0m10.949-9.602L24 0v11.4H10.949M0 12.6h9.75v9.451L0 20.699M10.949 12.6H24V24l-12.9-1.801" fill="#606060"/></svg>
|
||||
|
After Width: | Height: | Size: 253 B |
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -59,14 +59,47 @@
|
|||
class="col-span-2 flex items-center justify-between space-x-2"
|
||||
>
|
||||
<div class="flex">
|
||||
<HoppButtonSecondary
|
||||
v-if="showInstallButton"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('header.install_pwa')"
|
||||
:icon="IconDownload"
|
||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
@click="installPWA()"
|
||||
/>
|
||||
<tippy
|
||||
v-if="
|
||||
kernelMode === 'web' &&
|
||||
downloadableLinks &&
|
||||
downloadableLinks.length > 0
|
||||
"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
:on-shown="() => downloadableLinksRef.focus()"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:icon="IconDownload"
|
||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="downloadableLinksRef"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<template v-for="link in downloadableLinks" :key="link.id">
|
||||
<HoppButtonSecondary
|
||||
v-if="link.show ?? true"
|
||||
:icon="link.icon"
|
||||
:label="link.text(t)"
|
||||
:blank="true"
|
||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark justify-between"
|
||||
:to="
|
||||
link.action.type === 'link' ? link.action.href : undefined
|
||||
"
|
||||
@click="
|
||||
link.action.type === 'custom' ? link.action.do() : null
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
:title="`${
|
||||
|
|
@ -318,7 +351,6 @@ import { getKernelMode } from "@hoppscotch/kernel"
|
|||
import { useI18n } from "@composables/i18n"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import { defineActionHandler, invokeAction } from "@helpers/actions"
|
||||
import { installPWA, pwaDefferedPrompt } from "@modules/pwa"
|
||||
import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
|
||||
import { useService } from "dioc/vue"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
|
|
@ -344,6 +376,7 @@ import IconUserPlus from "~icons/lucide/user-plus"
|
|||
import IconUsers from "~icons/lucide/users"
|
||||
import IconChevronDown from "~icons/lucide/chevron-down"
|
||||
import IconLayoutDashboard from "~icons/lucide/layout-dashboard"
|
||||
import { AdditionalLinksService } from "~/services/additionalLinks.service"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
|
@ -352,6 +385,8 @@ const instanceSwitcherService =
|
|||
kernelMode === "desktop" ? useService(InstanceSwitcherService) : null
|
||||
const instanceSwitcherRef =
|
||||
kernelMode === "desktop" ? ref<any | null>(null) : ref(null)
|
||||
const downloadableLinksRef =
|
||||
kernelMode === "web" ? ref<any | null>(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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
import { Container, ServiceClassInstance } from "dioc"
|
||||
import { AdditionalLinksService } from "~/services/additionalLinks.service"
|
||||
|
||||
export type AdditionalLinksPlatformDef = Array<
|
||||
ServiceClassInstance<unknown> & {
|
||||
new (c: Container): AdditionalLinksService
|
||||
}
|
||||
>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<typeof getI18n>) => string
|
||||
icon: Component
|
||||
action: { type: "link"; href: string } | { type: "custom"; do: () => void }
|
||||
show?: ComputedRef<boolean>
|
||||
}
|
||||
|
||||
// The possible links IDs
|
||||
type LinkID = "HEADER_DOWNLOADABLE_LINKS" | string
|
||||
|
||||
export interface AdditionalLinkSet {
|
||||
linkSetID: LinkID
|
||||
getLinks: () => Ref<Link[]>
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<string, AdditionalLinkSet> = 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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") {
|
||||
|
|
|
|||
|
|
@ -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<Link[]>([
|
||||
macOS,
|
||||
windows,
|
||||
linux,
|
||||
pwa,
|
||||
cli,
|
||||
])
|
||||
|
||||
override onServiceInit() {
|
||||
this.additionalLinkSet.registerAdditionalSet(this)
|
||||
}
|
||||
|
||||
getLinks(): Ref<Link[]> {
|
||||
// @ts-expect-error show type not recognizing ComputedRef
|
||||
return this.headerDownloadableLinks
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue