Add update notification when version changes
This commit is contained in:
parent
52c41e29d8
commit
ff668c223b
2 changed files with 131 additions and 1 deletions
115
ts/components/UpdateNotification.vue
Normal file
115
ts/components/UpdateNotification.vue
Normal 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>
|
||||
|
|
@ -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) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue