antd#Mentions TypeScript Examples

The following examples show how to use antd#Mentions. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utils.tsx    From antdp with MIT License 6 votes vote down vote up
getItem = ({ attr, type, inputNode }: {
  attr?: Partial<ItemChildAttr<any, any>>;
  type?: ItemChildType;
  inputNode?: ((...arg: any[]) => React.ReactNode) | React.ReactNode;
}) => {
  let renderItem = undefined;
  if (type === 'Input') {
    const inputAttr = attr as InputProps;
    renderItem = <Input {...inputAttr} />;
  } else if (type === 'TextArea') {
    const inputAttr = attr as TextAreaProps;
    renderItem = <Input.TextArea {...inputAttr} />;
  } else if (type === 'InputNumber') {
    const inputAttr = attr as InputNumberProps;
    renderItem = <InputNumber {...inputAttr} />;
  } else if (type === 'AutoComplete') {
    const inputAttr = attr as AutoCompleteProps;
    renderItem = <AutoComplete {...inputAttr} />;
  } else if (type === 'Cascader') {
    const inputAttr = attr as CascaderProps;
    renderItem = <Cascader {...inputAttr} />;
  } else if (type === 'DatePicker') {
    const inputAttr = attr as DatePickerProps;
    renderItem = <DatePicker {...inputAttr} />;
  } else if (type === 'Rate') {
    const inputAttr = attr as RateProps;
    renderItem = <Rate {...inputAttr} />;
  } else if (type === 'Slider') {
    const inputAttr = attr as SliderSingleProps;
    renderItem = <Slider {...inputAttr} />;
  } else if (type === 'TreeSelect') {
    const inputAttr = attr as TreeSelectProps<any>;
    renderItem = <TreeSelect {...inputAttr} />;
  } else if (type === 'Select') {
    const inputAttr = attr as SelectProps<any>;
    renderItem = <Select {...inputAttr} />;
  } else if (type === 'Checkbox') {
    const inputAttr = attr as CheckboxGroupProps;
    renderItem = <Checkbox.Group {...inputAttr} />;
  } else if (type === 'Mentions') {
    const inputAttr = attr as MentionProps;
    renderItem = <Mentions {...inputAttr} />;
  } else if (type === 'Radio') {
    const inputAttr = attr as RadioProps;
    renderItem = <Radio.Group {...inputAttr} />;
  } else if (type === 'Switch') {
    const inputAttr = attr as SwitchProps;
    renderItem = <Switch {...inputAttr} />;
  } else if (type === 'TimePicker') {
    const inputAttr = attr as TimePickerProps;
    renderItem = <TimePicker {...inputAttr} />;
  } else if (type === 'Upload') {
    const inputAttr = attr as UploadProps;
    renderItem = <Upload {...inputAttr} />;
  } else if (type === 'RangePicker') {
    const inputAttr = attr as RangePickerProps;
    renderItem = <RangePicker {...inputAttr} />;
  } else if (type === 'Custom') {
    renderItem = inputNode;
  }
  return renderItem;
}
Example #2
Source File: UploadImg.spec.tsx    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
describe("UploadImg", () => {
  it("should work", async () => {
    const onChange = jest.fn();
    const wrapper = mount(
      <UploadImg
        bucketName="agile"
        value={{
          text: "desc",
        }}
        listType="picture-card"
        onChange={onChange}
        showTextarea={true}
      />
    );
    expect(wrapper.find(Input.TextArea).text()).toBe("desc");
    wrapper.find(Input.TextArea).invoke("onChange")({
      target: {
        value: "",
      },
    });
    await Promise.resolve();
    expect(onChange).toHaveBeenCalled();
    message.error = jest.fn();
    wrapper.find(Upload).invoke("onChange")({
      file: {
        uid: "123",
        size: 1234,
        type: "application/json",
        name: "json",
        status: "done",
        url: "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png",
      },
      fileList: [...fileList],
    });
    const notImageResult = wrapper.find(Upload).invoke("beforeUpload")(
      {
        uid: "123",
        size: 1234,
        type: "application/json",
        name: "json",
      } as RcFile,
      [...fileList]
    );
    await expect(notImageResult).rejects.toStrictEqual(
      new Error("仅支持上传图片文件")
    );

    wrapper.setProps({
      limitSize: 2,
    });
    const overLimitResult = wrapper.find(Upload).invoke("beforeUpload")(
      {
        uid: "123",
        size: 1024 * 1024 * 3,
        type: "image/png",
        name: "png",
      } as RcFile,
      [...fileList]
    );
    await expect(overLimitResult).rejects.toStrictEqual(
      new Error("上传文件体积大于限定体积")
    );

    const allowResult = wrapper.find(Upload).invoke("beforeUpload")(
      {
        uid: "123",
        size: 1024 * 1024,
        type: "image/png",
        name: "png",
      } as RcFile,
      [...fileList]
    );
    await expect(allowResult).resolves.toMatchObject({
      size: 1024 * 1024,
    });

    wrapper.find(Upload).invoke("onChange")({
      file: {
        uid: "123",
        size: 1234,
        type: "image/png",
        name: "image.png",
        status: "done",
        response: {
          data: {
            objectName: "image.png",
          },
        },
      },
      fileList: [...fileList],
    });
    expect(wrapper.find(".ant-upload-list-item").length).toBe(1);
    wrapper.find(Upload).invoke("onChange")({
      file: {
        uid: "123",
        size: 1234,
        type: "image/png",
        name: "image.png",
        status: "removed",
        response: {
          data: {
            objectName: "image.png",
          },
        },
      },
      fileList: [],
    });
    await jest.runAllTimers();
    wrapper.update();
    expect(wrapper.find(".ant-upload-list-item").length).toBe(0);
    wrapper.find(Upload).invoke("onChange")({
      file: {
        uid: "-img1",
        size: 1024,
        type: "image/png",
        name: "image.png",
        status: "uploading",
        response: {
          data: {
            objectName: "image.png",
          },
        },
      },
      fileList: [
        ...fileList,
        {
          uid: "-img1",
          size: 1024,
          type: "image/png",
          name: "image.png",
          status: "uploading",
          response: {
            data: {
              objectName: "image.png",
            },
          },
        },
      ],
    });
    expect(wrapper.find(Upload).prop("disabled")).toBe(true);
    wrapper.find(Input.TextArea).invoke("onPaste")({
      clipboardData: {
        items: [
          {
            getAsFile: jest.fn(
              () => new File([], "xxx.json", { type: "application/json" })
            ),
          },
        ],
      },
    });
    expect(message.error).not.toBeCalledWith("还有附件正在上传,请稍候再试。");
    wrapper.find(Input.TextArea).invoke("onPaste")({
      clipboardData: {
        items: [
          {
            getAsFile: jest.fn(
              () => new File([], "xxx.png", { type: "image/png" })
            ),
          },
        ],
      },
    });
    expect(message.error).toBeCalledWith("还有附件正在上传,请稍候再试。");
    await act(async () => {
      wrapper.find(Upload).invoke("onPreview")({
        size: 1024,
        name: "123",
        type: "image/png",
        uid: "-3",
        status: "done",
        originFileObj: new File([], "xxx.png", { type: "image/png" }),
      });
      await (global as any).flushPromises();
    });
    wrapper.find(Upload).invoke("onPreview")({
      uid: "123",
      size: 1234,
      type: "image/png",
      name: "image",
      status: "done",
      url: "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png",
    });
    expect(wrapper.find(Modal).prop("visible")).toBe(true);
    wrapper.find(Modal).invoke("onCancel")({});
    expect(wrapper.find(Modal).prop("visible")).toBe(false);
  });

  it("should work with empty value", async () => {
    const onChange = jest.fn();
    const wrapper = mount(
      <UploadImg
        listType="picture-card"
        onChange={onChange}
        bucketName="agile"
        showTextarea={true}
      />
    );
    expect(wrapper.find(Input.TextArea).text()).toBe("");
    await act(async () => {
      message.error = jest.fn();
      jest.spyOn(http, "put").mockRejectedValueOnce(new Error("http error"));
      wrapper.find(Input.TextArea).invoke("onPaste")({
        clipboardData: {
          items: [
            {
              getAsFile: jest.fn(
                () => new File([], "xxx.png", { type: "image/png" })
              ),
            },
          ],
        },
      });
      await (global as any).flushPromises();
    });
    expect(message.error).toHaveBeenCalled();
    await act(async () => {
      message.error = jest.fn();
      jest.spyOn(http, "put").mockResolvedValueOnce({
        data: {
          objectName: "newImage.png",
        },
      });
      wrapper.find(Input.TextArea).invoke("onPaste")({
        clipboardData: {
          items: [
            {
              getAsFile: jest.fn(
                () => new File([], "newImage.png", { type: "image/png" })
              ),
            },
          ],
        },
      });
      await (global as any).flushPromises();
    });
    wrapper.update();
    expect(wrapper.find(".ant-upload-list-item").length).toBe(1);
  });

  it("test error", async () => {
    const onChange = jest.fn();
    const wrapper = mount(
      <UploadImg
        listType="picture-card"
        onChange={onChange}
        bucketName="agile"
        showTextarea={true}
      />
    );
    wrapper.find(Upload).invoke("onChange")({
      file: {
        uid: "-img1",
        size: 1024,
        type: "image/png",
        name: "image.png",
        status: "uploading",
      },
      fileList: [
        {
          uid: "-img1",
          size: 1024,
          type: "image/png",
          name: "image.png",
          status: "uploading",
        },
      ],
    });
    wrapper.update();
    expect(wrapper.find(".ant-upload-list-item").length).toBe(1);
    wrapper.find(Upload).invoke("onChange")({
      file: {
        uid: "-img1",
        size: 1024,
        type: "image/png",
        name: "image.png",
        status: "error",
      },
      fileList: [],
    });
    await act(async () => {
      await jest.runAllTimers();
    });
    wrapper.update();
    expect(wrapper.find(".ant-upload-list-item").length).toBe(0);
  });

  it("test set value by outside", async () => {
    const onChange = jest.fn();
    const wrapper = mount(
      <UploadImg
        listType="picture-card"
        onChange={onChange}
        bucketName="agile"
        showTextarea={true}
      />
    );
    wrapper.find(Upload).invoke("onChange")({
      file: {
        uid: "-img1",
        size: 1024,
        type: "image/png",
        name: "image.png",
        status: "done",
      },
      fileList: [
        {
          uid: "-img1",
          size: 1024,
          type: "image/png",
          name: "image.png",
          status: "done",
        },
      ],
    });
    wrapper.update();
    expect(wrapper.find(".ant-upload-list-item").length).toBe(1);
    wrapper.setProps({
      value: {
        images: [
          {
            url: "image2.png",
          },
          {
            url: "image2.png",
          },
        ],
      },
    });
    await act(async () => {
      await jest.runAllTimers();
    });
    wrapper.update();
    expect(wrapper.find(".ant-upload-list-item").length).toBe(2);
  });

  it("test maxNumber and showTextarea", async () => {
    const onChange = jest.fn();
    const wrapper = mount(
      <UploadImg
        listType="picture-card"
        onChange={onChange}
        bucketName="agile"
        maxNumber={1}
      />
    );
    await act(async () => {
      wrapper.find(Upload).invoke("onChange")({
        file: {
          uid: "-img1",
          size: 1024,
          type: "image/png",
          name: "image.png",
          status: "done",
          response: {
            data: {
              objectName: "image.png",
            },
          },
          originFileObj: new File([], "image.png", { type: "image/png" }),
        },
        fileList: [
          {
            uid: "-img1",
            size: 1024,
            type: "image/png",
            name: "image.png",
            status: "done",
            response: {
              data: {
                objectName: "image.png",
              },
            },
            originFileObj: new File([], "image.png", { type: "image/png" }),
          },
        ],
      });
      await (global as any).flushPromises();
    });
    wrapper.update();
    expect(wrapper.find(".ant-upload-list-item").length).toBe(1);
    expect(wrapper.find(".ant-upload-text").length).toBe(0);
    wrapper.setProps({
      maxNumber: 2,
      value: {
        images: [
          {
            url: "image2.png",
          },
          {
            url: "image2.png",
          },
        ],
      },
    });
    await act(async () => {
      await jest.runAllTimers();
    });
    wrapper.update();
    expect(wrapper.find(".ant-upload-list-item").length).toBe(2);
    expect(wrapper.find(Input.TextArea).length).toBe(0);
    expect(wrapper.find(".ant-upload-text").length).toBe(0);
    wrapper.setProps({
      maxNumber: 3,
    });
    expect(wrapper.find(".ant-upload-text").length).toBe(1);
  });

  it("should work when listType is picture", async () => {
    const onChange = jest.fn();
    const wrapper = mount(
      <UploadImg listType="picture" onChange={onChange} bucketName="agile" />
    );
    wrapper.find(Upload).invoke("onChange")({
      file: {
        uid: "-img1",
        size: 1024,
        percent: 28,
        type: "image/png",
        name: "image.png",
        status: "uploading",
      },
      fileList: [
        {
          uid: "-img1",
          size: 1024,
          percent: 28,
          type: "image/png",
          name: "image.png",
          status: "uploading",
        },
      ],
    });
    wrapper.update();
    expect(wrapper.find(".ant-upload-list-item").length).toBe(1);
    expect(wrapper.find(".upload-file-main-info").length).toBe(1);
    expect(wrapper.find(".upload-file-error-info").length).toBe(1);
    wrapper.find(Upload).invoke("onChange")({
      file: {
        uid: "-img1",
        size: 1024,
        type: "image/png",
        name: "image.png",
        status: "done",
      },
      fileList: [
        {
          uid: "-img1",
          size: 1024,
          type: "image/png",
          name: "image.png",
          status: "done",
        },
      ],
    });
    await act(async () => {
      await jest.runAllTimers();
    });
    wrapper.update();
    expect(wrapper.find(".ant-upload-list-item").length).toBe(1);
    expect(wrapper.find(".upload-file-main-info").length).toBe(1);
    expect(wrapper.find(".upload-file-error-info").length).toBe(0);
    wrapper.find(Upload).invoke("onChange")({
      file: {
        uid: "-img1",
        size: 1024,
        type: "image/png",
        name: "image.png",
        status: "error",
      },
      fileList: [
        {
          uid: "-img1",
          size: 1024,
          type: "image/png",
          name: "image.png",
          status: "error",
        },
      ],
    });
    await act(async () => {
      await jest.runAllTimers();
    });
    wrapper.update();
    expect(wrapper.find(".ant-upload-list-item").length).toBe(0);
    expect(wrapper.find(".upload-file-main-info").length).toBe(0);
    expect(wrapper.find(".upload-file-error-info").length).toBe(0);
  });

  it("test getPreview", async () => {
    const onChange = jest.fn();
    const wrapper = mount(
      <UploadImg
        listType="picture-card"
        onChange={onChange}
        bucketName="agile"
        maxNumber={1}
        getPreview={true}
      />
    );
    await act(async () => {
      wrapper.find(Upload).invoke("onChange")({
        file: {
          uid: "-img1",
          size: 1024,
          type: "image/png",
          name: "image.png",
          status: "done",
          response: {
            data: {
              objectName: "image.png",
            },
          },
          originFileObj: new File([], "image.png", { type: "image/png" }),
        },
        fileList: [
          {
            uid: "-img1",
            size: 1024,
            type: "image/png",
            name: "image.png",
            status: "done",
            response: {
              data: {
                objectName: "image.png",
              },
            },
            originFileObj: new File([], "image.png", { type: "image/png" }),
          },
        ],
      });
      await (global as any).flushPromises();
    });
    wrapper.update();
    expect(onChange).toBeCalledWith({
      images: [
        {
          name: "image.png",
          preview: "data:image/png;base64,",
          url: "api/gateway/object_store.object_store.GetObject/api/v1/objectStore/bucket/agile/object/image.png",
        },
      ],
    });

    const wrapper2 = mount(
      <UploadImg
        listType="picture-card"
        onChange={onChange}
        bucketName="agile"
        maxNumber={2}
        getPreview={true}
      />
    );
    await act(async () => {
      wrapper2.find(Upload).invoke("onChange")({
        file: {
          uid: "-img1",
          size: 1024,
          type: "image/png",
          name: "image.png",
          status: "done",
          response: {
            data: {
              objectName: "image.png",
            },
          },
          originFileObj: new File([], "image.png", { type: "image/png" }),
        },
        fileList: [
          {
            uid: "-img1",
            size: 1024,
            type: "image/png",
            name: "image.png",
            status: "done",
            response: {
              data: {
                objectName: "image.png",
              },
            },
            originFileObj: new File([], "image.png", { type: "image/png" }),
          },
        ],
      });
      await (global as any).flushPromises();
    });
    wrapper2.update();
    expect(onChange).toBeCalledWith({
      images: [
        {
          name: "image.png",
          preview: "data:image/png;base64,",
          url: "api/gateway/object_store.object_store.GetObject/api/v1/objectStore/bucket/agile/object/image.png",
        },
      ],
    });
  });
  it("should work when showMentions is  true", async () => {
    const onChange = jest.fn();
    const wrapper = mount(
      <UploadImg
        listType="picture-card"
        onChange={onChange}
        bucketName="monitor"
        showMentions={true}
        hideUploadButton={true}
      />
    );

    await act(async () => {
      await (global as any).flushPromises();
    });
    wrapper.update();
    expect(wrapper.find(Mentions).length).toBe(1);
    wrapper.find(Mentions).invoke("onChange")("123");
    wrapper.update();
    expect(onChange).toHaveBeenCalled();
    wrapper.setProps({
      showMentions: false,
    });
    expect(wrapper.find(Mentions).length).toBe(0);
  });

  it("should show dark icon", () => {
    const spyOnUseCurrentTheme = jest
      .spyOn(brickKit, "useCurrentTheme")
      .mockReturnValue("dark-v2");
    const wrapper = mount(
      <UploadImg listType="picture" bucketName="monitor" uploadDraggable />
    );

    expect(wrapper.find(GeneralIcon).at(0).prop("icon")).toEqual({
      category: "colored-common",
      icon: "upload-dark",
      lib: "easyops",
    });
    spyOnUseCurrentTheme.mockRestore();
  });
});
Example #3
Source File: UploadImg.tsx    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
export function RealUploadImg(
  props: UploadImgProps,
  ref: any
): React.ReactElement {
  const { t } = useTranslation(NS_FORMS);
  const action = `api/gateway/object_store.object_store.PutObject/api/v1/objectStore/bucket/${props.bucketName}/object`;
  const [value, setValue] = React.useState(props.value);
  const [imageList, setImageList] = useState(transformToImageList(props.value));
  const [previewImage, setPreviewImage] = useState("");
  const [previewVisible, setPreviewVisible] = useState(false);
  const [disabled, setDisabled] = useState(false);
  const [allUser, serAllUser] = useState<UserInfo[]>();
  const theme = useCurrentTheme();

  const buttonIcon: MenuIcon = {
    lib: "easyops",
    category: "colored-common",
    icon: theme == "dark-v2" ? "upload-dark" : "upload-light",
  };

  React.useEffect(() => {
    setValue(props.value);
    const isDifferent = compareValues(props.value?.images, imageList);
    if (isDifferent) {
      setImageList(transformToImageList(props.value));
    }
  }, [props.value]);

  React.useEffect(() => {
    const getAllUser = async () => {
      const userMap = await getRuntime().getAllUserMapAsync();
      serAllUser([...userMap.values()]);
    };
    if (props.showMentions) {
      getAllUser();
    }
  }, [props.showMentions]);

  const transformResponseToUrl = (objectName: string) => {
    const url = `api/gateway/object_store.object_store.GetObject/api/v1/objectStore/bucket/${props.bucketName}/object/${objectName}`;
    return props.useFullUrlPath ? `/next/${url}` : `${url}`;
  };

  const handleValueChange = (v: UploadImgValue) => {
    let newValue = { ...value, ...v };
    if (
      (newValue.text === "" || isNil(newValue.text)) &&
      isEmpty(newValue.images)
    ) {
      newValue = null;
    }
    setValue(newValue);
    props.onChange?.(newValue);
  };

  const handleFilesChange = async (
    newFile: ImageItem,
    newFileList: ImageItem[],
    isDone: boolean
  ): Promise<void> => {
    if (isDone) {
      if (props.maxNumber === 1) {
        newFile.preview =
          newFile.preview || (await getBase64(newFile.originFileObj));
        setImageList([
          {
            ...newFile,
          },
        ]);

        handleValueChange({
          images: [
            {
              ...(props.getPreview ? { preview: newFile.preview } : {}),
              url: newFile.url,
              name: newFile.name,
            },
          ],
        });
      } else {
        setImageList(
          update(newFileList, {
            [newFileList.length - 1]: { $set: newFile },
          })
        );

        handleValueChange({
          images: update(value?.images || [], {
            $push: [
              {
                ...(props.getPreview ? { preview: newFile.preview } : {}),
                url: newFile.url,
                name: newFile.name,
              },
            ],
          }),
        });
      }
    } else {
      if (props.maxNumber === 1) {
        setImageList([{ ...newFile }]);
      } else {
        setImageList(newFileList);
      }
    }
  };

  const handlePreview = async (file: any): Promise<void> => {
    if (!file.preview && file.originFileObj) {
      file.preview = await getBase64(file.originFileObj);
    }
    setPreviewImage(file.preview || file.url);
    setPreviewVisible(true);
  };

  const handleChange = ({
    file,
    fileList,
  }: {
    file: UploadFile;
    fileList: UploadFile[];
  }): void => {
    if (some(fileList, ["status", "uploading"])) {
      setDisabled(true);
    } else {
      setDisabled(false);
    }
    if (file.status === "removed") {
      const index = findIndex(imageList, ["uid", file.uid]);
      handleValueChange({
        images: update(value.images, { $splice: [[index, 1]] }),
      });

      setImageList(fileList);
    } else if (file.status === "error") {
      setDisabled(false);
      const index = findIndex(imageList, ["uid", file.uid]);
      if (index !== -1) {
        setImageList(update(imageList, { $splice: [[index, 1]] }));
      }
      message.error("上传文件失败");
    } else {
      if (file?.type.startsWith("image/")) {
        handleFilesChange(file, [...fileList], false);
        if (file.response && file.status === "done") {
          file.url = transformResponseToUrl(file.response.data.objectName);
          handleFilesChange(file, [...fileList], true);
        }
      } else {
        setDisabled(false);
      }
    }
  };

  const handleCancel = (): void => {
    setPreviewVisible(false);
  };

  const uploadButton = (): React.ReactElement => {
    if (props.hideUploadButton && !props.uploadDraggable) {
      return null;
    }
    if (props.uploadDraggable) {
      return (
        <>
          <p className="ant-upload-drag-icon">
            <GeneralIcon icon={buttonIcon} />
          </p>
          <p className="ant-upload-text">
            {props.draggableUploadText ?? t(K.DRAGGABLE_UPLOAD_TEXT)}
          </p>
          <p className="ant-upload-hint">
            {props.draggableUploadHint ?? t(K.DRAGGABLE_UPLOAD_HINT)}
          </p>
        </>
      );
    }
    if (props.listType === "picture-card") {
      return (
        <div>
          {props.maxNumber === 1 && disabled ? (
            <LoadingOutlined />
          ) : theme === "dark-v2" ? (
            <ImageUploadDark />
          ) : (
            <ImageUpload />
          )}

          <div className="ant-upload-text" style={{ marginTop: "-8px" }}>
            上传图片
          </div>
        </div>
      );
    } else {
      return (
        <Button>
          <UploadOutlined /> Upload
        </Button>
      );
    }
  };

  const uploadNode = (): React.ReactElement => {
    return !props.maxNumber || imageList?.length < props.maxNumber
      ? uploadButton()
      : null;
  };

  const filesPasted = (e): void => {
    const items = e.clipboardData.items;
    forEach(items, async (item) => {
      const file = item.getAsFile();
      if (file?.type.startsWith("image/")) {
        if (
          props.maxNumber &&
          imageList?.length >= props.maxNumber &&
          props.maxNumber !== 1
        ) {
          message.error(`仅支持上传 ${props.maxNumber} 张图片`);
          return;
        }
        if (disabled) {
          message.error("还有附件正在上传,请稍候再试。");
          return;
        }
        const fileInfo: any = {
          originFileObj: file,
          type: file.type,
          name: file.name,
          size: file.size,
          lastModified: file.lastModified,
          lastModifiedDate: file.lastModifiedDate,
          uid: uniqueId("-img"),
          status: "uploading",
          percent: 0,
        };

        const oldList = cloneDeep(imageList);
        handleFilesChange(fileInfo, [...oldList, fileInfo], false);
        const reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = () => {
          fileInfo.preview = reader.result as string;
          fileInfo.percent = 100;
        };
        // 上传文件
        setDisabled(true);
        try {
          const response = await ObjectStoreApi_putObject(props.bucketName, {
            file: file,
          });

          fileInfo.status = "done";
          fileInfo.url = transformResponseToUrl(response.objectName);
          handleFilesChange(fileInfo, [...oldList, fileInfo], true);
          setDisabled(false);
        } catch (err) {
          message.error("上传失败");
          setImageList(oldList);
          setDisabled(false);
        }
      }
    });
  };

  const handleTextChange = (e: any): void => {
    handleValueChange({
      text: e.target.value,
    });
  };
  const handleMentionsChange = (value: string): void => {
    handleValueChange({
      text: value,
    });
  };

  const handleRemove = (e: any): void => {
    props.onRemove?.(e);
  };

  const handleBeforeUpload = (file: RcFile): Promise<RcFile> => {
    return new Promise((resolve, reject) => {
      if (!file.type?.startsWith("image/")) {
        message.error("仅支持上传图片文件");
        reject(new Error("仅支持上传图片文件"));
      }
      if (FileUtils.sizeCompare(file, props.limitSize ?? 10)) {
        message.error(`上传文件体积大于限定体积`);
        reject(new Error("上传文件体积大于限定体积"));
      }
      resolve(file);
    });
  };

  const fileInfoNode = (file: UploadFile): ReactNode => (
    <>
      <div className={styles["upload-file-main-info"]}>
        <span className={styles["upload-file-name"]}>{file.name}</span>
        <span className={styles["upload-file-size"]}>
          {file.size &&
            (file.status === "uploading"
              ? `${sizeFormat(file.size)} (${Math.floor(file.percent)}%Done)`
              : sizeFormat(file.size))}
        </span>
      </div>
      <div className={styles["upload-file-else-info"]}>
        {(file.status === "error" || file.status === "uploading") && (
          <span className={styles["upload-file-error-info"]}>
            {file.status === "error" && "Wrong!"}
          </span>
        )}
      </div>
    </>
  );

  const cloneFileItemNode = (
    node: ReactElement,
    file: UploadFile
  ): ReactNode => {
    const nodeChildren = React.Children.map(node?.props?.children, (child) => {
      if (
        child?.props?.className
          ?.split(" ")
          ?.includes("ant-upload-list-item-name")
      ) {
        return React.cloneElement(child, null, fileInfoNode(file));
      }
      return cloneFileItemNode(child, file);
    });
    if (React.isValidElement(node)) {
      // children是function额外处理
      if (node?.props?.children instanceof Function)
        return React.cloneElement(node, null, node.props.children);
      return React.cloneElement(node, null, nodeChildren);
    }
    return node;
  };

  const textProps = {
    progress: {
      strokeColor: "#2FC25B",
      trailColor: "var(--theme-gray-background)",
      strokeWidth: "1px",
      showInfo: false,
    },

    showUploadList: {
      // eslint-disable-next-line react/display-name
      removeIcon: (file: UploadFile): ReactNode =>
        file.status === "error" ? (
          <GeneralIcon
            icon={{
              lib: "antd",
              theme: "outlined",
              icon: "close",
            }}
          />
        ) : (
          <GeneralIcon
            icon={{
              lib: "easyops",
              category: "default",
              icon: "delete",
            }}
          />
        ),
    },

    // eslint-disable-next-line react/display-name
    iconRender: (file: UploadFile): ReactNode =>
      file.status === "uploading" ? (
        <LoadingOutlined />
      ) : (
        <GeneralIcon
          icon={{
            lib: "antd",
            icon: "file-text",
            theme: "outlined",
          }}
        />
      ),
  };

  const pictureProps = {
    progress: {
      strokeColor: "var(--color-brand)",
      trailColor: "#FFF",
      strokeWidth: "4px",
      showInfo: false,
    },

    showUploadList: {
      // eslint-disable-next-line react/display-name
      removeIcon: (file: UploadFile): ReactNode =>
        file.status === "error" ? (
          <GeneralIcon
            icon={{
              lib: "antd",
              theme: "outlined",
              icon: "close",
            }}
          />
        ) : (
          <GeneralIcon
            icon={{
              lib: "easyops",
              category: "default",
              icon: "delete",
            }}
          />
        ),
    },

    // eslint-disable-next-line react/display-name
    itemRender: (originNode: ReactElement, file: UploadFile): ReactNode => {
      return cloneFileItemNode(originNode, file);
    },
  };

  let typeProps = {};
  if (props.listType === "picture") {
    typeProps = pictureProps;
  } else if (props.listType === "text") {
    typeProps = textProps;
  }

  const uploadProps = {
    className: classNames({
      [styles.uploadContainerDisplayNone]:
        props.uploadDraggable &&
        props.maxNumber &&
        imageList?.length >= props.maxNumber,
    }),

    method: "put",
    action,
    listType: props.listType,
    fileList: imageList,
    onPreview: handlePreview,
    onChange: handleChange,
    onRemove: handleRemove,
    beforeUpload: handleBeforeUpload,
    supportServerRender: true,
    disabled,
  };

  return (
    <div ref={ref} className={styles.uploadContainer}>
      {props.showTextarea && (
        <Input.TextArea
          onPaste={(e) => filesPasted(e)}
          onChange={handleTextChange}
          className={styles.textContainer}
          value={value?.text || ""}
          placeholder={props.placeholder}
          autoSize={props.autoSize}
        />
      )}
      {props.showMentions && !props.showTextarea && (
        <Mentions
          rows={2}
          onChange={handleMentionsChange}
          value={value?.text || ""}
          autoSize={props.autoSize}
          className={styles.textContainer}
          onPaste={(e) => filesPasted(e)}
          placeholder={props.placeholder}
        >
          {allUser &&
            allUser.map((item) => (
              <Mentions.Option value={item.name} key={item.name}>
                <Avatar
                  src={item.user_icon}
                  size={24}
                  className={classNames(styles.avatar, {
                    [styles.defaultIcon]: !item.user_icon,
                  })}
                >
                  {!item.user_icon && item.name?.slice(0, 2)}
                </Avatar>
                {item.name}
              </Mentions.Option>
            ))}
        </Mentions>
      )}

      {props.uploadDraggable ? (
        <Upload.Dragger {...uploadProps}>{uploadNode()}</Upload.Dragger>
      ) : (
        <Upload {...uploadProps} {...typeProps}>
          {uploadNode()}
        </Upload>
      )}

      <Modal visible={previewVisible} footer={null} onCancel={handleCancel}>
        <img alt="example" style={{ width: "100%" }} src={previewImage} />
      </Modal>
    </div>
  );
}
Example #4
Source File: logic.block.tsx    From ui with GNU Affero General Public License v3.0 4 votes vote down vote up
LogicBlock: React.FC<Props> = ({
  form,
  field,
  fields,
  remove,
  index,
}) => {
  const { t } = useTranslation()
  const evaluator = useMath()

  return (
    <div
      style={{
        borderRight: '5px solid #DDD',
        paddingRight: 10,
      }}
    >
      <Form.Item
        name={[field.name as string, 'formula']}
        labelCol={{ span: 6 }}
        label={'Formula'}
        rules={[{ required: true, message: 'combine other fields' }]}
        extra={'Save form to get new @IDs and $slugs. (example: $slug < 21 or @id = 42)'}
      >
        <Mentions rows={1}>
          {fields.map((field) => (
            <Mentions.Option key={field.id} value={field.id}>
              {field.title}
            </Mentions.Option>
          ))}
        </Mentions>
      </Form.Item>

      <Form.Item noStyle shouldUpdate>
        {(form: FormInstance & { prefixName: string[] }) => {
          try {
            const defaults = {}

            fields.forEach((field) => {
              defaults[`@${field.id}`] = field.defaultValue

              if (field.slug) {
                defaults[`$${field.slug}`] = field.defaultValue
              }
            })

            const result = evaluator(
              form.getFieldValue([
                ...form.prefixName,
                field.name as string,
                'formula',
              ]),
              defaults
            )

            return (
              <Alert
                type={result ? 'success' : 'warning'}
                message={
                  result
                    ? 'would trigger action with current default values'
                    : 'would NOT trigger action with current default values'
                }
                style={{ marginBottom: 24 }}
              />
            )
          } catch (e) {
            return (
              <Alert
                message={(e as Error).message || 'Failed to process formula'}
                type={'error'}
                style={{ marginBottom: 24 }}
              />
            )
          }
        }}
      </Form.Item>
      <Form.Item name={[field.name as string, 'action']} labelCol={{ span: 6 }} label={'Action'}>
        <Select
          options={[
            {
              value: 'jumpTo',
              label: t('form:logic.action.jumpTo'),
            },
            {
              value: 'visible',
              label: t('form:logic.action.visible'),
            },
            {
              value: 'disable',
              label: t('form:logic.action.disable'),
            },
            {
              value: 'require',
              label: t('form:logic.action.require'),
            },
          ]}
        />
      </Form.Item>
      <Form.Item noStyle shouldUpdate>
        {(form: FormInstance & { prefixName: string[] }) => {
          return (
            <Form.Item
              hidden={
                form.getFieldValue([
                  ...form.prefixName, field.name as string, 'action',
                ]) !==
                'jumpTo'
              }
              labelCol={{ span: 6 }}
              label={t('form:logic.action.jumpTo')}
              rules={[{ required: true, message: 'Jump target is required' }]}
              extra={'after selecting field (works best with clickable values)'}
            >
              <Select
                options={fields
                  .filter((field) => !/NEW/i.test(field.id))
                  .map((field) => ({
                    value: field.id,
                    label: field.title,
                  }))}
              />
            </Form.Item>
          )
        }}
      </Form.Item>

      <Form.Item noStyle shouldUpdate>
        {(form: FormInstance & { prefixName: string[] }) => {
          return (
            <Form.Item
              hidden={
                form.getFieldValue([
                  ...form.prefixName, field.name as string, 'action',
                ]) !==
                'visible'
              }
              initialValue={true}
              labelCol={{ span: 6 }}
              label={t('form:logic.action.visible')}
              valuePropName={'checked'}
              getValueFromEvent={(checked: boolean) => (checked ? '1' : '')}
              getValueProps={(e: string) => ({ checked: !!e })}
            >
              <Checkbox />
            </Form.Item>
          )
        }}
      </Form.Item>

      <Form.Item noStyle shouldUpdate>
        {(form: FormInstance & { prefixName: string[] }) => {
          return (
            <Form.Item
              hidden={
                form.getFieldValue([
                  ...form.prefixName, field.name as string, 'action',
                ]) !==
                'disable'
              }
              initialValue={false}
              labelCol={{ span: 6 }}
              label={t('form:logic.action.disable')}
              valuePropName={'checked'}
              getValueFromEvent={(checked: boolean) => (checked ? '1' : '')}
              getValueProps={(e: string) => ({ checked: !!e })}
            >
              <Checkbox />
            </Form.Item>
          )
        }}
      </Form.Item>

      <Form.Item noStyle shouldUpdate>
        {(form: FormInstance & { prefixName: string[] }) => {
          return (
            <Form.Item
              hidden={
                form.getFieldValue([
                  ...form.prefixName, field.name as string, 'action',
                ]) !==
                'require'
              }
              initialValue={true}
              labelCol={{ span: 6 }}
              label={t('form:logic.action.require')}
              valuePropName={'checked'}
              getValueFromEvent={(checked: boolean) => (checked ? '1' : '')}
              getValueProps={(e: string) => ({ checked: !!e })}
            >
              <Checkbox />
            </Form.Item>
          )
        }}
      </Form.Item>

      <Form.Item>
        <div style={{ textAlign: 'right' }}>
          <Popconfirm
            placement={'right'}
            title={t('type:confirmDelete')}
            okText={t('type:deleteNow')}
            okButtonProps={{ danger: true }}
            onConfirm={() => {
              remove(index)
            }}
          >
            <Button danger>
              <DeleteOutlined />
            </Button>
          </Popconfirm>
        </div>
      </Form.Item>
    </div>
  )
}