@eeacms/volto-arcgis-block 0.1.377 → 0.1.379
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -4,8 +4,26 @@ All notable changes to this project will be documented in this file. Dates are d
|
|
|
4
4
|
|
|
5
5
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
6
6
|
|
|
7
|
+
### [0.1.379](https://github.com/eea/volto-arcgis-block/compare/0.1.378...0.1.379) - 11 August 2025
|
|
8
|
+
|
|
9
|
+
#### :hammer_and_wrench: Others
|
|
10
|
+
|
|
11
|
+
- Merge pull request #998 from eea/CLMS-LOCAL-STORAGE-ISSUES-REFACTOR-CLEAN [Unai Bolivar - [`3b84fbd`](https://github.com/eea/volto-arcgis-block/commit/3b84fbd1cc3706976cc746a95c6885da7a746e22)]
|
|
12
|
+
- (lint): cleared console statements [Unai Bolivar - [`f6d649d`](https://github.com/eea/volto-arcgis-block/commit/f6d649d51d9254a250e49ca31b83bc4cc8c6b30a)]
|
|
13
|
+
- (bug): implemented safe store and recall for all user data in mapviewer fix [Unai Bolivar - [`a0491a7`](https://github.com/eea/volto-arcgis-block/commit/a0491a7bbeffdcd527eabc91b716757d680ea5d3)]
|
|
14
|
+
- (bug): rendering session data correctly [Unai Bolivar - [`0fd8434`](https://github.com/eea/volto-arcgis-block/commit/0fd843442b72b7d0980f98e182f4ec68dc00850c)]
|
|
15
|
+
- (bug): wrapping mapviewer in mapviewerstatemonitor to control and eliminate multiple mapviewer mounts and race conditions [Unai Bolivar - [`c3e5232`](https://github.com/eea/volto-arcgis-block/commit/c3e5232b64f318256f504575f7e6ee685d80e121)]
|
|
16
|
+
- (bug): fixed user anonymous session storage maintenance [Unai Bolivar - [`7bb1054`](https://github.com/eea/volto-arcgis-block/commit/7bb105406e272d73c115b1515313723890830d8e)]
|
|
17
|
+
- (bug): saving updates to retrieve current jsx file [Unai Bolivar - [`10e8b69`](https://github.com/eea/volto-arcgis-block/commit/10e8b69a2f9c6ebda49864bb431c6f8e4a49fa66)]
|
|
18
|
+
- (bug): prettified the file [Unai Bolivar - [`e561931`](https://github.com/eea/volto-arcgis-block/commit/e5619310b30c1c8935735d8de53939cc551fb1ba)]
|
|
19
|
+
- (bug): moved status monitoring to mapviewer HOC component for better synchronization and widget updates that actually catch the user login and userID information and setup session storage accordingly before proceeding with a full state update after mount is ready. [Unai Bolivar - [`16dd623`](https://github.com/eea/volto-arcgis-block/commit/16dd62302b4fa520185b4d9957e69e41c31e1fa6)]
|
|
20
|
+
### [0.1.378](https://github.com/eea/volto-arcgis-block/compare/0.1.377...0.1.378) - 11 August 2025
|
|
21
|
+
|
|
7
22
|
### [0.1.377](https://github.com/eea/volto-arcgis-block/compare/0.1.376...0.1.377) - 30 July 2025
|
|
8
23
|
|
|
24
|
+
#### :hammer_and_wrench: Others
|
|
25
|
+
|
|
26
|
+
- Merge pull request #995 from eea/develop [Unai Bolivar - [`194ef38`](https://github.com/eea/volto-arcgis-block/commit/194ef385764f9d153fb46ac121bdc0f55d75ce20)]
|
|
9
27
|
### [0.1.376](https://github.com/eea/volto-arcgis-block/compare/0.1.375...0.1.376) - 17 July 2025
|
|
10
28
|
|
|
11
29
|
### [0.1.375](https://github.com/eea/volto-arcgis-block/compare/0.1.374...0.1.375) - 14 July 2025
|
package/package.json
CHANGED
|
@@ -34,6 +34,12 @@ class AreaWidget extends React.Component {
|
|
|
34
34
|
showMapMenu: false,
|
|
35
35
|
showInfoPopup: this.props.download ? true : false,
|
|
36
36
|
infoPopupType: 'area',
|
|
37
|
+
coordinates: {
|
|
38
|
+
north: '',
|
|
39
|
+
south: '',
|
|
40
|
+
east: '',
|
|
41
|
+
west: '',
|
|
42
|
+
},
|
|
37
43
|
};
|
|
38
44
|
this.menuClass =
|
|
39
45
|
'esri-icon-cursor-marquee esri-widget--button esri-widget esri-interactive';
|
|
@@ -245,18 +251,14 @@ class AreaWidget extends React.Component {
|
|
|
245
251
|
document
|
|
246
252
|
.querySelector('.closeCoordinates')
|
|
247
253
|
.classList.replace('esri-icon-up-arrow', 'esri-icon-close');
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
if (document.getElementById('menu-west')) {
|
|
258
|
-
document.getElementById('menu-west').value = null;
|
|
259
|
-
}
|
|
254
|
+
this.setState({
|
|
255
|
+
coordinates: {
|
|
256
|
+
north: '',
|
|
257
|
+
south: '',
|
|
258
|
+
east: '',
|
|
259
|
+
west: '',
|
|
260
|
+
},
|
|
261
|
+
});
|
|
260
262
|
}
|
|
261
263
|
}
|
|
262
264
|
nuts0handler(e) {
|
|
@@ -1020,41 +1022,45 @@ class AreaWidget extends React.Component {
|
|
|
1020
1022
|
addCoordinates() {
|
|
1021
1023
|
this.clearWidget();
|
|
1022
1024
|
let valid = true;
|
|
1023
|
-
let pointNorth =
|
|
1025
|
+
let pointNorth = this.state.coordinates.north;
|
|
1024
1026
|
if (
|
|
1025
|
-
pointNorth
|
|
1026
|
-
pointNorth
|
|
1027
|
-
pointNorth
|
|
1027
|
+
pointNorth > 90 ||
|
|
1028
|
+
pointNorth < -90 ||
|
|
1029
|
+
pointNorth === null ||
|
|
1030
|
+
pointNorth === ''
|
|
1028
1031
|
) {
|
|
1029
1032
|
valid = false;
|
|
1030
1033
|
}
|
|
1031
|
-
let pointSouth =
|
|
1034
|
+
let pointSouth = this.state.coordinates.south;
|
|
1032
1035
|
if (
|
|
1033
|
-
pointSouth
|
|
1034
|
-
pointSouth
|
|
1035
|
-
pointSouth
|
|
1036
|
+
pointSouth > 90 ||
|
|
1037
|
+
pointSouth < -90 ||
|
|
1038
|
+
pointSouth === null ||
|
|
1039
|
+
pointSouth === ''
|
|
1036
1040
|
) {
|
|
1037
1041
|
valid = false;
|
|
1038
1042
|
}
|
|
1039
|
-
let pointEast =
|
|
1043
|
+
let pointEast = this.state.coordinates.east;
|
|
1040
1044
|
if (
|
|
1041
|
-
pointEast
|
|
1042
|
-
pointEast
|
|
1043
|
-
pointEast
|
|
1045
|
+
pointEast > 180 ||
|
|
1046
|
+
pointEast < -180 ||
|
|
1047
|
+
pointEast === null ||
|
|
1048
|
+
pointEast === ''
|
|
1044
1049
|
) {
|
|
1045
1050
|
valid = false;
|
|
1046
1051
|
}
|
|
1047
|
-
let pointWest =
|
|
1052
|
+
let pointWest = this.state.coordinates.west;
|
|
1048
1053
|
if (
|
|
1049
|
-
pointWest
|
|
1050
|
-
pointWest
|
|
1051
|
-
pointWest
|
|
1054
|
+
pointWest > 180 ||
|
|
1055
|
+
pointWest < -180 ||
|
|
1056
|
+
pointWest === null ||
|
|
1057
|
+
pointWest === ''
|
|
1052
1058
|
) {
|
|
1053
1059
|
valid = false;
|
|
1054
1060
|
}
|
|
1055
1061
|
if (valid) {
|
|
1056
|
-
let pointNW = [pointNorth
|
|
1057
|
-
let pointSE = [pointSouth
|
|
1062
|
+
let pointNW = [pointNorth, pointWest];
|
|
1063
|
+
let pointSE = [pointSouth, pointEast];
|
|
1058
1064
|
var fillSymbol = {
|
|
1059
1065
|
type: 'simple-fill',
|
|
1060
1066
|
color: [255, 255, 255, 0.5],
|
|
@@ -1107,6 +1113,17 @@ class AreaWidget extends React.Component {
|
|
|
1107
1113
|
});
|
|
1108
1114
|
}
|
|
1109
1115
|
}
|
|
1116
|
+
handleCoordinateChange = (e) => {
|
|
1117
|
+
const { id, value } = e.target;
|
|
1118
|
+
// id will be "menu-north", "menu-south", "menu-east", or "menu-west"
|
|
1119
|
+
const key = id.replace('menu-', ''); // gives you 'north', 'south', etc.
|
|
1120
|
+
this.setState((prevState) => ({
|
|
1121
|
+
coordinates: {
|
|
1122
|
+
...prevState.coordinates,
|
|
1123
|
+
[key]: value,
|
|
1124
|
+
},
|
|
1125
|
+
}));
|
|
1126
|
+
};
|
|
1110
1127
|
openCoordinates(event) {
|
|
1111
1128
|
this.clearWidget();
|
|
1112
1129
|
this.nutsRadioButton(event.target.value);
|
|
@@ -1823,6 +1840,8 @@ class AreaWidget extends React.Component {
|
|
|
1823
1840
|
max="90"
|
|
1824
1841
|
placeholder="00.000000"
|
|
1825
1842
|
className="coordinateInput"
|
|
1843
|
+
value={this.state.coordinates.north}
|
|
1844
|
+
onChange={this.handleCoordinateChange}
|
|
1826
1845
|
/>
|
|
1827
1846
|
</div>
|
|
1828
1847
|
<div>
|
|
@@ -1834,6 +1853,8 @@ class AreaWidget extends React.Component {
|
|
|
1834
1853
|
max="180"
|
|
1835
1854
|
placeholder="00.000000"
|
|
1836
1855
|
className="coordinateInput"
|
|
1856
|
+
value={this.state.coordinates.west}
|
|
1857
|
+
onChange={this.handleCoordinateChange}
|
|
1837
1858
|
/>
|
|
1838
1859
|
</div>
|
|
1839
1860
|
</div>
|
|
@@ -1848,6 +1869,8 @@ class AreaWidget extends React.Component {
|
|
|
1848
1869
|
max="90"
|
|
1849
1870
|
placeholder="00.000000"
|
|
1850
1871
|
className="coordinateInput"
|
|
1872
|
+
value={this.state.coordinates.south}
|
|
1873
|
+
onChange={this.handleCoordinateChange}
|
|
1851
1874
|
/>
|
|
1852
1875
|
</div>
|
|
1853
1876
|
<div>
|
|
@@ -1859,6 +1882,8 @@ class AreaWidget extends React.Component {
|
|
|
1859
1882
|
max="180"
|
|
1860
1883
|
placeholder="00.000000"
|
|
1861
1884
|
className="coordinateInput"
|
|
1885
|
+
value={this.state.coordinates.east}
|
|
1886
|
+
onChange={this.handleCoordinateChange}
|
|
1862
1887
|
/>
|
|
1863
1888
|
</div>
|
|
1864
1889
|
</div>
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
createRef,
|
|
3
|
+
createContext,
|
|
4
|
+
useContext,
|
|
5
|
+
useState,
|
|
6
|
+
useEffect,
|
|
7
|
+
useCallback,
|
|
8
|
+
useRef,
|
|
9
|
+
} from 'react';
|
|
2
10
|
import './css/ArcgisMap.css';
|
|
3
11
|
import classNames from 'classnames';
|
|
4
12
|
import { loadModules, loadCss } from 'esri-loader';
|
|
@@ -28,13 +36,417 @@ import { getTaxonomy } from '@eeacms/volto-taxonomy/actions';
|
|
|
28
36
|
//import "isomorphic-fetch"; <-- Necessary to use fetch?
|
|
29
37
|
var Map, MapView, Zoom, intl, Basemap, WebTileLayer, Extent;
|
|
30
38
|
let mapStatus = {};
|
|
39
|
+
|
|
31
40
|
const CheckLanguage = () => {
|
|
32
41
|
const { locale } = useIntl();
|
|
33
42
|
intl.setLocale(locale);
|
|
34
43
|
return null;
|
|
35
44
|
};
|
|
36
45
|
|
|
46
|
+
// Enhanced UserContext and Provider with state monitoring capabilities
|
|
47
|
+
const UserContext = createContext({ user_id: null, isLoggedIn: false });
|
|
48
|
+
const useUserContext = () => useContext(UserContext);
|
|
49
|
+
|
|
50
|
+
// Enhanced UserProvider with state change monitoring
|
|
51
|
+
const UserProvider = ({ children }) => {
|
|
52
|
+
const cartState = useCartState();
|
|
53
|
+
|
|
54
|
+
// Direct transformation of cart state to user context format
|
|
55
|
+
// No local state needed - context reactively updates when cartState changes
|
|
56
|
+
const userContextValue = {
|
|
57
|
+
user_id: cartState?.user_id || null,
|
|
58
|
+
isLoggedIn: cartState?.isLoggedIn || false,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Show loading only when cart state is completely undefined (initial load)
|
|
62
|
+
// Prevents UI flicker by distinguishing between "loading" and "logged out" states
|
|
63
|
+
if (cartState === undefined) {
|
|
64
|
+
return (
|
|
65
|
+
<div className="loading-container">
|
|
66
|
+
<div className="loading-text">Loading...</div>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<UserContext.Provider value={userContextValue}>
|
|
73
|
+
{children}
|
|
74
|
+
</UserContext.Provider>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// MapViewer State Monitor - Functional wrapper that monitors useCartState changes
|
|
79
|
+
// and ensures the class component always receives the latest authentication state
|
|
80
|
+
const MapViewerStateMonitor = ({ children, onUserStateChange }) => {
|
|
81
|
+
const cartState = useCartState();
|
|
82
|
+
const prevCartStateRef = useRef(null);
|
|
83
|
+
|
|
84
|
+
// Monitor cart state changes and notify parent component
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (cartState !== undefined) {
|
|
87
|
+
const currentUserState = {
|
|
88
|
+
user_id: cartState?.user_id || null,
|
|
89
|
+
isLoggedIn: cartState?.isLoggedIn || false,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Compare with previous state to detect changes
|
|
93
|
+
if (prevCartStateRef.current !== null) {
|
|
94
|
+
const hasUserIdChanged =
|
|
95
|
+
prevCartStateRef.current.user_id !== currentUserState.user_id;
|
|
96
|
+
const hasLoggedInChanged =
|
|
97
|
+
prevCartStateRef.current.isLoggedIn !== currentUserState.isLoggedIn;
|
|
98
|
+
|
|
99
|
+
if (hasUserIdChanged || hasLoggedInChanged) {
|
|
100
|
+
/* eslint-disable no-console */
|
|
101
|
+
console.log('[MapViewerStateMonitor] User state change detected:', {
|
|
102
|
+
previous: prevCartStateRef.current,
|
|
103
|
+
current: currentUserState,
|
|
104
|
+
userIdChanged: hasUserIdChanged,
|
|
105
|
+
loggedInChanged: hasLoggedInChanged,
|
|
106
|
+
});
|
|
107
|
+
/* eslint-enable no-console */
|
|
108
|
+
|
|
109
|
+
// Notify parent component of state change
|
|
110
|
+
if (onUserStateChange) {
|
|
111
|
+
onUserStateChange(currentUserState, prevCartStateRef.current);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Update previous state using ref (doesn't cause re-renders)
|
|
117
|
+
prevCartStateRef.current = currentUserState;
|
|
118
|
+
}
|
|
119
|
+
}, [cartState, onUserStateChange]);
|
|
120
|
+
|
|
121
|
+
return children;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// UserStorageManager now consumes user state directly from UserContext
|
|
125
|
+
// Eliminates prop drilling and ensures consistent user state access
|
|
126
|
+
const UserStorageManager = ({ children, onStorageManaged }) => {
|
|
127
|
+
const { user_id: userID, isLoggedIn } = useUserContext();
|
|
128
|
+
const [storageManaged, setStorageManaged] = useState(false);
|
|
129
|
+
const [isInitialized, setIsInitialized] = useState(false);
|
|
130
|
+
|
|
131
|
+
// Generate unique instance ID to track operations and prevent race conditions
|
|
132
|
+
const instanceId = useRef(Math.random().toString(36).substring(7));
|
|
133
|
+
|
|
134
|
+
// Track if this instance has successfully restored data
|
|
135
|
+
const hasRestoredData = useRef(false);
|
|
136
|
+
|
|
137
|
+
const populateSessionFromUserData = (userKey) => {
|
|
138
|
+
const userData = localStorage.getItem(userKey);
|
|
139
|
+
if (userData) {
|
|
140
|
+
/* eslint-disable no-console */
|
|
141
|
+
console.log(
|
|
142
|
+
`[UserStorageManager:${instanceId.current}] Restoring from localStorage:`,
|
|
143
|
+
{
|
|
144
|
+
key: userKey,
|
|
145
|
+
dataPreview:
|
|
146
|
+
userData.substring(0, 100) + (userData.length > 100 ? '...' : ''),
|
|
147
|
+
dataLength: userData.length,
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
/* eslint-enable no-console */
|
|
151
|
+
const parsed = JSON.parse(userData);
|
|
152
|
+
const userIdFromKey = (key) => key.replace(/^user_/, '');
|
|
153
|
+
const hydratedFor = sessionStorage.getItem('mv_hydrated_for');
|
|
154
|
+
const alreadyHasLayers = (() => {
|
|
155
|
+
try {
|
|
156
|
+
const cl = JSON.parse(
|
|
157
|
+
sessionStorage.getItem('checkedLayers') || '[]',
|
|
158
|
+
);
|
|
159
|
+
return Array.isArray(cl) && cl.length > 0;
|
|
160
|
+
} catch {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
})();
|
|
164
|
+
if (hydratedFor === userIdFromKey(userKey) || alreadyHasLayers) {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
Object.entries(parsed).forEach(([k, v]) => {
|
|
168
|
+
const existing = sessionStorage.getItem(k);
|
|
169
|
+
if (existing == null) {
|
|
170
|
+
sessionStorage.setItem(
|
|
171
|
+
k,
|
|
172
|
+
typeof v === 'object' ? JSON.stringify(v) : v,
|
|
173
|
+
);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (k === 'checkedLayers') {
|
|
177
|
+
try {
|
|
178
|
+
const a = JSON.parse(existing || '[]');
|
|
179
|
+
const b = typeof v === 'string' ? JSON.parse(v) : v;
|
|
180
|
+
const union = [...new Set([...(b || []), ...(a || [])])];
|
|
181
|
+
sessionStorage.setItem('checkedLayers', JSON.stringify(union));
|
|
182
|
+
} catch {}
|
|
183
|
+
} else if (k === 'visibleLayers' || k === 'layerOpacities') {
|
|
184
|
+
try {
|
|
185
|
+
const a = JSON.parse(existing || '{}');
|
|
186
|
+
const b = typeof v === 'string' ? JSON.parse(v) : v || {};
|
|
187
|
+
sessionStorage.setItem(k, JSON.stringify({ ...b, ...a }));
|
|
188
|
+
} catch {}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
sessionStorage.setItem('mv_hydrated_for', userIdFromKey(userKey));
|
|
192
|
+
|
|
193
|
+
// Mark that this instance has successfully restored data
|
|
194
|
+
hasRestoredData.current = true;
|
|
195
|
+
|
|
196
|
+
return true; // Indicate successful restoration
|
|
197
|
+
} else {
|
|
198
|
+
/* eslint-disable no-console */
|
|
199
|
+
console.log(
|
|
200
|
+
`[UserStorageManager:${instanceId.current}] No data found for key:`,
|
|
201
|
+
userKey,
|
|
202
|
+
);
|
|
203
|
+
/* eslint-enable no-console */
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// Improved instance management with lock mechanism to prevent race conditions
|
|
209
|
+
const acquireStorageLock = (lockKey) => {
|
|
210
|
+
const existingLock = localStorage.getItem(lockKey);
|
|
211
|
+
if (existingLock && existingLock !== instanceId.current) {
|
|
212
|
+
return false; // Another instance has the lock
|
|
213
|
+
}
|
|
214
|
+
localStorage.setItem(lockKey, instanceId.current);
|
|
215
|
+
return true;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const releaseStorageLock = (lockKey) => {
|
|
219
|
+
const currentLock = localStorage.getItem(lockKey);
|
|
220
|
+
if (currentLock === instanceId.current) {
|
|
221
|
+
localStorage.removeItem(lockKey);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// Initialization effect - handles user state readiness
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
if (userID !== undefined && isLoggedIn !== undefined) {
|
|
228
|
+
/* eslint-disable no-console */
|
|
229
|
+
console.log(
|
|
230
|
+
`[UserStorageManager:${instanceId.current}] User state initialized:`,
|
|
231
|
+
{
|
|
232
|
+
userID,
|
|
233
|
+
isLoggedIn,
|
|
234
|
+
},
|
|
235
|
+
);
|
|
236
|
+
/* eslint-enable no-console */
|
|
237
|
+
setIsInitialized(true);
|
|
238
|
+
}
|
|
239
|
+
}, [userID, isLoggedIn]);
|
|
240
|
+
|
|
241
|
+
// Initial storage management effect - runs once on mount with lock protection
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
if (!isInitialized || storageManaged) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const lockKey = `storage_lock_${userID || 'anonymous'}`;
|
|
248
|
+
|
|
249
|
+
/* eslint-disable no-console */
|
|
250
|
+
console.log(
|
|
251
|
+
`[UserStorageManager:${instanceId.current}] Component mounting with state:`,
|
|
252
|
+
{
|
|
253
|
+
isLoggedIn,
|
|
254
|
+
userID,
|
|
255
|
+
storageManaged,
|
|
256
|
+
isInitialized,
|
|
257
|
+
},
|
|
258
|
+
);
|
|
259
|
+
/* eslint-enable no-console */
|
|
260
|
+
|
|
261
|
+
// Acquire lock to prevent concurrent operations
|
|
262
|
+
if (!acquireStorageLock(lockKey)) {
|
|
263
|
+
/* eslint-disable no-console */
|
|
264
|
+
console.log(
|
|
265
|
+
`[UserStorageManager:${instanceId.current}] Another instance is managing storage for this user`,
|
|
266
|
+
);
|
|
267
|
+
/* eslint-enable no-console */
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
if (isLoggedIn === true) {
|
|
272
|
+
/* eslint-disable no-console */
|
|
273
|
+
console.log(
|
|
274
|
+
`[UserStorageManager:${instanceId.current}] Detected signed-in user, processing storage...`,
|
|
275
|
+
);
|
|
276
|
+
/* eslint-enable no-console */
|
|
277
|
+
|
|
278
|
+
if (
|
|
279
|
+
userID &&
|
|
280
|
+
userID !== null &&
|
|
281
|
+
userID !== undefined &&
|
|
282
|
+
userID !== ''
|
|
283
|
+
) {
|
|
284
|
+
const userKey = `user_${userID}`;
|
|
285
|
+
const userData = localStorage.getItem(userKey);
|
|
286
|
+
const anonymousData = localStorage.getItem('user_anonymous');
|
|
287
|
+
|
|
288
|
+
/* eslint-disable no-console */
|
|
289
|
+
console.log(
|
|
290
|
+
`[UserStorageManager:${instanceId.current}] Storage analysis:`,
|
|
291
|
+
{
|
|
292
|
+
userKey,
|
|
293
|
+
hasUserData: !!userData,
|
|
294
|
+
hasAnonymousData: !!anonymousData,
|
|
295
|
+
sessionEmpty: sessionStorage.length === 0,
|
|
296
|
+
sessionLength: sessionStorage.length,
|
|
297
|
+
},
|
|
298
|
+
);
|
|
299
|
+
/* eslint-enable no-console */
|
|
300
|
+
|
|
301
|
+
// Migration and restoration logic for signed-in users
|
|
302
|
+
if (!userData && anonymousData) {
|
|
303
|
+
/* eslint-disable no-console */
|
|
304
|
+
console.log(
|
|
305
|
+
`[UserStorageManager:${instanceId.current}] Migrating anonymous data to signed-in user`,
|
|
306
|
+
);
|
|
307
|
+
/* eslint-enable no-console */
|
|
308
|
+
// Move anonymous data to the signed-in user's localStorage key
|
|
309
|
+
localStorage.setItem(userKey, anonymousData);
|
|
310
|
+
// Delete the anonymous key from localStorage
|
|
311
|
+
localStorage.removeItem('user_anonymous');
|
|
312
|
+
// Populate sessionStorage with the migrated data from localStorage
|
|
313
|
+
populateSessionFromUserData(userKey);
|
|
314
|
+
} else if (userData) {
|
|
315
|
+
/* eslint-disable no-console */
|
|
316
|
+
console.log(
|
|
317
|
+
`[UserStorageManager:${instanceId.current}] Found existing user data - restoring to session storage`,
|
|
318
|
+
);
|
|
319
|
+
/* eslint-enable no-console */
|
|
320
|
+
// User has existing saved data - restore it to sessionStorage
|
|
321
|
+
populateSessionFromUserData(userKey);
|
|
322
|
+
} else {
|
|
323
|
+
/* eslint-disable no-console */
|
|
324
|
+
console.log(
|
|
325
|
+
`[UserStorageManager:${instanceId.current}] No saved user data found - preserving current session state`,
|
|
326
|
+
);
|
|
327
|
+
/* eslint-enable no-console */
|
|
328
|
+
}
|
|
329
|
+
setStorageManaged(true);
|
|
330
|
+
} else {
|
|
331
|
+
/* eslint-disable no-console */
|
|
332
|
+
console.log(
|
|
333
|
+
`[UserStorageManager:${instanceId.current}] valid userID unavailable: userID is ${userID}`,
|
|
334
|
+
);
|
|
335
|
+
/* eslint-enable no-console */
|
|
336
|
+
}
|
|
337
|
+
} else if (isLoggedIn === false) {
|
|
338
|
+
/* eslint-disable no-console */
|
|
339
|
+
console.log(
|
|
340
|
+
`[UserStorageManager:${instanceId.current}] Detected anonymous user, processing storage...`,
|
|
341
|
+
);
|
|
342
|
+
/* eslint-enable no-console */
|
|
343
|
+
// Handle anonymous user session - restore anonymous data if it exists
|
|
344
|
+
const anonymousKey = 'user_anonymous';
|
|
345
|
+
const anonymousData = localStorage.getItem(anonymousKey);
|
|
346
|
+
if (anonymousData) {
|
|
347
|
+
/* eslint-disable no-console */
|
|
348
|
+
console.log(
|
|
349
|
+
`[UserStorageManager:${instanceId.current}] Restoring anonymous data to session storage:`,
|
|
350
|
+
{
|
|
351
|
+
dataPreview:
|
|
352
|
+
anonymousData.substring(0, 100) +
|
|
353
|
+
(anonymousData.length > 100 ? '...' : ''),
|
|
354
|
+
dataLength: anonymousData.length,
|
|
355
|
+
},
|
|
356
|
+
);
|
|
357
|
+
/* eslint-enable no-console */
|
|
358
|
+
sessionStorage.clear();
|
|
359
|
+
const parsed = JSON.parse(anonymousData);
|
|
360
|
+
Object.keys(parsed).forEach((k) =>
|
|
361
|
+
sessionStorage.setItem(
|
|
362
|
+
k,
|
|
363
|
+
typeof parsed[k] === 'object'
|
|
364
|
+
? JSON.stringify(parsed[k])
|
|
365
|
+
: parsed[k],
|
|
366
|
+
),
|
|
367
|
+
);
|
|
368
|
+
hasRestoredData.current = true;
|
|
369
|
+
/* eslint-disable no-console */
|
|
370
|
+
console.log(
|
|
371
|
+
`[UserStorageManager:${instanceId.current}] Anonymous data restored to sessionStorage`,
|
|
372
|
+
);
|
|
373
|
+
/* eslint-enable no-console */
|
|
374
|
+
} else {
|
|
375
|
+
/* eslint-disable no-console */
|
|
376
|
+
console.log(
|
|
377
|
+
`[UserStorageManager:${instanceId.current}] No anonymous data found to restore`,
|
|
378
|
+
);
|
|
379
|
+
/* eslint-enable no-console */
|
|
380
|
+
}
|
|
381
|
+
setStorageManaged(true);
|
|
382
|
+
} else {
|
|
383
|
+
/* eslint-disable no-console */
|
|
384
|
+
console.log(
|
|
385
|
+
`[UserStorageManager:${instanceId.current}] User state does not match any processing condition - skipping storage logic`,
|
|
386
|
+
);
|
|
387
|
+
/* eslint-enable no-console */
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (onStorageManaged) {
|
|
391
|
+
onStorageManaged();
|
|
392
|
+
}
|
|
393
|
+
} finally {
|
|
394
|
+
// Always release the lock, even if an error occurred
|
|
395
|
+
releaseStorageLock(lockKey);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Cleanup function to release lock on unmount
|
|
399
|
+
return () => {
|
|
400
|
+
releaseStorageLock(lockKey);
|
|
401
|
+
};
|
|
402
|
+
}, [isInitialized, isLoggedIn, userID, storageManaged, onStorageManaged]);
|
|
403
|
+
|
|
404
|
+
// Data-reactive effect - ensures sessionStorage mirrors localStorage whenever userData changes
|
|
405
|
+
// useEffect(() => {
|
|
406
|
+
// /* eslint-disable no-console */
|
|
407
|
+
// console.log(
|
|
408
|
+
// `[UserStorageManager:${instanceId.current}] Data-reactive effect triggered:`,
|
|
409
|
+
// {
|
|
410
|
+
// storageManaged,
|
|
411
|
+
// userKey: userKey,
|
|
412
|
+
// hasUserData: !!userData,
|
|
413
|
+
// userDataLength: userData ? userData.length : 0,
|
|
414
|
+
// },
|
|
415
|
+
// );
|
|
416
|
+
// /* eslint-enable no-console */
|
|
417
|
+
|
|
418
|
+
// if (storageManaged && userKey && userData) {
|
|
419
|
+
// /* eslint-disable no-console */
|
|
420
|
+
// console.log(
|
|
421
|
+
// `[UserStorageManager:${instanceId.current}] Re-syncing session storage with localStorage changes`,
|
|
422
|
+
// );
|
|
423
|
+
// /* eslint-enable no-console */
|
|
424
|
+
// populateSessionFromUserData(userKey);
|
|
425
|
+
// } else {
|
|
426
|
+
// /* eslint-disable no-console */
|
|
427
|
+
// console.log(
|
|
428
|
+
// `[UserStorageManager:${instanceId.current}] Skipping data sync - conditions not met`,
|
|
429
|
+
// );
|
|
430
|
+
// /* eslint-enable no-console */
|
|
431
|
+
// }
|
|
432
|
+
// }, [userData, userKey, storageManaged]);
|
|
433
|
+
|
|
434
|
+
// Render only when properly initialized to prevent premature mounting
|
|
435
|
+
return isInitialized &&
|
|
436
|
+
storageManaged &&
|
|
437
|
+
userID !== undefined &&
|
|
438
|
+
isLoggedIn !== undefined ? (
|
|
439
|
+
children
|
|
440
|
+
) : (
|
|
441
|
+
<div className="loading-container">
|
|
442
|
+
<div className="loading-text">Loading...</div>
|
|
443
|
+
</div>
|
|
444
|
+
);
|
|
445
|
+
};
|
|
446
|
+
|
|
37
447
|
class MapViewer extends React.Component {
|
|
448
|
+
static contextType = UserContext;
|
|
449
|
+
|
|
38
450
|
/**
|
|
39
451
|
* This method does the creation of the main component
|
|
40
452
|
* @param {*} props
|
|
@@ -56,18 +468,28 @@ class MapViewer extends React.Component {
|
|
|
56
468
|
this.mapClass = classNames('map-container', {
|
|
57
469
|
[`${props.customClass}`]: props.customClass || null,
|
|
58
470
|
});
|
|
471
|
+
|
|
472
|
+
// Generate unique instance ID for tracking operations
|
|
473
|
+
this.instanceId = Math.random().toString(36).substring(7);
|
|
474
|
+
|
|
59
475
|
this.state = {
|
|
60
476
|
layerLoading: false,
|
|
61
477
|
layers: {},
|
|
62
478
|
uploadedFile: true,
|
|
63
479
|
wmsServiceUrl: '',
|
|
64
480
|
uploadError: false,
|
|
481
|
+
// Track current user state for comparison in componentDidUpdate
|
|
482
|
+
currentUserState: props.initialUserState || {
|
|
483
|
+
user_id: null,
|
|
484
|
+
isLoggedIn: false,
|
|
485
|
+
},
|
|
65
486
|
};
|
|
66
487
|
this.activeLayersHandler = this.activeLayersHandler.bind(this);
|
|
67
488
|
this.activeLayersArray = {};
|
|
68
489
|
this.props.mapviewer_config.loading = true;
|
|
69
490
|
this.cfgUrls = this.props.cfg.Urls;
|
|
70
|
-
|
|
491
|
+
// User state change handler
|
|
492
|
+
this.handleUserStateChange = this.handleUserStateChange.bind(this);
|
|
71
493
|
this.loadingHandler = this.loadingHandler.bind(this);
|
|
72
494
|
this.hotspotDataHandler = this.hotspotDataHandler.bind(this);
|
|
73
495
|
this.mapLayersHandler = this.mapLayersHandler.bind(this);
|
|
@@ -80,6 +502,285 @@ class MapViewer extends React.Component {
|
|
|
80
502
|
this.tax = null;
|
|
81
503
|
}
|
|
82
504
|
|
|
505
|
+
// Method to handle user state changes from the monitoring wrapper
|
|
506
|
+
handleUserStateChange(newUserState, previousUserState) {
|
|
507
|
+
/* eslint-disable no-console */
|
|
508
|
+
console.log('[MapViewer] Handling user state change:', {
|
|
509
|
+
newUserState,
|
|
510
|
+
previousUserState,
|
|
511
|
+
currentState: this.state.currentUserState,
|
|
512
|
+
});
|
|
513
|
+
/* eslint-enable no-console */
|
|
514
|
+
|
|
515
|
+
// Update current user state in component state
|
|
516
|
+
this.setState({ currentUserState: newUserState });
|
|
517
|
+
|
|
518
|
+
// Trigger session state update logic
|
|
519
|
+
this.handleSessionStateUpdate(newUserState, previousUserState);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Enhanced method to save session data to localStorage with instance tracking
|
|
523
|
+
saveSessionToLocalStorage = () => {
|
|
524
|
+
const { user_id, isLoggedIn } = this.context;
|
|
525
|
+
const instanceId = this.instanceId || 'unknown';
|
|
526
|
+
|
|
527
|
+
/* eslint-disable no-console */
|
|
528
|
+
console.log(`[MapViewer:${instanceId}] Saving session data:`, {
|
|
529
|
+
hasUserContext:
|
|
530
|
+
user_id !== undefined && user_id !== null && user_id !== '',
|
|
531
|
+
isLoggedIn,
|
|
532
|
+
userId:
|
|
533
|
+
user_id !== undefined && user_id !== null && user_id !== ''
|
|
534
|
+
? user_id
|
|
535
|
+
: 'anonymous',
|
|
536
|
+
sessionStorageLength: sessionStorage.length,
|
|
537
|
+
});
|
|
538
|
+
/* eslint-enable no-console */
|
|
539
|
+
|
|
540
|
+
if (sessionStorage.length === 0) {
|
|
541
|
+
/* eslint-disable no-console */
|
|
542
|
+
console.log(
|
|
543
|
+
`[MapViewer:${instanceId}] Skipping data save - session storage is empty`,
|
|
544
|
+
);
|
|
545
|
+
/* eslint-enable no-console */
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const saveToLocal = (key) => {
|
|
550
|
+
const data = {};
|
|
551
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
552
|
+
const k = sessionStorage.key(i);
|
|
553
|
+
data[k] = sessionStorage.getItem(k);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
try {
|
|
557
|
+
localStorage.setItem(key, JSON.stringify(data));
|
|
558
|
+
/* eslint-disable no-console */
|
|
559
|
+
console.log(
|
|
560
|
+
`[MapViewer:${instanceId}] Successfully saved data to localStorage key: ${key}`,
|
|
561
|
+
);
|
|
562
|
+
/* eslint-enable no-console */
|
|
563
|
+
} catch (error) {
|
|
564
|
+
/* eslint-disable no-console */
|
|
565
|
+
console.error(
|
|
566
|
+
`[MapViewer:${instanceId}] Error saving to localStorage key ${key}:`,
|
|
567
|
+
error,
|
|
568
|
+
);
|
|
569
|
+
/* eslint-enable no-console */
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
// Save session data to appropriate localStorage key based on user state (always overwrite user key)
|
|
574
|
+
if (
|
|
575
|
+
isLoggedIn &&
|
|
576
|
+
user_id !== undefined &&
|
|
577
|
+
user_id !== null &&
|
|
578
|
+
user_id !== ''
|
|
579
|
+
) {
|
|
580
|
+
if (localStorage.getItem(`user_${user_id}`)) {
|
|
581
|
+
/* eslint-disable no-console */
|
|
582
|
+
console.log(
|
|
583
|
+
`[MapViewer:${instanceId}] User session data already exists in localStorage`,
|
|
584
|
+
);
|
|
585
|
+
/* eslint-enable no-console */
|
|
586
|
+
}
|
|
587
|
+
saveToLocal(`user_${user_id}`);
|
|
588
|
+
} else if (!isLoggedIn) {
|
|
589
|
+
saveToLocal('user_anonymous');
|
|
590
|
+
}
|
|
591
|
+
sessionStorage.clear();
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
// Handle page unload events (navigation, refresh, close)
|
|
595
|
+
handlePageUnload = (event) => {
|
|
596
|
+
/* eslint-disable no-console */
|
|
597
|
+
console.log(
|
|
598
|
+
`[MapViewer:${this.instanceId}] Page unload detected, saving session data`,
|
|
599
|
+
);
|
|
600
|
+
/* eslint-enable no-console */
|
|
601
|
+
|
|
602
|
+
this.saveSessionToLocalStorage();
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
// Handle visibility changes (tab switches, window minimizing)
|
|
606
|
+
handleVisibilityChange = () => {
|
|
607
|
+
if (document.visibilityState === 'hidden') {
|
|
608
|
+
/* eslint-disable no-console */
|
|
609
|
+
console.log(
|
|
610
|
+
`[MapViewer:${this.instanceId}] Page hidden, saving session data`,
|
|
611
|
+
);
|
|
612
|
+
/* eslint-enable no-console */
|
|
613
|
+
|
|
614
|
+
this.saveSessionToLocalStorage();
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
// Method to handle session state updates when user authentication changes
|
|
619
|
+
handleSessionStateUpdate(newUserState, previousUserState) {
|
|
620
|
+
const { user_id: newUserId, isLoggedIn: newIsLoggedIn } = newUserState;
|
|
621
|
+
const {
|
|
622
|
+
user_id: prevUserId,
|
|
623
|
+
isLoggedIn: prevIsLoggedIn,
|
|
624
|
+
} = previousUserState;
|
|
625
|
+
|
|
626
|
+
/* eslint-disable no-console */
|
|
627
|
+
console.log('[MapViewer] Processing session state update:', {
|
|
628
|
+
userIdChanged: prevUserId !== newUserId,
|
|
629
|
+
loginStateChanged: prevIsLoggedIn !== newIsLoggedIn,
|
|
630
|
+
loginTransition: `${prevIsLoggedIn} -> ${newIsLoggedIn}`,
|
|
631
|
+
userIdTransition: `${prevUserId} -> ${newUserId}`,
|
|
632
|
+
});
|
|
633
|
+
/* eslint-enable no-console */
|
|
634
|
+
|
|
635
|
+
// Handle login/logout transitions
|
|
636
|
+
if (prevIsLoggedIn !== newIsLoggedIn) {
|
|
637
|
+
if (newIsLoggedIn && !prevIsLoggedIn) {
|
|
638
|
+
// User just logged in
|
|
639
|
+
this.handleUserLogin(newUserId);
|
|
640
|
+
} else if (!newIsLoggedIn && prevIsLoggedIn) {
|
|
641
|
+
// User just logged out
|
|
642
|
+
this.handleUserLogout(prevUserId);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Handle user ID changes (e.g., switching accounts)
|
|
647
|
+
if (prevUserId !== newUserId && newIsLoggedIn) {
|
|
648
|
+
this.handleUserSwitch(prevUserId, newUserId);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Handle user login - restore saved session or preserve current session
|
|
653
|
+
handleUserLogin(userId) {
|
|
654
|
+
/* eslint-disable no-console */
|
|
655
|
+
console.log('[MapViewer] User logged in:', userId);
|
|
656
|
+
/* eslint-enable no-console */
|
|
657
|
+
|
|
658
|
+
// Check if user has saved data
|
|
659
|
+
const userKey = `user_${userId}`;
|
|
660
|
+
const userData = localStorage.getItem(userKey);
|
|
661
|
+
|
|
662
|
+
if (userData) {
|
|
663
|
+
/* eslint-disable no-console */
|
|
664
|
+
console.log('[MapViewer] Restoring saved user session data');
|
|
665
|
+
/* eslint-enable no-console */
|
|
666
|
+
// Restore user's saved session
|
|
667
|
+
try {
|
|
668
|
+
const parsed = JSON.parse(userData);
|
|
669
|
+
Object.keys(parsed).forEach((k) =>
|
|
670
|
+
sessionStorage.setItem(
|
|
671
|
+
k,
|
|
672
|
+
typeof parsed[k] === 'object'
|
|
673
|
+
? JSON.stringify(parsed[k])
|
|
674
|
+
: parsed[k],
|
|
675
|
+
),
|
|
676
|
+
);
|
|
677
|
+
} catch (error) {
|
|
678
|
+
/* eslint-disable no-console */
|
|
679
|
+
console.error('[MapViewer] Error restoring user session:', error);
|
|
680
|
+
/* eslint-enable no-console */
|
|
681
|
+
}
|
|
682
|
+
} else {
|
|
683
|
+
/* eslint-disable no-console */
|
|
684
|
+
console.log(
|
|
685
|
+
'[MapViewer] No saved session found - preserving current session',
|
|
686
|
+
);
|
|
687
|
+
/* eslint-enable no-console */
|
|
688
|
+
// No saved data - preserve current session state
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Handle user logout - save current session to anonymous storage
|
|
693
|
+
handleUserLogout(prevUserId) {
|
|
694
|
+
/* eslint-disable no-console */
|
|
695
|
+
console.log('[MapViewer] User logged out:', prevUserId);
|
|
696
|
+
/* eslint-enable no-console */
|
|
697
|
+
|
|
698
|
+
// Save current session to anonymous storage
|
|
699
|
+
const sessionContents = {};
|
|
700
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
701
|
+
const key = sessionStorage.key(i);
|
|
702
|
+
sessionContents[key] = sessionStorage.getItem(key);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (Object.keys(sessionContents).length > 0) {
|
|
706
|
+
// Persist snapshot under previous user key first (if available)
|
|
707
|
+
if (prevUserId) {
|
|
708
|
+
try {
|
|
709
|
+
localStorage.setItem(
|
|
710
|
+
`user_${prevUserId}`,
|
|
711
|
+
JSON.stringify(sessionContents),
|
|
712
|
+
);
|
|
713
|
+
/* eslint-disable no-console */
|
|
714
|
+
console.log(
|
|
715
|
+
'[MapViewer] Saved session to previous user key before logout',
|
|
716
|
+
);
|
|
717
|
+
/* eslint-enable no-console */
|
|
718
|
+
} catch (error) {
|
|
719
|
+
/* eslint-disable no-console */
|
|
720
|
+
console.error(
|
|
721
|
+
'[MapViewer] Error saving previous user session on logout:',
|
|
722
|
+
error,
|
|
723
|
+
);
|
|
724
|
+
/* eslint-enable no-console */
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
try {
|
|
728
|
+
localStorage.setItem('user_anonymous', JSON.stringify(sessionContents));
|
|
729
|
+
/* eslint-disable no-console */
|
|
730
|
+
console.log('[MapViewer] Saved session to anonymous storage');
|
|
731
|
+
/* eslint-enable no-console */
|
|
732
|
+
} catch (error) {
|
|
733
|
+
/* eslint-disable no-console */
|
|
734
|
+
console.error('[MapViewer] Error saving anonymous session:', error);
|
|
735
|
+
/* eslint-enable no-console */
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Handle user account switching
|
|
741
|
+
handleUserSwitch(prevUserId, newUserId) {
|
|
742
|
+
/* eslint-disable no-console */
|
|
743
|
+
console.log('[MapViewer] User switched accounts:', {
|
|
744
|
+
prevUserId,
|
|
745
|
+
newUserId,
|
|
746
|
+
});
|
|
747
|
+
/* eslint-enable no-console */
|
|
748
|
+
|
|
749
|
+
// Save current session to previous user's storage
|
|
750
|
+
if (prevUserId) {
|
|
751
|
+
const sessionContents = {};
|
|
752
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
753
|
+
const key = sessionStorage.key(i);
|
|
754
|
+
sessionContents[key] = sessionStorage.getItem(key);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (Object.keys(sessionContents).length > 0) {
|
|
758
|
+
try {
|
|
759
|
+
localStorage.setItem(
|
|
760
|
+
`user_${prevUserId}`,
|
|
761
|
+
JSON.stringify(sessionContents),
|
|
762
|
+
);
|
|
763
|
+
/* eslint-disable no-console */
|
|
764
|
+
console.log(
|
|
765
|
+
'[MapViewer] Saved session for previous user:',
|
|
766
|
+
prevUserId,
|
|
767
|
+
);
|
|
768
|
+
/* eslint-enable no-console */
|
|
769
|
+
} catch (error) {
|
|
770
|
+
/* eslint-disable no-console */
|
|
771
|
+
console.error(
|
|
772
|
+
'[MapViewer] Error saving previous user session:',
|
|
773
|
+
error,
|
|
774
|
+
);
|
|
775
|
+
/* eslint-enable no-console */
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// Load new user's session
|
|
781
|
+
this.handleUserLogin(newUserId);
|
|
782
|
+
}
|
|
783
|
+
|
|
83
784
|
mapLayersHandler(newLayers) {
|
|
84
785
|
this.setState({ layers: newLayers });
|
|
85
786
|
}
|
|
@@ -249,13 +950,41 @@ class MapViewer extends React.Component {
|
|
|
249
950
|
logo: false,
|
|
250
951
|
});
|
|
251
952
|
|
|
953
|
+
/* eslint-disable no-console */
|
|
954
|
+
console.log(
|
|
955
|
+
`[MapViewer:${this.instanceId}] Attempting to recover map state from sessionStorage`,
|
|
956
|
+
);
|
|
957
|
+
/* eslint-enable no-console */
|
|
958
|
+
|
|
252
959
|
mapStatus = this.recoverState();
|
|
253
960
|
|
|
961
|
+
/* eslint-disable no-console */
|
|
962
|
+
console.log(`[MapViewer:${this.instanceId}] Recovered mapStatus:`, {
|
|
963
|
+
isNull: mapStatus === null,
|
|
964
|
+
isUndefined: mapStatus === undefined,
|
|
965
|
+
type: typeof mapStatus,
|
|
966
|
+
hasZoom: mapStatus && mapStatus.zoom !== undefined,
|
|
967
|
+
hasCenter: mapStatus && mapStatus.center !== undefined,
|
|
968
|
+
entryCount: mapStatus ? Object.entries(mapStatus).length : 0,
|
|
969
|
+
mapStatus: mapStatus,
|
|
970
|
+
});
|
|
971
|
+
/* eslint-enable no-console */
|
|
972
|
+
|
|
973
|
+
// Improved condition check to prevent false positives that overwrite restored data
|
|
254
974
|
if (
|
|
255
975
|
mapStatus === null ||
|
|
256
|
-
|
|
257
|
-
|
|
976
|
+
mapStatus === undefined ||
|
|
977
|
+
(typeof mapStatus === 'object' &&
|
|
978
|
+
mapStatus.zoom === undefined &&
|
|
979
|
+
mapStatus.center === undefined) ||
|
|
980
|
+
(typeof mapStatus === 'object' && Object.entries(mapStatus).length === 0)
|
|
258
981
|
) {
|
|
982
|
+
/* eslint-disable no-console */
|
|
983
|
+
console.log(
|
|
984
|
+
`[MapViewer:${this.instanceId}] No valid map state found - initializing with default configuration`,
|
|
985
|
+
);
|
|
986
|
+
/* eslint-enable no-console */
|
|
987
|
+
|
|
259
988
|
mapStatus = {};
|
|
260
989
|
mapStatus.zoom = this.mapCfg.zoom;
|
|
261
990
|
mapStatus.center = this.mapCfg.center;
|
|
@@ -263,6 +992,12 @@ class MapViewer extends React.Component {
|
|
|
263
992
|
this.setCenterState(this.mapCfg.center);
|
|
264
993
|
this.setZoomState(this.mapCfg.zoom);
|
|
265
994
|
this.activeLayersHandler(this.mapCfg.activeLayers);
|
|
995
|
+
} else {
|
|
996
|
+
/* eslint-disable no-console */
|
|
997
|
+
console.log(
|
|
998
|
+
`[MapViewer:${this.instanceId}] Using restored map state from sessionStorage`,
|
|
999
|
+
);
|
|
1000
|
+
/* eslint-enable no-console */
|
|
266
1001
|
}
|
|
267
1002
|
|
|
268
1003
|
this.view = new MapView({
|
|
@@ -320,15 +1055,26 @@ class MapViewer extends React.Component {
|
|
|
320
1055
|
// we will have stored the json response here:
|
|
321
1056
|
// this.props.mapviewer_config
|
|
322
1057
|
this.props.MapViewerConfig(flattenToAppURL(this.props.url));
|
|
323
|
-
|
|
324
|
-
//
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
1058
|
+
|
|
1059
|
+
// Add event listeners for page unload events to ensure data is saved
|
|
1060
|
+
/* eslint-disable no-console */
|
|
1061
|
+
console.log(
|
|
1062
|
+
`[MapViewer:${this.instanceId}] Adding page unload event listeners`,
|
|
1063
|
+
);
|
|
1064
|
+
/* eslint-enable no-console */
|
|
1065
|
+
|
|
1066
|
+
// beforeunload - most reliable for catching navigation away from page
|
|
1067
|
+
window.addEventListener('beforeunload', this.handlePageUnload);
|
|
1068
|
+
|
|
1069
|
+
// pagehide - more reliable than unload, catches all page hiding scenarios
|
|
1070
|
+
window.addEventListener('pagehide', this.handlePageUnload);
|
|
1071
|
+
|
|
1072
|
+
// visibilitychange - catches tab switches and window minimizing
|
|
1073
|
+
document.addEventListener('visibilitychange', this.handleVisibilityChange);
|
|
329
1074
|
}
|
|
330
1075
|
|
|
331
1076
|
componentDidUpdate(prevProps, prevState) {
|
|
1077
|
+
// Handle Download/dataset URL changes (existing logic)
|
|
332
1078
|
if (
|
|
333
1079
|
this.props.Download ||
|
|
334
1080
|
(this.location &&
|
|
@@ -341,17 +1087,63 @@ class MapViewer extends React.Component {
|
|
|
341
1087
|
}
|
|
342
1088
|
sessionStorage.setItem('toc_panel_scrolls', toc_panel_scrolls);
|
|
343
1089
|
}
|
|
344
|
-
|
|
345
|
-
//
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
1090
|
+
|
|
1091
|
+
// Handle user state changes from context
|
|
1092
|
+
const {
|
|
1093
|
+
user_id: currentUserId,
|
|
1094
|
+
isLoggedIn: currentIsLoggedIn,
|
|
1095
|
+
} = this.context;
|
|
1096
|
+
const { currentUserState: prevUserState } = prevState;
|
|
1097
|
+
|
|
1098
|
+
// Compare current context values with previous state
|
|
1099
|
+
if (
|
|
1100
|
+
prevUserState.user_id !== currentUserId ||
|
|
1101
|
+
prevUserState.isLoggedIn !== currentIsLoggedIn
|
|
1102
|
+
) {
|
|
1103
|
+
/* eslint-disable no-console */
|
|
1104
|
+
console.log(
|
|
1105
|
+
'[MapViewer] User state change detected in componentDidUpdate:',
|
|
1106
|
+
{
|
|
1107
|
+
previous: prevUserState,
|
|
1108
|
+
current: { user_id: currentUserId, isLoggedIn: currentIsLoggedIn },
|
|
1109
|
+
},
|
|
1110
|
+
);
|
|
1111
|
+
/* eslint-enable no-console */
|
|
1112
|
+
|
|
1113
|
+
// Update component state to reflect new user state
|
|
1114
|
+
const newUserState = {
|
|
1115
|
+
user_id: currentUserId,
|
|
1116
|
+
isLoggedIn: currentIsLoggedIn,
|
|
1117
|
+
};
|
|
1118
|
+
this.setState({ currentUserState: newUserState });
|
|
1119
|
+
|
|
1120
|
+
// Handle the user state change
|
|
1121
|
+
this.handleSessionStateUpdate(newUserState, prevUserState);
|
|
1122
|
+
}
|
|
351
1123
|
}
|
|
352
1124
|
|
|
353
1125
|
componentWillUnmount() {
|
|
354
|
-
//
|
|
1126
|
+
// Remove event listeners to prevent memory leaks
|
|
1127
|
+
/* eslint-disable no-console */
|
|
1128
|
+
console.log(`[MapViewer:${this.instanceId}] Removing event listeners`);
|
|
1129
|
+
/* eslint-enable no-console */
|
|
1130
|
+
|
|
1131
|
+
window.removeEventListener('beforeunload', this.handlePageUnload);
|
|
1132
|
+
window.removeEventListener('pagehide', this.handlePageUnload);
|
|
1133
|
+
document.removeEventListener(
|
|
1134
|
+
'visibilitychange',
|
|
1135
|
+
this.handleVisibilityChange,
|
|
1136
|
+
);
|
|
1137
|
+
|
|
1138
|
+
// Save data using the extracted method
|
|
1139
|
+
this.saveSessionToLocalStorage();
|
|
1140
|
+
|
|
1141
|
+
/* eslint-disable no-console */
|
|
1142
|
+
console.log(
|
|
1143
|
+
`[MapViewer:${this.instanceId}] Clearing session storage and cleaning up view`,
|
|
1144
|
+
);
|
|
1145
|
+
/* eslint-enable no-console */
|
|
1146
|
+
|
|
355
1147
|
if (this.view) {
|
|
356
1148
|
this.view.container = null;
|
|
357
1149
|
this.view.destroy();
|
|
@@ -554,7 +1346,7 @@ class MapViewer extends React.Component {
|
|
|
554
1346
|
if ('loading' in this.props.mapviewer_config) {
|
|
555
1347
|
return (
|
|
556
1348
|
<div className={this.mapClass}>
|
|
557
|
-
<div ref={this.mapdiv} className="map"
|
|
1349
|
+
<div ref={this.mapdiv} className="map" />
|
|
558
1350
|
</div>
|
|
559
1351
|
);
|
|
560
1352
|
} else {
|
|
@@ -572,10 +1364,8 @@ class MapViewer extends React.Component {
|
|
|
572
1364
|
{this.renderScale()}
|
|
573
1365
|
{this.renderInfo()}
|
|
574
1366
|
{this.renderHotspot()}
|
|
575
|
-
{/*this.renderMenu()*/}
|
|
576
|
-
{/*this.renderBookmark()*/}
|
|
577
|
-
<CheckUserID reference={this} />
|
|
578
1367
|
{this.renderLoadingSpinner()}
|
|
1368
|
+
<CheckUserID reference={this} />
|
|
579
1369
|
{this.renderUploadService()}
|
|
580
1370
|
</div>
|
|
581
1371
|
</div>
|
|
@@ -584,8 +1374,10 @@ class MapViewer extends React.Component {
|
|
|
584
1374
|
}
|
|
585
1375
|
}
|
|
586
1376
|
|
|
1377
|
+
// CheckLogin, CheckUserID now use useUserContext instead of useCartState
|
|
1378
|
+
|
|
587
1379
|
export const CheckLogin = ({ reference }) => {
|
|
588
|
-
|
|
1380
|
+
const { isLoggedIn } = useUserContext();
|
|
589
1381
|
return (
|
|
590
1382
|
<>
|
|
591
1383
|
{isLoggedIn && (
|
|
@@ -606,9 +1398,9 @@ export const CheckLogin = ({ reference }) => {
|
|
|
606
1398
|
</>
|
|
607
1399
|
);
|
|
608
1400
|
};
|
|
609
|
-
export const CheckUserID = ({ reference }) => {
|
|
610
|
-
let { user_id } = useCartState();
|
|
611
1401
|
|
|
1402
|
+
export const CheckUserID = ({ reference }) => {
|
|
1403
|
+
const { user_id, isLoggedIn } = useUserContext();
|
|
612
1404
|
return (
|
|
613
1405
|
<>
|
|
614
1406
|
{reference.view && (
|
|
@@ -653,6 +1445,7 @@ export const CheckUserID = ({ reference }) => {
|
|
|
653
1445
|
onServiceChange={reference.serviceChangeHandler}
|
|
654
1446
|
uploadFileErrorHandler={reference.uploadFileErrorHandler}
|
|
655
1447
|
userID={user_id}
|
|
1448
|
+
isLoggedIn={isLoggedIn}
|
|
656
1449
|
getTaxonomy={reference.getTaxonomy}
|
|
657
1450
|
tax={reference.tax}
|
|
658
1451
|
/>
|
|
@@ -661,10 +1454,47 @@ export const CheckUserID = ({ reference }) => {
|
|
|
661
1454
|
</>
|
|
662
1455
|
);
|
|
663
1456
|
};
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
1457
|
+
|
|
1458
|
+
const mapDispatchToProps = (dispatch) => ({
|
|
1459
|
+
getTaxonomy: (name) => dispatch(getTaxonomy(name)),
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1462
|
+
const MapViewerWithProvider = (props) => {
|
|
1463
|
+
const mapViewerRef = useRef(null);
|
|
1464
|
+
const cartState = useCartState();
|
|
1465
|
+
|
|
1466
|
+
// Get initial user state
|
|
1467
|
+
const initialUserState = {
|
|
1468
|
+
user_id: cartState?.user_id || null,
|
|
1469
|
+
isLoggedIn: cartState?.isLoggedIn || false,
|
|
667
1470
|
};
|
|
1471
|
+
|
|
1472
|
+
// Handle user state changes from the monitor
|
|
1473
|
+
const handleUserStateChange = useCallback(
|
|
1474
|
+
(newUserState, previousUserState) => {
|
|
1475
|
+
if (mapViewerRef.current) {
|
|
1476
|
+
mapViewerRef.current.handleUserStateChange(
|
|
1477
|
+
newUserState,
|
|
1478
|
+
previousUserState,
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
},
|
|
1482
|
+
[],
|
|
1483
|
+
);
|
|
1484
|
+
|
|
1485
|
+
return (
|
|
1486
|
+
<UserProvider>
|
|
1487
|
+
<UserStorageManager>
|
|
1488
|
+
<MapViewerStateMonitor onUserStateChange={handleUserStateChange}>
|
|
1489
|
+
<MapViewer
|
|
1490
|
+
{...props}
|
|
1491
|
+
ref={mapViewerRef}
|
|
1492
|
+
initialUserState={initialUserState}
|
|
1493
|
+
/>
|
|
1494
|
+
</MapViewerStateMonitor>
|
|
1495
|
+
</UserStorageManager>
|
|
1496
|
+
</UserProvider>
|
|
1497
|
+
);
|
|
668
1498
|
};
|
|
669
1499
|
|
|
670
1500
|
export default compose(
|
|
@@ -676,4 +1506,4 @@ export default compose(
|
|
|
676
1506
|
),
|
|
677
1507
|
connect(null, mapDispatchToProps),
|
|
678
1508
|
injectLazyLibs('highcharts'),
|
|
679
|
-
)(
|
|
1509
|
+
)(MapViewerWithProvider, MenuWidget);
|
|
@@ -1093,6 +1093,7 @@ class MenuWidget extends React.Component {
|
|
|
1093
1093
|
let button = familyDropdown.querySelector(
|
|
1094
1094
|
'.ccl-expandable__button',
|
|
1095
1095
|
);
|
|
1096
|
+
scrollPosition = familyDropdown.offsetTop;
|
|
1096
1097
|
if (button) {
|
|
1097
1098
|
button.setAttribute('aria-expanded', 'true');
|
|
1098
1099
|
}
|
|
@@ -2317,6 +2318,7 @@ class MenuWidget extends React.Component {
|
|
|
2317
2318
|
this.props.uploadFileErrorHandler();
|
|
2318
2319
|
return;
|
|
2319
2320
|
}
|
|
2321
|
+
} else {
|
|
2320
2322
|
}
|
|
2321
2323
|
}
|
|
2322
2324
|
|
|
@@ -2434,7 +2436,7 @@ class MenuWidget extends React.Component {
|
|
|
2434
2436
|
}
|
|
2435
2437
|
|
|
2436
2438
|
saveUserServicesToStorage(layers) {
|
|
2437
|
-
if (this.userID
|
|
2439
|
+
if (this.userID === null) return;
|
|
2438
2440
|
|
|
2439
2441
|
try {
|
|
2440
2442
|
const layersToSave = layers.map((layer) => {
|
|
@@ -2477,8 +2479,7 @@ class MenuWidget extends React.Component {
|
|
|
2477
2479
|
}
|
|
2478
2480
|
|
|
2479
2481
|
async loadUserServicesFromStorage() {
|
|
2480
|
-
if (this.userID
|
|
2481
|
-
sessionStorage.clear();
|
|
2482
|
+
if (this.userID !== null) {
|
|
2482
2483
|
try {
|
|
2483
2484
|
const savedServices = JSON.parse(
|
|
2484
2485
|
localStorage.getItem(USER_SERVICES_KEY + '_' + this.userID),
|
|
@@ -3361,33 +3362,24 @@ class MenuWidget extends React.Component {
|
|
|
3361
3362
|
try {
|
|
3362
3363
|
const response = await fetch(`${subLayer.url}?f=pjson`);
|
|
3363
3364
|
if (!response.ok) {
|
|
3364
|
-
|
|
3365
|
-
continue; // Skip this iteration on error
|
|
3365
|
+
continue;
|
|
3366
3366
|
}
|
|
3367
|
-
const subLayerData = await response.json();
|
|
3367
|
+
const subLayerData = await response.json();
|
|
3368
3368
|
if (subLayerData === null) {
|
|
3369
|
-
//console.log('no data retrieved:', subLayerData);
|
|
3370
3369
|
continue;
|
|
3371
3370
|
} else {
|
|
3372
|
-
// Convert bounding box data to correct extent values for map view
|
|
3373
|
-
|
|
3374
3371
|
let extent = this.convertBBOXValues(subLayerData.extent);
|
|
3375
|
-
|
|
3376
|
-
// Store sublayer extent
|
|
3377
|
-
|
|
3378
3372
|
BBoxes[subLayerData.name] = {
|
|
3379
3373
|
id: subLayerData.id,
|
|
3380
3374
|
extent: extent,
|
|
3381
3375
|
};
|
|
3382
3376
|
}
|
|
3383
|
-
} catch (error) {
|
|
3384
|
-
//console.error('Error fetching sublayer:', error);
|
|
3385
|
-
}
|
|
3377
|
+
} catch (error) {}
|
|
3386
3378
|
}
|
|
3387
3379
|
|
|
3388
3380
|
BBoxes['dataset'] = this.convertBBOXValues(layer?.fullExtent?.extent);
|
|
3389
3381
|
|
|
3390
|
-
return BBoxes;
|
|
3382
|
+
return BBoxes;
|
|
3391
3383
|
}
|
|
3392
3384
|
|
|
3393
3385
|
parseBBOXWMS(xml) {
|