diff --git a/config/config.js b/config/config.js index 337a53b04ed8187d4679bace9b5601f9ed02f01b..c7910ea693cb4c979ee30fcf8872b1b5166472cc 100644 --- a/config/config.js +++ b/config/config.js @@ -23,6 +23,12 @@ const plugins = [ dynamicImport: { loadingComponent: './components/PageLoading/index', }, + pwa: { + workboxPluginMode: 'InjectManifest', + workboxOptions: { + importWorkboxFrom: 'local', + }, + }, ...(!process.env.TEST && os.platform() === 'darwin' ? { dll: { diff --git a/public/icons/icon-128x128.png b/public/icons/icon-128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..48d0e2339a60a637b94319c65e8654289b4f4b6c Binary files /dev/null and b/public/icons/icon-128x128.png differ diff --git a/public/icons/icon-192x192.png b/public/icons/icon-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..938e9b53f6850d97c693b3e3107968fbced5050c Binary files /dev/null and b/public/icons/icon-192x192.png differ diff --git a/src/global.js b/src/global.js new file mode 100644 index 0000000000000000000000000000000000000000..8c215e9296504a7629d8cee7cc4916c4894ec7d4 --- /dev/null +++ b/src/global.js @@ -0,0 +1,39 @@ +import { Modal, message } from 'antd'; +import { formatMessage } from 'umi/locale'; + +// Notify user if offline now +window.addEventListener('sw.offline', () => { + message.warning(formatMessage({ id: 'app.pwa.offline' })); +}); + +// Pop up a prompt on the page asking the user if they want to use the latest version +window.addEventListener('sw.updated', e => { + Modal.confirm({ + title: formatMessage({ id: 'app.pwa.serviceworker.updated' }), + content: formatMessage({ id: 'app.pwa.serviceworker.updated.hint' }), + okText: formatMessage({ id: 'app.pwa.serviceworker.updated.ok' }), + onOk: async () => { + // Check if there is sw whose state is waiting in ServiceWorkerRegistration + // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration + const worker = e.detail && e.detail.waiting; + if (!worker) { + return Promise.resolve(); + } + // Send skip-waiting event to waiting SW with MessageChannel + await new Promise((resolve, reject) => { + const channel = new MessageChannel(); + channel.port1.onmessage = event => { + if (event.data.error) { + reject(event.data.error); + } else { + resolve(event.data); + } + }; + worker.postMessage({ type: 'skip-waiting' }, [channel.port2]); + }); + // Refresh current page to use the updated HTML and other assets after SW has skiped waiting + window.location.reload(true); + return true; + }, + }); +}); diff --git a/src/layouts/BasicLayout.js b/src/layouts/BasicLayout.js index 96b34ac2972e8d748fffded90e6e4691fb8a0f6d..06c403eac73762550f6260734d37c62b0659fdd3 100644 --- a/src/layouts/BasicLayout.js +++ b/src/layouts/BasicLayout.js @@ -180,11 +180,11 @@ class BasicLayout extends React.PureComponent { if (!currRouterData) { return 'Ant Design Pro'; } - const message = formatMessage({ + const pageName = formatMessage({ id: currRouterData.locale || currRouterData.name, defaultMessage: currRouterData.name, }); - return `${message} - Ant Design Pro`; + return `${pageName} - Ant Design Pro`; }; getLayoutStyle = () => { diff --git a/src/locales/en-US.js b/src/locales/en-US.js index 2d61a22d2059a752b3dd8c063c97da3c37cfab7f..fcaffcebaab472855ab6118ce547355bf38b3334 100644 --- a/src/locales/en-US.js +++ b/src/locales/en-US.js @@ -8,6 +8,7 @@ import monitor from './en-US/monitor'; import result from './en-US/result'; import settingDrawer from './en-US/settingDrawer'; import settings from './en-US/settings'; +import pwa from './en-US/pwa'; export default { 'navBar.lang': 'Languages', @@ -28,4 +29,5 @@ export default { ...result, ...settingDrawer, ...settings, + ...pwa, }; diff --git a/src/locales/en-US/pwa.js b/src/locales/en-US/pwa.js new file mode 100644 index 0000000000000000000000000000000000000000..ed8d199ead182b5e0834de03410de2eb19167a88 --- /dev/null +++ b/src/locales/en-US/pwa.js @@ -0,0 +1,6 @@ +export default { + 'app.pwa.offline': 'You are offline now', + 'app.pwa.serviceworker.updated': 'New content is available', + 'app.pwa.serviceworker.updated.hint': 'Please press the "Refresh" button to reload current page', + 'app.pwa.serviceworker.updated.ok': 'Refresh', +}; diff --git a/src/locales/pt-BR.js b/src/locales/pt-BR.js index 6ee728e3331044b3385d5b7e7e07f8c63ef3227b..45df10bbf3a73dc744102e93fcc65da83fde9017 100644 --- a/src/locales/pt-BR.js +++ b/src/locales/pt-BR.js @@ -8,6 +8,7 @@ import monitor from './pt-BR/monitor'; import result from './pt-BR/result'; import settingDrawer from './pt-BR/settingDrawer'; import settings from './pt-BR/settings'; +import pwa from './pt-BR/pwa'; export default { 'navBar.lang': 'Idiomas', @@ -28,4 +29,5 @@ export default { ...result, ...settingDrawer, ...settings, + ...pwa, }; diff --git a/src/locales/pt-BR/pwa.js b/src/locales/pt-BR/pwa.js new file mode 100644 index 0000000000000000000000000000000000000000..05cc79784227229ec18ed0b7598e6a03db6bb7d7 --- /dev/null +++ b/src/locales/pt-BR/pwa.js @@ -0,0 +1,7 @@ +export default { + 'app.pwa.offline': 'Você está offline agora', + 'app.pwa.serviceworker.updated': 'Novo conteúdo está disponível', + 'app.pwa.serviceworker.updated.hint': + 'Por favor, pressione o botão "Atualizar" para recarregar a página atual', + 'app.pwa.serviceworker.updated.ok': 'Atualizar', +}; diff --git a/src/locales/zh-CN.js b/src/locales/zh-CN.js index 0d25463a13ced0c12ad82b1a26b3048345ff3969..4330d60ae6d30bf9a3446b4ad777b9a8077b5a84 100644 --- a/src/locales/zh-CN.js +++ b/src/locales/zh-CN.js @@ -8,6 +8,7 @@ import monitor from './zh-CN/monitor'; import result from './zh-CN/result'; import settingDrawer from './zh-CN/settingDrawer'; import settings from './zh-CN/settings'; +import pwa from './zh-CN/pwa'; export default { 'navBar.lang': '语言', @@ -28,4 +29,5 @@ export default { ...result, ...settingDrawer, ...settings, + ...pwa, }; diff --git a/src/locales/zh-CN/pwa.js b/src/locales/zh-CN/pwa.js new file mode 100644 index 0000000000000000000000000000000000000000..e9504849e7840c963295ac2f732a68136a1e48f0 --- /dev/null +++ b/src/locales/zh-CN/pwa.js @@ -0,0 +1,6 @@ +export default { + 'app.pwa.offline': '当前处于离线状态', + 'app.pwa.serviceworker.updated': '有新内容', + 'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面', + 'app.pwa.serviceworker.updated.ok': '刷新', +}; diff --git a/src/locales/zh-TW.js b/src/locales/zh-TW.js index b78d64e380efd6cb61635b8628b43b6f355c3967..986820ef3af850807cf000d6ed9c1a0e8280a276 100644 --- a/src/locales/zh-TW.js +++ b/src/locales/zh-TW.js @@ -8,6 +8,7 @@ import monitor from './zh-TW/monitor'; import result from './zh-TW/result'; import settingDrawer from './zh-TW/settingDrawer'; import settings from './zh-TW/settings'; +import pwa from './zh-TW/pwa'; export default { 'navBar.lang': '語言', @@ -28,4 +29,5 @@ export default { ...result, ...settingDrawer, ...settings, + ...pwa, }; diff --git a/src/locales/zh-TW/pwa.js b/src/locales/zh-TW/pwa.js new file mode 100644 index 0000000000000000000000000000000000000000..108a6e489be8e7567ac6d41cd7d81d7715020b3b --- /dev/null +++ b/src/locales/zh-TW/pwa.js @@ -0,0 +1,6 @@ +export default { + 'app.pwa.offline': '當前處於離線狀態', + 'app.pwa.serviceworker.updated': '有新內容', + 'app.pwa.serviceworker.updated.hint': '請點擊“刷新”按鈕或者手動刷新頁面', + 'app.pwa.serviceworker.updated.ok': '刷新', +}; diff --git a/src/manifest.json b/src/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..69bce98250123746915fd5f9bf939bb254c3eb3b --- /dev/null +++ b/src/manifest.json @@ -0,0 +1,17 @@ +{ + "name": "ant-design-pro", + "short_name": "antd-pro", + "display": "standalone", + "start_url": "./?utm_source=homescreen", + "theme_color": "#002140", + "background_color": "#001529", + "icons": [ + { + "src": "icons/icon-192x192.png", + "sizes": "192x192" + },{ + "src": "icons/icon-128x128.png", + "sizes": "128x128" + } + ] +} \ No newline at end of file diff --git a/src/models/login.js b/src/models/login.js index fffc5197983624c6ddc13ea79e0983d17ff0bbda..82fc4249a57c96a9f6387cd34c3b152387f2b320 100644 --- a/src/models/login.js +++ b/src/models/login.js @@ -30,7 +30,7 @@ export default { if (redirectUrlParams.origin === urlParams.origin) { redirect = redirect.substr(urlParams.origin.length); if (redirect.match(/^\/.*#/)) { - redirect = redirect.substr(redirect.indexOf('#')+1); + redirect = redirect.substr(redirect.indexOf('#') + 1); } } else { window.location.href = redirect; diff --git a/src/service-worker.js b/src/service-worker.js new file mode 100644 index 0000000000000000000000000000000000000000..48d43c2545b6bd9e92ba6fce550dfbccbc3615f3 --- /dev/null +++ b/src/service-worker.js @@ -0,0 +1,65 @@ +/* globals workbox */ +/* eslint-disable no-restricted-globals */ +workbox.core.setCacheNameDetails({ + prefix: 'antd-pro', + suffix: 'v1', +}); +// Control all opened tabs ASAP +workbox.clientsClaim(); + +/** + * Use precaching list generated by workbox in build process. + * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching + */ +/* eslint-disable no-underscore-dangle */ +workbox.precaching.precacheAndRoute(self.__precacheManifest || []); + +/** + * Register a navigation route. + * https://developers.google.com/web/tools/workbox/modules/workbox-routing#how_to_register_a_navigation_route + */ +workbox.routing.registerNavigationRoute('/index.html'); + +/** + * Use runtime cache: + * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.routing#.registerRoute + * + * Workbox provides all common caching strategies including CacheFirst, NetworkFirst etc. + * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies + */ + +/** + * Handle API requests + */ +workbox.routing.registerRoute(/\/api\//, workbox.strategies.networkFirst()); + +/** + * Handle third party requests + */ +workbox.routing.registerRoute( + /^https:\/\/gw.alipayobjects.com\//, + workbox.strategies.networkFirst() +); +workbox.routing.registerRoute( + /^https:\/\/cdnjs.cloudflare.com\//, + workbox.strategies.networkFirst() +); +workbox.routing.registerRoute(/\/color.less/, workbox.strategies.networkFirst()); + +/** + * Response to client after skipping waiting with MessageChannel + */ +addEventListener('message', event => { + const replyPort = event.ports[0]; + const message = event.data; + if (replyPort && message && message.type === 'skip-waiting') { + event.waitUntil( + self + .skipWaiting() + .then( + () => replyPort.postMessage({ error: null }), + error => replyPort.postMessage({ error }) + ) + ); + } +});