appclean 1.9.0 → 2.0.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/.github/workflows/npm-publish.yml +61 -0
- package/DEVELOPMENT.md +84 -0
- package/GUI_IMPLEMENTATION_STATUS.md +143 -0
- package/MD_Files/INDEX.md +51 -0
- package/PHASE2_COMPLETION.md +281 -0
- package/PHASE3_COMPLETION.md +364 -0
- package/README.md +411 -91
- package/RELEASE_GUIDE.md +236 -0
- package/assets/logo.svg +34 -0
- package/dist/core/appUpdateChecker.js +12 -16
- package/dist/core/appUpdateChecker.js.map +1 -1
- package/dist/core/detector.js +14 -18
- package/dist/core/detector.js.map +1 -1
- package/dist/core/duplicateFileFinder.js +12 -19
- package/dist/core/duplicateFileFinder.js.map +1 -1
- package/dist/core/orphanedDependencyDetector.js +19 -26
- package/dist/core/orphanedDependencyDetector.js.map +1 -1
- package/dist/core/performanceOptimizer.js +6 -10
- package/dist/core/performanceOptimizer.js.map +1 -1
- package/dist/core/permissionHandler.js +21 -25
- package/dist/core/permissionHandler.js.map +1 -1
- package/dist/core/pluginSystem.js +9 -13
- package/dist/core/pluginSystem.js.map +1 -1
- package/dist/core/removalRecorder.js +12 -19
- package/dist/core/removalRecorder.js.map +1 -1
- package/dist/core/remover.js +59 -66
- package/dist/core/remover.js.map +1 -1
- package/dist/core/reportGenerator.d.ts +1 -1
- package/dist/core/reportGenerator.d.ts.map +1 -1
- package/dist/core/reportGenerator.js +27 -34
- package/dist/core/reportGenerator.js.map +1 -1
- package/dist/core/scheduledCleanup.js +23 -30
- package/dist/core/scheduledCleanup.js.map +1 -1
- package/dist/core/serviceFileDetector.js +24 -31
- package/dist/core/serviceFileDetector.js.map +1 -1
- package/dist/core/verificationModule.js +10 -14
- package/dist/core/verificationModule.js.map +1 -1
- package/dist/index.js +118 -156
- package/dist/index.js.map +1 -1
- package/dist/managers/brewManager.js +30 -37
- package/dist/managers/brewManager.js.map +1 -1
- package/dist/managers/customManager.js +23 -30
- package/dist/managers/customManager.js.map +1 -1
- package/dist/managers/linuxManager.js +29 -36
- package/dist/managers/linuxManager.js.map +1 -1
- package/dist/managers/npmManager.js +27 -34
- package/dist/managers/npmManager.js.map +1 -1
- package/dist/types/index.js +1 -2
- package/dist/ui/client/api/client.d.ts +24 -0
- package/dist/ui/client/api/client.d.ts.map +1 -0
- package/dist/ui/client/api/client.js +100 -0
- package/dist/ui/client/api/client.js.map +1 -0
- package/dist/ui/client/app.d.ts +7 -0
- package/dist/ui/client/app.d.ts.map +1 -0
- package/dist/ui/client/app.js +75 -0
- package/dist/ui/client/app.js.map +1 -0
- package/dist/ui/client/index.html +107 -0
- package/dist/ui/client/pages/appDetails.d.ts +8 -0
- package/dist/ui/client/pages/appDetails.d.ts.map +1 -0
- package/dist/ui/client/pages/appDetails.js +287 -0
- package/dist/ui/client/pages/appDetails.js.map +1 -0
- package/dist/ui/client/pages/appSearch.d.ts +2 -0
- package/dist/ui/client/pages/appSearch.d.ts.map +1 -0
- package/dist/ui/client/pages/appSearch.js +221 -0
- package/dist/ui/client/pages/appSearch.js.map +1 -0
- package/dist/ui/client/pages/dashboard.d.ts +2 -0
- package/dist/ui/client/pages/dashboard.d.ts.map +1 -0
- package/dist/ui/client/pages/dashboard.js +175 -0
- package/dist/ui/client/pages/dashboard.js.map +1 -0
- package/dist/ui/client/pages/settings.d.ts +7 -0
- package/dist/ui/client/pages/settings.d.ts.map +1 -0
- package/dist/ui/client/pages/settings.js +279 -0
- package/dist/ui/client/pages/settings.js.map +1 -0
- package/dist/ui/client/state/appStore.d.ts +38 -0
- package/dist/ui/client/state/appStore.d.ts.map +1 -0
- package/dist/ui/client/state/appStore.js +130 -0
- package/dist/ui/client/state/appStore.js.map +1 -0
- package/dist/ui/client/state/dashboardStore.d.ts +31 -0
- package/dist/ui/client/state/dashboardStore.d.ts.map +1 -0
- package/dist/ui/client/state/dashboardStore.js +76 -0
- package/dist/ui/client/state/dashboardStore.js.map +1 -0
- package/dist/ui/client/state/uiStore.d.ts +43 -0
- package/dist/ui/client/state/uiStore.d.ts.map +1 -0
- package/dist/ui/client/state/uiStore.js +109 -0
- package/dist/ui/client/state/uiStore.js.map +1 -0
- package/dist/ui/client/styles/animations.css +349 -0
- package/dist/ui/client/styles/base.css +214 -0
- package/dist/ui/client/styles/components.css +400 -0
- package/dist/ui/client/styles/layout.css +224 -0
- package/dist/ui/client/styles/variables.css +140 -0
- package/dist/ui/client/utils/events.d.ts +19 -0
- package/dist/ui/client/utils/events.d.ts.map +1 -0
- package/dist/ui/client/utils/events.js +54 -0
- package/dist/ui/client/utils/events.js.map +1 -0
- package/dist/ui/client/utils/formatting.d.ts +11 -0
- package/dist/ui/client/utils/formatting.d.ts.map +1 -0
- package/dist/ui/client/utils/formatting.js +104 -0
- package/dist/ui/client/utils/formatting.js.map +1 -0
- package/dist/ui/client/utils/router.d.ts +25 -0
- package/dist/ui/client/utils/router.d.ts.map +1 -0
- package/dist/ui/client/utils/router.js +90 -0
- package/dist/ui/client/utils/router.js.map +1 -0
- package/dist/ui/guiServer.d.ts +11 -5
- package/dist/ui/guiServer.d.ts.map +1 -1
- package/dist/ui/guiServer.js +180 -501
- package/dist/ui/guiServer.js.map +1 -1
- package/dist/ui/menu.js +18 -27
- package/dist/ui/menu.js.map +1 -1
- package/dist/ui/prompts.js +34 -47
- package/dist/ui/prompts.js.map +1 -1
- package/dist/ui/server/middleware/errorHandler.d.ts +19 -0
- package/dist/ui/server/middleware/errorHandler.d.ts.map +1 -0
- package/dist/ui/server/middleware/errorHandler.js +100 -0
- package/dist/ui/server/middleware/errorHandler.js.map +1 -0
- package/dist/ui/server/routes/apps.d.ts +8 -0
- package/dist/ui/server/routes/apps.d.ts.map +1 -0
- package/dist/ui/server/routes/apps.js +74 -0
- package/dist/ui/server/routes/apps.js.map +1 -0
- package/dist/ui/server/routes/dashboard.d.ts +4 -0
- package/dist/ui/server/routes/dashboard.d.ts.map +1 -0
- package/dist/ui/server/routes/dashboard.js +57 -0
- package/dist/ui/server/routes/dashboard.js.map +1 -0
- package/dist/ui/server/routes/settings.d.ts +6 -0
- package/dist/ui/server/routes/settings.d.ts.map +1 -0
- package/dist/ui/server/routes/settings.js +31 -0
- package/dist/ui/server/routes/settings.js.map +1 -0
- package/dist/ui/server/services/appService.d.ts +45 -0
- package/dist/ui/server/services/appService.d.ts.map +1 -0
- package/dist/ui/server/services/appService.js +114 -0
- package/dist/ui/server/services/appService.js.map +1 -0
- package/dist/ui/server/services/removalService.d.ts +24 -0
- package/dist/ui/server/services/removalService.d.ts.map +1 -0
- package/dist/ui/server/services/removalService.js +83 -0
- package/dist/ui/server/services/removalService.js.map +1 -0
- package/dist/utils/filesystem.js +32 -49
- package/dist/utils/filesystem.js.map +1 -1
- package/dist/utils/logger.js +9 -18
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/platform.js +10 -22
- package/dist/utils/platform.js.map +1 -1
- package/dist/utils/upgrade.d.ts +2 -1
- package/dist/utils/upgrade.d.ts.map +1 -1
- package/dist/utils/upgrade.js +24 -15
- package/dist/utils/upgrade.js.map +1 -1
- package/package.json +4 -2
- package/scripts/publish-npm.sh +64 -0
- package/src/core/appUpdateChecker.ts +1 -1
- package/src/core/detector.ts +6 -6
- package/src/core/duplicateFileFinder.ts +1 -1
- package/src/core/orphanedDependencyDetector.ts +2 -2
- package/src/core/performanceOptimizer.ts +1 -1
- package/src/core/permissionHandler.ts +2 -2
- package/src/core/pluginSystem.ts +1 -1
- package/src/core/removalRecorder.ts +2 -2
- package/src/core/remover.ts +11 -11
- package/src/core/reportGenerator.ts +2 -2
- package/src/core/scheduledCleanup.ts +2 -2
- package/src/core/serviceFileDetector.ts +2 -2
- package/src/core/verificationModule.ts +2 -2
- package/src/index.ts +8 -8
- package/src/managers/brewManager.ts +3 -3
- package/src/managers/customManager.ts +2 -2
- package/src/managers/linuxManager.ts +3 -3
- package/src/managers/npmManager.ts +3 -3
- package/src/ui/client/api/client.ts +168 -0
- package/src/ui/client/app.ts +125 -0
- package/src/ui/client/index.html +107 -0
- package/src/ui/client/pages/appDetails.ts +356 -0
- package/src/ui/client/pages/appSearch.ts +283 -0
- package/src/ui/client/pages/dashboard.ts +211 -0
- package/src/ui/client/pages/settings.ts +342 -0
- package/src/ui/client/state/appStore.ts +181 -0
- package/src/ui/client/state/dashboardStore.ts +123 -0
- package/src/ui/client/state/uiStore.ts +166 -0
- package/src/ui/client/styles/animations.css +349 -0
- package/src/ui/client/styles/base.css +214 -0
- package/src/ui/client/styles/components.css +400 -0
- package/src/ui/client/styles/layout.css +224 -0
- package/src/ui/client/styles/variables.css +140 -0
- package/src/ui/client/utils/events.ts +74 -0
- package/src/ui/client/utils/formatting.ts +157 -0
- package/src/ui/client/utils/router.ts +161 -0
- package/src/ui/guiServer.ts +245 -498
- package/src/ui/prompts.ts +1 -1
- package/src/ui/server/middleware/errorHandler.ts +174 -0
- package/src/ui/server/routes/apps.ts +132 -0
- package/src/ui/server/routes/dashboard.ts +93 -0
- package/src/ui/server/routes/settings.ts +63 -0
- package/src/ui/server/services/appService.ts +184 -0
- package/src/ui/server/services/removalService.ts +138 -0
- package/src/utils/upgrade.ts +19 -2
- package/tsconfig.json +3 -2
- package/INDEX.md +0 -165
- /package/{ACTION_CHECKLIST.md → MD_Files/ACTION_CHECKLIST.md} +0 -0
- /package/{APPCLEAN_SUMMARY.md → MD_Files/APPCLEAN_SUMMARY.md} +0 -0
- /package/{CHANGELOG.md → MD_Files/CHANGELOG.md} +0 -0
- /package/{CODE_OF_CONDUCT.md → MD_Files/CODE_OF_CONDUCT.md} +0 -0
- /package/{CODE_REVIEW_REPORT.md → MD_Files/CODE_REVIEW_REPORT.md} +0 -0
- /package/{COMMUNITY_POSTS.md → MD_Files/COMMUNITY_POSTS.md} +0 -0
- /package/{DEPLOYMENT_GUIDE.md → MD_Files/DEPLOYMENT_GUIDE.md} +0 -0
- /package/{DEPLOYMENT_STATUS.md → MD_Files/DEPLOYMENT_STATUS.md} +0 -0
- /package/{EXECUTIVE_REPORT.md → MD_Files/EXECUTIVE_REPORT.md} +0 -0
- /package/{GITHUB_OPTIMIZATION.md → MD_Files/GITHUB_OPTIMIZATION.md} +0 -0
- /package/{MARKETING_SUMMARY.md → MD_Files/MARKETING_SUMMARY.md} +0 -0
- /package/{NPM_PACKAGE_OPTIMIZATION.md → MD_Files/NPM_PACKAGE_OPTIMIZATION.md} +0 -0
- /package/{NPM_PUBLISH.md → MD_Files/NPM_PUBLISH.md} +0 -0
- /package/{PROJECT_SUMMARY.txt → MD_Files/PROJECT_SUMMARY.txt} +0 -0
- /package/{PUBLICATION_SUCCESS_REPORT.md → MD_Files/PUBLICATION_SUCCESS_REPORT.md} +0 -0
- /package/{QUICKSTART.md → MD_Files/QUICKSTART.md} +0 -0
- /package/{SETUP_GITHUB.md → MD_Files/SETUP_GITHUB.md} +0 -0
- /package/{TESTING_SUMMARY.md → MD_Files/TESTING_SUMMARY.md} +0 -0
- /package/{setup-github.sh → MD_Files/setup-github.sh} +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/* Design System - CSS Variables */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
/* Colors - Primary */
|
|
5
|
+
--color-primary: #3b82f6;
|
|
6
|
+
--color-primary-hover: #2563eb;
|
|
7
|
+
--color-primary-light: #dbeafe;
|
|
8
|
+
--color-primary-dark: #1e40af;
|
|
9
|
+
|
|
10
|
+
/* Colors - Semantic */
|
|
11
|
+
--color-success: #10b981;
|
|
12
|
+
--color-success-light: #d1fae5;
|
|
13
|
+
--color-warning: #f59e0b;
|
|
14
|
+
--color-warning-light: #fef3c7;
|
|
15
|
+
--color-danger: #ef4444;
|
|
16
|
+
--color-danger-light: #fee2e2;
|
|
17
|
+
--color-info: #0ea5e9;
|
|
18
|
+
--color-info-light: #cffafe;
|
|
19
|
+
|
|
20
|
+
/* Colors - Neutral */
|
|
21
|
+
--color-neutral-0: #ffffff;
|
|
22
|
+
--color-neutral-50: #f9fafb;
|
|
23
|
+
--color-neutral-100: #f3f4f6;
|
|
24
|
+
--color-neutral-200: #e5e7eb;
|
|
25
|
+
--color-neutral-300: #d1d5db;
|
|
26
|
+
--color-neutral-400: #9ca3af;
|
|
27
|
+
--color-neutral-500: #6b7280;
|
|
28
|
+
--color-neutral-600: #4b5563;
|
|
29
|
+
--color-neutral-700: #374151;
|
|
30
|
+
--color-neutral-800: #1f2937;
|
|
31
|
+
--color-neutral-900: #111827;
|
|
32
|
+
|
|
33
|
+
/* Background & Surface */
|
|
34
|
+
--bg-primary: var(--color-neutral-0);
|
|
35
|
+
--bg-secondary: var(--color-neutral-50);
|
|
36
|
+
--bg-hover: var(--color-neutral-100);
|
|
37
|
+
--text-primary: var(--color-neutral-900);
|
|
38
|
+
--text-secondary: var(--color-neutral-600);
|
|
39
|
+
--text-muted: var(--color-neutral-500);
|
|
40
|
+
--border-color: var(--color-neutral-200);
|
|
41
|
+
--border-color-hover: var(--color-neutral-300);
|
|
42
|
+
|
|
43
|
+
/* Dark Mode */
|
|
44
|
+
--dark-bg-primary: var(--color-neutral-900);
|
|
45
|
+
--dark-bg-secondary: var(--color-neutral-800);
|
|
46
|
+
--dark-bg-hover: var(--color-neutral-700);
|
|
47
|
+
--dark-text-primary: var(--color-neutral-0);
|
|
48
|
+
--dark-text-secondary: var(--color-neutral-300);
|
|
49
|
+
--dark-text-muted: var(--color-neutral-400);
|
|
50
|
+
--dark-border-color: var(--color-neutral-700);
|
|
51
|
+
--dark-border-color-hover: var(--color-neutral-600);
|
|
52
|
+
|
|
53
|
+
/* Typography */
|
|
54
|
+
--font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
|
|
55
|
+
--font-family-mono: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace;
|
|
56
|
+
|
|
57
|
+
/* Font Sizes */
|
|
58
|
+
--font-xs: 0.75rem; /* 12px */
|
|
59
|
+
--font-sm: 0.875rem; /* 14px */
|
|
60
|
+
--font-base: 1rem; /* 16px */
|
|
61
|
+
--font-lg: 1.125rem; /* 18px */
|
|
62
|
+
--font-xl: 1.25rem; /* 20px */
|
|
63
|
+
--font-2xl: 1.5rem; /* 24px */
|
|
64
|
+
--font-3xl: 1.875rem; /* 30px */
|
|
65
|
+
|
|
66
|
+
/* Font Weights */
|
|
67
|
+
--fw-regular: 400;
|
|
68
|
+
--fw-medium: 500;
|
|
69
|
+
--fw-semibold: 600;
|
|
70
|
+
--fw-bold: 700;
|
|
71
|
+
|
|
72
|
+
/* Line Heights */
|
|
73
|
+
--lh-tight: 1.25;
|
|
74
|
+
--lh-normal: 1.5;
|
|
75
|
+
--lh-relaxed: 1.625;
|
|
76
|
+
--lh-loose: 2;
|
|
77
|
+
|
|
78
|
+
/* Spacing Scale (4px base unit) */
|
|
79
|
+
--space-0: 0;
|
|
80
|
+
--space-1: 0.25rem; /* 4px */
|
|
81
|
+
--space-2: 0.5rem; /* 8px */
|
|
82
|
+
--space-3: 0.75rem; /* 12px */
|
|
83
|
+
--space-4: 1rem; /* 16px */
|
|
84
|
+
--space-6: 1.5rem; /* 24px */
|
|
85
|
+
--space-8: 2rem; /* 32px */
|
|
86
|
+
--space-12: 3rem; /* 48px */
|
|
87
|
+
--space-16: 4rem; /* 64px */
|
|
88
|
+
|
|
89
|
+
/* Border Radius */
|
|
90
|
+
--radius-none: 0;
|
|
91
|
+
--radius-sm: 0.375rem; /* 6px */
|
|
92
|
+
--radius-md: 0.5rem; /* 8px */
|
|
93
|
+
--radius-lg: 0.75rem; /* 12px */
|
|
94
|
+
--radius-xl: 1rem; /* 16px */
|
|
95
|
+
--radius-2xl: 1.5rem; /* 24px */
|
|
96
|
+
--radius-full: 9999px; /* Pill-shaped */
|
|
97
|
+
|
|
98
|
+
/* Shadows */
|
|
99
|
+
--shadow-none: 0 0 0 0 rgba(0, 0, 0, 0);
|
|
100
|
+
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
101
|
+
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
102
|
+
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
103
|
+
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
104
|
+
|
|
105
|
+
/* Transitions */
|
|
106
|
+
--transition-fast: 150ms ease-out;
|
|
107
|
+
--transition-normal: 250ms ease-out;
|
|
108
|
+
--transition-slow: 350ms ease-out;
|
|
109
|
+
|
|
110
|
+
/* Layout */
|
|
111
|
+
--max-width-sm: 640px;
|
|
112
|
+
--max-width-md: 768px;
|
|
113
|
+
--max-width-lg: 1024px;
|
|
114
|
+
--max-width-xl: 1280px;
|
|
115
|
+
--max-width-2xl: 1536px;
|
|
116
|
+
|
|
117
|
+
/* Z-index */
|
|
118
|
+
--z-hide: -1;
|
|
119
|
+
--z-auto: auto;
|
|
120
|
+
--z-base: 0;
|
|
121
|
+
--z-dropdown: 1000;
|
|
122
|
+
--z-sticky: 1020;
|
|
123
|
+
--z-fixed: 1030;
|
|
124
|
+
--z-modal-backdrop: 1040;
|
|
125
|
+
--z-modal: 1050;
|
|
126
|
+
--z-popover: 1060;
|
|
127
|
+
--z-tooltip: 1070;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* Dark mode */
|
|
131
|
+
body.dark-mode {
|
|
132
|
+
--bg-primary: var(--dark-bg-primary);
|
|
133
|
+
--bg-secondary: var(--dark-bg-secondary);
|
|
134
|
+
--bg-hover: var(--dark-bg-hover);
|
|
135
|
+
--text-primary: var(--dark-text-primary);
|
|
136
|
+
--text-secondary: var(--dark-text-secondary);
|
|
137
|
+
--text-muted: var(--dark-text-muted);
|
|
138
|
+
--border-color: var(--dark-border-color);
|
|
139
|
+
--border-color-hover: var(--dark-border-color-hover);
|
|
140
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple Event Emitter for Reactive State Management
|
|
3
|
+
* Provides publish/subscribe pattern for state changes
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type EventListener<T = any> = (data: T) => void;
|
|
7
|
+
|
|
8
|
+
export class EventEmitter<T = any> {
|
|
9
|
+
private listeners: EventListener<T>[] = [];
|
|
10
|
+
|
|
11
|
+
on(listener: EventListener<T>): () => void {
|
|
12
|
+
this.listeners.push(listener);
|
|
13
|
+
|
|
14
|
+
// Return unsubscribe function
|
|
15
|
+
return () => {
|
|
16
|
+
this.listeners = this.listeners.filter((l) => l !== listener);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
once(listener: EventListener<T>): void {
|
|
21
|
+
const unsubscribe = this.on((data: T) => {
|
|
22
|
+
listener(data);
|
|
23
|
+
unsubscribe();
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
emit(data: T): void {
|
|
28
|
+
this.listeners.forEach((listener) => {
|
|
29
|
+
try {
|
|
30
|
+
listener(data);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Event listener error:', error);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
clear(): void {
|
|
38
|
+
this.listeners = [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getListenerCount(): number {
|
|
42
|
+
return this.listeners.length;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Base Store class with reactive updates
|
|
48
|
+
*/
|
|
49
|
+
export abstract class Store<T> {
|
|
50
|
+
protected state: T;
|
|
51
|
+
protected stateChanged = new EventEmitter<T>();
|
|
52
|
+
|
|
53
|
+
constructor(initialState: T) {
|
|
54
|
+
this.state = initialState;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getState(): T {
|
|
58
|
+
return this.state;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
setState(newState: Partial<T>): void {
|
|
62
|
+
this.state = { ...this.state, ...newState };
|
|
63
|
+
this.stateChanged.emit(this.state);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
subscribe(listener: EventListener<T>): () => void {
|
|
67
|
+
return this.stateChanged.on(listener);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
protected updateState(updater: (state: T) => T): void {
|
|
71
|
+
this.state = updater(this.state);
|
|
72
|
+
this.stateChanged.emit(this.state);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formatting utilities for displaying data
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Format bytes to human-readable format
|
|
7
|
+
*/
|
|
8
|
+
export function formatBytes(bytes: number, decimals = 2): string {
|
|
9
|
+
if (bytes === 0) return '0 Bytes';
|
|
10
|
+
|
|
11
|
+
const k = 1024;
|
|
12
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
13
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
14
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
15
|
+
|
|
16
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Format date to readable string
|
|
21
|
+
*/
|
|
22
|
+
export function formatDate(timestamp: number | string | Date): string {
|
|
23
|
+
const date = new Date(timestamp);
|
|
24
|
+
const today = new Date();
|
|
25
|
+
const yesterday = new Date(today);
|
|
26
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
27
|
+
|
|
28
|
+
const isToday = date.toDateString() === today.toDateString();
|
|
29
|
+
const isYesterday = date.toDateString() === yesterday.toDateString();
|
|
30
|
+
|
|
31
|
+
if (isToday) {
|
|
32
|
+
return 'Today at ' + date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (isYesterday) {
|
|
36
|
+
return 'Yesterday at ' + date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return date.toLocaleDateString('en-US', {
|
|
40
|
+
month: 'short',
|
|
41
|
+
day: 'numeric',
|
|
42
|
+
year: date.getFullYear() !== today.getFullYear() ? 'numeric' : undefined,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Format relative time (e.g., "2 hours ago")
|
|
48
|
+
*/
|
|
49
|
+
export function formatRelativeTime(timestamp: number | string | Date): string {
|
|
50
|
+
const date = new Date(timestamp);
|
|
51
|
+
const now = new Date();
|
|
52
|
+
const diffMs = now.getTime() - date.getTime();
|
|
53
|
+
const diffSecs = Math.floor(diffMs / 1000);
|
|
54
|
+
const diffMins = Math.floor(diffSecs / 60);
|
|
55
|
+
const diffHours = Math.floor(diffMins / 60);
|
|
56
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
57
|
+
|
|
58
|
+
if (diffSecs < 60) return 'just now';
|
|
59
|
+
if (diffMins < 60) return `${diffMins} minute${diffMins > 1 ? 's' : ''} ago`;
|
|
60
|
+
if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
|
|
61
|
+
if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
|
|
62
|
+
|
|
63
|
+
return formatDate(timestamp);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Truncate string to max length with ellipsis
|
|
68
|
+
*/
|
|
69
|
+
export function truncate(str: string, maxLength: number): string {
|
|
70
|
+
if (str.length <= maxLength) return str;
|
|
71
|
+
return str.slice(0, maxLength - 3) + '...';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Capitalize first letter
|
|
76
|
+
*/
|
|
77
|
+
export function capitalize(str: string): string {
|
|
78
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Format install method name for display
|
|
83
|
+
*/
|
|
84
|
+
export function formatInstallMethod(method: string): string {
|
|
85
|
+
const methodMap: Record<string, string> = {
|
|
86
|
+
npm: 'npm',
|
|
87
|
+
yarn: 'Yarn',
|
|
88
|
+
pnpm: 'pnpm',
|
|
89
|
+
brew: 'Homebrew',
|
|
90
|
+
apt: 'apt',
|
|
91
|
+
yum: 'yum',
|
|
92
|
+
dnf: 'dnf',
|
|
93
|
+
custom: 'Custom',
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return methodMap[method] || capitalize(method);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get color for install method badge
|
|
101
|
+
*/
|
|
102
|
+
export function getMethodBadgeColor(method: string): string {
|
|
103
|
+
const colorMap: Record<string, string> = {
|
|
104
|
+
npm: 'badge-primary',
|
|
105
|
+
yarn: 'badge-primary',
|
|
106
|
+
pnpm: 'badge-primary',
|
|
107
|
+
brew: 'badge-success',
|
|
108
|
+
apt: 'badge-warning',
|
|
109
|
+
yum: 'badge-warning',
|
|
110
|
+
dnf: 'badge-warning',
|
|
111
|
+
custom: 'badge-neutral',
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return colorMap[method] || 'badge-neutral';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Format percentage for display
|
|
119
|
+
*/
|
|
120
|
+
export function formatPercent(value: number, decimals = 1): string {
|
|
121
|
+
return parseFloat(value.toFixed(decimals)) + '%';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Debounce function for search/filter
|
|
126
|
+
*/
|
|
127
|
+
export function debounce<T extends (...args: any[]) => any>(
|
|
128
|
+
func: T,
|
|
129
|
+
wait: number
|
|
130
|
+
): (...args: Parameters<T>) => void {
|
|
131
|
+
let timeout: NodeJS.Timeout | null = null;
|
|
132
|
+
|
|
133
|
+
return function (...args: Parameters<T>) {
|
|
134
|
+
if (timeout) clearTimeout(timeout);
|
|
135
|
+
timeout = setTimeout(() => {
|
|
136
|
+
func(...args);
|
|
137
|
+
}, wait);
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Throttle function
|
|
143
|
+
*/
|
|
144
|
+
export function throttle<T extends (...args: any[]) => any>(
|
|
145
|
+
func: T,
|
|
146
|
+
limit: number
|
|
147
|
+
): (...args: Parameters<T>) => void {
|
|
148
|
+
let lastRun = 0;
|
|
149
|
+
|
|
150
|
+
return function (...args: Parameters<T>) {
|
|
151
|
+
const now = Date.now();
|
|
152
|
+
if (now - lastRun >= limit) {
|
|
153
|
+
lastRun = now;
|
|
154
|
+
func(...args);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple Hash-Based Router for SPA
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type RouteHandler = (params: Record<string, string>) => void;
|
|
6
|
+
|
|
7
|
+
export interface Route {
|
|
8
|
+
path: string;
|
|
9
|
+
handler: RouteHandler;
|
|
10
|
+
title?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class Router {
|
|
14
|
+
private routes: Route[] = [];
|
|
15
|
+
private currentRoute: Route | null = null;
|
|
16
|
+
private onRouteChange: ((route: Route) => void) | null = null;
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
this.listenToHashChanges();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Register a route
|
|
24
|
+
*/
|
|
25
|
+
register(path: string, handler: RouteHandler, title?: string): void {
|
|
26
|
+
this.routes.push({ path, handler, title });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Navigate to a route
|
|
31
|
+
*/
|
|
32
|
+
navigate(path: string): void {
|
|
33
|
+
window.location.hash = `#/${path}`.replace('##', '#');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get current route path
|
|
38
|
+
*/
|
|
39
|
+
getCurrentPath(): string {
|
|
40
|
+
return window.location.hash.slice(2) || '';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Listen to hash changes and trigger handlers
|
|
45
|
+
*/
|
|
46
|
+
private listenToHashChanges(): void {
|
|
47
|
+
window.addEventListener('hashchange', () => {
|
|
48
|
+
this.route();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Initial route on load
|
|
52
|
+
this.route();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Route the current hash to a handler
|
|
57
|
+
*/
|
|
58
|
+
private route(): void {
|
|
59
|
+
const path = this.getCurrentPath();
|
|
60
|
+
const route = this.matchRoute(path);
|
|
61
|
+
|
|
62
|
+
if (route) {
|
|
63
|
+
const params = this.extractParams(route.path, path);
|
|
64
|
+
this.currentRoute = route;
|
|
65
|
+
|
|
66
|
+
if (route.title) {
|
|
67
|
+
document.title = route.title;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
route.handler(params);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error('Route handler error:', error);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (this.onRouteChange) {
|
|
77
|
+
this.onRouteChange(route);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
console.warn(`No route found for: ${path}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Match a path to a registered route
|
|
86
|
+
*/
|
|
87
|
+
private matchRoute(path: string): Route | null {
|
|
88
|
+
// Exact match first
|
|
89
|
+
const exactMatch = this.routes.find((r) => r.path === path || r.path === `/${path}`);
|
|
90
|
+
if (exactMatch) return exactMatch;
|
|
91
|
+
|
|
92
|
+
// Dynamic route match
|
|
93
|
+
for (const route of this.routes) {
|
|
94
|
+
if (this.pathMatches(route.path, path)) {
|
|
95
|
+
return route;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if a route path matches a given path
|
|
104
|
+
* Supports dynamic segments like /apps/:appName
|
|
105
|
+
*/
|
|
106
|
+
private pathMatches(routePath: string, actualPath: string): boolean {
|
|
107
|
+
const routeParts = routePath.split('/').filter(Boolean);
|
|
108
|
+
const actualParts = actualPath.split('/').filter(Boolean);
|
|
109
|
+
|
|
110
|
+
if (routeParts.length !== actualParts.length) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return routeParts.every((part, i) => {
|
|
115
|
+
return part.startsWith(':') || part === actualParts[i];
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Extract params from a path
|
|
121
|
+
* Example: /apps/:appName with path /apps/myapp returns { appName: 'myapp' }
|
|
122
|
+
*/
|
|
123
|
+
private extractParams(routePath: string, actualPath: string): Record<string, string> {
|
|
124
|
+
const params: Record<string, string> = {};
|
|
125
|
+
const routeParts = routePath.split('/').filter(Boolean);
|
|
126
|
+
const actualParts = actualPath.split('/').filter(Boolean);
|
|
127
|
+
|
|
128
|
+
routeParts.forEach((part, i) => {
|
|
129
|
+
if (part.startsWith(':')) {
|
|
130
|
+
const paramName = part.slice(1);
|
|
131
|
+
params[paramName] = decodeURIComponent(actualParts[i]);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return params;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Register a callback for route changes
|
|
140
|
+
*/
|
|
141
|
+
onchange(callback: (route: Route) => void): void {
|
|
142
|
+
this.onRouteChange = callback;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get all registered routes
|
|
147
|
+
*/
|
|
148
|
+
getRoutes(): Route[] {
|
|
149
|
+
return this.routes;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get current route
|
|
154
|
+
*/
|
|
155
|
+
getCurrentRoute(): Route | null {
|
|
156
|
+
return this.currentRoute;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Export singleton instance
|
|
161
|
+
export const router = new Router();
|