Optimized package analysis
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Button } from "./components/ui/button";
|
||||
import { Label } from "./components/ui/label";
|
||||
import { Input } from "./components/ui/input";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useState, useEffect, useRef, useMemo, useCallback, memo } from "react";
|
||||
import { Edit2, ChevronDown, ChevronUp, AlertCircle, Search, Package, Upload, CheckCircle2, XCircle, AlertTriangle, FileJson, TrendingUp, TrendingDown, Copy, Check, Terminal, X, Save, FolderOpen } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { PackageSelector } from "./PackageSelector";
|
||||
@@ -98,7 +98,7 @@ function satisfiesVersion(installedVersion: string, requiredRange: string): bool
|
||||
}
|
||||
}
|
||||
|
||||
function PackageResultCard({ result, onVersionChange, nodeVersion }: {
|
||||
const PackageResultCard = memo(function PackageResultCard({ result, onVersionChange, nodeVersion }: {
|
||||
result: AnalysisResult;
|
||||
onVersionChange: (packageName: string, version: string) => void;
|
||||
nodeVersion: string;
|
||||
@@ -111,11 +111,24 @@ function PackageResultCard({ result, onVersionChange, nodeVersion }: {
|
||||
const hasVersionChange = result.selectedVersion && result.selectedVersion !== result.currentVersion;
|
||||
const hasCompatibilityIssue = !result.nodeCompatible || (result.peerConflicts && result.peerConflicts.length > 0);
|
||||
|
||||
const sortedVersions = result.availableVersions ? sortVersions([...result.availableVersions]) : [];
|
||||
const filteredAvailableVersions = sortedVersions.filter(v =>
|
||||
v.toLowerCase().includes(versionSearchQuery.toLowerCase())
|
||||
const sortedVersions = useMemo(() =>
|
||||
result.availableVersions ? sortVersions([...result.availableVersions]) : [],
|
||||
[result.availableVersions]
|
||||
);
|
||||
|
||||
const filteredAvailableVersions = useMemo(() =>
|
||||
sortedVersions.filter(v =>
|
||||
v.toLowerCase().includes(versionSearchQuery.toLowerCase())
|
||||
),
|
||||
[sortedVersions, versionSearchQuery]
|
||||
);
|
||||
|
||||
const handleVersionChange = useCallback((version: string) => {
|
||||
onVersionChange(result.name, version);
|
||||
setVersionDropdownOpen(false);
|
||||
setVersionSearchQuery("");
|
||||
}, [result.name, onVersionChange]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!versionDropdownOpen) return;
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
@@ -233,11 +246,7 @@ function PackageResultCard({ result, onVersionChange, nodeVersion }: {
|
||||
filteredAvailableVersions.map((version) => (
|
||||
<button
|
||||
key={version}
|
||||
onClick={() => {
|
||||
onVersionChange(result.name, version);
|
||||
setVersionDropdownOpen(false);
|
||||
setVersionSearchQuery('');
|
||||
}}
|
||||
onClick={() => handleVersionChange(version)}
|
||||
className={`w-full text-left px-3 py-2 rounded-md transition-colors ${
|
||||
version === activeVersion
|
||||
? 'bg-accent font-medium border border-border'
|
||||
@@ -419,7 +428,7 @@ function PackageResultCard({ result, onVersionChange, nodeVersion }: {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export function PackageDetails() {
|
||||
const [packages, setPackages] = useState<PackageData[]>([]);
|
||||
@@ -480,7 +489,7 @@ export function PackageDetails() {
|
||||
});
|
||||
};
|
||||
|
||||
const handleVersionChange = (packageName: string, version: string) => {
|
||||
const handleVersionChange = useCallback((packageName: string, version: string) => {
|
||||
const newSelections = new Map(versionSelections);
|
||||
newSelections.set(packageName, version);
|
||||
setVersionSelections(newSelections);
|
||||
@@ -505,7 +514,7 @@ export function PackageDetails() {
|
||||
} else {
|
||||
toast.info(`${packageName} → ${version}`);
|
||||
}
|
||||
};
|
||||
}, [versionSelections, analysisResults, nodeVersion]);
|
||||
|
||||
useEffect(() => {
|
||||
if (analysisResults.length > 0) {
|
||||
@@ -995,44 +1004,69 @@ export function PackageDetails() {
|
||||
}
|
||||
}
|
||||
|
||||
const selectedPkgs = packages.filter(p => selectedPackages.has(p.name));
|
||||
const filteredVersions = nodeVersions.filter(v => {
|
||||
const selectedPkgs = useMemo(() =>
|
||||
packages.filter(p => selectedPackages.has(p.name)),
|
||||
[packages, selectedPackages]
|
||||
);
|
||||
|
||||
const filteredVersions = useMemo(() =>
|
||||
nodeVersions.filter(v => {
|
||||
const searchLower = nodeVersionSearch.toLowerCase();
|
||||
const matchesVersion = v.version.toLowerCase().includes(searchLower);
|
||||
const matchesLtsName = typeof v.lts === 'string' && v.lts.toLowerCase().includes(searchLower);
|
||||
const matchesLtsKeyword = v.lts && searchLower.includes('lts');
|
||||
return matchesVersion || matchesLtsName || matchesLtsKeyword;
|
||||
});
|
||||
}),
|
||||
[nodeVersions, nodeVersionSearch]
|
||||
);
|
||||
|
||||
const selectedVersionInfo = nodeVersions.find(v => v.version.replace('v', '') === nodeVersion);
|
||||
const selectedVersionInfo = useMemo(() =>
|
||||
nodeVersions.find(v => v.version.replace('v', '') === nodeVersion),
|
||||
[nodeVersions, nodeVersion]
|
||||
);
|
||||
|
||||
// Count issues
|
||||
const compatibilityIssues = analysisResults.filter(r => !r.nodeCompatible || (r.peerConflicts && r.peerConflicts.length > 0)).length;
|
||||
const modifiedPackages = analysisResults.filter(r => r.selectedVersion && r.selectedVersion !== r.currentVersion).length;
|
||||
const compatibilityIssues = useMemo(() =>
|
||||
analysisResults.filter(r => !r.nodeCompatible || (r.peerConflicts && r.peerConflicts.length > 0)).length,
|
||||
[analysisResults]
|
||||
);
|
||||
|
||||
const modifiedPackages = useMemo(() =>
|
||||
analysisResults.filter(r => r.selectedVersion && r.selectedVersion !== r.currentVersion).length,
|
||||
[analysisResults]
|
||||
);
|
||||
|
||||
// Get packages with compatibility issues among modified packages
|
||||
const modifiedWithIssues = analysisResults.filter(r =>
|
||||
const modifiedWithIssues = useMemo(() =>
|
||||
analysisResults.filter(r =>
|
||||
r.selectedVersion &&
|
||||
r.selectedVersion !== r.currentVersion &&
|
||||
(!r.nodeCompatible || (r.peerConflicts && r.peerConflicts.length > 0))
|
||||
),
|
||||
[analysisResults]
|
||||
);
|
||||
|
||||
// Get unmodified packages with issues
|
||||
const unmodifiedWithIssues = analysisResults.filter(r =>
|
||||
const unmodifiedWithIssues = useMemo(() =>
|
||||
analysisResults.filter(r =>
|
||||
(!r.selectedVersion || r.selectedVersion === r.currentVersion) &&
|
||||
(!r.nodeCompatible || (r.peerConflicts && r.peerConflicts.length > 0))
|
||||
),
|
||||
[analysisResults]
|
||||
);
|
||||
|
||||
// Generate npm install command
|
||||
const generateUpgradeCommand = () => {
|
||||
const upgradeCommand = useMemo(() => {
|
||||
const modifiedPkgs = analysisResults.filter(r => r.selectedVersion && r.selectedVersion !== r.currentVersion);
|
||||
if (modifiedPkgs.length === 0) return '';
|
||||
|
||||
const packages = modifiedPkgs.map(pkg => `${pkg.name}@${pkg.selectedVersion}`).join(' ');
|
||||
return `npm install ${packages}`;
|
||||
};
|
||||
}, [analysisResults]);
|
||||
|
||||
const copyToClipboard = async (text: string) => {
|
||||
const generateUpgradeCommand = () => upgradeCommand;
|
||||
|
||||
const copyToClipboard = useCallback(async (text: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
setCopiedCommand(true);
|
||||
@@ -1041,7 +1075,7 @@ export function PackageDetails() {
|
||||
} catch (err) {
|
||||
toast.error('Failed to copy to clipboard');
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="bg-background">
|
||||
|
||||
Reference in New Issue
Block a user