@umituz/react-native-design-system 4.25.96 → 4.25.98
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/package.json +1 -1
- package/src/atoms/picker/components/PickerModal.tsx +1 -1
- package/src/infinite-scroll/presentation/hooks/useInfiniteScroll.ts +15 -6
- package/src/layouts/ScreenLayout/ScreenLayout.tsx +1 -0
- package/src/layouts/index.ts +0 -2
- package/src/molecules/ConfirmationModalContent.tsx +1 -1
- package/src/molecules/ConfirmationModalMain.tsx +1 -1
- package/src/molecules/alerts/AlertStore.ts +29 -4
- package/src/molecules/alerts/hooks/useAlertDismissHandler.ts +1 -1
- package/src/molecules/circular-menu/index.ts +0 -1
- package/src/molecules/confirmation-modal/styles/confirmationModalStyles.ts +1 -1
- package/src/molecules/index.ts +9 -40
- package/src/molecules/navigation/hooks/useAppNavigation.ts +1 -0
- package/src/offline/infrastructure/utils/healthCheck.ts +33 -21
- package/src/offline/presentation/hooks/useOffline.ts +1 -1
- package/src/storage/presentation/hooks/__tests__/useStorageState.test.ts +3 -4
- package/src/storage/presentation/hooks/useStorageState.ts +1 -1
- package/src/theme/infrastructure/stores/themeStore.ts +23 -7
- package/src/timezone/infrastructure/utils/SimpleCache.ts +20 -8
- package/src/layouts/AppHeader/index.ts +0 -1
- package/src/layouts/ScreenHeader/index.ts +0 -1
- package/src/molecules/Divider/index.ts +0 -2
- package/src/molecules/action-footer/index.ts +0 -3
- package/src/molecules/hero-section/index.ts +0 -3
- /package/src/molecules/confirmation-modal/{types/index.ts → types.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "4.25.
|
|
3
|
+
"version": "4.25.98",
|
|
4
4
|
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image, timezone, offline, onboarding, and loading utilities",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -95,7 +95,7 @@ export const PickerModal: React.FC<PickerModalProps> = React.memo(({
|
|
|
95
95
|
{selected && <AtomicIcon name={icons.checkCircle} size="md" color="primary" />}
|
|
96
96
|
</TouchableOpacity>
|
|
97
97
|
);
|
|
98
|
-
}, [isSelected, onSelect, tokens, testID]);
|
|
98
|
+
}, [icons.checkCircle, isSelected, onSelect, tokens, testID]);
|
|
99
99
|
|
|
100
100
|
return (
|
|
101
101
|
<Modal visible={visible} animationType="none" transparent onRequestClose={onClose} testID={`${testID}-modal`} accessibilityViewIsModal={true}>
|
|
@@ -47,6 +47,12 @@ export function useInfiniteScroll<T>(
|
|
|
47
47
|
const isLoadingRef = useRef(false);
|
|
48
48
|
const isMountedRef = useRef(true);
|
|
49
49
|
const abortControllerRef = useRef<AbortController | null>(null);
|
|
50
|
+
const stateRef = useRef(state);
|
|
51
|
+
|
|
52
|
+
// Keep stateRef in sync with state
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
stateRef.current = state;
|
|
55
|
+
}, [state]);
|
|
50
56
|
|
|
51
57
|
useEffect(() => {
|
|
52
58
|
isMountedRef.current = true;
|
|
@@ -89,22 +95,25 @@ export function useInfiniteScroll<T>(
|
|
|
89
95
|
}, [config, initialPage, pageSize, totalItems, maxRetries, retryDelay, cancelPendingRequests]);
|
|
90
96
|
|
|
91
97
|
const loadMore = useCallback(async () => {
|
|
98
|
+
// Get fresh state from ref to avoid stale closure
|
|
99
|
+
const currentState = stateRef.current;
|
|
100
|
+
|
|
92
101
|
if (
|
|
93
102
|
isLoadingRef.current ||
|
|
94
|
-
!
|
|
95
|
-
|
|
96
|
-
|
|
103
|
+
!currentState.hasMore ||
|
|
104
|
+
currentState.isLoadingMore ||
|
|
105
|
+
currentState.isLoading
|
|
97
106
|
)
|
|
98
107
|
return;
|
|
99
108
|
|
|
100
|
-
if (isCursorMode(config) && !
|
|
109
|
+
if (isCursorMode(config) && !currentState.cursor) return;
|
|
101
110
|
|
|
102
111
|
isLoadingRef.current = true;
|
|
103
112
|
if (isMountedRef.current) setState((prev) => ({ ...prev, isLoadingMore: true, error: null }));
|
|
104
113
|
|
|
105
114
|
try {
|
|
106
115
|
const updates = await retryWithBackoff(
|
|
107
|
-
() => loadMoreData(config,
|
|
116
|
+
() => loadMoreData(config, currentState, pageSize),
|
|
108
117
|
maxRetries,
|
|
109
118
|
retryDelay,
|
|
110
119
|
);
|
|
@@ -120,7 +129,7 @@ export function useInfiniteScroll<T>(
|
|
|
120
129
|
} finally {
|
|
121
130
|
isLoadingRef.current = false;
|
|
122
131
|
}
|
|
123
|
-
}, [config,
|
|
132
|
+
}, [config, pageSize, maxRetries, retryDelay]);
|
|
124
133
|
|
|
125
134
|
const refresh = useCallback(async () => {
|
|
126
135
|
if (isLoadingRef.current) return;
|
|
@@ -15,6 +15,7 @@ import type { ScreenLayoutProps } from './types';
|
|
|
15
15
|
let KCKeyboardAvoidingView: React.ComponentType<any> | null = null;
|
|
16
16
|
let KCKeyboardAwareScrollView: React.ComponentType<any> | null = null;
|
|
17
17
|
try {
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
18
19
|
const kc = require('react-native-keyboard-controller');
|
|
19
20
|
KCKeyboardAvoidingView = kc.KeyboardAvoidingView ?? null;
|
|
20
21
|
KCKeyboardAwareScrollView = kc.KeyboardAwareScrollView ?? null;
|
package/src/layouts/index.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import React, { useMemo } from 'react';
|
|
8
8
|
import { View, ViewStyle, StyleProp } from 'react-native';
|
|
9
9
|
import { useAppDesignTokens } from '../theme';
|
|
10
|
-
import { ConfirmationModalVariant } from './confirmation-modal/types
|
|
10
|
+
import { ConfirmationModalVariant } from './confirmation-modal/types';
|
|
11
11
|
import {
|
|
12
12
|
getVariantConfig,
|
|
13
13
|
getModalContainerStyle,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import React, { useCallback } from 'react';
|
|
8
8
|
import { View, Modal, TouchableOpacity } from 'react-native';
|
|
9
9
|
import { useAppDesignTokens } from '../theme';
|
|
10
|
-
import { ConfirmationModalProps } from './confirmation-modal/types
|
|
10
|
+
import { ConfirmationModalProps } from './confirmation-modal/types';
|
|
11
11
|
import {
|
|
12
12
|
getModalOverlayStyle,
|
|
13
13
|
getBackdropStyle,
|
|
@@ -7,6 +7,7 @@ import { Alert } from './AlertTypes';
|
|
|
7
7
|
|
|
8
8
|
interface AlertState {
|
|
9
9
|
alerts: Alert[];
|
|
10
|
+
_updateInProgress: boolean;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
interface AlertActions {
|
|
@@ -19,13 +20,37 @@ export const useAlertStore = createStore<AlertState, AlertActions>({
|
|
|
19
20
|
name: 'alert-store',
|
|
20
21
|
initialState: {
|
|
21
22
|
alerts: [],
|
|
23
|
+
_updateInProgress: false,
|
|
22
24
|
},
|
|
23
25
|
persist: false,
|
|
24
26
|
actions: (set, get) => ({
|
|
25
|
-
addAlert: (alert: Alert) =>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
addAlert: (alert: Alert) => {
|
|
28
|
+
const { _updateInProgress, alerts } = get();
|
|
29
|
+
if (_updateInProgress) return;
|
|
30
|
+
|
|
31
|
+
const existingIndex = alerts.findIndex((a: Alert) => a.id === alert.id);
|
|
32
|
+
|
|
33
|
+
if (existingIndex >= 0) {
|
|
34
|
+
// Replace existing alert
|
|
35
|
+
const updatedAlerts = [...alerts];
|
|
36
|
+
updatedAlerts[existingIndex] = alert;
|
|
37
|
+
set({ alerts: updatedAlerts });
|
|
38
|
+
} else {
|
|
39
|
+
// Add new alert
|
|
40
|
+
set({ alerts: [...alerts, alert] });
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
dismissAlert: (id: string) => {
|
|
44
|
+
const { _updateInProgress, alerts } = get();
|
|
45
|
+
if (_updateInProgress) return;
|
|
46
|
+
|
|
47
|
+
const updatedAlerts = alerts.filter((a: Alert) => a.id !== id);
|
|
48
|
+
|
|
49
|
+
// Only update if something actually changed
|
|
50
|
+
if (updatedAlerts.length !== alerts.length) {
|
|
51
|
+
set({ alerts: updatedAlerts });
|
|
52
|
+
}
|
|
53
|
+
},
|
|
29
54
|
clearAlerts: () => set({ alerts: [] }),
|
|
30
55
|
}),
|
|
31
56
|
});
|
|
@@ -15,7 +15,7 @@ export function useAlertDismissHandler(alert: Alert) {
|
|
|
15
15
|
const handleDismiss = useCallback(() => {
|
|
16
16
|
dismissAlert(alert.id);
|
|
17
17
|
alert.onDismiss?.();
|
|
18
|
-
}, [alert
|
|
18
|
+
}, [alert, dismissAlert]);
|
|
19
19
|
|
|
20
20
|
return handleDismiss;
|
|
21
21
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { ViewStyle } from 'react-native';
|
|
8
|
-
import { ConfirmationModalVariant, ConfirmationModalVariantConfig } from '../types
|
|
8
|
+
import { ConfirmationModalVariant, ConfirmationModalVariantConfig } from '../types';
|
|
9
9
|
import type { DesignTokens } from '../../../theme';
|
|
10
10
|
|
|
11
11
|
/**
|
package/src/molecules/index.ts
CHANGED
|
@@ -8,62 +8,31 @@ export * from './avatar';
|
|
|
8
8
|
export * from './bottom-sheet';
|
|
9
9
|
export { FormField, type FormFieldProps } from './FormField';
|
|
10
10
|
export { ListItem, type ListItemProps } from './ListItem';
|
|
11
|
-
|
|
12
|
-
|
|
13
11
|
export { SearchBar, type SearchBarProps } from './SearchBar';
|
|
14
12
|
export { IconContainer } from './IconContainer';
|
|
15
13
|
export { BaseModal, type BaseModalProps } from './BaseModal';
|
|
16
14
|
export { ConfirmationModal } from './ConfirmationModalMain';
|
|
17
15
|
export { useConfirmationModal } from './confirmation-modal/useConfirmationModal';
|
|
18
16
|
|
|
19
|
-
//
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// Divider
|
|
26
|
-
export * from './Divider';
|
|
27
|
-
export * from "./StepProgress";
|
|
28
|
-
|
|
29
|
-
// Responsive Components
|
|
30
|
-
export { List, type ListProps } from './List';
|
|
31
|
-
|
|
32
|
-
// Alerts
|
|
17
|
+
// Other components
|
|
18
|
+
export * from './Divider/Divider';
|
|
19
|
+
export * from './Divider/types';
|
|
20
|
+
export * from './StepProgress';
|
|
21
|
+
export * from './List';
|
|
33
22
|
export * from './alerts';
|
|
34
|
-
|
|
35
|
-
// Calendar
|
|
36
23
|
export * from './calendar';
|
|
37
|
-
|
|
38
|
-
// Swipe Actions
|
|
39
24
|
export * from './swipe-actions';
|
|
40
|
-
|
|
41
|
-
// Navigation
|
|
42
25
|
export * from './navigation';
|
|
43
|
-
|
|
44
|
-
// Long Press Menu
|
|
45
26
|
export * from './long-press-menu';
|
|
46
|
-
|
|
47
|
-
// Step Header
|
|
48
27
|
export * from './StepHeader';
|
|
49
|
-
|
|
50
|
-
// Emoji
|
|
51
28
|
export * from './emoji';
|
|
52
|
-
|
|
53
|
-
// Countdown
|
|
54
29
|
export * from './countdown';
|
|
55
|
-
|
|
56
|
-
// Splash
|
|
57
30
|
export * from './splash';
|
|
58
|
-
|
|
59
|
-
|
|
60
31
|
export * from './filter-group';
|
|
61
|
-
export * from './action-footer';
|
|
62
|
-
|
|
63
|
-
export * from './hero-section';
|
|
32
|
+
export * from './action-footer/ActionFooter';
|
|
33
|
+
export * from './action-footer/types';
|
|
34
|
+
export * from './hero-section/HeroSection';
|
|
35
|
+
export * from './hero-section/types';
|
|
64
36
|
export * from './info-grid';
|
|
65
|
-
|
|
66
|
-
|
|
67
37
|
export * from './circular-menu';
|
|
68
|
-
|
|
69
38
|
export * from './icon-grid';
|
|
@@ -31,6 +31,7 @@ export function useAppNavigation(): AppNavigationResult {
|
|
|
31
31
|
|
|
32
32
|
const navigate = useCallback(
|
|
33
33
|
(screen: string, params?: Record<string, unknown>) => {
|
|
34
|
+
// Dynamic navigation: bypass ParamListBase constraint to allow arbitrary screen names
|
|
34
35
|
(navigation as any).navigate(screen, params);
|
|
35
36
|
},
|
|
36
37
|
[navigation]
|
|
@@ -11,6 +11,7 @@ const DEFAULT_TIMEOUT = 5000;
|
|
|
11
11
|
export class HealthCheck {
|
|
12
12
|
private intervalId: ReturnType<typeof setInterval> | null = null;
|
|
13
13
|
private isChecking = false;
|
|
14
|
+
private pendingCheck: Promise<boolean> | null = null;
|
|
14
15
|
private config: Required<OfflineConfig>;
|
|
15
16
|
|
|
16
17
|
constructor(config: OfflineConfig = {}) {
|
|
@@ -27,37 +28,47 @@ export class HealthCheck {
|
|
|
27
28
|
* Perform a single health check
|
|
28
29
|
*/
|
|
29
30
|
async check(): Promise<boolean> {
|
|
30
|
-
if
|
|
31
|
-
|
|
31
|
+
// Return existing promise if check is in progress
|
|
32
|
+
if (this.pendingCheck) {
|
|
33
|
+
return this.pendingCheck;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
this.
|
|
36
|
+
this.pendingCheck = (async () => {
|
|
37
|
+
if (this.isChecking) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
const controller = new AbortController();
|
|
38
|
-
const timeoutId = setTimeout(() => controller.abort(), this.config.healthCheckTimeout);
|
|
41
|
+
this.isChecking = true;
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
});
|
|
43
|
+
try {
|
|
44
|
+
const controller = new AbortController();
|
|
45
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.healthCheckTimeout);
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
const response = await fetch(this.config.healthCheckUrl, {
|
|
48
|
+
method: 'HEAD',
|
|
49
|
+
signal: controller.signal,
|
|
50
|
+
});
|
|
46
51
|
|
|
47
|
-
|
|
52
|
+
clearTimeout(timeoutId);
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
}
|
|
54
|
+
const isHealthy = response.ok;
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
if (this.config.debug) {
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return isHealthy;
|
|
60
|
+
} catch (_error) {
|
|
61
|
+
if (this.config.debug) {
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return false;
|
|
65
|
+
} finally {
|
|
66
|
+
this.isChecking = false;
|
|
67
|
+
this.pendingCheck = null;
|
|
55
68
|
}
|
|
69
|
+
})();
|
|
56
70
|
|
|
57
|
-
|
|
58
|
-
} finally {
|
|
59
|
-
this.isChecking = false;
|
|
60
|
-
}
|
|
71
|
+
return this.pendingCheck;
|
|
61
72
|
}
|
|
62
73
|
|
|
63
74
|
/**
|
|
@@ -101,5 +112,6 @@ export class HealthCheck {
|
|
|
101
112
|
*/
|
|
102
113
|
destroy(): void {
|
|
103
114
|
this.stop();
|
|
115
|
+
this.pendingCheck = null;
|
|
104
116
|
}
|
|
105
117
|
}
|
|
@@ -76,7 +76,7 @@ export const useOffline = (config?: OfflineConfig) => {
|
|
|
76
76
|
|
|
77
77
|
networkEvents.emit('change', networkState);
|
|
78
78
|
previousStateRef.current = networkState;
|
|
79
|
-
}, [store
|
|
79
|
+
}, [store]);
|
|
80
80
|
|
|
81
81
|
useEffect(() => {
|
|
82
82
|
if (isInitialized.current) return;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
1
|
/**
|
|
3
2
|
* useStorageState Hook Tests
|
|
4
3
|
*
|
|
@@ -107,7 +106,7 @@ describe('useStorageState Hook', () => {
|
|
|
107
106
|
const newValue = 'new-value';
|
|
108
107
|
|
|
109
108
|
// Mock slow storage
|
|
110
|
-
let
|
|
109
|
+
let _resolveStorage: (value: string) => void;
|
|
111
110
|
(AsyncStorage.getItem as jest.Mock).mockImplementation(() =>
|
|
112
111
|
new Promise(resolve => {
|
|
113
112
|
resolveStorage = resolve;
|
|
@@ -133,7 +132,7 @@ describe('useStorageState Hook', () => {
|
|
|
133
132
|
const defaultValue = 'default';
|
|
134
133
|
|
|
135
134
|
// Mock slow storage
|
|
136
|
-
let
|
|
135
|
+
let _resolveStorage: (value: string) => void;
|
|
137
136
|
(AsyncStorage.getItem as jest.Mock).mockImplementation(() =>
|
|
138
137
|
new Promise(resolve => {
|
|
139
138
|
resolveStorage = resolve;
|
|
@@ -198,7 +197,7 @@ describe('useStorageState Hook', () => {
|
|
|
198
197
|
|
|
199
198
|
await AsyncStorage.setItem(key, JSON.stringify('stored-value'));
|
|
200
199
|
|
|
201
|
-
const { result, rerender, waitForNextUpdate } = renderHook(
|
|
200
|
+
const { result: _result, rerender, waitForNextUpdate } = renderHook(
|
|
202
201
|
({ defaultValue }) => useStorageState(key, defaultValue),
|
|
203
202
|
{ initialProps: { defaultValue: defaultValue1 } }
|
|
204
203
|
);
|
|
@@ -22,6 +22,7 @@ interface ThemeState {
|
|
|
22
22
|
isInitialized: boolean;
|
|
23
23
|
_updateInProgress: boolean;
|
|
24
24
|
_initInProgress: boolean;
|
|
25
|
+
_lastUpdateId?: number;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
interface ThemeActions {
|
|
@@ -46,6 +47,7 @@ export const useTheme = createStore<ThemeState, ThemeActions>({
|
|
|
46
47
|
isInitialized: false,
|
|
47
48
|
_updateInProgress: false,
|
|
48
49
|
_initInProgress: false,
|
|
50
|
+
_lastUpdateId: undefined,
|
|
49
51
|
},
|
|
50
52
|
persist: false,
|
|
51
53
|
actions: (set, get) => ({
|
|
@@ -88,30 +90,44 @@ export const useTheme = createStore<ThemeState, ThemeActions>({
|
|
|
88
90
|
setThemeMode: async (mode: ThemeMode) => {
|
|
89
91
|
const { _updateInProgress } = get();
|
|
90
92
|
if (_updateInProgress) return;
|
|
91
|
-
|
|
93
|
+
|
|
94
|
+
const updateId = Date.now();
|
|
95
|
+
set({ _updateInProgress: true, _lastUpdateId: updateId });
|
|
92
96
|
|
|
93
97
|
try {
|
|
94
98
|
const theme = mode === 'light' ? lightTheme : darkTheme;
|
|
95
99
|
set({ themeMode: mode, theme, isDark: mode === 'dark' });
|
|
96
100
|
await ThemeStorage.setThemeMode(mode);
|
|
97
101
|
useDesignSystemTheme.getState().setThemeMode(mode);
|
|
98
|
-
} catch {
|
|
99
|
-
//
|
|
102
|
+
} catch (error) {
|
|
103
|
+
// Revert state on error
|
|
104
|
+
set({ _lastUpdateId: undefined });
|
|
105
|
+
if (__DEV__) {
|
|
106
|
+
console.error('[ThemeStore] Failed to set theme mode:', error);
|
|
107
|
+
}
|
|
108
|
+
throw error;
|
|
100
109
|
} finally {
|
|
101
110
|
set({ _updateInProgress: false });
|
|
102
111
|
}
|
|
103
112
|
},
|
|
104
113
|
|
|
105
114
|
setCustomColors: async (colors?: CustomThemeColors) => {
|
|
106
|
-
const { _updateInProgress } = get();
|
|
115
|
+
const { _updateInProgress, customColors: currentColors } = get();
|
|
107
116
|
if (_updateInProgress) return;
|
|
108
|
-
|
|
117
|
+
|
|
118
|
+
const updateId = Date.now();
|
|
119
|
+
set({ _updateInProgress: true, _lastUpdateId: updateId, customColors: colors });
|
|
109
120
|
|
|
110
121
|
try {
|
|
111
122
|
await ThemeStorage.setCustomColors(colors);
|
|
112
123
|
useDesignSystemTheme.getState().setCustomColors(colors);
|
|
113
|
-
} catch {
|
|
114
|
-
//
|
|
124
|
+
} catch (error) {
|
|
125
|
+
// Revert to previous colors on error
|
|
126
|
+
set({ customColors: currentColors, _lastUpdateId: undefined });
|
|
127
|
+
if (__DEV__) {
|
|
128
|
+
console.error('[ThemeStore] Failed to set custom colors:', error);
|
|
129
|
+
}
|
|
130
|
+
throw error;
|
|
115
131
|
} finally {
|
|
116
132
|
set({ _updateInProgress: false });
|
|
117
133
|
}
|
|
@@ -15,6 +15,7 @@ export class SimpleCache<T> {
|
|
|
15
15
|
private defaultTTL: number;
|
|
16
16
|
private cleanupTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
17
17
|
private destroyed = false;
|
|
18
|
+
private cleanupScheduleLock = false;
|
|
18
19
|
|
|
19
20
|
constructor(defaultTTL: number = 60000) {
|
|
20
21
|
this.defaultTTL = defaultTTL;
|
|
@@ -26,6 +27,7 @@ export class SimpleCache<T> {
|
|
|
26
27
|
*/
|
|
27
28
|
destroy(): void {
|
|
28
29
|
this.destroyed = true;
|
|
30
|
+
this.cleanupScheduleLock = false;
|
|
29
31
|
if (this.cleanupTimeout) {
|
|
30
32
|
clearTimeout(this.cleanupTimeout);
|
|
31
33
|
this.cleanupTimeout = null;
|
|
@@ -76,16 +78,26 @@ export class SimpleCache<T> {
|
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
private scheduleCleanup(): void {
|
|
79
|
-
if (this.destroyed) return;
|
|
81
|
+
if (this.destroyed || this.cleanupScheduleLock) return;
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
clearTimeout(this.cleanupTimeout);
|
|
83
|
-
}
|
|
83
|
+
this.cleanupScheduleLock = true;
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
try {
|
|
86
|
+
if (this.cleanupTimeout) {
|
|
87
|
+
clearTimeout(this.cleanupTimeout);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.cleanup();
|
|
86
91
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
if (!this.destroyed) {
|
|
93
|
+
this.cleanupTimeout = setTimeout(() => {
|
|
94
|
+
this.cleanupScheduleLock = false;
|
|
95
|
+
this.scheduleCleanup();
|
|
96
|
+
}, 60000);
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
this.cleanupScheduleLock = false;
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
90
102
|
}
|
|
91
103
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './AppHeader';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './ScreenHeader';
|
|
File without changes
|