Add update notification when version changes

This commit is contained in:
Eli Ribble 2026-04-28 17:09:43 +00:00
parent 52c41e29d8
commit ff668c223b
No known key found for this signature in database
2 changed files with 131 additions and 1 deletions

View file

@ -0,0 +1,115 @@
<style scoped>
.slide-fade-enter-active {
transition: all 0.3s ease-out;
}
.slide-fade-leave-active {
transition: all 0.3s ease-in;
}
.slide-fade-enter-from {
transform: translateX(-50%) translateY(20px);
opacity: 0;
}
.slide-fade-leave-to {
transform: translateX(-50%) translateY(20px);
opacity: 0;
}
</style>
<template>
<Transition name="slide-fade">
<div
v-if="updateDate"
class="alert alert-info alert-dismissible fade show position-fixed bottom-0 start-50 translate-middle-x mb-3 shadow-sm"
style="z-index: 1050; max-width: 400px"
role="alert"
>
<div class="d-flex align-items-center gap-2">
<div class="flex-grow-1">
<div class="fw-semibold">App updates available</div>
<small class="text-muted">{{ timeSinceUpdate }}</small>
</div>
<i
ref="infoIcon"
class="bi bi-info-circle text-primary"
style="cursor: pointer; font-size: 1.2rem"
data-bs-toggle="popover"
data-bs-trigger="hover focus"
data-bs-placement="top"
data-bs-content="Please save your work before refreshing the page."
tabindex="0"
></i>
<button class="btn btn-sm btn-primary" @click="refreshPage">
Refresh
</button>
</div>
</div>
</Transition>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted } from "vue";
import { Popover } from "bootstrap";
interface Props {
updateDate: Date | null;
}
const props = defineProps<Props>();
const infoIcon = ref<HTMLElement | null>(null);
let popoverInstance: Popover | null = null;
let intervalId: number | null = null;
// Force reactivity update every minute
const tick = ref(0);
const timeSinceUpdate = computed(() => {
if (!props.updateDate) return "";
// Access tick to ensure reactivity
tick.value;
const now = new Date();
const diff = now.getTime() - props.updateDate.getTime();
const minutes = Math.floor(diff / 60000);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) return `${days} day${days > 1 ? "s" : ""} ago`;
if (hours > 0) return `${hours} hour${hours > 1 ? "s" : ""} ago`;
if (minutes > 0) return `${minutes} minute${minutes > 1 ? "s" : ""} ago`;
return "just now";
});
const refreshPage = () => {
window.location.reload();
};
// Initialize popover when icon is mounted
watch(infoIcon, (element) => {
if (element && !popoverInstance) {
popoverInstance = new Popover(element);
}
});
onMounted(() => {
// Update time display every minute
intervalId = window.setInterval(() => {
tick.value++;
}, 60000);
});
onUnmounted(() => {
if (popoverInstance) {
popoverInstance.dispose();
}
if (intervalId !== null) {
clearInterval(intervalId);
}
});
</script>

View file

@ -15,21 +15,36 @@
<component :is="Component" />
</router-view>
</MainContent>
<UpdateNotification :updateDate="updateDate" />
</template>
</div>
</template>
<script setup lang="ts">
import { onMounted } from "vue";
import { onMounted, ref } from "vue";
import Sidebar from "@/components/layout/Sidebar.vue";
import MainContent from "@/components/layout/MainContent.vue";
import UpdateNotification from "@/components/UpdateNotification.vue";
import { SSEManager, type SSEMessageStatus } from "@/SSEManager";
import { Session } from "@/type/api";
import { router } from "@/route/config";
import { useRoutes } from "@/route/use";
import { useSessionStore } from "@/store/session";
const revision = ref<string>("");
const session = useSessionStore();
const updateDate = ref<Date | null>(null);
onMounted(() => {
SSEManager.subscribeStatus((msg: SSEMessageStatus) => {
if (msg.status == "connected") {
if (revision.value == "") {
revision.value = msg.revision;
} else {
updateDate.value = new Date();
}
}
console.log("status update:", msg);
});
session
.get()
.then((session: Session) => {