Commit 2f0beef8 authored by Andrew Hrdy's avatar Andrew Hrdy

Added icons for full PWA support. Fixed handler for service worker cache updates.

parent 6eb4c2f0
Pipeline #5490 passed with stages
in 1 minute and 50 seconds
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"webcredentials": {
"apps": [
"K5MMVK2UFR.edu.gmu.srct.whatsopen"
]
},
"applinks": {
"apps": [],
"details": [
{
"appID": "K5MMVK2UFR.edu.gmu.srct.whatsopen",
"paths": [
"/",
"/facility/*"
]
}
]
}
}
\ No newline at end of file
......@@ -28,8 +28,17 @@
<meta name="twitter:title" content="What's Open">
<meta name="twitter:description" content="Facility hours for George Mason University">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-title" content="What's Open">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="apple-touch-icon" sizes="120x120" href="./assets/icons/whats-open-120x120.png">
<link rel="apple-touch-icon" sizes="152x152" href="./assets/icons/whats-open-152x152.png">
<link rel="apple-touch-icon" sizes="167x167" href="./assets/icons/whats-open-167x167.png">
<link rel="apple-touch-icon" sizes="180x180" href="./assets/icons/whats-open-180x180.png">
<link rel="manifest" href="./manifest.json">
<link rel="shortcut icon" href="./favicon.png">
<link rel="shortcut icon" href="./assets/icons/whats-open-512x512.png">
</head>
<body>
......
{
"short_name": "What's Open",
"name": "What's Open",
"icons": [
{
"src": "favicon.png",
"sizes": "192x192",
"type": "image/png"
}
],
"related_applications": [
{
"platform": "play",
"id": "srct.whatsopen"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
"short_name": "What's Open",
"name": "What's Open",
"icons": [
{
"src": "assets/icons/whats-open-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "assets/icons/whats-open-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "assets/icons/whats-open-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "assets/icons/whats-open-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "assets/icons/whats-open-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "assets/icons/whats-open-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "assets/icons/whats-open-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "assets/icons/whats-open-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "/",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
\ No newline at end of file
......@@ -13,7 +13,7 @@ export default () => {
className={classNames('app-bar', isSearchExpanded && 'app-bar-search-expanded')}>
<Toolbar className={'app-bar-tool-bar'}>
<div className={'app-bar-logo-name'}>
<img src={'favicon.png'} className={'app-bar-logo'}/>
<img src={'assets/icons/whats-open-512x512.png'} className={'app-bar-logo'}/>
<Typography variant="h6" className={classNames('app-bar-title', 'app-bar-text-color')}>
What's Open
</Typography>
......
......@@ -7,16 +7,15 @@ import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
import './styles/whatsOpen.scss';
import 'typeface-roboto';
import '../public/manifest.json';
import '../public/favicon.png';
import '../public/apple-app-site-association';
import { generateStore } from './store';
import { themeTypography, themeOverrides } from './theme';
import { isMobile } from 'react-device-detect';
import MobileLayout from './containers/MobileLayout';
import { useMediaQuery } from '@material-ui/core';
import { fetchFacilitiesAction, setFacilitiesAction } from './store/facility/facility.actions';
import { fetchFacilitiesAction, sortAndSetFacilitiesAction } from './store/facility/facility.actions';
import { fetchAlertsAction } from './store/alert/alert.actions';
import { IFacility } from './models/facility.model';
import { Workbox } from 'workbox-window';
if (isMobile) {
require('./styles/whatsOpenMobile.scss');
......@@ -42,19 +41,51 @@ const App = () => {
const store = generateStore();
// Event listener must be before the fetch calls
navigator.serviceWorker.addEventListener('message', async(event: any) => {
if (event.meta === 'workbox-broadcast-update') {
const { cacheName, updatedUrl } = event.data.payload;
/*
Must be before the fetch calls because the event listener
for the workbox-broadcast-update must be registered before
fetching.
*/
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
wb.addEventListener('activated', event => {
if (event.isUpdate) {
window.location.reload();
}
});
wb.addEventListener('message', async(event) => {
if (event.data.meta === 'workbox-broadcast-update') {
const { cacheName } = event.data.payload;
const cache = await caches.open(cacheName);
const keys = await cache.keys();
/*
This cache should only ever have one key.
If it does not, reset the cache so it is
correct in the future (might be a versioning
issue).
*/
if (keys.length !== 1) {
for (let i = 0; i < keys.length; i++) {
cache.delete(keys[i]);
}
return;
}
const cache = await caches.open(cacheName);
const resp = await cache.match(updatedUrl);
const resp = await cache.match(keys[0]);
const updatedFacilities: IFacility[] = await resp.json();
const updatedFacilities: IFacility[] = await resp.json();
store.dispatch(setFacilitiesAction(updatedFacilities));
}
});
store.dispatch(sortAndSetFacilitiesAction(updatedFacilities));
}
});
wb.register();
}
// Fetch facilities and alerts before app loads.
store.dispatch(fetchFacilitiesAction());
......@@ -63,11 +94,4 @@ store.dispatch(fetchAlertsAction());
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root'));
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
// This is the path on the server, not path in src.
navigator.serviceWorker.register('/sw.js');
});
}
</Provider>, document.getElementById('root'));
\ No newline at end of file
......@@ -3,7 +3,8 @@ import { IFacility } from '../../models/facility.model';
export enum FacilityActionTypes {
FETCH_FACILITIES = 'FETCH_FACILITIES',
SET_FACILITIES = 'SET_FACILITIES'
SET_FACILITIES = 'SET_FACILITIES',
SORT_AND_SET_FACILITIES = 'SORT_AND_SET_FACILITIES'
}
export interface FetchFacilitiesAction extends Action<FacilityActionTypes> {
......@@ -15,6 +16,12 @@ export interface SetFacilitiesAction extends Action<FacilityActionTypes> {
facilities: IFacility[];
}
export interface SortAndSetFacilitiesAction extends Action<FacilityActionTypes> {
type: FacilityActionTypes.SORT_AND_SET_FACILITIES;
facilities: IFacility[];
}
export type FacilityAction =
FetchFacilitiesAction |
SetFacilitiesAction;
\ No newline at end of file
SetFacilitiesAction |
SortAndSetFacilitiesAction;
\ No newline at end of file
import { FetchFacilitiesAction, FacilityActionTypes, SetFacilitiesAction } from './facility.action-types';
import { FetchFacilitiesAction, FacilityActionTypes, SetFacilitiesAction, SortAndSetFacilitiesAction } from './facility.action-types';
import { IFacility } from '../../models/facility.model';
export const fetchFacilitiesAction = (): FetchFacilitiesAction => ({
......@@ -8,4 +8,9 @@ export const fetchFacilitiesAction = (): FetchFacilitiesAction => ({
export const setFacilitiesAction = (facilities: IFacility[]): SetFacilitiesAction => ({
type: FacilityActionTypes.SET_FACILITIES,
facilities: facilities
});
export const sortAndSetFacilitiesAction = (facilities: IFacility[]): SortAndSetFacilitiesAction => ({
type: FacilityActionTypes.SORT_AND_SET_FACILITIES,
facilities: facilities
});
\ No newline at end of file
import { call, put, takeEvery, fork, all, select } from 'redux-saga/effects';
import { callApi } from '../../utils/api.util';
import { setFacilitiesAction } from './facility.actions';
import { FacilityActionTypes } from './facility.action-types';
import { setFacilitiesAction, sortAndSetFacilitiesAction } from './facility.actions';
import { FacilityActionTypes, SortAndSetFacilitiesAction } from './facility.action-types';
import { IFacility } from '../../models/facility.model';
import { isFacilityOpen } from '../../utils/facility.util';
import { ApplicationState } from '..';
......@@ -35,18 +35,27 @@ const facilitySort = (favorites: string[]) => (a: IFacility, b: IFacility) => {
function* handleFetchFacilities() {
try {
const res: IFacility[] = yield call(callApi, 'GET', API_GET_FACILITIES);
const favorites: string[] = yield select((state: ApplicationState) => state.ui.favorites);
const facilities = res.sort(facilitySort(favorites));
yield put(setFacilitiesAction(facilities));
yield put(sortAndSetFacilitiesAction(res));
} catch (err) {}
}
function* handleSortAndSetFacilities(sortAndSetFacilities: SortAndSetFacilitiesAction) {
const favorites: string[] = yield select((state: ApplicationState) => state.ui.favorites);
const facilities = sortAndSetFacilities.facilities.sort(facilitySort(favorites));
yield put(setFacilitiesAction(facilities));
}
function* watchFetchFacilities() {
yield takeEvery(FacilityActionTypes.FETCH_FACILITIES, handleFetchFacilities);
}
function* watchSortAndSetFacilities() {
yield takeEvery(FacilityActionTypes.SORT_AND_SET_FACILITIES, handleSortAndSetFacilities);
}
export default function* facilitySaga() {
yield all([fork(watchFetchFacilities)]);
yield all([fork(watchFetchFacilities), fork(watchSortAndSetFacilities)]);
}
\ No newline at end of file
......@@ -7,7 +7,6 @@ export interface SearchBarState {
campusRegion: CampusRegion;
}
export interface UiState {
search: SearchBarState;
favorites: string[];
......
......@@ -44,6 +44,9 @@
border-radius: 100%;
height: 10px;
width: 10px;
display: flex;
justify-content: center;
align-items: center;
}
.alert-container-number-text {
......
......@@ -58,41 +58,30 @@ const scssExtractLoader = {
]
};
const fileLoader = {
exclude: [
/\.html$/,
/\.tsx?$/,
/\.jsx?$/,
/\.css$/,
/\.scss$/,
/\.json$/,
/\.bmp$/,
/\.gif$/,
/\.jpe?g$/,
/\.png$/
],
loader: require.resolve('file-loader'),
const fontLoader = {
test: /\.woff2?$/,
loader: 'file-loader',
options: {
name: 'static/media/[name].[hash:8].[ext]'
name: 'assets/font/[name].[hash:8].[ext]'
}
};
}
const faviconManifestLoader = {
const manifestLoader = {
type: 'javascript/auto',
test: /(favicon.png|manifest.json)$/,
test: /manifest.json$/,
loader: 'file-loader',
options: {
name: '[name].[ext]'
}
};
const appleAppSiteAssociationLoader = {
test: /apple-app-site-association$/,
const iconsLoader = {
test: /whats-open-.*.*\.png$/,
loader: 'file-loader',
options: {
name: '.well-known/[name]'
name: 'assets/[name].[ext]'
}
};
}
module.exports = {
tsLoader: tsLoader,
......@@ -101,7 +90,7 @@ module.exports = {
scssLoader: scssLoader,
cssExtractLoader: cssExtractLoader,
scssExtractLoader: scssExtractLoader,
fileLoader: fileLoader,
faviconManifestLoader: faviconManifestLoader,
appleAppSiteAssociationLoader: appleAppSiteAssociationLoader
fontLoader: fontLoader,
manifestLoader: manifestLoader,
iconsLoader: iconsLoader,
};
\ No newline at end of file
......@@ -11,6 +11,6 @@ module.exports = {
appBuild: resolveApp('build'),
appSrc: resolveApp('src'),
appSw: resolveApp('src/sw.ts'),
appOutputJs: 'static/js/bundle.js',
appOutputJs: 'bundle.js',
publicPath: '/'
};
\ No newline at end of file
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const paths = require('./paths');
const loaders = require('./loaders');
const WorkboxPlugin = require('workbox-webpack-plugin');
......@@ -23,8 +24,9 @@ module.exports = {
loaders.tslintLoader,
loaders.cssLoader,
loaders.scssLoader,
loaders.fileLoader,
loaders.faviconManifestLoader
loaders.fontLoader,
loaders.manifestLoader,
loaders.iconsLoader
]
},
plugins: [
......@@ -37,6 +39,9 @@ module.exports = {
swDest: 'sw.js',
maximumFileSizeToCacheInBytes: 1024 * 1024 * 10 // Dev builds are not minified, allow large files (10MB) for the bundle
}),
new CopyWebpackPlugin([
{ from: 'assets', to: 'assets' }
]),
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
],
......
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const WorkboxPlugin = require('workbox-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const paths = require('./paths');
const loaders = require('./loaders');
const WorkboxPlugin = require('workbox-webpack-plugin');
module.exports = {
mode: 'production',
......@@ -27,9 +28,9 @@ module.exports = {
loaders.tslintLoader,
loaders.cssExtractLoader,
loaders.scssExtractLoader,
loaders.fileLoader,
loaders.faviconManifestLoader,
loaders.appleAppSiteAssociationLoader
loaders.fontLoader,
loaders.manifestLoader,
loaders.iconsLoader
]
},
optimization: {
......@@ -62,6 +63,9 @@ module.exports = {
swSrc: paths.appSw,
swDest: 'sw.js'
}),
new CopyWebpackPlugin([
{ from: 'assets', to: 'assets' }
]),
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment