@moontra/moonui-pro 2.20.2 → 2.20.4
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 +8 -3
- package/plugin/index.d.ts +86 -0
- package/plugin/index.js +308 -0
- package/scripts/postinstall.js +191 -23
- package/src/components/advanced-chart/index.tsx +0 -1246
- package/src/components/advanced-forms/index.tsx +0 -585
- package/src/components/animated-button/index.tsx +0 -385
- package/src/components/calendar/event-dialog.tsx +0 -377
- package/src/components/calendar/index.tsx +0 -1220
- package/src/components/calendar-pro/index.tsx +0 -1697
- package/src/components/color-picker/index.tsx +0 -432
- package/src/components/credit-card-input/index.tsx +0 -406
- package/src/components/dashboard/dashboard-grid.tsx +0 -480
- package/src/components/dashboard/demo.tsx +0 -425
- package/src/components/dashboard/index.tsx +0 -1046
- package/src/components/dashboard/time-range-picker.tsx +0 -336
- package/src/components/dashboard/types.ts +0 -225
- package/src/components/dashboard/widgets/activity-feed.tsx +0 -349
- package/src/components/dashboard/widgets/chart-widget.tsx +0 -418
- package/src/components/dashboard/widgets/comparison-widget.tsx +0 -177
- package/src/components/dashboard/widgets/index.ts +0 -5
- package/src/components/dashboard/widgets/metric-card.tsx +0 -363
- package/src/components/dashboard/widgets/progress-widget.tsx +0 -113
- package/src/components/data-table/data-table-bulk-actions.tsx +0 -204
- package/src/components/data-table/data-table-column-toggle.tsx +0 -169
- package/src/components/data-table/data-table-export.ts +0 -156
- package/src/components/data-table/data-table-filter-drawer.tsx +0 -448
- package/src/components/data-table/index.tsx +0 -845
- package/src/components/draggable-list/index.tsx +0 -100
- package/src/components/error-boundary/index.tsx +0 -232
- package/src/components/file-upload/index.tsx +0 -1660
- package/src/components/floating-action-button/index.tsx +0 -206
- package/src/components/form-wizard/form-wizard-context.tsx +0 -335
- package/src/components/form-wizard/form-wizard-navigation.tsx +0 -118
- package/src/components/form-wizard/form-wizard-progress.tsx +0 -329
- package/src/components/form-wizard/form-wizard-step.tsx +0 -111
- package/src/components/form-wizard/index.tsx +0 -102
- package/src/components/form-wizard/types.ts +0 -77
- package/src/components/gesture-drawer/index.tsx +0 -551
- package/src/components/github-stars/github-api.ts +0 -426
- package/src/components/github-stars/hooks.ts +0 -517
- package/src/components/github-stars/index.tsx +0 -375
- package/src/components/github-stars/types.ts +0 -148
- package/src/components/github-stars/variants.tsx +0 -515
- package/src/components/health-check/index.tsx +0 -439
- package/src/components/hover-card-3d/index.tsx +0 -529
- package/src/components/index.ts +0 -130
- package/src/components/internal/index.ts +0 -78
- package/src/components/kanban/add-card-modal.tsx +0 -502
- package/src/components/kanban/card-detail-modal.tsx +0 -761
- package/src/components/kanban/index.ts +0 -13
- package/src/components/kanban/kanban.tsx +0 -1689
- package/src/components/kanban/types.ts +0 -168
- package/src/components/lazy-component/index.tsx +0 -823
- package/src/components/license-error/index.tsx +0 -31
- package/src/components/magnetic-button/index.tsx +0 -216
- package/src/components/memory-efficient-data/index.tsx +0 -1018
- package/src/components/moonui-quiz-form/index.tsx +0 -817
- package/src/components/navbar/index.tsx +0 -781
- package/src/components/optimized-image/index.tsx +0 -425
- package/src/components/performance-debugger/index.tsx +0 -613
- package/src/components/performance-monitor/index.tsx +0 -808
- package/src/components/phone-number-input/index.tsx +0 -343
- package/src/components/phone-number-input/phone-number-input-simple.tsx +0 -167
- package/src/components/pinch-zoom/index.tsx +0 -566
- package/src/components/quiz-form/index.tsx +0 -479
- package/src/components/rich-text-editor/index.tsx +0 -2322
- package/src/components/rich-text-editor/slash-commands-extension.ts +0 -230
- package/src/components/rich-text-editor/slash-commands.css +0 -35
- package/src/components/rich-text-editor/table-styles.css +0 -65
- package/src/components/sidebar/index.tsx +0 -884
- package/src/components/spotlight-card/index.tsx +0 -191
- package/src/components/swipeable-card/index.tsx +0 -100
- package/src/components/timeline/index.tsx +0 -1183
- package/src/components/ui/accordion.tsx +0 -581
- package/src/components/ui/alert-dialog.tsx +0 -141
- package/src/components/ui/alert.tsx +0 -141
- package/src/components/ui/aspect-ratio.tsx +0 -245
- package/src/components/ui/avatar.tsx +0 -155
- package/src/components/ui/badge.tsx +0 -230
- package/src/components/ui/breadcrumb.tsx +0 -216
- package/src/components/ui/button.tsx +0 -228
- package/src/components/ui/calendar.tsx +0 -387
- package/src/components/ui/card.tsx +0 -216
- package/src/components/ui/checkbox.tsx +0 -259
- package/src/components/ui/collapsible.tsx +0 -631
- package/src/components/ui/color-picker.tsx +0 -97
- package/src/components/ui/command.tsx +0 -948
- package/src/components/ui/dialog.tsx +0 -752
- package/src/components/ui/dropdown-menu.tsx +0 -706
- package/src/components/ui/gesture-drawer.tsx +0 -11
- package/src/components/ui/hover-card.tsx +0 -29
- package/src/components/ui/index.ts +0 -222
- package/src/components/ui/input.tsx +0 -224
- package/src/components/ui/label.tsx +0 -29
- package/src/components/ui/lightbox.tsx +0 -606
- package/src/components/ui/magnetic-button.tsx +0 -129
- package/src/components/ui/media-gallery.tsx +0 -611
- package/src/components/ui/navigation-menu.tsx +0 -130
- package/src/components/ui/pagination.tsx +0 -125
- package/src/components/ui/popover.tsx +0 -185
- package/src/components/ui/progress.tsx +0 -30
- package/src/components/ui/radio-group.tsx +0 -257
- package/src/components/ui/scroll-area.tsx +0 -47
- package/src/components/ui/select.tsx +0 -378
- package/src/components/ui/separator.tsx +0 -145
- package/src/components/ui/sheet.tsx +0 -139
- package/src/components/ui/skeleton.tsx +0 -20
- package/src/components/ui/slider.tsx +0 -354
- package/src/components/ui/spotlight-card.tsx +0 -119
- package/src/components/ui/switch.tsx +0 -86
- package/src/components/ui/table.tsx +0 -331
- package/src/components/ui/tabs-pro.tsx +0 -542
- package/src/components/ui/tabs.tsx +0 -54
- package/src/components/ui/textarea.tsx +0 -28
- package/src/components/ui/toast.tsx +0 -317
- package/src/components/ui/toggle.tsx +0 -119
- package/src/components/ui/tooltip.tsx +0 -151
- package/src/components/virtual-list/index.tsx +0 -668
- package/src/hooks/use-chart.ts +0 -205
- package/src/hooks/use-data-table.ts +0 -182
- package/src/hooks/use-docs-pro-access.ts +0 -13
- package/src/hooks/use-license-check.ts +0 -65
- package/src/hooks/use-subscription.ts +0 -19
- package/src/hooks/use-toast.ts +0 -15
- package/src/index.ts +0 -22
- package/src/lib/ai-providers.ts +0 -377
- package/src/lib/component-metadata.ts +0 -18
- package/src/lib/micro-interactions.ts +0 -255
- package/src/lib/paddle.ts +0 -17
- package/src/lib/utils.ts +0 -6
- package/src/patterns/login-form/index.tsx +0 -276
- package/src/patterns/login-form/types.ts +0 -67
- package/src/setupTests.ts +0 -41
- package/src/styles/advanced-chart.css +0 -239
- package/src/styles/calendar.css +0 -35
- package/src/styles/design-system.css +0 -363
- package/src/styles/index.css +0 -681
- package/src/styles/tailwind.css +0 -7
- package/src/styles/tokens.css +0 -455
- package/src/types/next-auth.d.ts +0 -21
- package/src/use-intersection-observer.tsx +0 -154
- package/src/use-local-storage.tsx +0 -71
- package/src/use-paddle.ts +0 -138
- package/src/use-performance-optimizer.ts +0 -389
- package/src/use-pro-access.ts +0 -141
- package/src/use-scroll-animation.ts +0 -219
- package/src/use-subscription.ts +0 -37
- package/src/use-toast.ts +0 -32
- package/src/utils/chart-helpers.ts +0 -357
- package/src/utils/cn.ts +0 -6
- package/src/utils/data-processing.ts +0 -151
- package/src/utils/license-validator.tsx +0 -183
|
@@ -1,1018 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import React, { useMemo, useCallback, useState, useEffect, useRef } from 'react'
|
|
4
|
-
import { motion, AnimatePresence } from 'framer-motion'
|
|
5
|
-
import { cn } from '../../lib/utils'
|
|
6
|
-
import { Search, Filter, Download, SortAsc, SortDesc, BarChart3, Cpu, Clock, Zap } from 'lucide-react'
|
|
7
|
-
|
|
8
|
-
// Type definitions for memory configuration
|
|
9
|
-
interface MemoryConfig {
|
|
10
|
-
chunkSize?: number
|
|
11
|
-
maxMemoryItems?: number
|
|
12
|
-
cacheStrategy?: 'lru' | 'fifo' | 'lfu'
|
|
13
|
-
compression?: boolean
|
|
14
|
-
autoCleanup?: boolean
|
|
15
|
-
cleanupThreshold?: number
|
|
16
|
-
streamingMode?: boolean
|
|
17
|
-
enableAnalytics?: boolean
|
|
18
|
-
analyticsCallback?: (stats: MemoryStats) => void
|
|
19
|
-
// New features
|
|
20
|
-
variableHeight?: boolean
|
|
21
|
-
preloadStrategy?: 'none' | 'aggressive' | 'smart'
|
|
22
|
-
performanceTracking?: boolean
|
|
23
|
-
enableSearch?: boolean
|
|
24
|
-
enableSort?: boolean
|
|
25
|
-
enableFilter?: boolean
|
|
26
|
-
enableExport?: boolean
|
|
27
|
-
animations?: boolean
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Type definition for performance metrics
|
|
31
|
-
interface MemoryPerformanceMetrics {
|
|
32
|
-
fps: number
|
|
33
|
-
renderTime: number
|
|
34
|
-
memoryLeaks: number
|
|
35
|
-
scrollPerformance: number
|
|
36
|
-
lastUpdate: number
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Type definitions for Search/Sort/Filter
|
|
40
|
-
interface TableFeatures<T> {
|
|
41
|
-
searchTerm?: string
|
|
42
|
-
sortConfig?: {
|
|
43
|
-
key: keyof T
|
|
44
|
-
direction: 'asc' | 'desc'
|
|
45
|
-
}
|
|
46
|
-
filters?: Array<{
|
|
47
|
-
key: keyof T
|
|
48
|
-
value: any
|
|
49
|
-
operator: 'equals' | 'contains' | 'greaterThan' | 'lessThan'
|
|
50
|
-
}>
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Type definition for height measurement
|
|
54
|
-
interface ItemHeightInfo {
|
|
55
|
-
index: number
|
|
56
|
-
height: number
|
|
57
|
-
offset: number
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Type definition for memory statistics
|
|
61
|
-
interface MemoryStats {
|
|
62
|
-
memoryUsage: number
|
|
63
|
-
itemCount: number
|
|
64
|
-
cacheHitRate: number
|
|
65
|
-
compressionRatio?: number
|
|
66
|
-
lastCleanup?: number
|
|
67
|
-
performance?: MemoryPerformanceMetrics
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Main component props
|
|
71
|
-
export interface MemoryEfficientDataProps<T = any> {
|
|
72
|
-
data: T[]
|
|
73
|
-
config?: MemoryConfig
|
|
74
|
-
renderItem: (item: T, index: number) => React.ReactNode
|
|
75
|
-
height?: number
|
|
76
|
-
onMemoryUpdate?: (stats: MemoryStats) => void
|
|
77
|
-
className?: string
|
|
78
|
-
// New features
|
|
79
|
-
itemHeight?: number | ((item: T, index: number) => number)
|
|
80
|
-
onSearch?: (searchTerm: string) => T[]
|
|
81
|
-
onSort?: (data: T[], key: keyof T, direction: 'asc' | 'desc') => T[]
|
|
82
|
-
onFilter?: (data: T[], filters: TableFeatures<T>['filters']) => T[]
|
|
83
|
-
onExport?: (data: T[], format: 'csv' | 'json' | 'xlsx') => void
|
|
84
|
-
searchPlaceholder?: string
|
|
85
|
-
emptyStateMessage?: string
|
|
86
|
-
loadingComponent?: React.ReactNode
|
|
87
|
-
errorComponent?: React.ReactNode
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Cache node yapısı
|
|
91
|
-
interface CacheNode<T> {
|
|
92
|
-
data: T[]
|
|
93
|
-
timestamp: number
|
|
94
|
-
accessCount: number
|
|
95
|
-
compressed?: boolean
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Memory cache sınıfı
|
|
99
|
-
class MemoryCache<T> {
|
|
100
|
-
private cache = new Map<string, CacheNode<T>>()
|
|
101
|
-
private maxSize: number
|
|
102
|
-
private strategy: 'lru' | 'fifo' | 'lfu'
|
|
103
|
-
|
|
104
|
-
constructor(maxSize: number, strategy: 'lru' | 'fifo' | 'lfu' = 'lru') {
|
|
105
|
-
this.maxSize = maxSize
|
|
106
|
-
this.strategy = strategy
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
set(key: string, data: T[], compressed = false): void {
|
|
110
|
-
if (this.cache.size >= this.maxSize) {
|
|
111
|
-
this.evict()
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
this.cache.set(key, {
|
|
115
|
-
data,
|
|
116
|
-
timestamp: Date.now(),
|
|
117
|
-
accessCount: 0,
|
|
118
|
-
compressed
|
|
119
|
-
})
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
private evict(): void {
|
|
124
|
-
if (this.cache.size === 0) return
|
|
125
|
-
|
|
126
|
-
let keyToDelete: string | null = null
|
|
127
|
-
|
|
128
|
-
switch (this.strategy) {
|
|
129
|
-
case 'lru':
|
|
130
|
-
// Delete least recently used
|
|
131
|
-
let oldestTime = Infinity
|
|
132
|
-
for (const [key, node] of this.cache) {
|
|
133
|
-
if (node.timestamp < oldestTime) {
|
|
134
|
-
oldestTime = node.timestamp
|
|
135
|
-
keyToDelete = key
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
break
|
|
139
|
-
|
|
140
|
-
case 'lfu':
|
|
141
|
-
// Delete least frequently used
|
|
142
|
-
let minAccess = Infinity
|
|
143
|
-
for (const [key, node] of this.cache) {
|
|
144
|
-
if (node.accessCount < minAccess) {
|
|
145
|
-
minAccess = node.accessCount
|
|
146
|
-
keyToDelete = key
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
break
|
|
150
|
-
|
|
151
|
-
case 'fifo':
|
|
152
|
-
// Delete first in
|
|
153
|
-
keyToDelete = this.cache.keys().next().value || null
|
|
154
|
-
break
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (keyToDelete) {
|
|
158
|
-
this.cache.delete(keyToDelete)
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
clear(): void {
|
|
163
|
-
this.cache.clear()
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
getStats(): { size: number; hitRate: number; memoryUsage: number } {
|
|
167
|
-
let totalMemory = 0
|
|
168
|
-
for (const [, node] of this.cache) {
|
|
169
|
-
totalMemory += JSON.stringify(node.data).length * 2 // Rough memory calculation
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
size: this.cache.size,
|
|
174
|
-
hitRate: this.calculateHitRate(),
|
|
175
|
-
memoryUsage: totalMemory
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
private hitCount = 0
|
|
180
|
-
private missCount = 0
|
|
181
|
-
|
|
182
|
-
private calculateHitRate(): number {
|
|
183
|
-
const total = this.hitCount + this.missCount
|
|
184
|
-
return total === 0 ? 0 : this.hitCount / total
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Update for hit/miss tracking
|
|
188
|
-
get(key: string): T[] | null {
|
|
189
|
-
const node = this.cache.get(key)
|
|
190
|
-
if (!node) {
|
|
191
|
-
this.missCount++
|
|
192
|
-
return null
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
this.hitCount++
|
|
196
|
-
node.accessCount++
|
|
197
|
-
node.timestamp = Date.now()
|
|
198
|
-
return node.data
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Performance Tracker sınıfı
|
|
203
|
-
class PerformanceTracker {
|
|
204
|
-
private frames: number[] = []
|
|
205
|
-
private renderTimes: number[] = []
|
|
206
|
-
private startTime = 0
|
|
207
|
-
private lastFrameTime = 0
|
|
208
|
-
|
|
209
|
-
startRender(): void {
|
|
210
|
-
this.startTime = performance.now()
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
endRender(): void {
|
|
214
|
-
const renderTime = performance.now() - this.startTime
|
|
215
|
-
this.renderTimes.push(renderTime)
|
|
216
|
-
|
|
217
|
-
// Keep last 60 render times
|
|
218
|
-
if (this.renderTimes.length > 60) {
|
|
219
|
-
this.renderTimes.shift()
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
recordFrame(): void {
|
|
224
|
-
const now = performance.now()
|
|
225
|
-
if (this.lastFrameTime > 0) {
|
|
226
|
-
const fps = 1000 / (now - this.lastFrameTime)
|
|
227
|
-
this.frames.push(fps)
|
|
228
|
-
|
|
229
|
-
// Keep last 60 frames
|
|
230
|
-
if (this.frames.length > 60) {
|
|
231
|
-
this.frames.shift()
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
this.lastFrameTime = now
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
getMetrics(): MemoryPerformanceMetrics {
|
|
238
|
-
const avgFPS = this.frames.length > 0
|
|
239
|
-
? this.frames.reduce((a, b) => a + b, 0) / this.frames.length
|
|
240
|
-
: 0
|
|
241
|
-
|
|
242
|
-
const avgRenderTime = this.renderTimes.length > 0
|
|
243
|
-
? this.renderTimes.reduce((a, b) => a + b, 0) / this.renderTimes.length
|
|
244
|
-
: 0
|
|
245
|
-
|
|
246
|
-
return {
|
|
247
|
-
fps: Math.round(avgFPS),
|
|
248
|
-
renderTime: Math.round(avgRenderTime * 100) / 100,
|
|
249
|
-
memoryLeaks: this.detectMemoryLeaks(),
|
|
250
|
-
scrollPerformance: this.calculateScrollPerformance(),
|
|
251
|
-
lastUpdate: Date.now()
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
private detectMemoryLeaks(): number {
|
|
256
|
-
// Simple memory leak detection
|
|
257
|
-
return this.renderTimes.filter(time => time > 16).length // 16ms = 60fps threshold
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
private calculateScrollPerformance(): number {
|
|
261
|
-
// Calculate scroll performance (0-100 score)
|
|
262
|
-
const avgFPS = this.frames.length > 0
|
|
263
|
-
? this.frames.reduce((a, b) => a + b, 0) / this.frames.length
|
|
264
|
-
: 60
|
|
265
|
-
|
|
266
|
-
return Math.min(100, Math.max(0, (avgFPS / 60) * 100))
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Variable Height Manager sınıfı
|
|
271
|
-
class VariableHeightManager {
|
|
272
|
-
private heights = new Map<number, number>()
|
|
273
|
-
private defaultHeight: number
|
|
274
|
-
private totalHeight = 0
|
|
275
|
-
private measuredCount = 0
|
|
276
|
-
|
|
277
|
-
constructor(defaultHeight = 60) {
|
|
278
|
-
this.defaultHeight = defaultHeight
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
setItemHeight(index: number, height: number): void {
|
|
282
|
-
const oldHeight = this.heights.get(index) || this.defaultHeight
|
|
283
|
-
this.heights.set(index, height)
|
|
284
|
-
|
|
285
|
-
if (!this.heights.has(index)) {
|
|
286
|
-
this.measuredCount++
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
this.totalHeight += height - oldHeight
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
getItemHeight(index: number): number {
|
|
293
|
-
return this.heights.get(index) || this.defaultHeight
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
getOffsetForIndex(index: number): number {
|
|
297
|
-
let offset = 0
|
|
298
|
-
for (let i = 0; i < index; i++) {
|
|
299
|
-
offset += this.getItemHeight(i)
|
|
300
|
-
}
|
|
301
|
-
return offset
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
getTotalHeight(itemCount: number): number {
|
|
305
|
-
if (this.measuredCount === 0) {
|
|
306
|
-
return itemCount * this.defaultHeight
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const measuredHeight = Array.from(this.heights.values())
|
|
310
|
-
.reduce((sum, height) => sum + height, 0)
|
|
311
|
-
const averageHeight = measuredHeight / this.measuredCount
|
|
312
|
-
const unmeasuredCount = itemCount - this.measuredCount
|
|
313
|
-
|
|
314
|
-
return measuredHeight + (unmeasuredCount * averageHeight)
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
getVisibleRange(scrollTop: number, containerHeight: number, itemCount: number): { start: number; end: number } {
|
|
318
|
-
let start = 0
|
|
319
|
-
let currentOffset = 0
|
|
320
|
-
|
|
321
|
-
// Count items up to scroll position
|
|
322
|
-
while (start < itemCount && currentOffset < scrollTop) {
|
|
323
|
-
currentOffset += this.getItemHeight(start)
|
|
324
|
-
start++
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
start = Math.max(0, start - 1)
|
|
328
|
-
|
|
329
|
-
// Continue through visible area
|
|
330
|
-
let end = start
|
|
331
|
-
const targetOffset = scrollTop + containerHeight
|
|
332
|
-
currentOffset = this.getOffsetForIndex(start)
|
|
333
|
-
|
|
334
|
-
while (end < itemCount && currentOffset < targetOffset) {
|
|
335
|
-
currentOffset += this.getItemHeight(end)
|
|
336
|
-
end++
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return { start, end: Math.min(itemCount, end + 1) }
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Main Memory Efficient Data Component
|
|
344
|
-
function MemoryEfficientData<T = any>({
|
|
345
|
-
data,
|
|
346
|
-
config = {},
|
|
347
|
-
renderItem,
|
|
348
|
-
height = 400,
|
|
349
|
-
onMemoryUpdate,
|
|
350
|
-
className,
|
|
351
|
-
// New props
|
|
352
|
-
itemHeight = 60,
|
|
353
|
-
onSearch,
|
|
354
|
-
onSort,
|
|
355
|
-
onFilter,
|
|
356
|
-
onExport,
|
|
357
|
-
searchPlaceholder = "Search...",
|
|
358
|
-
emptyStateMessage = "No data found",
|
|
359
|
-
loadingComponent,
|
|
360
|
-
errorComponent
|
|
361
|
-
}: MemoryEfficientDataProps<T>) {
|
|
362
|
-
const {
|
|
363
|
-
chunkSize = 1000,
|
|
364
|
-
maxMemoryItems = 10000,
|
|
365
|
-
cacheStrategy = 'lru',
|
|
366
|
-
compression = false,
|
|
367
|
-
autoCleanup = true,
|
|
368
|
-
cleanupThreshold = 0.8,
|
|
369
|
-
streamingMode = false,
|
|
370
|
-
enableAnalytics = false,
|
|
371
|
-
analyticsCallback,
|
|
372
|
-
// New configurations
|
|
373
|
-
variableHeight = false,
|
|
374
|
-
preloadStrategy = 'smart',
|
|
375
|
-
performanceTracking = false,
|
|
376
|
-
enableSearch = false,
|
|
377
|
-
enableSort = false,
|
|
378
|
-
enableFilter = false,
|
|
379
|
-
enableExport = false,
|
|
380
|
-
animations = true
|
|
381
|
-
} = config
|
|
382
|
-
|
|
383
|
-
// State management
|
|
384
|
-
const [visibleRange, setVisibleRange] = useState({ start: 0, end: Math.min(chunkSize, data.length) })
|
|
385
|
-
const [memoryStats, setMemoryStats] = useState<MemoryStats>({
|
|
386
|
-
memoryUsage: 0,
|
|
387
|
-
itemCount: 0,
|
|
388
|
-
cacheHitRate: 0.95
|
|
389
|
-
})
|
|
390
|
-
const [tableFeatures, setTableFeatures] = useState<TableFeatures<T>>({
|
|
391
|
-
searchTerm: '',
|
|
392
|
-
sortConfig: undefined,
|
|
393
|
-
filters: []
|
|
394
|
-
})
|
|
395
|
-
|
|
396
|
-
// Refs
|
|
397
|
-
const containerRef = useRef<HTMLDivElement>(null)
|
|
398
|
-
const cacheRef = useRef(new MemoryCache<T>(Math.floor(maxMemoryItems / chunkSize), cacheStrategy))
|
|
399
|
-
const scrollTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined)
|
|
400
|
-
const performanceTrackerRef = useRef(new PerformanceTracker())
|
|
401
|
-
const heightManagerRef = useRef(new VariableHeightManager(typeof itemHeight === 'number' ? itemHeight : 60))
|
|
402
|
-
const itemRefs = useRef<Map<number, HTMLDivElement>>(new Map())
|
|
403
|
-
|
|
404
|
-
// Calculate processed data (search, sort, filter)
|
|
405
|
-
const processedData = useMemo(() => {
|
|
406
|
-
let result = [...data]
|
|
407
|
-
|
|
408
|
-
try {
|
|
409
|
-
// Search functionality
|
|
410
|
-
if (enableSearch && tableFeatures.searchTerm && onSearch) {
|
|
411
|
-
result = onSearch(tableFeatures.searchTerm)
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// Filter functionality
|
|
415
|
-
if (enableFilter && tableFeatures.filters && tableFeatures.filters.length > 0 && onFilter) {
|
|
416
|
-
result = onFilter(result, tableFeatures.filters)
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Sort functionality
|
|
420
|
-
if (enableSort && tableFeatures.sortConfig && onSort) {
|
|
421
|
-
result = onSort(result, tableFeatures.sortConfig.key, tableFeatures.sortConfig.direction)
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
return result
|
|
425
|
-
} catch (err) {
|
|
426
|
-
console.error('Error processing data:', err)
|
|
427
|
-
return data
|
|
428
|
-
}
|
|
429
|
-
}, [data, tableFeatures, enableSearch, enableFilter, enableSort, onSearch, onFilter, onSort])
|
|
430
|
-
|
|
431
|
-
// Memoize visible data
|
|
432
|
-
const visibleData = useMemo(() => {
|
|
433
|
-
if (performanceTracking) {
|
|
434
|
-
performanceTrackerRef.current.startRender()
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const start = visibleRange.start
|
|
438
|
-
const end = Math.min(visibleRange.end, processedData.length)
|
|
439
|
-
|
|
440
|
-
// Check from cache
|
|
441
|
-
const cacheKey = `${start}-${end}-${JSON.stringify(tableFeatures)}`
|
|
442
|
-
const cachedData = cacheRef.current.get(cacheKey)
|
|
443
|
-
|
|
444
|
-
if (cachedData && cachedData.length === (end - start)) {
|
|
445
|
-
if (performanceTracking) {
|
|
446
|
-
performanceTrackerRef.current.endRender()
|
|
447
|
-
}
|
|
448
|
-
return cachedData
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// Create new data slice
|
|
452
|
-
const newData = processedData.slice(start, end)
|
|
453
|
-
|
|
454
|
-
// Save to cache
|
|
455
|
-
cacheRef.current.set(cacheKey, newData, compression)
|
|
456
|
-
|
|
457
|
-
if (performanceTracking) {
|
|
458
|
-
performanceTrackerRef.current.endRender()
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
return newData
|
|
462
|
-
}, [processedData, visibleRange, compression, tableFeatures, performanceTracking])
|
|
463
|
-
|
|
464
|
-
// Item height measurement
|
|
465
|
-
const measureItemHeight = useCallback((index: number, element: HTMLDivElement) => {
|
|
466
|
-
if (!variableHeight) return
|
|
467
|
-
|
|
468
|
-
const height = element.getBoundingClientRect().height
|
|
469
|
-
heightManagerRef.current.setItemHeight(index, height)
|
|
470
|
-
|
|
471
|
-
// Force re-render if height changed significantly
|
|
472
|
-
const oldHeight = heightManagerRef.current.getItemHeight(index)
|
|
473
|
-
if (Math.abs(height - oldHeight) > 5) {
|
|
474
|
-
// Recalculate visible range
|
|
475
|
-
const container = containerRef.current
|
|
476
|
-
if (container) {
|
|
477
|
-
handleScroll({ currentTarget: container } as React.UIEvent<HTMLDivElement>)
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}, [variableHeight])
|
|
481
|
-
|
|
482
|
-
// Scroll handler
|
|
483
|
-
const handleScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
|
|
484
|
-
if (performanceTracking) {
|
|
485
|
-
performanceTrackerRef.current.recordFrame()
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
const scrollTop = e.currentTarget.scrollTop
|
|
489
|
-
const containerHeight = e.currentTarget.clientHeight
|
|
490
|
-
|
|
491
|
-
let newStart: number, newEnd: number
|
|
492
|
-
|
|
493
|
-
if (variableHeight) {
|
|
494
|
-
// Use variable height
|
|
495
|
-
const range = heightManagerRef.current.getVisibleRange(scrollTop, containerHeight, processedData.length)
|
|
496
|
-
newStart = range.start
|
|
497
|
-
newEnd = range.end
|
|
498
|
-
} else {
|
|
499
|
-
// Use fixed height
|
|
500
|
-
const itemHeightValue = typeof itemHeight === 'number' ? itemHeight : 60
|
|
501
|
-
const startIndex = Math.floor(scrollTop / itemHeightValue)
|
|
502
|
-
const visibleCount = Math.ceil(containerHeight / itemHeightValue)
|
|
503
|
-
|
|
504
|
-
// Add buffer
|
|
505
|
-
const bufferSize = Math.floor(visibleCount * 0.5)
|
|
506
|
-
newStart = Math.max(0, startIndex - bufferSize)
|
|
507
|
-
newEnd = Math.min(processedData.length, startIndex + visibleCount + bufferSize)
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// Preloading strategy
|
|
511
|
-
if (preloadStrategy === 'aggressive') {
|
|
512
|
-
const extraBuffer = Math.floor((newEnd - newStart) * 0.5)
|
|
513
|
-
newStart = Math.max(0, newStart - extraBuffer)
|
|
514
|
-
newEnd = Math.min(processedData.length, newEnd + extraBuffer)
|
|
515
|
-
} else if (preloadStrategy === 'smart') {
|
|
516
|
-
// Preload based on scroll direction
|
|
517
|
-
const currentScroll = scrollTop
|
|
518
|
-
const lastScroll = scrollTimeoutRef.current ? parseInt(scrollTimeoutRef.current as any) : 0
|
|
519
|
-
const isScrollingDown = currentScroll > lastScroll
|
|
520
|
-
|
|
521
|
-
if (isScrollingDown) {
|
|
522
|
-
newEnd = Math.min(processedData.length, newEnd + Math.floor((newEnd - newStart) * 0.2))
|
|
523
|
-
} else {
|
|
524
|
-
newStart = Math.max(0, newStart - Math.floor((newEnd - newStart) * 0.2))
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// Throttle scroll updates
|
|
529
|
-
if (scrollTimeoutRef.current) {
|
|
530
|
-
clearTimeout(scrollTimeoutRef.current)
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
scrollTimeoutRef.current = setTimeout(() => {
|
|
534
|
-
setVisibleRange({ start: newStart, end: newEnd })
|
|
535
|
-
}, 16) // ~60fps
|
|
536
|
-
}, [processedData.length, variableHeight, itemHeight, preloadStrategy, performanceTracking])
|
|
537
|
-
|
|
538
|
-
// Search handler
|
|
539
|
-
const handleSearch = useCallback((searchTerm: string) => {
|
|
540
|
-
setTableFeatures(prev => ({ ...prev, searchTerm }))
|
|
541
|
-
}, [])
|
|
542
|
-
|
|
543
|
-
// Export handler
|
|
544
|
-
const handleExport = useCallback((format: 'csv' | 'json' | 'xlsx') => {
|
|
545
|
-
if (onExport) {
|
|
546
|
-
onExport(processedData, format)
|
|
547
|
-
}
|
|
548
|
-
}, [processedData, onExport])
|
|
549
|
-
|
|
550
|
-
// Update memory statistics - debounced to prevent excessive updates
|
|
551
|
-
useEffect(() => {
|
|
552
|
-
if (!enableAnalytics && !onMemoryUpdate) return
|
|
553
|
-
|
|
554
|
-
const updateStats = () => {
|
|
555
|
-
const cacheStats = cacheRef.current.getStats()
|
|
556
|
-
const performanceMetrics = performanceTracking ? performanceTrackerRef.current.getMetrics() : undefined
|
|
557
|
-
|
|
558
|
-
const newStats: MemoryStats = {
|
|
559
|
-
memoryUsage: cacheStats.memoryUsage,
|
|
560
|
-
itemCount: visibleData.length,
|
|
561
|
-
cacheHitRate: cacheStats.hitRate,
|
|
562
|
-
compressionRatio: compression ? 0.7 : 1.0,
|
|
563
|
-
lastCleanup: Date.now(),
|
|
564
|
-
performance: performanceMetrics
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
setMemoryStats(newStats)
|
|
568
|
-
|
|
569
|
-
if (enableAnalytics && analyticsCallback) {
|
|
570
|
-
analyticsCallback(newStats)
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
if (onMemoryUpdate) {
|
|
574
|
-
onMemoryUpdate(newStats)
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// Debounce updates to prevent loops
|
|
579
|
-
const timeoutId = setTimeout(updateStats, 500)
|
|
580
|
-
|
|
581
|
-
return () => clearTimeout(timeoutId)
|
|
582
|
-
}, [visibleData.length, enableAnalytics, performanceTracking, compression]) // Remove callbacks from deps
|
|
583
|
-
|
|
584
|
-
// Auto cleanup effect
|
|
585
|
-
useEffect(() => {
|
|
586
|
-
if (!autoCleanup) return
|
|
587
|
-
|
|
588
|
-
const cleanupInterval = setInterval(() => {
|
|
589
|
-
const memoryUsage = (visibleData.length / maxMemoryItems)
|
|
590
|
-
if (memoryUsage > cleanupThreshold) {
|
|
591
|
-
// Simple cleanup - clear cache
|
|
592
|
-
cacheRef.current.clear()
|
|
593
|
-
}
|
|
594
|
-
}, 5000) // Check every 5 seconds
|
|
595
|
-
|
|
596
|
-
return () => clearInterval(cleanupInterval)
|
|
597
|
-
}, [autoCleanup, cleanupThreshold, maxMemoryItems, visibleData.length])
|
|
598
|
-
|
|
599
|
-
// Render for loading and error states
|
|
600
|
-
if (loadingComponent && data.length === 0) {
|
|
601
|
-
return <>{loadingComponent}</>
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
// Calculate total height
|
|
605
|
-
const totalHeight = variableHeight
|
|
606
|
-
? heightManagerRef.current.getTotalHeight(processedData.length)
|
|
607
|
-
: processedData.length * (typeof itemHeight === 'number' ? itemHeight : 60)
|
|
608
|
-
|
|
609
|
-
// Calculate offset
|
|
610
|
-
const offsetY = variableHeight
|
|
611
|
-
? heightManagerRef.current.getOffsetForIndex(visibleRange.start)
|
|
612
|
-
: visibleRange.start * (typeof itemHeight === 'number' ? itemHeight : 60)
|
|
613
|
-
|
|
614
|
-
return (
|
|
615
|
-
<div className={cn("flex flex-col", className)}>
|
|
616
|
-
{/* Control Bar */}
|
|
617
|
-
{(enableSearch || enableSort || enableFilter || enableExport) && (
|
|
618
|
-
<motion.div
|
|
619
|
-
className="flex items-center gap-2 p-3 border-b bg-muted/5"
|
|
620
|
-
initial={animations ? { opacity: 0, y: -10 } : {}}
|
|
621
|
-
animate={animations ? { opacity: 1, y: 0 } : {}}
|
|
622
|
-
transition={{ duration: 0.2 }}
|
|
623
|
-
>
|
|
624
|
-
{enableSearch && (
|
|
625
|
-
<div className="relative flex-1 max-w-sm">
|
|
626
|
-
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
627
|
-
<input
|
|
628
|
-
type="text"
|
|
629
|
-
placeholder={searchPlaceholder}
|
|
630
|
-
value={tableFeatures.searchTerm}
|
|
631
|
-
onChange={(e) => handleSearch(e.target.value)}
|
|
632
|
-
className="w-full pl-10 pr-4 py-2 border rounded-md bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary"
|
|
633
|
-
/>
|
|
634
|
-
</div>
|
|
635
|
-
)}
|
|
636
|
-
|
|
637
|
-
<div className="flex items-center gap-1">
|
|
638
|
-
{enableFilter && (
|
|
639
|
-
<button
|
|
640
|
-
onClick={() => {/* Filter modal will open */}}
|
|
641
|
-
className="p-2 hover:bg-muted rounded-md transition-colors"
|
|
642
|
-
title="Filter"
|
|
643
|
-
>
|
|
644
|
-
<Filter className="h-4 w-4" />
|
|
645
|
-
</button>
|
|
646
|
-
)}
|
|
647
|
-
|
|
648
|
-
{enableExport && (
|
|
649
|
-
<div className="relative">
|
|
650
|
-
<button
|
|
651
|
-
onClick={() => handleExport('csv')}
|
|
652
|
-
className="p-2 hover:bg-muted rounded-md transition-colors"
|
|
653
|
-
title="Export"
|
|
654
|
-
>
|
|
655
|
-
<Download className="h-4 w-4" />
|
|
656
|
-
</button>
|
|
657
|
-
</div>
|
|
658
|
-
)}
|
|
659
|
-
</div>
|
|
660
|
-
</motion.div>
|
|
661
|
-
)}
|
|
662
|
-
|
|
663
|
-
{/* Data Container */}
|
|
664
|
-
<div
|
|
665
|
-
ref={containerRef}
|
|
666
|
-
className="overflow-auto border rounded-lg bg-background"
|
|
667
|
-
style={{ height: typeof height === 'number' ? height - (enableSearch || enableSort || enableFilter || enableExport ? 60 : 0) : height }}
|
|
668
|
-
onScroll={handleScroll}
|
|
669
|
-
>
|
|
670
|
-
{processedData.length === 0 ? (
|
|
671
|
-
<motion.div
|
|
672
|
-
className="flex items-center justify-center h-full text-muted-foreground"
|
|
673
|
-
initial={animations ? { opacity: 0 } : {}}
|
|
674
|
-
animate={animations ? { opacity: 1 } : {}}
|
|
675
|
-
>
|
|
676
|
-
{emptyStateMessage}
|
|
677
|
-
</motion.div>
|
|
678
|
-
) : (
|
|
679
|
-
<div style={{ height: totalHeight, position: 'relative' }}>
|
|
680
|
-
<motion.div
|
|
681
|
-
style={{
|
|
682
|
-
transform: `translateY(${offsetY}px)`,
|
|
683
|
-
position: 'absolute',
|
|
684
|
-
width: '100%'
|
|
685
|
-
}}
|
|
686
|
-
animate={animations ? { y: offsetY } : {}}
|
|
687
|
-
transition={animations ? { duration: 0.1, ease: "linear" } : {}}
|
|
688
|
-
>
|
|
689
|
-
<AnimatePresence mode="popLayout">
|
|
690
|
-
{visibleData.map((item, index) => {
|
|
691
|
-
const globalIndex = visibleRange.start + index
|
|
692
|
-
const itemHeightValue = typeof itemHeight === 'function'
|
|
693
|
-
? itemHeight(item, globalIndex)
|
|
694
|
-
: (typeof itemHeight === 'number' ? itemHeight : 60)
|
|
695
|
-
|
|
696
|
-
return (
|
|
697
|
-
<motion.div
|
|
698
|
-
key={globalIndex}
|
|
699
|
-
ref={(el) => {
|
|
700
|
-
if (el && variableHeight) {
|
|
701
|
-
itemRefs.current.set(globalIndex, el)
|
|
702
|
-
measureItemHeight(globalIndex, el)
|
|
703
|
-
}
|
|
704
|
-
}}
|
|
705
|
-
style={variableHeight ? {} : { height: itemHeightValue }}
|
|
706
|
-
initial={animations ? { opacity: 0, y: 20 } : {}}
|
|
707
|
-
animate={animations ? { opacity: 1, y: 0 } : {}}
|
|
708
|
-
exit={animations ? { opacity: 0, y: -20 } : {}}
|
|
709
|
-
transition={animations ? { duration: 0.2 } : {}}
|
|
710
|
-
layout={animations}
|
|
711
|
-
>
|
|
712
|
-
{renderItem(item, globalIndex)}
|
|
713
|
-
</motion.div>
|
|
714
|
-
)
|
|
715
|
-
})}
|
|
716
|
-
</AnimatePresence>
|
|
717
|
-
</motion.div>
|
|
718
|
-
</div>
|
|
719
|
-
)}
|
|
720
|
-
</div>
|
|
721
|
-
|
|
722
|
-
{/* Performance Monitor */}
|
|
723
|
-
{performanceTracking && memoryStats.performance && (
|
|
724
|
-
<div className="p-2 border-t bg-muted/5 text-xs text-muted-foreground flex items-center gap-4">
|
|
725
|
-
<div className="flex items-center gap-1">
|
|
726
|
-
<Zap className="h-3 w-3" />
|
|
727
|
-
<span>FPS: {memoryStats.performance.fps}</span>
|
|
728
|
-
</div>
|
|
729
|
-
<div className="flex items-center gap-1">
|
|
730
|
-
<Clock className="h-3 w-3" />
|
|
731
|
-
<span>Render: {memoryStats.performance.renderTime}ms</span>
|
|
732
|
-
</div>
|
|
733
|
-
<div className="flex items-center gap-1">
|
|
734
|
-
<Cpu className="h-3 w-3" />
|
|
735
|
-
<span>Scroll: {memoryStats.performance.scrollPerformance}%</span>
|
|
736
|
-
</div>
|
|
737
|
-
<div className="flex items-center gap-1">
|
|
738
|
-
<BarChart3 className="h-3 w-3" />
|
|
739
|
-
<span>Cache: {Math.round(memoryStats.cacheHitRate * 100)}%</span>
|
|
740
|
-
</div>
|
|
741
|
-
</div>
|
|
742
|
-
)}
|
|
743
|
-
</div>
|
|
744
|
-
)
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
// Real-time Performance Monitor Component
|
|
748
|
-
interface RealTimePerformanceMonitorProps {
|
|
749
|
-
memoryStats?: MemoryStats
|
|
750
|
-
className?: string
|
|
751
|
-
showChart?: boolean
|
|
752
|
-
updateInterval?: number
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
function RealTimePerformanceMonitor({
|
|
756
|
-
memoryStats,
|
|
757
|
-
className,
|
|
758
|
-
showChart = false
|
|
759
|
-
}: RealTimePerformanceMonitorProps) {
|
|
760
|
-
const [history, setHistory] = useState<MemoryStats[]>([])
|
|
761
|
-
const [isActive, setIsActive] = useState(true)
|
|
762
|
-
|
|
763
|
-
useEffect(() => {
|
|
764
|
-
if (!memoryStats || !isActive) return
|
|
765
|
-
|
|
766
|
-
setHistory(prev => {
|
|
767
|
-
const newHistory = [...prev, memoryStats]
|
|
768
|
-
// Keep last 60 entries (1 minute)
|
|
769
|
-
return newHistory.length > 60 ? newHistory.slice(-60) : newHistory
|
|
770
|
-
})
|
|
771
|
-
}, [memoryStats, isActive])
|
|
772
|
-
|
|
773
|
-
const formatBytes = (bytes: number) => {
|
|
774
|
-
if (bytes === 0) return '0 Bytes'
|
|
775
|
-
const k = 1024
|
|
776
|
-
const sizes = ['Bytes', 'KB', 'MB', 'GB']
|
|
777
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
778
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
const getPerformanceColor = (value: number, threshold: { good: number; warning: number }) => {
|
|
782
|
-
if (value >= threshold.good) return 'text-green-500'
|
|
783
|
-
if (value >= threshold.warning) return 'text-yellow-500'
|
|
784
|
-
return 'text-red-500'
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
if (!memoryStats) {
|
|
788
|
-
return (
|
|
789
|
-
<div className={cn("p-4 border rounded-lg bg-muted/5", className)}>
|
|
790
|
-
<div className="text-center text-muted-foreground">
|
|
791
|
-
Performance monitoring disabled
|
|
792
|
-
</div>
|
|
793
|
-
</div>
|
|
794
|
-
)
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
return (
|
|
798
|
-
<motion.div
|
|
799
|
-
className={cn("p-4 border rounded-lg bg-background space-y-4", className)}
|
|
800
|
-
initial={{ opacity: 0, y: 20 }}
|
|
801
|
-
animate={{ opacity: 1, y: 0 }}
|
|
802
|
-
transition={{ duration: 0.3 }}
|
|
803
|
-
>
|
|
804
|
-
<div className="flex items-center justify-between">
|
|
805
|
-
<h3 className="font-semibold flex items-center gap-2">
|
|
806
|
-
<BarChart3 className="h-4 w-4" />
|
|
807
|
-
Real-time Performance
|
|
808
|
-
</h3>
|
|
809
|
-
<button
|
|
810
|
-
onClick={() => setIsActive(!isActive)}
|
|
811
|
-
className={cn(
|
|
812
|
-
"px-3 py-1 text-xs rounded-md transition-colors",
|
|
813
|
-
isActive
|
|
814
|
-
? "bg-green-500/10 text-green-500 hover:bg-green-500/20"
|
|
815
|
-
: "bg-muted text-muted-foreground hover:bg-muted/80"
|
|
816
|
-
)}
|
|
817
|
-
>
|
|
818
|
-
{isActive ? 'Active' : 'Paused'}
|
|
819
|
-
</button>
|
|
820
|
-
</div>
|
|
821
|
-
|
|
822
|
-
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
823
|
-
{/* FPS */}
|
|
824
|
-
<div className="space-y-1">
|
|
825
|
-
<div className="text-xs text-muted-foreground">FPS</div>
|
|
826
|
-
<div className={cn(
|
|
827
|
-
"text-lg font-mono font-bold",
|
|
828
|
-
memoryStats.performance ? getPerformanceColor(memoryStats.performance.fps, { good: 55, warning: 30 }) : ''
|
|
829
|
-
)}>
|
|
830
|
-
{memoryStats.performance?.fps || 0}
|
|
831
|
-
</div>
|
|
832
|
-
</div>
|
|
833
|
-
|
|
834
|
-
{/* Render Time */}
|
|
835
|
-
<div className="space-y-1">
|
|
836
|
-
<div className="text-xs text-muted-foreground">Render Time</div>
|
|
837
|
-
<div className={cn(
|
|
838
|
-
"text-lg font-mono font-bold",
|
|
839
|
-
memoryStats.performance ? getPerformanceColor(100 - memoryStats.performance.renderTime, { good: 84, warning: 70 }) : ''
|
|
840
|
-
)}>
|
|
841
|
-
{memoryStats.performance?.renderTime.toFixed(2) || 0}ms
|
|
842
|
-
</div>
|
|
843
|
-
</div>
|
|
844
|
-
|
|
845
|
-
{/* Memory Usage */}
|
|
846
|
-
<div className="space-y-1">
|
|
847
|
-
<div className="text-xs text-muted-foreground">Memory Usage</div>
|
|
848
|
-
<div className="text-lg font-mono font-bold">
|
|
849
|
-
{formatBytes(memoryStats.memoryUsage)}
|
|
850
|
-
</div>
|
|
851
|
-
</div>
|
|
852
|
-
|
|
853
|
-
{/* Cache Hit Rate */}
|
|
854
|
-
<div className="space-y-1">
|
|
855
|
-
<div className="text-xs text-muted-foreground">Cache Hit Rate</div>
|
|
856
|
-
<div className={cn(
|
|
857
|
-
"text-lg font-mono font-bold",
|
|
858
|
-
getPerformanceColor(memoryStats.cacheHitRate * 100, { good: 85, warning: 70 })
|
|
859
|
-
)}>
|
|
860
|
-
{Math.round(memoryStats.cacheHitRate * 100)}%
|
|
861
|
-
</div>
|
|
862
|
-
</div>
|
|
863
|
-
</div>
|
|
864
|
-
|
|
865
|
-
{/* Memory Leaks & Scroll Performance */}
|
|
866
|
-
{memoryStats.performance && (
|
|
867
|
-
<div className="grid grid-cols-2 gap-4 pt-2 border-t">
|
|
868
|
-
<div className="space-y-1">
|
|
869
|
-
<div className="text-xs text-muted-foreground">Memory Leaks</div>
|
|
870
|
-
<div className={cn(
|
|
871
|
-
"text-sm font-mono",
|
|
872
|
-
memoryStats.performance.memoryLeaks > 5 ? 'text-red-500' : 'text-green-500'
|
|
873
|
-
)}>
|
|
874
|
-
{memoryStats.performance.memoryLeaks} detection
|
|
875
|
-
</div>
|
|
876
|
-
</div>
|
|
877
|
-
<div className="space-y-1">
|
|
878
|
-
<div className="text-xs text-muted-foreground">Scroll Performance</div>
|
|
879
|
-
<div className={cn(
|
|
880
|
-
"text-sm font-mono",
|
|
881
|
-
getPerformanceColor(memoryStats.performance.scrollPerformance, { good: 85, warning: 70 })
|
|
882
|
-
)}>
|
|
883
|
-
{memoryStats.performance.scrollPerformance}%
|
|
884
|
-
</div>
|
|
885
|
-
</div>
|
|
886
|
-
</div>
|
|
887
|
-
)}
|
|
888
|
-
|
|
889
|
-
{/* Mini Chart */}
|
|
890
|
-
{showChart && history.length > 1 && (
|
|
891
|
-
<div className="pt-2 border-t">
|
|
892
|
-
<div className="text-xs text-muted-foreground mb-2">FPS History (Last 60 updates)</div>
|
|
893
|
-
<div className="h-16 flex items-end gap-1">
|
|
894
|
-
{history.slice(-30).map((stat, index) => {
|
|
895
|
-
const fps = stat.performance?.fps || 0
|
|
896
|
-
const height = Math.max(4, (fps / 60) * 100)
|
|
897
|
-
const color = fps >= 55 ? 'bg-green-500' : fps >= 30 ? 'bg-yellow-500' : 'bg-red-500'
|
|
898
|
-
|
|
899
|
-
return (
|
|
900
|
-
<motion.div
|
|
901
|
-
key={index}
|
|
902
|
-
className={cn("w-1 rounded-t-sm", color)}
|
|
903
|
-
style={{ height: `${height}%` }}
|
|
904
|
-
initial={{ height: 0 }}
|
|
905
|
-
animate={{ height: `${height}%` }}
|
|
906
|
-
transition={{ duration: 0.2 }}
|
|
907
|
-
/>
|
|
908
|
-
)
|
|
909
|
-
})}
|
|
910
|
-
</div>
|
|
911
|
-
</div>
|
|
912
|
-
)}
|
|
913
|
-
</motion.div>
|
|
914
|
-
)
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
// Memory Analytics Component (Backward compatibility)
|
|
918
|
-
interface MemoryAnalyticsProps {
|
|
919
|
-
onStatsUpdate?: (stats: MemoryStats) => void
|
|
920
|
-
showRealTime?: boolean
|
|
921
|
-
className?: string
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
function MemoryAnalytics({
|
|
925
|
-
onStatsUpdate,
|
|
926
|
-
showRealTime = false,
|
|
927
|
-
className
|
|
928
|
-
}: MemoryAnalyticsProps) {
|
|
929
|
-
const [stats, setStats] = useState<MemoryStats>({
|
|
930
|
-
memoryUsage: 0,
|
|
931
|
-
itemCount: 0,
|
|
932
|
-
cacheHitRate: 0.95
|
|
933
|
-
})
|
|
934
|
-
|
|
935
|
-
// Simulate real-time updates with realistic data
|
|
936
|
-
useEffect(() => {
|
|
937
|
-
if (!showRealTime) return
|
|
938
|
-
|
|
939
|
-
// Initialize with some realistic base values
|
|
940
|
-
let baseMemory = 10 * 1024 * 1024 // 10MB base
|
|
941
|
-
let currentFPS = 60
|
|
942
|
-
let currentRenderTime = 5
|
|
943
|
-
|
|
944
|
-
const interval = setInterval(() => {
|
|
945
|
-
// Simulate realistic memory usage growth
|
|
946
|
-
baseMemory += Math.random() * 1024 * 1024 - 500 * 1024 // Fluctuate around base
|
|
947
|
-
|
|
948
|
-
// Simulate FPS variations
|
|
949
|
-
currentFPS = Math.min(60, Math.max(30, currentFPS + (Math.random() - 0.5) * 10))
|
|
950
|
-
|
|
951
|
-
// Simulate render time variations
|
|
952
|
-
currentRenderTime = Math.max(1, Math.min(16, currentRenderTime + (Math.random() - 0.5) * 2))
|
|
953
|
-
|
|
954
|
-
const newStats: MemoryStats = {
|
|
955
|
-
memoryUsage: Math.max(0, baseMemory),
|
|
956
|
-
itemCount: 50000, // Fixed item count for demo
|
|
957
|
-
cacheHitRate: 0.85 + Math.random() * 0.15, // 85-100% hit rate
|
|
958
|
-
lastCleanup: Date.now(),
|
|
959
|
-
performance: {
|
|
960
|
-
fps: Math.round(currentFPS),
|
|
961
|
-
renderTime: parseFloat(currentRenderTime.toFixed(2)),
|
|
962
|
-
memoryLeaks: currentRenderTime > 10 ? Math.floor(currentRenderTime / 5) : 0,
|
|
963
|
-
scrollPerformance: Math.round((currentFPS / 60) * 100),
|
|
964
|
-
lastUpdate: Date.now()
|
|
965
|
-
} as MemoryPerformanceMetrics
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
setStats(newStats)
|
|
969
|
-
if (onStatsUpdate) {
|
|
970
|
-
onStatsUpdate(newStats)
|
|
971
|
-
}
|
|
972
|
-
}, 1000)
|
|
973
|
-
|
|
974
|
-
return () => clearInterval(interval)
|
|
975
|
-
}, [showRealTime, onStatsUpdate])
|
|
976
|
-
|
|
977
|
-
return <RealTimePerformanceMonitor memoryStats={stats} className={className} showChart={showRealTime} />
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
// Utility hook for streaming data
|
|
981
|
-
function useStreamingData<T>(
|
|
982
|
-
initialData: T[] = [],
|
|
983
|
-
streamingConfig?: { maxItems?: number }
|
|
984
|
-
) {
|
|
985
|
-
const [data, setData] = useState<T[]>(initialData)
|
|
986
|
-
const { maxItems = 10000 } = streamingConfig || {}
|
|
987
|
-
|
|
988
|
-
const addItem = useCallback((newItem: T) => {
|
|
989
|
-
setData(prev => {
|
|
990
|
-
const newData = [...prev, newItem]
|
|
991
|
-
return newData.length > maxItems ? newData.slice(-maxItems) : newData
|
|
992
|
-
})
|
|
993
|
-
}, [maxItems])
|
|
994
|
-
|
|
995
|
-
const clearData = useCallback(() => {
|
|
996
|
-
setData([])
|
|
997
|
-
}, [])
|
|
998
|
-
|
|
999
|
-
return { data, addItem, clearData }
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
// Export components and types
|
|
1003
|
-
export {
|
|
1004
|
-
MemoryEfficientData,
|
|
1005
|
-
MemoryAnalytics,
|
|
1006
|
-
RealTimePerformanceMonitor,
|
|
1007
|
-
useStreamingData
|
|
1008
|
-
};
|
|
1009
|
-
|
|
1010
|
-
export type {
|
|
1011
|
-
MemoryConfig,
|
|
1012
|
-
MemoryStats,
|
|
1013
|
-
MemoryAnalyticsProps,
|
|
1014
|
-
MemoryPerformanceMetrics,
|
|
1015
|
-
TableFeatures,
|
|
1016
|
-
ItemHeightInfo,
|
|
1017
|
-
RealTimePerformanceMonitorProps
|
|
1018
|
-
};
|