From 7769e2bdcf67b60f4926882e6e9db9ab8fec49fe Mon Sep 17 00:00:00 2001 From: Daniel Ignat Date: Sat, 13 Apr 2019 13:13:11 +0200 Subject: [PATCH] Normalize file endings (#32) * Make cross-platform development friction free Add .gitattributes recommended by gitattributes.io * Normalize all the line endings --- .gitattributes | 241 ++++++ README.md | 122 +-- src/main.js | 148 ++-- src/state/timer-state.js | 418 +++++----- src/windows/config/index.css | 228 +++--- src/windows/config/index.html | 158 ++-- src/windows/config/index.js | 382 ++++----- src/windows/fullscreen/index.css | 124 +-- src/windows/fullscreen/index.html | 66 +- src/windows/fullscreen/index.js | 58 +- src/windows/timer/index.css | 318 ++++---- src/windows/timer/index.html | 72 +- src/windows/timer/index.js | 234 +++--- src/windows/window-snapper.js | 124 +-- src/windows/windows.js | 338 ++++---- test/state/test-timer.js | 42 +- test/state/timer-state.specs.js | 1190 ++++++++++++++--------------- 17 files changed, 2252 insertions(+), 2011 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..b86dc246 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,241 @@ +# Nothing here yet + +# 👆 Custom rules specific to this project +# 👇 This is generated, don't manually edit it, follow the links and replace the whole thing! + +# Generated at https://gitattributes.io/ (https://gitattributes.io/api/common%2Cweb) + +#common settings that generally should always be used with your language specific settings + +# Auto detect text files and perform LF normalization +# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/ +* text=auto + +# +# The above will handle all files NOT found below +# + +# Documents +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain +*.md text +*.adoc text +*.textile text +*.mustache text +*.csv text +*.tab text +*.tsv text +*.sql text + +# Graphics +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.tif binary +*.tiff binary +*.ico binary +# SVG treated as an asset (binary) by default. If you want to treat it as text, +# comment-out the following line and uncomment the line after. +*.svg binary +#*.svg text +*.eps binary +## GITATTRIBUTES FOR WEB PROJECTS +# +# These settings are for any web project. +# +# Details per file setting: +# text These files should be normalized (i.e. convert CRLF to LF). +# binary These files are binary and should be left untouched. +# +# Note that binary is a macro for -text -diff. +###################################################################### + +## AUTO-DETECT +## Handle line endings automatically for files detected as +## text and leave all files detected as binary untouched. +## This will handle all files NOT defined below. +* text=auto + +## SOURCE CODE +*.bat text eol=crlf +*.coffee text +*.css text +*.htm text +*.html text +*.inc text +*.ini text +*.js text +*.json text +*.jsx text +*.less text +*.od text +*.onlydata text +*.php text +*.pl text +*.py text +*.rb text +*.sass text +*.scm text +*.scss text +*.sh text eol=lf +*.sql text +*.styl text +*.tag text +*.ts text +*.tsx text +*.xml text +*.xhtml text + +## DOCKER +*.dockerignore text +Dockerfile text + +## DOCUMENTATION +*.markdown text +*.md text +*.mdwn text +*.mdown text +*.mkd text +*.mkdn text +*.mdtxt text +*.mdtext text +*.txt text +AUTHORS text +CHANGELOG text +CHANGES text +CONTRIBUTING text +COPYING text +copyright text +*COPYRIGHT* text +INSTALL text +license text +LICENSE text +NEWS text +readme text +*README* text +TODO text + +## TEMPLATES +*.dot text +*.ejs text +*.haml text +*.handlebars text +*.hbs text +*.hbt text +*.jade text +*.latte text +*.mustache text +*.njk text +*.phtml text +*.tmpl text +*.tpl text +*.twig text + +## LINTERS +.csslintrc text +.eslintrc text +.htmlhintrc text +.jscsrc text +.jshintrc text +.jshintignore text +.stylelintrc text + +## CONFIGS +*.bowerrc text +*.cnf text +*.conf text +*.config text +.browserslistrc text +.editorconfig text +.gitattributes text +.gitconfig text +.htaccess text +*.npmignore text +*.yaml text +*.yml text +browserslist text +Makefile text +makefile text + +## HEROKU +Procfile text +.slugignore text + +## GRAPHICS +*.ai binary +*.bmp binary +*.eps binary +*.gif binary +*.ico binary +*.jng binary +*.jp2 binary +*.jpg binary +*.jpeg binary +*.jpx binary +*.jxr binary +*.pdf binary +*.png binary +*.psb binary +*.psd binary +*.svg text +*.svgz binary +*.tif binary +*.tiff binary +*.wbmp binary +*.webp binary + +## AUDIO +*.kar binary +*.m4a binary +*.mid binary +*.midi binary +*.mp3 binary +*.ogg binary +*.ra binary + +## VIDEO +*.3gpp binary +*.3gp binary +*.as binary +*.asf binary +*.asx binary +*.fla binary +*.flv binary +*.m4v binary +*.mng binary +*.mov binary +*.mp4 binary +*.mpeg binary +*.mpg binary +*.ogv binary +*.swc binary +*.swf binary +*.webm binary + +## ARCHIVES +*.7z binary +*.gz binary +*.jar binary +*.rar binary +*.tar binary +*.zip binary + +## FONTS +*.ttf binary +*.eot binary +*.otf binary +*.woff binary +*.woff2 binary + +## EXECUTABLES +*.exe binary +*.pyc binary \ No newline at end of file diff --git a/README.md b/README.md index 1b57b789..df040ce0 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,61 @@ -# Mob Timer - -[![GitHub release](https://img.shields.io/github/release/mob-timer/mob-timer.svg)](https://github.com/mob-timer/mob-timer/releases) -[![Greenkeeper badge](https://badges.greenkeeper.io/mob-timer/mob-timer.svg)](https://greenkeeper.io/) -[![License](https://img.shields.io/github/license/mob-timer/mob-timer.svg)](LICENSE) -[![GitHub contributors](https://img.shields.io/github/contributors/mob-timer/mob-timer.svg)](https://github.com/mob-timer/mob-timer/graphs/contributors) -[![Travis (.org)](https://img.shields.io/travis/mob-timer/mob-timer/master.svg)](https://travis-ci.org/mob-timer/mob-timer/branches) -[![Coverage Status](https://coveralls.io/repos/github/mob-timer/mob-timer/badge.svg?branch=master)](https://coveralls.io/github/mob-timer/mob-timer?branch=master) -![GitHub All Releases](https://img.shields.io/github/downloads/mob-timer/mob-timer/total.svg) -[![GitHub open idea issues](https://img.shields.io/github/issues-raw/mob-timer/mob-timer/idea.svg?color=blue)](https://github.com/mob-timer/mob-timer/issues?q=is%3Aissue+is%3Aopen+label%3Aidea) - -A cross-platform mob-timer built on [Electron](http://electron.atom.io/) -for doing [Mob Programming](http://mobprogramming.org/). This is a fork from [pluralsight/mob-timer](https://github.com/pluralsight/mob-timer). - -![Example Timer Image](timer-example.png) - -Click the gear icon in the top right to configure the mob-timer. -Then click the large circle to start/stop the mob-timer, -or the smaller circle to skip to the next mobber. - -# Running the mob-timer - -You can either build the mob-timer from source or [download a pre-built version](https://github.com/mob-timer/mob-timer/releases). - -## Build mob-timer - -Run `npm install` and then one of the following commands for your respective operating system: -- Windows: `npm run build-win` -- Mac OS X: `npm run build-mac` -- Linux: `npm run build-linux` (You may need to install `libcanberra-gtk-module`) - -Platform specific packages will be placed in the `dist` directory. -If you need a platform other than these, you will need to modify the build script in the `package.json` file. - - -# Development - -Run `npm install` to get the dependencies, then `npm start` to run the timer. -Run `npm test` to run the unit tests once, or alternatively `npm run watch` to run them on changes. - - -# Contributing - -Feel free to open Issues and Pull Requests discussing additions to this project. You can also have a look at the [existing issues](https://github.com/mob-timer/mob-timer/issues). Keep the Pull Requests small and make sure the tests and code style checks pass. - -If you are uncertain, please reach out first (by opening an issue) before investing too much time. :) - -# Reasons for forking - -_This is a fork from [pluralsight/mob-timer](https://github.com/pluralsight/mob-timer), please have a look to see if that project is more suited to your needs!_ 🙂 - -There are a few main reasons for this fork existing: - -- To build in CI and attach to release using [Travis CI](https://travis-ci.org/) -- To stay up to date with dependencies using [Greenkeeper.io](https://greenkeeper.io/) -- To move to code style and tooling suited for project, not needing to take internal company best practices into account -- To have an independent organization where the mob-timer is the focus - -# License - -The Mob Timer is licensed under the [Apache 2.0 license](LICENSE). +# Mob Timer + +[![GitHub release](https://img.shields.io/github/release/mob-timer/mob-timer.svg)](https://github.com/mob-timer/mob-timer/releases) +[![Greenkeeper badge](https://badges.greenkeeper.io/mob-timer/mob-timer.svg)](https://greenkeeper.io/) +[![License](https://img.shields.io/github/license/mob-timer/mob-timer.svg)](LICENSE) +[![GitHub contributors](https://img.shields.io/github/contributors/mob-timer/mob-timer.svg)](https://github.com/mob-timer/mob-timer/graphs/contributors) +[![Travis (.org)](https://img.shields.io/travis/mob-timer/mob-timer/master.svg)](https://travis-ci.org/mob-timer/mob-timer/branches) +[![Coverage Status](https://coveralls.io/repos/github/mob-timer/mob-timer/badge.svg?branch=master)](https://coveralls.io/github/mob-timer/mob-timer?branch=master) +![GitHub All Releases](https://img.shields.io/github/downloads/mob-timer/mob-timer/total.svg) +[![GitHub open idea issues](https://img.shields.io/github/issues-raw/mob-timer/mob-timer/idea.svg?color=blue)](https://github.com/mob-timer/mob-timer/issues?q=is%3Aissue+is%3Aopen+label%3Aidea) + +A cross-platform mob-timer built on [Electron](http://electron.atom.io/) +for doing [Mob Programming](http://mobprogramming.org/). This is a fork from [pluralsight/mob-timer](https://github.com/pluralsight/mob-timer). + +![Example Timer Image](timer-example.png) + +Click the gear icon in the top right to configure the mob-timer. +Then click the large circle to start/stop the mob-timer, +or the smaller circle to skip to the next mobber. + +# Running the mob-timer + +You can either build the mob-timer from source or [download a pre-built version](https://github.com/mob-timer/mob-timer/releases). + +## Build mob-timer + +Run `npm install` and then one of the following commands for your respective operating system: +- Windows: `npm run build-win` +- Mac OS X: `npm run build-mac` +- Linux: `npm run build-linux` (You may need to install `libcanberra-gtk-module`) + +Platform specific packages will be placed in the `dist` directory. +If you need a platform other than these, you will need to modify the build script in the `package.json` file. + + +# Development + +Run `npm install` to get the dependencies, then `npm start` to run the timer. +Run `npm test` to run the unit tests once, or alternatively `npm run watch` to run them on changes. + + +# Contributing + +Feel free to open Issues and Pull Requests discussing additions to this project. You can also have a look at the [existing issues](https://github.com/mob-timer/mob-timer/issues). Keep the Pull Requests small and make sure the tests and code style checks pass. + +If you are uncertain, please reach out first (by opening an issue) before investing too much time. :) + +# Reasons for forking + +_This is a fork from [pluralsight/mob-timer](https://github.com/pluralsight/mob-timer), please have a look to see if that project is more suited to your needs!_ 🙂 + +There are a few main reasons for this fork existing: + +- To build in CI and attach to release using [Travis CI](https://travis-ci.org/) +- To stay up to date with dependencies using [Greenkeeper.io](https://greenkeeper.io/) +- To move to code style and tooling suited for project, not needing to take internal company best practices into account +- To have an independent organization where the mob-timer is the focus + +# License + +The Mob Timer is licensed under the [Apache 2.0 license](LICENSE). diff --git a/src/main.js b/src/main.js index abf72a7f..8ccc3951 100644 --- a/src/main.js +++ b/src/main.js @@ -1,74 +1,74 @@ -const electron = require("electron"); -const { app, ipcMain: ipc } = electron; - -let windows = require("./windows/windows"); -let TimerState = require("./state/timer-state"); -let statePersister = require("./state/state-persister"); - -let timerState = new TimerState(); - -app.on("ready", () => { - timerState.setCallback(onTimerEvent); - timerState.loadState(statePersister.read()); - windows.setConfigState(timerState.getState()); - windows.createTimerWindow(); - if (timerState.getState().shuffleMobbersOnStartup) { - timerState.shuffleMobbers(); - } -}); - -function onTimerEvent(event, data) { - windows.dispatchEvent(event, data); - if (event === "configUpdated") { - statePersister.write(timerState.getState()); - } -} - -ipc.on("timerWindowReady", () => timerState.initialize()); -ipc.on("configWindowReady", () => timerState.publishConfig()); -ipc.on("fullscreenWindowReady", () => timerState.publishConfig()); - -ipc.on("pause", () => timerState.pause()); -ipc.on("unpause", () => timerState.start()); -ipc.on("skip", () => timerState.rotate()); -ipc.on("startTurn", () => timerState.start()); -ipc.on("configure", () => { - windows.showConfigWindow(); - windows.closeFullscreenWindow(); -}); - -ipc.on("shuffleMobbers", () => timerState.shuffleMobbers()); -ipc.on("addMobber", (event, mobber) => timerState.addMobber(mobber)); -ipc.on("removeMobber", (event, mobber) => timerState.removeMobber(mobber)); -ipc.on("updateMobber", (event, mobber) => timerState.updateMobber(mobber)); -ipc.on("setSecondsPerTurn", (event, secondsPerTurn) => - timerState.setSecondsPerTurn(secondsPerTurn) -); -ipc.on("setSecondsUntilFullscreen", (event, secondsUntilFullscreen) => - timerState.setSecondsUntilFullscreen(secondsUntilFullscreen) -); -ipc.on("setSnapThreshold", (event, threshold) => - timerState.setSnapThreshold(threshold) -); -ipc.on("setAlertSoundTimes", (event, alertSoundTimes) => - timerState.setAlertSoundTimes(alertSoundTimes) -); -ipc.on("setAlertSound", (event, alertSound) => - timerState.setAlertSound(alertSound) -); -ipc.on("setTimerAlwaysOnTop", (event, value) => - timerState.setTimerAlwaysOnTop(value) -); -ipc.on("setShuffleMobbersOnStartup", (event, value) => - timerState.setShuffleMobbersOnStartup(value) -); - -app.on("window-all-closed", function() { - if (process.platform !== "darwin") { - app.quit(); - } -}); - -app.on("activate", function() { - windows.createTimerWindow(); -}); +const electron = require("electron"); +const { app, ipcMain: ipc } = electron; + +let windows = require("./windows/windows"); +let TimerState = require("./state/timer-state"); +let statePersister = require("./state/state-persister"); + +let timerState = new TimerState(); + +app.on("ready", () => { + timerState.setCallback(onTimerEvent); + timerState.loadState(statePersister.read()); + windows.setConfigState(timerState.getState()); + windows.createTimerWindow(); + if (timerState.getState().shuffleMobbersOnStartup) { + timerState.shuffleMobbers(); + } +}); + +function onTimerEvent(event, data) { + windows.dispatchEvent(event, data); + if (event === "configUpdated") { + statePersister.write(timerState.getState()); + } +} + +ipc.on("timerWindowReady", () => timerState.initialize()); +ipc.on("configWindowReady", () => timerState.publishConfig()); +ipc.on("fullscreenWindowReady", () => timerState.publishConfig()); + +ipc.on("pause", () => timerState.pause()); +ipc.on("unpause", () => timerState.start()); +ipc.on("skip", () => timerState.rotate()); +ipc.on("startTurn", () => timerState.start()); +ipc.on("configure", () => { + windows.showConfigWindow(); + windows.closeFullscreenWindow(); +}); + +ipc.on("shuffleMobbers", () => timerState.shuffleMobbers()); +ipc.on("addMobber", (event, mobber) => timerState.addMobber(mobber)); +ipc.on("removeMobber", (event, mobber) => timerState.removeMobber(mobber)); +ipc.on("updateMobber", (event, mobber) => timerState.updateMobber(mobber)); +ipc.on("setSecondsPerTurn", (event, secondsPerTurn) => + timerState.setSecondsPerTurn(secondsPerTurn) +); +ipc.on("setSecondsUntilFullscreen", (event, secondsUntilFullscreen) => + timerState.setSecondsUntilFullscreen(secondsUntilFullscreen) +); +ipc.on("setSnapThreshold", (event, threshold) => + timerState.setSnapThreshold(threshold) +); +ipc.on("setAlertSoundTimes", (event, alertSoundTimes) => + timerState.setAlertSoundTimes(alertSoundTimes) +); +ipc.on("setAlertSound", (event, alertSound) => + timerState.setAlertSound(alertSound) +); +ipc.on("setTimerAlwaysOnTop", (event, value) => + timerState.setTimerAlwaysOnTop(value) +); +ipc.on("setShuffleMobbersOnStartup", (event, value) => + timerState.setShuffleMobbersOnStartup(value) +); + +app.on("window-all-closed", function() { + if (process.platform !== "darwin") { + app.quit(); + } +}); + +app.on("activate", function() { + windows.createTimerWindow(); +}); diff --git a/src/state/timer-state.js b/src/state/timer-state.js index 1e6fe6da..5e64df51 100644 --- a/src/state/timer-state.js +++ b/src/state/timer-state.js @@ -1,209 +1,209 @@ -const Timer = require("./timer"); -const Mobbers = require("./mobbers"); - -class TimerState { - constructor(options) { - if (!options) { - options = {}; - } - this.secondsPerTurn = 600; - this.mobbers = new Mobbers(); - this.secondsUntilFullscreen = 30; - this.snapThreshold = 25; - this.alertSound = null; - this.alertSoundTimes = []; - this.timerAlwaysOnTop = true; - this.shuffleMobbersOnStartup = false; - - this.createTimers(options.Timer || Timer); - } - - setCallback(callback) { - this.callback = callback; - } - - createTimers(TimerClass) { - this.mainTimer = new TimerClass( - { countDown: true, time: this.secondsPerTurn }, - secondsRemaining => { - this.dispatchTimerChange(secondsRemaining); - if (secondsRemaining < 0) { - this.pause(); - this.rotate(); - this.callback("turnEnded"); - this.startAlerts(); - } - } - ); - - this.alertsTimer = new TimerClass({ countDown: false }, alertSeconds => { - this.callback("alert", alertSeconds); - }); - } - - dispatchTimerChange(secondsRemaining) { - this.callback("timerChange", { - secondsRemaining, - secondsPerTurn: this.secondsPerTurn - }); - } - - reset() { - this.mainTimer.reset(this.secondsPerTurn); - this.dispatchTimerChange(this.secondsPerTurn); - } - - startAlerts() { - this.alertsTimer.reset(0); - this.alertsTimer.start(); - this.callback("alert", 0); - } - - stopAlerts() { - this.alertsTimer.pause(); - this.callback("stopAlerts"); - } - - start() { - this.mainTimer.start(); - this.callback("started"); - this.stopAlerts(); - } - - pause() { - this.mainTimer.pause(); - this.callback("paused"); - this.stopAlerts(); - } - - rotate() { - this.reset(); - this.mobbers.rotate(); - this.callback("rotated", this.mobbers.getCurrentAndNextMobbers()); - } - - initialize() { - this.rotate(); - this.callback("turnEnded"); - this.publishConfig(); - } - - publishConfig() { - this.callback("configUpdated", this.getState()); - this.callback("rotated", this.mobbers.getCurrentAndNextMobbers()); - } - - addMobber(mobber) { - this.mobbers.addMobber(mobber); - this.publishConfig(); - } - - removeMobber(mobber) { - let currentMobber = this.mobbers.getCurrentAndNextMobbers().current; - let isRemovingCurrentMobber = currentMobber - ? currentMobber.name === mobber.name - : false; - - this.mobbers.removeMobber(mobber); - - if (isRemovingCurrentMobber) { - this.pause(); - this.reset(); - this.callback("turnEnded"); - } - - this.publishConfig(); - } - - updateMobber(mobber) { - const currentMobber = this.mobbers.getCurrentAndNextMobbers().current; - const disablingCurrentMobber = - currentMobber.id === mobber.id && mobber.disabled; - - this.mobbers.updateMobber(mobber); - - if (disablingCurrentMobber) { - this.pause(); - this.reset(); - this.callback("turnEnded"); - } - - this.publishConfig(); - } - - setSecondsPerTurn(value) { - this.secondsPerTurn = value; - this.publishConfig(); - this.reset(); - } - - setSecondsUntilFullscreen(value) { - this.secondsUntilFullscreen = value; - this.publishConfig(); - } - - setSnapThreshold(value) { - this.snapThreshold = value; - this.publishConfig(); - } - - setAlertSound(soundFile) { - this.alertSound = soundFile; - this.publishConfig(); - } - - setAlertSoundTimes(secondsArray) { - this.alertSoundTimes = secondsArray; - this.publishConfig(); - } - - setTimerAlwaysOnTop(value) { - this.timerAlwaysOnTop = value; - this.publishConfig(); - } - - setShuffleMobbersOnStartup(value) { - this.shuffleMobbersOnStartup = value; - this.publishConfig(); - } - - shuffleMobbers() { - this.mobbers.shuffleMobbers(); - this.publishConfig(); - } - - getState() { - return { - mobbers: this.mobbers.getAll(), - secondsPerTurn: this.secondsPerTurn, - secondsUntilFullscreen: this.secondsUntilFullscreen, - snapThreshold: this.snapThreshold, - alertSound: this.alertSound, - alertSoundTimes: this.alertSoundTimes, - timerAlwaysOnTop: this.timerAlwaysOnTop, - shuffleMobbersOnStartup: this.shuffleMobbersOnStartup - }; - } - - loadState(state) { - if (state.mobbers) { - state.mobbers.forEach(x => this.addMobber(x)); - } - - this.setSecondsPerTurn(state.secondsPerTurn || this.secondsPerTurn); - if (typeof state.secondsUntilFullscreen === "number") { - this.setSecondsUntilFullscreen(state.secondsUntilFullscreen); - } - if (typeof state.snapThreshold === "number") { - this.setSnapThreshold(state.snapThreshold); - } - this.alertSound = state.alertSound || null; - this.alertSoundTimes = state.alertSoundTimes || []; - if (typeof state.timerAlwaysOnTop === "boolean") { - this.timerAlwaysOnTop = state.timerAlwaysOnTop; - } - this.shuffleMobbersOnStartup = !!state.shuffleMobbersOnStartup; - } -} - -module.exports = TimerState; +const Timer = require("./timer"); +const Mobbers = require("./mobbers"); + +class TimerState { + constructor(options) { + if (!options) { + options = {}; + } + this.secondsPerTurn = 600; + this.mobbers = new Mobbers(); + this.secondsUntilFullscreen = 30; + this.snapThreshold = 25; + this.alertSound = null; + this.alertSoundTimes = []; + this.timerAlwaysOnTop = true; + this.shuffleMobbersOnStartup = false; + + this.createTimers(options.Timer || Timer); + } + + setCallback(callback) { + this.callback = callback; + } + + createTimers(TimerClass) { + this.mainTimer = new TimerClass( + { countDown: true, time: this.secondsPerTurn }, + secondsRemaining => { + this.dispatchTimerChange(secondsRemaining); + if (secondsRemaining < 0) { + this.pause(); + this.rotate(); + this.callback("turnEnded"); + this.startAlerts(); + } + } + ); + + this.alertsTimer = new TimerClass({ countDown: false }, alertSeconds => { + this.callback("alert", alertSeconds); + }); + } + + dispatchTimerChange(secondsRemaining) { + this.callback("timerChange", { + secondsRemaining, + secondsPerTurn: this.secondsPerTurn + }); + } + + reset() { + this.mainTimer.reset(this.secondsPerTurn); + this.dispatchTimerChange(this.secondsPerTurn); + } + + startAlerts() { + this.alertsTimer.reset(0); + this.alertsTimer.start(); + this.callback("alert", 0); + } + + stopAlerts() { + this.alertsTimer.pause(); + this.callback("stopAlerts"); + } + + start() { + this.mainTimer.start(); + this.callback("started"); + this.stopAlerts(); + } + + pause() { + this.mainTimer.pause(); + this.callback("paused"); + this.stopAlerts(); + } + + rotate() { + this.reset(); + this.mobbers.rotate(); + this.callback("rotated", this.mobbers.getCurrentAndNextMobbers()); + } + + initialize() { + this.rotate(); + this.callback("turnEnded"); + this.publishConfig(); + } + + publishConfig() { + this.callback("configUpdated", this.getState()); + this.callback("rotated", this.mobbers.getCurrentAndNextMobbers()); + } + + addMobber(mobber) { + this.mobbers.addMobber(mobber); + this.publishConfig(); + } + + removeMobber(mobber) { + let currentMobber = this.mobbers.getCurrentAndNextMobbers().current; + let isRemovingCurrentMobber = currentMobber + ? currentMobber.name === mobber.name + : false; + + this.mobbers.removeMobber(mobber); + + if (isRemovingCurrentMobber) { + this.pause(); + this.reset(); + this.callback("turnEnded"); + } + + this.publishConfig(); + } + + updateMobber(mobber) { + const currentMobber = this.mobbers.getCurrentAndNextMobbers().current; + const disablingCurrentMobber = + currentMobber.id === mobber.id && mobber.disabled; + + this.mobbers.updateMobber(mobber); + + if (disablingCurrentMobber) { + this.pause(); + this.reset(); + this.callback("turnEnded"); + } + + this.publishConfig(); + } + + setSecondsPerTurn(value) { + this.secondsPerTurn = value; + this.publishConfig(); + this.reset(); + } + + setSecondsUntilFullscreen(value) { + this.secondsUntilFullscreen = value; + this.publishConfig(); + } + + setSnapThreshold(value) { + this.snapThreshold = value; + this.publishConfig(); + } + + setAlertSound(soundFile) { + this.alertSound = soundFile; + this.publishConfig(); + } + + setAlertSoundTimes(secondsArray) { + this.alertSoundTimes = secondsArray; + this.publishConfig(); + } + + setTimerAlwaysOnTop(value) { + this.timerAlwaysOnTop = value; + this.publishConfig(); + } + + setShuffleMobbersOnStartup(value) { + this.shuffleMobbersOnStartup = value; + this.publishConfig(); + } + + shuffleMobbers() { + this.mobbers.shuffleMobbers(); + this.publishConfig(); + } + + getState() { + return { + mobbers: this.mobbers.getAll(), + secondsPerTurn: this.secondsPerTurn, + secondsUntilFullscreen: this.secondsUntilFullscreen, + snapThreshold: this.snapThreshold, + alertSound: this.alertSound, + alertSoundTimes: this.alertSoundTimes, + timerAlwaysOnTop: this.timerAlwaysOnTop, + shuffleMobbersOnStartup: this.shuffleMobbersOnStartup + }; + } + + loadState(state) { + if (state.mobbers) { + state.mobbers.forEach(x => this.addMobber(x)); + } + + this.setSecondsPerTurn(state.secondsPerTurn || this.secondsPerTurn); + if (typeof state.secondsUntilFullscreen === "number") { + this.setSecondsUntilFullscreen(state.secondsUntilFullscreen); + } + if (typeof state.snapThreshold === "number") { + this.setSnapThreshold(state.snapThreshold); + } + this.alertSound = state.alertSound || null; + this.alertSoundTimes = state.alertSoundTimes || []; + if (typeof state.timerAlwaysOnTop === "boolean") { + this.timerAlwaysOnTop = state.timerAlwaysOnTop; + } + this.shuffleMobbersOnStartup = !!state.shuffleMobbersOnStartup; + } +} + +module.exports = TimerState; diff --git a/src/windows/config/index.css b/src/windows/config/index.css index 7734bb7b..a5cba0e9 100644 --- a/src/windows/config/index.css +++ b/src/windows/config/index.css @@ -1,114 +1,114 @@ -@import url('../theme.css'); - -body { - margin: 0; - padding: 0; - font-family: sans-serif; - color: var(--main-text-color); - background-color: var(--main-background-color); -} - -input { - height: 22px; -} - -.container { - margin: 20px; -} - -.minutes { - width: 40px; -} - -.mobber { - display: flex; - padding: 4px; - align-items: center; -} - -.mobber .image { - margin-right: 5px; - height: 24px; - width: 24px; - border-radius: 50%; - border: 2px solid var(--mobber-border-color); - cursor: pointer; -} - -.mobber .image:hover { - border: 2px solid var(--mobber-border-highlight-color); -} - -.mobber:nth-child(odd) { - background-color: var(--table-stripe-color); -} - -.mobber .btn:first-of-type { - display: block; - margin-left: auto; -} - -.mobber.disabled .image { - opacity: .3; -} - -.mobber.disabled .name { - font-style: italic; - color: var(--disabled-text-color); - text-decoration: line-through; -} - -.oneLineInputAndButton { - margin: 4px 0; -} - -.addLabel { - display: flex; - align-items: center; -} - -.mobberListActions { - margin: 4px 0 4px -5px; - display: flex; -} - -.btn { - background-color: var(--button-background-color); - border: 0; - border-radius: 3px; - padding: 7px 10px; - color: var(--button-text-color); - margin-left: 5px; - cursor: pointer; -} - -h1 { - font-size: 18px; - border-bottom: 1px solid var(--main-text-color); - margin-top: 20px; -} - -input[type="checkbox" i] { - position: relative; - vertical-align: middle; -} - -.settings > div { - margin-bottom: 10px; -} - -.fullscreen-seconds { - width: 40px; -} - -.replay-audio-seconds { - width: 40px; -} - -#replayAudioContainer.disabled { - color: var(--disabled-text-color); -} - -.custom-sound { - width: 145px; -} +@import url('../theme.css'); + +body { + margin: 0; + padding: 0; + font-family: sans-serif; + color: var(--main-text-color); + background-color: var(--main-background-color); +} + +input { + height: 22px; +} + +.container { + margin: 20px; +} + +.minutes { + width: 40px; +} + +.mobber { + display: flex; + padding: 4px; + align-items: center; +} + +.mobber .image { + margin-right: 5px; + height: 24px; + width: 24px; + border-radius: 50%; + border: 2px solid var(--mobber-border-color); + cursor: pointer; +} + +.mobber .image:hover { + border: 2px solid var(--mobber-border-highlight-color); +} + +.mobber:nth-child(odd) { + background-color: var(--table-stripe-color); +} + +.mobber .btn:first-of-type { + display: block; + margin-left: auto; +} + +.mobber.disabled .image { + opacity: .3; +} + +.mobber.disabled .name { + font-style: italic; + color: var(--disabled-text-color); + text-decoration: line-through; +} + +.oneLineInputAndButton { + margin: 4px 0; +} + +.addLabel { + display: flex; + align-items: center; +} + +.mobberListActions { + margin: 4px 0 4px -5px; + display: flex; +} + +.btn { + background-color: var(--button-background-color); + border: 0; + border-radius: 3px; + padding: 7px 10px; + color: var(--button-text-color); + margin-left: 5px; + cursor: pointer; +} + +h1 { + font-size: 18px; + border-bottom: 1px solid var(--main-text-color); + margin-top: 20px; +} + +input[type="checkbox" i] { + position: relative; + vertical-align: middle; +} + +.settings > div { + margin-bottom: 10px; +} + +.fullscreen-seconds { + width: 40px; +} + +.replay-audio-seconds { + width: 40px; +} + +#replayAudioContainer.disabled { + color: var(--disabled-text-color); +} + +.custom-sound { + width: 145px; +} diff --git a/src/windows/config/index.html b/src/windows/config/index.html index fbb622f1..13133032 100644 --- a/src/windows/config/index.html +++ b/src/windows/config/index.html @@ -1,79 +1,79 @@ - - - - - Mob Timer Configuration - - - -
-
- -
-
-

Mobbers

-
-
- -
-
- -
-
-
-

Additional Settings

-
- Fullscreen view appears after: - seconds -
-
- -
-
- -
-
- - seconds -
-
- - -
-
- -
-
- -
-
-
- - - + + + + + Mob Timer Configuration + + + +
+
+ +
+
+

Mobbers

+
+
+ +
+
+ +
+
+
+

Additional Settings

+
+ Fullscreen view appears after: + seconds +
+
+ +
+
+ +
+
+ + seconds +
+
+ + +
+
+ +
+
+ +
+
+
+ + + diff --git a/src/windows/config/index.js b/src/windows/config/index.js index 8b6bb7f5..951f4ad4 100644 --- a/src/windows/config/index.js +++ b/src/windows/config/index.js @@ -1,191 +1,191 @@ -const ipc = require("electron").ipcRenderer; -const { dialog } = require("electron").remote; - -const mobbersEl = document.getElementById("mobbers"); -const shuffleEl = document.getElementById("shuffle"); -const minutesEl = document.getElementById("minutes"); -const addEl = document.getElementById("add"); -const addMobberForm = document.getElementById("addMobberForm"); -const fullscreenSecondsEl = document.getElementById("fullscreen-seconds"); -const snapToEdgesCheckbox = document.getElementById("snap-to-edges"); -const alertAudioCheckbox = document.getElementById("alertAudio"); -const replayAudioContainer = document.getElementById("replayAudioContainer"); -const replayAlertAudioCheckbox = document.getElementById("replayAlertAudio"); -const replayAudioAfterSeconds = document.getElementById( - "replayAudioAfterSeconds" -); -const useCustomSoundCheckbox = document.getElementById("useCustomSound"); -const customSoundEl = document.getElementById("customSound"); -const timerAlwaysOnTopCheckbox = document.getElementById("timerAlwaysOnTop"); -const shuffleMobbersOnStartupCheckbox = document.getElementById( - "shuffleMobbersOnStartup" -); - -function createMobberEl(mobber) { - const el = document.createElement("div"); - el.classList.add("mobber"); - if (mobber.disabled) { - el.classList.add("disabled"); - } - - const imgEl = document.createElement("img"); - imgEl.src = mobber.image || "../img/sad-cyclops.png"; - imgEl.classList.add("image"); - el.appendChild(imgEl); - - const nameEl = document.createElement("div"); - nameEl.innerHTML = mobber.name; - nameEl.classList.add("name"); - el.appendChild(nameEl); - - const disableBtn = document.createElement("button"); - disableBtn.classList.add("btn"); - disableBtn.innerHTML = mobber.disabled ? "Enable" : "Disable"; - el.appendChild(disableBtn); - - const rmBtn = document.createElement("button"); - rmBtn.classList.add("btn"); - rmBtn.innerHTML = "Remove"; - el.appendChild(rmBtn); - - imgEl.addEventListener("click", () => selectImage(mobber)); - disableBtn.addEventListener("click", () => toggleMobberDisabled(mobber)); - rmBtn.addEventListener("click", () => ipc.send("removeMobber", mobber)); - - return el; -} - -function selectImage(mobber) { - var image = dialog.showOpenDialog({ - title: "Select image", - filters: [{ name: "Images", extensions: ["jpg", "png", "gif"] }], - properties: ["openFile"] - }); - - if (image) { - mobber.image = image[0]; - ipc.send("updateMobber", mobber); - } -} - -function toggleMobberDisabled(mobber) { - mobber.disabled = !mobber.disabled; - ipc.send("updateMobber", mobber); -} - -ipc.on("configUpdated", (event, data) => { - minutesEl.value = Math.ceil(data.secondsPerTurn / 60); - mobbersEl.innerHTML = ""; - const frag = document.createDocumentFragment(); - data.mobbers.map(mobber => { - frag.appendChild(createMobberEl(mobber)); - }); - mobbersEl.appendChild(frag); - fullscreenSecondsEl.value = data.secondsUntilFullscreen; - snapToEdgesCheckbox.checked = data.snapThreshold > 0; - - alertAudioCheckbox.checked = data.alertSoundTimes.length > 0; - replayAlertAudioCheckbox.checked = data.alertSoundTimes.length > 1; - replayAudioAfterSeconds.value = - data.alertSoundTimes.length > 1 ? data.alertSoundTimes[1] : 30; - updateAlertControls(); - - useCustomSoundCheckbox.checked = !!data.alertSound; - customSoundEl.value = data.alertSound; - - timerAlwaysOnTopCheckbox.checked = data.timerAlwaysOnTop; - shuffleMobbersOnStartupCheckbox.checked = data.shuffleMobbersOnStartup; -}); - -minutesEl.addEventListener("change", () => { - ipc.send("setSecondsPerTurn", minutesEl.value * 60); -}); - -addMobberForm.addEventListener("submit", event => { - event.preventDefault(); - let value = addEl.value.trim(); - if (!value) { - return; - } - ipc.send("addMobber", { name: value }); - addEl.value = ""; -}); - -shuffleEl.addEventListener("click", event => { - event.preventDefault(); - ipc.send("shuffleMobbers"); -}); - -fullscreenSecondsEl.addEventListener("change", () => { - ipc.send("setSecondsUntilFullscreen", fullscreenSecondsEl.value * 1); -}); - -ipc.send("configWindowReady"); - -snapToEdgesCheckbox.addEventListener("change", () => { - ipc.send("setSnapThreshold", snapToEdgesCheckbox.checked ? 25 : 0); -}); - -alertAudioCheckbox.addEventListener("change", () => updateAlertTimes()); -replayAlertAudioCheckbox.addEventListener("change", () => updateAlertTimes()); -replayAudioAfterSeconds.addEventListener("change", () => updateAlertTimes()); - -function updateAlertTimes() { - updateAlertControls(); - - let alertSeconds = []; - if (alertAudioCheckbox.checked) { - alertSeconds.push(0); - if (replayAlertAudioCheckbox.checked) { - alertSeconds.push(replayAudioAfterSeconds.value * 1); - } - } - - ipc.send("setAlertSoundTimes", alertSeconds); -} - -function updateAlertControls() { - let replayDisabled = !alertAudioCheckbox.checked; - replayAlertAudioCheckbox.disabled = replayDisabled; - - if (replayDisabled) { - replayAlertAudioCheckbox.checked = false; - replayAudioContainer.classList.add("disabled"); - } else { - replayAudioContainer.classList.remove("disabled"); - } - - let secondsDisabled = !replayAlertAudioCheckbox.checked; - replayAudioAfterSeconds.disabled = secondsDisabled; -} - -useCustomSoundCheckbox.addEventListener("change", () => { - let mp3 = null; - - if (useCustomSoundCheckbox.checked) { - const selectedMp3 = dialog.showOpenDialog({ - title: "Select alert sound", - filters: [{ name: "MP3", extensions: ["mp3"] }], - properties: ["openFile"] - }); - - if (selectedMp3) { - mp3 = selectedMp3[0]; - } else { - useCustomSoundCheckbox.checked = false; - } - } - - ipc.send("setAlertSound", mp3); -}); - -timerAlwaysOnTopCheckbox.addEventListener("change", () => { - ipc.send("setTimerAlwaysOnTop", timerAlwaysOnTopCheckbox.checked); -}); - -shuffleMobbersOnStartupCheckbox.addEventListener("change", () => { - ipc.send( - "setShuffleMobbersOnStartup", - shuffleMobbersOnStartupCheckbox.checked - ); -}); +const ipc = require("electron").ipcRenderer; +const { dialog } = require("electron").remote; + +const mobbersEl = document.getElementById("mobbers"); +const shuffleEl = document.getElementById("shuffle"); +const minutesEl = document.getElementById("minutes"); +const addEl = document.getElementById("add"); +const addMobberForm = document.getElementById("addMobberForm"); +const fullscreenSecondsEl = document.getElementById("fullscreen-seconds"); +const snapToEdgesCheckbox = document.getElementById("snap-to-edges"); +const alertAudioCheckbox = document.getElementById("alertAudio"); +const replayAudioContainer = document.getElementById("replayAudioContainer"); +const replayAlertAudioCheckbox = document.getElementById("replayAlertAudio"); +const replayAudioAfterSeconds = document.getElementById( + "replayAudioAfterSeconds" +); +const useCustomSoundCheckbox = document.getElementById("useCustomSound"); +const customSoundEl = document.getElementById("customSound"); +const timerAlwaysOnTopCheckbox = document.getElementById("timerAlwaysOnTop"); +const shuffleMobbersOnStartupCheckbox = document.getElementById( + "shuffleMobbersOnStartup" +); + +function createMobberEl(mobber) { + const el = document.createElement("div"); + el.classList.add("mobber"); + if (mobber.disabled) { + el.classList.add("disabled"); + } + + const imgEl = document.createElement("img"); + imgEl.src = mobber.image || "../img/sad-cyclops.png"; + imgEl.classList.add("image"); + el.appendChild(imgEl); + + const nameEl = document.createElement("div"); + nameEl.innerHTML = mobber.name; + nameEl.classList.add("name"); + el.appendChild(nameEl); + + const disableBtn = document.createElement("button"); + disableBtn.classList.add("btn"); + disableBtn.innerHTML = mobber.disabled ? "Enable" : "Disable"; + el.appendChild(disableBtn); + + const rmBtn = document.createElement("button"); + rmBtn.classList.add("btn"); + rmBtn.innerHTML = "Remove"; + el.appendChild(rmBtn); + + imgEl.addEventListener("click", () => selectImage(mobber)); + disableBtn.addEventListener("click", () => toggleMobberDisabled(mobber)); + rmBtn.addEventListener("click", () => ipc.send("removeMobber", mobber)); + + return el; +} + +function selectImage(mobber) { + var image = dialog.showOpenDialog({ + title: "Select image", + filters: [{ name: "Images", extensions: ["jpg", "png", "gif"] }], + properties: ["openFile"] + }); + + if (image) { + mobber.image = image[0]; + ipc.send("updateMobber", mobber); + } +} + +function toggleMobberDisabled(mobber) { + mobber.disabled = !mobber.disabled; + ipc.send("updateMobber", mobber); +} + +ipc.on("configUpdated", (event, data) => { + minutesEl.value = Math.ceil(data.secondsPerTurn / 60); + mobbersEl.innerHTML = ""; + const frag = document.createDocumentFragment(); + data.mobbers.map(mobber => { + frag.appendChild(createMobberEl(mobber)); + }); + mobbersEl.appendChild(frag); + fullscreenSecondsEl.value = data.secondsUntilFullscreen; + snapToEdgesCheckbox.checked = data.snapThreshold > 0; + + alertAudioCheckbox.checked = data.alertSoundTimes.length > 0; + replayAlertAudioCheckbox.checked = data.alertSoundTimes.length > 1; + replayAudioAfterSeconds.value = + data.alertSoundTimes.length > 1 ? data.alertSoundTimes[1] : 30; + updateAlertControls(); + + useCustomSoundCheckbox.checked = !!data.alertSound; + customSoundEl.value = data.alertSound; + + timerAlwaysOnTopCheckbox.checked = data.timerAlwaysOnTop; + shuffleMobbersOnStartupCheckbox.checked = data.shuffleMobbersOnStartup; +}); + +minutesEl.addEventListener("change", () => { + ipc.send("setSecondsPerTurn", minutesEl.value * 60); +}); + +addMobberForm.addEventListener("submit", event => { + event.preventDefault(); + let value = addEl.value.trim(); + if (!value) { + return; + } + ipc.send("addMobber", { name: value }); + addEl.value = ""; +}); + +shuffleEl.addEventListener("click", event => { + event.preventDefault(); + ipc.send("shuffleMobbers"); +}); + +fullscreenSecondsEl.addEventListener("change", () => { + ipc.send("setSecondsUntilFullscreen", fullscreenSecondsEl.value * 1); +}); + +ipc.send("configWindowReady"); + +snapToEdgesCheckbox.addEventListener("change", () => { + ipc.send("setSnapThreshold", snapToEdgesCheckbox.checked ? 25 : 0); +}); + +alertAudioCheckbox.addEventListener("change", () => updateAlertTimes()); +replayAlertAudioCheckbox.addEventListener("change", () => updateAlertTimes()); +replayAudioAfterSeconds.addEventListener("change", () => updateAlertTimes()); + +function updateAlertTimes() { + updateAlertControls(); + + let alertSeconds = []; + if (alertAudioCheckbox.checked) { + alertSeconds.push(0); + if (replayAlertAudioCheckbox.checked) { + alertSeconds.push(replayAudioAfterSeconds.value * 1); + } + } + + ipc.send("setAlertSoundTimes", alertSeconds); +} + +function updateAlertControls() { + let replayDisabled = !alertAudioCheckbox.checked; + replayAlertAudioCheckbox.disabled = replayDisabled; + + if (replayDisabled) { + replayAlertAudioCheckbox.checked = false; + replayAudioContainer.classList.add("disabled"); + } else { + replayAudioContainer.classList.remove("disabled"); + } + + let secondsDisabled = !replayAlertAudioCheckbox.checked; + replayAudioAfterSeconds.disabled = secondsDisabled; +} + +useCustomSoundCheckbox.addEventListener("change", () => { + let mp3 = null; + + if (useCustomSoundCheckbox.checked) { + const selectedMp3 = dialog.showOpenDialog({ + title: "Select alert sound", + filters: [{ name: "MP3", extensions: ["mp3"] }], + properties: ["openFile"] + }); + + if (selectedMp3) { + mp3 = selectedMp3[0]; + } else { + useCustomSoundCheckbox.checked = false; + } + } + + ipc.send("setAlertSound", mp3); +}); + +timerAlwaysOnTopCheckbox.addEventListener("change", () => { + ipc.send("setTimerAlwaysOnTop", timerAlwaysOnTopCheckbox.checked); +}); + +shuffleMobbersOnStartupCheckbox.addEventListener("change", () => { + ipc.send( + "setShuffleMobbersOnStartup", + shuffleMobbersOnStartupCheckbox.checked + ); +}); diff --git a/src/windows/fullscreen/index.css b/src/windows/fullscreen/index.css index f29a72e6..2927a4f0 100644 --- a/src/windows/fullscreen/index.css +++ b/src/windows/fullscreen/index.css @@ -1,62 +1,62 @@ -@import url('../theme.css'); - -body { - margin: 0; - padding: 0; - font-family: sans-serif; - user-select: none; - color: var(--main-text-color); - background-color: var(--main-background-color); -} -.container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100vh; -} -.current { - font-size: 38px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - width: 90%; - text-align: center; - margin-bottom: 15px; -} -.controls { - display: flex; - margin-bottom: 15px; -} -.btn { - background: var(--button-background-color); - border-radius: 3px; - border: 0; - color: var(--button-text-color); - padding: 10px 20px; - margin: 0 5px; - cursor: pointer; - font-size: 20px; -} -.next { - font-size: 20px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - width: 90%; - text-align: center; -} -.pic { - border-radius: 50%; - border: 2px solid var(--mobber-border-color); -} -.current .pic { - width: 150px; - height: 150px; - border-width: 5px; -} -.next .pic { - width: 30px; - height: 30px; - vertical-align: bottom; -} +@import url('../theme.css'); + +body { + margin: 0; + padding: 0; + font-family: sans-serif; + user-select: none; + color: var(--main-text-color); + background-color: var(--main-background-color); +} +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; +} +.current { + font-size: 38px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 90%; + text-align: center; + margin-bottom: 15px; +} +.controls { + display: flex; + margin-bottom: 15px; +} +.btn { + background: var(--button-background-color); + border-radius: 3px; + border: 0; + color: var(--button-text-color); + padding: 10px 20px; + margin: 0 5px; + cursor: pointer; + font-size: 20px; +} +.next { + font-size: 20px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 90%; + text-align: center; +} +.pic { + border-radius: 50%; + border: 2px solid var(--mobber-border-color); +} +.current .pic { + width: 150px; + height: 150px; + border-width: 5px; +} +.next .pic { + width: 30px; + height: 30px; + vertical-align: bottom; +} diff --git a/src/windows/fullscreen/index.html b/src/windows/fullscreen/index.html index 1c256e94..de5da884 100644 --- a/src/windows/fullscreen/index.html +++ b/src/windows/fullscreen/index.html @@ -1,33 +1,33 @@ - - - - - Mob Timer - - - - -
- -
- -
- {name}, please sit at the keyboard -
-
- - - -
- - -
- - - - - + + + + + Mob Timer + + + + +
+ +
+ +
+ {name}, please sit at the keyboard +
+
+ + + +
+ + +
+ + + + + diff --git a/src/windows/fullscreen/index.js b/src/windows/fullscreen/index.js index bfb530c1..cbe290df 100644 --- a/src/windows/fullscreen/index.js +++ b/src/windows/fullscreen/index.js @@ -1,29 +1,29 @@ -const ipc = require("electron").ipcRenderer; - -const skipBtn = document.getElementById("skip"); -const startTurnBtn = document.getElementById("startTurn"); -const configureBtn = document.getElementById("configure"); -const currentEl = document.getElementById("current"); -const currentPicEl = document.getElementById("currentPic"); -const nextEl = document.getElementById("next"); -const nextPicEl = document.getElementById("nextPic"); - -ipc.on("rotated", (event, data) => { - if (!data.current) { - data.current = { name: "Add a mobber" }; - } - currentEl.innerHTML = data.current.name; - currentPicEl.src = data.current.image || "../img/sad-cyclops.png"; - - if (!data.next) { - data.next = data.current; - } - nextEl.innerHTML = data.next.name; - nextPicEl.src = data.next.image || "../img/sad-cyclops.png"; -}); - -skipBtn.addEventListener("click", () => ipc.send("skip")); -startTurnBtn.addEventListener("click", () => ipc.send("startTurn")); -configureBtn.addEventListener("click", () => ipc.send("configure")); - -ipc.send("fullscreenWindowReady"); +const ipc = require("electron").ipcRenderer; + +const skipBtn = document.getElementById("skip"); +const startTurnBtn = document.getElementById("startTurn"); +const configureBtn = document.getElementById("configure"); +const currentEl = document.getElementById("current"); +const currentPicEl = document.getElementById("currentPic"); +const nextEl = document.getElementById("next"); +const nextPicEl = document.getElementById("nextPic"); + +ipc.on("rotated", (event, data) => { + if (!data.current) { + data.current = { name: "Add a mobber" }; + } + currentEl.innerHTML = data.current.name; + currentPicEl.src = data.current.image || "../img/sad-cyclops.png"; + + if (!data.next) { + data.next = data.current; + } + nextEl.innerHTML = data.next.name; + nextPicEl.src = data.next.image || "../img/sad-cyclops.png"; +}); + +skipBtn.addEventListener("click", () => ipc.send("skip")); +startTurnBtn.addEventListener("click", () => ipc.send("startTurn")); +configureBtn.addEventListener("click", () => ipc.send("configure")); + +ipc.send("fullscreenWindowReady"); diff --git a/src/windows/timer/index.css b/src/windows/timer/index.css index 6bad45e9..f2479dd5 100644 --- a/src/windows/timer/index.css +++ b/src/windows/timer/index.css @@ -1,159 +1,159 @@ -@import url('../theme.css'); - -body { - margin: 0; - padding: 0; - font-family: sans-serif; - -webkit-app-region: drag; - user-select: none; - color: var(--main-text-color); - background-color: var(--main-background-color); -} - -.container { - position: relative; - height: 100vh; -} - -.button { - cursor: pointer; - -webkit-app-region: no-drag; -} - -.pic { - border-radius: 50%; -} - -.timerContainer { - position: absolute; - top: 5px; - left: 5px; -} - -.currentPic { - position: absolute; - top: 1px; - left: 1px; - width: 76px; - height: 76px; -} - -.timerCanvas { - position: absolute; - width: 80px; - height: 80px; -} - -.toggle { - width: 80px; - height: 80px; - border-radius: 50%; -} - -.overlay { - opacity: 0; -} - -.overlay:hover { - opacity: .5; -} - -.play { - background-image: url(../img/play.png); - background-size: 80px 80px; -} - -.pause { - background-image: url(../img/pause.png); - background-size: 80px 80px; -} - -.current { - position: absolute; - top: 25px; - left: 90px; - display: inline-block; - font-size: 16px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - width: 120px; -} - -.nextContainer { - position: absolute; - top: 50px; - left: 90px; - width: 100px; -} - -.next { - position: absolute; - border-radius: 50%; - width: 25px; - height: 25px; - border: 2px solid var(--mobber-border-color); -} - -.overlay.next { - background-image: url(../img/skip.png); - background-size: 25px 25px; -} - -.nextContainer span { - position: absolute; - top: 10px; - left: 35px; - font-size: 11px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - display: inline-block; - width: 90px; -} - -.configure { - position: absolute; - display: block; - top: 5px; - right: 5px; - width: 20px; - height: 20px; - background-color: transparent; - background-image: url(../img/configure.png); - background-size: 20px 20px; - border: 0; - opacity: .5; -} - -.configure:hover { - opacity: .7; -} - -@keyframes pulse { - 0% { - background: var(--timer-pulse-color-1); - } - 50% { - background: var(--timer-pulse-color-2); - } - 100% { - background: var(--timer-pulse-color-1); - } -} - -.isTurnEnded { - animation-duration: 1s; - animation-name: pulse; - animation-iteration-count: infinite; - color: var(--timer-pulse-text-color); -} - -.isPaused { - color: var(--timer-paused-text-color); - background: var(--timer-paused-background-color); -} - -audio { - display: none; -} +@import url('../theme.css'); + +body { + margin: 0; + padding: 0; + font-family: sans-serif; + -webkit-app-region: drag; + user-select: none; + color: var(--main-text-color); + background-color: var(--main-background-color); +} + +.container { + position: relative; + height: 100vh; +} + +.button { + cursor: pointer; + -webkit-app-region: no-drag; +} + +.pic { + border-radius: 50%; +} + +.timerContainer { + position: absolute; + top: 5px; + left: 5px; +} + +.currentPic { + position: absolute; + top: 1px; + left: 1px; + width: 76px; + height: 76px; +} + +.timerCanvas { + position: absolute; + width: 80px; + height: 80px; +} + +.toggle { + width: 80px; + height: 80px; + border-radius: 50%; +} + +.overlay { + opacity: 0; +} + +.overlay:hover { + opacity: .5; +} + +.play { + background-image: url(../img/play.png); + background-size: 80px 80px; +} + +.pause { + background-image: url(../img/pause.png); + background-size: 80px 80px; +} + +.current { + position: absolute; + top: 25px; + left: 90px; + display: inline-block; + font-size: 16px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 120px; +} + +.nextContainer { + position: absolute; + top: 50px; + left: 90px; + width: 100px; +} + +.next { + position: absolute; + border-radius: 50%; + width: 25px; + height: 25px; + border: 2px solid var(--mobber-border-color); +} + +.overlay.next { + background-image: url(../img/skip.png); + background-size: 25px 25px; +} + +.nextContainer span { + position: absolute; + top: 10px; + left: 35px; + font-size: 11px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + width: 90px; +} + +.configure { + position: absolute; + display: block; + top: 5px; + right: 5px; + width: 20px; + height: 20px; + background-color: transparent; + background-image: url(../img/configure.png); + background-size: 20px 20px; + border: 0; + opacity: .5; +} + +.configure:hover { + opacity: .7; +} + +@keyframes pulse { + 0% { + background: var(--timer-pulse-color-1); + } + 50% { + background: var(--timer-pulse-color-2); + } + 100% { + background: var(--timer-pulse-color-1); + } +} + +.isTurnEnded { + animation-duration: 1s; + animation-name: pulse; + animation-iteration-count: infinite; + color: var(--timer-pulse-text-color); +} + +.isPaused { + color: var(--timer-paused-text-color); + background: var(--timer-paused-background-color); +} + +audio { + display: none; +} diff --git a/src/windows/timer/index.html b/src/windows/timer/index.html index b0eb84b0..ba182274 100644 --- a/src/windows/timer/index.html +++ b/src/windows/timer/index.html @@ -1,36 +1,36 @@ - - - - - Mob Timer - - - - -
-
- - -
-
- -
{name}
- - - -
- - - {name} -
-
- - - - - - + + + + + Mob Timer + + + + +
+
+ + +
+
+ +
{name}
+ + + +
+ + + {name} +
+
+ + + + + + diff --git a/src/windows/timer/index.js b/src/windows/timer/index.js index de356b89..c808d007 100644 --- a/src/windows/timer/index.js +++ b/src/windows/timer/index.js @@ -1,117 +1,117 @@ -const theme = require("../theme.js"); - -const ipc = require("electron").ipcRenderer; - -const containerEl = document.getElementById("container"); -const toggleBtn = document.getElementById("toggleButton"); -const configureBtn = document.getElementById("configureButton"); -const currentEl = document.getElementById("current"); -const nextEl = document.getElementById("next"); -const currentPicEl = document.getElementById("currentPic"); -const nextPicEl = document.getElementById("nextPic"); -const nextBtn = document.getElementById("nextButton"); -const timerCanvas = document.getElementById("timerCanvas"); -const alertAudio = document.getElementById("alertAudio"); - -const context = timerCanvas.getContext("2d"); - -let paused = true; -let alertSoundTimes = []; - -ipc.on("timerChange", (event, data) => { - clearCanvas(); - drawTimerCircle(); - drawTimerArc(data.secondsRemaining, data.secondsPerTurn); -}); - -function clearCanvas() { - context.clearRect(0, 0, timerCanvas.width, timerCanvas.height); -} - -function drawTimerCircle() { - const begin = 0; - const end = 2 * Math.PI; - drawArc(begin, end, "#EEEEEE"); -} - -function drawArc(begin, end, color) { - const circleCenterX = timerCanvas.width / 2; - const circleCenterY = circleCenterX; - const circleRadius = circleCenterX - 6; - context.beginPath(); - context.arc(circleCenterX, circleCenterY, circleRadius, begin, end); - context.strokeStyle = color; - context.lineWidth = 10; - context.stroke(); -} - -function drawTimerArc(seconds, maxSeconds) { - let percent = 1 - seconds / maxSeconds; - if (percent === 0) { - return; - } - let begin = -(0.5 * Math.PI); - let end = begin + 2 * Math.PI * percent; - drawArc(begin, end, theme.mobberBorderHighlightColor); -} - -ipc.on("rotated", (event, data) => { - if (!data.current) { - data.current = { name: "Add a mobber" }; - } - currentPicEl.src = data.current.image || "../img/sad-cyclops.png"; - currentEl.innerHTML = data.current.name; - - if (!data.next) { - data.next = data.current; - } - nextPicEl.src = data.next.image || "../img/sad-cyclops.png"; - nextEl.innerHTML = data.next.name; -}); - -ipc.on("paused", () => { - paused = true; - containerEl.classList.add("isPaused"); - toggleBtn.classList.add("play"); - toggleBtn.classList.remove("pause"); -}); - -ipc.on("started", () => { - paused = false; - containerEl.classList.remove("isPaused"); - containerEl.classList.remove("isTurnEnded"); - toggleBtn.classList.remove("play"); - toggleBtn.classList.add("pause"); -}); - -ipc.on("turnEnded", () => { - paused = true; - containerEl.classList.remove("isPaused"); - containerEl.classList.add("isTurnEnded"); - toggleBtn.classList.add("play"); - toggleBtn.classList.remove("pause"); -}); - -ipc.on("configUpdated", (event, data) => { - alertSoundTimes = data.alertSoundTimes; - alertAudio.src = data.alertSound || "./default.mp3"; -}); - -ipc.on("alert", (event, data) => { - if (alertSoundTimes.some(item => item === data)) { - alertAudio.currentTime = 0; - alertAudio.play(); - } -}); - -ipc.on("stopAlerts", () => { - alertAudio.pause(); -}); - -toggleBtn.addEventListener("click", () => { - paused ? ipc.send("unpause") : ipc.send("pause"); -}); -nextBtn.addEventListener("click", () => ipc.send("skip")); -configureBtn.addEventListener("click", () => ipc.send("configure")); - -ipc.send("timerWindowReady"); +const theme = require("../theme.js"); + +const ipc = require("electron").ipcRenderer; + +const containerEl = document.getElementById("container"); +const toggleBtn = document.getElementById("toggleButton"); +const configureBtn = document.getElementById("configureButton"); +const currentEl = document.getElementById("current"); +const nextEl = document.getElementById("next"); +const currentPicEl = document.getElementById("currentPic"); +const nextPicEl = document.getElementById("nextPic"); +const nextBtn = document.getElementById("nextButton"); +const timerCanvas = document.getElementById("timerCanvas"); +const alertAudio = document.getElementById("alertAudio"); + +const context = timerCanvas.getContext("2d"); + +let paused = true; +let alertSoundTimes = []; + +ipc.on("timerChange", (event, data) => { + clearCanvas(); + drawTimerCircle(); + drawTimerArc(data.secondsRemaining, data.secondsPerTurn); +}); + +function clearCanvas() { + context.clearRect(0, 0, timerCanvas.width, timerCanvas.height); +} + +function drawTimerCircle() { + const begin = 0; + const end = 2 * Math.PI; + drawArc(begin, end, "#EEEEEE"); +} + +function drawArc(begin, end, color) { + const circleCenterX = timerCanvas.width / 2; + const circleCenterY = circleCenterX; + const circleRadius = circleCenterX - 6; + context.beginPath(); + context.arc(circleCenterX, circleCenterY, circleRadius, begin, end); + context.strokeStyle = color; + context.lineWidth = 10; + context.stroke(); +} + +function drawTimerArc(seconds, maxSeconds) { + let percent = 1 - seconds / maxSeconds; + if (percent === 0) { + return; + } + let begin = -(0.5 * Math.PI); + let end = begin + 2 * Math.PI * percent; + drawArc(begin, end, theme.mobberBorderHighlightColor); +} + +ipc.on("rotated", (event, data) => { + if (!data.current) { + data.current = { name: "Add a mobber" }; + } + currentPicEl.src = data.current.image || "../img/sad-cyclops.png"; + currentEl.innerHTML = data.current.name; + + if (!data.next) { + data.next = data.current; + } + nextPicEl.src = data.next.image || "../img/sad-cyclops.png"; + nextEl.innerHTML = data.next.name; +}); + +ipc.on("paused", () => { + paused = true; + containerEl.classList.add("isPaused"); + toggleBtn.classList.add("play"); + toggleBtn.classList.remove("pause"); +}); + +ipc.on("started", () => { + paused = false; + containerEl.classList.remove("isPaused"); + containerEl.classList.remove("isTurnEnded"); + toggleBtn.classList.remove("play"); + toggleBtn.classList.add("pause"); +}); + +ipc.on("turnEnded", () => { + paused = true; + containerEl.classList.remove("isPaused"); + containerEl.classList.add("isTurnEnded"); + toggleBtn.classList.add("play"); + toggleBtn.classList.remove("pause"); +}); + +ipc.on("configUpdated", (event, data) => { + alertSoundTimes = data.alertSoundTimes; + alertAudio.src = data.alertSound || "./default.mp3"; +}); + +ipc.on("alert", (event, data) => { + if (alertSoundTimes.some(item => item === data)) { + alertAudio.currentTime = 0; + alertAudio.play(); + } +}); + +ipc.on("stopAlerts", () => { + alertAudio.pause(); +}); + +toggleBtn.addEventListener("click", () => { + paused ? ipc.send("unpause") : ipc.send("pause"); +}); +nextBtn.addEventListener("click", () => ipc.send("skip")); +configureBtn.addEventListener("click", () => ipc.send("configure")); + +ipc.send("timerWindowReady"); diff --git a/src/windows/window-snapper.js b/src/windows/window-snapper.js index 9c16f435..096cf935 100644 --- a/src/windows/window-snapper.js +++ b/src/windows/window-snapper.js @@ -1,62 +1,62 @@ -const snapCheck = (windowBounds, screenBounds, snapThreshold) => { - if (snapThreshold <= 0) { - throw new Error( - "Not supported, not supposed to call window-snapper if threshold <= 0!" - ); - } - - const noSnap = { ...windowBounds, shouldSnap: false }; - const isWithinThreshold = setupThresholdCheck(snapThreshold); - - return { - ...noSnap, - ...snapLeftCheck(windowBounds, screenBounds, isWithinThreshold), - ...snapRightCheck(windowBounds, screenBounds, isWithinThreshold), - ...snapTopCheck(windowBounds, screenBounds, isWithinThreshold), - ...snapBottomCheck(windowBounds, screenBounds, isWithinThreshold) - }; -}; - -const setupThresholdCheck = snapThreshold => (a, b) => { - return Math.abs(a - b) <= snapThreshold; -}; - -const snapLeftCheck = (windowBounds, screenBounds, isWithinThreshold) => - isWithinThreshold(windowBounds.x, screenBounds.x) - ? { - x: screenBounds.x, - shouldSnap: true - } - : undefined; - -const snapRightCheck = (windowBounds, screenBounds, isWithinThreshold) => { - const rightWindowEdge = windowBounds.x + windowBounds.width; - const rightScreenEdge = screenBounds.x + screenBounds.width; - return isWithinThreshold(rightWindowEdge, rightScreenEdge) - ? { - x: rightScreenEdge - windowBounds.width, - shouldSnap: true - } - : undefined; -}; - -const snapTopCheck = (windowBounds, screenBounds, isWithinThreshold) => - isWithinThreshold(windowBounds.y, screenBounds.y) - ? { - y: screenBounds.y, - shouldSnap: true - } - : undefined; - -const snapBottomCheck = (windowBounds, screenBounds, isWithinThreshold) => { - const bottomWindowEdge = windowBounds.y + windowBounds.height; - const bottomScreenEdge = screenBounds.y + screenBounds.height; - return isWithinThreshold(bottomWindowEdge, bottomScreenEdge) - ? { - y: bottomScreenEdge - windowBounds.height, - shouldSnap: true - } - : undefined; -}; - -module.exports = { snapCheck }; +const snapCheck = (windowBounds, screenBounds, snapThreshold) => { + if (snapThreshold <= 0) { + throw new Error( + "Not supported, not supposed to call window-snapper if threshold <= 0!" + ); + } + + const noSnap = { ...windowBounds, shouldSnap: false }; + const isWithinThreshold = setupThresholdCheck(snapThreshold); + + return { + ...noSnap, + ...snapLeftCheck(windowBounds, screenBounds, isWithinThreshold), + ...snapRightCheck(windowBounds, screenBounds, isWithinThreshold), + ...snapTopCheck(windowBounds, screenBounds, isWithinThreshold), + ...snapBottomCheck(windowBounds, screenBounds, isWithinThreshold) + }; +}; + +const setupThresholdCheck = snapThreshold => (a, b) => { + return Math.abs(a - b) <= snapThreshold; +}; + +const snapLeftCheck = (windowBounds, screenBounds, isWithinThreshold) => + isWithinThreshold(windowBounds.x, screenBounds.x) + ? { + x: screenBounds.x, + shouldSnap: true + } + : undefined; + +const snapRightCheck = (windowBounds, screenBounds, isWithinThreshold) => { + const rightWindowEdge = windowBounds.x + windowBounds.width; + const rightScreenEdge = screenBounds.x + screenBounds.width; + return isWithinThreshold(rightWindowEdge, rightScreenEdge) + ? { + x: rightScreenEdge - windowBounds.width, + shouldSnap: true + } + : undefined; +}; + +const snapTopCheck = (windowBounds, screenBounds, isWithinThreshold) => + isWithinThreshold(windowBounds.y, screenBounds.y) + ? { + y: screenBounds.y, + shouldSnap: true + } + : undefined; + +const snapBottomCheck = (windowBounds, screenBounds, isWithinThreshold) => { + const bottomWindowEdge = windowBounds.y + windowBounds.height; + const bottomScreenEdge = screenBounds.y + screenBounds.height; + return isWithinThreshold(bottomWindowEdge, bottomScreenEdge) + ? { + y: bottomScreenEdge - windowBounds.height, + shouldSnap: true + } + : undefined; +}; + +module.exports = { snapCheck }; diff --git a/src/windows/windows.js b/src/windows/windows.js index d0c77132..c8c87ce3 100644 --- a/src/windows/windows.js +++ b/src/windows/windows.js @@ -1,169 +1,169 @@ -const electron = require("electron"); -const { app } = electron; -const { snapCheck } = require("./window-snapper"); -const path = require("path"); -const { debounce } = require("debounce"); - -let timerWindow, configWindow, fullscreenWindow; -let snapThreshold, secondsUntilFullscreen, timerAlwaysOnTop; -const timerWindowSize = { - width: 220, - height: 90 -}; - -exports.createTimerWindow = () => { - if (timerWindow) { - return; - } - - let { width, height } = electron.screen.getPrimaryDisplay().workAreaSize; - timerWindow = new electron.BrowserWindow({ - x: width - timerWindowSize.width, - y: height - timerWindowSize.height, - width: timerWindowSize.width, - height: timerWindowSize.height, - resizable: false, - alwaysOnTop: timerAlwaysOnTop, - frame: false, - icon: path.join(__dirname, "/../../src/windows/img/icon.png") - }); - - timerWindow.loadURL(`file://${__dirname}/timer/index.html`); - timerWindow.on("closed", () => (timerWindow = null)); - const delayedSetBounds = debounce(timerWindow.setBounds, 100); - - timerWindow.on("move", () => { - if (snapThreshold <= 0) { - return; - } - - let getCenter = bounds => { - return { - x: bounds.x + bounds.width / 2, - y: bounds.y + bounds.height / 2 - }; - }; - - let windowBounds = { - ...timerWindow.getBounds(), - width: timerWindowSize.width, - height: timerWindowSize.height - }; - let screenBounds = electron.screen.getDisplayNearestPoint( - getCenter(windowBounds) - ).workArea; - - const { shouldSnap, ...snapBounds } = snapCheck( - windowBounds, - screenBounds, - snapThreshold - ); - if (shouldSnap) { - delayedSetBounds(snapBounds); - } else { - delayedSetBounds.clear(); - } - }); -}; - -exports.showConfigWindow = () => { - if (configWindow) { - configWindow.show(); - return; - } - exports.createConfigWindow(); -}; - -exports.createConfigWindow = () => { - if (configWindow) { - return; - } - - configWindow = new electron.BrowserWindow({ - width: 420, - height: 500, - autoHideMenuBar: true - }); - - configWindow.loadURL(`file://${__dirname}/config/index.html`); - configWindow.on("closed", () => (configWindow = null)); -}; - -exports.createFullscreenWindow = () => { - if (fullscreenWindow) { - return; - } - - const { width, height } = electron.screen.getPrimaryDisplay().workAreaSize; - fullscreenWindow = createAlwaysOnTopFullscreenInterruptingWindow({ - width, - height, - resizable: false, - frame: false - }); - - fullscreenWindow.loadURL(`file://${__dirname}/fullscreen/index.html`); - fullscreenWindow.on("closed", () => (fullscreenWindow = null)); -}; - -exports.closeFullscreenWindow = () => { - if (fullscreenWindow) { - fullscreenWindow.close(); - } -}; - -exports.dispatchEvent = (event, data) => { - if (event === "configUpdated") { - exports.setConfigState(data); - } - if (event === "alert" && data === secondsUntilFullscreen) { - exports.createFullscreenWindow(); - } - if (event === "stopAlerts") { - exports.closeFullscreenWindow(); - } - - if (timerWindow) { - timerWindow.webContents.send(event, data); - } - if (configWindow) { - configWindow.webContents.send(event, data); - } - if (fullscreenWindow) { - fullscreenWindow.webContents.send(event, data); - } -}; - -exports.setConfigState = data => { - var needToRecreateTimerWindow = timerAlwaysOnTop !== data.timerAlwaysOnTop; - - snapThreshold = data.snapThreshold; - secondsUntilFullscreen = data.secondsUntilFullscreen; - timerAlwaysOnTop = data.timerAlwaysOnTop; - - if (needToRecreateTimerWindow && timerWindow) { - timerWindow.close(); - exports.createTimerWindow(); - } -}; - -function createAlwaysOnTopFullscreenInterruptingWindow(options) { - return whileAppDockHidden(() => { - const window = new electron.BrowserWindow(options); - window.setAlwaysOnTop(true, "screen-saver"); - return window; - }); -} - -function whileAppDockHidden(work) { - if (app.dock) { - // Mac OS: The window will be able to float above fullscreen windows too - app.dock.hide(); - } - const result = work(); - if (app.dock) { - // Mac OS: Show in dock again, window has been created - app.dock.show(); - } - return result; -} +const electron = require("electron"); +const { app } = electron; +const { snapCheck } = require("./window-snapper"); +const path = require("path"); +const { debounce } = require("debounce"); + +let timerWindow, configWindow, fullscreenWindow; +let snapThreshold, secondsUntilFullscreen, timerAlwaysOnTop; +const timerWindowSize = { + width: 220, + height: 90 +}; + +exports.createTimerWindow = () => { + if (timerWindow) { + return; + } + + let { width, height } = electron.screen.getPrimaryDisplay().workAreaSize; + timerWindow = new electron.BrowserWindow({ + x: width - timerWindowSize.width, + y: height - timerWindowSize.height, + width: timerWindowSize.width, + height: timerWindowSize.height, + resizable: false, + alwaysOnTop: timerAlwaysOnTop, + frame: false, + icon: path.join(__dirname, "/../../src/windows/img/icon.png") + }); + + timerWindow.loadURL(`file://${__dirname}/timer/index.html`); + timerWindow.on("closed", () => (timerWindow = null)); + const delayedSetBounds = debounce(timerWindow.setBounds, 100); + + timerWindow.on("move", () => { + if (snapThreshold <= 0) { + return; + } + + let getCenter = bounds => { + return { + x: bounds.x + bounds.width / 2, + y: bounds.y + bounds.height / 2 + }; + }; + + let windowBounds = { + ...timerWindow.getBounds(), + width: timerWindowSize.width, + height: timerWindowSize.height + }; + let screenBounds = electron.screen.getDisplayNearestPoint( + getCenter(windowBounds) + ).workArea; + + const { shouldSnap, ...snapBounds } = snapCheck( + windowBounds, + screenBounds, + snapThreshold + ); + if (shouldSnap) { + delayedSetBounds(snapBounds); + } else { + delayedSetBounds.clear(); + } + }); +}; + +exports.showConfigWindow = () => { + if (configWindow) { + configWindow.show(); + return; + } + exports.createConfigWindow(); +}; + +exports.createConfigWindow = () => { + if (configWindow) { + return; + } + + configWindow = new electron.BrowserWindow({ + width: 420, + height: 500, + autoHideMenuBar: true + }); + + configWindow.loadURL(`file://${__dirname}/config/index.html`); + configWindow.on("closed", () => (configWindow = null)); +}; + +exports.createFullscreenWindow = () => { + if (fullscreenWindow) { + return; + } + + const { width, height } = electron.screen.getPrimaryDisplay().workAreaSize; + fullscreenWindow = createAlwaysOnTopFullscreenInterruptingWindow({ + width, + height, + resizable: false, + frame: false + }); + + fullscreenWindow.loadURL(`file://${__dirname}/fullscreen/index.html`); + fullscreenWindow.on("closed", () => (fullscreenWindow = null)); +}; + +exports.closeFullscreenWindow = () => { + if (fullscreenWindow) { + fullscreenWindow.close(); + } +}; + +exports.dispatchEvent = (event, data) => { + if (event === "configUpdated") { + exports.setConfigState(data); + } + if (event === "alert" && data === secondsUntilFullscreen) { + exports.createFullscreenWindow(); + } + if (event === "stopAlerts") { + exports.closeFullscreenWindow(); + } + + if (timerWindow) { + timerWindow.webContents.send(event, data); + } + if (configWindow) { + configWindow.webContents.send(event, data); + } + if (fullscreenWindow) { + fullscreenWindow.webContents.send(event, data); + } +}; + +exports.setConfigState = data => { + var needToRecreateTimerWindow = timerAlwaysOnTop !== data.timerAlwaysOnTop; + + snapThreshold = data.snapThreshold; + secondsUntilFullscreen = data.secondsUntilFullscreen; + timerAlwaysOnTop = data.timerAlwaysOnTop; + + if (needToRecreateTimerWindow && timerWindow) { + timerWindow.close(); + exports.createTimerWindow(); + } +}; + +function createAlwaysOnTopFullscreenInterruptingWindow(options) { + return whileAppDockHidden(() => { + const window = new electron.BrowserWindow(options); + window.setAlwaysOnTop(true, "screen-saver"); + return window; + }); +} + +function whileAppDockHidden(work) { + if (app.dock) { + // Mac OS: The window will be able to float above fullscreen windows too + app.dock.hide(); + } + const result = work(); + if (app.dock) { + // Mac OS: Show in dock again, window has been created + app.dock.show(); + } + return result; +} diff --git a/test/state/test-timer.js b/test/state/test-timer.js index 4b9ead02..29c938e8 100644 --- a/test/state/test-timer.js +++ b/test/state/test-timer.js @@ -1,21 +1,21 @@ -class TestTimer { - constructor(options, callback) { - this.options = options; - this.callback = callback; - this.isRunning = false; - } - - start() { - this.isRunning = true; - } - - pause() { - this.isRunning = false; - } - - reset(value) { - this.time = value; - } -} - -module.exports = TestTimer; +class TestTimer { + constructor(options, callback) { + this.options = options; + this.callback = callback; + this.isRunning = false; + } + + start() { + this.isRunning = true; + } + + pause() { + this.isRunning = false; + } + + reset(value) { + this.time = value; + } +} + +module.exports = TestTimer; diff --git a/test/state/timer-state.specs.js b/test/state/timer-state.specs.js index 40f64f72..07f58fca 100644 --- a/test/state/timer-state.specs.js +++ b/test/state/timer-state.specs.js @@ -1,595 +1,595 @@ -let TimerState = require("../../src/state/timer-state"); -let TestTimer = require("./test-timer"); -let assert = require("assert"); - -describe("timer-state", () => { - let timerState; - let events; - - let assertEvent = eventName => { - var event = events.find(x => x.event === eventName); - assert(event, eventName + " event not found"); - return event; - }; - - beforeEach(() => { - events = []; - timerState = new TimerState({ Timer: TestTimer }); - timerState.setCallback((event, data) => { - events.push({ event, data }); - }); - }); - - describe("initialize", () => { - beforeEach(() => timerState.initialize()); - - it("should publish a timerChange event", () => { - var event = assertEvent("timerChange"); - assert.deepStrictEqual(event.data, { - secondsRemaining: 600, - secondsPerTurn: 600 - }); - }); - - it("should publish a rotated event", () => { - var event = assertEvent("rotated"); - assert.deepStrictEqual(event.data, { current: null, next: null }); - }); - - it("should publish a turnEnded event", () => { - assertEvent("turnEnded"); - }); - - it("should publish a configUpdated event", () => { - assertEvent("configUpdated"); - }); - }); - - describe("reset", () => { - beforeEach(() => timerState.reset()); - - it("should publish a timerChange event", () => { - var event = assertEvent("timerChange"); - assert.deepStrictEqual(event.data, { - secondsRemaining: 600, - secondsPerTurn: 600 - }); - }); - }); - - describe("start", () => { - beforeEach(() => timerState.start()); - - it("should start the mainTimer", function() { - assert.strictEqual(timerState.mainTimer.isRunning, true); - }); - - it("should publish a started event", () => { - assertEvent("started"); - }); - - it("should publish a stopAlerts event", () => { - assertEvent("stopAlerts"); - }); - - it("should publish a timerChange event when the timer calls back", () => { - timerState.mainTimer.callback(599); - var event = assertEvent("timerChange"); - assert.deepStrictEqual(event.data, { - secondsRemaining: 599, - secondsPerTurn: 600 - }); - }); - - it("should publish events when the time is up", () => { - timerState.mainTimer.callback(-1); - assertEvent("turnEnded"); - assertEvent("paused"); - assertEvent("rotated"); - var alertEvent = assertEvent("alert"); - assert.strictEqual(alertEvent.data, 0); - }); - - it("should start the alertsTimer after the timer is up", () => { - assert.strictEqual(timerState.alertsTimer.isRunning, false); - timerState.mainTimer.callback(-1); - assert.strictEqual(timerState.alertsTimer.isRunning, true); - }); - - it("should publish alert events after the time is up", () => { - timerState.alertsTimer.callback(1); - var event = assertEvent("alert"); - assert.strictEqual(event.data, 1); - }); - }); - - describe("pause", () => { - beforeEach(() => timerState.pause()); - - it("should publish a paused event", () => { - assertEvent("paused"); - }); - - it("should publish a stopAlerts event", () => { - assertEvent("stopAlerts"); - }); - - it("should stop the mainTimer", () => { - timerState.start(); - assert.strictEqual(timerState.mainTimer.isRunning, true); - - timerState.pause(); - assert.strictEqual(timerState.mainTimer.isRunning, false); - }); - }); - - describe("rotate", () => { - beforeEach(() => { - timerState.addMobber({ name: "A" }); - timerState.addMobber({ name: "B" }); - timerState.addMobber({ name: "C" }); - events = []; - timerState.rotate(); - }); - - it("should publish a rotated event", () => { - var event = assertEvent("rotated"); - assert.strictEqual( - event.data.current.name, - "B", - "expected B to be current" - ); - assert.strictEqual(event.data.next.name, "C", "expected C to be next"); - }); - - it("should publish a timerChange event", () => { - var event = assertEvent("timerChange"); - assert.deepStrictEqual(event.data, { - secondsRemaining: 600, - secondsPerTurn: 600 - }); - }); - - it("should wrap around at the end of the list", () => { - events = []; - timerState.rotate(); - var event = assertEvent("rotated"); - assert.strictEqual( - event.data.current.name, - "C", - "expected C to be current" - ); - assert.strictEqual(event.data.next.name, "A", "expected A to be next"); - }); - }); - - describe("publishConfig", () => { - beforeEach(() => timerState.publishConfig()); - - it("should publish a configUpdated event", () => { - var event = assertEvent("configUpdated"); - assert.deepStrictEqual(event.data.mobbers, []); - assert.strictEqual(event.data.secondsPerTurn, 600); - assert.strictEqual(event.data.secondsUntilFullscreen, 30); - assert.strictEqual(event.data.snapThreshold, 25); - assert.strictEqual(event.data.alertSound, null); - assert.deepStrictEqual(event.data.alertSoundTimes, []); - assert.strictEqual(event.data.timerAlwaysOnTop, true); - assert.strictEqual(event.data.shuffleMobbersOnStartup, false); - }); - - it("should contain the mobbers if there are some", () => { - timerState.addMobber({ name: "A" }); - timerState.addMobber({ name: "B" }); - events = []; - - timerState.publishConfig(); - var event = assertEvent("configUpdated"); - assert.strictEqual(event.data.mobbers[0].name, "A"); - assert.strictEqual(event.data.mobbers[1].name, "B"); - - timerState.removeMobber({ name: "A" }); - timerState.removeMobber({ name: "B" }); - }); - - it("should publish a rotated event", () => { - assertEvent("rotated"); - }); - }); - - describe("addMobber", () => { - beforeEach(() => timerState.addMobber({ name: "A" })); - - it("should publish a configUpdated event", () => { - var event = assertEvent("configUpdated"); - assert.strictEqual(event.data.mobbers[0].name, "A"); - assert.strictEqual(event.data.secondsPerTurn, 600); - }); - - it("should publish a rotated event", () => { - var event = assertEvent("rotated"); - assert.strictEqual(event.data.current.name, "A"); - assert.strictEqual(event.data.next.name, "A"); - }); - }); - - describe("removeMobber", () => { - beforeEach(() => { - timerState.addMobber({ name: "A", id: "a" }); - timerState.addMobber({ name: "B", id: "b" }); - timerState.addMobber({ name: "C", id: "c" }); - events = []; - timerState.removeMobber({ name: "B", id: "b" }); - }); - - it("should publish a configUpdated event", () => { - var event = assertEvent("configUpdated"); - assert.strictEqual(event.data.mobbers[0].name, "A"); - assert.strictEqual(event.data.mobbers[1].name, "C"); - assert.strictEqual(event.data.secondsPerTurn, 600); - }); - - it("should publish a rotated event", () => { - var event = assertEvent("rotated"); - assert.strictEqual(event.data.current.name, "A"); - assert.strictEqual(event.data.next.name, "C"); - }); - - it("should NOT publish a turnEnded event if the removed user was NOT current", () => { - var event = events.find(x => x.event === "turnEnded"); - assert.strictEqual(event, undefined); - }); - - it("should publish a turnEnded event if the removed user was current", () => { - timerState.removeMobber({ name: "A" }); - assertEvent("turnEnded"); - }); - - it("should publish a timerChange event if the removed user was current", () => { - timerState.removeMobber({ name: "A" }); - assertEvent("timerChange"); - }); - - it("should publish a paused event if the removed user was current", () => { - timerState.removeMobber({ name: "A" }); - assertEvent("paused"); - }); - - it("should update correctly if the removed user was current", () => { - timerState.rotate(); - events = []; - timerState.removeMobber({ name: "C", id: "c" }); - var event = assertEvent("rotated"); - assert.strictEqual(event.data.current.name, "A"); - assert.strictEqual(event.data.next.name, "A"); - }); - }); - - describe("updateMobber", () => { - beforeEach(() => { - timerState.addMobber({ id: "a", name: "A1" }); - events = []; - timerState.updateMobber({ id: "a", name: "A2" }); - }); - - it("should publish a configUpdated event", () => { - var event = assertEvent("configUpdated"); - assert.strictEqual(event.data.mobbers[0].name, "A2"); - assert.strictEqual(event.data.secondsPerTurn, 600); - }); - - it("should update correctly if the update disabled the current mobber", () => { - timerState.addMobber({ id: "b", name: "B" }); - timerState.addMobber({ id: "c", name: "C" }); - timerState.rotate(); - events = []; - - timerState.updateMobber({ id: "b", name: "B", disabled: true }); - - assertEvent("paused"); - assertEvent("turnEnded"); - assertEvent("configUpdated"); - var rotatedEvent = assertEvent("rotated"); - assert.strictEqual(rotatedEvent.data.current.name, "C"); - assert.strictEqual(rotatedEvent.data.next.name, "A2"); - var timerChangeEvent = assertEvent("timerChange"); - assert.deepStrictEqual(timerChangeEvent.data, { - secondsRemaining: 600, - secondsPerTurn: 600 - }); - }); - }); - - describe("shuffleMobbers", () => { - beforeEach(() => { - const letters = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]; - letters.forEach(x => timerState.addMobber({ id: x })); - events = []; - timerState.shuffleMobbers(); - }); - - it("should publish a configUpdated event", () => - assertEvent("configUpdated")); - - it("should publish a rotated event", () => assertEvent("rotated")); - - it("should shuffle the mobbers", () => { - const mobbers = timerState - .getState() - .mobbers.map(x => x.id) - .join(""); - assert.notStrictEqual(mobbers, "abcdefghij"); - }); - }); - - describe("setSecondsPerTurn", () => { - beforeEach(() => timerState.setSecondsPerTurn(300)); - - it("should publish a configUpdated event", () => { - var event = assertEvent("configUpdated"); - assert.strictEqual(event.data.secondsPerTurn, 300); - }); - - it("should publish a timerChange event", () => { - var event = assertEvent("timerChange"); - assert.deepStrictEqual(event.data, { - secondsRemaining: 300, - secondsPerTurn: 300 - }); - }); - }); - - describe("setSecondsUntilFullscreen", () => { - beforeEach(() => timerState.setSecondsUntilFullscreen(5)); - - it("should publish a configUpdated event", () => { - var event = assertEvent("configUpdated"); - assert.strictEqual(event.data.secondsUntilFullscreen, 5); - }); - }); - - describe("when setting snap threshold", () => { - beforeEach(() => timerState.setSnapThreshold(100)); - - it("should publish configUpdated event", () => { - var event = assertEvent("configUpdated"); - assert.strictEqual(event.data.snapThreshold, 100); - }); - }); - - describe("when setting the alert sound file", () => { - beforeEach(() => timerState.setAlertSound("new-sound.mp3")); - - it("should publish a configUpdated event", () => { - var event = assertEvent("configUpdated"); - assert.strictEqual(event.data.alertSound, "new-sound.mp3"); - }); - }); - - describe("when setting the alert sound times", () => { - beforeEach(() => timerState.setAlertSoundTimes([1, 2, 3])); - - it("should publish a configUpdated event", () => { - var event = assertEvent("configUpdated"); - assert.deepStrictEqual(event.data.alertSoundTimes, [1, 2, 3]); - }); - }); - - describe("when setting the timer always on top", () => { - beforeEach(() => timerState.setTimerAlwaysOnTop(false)); - - it("should publish a configUpdated event", () => { - var event = assertEvent("configUpdated"); - assert.deepStrictEqual(event.data.timerAlwaysOnTop, false); - }); - }); - - describe("when setting shuffle mobbers on startup", () => { - beforeEach(() => timerState.setShuffleMobbersOnStartup(true)); - - it("should publish a configUpdated event", () => { - var event = assertEvent("configUpdated"); - assert.deepStrictEqual(event.data.shuffleMobbersOnStartup, true); - }); - }); - - describe("getState", () => { - describe("when getting non-default state", () => { - beforeEach(() => { - timerState.addMobber(expectedJack); - timerState.addMobber(expectedJill); - timerState.setSecondsPerTurn(expectedSecondsPerTurn); - timerState.setSecondsUntilFullscreen(expectedSecondsUntilFullscreen); - timerState.setSnapThreshold(expectedSnapThreshold); - timerState.setAlertSound(expectedAlertSound); - timerState.setAlertSoundTimes(expectedAlertSoundTimes); - timerState.setTimerAlwaysOnTop(expectedTimerAlwaysOnTop); - timerState.setShuffleMobbersOnStartup(expectedShuffleMobbersOnStartup); - - result = timerState.getState(); - }); - - it("should get correct mobbers", () => { - var actualJack = result.mobbers.find(x => x.name === expectedJack.name); - var actualJill = result.mobbers.find(x => x.name === expectedJill.name); - - assert.deepStrictEqual(expectedJack, actualJack); - assert.deepStrictEqual(expectedJill, actualJill); - }); - - it("should get correct seconds per turn", () => { - assert.strictEqual(result.secondsPerTurn, expectedSecondsPerTurn); - }); - - it("should get the correct seconds until fullscreen", () => { - assert.strictEqual( - result.secondsUntilFullscreen, - expectedSecondsUntilFullscreen - ); - }); - - it("should get the correct seconds until fullscreen", () => { - assert.strictEqual(result.snapThreshold, expectedSnapThreshold); - }); - - it("should get the correct alert sound", () => { - assert.strictEqual(result.alertSound, expectedAlertSound); - }); - - it("should get the correct alert sound times", () => { - assert.strictEqual(result.alertSoundTimes, expectedAlertSoundTimes); - }); - - it("should get the correct timer always on top", () => { - assert.strictEqual(result.timerAlwaysOnTop, expectedTimerAlwaysOnTop); - }); - - it("should get the correct shuffle mobbers on startup", () => { - assert.strictEqual( - result.shuffleMobbersOnStartup, - expectedShuffleMobbersOnStartup - ); - }); - - let result = {}; - let expectedJack = { name: "jack" }; - let expectedJill = { name: "jill" }; - let expectedSecondsPerTurn = 599; - let expectedSecondsUntilFullscreen = 3; - let expectedSnapThreshold = 42; - let expectedAlertSound = "alert.mp3"; - let expectedAlertSoundTimes = [0, 15]; - let expectedTimerAlwaysOnTop = false; - let expectedShuffleMobbersOnStartup = true; - }); - - describe("when getting default state", () => { - beforeEach(() => (result = timerState.getState())); - - it("should get no mobbers", () => assert(result.mobbers.length === 0)); - it("should have a default secondsPerTurn greater than zero", () => - assert(result.secondsPerTurn > 0)); - it("should have a default snapThreshold greater than zero", () => - assert(result.snapThreshold > 0)); - it("should have a null alert sound", () => - assert(result.alertSound === null)); - it("should have an empty array of alert sound times", () => - assert.deepStrictEqual(result.alertSoundTimes, [])); - it("should have a default timerAlwaysOnTop", () => - assert.deepStrictEqual(result.timerAlwaysOnTop, true)); - it("should have a default shuffleMobbersOnStartup", () => - assert.strictEqual(result.shuffleMobbersOnStartup, false)); - - let result = {}; - }); - - describe("when there is one mobber", () => { - before(() => { - timerState.addMobber(expectedJack); - - result = timerState.getState(); - }); - - it("should get correct mobber", () => { - var actualJack = result.mobbers.find(x => x.name === expectedJack.name); - - assert.deepStrictEqual(expectedJack, actualJack); - }); - - let result = {}; - let expectedJack = { name: "jack" }; - }); - }); - - describe("loadState", () => { - describe("when loading state data", () => { - before(() => { - state = { - mobbers: [{ name: "jack" }, { name: "jill" }], - secondsPerTurn: 400, - secondsUntilFullscreen: 0, - snapThreshold: 22, - alertSound: "bell.mp3", - alertSoundTimes: [2, 3, 5, 8], - timerAlwaysOnTop: false, - shuffleMobbersOnStartup: true - }; - - timerState.loadState(state); - - result = timerState.getState(); - }); - - it("should load mobbers", () => - assert.deepStrictEqual(result.mobbers, state.mobbers)); - it("should load secondsPerTurn", () => - assert.strictEqual(result.secondsPerTurn, state.secondsPerTurn)); - it("should load secondsUntilFullscreen", () => - assert.strictEqual( - result.secondsUntilFullscreen, - state.secondsUntilFullscreen - )); - it("should load snapThreshold", () => - assert.strictEqual(result.snapThreshold, state.snapThreshold)); - it("should load alertSound", () => - assert.strictEqual(result.alertSound, state.alertSound)); - it("should load alertSoundTimes", () => - assert.deepStrictEqual(result.alertSoundTimes, [2, 3, 5, 8])); - it("should load timerAlwaysOnTop", () => - assert.strictEqual(result.timerAlwaysOnTop, state.timerAlwaysOnTop)); - it("should load shuffleMobbersOnStartup", () => - assert.strictEqual( - result.shuffleMobbersOnStartup, - state.shuffleMobbersOnStartup - )); - - let result = {}; - let state = {}; - }); - - describe("when loading an empty state", () => { - before(() => { - timerState.loadState({}); - - result = timerState.getState(); - }); - - it("should NOT load any mobbers", () => - assert.strictEqual(result.mobbers.length, 0)); - it("should have a default secondsPerTurn greater than zero", () => - assert(result.secondsPerTurn > 0)); - it("should have a default secondsUntilFullscreen greater than zero", () => - assert(result.secondsUntilFullscreen > 0)); - it("should have a default snapThreshold greater than zero", () => - assert(result.snapThreshold > 0)); - it("should have a null alertSound", () => - assert.strictEqual(result.alertSound, null)); - it("should have an empty array of alertSoundTimes", () => - assert.deepStrictEqual(result.alertSoundTimes, [])); - it("should have a default timerAlwaysOnTop", () => - assert.strictEqual(result.timerAlwaysOnTop, true)); - it("should have a default shuffleMobbersOnStartup", () => - assert.strictEqual(result.shuffleMobbersOnStartup, false)); - - let result = {}; - }); - - describe("when loading state with one mobber", () => { - before(() => { - state = { - mobbers: [{ name: "jack" }] - }; - - timerState.loadState(state); - - result = timerState.getState(); - }); - - it("should load one mobber", () => - assert.deepStrictEqual(state.mobbers, result.mobbers)); - - let result = {}; - let state = {}; - }); - }); -}); +let TimerState = require("../../src/state/timer-state"); +let TestTimer = require("./test-timer"); +let assert = require("assert"); + +describe("timer-state", () => { + let timerState; + let events; + + let assertEvent = eventName => { + var event = events.find(x => x.event === eventName); + assert(event, eventName + " event not found"); + return event; + }; + + beforeEach(() => { + events = []; + timerState = new TimerState({ Timer: TestTimer }); + timerState.setCallback((event, data) => { + events.push({ event, data }); + }); + }); + + describe("initialize", () => { + beforeEach(() => timerState.initialize()); + + it("should publish a timerChange event", () => { + var event = assertEvent("timerChange"); + assert.deepStrictEqual(event.data, { + secondsRemaining: 600, + secondsPerTurn: 600 + }); + }); + + it("should publish a rotated event", () => { + var event = assertEvent("rotated"); + assert.deepStrictEqual(event.data, { current: null, next: null }); + }); + + it("should publish a turnEnded event", () => { + assertEvent("turnEnded"); + }); + + it("should publish a configUpdated event", () => { + assertEvent("configUpdated"); + }); + }); + + describe("reset", () => { + beforeEach(() => timerState.reset()); + + it("should publish a timerChange event", () => { + var event = assertEvent("timerChange"); + assert.deepStrictEqual(event.data, { + secondsRemaining: 600, + secondsPerTurn: 600 + }); + }); + }); + + describe("start", () => { + beforeEach(() => timerState.start()); + + it("should start the mainTimer", function() { + assert.strictEqual(timerState.mainTimer.isRunning, true); + }); + + it("should publish a started event", () => { + assertEvent("started"); + }); + + it("should publish a stopAlerts event", () => { + assertEvent("stopAlerts"); + }); + + it("should publish a timerChange event when the timer calls back", () => { + timerState.mainTimer.callback(599); + var event = assertEvent("timerChange"); + assert.deepStrictEqual(event.data, { + secondsRemaining: 599, + secondsPerTurn: 600 + }); + }); + + it("should publish events when the time is up", () => { + timerState.mainTimer.callback(-1); + assertEvent("turnEnded"); + assertEvent("paused"); + assertEvent("rotated"); + var alertEvent = assertEvent("alert"); + assert.strictEqual(alertEvent.data, 0); + }); + + it("should start the alertsTimer after the timer is up", () => { + assert.strictEqual(timerState.alertsTimer.isRunning, false); + timerState.mainTimer.callback(-1); + assert.strictEqual(timerState.alertsTimer.isRunning, true); + }); + + it("should publish alert events after the time is up", () => { + timerState.alertsTimer.callback(1); + var event = assertEvent("alert"); + assert.strictEqual(event.data, 1); + }); + }); + + describe("pause", () => { + beforeEach(() => timerState.pause()); + + it("should publish a paused event", () => { + assertEvent("paused"); + }); + + it("should publish a stopAlerts event", () => { + assertEvent("stopAlerts"); + }); + + it("should stop the mainTimer", () => { + timerState.start(); + assert.strictEqual(timerState.mainTimer.isRunning, true); + + timerState.pause(); + assert.strictEqual(timerState.mainTimer.isRunning, false); + }); + }); + + describe("rotate", () => { + beforeEach(() => { + timerState.addMobber({ name: "A" }); + timerState.addMobber({ name: "B" }); + timerState.addMobber({ name: "C" }); + events = []; + timerState.rotate(); + }); + + it("should publish a rotated event", () => { + var event = assertEvent("rotated"); + assert.strictEqual( + event.data.current.name, + "B", + "expected B to be current" + ); + assert.strictEqual(event.data.next.name, "C", "expected C to be next"); + }); + + it("should publish a timerChange event", () => { + var event = assertEvent("timerChange"); + assert.deepStrictEqual(event.data, { + secondsRemaining: 600, + secondsPerTurn: 600 + }); + }); + + it("should wrap around at the end of the list", () => { + events = []; + timerState.rotate(); + var event = assertEvent("rotated"); + assert.strictEqual( + event.data.current.name, + "C", + "expected C to be current" + ); + assert.strictEqual(event.data.next.name, "A", "expected A to be next"); + }); + }); + + describe("publishConfig", () => { + beforeEach(() => timerState.publishConfig()); + + it("should publish a configUpdated event", () => { + var event = assertEvent("configUpdated"); + assert.deepStrictEqual(event.data.mobbers, []); + assert.strictEqual(event.data.secondsPerTurn, 600); + assert.strictEqual(event.data.secondsUntilFullscreen, 30); + assert.strictEqual(event.data.snapThreshold, 25); + assert.strictEqual(event.data.alertSound, null); + assert.deepStrictEqual(event.data.alertSoundTimes, []); + assert.strictEqual(event.data.timerAlwaysOnTop, true); + assert.strictEqual(event.data.shuffleMobbersOnStartup, false); + }); + + it("should contain the mobbers if there are some", () => { + timerState.addMobber({ name: "A" }); + timerState.addMobber({ name: "B" }); + events = []; + + timerState.publishConfig(); + var event = assertEvent("configUpdated"); + assert.strictEqual(event.data.mobbers[0].name, "A"); + assert.strictEqual(event.data.mobbers[1].name, "B"); + + timerState.removeMobber({ name: "A" }); + timerState.removeMobber({ name: "B" }); + }); + + it("should publish a rotated event", () => { + assertEvent("rotated"); + }); + }); + + describe("addMobber", () => { + beforeEach(() => timerState.addMobber({ name: "A" })); + + it("should publish a configUpdated event", () => { + var event = assertEvent("configUpdated"); + assert.strictEqual(event.data.mobbers[0].name, "A"); + assert.strictEqual(event.data.secondsPerTurn, 600); + }); + + it("should publish a rotated event", () => { + var event = assertEvent("rotated"); + assert.strictEqual(event.data.current.name, "A"); + assert.strictEqual(event.data.next.name, "A"); + }); + }); + + describe("removeMobber", () => { + beforeEach(() => { + timerState.addMobber({ name: "A", id: "a" }); + timerState.addMobber({ name: "B", id: "b" }); + timerState.addMobber({ name: "C", id: "c" }); + events = []; + timerState.removeMobber({ name: "B", id: "b" }); + }); + + it("should publish a configUpdated event", () => { + var event = assertEvent("configUpdated"); + assert.strictEqual(event.data.mobbers[0].name, "A"); + assert.strictEqual(event.data.mobbers[1].name, "C"); + assert.strictEqual(event.data.secondsPerTurn, 600); + }); + + it("should publish a rotated event", () => { + var event = assertEvent("rotated"); + assert.strictEqual(event.data.current.name, "A"); + assert.strictEqual(event.data.next.name, "C"); + }); + + it("should NOT publish a turnEnded event if the removed user was NOT current", () => { + var event = events.find(x => x.event === "turnEnded"); + assert.strictEqual(event, undefined); + }); + + it("should publish a turnEnded event if the removed user was current", () => { + timerState.removeMobber({ name: "A" }); + assertEvent("turnEnded"); + }); + + it("should publish a timerChange event if the removed user was current", () => { + timerState.removeMobber({ name: "A" }); + assertEvent("timerChange"); + }); + + it("should publish a paused event if the removed user was current", () => { + timerState.removeMobber({ name: "A" }); + assertEvent("paused"); + }); + + it("should update correctly if the removed user was current", () => { + timerState.rotate(); + events = []; + timerState.removeMobber({ name: "C", id: "c" }); + var event = assertEvent("rotated"); + assert.strictEqual(event.data.current.name, "A"); + assert.strictEqual(event.data.next.name, "A"); + }); + }); + + describe("updateMobber", () => { + beforeEach(() => { + timerState.addMobber({ id: "a", name: "A1" }); + events = []; + timerState.updateMobber({ id: "a", name: "A2" }); + }); + + it("should publish a configUpdated event", () => { + var event = assertEvent("configUpdated"); + assert.strictEqual(event.data.mobbers[0].name, "A2"); + assert.strictEqual(event.data.secondsPerTurn, 600); + }); + + it("should update correctly if the update disabled the current mobber", () => { + timerState.addMobber({ id: "b", name: "B" }); + timerState.addMobber({ id: "c", name: "C" }); + timerState.rotate(); + events = []; + + timerState.updateMobber({ id: "b", name: "B", disabled: true }); + + assertEvent("paused"); + assertEvent("turnEnded"); + assertEvent("configUpdated"); + var rotatedEvent = assertEvent("rotated"); + assert.strictEqual(rotatedEvent.data.current.name, "C"); + assert.strictEqual(rotatedEvent.data.next.name, "A2"); + var timerChangeEvent = assertEvent("timerChange"); + assert.deepStrictEqual(timerChangeEvent.data, { + secondsRemaining: 600, + secondsPerTurn: 600 + }); + }); + }); + + describe("shuffleMobbers", () => { + beforeEach(() => { + const letters = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]; + letters.forEach(x => timerState.addMobber({ id: x })); + events = []; + timerState.shuffleMobbers(); + }); + + it("should publish a configUpdated event", () => + assertEvent("configUpdated")); + + it("should publish a rotated event", () => assertEvent("rotated")); + + it("should shuffle the mobbers", () => { + const mobbers = timerState + .getState() + .mobbers.map(x => x.id) + .join(""); + assert.notStrictEqual(mobbers, "abcdefghij"); + }); + }); + + describe("setSecondsPerTurn", () => { + beforeEach(() => timerState.setSecondsPerTurn(300)); + + it("should publish a configUpdated event", () => { + var event = assertEvent("configUpdated"); + assert.strictEqual(event.data.secondsPerTurn, 300); + }); + + it("should publish a timerChange event", () => { + var event = assertEvent("timerChange"); + assert.deepStrictEqual(event.data, { + secondsRemaining: 300, + secondsPerTurn: 300 + }); + }); + }); + + describe("setSecondsUntilFullscreen", () => { + beforeEach(() => timerState.setSecondsUntilFullscreen(5)); + + it("should publish a configUpdated event", () => { + var event = assertEvent("configUpdated"); + assert.strictEqual(event.data.secondsUntilFullscreen, 5); + }); + }); + + describe("when setting snap threshold", () => { + beforeEach(() => timerState.setSnapThreshold(100)); + + it("should publish configUpdated event", () => { + var event = assertEvent("configUpdated"); + assert.strictEqual(event.data.snapThreshold, 100); + }); + }); + + describe("when setting the alert sound file", () => { + beforeEach(() => timerState.setAlertSound("new-sound.mp3")); + + it("should publish a configUpdated event", () => { + var event = assertEvent("configUpdated"); + assert.strictEqual(event.data.alertSound, "new-sound.mp3"); + }); + }); + + describe("when setting the alert sound times", () => { + beforeEach(() => timerState.setAlertSoundTimes([1, 2, 3])); + + it("should publish a configUpdated event", () => { + var event = assertEvent("configUpdated"); + assert.deepStrictEqual(event.data.alertSoundTimes, [1, 2, 3]); + }); + }); + + describe("when setting the timer always on top", () => { + beforeEach(() => timerState.setTimerAlwaysOnTop(false)); + + it("should publish a configUpdated event", () => { + var event = assertEvent("configUpdated"); + assert.deepStrictEqual(event.data.timerAlwaysOnTop, false); + }); + }); + + describe("when setting shuffle mobbers on startup", () => { + beforeEach(() => timerState.setShuffleMobbersOnStartup(true)); + + it("should publish a configUpdated event", () => { + var event = assertEvent("configUpdated"); + assert.deepStrictEqual(event.data.shuffleMobbersOnStartup, true); + }); + }); + + describe("getState", () => { + describe("when getting non-default state", () => { + beforeEach(() => { + timerState.addMobber(expectedJack); + timerState.addMobber(expectedJill); + timerState.setSecondsPerTurn(expectedSecondsPerTurn); + timerState.setSecondsUntilFullscreen(expectedSecondsUntilFullscreen); + timerState.setSnapThreshold(expectedSnapThreshold); + timerState.setAlertSound(expectedAlertSound); + timerState.setAlertSoundTimes(expectedAlertSoundTimes); + timerState.setTimerAlwaysOnTop(expectedTimerAlwaysOnTop); + timerState.setShuffleMobbersOnStartup(expectedShuffleMobbersOnStartup); + + result = timerState.getState(); + }); + + it("should get correct mobbers", () => { + var actualJack = result.mobbers.find(x => x.name === expectedJack.name); + var actualJill = result.mobbers.find(x => x.name === expectedJill.name); + + assert.deepStrictEqual(expectedJack, actualJack); + assert.deepStrictEqual(expectedJill, actualJill); + }); + + it("should get correct seconds per turn", () => { + assert.strictEqual(result.secondsPerTurn, expectedSecondsPerTurn); + }); + + it("should get the correct seconds until fullscreen", () => { + assert.strictEqual( + result.secondsUntilFullscreen, + expectedSecondsUntilFullscreen + ); + }); + + it("should get the correct seconds until fullscreen", () => { + assert.strictEqual(result.snapThreshold, expectedSnapThreshold); + }); + + it("should get the correct alert sound", () => { + assert.strictEqual(result.alertSound, expectedAlertSound); + }); + + it("should get the correct alert sound times", () => { + assert.strictEqual(result.alertSoundTimes, expectedAlertSoundTimes); + }); + + it("should get the correct timer always on top", () => { + assert.strictEqual(result.timerAlwaysOnTop, expectedTimerAlwaysOnTop); + }); + + it("should get the correct shuffle mobbers on startup", () => { + assert.strictEqual( + result.shuffleMobbersOnStartup, + expectedShuffleMobbersOnStartup + ); + }); + + let result = {}; + let expectedJack = { name: "jack" }; + let expectedJill = { name: "jill" }; + let expectedSecondsPerTurn = 599; + let expectedSecondsUntilFullscreen = 3; + let expectedSnapThreshold = 42; + let expectedAlertSound = "alert.mp3"; + let expectedAlertSoundTimes = [0, 15]; + let expectedTimerAlwaysOnTop = false; + let expectedShuffleMobbersOnStartup = true; + }); + + describe("when getting default state", () => { + beforeEach(() => (result = timerState.getState())); + + it("should get no mobbers", () => assert(result.mobbers.length === 0)); + it("should have a default secondsPerTurn greater than zero", () => + assert(result.secondsPerTurn > 0)); + it("should have a default snapThreshold greater than zero", () => + assert(result.snapThreshold > 0)); + it("should have a null alert sound", () => + assert(result.alertSound === null)); + it("should have an empty array of alert sound times", () => + assert.deepStrictEqual(result.alertSoundTimes, [])); + it("should have a default timerAlwaysOnTop", () => + assert.deepStrictEqual(result.timerAlwaysOnTop, true)); + it("should have a default shuffleMobbersOnStartup", () => + assert.strictEqual(result.shuffleMobbersOnStartup, false)); + + let result = {}; + }); + + describe("when there is one mobber", () => { + before(() => { + timerState.addMobber(expectedJack); + + result = timerState.getState(); + }); + + it("should get correct mobber", () => { + var actualJack = result.mobbers.find(x => x.name === expectedJack.name); + + assert.deepStrictEqual(expectedJack, actualJack); + }); + + let result = {}; + let expectedJack = { name: "jack" }; + }); + }); + + describe("loadState", () => { + describe("when loading state data", () => { + before(() => { + state = { + mobbers: [{ name: "jack" }, { name: "jill" }], + secondsPerTurn: 400, + secondsUntilFullscreen: 0, + snapThreshold: 22, + alertSound: "bell.mp3", + alertSoundTimes: [2, 3, 5, 8], + timerAlwaysOnTop: false, + shuffleMobbersOnStartup: true + }; + + timerState.loadState(state); + + result = timerState.getState(); + }); + + it("should load mobbers", () => + assert.deepStrictEqual(result.mobbers, state.mobbers)); + it("should load secondsPerTurn", () => + assert.strictEqual(result.secondsPerTurn, state.secondsPerTurn)); + it("should load secondsUntilFullscreen", () => + assert.strictEqual( + result.secondsUntilFullscreen, + state.secondsUntilFullscreen + )); + it("should load snapThreshold", () => + assert.strictEqual(result.snapThreshold, state.snapThreshold)); + it("should load alertSound", () => + assert.strictEqual(result.alertSound, state.alertSound)); + it("should load alertSoundTimes", () => + assert.deepStrictEqual(result.alertSoundTimes, [2, 3, 5, 8])); + it("should load timerAlwaysOnTop", () => + assert.strictEqual(result.timerAlwaysOnTop, state.timerAlwaysOnTop)); + it("should load shuffleMobbersOnStartup", () => + assert.strictEqual( + result.shuffleMobbersOnStartup, + state.shuffleMobbersOnStartup + )); + + let result = {}; + let state = {}; + }); + + describe("when loading an empty state", () => { + before(() => { + timerState.loadState({}); + + result = timerState.getState(); + }); + + it("should NOT load any mobbers", () => + assert.strictEqual(result.mobbers.length, 0)); + it("should have a default secondsPerTurn greater than zero", () => + assert(result.secondsPerTurn > 0)); + it("should have a default secondsUntilFullscreen greater than zero", () => + assert(result.secondsUntilFullscreen > 0)); + it("should have a default snapThreshold greater than zero", () => + assert(result.snapThreshold > 0)); + it("should have a null alertSound", () => + assert.strictEqual(result.alertSound, null)); + it("should have an empty array of alertSoundTimes", () => + assert.deepStrictEqual(result.alertSoundTimes, [])); + it("should have a default timerAlwaysOnTop", () => + assert.strictEqual(result.timerAlwaysOnTop, true)); + it("should have a default shuffleMobbersOnStartup", () => + assert.strictEqual(result.shuffleMobbersOnStartup, false)); + + let result = {}; + }); + + describe("when loading state with one mobber", () => { + before(() => { + state = { + mobbers: [{ name: "jack" }] + }; + + timerState.loadState(state); + + result = timerState.getState(); + }); + + it("should load one mobber", () => + assert.deepStrictEqual(state.mobbers, result.mobbers)); + + let result = {}; + let state = {}; + }); + }); +});