ItemForm.jsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import React from "react";
  2. import { useTranslation } from "react-i18next";
  3. import { Field } from "react-final-form";
  4. import Label from "../ui/formUtils/Label";
  5. import Hint from "../ui/formUtils/Hint";
  6. import Slider from "../ui/Slider";
  7. import ActionList from "./ActionList";
  8. import { itemTemplates } from "./";
  9. export const getFormFieldComponent = (type) => {
  10. if (type in itemTemplates) {
  11. return itemTemplates[type].form;
  12. }
  13. return () => null;
  14. };
  15. const getDefaultActionsFromItem = (item) => {
  16. if (item.type in itemTemplates) {
  17. const actions = itemTemplates[item.type].defaultActions;
  18. if (typeof actions === "function") {
  19. return actions(item);
  20. }
  21. return actions;
  22. }
  23. return [];
  24. };
  25. const getAvailableActionsFromItem = (item) => {
  26. if (item.type in itemTemplates) {
  27. const actions = itemTemplates[item.type].availableActions;
  28. if (typeof actions === "function") {
  29. return actions(item);
  30. }
  31. return actions;
  32. }
  33. return [];
  34. };
  35. const getExcludedFields = (types) => {
  36. return types.reduce((excluded, type) => {
  37. return Object.assign(excluded, itemTemplates[type].excludeFields || {});
  38. }, {});
  39. };
  40. const ItemForm = ({ items, types, extraExcludeFields = {} }) => {
  41. const { t } = useTranslation();
  42. let FieldsComponent;
  43. const oneType = types.length === 1;
  44. if (items.length === 1) {
  45. FieldsComponent = getFormFieldComponent(items[0].type);
  46. } else {
  47. if (oneType) {
  48. FieldsComponent = getFormFieldComponent(types[0]);
  49. } else {
  50. FieldsComponent = () => null;
  51. }
  52. }
  53. const defaultActions = React.useMemo(() => {
  54. if (oneType) {
  55. return getDefaultActionsFromItem(items[0]);
  56. }
  57. return undefined;
  58. }, [items, oneType]);
  59. const availableActions = React.useMemo(() => {
  60. if (oneType) {
  61. return getAvailableActionsFromItem(items[0]);
  62. }
  63. return [];
  64. }, [items, oneType]);
  65. // Merge extra excluded fields and all item excluded fields
  66. const excludeFields = { ...getExcludedFields(types), ...extraExcludeFields };
  67. let initialValues;
  68. // Set initial values to item values only if one element selected
  69. // Empty object otherwise
  70. if (items.length === 1) {
  71. initialValues = { ...items[0] };
  72. if (items.length === 1) {
  73. initialValues.actions = (initialValues.actions || defaultActions).map(
  74. (action) => {
  75. if (typeof action === "string") {
  76. return { name: action };
  77. }
  78. return action;
  79. }
  80. );
  81. }
  82. } else {
  83. initialValues = {};
  84. }
  85. return (
  86. <>
  87. {!excludeFields.locked && (
  88. <Label>
  89. <Field
  90. name="locked"
  91. component="input"
  92. type="checkbox"
  93. initialValue={initialValues.locked}
  94. />
  95. <span className="checkable">{t("Locked?")}</span>
  96. <Hint>{t("Lock action help")}</Hint>
  97. </Label>
  98. )}
  99. {!excludeFields.rotation && (
  100. <Label>
  101. {t("Rotation")}
  102. <Field name="rotation" initialValue={initialValues.rotation}>
  103. {({ input: { onChange, value } }) => {
  104. return (
  105. <Slider
  106. defaultValue={0}
  107. value={value}
  108. min={-180}
  109. max={180}
  110. step={5}
  111. included={false}
  112. marks={{
  113. "-180": -180,
  114. "-90": -90,
  115. "-45": -45,
  116. "-30": -30,
  117. 0: 0,
  118. 30: 30,
  119. 45: 45,
  120. 90: 90,
  121. 180: 180,
  122. }}
  123. onChange={onChange}
  124. className={"slider-rotation"}
  125. />
  126. );
  127. }}
  128. </Field>
  129. </Label>
  130. )}
  131. {!excludeFields.layer && (
  132. <Label>
  133. {t("Layer")}
  134. <Field name="layer" initialValue={initialValues.layer}>
  135. {({ input: { onChange, value } }) => {
  136. return (
  137. <Slider
  138. defaultValue={0}
  139. value={value}
  140. min={-3}
  141. max={3}
  142. step={1}
  143. included={false}
  144. marks={{
  145. "-3": -3,
  146. "-2": -2,
  147. "-1": -1,
  148. 0: 0,
  149. "1": 1,
  150. "2": 2,
  151. "3": 3,
  152. }}
  153. onChange={onChange}
  154. className={"slider-layer"}
  155. />
  156. );
  157. }}
  158. </Field>
  159. </Label>
  160. )}
  161. <FieldsComponent initialValues={initialValues} />
  162. <h3>{t("Snap to grid")}</h3>
  163. <Label>
  164. {t("Grid type")}
  165. <Field
  166. name="grid.type"
  167. initialValue={initialValues.grid?.type}
  168. component="select"
  169. >
  170. <option value="none">{t("None")}</option>
  171. <option value="grid">{t("Grid")}</option>
  172. <option value="hexH">{t("Horizontal hexagons")}</option>
  173. <option value="hexV">{t("Vertical hexagons")}</option>
  174. </Field>
  175. </Label>
  176. <Label>
  177. {t("Size")}
  178. <Field
  179. name="grid.size"
  180. component="input"
  181. initialValue={initialValues.grid?.size}
  182. >
  183. {(props) => <input {...props.input} type="number" />}
  184. </Field>
  185. </Label>
  186. {oneType && (
  187. <>
  188. <h3>{t("Item actions")}</h3>
  189. <ActionList
  190. name="actions"
  191. initialValue={initialValues.actions}
  192. availableActions={availableActions}
  193. />
  194. </>
  195. )}
  196. </>
  197. );
  198. };
  199. export default React.memo(ItemForm);