Fix file rename bugs and hide file extensions
All checks were successful
Automated Container Build / build-and-push (push) Successful in 34s

This commit is contained in:
Elijah 2026-05-23 07:52:47 -07:00
parent 5200654d0f
commit 99e0cd5f0c
2 changed files with 51 additions and 17 deletions

View file

@ -46,6 +46,20 @@ interface UploadProgress {
error?: string; error?: string;
} }
function getFileDisplayName(name: string, isDir: boolean) {
if (isDir) return name;
const dotIndex = name.lastIndexOf('.');
if (dotIndex > 0) return name.substring(0, dotIndex);
return name;
}
function getFileExtension(name: string, isDir: boolean) {
if (isDir) return '';
const dotIndex = name.lastIndexOf('.');
if (dotIndex > 0) return name.substring(dotIndex);
return '';
}
export default function FileExplorer({ onLogout }: FileExplorerProps) { export default function FileExplorer({ onLogout }: FileExplorerProps) {
const [currentPath, setCurrentPath] = useState(() => { const [currentPath, setCurrentPath] = useState(() => {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
@ -324,6 +338,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
} }
function handleFileClick(file: FileItem, e: React.MouseEvent) { function handleFileClick(file: FileItem, e: React.MouseEvent) {
if (renaming === file.path) return;
if (e.shiftKey) { if (e.shiftKey) {
// Multi-select with shift // Multi-select with shift
setSelectedFiles(prev => { setSelectedFiles(prev => {
@ -406,9 +421,20 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
} }
} }
async function handleRename(path: string) { async function handleRename(path: string, originalName: string, isDir: boolean) {
if (!newName.trim()) return; if (!newName.trim()) {
await api.rename(path, newName); setRenaming(null);
setNewName('');
return;
}
const ext = getFileExtension(originalName, isDir);
const finalName = newName.trim() + ext;
if (finalName === originalName) {
setRenaming(null);
setNewName('');
return;
}
await api.rename(path, finalName);
setRenaming(null); setRenaming(null);
setNewName(''); setNewName('');
loadFiles(); loadFiles();
@ -785,7 +811,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
<div className="w-4 h-4 flex items-center justify-center flex-shrink-0 [&>svg]:w-4 [&>svg]:h-4"> <div className="w-4 h-4 flex items-center justify-center flex-shrink-0 [&>svg]:w-4 [&>svg]:h-4">
{getFileIcon(file)} {getFileIcon(file)}
</div> </div>
<span className="truncate flex-1">{file.name}</span> <span className="truncate flex-1" title={getFileDisplayName(file.name, file.is_dir)}>{getFileDisplayName(file.name, file.is_dir)}</span>
</div> </div>
)) ))
)} )}
@ -1141,10 +1167,11 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
type="text" type="text"
value={newName} value={newName}
onChange={(e) => setNewName(e.target.value)} onChange={(e) => setNewName(e.target.value)}
onKeyDown={(e) => { if (e.key === 'Enter') handleRename(file.path); if (e.key === 'Escape') setRenaming(null); }} onKeyDown={(e) => { if (e.key === 'Enter') handleRename(file.path, file.name, file.is_dir); if (e.key === 'Escape') setRenaming(null); }}
onBlur={() => handleRename(file.path)} onBlur={() => handleRename(file.path, file.name, file.is_dir)}
autoFocus autoFocus
onFocus={(e) => e.target.select()} onFocus={(e) => e.target.select()}
onClick={(e) => e.stopPropagation()}
className="w-full text-xs text-center bg-transparent outline-none" className="w-full text-xs text-center bg-transparent outline-none"
style={{ style={{
color: 'var(--color-text-primary)', color: 'var(--color-text-primary)',
@ -1155,9 +1182,9 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
<span <span
className="text-xs font-medium text-center w-full truncate" className="text-xs font-medium text-center w-full truncate"
style={{ color: 'var(--color-text-primary)' }} style={{ color: 'var(--color-text-primary)' }}
title={file.name} title={getFileDisplayName(file.name, file.is_dir)}
> >
{file.name} {getFileDisplayName(file.name, file.is_dir)}
</span> </span>
)} )}
</div> </div>
@ -1221,15 +1248,16 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
type="text" type="text"
value={newName} value={newName}
onChange={(e) => setNewName(e.target.value)} onChange={(e) => setNewName(e.target.value)}
onKeyDown={(e) => { if (e.key === 'Enter') handleRename(file.path); if (e.key === 'Escape') setRenaming(null); }} onKeyDown={(e) => { if (e.key === 'Enter') handleRename(file.path, file.name, file.is_dir); if (e.key === 'Escape') setRenaming(null); }}
onBlur={() => handleRename(file.path)} onBlur={() => handleRename(file.path, file.name, file.is_dir)}
autoFocus autoFocus
onFocus={(e) => e.target.select()} onFocus={(e) => e.target.select()}
onClick={(e) => e.stopPropagation()}
className="flex-1 text-sm bg-transparent outline-none" className="flex-1 text-sm bg-transparent outline-none"
style={{ color: 'var(--color-text-primary)', borderBottom: '1px solid var(--color-accent)' }} style={{ color: 'var(--color-text-primary)', borderBottom: '1px solid var(--color-accent)' }}
/> />
) : ( ) : (
<span className="text-sm truncate" style={{ color: 'var(--color-text-primary)' }}>{file.name}</span> <span className="text-sm truncate" style={{ color: 'var(--color-text-primary)' }} title={getFileDisplayName(file.name, file.is_dir)}>{getFileDisplayName(file.name, file.is_dir)}</span>
)} )}
{file.is_pinned && <Pin className="w-3 h-3 flex-shrink-0" style={{ color: 'var(--color-warning)' }} />} {file.is_pinned && <Pin className="w-3 h-3 flex-shrink-0" style={{ color: 'var(--color-warning)' }} />}
</div> </div>
@ -1376,7 +1404,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
label="Rename" label="Rename"
onClick={() => { onClick={() => {
setRenaming(contextMenu.file.path); setRenaming(contextMenu.file.path);
setNewName(contextMenu.file.name); setNewName(getFileDisplayName(contextMenu.file.name, contextMenu.file.is_dir));
setContextMenu(null); setContextMenu(null);
}} }}
/> />
@ -1490,9 +1518,9 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
<span <span
className="text-xs font-medium truncate max-w-[200px]" className="text-xs font-medium truncate max-w-[200px]"
style={{ color: 'var(--color-text-primary)' }} style={{ color: 'var(--color-text-primary)' }}
title={upload.name} title={getFileDisplayName(upload.name, false)}
> >
{upload.name} {getFileDisplayName(upload.name, false)}
</span> </span>
<span className="text-xs" style={{ color: 'var(--color-text-tertiary)' }}> <span className="text-xs" style={{ color: 'var(--color-text-tertiary)' }}>
{upload.status === 'complete' ? '✓' : upload.status === 'error' ? '✗' : `${Math.round(upload.progress)}%`} {upload.status === 'complete' ? '✓' : upload.status === 'error' ? '✗' : `${Math.round(upload.progress)}%`}

View file

@ -18,6 +18,12 @@ interface FilePreviewProps {
onClose: () => void; onClose: () => void;
} }
function getFileDisplayName(name: string) {
const dotIndex = name.lastIndexOf('.');
if (dotIndex > 0) return name.substring(0, dotIndex);
return name;
}
export default function FilePreview({ file, onClose }: FilePreviewProps) { export default function FilePreview({ file, onClose }: FilePreviewProps) {
const [content, setContent] = useState<string | null>(null); const [content, setContent] = useState<string | null>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -87,8 +93,8 @@ export default function FilePreview({ file, onClose }: FilePreviewProps) {
<div className="flex items-center justify-between px-4 py-3 border-b shrink-0" style={{ borderColor: 'var(--color-border-subtle)', backgroundColor: 'var(--color-bg-secondary)' }}> <div className="flex items-center justify-between px-4 py-3 border-b shrink-0" style={{ borderColor: 'var(--color-border-subtle)', backgroundColor: 'var(--color-bg-secondary)' }}>
<div className="flex items-center gap-3 min-w-0"> <div className="flex items-center gap-3 min-w-0">
{getIcon(previewType)} {getIcon(previewType)}
<span className="font-medium truncate" style={{ color: 'var(--color-text-primary)' }} title={file.name}> <span className="font-medium truncate" style={{ color: 'var(--color-text-primary)' }} title={getFileDisplayName(file.name)}>
{file.name} {getFileDisplayName(file.name)}
</span> </span>
<span className="text-xs px-2 py-1 rounded-md" style={{ backgroundColor: 'var(--color-bg-tertiary)', color: 'var(--color-text-tertiary)' }}> <span className="text-xs px-2 py-1 rounded-md" style={{ backgroundColor: 'var(--color-bg-tertiary)', color: 'var(--color-text-tertiary)' }}>
{formatSize(file.size)} {formatSize(file.size)}
@ -139,7 +145,7 @@ export default function FilePreview({ file, onClose }: FilePreviewProps) {
{previewType === 'audio' && ( {previewType === 'audio' && (
<div className="w-full max-w-md p-6 rounded-xl text-center" style={{ backgroundColor: 'var(--color-bg-elevated)', border: '1px solid var(--color-border)' }}> <div className="w-full max-w-md p-6 rounded-xl text-center" style={{ backgroundColor: 'var(--color-bg-elevated)', border: '1px solid var(--color-border)' }}>
<Music className="w-16 h-16 mx-auto mb-6" style={{ color: 'var(--color-accent)' }} /> <Music className="w-16 h-16 mx-auto mb-6" style={{ color: 'var(--color-accent)' }} />
<h3 className="text-lg font-medium mb-4 truncate" style={{ color: 'var(--color-text-primary)' }}>{file.name}</h3> <h3 className="text-lg font-medium mb-4 truncate" style={{ color: 'var(--color-text-primary)' }}>{getFileDisplayName(file.name)}</h3>
<audio src={downloadUrl} controls autoPlay className="w-full outline-none" /> <audio src={downloadUrl} controls autoPlay className="w-full outline-none" />
</div> </div>
)} )}