This commit is contained in:
@@ -9,7 +9,7 @@ import { LoadingModal, useMinimumLoading } from '../operations/operation-utils';
|
|||||||
import {
|
import {
|
||||||
getLauncherSource,
|
getLauncherSource,
|
||||||
getGiteaCommitDiff,
|
getGiteaCommitDiff,
|
||||||
listGiteaCommits,
|
listGiteaCommitsPage,
|
||||||
listLauncherVersions,
|
listLauncherVersions,
|
||||||
listPlatformRepos,
|
listPlatformRepos,
|
||||||
updateLauncherSource,
|
updateLauncherSource,
|
||||||
@@ -130,6 +130,42 @@ function commitRangeSince(range: CommitRange) {
|
|||||||
return date.toISOString();
|
return date.toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function diffLineClass(line: string) {
|
||||||
|
if (line.startsWith('+++') || line.startsWith('---')) {
|
||||||
|
return 'bg-sky-500/10 text-sky-200';
|
||||||
|
}
|
||||||
|
if (line.startsWith('@@')) {
|
||||||
|
return 'bg-violet-500/15 text-violet-200';
|
||||||
|
}
|
||||||
|
if (line.startsWith('+')) {
|
||||||
|
return 'bg-emerald-500/15 text-emerald-100';
|
||||||
|
}
|
||||||
|
if (line.startsWith('-')) {
|
||||||
|
return 'bg-rose-500/15 text-rose-100';
|
||||||
|
}
|
||||||
|
if (line.startsWith('diff --git') || line.startsWith('index ')) {
|
||||||
|
return 'bg-muted/40 text-foreground';
|
||||||
|
}
|
||||||
|
return 'text-muted-foreground';
|
||||||
|
}
|
||||||
|
|
||||||
|
function DiffViewer({ diff }: { diff: string }) {
|
||||||
|
const lines = diff ? diff.split(/\r?\n/) : ['표시할 변경 내역이 없습니다.'];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-h-[70vh] overflow-auto rounded-md border border-border bg-[#0b0f14] font-mono text-xs leading-5">
|
||||||
|
{lines.map((line, index) => (
|
||||||
|
<div key={`${index}-${line}`} className={`grid min-w-max grid-cols-[64px_minmax(0,1fr)] ${diffLineClass(line)}`}>
|
||||||
|
<span className="select-none border-r border-white/10 px-3 py-0.5 text-right text-[11px] text-muted-foreground">
|
||||||
|
{index + 1}
|
||||||
|
</span>
|
||||||
|
<pre className="whitespace-pre px-3 py-0.5">{line || ' '}</pre>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function LauncherSourcePage() {
|
export function LauncherSourcePage() {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [activeTarget, setActiveTarget] = useState<SourceTarget>('launcher-client');
|
const [activeTarget, setActiveTarget] = useState<SourceTarget>('launcher-client');
|
||||||
@@ -144,6 +180,7 @@ export function LauncherSourcePage() {
|
|||||||
const [commitRange, setCommitRange] = useState<CommitRange>('week');
|
const [commitRange, setCommitRange] = useState<CommitRange>('week');
|
||||||
const [commitPage, setCommitPage] = useState(1);
|
const [commitPage, setCommitPage] = useState(1);
|
||||||
const [hasNextCommitPage, setHasNextCommitPage] = useState(false);
|
const [hasNextCommitPage, setHasNextCommitPage] = useState(false);
|
||||||
|
const [totalCommits, setTotalCommits] = useState<number | null>(null);
|
||||||
const [commitsLoading, setCommitsLoading] = useState(false);
|
const [commitsLoading, setCommitsLoading] = useState(false);
|
||||||
const [commitsError, setCommitsError] = useState('');
|
const [commitsError, setCommitsError] = useState('');
|
||||||
const [diffTarget, setDiffTarget] = useState<GiteaCommit | null>(null);
|
const [diffTarget, setDiffTarget] = useState<GiteaCommit | null>(null);
|
||||||
@@ -158,6 +195,7 @@ export function LauncherSourcePage() {
|
|||||||
const activeVersion = useMemo(() => versions.find((version) => version.isActive) ?? versions[0], [versions]);
|
const activeVersion = useMemo(() => versions.find((version) => version.isActive) ?? versions[0], [versions]);
|
||||||
const activeForm = forms[activeTarget];
|
const activeForm = forms[activeTarget];
|
||||||
const activeTargetMeta = targetLabels[activeTarget];
|
const activeTargetMeta = targetLabels[activeTarget];
|
||||||
|
const totalCommitPages = totalCommits === null ? null : Math.max(1, Math.ceil(totalCommits / commitPageSize));
|
||||||
|
|
||||||
const updateForm = (patch: Partial<SourceForm>) => {
|
const updateForm = (patch: Partial<SourceForm>) => {
|
||||||
setForms((prev) => ({
|
setForms((prev) => ({
|
||||||
@@ -215,27 +253,30 @@ export function LauncherSourcePage() {
|
|||||||
setCommits([]);
|
setCommits([]);
|
||||||
setCommitsError('');
|
setCommitsError('');
|
||||||
setHasNextCommitPage(false);
|
setHasNextCommitPage(false);
|
||||||
|
setTotalCommits(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let canceled = false;
|
let canceled = false;
|
||||||
setCommitsLoading(true);
|
setCommitsLoading(true);
|
||||||
setCommitsError('');
|
setCommitsError('');
|
||||||
listGiteaCommits(repository.owner, repository.repo, sourceBranch, {
|
listGiteaCommitsPage(repository.owner, repository.repo, sourceBranch, {
|
||||||
page: commitPage,
|
page: commitPage,
|
||||||
limit: commitPageSize + 1,
|
limit: commitPageSize,
|
||||||
since: commitRangeSince(commitRange)
|
since: commitRangeSince(commitRange)
|
||||||
})
|
})
|
||||||
.then((nextCommits) => {
|
.then((result) => {
|
||||||
if (!canceled) {
|
if (!canceled) {
|
||||||
setCommits(nextCommits.slice(0, commitPageSize));
|
setCommits(result.commits);
|
||||||
setHasNextCommitPage(nextCommits.length > commitPageSize);
|
setTotalCommits(result.total);
|
||||||
|
setHasNextCommitPage(result.total === null ? result.commits.length === commitPageSize : commitPage < Math.max(1, Math.ceil(result.total / commitPageSize)));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error: Error) => {
|
.catch((error: Error) => {
|
||||||
if (!canceled) {
|
if (!canceled) {
|
||||||
setCommits([]);
|
setCommits([]);
|
||||||
setHasNextCommitPage(false);
|
setHasNextCommitPage(false);
|
||||||
|
setTotalCommits(null);
|
||||||
setCommitsError(error.message);
|
setCommitsError(error.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -460,7 +501,9 @@ export function LauncherSourcePage() {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between gap-3 border-t border-border px-5 py-4 text-sm">
|
<div className="flex items-center justify-between gap-3 border-t border-border px-5 py-4 text-sm">
|
||||||
<span className="text-muted-foreground">페이지 {commitPage}</span>
|
<span className="text-muted-foreground">
|
||||||
|
{totalCommits === null ? `페이지 ${commitPage}` : `총 ${totalCommits}건 · ${commitPage} / ${totalCommitPages}`}
|
||||||
|
</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -473,8 +516,8 @@ export function LauncherSourcePage() {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="h-8 px-3 text-[13px]"
|
className="h-8 px-3 text-[13px]"
|
||||||
disabled={!hasNextCommitPage || commitsLoading}
|
disabled={commitsLoading || (totalCommitPages !== null ? commitPage >= totalCommitPages : !hasNextCommitPage)}
|
||||||
onClick={() => setCommitPage((page) => page + 1)}
|
onClick={() => setCommitPage((page) => totalCommitPages === null ? page + 1 : Math.min(totalCommitPages, page + 1))}
|
||||||
>
|
>
|
||||||
다음
|
다음
|
||||||
</Button>
|
</Button>
|
||||||
@@ -486,7 +529,7 @@ export function LauncherSourcePage() {
|
|||||||
open={Boolean(diffTarget)}
|
open={Boolean(diffTarget)}
|
||||||
title="커밋 내역 상세"
|
title="커밋 내역 상세"
|
||||||
onClose={() => setDiffTarget(null)}
|
onClose={() => setDiffTarget(null)}
|
||||||
className="max-w-5xl"
|
className="max-w-[min(96vw,1400px)]"
|
||||||
footer={<Button variant="outline" onClick={() => setDiffTarget(null)}>닫기</Button>}
|
footer={<Button variant="outline" onClick={() => setDiffTarget(null)}>닫기</Button>}
|
||||||
>
|
>
|
||||||
{diffTarget ? (
|
{diffTarget ? (
|
||||||
@@ -505,9 +548,7 @@ export function LauncherSourcePage() {
|
|||||||
) : diffError ? (
|
) : diffError ? (
|
||||||
<div className="rounded-md border border-rose-500/30 bg-rose-500/10 px-4 py-3 text-sm text-rose-200">Diff 조회 실패: {diffError}</div>
|
<div className="rounded-md border border-rose-500/30 bg-rose-500/10 px-4 py-3 text-sm text-rose-200">Diff 조회 실패: {diffError}</div>
|
||||||
) : (
|
) : (
|
||||||
<pre className="max-h-[58vh] overflow-auto rounded-md border border-border bg-background p-4 font-mono text-xs leading-5 text-muted-foreground">
|
<DiffViewer diff={diffContent} />
|
||||||
{diffContent || '표시할 변경 내역이 없습니다.'}
|
|
||||||
</pre>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -221,6 +221,23 @@ export function listGiteaCommits(
|
|||||||
).then((result) => result.commits);
|
).then((result) => result.commits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function listGiteaCommitsPage(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
branch = 'develop',
|
||||||
|
options: { page: number; limit: number; since?: string }
|
||||||
|
) {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
sha: branch,
|
||||||
|
page: String(options.page),
|
||||||
|
limit: String(options.limit)
|
||||||
|
});
|
||||||
|
if (options.since) params.set('since', options.since);
|
||||||
|
return apiRequest<{ commits: GiteaCommit[]; total: number | null; page: number; limit: number }>(
|
||||||
|
`/gitea/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/commits?${params.toString()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function getGiteaCommitDiff(owner: string, repo: string, sha: string) {
|
export function getGiteaCommitDiff(owner: string, repo: string, sha: string) {
|
||||||
return apiRequest<{ diff: string }>(
|
return apiRequest<{ diff: string }>(
|
||||||
`/gitea/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/commits/${encodeURIComponent(sha)}/diff`
|
`/gitea/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/commits/${encodeURIComponent(sha)}/diff`
|
||||||
|
|||||||
Reference in New Issue
Block a user