MachineSelector.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import React, { useCallback, useEffect, useMemo, useState, type CSSProperties } from "react";
  2. import data from "../../assets/data/2.0/data.json";
  3. import styles from "./MachineSelector.module.css";
  4. import Icon from "../icon";
  5. import { Autocomplete, Box, Popper, TextField, InputAdornment } from "@mui/material";
  6. import SelectMenu from "./SelectFactorioMenu";
  7. import Tooltip from "../Tooltip";
  8. import type { Recipe } from "../../../scripts/factorio-dump/helpers/recipes.helper";
  9. import type { Machine } from "../../../scripts/factorio-dump/process-data.models";
  10. import { useQualityScroller } from "../../hooks/useQualityScroller";
  11. const recipeList = data.recipeGroup.flatMap((r) => r.subGroup.flatMap((s) => (s.children ?? []) as Recipe[]));
  12. const machines = data.machines as Machine[];
  13. type MachineSelectorProps = {
  14. className?: string;
  15. style?: CSSProperties;
  16. onChange?: (selection: { machine: Machine | null; qualityLevel: number; recipe: Recipe | null }) => void;
  17. };
  18. function MachineSelector({ style, className, onChange }: MachineSelectorProps) {
  19. const [machine, setMachine] = React.useState<Machine | null>(null);
  20. const [recipe, setRecipe] = useState<null | Recipe>(null);
  21. const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
  22. const [showMenuRecipe, setShowMenuRecipe] = useState(false);
  23. const { scrollRef, activeQuality, setQualityName } = useQualityScroller("normal");
  24. useEffect(() => {
  25. if (onChange) {
  26. onChange({ machine, qualityLevel: activeQuality.level, recipe });
  27. }
  28. }, [machine, activeQuality, recipe]);
  29. const machineOptions = useMemo(() => {
  30. if (recipe == null) return [];
  31. const categories = recipe.categories ?? [];
  32. return machines.filter((m) => m.crafting_categories.some((c) => categories.includes(c)));
  33. }, [recipe]);
  34. useEffect(() => {
  35. if (machineOptions.length === 1) setMachine(machineOptions[0]);
  36. if (machine && !machineOptions.includes(machine)) setMachine(null);
  37. }, [machineOptions, machine]);
  38. const onOpenRecipe = useCallback(
  39. (event: React.MouseEvent<HTMLElement>) => {
  40. setAnchorEl(anchorEl ? null : event.currentTarget);
  41. setShowMenuRecipe(!showMenuRecipe);
  42. },
  43. [anchorEl, showMenuRecipe],
  44. );
  45. const onSelectRecipe = useCallback((name: string) => {
  46. setRecipe(recipeList.find((r) => r.name === name) ?? null);
  47. setAnchorEl(null);
  48. setShowMenuRecipe(false);
  49. }, []);
  50. return (
  51. <div className={className} style={{ ...style, display: "flex", gap: "12px", alignItems: "center" }}>
  52. {/* Recipe Trigger */}
  53. <div onClick={onOpenRecipe} className={styles.machineSpecRecipe} style={{ cursor: "pointer" }}>
  54. {recipe == null ? (
  55. <div
  56. style={{
  57. padding: "8px 12px",
  58. border: "1px dashed #646464",
  59. borderRadius: "4px",
  60. }}
  61. >
  62. Select recipe
  63. </div>
  64. ) : (
  65. <Tooltip title={recipe.name}>
  66. <div>
  67. <Icon iconName={recipe.icon ?? ""} size={40} />
  68. </div>
  69. </Tooltip>
  70. )}
  71. </div>
  72. <Autocomplete
  73. ref={scrollRef}
  74. value={machine}
  75. onChange={(_: any, newValue: Machine | null) => {
  76. setMachine(newValue);
  77. }}
  78. disablePortal
  79. options={machineOptions}
  80. sx={{ width: 300 }}
  81. getOptionLabel={(option) => option.name}
  82. disabled={!recipe}
  83. renderInput={(params: any) => (
  84. <TextField
  85. {...params}
  86. label={recipe ? "Machine (Alt+Scroll)" : "Select recipe first"}
  87. InputProps={{
  88. ...params.InputProps,
  89. startAdornment: machine ? (
  90. <InputAdornment position="start">
  91. <div title="Alt + Scroll to change quality" style={{ cursor: "ns-resize", display: "flex" }}>
  92. <Icon iconName={machine.icon ?? ""} size={28} qualityLevel={activeQuality.level} />
  93. </div>
  94. </InputAdornment>
  95. ) : null,
  96. }}
  97. />
  98. )}
  99. renderOption={(props: any, option: Machine) => {
  100. const { key, ...optionProps } = props;
  101. return (
  102. <Box key={key} component="li" {...optionProps} sx={{ gap: 2 }}>
  103. <Icon iconName={option.icon ?? ""} size={30} qualityLevel={activeQuality.level} />
  104. {option.name}
  105. </Box>
  106. );
  107. }}
  108. />
  109. <Popper open={showMenuRecipe} anchorEl={anchorEl} placement="bottom-start" style={{ zIndex: 1300 }}>
  110. <SelectMenu
  111. title="Select recipe"
  112. categories={data.recipeGroup}
  113. onSelectItem={onSelectRecipe}
  114. onClose={() => setShowMenuRecipe(false)}
  115. />
  116. </Popper>
  117. </div>
  118. );
  119. }
  120. export default MachineSelector;