├── .github
└── workflows
│ └── static.yml
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.png
├── google3aaf24fad574bf82.html
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
└── src
├── App.css
├── App.js
├── Game.js
├── components
├── DayButton.js
├── DayButtonGrid.js
├── ExplanationModal.js
└── FooterBar.js
├── functions
├── useDidUpdate.js
└── useHighScoreState.js
├── index.js
├── serviceWorker.js
└── services
└── storage.js
/.github/workflows/static.yml:
--------------------------------------------------------------------------------
1 | name: Deploy static content to Pages
2 |
3 | on:
4 | # Runs on pushes to the gh-pages branch
5 | push:
6 | branches:
7 | - gh-pages
8 | # Allows you to run this workflow manually from the Actions tab
9 | workflow_dispatch:
10 |
11 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
12 | permissions:
13 | contents: read
14 | pages: write
15 | id-token: write
16 |
17 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
18 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
19 | concurrency:
20 | group: "pages"
21 | cancel-in-progress: false
22 |
23 | jobs:
24 | # Single deploy job since we're just deploying
25 | deploy:
26 | environment:
27 | name: github-pages
28 | url: ${{ steps.deployment.outputs.page_url }}
29 | runs-on: ubuntu-latest
30 | steps:
31 | - name: Checkout
32 | uses: actions/checkout@v4
33 | - name: Setup Pages
34 | uses: actions/configure-pages@v4
35 | - name: Upload artifact
36 | uses: actions/upload-pages-artifact@v3
37 | with:
38 | # Upload entire repository
39 | path: '.'
40 | - name: Deploy to GitHub Pages
41 | id: deployment
42 | uses: actions/deploy-pages@v4
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | /.idea
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ##### Learn how to find the day of the week of any date just using your brain. Web app works offline.
2 |
3 | ##### Learn more about the [doomsday algorithm](https://en.wikipedia.org/wiki/Doomsday_rule)
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "math-games",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "grommet": "^2.8.1",
7 | "grommet-icons": "^4.4.0",
8 | "moment": "^2.24.0",
9 | "moment-random": "^1.0.5",
10 | "react": "^16.12.0",
11 | "react-dom": "^16.12.0",
12 | "react-github-btn": "^1.2.1",
13 | "react-scripts": "^5.0.1",
14 | "styled-components": "^5.1.0"
15 | },
16 | "scripts": {
17 | "start": "react-scripts start",
18 | "build": "react-scripts build",
19 | "test": "react-scripts test",
20 | "eject": "react-scripts eject",
21 | "predeploy": "npm run build",
22 | "deploy": "gh-pages -d build"
23 | },
24 | "eslintConfig": {
25 | "extends": "react-app"
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | },
39 | "homepage": "https://grantas33.github.io/Doomsday-algorithm-practice",
40 | "devDependencies": {
41 | "gh-pages": "^2.1.1"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grantas33/Doomsday-algorithm-practice/c042ffa0805dfba93d0b06a724b37e4cf18ce467/public/favicon.png
--------------------------------------------------------------------------------
/public/google3aaf24fad574bf82.html:
--------------------------------------------------------------------------------
1 | google-site-verification: google3aaf24fad574bf82.html
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
18 |
19 |
28 | Learn the doomsday algorithm
29 |
34 |
35 |
36 | You need to enable JavaScript to run this app.
37 |
38 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grantas33/Doomsday-algorithm-practice/c042ffa0805dfba93d0b06a724b37e4cf18ce467/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grantas33/Doomsday-algorithm-practice/c042ffa0805dfba93d0b06a724b37e4cf18ce467/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Learn the doomsday algorithm",
3 | "name": "Learn and practice the doomsday algorithm, which is used to find the day of the week of any date",
4 | "icons": [
5 | {
6 | "src": "logo192.png",
7 | "type": "image/png",
8 | "sizes": "192x192"
9 | },
10 | {
11 | "src": "logo512.png",
12 | "type": "image/png",
13 | "sizes": "512x512"
14 | }
15 | ],
16 | "start_url": ".",
17 | "display": "standalone",
18 | "theme_color": "#000000",
19 | "background_color": "#ffffff",
20 | "orientation": "portrait"
21 | }
22 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .century { color: red }
2 | .centuryIndex { color: dodgerblue }
3 | .year { color: #00ff82 }
4 | .yearIndex { color: hotpink }
5 | .nearestMultiple { color: greenyellow}
6 | .month { color: green }
7 | .day { color: darkorange }
8 | .doomsdayWeekDay { color: #999900 }
9 | .doomsday { color: blueviolet }
10 |
11 | @media (min-width: 768px) {
12 | .modal {
13 | width: fit-content;
14 | margin: auto; /* This centers the modal */
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Grommet} from 'grommet';
3 | import Game from "./Game";
4 | import './App.css';
5 | const theme = {
6 | global: {
7 | font: {
8 | family: 'Solway'
9 | }
10 | },
11 | };
12 |
13 | function App() {
14 |
15 | return (
16 |
17 |
18 |
19 | );
20 | }
21 |
22 | export default App;
23 |
--------------------------------------------------------------------------------
/src/Game.js:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useRef, useState} from 'react';
2 | import {Box, Heading, Text} from 'grommet';
3 | import FooterBar from "./components/FooterBar";
4 | import DayButtonGrid from "./components/DayButtonGrid";
5 | import {useHighScoreState} from "./functions/useHighScoreState";
6 | import GitHubButton from 'react-github-btn'
7 | const moment = require('moment');
8 | const momentRandom = require('moment-random');
9 |
10 | function Game(props) {
11 |
12 | const generateRandomDay = () => momentRandom(
13 | moment(props.endDate),
14 | moment(props.startDate)
15 | );
16 |
17 | const parseDateToWeekDayNumber = (date) => Number(date.format('d'));
18 |
19 | let initialDay = generateRandomDay();
20 | const [currentDay, setCurrentDay] = useState(initialDay.format("Y-MM-DD"));
21 | const [score, setScore] = useState(0);
22 | const [highScore, setHighScore] = useHighScoreState();
23 | const [selectedDayOfWeek, setSelectedDayOfWeek] = useState();
24 | const [expectedDayOfWeek, setExpectedDayOfWeek] = useState(parseDateToWeekDayNumber(initialDay));
25 | const [timeLeft, setTimeLeft] = useState(10);
26 | let timer = useRef();
27 |
28 | useEffect(() => {
29 | if (timeLeft > 0 && selectedDayOfWeek === undefined) {
30 | timer.current = setTimeout(() => {
31 | setTimeLeft(t => t - 1)
32 | }, 1000)
33 | } else {
34 | clearTimeout(timer.current)
35 | }
36 | }, [timeLeft, selectedDayOfWeek]);
37 |
38 | const startNewRound = () => {
39 | let nextDay = generateRandomDay();
40 | setCurrentDay(nextDay.format("Y-MM-DD"));
41 | if (selectedDayOfWeek === expectedDayOfWeek && timeLeft > 0) {
42 | setScore(score => score + 1);
43 | if (score + 1 > highScore) setHighScore(score + 1);
44 | } else {
45 | setScore(0)
46 | }
47 | setSelectedDayOfWeek(undefined);
48 | setExpectedDayOfWeek(parseDateToWeekDayNumber(nextDay));
49 | setTimeLeft(10)
50 | };
51 |
52 | return (
53 |
54 |
55 |
60 | Highscore: {highScore}
61 | Score: {score}
62 |
63 |
70 | 0) ? {type: "pulse", size: "large", duration: 500} : {}} flex={{grow: 1}}>
71 |
72 | {timeLeft}
73 |
74 |
75 |
76 |
77 | {currentDay}
78 |
79 |
80 |
81 |
82 |
83 |
88 | Star
89 |
90 |
91 |
92 | );
93 | }
94 |
95 | export default Game;
96 |
--------------------------------------------------------------------------------
/src/components/DayButton.js:
--------------------------------------------------------------------------------
1 | import {Box, Text} from "grommet";
2 | import React from "react";
3 | import moment from "moment";
4 |
5 | export default function DayButton(props) {
6 |
7 | const anySelected = props.selectedDayOfWeek !== undefined;
8 |
9 | const isSelectedButFalse = anySelected &&
10 | props.selectedDayOfWeek !== props.expectedDayOfWeek &&
11 | props.number === props.selectedDayOfWeek;
12 |
13 | const isCorrect = anySelected &&
14 | props.number === props.expectedDayOfWeek;
15 |
16 | return props.number !== undefined ?
17 | {
26 | if (!anySelected) props.setSelectedDayOfWeek(props.number)
27 | }}
28 | >
29 | {moment.weekdays(props.number)}
30 | :
31 | ;
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/DayButtonGrid.js:
--------------------------------------------------------------------------------
1 | import {Box} from "grommet";
2 | import DayButton from "./DayButton";
3 | import React from "react";
4 |
5 | export default function DayButtonGrid(props) {
6 |
7 | let selected = props.selectedDayOfWeek;
8 | let setSelected = props.setSelectedDayOfWeek;
9 | let expected = props.expectedDayOfWeek;
10 |
11 | return
18 |
24 |
25 |
26 |
27 |
28 |
34 |
35 |
36 |
37 |
38 |
44 |
45 |
46 |
47 |
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/components/ExplanationModal.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { Box, Heading, Text, Tip } from "grommet";
3 | import * as moment from "moment";
4 |
5 | const centuryIndexMap = { 18: 5, 19: 3, 20: 2, 21: 0 };
6 | const doomsdaysForMonth = { 1: "03", 2: "14", 3: "07", 4: "04", 5: "09", 6: "06", 7: "11", 8: "08", 9: "05", 10: "10", 11: "07", 12: "12" };
7 | const doomsdaysForMonthInLeapYear = { ...doomsdaysForMonth, 1: "04", 2: "15" };
8 |
9 | export default function ExplanationModal(props) {
10 |
11 | let day = moment(props.currentDay);
12 |
13 | let centuryIndex = centuryIndexMap[day.format("Y").substring(0, 2)];
14 | let year = Number(day.format("YY"));
15 | let yearAfter1Step = year % 2 === 1 ? (year + 11) : year;
16 | let yearAfter2Step = yearAfter1Step / 2;
17 | let yearAfter3Step = yearAfter2Step % 2 === 1 ? yearAfter2Step + 11 : yearAfter2Step;
18 | let nearestMultiple = yearAfter3Step % 7 === 0 ? yearAfter3Step : yearAfter3Step + 7 - (yearAfter3Step % 7);
19 | let weekDay = (nearestMultiple - yearAfter3Step + centuryIndex) % 7;
20 | let doomsday = day.isLeapYear() ?
21 | `${day.format("MM-")}${doomsdaysForMonthInLeapYear[day.format("M")]}` :
22 | `${day.format("MM-")}${doomsdaysForMonth[day.format("M")]}`;
23 |
24 | let dayNumber = Number(day.format("D"));
25 | let doomsdayNumber = Number(doomsday.substring(3));
26 | let doomsdayToDayChain = [];
27 |
28 | while (Math.abs(dayNumber - doomsdayNumber) > 6) {
29 | doomsdayToDayChain.push(`${day.format("MM-")}${doomsdayNumber < 10 ? `0${doomsdayNumber}` : doomsdayNumber}`);
30 | if (doomsdayNumber < dayNumber) doomsdayNumber += 7;
31 | else doomsdayNumber -= 7;
32 | }
33 | if (doomsdayToDayChain.length > 0) {
34 | doomsdayToDayChain.push(`${day.format("MM-")}${doomsdayNumber < 10 ? `0${doomsdayNumber}` : doomsdayNumber}`);
35 | }
36 |
37 | return
38 |
39 | {day.format("Y").substring(0, 2)}
40 | {day.format("YY")} -
41 | {day.format("MM")} -
42 | {day.format("DD")}
43 |
44 |
45 |
48 | 1800s: 5
49 | 1900s: 3
50 | 2000s: 2
51 | 2100s: 0
52 |
53 | }
54 | dropProps={{ align: { top: 'bottom' } }}
55 | >
56 | Century index
57 | for the {day.format("Y").substring(0, 2)} 00s is {centuryIndex} .
58 |
59 |
60 | Calculating the year index for year {day.format("YY")} using
63 | 1. If odd, add 11
64 | 2. Divide by 2
65 | 3. If odd, add 11
66 |
67 | }
68 | dropProps={{ align: { top: 'bottom' } }}
69 | >"odd + 11"
70 | method:
71 |
72 |
73 | {year !== yearAfter1Step &&
74 | {year} is odd, adding 11: {year} + 11 = {yearAfter1Step};
75 | }
76 | {yearAfter1Step} is even, dividing by 2: {yearAfter1Step} / 2 = {yearAfter2Step === yearAfter3Step ? <>{yearAfter2Step} .> : `${yearAfter2Step};`}
77 | {yearAfter2Step !== yearAfter3Step &&
78 | {yearAfter2Step} is odd, adding 11: {yearAfter2Step} + 11 = {yearAfter3Step} .
79 | }
80 |
81 | The nearest higher multiple of 7 to {yearAfter3Step} is {nearestMultiple} .
82 | Calculating the weekday of the doomsday: ({nearestMultiple} - {yearAfter3Step} + {centuryIndex} ) mod 7 = {weekDay} ({moment.weekdays(weekDay)}) .
83 |
84 |
87 |
88 |
89 |
90 | Month
91 | Doomsday
92 |
93 |
94 |
95 | {Object.entries(doomsdaysForMonth).map(([month, doomsday]) => (
96 |
97 | {month}
98 | {doomsday} {month === '1' && '(04 on leap years)'} {month === '2' && '(15 on leap years)'}
99 |
100 | ))}
101 |
102 |
103 |
104 | }
105 | dropProps={{ align: { bottom: 'top' } }}
106 | >
107 | Doomsday
108 | for {day.format("MMMM")} is {doomsday.substring(0, 3)} {doomsday.substring(3)} {day.isLeapYear() && (day.format("M") < 3) && (leap year) }.
109 |
110 | {doomsdayToDayChain.length > 0 && <>
111 | Selecting a doomsday closer to our date:
112 |
113 |
114 | {doomsdayToDayChain.map((date, index) => {
115 | if (index === doomsdayToDayChain.length - 1) return {date.substring(0, 3)} {date.substring(3)} . ;
116 | return `${date} -> `
117 | })}
118 |
119 |
120 | >
121 | }
122 | {doomsdayNumber === dayNumber ?
123 | Our date matches the doomsday, and it is {day.format("dddd")} . :
124 | Calculating the day of the week: ({weekDay} {dayNumber > doomsdayNumber ?
125 | <> + ({dayNumber} - {doomsdayNumber} )> :
126 | <> - ({doomsdayNumber} - {dayNumber} )>
127 | }) mod 7 = {day.format("d")} ({day.format("dddd")}) .
128 | }
129 |
130 | }
--------------------------------------------------------------------------------
/src/components/FooterBar.js:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react';
2 | import {Box, Button, Layer} from "grommet";
3 | import useDidUpdate from "../functions/useDidUpdate";
4 | import ExplanationModal from "./ExplanationModal";
5 |
6 | export default function FooterBar(props) {
7 |
8 | const [fullyHidden, setFullyHidden] = useState(true);
9 | const [showExplanation, setShowExplanation] = React.useState(false);
10 |
11 | useDidUpdate(() => {
12 | setFullyHidden(false)
13 | }, [props.isVisible]);
14 |
15 | return
22 | setShowExplanation(true) : () => {}}
25 | style={{visibility: (fullyHidden ? "hidden" : "visible")}}
26 | />
27 | {showExplanation && (
28 | setShowExplanation(false)}
30 | onClickOutside={() => setShowExplanation(false)}
31 | margin={"medium"}
32 | className={"modal"}
33 | responsive={false}
34 | full="horizontal"
35 | >
36 |
37 |
38 | )}
39 | {}}
42 | style={{visibility: (fullyHidden ? "hidden" : "visible")}}
43 | />
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/functions/useDidUpdate.js:
--------------------------------------------------------------------------------
1 | import {useEffect, useRef} from 'react';
2 |
3 | export default function useDidUpdate (callback, deps) {
4 | const hasMount = useRef(false);
5 |
6 | useEffect(() => {
7 | if (hasMount.current) {
8 | callback()
9 | } else {
10 | hasMount.current = true
11 | }
12 | }, deps)
13 | }
14 |
--------------------------------------------------------------------------------
/src/functions/useHighScoreState.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import {storage} from "../services/storage";
3 |
4 | export function useHighScoreState() {
5 | const [highScore, setHighScore] = useState(storage.getHighScore());
6 |
7 | useEffect(() => {
8 | storage.setHighScore(highScore)
9 | }, [highScore]);
10 |
11 | return [highScore, setHighScore];
12 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import * as serviceWorker from './serviceWorker';
5 |
6 | ReactDOM.render( , document.getElementById('root'));
7 |
8 | // If you want your app to work offline and load faster, you can change
9 | // unregister() to register() below. Note this comes with some pitfalls.
10 | // Learn more about service workers: https://bit.ly/CRA-PWA
11 | serviceWorker.register();
12 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/services/storage.js:
--------------------------------------------------------------------------------
1 | const getHighScore = () => localStorage.getItem('highScore') || 0;
2 | const setHighScore = (score) => {
3 | localStorage.setItem('highScore', score)
4 | };
5 |
6 | export const storage = {
7 | getHighScore,
8 | setHighScore
9 | };
--------------------------------------------------------------------------------