Fermer

mai 15, 2019

Composants de page Web SVG pour IoT et Makers (2ème partie)22 minutes de lecture



Lors de la conception d'interfaces pour une page Web IoT, on a toujours beaucoup d'options. Dans la partie précédente de cet article, Richard Leddy a apporté des éclaircissements sur la signification de l'IoT et sur la manière dont Vue.js peut être utilisé pour héberger des groupes d'interfaces homme-machine IoT. Aujourd'hui, examinons de plus près le chargement paresseux des panneaux et comment garder l'état de Vue synchronisé avec les périphériques.

Nous avons donc déjà les moyens de charger dynamiquement un menu d'icônes SVG conçu pour réagir en chargeant les panneaux si nous le voulons. désir, mais les icônes ne sont pas des composants réels. Nous avons pu utiliser une astuce simple consistant à importer le fichier SVG pour chaque icône et à le transférer dans l'application Vue. C'était assez simple de générer une liste d'icônes, et chaque icône réagissait de la même manière, à l'exception de petites différences de données. La différence de données permettait de lier le nom d'un panneau à chaque icône de manière à ce que le gestionnaire du clic de l'icône puisse le transmettre.

Lorsqu'un panneau est chargé sous la forme d'un composant Vue, tout ce qui concerne le panneau et ses composants doivent être chargés, modèles, JavaScript, etc. La tâche de gérer le chargement du panneau est donc plus importante que celle que nous avons eu jusqu’à présent dans cette discussion.

Voyons la manière dont Vue fournit un point d’accès pour le chargement asynchrone. L'extrait suivant est extrait du guide Vue .

 Vue.component ('async-example', function (résoudre, rejeter) {
  setTimeout (function () {
    // passe la définition du composant au rappel de résolution
    résoudre({
      template: '
Je suis async!
'     })   }, 1000) })

Le guide nous indique que la fonction setTimeout est un exemple d'utilisation de Synchronicity avec les composants Vue. Notez que là où il y avait auparavant un objet comme deuxième paramètre de Vue.component il existe maintenant une fonction, appelée fonction d'usine. Dans la résolution du rappel se trouve une définition de composant, ce qui aurait été le deuxième paramètre de Vue.component auparavant.

Je devais donc regarder cet exemple un peu avant. avait du sens pour moi. Voici un autre exemple qui me convient mieux:

 Vue.component ('async-example', function (résoudre, rejeter) {
  // Vue appellera cette fonction et promettra de gérer
  // quand il revient avec les données.
  
  // cette fonction peut alors appeler un chargeur d'objet prometteur
  // ici la fonction 'loader' est une fonction abstraite.
  // Très probablement, l'application utilisera 'fetch'
  // mais ça pourrait être autre chose.
  chargeur ('/ mon / ressource / sur / serveur.json').
    then (fonction (JSON_data) {
         var object = transformJSONToJSObject (JSON_data);
          résoudre (objet)
    }). catch ((error) => {le manipuler});

Il semble approprié de créer une fonction plus générale pour contourner ce formulaire.

 function composantLoader (c_name, resource_url) {
  Vue.component (c_name, function (résoudre, rejeter) {
    chargeur (URL_ressource).
      then (fonction (JSON_data) {
           var object = transformJSONToJSObject (JSON_data);
            résoudre (objet)
      }). catch ((error) => {le manipuler});
}

Donc, en général, pour charger un composant, il vous suffirait d'une ligne comme celle-ci:

 composantLoader ('ThermoPanel', '. / JSON / thermo-panel.json');

Alors, quel est le fichier JSON chargé? Il peut inclure tout ce qui concerne le composant. Dans ce cas, en tant que composant de panneau, il peut inclure des thermomètres, des commutateurs de machine, des curseurs, des jauges, etc. Bien qu’il ait semblé plus pratique de conserver les composants sur la page Web, il serait peut-être plus judicieux d’utiliser le champ de sous-composant présenté dans l’exemple plus long de «thermo-panel» que nous avons créé auparavant, ainsi que pour les autres panneaux de construction similaire. Le JSON contiendra une structure de panneau complète.

Toutefois, si le lecteur remarquera l'inclusion de l'appel de fonction dans transformJSONToJSObject il comprendra que le code JSON pourrait être codé de manière à faciliter le transport et pour permettre à un serveur de gérer plus facilement la définition. Après tout, la définition inclura des modèles SVG complets, des définitions de fonctions et d'autres expressions JavaScript. De plus, l'objet JSON peut contenir plus que la définition du panneau, car certaines informations peuvent simplement aider à la comptabilité ou à la validation. On peut donc s'attendre à ce que l'objet reçoive un traitement lors de sa réception.

En ce qui concerne le codage, les données provenant du serveur peuvent être codées de différentes manières. Peut-être que ce sera simplement une URL encodée. Ou plus sûrement, il pourrait être chiffré. Pour cette discussion, nous pouvons simplement utiliser le codage d'URL.

Certains des outils disponibles pour créer des applications Vue prennent sans aucun doute en charge la transformation JSON. Mais, cette discussion a jusqu’à présent évité l’utilisation d’outils de ligne de commande. Cette omission n’est pas si grave car nous avons également utilisé Vue avec le minimum de ressources, en utilisant une seule balise de script pour la référence au CDN. Cependant, je recommande certainement de consulter les outils de ligne de commande, en particulier pour l'organisation des projets.

Lorsque le JSON arrive à la page, étant donné que le composant est entièrement assemblé avec des sous-composants, il n'est plus nécessaire de récupérer les éléments. Nous pouvons supposer que toutes les composantes entreront pleinement définies pour la suite de la discussion. Toutefois, l’assemblage de hiérarchies complètes de composants nécessitera un outil de ligne de commande à un moment donné.

Le processus d’édition SVG exigera également quelques travaux. Les processus d’édition SVG permettent au concepteur de dessiner un panneau et tous les composants qu’il contient. Toutefois, chaque sous-composant doit être identifié, appelé dans un groupe ou attribué à un remplaçant. Toute approche d'utilisation du dessin nécessite un traitement du SVG afin que les balises de composant Vue puissent remplacer les groupes ou les éléments graphiques. De cette manière, tout rendu d'artiste peut devenir un modèle. Et, les sous-composants dessinés devront être désassemblés en modèles pour les sous-composants Vue.

Ce type de parcimonie est contraire au flux de travail de la plupart des frameworks JavaScript. Les frameworks concernent l’assemblage de pages. Mais éditer ou dessiner donne quelque chose de déjà assemblé par un artiste. En pratique, le résultat de la modification ne fournit pas de fichier texte correspondant directement à la définition d'un composant du cadre.

Il est possible d'examiner plus en détail le processus de modification dans une autre discussion. Il y a beaucoup de choses. Mais, pour le moment, nous disposons des outils nécessaires pour charger des composants hiérarchiques et les rendre vivants.

L'application paresseuse

Pour la construction de nos panneaux IoT, nous disposons déjà d'une barre de sélection qui répond aux recherches. Et nous avons un moyen de charger des composants quand nous en avons besoin. Nous avons juste besoin de connecter ces parties. Et, enfin, nous devons nous assurer que les panneaux apparaissent et qu'ils commencent à fonctionner quand ils le font.

Le chargement paresseux des panneaux effectué par le code async ci-dessus fournit un aperçu d'une idée. Heureusement, certaines personnes ont fait des expériences pour trouver des moyens de s’assurer que toutes sortes de composants peuvent être chargés. Il y a une entrée codepen qui montre comment mettre à jour les applications Vue avec de nouveaux composants de types variés. C'est le mécanisme nécessaire pour mettre à jour une partie désignée de la page avec différents types de panneaux.

Grâce à la possibilité d'ajouter différents types de panneaux et à un mécanisme simple pour charger leurs définitions, nous pouvons enfin avoir notre page de recherche de panneau.

Voici le code HTML dont nous avons besoin dans notre page pour que l’application Vue puisse placer des composants de manière dynamique:

 

La balise du composant est une méta-balise Vue. Voir la référence pour composants dynamiques . Les propriétés, attributs spéciaux, utilisées pour la balise du composant dans ce cas sont est et clé . L'attribut est pour les composants dynamiques. Et la clé garantit que les nouveaux enfants auront des identités différentes les unes des autres et aide Vue à décider quoi dessiner.

«Les enfants du même parent commun doivent avoir des clés uniques. Des clés en double provoqueront des erreurs de rendu. ”

La balise template parcourt les composants fournis dans le champ de données panelList de l'application.

Donc, en commençant par le niveau d'application Définition de la vue pour l'application icône, nous pouvons apporter des modifications pour inclure la liste de panneaux dans les éléments de données. (Appelons-le maintenant panelApp).

 var panelApp = new Vue ({
        el: '#PanelApp',
        Les données: {
        iconList: [  // Where is the data? Still on the server.
        ],
        panelList: [
        ],
        queryToken: "Thermo Batch" // a choisi un nom pour la démo
        },
        méthodes: {
          goGetPanel: function (pname) {
            //
              var url = panelURL (pname); // ceci est personnalisé sur le site.
              fetch (url) .then ((response) => {// c'est maintenant le navigateur natif
                response.text (). then ((text) => {
                      var newData = decodeURIComponent (text);
                       eval (pHat); // widgdef = object def, doit être une affectation
                       pHat = widgdef;
                     var pnameHat = pname + pcount ++;
                     pHat.name = pnameHat; // c'est nécessaire pour la clé
                     this.panelList.push (pHat); // maintenant c'est là.
                  }). catch (error => {/ * le manipuler * /});
          }
        }
    });

En plus d'ajouter dans le panneau, goGetPanel est maintenant sous une forme nécessaire pour obtenir une définition de composant à partir d'une base de données ou d'un autre magasin. Le serveur doit faire attention à fournir du code JavaScript au format correct. En ce qui concerne ce à quoi l'objet ressemble venant du serveur, nous l'avons déjà vu. C'est le type d'objet utilisé en tant que paramètre de Vue.component .

Voici le corps complet de l'application Vue qui fournit un menu sous forme de résultat de recherche et un endroit où placer des panneaux extraits du serveur lorsque l'utilisateur clique sur une icône.

 
  
  

Requête de groupes MCU

Il s'agit de groupes répondant à cette requête: {{queryToken}}.

  
     
     
     
       
  

Dans le dernier div la balise du composant comporte désormais un paramètre ref lié au nom du panneau. Le paramètre ref permet à Vue app d'identifier le composant à mettre à jour avec les données et de garder les composants séparés. Les paramètres ref permettent également à notre application d'accéder aux nouveaux composants chargés dynamiquement.

Dans une version de test de l'application du panneau, j'ai le gestionnaire d'intervalle suivant:

 setInterval (() => {
  var refall = panelApp. $ refs; // tous les enfants nommés qui panneaux
  for (var pname in refall) {// dans un objet
    var pdata = refall [pname][0]; // off Traduction, mais c’est là.
    pdata.temp1 = Math.round (Math.random () * 100); // fait sauter le thermos.
    pdata.temp2 = Math.round (Math.random () * 100);
  }
}, 2000)

Le code fournit une petite animation, changeant les thermomètres de manière aléatoire. Chaque panneau est doté de deux thermomètres et l'application permet à l'utilisateur de continuer à ajouter des panneaux. (Dans la version finale, certains panneaux doivent être jetés.) Les références sont accessibles à l'aide de panelApp. $ Refs un champ créé par Vue à l'aide des informations refs du fichier . ] composant tag.

Voici donc à quoi ressemblent les thermomètres à sauts aléatoires dans un instantané:

 Collection de panneaux animés pour un type de panneau (ou composant) montrant des thermomètres.
de panneaux animés pour un type de panneau (ou composant). ( Grand aperçu )

Connexion du panneau au périphérique IdO

Le dernier morceau de code est donc un test setInterval mettant à jour des thermomètres avec des valeurs aléatoires toutes les deux secondes. Mais ce que nous voulons faire, c'est lire des données réelles à partir de machines réelles. Pour ce faire, nous aurons besoin d’une forme de communication.

Il existe différentes façons de procéder. Mais utilisons maintenant MQTT, qui est un système de messagerie pub / sub. Notre SPWA peut s'abonner aux messages des appareils à tout moment. Quand il reçoit ces messages, SPWA peut diriger chaque message vers le gestionnaire de données approprié pour le panneau mappé sur le périphérique identifié dans le message.

Nous devons donc remplacer le setInterval par un gestionnaire de réponse. Et ce sera pour un panel. Nous voulons probablement mapper les panneaux aux gestionnaires lorsqu'ils sont chargés. Et, il appartient au serveur Web de vérifier que le mappage correct est fourni.

Une fois que le serveur Web et SPWA ont la page prête à fonctionner, le serveur Web n'a plus besoin de gérer la messagerie entre la page et le dispositif. le protocole MQTT spécifie un serveur de routage pour gérer pub / sub. Un certain nombre de serveurs MQTT ont été créés. Certains d'entre eux sont open source. Un très populaire est Mosquito et il en existe quelques-uns développés au-dessus de Node.js.

Le processus pour la page est simple. La SPWA est abonnée à un sujet. Une bonne version d'un sujet est un identifiant pour un MCU, tel qu'une adresse MAC ou un numéro de série. Ou bien, le SPWA pourrait souscrire à toutes les lectures de température. Mais alors, la page devrait filtrer les messages de tous les appareils. La publication dans MQTT est essentiellement une émission ou une multidiffusion.

Voyons l’interface entre SPWA et MQTT.

Initialisation de MQTT sur SPWA

Vous avez le choix entre plusieurs bibliothèques clientes. L'un, par exemple, est un MQTT.js . Une autre est eclipse paho . Il y a plus bien sûr. Utilisons Eclipse Paho car il possède une version stockée sur CDN. Nous avons juste besoin d'ajouter la ligne suivante à notre page:

  

Le client MQTT doit se connecter à un serveur avant de pouvoir envoyer et recevoir des messages. Ainsi, les lignes établissant la connexion doivent également être incluses dans JavaScript. Nous pouvons ajouter une fonction MQTTinitialize qui configure le client et les réponses pour la gestion de la connexion et la réception du message.

 var messagesReady = false;
var mqttClient = null;

fonction MQTTinitialize () {
  mqttClient = new Paho.MQTT.Client (MQTTHostname, Number (MQTTPort), "clientId");
  mqttClient.onMessageArrived = onMessageArrived;
  // connecte le client
  mqttClient.connect ({
           onSuccess: () => {
             messagesReady = true;
           }
        });
  // définit les gestionnaires de rappel
  mqttClient.onConnectionLost = (réponse) => {
    //
    messagesReady = false;
    //
    if (response.errorCode! == 0) {
      console.log ("onConnectionLost:" + response.errorMessage);
    }
    setTimeout (() => {
            MQTTinitialize ()
           }, 1000); // réessaie en une seconde
  };
}

Configuration de l'abonnement

Une fois la connexion prête, le client peut s'abonner à des canaux de messages, y envoyer des messages, etc. Quelques routines suffisent pour effectuer la plupart des tâches nécessaires à la connexion de panneaux aux voies MQTT. [19659005] Pour le panneau SPWA, le moment de la souscription peut être utilisé pour établir l'association entre le panneau et le sujet, l'identificateur de la MCU.

 function panelSubcription (topic, panel) {
    gTopicToPanel [topic] = panneau;
    gPanelToTopic [panel] = sujet;
    mqttClient.subscribe (topic);
}

Etant donné qu'une MCU est en train de publier sur son sujet, le SPWA recevra un message. Ici, le message Paho est décompressé. Ensuite, le message est transmis à la mécanique de l'application.

 function onMessageArrived (pmessage) {
  //
  var topic = pmessage.destinationName;
  var message = pmessage.payloadString;
  //
  var panel = gTopicToPanel [topic];
  deliverToPanel (panneau, message);
}

Il ne reste donc plus qu'à créer deliverToPanel qui devrait être un peu comme le gestionnaire d'intervalles que nous avions auparavant. Toutefois, le panneau est clairement identifié et seules les données codées envoyées dans le message particulier peuvent être mises à jour.

 function deliverToPanel (panneau, message) {
  var refall = panelApp. $ refs; // tous les enfants nommés qui panneaux
  var pdata = refall [panel][0]; // off Traduction, mais c’est là.
  var MCU_updates = JSON.parse (message);
  pour (var ky dans MCU_updates) {
    pdata [ky] = MCU_updates [ky]
  }
}

Cette fonction deliverToPanel est suffisamment abstraite pour permettre toute définition de panneau avec un nombre quelconque de points de données pour l'animation.

Envoi de messages

Pour terminer la boucle d'application entre le MCU et le SPWA, nous définir une fonction pour envoyer un message.

 function sendPanelMessage (panneau, message) {
    var topic = gPanelToTopic [panel];
    var pmessage = new Paho.MQTT.Message (message);
    pmessage.destinationName = topic;
    mqttClient.send (pmessage);
}

La fonction sendPanelMessage se contente d'envoyer le message sur le même chemin que celui auquel la SPWA souscrit.

Comme nous prévoyons de créer des boutons d'icône chargés d'introduire un certain nombre de panneaux. pour un seul groupe de MCU, il y aura plus d'un panneau à gérer. Cependant, nous gardons à l'esprit que chaque panneau correspond à un seul MCU, nous avons donc un mappage un-un pour lequel nous pouvons utiliser deux mappages JavaScript pour la mappe et l'inverse.

Alors, quand envoyons-nous des messages? Habituellement, l'application Panel envoie un message lorsqu'elle souhaite modifier l'état de la MCU.

Maintien de l'état de Vue (Vue) synchronisé avec les périphériques

L'un des avantages de Vue est qu'il est très facile. maintenir le modèle de données synchronisé avec l'activité de l'utilisateur, qui peut modifier des champs, cliquer sur des boutons, utiliser des curseurs, etc. Vous pouvez être sûr que les modifications apportées aux boutons et aux champs seront immédiatement répercutées dans les champs de données des composants.

Mais nous voulons que les modifications déclenchent des messages vers la MCU dès que les modifications se produisent. Nous cherchons donc à utiliser les événements d'interface que Vue peut gouverner. Nous cherchons à réagir à un tel événement, mais seulement une fois que le modèle de données Vue est prêt avec la valeur actuelle.

J'ai créé un autre type de panneau, celui-ci comportant un bouton d'aspect assez artistique (inspiré peut-être de Jackson Pollock). Et, je me suis mis à le transformer en quelque chose dont le clic renvoie l’état au panneau qui le contient. Ce n'était pas un processus si simple.

Une des choses qui m'a dérangé, c'est que j'avais oublié certaines des bizarreries de la gestion de SVG. J'ai d'abord essayé de changer la chaîne de style pour que le champ d'affichage du style CSS soit soit «Aucun», soit «quelque chose». Mais, le navigateur n'a jamais réécrit la chaîne de styles. Mais, comme c'était lourd, j'ai essayé de changer la classe CSS. Cela n'a également eu aucun effet. Mais là, il y a l'attribut de visibilité que la plupart d'entre nous se souviennent de l'ancien HTML (version 1.0 peut-être), mais qui est très à jour en SVG. Et ça marche bien. Tout ce que je devais faire, c'était que l'événement click du bouton se propage.

Vue a conçu des propriétés pour se propager dans une direction, parent à enfant. Ainsi, pour modifier des données dans l'application ou dans le panneau, vous devez envoyer un événement de modification au parent. Ensuite, vous pouvez modifier les données. Le changement de l'élément de données contrôlant le bouton amène Vue à mettre à jour la propriété affectant la visibilité de l'élément SVG que nous avons choisi d'indiquer state.
Voici un exemple:

 Plus d’un type de panneau et plus d’une instance d’animation par type
Enfin, une collection de différents types de panneaux, chacun ayant des instances affectées à des MCU distinctes. ( Grand aperçu )

Chaque instance du panneau de boutons ondulé est indépendante. Donc, certains sont allumés et d'autres sont éteints.

Cet extrait de SVG contient l'indicateur bizarre jaune:

 

La visibilité est peuplée par stateView une variable calculée qui mappe l'état booléen sur une chaîne pour SVG.

Voici le modèle de définition de composant de panneau:

  

Et voici la définition JavaScript du panneau Vue avec ses enfants en tant que sous-composants:

 var widgdef = {
  data: function () {
    var currentPanel = {// au niveau supérieur, valeurs contrôlant les enfants
      bstate: true,
      fluidLevel: Math.round (Math.random () * 100)
    }
    //
    return currentPanel
  },
  template: '# mcu-control-panel-template',
  méthodes: {
    saveChanges: function () {// dans la vie réelle, il y a plus de spécificité
      this.bstate =! this.bstate
      relayToMCU (this.name, "bouton", this.bstate) // à définir
    }
  },
  Composants: {
    'control-switch': {// le bouton bizarre
      accessoires: [’state'],
      template: '# control-switch-template', // pour la démonstration c'est dans la page.
      calculé: {
        // vous avez vu cela dans le SVG ci-dessus.
        stateView: function () {
          retourner (this.state)? "visible": "caché"
        }
      },
      méthodes: {
        // le gestionnaire de bouton se trouve dans le modèle SVG en haut.
        stateChange: function () {// peut envoyer
          this. $ emit ('changé'); // dit au parent. Voir sur l'instance de template
        }
      }
    },
    'gauge': {// un autre joli morceau de SVG
      accessoires: ['level'],
      template: '# gauge-template'
    }
  }
}

Le mécanisme pour un bouton unique intégré à un panneau est maintenant défini. Et, il doit y avoir un crochet pour dire à la MCU que quelque chose s'est passé. Il doit être appelé immédiatement après la mise à jour de l'état des données du composant du panneau. Définissons-le ici:

 function relayToMCU (panel, switchName, bstate) {
  var message = switchName + ':' + bstate // une chaîne de paramètre d'élément on.
  sendPanelMessage (panneau, message)
}

Il n’ya que deux lignes de code pour modifier le mode de fonctionnement du matériel.

Mais c’est un cas assez simple. Tout commutateur peut être considéré comme un appel de fonction à un élément matériel dans le monde. La chaîne peut donc contenir le nom du commutateur et plusieurs autres éléments de données. Ainsi, la méthode de composant que les registres changent devra comporter un traitement personnalisé afin de pouvoir rassembler tous les éléments de données du panneau et les envoyer avec une seule chaîne de commande. Même la chaîne de commande est un peu simple. Si la MCU est assez petite, il peut être nécessaire de traduire la chaîne de commande en code. Si la MCU dispose de nombreuses fonctionnalités, la chaîne de commande peut en réalité être une structure JSON ou peut-être toutes les données hébergées par le panneau.

Dans cette discussion, les boutons du panneau d'icônes contiennent le nom du panneau à extraire. . Cela peut aussi être assez simplifié. Il semble logique que ce paramètre puisse s’appliquer à tout panneau pouvant être stocké dans une base de données d’entreprise. Mais, peut-être que c'est une formule. Peut-être que les informations sur le panneau devraient être regroupées autour de la définition du panneau que nous recevons du serveur. Dans tous les cas, les bases peuvent être facilement développées une fois que certains maux de tête sont résolus, par exemple, si le SVG répond correctement aux clics.

Conclusion

Cette discussion a exposé certaines étapes et décisions fondamentales menant au réalisation d’une application Web à page unique (SPWA) pouvant s’interfacer avec des appareils IoT. Nous savons maintenant comment obtenir des panneaux d'un serveur Web et les transformer en interface MCU.

Cette discussion comporte bien d'autres éléments, dont plusieurs autres peuvent suivre. Commencer par Vue est une chose à laquelle réfléchir. Ensuite, il y a toute l'histoire du MCU, que nous n'avons abordée que brièvement.

En particulier, en choisissant MQTT comme substrat de communication, nous supposons que les dispositifs IoT situés à l'autre bout peuvent d'une manière ou d'une autre être contrôlés par MQTT. Mais, cela peut ne pas toujours être le cas. Des passerelles sont parfois nécessaires si MQTT doit accéder à un périphérique avec des liaisons série ou Bluetooth. Ou peut-être que tout ce dont on a besoin sur la page Web est WebSockets. Néanmoins, nous avons utilisé l'exemple de MQTT pour montrer comment Vue pouvait recevoir et envoyer des données tout en maintenant son état synchronisé avec les périphériques.

Encore une fois, nous n'avons qu'une partie de l'histoire. Cette fois, il s’agit d’une synchronisation car la page devrait pouvoir traiter les alertes et gêner l’utilisateur si un événement critique se produisait. Parfois, les messages peuvent être perdus. Nous devons donc disposer d’un mécanisme d’accusé de réception.

Enfin, j’estime que Vue rend la mise à jour des données dès sa réception assez élégante. Mais, envoyer les changements d'état n'est pas si simple. Il ne semble pas que le travail soit beaucoup plus simple qu'avec JavaScript JavaScript. Mais il y a un moyen et c'est logique.

Peut-être qu'une bibliothèque propre peut être construite pour créer un ensemble universel de composants pour tous les panneaux. Les éléments permettant de créer de telles bibliothèques et de les stocker dans une base de données ont été brièvement mentionnés. Des outils qui vont au-delà de la simple création d'images SVG devront peut-être être développés. Quoi qu’il en soit, il est probable que de nombreuses mesures seront prises pour les prochaines étapes.

 Smashing Editorial (dm, yk, il)




Source link