diff --git a/app/src/components/ReloadService.tsx b/app/src/components/ReloadService.tsx
new file mode 100644
index 00000000..d031ff8d
--- /dev/null
+++ b/app/src/components/ReloadService.tsx
@@ -0,0 +1,53 @@
+import { useRegisterSW } from 'virtual:pwa-register/react'
+import {version} from "../conf.ts";
+import {useTranslation} from "react-i18next";
+import {useToast} from "./ui/use-toast.ts";
+import {useEffect} from "react";
+import {ToastAction} from "./ui/toast.tsx";
+
+function ReloadPrompt() {
+ const { t } = useTranslation();
+ const { toast } = useToast();
+
+ const {
+ offlineReady: [offlineReady, setOfflineReady],
+ needRefresh: [needRefresh, setNeedRefresh],
+ updateServiceWorker,
+ } = useRegisterSW({
+ onRegisteredSW() {
+ console.debug(`[service] service worker registered (version ${version})`);
+ },
+ onRegisterError(error) {
+ console.log(`[service] service worker registration failed: ${error.message}`);
+ },
+ });
+
+ useEffect(() => {
+ if (offlineReady) {
+ toast({
+ title: t('service.offline-title'),
+ description: t('service.offline'),
+ })
+ }
+
+ if (needRefresh) {
+ toast({
+ title: t('service.title'),
+ description: t('service.description'),
+ action: (
+ updateServiceWorker(true)}>
+ {t('service.update')}
+
+ ),
+ });
+
+ setOfflineReady(false);
+ setNeedRefresh(false);
+ }
+ }, []);
+
+ return <>>;
+}
+
+export default ReloadPrompt;
+
diff --git a/app/src/conf.ts b/app/src/conf.ts
index cd565090..cd686147 100644
--- a/app/src/conf.ts
+++ b/app/src/conf.ts
@@ -1,6 +1,6 @@
import axios from "axios";
-export const version: string = "3.2.2";
+export const version: string = "3.2.3";
export const deploy: boolean = true;
export let rest_api: string = "http://localhost:8094";
export let ws_api: string = "ws://localhost:8094";
diff --git a/app/src/i18n.ts b/app/src/i18n.ts
index d48675a8..f3706706 100644
--- a/app/src/i18n.ts
+++ b/app/src/i18n.ts
@@ -162,6 +162,13 @@ const resources = {
"copied-description": "API key has been copied to clipboard",
"learn-more": "Learn more",
},
+ service: {
+ "title": "New Version Available",
+ "description": "A new version is available. Do you want to update now?",
+ "update": "Update",
+ "offline-title": "Offline Mode",
+ "offline": "App is currently offline.",
+ }
},
},
cn: {
@@ -310,6 +317,13 @@ const resources = {
"copied-description": "API 密钥已复制到剪贴板",
"learn-more": "了解更多",
},
+ service: {
+ "title": "发现新版本",
+ "description": "发现新版本,是否立即更新?",
+ "update": "更新",
+ "offline-title": "离线模式",
+ "offline": "应用当前处于离线状态。",
+ }
},
},
ru: {
@@ -469,6 +483,13 @@ const resources = {
"copied-description": "Ключ API скопирован в буфер обмена",
"learn-more": "Узнать больше",
},
+ service: {
+ "title": "Доступна новая версия",
+ "description": "Доступна новая версия. Хотите обновить сейчас?",
+ "update": "Обновить",
+ "offline-title": "Режим оффлайн",
+ "offline": "Приложение в настоящее время находится в автономном режиме.",
+ }
},
},
};
diff --git a/app/src/main.tsx b/app/src/main.tsx
index f061c07d..8e8c01ac 100644
--- a/app/src/main.tsx
+++ b/app/src/main.tsx
@@ -5,11 +5,12 @@ import "./conf.ts";
import "./i18n.ts";
import "./assets/main.less";
import "./assets/globals.less";
-import "./service.ts";
import "./conf.ts";
+import ReloadPrompt from "./components/ReloadService.tsx";
ReactDOM.createRoot(document.getElementById("root")!).render(
+
,
);
diff --git a/app/src/service.ts b/app/src/service.ts
deleted file mode 100644
index c97718d3..00000000
--- a/app/src/service.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-// @ts-ignore
-import { registerSW } from "virtual:pwa-register";
-import { version } from "./conf.ts";
-
-export const updateSW = registerSW({
- onRegisteredSW(url: string, registration: ServiceWorkerRegistration) {
- if (!(!registration.installing && navigator)) return;
- if ("connection" in navigator && !navigator.onLine) return;
-
- console.debug(
- "[service] checking for update (current version: %s)",
- version,
- );
- fetch(url, {
- headers: { "Service-Worker": "script", "Cache-Control": "no-cache" },
- cache: "no-store",
- }).then(async (resp) => {
- if (resp?.status === 200) {
- await registration.update();
- if (registration.onupdatefound) console.debug("[service] update found");
- }
- });
- },
-});
diff --git a/app/src/types/service.d.ts b/app/src/types/service.d.ts
new file mode 100644
index 00000000..5ef1ae1a
--- /dev/null
+++ b/app/src/types/service.d.ts
@@ -0,0 +1,15 @@
+declare module 'virtual:pwa-register/react' {
+ // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
+ // @ts-expect-error ignore when React is not installed
+ import type { Dispatch, SetStateAction } from 'react'
+ import type { RegisterSWOptions } from 'vite-plugin-pwa/types'
+
+ export type { RegisterSWOptions }
+
+ export function useRegisterSW(options?: RegisterSWOptions): {
+ needRefresh: [boolean, Dispatch>]
+ offlineReady: [boolean, Dispatch>]
+ updateServiceWorker: (reloadPage?: boolean) => Promise
+ onRegistered: (registration: ServiceWorkerRegistration) => void
+ }
+}
diff --git a/app/tsconfig.json b/app/tsconfig.json
index 2d44c3d1..c6772b12 100644
--- a/app/tsconfig.json
+++ b/app/tsconfig.json
@@ -5,6 +5,10 @@
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
+ "types": [
+ "vite-plugin-pwa/react",
+ "./types/*.d.ts",
+ ],
/* Bundler mode */
"moduleResolution": "bundler",
@@ -25,5 +29,5 @@
"paths": {
"@/*": ["./src/*"]
},
- "references": [{ "path": "./tsconfig.node.json" }]
+ "references": [{ "path": "./tsconfig.node.json" }],
}