Skip to content

feat(panoramax) : ✨ nouveau widget Panoramax#492

Open
lowzonenose wants to merge 46 commits intomainfrom
feature/panoramax
Open

feat(panoramax) : ✨ nouveau widget Panoramax#492
lowzonenose wants to merge 46 commits intomainfrom
feature/panoramax

Conversation

@lowzonenose
Copy link
Copy Markdown
Collaborator

@lowzonenose lowzonenose commented Feb 23, 2026

Mise en place d'un widget pour visualiser Panoramax sous OpenLayers

🎉 Les étapes :

  • Ajouter la Couche des objets Panoramax sur un fond gris
    • choix d'un fond gris
    • choix d'une couche panoramax
    • une plage de zooms (6 à 21)
    • interaction clic sur un point grid zoome sur la zone des séquences
    • interaction clic sur une ligne sequence zoome sur la zone des images
    • choix d'un LayerGroup
  • Action de Prévisualiser des images (preview)
    • preview sur le type grid fournit le nombre d'images
    • preview sur le type sequence affiche une image de la séquence
    • preview sur le type picture affiche l'image
    • css pour la popup de preview
  • Les boutons / Menus
    • configurer la position de la fenêtre des boutons
    • css pour les boutons / menu
    • configurer l'ordre des boutons
    • Gestion du menu des Filtres
      • filtre par dates
      • filtre par periodes
      • filtre par type d'image
      • implémenter le filtrage des styles mapbox
      • [configurer la position du menu]
      • mise à jour du style avec ol-mapbox-style
    • Ajouter un bouton Contributions
      • choix du lien paramétrable
    • 🔨Ajouter un bouton toggle pour activer / desactiver le mode hover des preview
    • 🔨 Ajouter un bouton pour choisir le rendu, parmi les styles preconfigurés
    • 🔨Ajouter un bouton toggle pour activer / desactiver la couche de fond
  • Mise en place de la Fenêtre de visualisation avec photo-sphere-viewer
    • configurer la position de la fenêtre
    • configurer la taille de la fenêtre : small, medium, fullscreen, fullscreen-map
    • photo-sphere-viewer
      • charger une photo
      • bouton retour sur la carte
      • bouton fermeture
      • bouton fullscreen
      • customisés les widgets

💥 Étude technique à faire sur le PhotoViewer de Panoramax sur les sujets suivants :

  • ajouter du dsfr
  • créer des nouveaux widgets (ex. minimap)

DSFR

Pouvoir surcharger les webcomponent Panoramax avec des CSS dsfr, Vincent a identifié des améliorations / contributions à faire avec Panoramax sur le shadow dom et le mode ::part() pour les css (https://developer.mozilla.org/fr/docs/Web/CSS/Reference/Selectors/::part).

MINIMAP

Ajouter un plugin PSV pour la mini-map. Il existe déjà un plugin pour la mini-map avec Leaflet : https://photo-sphere-viewer.js.org/plugins/plan.html#planplugin
Essayer d'en créer un avec OpenLayers en se basant sur celui de PSV : https://photo-sphere-viewer.js.org/plugins/writing-a-plugin.html

⚠️ Une demande d'assistance à l'équipe Panoramax sur l'intégration d'un nouveau plugin PSV est en cours...


📖 Divers

  • documentation utilisateur
  • documentation développeur
  • build en mode bundle
  • panoramax : dépendance externe !
  • licence ?
  • intégrer aux demos frameworks

  • coller à la proposition de maquette UX/UI

La prévisualisation
image

Les boutons
image

le photo viewer
image


implementation actuelle
image

avec le photo viewer en fullscreen
image

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new Panoramax widget for OpenLayers that enables users to visualize panoramic images from the Panoramax service directly on the map. The widget integrates the @panoramax/web-viewer library (v4.4.0) and provides an interactive interface for browsing and viewing 360° and flat panoramic images with preview capabilities, filtering options, and customizable visualization settings.

Changes:

  • Added new Panoramax control with comprehensive DOM generation, event handling, and photo viewer integration
  • Implemented preview system with hover functionality showing image metadata and thumbnails for grid, sequence, and picture layers
  • Integrated @panoramax/web-viewer dependency and configured webpack builds to include Panoramax module and CSS assets

Reviewed changes

Copilot reviewed 12 out of 14 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/packages/bundle.js Exports the new Panoramax control in the bundle
src/index.js Exports Panoramax control for module consumption
src/packages/Controls/Panoramax/Panoramax.js Main control implementation with initialization, event handling, layer management, preview system, and photo viewer integration
src/packages/Controls/Panoramax/PanoramaxDOM.js DOM generation for widget UI including buttons, panels, toggles, and viewer container
src/packages/CSS/Controls/Panoramax/GPFpanoramax.css Core layout and positioning styles for the Panoramax widget
src/packages/CSS/Controls/Panoramax/DSFRpanoramaxStyle.css DSFR theme-specific styles for icons, buttons, panels, popups, and markers
src/packages/CSS/Controls/Panoramax/GPFpanoramaxStyle.css Empty placeholder file for additional custom styles
src/packages/CSS/Controls/Panoramax/img/dsfr/panoramax.svg Panoramax icon in SVG format for widget button
package.json Adds @panoramax/web-viewer@^4.4.0 dependency
build/webpack/modules.webpack.config.js Configures GpfExtOlPanoramax module entry and includes web-viewer CSS in build
build/webpack/extend.themes.webpack.js Adds Panoramax style files to GPF and DSFR theme builds
build/webpack/extend.base.webpack.js Includes @panoramax/web-viewer CSS in base webpack configuration
build/webpack/bundle.webpack.config.js Includes @panoramax/web-viewer CSS in bundle webpack configuration
samples-src/pages/tests/Panoramax/pages-ol-panoramax-modules-options-dsfr.html Sample HTML demonstrating Panoramax widget usage with default options

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1163 to +1166
var api = (this.options.viewer.endpoint || "https://explore.panoramax.fr/api").replace(/\/+$/, "");
var className = "pnx-preview-sequence-popup";
var imageHtml = encodedPictureId
? `<img src="${api}/collections/${encodedPictureId}/thumb.jpg" alt="Preview image" class="pnx-preview-picture-popup__img">`
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The XSS prevention using the _escape method properly escapes the id and date values, but the API endpoint URL is not validated before being used to construct the image src attribute. If options.viewer.endpoint comes from user input, this could potentially be exploited. Consider validating that the endpoint URL uses an expected protocol (https) and domain pattern before using it in the image src.

Copilot uses AI. Check for mistakes.
Comment on lines +627 to +638
var newZoom = zoom + 4; // FIXME valeur aleatoire !?
e.map.getView().animate({
center : feature.coordinates,
zoom : newZoom,
duration : 500
});
}
if (type === "sequences") {
// zoom on the clicked coordinates with a zoom level
// depending on the density of images in the sequence
var zoom = e.map.getView().getZoom();
var newZoom = zoom + 2; // FIXME valeur aleatoire !?
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The zoom calculation uses arbitrary values (4 and 2) for grid and sequences types. These hardcoded magic numbers should be documented with a comment explaining why these specific values were chosen, or they should be extracted as named constants with descriptive names to improve code maintainability and readability.

Copilot uses AI. Check for mistakes.
…e boutons et de visualisation dans le widget Panoramax
…mélioration des filtres et des méthodes associées
…amax, amélioration des méthodes de chargement et de réinitialisation
…ion debounce pour les événements de mouvement de la souris et nettoyage des attributs du visualiseur photo
…ax, incluant des événements pour les dates de début et de fin
…mes de cache dans MapBox GL JS lors du rechargement des couches
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 20 changed files in this pull request and generated 17 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +231 to +235
href.setAttribute("aria-label", opts.description);
href.setAttribute("target", "_self");
href.setAttribute("href", opts.link);
href.innerHTML = opts.label;

Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

opts.label is assigned via innerHTML. Since labels are configurable, this can become an injection/XSS vector if untrusted content is passed. Prefer textContent/innerText for plain text labels.

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@lowzonenose lowzonenose requested review from IGNFhc and azarz March 7, 2026 21:05
@lowzonenose lowzonenose changed the title feat(panoramax) : nouveau widget Panoramax feat(panoramax) : ✨ nouveau widget Panoramax Mar 7, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 20 changed files in this pull request and generated 15 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +972 to +975
map.un("click", this.eventsListeners[this.CLICKED_DATA_PANORAMAX_CB]);
delete this.eventsListeners[this.CLICKED_DATA_PANORAMAX_CB];
map.un("pointermove", this.onPointerMoveDebounced());
delete this.eventsListeners[this.HOVERED_DATA_PANORAMAX_CB];
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removeEventsListeners() utilise aussi map.un("pointermove", this.onPointerMoveDebounced()) ce qui ne correspond pas au handler réellement enregistré (nouvelle fonction à chaque appel). Cela empêche le cleanup des listeners lors du détachement du contrôle. Utiliser la même référence de fonction debounced stockée sur l'instance, et la supprimer ici.

Copilot uses AI. Check for mistakes.
// abonnement/desabonnement aux evenements permettant la conversion en n/b
var id = e.target.gpLayerId;
var layer = this._layers[id].layer;
if (layer.getLayers && layer.getLayers().getArray().length > 0) {
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Le test if (layer.getLayers && layer.getLayers().getArray().length > 0) ne couvre que les LayerGroup non vides. Un LayerGroup vide passera au travers, puis layer.getSource() sera appelé et plantera (LayerGroup n’a pas de source). Tester explicitement le fait que c’est un LayerGroup (layer.getLayers ou layer instanceof LayerGroup) et retourner dans tous les cas.

Suggested change
if (layer.getLayers && layer.getLayers().getArray().length > 0) {
if (typeof layer.getLayers === "function") {

Copilot uses AI. Check for mistakes.
"name": "geopf-extensions-openlayers",
"description": "French Geoportal Extensions for OpenLayers libraries",
"version": "1.0.0-beta.9-496",
"version": "1.0.0-beta.9-492",
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

La version du package est décrémentée (...-496 -> ...-492). Sauf intention explicite, ça casse la monotonie des versions et peut perturber la publication/CI. Ajuster pour incrémenter (ou justifier le changement).

Suggested change
"version": "1.0.0-beta.9-492",
"version": "1.0.0-beta.9-497",

Copilot uses AI. Check for mistakes.
photoViewer = document.createElement("pnx-photo-viewer");
photoViewer.id = "pnx-photo-viewer-" + this.uid;
photoViewer.className = "pnx-photo-viewer-container";
photoViewer.style = "width: 100%; height: 100%";
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

photoViewer.style = "width: 100%; height: 100%" n'applique pas correctement du CSS (la propriété style est un objet CSSStyleDeclaration). Utiliser photoViewer.style.width = "100%" / photoViewer.style.height = "100%" ou photoViewer.style.cssText = "width:100%;height:100%".

Suggested change
photoViewer.style = "width: 100%; height: 100%";
photoViewer.style.cssText = "width:100%;height:100%";

Copilot uses AI. Check for mistakes.
Comment on lines +1657 to +1662
var pnxAnnotationsSwitch = this.photoViewerPanoramax.querySelector("pnx-annotations-switch");
pnxAnnotationsSwitch.setAttribute("slot", "top-right");
pnxAnnotationsSwitch.addEventListener("change", (e) => {
var checked = e.target.checked;
this.onTogglePnxViewerWidgetAnnotationsSwitch(checked);
});
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modifyWidgetAnnotationsSwitch() suppose que pnx-annotations-switch existe toujours et fait directement setAttribute(...) + addEventListener(...). Si le widget n’est pas présent (selon config/version du viewer), querySelector renverra null et cela plantera au ready. Ajouter un test if (!pnxAnnotationsSwitch) return null; avant de manipuler l’élément.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants