Source: control/main.js

/**
 * @module Main
 */

import PomoAudio from '../components/pomo-audio.js';
import PomoFinish from '../components/pomo-finish/pomo-finish.js';
import PomoInfo from '../components/pomo-info/pomo-info.js';
import PomoSettings from '../components/pomo-settings/pomo-settings.js';
import * as PomoStorage from '../components/pomo-storage.js';
import * as PomoTab from '../components/pomo-tab.js';
import PomoTimer from '../components/pomo-timer/pomo-timer.js';

/**
 * Initialize page styling
 */
const stylesheet = document.createElement('link');
stylesheet.rel = 'stylesheet';
stylesheet.href = './index.css';
stylesheet.setAttribute('href', './index.css');
document.head.appendChild(stylesheet);

/**
 * Initialize web components
 * Attach to window for method access and Cypress testing
 */
const pomoAudio = new PomoAudio();
pomoAudio.setAttribute('id', 'pomo-audio');
window.pomoAudio = pomoAudio;

const pomoFinish = new PomoFinish();
pomoFinish.setAttribute('id', 'pomo-finish');
window.pomoFinish = pomoFinish;

const pomoInfo = new PomoInfo();
pomoInfo.setAttribute('id', 'pomo-info');
window.pomoInfo = pomoInfo;

const pomoSettings = new PomoSettings();
pomoSettings.setAttribute('id', 'pomo-settings');
window.pomoSettings = pomoSettings;

window.pomoStorage = PomoStorage;

window.pomoTab = PomoTab;

const pomoTimer = new PomoTimer();
pomoTimer.setAttribute('id', 'pomo-timer');
window.pomoTimer = pomoTimer;

/**
 * Add components to their proper locations on the page
 */
const body = document.getElementById('body');
body.appendChild(pomoAudio);

const header = document.getElementById('header');
header.appendChild(pomoSettings);
header.appendChild(pomoInfo);

const main = document.getElementById('main');
main.appendChild(pomoTimer);

const footer = document.getElementById('footer');
footer.appendChild(pomoFinish);

/**
 * Use Mode as an enum to prevent reuse of strings
 */
let currentMode = '';
const Mode = {
  work: 'work',
  short: 'short break',
  long: 'long break',
};

/**
 * Track the length of each mode
 * Variables are initialized in the onLoad method
 */
let workLength = 1;
let shortLength = 1;
let longLength = 1;

/**
 * Track the counts of each mode, including interrupted work
 * Variables are initialized in the onLoad method
 */
let workCount = 0;
let shortCount = 0;
let longCount = 0;
let interruptCount = 0;

/**
 * Track if accessibility is currently enabled
 * Used to re-activate components after a modal has been closed
 */
let accessibility = true;

/**
 * @function
 * Enable all components to be accessed
 * Used when timer finishes running
 */
function enableAll() {
  pomoFinish.enableFinish();
  pomoInfo.enableInfo();
  pomoSettings.enableSettings();
}

/**
 * @function
 * Disable all components to be accessed
 * Used when timer starts running
 */
function disableAll() {
  pomoFinish.disableFinish();
  pomoInfo.disableInfo();
  pomoSettings.disableSettings();
}

/* ---------------------------------------------------------------------------------------------- */
/* PomoTimer Event Functions */

/**
 * When a work session is started, disable other components
 */
pomoTimer.addEventListener('timerStart', () => {
  if (currentMode === Mode.work) {
    disableAll();
  }
});

/**
 * When the timer value changes, update the tab as well
 */
pomoTimer.addEventListener('tick', (event) => {
  PomoTab.setTab(event.detail.timeRemaining(), currentMode);
});

/**
 * If a work session is reset, add to the interrupt count and store it
 * Enable other components and reset the tab for all modes being reset
 */
pomoTimer.addEventListener('timerReset', () => {
  if (currentMode === Mode.work) {
    interruptCount += 1;
    PomoStorage.setDayCounts(workCount, shortCount, longCount, interruptCount);
  }

  enableAll();
  PomoTab.defaultTab();
});

/**
 * When the timer finishes, update counts based on the current mode completed
 * Determine the next mode based on workCount and previously completed mode
 */
pomoTimer.addEventListener('timerFinish', () => {
  switch (currentMode) {
    case Mode.work:
      workCount += 1;
      // Start a long break every 4 work sessions completed, but not when 0 sessions are complete
      if (workCount !== 0 && workCount % 4 === 0) {
        pomoTimer.setProgress(4);
        currentMode = Mode.long;
        pomoTimer.setTimer(longLength, Mode.long);
        PomoStorage.setMode(Mode.long);
      } else {
        pomoTimer.setProgress(workCount % 4);
        currentMode = Mode.short;
        pomoTimer.setTimer(shortLength, Mode.short);
        PomoStorage.setMode(Mode.short);
      }
      break;

    case Mode.short:
      shortCount += 1;
      currentMode = Mode.work;
      pomoTimer.setTimer(workLength, Mode.work);
      PomoStorage.setMode(Mode.work);
      break;

    case Mode.long:
      pomoTimer.setProgress(0);
      longCount += 1;
      currentMode = Mode.work;
      pomoTimer.setTimer(workLength, Mode.work);
      PomoStorage.setMode(Mode.work);
      break;

    default:
      break;
  }

  PomoStorage.setDayCounts(workCount, shortCount, longCount, interruptCount);

  pomoAudio.playSound();

  enableAll();
});

/* ---------------------------------------------------------------------------------------------- */
/* PomoFinish Event Functions */

/**
 * When the finish modal is requested, provide the current counts and display the modal
 */
pomoFinish.addEventListener('modalRequest', () => {
  pomoFinish.showModal(workCount, shortCount, longCount, interruptCount);
});

/* ---------------------------------------------------------------------------------------------- */
/* PomoFinish Settings Functions */

/**
 * @function
 * When the work length is changed, update the control variable
 * Update timer if it is currently starting a work session
 */
function workSet(work) {
  workLength = work;

  if (currentMode === Mode.work) {
    pomoTimer.setTimer(work, currentMode);
  }
}

/**
 * Wire up previous method to the settings value being changed
 */
pomoSettings.addEventListener('workSet', (event) => {
  const work = event.detail.work();
  PomoStorage.setWork(work);

  workSet(work);
});

/**
 * @function
 * When the short break length is changed, update the control variable
 * Update timer if it is currently starting a short break
 */
function shortBreakSet(shortBreak) {
  shortLength = shortBreak;

  if (currentMode === Mode.short) {
    pomoTimer.setTimer(shortBreak, currentMode);
  }
}

/**
 * Wire up previous method to the settings value being changed
 */
pomoSettings.addEventListener('shortBreakSet', (event) => {
  const shortBreak = event.detail.shortBreak();
  PomoStorage.setShortBreak(shortBreak);

  shortBreakSet(shortBreak);
});

/**
 * @function
 * When the long break length is changed, update the control variable
 * Update timer if it is currently starting a long break
 */
function longBreakSet(longBreak) {
  longLength = longBreak;

  if (currentMode === Mode.long) {
    pomoTimer.setTimer(longBreak, currentMode);
  }
}

/**
 * Wire up previous method to the settings value being changed
 */
pomoSettings.addEventListener('longBreakSet', (event) => {
  const longBreak = event.detail.longBreak();
  PomoStorage.setLongBreak(longBreak);

  longBreakSet(longBreak);
});

/**
 * @function
 * Update the audio component when the volume is changed
 */
function volumeSet(volume) {
  pomoAudio.setVolume(volume);
}

/**
 * Wire up previous method to the settings value being changed
 */
pomoSettings.addEventListener('volumeSet', (event) => {
  const volume = event.detail.volume();
  PomoStorage.setVolume(volume);

  volumeSet(volume);
});

/**
 * @function
 * Update the audio component when the sound source is changed
 */
function soundSet(sound) {
  pomoAudio.setSound(sound);
}

/**
 * Wire up previous method to the settings value being changed
 */
pomoSettings.addEventListener('soundSet', (event) => {
  const sound = event.detail.sound();
  PomoStorage.setSound(sound);

  soundSet(sound);
});

/**
 * @function
 * Update the necessary components when the calm mode changes
 */
function calmSet(calm) {
  pomoTimer.setCalm(calm);
  PomoTab.setCalm(calm);
}

/**
 * Wire up previous method to the settings value being changed
 */
pomoSettings.addEventListener('calmSet', (event) => {
  const calm = event.detail.calm();
  PomoStorage.setCalm(calm);

  calmSet(calm);
});

/**
 * @function
 * Update the necessary components when the dark mode changes
 */
function darkSet(dark) {
  pomoFinish.setDark(dark);
  pomoInfo.setDark(dark);
  pomoSettings.setDark(dark);
  pomoTimer.setDark(dark);

  if (dark) {
    stylesheet.setAttribute('href', './index.css');
  } else {
    stylesheet.setAttribute('href', './index-light.css');
  }
}

/**
 * Wire up previous method to the settings value being changed
 */
pomoSettings.addEventListener('darkSet', (event) => {
  const dark = event.detail.dark();
  PomoStorage.setDark(dark);
  darkSet(dark);
});

/**
 * @function
 * Update the necessary components when the accessibility mode changes
 */
function accessibleSet(accessible) {
  pomoFinish.setAccessibility(accessible);
  pomoInfo.setAccessibility(accessible);
  pomoSettings.setAccessibility(accessible);
  pomoTimer.setAccessibility(accessible);

  accessibility = accessible;
}

/**
 * Wire up previous method to the settings value being changed
 */
pomoSettings.addEventListener('accessSet', (event) => {
  const accessible = event.detail.accessible();
  PomoStorage.setAccessibility(accessible);
  accessibleSet(accessible);
});

/* ---------------------------------------------------------------------------------------------- */
/* Accessibility Functions */

/**
 * Disable info, settings, and timer accessibility when finish is opened
 */
pomoFinish.addEventListener('openEvent', () => {
  pomoInfo.disableInfo();
  pomoSettings.setAccessibility(false);
  pomoTimer.disableTimer();
});

/**
 * Enable info, settings, and timer accessibility when finish is closed
 */
pomoFinish.addEventListener('closeEvent', () => {
  pomoInfo.enableInfo();
  pomoSettings.setAccessibility(accessibility);
  pomoTimer.enableTimer();
});

/**
 * Disable finish, settings, and timer accessibility when info is opened
 */
pomoInfo.addEventListener('openEvent', () => {
  pomoFinish.disableFinish();
  pomoSettings.setAccessibility(false);
  pomoTimer.disableTimer();
});

/**
 * Enable finish, settings, and timer accessibility when info is closed
 */
pomoInfo.addEventListener('closeEvent', () => {
  pomoFinish.enableFinish();
  pomoSettings.setAccessibility(accessibility);
  pomoTimer.enableTimer();
});

/**
 * Disable finish, info, and timer accessibility when settings is opened
 */
pomoSettings.addEventListener('openEvent', () => {
  if (pomoSettings.enabled) {
    pomoFinish.disableFinish();
    pomoInfo.disableInfo();
    pomoTimer.disableTimer();
  }
});

/**
 * Enable finish, info, and timer accessibility when settings is closed
 */
pomoSettings.addEventListener('closeEvent', () => {
  if (pomoSettings.enabled) {
    pomoFinish.enableFinish();
    pomoInfo.enableInfo();
    pomoTimer.enableTimer();
  }
});

/**
 * @function
 * Load preferences from storage and update the control and settings to reflect those values
 */
function onload() {
  const calmIn = PomoStorage.getCalm();
  const volumeIn = PomoStorage.getVolume();
  const soundIn = PomoStorage.getSound();
  const darkIn = PomoStorage.getDark();
  const workIn = PomoStorage.getWork();
  const shortIn = PomoStorage.getShortBreak();
  const longIn = PomoStorage.getLongBreak();
  const accessIn = PomoStorage.getAccessibility();
  const modeIn = PomoStorage.getMode();

  const {
    work: workCountIn,
    shortBreak: shortBreakCountIn,
    longBreak: longBreakCountIn,
    interrupts: interruptCountIn,
  } = PomoStorage.getDayCounts();

  // Update settings
  pomoSettings.loadSettings(calmIn, volumeIn, soundIn, darkIn, workIn, shortIn, longIn, accessIn);

  // Update control
  currentMode = modeIn;
  workLength = workIn;
  shortLength = shortIn;
  longLength = longIn;
  workCount = workCountIn;
  shortCount = shortBreakCountIn;
  longCount = longBreakCountIn;
  interruptCount = interruptCountIn;
  accessibility = accessIn;

  // Configure initial ctimer state
  switch (currentMode) {
    case Mode.work:
      pomoTimer.setTimer(workLength, currentMode);
      break;
    case Mode.short:
      pomoTimer.setTimer(shortLength, currentMode);
      break;
    case Mode.long:
      pomoTimer.setTimer(longLength, currentMode);
      break;
    default:
      // Default to work if invalid saved mode
      pomoTimer.setTimer(workLength, currentMode);
      break;
  }

  // Update timer progress as well
  pomoTimer.setProgress(workCount % 4);

  // Run updater methods for the other variables
  volumeSet(volumeIn);
  soundSet(soundIn);
  calmSet(calmIn);
  darkSet(darkIn);
}

/**
 * Run onload method once when page loads
 */
onload();