Text.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import React, { useEffect, useMemo, useRef, useState } from "react";
  2. import { Input, InputRef } from "antd";
  3. import { Node } from "@antv/x6";
  4. import { useSafeState } from "ahooks";
  5. export default function Text(props: {
  6. value: string;
  7. styles: React.CSSProperties & {
  8. bold: boolean;
  9. italic: boolean;
  10. };
  11. node: Node;
  12. fixedWidth: boolean;
  13. placeholder?: string;
  14. txtStyle?: React.CSSProperties;
  15. onChange?: (value: string) => void;
  16. }) {
  17. const { value, styles, node, placeholder, txtStyle } = props;
  18. const [isEditing, setIsEditing] = useSafeState(false);
  19. const inputRef = useRef<InputRef>(null);
  20. const [labelText, setLabelText] = useState(value);
  21. useEffect(() => {
  22. setLabelText(value);
  23. }, [value]);
  24. const style = useMemo((): React.CSSProperties => {
  25. return {
  26. ...styles,
  27. fontWeight: styles.bold ? "bold" : undefined,
  28. fontStyle: styles.italic ? "italic" : undefined,
  29. minHeight: "12px",
  30. padding: 0,
  31. wordBreak: "break-all",
  32. };
  33. }, [styles]);
  34. const handleChange = (val: string) => {
  35. // node.setData({ label: val });
  36. setLabelText(val);
  37. props.onChange?.(val);
  38. };
  39. const handleSetEditing = (edit: boolean) => {
  40. if (node.data?.lock) {
  41. return;
  42. }
  43. node.setData({
  44. ignoreDrag: edit
  45. });
  46. if (edit) {
  47. // 置顶
  48. node.toFront();
  49. setTimeout(() => {
  50. inputRef.current?.focus({ cursor: "all" });
  51. }, 100);
  52. } else {
  53. // 更新数据
  54. node.prop("changeNodeData", {
  55. cellId: node.id,
  56. data: {
  57. label: labelText
  58. },
  59. ignoreRender: false,
  60. });
  61. }
  62. setIsEditing(edit);
  63. };
  64. const [findObj, setFindObj] = useState<{
  65. findStr: string;
  66. currentCellId?: string;
  67. currentIndex: number;
  68. }>();
  69. // 查找
  70. const handleFind = (args: any) => {
  71. setFindObj(args?.current || {});
  72. };
  73. const label = useMemo(() => {
  74. if (!findObj) return value;
  75. const list = (value || "").split(findObj.findStr || "");
  76. return list.map((str: string, index) => {
  77. // 当前的节点展示
  78. const style =
  79. findObj.currentCellId === node.id
  80. ? {
  81. background:
  82. index + 1 === findObj.currentIndex
  83. ? "#FF9933"
  84. : "rgba(255, 153, 51, 0.25)",
  85. }
  86. : {
  87. background: "#ffff00",
  88. };
  89. return (
  90. <span key={index}>
  91. {str}
  92. {index < list.length - 1 && (
  93. <span style={{
  94. ...style,
  95. color: '#333'
  96. }}>{findObj.findStr}</span>
  97. )}
  98. </span>
  99. );
  100. });
  101. }, [value, findObj]);
  102. const handleReplace = (args: any) => {
  103. const { type, searchText, replaceText, currentIndex, currentCellId } = args?.current || {};
  104. // 单个替换 全部替换不在此实现
  105. if(type === 'replace' && currentCellId === node.id) {
  106. const list = value.split(searchText);
  107. const text = list.map((str, index) => {
  108. const result = index + 1 === currentIndex ? replaceText : searchText;
  109. return str + (index < list.length - 1 ? result : '');
  110. }).join("");
  111. handleChange(text);
  112. }
  113. };
  114. const handleClear = () => {
  115. setFindObj(undefined);
  116. };
  117. useEffect(() => {
  118. node.off("change:find", handleFind);
  119. node.off("change:replace", handleReplace);
  120. node.off("change:clearFind", handleClear);
  121. node.off("change:customEdit", handleSetEditing);
  122. node.on("change:find", handleFind);
  123. node.on("change:replace", handleReplace);
  124. node.on("change:clearFind", handleClear);
  125. node.on("change:customEdit", handleSetEditing);
  126. return () => {
  127. node.off("change:find", handleFind);
  128. node.off("change:replace", handleReplace);
  129. node.off("change:clearFind", handleClear);
  130. node.off("change:customEdit", handleSetEditing);
  131. };
  132. }, [value]);
  133. return (
  134. <div style={txtStyle}>
  135. <div
  136. style={{
  137. ...style,
  138. opacity: isEditing ? 0 : 1,
  139. }}
  140. className="min-w-20px"
  141. onDoubleClick={() => handleSetEditing(true)}
  142. >
  143. {label}
  144. </div>
  145. {isEditing && (
  146. <Input.TextArea
  147. ref={inputRef}
  148. placeholder={placeholder}
  149. value={labelText}
  150. variant="borderless"
  151. style={{
  152. ...style,
  153. position: "absolute",
  154. left: 0,
  155. top: 0
  156. }}
  157. onChange={(e) => handleChange(e.target.value)}
  158. onBlur={() => handleSetEditing(false)}
  159. autoSize
  160. />
  161. )}
  162. </div>
  163. );
  164. }