@trackunit/react-core-hooks 1.12.67 → 1.13.0
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/index.cjs.js +166 -41
- package/index.esm.js +168 -44
- package/package.json +4 -4
- package/src/geolocation/useGeolocation.d.ts +23 -0
- package/src/index.d.ts +1 -0
package/index.cjs.js
CHANGED
|
@@ -230,6 +230,99 @@ const useFilterBarContext = () => {
|
|
|
230
230
|
return context;
|
|
231
231
|
};
|
|
232
232
|
|
|
233
|
+
const useStandaloneGeolocation = ({ enabled, requestOnMount, }) => {
|
|
234
|
+
const [position, setPosition] = react.useState(null);
|
|
235
|
+
const queryGeolocationPermission = react.useCallback(async () => {
|
|
236
|
+
if (!enabled || typeof navigator === "undefined") {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
const status = await navigator.permissions.query({ name: "geolocation" });
|
|
241
|
+
return status.state;
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}, [enabled]);
|
|
247
|
+
const getCurrentPosition = react.useCallback(async () => {
|
|
248
|
+
if (!enabled || typeof navigator === "undefined") {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
return await new Promise(resolve => {
|
|
252
|
+
try {
|
|
253
|
+
navigator.geolocation.getCurrentPosition(currentPosition => {
|
|
254
|
+
resolve([currentPosition.coords.longitude, currentPosition.coords.latitude]);
|
|
255
|
+
}, () => {
|
|
256
|
+
resolve(null);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
resolve(null);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}, [enabled]);
|
|
264
|
+
const getPosition = react.useCallback(async (options) => {
|
|
265
|
+
const prompt = options?.prompt !== false;
|
|
266
|
+
if (!prompt) {
|
|
267
|
+
const permissionState = await queryGeolocationPermission();
|
|
268
|
+
if (permissionState !== "granted") {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const currentPosition = await getCurrentPosition();
|
|
273
|
+
if (currentPosition !== null) {
|
|
274
|
+
setPosition(currentPosition);
|
|
275
|
+
}
|
|
276
|
+
return currentPosition;
|
|
277
|
+
}, [getCurrentPosition, queryGeolocationPermission]);
|
|
278
|
+
react.useEffect(() => {
|
|
279
|
+
if (!enabled || requestOnMount !== true) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
void queryGeolocationPermission()
|
|
283
|
+
.then(permissionState => {
|
|
284
|
+
if (permissionState !== "granted") {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
return getCurrentPosition();
|
|
288
|
+
})
|
|
289
|
+
.then(currentPosition => {
|
|
290
|
+
if (currentPosition !== null) {
|
|
291
|
+
setPosition(currentPosition);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}, [enabled, getCurrentPosition, queryGeolocationPermission, requestOnMount]);
|
|
295
|
+
return react.useMemo(() => ({
|
|
296
|
+
position,
|
|
297
|
+
getPosition,
|
|
298
|
+
}), [getPosition, position]);
|
|
299
|
+
};
|
|
300
|
+
/**
|
|
301
|
+
* Hook providing geolocation capabilities.
|
|
302
|
+
*
|
|
303
|
+
* In the host, geolocation is resolved directly via the browser API.
|
|
304
|
+
* In Iris Apps, requests are proxied to the host via the iframe bridge,
|
|
305
|
+
* so permission is only requested once at the host level.
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```ts
|
|
309
|
+
* const { position, getPosition } = useGeolocation();
|
|
310
|
+
*
|
|
311
|
+
* // User-initiated request (e.g. "My Location" button)
|
|
312
|
+
* onClick: () => {
|
|
313
|
+
* void getPosition().then(pos => { if (pos) fitBounds(pos); });
|
|
314
|
+
* }
|
|
315
|
+
* ```
|
|
316
|
+
*/
|
|
317
|
+
const useGeolocation = (options) => {
|
|
318
|
+
const context = react.useContext(reactCoreContextsApi.GeolocationContext);
|
|
319
|
+
const standaloneGeolocation = useStandaloneGeolocation({
|
|
320
|
+
enabled: context === null,
|
|
321
|
+
requestOnMount: options?.requestOnMount,
|
|
322
|
+
});
|
|
323
|
+
return context ?? standaloneGeolocation;
|
|
324
|
+
};
|
|
325
|
+
|
|
233
326
|
/**
|
|
234
327
|
* This is a hook to use the TokenContext.
|
|
235
328
|
*
|
|
@@ -396,7 +489,7 @@ const resolveAccess = async (context, options) => {
|
|
|
396
489
|
}
|
|
397
490
|
return context.hasAccessTo(options);
|
|
398
491
|
};
|
|
399
|
-
const INITIAL_STATE = {
|
|
492
|
+
const INITIAL_STATE$1 = {
|
|
400
493
|
status: "loading",
|
|
401
494
|
hasAccess: undefined,
|
|
402
495
|
error: undefined,
|
|
@@ -404,7 +497,7 @@ const INITIAL_STATE = {
|
|
|
404
497
|
const accessReducer = (_state, action) => {
|
|
405
498
|
switch (action.type) {
|
|
406
499
|
case "FETCH_START":
|
|
407
|
-
return INITIAL_STATE;
|
|
500
|
+
return INITIAL_STATE$1;
|
|
408
501
|
case "FETCH_SUCCESS":
|
|
409
502
|
return { status: "success", hasAccess: action.hasAccess, error: undefined };
|
|
410
503
|
case "FETCH_ERROR":
|
|
@@ -427,7 +520,7 @@ const accessReducer = (_state, action) => {
|
|
|
427
520
|
const useHasAccessTo = (options) => {
|
|
428
521
|
const context = react.useContext(reactCoreContextsApi.NavigationContext);
|
|
429
522
|
const [stableOptions, setStableOptions] = react.useState(options);
|
|
430
|
-
const [state, dispatch] = react.useReducer(accessReducer, INITIAL_STATE);
|
|
523
|
+
const [state, dispatch] = react.useReducer(accessReducer, INITIAL_STATE$1);
|
|
431
524
|
if (!esToolkit.isEqual(stableOptions, options)) {
|
|
432
525
|
setStableOptions(options);
|
|
433
526
|
}
|
|
@@ -576,6 +669,17 @@ const useCustomerRuntime = () => {
|
|
|
576
669
|
return { customerInfo, loading, error };
|
|
577
670
|
};
|
|
578
671
|
|
|
672
|
+
const eventRuntimeReducer = (_state, action) => {
|
|
673
|
+
switch (action.type) {
|
|
674
|
+
case "success":
|
|
675
|
+
return { status: "success", eventInfo: action.eventInfo };
|
|
676
|
+
case "error":
|
|
677
|
+
return { status: "error", error: action.error };
|
|
678
|
+
default:
|
|
679
|
+
return _state;
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
const INITIAL_STATE = { status: "loading" };
|
|
579
683
|
/**
|
|
580
684
|
* A hook to expose event runtime for React components
|
|
581
685
|
*
|
|
@@ -594,25 +698,17 @@ const useCustomerRuntime = () => {
|
|
|
594
698
|
* }, [getEventQuery, eventInfo]);
|
|
595
699
|
*/
|
|
596
700
|
const useEventRuntime = () => {
|
|
597
|
-
const [
|
|
598
|
-
const [loading, setLoading] = react.useState(true);
|
|
599
|
-
const [error, setError] = react.useState();
|
|
701
|
+
const [state, dispatch] = react.useReducer(eventRuntimeReducer, INITIAL_STATE);
|
|
600
702
|
react.useEffect(() => {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
const updatedEventInfo = await irisAppRuntimeCore.EventRuntime.getEventInfo();
|
|
605
|
-
setLoading(false);
|
|
606
|
-
setEventInfo(updatedEventInfo);
|
|
607
|
-
}
|
|
608
|
-
catch (e) {
|
|
609
|
-
setLoading(false);
|
|
610
|
-
setError(new Error("Failed to get event info"));
|
|
611
|
-
}
|
|
612
|
-
};
|
|
613
|
-
void getEventInfo();
|
|
703
|
+
void irisAppRuntimeCore.EventRuntime.getEventInfo()
|
|
704
|
+
.then(eventInfo => dispatch({ type: "success", eventInfo }))
|
|
705
|
+
.catch(() => dispatch({ type: "error", error: new Error("Failed to get event info") }));
|
|
614
706
|
}, []);
|
|
615
|
-
return {
|
|
707
|
+
return {
|
|
708
|
+
eventInfo: state.status === "success" ? state.eventInfo : undefined,
|
|
709
|
+
loading: state.status === "loading",
|
|
710
|
+
error: state.status === "error" ? state.error : undefined,
|
|
711
|
+
};
|
|
616
712
|
};
|
|
617
713
|
|
|
618
714
|
/**
|
|
@@ -981,6 +1077,28 @@ const useWidgetConfigAsync = () => {
|
|
|
981
1077
|
}
|
|
982
1078
|
return context;
|
|
983
1079
|
};
|
|
1080
|
+
const widgetDataReducer = (state, action) => {
|
|
1081
|
+
switch (action.type) {
|
|
1082
|
+
case "dataLoaded":
|
|
1083
|
+
return { ...state, data: action.data, loadingData: false };
|
|
1084
|
+
case "dataVersionLoaded":
|
|
1085
|
+
return { ...state, dataVersion: action.dataVersion };
|
|
1086
|
+
case "titleLoaded":
|
|
1087
|
+
return { ...state, title: action.title };
|
|
1088
|
+
case "dataUpdated":
|
|
1089
|
+
return { ...state, data: action.data, dataVersion: action.dataVersion };
|
|
1090
|
+
case "titleUpdated":
|
|
1091
|
+
return { ...state, title: action.title };
|
|
1092
|
+
default:
|
|
1093
|
+
return state;
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1096
|
+
const INITIAL_WIDGET_DATA_STATE = {
|
|
1097
|
+
data: null,
|
|
1098
|
+
loadingData: true,
|
|
1099
|
+
dataVersion: null,
|
|
1100
|
+
title: null,
|
|
1101
|
+
};
|
|
984
1102
|
/**
|
|
985
1103
|
* This is a hook to use the WidgetConfigContext.
|
|
986
1104
|
*
|
|
@@ -996,13 +1114,9 @@ const useWidgetConfigAsync = () => {
|
|
|
996
1114
|
*/
|
|
997
1115
|
const useWidgetConfig = () => {
|
|
998
1116
|
const widgetConfigContext = useWidgetConfigAsync();
|
|
999
|
-
const [
|
|
1000
|
-
const [loadingData, setLoadingData] = react.useState(true);
|
|
1001
|
-
const [dataVersion, setDataVersion] = react.useState(null);
|
|
1002
|
-
const [title, setTitle] = react.useState(null);
|
|
1117
|
+
const [widgetData, dispatch] = react.useReducer(widgetDataReducer, INITIAL_WIDGET_DATA_STATE);
|
|
1003
1118
|
const filters = useFilterBarContext();
|
|
1004
1119
|
const { timeRange } = useTimeRange();
|
|
1005
|
-
// use window.location.hash directly to avoid depending on tanstack router in core-hooks
|
|
1006
1120
|
const [edit, setEdit] = react.useState(() => window.location.hash.includes("edit=true"));
|
|
1007
1121
|
react.useEffect(() => {
|
|
1008
1122
|
const handleHashChange = () => {
|
|
@@ -1013,29 +1127,27 @@ const useWidgetConfig = () => {
|
|
|
1013
1127
|
}, []);
|
|
1014
1128
|
const widgetConfigContextRef = react.useRef(widgetConfigContext);
|
|
1015
1129
|
react.useEffect(() => {
|
|
1016
|
-
void widgetConfigContextRef.current.getData().then(d => {
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
void widgetConfigContextRef.current.
|
|
1021
|
-
void widgetConfigContextRef.current.getTitle().then(t => setTitle(t));
|
|
1130
|
+
void widgetConfigContextRef.current.getData().then(d => dispatch({ type: "dataLoaded", data: d }));
|
|
1131
|
+
void widgetConfigContextRef.current
|
|
1132
|
+
.getDataVersion()
|
|
1133
|
+
.then(dv => dispatch({ type: "dataVersionLoaded", dataVersion: dv }));
|
|
1134
|
+
void widgetConfigContextRef.current.getTitle().then(t => dispatch({ type: "titleLoaded", title: t }));
|
|
1022
1135
|
}, []);
|
|
1023
1136
|
const result = react.useMemo(() => ({
|
|
1024
|
-
data,
|
|
1137
|
+
data: widgetData.data,
|
|
1025
1138
|
setData: async (newData, newDataVersion) => {
|
|
1026
1139
|
await widgetConfigContext.setData(newData, newDataVersion);
|
|
1027
|
-
|
|
1028
|
-
setDataVersion(newDataVersion);
|
|
1140
|
+
dispatch({ type: "dataUpdated", data: newData, dataVersion: newDataVersion });
|
|
1029
1141
|
},
|
|
1030
|
-
dataVersion,
|
|
1031
|
-
loadingData,
|
|
1142
|
+
dataVersion: widgetData.dataVersion,
|
|
1143
|
+
loadingData: widgetData.loadingData,
|
|
1032
1144
|
setLoadingState: async (newLoadingState) => {
|
|
1033
1145
|
await widgetConfigContext.setLoadingState(newLoadingState);
|
|
1034
1146
|
},
|
|
1035
|
-
title,
|
|
1147
|
+
title: widgetData.title,
|
|
1036
1148
|
setTitle: async (newTitle) => {
|
|
1037
1149
|
await widgetConfigContext.setTitle(newTitle);
|
|
1038
|
-
|
|
1150
|
+
dispatch({ type: "titleUpdated", title: newTitle });
|
|
1039
1151
|
},
|
|
1040
1152
|
filters,
|
|
1041
1153
|
timeRange,
|
|
@@ -1048,13 +1160,25 @@ const useWidgetConfig = () => {
|
|
|
1048
1160
|
if (props) {
|
|
1049
1161
|
if (props.newData && props.newData.data) {
|
|
1050
1162
|
await irisAppRuntimeCore.WidgetConfigRuntime.setWidgetData(props.newData.data, props.newData.dataVersion ?? 1, props.newTitle ?? undefined);
|
|
1051
|
-
|
|
1052
|
-
|
|
1163
|
+
dispatch({
|
|
1164
|
+
type: "dataUpdated",
|
|
1165
|
+
data: props.newData.data,
|
|
1166
|
+
dataVersion: props.newData.dataVersion ?? 1,
|
|
1167
|
+
});
|
|
1053
1168
|
}
|
|
1054
1169
|
}
|
|
1055
1170
|
await widgetConfigContext.closeEditMode();
|
|
1056
1171
|
},
|
|
1057
|
-
}), [
|
|
1172
|
+
}), [
|
|
1173
|
+
widgetData.data,
|
|
1174
|
+
widgetData.dataVersion,
|
|
1175
|
+
widgetData.title,
|
|
1176
|
+
filters,
|
|
1177
|
+
timeRange,
|
|
1178
|
+
edit,
|
|
1179
|
+
widgetData.loadingData,
|
|
1180
|
+
widgetConfigContext,
|
|
1181
|
+
]);
|
|
1058
1182
|
return result;
|
|
1059
1183
|
};
|
|
1060
1184
|
|
|
@@ -1078,6 +1202,7 @@ exports.useExportDataContext = useExportDataContext;
|
|
|
1078
1202
|
exports.useFeatureBranchQueryString = useFeatureBranchQueryString;
|
|
1079
1203
|
exports.useFeatureFlags = useFeatureFlags;
|
|
1080
1204
|
exports.useFilterBarContext = useFilterBarContext;
|
|
1205
|
+
exports.useGeolocation = useGeolocation;
|
|
1081
1206
|
exports.useHasAccessTo = useHasAccessTo;
|
|
1082
1207
|
exports.useImageUploader = useImageUploader;
|
|
1083
1208
|
exports.useIrisAppId = useIrisAppId;
|
package/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AnalyticsContext, AssetSortingContext, ConfirmationDialogContext, EnvironmentContext, ErrorHandlingContext, ExportDataContext, FeatureFlagContext, FilterBarContext, TokenContext, ModalDialogContext, NavigationContext, OemBrandingContext, UserSubscriptionContext, TimeRangeContext, ToastContext, CurrentUserContext, CurrentUserPreferenceContext, WidgetConfigContext } from '@trackunit/react-core-contexts-api';
|
|
2
|
-
import { useContext, useMemo, useState, useCallback,
|
|
1
|
+
import { AnalyticsContext, AssetSortingContext, ConfirmationDialogContext, EnvironmentContext, ErrorHandlingContext, ExportDataContext, FeatureFlagContext, FilterBarContext, GeolocationContext, TokenContext, ModalDialogContext, NavigationContext, OemBrandingContext, UserSubscriptionContext, TimeRangeContext, ToastContext, CurrentUserContext, CurrentUserPreferenceContext, WidgetConfigContext } from '@trackunit/react-core-contexts-api';
|
|
2
|
+
import { useContext, useMemo, useState, useCallback, useEffect, useReducer, useRef } from 'react';
|
|
3
3
|
import { isEqual } from 'es-toolkit';
|
|
4
4
|
import { AssetRuntime, CustomerRuntime, EventRuntime, ParamsRuntime, SiteRuntime, WidgetConfigRuntime } from '@trackunit/iris-app-runtime-core';
|
|
5
5
|
|
|
@@ -228,6 +228,99 @@ const useFilterBarContext = () => {
|
|
|
228
228
|
return context;
|
|
229
229
|
};
|
|
230
230
|
|
|
231
|
+
const useStandaloneGeolocation = ({ enabled, requestOnMount, }) => {
|
|
232
|
+
const [position, setPosition] = useState(null);
|
|
233
|
+
const queryGeolocationPermission = useCallback(async () => {
|
|
234
|
+
if (!enabled || typeof navigator === "undefined") {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
const status = await navigator.permissions.query({ name: "geolocation" });
|
|
239
|
+
return status.state;
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}, [enabled]);
|
|
245
|
+
const getCurrentPosition = useCallback(async () => {
|
|
246
|
+
if (!enabled || typeof navigator === "undefined") {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
return await new Promise(resolve => {
|
|
250
|
+
try {
|
|
251
|
+
navigator.geolocation.getCurrentPosition(currentPosition => {
|
|
252
|
+
resolve([currentPosition.coords.longitude, currentPosition.coords.latitude]);
|
|
253
|
+
}, () => {
|
|
254
|
+
resolve(null);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
resolve(null);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}, [enabled]);
|
|
262
|
+
const getPosition = useCallback(async (options) => {
|
|
263
|
+
const prompt = options?.prompt !== false;
|
|
264
|
+
if (!prompt) {
|
|
265
|
+
const permissionState = await queryGeolocationPermission();
|
|
266
|
+
if (permissionState !== "granted") {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const currentPosition = await getCurrentPosition();
|
|
271
|
+
if (currentPosition !== null) {
|
|
272
|
+
setPosition(currentPosition);
|
|
273
|
+
}
|
|
274
|
+
return currentPosition;
|
|
275
|
+
}, [getCurrentPosition, queryGeolocationPermission]);
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
if (!enabled || requestOnMount !== true) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
void queryGeolocationPermission()
|
|
281
|
+
.then(permissionState => {
|
|
282
|
+
if (permissionState !== "granted") {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
return getCurrentPosition();
|
|
286
|
+
})
|
|
287
|
+
.then(currentPosition => {
|
|
288
|
+
if (currentPosition !== null) {
|
|
289
|
+
setPosition(currentPosition);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}, [enabled, getCurrentPosition, queryGeolocationPermission, requestOnMount]);
|
|
293
|
+
return useMemo(() => ({
|
|
294
|
+
position,
|
|
295
|
+
getPosition,
|
|
296
|
+
}), [getPosition, position]);
|
|
297
|
+
};
|
|
298
|
+
/**
|
|
299
|
+
* Hook providing geolocation capabilities.
|
|
300
|
+
*
|
|
301
|
+
* In the host, geolocation is resolved directly via the browser API.
|
|
302
|
+
* In Iris Apps, requests are proxied to the host via the iframe bridge,
|
|
303
|
+
* so permission is only requested once at the host level.
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```ts
|
|
307
|
+
* const { position, getPosition } = useGeolocation();
|
|
308
|
+
*
|
|
309
|
+
* // User-initiated request (e.g. "My Location" button)
|
|
310
|
+
* onClick: () => {
|
|
311
|
+
* void getPosition().then(pos => { if (pos) fitBounds(pos); });
|
|
312
|
+
* }
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
315
|
+
const useGeolocation = (options) => {
|
|
316
|
+
const context = useContext(GeolocationContext);
|
|
317
|
+
const standaloneGeolocation = useStandaloneGeolocation({
|
|
318
|
+
enabled: context === null,
|
|
319
|
+
requestOnMount: options?.requestOnMount,
|
|
320
|
+
});
|
|
321
|
+
return context ?? standaloneGeolocation;
|
|
322
|
+
};
|
|
323
|
+
|
|
231
324
|
/**
|
|
232
325
|
* This is a hook to use the TokenContext.
|
|
233
326
|
*
|
|
@@ -394,7 +487,7 @@ const resolveAccess = async (context, options) => {
|
|
|
394
487
|
}
|
|
395
488
|
return context.hasAccessTo(options);
|
|
396
489
|
};
|
|
397
|
-
const INITIAL_STATE = {
|
|
490
|
+
const INITIAL_STATE$1 = {
|
|
398
491
|
status: "loading",
|
|
399
492
|
hasAccess: undefined,
|
|
400
493
|
error: undefined,
|
|
@@ -402,7 +495,7 @@ const INITIAL_STATE = {
|
|
|
402
495
|
const accessReducer = (_state, action) => {
|
|
403
496
|
switch (action.type) {
|
|
404
497
|
case "FETCH_START":
|
|
405
|
-
return INITIAL_STATE;
|
|
498
|
+
return INITIAL_STATE$1;
|
|
406
499
|
case "FETCH_SUCCESS":
|
|
407
500
|
return { status: "success", hasAccess: action.hasAccess, error: undefined };
|
|
408
501
|
case "FETCH_ERROR":
|
|
@@ -425,7 +518,7 @@ const accessReducer = (_state, action) => {
|
|
|
425
518
|
const useHasAccessTo = (options) => {
|
|
426
519
|
const context = useContext(NavigationContext);
|
|
427
520
|
const [stableOptions, setStableOptions] = useState(options);
|
|
428
|
-
const [state, dispatch] = useReducer(accessReducer, INITIAL_STATE);
|
|
521
|
+
const [state, dispatch] = useReducer(accessReducer, INITIAL_STATE$1);
|
|
429
522
|
if (!isEqual(stableOptions, options)) {
|
|
430
523
|
setStableOptions(options);
|
|
431
524
|
}
|
|
@@ -574,6 +667,17 @@ const useCustomerRuntime = () => {
|
|
|
574
667
|
return { customerInfo, loading, error };
|
|
575
668
|
};
|
|
576
669
|
|
|
670
|
+
const eventRuntimeReducer = (_state, action) => {
|
|
671
|
+
switch (action.type) {
|
|
672
|
+
case "success":
|
|
673
|
+
return { status: "success", eventInfo: action.eventInfo };
|
|
674
|
+
case "error":
|
|
675
|
+
return { status: "error", error: action.error };
|
|
676
|
+
default:
|
|
677
|
+
return _state;
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
const INITIAL_STATE = { status: "loading" };
|
|
577
681
|
/**
|
|
578
682
|
* A hook to expose event runtime for React components
|
|
579
683
|
*
|
|
@@ -592,25 +696,17 @@ const useCustomerRuntime = () => {
|
|
|
592
696
|
* }, [getEventQuery, eventInfo]);
|
|
593
697
|
*/
|
|
594
698
|
const useEventRuntime = () => {
|
|
595
|
-
const [
|
|
596
|
-
const [loading, setLoading] = useState(true);
|
|
597
|
-
const [error, setError] = useState();
|
|
699
|
+
const [state, dispatch] = useReducer(eventRuntimeReducer, INITIAL_STATE);
|
|
598
700
|
useEffect(() => {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const updatedEventInfo = await EventRuntime.getEventInfo();
|
|
603
|
-
setLoading(false);
|
|
604
|
-
setEventInfo(updatedEventInfo);
|
|
605
|
-
}
|
|
606
|
-
catch (e) {
|
|
607
|
-
setLoading(false);
|
|
608
|
-
setError(new Error("Failed to get event info"));
|
|
609
|
-
}
|
|
610
|
-
};
|
|
611
|
-
void getEventInfo();
|
|
701
|
+
void EventRuntime.getEventInfo()
|
|
702
|
+
.then(eventInfo => dispatch({ type: "success", eventInfo }))
|
|
703
|
+
.catch(() => dispatch({ type: "error", error: new Error("Failed to get event info") }));
|
|
612
704
|
}, []);
|
|
613
|
-
return {
|
|
705
|
+
return {
|
|
706
|
+
eventInfo: state.status === "success" ? state.eventInfo : undefined,
|
|
707
|
+
loading: state.status === "loading",
|
|
708
|
+
error: state.status === "error" ? state.error : undefined,
|
|
709
|
+
};
|
|
614
710
|
};
|
|
615
711
|
|
|
616
712
|
/**
|
|
@@ -979,6 +1075,28 @@ const useWidgetConfigAsync = () => {
|
|
|
979
1075
|
}
|
|
980
1076
|
return context;
|
|
981
1077
|
};
|
|
1078
|
+
const widgetDataReducer = (state, action) => {
|
|
1079
|
+
switch (action.type) {
|
|
1080
|
+
case "dataLoaded":
|
|
1081
|
+
return { ...state, data: action.data, loadingData: false };
|
|
1082
|
+
case "dataVersionLoaded":
|
|
1083
|
+
return { ...state, dataVersion: action.dataVersion };
|
|
1084
|
+
case "titleLoaded":
|
|
1085
|
+
return { ...state, title: action.title };
|
|
1086
|
+
case "dataUpdated":
|
|
1087
|
+
return { ...state, data: action.data, dataVersion: action.dataVersion };
|
|
1088
|
+
case "titleUpdated":
|
|
1089
|
+
return { ...state, title: action.title };
|
|
1090
|
+
default:
|
|
1091
|
+
return state;
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
const INITIAL_WIDGET_DATA_STATE = {
|
|
1095
|
+
data: null,
|
|
1096
|
+
loadingData: true,
|
|
1097
|
+
dataVersion: null,
|
|
1098
|
+
title: null,
|
|
1099
|
+
};
|
|
982
1100
|
/**
|
|
983
1101
|
* This is a hook to use the WidgetConfigContext.
|
|
984
1102
|
*
|
|
@@ -994,13 +1112,9 @@ const useWidgetConfigAsync = () => {
|
|
|
994
1112
|
*/
|
|
995
1113
|
const useWidgetConfig = () => {
|
|
996
1114
|
const widgetConfigContext = useWidgetConfigAsync();
|
|
997
|
-
const [
|
|
998
|
-
const [loadingData, setLoadingData] = useState(true);
|
|
999
|
-
const [dataVersion, setDataVersion] = useState(null);
|
|
1000
|
-
const [title, setTitle] = useState(null);
|
|
1115
|
+
const [widgetData, dispatch] = useReducer(widgetDataReducer, INITIAL_WIDGET_DATA_STATE);
|
|
1001
1116
|
const filters = useFilterBarContext();
|
|
1002
1117
|
const { timeRange } = useTimeRange();
|
|
1003
|
-
// use window.location.hash directly to avoid depending on tanstack router in core-hooks
|
|
1004
1118
|
const [edit, setEdit] = useState(() => window.location.hash.includes("edit=true"));
|
|
1005
1119
|
useEffect(() => {
|
|
1006
1120
|
const handleHashChange = () => {
|
|
@@ -1011,29 +1125,27 @@ const useWidgetConfig = () => {
|
|
|
1011
1125
|
}, []);
|
|
1012
1126
|
const widgetConfigContextRef = useRef(widgetConfigContext);
|
|
1013
1127
|
useEffect(() => {
|
|
1014
|
-
void widgetConfigContextRef.current.getData().then(d => {
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
void widgetConfigContextRef.current.
|
|
1019
|
-
void widgetConfigContextRef.current.getTitle().then(t => setTitle(t));
|
|
1128
|
+
void widgetConfigContextRef.current.getData().then(d => dispatch({ type: "dataLoaded", data: d }));
|
|
1129
|
+
void widgetConfigContextRef.current
|
|
1130
|
+
.getDataVersion()
|
|
1131
|
+
.then(dv => dispatch({ type: "dataVersionLoaded", dataVersion: dv }));
|
|
1132
|
+
void widgetConfigContextRef.current.getTitle().then(t => dispatch({ type: "titleLoaded", title: t }));
|
|
1020
1133
|
}, []);
|
|
1021
1134
|
const result = useMemo(() => ({
|
|
1022
|
-
data,
|
|
1135
|
+
data: widgetData.data,
|
|
1023
1136
|
setData: async (newData, newDataVersion) => {
|
|
1024
1137
|
await widgetConfigContext.setData(newData, newDataVersion);
|
|
1025
|
-
|
|
1026
|
-
setDataVersion(newDataVersion);
|
|
1138
|
+
dispatch({ type: "dataUpdated", data: newData, dataVersion: newDataVersion });
|
|
1027
1139
|
},
|
|
1028
|
-
dataVersion,
|
|
1029
|
-
loadingData,
|
|
1140
|
+
dataVersion: widgetData.dataVersion,
|
|
1141
|
+
loadingData: widgetData.loadingData,
|
|
1030
1142
|
setLoadingState: async (newLoadingState) => {
|
|
1031
1143
|
await widgetConfigContext.setLoadingState(newLoadingState);
|
|
1032
1144
|
},
|
|
1033
|
-
title,
|
|
1145
|
+
title: widgetData.title,
|
|
1034
1146
|
setTitle: async (newTitle) => {
|
|
1035
1147
|
await widgetConfigContext.setTitle(newTitle);
|
|
1036
|
-
|
|
1148
|
+
dispatch({ type: "titleUpdated", title: newTitle });
|
|
1037
1149
|
},
|
|
1038
1150
|
filters,
|
|
1039
1151
|
timeRange,
|
|
@@ -1046,14 +1158,26 @@ const useWidgetConfig = () => {
|
|
|
1046
1158
|
if (props) {
|
|
1047
1159
|
if (props.newData && props.newData.data) {
|
|
1048
1160
|
await WidgetConfigRuntime.setWidgetData(props.newData.data, props.newData.dataVersion ?? 1, props.newTitle ?? undefined);
|
|
1049
|
-
|
|
1050
|
-
|
|
1161
|
+
dispatch({
|
|
1162
|
+
type: "dataUpdated",
|
|
1163
|
+
data: props.newData.data,
|
|
1164
|
+
dataVersion: props.newData.dataVersion ?? 1,
|
|
1165
|
+
});
|
|
1051
1166
|
}
|
|
1052
1167
|
}
|
|
1053
1168
|
await widgetConfigContext.closeEditMode();
|
|
1054
1169
|
},
|
|
1055
|
-
}), [
|
|
1170
|
+
}), [
|
|
1171
|
+
widgetData.data,
|
|
1172
|
+
widgetData.dataVersion,
|
|
1173
|
+
widgetData.title,
|
|
1174
|
+
filters,
|
|
1175
|
+
timeRange,
|
|
1176
|
+
edit,
|
|
1177
|
+
widgetData.loadingData,
|
|
1178
|
+
widgetConfigContext,
|
|
1179
|
+
]);
|
|
1056
1180
|
return result;
|
|
1057
1181
|
};
|
|
1058
1182
|
|
|
1059
|
-
export { fetchAssetBlobUrl, useAnalytics, useAssetRuntime, useAssetSorting, useConfirmationDialog, useCurrentUser, useCurrentUserFavoriteAdvancedSensors, useCurrentUserFavoriteInsights, useCurrentUserLanguage, useCurrentUserSystemOfMeasurement, useCurrentUserTimeZonePreference, useCustomerRuntime, useEnvironment, useErrorHandler, useErrorHandlerOrNull, useEventRuntime, useExportDataContext, useFeatureBranchQueryString, useFeatureFlags, useFilterBarContext, useHasAccessTo, useImageUploader, useIrisAppId, useIrisAppImage, useIrisAppName, useModalDialogContext, useNavigateInHost, useOemBrandingContext, useSiteRuntime, useTimeRange, useToast, useToken, useUserPermission, useUserSubscription, useWidgetConfig, useWidgetConfigAsync };
|
|
1183
|
+
export { fetchAssetBlobUrl, useAnalytics, useAssetRuntime, useAssetSorting, useConfirmationDialog, useCurrentUser, useCurrentUserFavoriteAdvancedSensors, useCurrentUserFavoriteInsights, useCurrentUserLanguage, useCurrentUserSystemOfMeasurement, useCurrentUserTimeZonePreference, useCustomerRuntime, useEnvironment, useErrorHandler, useErrorHandlerOrNull, useEventRuntime, useExportDataContext, useFeatureBranchQueryString, useFeatureFlags, useFilterBarContext, useGeolocation, useHasAccessTo, useImageUploader, useIrisAppId, useIrisAppImage, useIrisAppName, useModalDialogContext, useNavigateInHost, useOemBrandingContext, useSiteRuntime, useTimeRange, useToast, useToken, useUserPermission, useUserSubscription, useWidgetConfig, useWidgetConfigAsync };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-core-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"react": "19.0.0",
|
|
11
11
|
"es-toolkit": "^1.39.10",
|
|
12
|
-
"@trackunit/iris-app-runtime-core": "1.
|
|
13
|
-
"@trackunit/iris-app-runtime-core-api": "1.
|
|
14
|
-
"@trackunit/react-core-contexts-api": "1.
|
|
12
|
+
"@trackunit/iris-app-runtime-core": "1.14.0",
|
|
13
|
+
"@trackunit/iris-app-runtime-core-api": "1.13.0",
|
|
14
|
+
"@trackunit/react-core-contexts-api": "1.14.0"
|
|
15
15
|
},
|
|
16
16
|
"module": "./index.esm.js",
|
|
17
17
|
"main": "./index.cjs.js",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type GeolocationContextValue } from "@trackunit/react-core-contexts-api";
|
|
2
|
+
type UseGeolocationOptions = Readonly<{
|
|
3
|
+
requestOnMount?: boolean;
|
|
4
|
+
}>;
|
|
5
|
+
/**
|
|
6
|
+
* Hook providing geolocation capabilities.
|
|
7
|
+
*
|
|
8
|
+
* In the host, geolocation is resolved directly via the browser API.
|
|
9
|
+
* In Iris Apps, requests are proxied to the host via the iframe bridge,
|
|
10
|
+
* so permission is only requested once at the host level.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const { position, getPosition } = useGeolocation();
|
|
15
|
+
*
|
|
16
|
+
* // User-initiated request (e.g. "My Location" button)
|
|
17
|
+
* onClick: () => {
|
|
18
|
+
* void getPosition().then(pos => { if (pos) fitBounds(pos); });
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare const useGeolocation: (options?: UseGeolocationOptions) => GeolocationContextValue;
|
|
23
|
+
export {};
|
package/src/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./exportData/useExportDataContext";
|
|
|
7
7
|
export * from "./featureFlags/useFeatureFlags";
|
|
8
8
|
export * from "./fetchAssetBlobUrl";
|
|
9
9
|
export * from "./filterBar/useFilterBarContext";
|
|
10
|
+
export * from "./geolocation/useGeolocation";
|
|
10
11
|
export * from "./images/useImageUploader";
|
|
11
12
|
export * from "./images/useIrisAppImage";
|
|
12
13
|
export * from "./modalDialog/useModalDialogContext";
|