Jak jsem vytvořil svou první aplikaci Electron &You Can Too Pt.4 – Přidání časovače a upozornění

Vytvoření elektronické aplikace je tak snadné, že to může udělat každý! Vše, co potřebujete, je jen nápad. Pak vám tato mini série krok za krokem ukáže, jak vzít svůj nápad a vytvořit svou první elektronovou aplikaci. V této části bude naším cílem vytvořit a následně implementovat komponentu Timer s jednoduchým upozorněním pro naši elektronovou aplikaci Grease the Groove. Bez dalších okolků začněme... a bavme se!

Jak jsem vytvořil svou první aplikaci Electron &You Can Too, část 1.

Jak jsem vytvořil svou první aplikaci Electron &You Can Too část 2.

Jak jsem vytvořil svou první aplikaci Electron &You Can Too část 3.

Jak jsem vytvořil svou první aplikaci Electron &You Can Too, část 5.

Než začneme, dovolte mi, abych vám rychle ukázal strukturu složek, kterou jsme probrali ve druhé části. Velmi nám to usnadní a zrychlí navigaci v projektu. Kdykoli nebudete vědět, kam jít, můžete se podívat sem.

grease-the-groove-app
├── builds/
├── dist/
├── node_modules/
├── src/
│   └── app/
│       └── components/
│       └── App.jsx
│   └── assets/
│       └── grease-the-groove-icon.ico
│       └── grease-the-groove-icon.png
│       └── grease-the-groove-icon.svg
│   └── index.js
├── .babelrc
├── .editorconfig
├── main.js
├── package.json
├── README.md
├── webpack.build.config.js
├── webpack.dev.config.js
└── yarn.lock

Vytvoření komponenty Timer

Na konci předchozího dílu jsem vám slíbil, že dnes začneme s tvorbou a implementací komponenty pro Timer pro naši elektronovou aplikaci. Začněme tedy touto výzvou nebo hádankou. Pojďme se hned na začátku vypořádat s nejtěžším úkolem této části. Nejprve přejděte na src/app/components a vytvořte nový soubor s názvem Timer.jsx tady. Nyní otevřeme tento soubor a dáme dohromady kód, který budeme potřebovat, abychom zprovoznili náš časovač.

Pojďme mluvit o časovači. Jakou potřebnou funkčnost, co musíme mít, bychom chtěli mít? Nejprve musíme vytvořit metodu, která převede čas nastavený uživatelem do čitelnějšího formátu. To znamená převést vstup uživatele na sekundy, minuty a hodiny, abychom pak mohli tyto hodnoty zobrazit. Dále přidáme metodu, která bude mít hodnotu pauseLength prop a uložte jej do state . componentDidMount udělá práci. To také znamená, že časovač bude stavovou komponentou.

Dále můžeme přidat několik dalších metod, které nám i dalším potenciálním uživatelům umožní spouštět, zastavovat a restartovat časovač. Díky těmto funkcím bude naše elektronová aplikace užitečnější a bude se s ní snadněji pracovat. Pak budeme potřebovat metodu pro samotný časovač neboli odpočítávání. Tato metoda zkrátí délku pauzy o 1 sekundu a aktualizuje specifické klíče v state . Při každém poklesu nebo cyklu také zkontroluje, zda jsme na 0. Pokud ano, zastaví časovač.

Nakonec přidáme jednu další a velmi jednoduchou metodu, která přehraje zvuk a zobrazí vyskakovací okno s upozorněním JavaScript. To pak můžeme použít jako upozornění, abychom dali uživateli vědět, že odpočinková pauza skončila a že je čas namazat drážku a udělat další sadu.

Nastavení stavu

Začněme kostrou Timer a nastavení state . state bude obsahovat tři položky. Bude to isTimerRunning , seconds a time . isTimerRunning bude boolean a použijeme jej jako indikátor, zda časovač běží nebo ne. seconds uloží délku klidové pauzy v sekundách. time bude objekt, který bude ukládat zbývající čas v sekundách, minutách a hodinách, pro každý jeden pár klíč/hodnota.

Dále stále v constructor pojďme také svázat metody, které vytvoříme pro naši komponentu Timer. Jedná se o countDown , playSound , restartTimer , startTimer a stopTimer . Přidejme také jednu další proměnnou, timer , později použijeme ke spuštění a zastavení časovače. A to je vše, co nyní potřebujeme.

// Import React library
import React from 'react'

// Component for Timer
export default class Timer extends React.Component {
  constructor(props) {
    super(props)

    // Create state with default key we will use later.
    this.state = {
      isTimerRunning: false,
      seconds: this.props.pauseLength * 60,
      time: {}
    }

    this.timer = 0

    // Bind methods we will use
    this.countDown = this.countDown.bind(this)
    this.playSound = this.playSound.bind(this)
    this.restartTimer = this.restartTimer.bind(this)
    this.startTimer = this.startTimer.bind(this)
    this.stopTimer = this.stopTimer.bind(this)
  }
}

Převod času na sekundy, minuty a hodiny

Nyní můžeme vytvořit první metodu pro náš časovač. Tato metoda, říkejme jí timeConverter , umožní naší elektronové aplikaci přijímat zadání uživatele, jak dlouho si chce odpočinout. Poté tato data převede na sekundy, minuty a hodiny. Nakonec tyto hodnoty uloží do objektu a vrátí jej.

// Method for converting and formatting seconds to more readable format.
timeConverter(secs) {
  let hours = Math.floor(secs / (60 * 60))

  let minutesDivisor = secs % (60 * 60)
  let minutes = Math.floor(minutesDivisor / 60)

  let secondsDivisor = minutesDivisor % 60
  let seconds = Math.ceil(secondsDivisor)

  let obj = {
    'h': hours,
    'm': minutes,
    's': seconds
  }

  return obj
}

Připojit, spustit, zastavit a … restartovat

Nyní se pojďme zabývat jednoduššími a menšími metodami, které do naší elektronové aplikace přidají určitou užitečnost. Metody, o kterých mluvím, vytvoříme jako další, budou startTimer , stopTimer , restartTimer . startTimer zkontroluje, zda časovač běží, a pokud ne, spustí jej pomocí setInterval a countDown metody. Vytvoříme countDown metoda hned po těchto. stopTimer metoda zastaví časovač „vymazáním“ aktuálně běžícího intervalu. Poté nastaví isTimerRunning na nepravdu.

Třetí metoda, restartTimer , také zastaví časovač „vymazáním“ aktuálně běžícího intervalu. Poté bude trvat předdefinovanou délku pauzy od pauseLength prop, převeďte jej pomocí timeConverter , zkraťte čas o 1 sekundu a aktualizujte konkrétní klíče v state , konkrétně isTimerRunning , seconds a time . Před tím však rychle přidáme componentDidMount metoda, která aktualizuje time klíč uvnitř state s hodnotou pauseLength prop, po naformátování.

// When component is mounted, update the state.
// with new data for 'time' key (the length of pause between sets).
componentDidMount() {
  let restPauseLength = this.props.pauseLength * 60 // pauseLength is in minutes

  this.setState({
    time: this.timeConverter(this.state.seconds)
  })
}

// Method for starting the timer.
startTimer() {
  if (!this.state.isTimerRunning) {
    this.timer = setInterval(this.countDown, 1000)
  }
}

// Method for stopping the timer.
stopTimer() {
  clearInterval(this.timer)

  this.setState({
    isTimerRunning: false
  })
}

// Method for restarting the timer.
// This method will stop the timer and revert it to its initial state.
restartTimer() {
  clearInterval(this.timer)

  let newTime = this.timeConverter(this.props.pauseLength * 60)
  let newSeconds = this.state.seconds - 1

  this.setState({
    isTimerRunning: false,
    seconds: this.props.pauseLength * 60,
    time: newTime
  })
}

Vytvoření odpočítávání

Jako další vytvoříme základní metodu pro časovač, počítadlo. Tato metoda zabere čas uložený v seconds zadejte state , snižte jej o 1 sekundu a aktualizujte isTimerRunning , seconds , time klíče uvnitř časovače state . Poté jako poslední věc zkontroluje, zda je časovač na 0. Pokud ano, přehraje zvuk a zobrazí upozornění a zastaví časovač „vymazáním“ aktuálně běžícího intervalu.

// Method for the countdown.
countDown() {
  // Remove one second, set state so a re-render happens.
  let seconds = this.state.seconds - 1

  // Update specific keys in state.
  this.setState({
    isTimerRunning: true,
    seconds: seconds,
    time: this.timeConverter(seconds)
  })

  // If we're at 0 play notification sound and stop the timer.
  if (seconds === 0) {
    this.playSound()

    clearInterval(this.timer)
  }
}

Vytvoření jednoduchého oznámení

Je čas na poslední metodu pro naši komponentu Timer, stejně jako naši elektronovou aplikaci. Již existují různé balíčky npm pro implementaci nativních oznámení na ploše. Nicméně jsem se rozhodl, že to zatím ponechám velmi jednoduché a použiji pouze zvuk a JavaScript alert vyskakovací okno. K tomu nám poslouží mp3 soubor s krátkým zvukem. Naimportujme jej na začátek tohoto souboru (komponenta Timer), aby všechny importy zůstaly na jednom místě.

Potom tento soubor použijeme a vytvoříme nový Audio objekt, nastavte výchozí hlasitost a použijte play způsob přehrávání zvuku při playSound je nazýván. Když se zvuk přehraje, po malé prodlevě (5 milisekund) zobrazíme alert vyskakovací okno s nějakou zprávou.

// Import sound for notification
import bellSound from './../../assets/definite.mp3'

// ... some code

// Method for playing notification sound
// and displaying a simple alert popup window.
playSound() {
  const soundFile = new Audio(bellSound);

  soundFile.volume = 1 // 0.5 is half volume

  soundFile.play()

  // Wait for 0.5s and display the notification
  // in the form of JavaScript alert popup window.
  setTimeout(() => {
    alert('Time to Grease the Groove!')
  }, 500)
}

Spojení komponenty časovače

Nyní dáme dohromady všechny části, které jsme dříve vytvořili, a importujeme soubor pro zvukové upozornění. Tento soubor můžeme vložit do assets adresář, uvnitř src . Poté můžeme vytvořit strukturu pro vykreslení komponenty Timer. Struktura bude jednoduchá. Jeden p prvek a tři tlačítka, jedno pro spuštění časovače, jedno pro jeho resetování a jedno pro jeho zastavení.

// Import React library
import React from 'react'

// Import sound for notification
import bellSound from './../../assets/definite.mp3'

// Component for Timer
export default class Timer extends React.Component {
  constructor(props) {
    super(props)

    // Create state with default key we will use later.
    this.state = {
      isTimerRunning: false,
      seconds: this.props.pauseLength * 60,
      time: {}
    }

    this.timer = 0

    // Bind methods we will use
    this.countDown = this.countDown.bind(this)
    this.playSound = this.playSound.bind(this)
    this.restartTimer = this.restartTimer.bind(this)
    this.startTimer = this.startTimer.bind(this)
    this.stopTimer = this.stopTimer.bind(this)
  }

  // Method for converting and formatting seconds to more readable format.
  timeConverter(secs) {
    let hours = Math.floor(secs / (60 * 60))

    let minutesDivisor = secs % (60 * 60)
    let minutes = Math.floor(minutesDivisor / 60)

    let secondsDivisor = minutesDivisor % 60
    let seconds = Math.ceil(secondsDivisor)

    let obj = {
      'h': hours,
      'm': minutes,
      's': seconds
    }

    return obj
  }

  // When component is mounted, update the state.
  // with new data for 'time' key (the length of pause between sets).
  componentDidMount() {
    let restPauseLength = this.props.pauseLength * 60 // pauseLength is in minutes

    this.setState({
      time: this.timeConverter(this.state.seconds)
    })
  }

  // Method for starting the timer.
  startTimer() {
    if (!this.state.isTimerRunning) {
      this.timer = setInterval(this.countDown, 1000)
    }
  }

  // Method for stopping the timer.
  stopTimer() {
    clearInterval(this.timer)

    this.setState({
      isTimerRunning: false
    })
  }

  // Method for restarting the timer.
  // This method will stop the timer and revert it to its initial state.
  restartTimer() {
    clearInterval(this.timer)

    let newTime = this.timeConverter(this.props.pauseLength * 60)
    let newSeconds = this.state.seconds - 1

    this.setState({
      isTimerRunning: false,
      seconds: this.props.pauseLength * 60,
      time: newTime
    })
  }

  // Method for the countdown.
  countDown() {
    // Remove one second, set state so a re-render happens.
    let seconds = this.state.seconds - 1

    // Update specific keys in state.
    this.setState({
      isTimerRunning: true,
      seconds: seconds,
      time: this.timeConverter(seconds)
    })

    // If we're at zero play notification sound and stop the timer.
    if (seconds === 0) {
      this.playSound()

      clearInterval(this.timer)
    }
  }

  // Method for playing notification sound
  // and displaying a simple alert popup window.
  playSound() {
    const soundFile = new Audio(bellSound);

    soundFile.volume = 1 // 0.5 is half volume

    soundFile.play()

    // Wait for 0.5s and display the notification
    // in the form of JavaScript alert popup window.
    setTimeout(() => {
      alert('Time to Grease the Groove!')
    }, 500)
  }

  render() {
    return(
      <div>
        {/* Remaining rest time in readable format. */}
        <p>{this.state.time.h}h {this.state.time.m}m {this.state.time.s}s</p>

        {/* Buttons for interacting with timer. */}
        <button onClick={this.startTimer}>Start</button>

        <button onClick={this.restartTimer}>Reset</button>

        <button onClick={this.stopTimer}>Stop</button>
      </div>
    )
  }
}

Implementace komponenty Timer

Toto je poslední krok k implementaci naší nové komponenty Timer do naší elektronové aplikace. Musíme se vrátit k App.jsx soubor. Zde musíme přidat nový import pro časovač nahoře. Dále můžeme odkomentovat komponentu časovače, kterou již máme uvnitř render metoda.

// Import React library
import React from 'react'

// Import timer
import Timer from './components/Timer'

// Create the main component for our electron app
class App extends React.Component {

  // ... code from previus parts

  // Create the main render method
  render() {
    return (
      <div>
        <h1>Grease the Groove!</h1>

        <p>Are you ready to get stronger?</p>

        {/* Button to open/close the settings div */}
        <a href="#" onClick={(e) => this.toggleSettings(e)}>Settings</a>

        {/* Button to show/hide the Timer */}
        <a href="#" onClick={(e) => this.toggleTimer(e)}>Timer</a>

        {/* If the value of `isSettingsOpen` is true, open settings. */}
        {this.state.isSettingsOpen && <div className="settings">
          <p>How many sets do you want to do?</p>

          {/* Number input to let the user specify the number of sets he wants to do in a day. */}
          <input type="number" placeholder={this.state.numOfSets} onChange={(e) => this.updateNumOfSets(e)} />

          <p>How long should the rest pause be (in minutes)? You can use decimal numbers for seconds, i.e.: 0.2 for 12s.</p>

          {/* Number input to let the user specify the rest pause between sets. */}
          <input type="number" value={this.state.restPauseLength} onChange={(e) => this.updateRestPauseLength(e)} />
        </div>}

        {/* If the value of `isTimerShown` is true, show timer */}
        {/* and provide the timer with data about the length of the rest pause,
        stored inside app state via 'pauseLength' prop */}
        {this.state.isTimerShown && <Timer pauseLength={this.state.restPauseLength} />}

        {/* Create list of sets to do */}
        <ul>
          {this.generateSetsList()}
        </ul>
      </div>
    )
  }
}

Je čas otestovat Pojďme se konečně podívat na výsledky naší práce a poprvé ji spustit. No, skoro. Je tu poslední věc, kterou musíme udělat. Musíme Webpacku „sdělit“, že jsme přidali soubor mp3. Jinými slovy, musíme přidat nový test pro soubory „mp3“ pomocí file-loader , na rules objekt. Musíme se také ujistit, že tuto změnu přidáme do obou konfigurací, pro sestavení i pro vývojáře.

// webpack.build.config.js and webpack.dev.config.js
module.exports = {
  // ... the rest of the code

  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: [{ loader: 'babel-loader' }],
        include: defaultInclude
      },
      {
        test: /\.(jpe?g|png|gif|ico)$/,
        use: [{ loader: 'file-loader?name=img/[name]__[hash:base64:5].[ext]' }],
        include: defaultInclude
      },
      {
        test: /\.(eot|svg|ttf|woff|woff2)$/,
        use: [{ loader: 'file-loader?name=font/[name]__[hash:base64:5].[ext]' }],
        include: defaultInclude
      },
      {
        test: /\.mp3$/,
        use: 'file-loader'
      }
    ]
  },

  // ... the rest of the code
}

A s touto malou změnou na místě můžeme konečně spustit příkazový řádek nebo terminál a spustit buď yarn run dev nebo npm run dev spusťte naši elektronovou aplikaci ve vývojářském režimu a uvidíte výsledky naší dnešní práce. Pokud jste postupovali podle této mini série a použili jste kód z předchozích dílů, měli byste vidět, jak se vaše první elektronová aplikace úspěšně spouští. Pokud ne, zkontrolujte příkazový řádek nebo terminál a konzolu a opravte všechny chyby, které se objevily.

Závěrečné úvahy o budování elektronové aplikace

Gratulujeme! Právě jste dokončili čtvrtý díl této mini série o budování elektronové aplikace. Dnes jste udělali hodně práce. Výsledkem je, že vaše nová elektronová aplikace nyní nejen pomáhá uživateli přihlásit sady. Postará se také o měření klidové pauzy a upozornění uživatele jednoduchým upozorněním, kdy je čas drážku znovu namazat. Poslední otázkou je, co přichází v pátém a pravděpodobně také posledním díle této mini série?

V příštím díle se podíváme na to, jak vylepšit uživatelské rozhraní naší elektronové aplikace. Naším hlavním cílem a úkolem bude dát dohromady styly a vylepšit uživatelské rozhraní s styled-components . Poté se podíváme na to, jak můžeme kód vylepšit a vyčistit. Jinými slovy, náš nový elektron vyleštíme na obou stranách, viditelné i skryté. Poté bude naše elektronová aplikace připravena k oficiálnímu vydání.