WbwSent.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. import { Button, Dropdown, message, Tree } from "antd";
  2. import { useEffect, useState } from "react";
  3. import { MoreOutlined } from "@ant-design/icons";
  4. import { useAppSelector } from "../../hooks";
  5. import { mode as _mode } from "../../reducers/article-mode";
  6. import { post } from "../../request";
  7. import { ArticleMode } from "../article/Article";
  8. import WbwWord, {
  9. IWbw,
  10. IWbwFields,
  11. TWbwDisplayMode,
  12. WbwElement,
  13. WbwStatus,
  14. } from "./Wbw/WbwWord";
  15. import { TChannelType } from "../api/Channel";
  16. import { IDictRequest, IDictResponse, IUserDictCreate } from "../api/Dict";
  17. import { useIntl } from "react-intl";
  18. import { add } from "../../reducers/sent-word";
  19. import store from "../../store";
  20. import { settingInfo } from "../../reducers/setting";
  21. import { GetUserSetting } from "../auth/setting/default";
  22. interface IMagicDictRequest {
  23. book: number;
  24. para: number;
  25. word_start: number;
  26. word_end: number;
  27. data: IWbw[];
  28. channel_id: string;
  29. lang?: string[];
  30. }
  31. interface IMagicDictResponse {
  32. ok: boolean;
  33. message: string;
  34. data: IWbw[];
  35. }
  36. interface IWbwXml {
  37. id: string;
  38. pali: WbwElement<string>;
  39. real?: WbwElement<string | null>;
  40. type?: WbwElement<string | null>;
  41. gramma?: WbwElement<string | null>;
  42. mean?: WbwElement<string | null>;
  43. org?: WbwElement<string | null>;
  44. om?: WbwElement<string | null>;
  45. case?: WbwElement<string | null>;
  46. parent?: WbwElement<string | null>;
  47. pg?: WbwElement<string | null>;
  48. parent2?: WbwElement<string | null>;
  49. rela?: WbwElement<string | null>;
  50. lock?: boolean;
  51. bmt?: WbwElement<string | null>;
  52. bmc?: WbwElement<number | null>;
  53. cf: number;
  54. }
  55. interface IWbwUpdateResponse {
  56. ok: boolean;
  57. message: string;
  58. data: { rows?: IWbwXml[]; count: number };
  59. }
  60. interface IWbwWord {
  61. words: IWbwXml[];
  62. sn: number;
  63. }
  64. interface IWbwRequest {
  65. book: number;
  66. para: number;
  67. sn: number;
  68. channel_id: string;
  69. data: IWbwWord[];
  70. }
  71. interface IWidget {
  72. data: IWbw[];
  73. book: number;
  74. para: number;
  75. wordStart: number;
  76. wordEnd: number;
  77. channelId: string;
  78. channelType?: TChannelType;
  79. display?: TWbwDisplayMode;
  80. fields?: IWbwFields;
  81. layoutDirection?: "h" | "v";
  82. magicDict?: string;
  83. refreshable?: boolean;
  84. mode?: ArticleMode;
  85. onMagicDictDone?: Function;
  86. onChange?: Function;
  87. }
  88. export const WbwSentCtl = ({
  89. data,
  90. channelId,
  91. channelType,
  92. book,
  93. para,
  94. wordStart,
  95. wordEnd,
  96. display = "block",
  97. fields,
  98. layoutDirection = "h",
  99. mode,
  100. magicDict,
  101. refreshable = false,
  102. onChange,
  103. onMagicDictDone,
  104. }: IWidget) => {
  105. const intl = useIntl();
  106. const [wordData, setWordData] = useState<IWbw[]>(data);
  107. const [wbwMode, setWbwMode] = useState(display);
  108. const [fieldDisplay, setFieldDisplay] = useState(fields);
  109. const [displayMode, setDisplayMode] = useState<ArticleMode>();
  110. const [magic, setMagic] = useState<string>();
  111. const [loading, setLoading] = useState(false);
  112. const settings = useAppSelector(settingInfo);
  113. useEffect(() => {
  114. setMagic(magicDict);
  115. }, [magicDict]);
  116. const newMode = useAppSelector(_mode);
  117. const update = (data: IWbw[]) => {
  118. setWordData(data);
  119. if (typeof onChange !== "undefined") {
  120. onChange(data);
  121. }
  122. };
  123. useEffect(() => {
  124. if (refreshable) {
  125. setWordData(data);
  126. }
  127. }, [data, refreshable]);
  128. useEffect(() => {
  129. //发布句子里面的单词的变更。给术语输入菜单用。
  130. let words = new Map<string, number>();
  131. wordData
  132. .filter(
  133. (value) =>
  134. value.type?.value !== null &&
  135. value.type?.value !== ".ctl." &&
  136. value.real.value &&
  137. value.real.value.length > 0
  138. )
  139. .forEach((value) => {
  140. words.set(value.real.value ? value.real.value : "", 1);
  141. if (value.parent?.value) {
  142. words.set(value.parent.value, 1);
  143. }
  144. });
  145. let pubWords: string[] = [];
  146. words.forEach((value, key) => pubWords.push(key));
  147. const sentId = `${book}-${para}-${wordStart}-${wordEnd}`;
  148. store.dispatch(add({ sentId: sentId, words: pubWords }));
  149. }, [book, para, wordData, wordEnd, wordStart]);
  150. useEffect(() => {
  151. console.log("mode", mode);
  152. let currMode: ArticleMode | undefined;
  153. if (typeof mode !== "undefined") {
  154. currMode = mode;
  155. } else if (typeof newMode !== "undefined") {
  156. currMode = newMode;
  157. } else {
  158. currMode = undefined;
  159. }
  160. setDisplayMode(currMode);
  161. switch (currMode) {
  162. case "edit":
  163. if (typeof display === "undefined") {
  164. setWbwMode("block");
  165. }
  166. if (typeof fields === "undefined") {
  167. setFieldDisplay({
  168. meaning: true,
  169. factors: false,
  170. factorMeaning: false,
  171. case: false,
  172. });
  173. }
  174. break;
  175. case "wbw":
  176. if (typeof display === "undefined") {
  177. setWbwMode("block");
  178. }
  179. if (typeof fields === "undefined") {
  180. setFieldDisplay({
  181. meaning: true,
  182. factors: true,
  183. factorMeaning: true,
  184. case: true,
  185. });
  186. }
  187. break;
  188. }
  189. }, [newMode, mode]);
  190. useEffect(() => {
  191. if (typeof magic === "undefined") {
  192. return;
  193. }
  194. let _lang = GetUserSetting("setting.dict.lang", settings);
  195. const url = `/v2/wbwlookup`;
  196. console.log("magic dict url", url);
  197. if (typeof _lang === "object") {
  198. }
  199. post<IMagicDictRequest, IMagicDictResponse>(url, {
  200. book: book,
  201. para: para,
  202. word_start: wordStart,
  203. word_end: wordEnd,
  204. data: wordData,
  205. channel_id: channelId,
  206. lang: _lang?.toString().split(","),
  207. })
  208. .then((json) => {
  209. if (json.ok) {
  210. console.log("magic dict result", json.data);
  211. update(json.data);
  212. updateWbwAll(json.data);
  213. } else {
  214. console.error(json.message);
  215. }
  216. })
  217. .finally(() => {
  218. setMagic(undefined);
  219. setLoading(false);
  220. if (typeof onMagicDictDone !== "undefined") {
  221. onMagicDictDone();
  222. }
  223. });
  224. }, [magic]);
  225. const wbwToXml = (item: IWbw) => {
  226. return {
  227. pali: item.word,
  228. real: item.real,
  229. id: `${book}-${para}-` + item.sn.join("-"),
  230. type: item.type,
  231. gramma: item.grammar,
  232. mean: item.meaning
  233. ? {
  234. value: item.meaning.value,
  235. status: item.meaning?.status,
  236. }
  237. : undefined,
  238. org: item.factors,
  239. om: item.factorMeaning,
  240. case: item.case,
  241. parent: item.parent,
  242. pg: item.grammar2,
  243. parent2: item.parent2,
  244. rela: item.relation,
  245. lock: item.locked,
  246. note: item.note,
  247. bmt: item.bookMarkText,
  248. bmc: item.bookMarkColor,
  249. attachments: JSON.stringify(item.attachments),
  250. cf: item.confidence,
  251. };
  252. };
  253. const updateWbwAll = (wbwData: IWbw[]) => {
  254. let arrSn: number[] = [];
  255. wbwData.forEach((value) => {
  256. if (!arrSn.includes(value.sn[0])) {
  257. arrSn.push(value.sn[0]);
  258. }
  259. });
  260. const postParam: IWbwRequest = {
  261. book: book,
  262. para: para,
  263. channel_id: channelId,
  264. sn: wbwData[0].sn[0],
  265. data: arrSn.map((item) => {
  266. return {
  267. sn: item,
  268. words: wbwData.filter((value) => value.sn[0] === item).map(wbwToXml),
  269. };
  270. }),
  271. };
  272. post<IWbwRequest, IWbwUpdateResponse>(`/v2/wbw`, postParam).then((json) => {
  273. if (json.ok) {
  274. message.info(json.data.count + " updated");
  275. } else {
  276. message.error(json.message);
  277. }
  278. });
  279. };
  280. const saveWord = (wbwData: IWbw[], sn: number) => {
  281. if (channelType === "nissaya") {
  282. } else {
  283. const data = wbwData.filter((value) => value.sn[0] === sn);
  284. const postParam: IWbwRequest = {
  285. book: book,
  286. para: para,
  287. channel_id: channelId,
  288. sn: sn,
  289. data: [
  290. {
  291. sn: sn,
  292. words: data.map(wbwToXml),
  293. },
  294. ],
  295. };
  296. post<IWbwRequest, IWbwUpdateResponse>(`/v2/wbw`, postParam).then(
  297. (json) => {
  298. if (json.ok) {
  299. message.info(json.data.count + " updated");
  300. } else {
  301. message.error(json.message);
  302. }
  303. }
  304. );
  305. }
  306. };
  307. const wordSplit = (id: number, hyphen = "-") => {
  308. let factors = wordData[id]?.factors?.value;
  309. if (typeof factors === "string") {
  310. let sFm = wordData[id]?.factorMeaning?.value;
  311. if (typeof sFm === "undefined" || sFm === null) {
  312. sFm = new Array(factors.split("+")).fill([""]).join("+");
  313. }
  314. if (wordData[id].case?.value?.split("#")[0] === ".un.") {
  315. factors = `[+${factors}+]`;
  316. sFm = `+${sFm}+`;
  317. } else if (hyphen !== "") {
  318. factors = factors.replaceAll("+", `+${hyphen}+`);
  319. sFm = sFm.replaceAll("+", `+${hyphen}+`);
  320. }
  321. let fm = sFm.split("+");
  322. const children: IWbw[] | undefined = factors
  323. .split("+")
  324. .map((item, index) => {
  325. return {
  326. word: { value: item, status: 5 },
  327. real: {
  328. value: item
  329. .replaceAll("-", "")
  330. .replaceAll("[", "")
  331. .replaceAll("]", ""),
  332. status: 5,
  333. },
  334. meaning: { value: fm[index], status: 5 },
  335. book: wordData[id].book,
  336. para: wordData[id].para,
  337. sn: [...wordData[id].sn, index],
  338. confidence: 1,
  339. };
  340. });
  341. if (typeof children !== "undefined") {
  342. console.log("children", children);
  343. const newData: IWbw[] = [...wordData];
  344. newData.splice(id + 1, 0, ...children);
  345. console.log("new-data", newData);
  346. update(newData);
  347. saveWord(newData, wordData[id].sn[0]);
  348. }
  349. }
  350. };
  351. const wbwPublish = (wbwData: IWbw[]) => {
  352. let wordData: IDictRequest[] = [];
  353. wbwData.forEach((data) => {
  354. const [wordType, wordGrammar] = data.case?.value
  355. ? data.case?.value?.split("#")
  356. : ["", ""];
  357. wordData.push({
  358. word: data.real.value ? data.real.value : "",
  359. type: wordType,
  360. grammar: wordGrammar,
  361. mean: data.meaning?.value,
  362. parent: data.parent?.value,
  363. factors: data.factors?.value,
  364. factormean: data.factorMeaning?.value,
  365. confidence: data.confidence,
  366. });
  367. if (data.parent?.value && wordType !== "") {
  368. if (!wordType.includes("base") && wordType !== ".ind.") {
  369. wordData.push({
  370. word: data.parent.value,
  371. type: "." + wordType.replaceAll(".", "") + ":base.",
  372. grammar: wordGrammar,
  373. mean: data.meaning?.value,
  374. parent: data.parent2?.value ? data.parent2?.value : undefined,
  375. factors: data.factors?.value,
  376. factormean: data.factorMeaning?.value,
  377. confidence: data.confidence,
  378. });
  379. }
  380. }
  381. });
  382. post<IUserDictCreate, IDictResponse>("/v2/userdict", {
  383. view: "wbw",
  384. data: JSON.stringify(wordData),
  385. })
  386. .finally(() => {
  387. setLoading(false);
  388. })
  389. .then((json) => {
  390. if (json.ok) {
  391. message.success(
  392. "wbw " + intl.formatMessage({ id: "flashes.success" })
  393. );
  394. } else {
  395. message.error(json.message);
  396. }
  397. });
  398. };
  399. const wbwRender = (item: IWbw, id: number) => {
  400. return (
  401. <WbwWord
  402. data={item}
  403. key={id}
  404. mode={displayMode}
  405. display={wbwMode}
  406. fields={fieldDisplay}
  407. onChange={(e: IWbw, isPublish?: boolean) => {
  408. let newData = [...wordData];
  409. newData.forEach((value, index, array) => {
  410. //把新的数据更新到数组
  411. if (value.sn.join() === e.sn.join()) {
  412. console.log("found", e.sn);
  413. array[index] = e;
  414. }
  415. });
  416. if (e.sn.length > 1) {
  417. //把meaning 数据更新到 拆分前单词的factor meaning
  418. const factorMeaning = newData
  419. .filter((value) => {
  420. if (
  421. value.sn.length === e.sn.length &&
  422. e.sn.slice(0, e.sn.length - 1).join() ===
  423. value.sn.slice(0, e.sn.length - 1).join() &&
  424. value.real.value &&
  425. value.real.value.length > 0
  426. ) {
  427. return true;
  428. } else {
  429. return false;
  430. }
  431. })
  432. .map((item) => item.meaning?.value)
  433. .join("+");
  434. console.log("fm", factorMeaning);
  435. newData.forEach((value, index, array) => {
  436. //把新的数据更新到数组
  437. if (value.sn.join() === e.sn.slice(0, e.sn.length - 1).join()) {
  438. console.log("found", value.sn);
  439. array[index].factorMeaning = {
  440. value: factorMeaning,
  441. status: 5,
  442. };
  443. if (array[index].meaning?.status !== WbwStatus.manual) {
  444. array[index].meaning = {
  445. value: factorMeaning.replaceAll("+", " "),
  446. status: 5,
  447. };
  448. }
  449. }
  450. });
  451. }
  452. update(newData);
  453. saveWord(newData, e.sn[0]);
  454. if (isPublish) {
  455. wbwPublish([e]);
  456. }
  457. }}
  458. onSplit={() => {
  459. const newData: IWbw[] = JSON.parse(JSON.stringify(wordData));
  460. if (
  461. id < wordData.length - 1 &&
  462. wordData[id + 1].sn.join("-").indexOf(wordData[id].sn.join("-")) ===
  463. 0
  464. ) {
  465. //合并
  466. console.log("合并");
  467. const compactData = newData.filter((value, index) => {
  468. if (
  469. index !== id &&
  470. value.sn.join("-").indexOf(wordData[id].sn.join("-")) === 0
  471. ) {
  472. return false;
  473. } else {
  474. return true;
  475. }
  476. });
  477. update(compactData);
  478. saveWord(compactData, wordData[id].sn[0]);
  479. } else {
  480. //拆开
  481. console.log("拆开");
  482. wordSplit(id);
  483. }
  484. }}
  485. />
  486. );
  487. };
  488. return (
  489. <div className={`layout-${layoutDirection}`}>
  490. <Dropdown
  491. menu={{
  492. items: [
  493. {
  494. key: "magic-dict-current",
  495. label: "神奇字典",
  496. },
  497. {
  498. key: "wbw-dict-publish-all",
  499. label: "发布全部单词",
  500. },
  501. {
  502. type: "divider",
  503. },
  504. {
  505. key: "copy-text",
  506. label: intl.formatMessage({
  507. id: "buttons.copy.pali.text",
  508. }),
  509. },
  510. ],
  511. onClick: ({ key }) => {
  512. console.log(`Click on item ${key}`);
  513. switch (key) {
  514. case "magic-dict-current":
  515. setLoading(true);
  516. setMagic("curr");
  517. break;
  518. case "wbw-dict-publish-all":
  519. wbwPublish(wordData);
  520. break;
  521. case "copy-text":
  522. const paliText = wordData
  523. .filter((value) => value.type?.value !== ".ctl.")
  524. .map((item) => item.word.value)
  525. .join(" ");
  526. navigator.clipboard.writeText(paliText).then(() => {
  527. message.success("已经拷贝到剪贴板");
  528. });
  529. break;
  530. }
  531. },
  532. }}
  533. placement="bottomLeft"
  534. >
  535. <Button
  536. loading={loading}
  537. onClick={(e) => e.preventDefault()}
  538. icon={<MoreOutlined />}
  539. size="small"
  540. type="text"
  541. style={{ backgroundColor: "lightblue", opacity: 0.3 }}
  542. />
  543. </Dropdown>
  544. {layoutDirection === "h" ? (
  545. wordData.map((item, id) => {
  546. return wbwRender(item, id);
  547. })
  548. ) : (
  549. <Tree
  550. selectable={true}
  551. blockNode
  552. treeData={wordData
  553. .filter((value) => value.sn.length === 1)
  554. .map((item, id) => {
  555. const children = wordData.filter(
  556. (value) =>
  557. value.sn.length === 2 &&
  558. value.sn.slice(0, 1).join() === wordData[id].sn.join()
  559. );
  560. return {
  561. title: wbwRender(item, id),
  562. key: item.sn.join(),
  563. isLeaf: !item.factors?.value?.includes("+"),
  564. children:
  565. children.length > 0
  566. ? children.map((item, id) => {
  567. return {
  568. title: wbwRender(item, id),
  569. key: item.sn.join(),
  570. isLeaf: true,
  571. };
  572. })
  573. : undefined,
  574. };
  575. })}
  576. loadData={({ key, children }: any) =>
  577. new Promise<void>((resolve) => {
  578. console.log("key", key, children);
  579. if (children) {
  580. resolve();
  581. return;
  582. }
  583. wordSplit(key, "");
  584. resolve();
  585. })
  586. }
  587. />
  588. )}
  589. </div>
  590. );
  591. };
  592. interface IWidgetWbwSent {
  593. props: string;
  594. }
  595. const WbwSentWidget = ({ props }: IWidgetWbwSent) => {
  596. const prop = JSON.parse(atob(props)) as IWidget;
  597. return <WbwSentCtl {...prop} />;
  598. };
  599. export default WbwSentWidget;