@geogirafe/lib-geoportal 1.1.0-dev.2468633458 → 1.1.0-dev.2527788074

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -243,6 +243,15 @@ For major changes, please open an issue first to discuss what you would like to
243
243
  Contribution guidelines are available here: [CONTRIBUTING.md](CONTRIBUTING.md).
244
244
  **Please read them before contributing.**
245
245
 
246
+ # Supporting
247
+
248
+ There are many ways to support GeoGirafe.
249
+
250
+ - You can use the following tag on your project to help promote GeoGirafe:
251
+ [![GeoGirafe](https://geogirafe.org/wp-content/uploads/geogirafe-badge.svg)](https://geogirafe.org)
252
+ - If you are interested in providing financial assistance, please reach out to us at contact@geogirafe.org.
253
+ - If you have another idea how to support the project, we invite you to join us on the dedicaced discord server: https://discord.gg/kdrXjaqBbH.
254
+
246
255
  # License
247
256
 
248
257
  Apache License, Version 2.0
@@ -55,6 +55,7 @@
55
55
  "Copy ShortUrl": "Kurz-URL kopieren",
56
56
  "Copy to Clipboard": "In die Zwischenablage kopieren",
57
57
  "Copy value": "Wert kopieren",
58
+ "copy-cell-content": "Zelleninhalt kopieren",
58
59
  "Create custom theme": "Thema erstellen",
59
60
  "cross-section-settings": "Querschnitt",
60
61
  "CSV export": "CSV-Export",
@@ -155,6 +156,7 @@
155
156
  "fr": "Französisch",
156
157
  "Freehand line": "Freihandlinie",
157
158
  "Freehand polygon": "Freihandpolygon",
159
+ "freeze-column": "Spalte fixieren",
158
160
  "Generate and copy code": "Code generieren und kopieren",
159
161
  "Generate and copy link": "Link generieren und kopieren",
160
162
  "Generate CSV": "CSV generieren",
@@ -440,6 +442,7 @@
440
442
  "Toggle all legends": "Alle Legenden umschalten",
441
443
  "Toggle legend": "Legende umschalten",
442
444
  "Tools": "Werkzeuge",
445
+ "unfreeze-column": "Spaltenfixierung aufheben",
443
446
  "Unselect all filtered layers": "Alle Layers im Filter abwählen",
444
447
  "Unsupported file format": "Nicht unterstütztes Dateiformat",
445
448
  "URL (auto_WMTS)": "URL",
@@ -55,6 +55,7 @@
55
55
  "Copy ShortUrl": "Copy ShortUrl",
56
56
  "Copy to Clipboard": "Copy to Clipboard",
57
57
  "Copy value": "Copier value",
58
+ "copy-cell-content": "Copy cell content",
58
59
  "Create custom theme": "Create custom theme",
59
60
  "cross-section-settings": "Cross-section",
60
61
  "CSV export": "CSV export",
@@ -156,6 +157,7 @@
156
157
  "fr": "French",
157
158
  "Freehand line": "Freehand line",
158
159
  "Freehand polygon": "Freehand polygon",
160
+ "freeze-column": "Freeze column",
159
161
  "Generate and copy code": "Generate and copy code",
160
162
  "Generate and copy link": "Generate and copy link",
161
163
  "Generate CSV": "Generate CSV",
@@ -442,6 +444,7 @@
442
444
  "Toggle all legends": "Toggle all legends",
443
445
  "Toggle legend": "Toggle legend",
444
446
  "Tools": "Tools",
447
+ "unfreeze-column": "Unfreeze column",
445
448
  "Unselect all filtered layers": "Unselect all filtered layers",
446
449
  "Unsupported file format": "Unsupported file format",
447
450
  "URL (auto_WMTS)": "URL",
@@ -55,6 +55,7 @@
55
55
  "Copy ShortUrl": "Copier le lien court",
56
56
  "Copy to Clipboard": "Copier dans le presse-papiers",
57
57
  "Copy value": "Copier la valeur",
58
+ "copy-cell-content": "Copier le contenu de la cellule",
58
59
  "Create custom theme": "Créer un thème personnalisé",
59
60
  "cross-section-settings": "Coupe transversale",
60
61
  "CSV export": "Export CSV",
@@ -155,6 +156,7 @@
155
156
  "fr": "Français",
156
157
  "Freehand line": "Ligne à main levée",
157
158
  "Freehand polygon": "Polygone à main levée",
159
+ "freeze-column": "Figer la colonne",
158
160
  "Generate and copy code": "Générer et copier le code",
159
161
  "Generate and copy link": "Générer et copier le lien",
160
162
  "Generate CSV": "Générer CSV",
@@ -440,6 +442,7 @@
440
442
  "Toggle all legends": "Afficher / Masquer toutes les légendes",
441
443
  "Toggle legend": "Afficher / Masquer la légende",
442
444
  "Tools": "Outis",
445
+ "unfreeze-column": "Libérer la colonne",
443
446
  "Unselect all filtered layers": "Déselectionner toutes les couches filtrées",
444
447
  "Unsupported file format": "Format de fichier non supporté",
445
448
  "URL (auto_WMTS)": "URL",
@@ -55,6 +55,7 @@
55
55
  "Copy ShortUrl": "Copia ShortUrl",
56
56
  "Copy to Clipboard": "Copia negli appunti",
57
57
  "Copy value": "Copia il valore",
58
+ "copy-cell-content": "Copia contenuto cella",
58
59
  "Create custom theme": "Crea un tema personalizzato",
59
60
  "cross-section-settings": "Sezione trasversale",
60
61
  "CSV export": "Esporta CSV",
@@ -155,6 +156,7 @@
155
156
  "fr": "Francese",
156
157
  "Freehand line": "Linea a mano libera",
157
158
  "Freehand polygon": "Poligono a mano libera",
159
+ "freeze-column": "Blocca colonna",
158
160
  "Generate and copy code": "Genera e copia il codice",
159
161
  "Generate and copy link": "Genera e copia il link",
160
162
  "Generate CSV": "Genera CSV",
@@ -440,6 +442,7 @@
440
442
  "Toggle all legends": "Attiva / Disattiva tutte le legende",
441
443
  "Toggle legend": "Attiva / Disattiva legenda",
442
444
  "Tools": "Strumenti",
445
+ "unfreeze-column": "Sblocca colonna",
443
446
  "Unselect all filtered layers": "Deselezionare tutti i layers filtrati",
444
447
  "Unsupported file format": "Formato di file non supportato",
445
448
  "URL (auto_WMTS)": "URL",
@@ -20,7 +20,7 @@ class SelectionGridComponent extends GirafeResizableElement {
20
20
  </style><style>
21
21
  .tabulator{text-align:left;background-color:#888;border:1px solid #999;font-size:14px;position:relative;overflow:hidden;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.tabulator[tabulator-layout=fitDataFill] .tabulator-tableholder .tabulator-table{min-width:100%}.tabulator[tabulator-layout=fitDataTable]{display:inline-block}.tabulator.tabulator-block-select,.tabulator.tabulator-ranges .tabulator-cell:not(.tabulator-editing){user-select:none}.tabulator .tabulator-header{box-sizing:border-box;color:#555;-webkit-user-select:none;-moz-user-select:none;-khtml-user-select:none;-o-user-select:none;white-space:nowrap;background-color:#e6e6e6;border-bottom:1px solid #999;outline:none;width:100%;font-weight:700;position:relative;overflow:hidden}.tabulator .tabulator-header.tabulator-header-hidden{display:none}.tabulator .tabulator-header .tabulator-header-contents{position:relative;overflow:hidden}.tabulator .tabulator-header .tabulator-header-contents .tabulator-headers{display:inline-block}.tabulator .tabulator-header .tabulator-col{box-sizing:border-box;text-align:left;vertical-align:bottom;background:#e6e6e6;border-right:1px solid #aaa;flex-direction:column;justify-content:flex-start;display:inline-flex;position:relative;overflow:hidden}.tabulator .tabulator-header .tabulator-col.tabulator-moving{pointer-events:none;background:#cdcdcd;border:1px solid #999;position:absolute}.tabulator .tabulator-header .tabulator-col.tabulator-range-highlight{color:#000;background-color:#d6d6d6}.tabulator .tabulator-header .tabulator-col.tabulator-range-selected{color:#fff;background-color:#3876ca}.tabulator .tabulator-header .tabulator-col .tabulator-col-content{box-sizing:border-box;padding:4px;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button{padding:0 8px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button:hover{cursor:pointer;opacity:.6}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title-holder{position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title{box-sizing:border-box;text-overflow:ellipsis;vertical-align:bottom;white-space:nowrap;width:100%;overflow:hidden}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title.tabulator-col-title-wrap{text-overflow:clip;white-space:normal}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-title-editor{box-sizing:border-box;background:#fff;border:1px solid #999;width:100%;padding:1px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-header-popup-button+.tabulator-title-editor{width:calc(100% - 22px)}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{align-items:center;display:flex;position:absolute;top:0;bottom:0;right:4px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-left:6px solid #0000;border-right:6px solid #0000;width:0;height:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{border-top:1px solid #aaa;margin-right:-1px;display:flex;position:relative;overflow:hidden}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter{box-sizing:border-box;text-align:center;width:100%;margin-top:2px;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter textarea{height:auto!important}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter svg{margin-top:3px}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input::-ms-clear{width:0;height:0}.tabulator .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-right:25px}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover{cursor:pointer;background-color:#cdcdcd}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter{color:#bbb}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{cursor:pointer;border-bottom:6px solid #555}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-top:none;border-bottom:6px solid #bbb}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{cursor:pointer;border-bottom:6px solid #555}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-top:none;border-bottom:6px solid #666}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{cursor:pointer;border-top:6px solid #555}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{color:#666;border-top:6px solid #666;border-bottom:none}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical .tabulator-col-content .tabulator-col-title{text-orientation:mixed;writing-mode:vertical-rl;justify-content:center;align-items:center;display:flex}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-col-vertical-flip .tabulator-col-title{transform:rotate(180deg)}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-title{padding-top:20px;padding-right:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable.tabulator-col-vertical-flip .tabulator-col-title{padding-bottom:20px;padding-right:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-sorter{justify-content:center;inset:4px 0 auto}.tabulator .tabulator-header .tabulator-frozen{z-index:11;position:sticky;left:0}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-right{border-left:2px solid #aaa}.tabulator .tabulator-header .tabulator-calcs-holder{box-sizing:border-box;border-top:1px solid #aaa;border-bottom:1px solid #aaa;display:inline-block;background:#f3f3f3!important}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row{background:#f3f3f3!important}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-header .tabulator-frozen-rows-holder{padding-top:1em;display:inline-block}.tabulator .tabulator-header .tabulator-frozen-rows-holder:empty{display:none}.tabulator .tabulator-tableholder{-webkit-overflow-scrolling:touch;white-space:nowrap;width:100%;position:relative;overflow:auto}.tabulator .tabulator-tableholder:focus{outline:none}.tabulator .tabulator-tableholder .tabulator-placeholder{box-sizing:border-box;justify-content:center;align-items:center;width:100%;min-width:100%;display:flex}.tabulator .tabulator-tableholder .tabulator-placeholder[tabulator-render-mode=virtual]{min-height:100%}.tabulator .tabulator-tableholder .tabulator-placeholder .tabulator-placeholder-contents{color:#ccc;text-align:center;white-space:normal;padding:10px;font-size:20px;font-weight:700;display:inline-block}.tabulator .tabulator-tableholder .tabulator-table{color:#333;white-space:nowrap;background-color:#fff;display:inline-block;position:relative;overflow:visible}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs{font-weight:700;background:#e2e2e2!important}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-top{border-bottom:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-bottom{border-top:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-range-overlay{pointer-events:none;z-index:10;position:absolute;inset:0}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range{box-sizing:border-box;border:1px solid #2975dd;position:absolute}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range.tabulator-range-active:after{content:"";background-color:#2975dd;border-radius:999px;width:6px;height:6px;position:absolute;bottom:-3px;right:-3px}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range-cell-active{box-sizing:border-box;border:2px solid #2975dd;position:absolute}.tabulator .tabulator-footer{color:#555;-webkit-user-select:none;-moz-user-select:none;user-select:none;-khtml-user-select:none;-o-user-select:none;white-space:nowrap;background-color:#e6e6e6;border-top:1px solid #999;font-weight:700}.tabulator .tabulator-footer .tabulator-footer-contents{flex-direction:row;justify-content:space-between;align-items:center;padding:5px 10px;display:flex}.tabulator .tabulator-footer .tabulator-footer-contents:empty{display:none}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs{margin-top:-5px;overflow-x:auto}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab{border:1px solid #999;border-top:none;border-bottom-right-radius:5px;border-bottom-left-radius:5px;padding:5px;font-size:.9em;display:inline-block}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab:hover{cursor:pointer;opacity:.7}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab.tabulator-spreadsheet-tab-active{background:#fff}.tabulator .tabulator-footer .tabulator-calcs-holder{box-sizing:border-box;text-align:left;border-top:1px solid #aaa;border-bottom:1px solid #aaa;width:100%;overflow:hidden;background:#f3f3f3!important}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row{display:inline-block;background:#f3f3f3!important}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder:only-child{border-bottom:none;margin-bottom:-5px}.tabulator .tabulator-footer>*+.tabulator-page-counter{margin-left:10px}.tabulator .tabulator-footer .tabulator-page-counter{font-weight:400}.tabulator .tabulator-footer .tabulator-paginator{color:#555;font-family:inherit;font-size:inherit;font-weight:inherit;text-align:right;flex:1}.tabulator .tabulator-footer .tabulator-page-size{border:1px solid #aaa;border-radius:3px;margin:0 5px;padding:2px 5px;display:inline-block}.tabulator .tabulator-footer .tabulator-pages{margin:0 7px}.tabulator .tabulator-footer .tabulator-page{background:#fff3;border:1px solid #aaa;border-radius:3px;margin:0 2px;padding:2px 5px;display:inline-block}.tabulator .tabulator-footer .tabulator-page.active{color:#d00}.tabulator .tabulator-footer .tabulator-page:disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-footer .tabulator-page:not(disabled):hover{color:#fff;cursor:pointer;background:#0003}}.tabulator .tabulator-col-resize-handle{vertical-align:middle;z-index:11;width:6px;margin-left:-3px;margin-right:-3px;display:inline-block;position:relative}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-col-resize-handle:hover{cursor:ew-resize}}.tabulator .tabulator-col-resize-handle:last-of-type{width:3px;margin-right:0}.tabulator .tabulator-col-resize-guide{opacity:.5;background-color:#999;width:4px;height:100%;margin-left:-.5px;position:absolute;top:0}.tabulator .tabulator-row-resize-guide{opacity:.5;background-color:#999;width:100%;height:4px;margin-top:-.5px;position:absolute;left:0}.tabulator .tabulator-alert{text-align:center;z-index:100;background:#0006;align-items:center;width:100%;height:100%;display:flex;position:absolute;top:0;left:0}.tabulator .tabulator-alert .tabulator-alert-msg{background:#fff;border-radius:10px;margin:0 auto;padding:10px 20px;font-size:16px;font-weight:700;display:inline-block}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-msg{color:#000;border:4px solid #333}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-error{color:#590000;border:4px solid #d00}.tabulator-row{box-sizing:border-box;background-color:#fff;min-height:22px;position:relative}.tabulator-row.tabulator-row-even{background-color:#efefef}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selectable:hover{cursor:pointer;background-color:#bbb}}.tabulator-row.tabulator-selected{background-color:#9abcea}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selected:hover{cursor:pointer;background-color:#769bcc}}.tabulator-row.tabulator-row-moving{background:#fff;border:1px solid #000}.tabulator-row.tabulator-moving{pointer-events:none;z-index:15;border-top:1px solid #aaa;border-bottom:1px solid #aaa;position:absolute}.tabulator-row.tabulator-range-highlight .tabulator-cell.tabulator-range-row-header{color:#000;background-color:#d6d6d6}.tabulator-row.tabulator-range-highlight.tabulator-range-selected .tabulator-cell.tabulator-range-row-header,.tabulator-row.tabulator-range-selected .tabulator-cell.tabulator-range-row-header{color:#fff;background-color:#3876ca}.tabulator-row .tabulator-row-resize-handle{height:5px;position:absolute;bottom:0;left:0;right:0}.tabulator-row .tabulator-row-resize-handle.prev{top:0;bottom:auto}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-row-resize-handle:hover{cursor:ns-resize}}.tabulator-row .tabulator-responsive-collapse{box-sizing:border-box;border-top:1px solid #aaa;border-bottom:1px solid #aaa;padding:5px}.tabulator-row .tabulator-responsive-collapse:empty{display:none}.tabulator-row .tabulator-responsive-collapse table{font-size:14px}.tabulator-row .tabulator-responsive-collapse table tr td{position:relative}.tabulator-row .tabulator-responsive-collapse table tr td:first-of-type{padding-right:10px}.tabulator-row .tabulator-cell{box-sizing:border-box;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap;border-right:1px solid #aaa;outline:none;padding:4px;display:inline-block;position:relative;overflow:hidden}.tabulator-row .tabulator-cell.tabulator-row-header{background:#e6e6e6;border-bottom:1px solid #aaa;border-right:1px solid #999}.tabulator-row .tabulator-cell.tabulator-frozen{background-color:inherit;z-index:11;display:inline-block;position:sticky;left:0}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-left:2px solid #aaa}.tabulator-row .tabulator-cell.tabulator-editing{border:1px solid #1d68cd;outline:none;padding:0}.tabulator-row .tabulator-cell.tabulator-editing input,.tabulator-row .tabulator-cell.tabulator-editing select{background:0 0;border:1px;outline:none}.tabulator-row .tabulator-cell.tabulator-validation-fail{border:1px solid #d00}.tabulator-row .tabulator-cell.tabulator-validation-fail input,.tabulator-row .tabulator-cell.tabulator-validation-fail select{color:#d00;background:0 0;border:1px}.tabulator-row .tabulator-cell.tabulator-row-handle{-webkit-user-select:none;-moz-user-select:none;-khtml-user-select:none;-o-user-select:none;justify-content:center;align-items:center;display:inline-flex}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box{width:80%}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box .tabulator-row-handle-bar{background:#666;width:100%;height:3px;margin-top:2px}.tabulator-row .tabulator-cell.tabulator-range-selected:not(.tabulator-range-only-cell-selected):not(.tabulator-range-row-header){background-color:#9abcea}.tabulator-row .tabulator-cell .tabulator-data-tree-branch-empty{width:7px;display:inline-block}.tabulator-row .tabulator-cell .tabulator-data-tree-branch{vertical-align:middle;border-bottom:2px solid #aaa;border-left:2px solid #aaa;border-bottom-left-radius:1px;width:7px;height:9px;margin-top:-9px;margin-right:5px;display:inline-block}.tabulator-row .tabulator-cell .tabulator-data-tree-control{vertical-align:middle;background:#0000001a;border:1px solid #333;border-radius:2px;justify-content:center;align-items:center;width:11px;height:11px;margin-right:5px;display:inline-flex;overflow:hidden}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-data-tree-control:hover{cursor:pointer;background:#0003}}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:0 0;width:1px;height:7px;display:inline-block;position:relative}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{content:"";background:#333;width:7px;height:1px;position:absolute;top:3px;left:-3px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#333;width:1px;height:7px;display:inline-block;position:relative}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{content:"";background:#333;width:7px;height:1px;position:absolute;top:3px;left:-3px}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle{color:#fff;-webkit-user-select:none;-moz-user-select:none;-khtml-user-select:none;-o-user-select:none;background:#666;border-radius:20px;justify-content:center;align-items:center;width:15px;height:15px;font-size:1.1em;font-weight:700;display:inline-flex}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle:hover{cursor:pointer;opacity:.7}}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-close{display:initial}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-open{display:none}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle svg{stroke:#fff}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle .tabulator-responsive-collapse-toggle-close{display:none}.tabulator-row .tabulator-cell .tabulator-traffic-light{border-radius:14px;width:14px;height:14px;display:inline-block}.tabulator-row.tabulator-group{box-sizing:border-box;background:#ccc;border-top:1px solid #999;border-bottom:1px solid #999;border-right:1px solid #aaa;min-width:100%;padding:5px 5px 5px 10px;font-weight:700}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-group:hover{cursor:pointer;background-color:#0000001a}}.tabulator-row.tabulator-group.tabulator-group-visible .tabulator-arrow{border:6px solid #0000;border-top-color:#666;border-bottom:0;margin-right:10px}.tabulator-row.tabulator-group.tabulator-group-level-1{padding-left:30px}.tabulator-row.tabulator-group.tabulator-group-level-2{padding-left:50px}.tabulator-row.tabulator-group.tabulator-group-level-3{padding-left:70px}.tabulator-row.tabulator-group.tabulator-group-level-4{padding-left:90px}.tabulator-row.tabulator-group.tabulator-group-level-5{padding-left:110px}.tabulator-row.tabulator-group .tabulator-group-toggle{display:inline-block}.tabulator-row.tabulator-group .tabulator-arrow{vertical-align:middle;border:6px solid #0000;border-left-color:#666;border-right:0;width:0;height:0;margin-right:16px;display:inline-block}.tabulator-row.tabulator-group span{color:#d00;margin-left:10px}.tabulator-toggle{box-sizing:border-box;background:#dcdcdc;border:1px solid #ccc;flex-direction:row;display:flex}.tabulator-toggle.tabulator-toggle-on{background:#1c6cc2}.tabulator-toggle .tabulator-toggle-switch{box-sizing:border-box;background:#fff;border:1px solid #ccc}.tabulator-popup-container{-webkit-overflow-scrolling:touch;box-sizing:border-box;z-index:10000;background:#fff;border:1px solid #aaa;font-size:14px;display:inline-block;position:absolute;overflow-y:auto;box-shadow:0 0 5px #0003}.tabulator-popup{border-radius:3px;padding:5px}.tabulator-tooltip{box-shadow:none;pointer-events:none;border-radius:2px;max-width:min(500px,100%);padding:3px 5px;font-size:12px}.tabulator-menu .tabulator-menu-item{box-sizing:border-box;user-select:none;padding:5px 10px;position:relative}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator-menu .tabulator-menu-item:not(.tabulator-menu-item-disabled):hover{cursor:pointer;background:#efefef}}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu{padding-right:25px}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu:after{content:"";vertical-align:top;border:1px solid #aaa;border-width:1px 1px 0 0;width:7px;height:7px;display:inline-block;position:absolute;top:calc(5px + .4em);right:10px;transform:rotate(45deg)}.tabulator-menu .tabulator-menu-separator{border-top:1px solid #aaa}.tabulator-edit-list{-webkit-overflow-scrolling:touch;max-height:200px;font-size:14px;overflow-y:auto}.tabulator-edit-list .tabulator-edit-list-item{color:#333;outline:none;padding:4px}.tabulator-edit-list .tabulator-edit-list-item.active{color:#fff;background:#1d68cd}.tabulator-edit-list .tabulator-edit-list-item.active.focused{outline:1px solid #ffffff80}.tabulator-edit-list .tabulator-edit-list-item.focused{outline:1px solid #1d68cd}@media (hover:hover) and (pointer:fine){.tabulator-edit-list .tabulator-edit-list-item:hover{color:#fff;cursor:pointer;background:#1d68cd}}.tabulator-edit-list .tabulator-edit-list-placeholder{color:#333;text-align:center;padding:4px}.tabulator-edit-list .tabulator-edit-list-group{color:#333;border-bottom:1px solid #aaa;padding:6px 4px 4px;font-weight:700}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-2,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-2{padding-left:12px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-3,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-3{padding-left:20px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-4,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-4{padding-left:28px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-5,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-5{padding-left:36px}.tabulator.tabulator-ltr{direction:ltr}.tabulator.tabulator-rtl{text-align:initial;direction:rtl}.tabulator.tabulator-rtl .tabulator-header .tabulator-col{border-left:1px solid #aaa;border-right:initial;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{margin-left:-1px;margin-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-left:25px;padding-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{left:8px;right:auto}.tabulator.tabulator-rtl .tabulator-tableholder .tabulator-range-overlay .tabulator-range.tabulator-range-active:after{content:"";background-color:#2975dd;border-radius:999px;width:6px;height:6px;position:absolute;bottom:-3px;left:-3px;right:auto}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell{border-left:1px solid #aaa;border-right:initial}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-left:initial;border-right:2px solid #aaa;border-bottom-right-radius:1px;border-bottom-left-radius:0;margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-control{margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-left:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-right:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-col-resize-handle:last-of-type{width:3px;margin-left:0;margin-right:-3px}.tabulator.tabulator-rtl .tabulator-footer .tabulator-calcs-holder{text-align:initial}.tabulator-print-fullscreen{z-index:10000;position:absolute;inset:0}body.tabulator-print-fullscreen-hide>:not(.tabulator-print-fullscreen){display:none!important}.tabulator-print-table{border-collapse:collapse}.tabulator-print-table .tabulator-data-tree-branch{vertical-align:middle;border-bottom:2px solid #aaa;border-left:2px solid #aaa;border-bottom-left-radius:1px;width:7px;height:9px;margin-top:-9px;margin-right:5px;display:inline-block}.tabulator-print-table .tabulator-print-table-group{box-sizing:border-box;background:#ccc;border-top:1px solid #999;border-bottom:1px solid #999;border-right:1px solid #aaa;min-width:100%;padding:5px 5px 5px 10px;font-weight:700}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-print-table-group:hover{cursor:pointer;background-color:#0000001a}}.tabulator-print-table .tabulator-print-table-group.tabulator-group-visible .tabulator-arrow{border:6px solid #0000;border-top-color:#666;border-bottom:0;margin-right:10px}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-1 td{padding-left:30px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-2 td{padding-left:50px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-3 td{padding-left:70px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-4 td{padding-left:90px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-5 td{padding-left:110px!important}.tabulator-print-table .tabulator-print-table-group .tabulator-group-toggle{display:inline-block}.tabulator-print-table .tabulator-print-table-group .tabulator-arrow{vertical-align:middle;border:6px solid #0000;border-left-color:#666;border-right:0;width:0;height:0;margin-right:16px;display:inline-block}.tabulator-print-table .tabulator-print-table-group span{color:#d00;margin-left:10px}.tabulator-print-table .tabulator-data-tree-control{vertical-align:middle;background:#0000001a;border:1px solid #333;border-radius:2px;justify-content:center;align-items:center;width:11px;height:11px;margin-right:5px;display:inline-flex;overflow:hidden}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-data-tree-control:hover{cursor:pointer;background:#0003}}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:0 0;width:1px;height:7px;display:inline-block;position:relative}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{content:"";background:#333;width:7px;height:1px;position:absolute;top:3px;left:-3px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#333;width:1px;height:7px;display:inline-block;position:relative}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{content:"";background:#333;width:7px;height:1px;position:absolute;top:3px;left:-3px}
22
22
  </style><style>
23
- :host,#panel{background:var(--bkg-color);height:16rem;min-height:10rem}#panel{inset:0;overflow:hidden}#gutter{background:var(--bkg-color);cursor:row-resize;width:100%;height:.4rem;position:absolute;top:0;left:0}#gutter:hover{background:var(--bkg-color-grad1)}#gutter:after{content:" ";background:var(--bkg-color-grad1);border-radius:1rem;width:4rem;height:.4rem;display:block;position:relative;left:calc(50% - 2rem)}#close{color:#777;background-color:var(--bkg-color);cursor:pointer;border:none;width:1rem;height:1rem;margin:.5rem;padding:0;line-height:1rem;position:absolute;top:6px;right:0}#close i:before{content:"uf00d"}#close:hover{color:#000;border-color:#000}#content{background:var(--bkg-color);flex-direction:column;margin:0 .5rem .5rem;padding:0;display:flex;position:absolute;inset:.5rem 0 0;overflow:hidden}#header{flex:0;justify-content:space-between;display:flex}.selection-panel{margin-right:.5rem;display:flex;& .gg-icon-button[disabled]{pointer-events:none;opacity:.5}& .gg-icon-button[disabled]:hover{background-color:#0000}& .label{align-self:center;padding-left:1rem}}.gg-tabs{margin:0}#grid{flex:1;overflow:hidden}.geo-type{margin-right:.5rem}::-webkit-scrollbar{width:8px}::-webkit-scrollbar-thumb{background:#999}.tiny-margin{margin:.1rem}.tabulator-col[aria-sort=none] .tabulator-col-sorter i{color:#999}.tabulator-col[aria-sort=asc] .tabulator-col-sorter i{color:red}.tabulator-col[aria-sort=desc] .tabulator-col-sorter i{color:red;transform:scaleY(-1)}#tabulator{background-color:var(--bkg-color)}#tabulator .tabulator-header .tabulator-col-content{background-color:var(--bkg-color);color:var(--text-color)}#tabulator .tabulator-header .tabulator-col,#tabulator .tabulator-header .tabulator-col-row-handle{white-space:normal}#tabulator .tabulator-tableholder .tabulator-table .tabulator-row{background-color:var(--bkg-color);color:var(--text-color)}#tabulator .tabulator-tableholder .tabulator-table .tabulator-row:nth-child(2n){background-color:var(--bkg-color-grad1)}#tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-selected{background-color:#9abcea}.tabulator-cell img{float:left;height:1.5rem;margin-right:.5rem}.tabulator-cell span{align-items:center;line-height:1.5rem;display:inline-flex;position:relative}.tabulator-cell span sup{position:absolute;top:-.3rem;right:-.5rem}@media (hover:hover) and (pointer:fine){#tabulator .tabulator-tableholder .tabulator-table .tabulator-row:hover{cursor:pointer;background-color:var(--bkg-color-grad2)}}img.sort-icon{width:1rem;height:1rem;&.default{opacity:.5}&:hover{opacity:1}}
23
+ :host,#panel{background:var(--bkg-color);height:16rem;min-height:10rem}#panel{inset:0;overflow:hidden}#gutter{background:var(--bkg-color);cursor:row-resize;width:100%;height:.4rem;position:absolute;top:0;left:0}#gutter:hover{background:var(--bkg-color-grad1)}#gutter:after{content:" ";background:var(--bkg-color-grad1);border-radius:1rem;width:4rem;height:.4rem;display:block;position:relative;left:calc(50% - 2rem)}#close{color:#777;background-color:var(--bkg-color);cursor:pointer;border:none;width:1rem;height:1rem;margin:.5rem;padding:0;line-height:1rem;position:absolute;top:6px;right:0}#close i:before{content:"uf00d"}#close:hover{color:#000;border-color:#000}#content{background:var(--bkg-color);flex-direction:column;margin:0 .5rem .5rem;padding:0;display:flex;position:absolute;inset:.5rem 0 0;overflow:hidden}#header{flex:0;justify-content:space-between;display:flex}.selection-panel{margin-right:.5rem;display:flex;& .gg-icon-button[disabled]{pointer-events:none;opacity:.5}& .gg-icon-button[disabled]:hover{background-color:#0000}& .label{align-self:center;padding-left:1rem}}.gg-tabs{margin:0}#grid{flex:1;overflow:hidden}.geo-type{margin-right:.5rem}::-webkit-scrollbar{width:8px}::-webkit-scrollbar-thumb{background:#999}.tiny-margin{margin:.1rem}.tabulator-col[aria-sort=none] .tabulator-col-sorter i{color:#999}.tabulator-col[aria-sort=asc] .tabulator-col-sorter i{color:red}.tabulator-col[aria-sort=desc] .tabulator-col-sorter i{color:red;transform:scaleY(-1)}#tabulator{background-color:var(--bkg-color)}#tabulator .tabulator-header .tabulator-col-content{background-color:var(--bkg-color);color:var(--text-color)}#tabulator .tabulator-header .tabulator-col,#tabulator .tabulator-header .tabulator-col-row-handle{white-space:normal}#tabulator .tabulator-tableholder .tabulator-table .tabulator-row{background-color:var(--bkg-color);color:var(--text-color)}#tabulator .tabulator-tableholder .tabulator-table .tabulator-row:nth-child(2n){background-color:var(--bkg-color-grad1)}#tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-selected{background-color:#9abcea}.tabulator{font-size:inherit!important}.tabulator-cell{text-wrap:pretty!important;height:fit-content!important}.tabulator-cell a{width:inherit;height:inherit}.tabulator-cell img{width:inherit;height:inherit;object-fit:scale-down;float:left;max-height:3rem;margin-right:.5rem}.tabulator-cell span{align-items:center;line-height:1.5rem;display:inline-flex;position:relative}.tabulator-cell span sup{position:absolute;top:-.3rem;right:-.5rem}@media (hover:hover) and (pointer:fine){#tabulator .tabulator-tableholder .tabulator-table .tabulator-row:hover{cursor:pointer;background-color:var(--bkg-color-grad2)}}img.sort-icon{width:1rem;height:1rem;&.default{opacity:.5}&:hover{opacity:1}}
24
24
  </style>
25
25
  <div id="panel"><div id="content"><div id="header"><div class="gg-tabs">${this.getTabHeaders().map((th) => uHtml ` <button id="${th.id}" i18n="${th.text}" class="${th.active ? 'gg-tab active' : 'gg-tab'}" .active="${th.active}" onclick="${() => this.displayGrid(th.id, true)}"></button> `)}</div><div class="selection-panel"><span class="label" i18n="Selection"></span> <button class="gg-icon-button gg-medium" onclick="${() => this.selectAll()}" tip="Select all"><img alt="select all icon" src="icons/select-all-on.svg"></button> <button class="gg-icon-button gg-medium" onclick="${() => this.selectNone()}" tip="Select none"><img alt="select none icon" src="icons/select-all-off.svg"></button> <button class="gg-icon-button gg-medium" onclick="${() => this.invertSelection()}" tip="Invert selection"><img alt="invert selection icon" src="icons/select-invert.svg"></button><girafe-get-directions></girafe-get-directions><button class="${this.resultsSelected ? 'gg-icon-button gg-medium' : 'gg-icon-button gg-medium disabled'}" ?disabled="${!this.resultsSelected}" onclick="${() => this.zoomToSelection()}" tip="Zoom to"><img alt="zoom to selection icon" src="icons/zoom-to-selection.svg"></button> <button class="${this.resultsSelected ? 'gg-icon-button gg-medium' : 'gg-icon-button gg-medium disabled'}" ?disabled="${!this.resultsSelected}" onclick="${() => this.generateCSV()}" tip="Generate CSV"><img alt="download csv icon" src="icons/arrow-download.svg"></button> <button class="gg-icon-button gg-medium" onclick="${() => this.closePanel()}" tip="Clear results and close Panel"><img alt="close-icon" src="icons/close.svg"></button></div></div><div id="tabulator"></div></div><div id="gutter"></div></div>`;
26
26
  };
@@ -1,6 +1,6 @@
1
1
  import type OlFeature from 'ol/Feature.js';
2
2
  import { GridDataById } from '../../../tools/featuretogriddatabyid.js';
3
- import { ColumnDefinition, RowComponent } from 'tabulator-tables';
3
+ import { ColumnComponent, ColumnDefinition, RowComponent } from 'tabulator-tables';
4
4
  import IGirafeContext from '../../../tools/context/icontext.js';
5
5
  /**
6
6
  * Represents the header text and state of a tab.
@@ -34,6 +34,11 @@ export default class SelectionTabulatorManager {
34
34
  private data;
35
35
  private readonly context;
36
36
  private readonly columnAliasHelper;
37
+ private readonly minimalColumnWidth;
38
+ private readonly columnPositionsBeforeFreezing;
39
+ private popupLastImageSrc;
40
+ private popupLastInstance;
41
+ private get tableColumns();
37
42
  constructor(context: IGirafeContext);
38
43
  /**
39
44
  * Set the HTML element to be used by the grid.
@@ -62,6 +67,7 @@ export default class SelectionTabulatorManager {
62
67
  */
63
68
  private gridDataToGridTab;
64
69
  columnsToGridColumns(idTable: string, columns: string[]): ColumnDefinition[];
70
+ getLookupColumnForFreezing(): [ColumnComponent, boolean];
65
71
  filterEmptyColumnsOnData(data: GridDataById): GridDataById;
66
72
  /**
67
73
  * @returns A newly created grid column object.
@@ -3,6 +3,8 @@ import { TabulatorFull as Tabulator } from 'tabulator-tables';
3
3
  import { getUid } from 'ol/util.js';
4
4
  import ColumnAliasHelper from '../../../tools/utils/aliases.js';
5
5
  import { linkify } from '../../../tools/utils/utils.js';
6
+ import { noop } from '../../../tools/utils/async.js';
7
+ import tippy from 'tippy.js';
6
8
  const geometryColumns = new Set(['geom', 'the_geom', 'geometry']);
7
9
  export default class SelectionTabulatorManager {
8
10
  featureToGridData;
@@ -13,10 +15,17 @@ export default class SelectionTabulatorManager {
13
15
  data = {};
14
16
  context;
15
17
  columnAliasHelper;
18
+ minimalColumnWidth = 64;
19
+ columnPositionsBeforeFreezing = {};
20
+ popupLastImageSrc;
21
+ popupLastInstance;
22
+ get tableColumns() {
23
+ return this.table?.getColumns(false) ?? [];
24
+ }
16
25
  constructor(context) {
17
26
  this.context = context;
18
27
  this.columnAliasHelper = new ColumnAliasHelper(context.stateManager);
19
- this.featureToGridData = new FeatureToGridDataById({ keepGeomProperty: true });
28
+ this.featureToGridData = new FeatureToGridDataById(this.context, { keepGeomProperty: true });
20
29
  }
21
30
  /**
22
31
  * Set the HTML element to be used by the grid.
@@ -56,9 +65,11 @@ export default class SelectionTabulatorManager {
56
65
  data: this.data[id].notOlProperties,
57
66
  columns: this.columnsToGridColumns(id, this.data[id].columns),
58
67
  selectableRows: true,
59
- layout: 'fitColumns',
68
+ layout: 'fitDataStretch',
69
+ movableColumns: true,
60
70
  locale: true,
61
71
  maxHeight: '750',
72
+ resizableRows: true,
62
73
  headerSortElement: function (_, dir) {
63
74
  switch (dir) {
64
75
  case 'asc':
@@ -68,7 +79,8 @@ export default class SelectionTabulatorManager {
68
79
  default:
69
80
  return '<img alt="sort-icon" src="icons/sort.svg" class="sort-icon default" />';
70
81
  }
71
- }
82
+ },
83
+ popupContainer: this.element
72
84
  });
73
85
  this.table?.on('rowSelectionChanged', (selection) => {
74
86
  // True if at least one row is selected.
@@ -137,11 +149,74 @@ export default class SelectionTabulatorManager {
137
149
  columnDefinition.push({
138
150
  title: this.context.i18nManager.getTranslation(columnAlias),
139
151
  field: column,
140
- formatter: 'html'
152
+ formatter: 'html',
153
+ headerTooltip: true,
154
+ minWidth: this.minimalColumnWidth,
155
+ headerMenu: (_e, _c) => [
156
+ {
157
+ label: (column) => this.context.i18nManager.getTranslation(column.getDefinition().frozen ? 'unfreeze-column' : 'freeze-column'),
158
+ action: (_e, column) => {
159
+ const columnDefinition = column.getDefinition();
160
+ const freezeOrNot = !columnDefinition.frozen;
161
+ const [toColumn, after] = this.getLookupColumnForFreezing();
162
+ if (freezeOrNot) {
163
+ this.columnPositionsBeforeFreezing[column.getField()] = this.tableColumns.findIndex((colComp) => colComp.getField() === column.getField());
164
+ this.table?.moveColumn(column, toColumn, after);
165
+ }
166
+ else {
167
+ const colPos = this.columnPositionsBeforeFreezing[column.getField()];
168
+ if (colPos) {
169
+ const previousColumn = this.tableColumns.at(colPos);
170
+ if (previousColumn) {
171
+ this.table?.moveColumn(column, previousColumn, true);
172
+ }
173
+ }
174
+ }
175
+ column
176
+ .updateDefinition({
177
+ ...columnDefinition,
178
+ frozen: !columnDefinition.frozen
179
+ })
180
+ .then(noop);
181
+ }
182
+ }
183
+ ],
184
+ contextMenu: [
185
+ {
186
+ label: this.context.i18nManager.getTranslation('copy-cell-content'),
187
+ action: (_e, cell) => {
188
+ navigator.clipboard?.writeText(cell.getElement().textContent);
189
+ }
190
+ }
191
+ ],
192
+ cellMouseEnter: (_e, cell) => {
193
+ const imageElement = cell.getElement().querySelector('img');
194
+ if (imageElement && this.popupLastImageSrc !== imageElement.src) {
195
+ if (this.popupLastInstance) {
196
+ this.popupLastInstance.destroy();
197
+ }
198
+ this.popupLastImageSrc = imageElement.src;
199
+ this.popupLastInstance = tippy(imageElement, {
200
+ arrow: true,
201
+ theme: 'image-popup',
202
+ content: (_ref) => {
203
+ const popupImageElement = document.createElement('img');
204
+ popupImageElement.src = imageElement.src;
205
+ return popupImageElement;
206
+ }
207
+ });
208
+ this.popupLastInstance.show();
209
+ }
210
+ }
141
211
  });
142
212
  }
143
213
  return columnDefinition;
144
214
  }
215
+ getLookupColumnForFreezing() {
216
+ const firstFrozenColumnDefinition = this.tableColumns.find((colComp) => colComp.getDefinition().frozen);
217
+ const firstColumnDefinition = this.tableColumns[0];
218
+ return firstFrozenColumnDefinition ? [firstFrozenColumnDefinition, true] : [firstColumnDefinition, false];
219
+ }
145
220
  filterEmptyColumnsOnData(data) {
146
221
  for (const [_, entry] of Object.entries(data)) {
147
222
  const columns = entry.columns;
@@ -26,7 +26,7 @@ declare class SelectionWindowComponent extends GirafeDraggableElement {
26
26
  private isVisibleComponentSetup;
27
27
  private readonly debounceOnFeaturesSelected;
28
28
  private resizeWindow;
29
- private readonly featureToGridData;
29
+ private featureToGridData;
30
30
  private windowFeatures;
31
31
  private windowLayers;
32
32
  visible: boolean;
@@ -33,7 +33,7 @@ table{border-collapse:collapse;table-layout:auto;width:100%}td.feature-id{text-a
33
33
  isVisibleComponentSetup = false;
34
34
  debounceOnFeaturesSelected = debounce(this.onFeaturesSelected.bind(this), 200);
35
35
  resizeWindow = null;
36
- featureToGridData = new FeatureToGridDataById({ removeEmptyColumns: false });
36
+ featureToGridData;
37
37
  windowFeatures = [];
38
38
  windowLayers = [];
39
39
  visible = false;
@@ -52,6 +52,7 @@ table{border-collapse:collapse;table-layout:auto;width:100%}td.feature-id{text-a
52
52
  super.connectedCallback();
53
53
  this.csvManager = new CsvManager(this.context);
54
54
  this.columnAliasHelper = new ColumnAliasHelper(this.context.stateManager);
55
+ this.featureToGridData = new FeatureToGridDataById(this.context, { removeEmptyColumns: false });
55
56
  this.render();
56
57
  this.registerVisibilityEvents();
57
58
  }
@@ -96,6 +96,8 @@ header{border-top:1px solid #ddd;align-items:center;height:2rem;display:flex}.to
96
96
  }
97
97
  createOpacityTooltip() {
98
98
  const el = this.shadow.getElementById('opacity');
99
+ if (!el)
100
+ return;
99
101
  tippy(el, {
100
102
  trigger: 'click',
101
103
  arrow: true,
@@ -116,6 +118,8 @@ header{border-top:1px solid #ddd;align-items:center;height:2rem;display:flex}.to
116
118
  }
117
119
  createFilterTooltip() {
118
120
  const el = this.shadow.getElementById('filter');
121
+ if (!el)
122
+ return;
119
123
  tippy(el, {
120
124
  trigger: 'click',
121
125
  arrow: true,
package/decs.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- declare module 'tippy.js';
2
+ // declare module 'tippy.js';
3
3
  declare module 'olcs';
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "GeoGirafe PSC",
6
6
  "url": "https://doc.geomapfish.dev"
7
7
  },
8
- "version": "1.1.0-dev.2468633458",
8
+ "version": "1.1.0-dev.2527788074",
9
9
  "type": "module",
10
10
  "engines": {
11
11
  "node": ">=20.19.0"
package/service-worker.js CHANGED
@@ -1,11 +1,11 @@
1
1
  "use strict";
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  /**
4
- * To allow offline mode, the first 100 queries made by the application will be cached.
4
+ * To allow offline mode, the first 300 queries made by the application will be cached.
5
5
  * We could have implemented a more specific cache with file extensions for example
6
6
  * But then we should define exactly what should be cached.
7
7
  * This can be quite complex, because we cannot just cache all images,
8
- * as some of them are WMTS or WMTS results and others ar icons.
8
+ * as some of them are WMTS or WMS results and others are icons.
9
9
  * So a simple solution here is to cache all the first queries that are done
10
10
  * by the application when it starts. With this we should have all the necessary
11
11
  * cache to be able to start the application in offline mode
@@ -15,23 +15,37 @@
15
15
  */
16
16
  const sw = globalThis;
17
17
  const currentOrigin = self.location.origin;
18
- let storeVersion; // Version of the store. Value is defined by OfflineManager.
19
- let dbCacheName; // Name of the cache for downloaded data. Value is defined by OfflineManager.
20
- let tilesStoreName; // Name of the store used to downloaded tiles. Value is defined by OfflineManager.
21
- let logLevel; // Current log level. Value is defined by OfflineManager.
22
- let audience; // List of domains for which the Authorization Token and cookies should be sent
23
- let audienceExcludedPaths; // List of regex paths at audience where Authorization Token and cookies should not be sent
24
- let accessToken; // The current access token
25
- let loginState; // The current login status
26
- let authMode; // How the authentication should be sent to the audience
27
- let refererPolicy; // RefererPolicy used for the queries. Default strict-origin-when-cross-origin
18
+ /**
19
+ * The service worker state needs to be persisted
20
+ * because when not using the app, the service worker can be paused by the browser
21
+ * and in this case it will be reinitialized with the next query
22
+ * with a base default empty state.
23
+ * See: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope
24
+ */
25
+ const stateDbName = 'geogirafe-sw-state';
26
+ const stateStoreName = 'state';
27
+ const stateKey = 'config';
28
+ let initialized = false;
29
+ let state = {
30
+ storeVersion: 1,
31
+ dbCacheName: 'geogirafe-cache',
32
+ tilesStoreName: 'tiles',
33
+ logLevel: 'warning',
34
+ audience: [],
35
+ audienceExcludedPaths: [],
36
+ accessToken: undefined,
37
+ loginState: undefined,
38
+ authMode: undefined,
39
+ refererPolicy: undefined
40
+ };
28
41
  function isLoggedIn() {
29
- return loginState === 'loggedIn' || loginState === 'issuer.loggedIn';
42
+ return state.loginState === 'loggedIn' || state.loginState === 'issuer.loggedIn';
30
43
  }
31
44
  const offlineTimeout = 3000; // Timeout in case of not reachable IndexedDB. (3 sec.)
32
45
  const appCacheName = 'pages'; // Name of the cache for application pages
33
46
  const maxCacheCount = 300; // Number of queries that should be cached by the service-worker for offline usage.
34
47
  let cacheCount = 0; // Counter related to the max value above
48
+ let audienceExcludedPathPatterns = []; // list of regex pattern built from state.audienceExcludedPaths
35
49
  sw.addEventListener('message', handleMessage);
36
50
  sw.addEventListener('install', handleInstall);
37
51
  sw.addEventListener('activate', handleActivate);
@@ -43,79 +57,86 @@ sw.addEventListener('fetch', (event) => {
43
57
  });
44
58
  function handleMessage(event) {
45
59
  if (event.origin !== currentOrigin) {
46
- console.warn(`Message ignored: origin not allwed (${event.origin}`);
60
+ console.warn(`Message ignored: origin not allowed (${event.origin})`);
47
61
  return;
48
62
  }
49
63
  const data = event.data;
50
64
  if (data.logLevel) {
51
- logLevel = data.logLevel;
52
- log(`LogLevel changed: ${logLevel}`);
65
+ state.logLevel = data.logLevel;
66
+ log(`LogLevel changed: ${state.logLevel}`);
53
67
  }
54
68
  if (data.tilesStoreName) {
55
- tilesStoreName = data.tilesStoreName;
56
- log(`tilesStoreName changed: ${tilesStoreName}`);
69
+ state.tilesStoreName = data.tilesStoreName;
70
+ log(`tilesStoreName changed: ${state.tilesStoreName}`);
57
71
  }
58
72
  if (data.storeVersion) {
59
- storeVersion = data.storeVersion;
60
- log(`storeVersion changed: ${storeVersion}`);
73
+ state.storeVersion = data.storeVersion;
74
+ log(`storeVersion changed: ${state.storeVersion}`);
61
75
  }
62
76
  if (data.dbCacheName) {
63
- dbCacheName = data.dbCacheName;
64
- log(`dbCacheName changed: ${dbCacheName}`);
77
+ state.dbCacheName = data.dbCacheName;
78
+ log(`dbCacheName changed: ${state.dbCacheName}`);
65
79
  }
66
80
  if (data.audience) {
67
- audience = data.audience ?? [];
68
- log(`audience changed: ${audience}`);
81
+ state.audience = data.audience ?? [];
82
+ log(`audience changed: ${state.audience}`);
69
83
  }
70
- audienceExcludedPaths = [];
71
84
  if (data.audienceExcludedPaths) {
72
- audienceExcludedPaths = data.audienceExcludedPaths.map((str) => new RegExp(str));
73
- log(`audienceExcludedPaths changed: ${audienceExcludedPaths}`);
85
+ state.audienceExcludedPaths = data.audienceExcludedPaths;
86
+ audienceExcludedPathPatterns = state.audienceExcludedPaths.map((str) => new RegExp(str));
87
+ log(`audienceExcludedPaths changed: ${state.audienceExcludedPaths}`);
74
88
  }
75
89
  if (data.access_token) {
76
- accessToken = data.access_token;
77
- log(`access_token changed: ${accessToken}`);
90
+ state.accessToken = data.access_token;
91
+ log(`access_token changed: ${state.accessToken}`);
78
92
  }
79
93
  if (data.clear_access_token) {
80
- accessToken = undefined;
94
+ state.accessToken = undefined;
81
95
  log('access_token cleared');
82
96
  }
83
97
  if (data.authMode) {
84
- authMode = data.authMode;
85
- log(`authMode changed: ${authMode}`);
98
+ state.authMode = data.authMode;
99
+ log(`authMode changed: ${state.authMode}`);
86
100
  }
87
101
  if (data.refererPolicy) {
88
- refererPolicy = data.refererPolicy;
89
- log(`refererPolicy changed: ${refererPolicy}`);
102
+ state.refererPolicy = data.refererPolicy;
103
+ log(`refererPolicy changed: ${state.refererPolicy}`);
90
104
  }
91
105
  if (data.loginState) {
92
- loginState = data.loginState;
93
- log(`loginState changed: ${loginState}`);
106
+ state.loginState = data.loginState;
107
+ log(`loginState changed: ${state.loginState}`);
94
108
  }
95
- if (data.messageId) {
96
- // Reply to the message with the same messageId
97
- const source = event.source;
98
- if (source) {
99
- source.postMessage({ messageId: data.messageId, status: 'ServiceWorker updated' });
109
+ event.waitUntil((async () => {
110
+ // Persist the service worker state before acknowledging the message.
111
+ await saveState();
112
+ if (data.messageId) {
113
+ const source = event.source;
114
+ if (source) {
115
+ source.postMessage({ messageId: data.messageId, status: 'ServiceWorker updated' });
116
+ }
100
117
  }
101
- }
118
+ })());
102
119
  }
103
120
  function handleInstall() {
104
121
  sw.skipWaiting();
105
- log('Service Worker installed');
122
+ log('Service worker installed');
106
123
  }
107
124
  function handleActivate(event) {
108
125
  event.waitUntil((async () => {
109
- if ('clients' in self) {
126
+ if ('clients' in sw) {
127
+ await sw.clients.claim();
110
128
  const clientsList = await sw.clients.matchAll({ type: 'window' });
111
129
  for (const client of clientsList) {
112
130
  client.navigate(client.url);
113
- log('Page reloaded by the Service Worker.');
131
+ log('Page reloaded by the service worker.');
114
132
  }
115
133
  }
116
134
  })());
117
135
  }
118
136
  async function handleFetchEvent(event) {
137
+ if (!initialized) {
138
+ await loadState();
139
+ }
119
140
  const newRequest = getRequest(event.request);
120
141
  let response = await fetchAndCache(newRequest);
121
142
  // Use cache only for GET queries
@@ -125,7 +146,7 @@ async function handleFetchEvent(event) {
125
146
  response = await loadFromCache(event.request);
126
147
  }
127
148
  if (!response) {
128
- // Not found in cache. We try to load from indexedDB
149
+ // Not found in cache. We try to load from IndexedDB.
129
150
  response = await loadFromIndexedDB(event.request);
130
151
  }
131
152
  }
@@ -148,7 +169,7 @@ function isCacheAllowed(request) {
148
169
  async function fetchAndCache(request) {
149
170
  try {
150
171
  const response = await fetch(request);
151
- if (cacheCount < maxCacheCount && isCacheAllowed(request) && response.type !== 'opaque') {
172
+ if (cacheCount < maxCacheCount && isCacheAllowed(request) && response.ok && response.type !== 'opaque') {
152
173
  // Fetch was successful. We cache the result if necessary and return the response.
153
174
  cacheCount++;
154
175
  log(`SW ${cacheCount}/${maxCacheCount} caching ${request.url} for offline use.`);
@@ -169,19 +190,19 @@ function getRequest(request) {
169
190
  try {
170
191
  const requestedUrl = new URL(request.url);
171
192
  const hostname = requestedUrl.hostname;
172
- const shouldExclude = audienceExcludedPaths.some((pattern) => pattern.test(requestedUrl.pathname));
173
- if (audience?.includes(hostname) && !shouldExclude) {
193
+ const shouldExclude = audienceExcludedPathPatterns.some((pattern) => pattern.test(requestedUrl.pathname));
194
+ if (state.audience?.includes(hostname) && !shouldExclude) {
174
195
  // Prepare headers
175
196
  const headers = new Headers(request.headers);
176
- if (authMode == 'token' && isLoggedIn() && accessToken) {
177
- headers.set('Authorization', `Bearer ${accessToken}`);
197
+ if (state.authMode == 'token' && isLoggedIn() && state.accessToken) {
198
+ headers.set('Authorization', `Bearer ${state.accessToken}`);
178
199
  }
179
200
  // Prepare options
180
201
  const fetchOptions = {
181
202
  headers: headers,
182
- credentials: isLoggedIn() && authMode === 'cookie' ? 'include' : 'omit',
203
+ credentials: isLoggedIn() && state.authMode === 'cookie' ? 'include' : 'omit',
183
204
  referrer: request.referrer,
184
- referrerPolicy: refererPolicy
205
+ referrerPolicy: state.refererPolicy
185
206
  };
186
207
  return new Request(request, fetchOptions);
187
208
  }
@@ -209,15 +230,15 @@ async function loadFromCache(request) {
209
230
  async function loadFromIndexedDB(request) {
210
231
  try {
211
232
  const database = await openIndexedDB();
212
- const transaction = database.transaction([tilesStoreName], 'readonly');
213
- const store = transaction.objectStore(tilesStoreName);
233
+ const transaction = database.transaction([state.tilesStoreName], 'readonly');
234
+ const store = transaction.objectStore(state.tilesStoreName);
214
235
  const index = store.index('url');
215
236
  return new Promise((resolve, reject) => {
216
237
  const dbRequest = index.get(request.url);
217
238
  dbRequest.onsuccess = () => {
218
239
  if (dbRequest.result) {
219
240
  const blob = dbRequest.result.data;
220
- log('Tile found in Cache.');
241
+ log('Tile found in cache.');
221
242
  resolve(new Response(blob));
222
243
  }
223
244
  else {
@@ -235,11 +256,11 @@ async function loadFromIndexedDB(request) {
235
256
  }
236
257
  }
237
258
  /**
238
- * Open indexedDB
259
+ * Open IndexedDB
239
260
  */
240
261
  async function openIndexedDB() {
241
262
  return new Promise((resolve, reject) => {
242
- const request = indexedDB.open(dbCacheName, storeVersion);
263
+ const request = indexedDB.open(state.dbCacheName, state.storeVersion);
243
264
  let timedOut = false;
244
265
  const timeoutId = setTimeout(() => {
245
266
  timedOut = true;
@@ -259,8 +280,52 @@ async function openIndexedDB() {
259
280
  };
260
281
  });
261
282
  }
283
+ /**
284
+ * Open IndexedDB for the service worker state
285
+ */
286
+ async function openStateDB() {
287
+ return new Promise((resolve, reject) => {
288
+ const request = indexedDB.open(stateDbName, 1);
289
+ request.onupgradeneeded = () => {
290
+ request.result.createObjectStore(stateStoreName);
291
+ };
292
+ request.onsuccess = () => resolve(request.result);
293
+ request.onerror = () => reject(request.error);
294
+ });
295
+ }
296
+ /**
297
+ * Saves the service worker state
298
+ */
299
+ async function saveState() {
300
+ log('Save state');
301
+ const db = await openStateDB();
302
+ return new Promise((resolve, reject) => {
303
+ const tx = db.transaction(stateStoreName, 'readwrite');
304
+ tx.objectStore(stateStoreName).put(state, stateKey);
305
+ tx.oncomplete = () => resolve();
306
+ tx.onerror = () => reject(tx.error);
307
+ });
308
+ }
309
+ /**
310
+ * Reloads the service worker state from indexDB
311
+ */
312
+ async function loadState() {
313
+ log('Reload state');
314
+ const db = await openStateDB();
315
+ const loadedState = await new Promise((resolve, reject) => {
316
+ const tx = db.transaction(stateStoreName, 'readonly');
317
+ const req = tx.objectStore(stateStoreName).get(stateKey);
318
+ req.onsuccess = () => resolve(req.result);
319
+ req.onerror = () => reject(req.error);
320
+ });
321
+ if (loadedState) {
322
+ state = loadedState;
323
+ audienceExcludedPathPatterns = state.audienceExcludedPaths.map((str) => new RegExp(str));
324
+ }
325
+ initialized = true;
326
+ }
262
327
  function log(str, error) {
263
- if (logLevel === 'debug') {
328
+ if (state.logLevel === 'debug') {
264
329
  console.debug(`SW: ${str}`, error ?? '');
265
330
  }
266
331
  }
package/styles/index.css CHANGED
@@ -223,3 +223,18 @@ girafe-prototype-banner {
223
223
  font-size: 1.2rem;
224
224
  font-weight: bold;
225
225
  }
226
+
227
+ /* For showing a Popup of an Image found in Selection */
228
+ div.tippy-box[data-theme=image-popup] {
229
+ border: var(--app-standard-border) !important;
230
+ div.tippy-content {
231
+ background-color: var(--bkg-color) !important;
232
+ img {
233
+ max-height: 20rem;
234
+ max-width: 20rem;
235
+ }
236
+ }
237
+ div.tippy-arrow {
238
+ color: var(--bkg-color) !important;
239
+ }
240
+ }
@@ -1 +1 @@
1
- {"version":"1.1.0-dev.2468633458", "build":"2468633458", "date":"21/04/2026"}
1
+ {"version":"1.1.0-dev.2527788074", "build":"2527788074", "date":"15/05/2026"}
@@ -58,7 +58,15 @@ export default defineConfig(({ command, mode }) => {
58
58
  key: readFileSync(`${certsDirectory}/app.localhost.key.pem`),
59
59
  cert: readFileSync(`${certsDirectory}/app.localhost.cert.pem`)
60
60
  }
61
- : false
61
+ : false,
62
+ // To be able to see Images locally and resolve clickable Links correctly
63
+ proxy: {
64
+ '^/file_proxy/.*': {
65
+ target: "https://map.geo.bs.ch/file_proxy/",
66
+ changeOrigin: true,
67
+ rewrite: (path) => path.replace(/^\/file_proxy/, '')
68
+ }
69
+ }
62
70
  },
63
71
  build: {
64
72
  outDir: 'dist/app',
@@ -127,7 +135,7 @@ export default defineConfig(({ command, mode }) => {
127
135
  { src: 'node_modules/ol/ol.css', dest: 'lib/ol/' },
128
136
  { src: 'node_modules/tabulator-tables/dist/css/tabulator.min.css', dest: 'lib/tabulator-tables/' },
129
137
  { src: 'node_modules/tippy.js/dist/*.css', dest: 'lib/tippy.js/' },
130
- { src: 'node_modules/vanilla-picker/dist/*.css', dest: 'lib/vanilla-picker/' }
138
+ { src: 'node_modules/vanilla-picker/dist/*.css', dest: 'lib/vanilla-picker/' },
131
139
  ]
132
140
  }),
133
141
  InlineTemplatesPlugin(),
@@ -1,6 +1,7 @@
1
1
  declare class GirafeConfig {
2
2
  general: {
3
3
  locale: string;
4
+ defaultUrlPrefix: string;
4
5
  logLevel: 'debug' | 'info' | 'warn' | 'error';
5
6
  };
6
7
  languages: {
@@ -163,9 +164,6 @@ declare class GirafeConfig {
163
164
  quote: string;
164
165
  separator: string;
165
166
  };
166
- metadata: {
167
- metadataUrlPrefix: string;
168
- };
169
167
  infoWindow: {
170
168
  defaultWindowWidth: string;
171
169
  defaultWindowHeight: string;
@@ -263,7 +261,6 @@ declare class GirafeConfig {
263
261
  private initConfigLidar;
264
262
  private initConfigNews;
265
263
  private initConfigCsv;
266
- private initConfigMetadata;
267
264
  private initConfigInfoWindow;
268
265
  private initConfigContact;
269
266
  private initConfigOffline;
@@ -20,7 +20,6 @@ class GirafeConfig {
20
20
  contextmenu;
21
21
  crs;
22
22
  csv;
23
- metadata;
24
23
  infoWindow;
25
24
  offline;
26
25
  query;
@@ -56,7 +55,6 @@ class GirafeConfig {
56
55
  this.news = this.initConfigNews(config);
57
56
  this.lidar = this.initConfigLidar(config);
58
57
  this.csv = this.initConfigCsv(config);
59
- this.metadata = this.initConfigMetadata(config);
60
58
  this.infoWindow = this.initConfigInfoWindow(config);
61
59
  this.offline = this.initConfigOffline(config);
62
60
  this.query = this.initConfigQuery(config);
@@ -255,15 +253,6 @@ class GirafeConfig {
255
253
  ...config.csv
256
254
  };
257
255
  }
258
- initConfigMetadata(config) {
259
- const defaultConfig = {
260
- metadataUrlPrefix: ''
261
- };
262
- return {
263
- ...defaultConfig,
264
- ...config.metadata
265
- };
266
- }
267
256
  initConfigInfoWindow(config) {
268
257
  const defaultConfig = {
269
258
  defaultWindowWidth: '960px',
@@ -315,6 +304,7 @@ class GirafeConfig {
315
304
  initConfigGeneral(config) {
316
305
  return {
317
306
  locale: config.general.locale ?? GirafeConfig.DEFAULT_LOCALE,
307
+ defaultUrlPrefix: config.general.defaultUrlPrefix ?? '',
318
308
  // NOTE REG: Small hack specific to Vite: When running in debug mode, we force the logLevel to debug.
319
309
  // Otherwise we will always have to manually activate it.
320
310
  logLevel: import.meta?.env?.DEV ? 'debug' : (config.general.logLevel ?? 'warn')
@@ -42,8 +42,13 @@ ${stack}
42
42
  }
43
43
  listenToAllErrors() {
44
44
  // Listen to all uncatched errors
45
- window.onerror = async (_message, file, line, col, error) => {
46
- const title = error?.message ?? `Unknown error in ${file} at line ${line} and column ${col}.`;
45
+ window.onerror = async (message, file, line, col, error) => {
46
+ if (message === 'ResizeObserver loop completed with undelivered notifications.') {
47
+ // Seems to be an Issue on Tabulator Tables: https://github.com/olifolkerd/tabulator/issues?q=ResizeObserver%20loop%20completed%20with%20undelivered%20notifications
48
+ console.debug(`Ignored Error: ${message}`);
49
+ return false;
50
+ }
51
+ const title = error?.message ?? message ?? `Unknown error in ${file} at line ${line} and column ${col}.`;
47
52
  const stack = await this.getStackTrace(error);
48
53
  this.pushErrorMessageWithStack(title, stack);
49
54
  return false;
@@ -1,4 +1,5 @@
1
1
  import type OlFeature from 'ol/Feature.js';
2
+ import IGirafeContext from './context/icontext.js';
2
3
  /**
3
4
  * Represents a grid data organized by unique ids.
4
5
  */
@@ -30,8 +31,9 @@ export interface FeatureToGridDataOptions {
30
31
  * A feature-to-grid data converter, to show feature's properties in grid-like system.
31
32
  */
32
33
  export default class FeatureToGridDataById {
34
+ private readonly context;
33
35
  options: FeatureToGridDataOptions;
34
- constructor(options: FeatureToGridDataOptions);
36
+ constructor(context: IGirafeContext, options: FeatureToGridDataOptions);
35
37
  /**
36
38
  * Takes an array of OpenLayers features and converts them into a grid data object, where each feature
37
39
  * is mapped by its ID as the key.
@@ -43,6 +45,8 @@ export default class FeatureToGridDataById {
43
45
  * @private
44
46
  */
45
47
  private addFeatureToGridDataById;
48
+ private ensureAbsolutePath;
49
+ private isLikelyHtml;
46
50
  /**
47
51
  * @returns {GridData} A new empty grid data object with columns from given properties.
48
52
  * @static
@@ -4,8 +4,10 @@ import WfsManager from './wfs/wfsmanager.js';
4
4
  * A feature-to-grid data converter, to show feature's properties in grid-like system.
5
5
  */
6
6
  export default class FeatureToGridDataById {
7
+ context;
7
8
  options;
8
- constructor(options) {
9
+ constructor(context, options) {
10
+ this.context = context;
9
11
  this.options = {
10
12
  ...{
11
13
  keepGeomProperty: false,
@@ -32,11 +34,13 @@ export default class FeatureToGridDataById {
32
34
  */
33
35
  addFeatureToGridDataById(gridDataById, feature) {
34
36
  const featureType = FeatureToGridDataById.getUserFeatureType(feature);
35
- const notOlProperties = removeUnwantedOlParams(feature, this.options.keepGeomProperty);
37
+ let notOlProperties = removeUnwantedOlParams(feature, this.options.keepGeomProperty);
36
38
  if (!Object.keys(notOlProperties).length) {
37
39
  // Don't keep feature without properties.
38
40
  return gridDataById;
39
41
  }
42
+ // Apply default prefix to links and images if necessary
43
+ notOlProperties = Object.fromEntries(Object.entries(notOlProperties).map(([key, value]) => [key, this.ensureAbsolutePath(value)]));
40
44
  if (!Object.keys(gridDataById).includes(featureType)) {
41
45
  gridDataById[featureType] = FeatureToGridDataById.createGridDataWithColumns(notOlProperties);
42
46
  }
@@ -49,6 +53,19 @@ export default class FeatureToGridDataById {
49
53
  gridData.data.push(data);
50
54
  return gridDataById;
51
55
  }
56
+ ensureAbsolutePath(attributeContent) {
57
+ if (this.context.configManager.Config.general.defaultUrlPrefix && this.isLikelyHtml(attributeContent)) {
58
+ const regex = /(href|src)="\/([^"]*)"/g;
59
+ return attributeContent.replaceAll(regex, (_match, attr, path) => {
60
+ return `${attr}="${this.context.configManager.Config.general.defaultUrlPrefix}/${path}"`;
61
+ });
62
+ }
63
+ // Not an HTML string
64
+ return attributeContent;
65
+ }
66
+ isLikelyHtml(str) {
67
+ return /<[a-z][\s\S]*>/i.test(str);
68
+ }
52
69
  /**
53
70
  * @returns {GridData} A new empty grid data object with columns from given properties.
54
71
  * @static
package/tools/main.d.ts CHANGED
@@ -111,7 +111,7 @@ export { unByKeyAll, getOlayerByName, removeUnwantedOlParams, polygonFromCircle,
111
111
  export { getPropertyByPath, setPropertyByPath, createObjectFromPath, deletePropertyByPath, mergeObjects } from './utils/pathUtils.js';
112
112
  export { generateQrCode } from './utils/qrcode.js';
113
113
  export { default as ServiceWorkerHelper } from './utils/swhelper.js';
114
- export { systemIsInDarkMode, isSafari, isFirefox, getValidIndex, minMax, hexToRgbaArray, rgbStrToRgbaArray, colorToRgbaArray, isValidEmail, applyOpacityToLayers, applyFeaturesToSelection, linkify } from './utils/utils.js';
114
+ export { systemIsInDarkMode, isSafari, isFirefox, getValidIndex, minMax, hexToRgbaArray, rgbStrToRgbaArray, colorToRgbaArray, isValidEmail, applyOpacityToLayers, applyFeaturesToSelection, linkify, applyDefaultPrefixToUrl } from './utils/utils.js';
115
115
  export { default as VendorSpecificOgcServerManager } from './vendorspecificogcservermanager.js';
116
116
  export type { WfsClientOptions, WfsClientOptionalOptions, QueryableLayerWms, GetFeatureOptionsPartial } from './wfs/wfsclient.js';
117
117
  export { default as WfsClient, WfsClientMapServer, WfsClientQgis, WfsClientGeorama, WfsClientDefault, WfsClientGeoServer } from './wfs/wfsclient.js';
package/tools/main.js CHANGED
@@ -86,7 +86,7 @@ export { unByKeyAll, getOlayerByName, removeUnwantedOlParams, polygonFromCircle,
86
86
  export { getPropertyByPath, setPropertyByPath, createObjectFromPath, deletePropertyByPath, mergeObjects } from './utils/pathUtils.js';
87
87
  export { generateQrCode } from './utils/qrcode.js';
88
88
  export { default as ServiceWorkerHelper } from './utils/swhelper.js';
89
- export { systemIsInDarkMode, isSafari, isFirefox, getValidIndex, minMax, hexToRgbaArray, rgbStrToRgbaArray, colorToRgbaArray, isValidEmail, applyOpacityToLayers, applyFeaturesToSelection, linkify } from './utils/utils.js';
89
+ export { systemIsInDarkMode, isSafari, isFirefox, getValidIndex, minMax, hexToRgbaArray, rgbStrToRgbaArray, colorToRgbaArray, isValidEmail, applyOpacityToLayers, applyFeaturesToSelection, linkify, applyDefaultPrefixToUrl } from './utils/utils.js';
90
90
  export { default as VendorSpecificOgcServerManager } from './vendorspecificogcservermanager.js';
91
91
  export { default as WfsClient, WfsClientMapServer, WfsClientQgis, WfsClientGeorama, WfsClientDefault, WfsClientGeoServer } from './wfs/wfsclient.js';
92
92
  export { default as WfsFilter, wfsOperatorsStrList, isWfsOperator, mapAttributeTypeToFilterOperators } from './wfs/wfsfilter.js';
@@ -2,6 +2,7 @@ export declare const MockConfig: {
2
2
  general: {
3
3
  locale: string;
4
4
  logLevel: string;
5
+ defaultUrlPrefix: string;
5
6
  };
6
7
  languages: {
7
8
  translations: {
@@ -2,7 +2,8 @@
2
2
  export const MockConfig = {
3
3
  general: {
4
4
  locale: 'en-US',
5
- logLevel: 'debug'
5
+ logLevel: 'debug',
6
+ defaultUrlPrefix: 'https://map.geo.bs.ch'
6
7
  },
7
8
  languages: {
8
9
  translations: {
@@ -26,7 +26,6 @@ declare class ThemesManager extends GirafeSingleton {
26
26
  private applyOpacityToBasemaps;
27
27
  private applyOpacityToBasemap;
28
28
  private prepareThemes;
29
- private calculateMetadataUrl;
30
29
  /**
31
30
  * Will create layer and child layers if elem passed is a group of layers
32
31
  * @param elem either a layer or a group of layers
@@ -16,6 +16,7 @@ import BasemapSwisstopoVectorTiles from '../../models/basemaps/basemapswisstopov
16
16
  import BasemapOsm from '../../models/basemaps/basemaposm.js';
17
17
  import { DEFAULT_OPACITY, OPACITY_FOR_DEFAULT_BASEMAP } from './themes-config.js';
18
18
  import Layer from '../../models/layers/layer.js';
19
+ import { applyDefaultPrefixToUrl } from '../utils/utils.js';
19
20
  class ThemesManager extends GirafeSingleton {
20
21
  anonymousUserInfo = { u: 'anonymous' };
21
22
  get state() {
@@ -246,15 +247,6 @@ class ThemesManager extends GirafeSingleton {
246
247
  });
247
248
  return { themes, themesFunctionalities };
248
249
  }
249
- calculateMetadataUrl(metadataUrl) {
250
- if (!metadataUrl) {
251
- return undefined;
252
- }
253
- if (!metadataUrl.startsWith('http') && this.context.configManager.Config.metadata.metadataUrlPrefix) {
254
- return this.context.configManager.Config.metadata.metadataUrlPrefix + metadataUrl;
255
- }
256
- return metadataUrl;
257
- }
258
250
  /**
259
251
  * Will create layer and child layers if elem passed is a group of layers
260
252
  * @param elem either a layer or a group of layers
@@ -312,7 +304,7 @@ class ThemesManager extends GirafeSingleton {
312
304
  time: elem.time
313
305
  };
314
306
  if (options.metadataUrl) {
315
- options.metadataUrl = this.calculateMetadataUrl(options.metadataUrl);
307
+ options.metadataUrl = applyDefaultPrefixToUrl(this.context, options.metadataUrl);
316
308
  }
317
309
  const group = new GroupLayer(elem.id, elem.name, order.value, options);
318
310
  // Append children
@@ -336,7 +328,7 @@ class ThemesManager extends GirafeSingleton {
336
328
  const ogcServer = this.state.ogcServers[ogcServerName];
337
329
  const options = elem;
338
330
  if (options.metadata?.metadataUrl) {
339
- options.metadata.metadataUrl = this.calculateMetadataUrl(options.metadata.metadataUrl);
331
+ options.metadata.metadataUrl = applyDefaultPrefixToUrl(this.context, options.metadata.metadataUrl);
340
332
  }
341
333
  const wmsLayer = new LayerWms(elem.id, elem.name, order.value, ogcServer, elem);
342
334
  // For WMFS queries, the layers attribute will be used. It can be different from the name.
@@ -356,7 +348,7 @@ class ThemesManager extends GirafeSingleton {
356
348
  const ogcServer = elem.metadata?.ogcServer ? this.state.ogcServers[elem.metadata?.ogcServer] : undefined;
357
349
  const options = elem;
358
350
  if (options.metadata?.metadataUrl) {
359
- options.metadata.metadataUrl = this.calculateMetadataUrl(options.metadata.metadataUrl);
351
+ options.metadata.metadataUrl = applyDefaultPrefixToUrl(this.context, options.metadata.metadataUrl);
360
352
  }
361
353
  return new LayerWmts(elem.id, elem.name, order.value, elem.url, elem.layer, options, ogcServer);
362
354
  }
@@ -376,7 +368,7 @@ class ThemesManager extends GirafeSingleton {
376
368
  layerName: elem.metadata.layerName
377
369
  };
378
370
  if (options.metadata?.metadataUrl) {
379
- options.metadata.metadataUrl = this.calculateMetadataUrl(options.metadata.metadataUrl);
371
+ options.metadata.metadataUrl = applyDefaultPrefixToUrl(this.context, options.metadata.metadataUrl);
380
372
  }
381
373
  return new LayerVectorTiles(elem.id, elem.name, order.value, elem.style, options);
382
374
  }
@@ -2,6 +2,7 @@ import BaseLayer from '../../models/layers/baselayer.js';
2
2
  import Feature from 'ol/Feature.js';
3
3
  import { Geometry } from 'ol/geom.js';
4
4
  import State from '../state/state.js';
5
+ import IGirafeContext from '../context/icontext.js';
5
6
  /**
6
7
  * Checks if the system prefers dark mode.
7
8
  * Falls back safely if matchMedia is not supported.
@@ -68,3 +69,11 @@ export declare const applyOpacityToLayers: (opacity: number, layers: BaseLayer[]
68
69
  */
69
70
  export declare const applyFeaturesToSelection: (features: Feature<Geometry>[], state: State) => void;
70
71
  export declare const linkify: (str: string) => string;
72
+ /**
73
+ * Apply default prefix to a relative URL
74
+ * Is usefull on the demo website for metadata or some images urls
75
+ * @param context
76
+ * @param metadataUrl
77
+ * @returns
78
+ */
79
+ export declare function applyDefaultPrefixToUrl(context: IGirafeContext, metadataUrl?: string): string | undefined;
@@ -183,3 +183,19 @@ export const linkify = (str) => {
183
183
  }
184
184
  return str;
185
185
  };
186
+ /**
187
+ * Apply default prefix to a relative URL
188
+ * Is usefull on the demo website for metadata or some images urls
189
+ * @param context
190
+ * @param metadataUrl
191
+ * @returns
192
+ */
193
+ export function applyDefaultPrefixToUrl(context, metadataUrl) {
194
+ if (!metadataUrl) {
195
+ return undefined;
196
+ }
197
+ if (!metadataUrl.startsWith('http') && context.configManager.Config.general.defaultUrlPrefix) {
198
+ return `${context.configManager.Config.general.defaultUrlPrefix}${metadataUrl}`;
199
+ }
200
+ return metadataUrl;
201
+ }