@geogirafe/lib-geoportal 1.2.0-dev.2836809138 → 1.2.0-dev.2844989778
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/api/apigeogirafeapp.js +4 -2
- package/api/style.css +4 -10
- package/assets/i18n/de.json +7 -3
- package/assets/i18n/en.json +4 -0
- package/assets/i18n/fr.json +4 -0
- package/assets/i18n/it.json +4 -0
- package/components/layout/component.d.ts +34 -3
- package/components/layout/component.js +66 -28
- package/components/layout/style.css +13 -0
- package/components/main.d.ts +1 -0
- package/components/main.js +1 -0
- package/components/map-2d/component.d.ts +0 -6
- package/components/map-2d/component.js +3 -22
- package/components/map-3d/component.d.ts +4 -2
- package/components/map-3d/component.js +7 -18
- package/components/map-grid/component.d.ts +70 -0
- package/components/map-grid/component.js +206 -0
- package/components/map-grid/style.css +99 -0
- package/components/menu-buttons-mobile/MenuMobile3dButton.js +3 -1
- package/package.json +1 -1
- package/styles/iframe.css +4 -10
- package/styles/index.css +4 -10
- package/styles/mobile.css +4 -10
- package/templates/iframe.html +2 -2
- package/templates/index.html +2 -2
- package/templates/mobile.html +2 -2
- package/templates/public/about.json +1 -1
- package/tools/app/geogirafeapp-mobile.js +2 -0
- package/tools/app/geogirafeapp.js +2 -0
- package/tools/configuration/girafeconfig.d.ts +4 -0
- package/tools/configuration/girafeconfig.js +20 -2
- package/tools/main.d.ts +3 -0
- package/tools/main.js +2 -0
- package/tools/share/serializers/globeserializer.d.ts +2 -0
- package/tools/share/serializers/globeserializer.js +26 -4
- package/tools/share/serializers/layoutserializer.d.ts +17 -0
- package/tools/share/serializers/layoutserializer.js +44 -0
- package/tools/share/stateserializer.js +3 -0
- package/tools/state/globe.d.ts +0 -1
- package/tools/state/globe.js +0 -1
- package/tools/state/layout.d.ts +62 -0
- package/tools/state/layout.js +67 -0
- package/tools/state/state.d.ts +2 -0
- package/tools/state/state.js +8 -0
- package/tools/state/statemanager.d.ts +1 -0
- package/tools/state/statemanager.js +8 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { html as uHtml } from 'uhtml';
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import GirafeHTMLElement from '../../base/GirafeHTMLElement.js';
|
|
4
|
+
import { AVAILABLE_CELL_COMPONENTS, getGridSize, MAX_CELL_SIZE, MIN_CELL_SIZE } from '../../tools/state/layout.js';
|
|
5
|
+
/**
|
|
6
|
+
* The Grid in which the Map Components are displayed.
|
|
7
|
+
*
|
|
8
|
+
* The Components are written as children of this Element in the HTML of the application
|
|
9
|
+
* and are moved into the Cells of the Grid, depending on <code>state.layout</code>.
|
|
10
|
+
* A Component that is not placed in any Cell is not slotted, and therefore not displayed.
|
|
11
|
+
*/
|
|
12
|
+
class MapGridComponent extends GirafeHTMLElement {
|
|
13
|
+
templateUrl = null;
|
|
14
|
+
styleUrls = null;
|
|
15
|
+
template = () => {
|
|
16
|
+
return uHtml `<style>
|
|
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
|
+
</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)}
|
|
20
|
+
</style>
|
|
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>
|
|
23
|
+
${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
|
|
24
|
+
};
|
|
25
|
+
/** Index of each Cell of the current Layout, used to render the Cells and their slots. */
|
|
26
|
+
cellIndexes = [];
|
|
27
|
+
/** The splitters between the Cells of the current Layout. */
|
|
28
|
+
splitters = [];
|
|
29
|
+
/** The splitter that is currently dragged, if any. */
|
|
30
|
+
draggedSplitter = null;
|
|
31
|
+
constructor() {
|
|
32
|
+
super('map-grid');
|
|
33
|
+
}
|
|
34
|
+
get layout() {
|
|
35
|
+
return this.state.layout;
|
|
36
|
+
}
|
|
37
|
+
get grid() {
|
|
38
|
+
return this.getById('grid');
|
|
39
|
+
}
|
|
40
|
+
connectedCallback() {
|
|
41
|
+
super.connectedCallback();
|
|
42
|
+
this.render();
|
|
43
|
+
// The subscription on layout.type is called immediately, which builds the Grid.
|
|
44
|
+
this.registerEvents();
|
|
45
|
+
}
|
|
46
|
+
registerEvents() {
|
|
47
|
+
this.subscribe('layout.type', () => this.applyLayout());
|
|
48
|
+
this.subscribe('layout.cells', () => this.applyCells());
|
|
49
|
+
this.subscribe('layout.sizes.columns', () => this.applySizes());
|
|
50
|
+
this.subscribe('layout.sizes.rows', () => this.applySizes());
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Rebuilds the Cells and the splitters of the Grid for the current Layout,
|
|
54
|
+
* then places the Components and applies the sizes.
|
|
55
|
+
*/
|
|
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);
|
|
60
|
+
this.refreshRender();
|
|
61
|
+
this.applyCells();
|
|
62
|
+
this.applySizes();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
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.
|
|
68
|
+
*/
|
|
69
|
+
createSplitters(columns, rows) {
|
|
70
|
+
const splitters = [];
|
|
71
|
+
for (let index = 0; index < columns - 1; index++) {
|
|
72
|
+
splitters.push({ orientation: 'vertical', index: index, position: 0 });
|
|
73
|
+
}
|
|
74
|
+
for (let index = 0; index < rows - 1; index++) {
|
|
75
|
+
splitters.push({ orientation: 'horizontal', index: index, position: 0 });
|
|
76
|
+
}
|
|
77
|
+
return splitters;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Moves each Component into the Cell it is configured for, by setting the name of the slot
|
|
81
|
+
* it must be displayed in. Components that are not placed in any Cell get no slot,
|
|
82
|
+
* which means that they are not displayed at all.
|
|
83
|
+
*/
|
|
84
|
+
applyCells() {
|
|
85
|
+
const cells = this.layout.cells;
|
|
86
|
+
for (const component of AVAILABLE_CELL_COMPONENTS) {
|
|
87
|
+
const element = this.querySelector(component.tagName);
|
|
88
|
+
if (!element) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const cellIndex = cells.indexOf(component.id);
|
|
92
|
+
if (cellIndex >= 0) {
|
|
93
|
+
element.setAttribute('slot', `cell-${cellIndex}`);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
element.removeAttribute('slot');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** Applies the sizes of the columns and of the rows to the Grid and to the splitters. */
|
|
101
|
+
applySizes() {
|
|
102
|
+
const grid = this.grid;
|
|
103
|
+
if (!grid) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const { columns, rows } = this.layout.sizes;
|
|
107
|
+
grid.style.gridTemplateColumns = columns.map((size) => `${size}%`).join(' ');
|
|
108
|
+
grid.style.gridTemplateRows = rows.map((size) => `${size}%`).join(' ');
|
|
109
|
+
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);
|
|
112
|
+
this.applySplitterPosition(splitter);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
applySplitterPosition(splitter) {
|
|
116
|
+
const element = this.getById(this.getSplitterId(splitter));
|
|
117
|
+
if (!element) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// When the Layout changes, uHtml reuses the Elements of the previous splitters, which can have
|
|
121
|
+
// the other orientation. The position of the other orientation has therefore to be removed,
|
|
122
|
+
// otherwise it would override the size the splitter gets from its stylesheet.
|
|
123
|
+
const isVertical = splitter.orientation === 'vertical';
|
|
124
|
+
element.style.left = isVertical ? `${splitter.position}%` : '';
|
|
125
|
+
element.style.top = isVertical ? '' : `${splitter.position}%`;
|
|
126
|
+
}
|
|
127
|
+
getSplitterId(splitter) {
|
|
128
|
+
return `splitter-${splitter.orientation}-${splitter.index}`;
|
|
129
|
+
}
|
|
130
|
+
onSplitterPointerDown(event, splitter) {
|
|
131
|
+
event.preventDefault();
|
|
132
|
+
this.draggedSplitter = splitter;
|
|
133
|
+
event.target.setPointerCapture(event.pointerId);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Resizes the two columns (or the two rows) around the dragged splitter.
|
|
137
|
+
* While dragging, the new sizes are only applied to the DOM: the state is updated
|
|
138
|
+
* at the end of the drag, to prevent flooding it with intermediate values.
|
|
139
|
+
*/
|
|
140
|
+
onSplitterPointerMove(event, splitter) {
|
|
141
|
+
if (this.draggedSplitter !== splitter) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const gridBounds = this.grid.getBoundingClientRect();
|
|
145
|
+
const isVertical = splitter.orientation === 'vertical';
|
|
146
|
+
const position = isVertical
|
|
147
|
+
? ((event.clientX - gridBounds.left) / gridBounds.width) * 100
|
|
148
|
+
: ((event.clientY - gridBounds.top) / gridBounds.height) * 100;
|
|
149
|
+
const sizes = this.getResizedSizes(splitter, position);
|
|
150
|
+
if (!sizes) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
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
|
+
}
|
|
162
|
+
}
|
|
163
|
+
onSplitterPointerUp(event, splitter) {
|
|
164
|
+
if (this.draggedSplitter !== splitter) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
this.draggedSplitter = null;
|
|
168
|
+
event.target.releasePointerCapture(event.pointerId);
|
|
169
|
+
const sizes = this.getResizedSizes(splitter, splitter.position);
|
|
170
|
+
if (sizes) {
|
|
171
|
+
if (splitter.orientation === 'vertical') {
|
|
172
|
+
this.layout.sizes.columns = sizes;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
this.layout.sizes.rows = sizes;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Computes the new sizes of the columns (or of the rows) when the splitter is moved
|
|
181
|
+
* to the given position, in percent of the Grid. The two resized columns (or rows) are
|
|
182
|
+
* kept between MIN_CELL_SIZE and MAX_CELL_SIZE, the other ones keep their size.
|
|
183
|
+
*/
|
|
184
|
+
getResizedSizes(splitter, position) {
|
|
185
|
+
const sizes = [
|
|
186
|
+
...(splitter.orientation === 'vertical' ? this.layout.sizes.columns : this.layout.sizes.rows)
|
|
187
|
+
];
|
|
188
|
+
const before = splitter.index;
|
|
189
|
+
const after = splitter.index + 1;
|
|
190
|
+
// Size the splitter can be dragged in, without taking the columns/rows that are not resized into account.
|
|
191
|
+
const offset = sizes.slice(0, before).reduce((sum, size) => sum + size, 0);
|
|
192
|
+
const available = sizes[before] + sizes[after];
|
|
193
|
+
let newSizeBefore = position - offset;
|
|
194
|
+
newSizeBefore = Math.max(MIN_CELL_SIZE, Math.min(MAX_CELL_SIZE, newSizeBefore));
|
|
195
|
+
newSizeBefore = Math.min(newSizeBefore, available - MIN_CELL_SIZE);
|
|
196
|
+
newSizeBefore = Math.max(newSizeBefore, available - MAX_CELL_SIZE);
|
|
197
|
+
sizes[before] = newSizeBefore;
|
|
198
|
+
sizes[after] = available - newSizeBefore;
|
|
199
|
+
return sizes;
|
|
200
|
+
}
|
|
201
|
+
render() {
|
|
202
|
+
super.render();
|
|
203
|
+
super.girafeTranslate();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
export default MapGridComponent;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: Apache-2.0 */
|
|
2
|
+
#grid {
|
|
3
|
+
display: grid;
|
|
4
|
+
position: relative;
|
|
5
|
+
width: 100%;
|
|
6
|
+
height: 100%;
|
|
7
|
+
overflow: hidden;
|
|
8
|
+
background-color: var(--bkg-color);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.cell {
|
|
12
|
+
position: relative;
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
/* Prevents the Components from growing the Cells over the sizes defined by the Grid. */
|
|
15
|
+
min-width: 0;
|
|
16
|
+
min-height: 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
::slotted(*) {
|
|
20
|
+
display: block;
|
|
21
|
+
width: 100%;
|
|
22
|
+
height: 100%;
|
|
23
|
+
}
|
|
24
|
+
|
|
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. */
|
|
28
|
+
.splitter {
|
|
29
|
+
position: absolute;
|
|
30
|
+
z-index: 100;
|
|
31
|
+
background: var(--bkg-color);
|
|
32
|
+
border: var(--app-standard-border);
|
|
33
|
+
border-radius: 1rem;
|
|
34
|
+
touch-action: none;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.splitter-vertical {
|
|
38
|
+
top: 2rem;
|
|
39
|
+
bottom: 2rem;
|
|
40
|
+
width: 6px;
|
|
41
|
+
/* Centers the splitter on the boundary, whatever its borders add to its width. */
|
|
42
|
+
transform: translateX(-50%);
|
|
43
|
+
cursor: col-resize;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.splitter-horizontal {
|
|
47
|
+
left: 2rem;
|
|
48
|
+
right: 2rem;
|
|
49
|
+
height: 6px;
|
|
50
|
+
transform: translateY(-50%);
|
|
51
|
+
cursor: row-resize;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* 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. */
|
|
56
|
+
.splitter::before,
|
|
57
|
+
.splitter::after {
|
|
58
|
+
content: '';
|
|
59
|
+
position: absolute;
|
|
60
|
+
background: var(--app-color-grey);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.splitter-vertical::before,
|
|
64
|
+
.splitter-vertical::after {
|
|
65
|
+
left: 50%;
|
|
66
|
+
width: 1px;
|
|
67
|
+
height: 2rem;
|
|
68
|
+
transform: translateX(-50%);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* 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. */
|
|
73
|
+
.splitter-vertical::before {
|
|
74
|
+
top: calc(-2rem - 1px);
|
|
75
|
+
height: calc(2rem + 1px);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.splitter-vertical::after {
|
|
79
|
+
bottom: calc(-2rem - 1px);
|
|
80
|
+
height: calc(2rem + 1px);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.splitter-horizontal::before,
|
|
84
|
+
.splitter-horizontal::after {
|
|
85
|
+
top: 50%;
|
|
86
|
+
height: 1px;
|
|
87
|
+
width: 2rem;
|
|
88
|
+
transform: translateY(-50%);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.splitter-horizontal::before {
|
|
92
|
+
left: calc(-2rem - 1px);
|
|
93
|
+
width: calc(2rem + 1px);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.splitter-horizontal::after {
|
|
97
|
+
right: calc(-2rem - 1px);
|
|
98
|
+
width: calc(2rem + 1px);
|
|
99
|
+
}
|
|
@@ -18,7 +18,9 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
|
|
|
18
18
|
super('mobile-3d-button');
|
|
19
19
|
}
|
|
20
20
|
onPointerUp() {
|
|
21
|
-
|
|
21
|
+
// On mobile, the Grid only has one Cell, in which the 2D and the 3D Map are exchanged.
|
|
22
|
+
const cells = this.state.layout.cells;
|
|
23
|
+
this.state.layout.cells = [cells[0] === 'map-3d' ? 'map-2d' : 'map-3d'];
|
|
22
24
|
}
|
|
23
25
|
connectedCallback() {
|
|
24
26
|
super.connectedCallback();
|
package/package.json
CHANGED
package/styles/iframe.css
CHANGED
|
@@ -39,27 +39,21 @@ body {
|
|
|
39
39
|
overflow-y: hidden;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
girafe-map-grid {
|
|
43
|
+
display: block;
|
|
43
44
|
position: relative;
|
|
44
45
|
width: 100%;
|
|
45
46
|
height: 100%;
|
|
46
47
|
overflow: hidden;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
girafe-map-2d
|
|
50
|
+
girafe-map-2d,
|
|
51
|
+
girafe-map-3d {
|
|
50
52
|
display: block;
|
|
51
53
|
position: relative;
|
|
52
54
|
width: 100%;
|
|
53
55
|
height: 100%;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
girafe-map-3d {
|
|
57
|
-
display: none;
|
|
58
|
-
position: absolute;
|
|
59
|
-
top: 0;
|
|
60
|
-
height: 100%;
|
|
61
56
|
overflow: hidden;
|
|
62
|
-
border-left: solid 3px #444;
|
|
63
57
|
}
|
|
64
58
|
|
|
65
59
|
girafe-basemap {
|
package/styles/index.css
CHANGED
|
@@ -190,27 +190,21 @@ girafe-lr-panel > * {
|
|
|
190
190
|
height: 100%;
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
|
|
193
|
+
girafe-map-grid {
|
|
194
|
+
display: block;
|
|
194
195
|
position: relative;
|
|
195
196
|
width: 100%;
|
|
196
197
|
flex-grow: 1;
|
|
197
198
|
overflow: hidden;
|
|
198
199
|
}
|
|
199
200
|
|
|
200
|
-
girafe-map-2d
|
|
201
|
+
girafe-map-2d,
|
|
202
|
+
girafe-map-3d {
|
|
201
203
|
display: block;
|
|
202
204
|
position: relative;
|
|
203
205
|
width: 100%;
|
|
204
206
|
height: 100%;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
girafe-map-3d {
|
|
208
|
-
display: none;
|
|
209
|
-
position: absolute;
|
|
210
|
-
top: 0;
|
|
211
|
-
height: 100%;
|
|
212
207
|
overflow: hidden;
|
|
213
|
-
border-left: solid 3px #444;
|
|
214
208
|
}
|
|
215
209
|
|
|
216
210
|
.info-area {
|
package/styles/mobile.css
CHANGED
|
@@ -91,27 +91,21 @@ girafe-search {
|
|
|
91
91
|
right: 0;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
girafe-map-grid {
|
|
95
|
+
display: block;
|
|
95
96
|
position: relative;
|
|
96
97
|
width: 100%;
|
|
97
98
|
height: 100%;
|
|
98
99
|
overflow: hidden;
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
girafe-map-2d
|
|
102
|
+
girafe-map-2d,
|
|
103
|
+
girafe-map-3d {
|
|
102
104
|
display: block;
|
|
103
105
|
position: relative;
|
|
104
106
|
width: 100%;
|
|
105
107
|
height: 100%;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
girafe-map-3d {
|
|
109
|
-
display: none;
|
|
110
|
-
position: absolute;
|
|
111
|
-
top: 0;
|
|
112
|
-
height: 100%;
|
|
113
108
|
overflow: hidden;
|
|
114
|
-
border-left: solid 3px #444;
|
|
115
109
|
}
|
|
116
110
|
|
|
117
111
|
girafe-infobox {
|
package/templates/iframe.html
CHANGED
|
@@ -111,10 +111,10 @@
|
|
|
111
111
|
|
|
112
112
|
<div id="content">
|
|
113
113
|
<div id="main">
|
|
114
|
-
<
|
|
114
|
+
<girafe-map-grid>
|
|
115
115
|
<girafe-map-2d></girafe-map-2d>
|
|
116
116
|
<girafe-map-3d></girafe-map-3d>
|
|
117
|
-
</
|
|
117
|
+
</girafe-map-grid>
|
|
118
118
|
<girafe-basemap></girafe-basemap>
|
|
119
119
|
<girafe-search id="search"></girafe-search>
|
|
120
120
|
</div>
|
package/templates/index.html
CHANGED
|
@@ -140,10 +140,10 @@
|
|
|
140
140
|
<girafe-tree-view slot="main"></girafe-tree-view>
|
|
141
141
|
</girafe-lr-panel>
|
|
142
142
|
<div id="content-map">
|
|
143
|
-
<
|
|
143
|
+
<girafe-map-grid>
|
|
144
144
|
<girafe-map-2d></girafe-map-2d>
|
|
145
145
|
<girafe-map-3d></girafe-map-3d>
|
|
146
|
-
</
|
|
146
|
+
</girafe-map-grid>
|
|
147
147
|
<div class="info-area">
|
|
148
148
|
<girafe-basemap data-tour="basemap-selection"></girafe-basemap>
|
|
149
149
|
<girafe-infobox></girafe-infobox>
|
package/templates/mobile.html
CHANGED
|
@@ -92,10 +92,10 @@
|
|
|
92
92
|
<girafe-search></girafe-search>
|
|
93
93
|
<girafe-modals></girafe-modals>
|
|
94
94
|
<div id="content">
|
|
95
|
-
<
|
|
95
|
+
<girafe-map-grid>
|
|
96
96
|
<girafe-map-2d></girafe-map-2d>
|
|
97
97
|
<girafe-map-3d></girafe-map-3d>
|
|
98
|
-
</
|
|
98
|
+
</girafe-map-grid>
|
|
99
99
|
<girafe-infobox></girafe-infobox>
|
|
100
100
|
</div>
|
|
101
101
|
<girafe-swipe-up-panel>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.2.0-dev.
|
|
1
|
+
{"version":"1.2.0-dev.2844989778", "build":"2844989778", "date":"13/09/2026"}
|
|
@@ -11,6 +11,7 @@ import InfoboxComponent from '../../components/infobox/component.js';
|
|
|
11
11
|
import Map2dComponent from '../../components/map-2d/component.js';
|
|
12
12
|
import DeprecatedMapComponent from '../../components/map-2d/deprecatedmapcomponent.js';
|
|
13
13
|
import Map3dComponent from '../../components/map-3d/component.js';
|
|
14
|
+
import MapGridComponent from '../../components/map-grid/component.js';
|
|
14
15
|
import MenuMobile3dButton from '../../components/menu-buttons-mobile/MenuMobile3dButton.js';
|
|
15
16
|
import MenuMobileDrawingButton from '../../components/menu-buttons-mobile/MenuMobileDrawingButton.js';
|
|
16
17
|
import MenuMobileOfflineButton from '../../components/menu-buttons-mobile/MenuMobileOfflineButton.js';
|
|
@@ -48,6 +49,7 @@ export default class GeoGirafeAppMobile extends GeoGirafeApp {
|
|
|
48
49
|
customElements.define('girafe-infobox', InfoboxComponent);
|
|
49
50
|
customElements.define('girafe-map-2d', Map2dComponent);
|
|
50
51
|
customElements.define('girafe-map-3d', Map3dComponent);
|
|
52
|
+
customElements.define('girafe-map-grid', MapGridComponent);
|
|
51
53
|
// Deprecated alias of <girafe-map-2d>, kept for the applications that were not migrated yet.
|
|
52
54
|
customElements.define('girafe-map', DeprecatedMapComponent);
|
|
53
55
|
customElements.define('girafe-search', MobileSearchComponent);
|
|
@@ -20,6 +20,7 @@ import LRPanelComponent from '../../components/lr-panel/component.js';
|
|
|
20
20
|
import Map2dComponent from '../../components/map-2d/component.js';
|
|
21
21
|
import DeprecatedMapComponent from '../../components/map-2d/deprecatedmapcomponent.js';
|
|
22
22
|
import Map3dComponent from '../../components/map-3d/component.js';
|
|
23
|
+
import MapGridComponent from '../../components/map-grid/component.js';
|
|
23
24
|
import MenuButtonComponent from '../../components/menubutton/component.js';
|
|
24
25
|
import ModalsComponent from '../../components/modals/component.js';
|
|
25
26
|
import NavigationComponent from '../../components/navigation/component.js';
|
|
@@ -121,6 +122,7 @@ export default class GeoGirafeApp {
|
|
|
121
122
|
customElements.define('girafe-lr-panel', LRPanelComponent);
|
|
122
123
|
customElements.define('girafe-map-2d', Map2dComponent);
|
|
123
124
|
customElements.define('girafe-map-3d', Map3dComponent);
|
|
125
|
+
customElements.define('girafe-map-grid', MapGridComponent);
|
|
124
126
|
// Deprecated alias of <girafe-map-2d>, kept for the applications that were not migrated yet.
|
|
125
127
|
customElements.define('girafe-map', DeprecatedMapComponent);
|
|
126
128
|
customElements.define('girafe-menu-button', MenuButtonComponent);
|
|
@@ -3,6 +3,7 @@ export declare enum HighlightMode {
|
|
|
3
3
|
HighlightPerLayer = "highlight-per-layer"
|
|
4
4
|
}
|
|
5
5
|
import { Config as SanitizeConfig } from 'dompurify';
|
|
6
|
+
import { LayoutType } from '../state/layout.js';
|
|
6
7
|
declare class GirafeConfig {
|
|
7
8
|
general: {
|
|
8
9
|
locale: string;
|
|
@@ -20,6 +21,8 @@ declare class GirafeConfig {
|
|
|
20
21
|
defaultSelectionComponent: string;
|
|
21
22
|
darkFrontendMode: boolean | undefined;
|
|
22
23
|
darkMapMode: boolean;
|
|
24
|
+
layouts: LayoutType[];
|
|
25
|
+
defaultLayout: LayoutType;
|
|
23
26
|
};
|
|
24
27
|
themes: {
|
|
25
28
|
url: string;
|
|
@@ -315,6 +318,7 @@ declare class GirafeConfig {
|
|
|
315
318
|
private initConfigThemes;
|
|
316
319
|
private initConfigLanguages;
|
|
317
320
|
private initConfigInterface;
|
|
321
|
+
private initConfigLayouts;
|
|
318
322
|
private initConfigGeneral;
|
|
319
323
|
private initGmfOauth;
|
|
320
324
|
private initConfigOauth;
|
|
@@ -5,6 +5,7 @@ export var HighlightMode;
|
|
|
5
5
|
HighlightMode["HighlightPerLayer"] = "highlight-per-layer";
|
|
6
6
|
})(HighlightMode || (HighlightMode = {}));
|
|
7
7
|
import { defaultNewsSanitizeConfig, defaultPermalinkSanitizeConfig, defaultQuerySanitizeConfig } from './sanitizeconfig-defaults.js';
|
|
8
|
+
import { isLayoutType, LAYOUT_TYPES } from '../state/layout.js';
|
|
8
9
|
class GirafeConfig {
|
|
9
10
|
general;
|
|
10
11
|
languages;
|
|
@@ -348,12 +349,29 @@ class GirafeConfig {
|
|
|
348
349
|
const defaultConfig = {
|
|
349
350
|
defaultSelectionComponent: 'window',
|
|
350
351
|
darkFrontendMode: undefined,
|
|
351
|
-
darkMapMode: false
|
|
352
|
+
darkMapMode: false,
|
|
353
|
+
layouts: [...LAYOUT_TYPES],
|
|
354
|
+
defaultLayout: LAYOUT_TYPES[0]
|
|
352
355
|
};
|
|
353
|
-
|
|
356
|
+
const interfaceConfig = {
|
|
354
357
|
...defaultConfig,
|
|
355
358
|
...config.interface
|
|
356
359
|
};
|
|
360
|
+
interfaceConfig.layouts = this.initConfigLayouts(interfaceConfig.layouts);
|
|
361
|
+
if (!interfaceConfig.layouts.includes(interfaceConfig.defaultLayout)) {
|
|
362
|
+
interfaceConfig.defaultLayout = interfaceConfig.layouts[0];
|
|
363
|
+
}
|
|
364
|
+
return interfaceConfig;
|
|
365
|
+
}
|
|
366
|
+
initConfigLayouts(layouts) {
|
|
367
|
+
const validLayouts = layouts.filter((layout) => {
|
|
368
|
+
if (isLayoutType(layout)) {
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
console.warn(`${layout} is not a valid layout and will be ignored. Valid layouts are: ${LAYOUT_TYPES.join(', ')}.`);
|
|
372
|
+
return false;
|
|
373
|
+
});
|
|
374
|
+
return validLayouts.length > 0 ? validLayouts : [...LAYOUT_TYPES];
|
|
357
375
|
}
|
|
358
376
|
initConfigGeneral(config) {
|
|
359
377
|
return {
|
package/tools/main.d.ts
CHANGED
|
@@ -73,6 +73,7 @@ export { default as CustomLayersSerializer } from './share/serializers/customlay
|
|
|
73
73
|
export { default as GlobeSerializer } from './share/serializers/globeserializer.js';
|
|
74
74
|
export { default as InterfaceSerializer } from './share/serializers/interfaceserializer.js';
|
|
75
75
|
export { default as LayersConfigSerializer } from './share/serializers/layerconfigserializer.js';
|
|
76
|
+
export { default as LayoutSerializer } from './share/serializers/layoutserializer.js';
|
|
76
77
|
export { default as MapPositionSerializer } from './share/serializers/mappositionserializer.js';
|
|
77
78
|
export type { SharedInitialSelection } from './share/serializers/selectionserializer.js';
|
|
78
79
|
export { default as SelectionSerializer } from './share/serializers/selectionserializer.js';
|
|
@@ -97,6 +98,8 @@ export { default as GraphicalInterface } from './state/graphicalInterface.js';
|
|
|
97
98
|
export type { default as IGirafePanel } from './state/igirafepanel.js';
|
|
98
99
|
export { isGirafePanel } from './state/igirafepanel.js';
|
|
99
100
|
export { default as LayersConfig } from './state/layersConfig.js';
|
|
101
|
+
export type { LayoutType, CellComponent, CellContent } from './state/layout.js';
|
|
102
|
+
export { default as LayoutState, LAYOUT_TYPES, MIN_CELL_SIZE, MAX_CELL_SIZE, AVAILABLE_CELL_COMPONENTS, isLayoutType, isCellComponent, getGridSize, getDefaultSizes, distributeCells } from './state/layout.js';
|
|
100
103
|
export { default as MapManager } from './state/mapManager.js';
|
|
101
104
|
export type { MapMarkerTooltip, MapMarker } from './state/mapposition.js';
|
|
102
105
|
export { default as MapPosition } from './state/mapposition.js';
|
package/tools/main.js
CHANGED
|
@@ -57,6 +57,7 @@ export { default as CustomLayersSerializer } from './share/serializers/customlay
|
|
|
57
57
|
export { default as GlobeSerializer } from './share/serializers/globeserializer.js';
|
|
58
58
|
export { default as InterfaceSerializer } from './share/serializers/interfaceserializer.js';
|
|
59
59
|
export { default as LayersConfigSerializer } from './share/serializers/layerconfigserializer.js';
|
|
60
|
+
export { default as LayoutSerializer } from './share/serializers/layoutserializer.js';
|
|
60
61
|
export { default as MapPositionSerializer } from './share/serializers/mappositionserializer.js';
|
|
61
62
|
export { default as SelectionSerializer } from './share/serializers/selectionserializer.js';
|
|
62
63
|
export { serializeWfsFilter, deserializeWfsFilter } from './share/serializers/wfsfilterserializer.js';
|
|
@@ -73,6 +74,7 @@ export { default as GlobeState } from './state/globe.js';
|
|
|
73
74
|
export { default as GraphicalInterface } from './state/graphicalInterface.js';
|
|
74
75
|
export { isGirafePanel } from './state/igirafepanel.js';
|
|
75
76
|
export { default as LayersConfig } from './state/layersConfig.js';
|
|
77
|
+
export { default as LayoutState, LAYOUT_TYPES, MIN_CELL_SIZE, MAX_CELL_SIZE, AVAILABLE_CELL_COMPONENTS, isLayoutType, isCellComponent, getGridSize, getDefaultSizes, distributeCells } from './state/layout.js';
|
|
76
78
|
export { default as MapManager } from './state/mapManager.js';
|
|
77
79
|
export { default as MapPosition } from './state/mapposition.js';
|
|
78
80
|
export { default as ObjectSelection } from './state/objectselection.js';
|
|
@@ -7,4 +7,6 @@ export default class GlobeSerializer implements IBrainSerializer<GlobeState> {
|
|
|
7
7
|
private get globe();
|
|
8
8
|
brainSerialize(globe: GlobeState): string;
|
|
9
9
|
brainDeserialize(str: string): void;
|
|
10
|
+
/** Applies the layout a display mode of the previous versions corresponds to. */
|
|
11
|
+
private deserializeLegacyDisplay;
|
|
10
12
|
}
|