Просмотр исходного кода

Add plate calibration to backup and fix directory creation

- Add "Plate Detection" option to backup modal for exporting empty plate
  reference images (requires Printers to be selected)
- Add parents=True to mkdir calls in config.py to create full directory
  tree on first run (fixes startup crash when data dir doesn't exist)
- Update BackupModal test to use exact match for "Printers" text
maziggy 7 месяцев назад
Родитель
Сommit
30f7d41ec3

+ 1 - 1
frontend/src/__tests__/components/BackupModal.test.tsx

@@ -59,7 +59,7 @@ describe('BackupModal', () => {
     it('has checkbox for printers', () => {
       render(<BackupModal onClose={mockOnClose} />);
 
-      expect(screen.getByText(/printers/i)).toBeInTheDocument();
+      expect(screen.getByText('Printers')).toBeInTheDocument();
     });
 
     it('has checkbox for archives', () => {

+ 1 - 0
frontend/src/api/client.ts

@@ -2214,6 +2214,7 @@ export const api = {
       if (categories.smart_plugs !== undefined) params.set('include_smart_plugs', String(categories.smart_plugs));
       if (categories.external_links !== undefined) params.set('include_external_links', String(categories.external_links));
       if (categories.printers !== undefined) params.set('include_printers', String(categories.printers));
+      if (categories.plate_calibration !== undefined) params.set('include_plate_calibration', String(categories.plate_calibration));
       if (categories.filaments !== undefined) params.set('include_filaments', String(categories.filaments));
       if (categories.maintenance !== undefined) params.set('include_maintenance', String(categories.maintenance));
       if (categories.archives !== undefined) params.set('include_archives', String(categories.archives));

+ 28 - 9
frontend/src/components/BackupModal.tsx

@@ -1,5 +1,5 @@
 import { useEffect, useState } from 'react';
-import { Download, X, Settings, Bell, FileText, Plug, Printer, Palette, Wrench, Archive, Loader2, Key, AlertTriangle, Link, FolderKanban, Upload } from 'lucide-react';
+import { Download, X, Settings, Bell, FileText, Plug, Printer, Palette, Wrench, Archive, Loader2, Key, AlertTriangle, Link, FolderKanban, Upload, Camera } from 'lucide-react';
 import { useTranslation } from 'react-i18next';
 import { Card, CardContent } from './Card';
 import { Button } from './Button';
@@ -12,6 +12,7 @@ interface BackupCategory {
   icon: React.ReactNode;
   default: boolean;
   description: string;
+  requiresPrinters?: boolean;
 }
 
 const BACKUP_CATEGORIES: BackupCategory[] = [
@@ -63,6 +64,15 @@ const BACKUP_CATEGORIES: BackupCategory[] = [
     default: false,
     description: 'Printer info (access codes excluded)',
   },
+  {
+    id: 'plate_calibration',
+    labelKey: 'backup.categories.plateCalibration',
+    defaultLabel: 'Plate Detection',
+    icon: <Camera className="w-4 h-4" />,
+    default: false,
+    description: 'Empty plate reference images',
+    requiresPrinters: true,
+  },
   {
     id: 'filaments',
     labelKey: 'backup.categories.filaments',
@@ -221,33 +231,42 @@ export function BackupModal({ onClose, onExport }: BackupModalProps) {
 
           {/* Categories */}
           <div className={`p-4 space-y-2 max-h-[400px] overflow-y-auto ${isExporting ? 'opacity-50 pointer-events-none' : ''}`}>
-            {BACKUP_CATEGORIES.map((category) => (
+            {BACKUP_CATEGORIES.map((category) => {
+              const isDisabled = isExporting || (category.requiresPrinters && !selected.printers);
+              return (
               <label
                 key={category.id}
-                className={`flex items-center gap-3 p-3 rounded-lg cursor-pointer transition-colors ${
-                  selected[category.id]
+                className={`flex items-center gap-3 p-3 rounded-lg transition-colors ${
+                  isDisabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'
+                } ${
+                  selected[category.id] && !isDisabled
                     ? 'bg-bambu-green/10 border border-bambu-green/30'
                     : 'bg-bambu-dark hover:bg-bambu-dark-tertiary border border-transparent'
                 }`}
               >
                 <input
                   type="checkbox"
-                  checked={selected[category.id]}
+                  checked={selected[category.id] && !isDisabled}
                   onChange={() => toggleCategory(category.id)}
-                  disabled={isExporting}
+                  disabled={isDisabled}
                   className="w-4 h-4 rounded border-bambu-gray bg-bambu-dark text-bambu-green focus:ring-bambu-green focus:ring-offset-0"
                 />
-                <div className={`${selected[category.id] ? 'text-bambu-green' : 'text-bambu-gray'}`}>
+                <div className={`${selected[category.id] && !isDisabled ? 'text-bambu-green' : 'text-bambu-gray'}`}>
                   {category.icon}
                 </div>
                 <div className="flex-1">
                   <div className="text-white text-sm font-medium">
                     {t(category.labelKey, { defaultValue: category.defaultLabel })}
                   </div>
-                  <div className="text-xs text-bambu-gray">{category.description}</div>
+                  <div className="text-xs text-bambu-gray">
+                    {category.requiresPrinters && !selected.printers
+                      ? 'Requires Printers to be selected'
+                      : category.description}
+                  </div>
                 </div>
               </label>
-            ))}
+              );
+            })}
           </div>
 
           {/* Archive warning */}