Cours logiciel - Electron

Chap 3 : Explorateur de fichier redimensionable, lecture de…

Préface

Nous avons vu au chapitre précédent comment réaliser la barre d’outils principal du logiciel.

Nous allons maintenant rentrer dans le vif du sujet pour la réalisation d’un explorateur de fichier texte basique.

Le code source concernant ce chapitre est disponible sur mon Github.

Résultats du cours

 

Explorateur de fichiers redimensionable

C’est tout bête, mais il va arriver à un moment ou à un autre que l’on tombe sur des noms de fichiers plus ou moins long, donc certains pourrait être tronqué. Autant faire quelque chose de propre, et donner la possibilité à notre explorateur d’être redimensionable par l’utilisateur.

Depuis le dernier chapitre, j’ai légèrement modifié l’architecture du projet afin de le rendre plus maintenable pour la suite des tutoriels. En voici une présentation simplifié :

Diagramme général
  • Module App
    • appComponent.ts
    • appComponent.html
    • appComponent.scss
    • appRouting.ts
    • Module Settings
      • settingsComponent.ts
      • settingsComponent.html
      • settingsComponent.scss
      • settingsRouting.ts
    • Module Home
      • homeComponent.ts
      • homeComponent.html
      • homeComponent.scss
      • homeRouting.ts
    • Module Projects
      • projectsComponent.ts
      • projectsComponent.html
      • projectsComponent.scss
      • projectsRouting.ts
        • File Explorer
          • fileExplorerComponent.ts
          • fileExplorerComponent.html
          • fileExplorerComponent.scss
        • File Editor
          • fileEditorComponent.ts
          • fileEditorComponent.html
          • fileEditorComponent.scss

 

 

Pour la partie graphique de notre file explorer, il va être composé d’une toolbar pour permettre d’ouvrir un dossier, une partie pour afficher nos fichier en colonne, et enfin un grabber nous permettant de modifier la taille de notre fenêtre :

On va utilise une directive de angular pour nous permettre de bind la largeur de notre file explorer [style.width.px]=’divWidth’.

Pour la partie de style, rien de bien sorcier, juste de prévoir de changer le curseur de souris lorsque l’on passera sur le grabber afin de faire remarquer à l’utilisateur que la fenêtre est modifiable :

 

La partie la plus intéressante et ou se passe la magie de notre grabber est dans notre contrôleur. On va définir :

  • un boolean, pour savoir si on a le clique enfoncé ou non,
  • une largeur de fenêtre,
  • une ancienne largeur de fenêtre pour connaître les déplacements.

 

Dernière étape, on va devoir ajouter des listener d’actions :

  • Un pour savoir quand on bouge la souris,
  • un autre pour savoir quand on enfonce le clic,
  • et un dernier pour savoir quand on relâche le clic

Dans l’ordre, cela va nous pouvoir de modifier la taille en temps réel, de savoir quand effectuer cette action, et savoir quand l’arrêter :

 

 

Explorateur basique de fichier texte

On souhaite avoir  un système qui puisse lister l’ensemble des fichiers textes d’un dossier, et nous l’afficher sous une forme de liste au sein de notre logiciel. Cela nous permettra d’un simple clique de pouvoir ouvrir tel ou tel fichier texte à modifier.

 

Demande de fichier depuis le Frontend

On va ajouter un bouton nous permettant à son click, de notifier le backend afin d’ouvrir une fenêtre de dialogue pour sélectionner un dossier, dans lequel on souhaite récupérer l’ensemble des fichiers textes qui y sont situé. Rien de bien complexe pour la partie graphique, juste un bouton contenant une icone, qui à son click sera bindé avec l’appel de ‘openFolderDirectory()’. L’utilisation de la flexbox nous permet de positionner le boutton sur le côté gauche de la div.

 

Dans le contrôleur on va ajouter la fonction précédente. Celle-ci fait appel au module ipcRender, qui permet la communication de message du renderProcess vers le mainProcess. Il permet d’envoyer des messages via la fonction ‘send’, ou d’écouter via ‘on’. Cela fonctionne exactement comme des sockets, si vous en avez déjà utilisé. Dans notre cas on va juste envoyer un message vide, une sorte de ping pour exécuter une fonction.

 

Traitement de la demande par le Backend

On va réceptionner la notification venant du front via le module ipcMain dans notre backend, avec sa méthode ‘on’.  La fonction prend un argument ‘event’, permettant de renvoyer un message, ainsi qu’un second argument ‘message’ contenant des données envoyé. Mais rappelez vous, on a juste ping sans envoyer le donnée, cet argument sera donc vide.

Le module dialog va nous permettre de créer une fenêtre de dialogue, avec en argument notre fenêtre principal, suivit d’un dictionnaire d’options permettant de définir si l’on souhaite ouvrir un dossier, un fichier, de définir un nom de fenêtre en particulier, etc.

Cette fonction nous renvoi une promesse, sur laquelle on va pouvoir lui attache deux bloc :

  • .then() : est appelé si l’ouverture du dialog se passe correctement,
  • .catch() : est appelé si une erreur est lancé lors de son ouverture.

Dans le cas ou on a une erreur, on la remonte simplement dans la console du back.

Dans le cas ou tout se passe bien, on va vérifier que l’utilisateur à bien choisit un dossier et n’a tout simplement pas annulé son action par la fermeture de boite de dialogue.  On va alors appelé le module fs de Node qui permet de réaliser des opérations de lectures et d’écritures sur le disque. On va dans un premier temps récupérer l’ensemble des fichiers contenu dans le dossier renseigné pour l’utiliser, puis lui appliquer un filtre via une regexp, permettant de garder seulement les fichiers dont leurs noms se termine par .txt.

On va utiliser le premier argument pour pouvoir répondre au front, en lui envoyant dans un nouveau canal, un tableau contenant les noms de fichiers textes étant dans son dossier de sélection.

 

Affichage de la réponse dans le Frontend

On va réceptionner la réponse venant du Backend via une fonction écoutant sur le même canal que celui utilisé pour l’émission. On va récuperer le tableau de données, et l’associer à un attribut de composant que l’on aura déclaré au préalable.

 

Maintenant que l’on a nos données, on a plus qu’a les afficher comme une liste dans notre vue. On utilisera une mat list item, et on utilisera le bind *ngFor de Angular pour parcourir l’ensemble des items de notre tableau de données.

 

Un petit plus pour l’ésthetisme

Vous avez vu le binding [ngClass] ? C’est une directive de Angular, permettant de lui associer une classe CSS en plus (highlight, dans mon cas), si la condition file.highlight est respecté, soit si et seulement si le booléan est à true. Lorsque l’utilisateur clique sur un fichier, une fonction sera appelé. Celle-ci mettra à jour le boolean de l’ensemble des fichiers à faux, et mettra à true sur celui qui a été sélectionné.

Si vous regarder le code, concrètement cela permet lors d’une sélection d’un fichier, de lui ajouter une couleur plus clair que les autres, pour renseigner de façon plus jolie à l’utilisateur, sur quel fichier il est. Cela aura d’avantage de sens lors du prochain chapitre vous verrez 😉

 

Conclusion

Vous pouvez rendre plus complexe votre explorateur en y ajoutant certaines fonctionnalités comme :

  • Pouvoir remonter dans le dossier parente : pour cela vous n’avez qu’a juste ajouter un bouton ‘parent’, qui va ré-appeler notre fonction de ping du backend, mais en lui envoyant un chemin avec un niveau plus haut.
  • Possibilité d’ajouter un logo à côté de chaque fichier, en fonction de leur type. Vous aurez juste besoin d’une fonction qui split le nom d’un fichier, et qui compare l’extension, et affiche un type d’icone en fonction de celle-ci. A faire soit directement dans la vue avec un binding *ngSwitch, soit d’ajouter un nouvel attribut dans notre FileType.

 

Le prochain chapitre portera sur l’ouverture d’un fichier texte dans notre éditeur, pour pouvoir le modifier et le sauvegarder sur le disque.
 

 

Cours logiciel - Electron

Chap 0 : Présentation de Electron

Présentation

Electron est un framework permettant de développer des applications bureaux multi plateforme ( Linux, Windows, MacOS ) avec des technologies web ( HTML, CSS et Typescript/Javascript ). Il est open source et permet de réaliser très rapidement des applications. Vous pensez que cela n’est pas possible ? Et pourtant vous en utilisez surement sans même le savoir ; Atom, Visual studio, Slack pour n’en citer que les plus gros.

Vous allez donc développer votre application comme si vous développiez un site web.

 

Composition de Electron

composition electron nodejs chronium

 

Electron embarque plusieurs outils/bibliothèque pour permettre d’avoir les mêmes accès qu’un logiciel développé avec un langage plus adapté et/ou de plus bas niveau :

  • Chronium : c’est le navigateur open source qui sert de base au célèbre Chrome de Google. Il va assurer le rendu visuel de l’application.
  • NodeJS : c’est un environnement d’exécution de code javascript. Il permet l’accès au système de fichier de l’ordinateur, ainsi que le réseau.
  • APIs Natives : permet l’accès aux fonctions natives, propres à chacun des OS.

 

Fonctionnement global

Le développement en est extrêmement simplifié, mais aussi accéléré, car vous aurez accès à plus de 300 000 modules sur NPM. C’est une sorte d’hébergeur de module, qui permet de réaliser certaines tâches. Vous ajoutez donc en quelques secondes de nouvelles fonctionnalité sur votre application.

D’autant plus que vous pouvez ajouter un framework pour le frontend pour structurer votre application : Angular, React, VueJS…

 

Vous allez avoir deux processus différent pour faire fonctionner une application tournant sous Electron :

 

  • Main process

C’est le point d’entrée de votre application. Il va contrôler le cycle de vie de l’application. Vous aurez tout les accès depuis ce processus, via les API native ou de NodeJS. Il peut aussi créer de nouveau processus de rendu, ainsi que de démarrer et de quitter l’application.

Ce processus est unique

 

  • Render process

Il va être responsable de la vue de votre application, par le biais d’affichage de vos pages HTML/CSS. Vous aurez accès au javascript pour gérer les contrôleurs et interactions. Mais attention, pas d’accès direct au système.

Chacun des processus de rendu sont indépendant les uns des autres. Si un crash, il n’affecte pas ses voisins. Il peut être caché, permettant d’exécuter du code en arrière plan.

Ce processus peut être multiple.

 

/!\ L’ensemble des fonctionnalités disponible par l’API de Electron ne sont pas forcement accessible depuis les deux types processus. Certains ne seront garanti que dans un seul des deux type de processus.

 

Communication entre Render et Main process

Electron à mit en place un module, appelé IPC, permettant de réaliser une communication ainsi qu’un échange de données entre main et render process, qui est appelable depuis chacun des processus. Cette communication fonctionne sous forme de canaux, et l’échange est bi-latéral. Celle-ci s’apparente à des sockets.

 

Architecture d’une application Electron

Le schéma suivant montre d’une façon simplifié le fonctionnement de base d’une appli Electron.

Architecture simplifiée
Architecture simplifiée
  1. Le package.json est le point d’entrée de votre application. Il va indiquer à Electron ou est le main process,
  2. Le main.js définit votre processus principal. Il va créer la fenêtre graphique pour y appeler le render process.
  3. Le index.html définit votre vue.
  4. Le module IPC permet l’échange d’informations entre les divers processus.

 

Conclusion

Points positifs

  • Stack web facile à apprendre
  • Dév rapide ( hot reload, console chronium, modules NPM… )
  • Cross-platform

Points négatifs

  • Consommation excessive de RAM
  • Taille du bundle ( ~100Mo pour un simple ‘Hello World !’ )

 

Maintenant que vous voyez le fonctionnement global d’un projet sous Electron, je vous propose d’expérimenter vous même, et de réaliser un traitement de texte basique sur le chapitre suivant.