@geogirafe/lib-geoportal 1.2.0-dev.2872463389 → 1.2.0-dev.2889263072

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 (121) hide show
  1. package/api/apicontext.d.ts +12 -1
  2. package/api/apicontext.js +19 -111
  3. package/api/apisessionmanager.d.ts +1 -1
  4. package/api/apisessionmanager.js +1 -1
  5. package/base/GirafeDraggableElement.js +2 -2
  6. package/base/GirafeResizableElement.js +4 -4
  7. package/base/GirafeSingleton.d.ts +1 -1
  8. package/base/GirafeSingleton.js +1 -1
  9. package/components/basemap/component.js +1 -1
  10. package/components/cross-section/cross-section-settings/component.js +1 -1
  11. package/components/cross-section/scatterplot.js +1 -1
  12. package/components/drawing/component-mobile.js +1 -1
  13. package/components/drawing/component.js +4 -4
  14. package/components/drawing/fixed-dimension/component.js +1 -1
  15. package/components/edit/component.js +1 -1
  16. package/components/edit/editform/component.js +1 -1
  17. package/components/extlayers/component.js +1 -1
  18. package/components/map-2d/component.js +1 -1
  19. package/components/map-2d/tools/swipemanager.js +2 -2
  20. package/components/modals/component.js +1 -1
  21. package/components/navigation/component.d.ts +2 -2
  22. package/components/navigation/component.js +6 -6
  23. package/components/news/news-button/component.d.ts +0 -1
  24. package/components/news/news-button/component.js +0 -6
  25. package/components/news/news-panel/component.d.ts +2 -2
  26. package/components/news/news-panel/component.js +5 -5
  27. package/components/print/component.js +7 -7
  28. package/components/print/tools/MFPLegendEncoder.js +1 -1
  29. package/components/querybuilder/component.js +2 -2
  30. package/components/scale/component.js +1 -1
  31. package/components/selectionwindow/component.js +1 -1
  32. package/components/share/component.js +1 -1
  33. package/components/themes/component.d.ts +1 -1
  34. package/components/themes/component.js +4 -4
  35. package/components/themes-mobile/layer-selected/component.js +1 -1
  36. package/components/timerestriction/timepicker/component.js +1 -1
  37. package/components/timerestriction/timeslider/component.js +4 -4
  38. package/components/treeview/treeviewroot/component.js +1 -1
  39. package/components/userpreferences/component.d.ts +1 -1
  40. package/components/userpreferences/component.js +7 -7
  41. package/models/xmlTypes.js +1 -1
  42. package/package.json +1 -1
  43. package/templates/public/about.json +1 -1
  44. package/tools/app/lifecyclemanager.d.ts +1 -1
  45. package/tools/app/lifecyclemanager.js +1 -1
  46. package/tools/auth/abstractconnectmanager.js +2 -2
  47. package/tools/auth/authmanager.d.ts +1 -1
  48. package/tools/auth/authmanager.js +1 -1
  49. package/tools/auth/pluginmanager.d.ts +1 -1
  50. package/tools/auth/pluginmanager.js +1 -1
  51. package/tools/configuration/configmanager.d.ts +3 -2
  52. package/tools/configuration/configmanager.js +9 -5
  53. package/tools/configuration/girafeconfig.d.ts +6 -5
  54. package/tools/configuration/girafeconfig.js +3 -0
  55. package/tools/context/context.d.ts +1 -0
  56. package/tools/context/context.js +4 -112
  57. package/tools/context/contexthelper.d.ts +6 -0
  58. package/tools/context/contexthelper.js +102 -0
  59. package/tools/error/errormanager.d.ts +1 -1
  60. package/tools/error/errormanager.js +1 -1
  61. package/tools/feedback/feedbackmanager.d.ts +1 -2
  62. package/tools/feedback/feedbackmanager.js +6 -8
  63. package/tools/i18n/i18nmanager.d.ts +1 -1
  64. package/tools/i18n/i18nmanager.js +22 -20
  65. package/tools/layers/layermanager.d.ts +1 -1
  66. package/tools/layers/layermanager.js +1 -1
  67. package/tools/layers/snapmanager.d.ts +1 -1
  68. package/tools/layers/snapmanager.js +1 -1
  69. package/tools/localfile/localfilemanager.d.ts +1 -1
  70. package/tools/localfile/localfilemanager.js +1 -1
  71. package/tools/logging/defaultconsole.d.ts +8 -0
  72. package/tools/logging/defaultconsole.js +9 -0
  73. package/tools/logging/logmanager.d.ts +1 -7
  74. package/tools/logging/logmanager.js +8 -13
  75. package/tools/main.d.ts +3 -1
  76. package/tools/main.js +2 -0
  77. package/tools/offline/offlinemanager.d.ts +3 -3
  78. package/tools/offline/offlinemanager.js +6 -6
  79. package/tools/ogcapi/ogcapifeaturesmanager.d.ts +1 -1
  80. package/tools/ogcapi/ogcapifeaturesmanager.js +1 -1
  81. package/tools/onboarding/onboardingmanager.js +4 -4
  82. package/tools/ordering/orderingmanager.d.ts +1 -1
  83. package/tools/ordering/orderingmanager.js +1 -1
  84. package/tools/resizewindow.js +6 -6
  85. package/tools/search/abstractsearchclient.d.ts +1 -1
  86. package/tools/search/abstractsearchclient.js +2 -2
  87. package/tools/search/searchmanager.d.ts +1 -1
  88. package/tools/search/searchmanager.js +2 -2
  89. package/tools/selection/selectionmanager.d.ts +1 -1
  90. package/tools/selection/selectionmanager.js +1 -1
  91. package/tools/share/sessionmanager.d.ts +1 -1
  92. package/tools/share/sessionmanager.js +1 -1
  93. package/tools/share/sharemanager.d.ts +1 -1
  94. package/tools/share/sharemanager.js +1 -1
  95. package/tools/share/stateserializer.d.ts +1 -1
  96. package/tools/share/stateserializer.js +1 -1
  97. package/tools/state/statemanager.d.ts +1 -1
  98. package/tools/state/statemanager.js +1 -1
  99. package/tools/tests/mockconfig.d.ts +2 -3
  100. package/tools/tests/mockconfig.js +0 -1
  101. package/tools/tests/mockcontext.d.ts +13 -1
  102. package/tools/tests/mockcontext.js +27 -112
  103. package/tools/tests/mockhelper.d.ts +2 -2
  104. package/tools/tests/mockhelper.js +4 -5
  105. package/tools/themes/customthemesmanager.d.ts +3 -3
  106. package/tools/themes/customthemesmanager.js +9 -9
  107. package/tools/themes/themefavoritesmanager.d.ts +5 -5
  108. package/tools/themes/themefavoritesmanager.js +16 -16
  109. package/tools/themes/themeshelper.d.ts +1 -1
  110. package/tools/themes/themeshelper.js +1 -1
  111. package/tools/themes/themesmanager.d.ts +1 -1
  112. package/tools/themes/themesmanager.js +2 -2
  113. package/tools/time/layertimeformatter.js +1 -1
  114. package/tools/url/permalinkmanager.d.ts +1 -1
  115. package/tools/url/permalinkmanager.js +1 -1
  116. package/tools/userdata/userdatamanager.d.ts +18 -39
  117. package/tools/userdata/userdatamanager.js +111 -75
  118. package/tools/wfs/wfsmanager.d.ts +1 -1
  119. package/tools/wfs/wfsmanager.js +1 -1
  120. package/tools/wms/wmsmanager.d.ts +1 -1
  121. package/tools/wms/wmsmanager.js +1 -1
@@ -46,8 +46,8 @@ export default class GirafeApiContext implements IGirafeContext {
46
46
  readonly themesManager: ThemesManager;
47
47
  readonly themesHelper: ThemesHelper;
48
48
  readonly permalinkManager: PermalinkManager;
49
- readonly urlManager: UrlManager;
50
49
  readonly dragManager: DragManager;
50
+ readonly urlManager: UrlManager;
51
51
  readonly layerManager: LayerManager;
52
52
  readonly sessionManager: ApiSessionManager;
53
53
  readonly stateSerializer: StateSerializer;
@@ -72,6 +72,17 @@ export default class GirafeApiContext implements IGirafeContext {
72
72
  readonly themeFavoritesManager: ThemeFavoritesManager;
73
73
  readonly searchManager: SearchManager;
74
74
  readonly feedbackManager: FeedbackManager;
75
+ private readonly contextHelper;
75
76
  constructor();
76
77
  initialize(): Promise<void>;
78
+ /** The context can contains hook methods that will be called after each manager initialization.
79
+ * The hook method must be named with the property name + the "Hook" suffix.
80
+ * The hook method must be async and will be awaited
81
+ * For example, the methods themesManagerHook() will be called immediately after
82
+ * the call of themesManager.initializeSingleton().
83
+ * This allows specific initialization methods when defining custom contexts
84
+ * (for example for the apiContext, the mockContext, or any customContext)
85
+ */
86
+ protected configManagerHook(): Promise<void>;
87
+ protected stateManagerHook(): Promise<void>;
77
88
  }
package/api/apicontext.js CHANGED
@@ -1,41 +1,6 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- import ApplicationLifeCycleManager from '../tools/app/lifecyclemanager.js';
3
- import AuthManager from '../tools/auth/authmanager.js';
4
- import DragManager from '../components/treeview/tools/dragmanager.js';
5
- import PluginManager from '../tools/auth/pluginmanager.js';
6
- import ConfigManager from '../tools/configuration/configmanager.js';
7
- import ErrorManager from '../tools/error/errormanager.js';
8
- import I18nManager from '../tools/i18n/i18nmanager.js';
9
- import LayerManager from '../tools/layers/layermanager.js';
10
- import SnapManager from '../tools/layers/snapmanager.js';
11
- import LocalFileManager from '../tools/localfile/localfilemanager.js';
12
- import LogManager from '../tools/logging/logmanager.js';
13
- import OfflineManager from '../tools/offline/offlinemanager.js';
14
- import OgcApiFeaturesManager from '../tools/ogcapi/ogcapifeaturesmanager.js';
15
- import OrderingManager from '../tools/ordering/orderingmanager.js';
16
- import ShareManager from '../tools/share/sharemanager.js';
17
- import StateSerializer from '../tools/share/stateserializer.js';
18
- import ComponentManager from '../tools/state/componentManager.js';
19
- import MapManager from '../tools/state/mapManager.js';
20
- import StateManager from '../tools/state/statemanager.js';
21
- import UserInteractionManager from '../tools/state/userInteractionManager.js';
22
- import CustomThemesManager from '../tools/themes/customthemesmanager.js';
23
- import ThemesHelper from '../tools/themes/themeshelper.js';
24
- import ThemesManager from '../tools/themes/themesmanager.js';
25
- import UserLayerManager from '../tools/themes/userlayermanager.js';
26
- import PermalinkManager from '../tools/url/permalinkmanager.js';
27
- import UrlManager from '../tools/url/urlmanager.js';
28
- import UserDataManager from '../tools/userdata/userdatamanager.js';
29
- import WfsManager from '../tools/wfs/wfsmanager.js';
30
- import WmsManager from '../tools/wms/wmsmanager.js';
31
- import WmtsManager from '../tools/wmts/wmtsmanager.js';
32
- import SelectionManager from '../tools/selection/selectionmanager.js';
33
1
  import ApiSessionManager from './apisessionmanager.js';
34
- import OnBoardingManager from '../tools/onboarding/onboardingmanager.js';
35
- import ThemeFavoritesManager from '../tools/themes/themefavoritesmanager.js';
36
- import SearchManager from '../tools/search/searchmanager.js';
37
- import FeedbackManager from '../tools/feedback/feedbackmanager.js';
38
2
  import { updateConfigPaths } from './apiutils.js';
3
+ import ContextInitializationHelper from '../tools/context/contexthelper.js';
39
4
  export default class GirafeApiContext {
40
5
  userDataManager;
41
6
  configManager;
@@ -47,8 +12,8 @@ export default class GirafeApiContext {
47
12
  themesManager;
48
13
  themesHelper;
49
14
  permalinkManager;
50
- urlManager;
51
15
  dragManager;
16
+ urlManager;
52
17
  layerManager;
53
18
  sessionManager;
54
19
  stateSerializer;
@@ -73,87 +38,30 @@ export default class GirafeApiContext {
73
38
  themeFavoritesManager;
74
39
  searchManager;
75
40
  feedbackManager;
41
+ contextHelper = new ContextInitializationHelper();
76
42
  constructor() {
77
- this.componentManager = new ComponentManager(this);
78
- this.userDataManager = new UserDataManager(this);
79
- this.configManager = new ConfigManager(this);
80
- this.logManager = new LogManager(this);
81
- this.stateManager = new StateManager(this);
82
- this.orderingManager = new OrderingManager(this);
83
- this.applicationLifeCycleManager = new ApplicationLifeCycleManager(this);
84
- this.mapManager = new MapManager(this);
85
- this.offlineManager = new OfflineManager(this);
86
- this.snapManager = new SnapManager(this);
87
- this.authManager = new AuthManager(this);
88
- this.pluginManager = new PluginManager(this);
89
- this.i18nManager = new I18nManager(this);
90
- this.userInteractionManager = new UserInteractionManager(this);
91
- this.dragManager = new DragManager(this);
92
- this.urlManager = new UrlManager(this);
93
- this.permalinkManager = new PermalinkManager(this);
94
- this.layerManager = new LayerManager(this);
95
- this.userLayerManager = new UserLayerManager(this);
96
- this.themesHelper = new ThemesHelper(this);
97
- this.stateSerializer = new StateSerializer(this);
43
+ this.contextHelper.constructContext(this, ['sessionManager']);
98
44
  this.sessionManager = new ApiSessionManager(this);
99
- this.shareManager = new ShareManager(this);
100
- this.wfsManager = new WfsManager(this);
101
- this.errorManager = new ErrorManager(this);
102
- this.customThemesManager = new CustomThemesManager(this);
103
- this.themesManager = new ThemesManager(this);
104
- this.wmsManager = new WmsManager(this);
105
- this.wmtsManager = new WmtsManager(this);
106
- this.selectionManager = new SelectionManager(this);
107
- this.localFileManager = new LocalFileManager(this);
108
- this.ogcApiFeaturesManager = new OgcApiFeaturesManager(this);
109
- this.onBoardingManager = new OnBoardingManager(this);
110
- this.themeFavoritesManager = new ThemeFavoritesManager(this);
111
- this.searchManager = new SearchManager(this);
112
- this.feedbackManager = new FeedbackManager(this);
113
45
  }
114
46
  async initialize() {
115
- // NOTE : This initialization order is important, because some singleton will need other ones !
116
- this.componentManager.initializeSingleton();
117
- this.userDataManager.initializeSingleton();
118
- this.configManager.initializeSingleton();
119
- await this.configManager.loadConfig();
47
+ await this.contextHelper.initializeContext(this);
48
+ }
49
+ /** The context can contains hook methods that will be called after each manager initialization.
50
+ * The hook method must be named with the property name + the "Hook" suffix.
51
+ * The hook method must be async and will be awaited
52
+ * For example, the methods themesManagerHook() will be called immediately after
53
+ * the call of themesManager.initializeSingleton().
54
+ * This allows specific initialization methods when defining custom contexts
55
+ * (for example for the apiContext, the mockContext, or any customContext)
56
+ */
57
+ async configManagerHook() {
58
+ await this.configManager.initializeSingleton();
120
59
  // For the API context, some configuraton options in the config file can be relative.
121
60
  // We have to convert them to absolute paths.
122
61
  updateConfigPaths(this.configManager.Config);
123
- this.logManager.initializeSingleton();
124
- this.logManager.initLogging();
125
- this.stateManager.initializeSingleton();
62
+ }
63
+ async stateManagerHook() {
64
+ await this.stateManager.initializeSingleton();
126
65
  this.stateManager.state.interface.isApi = true;
127
- this.orderingManager.initializeSingleton();
128
- this.applicationLifeCycleManager.initializeSingleton();
129
- this.mapManager.initializeSingleton();
130
- this.offlineManager.initializeSingleton();
131
- this.snapManager.initializeSingleton();
132
- this.authManager.initializeSingleton();
133
- this.pluginManager.initializeSingleton();
134
- this.i18nManager.initializeSingleton();
135
- this.userInteractionManager.initializeSingleton();
136
- this.dragManager.initializeSingleton();
137
- this.urlManager.initializeSingleton();
138
- this.permalinkManager.initializeSingleton();
139
- this.layerManager.initializeSingleton();
140
- this.userLayerManager.initializeSingleton();
141
- this.themesHelper.initializeSingleton();
142
- this.stateSerializer.initializeSingleton();
143
- this.sessionManager.initializeSingleton();
144
- this.shareManager.initializeSingleton();
145
- this.wfsManager.initializeSingleton();
146
- this.errorManager.initializeSingleton();
147
- this.customThemesManager.initializeSingleton();
148
- this.themesManager.initializeSingleton();
149
- this.wmsManager.initializeSingleton();
150
- this.wmtsManager.initializeSingleton();
151
- this.localFileManager.initializeSingleton();
152
- this.selectionManager.initializeSingleton();
153
- this.ogcApiFeaturesManager.initializeSingleton();
154
- this.onBoardingManager.initializeSingleton();
155
- this.themeFavoritesManager.initializeSingleton();
156
- this.searchManager.initializeSingleton();
157
- this.feedbackManager.initializeSingleton();
158
66
  }
159
67
  }
@@ -1,7 +1,7 @@
1
1
  import GirafeSingleton from '../base/GirafeSingleton.js';
2
2
  import ISessionManager from '../tools/share/isessionmanager.js';
3
3
  declare class ApiSessionManager extends GirafeSingleton implements ISessionManager {
4
- initializeSingleton(): void;
4
+ initializeSingleton(): Promise<void>;
5
5
  beginSession(): void;
6
6
  saveStateToSession(): void;
7
7
  hasState(): boolean;
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  import GirafeSingleton from '../base/GirafeSingleton.js';
3
3
  class ApiSessionManager extends GirafeSingleton {
4
- initializeSingleton() {
4
+ async initializeSingleton() {
5
5
  /* For the API: Do Nothing */
6
6
  }
7
7
  beginSession() {
@@ -112,10 +112,10 @@ class GirafeDraggableElement extends GirafeHTMLElement {
112
112
  const height = this.getContainerHeight();
113
113
  const left = _this.host.style.left;
114
114
  const top = _this.host.style.top;
115
- if (parseInt(left) + _this.host.offsetWidth > width) {
115
+ if (Number.parseInt(left) + _this.host.offsetWidth > width) {
116
116
  _this.host.style.left = width - _this.host.offsetWidth + 'px';
117
117
  }
118
- if (parseInt(top) + _this.host.offsetHeight > height) {
118
+ if (Number.parseInt(top) + _this.host.offsetHeight > height) {
119
119
  _this.host.style.top = height - _this.host.offsetHeight + 'px';
120
120
  }
121
121
  }
@@ -66,13 +66,13 @@ class GirafeResizableElement extends GirafeHTMLElement {
66
66
  // If there is a configured minWidth or maxWidth, we have to take care of it
67
67
  const css = getComputedStyle(this);
68
68
  const minWidth = Number.parseFloat(css.minWidth);
69
- this.minWidth = isNaN(minWidth) ? undefined : minWidth;
69
+ this.minWidth = Number.isNaN(minWidth) ? undefined : minWidth;
70
70
  const maxWidth = Number.parseFloat(css.maxWidth);
71
- this.maxWidth = isNaN(maxWidth) ? undefined : maxWidth;
71
+ this.maxWidth = Number.isNaN(maxWidth) ? undefined : maxWidth;
72
72
  const minHeight = Number.parseFloat(css.minHeight);
73
- this.minHeight = isNaN(minHeight) ? undefined : minHeight;
73
+ this.minHeight = Number.isNaN(minHeight) ? undefined : minHeight;
74
74
  const maxHeight = Number.parseFloat(css.maxHeight);
75
- this.maxHeight = isNaN(maxHeight) ? undefined : maxHeight;
75
+ this.maxHeight = Number.isNaN(maxHeight) ? undefined : maxHeight;
76
76
  }
77
77
  mousedown(e) {
78
78
  e.preventDefault();
@@ -2,7 +2,7 @@ import IGirafeContext from '../tools/context/icontext.js';
2
2
  declare class GirafeSingleton {
3
3
  protected readonly context: IGirafeContext;
4
4
  constructor(context: IGirafeContext);
5
- initializeSingleton(): void;
5
+ initializeSingleton(): Promise<void>;
6
6
  protected isNullOrUndefinedOrBlank(val: unknown): val is "" | null | undefined;
7
7
  }
8
8
  export default GirafeSingleton;
@@ -4,7 +4,7 @@ class GirafeSingleton {
4
4
  constructor(context) {
5
5
  this.context = context;
6
6
  }
7
- initializeSingleton() {
7
+ async initializeSingleton() {
8
8
  /*
9
9
  * This method does nothing by default, but will be called by the context when creating the singleton
10
10
  * (This prevent overriding the constructor in every songleton and creating a strong dependency to the context object)
@@ -13,7 +13,7 @@ class BasemapComponent extends GirafeHTMLElement {
13
13
  .basemap-button{background-color:var(--bkg-color);color:var(--text-color);border:var(--app-standard-border);box-sizing:border-box;cursor:pointer;box-shadow:var(--bx-shdw);border-width:2px;border-radius:4px;outline:0;margin:0;padding:5px;display:inline-flex}#container{flex-direction:column;width:max-content;display:inline-flex}.basemap-icon{background:var(--svg-legend-bkg);filter:var(--svg-map-filter)}.active-basemap{background-color:var(--bkg-color-grad2)!important}.opacity-slider{flex-direction:row;height:20px;padding:0 5px;display:flex}.opacity-slider img{height:16px;position:relative;top:2px}.opacity-slider input[type=range]{flex-grow:1}.basemap-container{flex-direction:column;display:flex}.basemap-container button{color:var(--text-color);background-color:#0000;border:none;flex-direction:row;flex-grow:1;display:flex}.basemap-container button img{height:2rem;margin:.3rem}.basemap-container button span{height:2rem;margin:.3rem;line-height:2rem}.basemap-container:hover{background-color:var(--bkg-color-grad1)}.basemaps-with-opacity-separator{border-top:1px solid var(--bkg-color-grad2);height:1px;margin:6px 8px}
14
14
  </style>
15
15
  <style>${this.customStyle}</style>
16
- <girafe-menu-button direction="up-right" id="basemap-menu-button"><button slot="menu-button" class="basemap-button" tip="Select basemap"><img class="basemap-icon" alt="menu-icon" src="images/button_basemaps.webp"></button><div slot="menu-content" id="container">${Object.values(this.state.basemaps).filter(basemap => basemap.opacity == -1).map(basemap => uHtmlFor(basemap, basemap.id) `<div class="${this.determineClassnames(basemap)}"><button onclick="${() => this.changeBasemap(basemap)}"><img class="basemap-icon" alt="basemap-icon" src="${basemap.thumbnail}"> <span i18n="${basemap.name}">${basemap.name}</span></button></div>`)}<div class="basemaps-with-opacity-separator"></div>${Object.values(this.state.basemaps).filter(basemap => basemap.opacity != -1).map(basemap => uHtmlFor(basemap, basemap.id) `<div class="${this.determineClassnames(basemap)}"><button onclick="${() => this.changeBasemap(basemap)}"><img class="basemap-icon" alt="basemap-icon" src="${basemap.thumbnail}"> <span i18n="${basemap.name}">${basemap.name}</span></button><div class="opacity-slider"><img alt="Control opacity" src="icons/opacity.svg"> <input type="range" min="0" max="1" step="0.01" class="gg-range" value="${basemap.opacity}" onkeydown="${(e) => { e.preventDefault(); e.stopPropagation(); }}" onclick="${(e) => { e.preventDefault(); e.stopPropagation(); }}" onchange="${(e) => this.changeBasemapOpacity(basemap, e)}"></div></div>`)}</div></girafe-menu-button>
16
+ <girafe-menu-button direction="up-right" id="basemap-menu-button"><button slot="menu-button" class="basemap-button" tip="Select basemap"><img class="basemap-icon" alt="menu-icon" src="images/button_basemaps.webp"></button><div slot="menu-content" id="container">${Object.values(this.state.basemaps).filter(basemap => basemap.opacity == -1).map(basemap => uHtmlFor(basemap, basemap.id) `<div class="${this.determineClassnames(basemap)}"><button onclick="${() => this.changeBasemap(basemap)}"><img class="basemap-icon" alt="basemap-icon" src="${basemap.thumbnail}"> <span i18n="${basemap.name}">${basemap.name}</span></button></div>`)}<div class="basemaps-with-opacity-separator"></div>${Object.values(this.state.basemaps).filter(basemap => basemap.opacity != -1).map(basemap => uHtmlFor(basemap, basemap.id) `<div class="${this.determineClassnames(basemap)}"><button onclick="${() => this.changeBasemap(basemap)}"><img class="basemap-icon" alt="basemap-icon" src="${basemap.thumbnail}"> <span i18n="${basemap.name}">${basemap.name}</span></button><div class="opacity-slider"><img alt="Control opacity" src="icons/opacity.svg"> <input type="range" aria-label="Opacity" min="0" max="1" step="0.01" class="gg-range" value="${basemap.opacity}" onkeydown="${(e) => { e.preventDefault(); e.stopPropagation(); }}" onclick="${(e) => { e.preventDefault(); e.stopPropagation(); }}" onchange="${(e) => this.changeBasemapOpacity(basemap, e)}"></div></div>`)}</div></girafe-menu-button>
17
17
  ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
18
18
  };
19
19
  constructor() {
@@ -23,7 +23,7 @@ class CrossSectionSettingsComponent extends GirafeHTMLElement {
23
23
  .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}}
24
24
  </style>
25
25
  <style>${this.customStyle}</style>
26
- <div id="panel"><div id="content"><div class="btn-group-flex"><button id="draw-profile-btn" class="${this.crossSectionState.drawProfile ? 'gg-button active' : 'gg-button'}" tip="draw_profile_help" onclick="${() => this.toggleLineDraw()}" i18n="draw_profile">Draw</button> <button id="delete-profile-btn" class="gg-button" tip="delete_help" onclick="${() => this.deleteProfile()}" i18n="delete">Delete</button></div><div class="btn-group-flex"><label for="section-width-btn" i18n="profile_width" class="gg-label">Width [m]</label> <input id="section-width-btn" class="gg-input" type="number" value="${this.crossSectionState.sectionWidthSettings.value}" min="${this.crossSectionState.sectionWidthSettings.min}" max="${this.crossSectionState.sectionWidthSettings.max}" step="${this.crossSectionState.sectionWidthSettings.step}" onchange="${(e) => this.setSectionWidth(e.target.value)}"></div><div class="btn-group-flex"><button id="reset-zoom-btn" class="gg-button" tip="profile_reset_view_help" onclick="${() => this.resetZoom()}" i18n="profile_reset_view">Reset view</button> <button id="sync-views-btn" class="${this.crossSectionState.syncViews ? 'gg-button active' : 'gg-button'}" tip="profile_sync_view_help" onclick="${() => this.setViewSync(!this.crossSectionState.syncViews)}" i18n="profile_sync_view">Sync. views</button></div><div class="btn-group-flex"><button class="gg-icon-button gg-big" tip="profile_shift_up_help" onclick="${() => this.shiftLinestring([0.0, this.crossSectionState.linestringShift])}"><img src="icons/arrow_up.svg" alt="${this.context.i18nManager.getTranslation('profile_shift_up_help')}"></button> <button class="gg-icon-button gg-big" tip="profile_shift_down_help" onclick="${() => this.shiftLinestring([0.0, -this.crossSectionState.linestringShift])}"><img src="icons/arrow_down.svg" alt="${this.context.i18nManager.getTranslation('profile_shift_down_help')}"></button> <button class="gg-icon-button gg-big" tip="profile_shift_left_help" onclick="${() => this.shiftLinestring([-this.crossSectionState.linestringShift, 0.0])}"><img src="icons/arrow_left.svg" alt="${this.context.i18nManager.getTranslation('profile_shift_left_help')}"></button> <button class="gg-icon-button gg-big" tip="profile_shift_right_help" onclick="${() => this.shiftLinestring([this.crossSectionState.linestringShift, 0.0])}"><img src="icons/arrow_right.svg" alt="${this.context.i18nManager.getTranslation('profile_shift_right_help')}"></button></div><div class="btn-group-flex"><div class="cell"><label for="z-ratio-btn" class="gg-label">${this.context.i18nManager.getTranslation('profile_exaggeration')} (${this.crossSectionState.verticalExaggerationSettings.value})</label><div style="display: flex; align-items: center"><input id="z-ratio-btn" type="range" class="gg-range" min="${this.crossSectionState.verticalExaggerationSettings.min}" max="${this.crossSectionState.verticalExaggerationSettings.max}" step="${this.crossSectionState.verticalExaggerationSettings.step}" value="${this.crossSectionState.verticalExaggerationSettings.value}" oninput="${(e) => this.setVerticalExaggeration(parseFloat(e.target.value))}" style="flex: 1; min-width: 0; box-sizing: border-box"> <button id="z-ratio-reset-btn" class="reset-btn" tip="profile_exaggeration_reset_help" onclick="${() => this.setVerticalExaggeration(this.crossSectionState.verticalExaggerationSettings.default)}"><img src="icons/restart_alt.svg" alt="${this.context.i18nManager.getTranslation('profile_exaggeration_reset_help')}"></button></div></div></div><div class="btn-group-flex"><div class="cell"><label for="point-size-label" class="gg-label">${this.context.i18nManager.getTranslation('profile_point_size')} (${this.crossSectionState.pointSizeSettings.value})</label><div style="display: flex; align-items: center"><input id="point-size-btn" type="range" class="gg-range" min="${this.crossSectionState.pointSizeSettings.min}" max="${this.crossSectionState.pointSizeSettings.max}" step="${this.crossSectionState.pointSizeSettings.step}" value="${this.crossSectionState.pointSizeSettings.value}" oninput="${(e) => this.setPointSize(parseFloat(e.target.value))}" style="flex: 1; min-width: 0; box-sizing: border-box"> <button id="point-size-reset-btn" class="reset-btn" tip="profile_point_size_reset_help" onclick="${() => this.setPointSize(this.crossSectionState.pointSizeSettings.default)}"><img src="icons/restart_alt.svg" alt="${this.context.i18nManager.getTranslation('profile_point_size_reset_help')}"></button></div></div></div><div class="btn-group-flex"><label for="background-color-btn" i18n="profile_background_color" class="gg-label">Background color</label> <input id="background-color-btn" class="gg-input" type="color" value="${this.crossSectionState.backgroundColor}" oninput="${(e) => this.setBackgroundColor(e.target.value)}"></div><div class="btn-group-flex"><label for="sequential-color-btn" i18n="profile_colormap" class="gg-label">Colorscale</label> <select id="sequential-color-btn" class="gg-select" oninput="${(e) => this.setColorPalette(e.target.value)}"><option value="spectral" ?selected="${this.crossSectionState.colorPalette === 'spectral'}" i18n="palette_option_spectral">Spectral<option value="greys" ?selected="${this.crossSectionState.colorPalette === 'greys'}" i18n="palette_option_greys">Greys<option value="blues" ?selected="${this.crossSectionState.colorPalette === 'blues'}" i18n="palette_option_blues">Blues<option value="viridis" ?selected="${this.crossSectionState.colorPalette === 'viridis'}" i18n="palette_option_viridis">Viridis<option value="magma" ?selected="${this.crossSectionState.colorPalette === 'magma'}" i18n="palette_option_magma">Magma</select></div><div class="option"><label for="grid-btn" i18n="profile_toggle_grid" class="gg-label">Show grid</label> <input id="grid-btn" class="gg-checkbox" ?checked="${this.crossSectionState.gridVisible}" type="checkbox" onchange="${(e) => this.setGridVisibility(e.target.checked)}"></div><h3 i18n="profile_pointcloud">Pointclouds</h3><div class="btn-group-flex loader-progress"><div class="progress-wrapper" tip="profile_load_indicator_help"><progress id="load-progress" class="gg-progress" value="${this.crossSectionState.numberOfPoints}" max="${this.crossSectionState.maxNumberOfPoints}"></progress> <span class="progress-label">${(this.crossSectionState.loading) ? uHtml `<img src="icons/progress.svg" alt="Loading" class="gg-spin gg-small progress-icon">` : uHtml ``} ${this.crossSectionState.numberOfPoints} / ${this.crossSectionState.maxNumberOfPoints}</span></div></div><div>${this.crossSectionState.datasets.map(f => uHtml `<div class="dataset-row"><input type="checkbox" id="${f.id}" class="gg-checkbox" ?checked="${f.active}" onchange="${() => this.toggleVisibility(f.id)}"> <span class="dataset-name" i18n="${f.id}">${f.id}</span> <select class="gg-select" oninput="${(e) => this.setColorVariable(f.id, e.target.value)}"><option value="uniform" ?selected="${f.colorby === 'uniform'}" i18n="color_option_uniform">Uniform<option value="natural" ?selected="${f.colorby === 'natural'}" i18n="color_option_natural">Natural<option value="intensity" ?selected="${f.colorby === 'intensity'}" i18n="color_option_intensity">Intensity<option value="classification" ?selected="${f.colorby === 'classification'}" i18n="color_option_classification">Classification</select> <input id="uniform-color-btn" type="color" value="${f.color}" oninput="${(e) => this.setColor(f.id, e.target.value)}"></div>`)}</div><h3 i18n="profile_measure_and_annotate">Measurements and annotations</h3><div class="btn-group-flex"><button id="draw-measurements-btn" class="${this.crossSectionState.drawMeasurement ? 'gg-button active' : 'gg-button'}" tip="profile_measurement_help" onclick="${() => this.toggleMeasureDraw()}" i18n="profile_measurement" ?disabled="${!this.crossSectionState.enableMeasurement}">Measure</button> <button id="delete-measurements-btn" class="gg-button" tip="profile_delete_measurements_help" onclick="${() => this.deleteAllMeasurements()}" i18n="profile_delete_measurements">Delete all</button></div><div class="scrollable-table-container"><table id="measurements-table" class="scrollable-table"><thead><tr><th class="table-cell sticky-header" style="text-align: center"><th class="table-cell sticky-header" style="text-align: right" i18n="profile_table_dist">DIST.<th class="table-cell sticky-header" style="text-align: right" i18n="profile_table_slope">SLOPE.<th class="table-cell sticky-header" style="text-align: center"><tbody></table></div><div class="btn-group-flex"><button id="draw-markers-btn" class="${this.crossSectionState.drawMarker ? 'gg-button active' : 'gg-button'}" tip="profile_draw_markers_help" onclick="${() => this.toggleMarkerDraw()}" i18n="profile_draw_markers" ?disabled="${!this.crossSectionState.enableAnnotation}">Annotate</button> <button id="delete-markers-btn" class="gg-button" tip="profile_delete_markers_help" onclick="${() => this.deleteAllMarkers()}" i18n="profile_delete_markers">Delete all</button></div><div class="scrollable-table-container"><table id="markers-table" class="scrollable-table"><thead><tr><th class="sticky-header" style="text-align: center" i18n="profile_table_id">ID<th class="sticky-header" style="text-align: right" i18n="profile_table_coord">COORD.<th class="sticky-header" style="text-align: center"><th class="sticky-header" style="text-align: center"><th class="sticky-header" style="text-align: center"><tbody></table></div><h3 i18n="profile_print_title">Print</h3><div class="btn-group-flex"><select id="print-profile-format-btn" class="gg-select" oninput="${(e) => this.setPrintFileFormat(e.target.value)}"><option value="svg" i18n="SVG">SVG</select> <button id="print-profile-btn" class="gg-button" tip="profile_print_help" onclick="${() => this.printPlot(this.crossSectionState.printFileFormat)}" i18n="profile_print">Print</button></div><h3 i18n="profile_download_title">Downloads</h3><div class="text-label" i18n="profile_markers_download">Markers</div><div class="btn-group-flex"><select id="download-markers-format-btn" class="gg-select" oninput="${(e) => this.setAnnotationsFileFormat(e.target.value)}"><option value="kml" i18n="KML">KML<option value="geojson" i18n="GeoJSON">GeoJSON<option value="gpx" i18n="GPX">GPX</select> <button id="download-markers-btn" class="gg-button" tip="download_help" onclick="${() => this.exportFeature(this.points, 'markers', this.crossSectionState.downloadAnnotationsFormat)}" i18n="download" ?disabled="${!this.crossSectionState.enableAnnotationsDownload}">Download</button></div><div class="text-label" i18n="profile_linestring_download">Profile line</div><div class="btn-group-flex"><select id="download-linestring-format-btn" class="gg-select" oninput="${(e) => this.setLineFileFormat(e.target.value)}"><option value="kml" i18n="KML">KML<option value="geojson" i18n="GeoJSON">GeoJSON<option value="gpx" i18n="GPX">GPX</select> <button id="download-linestring-btn" class="gg-button" tip="download_help" onclick="${() => this.exportFeature([this.linestring], 'linestring', this.crossSectionState.downloadLinestringFormat)}" i18n="download" ?disabled="${!this.crossSectionState.enableLinestringDownload}">Download</button></div></div></div>
26
+ <div id="panel"><div id="content"><div class="btn-group-flex"><button id="draw-profile-btn" class="${this.crossSectionState.drawProfile ? 'gg-button active' : 'gg-button'}" tip="draw_profile_help" onclick="${() => this.toggleLineDraw()}" i18n="draw_profile">Draw</button> <button id="delete-profile-btn" class="gg-button" tip="delete_help" onclick="${() => this.deleteProfile()}" i18n="delete">Delete</button></div><div class="btn-group-flex"><label for="section-width-btn" i18n="profile_width" class="gg-label">Width [m]</label> <input id="section-width-btn" class="gg-input" type="number" value="${this.crossSectionState.sectionWidthSettings.value}" min="${this.crossSectionState.sectionWidthSettings.min}" max="${this.crossSectionState.sectionWidthSettings.max}" step="${this.crossSectionState.sectionWidthSettings.step}" onchange="${(e) => this.setSectionWidth(e.target.value)}"></div><div class="btn-group-flex"><button id="reset-zoom-btn" class="gg-button" tip="profile_reset_view_help" onclick="${() => this.resetZoom()}" i18n="profile_reset_view">Reset view</button> <button id="sync-views-btn" class="${this.crossSectionState.syncViews ? 'gg-button active' : 'gg-button'}" tip="profile_sync_view_help" onclick="${() => this.setViewSync(!this.crossSectionState.syncViews)}" i18n="profile_sync_view">Sync. views</button></div><div class="btn-group-flex"><button class="gg-icon-button gg-big" tip="profile_shift_up_help" onclick="${() => this.shiftLinestring([0.0, this.crossSectionState.linestringShift])}"><img src="icons/arrow_up.svg" alt="${this.context.i18nManager.getTranslation('profile_shift_up_help')}"></button> <button class="gg-icon-button gg-big" tip="profile_shift_down_help" onclick="${() => this.shiftLinestring([0.0, -this.crossSectionState.linestringShift])}"><img src="icons/arrow_down.svg" alt="${this.context.i18nManager.getTranslation('profile_shift_down_help')}"></button> <button class="gg-icon-button gg-big" tip="profile_shift_left_help" onclick="${() => this.shiftLinestring([-this.crossSectionState.linestringShift, 0.0])}"><img src="icons/arrow_left.svg" alt="${this.context.i18nManager.getTranslation('profile_shift_left_help')}"></button> <button class="gg-icon-button gg-big" tip="profile_shift_right_help" onclick="${() => this.shiftLinestring([this.crossSectionState.linestringShift, 0.0])}"><img src="icons/arrow_right.svg" alt="${this.context.i18nManager.getTranslation('profile_shift_right_help')}"></button></div><div class="btn-group-flex"><div class="cell"><label for="z-ratio-btn" class="gg-label">${this.context.i18nManager.getTranslation('profile_exaggeration')} (${this.crossSectionState.verticalExaggerationSettings.value})</label><div style="display: flex; align-items: center"><input id="z-ratio-btn" type="range" class="gg-range" min="${this.crossSectionState.verticalExaggerationSettings.min}" max="${this.crossSectionState.verticalExaggerationSettings.max}" step="${this.crossSectionState.verticalExaggerationSettings.step}" value="${this.crossSectionState.verticalExaggerationSettings.value}" oninput="${(e) => this.setVerticalExaggeration(Number.parseFloat(e.target.value))}" style="flex: 1; min-width: 0; box-sizing: border-box"> <button id="z-ratio-reset-btn" class="reset-btn" tip="profile_exaggeration_reset_help" onclick="${() => this.setVerticalExaggeration(this.crossSectionState.verticalExaggerationSettings.default)}"><img src="icons/restart_alt.svg" alt="${this.context.i18nManager.getTranslation('profile_exaggeration_reset_help')}"></button></div></div></div><div class="btn-group-flex"><div class="cell"><label for="point-size-btn" class="gg-label">${this.context.i18nManager.getTranslation('profile_point_size')} (${this.crossSectionState.pointSizeSettings.value})</label><div style="display: flex; align-items: center"><input id="point-size-btn" type="range" class="gg-range" min="${this.crossSectionState.pointSizeSettings.min}" max="${this.crossSectionState.pointSizeSettings.max}" step="${this.crossSectionState.pointSizeSettings.step}" value="${this.crossSectionState.pointSizeSettings.value}" oninput="${(e) => this.setPointSize(Number.parseFloat(e.target.value))}" style="flex: 1; min-width: 0; box-sizing: border-box"> <button id="point-size-reset-btn" class="reset-btn" tip="profile_point_size_reset_help" onclick="${() => this.setPointSize(this.crossSectionState.pointSizeSettings.default)}"><img src="icons/restart_alt.svg" alt="${this.context.i18nManager.getTranslation('profile_point_size_reset_help')}"></button></div></div></div><div class="btn-group-flex"><label for="background-color-btn" i18n="profile_background_color" class="gg-label">Background color</label> <input id="background-color-btn" class="gg-input" type="color" value="${this.crossSectionState.backgroundColor}" oninput="${(e) => this.setBackgroundColor(e.target.value)}"></div><div class="btn-group-flex"><label for="sequential-color-btn" i18n="profile_colormap" class="gg-label">Colorscale</label> <select id="sequential-color-btn" class="gg-select" oninput="${(e) => this.setColorPalette(e.target.value)}"><option value="spectral" ?selected="${this.crossSectionState.colorPalette === 'spectral'}" i18n="palette_option_spectral">Spectral<option value="greys" ?selected="${this.crossSectionState.colorPalette === 'greys'}" i18n="palette_option_greys">Greys<option value="blues" ?selected="${this.crossSectionState.colorPalette === 'blues'}" i18n="palette_option_blues">Blues<option value="viridis" ?selected="${this.crossSectionState.colorPalette === 'viridis'}" i18n="palette_option_viridis">Viridis<option value="magma" ?selected="${this.crossSectionState.colorPalette === 'magma'}" i18n="palette_option_magma">Magma</select></div><div class="option"><label for="grid-btn" i18n="profile_toggle_grid" class="gg-label">Show grid</label> <input id="grid-btn" class="gg-checkbox" ?checked="${this.crossSectionState.gridVisible}" type="checkbox" onchange="${(e) => this.setGridVisibility(e.target.checked)}"></div><h3 i18n="profile_pointcloud">Pointclouds</h3><div class="btn-group-flex loader-progress"><div class="progress-wrapper" tip="profile_load_indicator_help"><progress id="load-progress" class="gg-progress" value="${this.crossSectionState.numberOfPoints}" max="${this.crossSectionState.maxNumberOfPoints}"></progress> <span class="progress-label">${(this.crossSectionState.loading) ? uHtml `<img src="icons/progress.svg" alt="Loading" class="gg-spin gg-small progress-icon">` : uHtml ``} ${this.crossSectionState.numberOfPoints} / ${this.crossSectionState.maxNumberOfPoints}</span></div></div><div>${this.crossSectionState.datasets.map(f => uHtml `<div class="dataset-row"><input type="checkbox" id="${f.id}" class="gg-checkbox" aria-label="Dataset ${f.id}" ?checked="${f.active}" onchange="${() => this.toggleVisibility(f.id)}"> <span class="dataset-name" i18n="${f.id}">${f.id}</span> <select class="gg-select" aria-label="Color variable" oninput="${(e) => this.setColorVariable(f.id, e.target.value)}"><option value="uniform" ?selected="${f.colorby === 'uniform'}" i18n="color_option_uniform">Uniform<option value="natural" ?selected="${f.colorby === 'natural'}" i18n="color_option_natural">Natural<option value="intensity" ?selected="${f.colorby === 'intensity'}" i18n="color_option_intensity">Intensity<option value="classification" ?selected="${f.colorby === 'classification'}" i18n="color_option_classification">Classification</select> <input id="uniform-color-btn" type="color" aria-label="Uniform color" value="${f.color}" oninput="${(e) => this.setColor(f.id, e.target.value)}"></div>`)}</div><h3 i18n="profile_measure_and_annotate">Measurements and annotations</h3><div class="btn-group-flex"><button id="draw-measurements-btn" class="${this.crossSectionState.drawMeasurement ? 'gg-button active' : 'gg-button'}" tip="profile_measurement_help" onclick="${() => this.toggleMeasureDraw()}" i18n="profile_measurement" ?disabled="${!this.crossSectionState.enableMeasurement}">Measure</button> <button id="delete-measurements-btn" class="gg-button" tip="profile_delete_measurements_help" onclick="${() => this.deleteAllMeasurements()}" i18n="profile_delete_measurements">Delete all</button></div><div class="scrollable-table-container"><table id="measurements-table" class="scrollable-table"><thead><tr><th class="table-cell sticky-header" style="text-align: center"><th class="table-cell sticky-header" style="text-align: right" i18n="profile_table_dist">DIST.<th class="table-cell sticky-header" style="text-align: right" i18n="profile_table_slope">SLOPE.<th class="table-cell sticky-header" style="text-align: center"><tbody></table></div><div class="btn-group-flex"><button id="draw-markers-btn" class="${this.crossSectionState.drawMarker ? 'gg-button active' : 'gg-button'}" tip="profile_draw_markers_help" onclick="${() => this.toggleMarkerDraw()}" i18n="profile_draw_markers" ?disabled="${!this.crossSectionState.enableAnnotation}">Annotate</button> <button id="delete-markers-btn" class="gg-button" tip="profile_delete_markers_help" onclick="${() => this.deleteAllMarkers()}" i18n="profile_delete_markers">Delete all</button></div><div class="scrollable-table-container"><table id="markers-table" class="scrollable-table"><thead><tr><th class="sticky-header" style="text-align: center" i18n="profile_table_id">ID<th class="sticky-header" style="text-align: right" i18n="profile_table_coord">COORD.<th class="sticky-header" style="text-align: center"><th class="sticky-header" style="text-align: center"><th class="sticky-header" style="text-align: center"><tbody></table></div><h3 i18n="profile_print_title">Print</h3><div class="btn-group-flex"><select id="print-profile-format-btn" class="gg-select" aria-label="Print format" oninput="${(e) => this.setPrintFileFormat(e.target.value)}"><option value="svg" i18n="SVG">SVG</select> <button id="print-profile-btn" class="gg-button" tip="profile_print_help" onclick="${() => this.printPlot(this.crossSectionState.printFileFormat)}" i18n="profile_print">Print</button></div><h3 i18n="profile_download_title">Downloads</h3><div class="text-label" i18n="profile_markers_download">Markers</div><div class="btn-group-flex"><select id="download-markers-format-btn" class="gg-select" aria-label="Markers format" oninput="${(e) => this.setAnnotationsFileFormat(e.target.value)}"><option value="kml" i18n="KML">KML<option value="geojson" i18n="GeoJSON">GeoJSON<option value="gpx" i18n="GPX">GPX</select> <button id="download-markers-btn" class="gg-button" tip="download_help" onclick="${() => this.exportFeature(this.points, 'markers', this.crossSectionState.downloadAnnotationsFormat)}" i18n="download" ?disabled="${!this.crossSectionState.enableAnnotationsDownload}">Download</button></div><div class="text-label" i18n="profile_linestring_download">Profile line</div><div class="btn-group-flex"><select id="download-linestring-format-btn" class="gg-select" aria-label="Profile line format" oninput="${(e) => this.setLineFileFormat(e.target.value)}"><option value="kml" i18n="KML">KML<option value="geojson" i18n="GeoJSON">GeoJSON<option value="gpx" i18n="GPX">GPX</select> <button id="download-linestring-btn" class="gg-button" tip="download_help" onclick="${() => this.exportFeature([this.linestring], 'linestring', this.crossSectionState.downloadLinestringFormat)}" i18n="download" ?disabled="${!this.crossSectionState.enableLinestringDownload}">Download</button></div></div></div>
27
27
  ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
28
28
  };
29
29
  crossSectionState;
@@ -1028,7 +1028,7 @@ export class Scatterplot {
1028
1028
  }
1029
1029
  generateId(markers) {
1030
1030
  let id = 1;
1031
- const existingIds = new Set(markers.map((marker) => parseInt(marker.label)));
1031
+ const existingIds = new Set(markers.map((marker) => Number.parseInt(marker.label)));
1032
1032
  while (existingIds.has(id)) {
1033
1033
  id++;
1034
1034
  }
@@ -18,7 +18,7 @@ input{border:1px solid var(--bkg-color-grad1);width:inherit;background-color:var
18
18
  button.selected{background-color:#daebff!important;border:1px solid #54a4ff!important}#toolbar{box-sizing:border-box;grid-template-columns:repeat(5,1fr);gap:20px;width:100%;display:grid}#toolbar>button{box-sizing:border-box;filter:drop-shadow(0 4px 7px #0004382c);background-color:#fff;border-radius:8px;padding:10px}#toolbar>button>img{opacity:.7;width:100%}.icon{width:1.5rem;height:1.5rem}.gg-icon-button:hover{background-color:#0000}#panel{font-size:1.5em}#drawingList{gap:10px}#drawingList .girafe{filter:drop-shadow(0 4px 7px #0004381c);background:#fff;border:none;border-radius:8px;padding:10px}#optionsTitle{text-align:center;filter:drop-shadow(0 4px 7px #0004381c);color:#4553ff;border-radius:8px;width:100%;margin-bottom:20px;padding:10px;font-size:1.6rem}.gg-input:hover{filter:drop-shadow(0 4px 7px #4c58ff70);background-color:#fff}#optionsColorLine{flex-direction:column;display:flex}#optionsColorLine>div{text-align:end;width:100%}.selectable-label{font-size:1.3em}#optionsNameFontSize,#optionsMeasuresFontSize,#optionsStrokeWidth,#line-style,#arrow-style,#arrow-position{text-align:right;border:1px solid #e3e3e3;padding:1px;font-size:1em}.popup.popup_top{translate:15px -15px}#optionsExportLine{display:none}
19
19
  </style>
20
20
  <style>${this.customStyle}</style>
21
- <div id="panel"><div><div id="toolbar"><button id="disable" tip="Disable drawing" class="gg-icon-button gg-medium"><img alt="select icon" src="icons/arrow.svg"></button> <button id="point" tip="Point" class="gg-icon-button gg-medium"><img alt="point icon" src="icons/point.svg"></button> <button id="line" tip="Line" class="gg-icon-button gg-medium"><img alt="polyline icon" src="icons/polyline.svg"></button> <button id="polygon" tip="Polygon" class="gg-icon-button gg-medium"><img alt="polygon icon" src="icons/polygon.svg"></button> <button id="square" tip="Square" class="gg-icon-button gg-medium"><img alt="square icon" src="icons/square.svg"></button> <button id="rectangle" tip="Rectangle" class="gg-icon-button gg-medium"><img alt="rectangle icon" src="icons/rectangle.svg"></button> <button id="circle" tip="Circle" class="gg-icon-button gg-medium"><img alt="circle icon" src="icons/circle.svg"></button> <button id="freeline" tip="Freehand line" class="gg-icon-button gg-medium"><img alt="polyline-free icon" src="icons/polyline-free.svg"></button> <button id="freepolygon" tip="Freehand polygon" class="gg-icon-button gg-medium"><img alt="polygon-free icon" src="icons/polygon-free.svg"></button> <button id="text" tip="Text" class="gg-icon-button gg-medium"><img alt="text icon" src="icons/text-font.svg"></button></div><div id="toolParametersContainer"><span i18n="Settings" class="title"></span><div class="toolParameters" data-tool="Polyline,Polygon"><girafe-fixed-dimension dimensionname="length" dimension="length" id="fixedLineLength" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Square"><girafe-fixed-dimension dimensionname="side length" dimension="side" id="fixedSquareSide" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Rectangle"><girafe-fixed-dimension dimensionname="width" dimension="width" id="fixedRectangleWidth" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension><girafe-fixed-dimension dimensionname="height" dimension="height" id="fixedRectangleHeight" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Text"><label class="gg-label" for="drawingText" i18n="Text to draw">Text to draw</label> <input id="drawingText" class="gg-input" value="${this.drawingText}" oninput="${(e) => this.drawingTextChangedHandler(e)}"></div></div></div><div id="drawingListContainer"><div class="titleContainer"><span i18n="Drawing List" class="title"></span> <button class="gg-icon-button icon" tip="Delete all shapes" onclick="${() => this.deleteAllFeatures()}" ?disabled="${this.drawingState.features.length === 0}">${this.htmlUnsafe(this.trashIcon)}</button></div><div id="drawingList">${this.drawingState.features.map(feature => uHtmlFor(feature, feature.id) `<div id="${'f-' + feature.id}" class="girafe"><button id="${'f-select-' + feature.id}" class="gg-icon-button gg-small" tip="Select to change appearance" onclick="${() => this.onToggleFeatureSelection(feature)}"><div class="icon">${this.htmlUnsafe(feature.selected ? this.checkedIcon : this.noCheckedIcon)}</div></button><div class="label-container"><button class="${feature.selected ? 'selectable-label gg-icon-button gg-small gg-selected' : 'selectable-label gg-icon-button gg-small gg-opacity'}" onclick="${() => this.onToggleFeatureSelection(feature)}"><span id="${'f-name-' + feature.id}">${feature.name}</span></button></div><button class="gg-icon-button icon" tip="Center on shape" onclick="${() => this.olDrawing.centerViewOnFeature(feature)}">${this.htmlUnsafe(this.locateIcon)}</button> <button class="gg-icon-button icon" tip="Delete shape" onclick="${() => this.deleteFeature(feature)}">${this.htmlUnsafe(this.trashIcon)}</button></div>`)}</div></div><div id="options" class="${this.selectedFeatures.length > 0 ? '' : 'disabled'}"><fieldset class="field-wrapper" disabled="${this.selectedFeatures.length <= 0}"><input id="optionsTitle" value="${this.getOptionsTitle()}" class="gg-input" disabled="${this.selectedFeatures.length > 1}"><div id="optionsText"><div id="optionsTextLines"><span class="label first" i18n="Name">Name</span> <button class="gg-icon-button icon" id="visibleIconName" tip="Show or hide shape name" onclick="${() => this.toggleNameVisibility()}">${this.htmlUnsafe(this.isDisplayNameEnabled() ? this.visibleIcon : this.notVisibleIcon)}</button> <span class="label" i18n="Font">Font</span> <input id="optionsNameFontSize" class="gg-input" type="number" min="1" value="${this.selectedFeatures[0]?.nameFontSize}" oninput="${() => this.onOptionsChange()}"> <span class="unit">pt</span> <button class="icon colorPicker" id="nameColorPicker"></button> <span class="${this.isMeasureInformationEnabled() ? 'label first' : 'label first disabled'}" i18n="Measures">Measures</span> <select id="measure-information" class="gg-select" tip="${this.measureInformationTooltip}" disabled="${!this.isMeasureInformationEnabled()}" onchange="${() => this.onOptionsChange()}">${Object.entries(this.measureInformations).map((entry) => uHtml `<option selected="${this.selectedFeatureMeasureInformation === entry[0]}" value="${entry[0]}"><span i18n="${entry[1]}">${entry[0]}</span></option>`)}</select> <span class="${this.isMeasureInformationEnabled() ? 'label' : 'label disabled'}" i18n="Font">Font</span> <input id="optionsMeasuresFontSize" class="gg-input" type="number" min="1" value="${this.selectedFeatures[0]?.measureFontSize}" disabled="${!this.isMeasureInformationEnabled()}" oninput="${() => this.onOptionsChange()}"> <span class="${this.isMeasureInformationEnabled() ? 'unit' : 'unit disabled'}">pt</span> <button class="icon colorPicker" id="measureColorPicker" disabled="${!this.isMeasureInformationEnabled()}"></button></div></div><hr><div id="optionsColor"><div id="optionsColorLine"><div><span class="${this.isStrokeColorEnabled() ? 'text-end' : 'text-end disabled'}" i18n="Stroke color">Stroke color</span> <button class="icon colorPicker" disabled="${!this.isStrokeColorEnabled()}" id="strokePicker"></button></div><div><span class="${this.isFillColorEnabled() ? 'text-end' : 'text-end disabled'}" id="fillPickerSpan" i18n="Fill color">Fill color</span> <button class="icon colorPicker" disabled="${!this.isFillColorEnabled()}" id="fillPicker"></button></div></div></div><hr><div id="optionsLine"><div class="${this.isLineStyleEnabled() ? '' : 'disabled'}" id="optionsLineLines"><span i18n="Line width">Line width</span> <input id="optionsStrokeWidth" type="number" class="gg-input" min="1" value="${this.selectedFeatures[0]?.strokeWidth}" oninput="${() => this.onOptionsChange()}"> <span class="unit">px</span> <select id="line-style" class="gg-select" onchange="${() => this.onOptionsChange()}">${Object.entries(this.lineStyles).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.lineStroke === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select></div></div><hr><div id="optionsArrow"><div class="${this.isLineArrowEnabled() ? '' : 'disabled'}" id="optionsArrowStyles"><span i18n="Arrows style">Arrows style</span> <select id="arrow-style" class="gg-select" onchange="${() => this.onArrowsChange()}">${Object.entries(this.arrowStyles).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.arrowStyle === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select> <select id="arrow-position" class="gg-select" onchange="${() => this.onArrowsChange()}">${Object.entries(this.arrowPositions).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.arrowPosition === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select></div></div><hr><div id="optionsExport"><div id="optionsExportLine"><label for="export" class="gg-label" i18n="Export">Export</label> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('geojson')}">GeoJson</button> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('kml')}">KML</button> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('gpx')}">GPX</button></div></div></fieldset></div></div>
21
+ <div id="panel"><div><div id="toolbar"><button id="disable" tip="Disable drawing" class="gg-icon-button gg-medium"><img alt="select icon" src="icons/arrow.svg"></button> <button id="point" tip="Point" class="gg-icon-button gg-medium"><img alt="point icon" src="icons/point.svg"></button> <button id="line" tip="Line" class="gg-icon-button gg-medium"><img alt="polyline icon" src="icons/polyline.svg"></button> <button id="polygon" tip="Polygon" class="gg-icon-button gg-medium"><img alt="polygon icon" src="icons/polygon.svg"></button> <button id="square" tip="Square" class="gg-icon-button gg-medium"><img alt="square icon" src="icons/square.svg"></button> <button id="rectangle" tip="Rectangle" class="gg-icon-button gg-medium"><img alt="rectangle icon" src="icons/rectangle.svg"></button> <button id="circle" tip="Circle" class="gg-icon-button gg-medium"><img alt="circle icon" src="icons/circle.svg"></button> <button id="freeline" tip="Freehand line" class="gg-icon-button gg-medium"><img alt="polyline-free icon" src="icons/polyline-free.svg"></button> <button id="freepolygon" tip="Freehand polygon" class="gg-icon-button gg-medium"><img alt="polygon-free icon" src="icons/polygon-free.svg"></button> <button id="text" tip="Text" class="gg-icon-button gg-medium"><img alt="text icon" src="icons/text-font.svg"></button></div><div id="toolParametersContainer"><span i18n="Settings" class="title"></span><div class="toolParameters" data-tool="Polyline,Polygon"><girafe-fixed-dimension dimensionname="length" dimension="length" id="fixedLineLength" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Square"><girafe-fixed-dimension dimensionname="side length" dimension="side" id="fixedSquareSide" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Rectangle"><girafe-fixed-dimension dimensionname="width" dimension="width" id="fixedRectangleWidth" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension><girafe-fixed-dimension dimensionname="height" dimension="height" id="fixedRectangleHeight" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Text"><label class="gg-label" for="drawingText" i18n="Text to draw">Text to draw</label> <input id="drawingText" class="gg-input" value="${this.drawingText}" oninput="${(e) => this.drawingTextChangedHandler(e)}"></div></div></div><div id="drawingListContainer"><div class="titleContainer"><span i18n="Drawing List" class="title"></span> <button class="gg-icon-button icon" tip="Delete all shapes" onclick="${() => this.deleteAllFeatures()}" ?disabled="${this.drawingState.features.length === 0}">${this.htmlUnsafe(this.trashIcon)}</button></div><div id="drawingList">${this.drawingState.features.map(feature => uHtmlFor(feature, feature.id) `<div id="${'f-' + feature.id}" class="girafe"><button id="${'f-select-' + feature.id}" class="gg-icon-button gg-small" tip="Select to change appearance" onclick="${() => this.onToggleFeatureSelection(feature)}"><div class="icon">${this.htmlUnsafe(feature.selected ? this.checkedIcon : this.noCheckedIcon)}</div></button><div class="label-container"><button class="${feature.selected ? 'selectable-label gg-icon-button gg-small gg-selected' : 'selectable-label gg-icon-button gg-small gg-opacity'}" onclick="${() => this.onToggleFeatureSelection(feature)}"><span id="${'f-name-' + feature.id}">${feature.name}</span></button></div><button class="gg-icon-button icon" tip="Center on shape" onclick="${() => this.olDrawing.centerViewOnFeature(feature)}">${this.htmlUnsafe(this.locateIcon)}</button> <button class="gg-icon-button icon" tip="Delete shape" onclick="${() => this.deleteFeature(feature)}">${this.htmlUnsafe(this.trashIcon)}</button></div>`)}</div></div><div id="options" class="${this.selectedFeatures.length > 0 ? '' : 'disabled'}"><fieldset class="field-wrapper" disabled="${this.selectedFeatures.length <= 0}"><input id="optionsTitle" aria-label="Title" value="${this.getOptionsTitle()}" class="gg-input" disabled="${this.selectedFeatures.length > 1}"><div id="optionsText"><div id="optionsTextLines"><span class="label first" i18n="Name">Name</span> <button class="gg-icon-button icon" id="visibleIconName" tip="Show or hide shape name" onclick="${() => this.toggleNameVisibility()}">${this.htmlUnsafe(this.isDisplayNameEnabled() ? this.visibleIcon : this.notVisibleIcon)}</button> <span class="label" i18n="Font">Font</span> <input id="optionsNameFontSize" class="gg-input" type="number" aria-label="Name font size" min="1" value="${this.selectedFeatures[0]?.nameFontSize}" oninput="${() => this.onOptionsChange()}"> <span class="unit">pt</span> <button class="icon colorPicker" id="nameColorPicker"></button> <span class="${this.isMeasureInformationEnabled() ? 'label first' : 'label first disabled'}" i18n="Measures">Measures</span> <select id="measure-information" class="gg-select" aria-label="Measures information" tip="${this.measureInformationTooltip}" disabled="${!this.isMeasureInformationEnabled()}" onchange="${() => this.onOptionsChange()}">${Object.entries(this.measureInformations).map((entry) => uHtml `<option selected="${this.selectedFeatureMeasureInformation === entry[0]}" value="${entry[0]}"><span i18n="${entry[1]}">${entry[0]}</span></option>`)}</select> <span class="${this.isMeasureInformationEnabled() ? 'label' : 'label disabled'}" i18n="Font">Font</span> <input id="optionsMeasuresFontSize" class="gg-input" type="number" aria-label="Measures font size" min="1" value="${this.selectedFeatures[0]?.measureFontSize}" disabled="${!this.isMeasureInformationEnabled()}" oninput="${() => this.onOptionsChange()}"> <span class="${this.isMeasureInformationEnabled() ? 'unit' : 'unit disabled'}">pt</span> <button class="icon colorPicker" id="measureColorPicker" disabled="${!this.isMeasureInformationEnabled()}"></button></div></div><hr><div id="optionsColor"><div id="optionsColorLine"><div><span class="${this.isStrokeColorEnabled() ? 'text-end' : 'text-end disabled'}" i18n="Stroke color">Stroke color</span> <button class="icon colorPicker" disabled="${!this.isStrokeColorEnabled()}" id="strokePicker"></button></div><div><span class="${this.isFillColorEnabled() ? 'text-end' : 'text-end disabled'}" id="fillPickerSpan" i18n="Fill color">Fill color</span> <button class="icon colorPicker" disabled="${!this.isFillColorEnabled()}" id="fillPicker"></button></div></div></div><hr><div id="optionsLine"><div class="${this.isLineStyleEnabled() ? '' : 'disabled'}" id="optionsLineLines"><span i18n="Line width">Line width</span> <input id="optionsStrokeWidth" type="number" aria-label="Line width" class="gg-input" min="1" value="${this.selectedFeatures[0]?.strokeWidth}" oninput="${() => this.onOptionsChange()}"> <span class="unit">px</span> <select id="line-style" class="gg-select" aria-label="Line style" onchange="${() => this.onOptionsChange()}">${Object.entries(this.lineStyles).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.lineStroke === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select></div></div><hr><div id="optionsArrow"><div class="${this.isLineArrowEnabled() ? '' : 'disabled'}" id="optionsArrowStyles"><span i18n="Arrows style">Arrows style</span> <select id="arrow-style" class="gg-select" aria-label="Arrows style" onchange="${() => this.onArrowsChange()}">${Object.entries(this.arrowStyles).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.arrowStyle === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select> <select id="arrow-position" class="gg-select" aria-label="Arrow position" onchange="${() => this.onArrowsChange()}">${Object.entries(this.arrowPositions).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.arrowPosition === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select></div></div><hr><div id="optionsExport"><div id="optionsExportLine"><label for="export" class="gg-label" i18n="Export">Export</label> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('geojson')}">GeoJson</button> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('kml')}">KML</button> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('gpx')}">GPX</button></div></div></fieldset></div></div>
22
22
  ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
23
23
  };
24
24
  constructor() {
@@ -41,7 +41,7 @@ export default class DrawingComponent extends GirafeHTMLElement {
41
41
  input{border:1px solid var(--bkg-color-grad1);width:inherit;background-color:var(--bkg-color-input);padding-right:0}input:focus{border:1px solid var(--lt-color-gray-900)}button{border:solid 1px var(--bkg-color);color:var(--text-color);background-color:var(--bkg-color);cursor:pointer;padding:0}button:hover{border:solid 1px var(--text-color-grad2)}button.selected{border:solid 1px var(--text-color-grad2);background-color:var(--bkg-color-grad1);color:var(--bkg-color)}#toolbar{flex-wrap:wrap;display:flex}.label-container{flex-grow:1}.text-end{text-align:end}.icon{width:1.2rem;height:1.2rem;fill:var(--text-color);flex-shrink:0;margin:3px}.icon:hover{fill:var(--text-color-grad2)}.gg-icon-button:hover{border:none}.colorPicker{border:1px solid var(--text-color);border-radius:5px}.picker_wrapper{transform:translate(-250px)}.picker_arrow{transform:translate(200px)rotate(90deg)scaleX(-1)!important}.picker_wrapper,.picker_arrow:after,.picker_arrow:before{background-color:var(--bkg-color)!important}.picker_wrapper,.picker_arrow:before{box-shadow:0 0 10px 1px var(--text-color-grad1)!important}button:disabled,input:disabled,.disabled{pointer-events:none;opacity:.5}fieldset.field-wrapper{border:0;margin:0;padding:0}#panel{height:100%;color:var(--text-color);flex-direction:column;display:flex}#panel>div{padding:1rem}#toolParametersContainer{& .title{margin-top:.5rem;margin-bottom:.75rem;display:inline-block}}.toolParameters{flex-direction:column;justify-content:left;row-gap:.25rem;display:flex}.toolParameters>label{padding:0 5px}#drawingListContainer{flex-grow:1;overflow:hidden auto;padding-top:0!important;& .titleContainer{margin-bottom:1rem;display:flex;& .title{flex-grow:1;align-items:center;display:flex}}}#drawingList{flex-flow:column;gap:.15rem;display:flex}#drawingList .girafe{border:solid 1px var(--text-color);flex-wrap:wrap;align-items:center;padding:.3rem;display:flex;position:relative}#drawingList .girafe span{flex-grow:1;padding-left:5px}#options{box-shadow:0px -2px 3px -1px var(--text-color-grad1);z-index:100;flex-direction:column;display:flex;position:relative}#optionsTitle{text-align:center;margin-bottom:20px;font-size:1.2rem}#options hr{width:100%;margin:8px 0}#optionsTextLines,#optionsColorLine,#optionsLineLines{align-items:center;display:grid}#optionsText{margin:auto}#optionsTextLines{grid-template-columns:.825fr 2.5fr 1fr 4em .5fr .5fr}#optionsTextLines button:hover{border:none}#optionsColor,#optionsLine,#optionsExport,#optionsArrow{margin:auto}#optionsColorLine{display:flex}#optionsColorLine>div{align-items:center;width:100%;display:flex}#optionsLineLines,#optionsArrowStyle{grid-template-columns:3fr 4em 1fr auto}#optionsExportLine{grid-template-columns:60px 60px 45px 45px;gap:.2rem;display:flex}span.label{padding-left:.5rem;padding-right:.5rem;&.first{padding-left:0}}span.unit{padding-left:.5rem}
42
42
  </style>
43
43
  <style>${this.customStyle}</style>
44
- <div id="panel"><div><div id="toolbar"><button id="disable" tip="Disable drawing" class="gg-icon-button gg-medium"><img alt="select icon" src="icons/arrow.svg"></button> <button id="point" tip="Point" class="gg-icon-button gg-medium"><img alt="point icon" src="icons/point.svg"></button> <button id="line" tip="Line" class="gg-icon-button gg-medium"><img alt="polyline icon" src="icons/polyline.svg"></button> <button id="polygon" tip="Polygon" class="gg-icon-button gg-medium"><img alt="polygon icon" src="icons/polygon.svg"></button> <button id="square" tip="Square" class="gg-icon-button gg-medium"><img alt="square icon" src="icons/square.svg"></button> <button id="rectangle" tip="Rectangle" class="gg-icon-button gg-medium"><img alt="rectangle icon" src="icons/rectangle.svg"></button> <button id="circle" tip="Circle" class="gg-icon-button gg-medium"><img alt="circle icon" src="icons/circle.svg"></button> <button id="freeline" tip="Freehand line" class="gg-icon-button gg-medium"><img alt="polyline-free icon" src="icons/polyline-free.svg"></button> <button id="freepolygon" tip="Freehand polygon" class="gg-icon-button gg-medium"><img alt="polygon-free icon" src="icons/polygon-free.svg"></button> <button id="text" tip="Text" class="gg-icon-button gg-medium"><img alt="text icon" src="icons/text-font.svg"></button></div><div id="toolParametersContainer"><span i18n="Settings" class="title"></span><div class="toolParameters" data-tool="Polyline,Polygon"><girafe-fixed-dimension dimensionname="length" dimension="length" id="fixedLineLength" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Square"><girafe-fixed-dimension dimensionname="side length" dimension="side" id="fixedSquareSide" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Rectangle"><girafe-fixed-dimension dimensionname="width" dimension="width" id="fixedRectangleWidth" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension><girafe-fixed-dimension dimensionname="height" dimension="height" id="fixedRectangleHeight" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Text"><label class="gg-label" for="drawingText" i18n="Text to draw">Text to draw</label> <input id="drawingText" class="gg-input" value="${this.drawingText}" oninput="${(e) => this.drawingTextChangedHandler(e)}"></div></div></div><div id="drawingListContainer"><div class="titleContainer"><span i18n="Drawing List" class="title"></span> <button class="gg-icon-button icon" tip="Delete all shapes" onclick="${() => this.deleteAllFeatures()}" ?disabled="${this.drawingState.features.length === 0}">${this.htmlUnsafe(this.trashIcon)}</button></div><div id="drawingList">${this.drawingState.features.map(feature => uHtmlFor(feature, feature.id) `<div id="${'f-' + feature.id}" class="girafe"><button id="${'f-select-' + feature.id}" class="gg-icon-button gg-small" tip="Select to change appearance" onclick="${() => this.onToggleFeatureSelection(feature)}"><div class="icon">${this.htmlUnsafe(feature.selected ? this.checkedIcon : this.noCheckedIcon)}</div></button><div class="label-container"><button class="${feature.selected ? 'selectable-label gg-icon-button gg-small gg-selected' : 'selectable-label gg-icon-button gg-small gg-opacity'}" onclick="${() => this.onToggleFeatureSelection(feature)}"><span id="${'f-name-' + feature.id}">${feature.name}</span></button></div><button class="gg-icon-button icon" tip="Center on shape" onclick="${() => this.olDrawing.centerViewOnFeature(feature)}">${this.htmlUnsafe(this.locateIcon)}</button> <button class="gg-icon-button icon" tip="Delete shape" onclick="${() => this.deleteFeature(feature)}">${this.htmlUnsafe(this.trashIcon)}</button></div>`)}</div></div><div id="options" class="${this.selectedFeatures.length > 0 ? '' : 'disabled'}"><fieldset class="field-wrapper" disabled="${this.selectedFeatures.length <= 0}"><input id="optionsTitle" value="${this.getOptionsTitle()}" class="gg-input" disabled="${this.selectedFeatures.length > 1}"><div id="optionsText"><div id="optionsTextLines"><span class="label first" i18n="Name">Name</span> <button class="gg-icon-button icon" id="visibleIconName" tip="Show or hide shape name" onclick="${() => this.toggleNameVisibility()}">${this.htmlUnsafe(this.isDisplayNameEnabled() ? this.visibleIcon : this.notVisibleIcon)}</button> <span class="label" i18n="Font">Font</span> <input id="optionsNameFontSize" class="gg-input" type="number" min="1" value="${this.selectedFeatures[0]?.nameFontSize}" oninput="${() => this.onOptionsChange()}"> <span class="unit">pt</span> <button class="icon colorPicker" id="nameColorPicker"></button> <span class="${this.isMeasureInformationEnabled() ? 'label first' : 'label first disabled'}" i18n="Measures">Measures</span> <select id="measure-information" class="gg-select" tip="${this.measureInformationTooltip}" disabled="${!this.isMeasureInformationEnabled()}" onchange="${() => this.onOptionsChange()}">${Object.entries(this.measureInformations).map((entry) => uHtml `<option selected="${this.selectedFeatureMeasureInformation === entry[0]}" value="${entry[0]}"><span i18n="${entry[1]}">${entry[0]}</span></option>`)}</select> <span class="${this.isMeasureInformationEnabled() ? 'label' : 'label disabled'}" i18n="Font">Font</span> <input id="optionsMeasuresFontSize" class="gg-input" type="number" min="1" value="${this.selectedFeatures[0]?.measureFontSize}" disabled="${!this.isMeasureInformationEnabled()}" oninput="${() => this.onOptionsChange()}"> <span class="${this.isMeasureInformationEnabled() ? 'unit' : 'unit disabled'}">pt</span> <button class="icon colorPicker" id="measureColorPicker" disabled="${!this.isMeasureInformationEnabled()}"></button></div></div><hr><div id="optionsColor"><div id="optionsColorLine"><div><span class="${this.isStrokeColorEnabled() ? 'text-end' : 'text-end disabled'}" i18n="Stroke color">Stroke color</span> <button class="icon colorPicker" disabled="${!this.isStrokeColorEnabled()}" id="strokePicker"></button></div><div><span class="${this.isFillColorEnabled() ? 'text-end' : 'text-end disabled'}" id="fillPickerSpan" i18n="Fill color">Fill color</span> <button class="icon colorPicker" disabled="${!this.isFillColorEnabled()}" id="fillPicker"></button></div></div></div><hr><div id="optionsLine"><div class="${this.isLineStyleEnabled() ? '' : 'disabled'}" id="optionsLineLines"><span i18n="Line width">Line width</span> <input id="optionsStrokeWidth" type="number" class="gg-input" min="1" value="${this.selectedFeatures[0]?.strokeWidth}" oninput="${() => this.onOptionsChange()}"> <span class="unit">px</span> <select id="line-style" class="gg-select" onchange="${() => this.onOptionsChange()}">${Object.entries(this.lineStyles).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.lineStroke === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select></div></div><hr><div id="optionsArrow"><div class="${this.isLineArrowEnabled() ? '' : 'disabled'}" id="optionsArrowStyles"><span i18n="Arrows style">Arrows style</span> <select id="arrow-style" class="gg-select" onchange="${() => this.onArrowsChange()}">${Object.entries(this.arrowStyles).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.arrowStyle === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select> <select id="arrow-position" class="gg-select" onchange="${() => this.onArrowsChange()}">${Object.entries(this.arrowPositions).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.arrowPosition === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select></div></div><hr><div id="optionsExport"><div id="optionsExportLine"><label for="export" class="gg-label" i18n="Export">Export</label> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('geojson')}">GeoJson</button> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('kml')}">KML</button> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('gpx')}">GPX</button></div></div></fieldset></div></div>
44
+ <div id="panel"><div><div id="toolbar"><button id="disable" tip="Disable drawing" class="gg-icon-button gg-medium"><img alt="select icon" src="icons/arrow.svg"></button> <button id="point" tip="Point" class="gg-icon-button gg-medium"><img alt="point icon" src="icons/point.svg"></button> <button id="line" tip="Line" class="gg-icon-button gg-medium"><img alt="polyline icon" src="icons/polyline.svg"></button> <button id="polygon" tip="Polygon" class="gg-icon-button gg-medium"><img alt="polygon icon" src="icons/polygon.svg"></button> <button id="square" tip="Square" class="gg-icon-button gg-medium"><img alt="square icon" src="icons/square.svg"></button> <button id="rectangle" tip="Rectangle" class="gg-icon-button gg-medium"><img alt="rectangle icon" src="icons/rectangle.svg"></button> <button id="circle" tip="Circle" class="gg-icon-button gg-medium"><img alt="circle icon" src="icons/circle.svg"></button> <button id="freeline" tip="Freehand line" class="gg-icon-button gg-medium"><img alt="polyline-free icon" src="icons/polyline-free.svg"></button> <button id="freepolygon" tip="Freehand polygon" class="gg-icon-button gg-medium"><img alt="polygon-free icon" src="icons/polygon-free.svg"></button> <button id="text" tip="Text" class="gg-icon-button gg-medium"><img alt="text icon" src="icons/text-font.svg"></button></div><div id="toolParametersContainer"><span i18n="Settings" class="title"></span><div class="toolParameters" data-tool="Polyline,Polygon"><girafe-fixed-dimension dimensionname="length" dimension="length" id="fixedLineLength" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Square"><girafe-fixed-dimension dimensionname="side length" dimension="side" id="fixedSquareSide" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Rectangle"><girafe-fixed-dimension dimensionname="width" dimension="width" id="fixedRectangleWidth" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension><girafe-fixed-dimension dimensionname="height" dimension="height" id="fixedRectangleHeight" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Text"><label class="gg-label" for="drawingText" i18n="Text to draw">Text to draw</label> <input id="drawingText" class="gg-input" value="${this.drawingText}" oninput="${(e) => this.drawingTextChangedHandler(e)}"></div></div></div><div id="drawingListContainer"><div class="titleContainer"><span i18n="Drawing List" class="title"></span> <button class="gg-icon-button icon" tip="Delete all shapes" onclick="${() => this.deleteAllFeatures()}" ?disabled="${this.drawingState.features.length === 0}">${this.htmlUnsafe(this.trashIcon)}</button></div><div id="drawingList">${this.drawingState.features.map(feature => uHtmlFor(feature, feature.id) `<div id="${'f-' + feature.id}" class="girafe"><button id="${'f-select-' + feature.id}" class="gg-icon-button gg-small" tip="Select to change appearance" onclick="${() => this.onToggleFeatureSelection(feature)}"><div class="icon">${this.htmlUnsafe(feature.selected ? this.checkedIcon : this.noCheckedIcon)}</div></button><div class="label-container"><button class="${feature.selected ? 'selectable-label gg-icon-button gg-small gg-selected' : 'selectable-label gg-icon-button gg-small gg-opacity'}" onclick="${() => this.onToggleFeatureSelection(feature)}"><span id="${'f-name-' + feature.id}">${feature.name}</span></button></div><button class="gg-icon-button icon" tip="Center on shape" onclick="${() => this.olDrawing.centerViewOnFeature(feature)}">${this.htmlUnsafe(this.locateIcon)}</button> <button class="gg-icon-button icon" tip="Delete shape" onclick="${() => this.deleteFeature(feature)}">${this.htmlUnsafe(this.trashIcon)}</button></div>`)}</div></div><div id="options" class="${this.selectedFeatures.length > 0 ? '' : 'disabled'}"><fieldset class="field-wrapper" disabled="${this.selectedFeatures.length <= 0}"><input id="optionsTitle" aria-label="Title" value="${this.getOptionsTitle()}" class="gg-input" disabled="${this.selectedFeatures.length > 1}"><div id="optionsText"><div id="optionsTextLines"><span class="label first" i18n="Name">Name</span> <button class="gg-icon-button icon" id="visibleIconName" tip="Show or hide shape name" onclick="${() => this.toggleNameVisibility()}">${this.htmlUnsafe(this.isDisplayNameEnabled() ? this.visibleIcon : this.notVisibleIcon)}</button> <span class="label" i18n="Font">Font</span> <input id="optionsNameFontSize" class="gg-input" type="number" aria-label="Name font size" min="1" value="${this.selectedFeatures[0]?.nameFontSize}" oninput="${() => this.onOptionsChange()}"> <span class="unit">pt</span> <button class="icon colorPicker" id="nameColorPicker"></button> <span class="${this.isMeasureInformationEnabled() ? 'label first' : 'label first disabled'}" i18n="Measures">Measures</span> <select id="measure-information" class="gg-select" aria-label="Measures information" tip="${this.measureInformationTooltip}" disabled="${!this.isMeasureInformationEnabled()}" onchange="${() => this.onOptionsChange()}">${Object.entries(this.measureInformations).map((entry) => uHtml `<option selected="${this.selectedFeatureMeasureInformation === entry[0]}" value="${entry[0]}"><span i18n="${entry[1]}">${entry[0]}</span></option>`)}</select> <span class="${this.isMeasureInformationEnabled() ? 'label' : 'label disabled'}" i18n="Font">Font</span> <input id="optionsMeasuresFontSize" class="gg-input" type="number" aria-label="Measures font size" min="1" value="${this.selectedFeatures[0]?.measureFontSize}" disabled="${!this.isMeasureInformationEnabled()}" oninput="${() => this.onOptionsChange()}"> <span class="${this.isMeasureInformationEnabled() ? 'unit' : 'unit disabled'}">pt</span> <button class="icon colorPicker" id="measureColorPicker" disabled="${!this.isMeasureInformationEnabled()}"></button></div></div><hr><div id="optionsColor"><div id="optionsColorLine"><div><span class="${this.isStrokeColorEnabled() ? 'text-end' : 'text-end disabled'}" i18n="Stroke color">Stroke color</span> <button class="icon colorPicker" disabled="${!this.isStrokeColorEnabled()}" id="strokePicker"></button></div><div><span class="${this.isFillColorEnabled() ? 'text-end' : 'text-end disabled'}" id="fillPickerSpan" i18n="Fill color">Fill color</span> <button class="icon colorPicker" disabled="${!this.isFillColorEnabled()}" id="fillPicker"></button></div></div></div><hr><div id="optionsLine"><div class="${this.isLineStyleEnabled() ? '' : 'disabled'}" id="optionsLineLines"><span i18n="Line width">Line width</span> <input id="optionsStrokeWidth" type="number" aria-label="Line width" class="gg-input" min="1" value="${this.selectedFeatures[0]?.strokeWidth}" oninput="${() => this.onOptionsChange()}"> <span class="unit">px</span> <select id="line-style" class="gg-select" aria-label="Line style" onchange="${() => this.onOptionsChange()}">${Object.entries(this.lineStyles).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.lineStroke === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select></div></div><hr><div id="optionsArrow"><div class="${this.isLineArrowEnabled() ? '' : 'disabled'}" id="optionsArrowStyles"><span i18n="Arrows style">Arrows style</span> <select id="arrow-style" class="gg-select" aria-label="Arrows style" onchange="${() => this.onArrowsChange()}">${Object.entries(this.arrowStyles).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.arrowStyle === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select> <select id="arrow-position" class="gg-select" aria-label="Arrow position" onchange="${() => this.onArrowsChange()}">${Object.entries(this.arrowPositions).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.arrowPosition === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select></div></div><hr><div id="optionsExport"><div id="optionsExportLine"><label for="export" class="gg-label" i18n="Export">Export</label> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('geojson')}">GeoJson</button> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('kml')}">KML</button> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('gpx')}">GPX</button></div></div></fieldset></div></div>
45
45
  ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
46
46
  };
47
47
  isPanelVisible = false;
@@ -454,9 +454,9 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
454
454
  }
455
455
  }
456
456
  onOptionsChange() {
457
- const nameFontSize = parseInt(this.getById('optionsNameFontSize').value);
458
- const measureFontSize = parseInt(this.getById('optionsMeasuresFontSize').value);
459
- const strokeWidth = parseInt(this.getById('optionsStrokeWidth').value);
457
+ const nameFontSize = Number.parseInt(this.getById('optionsNameFontSize').value);
458
+ const measureFontSize = Number.parseInt(this.getById('optionsMeasuresFontSize').value);
459
+ const strokeWidth = Number.parseInt(this.getById('optionsStrokeWidth').value);
460
460
  const lineStroke = this.getById('line-style').value;
461
461
  const measureInformation = this.getById('measure-information').value;
462
462
  this.selectedFeatures.forEach((f) => (f.nameFontSize = nameFontSize));
@@ -22,7 +22,7 @@ class FixedDimensionComponent extends GirafeHTMLElement {
22
22
  :host{grid-template-columns:auto auto 60px auto;justify-content:left;align-items:center;display:grid}input{border:1px solid var(--bkg-color-grad1);width:inherit;background-color:var(--bkg-color-input);padding-right:0}input:focus{border:1px solid var(--lt-color-gray-900)}button{border:solid 1px var(--bkg-color);color:var(--text-color);background-color:var(--bkg-color);cursor:pointer;padding:0}button:hover{border:solid 1px var(--text-color-grad2)}button.selected{border:solid 1px var(--text-color-grad2);background-color:var(--bkg-color-grad1);color:var(--bkg-color)}.icon{width:1.2rem;height:1.2rem;fill:var(--text-color);flex-shrink:0;margin:3px}.icon:hover{fill:var(--text-color-grad2)}.gg-icon-button:hover{border:none}button:disabled,input:disabled,.disabled{pointer-events:none;opacity:.5}label.gg-label{padding:0 5px!important;&.disabled{pointer-events:revert}}
23
23
  </style>
24
24
  <style>${this.customStyle}</style>
25
- <button class="gg-icon-button icon" onclick="${() => this.toggleFixedDimensionEnabled()}">${this.htmlUnsafe(this.fixedDimensionEnabled ? this.checkedIcon : this.notCheckedIcon)}</button> <label class="${this.fixedDimensionEnabled ? 'gg-label' : 'gg-label disabled'}" onclick="${() => this.toggleFixedDimensionEnabled()}" onkeyup="${() => this.toggleFixedDimensionEnabled()}" for="fixedValue" i18n="${'Fix ' + this.dimensionName + ' to'}"></label> <input class="gg-input" disabled="${!this.fixedDimensionEnabled}" oninput="${(e) => this.fixedValueChanged(e)}" type="number" id="fixedValue"> <select id="dimensionUnits" class="gg-select" disabled="${!this.fixedDimensionEnabled}" onchange="${(e) => this.dimensionUnitChanged(e)}">${this.units.map((unit) => uHtml `<option ?selected="${this.unit?.name === unit.name}" value="${unit.factor}">${unit.name}</option>`)}</select>
25
+ <button class="gg-icon-button icon" onclick="${() => this.toggleFixedDimensionEnabled()}">${this.htmlUnsafe(this.fixedDimensionEnabled ? this.checkedIcon : this.notCheckedIcon)}</button> <label class="${this.fixedDimensionEnabled ? 'gg-label' : 'gg-label disabled'}" onclick="${() => this.toggleFixedDimensionEnabled()}" onkeyup="${() => this.toggleFixedDimensionEnabled()}" for="fixedValue" i18n="${'Fix ' + this.dimensionName + ' to'}"></label> <input class="gg-input" disabled="${!this.fixedDimensionEnabled}" oninput="${(e) => this.fixedValueChanged(e)}" type="number" id="fixedValue"> <select id="dimensionUnits" class="gg-select" aria-label="Units" disabled="${!this.fixedDimensionEnabled}" onchange="${(e) => this.dimensionUnitChanged(e)}">${this.units.map((unit) => uHtml `<option ?selected="${this.unit?.name === unit.name}" value="${unit.factor}">${unit.name}</option>`)}</select>
26
26
  ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
27
27
  };
28
28
  checkedIcon = checkedIcon;
@@ -33,7 +33,7 @@ export default class EditComponent extends GirafeHTMLElement {
33
33
  #content{background:var(--bkg-color);color:var(--text-color);flex-direction:column;gap:1rem;margin:0;padding:1rem;display:flex}.girafe-prototype{color:#fff;text-align:center;background:#ec5555;font-size:1rem;font-weight:700;line-height:40px}.editing{flex-direction:column;gap:1rem;display:flex}#layer-selector{width:100%}.draw-btn{text-align:right}.current-feature{border-top:var(--app-standard-border);margin-top:2rem}.current-feature-row{align-items:center;gap:.5em;display:flex}.current-feature-row>button{margin-left:auto}.current-feature-row>div:first-child{font-weight:700}.attribute-form-btns{justify-content:flex-end;gap:.5rem;margin-top:1rem;display:flex}.btn-active{background-color:var(--bkg-color-grad2)}
34
34
  </style>
35
35
  <style>${this.customStyle}</style>
36
- <div id="content"><div class="girafe-prototype" i18n="PROTOTYPE - Work in progress">PROTOTYPE - Work in progress</div><div class="editing"><select id="layer-selector" class="gg-select" disabled="${this.feature}" onchange="${(evt) => this.onSelectLayer(evt)}">${this.editableLayersList.length === 0 && this.state.oauth.status !== 'loggedIn' ? uHtml `<option i18n="Please log in to edit layers..." ?selected="true" value="">Please log in to edit layers...</option>` : uHtml `<option i18n="Choose a layer..." ?selected="true" value="">Choose a layer...</option>`} ${this.editableLayersList.map(layer => uHtmlFor(layer, layer.collectionId) `<option value="${layer.collectionId}">${layer.displayName}</option>`)}</select> ${this.layer ? uHtml `<div class="draw-btn">${this.displayDrawingCancelButton() ? uHtml ` <button class="gg-button gg-medium-withtext btn-active" onclick="${() => this.onCancelDrawing()}"><img alt="cancel-drawing-icon" src="icons/close.svg"> <span i18n="Cancel editing">Cancel editing</span></button> ` : uHtml ` <button class="gg-button gg-medium-withtext" onclick="${() => this.onStartDrawing()}"><img alt="start-drawing-icon" src="${this.getButtonIcon()}"> <span i18n="${this.getButtonText()}">${this.getButtonText()}</span></button> `}</div>` : uHtml ``} ${this.feature ? uHtml `<div class="current-feature"><h3 i18n="Current Feature">Current Feature</h3><div class="current-feature-row"><div i18n="Feature ID">Feature ID</div><div>${this.getFeatureId()}</div><button class="${this.canDelete() ? 'gg-button gg-medium-withtext delete-btn' : 'hidden'}" onclick="${() => this.onDelete()}"><img alt="delete-icon" src="icons/trash.svg"> <span i18n="delete">Delete</span></button></div></div>` : uHtml ``}<girafe-edit-form id="edit-form"></girafe-edit-form>${this.feature ? uHtml `<div class="attribute-form-btns"><button class="gg-button" disabled="${!this.isValidForm()}" onclick="${() => this.onSave()}"><span i18n="Save">Save</span></button> <button class="gg-button" onclick="${() => this.onDiscard()}"><span i18n="Discard">Discard</span></button></div>` : uHtml ``}</div></div>
36
+ <div id="content"><div class="girafe-prototype" i18n="PROTOTYPE - Work in progress">PROTOTYPE - Work in progress</div><div class="editing"><select id="layer-selector" class="gg-select" aria-label="Layer to edit" disabled="${this.feature}" onchange="${(evt) => this.onSelectLayer(evt)}">${this.editableLayersList.length === 0 && this.state.oauth.status !== 'loggedIn' ? uHtml `<option i18n="Please log in to edit layers..." ?selected="true" value="">Please log in to edit layers...</option>` : uHtml `<option i18n="Choose a layer..." ?selected="true" value="">Choose a layer...</option>`} ${this.editableLayersList.map(layer => uHtmlFor(layer, layer.collectionId) `<option value="${layer.collectionId}">${layer.displayName}</option>`)}</select> ${this.layer ? uHtml `<div class="draw-btn">${this.displayDrawingCancelButton() ? uHtml ` <button class="gg-button gg-medium-withtext btn-active" onclick="${() => this.onCancelDrawing()}"><img alt="cancel-drawing-icon" src="icons/close.svg"> <span i18n="Cancel editing">Cancel editing</span></button> ` : uHtml ` <button class="gg-button gg-medium-withtext" onclick="${() => this.onStartDrawing()}"><img alt="start-drawing-icon" src="${this.getButtonIcon()}"> <span i18n="${this.getButtonText()}">${this.getButtonText()}</span></button> `}</div>` : uHtml ``} ${this.feature ? uHtml `<div class="current-feature"><h3 i18n="Current Feature">Current Feature</h3><div class="current-feature-row"><div i18n="Feature ID">Feature ID</div><div>${this.getFeatureId()}</div><button class="${this.canDelete() ? 'gg-button gg-medium-withtext delete-btn' : 'hidden'}" onclick="${() => this.onDelete()}"><img alt="delete-icon" src="icons/trash.svg"> <span i18n="delete">Delete</span></button></div></div>` : uHtml ``}<girafe-edit-form id="edit-form"></girafe-edit-form>${this.feature ? uHtml `<div class="attribute-form-btns"><button class="gg-button" disabled="${!this.isValidForm()}" onclick="${() => this.onSave()}"><span i18n="Save">Save</span></button> <button class="gg-button" onclick="${() => this.onDiscard()}"><span i18n="Discard">Discard</span></button></div>` : uHtml ``}</div></div>
37
37
  ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
38
38
  };
39
39
  isPanelVisible = false;
@@ -56,7 +56,7 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
56
56
  const formField = this.getById(`form-${attrName}`);
57
57
  let formValue = formField.value;
58
58
  if (this.featureSchema.isNumericProperty(attrName)) {
59
- if (formValue === '' || isNaN(Number(formValue))) {
59
+ if (formValue === '' || Number.isNaN(Number(formValue))) {
60
60
  formValue = null;
61
61
  }
62
62
  else {