├── .dockerignore
├── .github
└── workflows
│ └── client.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── docker-compose.dev.yml
├── docker-compose.production.yml
├── docker-compose.yml
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
├── robots.txt
└── variables.js
├── scripts
└── build.sh
├── src
├── App.css
├── App.js
├── index.css
├── index.js
├── scenes
│ └── Home
│ │ ├── Block
│ │ ├── index.js
│ │ └── index.module.css
│ │ ├── NumberDisplay
│ │ ├── index.js
│ │ └── index.module.css
│ │ ├── index.js
│ │ └── index.module.css
├── service
│ └── date.js
└── serviceWorker.js
├── variables.sh
└── yarn.lock
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | docker-compose.*
3 | .gitignore
4 | README.md
5 | Dockerfile
--------------------------------------------------------------------------------
/.github/workflows/client.yml:
--------------------------------------------------------------------------------
1 |
2 | name: Client CI
3 |
4 | on:
5 | push:
6 | paths-ignore:
7 | - 'README.md'
8 | branches:
9 | - master
10 |
11 | jobs:
12 | build:
13 |
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - name: Checkout
18 | uses: actions/checkout@v1
19 | - name: Set up Docker Buildx
20 | id: buildx
21 | uses: crazy-max/ghaction-docker-buildx@v1
22 | with:
23 | version: latest
24 | - name: Docker login
25 | run: echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
26 | - name: Run Buildx
27 | working-directory: ./
28 | run: |
29 | ls && docker buildx build \
30 | --platform linux/386,linux/amd64,linux/arm64 \
31 | --output "type=image" --push \
32 | --tag yooooomi/easy-countdown ./
33 |
--------------------------------------------------------------------------------
/.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 | public/variables-final.js
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:13.12.0-alpine
2 |
3 | WORKDIR /app
4 |
5 | ARG ENVIRONMENT
6 | ENV ENVIRONMENT ${ENVIRONMENT:-PRODUCTION}
7 |
8 | RUN apk add bash
9 | RUN npm install serve -g
10 |
11 | COPY package.json ./
12 | COPY package-lock.json ./
13 |
14 | RUN npm install --silent
15 |
16 | COPY . ./
17 |
18 | RUN ./scripts/build.sh
19 |
20 | # start app
21 | CMD ./variables.sh build && serve -s -l tcp://0.0.0.0:3000 build/
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Timothee Boussus
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | 
4 |
5 | # Easy countdown
6 |
7 | Easy countdown is an easy to setup countdown page. Can be setup as a countdown or as a timer
8 |
9 | # Setup
10 |
11 | ## Using docker (Recommended)
12 |
13 | If you use docker, just edit the `docker-compose.yml` file so that it fits your needs
14 |
15 | |Variables|Definition|Example|
16 | |-|-|-|
17 | |TIMER_BACKGROUND|The url of an image that will be used for as background|https://wallpaperplay.com/walls/full/0/7/6/29912.jpg|
18 | |TIMER_TARGET|The target date of the countdown, if date is in the future, timer will decrease, otherwise it will increase|Fri Oct 01 2021 15:33:36 GMT+0200|
19 | |TIMER_TITLE|The title of the countdown, can be empty|My title!|
20 |
21 | ### Example of `docker-compose.yml` file
22 |
23 | > This example is a copy-paste of `docker-compose.production.yml` in the repo
24 |
25 | ```yml
26 | version: "3.8"
27 |
28 | services:
29 | web:
30 | stdin_open: true # So that the serving is not exited with code 0
31 | image: yooooomi/easy-countdown
32 | environment:
33 | TIMER_BACKGROUND: https://wallpaperplay.com/walls/full/0/7/6/29912.jpg
34 | TIMER_TARGET: "Fri Oct 01 2021 15:33:36 GMT+0200" # Get help with https://esqsoft.com/javascript_examples/date-to-epoch.htm
35 | TIMER_TITLE: "My next birthday" # Can be empty
36 | ports:
37 | - "3000:3000"
38 | ```
39 |
40 | ## Without docker
41 |
42 | > This method builds the project following the env variables you gave, producing a `build` folder that has to be served manually afterwards. You can use [`serve`](https://www.npmjs.com/package/serve) to achieve it
43 |
44 | Use `yarn` to use the build script from the
45 | `package.json`. Simply use `yarn build`. Use the variables above in the env to personalize your countdown
46 |
47 | - `npm install`
48 | - `TIMER_TITLE="example" yarn build`
49 |
50 | > Variables will be taken from env, and are the same as above
51 |
52 | I.E: `TIMER_TARGET="Fri Oct 01 2021 15:33:36 GMT+0200" yarn build && serve -s -l tcp://0.0.0.0:3000 build/`
53 |
--------------------------------------------------------------------------------
/docker-compose.dev.yml:
--------------------------------------------------------------------------------
1 | version: "3.8"
2 |
3 | services:
4 | web:
5 | build:
6 | args:
7 | ENVIRONMENT: "DEV"
8 | command: yarn start
9 | volumes:
10 | - "./src/:/app/src/"
11 | - "./public/:/app/public/"
--------------------------------------------------------------------------------
/docker-compose.production.yml:
--------------------------------------------------------------------------------
1 | version: "3.8"
2 |
3 | services:
4 | web:
5 | stdin_open: true # So that the serving is not exited with code 0
6 | image: yooooomi/easy-countdown
7 | environment:
8 | TIMER_BACKGROUND: https://wallpaperplay.com/walls/full/0/7/6/29912.jpg
9 | TIMER_TARGET: "Fri Oct 01 2021 15:33:36 GMT+0200" # Get help with https://esqsoft.com/javascript_examples/date-to-epoch.htm
10 | TIMER_TITLE: "My next birthday"
11 | ports:
12 | - "3000:3000"
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.8"
2 |
3 | services:
4 | web:
5 | stdin_open: true # So that the serving is not exited with code 0
6 | build:
7 | context: .
8 | dockerfile: Dockerfile
9 | args:
10 | ENVIRONMENT: "PRODUCTION"
11 | environment:
12 | TIMER_BACKGROUND: https://wallpaperplay.com/walls/full/0/7/6/29912.jpg
13 | TIMER_TARGET: "Wed Oct 28 2020 00:00:00 GMT+0100" # Get help with https://esqsoft.com/javascript_examples/date-to-epoch.htm
14 | TIMER_TITLE: "My birthday in"
15 | ports:
16 | - "3000:3000"
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "countdown",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^4.2.4",
7 | "@testing-library/react": "^9.3.2",
8 | "@testing-library/user-event": "^7.1.2",
9 | "classnames": "^2.2.6",
10 | "react": "^16.13.1",
11 | "react-dom": "^16.13.1",
12 | "react-scripts": "3.4.1"
13 | },
14 | "scripts": {
15 | "start": "./variables.sh public && react-scripts start",
16 | "build": "./variables.sh public && react-scripts build",
17 | "test": "react-scripts test",
18 | "eject": "react-scripts eject"
19 | },
20 | "eslintConfig": {
21 | "extends": "react-app"
22 | },
23 | "browserslist": {
24 | "production": [
25 | ">0.2%",
26 | "not dead",
27 | "not op_mini all"
28 | ],
29 | "development": [
30 | "last 1 chrome version",
31 | "last 1 firefox version",
32 | "last 1 safari version"
33 | ]
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yooooomi/easy-countdown/86dd114ceec93a175abfafacfa342e1354849d3e/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 | React App
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yooooomi/easy-countdown/86dd114ceec93a175abfafacfa342e1354849d3e/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yooooomi/easy-countdown/86dd114ceec93a175abfafacfa342e1354849d3e/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Easy countdown",
3 | "name": "Countdown created using easy-countdown",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/variables.js:
--------------------------------------------------------------------------------
1 |
2 | window.background = "__BACKGROUND__";
3 | window.target = new Date("__END__");
4 | window.title = "__TITLE__" || "";
5 |
--------------------------------------------------------------------------------
/scripts/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | if [[ "$ENVIRONMENT" != "DEV" ]]
4 | then
5 | yarn build
6 | fi
7 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './App.css';
3 | import Home from './scenes/Home';
4 |
5 | function App() {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default App;
14 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Balsamiq+Sans:wght@400;700&display=swap');
2 |
3 | body {
4 | margin: 0;
5 | font-family: "Balsamiq Sans";
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want your app to work offline and load faster, you can change
15 | // unregister() to register() below. Note this comes with some pitfalls.
16 | // Learn more about service workers: https://bit.ly/CRA-PWA
17 | serviceWorker.unregister();
18 |
--------------------------------------------------------------------------------
/src/scenes/Home/Block/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cl from 'classnames';
3 | import s from './index.module.css';
4 | import NumberDisplay from '../NumberDisplay';
5 |
6 | function Block({ className, value, title }) {
7 | return (
8 |
9 |
10 | {value.split('').map((v, k) => )}
11 |
12 |
{title}
13 |
14 | );
15 | }
16 |
17 | export default Block;
18 |
--------------------------------------------------------------------------------
/src/scenes/Home/Block/index.module.css:
--------------------------------------------------------------------------------
1 |
2 | .root {
3 | padding: 50px;
4 | height: 70px;
5 | width: 140px;
6 | box-shadow: 0px 5px 33px -13px rgba(0,0,0,0.5);
7 | background: rgba(211, 211, 211, 0.349);
8 | display: flex;
9 | flex-direction: column;
10 | justify-content: center;
11 | align-items: center;
12 | border-radius: 6px;
13 | }
14 |
15 | .numbers {
16 | display: flex;
17 | align-items: center;
18 | justify-content: center;
19 | min-height: 100px;
20 | }
21 |
22 | .title {
23 | font-size: 30px;
24 | color: white;
25 | }
26 |
--------------------------------------------------------------------------------
/src/scenes/Home/NumberDisplay/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import s from './index.module.css';
3 |
4 | const ten = Array.from(Array(10).keys());
5 |
6 | function NumberDisplay({ value: v }) {
7 | const [fd, setFd] = useState(true);
8 |
9 | useEffect(() => {
10 | setFd(false);
11 | }, []);
12 |
13 | const value = fd ? Math.floor(Math.random() * 11) : v;
14 |
15 | return (
16 |
17 |
18 | {ten.map(t => (
19 |
{t}
20 | ))}
21 |
22 |
23 | );
24 | }
25 |
26 | export default NumberDisplay;
27 |
--------------------------------------------------------------------------------
/src/scenes/Home/NumberDisplay/index.module.css:
--------------------------------------------------------------------------------
1 |
2 | .value {
3 | position: relative;
4 | height: 100px;
5 | width: 70px;
6 | font-size: 100px;
7 | overflow: hidden;
8 | }
9 |
10 | .numbercontainer {
11 | position: relative;
12 | height: 100%;
13 | width: 100%;
14 | transition: all 800ms;
15 | }
16 |
17 | .number {
18 | color: white;
19 | font-weight: bold;
20 | position: absolute;
21 | left: 0px;
22 | right: 0px;
23 | height: 50px;
24 | }
--------------------------------------------------------------------------------
/src/scenes/Home/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useMemo } from 'react';
2 | import s from './index.module.css';
3 | import { describe } from '../../service/date';
4 | import Block from './Block';
5 |
6 | let end = window.target;
7 |
8 | function Home() {
9 | const [date, setDate] = useState(new Date());
10 |
11 | useEffect(() => {
12 | document.title = window.title || "Easy countdown"
13 | const inter = setInterval(() => setDate(new Date()), 1000);
14 | return () => clearInterval(inter);
15 | }, []);
16 |
17 | const described = useMemo(() => {
18 | return describe(date, end);
19 | }, [date]);
20 |
21 | return (
22 |
23 |
24 | {(window.title && window.title.length > 0) &&
{window.title}
}
25 |
26 | {Object.entries(described).map(([key, value], idx, arr) => (
27 | 1 ? 's' : ''}`}
31 | value={value.toString().padStart(2, '0')}
32 | />
33 | ))}
34 |
35 |
36 |
37 | );
38 | }
39 |
40 | export default Home;
41 |
--------------------------------------------------------------------------------
/src/scenes/Home/index.module.css:
--------------------------------------------------------------------------------
1 |
2 |
3 | .root {
4 | height: 100vh;
5 | display: flex;
6 | align-items: center;
7 | justify-content: center;
8 | background-repeat: no-repeat;
9 | background-size: cover;
10 | }
11 |
12 | .title {
13 | color: white;
14 | font-size: 1.8em;
15 | margin-bottom: 15px;
16 | }
17 |
18 | .blocks {
19 | display: flex;
20 | align-items: flex-start;
21 | justify-content: center;
22 | flex-direction: row;
23 | }
24 |
25 | .block {
26 | margin-right: 15px;
27 | }
28 |
29 | @media only screen and (max-width: 1020px) {
30 | .blocks {
31 | flex-direction: column;
32 | }
33 | .block {
34 | margin-bottom: 15px;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/service/date.js:
--------------------------------------------------------------------------------
1 | const day = 1000 * 60 * 60 * 24;
2 | const hour = 1000 * 60 * 60;
3 | const minute = 1000 * 60;
4 | const second = 1000;
5 |
6 | const numbers = [
7 | { nb: day, label: 'day' },
8 | { nb: hour, label: 'hour' },
9 | { nb: minute, label: 'minute' },
10 | { nb: second, label: 'second' },
11 | ];
12 |
13 | export function describe(a, b) {
14 | let ms = Math.abs(b.getTime() - a.getTime());
15 | const result = {};
16 |
17 | for (let i = 0; i < numbers.length; i += 1) {
18 | const field = numbers[i].label;
19 | const nb = numbers[i].nb;
20 |
21 | result[field] = Math.floor(ms / nb);
22 | ms -= result[field] * nb;
23 | }
24 | return result;
25 | }
26 |
--------------------------------------------------------------------------------
/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.0/8 are 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 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister();
136 | })
137 | .catch(error => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/variables.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | cp $1/variables.js $1/variables-final.js
4 |
5 | sed -i -e "s@__BACKGROUND__@$TIMER_BACKGROUND@g" $1/variables-final.js
6 | sed -i -e "s@__END__@$TIMER_TARGET@g" $1/variables-final.js
7 | sed -i -e "s@__TITLE__@$TIMER_TITLE@g" $1/variables-final.js
8 |
--------------------------------------------------------------------------------