97 lines
4.0 KiB
TypeScript
97 lines
4.0 KiB
TypeScript
/**
|
|
* Revision Sidebar — slide-in panel listing file revisions
|
|
* Ball Studios <https://git.balls.studio>
|
|
*/
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
import http from '@/api/http';
|
|
import DiffViewer from './DiffViewer';
|
|
import RestoreModal from './RestoreModal';
|
|
|
|
interface Revision {
|
|
id: number;
|
|
revision_number: number;
|
|
size_bytes: number;
|
|
change_summary: string;
|
|
created_at: string;
|
|
author: { username: string } | null;
|
|
}
|
|
|
|
interface Props {
|
|
serverUuid: string;
|
|
filePath: string;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export default function RevisionSidebar({ serverUuid, filePath, onClose }: Props) {
|
|
const [revisions, setRevisions] = useState<Revision[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [viewing, setViewing] = useState<Revision | null>(null);
|
|
const [restoring, setRestoring] = useState<Revision | null>(null);
|
|
|
|
useEffect(() => {
|
|
http.get(`/api/client/servers/${serverUuid}/addons/files/revisions`, {
|
|
params: { path: filePath },
|
|
})
|
|
.then(({ data }) => setRevisions(data.data ?? []))
|
|
.finally(() => setLoading(false));
|
|
}, [serverUuid, filePath]);
|
|
|
|
return (
|
|
<div className='addon-sidebar-overlay' onClick={onClose} aria-modal='true' role='dialog'>
|
|
<div className='addon-sidebar' onClick={e => e.stopPropagation()}>
|
|
<div className='addon-sidebar-header'>
|
|
<span>🕐 File History — <code>{filePath}</code></span>
|
|
<button className='addon-sidebar-close' onClick={onClose} aria-label='Close'>✕</button>
|
|
</div>
|
|
|
|
{loading && <div className='addon-loading'>Loading revisions…</div>}
|
|
|
|
{!loading && revisions.length === 0 && (
|
|
<div className='addon-empty'>No revisions recorded for this file yet.</div>
|
|
)}
|
|
|
|
<ul className='addon-revision-list'>
|
|
{revisions.map(rev => (
|
|
<li key={rev.id} className='addon-revision-item'>
|
|
<div className='addon-revision-meta'>
|
|
<span className='addon-revision-num'>#{rev.revision_number}</span>
|
|
<span className='addon-revision-author'>{rev.author?.username ?? 'Unknown'}</span>
|
|
<span className='addon-revision-date'>{new Date(rev.created_at).toLocaleString()}</span>
|
|
<span className='addon-revision-size'>{(rev.size_bytes / 1024).toFixed(1)} KB</span>
|
|
</div>
|
|
<p className='addon-revision-summary'>{rev.change_summary}</p>
|
|
<div className='addon-revision-actions'>
|
|
<button className='addon-btn addon-btn-sm' onClick={() => setViewing(rev)}>View Diff</button>
|
|
<button className='addon-btn addon-btn-sm addon-btn-primary' onClick={() => setRestoring(rev)}>Restore</button>
|
|
<a
|
|
className='addon-btn addon-btn-sm'
|
|
href={`/api/client/servers/${serverUuid}/addons/files/revisions/${rev.id}/download`}
|
|
download
|
|
>Download</a>
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
|
|
{viewing && (
|
|
<DiffViewer
|
|
serverUuid={serverUuid}
|
|
revision={viewing}
|
|
onClose={() => setViewing(null)}
|
|
/>
|
|
)}
|
|
|
|
{restoring && (
|
|
<RestoreModal
|
|
serverUuid={serverUuid}
|
|
revision={restoring}
|
|
onClose={() => setRestoring(null)}
|
|
onRestored={() => { setRestoring(null); onClose(); }}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|