PreheatFilamentTargetsEditor.tsx 2.5 KB

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