File transfer
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user