feat: added change log prompt for PWA updates (#4098)

Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
Co-authored-by: nivedin <nivedinp@gmail.com>
Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
Anwarul Islam
2024-06-27 22:03:33 +06:00
committed by GitHub
parent 3b70668162
commit b851d3003c
12 changed files with 261 additions and 98 deletions

View File

@@ -98,7 +98,9 @@
"twitter": "Twitter",
"type_a_command_search": "Type a command or search…",
"we_use_cookies": "We use cookies",
"updated_text": "Hoppscotch has been updated to the version {version} 🎉",
"whats_new": "What's new?",
"see_whats_new": "See whats new",
"wiki": "Wiki"
},
"auth": {

View File

@@ -36,7 +36,7 @@
"@hoppscotch/codemirror-lang-graphql": "workspace:^",
"@hoppscotch/data": "workspace:^",
"@hoppscotch/js-sandbox": "workspace:^",
"@hoppscotch/ui": "0.1.0",
"@hoppscotch/ui": "0.2.0",
"@hoppscotch/vue-toasted": "0.1.0",
"@lezer/highlight": "1.2.0",
"@unhead/vue": "1.8.8",

View File

@@ -8,6 +8,7 @@
</div>
<ErrorPage v-if="errorInfo !== null" :error="errorInfo" />
<RouterView v-else />
<Toaster rich-colors />
</div>
</template>
@@ -19,6 +20,7 @@ import { isLoadingInitialRoute } from "@modules/router"
import { useI18n } from "@composables/i18n"
import { APP_IS_IN_DEV_MODE } from "@helpers/dev"
import { platform } from "./platform"
import { Toaster } from "@hoppscotch/ui"
const t = useI18n()

View File

@@ -23,6 +23,7 @@ declare module 'vue' {
AppLogo: typeof import('./components/app/Logo.vue')['default']
AppOptions: typeof import('./components/app/Options.vue')['default']
AppPaneLayout: typeof import('./components/app/PaneLayout.vue')['default']
AppPWAPrompt: typeof import('./components/app/PWAPrompt.vue')['default']
AppShare: typeof import('./components/app/Share.vue')['default']
AppShortcuts: typeof import('./components/app/Shortcuts.vue')['default']
AppShortcutsEntry: typeof import('./components/app/ShortcutsEntry.vue')['default']
@@ -38,6 +39,7 @@ declare module 'vue' {
AppSpotlightEntryRESTTeamRequestEntry: typeof import('./components/app/spotlight/entry/RESTTeamRequestEntry.vue')['default']
AppSpotlightSearch: typeof import('./components/app/SpotlightSearch.vue')['default']
AppSupport: typeof import('./components/app/Support.vue')['default']
AppWhatsNewDialog: typeof import('./components/app/WhatsNewDialog.vue')['default']
Collections: typeof import('./components/collections/index.vue')['default']
CollectionsAdd: typeof import('./components/collections/Add.vue')['default']
CollectionsAddFolder: typeof import('./components/collections/AddFolder.vue')['default']

View File

@@ -0,0 +1,46 @@
<template>
<div
class="flex flex-col py-2 px-4 w-72 relative border border-[#BCB78B] bg-[#FEFFD2] rounded-md text-[#7E7103]"
>
<button
class="absolute top-2 right-2 hover:text-black"
@click="$emit('close-toast')"
>
<IconLucideX />
</button>
<div class="flex flex-col space-y-3">
<p class="leading-5 font-semibold">
{{ t("app.updated_text", { version: version }) }}
</p>
<button
class="flex items-center space-x-1 hover:underline"
@click="openWhatsNew"
>
<span>
{{ t("app.see_whats_new") }}
</span>
<IconLucideArrowUpRight />
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { useI18n } from "~/composables/i18n"
import { platform } from "~/platform"
const t = useI18n()
const props = defineProps<{
notesUrl: string
version: string
}>()
defineEmits<{
(e: "close-toast"): void
}>()
const openWhatsNew = () => {
if (props.notesUrl) platform.io.openExternalLink(props.notesUrl)
}
</script>

View File

@@ -1,6 +1,6 @@
<template>
<div class="flex flex-col">
<label for="requestType" class="px-4 pb-4" v-if="!hideLabel">
<label v-if="!hideLabel" for="requestType" class="px-4 pb-4">
{{ t("request.choose_language") }}
</label>
<tippy

View File

@@ -46,9 +46,9 @@
{{ t("tab.code_snippet") }}
</div>
<HttpCodegen
v-if="selectedNavigationTab === 'codegen'"
:hide-label="true"
class="px-4 mt-4"
v-if="selectedNavigationTab === 'codegen'"
/>
</HoppSmartTab>
</HoppSmartTabs>

View File

@@ -1,7 +1,7 @@
import { watch } from "vue"
import { useToast } from "@composables/toast"
import { useI18n } from "@composables/i18n"
import { useToast } from "@composables/toast"
import { pwaNeedsRefresh, refreshAppForPWAUpdate } from "@modules/pwa"
import { watch } from "vue"
export const usePwaPrompt = function () {
const toast = useToast()
@@ -11,28 +11,33 @@ export const usePwaPrompt = function () {
pwaNeedsRefresh,
(value) => {
if (value) {
toast.show(`${t("app.new_version_found")}`, {
duration: 0,
action: [
{
text: `${t("action.dismiss")}`,
onClick: (_, toastObject) => {
toastObject.goAway(0)
},
},
{
text: `${t("app.reload")}`,
onClick: (_, toastObject) => {
toastObject.goAway(0)
refreshAppForPWAUpdate()
},
},
],
})
showUpdateToast()
}
},
{
immediate: true,
}
)
function showUpdateToast() {
toast.show(`${t("app.new_version_found")}`, {
position: "bottom-left",
duration: 0,
action: [
{
text: `${t("action.dismiss")}`,
onClick: (_, toastObject) => {
toastObject.goAway(0)
},
},
{
text: `${t("app.reload")}`,
onClick: (_, toastObject) => {
toastObject.goAway(0)
refreshAppForPWAUpdate()
},
},
],
})
}
}

View File

@@ -0,0 +1,61 @@
import { toast as sonner } from "@hoppscotch/ui"
import { markRaw } from "vue"
import WhatsNewDialog from "~/components/app/WhatsNewDialog.vue"
import { getService } from "~/modules/dioc"
import { PersistenceService } from "~/services/persistence"
import { version as hoppscotchCommonPkgVersion } from "./../../package.json"
export const useWhatsNewDialog = async function () {
const persistenceService = getService(PersistenceService)
const versionFromLocalStorage = persistenceService.getLocalConfig("hopp_v")
// Set new entry `hopp_v` under `localStorage` if not present
if (!versionFromLocalStorage) {
persistenceService.setLocalConfig("hopp_v", hoppscotchCommonPkgVersion)
return
}
// Already on the latest version
if (versionFromLocalStorage === hoppscotchCommonPkgVersion) {
return
}
const getMajorVersion = (v: string) => v.split(".").slice(0, 2).join(".")
const majorVersionFromLocalStorage = getMajorVersion(versionFromLocalStorage)
const hoppscotchCommonPkgMajorVersion = getMajorVersion(
hoppscotchCommonPkgVersion
)
// Skipping the minor version update. e.g. 2024.1.0 -> 2024.1.1
// Checking major version update. e.g. 2024.1 -> 2024.2
// Show the release notes during a major version update
if (majorVersionFromLocalStorage !== hoppscotchCommonPkgMajorVersion) {
const notesUrl = await getReleaseNotes(hoppscotchCommonPkgMajorVersion)
if (notesUrl) {
sonner.custom(markRaw(WhatsNewDialog), {
componentProps: {
notesUrl,
version: hoppscotchCommonPkgVersion,
},
})
}
}
persistenceService.setLocalConfig("hopp_v", hoppscotchCommonPkgVersion)
}
async function getReleaseNotes(v: string): Promise<string | undefined> {
try {
const { release_notes } = await fetch(
`https://releases.hoppscotch.com/releases/${v}.json`
).then((res) => res.json())
return release_notes
} catch (_) {
return undefined
}
}

View File

@@ -0,0 +1,8 @@
import { useWhatsNewDialog } from "~/composables/whats-new"
import { HoppModule } from "."
export default <HoppModule>{
onRootSetup() {
useWhatsNewDialog()
},
}