@moontra/moonui-pro 2.18.0 → 2.18.2
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.mjs +486 -434
- package/package.json +1 -1
- package/src/components/github-stars/hooks.ts +79 -8
- package/src/components/github-stars/index.tsx +65 -57
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moontra/moonui-pro",
|
|
3
|
-
"version": "2.18.
|
|
3
|
+
"version": "2.18.2",
|
|
4
4
|
"description": "Premium React components for MoonUI - Advanced UI library with 50+ pro components including performance, interactive, and gesture components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -54,17 +54,32 @@ export function useGitHubData({
|
|
|
54
54
|
|
|
55
55
|
const refreshTimeoutRef = useRef<NodeJS.Timeout>()
|
|
56
56
|
const previousStarsRef = useRef<Map<string, number>>(new Map())
|
|
57
|
+
const errorCountRef = useRef<number>(0) // Hata sayısını takip et
|
|
58
|
+
const maxErrorCount = 2 // Maksimum hata sayısı
|
|
59
|
+
|
|
60
|
+
// checkMilestones fonksiyonunu ref olarak sakla - bu şekilde her render'da yeniden oluşturulmaz
|
|
61
|
+
const milestonesRef = useRef(milestones)
|
|
62
|
+
const onMilestoneReachedRef = useRef(onMilestoneReached)
|
|
63
|
+
|
|
64
|
+
// Ref'leri güncelle
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
milestonesRef.current = milestones
|
|
67
|
+
}, [milestones])
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
onMilestoneReachedRef.current = onMilestoneReached
|
|
71
|
+
}, [onMilestoneReached])
|
|
57
72
|
|
|
58
73
|
const checkMilestones = useCallback((repos: GitHubRepository[]) => {
|
|
59
|
-
if (!
|
|
74
|
+
if (!onMilestoneReachedRef.current) return
|
|
60
75
|
|
|
61
76
|
repos.forEach(repo => {
|
|
62
77
|
const previousStars = previousStarsRef.current.get(repo.full_name) || 0
|
|
63
78
|
const currentStars = repo.stargazers_count
|
|
64
79
|
|
|
65
|
-
|
|
80
|
+
milestonesRef.current.forEach(milestone => {
|
|
66
81
|
if (previousStars < milestone && currentStars >= milestone) {
|
|
67
|
-
|
|
82
|
+
onMilestoneReachedRef.current?.({
|
|
68
83
|
count: milestone,
|
|
69
84
|
reached: true,
|
|
70
85
|
date: new Date().toISOString(),
|
|
@@ -75,9 +90,33 @@ export function useGitHubData({
|
|
|
75
90
|
|
|
76
91
|
previousStarsRef.current.set(repo.full_name, currentStars)
|
|
77
92
|
})
|
|
78
|
-
}, [
|
|
93
|
+
}, []) // Boş dependency array - fonksiyon asla yeniden oluşturulmaz
|
|
79
94
|
|
|
80
95
|
const fetchData = useCallback(async () => {
|
|
96
|
+
// Hata limiti aşıldıysa istek yapma
|
|
97
|
+
if (errorCountRef.current >= maxErrorCount) {
|
|
98
|
+
console.warn("Maximum error count reached. Stopping requests.")
|
|
99
|
+
setLoading(false)
|
|
100
|
+
setError("Maximum retry limit exceeded. Please check your configuration.")
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Gerekli bilgiler yoksa istek yapma
|
|
105
|
+
const hasValidInput =
|
|
106
|
+
(username && repository) || // Tek repository modu
|
|
107
|
+
(username && repositories && repositories.length > 0) || // Çoklu repository modu
|
|
108
|
+
(repositories && repositories.length > 0 && repositories.every(r => r.includes('/'))) || // Full path repositories
|
|
109
|
+
username // Kullanıcının tüm repositoryleri
|
|
110
|
+
|
|
111
|
+
if (!hasValidInput) {
|
|
112
|
+
console.warn("No valid input provided. Skipping API request.")
|
|
113
|
+
setLoading(false)
|
|
114
|
+
setError(null)
|
|
115
|
+
setRepos([])
|
|
116
|
+
setStats(null)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
81
120
|
try {
|
|
82
121
|
setLoading(true)
|
|
83
122
|
setError(null)
|
|
@@ -101,7 +140,15 @@ export function useGitHubData({
|
|
|
101
140
|
const repo = await fetchRepository(username, repository, token)
|
|
102
141
|
fetchedRepos = [repo]
|
|
103
142
|
}
|
|
104
|
-
// Multiple specific repositories
|
|
143
|
+
// Multiple specific repositories with full paths
|
|
144
|
+
else if (repositories && repositories.length > 0 && repositories.every(r => r.includes('/'))) {
|
|
145
|
+
const repoPromises = repositories.map(fullPath => {
|
|
146
|
+
const [owner, name] = fullPath.split('/')
|
|
147
|
+
return fetchRepository(owner, name, token)
|
|
148
|
+
})
|
|
149
|
+
fetchedRepos = await Promise.all(repoPromises)
|
|
150
|
+
}
|
|
151
|
+
// Multiple specific repositories with username
|
|
105
152
|
else if (repositories && repositories.length > 0 && username) {
|
|
106
153
|
const repoPromises = repositories.map(repoName =>
|
|
107
154
|
fetchRepository(username, repoName, token)
|
|
@@ -173,9 +220,16 @@ export function useGitHubData({
|
|
|
173
220
|
}
|
|
174
221
|
|
|
175
222
|
setLastUpdated(new Date())
|
|
223
|
+
// Başarılı olduğunda hata sayacını sıfırla
|
|
224
|
+
errorCountRef.current = 0
|
|
176
225
|
} catch (err) {
|
|
177
226
|
const errorMessage = err instanceof Error ? err.message : "Failed to fetch data"
|
|
178
227
|
setError(errorMessage)
|
|
228
|
+
|
|
229
|
+
// Hata sayacını artır
|
|
230
|
+
errorCountRef.current += 1
|
|
231
|
+
console.error(`GitHub API error (${errorCountRef.current}/${maxErrorCount}):`, errorMessage)
|
|
232
|
+
|
|
179
233
|
if (onError) {
|
|
180
234
|
onError(err instanceof Error ? err : new Error(errorMessage))
|
|
181
235
|
}
|
|
@@ -185,18 +239,29 @@ export function useGitHubData({
|
|
|
185
239
|
}, [
|
|
186
240
|
username,
|
|
187
241
|
repository,
|
|
188
|
-
repositories,
|
|
242
|
+
repositories?.join(','), // Array'i string'e çevir ki referans değişmesin
|
|
189
243
|
token,
|
|
190
244
|
sortBy,
|
|
191
245
|
maxItems,
|
|
192
|
-
checkMilestones,
|
|
246
|
+
checkMilestones, // Artık stable
|
|
193
247
|
onDataUpdate,
|
|
194
248
|
onError,
|
|
195
249
|
])
|
|
196
250
|
|
|
197
251
|
// Initial fetch
|
|
198
252
|
useEffect(() => {
|
|
199
|
-
|
|
253
|
+
// Sadece geçerli input varsa fetch yap
|
|
254
|
+
const hasValidInput =
|
|
255
|
+
(username && repository) ||
|
|
256
|
+
(username && repositories && repositories.length > 0) ||
|
|
257
|
+
(repositories && repositories.length > 0 && repositories.every(r => r.includes('/'))) ||
|
|
258
|
+
username
|
|
259
|
+
|
|
260
|
+
if (hasValidInput) {
|
|
261
|
+
fetchData()
|
|
262
|
+
} else {
|
|
263
|
+
setLoading(false)
|
|
264
|
+
}
|
|
200
265
|
}, [fetchData])
|
|
201
266
|
|
|
202
267
|
// Auto-refresh
|
|
@@ -220,6 +285,12 @@ export function useGitHubData({
|
|
|
220
285
|
}, [autoRefresh, refreshInterval, fetchData])
|
|
221
286
|
|
|
222
287
|
const refresh = useCallback(() => {
|
|
288
|
+
// Hata limiti aşıldıysa refresh yapma
|
|
289
|
+
if (errorCountRef.current >= maxErrorCount) {
|
|
290
|
+
console.warn("Cannot refresh: maximum error count reached")
|
|
291
|
+
return Promise.resolve()
|
|
292
|
+
}
|
|
293
|
+
|
|
223
294
|
clearCache()
|
|
224
295
|
return fetchData()
|
|
225
296
|
}, [fetchData])
|
|
@@ -62,6 +62,27 @@ const GitHubStarsInternal: React.FC<GitHubStarsProps> = ({
|
|
|
62
62
|
}) => {
|
|
63
63
|
const { notify } = useGitHubNotifications(enableNotifications)
|
|
64
64
|
|
|
65
|
+
// onMilestoneReached callback'ini memoize et
|
|
66
|
+
const handleMilestoneReached = React.useCallback((milestone: any) => {
|
|
67
|
+
// Handle milestone reached
|
|
68
|
+
if (celebrateAt?.includes(milestone.count)) {
|
|
69
|
+
// Trigger celebration animation
|
|
70
|
+
confetti({
|
|
71
|
+
particleCount: 100,
|
|
72
|
+
spread: 70,
|
|
73
|
+
origin: { y: 0.6 },
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// Show notification
|
|
77
|
+
notify(`🎉 Milestone Reached!`, {
|
|
78
|
+
body: `${milestone.count} stars achieved!`,
|
|
79
|
+
tag: `milestone-${milestone.count}`,
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
onMilestoneReached?.(milestone)
|
|
84
|
+
}, [celebrateAt, notify, onMilestoneReached])
|
|
85
|
+
|
|
65
86
|
const {
|
|
66
87
|
repos,
|
|
67
88
|
stats,
|
|
@@ -81,29 +102,11 @@ const GitHubStarsInternal: React.FC<GitHubStarsProps> = ({
|
|
|
81
102
|
maxItems,
|
|
82
103
|
onError,
|
|
83
104
|
onDataUpdate,
|
|
84
|
-
onMilestoneReached:
|
|
85
|
-
// Handle milestone reached
|
|
86
|
-
if (celebrateAt?.includes(milestone.count)) {
|
|
87
|
-
// Trigger celebration animation
|
|
88
|
-
confetti({
|
|
89
|
-
particleCount: 100,
|
|
90
|
-
spread: 70,
|
|
91
|
-
origin: { y: 0.6 },
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
// Show notification
|
|
95
|
-
notify(`🎉 Milestone Reached!`, {
|
|
96
|
-
body: `${milestone.count} stars achieved!`,
|
|
97
|
-
tag: `milestone-${milestone.count}`,
|
|
98
|
-
})
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
onMilestoneReached?.(milestone)
|
|
102
|
-
},
|
|
105
|
+
onMilestoneReached: handleMilestoneReached,
|
|
103
106
|
milestones,
|
|
104
107
|
})
|
|
105
108
|
|
|
106
|
-
const handleExport = (format: "json" | "csv") => {
|
|
109
|
+
const handleExport = React.useCallback((format: "json" | "csv") => {
|
|
107
110
|
if (!enableExport) return
|
|
108
111
|
|
|
109
112
|
const timestamp = new Date().toISOString().split("T")[0]
|
|
@@ -116,7 +119,47 @@ const GitHubStarsInternal: React.FC<GitHubStarsProps> = ({
|
|
|
116
119
|
} else {
|
|
117
120
|
exportAsCSV(repos, `github-stars-${username}-${timestamp}.csv`)
|
|
118
121
|
}
|
|
119
|
-
}
|
|
122
|
+
}, [enableExport, repos, stats, username])
|
|
123
|
+
|
|
124
|
+
// handleDetailedExport fonksiyonunu memoize et
|
|
125
|
+
const handleDetailedExport = React.useCallback((e: React.MouseEvent) => {
|
|
126
|
+
if (!enableExport) return
|
|
127
|
+
|
|
128
|
+
// Show export dropdown
|
|
129
|
+
const dropdown = document.createElement("div")
|
|
130
|
+
dropdown.className = "absolute z-50 mt-2 w-48 rounded-md shadow-lg bg-popover border"
|
|
131
|
+
dropdown.innerHTML = `
|
|
132
|
+
<div class="py-1">
|
|
133
|
+
<button class="w-full text-left px-4 py-2 text-sm hover:bg-accent" data-format="json">
|
|
134
|
+
Export as JSON
|
|
135
|
+
</button>
|
|
136
|
+
<button class="w-full text-left px-4 py-2 text-sm hover:bg-accent" data-format="csv">
|
|
137
|
+
Export as CSV
|
|
138
|
+
</button>
|
|
139
|
+
</div>
|
|
140
|
+
`
|
|
141
|
+
|
|
142
|
+
dropdown.addEventListener("click", (e) => {
|
|
143
|
+
const target = e.target as HTMLElement
|
|
144
|
+
const format = target.getAttribute("data-format")
|
|
145
|
+
if (format === "json" || format === "csv") {
|
|
146
|
+
handleExport(format)
|
|
147
|
+
dropdown.remove()
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
document.body.appendChild(dropdown)
|
|
152
|
+
|
|
153
|
+
// Position dropdown
|
|
154
|
+
const rect = (e.target as HTMLElement).getBoundingClientRect()
|
|
155
|
+
dropdown.style.top = `${rect.bottom + window.scrollY}px`
|
|
156
|
+
dropdown.style.left = `${rect.left + window.scrollX}px`
|
|
157
|
+
|
|
158
|
+
// Remove on outside click
|
|
159
|
+
setTimeout(() => {
|
|
160
|
+
document.addEventListener("click", () => dropdown.remove(), { once: true })
|
|
161
|
+
}, 0)
|
|
162
|
+
}, [enableExport, handleExport])
|
|
120
163
|
|
|
121
164
|
// Loading state
|
|
122
165
|
if (loading) {
|
|
@@ -201,42 +244,7 @@ const GitHubStarsInternal: React.FC<GitHubStarsProps> = ({
|
|
|
201
244
|
return (
|
|
202
245
|
<DetailedVariant
|
|
203
246
|
{...baseProps}
|
|
204
|
-
onExport={
|
|
205
|
-
// Show export dropdown
|
|
206
|
-
const dropdown = document.createElement("div")
|
|
207
|
-
dropdown.className = "absolute z-50 mt-2 w-48 rounded-md shadow-lg bg-popover border"
|
|
208
|
-
dropdown.innerHTML = `
|
|
209
|
-
<div class="py-1">
|
|
210
|
-
<button class="w-full text-left px-4 py-2 text-sm hover:bg-accent" data-format="json">
|
|
211
|
-
Export as JSON
|
|
212
|
-
</button>
|
|
213
|
-
<button class="w-full text-left px-4 py-2 text-sm hover:bg-accent" data-format="csv">
|
|
214
|
-
Export as CSV
|
|
215
|
-
</button>
|
|
216
|
-
</div>
|
|
217
|
-
`
|
|
218
|
-
|
|
219
|
-
dropdown.addEventListener("click", (e) => {
|
|
220
|
-
const target = e.target as HTMLElement
|
|
221
|
-
const format = target.getAttribute("data-format")
|
|
222
|
-
if (format === "json" || format === "csv") {
|
|
223
|
-
handleExport(format)
|
|
224
|
-
dropdown.remove()
|
|
225
|
-
}
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
document.body.appendChild(dropdown)
|
|
229
|
-
|
|
230
|
-
// Position dropdown
|
|
231
|
-
const rect = (e.target as HTMLElement).getBoundingClientRect()
|
|
232
|
-
dropdown.style.top = `${rect.bottom + window.scrollY}px`
|
|
233
|
-
dropdown.style.left = `${rect.left + window.scrollX}px`
|
|
234
|
-
|
|
235
|
-
// Remove on outside click
|
|
236
|
-
setTimeout(() => {
|
|
237
|
-
document.addEventListener("click", () => dropdown.remove(), { once: true })
|
|
238
|
-
}, 0)
|
|
239
|
-
}}
|
|
247
|
+
onExport={handleDetailedExport}
|
|
240
248
|
/>
|
|
241
249
|
)
|
|
242
250
|
case "card":
|