Browse Source

:sparkles: 版本间译文复制

visuddhinanda 3 years ago
parent
commit
588be3f964

+ 208 - 0
dashboard/src/components/channel/ChannelSentDiff.tsx

@@ -0,0 +1,208 @@
+import { Button, Col, List, message, Row, Space, Typography } from "antd";
+import { diffWords } from "diff";
+import { useEffect, useState } from "react";
+import { SwapRightOutlined } from "@ant-design/icons";
+
+import { post } from "../../request";
+import {
+  ISentenceDiffData,
+  ISentenceDiffRequest,
+  ISentenceDiffResponse,
+  ISentenceNewMultiResponse,
+  ISentenceNewRequest,
+} from "../api/Corpus";
+import { IChannel } from "./Channel";
+
+const { Text } = Typography;
+
+interface IDiffData {
+  id: string;
+  srcContent?: string;
+  destContent?: string | JSX.Element;
+}
+interface ISentenceData {
+  book: number;
+  paragraph: number;
+  wordStart: number;
+  wordEnd: number;
+  content: string;
+}
+
+interface IWidget {
+  srcChannel?: IChannel;
+  destChannel?: IChannel;
+  sentences?: string[];
+  goPrev?: Function;
+  onSubmit?: Function;
+}
+const Widget = ({
+  srcChannel,
+  destChannel,
+  sentences,
+  goPrev,
+  onSubmit,
+}: IWidget) => {
+  const [srcApiData, setSrcApiData] = useState<ISentenceDiffData[]>([]);
+  const [srcData, setSrcData] = useState<ISentenceData[]>([]);
+  const [destData, setDestData] = useState<ISentenceData[]>([]);
+  const [diffData, setDiffData] = useState<IDiffData[]>();
+  const [loading, setLoading] = useState(false);
+
+  useEffect(() => {
+    if (sentences && srcChannel) {
+      post<ISentenceDiffRequest, ISentenceDiffResponse>(`/v2/sent-in-channel`, {
+        sentences: sentences,
+        channel: srcChannel.id,
+      }).then((json) => {
+        if (json.ok) {
+          console.log("src", srcChannel.id, json.data.rows);
+          setSrcApiData(json.data.rows);
+          const data = json.data.rows.map((item) => {
+            return {
+              book: item.book_id,
+              paragraph: item.paragraph,
+              wordStart: item.word_start,
+              wordEnd: item.word_end,
+              content: item.content,
+            };
+          });
+          setSrcData(data);
+        }
+      });
+    }
+  }, [srcChannel, sentences]);
+
+  useEffect(() => {
+    if (sentences && destChannel) {
+      post<ISentenceDiffRequest, ISentenceDiffResponse>(`/v2/sent-in-channel`, {
+        sentences: sentences,
+        channel: destChannel.id,
+      }).then((json) => {
+        if (json.ok) {
+          console.log("dest", destChannel.id, json.data.rows);
+          const data = json.data.rows.map((item) => {
+            return {
+              book: item.book_id,
+              paragraph: item.paragraph,
+              wordStart: item.word_start,
+              wordEnd: item.word_end,
+              content: item.content,
+            };
+          });
+          setDestData(data);
+        }
+      });
+    }
+  }, [destChannel, sentences]);
+
+  useEffect(() => {
+    const diffList = sentences?.map((item) => {
+      const id = item.split("-");
+      const srcContent = srcData.find(
+        (element) =>
+          element.book === parseInt(id[0]) &&
+          element.paragraph === parseInt(id[1]) &&
+          element.wordStart === parseInt(id[2]) &&
+          element.wordEnd === parseInt(id[3])
+      );
+
+      const destContent = destData.find(
+        (element) =>
+          element.book === parseInt(id[0]) &&
+          element.paragraph === parseInt(id[1]) &&
+          element.wordStart === parseInt(id[2]) &&
+          element.wordEnd === parseInt(id[3])
+      );
+      const diff = diffWords(
+        destContent ? destContent.content : "",
+        srcContent ? srcContent.content : ""
+      );
+      const diffResult = diff.map((item) => {
+        return (
+          <Text
+            type={
+              item.added ? "success" : item.removed ? "danger" : "secondary"
+            }
+            delete={item.removed ? true : undefined}
+          >
+            {item.value}
+          </Text>
+        );
+      });
+      return {
+        id: item,
+        srcContent: srcContent?.content,
+        destContent: <>{diffResult}</>,
+      };
+    });
+    setDiffData(diffList);
+  }, [destData, sentences, srcData]);
+
+  return (
+    <div>
+      <div style={{ display: "flex", justifyContent: "space-between" }}>
+        <Button
+          onClick={() => {
+            if (typeof goPrev !== "undefined") {
+              goPrev();
+            }
+          }}
+        >
+          上一步
+        </Button>
+        <Space>
+          {srcChannel?.name}
+          <SwapRightOutlined />
+          {destChannel?.name}
+        </Space>
+        <Button
+          type="primary"
+          loading={loading}
+          onClick={() => {
+            setLoading(true);
+            post<ISentenceNewRequest, ISentenceNewMultiResponse>(
+              `/v2/sentence`,
+              {
+                sentences: srcApiData,
+                channel: destChannel?.id,
+              }
+            )
+              .then((json) => {
+                if (json.ok) {
+                  if (typeof onSubmit !== "undefined") {
+                    onSubmit();
+                  }
+                } else {
+                  message.error(json.message);
+                }
+              })
+              .catch((e) => {
+                console.log(e);
+              })
+              .finally(() => {
+                setLoading(false);
+              });
+          }}
+        >
+          开始复制
+        </Button>
+      </div>
+      <List
+        header={<div>Header</div>}
+        footer={<div>Footer</div>}
+        bordered
+        dataSource={diffData}
+        renderItem={(item) => (
+          <List.Item>
+            <Row style={{ width: "100%" }}>
+              <Col span={12}>{item.srcContent}</Col>
+              <Col span={12}>{item.destContent}</Col>
+            </Row>
+          </List.Item>
+        )}
+      />
+    </div>
+  );
+};
+
+export default Widget;

+ 53 - 0
dashboard/src/components/channel/CopyToModal.tsx

@@ -0,0 +1,53 @@
+import { useState } from "react";
+import { Modal } from "antd";
+
+import CopyToStep from "./CopyToStep";
+import { IChannel } from "./Channel";
+
+interface IWidget {
+  trigger: JSX.Element | string;
+  channel?: IChannel;
+}
+const Widget = ({ trigger, channel }: IWidget) => {
+  const [isModalOpen, setIsModalOpen] = useState(false);
+  const [initStep, setInitStep] = useState(0);
+
+  const showModal = () => {
+    setIsModalOpen(true);
+    setInitStep(0);
+  };
+
+  const handleOk = () => {
+    setIsModalOpen(false);
+  };
+
+  const handleCancel = () => {
+    setIsModalOpen(false);
+  };
+
+  return (
+    <>
+      <span onClick={showModal}>{trigger}</span>
+      <Modal
+        width={"80%"}
+        style={{ maxWidth: 1000 }}
+        title="版本间复制"
+        open={isModalOpen}
+        onOk={handleOk}
+        onCancel={handleCancel}
+        footer={[]}
+      >
+        <CopyToStep
+          initStep={initStep}
+          channel={channel}
+          onClose={() => {
+            setIsModalOpen(false);
+            Modal.destroyAll();
+          }}
+        />
+      </Modal>
+    </>
+  );
+};
+
+export default Widget;

+ 40 - 0
dashboard/src/components/channel/CopyToResult.tsx

@@ -0,0 +1,40 @@
+import { Button, Result } from "antd";
+
+interface IWidget {
+  onClose?: Function;
+  onInit?: Function;
+}
+const Widget = ({ onClose, onInit }: IWidget) => {
+  return (
+    <Result
+      status="success"
+      title="Successfully Copied!"
+      subTitle="Sentence: 23 , Words: 143"
+      extra={[
+        <Button
+          key="init"
+          onClick={() => {
+            if (typeof onInit !== "undefined") {
+              onInit();
+            }
+          }}
+        >
+          从新开始
+        </Button>,
+        <Button
+          key="close"
+          type="primary"
+          onClick={() => {
+            if (typeof onClose !== "undefined") {
+              onClose();
+            }
+          }}
+        >
+          关闭
+        </Button>,
+      ]}
+    />
+  );
+};
+
+export default Widget;

+ 117 - 0
dashboard/src/components/channel/CopyToStep.tsx

@@ -0,0 +1,117 @@
+import { Steps } from "antd";
+import { useEffect, useState } from "react";
+
+import { ArticleType } from "../article/Article";
+import { IChannel } from "./Channel";
+import ChannelPickerTable from "./ChannelPickerTable";
+import { useAppSelector } from "../../hooks";
+import { sentenceList } from "../../reducers/sentence";
+import ChannelSentDiff from "./ChannelSentDiff";
+import CopyToResult from "./CopyToResult";
+
+interface IWidget {
+  initStep?: number;
+  channel?: IChannel;
+  type?: ArticleType;
+  articleId?: string;
+  sentence?: string[];
+  stepChange?: Function;
+  onClose?: Function;
+}
+const Widget = ({
+  initStep = 0,
+  channel,
+  type,
+  articleId,
+  sentence,
+  stepChange,
+  onClose,
+}: IWidget) => {
+  const [current, setCurrent] = useState(0);
+  const [destChannel, setDestChannel] = useState<IChannel>();
+  const [copyPercent, setCopyPercent] = useState<number>();
+
+  const sentences = useAppSelector(sentenceList);
+
+  useEffect(() => {
+    setCurrent(initStep);
+  }, [initStep]);
+
+  const next = () => {
+    setCurrent(current + 1);
+  };
+
+  const prev = () => {
+    setCurrent(current - 1);
+  };
+  const steps = [
+    {
+      title: "选择目标版本",
+      key: "channel",
+      content: (
+        <ChannelPickerTable
+          type="editable"
+          multiSelect={false}
+          onSelect={(e: IChannel) => {
+            console.log(e);
+            setDestChannel(e);
+            next();
+          }}
+        />
+      ),
+    },
+    {
+      title: "文本比对",
+      key: "diff",
+      content: (
+        <div>
+          <ChannelSentDiff
+            srcChannel={channel}
+            destChannel={destChannel}
+            sentences={sentences}
+            goPrev={() => {
+              prev();
+            }}
+            onSubmit={() => {
+              next();
+            }}
+          />
+        </div>
+      ),
+    },
+    {
+      title: "完成",
+      key: "finish",
+      content: (
+        <CopyToResult
+          onClose={() => {
+            if (typeof onClose !== "undefined") {
+              onClose();
+            }
+          }}
+          onInit={() => {
+            setCurrent(0);
+          }}
+        />
+      ),
+    },
+  ];
+  const items = steps.map((item) => ({ key: item.key, title: item.title }));
+
+  const contentStyle: React.CSSProperties = {
+    backgroundColor: "#fafafa",
+    borderRadius: 5,
+    border: `1px dashed gray`,
+    marginTop: 16,
+    height: 400,
+    overflowY: "scroll",
+  };
+  return (
+    <div>
+      <Steps current={current} items={items} percent={copyPercent} />
+      <div style={contentStyle}>{steps[current].content}</div>
+    </div>
+  );
+};
+
+export default Widget;