Improve system project UX and make dependencies a modal
- Hide tag count stat for system projects (show "versions" instead of "artifacts") - Hide "Latest" tag stat for system projects - Change "Create/Update Tag" to only show for non-system projects - Add "View Artifact ID" menu option with modal showing the SHA256 hash - Move dependencies section to a modal (opened via "View Dependencies" menu) - Add deps-modal and artifact-id-modal CSS styles
This commit is contained in:
@@ -930,3 +930,57 @@ tr:hover .copy-btn {
|
|||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
border-top: 1px solid var(--border-primary);
|
border-top: 1px solid var(--border-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dependencies Modal */
|
||||||
|
.deps-modal {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
width: 90%;
|
||||||
|
max-width: 600px;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deps-modal .modal-body {
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deps-modal-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Artifact ID Modal */
|
||||||
|
.artifact-id-modal {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
width: 90%;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.artifact-id-display {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.artifact-id-display code {
|
||||||
|
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
word-break: break-all;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.artifact-id-display .copy-btn {
|
||||||
|
opacity: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -91,6 +91,13 @@ function PackagePage() {
|
|||||||
// Dependency graph modal state
|
// Dependency graph modal state
|
||||||
const [showGraph, setShowGraph] = useState(false);
|
const [showGraph, setShowGraph] = useState(false);
|
||||||
|
|
||||||
|
// Dependencies modal state
|
||||||
|
const [showDepsModal, setShowDepsModal] = useState(false);
|
||||||
|
|
||||||
|
// Artifact ID modal state
|
||||||
|
const [showArtifactIdModal, setShowArtifactIdModal] = useState(false);
|
||||||
|
const [viewArtifactId, setViewArtifactId] = useState<string | null>(null);
|
||||||
|
|
||||||
// Ensure file modal state
|
// Ensure file modal state
|
||||||
const [showEnsureFile, setShowEnsureFile] = useState(false);
|
const [showEnsureFile, setShowEnsureFile] = useState(false);
|
||||||
const [ensureFileContent, setEnsureFileContent] = useState<string | null>(null);
|
const [ensureFileContent, setEnsureFileContent] = useState<string | null>(null);
|
||||||
@@ -516,18 +523,21 @@ function PackagePage() {
|
|||||||
style={{ top: menuPosition.top, left: menuPosition.left }}
|
style={{ top: menuPosition.top, left: menuPosition.left }}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
|
<button onClick={() => { setViewArtifactId(t.artifact_id); setShowArtifactIdModal(true); setOpenMenuId(null); setMenuPosition(null); }}>
|
||||||
|
View Artifact ID
|
||||||
|
</button>
|
||||||
<button onClick={() => { navigator.clipboard.writeText(t.artifact_id); setOpenMenuId(null); setMenuPosition(null); }}>
|
<button onClick={() => { navigator.clipboard.writeText(t.artifact_id); setOpenMenuId(null); setMenuPosition(null); }}>
|
||||||
Copy Artifact ID
|
Copy Artifact ID
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => { fetchEnsureFileForTag(t.name); setOpenMenuId(null); setMenuPosition(null); }}>
|
<button onClick={() => { fetchEnsureFileForTag(t.name); setOpenMenuId(null); setMenuPosition(null); }}>
|
||||||
View Ensure File
|
View Ensure File
|
||||||
</button>
|
</button>
|
||||||
{canWrite && (
|
{canWrite && !isSystemProject && (
|
||||||
<button onClick={() => { setCreateTagArtifactId(t.artifact_id); setShowCreateTagModal(true); setOpenMenuId(null); setMenuPosition(null); }}>
|
<button onClick={() => { setCreateTagArtifactId(t.artifact_id); setShowCreateTagModal(true); setOpenMenuId(null); setMenuPosition(null); }}>
|
||||||
Create/Update Tag
|
Create/Update Tag
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button onClick={() => { handleTagSelect(t); setOpenMenuId(null); setMenuPosition(null); }}>
|
<button onClick={() => { handleTagSelect(t); setShowDepsModal(true); setOpenMenuId(null); setMenuPosition(null); }}>
|
||||||
View Dependencies
|
View Dependencies
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -606,14 +616,14 @@ function PackagePage() {
|
|||||||
</div>
|
</div>
|
||||||
{pkg && (pkg.tag_count !== undefined || pkg.artifact_count !== undefined) && (
|
{pkg && (pkg.tag_count !== undefined || pkg.artifact_count !== undefined) && (
|
||||||
<div className="package-header-stats">
|
<div className="package-header-stats">
|
||||||
{pkg.tag_count !== undefined && (
|
{!isSystemProject && pkg.tag_count !== undefined && (
|
||||||
<span className="stat-item">
|
<span className="stat-item">
|
||||||
<strong>{pkg.tag_count}</strong> tags
|
<strong>{pkg.tag_count}</strong> tags
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{pkg.artifact_count !== undefined && (
|
{pkg.artifact_count !== undefined && (
|
||||||
<span className="stat-item">
|
<span className="stat-item">
|
||||||
<strong>{pkg.artifact_count}</strong> artifacts
|
<strong>{pkg.artifact_count}</strong> {isSystemProject ? 'versions' : 'artifacts'}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{pkg.total_size !== undefined && pkg.total_size > 0 && (
|
{pkg.total_size !== undefined && pkg.total_size > 0 && (
|
||||||
@@ -621,7 +631,7 @@ function PackagePage() {
|
|||||||
<strong>{formatBytes(pkg.total_size)}</strong> total
|
<strong>{formatBytes(pkg.total_size)}</strong> total
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{pkg.latest_tag && (
|
{!isSystemProject && pkg.latest_tag && (
|
||||||
<span className="stat-item">
|
<span className="stat-item">
|
||||||
Latest: <strong className="accent">{pkg.latest_tag}</strong>
|
Latest: <strong className="accent">{pkg.latest_tag}</strong>
|
||||||
</span>
|
</span>
|
||||||
@@ -680,110 +690,6 @@ function PackagePage() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Dependencies Section */}
|
|
||||||
{tags.length > 0 && (
|
|
||||||
<div className="dependencies-section card">
|
|
||||||
<div className="dependencies-header">
|
|
||||||
<h3>Dependencies</h3>
|
|
||||||
<div className="dependencies-controls">
|
|
||||||
{selectedTag && (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
className="btn btn-secondary btn-small"
|
|
||||||
onClick={fetchEnsureFile}
|
|
||||||
disabled={ensureFileLoading}
|
|
||||||
title="View orchard.ensure file"
|
|
||||||
>
|
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ marginRight: '6px' }}>
|
|
||||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
|
||||||
<polyline points="14 2 14 8 20 8"></polyline>
|
|
||||||
<line x1="16" y1="13" x2="8" y2="13"></line>
|
|
||||||
<line x1="16" y1="17" x2="8" y2="17"></line>
|
|
||||||
<polyline points="10 9 9 9 8 9"></polyline>
|
|
||||||
</svg>
|
|
||||||
{ensureFileLoading ? 'Loading...' : 'View Ensure File'}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="btn btn-secondary btn-small"
|
|
||||||
onClick={() => setShowGraph(true)}
|
|
||||||
title="View full dependency tree"
|
|
||||||
>
|
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ marginRight: '6px' }}>
|
|
||||||
<circle cx="12" cy="12" r="3"></circle>
|
|
||||||
<circle cx="4" cy="4" r="2"></circle>
|
|
||||||
<circle cx="20" cy="4" r="2"></circle>
|
|
||||||
<circle cx="4" cy="20" r="2"></circle>
|
|
||||||
<circle cx="20" cy="20" r="2"></circle>
|
|
||||||
<line x1="9.5" y1="9.5" x2="5.5" y2="5.5"></line>
|
|
||||||
<line x1="14.5" y1="9.5" x2="18.5" y2="5.5"></line>
|
|
||||||
<line x1="9.5" y1="14.5" x2="5.5" y2="18.5"></line>
|
|
||||||
<line x1="14.5" y1="14.5" x2="18.5" y2="18.5"></line>
|
|
||||||
</svg>
|
|
||||||
View Graph
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="dependencies-tag-select">
|
|
||||||
{selectedTag && (
|
|
||||||
<select
|
|
||||||
className="tag-selector"
|
|
||||||
value={selectedTag.id}
|
|
||||||
onChange={(e) => {
|
|
||||||
const tag = tags.find(t => t.id === e.target.value);
|
|
||||||
if (tag) setSelectedTag(tag);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{tags.map(t => (
|
|
||||||
<option key={t.id} value={t.id}>
|
|
||||||
{t.name}{t.version ? ` (${t.version})` : ''}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{depsLoading ? (
|
|
||||||
<div className="deps-loading">Loading dependencies...</div>
|
|
||||||
) : depsError ? (
|
|
||||||
<div className="deps-error">{depsError}</div>
|
|
||||||
) : dependencies.length === 0 ? (
|
|
||||||
<div className="deps-empty">
|
|
||||||
{selectedTag ? (
|
|
||||||
<span><strong>{selectedTag.name}</strong> has no dependencies</span>
|
|
||||||
) : (
|
|
||||||
<span>No dependencies</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="deps-list">
|
|
||||||
<div className="deps-summary">
|
|
||||||
<strong>{selectedTag?.name}</strong> has {dependencies.length} {dependencies.length === 1 ? 'dependency' : 'dependencies'}:
|
|
||||||
</div>
|
|
||||||
<ul className="deps-items">
|
|
||||||
{dependencies.map((dep) => (
|
|
||||||
<li key={dep.id} className="dep-item">
|
|
||||||
<Link
|
|
||||||
to={`/project/${dep.project}/${dep.package}`}
|
|
||||||
className="dep-link"
|
|
||||||
>
|
|
||||||
{dep.project}/{dep.package}
|
|
||||||
</Link>
|
|
||||||
<span className="dep-constraint">
|
|
||||||
@ {dep.version || dep.tag}
|
|
||||||
</span>
|
|
||||||
<span className="dep-status dep-status--ok" title="Package exists">
|
|
||||||
✓
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Used By (Reverse Dependencies) Section */}
|
{/* Used By (Reverse Dependencies) Section */}
|
||||||
<div className="used-by-section card">
|
<div className="used-by-section card">
|
||||||
<h3>Used By</h3>
|
<h3>Used By</h3>
|
||||||
@@ -1016,6 +922,104 @@ function PackagePage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Dependencies Modal */}
|
||||||
|
{showDepsModal && selectedTag && (
|
||||||
|
<div className="modal-overlay" onClick={() => setShowDepsModal(false)}>
|
||||||
|
<div className="deps-modal" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div className="modal-header">
|
||||||
|
<h3>Dependencies for {selectedTag.version || selectedTag.name}</h3>
|
||||||
|
<button
|
||||||
|
className="modal-close"
|
||||||
|
onClick={() => setShowDepsModal(false)}
|
||||||
|
title="Close"
|
||||||
|
>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||||
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||||
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<div className="deps-modal-controls">
|
||||||
|
<button
|
||||||
|
className="btn btn-secondary btn-small"
|
||||||
|
onClick={fetchEnsureFile}
|
||||||
|
disabled={ensureFileLoading}
|
||||||
|
>
|
||||||
|
View Ensure File
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-secondary btn-small"
|
||||||
|
onClick={() => { setShowDepsModal(false); setShowGraph(true); }}
|
||||||
|
>
|
||||||
|
View Graph
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{depsLoading ? (
|
||||||
|
<div className="deps-loading">Loading dependencies...</div>
|
||||||
|
) : depsError ? (
|
||||||
|
<div className="deps-error">{depsError}</div>
|
||||||
|
) : dependencies.length === 0 ? (
|
||||||
|
<div className="deps-empty">No dependencies</div>
|
||||||
|
) : (
|
||||||
|
<div className="deps-list">
|
||||||
|
<div className="deps-summary">
|
||||||
|
{dependencies.length} {dependencies.length === 1 ? 'dependency' : 'dependencies'}:
|
||||||
|
</div>
|
||||||
|
<ul className="deps-items">
|
||||||
|
{dependencies.map((dep) => (
|
||||||
|
<li key={dep.id} className="dep-item">
|
||||||
|
<Link
|
||||||
|
to={`/project/${dep.project}/${dep.package}`}
|
||||||
|
className="dep-link"
|
||||||
|
onClick={() => setShowDepsModal(false)}
|
||||||
|
>
|
||||||
|
{dep.project}/{dep.package}
|
||||||
|
</Link>
|
||||||
|
<span className="dep-constraint">
|
||||||
|
@ {dep.version || dep.tag}
|
||||||
|
</span>
|
||||||
|
<span className="dep-status dep-status--ok" title="Package exists">
|
||||||
|
✓
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Artifact ID Modal */}
|
||||||
|
{showArtifactIdModal && viewArtifactId && (
|
||||||
|
<div className="modal-overlay" onClick={() => setShowArtifactIdModal(false)}>
|
||||||
|
<div className="artifact-id-modal" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div className="modal-header">
|
||||||
|
<h3>Artifact ID</h3>
|
||||||
|
<button
|
||||||
|
className="modal-close"
|
||||||
|
onClick={() => setShowArtifactIdModal(false)}
|
||||||
|
title="Close"
|
||||||
|
>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||||
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||||
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<p className="modal-description">SHA256 hash identifying this artifact:</p>
|
||||||
|
<div className="artifact-id-display">
|
||||||
|
<code>{viewArtifactId}</code>
|
||||||
|
<CopyButton text={viewArtifactId} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Action Menu Dropdown */}
|
{/* Action Menu Dropdown */}
|
||||||
{renderActionMenu()}
|
{renderActionMenu()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user