Skip to content

Commit

Permalink
Initial release (v1.0.0)
Browse files Browse the repository at this point in the history
Edited README with link to latest release and screenshots
Added mute control for beep audio
Removed external JSON string functionality
Prevent form from submitting if there are errors
Some refactoring and bug fixes
  • Loading branch information
aouerfelli committed Jan 3, 2017
1 parent 47b536a commit 70ab8ed
Show file tree
Hide file tree
Showing 19 changed files with 121 additions and 93 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Station Timer
[![GitHub release](https://img.shields.io/badge/download-latest-blue.svg)](https://github.com/aouerfelli/station-timer/releases/latest)

A minimal timer application built with [Electron](http://electron.atom.io) that
counts down and loops for a given number of stations. This was originally
Expand All @@ -7,7 +8,9 @@ stations set up and would allocate a certain amount of time to stay at each
station, and would then give some additional time to clean up and go to the next
station.

## Releases (NOT AVAILABE YET)
![](screenshots.gif)

## Releases
There are builds available for Windows and macOS, since those are the only
platforms I am able to use and test on at the moment. If you would like to
build for your own platform, you can follow the steps [below](#Build).
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
13 changes: 6 additions & 7 deletions app/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const {app, BrowserWindow, ipcMain} = require('electron');

let mainWindow, setupWindow, pauseWindow;

function createMainWindow() {
function createMainWindow () {
mainWindow = new BrowserWindow({
fullscreen: true,
frame: false
Expand All @@ -15,7 +15,7 @@ function createMainWindow() {
mainWindow.on('closed', () => mainWindow = null);
}

function createSetupModalWindow() {
function createSetupModalWindow () {
setupWindow = new BrowserWindow({
parent: mainWindow,
modal: true,
Expand All @@ -33,7 +33,7 @@ function createSetupModalWindow() {
setupWindow.on('closed', () => setupWindow = null);
}

function createPauseModalWindow() {
function createPauseModalWindow () {
mainWindow.webContents.executeJavaScript(
'document.body.classList.add(\'dim\')'
);
Expand Down Expand Up @@ -62,16 +62,15 @@ function createPauseModalWindow() {
});
}

function createStartWindows() {
function createStartWindows () {
createMainWindow();
createSetupModalWindow();
}

function exitApp() {
function exitApp () {
// Do not exit the program on macOS (standard OS-specific behaviour).
// Instead, close all open windows.
// Instead, lose app focus and close all open windows.
if (process.platform === 'darwin') {
// Lose app and window focus before closing windows
app.hide();
BrowserWindow.getAllWindows().forEach(win => win.close());
} else {
Expand Down
75 changes: 41 additions & 34 deletions app/renderers/index.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,28 @@
const {ipcRenderer, remote} = require('electron');
const webContents = remote.getCurrentWebContents();

const counterTextView = document.getElementById('counter');
const secondProgressBar = document.getElementById('progress');
const infoTextView = document.getElementById('info');
const pauseButton = document.getElementById('pause');
const restartButton = document.getElementById('restart');
const muteOnButton = document.getElementById('mute-on');
const muteOffButton = document.getElementById('mute-off');
const exitButton = document.getElementById('exit');

const beepAudio = new Audio('../res/audio/beep.wav');
const beepAudio = new Audio('../assets/audio/beep.wav');

// Object containing strings used in the counter
// I know it's pretty ugly, but I'm sure you've seen worse
const text = (() => {
try {
return require('./../res/strings.json');
} catch (err) {
// If the JSON wasn't found or couldn't be parsed, then set default values
return {
counterText: {
end: '0'
},
infoText: {
active: 'Complete your activity',
coolDown: 'Go to your next station',
complete: 'Return to your original station'
}
};
const text = {
counterText: {
end: '0'
},
infoText: {
active: 'Complete your activity',
coolDown: 'Go to your next station',
complete: 'Return to your original station'
}
})();
};

// Object containing values for duration, break duration and number of repeats
let settings;
Expand All @@ -41,9 +36,21 @@ pauseButton.addEventListener('click', () => {
});

restartButton.addEventListener('click', () =>
remote.getCurrentWebContents().send('start-timer', settings)
webContents.send('start-timer', settings)
);

muteOnButton.addEventListener('click', () => {
webContents.setAudioMuted(true);
muteOnButton.parentElement.style.display = 'none';
muteOffButton.parentElement.style.display = '';
});

muteOffButton.addEventListener('click', () => {
webContents.setAudioMuted(false);
muteOffButton.parentElement.style.display = 'none';
muteOnButton.parentElement.style.display = '';
});

exitButton.addEventListener('click', () =>
ipcRenderer.send('exit')
);
Expand All @@ -64,7 +71,7 @@ counterTextView.addEventListener('click', () => {
*
* @param generatorFn the generator function that will yield Promises.
*/
function async(generatorFn) {
function async (generatorFn) {
function continuer(verb, arg) {
let result;
try {
Expand All @@ -83,7 +90,7 @@ function async(generatorFn) {
return onResolved();
}

function skipTransition(elements, action) {
function skipTransition (elements, action) {
// If a single element is given, place it in an array
if (elements.constructor !== Array) {
elements = [elements];
Expand All @@ -99,13 +106,13 @@ function skipTransition(elements, action) {
});
}

function setProgressBar() {
function setProgressBar () {
skipTransition(secondProgressBar, () =>
secondProgressBar.classList.remove('expand'));
secondProgressBar.classList.add('expand');
}

function getFormattedTime(seconds) {
function getFormattedTime (seconds) {
// Get units of time (from seconds up to hours)
let hh = parseInt(seconds / 3600, 10);
let mm = parseInt((seconds % 3600) / 60, 10);
Expand All @@ -121,19 +128,19 @@ function getFormattedTime(seconds) {
return hh + mm + ss;
}

function sleep(ms) {
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

function waitPause() {
function pauseWait () {
// The pause-wait channel will return a value of false when the pause modal is
// closed, which we can set to the paused flag. When the flag is set, the
// Promise will be resolved.
return Promise.resolve(paused = ipcRenderer.sendSync('pause-wait'));
}

function countdown(duration, view, onEachSecond) {
function action() {
function countdown (duration, view, onEachSecond) {
function action () {
// Run the onEachSecond function if it is given
if (typeof onEachSecond === 'function') {
onEachSecond();
Expand All @@ -145,23 +152,23 @@ function countdown(duration, view, onEachSecond) {
// We'll be decrementing duration each second in action()
while (duration > 0) {
if (paused) {
yield waitPause();
yield pauseWait();
} else {
action();
yield sleep(1000);
}
}
// Check before ending countdown
// Check for pause before ending countdown
if (paused) {
yield waitPause();
yield pauseWait();
}
}));
}

ipcRenderer.on('start-timer', (evt, userSettings) => {
let {duration, breakDuration, numRepeats} = settings = userSettings;

function resetTimer() {
function resetTimer () {
// Reset elements to their intended initial visibility
restartButton.parentElement.style.display = 'none';
pauseButton.parentElement.style.display = '';
Expand All @@ -172,15 +179,15 @@ ipcRenderer.on('start-timer', (evt, userSettings) => {
infoTextView.classList = '';
}

function durationCountdown() {
function durationCountdown () {
counterTextView.classList.remove('red');
counterTextView.classList.add('primary');
secondProgressBar.classList.remove('red');
infoTextView.textContent = text.infoText.active;
return countdown(duration, counterTextView, setProgressBar);
}

function breakDurationCountdown() {
function breakDurationCountdown () {
counterTextView.classList.remove('primary');
counterTextView.classList.add('red');
secondProgressBar.classList.add('red');
Expand All @@ -191,7 +198,7 @@ ipcRenderer.on('start-timer', (evt, userSettings) => {
});
}

function endTimer() {
function endTimer () {
// Setting end classes
secondProgressBar.classList.add('remove');
skipTransition(counterTextView, () =>
Expand Down
40 changes: 29 additions & 11 deletions app/renderers/setup.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,45 @@
const {ipcRenderer, remote} = require('electron');

const setupForm = document.forms.namedItem('setup');
const setupFormInputs = Array.from(
setupForm.querySelectorAll('input[type="number"]')
);
const exitButton = document.getElementById('exit');

Array.from(document.querySelectorAll('input[type="number"]')).forEach(input => {
input.addEventListener('input', () => {
// The handling of empty inputs is done in CSS. If the input is not
// empty and the value is an empty string, that means the input is
// invalid.
let valid = input.value !== '' &&
(Number.isInteger(Number(input.value))
&& (input.min === '' ||
parseInt(input.value, 10) >= parseInt(input.min, 10))
&& (input.max === '' ||
parseInt(input.value, 10) <= parseInt(input.max, 10)));
function checkValidInput (value, min, max) {
// The handling of empty inputs is done in CSS. If the input is not empty and
// the value is an empty string, that means the input is invalid.
return value !== '' &&
(Number.isSafeInteger(Number(value))
&& (min === '' ||
parseInt(value, 10) >= parseInt(min, 10))
&& (max === '' ||
parseInt(value, 10) <= parseInt(max, 10)));
}

setupFormInputs.forEach(input => {
input.addEventListener('input', () => {
let valid = checkValidInput(input.value, input.min, input.max);
input.classList.toggle('error', !valid);
});
});

setupForm.addEventListener('submit', evt => {
evt.preventDefault();

let valid = setupFormInputs.every(input => {
let invalid = input.classList.contains('error');
if (invalid) {
// If any of the form inputs contain errors, focus on it
input.focus();
}
return !invalid;
});
// Return early (don't finish submitting the form) if not all inputs are valid
if (!valid) {
return;
}

ipcRenderer.send(
'setup-timer',
Object.assign(...Array.from(new FormData(setupForm))
Expand Down
10 changes: 0 additions & 10 deletions app/res/strings.json

This file was deleted.

15 changes: 6 additions & 9 deletions app/styles/_base.css → app/styles/base.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* ================================================
* Accumulated styles common to all HTML documents
* ================================================ */
/* Note that this stylesheet will be loaded using HTML instead of being
* imported directly into the other stylesheets for performance reasons
* (importing does not load styles asynchronously). */
/* Note that this stylesheet will be loaded using HTML instead of being
* imported directly into the other stylesheets for performance reasons
* (importing does not load styles asynchronously). */

/* =====================
* Local Fonts [*.html]
Expand All @@ -15,7 +15,7 @@
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'),
url('../res/fonts/Roboto-Regular.woff2') format('woff2');
url('../assets/fonts/Roboto-Regular.woff2') format('woff2');
}

/* Roboto Bold (700) */
Expand All @@ -24,7 +24,7 @@
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'),
url('../res/fonts/Roboto-Bold.woff2') format('woff2');
url('../assets/fonts/Roboto-Bold.woff2') format('woff2');
}

/* ===================
Expand All @@ -33,8 +33,6 @@

:root {
/* Colors */
--color-statusbar: #E0E0E0;
--color-appbar: #F5F5F5;
--color-background: #FAFAFA;
--color-card: #FFF;
--color-grey: #9E9E9E;
Expand Down Expand Up @@ -62,7 +60,7 @@
--curve-sharp: cubic-bezier(0.4, 0.0, 0.6, 1); /* ease in out */


/* Shadows */
/* Shadows (from MaterializeCSS) */
--shadow-depth-1: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
--shadow-depth-2: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
--shadow-depth-3: 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12), 0 3px 5px -1px rgba(0, 0, 0, 0.3);
Expand Down Expand Up @@ -252,7 +250,6 @@ button[type='submit'] svg {
display: inline-block;
padding: 0.25rem 0.5rem;
font-size: 0.8em;
letter-spacing: 0.5px;
border-radius: var(--corner-radius);
color: var(--font-color-light);
background-color: var(--font-color-dark);
Expand Down
Loading

0 comments on commit 70ab8ed

Please sign in to comment.