@umituz/react-native-design-system 4.23.83 → 4.23.85
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/input/hooks/useInputState.ts +2 -4
- package/src/image/presentation/components/editor/text-editor/TextTransformTab.tsx +40 -152
- package/src/image/presentation/components/editor/text-editor/components/TransformButtonRow.tsx +124 -0
- package/src/media/domain/utils/FileValidator.ts +156 -0
- package/src/media/infrastructure/services/MediaPickerService.ts +18 -57
- package/src/media/infrastructure/utils/PermissionManager.ts +92 -0
- package/src/media/presentation/hooks/useMedia.ts +5 -4
- package/src/molecules/alerts/AlertBanner.tsx +9 -25
- package/src/molecules/alerts/AlertInline.tsx +4 -23
- package/src/molecules/alerts/AlertModal.tsx +4 -11
- package/src/molecules/alerts/AlertToast.tsx +14 -13
- package/src/molecules/alerts/utils/alertUtils.ts +133 -0
- package/src/molecules/calendar/infrastructure/storage/CalendarStore.ts +65 -25
- package/src/molecules/countdown/hooks/useCountdown.ts +13 -5
- package/src/molecules/swipe-actions/domain/entities/SwipeAction.ts +15 -123
- package/src/molecules/swipe-actions/domain/utils/swipeActionHelpers.ts +109 -0
- package/src/molecules/swipe-actions/domain/utils/swipeActionValidator.ts +54 -0
- package/src/molecules/swipe-actions/presentation/components/SwipeActionButton.tsx +10 -5
- package/src/tanstack/domain/utils/MetricsCalculator.ts +103 -0
- package/src/tanstack/infrastructure/monitoring/DevMonitor.ts +35 -29
- package/src/timezone/infrastructure/utils/SimpleCache.ts +24 -2
- package/src/molecules/alerts/utils/alertToastHelpers.ts +0 -70
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* Swipe Actions Domain - Entity Layer
|
|
3
3
|
*
|
|
4
4
|
* Core swipe action types and configurations.
|
|
5
|
-
* Defines pre-built action types, colors, icons, and utilities.
|
|
6
5
|
*
|
|
7
6
|
* @domain swipe-actions
|
|
8
7
|
* @layer domain/entities
|
|
@@ -61,53 +60,6 @@ export interface SwipeableConfig {
|
|
|
61
60
|
friction?: number;
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
/**
|
|
65
|
-
* Pre-built action type configurations
|
|
66
|
-
*/
|
|
67
|
-
export const ACTION_PRESETS: Record<Exclude<SwipeActionType, 'custom'>, {
|
|
68
|
-
label: string;
|
|
69
|
-
icon: IconName;
|
|
70
|
-
colorKey: 'error' | 'success' | 'primary' | 'secondary' | 'warning' | 'textSecondary';
|
|
71
|
-
hapticsIntensity: 'Light' | 'Medium' | 'Heavy';
|
|
72
|
-
}> = {
|
|
73
|
-
delete: {
|
|
74
|
-
label: 'Delete',
|
|
75
|
-
icon: 'Trash2',
|
|
76
|
-
colorKey: 'error',
|
|
77
|
-
hapticsIntensity: 'Heavy',
|
|
78
|
-
},
|
|
79
|
-
archive: {
|
|
80
|
-
label: 'Archive',
|
|
81
|
-
icon: 'Archive',
|
|
82
|
-
colorKey: 'success',
|
|
83
|
-
hapticsIntensity: 'Medium',
|
|
84
|
-
},
|
|
85
|
-
edit: {
|
|
86
|
-
label: 'Edit',
|
|
87
|
-
icon: 'Pencil',
|
|
88
|
-
colorKey: 'primary',
|
|
89
|
-
hapticsIntensity: 'Light',
|
|
90
|
-
},
|
|
91
|
-
share: {
|
|
92
|
-
label: 'Share',
|
|
93
|
-
icon: 'Share2',
|
|
94
|
-
colorKey: 'secondary',
|
|
95
|
-
hapticsIntensity: 'Light',
|
|
96
|
-
},
|
|
97
|
-
favorite: {
|
|
98
|
-
label: 'Favorite',
|
|
99
|
-
icon: 'Heart',
|
|
100
|
-
colorKey: 'warning',
|
|
101
|
-
hapticsIntensity: 'Light',
|
|
102
|
-
},
|
|
103
|
-
more: {
|
|
104
|
-
label: 'More',
|
|
105
|
-
icon: 'MoveHorizontal',
|
|
106
|
-
colorKey: 'textSecondary',
|
|
107
|
-
hapticsIntensity: 'Light',
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
|
|
111
63
|
/**
|
|
112
64
|
* Default swipe configuration
|
|
113
65
|
*/
|
|
@@ -117,78 +69,18 @@ export const DEFAULT_SWIPE_CONFIG: Required<Omit<SwipeableConfig, 'leftActions'
|
|
|
117
69
|
friction: 2,
|
|
118
70
|
};
|
|
119
71
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
* Validates swipe action configuration
|
|
136
|
-
*/
|
|
137
|
-
static validateAction(action: SwipeActionConfig): boolean {
|
|
138
|
-
// Must have onPress handler
|
|
139
|
-
if (!action.onPress || typeof action.onPress !== 'function') {
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Custom actions must have label, icon, and color
|
|
144
|
-
if (action.type === 'custom') {
|
|
145
|
-
return !!(action.label && action.icon && action.color);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return true;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Gets action display label
|
|
153
|
-
*/
|
|
154
|
-
static getLabel(action: SwipeActionConfig): string {
|
|
155
|
-
if (action.label) {
|
|
156
|
-
return action.label;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const preset = this.getPreset(action.type);
|
|
160
|
-
return preset?.label || 'Action';
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Gets action icon name
|
|
165
|
-
*/
|
|
166
|
-
static getIcon(action: SwipeActionConfig): IconName {
|
|
167
|
-
if (action.icon) {
|
|
168
|
-
return action.icon;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const preset = this.getPreset(action.type);
|
|
172
|
-
return preset?.icon || 'MoveHorizontal';
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Gets action color key for theme
|
|
177
|
-
*/
|
|
178
|
-
static getColorKey(action: SwipeActionConfig): string | null {
|
|
179
|
-
if (action.color) {
|
|
180
|
-
return null; // Use custom color
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const preset = this.getPreset(action.type);
|
|
184
|
-
return preset?.colorKey || null;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Gets haptics intensity for action
|
|
189
|
-
*/
|
|
190
|
-
static getHapticsIntensity(action: SwipeActionConfig): 'Light' | 'Medium' | 'Heavy' {
|
|
191
|
-
const preset = this.getPreset(action.type);
|
|
192
|
-
return preset?.hapticsIntensity || 'Light';
|
|
193
|
-
}
|
|
194
|
-
}
|
|
72
|
+
// Re-export utilities for backward compatibility
|
|
73
|
+
export {
|
|
74
|
+
ACTION_PRESETS,
|
|
75
|
+
getPreset,
|
|
76
|
+
getActionLabel,
|
|
77
|
+
getActionIcon,
|
|
78
|
+
getActionColorKey,
|
|
79
|
+
getHapticsIntensity,
|
|
80
|
+
} from '../utils/swipeActionHelpers';
|
|
81
|
+
|
|
82
|
+
export {
|
|
83
|
+
validateSwipeAction,
|
|
84
|
+
validateSwipeActions,
|
|
85
|
+
getValidationError,
|
|
86
|
+
} from '../utils/swipeActionValidator';
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swipe Action Helpers
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for swipe action styling and behavior.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { IconName } from '../../../../atoms/icon';
|
|
8
|
+
import type { SwipeActionType } from '../entities/SwipeAction';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Pre-built action type configurations
|
|
12
|
+
*/
|
|
13
|
+
export const ACTION_PRESETS: Record<Exclude<SwipeActionType, 'custom'>, {
|
|
14
|
+
label: string;
|
|
15
|
+
icon: IconName;
|
|
16
|
+
colorKey: 'error' | 'success' | 'primary' | 'secondary' | 'warning' | 'textSecondary';
|
|
17
|
+
hapticsIntensity: 'Light' | 'Medium' | 'Heavy';
|
|
18
|
+
}> = {
|
|
19
|
+
delete: {
|
|
20
|
+
label: 'Delete',
|
|
21
|
+
icon: 'Trash2',
|
|
22
|
+
colorKey: 'error',
|
|
23
|
+
hapticsIntensity: 'Heavy',
|
|
24
|
+
},
|
|
25
|
+
archive: {
|
|
26
|
+
label: 'Archive',
|
|
27
|
+
icon: 'Archive',
|
|
28
|
+
colorKey: 'success',
|
|
29
|
+
hapticsIntensity: 'Medium',
|
|
30
|
+
},
|
|
31
|
+
edit: {
|
|
32
|
+
label: 'Edit',
|
|
33
|
+
icon: 'Pencil',
|
|
34
|
+
colorKey: 'primary',
|
|
35
|
+
hapticsIntensity: 'Light',
|
|
36
|
+
},
|
|
37
|
+
share: {
|
|
38
|
+
label: 'Share',
|
|
39
|
+
icon: 'Share2',
|
|
40
|
+
colorKey: 'secondary',
|
|
41
|
+
hapticsIntensity: 'Light',
|
|
42
|
+
},
|
|
43
|
+
favorite: {
|
|
44
|
+
label: 'Favorite',
|
|
45
|
+
icon: 'Heart',
|
|
46
|
+
colorKey: 'warning',
|
|
47
|
+
hapticsIntensity: 'Light',
|
|
48
|
+
},
|
|
49
|
+
more: {
|
|
50
|
+
label: 'More',
|
|
51
|
+
icon: 'MoveHorizontal',
|
|
52
|
+
colorKey: 'textSecondary',
|
|
53
|
+
hapticsIntensity: 'Light',
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Gets preset configuration for action type
|
|
59
|
+
*/
|
|
60
|
+
export function getPreset(type: SwipeActionType) {
|
|
61
|
+
if (type === 'custom') {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return ACTION_PRESETS[type];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Gets action display label
|
|
69
|
+
*/
|
|
70
|
+
export function getActionLabel(action: { type: SwipeActionType; label?: string }): string {
|
|
71
|
+
if (action.label) {
|
|
72
|
+
return action.label;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const preset = getPreset(action.type);
|
|
76
|
+
return preset?.label || 'Action';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Gets action icon name
|
|
81
|
+
*/
|
|
82
|
+
export function getActionIcon(action: { type: SwipeActionType; icon?: IconName }): IconName {
|
|
83
|
+
if (action.icon) {
|
|
84
|
+
return action.icon;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const preset = getPreset(action.type);
|
|
88
|
+
return preset?.icon || 'MoveHorizontal';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Gets action color key for theme
|
|
93
|
+
*/
|
|
94
|
+
export function getActionColorKey(action: { type: SwipeActionType; color?: string }): string | null {
|
|
95
|
+
if (action.color) {
|
|
96
|
+
return null; // Use custom color
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const preset = getPreset(action.type);
|
|
100
|
+
return preset?.colorKey || null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Gets haptics intensity for action
|
|
105
|
+
*/
|
|
106
|
+
export function getHapticsIntensity(action: { type: SwipeActionType; enableHaptics?: boolean }): 'Light' | 'Medium' | 'Heavy' {
|
|
107
|
+
const preset = getPreset(action.type);
|
|
108
|
+
return preset?.hapticsIntensity || 'Light';
|
|
109
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swipe Action Validator
|
|
3
|
+
*
|
|
4
|
+
* Validation functions for swipe action configurations.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { SwipeActionConfig } from '../entities/SwipeAction';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Validates swipe action configuration
|
|
11
|
+
*/
|
|
12
|
+
export function validateSwipeAction(action: SwipeActionConfig): boolean {
|
|
13
|
+
// Must have onPress handler
|
|
14
|
+
if (!action.onPress || typeof action.onPress !== 'function') {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Custom actions must have label, icon, and color
|
|
19
|
+
if (action.type === 'custom') {
|
|
20
|
+
return !!(action.label && action.icon && action.color);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validates multiple swipe actions
|
|
28
|
+
*/
|
|
29
|
+
export function validateSwipeActions(actions: SwipeActionConfig[]): boolean {
|
|
30
|
+
return actions.every(validateSwipeAction);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Gets validation error message for an action
|
|
35
|
+
*/
|
|
36
|
+
export function getValidationError(action: SwipeActionConfig): string | null {
|
|
37
|
+
if (!action.onPress || typeof action.onPress !== 'function') {
|
|
38
|
+
return 'Action must have an onPress handler';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (action.type === 'custom') {
|
|
42
|
+
if (!action.label) {
|
|
43
|
+
return 'Custom action must have a label';
|
|
44
|
+
}
|
|
45
|
+
if (!action.icon) {
|
|
46
|
+
return 'Custom action must have an icon';
|
|
47
|
+
}
|
|
48
|
+
if (!action.color) {
|
|
49
|
+
return 'Custom action must have a color';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
@@ -14,7 +14,12 @@ import { AtomicText, AtomicIcon } from '../../../../atoms';
|
|
|
14
14
|
import { useAppDesignTokens } from '../../../../theme/hooks/useAppDesignTokens';
|
|
15
15
|
import { HapticService } from '../../../../haptics';
|
|
16
16
|
import type { SwipeActionConfig } from '../../domain/entities/SwipeAction';
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
getActionLabel,
|
|
19
|
+
getActionIcon,
|
|
20
|
+
getActionColorKey,
|
|
21
|
+
getHapticsIntensity,
|
|
22
|
+
} from '../../domain/entities/SwipeAction';
|
|
18
23
|
|
|
19
24
|
/**
|
|
20
25
|
* SwipeActionButton component props
|
|
@@ -58,9 +63,9 @@ export const SwipeActionButton: React.FC<SwipeActionButtonProps> = ({
|
|
|
58
63
|
const tokens = useAppDesignTokens();
|
|
59
64
|
|
|
60
65
|
// Get action properties
|
|
61
|
-
const label =
|
|
62
|
-
const iconName =
|
|
63
|
-
const colorKey =
|
|
66
|
+
const label = getActionLabel(action);
|
|
67
|
+
const iconName = getActionIcon(action);
|
|
68
|
+
const colorKey = getActionColorKey(action);
|
|
64
69
|
const customColor = action.color;
|
|
65
70
|
const enableHaptics = action.enableHaptics !== false;
|
|
66
71
|
|
|
@@ -83,7 +88,7 @@ export const SwipeActionButton: React.FC<SwipeActionButtonProps> = ({
|
|
|
83
88
|
const handlePress = async () => {
|
|
84
89
|
// Trigger haptic feedback
|
|
85
90
|
if (enableHaptics) {
|
|
86
|
-
const intensity =
|
|
91
|
+
const intensity = getHapticsIntensity(action);
|
|
87
92
|
await HapticService.impact(intensity);
|
|
88
93
|
}
|
|
89
94
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metrics Calculator
|
|
3
|
+
*
|
|
4
|
+
* Calculates query performance metrics for monitoring.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Query } from '@tanstack/react-query';
|
|
8
|
+
import type { QueryMetrics, DevMonitorOptions } from '../../infrastructure/monitoring/DevMonitor.types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Metrics calculator for query performance
|
|
12
|
+
*/
|
|
13
|
+
export class MetricsCalculator {
|
|
14
|
+
/**
|
|
15
|
+
* Gets unique key string for query
|
|
16
|
+
*/
|
|
17
|
+
static getQueryKeyString(queryKey: readonly unknown[]): string {
|
|
18
|
+
return JSON.stringify(queryKey);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Calculates fetch time for a query
|
|
23
|
+
*/
|
|
24
|
+
static calculateFetchTime(query: Query): number {
|
|
25
|
+
return Date.now() - (query.state.dataUpdatedAt ?? Date.now());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Creates initial metrics for a query
|
|
30
|
+
*/
|
|
31
|
+
static createInitialMetrics(query: Query): QueryMetrics {
|
|
32
|
+
return {
|
|
33
|
+
queryKey: query.queryKey,
|
|
34
|
+
fetchCount: 0,
|
|
35
|
+
totalFetchTime: 0,
|
|
36
|
+
averageFetchTime: 0,
|
|
37
|
+
slowFetchCount: 0,
|
|
38
|
+
lastFetchTime: null,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Updates metrics with new fetch data
|
|
44
|
+
*/
|
|
45
|
+
static updateMetrics(
|
|
46
|
+
current: QueryMetrics,
|
|
47
|
+
fetchTime: number
|
|
48
|
+
): QueryMetrics {
|
|
49
|
+
const newFetchCount = current.fetchCount + 1;
|
|
50
|
+
const newTotalFetchTime = current.totalFetchTime + fetchTime;
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
...current,
|
|
54
|
+
fetchCount: newFetchCount,
|
|
55
|
+
totalFetchTime: newTotalFetchTime,
|
|
56
|
+
averageFetchTime: newTotalFetchTime / newFetchCount,
|
|
57
|
+
lastFetchTime: fetchTime,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Checks if fetch time exceeds slow query threshold
|
|
63
|
+
*/
|
|
64
|
+
static isSlowQuery(fetchTime: number, threshold: number): boolean {
|
|
65
|
+
return fetchTime > threshold;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Increments slow fetch count if query is slow
|
|
70
|
+
*/
|
|
71
|
+
static incrementSlowCountIfNeeded(
|
|
72
|
+
metrics: QueryMetrics,
|
|
73
|
+
fetchTime: number,
|
|
74
|
+
threshold: number
|
|
75
|
+
): QueryMetrics {
|
|
76
|
+
if (this.isSlowQuery(fetchTime, threshold)) {
|
|
77
|
+
return {
|
|
78
|
+
...metrics,
|
|
79
|
+
slowFetchCount: metrics.slowFetchCount + 1,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return metrics;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Calculates complete metrics for a query
|
|
87
|
+
*/
|
|
88
|
+
static calculateQueryMetrics(
|
|
89
|
+
query: Query,
|
|
90
|
+
currentMetrics: QueryMetrics | null,
|
|
91
|
+
options: Required<DevMonitorOptions>
|
|
92
|
+
): QueryMetrics {
|
|
93
|
+
const fetchTime = this.calculateFetchTime(query);
|
|
94
|
+
const baseMetrics = currentMetrics ?? this.createInitialMetrics(query);
|
|
95
|
+
const updatedMetrics = this.updateMetrics(baseMetrics, fetchTime);
|
|
96
|
+
|
|
97
|
+
return this.incrementSlowCountIfNeeded(
|
|
98
|
+
updatedMetrics,
|
|
99
|
+
fetchTime,
|
|
100
|
+
options.slowQueryThreshold
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
import type { Query, QueryClient } from '@tanstack/react-query';
|
|
10
10
|
import type { QueryMetrics, CacheStats, DevMonitorOptions } from './DevMonitor.types';
|
|
11
11
|
import { DevMonitorLogger } from './DevMonitorLogger';
|
|
12
|
+
import { MetricsCalculator } from '../../domain/utils/MetricsCalculator';
|
|
12
13
|
|
|
13
14
|
class DevMonitorClass {
|
|
14
15
|
private metrics: Map<string, QueryMetrics> = new Map();
|
|
15
16
|
private queryClient: QueryClient | null = null;
|
|
16
17
|
private options: Required<DevMonitorOptions>;
|
|
17
18
|
private statsInterval: ReturnType<typeof setInterval> | null = null;
|
|
19
|
+
private cacheSubscription: (() => void) | null = null;
|
|
18
20
|
private isEnabled: boolean;
|
|
19
21
|
|
|
20
22
|
constructor(options: DevMonitorOptions = {}) {
|
|
@@ -38,37 +40,22 @@ class DevMonitorClass {
|
|
|
38
40
|
this.startStatsLogging();
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
private getQueryKeyString(queryKey: readonly unknown[]): string {
|
|
42
|
-
return JSON.stringify(queryKey);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
43
|
private trackQuery(query: Query): void {
|
|
46
44
|
if (!this.isEnabled) return;
|
|
47
45
|
|
|
48
|
-
const queryKeyString =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
averageFetchTime: 0,
|
|
56
|
-
slowFetchCount: 0,
|
|
57
|
-
lastFetchTime: null,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const metrics = this.metrics.get(queryKeyString)!;
|
|
62
|
-
const fetchTime = Date.now() - (query.state.dataUpdatedAt ?? Date.now());
|
|
46
|
+
const queryKeyString = MetricsCalculator.getQueryKeyString(query.queryKey);
|
|
47
|
+
const currentMetrics = this.metrics.get(queryKeyString) ?? null;
|
|
48
|
+
const updatedMetrics = MetricsCalculator.calculateQueryMetrics(
|
|
49
|
+
query,
|
|
50
|
+
currentMetrics,
|
|
51
|
+
this.options
|
|
52
|
+
);
|
|
63
53
|
|
|
64
|
-
metrics.
|
|
65
|
-
metrics.totalFetchTime += fetchTime;
|
|
66
|
-
metrics.averageFetchTime = metrics.totalFetchTime / metrics.fetchCount;
|
|
67
|
-
metrics.lastFetchTime = fetchTime;
|
|
54
|
+
this.metrics.set(queryKeyString, updatedMetrics);
|
|
68
55
|
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
if (this.options.
|
|
56
|
+
if (this.options.enableLogging && updatedMetrics.slowFetchCount > 0) {
|
|
57
|
+
const fetchTime = MetricsCalculator.calculateFetchTime(query);
|
|
58
|
+
if (MetricsCalculator.isSlowQuery(fetchTime, this.options.slowQueryThreshold)) {
|
|
72
59
|
DevMonitorLogger.logSlowQuery(queryKeyString, fetchTime);
|
|
73
60
|
}
|
|
74
61
|
}
|
|
@@ -80,8 +67,13 @@ class DevMonitorClass {
|
|
|
80
67
|
attach(queryClient: QueryClient): void {
|
|
81
68
|
if (!this.isEnabled) return;
|
|
82
69
|
|
|
70
|
+
// Detach from previous client if attached
|
|
71
|
+
if (this.cacheSubscription) {
|
|
72
|
+
this.detach();
|
|
73
|
+
}
|
|
74
|
+
|
|
83
75
|
this.queryClient = queryClient;
|
|
84
|
-
queryClient.getQueryCache().subscribe((event) => {
|
|
76
|
+
this.cacheSubscription = queryClient.getQueryCache().subscribe((event) => {
|
|
85
77
|
if (event.query) {
|
|
86
78
|
this.trackQuery(event.query as Query);
|
|
87
79
|
}
|
|
@@ -92,6 +84,20 @@ class DevMonitorClass {
|
|
|
92
84
|
}
|
|
93
85
|
}
|
|
94
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Detach monitor from query client
|
|
89
|
+
*/
|
|
90
|
+
detach(): void {
|
|
91
|
+
if (!this.isEnabled) return;
|
|
92
|
+
|
|
93
|
+
if (this.cacheSubscription) {
|
|
94
|
+
this.cacheSubscription();
|
|
95
|
+
this.cacheSubscription = null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this.queryClient = null;
|
|
99
|
+
}
|
|
100
|
+
|
|
95
101
|
/**
|
|
96
102
|
* Get all query metrics
|
|
97
103
|
*/
|
|
@@ -105,7 +111,7 @@ class DevMonitorClass {
|
|
|
105
111
|
*/
|
|
106
112
|
getQueryMetrics(queryKey: readonly unknown[]): QueryMetrics | undefined {
|
|
107
113
|
if (!this.isEnabled) return undefined;
|
|
108
|
-
const queryKeyString =
|
|
114
|
+
const queryKeyString = MetricsCalculator.getQueryKeyString(queryKey);
|
|
109
115
|
return this.metrics.get(queryKeyString);
|
|
110
116
|
}
|
|
111
117
|
|
|
@@ -179,9 +185,9 @@ class DevMonitorClass {
|
|
|
179
185
|
*/
|
|
180
186
|
reset(): void {
|
|
181
187
|
if (!this.isEnabled) return;
|
|
188
|
+
this.detach();
|
|
182
189
|
this.stopStatsLogging();
|
|
183
190
|
this.clear();
|
|
184
|
-
this.queryClient = null;
|
|
185
191
|
if (this.options.enableLogging) {
|
|
186
192
|
DevMonitorLogger.logReset();
|
|
187
193
|
}
|
|
@@ -13,10 +13,22 @@ interface CacheEntry<T> {
|
|
|
13
13
|
export class SimpleCache<T> {
|
|
14
14
|
private cache = new Map<string, CacheEntry<T>>();
|
|
15
15
|
private defaultTTL: number;
|
|
16
|
+
private cleanupTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
16
17
|
|
|
17
18
|
constructor(defaultTTL: number = 60000) {
|
|
18
19
|
this.defaultTTL = defaultTTL;
|
|
19
|
-
this.
|
|
20
|
+
this.scheduleCleanup();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Destroy the cache and stop cleanup timer
|
|
25
|
+
*/
|
|
26
|
+
destroy(): void {
|
|
27
|
+
if (this.cleanupTimeout) {
|
|
28
|
+
clearTimeout(this.cleanupTimeout);
|
|
29
|
+
this.cleanupTimeout = null;
|
|
30
|
+
}
|
|
31
|
+
this.cache.clear();
|
|
20
32
|
}
|
|
21
33
|
|
|
22
34
|
set(key: string, value: T, ttl?: number): void {
|
|
@@ -58,7 +70,17 @@ export class SimpleCache<T> {
|
|
|
58
70
|
this.cache.delete(key);
|
|
59
71
|
}
|
|
60
72
|
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private scheduleCleanup(): void {
|
|
76
|
+
if (this.cleanupTimeout) {
|
|
77
|
+
clearTimeout(this.cleanupTimeout);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.cleanup();
|
|
61
81
|
|
|
62
|
-
setTimeout(() =>
|
|
82
|
+
this.cleanupTimeout = setTimeout(() => {
|
|
83
|
+
this.scheduleCleanup();
|
|
84
|
+
}, 60000);
|
|
63
85
|
}
|
|
64
86
|
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Alert Toast Helper Functions
|
|
3
|
-
* Style and color helpers for alert toast component
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { AlertType } from '../AlertTypes';
|
|
7
|
-
import type { DesignTokens } from '../../../theme';
|
|
8
|
-
import type { StyleProp, ViewStyle } from 'react-native';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Gets background color for alert type
|
|
12
|
-
*
|
|
13
|
-
* @param type - Alert type
|
|
14
|
-
* @param tokens - Design tokens
|
|
15
|
-
* @returns Background color string
|
|
16
|
-
*/
|
|
17
|
-
export function getAlertBackgroundColor(type: AlertType, tokens: DesignTokens): string {
|
|
18
|
-
const colors = {
|
|
19
|
-
[AlertType.SUCCESS]: tokens.colors.success,
|
|
20
|
-
[AlertType.ERROR]: tokens.colors.error,
|
|
21
|
-
[AlertType.WARNING]: tokens.colors.warning,
|
|
22
|
-
[AlertType.INFO]: tokens.colors.info,
|
|
23
|
-
};
|
|
24
|
-
return colors[type] || tokens.colors.backgroundSecondary;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Gets action button style
|
|
29
|
-
*
|
|
30
|
-
* @param style - Button style type
|
|
31
|
-
* @param tokens - Design tokens
|
|
32
|
-
* @returns Style object
|
|
33
|
-
*/
|
|
34
|
-
export function getActionButtonStyle(
|
|
35
|
-
style: 'primary' | 'secondary' | 'destructive' | undefined,
|
|
36
|
-
tokens: DesignTokens
|
|
37
|
-
): StyleProp<ViewStyle> {
|
|
38
|
-
if (style === 'secondary') {
|
|
39
|
-
return {
|
|
40
|
-
backgroundColor: undefined,
|
|
41
|
-
borderWidth: 1,
|
|
42
|
-
borderColor: tokens.colors.textInverse,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const colors = {
|
|
47
|
-
primary: tokens.colors.backgroundPrimary,
|
|
48
|
-
destructive: tokens.colors.error,
|
|
49
|
-
};
|
|
50
|
-
return { backgroundColor: colors[style as keyof typeof colors] || tokens.colors.backgroundSecondary };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Gets action text color
|
|
55
|
-
*
|
|
56
|
-
* @param style - Button style type
|
|
57
|
-
* @param tokens - Design tokens
|
|
58
|
-
* @returns Text color string
|
|
59
|
-
*/
|
|
60
|
-
export function getActionTextColor(
|
|
61
|
-
style: 'primary' | 'secondary' | 'destructive' | undefined,
|
|
62
|
-
tokens: DesignTokens
|
|
63
|
-
): string {
|
|
64
|
-
return style === 'primary' ? tokens.colors.textPrimary : tokens.colors.textInverse;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Default toast duration in milliseconds
|
|
69
|
-
*/
|
|
70
|
-
export const DEFAULT_TOAST_DURATION = 3000;
|