main
vue 107 lines 3.13 KB
Raw
1 <template>
2 <n-alert v-if="showBanner" title="New version available!" type="info" closable @close="dismissBanner">
3 <template #icon>
4 <Icon :name="UpdateIcon" :size="18" />
5 </template>
6 <template #header>New version available!</template>
7 <div v-if="versionInfo" class="flex flex-col">
8 <div class="flex items-center justify-between gap-4">
9 <div class="text-sm">
10 You're running v{{ versionInfo.current_version }}, v{{ versionInfo.latest_version }} is now
11 available.
12 <span v-if="versionInfo.published_at" class="opacity-75">
13 (Released {{ formatDate(versionInfo.published_at, dFormats.datetime) }})
14 </span>
15 </div>
16 <div class="flex gap-2">
17 <n-button v-if="showReleaseNotes" size="small" secondary @click="toggleNotes">
18 {{ expandedNotes ? "Hide" : "Show" }} Release Notes
19 </n-button>
20 <n-button
21 v-if="versionInfo.release_url"
22 tag="a"
23 :href="versionInfo.release_url"
24 target="_blank"
25 size="small"
26 type="primary"
27 >
28 View on GitHub
29 </n-button>
30 </div>
31 </div>
32 <n-collapse-transition :show="expandedNotes">
33 <n-card v-if="versionInfo.release_notes" class="mt-4" content-class="pr-2!">
34 <n-scrollbar class="max-h-50 pr-4">
35 <Markdown :source="versionInfo.release_notes" breaks />
36 </n-scrollbar>
37 </n-card>
38 </n-collapse-transition>
39 </div>
40 </n-alert>
41 </template>
42
43 <script setup lang="ts">
44 import type { VersionCheckResponse } from "@/types/version.d"
45 import { useStorage } from "@vueuse/core"
46 import { NAlert, NButton, NCard, NCollapseTransition, NScrollbar } from "naive-ui"
47 import { computed, onBeforeMount, ref } from "vue"
48 import Api from "@/api"
49 import Icon from "@/components/common/Icon.vue"
50 import Markdown from "@/components/common/Markdown.vue"
51 import { useSettingsStore } from "@/stores/settings"
52 import { formatDate } from "@/utils/format"
53
54 const UpdateIcon = "carbon:update-now"
55 const versionInfo = ref<VersionCheckResponse | null>(null)
56 const showBanner = ref(false)
57 const expandedNotes = ref(false)
58
59 const dFormats = useSettingsStore().dateFormat
60 const DISMISS_KEY = "version_banner_dismissed"
61 const DISMISS_DURATION = 24 * 60 * 60 * 1000 // 24 hours
62
63 const dismissedAt = useStorage<number | null>(DISMISS_KEY, null)
64
65 const showReleaseNotes = computed(() => {
66 return versionInfo.value?.release_notes && versionInfo.value.release_notes.trim().length > 0
67 })
68
69 function toggleNotes() {
70 expandedNotes.value = !expandedNotes.value
71 }
72
73 function checkIfDismissed() {
74 if (dismissedAt.value) {
75 // Show again after 24 hours
76 return Date.now() - dismissedAt.value < DISMISS_DURATION
77 }
78 return false
79 }
80
81 function dismissBanner() {
82 dismissedAt.value = Date.now()
83 showBanner.value = false
84 }
85
86 async function checkVersion() {
87 // Don't check if recently dismissed
88 if (checkIfDismissed()) {
89 return
90 }
91
92 try {
93 const response = await Api.version.checkVersion()
94 if (response.data.success) {
95 versionInfo.value = response.data
96 showBanner.value = response.data.is_outdated
97 }
98 } catch (err) {
99 // Silently fail - version check is not critical
100 console.warn("Failed to check version:", err)
101 }
102 }
103
104 onBeforeMount(() => {
105 checkVersion()
106 })
107 </script>