@geogirafe/lib-geoportal 1.3.0-dev.2922702126 → 1.3.0-dev.2926144279

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.
Files changed (52) hide show
  1. package/api/apigeogirafeapp.js +3 -1
  2. package/assets/i18n/de.json +4 -0
  3. package/assets/i18n/en.json +4 -0
  4. package/assets/i18n/fr.json +4 -0
  5. package/assets/i18n/it.json +4 -0
  6. package/assets/icons/layout-1+2.svg +1 -0
  7. package/assets/icons/layout-2+1.svg +1 -0
  8. package/assets/icons/streetview-marker.svg +1 -0
  9. package/components/layout/component.d.ts +8 -3
  10. package/components/layout/component.js +15 -9
  11. package/components/main.d.ts +1 -0
  12. package/components/main.js +1 -0
  13. package/components/map-grid/component.d.ts +19 -5
  14. package/components/map-grid/component.js +58 -29
  15. package/components/map-grid/style.css +10 -8
  16. package/components/share/tools/geogirafesharemanager.js +5 -2
  17. package/components/streetview/component.d.ts +59 -0
  18. package/components/streetview/component.js +193 -0
  19. package/components/streetview/google.d.ts +7 -0
  20. package/components/streetview/google.js +16 -0
  21. package/components/streetview/style.css +41 -0
  22. package/components/streetview/tools/googleprovider.d.ts +26 -0
  23. package/components/streetview/tools/googleprovider.js +102 -0
  24. package/components/streetview/tools/provider.d.ts +27 -0
  25. package/components/streetview/tools/provider.js +1 -0
  26. package/package.json +2 -1
  27. package/templates/iframe.html +1 -0
  28. package/templates/index.html +1 -0
  29. package/templates/mobile.html +1 -0
  30. package/templates/public/about.json +1 -1
  31. package/tools/app/geogirafeapp-mobile.js +2 -0
  32. package/tools/app/geogirafeapp.js +2 -0
  33. package/tools/auth/abstractconnectmanager.js +6 -0
  34. package/tools/configuration/girafeconfig.d.ts +2 -0
  35. package/tools/configuration/girafeconfig.js +10 -0
  36. package/tools/configuration/streetviewconfig.d.ts +13 -0
  37. package/tools/configuration/streetviewconfig.js +18 -0
  38. package/tools/main.d.ts +8 -2
  39. package/tools/main.js +5 -1
  40. package/tools/share/iurlshortener.d.ts +1 -0
  41. package/tools/share/iurlshortener.js +2 -1
  42. package/tools/share/serializers/layoutserializer.js +3 -2
  43. package/tools/share/serializers/streetviewserializer.d.ts +18 -0
  44. package/tools/share/serializers/streetviewserializer.js +29 -0
  45. package/tools/share/sharemanager.js +9 -0
  46. package/tools/share/stateserializer.js +3 -0
  47. package/tools/state/layout.d.ts +37 -7
  48. package/tools/state/layout.js +55 -12
  49. package/tools/state/state.d.ts +2 -0
  50. package/tools/state/state.js +6 -0
  51. package/tools/state/streetview.d.ts +22 -0
  52. package/tools/state/streetview.js +24 -0
@@ -4,6 +4,7 @@ import proj4 from 'proj4';
4
4
  import GirafeHTMLElement from '../base/GirafeHTMLElement.js';
5
5
  import Map2dComponent from '../components/map-2d/component.js';
6
6
  import Map3dComponent from '../components/map-3d/component.js';
7
+ import GoogleStreetViewComponent from '../components/streetview/google.js';
7
8
  import MapGridComponent from '../components/map-grid/component.js';
8
9
  import { register } from 'ol/proj/proj4.js';
9
10
  import GirafeApiContext from './apicontext.js';
@@ -39,7 +40,7 @@ export default class GeoGirafeApi extends GirafeHTMLElement {
39
40
  :host{position:relative}girafe-map-grid,girafe-map-2d,girafe-map-3d{width:100%;height:100%;display:block;position:relative;overflow:hidden}girafe-basemap{display:block;position:absolute;bottom:.5rem;left:.5rem}girafe-search{width:50%;height:2.25rem;display:block;position:absolute;top:.5rem;left:.5rem;box-shadow:0 1px 4px #0000004d}girafe-selection-window{z-index:100;width:400px;height:240px;position:absolute;bottom:1rem;right:1rem}
40
41
  </style>
41
42
  <style>${this.customStyle}</style>
42
- <girafe-map-grid><girafe-map-2d></girafe-map-2d><girafe-map-3d></girafe-map-3d></girafe-map-grid>
43
+ <girafe-map-grid><girafe-map-2d></girafe-map-2d><girafe-map-3d></girafe-map-3d><girafe-streetview-google></girafe-streetview-google></girafe-map-grid>
43
44
  ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
44
45
  };
45
46
  isInitialized = false;
@@ -108,6 +109,7 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
108
109
  if (!customElements.get('girafe-map-2d')) {
109
110
  customElements.define('girafe-map-2d', Map2dComponent);
110
111
  customElements.define('girafe-map-3d', Map3dComponent);
112
+ customElements.define('girafe-streetview-google', GoogleStreetViewComponent);
111
113
  customElements.define('girafe-map-grid', MapGridComponent);
112
114
  }
113
115
  }
@@ -52,6 +52,7 @@
52
52
  "Classes": "Klassen",
53
53
  "Classification": "Klassifikation",
54
54
  "Clear results and close Panel": "Ergebnisse löschen und Panel schliessen",
55
+ "Click on the map to display the StreetView at this location": "Klicken Sie auf die Karte, um die StreetView an dieser Stelle anzuzeigen",
55
56
  "Close message": "Nachricht schliessen",
56
57
  "color_option_classification": "Klassifizierung",
57
58
  "color_option_intensity": "Intensität",
@@ -312,6 +313,7 @@
312
313
  "Loading cesium...": "Cesium wird geladen...",
313
314
  "Loading LOD": "LOD wird geladen",
314
315
  "Loading server configuration...": "Serverkonfiguration wird geladen...",
316
+ "Loading StreetView...": "StreetView wird geladen...",
315
317
  "local": "Lokal",
316
318
  "local file": "Lokale Datei",
317
319
  "Local Files": "Lokale Dateien",
@@ -351,6 +353,7 @@
351
353
  "No graph to export in PNG!": "Kein Diagramm zum Exportieren als PNG!",
352
354
  "No layer available": "Kein Layer verfügbar",
353
355
  "No match found": "Keine Ergebnisse gefunden",
356
+ "No StreetView footage was found near this location": "In der Nähe dieser Stelle wurden keine StreetView-Aufnahmen gefunden",
354
357
  "Nothing here yet!": "Hier ist noch nichts!",
355
358
  "Offline data": "Offline-Daten",
356
359
  "Offline mode": "Offline-Modus",
@@ -532,6 +535,7 @@
532
535
  "Source Type": "Typ der Quelle",
533
536
  "Square": "Quadrat",
534
537
  "stop_video": "Video stoppen",
538
+ "StreetView": "StreetView",
535
539
  "Stroke color": "Strichfarbe",
536
540
  "Suggestions": "Vorschläge",
537
541
  "SVG": "SVG",
@@ -52,6 +52,7 @@
52
52
  "Classes": "Classes",
53
53
  "Classification": "Classification",
54
54
  "Clear results and close Panel": "Clear results and close Panel",
55
+ "Click on the map to display the StreetView at this location": "Click on the map to display the StreetView at this location",
55
56
  "Close message": "Close message",
56
57
  "color_option_classification": "Classification",
57
58
  "color_option_intensity": "Intensity",
@@ -314,6 +315,7 @@
314
315
  "Loading cesium...": "Loading cesium...",
315
316
  "Loading LOD": "Loading level of detail",
316
317
  "Loading server configuration...": "Loading server configuration...",
318
+ "Loading StreetView...": "Loading StreetView...",
317
319
  "local": "Local",
318
320
  "local file": "Local File",
319
321
  "Local Files": "Local Files",
@@ -353,6 +355,7 @@
353
355
  "No graph to export in PNG!": "No graph to export in PNG!",
354
356
  "No layer available": "No layer available",
355
357
  "No match found": "No match found",
358
+ "No StreetView footage was found near this location": "No StreetView footage was found near this location",
356
359
  "Nothing here yet!": "Nothing here yet!",
357
360
  "Offline data": "Offline data",
358
361
  "Offline mode": "Offline mode",
@@ -535,6 +538,7 @@
535
538
  "Source Type": "Source Type",
536
539
  "Square": "Square",
537
540
  "stop_video": "Stop video",
541
+ "StreetView": "StreetView",
538
542
  "Stroke color": "Stroke color",
539
543
  "Suggestions": "Suggestions",
540
544
  "SVG": "SVG",
@@ -52,6 +52,7 @@
52
52
  "Classes": "Classes",
53
53
  "Classification": "Classification",
54
54
  "Clear results and close Panel": "Désélectionner et fermer le panneau",
55
+ "Click on the map to display the StreetView at this location": "Cliquez sur la carte pour afficher la StreetView à cet endroit",
55
56
  "Close message": "Fermer le message",
56
57
  "color_option_classification": "Classification",
57
58
  "color_option_intensity": "Intensité",
@@ -312,6 +313,7 @@
312
313
  "Loading cesium...": "Chargement de cesium...",
313
314
  "Loading LOD": "Chargement des niveaux de détail",
314
315
  "Loading server configuration...": "Chargement de la configuration du serveur...",
316
+ "Loading StreetView...": "Chargement de StreetView...",
315
317
  "local": "Locale",
316
318
  "local file": "Fichier local",
317
319
  "Local Files": "Fichiers locaux",
@@ -351,6 +353,7 @@
351
353
  "No graph to export in PNG!": "Aucun graphique à exporter en PNG !",
352
354
  "No layer available": "Aucune couche disponible",
353
355
  "No match found": "Aucun résultat correspondant",
356
+ "No StreetView footage was found near this location": "Aucune image StreetView n'a été trouvée à proximité de cet endroit",
354
357
  "Nothing here yet!": "Rien ici pour le moment !",
355
358
  "Offline data": "Données hors-ligne",
356
359
  "Offline mode": "Mode hors-ligne",
@@ -532,6 +535,7 @@
532
535
  "Source Type": "Type de la source",
533
536
  "Square": "Carré",
534
537
  "stop_video": "Arrêter la vidéo",
538
+ "StreetView": "StreetView",
535
539
  "Stroke color": "Couleur du trait",
536
540
  "Suggestions": "Suggestions",
537
541
  "SVG": "SVG",
@@ -52,6 +52,7 @@
52
52
  "Classes": "Classi",
53
53
  "Classification": "Classificazione",
54
54
  "Clear results and close Panel": "Cancella selezione e chiudi pannello",
55
+ "Click on the map to display the StreetView at this location": "Clicca sulla mappa per visualizzare la StreetView in questo punto",
55
56
  "Close message": "Chiudi messaggio",
56
57
  "color_option_classification": "Classificazione",
57
58
  "color_option_intensity": "Intensità",
@@ -312,6 +313,7 @@
312
313
  "Loading cesium...": "Caricamento cesium...",
313
314
  "Loading LOD": "Caricamento dei livelli di dettaglio",
314
315
  "Loading server configuration...": "Caricamento configurazione server...",
316
+ "Loading StreetView...": "Caricamento di StreetView...",
315
317
  "local": "Locale",
316
318
  "local file": "File locale",
317
319
  "Local Files": "File locali",
@@ -351,6 +353,7 @@
351
353
  "No graph to export in PNG!": "Nessun grafico da esportare in PNG!",
352
354
  "No layer available": "Nessun layer disponibile",
353
355
  "No match found": "Nessun risultato corrispondente",
356
+ "No StreetView footage was found near this location": "Nessuna immagine StreetView è stata trovata nelle vicinanze di questo punto",
354
357
  "Nothing here yet!": "Ancora nessun contenuto!",
355
358
  "Offline data": "Dati offline",
356
359
  "Offline mode": "Modalità offline",
@@ -532,6 +535,7 @@
532
535
  "Source Type": "Tipo di layer",
533
536
  "Square": "Quadrato",
534
537
  "stop_video": "Interrompere il video",
538
+ "StreetView": "StreetView",
535
539
  "Stroke color": "Colore della linea",
536
540
  "Suggestions": "Suggerimenti",
537
541
  "SVG": "SVG",
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><g fill="none" stroke="#000" stroke-width="3"><path d="M3.5 3.5h32v32h-32zM3.5 19.5h32M19.5 19.5v16"/></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><g fill="none" stroke="#000" stroke-width="3"><path d="M3.5 3.5h32v32h-32zM19.5 3.5v16M3.5 19.5h32"/></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path fill="#1a73e8" fill-opacity=".35" d="M24 24 10 4a24 24 0 0 1 28 0Z"/><circle cx="24" cy="24" r="6" fill="#1a73e8" stroke="#fff" stroke-width="2"/></svg>
@@ -9,15 +9,20 @@ declare class LayoutComponent extends GirafeHTMLElement implements IGirafePanel
9
9
  panelTitle: string;
10
10
  panelTogglePath: string;
11
11
  /** The Components the user can choose between for each Cell of the Grid. */
12
- availableComponents: readonly [{
12
+ get availableComponents(): ({
13
13
  readonly id: "map-2d";
14
14
  readonly tagName: "girafe-map-2d";
15
15
  readonly label: "2D Map";
16
- }, {
16
+ } | {
17
17
  readonly id: "map-3d";
18
18
  readonly tagName: "girafe-map-3d";
19
19
  readonly label: "3D Map";
20
- }];
20
+ } | {
21
+ readonly id: "streetview-google";
22
+ readonly tagName: "girafe-streetview-google";
23
+ readonly label: "Google Street View";
24
+ readonly provider: "google";
25
+ })[];
21
26
  constructor();
22
27
  private get layout();
23
28
  /** The Layouts the user can choose between, as configured in the application. */
@@ -1,12 +1,14 @@
1
1
  import { html as uHtml } from 'uhtml';
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  import GirafeHTMLElement from '../../base/GirafeHTMLElement.js';
4
- import { AVAILABLE_CELL_COMPONENTS, distributeCells, getDefaultSizes, getGridSize, isCellComponent } from '../../tools/state/layout.js';
4
+ import { AVAILABLE_CELL_COMPONENTS, distributeCells, getDefaultSizes, getLayoutCells, isCellComponent, isCellComponentAvailable } from '../../tools/state/layout.js';
5
5
  const LAYOUT_ICONS = {
6
6
  '1x1': 'icons/layout-1x1.svg',
7
7
  '2x1': 'icons/layout-2x1.svg',
8
8
  '1x2': 'icons/layout-1x2.svg',
9
- '2x2': 'icons/layout-2x2.svg'
9
+ '2x2': 'icons/layout-2x2.svg',
10
+ '2+1': 'icons/layout-2+1.svg',
11
+ '1+2': 'icons/layout-1+2.svg'
10
12
  };
11
13
  class LayoutComponent extends GirafeHTMLElement {
12
14
  templateUrl = null;
@@ -25,7 +27,10 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
25
27
  panelTitle = 'layout-panel';
26
28
  panelTogglePath = 'interface.layoutPanelVisible';
27
29
  /** The Components the user can choose between for each Cell of the Grid. */
28
- availableComponents = AVAILABLE_CELL_COMPONENTS;
30
+ get availableComponents() {
31
+ const streetviewConfig = this.context.configManager.Config.streetview;
32
+ return AVAILABLE_CELL_COMPONENTS.filter((component) => isCellComponentAvailable(component.id, streetviewConfig));
33
+ }
29
34
  constructor() {
30
35
  super('layout');
31
36
  this.onShadowsToggle = this.onShadowsToggle.bind(this);
@@ -43,12 +48,13 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
43
48
  }
44
49
  /** The Cells of the current Layout, one line of the schema per row of the Grid. */
45
50
  get cellRows() {
46
- const { columns, rows } = getGridSize(this.layout.type);
47
- const cells = this.layout.cells;
48
- return Array.from({ length: rows }, (_, row) => Array.from({ length: columns }, (_, column) => {
49
- const index = row * columns + column;
50
- return { index: index, content: cells[index] ?? null };
51
- }));
51
+ const contents = this.layout.cells;
52
+ const rows = [];
53
+ for (const [index, cell] of getLayoutCells(this.layout.type).entries()) {
54
+ rows[cell.row] ??= [];
55
+ rows[cell.row].push({ index: index, content: contents[index] ?? null });
56
+ }
57
+ return rows;
52
58
  }
53
59
  registerEvents() {
54
60
  this.subscribe('layout.type', () => this.onLayoutChanged());
@@ -55,6 +55,7 @@ export { default as SelectionToolComponent } from './selectiontool/component.js'
55
55
  export { default as SelectionWindowComponent } from './selectionwindow/component.js';
56
56
  export { default as ShareComponent } from './share/component.js';
57
57
  export { default as ShareMobileComponent } from './share-mobile/component.js';
58
+ export { default as StreetViewComponent } from './streetview/component.js';
58
59
  export { default as SwipeUpPanelMobile } from './swipe-up-panel-mobile/component.js';
59
60
  export { default as ThemeComponent } from './themes/component.js';
60
61
  export { default as MobileGroupElementComponent } from './themes-mobile/group/component.js';
@@ -53,6 +53,7 @@ export { default as SelectionToolComponent } from './selectiontool/component.js'
53
53
  export { default as SelectionWindowComponent } from './selectionwindow/component.js';
54
54
  export { default as ShareComponent } from './share/component.js';
55
55
  export { default as ShareMobileComponent } from './share-mobile/component.js';
56
+ export { default as StreetViewComponent } from './streetview/component.js';
56
57
  export { default as SwipeUpPanelMobile } from './swipe-up-panel-mobile/component.js';
57
58
  export { default as ThemeComponent } from './themes/component.js';
58
59
  export { default as MobileGroupElementComponent } from './themes-mobile/group/component.js';
@@ -1,4 +1,5 @@
1
1
  import GirafeHTMLElement from '../../base/GirafeHTMLElement.js';
2
+ import { LayoutCell } from '../../tools/state/layout.js';
2
3
  /** A splitter, placed on the boundary between two columns or two rows of the Grid. */
3
4
  type Splitter = {
4
5
  /** Whether the splitter is dragged left/right (vertical) or up/down (horizontal). */
@@ -7,6 +8,15 @@ type Splitter = {
7
8
  index: number;
8
9
  /** Position of the splitter, in percent of the Grid. */
9
10
  position: number;
11
+ /**
12
+ * Index of the first and of the last row (vertical) or column (horizontal) the splitter goes along:
13
+ * a splitter only goes along the Cells that share its boundary.
14
+ */
15
+ from: number;
16
+ to: number;
17
+ /** Start and end of the splitter along its boundary, in percent of the Grid. */
18
+ start: number;
19
+ end: number;
10
20
  };
11
21
  /**
12
22
  * The Grid in which the Map Components are displayed.
@@ -19,8 +29,8 @@ declare class MapGridComponent extends GirafeHTMLElement {
19
29
  protected templateUrl: string | null;
20
30
  protected styleUrls: string[] | null;
21
31
  template: () => import("uhtml").Hole;
22
- /** Index of each Cell of the current Layout, used to render the Cells and their slots. */
23
- cellIndexes: number[];
32
+ /** The Cells of the current Layout, used to render them and their slots. */
33
+ cells: LayoutCell[];
24
34
  /** The splitters between the Cells of the current Layout. */
25
35
  splitters: Splitter[];
26
36
  /** The splitter that is currently dragged, if any. */
@@ -37,18 +47,22 @@ declare class MapGridComponent extends GirafeHTMLElement {
37
47
  private applyLayout;
38
48
  /**
39
49
  * Creates one splitter for each boundary between two columns and between two rows.
40
- * The splitters go through the whole Grid, so that the columns and the rows keep
41
- * the same sizes in every Cell of the Grid.
50
+ * The columns and the rows keep the same sizes in every Cell of the Grid: a vertical splitter
51
+ * goes along all the rows that have a Cell ending on its boundary, and a horizontal splitter
52
+ * goes along all the columns.
42
53
  */
43
54
  private createSplitters;
55
+ getCellArea(cell: LayoutCell): string;
44
56
  /**
45
57
  * Moves each Component into the Cell it is configured for, by setting the name of the slot
46
58
  * it must be displayed in. Components that are not placed in any Cell get no slot,
47
59
  * which means that they are not displayed at all.
48
60
  */
49
61
  private applyCells;
50
- /** Applies the sizes of the columns and of the rows to the Grid and to the splitters. */
51
62
  private applySizes;
63
+ /** Applies the sizes of the columns and of the rows to the Grid and to the splitters. */
64
+ private applyGridSizes;
65
+ private sumOf;
52
66
  private applySplitterPosition;
53
67
  getSplitterId(splitter: Splitter): string;
54
68
  onSplitterPointerDown(event: PointerEvent, splitter: Splitter): void;
@@ -1,7 +1,7 @@
1
1
  import { html as uHtml } from 'uhtml';
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  import GirafeHTMLElement from '../../base/GirafeHTMLElement.js';
4
- import { AVAILABLE_CELL_COMPONENTS, getGridSize, MAX_CELL_SIZE, MIN_CELL_SIZE } from '../../tools/state/layout.js';
4
+ import { AVAILABLE_CELL_COMPONENTS, getGridSize, getLayoutCells, MAX_CELL_SIZE, MIN_CELL_SIZE } from '../../tools/state/layout.js';
5
5
  /**
6
6
  * The Grid in which the Map Components are displayed.
7
7
  *
@@ -16,14 +16,14 @@ class MapGridComponent extends GirafeHTMLElement {
16
16
  return uHtml `<style>
17
17
  .hidden{display:none!important}.gg-rotate90{transform:rotate(90deg)}.gg-rotate180{transform:rotate(180deg)}.gg-rotate270{transform:rotate(270deg)}img{filter:var(--svg-filter)}img.legend-image{filter:var(--svg-map-filter);background:var(--svg-legend-bkg)}div{scrollbar-width:thin}a,a:visited{color:var(--link-color)}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes spin-wait{0%{transform:rotate(0)}7%{transform:rotate(360deg)}to{transform:rotate(360deg)}}.gg-spin{animation-name:spin;animation-duration:2s;animation-timing-function:linear;animation-iteration-count:infinite}.gg-spin-wait{animation-name:spin-wait;animation-duration:10s;animation-timing-function:linear;animation-iteration-count:infinite}::-webkit-scrollbar{width:5px}::-webkit-scrollbar-thumb{background:#999}button,input,select,textarea{font:inherit}.gg-button,.gg-select,.gg-input,.gg-textarea{background-color:var(--bkg-color);color:var(--text-color);border:var(--app-standard-border);box-sizing:border-box;cursor:pointer;border-radius:3px;outline:0;margin:0;padding:0 0 0 .5rem;display:inline-block}.gg-label{background-color:var(--bkg-color);color:var(--text-color);border:none;align-items:center;margin:0;padding:0;display:flex}.gg-button,.gg-select,.gg-input,.gg-label{min-height:calc(var(--app-standard-height) / 1.5)}.gg-textarea{max-height:initial;resize:vertical;height:6rem;padding:.5rem;line-height:1.3rem}.gg-input{cursor:text}.gg-checkbox{accent-color:var(--text-color);cursor:pointer;width:1.2rem}.gg-range{accent-color:var(--text-color)}.gg-button{padding:0 .5rem}.gg-button.active{border:solid 1px var(--text-color-grad1);background-color:var(--bkg-color-grad2)}.gg-button:disabled{background-color:var(--bkg-color-grad1);color:var(--text-color-grad2);cursor:not-allowed;border:none}.gg-button:disabled img{filter:opacity(.6)}.gg-input:disabled,.gg-select:disabled,.gg-textarea:disabled{background-color:var(--bkg-color-grad1);color:var(--text-color-grad2);cursor:not-allowed}.gg-button>img{vertical-align:middle}.gg-icon-button{color:var(--text-color);cursor:pointer;background-color:#0000;border:none;flex-direction:column;justify-content:center;align-items:center;padding:0;display:flex}.gg-icon{justify-content:center;align-items:center;display:flex}.gg-big,.gg-big-withtext{min-width:var(--app-standard-height);min-height:var(--app-standard-height);max-height:var(--app-standard-height)}.gg-big img,.gg-big-withtext img{width:calc(var(--app-standard-height) - 1.5rem);margin:0}.gg-big-withtext span{font-variant:small-caps;padding:0 1rem;font-size:.9rem}.gg-medium,.gg-medium-withtext{min-width:calc(var(--app-standard-height) / 1.2);min-height:calc(var(--app-standard-height) / 1.2);max-height:calc(var(--app-standard-height) / 1.2);flex-direction:row}.gg-medium img{width:calc(var(--app-standard-height) / 2.4);margin:0}.gg-medium-withtext img{width:calc(var(--app-standard-height) / 2.4);margin-left:.5rem}.gg-medium-withtext span{padding:0 1rem 0 .5rem;font-size:.9rem}.gg-small,.gg-small-withtext{min-width:calc(var(--app-standard-height) / 2);min-height:calc(var(--app-standard-height) / 2);max-height:calc(var(--app-standard-height) / 2);flex-direction:row}.gg-small img{width:calc(var(--app-standard-height) / 3);margin:0}.gg-small-withtext img{width:calc(var(--app-standard-height) / 3);margin-left:.5rem}.gg-small-withtext span{padding:0 .5rem 0 .3rem;font-size:.9rem}.gg-button:hover:not(:disabled),.gg-select:hover:not(:disabled),.gg-input:hover:not(:disabled),.gg-textarea:hover:not(:disabled),.gg-icon-button:hover:not(:disabled){background-color:var(--bkg-color-grad1)}.gg-opacity{opacity:.5}.gg-opacity:hover{opacity:1;background-color:#0000}.gg-tabs{cursor:pointer;grid-auto-flow:column;padding-bottom:1rem;font-size:1rem;display:grid}.gg-tab{border:none;border-bottom:var(--app-standard-border);cursor:pointer;color:var(--text-color);background:0 0;padding:.5rem}.gg-tab.active{border-bottom:solid 1px var(--text-color)}.girafe-button-big,.girafe-button-large,.girafe-button-small,.girafe-button-tiny{color:var(--text-color);background-color:#0000;border:none;flex-direction:column;display:flex}.girafe-button-big:hover,.girafe-button-large:hover,.girafe-button-small:hover,.girafe-button-tiny:hover{background-color:var(--bkg-color-grad1);cursor:pointer}.girafe-button-big.dark,.girafe-button-large.dark,.girafe-button-small.dark,.girafe-button-tiny.dark{background-color:var(--bkg-color);filter:invert()}.girafe-button-big{width:var(--app-standard-height);height:var(--app-standard-height);align-items:center;padding:1rem}.girafe-button-big img{overflow:hidden}.girafe-button-large{flex-direction:row}.girafe-button-large img{height:2rem;margin:.3rem}.girafe-button-large span{height:2rem;margin:.3rem;line-height:2rem}.girafe-button-small{min-width:calc(var(--app-standard-height) / 2);height:calc(var(--app-standard-height) / 2);align-items:center;padding:.5rem}.girafe-button-small img{overflow:hidden}.girafe-button-small span{text-align:left;text-overflow:ellipsis;width:100%;overflow:hidden}.girafe-button-tiny{align-items:center;width:1rem;height:1rem;padding:0}.girafe-button-tiny img{overflow:hidden}.girafe-onboarding-theme{background-color:var(--bkg-color)!important;color:var(--text-color)!important}.girafe-onboarding-theme button{background-color:var(--bkg-color)!important;color:var(--text-color)!important;text-shadow:none!important}.girafe-onboarding-theme button.driver-popover-close-btn{z-index:10000}.tippy-box{background-color:var(--bkg-color)!important;border:var(--app-standard-border)!important}.tippy-content{color:var(--text-color)!important}.tippy-arrow{color:var(--bkg-color)!important}.tippy-box[data-theme~=error]{background-color:var(--error-color);white-space:pre-line}div.tippy-box[data-theme=image-popup]{border:var(--app-standard-border)!important;& div.tippy-content{background-color:var(--bkg-color)!important;& img{max-width:20rem;max-height:20rem}}& div.tippy-arrow{color:var(--bkg-color)!important}}
18
18
  </style><style>
19
- #grid{background-color:var(--bkg-color);width:100%;height:100%;display:grid;position:relative;overflow:hidden}.cell{min-width:0;min-height:0;position:relative;overflow:hidden}::slotted(*){width:100%;height:100%;display:block}.splitter{z-index:100;background:var(--bkg-color);border:var(--app-standard-border);touch-action:none;border-radius:1rem;position:absolute}.splitter-vertical{cursor:col-resize;width:6px;top:2rem;bottom:2rem;transform:translate(-50%)}.splitter-horizontal{cursor:row-resize;height:6px;left:2rem;right:2rem;transform:translateY(-50%)}.splitter:before,.splitter:after{content:"";background:var(--app-color-grey);position:absolute}.splitter-vertical:before,.splitter-vertical:after{width:1px;height:2rem;left:50%;transform:translate(-50%)}.splitter-vertical:before{height:calc(2rem + 1px);top:calc(-2rem - 1px)}.splitter-vertical:after{height:calc(2rem + 1px);bottom:calc(-2rem - 1px)}.splitter-horizontal:before,.splitter-horizontal:after{width:2rem;height:1px;top:50%;transform:translateY(-50%)}.splitter-horizontal:before{width:calc(2rem + 1px);left:calc(-2rem - 1px)}.splitter-horizontal:after{width:calc(2rem + 1px);right:calc(-2rem - 1px)}
19
+ #grid{background-color:var(--bkg-color);width:100%;height:100%;display:grid;position:relative;overflow:hidden}.cell{min-width:0;min-height:0;position:relative;overflow:hidden}::slotted(*){width:100%;height:100%;display:block}.splitter{z-index:100;background:var(--bkg-color);border:var(--app-standard-border);touch-action:none;border-radius:1rem;position:absolute}.splitter-vertical{top:calc(var(--start) + 2rem);bottom:calc(var(--end) + 2rem);cursor:col-resize;width:6px;transform:translate(-50%)}.splitter-horizontal{left:calc(var(--start) + 2rem);right:calc(var(--end) + 2rem);cursor:row-resize;height:6px;transform:translateY(-50%)}.splitter:before,.splitter:after{content:"";background:var(--app-color-grey);position:absolute}.splitter-vertical:before,.splitter-vertical:after{width:1px;height:2rem;left:50%;transform:translate(-50%)}.splitter-vertical:before{height:calc(2rem + 1px);top:calc(-2rem - 1px)}.splitter-vertical:after{height:calc(2rem + 1px);bottom:calc(-2rem - 1px)}.splitter-horizontal:before,.splitter-horizontal:after{width:2rem;height:1px;top:50%;transform:translateY(-50%)}.splitter-horizontal:before{width:calc(2rem + 1px);left:calc(-2rem - 1px)}.splitter-horizontal:after{width:calc(2rem + 1px);right:calc(-2rem - 1px)}
20
20
  </style>
21
21
  <style>${this.customStyle}</style>
22
- <div id="grid">${this.cellIndexes.map((index) => uHtml `<div class="cell"><slot name="${'cell-' + index}"></slot></div>`)} ${this.splitters.map((splitter) => uHtml `<div id="${this.getSplitterId(splitter)}" class="${'splitter splitter-' + splitter.orientation}" role="separator" onpointerdown="${(e) => this.onSplitterPointerDown(e, splitter)}" onpointermove="${(e) => this.onSplitterPointerMove(e, splitter)}" onpointerup="${(e) => this.onSplitterPointerUp(e, splitter)}"></div>`)}</div>
22
+ <div id="grid">${this.cells.map((cell, index) => uHtml `<div class="cell" style="${this.getCellArea(cell)}"><slot name="${'cell-' + index}"></slot></div>`)} ${this.splitters.map((splitter) => uHtml `<div id="${this.getSplitterId(splitter)}" class="${'splitter splitter-' + splitter.orientation}" role="separator" onpointerdown="${(e) => this.onSplitterPointerDown(e, splitter)}" onpointermove="${(e) => this.onSplitterPointerMove(e, splitter)}" onpointerup="${(e) => this.onSplitterPointerUp(e, splitter)}"></div>`)}</div>
23
23
  ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
24
24
  };
25
- /** Index of each Cell of the current Layout, used to render the Cells and their slots. */
26
- cellIndexes = [];
25
+ /** The Cells of the current Layout, used to render them and their slots. */
26
+ cells = [];
27
27
  /** The splitters between the Cells of the current Layout. */
28
28
  splitters = [];
29
29
  /** The splitter that is currently dragged, if any. */
@@ -54,28 +54,53 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
54
54
  * then places the Components and applies the sizes.
55
55
  */
56
56
  applyLayout() {
57
- const { columns, rows } = getGridSize(this.layout.type);
58
- this.cellIndexes = Array.from({ length: columns * rows }, (_, index) => index);
59
- this.splitters = this.createSplitters(columns, rows);
57
+ const type = this.layout.type;
58
+ this.cells = getLayoutCells(type);
59
+ this.splitters = this.createSplitters(type);
60
60
  this.refreshRender();
61
61
  this.applyCells();
62
62
  this.applySizes();
63
63
  }
64
64
  /**
65
65
  * Creates one splitter for each boundary between two columns and between two rows.
66
- * The splitters go through the whole Grid, so that the columns and the rows keep
67
- * the same sizes in every Cell of the Grid.
66
+ * The columns and the rows keep the same sizes in every Cell of the Grid: a vertical splitter
67
+ * goes along all the rows that have a Cell ending on its boundary, and a horizontal splitter
68
+ * goes along all the columns.
68
69
  */
69
- createSplitters(columns, rows) {
70
+ createSplitters(type) {
71
+ const { columns, rows } = getGridSize(type);
72
+ const cells = getLayoutCells(type);
70
73
  const splitters = [];
71
74
  for (let index = 0; index < columns - 1; index++) {
72
- splitters.push({ orientation: 'vertical', index: index, position: 0 });
75
+ const rowsAtBoundary = cells
76
+ .filter((cell) => cell.column + cell.columnSpan === index + 1)
77
+ .map((cell) => cell.row);
78
+ splitters.push({
79
+ orientation: 'vertical',
80
+ index: index,
81
+ position: 0,
82
+ from: Math.min(...rowsAtBoundary),
83
+ to: Math.max(...rowsAtBoundary),
84
+ start: 0,
85
+ end: 0
86
+ });
73
87
  }
74
88
  for (let index = 0; index < rows - 1; index++) {
75
- splitters.push({ orientation: 'horizontal', index: index, position: 0 });
89
+ splitters.push({
90
+ orientation: 'horizontal',
91
+ index: index,
92
+ position: 0,
93
+ from: 0,
94
+ to: columns - 1,
95
+ start: 0,
96
+ end: 0
97
+ });
76
98
  }
77
99
  return splitters;
78
100
  }
101
+ getCellArea(cell) {
102
+ return `grid-area: ${cell.row + 1} / ${cell.column + 1} / span 1 / span ${cell.columnSpan}`;
103
+ }
79
104
  /**
80
105
  * Moves each Component into the Cell it is configured for, by setting the name of the slot
81
106
  * it must be displayed in. Components that are not placed in any Cell get no slot,
@@ -97,21 +122,30 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
97
122
  }
98
123
  }
99
124
  }
100
- /** Applies the sizes of the columns and of the rows to the Grid and to the splitters. */
101
125
  applySizes() {
102
- const grid = this.grid;
103
- if (!grid) {
126
+ if (!this.grid) {
104
127
  return;
105
128
  }
106
129
  const { columns, rows } = this.layout.sizes;
107
- grid.style.gridTemplateColumns = columns.map((size) => `${size}%`).join(' ');
108
- grid.style.gridTemplateRows = rows.map((size) => `${size}%`).join(' ');
130
+ this.applyGridSizes(columns, rows);
131
+ }
132
+ /** Applies the sizes of the columns and of the rows to the Grid and to the splitters. */
133
+ applyGridSizes(columns, rows) {
134
+ this.grid.style.gridTemplateColumns = columns.map((size) => `${size}%`).join(' ');
135
+ this.grid.style.gridTemplateRows = rows.map((size) => `${size}%`).join(' ');
109
136
  for (const splitter of this.splitters) {
110
- const sizes = splitter.orientation === 'vertical' ? columns : rows;
111
- splitter.position = sizes.slice(0, splitter.index + 1).reduce((sum, size) => sum + size, 0);
137
+ const isVertical = splitter.orientation === 'vertical';
138
+ const sizesAcross = isVertical ? columns : rows;
139
+ const sizesAlong = isVertical ? rows : columns;
140
+ splitter.position = this.sumOf(sizesAcross.slice(0, splitter.index + 1));
141
+ splitter.start = this.sumOf(sizesAlong.slice(0, splitter.from));
142
+ splitter.end = this.sumOf(sizesAlong.slice(0, splitter.to + 1));
112
143
  this.applySplitterPosition(splitter);
113
144
  }
114
145
  }
146
+ sumOf(sizes) {
147
+ return sizes.reduce((sum, size) => sum + size, 0);
148
+ }
115
149
  applySplitterPosition(splitter) {
116
150
  const element = this.getById(this.getSplitterId(splitter));
117
151
  if (!element) {
@@ -123,6 +157,8 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
123
157
  const isVertical = splitter.orientation === 'vertical';
124
158
  element.style.left = isVertical ? `${splitter.position}%` : '';
125
159
  element.style.top = isVertical ? '' : `${splitter.position}%`;
160
+ element.style.setProperty('--start', `${splitter.start}%`);
161
+ element.style.setProperty('--end', `${100 - splitter.end}%`);
126
162
  }
127
163
  getSplitterId(splitter) {
128
164
  return `splitter-${splitter.orientation}-${splitter.index}`;
@@ -150,15 +186,8 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
150
186
  if (!sizes) {
151
187
  return;
152
188
  }
153
- splitter.position = sizes.slice(0, splitter.index + 1).reduce((sum, size) => sum + size, 0);
154
- this.applySplitterPosition(splitter);
155
- const cssSizes = sizes.map((size) => `${size}%`).join(' ');
156
- if (isVertical) {
157
- this.grid.style.gridTemplateColumns = cssSizes;
158
- }
159
- else {
160
- this.grid.style.gridTemplateRows = cssSizes;
161
- }
189
+ const { columns, rows } = this.layout.sizes;
190
+ this.applyGridSizes(isVertical ? sizes : columns, isVertical ? rows : sizes);
162
191
  }
163
192
  onSplitterPointerUp(event, splitter) {
164
193
  if (this.draggedSplitter !== splitter) {
@@ -23,8 +23,10 @@
23
23
  }
24
24
 
25
25
  /* The splitters look like the gutters used to resize the panels (see #gutter of the lr-panel),
26
- but they go through the whole Grid, with a 2rem gap at both ends,
27
- and are centered on the boundary they can move. */
26
+ but they go along the boundary they can move, with a 2rem gap at both ends,
27
+ and are centered on it. The Component sets --start and --end: the insets of the two ends
28
+ of the splitter from the edges of the Grid, as a splitter only goes along the Cells
29
+ that share its boundary. */
28
30
  .splitter {
29
31
  position: absolute;
30
32
  z-index: 100;
@@ -35,8 +37,8 @@
35
37
  }
36
38
 
37
39
  .splitter-vertical {
38
- top: 2rem;
39
- bottom: 2rem;
40
+ top: calc(var(--start) + 2rem);
41
+ bottom: calc(var(--end) + 2rem);
40
42
  width: 6px;
41
43
  /* Centers the splitter on the boundary, whatever its borders add to its width. */
42
44
  transform: translateX(-50%);
@@ -44,15 +46,15 @@
44
46
  }
45
47
 
46
48
  .splitter-horizontal {
47
- left: 2rem;
48
- right: 2rem;
49
+ left: calc(var(--start) + 2rem);
50
+ right: calc(var(--end) + 2rem);
49
51
  height: 6px;
50
52
  transform: translateY(-50%);
51
53
  cursor: row-resize;
52
54
  }
53
55
 
54
56
  /* The 2rem at both ends that the splitter itself does not cover are filled with a thin line,
55
- so that the boundary between the cells stays visible over the whole Grid. */
57
+ so that the boundary between the cells stays visible over its whole length. */
56
58
  .splitter::before,
57
59
  .splitter::after {
58
60
  content: '';
@@ -69,7 +71,7 @@
69
71
  }
70
72
 
71
73
  /* The lines are placed against the padding box of the splitter, which is inset by its border:
72
- they are made 1px longer, so that they reach the edge of the Grid. */
74
+ they are made 1px longer, so that they reach the end of the boundary. */
73
75
  .splitter-vertical::before {
74
76
  top: calc(-2rem - 1px);
75
77
  height: calc(2rem + 1px);
@@ -1,3 +1,4 @@
1
+ import { SHORTLINK_PARAMETER } from '../../../tools/share/iurlshortener.js';
1
2
  import { generateQrCode } from '../../../tools/utils/qrcode.js';
2
3
  class GeoGirafeShareManager {
3
4
  serviceUrl;
@@ -24,8 +25,10 @@ class GeoGirafeShareManager {
24
25
  const response_data = (await response.json());
25
26
  if (response_data) {
26
27
  const baseUrl = forcedBaseUrl ?? this.urlManager.getBaseUrlPath();
27
- const hash = response_data.short_url.split('/').pop();
28
- const shortUrl = `${baseUrl}#gg-${hash}`;
28
+ const shortId = response_data.short_url.split('/').pop() ?? '';
29
+ const url = new URL(baseUrl);
30
+ url.searchParams.set(SHORTLINK_PARAMETER, shortId);
31
+ const shortUrl = url.toString();
29
32
  const qrcode = await generateQrCode(shortUrl);
30
33
  return {
31
34
  success: true,
@@ -0,0 +1,59 @@
1
+ import GirafeHTMLElement from '../../base/GirafeHTMLElement.js';
2
+ import { StreetViewProvider } from '../../tools/state/streetview.js';
3
+ import type { StreetViewProviderAdapter } from './tools/provider.js';
4
+ /**
5
+ * A StreetView, displaying the footage of a provider (Google Street View, ...) side by side with the 2D Map.
6
+ *
7
+ * The Component stays lightweight: the provider and its external library are only loaded when the
8
+ * StreetView is displayed for the first time. A click on the map displays the closest footage, and a
9
+ * marker on the map shows the position and the direction of sight of the displayed footage.
10
+ * The map is accessed through the MapManager: there is no dependency to the map-2d Component.
11
+ */
12
+ export default abstract class StreetViewComponent extends GirafeHTMLElement {
13
+ protected templateUrl: string | null;
14
+ protected styleUrls: string[] | null;
15
+ template: () => import("uhtml").Hole;
16
+ loading: boolean;
17
+ hasFootage: boolean;
18
+ protected readonly provider: StreetViewProvider;
19
+ private adapter;
20
+ /** Promise of the currently running adapter creation, to prevent creating it twice. */
21
+ private adapterCreation;
22
+ /** The view of the footage displayed by the adapter, to tell the views coming from a shared state apart. */
23
+ private displayedView;
24
+ private readonly markerIcon;
25
+ private readonly markerGeometry;
26
+ private readonly marker;
27
+ private readonly markerSource;
28
+ private readonly markerLayer;
29
+ protected constructor(provider: StreetViewProvider);
30
+ private get olMap();
31
+ private get cellId();
32
+ private get isDisplayed();
33
+ private get searchRadius();
34
+ /** Creates the adapter of the provider, loading its module on demand. */
35
+ protected abstract createAdapter(): Promise<StreetViewProviderAdapter>;
36
+ /** The StreetView is displayed in a Cell of the Grid, like the Maps. */
37
+ protected get isFullscreenComponent(): boolean;
38
+ protected connectedCallback(): void;
39
+ protected disconnectedCallback(): void;
40
+ render(): void;
41
+ private registerEvents;
42
+ /**
43
+ * Loads the provider and shows its viewer when the StreetView is displayed in a Cell of the Grid,
44
+ * and hides it when it is not displayed anymore. The clicks on the map are only followed while displayed.
45
+ */
46
+ private onDisplayToggled;
47
+ private createAdapterOnce;
48
+ private onMapClick;
49
+ private showClosestFootage;
50
+ private onFootageChanged;
51
+ private onViewChanged;
52
+ /**
53
+ * Displays the footage of the view in the state when it was not displayed by this Component itself,
54
+ * which is the case when a shared state is restored.
55
+ */
56
+ private showStateView;
57
+ private isDisplayedView;
58
+ private updateMarker;
59
+ }