L’enfer est pavé de bonnes intentions

Notre premier projet étant fait, et le second ayant des besoins commun, j’ai voulu créer ma première librairie et ainsi la partager entre mes projets comme le fait NPM si je publiais. Évidemment je suis sur un git privé, pas sur Github, ni sur NPM, du coup pour le déploiement etc. ben, c’est toute une histoire…

Donc on part d’un code fonctionnel inclus dans un folder de mon app, on crée un nouveau projet de type librairie et on colle ça dedans, on nettoie le premier projet et on fait le lien entre les libs, on compile l’un et l’autre, l’impatience est à son comble ! Et ça ne va pas…

Donc on part d’une lib de référence (Material Design) et on regarde comment ils font, on s’en inspire, on prend les conventions et on observe les liens, on compile etc. Le stress est palpable ! Et ça ne va pas…

Là, le doute s’installe, malgré les lectures officielles, des articles, des mails, un stackoverflow, un appel à l’aide général et de la patience… rien n’y fit. Et la raison reste un mystère.

Que ça soit l’inclusion d’une lib en local, ou via son git perso, que ça soit un module qui ne va pas alors que l’autre oui, la folie s’installe… et surtout le rejet de ce méchant projet, ce qui le met en péril de progression.

Après avoir accepté l’échec de cette noble tentative et un gros FUCK à Angular et leur système « y a qu’les hypsters qui croivent » (comprendre : une élite peu nombreuse et souvent prétentieuse). Ô rage et désespoir… Enfin donc une idée surgit de la bonne vieille école des barbus, à l’ancienne mais restons propre.

Donc on part d’un code fonctionnel inclus dans un folder de mon app et on crée un répertoire vide en dehors et on tape le code dedans sans lui faire de mal, on ajoute un index.ts et on check que tous les liens de notre point d’entrée sont bons et accessibles.

On adapte notre projet avec le tsconfig.json et le package.json. On ajoute un script de post installation et on peut résumer ainsi : je git clone mon projet ou un nouveau et je fais le npm install, il rapatrie les node_modules nécessaires, ça on connait, il lance tout seul comme un grand le script de postinstall et va cloner notre « lib maison » sous forme de répertoire à l’intérieur de notre projet (ajouté au .gitignore), ensuite grâce aux modif du tsconfig.json, il suffit de faire une référence unique vers cette lib « comme » si c’était une lib NPM.

On respecte donc l’énoncée :

  • Chaque projet doit avoir son repo git
  • La « librairie » est facilement maintenable
  • Les projets utilisent la « librairie » facilement
  • L’ensemble ne se gêne pas

Dans le détail, dans package.json :

"postinstall": "git clone ssh://user@server.net/blabla.git src/mon-folder" 

Ajouter votre répertoire au .gitignore de votre projet :

#mon folder
/src/mon-folder

Vient ensuite votre tsconfig.json :

"baseUrl": ".",
...
"paths": {
  "@ma-lib/*": ["src/mon-folder/*"]
}

Enfin, l’usage dans votre app.module.ts :

import { MonModule } from '@ma-lib/index'; 

Un mois… ça aura pris un mois, mais on peut enfin continuer…

Et de un, maintenant à nous deux !

Ça y est, Schema Definer a été fini le week-end passé ! Le temps de redescendre du dernier rush, de valider les points et tâches dans le Redmine (système de tickets). On respire et on prend du recul.

Nous avons désormais le moyen de paramétrer notre schéma, sa définition. Nous allons pouvoir l’utiliser et en faire Folkopedia, ce qui reste le but de tout ceci. Cependant, ce serait vendre une voiture sans avoir assembler les pièces, cela reste décousu, non-défini.

Quand on fabrique un site web manipulant des données, nous utilisons des outils pour regarder à quoi ressemble les données stockées et de les manipuler. Il nous permet de savoir à quel résultat on s’attend à la sortie, et si ce n’est pas le cas d’être aidé à comprendre.

Dans notre cas spécifique ce même outil ne peut nous aider qu’à voir notre schéma et, de manière éparse, nos entités et attributs. Si l’on doit travailler avec il nous faut un outil équivalent capable d’utiliser notre schéma. Ainsi, par équivalence, nous aurons un outils d’inspection et de manipulation des entités créées.

J’ai nommé ce projet Entity Manager car le but à ce niveau est bien de travailler au niveau des entités et de leur attributs. Par extension cela concerne également leurs relations et la visibilité des contenus, ce qui couvre la totalité du schéma dans son état présent.

Voici donc un projet qui se glisse entre deux, mais qui ne ralentira que peu Folkopedia, car ce qui sera développé dans Entity Manager, telle qu’une partie de Schema Definer, pourront être mis en commun et ne pas devoir être réécrit. Cela peut paraître évident, mais pas pour tout le monde, ni rendu simple par les technologies utilisées et dans notre contexte privé.

Je vais donc techniquement partir sur une librairie Angular partagée entre nos projets qui composent Folkopedia. Pour les connaisseurs, il ne s’agit pas ici d’un projet monolithique-monorepository mais bien d’app séparée par projet ayant une lib privée en commun. La documentation étant quasi inexistante, comme par hasard, nous revoilà en quête d’impossible et de challenges d’évidences.

Links to DRY véier

Deux blocs ont été fait entièrement, les deux plus simple pour commencer et concevoir les éléments nécessaires.

Tout d’abord Visibility Level, niveau de visibilité de l’entité associée, qui a permis de mettre en place le CRUD (Create, Read, Update, Delete) requis. Ainsi sur les 5 endpoints prévu, 4 sont utiles actuellement et ont été réalisé : lister l’ensemble, ajouter, éditer et supprimer.

Pour la suppression, comme vous le savez ou l’espérez, cliquer sur le bouton ne doit pas faire l’action sans une confirmation. Généralement on vous affiche une boîte de dialogue pour vous demander de confirmer, d’une manière ou d’une autre cela vous fait regarder ailleurs ou vous masque ce vous faites.

Material Angular n’a pas de mécanique de popover tel que Bootstrap a, il n’a qu’un tooltip discret. Du coup j’en ai conçus un.

Confirmation

Alors évidemment il est personnalisable : nombre de boutons, valeur, couleur, tooltip et texte (label). Il na pas encore la fonctionnalité de position modifiable (haut, gauche, droite), il va juste dessous celui qui en a besoin, ici un bouton de suppression à confirmer.

Il verrouille le bouton cliqué, affiche sa boite élégante et attend votre clique, à côté il ferme la boute et relâche le bouton verrouillé et sur un bouton renvoie la valeur. Simple, efficace et réutilisable ! Car oui j’en ai fait un module et une directive, tout en un tant qu’on y est et qu’en plus on l’a jamais fait 🙂 mouahahaha la blague…

Vous trouverez pas mal de tuto mais aucun avec les 2 ensembles, et évidemment, aucun complet… Pensez à regarder après ‘entryComponents‘ dans votre fichier app.module.ts à l’occasion quand vous ferez ça ;).


Ceci dit, j’ai également fait le bloc Link Types, les types de lien ! 🙂 Il a une petite particularité, une variable en plus, du coup ça permet d’y aller mollo en difficulté.

Par contre les 2 suivants seront d’un coup plus complexe : défis accepté !


Il y a aussi quelques changements côté back : des tests en plus suite à l’oubli du check d’unicité durant les updates, et évidement le code de test ajouté. On a +100 tests avec ~200 comparaisons ^^ et une couverture inchangée actuellement, toujours 95% !

Autre modification back : un complément pour la liste des Link Types qui ne répondait pas parfaitement à la description de l’API. Détails, mais c’est mieux avec.

Sur le Front rouge

Après plus de douze heures à creuser ma tranchée et à établir mon avant-poste, un bivouaque, un feu et ma radio, voici enfin venu le temps de faire le compte-rendu de ce week-end.

Nous y sommes, sur le Front du combat que l’on mène depuis bientôt presque un mois, au devant du combat, dans le feux de l’Action, loin du doux cocon de l’API. Enfin, pour ceux qui ont suivi, c’était loin d’être un simple doux cocon… ah ah !

Comme prévu, à l’image de schema.org, voici l’allure de notre Schema Definer fraîchement créé. Simple et concis, de quoi parcourir les définitions et, à terme, de les éditer.

Pour faire un point technique, et justifier les 12h dites en début d’article, il a fallu commencer par mettre mon poste à jour et comme toujours non sans mal avec @angular/cli; Node ça été tout seul, et une fois le CLI (Command Line Interface) installé à la bonne version c’est Angular qui a suivi. Nous voilà donc avec un environnement propre Angular 8 et Material Design pour le fun et l’inconnue.

Pour ceux que ça intéresse, faites gaffe avec Material Design, cela ne remplace pas Bootstrap (grille, typo, style etc), mais ne concerne que les composants (bouton, tableau, formulaire, …). En bref, faites attention.

S’en est suivis une mise en place du projet et de l’authentification avec service et gardien (guard). Je vous passe les emmerdes et vous redirige vers quelques articles qui m’ont bien aidé/inspiré.

La liste n’est pas exhaustive. C’est surtout le premier qui m’a bien aidé à comprendre la mise en place de la mécanique du gardien en Angular, ainsi que les interceptor et quelques opérateurs rxjs.

Vu la progression du week-end, voici un extrait représentatif du travail accompli depuis le début du mois (le détail serait beaucoup trop long), et qui, ce week-end, a fait un beau bon en avant.

Maintenant que la machine est en place, le reste devrait suivre sans nouvelle mauvaise surprise. Enfin, j’espère…

Shader 15 ans plus tard

Ça fout une claque, mais la dernière fois que j’ai touché du HLSL c’était pour mon travail de fin d’étude en 2005 dans un projet que nous avions baptisé Animator 4D. Bon, ce n’est pas le sujet qui nous amène ici, nous allons plutôt « simplifier » l’usage basique du WEBGL dans un contexte de rendu 2D.

Je vous recommande le très bon site
https://webglfundamentals.org/ ! Basé sur ses articles, et d’autres, je vais vous présenter une simplification de compréhension.

Pour info : je travaille en TypeScript (TS).

Charger une texture

C’est relativement simple si vous avez déjà votre img et son event load prêt. En dedans on va ajouter ceci :

const maTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, maTex);
// Let's assume all images are not a power of 2
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
// Loaded: so copy in the texture
gl.bindTexture(gl.TEXTURE_2D, maTex); // TODO why twice ?
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, monImgObjHTML);

On considérera que monImgObjHTML est votre HTMLImageElement chargé et la variable maTex un attribut de classe ou autre accessible.

Les shaders

En fait il vous en faut 2, un qui gère les vertex (vertex shader) et celui qui s’occupe de la couleur (fragment shader). Du coup, vous aurez besoin de vous faire 2 petites fonctions pour vous aider lors de l’init : une de chargement de shader et une de création de programme contenant vos shader. En résumé (détail sur le site de webglfundamentals) nous avons :

createShader(type: number, source: string): WebGLShader {
    const gl = this.context;

    // Create the shader
    const shader: WebGLShader = gl.createShader(type);

    // Set the source code
    gl.shaderSource(shader, source);

    // Compile the shader
    gl.compileShader(shader);

    // Check compilation status
    const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
    if (!success) {
      // Something went wrong during compilation; get the error
      throw new Error('could not compile shader: ' + gl.getShaderInfoLog(shader));
    }

    return shader;
  }

  createProgram(vertexShader: WebGLShader, fragmentShader: WebGLShader): WebGLProgram {
    const gl = this.context;

    // Create a program
    const program: WebGLProgram = gl.createProgram();

    // Attach the shaders
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);

    // Link the program
    gl.linkProgram(program);

    // Check if it linked
    const success = gl.getProgramParameter(program, gl.LINK_STATUS);
    if (!success) {
        // something went wrong with the link
        throw new Error('program filed to link: ' + gl.getProgramInfoLog (program));
    }

    return program;
  }

CreateShader va charger un texte plein (dans ma solution), ou ce que vous voulez, c’est bien foutu; et vous rendre un WebGLShader. Du coup, vous pouvez faire un WebGLProgram qui contiendra vos 2 shaders. Hop le tour est joué.

Évidemment cela demande une fonction d’init’ dans laquelle vous allez préciser les variables de vos shaders et appelez vos 2 fonctions.

maFonctionInit() {
  const vertexShader: WebGLShader = this.createShader(gl.VERTEX_SHADER, vertexShaderTxtCode);
  const fragmentShader: WebGLShader = this.createShader(gl.FRAGMENT_SHADER, fragmentShaderTxtCode);

  // Create program
  const program = createProgram(vertexShader, fragmentShader);

  // Buffers
  const colorLocation = gl.getUniformLocation(program, 'u_color');

  ...
}

Bon du coup on est initialisé, et on suppose que le workflow est bon, donc on peut dessiner 🙂 / faire son rendu !

Le rendu

render() {
  gl.bindTexture(gl.TEXTURE_2D, maTex);
  gl.enable(gl.BLEND);
  gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);

  // Tell WebGL to use our shader program pair
  gl.useProgram(program);
  gl.uniform3fv(colorLocation, new Float32Array([0.0, 0.0, 1.0]));

  ...
}

On lie notre texture, on utilise notre programme et on envoie une valeur.

Bonus je vous ai indiqué pour le support de la transparence (genre votre texture en PNG avec transparence). Pensez au premultipliedAlpha: false pour votre canvas !

Pour la fin de la fonction, webglfundamentals vous donnera toutes les infos nécessaires, y compris des librairies de matrices m3/m4 adaptées et bien foutue.

Vous voilà orienté dans une des directions possible pour atteindre votre objectif de rendu 2D en 3D :p.

IE10:page-break-inside vs print

J’ai développé une application web de gestion des évaluations du personnel. Ceux-ci impriment leur évaluation, et dans toutes grandes entreprises, il n’y a pas un type de poste mais autant que d’employé 🙂 , surtout quand ils accèdent depuis l’extérieur.

J’ai opté pour Bootstrap sur un Zend Framework 2 et une impression naturelle de la page web plutôt que de générer un PDF. Pourquoi faire 2 fois le travail quand il suffit d’un peu de CSS :).

C’est là qu’on me dit que rien ne va quand on imprime, que le nombre de pages est infini, etc. L’utilisateur dans sa splendeur 🙂 rien ne va mais tout va SAUF l’impression sous Windows 8.0 et IE10.

Après une recherche dichotomique pour isoler le méchant code, voici le meurtrieur de navigateur avec sa solution, hop tout en un.

.container .tab-pane {
    page-break-after: always;
    page-break-inside: avoid;
}
.ie10 .container .tab-pane {
    page-break-inside: auto;
}

Le page-break-inside, qui date de IE8 en terme de support, a une belle régression ici et ne s’y retrouve plus, générant ainsi une infinité de page, pète la mémoire et fin de l’histoire, ça ne sort jamais.

Il vous faudra également un petit JS trouvé sur le net qui aidera à la détection de IE10 qui ne peut plus se faire avec les commentaires conditionnés (<!–[if lt IE 9]><![endif]–>).

if (/*@cc_on!@*/false && document.documentMode === 10) {
    document.documentElement.className+=' ie10';
}

Source : http://www.impressivewebs.com/ie10-css-hacks/

Remettre la propriété en auto lui permettra de reprendre ses esprits et de faire son impression.

[jQuery] Self clic & child force click

$('.category-blocs li').click(function(e){
    if (!$(e.target).is('a')) {
        $(this).find('a')[0].click();
    }
}).addClass('clickable');

L’idée est une liste avec du contenu, contenant un lien, et on veut que tout le <li> soit clickable. Hors on ne mettra pas des éléments bloc dans notre lien <a>.

Une solution javascript serait de dire au <li>, lors du click, de chercher l’enfant <a> (considéré seul ici, ou le premier trouvé) et de forcer le click de ce lien.

Too much recursion

Premier problème, ça boucle sur lui même et pète. Un effet de propagation (bubbling) que l’on échappe grâce à un test sur le target.

http://stackoverflow.com/questions/5967923/jquery-trigger-click-gives-too-much-recursion

Force click

Si vous targetez directement l’événement du lien comme ceci :

$(this).find('a').click();

cela ne donnera rien, vous devez prendre l’élément du DOM et non l’objet jQuery.

http://stackoverflow.com/questions/5838241/jquery-force-click-href