OPC # 0001: Extract OPC into standalone repo
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { Button, Drawer, Intent, NonIdealState, Spinner, Tag, Tooltip } from '@blueprintjs/core';
|
||||
import { html as diff2htmlHtml } from 'diff2html';
|
||||
import 'diff2html/bundles/css/diff2html.min.css';
|
||||
import hljs from 'highlight.js';
|
||||
import 'highlight.js/styles/github.css';
|
||||
import { getCommitDetail, type CommitDetail } from '../api/opcApi';
|
||||
|
||||
interface Props {
|
||||
hash: string | null;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function GitCommitDrawer({ hash, onClose }: Props) {
|
||||
const [detail, setDetail] = useState<CommitDetail | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const diffRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hash) { setDetail(null); setError(null); return; }
|
||||
setLoading(true); setDetail(null); setError(null);
|
||||
getCommitDetail(hash)
|
||||
.then(setDetail)
|
||||
.catch(e => setError(String(e)))
|
||||
.finally(() => setLoading(false));
|
||||
}, [hash]);
|
||||
|
||||
// After diff HTML is injected, run highlight.js over code blocks
|
||||
useEffect(() => {
|
||||
if (detail && diffRef.current) {
|
||||
diffRef.current.querySelectorAll<HTMLElement>('code[class]').forEach(el => {
|
||||
hljs.highlightElement(el);
|
||||
});
|
||||
}
|
||||
}, [detail]);
|
||||
|
||||
const combinedPatch = detail?.files.map(f => f.patch).join('\n') ?? '';
|
||||
const diffHtml = combinedPatch
|
||||
? diff2htmlHtml(combinedPatch, {
|
||||
drawFileList: true,
|
||||
matching: 'lines',
|
||||
outputFormat: 'line-by-line',
|
||||
renderNothingWhenEmpty: false,
|
||||
})
|
||||
: '';
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
isOpen={!!hash}
|
||||
onClose={onClose}
|
||||
title={detail ? (
|
||||
<span className="git-drawer-title">
|
||||
<code className="git-drawer-hash">{detail.shortHash}</code>
|
||||
<span className="git-drawer-subject">{detail.subject}</span>
|
||||
</span>
|
||||
) : 'Commit Diff'}
|
||||
size="70%"
|
||||
position="right"
|
||||
className="git-commit-drawer"
|
||||
>
|
||||
<div className="git-drawer-body">
|
||||
{loading && <NonIdealState icon={<Spinner size={24} />} title="Loading diff…" />}
|
||||
{error && <NonIdealState icon="error" intent={Intent.DANGER} title="Failed to load commit" description={error} />}
|
||||
|
||||
{detail && (
|
||||
<>
|
||||
{/* Metadata bar */}
|
||||
<div className="git-commit-meta-bar">
|
||||
<div className="git-commit-meta-left">
|
||||
<Tooltip content="Copy full hash">
|
||||
<code
|
||||
className="git-commit-hash-chip"
|
||||
onClick={() => navigator.clipboard.writeText(detail.hash)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
{detail.shortHash}
|
||||
</code>
|
||||
</Tooltip>
|
||||
<span className="git-commit-author">{detail.author}</span>
|
||||
<span className="git-commit-date">{detail.date}</span>
|
||||
</div>
|
||||
<div className="git-commit-meta-right">
|
||||
<Tag intent={Intent.SUCCESS} minimal round icon="add">
|
||||
+{detail.files.reduce((a, f) => a + f.additions, 0)}
|
||||
</Tag>
|
||||
<Tag intent={Intent.DANGER} minimal round icon="remove">
|
||||
-{detail.files.reduce((a, f) => a + f.deletions, 0)}
|
||||
</Tag>
|
||||
<Tag minimal round>{detail.files.length} file{detail.files.length !== 1 ? 's' : ''}</Tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Commit body if multiline */}
|
||||
{detail.body.trim() !== detail.subject.trim() && (
|
||||
<pre className="git-commit-body">{detail.body.trim()}</pre>
|
||||
)}
|
||||
|
||||
{/* Diff */}
|
||||
{diffHtml
|
||||
? <div ref={diffRef} className="git-diff-container" dangerouslySetInnerHTML={{ __html: diffHtml }} />
|
||||
: <NonIdealState icon="git-commit" title="No diff" description="This commit has no file changes." />
|
||||
}
|
||||
</>
|
||||
)}
|
||||
|
||||
{!loading && !error && !detail && hash && (
|
||||
<NonIdealState icon={<Spinner size={20} />} title="Loading…" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="git-drawer-footer">
|
||||
<Button text="Close" onClick={onClose} />
|
||||
</div>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user