Maison > développement back-end > Golang > Une application de bureau de gestion de mots de passe minimaliste : une incursion dans le framework Wails de Golang (partie 2)

Une application de bureau de gestion de mots de passe minimaliste : une incursion dans le framework Wails de Golang (partie 2)

Susan Sarandon
Libérer: 2024-12-30 09:50:16
original
504 Les gens l'ont consulté

Re-bonjour les codeurs ! Dans la première partie de cette courte série, nous avons vu la création et le fonctionnement d'une application de bureau pour stocker et crypter nos mots de passe réalisés avec le framework Wails. Nous avons également fait une description du backend Go et de la manière dont nous le lions au côté frontend.

Dans cette partie, nous allons aborder l'interface utilisateur. Comme nous l'avons indiqué dans cet article, Wails nous permet d'utiliser n'importe quel framework Web que nous aimons, même Vanilla JS, pour créer notre interface graphique. Comme je l'ai dit, il semble que les créateurs de Wails aient une préférence pour Svelte, car ils le mentionnent toujours comme leur premier choix. La CLI Wails (dans sa version actuelle) lorsque nous demandons de créer un projet avec Svelte Typescript (wails init -n myproject -t svelte-ts) génère l'échafaudage avec Svelte3. Comme je vous l'ai déjà dit, si vous préférez utiliser Svelte5 (et ses nouvelles fonctionnalités) j'ai un script bash qui automatise sa création (dans tous les cas, il faut que la CLI Wails soit installée). De plus, il ajoute Taildwindcss Daisyui qui me semble une combinaison parfaite pour le design de l'interface.

La vérité est que j'avais d'abord travaillé avec Vanilla Js et Vue, puis avec React, et même avec cette étrange bibliothèque qui pour beaucoup est HTMX (que je dois dire que j'adore ❤️). Mais Svelte fait tomber amoureux dès le début, et je dois dire que c'est en expérimentant avec Wails que je l'ai utilisé pour la première fois (et je promets de continuer à l'utiliser…). Mais aussi confortable que soit un framework web, il faut rappeler aux développeurs backend que le frontend n'est pas si simple ?!!

Mais allons droit au but.

I - Un aperçu de la structure du frontend

Si vous avez utilisé un framework Web, vous reconnaîtrez rapidement que la Wails CLI utilise ViteJs sous le capot :

...
.
├── index.html
├── package.json
├── package.json.md5
├── package-lock.json
├── postcss.config.js
├── README.md
├── src
│   ├── App.svelte
│   ├── assets
│   │   ├── fonts
│   │   │   ├── nunito-v16-latin-regular.woff2
│   │   │   └── OFL.txt
│   │   └── images
│   │       └── logo-universal.png
│   ├── lib
│   │   ├── BackBtn.svelte
│   │   ├── BottomActions.svelte
│   │   ├── EditActions.svelte
│   │   ├── EntriesList.svelte
│   │   ├── Language.svelte
│   │   ├── popups
│   │   │   ├── alert-icons.ts
│   │   │   └── popups.ts
│   │   ├── ShowPasswordBtn.svelte
│   │   └── TopActions.svelte
│   ├── locales
│   │   ├── en.json
│   │   └── es.json
│   ├── main.ts
│   ├── pages
│   │   ├── About.svelte
│   │   ├── AddPassword.svelte
│   │   ├── Details.svelte
│   │   ├── EditPassword.svelte
│   │   ├── Home.svelte
│   │   ├── Login.svelte
│   │   └── Settings.svelte
│   ├── style.css
│   └── vite-env.d.ts
├── svelte.config.js
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── wailsjs
    ├── go
    │   ├── main
    │   │   ├── App.d.ts
    │   │   └── App.js
    │   └── models.ts
    └── runtime
        ├── package.json
        ├── runtime.d.ts
        └── runtime.js

...
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Si vous avez utilisé un framework web généré par Vite vous ne serez pas surpris par ses fichiers de configuration. Ici j'utilise Svelte5 (plus la configuration de Taildwindcss Daisyui) qui génère mon propre script bash, comme je vous l'ai déjà dit. Nous utilisons également TypeScript, qui facilitera le développement du frontend, afin que vous puissiez également voir ses configurations.

Mais la chose importante dans cette explication est le contenu du dossier wailsjs. C'est là que la compilation réalisée par Wails a opéré sa magie. Le sous-dossier go est l'endroit où sont stockées les méthodes "traduites" en Js/Ts de la partie backend qui doit interagir avec le frontend. Par exemple, dans main/App.js (ou sa version TypeScript, main/App.d.ts) se trouvent toutes les méthodes exportées (publiques) de la structure App :

...
.
├── index.html
├── package.json
├── package.json.md5
├── package-lock.json
├── postcss.config.js
├── README.md
├── src
│   ├── App.svelte
│   ├── assets
│   │   ├── fonts
│   │   │   ├── nunito-v16-latin-regular.woff2
│   │   │   └── OFL.txt
│   │   └── images
│   │       └── logo-universal.png
│   ├── lib
│   │   ├── BackBtn.svelte
│   │   ├── BottomActions.svelte
│   │   ├── EditActions.svelte
│   │   ├── EntriesList.svelte
│   │   ├── Language.svelte
│   │   ├── popups
│   │   │   ├── alert-icons.ts
│   │   │   └── popups.ts
│   │   ├── ShowPasswordBtn.svelte
│   │   └── TopActions.svelte
│   ├── locales
│   │   ├── en.json
│   │   └── es.json
│   ├── main.ts
│   ├── pages
│   │   ├── About.svelte
│   │   ├── AddPassword.svelte
│   │   ├── Details.svelte
│   │   ├── EditPassword.svelte
│   │   ├── Home.svelte
│   │   ├── Login.svelte
│   │   └── Settings.svelte
│   ├── style.css
│   └── vite-env.d.ts
├── svelte.config.js
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── wailsjs
    ├── go
    │   ├── main
    │   │   ├── App.d.ts
    │   │   └── App.js
    │   └── models.ts
    └── runtime
        ├── package.json
        ├── runtime.d.ts
        └── runtime.js

...
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Tous renvoient une promesse. Si la promesse "enveloppe" une structure Go utilisée comme type de retour ou si la fonction respective prend un type d'argument, il y aura un module (models.ts, tapé dans ce cas, car nous utilisons TypeScript) qui contiendra la classe correspondant au Go structure et son constructeur dans un espace de noms.

De plus, le sous-dossier d'exécution contient toutes les méthodes du package d'exécution de Go qui nous permettent de manipuler la fenêtre et les événements écoutés ou émis depuis ou vers le backend, respectivement.

Le dossier src contient les fichiers qui seront compilés par Vite pour les sauvegarder dans "frontend/dist" (et embarqués dans l'exécutable final), comme dans n'importe quelle application web. Notez que, puisque nous utilisons Tailwindcss, style.css contient la configuration de base de Tailwind ainsi que toutes les classes CSS que nous devons utiliser. De plus, comme avantage de l'utilisation de la technologie Web pour l'interface, nous pouvons facilement utiliser une ou plusieurs polices (dossier actifs/polices) ou les échanger.

Pour terminer ce tour d'horizon, notons que lorsque l'on compile en mode développement (wails dev), en plus de nous permettre de recharger à chaud, on peut non seulement observer les changements effectués (aussi bien dans le backend que dans le frontend) dans dans la fenêtre de l'application elle-même, mais également dans un navigateur web via l'adresse http://localhost:34115, puisqu'un serveur web est démarré. Cela vous permet d'utiliser vos extensions de développement de navigateur préférées. Même s'il faut dire que Wails lui-même nous fournit des outils de développement très utiles, lorsque nous faisons un clic droit sur la fenêtre de l'application (uniquement en mode développement) et choisissons "Inspecter l'élément" :

A minimalist password manager desktop app: a foray into Golang

II - Et maintenant… une plongée dans le HTML, CSS et JavaScript ?


// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {models} from '../models';

export function AddPasswordEntry(arg1:string,arg2:string,arg3:string):Promise<string>;

export function CheckMasterPassword(arg1:string):Promise<boolean>;

export function DeleteEntry(arg1:string):Promise<void>;

export function Drop():Promise<void>;

export function GetAllEntries():Promise<Array<models.PasswordEntry>>;

export function GetEntryById(arg1:string):Promise<models.PasswordEntry>;

export function GetLanguage():Promise<string>;

export function GetMasterPassword():Promise<boolean>;

export function GetPasswordCount():Promise<number>;

export function SaveLanguage(arg1:string):Promise<void>;

export function SaveMasterPassword(arg1:string):Promise<string>;

export function UpdateEntry(arg1:models.PasswordEntry):Promise<boolean>;
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Comme vous pouvez le voir, j'ai ajouté 4 packages JavaScript à Svelte (en dehors du Tailwindcss Daisyui déjà mentionné) :

  • svelte-copy, pour faciliter la copie du nom d'utilisateur et du mot de passe dans le presse-papiers.
  • svelte-i18n, pour la gestion du i18n, c'est à dire permettre à l'utilisateur de changer la langue de l'application.
  • svelte-spa-router, une petite bibliothèque de routage pour Svelte, qui facilite le changement de vue dans la fenêtre de l'application, puisque cela ne vaut pas la peine, dans ce cas, d'utiliser le routage "officiel" fourni par SvelteKit.
  • sweetalert2, utilisez-le essentiellement pour créer des modaux/boîtes de dialogue facilement et rapidement.

Le point d'entrée de chaque SPA est le fichier main.js (ou main.ts), alors commençons par ça :

...
.
├── index.html
├── package.json
├── package.json.md5
├── package-lock.json
├── postcss.config.js
├── README.md
├── src
│   ├── App.svelte
│   ├── assets
│   │   ├── fonts
│   │   │   ├── nunito-v16-latin-regular.woff2
│   │   │   └── OFL.txt
│   │   └── images
│   │       └── logo-universal.png
│   ├── lib
│   │   ├── BackBtn.svelte
│   │   ├── BottomActions.svelte
│   │   ├── EditActions.svelte
│   │   ├── EntriesList.svelte
│   │   ├── Language.svelte
│   │   ├── popups
│   │   │   ├── alert-icons.ts
│   │   │   └── popups.ts
│   │   ├── ShowPasswordBtn.svelte
│   │   └── TopActions.svelte
│   ├── locales
│   │   ├── en.json
│   │   └── es.json
│   ├── main.ts
│   ├── pages
│   │   ├── About.svelte
│   │   ├── AddPassword.svelte
│   │   ├── Details.svelte
│   │   ├── EditPassword.svelte
│   │   ├── Home.svelte
│   │   ├── Login.svelte
│   │   └── Settings.svelte
│   ├── style.css
│   └── vite-env.d.ts
├── svelte.config.js
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── wailsjs
    ├── go
    │   ├── main
    │   │   ├── App.d.ts
    │   │   └── App.js
    │   └── models.ts
    └── runtime
        ├── package.json
        ├── runtime.d.ts
        └── runtime.js

...
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

J'ai mis en évidence les éléments que j'ai ajoutés au squelette généré par la Wails CLI. La bibliothèque svelte-i18n nécessite que les fichiers JSON contenant des traductions soient enregistrés dans le fichier main.js/ts, en même temps que la définition de la langue fallback/initial (bien que comme nous le Je verrai, cela sera manipulé plus tard en fonction de ce que l'utilisateur a sélectionné comme préférences). Les fichiers JSON contenant les traductions sont au format :

// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {models} from '../models';

export function AddPasswordEntry(arg1:string,arg2:string,arg3:string):Promise<string>;

export function CheckMasterPassword(arg1:string):Promise<boolean>;

export function DeleteEntry(arg1:string):Promise<void>;

export function Drop():Promise<void>;

export function GetAllEntries():Promise<Array<models.PasswordEntry>>;

export function GetEntryById(arg1:string):Promise<models.PasswordEntry>;

export function GetLanguage():Promise<string>;

export function GetMasterPassword():Promise<boolean>;

export function GetPasswordCount():Promise<number>;

export function SaveLanguage(arg1:string):Promise<void>;

export function SaveMasterPassword(arg1:string):Promise<string>;

export function UpdateEntry(arg1:models.PasswordEntry):Promise<boolean>;
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Je trouve le système de cette bibliothèque simple et pratique pour faciliter les traductions des applications Svelte (vous pouvez consulter sa documentation pour plus de détails) :

/* package.json */
...
},
  "dependencies": {
    "svelte-copy": "^2.0.0",
    "svelte-i18n": "^4.0.1",
    "svelte-spa-router": "^4.0.1",
    "sweetalert2": "^11.14.5"
  }
...

Copier après la connexion
Copier après la connexion
Copier après la connexion

Vous pouvez également utiliser des sites comme celui-ci, qui vous aideront à traduire les fichiers JSON dans différentes langues. Cependant, le problème est que lorsque vous remplissez vos fichiers .svelte avec le format $, vous devez les suivre manuellement, ce qui est fastidieux et sujet aux erreurs. Je ne connais aucun moyen d'automatiser cette tâche, si quelqu'un le sait, je serais intéressé si vous me le faites savoir ?… Sinon, je devrais penser à une sorte de script pour faire ce travail.

La prochaine étape dans la création de l'interface, comme dans toute application Svelte, est le fichier App.svelte :

/* main.ts */

import { mount } from 'svelte'
import './style.css'
import App from './App.svelte'
import { addMessages, init } from "svelte-i18n"; // ⇐ ⇐
import en from './locales/en.json'; // ⇐ ⇐
import es from './locales/es.json'; // ⇐ ⇐

addMessages('en', en); // ⇐ ⇐
addMessages('es', es); // ⇐ ⇐

init({
  fallbackLocale: 'en', // ⇐ ⇐
  initialLocale: 'en', // ⇐ ⇐
});

const app = mount(App, {
  target: document.getElementById('app')!,
})

export default app

Copier après la connexion
Copier après la connexion

Ici nous utilisons GetMasterPassword qui est une binding générée automatiquement lors de la compilation de l'application et qui a été déclarée comme méthode publique de la struct App (voir la première partie de cette série). Cette fonction interroge la base de données et, dans le cas où un mot de passe principal y est enregistré, elle considère l'utilisateur comme déjà enregistré (elle renvoie une promesse qui enveloppe une valeur booléenne), en lui demandant d'entrer ledit mot de passe pour lui permettre d'accéder au reste. des vues. S'il n'y a pas de mot de passe principal dans la base de données, l'utilisateur est considéré comme "nouveau" et il lui est demandé de générer son propre mot de passe pour accéder à l'application pour la première fois.

Enfin, lors du montage du composant Login.svelte, nous faisons quelque chose qui est important pour le reste de l'application. Bien que la bibliothèque svelte-i18n nous oblige à déclarer le code de langue initial, comme nous l'avons déjà vu, lors du montage de Login.svelte, nous demandons à la base de données (en utilisant la liaison GetLanguage) de vérifier si un code de langue est enregistré. Dans le cas où la base de données renvoie une chaîne vide, c'est-à-dire si aucune langue n'est configurée comme préférence de l'utilisateur, svelte-i18n utilisera la valeur configurée comme initialLocale. Si par contre une langue est configurée, cette langue sera définie (locale.set(result);) et l'événement "change_titles" sera émis, auquel seront transmis les titres traduits de la barre de titre et les boîtes de dialogue natives de l'application. à gérer par le backend :

/* frontend/src/locales/en.json */

{
    "language": "Language",
    "app_title": "Nu-i uita • minimalist password store",
    "select_directory": "Select the directory where to save the data export",
    "select_file": "Select the backup file to import",
    "master_password": "Master Password ?",
    "generate": "Generate",
    "insert": "Insert",
    "login": "Login",
    ...
}


/* frontend/src/locales/es.json */

{
    "language": "Idioma",
    "app_title": "Nu-i uita • almacén de contraseñas minimalista",
    "select_directory": "Selecciona el directorio donde guardar los datos exportados",
    "select_file": "Selecciona el archivo de respaldo que deseas importar",
    "master_password": "Contraseña Maestra ?",
    "generate": "Generar",
    "insert": "Insertar",
    "login": "Inciar sesión",
    ...
}

Copier après la connexion
Copier après la connexion

Voici la logique de gestion de la connexion :

...
.
├── index.html
├── package.json
├── package.json.md5
├── package-lock.json
├── postcss.config.js
├── README.md
├── src
│   ├── App.svelte
│   ├── assets
│   │   ├── fonts
│   │   │   ├── nunito-v16-latin-regular.woff2
│   │   │   └── OFL.txt
│   │   └── images
│   │       └── logo-universal.png
│   ├── lib
│   │   ├── BackBtn.svelte
│   │   ├── BottomActions.svelte
│   │   ├── EditActions.svelte
│   │   ├── EntriesList.svelte
│   │   ├── Language.svelte
│   │   ├── popups
│   │   │   ├── alert-icons.ts
│   │   │   └── popups.ts
│   │   ├── ShowPasswordBtn.svelte
│   │   └── TopActions.svelte
│   ├── locales
│   │   ├── en.json
│   │   └── es.json
│   ├── main.ts
│   ├── pages
│   │   ├── About.svelte
│   │   ├── AddPassword.svelte
│   │   ├── Details.svelte
│   │   ├── EditPassword.svelte
│   │   ├── Home.svelte
│   │   ├── Login.svelte
│   │   └── Settings.svelte
│   ├── style.css
│   └── vite-env.d.ts
├── svelte.config.js
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── wailsjs
    ├── go
    │   ├── main
    │   │   ├── App.d.ts
    │   │   └── App.js
    │   └── models.ts
    └── runtime
        ├── package.json
        ├── runtime.d.ts
        └── runtime.js

...
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

En termes simples : newPassword, l'état lié à l'entrée qui obtient ce que l'utilisateur tape, est d'abord vérifié par onLogin pour voir s'il comporte au moins 6 caractères et qu'ils sont tous des caractères ASCII, c'est-à-dire qu'ils ne font qu'un octet (voir la raison dans la première partie de cette série) par cette petite fonction const isAscii = (str: string): boolean => /^[x00-x7F] $/.test(str);. Si la vérification échoue, la fonction revient et affiche un avertissement toast à l'utilisateur. Ensuite, s'il n'y a pas de mot de passe maître enregistré dans la base de données (isLogin = false), quel que soit le type d'utilisateur, il est enregistré par la fonction SaveMasterPassword (une liaison générée par Wails) ; Si la promesse est résolue avec succès (renvoie une chaîne uuid comme Id de l'enregistrement stocké dans la base de données), l'utilisateur est redirigé vers la vue d'accueil par le svelte-spa-router méthode push de la bibliothèque. Inversement, si le mot de passe réussit le contrôle de longueur et d'absence de caractères non-ASCII et qu'il existe un mot de passe principal dans la base de données (isLogin = true), alors la fonction CheckMasterPassword vérifie son identité par rapport à celui stocké et soit amène l'utilisateur à la vue d'accueil (promesse résolue avec vrai) ou un toast s'affiche indiquant que le mot de passe saisi était incorrect.

La vue centrale de l'application et en même temps la plus complexe est la vue d'accueil. Son HTML est en fait subdivisé en 3 composants : une barre de boutons supérieure avec une entrée de recherche (composant TopActions), une barre de boutons inférieure (composant BottomActions) et une zone centrale où le nombre total d'entrées de mot de passe enregistrées ou la liste de celles-ci est affichée à l'aide de une fenêtre déroulante (composant EntriesList) :

// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {models} from '../models';

export function AddPasswordEntry(arg1:string,arg2:string,arg3:string):Promise<string>;

export function CheckMasterPassword(arg1:string):Promise<boolean>;

export function DeleteEntry(arg1:string):Promise<void>;

export function Drop():Promise<void>;

export function GetAllEntries():Promise<Array<models.PasswordEntry>>;

export function GetEntryById(arg1:string):Promise<models.PasswordEntry>;

export function GetLanguage():Promise<string>;

export function GetMasterPassword():Promise<boolean>;

export function GetPasswordCount():Promise<number>;

export function SaveLanguage(arg1:string):Promise<void>;

export function SaveMasterPassword(arg1:string):Promise<string>;

export function UpdateEntry(arg1:models.PasswordEntry):Promise<boolean>;
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

C'est-à-dire que l'état de recherche (searchTerms) est une chaîne vide, de sorte que s'il y a des termes de recherche, il est réinitialisé et ainsi la liste entière est affichée. Et d'un autre côté, il bascule l'état showList (props isEntriesList dans TopActions) afin que le composant parent affiche ou masque la liste.

Comme nous pouvons le voir dans le diagramme ci-dessus, les deux composants enfants partagent les mêmes props avec l'état searchTerms du parent. Le composant TopActions capture l'entrée de l'utilisateur et la transmet comme état au composant parent Home, qui à son tour la transmet comme props à son composant enfant EntriesList.

La logique principale d'affichage de la liste complète ou d'une liste filtrée par les termes de recherche saisis par l'utilisateur est réalisée, comme prévu, par le composant EntriesList :

...
.
├── index.html
├── package.json
├── package.json.md5
├── package-lock.json
├── postcss.config.js
├── README.md
├── src
│   ├── App.svelte
│   ├── assets
│   │   ├── fonts
│   │   │   ├── nunito-v16-latin-regular.woff2
│   │   │   └── OFL.txt
│   │   └── images
│   │       └── logo-universal.png
│   ├── lib
│   │   ├── BackBtn.svelte
│   │   ├── BottomActions.svelte
│   │   ├── EditActions.svelte
│   │   ├── EntriesList.svelte
│   │   ├── Language.svelte
│   │   ├── popups
│   │   │   ├── alert-icons.ts
│   │   │   └── popups.ts
│   │   ├── ShowPasswordBtn.svelte
│   │   └── TopActions.svelte
│   ├── locales
│   │   ├── en.json
│   │   └── es.json
│   ├── main.ts
│   ├── pages
│   │   ├── About.svelte
│   │   ├── AddPassword.svelte
│   │   ├── Details.svelte
│   │   ├── EditPassword.svelte
│   │   ├── Home.svelte
│   │   ├── Login.svelte
│   │   └── Settings.svelte
│   ├── style.css
│   └── vite-env.d.ts
├── svelte.config.js
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── wailsjs
    ├── go
    │   ├── main
    │   │   ├── App.d.ts
    │   │   └── App.js
    │   └── models.ts
    └── runtime
        ├── package.json
        ├── runtime.d.ts
        └── runtime.js

...
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Comme nous l'avons dit, 2 accessoires sont reçus (listCounter et search) et un état est maintenu (laissez les entrées : models.PasswordEntry[] = $state([]);). Lorsque le composant est monté à la demande de l'utilisateur, le backend est invité à fournir la liste complète des entrées de mot de passe enregistrées. S'il n'y a pas de termes de recherche, ils sont stockés dans l'état ; s'il y en a, un simple filtrage du tableau obtenu est effectué et il est enregistré dans l'état :

// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {models} from '../models';

export function AddPasswordEntry(arg1:string,arg2:string,arg3:string):Promise<string>;

export function CheckMasterPassword(arg1:string):Promise<boolean>;

export function DeleteEntry(arg1:string):Promise<void>;

export function Drop():Promise<void>;

export function GetAllEntries():Promise<Array<models.PasswordEntry>>;

export function GetEntryById(arg1:string):Promise<models.PasswordEntry>;

export function GetLanguage():Promise<string>;

export function GetMasterPassword():Promise<boolean>;

export function GetPasswordCount():Promise<number>;

export function SaveLanguage(arg1:string):Promise<void>;

export function SaveMasterPassword(arg1:string):Promise<string>;

export function UpdateEntry(arg1:models.PasswordEntry):Promise<boolean>;
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Dans la liste affichée, l'utilisateur peut effectuer 2 actions. La première consiste à afficher le détail de la saisie, qui s'effectue lorsqu'il clique sur le bouton correspondant : onclick={() => push(`/details/${entry.Id}`)}. En gros, on appelle la méthode push de la bibliothèque de routage pour amener l'utilisateur vers la vue détails, mais en passant le paramètre Id correspondant à l'élément en question.

L'autre action que l'utilisateur peut effectuer est de supprimer un élément de la liste. S'il clique sur le bouton correspondant, une fenêtre contextuelle de confirmation s'affichera, appelant la fonction showAlert. Cette fonction appelle à son tour showWarning, qui est en fait une couche d'abstraction sur la bibliothèque sweetalert2 (toutes les fonctions qui appellent la bibliothèque sweetalert2 se trouvent dans frontend/src/lib/popups/popups.ts). Si l'utilisateur confirme l'action de suppression, la liaison DeleteEntry est appelée (pour la supprimer de la base de données) et, à son tour, si la promesse qu'elle renvoie est résolue, deleteItem est appelé (pour la supprimer du tableau stocké dans l'état des entrées). :

/* package.json */
...
},
  "dependencies": {
    "svelte-copy": "^2.0.0",
    "svelte-i18n": "^4.0.1",
    "svelte-spa-router": "^4.0.1",
    "sweetalert2": "^11.14.5"
  }
...

Copier après la connexion
Copier après la connexion
Copier après la connexion

L'autre composant de la vue Accueil (BottomActions) est beaucoup plus simple : il ne reçoit pas de props et se limite à rediriger l'utilisateur vers différentes vues (Paramètres, À propos ou AddPassword).

Les vues AddPassword et EditPassword partagent une logique très similaire et sont également similaires à la vue Connexion. Les deux ne permettent pas à l'utilisateur de saisir des espaces au début et à la fin de ce qu'il a saisi dans la saisie de texte et suivent la même politique que la vue de connexion consistant à exiger que les mots de passe comportent au moins 6 caractères ASCII. Fondamentalement, ce qui les distingue, c'est qu'ils appellent les liens générés par Wails pertinents pour l'action qu'ils doivent effectuer :

/* main.ts */

import { mount } from 'svelte'
import './style.css'
import App from './App.svelte'
import { addMessages, init } from "svelte-i18n"; // ⇐ ⇐
import en from './locales/en.json'; // ⇐ ⇐
import es from './locales/es.json'; // ⇐ ⇐

addMessages('en', en); // ⇐ ⇐
addMessages('es', es); // ⇐ ⇐

init({
  fallbackLocale: 'en', // ⇐ ⇐
  initialLocale: 'en', // ⇐ ⇐
});

const app = mount(App, {
  target: document.getElementById('app')!,
})

export default app

Copier après la connexion
Copier après la connexion

L'autre vue quelque peu complexe est celle des Paramètres. Celui-ci a un composant Language qui reçoit comme props LanguageName de son composant parent (Paramètres) :

/* frontend/src/locales/en.json */

{
    "language": "Language",
    "app_title": "Nu-i uita • minimalist password store",
    "select_directory": "Select the directory where to save the data export",
    "select_file": "Select the backup file to import",
    "master_password": "Master Password ?",
    "generate": "Generate",
    "insert": "Insert",
    "login": "Login",
    ...
}


/* frontend/src/locales/es.json */

{
    "language": "Idioma",
    "app_title": "Nu-i uita • almacén de contraseñas minimalista",
    "select_directory": "Selecciona el directorio donde guardar los datos exportados",
    "select_file": "Selecciona el archivo de respaldo que deseas importar",
    "master_password": "Contraseña Maestra ?",
    "generate": "Generar",
    "insert": "Insertar",
    "login": "Inciar sesión",
    ...
}

Copier après la connexion
Copier après la connexion

Le code HTML de ce composant est un seul select qui gère le choix de langue de l'utilisateur. Dans son événement onchange, il reçoit une fonction (handleChange) qui fait 3 choses :

  • définit la langue sur le frontend à l'aide de la bibliothèque svelte-i18n
  • émet un événement ("change_titles") pour que le runtime Wails change le titre de la barre de titre de l'application et les titres des boîtes de dialogue Sélectionner un répertoire et Sélectionner un fichier en relation à l'action précédente
  • enregistre la langue sélectionnée par l'utilisateur dans la base de données afin qu'au prochain démarrage de l'application, elle s'ouvre configurée avec cette langue.

En revenant à la vue Paramètres, l'ensemble de son fonctionnement est régi par une série d'événements qui sont envoyés et reçus vers ou depuis le backend. Le plus simple de tous est le bouton Quitter : lorsque l'utilisateur clique dessus, un événement quit est déclenché et écouté dans le backend et l'application se ferme (onclick={() => EventsEmit("quit")}). Une astuce informe l'utilisateur que la touche Échap (raccourci) effectue la même action, comme nous l'avons déjà expliqué.

Le bouton de réinitialisation appelle une fonction qui affiche une fenêtre contextuelle :

...
.
├── index.html
├── package.json
├── package.json.md5
├── package-lock.json
├── postcss.config.js
├── README.md
├── src
│   ├── App.svelte
│   ├── assets
│   │   ├── fonts
│   │   │   ├── nunito-v16-latin-regular.woff2
│   │   │   └── OFL.txt
│   │   └── images
│   │       └── logo-universal.png
│   ├── lib
│   │   ├── BackBtn.svelte
│   │   ├── BottomActions.svelte
│   │   ├── EditActions.svelte
│   │   ├── EntriesList.svelte
│   │   ├── Language.svelte
│   │   ├── popups
│   │   │   ├── alert-icons.ts
│   │   │   └── popups.ts
│   │   ├── ShowPasswordBtn.svelte
│   │   └── TopActions.svelte
│   ├── locales
│   │   ├── en.json
│   │   └── es.json
│   ├── main.ts
│   ├── pages
│   │   ├── About.svelte
│   │   ├── AddPassword.svelte
│   │   ├── Details.svelte
│   │   ├── EditPassword.svelte
│   │   ├── Home.svelte
│   │   ├── Login.svelte
│   │   └── Settings.svelte
│   ├── style.css
│   └── vite-env.d.ts
├── svelte.config.js
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── wailsjs
    ├── go
    │   ├── main
    │   │   ├── App.d.ts
    │   │   └── App.js
    │   └── models.ts
    └── runtime
        ├── package.json
        ├── runtime.d.ts
        └── runtime.js

...
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Si l'utilisateur accepte l'action, la liaison Drop est appelée, ce qui nettoie toutes les collections de la base de données, et si la promesse qu'elle renvoie est résolue, elle envoie l'utilisateur à la vue de connexion, affichant un modal indiquant le succès de l'action.

Les deux autres actions restantes sont similaires, regardons donc Importer des données.

Si l'utilisateur clique sur le bouton correspondant, un événement est émis (onclick={() => EventsEmit("import_data")}) qui est écouté dans le backend. Une fois reçu, la boîte de dialogue native Sélectionner un fichier s'ouvre pour permettre à l'utilisateur de sélectionner le fichier de sauvegarde. Si l'utilisateur choisit le fichier, la variable contenant le chemin (fileLocation) ne contiendra pas de chaîne vide et cela déclenchera un événement dans le backend ("enter_password") qui est désormais écouté dans le frontend pour, à son tour, afficher un nouvelle fenêtre contextuelle demandant le mot de passe principal utilisé lors de l'exportation. Encore une fois, le frontend émettra un autre événement ("mot de passe") qui porte le mot de passe principal saisi par l'utilisateur. Ce nouvel événement, lorsqu'il est reçu dans le backend, exécute la méthode ImportDump du package Db qui effectue le travail de lecture et de restauration des données dans la base de données à partir du fichier de sauvegarde sélectionné par l'utilisateur. En conséquence, un nouvel événement ("imported_data") est émis, qui transporte le résultat (réussi ou non) de son exécution sous forme de données jointes. Le frontend, lorsqu'il reçoit l'événement, n'a qu'à effectuer 2 tâches :

  • si le résultat a réussi, définissez la langue qui a été enregistrée dans le fichier de sauvegarde et affichez un modal indiquant le succès de l'action
  • Si pour une raison quelconque l'importation n'a pas pu être effectuée, affichez l'erreur et sa cause.

Tout cela est beaucoup plus facile à voir dans la logique du code qu'à expliquer avec des mots ?:

...
.
├── index.html
├── package.json
├── package.json.md5
├── package-lock.json
├── postcss.config.js
├── README.md
├── src
│   ├── App.svelte
│   ├── assets
│   │   ├── fonts
│   │   │   ├── nunito-v16-latin-regular.woff2
│   │   │   └── OFL.txt
│   │   └── images
│   │       └── logo-universal.png
│   ├── lib
│   │   ├── BackBtn.svelte
│   │   ├── BottomActions.svelte
│   │   ├── EditActions.svelte
│   │   ├── EntriesList.svelte
│   │   ├── Language.svelte
│   │   ├── popups
│   │   │   ├── alert-icons.ts
│   │   │   └── popups.ts
│   │   ├── ShowPasswordBtn.svelte
│   │   └── TopActions.svelte
│   ├── locales
│   │   ├── en.json
│   │   └── es.json
│   ├── main.ts
│   ├── pages
│   │   ├── About.svelte
│   │   ├── AddPassword.svelte
│   │   ├── Details.svelte
│   │   ├── EditPassword.svelte
│   │   ├── Home.svelte
│   │   ├── Login.svelte
│   │   └── Settings.svelte
│   ├── style.css
│   └── vite-env.d.ts
├── svelte.config.js
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── wailsjs
    ├── go
    │   ├── main
    │   │   ├── App.d.ts
    │   │   └── App.js
    │   └── models.ts
    └── runtime
        ├── package.json
        ├── runtime.d.ts
        └── runtime.js

...
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Il convient de mentionner que la fonction d'exécution Wails qui enregistre les auditeurs sur le frontend (EventsOn) renvoie une fonction qui, lorsqu'elle est appelée, annule ledit auditeur. Il est pratique d'annuler ces auditeurs lorsque le composant est détruit. De la même manière que React le hook onMount peut "nettoyer" lesdits auditeurs en leur faisant renvoyer une fonction de nettoyage qui, dans ce cas, appellera toutes les fonctions renvoyées par EventsOn que nous avons pris la précaution de sauvegarder dans des fichiers séparés. variables :

// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {models} from '../models';

export function AddPasswordEntry(arg1:string,arg2:string,arg3:string):Promise<string>;

export function CheckMasterPassword(arg1:string):Promise<boolean>;

export function DeleteEntry(arg1:string):Promise<void>;

export function Drop():Promise<void>;

export function GetAllEntries():Promise<Array<models.PasswordEntry>>;

export function GetEntryById(arg1:string):Promise<models.PasswordEntry>;

export function GetLanguage():Promise<string>;

export function GetMasterPassword():Promise<boolean>;

export function GetPasswordCount():Promise<number>;

export function SaveLanguage(arg1:string):Promise<void>;

export function SaveMasterPassword(arg1:string):Promise<string>;

export function UpdateEntry(arg1:models.PasswordEntry):Promise<boolean>;
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Pour terminer cette revue de la partie frontend de notre application, il ne reste plus qu'à dire quelque chose sur le composant About. Cela a peu de logique puisqu'il se limite à afficher des informations sur l'application comme c'est l'habitude dans un about. Il faut cependant dire que, comme on peut le voir, la vue affiche un lien vers le référentiel de l'application. Évidemment, dans une page Web normale, une balise d'ancrage () nous ferait naviguer vers le lien correspondant, mais dans une application de bureau, cela ne se produirait pas si Wails n'avait pas une fonction spécifique (BrowserOpenURL) pour cela dans son exécution. :

/* package.json */
...
},
  "dependencies": {
    "svelte-copy": "^2.0.0",
    "svelte-i18n": "^4.0.1",
    "svelte-spa-router": "^4.0.1",
    "sweetalert2": "^11.14.5"
  }
...

Copier après la connexion
Copier après la connexion
Copier après la connexion

Cela construira le binaire dans le dossier build/bin. Cependant, pour choisir d'autres options de construction ou effectuer une compilation croisée, vous souhaiterez peut-être jeter un œil à la documentation Wails CLI.

Pour cette application, je pense l'avoir déjà évoqué dans la première partie de cette série, je me suis uniquement concentré sur la compilation pour Windows et Linux. Pour effectuer ces tâches (qui, en raison des tests, sont répétitives) de manière confortable, j'ai créé quelques petits scripts et un Makefile qui les "coordonne".

La commande make create-bundles crée pour la version Linux un fichier compressé .tar.xz avec l'application et un Makefile qui agit comme un 'installateur' qui installe l'exécutable, une entrée de bureau pour créer une entrée dans le Menu Démarrer et l'icône de l'application correspondante. Pour la version Windows, le binaire est simplement compressé sous forme de .zip dans un dossier appelé dist/. Cependant, si vous préférez une construction automatisée multiplateforme, Wails dispose d'une action Github qui vous permet de télécharger (option par défaut) les artefacts générés. à votre référentiel.

Notez que si vous utilisez la commande make create-bundles lors de son exécution, elle appellera les commandes Wails wails build -clean -upx (dans le cas de Linux) ou wails build -skipbindings -s -platform windows/amd64 - upx (dans le cas de Windows). L'indicateur -upx fait référence à la compression du binaire à l'aide de l'utilitaire UPX que vous auriez dû installer sur votre ordinateur. Une partie du secret de la petite taille de l'exécutable est due au magnifique travail de compression effectué par cet utilitaire.

Enfin, notez que les scripts de build ajoutent automatiquement la balise du référentiel actuel à la vue À propos et après la build restaurent sa valeur par défaut (DEV_VERSION).

Ouf ! Ces 2 posts ont fini par être plus longs que je ne le pensais ! Mais j’espère qu’ils vous ont plu et surtout qu’ils vous aident à réfléchir à de nouveaux projets. Apprendre quelque chose en programmation fonctionne comme ça…

N'oubliez pas que vous pouvez trouver tout le code de l'application dans ce référentiel GitHub.

Je suis sûr que je vous reverrai dans d'autres publications. Bon codage ?!!

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal