Browse Source

Merge pull request #1036 from visuddhinanda/agile

:art: 调整逐词解析样式与原来的软件一致
visuddhinanda 3 years ago
parent
commit
cdbb2d634f

+ 16 - 1
dashboard/src/assets/icon/index.tsx

@@ -1,6 +1,5 @@
 import Icon from "@ant-design/icons";
 import type { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon";
-import { suggestion } from "../../reducers/suggestion";
 
 const DictSvg = () => (
   <svg
@@ -53,6 +52,18 @@ const LockSvg = () => (
     <path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2zM5 8h6a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z" />
   </svg>
 );
+
+const UnLockSvg = () => (
+  <svg
+    xmlns="http://www.w3.org/2000/svg"
+    width="1em"
+    height="1em"
+    fill="currentColor"
+    viewBox="0 0 16 16"
+  >
+    <path d="M11 1a2 2 0 0 0-2 2v4a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h5V3a3 3 0 0 1 6 0v4a.5.5 0 0 1-1 0V3a2 2 0 0 0-2-2zM3 8a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V9a1 1 0 0 0-1-1H3z" />
+  </svg>
+);
 export const DictIcon = (props: Partial<CustomIconComponentProps>) => (
   <Icon component={DictSvg} {...props} />
 );
@@ -67,3 +78,7 @@ export const SuggestionIcon = (props: Partial<CustomIconComponentProps>) => (
 export const LockIcon = (props: Partial<CustomIconComponentProps>) => (
   <Icon component={LockSvg} {...props} />
 );
+
+export const UnLockIcon = (props: Partial<CustomIconComponentProps>) => (
+  <Icon component={UnLockSvg} {...props} />
+);

+ 2 - 25
dashboard/src/components/nut/Home.tsx

@@ -2,42 +2,19 @@ import ReactMarkdown from "react-markdown";
 import code_png from "../../assets/nut/code.png";
 import ChannelPicker from "../channel/ChannelPicker";
 import MdView from "../template/MdView";
-import { IWbw } from "../template/Wbw/WbwWord";
-import WbwSent from "../template/WbwSent";
 
 import MarkdownForm from "./MarkdownForm";
 import MarkdownShow from "./MarkdownShow";
 import FontBox from "./FontBox";
 import DemoForm from "./Form";
+import WbwTest from "./WbwTest";
 
 const Widget = () => {
-  let wbwData: IWbw[] = [];
-  const valueMake = (value: string) => {
-    return { value: value, status: 3 };
-  };
-  for (let index = 0; index < 20; index++) {
-    wbwData.push({
-      word: valueMake("Word" + index),
-      real: valueMake("word" + index),
-      meaning: { value: ["意思" + index], status: 3 },
-      factors: valueMake("word+word"),
-      factorMeaning: valueMake("mean+mean"),
-      type: valueMake(".n."),
-      grammar: valueMake(".m.$.sg.$.nom."),
-      confidence: 1,
-    });
-  }
   return (
     <div>
       <h1>Home</h1>
       <h2>wbw</h2>
-      <div style={{ width: 700 }}>
-        <WbwSent
-          data={wbwData}
-          display="inline"
-          fields={{ meaning: true, factors: false, case: false }}
-        />
-      </div>
+      <WbwTest />
       <h2>channel picker</h2>
       <div style={{ width: 1000 }}>
         <ChannelPicker type="chapter" articleId="168-915" />

+ 41 - 0
dashboard/src/components/nut/WbwTest.tsx

@@ -0,0 +1,41 @@
+import { IWbw } from "../template/Wbw/WbwWord";
+import WbwSent from "../template/WbwSent";
+
+const Widget = () => {
+  let wbwData: IWbw[] = [];
+  const valueMake = (value: string) => {
+    return { value: value, status: 3 };
+  };
+  const valueMake2 = (value: string[]) => {
+    return { value: value, status: 3 };
+  };
+  for (let index = 0; index < 20; index++) {
+    wbwData.push({
+      word: valueMake("Word" + index),
+      real: valueMake("word" + index),
+      meaning: { value: ["意思" + index], status: 3 },
+      factors: valueMake("word+word"),
+      factorMeaning: valueMake("mean+mean"),
+      type: valueMake(".n."),
+      grammar: valueMake(".m.$.sg.$.nom."),
+      case: valueMake2(["n", "m", "sg", "nom"]),
+      confidence: 1,
+    });
+  }
+  return (
+    <div style={{ width: 700 }}>
+      <WbwSent
+        data={wbwData}
+        display="block"
+        fields={{
+          meaning: true,
+          factors: true,
+          factorMeaning: true,
+          case: true,
+        }}
+      />
+    </div>
+  );
+};
+
+export default Widget;

+ 10 - 7
dashboard/src/components/studio/SelectCase.tsx

@@ -8,8 +8,9 @@ interface CascaderOption {
 }
 interface IWidget {
   defaultValue?: string[];
+  onCaseChange?: Function;
 }
-const Widget = ({ defaultValue }: IWidget) => {
+const Widget = ({ defaultValue, onCaseChange }: IWidget) => {
   const intl = useIntl();
 
   const case8 = [
@@ -197,17 +198,17 @@ const Widget = ({ defaultValue }: IWidget) => {
   ];
   const options: CascaderOption[] = [
     {
-      value: ".n.",
+      value: "n",
       label: intl.formatMessage({ id: "dict.fields.type.n.label" }),
       children: case3,
     },
     {
-      value: ".ti.",
+      value: "ti",
       label: intl.formatMessage({ id: "dict.fields.type.ti.label" }),
       children: case3,
     },
     {
-      value: ".v.",
+      value: "v",
       label: intl.formatMessage({ id: "dict.fields.type.v.label" }),
       children: caseVerb1,
     },
@@ -226,9 +227,11 @@ const Widget = ({ defaultValue }: IWidget) => {
       children: case3,
     },
   ];
-  type SingleValueType = (string | number)[];
-  const onChange = (value: SingleValueType) => {
-    console.log(value);
+  const onChange = (value: (string | number)[]) => {
+    console.log("case changed", value);
+    if (typeof onCaseChange !== "undefined") {
+      onCaseChange(value);
+    }
   };
 
   return (

+ 21 - 2
dashboard/src/components/template/Wbw/WbwCase.tsx

@@ -1,12 +1,31 @@
+import { useIntl } from "react-intl";
+import { Typography } from "antd";
+
 import { IWbw } from "./WbwWord";
+import "./wbw.css"; // 告诉 umi 编译这个 css
+
+const { Text } = Typography;
 
 interface IWidget {
   data: IWbw;
 }
+
 const Widget = ({ data }: IWidget) => {
+  const intl = useIntl();
+  console.log("case", data.case?.value);
   return (
-    <div>
-      {data.type?.value}-{data.grammar?.value}
+    <div className="wbw_word_item" style={{ display: "flex" }}>
+      <Text type="secondary">
+        <div>
+          {data.case?.value.map((item, id) => {
+            return (
+              <span key={id} className="case">
+                {intl.formatMessage({ id: `dict.fields.type.${item}.label` })}
+              </span>
+            );
+          })}
+        </div>
+      </Text>
     </div>
   );
 };

+ 54 - 25
dashboard/src/components/template/Wbw/WbwDetail.tsx

@@ -1,48 +1,68 @@
 import { useState } from "react";
 import { useIntl } from "react-intl";
-import { Dropdown, Tabs, Divider, Button } from "antd";
+import { Dropdown, Tabs, Divider, Button, Switch, Rate } from "antd";
 import type { MenuProps } from "antd";
 import { SaveOutlined } from "@ant-design/icons";
 
-import { IWbw, IWbwField } from "./WbwWord";
-import WbwDetailBasic, { IWordBasic } from "./WbwDetailBasic";
+import { IWbw, IWbwField, TFieldName } from "./WbwWord";
+import WbwDetailBasic from "./WbwDetailBasic";
 import WbwDetailBookMark from "./WbwDetailBookMark";
 import WbwDetailNote from "./WbwDetailNote";
 import WbwDetailAdvance from "./WbwDetailAdvance";
+import { LockIcon, UnLockIcon } from "../../../assets/icon";
 
 interface IWidget {
   data: IWbw;
   onClose?: Function;
-  onChange?: Function;
   onSave?: Function;
 }
-const Widget = ({ data, onClose, onChange, onSave }: IWidget) => {
+const Widget = ({ data, onClose, onSave }: IWidget) => {
   const intl = useIntl();
-  const [basicSubmit, setBasicSubmit] = useState(false);
   const [currWbwData, setCurrWbwData] = useState(data);
-  const fieldChanged = (value: IWbwField) => {
+  function fieldChanged(field: TFieldName, value: string) {
     let mData = currWbwData;
-    switch (value.field) {
+    switch (field) {
       case "note":
-        mData.note = { value: value.value, status: 5 };
+        mData.note = { value: value, status: 5 };
         break;
       case "bookMarkColor":
-        mData.bookMarkColor = { value: value.value, status: 5 };
+        mData.bookMarkColor = { value: parseInt(value), status: 5 };
         break;
       case "bookMarkText":
-        mData.bookMarkText = { value: value.value, status: 5 };
+        mData.bookMarkText = { value: value, status: 5 };
         break;
       case "word":
-        mData.word = { value: value.value, status: 5 };
+        mData.word = { value: value, status: 5 };
         break;
       case "real":
-        mData.real = { value: value.value, status: 5 };
+        mData.real = { value: value, status: 5 };
+        break;
+      case "meaning":
+        mData.meaning = { value: value.split("$"), status: 5 };
+        break;
+      case "factors":
+        mData.factors = { value: value, status: 5 };
+        break;
+      case "factorMeaning":
+        mData.factorMeaning = { value: value, status: 5 };
+        break;
+      case "parent":
+        mData.parent = { value: value, status: 5 };
+        break;
+      case "case":
+        mData.case = { value: value.split("$"), status: 5 };
+        break;
+      case "confidence":
+        mData.confidence = parseFloat(value);
+        break;
+      case "locked":
+        mData.locked = JSON.parse(value);
         break;
       default:
         break;
     }
     setCurrWbwData(mData);
-  };
+  }
   const onMenuClick: MenuProps["onClick"] = (e) => {
     console.log("click", e);
   };
@@ -70,13 +90,9 @@ const Widget = ({ data, onClose, onChange, onSave }: IWidget) => {
               <div>
                 <WbwDetailBasic
                   data={data}
-                  submit={basicSubmit}
-                  onSubmit={(e: IWordBasic) => {
+                  onChange={(e: IWbwField) => {
                     console.log(e);
-                    if (typeof onChange !== "undefined") {
-                      onChange(currWbwData);
-                    }
-                    setBasicSubmit(false);
+                    fieldChanged(e.field, e.value);
                   }}
                 />
               </div>
@@ -89,7 +105,7 @@ const Widget = ({ data, onClose, onChange, onSave }: IWidget) => {
               <WbwDetailBookMark
                 data={data}
                 onChange={(e: IWbwField) => {
-                  fieldChanged(e);
+                  fieldChanged(e.field, e.value);
                 }}
               />
             ),
@@ -101,7 +117,7 @@ const Widget = ({ data, onClose, onChange, onSave }: IWidget) => {
               <WbwDetailNote
                 data={data}
                 onChange={(e: IWbwField) => {
-                  fieldChanged(e);
+                  fieldChanged(e.field, e.value);
                 }}
               />
             ),
@@ -114,7 +130,7 @@ const Widget = ({ data, onClose, onChange, onSave }: IWidget) => {
                 <WbwDetailAdvance
                   data={currWbwData}
                   onChange={(e: IWbwField) => {
-                    fieldChanged(e);
+                    fieldChanged(e.field, e.value);
                   }}
                 />
               </div>
@@ -122,7 +138,21 @@ const Widget = ({ data, onClose, onChange, onSave }: IWidget) => {
           },
         ]}
       />
-      <Divider style={{ margin: "8px 0" }}></Divider>
+      <Divider style={{ margin: "4px 0" }}></Divider>
+      <div style={{ display: "flex", justifyContent: "space-between" }}>
+        <Switch
+          checkedChildren={<LockIcon />}
+          unCheckedChildren={<UnLockIcon />}
+        />
+        <Rate
+          allowHalf
+          defaultValue={5}
+          onChange={(value: number) => {
+            fieldChanged("confidence", (value / 5).toString());
+          }}
+        />
+      </div>
+      <Divider style={{ margin: "4px 0" }}></Divider>
       <div style={{ display: "flex", justifyContent: "space-between" }}>
         <div>
           <Button
@@ -141,7 +171,6 @@ const Widget = ({ data, onClose, onChange, onSave }: IWidget) => {
           type="primary"
           menu={{ items, onClick: onMenuClick }}
           onClick={() => {
-            setBasicSubmit(true);
             console.log("data", currWbwData);
             if (typeof onSave !== "undefined") {
               onSave(currWbwData);

+ 0 - 5
dashboard/src/components/template/Wbw/WbwDetailAdvance.tsx

@@ -1,13 +1,8 @@
-import { useState, useEffect } from "react";
 import { useIntl } from "react-intl";
-import type { RadioChangeEvent } from "antd";
-import { Radio } from "antd";
 import { Input } from "antd";
 
 import { IWbw } from "./WbwWord";
 
-const { TextArea } = Input;
-
 interface IWidget {
   data: IWbw;
   onChange?: Function;

+ 148 - 156
dashboard/src/components/template/Wbw/WbwDetailBasic.tsx

@@ -1,23 +1,15 @@
-import { useRef, useState, useEffect } from "react";
+import { useState } from "react";
 import { useIntl } from "react-intl";
-import {
-  ProForm,
-  ProFormText,
-  ProFormSelect,
-  ProFormInstance,
-} from "@ant-design/pro-components";
-import { Divider, message, Form, Select } from "antd";
-import { Collapse, Tag } from "antd";
+import { Divider, Form, Select, Input } from "antd";
+import { Collapse } from "antd";
 
 import SelectCase from "../../studio/SelectCase";
 import { IWbw } from "./WbwWord";
+import WbwMeaningSelect from "./WbwMeaningSelect";
 
+const { Option } = Select;
 const { Panel } = Collapse;
 
-const handleChange = (value: string | string[]) => {
-  console.log(`Selected: ${value}`);
-};
-
 export interface IWordBasic {
   meaning?: string[];
   case?: string;
@@ -27,169 +19,169 @@ export interface IWordBasic {
 }
 interface IWidget {
   data: IWbw;
-  submit?: boolean;
-  onSubmit?: Function;
+  onChange?: Function;
 }
-const Widget = ({ data, submit = false, onSubmit }: IWidget) => {
-  const formRef = useRef<ProFormInstance>();
+const Widget = ({ data, onChange }: IWidget) => {
+  const [form] = Form.useForm();
   const intl = useIntl();
   const [items, setItems] = useState(["jack", "lucy"]);
+
   const formItemLayout = {
     labelCol: { span: 4 },
-    wrapperCol: { span: 14 },
+    wrapperCol: { span: 20 },
   };
-  useEffect(() => {
-    if (submit) {
-      if (typeof onSubmit !== "undefined") {
-        onSubmit(formRef.current?.getFieldFormatValue?.());
-      }
-    }
-  }, [submit]);
+
   return (
-    <ProForm<IWordBasic>
-      formRef={formRef}
-      {...formItemLayout}
-      layout="horizontal"
-      submitter={{
-        render: (props, doms) => {
-          return <></>;
-        },
-      }}
-      onFinish={async (values) => {
-        console.log(values);
-        message.success("提交成功");
-      }}
-      params={{}}
-      request={async () => {
-        return {
+    <>
+      <Form
+        {...formItemLayout}
+        name="basic"
+        form={form}
+        initialValues={{
           meaning: data.meaning?.value,
           factors: data.factors?.value,
+          factorMeaning: data.factorMeaning?.value,
           parent: data.parent?.value,
-          case: data.type?.value,
-        };
-      }}
-    >
-      <Select
-        mode="tags"
-        placeholder="Please select"
-        defaultValue={data.meaning?.value}
-        onChange={handleChange}
-        style={{ width: "100%" }}
-        options={items.map((item) => ({ label: item, value: item }))}
-      />
-      <ProFormSelect
-        width="md"
-        name="meaning"
-        dependencies={["meaning"]}
-        label={intl.formatMessage({ id: "forms.fields.meaning.label" })}
-        tooltip={intl.formatMessage({ id: "forms.fields.meaning.tooltip" })}
-        placeholder={intl.formatMessage({ id: "forms.fields.meaning.label" })}
-        options={items.map((item) => ({ label: item, value: item }))}
-        fieldProps={{
-          mode: "tags",
-          optionItemRender(item) {
-            return item.label + " - " + item.value;
-          },
-          dropdownRender(menu) {
-            return (
+        }}
+      >
+        <Form.Item
+          name="meaning"
+          label={intl.formatMessage({ id: "forms.fields.meaning.label" })}
+          tooltip={intl.formatMessage({ id: "forms.fields.meaning.tooltip" })}
+        >
+          <Select
+            allowClear
+            mode="tags"
+            onChange={(value: string | string[]) => {
+              console.log(`Selected: ${value}`);
+              if (typeof onChange !== "undefined") {
+                if (typeof value === "string") {
+                  onChange({ field: "meaning", value: value });
+                } else {
+                  onChange({ field: "meaning", value: value.join("$") });
+                }
+              }
+            }}
+            style={{ width: "100%" }}
+            placeholder={intl.formatMessage({
+              id: "forms.fields.meaning.label",
+            })}
+            options={items.map((item) => ({ label: item, value: item }))}
+            dropdownRender={(menu) => (
               <>
+                {" "}
                 {menu}
                 <Divider style={{ margin: "8px 0" }}>更多</Divider>
-                <Collapse defaultActiveKey={["1"]}>
-                  <Panel
-                    header="This is panel header 1"
-                    style={{ padding: 0 }}
-                    key="1"
-                  >
-                    <Tag
-                      onClick={(e: React.MouseEvent<HTMLAnchorElement>) => {
-                        e.preventDefault();
-
-                        const it =
-                          formRef.current?.getFieldValue("meaning") || [];
-                        console.log(it);
-                        if (!items.includes("hello")) {
-                          setItems([...items, "hello"]);
-                        }
-                        if (!it.includes("hello")) {
-                          it.push("hello");
-                          console.log("it push", it);
-                        }
-                        formRef.current?.setFieldsValue({ meaning: it });
-                      }}
-                    >
-                      意思
-                    </Tag>
-                  </Panel>
-                  <Panel header="This is panel header 2" key="2">
-                    <Tag>意思</Tag>
-                  </Panel>
-                  <Panel header="This is panel header 3" key="3">
-                    <Tag>意思</Tag>
-                  </Panel>
-                </Collapse>
+                <WbwMeaningSelect
+                  data={data}
+                  onSelect={(meaning: string) => {
+                    const currMeanings = form.getFieldValue("meaning") || [];
+                    console.log(meaning);
+                    if (!items.includes(meaning)) {
+                      setItems([...items, meaning]);
+                    }
+                    if (!currMeanings.includes(meaning)) {
+                      currMeanings.push(meaning);
+                      console.log("it push", meaning);
+                    }
+                    form.setFieldsValue({
+                      meaning: currMeanings,
+                    });
+                  }}
+                />
               </>
-            );
-          },
-        }}
-      />
-      <ProFormText
-        width="md"
-        name="factors"
-        label={intl.formatMessage({ id: "forms.fields.factors.label" })}
-        tooltip={intl.formatMessage({ id: "forms.fields.factors.tooltip" })}
-        placeholder={intl.formatMessage({ id: "forms.fields.factors.label" })}
-      />
-      <Form.Item
-        label={intl.formatMessage({ id: "forms.fields.case.label" })}
-        tooltip={intl.formatMessage({ id: "forms.fields.case.tooltip" })}
-        name="case"
-      >
-        <SelectCase />
-      </Form.Item>
-      <ProFormText
-        name="parent"
-        width="md"
-        label={intl.formatMessage({ id: "forms.fields.parent.label" })}
-        tooltip={intl.formatMessage({ id: "forms.fields.parent.tooltip" })}
-        placeholder={intl.formatMessage({ id: "forms.fields.parent.label" })}
-      />
-      <Collapse bordered={false}>
-        <Panel header="词源" key="1">
-          <ProFormText
-            name="parent1"
-            width="md"
-            label={intl.formatMessage({ id: "forms.fields.parent.label" })}
-            tooltip={intl.formatMessage({ id: "forms.fields.parent.tooltip" })}
+            )}
+          />
+        </Form.Item>
+        <Form.Item
+          name="factors"
+          label={intl.formatMessage({ id: "forms.fields.factors.label" })}
+          tooltip={intl.formatMessage({ id: "forms.fields.factors.tooltip" })}
+        >
+          <Input
+            allowClear
             placeholder={intl.formatMessage({
-              id: "forms.fields.parent.label",
+              id: "forms.fields.factors.label",
             })}
           />
-          <ProFormSelect
-            width="md"
-            name="grammar"
-            label={intl.formatMessage({ id: "forms.fields.meaning.label" })}
-            tooltip={intl.formatMessage({ id: "forms.fields.meaning.tooltip" })}
+        </Form.Item>
+        <Form.Item
+          label={intl.formatMessage({ id: "forms.fields.case.label" })}
+          tooltip={intl.formatMessage({ id: "forms.fields.case.tooltip" })}
+          name="case"
+        >
+          <SelectCase
+            onCaseChange={(value: (string | number)[]) => {
+              if (typeof onChange !== "undefined") {
+                onChange({ field: "case", value: value.join("$") });
+              }
+            }}
+          />
+        </Form.Item>
+        <Form.Item
+          name="factorMeaning"
+          label={intl.formatMessage({
+            id: "forms.fields.factor.meaning.label",
+          })}
+          tooltip={intl.formatMessage({
+            id: "forms.fields.factor.meaning.tooltip",
+          })}
+        >
+          <Input
+            allowClear
             placeholder={intl.formatMessage({
-              id: "forms.fields.meaning.label",
+              id: "forms.fields.factor.meaning.label",
+            })}
+          />
+        </Form.Item>
+        <Form.Item
+          name="parent"
+          label={intl.formatMessage({
+            id: "forms.fields.parent.label",
+          })}
+          tooltip={intl.formatMessage({
+            id: "forms.fields.parent.tooltip",
+          })}
+        >
+          <Input
+            allowClear
+            placeholder={intl.formatMessage({
+              id: "forms.fields.parent.label",
             })}
-            options={[
-              { label: "过去分词", value: "pp" },
-              { label: "现在分词", value: "prp" },
-              { label: "未来分词", value: "fpp" },
-            ]}
-            fieldProps={{
-              optionItemRender(item) {
-                return item.label + " - " + item.value;
-              },
-            }}
           />
-        </Panel>
-        <Panel header="关系" key="2">
-          关系语法
-        </Panel>
-      </Collapse>
-    </ProForm>
+        </Form.Item>
+        <Collapse bordered={false}>
+          <Panel header="词源" key="1">
+            <Form.Item
+              name="parent1"
+              label={intl.formatMessage({ id: "forms.fields.parent.label" })}
+              tooltip={intl.formatMessage({
+                id: "forms.fields.parent.tooltip",
+              })}
+            >
+              <Input
+                allowClear
+                placeholder={intl.formatMessage({
+                  id: "forms.fields.parent.label",
+                })}
+                addonAfter={
+                  <Form.Item name="suffix" noStyle>
+                    <Select style={{ width: 100 }} allowClear>
+                      <Option value="prp">现在分词</Option>
+                      <Option value="pp">过去分词</Option>
+                      <Option value="fpp">未来分词</Option>
+                    </Select>
+                  </Form.Item>
+                }
+              />
+            </Form.Item>
+          </Panel>
+          <Panel header="关系" key="2">
+            关系语法
+          </Panel>
+        </Collapse>
+      </Form>
+    </>
   );
 };
 

+ 10 - 38
dashboard/src/components/template/Wbw/WbwDetailBookMark.tsx

@@ -1,5 +1,4 @@
-import { useState, useEffect } from "react";
-import { useIntl } from "react-intl";
+import { useState } from "react";
 import type { RadioChangeEvent } from "antd";
 import { Radio } from "antd";
 import { Input } from "antd";
@@ -8,18 +7,13 @@ import { IWbw } from "./WbwWord";
 
 const { TextArea } = Input;
 
-const onTextChange = (
-  e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
-) => {
-  console.log("Change:", e.target.value);
-};
+export const bookMarkColor = ["#fff", "#f99", "#ff9", "#9f9", "#9ff", "#99f"];
 
 interface IWidget {
   data: IWbw;
   onChange?: Function;
 }
 const Widget = ({ data, onChange }: IWidget) => {
-  const intl = useIntl();
   const [value, setValue] = useState("none");
 
   const styleColor: React.CSSProperties = {
@@ -27,43 +21,21 @@ const Widget = ({ data, onChange }: IWidget) => {
     width: 28,
     height: 18,
   };
-  const options = [
-    {
-      label: (
-        <span
-          style={{
-            ...styleColor,
-            backgroundColor: "white",
-          }}
-        >
-          none
-        </span>
-      ),
-      value: "unset",
-    },
-    {
-      label: (
-        <span
-          style={{
-            ...styleColor,
-            backgroundColor: "blue",
-          }}
-        ></span>
-      ),
-      value: "blue",
-    },
-    {
+
+  const options = bookMarkColor.map((item, id) => {
+    return {
       label: (
         <span
           style={{
             ...styleColor,
-            backgroundColor: "yellow",
+            backgroundColor: item,
           }}
         ></span>
       ),
-      value: "yellow",
-    },
-  ];
+      value: id,
+    };
+  });
+
   const onColorChange = ({ target: { value } }: RadioChangeEvent) => {
     console.log("radio3 checked", value);
     setValue(value);

+ 8 - 1
dashboard/src/components/template/Wbw/WbwFactorMeaning.tsx

@@ -1,10 +1,17 @@
+import { Typography } from "antd";
+
 import { IWbw } from "./WbwWord";
+const { Text } = Typography;
 
 interface IWidget {
   data: IWbw;
 }
 const Widget = ({ data }: IWidget) => {
-  return <div>{data.factorMeaning?.value}</div>;
+  return (
+    <div>
+      <Text type="secondary">{data.factorMeaning?.value}</Text>
+    </div>
+  );
 };
 
 export default Widget;

+ 29 - 1
dashboard/src/components/template/Wbw/WbwFactors.tsx

@@ -1,10 +1,38 @@
+import type { MenuProps } from "antd";
+import { Dropdown } from "antd";
+import { Typography } from "antd";
+
 import { IWbw } from "./WbwWord";
+const { Text } = Typography;
+
+const items: MenuProps["items"] = [
+  {
+    key: "1",
+    label: "factors",
+  },
+  {
+    key: "2",
+    label: "factors",
+  },
+  {
+    key: "3",
+    label: "factors",
+  },
+];
 
 interface IWidget {
   data: IWbw;
 }
 const Widget = ({ data }: IWidget) => {
-  return <div>{data.factors?.value}</div>;
+  return (
+    <div>
+      <Text type="secondary">
+        <Dropdown menu={{ items }} placement="bottomLeft">
+          <span>{data.factors ? data.factors?.value : "拆分"}</span>
+        </Dropdown>
+      </Text>
+    </div>
+  );
 };
 
 export default Widget;

+ 26 - 2
dashboard/src/components/template/Wbw/WbwMeaning.tsx

@@ -1,10 +1,34 @@
+import { Popover } from "antd";
 import { IWbw } from "./WbwWord";
+import WbwMeaningSelect from "./WbwMeaningSelect";
 
 interface IWidget {
   data: IWbw;
+  onChange?: Function;
 }
-const Widget = ({ data }: IWidget) => {
-  return <div>{data.meaning?.value}</div>;
+const Widget = ({ data, onChange }: IWidget) => {
+  return (
+    <div>
+      <Popover
+        content={
+          <div style={{ width: 500 }}>
+            <WbwMeaningSelect
+              data={data}
+              onSelect={(e: string) => {
+                if (typeof onChange !== "undefined") {
+                  onChange(e);
+                }
+              }}
+            />
+          </div>
+        }
+        placement="bottomLeft"
+        trigger="hover"
+      >
+        {data.meaning?.value}
+      </Popover>
+    </div>
+  );
 };
 
 export default Widget;

+ 74 - 0
dashboard/src/components/template/Wbw/WbwMeaningSelect.tsx

@@ -0,0 +1,74 @@
+import { Collapse, Tag } from "antd";
+
+import { IWbw } from "./WbwWord";
+
+const { Panel } = Collapse;
+
+interface IMeaning {
+  text: string;
+  count: number;
+}
+interface IDict {
+  name: string;
+  meaning: IMeaning[];
+}
+interface IParent {
+  word: string;
+  dict: IDict[];
+}
+
+interface IWidget {
+  data: IWbw;
+  onSelect?: Function;
+}
+const Widget = ({ data, onSelect }: IWidget) => {
+  const meaning: IMeaning[] = Array.from(Array(10).keys()).map((item) => {
+    return { text: "意思" + item, count: item };
+  });
+  const dict: IDict[] = Array.from(Array(3).keys()).map((item) => {
+    return { name: "字典" + item, meaning: meaning };
+  });
+  const parent: IParent[] = Array.from(Array(3).keys()).map((item) => {
+    return { word: data.word.value + item, dict: dict };
+  });
+  return (
+    <div>
+      <Collapse defaultActiveKey={["0"]}>
+        {parent.map((item, id) => {
+          return (
+            <Panel header={item.word} style={{ padding: 0 }} key={id}>
+              {item.dict.map((itemDict, idDict) => {
+                return (
+                  <div key={idDict}>
+                    <div>{itemDict.name}</div>
+                    <div>
+                      {itemDict.meaning.map((itemMeaning, idMeaning) => {
+                        return (
+                          <Tag
+                            key={idMeaning}
+                            onClick={(
+                              e: React.MouseEvent<HTMLAnchorElement>
+                            ) => {
+                              e.preventDefault();
+                              if (typeof onSelect !== "undefined") {
+                                onSelect(itemMeaning.text);
+                              }
+                            }}
+                          >
+                            {itemMeaning.text}-{itemMeaning.count}
+                          </Tag>
+                        );
+                      })}
+                    </div>
+                  </div>
+                );
+              })}
+            </Panel>
+          );
+        })}
+      </Collapse>
+    </div>
+  );
+};
+
+export default Widget;

+ 11 - 10
dashboard/src/components/template/Wbw/WbwPali.tsx

@@ -1,16 +1,17 @@
 import { useState } from "react";
 import { Popover } from "antd";
-import { TagFilled, InfoCircleOutlined } from "@ant-design/icons";
+import { TagTwoTone, InfoCircleOutlined } from "@ant-design/icons";
 
 import WbwDetail from "./WbwDetail";
 import { IWbw } from "./WbwWord";
+import { bookMarkColor } from "./WbwDetailBookMark";
+import "./wbw.css";
 
 interface IWidget {
   data: IWbw;
-  onChange?: Function;
   onSave?: Function;
 }
-const Widget = ({ data, onChange, onSave }: IWidget) => {
+const Widget = ({ data, onSave }: IWidget) => {
   const [open, setOpen] = useState(false);
   const [paliColor, setPaliColor] = useState("unset");
   const wbwDetail = (
@@ -20,11 +21,6 @@ const Widget = ({ data, onChange, onSave }: IWidget) => {
         setPaliColor("unset");
         setOpen(false);
       }}
-      onChange={(e: IWbw) => {
-        if (typeof onChange !== "undefined") {
-          onChange(e);
-        }
-      }}
       onSave={(e: IWbw) => {
         if (typeof onSave !== "undefined") {
           onSave(e);
@@ -49,15 +45,19 @@ const Widget = ({ data, onChange, onSave }: IWidget) => {
   ) : (
     <></>
   );
+  const color = data.bookMarkColor
+    ? bookMarkColor[data.bookMarkColor.value]
+    : "white";
+
   const bookMarkIcon = data.bookMarkText ? (
     <Popover content={data.bookMarkText.value} placement="bottom">
-      <TagFilled style={{ color: data.bookMarkColor?.value }} />
+      <TagTwoTone twoToneColor={color} />
     </Popover>
   ) : (
     <></>
   );
   return (
-    <div>
+    <div className="pali_shell">
       <Popover
         content={wbwDetail}
         placement="bottom"
@@ -66,6 +66,7 @@ const Widget = ({ data, onChange, onSave }: IWidget) => {
         onOpenChange={handleClickChange}
       >
         <span
+          className="pali"
           style={{ backgroundColor: paliColor, padding: 4, borderRadius: 5 }}
         >
           {data.word.value}

+ 35 - 15
dashboard/src/components/template/Wbw/WbwWord.tsx

@@ -1,12 +1,13 @@
 import { useState } from "react";
-import { onChange } from "../../../reducers/setting";
 import WbwCase from "./WbwCase";
+import { bookMarkColor } from "./WbwDetailBookMark";
 import WbwFactorMeaning from "./WbwFactorMeaning";
 import WbwFactors from "./WbwFactors";
 import WbwMeaning from "./WbwMeaning";
 import WbwPali from "./WbwPali";
+import "./wbw.css";
 
-type FieldName =
+export type TFieldName =
   | "word"
   | "real"
   | "meaning"
@@ -20,12 +21,14 @@ type FieldName =
   | "note"
   | "bookMarkColor"
   | "bookMarkText"
+  | "locked"
   | "confidence";
 
 export interface IWbwField {
-  field: FieldName;
+  field: TFieldName;
   value: string;
 }
+
 enum WbwStatus {
   initiate = 0,
   auto = 3,
@@ -39,20 +42,25 @@ interface WbwElement2 {
   value: string[];
   status: WbwStatus;
 }
+interface WbwElement3 {
+  value: number;
+  status: WbwStatus;
+}
 export interface IWbw {
   word: WbwElement;
   real?: WbwElement;
   meaning?: WbwElement2;
   type?: WbwElement;
   grammar?: WbwElement;
-  case?: WbwElement;
+  case?: WbwElement2;
   parent?: WbwElement;
   factors?: WbwElement;
   factorMeaning?: WbwElement;
   relation?: WbwElement;
   note?: WbwElement;
-  bookMarkColor?: WbwElement;
+  bookMarkColor?: WbwElement3;
   bookMarkText?: WbwElement;
+  locked?: boolean;
   confidence: number;
 }
 export interface IWbwFields {
@@ -78,27 +86,39 @@ const Widget = ({
   const styleWbw: React.CSSProperties = {
     display: display === "block" ? "block" : "flex",
   };
+  const color = wordData.bookMarkColor
+    ? bookMarkColor[wordData.bookMarkColor.value]
+    : "unset";
   return (
-    <div style={styleWbw}>
+    <div className={`wbw_word ${display}`} style={styleWbw}>
       <WbwPali
         data={wordData}
-        onChange={(e: IWbw) => {
-          //setWordData(e);
-          if (typeof onChange !== "undefined") {
-            // onChange(e);
-          }
-        }}
         onSave={(e: IWbw) => {
           console.log("save", e);
           const newData: IWbw = JSON.parse(JSON.stringify(e));
           setWordData(newData);
           if (typeof onChange !== "undefined") {
-            //onChange(e);
+            onChange(e);
           }
         }}
       />
-      <div style={{ backgroundColor: wordData.bookMarkColor?.value }}>
-        {fields?.meaning ? <WbwMeaning data={wordData} /> : undefined}
+      <div
+        className="wbw_body"
+        style={{
+          background: `linear-gradient(90deg, rgba(255, 255, 255, 0), ${color})`,
+        }}
+      >
+        {fields?.meaning ? (
+          <WbwMeaning
+            data={wordData}
+            onChange={(e: string) => {
+              console.log("meaning change", e);
+              const newData: IWbw = JSON.parse(JSON.stringify(wordData));
+              newData.meaning = { value: [e], status: 5 };
+              setWordData(newData);
+            }}
+          />
+        ) : undefined}
         {fields?.factors ? <WbwFactors data={wordData} /> : undefined}
         {fields?.factorMeaning ? (
           <WbwFactorMeaning data={wordData} />

+ 38 - 0
dashboard/src/components/template/Wbw/wbw.css

@@ -0,0 +1,38 @@
+.wbw_word {
+  margin: 0 0 0.2em 0;
+  padding-right: 0;
+  max-width: 60vw;
+}
+.block .pali {
+  font-weight: 500;
+  font-size: 110%;
+  padding: 0px 2px;
+  margin: 0px;
+  line-height: 1.5em;
+}
+.block .pali_shell {
+  border-bottom: 1px solid gray;
+}
+.inline .pali {
+  color: brown;
+}
+.block .wbw_body {
+  padding-right: 0.5em;
+  padding-top: 0.2em;
+}
+.wbw_word_item {
+  min-width: 3em;
+  padding: 0;
+  cursor: pointer;
+}
+
+.case {
+  padding: 0 2px;
+  line-height: 1.5em;
+}
+.case:first-child {
+  outline: 1px solid;
+  outline-offset: -1px;
+  margin: 0 0.3em 0 1px;
+  padding: 0px 2px;
+}