Electron angular bootstrap pour créer des applications desktop

Aujourdhui tout développeur est un développeur web, on parle souvent de développeur backend ou front end, mais à la base, avant la venue du web, on faisait plus des applications qu’on appelle « desktop », dans cet article je vous propose d’en créer une, mais en utilisant de l’HTML, CSS, et Javascript avec Electron framework.

c’est vrai que ça existe depuis un bout de temps mais je voulais profiter de la sortie de la version 1.0 d’Electron Framework pour tester le produit.

Electron ?

Electron permet d’exécuter des applications développés en HTML5 dans un container Chromium tout en fournissant des API pour accéder au ressources du système hôte, il permet aussi de compiler des exécutables vers Windows, OSX ou Linux ( on peux dire que c’est le cordova du desktop).

Objectif

Dans le but de voir ce qu’Electron a dans le ventre, essayons de créer une application avec des technos webs populaires, je vous propose de créer un lecteur de musiques avec AngularJS 1.5 et Bootstrap.

Résultat final

electron music player angularJS

electron music player angularJS

Le code source complet est disponible sur github.

Préparation de l’environnement

On va commencer par poser la structure de notre projet avant d’attaquer le développement du lecteur musical:

electron app angular structure

la structure d’une application electron ressemble à celle d’une application nodeJS, on commence donc par créer un fichier package.json avec les dépendances à utiliser :

{
  "name": "music-demo",
  "version": "1.0.0",
  "description": "demo application using electron framework",
  "main": "index.js",
  "scripts": {
    "start": "electron index.js"
  },
  "license": "ISC",
  "devDependencies": {
    "electron-prebuilt": "^1.1.0"
  },
  "dependencies": {
    "bluebird": "^3.3.5"
  }
}

pour ce projet j’ai les dépendances suivantes :

  • electron-prebuilt: contient les librairies
  • bluebird: sera utilisé plus tard, mais c’est optionel

on définit aussi le point d’entrée de l’application (index.js), ce fichier va se charger d’ouvrir une fenêtre dans laquelle notre application va s’ouvrir:

index.js

    const electron = require('electron')
    const app = electron.app
    const BrowserWindow = electron.BrowserWindow

    let mainWindow

    app.on('ready', createWindow)

    app.on('window-all-closed', function () {
      // On OS X it is common for applications and their menu bar
      // to stay active until the user quits explicitly with Cmd + Q
      if (process.platform !== 'darwin') {
        app.quit()
      }
    })

    app.on('activate', function () {
      // On OS X it's common to re-create a window in the app when the
      // dock icon is clicked and there are no other windows open.
      if (mainWindow === null) {
        createWindow()
      }
    })

    function createWindow () {
      mainWindow = new BrowserWindow({width: 1024, height: 768})
      mainWindow.setResizable(false)
      mainWindow.loadURL(`file://${__dirname}/app/index.html`)

      mainWindow.webContents.openDevTools()

      mainWindow.on('closed', function () {
        mainWindow = null
      })
    }

  

c’est tout ce dont vous aurez besoin pour démarrer votre application, ici on va créer une fenêtre de la taille 1024 x 768, ouvrir le fichier app/index.html puis ouvrir les outils de développement chrome ( voir la fonction createWindow() ).

On peux maintenant créer le code de notre application dans le dossier app de la même manière qu’une application web sous angularJS … enfin presque, avec electron on peux utiliser les modules nodeJS directement dans le code source d’AngularJS, ce qui offre des possibilités très intéressantes comme l’accès directe au ressources système et disque directement depuis le code Angularjs, ce qu’on ne peux pas faire dans le contexte d’une application web.

On va donc essayer d’exploiter cette possibilités pour créer un lecteur de musiques basique, il devra faire les choses suivantes :

  • Charger les musiques présentes dans un dossier et les afficher dans une liste
  • lire une musique quand on clic sur un élément
  • gérer les boutons pause/play et suivant/précédent

commençons donc par installer les librairies qu’on va utiliser, pour cela on va utiliser bower, dans le dossier app lancer les commandes suivantes :

  cd app/
  bower install angular --save
  bower install bootstrap --save
  bower install angular-audio --save
  

les dépendences installées sont :

  • angularJS: le framework qu’on va utiliser
  • bootstrap: pour avoir une UI pas trop moche
  • angular-audio: faciliter l’utilisation de la balise audio avec AngularJS

après avoir installé les dépendances, on continue par construire notre interface graphique, pour cela on utilise bootstrap :

» Afficher le fichier : app/index.html

<!DOCTYPE html>
<html ng-app="app">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Angular music player</title>

    <link href="./bower_components/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
    <link href="./styles/app.css" rel="stylesheet">

  </head>
  <body class="container-fluid" ng-controller="MainController">

    <div class="row">
      <div class="col-xs-3">
        <div class="btn-group btn-group-lg" role="group">
          <button 
          ng-click="prevSound()"
          type="button" class="btn btn-default">
            <i class="glyphicon glyphicon-fast-backward"></i>
          </button>
          <button 
          ng-click="playPause()"
          type="button" class="btn btn-default">
            <i 
            ng-class="{'glyphicon-pause': isPlaying(), 'glyphicon-play': !isPlaying()}"
            class="glyphicon"></i>
          </button>
          <button
            ng-click="nextSound()"
           type="button" class="btn btn-default">
            <i class="glyphicon glyphicon-fast-forward"></i>
          </button>
        </div>

      </div>
      <div class="col-xs-9">
      
        <form class="form-inline">

          <div class="input-group input-group-lg file-browse-box">
            <input type="text" class="form-control"
                  ng-model="selectedFolder"
                  ng-disabled="true"
                   placeholder="Choose a folder">
            <span class="input-group-btn">
              <span class="btn btn-default btn-file">
                  <i class="glyphicon glyphicon-folder-open"></i>
                   <input ng-model="selectedFolder" type="file" webkitdirectory directory multiple file-change-callback = "onFolderSelected" />
              </span>
            </span>
          </div>
        
        </form>
      </div>

    </div>


    <div class="row list-musics-block" ng-show="musicFiles.length > 0">
      <div class="col-xs-3"></div>
      <div class="col-xs-9">
        <div class="panel panel-default">
          <div class="panel-body">

            <form>
              <div class="form-group">
                <input class="form-control" placeholder="filter" ng-model="searchFilter">
              </div>
            </form>

            <div class="list-musics">
              <div class="list-group">
                <a
                  href="#"
                 ng-click="playMusic(file, $index)"
                  ng-class="{active: activeFile === file}"
                 class="list-group-item"
                  ng-repeat="file in musicFiles | filter : searchFilter">{{file.name}}</a>
              </div>
            </div>
            
          </div>
        </div>
      </div>
    </div>

    
    <script src="./bower_components/angular/angular.js"></script>
    <script src="./bower_components/angular-audio/app/angular.audio.js"></script>
    <script src="scripts/app.js" ></script>
    <script src="scripts/controllers.js" ></script>
    <script src="scripts/services.js" ></script>
    <script src="scripts/directives.js" ></script>

  </body>

</html>

» Cacher

Ensuite on va créer le module angular principal de l’application qu’on appelle app, il va avoir une seule dépendence vers le module ngAudio qui est la directive qu’on va utiliser pour faciliter l’utilisation de la balise audio d’HTML5.

    angular.module('app', ['ngAudio'])
  

la prochaine étape est de préparer un service pour lire des fichiers à partir d’un dossier, et gràce à Electron on peux faire cela comme dans une application nodeJS en utilisant le module fs.

Donc on va créer un service AngularJS qui jouera le rôle de couche d’abstraction de toute la partie accès au disque :

» Afficher le fichier : app/scripts/fileService.js


var fs = require('fs');
var path = require('path');
require('bluebird').promisifyAll(fs);

angular.module('app')
.factory('FileService', () => {
  return {
    getFiles: function(dirPath){
               return fs.readdirAsync(dirPath)
                        .filter(file => file.substring(0, 1) !== '.')
                        .map(file => {
                            var filePath = path.join(dirPath, file);
                            return fs.statAsync(filePath)
                                    .then(stats => {
                                        return {
                                            name: file,
                                            type: stats.isFile() ? 'File' : 'Directory',
                                            path: filePath
                                        }
                                    })
                        })
            }

  }
})

» Cacher

on utilise ici bluebird pour utiliser le module fs avec des promises, vous pouvez trouver plus de détails sur l’intêret d’utiliser les promises avec le module fs de nodeJS sur ce lien, ou ce lien pour une version plus courte

on va maintenant créer notre controlleur qui va se charger de charger la liste des musiques, gérer les boutons …

» Afficher le fichier : app/scripts/MainController.js


angular.module('app')
    .controller('MainController', ($scope, FileService, $timeout, ngAudio) => {
        $scope.activeFile = undefined

        $scope.selectedFolder = undefined

        let currentIndex 


        function isMusicFile(file) {
            let extension = file.name.split('.').pop()
            return extension === 'mp3'
        }

        $scope.onFolderSelected = (event) => {
            $scope.showMusicsInFolder(event.target.files[0].path)
            $scope.selectedFolder = event.target.files[0].path
        }

        $scope.showMusicsInFolder = (path) => {

            FileService.getFiles(path)
                .filter(file => file.type === 'File')
                .filter(isMusicFile)
                .then(function(data) {
                    $timeout(function() {
                        $scope.musicFiles = data
                    })
                })
        }

        $scope.playMusic = (file, index) => {
            $scope.activeFile = file
            currentIndex = index

            if ($scope.activeSound) {
                $scope.activeSound.stop()
            }

            $scope.activeSound = ngAudio.load(file.path);
            $scope.activeSound.play()
        }

        $scope.playPause = () => {
            if ($scope.activeSound) {
                if ($scope.isPlaying()) {
                    $scope.activeSound.pause()
                } else {
                    $scope.activeSound.play()
                }
            }
        }

        $scope.nextSound = () => {
          currentIndex++
          if($scope.musicFiles[currentIndex]){

            $scope.playMusic($scope.musicFiles[currentIndex], currentIndex)
          }
        }

        $scope.prevSound = () => {
          currentIndex--
          if($scope.musicFiles[currentIndex]){
            $scope.playMusic($scope.musicFiles[currentIndex], currentIndex)
          }
        }

        $scope.isPlaying = () => {
            if ($scope.activeSound) {
                return !$scope.activeSound.paused
            }

        }

    })

» Cacher

Distribution de l’application

Maintenant qu’on a créé notre application, il est temps de la distribuer, afin de faciliter la distribution de l’application on va utiliser electron-packager


npm install electron-packager -g

Après on va générer les exécutables par plateforme avec la commande suivante :


electron-packager {dossier} {nom de l'application} --all

exemple:

electron-packager musicPlayer/ angMusic --all

Il ne reste plus qu’a tester votre application sur les plateformes de votre choix.

Le code source complet est disponible sur github.

Conclusion

Pour peu que vous ayez de bonnes bases en Javascript, HTML et CSS, vous pourrez désormais développer des applications desktop aussi rapidement qu’un site web, attention quand même car on ne pourra quand même pas créer un 3DS max, ou un photoshop killer avec Electron, n’oublions pas que derrière tout ce qu’electron fait c’est d’ouvrir une fenêtre chromium et exécuter dedans notre Javascript, mais ça risque certainement de changer dans le future avec la montée en puissance des moteurs Javascript.

2 commentaires

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *