mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-22 03:49:18 +02:00
Merge branch 'copilot/update-file-upload-method' into copilot/fix-pull-request-1-failures
This commit is contained in:
@@ -535,7 +535,7 @@ describe('FileManagerPage', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('upload modal STL options', () => {
|
||||
describe('upload modal with advanced 3MF support', () => {
|
||||
it('opens upload modal', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<FileManagerPage />);
|
||||
@@ -552,6 +552,37 @@ describe('FileManagerPage', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('shows 3MF extraction info when 3MF file is added', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<FileManagerPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Upload')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
await user.click(screen.getByText('Upload'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Upload Files')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Create a mock 3MF file
|
||||
const threemfFile = new File(['content'], 'model.gcode.3mf', { type: 'application/octet-stream' });
|
||||
|
||||
// Get the hidden file input
|
||||
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
||||
expect(fileInput).toBeInTheDocument();
|
||||
|
||||
// Simulate file selection
|
||||
await user.upload(fileInput, threemfFile);
|
||||
|
||||
// 3MF extraction info should appear
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('3MF files detected')).toBeInTheDocument();
|
||||
expect(screen.getByText(/Printer model.*will be automatically extracted/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows STL thumbnail option when STL file is added', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<FileManagerPage />);
|
||||
@@ -579,96 +610,7 @@ describe('FileManagerPage', () => {
|
||||
// STL thumbnail option should appear
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('STL thumbnail generation')).toBeInTheDocument();
|
||||
expect(screen.getByText('Generate thumbnails for STL files')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('STL thumbnail checkbox is checked by default', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<FileManagerPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Upload')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
await user.click(screen.getByText('Upload'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Upload Files')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Add an STL file
|
||||
const stlFile = new File(['solid test'], 'model.stl', { type: 'application/sla' });
|
||||
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
||||
await user.upload(fileInput, stlFile);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Generate thumbnails for STL files')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Checkbox should be checked by default
|
||||
const checkbox = screen.getByRole('checkbox', { name: /Generate thumbnails for STL files/i });
|
||||
expect(checkbox).toBeChecked();
|
||||
});
|
||||
|
||||
it('can toggle STL thumbnail checkbox', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<FileManagerPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Upload')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
await user.click(screen.getByText('Upload'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Upload Files')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Add an STL file
|
||||
const stlFile = new File(['solid test'], 'model.stl', { type: 'application/sla' });
|
||||
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
||||
await user.upload(fileInput, stlFile);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Generate thumbnails for STL files')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const checkbox = screen.getByRole('checkbox', { name: /Generate thumbnails for STL files/i });
|
||||
expect(checkbox).toBeChecked();
|
||||
|
||||
// Toggle off
|
||||
await user.click(checkbox);
|
||||
expect(checkbox).not.toBeChecked();
|
||||
|
||||
// Toggle back on
|
||||
await user.click(checkbox);
|
||||
expect(checkbox).toBeChecked();
|
||||
});
|
||||
|
||||
it('shows STL thumbnail option for ZIP files', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<FileManagerPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Upload')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
await user.click(screen.getByText('Upload'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Upload Files')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Create a mock ZIP file
|
||||
const zipFile = new File(['PK'], 'models.zip', { type: 'application/zip' });
|
||||
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
||||
await user.upload(fileInput, zipFile);
|
||||
|
||||
// STL thumbnail option should appear for ZIP files (may contain STLs)
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('STL thumbnail generation')).toBeInTheDocument();
|
||||
expect(screen.getByText(/ZIP files may contain STL files/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Thumbnails can be generated/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1837,6 +1837,8 @@ export default {
|
||||
zipMayContainStl: 'ZIP files may contain STL files. Thumbnails can be generated during extraction.',
|
||||
thumbnailsCanBeGenerated: 'Thumbnails can be generated for STL files. Large models may take longer to process.',
|
||||
generateThumbnailsForStl: 'Generate thumbnails for STL files',
|
||||
threemfDetected: '3MF files detected',
|
||||
threemfExtractionInfo: 'Printer model, material, color, and print settings will be automatically extracted from 3MF files.',
|
||||
willBeExtracted: 'Will be extracted',
|
||||
filesExtracted: '{{count}} files extracted',
|
||||
uploadComplete: 'Upload complete: {{succeeded}} succeeded',
|
||||
|
||||
@@ -429,7 +429,9 @@ interface UploadFile {
|
||||
status: 'pending' | 'uploading' | 'success' | 'error';
|
||||
error?: string;
|
||||
isZip?: boolean;
|
||||
is3mf?: boolean;
|
||||
extractedCount?: number;
|
||||
archiveId?: number;
|
||||
}
|
||||
|
||||
function UploadModal({ folderId, onClose, onUploadComplete, t }: UploadModalProps) {
|
||||
@@ -469,6 +471,7 @@ function UploadModal({ folderId, onClose, onUploadComplete, t }: UploadModalProp
|
||||
file,
|
||||
status: 'pending',
|
||||
isZip: file.name.toLowerCase().endsWith('.zip'),
|
||||
is3mf: file.name.toLowerCase().endsWith('.3mf'),
|
||||
}));
|
||||
setFiles((prev) => [...prev, ...uploadFiles]);
|
||||
};
|
||||
@@ -479,14 +482,56 @@ function UploadModal({ folderId, onClose, onUploadComplete, t }: UploadModalProp
|
||||
|
||||
const hasZipFiles = files.some((f) => f.isZip && f.status === 'pending');
|
||||
const hasStlFiles = files.some((f) => f.file.name.toLowerCase().endsWith('.stl') && f.status === 'pending');
|
||||
const has3mfFiles = files.some((f) => f.is3mf && f.status === 'pending');
|
||||
|
||||
const handleUpload = async () => {
|
||||
if (files.length === 0) return;
|
||||
|
||||
setIsUploading(true);
|
||||
|
||||
// Handle .3mf files with bulk upload API (advanced extraction)
|
||||
const threemfFiles = files.filter((f) => f.is3mf && f.status === 'pending');
|
||||
if (threemfFiles.length > 0) {
|
||||
try {
|
||||
// Mark files as uploading
|
||||
setFiles((prev) =>
|
||||
prev.map((f) => (f.is3mf && f.status === 'pending' ? { ...f, status: 'uploading' } : f))
|
||||
);
|
||||
|
||||
// Use the archives bulk upload API for .3mf files (extracts printer model)
|
||||
const result = await api.uploadArchivesBulk(threemfFiles.map((f) => f.file));
|
||||
|
||||
// Update file statuses based on result
|
||||
setFiles((prev) =>
|
||||
prev.map((f) => {
|
||||
if (!f.is3mf || f.status !== 'uploading') return f;
|
||||
|
||||
const success = result.results.find((r) => r.filename === f.file.name);
|
||||
const error = result.errors.find((e) => e.filename === f.file.name);
|
||||
|
||||
if (success) {
|
||||
return { ...f, status: 'success', archiveId: success.id };
|
||||
}
|
||||
if (error) {
|
||||
return { ...f, status: 'error', error: error.error };
|
||||
}
|
||||
return f;
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
setFiles((prev) =>
|
||||
prev.map((f) =>
|
||||
f.is3mf && f.status === 'uploading'
|
||||
? { ...f, status: 'error', error: err instanceof Error ? err.message : 'Upload failed' }
|
||||
: f
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle other files (ZIP and regular files) with library upload
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (files[i].status !== 'pending') continue;
|
||||
if (files[i].status !== 'pending' || files[i].is3mf) continue;
|
||||
|
||||
setFiles((prev) =>
|
||||
prev.map((f, idx) => (idx === i ? { ...f, status: 'uploading' } : f))
|
||||
@@ -509,7 +554,7 @@ function UploadModal({ folderId, onClose, onUploadComplete, t }: UploadModalProp
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Regular file upload
|
||||
// Regular file upload (STL, etc.)
|
||||
await api.uploadLibraryFile(files[i].file, folderId, generateStlThumbnails);
|
||||
setFiles((prev) =>
|
||||
prev.map((f, idx) => (idx === i ? { ...f, status: 'success' } : f))
|
||||
@@ -609,6 +654,21 @@ function UploadModal({ folderId, onClose, onUploadComplete, t }: UploadModalProp
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 3MF File Info - Advanced Extraction */}
|
||||
{has3mfFiles && (
|
||||
<div className="p-3 bg-purple-500/10 border border-purple-500/30 rounded-lg">
|
||||
<div className="flex items-start gap-3">
|
||||
<Printer className="w-5 h-5 text-purple-400 mt-0.5 flex-shrink-0" />
|
||||
<div className="flex-1">
|
||||
<p className="text-sm text-purple-300 font-medium">{t('fileManager.threemfDetected')}</p>
|
||||
<p className="text-xs text-purple-300/70 mt-1">
|
||||
{t('fileManager.threemfExtractionInfo')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* STL Thumbnail Options - show for STL files or ZIP files (which may contain STLs) */}
|
||||
{(hasStlFiles || hasZipFiles) && (
|
||||
<div className="p-3 bg-bambu-green/10 border border-bambu-green/30 rounded-lg">
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-2
@@ -23,8 +23,8 @@
|
||||
|
||||
<!-- Splash screens for iOS -->
|
||||
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
|
||||
<script type="module" crossorigin src="/assets/index-B5vyvTs3.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DME4t7XG.css">
|
||||
<script type="module" crossorigin src="/assets/index-ChuiDDfR.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Mcnrrd7v.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
Reference in New Issue
Block a user