@moontra/moonui-pro 2.20.1 → 2.20.3
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/dist/index.d.ts +691 -261
- package/dist/index.mjs +7418 -4934
- package/package.json +11 -5
- package/plugin/index.d.ts +86 -0
- package/plugin/index.js +308 -0
- package/scripts/postbuild.js +27 -0
- package/scripts/postinstall.js +176 -23
- package/src/__tests__/use-intersection-observer.test.tsx +0 -216
- package/src/__tests__/use-local-storage.test.tsx +0 -174
- package/src/__tests__/use-pro-access.test.tsx +0 -183
- package/src/components/advanced-chart/advanced-chart.test.tsx +0 -281
- package/src/components/advanced-chart/index.tsx +0 -1242
- package/src/components/advanced-forms/index.tsx +0 -426
- package/src/components/animated-button/index.tsx +0 -385
- package/src/components/calendar/event-dialog.tsx +0 -372
- package/src/components/calendar/index.tsx +0 -1073
- 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 -462
- 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 -222
- package/src/components/dashboard/widgets/activity-feed.tsx +0 -344
- package/src/components/dashboard/widgets/chart-widget.tsx +0 -418
- package/src/components/dashboard/widgets/metric-card.tsx +0 -343
- 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/data-table.test.tsx +0 -187
- package/src/components/data-table/index.tsx +0 -845
- package/src/components/draggable-list/index.tsx +0 -100
- package/src/components/enhanced/badge.tsx +0 -191
- package/src/components/enhanced/button.tsx +0 -362
- package/src/components/enhanced/card.tsx +0 -266
- package/src/components/enhanced/dialog.tsx +0 -246
- package/src/components/enhanced/index.ts +0 -4
- package/src/components/error-boundary/index.tsx +0 -109
- package/src/components/file-upload/file-upload.test.tsx +0 -243
- 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 -307
- package/src/components/form-wizard/form-wizard-navigation.tsx +0 -118
- package/src/components/form-wizard/form-wizard-progress.tsx +0 -298
- 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 -76
- 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 -516
- 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 -513
- package/src/components/health-check/index.tsx +0 -439
- package/src/components/hover-card-3d/index.tsx +0 -530
- package/src/components/index.ts +0 -128
- 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 -1684
- 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 -29
- package/src/components/magnetic-button/index.tsx +0 -167
- package/src/components/memory-efficient-data/index.tsx +0 -1016
- package/src/components/moonui-quiz-form/index.tsx +0 -817
- package/src/components/optimized-image/index.tsx +0 -425
- package/src/components/performance-debugger/index.tsx +0 -589
- package/src/components/performance-monitor/index.tsx +0 -794
- package/src/components/phone-number-input/index.tsx +0 -338
- 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-old-backup.tsx +0 -437
- package/src/components/rich-text-editor/index.tsx +0 -2324
- package/src/components/rich-text-editor/slash-commands-extension.ts +0 -220
- 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 -865
- 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 -1148
- package/src/components/ui/accordion.tsx +0 -73
- 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 -153
- package/src/components/ui/badge.tsx +0 -228
- package/src/components/ui/breadcrumb.tsx +0 -214
- package/src/components/ui/button.tsx +0 -222
- package/src/components/ui/calendar.tsx +0 -387
- package/src/components/ui/card.tsx +0 -214
- package/src/components/ui/checkbox.tsx +0 -259
- package/src/components/ui/collapsible.tsx +0 -135
- package/src/components/ui/color-picker.tsx +0 -97
- package/src/components/ui/command.tsx +0 -225
- package/src/components/ui/dialog.tsx +0 -334
- package/src/components/ui/dropdown-menu.tsx +0 -218
- 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 -190
- package/src/components/ui/input.tsx +0 -222
- 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 -612
- package/src/components/ui/pagination.tsx +0 -123
- 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 -374
- 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 -329
- package/src/components/ui/tabs.tsx +0 -198
- 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 -14
- 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 -85
- package/src/styles/tailwind.css +0 -7
- package/src/styles/tokens.css +0 -455
- package/src/types/moonui.d.ts +0 -22
- 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,1016 +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
|
-
// Memory konfigürasyonu için tip tanımları
|
|
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
|
-
// Yeni özellikler
|
|
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
|
-
// Performance metrikleri için tip tanımı
|
|
31
|
-
interface MemoryPerformanceMetrics {
|
|
32
|
-
fps: number
|
|
33
|
-
renderTime: number
|
|
34
|
-
memoryLeaks: number
|
|
35
|
-
scrollPerformance: number
|
|
36
|
-
lastUpdate: number
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Search/Sort/Filter için tip tanımları
|
|
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
|
-
// Height measurement için tip tanımı
|
|
54
|
-
interface ItemHeightInfo {
|
|
55
|
-
index: number
|
|
56
|
-
height: number
|
|
57
|
-
offset: number
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Memory istatistikleri için tip tanımı
|
|
61
|
-
interface MemoryStats {
|
|
62
|
-
memoryUsage: number
|
|
63
|
-
itemCount: number
|
|
64
|
-
cacheHitRate: number
|
|
65
|
-
compressionRatio?: number
|
|
66
|
-
lastCleanup?: number
|
|
67
|
-
performance?: MemoryPerformanceMetrics
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Ana 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
|
-
// Yeni özellikler
|
|
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
|
-
// En eski erişileni sil
|
|
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
|
-
// En az kullanılanı sil
|
|
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
|
-
// İlk gireni sil
|
|
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
|
-
// Hit/miss tracking için güncelle
|
|
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
|
-
// Son 60 render time'ı tut
|
|
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
|
-
// Son 60 frame'i tut
|
|
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
|
-
// Basit memory leak detection
|
|
257
|
-
return this.renderTimes.filter(time => time > 16).length // 16ms = 60fps threshold
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
private calculateScrollPerformance(): number {
|
|
261
|
-
// Scroll performance hesaplama (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
|
-
// Scroll position'a kadar olan itemları say
|
|
322
|
-
while (start < itemCount && currentOffset < scrollTop) {
|
|
323
|
-
currentOffset += this.getItemHeight(start)
|
|
324
|
-
start++
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
start = Math.max(0, start - 1)
|
|
328
|
-
|
|
329
|
-
// Görünür alan boyunca devam et
|
|
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
|
-
// Ana Memory Efficient Data Component
|
|
344
|
-
export function MemoryEfficientData<T = any>({
|
|
345
|
-
data,
|
|
346
|
-
config = {},
|
|
347
|
-
renderItem,
|
|
348
|
-
height = 400,
|
|
349
|
-
onMemoryUpdate,
|
|
350
|
-
className,
|
|
351
|
-
// Yeni props
|
|
352
|
-
itemHeight = 60,
|
|
353
|
-
onSearch,
|
|
354
|
-
onSort,
|
|
355
|
-
onFilter,
|
|
356
|
-
onExport,
|
|
357
|
-
searchPlaceholder = "Ara...",
|
|
358
|
-
emptyStateMessage = "Veri bulunamadı",
|
|
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
|
-
// Yeni konfigürasyonlar
|
|
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 yönetimi
|
|
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
|
-
const [isLoading, setIsLoading] = useState(false)
|
|
396
|
-
const [error, setError] = useState<string | null>(null)
|
|
397
|
-
|
|
398
|
-
// Refs
|
|
399
|
-
const containerRef = useRef<HTMLDivElement>(null)
|
|
400
|
-
const cacheRef = useRef(new MemoryCache<T>(Math.floor(maxMemoryItems / chunkSize), cacheStrategy))
|
|
401
|
-
const scrollTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined)
|
|
402
|
-
const performanceTrackerRef = useRef(new PerformanceTracker())
|
|
403
|
-
const heightManagerRef = useRef(new VariableHeightManager(typeof itemHeight === 'number' ? itemHeight : 60))
|
|
404
|
-
const itemRefs = useRef<Map<number, HTMLDivElement>>(new Map())
|
|
405
|
-
|
|
406
|
-
// İşlenmiş data'yı hesapla (search, sort, filter)
|
|
407
|
-
const processedData = useMemo(() => {
|
|
408
|
-
let result = [...data]
|
|
409
|
-
|
|
410
|
-
try {
|
|
411
|
-
setIsLoading(true)
|
|
412
|
-
|
|
413
|
-
// Search functionality
|
|
414
|
-
if (enableSearch && tableFeatures.searchTerm && onSearch) {
|
|
415
|
-
result = onSearch(tableFeatures.searchTerm)
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Filter functionality
|
|
419
|
-
if (enableFilter && tableFeatures.filters && tableFeatures.filters.length > 0 && onFilter) {
|
|
420
|
-
result = onFilter(result, tableFeatures.filters)
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// Sort functionality
|
|
424
|
-
if (enableSort && tableFeatures.sortConfig && onSort) {
|
|
425
|
-
result = onSort(result, tableFeatures.sortConfig.key, tableFeatures.sortConfig.direction)
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
setError(null)
|
|
429
|
-
return result
|
|
430
|
-
} catch (err) {
|
|
431
|
-
setError(err instanceof Error ? err.message : 'Veri işlenirken hata oluştu')
|
|
432
|
-
return data
|
|
433
|
-
} finally {
|
|
434
|
-
setIsLoading(false)
|
|
435
|
-
}
|
|
436
|
-
}, [data, tableFeatures, enableSearch, enableFilter, enableSort, onSearch, onFilter, onSort])
|
|
437
|
-
|
|
438
|
-
// Visible data'yı memoize et
|
|
439
|
-
const visibleData = useMemo(() => {
|
|
440
|
-
if (performanceTracking) {
|
|
441
|
-
performanceTrackerRef.current.startRender()
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
const start = visibleRange.start
|
|
445
|
-
const end = Math.min(visibleRange.end, processedData.length)
|
|
446
|
-
|
|
447
|
-
// Cache'den kontrol et
|
|
448
|
-
const cacheKey = `${start}-${end}-${JSON.stringify(tableFeatures)}`
|
|
449
|
-
const cachedData = cacheRef.current.get(cacheKey)
|
|
450
|
-
|
|
451
|
-
if (cachedData && cachedData.length === (end - start)) {
|
|
452
|
-
if (performanceTracking) {
|
|
453
|
-
performanceTrackerRef.current.endRender()
|
|
454
|
-
}
|
|
455
|
-
return cachedData
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// Yeni data slice'ını oluştur
|
|
459
|
-
const newData = processedData.slice(start, end)
|
|
460
|
-
|
|
461
|
-
// Cache'e kaydet
|
|
462
|
-
cacheRef.current.set(cacheKey, newData, compression)
|
|
463
|
-
|
|
464
|
-
if (performanceTracking) {
|
|
465
|
-
performanceTrackerRef.current.endRender()
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
return newData
|
|
469
|
-
}, [processedData, visibleRange, compression, tableFeatures, performanceTracking])
|
|
470
|
-
|
|
471
|
-
// Item height measurement
|
|
472
|
-
const measureItemHeight = useCallback((index: number, element: HTMLDivElement) => {
|
|
473
|
-
if (!variableHeight) return
|
|
474
|
-
|
|
475
|
-
const height = element.getBoundingClientRect().height
|
|
476
|
-
heightManagerRef.current.setItemHeight(index, height)
|
|
477
|
-
|
|
478
|
-
// Force re-render if height changed significantly
|
|
479
|
-
const oldHeight = heightManagerRef.current.getItemHeight(index)
|
|
480
|
-
if (Math.abs(height - oldHeight) > 5) {
|
|
481
|
-
// Recalculate visible range
|
|
482
|
-
const container = containerRef.current
|
|
483
|
-
if (container) {
|
|
484
|
-
handleScroll({ currentTarget: container } as React.UIEvent<HTMLDivElement>)
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}, [variableHeight])
|
|
488
|
-
|
|
489
|
-
// Scroll handler
|
|
490
|
-
const handleScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
|
|
491
|
-
if (performanceTracking) {
|
|
492
|
-
performanceTrackerRef.current.recordFrame()
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
const scrollTop = e.currentTarget.scrollTop
|
|
496
|
-
const containerHeight = e.currentTarget.clientHeight
|
|
497
|
-
|
|
498
|
-
let newStart: number, newEnd: number
|
|
499
|
-
|
|
500
|
-
if (variableHeight) {
|
|
501
|
-
// Variable height kullan
|
|
502
|
-
const range = heightManagerRef.current.getVisibleRange(scrollTop, containerHeight, processedData.length)
|
|
503
|
-
newStart = range.start
|
|
504
|
-
newEnd = range.end
|
|
505
|
-
} else {
|
|
506
|
-
// Fixed height kullan
|
|
507
|
-
const itemHeightValue = typeof itemHeight === 'number' ? itemHeight : 60
|
|
508
|
-
const startIndex = Math.floor(scrollTop / itemHeightValue)
|
|
509
|
-
const visibleCount = Math.ceil(containerHeight / itemHeightValue)
|
|
510
|
-
|
|
511
|
-
// Buffer ekle
|
|
512
|
-
const bufferSize = Math.floor(visibleCount * 0.5)
|
|
513
|
-
newStart = Math.max(0, startIndex - bufferSize)
|
|
514
|
-
newEnd = Math.min(processedData.length, startIndex + visibleCount + bufferSize)
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
// Preloading strategy
|
|
518
|
-
if (preloadStrategy === 'aggressive') {
|
|
519
|
-
const extraBuffer = Math.floor((newEnd - newStart) * 0.5)
|
|
520
|
-
newStart = Math.max(0, newStart - extraBuffer)
|
|
521
|
-
newEnd = Math.min(processedData.length, newEnd + extraBuffer)
|
|
522
|
-
} else if (preloadStrategy === 'smart') {
|
|
523
|
-
// Scroll direction'a göre preload
|
|
524
|
-
const currentScroll = scrollTop
|
|
525
|
-
const lastScroll = scrollTimeoutRef.current ? parseInt(scrollTimeoutRef.current as any) : 0
|
|
526
|
-
const isScrollingDown = currentScroll > lastScroll
|
|
527
|
-
|
|
528
|
-
if (isScrollingDown) {
|
|
529
|
-
newEnd = Math.min(processedData.length, newEnd + Math.floor((newEnd - newStart) * 0.2))
|
|
530
|
-
} else {
|
|
531
|
-
newStart = Math.max(0, newStart - Math.floor((newEnd - newStart) * 0.2))
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// Throttle scroll updates
|
|
536
|
-
if (scrollTimeoutRef.current) {
|
|
537
|
-
clearTimeout(scrollTimeoutRef.current)
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
scrollTimeoutRef.current = setTimeout(() => {
|
|
541
|
-
setVisibleRange({ start: newStart, end: newEnd })
|
|
542
|
-
}, 16) // ~60fps
|
|
543
|
-
}, [processedData.length, variableHeight, itemHeight, preloadStrategy, performanceTracking])
|
|
544
|
-
|
|
545
|
-
// Search handler
|
|
546
|
-
const handleSearch = useCallback((searchTerm: string) => {
|
|
547
|
-
setTableFeatures(prev => ({ ...prev, searchTerm }))
|
|
548
|
-
}, [])
|
|
549
|
-
|
|
550
|
-
// Sort handler
|
|
551
|
-
const handleSort = useCallback((key: keyof T) => {
|
|
552
|
-
setTableFeatures(prev => {
|
|
553
|
-
const currentDirection = prev.sortConfig?.key === key && prev.sortConfig?.direction === 'asc' ? 'desc' : 'asc'
|
|
554
|
-
return {
|
|
555
|
-
...prev,
|
|
556
|
-
sortConfig: { key, direction: currentDirection }
|
|
557
|
-
}
|
|
558
|
-
})
|
|
559
|
-
}, [])
|
|
560
|
-
|
|
561
|
-
// Filter handler
|
|
562
|
-
const handleFilter = useCallback((filters: TableFeatures<T>['filters']) => {
|
|
563
|
-
setTableFeatures(prev => ({ ...prev, filters }))
|
|
564
|
-
}, [])
|
|
565
|
-
|
|
566
|
-
// Export handler
|
|
567
|
-
const handleExport = useCallback((format: 'csv' | 'json' | 'xlsx') => {
|
|
568
|
-
if (onExport) {
|
|
569
|
-
onExport(processedData, format)
|
|
570
|
-
}
|
|
571
|
-
}, [processedData, onExport])
|
|
572
|
-
|
|
573
|
-
// Memory statistics'i güncelle
|
|
574
|
-
useEffect(() => {
|
|
575
|
-
const cacheStats = cacheRef.current.getStats()
|
|
576
|
-
const performanceMetrics = performanceTracking ? performanceTrackerRef.current.getMetrics() : undefined
|
|
577
|
-
|
|
578
|
-
const newStats: MemoryStats = {
|
|
579
|
-
memoryUsage: cacheStats.memoryUsage,
|
|
580
|
-
itemCount: visibleData.length,
|
|
581
|
-
cacheHitRate: cacheStats.hitRate,
|
|
582
|
-
compressionRatio: compression ? 0.7 : 1.0,
|
|
583
|
-
lastCleanup: Date.now(),
|
|
584
|
-
performance: performanceMetrics
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
setMemoryStats(newStats)
|
|
588
|
-
|
|
589
|
-
if (enableAnalytics && analyticsCallback) {
|
|
590
|
-
analyticsCallback(newStats)
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
if (onMemoryUpdate) {
|
|
594
|
-
onMemoryUpdate(newStats)
|
|
595
|
-
}
|
|
596
|
-
}, [visibleData, enableAnalytics, analyticsCallback, onMemoryUpdate, compression, performanceTracking])
|
|
597
|
-
|
|
598
|
-
// Auto cleanup effect
|
|
599
|
-
useEffect(() => {
|
|
600
|
-
if (!autoCleanup) return
|
|
601
|
-
|
|
602
|
-
const cleanupInterval = setInterval(() => {
|
|
603
|
-
const memoryUsage = (visibleData.length / maxMemoryItems)
|
|
604
|
-
if (memoryUsage > cleanupThreshold) {
|
|
605
|
-
// Basit cleanup - cache'i temizle
|
|
606
|
-
cacheRef.current.clear()
|
|
607
|
-
}
|
|
608
|
-
}, 5000) // Her 5 saniyede kontrol et
|
|
609
|
-
|
|
610
|
-
return () => clearInterval(cleanupInterval)
|
|
611
|
-
}, [autoCleanup, cleanupThreshold, maxMemoryItems, visibleData.length])
|
|
612
|
-
|
|
613
|
-
// Loading ve Error states için render
|
|
614
|
-
if (error && errorComponent) {
|
|
615
|
-
return errorComponent
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
if (isLoading && loadingComponent) {
|
|
619
|
-
return loadingComponent
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// Total height hesapla
|
|
623
|
-
const totalHeight = variableHeight
|
|
624
|
-
? heightManagerRef.current.getTotalHeight(processedData.length)
|
|
625
|
-
: processedData.length * (typeof itemHeight === 'number' ? itemHeight : 60)
|
|
626
|
-
|
|
627
|
-
// Offset hesapla
|
|
628
|
-
const offsetY = variableHeight
|
|
629
|
-
? heightManagerRef.current.getOffsetForIndex(visibleRange.start)
|
|
630
|
-
: visibleRange.start * (typeof itemHeight === 'number' ? itemHeight : 60)
|
|
631
|
-
|
|
632
|
-
return (
|
|
633
|
-
<div className={cn("flex flex-col", className)}>
|
|
634
|
-
{/* Control Bar */}
|
|
635
|
-
{(enableSearch || enableSort || enableFilter || enableExport) && (
|
|
636
|
-
<motion.div
|
|
637
|
-
className="flex items-center gap-2 p-3 border-b bg-muted/5"
|
|
638
|
-
initial={animations ? { opacity: 0, y: -10 } : {}}
|
|
639
|
-
animate={animations ? { opacity: 1, y: 0 } : {}}
|
|
640
|
-
transition={{ duration: 0.2 }}
|
|
641
|
-
>
|
|
642
|
-
{enableSearch && (
|
|
643
|
-
<div className="relative flex-1 max-w-sm">
|
|
644
|
-
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
645
|
-
<input
|
|
646
|
-
type="text"
|
|
647
|
-
placeholder={searchPlaceholder}
|
|
648
|
-
value={tableFeatures.searchTerm}
|
|
649
|
-
onChange={(e) => handleSearch(e.target.value)}
|
|
650
|
-
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"
|
|
651
|
-
/>
|
|
652
|
-
</div>
|
|
653
|
-
)}
|
|
654
|
-
|
|
655
|
-
<div className="flex items-center gap-1">
|
|
656
|
-
{enableFilter && (
|
|
657
|
-
<button
|
|
658
|
-
onClick={() => {/* Filter modal açılacak */}}
|
|
659
|
-
className="p-2 hover:bg-muted rounded-md transition-colors"
|
|
660
|
-
title="Filtrele"
|
|
661
|
-
>
|
|
662
|
-
<Filter className="h-4 w-4" />
|
|
663
|
-
</button>
|
|
664
|
-
)}
|
|
665
|
-
|
|
666
|
-
{enableExport && (
|
|
667
|
-
<div className="relative">
|
|
668
|
-
<button
|
|
669
|
-
onClick={() => handleExport('csv')}
|
|
670
|
-
className="p-2 hover:bg-muted rounded-md transition-colors"
|
|
671
|
-
title="Dışa Aktar"
|
|
672
|
-
>
|
|
673
|
-
<Download className="h-4 w-4" />
|
|
674
|
-
</button>
|
|
675
|
-
</div>
|
|
676
|
-
)}
|
|
677
|
-
</div>
|
|
678
|
-
</motion.div>
|
|
679
|
-
)}
|
|
680
|
-
|
|
681
|
-
{/* Data Container */}
|
|
682
|
-
<div
|
|
683
|
-
ref={containerRef}
|
|
684
|
-
className="overflow-auto border rounded-lg bg-background"
|
|
685
|
-
style={{ height: typeof height === 'number' ? height - (enableSearch || enableSort || enableFilter || enableExport ? 60 : 0) : height }}
|
|
686
|
-
onScroll={handleScroll}
|
|
687
|
-
>
|
|
688
|
-
{processedData.length === 0 ? (
|
|
689
|
-
<motion.div
|
|
690
|
-
className="flex items-center justify-center h-full text-muted-foreground"
|
|
691
|
-
initial={animations ? { opacity: 0 } : {}}
|
|
692
|
-
animate={animations ? { opacity: 1 } : {}}
|
|
693
|
-
>
|
|
694
|
-
{emptyStateMessage}
|
|
695
|
-
</motion.div>
|
|
696
|
-
) : (
|
|
697
|
-
<div style={{ height: totalHeight, position: 'relative' }}>
|
|
698
|
-
<motion.div
|
|
699
|
-
style={{
|
|
700
|
-
transform: `translateY(${offsetY}px)`,
|
|
701
|
-
position: 'absolute',
|
|
702
|
-
width: '100%'
|
|
703
|
-
}}
|
|
704
|
-
animate={animations ? { y: offsetY } : {}}
|
|
705
|
-
transition={animations ? { duration: 0.1, ease: "linear" } : {}}
|
|
706
|
-
>
|
|
707
|
-
<AnimatePresence mode="popLayout">
|
|
708
|
-
{visibleData.map((item, index) => {
|
|
709
|
-
const globalIndex = visibleRange.start + index
|
|
710
|
-
const itemHeightValue = typeof itemHeight === 'function'
|
|
711
|
-
? itemHeight(item, globalIndex)
|
|
712
|
-
: (typeof itemHeight === 'number' ? itemHeight : 60)
|
|
713
|
-
|
|
714
|
-
return (
|
|
715
|
-
<motion.div
|
|
716
|
-
key={globalIndex}
|
|
717
|
-
ref={(el) => {
|
|
718
|
-
if (el && variableHeight) {
|
|
719
|
-
itemRefs.current.set(globalIndex, el)
|
|
720
|
-
measureItemHeight(globalIndex, el)
|
|
721
|
-
}
|
|
722
|
-
}}
|
|
723
|
-
style={variableHeight ? {} : { height: itemHeightValue }}
|
|
724
|
-
initial={animations ? { opacity: 0, y: 20 } : {}}
|
|
725
|
-
animate={animations ? { opacity: 1, y: 0 } : {}}
|
|
726
|
-
exit={animations ? { opacity: 0, y: -20 } : {}}
|
|
727
|
-
transition={animations ? { duration: 0.2 } : {}}
|
|
728
|
-
layout={animations}
|
|
729
|
-
>
|
|
730
|
-
{renderItem(item, globalIndex)}
|
|
731
|
-
</motion.div>
|
|
732
|
-
)
|
|
733
|
-
})}
|
|
734
|
-
</AnimatePresence>
|
|
735
|
-
</motion.div>
|
|
736
|
-
</div>
|
|
737
|
-
)}
|
|
738
|
-
</div>
|
|
739
|
-
|
|
740
|
-
{/* Performance Monitor */}
|
|
741
|
-
{performanceTracking && memoryStats.performance && (
|
|
742
|
-
<div className="p-2 border-t bg-muted/5 text-xs text-muted-foreground flex items-center gap-4">
|
|
743
|
-
<div className="flex items-center gap-1">
|
|
744
|
-
<Zap className="h-3 w-3" />
|
|
745
|
-
<span>FPS: {memoryStats.performance.fps}</span>
|
|
746
|
-
</div>
|
|
747
|
-
<div className="flex items-center gap-1">
|
|
748
|
-
<Clock className="h-3 w-3" />
|
|
749
|
-
<span>Render: {memoryStats.performance.renderTime}ms</span>
|
|
750
|
-
</div>
|
|
751
|
-
<div className="flex items-center gap-1">
|
|
752
|
-
<Cpu className="h-3 w-3" />
|
|
753
|
-
<span>Scroll: {memoryStats.performance.scrollPerformance}%</span>
|
|
754
|
-
</div>
|
|
755
|
-
<div className="flex items-center gap-1">
|
|
756
|
-
<BarChart3 className="h-3 w-3" />
|
|
757
|
-
<span>Cache: {Math.round(memoryStats.cacheHitRate * 100)}%</span>
|
|
758
|
-
</div>
|
|
759
|
-
</div>
|
|
760
|
-
)}
|
|
761
|
-
</div>
|
|
762
|
-
)
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
// Real-time Performance Monitor Component
|
|
766
|
-
interface RealTimePerformanceMonitorProps {
|
|
767
|
-
memoryStats?: MemoryStats
|
|
768
|
-
className?: string
|
|
769
|
-
showChart?: boolean
|
|
770
|
-
updateInterval?: number
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
export function RealTimePerformanceMonitor({
|
|
774
|
-
memoryStats,
|
|
775
|
-
className,
|
|
776
|
-
showChart = false,
|
|
777
|
-
updateInterval = 1000
|
|
778
|
-
}: RealTimePerformanceMonitorProps) {
|
|
779
|
-
const [history, setHistory] = useState<MemoryStats[]>([])
|
|
780
|
-
const [isActive, setIsActive] = useState(true)
|
|
781
|
-
|
|
782
|
-
useEffect(() => {
|
|
783
|
-
if (!memoryStats || !isActive) return
|
|
784
|
-
|
|
785
|
-
setHistory(prev => {
|
|
786
|
-
const newHistory = [...prev, memoryStats]
|
|
787
|
-
// Son 60 entry'yi tut (1 dakika)
|
|
788
|
-
return newHistory.length > 60 ? newHistory.slice(-60) : newHistory
|
|
789
|
-
})
|
|
790
|
-
}, [memoryStats, isActive])
|
|
791
|
-
|
|
792
|
-
const formatBytes = (bytes: number) => {
|
|
793
|
-
if (bytes === 0) return '0 Bytes'
|
|
794
|
-
const k = 1024
|
|
795
|
-
const sizes = ['Bytes', 'KB', 'MB', 'GB']
|
|
796
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
797
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
const getPerformanceColor = (value: number, threshold: { good: number; warning: number }) => {
|
|
801
|
-
if (value >= threshold.good) return 'text-green-500'
|
|
802
|
-
if (value >= threshold.warning) return 'text-yellow-500'
|
|
803
|
-
return 'text-red-500'
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
if (!memoryStats) {
|
|
807
|
-
return (
|
|
808
|
-
<div className={cn("p-4 border rounded-lg bg-muted/5", className)}>
|
|
809
|
-
<div className="text-center text-muted-foreground">
|
|
810
|
-
Performance monitoring devre dışı
|
|
811
|
-
</div>
|
|
812
|
-
</div>
|
|
813
|
-
)
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
return (
|
|
817
|
-
<motion.div
|
|
818
|
-
className={cn("p-4 border rounded-lg bg-background space-y-4", className)}
|
|
819
|
-
initial={{ opacity: 0, y: 20 }}
|
|
820
|
-
animate={{ opacity: 1, y: 0 }}
|
|
821
|
-
transition={{ duration: 0.3 }}
|
|
822
|
-
>
|
|
823
|
-
<div className="flex items-center justify-between">
|
|
824
|
-
<h3 className="font-semibold flex items-center gap-2">
|
|
825
|
-
<BarChart3 className="h-4 w-4" />
|
|
826
|
-
Gerçek Zamanlı Performans
|
|
827
|
-
</h3>
|
|
828
|
-
<button
|
|
829
|
-
onClick={() => setIsActive(!isActive)}
|
|
830
|
-
className={cn(
|
|
831
|
-
"px-3 py-1 text-xs rounded-md transition-colors",
|
|
832
|
-
isActive
|
|
833
|
-
? "bg-green-500/10 text-green-500 hover:bg-green-500/20"
|
|
834
|
-
: "bg-muted text-muted-foreground hover:bg-muted/80"
|
|
835
|
-
)}
|
|
836
|
-
>
|
|
837
|
-
{isActive ? 'Aktif' : 'Durduruldu'}
|
|
838
|
-
</button>
|
|
839
|
-
</div>
|
|
840
|
-
|
|
841
|
-
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
842
|
-
{/* FPS */}
|
|
843
|
-
<div className="space-y-1">
|
|
844
|
-
<div className="text-xs text-muted-foreground">FPS</div>
|
|
845
|
-
<div className={cn(
|
|
846
|
-
"text-lg font-mono font-bold",
|
|
847
|
-
memoryStats.performance ? getPerformanceColor(memoryStats.performance.fps, { good: 55, warning: 30 }) : ''
|
|
848
|
-
)}>
|
|
849
|
-
{memoryStats.performance?.fps || 0}
|
|
850
|
-
</div>
|
|
851
|
-
</div>
|
|
852
|
-
|
|
853
|
-
{/* Render Time */}
|
|
854
|
-
<div className="space-y-1">
|
|
855
|
-
<div className="text-xs text-muted-foreground">Render Süresi</div>
|
|
856
|
-
<div className={cn(
|
|
857
|
-
"text-lg font-mono font-bold",
|
|
858
|
-
memoryStats.performance ? getPerformanceColor(100 - memoryStats.performance.renderTime, { good: 84, warning: 70 }) : ''
|
|
859
|
-
)}>
|
|
860
|
-
{memoryStats.performance?.renderTime.toFixed(2) || 0}ms
|
|
861
|
-
</div>
|
|
862
|
-
</div>
|
|
863
|
-
|
|
864
|
-
{/* Memory Usage */}
|
|
865
|
-
<div className="space-y-1">
|
|
866
|
-
<div className="text-xs text-muted-foreground">Bellek Kullanımı</div>
|
|
867
|
-
<div className="text-lg font-mono font-bold">
|
|
868
|
-
{formatBytes(memoryStats.memoryUsage)}
|
|
869
|
-
</div>
|
|
870
|
-
</div>
|
|
871
|
-
|
|
872
|
-
{/* Cache Hit Rate */}
|
|
873
|
-
<div className="space-y-1">
|
|
874
|
-
<div className="text-xs text-muted-foreground">Cache Hit Rate</div>
|
|
875
|
-
<div className={cn(
|
|
876
|
-
"text-lg font-mono font-bold",
|
|
877
|
-
getPerformanceColor(memoryStats.cacheHitRate * 100, { good: 85, warning: 70 })
|
|
878
|
-
)}>
|
|
879
|
-
{Math.round(memoryStats.cacheHitRate * 100)}%
|
|
880
|
-
</div>
|
|
881
|
-
</div>
|
|
882
|
-
</div>
|
|
883
|
-
|
|
884
|
-
{/* Memory Leaks & Scroll Performance */}
|
|
885
|
-
{memoryStats.performance && (
|
|
886
|
-
<div className="grid grid-cols-2 gap-4 pt-2 border-t">
|
|
887
|
-
<div className="space-y-1">
|
|
888
|
-
<div className="text-xs text-muted-foreground">Memory Leaks</div>
|
|
889
|
-
<div className={cn(
|
|
890
|
-
"text-sm font-mono",
|
|
891
|
-
memoryStats.performance.memoryLeaks > 5 ? 'text-red-500' : 'text-green-500'
|
|
892
|
-
)}>
|
|
893
|
-
{memoryStats.performance.memoryLeaks} detection
|
|
894
|
-
</div>
|
|
895
|
-
</div>
|
|
896
|
-
<div className="space-y-1">
|
|
897
|
-
<div className="text-xs text-muted-foreground">Scroll Performance</div>
|
|
898
|
-
<div className={cn(
|
|
899
|
-
"text-sm font-mono",
|
|
900
|
-
getPerformanceColor(memoryStats.performance.scrollPerformance, { good: 85, warning: 70 })
|
|
901
|
-
)}>
|
|
902
|
-
{memoryStats.performance.scrollPerformance}%
|
|
903
|
-
</div>
|
|
904
|
-
</div>
|
|
905
|
-
</div>
|
|
906
|
-
)}
|
|
907
|
-
|
|
908
|
-
{/* Mini Chart */}
|
|
909
|
-
{showChart && history.length > 1 && (
|
|
910
|
-
<div className="pt-2 border-t">
|
|
911
|
-
<div className="text-xs text-muted-foreground mb-2">FPS Geçmişi (Son 60 güncelleme)</div>
|
|
912
|
-
<div className="h-16 flex items-end gap-1">
|
|
913
|
-
{history.slice(-30).map((stat, index) => {
|
|
914
|
-
const fps = stat.performance?.fps || 0
|
|
915
|
-
const height = Math.max(4, (fps / 60) * 100)
|
|
916
|
-
const color = fps >= 55 ? 'bg-green-500' : fps >= 30 ? 'bg-yellow-500' : 'bg-red-500'
|
|
917
|
-
|
|
918
|
-
return (
|
|
919
|
-
<motion.div
|
|
920
|
-
key={index}
|
|
921
|
-
className={cn("w-1 rounded-t-sm", color)}
|
|
922
|
-
style={{ height: `${height}%` }}
|
|
923
|
-
initial={{ height: 0 }}
|
|
924
|
-
animate={{ height: `${height}%` }}
|
|
925
|
-
transition={{ duration: 0.2 }}
|
|
926
|
-
/>
|
|
927
|
-
)
|
|
928
|
-
})}
|
|
929
|
-
</div>
|
|
930
|
-
</div>
|
|
931
|
-
)}
|
|
932
|
-
</motion.div>
|
|
933
|
-
)
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
// Memory Analytics Component (Backward compatibility)
|
|
937
|
-
interface MemoryAnalyticsProps {
|
|
938
|
-
onStatsUpdate?: (stats: MemoryStats) => void
|
|
939
|
-
showRealTime?: boolean
|
|
940
|
-
className?: string
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
export function MemoryAnalytics({
|
|
944
|
-
onStatsUpdate,
|
|
945
|
-
showRealTime = false,
|
|
946
|
-
className
|
|
947
|
-
}: MemoryAnalyticsProps) {
|
|
948
|
-
const [stats, setStats] = useState<MemoryStats>({
|
|
949
|
-
memoryUsage: 0,
|
|
950
|
-
itemCount: 0,
|
|
951
|
-
cacheHitRate: 0.95
|
|
952
|
-
})
|
|
953
|
-
|
|
954
|
-
// Mock real-time updates for backward compatibility
|
|
955
|
-
useEffect(() => {
|
|
956
|
-
if (!showRealTime) return
|
|
957
|
-
|
|
958
|
-
const interval = setInterval(() => {
|
|
959
|
-
const newStats: MemoryStats = {
|
|
960
|
-
memoryUsage: Math.random() * 50 * 1024 * 1024,
|
|
961
|
-
itemCount: Math.floor(Math.random() * 50000),
|
|
962
|
-
cacheHitRate: 0.9 + Math.random() * 0.1,
|
|
963
|
-
lastCleanup: Date.now(),
|
|
964
|
-
performance: {
|
|
965
|
-
fps: Math.floor(30 + Math.random() * 30),
|
|
966
|
-
renderTime: Math.random() * 16,
|
|
967
|
-
memoryLeaks: Math.floor(Math.random() * 3),
|
|
968
|
-
scrollPerformance: 70 + Math.random() * 30,
|
|
969
|
-
lastUpdate: Date.now()
|
|
970
|
-
} as MemoryPerformanceMetrics
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
setStats(newStats)
|
|
974
|
-
if (onStatsUpdate) {
|
|
975
|
-
onStatsUpdate(newStats)
|
|
976
|
-
}
|
|
977
|
-
}, 1000)
|
|
978
|
-
|
|
979
|
-
return () => clearInterval(interval)
|
|
980
|
-
}, [showRealTime, onStatsUpdate])
|
|
981
|
-
|
|
982
|
-
return <RealTimePerformanceMonitor memoryStats={stats} className={className} showChart={showRealTime} />
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
// Utility hook for streaming data
|
|
986
|
-
export function useStreamingData<T>(
|
|
987
|
-
initialData: T[] = [],
|
|
988
|
-
streamingConfig?: { interval?: number; maxItems?: number }
|
|
989
|
-
) {
|
|
990
|
-
const [data, setData] = useState<T[]>(initialData)
|
|
991
|
-
const { interval = 1000, maxItems = 10000 } = streamingConfig || {}
|
|
992
|
-
|
|
993
|
-
const addItem = useCallback((newItem: T) => {
|
|
994
|
-
setData(prev => {
|
|
995
|
-
const newData = [...prev, newItem]
|
|
996
|
-
return newData.length > maxItems ? newData.slice(-maxItems) : newData
|
|
997
|
-
})
|
|
998
|
-
}, [maxItems])
|
|
999
|
-
|
|
1000
|
-
const clearData = useCallback(() => {
|
|
1001
|
-
setData([])
|
|
1002
|
-
}, [])
|
|
1003
|
-
|
|
1004
|
-
return { data, addItem, clearData }
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
// Export types
|
|
1008
|
-
export type {
|
|
1009
|
-
MemoryConfig,
|
|
1010
|
-
MemoryStats,
|
|
1011
|
-
MemoryAnalyticsProps,
|
|
1012
|
-
MemoryPerformanceMetrics,
|
|
1013
|
-
TableFeatures,
|
|
1014
|
-
ItemHeightInfo,
|
|
1015
|
-
RealTimePerformanceMonitorProps
|
|
1016
|
-
}
|