PreheatFilamentTargetsEditor.tsx 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. import { useTranslation } from 'react-i18next';
  2. import {
  3. DEFAULT_PREHEAT_FILAMENT_TARGETS,
  4. PREHEAT_FILAMENT_ORDER,
  5. parsePreheatFilamentTargets,
  6. serializePreheatFilamentTargets,
  7. } from '../utils/preheatFilamentTargets';
  8. interface Props {
  9. // JSON-encoded map; empty string means "use bundled defaults".
  10. value: string;
  11. onChange: (next: string) => void;
  12. disabled?: boolean;
  13. }
  14. // Per-filament chamber target editor for Settings → Workflow → Preheat card
  15. // (#1468). Renders one row per filament type with a numeric input clamped to
  16. // 0-60 °C. Stripping back to the bundled defaults is handled by the parent
  17. // (Reset button next to the section title) — passing an empty string upward
  18. // is the canonical "use defaults" signal, which keeps the editor stateless
  19. // across resets.
  20. export function PreheatFilamentTargetsEditor({ value, onChange, disabled = false }: Props) {
  21. const { t } = useTranslation();
  22. const map = parsePreheatFilamentTargets(value);
  23. const updateOne = (key: string, next: number) => {
  24. const clamped = Math.max(0, Math.min(60, Math.round(next)));
  25. const updated = { ...map, [key]: clamped };
  26. onChange(serializePreheatFilamentTargets(updated));
  27. };
  28. return (
  29. <div className="grid grid-cols-1 sm:grid-cols-2 gap-x-3 gap-y-1.5">
  30. {PREHEAT_FILAMENT_ORDER.map((key) => {
  31. const current = map[key] ?? DEFAULT_PREHEAT_FILAMENT_TARGETS[key] ?? 0;
  32. const label = key === 'default'
  33. ? t('settings.preheatFilamentTargetsDefaultRow', 'Other / unmapped')
  34. : key;
  35. return (
  36. <div key={key} className="flex items-center justify-between gap-2">
  37. <span className={`text-xs ${key === 'default' ? 'text-bambu-gray italic' : 'text-bambu-gray'}`}>
  38. {label}
  39. </span>
  40. <div className="flex items-center gap-1">
  41. <input
  42. type="number"
  43. min={0}
  44. max={60}
  45. step={1}
  46. value={current}
  47. onChange={(e) => updateOne(key, parseInt(e.target.value, 10) || 0)}
  48. disabled={disabled}
  49. className="w-16 px-2 py-1 bg-bambu-dark border border-bambu-dark-tertiary rounded text-white text-xs text-right focus:outline-none focus:border-bambu-green disabled:opacity-50"
  50. />
  51. <span className="text-xs text-bambu-gray">°C</span>
  52. </div>
  53. </div>
  54. );
  55. })}
  56. </div>
  57. );
  58. }