BeaconConfigurator.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import data from "../../assets/data/2.0/data.json"; // Adjust path if needed
  2. import ModuleSlots from "./ModuleSlots";
  3. import type { Beacon } from "../../../scripts/factorio-dump/process-data.models";
  4. import { qualityShortcut, useQualityScroller } from "../../hooks/useQualityScroller";
  5. import Icon from "../icon";
  6. import NumberInput from "../components/NumberInput";
  7. import styles from "./BeaconConfigurator.module.css";
  8. import type { BeaconGroup } from "../../engine";
  9. const beaconsData = data.beacons as Beacon[];
  10. export type BeaconGroupConf = BeaconGroup & {
  11. id: string;
  12. };
  13. type BeaconConfiguratorProps = {
  14. groups: BeaconGroupConf[];
  15. onChange: (g: BeaconGroupConf[]) => void;
  16. direction?: "horizontal" | "vertical";
  17. };
  18. function BeaconIconWrapper({
  19. beacon,
  20. qualityLevel,
  21. onChangeQuality,
  22. }: {
  23. beacon: Beacon;
  24. qualityLevel: number;
  25. onChangeQuality: (qualityLevel: number) => void;
  26. }) {
  27. // Use the hook in controlled mode by passing qualityLevel
  28. const { scrollRef, activeQuality } = useQualityScroller("normal", qualityLevel, (q) => onChangeQuality(q.level));
  29. return (
  30. <div ref={scrollRef} className={styles.iconWrapper} title={qualityShortcut + " to change beacon quality"}>
  31. <label>Beacon</label>
  32. <Icon iconName={beacon.icon ?? ""} size={44} qualityLevel={activeQuality.level} />
  33. </div>
  34. );
  35. }
  36. export default function BeaconConfigurator({ groups, onChange, direction = "vertical" }: BeaconConfiguratorProps) {
  37. const handleAddGroup = () => {
  38. const defaultBeacon = beaconsData[0];
  39. onChange([
  40. ...groups,
  41. {
  42. id: crypto.randomUUID(), // Use modern UUID instead of Date.now()
  43. beacon: defaultBeacon,
  44. beaconQualityLevel: 0,
  45. count: 1,
  46. modules: [],
  47. },
  48. ]);
  49. };
  50. const updateGroup = (id: string, patch: Partial<BeaconGroup>) => {
  51. onChange(groups.map((g) => (g.id === id ? { ...g, ...patch } : g)));
  52. };
  53. return (
  54. <div className={`${styles.container} ${direction === "horizontal" ? styles.horizontal : styles.vertical}`}>
  55. {groups.map((g) => (
  56. <div key={g.id} className={styles.groupCard}>
  57. {/* Beacon Count */}
  58. <div className="field" style={{ minWidth: "60px" }}>
  59. <label style={{ fontSize: "11px", color: "#999", marginBottom: "4px" }}>Count</label>
  60. <NumberInput min={1} max={50} value={g.count} onChange={(val) => updateGroup(g.id, { count: val || 1 })} />
  61. </div>
  62. {/* Beacon Type & Quality */}
  63. <BeaconIconWrapper
  64. beacon={g.beacon}
  65. qualityLevel={g.beaconQualityLevel ?? 0} // Pass the controlled state down!
  66. onChangeQuality={(q) => updateGroup(g.id, { beaconQualityLevel: q })}
  67. />
  68. {/* Beacon Modules */}
  69. <div className="field">
  70. <label style={{ fontSize: "11px", color: "#999", marginBottom: "4px" }}>
  71. Modules ({g.beacon.module_slots})
  72. </label>
  73. <ModuleSlots
  74. value={g.modules} // Pass the controlled state down!
  75. maxSlots={g.beacon.module_slots}
  76. allowedEffects={g.beacon.allowed_effects as string[]}
  77. onChange={(mods) => updateGroup(g.id, { modules: mods })}
  78. />
  79. </div>
  80. <button
  81. className={styles.removeBtn}
  82. onClick={() => onChange(groups.filter((x) => x.id !== g.id))}
  83. title="Remove Beacon Group"
  84. >
  85. </button>
  86. </div>
  87. ))}
  88. <button onClick={handleAddGroup} className={styles.addBtn}>
  89. + Add Beacons
  90. </button>
  91. </div>
  92. );
  93. }