aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2020-06-11 23:15:10 +0800
committercrupest <crupest@outlook.com>2020-06-11 23:15:10 +0800
commit64c4376ed388af106c1de5ec8bd1d1743950a27e (patch)
treec1b4d3f4b83f5114febecb6f2e2cc6e982ec97f2
parent93ce8560fa19c3a91de99643fdbbe4f895a47b84 (diff)
downloadtimeline-64c4376ed388af106c1de5ec8bd1d1743950a27e.tar.gz
timeline-64c4376ed388af106c1de5ec8bd1d1743950a27e.tar.bz2
timeline-64c4376ed388af106c1de5ec8bd1d1743950a27e.zip
feat(front): Application upgrade ui.
-rw-r--r--Timeline/ClientApp/package.json1
-rw-r--r--Timeline/ClientApp/src/app/common/AlertHost.tsx29
-rw-r--r--Timeline/ClientApp/src/app/common/alert-service.ts4
-rw-r--r--Timeline/ClientApp/src/app/index.tsx7
-rw-r--r--Timeline/ClientApp/src/app/locales/en/translation.ts6
-rw-r--r--Timeline/ClientApp/src/app/locales/scheme.ts5
-rw-r--r--Timeline/ClientApp/src/app/locales/zh/translation.ts5
-rw-r--r--Timeline/ClientApp/src/app/service-worker.tsx74
-rw-r--r--Timeline/ClientApp/src/sw/sw.ts6
-rw-r--r--Timeline/ClientApp/src/tsconfig.json2
10 files changed, 127 insertions, 12 deletions
diff --git a/Timeline/ClientApp/package.json b/Timeline/ClientApp/package.json
index ac8f189b..a4bac434 100644
--- a/Timeline/ClientApp/package.json
+++ b/Timeline/ClientApp/package.json
@@ -27,6 +27,7 @@
"workbox-precaching": "^5.1.3",
"workbox-routing": "^5.1.3",
"workbox-strategies": "^5.1.3",
+ "workbox-window": "^5.1.3",
"xregexp": "^4.3.0"
},
"scripts": {
diff --git a/Timeline/ClientApp/src/app/common/AlertHost.tsx b/Timeline/ClientApp/src/app/common/AlertHost.tsx
index c815db2b..23b6c5f4 100644
--- a/Timeline/ClientApp/src/app/common/AlertHost.tsx
+++ b/Timeline/ClientApp/src/app/common/AlertHost.tsx
@@ -9,6 +9,7 @@ import {
kAlertHostId,
AlertInfo,
} from './alert-service';
+import { useTranslation } from 'react-i18next';
interface AutoCloseAlertProps {
alert: AlertInfo;
@@ -17,15 +18,35 @@ interface AutoCloseAlertProps {
export const AutoCloseAlert: React.FC<AutoCloseAlertProps> = (props) => {
const { alert } = props;
+ const { dismissTime } = alert;
+
+ const { t } = useTranslation();
React.useEffect(() => {
- const tag = window.setTimeout(props.close, 5000);
- return () => window.clearTimeout(tag);
- }, [props.close]);
+ const tag =
+ dismissTime === 'never'
+ ? null
+ : typeof dismissTime === 'number'
+ ? window.setTimeout(props.close, dismissTime)
+ : window.setTimeout(props.close, 5000);
+ return () => {
+ if (tag != null) {
+ window.clearTimeout(tag);
+ }
+ };
+ }, [dismissTime, props.close]);
return (
<Alert className="m-3" color={alert.type ?? 'primary'} toggle={props.close}>
- {alert.message}
+ {(() => {
+ const { message } = alert;
+ if (typeof message === 'function') {
+ const Message = message;
+ return <Message />;
+ } else if (typeof message === 'object' && message.type === 'i18n') {
+ return t(message.key);
+ } else return alert.message;
+ })()}
</Alert>
);
};
diff --git a/Timeline/ClientApp/src/app/common/alert-service.ts b/Timeline/ClientApp/src/app/common/alert-service.ts
index 6d3f8af8..79eecc82 100644
--- a/Timeline/ClientApp/src/app/common/alert-service.ts
+++ b/Timeline/ClientApp/src/app/common/alert-service.ts
@@ -1,8 +1,10 @@
+import React from 'react';
import pull from 'lodash/pull';
export interface AlertInfo {
type?: 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info';
- message: string;
+ message: string | React.FC<unknown> | { type: 'i18n'; key: string };
+ dismissTime?: number | 'never';
}
export interface AlertInfoEx extends AlertInfo {
diff --git a/Timeline/ClientApp/src/app/index.tsx b/Timeline/ClientApp/src/app/index.tsx
index 73788c3a..ff874c45 100644
--- a/Timeline/ClientApp/src/app/index.tsx
+++ b/Timeline/ClientApp/src/app/index.tsx
@@ -14,9 +14,4 @@ import App from './App';
ReactDOM.render(<App />, document.getElementById('app'));
-if ('serviceWorker' in navigator) {
- // Use the window load event to keep the page load performant
- window.addEventListener('load', () => {
- void navigator.serviceWorker.register('/sw.js');
- });
-}
+import './service-worker';
diff --git a/Timeline/ClientApp/src/app/locales/en/translation.ts b/Timeline/ClientApp/src/app/locales/en/translation.ts
index 00672f67..b9fa42c9 100644
--- a/Timeline/ClientApp/src/app/locales/en/translation.ts
+++ b/Timeline/ClientApp/src/app/locales/en/translation.ts
@@ -3,6 +3,12 @@ import TranslationResource from '../scheme';
const translation: TranslationResource = {
welcome: 'Welcome!',
search: 'Search',
+ serviceWorker: {
+ availableOffline:
+ 'This app will be cached in your computer and you can use it offline.',
+ upgradeTitle: 'App is getting a new version!',
+ upgradeNow: 'Upgrade Now',
+ },
nav: {
settings: 'Settings',
login: 'Login',
diff --git a/Timeline/ClientApp/src/app/locales/scheme.ts b/Timeline/ClientApp/src/app/locales/scheme.ts
index bb3f14df..0a8ce278 100644
--- a/Timeline/ClientApp/src/app/locales/scheme.ts
+++ b/Timeline/ClientApp/src/app/locales/scheme.ts
@@ -3,6 +3,11 @@ export default interface TranslationResource {
search: string;
chooseImage: string;
loadImageError: string;
+ serviceWorker: {
+ availableOffline: string;
+ upgradeTitle: string;
+ upgradeNow: string;
+ };
nav: {
settings: string;
login: string;
diff --git a/Timeline/ClientApp/src/app/locales/zh/translation.ts b/Timeline/ClientApp/src/app/locales/zh/translation.ts
index 66421375..03fb470a 100644
--- a/Timeline/ClientApp/src/app/locales/zh/translation.ts
+++ b/Timeline/ClientApp/src/app/locales/zh/translation.ts
@@ -3,6 +3,11 @@ import TranslationResource from '../scheme';
const translation: TranslationResource = {
welcome: '欢迎!',
search: '搜索',
+ serviceWorker: {
+ availableOffline: '这个 App 将会缓存在本地,你将可以离线使用它。',
+ upgradeTitle: 'App 有新版本!',
+ upgradeNow: '现在升级',
+ },
nav: {
settings: '设置',
login: '登陆',
diff --git a/Timeline/ClientApp/src/app/service-worker.tsx b/Timeline/ClientApp/src/app/service-worker.tsx
new file mode 100644
index 00000000..ca59445e
--- /dev/null
+++ b/Timeline/ClientApp/src/app/service-worker.tsx
@@ -0,0 +1,74 @@
+import React from 'react';
+import { WorkboxLifecycleEvent } from 'workbox-window/utils/WorkboxEvent';
+import { Button } from 'reactstrap';
+import { useTranslation } from 'react-i18next';
+
+import { pushAlert } from './common/alert-service';
+
+if ('serviceWorker' in navigator) {
+ void import('workbox-window').then(({ Workbox, messageSW }) => {
+ const wb = new Workbox('/sw.js');
+ let registration: ServiceWorkerRegistration | undefined;
+
+ const showFirstPrompt = (event: WorkboxLifecycleEvent): void => {
+ if (!event.isUpdate) {
+ pushAlert({
+ message: {
+ type: 'i18n',
+ key: 'serviceWorker.availableOffline',
+ },
+ type: 'success',
+ });
+ }
+ };
+
+ wb.addEventListener('activated', showFirstPrompt);
+ wb.addEventListener('externalactivated', showFirstPrompt);
+
+ const showSkipWaitingPrompt = (): void => {
+ const upgrade = (): void => {
+ // Assuming the user accepted the update, set up a listener
+ // that will reload the page as soon as the previously waiting
+ // service worker has taken control.
+ wb.addEventListener('controlling', () => {
+ window.location.reload();
+ });
+
+ if (registration && registration.waiting) {
+ // Send a message to the waiting service worker,
+ // instructing it to activate.
+ // Note: for this to work, you have to add a message
+ // listener in your service worker. See below.
+ void messageSW(registration.waiting, { type: 'SKIP_WAITING' });
+ }
+ };
+
+ const UpgradeMessage: React.FC = () => {
+ const { t } = useTranslation();
+ return (
+ <>
+ {t('serviceWorker.upgradeTitle')}
+ <Button color="success" size="sm" onClick={upgrade} outline>
+ {t('serviceWorker.upgradeNow')}
+ </Button>
+ </>
+ );
+ };
+
+ pushAlert({
+ message: UpgradeMessage,
+ dismissTime: 'never',
+ type: 'success',
+ });
+ };
+
+ // Add an event listener to detect when the registered
+ // service worker has installed but is waiting to activate.
+ wb.addEventListener('waiting', showSkipWaitingPrompt);
+ wb.addEventListener('externalwaiting', showSkipWaitingPrompt);
+
+ void wb.register().then((reg) => {
+ registration = reg;
+ });
+ });
+}
diff --git a/Timeline/ClientApp/src/sw/sw.ts b/Timeline/ClientApp/src/sw/sw.ts
index 67f5dfd4..e7558015 100644
--- a/Timeline/ClientApp/src/sw/sw.ts
+++ b/Timeline/ClientApp/src/sw/sw.ts
@@ -4,6 +4,12 @@ import { NetworkOnly } from 'workbox-strategies';
declare let self: ServiceWorkerGlobalScope;
+self.addEventListener('message', (event) => {
+ if (event.data && (event.data as { type: string }).type === 'SKIP_WAITING') {
+ void self.skipWaiting();
+ }
+});
+
precacheAndRoute(self.__WB_MANIFEST);
const networkOnly = new NetworkOnly();
diff --git a/Timeline/ClientApp/src/tsconfig.json b/Timeline/ClientApp/src/tsconfig.json
index ec0a3fad..320253fa 100644
--- a/Timeline/ClientApp/src/tsconfig.json
+++ b/Timeline/ClientApp/src/tsconfig.json
@@ -1,6 +1,6 @@
{
"compilerOptions": {
- "target": "es5",
+ "target": "esnext",
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,