ItemFormFactory.jsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import React from "react";
  2. import { useTranslation } from "react-i18next";
  3. import { useRecoilValue } from "recoil";
  4. import Slider from "rc-slider";
  5. import { Form, Field } from "react-final-form";
  6. import AutoSave from "../../ui/formUtils/AutoSave";
  7. import { ItemMapAtom, selectedItemsAtom } from "../Board/";
  8. import { useItems } from "../Board/Items";
  9. import Label from "../../ui/formUtils/Label";
  10. import Hint from "../../ui/formUtils/Hint";
  11. import ActionsField from "./ActionsField";
  12. import {
  13. getFormFieldComponent,
  14. getDefaultActionsFromItem,
  15. getAvailableActionsFromItem,
  16. } from ".";
  17. const ItemFormFactory = () => {
  18. const { t } = useTranslation();
  19. const { batchUpdateItems } = useItems();
  20. const selectedItems = useRecoilValue(selectedItemsAtom);
  21. const itemMap = useRecoilValue(ItemMapAtom);
  22. const item = itemMap[selectedItems[0]];
  23. const [defaultActions] = React.useState(getDefaultActionsFromItem(item));
  24. const [availableActions] = React.useState(getAvailableActionsFromItem(item));
  25. let FieldsComponent;
  26. if (selectedItems.length === 1) {
  27. FieldsComponent = getFormFieldComponent(item.type);
  28. } else {
  29. const types = new Set(selectedItems.map((itemId) => itemMap[itemId].type));
  30. if (types.size === 1) {
  31. FieldsComponent = getFormFieldComponent(Array.from(types)[0]);
  32. } else {
  33. FieldsComponent = () => null;
  34. }
  35. }
  36. let initialValues;
  37. // Set initial values to item values if only one element selected
  38. // Empty object otherwise
  39. if (selectedItems.length === 1) {
  40. initialValues = { ...item };
  41. initialValues.actions = initialValues.actions || defaultActions;
  42. } else {
  43. initialValues = {};
  44. }
  45. const onSubmitHandler = React.useCallback(
  46. (formValues) => {
  47. batchUpdateItems(selectedItems, (item) => ({
  48. ...item,
  49. ...formValues,
  50. }));
  51. },
  52. [batchUpdateItems, selectedItems]
  53. );
  54. return (
  55. <Form
  56. onSubmit={onSubmitHandler}
  57. render={() => (
  58. <div
  59. style={{
  60. display: "flex",
  61. flexDirection: "column",
  62. }}
  63. >
  64. <AutoSave save={onSubmitHandler} />
  65. <Label>
  66. <Field
  67. name="locked"
  68. component="input"
  69. type="checkbox"
  70. initialValue={initialValues.locked}
  71. />
  72. <span className="checkable">{t("Locked?")}</span>
  73. <Hint>{t("Lock action help")}</Hint>
  74. </Label>
  75. <Label>
  76. {t("Rotation")}
  77. <Field name="rotation" initialValue={initialValues.rotation}>
  78. {({ input: { onChange, value } }) => {
  79. return (
  80. <Slider
  81. defaultValue={0}
  82. value={value}
  83. min={-180}
  84. max={180}
  85. step={5}
  86. included={false}
  87. marks={{
  88. "-90": -90,
  89. "-45": -45,
  90. "-30": -30,
  91. 0: 0,
  92. 30: 30,
  93. 45: 45,
  94. 90: 90,
  95. }}
  96. onChange={onChange}
  97. />
  98. );
  99. }}
  100. </Field>
  101. </Label>
  102. <Label>
  103. {t("Layer")}
  104. <Field name="layer" initialValue={initialValues.layer}>
  105. {({ input: { onChange, value } }) => {
  106. return (
  107. <Slider
  108. defaultValue={0}
  109. value={value}
  110. min={-3}
  111. max={3}
  112. step={1}
  113. included={false}
  114. marks={{
  115. "-3": -3,
  116. "-2": -2,
  117. "-1": -1,
  118. 0: 0,
  119. "1": 1,
  120. "2": 2,
  121. "3": 3,
  122. }}
  123. onChange={onChange}
  124. />
  125. );
  126. }}
  127. </Field>
  128. </Label>
  129. <FieldsComponent initialValues={initialValues} />
  130. <h3>{t("Snap to grid")}</h3>
  131. <Label>
  132. {t("Grid type")}
  133. <Field
  134. name="grid.type"
  135. initialValue={initialValues.grid?.type}
  136. component="select"
  137. >
  138. <option value="">{t("None")}</option>
  139. <option value="grid">{t("Grid")}</option>
  140. <option value="hexH">{t("Horizontal hexagons")}</option>
  141. <option value="hexV">{t("Vertical hexagons")}</option>
  142. </Field>
  143. </Label>
  144. <Label>
  145. {t("Size")}
  146. <Field
  147. name="grid.size"
  148. component="input"
  149. initialValue={initialValues.grid?.size}
  150. >
  151. {(props) => <input {...props.input} type="number" />}
  152. </Field>
  153. </Label>
  154. <h3>{t("Available actions")}</h3>
  155. <Label>
  156. <Field name="actions" initialValue={initialValues.actions}>
  157. {({ input: { onChange, value } }) => (
  158. <ActionsField
  159. onChange={onChange}
  160. value={value}
  161. availableActions={availableActions}
  162. />
  163. )}
  164. </Field>
  165. </Label>
  166. </div>
  167. )}
  168. />
  169. );
  170. };
  171. export default React.memo(ItemFormFactory);