File transfer

This commit is contained in:
2026-06-11 20:33:44 -05:00
parent 2cb1d58264
commit b362357bcb
58 changed files with 5096 additions and 4 deletions

View File

@@ -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>
);
}