Fix file rename bugs and hide file extensions
All checks were successful
Automated Container Build / build-and-push (push) Successful in 34s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 34s
This commit is contained in:
parent
5200654d0f
commit
99e0cd5f0c
2 changed files with 51 additions and 17 deletions
|
|
@ -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)}%`}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Reference in a new issue