Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { storeToRefs } from 'pinia'
import { onMounted, onUnmounted, watch } from 'vue'
import { RouterView } from 'vue-router'
import AppNotifications from '@/components/ui/notification/AppNotifications.vue'
import UpdateNotification from '@/components/UpdateNotification.vue'
import { useAppUpdater } from '@/composables/useAppUpdater'
import { useAccountStore } from '@/store/accountStore'
import { useAppStore } from '@/store/appStore'
Expand Down Expand Up @@ -41,4 +42,5 @@ onUnmounted(() => {
<template>
<RouterView />
<AppNotifications />
<UpdateNotification />
</template>
8 changes: 8 additions & 0 deletions src/assets/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--success: 142.1 70.6% 35.5%;
--success-foreground: 0 0% 100%;
--success-muted: 138 76% 95%;
--success-border: 140 58% 85%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 220 13% 60%;
Expand Down Expand Up @@ -43,6 +47,10 @@
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--success: 142.1 70% 45%;
--success-foreground: 0 0% 100%;
--success-muted: 142 35% 16%;
--success-border: 142 30% 25%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 68%;
Expand Down
168 changes: 168 additions & 0 deletions src/components/UpdateNotification.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { Button } from '@/components/ui/button'
import { useAppUpdater } from '@/composables/useAppUpdater'

const { t } = useI18n()

const {
updateAvailable,
updateInfo,
isDownloading,
downloadProgress,
isInstalling,
isRestarting,
downloadAndInstall,
skipUpdate,
dismissUpdate,
} = useAppUpdater()

const installButtonLabel = computed(() => {
if (isRestarting.value)
return t('updater.restarting')
if (isDownloading.value && downloadProgress.value !== null)
return t('updater.downloadingPercent', { percent: downloadProgress.value })
if (isDownloading.value)
return t('updater.downloading')
if (isInstalling.value)
return t('updater.installing')
return t('updater.updateNow')
})

const progressLabel = computed(() => {
if (isRestarting.value)
return t('updater.restarting')
if (isInstalling.value)
return t('updater.installing')
return t('updater.downloading')
})

const isProgressActive = computed(() =>
isDownloading.value || isInstalling.value || isRestarting.value,
)

const buttonsDisabled = computed(() =>
isDownloading.value || isInstalling.value || isRestarting.value,
)
</script>

<template>
<div v-if="updateAvailable" class="bottom-6 right-6 fixed z-50">
<div
class="border rounded-2xl bg-background w-[420px] shadow-xl overflow-hidden"
>
<!-- Header -->
<div class="p-5 pb-4 flex gap-3 items-start">
<div class="border border-success-border rounded-xl bg-success-muted inline-flex shrink-0 h-[42px] w-[42px] items-center justify-center">
<svg
class="text-success h-5 w-5"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 4v13m0 0l-5-5m5 5l5-5M5 21h14"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</div>

<div class="flex-1 min-w-0">
<div class="text-[16px] text-foreground font-bold mb-0.5">
{{ t('updater.updateAvailable') }}
</div>
<div class="text-sm text-muted-foreground">
{{ t('updater.newVersion', { version: updateInfo?.version }) }}
</div>
</div>

<button
class="text-muted-foreground rounded-lg border-none bg-transparent inline-flex shrink-0 h-7 w-7 transition-colors items-center justify-center hover:text-foreground hover:bg-accent disabled:opacity-50 disabled:cursor-not-allowed"
:disabled="buttonsDisabled"
@click="dismissUpdate"
>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 3L3 9M3 3l6 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
</button>
</div>

<!-- Progress bar -->
<div v-if="isProgressActive" class="px-5 pb-3 space-y-1.5">
<div class="flex items-center justify-between">
<span class="text-xs text-muted-foreground font-medium">{{ progressLabel }}</span>
<span
v-if="isDownloading && downloadProgress !== null"
class="text-xs text-success font-semibold"
>
{{ downloadProgress }}%
</span>
</div>
<div class="rounded-full bg-secondary h-[5px] w-full overflow-hidden">
<div
class="rounded-full bg-success h-full transition-all duration-300"
:class="{
'w-[40%] animate-progress-slide': isInstalling || isRestarting || (isDownloading && downloadProgress === null),
}"
:style="isDownloading && downloadProgress !== null ? { width: `${downloadProgress}%` } : {}"
/>
</div>
</div>

<!-- Footer -->
<div class="px-4 py-3 border-t flex items-center justify-between">
<button
class="text-sm text-muted-foreground font-medium border-none bg-transparent transition-colors hover:text-foreground disabled:opacity-50 disabled:cursor-not-allowed"
:disabled="buttonsDisabled"
@click="skipUpdate"
>
{{ t('updater.skip') }}
</button>

<div class="flex gap-2">
<Button
variant="outline"
size="sm"
class="font-semibold min-w-[96px]"
:disabled="buttonsDisabled"
@click="dismissUpdate"
>
{{ t('updater.later') }}
</Button>
<Button
variant="default"
size="sm"
class="text-white font-semibold min-w-[96px]"
:class="{
'opacity-50 cursor-not-allowed': buttonsDisabled,
}"
:disabled="buttonsDisabled"
@click="downloadAndInstall"
>
{{ installButtonLabel }}
</Button>
</div>
</div>
</div>
</div>
</template>

<style scoped>
/* Full tailwind-like animation for indeterminate progress bar */
@keyframes progress-slide {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(350%);
}
}

.animate-progress-slide {
animation: progress-slide 1.4s ease-in-out infinite;
}
</style>
Loading
Loading