fix: Go compiler error with MoveRequest inline struct and remove refactor.js
All checks were successful
Automated Container Build / build-and-push (push) Successful in 57s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 57s
This commit is contained in:
parent
10bab4ea5b
commit
45989ef97d
2 changed files with 7 additions and 306 deletions
|
|
@ -403,13 +403,16 @@ func (h *FSHandler) Rename(c *fiber.Ctx) error {
|
||||||
return c.JSON(fiber.Map{"message": "renamed successfully", "new_path": filepath.ToSlash(newPath)})
|
return c.JSON(fiber.Map{"message": "renamed successfully", "new_path": filepath.ToSlash(newPath)})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MoveRequest struct {
|
||||||
|
SourcePath string `json:"source_path"`
|
||||||
|
DestPath string `json:"dest_path"`
|
||||||
|
Resolution string `json:"resolution"` // "overwrite", "rename", "skip"
|
||||||
|
}
|
||||||
|
|
||||||
// Move moves a file or folder to a new location.
|
// Move moves a file or folder to a new location.
|
||||||
// POST /api/files/move
|
// POST /api/files/move
|
||||||
func (h *FSHandler) Move(c *fiber.Ctx) error {
|
func (h *FSHandler) Move(c *fiber.Ctx) error {
|
||||||
var body struct {
|
var body MoveRequest
|
||||||
SourcePath string `json:"source_path"`
|
|
||||||
DestPath string `json:"dest_path"`
|
|
||||||
}
|
|
||||||
if err := c.BodyParser(&body); err != nil {
|
if err := c.BodyParser(&body); err != nil {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
302
refactor.js
302
refactor.js
|
|
@ -1,302 +0,0 @@
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
let content = fs.readFileSync('frontend/src/components/FileExplorer.tsx', 'utf-8');
|
|
||||||
|
|
||||||
// 1. Add ConflictState to the component state
|
|
||||||
const stateInsertPos = content.indexOf(' const [deleteConfirm');
|
|
||||||
const conflictStateCode = ` const [conflictState, setConflictState] = useState<{
|
|
||||||
type: 'upload' | 'move' | 'copy';
|
|
||||||
files?: {file: File, path: string}[];
|
|
||||||
sourcePaths?: string[];
|
|
||||||
destPath: string;
|
|
||||||
conflicts: string[];
|
|
||||||
} | null>(null);\n`;
|
|
||||||
content = content.slice(0, stateInsertPos) + conflictStateCode + content.slice(stateInsertPos);
|
|
||||||
|
|
||||||
// 2. Replace handleDrop and uploadFile with processUploadBatch and updated handleDrop
|
|
||||||
const uploadFileStart = content.indexOf(' async function uploadFile');
|
|
||||||
const handleCreateFolderStart = content.indexOf(' async function handleCreateFolder');
|
|
||||||
|
|
||||||
const newUploadCode = ` async function processUploadBatch(entries: {file: File, path: string}[], destPath: string, overwrite: boolean = false) {
|
|
||||||
if (!entries.length) return;
|
|
||||||
const batchId = 'batch-' + Date.now();
|
|
||||||
const totalFiles = entries.length;
|
|
||||||
const totalBytes = entries.reduce((acc, curr) => acc + curr.file.size, 0);
|
|
||||||
const batchName = totalFiles === 1 ? entries[0].file.name : \`Uploading \${totalFiles} items\`;
|
|
||||||
|
|
||||||
setUploads(prev => [...prev, {
|
|
||||||
id: batchId,
|
|
||||||
name: batchName,
|
|
||||||
totalBytes,
|
|
||||||
uploadedBytes: 0,
|
|
||||||
totalFiles,
|
|
||||||
filesUploaded: 0,
|
|
||||||
status: 'uploading'
|
|
||||||
}]);
|
|
||||||
|
|
||||||
const MAX_CONCURRENT = 4;
|
|
||||||
let currentIndex = 0;
|
|
||||||
let completedFiles = 0;
|
|
||||||
let currentUploadedBytes = 0;
|
|
||||||
const fileProgressMap = new Map<string, number>();
|
|
||||||
|
|
||||||
const updateProgress = () => {
|
|
||||||
let totalUploaded = 0;
|
|
||||||
fileProgressMap.forEach(bytes => { totalUploaded += bytes; });
|
|
||||||
setUploads(prev => prev.map(b => b.id === batchId ? {
|
|
||||||
...b,
|
|
||||||
uploadedBytes: totalUploaded,
|
|
||||||
filesUploaded: completedFiles,
|
|
||||||
status: completedFiles === totalFiles ? 'complete' : 'uploading'
|
|
||||||
} : b));
|
|
||||||
};
|
|
||||||
|
|
||||||
const worker = async () => {
|
|
||||||
while (currentIndex < entries.length) {
|
|
||||||
const index = currentIndex++;
|
|
||||||
const { file, path } = entries[index];
|
|
||||||
const uploadPath = path ? (destPath === '.' ? path : \`\${destPath}/\${path}\`) : destPath;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await api.upload(file, uploadPath, {
|
|
||||||
overwrite,
|
|
||||||
onProgress: (percent) => {
|
|
||||||
fileProgressMap.set(file.name + path, (percent / 100) * file.size);
|
|
||||||
updateProgress();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
completedFiles++;
|
|
||||||
fileProgressMap.set(file.name + path, file.size);
|
|
||||||
updateProgress();
|
|
||||||
} catch (err: any) {
|
|
||||||
setUploads(prev => prev.map(b => b.id === batchId ? { ...b, status: 'error', error: err.message || 'Upload failed' } : b));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const workers = [];
|
|
||||||
for (let i = 0; i < Math.min(MAX_CONCURRENT, entries.length); i++) {
|
|
||||||
workers.push(worker());
|
|
||||||
}
|
|
||||||
await Promise.all(workers);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
setUploads(prev => prev.filter(u => u.id !== batchId));
|
|
||||||
loadFiles(undefined, true);
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleDrop(e: React.DragEvent) {
|
|
||||||
e.preventDefault();
|
|
||||||
setDragOver(false);
|
|
||||||
if (draggedFile) return;
|
|
||||||
|
|
||||||
if (e.dataTransfer.items) {
|
|
||||||
const filesToUpload: { file: File; path: string }[] = [];
|
|
||||||
const promises: Promise<void>[] = [];
|
|
||||||
|
|
||||||
const processEntry = async (entry: any, path: string = '') => {
|
|
||||||
if (entry.isFile) {
|
|
||||||
const file = await new Promise<File>((resolve) => entry.file(resolve));
|
|
||||||
filesToUpload.push({ file, path: path.replace(/\\/$/, '') });
|
|
||||||
} else if (entry.isDirectory) {
|
|
||||||
const dirReader = entry.createReader();
|
|
||||||
let entries: any[] = [];
|
|
||||||
|
|
||||||
const readAllEntries = async () => {
|
|
||||||
const batch = await new Promise<any[]>((resolve) => dirReader.readEntries(resolve));
|
|
||||||
if (batch.length > 0) {
|
|
||||||
entries = entries.concat(batch);
|
|
||||||
await readAllEntries();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
await readAllEntries();
|
|
||||||
|
|
||||||
for (const child of entries) {
|
|
||||||
await processEntry(child, \`\${path}\${entry.name}/\`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < e.dataTransfer.items.length; i++) {
|
|
||||||
const item = e.dataTransfer.items[i];
|
|
||||||
if (item.kind === 'file') {
|
|
||||||
const entry = item.webkitGetAsEntry();
|
|
||||||
if (entry) {
|
|
||||||
promises.push(processEntry(entry));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
|
|
||||||
if (filesToUpload.length > 0) {
|
|
||||||
// Collect destination paths to create folders
|
|
||||||
const uniqueDirs = new Set<string>();
|
|
||||||
filesToUpload.forEach(({path}) => {
|
|
||||||
if (path) uniqueDirs.add(currentPath === '.' ? path : \`\${currentPath}/\${path}\`);
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const dir of Array.from(uniqueDirs)) {
|
|
||||||
try { await api.createFolder(dir); } catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileNames = filesToUpload.map(f => f.path ? \`\${f.path}/\${f.file.name}\` : f.file.name);
|
|
||||||
try {
|
|
||||||
const res = await api.checkConflicts(currentPath, fileNames);
|
|
||||||
if (res.conflicts && res.conflicts.length > 0) {
|
|
||||||
setConflictState({ type: 'upload', files: filesToUpload, destPath: currentPath, conflicts: res.conflicts });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
processUploadBatch(filesToUpload, currentPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleFileUpload(e: React.ChangeEvent<HTMLInputElement>) {
|
|
||||||
const uploadFiles = e.target.files;
|
|
||||||
if (!uploadFiles) return;
|
|
||||||
const filesArray = Array.from(uploadFiles).map(f => ({ file: f, path: '' }));
|
|
||||||
|
|
||||||
const fileNames = filesArray.map(f => f.file.name);
|
|
||||||
try {
|
|
||||||
const res = await api.checkConflicts(currentPath, fileNames);
|
|
||||||
if (res.conflicts && res.conflicts.length > 0) {
|
|
||||||
setConflictState({ type: 'upload', files: filesArray, destPath: currentPath, conflicts: res.conflicts });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
processUploadBatch(filesArray, currentPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
`;
|
|
||||||
|
|
||||||
content = content.slice(0, uploadFileStart) + newUploadCode + content.slice(handleCreateFolderStart);
|
|
||||||
|
|
||||||
// Add Conflict Modal rendering
|
|
||||||
const renderModalsPos = content.lastIndexOf('{showMoveModal && (');
|
|
||||||
const conflictModalCode = `
|
|
||||||
{conflictState && (
|
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/40 backdrop-blur-sm" onClick={() => setConflictState(null)}>
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, scale: 0.95 }}
|
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
|
||||||
className="w-full max-w-md rounded-2xl shadow-xl overflow-hidden flex flex-col"
|
|
||||||
style={{ backgroundColor: 'var(--color-bg-primary)', border: '1px solid var(--color-border)', maxHeight: '90vh' }}
|
|
||||||
onClick={e => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<div className="p-6 border-b" style={{ borderColor: 'var(--color-border)' }}>
|
|
||||||
<h2 className="text-xl font-semibold" style={{ color: 'var(--color-text-primary)' }}>Name Conflict Detected</h2>
|
|
||||||
<p className="mt-1 text-sm" style={{ color: 'var(--color-text-secondary)' }}>
|
|
||||||
The following files already exist in the destination. How would you like to proceed?
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="overflow-y-auto p-6 text-sm flex-1" style={{ backgroundColor: 'var(--color-bg-secondary)', color: 'var(--color-text-secondary)' }}>
|
|
||||||
<ul className="list-disc pl-4 space-y-1">
|
|
||||||
{conflictState.conflicts.map(c => <li key={c} className="break-all">{c}</li>)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div className="p-6 flex flex-col gap-3 border-t" style={{ borderColor: 'var(--color-border)' }}>
|
|
||||||
<button onClick={() => executeConflictResolution('overwrite')} className="w-full py-2.5 rounded-xl font-medium text-white shadow-sm transition-opacity hover:opacity-90 bg-red-500">
|
|
||||||
Replace
|
|
||||||
</button>
|
|
||||||
<button onClick={() => executeConflictResolution('rename')} className="w-full py-2.5 rounded-xl font-medium text-white shadow-sm transition-opacity hover:opacity-90" style={{ backgroundColor: 'var(--color-accent)' }}>
|
|
||||||
Auto-Rename (Keep Both)
|
|
||||||
</button>
|
|
||||||
<button onClick={() => executeConflictResolution('skip')} className="w-full py-2.5 rounded-xl font-medium shadow-sm transition-opacity hover:opacity-90" style={{ backgroundColor: 'var(--color-bg-elevated)', color: 'var(--color-text-primary)' }}>
|
|
||||||
Skip Conflicting
|
|
||||||
</button>
|
|
||||||
<button onClick={() => setConflictState(null)} className="w-full py-2.5 rounded-xl font-medium shadow-sm transition-opacity hover:opacity-90 border" style={{ borderColor: 'var(--color-border)', color: 'var(--color-text-primary)' }}>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
`;
|
|
||||||
content = content.slice(0, renderModalsPos) + conflictModalCode + content.slice(renderModalsPos);
|
|
||||||
|
|
||||||
// Add executeConflictResolution function
|
|
||||||
const execFunctionCode = `
|
|
||||||
async function executeConflictResolution(resolution: string) {
|
|
||||||
if (!conflictState) return;
|
|
||||||
const { type, destPath, conflicts } = conflictState;
|
|
||||||
|
|
||||||
if (type === 'upload' && conflictState.files) {
|
|
||||||
let filesToUpload = conflictState.files;
|
|
||||||
if (resolution === 'skip') {
|
|
||||||
filesToUpload = filesToUpload.filter(f => {
|
|
||||||
const name = f.path ? \`\${f.path}/\${f.file.name}\` : f.file.name;
|
|
||||||
return !conflicts.includes(name);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setConflictState(null);
|
|
||||||
if (filesToUpload.length > 0) {
|
|
||||||
processUploadBatch(filesToUpload, destPath, resolution === 'overwrite');
|
|
||||||
}
|
|
||||||
} else if (type === 'move' && conflictState.sourcePaths) {
|
|
||||||
let pathsToMove = conflictState.sourcePaths;
|
|
||||||
if (resolution === 'skip') {
|
|
||||||
pathsToMove = pathsToMove.filter(p => !conflicts.includes(p.split('/').pop()!));
|
|
||||||
}
|
|
||||||
setConflictState(null);
|
|
||||||
if (pathsToMove.length > 0) {
|
|
||||||
try {
|
|
||||||
await Promise.all(pathsToMove.map(src => api.move(src, destPath, resolution)));
|
|
||||||
setShowMoveModal(false);
|
|
||||||
setMovingFiles([]);
|
|
||||||
loadFiles(undefined, true);
|
|
||||||
setSelectedFiles(new Set());
|
|
||||||
showToast(\`Moved \${pathsToMove.length} item\${pathsToMove.length > 1 ? 's' : ''}\`);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
`;
|
|
||||||
|
|
||||||
const handleDropOnFolderStart = content.indexOf(' async function handleDropOnFolder');
|
|
||||||
content = content.slice(0, handleDropOnFolderStart) + execFunctionCode + content.slice(handleDropOnFolderStart);
|
|
||||||
|
|
||||||
// Update handleMoveExecute
|
|
||||||
content = content.replace(
|
|
||||||
\` async function handleMoveExecute() {
|
|
||||||
if (movingFiles.length === 0) return;
|
|
||||||
try {
|
|
||||||
await Promise.all(movingFiles.map(src => api.move(src, moveModalPath === '.' ? '.' : moveModalPath)));\`,
|
|
||||||
\` async function handleMoveExecute() {
|
|
||||||
if (movingFiles.length === 0) return;
|
|
||||||
try {
|
|
||||||
const destPath = moveModalPath === '.' ? '.' : moveModalPath;
|
|
||||||
const fileNames = movingFiles.map(src => src.split('/').pop() || src);
|
|
||||||
const res = await api.checkConflicts(destPath, fileNames);
|
|
||||||
if (res.conflicts && res.conflicts.length > 0) {
|
|
||||||
setConflictState({ type: 'move', sourcePaths: movingFiles, destPath, conflicts: res.conflicts });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await Promise.all(movingFiles.map(src => api.move(src, destPath)));\`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update handleDropOnFolder
|
|
||||||
content = content.replace(
|
|
||||||
\` async function handleDropOnFolder(file: FileItem, targetFolder: FileItem) {
|
|
||||||
if (file.path === targetFolder.path) return;
|
|
||||||
try {
|
|
||||||
await api.move(file.path, targetFolder.path);\`,
|
|
||||||
\` async function handleDropOnFolder(file: FileItem, targetFolder: FileItem) {
|
|
||||||
if (file.path === targetFolder.path) return;
|
|
||||||
try {
|
|
||||||
const res = await api.checkConflicts(targetFolder.path, [file.name]);
|
|
||||||
if (res.conflicts && res.conflicts.length > 0) {
|
|
||||||
setConflictState({ type: 'move', sourcePaths: [file.path], destPath: targetFolder.path, conflicts: res.conflicts });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await api.move(file.path, targetFolder.path);\`
|
|
||||||
);
|
|
||||||
|
|
||||||
fs.writeFileSync('frontend/src/components/FileExplorer.tsx', content);
|
|
||||||
Reference in a new issue