@nice2dev/icons 1.3.15 → 1.3.17
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/CHANGELOG.md +26 -0
- package/README.md +1 -1
- package/dist/cjs/NiceDesktopIconExporter.js +688 -599
- package/dist/cjs/NiceFaviconGenerator.js +603 -417
- package/dist/cjs/NiceIconPicker.js +788 -508
- package/dist/cjs/NiceIconPreview.js +246 -188
- package/dist/cjs/NiceIconSetCreator.js +683 -552
- package/dist/cjs/NiceIconStacker.js +547 -171
- package/dist/cjs/NiceIconValue.js +27 -35
- package/dist/cjs/NtdIconBadge.js +384 -360
- package/dist/cjs/NtdIconMorph.js +419 -354
- package/dist/cjs/NtdStateMorph.js +150 -147
- package/dist/cjs/advancedIconSearch.js +373 -510
- package/dist/cjs/animationControls.js +518 -642
- package/dist/cjs/animationSpeed.js +69 -0
- package/dist/cjs/client.js +12 -15
- package/dist/cjs/createIcon.js +374 -252
- package/dist/cjs/design-tokens.js +324 -390
- package/dist/cjs/figmaExport.js +162 -257
- package/dist/cjs/figmaSync.js +234 -316
- package/dist/cjs/headless-ui.js +240 -368
- package/dist/cjs/iconAnalytics.js +337 -393
- package/dist/cjs/iconHtml.js +64 -86
- package/dist/cjs/iconMetadata.js +392 -472
- package/dist/cjs/iconParts.js +120 -90
- package/dist/cjs/iconStack.js +136 -155
- package/dist/cjs/iconStyles.js +1670 -509
- package/dist/cjs/iconValue.js +359 -430
- package/dist/cjs/icons/accessibility.js +1119 -134
- package/dist/cjs/icons/actions.js +1290 -330
- package/dist/cjs/icons/ai.js +1424 -144
- package/dist/cjs/icons/brand.js +613 -184
- package/dist/cjs/icons/business.js +938 -192
- package/dist/cjs/icons/devtech.js +1656 -227
- package/dist/cjs/icons/ecommerce.js +802 -111
- package/dist/cjs/icons/education.js +1345 -199
- package/dist/cjs/icons/files.js +1597 -328
- package/dist/cjs/icons/fintech.js +793 -104
- package/dist/cjs/icons/food.js +1028 -156
- package/dist/cjs/icons/fun.js +804 -109
- package/dist/cjs/icons/gaming.js +916 -170
- package/dist/cjs/icons/health.js +623 -112
- package/dist/cjs/icons/index.js +32 -42
- package/dist/cjs/icons/input.js +282 -83
- package/dist/cjs/icons/legal.js +1065 -122
- package/dist/cjs/icons/media.js +1061 -267
- package/dist/cjs/icons/navigation.js +407 -123
- package/dist/cjs/icons/omniverk.js +523 -93
- package/dist/cjs/icons/realestate.js +1520 -193
- package/dist/cjs/icons/saas.js +867 -98
- package/dist/cjs/icons/science.js +1344 -202
- package/dist/cjs/icons/shapes.js +1316 -308
- package/dist/cjs/icons/smarthome.js +786 -90
- package/dist/cjs/icons/social.js +591 -59
- package/dist/cjs/icons/sports.js +1133 -133
- package/dist/cjs/icons/sustainability.js +1211 -139
- package/dist/cjs/icons/travel.js +1470 -148
- package/dist/cjs/icons/ui.js +1508 -315
- package/dist/cjs/icons/weather.js +1160 -173
- package/dist/cjs/index.js +559 -568
- package/dist/cjs/lottieIntegration.js +261 -266
- package/dist/cjs/nicetodev-icons.css +1 -1
- package/dist/cjs/particleEffects.js +270 -251
- package/dist/cjs/resolver.js +229 -284
- package/dist/cjs/rsc.js +175 -193
- package/dist/cjs/sharpen.js +73 -130
- package/dist/cjs/smilAnimations.js +380 -165
- package/dist/cjs/stacking.js +87 -122
- package/dist/cjs/tailwind-plugin.js +280 -320
- package/dist/cjs/types.js +46 -72
- package/dist/cjs/utilities.js +163 -324
- package/dist/cjs/variantProps.js +12 -52
- package/dist/esm/NiceDesktopIconExporter.js +671 -598
- package/dist/esm/NiceFaviconGenerator.js +583 -416
- package/dist/esm/NiceIconPicker.js +789 -507
- package/dist/esm/NiceIconPreview.js +247 -187
- package/dist/esm/NiceIconSetCreator.js +685 -551
- package/dist/esm/NiceIconStacker.js +549 -170
- package/dist/esm/NiceIconValue.js +29 -34
- package/dist/esm/NtdIconBadge.js +388 -359
- package/dist/esm/NtdIconMorph.js +423 -353
- package/dist/esm/NtdStateMorph.js +152 -146
- package/dist/esm/advancedIconSearch.js +391 -509
- package/dist/esm/animationControls.js +527 -641
- package/dist/esm/animationSpeed.js +69 -0
- package/dist/esm/client.js +48 -10
- package/dist/esm/createIcon.js +375 -251
- package/dist/esm/design-tokens.js +334 -387
- package/dist/esm/figmaExport.js +170 -256
- package/dist/esm/figmaSync.js +237 -315
- package/dist/esm/headless-ui.js +254 -365
- package/dist/esm/iconAnalytics.js +342 -392
- package/dist/esm/iconHtml.js +67 -85
- package/dist/esm/iconMetadata.js +404 -471
- package/dist/esm/iconParts.js +125 -89
- package/dist/esm/iconStack.js +145 -154
- package/dist/esm/iconStyles.js +1681 -508
- package/dist/esm/iconValue.js +361 -429
- package/dist/esm/icons/accessibility.js +1133 -133
- package/dist/esm/icons/actions.js +1320 -329
- package/dist/esm/icons/ai.js +1441 -143
- package/dist/esm/icons/brand.js +616 -179
- package/dist/esm/icons/business.js +957 -191
- package/dist/esm/icons/devtech.js +1687 -226
- package/dist/esm/icons/ecommerce.js +816 -110
- package/dist/esm/icons/education.js +1364 -198
- package/dist/esm/icons/files.js +1628 -327
- package/dist/esm/icons/fintech.js +807 -103
- package/dist/esm/icons/food.js +1043 -153
- package/dist/esm/icons/fun.js +819 -108
- package/dist/esm/icons/gaming.js +933 -169
- package/dist/esm/icons/health.js +636 -111
- package/dist/esm/icons/index.js +589 -32
- package/dist/esm/icons/input.js +326 -82
- package/dist/esm/icons/legal.js +1079 -121
- package/dist/esm/icons/media.js +1086 -266
- package/dist/esm/icons/navigation.js +421 -122
- package/dist/esm/icons/omniverk.js +534 -91
- package/dist/esm/icons/realestate.js +1544 -198
- package/dist/esm/icons/saas.js +880 -97
- package/dist/esm/icons/science.js +1370 -208
- package/dist/esm/icons/shapes.js +1343 -307
- package/dist/esm/icons/smarthome.js +799 -89
- package/dist/esm/icons/social.js +599 -58
- package/dist/esm/icons/sports.js +1148 -132
- package/dist/esm/icons/sustainability.js +1226 -138
- package/dist/esm/icons/travel.js +1486 -147
- package/dist/esm/icons/ui.js +1540 -314
- package/dist/esm/icons/weather.js +1181 -172
- package/dist/esm/index.js +749 -58
- package/dist/esm/lottieIntegration.js +264 -265
- package/dist/esm/nicetodev-icons.css +1 -1
- package/dist/esm/particleEffects.js +271 -250
- package/dist/esm/resolver.js +231 -283
- package/dist/esm/rsc.js +182 -192
- package/dist/esm/sharpen.js +78 -129
- package/dist/esm/smilAnimations.js +385 -164
- package/dist/esm/stacking.js +99 -121
- package/dist/esm/tailwind-plugin.js +282 -319
- package/dist/esm/types.js +50 -71
- package/dist/esm/utilities.js +177 -321
- package/dist/esm/variantProps.js +23 -52
- package/dist/types/animationSpeed.d.ts +19 -0
- package/dist/types/createIcon.d.ts +6 -0
- package/dist/types/iconStyles.d.ts +6 -1
- package/dist/types/icons/brand.d.ts +0 -4
- package/dist/types/icons/food.d.ts +0 -3
- package/dist/types/icons/index.d.ts +3 -3
- package/dist/types/icons/omniverk.d.ts +0 -3
- package/dist/types/icons/science.d.ts +1 -1
- package/dist/types/smilAnimations.d.ts +4 -2
- package/dist/types/types.d.ts +7 -0
- package/package.json +15 -22
|
@@ -1,432 +1,376 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @file iconAnalytics.ts
|
|
7
|
-
* @module @nice2dev/icons
|
|
8
|
-
* @description PRO-28.3 — Icon Usage Analytics Service
|
|
9
|
-
*
|
|
10
|
-
* Tracks and analyzes icon usage patterns:
|
|
11
|
-
* - Most used icons
|
|
12
|
-
* - Icons per project
|
|
13
|
-
* - Missing icons suggestions
|
|
14
|
-
* - Unused icons cleanup
|
|
15
|
-
*/
|
|
16
|
-
/* ══════════════════════════════════════════════════════════════════════════════
|
|
17
|
-
Analytics Service Class
|
|
18
|
-
══════════════════════════════════════════════════════════════════════════════ */
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const React = require("react");
|
|
19
4
|
const DEFAULT_CONFIG = {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
5
|
+
maxEventsInMemory: 1e4,
|
|
6
|
+
persistInterval: 6e4,
|
|
7
|
+
// 1 minute
|
|
8
|
+
storageKey: "nice2dev-icon-analytics",
|
|
9
|
+
debug: false
|
|
24
10
|
};
|
|
25
|
-
/**
|
|
26
|
-
* Icon Analytics Service for tracking usage patterns.
|
|
27
|
-
*/
|
|
28
11
|
class IconAnalyticsService {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
this.events = this.events.slice(-this.config.maxEventsInMemory);
|
|
56
|
-
}
|
|
57
|
-
if (this.config.debug) {
|
|
58
|
-
console.log('[IconAnalytics] Tracked:', event.iconName);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Track a search miss (icon not found).
|
|
63
|
-
*/
|
|
64
|
-
trackSearchMiss(query) {
|
|
65
|
-
const normalized = query.toLowerCase().trim();
|
|
66
|
-
if (normalized.length < 2) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
const count = this.searchMisses.get(normalized) || 0;
|
|
70
|
-
this.searchMisses.set(normalized, count + 1);
|
|
12
|
+
constructor(config = {}) {
|
|
13
|
+
this.events = [];
|
|
14
|
+
this.iconStats = /* @__PURE__ */ new Map();
|
|
15
|
+
this.persistTimer = null;
|
|
16
|
+
this.knownIcons = /* @__PURE__ */ new Set();
|
|
17
|
+
this.deprecatedIcons = /* @__PURE__ */ new Set();
|
|
18
|
+
this.searchMisses = /* @__PURE__ */ new Map();
|
|
19
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
20
|
+
this.loadFromStorage();
|
|
21
|
+
this.startPersistTimer();
|
|
22
|
+
}
|
|
23
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
Event Tracking
|
|
25
|
+
──────────────────────────────────────────────────────────────────────────── */
|
|
26
|
+
/**
|
|
27
|
+
* Track an icon usage event.
|
|
28
|
+
*/
|
|
29
|
+
track(event) {
|
|
30
|
+
const fullEvent = {
|
|
31
|
+
...event,
|
|
32
|
+
timestamp: Date.now()
|
|
33
|
+
};
|
|
34
|
+
this.events.push(fullEvent);
|
|
35
|
+
this.updateStats(fullEvent);
|
|
36
|
+
if (this.events.length > this.config.maxEventsInMemory) {
|
|
37
|
+
this.events = this.events.slice(-this.config.maxEventsInMemory);
|
|
71
38
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
*/
|
|
75
|
-
registerKnownIcon(name, deprecated = false) {
|
|
76
|
-
this.knownIcons.add(name);
|
|
77
|
-
if (deprecated) {
|
|
78
|
-
this.deprecatedIcons.add(name);
|
|
79
|
-
}
|
|
39
|
+
if (this.config.debug) {
|
|
40
|
+
console.log("[IconAnalytics] Tracked:", event.iconName);
|
|
80
41
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Track a search miss (icon not found).
|
|
45
|
+
*/
|
|
46
|
+
trackSearchMiss(query) {
|
|
47
|
+
const normalized = query.toLowerCase().trim();
|
|
48
|
+
if (normalized.length < 2) {
|
|
49
|
+
return;
|
|
88
50
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
lastUsed: event.timestamp,
|
|
100
|
-
contexts: new Map(),
|
|
101
|
-
projects: new Set(),
|
|
102
|
-
variants: new Map(),
|
|
103
|
-
avgSize: 0,
|
|
104
|
-
};
|
|
105
|
-
this.iconStats.set(event.iconName, stats);
|
|
106
|
-
}
|
|
107
|
-
stats.totalUsage++;
|
|
108
|
-
stats.lastUsed = event.timestamp;
|
|
109
|
-
if (event.context) {
|
|
110
|
-
const ctxCount = stats.contexts.get(event.context) || 0;
|
|
111
|
-
stats.contexts.set(event.context, ctxCount + 1);
|
|
112
|
-
}
|
|
113
|
-
if (event.projectId) {
|
|
114
|
-
stats.projects.add(event.projectId);
|
|
115
|
-
}
|
|
116
|
-
if (event.variant) {
|
|
117
|
-
const varCount = stats.variants.get(event.variant) || 0;
|
|
118
|
-
stats.variants.set(event.variant, varCount + 1);
|
|
119
|
-
}
|
|
120
|
-
if (event.size !== undefined) {
|
|
121
|
-
const sizeNum = typeof event.size === 'number' ? event.size : parseInt(event.size, 10);
|
|
122
|
-
if (!isNaN(sizeNum)) {
|
|
123
|
-
// Running average
|
|
124
|
-
stats.avgSize = (stats.avgSize * (stats.totalUsage - 1) + sizeNum) / stats.totalUsage;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
51
|
+
const count = this.searchMisses.get(normalized) || 0;
|
|
52
|
+
this.searchMisses.set(normalized, count + 1);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Register an icon as known (for finding unused).
|
|
56
|
+
*/
|
|
57
|
+
registerKnownIcon(name, deprecated = false) {
|
|
58
|
+
this.knownIcons.add(name);
|
|
59
|
+
if (deprecated) {
|
|
60
|
+
this.deprecatedIcons.add(name);
|
|
127
61
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return this.iconStats.get(iconName);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Bulk register icons.
|
|
65
|
+
*/
|
|
66
|
+
registerKnownIcons(icons) {
|
|
67
|
+
for (const icon of icons) {
|
|
68
|
+
this.registerKnownIcon(icon.name, icon.deprecated);
|
|
136
69
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
70
|
+
}
|
|
71
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
72
|
+
Stats Management
|
|
73
|
+
──────────────────────────────────────────────────────────────────────────── */
|
|
74
|
+
updateStats(event) {
|
|
75
|
+
let stats = this.iconStats.get(event.iconName);
|
|
76
|
+
if (!stats) {
|
|
77
|
+
stats = {
|
|
78
|
+
name: event.iconName,
|
|
79
|
+
totalUsage: 0,
|
|
80
|
+
firstUsed: event.timestamp,
|
|
81
|
+
lastUsed: event.timestamp,
|
|
82
|
+
contexts: /* @__PURE__ */ new Map(),
|
|
83
|
+
projects: /* @__PURE__ */ new Set(),
|
|
84
|
+
variants: /* @__PURE__ */ new Map(),
|
|
85
|
+
avgSize: 0
|
|
86
|
+
};
|
|
87
|
+
this.iconStats.set(event.iconName, stats);
|
|
145
88
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
.sort((a, b) => a.totalUsage - b.totalUsage)
|
|
152
|
-
.slice(0, limit)
|
|
153
|
-
.map((s) => ({ name: s.name, count: s.totalUsage }));
|
|
89
|
+
stats.totalUsage++;
|
|
90
|
+
stats.lastUsed = event.timestamp;
|
|
91
|
+
if (event.context) {
|
|
92
|
+
const ctxCount = stats.contexts.get(event.context) || 0;
|
|
93
|
+
stats.contexts.set(event.context, ctxCount + 1);
|
|
154
94
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
*/
|
|
158
|
-
getUnusedIcons() {
|
|
159
|
-
const usedIcons = new Set(this.iconStats.keys());
|
|
160
|
-
return Array.from(this.knownIcons).filter((name) => !usedIcons.has(name));
|
|
95
|
+
if (event.projectId) {
|
|
96
|
+
stats.projects.add(event.projectId);
|
|
161
97
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
getDeprecatedInUse() {
|
|
166
|
-
const results = [];
|
|
167
|
-
for (const name of this.deprecatedIcons) {
|
|
168
|
-
const stats = this.iconStats.get(name);
|
|
169
|
-
if (stats) {
|
|
170
|
-
results.push({ name, count: stats.totalUsage });
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return results.sort((a, b) => b.count - a.count);
|
|
98
|
+
if (event.variant) {
|
|
99
|
+
const varCount = stats.variants.get(event.variant) || 0;
|
|
100
|
+
stats.variants.set(event.variant, varCount + 1);
|
|
174
101
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
.filter(([_, count]) => count >= minMisses)
|
|
181
|
-
.sort((a, b) => b[1] - a[1])
|
|
182
|
-
.map(([query, misses]) => ({ query, misses }));
|
|
102
|
+
if (event.size !== void 0) {
|
|
103
|
+
const sizeNum = typeof event.size === "number" ? event.size : parseInt(event.size, 10);
|
|
104
|
+
if (!isNaN(sizeNum)) {
|
|
105
|
+
stats.avgSize = (stats.avgSize * (stats.totalUsage - 1) + sizeNum) / stats.totalUsage;
|
|
106
|
+
}
|
|
183
107
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
108
|
+
}
|
|
109
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
110
|
+
Queries & Reports
|
|
111
|
+
──────────────────────────────────────────────────────────────────────────── */
|
|
112
|
+
/**
|
|
113
|
+
* Get usage stats for a specific icon.
|
|
114
|
+
*/
|
|
115
|
+
getIconStats(iconName) {
|
|
116
|
+
return this.iconStats.get(iconName);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get most used icons.
|
|
120
|
+
*/
|
|
121
|
+
getMostUsedIcons(limit = 20) {
|
|
122
|
+
return Array.from(this.iconStats.values()).sort((a, b) => b.totalUsage - a.totalUsage).slice(0, limit).map((s) => ({ name: s.name, count: s.totalUsage }));
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get least used icons (icons used but rarely).
|
|
126
|
+
*/
|
|
127
|
+
getLeastUsedIcons(limit = 20) {
|
|
128
|
+
return Array.from(this.iconStats.values()).sort((a, b) => a.totalUsage - b.totalUsage).slice(0, limit).map((s) => ({ name: s.name, count: s.totalUsage }));
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get icons that have never been used.
|
|
132
|
+
*/
|
|
133
|
+
getUnusedIcons() {
|
|
134
|
+
const usedIcons = new Set(this.iconStats.keys());
|
|
135
|
+
return Array.from(this.knownIcons).filter((name) => !usedIcons.has(name));
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get deprecated icons that are still being used.
|
|
139
|
+
*/
|
|
140
|
+
getDeprecatedInUse() {
|
|
141
|
+
const results = [];
|
|
142
|
+
for (const name of this.deprecatedIcons) {
|
|
143
|
+
const stats = this.iconStats.get(name);
|
|
144
|
+
if (stats) {
|
|
145
|
+
results.push({ name, count: stats.totalUsage });
|
|
146
|
+
}
|
|
197
147
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
// Calculate unique icons and top icons per project
|
|
219
|
-
for (const summary of projects.values()) {
|
|
220
|
-
const iconsInProject = this.getIconsByProject(summary.projectId);
|
|
221
|
-
summary.uniqueIcons = iconsInProject.length;
|
|
222
|
-
summary.topIcons = iconsInProject.slice(0, 10);
|
|
223
|
-
}
|
|
224
|
-
return Array.from(projects.values());
|
|
148
|
+
return results.sort((a, b) => b.count - a.count);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get potentially missing icons based on search misses.
|
|
152
|
+
*/
|
|
153
|
+
getPotentiallyMissingIcons(minMisses = 3) {
|
|
154
|
+
return Array.from(this.searchMisses.entries()).filter(([_, count]) => count >= minMisses).sort((a, b) => b[1] - a[1]).map(([query, misses]) => ({ query, misses }));
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get icons used in a specific project.
|
|
158
|
+
*/
|
|
159
|
+
getIconsByProject(projectId) {
|
|
160
|
+
const projectIcons = [];
|
|
161
|
+
for (const stats of this.iconStats.values()) {
|
|
162
|
+
if (stats.projects.has(projectId)) {
|
|
163
|
+
const count = this.events.filter(
|
|
164
|
+
(e) => e.iconName === stats.name && e.projectId === projectId
|
|
165
|
+
).length;
|
|
166
|
+
projectIcons.push({ name: stats.name, count });
|
|
167
|
+
}
|
|
225
168
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const mostUsed = this.getMostUsedIcons(20);
|
|
244
|
-
// Trending (icons with increasing usage recently)
|
|
245
|
-
const recentCutoff = now - 7 * 24 * 60 * 60 * 1000; // last 7 days
|
|
246
|
-
const recentEvents = this.events.filter((e) => e.timestamp >= recentCutoff);
|
|
247
|
-
const olderEvents = this.events.filter((e) => e.timestamp < recentCutoff && e.timestamp >= recentCutoff - 7 * 24 * 60 * 60 * 1000);
|
|
248
|
-
const recentCounts = new Map();
|
|
249
|
-
const olderCounts = new Map();
|
|
250
|
-
for (const e of recentEvents) {
|
|
251
|
-
recentCounts.set(e.iconName, (recentCounts.get(e.iconName) || 0) + 1);
|
|
252
|
-
}
|
|
253
|
-
for (const e of olderEvents) {
|
|
254
|
-
olderCounts.set(e.iconName, (olderCounts.get(e.iconName) || 0) + 1);
|
|
255
|
-
}
|
|
256
|
-
const trendingIcons = [];
|
|
257
|
-
for (const [name, recent] of recentCounts) {
|
|
258
|
-
const older = olderCounts.get(name) || 1;
|
|
259
|
-
const growth = ((recent - older) / older) * 100;
|
|
260
|
-
if (growth > 50) {
|
|
261
|
-
trendingIcons.push({ name, growth });
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
trendingIcons.sort((a, b) => b.growth - a.growth);
|
|
265
|
-
// Underutilized
|
|
266
|
-
const underutilized = this.getLeastUsedIcons(50)
|
|
267
|
-
.filter((i) => i.count === 1)
|
|
268
|
-
.map((i) => i.name);
|
|
269
|
-
// Potentially missing
|
|
270
|
-
const potentiallyMissing = this.getPotentiallyMissingIcons()
|
|
271
|
-
.slice(0, 20)
|
|
272
|
-
.map((p) => p.query);
|
|
273
|
-
// Deprecated in use
|
|
274
|
-
const deprecatedInUse = this.getDeprecatedInUse();
|
|
275
|
-
// Project summaries
|
|
276
|
-
const projectSummaries = this.getProjectSummaries();
|
|
277
|
-
return {
|
|
278
|
-
periodStart,
|
|
279
|
-
periodEnd,
|
|
280
|
-
totalEvents: periodEvents.length,
|
|
281
|
-
uniqueIcons,
|
|
282
|
-
mostUsed,
|
|
283
|
-
trendingIcons: trendingIcons.slice(0, 10),
|
|
284
|
-
underutilized: underutilized.slice(0, 20),
|
|
285
|
-
potentiallyMissing,
|
|
286
|
-
deprecatedInUse,
|
|
287
|
-
projectSummaries,
|
|
169
|
+
return projectIcons.sort((a, b) => b.count - a.count);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get project summaries.
|
|
173
|
+
*/
|
|
174
|
+
getProjectSummaries() {
|
|
175
|
+
const projects = /* @__PURE__ */ new Map();
|
|
176
|
+
for (const event of this.events) {
|
|
177
|
+
const pid = event.projectId || "unknown";
|
|
178
|
+
let summary = projects.get(pid);
|
|
179
|
+
if (!summary) {
|
|
180
|
+
summary = {
|
|
181
|
+
projectId: pid,
|
|
182
|
+
uniqueIcons: 0,
|
|
183
|
+
totalUsage: 0,
|
|
184
|
+
topIcons: [],
|
|
185
|
+
categories: /* @__PURE__ */ new Map()
|
|
288
186
|
};
|
|
187
|
+
projects.set(pid, summary);
|
|
188
|
+
}
|
|
189
|
+
summary.totalUsage++;
|
|
289
190
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (this.config.persistInterval > 0 && typeof window !== 'undefined') {
|
|
295
|
-
this.persistTimer = setInterval(() => {
|
|
296
|
-
this.saveToStorage();
|
|
297
|
-
}, this.config.persistInterval);
|
|
298
|
-
}
|
|
191
|
+
for (const summary of projects.values()) {
|
|
192
|
+
const iconsInProject = this.getIconsByProject(summary.projectId);
|
|
193
|
+
summary.uniqueIcons = iconsInProject.length;
|
|
194
|
+
summary.topIcons = iconsInProject.slice(0, 10);
|
|
299
195
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
196
|
+
return Array.from(projects.values());
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Generate full analytics report.
|
|
200
|
+
*/
|
|
201
|
+
generateReport(periodDays) {
|
|
202
|
+
const now = Date.now();
|
|
203
|
+
const periodStart = periodDays ? now - periodDays * 24 * 60 * 60 * 1e3 : this.events[0]?.timestamp || now;
|
|
204
|
+
const periodEnd = now;
|
|
205
|
+
const periodEvents = periodDays ? this.events.filter((e) => e.timestamp >= periodStart) : this.events;
|
|
206
|
+
const uniqueIcons = new Set(periodEvents.map((e) => e.iconName)).size;
|
|
207
|
+
const mostUsed = this.getMostUsedIcons(20);
|
|
208
|
+
const recentCutoff = now - 7 * 24 * 60 * 60 * 1e3;
|
|
209
|
+
const recentEvents = this.events.filter((e) => e.timestamp >= recentCutoff);
|
|
210
|
+
const olderEvents = this.events.filter(
|
|
211
|
+
(e) => e.timestamp < recentCutoff && e.timestamp >= recentCutoff - 7 * 24 * 60 * 60 * 1e3
|
|
212
|
+
);
|
|
213
|
+
const recentCounts = /* @__PURE__ */ new Map();
|
|
214
|
+
const olderCounts = /* @__PURE__ */ new Map();
|
|
215
|
+
for (const e of recentEvents) {
|
|
216
|
+
recentCounts.set(e.iconName, (recentCounts.get(e.iconName) || 0) + 1);
|
|
305
217
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
try {
|
|
311
|
-
const data = {
|
|
312
|
-
events: this.events.slice(-this.config.maxEventsInMemory),
|
|
313
|
-
searchMisses: Array.from(this.searchMisses.entries()),
|
|
314
|
-
savedAt: Date.now(),
|
|
315
|
-
};
|
|
316
|
-
localStorage.setItem(this.config.storageKey, JSON.stringify(data));
|
|
317
|
-
if (this.config.debug) {
|
|
318
|
-
console.log('[IconAnalytics] Saved to storage');
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
catch (err) {
|
|
322
|
-
console.warn('[IconAnalytics] Failed to save:', err);
|
|
323
|
-
}
|
|
218
|
+
for (const e of olderEvents) {
|
|
219
|
+
olderCounts.set(e.iconName, (olderCounts.get(e.iconName) || 0) + 1);
|
|
324
220
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const data = JSON.parse(stored);
|
|
333
|
-
this.events = data.events || [];
|
|
334
|
-
this.searchMisses = new Map(data.searchMisses || []);
|
|
335
|
-
// Rebuild stats from events
|
|
336
|
-
for (const event of this.events) {
|
|
337
|
-
this.updateStats(event);
|
|
338
|
-
}
|
|
339
|
-
if (this.config.debug) {
|
|
340
|
-
console.log('[IconAnalytics] Loaded from storage:', this.events.length, 'events');
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
catch (err) {
|
|
345
|
-
console.warn('[IconAnalytics] Failed to load:', err);
|
|
346
|
-
}
|
|
221
|
+
const trendingIcons = [];
|
|
222
|
+
for (const [name, recent] of recentCounts) {
|
|
223
|
+
const older = olderCounts.get(name) || 1;
|
|
224
|
+
const growth = (recent - older) / older * 100;
|
|
225
|
+
if (growth > 50) {
|
|
226
|
+
trendingIcons.push({ name, growth });
|
|
227
|
+
}
|
|
347
228
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
229
|
+
trendingIcons.sort((a, b) => b.growth - a.growth);
|
|
230
|
+
const underutilized = this.getLeastUsedIcons(50).filter((i) => i.count === 1).map((i) => i.name);
|
|
231
|
+
const potentiallyMissing = this.getPotentiallyMissingIcons().slice(0, 20).map((p) => p.query);
|
|
232
|
+
const deprecatedInUse = this.getDeprecatedInUse();
|
|
233
|
+
const projectSummaries = this.getProjectSummaries();
|
|
234
|
+
return {
|
|
235
|
+
periodStart,
|
|
236
|
+
periodEnd,
|
|
237
|
+
totalEvents: periodEvents.length,
|
|
238
|
+
uniqueIcons,
|
|
239
|
+
mostUsed,
|
|
240
|
+
trendingIcons: trendingIcons.slice(0, 10),
|
|
241
|
+
underutilized: underutilized.slice(0, 20),
|
|
242
|
+
potentiallyMissing,
|
|
243
|
+
deprecatedInUse,
|
|
244
|
+
projectSummaries
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
248
|
+
Persistence
|
|
249
|
+
──────────────────────────────────────────────────────────────────────────── */
|
|
250
|
+
startPersistTimer() {
|
|
251
|
+
if (this.config.persistInterval > 0 && typeof window !== "undefined") {
|
|
252
|
+
this.persistTimer = setInterval(() => {
|
|
352
253
|
this.saveToStorage();
|
|
254
|
+
}, this.config.persistInterval);
|
|
353
255
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
256
|
+
}
|
|
257
|
+
stopPersistTimer() {
|
|
258
|
+
if (this.persistTimer) {
|
|
259
|
+
clearInterval(this.persistTimer);
|
|
260
|
+
this.persistTimer = null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
saveToStorage() {
|
|
264
|
+
if (typeof localStorage === "undefined") {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
const data = {
|
|
269
|
+
events: this.events.slice(-this.config.maxEventsInMemory),
|
|
270
|
+
searchMisses: Array.from(this.searchMisses.entries()),
|
|
271
|
+
savedAt: Date.now()
|
|
272
|
+
};
|
|
273
|
+
localStorage.setItem(this.config.storageKey, JSON.stringify(data));
|
|
274
|
+
if (this.config.debug) {
|
|
275
|
+
console.log("[IconAnalytics] Saved to storage");
|
|
276
|
+
}
|
|
277
|
+
} catch (err) {
|
|
278
|
+
console.warn("[IconAnalytics] Failed to save:", err);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
loadFromStorage() {
|
|
282
|
+
if (typeof localStorage === "undefined") {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
const stored = localStorage.getItem(this.config.storageKey);
|
|
287
|
+
if (stored) {
|
|
288
|
+
const data = JSON.parse(stored);
|
|
289
|
+
this.events = data.events || [];
|
|
290
|
+
this.searchMisses = new Map(data.searchMisses || []);
|
|
291
|
+
for (const event of this.events) {
|
|
292
|
+
this.updateStats(event);
|
|
363
293
|
}
|
|
294
|
+
if (this.config.debug) {
|
|
295
|
+
console.log("[IconAnalytics] Loaded from storage:", this.events.length, "events");
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} catch (err) {
|
|
299
|
+
console.warn("[IconAnalytics] Failed to load:", err);
|
|
364
300
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Force save to storage.
|
|
304
|
+
*/
|
|
305
|
+
persist() {
|
|
306
|
+
this.saveToStorage();
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Clear all analytics data.
|
|
310
|
+
*/
|
|
311
|
+
clear() {
|
|
312
|
+
this.events = [];
|
|
313
|
+
this.iconStats.clear();
|
|
314
|
+
this.searchMisses.clear();
|
|
315
|
+
if (typeof localStorage !== "undefined") {
|
|
316
|
+
localStorage.removeItem(this.config.storageKey);
|
|
371
317
|
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Destroy the service (cleanup timers).
|
|
321
|
+
*/
|
|
322
|
+
destroy() {
|
|
323
|
+
this.stopPersistTimer();
|
|
324
|
+
this.saveToStorage();
|
|
325
|
+
}
|
|
372
326
|
}
|
|
373
|
-
/* ══════════════════════════════════════════════════════════════════════════════
|
|
374
|
-
Singleton Instance
|
|
375
|
-
══════════════════════════════════════════════════════════════════════════════ */
|
|
376
327
|
let defaultInstance = null;
|
|
377
|
-
/**
|
|
378
|
-
* Get the default analytics service instance.
|
|
379
|
-
*/
|
|
380
328
|
function getIconAnalytics(config) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
329
|
+
if (!defaultInstance) {
|
|
330
|
+
defaultInstance = new IconAnalyticsService(config);
|
|
331
|
+
}
|
|
332
|
+
return defaultInstance;
|
|
385
333
|
}
|
|
386
|
-
/**
|
|
387
|
-
* Track an icon usage (shorthand).
|
|
388
|
-
*/
|
|
389
334
|
function trackIconUsage(iconName, options = {}) {
|
|
390
|
-
|
|
335
|
+
getIconAnalytics().track({ iconName, ...options });
|
|
391
336
|
}
|
|
392
|
-
/**
|
|
393
|
-
* Track a search miss (shorthand).
|
|
394
|
-
*/
|
|
395
337
|
function trackIconSearchMiss(query) {
|
|
396
|
-
|
|
338
|
+
getIconAnalytics().trackSearchMiss(query);
|
|
397
339
|
}
|
|
398
|
-
/**
|
|
399
|
-
* React hook for icon analytics.
|
|
400
|
-
*/
|
|
401
340
|
function useIconAnalytics(projectId) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
341
|
+
const analytics = getIconAnalytics();
|
|
342
|
+
const [mostUsed, setMostUsed] = React.useState([]);
|
|
343
|
+
const [unused, setUnused] = React.useState([]);
|
|
344
|
+
const [report, setReport] = React.useState(null);
|
|
345
|
+
const refresh = React.useCallback(() => {
|
|
346
|
+
setMostUsed(analytics.getMostUsedIcons(20));
|
|
347
|
+
setUnused(analytics.getUnusedIcons().slice(0, 50));
|
|
348
|
+
}, [analytics]);
|
|
349
|
+
const track = React.useCallback(
|
|
350
|
+
(iconName, context) => {
|
|
351
|
+
analytics.track({ iconName, context, projectId });
|
|
352
|
+
refresh();
|
|
353
|
+
},
|
|
354
|
+
[analytics, projectId, refresh]
|
|
355
|
+
);
|
|
356
|
+
const generateReportFn = React.useCallback(
|
|
357
|
+
(periodDays) => {
|
|
358
|
+
setReport(analytics.generateReport(periodDays));
|
|
359
|
+
},
|
|
360
|
+
[analytics]
|
|
361
|
+
);
|
|
362
|
+
React.useEffect(() => {
|
|
363
|
+
refresh();
|
|
364
|
+
}, [refresh]);
|
|
365
|
+
return {
|
|
366
|
+
track,
|
|
367
|
+
mostUsed,
|
|
368
|
+
unused,
|
|
369
|
+
refresh,
|
|
370
|
+
report,
|
|
371
|
+
generateReport: generateReportFn
|
|
372
|
+
};
|
|
428
373
|
}
|
|
429
|
-
|
|
430
374
|
exports.IconAnalyticsService = IconAnalyticsService;
|
|
431
375
|
exports.getIconAnalytics = getIconAnalytics;
|
|
432
376
|
exports.trackIconSearchMiss = trackIconSearchMiss;
|