PlateSelector.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import { Layers, Check, AlertTriangle, Square, CheckSquare } from 'lucide-react';
  2. import { useTranslation } from 'react-i18next';
  3. import type { PlateSelectorProps } from './types';
  4. import { formatDuration } from '../../utils/date';
  5. import { withStreamToken } from '../../api/client';
  6. /**
  7. * Plate selection grid for multi-plate 3MF files.
  8. * Shows thumbnails, names, objects, and print times for each plate.
  9. * In multi-select mode (add-to-queue), plates have checkboxes for selecting a subset.
  10. * In single-select mode (reprint/edit), only one plate can be selected at a time.
  11. */
  12. export function PlateSelector({
  13. plates,
  14. isMultiPlate,
  15. selectedPlates,
  16. onToggle,
  17. onSelectAll,
  18. onDeselectAll,
  19. multiSelect,
  20. }: PlateSelectorProps) {
  21. const { t } = useTranslation();
  22. // Only show for multi-plate files with multiple plates
  23. if (!isMultiPlate || plates.length <= 1) {
  24. return null;
  25. }
  26. const allSelected = selectedPlates.size === plates.length;
  27. return (
  28. <div className="mb-4">
  29. <div className="flex items-center gap-2 mb-2">
  30. <Layers className="w-4 h-4 text-bambu-gray" />
  31. <span className="text-sm text-bambu-gray">Select Plate{multiSelect ? 's' : ''} to Print</span>
  32. {selectedPlates.size === 0 && (
  33. <span className="text-xs text-orange-400 flex items-center gap-1">
  34. <AlertTriangle className="w-3 h-3" />
  35. Selection required
  36. </span>
  37. )}
  38. {multiSelect && onSelectAll && onDeselectAll && (
  39. <button
  40. type="button"
  41. onClick={allSelected ? onDeselectAll : onSelectAll}
  42. className={`ml-auto text-xs px-2 py-0.5 rounded-full border transition-colors ${
  43. allSelected
  44. ? 'border-bambu-green bg-bambu-green/10 text-bambu-green'
  45. : 'border-bambu-dark-tertiary text-bambu-gray hover:border-bambu-gray'
  46. }`}
  47. >
  48. {allSelected
  49. ? t('queue.deselectAll')
  50. : t('queue.selectAllPlates', { count: plates.length })}
  51. </button>
  52. )}
  53. </div>
  54. <div className="grid grid-cols-2 gap-2">
  55. {plates.map((plate) => {
  56. const isSelected = selectedPlates.has(plate.index);
  57. return (
  58. <button
  59. key={plate.index}
  60. type="button"
  61. onClick={() => onToggle(plate.index)}
  62. className={`flex items-center gap-2 p-2 rounded-lg border transition-colors text-left ${
  63. isSelected
  64. ? 'border-bambu-green bg-bambu-green/10'
  65. : 'border-bambu-dark-tertiary bg-bambu-dark hover:border-bambu-gray'
  66. }`}
  67. >
  68. {multiSelect && (
  69. isSelected
  70. ? <CheckSquare className="w-4 h-4 text-bambu-green flex-shrink-0" />
  71. : <Square className="w-4 h-4 text-bambu-gray flex-shrink-0" />
  72. )}
  73. {plate.has_thumbnail && plate.thumbnail_url != null ? (
  74. <img
  75. src={withStreamToken(plate.thumbnail_url)}
  76. alt={`Plate ${plate.index}`}
  77. className="w-10 h-10 rounded object-cover bg-bambu-dark-tertiary"
  78. />
  79. ) : (
  80. <div className="w-10 h-10 rounded bg-bambu-dark-tertiary flex items-center justify-center">
  81. <Layers className="w-5 h-5 text-bambu-gray" />
  82. </div>
  83. )}
  84. <div className="min-w-0 flex-1">
  85. <p className="text-sm text-white font-medium truncate">
  86. {plate.name || `Plate ${plate.index}`}
  87. </p>
  88. <p className="text-xs text-bambu-gray truncate">
  89. {plate.objects.length > 0
  90. ? plate.objects.slice(0, 3).join(', ') +
  91. (plate.objects.length > 3 ? '...' : '')
  92. : `${plate.filaments.length} filament${plate.filaments.length !== 1 ? 's' : ''}`}
  93. {plate.print_time_seconds != null ? ` • ${formatDuration(plate.print_time_seconds)}` : ''}
  94. </p>
  95. </div>
  96. {!multiSelect && isSelected && (
  97. <Check className="w-4 h-4 text-bambu-green flex-shrink-0" />
  98. )}
  99. </button>
  100. );
  101. })}
  102. </div>
  103. </div>
  104. );
  105. }