Skip to content

Fork feature update: node/edge editing, formula columns, selection panel, mobile UX & pre-production CI - #327

Open
JeanGarf wants to merge 205 commits into
gephi:mainfrom
JeanGarf:develop
Open

Fork feature update: node/edge editing, formula columns, selection panel, mobile UX & pre-production CI#327
JeanGarf wants to merge 205 commits into
gephi:mainfrom
JeanGarf:develop

Conversation

@JeanGarf

Copy link
Copy Markdown

Pull request type

Check the type of change your PR introduces:

  • Bugfix
  • Feature
  • Code style update (formatting, renaming)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • Documentation content changes
  • Other (please describe):

Summary of changes (d906a959db74cb)

This range covers the full set of changes accumulated on this fork since the base commit, spanning many feature/fix branches merged into develop and periodically promoted to main_jg.

Save & session

  • Fix GitHub file being overwritten after creating a new workspace (Critical Bug)

  • Add a save icon next to "Workspace", greyed out until there are unsaved changes

  • Persist session settings (layout & metrics parameters) together with the graph

Node & Edge editing

  • Avoid Gephi Lite to crash when deleting an Edge [Deleting Edge makes Gephi lite to crash #318] (critical bug)

  • Prevent creation of duplicate nodes/edges

  • Support changing edge extremities (source/target) via updateEdge, add a swap button in the edit form, and an "Invert arrow's direction" menu item [Add button to invert Source and Target of an Edge #321 ]

  • Pre-fill default attribute values on node/edge creation [Default value for attributes #326 ]

  • Auto-focus the first empty field in node/edge modals

  • Numeric attributes: cross-platform +/− stepper

  • Add a validation button in the modal header and duplicate the OK button in the modal footer (espacially for Android)

  • Fix edge weight resetting to 1 on creation [weight field is reseted to 1 #322 ]

  • Move ID field to the end of node/edge create/edit forms and data columns; move x/y position fields to the end of the edit form

Formula (scripted) columns & data columns

  • Scripted columns: persisted script, live recompute, editable label, and a "Freeze values" action to materialize computed values into a static attribute

  • Add automatic "Update Date" / "Creation Date" columns, with backfill for graphs already loaded in session

  • GEXF export now materializes formula column values

  • Rename "Delete attribute" → "Delete column"; rename "Identifier" → "ID"

Selection panel & Graph view

  • Add a title and selected-item count to the selection panel;
  • Make selected nodes/edges clickable to locate them
  • Click a node's label to locate it;
  • Add quick-action icons (locate, select neighbors, edit, invert direction, delete);
  • Add icons to create an edge from a node and select a node's edges
  • Sort selected edges by source, target, then label; show only endpoint labels for a selected edge and refocus dimming on it
  • Improve camera framing when locating an edge; unify multi-edge zoom between table and panel (mobile-aware); fix excessive zoom-out on multi-node framing
  • Scroll the selection panel to top after locating an item

Data tables

Color palette

  • Sort the color legend alphabetically and keep existing colors when a new category is added
  • Remove obsolete categories from the palette; sync the palette when data changes, not only when fields change

Mobile & navigation

  • Add mobile header with node/edge creation icons; expand the Workspace toolbar by default on mobile
  • Fix the Layout submenu appearing unexpectedly on mobile
  • Android Back button closes the popup instead of navigating; prevent Back from popping Graph/Data views or exiting the app with unsaved work; warn before leaving the app with unsaved changes

Default labels

  • Use ID as the default label for nodes/edges; don't show the ID when "No label" is selected

Pre-production banner & CI

  • Add CI deployment of pre-production to GitHub Pages from develop; configure GitHub Pages for this fork; grant contents:write permission to the deploy workflow
  • Move the pre-production banner to the top of the graph; detect preprod via URL

Misc / internal

  • Factor the duplicated data-creation menu into a shared constant; add it to the Graph view above Spatialisation [Data Creation Menu also on Graph View #319 ]
  • Refresh the filtered graph cache on node/edge data changes, not only on fullGraph reference changes
  • Documentation: factoring rule (avoid divergent behavior across call sites) and feature-branch sync rule (merge --no-ff)

JeanGarf and others added 30 commits July 13, 2026 01:27
…ion, delete) to the graph view selection panel

Reuses the existing Data view icon buttons for the per-item panel in the Graph view, adds a select-neighbors button for nodes and an invert-direction button for edges, placed just above the attribute list.
Keeps it consistent with the Node ID/Edge ID column already moved to the end in the data table views.
# Conflicts:
#	packages/gephi-lite/src/views/graphPage/Selection.tsx
Remove the CNAME pointing to the upstream lite.gephi.org domain,
switch redirect URLs to relative paths, trigger deploy-app on
push to main_jg instead of main (kept in sync with upstream),
and disable the automatic Docker Hub publish workflow.
- Nouvelle icône dans le volet de sélection d'un nœud ouvrant le formulaire
  de création d'edge avec ce nœud pré-rempli comme source et le focus placé
  sur le champ cible.
- Support d'un prop autoFocus dans GraphSearch.
- Correction d'un crash latent : le rendu d'un item de sélection était tenté
  avant que le dataset ait rattrapé la sélection (fenêtre transitoire après
  création), provoquant un accès sur des données undefined. Le rendu est
  désormais ignoré pour ce frame et se corrige au rendu suivant. Les deux
  listes partagent le même renderItem factorisé.
- Traductions create_edge_from_node ajoutées (dev, en, fr, hu, ko, uk).
Lors de la sélection d'un ou plusieurs edges, les nœuds source et cible
sont désormais mis en avant (labels forcés) et tous les autres nœuds sont
estompés avec leur label masqué, de la même façon que lors de la sélection
d'un nœud.
claude added 30 commits July 25, 2026 21:26
…s scriptés

1) Réordonnancement

Les filtres s'appliquent en cascade, chacun sur le graphe produit par les
précédents : leur ordre est donc porteur de sens, mais n'était pas modifiable.
Ajoute un couple de boutons monter/descendre sur chaque filtre (désactivés aux
extrémités), adossés à un producteur `moveFilter` calqué sur `moveFieldModel`.

2) Attributs scriptés (colonnes formule)

Les valeurs des colonnes formule ne sont pas stockées : elles sont recalculées à
la volée et rangées dans le canal de données « dynamique », comme les attributs
topologiques (degré...). Or les filtres ne construisaient ce canal que lorsque
`field.dynamic` était vrai — jamais le cas d'une colonne formule — et ne
calculaient de toute façon que la moitié topologique. Résultat : un attribut
scripté était systématiquement lu comme `undefined`, donc traité comme une
valeur manquante. Concrètement l'histogramme restait vide et le filtre ne
filtrait rien (ou tout, selon « conserver les valeurs manquantes »).

Factorise le calcul complet du canal dynamique — attributs topologiques ET
colonnes formule — dans `computeAllComputedAttributes`, seul point d'entrée
désormais utilisé par `filterGraph`, l'histogramme, la liste de valeurs et
l'atome de données dynamiques (qui dupliquait cette logique). Les scripts sont
toujours pris dans le modèle de champs courant, si bien qu'éditer une formule
rafraîchit les filtres qui s'en servent.

Les deux composants de filtre qui recalculaient ce canal chacun de leur côté
passent par un hook partagé `useFilterItemData`, pour qu'ils décrivent
exactement ce que `filterGraph` gardera ou écartera.
…dans les données)

Pour tracer l'histogramme, la plage de valeurs est découpée en tranches
arrondies à une unité lisible (ex. par 2, par 5...). Les champs « De »/« à »
affichaient ensuite les bornes de ces tranches arrondies plutôt que le vrai
min/max des données : sur un attribut Degree variant de 1 à 36, le panneau
affichait par exemple 0 et 39. Utilise désormais le vrai min/max des valeurs,
calculé indépendamment du découpage en tranches, pour ces deux champs.
…n/max

Jusqu'ici, l'échelle min/max utilisée pour interpoler la taille se basait
tantôt sur tout le jeu de données, tantôt sur les seuls éléments visibles
après filtrage, selon que l'attribut choisi était calculé (Degree, colonne
formule) ou non - un attribut calculé recalcule ses valeurs sur le graphe
filtré, ce qui restreignait l'échelle sans le vouloir. Concrètement : filtrer
pour ne garder que les deux plus grandes valeurs faisait remonter la seconde
à la taille min, comme si elle était la plus petite de tout le graphe.

Ajoute une case à cocher, décochée par défaut, sous « Interpoler entre les
valeurs personnalisées min et max » : décochée, l'échelle est toujours
calculée sur l'ensemble du jeu de données (comme dans la Vue Data), quel que
soit le filtrage en cours et quel que soit le type d'attribut ; cochée, elle
se restreint aux éléments actuellement conservés par les filtres.

La valeur propre à chaque nœud/lien (utilisée pour son rendu) n'est pas
affectée par ce réglage : seule l'échelle d'interpolation change.
1) Le widget « élément le plus récent » (Nœuds/Liens) affichait un simple "?"
lorsque cet élément était filtré, sans expliquer pourquoi. Affiche désormais
"Filtered"/"Filtré" selon la langue - ce même repli étant partagé par
NodeComponentById/EdgeComponentById, la Vue Data (colonne aperçu) et la
recherche fuzzy en bénéficient aussi.

2) L'icône entonnoir du volet gauche n'avait ni info-bulle ni action : un
badge purement décoratif, visible seulement quand des filtres étaient actifs.
Elle est maintenant toujours affichée (creuse si aucun filtre actif, pleine
sinon), avec une info-bulle explicite, et cliquable pour ouvrir directement
le panneau Filtres (Vue Graphe comme Vue Data).

3) Sélectionner les liens d'un nœud ne garde que les liens non filtrés, sans
l'indiquer. Le titre du volet de sélection affiche maintenant, dès qu'un
élément sélectionné est filtré, "(3, 5 filtered)" (visibles, filtrés) au lieu
du seul total ; ce format remplace le message "X hidden selected nodes" qui
faisait doublon juste en dessous du titre. La liste dépliable des éléments
masqués plus bas, elle, reste inchangée.
- l'icône entonnoir du volet gauche ne reste plus pleine quand tous les
  filtres sont désactivés
- « Sélectionner les liens / les voisins » d'un nœud partent du graphe
  complet, donc les éléments filtrés restent dans la sélection au lieu
  d'être silencieusement perdus ; les boutons sont désactivés pour un
  nœud isolé, qui n'a rien à sélectionner
- volet de sélection : le décompte « (9, 10 filtrés) » passe à la ligne
  sous le titre, la partie filtrée est en rouge, et les éléments filtrés
  sont regroupés sous un titre rouge de même niveau que celui du volet,
  suivi d'un séparateur et repliable (replié par défaut)
- un élément filtré garde son libellé mais est toujours dessiné en gris
  (disque creux, lien pointillé, libellé gris italique) au lieu d'être
  remplacé par « Filtered » : même rendu dans le volet de sélection, le
  résumé du graphe et la vue Data
- les décomptes n'écrivent plus les nombres en toutes lettres (« 1
  filtré » et non « un filtré ») et parlent de « filtrés » plutôt que
  de « masqués »
Un lien absent du graphe filtré se lit désormais comme filtré dans son
ensemble : ses nœuds source et cible sont dessinés en cercle creux gris
avec un libellé gris italique, même lorsque ces nœuds passent eux-mêmes
les filtres.
Quand exactement deux nœuds sont sélectionnés, un bouton apparaît à côté
du bouton de localisation : il sélectionne les liens d'un plus court
chemin entre les deux nœuds et cadre la caméra dessus. Le rendu du
graphe met déjà en avant les liens sélectionnés et leurs extrémités en
estompant le reste, donc aucun mécanisme d'affichage n'est ajouté.

Le chemin est cherché dans le graphe complet, sans tenir compte du sens
des liens : un lien masqué par un filtre en fait donc partie et est
signalé comme filtré dans le volet, comme pour toute autre sélection.
Si aucun chemin ne relie les deux nœuds, une notification le signale et
la sélection reste inchangée.

Le parcours en largeur est isolé dans core/graph/utils
(getShortestPathEdges / getEdgesBetween) et couvert par des tests.
Un écran tactile n'a pas de touche Ctrl : taper un deuxième nœud ne
pouvait que remplacer la sélection, ce qui rendait inaccessible sur
mobile tout ce qui demande plusieurs éléments sélectionnés (dont le
nouveau bouton de chemin entre deux nœuds). Un appui maintenu bascule
désormais l'élément dans la sélection, comme le fait Ctrl+clic.

La durée est mesurée au relâchement plutôt qu'avec un minuteur : sigma
ne signale un clic que si le doigt a très peu bougé, donc un appui long
qui se transforme en déplacement de nœud ou en panoramique ne bascule
rien. Le comportement est restreint au tactile, pour qu'une hésitation
avant de déplacer un nœud à la souris ne modifie pas la sélection.
Sur petit écran le volet de sélection et le panneau de menu sont
empilés et ne peuvent pas être ouverts en même temps, et le seul bouton
pour refermer le volet vidait la sélection : le champ de recherche
n'était donc atteignable qu'en perdant ce qui était sélectionné.

Ce bouton devient un chevron qui replie simplement le volet, la
sélection étant conservée. Un second appui ouvre le panneau de menu et
donne accès à la recherche. Toute modification de la sélection redéploie
le volet, ce qui est le chemin de retour.

Vider la sélection se fait désormais depuis une croix ajoutée dans la
barre de titre du volet, à droite du bouton de localisation et séparée
de lui pour éviter les appuis manqués. Sur grand écran rien ne change :
la croix en haut à droite du volet vide toujours la sélection, et la
nouvelle croix n'y est pas affichée.
Un bouton dans la barre de titre bascule l'ordre de la liste entre
alphabétique (par libellé pour un nœud ; par source, cible puis libellé
pour un lien, l'ordre existant) et taille décroissante. La taille est
celle du rendu, calculée par Apparence > Taille, donc l'ordre du volet
correspond à ce que montre le graphe ; à taille égale, le tri
alphabétique départage pour que la liste reste lisible. Les nœuds
n'étaient jusqu'ici pas triés du tout et apparaissaient dans l'ordre
d'insertion.

Le réglage est persisté dans les préférences, séparément pour les nœuds
et pour les liens.

Corrige au passage le désérialiseur du SDK : il prenait pour un graphe
sérialisé tout objet possédant les clés « nodes » et « edges », sans
vérifier qu'il s'agissait bien de tableaux. Un état simplement indexé
par type d'élément faisait donc échouer l'analyse et l'ensemble du
document était silencieusement abandonné - ce qui rendait ici toutes les
préférences non persistantes, thème compris.
…pas un

Après une sauvegarde, la date de référence était mémorisée depuis la
réponse du PATCH, alors que toutes les vérifications ultérieures lisent
la date depuis GET /gists/{id}. Les deux peuvent différer pour la
version qui vient d'être écrite, et le garde-fou croyait alors que le
distant avait bougé - à chaque vérification, jusqu'à la sauvegarde
suivante. La date est désormais relue depuis le même endpoint que les
vérifications, comme le fait déjà l'ouverture d'un fichier.

Deuxième garde-fou, indépendant de la cause : avant d'alerter, le
contenu distant est téléchargé et comparé à l'empreinte de ce qui a été
synchronisé en dernier. La date de mise à jour d'un gist bouge en effet
pour des raisons qui ne touchent pas au fichier (étoile, fork,
commentaire, ou deux lectures de la même version qui ne s'accordent
pas) ; seules des données réellement différentes déclenchent désormais
l'avertissement. Quand elles sont identiques, la nouvelle date est
adoptée comme référence pour ne pas re-télécharger à chaque minute.

L'empreinte est rangée dans l'état fichier, donc persistée : elle
survit à un rechargement de page. Sans empreinte connue, le
comportement précédent s'applique (comparaison de dates seule), et un
échec de téléchargement alerte aussi - le doute doit rester du côté qui
protège le travail distant.
Le bouton Retour d'Android fermait l'éditeur sans condition, effaçant au
passage ce que l'utilisateur avait commencé à saisir. Il demande
désormais confirmation dès que quelque chose a été modifié, et la
réponse par défaut - bouton principal, focalisé, et réponse de la touche
Échap - est de poursuivre la saisie. Sans aucune modification, la
fermeture reste immédiate.

Même traitement pour les deux autres fermetures involontaires que ces
éditeurs autorisent, la touche Échap et le clic à l'extérieur : elles
perdaient la saisie exactement de la même façon. Les boutons de
fermeture et d'annulation restent sans confirmation, un clic dessus
étant une intention explicite.

La demande de fermeture passe maintenant par l'état des modales
(`requestCloseModal`) plutôt que par une fermeture directe, ce qui
permet à la modale ouverte d'intervenir ; les modales sans saisie en
cours se ferment comme avant. L'état « saisie en cours » vient de
`dirtyFields` et non de `formState.isDirty`, ce dernier étant déjà vrai à
l'ouverture parce qu'un champ dont la valeur par défaut est indéfinie
rapporte une chaîne vide dès son enregistrement.
Les deux croix de fermeture - celle de l'en-tête de la modale et celle
du pied de formulaire - passent désormais par la même demande de
fermeture que la touche Retour, Échap et le clic à l'extérieur : sans
saisie en cours elles ferment directement, sinon elles affichent la
confirmation qui, par défaut, poursuit la saisie.

La modale expose sa demande de fermeture par contexte, ce qui permet au
pied de formulaire - construit par le formulaire et non par la modale -
de l'utiliser sans dupliquer la logique. Hors modale (les mêmes
formulaires sont aussi rendus dans un volet latéral), le bouton
d'annulation retombe sur son gestionnaire d'origine.

Ajoute au CLAUDE.md la règle correspondante : une même action
déclenchable de plusieurs façons doit passer par un seul et même code, et
une demande visant un seul de ces déclencheurs s'applique aux autres.
Sous le numéro de version, en gris et en petit pour ne pas attirer
l'œil : le hash de commit git et la date/heure de build, de manière à
pouvoir vérifier qu'on est bien sur la dernière version déployée.

Les deux valeurs sont injectées une fois pour toutes à la compilation
(vite.config.mts, via `define`), donc gravées dans le bundle : rien à
calculer ni à récupérer au chargement de la page. Absentes (nœud null)
dans un environnement sans checkout git pour lire le hash, auquel cas
la ligne ne s'affiche pas du tout plutôt que d'afficher une valeur
trompeuse.
L'empreinte de référence était prise sur les octets envoyés lors d'une
sauvegarde, alors que la vérification compare avec les octets que le
distant renvoie. Toute différence introduite par GitHub sur le trajet
suffisait donc à faire échouer chaque comparaison ultérieure, et la
protection par contenu ajoutée précédemment ne servait plus à rien. La
sauvegarde relit désormais le gist et prend l'empreinte de ce qu'il
contient réellement — même lecture, même côté du fil.

Une seule requête ramène maintenant métadonnées et contenu (readGist),
ce qui évite aussi de mélanger une date issue d'une lecture avec un
contenu issu d'une autre.

Par ailleurs, répondre « Conserver mes modifications » ne changeait rien
à l'état : la vérification suivante retrouvait le même écart de date et
réaffichait l'alerte, toutes les minutes. La version distante ainsi
écartée est désormais mémorisée et n'est plus resignalée ; une version
distante réellement nouvelle alerte toujours, et la vérification avant
sauvegarde reste inchangée pour ne pas écraser sans confirmation.

Enfin, quand aucune empreinte n'est connue (état enregistré par une
version antérieure), celle du distant est mémorisée au passage, pour que
la vérification suivante puisse trancher au lieu de redemander sans fin.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants