@geogirafe/lib-geoportal 0.6.0-1428917543 → 0.6.0-1433157121

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 (38) hide show
  1. package/components/main.d.ts +1 -0
  2. package/components/main.js +1 -0
  3. package/components/metadatawindow/component.d.ts +12 -0
  4. package/components/metadatawindow/component.js +104 -0
  5. package/components/selectiongrid/component.d.ts +2 -1
  6. package/components/selectiongrid/component.js +38 -12
  7. package/components/selectiongrid/tools/selectiontabulatormanager.d.ts +81 -0
  8. package/components/selectiongrid/tools/selectiontabulatormanager.js +193 -0
  9. package/components/treeviewgroup/component.d.ts +1 -0
  10. package/components/treeviewgroup/component.js +19 -1
  11. package/components/treeviewitem/component.d.ts +1 -0
  12. package/components/treeviewitem/component.js +19 -1
  13. package/main.js +2 -0
  14. package/models/gmf.d.ts +1 -0
  15. package/models/layers/baselayer.d.ts +3 -0
  16. package/models/layers/baselayer.js +17 -0
  17. package/models/layers/grouplayer.d.ts +1 -0
  18. package/models/layers/grouplayer.js +1 -0
  19. package/models/layers/layer.d.ts +1 -0
  20. package/models/layers/layercog.d.ts +1 -0
  21. package/models/layers/layercog.js +2 -0
  22. package/models/layers/layervectortiles.d.ts +1 -0
  23. package/models/layers/layervectortiles.js +1 -0
  24. package/models/layers/layerwms.d.ts +1 -0
  25. package/models/layers/layerwms.js +2 -0
  26. package/models/layers/layerwmts.d.ts +1 -0
  27. package/models/layers/layerwmts.js +2 -0
  28. package/models/layers/layerxyz.d.ts +1 -0
  29. package/models/layers/layerxyz.js +2 -0
  30. package/models/layers/themelayer.d.ts +1 -0
  31. package/package.json +3 -1
  32. package/public/about.json +1 -1
  33. package/styles/index.css +8 -0
  34. package/tools/configuration/girafeconfig.d.ts +6 -0
  35. package/tools/configuration/girafeconfig.js +18 -0
  36. package/tools/state/state.d.ts +6 -0
  37. package/tools/state/state.js +10 -0
  38. package/tools/themesmanager.js +1 -0
@@ -12,6 +12,7 @@ export { default as LidarProfileComponent } from './lidar/profile/component';
12
12
  export { default as LRPanelComponent } from './lr-panel/component';
13
13
  export { default as MapComponent } from './map/component';
14
14
  export { default as MenuButtonComponent } from './menubutton/component';
15
+ export { default as MetadataWindowComponent } from './metadatawindow/component';
15
16
  export { default as NavBookmarksComponent } from './navigation/navbookmarks/component';
16
17
  export { default as NavHelperComponent } from './navigation/navhelper/component';
17
18
  export { default as OfflineComponent } from './offline/component';
@@ -12,6 +12,7 @@ export { default as LidarProfileComponent } from './lidar/profile/component';
12
12
  export { default as LRPanelComponent } from './lr-panel/component';
13
13
  export { default as MapComponent } from './map/component';
14
14
  export { default as MenuButtonComponent } from './menubutton/component';
15
+ export { default as MetadataWindowComponent } from './metadatawindow/component';
15
16
  export { default as NavBookmarksComponent } from './navigation/navbookmarks/component';
16
17
  export { default as NavHelperComponent } from './navigation/navhelper/component';
17
18
  export { default as OfflineComponent } from './offline/component';
@@ -0,0 +1,12 @@
1
+ import GirafeDraggableElement from '../../base/GirafeDraggableElement';
2
+ declare class MetadataWindowComponent extends GirafeDraggableElement {
3
+ template: () => import("uhtml").Hole;
4
+ private initialized;
5
+ constructor();
6
+ registerEvents(): void;
7
+ render(): void;
8
+ toggleMetadata(visible: boolean): void;
9
+ closeWindow(): void;
10
+ connectedCallback(): void;
11
+ }
12
+ export default MetadataWindowComponent;
@@ -0,0 +1,104 @@
1
+ import { html as uHtml } from 'uhtml';
2
+ import GirafeDraggableElement from '../../base/GirafeDraggableElement';
3
+ class MetadataWindowComponent extends GirafeDraggableElement {
4
+ constructor() {
5
+ super('metadatawindow');
6
+ Object.defineProperty(this, "template", {
7
+ enumerable: true,
8
+ configurable: true,
9
+ writable: true,
10
+ value: () => {
11
+ return uHtml `<style>
12
+ #draggable {
13
+ border-radius: 1px;
14
+ box-shadow: var(--bx-shdw);
15
+ height: 100%;
16
+ width: 100%;
17
+ }
18
+
19
+ #header {
20
+ height: 2rem;
21
+ line-height: 2rem;
22
+ cursor: grabbing;
23
+ z-index: 10;
24
+ background: var(--bkg-color-grad2);
25
+ color: var(--text-color);
26
+ text-align: center;
27
+ }
28
+
29
+ #content {
30
+ padding-left: 1rem;
31
+ width: calc(100% - 1rem);
32
+ height: calc(100% - 2rem);
33
+ }
34
+
35
+ #close {
36
+ cursor: pointer;
37
+ float: right;
38
+ width: 12px;
39
+ color: var(--text-color);
40
+ padding: 0.45rem;
41
+ }
42
+
43
+ iframe {
44
+ border: none;
45
+ width: 100%;
46
+ height: 100%;
47
+ }
48
+
49
+ </style>
50
+ <div id="draggable">
51
+ <div id="header">
52
+ <span i18n="${this.state.metadata.title}"></span>
53
+ <img id="close" alt="close-icon" src="icons/close.svg" />
54
+ </div>
55
+ <div id="content">
56
+ <iframe title="iframe content" src="${this.state.metadata.url}"></iframe>
57
+ </div>
58
+ </div>
59
+ `;
60
+ }
61
+ });
62
+ Object.defineProperty(this, "initialized", {
63
+ enumerable: true,
64
+ configurable: true,
65
+ writable: true,
66
+ value: false
67
+ });
68
+ }
69
+ registerEvents() {
70
+ this.stateManager.subscribe('interface.metadataVisible', (_oldValue, newValue) => this.toggleMetadata(newValue));
71
+ this.stateManager.subscribe(/metadata.*/, () => this.render());
72
+ }
73
+ render() {
74
+ if (!this.initialized) {
75
+ const host = this.shadow.getRootNode().host;
76
+ host.style.width = this.configManager.Config.metadata.defaultWindowWidth;
77
+ host.style.height = this.configManager.Config.metadata.defaultWindowHeight;
78
+ this.initialized = true;
79
+ }
80
+ super.render();
81
+ this.girafeTranslate();
82
+ this.makeDraggable();
83
+ }
84
+ toggleMetadata(visible) {
85
+ if (visible) {
86
+ this.render();
87
+ }
88
+ else {
89
+ this.renderEmpty();
90
+ }
91
+ }
92
+ closeWindow() {
93
+ this.state.interface.metadataVisible = false;
94
+ }
95
+ connectedCallback() {
96
+ this.loadConfig().then(() => {
97
+ this.render();
98
+ this.girafeTranslate();
99
+ this.makeDraggable();
100
+ this.registerEvents();
101
+ });
102
+ }
103
+ }
104
+ export default MetadataWindowComponent;
@@ -10,6 +10,7 @@ declare class SelectionGridComponent extends GirafeResizableElement {
10
10
  template: () => import("uhtml").Hole;
11
11
  private readonly eventsCallbacks;
12
12
  private readonly selectionGridManager;
13
+ private readonly selectionTabulatorManager;
13
14
  private isVisibleComponentSetup;
14
15
  private debounceOnFeaturesSelected;
15
16
  visible: boolean;
@@ -26,7 +27,7 @@ declare class SelectionGridComponent extends GirafeResizableElement {
26
27
  /**
27
28
  * Activates the specified tab, renders the grid, and displays the grid for the specified id.
28
29
  */
29
- displayGrid(id: string): void;
30
+ displayGrid(id: string, replaceData?: boolean): void;
30
31
  /**
31
32
  * Clean the grid, closes the panel and deselect the selected features.
32
33
  */
@@ -2,6 +2,7 @@ import { html as uHtml } from 'uhtml';
2
2
  import GirafeResizableElement from '../../base/GirafeResizableElement';
3
3
  import { debounce } from '../../tools/utils/debounce';
4
4
  import SelectionGridManager from './tools/selectiongridmanager';
5
+ import SelectionTabulatorManager from './tools/selectiontabulatormanager';
5
6
  /**
6
7
  * Represents a selection grid component based on GridJs.
7
8
  * Display itself when it should be visible and have selected features.
@@ -25,7 +26,6 @@ class SelectionGridComponent extends GirafeResizableElement {
25
26
  }
26
27
 
27
28
  #panel {
28
- position: absolute;
29
29
  top: 0;
30
30
  left: 0;
31
31
  right: 0;
@@ -161,11 +161,28 @@ th.gridjs-th {
161
161
  margin: 0.1rem;
162
162
  }
163
163
 
164
+ /* Define the style when the column is not sorted */
165
+ .tabulator-col[aria-sort='none'] .tabulator-col-sorter i {
166
+ color: #999;
167
+ }
168
+
169
+ /* Define the style when the column is sorted in ascending order */
170
+ .tabulator-col[aria-sort='asc'] .tabulator-col-sorter i {
171
+ color: #f00;
172
+ }
173
+
174
+ /* Define the style when the column is sorted in descending order */
175
+ .tabulator-col[aria-sort='desc'] .tabulator-col-sorter i {
176
+ color: #f00;
177
+ transform: scaleY(-1); /* flip the icon vertically so the arrow points down */
178
+ }
179
+
164
180
  </style>
165
181
  <link rel="stylesheet" href="styles/common.css" />
166
182
  <link rel="stylesheet" href="lib/gridjs/mermaid.min.css" />
167
183
  <link rel="stylesheet" href="lib/fontawesome/css/all.min.css" />
168
184
  <link rel="stylesheet" href="lib/font-gis/font-gis.css" />
185
+ <link rel="stylesheet" href="../../../node_modules/tabulator-tables/dist/css/tabulator.css" />
169
186
 
170
187
  <div id="panel">
171
188
  <div id="content">
@@ -176,10 +193,10 @@ th.gridjs-th {
176
193
  i18n="${th.text}"
177
194
  class="${th.active ? 'tab active' : 'tab'}"
178
195
  .active="${th.active}"
179
- onclick="${() => this.displayGrid(th.id)}"></button>
196
+ onclick="${() => this.displayGrid(th.id, true)}"></button>
180
197
  `)}
181
198
  </div>
182
- <div id="grid"></div>
199
+ <div id="tabulator"></div>
183
200
  </div>
184
201
  <div id="gutter"></div>
185
202
  <button id="close" onclick="this.closePanel()">
@@ -201,6 +218,12 @@ th.gridjs-th {
201
218
  writable: true,
202
219
  value: new SelectionGridManager()
203
220
  });
221
+ Object.defineProperty(this, "selectionTabulatorManager", {
222
+ enumerable: true,
223
+ configurable: true,
224
+ writable: true,
225
+ value: new SelectionTabulatorManager()
226
+ });
204
227
  Object.defineProperty(this, "isVisibleComponentSetup", {
205
228
  enumerable: true,
206
229
  configurable: true,
@@ -234,15 +257,18 @@ th.gridjs-th {
234
257
  * @returns The current array of TabHeader objects.
235
258
  */
236
259
  getTabHeaders() {
237
- return this.selectionGridManager.tabHeaders;
260
+ return this.selectionTabulatorManager.tabHeaders;
238
261
  }
239
262
  /**
240
263
  * Activates the specified tab, renders the grid, and displays the grid for the specified id.
241
264
  */
242
- displayGrid(id) {
243
- this.selectionGridManager.activateTab(id);
265
+ displayGrid(id, replaceData = false) {
266
+ if (replaceData) {
267
+ this.selectionTabulatorManager.replaceData(id);
268
+ }
269
+ this.selectionTabulatorManager.activateTab(id);
244
270
  this.render();
245
- this.selectionGridManager.displayGrid(id);
271
+ this.selectionTabulatorManager.displayGrid(id);
246
272
  }
247
273
  /**
248
274
  * Clean the grid, closes the panel and deselect the selected features.
@@ -260,7 +286,6 @@ th.gridjs-th {
260
286
  renderComponent() {
261
287
  super.render();
262
288
  super.girafeTranslate();
263
- this.selectionGridManager.setGridElement(this.shadow.querySelector('#grid'));
264
289
  this.activateTooltips(false, [800, 0], 'top-end');
265
290
  if (!this.isVisibleComponentSetup) {
266
291
  this.setupVisibleComponent();
@@ -309,15 +334,16 @@ th.gridjs-th {
309
334
  * @private
310
335
  */
311
336
  onFeaturesSelected(features) {
312
- this.selectionGridManager.tabHeaders = [];
337
+ this.selectionTabulatorManager.tabHeaders = [];
313
338
  // No feature ? Close.
314
339
  if (this.isNullOrUndefined(features) || features.length <= 0) {
315
340
  this.closePanel();
316
341
  return;
317
342
  }
318
- // Otherwise, transforms features to grid data.
319
- this.selectionGridManager.featuresToGridData(features);
320
- const tabIds = Object.keys(this.selectionGridManager.idTab);
343
+ this.selectionTabulatorManager.featuresToGridData(features);
344
+ const tabIds = Object.keys(this.selectionTabulatorManager.idTab);
345
+ //define table element
346
+ this.selectionTabulatorManager.setElement(this.shadow.querySelector('#tabulator'));
321
347
  // No tab ? Close the panel.
322
348
  if (!tabIds.length) {
323
349
  this.closePanel();
@@ -0,0 +1,81 @@
1
+ import type OlFeature from 'ol/Feature';
2
+ import { GridDataById } from '../../../tools/featuretogriddatabyid';
3
+ import { ColumnDefinition, TabulatorFull as Tabulator } from 'tabulator-tables';
4
+ /**
5
+ * Represents the header text and state of a tab.
6
+ */
7
+ export interface TabHeader {
8
+ id: string;
9
+ text: string;
10
+ active: boolean;
11
+ }
12
+ /**
13
+ * Represents a column in a data table.
14
+ */
15
+ export interface Column {
16
+ id: string;
17
+ name: string;
18
+ }
19
+ /**
20
+ * Represents the content and backrefs of a tab.
21
+ */
22
+ export interface TabContent {
23
+ columns: Column[];
24
+ data: unknown[][];
25
+ features: OlFeature[];
26
+ }
27
+ export default class SelectionTabulatorManager {
28
+ private readonly formatGridGeomValue;
29
+ private configManager;
30
+ private readonly featureToGridData;
31
+ idTab: Record<string, TabContent>;
32
+ tabHeaders: TabHeader[];
33
+ table: Tabulator | null;
34
+ element: string | HTMLElement;
35
+ data: GridDataById;
36
+ constructor();
37
+ /**
38
+ * Set the HTML element to be used by the grid.
39
+ */
40
+ setElement(htmlElement: HTMLElement): void;
41
+ /**
42
+ * Replace the data of the current grid with the data of the specified tab.
43
+ */
44
+ replaceData(id: string): void;
45
+ /**
46
+ * Activates a tab matching the given id.
47
+ */
48
+ activateTab(id: string): void;
49
+ /**
50
+ * Creates (replace) the GridJS grid based on the provided tab id and related data and features.
51
+ */
52
+ displayGrid(id: string): void;
53
+ /**
54
+ * Transforms the given features into grid data and sets tab headers.
55
+ * The grid data are completely replaced.
56
+ */
57
+ featuresToGridData(features: OlFeature[]): void;
58
+ /**
59
+ * Converts grid data to grid tab format and adds it to the idTab object.
60
+ * @private
61
+ */
62
+ private gridDataToGridTab;
63
+ columnsToGridColumns(columns: string[]): ColumnDefinition[];
64
+ filterEmptyColumnsOnData(data: GridDataById): GridDataById;
65
+ /**
66
+ * @returns A newly created grid column object.
67
+ * @private
68
+ */
69
+ private createGridColumn;
70
+ /**
71
+ * Creates grid data by mapping values (simple values, or transformed values in case of HTML).
72
+ * @returns The grid data array.
73
+ * @private
74
+ */
75
+ private createGridData;
76
+ /**
77
+ * @returns The locale specified in the configuration.
78
+ * @private
79
+ */
80
+ private getLocale;
81
+ }
@@ -0,0 +1,193 @@
1
+ import OlGeomGeometry from 'ol/geom/Geometry';
2
+ import ConfigManager from '../../../tools/configuration/configmanager';
3
+ import FeatureToGridDataById from '../../../tools/featuretogriddatabyid';
4
+ import I18nManager from '../../../tools/i18n/i18nmanager';
5
+ import FormatGridGeomValue from './formatgridgeomvalue';
6
+ import { TabulatorFull as Tabulator } from 'tabulator-tables';
7
+ export default class SelectionTabulatorManager {
8
+ constructor() {
9
+ Object.defineProperty(this, "formatGridGeomValue", {
10
+ enumerable: true,
11
+ configurable: true,
12
+ writable: true,
13
+ value: new FormatGridGeomValue()
14
+ });
15
+ Object.defineProperty(this, "configManager", {
16
+ enumerable: true,
17
+ configurable: true,
18
+ writable: true,
19
+ value: void 0
20
+ });
21
+ Object.defineProperty(this, "featureToGridData", {
22
+ enumerable: true,
23
+ configurable: true,
24
+ writable: true,
25
+ value: new FeatureToGridDataById({ keepGeomProperty: true })
26
+ });
27
+ Object.defineProperty(this, "idTab", {
28
+ enumerable: true,
29
+ configurable: true,
30
+ writable: true,
31
+ value: {}
32
+ });
33
+ Object.defineProperty(this, "tabHeaders", {
34
+ enumerable: true,
35
+ configurable: true,
36
+ writable: true,
37
+ value: []
38
+ });
39
+ Object.defineProperty(this, "table", {
40
+ enumerable: true,
41
+ configurable: true,
42
+ writable: true,
43
+ value: null
44
+ });
45
+ Object.defineProperty(this, "element", {
46
+ enumerable: true,
47
+ configurable: true,
48
+ writable: true,
49
+ value: ''
50
+ });
51
+ Object.defineProperty(this, "data", {
52
+ enumerable: true,
53
+ configurable: true,
54
+ writable: true,
55
+ value: {}
56
+ });
57
+ this.configManager = ConfigManager.getInstance();
58
+ }
59
+ /**
60
+ * Set the HTML element to be used by the grid.
61
+ */
62
+ setElement(htmlElement) {
63
+ this.element = htmlElement;
64
+ }
65
+ /**
66
+ * Replace the data of the current grid with the data of the specified tab.
67
+ */
68
+ replaceData(id) {
69
+ const columns = this.columnsToGridColumns(this.data[id].columns);
70
+ this.table?.setColumns(columns);
71
+ this.table?.replaceData(this.data[id].notOlProperties);
72
+ }
73
+ /**
74
+ * Activates a tab matching the given id.
75
+ */
76
+ activateTab(id) {
77
+ this.tabHeaders?.forEach((tabHeader) => (tabHeader.active = false));
78
+ const visibleTabHeader = this.tabHeaders?.find((tabHeader) => tabHeader.id === id);
79
+ if (!visibleTabHeader) {
80
+ return;
81
+ }
82
+ visibleTabHeader.active = true;
83
+ }
84
+ /**
85
+ * Creates (replace) the GridJS grid based on the provided tab id and related data and features.
86
+ */
87
+ displayGrid(id) {
88
+ if (!this.element) {
89
+ return;
90
+ }
91
+ this.table = new Tabulator(this.element, {
92
+ data: this.data[id].notOlProperties,
93
+ columns: this.columnsToGridColumns(this.data[id].columns),
94
+ selectableRows: true,
95
+ layout: 'fitColumns',
96
+ headerSortElement: function (_, dir) {
97
+ switch (dir) {
98
+ case 'asc':
99
+ return "<i class='fas fa-sort-up'>";
100
+ case 'desc':
101
+ return "<i class='fas fa-sort-down'>";
102
+ default:
103
+ return "<i class='fas fa-sort'>";
104
+ }
105
+ }
106
+ });
107
+ }
108
+ /**
109
+ * Transforms the given features into grid data and sets tab headers.
110
+ * The grid data are completely replaced.
111
+ */
112
+ featuresToGridData(features) {
113
+ this.idTab = {};
114
+ const gridDataById = this.featureToGridData.toGridDataById(features);
115
+ // Create tabs and collect data.
116
+ Object.keys(gridDataById).forEach((id) => this.gridDataToGridTab(id, gridDataById[id]));
117
+ // Create tabs headers
118
+ this.tabHeaders = Object.keys(this.idTab).map((key) => {
119
+ return {
120
+ id: key,
121
+ text: I18nManager.getInstance().getTranslation(key),
122
+ active: false
123
+ };
124
+ });
125
+ this.data = this.filterEmptyColumnsOnData(gridDataById);
126
+ }
127
+ /**
128
+ * Converts grid data to grid tab format and adds it to the idTab object.
129
+ * @private
130
+ */
131
+ gridDataToGridTab(id, gridData) {
132
+ if (id in this.idTab) {
133
+ // Already in ? Don't add a new one.
134
+ return;
135
+ }
136
+ this.idTab[id] = {
137
+ columns: gridData.columns.map((column) => this.createGridColumn(column)),
138
+ data: gridData.data.map((data) => this.createGridData(data)),
139
+ features: gridData.features
140
+ };
141
+ }
142
+ columnsToGridColumns(columns) {
143
+ const columnDefinition = [];
144
+ columns.map((column) => columnDefinition.push({ title: column, field: column }));
145
+ columnDefinition.forEach((column) => {
146
+ if (column.title === 'geometry' || column.title === 'geom') {
147
+ column.formatter = (cell) => {
148
+ return this.formatGridGeomValue.getGeometryIcons(cell.getValue(), this.getLocale()) ?? cell.getValue();
149
+ };
150
+ }
151
+ });
152
+ return columnDefinition;
153
+ }
154
+ filterEmptyColumnsOnData(data) {
155
+ for (const [_, entry] of Object.entries(data)) {
156
+ const columns = entry.columns;
157
+ const notOlProperties = entry.notOlProperties;
158
+ for (const [_, row] of Object.entries(notOlProperties)) {
159
+ for (const [key, _] of Object.entries(row)) {
160
+ if (!columns.some((column) => column === key)) {
161
+ delete row[key];
162
+ }
163
+ }
164
+ }
165
+ }
166
+ return data;
167
+ }
168
+ /**
169
+ * @returns A newly created grid column object.
170
+ * @private
171
+ */
172
+ createGridColumn(id) {
173
+ return {
174
+ id,
175
+ name: I18nManager.getInstance().getTranslation(id)
176
+ };
177
+ }
178
+ /**
179
+ * Creates grid data by mapping values (simple values, or transformed values in case of HTML).
180
+ * @returns The grid data array.
181
+ * @private
182
+ */
183
+ createGridData(values) {
184
+ return values.map((value) => value instanceof OlGeomGeometry ? this.formatGridGeomValue.getGeometryIcons(value, this.getLocale()) : value);
185
+ }
186
+ /**
187
+ * @returns The locale specified in the configuration.
188
+ * @private
189
+ */
190
+ getLocale() {
191
+ return this.configManager.Config.general.locale;
192
+ }
193
+ }
@@ -15,6 +15,7 @@ declare class TreeViewGroupComponent extends GirafeHTMLElement {
15
15
  toggle(state?: 'on' | 'off' | 'semi'): void;
16
16
  connectedCallback(): void;
17
17
  deactivateGroup(layer: BaseLayer): void;
18
+ showMetadata(): void;
18
19
  deleteGroup(): void;
19
20
  private deleteGroupRecursive;
20
21
  }
@@ -80,8 +80,13 @@ i {
80
80
  width: 0;
81
81
  }
82
82
 
83
- .delete {
83
+ .metadata {
84
84
  order: 5;
85
+ padding: 0.45rem;
86
+ }
87
+
88
+ .delete {
89
+ order: 6;
85
90
  padding-top: 0.45rem;
86
91
  }
87
92
 
@@ -144,6 +149,12 @@ i {
144
149
  >${this.group.name}</span
145
150
  >
146
151
 
152
+ <!-- Metadata icon -->
153
+ <i
154
+ class="${this.group.metadataUrl ? 'fa fa-solid fa-circle-info tool metadata selectable' : 'metadata hidden'}"
155
+ tip="More information"
156
+ onclick="${() => this.showMetadata()}"></i>
157
+
147
158
  <!-- Remove icon -->
148
159
  <i
149
160
  class="fa fa-solid fa-xmark tool delete selectable"
@@ -231,6 +242,13 @@ i {
231
242
  }
232
243
  }
233
244
  }
245
+ showMetadata() {
246
+ this.state.metadata = {
247
+ title: this.group.name,
248
+ url: this.group.metadataUrl ?? null
249
+ };
250
+ this.state.interface.metadataVisible = true;
251
+ }
234
252
  deleteGroup() {
235
253
  this.deactivateGroup(this.group);
236
254
  if (!this.deleteGroupRecursive(this.state.layers.layersList)) {
@@ -21,6 +21,7 @@ declare class TreeViewItemComponent extends GirafeHTMLElement {
21
21
  zoomToVisibleResolution(): void;
22
22
  zoomToFullExtent(): void;
23
23
  swipeLayer(side: 'left' | 'right'): void;
24
+ showMetadata(): void;
24
25
  connectedCallback(): void;
25
26
  }
26
27
  export default TreeViewItemComponent;
@@ -125,8 +125,13 @@ i {
125
125
  order: 11;
126
126
  }
127
127
 
128
- .legend {
128
+ .metadata {
129
129
  order: 12;
130
+ padding-right: 0.15rem;
131
+ }
132
+
133
+ .legend {
134
+ order: 13;
130
135
  }
131
136
 
132
137
  .item i.advanced {
@@ -228,6 +233,12 @@ i {
228
233
  class="${this.layer instanceof LayerWms && this.layer.queryable ? 'fa-solid fa-filter tool selectable filter advanced' : 'filter invisible'}"
229
234
  tip="Filter layer"></i>
230
235
 
236
+ <!-- Metadata icon -->
237
+ <i
238
+ class="${this.layer.metadataUrl ? 'fa fa-solid fa-circle-info tool metadata selectable' : 'metadata hidden'}"
239
+ tip="More information"
240
+ onclick="${() => this.showMetadata()}"></i>
241
+
231
242
  <!-- Toggle legend icon -->
232
243
  <div
233
244
  class="${this.layer instanceof LayerLocalFile || (this.layer instanceof LayerWms && this.layer.legend) ? 'legend selectable' : 'legend invisible'}">
@@ -456,6 +467,13 @@ ${Object.keys(this.legendUrls).map((l) => uHtml `<img
456
467
  // Lastly, we update the state
457
468
  this.state.layers.swipedLayers = newSwipedLayers;
458
469
  }
470
+ showMetadata() {
471
+ this.state.metadata = {
472
+ title: this.layer.name,
473
+ url: this.layer.metadataUrl ?? null
474
+ };
475
+ this.state.interface.metadataVisible = true;
476
+ }
459
477
  connectedCallback() {
460
478
  this.loadConfig().then(() => {
461
479
  this.render();
package/main.js CHANGED
@@ -17,6 +17,7 @@ import ColorSwitcherComponent from './components/colorswitcher/component';
17
17
  import CoordinateComponent from './components/coordinate/component';
18
18
  import GlobeComponent from './components/globe/component';
19
19
  import HelpComponent from './components/help/component';
20
+ import MetadataWindowComponent from './components/metadatawindow/component';
20
21
  import InfoboxComponent from './components/infobox/component';
21
22
  import LanguageComponent from './components/language/component';
22
23
  import LidarPanelComponent from './components/lidar/panel/component';
@@ -81,6 +82,7 @@ try {
81
82
  customElements.define('girafe-coordinate', CoordinateComponent);
82
83
  customElements.define('girafe-globe-select', GlobeComponent);
83
84
  customElements.define('girafe-help', HelpComponent);
85
+ customElements.define('girafe-metadata-window', MetadataWindowComponent);
84
86
  customElements.define('girafe-infobox', InfoboxComponent);
85
87
  customElements.define('girafe-language-select', LanguageComponent);
86
88
  customElements.define('girafe-lidar-panel', LidarPanelComponent);
package/models/gmf.d.ts CHANGED
@@ -13,6 +13,7 @@ export interface GMFMetadata {
13
13
  legendRule?: string;
14
14
  legendImage?: string;
15
15
  disclaimer?: string;
16
+ metadataUrl?: string;
16
17
  hiDPILegendImages?: Record<string, string>;
17
18
  printLayers?: string;
18
19
  queryLayers?: string;
@@ -3,6 +3,7 @@ import ThemeLayer from './themelayer';
3
3
  type BaseLayerOptions = {
4
4
  isDefaultChecked?: boolean;
5
5
  disclaimer?: string;
6
+ metadataUrl?: string;
6
7
  };
7
8
  declare abstract class BaseLayer {
8
9
  /**
@@ -18,6 +19,7 @@ declare abstract class BaseLayer {
18
19
  order: number;
19
20
  isDefaultChecked: boolean;
20
21
  disclaimer?: string;
22
+ metadataUrl?: string;
21
23
  hasError: boolean;
22
24
  errorMessage: string | null;
23
25
  abstract activeState: string;
@@ -26,6 +28,7 @@ declare abstract class BaseLayer {
26
28
  abstract clone(): BaseLayer;
27
29
  parent: ThemeLayer | GroupLayer | null;
28
30
  constructor(id: number, name: string, order: number, options?: BaseLayerOptions);
31
+ private calculateMetadataUrl;
29
32
  /**
30
33
  * @returns the name of the class to facilitate the identification
31
34
  * of subclasses, even in Proxy objects.
@@ -1,3 +1,4 @@
1
+ import ConfigManager from '../../tools/configuration/configmanager';
1
2
  import { v4 as uuidv4 } from 'uuid';
2
3
  class BaseLayer {
3
4
  constructor(id, name, order, options) {
@@ -44,6 +45,12 @@ class BaseLayer {
44
45
  writable: true,
45
46
  value: void 0
46
47
  });
48
+ Object.defineProperty(this, "metadataUrl", {
49
+ enumerable: true,
50
+ configurable: true,
51
+ writable: true,
52
+ value: void 0
53
+ });
47
54
  Object.defineProperty(this, "hasError", {
48
55
  enumerable: true,
49
56
  configurable: true,
@@ -68,6 +75,16 @@ class BaseLayer {
68
75
  this.order = order;
69
76
  this.isDefaultChecked = options?.isDefaultChecked || false;
70
77
  this.disclaimer = options?.disclaimer;
78
+ this.metadataUrl = this.calculateMetadataUrl(options?.metadataUrl);
79
+ }
80
+ calculateMetadataUrl(metadataUrl) {
81
+ if (!metadataUrl) {
82
+ return undefined;
83
+ }
84
+ if (!metadataUrl.startsWith('http') && ConfigManager.getInstance().Config.metadata.metadataUrlPrefix) {
85
+ return ConfigManager.getInstance().Config.metadata.metadataUrlPrefix + metadataUrl;
86
+ }
87
+ return metadataUrl;
71
88
  }
72
89
  /**
73
90
  * @returns the name of the class to facilitate the identification
@@ -2,6 +2,7 @@ import BaseLayer from './baselayer';
2
2
  export type GroupLayerOptions = {
3
3
  isDefaultChecked?: boolean;
4
4
  disclaimer?: string;
5
+ metadataUrl?: string;
5
6
  isDefaultExpanded?: boolean;
6
7
  isExclusiveGroup?: boolean;
7
8
  };
@@ -39,6 +39,7 @@ class GroupLayer extends BaseLayer {
39
39
  clone() {
40
40
  const options = {
41
41
  isDefaultChecked: this.isDefaultChecked,
42
+ metadataUrl: this.metadataUrl,
42
43
  disclaimer: this.disclaimer,
43
44
  isDefaultExpanded: this.isExpanded,
44
45
  isExclusiveGroup: this.isExclusiveGroup
@@ -2,6 +2,7 @@ import BaseLayer from './baselayer';
2
2
  type LayerOptions = {
3
3
  isDefaultChecked?: boolean;
4
4
  disclaimer?: string;
5
+ metadataUrl?: string;
5
6
  opacity?: number;
6
7
  };
7
8
  declare abstract class Layer extends BaseLayer {
@@ -3,6 +3,7 @@ import Layer from './layer';
3
3
  type LayerCogTilesOptions = {
4
4
  isDefaultChecked?: boolean;
5
5
  disclaimer?: string;
6
+ metadataUrl?: string;
6
7
  opacity?: number;
7
8
  };
8
9
  declare class LayerCog extends Layer {
@@ -22,6 +22,7 @@ class LayerCog extends Layer {
22
22
  clone() {
23
23
  const options = {
24
24
  isDefaultChecked: this.isDefaultChecked,
25
+ metadataUrl: this.metadataUrl,
25
26
  disclaimer: this.disclaimer,
26
27
  opacity: this.opacity
27
28
  };
@@ -35,6 +36,7 @@ class LayerCog extends Layer {
35
36
  static getOptionsFromGMFTreeItem(options) {
36
37
  return {
37
38
  isDefaultChecked: options.metadata?.isChecked,
39
+ metadataUrl: options.metadata?.metadataUrl,
38
40
  disclaimer: options.metadata?.disclaimer,
39
41
  opacity: 1 // TODO REG : Set default opacity
40
42
  };
@@ -3,6 +3,7 @@ type LayerVectorTilesOptions = {
3
3
  projection?: string;
4
4
  isDefaultChecked?: boolean;
5
5
  disclaimer?: string;
6
+ metadataUrl?: string;
6
7
  opacity?: number;
7
8
  };
8
9
  declare class LayerVectorTiles extends Layer {
@@ -35,6 +35,7 @@ class LayerVectorTiles extends Layer {
35
35
  const options = {
36
36
  projection: this.projection,
37
37
  isDefaultChecked: this.isDefaultChecked,
38
+ metadataUrl: this.metadataUrl,
38
39
  disclaimer: this.disclaimer,
39
40
  opacity: this.opacity
40
41
  };
@@ -7,6 +7,7 @@ import Layer from './layer';
7
7
  export type LayerWmsOptions = {
8
8
  isDefaultChecked?: boolean;
9
9
  disclaimer?: string;
10
+ metadataUrl?: string;
10
11
  opacity?: number;
11
12
  minResolution?: number;
12
13
  maxResolution?: number;
@@ -143,6 +143,7 @@ class LayerWms extends Layer {
143
143
  clone() {
144
144
  const options = {
145
145
  isDefaultChecked: this.isDefaultChecked,
146
+ metadataUrl: this.metadataUrl,
146
147
  disclaimer: this.disclaimer,
147
148
  opacity: this.opacity,
148
149
  minResolution: this.minResolution,
@@ -180,6 +181,7 @@ class LayerWms extends Layer {
180
181
  static getOptionsFromGMFTreeItem(options) {
181
182
  const opts = {
182
183
  isDefaultChecked: options.metadata?.isChecked,
184
+ metadataUrl: options.metadata?.metadataUrl,
183
185
  disclaimer: options.metadata?.disclaimer,
184
186
  opacity: 1, // TODO REG : Set default opacity
185
187
  minResolution: options.minResolutionHint,
@@ -6,6 +6,7 @@ import WMTS from 'ol/source/WMTS';
6
6
  export type LayerWmtsOptions = {
7
7
  isDefaultChecked?: boolean;
8
8
  disclaimer?: string;
9
+ metadataUrl?: string;
9
10
  opacity?: number;
10
11
  dimensions?: Record<string, object>;
11
12
  imageType?: string;
@@ -115,6 +115,7 @@ class LayerWmts extends Layer {
115
115
  clone() {
116
116
  const options = {
117
117
  isDefaultChecked: this.isDefaultChecked,
118
+ metadataUrl: this.metadataUrl,
118
119
  disclaimer: this.disclaimer,
119
120
  opacity: this.opacity,
120
121
  dimensions: this.dimensions,
@@ -145,6 +146,7 @@ class LayerWmts extends Layer {
145
146
  static getOptionsFromGMFTreeItem(options) {
146
147
  return {
147
148
  isDefaultChecked: options.metadata?.isChecked,
149
+ metadataUrl: options.metadata?.metadataUrl,
148
150
  disclaimer: options.metadata?.disclaimer,
149
151
  opacity: 1, // TODO REG : Set default opacity
150
152
  dimensions: options.dimensions,
@@ -3,6 +3,7 @@ import Layer from './layer';
3
3
  type LayerXYZTilesOptions = {
4
4
  isDefaultChecked?: boolean;
5
5
  disclaimer?: string;
6
+ metadataUrl?: string;
6
7
  opacity?: number;
7
8
  };
8
9
  declare class LayerXYZ extends Layer {
@@ -22,6 +22,7 @@ class LayerXYZ extends Layer {
22
22
  clone() {
23
23
  const options = {
24
24
  isDefaultChecked: this.isDefaultChecked,
25
+ metadataUrl: this.metadataUrl,
25
26
  disclaimer: this.disclaimer,
26
27
  opacity: this.opacity
27
28
  };
@@ -35,6 +36,7 @@ class LayerXYZ extends Layer {
35
36
  static getOptionsFromGMFTreeItem(options) {
36
37
  return {
37
38
  isDefaultChecked: options.metadata?.isChecked,
39
+ metadataUrl: options.metadata?.metadataUrl,
38
40
  disclaimer: options.metadata?.disclaimer,
39
41
  opacity: 1 // TODO REG : Set default opacity
40
42
  };
@@ -3,6 +3,7 @@ import { Coordinate } from 'ol/coordinate';
3
3
  export type ThemeLayerOptions = {
4
4
  isDefaultChecked?: boolean;
5
5
  disclaimer?: string;
6
+ metadataUrl?: string;
6
7
  isDefaultExpanded?: boolean;
7
8
  isExclusiveTheme?: boolean;
8
9
  };
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "GeoGirafe PSC",
6
6
  "url": "https://doc.geomapfish.dev"
7
7
  },
8
- "version": "0.6.0-1428917543",
8
+ "version": "0.6.0-1433157121",
9
9
  "type": "module",
10
10
  "engines": {
11
11
  "node": ">=20.0.0"
@@ -40,6 +40,7 @@
40
40
  "gridjs": "6.2.0",
41
41
  "lz-string": "1.5.0",
42
42
  "on-change": "5.0.1",
43
+ "tabulator-tables": "^6.2.5",
43
44
  "source-map-js": "1.2.0",
44
45
  "tippy.js": "6.3.7",
45
46
  "uhtml": "4.5.9",
@@ -63,6 +64,7 @@
63
64
  "@types/file-saver": "2.0.7",
64
65
  "@types/geojson": "7946.0.14",
65
66
  "@types/proj4": "2.5.5",
67
+ "@types/tabulator-tables": "^6.2.3",
66
68
  "@types/uuid": "10.0.0",
67
69
  "@typescript-eslint/eslint-plugin": "7.15.0",
68
70
  "@typescript-eslint/parser": "7.15.0",
package/public/about.json CHANGED
@@ -1 +1 @@
1
- {"version":"0.6.0-1428917543", "build":"1428917543", "date":"27/08/2024"}
1
+ {"version":"0.6.0-1433157121", "build":"1433157121", "date":"30/08/2024"}
package/styles/index.css CHANGED
@@ -265,6 +265,14 @@ girafe-share {
265
265
  transform: translate(-50%, -50%);
266
266
  }
267
267
 
268
+ girafe-metadata-window {
269
+ position: absolute;
270
+ z-index: 100;
271
+ background: var(--bkg-color);
272
+ top: 1rem;
273
+ left: 2rem;
274
+ }
275
+
268
276
  .border-top {
269
277
  border-top: solid 1px #fff;
270
278
  }
@@ -121,6 +121,11 @@ declare class GirafeConfig {
121
121
  quote: string;
122
122
  separator: string;
123
123
  };
124
+ metadata: {
125
+ metadataUrlPrefix: string;
126
+ defaultWindowWidth: string;
127
+ defaultWindowHeight: string;
128
+ };
124
129
  offline?: {
125
130
  downloadStartZoom: number;
126
131
  downloadEndZoom: number;
@@ -145,6 +150,7 @@ declare class GirafeConfig {
145
150
  private initConfigBasemaps;
146
151
  private initConfigLidar;
147
152
  private initConfigCsv;
153
+ private initConfigMetadata;
148
154
  private initConfigOffline;
149
155
  private initConfigBookmarks;
150
156
  private initConfigThemes;
@@ -108,6 +108,12 @@ class GirafeConfig {
108
108
  writable: true,
109
109
  value: void 0
110
110
  });
111
+ Object.defineProperty(this, "metadata", {
112
+ enumerable: true,
113
+ configurable: true,
114
+ writable: true,
115
+ value: void 0
116
+ });
111
117
  Object.defineProperty(this, "offline", {
112
118
  enumerable: true,
113
119
  configurable: true,
@@ -129,6 +135,7 @@ class GirafeConfig {
129
135
  this.bookmarks = this.initConfigBookmarks(config);
130
136
  this.lidar = this.initConfigLidar(config);
131
137
  this.csv = this.initConfigCsv(config);
138
+ this.metadata = this.initConfigMetadata(config);
132
139
  this.offline = this.initConfigOffline(config);
133
140
  try {
134
141
  this.search = this.initConfigSearch(config);
@@ -302,6 +309,17 @@ class GirafeConfig {
302
309
  ...config.csv
303
310
  };
304
311
  }
312
+ initConfigMetadata(config) {
313
+ const defaultConfig = {
314
+ metadataUrlPrefix: '',
315
+ defaultWindowWidth: '960px',
316
+ defaultWindowHeight: '460px'
317
+ };
318
+ return {
319
+ ...defaultConfig,
320
+ ...config.metadata
321
+ };
322
+ }
305
323
  initConfigOffline(config) {
306
324
  // This can be null, that's not a problem. No default value either.
307
325
  return config.offline;
@@ -18,6 +18,7 @@ type GraphicalInterface = {
18
18
  selectionComponentVisible: boolean;
19
19
  selectionComponent: string;
20
20
  aboutVisible: boolean;
21
+ metadataVisible: boolean;
21
22
  shareVisible: boolean;
22
23
  darkMapMode: boolean;
23
24
  darkFrontendMode: boolean;
@@ -69,6 +70,10 @@ export type Lidar = {
69
70
  line: OlGeomLineString | null;
70
71
  drawActive: boolean;
71
72
  };
73
+ export type Metadata = {
74
+ title: string | null;
75
+ url: string | null;
76
+ };
72
77
  export default class State {
73
78
  /**
74
79
  * This class is a used as the state of the application, which will be accessed behind a javascript proxy.
@@ -97,6 +102,7 @@ export default class State {
97
102
  infobox: {
98
103
  elements: InfoBoxContent[];
99
104
  };
105
+ metadata: Metadata;
100
106
  isOffline: boolean;
101
107
  extendedState: Record<string, object>;
102
108
  }
@@ -70,6 +70,7 @@ export default class State {
70
70
  selectionComponentVisible: false,
71
71
  selectionComponent: '',
72
72
  aboutVisible: false,
73
+ metadataVisible: false,
73
74
  shareVisible: false,
74
75
  darkMapMode: false,
75
76
  darkFrontendMode: false
@@ -177,6 +178,15 @@ export default class State {
177
178
  elements: []
178
179
  }
179
180
  });
181
+ Object.defineProperty(this, "metadata", {
182
+ enumerable: true,
183
+ configurable: true,
184
+ writable: true,
185
+ value: {
186
+ title: null,
187
+ url: null
188
+ }
189
+ });
180
190
  // Indicates is the application is currently used in offline mode
181
191
  Object.defineProperty(this, "isOffline", {
182
192
  enumerable: true,
@@ -241,6 +241,7 @@ class ThemesManager extends GirafeSingleton {
241
241
  // Group
242
242
  const options = {
243
243
  isDefaultChecked: elem.metadata?.isChecked,
244
+ metadataUrl: elem.metadata?.metadataUrl,
244
245
  disclaimer: elem.metadata?.disclaimer,
245
246
  isDefaultExpanded: elem.metadata?.isExpanded,
246
247
  isExclusiveGroup: elem.metadata?.exclusiveGroup